api_request log line and the <env>-api-events-request-latency metric in Cloud Monitoring) tells you which route is slow. pg_stat_statements tells you which SQL is slow. Use the route alert to find the route, then this page to find the statement.
The production database is Azure Database for PostgreSQL Flexible Server prod-cus-core-postgres (PostgreSQL 16, B_Standard_B2s: 2 burstable vCores, 4 GiB), managed by infra/modules/database.
Enable it (one time per server)
Enabling it takes two server settings and oneCREATE EXTENSION in the application database.
-
Preload the library.
pg_stat_statementsonly collects data when it is inshared_preload_libraries. First check whether it is already there:Ifpg_stat_statementsis missing, add it: Azure portal → the server → Server parameters →shared_preload_libraries→ addPG_STAT_STATEMENTS→ Save. This parameter is static: the server restarts. With no HA, that restart takes api-events’ database offline for a minute or two, so do it in a quiet window. Setpg_stat_statements.tracktotopif it isnone. That parameter is dynamic, so no restart. -
Allow the extension. Azure refuses
CREATE EXTENSIONfor anything that is not inazure.extensions. That list is managed by Terraform (var.postgres_extensionsininfra/modules/database, defaultPG_TRGM,BTREE_GIN), so change it there, not in the portal, or the next apply reverts it:azure.extensionsis dynamic. Applying it does not restart the server. -
Create the extension in the
soundcheckdatabase, as the admin role:This is not in a Prisma migration on purpose. Local and CI databases (postgres:14-alpine) do not preload the library, and the extension only works on a server that does.
pg_stat_statements.max, default 5000 entries). Query text is normalized: literals become $1, $2, so the view holds query shapes, not customer values.
Read it
api-events/scripts/perf/pgStatStatements.ts prints the top statements for the current database. It only reads. It connects with default_transaction_read_only=on, runs inside BEGIN READ ONLY, rolls back, and never calls pg_stat_statements_reset().
Output columns:
The script exits
2 with setup steps when the extension is missing, and 1 on any other error.
Use a read-only database role for this, not the admin login. The pg_read_all_stats role is enough to see every user’s statements.
From a slow route to a statement
-
The per-route alert, or the “p95 latency by route” tile on the
<env> api-events latencydashboard, names the route, e.g.GET /api/v1/organizations/:orgId/offers. -
In Cloud Logging, filter
jsonPayload.event="api_request" AND jsonPayload.route="GET /api/v1/organizations/:orgId/offers"and sort byjsonPayload.duration_msto see how slow it is and when. The same records, minus/healthand/, are in PostHog Logs (servicesoundcheck-api-events, messageapi_request), so you can rank routes with SQL:SetAPI_REQUEST_LOG_POSTHOG=falseon api-events to stop the PostHog copy without touching the Cloud Logging line. -
Run the report with
--order total, then with--order mean --min-calls 50. Match statements to the route’s operation inapi-events/src/systems/<domain>/(table names andWHEREshapes are usually enough). -
Check the candidate with
EXPLAIN (ANALYZE, BUFFERS)on a local seeded database first. Any fix goes through a normal migration (see Database).