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

推荐订阅源

WordPress大学
WordPress大学
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
罗磊的独立博客
T
Tenable Blog
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
L
LINUX DO - 最新话题
AI
AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
S
Secure Thoughts
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
Last Week in AI
Last Week in AI
W
WeLiveSecurity
博客园_首页
Forbes - Security
Forbes - Security
博客园 - 聂微东
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
V
Visual Studio Blog
Microsoft Security Blog
Microsoft Security Blog
Vercel News
Vercel News
小众软件
小众软件
Webroot Blog
Webroot Blog
V2EX - 技术
V2EX - 技术
博客园 - 叶小钗
T
The Exploit Database - CXSecurity.com
TaoSecurity Blog
TaoSecurity Blog
L
Lohrmann on Cybersecurity
I
InfoQ
J
Java Code Geeks
P
Privacy International News Feed
Spread Privacy
Spread Privacy
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志
D
Docker
P
Proofpoint News Feed
B
Blog
Cisco Talos Blog
Cisco Talos Blog
M
MIT News - Artificial intelligence
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Threat Research - Cisco Blogs
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements

DEV Community

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

Disclosure: I am a senior backend tech lead in Paris and I run HostingGuru, a small European PaaS for solo founders and small teams. This article mentions HostingGuru once near the end. The fix works on any platform: Render, Fly, Railway, Supabase, your own VPS, whatever you ship on.

You launch on Product Hunt. By 11h Pacific you have 87 people poking around your dashboard. Your error rate starts climbing. Sentry begins shouting PrismaClientInitializationError: Can't reach database server. Your homepage returns blank. You open your Postgres metrics expecting to see CPU pinned at 100%. CPU is at 3%. Memory is barely touched. But pg_stat_activity shows 100 active connections, and you cannot make a new one.

Your database did not run out of compute. It ran out of seats.

This is the single most common production outage I see in indie SaaS, and it almost never gets blamed correctly. Founders blame the framework, the host, "scaling", or the AI model that wrote the code. The real cause is two paragraphs deep in the Postgres docs and nobody reads them until 11h on Product Hunt day.

Why Postgres has a seat limit at all

Every Postgres connection is a real OS process. Not a thread. A process, with its own memory footprint, typically 5 to 15 MB on a default config. The reason for this design is historical (Postgres predates threads being good at anything in 1996) and operational (process isolation makes one crashed query much less likely to take down the whole server). The cost is that connections are expensive.

So Postgres caps them with max_connections. The defaults you will see in the wild:

  • Supabase free tier: 60 (direct), but with their Supavisor pooler in front
  • Neon free tier: 100 via their built-in pooler, ~20 direct
  • Heroku Postgres Mini: 20
  • Heroku Postgres Basic: 20
  • RDS db.t4g.micro: 81
  • Self-hosted Postgres on a 2 GB VPS, default config: 100

Hit the cap and Postgres does not slow down. It refuses. FATAL: too_many_connections for role "app". Your application's retry logic kicks in. Cold-start serverless functions fan out, each opening its own connection, each getting rejected, each retrying. Your queue backs up. Within 90 seconds the system is fully wedged and the only fix is to restart enough of your app servers to drop the held connections.

I have watched this happen to three clients in the last six months. One of them was a Bolt-built B2B tool that survived its private beta and died on its public launch with 41 concurrent users.

Why this bites indie SaaS specifically

Big companies do not hit this because they paid a platform team to set up connection pooling five years ago. Indie founders hit it because three things stack:

The first is serverless and edge runtimes. Every Vercel function, every Lambda, every Netlify function that touches your database wants its own connection. A single page load on a modern Next.js app can spin up 4 to 8 functions in parallel (your loader, your action, your middleware, your API route, your auth check). Multiply by 50 concurrent users and you are at 400 simultaneous connection attempts to a database that allows 100.

The second is ORM defaults. Prisma, by default, opens a connection pool sized to your CPU count, per process. Run that on 10 Vercel functions and you have 10 separate pools, each sized to whatever Vercel reports as CPU count, often 4 or 8. Drizzle's node-postgres driver defaults to a pool of 10. SQLAlchemy defaults to pool_size=5, max_overflow=10. Stack a few worker dynos and a web server and you can blow past your max_connections limit without doing anything wrong.

The third is hot reload during development. Run npm run dev with Prisma and every save spawns a new client without closing the old one (this got better in recent Prisma versions but is still common). On a small free-tier database, three hours of local dev can hold 30 connections that the database never sees you releasing.

