Tune PostgreSQL

  • Tier: Free, Premium, Ultimate
  • Offering: GitLab Self-Managed

You should tune PostgreSQL when:

  • Other GitLab components are reconfigured or scaled up in a way that affects the database.
  • The performance of your GitLab environment is impaired.
  • GitLab uses an external PostgreSQL service.

Required settings for external instances

The following settings are required for externally managed PostgreSQL instances.

Tunable settingRequired valueMore information
work_memminimum 8 MBThis value is the Linux package default. In large deployments, if queries create temporary files, you should increase this setting.
maintenance_work_memminimum 64 MBYou require more for larger database servers.
max_connectionsminimum 400Set based on what the database host can serve. See Determine your database host capacity. The connection-demand formula gives frontend demand, not this value.
shared_buffersminimum 2 GBYou require more for larger database servers. The Linux package default is set to 25% of server RAM.
statement_timeout15000 to 60000A statement timeout prevents runaway issues with locks and the database rejecting new clients. You should use values between 15 and 60 seconds (15000 to 60000 milliseconds), where one minute matches the Puma rack timeout setting.
hot_standby_feedbackonFor configurations with multiple nodes and database load balancing configured, ensure that all replica nodes have hot_standby_feedback enabled to prevent lag buildup.

You can configure some PostgreSQL settings for the specific database, rather than for all databases on the server.

  • You might limit configuration to specific databases when hosting multiple databases on the same server.
  • For guidance on where to apply configuration, consult your database administrator or vendor.
  • For GCP Cloud SQL, you can set statement_timeout on a specific database or user, but not as a database flag. For example: ALTER DATABASE gitlab SET statement_timeout = '60s';

Plan your database connections

This formula computes the application’s frontend connection demand - the number of connections Rails opens to the database (or to PgBouncer). This is not the value to use for PostgreSQL max_connections. See Determine your database host capacity for how to size max_connections.

GitLab versions 16.0 and later use two sets of database connections for the main and ci tables. This doubles connection usage, even when the same PostgreSQL database serves both sets of tables.

GitLab uses database connections from multiple components. Proper connection planning prevents database connection exhaustion and performance issues.

Each GitLab component uses database connections based on its configuration. Sidekiq and Puma establish a pool of connections to PostgreSQL at initialization. The number of connections in the pool can increase later if there are connection spikes or temporary increases in demand:

  • Configure database pool headroom with the environment variable DB_POOL_HEADROOM.
  • When you tune PostgreSQL, plan for pool headroom but do not change it. GitLab deployments respond better to higher demand if more capacity is available: deploy more Sidekiq or Puma workers.

Puma

Puma connections = puma['worker_processes'] × (puma['max_threads'] + DB_POOL_HEADROOM)

By default:

  • puma['worker_processes'] is based on CPU core count.
  • puma['max_threads'] is 4.
  • DB_POOL_HEADROOM is 10.

Per-worker calculation: Each Puma worker uses 4 threads + 10 headroom, for a total of 14 connections.

Default calculation, assuming 8 vCPU: 8 workers × 14 connections per worker, for a total of 112 Puma connections.

Sidekiq

Sidekiq connections = Number of Sidekiq processes × (sidekiq['concurrency'] + 1 + DB_POOL_HEADROOM)

By default:

  • The number of Sidekiq processes is 1.
  • sidekiq['concurrency'] is 20.
  • DB_POOL_HEADROOM is 10.

Default calculation: 1 Sidekiq process × (20 concurrency + 1 + 10 headroom), for a total of 31 total Sidekiq connections.

Geo Log Cursor (Geo installations only)

The Geo Log Cursor daemon runs on all GitLab Rails nodes in a secondary site.

Geo log cursor connections = 1 + DB_POOL_HEADROOM

Default calculation: 1 + 10 headroom, for a total of 11 Geo connections.

Total connection requirements

For single node installations:

Total connections = 2 × (Puma + Sidekiq + Geo)

For multi-node installations, multiply by the number of nodes running each component:

Total connections = 2 × ((Puma × Rails nodes) + (Sidekiq × Sidekiq nodes) + (Geo × secondary Rails nodes))

Multiplying by 2 accounts for the dual database connections in GitLab 16.0 and later.

