惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Cloudbric
Cloudbric
The Register - Security
The Register - Security
小众软件
小众软件
PCI Perspectives
PCI Perspectives
G
Google Developers Blog
AI
AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
量子位
TaoSecurity Blog
TaoSecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Full Disclosure
N
Netflix TechBlog - Medium
博客园_首页
Last Week in AI
Last Week in AI
A
Arctic Wolf
B
Blog RSS Feed
J
Java Code Geeks
C
Cybersecurity and Infrastructure Security Agency CISA
I
InfoQ
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
MyScale Blog
MyScale Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
有赞技术团队
有赞技术团队
S
Schneier on Security
L
Lohrmann on Cybersecurity
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
S
Security @ Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Vercel News
Vercel News
博客园 - 司徒正美
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN
A
About on SuperTechFans

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
What Should You Use Now That Google Interview Warmup Is Dead?
Ankit Singh · 2026-05-17 · via DEV Community

How I built 10xInterview.com solo — the dual-backend AI router, SSE token streaming, pgvector for embeddings, and webhook-driven billing. A Google Interview Warmup alternative.

When Google quietly retired Interview Warmup, a small but useful tool that let you record spoken interview answers and get AI feedback, I had two reactions. First: that's a shame, I used it. Second: I can build something better.

A year later, 10xInterview is live. Solo-built. One Go binary, one React SPA, one Postgres database, one shell script to deploy the whole thing.

This post is the engineering walkthrough — the four architectural decisions that made the codebase still feel small after a year of shipping features. If you're building anything AI-heavy as a solo founder, the patterns below are the ones I'd reach for again.

TL;DR: Boring stack, opinionated routing, stream everything, single database, webhooks as source of truth. Code samples inline.


The stack (deliberately boring)

Backend     Go 1.23, Chi router, single binary
Database    Postgres 17 + pgvector
Frontend    React 19, Vite, TanStack Query, shadcn/ui
AI          Vertex AI (free tier) + Gemini API (Pro)
Speech      Google STT + TTS
Infra       Cloud Run x2, Cloud SQL, HTTPS LB, Secret Manager
Billing     Razorpay Subscriptions (webhook-driven)
Deploy      One idempotent shell script

Enter fullscreen mode Exit fullscreen mode

No microservices. No event bus. No Kafka. No Redis (yet). No second database for vectors. No SSR. No global client store. No framework-of-the-month.

The constraint of "one person has to be able to operate this at 3am" drove every choice.


Decision 1: The dual-backend AI Router

Problem: Free users will exhaust an expensive LLM API instantly. But Pro users expect a meaningfully better experience. How do you serve both from the same handler code?

Solution: Every AI capability is an interface with two implementations. A Router struct picks at request time based on the plan in context.

// services/agent/router.go
type Reviewer interface {
    Review(ctx context.Context, in ReviewInput) (ReviewOutput, error)
}

type ReviewerRouter struct {
    Free Reviewer  // Vertex AI, Gemini 2.5 Flash
    Paid Reviewer  // Gemini API, stronger model
}

func (r *ReviewerRouter) Review(ctx context.Context, in ReviewInput) (ReviewOutput, error) {
    if auth.PlanFromContext(ctx) == auth.PlanPro {
        return r.Paid.Review(ctx, in)
    }
    return r.Free.Review(ctx, in)
}

Enter fullscreen mode Exit fullscreen mode

Handlers never check the plan. They never know which backend they hit. The auth middleware puts the plan in context; the router does the right thing.

Three benefits compound from this pattern:

  1. Graceful degradation by default. If GEMINI_API_KEY isn't set, the Paid implementation is nil and the router falls back to Free. Pro users get the cheaper model silently rather than a 500. A rotated key doesn't take the site down.
  2. Local dev with zero credentials. If AGENT_ENABLED=false, every agent becomes a deterministic stub returning canned data. New contributors clone the repo, go run ./cmd/server, and have a working app without ever touching Google Cloud.
  3. Adding a third tier is a struct field. When (if) I add an Enterprise tier with a different model, it's r.Enterprise = ... and one extra case in the dispatch. No handler changes. I have ~6 of these routers now — reviewer, generator, designer, live interviewer, explainer, resume parser. The pattern paid for itself by Router #2.

Decision 2: SSE token streaming over a tiny in-process broker

Problem: The first version of answer review was a synchronous REST call. Upload audio → wait 8–14 seconds → render the JSON response. It worked. It also felt awful.

Solution: Stream the LLM output token-by-token over Server-Sent Events. The frontend renders score and feedback as they generate.

The architecture is intentionally minimal:

// Broker.go — ~150 lines total
type Broker struct {
    mu   sync.RWMutex
    subs map[string][]chan Event  // submissionID -> subscribers
}

func (b *Broker) Subscribe(id string) (<-chan Event, func()) {
    ch := make(chan Event, 32)
    b.mu.Lock()
    b.subs[id] = append(b.subs[id], ch)
    b.mu.Unlock()
    return ch, func() { /* unsubscribe + close */ }
}

