<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Syed Anzar</title>
    <description>The latest articles on DEV Community by Syed Anzar (@syed_anzar).</description>
    <link>https://dev.to/syed_anzar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4069010%2Fda8f710a-f285-4309-8c69-770badbe9c7b.jpg</url>
      <title>DEV Community: Syed Anzar</title>
      <link>https://dev.to/syed_anzar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/syed_anzar"/>
    <language>en</language>
    <item>
      <title>Plane: The Open-Source Jira/Linear Alternative — What Actually Happens When You Self-Host a 61k-Star Project Management Platform</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Tue, 03 Nov 2026 13:40:34 +0000</pubDate>
      <link>https://dev.to/syed_anzar/plane-the-open-source-jiralinear-alternative-what-actually-happens-when-you-self-host-a-2158</link>
      <guid>https://dev.to/syed_anzar/plane-the-open-source-jiralinear-alternative-what-actually-happens-when-you-self-host-a-2158</guid>
      <description>&lt;h1&gt;
  
  
  Plane: The Open-Source Jira/Linear Alternative — What Actually Happens When You Self-Host a 61k-Star Project Management Platform
&lt;/h1&gt;

&lt;p&gt;You use Jira. Or Linear. Or ClickUp. Every day. Click "Create Issue," drag cards across a Kanban board, run a sprint retrospective. It works. Mostly.&lt;/p&gt;

&lt;p&gt;But have you ever wondered what it takes to &lt;em&gt;run&lt;/em&gt; one of these things yourself? Not the SaaS version — the actual software, on your own infrastructure, your own data, zero vendor lock-in.&lt;/p&gt;

&lt;p&gt;Plane is a 61.9k-star open-source project management platform that replaces Jira, Linear, Monday, and ClickUp. Not a toy. A production-grade system with real-time collaboration, AI-powered workflows, a native MCP server for AI agents, and a microservice architecture running on PostgreSQL, Redis, RabbitMQ, and MinIO.&lt;/p&gt;

&lt;p&gt;Here's what happens under the hood when you self-host it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: 10 Services, One Platform
&lt;/h2&gt;