For Geo installations:

  • Primary site: Use Geo = 0. Geo Log Cursor doesn’t run on primary sites.
  • Secondary sites: Calculate the Geo Log Cursor database connections for one secondary site, and apply that same calculation to all secondary sites.
  • Each Geo site connects to its own database, so you don’t need to sum connections across multiple Geo sites.
  • Set max_connections to the same value on both the primary PostgreSQL database and all replica databases, using the highest connection requirement across all Geo sites.

Examples

Single node installation

This example is based on the GitLab reference architecture for 20 RPS (requests per second) or 1000 users:

ComponentNodesConfigurationConnections per componentComponent total, dual database
Puma18 workers, 4 threads each14 per worker224
Sidekiq11 process, 20 concurrency31 per process62
Total286

Multi-node installation

This example is based on the GitLab reference architecture for 40 RPS (requests per second) or 2000 users:

ComponentNodesConfigurationConnections per componentComponent total, dual database
Puma28 workers per node, 4 threads each14 per worker448
Sidekiq14 processes, 20 concurrency each31 per process248
Total696

Single node installation with Geo

This example is based on the GitLab reference architecture for 20 RPS (requests per second) or 1000 users.

Component per Geo siteNodesConfigurationConnections per componentComponent total, dual database
Puma18 workers, 4 threads each14 per worker224
Sidekiq11 process, 20 concurrency31 per process62
Geo Log Cursor (secondary sites only)11 process11 per process22
Total308

Multi-node installation with Geo

This example is based on the GitLab reference architecture for 40 RPS (requests per second) or 2000 users:

Component per Geo siteNodesConfigurationConnections per componentComponent total, dual database
Puma28 workers per node, 4 threads each14 per worker448
Sidekiq14 processes, 20 concurrency each31 per process248
Geo Log Cursor (secondary sites only)21 process per Rails node11 per process44
Total740

Determine your database host capacity (max_connections)

max_connections is bounded by what the database host can actually serve - a function of CPU, memory, and IO capacity. It is not derived from the application connection-demand formula.

  • As a general rule, set max_connections to a multiple of the number of vCPUs, typically in the 2-10x range. This is only a starting point: the value the host can sustain also depends on available memory, IO capacity, and workload.
  • For finer tuning, use PGTune, which accounts for memory, storage type, and workload.
  • If you use a managed PostgreSQL database (RDS, Cloud SQL, Azure Database for PostgreSQL), refer to the vendor’s documentation.
  • Changing the max_connections value requires restarting all PostgreSQL nodes.

For Geo installations, set max_connections to the same value on the primary PostgreSQL database and all replica databases. See Total connection requirements for the Geo-specific guidance.

Size connections for your topology

Use the following guidance based on whether PgBouncer sits between the application and the database.

Without PgBouncer

The application connects directly to PostgreSQL, so every connection in the demand calculation must be served by a backend.

  1. Calculate total frontend connection demand using the Plan your database connections formula.
  2. Set PostgreSQL max_connections to at least that value, provided the host can serve it. See Determine your database host capacity.
  3. If total connection demand exceeds what the host can serve - as a rough guide, when it pushes past the 2-10x vCPU range, or drives CPU, memory, or IO past a healthy level - do not raise max_connections to match. This is a clear sign the installation needs connection pooling. Add PgBouncer and size it as described in With PgBouncer.

With PgBouncer

PgBouncer pools connections, so the application’s frontend demand and the database’s backend connections are sized separately.

In PgBouncer terminology:

  • pgbouncer['max_client_conn'] is the frontend pool: connections from Rails to PgBouncer.
  • pgbouncer['default_pool_size'] is the backend pool: connections from PgBouncer to the database. This value is sized per pool (per database/user pair). Because GitLab 16.0 and later opens separate pools for main and ci, the backend pool budget must account for both pools per PgBouncer node.

To size connections when using PgBouncer:

  1. Calculate your worst-case frontend connection demand using the Plan your database connections formula.
  2. Divide that total by the number of PgBouncer nodes, and use the result multiplied by 2 for each node’s pgbouncer['max_client_conn'].
  3. Determine PostgreSQL max_connections from what the database host can serve, not from the frontend demand formula.
  4. Divide that max_connections value by the number of PgBouncer nodes, and use the result for each node’s pgbouncer['default_pool_size'].

For detailed reference on these parameters, see Fine tuning in the PgBouncer documentation.