func (b *Broker) Publish(id string, e Event) {
    b.mu.RLock()
    for _, ch := range b.subs[id] {
        select {
        case ch <- e:
        default: // drop if subscriber is slow
        }
    }
    b.mu.RUnlock()
}

Enter fullscreen mode Exit fullscreen mode

The HTTP handler:

func (h *Handler) StreamSubmission(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    flusher := w.(http.Flusher)

    events, cancel := h.broker.Subscribe(submissionID)
    defer cancel()

    for {
        select {
        case e := <-events:
            fmt.Fprintf(w, "data: %s\n\n", e.JSON())
            flusher.Flush()
            if e.Type == "complete" { return }
        case <-r.Context().Done():
            return
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

The frontend opens an EventSource:

const es = new EventSource(`/api/v1/submissions/${id}/stream`);
es.onmessage = (e) => {
  const event = JSON.parse(e.data);
  setReview((prev) => mergeEvent(prev, event));
};

Enter fullscreen mode Exit fullscreen mode

Total wall-clock time is identical to the synchronous version. Perceived time is roughly half. Users start reading feedback at token ~30 instead of waiting for token ~400.

Cost of this UX upgrade: ~200 lines of Go, ~40 lines of TS, zero new infrastructure. No Redis, no NATS, no Kafka. Pub/sub inside a monolith doesn't need a message queue.

Caveat: This pattern only works because the backend is a single Cloud Run instance during a session — submissions and subscribers live in the same process. The moment I need to scale to multiple instances per user session, I'll either swap the broker for Redis pub/sub or pin sessions with sticky cookies. Not today's problem.


Decision 3: Postgres + pgvector instead of a vector database

Problem: Two features need vector similarity:

  1. Question deduplication — when admins or the AI bulk-generate new questions, "What is a closure?" worded 14 different ways shouldn't all enter the catalog.
  2. Resume-aware recommendations — given an uploaded resume's embedding, surface the questions most similar to the candidate's stated skills. The almost-mistake: I nearly reached for Pinecone.

The actual solution: CREATE EXTENSION vector; in a migration, one extra column on the questions table, one extra column on resumes, done.

-- migrations/0007_add_embeddings.sql
CREATE EXTENSION IF NOT EXISTS vector;

ALTER TABLE questions
  ADD COLUMN embedding vector(768);

CREATE INDEX questions_embedding_idx
  ON questions
  USING hnsw (embedding vector_cosine_ops);

Enter fullscreen mode Exit fullscreen mode

The dedup check before insert:

SELECT id, title, 1 - (embedding <=> $1) AS similarity
FROM questions
WHERE topic_id = $2
ORDER BY embedding <=> $1
LIMIT 1;
-- reject if similarity > 0.92

Enter fullscreen mode Exit fullscreen mode

The recommendation query:

SELECT q.id, q.title, q.topic_id
FROM questions q
JOIN topics t ON t.id = q.topic_id
WHERE t.id = ANY($2)  -- relevant topics from resume
ORDER BY q.embedding <=> $1  -- resume embedding
LIMIT 20;

Enter fullscreen mode Exit fullscreen mode

One database. One backup. One thing to monitor. One connection pool.

The argument against pgvector is usually "it doesn't scale past N vectors." For 10xInterview's workload — currently ~50k question embeddings, growing slowly — that ceiling is years away. The day it stops being the right call, swapping it out is a localized refactor in one file. The optionality is preserved.

If you're at sub-1M vectors and you're considering a separate vector DB: try pgvector first. You'll likely never need to migrate.


Decision 4: Webhooks are the single source of truth for billing

Problem: Billing bugs are the worst bugs. Users pay, the system doesn't notice, support tickets pile up. Trust evaporates.

The rule: The checkout endpoint never marks a user Pro. Only webhooks do.

POST /api/v1/billing/checkout   →  Mints Razorpay subscription, returns IDs.
                                   Does NOT change user.plan.

POST /webhooks/razorpay         →  Razorpay-initiated. HMAC verified.
                                   Inserts to payment_events.
                                   Updates user.plan ONLY if insert succeeded.

Enter fullscreen mode Exit fullscreen mode

The webhook handler in full:

func (h *Handler) RazorpayWebhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    sig := r.Header.Get("X-Razorpay-Signature")

    if !verifyHMAC(body, sig, h.cfg.RazorpayWebhookSecret) {
        http.Error(w, "bad signature", 401)
        return
    }

    var evt RazorpayEvent
    if err := json.Unmarshal(body, &evt); err != nil {
        http.Error(w, "bad json", 400); return
    }

    // The idempotency key
    err := h.db.InsertPaymentEvent(r.Context(), PaymentEvent{
        ProviderEventID: evt.ID,    // UNIQUE constraint
        Type:            evt.Event,
        Payload:         body,
    })
    if errors.Is(err, ErrDuplicate) {
        w.WriteHeader(200)  // already processed; no-op
        return
    }
    if err != nil {
        http.Error(w, "db error", 500); return
    }

    switch evt.Event {
    case "subscription.activated", "subscription.charged":
        h.db.UpgradeUser(r.Context(), evt.Subscription.UserID, "pro", evt.Subscription.EndAt)
    case "subscription.cancelled", "subscription.halted":
        // Don't downgrade immediately. Let it expire naturally.
        h.db.MarkCancelled(r.Context(), evt.Subscription.UserID)
    }

    w.WriteHeader(200)
}

Enter fullscreen mode Exit fullscreen mode

Four properties this gives you:

  1. Idempotent by construction. Razorpay can retry the same webhook 10 times. The unique constraint on provider_event_id means the first one wins and the rest are no-ops. No application-level dedup logic required.
  2. Cookie-auth-free endpoint. The webhook route is mounted at the root, outside the cookie middleware. Razorpay doesn't bring cookies, and there's nothing for an attacker to forge — the HMAC is the auth.
  3. Audit trail for free. Every payment event ever received is in payment_events. Disputes, refunds, "why was I charged" tickets — all answerable with one SQL query.
  4. No nightly cron. Downgrade-on-expiry is handled by the auth middleware: when a Pro user's pro_until is in the past on an incoming request, the middleware downgrades them in-flight. State is always correct because state is always checked, not swept. One operational note: Razorpay Subscription Plans only support INR without a support-ticket process to enable multi-currency. So the pricing page shows USD as a display-only conversion for non-Indian visitors (detected via browser timezone) and charges INR. Small detail. Saves a lot of confused support tickets.

The deploy story

PROJECT_ID=my-prj DOMAIN=10xinterview.com ADMIN_EMAILS=me@x.com \
  GOOGLE_CLIENT_ID=GOOGLE_CLIENT_SECRET=\
  ./deploy.sh

Enter fullscreen mode Exit fullscreen mode

That's a fresh GCP project to a live deployment in ~12 minutes. deploy.sh provisions:

  • Cloud SQL instance with pgvector enabled
  • Two Cloud Run services (api + web)
  • HTTPS Load Balancer with a Google-managed cert
  • Cloud Run Job for the embeddings backfill
  • Secret Manager entries for every credential Every step uses describe-or-create. Re-running is safe. Mid-script failures are recoverable just by running again.

Razorpay and Gemini API keys are mounted only after their secrets exist — a first-run deploy without them is fine. The AI features degrade to free-tier behavior; billing endpoints return 503 Service Unavailable until you add the key.

Idempotent deploys + graceful degradation = a one-person SaaS you can actually sleep through the night with.


What I'd do differently

A year in, three things I'd change if I started over:

  1. Adopt sqlc earlier. I started with hand-written database/sql and migrated to sqlc around month 4. The codebase would be cleaner if I'd started there.
  2. Use the same model for embeddings on both tiers. I briefly tried using a stronger embedding model for Pro users. The recall difference wasn't worth the cost and the dual-embedding bookkeeping was a nightmare. Now both tiers use the same Vertex embedding model.
  3. Skip the design system experimentation. I tried Tailwind UI, then Park UI, then shadcn/ui. Should've started with shadcn/ui. Lost two weekends.

If you used to use Google's Interview Warmup

A quick aside, because I keep seeing this question on Reddit.

Google retired Interview Warmup — the experimental tool from Grow with Google that let you record an answer and get AI feedback. A lot of people liked it. It's gone.

10xInterview isn't a clone. The mapping:

  • What Warmup did: record a spoken answer → get analysis on insights mentioned, vocabulary, talking points.
  • What 10xInterview overlaps on: record a spoken answer → get a 0–100 score and specific suggestions, streamed in real time.
  • What 10xInterview adds: curated question library by topic and skill, mock interviews with aggregated final reports, a live AI interviewer that asks adaptive follow-ups, resume-aware question recommendations, on-demand explanations with mermaid diagrams.
  • What it doesn't do yet: STAR-format behavioural scoring (on the near-term roadmap). Not affiliated with Google. Just filling a gap they left.

The free tier covers the core "record and get scored" loop with weekly limits. The Pro tier unlocks the live interactive interviewer — the harder pressure test that Warmup never offered.


Try it

10xinterview.com

Sign in with Google. Record one answer. The free tier gives you the full library, scored answers, and mock interviews without paying anything.

If you spot an architectural choice in this post you'd have made differently — I want to hear it. Reply here or grab my email from the site.


Built with Go, React, Postgres, and Google Cloud. The entire backend is small enough to read in an afternoon. The Router pattern, SSE broker, pgvector queries, and webhook handler shown above are the load-bearing pieces — everything else is glue.

*If this was useful, a ❤️ helps the post reach more devs. The next post in this series will be a deep-dive on the SSE broker pattern — implementing it in Go without external dependencies, edge cases I hit, and when you should reach fo