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:

  • Customer records.

  • Product information.

  • Transaction history.

  • Application logs.

  • Audit records.

  • Background processing data.

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:

Code
                    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.

Bash
heroku login

Create an Advanced database using the following command:

Bash
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:

Bash
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:

Bash
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:

JavaScript
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:

JavaScript
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:

  • DATABASE_URL points to the leader database.

  • DATABASE_READER_URL points to the follower pool.

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.

JavaScript
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:

Bash
heroku data:pg:levels

To update an existing database, use:

Bash
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:

SQL
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:

SQL
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:

  1. Measure normal database usage over a representative period.

  2. Identify peak connection counts and query activity.

  3. Review database storage growth.

  4. Identify read-heavy and write-heavy workloads.

  5. Estimate upcoming traffic increases.

  6. Test the proposed configuration under realistic workloads.

  7. 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.