&lt;p&gt;Plane isn't a monolith. It's a distributed system disguised as a project management tool. Run &lt;code&gt;docker-compose up&lt;/code&gt; and you're spinning up &lt;strong&gt;ten distinct services&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;web&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Main React frontend — work items, cycles, pages, analytics&lt;/td&gt;
&lt;td&gt;React 19, React Router 7, MobX, Tailwind, Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;api&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Core Django REST API — all data operations&lt;/td&gt;
&lt;td&gt;Django 5.2, DRF, Celery, Channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;worker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Async background jobs (imports, exports, notifications)&lt;/td&gt;
&lt;td&gt;Celery + RabbitMQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;beat-worker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scheduled tasks (cleanup, reports, reminders)&lt;/td&gt;
&lt;td&gt;Celery Beat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;migrator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Schema migrations on deploy (runs once, exits)&lt;/td&gt;
&lt;td&gt;Django migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;live&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time WebSocket server (cursors, presence, live updates)&lt;/td&gt;
&lt;td&gt;Node.js, WebSockets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;proxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SSL termination, routing, cert management (Caddy)&lt;/td&gt;
&lt;td&gt;Caddy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Public sharing — publish projects/views without login&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;admin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Instance administration (billing, licensing, SSO)&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;silo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Third-party integrations (GitHub, GitLab, Slack OAuth/webhooks)&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus &lt;strong&gt;five infrastructure dependencies&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL 15.7+&lt;/strong&gt; — primary data store (&lt;code&gt;max_connections=1000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valkey 7.2&lt;/strong&gt; (Redis-compatible) — cache, sessions, real-time collab state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RabbitMQ 3.13&lt;/strong&gt; — message queue for all async work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MinIO/S3&lt;/strong&gt; (via &lt;code&gt;pgsty/silo&lt;/code&gt;) — file uploads, attachments, exports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenSearch&lt;/strong&gt; (optional) — full-text search indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't "Django + React." This is a &lt;strong&gt;platform&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happens When You Create a Work Item
&lt;/h2&gt;

&lt;p&gt;Click "New Issue" in the React frontend. Here's the path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend (MobX store)&lt;/strong&gt; → optimistic UI update, instant feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API call&lt;/strong&gt; → &lt;code&gt;POST /api/v1/workspaces/{slug}/projects/{project_id}/work-items/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Django viewset&lt;/strong&gt; → validation, permissions, business logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; → &lt;code&gt;INSERT&lt;/code&gt; into &lt;code&gt;work_items&lt;/code&gt; with all relations (project, assignee, state, cycle, module)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis cache invalidation&lt;/strong&gt; → affected queries purged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RabbitMQ message&lt;/strong&gt; → &lt;code&gt;work_item.created&lt;/code&gt; event published&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker picks up&lt;/strong&gt; → sends notifications, updates search index (OpenSearch), triggers webhooks (Silo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live service&lt;/strong&gt; → WebSocket broadcast to all connected clients in that workspace → real-time UI update for collaborators&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero polling. Zero manual refresh. The WebSocket connection stays open. When your teammate moves a card, you see it move instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-Time Collaboration: The &lt;code&gt;live&lt;/code&gt; Service
&lt;/h2&gt;

&lt;p&gt;Most "real-time" tools fake it with long-polling or frequent AJAX. Plane runs a dedicated &lt;strong&gt;Node.js WebSocket server&lt;/strong&gt; (&lt;code&gt;apps/live&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────────────────────────────────│
│  Browser A  │◄──────────────────────────────────────────────────────────────────────────────────────││   live svc  │
└──────────────────────────────────────────────────────────────────────────────────────────────│└────│
───────────────────────────────────────────────────────────────────────────────────────────────────────────────│ Redis Pub/Sub
┌─────────────────────────────────────────────────────────────────────────────────────────────────────│
│  Browser B  │◄──────────────────────────────────────────────────────────────────────────────────││   live svc  │
└──────────────────────────────────────────────────────────────────────────────────────│└────│
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Presence&lt;/strong&gt;: Who's online, where their cursor is, what they're viewing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimistic sync&lt;/strong&gt;: Local changes broadcast immediately; server resolves conflicts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: Multiple &lt;code&gt;live&lt;/code&gt; replicas behind Redis Pub/Sub — horizontal scaling works&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;live&lt;/code&gt; service doesn't touch PostgreSQL. Pure real-time layer. Django handles persistence; &lt;code&gt;live&lt;/code&gt; handles &lt;em&gt;now&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background Jobs: Celery + RabbitMQ Done Right
&lt;/h2&gt;

&lt;p&gt;Import 10,000 issues from Jira. You don't wait. The API returns &lt;code&gt;202 Accepted&lt;/code&gt; immediately.&lt;/p&gt;

&lt;p&gt;What happens next:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API enqueues &lt;code&gt;jira_import_job&lt;/code&gt; to RabbitMQ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker&lt;/strong&gt; (Celery) picks it up — runs in isolation, no request timeout&lt;/li&gt;
&lt;li&gt;Chunks the import, processes batches, updates progress in Redis&lt;/li&gt;
&lt;li&gt;Each created work item emits &lt;code&gt;work_item.created&lt;/code&gt; → RabbitMQ → other workers → notifications, search indexing, webhooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beat worker&lt;/strong&gt; runs nightly cleanup: expired sessions, stale locks, old audit logs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is &lt;strong&gt;durable execution&lt;/strong&gt;. Worker crashes mid-import? RabbitMQ requeues. The job completes. No lost work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Self-Hosting: Data Sovereignty Without the DevOps Nightmare
&lt;/h2&gt;

&lt;p&gt;Plane's Docker Compose file is &lt;strong&gt;177 lines&lt;/strong&gt;. One command brings up the entire stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (simplified)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./apps/api&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;plane-db&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;plane-redis&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./apps/api/.env&lt;/span&gt;
  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./apps/api&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./bin/docker-entrypoint-worker.sh&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;plane-db&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;plane-redis&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;plane-mq&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;beat-worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./bin/docker-entrypoint-beat.sh&lt;/span&gt;
  &lt;span class="na"&gt;migrator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;           &lt;span class="c1"&gt;# runs once, exits&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./bin/docker-entrypoint-migrator.sh&lt;/span&gt;
  &lt;span class="na"&gt;live&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./apps/live&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./apps/proxy&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;plane-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15.7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres -c 'max_connections=1000'&lt;/span&gt;
  &lt;span class="na"&gt;plane-redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;valkey/valkey:7.2.11-alpine&lt;/span&gt;
  &lt;span class="na"&gt;plane-mq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rabbitmq:3.13.6-management-alpine&lt;/span&gt;
  &lt;span class="na"&gt;plane-minio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/pgsty/silo:RELEASE.2026-09-16T00-00-00Z&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Details that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caddy&lt;/strong&gt; handles automatic Let's Encrypt certificates — set &lt;code&gt;CERT_EMAIL&lt;/code&gt; and it works&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrator&lt;/strong&gt; runs &lt;code&gt;restart: no&lt;/code&gt; — applies schema changes on deploy, then exits cleanly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valkey&lt;/strong&gt; (not Redis) — drop-in replacement, better memory efficiency, same protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pgsty/silo&lt;/strong&gt; — MinIO fork optimized for single-node deployments, lighter resource usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL &lt;code&gt;max_connections=1000&lt;/code&gt;&lt;/strong&gt; — tuned for connection pooling under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Air-gapped deployment supported. No internet required after initial pull. Helm charts for Kubernetes. Prime CLI for multi-instance management.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech Stack: Battle-Tested, Not Trendy
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Django 5.2 LTS&lt;/td&gt;
&lt;td&gt;Mature ORM, admin, auth, migrations, security built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Async&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Celery 5.5 + RabbitMQ&lt;/td&gt;
&lt;td&gt;Durable queues, retries, dead-letter, monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Realtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js + WebSockets&lt;/td&gt;
&lt;td&gt;Separate service = no blocking Django workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React 19 + React Router 7&lt;/td&gt;
&lt;td&gt;Modern, fast, MobX for fine-grained reactivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL 15/16&lt;/td&gt;
&lt;td&gt;ACID, JSONB, full-text search, proven at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Valkey 7.2&lt;/td&gt;
&lt;td&gt;Redis API, better memory, active development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S3/MinIO&lt;/td&gt;
&lt;td&gt;Object storage standard, any provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monorepo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Turborepo + pnpm&lt;/td&gt;
&lt;td&gt;Shared types, incremental builds, cached tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No custom consensus algorithms. No exotic databases. Just solid engineering with the right tool for each job.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI &amp;amp; Extensibility: Not an Afterthought
&lt;/h2&gt;

&lt;p&gt;Plane has a &lt;strong&gt;native MCP (Model Context Protocol) server&lt;/strong&gt; built in. AI agents can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create/update work items&lt;/li&gt;
&lt;li&gt;Query projects, cycles, analytics&lt;/li&gt;
&lt;li&gt;Run workflows&lt;/li&gt;
&lt;li&gt;Access docs and pages
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;MCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;example&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_work_item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a new work item in Plane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"issue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"epic"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"state_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;strong&gt;Slack &lt;code&gt;@Plane&lt;/code&gt; mentions&lt;/strong&gt; — type &lt;code&gt;@Plane create issue "Fix login bug" in Project X&lt;/code&gt; and it happens. GitHub/GitLab two-way sync. Webhook framework for custom integrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration: You're Not Trapped
&lt;/h2&gt;

&lt;p&gt;Jira → Plane importer: issues, sprints, epics, attachments, comments, users.&lt;br&gt;
Linear → Plane importer: preserves cycles, labels, relations.&lt;br&gt;
GitHub → Plane: bidirectional issue sync.&lt;/p&gt;

&lt;p&gt;One-way door? No. Export your data anytime. PostgreSQL dump. S3 bucket. You own it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You're Signing Up For
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reality&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource baseline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~4 GB RAM minimum (Postgres + Redis + RabbitMQ + 4 API workers + live + frontend)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker-compose pull &amp;amp;&amp;amp; docker-compose up -d&lt;/code&gt; for updates. Migrator handles schema.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backups&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pg_dump&lt;/code&gt; + S3 sync + Redis RDB + MinIO bucket versioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Add API workers, live replicas, RabbitMQ consumers. Postgres read replicas for heavy read workloads.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AGPL-3.0 — if you modify and expose publicly, share changes. Self-hosted internal use: no obligation.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why This Matters for Developers
&lt;/h2&gt;

&lt;p&gt;You spend years learning React, Django, PostgreSQL, Redis, RabbitMQ, WebSockets, Docker, CI/CD. Plane &lt;strong&gt;is&lt;/strong&gt; the reference architecture for a modern full-stack application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to structure a Turborepo monorepo with shared TypeScript types across frontend/backend&lt;/li&gt;
&lt;li&gt;How to separate real-time concerns from transactional API&lt;/li&gt;
&lt;li&gt;How to design durable background job processing&lt;/li&gt;
&lt;li&gt;How to run PostgreSQL at &lt;code&gt;max_connections=1000&lt;/code&gt; with connection pooling&lt;/li&gt;
&lt;li&gt;How to do zero-downtime migrations with a dedicated migrator service&lt;/li&gt;
&lt;li&gt;How to build a plugin/integration system (Silo) that doesn't couple to core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Studying Plane's codebase teaches you more about systems engineering than most tutorials.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Plane replaces Jira/Linear/ClickUp with a self-hosted, open-source platform that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs on &lt;strong&gt;standard infrastructure&lt;/strong&gt; (Postgres, Redis, RabbitMQ, S3)&lt;/li&gt;
&lt;li&gt;Uses a &lt;strong&gt;microservice architecture&lt;/strong&gt; (10 services, each with a clear responsibility)&lt;/li&gt;
&lt;li&gt;Delivers &lt;strong&gt;true real-time collaboration&lt;/strong&gt; via a dedicated WebSocket service&lt;/li&gt;
&lt;li&gt;Handles &lt;strong&gt;durable async execution&lt;/strong&gt; via Celery + RabbitMQ&lt;/li&gt;
&lt;li&gt;Includes &lt;strong&gt;AI/agent readiness&lt;/strong&gt; via native MCP server&lt;/li&gt;
&lt;li&gt;Gives you &lt;strong&gt;full data ownership&lt;/strong&gt; — AGPL-3.0, air-gapped ready, one-command deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time you drag a card in Jira, remember: there's a whole distributed system making that feel instant. Plane lets you &lt;em&gt;own&lt;/em&gt; that system.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;code&gt;makeplane/plane&lt;/code&gt; (61.9k ⭐, AGPL-3.0)&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;code&gt;developers.plane.so/self-hosting/overview&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Try it:&lt;/strong&gt; &lt;code&gt;docker-compose up&lt;/code&gt; — 2 minutes to a running instance&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>projectmanagement</category>
      <category>django</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>How HTTP/3 and QUIC Actually Work: UDP, Streams, and Why 0-RTT Changes Everything</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:39:52 +0000</pubDate>
      <link>https://dev.to/syed_anzar/how-http3-and-quic-actually-work-udp-streams-and-why-0-rtt-changes-everything-1jnf</link>
      <guid>https://dev.to/syed_anzar/how-http3-and-quic-actually-work-udp-streams-and-why-0-rtt-changes-everything-1jnf</guid>
      <description>&lt;h1&gt;
  
  
  How HTTP/3 and QUIC Actually Work: UDP, Streams, and Why 0-RTT Changes Everything
&lt;/h1&gt;

&lt;p&gt;You type &lt;code&gt;https://example.com&lt;/code&gt; and hit Enter. Your browser opens a connection, negotiates TLS, sends an HTTP request, gets a response. That's the mental model most developers carry.&lt;/p&gt;

&lt;p&gt;It's incomplete. Since 2022, there's a real chance that connection isn't TCP at all — it's QUIC. And QUIC doesn't just swap TCP for UDP. It restructures how transport, security, and application semantics interact at the packet level.&lt;/p&gt;

&lt;p&gt;This article explains what actually happens when your browser speaks HTTP/3, from the wire format up to the stream multiplexing that makes head-of-line blocking a thing of the past.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Short Version: What Changed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;HTTP/2 (TCP + TLS)&lt;/th&gt;
&lt;th&gt;HTTP/3 (QUIC)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;TCP (reliable, ordered byte stream)&lt;/td&gt;
&lt;td&gt;QUIC over UDP (reliable, ordered &lt;strong&gt;per stream&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;TLS 1.3 layered &lt;em&gt;over&lt;/em&gt; TCP&lt;/td&gt;
&lt;td&gt;TLS 1.3 integrated &lt;em&gt;into&lt;/em&gt; QUIC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiplexing&lt;/td&gt;
&lt;td&gt;HTTP/2 frames on one TCP stream&lt;/td&gt;
&lt;td&gt;QUIC native streams (independent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handshake&lt;/td&gt;
&lt;td&gt;TCP SYN → TLS ClientHello → ... (2–3 RTT)&lt;/td&gt;
&lt;td&gt;QUIC Initial + TLS in 1 RTT (or 0-RTT resume)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection ID&lt;/td&gt;
&lt;td&gt;4-tuple (src IP, src port, dst IP, dst port)&lt;/td&gt;
&lt;td&gt;Opaque Connection ID (survives IP/port change)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Head-of-line blocking&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt; — one lost packet stalls all streams&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; — loss only blocks the affected stream&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The headline feature: QUIC moves reliability and ordering from the connection level to the stream level. A lost packet on stream 4 doesn't stall stream 0, 8, or 12.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Wire Format: Packets, Frames, and Encryption Levels
&lt;/h2&gt;

&lt;p&gt;QUIC doesn't send "data." It sends packets, each carrying frames. The frames belong to streams. The packets live inside UDP datagrams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Encryption Levels = Four Packet Types
&lt;/h3&gt;

&lt;p&gt;Every QUIC packet has an encryption level. This determines which frames it can carry:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Packet Type&lt;/th&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Encryption Level&lt;/th&gt;
&lt;th&gt;Frames Allowed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Initial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long&lt;/td&gt;
&lt;td&gt;Initial keys (derived from client's random DCID)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CRYPTO&lt;/code&gt;, &lt;code&gt;ACK&lt;/code&gt;, &lt;code&gt;PADDING&lt;/code&gt;, &lt;code&gt;PING&lt;/code&gt;, &lt;code&gt;CONNECTION_CLOSE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0-RTT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long&lt;/td&gt;
&lt;td&gt;0-RTT keys (resumed session)&lt;/td&gt;
&lt;td&gt;Everything Initial allows + &lt;code&gt;STREAM&lt;/code&gt;, &lt;code&gt;MAX_DATA&lt;/code&gt;, &lt;code&gt;NEW_CONNECTION_ID&lt;/code&gt;, &lt;code&gt;DATAGRAM&lt;/code&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Handshake&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long&lt;/td&gt;
&lt;td&gt;Handshake keys&lt;/td&gt;
&lt;td&gt;Everything 0-RTT allows + &lt;code&gt;NEW_TOKEN&lt;/code&gt;, &lt;code&gt;PATH_RESPONSE&lt;/code&gt;, &lt;code&gt;HANDSHAKE_DONE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1-RTT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Short&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1-RTT keys (established)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;All frames&lt;/strong&gt; — this is steady-state traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The long header (used during handshake) is self-describing: version, both Connection IDs with lengths, explicit packet type. The short header (1-RTT) strips all that — just a header byte, Destination CID, packet number, and two flag bits (spin bit, key phase). Header protection encrypts the packet number and reserved bits so on-wire they look like noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frames: The Actual Payload
&lt;/h3&gt;

&lt;p&gt;Frames are typed. The workhorse is STREAM — carries application data with a stream ID, offset, length, and FIN bit. Others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ACK — acknowledges received packet numbers (with ECN support)&lt;/li&gt;
&lt;li&gt;CRYPTO — carries TLS handshake messages (yes, TLS lives inside QUIC frames)&lt;/li&gt;
&lt;li&gt;MAX_DATA / MAX_STREAM_DATA / MAX_STREAMS — flow control&lt;/li&gt;
&lt;li&gt;NEW_CONNECTION_ID / RETIRE_CONNECTION_ID — connection migration&lt;/li&gt;
&lt;li&gt;PATH_CHALLENGE / PATH_RESPONSE — path validation for migration&lt;/li&gt;
&lt;li&gt;DATAGRAM (RFC 9221) — unreliable, unordered data for real-time media&lt;/li&gt;
&lt;li&gt;CONNECTION_CLOSE — terminates connection (with error code)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiplexing happens at the frame level. One packet can carry a STREAM frame for stream 0, another STREAM for stream 4, and an ACK frame — all at once. There's no "this packet belongs to stream X." The frames inside decide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coalescing ≠ Multiplexing
&lt;/h3&gt;

&lt;p&gt;Multiple QUIC packets (even from different packet number spaces) can be concatenated into one UDP datagram. This is coalescing — distinct from multiplexing. A single datagram can do both: coalesce an Initial + Handshake packet, each carrying frames from different streams.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connection IDs: The Connection That Outlives Its Address
&lt;/h2&gt;

&lt;p&gt;TCP identifies a connection by the 4-tuple. Change IP or port (Wi-Fi to cellular, NAT rebinding) and the connection dies.&lt;/p&gt;

&lt;p&gt;QUIC uses Connection IDs (CIDs). Each endpoint chooses the CIDs it wants to receive and advertises them in the Source CID field of packets it sends. The peer copies that into the Destination CID of return packets.&lt;/p&gt;

&lt;p&gt;Key insight: the Destination CID names the recipient, not the sender. On a client-to-server packet, the DCID is a value the server chose. The SCID is a value the client chose.&lt;/p&gt;

&lt;p&gt;The client's very first Initial packet has no server CID yet, so it generates a random DCID (8+ bytes). This random value also seeds the Initial encryption keys. Once the server replies with its own SCID, the client adopts that as its new DCID and never uses the random one again.&lt;/p&gt;

&lt;p&gt;Endpoints issue multiple CIDs over a connection's life (NEW_CONNECTION_ID frames). A migrating client switches to an unused CID, preventing passive observers from linking old and new paths by a constant CID.&lt;/p&gt;

&lt;p&gt;Load balancers exploit this: they encode routing info in the CID they hand out, so later packets are steered to the right server without deep packet inspection.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Handshake: 1-RTT by Default, 0-RTT on Resume
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TCP + TLS 1.3: 2 RTT Minimum
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client                    Server
  SYN ──────────────────→
  ←────────────────── SYN+ACK
  ClientHello ─────────→
  ←──────── ServerHello, Certificate, Finished
  Finished ───────────→
  Application Data ────→
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two round trips before application data flows (TCP handshake + TLS handshake). TCP Fast Open and TLS early data exist but are hampered by middleboxes.&lt;/p&gt;

&lt;h3&gt;
  
  
  QUIC: 1 RTT (New Session)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client                    Server
  Initial (CH) ────────→
  ←──────── Initial (SH, Cert, Finished)
  Handshake (Finished) →
  1-RTT (App Data) ────→
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The QUIC Initial packet is the ClientHello. The server's Initial response carries ServerHello, Certificate, and Finished. The client's Handshake packet carries its Finished. Application data flows on 1-RTT packets immediately after.&lt;/p&gt;

&lt;h3&gt;
  
  
  0-RTT: Send Data Before the Server Replies
&lt;/h3&gt;

&lt;p&gt;If the client has a session ticket from a previous connection (containing server transport parameters and TLS resumption PSK), it can send 0-RTT packets with application data alongside its Initial packet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client                    Server
  Initial + 0-RTT (App) →
  ←──────── Initial (SH, Cert, Finished)
  Handshake (Finished) →
  1-RTT (App Data) ────→
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch: 0-RTT provides no replay protection. A captured 0-RTT packet can be resent by an attacker, and the server will process it again. HTTP/3 mitigates this via RFC 9221 — only idempotent requests (GET, HEAD) should use 0-RTT, and servers must implement anti-replay (single-use tokens, timestamp windows).&lt;/p&gt;




&lt;h2&gt;
  
  
  Streams: Independent, Ordered, Flow-Controlled
&lt;/h2&gt;

&lt;p&gt;Each QUIC stream is an independent, reliable, ordered byte stream. Streams have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stream ID — 62-bit integer. Client-initiated bidirectional: 0, 4, 8... Server-initiated bidirectional: 1, 5, 9... Client-initiated unidirectional: 2, 6, 10... Server-initiated unidirectional: 3, 7, 11...&lt;/li&gt;
&lt;li&gt;Offset — byte position within the stream (like TCP sequence number but per-stream)&lt;/li&gt;
&lt;li&gt;FIN bit — marks end of stream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flow control is per-stream AND connection-wide. MAX_STREAM_DATA limits bytes on a single stream; MAX_DATA limits total bytes across all streams. MAX_STREAMS limits concurrent stream count. This prevents a runaway stream from starving others.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Head-of-Line Blocking
&lt;/h3&gt;

&lt;p&gt;TCP delivers bytes in strict sequence order. If packet N is lost, packet N+1 sits in the receive buffer until N is retransmitted — even if N+1 belongs to a completely different logical request.&lt;/p&gt;

&lt;p&gt;QUIC delivers per-stream. If stream 4 loses a packet, stream 0, 8, and 12 keep delivering data to the application. The transport layer handles retransmission for stream 4 independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conceptual: HTTP/3 request streams are independent
# Stream 0: GET /api/user → HEADERS + DATA frames
# Stream 4: GET /api/posts → HEADERS + DATA frames
# Stream 8: GET /api/comments → HEADERS + DATA frames
&lt;/span&gt;
&lt;span class="c1"&gt;# If stream 4's packet is lost:
# - Stream 0 and 8 continue processing
# - Only stream 4's application callback waits
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  HTTP/3 Mapping: Requests = Bidirectional Streams
&lt;/h2&gt;

&lt;p&gt;HTTP/3 maps each request-response pair to a single client-initiated bidirectional QUIC stream (stream 0, 4, 8...). The request goes on the stream; the response comes back on the same stream.&lt;/p&gt;

&lt;p&gt;Two unidirectional control streams carry connection-level frames:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client to Server: SETTINGS, GOAWAY, CANCEL_PUSH&lt;/li&gt;
&lt;li&gt;Server to Client: SETTINGS, GOAWAY, PUSH_PROMISE, MAX_PUSH_ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;QPACK replaces HPACK. HPACK assumes in-order header delivery (true for HTTP/2 on TCP). QUIC doesn't guarantee cross-stream order. QPACK uses dedicated unidirectional streams for encoder/decoder state updates, while encoded header blocks reference static state without modifying it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stream Types in HTTP/3
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stream Type&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request stream&lt;/td&gt;
&lt;td&gt;Bidirectional (client-initiated)&lt;/td&gt;
&lt;td&gt;HTTP request + response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control stream (client)&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;SETTINGS, GOAWAY, CANCEL_PUSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control stream (server)&lt;/td&gt;
&lt;td&gt;Unidirectional&lt;/td&gt;
&lt;td&gt;SETTINGS, GOAWAY, PUSH_PROMISE, MAX_PUSH_ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push stream&lt;/td&gt;
&lt;td&gt;Unidirectional (server-initiated)&lt;/td&gt;
&lt;td&gt;Server push responses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Connection Migration: Surviving Network Changes
&lt;/h2&gt;

&lt;p&gt;When your phone switches from Wi-Fi to cellular, the IP and port change. TCP dies. QUIC survives.&lt;/p&gt;

&lt;p&gt;Mechanism: the client sends packets from the new path with a new Destination CID (one the server issued via NEW_CONNECTION_ID). The server sees the CID, recognizes the connection, and validates the new path with PATH_CHALLENGE / PATH_RESPONSE frames (1 RTT). Once validated, traffic shifts to the new path.&lt;/p&gt;

&lt;p&gt;Only the client can actively migrate in QUIC v1. Servers can indicate a preferred address during handshake (e.g., move from a shared anycast IP to a dedicated server IP).&lt;/p&gt;

&lt;p&gt;Congestion control resets after migration — you're on a new path with unknown characteristics. This is why migration has a performance cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  0-RTT in Practice: The Replay Problem
&lt;/h2&gt;

&lt;p&gt;0-RTT is seductive — zero-round-trip resume. But it's a loaded gun.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attacker captures client's 0-RTT packet (POST /api/transfer {to: attacker, amount: 1000})
Attacker replays it to server
Server processes it again → money transferred twice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP/3 requires servers to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only accept idempotent methods in 0-RTT (GET, HEAD)&lt;/li&gt;
&lt;li&gt;Implement anti-replay: single-use tokens, timestamp validation, or bloom filters&lt;/li&gt;
&lt;li&gt;Reject 0-RTT if anti-replay state is unavailable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Developers using HTTP/3 libraries: don't assume 0-RTT is safe for mutations. Configure your client to only send safe methods in 0-RTT, or disable 0-RTT entirely for non-idempotent APIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Congestion Control: The Hidden Performance Variable
&lt;/h2&gt;

&lt;p&gt;QUIC specifies reliability and flow control but not congestion control. Implementations choose: CUBIC, BBR, NewReno, etc.&lt;/p&gt;

&lt;p&gt;This matters enormously. Cloudflare's QUIC uses CUBIC; their TCP uses BBR. In production benchmarks with 1% packet loss, Cloudflare's HTTP/3 lagged behind HTTP/2 because CUBIC collapses its congestion window aggressively on early loss, while BBR (rate-based) doesn't.&lt;/p&gt;

&lt;p&gt;Google's QUIC uses BBR — their HTTP/3 matches or beats HTTP/2 under loss.&lt;/p&gt;

&lt;p&gt;The lesson: QUIC performance doesn't equal protocol performance. It's implementation performance. When benchmarking, you're testing that specific QUIC stack's congestion control, not QUIC itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Performance: What the Data Shows
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;HTTP/3 vs HTTP/2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small payloads (&amp;lt;100 KB), no loss&lt;/td&gt;
&lt;td&gt;HTTP/3 ~12% faster (1-RTT handshake advantage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large payloads (&amp;gt;1 MB), no loss&lt;/td&gt;
&lt;td&gt;Roughly equivalent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Added loss (1%)&lt;/td&gt;
&lt;td&gt;Mixed: Google HTTP/3 ≈ HTTP/2; Cloudflare HTTP/3 worse (CUBIC vs BBR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Added latency (100 ms)&lt;/td&gt;
&lt;td&gt;HTTP/3 better on Google; worse on Facebook (implementation quirks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page Load Time (multi-object)&lt;/td&gt;
&lt;td&gt;HOL blocking removal has little measurable impact on Speed Index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection migration&lt;/td&gt;
&lt;td&gt;Works but resets congestion control — brief throughput dip&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Key findings from production studies (Cloudflare, Google, Facebook, APNIC, IMC 2021):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 1-RTT handshake advantage dominates for short-lived connections (&amp;lt;1 MB)&lt;/li&gt;
&lt;li&gt;For long-lived transfers, congestion control implementation matters more than protocol design&lt;/li&gt;
&lt;li&gt;HOL blocking removal rarely moves the needle on real page loads — most pages don't have enough concurrent streams hitting loss simultaneously&lt;/li&gt;
&lt;li&gt;Client implementation matters: Chrome's SSL verification adds ~20ms overhead vs command-line clients&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If You're a Backend Developer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enable HTTP/3 (ALPN h3) — it's standardized, widely supported, and free latency reduction for new connections&lt;/li&gt;
&lt;li&gt;Configure your QUIC stack's congestion control — BBRv2 or BBRv3 outperforms CUBIC under loss&lt;/li&gt;
&lt;li&gt;Set MAX_STREAMS and flow control windows appropriately — defaults may limit parallelism&lt;/li&gt;
&lt;li&gt;Implement 0-RTT anti-replay if you enable it — or disable 0-RTT for non-GET endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  If You're a Frontend Developer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Don't assume HTTP/3 is magic. The 1-RTT handshake helps first paint; HOL blocking removal helps only under loss with many concurrent resources&lt;/li&gt;
&lt;li&gt;Resource prioritization still matters. HTTP/3 has no priority tree (HTTP/2 priorities were dropped). Use fetchpriority and preload instead&lt;/li&gt;
&lt;li&gt;Connection pooling changes. QUIC connections are more expensive to create (crypto) but cheaper to maintain (no TCP state). Reuse aggressively&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  If You're Running Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;UDP traffic must pass. Corporate firewalls, load balancers, and DDoS mitigation often drop or rate-limit UDP. Test end-to-end&lt;/li&gt;
&lt;li&gt;CID routing. If you terminate QUIC at a load balancer, encode backend routing in the CID — otherwise migration breaks&lt;/li&gt;
&lt;li&gt;Monitor QUIC-specific metrics: handshake failure rate, 0-RTT acceptance rate, migration frequency, CID rotation rate&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Mental Model to Keep
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                        UDP DATAGRAM                          │
│  ┌──────────────────┐  ┌──────────────────┐                 │
│  │   QUIC Packet    │  │   QUIC Packet    │  (coalesced)    │
│  │  (Initial, long) │  │ (Handshake, long)│                 │
│  └────────┬─────────┘  └────────┬─────────┘                 │
│           │                     │                            │
│  ┌────────▼─────────────────────▼────────────────────────┐  │
│  │                    FRAMES                               │  │
│  │  CRYPTO(frame)  ACK(frame)  PADDING(frame)              │  │
│  │  STREAM(id=0, off=0, data="GET /")  STREAM(id=4, ...)   │  │
│  └────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QUIC is not "TCP over UDP." It's a new transport that integrates TLS, multiplexes at the frame level, identifies connections by CID instead of 4-tuple, and pushes reliability to the stream layer.&lt;/p&gt;

&lt;p&gt;The next time you see h3 in your browser's network tab, you'll know exactly what's happening on the wire — and why it's not just TCP with a different header.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RFC 9000 — QUIC Transport Protocol (the bible)&lt;/li&gt;
&lt;li&gt;RFC 9001 — QUIC TLS Integration&lt;/li&gt;
&lt;li&gt;RFC 9114 — HTTP/3&lt;/li&gt;
&lt;li&gt;RFC 9221 — QUIC DATAGRAM Extension&lt;/li&gt;
&lt;li&gt;Cloudflare Blog: "HTTP/3 vs HTTP/2 Performance" — real deployment data&lt;/li&gt;
&lt;li&gt;APNIC Blog: "Dissecting the Performance of Production QUIC" — independent benchmarks&lt;/li&gt;
&lt;li&gt;IMC 2021: "Dissecting Performance of Production QUIC" — academic analysis with root-cause visualization&lt;/li&gt;
&lt;li&gt;surajkjai.github.io/quic-http3-masque-guide — worked examples, packet diagrams, field-by-field breakdowns&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helped you build a correct mental model of HTTP/3, share it with a colleague who still thinks "QUIC is just TCP over UDP."&lt;/p&gt;

</description>
      <category>http3</category>
      <category>quic</category>
      <category>networking</category>
      <category>performance</category>
    </item>
    <item>
      <title>Trigger.dev: The Open-Source Durable Execution Platform That Replaces Cron, BullMQ, and Inngest for Long-Running TypeScript Jobs</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Sun, 01 Nov 2026 04:59:47 +0000</pubDate>
      <link>https://dev.to/syed_anzar/triggerdev-the-open-source-durable-execution-platform-that-replaces-cron-bullmq-and-inngest-for-33c3</link>
      <guid>https://dev.to/syed_anzar/triggerdev-the-open-source-durable-execution-platform-that-replaces-cron-bullmq-and-inngest-for-33c3</guid>
      <description>&lt;h1&gt;
  
  
  Trigger.dev: The Open-Source Durable Execution Platform That Replaces Cron, BullMQ, and Inngest for Long-Running TypeScript Jobs
&lt;/h1&gt;

&lt;p&gt;You've written the same background job pattern a dozen times. A user uploads a video. Your API returns 202 Accepted. Somewhere, a worker picks it up, transcodes it, uploads the result, sends an email. If the worker crashes halfway through, the user never gets notified. If the video takes 45 minutes, your serverless function times out at 10. So you split it into steps, add a queue, provision Redis, write retry logic, build a dashboard to see what's stuck. Congratulations - you've reinvented a job platform, badly.&lt;/p&gt;

&lt;p&gt;This is where most TypeScript teams live. They know BullMQ. They've heard of Temporal. They've tried Inngest. But they keep hitting the same walls: timeouts, no visibility, retry storms that charge customers twice, infrastructure they didn't sign up to operate.&lt;/p&gt;

&lt;p&gt;Trigger.dev solves this differently. It's not a queue library. It's not a workflow engine that forces you to rewrite code into deterministic activities. It's a &lt;strong&gt;durable execution platform&lt;/strong&gt; that runs your plain async TypeScript functions on managed compute, checkpoints them mid-execution using Linux CRIU, and resumes them exactly where they left off - hours, days, or weeks later - with zero infrastructure for you to manage.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Developers Get Wrong About Background Jobs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Myth: "I just need a queue."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A queue gives you &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;pop&lt;/code&gt;. It doesn't give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A job that survives a deploy, a crash, or a Kubernetes node rotation&lt;/li&gt;
&lt;li&gt;Automatic retries that don't redo work already done (idempotency keys + warm starts)&lt;/li&gt;
&lt;li&gt;A 14-day &lt;code&gt;wait.until()&lt;/code&gt; that costs zero compute while paused&lt;/li&gt;
&lt;li&gt;Realtime progress streaming to your React frontend&lt;/li&gt;
&lt;li&gt;Observability that shows the full trace of a 3-hour video pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Myth: "Serverless functions are fine if I chain them."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Chaining serverless functions means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every step fits in the platform timeout (10s–15m)&lt;/li&gt;
&lt;li&gt;You manually pass state between steps (serialization hell)&lt;/li&gt;
&lt;li&gt;A failure at step 7 means re-running steps 1–6 unless you built your own checkpointing&lt;/li&gt;
&lt;li&gt;Cold starts on every step&lt;/li&gt;
&lt;li&gt;No single trace across the whole workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Myth: "Temporal is the only real durable execution."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Temporal is powerful. It also demands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic workflow code (no &lt;code&gt;Date.now()&lt;/code&gt;, no &lt;code&gt;Math.random()&lt;/code&gt;, no direct &lt;code&gt;fetch&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Splitting every side effect into Activities&lt;/li&gt;
&lt;li&gt;Running your own workers (even on Temporal Cloud)&lt;/li&gt;
&lt;li&gt;A week of ramp-up for new engineers&lt;/li&gt;
&lt;li&gt;Versioning workflows with patching APIs when you change logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trigger.dev takes a different bet: &lt;strong&gt;your code should run as written.&lt;/strong&gt; No DSL. No determinism rules. No replay. Just TypeScript.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Trigger.dev Actually Works: Checkpoint-Resume, Not Event Sourcing
&lt;/h2&gt;

&lt;p&gt;The core insight: &lt;strong&gt;a Linux process can be snapshotted and restored.&lt;/strong&gt; CRIU (Checkpoint/Restore In Userspace) does exactly this - it freezes a process tree, serializes memory pages, CPU registers, open file descriptors, and kernel state to disk, then restores it later on the same or a different machine.&lt;/p&gt;

&lt;p&gt;Trigger.dev wraps this in a TypeScript runtime. Here's the lifecycle:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Task Runs in an Isolated Container
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tasks/process-video.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trigger.dev/sdk/v3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processVideo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;process-video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// 1. Download (could take 10 minutes)&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;downloadVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="c1"&gt;// 2. Transcode with FFmpeg (could take 2 hours)&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;transcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="c1"&gt;// 3. Upload result&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cdnUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadToCDN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="c1"&gt;// 4. Notify user&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cdnUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cdnUrl&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs on Trigger.dev's managed compute (or your self-hosted workers). No timeout. The default machine is &lt;code&gt;small-1x&lt;/code&gt; (0.5 vCPU, 0.5 GB RAM); you pick per task: &lt;code&gt;machine: "large-2x"&lt;/code&gt; for the transcode step, &lt;code&gt;micro&lt;/code&gt; for the email.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Wait = Checkpoint + Resource Release
&lt;/h3&gt;

&lt;p&gt;When your code hits a wait point, the runtime checkpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;longWorkflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;long-workflow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="c1"&gt;// This pauses for 3 days. Zero compute billed.&lt;/span&gt;
 &lt;span class="c1"&gt;// Full process state (variables, call stack, open FDs) snapshotted via CRIU.&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="c1"&gt;// Resumes HERE, with `step1` still in scope.&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doSomethingElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;step2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait points that trigger checkpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wait.for({ minutes: 5 })&lt;/code&gt; / &lt;code&gt;wait.until(date)&lt;/code&gt; / &lt;code&gt;wait.forToken(token)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;triggerAndWait()&lt;/code&gt; / &lt;code&gt;batchTriggerAndWait()&lt;/code&gt; (waiting on child tasks)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wait.forRequest()&lt;/code&gt; (human-in-the-loop approval)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key detail:&lt;/strong&gt; waits under 60 seconds don't checkpoint - the worker just blocks. This avoids overhead for short pauses.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Checkpoint = CRIU Snapshot → OCI Image → Registry
&lt;/h3&gt;

&lt;p&gt;In Kubernetes mode (production), the coordinator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calls &lt;code&gt;crictl checkpoint&lt;/code&gt; on the container → tar archive of process state&lt;/li&gt;
&lt;li&gt;Wraps it with Buildah into an OCI image: &lt;code&gt;buildah from scratch → add → config → commit → push&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Stores the image reference in PostgreSQL; the blob lives in S3-compatible storage&lt;/li&gt;
&lt;li&gt;Marks the run &lt;code&gt;SUSPENDED&lt;/code&gt;, releases the worker, re-enqueues the run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On resume, the run is dequeued, the image is pulled, and the container is restored via CRIU. Execution continues from the instruction after the wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Warm Starts: Retries That Skip Completed Work
&lt;/h3&gt;

&lt;p&gt;If a task crashes after 45 minutes of a 60-minute run, Trigger.dev can &lt;strong&gt;warm-start&lt;/strong&gt; from the last checkpoint instead of restarting from zero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unreliableTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unreliable-task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="c1"&gt;// Only checkpoint for retries if the run lasted &amp;gt; 30s&lt;/span&gt;
 &lt;span class="na"&gt;retryWarmStartThresholdMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 10 min&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;step2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 20 min ← crashes here&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;step3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// never reached&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On retry, the runtime restores the checkpoint from after &lt;code&gt;step2&lt;/code&gt;, reuses the cached result of &lt;code&gt;step1&lt;/code&gt; (via idempotency keys on &lt;code&gt;triggerAndWait&lt;/code&gt;), and continues at &lt;code&gt;step3&lt;/code&gt;. The 30 minutes of work isn't redone.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model: One Concept - Tasks
&lt;/h2&gt;

&lt;p&gt;Everything in Trigger.dev is a &lt;strong&gt;task&lt;/strong&gt;. No workflows, activities, signals, or DSL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A task is just an async function with metadata&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// unique, used for triggering&lt;/span&gt;
 &lt;span class="na"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// optional concurrency control&lt;/span&gt;
 &lt;span class="na"&gt;concurrencyLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;per-user-${payload.userId}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// serialize per user&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="na"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium-1x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// CPU/RAM preset&lt;/span&gt;
 &lt;span class="na"&gt;maxDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// optional cap (seconds)&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// Your code. Plain TypeScript.&lt;/span&gt;
 &lt;span class="c1"&gt;// fetch, Date.now(), Math.random(), any npm package - all allowed.&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Triggering from Anywhere
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From your API route (Next.js, Express, Hono, Fastify...)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trigger.dev/sdk/v3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;process-video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="c1"&gt;// handle.id, handle.publicAccessToken for realtime UI&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// From inside another task (fan-out, orchestration)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// Fire-and-forget&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;childTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="c1"&gt;// Wait for result (checkpoints parent)&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;childTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;triggerAndWait&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="c1"&gt;// Parallel fan-out with batch&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;batchTriggerAndWait&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="p"&gt;]);&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Realtime: Stream Progress to Your UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React frontend&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRealtimeRun&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trigger.dev/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;VideoProgress&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicAccessToken&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRealtimeRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicAccessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Status: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;progress&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
 &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Done: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cdnUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The run metadata updates live as your task progresses. No WebSocket server to build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison: Where Trigger.dev Wins (and Where It Doesn't)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Trigger.dev&lt;/th&gt;
&lt;th&gt;Inngest&lt;/th&gt;
&lt;th&gt;Temporal&lt;/th&gt;
&lt;th&gt;BullMQ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Durable tasks (checkpoint-resume)&lt;/td&gt;
&lt;td&gt;Durable steps (HTTP invoke + memoization)&lt;/td&gt;
&lt;td&gt;Workflow replay (event sourcing)&lt;/td&gt;
&lt;td&gt;At-least-once queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where code runs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Trigger.dev compute (Cloud) or your workers (self-host)&lt;/td&gt;
&lt;td&gt;Your app (HTTP invoke)&lt;/td&gt;
&lt;td&gt;Your workers&lt;/td&gt;
&lt;td&gt;Your workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Languages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TypeScript (Python via extension)&lt;/td&gt;
&lt;td&gt;TS, Python, Go&lt;/td&gt;
&lt;td&gt;Go, Java, TS, Python, .NET, more&lt;/td&gt;
&lt;td&gt;Node.js (Python port exists)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Long-running jobs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ No timeout, checkpoint on wait&lt;/td&gt;
&lt;td&gt;✅ Split into steps&lt;/td&gt;
&lt;td&gt;✅ Continue-As-New&lt;/td&gt;
&lt;td&gt;✅ If worker stays up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Determinism required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes (workflow code)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Managed compute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Cloud runs your tasks&lt;/td&gt;
&lt;td&gt;❌ You run functions&lt;/td&gt;
&lt;td&gt;❌ You run workers&lt;/td&gt;
&lt;td&gt;❌ You run workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Docker/K8s, same codebase&lt;/td&gt;
&lt;td&gt;Possible, less common&lt;/td&gt;
&lt;td&gt;✅ Heavy ops&lt;/td&gt;
&lt;td&gt;It's a library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flow control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Queues, concurrency keys&lt;/td&gt;
&lt;td&gt;Best in class (throttle, debounce, priority)&lt;/td&gt;
&lt;td&gt;Build in workflow code&lt;/td&gt;
&lt;td&gt;Rate limits, priorities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gentle&lt;/td&gt;
&lt;td&gt;Gentle&lt;/td&gt;
&lt;td&gt;Steep&lt;/td&gt;
&lt;td&gt;Flat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI pipelines, media jobs, long compute&lt;/td&gt;
&lt;td&gt;Serverless TS apps, event-driven&lt;/td&gt;
&lt;td&gt;Payments, sagas, regulated&lt;/td&gt;
&lt;td&gt;Simple jobs on existing Redis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The fork is where compute runs.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger.dev Cloud&lt;/strong&gt; runs your tasks. You deploy tasks; Trigger.dev owns the queue, the execution environment, and observability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inngest&lt;/strong&gt; orchestrates your serverless functions over HTTP. Your functions run on &lt;em&gt;your&lt;/em&gt; infra (Vercel, Lambda, your VPS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporal&lt;/strong&gt; manages the server; you run workers that poll it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your jobs are &lt;strong&gt;minutes to hours of compute&lt;/strong&gt; (video, AI, PDF, data pipelines), Trigger.dev's managed compute removes the timeout ceiling entirely. If your jobs are &lt;strong&gt;short steps triggered by events&lt;/strong&gt; and you want zero new infrastructure, Inngest is simpler. If you need &lt;strong&gt;exactly-once workflow logic across polyglot services&lt;/strong&gt; and will staff the ops model, Temporal is the standard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Self-Hosting: No Lock-In, Same Codebase
&lt;/h2&gt;

&lt;p&gt;Trigger.dev is Apache 2.0. The dashboard, orchestrator, workers, and CLI all live in the public monorepo. Self-hosting uses the &lt;strong&gt;identical runtime&lt;/strong&gt; as Cloud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Docker Compose (dev/simple prod)&lt;/span&gt;
git clone https://github.com/triggerdotdev/trigger.dev
&lt;span class="nb"&gt;cd &lt;/span&gt;trigger.dev
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Kubernetes (Helm) - production scale&lt;/span&gt;
&lt;span class="s"&gt;helm repo add trigger https://triggerdotdev.github.io/helm-charts&lt;/span&gt;
&lt;span class="s"&gt;helm install trigger trigger/trigger \&lt;/span&gt;
 &lt;span class="s"&gt;--set postgresql.enabled=true \&lt;/span&gt;
 &lt;span class="s"&gt;--set redis.enabled=true \&lt;/span&gt;
 &lt;span class="s"&gt;--set objectStorage.s3.bucket=my-bucket&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You operate Postgres, Redis (or Valkey/Dragonfly), and the orchestrator. Cloud-only features you lose: warm starts, auto-scaling, dedicated support, managed checkpoints. Everything else - durable tasks, checkpoints, realtime, build extensions - works identically.&lt;/p&gt;

&lt;p&gt;This matters for &lt;strong&gt;compliance&lt;/strong&gt;. GovSignals runs Trigger.dev self-hosted at FedRAMP High. Your security team audits the exact code that runs your workloads. Start on Cloud, migrate to self-hosted (or vice versa) without changing a line of task code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build Extensions: FFmpeg, Playwright, Python - No Dockerfile
&lt;/h2&gt;

&lt;p&gt;The hardest part of self-hosted workers: &lt;strong&gt;custom dependencies&lt;/strong&gt;. FFmpeg for video. Playwright for scraping. Python for ML. Trigger.dev solves this at build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// trigger.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trigger.dev/sdk/v3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ffmpegExtension&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trigger.dev/build/extensions/ffmpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;playwrightExtension&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trigger.dev/build/extensions/playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pythonExtension&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@trigger.dev/build/extensions/python&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-proj&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="nf"&gt;ffmpegExtension&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// adds ffmpeg, ffprobe&lt;/span&gt;
 &lt;span class="nf"&gt;playwrightExtension&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// adds Chromium + deps&lt;/span&gt;
 &lt;span class="nf"&gt;pythonExtension&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="c1"&gt;// run Python from TS&lt;/span&gt;
 &lt;span class="na"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pandas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;numpy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="p"&gt;}),&lt;/span&gt;
 &lt;span class="c1"&gt;// Or install any apt package:&lt;/span&gt;
 &lt;span class="c1"&gt;// aptGetExtension({ packages: ["libreoffice", "imagemagick"] }),&lt;/span&gt;
 &lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line in config. The CLI builds a Docker image with those binaries baked in. No Dockerfile to write, no base image to maintain, no &lt;code&gt;RUN apt-get update&lt;/code&gt; debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AI Agent with Human-in-the-Loop
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Outline: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="c1"&gt;// Pause for human approval - days if needed, zero cost&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;approval&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;7d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rejected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Write: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;edited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;humanReview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// another waitpoint&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;edited&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scheduled Jobs That Survive Deploys
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dailyReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;daily-report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="c1"&gt;// Cron schedules are durable - attached to the task, not a separate cron process&lt;/span&gt;
 &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 6 * * *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchAnalytics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generatePDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadAndEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;cron&lt;/code&gt; process to monitor. No "did it run?" guesswork. The dashboard shows every run, its trace, and its output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Tenant Queue Isolation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenantJob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tenant-job&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// Each tenant gets their own lane; one noisy tenant doesn't starve others&lt;/span&gt;
 &lt;span class="na"&gt;concurrencyLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tenant-${payload.tenantId}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// Process tenant's work&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Honest Gaps (as of v3)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No sandboxed code execution&lt;/strong&gt; - coming, not shipped&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No built-in agent memory&lt;/strong&gt; across runs - you bring your own DB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript-first&lt;/strong&gt; - Python via build extension only; no Go, Java, Rust SDKs yet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No time-based rate limiting&lt;/strong&gt; - concurrency caps only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No offline local dev&lt;/strong&gt; - needs connection to dev environment or Cloud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No visual workflow builder&lt;/strong&gt; - code-first by design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No LLM cost caps per run&lt;/strong&gt; - you implement budgeting&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When to Adopt Trigger.dev
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with Trigger.dev if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You write TypeScript/Next.js and have jobs that exceed serverless timeouts&lt;/li&gt;
&lt;li&gt;You want managed compute so you don't operate Redis + workers&lt;/li&gt;
&lt;li&gt;You need realtime progress in your UI (AI generation, video processing)&lt;/li&gt;
&lt;li&gt;You want self-hosting as an exit hatch (compliance, data residency)&lt;/li&gt;
&lt;li&gt;You're building AI agents with human-in-the-loop, long waits, or tool calling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stick with BullMQ if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You already run Redis well, jobs are single-step, and you want zero vendor bill&lt;/li&gt;
&lt;li&gt;You need raw queue primitives (priority, rate limiting, parent-child flows) and are happy operating workers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pick Inngest if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You deploy to Vercel/Netlify/Cloudflare and want functions to run &lt;em&gt;in your app&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;You need best-in-class flow control (debounce, throttle, priority per key)&lt;/li&gt;
&lt;li&gt;You want the smoothest local dev experience (HTTP invoke, no new processes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pick Temporal if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workflows touch money, healthcare, or regulated data where exactly-once matters&lt;/li&gt;
&lt;li&gt;You need polyglot workers (Go, Java, .NET) and will invest in the learning curve&lt;/li&gt;
&lt;li&gt;You want the most battle-tested durability model (Uber, Stripe, Coinbase scale)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Background jobs are infrastructure. Most teams don't want to build infrastructure - they want to ship features. Trigger.dev gives you &lt;strong&gt;durable execution as a platform&lt;/strong&gt;, not a library you bolt onto a queue.&lt;/p&gt;

&lt;p&gt;You write tasks. They run on managed compute. They checkpoint when they wait. They resume where they left off. You get observability, realtime, scheduling, and self-hosting for free. The code you write is the code that runs - no translation layer, no determinism tax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/triggerdotdev/trigger.dev" rel="noopener noreferrer"&gt;https://github.com/triggerdotdev/trigger.dev&lt;/a&gt; (16k+ stars, Apache 2.0) &lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://trigger.dev/docs" rel="noopener noreferrer"&gt;https://trigger.dev/docs&lt;/a&gt; &lt;br&gt;
&lt;strong&gt;Try it:&lt;/strong&gt; &lt;code&gt;npx trigger.dev@latest init&lt;/code&gt; - first deploy in ~5 minutes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Stop managing workers. Start writing tasks that just work.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>backgroundjobs</category>
      <category>durableexecution</category>
    </item>
    <item>
      <title>HyperSwitch: The Open-Source Payments Orchestrator That Replaces Your PSP Stack</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Sat, 31 Oct 2026 12:49:09 +0000</pubDate>
      <link>https://dev.to/syed_anzar/hyperswitch-the-open-source-payments-orchestrator-that-replaces-your-psp-stack-2h2c</link>
      <guid>https://dev.to/syed_anzar/hyperswitch-the-open-source-payments-orchestrator-that-replaces-your-psp-stack-2h2c</guid>
      <description>&lt;h1&gt;
  
  
  HyperSwitch: The Open-Source Payments Orchestrator That Replaces Your PSP Stack
&lt;/h1&gt;

&lt;p&gt;You integrate Stripe. Then Adyen for Europe. Then Braintree for PayPal. Then a local acquirer for Brazil. Each has its own SDK, webhook format, retry logic, and fee structure. Your codebase grows &lt;code&gt;if (provider === 'stripe')&lt;/code&gt; branches like kudzu.&lt;/p&gt;

&lt;p&gt;This is the problem HyperSwitch solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Is HyperSwitch?
&lt;/h2&gt;

&lt;p&gt;HyperSwitch is an open-source payments orchestration platform — 45.6k GitHub stars, Apache 2.0, written in Rust, battle-tested at 300M daily transactions and $1T+ annual TPV by Juspay (India's largest payments processor). It sits between your application and 120+ payment processors, providing a single API that handles routing, retries, vaulting, reconciliation, and cost observability.&lt;/p&gt;

&lt;p&gt;Think of it as the Linux of payments: you integrate once, then plug in whichever processors you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture That Makes It Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Router + Scheduler: Two Services, One Job
&lt;/h3&gt;

&lt;p&gt;HyperSwitch runs as two stateless Rust binaries:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Router&lt;/strong&gt; — The hot path. Handles every payment request: authentication, FRM checks, 3DS decisions, connector selection via the Euclid routing engine, credential resolution from the vault, and connector dispatch through the Connector Abstraction Layer (CAL).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduler&lt;/strong&gt; — Background jobs. Producer batches scheduled tasks (card cleanup, API key expiry notices) into Redis queues; Consumer executes them. Horizontal scaling is trivial — any instance can handle any request.&lt;/p&gt;

&lt;p&gt;All state lives in PostgreSQL. Redis handles caching, distributed locks, and the scheduler queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Payment State Machine: Intent vs Attempt
&lt;/h3&gt;

&lt;p&gt;This is where most orchestrators get it wrong. HyperSwitch separates two concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;payment_intent&lt;/code&gt;&lt;/strong&gt; — The merchant's request. One per checkout session. Holds amount, currency, customer, capture method, setup_future_usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;payment_attempt&lt;/code&gt;&lt;/strong&gt; — A single authorization try against a specific PSP. Multiple attempts per intent (retries). Each has its own &lt;code&gt;connector&lt;/code&gt;, &lt;code&gt;connector_transaction_id&lt;/code&gt;, &lt;code&gt;error_code&lt;/code&gt;, &lt;code&gt;error_details&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a retry happens, a new &lt;code&gt;payment_attempt&lt;/code&gt; is created. The merchant-facing &lt;code&gt;payment_id&lt;/code&gt; never changes — only the internal suffix (&lt;code&gt;pay_abc_1&lt;/code&gt;, &lt;code&gt;pay_abc_2&lt;/code&gt;) sent to the PSP changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Euclid: Routing That Isn't Just Round-Robin
&lt;/h3&gt;

&lt;p&gt;The Euclid routing engine evaluates strategies in strict priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rule-based&lt;/strong&gt; — Merchant-defined conditions: &lt;code&gt;payment_method_type = visa AND country = DE AND amount &amp;gt; 10000&lt;/code&gt; → use Adyen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume-based&lt;/strong&gt; — Percentage split across eligible connectors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-based&lt;/strong&gt; — Select lowest MDR (merchant discount rate) among eligible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default fallback&lt;/strong&gt; — Ordered priority list from merchant config&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After selection, &lt;strong&gt;eligibility analysis&lt;/strong&gt; runs: Does this connector support the payment method + currency? Is it enabled for this profile? If any check fails, Euclid moves to the next candidate. Exhaust the list → payment fails without a single PSP call.&lt;/p&gt;

&lt;h3&gt;
  
  
  GSM: Normalizing 120+ Error Languages
&lt;/h3&gt;

&lt;p&gt;Every PSP returns proprietary error codes. Stripe's &lt;code&gt;card_declined&lt;/code&gt; ≠ Adyen's &lt;code&gt;REFUSED&lt;/code&gt; ≠ Braintree's &lt;code&gt;2001&lt;/code&gt;. GSM (Global Status Monitoring) maps &lt;code&gt;(connector, error_code, error_message)&lt;/code&gt; → standardized &lt;code&gt;unified_code&lt;/code&gt; + retry flags:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;retriable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Any retry eligible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;step_up_possible&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3DS challenge could succeed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clear_pan_possible&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network token failed, try raw PAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alternate_network_possible&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch debit network (Visa → Maestro)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The retry decision flow is deterministic: GSM lookup → check flags in order (Step-Up → Clear PAN → Network → Cascading) → &lt;code&gt;do_retry()&lt;/code&gt; creates new &lt;code&gt;payment_attempt&lt;/code&gt; → re-enter Euclid.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vault: PCI Compliance Without the Pain
&lt;/h3&gt;

&lt;p&gt;The Card Vault is a separate microservice. Sensitive data never touches your Router — or your application if you use the client-side SDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tokenization flow (client-side):&lt;/strong&gt; SDK → Vault &lt;code&gt;/session_tokens&lt;/code&gt; → JWE-encrypted request → Vault decrypts, stores PAN, returns &lt;code&gt;payment_method_id&lt;/code&gt; → Your backend sends &lt;code&gt;payment_method_id&lt;/code&gt; to Router → Router calls Vault Locker API → Vault returns credential (PAN, network token, or proxy reference) → Router dispatches to PSP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key management:&lt;/strong&gt; Custodian-based. Master key derived from multiple custodian shares (Shamir's Secret Sharing). Vault only activates when quorum of custodians unlock it. Zero data egress in self-hosted mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reconciliation: Double-Entry for Payments
&lt;/h3&gt;

&lt;p&gt;Merchants routing across PSPs get separate settlement reports — different formats, different schedules. HyperSwitch's reconciliation engine applies double-entry bookkeeping: every payment event generates balanced debit/credit ledger entries.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2-way&lt;/strong&gt;: Hyperswitch ledger ↔ PSP settlement report&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3-way&lt;/strong&gt;: Hyperswitch ledger ↔ PSP settlement ↔ OMS order data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auto-fetch pipelines pull reports from PSPs (SFTP, API, email), normalize via connector-specific rules, match on &lt;code&gt;connector_transaction_id&lt;/code&gt; + amount + date with configurable tolerance windows, age unmatched entries, and surface exceptions in the Control Center.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Replaces
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Paid Stack&lt;/th&gt;
&lt;th&gt;HyperSwitch Module&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stripe + Adyen + Braintree integrations&lt;/td&gt;
&lt;td&gt;Single API, 120+ connectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom routing logic&lt;/td&gt;
&lt;td&gt;Euclid engine (rules, volume, cost)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual retry code&lt;/td&gt;
&lt;td&gt;GSM-driven smart retries (4 strategies)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCI vault (TokenEx, VGS)&lt;/td&gt;
&lt;td&gt;Built-in Card Vault + Network Tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spreadsheet reconciliation&lt;/td&gt;
&lt;td&gt;Automated 2/3-way engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataDog/New Relic for payments&lt;/td&gt;
&lt;td&gt;Cost Observability dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Self-Hosting: What You Actually Need
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (simplified)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;juspaydotin/hyperswitch-router:latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;LOCKER_URL=http://vault:8080&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;juspaydotin/hyperswitch-locker:latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CUSTODIAN_KEYS=...&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8081:8081"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;scheduler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;juspaydotin/hyperswitch-scheduler:latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://...&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy via Docker Compose, Kubernetes (Helm), or AWS CloudFormation. Control Center (React dashboard) runs separately for routing config, analytics, and exception management.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-offs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Not a drop-in Stripe replacement.&lt;/strong&gt; You operate infrastructure. PCI-SSS validation requires the Encryption Service + custodian ceremony. Connector coverage is broad but not infinite — niche local methods may need custom connectors (template provided). The learning curve is real: routing rules, GSM mappings, vault activation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But you get:&lt;/strong&gt; Zero vendor lock-in. Full visibility into every hop. Routing logic in config, not code. No per-transaction markup. Apache 2.0 — fork it, extend it, audit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Adopt
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multiple PSPs already, or planning to add them&lt;/li&gt;
&lt;li&gt;Team capable of running PostgreSQL + Redis + Rust services&lt;/li&gt;
&lt;li&gt;Need routing/retries/vault/reconciliation as configurable infrastructure&lt;/li&gt;
&lt;li&gt;Willing to invest in ops for long-term control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start Here
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick local test&lt;/span&gt;
git clone https://github.com/juspay/hyperswitch
&lt;span class="nb"&gt;cd &lt;/span&gt;hyperswitch/docker
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="c"&gt;# Router at localhost:8080, Control Center at localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the &lt;a href="https://deepwiki.com/juspay/hyperswitch-docs/10.1-router-architecture-deep-dive" rel="noopener noreferrer"&gt;architecture deep-dive&lt;/a&gt; for the Router internals, &lt;a href="https://deepwiki.com/juspay/hyperswitch-docs/10.2-vault-architecture-deep-dive" rel="noopener noreferrer"&gt;Vault architecture&lt;/a&gt; for tokenization internals, and &lt;a href="https://deepwiki.com/juspay/hyperswitch-docs/10.3-reconciliation-architecture" rel="noopener noreferrer"&gt;Reconciliation architecture&lt;/a&gt; for the ledger model.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The payments stack you'd build if you had 12 years and 300M daily transactions to refine it. Now you don't have to.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>payments</category>
      <category>fintech</category>
      <category>rust</category>
    </item>
    <item>
      <title>What Actually Happens When You Resolve a Domain Name</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Sat, 31 Oct 2026 12:22:41 +0000</pubDate>
      <link>https://dev.to/syed_anzar/what-actually-happens-when-you-resolve-a-domain-name-4k9f</link>
      <guid>https://dev.to/syed_anzar/what-actually-happens-when-you-resolve-a-domain-name-4k9f</guid>
      <description>&lt;h1&gt;
  
  
  What Actually Happens When You Resolve a Domain Name
&lt;/h1&gt;

&lt;p&gt;You type &lt;code&gt;example.com&lt;/code&gt; in your browser. Milliseconds later, the page loads. What happened in between?&lt;/p&gt;

&lt;p&gt;Most developers know DNS "turns names into IPs." Few can explain what actually happens when the cache is cold. Let's trace the full path—from your stub resolver to the root servers, through TLDs, to the authoritative answer—and the protocol details that make it work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Big Picture
&lt;/h2&gt;

&lt;p&gt;DNS resolution is a &lt;strong&gt;distributed delegation chain&lt;/strong&gt;. No single server knows everything. Each server knows only its zone and where to find the next level down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client → Stub Resolver → Recursive Resolver → Root → TLD → Authoritative → Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your browser doesn't talk to root servers. It talks to a &lt;strong&gt;stub resolver&lt;/strong&gt; (OS DNS client). That stub forwards to a &lt;strong&gt;recursive resolver&lt;/strong&gt; (your ISP, 1.1.1.1, 8.8.8.8, or a local Unbound/BIND). The recursive resolver does the heavy lifting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 0: The Cache Check
&lt;/h2&gt;

&lt;p&gt;Before any network traffic, the recursive resolver checks its cache:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Exact match?&lt;/strong&gt; Return immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNAME chain?&lt;/strong&gt; Follow it, then return the final target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative cache (NXDOMAIN/NODATA)?&lt;/strong&gt; Return "doesn't exist" without querying upstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial match (NS records for a parent zone)?&lt;/strong&gt; Start from there—this is the &lt;strong&gt;closest delegation point&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the cache is cold (resolver just restarted), it has only &lt;strong&gt;root hints&lt;/strong&gt;: a built-in list of 13 root server names (&lt;code&gt;a.root-servers.net&lt;/code&gt; through &lt;code&gt;m.root-servers.net&lt;/code&gt;) and their IPs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Priming — Getting the Real Root List
&lt;/h2&gt;

&lt;p&gt;Root hints are &lt;em&gt;hints&lt;/em&gt;. They can be stale. Before trusting them, the resolver sends a &lt;strong&gt;priming query&lt;/strong&gt; (RFC 8109):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QNAME: . (root)
QTYPE: NS
RD: 0 or 1 (undefined for priming)
EDNS(0): YES, UDP payload ≥ 1024 bytes
DO bit: optionally set
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sent to &lt;em&gt;one&lt;/em&gt; root hint address (selected randomly to spread load). The root responds with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Answer section&lt;/strong&gt;: NS RRset for &lt;code&gt;.&lt;/code&gt; (the 13 root server names)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional section&lt;/strong&gt;: A/AAAA records for those names (glue records)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AA bit = 1&lt;/strong&gt;: authoritative answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This primes the resolver with the &lt;em&gt;current, authoritative&lt;/em&gt; root NS set and addresses. TTL is typically 6 days (518,400 seconds).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: The Descent — Iterative Queries
&lt;/h2&gt;

&lt;p&gt;Now the resolver has a working root NS set. It begins &lt;strong&gt;iterative resolution&lt;/strong&gt; (RD=0) for &lt;code&gt;www.example.com&lt;/code&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Query to Root (&lt;code&gt;.&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QNAME: www.example.com (or minimized: com — see QNAME minimization below)
QTYPE: A (or AAAA)
RD: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Root doesn't know &lt;code&gt;example.com&lt;/code&gt;. It's only authoritative for &lt;code&gt;.&lt;/code&gt;. It responds with a &lt;strong&gt;referral&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authority section&lt;/strong&gt;: NS records for &lt;code&gt;.com&lt;/code&gt; (the TLD nameservers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional section&lt;/strong&gt;: Glue A/AAAA for those NS names (e.g., &lt;code&gt;a.gtld-servers.net&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Query to TLD (&lt;code&gt;.com&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Resolver picks one &lt;code&gt;.com&lt;/code&gt; nameserver, sends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QNAME: www.example.com (or minimized: example.com)
QTYPE: A
RD: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TLD server isn't authoritative for &lt;code&gt;example.com&lt;/code&gt; either. Another referral:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authority&lt;/strong&gt;: NS records for &lt;code&gt;example.com&lt;/code&gt; (your domain's authoritative nameservers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional&lt;/strong&gt;: Glue records if the NS names are &lt;em&gt;inside&lt;/em&gt; &lt;code&gt;example.com&lt;/code&gt; (e.g., &lt;code&gt;ns1.example.com&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Query to Authoritative (&lt;code&gt;example.com&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Resolver picks one authoritative NS for &lt;code&gt;example.com&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QNAME: www.example.com
QTYPE: A
RD: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This server &lt;em&gt;is&lt;/em&gt; authoritative. It responds with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Answer section&lt;/strong&gt;: The A record (e.g., &lt;code&gt;93.184.216.34&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AA bit = 1&lt;/strong&gt;: "I own this data"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL&lt;/strong&gt;: How long to cache (e.g., 3600 seconds)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resolver caches the answer, the NS records, and glue—and returns it to the stub resolver.&lt;/p&gt;




&lt;h2&gt;
  
  
  QNAME Minimization: Privacy by Design (RFC 7816 / 9156)
&lt;/h2&gt;

&lt;p&gt;Traditional resolvers send the &lt;strong&gt;full QNAME&lt;/strong&gt; (&lt;code&gt;www.example.com&lt;/code&gt;) to &lt;em&gt;every&lt;/em&gt; server in the chain. The root doesn't need &lt;code&gt;www.example.com&lt;/code&gt;—it only needs &lt;code&gt;.com&lt;/code&gt;. The TLD only needs &lt;code&gt;example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QNAME minimization&lt;/strong&gt; sends only what each server needs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Traditional Query&lt;/th&gt;
&lt;th&gt;Minimized Query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Root&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;www.example.com&lt;/code&gt; A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;com&lt;/code&gt; NS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLD&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;www.example.com&lt;/code&gt; A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;example.com&lt;/code&gt; NS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;www.example.com&lt;/code&gt; A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;www.example.com&lt;/code&gt; A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The resolver tracks &lt;strong&gt;zone cuts&lt;/strong&gt; (delegation boundaries) in its cache. When it doesn't know where the cut is, it probes one label at a time (NS queries), caching NODATA responses to learn the boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;: Root servers see only &lt;code&gt;com&lt;/code&gt;, not your full browsing history. Reduces data exposure to authoritative operators. Also enables &lt;strong&gt;aggressive negative caching&lt;/strong&gt; (RFC 8020): if &lt;code&gt;.example&lt;/code&gt; doesn't exist, nothing under it exists either.&lt;/p&gt;




&lt;h2&gt;
  
  
  EDNS(0): Breaking the 512-Byte Limit (RFC 6891)
&lt;/h2&gt;

&lt;p&gt;Original DNS: 512-byte UDP max. DNSSEC responses (with RRSIG, DNSKEY, NSEC) routinely exceed this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EDNS(0)&lt;/strong&gt; adds an &lt;strong&gt;OPT pseudo-record&lt;/strong&gt; in the additional section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME: . (root)
TYPE: OPT (41)
CLASS: Requestor's UDP payload size (e.g., 4096)
TTL: Extended RCODE (high 8 bits) + flags (DO bit in bit 15)
RDATA: Variable {option-code, option-length, option-data} tuples
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UDP payload size&lt;/strong&gt;: "I can receive this many bytes." Default 4096. Values &amp;lt; 512 treated as 512.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DO bit (DNSSEC OK)&lt;/strong&gt;: "Send me DNSSEC records."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Options&lt;/strong&gt;: COOKIE (RFC 7873), ECS (RFC 7871), PADDING, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a responder can't fit the answer in the advertised size, it sets &lt;strong&gt;TC=1 (truncated)&lt;/strong&gt;. Resolver retries over TCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Middlebox warning&lt;/strong&gt;: Firewalls/load balancers that strip OPT records or block &amp;gt;512-byte UDP break DNSSEC and large responses. RFC 6891 says they MUST NOT do this—but they do.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Kaminsky Attack &amp;amp; Why Source Port Randomization Matters
&lt;/h2&gt;

&lt;p&gt;In 2008, Dan Kaminsky showed how to poison a resolver's cache &lt;strong&gt;off-path&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The vulnerability&lt;/strong&gt;: Resolvers accepted the first response matching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query name + QTYPE&lt;/li&gt;
&lt;li&gt;16-bit Transaction ID (TXID)&lt;/li&gt;
&lt;li&gt;Source/destination IP&lt;/li&gt;
&lt;li&gt;Source/destination port (often fixed at 53)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Attacker floods resolver with spoofed responses, guessing TXID. With fixed source port, only 65,536 guesses needed—trivial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt;: &lt;strong&gt;Source port randomization&lt;/strong&gt; (RFC 5452). Resolver picks a random ephemeral source port per query. Now attacker must guess &lt;strong&gt;TXID (16 bits) + source port (~15 bits) = ~31 bits of entropy&lt;/strong&gt;. At 50,000 packets/sec, success probability drops from ~100% in seconds to ~64% in 24 hours with ~0.4 TB of traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remaining weaknesses&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NAT devices with predictable port allocation (sequential, port-preserving) reduce entropy.&lt;/li&gt;
&lt;li&gt;ICMP side channels can leak source ports on some OS/resolver combos (CCS 2020).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real fix&lt;/strong&gt;: DNSSEC (cryptographic validation). Port randomization is a band-aid; DNSSEC is the cure.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  DNSSEC: The Chain of Trust
&lt;/h2&gt;

&lt;p&gt;DNSSEC signs zones with asymmetric crypto. Each zone has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;KSK (Key Signing Key)&lt;/strong&gt;: Signs the DNSKEY RRset. Published as DS record in parent zone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ZSK (Zone Signing Key)&lt;/strong&gt;: Signs all other records in the zone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Validation flow&lt;/strong&gt; for &lt;code&gt;www.example.com&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resolver gets A record + RRSIG from &lt;code&gt;example.com&lt;/code&gt; authoritative.&lt;/li&gt;
&lt;li&gt;Resolver gets DNSKEY (ZSK) for &lt;code&gt;example.com&lt;/code&gt; + RRSIG.&lt;/li&gt;
&lt;li&gt;Resolver gets DS record for &lt;code&gt;example.com&lt;/code&gt; from &lt;code&gt;.com&lt;/code&gt; TLD + RRSIG.&lt;/li&gt;
&lt;li&gt;Resolver gets DNSKEY (KSK) for &lt;code&gt;.com&lt;/code&gt; from &lt;code&gt;.com&lt;/code&gt; authoritative + RRSIG.&lt;/li&gt;
&lt;li&gt;Resolver gets DS record for &lt;code&gt;.com&lt;/code&gt; from root + RRSIG.&lt;/li&gt;
&lt;li&gt;Resolver gets DNSKEY for root (trust anchor, pre-configured).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If every signature verifies up to the trust anchor → &lt;strong&gt;AD bit = 1&lt;/strong&gt; (Authenticated Data). If any break → &lt;strong&gt;SERVFAIL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NSEC/NSEC3&lt;/strong&gt;: Prove non-existence. NSEC lists "next secure name"—enables zone walking. NSEC3 hashes names (salted) to prevent enumeration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Misconceptions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Myth&lt;/th&gt;
&lt;th&gt;Reality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"My browser queries root servers"&lt;/td&gt;
&lt;td&gt;Browser → stub → recursive resolver. Recursive does the descent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"DNS is a single database"&lt;/td&gt;
&lt;td&gt;Distributed delegation. Each zone owns its data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"TTL is a guarantee"&lt;/td&gt;
&lt;td&gt;TTL is a &lt;em&gt;hint&lt;/em&gt;. Resolvers may cap it (e.g., 2-day max). Negative TTL from SOA MINIMUM.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"CNAME at apex works"&lt;/td&gt;
&lt;td&gt;RFC 1912: CNAME at zone apex breaks NS/SOA. Use ALIAS/ANAME (non-standard) or flattening (Cloudflare).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"DNSSEC encrypts queries"&lt;/td&gt;
&lt;td&gt;DNSSEC &lt;em&gt;signs&lt;/em&gt; data (authenticity/integrity). It does NOT encrypt. Use DoH/DoT/DoQ for privacy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"13 root servers = 13 machines"&lt;/td&gt;
&lt;td&gt;13 &lt;em&gt;identities&lt;/em&gt; (a–m.root-servers.net). Each is a global anycast constellation (hundreds of physical servers).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Practical Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run a local validating resolver&lt;/strong&gt; (Unbound, Knot Resolver). Cuts latency, enables DNSSEC validation, supports QNAME minimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable DNSSEC validation&lt;/strong&gt;. It's the only real protection against cache poisoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand TTL caps&lt;/strong&gt;. Don't rely on 60-second TTLs for fast failover—resolvers may ignore them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor your glue&lt;/strong&gt;. If you rename NS hosts inside your zone, update glue at the registrar &lt;em&gt;before&lt;/em&gt; the old records expire.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;dig +trace&lt;/code&gt;&lt;/strong&gt; to see the actual delegation chain. &lt;code&gt;dig +dnssec&lt;/code&gt; to see validation.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Mental Model
&lt;/h2&gt;

&lt;p&gt;DNS isn't a lookup table. It's a &lt;strong&gt;tree of delegations&lt;/strong&gt;, each signed (optionally), cached at every level, with privacy minimizations and protocol extensions layered on top. The next time you hit &lt;code&gt;example.com&lt;/code&gt;, you're not "querying a database." You're walking a chain of trust from root to leaf—one referral at a time.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: dns, networking, systems, security&lt;/p&gt;

</description>
      <category>dns</category>
      <category>networking</category>
      <category>systems</category>
      <category>security</category>
    </item>
    <item>
      <title>Test Article</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Sat, 31 Oct 2026 12:21:04 +0000</pubDate>
      <link>https://dev.to/syed_anzar/test-article-40cj</link>
      <guid>https://dev.to/syed_anzar/test-article-40cj</guid>
      <description>&lt;h1&gt;
  
  
  Test
&lt;/h1&gt;

&lt;p&gt;Test content&lt;/p&gt;

</description>
      <category>test</category>
    </item>
    <item>
      <title>SigNoz: The Open-Source Observability Platform That Actually Replaces Datadog</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Fri, 30 Oct 2026 13:26:47 +0000</pubDate>
      <link>https://dev.to/syed_anzar/signoz-the-open-source-observability-platform-that-actually-replaces-datadog-4bfe</link>
      <guid>https://dev.to/syed_anzar/signoz-the-open-source-observability-platform-that-actually-replaces-datadog-4bfe</guid>
      <description>&lt;h1&gt;
  
  
  SigNoz: The Open-Source Observability Platform That Actually Replaces Datadog
&lt;/h1&gt;

&lt;p&gt;You've probably heard "SigNoz is the open-source Datadog alternative" in every other dev post. But most explanations stop at "it uses OpenTelemetry and ClickHouse" -- as if that explains why it works.&lt;/p&gt;

&lt;p&gt;It doesn't.&lt;/p&gt;

&lt;p&gt;The real difference isn't the tech stack. It's how SigNoz solves the fundamental problem every observability platform faces: &lt;strong&gt;how to store and query logs, metrics, and traces together without melting your infrastructure or your budget.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Everyone Lives With
&lt;/h2&gt;

&lt;p&gt;If you've used Datadog, New Relic, or any major SaaS observability platform, you know the drill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logs go to one system&lt;/strong&gt; (Elasticsearch, Loki, CloudWatch)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics go to another&lt;/strong&gt; (Prometheus, Datadog metrics, InfluxDB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traces go to a third&lt;/strong&gt; (Jaeger, Zipkin, Tempo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlation happens in your head&lt;/strong&gt; -- you switch tabs, copy trace IDs, pray the timestamps align&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't an accident. It's the natural consequence of how these systems evolved. Logs need full-text search. Metrics need time-series aggregation. Traces need span trees and latency percentiles. Three different query patterns. Three different storage engines.&lt;/p&gt;

&lt;p&gt;Datadog solves this by charging you for three different products and gluing them together in the UI. You pay per host, per custom metric, per log ingested, per trace retained. The bill grows faster than your traffic.&lt;/p&gt;

&lt;p&gt;SigNoz takes a different bet: &lt;strong&gt;one columnar database for everything.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ClickHouse Changes the Game
&lt;/h2&gt;

&lt;p&gt;ClickHouse isn't just "fast." It's a column-oriented OLAP database built for analytical queries over massive datasets. Here's why that matters for observability:&lt;/p&gt;

&lt;h3&gt;
  
  
  One Query Language for All Signals
&lt;/h3&gt;

&lt;p&gt;In SigNoz, a trace, a log line, and a metric live in the same database. You can write a single SQL query that joins spans with logs and aggregates metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Find all errors in the payment service, show related logs, and calculate error rate&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;toDateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts_bucket_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;countIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;has_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_spans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;error_count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_spans&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;signoz_index_v3&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'payment-service'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;ts_bucket_start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try doing that across Elasticsearch + Prometheus + Jaeger. You can't. You'd need application-level correlation, which means more code, more latency, more things to break.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Cardinality Without the Tax
&lt;/h3&gt;

&lt;p&gt;This is the killer feature nobody talks about.&lt;/p&gt;

&lt;p&gt;In Prometheus/Datadog, every unique label combination creates a new time series. Add &lt;code&gt;request_id&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;session_id&lt;/code&gt; to your metrics and you've created a cardinality explosion. Your bill explodes. Your query latency explodes. You're told "don't do that."&lt;/p&gt;

&lt;p&gt;In ClickHouse, high-cardinality attributes live in typed &lt;code&gt;Map&lt;/code&gt; columns. They're compressed with ZSTD. They don't create new time series. You &lt;em&gt;can&lt;/em&gt; filter by &lt;code&gt;user_id&lt;/code&gt; across billions of spans. It just works.&lt;/p&gt;

&lt;p&gt;SigNoz takes this further: hot attribute keys get promoted to materialized columns automatically. The system adapts to your query patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compression That Actually Matters
&lt;/h3&gt;

&lt;p&gt;ClickHouse uses per-column codecs tuned for observability data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Type&lt;/th&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;Why It Works&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Timestamps&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DoubleDelta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Monotonic increasing = tiny deltas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strings (labels, trace IDs)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ZSTD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dictionary + entropy coding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metric values (latencies, counts)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Gorilla&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XOR-based floating-point compression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integers (durations, counts)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Variable-byte encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Typical observability datasets compress 10-20x. That's not marketing -- it's columnar storage doing what it was designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Three Planes, Not Five Microservices
&lt;/h2&gt;

&lt;p&gt;SigNoz used to ship five separate containers: query-service, frontend, alertmanager, otel-collector, ClickHouse. That was v0.75 and earlier.&lt;/p&gt;

&lt;p&gt;Since v0.76 (March 2025), it's three components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------------------+
|                    SigNoz Binary (single Go binary)           |
|  +----------+ +----------+ +----------+ +------------------+  |
|  |  React   | |  API     | |  Ruler   | |  Alertmanager    |  |
|  |  UI      | |  Server  | |  (alerts)| |  (notifications) |  |
|  +----------+ +----------+ +----------+ +------------------+  |
|                        |                                     |
|                  +-----+-----+                               |
|                  |  OpAMP    | &amp;lt;-- Manages collector config  |
|                  |  Server   |                               |
|                  +-----------+                               |
+---------------------------------------------------------------+
                              |
                              v
+---------------------------------------------------------------+
|              SigNoz OTel Collector (ingestion plane)          |
|  OTLP gRPC/HTTP * Jaeger * Zipkin * Prometheus * Kafka       |
|  Custom processors: span metrics, span mapper, LLM pricing   |
+---------------------------------------------------------------+
                              |
                              v
+---------------------------------------------------------------+
|                      ClickHouse (+ Keeper/ZooKeeper)          |
|  signoz_traces * signoz_logs * signoz_metrics * signoz_meter |
|  Resource fingerprint tables for fast filtering              |
+---------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Ingestion Plane: SigNoz OTel Collector
&lt;/h3&gt;

&lt;p&gt;This isn't a vanilla OpenTelemetry Collector. It's a curated distribution with SigNoz-specific components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Receivers:&lt;/strong&gt; OTLP (gRPC 4317, HTTP 4318), Jaeger, Zipkin, Prometheus, Kafka, OpenCensus, HTTP logs (Heroku, JSON)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processors that matter:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;signozspanmetrics/delta&lt;/code&gt; -- computes RED metrics (Rate, Errors, Duration) from spans automatically. No separate metrics pipeline needed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;signozspanmapper&lt;/code&gt; -- normalizes framework-specific attributes (LangChain, LlamaIndex, etc.) into standard &lt;code&gt;gen_ai.*&lt;/code&gt; semantic conventions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;signozllmpricing&lt;/code&gt; -- multiplies token counts by per-model prices, writes cost attributes per span&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batch&lt;/code&gt; -- 50k spans / 5 seconds (ClickHouse best practice: large, infrequent inserts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Exporters:&lt;/strong&gt; ClickHouse (traces, logs, metrics, meter), metadata exporter (drives query builder autocomplete)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connector:&lt;/strong&gt; &lt;code&gt;signozmeter&lt;/code&gt; -- counts ingested volume per service/environment/host, powers usage dashboards&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extension:&lt;/strong&gt; OpAMP health check + managed config. The collector connects to the OpAMP server inside the SigNoz binary, receives its config remotely, and applies it without redeployment. Log pipelines, span mapper rules, LLM pricing -- all editable in the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Storage Plane: ClickHouse Schema Design
&lt;/h3&gt;

&lt;p&gt;This is where most observability platforms struggle. SigNoz's schema is worth understanding because it explains the query performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traces (&lt;code&gt;signoz_index_v3&lt;/code&gt;) and Logs (&lt;code&gt;logs_v2&lt;/code&gt;) share the same layout:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Main table:           Resource table (companion):
+-----------------+   +---------------------------+
| ts_bucket_start |   | resource_fingerprint (PK) |
| resource_fp ----+--&amp;gt;| labels (string map)       |
| trace_id        |   | seen_at_ts_bucket_start   |
| span_id         |   +---------------------------+
| duration_nano   |
| has_error       |
| ...attributes...|
+-----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resource attributes (&lt;code&gt;service.name&lt;/code&gt;, &lt;code&gt;k8s.namespace.name&lt;/code&gt;, &lt;code&gt;deployment.environment&lt;/code&gt;) are hashed into a &lt;code&gt;resource_fingerprint&lt;/code&gt;. The fingerprint is stored once per time bucket in the companion table. The main table only carries the fingerprint + timestamp bucket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query execution:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filter resource table by attributes (e.g., &lt;code&gt;service.name = 'payment-service'&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Get matching fingerprints&lt;/li&gt;
&lt;li&gt;Scan main table granules: &lt;code&gt;resource_fingerprint GLOBAL IN (...)&lt;/code&gt; + &lt;code&gt;ts_bucket_start&lt;/code&gt; range&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is why SigNoz docs tell you to &lt;strong&gt;always filter on resource attributes first&lt;/strong&gt;. Without a resource filter, you scan all granules. With it, you scan only the relevant ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metrics (&lt;code&gt;signoz_metrics_v2&lt;/code&gt;)&lt;/strong&gt; use a different layout optimized for time-series: metric name + labels as columns, value + timestamp. PromQL and ClickHouse SQL both work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TTL strategy:&lt;/strong&gt; Daily partitions, &lt;code&gt;ttl_only_drop_parts = 1&lt;/code&gt;. Retention drops whole parts (15 days for logs/traces, 30 days for metrics) -- no row-level deletes, no merge overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Actually Get: Feature Walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  APM That Works Out of the Box
&lt;/h3&gt;

&lt;p&gt;Instrument your app with OpenTelemetry (auto-instrumentation exists for Java, Python, Node, Go, .NET, PHP, Ruby). Send to SigNoz OTel Collector. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service map with latency/error/throughput per service&lt;/li&gt;
&lt;li&gt;Top endpoints with p50/p90/p99 latency, error rate, Apdex&lt;/li&gt;
&lt;li&gt;Database calls, external calls, message queue spans -- all correlated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No dashboard building required.&lt;/strong&gt; The APM views are generated from span data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Logs That Correlate With Traces
&lt;/h3&gt;

&lt;p&gt;Click a trace span -&amp;gt; see all logs with the same &lt;code&gt;trace_id&lt;/code&gt;. Click a log -&amp;gt; jump to its trace. This works because both share the resource fingerprint + time bucket layout. No separate correlation engine needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics From Spans (No Separate Pipeline)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;signozspanmetrics&lt;/code&gt; processor computes delta-temporality histograms and call counts per service/operation every 60 seconds. Dimensions: &lt;code&gt;service.namespace&lt;/code&gt;, &lt;code&gt;deployment.environment&lt;/code&gt;, &lt;code&gt;service.version&lt;/code&gt;. Your APM dashboards are powered by span-derived metrics -- you don't need a separate Prometheus scrape setup for RED metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLM/AI Observability Built In
&lt;/h3&gt;

&lt;p&gt;Since v0.143, the collector includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;signozspanmapper&lt;/code&gt; -- normalizes &lt;code&gt;input.value&lt;/code&gt;, &lt;code&gt;gen_ai.prompt&lt;/code&gt; -&amp;gt; standard &lt;code&gt;gen_ai.input.messages&lt;/code&gt; / &lt;code&gt;gen_ai.output.messages&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;signozllmpricing&lt;/code&gt; -- reads &lt;code&gt;gen_ai.usage.*_tokens&lt;/code&gt;, multiplies by model prices (pushed via OpAMP), writes &lt;code&gt;signoz.gen_ai.usage.*.cost&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get token usage, latency, cost per request, model breakdown -- all in the same traces view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alerting That Doesn't Require a Separate Stack
&lt;/h3&gt;

&lt;p&gt;The ruler (inside the SigNoz binary) evaluates alert rules on a schedule. Queries run against ClickHouse. Alertmanager handles deduplication, grouping, notification routing (Slack, PagerDuty, OpsGenie, webhook, email). Threshold, anomaly, and Apdex-based alerts all supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboards: Query Builder, PromQL, or Raw ClickHouse SQL
&lt;/h3&gt;

&lt;p&gt;Three query modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Query Builder&lt;/strong&gt; -- visual, no-code, generates ClickHouse SQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PromQL&lt;/strong&gt; -- for Prometheus migrants, runs via ClickHouse's PromQL-compatible engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ClickHouse SQL&lt;/strong&gt; -- full power for complex joins, cross-signal correlation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dashboard panels can mix all three. Variables, templating, drill-downs -- standard fare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: From Laptop to Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local (Docker Compose)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/SigNoz/signoz.git
&lt;span class="nb"&gt;cd &lt;/span&gt;signoz/deploy/docker/clickhouse-setup
./install.sh
&lt;span class="c"&gt;# UI at http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bare Metal (systemd via Foundry)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Foundry = declarative CLI for self-hosted SigNoz&lt;/span&gt;
&lt;span class="c"&gt;# One config provisions infra, installs backend, sets up OTel collection&lt;/span&gt;
foundry apply &lt;span class="nt"&gt;-f&lt;/span&gt; config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supports Docker, Swarm, systemd, Kubernetes, ECS, bare metal. Single binary + ClickHouse + Keeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kubernetes (Helm)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add signoz https://charts.signoz.io
helm &lt;span class="nb"&gt;install &lt;/span&gt;signoz signoz/signoz &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production: replicated ClickHouse (3+ shards), 3-node Keeper ensemble, PostgreSQL metastore, multiple SigNoz replicas behind ingress.&lt;/p&gt;

&lt;h3&gt;
  
  
  SigNoz Cloud
&lt;/h3&gt;

&lt;p&gt;Fully managed. 30-day free trial, usage-based pricing starting at $49/month. SOC 2 Type II, HIPAA, RBAC, SSO, BYOC option.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-offs (Be Honest)
&lt;/h2&gt;

&lt;p&gt;SigNoz isn't magic. Here's what you're signing up for:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Advantage&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;No vendor lock-in&lt;/strong&gt; -- OpenTelemetry native, your instrumentation is portable&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;You operate ClickHouse&lt;/strong&gt; -- need fast local disks (NVMe), CPU for merges, Keeper/ZooKeeper quorum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Predictable pricing&lt;/strong&gt; -- no per-host, per-custom-metric, per-user-seat fees&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Schema migrations&lt;/strong&gt; -- upgrades require running the migrator job, occasional breaking changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Single database for all signals&lt;/strong&gt; -- cross-signal correlation in SQL&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Operational complexity&lt;/strong&gt; -- more moving parts than a SaaS dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;High-cardinality friendly&lt;/strong&gt; -- filter by user_id, request_id freely&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Metrics cardinality still costs&lt;/strong&gt; -- unbounded labels on metrics create time series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Self-hostable on bare metal&lt;/strong&gt; -- no Docker/K8s required&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Foundry learning curve&lt;/strong&gt; -- new tool, replaces raw Compose files since v0.130&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The AGPL collector matters:&lt;/strong&gt; The OTel Collector distribution is AGPL-3.0. If you modify and redistribute it, you must open-source your changes. Running it unmodified? No issue. Building a managed service on top? Talk to a lawyer.&lt;/p&gt;

&lt;h2&gt;
  
  
  When SigNoz Makes Sense
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You're already on OpenTelemetry&lt;/strong&gt; or willing to adopt it (vendor-free instrumentation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want correlated logs/metrics/traces&lt;/strong&gt; without stitching three SaaS products&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-cardinality attributes&lt;/strong&gt; are part of your debugging workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have infrastructure capacity&lt;/strong&gt; (or budget for managed Cloud) to run ClickHouse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You prefer SQL/PromQL&lt;/strong&gt; over proprietary query languages&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Stick With Datadog/New Relic
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You only need infrastructure monitoring&lt;/strong&gt; (host maps, cloud integration depth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You don't want to operate a database&lt;/strong&gt; -- SaaS wins on operational simplicity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need Log Patterns / anomaly detection on logs&lt;/strong&gt; -- SigNoz doesn't have this yet (roadmap)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your team is small and observability isn't a core competency&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Start: Instrument a Go Service in 5 Minutes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// main.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"go.opentelemetry.io/otel"&lt;/span&gt;
    &lt;span class="s"&gt;"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"&lt;/span&gt;
    &lt;span class="s"&gt;"go.opentelemetry.io/otel/sdk/resource"&lt;/span&gt;
    &lt;span class="s"&gt;"go.opentelemetry.io/otel/sdk/trace"&lt;/span&gt;
    &lt;span class="n"&gt;semconv&lt;/span&gt; &lt;span class="s"&gt;"go.opentelemetry.io/otel/semconv/v1.26.0"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;initTracer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;otlptracehttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;otlptracehttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:4318"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;otlptracehttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithInsecure&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWithAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SchemaURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"go-demo"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeploymentEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;otel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;initTracer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;otel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"go-demo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"hello-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// Simulate work&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, SigNoz!"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run SigNoz locally (&lt;code&gt;./install.sh&lt;/code&gt; in the docker compose dir), run this service, hit &lt;code&gt;/hello&lt;/code&gt; a few times. Open &lt;code&gt;http://localhost:8080&lt;/code&gt; -&amp;gt; Traces -&amp;gt; &lt;code&gt;go-demo&lt;/code&gt;. You'll see the span, latency, and if you add logs, they'll correlate automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;SigNoz represents a shift in how observability is built: &lt;strong&gt;open standards + columnar storage + unified query layer&lt;/strong&gt; instead of &lt;strong&gt;proprietary agents + fragmented storage + per-signal pricing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The industry is moving toward OpenTelemetry anyway. Datadog, New Relic, Grafana, Splunk -- all support OTLP ingestion now. But they still charge you for the storage and query layer they built around it.&lt;/p&gt;

&lt;p&gt;SigNoz says: the storage and query layer should be open too.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/SigNoz/signoz" rel="noopener noreferrer"&gt;github.com/SigNoz/signoz&lt;/a&gt; (32k+ stars)&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://signoz.io/docs/" rel="noopener noreferrer"&gt;signoz.io/docs&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Slack:&lt;/strong&gt; &lt;a href="https://signoz.io/slack/" rel="noopener noreferrer"&gt;signoz.io/slack&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Try Cloud:&lt;/strong&gt; &lt;a href="https://signoz.io/teams/" rel="noopener noreferrer"&gt;signoz.io/teams&lt;/a&gt; (30-day free trial)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tags: opensource, observability, opentelemetry, clickhouse, datadog-alternative, selfhosted&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>clickhouse</category>
    </item>
    <item>
      <title>TigerBeetle: What Actually Happens When You Need a Database That Can't Lose Money</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Fri, 30 Oct 2026 11:16:52 +0000</pubDate>
      <link>https://dev.to/syed_anzar/tigerbeetle-what-actually-happens-when-you-need-a-database-that-cant-lose-money-4pg</link>
      <guid>https://dev.to/syed_anzar/tigerbeetle-what-actually-happens-when-you-need-a-database-that-cant-lose-money-4pg</guid>
      <description>&lt;h1&gt;
  
  
  TigerBeetle: What Actually Happens When You Need a Database That Can't Lose Money
&lt;/h1&gt;

&lt;p&gt;You use PostgreSQL. You use MySQL. You've probably heard of Redis, Cassandra, maybe even CockroachDB. But there's a database that financial infrastructure companies quietly rely on for the hot path of money movement—and almost no application developers know it exists.&lt;/p&gt;

&lt;p&gt;TigerBeetle.&lt;/p&gt;

&lt;p&gt;It's not a general-purpose database. It doesn't do JOINs. It doesn't have a query planner. It doesn't support arbitrary schemas. What it does is one thing: &lt;strong&gt;financial transactions at a million transfers per second on a single core, with durability guarantees that survive disk corruption, network partitions, and power loss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what actually happens under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: OLTP Isn't OLGP
&lt;/h2&gt;

&lt;p&gt;Most developers think of databases as "places to store data." But there's a fundamental split:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OLGP (Online General Purpose)&lt;/strong&gt; — PostgreSQL, MySQL, SQLite. Flexible schemas, ad-hoc queries, ACID transactions, indexes for everything. Great for "what's this user's order history?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OLTP (Online Transaction Processing)&lt;/strong&gt; — TigerBeetle. Rigid schema (double-entry bookkeeping), no ad-hoc queries, deterministic execution, hardware-aware storage. Great for "move $42.37 from account A to account B, right now, and never lose it."&lt;/p&gt;

&lt;p&gt;The world is becoming more transactional. India's UPI processed 16.9 billion payments in January 2025 &lt;em&gt;alone&lt;/em&gt;. Serverless APIs bill by the millisecond. Smart meters trade energy by the kilowatt-hour every 15 minutes. General-purpose databases weren't built for this write-heavy, high-contention reality.&lt;/p&gt;

&lt;p&gt;TigerBeetle was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Double-Entry Isn't Just Accounting—It's a Data Model
&lt;/h2&gt;

&lt;p&gt;Every transfer in TigerBeetle has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;debit_account_id&lt;/code&gt; — where value comes from&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;credit_account_id&lt;/code&gt; — where value goes to&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;amount&lt;/code&gt; — unsigned 128-bit integer (no floating point, ever)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ledger&lt;/code&gt; — which asset type (USD, BTC, kilowatt-hours, loyalty points)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;code&lt;/code&gt; — business event type (refund, payout, fee, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_data_128&lt;/code&gt; — your idempotency key (critical for exactly-once)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No NULLs. No variable-length strings. Every transfer is exactly &lt;strong&gt;128 bytes&lt;/strong&gt;, cache-line aligned. The entire schema is baked into the binary.&lt;/p&gt;

&lt;p&gt;Why double-entry? Because &lt;code&gt;credits == debits&lt;/code&gt; is an invariant the database can enforce &lt;em&gt;without&lt;/em&gt; application logic. If the sum ever diverges, something is corrupted. This isn't just accounting hygiene—it's a correctness check that runs at wire speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single-Threaded by Design (And Faster for It)
&lt;/h2&gt;

&lt;p&gt;"Wait, single-threaded? In 2026?"&lt;/p&gt;

&lt;p&gt;Yes. And here's why.&lt;/p&gt;

&lt;p&gt;Financial workloads are &lt;strong&gt;contention-heavy&lt;/strong&gt;. A few accounts (the "hot" ones) get hammered—think a payment processor's settlement account, or a popular creator's payout wallet. Transfers between hot accounts &lt;em&gt;must&lt;/em&gt; serialize. No amount of sharding or parallelism fixes this; Amdahl's Law sees to that.&lt;/p&gt;

&lt;p&gt;TigerBeetle leans into it: one thread, one event loop, zero locks, zero mutexes, zero GC pauses (it's written in Zig, no runtime). The &lt;code&gt;commit&lt;/code&gt; function that executes a batch of transfers is a &lt;strong&gt;tight CPU loop&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transfers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;debit_account_id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;credit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;credit_account_id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;insufficient_funds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;success&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two hash-map lookups, a bounds check, two integer additions. That's the "speed of light" for this problem. TigerBeetle gets within striking distance by eliminating everything else: no network round-trips to application code, no lock acquisition, no page cache indirection.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Prefetch Trick: IO Parallelism Without Thread Parallelism
&lt;/h2&gt;

&lt;p&gt;Here's the clever part. The &lt;code&gt;commit&lt;/code&gt; function &lt;em&gt;doesn't read from disk&lt;/em&gt;. It only touches in-memory state.&lt;/p&gt;

&lt;p&gt;Before &lt;code&gt;commit&lt;/code&gt; runs, a separate &lt;strong&gt;prefetch phase&lt;/strong&gt; analyzes the batch, predicts which accounts will be touched, and issues parallel async IO to load them into memory. While &lt;code&gt;commit&lt;/code&gt; executes sequentially on one core, the storage subsystem is doing parallel reads across all available IO queues (io_uring on Linux).&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;commit&lt;/code&gt; starts, the data is already hot. Zero stall cycles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consensus: Viewstamped Replication, Not Raft
&lt;/h2&gt;

&lt;p&gt;TigerBeetle replicates across 6 replicas using a Viewstamped Replication derivative (VSR)—the same lineage as Raft, but with a critical difference: &lt;strong&gt;the log is the source of truth, not the state machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each replica stores a single file: the "data file." It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A hash-chained Write-Ahead Log (WAL) of "prepares" (batches of ~8,000 transfers)&lt;/li&gt;
&lt;li&gt;A forest of LSM trees for the derived state (account balances, transfer history)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The primary accepts requests, orders them, creates prepares, appends to WAL, replicates to backups. Quorum (4 of 6) = committed. Replicas apply prepares in sequence number order. Deterministic state machine = identical state everywhere.&lt;/p&gt;

&lt;p&gt;But here's what makes it unusual: &lt;strong&gt;the data file uses copy-on-write blocks with checksums at every level.&lt;/strong&gt; Blocks are 0.5 MiB. Each block references children by (offset, checksum). The superblock (root) is atomically flushed to form checkpoints.&lt;/p&gt;

&lt;p&gt;If a replica crashes, it reads the last checkpoint, replays the WAL suffix, and arrives at the exact same state. Determinism guarantees it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Durability That Survives Silent Corruption
&lt;/h2&gt;

&lt;p&gt;This is where TigerBeetle diverges from almost every other database.&lt;/p&gt;

&lt;p&gt;Most databases trust &lt;code&gt;fsync()&lt;/code&gt;. If &lt;code&gt;fsync()&lt;/code&gt; returns success, the data is "safe." But &lt;code&gt;fsync()&lt;/code&gt; can lie—silent disk corruption, controller bugs, helical corruption (where a write lands on the wrong track). Jepsen found TigerBeetle survives all of this.&lt;/p&gt;

&lt;p&gt;How? &lt;strong&gt;Logical AND physical determinism.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every block has a checksum. Every block references children by checksum. The hash chain from superblock to leaf &lt;em&gt;mathematically proves&lt;/em&gt; the data wasn't corrupted. If a replica reads a block and the checksum mismatches, it doesn't panic—it fetches the same block from another replica (which has the identical hash chain) and repairs itself transparently.&lt;/p&gt;

&lt;p&gt;The system doesn't distinguish between "local disk" and "remote replica" for checksummed data. It's all just "sources of blocks with verified checksums." This is why TigerBeetle can guarantee durability even when storage lies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture Around It: Gateways, Not Direct Access
&lt;/h2&gt;

&lt;p&gt;You don't connect your frontend to TigerBeetle. The architecture looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Mobile App] → [API Gateway] → [TigerBeetle Cluster]
                    ↓
            [PostgreSQL (metadata)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticates/authorizes&lt;/li&gt;
&lt;li&gt;Generates idempotency keys (128-bit, client-persisted)&lt;/li&gt;
&lt;li&gt;Batches individual transfers into large prepares&lt;/li&gt;
&lt;li&gt;Maps string enums ("refund", "USD") to TigerBeetle's integer codes&lt;/li&gt;
&lt;li&gt;Caches ledger/account metadata (which never changes—append-only)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TigerBeetle only sees integer IDs. It never does string parsing, never talks to untrusted clients, never does auth. It just processes transfers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Don't Get (And Why That's the Point)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;TigerBeetle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JOINs&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secondary indexes (arbitrary)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arbitrary schemas&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interactive transactions&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ad-hoc analytics&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal sharding&lt;/td&gt;
&lt;td&gt;❌ (6 replicas = HA, not scale-out)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you need to query "all transfers for user X in March," you do that in PostgreSQL. TigerBeetle stores the transfer, PostgreSQL stores the metadata linking transfer IDs to user IDs.&lt;/p&gt;

&lt;p&gt;This separation is intentional. The hot path (recording money movement) stays fast and simple. The control plane (who owns what, what's the currency name) stays flexible.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Actually Need This
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Payment processors (millions of txns/sec)&lt;/li&gt;
&lt;li&gt;Marketplace payouts (high contention on creator wallets)&lt;/li&gt;
&lt;li&gt;Ledger services (immutable audit trail required)&lt;/li&gt;
&lt;li&gt;Real-time billing (per-second serverless metering)&lt;/li&gt;
&lt;li&gt;Loyalty/points systems (high volume, low value, strict balances)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you &lt;em&gt;don't&lt;/em&gt; need it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User profiles, product catalogs, content management&lt;/li&gt;
&lt;li&gt;Anything with ad-hoc query patterns&lt;/li&gt;
&lt;li&gt;Low-write, read-heavy workloads&lt;/li&gt;
&lt;li&gt;Prototyping (operational complexity of 6-replica cluster)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Mental Model Shift
&lt;/h2&gt;

&lt;p&gt;Stop thinking "database = general purpose store." Start thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Hot path = specialized OLTP database. Cold path = general purpose OLGP database.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TigerBeetle doesn't replace PostgreSQL. It sits &lt;em&gt;beside&lt;/em&gt; it, handling the one thing PostgreSQL struggles with: high-contention, write-heavy, durability-critical financial transfers at scale.&lt;/p&gt;

&lt;p&gt;And it does that one thing so well that a single core handles a million transfers per second—while your PostgreSQL cluster chokes on lock contention for the same workload.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;TigerBeetle is open source (Apache 2.0), written in Zig, and production-ready. The &lt;a href="https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/ARCHITECTURE.md" rel="noopener noreferrer"&gt;architecture docs&lt;/a&gt; are unusually readable for a systems project. The &lt;a href="https://docs.tigerbeetle.com/single-page/" rel="noopener noreferrer"&gt;single-page docs&lt;/a&gt; get you running in minutes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>oltp</category>
      <category>financial</category>
      <category>tigerbeetle</category>
    </item>
    <item>
      <title>How Function Calling Actually Works: Grammar-Constrained Decoding Under the Hood</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Thu, 29 Oct 2026 07:34:51 +0000</pubDate>
      <link>https://dev.to/syed_anzar/how-function-calling-actually-works-grammar-constrained-decoding-under-the-hood-1513</link>
      <guid>https://dev.to/syed_anzar/how-function-calling-actually-works-grammar-constrained-decoding-under-the-hood-1513</guid>
      <description>&lt;h1&gt;
  
  
  How Function Calling Actually Works: Grammar-Constrained Decoding Under the Hood
&lt;/h1&gt;

&lt;p&gt;You send a function definition with a JSON schema. The model responds with a perfect tool call — valid JSON, correct types, enum values from the allowed list, required fields present. No parsing errors. No retries.&lt;/p&gt;

&lt;p&gt;It looks like the model "understands" the schema. It doesn't.&lt;/p&gt;

&lt;p&gt;What actually happens: your JSON schema gets compiled into a context-free grammar. That grammar drives a pushdown automaton at inference time. At every decoding step, the automaton computes which vocabulary tokens would keep the output valid. Those tokens keep their logits. Every other token gets its logit set to negative infinity. The model samples from what remains.&lt;/p&gt;

&lt;p&gt;The model still decides &lt;em&gt;which&lt;/em&gt; tool to call and &lt;em&gt;what arguments&lt;/em&gt; to pass. But the braces, commas, quotes, property names, type constraints, and enum restrictions? Those are mechanically guaranteed. The model literally cannot produce invalid structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Free Text Doesn't Guarantee Structure
&lt;/h2&gt;

&lt;p&gt;An LLM trained on next-token prediction assigns probabilities to every token in its vocabulary. Nothing in the training objective forces the sequence to form valid JSON, match a regex, or satisfy a context-free grammar.&lt;/p&gt;

&lt;p&gt;The failure modes are familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing or extra braces — the model loses track of nesting depth&lt;/li&gt;
&lt;li&gt;Wrong type — an integer field gets a quoted string&lt;/li&gt;
&lt;li&gt;Hallucinated enum values — the model invents a value outside the allowed set&lt;/li&gt;
&lt;li&gt;Truncated output — the model emits EOS mid-object&lt;/li&gt;
&lt;li&gt;Key name drift — &lt;code&gt;user_id&lt;/code&gt; becomes &lt;code&gt;userId&lt;/code&gt; under paraphrase pressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Post-hoc repair (parsing with fallback, asking the model to fix its own output) adds latency and is unreliable. The only robust solution: prevent invalid token sequences from being sampled in the first place.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Key Insight: Local Validity Is Enough
&lt;/h2&gt;

&lt;p&gt;You don't need to forbid bad sequences globally. You only need to forbid the &lt;em&gt;single next token&lt;/em&gt; that, given what has been emitted so far, would make the sequence irrecoverable.&lt;/p&gt;

&lt;p&gt;This is exactly what an automaton state transition tells you.&lt;/p&gt;




&lt;h2&gt;
  
  
  From JSON Schema to Context-Free Grammar
&lt;/h2&gt;

&lt;p&gt;When you send a function tool definition, the provider's infrastructure converts the JSON schema into a context-free grammar (CFG). The conversion is mechanical:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JSON Schema&lt;/th&gt;
&lt;th&gt;Grammar Rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"type": "string"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches any valid JSON string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"type": "number"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches JSON number literals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;"type": "object"&lt;/code&gt; with &lt;code&gt;"properties"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Matches object with exactly those keys and value types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"enum": [...]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alternation listing only allowed literals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"required": [...]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restricts which property combinations are accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nested objects/arrays&lt;/td&gt;
&lt;td&gt;Recursive grammar rules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a &lt;code&gt;get_weather&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"celsius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fahrenheit"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting grammar (Lark-like notation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start: "{" ws pair_city ws maybe_unit "}"
pair_city: "\"city\"" ws ":" ws STRING
maybe_unit: ("," ws pair_unit)?
pair_unit: "\"unit\"" ws ":" ws UNIT_ENUM
UNIT_ENUM: "\"celsius\"" | "\"fahrenheit\""
STRING: "\"" /[^"\\]*/ "\""
ws: /\s*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This grammar accepts &lt;code&gt;{"city": "Tokyo"}&lt;/code&gt; and &lt;code&gt;{"city": "Tokyo", "unit": "celsius"}&lt;/code&gt; but rejects &lt;code&gt;{"city": 42}&lt;/code&gt;, &lt;code&gt;{"unit": "kelvin"}&lt;/code&gt;, and &lt;code&gt;{"temperature": 22}&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finite-State Machines vs Pushdown Automata
&lt;/h2&gt;

&lt;p&gt;Regular expressions (finite-state machines) handle flat patterns — phone numbers, UUIDs, ISO 8601 dates. They cannot handle arbitrary nesting.&lt;/p&gt;

&lt;p&gt;JSON with arbitrarily nested objects and arrays requires a &lt;strong&gt;context-free grammar&lt;/strong&gt;, which corresponds to a &lt;strong&gt;pushdown automaton&lt;/strong&gt; — a finite automaton plus a stack. The stack tracks nesting depth: every &lt;code&gt;{&lt;/code&gt; pushes, every &lt;code&gt;}&lt;/code&gt; pops. When the stack is empty and you're in an accepting state, the JSON is complete and valid.&lt;/p&gt;




&lt;h2&gt;
  
  
  Logit Masking: The Enforcement Mechanism
&lt;/h2&gt;

&lt;p&gt;At each decoding step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The model computes raw logits ℓ ∈ ℝ^|V| (one per vocabulary token)&lt;/li&gt;
&lt;li&gt;The grammar engine inspects tokens generated so far, determines valid next tokens given current automaton state&lt;/li&gt;
&lt;li&gt;Invalid tokens get logit = -∞&lt;/li&gt;
&lt;li&gt;Softmax, temperature, top-k, top-p proceed over remaining valid tokens&lt;/li&gt;
&lt;li&gt;Token is sampled from constrained distribution&lt;/li&gt;
&lt;li&gt;Automaton advances with the chosen token
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ℓ̃_v = ℓ_v          if valid[q_t][v] = 1
      = -∞           otherwise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After softmax, disallowed tokens have probability zero. The sampler operates on the masked distribution exactly as it would on an unconstrained one. Temperature scaling, top-p, top-k all compose cleanly.&lt;/p&gt;

&lt;p&gt;The model's relative preferences over &lt;em&gt;allowed&lt;/em&gt; tokens are preserved. If the model thought "Tokyo" was twice as likely as "Osaka" before constraining, it's still twice as likely after. The grammar only removes options that would violate the schema.&lt;/p&gt;




&lt;h2&gt;
  
  
  Token-Level Masks: Why Character Simulation Matters
&lt;/h2&gt;

&lt;p&gt;Tokenizers operate over multi-character tokens, not single characters. A token like &lt;code&gt;"weather"&lt;/code&gt; (single token) or &lt;code&gt;"42"&lt;/code&gt; (single token in many tokenizers) consumes multiple characters at once.&lt;/p&gt;

&lt;p&gt;The mask must simulate each token's &lt;em&gt;full character span&lt;/em&gt; through the automaton, not just check the first character.&lt;/p&gt;

&lt;p&gt;Example: Grammar requires exactly 4 digits (&lt;code&gt;[0-9]{4}&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At state q0 (0 digits seen): token &lt;code&gt;"27"&lt;/code&gt; (2 chars) is &lt;strong&gt;allowed&lt;/strong&gt; → lands at q2&lt;/li&gt;
&lt;li&gt;At state q3 (3 digits seen): token &lt;code&gt;"27"&lt;/code&gt; is &lt;strong&gt;forbidden&lt;/strong&gt; → second char would overshoot into dead state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A token's legality flips with the state. This is why production systems (XGrammar, llguidance, Outlines) pre-compute token-level masks by simulating each token's full decoded string through the automaton.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inside XGrammar: Compiled Grammar + Matcher
&lt;/h2&gt;

&lt;p&gt;XGrammar (used in vLLM, SGLang) implements this at production speed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grammar&lt;/strong&gt; — describes output structure (JSON Schema → CFG)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GrammarCompiler&lt;/strong&gt; — compiles grammar to &lt;strong&gt;CompiledGrammar&lt;/strong&gt; (pre-computes token masks, builds transition tables)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GrammarMatcher&lt;/strong&gt; — stateful, per-request object:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;accept_token(token_id)&lt;/code&gt; — advances internal state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fill_next_token_bitmask()&lt;/code&gt; — produces 32-bit compressed bitmask for current state&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bitmask is a compressed bitset (int32 array). For 128K vocabulary: ~16 KB per request. The matcher only runs on CPU; the bitmask is copied to GPU where a single kernel applies it to logits before sampling.&lt;/p&gt;

&lt;p&gt;Adaptive token mask caching: requests with identical grammar/tokenizer state share masks. The cache key is the stack top node — validity of future tokens is determined by the current parser state.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Function Calling Pipeline End-to-End
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Schema Conversion
   JSON Schema → Context-Free Grammar (cached)

2. Prompt Construction
   Tool definitions serialized into model's prompt format
   (Harmony tokens, XML-wrapped definitions, etc.)

3. Free Generation
   Model generates conversational text unconstrained
   Until it emits tool-call trigger token (e.g., `&amp;lt;|tool_call|&amp;gt;`)

4. Constrained Generation
   Grammar engine activates
   At each step: compute valid tokens → mask logits → sample → advance automaton
   Continues until complete valid JSON object produced

5. Parsing
   Output (guaranteed structurally valid) parsed to extract function name + args

6. Execution
   Tool call returned to developer or executed server-side
   Result flows back to model, generation continues
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Structural correctness is a hard guarantee.&lt;/strong&gt; The model's training only needs to handle the semantic side: deciding &lt;em&gt;which&lt;/em&gt; tool to call and &lt;em&gt;what argument values&lt;/em&gt; to pass. The scaffolding — braces, commas, quotes, property names, type constraints — is enforced mechanically at every token.&lt;/p&gt;

&lt;p&gt;This is why function calls almost never return malformed JSON. Not because the model is that reliable. Because the sampling process won't let it be unreliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Developers Should Know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt the structure anyway.&lt;/strong&gt; Constrained decoding only affects sampling. You want the model's underlying distribution to already align with the structure. Describe the expected output in the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First request with a new schema is slower.&lt;/strong&gt; Grammar compilation and caching happen on first use. Subsequent requests reuse the compiled grammar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not all JSON Schema features are enforceable.&lt;/strong&gt; Recursive schemas, unbounded nesting, and some validation keywords may not be fully enforced by the decoder. Check your provider's supported subset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API-based services can't do this.&lt;/strong&gt; OpenAI, Anthropic APIs don't expose logit access. Constrained decoding requires access to the decoding loop (local models, vLLM, SGLang, self-hosted).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Function calling isn't magic. It's not prompt engineering. It's a pipeline where a JSON schema compiles to a formal grammar, and that grammar constrains the model's sampling so it &lt;em&gt;can only&lt;/em&gt; produce valid output.&lt;/p&gt;

&lt;p&gt;The model does the hard part: understanding intent, choosing the right tool, selecting the right values. The grammar engine does the mechanical part: guaranteeing every brace, comma, quote, and type constraint is correct.&lt;/p&gt;

&lt;p&gt;Next time you see a perfect tool call, remember: the model didn't "learn JSON." The decoder was simply forbidden from getting it wrong.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>functioncalling</category>
    </item>
    <item>
      <title>DuckDB: What Actually Happens Under the Hood — Vectorized Execution, Push-Based Pipelines, and Morsel-Driven Parallelism</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Wed, 28 Oct 2026 13:00:14 +0000</pubDate>
      <link>https://dev.to/syed_anzar/duckdb-what-actually-happens-under-the-hood-vectorized-execution-push-based-pipelines-and-4fi4</link>
      <guid>https://dev.to/syed_anzar/duckdb-what-actually-happens-under-the-hood-vectorized-execution-push-based-pipelines-and-4fi4</guid>
      <description>&lt;h1&gt;
  
  
  DuckDB: What Actually Happens Under the Hood — Vectorized Execution, Push-Based Pipelines, and Morsel-Driven Parallelism
&lt;/h1&gt;

&lt;p&gt;You know DuckDB as "SQLite for analytics." You've probably run &lt;code&gt;SELECT * FROM 'data.parquet'&lt;/code&gt; and watched it scan millions of rows in milliseconds. But do you know &lt;em&gt;why&lt;/em&gt; it's that fast?&lt;/p&gt;

&lt;p&gt;Most developers treat DuckDB as a black box: embed it, query it, get results. But the speed comes from three deliberate architectural choices that fundamentally differ from how PostgreSQL, SQLite, or even Spark execute queries.&lt;/p&gt;

&lt;p&gt;Let's open the box.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Pillars
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traditional DBs (PostgreSQL, SQLite)&lt;/th&gt;
&lt;th&gt;DuckDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Row-at-a-time (Volcano iterator)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Vectorized&lt;/strong&gt;: 2,048 rows per batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pull-based: parent asks child for data&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Push-based&lt;/strong&gt;: child pushes to parent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Centralized executor decides parallelism&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Morsel-driven&lt;/strong&gt;: each operator self-parallelizes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These aren't optimizations. They're a different execution model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vectorized Execution: Why 2,048 Rows?
&lt;/h2&gt;

&lt;p&gt;DuckDB doesn't process rows. It processes &lt;strong&gt;vectors&lt;/strong&gt; — contiguous arrays of 2,048 values per column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Traditional row-at-a-time (PostgreSQL, SQLite)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// 1 function call per row, terrible cache usage, no SIMD&lt;/span&gt;

&lt;span class="c1"&gt;// DuckDB vectorized&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// 2048 values in contiguous memory&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fn_simd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// 1 function call, SIMD-friendly&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why 2,048?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fits in L1/L2 cache (~16 KB for int64)&lt;/li&gt;
&lt;li&gt;Power of 2 -&amp;gt; efficient bit operations&lt;/li&gt;
&lt;li&gt;Large enough to amortize function call overhead&lt;/li&gt;
&lt;li&gt;Small enough to avoid memory pressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each vector lives inside a &lt;strong&gt;DataChunk&lt;/strong&gt; — the fundamental unit of data flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="nl"&gt;DataChunk:&lt;/span&gt;
  &lt;span class="n"&gt;Column&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;int2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;int2048&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;// 2048 integers&lt;/span&gt;
  &lt;span class="n"&gt;Column&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;str1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;str2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;str2048&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;// 2048 strings&lt;/span&gt;
  &lt;span class="n"&gt;Column&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ts1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;ts2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;ts2048&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="c1"&gt;// 2048 timestamps&lt;/span&gt;
  &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The count can be less than 2048 — that's how filters work. A &lt;code&gt;WHERE&lt;/code&gt; clause doesn't remove rows; it just reduces the &lt;code&gt;count&lt;/code&gt; field. The underlying memory stays allocated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Push-Based Execution: Data Flows Downhill
&lt;/h2&gt;

&lt;p&gt;Traditional databases use the &lt;strong&gt;Volcano iterator model&lt;/strong&gt; (pull-based):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Parent pulls from child&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;parent_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// if parent exists&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problem: the parent controls the pace. The child can't push multiple chunks ahead. You get pipeline bubbles — the parent waits for the child, the child waits for I/O, nobody overlaps work.&lt;/p&gt;

&lt;p&gt;DuckDB flips it. &lt;strong&gt;Children push to parents:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Child pushes to parent&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Child&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataChunk&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// push, don't wait&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What this enables
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;True pipelining&lt;/strong&gt; — A chunk flows through Filter -&amp;gt; Project -&amp;gt; Aggregate in one pass. No materialization between operators.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cache locality&lt;/strong&gt; — Data stays hot in L1/L2 as it moves through the pipeline. The same 2048-value array gets transformed in place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Natural parallelism&lt;/strong&gt; — Multiple pipelines run independently on different cores. No central scheduler bottleneck.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backpressure for free&lt;/strong&gt; — If a parent's buffer fills, the child's &lt;code&gt;Execute&lt;/code&gt; blocks. I/O pauses propagate naturally.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Morsel-Driven Parallelism: No Central Scheduler
&lt;/h2&gt;

&lt;p&gt;Most parallel databases have a central executor that hands out work. DuckDB doesn't.&lt;/p&gt;

&lt;p&gt;Instead, &lt;strong&gt;each operator decides its own parallelism&lt;/strong&gt; based on the data it sees.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;idx_t&lt;/span&gt; &lt;span class="n"&gt;PhysicalOperator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EstimatedThreadCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Table scan: parallelism = row groups / 2&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardinality&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROW_GROUP_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;UNION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Sum children's threads&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EstimatedThreadCount&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Other operators: max of children&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EstimatedThreadCount&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A table with 1M rows (8 row groups of ~122K each) on 4 cores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread 1: row groups 0, 1
Thread 2: row groups 2, 3
Thread 3: row groups 4, 5
Thread 4: row groups 6, 7
-&amp;gt; All partial results combined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No synchronization during processing.&lt;/strong&gt; Threads only sync at pipeline boundaries (HashJoin build, HashAggregate, Order).&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline breakers
&lt;/h3&gt;

&lt;p&gt;Some operators &lt;em&gt;must&lt;/em&gt; materialize and break the pipeline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;Why it breaks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HashJoin (build side)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must build complete hash table before probing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HashAggregate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must see all rows before finalizing groups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Order (Sort)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must collect all rows before sorting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DuckDB organizes operators into &lt;strong&gt;pipelines&lt;/strong&gt; separated by these breakers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="n"&gt;Pipeline&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TableScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HashAggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt; &lt;span class="n"&gt;different&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt; &lt;span class="n"&gt;builds&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;

&lt;span class="n"&gt;Pipeline&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashAggregate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Single&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="n"&gt;aggregates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;

&lt;span class="n"&gt;Pipeline&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Early&lt;/span&gt; &lt;span class="n"&gt;termination&lt;/span&gt; &lt;span class="k"&gt;after&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Optimizer: Stratified and Subquery-Aware
&lt;/h2&gt;

&lt;p&gt;Before execution, DuckDB runs a &lt;strong&gt;stratified optimizer&lt;/strong&gt; — multiple passes, each with a specific goal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Logical rewrites&lt;/strong&gt; — predicate pushdown, projection pushdown, join reordering, subquery unnesting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physical planning&lt;/strong&gt; — choose operators, partition joins, decide parallelism&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive execution&lt;/strong&gt; — runtime statistics feed back into filter ordering, join strategy&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Predicate pushdown (logical)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Original&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Optimized: filter pushed into table scan&lt;/span&gt;
&lt;span class="n"&gt;TableScan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DuckDB reads &lt;em&gt;only&lt;/em&gt; the &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; columns. The other 48 columns never leave disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Projection pushdown
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Only 'name' and 'age' columns read from storage&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Columnar storage + projection pushdown = massive I/O reduction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Join ordering
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Small table (countries) as build side, large table (users) as probe side&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;countries&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DuckDB estimates cardinalities and puts the smaller table on the build side automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adaptive Execution: Runtime Feedback
&lt;/h2&gt;

&lt;p&gt;DuckDB doesn't just trust the optimizer. It adapts &lt;em&gt;during&lt;/em&gt; execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adaptive filtering
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;expensive_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;cheap_column&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The executor tracks selectivity. If &lt;code&gt;cheap_column = 'value'&lt;/code&gt; eliminates 99% of rows, it evaluates that first next time — even if the optimizer ordered it differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adaptive hash joins
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data size&lt;/th&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;In-memory hash table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Partitioned hash table (radix)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Disk-based partitioning with spillage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same operator switches strategies mid-query based on actual row counts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means in Practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query Parquet files directly — no ETL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;duckdb&lt;/span&gt;

&lt;span class="c1"&gt;# Zero-copy: DuckDB reads Parquet column chunks directly
# No import, no copy, no schema inference step
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;duckdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SELECT station_id, AVG(temperature)
    FROM &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3://weather-data/2024/*.parquet&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
    WHERE year = 2024
    GROUP BY station_id
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;df&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;httpfs&lt;/code&gt; extension streams row groups. Predicate pushdown skips entire row groups via Parquet statistics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Larger-than-memory analytics
&lt;/h3&gt;

&lt;p&gt;DuckDB 0.9+ (Sep 2023) added out-of-core support for &lt;strong&gt;all operators&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash aggregate: spills to disk when groups exceed memory&lt;/li&gt;
&lt;li&gt;Hash join: partitions build/probe sides to disk&lt;/li&gt;
&lt;li&gt;Sort: external merge sort&lt;/li&gt;
&lt;li&gt;Window functions: segment tree with disk spill
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 50 GB data, 16 GB RAM — works&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'s3://huge-dataset/*.parquet'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  When NOT to Use DuckDB
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Better choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;High-concurrency OLTP (many writers)&lt;/td&gt;
&lt;td&gt;PostgreSQL, MySQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile/embedded with tiny footprint&lt;/td&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time replication, read replicas&lt;/td&gt;
&lt;td&gt;PostgreSQL + streaming replication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex transactions, foreign keys&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector search (semantic similarity)&lt;/td&gt;
&lt;td&gt;pgvector, sqlite-vec, or dedicated vector DB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DuckDB is &lt;strong&gt;single-writer, multiple-reader&lt;/strong&gt;. It's not a general-purpose RDBMS replacement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mental Model Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it means for you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vector (2048 rows)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU processes batches, not rows. SIMD, cache-friendly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DataChunk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Columns travel together. Filter = reduce count, not delete memory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Push-based&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data flows downhill. Pipelines fuse operators. No iterator overhead.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Morsel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~122K rows. Unit of parallelism. Threads steal morsels dynamically.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pipeline breaker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HashJoin build, HashAggregate, Sort. Synchronization points.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adaptive&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runtime stats override static plans. Filter reordering, join spill.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Peek Under the Hood Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- See the physical plan with parallelism&lt;/span&gt;
&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Enable detailed profiling&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;enable_profiling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;profiling_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'profile.json'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;span class="c1"&gt;-- Open profile.json: operator timings, memory, thread counts, actual vs estimated rows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The profile shows exactly which pipelines ran in parallel, where data spilled, and whether the optimizer's estimates matched reality.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;DuckDB isn't "SQLite for analytics" because of its API. It's "SQLite for analytics" because it &lt;strong&gt;threw away the Volcano iterator model&lt;/strong&gt; and built an execution engine from first principles for analytical workloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Columnar storage&lt;/strong&gt; -&amp;gt; read only what you need&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vectorized (2048)&lt;/strong&gt; -&amp;gt; SIMD, cache, amortized overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push-based&lt;/strong&gt; -&amp;gt; fused pipelines, no materialization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Morsel-driven&lt;/strong&gt; -&amp;gt; self-parallelizing operators, no central bottleneck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive&lt;/strong&gt; -&amp;gt; runtime reality beats static estimates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next time you run a query and get results in milliseconds on a 10 GB Parquet file, you'll know exactly which thread processed which morsel, which pipeline it flowed through, and why it didn't need a server.&lt;/p&gt;

&lt;p&gt;That's not magic. That's mechanical sympathy.&lt;/p&gt;

</description>
      <category>duckdb</category>
      <category>database</category>
      <category>performance</category>
      <category>systems</category>
    </item>
    <item>
      <title>Test Article</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:56:41 +0000</pubDate>
      <link>https://dev.to/syed_anzar/test-article-71j</link>
      <guid>https://dev.to/syed_anzar/test-article-71j</guid>
      <description>&lt;h1&gt;
  
  
  Test
&lt;/h1&gt;

&lt;p&gt;This is a test article.&lt;/p&gt;

</description>
      <category>test</category>
    </item>
    <item>
      <title>How RAG Actually Works: Chunking, Embeddings, Retrieval, and Reranking Under the Hood</title>
      <dc:creator>Syed Anzar</dc:creator>
      <pubDate>Wed, 28 Oct 2026 07:14:50 +0000</pubDate>
      <link>https://dev.to/syed_anzar/how-rag-actually-works-chunking-embeddings-retrieval-and-reranking-under-the-hood-12a</link>
      <guid>https://dev.to/syed_anzar/how-rag-actually-works-chunking-embeddings-retrieval-and-reranking-under-the-hood-12a</guid>
      <description>&lt;h1&gt;
  
  
  How RAG Actually Works: Chunking, Embeddings, Retrieval, and Reranking Under the Hood
&lt;/h1&gt;

&lt;p&gt;RAG feels simple on paper. Split documents into chunks, embed them, fetch the closest ones at query time. Done.&lt;/p&gt;

&lt;p&gt;That description hides half a dozen hard sub-problems. Split too coarsely and you retrieve paragraphs that bury the relevant sentence in noise. Split too finely and no single chunk carries enough context to be useful. Use only dense retrieval and you miss documents where an exact product code or rare proper noun is the critical signal. Use only keyword search and you miss paraphrases. Retrieve top-k blindly and the final context window fills with redundant or marginally relevant chunks.&lt;/p&gt;

&lt;p&gt;Most tutorials stop at "embed, store, retrieve." This article goes deeper — what actually happens at each stage, where the trade-offs live, and what production systems do differently.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline at a Glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INGESTION (offline, whenever documents change)
sources → parse/clean → deduplicate → chunk → embed (dense) + index terms (BM25) → store vectors, text, metadata

QUERY (online, per request)
question → (optional) rewrite/expand → hybrid retrieval (BM25 + dense, top 50–200) → rerank with cross-encoder (keep top 5–10) → select for relevance/diversity within token budget → build prompt with numbered sources → generate with citation instructions → check citations → return answer with links
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two pipelines meet at the index. The offline path runs when documents change. The online path runs per request. Keeping them aligned is where most RAG systems break.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Chunking: The Hardest Part Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Every embedding model has a maximum token length — 512 tokens for older bi-encoders, 4,096–8,192 for modern ones (&lt;code&gt;text-embedding-3-large&lt;/code&gt;, &lt;code&gt;nomic-embed-text-v1.5&lt;/code&gt;). But even if your model supports 8k tokens, shoving an entire chapter into a single vector is a mistake: the embedding averages the semantics of every sentence, so a query about one narrow fact scores poorly against a dense multi-topic chunk that happens to contain that fact.&lt;/p&gt;

&lt;p&gt;Chunking controls a fundamental quality–recall trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Too large chunks&lt;/strong&gt; — high recall (the fact is somewhere in there), but low precision (the LLM gets noise and may hallucinate or ignore the signal)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too small chunks&lt;/strong&gt; — high precision, but context loss: "It increased by 12%" is meaningless without its referent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting chunking wrong is probably the single most common cause of poor RAG performance in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixed-Length Chunking with Overlap
&lt;/h3&gt;

&lt;p&gt;The simplest approach: split every &lt;code&gt;chunk_size&lt;/code&gt; tokens, with an &lt;code&gt;overlap&lt;/code&gt; of tokens shared between adjacent chunks so sentence boundaries don't cut off context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fixed_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk_size must be strictly greater than overlap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical production values: &lt;code&gt;chunk_size=256–512&lt;/code&gt; tokens with &lt;code&gt;overlap=32–64&lt;/code&gt;. The overlap parameter is crucial — without it, a sentence split across two chunks loses its grammatical antecedent in both halves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic Chunking
&lt;/h3&gt;

&lt;p&gt;Fixed-length chunking is agnostic to content structure. Semantic chunking exploits embedding similarity to find natural topic breaks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split text into sentences&lt;/li&gt;
&lt;li&gt;Embed each sentence&lt;/li&gt;
&lt;li&gt;Compute cosine similarity between consecutive sentence embeddings&lt;/li&gt;
&lt;li&gt;Mark a chunk boundary wherever similarity drops below a threshold (or is a local minimum)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;semantic_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;embed_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_sentences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;embed_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# shape (N, D)
&lt;/span&gt;    &lt;span class="n"&gt;sims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;cosine_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sims&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;is_boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_sentences&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_boundary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Semantic chunking produces more coherent chunks but is slower (requires an embedding call) and more sensitive to the threshold. A common tuning strategy: run the chunker on a held-out set, visualize the similarity curve, and set the threshold at the 15th percentile of observed similarities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structure-Aware Chunking
&lt;/h3&gt;

&lt;p&gt;Real documents have structure: headings, paragraphs, bullet lists, code blocks. Respecting that structure almost always outperforms purely statistical methods.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The production equivalent: LangChain's MarkdownHeaderTextSplitter
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_text_splitters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MarkdownHeaderTextSplitter&lt;/span&gt;

&lt;span class="n"&gt;md_splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MarkdownHeaderTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;headers_to_split_on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;##&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;###&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;md_splitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;markdown_document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Each chunk carries heading metadata for downstream filtering
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The libraries you actually ship.&lt;/strong&gt; Nobody writes chunkers from scratch twice — you write them once to understand the trade-off, then use maintained splitters. The two dominant ones are &lt;strong&gt;LangChain&lt;/strong&gt; (&lt;code&gt;langchain_text_splitters&lt;/code&gt;) and &lt;strong&gt;LlamaIndex&lt;/strong&gt; (&lt;code&gt;llama_index.core.node_parser&lt;/code&gt;), and both express exactly the strategies above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Late Chunking (The Context-Preserving Trick)
&lt;/h3&gt;

&lt;p&gt;Late chunking, introduced by Günther et al. (2024) and popularized with &lt;code&gt;jina-embeddings-v2&lt;/code&gt;, inverts the usual order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pass the &lt;strong&gt;entire document&lt;/strong&gt; through the transformer encoder to produce contextualized token embeddings&lt;/li&gt;
&lt;li&gt;Mean-pool over the token spans corresponding to each logical chunk&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The attention mechanism lets every token see its full document context &lt;em&gt;before&lt;/em&gt; the pooling boundary is applied. "It increased by 12%" gets an embedding that encodes what "it" refers to, because self-attention already saw the preceding paragraph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# late_chunking.py — illustrative
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;late_chunk_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_boundaries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jinaai/jina-embeddings-v2-base-en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LateChunk&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trust_remote_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trust_remote_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_offsets_mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;truncation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;offset_mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;offset_mapping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;input_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;attention_mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attention_mask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;token_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attention_mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;attention_mask&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;last_hidden_state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;start_char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunk_boundaries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;token_mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset_mapping&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;start_char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset_mapping&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;end_char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset_mapping&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;offset_mapping&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;span_embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token_embeddings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token_mask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;span_embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;chunk_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;span_embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chunk_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk_embedding&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_embedding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LateChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start_char&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;end_char&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunk_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_char&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;start_char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_char&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;end_char&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Constraint: the full document must fit within the model's context window (~8k tokens). For longer documents, apply late chunking within sliding windows.&lt;/p&gt;

&lt;p&gt;Late chunking has a generative cousin: &lt;strong&gt;contextual retrieval&lt;/strong&gt; (Anthropic, 2024) asks an LLM to write a one-sentence situating preamble for each chunk and prepends it before embedding. Late chunking is cheap (one encoder pass, no LLM) but needs a long-context encoder; contextual retrieval works with any encoder but costs one LLM call per chunk at index time. They compose.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Hybrid Search: BM25 + Dense Retrieval
&lt;/h2&gt;

&lt;p&gt;Dense retrieval excels at semantic similarity but struggles with exact-match recall. If a user asks about "CVE-2023-44487" or a specific product SKU, the query embedding may not land near the document containing that exact string — because the model generalizes. BM25 handles these cases natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BM25 score&lt;/strong&gt; for query &lt;em&gt;q&lt;/em&gt; against document &lt;em&gt;d&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BM25(q,d) = Σ IDF(t) * (f(t,d) * (k1+1)) / (f(t,d) + k1 * (1 - b + b * |d|/avgdl))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;f(t,d)&lt;/code&gt; is term frequency, &lt;code&gt;|d|&lt;/code&gt; is document length, &lt;code&gt;avgdl&lt;/code&gt; is average document length, &lt;code&gt;k1≈1.2–2.0&lt;/code&gt;, &lt;code&gt;b≈0.75&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a real BM25 engine.&lt;/strong&gt; A naive Python implementation rescores the entire corpus on every query — O(N⋅|q|) — fine for teaching, hopeless beyond a few thousand chunks. Real lexical retrieval uses an &lt;strong&gt;inverted index&lt;/strong&gt;: for each query term, walk only the posting list of documents that actually contain it.&lt;/p&gt;

&lt;p&gt;Open-source ladder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bm25s&lt;/code&gt;&lt;/strong&gt; — sparse-matrix BM25 in Python; right default for single-process index up to millions of short chunks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pyserini&lt;/strong&gt; — Python bindings over Lucene; research-grade path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elasticsearch / OpenSearch&lt;/strong&gt; — distributed inverted index when corpus outgrows one machine; both ship native hybrid retrieval and built-in RRF&lt;/li&gt;
&lt;li&gt;Most vector databases now do the sparse leg too: Qdrant, Weaviate, Milvus all accept sparse vectors alongside dense and expose server-side RRF fusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reciprocal Rank Fusion (RRF)
&lt;/h3&gt;

&lt;p&gt;Given a BM25 ranking and a dense ranking, how to combine them? Cormack et al. (2009) introduced RRF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RRF(d) = Σ 1 / (k + rank_r(d))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;k = 60&lt;/code&gt; (smoothing constant). Documents not in a ranking are omitted from that term. Highest combined RRF score wins.&lt;/p&gt;

&lt;p&gt;RRF does not require score normalization — it only uses rank ordinals, making it immune to the scale mismatch between BM25 scores (0–20) and cosine similarities (0.5–1.0).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reciprocal_rank_fusion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rankings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]]],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="n"&gt;rrf_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ranked_list&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rankings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;rrf_scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;doc_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rrf_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worked example: BM25 ranks [D2, D1, D3, D4, D5], dense ranks [D1, D3, D2, D5, D4]. With k=60:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;D1: 1/62 + 1/61 = 0.0325 → &lt;strong&gt;rank 1&lt;/strong&gt; (wins despite BM25 rank 2)&lt;/li&gt;
&lt;li&gt;D2: 1/61 + 1/63 = 0.0323 → rank 2&lt;/li&gt;
&lt;li&gt;D3: 1/63 + 1/62 = 0.0320 → rank 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;D1 wins in fused ranking because its dense rank of 1 contributes a large term. The k=60 constant prevents any single top-1 ranking from dominating completely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learned Sparse Retrieval (SPLADE)
&lt;/h3&gt;

&lt;p&gt;BM25 and dense retrieval fail in opposite directions. &lt;strong&gt;Learned sparse retrieval&lt;/strong&gt; keeps the representation sparse (a vector over the tokenizer's vocabulary, lives in an inverted index) but &lt;em&gt;learns&lt;/em&gt; the weights with a transformer instead of counting term frequencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SPLADE&lt;/strong&gt; (Formal et al., SIGIR 2021) runs text through a masked-language-model head, producing a logit for every vocabulary term at every position. Then saturate and max-pool over positions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;w_j = max_i log(1 + ReLU(w_ij))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The log(1+·) is a saturation function borrowed from BM25's term-frequency damping. Sparsity is trained in with a FLOPS regularizer — typical passage keeps ~100 non-zero terms.&lt;/p&gt;

&lt;p&gt;Payoff: &lt;strong&gt;term expansion&lt;/strong&gt;. A passage about "myocardial infarction" gets non-zero weight on &lt;code&gt;heart&lt;/code&gt; and &lt;code&gt;attack&lt;/code&gt; even though those strings never appear, so a lexical index can now match a paraphrase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sentence-transformers v5+ added SparseEncoder
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparseEncoder&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SparseEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;naver/splade-v3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;doc_emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corpus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# sparse tensors, |V|-dimensional
&lt;/span&gt;&lt;span class="n"&gt;query_emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http2 rapid reset attack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_emb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc_emb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# sparse dot product
&lt;/span&gt;
&lt;span class="c1"&gt;# Inspect *why* a document scored
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc_emb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# e.g. [('http', 2.1), ('reset', 1.9), ...]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SPLADE is a first-stage retriever — drop-in replacement for (or third leg alongside) BM25. Fuse its ranking with dense ranking using RRF, then rerank with cross-encoder. Costs are real: encoding requires a transformer forward pass at both index and query time, and expansion terms lengthen posting lists. The honest 2026 summary: BM25 + strong dense encoder remains the pragmatic default; learned sparse is the upgrade when exact-match recall matters &lt;em&gt;and&lt;/em&gt; queries are paraphrase-heavy, or when you need interpretability without giving up semantic matching.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Cross-Encoder Rerankers: The Precision Stage
&lt;/h2&gt;

&lt;p&gt;The retrieval stage (BM25 or dense) must be fast — processing millions of documents in tens of milliseconds. Speed requires a &lt;strong&gt;bi-encoder&lt;/strong&gt; architecture: query and document embedded independently, similarity = cheap dot product. Downside: query and document tokens never "see" each other during encoding, so nuanced relevance signals (multi-hop, contrastive relevance) can be missed.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;cross-encoder&lt;/strong&gt; receives the concatenation &lt;code&gt;[query; document]&lt;/code&gt; as a single sequence and produces a scalar relevance score. Every query token attends to every document token, enabling fine-grained relevance reasoning at the cost of O(N) forward passes (one per candidate). This makes cross-encoders unsuitable for first-stage retrieval but ideal for &lt;strong&gt;reranking&lt;/strong&gt; a small shortlist of 20–100 candidates.&lt;/p&gt;

&lt;p&gt;There's a well-defined middle: &lt;strong&gt;late interaction (ColBERT)&lt;/strong&gt; keeps one vector per &lt;em&gt;token&lt;/em&gt; rather than one per document, precomputes all document token vectors offline like a bi-encoder, and scores with a MaxSim operator recovering much of the cross-encoder's token-level matching. Costs ~10–100x more index storage in exchange for cross-encoder-like quality at first-stage speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two-Stage Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query → Hybrid Retrieval (BM25 + dense) → top-100 candidates
          fast, cheap recall
      → Cross-Encoder Reranker → scores all 100 [query; passage] pairs
          slower, expensive, precise scoring
      → top-5 chunks → LLM Context Window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the 5 winning chunks reach the LLM context window, keeping prompt costs low while the cross-encoder's joint query-passage attention ensures relevance quality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CrossEncoder&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_reranker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cross-encoder/ms-marco-MiniLM-L-6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CrossEncoder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CrossEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CrossEncoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="n"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;passage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;passage&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Latency numbers (A100):
# cross-encoder/ms-marco-MiniLM-L-6-v2, 100 candidates, avg 200 tokens: ~60ms
# On CPU (Intel Xeon): ~400ms for same 100 candidates
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Practitioner tip:&lt;/strong&gt; Two APIs, one job. Classical BERT-style rerankers (&lt;code&gt;ms-marco-*&lt;/code&gt;, &lt;code&gt;bge-reranker-*&lt;/code&gt;, &lt;code&gt;jina-reranker-*&lt;/code&gt;) use &lt;code&gt;CrossEncoder&lt;/code&gt;. The 2025–2026 generation of &lt;strong&gt;LLM-initialized rerankers&lt;/strong&gt; — Qwen3-Reranker, mxbai-rerank-v2 — read relevance from a causal LM as the logit of a "yes"/"no" token given a prompt with instruction, query, and document. They buy prompt conditioning ("rank by whether the passage contains a numeric answer") and long context, and cost a full decoder forward pass per pair. Load with &lt;code&gt;AutoModelForCausalLM&lt;/code&gt; per model card, or use a wrapper: &lt;strong&gt;&lt;code&gt;rerankers&lt;/code&gt;&lt;/strong&gt; (AnswerDotAI) gives one &lt;code&gt;Reranker(...).rank(query, docs)&lt;/code&gt; call over cross-encoders, ColBERT, LLM rerankers, and hosted APIs alike. In production, serve the reranker off the request path — HuggingFace &lt;strong&gt;Text Embeddings Inference&lt;/strong&gt; hosts reranker models behind a &lt;code&gt;/rerank&lt;/code&gt; endpoint with continuous batching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Training Your Own Reranker
&lt;/h3&gt;

&lt;p&gt;If you have domain-specific relevance labels (click logs, human annotations, distillation from a more powerful model), fine-tune a cross-encoder with binary cross-entropy on (query, positive, negative) triples, or listwise ranking loss (LambdaLoss, ListNet).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dataset&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers.cross_encoder&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CrossEncoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CrossEncoderTrainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CrossEncoderTrainingArguments&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers.cross_encoder.losses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BinaryCrossEntropyLoss&lt;/span&gt;

&lt;span class="n"&gt;train_dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_dict&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is RRF?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is RRF?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;passage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reciprocal rank fusion combines ranked lists using 1/(k+rank).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The capital of France is Paris.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# 1 = relevant, 0 = hard negative
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CrossEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cross-encoder/ms-marco-MiniLM-L-6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BinaryCrossEntropyLoss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CrossEncoderTrainingArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;output_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./my-domain-reranker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_train_epochs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 1 epoch usually enough for domain adaptation
&lt;/span&gt;    &lt;span class="n"&gt;per_device_train_batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2e-5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;warmup_ratio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fp16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CrossEncoderTrainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./my-domain-reranker/final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The data matters more than the loss.&lt;/strong&gt; Pointwise BCE on random negatives teaches almost nothing — random negatives are trivially separable. Mine &lt;strong&gt;hard negatives&lt;/strong&gt;: run your existing first-stage retriever, take documents ranked 10–100 that are &lt;em&gt;not&lt;/em&gt; labelled relevant, and use those as zeros — they are exactly the confusions the reranker exists to fix. Usual ratio: 4–8 hard negatives per positive. With graded relevance or teacher scores, prefer listwise loss (LambdaLoss, ListNet) over BCE: it optimizes the &lt;em&gt;ordering&lt;/em&gt; of a candidate list, which is what nDCG actually measures. Distilling a large reranker's scores into a MiniLM-sized student with &lt;code&gt;MarginMSELoss&lt;/code&gt; is the standard way to get most quality at a fraction of latency.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Query Rewriting and HyDE
&lt;/h2&gt;

&lt;p&gt;The query the user types is rarely optimal for retrieval — short, colloquial, or implicit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Query Expansion
&lt;/h3&gt;

&lt;p&gt;Generate several paraphrases of the original query, retrieve for each, and union the result sets (deduplicating by document ID). Dramatically improves recall for queries that can be stated multiple ways.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;REWRITE_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a retrieval expert. Given a user question, produce {n} alternative phrasings that together cover the semantic space of the question. Return a JSON list of strings.

User question: {question}

Alternative phrasings (JSON list):&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expand_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm_call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REWRITE_PROMPT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_variants&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# extract JSON list from LLM output...
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;n_variants&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# include original
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HyDE — Hypothetical Document Embeddings
&lt;/h3&gt;

&lt;p&gt;Instead of embedding the (short, vague) query, ask the LLM to generate a &lt;strong&gt;hypothetical document&lt;/strong&gt; that would answer the query, then embed that document and retrieve using its embedding.&lt;/p&gt;

&lt;p&gt;The intuition: a hypothetical answer document and a real answer document inhabit closer regions of embedding space than the query and the answer document, because they share vocabulary, entity mentions, and syntactic structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;HYDE_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Write a short, factual paragraph (2-4 sentences) that directly answers the following question. Do not say &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t know&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; — write the best answer you can, even if uncertain.

Question: {question}

Answer:&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hyde_embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm_call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embed_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_hypotheses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;hypotheses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HYDE_PROMPT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_hypotheses&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;hypotheses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;llm_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;embed_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hypotheses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mean_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mean_embedding&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mean_embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;HyDE hallucination risk:&lt;/strong&gt; The LLM's hypothetical document will contain plausible-sounding but possibly incorrect facts. This is fine for retrieval (you only use the embedding, not the text), but be careful not to accidentally include the hypothesis in the LLM context. Always retrieve from the real corpus using the HyDE embedding, then pass the retrieved &lt;em&gt;real&lt;/em&gt; documents to the generator.&lt;/p&gt;

&lt;p&gt;Use HyDE when: corpus uses formal/technical language differing from query style; queries are short and ambiguous; you can afford 1–2 extra LLM calls per query (~200ms latency budget). Avoid when: queries are already long and specific; LLM hallucinations could mislead retrieval; low-latency SLA (&amp;lt;50ms) prevents extra LLM calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Metadata Filtering and Parent-Child Retrieval
&lt;/h2&gt;

&lt;p&gt;Purely semantic retrieval treats all documents as equally eligible. In practice, queries have hard constraints: "only Q4 2024", "only 'legal' category", "only product model XYZ-500". Metadata filtering applies these constraints &lt;em&gt;before&lt;/em&gt; or &lt;em&gt;during&lt;/em&gt; ANN search, drastically reducing the candidate pool and improving precision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-filtering vs Post-filtering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-filter&lt;/strong&gt; (filter before ANN): restrict index to documents matching metadata predicate, then search that subset. Exact, but gives up prebuilt ANN graph over surviving set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-filter&lt;/strong&gt; (retrieve top-k, then filter): fast, but may need to retrieve large k to guarantee filtered results cover top-k meaningful hits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern vector databases (Qdrant, Pinecone, Weaviate, Milvus) support &lt;strong&gt;hybrid pre/post-filtering&lt;/strong&gt; with HNSW graph filterable attributes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Qdrant example
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;qdrant_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;QdrantClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;qdrant_client.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FieldCondition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MatchValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Range&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filtered_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FieldCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MatchValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_before&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FieldCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;lte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date_before&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;

    &lt;span class="n"&gt;qdrant_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;must&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;collection_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;query_filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;qdrant_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;with_payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parent-Child Document Retrieval
&lt;/h3&gt;

&lt;p&gt;Index fine-grained &lt;strong&gt;child chunks&lt;/strong&gt; (single sentences or 128-token windows) for high-precision retrieval, but when a child chunk is retrieved, return its &lt;strong&gt;parent document&lt;/strong&gt; (full paragraph or section) as LLM context. Gives the LLM surrounding context while keeping the retrieval signal sharp.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChildChunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;parent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParentDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parent_child_retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent_store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ParentDocument&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;child_hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# over-fetch for unique parents
&lt;/span&gt;    &lt;span class="n"&gt;seen_parent_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;child_hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_id&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen_parent_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;seen_parent_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parent_store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent_store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both frameworks ship this: LangChain's &lt;code&gt;ParentDocumentRetriever&lt;/code&gt;, LlamaIndex's &lt;code&gt;SentenceWindowNodeParser&lt;/code&gt; (retrieve a sentence, return window of ±k sentences) and &lt;code&gt;AutoMergingRetriever&lt;/code&gt; (retrieve leaf chunks over hierarchy, substitute parent when enough siblings hit — the behavior worth stealing if you build your own).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One thing this doesn't fix: redundancy.&lt;/strong&gt; Five distinct chunks from five near-identical documents will still all survive into the prompt. Standard remedy: &lt;strong&gt;Maximal Marginal Relevance (MMR)&lt;/strong&gt; — greedily trade query relevance against novelty relative to already-selected chunks — applied &lt;em&gt;after&lt;/em&gt; reranking and &lt;em&gt;before&lt;/em&gt; prompt assembly.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. A Production RAG Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RAGConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;
    &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
    &lt;span class="n"&gt;chunking_strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fixed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# "fixed" | "semantic" | "structural"
&lt;/span&gt;    &lt;span class="n"&gt;bm25_top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
    &lt;span class="n"&gt;dense_top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
    &lt;span class="n"&gt;rrf_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="n"&gt;use_hyde&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;n_query_variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;use_reranker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;reranker_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cross-encoder/ms-marco-MiniLM-L-6-v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;rerank_candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="n"&gt;reranker_top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="n"&gt;metadata_filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_rag_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bm25_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dense_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embed_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reranker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RAGConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RAGResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 1: Query representation
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use_hyde&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hyde_embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;llm_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embed_fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;embed_fn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 2: Hybrid retrieval
&lt;/span&gt;    &lt;span class="n"&gt;bm25_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bm25_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bm25_top_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dense_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dense_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dense_top_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reciprocal_rank_fusion&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;bm25_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dense_results&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rrf_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fused_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;all_docs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fused&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;candidate_texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fused_chunks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;rrf_by_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fused_chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Reranking
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use_reranker&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;reranker&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;candidate_texts&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rerank_candidates&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reranker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reranker_top_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;final_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reranked&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;reranker_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reranked&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;final_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate_texts&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reranker_top_k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;reranker_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;RAGResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_chunks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;final_chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rrf_scores&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rrf_by_text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;final_chunks&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reranker_scores&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reranker_scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Choosing Configuration for Your Domain
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain Characteristic&lt;/th&gt;
&lt;th&gt;Recommended Setting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Precise entity lookup (medical codes, SKUs, legal citations)&lt;/td&gt;
&lt;td&gt;BM25 weight high; hybrid with RRF; add metadata filter on document type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversational QA over prose&lt;/td&gt;
&lt;td&gt;Dense retrieval dominant; enable HyDE; semantic chunking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long documents with clear section structure&lt;/td&gt;
&lt;td&gt;Structural chunking + parent-child retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilingual corpus&lt;/td&gt;
&lt;td&gt;Multilingual encoder + reranker (e.g., &lt;code&gt;BAAI/bge-m3&lt;/code&gt; + &lt;code&gt;BAAI/bge-reranker-v2-m3&lt;/code&gt;); BM25 needs per-language tokenization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paraphrase-heavy queries over technical text&lt;/td&gt;
&lt;td&gt;Add SPLADE as third ranker in RRF fusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low-latency SLA (&amp;lt;100ms)&lt;/td&gt;
&lt;td&gt;Skip HyDE; use MiniLM reranker or skip reranking; pre-filter metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-stakes accuracy requirement&lt;/td&gt;
&lt;td&gt;Enable all stages: HyDE + hybrid + full reranker; re-evaluate every 30 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  7. Diagnostics and Iteration
&lt;/h2&gt;

&lt;p&gt;A RAG pipeline has many knobs. Easy to tune one component in isolation while unknowingly degrading another. Rigorous approach: evaluate each stage separately with stage-specific metrics, then measure end-to-end quality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NamedTuple&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RetrievalMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NamedTuple&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;recall_at_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;mrr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;ndcg_at_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;latency_p50_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;latency_p99_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_recall_at_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relevant_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;retrieved_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;recalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relevant_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recalls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recalls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_mrr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relevant_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;retrieved_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relevant_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;rrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;rrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key metrics per stage:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval&lt;/strong&gt;: Recall@k, MRR, nDCG@k, latency p50/p99&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reranking&lt;/strong&gt;: nDCG@k on reranked list, latency per candidate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation&lt;/strong&gt;: Faithfulness, Answer Correctness, Context Relevancy (RAGAs framework)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-end&lt;/strong&gt;: Token-level F1, EM score on QA benchmarks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always evaluate on &lt;em&gt;your&lt;/em&gt; domain. The table in Section 6.1 gives starting points, not guarantees.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary: What Actually Matters
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chunking is the foundation&lt;/strong&gt;. Get it wrong and nothing downstream fixes it. Start with fixed-size recursive splitting (LangChain's &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt;) with &lt;code&gt;chunk_size=512, overlap=64&lt;/code&gt;. Upgrade to structure-aware or semantic chunking when you have evidence it helps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hybrid retrieval is the default&lt;/strong&gt;. Pure dense misses exact matches. Pure BM25 misses paraphrases. RRF fuses them without score normalization headaches. Add SPLADE as a third leg only when paraphrase-heavy queries over technical text justify the cost.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reranking is where precision lives&lt;/strong&gt;. Bi-encoders cast a wide net; cross-encoders score with joint attention. The two-stage funnel (100 → 5) is the cost-conscious architecture. Load the reranker &lt;em&gt;once&lt;/em&gt; at process startup — constructing it per query costs hundreds of milliseconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query rewriting is cheap recall&lt;/strong&gt;. Multi-query expansion and HyDE cost 1–2 LLM calls but often recover documents the raw query misses. HyDE turns query→document matching into document→document matching.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Metadata filtering is free precision&lt;/strong&gt;. Hard constraints (date, category, product) applied at the vector DB level eliminate irrelevant candidates before they consume retrieval or reranking budget.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parent-child retrieval solves the context gap&lt;/strong&gt;. Retrieve small chunks for sharp signals, return large contexts for generation. Deduplicate parents, then apply MMR for diversity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure each stage independently&lt;/strong&gt;. Recall@k for retrieval, nDCG@k for reranking, faithfulness/correctness for generation. Optimizing one metric while ignoring others is how you get a system that retrieves perfectly but hallucinates in the answer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The LLM Stack Book, Chapter 9.4&lt;/strong&gt; — Chunking, Reranking &amp;amp; Hybrid Search (this article's primary source, with runnable Colab notebooks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Searching for Best Practices in RAG&lt;/strong&gt; (Wang et al., 2024) — arXiv:2407.01219, systematic empirical study of RAG component combinations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG Beyond the Demo&lt;/strong&gt; (Xinyang Wu) — Pipeline, citations, evaluation, and when not to bother&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ColBERT / ColPali&lt;/strong&gt; — Late interaction: the architectural middle ground between bi-encoders and cross-encoders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GraphRAG / Agentic RAG&lt;/strong&gt; — When retrieval itself becomes multi-step reasoning&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you found this useful, follow for more deep dives into the mechanics developers often misunderstand. Next up: how vector databases actually scale ANN search, or what model quantization actually does to your weights.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>ai</category>
      <category>machinelearning</category>
      <category>retrieval</category>
    </item>
  </channel>
</rss>
