How to Fix PostgreSQL 'relation pg_stat_statements does not exist' Error
Threat/Impact Level: MEDIUM | Downtime Risk: HIGH (monitoring blind spot, query performance regression undetectable) | Time to Fix: 5 mins
TL;DR
- What broke:
pg_stat_statementsis referenced in a query or monitoring tool, but the extension was never created in the current database withCREATE EXTENSION. - How to fix it: Add
pg_stat_statementstoshared_preload_librariesinpostgresql.conf, restart Postgres, then runCREATE EXTENSION pg_stat_statements;as a superuser in the target database. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your
postgresql.confor failing migration SQL and get corrected output instantly.
The Incident (What Does the Error Mean?)
Raw error output from psql or application logs:
ERROR: relation "pg_stat_statements" does not exist
LINE 1: SELECT * FROM pg_stat_statements ORDER BY total_exec_time DESC;
^
Immediate consequence: Every query hitting pg_stat_statements — your APM agent, pgBadger, Datadog postgres integration, custom slow-query dashboards — returns a hard error. You are now flying blind on query performance. In a production incident, this means you cannot identify the offending query causing CPU spike or lock contention.
This is not a transient error. It will not self-heal. The view does not exist because the extension lifecycle was never completed.
The Attack Vector / Blast Radius
This is a monitoring gap, not a security exploit — but the blast radius is significant:
- Observability blackout: No
pg_stat_statementsmeans no per-query execution stats. You losecalls,total_exec_time,rows,shared_blk_hit— the entire query performance fingerprint. - Cascading alerting failures: PgBouncer health checks, Datadog
postgres.queries.countmetrics, and custom Grafana panels all start returning errors or zeroes. On-call engineers see false positives. - Post-incident forensics destroyed: If you hit a slow-query incident and this extension was never loaded, you have no historical execution data to do RCA.
- Multi-database trap: Even if
shared_preload_librariesis set correctly,CREATE EXTENSIONis per-database. A new database provisioned fromtemplate1will silently lack the extension unless your provisioning automation handles it.
How to Fix It
Step 1 — Preload the Module (requires restart)
postgresql.conf:
- #shared_preload_libraries = ''
+ shared_preload_libraries = 'pg_stat_statements'
If other libraries are already loaded:
- shared_preload_libraries = 'pg_partman_bgw'
+ shared_preload_libraries = 'pg_partman_bgw,pg_stat_statements'
Restart Postgres. On systemd:
systemctl restart postgresql
On RDS/Aurora: modify the parameter group, set shared_preload_libraries to include pg_stat_statements, and reboot the instance.
Step 2 — Create the Extension in the Target Database
Connect as superuser to each database that needs it:
- -- Extension was never created
- SELECT * FROM pg_stat_statements;
+ -- Run this once per database as superuser
+ CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
+ SELECT * FROM pg_stat_statements LIMIT 5;
Step 3 — Tune Collection Parameters (Enterprise Best Practice)
Default pg_stat_statements.max is 5000 — too low for high-throughput OLTP. Add to postgresql.conf:
- # No pg_stat_statements tuning
+ pg_stat_statements.max = 10000
+ pg_stat_statements.track = all
+ pg_stat_statements.track_utility = on
+ pg_stat_statements.save = on
Grant read access to a non-superuser monitoring role (least privilege):
- -- Monitoring agent connecting as superuser
+ GRANT pg_monitor TO monitoring_user;
+ GRANT SELECT ON pg_stat_statements TO monitoring_user;
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Enforce Extension in Database Migrations
Add this as the first migration in Flyway, Liquibase, or raw SQL migration scripts:
-- V001__baseline_extensions.sql
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
The IF NOT EXISTS guard makes it idempotent — safe to re-run.
2. Terraform / Helm Enforcement
If provisioning RDS via Terraform, enforce the parameter group:
- # No parameter group defined — using default
+ resource "aws_db_parameter_group" "postgres" {
+ family = "postgres15"
+ parameter {
+ name = "shared_preload_libraries"
+ value = "pg_stat_statements"
+ apply_method = "pending-reboot"
+ }
+ }
3. Checkov / OPA Policy Gate
Write a custom Checkov check that fails any Terraform plan where an aws_db_instance resource does not reference a parameter group containing pg_stat_statements. Block the PR before it reaches staging.
4. Post-Deploy Smoke Test
Add to your deployment pipeline's health check stage:
psql $DATABASE_URL -c "SELECT count(*) FROM pg_stat_statements;" || (echo 'pg_stat_statements missing' && exit 1)
This turns a silent monitoring gap into a hard pipeline failure.