A database can work perfectly when an application has a few hundred users and still become a serious bottleneck when traffic starts growing. Pages take longer to load, API requests wait for database responses, background jobs fall behind, and connection limits become harder to manage.
In many applications, developers initially try to solve these problems by increasing the database size. Sometimes that helps, but adding more resources does not automatically fix inefficient queries, excessive connections, or workloads competing for the same database resources.
Heroku Postgres Advanced provides database capabilities that help teams manage demanding workloads with more control over compute resources, storage, and database operations.
For applications handling high traffic, the important part is understanding which resource is becoming a bottleneck and choosing the right scaling strategy.
This article explains how Heroku Postgres Advanced can support high-traffic applications, how to approach database capacity planning, and what developers should check before changing their production database configuration.
Understanding Heroku Postgres Advanced
Heroku Postgres is a managed PostgreSQL service that allows developers to run PostgreSQL databases without managing the underlying database infrastructure themselves.
Heroku handles many operational responsibilities, including database provisioning, backups, maintenance, and infrastructure management, depending on the selected plan.
Heroku Postgres Advanced is intended for applications with more demanding database requirements. It provides capabilities for managing database resources and workloads as application requirements grow.
For development teams, the main objective is not simply to purchase a larger database plan. It is to understand how the database is being used and identify whether the application needs additional compute capacity, storage capacity, or better workload management.
A high-traffic application may experience several database problems at the same time:
Queries take longer as tables and indexes grow.
Too many application connections compete for available database connections.
Reporting queries interfere with transactional operations.
Background jobs consume resources needed by user-facing requests.
Storage growth increases maintenance and capacity requirements.
Each problem requires a different solution.
Understanding the Main Database Scaling Areas
Database scaling is easier when compute, storage, and workload requirements are considered separately.
1. Compute Scaling
Compute resources determine how much processing capacity is available to execute database operations.
PostgreSQL uses CPU and memory for query execution, sorting, joins, indexing, caching, and transaction processing.
When an application receives more requests, the database may need to process more queries simultaneously.
For example, consider an e-commerce application that receives a large number of product searches, customer requests, and order transactions.
If CPU utilization remains high and queries begin taking longer, the database may be reaching its compute capacity.
Increasing available compute resources can help, but developers should first investigate expensive queries and unnecessary database operations.
2. Storage Scaling
Storage capacity determines how much database data can be maintained.
As an application grows, its database may contain:
Storage planning should consider both current database size and expected growth.
For example, an application storing 50 GB of data today may need substantially more capacity as transaction history and customer activity increase.
However, storage capacity and query performance are not the same thing. Increasing available storage does not automatically make a slow SQL query execute faster.
3. Workload Management
Not all database queries have the same importance.
A customer placing an order should not have to compete unnecessarily with a background report generating millions of rows.
Consider two workloads:
Workload |
Typical priority |
Database behavior |
Customer login |
High |
Requires quick response |
Order creation |
High |
Requires reliable transaction processing |
Product search |
High |
Frequently executed queries |
Daily sales report |
Medium |
May scan large datasets |
Historical data export |
Low |
Can run in the background |
Data cleanup |
Low |
Can be scheduled during quieter periods |
Separating and managing these workloads helps protect important application operations from resource-intensive background processing.
Scaling Heroku Postgres Advanced for High-Traffic Applications
How Compute and Storage Work Independently
One of the important architectural changes in Heroku Postgres Advanced is the separation of compute and storage.
In traditional database configurations, increasing storage capacity may require moving to a larger database plan that also provides additional compute resources.
This can lead to unnecessary spending when an application needs more storage but does not require additional processing power.
Heroku Postgres Advanced separates these resources.
For example, consider an application with the following requirements:
Database storage is growing because of transaction history.
CPU utilization remains relatively low.
The application does not need additional database processing capacity.
In this situation, increasing compute resources would not necessarily solve the actual problem.
With independent storage scaling, the team can increase storage capacity without automatically increasing the compute level.
The same principle applies in reverse. If the database has sufficient storage but struggles with query processing during traffic spikes, the team can increase compute capacity without unnecessarily scaling storage.
This approach gives database administrators more flexibility when planning infrastructure resources.
Understanding Leader and Follower Instance Pools
Heroku Postgres Advanced introduces instance pools to help distribute database workloads.
An Advanced database cluster contains a leader instance pool and can have additional follower instance pools.
The leader pool handles read and write operations. Follower pools are designed for read operations.
A typical architecture looks like this:
Application
|
-----------------------
| |
Write Requests Read Requests
| |
v v
Leader Instance Follower Pool
| |
Read and Write Read Only
| |
+------ Shared -------+
Storage
The leader remains responsible for database writes and transactional operations.
Follower pools provide additional read capacity. They can serve application queries or separate workloads such as reporting and analytics.
An Advanced cluster supports up to five follower instance pools and a maximum of 13 follower instances across the cluster.
Each follower pool provides a single endpoint, with traffic distributed across its instances.
When Should You Use Follower Pools?
Follower pools are useful when an application performs significantly more reads than writes.
Consider an online shopping platform.
Customers frequently browse products, view categories, check order history, and search for available inventory. These operations generate substantial read traffic.
At the same time, customers create orders, update addresses, and complete payments. These operations require writes to the primary database.
Instead of sending every read request to the leader, the application can route suitable read operations to a follower pool.
This reduces competition for resources on the leader.
However, developers must understand that follower databases can have replication lag. A recently committed transaction on the leader may not be immediately visible on a follower.
For this reason, operations requiring immediate consistency should continue using the leader.
Examples include:
Reading an order immediately after creating it.
Checking the result of a payment transaction.
Reading updated inventory after a purchase.
Verifying a recently changed account setting.
Follower pools are better suited to workloads that can tolerate a small delay in receiving updated data.
Provisioning a Heroku Postgres Advanced Database
Before scaling an existing database, developers should understand how to provision and configure an Advanced cluster.
Heroku supports provisioning through its CLI and interactive configuration options.
First, make sure the Heroku CLI is installed and authenticated.
heroku login
Create an Advanced database using the following command:
heroku addons:create \
heroku-postgresql:advanced \
--app my-production-app
The exact provisioning options depend on the current Advanced database configuration and account access.
You can also specify a compute level and other supported settings through the provisioning options.
For example, a compute level can be selected using a command structure like this:
heroku addons:create \
heroku-postgresql:advanced \
--level 16G-Performance \
--app my-production-app
Use a level that matches the application's measured requirements rather than choosing the largest available configuration.
After provisioning, inspect the database details:
heroku addons --app my-production-app
The database is managed through Heroku's Advanced database configuration and its associated CLI commands. Verify the provisioning options supported by your account before running production changes.
Connecting an Application to Heroku Postgres Advanced
Once the database is provisioned, the next step is connecting the application.
Heroku exposes database connection information through configuration variables.
For a Node.js application using the pg package, a basic connection can look like this:
import pg from 'pg';
const { Pool } = pg;
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: true
},
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000
});
export default pool;
The application reads the database connection string from an environment variable instead of storing credentials directly in source code.
The max option controls the maximum number of connections maintained by this application-level pool.
The timeout settings help prevent application requests from waiting indefinitely when database connections cannot be established.
The SSL configuration assumes the environment has the appropriate trusted certificates. Follow Heroku's current connection guidance for the selected database environment rather than disabling certificate verification.
Routing Read and Write Queries
When using follower pools, applications can maintain separate connection pools for read and write operations.
For example:
import pg from 'pg';
const { Pool } = pg;
export const writePool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10
});
export const readPool = new Pool({
connectionString: process.env.DATABASE_READER_URL,
max: 20
});
Here:
The configuration variable names are application-defined. Configure the actual follower pool endpoint using the Heroku-provided connection information.
A service can then route queries based on their purpose.
import { writePool, readPool } from './database.js';
export async function getProducts() {
const result = await readPool.query(
'SELECT id, name, price FROM products'
);
return result.rows;
}
export async function createOrder(customerId, total) {
const result = await writePool.query(
`INSERT INTO orders (customer_id, total)
VALUES ($1, $2)
RETURNING id, customer_id, total`,
[customerId, total]
);
return result.rows[0];
}
This example sends product searches to the follower pool and order creation to the leader.
The separation is useful because product browsing and transactional writes have different consistency requirements.
For production applications, connection handling should also account for transaction boundaries, error recovery, retries, and graceful pool shutdown.
Scaling Compute Resources for Growing Traffic
When the leader database becomes the processing bottleneck, increasing its compute level may be necessary.
Heroku Postgres Advanced provides different compute levels with varying memory and vCPU allocations.
Compute level |
Memory |
vCPU |
4G-Performance |
4 GB |
2 |
8G-Performance |
8 GB |
2 |
16G-Performance |
16 GB |
2 |
32G-Performance |
32 GB |
4 |
64G-Performance |
64 GB |
8 |
128G-Performance |
128 GB |
16 |
These are examples of the available compute configurations. The appropriate level depends on query complexity, concurrency, caching requirements, and workload characteristics.
You can inspect the available levels using:
heroku data:pg:levels
To update an existing database, use:
heroku data:pg:update -a my-production-app
The interactive command allows you to select the database and change its leader compute level.
After selecting the required configuration, review the proposed change before confirming it.
Heroku supports changing Advanced compute configurations without requiring an application restart when existing attachments and configuration variables remain unchanged.
Monitoring Database Performance
Scaling without monitoring is mostly guesswork.
Before increasing database resources, understand what is happening inside PostgreSQL.
Heroku provides database metrics, logs, and dashboard information that help developers monitor database health.
The following areas deserve attention.
Connection Usage
PostgreSQL connections are limited resources.
If an application creates too many connections, new requests may fail even when the database has sufficient CPU and memory.
For example, consider a Node.js application running several web processes. If every process creates its own connection pool, the combined connection count can become much larger than expected.
You can inspect active database connections using:
SELECT
state,
COUNT(*) AS total_connections
FROM pg_stat_activity
GROUP BY state
ORDER BY total_connections DESC;
This query shows how many connections are active in different states.
Use the results to identify whether the application is maintaining too many idle connections or generating excessive concurrent queries.
Identify Expensive Queries
PostgreSQL provides the pg_stat_statements extension for tracking query execution statistics.
When available and enabled, it helps identify queries that consume significant execution time.
For example:
SELECT
query,
calls,
total_exec_time,
mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
This query lists frequently executed and expensive SQL statements based on accumulated execution time.
A query with a high total execution time may be worth investigating, even if its individual execution time appears reasonable.
Before increasing compute capacity, check whether indexes, query structure, or unnecessary database operations are responsible for the problem.
Planning Database Capacity Before Traffic Increases
Capacity planning should happen before the application reaches its resource limits.
Consider an application that experiences seasonal traffic increases during promotional campaigns.
Instead of waiting for the database to become slow, the development team can estimate future requirements using existing monitoring data.
A practical capacity planning process includes:
Measure normal database usage over a representative period.
Identify peak connection counts and query activity.
Review database storage growth.
Identify read-heavy and write-heavy workloads.
Estimate upcoming traffic increases.
Test the proposed configuration under realistic workloads.
Monitor resource consumption after deployment.
Do not select a database configuration based only on the number of application users.
Two applications with the same number of users can have very different database requirements depending on how frequently they query the database and how efficiently those queries are written.
High Availability and Database Reliability
Performance is only one part of database planning. Production applications also need to consider availability.
Heroku Postgres Advanced supports high availability through standby instances in the leader pool.
When high availability is enabled, a standby instance provides redundancy for the leader.
Follower pools can also have multiple instances. Two or more instances in a follower pool provide high availability within that pool.
However, read scaling and high availability solve different problems.
Capability |
Primary purpose |
Compute scaling |
Increase database processing capacity |
Follower pools |
Distribute read workloads |
High availability |
Improve resilience against instance failures |
Storage scaling |
Accommodate database growth |
Monitoring |
Identify problems before they become critical |
A follower pool should not be treated as a replacement for the leader's high-availability configuration.
Also, high availability does not eliminate the need for backup and recovery planning. Verify the backup and rollback capabilities supported by your selected Advanced configuration.
Best Practices for Scaling Heroku Postgres Advanced
Do Not Scale Before Investigating Slow Queries
A larger database may temporarily improve performance, but it will not fix poorly written SQL queries.
Use query statistics and execution plans to understand where time is being spent.
Separate Analytics From Transactional Workloads
Heavy reporting queries can compete with customer-facing operations.
Use dedicated follower pools for reporting and analytics workloads where eventual consistency is acceptable.
Keep Application Connection Pools Under Control
Do not configure every application process to use the maximum available database connections.
Calculate the combined connection requirement across all running processes and background workers.
Monitor Storage Growth
Set storage quotas and warning thresholds so that administrators receive notifications before reaching important capacity limits.
Heroku Postgres Advanced supports configurable storage quotas and thresholds.
Test Scaling Changes Before Peak Traffic
Use load testing to understand how the database behaves when concurrent requests increase.
Test representative read and write operations rather than relying only on simple health checks.
Review Database Costs Regularly
Additional compute instances and follower pools increase infrastructure costs.
Review actual utilization and remove unnecessary resources when workloads change.
Common Mistakes When Scaling PostgreSQL
Mistake |
Why it creates problems |
Better approach |
Increasing compute without investigation |
May not address the actual bottleneck |
Review query and resource metrics |
Sending every read to the leader |
Creates unnecessary competition |
Route suitable reads to followers |
Ignoring replication lag |
Users may receive outdated data |
Use the leader for consistency-sensitive reads |
Creating excessive connections |
Can exhaust available connection capacity |
Configure application connection pools |
Ignoring storage growth |
Can create unexpected capacity problems |
Configure storage monitoring and thresholds |
Using followers for transactional writes |
Follower pools are read-only |
Send writes to the leader |
Scaling without cost review |
Can increase infrastructure spending unnecessarily |
Match resources to measured demand |
Advantages and Disadvantages of Heroku Postgres Advanced
Advantages
Independent scaling of compute and storage.
Follower instance pools for distributing read workloads.
Flexible compute configurations for different application requirements.
High-availability support.
Centralized database management through the Heroku Dashboard and CLI.
Storage quotas and thresholds for capacity management.
Support for multiple PostgreSQL versions.
Disadvantages
Additional compute and follower instances increase costs.
Applications need changes to route reads and writes appropriately.
Replication lag must be considered when using follower pools.
Database scaling still requires performance monitoring and capacity planning.
Existing database architectures may need adjustments before adopting instance pools.
Summary
Scaling Heroku Postgres Advanced is not simply about choosing a larger database configuration. It requires understanding how an application uses compute resources, storage, connections, and database workloads.
Its separation of compute and storage gives development teams more flexibility when application requirements change. Leader and follower instance pools also provide a way to distribute read traffic and isolate analytics workloads from important transactional operations.
For existing applications, start by reviewing database metrics, identifying expensive queries, and checking connection usage. Then decide whether the application needs additional compute, storage, or follower capacity.
Most importantly, test scaling changes with realistic workloads and monitor the results after deployment.
A well-planned PostgreSQL architecture should support application growth without unnecessarily increasing infrastructure costs or putting important database operations at risk.
Join the conversation! Your thoughts help the community grow.