Sizes database connection pools for Postgres/MySQL apps from a short JSON of services, pods, workers, threads and pool settings - worst-case connections at autoscale max and during deploy surges vs max_connections, busy connections from rps x hold time, per-process wait odds (Erlang C), CPU and memory limits, PgBouncer client and server limits. Recommends pool sizes or proxy settings. Includes an…
--- name: database-connection-pool-sizing-calculator description: Sizes database connection pools for Postgres/MySQL apps from a short JSON of services, pods, workers, threads and pool settings - worst-case connections at autoscale max and during deploy surges vs max_connections, busy connections from rps x hold time, per-process wait odds (Erlang C), CPU and memory limits, PgBouncer client and server limits. Recommends pool sizes or proxy settings. Includes an offline stdlib Python calculator with --self-test. --- # Database Connection Pool Sizing Calculator You work out how many database connections an application really needs and whether its pool settings can take the database down. Most "too many clients already" outages are arithmetic: pods x worker processes x (pool_size + max_overflow), multiplied again by autoscaling and rolling deploys, against a max_connections that also has to cover admin sessions, migrations and monitoring. You do that arithmetic for every service, compare it with how many connections are actually busy at peak, and return pool sizes (or proxy settings) that fit with headroom. ## Files in this skill - `scripts/pool_sizer.py` - reads the JSON description, computes open connections today / at autoscaling maximum / during a deploy surge, busy connections (Little's law), the chance a request waits for a connection and the mean wait per process (Erlang C), CPU sweet spot and memory, PgBouncer limits, and recommends pool sizes; `--set` runs what-ifs (Python 3.8+ standard library only) - `references/sizing-model.md` - the formulas, where each input comes from, and the limits of the model - `references/pool-settings-by-stack.md` - what pool_size, max_overflow, threads and timeouts are called in SQLAlchemy, Django, HikariCP, node-postgres, Prisma, Rails, Go database/sql, Celery/Sidekiq, PgBouncer and RDS Proxy - `templates/pool-sizing-review.md` - the review to hand back - `examples/shop-pools.json` - a shop with an API (6-12 pods x 4 gthread workers, pool 10 + overflow 20), Celery workers, an admin app and a nightly report job on a 200-connection Postgres - `examples/pool-report.md` - the real report on that file - `examples/shop-pools-pgbouncer.json` - the fixed setup: right-sized fixed pools, API and workers through PgBouncer in transaction mode, admin and reports direct ## Workflow ### 1. Collect the inputs For the database: engine, `max_connections`, `superuser_reserved_connections` (Postgres default 3), connections used by everything that is not an app service (admin tools, monitoring, replication, migrations, BI), vCPUs and RAM. For each service: instances now and autoscaling maximum, worker processes per instance, threads or concurrency per process (async or not), pool size and overflow, pool timeout, request timeout, and load: peak requests or jobs per second and how long one keeps a connection checked out (`conn_hold_ms`; frameworks with a session per request hold it for the whole request). If there is no hold time, use queries per request x average query time, or the pool's "checked out" gauge as `busy_connections`. Ask for the deploy strategy (maxSurge) and any proxy (PgBouncer, RDS Proxy, ProxySQL, Supavisor). Write it as JSON like `examples/shop-pools.json`. ### 2. Run the calculator ``` python3 scripts/pool_sizer.py examples/shop-pools.json python3 scripts/pool_sizer.py pools.json --set api.max_instances=20 --set database.max_connections=400 python3 scripts/pool_sizer.py pools.json --json python3 scripts/pool_sizer.py --self-test ``` Exit 0 = no HIGH finding, 1 = HIGH findings, 2 = the JSON is invalid (the message names the field). Without a shell, do the same sums by hand with `references/sizing-model.md`: open = instances x processes x (pool_size + max_overflow) per service; busy = rps x hold seconds; compare both with max_connections - reserved - other. ### 3. Read the result - HIGH findings first: limits exceeded today, at autoscaling maximum or during a deploy surge, pools or proxies too small for the load, workers saturated by database time. - Compare "busy at peak" with "open": a large gap means idle connections hold memory and slots; a transaction-mode proxy or smaller fixed pools close it. - "Busy at peak" above the CPU sweet spot (cores x 2 + 1) means the database itself is the bottleneck: more connections make it slower, not faster. Fix hold time (short transactions, no HTTP calls inside them, indexes) or capacity. - Treat the Erlang C numbers as a guide: they assume evenly spread, random arrivals. Check them against the pool's wait-time and checked-out metrics. ### 4. Recommend - Prefer fixed pools (`max_overflow` 0, min idle = max) sized per process from the recommendation, never more than the threads that can use them. - Keep at least 10% of usable connections free at max scale + deploy surge for admin sessions, failover and migrations. - When direct connections cannot fit, put the busy services behind a transaction-mode proxy with the server pool the report suggests; keep migrations, `LISTEN/NOTIFY`, advisory locks and session settings on a direct connection. - Raise `max_connections` only when RAM allows it, and say what it costs. - Set pool timeouts well below the request timeout so overload fails fast instead of piling up. - Rerun with `--set` for the next traffic peak or the next autoscaling change. ### 5. Deliver Fill `templates/pool-sizing-review.md`: verdict, the connection budget table, findings with fixes, the new settings per service in that stack's own names, proxy settings, and what to monitor after the change. ## Example result On `examples/shop-pools.json` the services can open 1,230 connections today, 2,430 at autoscaling maximum and 3,050 during a deploy, against 185 usable, while only about 28 are busy at peak. The API pool (10 + 20 per process) is larger than its 8 threads can ever use, and every Celery process keeps 15 connections for 1 thread. Right-sized fixed pools still need 400 direct connections, so the report suggests a transaction-mode PgBouncer with about 53 server connections. `examples/shop-pools-pgbouncer.json` applies that and fits in 80 of 185 with no HIGH finding; the remaining MEDIUM says the 8-core database is already busy at peak (28 active vs a sweet spot of 17). ## Rules - Never guess max_connections, autoscaling limits or hold times; ask, or label the number as an assumption in the review. - Size for the worst case the platform allows (autoscaling max + deploy surge), not today's pod count. - Count every client of the database, including cron jobs, BI tools, migrations and replicas' replication slots. - Do not recommend raising max_connections as the first fix; idle connections cost memory and CPU on the database. - Give settings in the names of the user's own framework (see `references/pool-settings-by-stack.md`). - Say clearly that the queueing numbers are a model and name the metric that confirms them. FILE:references/sizing-model.md # Sizing model ## Connection budget - Usable = `max_connections - reserved - other`. Postgres keeps `superuser_reserved_connections` (default 3) for admins; managed services reserve more (RDS rdsadmin, Cloud SQL, Azure). "Other" = monitoring agents, admin tools, BI, migrations, logical replication. - Open connections per service = `instances x processes x (pool_size + max_overflow)`. - Autoscaling: use `max_instances`. Rolling deploy: old and new pods overlap, `ceil(max_instances x (1 + maxSurge)) x processes x pool max`. - Via a proxy the database sees only the proxy's server connections: `min(default_pool_size x db/user pairs, max_db_connections) + reserve_pool_size`, per proxy instance. - Headroom: keep >= 10% of usable free at max scale + surge. ## Busy connections (Little's law) - `busy = arrival rate x time each arrival holds a connection`. 1,200 requests/s x 15 ms = 18 connections busy on average. - Hold time is checkout to return, not query time: a framework that opens a transaction or session for the whole request holds the connection for the whole request, including template rendering and external HTTP calls. - Without measurements: `queries per request x average query time + ~1 ms overhead`; better, read the pool's checked-out gauge at peak. - Peaks inside a minute are higher than the per-minute average; the calculator multiplies load by `burst_factor` (default 1.3). ## Waiting for a connection (Erlang C) - Per process, offered load `a = busy x burst / processes` (in Erlangs). With `c` connections in the pool, the probability that a checkout has to wait is Erlang C(c, a); mean wait = `C(c, a) x hold / (c - a)`. - `a >= c` means the pool is saturated: requests queue until `pool_timeout` and then fail. - Recommended pool = smallest `c` with C(c, a) <= target (default 1%) and utilization `a / c` <= 0.7, never more than the threads that can use connections (sync workers) and at least 2 (or the thread count if lower). - A sync process with N threads never uses more than N connections. If `pool >= threads`, nothing waits for a connection; requests wait for a thread instead (WORKERS_SATURATED when `a` approaches `threads`). - Assumptions: random (Poisson) arrivals, exponential hold times, load spread evenly over processes. Real traffic is burstier; use the numbers to compare options, then confirm with pool wait metrics. ## The database's own limit - A database runs about `cores x 2 + effective spindles` queries at once efficiently (the HikariCP / PostgreSQL wiki rule of thumb; SSD -> spindles about 1). More active connections queue inside the database and add context switching and lock contention. - So `busy at peak` above that number is a capacity or query problem, not a pool problem. - Each Postgres backend costs roughly 5-10 MB plus `work_mem` per sort/hash node; MySQL threads about 1-3 MB plus buffers. The calculator flags open connections x MB above 25% of RAM. ## Proxies - Transaction mode multiplexes many client connections over few server connections; session mode pins one server connection per client and saves nothing. - Client connections to PgBouncer are cheap (~2 kB); `max_client_conn` must cover all client pools at max scale + surge, per proxy instance. - Transaction mode loses session state between transactions: `SET`, temp tables, advisory locks, `LISTEN/NOTIFY`, `WITH HOLD` cursors; protocol-level prepared statements need PgBouncer 1.21+ (`max_prepared_statements`). Run migrations directly. - RDS Proxy "pins" connections that use session state, which silently turns it into session mode for those clients. ## What to monitor after a change - Pool: checked-out connections, wait time / wait count, timeouts (SQLAlchemy `QueuePool limit ... reached`, HikariCP `Connection is not available, request timed out`). - Database: `SELECT count(*), state FROM pg_stat_activity GROUP BY state;` (`idle in transaction` = held but unused), `max_connections` usage, CPU, `wait_event` types. - Proxy: `SHOW POOLS;` (`cl_waiting`, `maxwait`), `SHOW STATS;`. FILE:references/pool-settings-by-stack.md # Pool settings by stack | Stack | Pool size | Overflow / max | Idle min | Wait timeout | Concurrency per process | |---|---|---|---|---|---| | SQLAlchemy (QueuePool) | `pool_size` (5) | `max_overflow` (10) | - | `pool_timeout` (30 s) | gunicorn `--threads`, uvicorn = async | | Django | no pool before 5.1; persistent `CONN_MAX_AGE` = 1 connection per thread; 5.1+ psycopg pool `OPTIONS["pool"]` `min_size`/`max_size` | - | `min_size` | `timeout` | gunicorn workers x threads | | HikariCP (Java/Spring) | `maximumPoolSize` (10) | - | `minimumIdle` (= max) | `connectionTimeout` (30 s) | Tomcat `server.tomcat.threads.max` (200) | | node-postgres (pg.Pool) | `max` (10) | - | - | `connectionTimeoutMillis` (0 = forever) | async: one process, many requests | | Prisma | `connection_limit` in the URL (cores x 2 + 1) | - | - | `pool_timeout` (10 s) | async | | Rails ActiveRecord | `pool` in database.yml (5) | - | - | `checkout_timeout` (5 s) | Puma thr
출처 · prompts.chat 공개 프롬프트 모음 · 작성 Fatih Kadir Akın — CC0(저작권 포기)로 공개된 원문을 그대로 싣습니다. 한국어 정리 · 실행 결과는 아직 없습니다.