Skip to content

ProjectDB PgBouncer pool sizing - #6965

Open
esoergel wants to merge 3 commits into
es/projectdb-pgbouncer-authfrom
es/projectdb-pool-sizing
Open

esoergel wants to merge 3 commits into
es/projectdb-pgbouncer-authfrom
es/projectdb-pool-sizing

Conversation

@esoergel

@esoergel esoergel commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

These three stacked PRs introduce a database-level safeguard that keeps a Project DB query from reading another domain's data, even if the query layer above it has a bug or is handed hostile SQL.

Part I: HQ Query Users (#6962)

Each domain gets its own postgres role, which has read-only access to only that domain's tables. Queries on behalf of that domain are authenticated as that user, so postgres enforces separation between tenants.

HQ provisions these query users using a constrained SECURITY DEFINER function called projectdb_provision_role which narrowly defines the action. This function is created during database provisioning. Passwords are derived from the domain name via django.utils.crypto.salted_hmac, never stored.

Part II: PgBouncer

PgBouncer connection pooling complicates things in two ways

Part IIa: PGBouncer Authentication (#6963)

PgBouncer authenticates queries itself, so it has to know user passwords. Currently, it validates against a file called userlist.txt which is managed by commcare-cloud. Adding a new user requires amending that file, which just isn't doable in this context. Instead, we give pgbouncer an auth_query it can run to get the username and password to authenticate a user that's not in userlist.txt.

To do this, we need a few things in place:

  • A user_lookup function as described in the pgbouncer docs (https://www.pgbouncer.org/config.html), supporting the auth_query
  • An auth_user pgbouncer can authenticate as when running the auth_query, and a password for that user
  • A role for the pgbouncer auth user with only LOGIN permissions by default
    • This role must also be granted access to the user_lookup function

Part IIb: (This PR)

PgBouncer keys its pools by (user, database) pair, meaning that each domain will have it's own pool in pgbouncer. Right now, we allow up to 490 open connections per pool (default_pool_size). Since ProjectDB uses many pools, we can instead set a max_db_connections as a limit across all ProjectDB pools, and set a much lower pool_size to prevent one domain from claiming all that for itself.

Environments Affected

Staging, since it has project_db configured already. Should only affect other environments when that's enabled.

Announce New Release

No. Shouldn't require action by anyone but me.

@esoergel
esoergel changed the base branch from master to es/projectdb-pgbouncer-auth August 14, 2026 11:58
@esoergel
esoergel force-pushed the es/projectdb-pool-sizing branch 2 times, most recently from d089750 to 5774de4 Compare August 14, 2026 14:18
@MartinRiese
MartinRiese force-pushed the es/projectdb-pool-sizing branch from 5774de4 to 5abec1a Compare September 17, 2026 19:11
esoergel and others added 3 commits September 18, 2026 09:28
ProjectDB uses a user for each domain, and pgbouncer keys its pools by
(user, database), so that's a pool per domain.  Add a max_db_connections
setting to create a cap across all domains.
Picks up pgbouncer_max_db_connections, null everywhere since no test
environment sets it.

Regenerated with:
  python -c "import sys; sys.path.insert(0, '.'); \
    from tests.test_postgresql_config import update_configs; update_configs()"

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Doc updates in this commit explain the change
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant