[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f20wjz3tr0565c":3},{"_id":4,"slug":5,"title":6,"subtitle":7,"kind":8,"cards":9,"tags":58,"categories":59,"source":60,"lang":63,"author":64,"audioState":67,"stats":68,"publishedAt":71,"renderer":72},"6abcbf83ca21c797c7ea0f5c","postgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80","Postgres with QUIC: Breaking the Client-Side Connection Bottleneck | Lupyd","Every backend engineer running Postgres at scale eventually learns the same painful lesson: connection pooling does not fix network physics.","news",[10,13,18,23,28,33,38,43,48,53],{"headline":6,"body":11,"imageUrl":12,"sourceImageUrl":12},"Every backend engineer running Postgres at scale eventually learns the same painful lesson: connection pooling does not fix network physics. We deploy PgBouncer or PgCat, configure transaction pooling, lock down backend connections so the database doesn't run out of memory, and celebrate. But if your application or edge services sit tens of milliseconds away from your database cluster, your queries are still choking on a transport bottleneck we rarely talk about: the client-side TCP connection pool.","https:\u002F\u002Fblogs.lupyd.com\u002Fstatic\u002Fpostgres-with-quic.svg",{"headline":14,"body":15,"imageUrl":16,"images":17},"Recently, I ran an experiment to address this","Recently, I ran an experiment to address this directly: running the Postgres protocol over QUIC (UDP) instead of traditional TCP. We modified PgCat to accept QUIC connections and multiplexed hundreds of virtual database streams over a tiny handful of UDP associations.","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F1.webp",{"local":16},{"headline":19,"body":20,"imageUrl":21,"images":22},"The outcome was startling. Under heavy load, our","The outcome was startling. Under heavy load, our QUIC setup pushed 9,718 queries per second at an average latency of 246ms. The identical workload over traditional TCP collapsed into a catastrophic queue pileup of over 40,000 backlogged queries, spiraling to 7,088ms average latency.","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F2.webp",{"local":21},{"headline":24,"body":25,"imageUrl":26,"images":27},"Here is the real engineering breakdown of why","Here is the real engineering breakdown of why this happens, the arithmetic of latency asymmetry, where QUIC genuinely changes the rules, and where it cannot save you. The Synchronous Postgres Reality","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F3.webp",{"local":26},{"headline":29,"body":30,"imageUrl":31,"images":32},"To understand the problem, you have to look","To understand the problem, you have to look at the PostgreSQL frontend\u002Fbackend wire protocol (Protocol 3.0). Postgres is fundamentally synchronous at the connection layer.","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F4.webp",{"local":31},{"headline":34,"body":35,"imageUrl":36,"images":37},"When a client sends a Query or Execute","When a client sends a Query or Execute message down a Postgres TCP connection, that socket is tied up until the server finishes processing and returns the matching CommandComplete and ReadyForQuery message. You cannot interleave two independent queries from different application threads on the same physical connection without strict sequential serialization.","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F5.webp",{"local":36},{"headline":39,"body":40,"imageUrl":41,"images":42},"1 Connection = 1 Active In-Flight Query or","1 Connection = 1 Active In-Flight Query or Transaction. A client socket is completely blocked from the moment bytes leave the network card until the final row batch and ReadyForQuery flag return across the wire.","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F6.webp",{"local":41},{"headline":44,"body":45,"imageUrl":46,"images":47},"Because establishing a new Postgres backend process on","Because establishing a new Postgres backend process on the database server involves fork-exec overhead, memory allocation (several megabytes per connection for work_mem, cache metadata, and process state), and catalog locks, we can't let 5,000 application threads open 5,000 direct connections to Postgres. The server would instantly melt from context switching and OOM crashes. So we put connection poolers in the middle. But look closely at where the pooler actually sits. The Latency Asymmetry: 30ms WAN vs 2ms LAN","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F7.webp",{"local":46},{"headline":49,"body":50,"imageUrl":51,"images":52},"In modern distributed architectures, your services don't live","In modern distributed architectures, your services don't live in the database rack. You have edge nodes, serverless workers, microservices in different availability zones, or regional clusters in us-east-1 querying a centralized database cluster in us-east-2 or eu-central-1. Let's consider a realistic, standard production deployment: Client to Pooler (WAN \u002F Inter-region): Round-trip time (RTT) is 30ms. Pooler to Postgres (Internal LAN \u002F Same VPC): Round-trip time is \u003C2ms (often sub-millisecond). Query execution time inside the Postgres engine: A fast indexed point lookup or one-liner takes 0.5ms. Each query occupies 1 connection for 30.5ms total waiting on network wire flight \u003C2ms internal LAN latency","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F8.webp",{"local":51},{"headline":54,"body":55,"imageUrl":56,"images":57},"The database engine executes the query in 0.5ms","The database engine executes the query in 0.5ms and the internal pooler recycles the connection in 2ms. But the client socket cannot be reused for 30.5ms because the bytes are still flying over the public internet or cross-region fiber! The Math of Client-Side Connection Starvation Here is where basic arithmetic exposes the flaw.","\u002Fapi\u002Fmedia\u002Fposts\u002Fpostgres-with-quic-breaking-the-client-side-connection-bottl-40ac0f80\u002F9.webp",{"local":56},[],[],{"name":61,"url":62},"Hacker News","https:\u002F\u002Fblogs.lupyd.com\u002Fblog\u002Fpostgres-with-quic\u002F","en",{"handle":65,"displayName":66},"spots","Spots","queued",{"views":69,"likes":70,"saves":70,"shares":70,"completions":70,"opens":70,"skips":70,"depthSum":70},1,0,"2026-09-30T07:51:31.664Z","local"]