Managed PostgreSQL Instance Sizing: CPU, Memory, Storage, and Connections
A practical managed PostgreSQL instance sizing guide for choosing CPU, memory, storage, I/O, and connection capacity without overbuying too early.
ArmorDB Engineering
ArmorDB engineering
On this page 11 sections
Choosing a managed PostgreSQL plan is easy to treat as a pricing exercise: pick the smallest tier, wait for trouble, then upgrade. That works for prototypes, but it becomes expensive and noisy once the database is carrying real product traffic. The better question is not "how large should PostgreSQL be?" but "which resource will run out first for this workload?"
Instance sizing is a tradeoff between CPU, memory, storage, I/O, connection capacity, backup expectations, and operational headroom. A small database with too many connections can behave worse than a larger dataset with a clean pooler. A write-heavy app can hit WAL and storage pressure before CPU looks busy. A reporting query can make a memory-light plan feel broken even when ordinary CRUD traffic is healthy.
Start from the workload, not the plan page
A managed provider's plan table is useful only after you understand the shape of the application. PostgreSQL is a server process with per-connection memory, shared cache, background maintenance, write-ahead logging, indexes, and query planning. The plan label does not know whether your app is a chat product with many small writes, a SaaS dashboard with tenant-filtered reads, a queue-like workload, or a reporting-heavy internal tool.
Start by writing down the normal path through the product. Count web requests, background jobs, migrations, admin tools, and scheduled reports separately. Then describe the database operations behind them: short indexed lookups, inserts, updates, JSONB filters, full-text search, batch imports, exports, or recurring aggregates. That inventory makes sizing less mysterious because each pattern stresses a different resource.
| Workload signal | Resource usually stressed first | What to check before upgrading |
|---|---|---|
| Many app workers and serverless functions | Connections and backend memory | Add PgBouncer or reduce per-process pool sizes |
| Repeated indexed reads with hot data | Memory and cache efficiency | Confirm indexes and working set before buying CPU |
| Large scans, exports, or reports | I/O, memory, and temporary files | Move work off peak hours or add a replica/export path |
| High insert/update volume | WAL, checkpoints, autovacuum, storage writes | Watch write I/O, dead tuples, and checkpoint behavior |
| Slow single query with low system load | Query plan or missing index | Use EXPLAIN before resizing the instance |
| Frequent maintenance or migrations | Operational headroom | Schedule work and monitor locks, WAL, and I/O |
This table is deliberately workload-first. It keeps the team from treating every symptom as a reason to move to the next tier. Sometimes the right answer is a bigger plan. Often it is a smaller change: a missing index, a pooler, a lower connection count, a safer migration window, or a query moved out of the request path.
CPU: useful, but rarely the whole story
CPU matters when PostgreSQL is spending time comparing rows, joining, sorting, aggregating, decompressing, planning, or executing functions. A CPU-bound database often shows high active sessions doing useful work rather than waiting on locks or I/O. If a dashboard endpoint runs the same expensive aggregate for every request, more CPU may help, but caching the result or adding the right index may help more.
The common mistake is using CPU as the first and only sizing signal. Low CPU does not mean the database is healthy if sessions are waiting on disk, locks, network writes, or client reads. High CPU does not automatically mean the plan is too small if one inefficient query dominates the workload. PostgreSQL's statistics views, slow-query logs, and EXPLAIN output are more useful than a single utilization graph because they show whether the work is expected.
For early SaaS products, CPU headroom is still worth buying once the database is production-facing. Deploys, migrations, autovacuum, backup activity, index builds, and traffic bursts all need room. The goal is not to keep CPU near zero; it is to avoid running ordinary customer traffic and maintenance work with no margin.
Memory: cache, connections, and query bursts
Memory is often the hidden constraint in PostgreSQL sizing. PostgreSQL uses shared memory for buffers, separate memory for backend processes, and additional memory for operations such as sorts and hashes. The official documentation describes settings such as shared_buffers and work_mem because they affect real behavior, not because they are decorative knobs.
A larger memory budget can help when the working set fits in cache, when repeated reads touch the same indexes, or when queries otherwise spill to temporary files. But memory can also disappear into too many connections. Each backend has overhead, and work_mem is applied per operation, not once for the whole database. A connection-heavy application can make a modest plan unstable even with ordinary traffic.
This is where managed PostgreSQL and pooling intersect. ArmorDB includes PgBouncer, and the same principle applies on any provider: size application pools from the database backward. If the database can comfortably run 40 active server connections, do not let ten web containers each open 20 direct connections. The product may not have enough traffic to need 200 database sessions, but the configuration can still ask for them.
Storage and I/O: capacity is not throughput
Storage sizing has two separate questions. The first is capacity: how much data, index, WAL, temporary file, and backup-related room do you need? The second is performance: can the storage layer deliver the read and write pattern your workload creates? A database can have enough disk space and still feel slow because reads are waiting on storage or writes are competing with checkpoints and autovacuum.
PostgreSQL writes WAL before data changes become durable. It also checkpoints, vacuums, analyzes, builds indexes, and writes temporary files for some sorts and joins. Those behaviors are normal, but they mean write-heavy workloads need more than a raw table-size estimate. Leave room for indexes, bloat, maintenance, and restore operations. A database running at the edge of disk capacity is harder to vacuum, harder to migrate, and riskier to recover.
For reads, separate hot OLTP paths from occasional broad scans. If a customer request reads a few indexed rows, it should not share the same expectations as a weekly export scanning millions of rows. When large reads are legitimate, schedule them deliberately, run them from a replica if the architecture supports it, or move the export into a workflow that does not compete with latency-sensitive traffic.
Connections: the multiplier most teams underestimate
PostgreSQL uses a process-per-connection model. That design is reliable and mature, but it means connection count is not free. Modern application platforms multiply connections quickly: web instances, background workers, serverless concurrency, preview deployments, migration tasks, local admin clients, and BI tools can all connect at once.
The sizing mistake is to add up user traffic but forget deployment topology. Five containers with a pool size of 20 can request 100 database connections before background workers or migrations are counted. If traffic doubles and autoscaling adds containers, the database connection demand can jump even if query volume grows smoothly.
A practical connection budget should list every source of connections and assign a maximum. Put PgBouncer in front of short transactional web traffic, keep direct connections for migrations and administrative work, and avoid session features that conflict with the chosen pooling mode. If prepared statements, temporary tables, LISTEN/NOTIFY, or session-level settings are important, review pooling mode carefully before assuming transaction pooling is safe. The ArmorDB guide to PgBouncer pooling modes goes deeper on those tradeoffs.
A practical sizing worksheet
The most useful sizing document is short enough to update after an incident. It should not be a capacity-planning novel. Use it to make assumptions visible, then revisit it when traffic, schema, or product behavior changes.
| Sizing area | Baseline question | Upgrade trigger | Non-upgrade fix to try first |
|---|---|---|---|
| CPU | Are active sessions CPU-bound during normal traffic? | Sustained saturation on known-good queries | Fix top queries, cache aggregates, move reports |
| Memory | Does the working set fit and are temp files rare? | Frequent spills or cache misses on important paths | Add indexes, reduce connection count, tune query shape |
| Connections | How many sessions can all app components open? | Pooler saturated or database backends near limit | Lower app pool sizes, use PgBouncer, split workloads |
| Storage capacity | How fast are tables, indexes, WAL, and bloat growing? | Less headroom than restore, vacuum, or growth policy needs | Archive data, drop unused indexes, fix bloat causes |
| Storage I/O | Are waits tied to reads, writes, checkpoints, or WAL? | Legitimate workload exceeds plan throughput | Reschedule jobs, improve indexes, separate exports |
| Recovery | Can the plan meet backup and restore expectations? | RTO/RPO cannot be met at current size or tier | Test restores and simplify recovery runbooks |
For a new production app, fill this out with estimates rather than pretending certainty. After launch, replace guesses with evidence from query statistics, provider metrics, and application telemetry. The worksheet becomes more valuable over time because it records why the current plan was chosen.
When to choose the smaller plan
Choose the smaller credible plan when the product is still proving traffic, the data model is changing quickly, and the team has a clean upgrade path. This is especially reasonable when the workload is mostly short indexed reads and writes, connection counts are controlled, and backups or restore requirements are modest. A smaller managed plan also forces useful discipline: keep indexes intentional, avoid runaway pools, and notice growth early.
The smaller plan is not an excuse to ignore production basics. Set connection limits, enable appropriate backups for the product stage, keep migrations reversible where possible, and watch slow queries. If the database is customer-facing, leave enough budget for the plan that meets actual reliability needs rather than optimizing for the lowest monthly number on day one. ArmorDB's pricing page is designed around that kind of gradual upgrade path: start simply, then move up when storage, backups, and production limits require it.
When to size up early
Size up earlier when downtime is expensive, growth is predictable, or the workload has known heavy operations. Examples include billing systems, customer-facing dashboards, write-heavy event ingestion, background jobs that must finish inside a time window, and products with strict restore expectations. In those cases, headroom is not waste. It is part of the reliability budget.
Also size up before known one-time events if the current plan is too tight: large imports, backfills, index builds, schema rewrites, or launch traffic. Temporary headroom can be cheaper than running an operationally risky migration at the edge of CPU, memory, and I/O. After the event, review whether the higher plan is still needed or whether the workload can return to the previous tier.
Common sizing mistakes
The first mistake is buying CPU for a connection problem. If hundreds of mostly idle sessions consume memory and create reconnect storms, a larger instance may only hide the issue. Fix pool sizes and pooling architecture first.
The second mistake is ignoring maintenance. Autovacuum, backups, restores, index builds, and ANALYZE are part of production PostgreSQL. A plan that handles the average request but collapses during routine maintenance is undersized for the real system.
The third mistake is comparing providers by headline storage alone. Managed PostgreSQL value includes backups, pooling, restore workflow, support path, networking, and the operational clarity of the plan. A cheap-looking plan can become expensive if every production need turns into a separate add-on or manual runbook.
Sources / further reading
- PostgreSQL documentation: Resource Consumption
- PostgreSQL documentation: Monitoring Database Activity
- PostgreSQL documentation: EXPLAIN
- PostgreSQL documentation: Write-Ahead Logging
- PgBouncer documentation: Features and pooling modes
Practical takeaway
Managed PostgreSQL sizing is a resource diagnosis problem, not a tier-name problem. Start with workload shape, connection budget, working set, storage growth, I/O pattern, and recovery expectations. Upgrade when evidence shows the current plan lacks headroom for real production behavior. Before upgrading, check whether the bottleneck is actually a query, an index, a pool setting, or a maintenance workflow. That approach keeps costs predictable without turning every performance symptom into guesswork.
Written by ArmorDB Engineering
Practical notes on PostgreSQL operations, security, and infrastructure decisions for teams building production applications.
Updated Aug 12, 2026
Keep exploring
Related reading
Comparisons · 8 min read
PostgreSQL Index Types Compared: B-tree, GIN, GiST, BRIN, and Hash
Compare PostgreSQL index types by workload, query pattern, maintenance cost, and production fit so you can choose the right index before adding another expensive structure.
Read articleComparisons · 8 min read
Serverless vs Provisioned PostgreSQL: How to Choose for Production
Compare serverless and provisioned PostgreSQL for latency, cost, pooling, operations, and production readiness before you choose a managed database architecture.
Read articleComparisons · 10 min read
Managed PostgreSQL Pricing: What to Compare Before You Choose
A practical comparison guide to managed PostgreSQL pricing, including compute, storage, backups, high availability, networking, and operational costs.
Read article