If any of those three apply to you (and at least one does), you are one Product Hunt feature away from the outage I just described.

The math you should actually do tonight

Before reaching for PgBouncer or Supavisor or any pooling layer, do the arithmetic. The formula is brutal but honest.

Take your real concurrency target. For most indie SaaS that is 50 simultaneously active users, not 50,000. Multiply by 2 to account for the fact that a single user action often hits 2 to 4 endpoints (page load, auth check, data fetch, mutation). Add a buffer of 20% for background workers, cron jobs, the Postgres backup process, and your own psql sessions. That is your connection demand.

Now look at your max_connections. Subtract 5 for Postgres's own internal processes (autovacuum, wal writer, etc). What is left is your real budget.

For a Heroku Postgres Basic at 20 max_connections, your real budget is 15. Divide by your per-process pool size, and you get the number of app processes you can safely run. If Prisma is opening 10 connections per process, you can run 1.5 processes. That is your headroom. One Heroku web dyno plus one worker dyno will already be over the limit, before any user shows up.

This is the math nobody runs.

When you need PgBouncer, and when you just need to fix your app

PgBouncer (or Supavisor, or PgCat, or your provider's built-in pooler) lets a small number of real Postgres connections back a much larger number of fake-looking client connections. Your 500 Next.js functions all open a connection to PgBouncer on port 6432. PgBouncer holds 15 real connections to Postgres on port 5432 and multiplexes them. As long as no single query holds a connection for too long, your fan-out problem disappears.

But pooling is not the first fix. It is the third.

Before adding a pooler, do these in order:

First, lower your ORM's per-process pool size. Most Prisma deployments do not need 10 connections per process. Set connection_limit=2 in your DATABASE_URL query string. For Drizzle/node-postgres, pass { max: 3 } to the pool constructor. For SQLAlchemy, set pool_size=2, max_overflow=0. This alone solves the issue for most indie SaaS.

Second, stop opening new clients on hot reload. The Prisma docs have a singleton pattern for Next.js dev mode that uses globalThis to reuse the client across reloads. Use it. SQLAlchemy users should make sure they are not calling create_engine() inside a request handler.

Third, find your connection leaks. Run SELECT pid, query, state, backend_start, state_change FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY state_change ASC; against your production database. Any row older than 5 minutes is a leak. It is almost always a missing await on a transaction, or an error path that does not call client.release(). Fix those before adding any infrastructure.

If after all three you still need more headroom, then add a pooler.

The PgBouncer config that actually works

If you self-host PgBouncer, the minimum useful config is shorter than people think:

[databases]
appdb = host=10.0.0.5 port=5432 dbname=appdb

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 500
default_pool_size = 15
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 60

The two settings that matter are pool_mode and default_pool_size. pool_mode = transaction is what makes the multiplexing actually work: PgBouncer hands a real Postgres connection to a client only for the duration of a single transaction, then reclaims it. default_pool_size = 15 means PgBouncer will open up to 15 real connections to Postgres per (user, database) pair. That number, plus a buffer, must stay under your max_connections.

max_client_conn = 500 is the lie you tell your application. From the app's perspective there are 500 connections available. From Postgres's perspective there are 15. PgBouncer mediates.

The prepared statement footgun

Here is the part that bites everyone, including me, the first time.

In pool_mode = transaction, a single client connection can land on different real Postgres connections from one transaction to the next. Prepared statements (PREPARE name AS SELECT...) live on a specific Postgres connection. So if your ORM prepares a statement on connection A and then tries to execute it on connection B because PgBouncer rotated, you get prepared statement "p1" does not exist. Your app starts throwing 500s on perfectly correct queries.

This hits Prisma users hard because Prisma uses prepared statements by default. The fix is either:

  • Use Prisma's ?pgbouncer=true flag in the connection string, which disables prepared statements
  • Or upgrade to Prisma 5.10+ and use ?statement_cache_size=0
  • Or run Prisma against Postgres directly and use PgBouncer only for non-Prisma workloads
  • Or use Supabase's Supavisor in "session mode" instead of transaction mode (less efficient, but compatible)

Django users with psycopg2 need DISABLE_SERVER_SIDE_CURSORS = True in their database config. Rails with the pg gem is mostly fine because Rails does not use prepared statements by default in production unless you opted in via prepared_statements: true.

SQLAlchemy users need pool_pre_ping=True and to make sure they are not using server-side cursors across transactions. The default is fine.

This is the one chunk of this article I would tape to your monitor. The error message prepared statement does not exist after introducing PgBouncer is not a bug in PgBouncer. It is your ORM assuming pool_mode = session when you gave it pool_mode = transaction.

What about Supabase, Neon, and the managed providers

Most of them already do this for you, kind of.

Supabase ships a pooler called Supavisor in front of every project. Your DATABASE_URL for "direct" connections hits Postgres on port 5432. Your "transaction" pooler URL hits Supavisor on port 6543. Use the latter from any serverless context. The former is fine for migrations, long-running scripts, and Studio.

Neon does similar with a pgbouncer-derived layer. Append ?pgbouncer=true to your connection string and you are using their pooled endpoint.

Render's managed Postgres has a built-in pooler since 2024, configured via the dashboard.

Heroku Postgres has no built-in pooler. You either run PgBouncer yourself on a separate dyno, or you switch providers.

The trap: founders use the pooled URL in their .env for everything, then run Prisma migrations through it, and the migration fails because migrations need session-mode features like advisory locks and SET search_path. So you keep two DATABASE_URL values: one for the app (pooled, transaction mode), one for migrations (direct, session mode). Most ORMs let you point migrations at a different URL via env var. Do that.

What I built

I kept solving this manually for clients, then I packaged it. I run HostingGuru, a small European PaaS where the managed Postgres ships with a pooler in front by default on every plan, and the dashboard surfaces pg_stat_activity so you can see your idle-in-transaction count before it kills you. There is also AI-driven log monitoring that pattern-matches too_many_connections and prepared statement does not exist and pings you on Telegram with the likely root cause. EU and US data centers, GDPR baked in. The free Starter tier never sleeps, which matters when your Product Hunt launch happens at 02h Paris time.

That is the one mention. The actual fix in this article works the same on Render, Fly, Supabase, or your own VPS. Pick the provider that fits.

What to do tonight regardless of which platform you use

Run SELECT count(*), state FROM pg_stat_activity GROUP BY state; against your production database right now. If idle in transaction is non-zero, you have a leak.

Open your .env or your platform's env vars page. If your DATABASE_URL has no connection_limit parameter (Prisma) or no pool size in the connection options (other ORMs), add one. Set it to 2 or 3.

Check whether your managed Postgres provider offers a pooled connection URL. Switch your serverless functions to use it. Keep the direct URL for migrations only.

Add an alert (Sentry, your log aggregator, Telegram, whatever you use) for the exact strings too_many_connections and prepared statement does not exist. Both indicate you are about to have a bad day.

If you self-host, install PgBouncer with pool_mode = transaction and the config above, and set default_pool_size to no more than max_connections - 5 divided by however many user-database pairs you have.

Document somewhere (a README, a Notion page, your project's ARCHITECTURE.md) what your real max_connections is and how many connections your app is allowed to take. Future-you on Product Hunt day will thank present-you.

What I do not know

The interesting open question for me is whether the next generation of "thin client" Postgres protocols (the HTTP-based ones like Neon's Serverless Driver and Supabase's PostgREST) make all of this obsolete. They sidestep the connection-per-process model entirely by going stateless. They also break every ORM that assumes a persistent connection.

My current bet is that the connection pool will remain the right abstraction for the next 3 to 5 years, because the alternative is rewriting your data layer for a protocol that has no transactions in the traditional sense. But I would love to hear from anyone running an indie SaaS on the new thin-client protocols. What broke? What got easier? Drop a comment.

Previous posts in this series

  1. Heroku just went into "sustaining engineering mode." Here are 5 alternatives whose free tier actually doesn't sleep.
  2. I built my MVP with Claude Code. Now I need to deploy it. Here's what nobody tells you.
  3. Your AI app is silently burning $2,000/month and you don't know it.
  4. Telegram alerts for any production app, a 5-minute setup.
  5. How I built a Discord 'ship-tracker' bot in a weekend.
  6. I migrated 12 client projects off Heroku. Here's the playbook.
  7. The Claude Code to production checklist: 15 things that aren't obvious.
  8. Your indie SaaS has zero working Postgres backups. Here's the 20-minute fix.
  9. Your Stripe webhook is going to silently drop a paid customer.
  10. Your crontab is silently failing. The 5 silent killers of VPS-based cron jobs.
  11. I deployed 12 vibe-coded apps to production. The same 6 things broke every single time.
  12. Your .env file is probably already in your Git history.