PostgreSQL Basic
The OpenTelemetry Collector's PostgreSQL receiver collects 34 metrics from PostgreSQL 9.6+, including connection counts, query performance, lock activity, WAL replication lag, and table/index statistics. This guide configures the receiver, sets up a monitoring user, and ships metrics to base14 Scout.
For advanced monitoring with query statistics, per-table metrics, and replication details, see PostgreSQL Advanced Monitoring.
Prerequisites​
| Requirement | Minimum | Recommended |
|---|---|---|
| PostgreSQL | 9.6 | 14+ |
| OTel Collector Contrib | 0.90.0 | latest |
| base14 Scout | Any | - |
Before starting:
- PostgreSQL must be accessible from the host running the Collector
- Superuser access for initial monitoring user creation
- OTel Collector installed - see Docker Compose Setup
What You'll Monitor​
- Connections: active backends, max connections
- Database: size, commit/rollback rates, temp files, operations
- Tables & Indexes: table/index count and size, vacuum count, sequential vs index scans
- Locks: active locks by type, deadlock count
- WAL & Replication: WAL age, lag, delay, replication data delay
- I/O: blocks read, buffer hits, tuple operations (insert/update/delete/fetch)
Full metric reference: OTel PostgreSQL Receiver
Access Setup​
Create a dedicated PostgreSQL user with monitoring privileges:
-- Connect as superuser (postgres)
CREATE USER postgres_exporter WITH PASSWORD '<your_password>';
GRANT pg_monitor TO postgres_exporter;
The pg_monitor role provides access to all the statistics views and functions
needed for monitoring without requiring superuser privileges.
Ensure your PostgreSQL instance allows connections and has the required statistics enabled:
-- Verify pg_stat_statements extension (optional but recommended)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Check current connections
SELECT count(*) FROM pg_stat_activity;
Test connectivity with the monitoring user:
# Test PostgreSQL connectivity
psql -h <postgres-host> -p <port> -U postgres_exporter -d <database-name> -c "SELECT version();"
Configuration​
receivers:
postgresql:
endpoint: "<postgres-endpoint>:<port>"
collection_interval: 10s
username: ${env:POSTGRES_USER}
password: ${env:POSTGRES_PASSWORD}
databases: ["<db-name>"]
tls:
insecure_skip_verify: true
metrics:
postgresql.database.locks:
enabled: true
postgresql.deadlocks:
enabled: true
postgresql.sequential_scans:
enabled: true
postgresql.bgwriter.buffers.allocated:
enabled: true
postgresql.bgwriter.buffers.writes:
enabled: true
postgresql.bgwriter.checkpoint.count:
enabled: true
postgresql.bgwriter.duration:
enabled: true
postgresql.bgwriter.maxwritten:
enabled: true
postgresql.blocks_read:
enabled: true
postgresql.commits:
enabled: true
postgresql.database.count:
enabled: true
postgresql.db_size:
enabled: true
postgresql.backends:
enabled: true
postgresql.connection.max:
enabled: true
postgresql.rows:
enabled: true
postgresql.index.scans:
enabled: true
postgresql.index.size:
enabled: true
postgresql.operations:
enabled: true
postgresql.replication.data_delay:
enabled: true
postgresql.rollbacks:
enabled: true
postgresql.table.count:
enabled: true
postgresql.table.size:
enabled: true
postgresql.table.vacuum.count:
enabled: true
postgresql.temp_files:
enabled: true
postgresql.wal.age:
enabled: true
postgresql.wal.lag:
enabled: true
postgresql.wal.delay:
enabled: true
postgresql.tup_updated:
enabled: true
postgresql.tup_returned:
enabled: true
postgresql.tup_fetched:
enabled: true
postgresql.tup_inserted:
enabled: true
postgresql.tup_deleted:
enabled: true
postgresql.blks_hit:
enabled: true
postgresql.blks_read:
enabled: true
processors:
resource:
attributes:
- key: environment
value: ${env:ENVIRONMENT}
action: upsert
- key: service.name
value: ${env:SERVICE_NAME}
action: upsert
batch:
timeout: 10s
send_batch_size: 1024
# Export to base14 Scout
exporters:
otlphttp/b14:
endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT}
tls:
insecure_skip_verify: true
service:
pipelines:
metrics:
receivers: [postgresql]
processors: [batch, resource]
exporters: [otlphttp/b14]
Environment Variables​
POSTGRES_USER=postgres_exporter
POSTGRES_PASSWORD=your_password
ENVIRONMENT=your_environment
SERVICE_NAME=your_service_name
OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-tenant>.base14.io
Verify the Setup​
Start the Collector and check for metrics within 60 seconds:
# Test PostgreSQL connection
psql -h ${POSTGRES_HOST} -p <port> -U postgres_exporter -d ${DATABASE_NAME} -c "SELECT version();"
-- Check database statistics
SELECT * FROM pg_stat_database WHERE datname = '<your-database>';
-- Check table statistics
SELECT * FROM pg_stat_user_tables LIMIT 5;
-- Check index usage
SELECT * FROM pg_stat_user_indexes LIMIT 5;
Troubleshooting​
Connection refused​
Cause: Collector cannot reach PostgreSQL at the configured endpoint.
Fix:
- Verify PostgreSQL is running:
systemctl status postgresqlordocker ps | grep postgres - Check
pg_hba.confallows connections from the Collector host - Confirm PostgreSQL is listening on the expected port:
ss -tlnp | grep 5432
Authentication failed​
Cause: Monitoring credentials are incorrect or the user lacks permissions.
Fix:
- Test credentials directly:
psql -h localhost -U postgres_exporter -d postgres - Verify the
pg_monitorrole is granted:SELECT rolname FROM pg_roles WHERE pg_has_role('postgres_exporter', oid, 'member');
No metrics appearing in Scout​
Cause: Metrics are collected but not exported.
Fix:
- Check Collector logs for export errors:
docker logs otel-collector - Verify
OTEL_EXPORTER_OTLP_ENDPOINTis set correctly - Confirm the pipeline includes both the receiver and exporter
WAL metrics showing null or zero​
Cause: WAL metrics require replication to be configured, or the PostgreSQL version does not support the queried stats view.
Fix:
postgresql.wal.agerequires PostgreSQL 13+ withpg_stat_walpostgresql.replication.data_delayrequires at least one replica connected - it reports zero with no replicas
FAQ​
Does this work with PostgreSQL running in Kubernetes?
Yes. Set endpoint to the PostgreSQL service DNS
(e.g., postgresql.default.svc.cluster.local:5432) and inject
credentials via a Kubernetes secret. The Collector can run as a sidecar
or DaemonSet.
How do I monitor multiple PostgreSQL instances?
Add multiple receiver blocks with distinct names:
receivers:
postgresql/primary:
endpoint: primary:5432
username: postgres_exporter
password: "<your_password>"
postgresql/replica:
endpoint: replica:5432
username: postgres_exporter
password: "<your_password>"
Then include both in the pipeline:
receivers: [postgresql/primary, postgresql/replica]
What is the difference between Basic and Advanced monitoring?
This guide uses the OTel PostgreSQL receiver for core database metrics. The Advanced guide adds deeper query-level statistics, per-table I/O, and detailed replication monitoring.
What permissions does the monitoring account need?
The pg_monitor role (available in PostgreSQL 10+). For PostgreSQL 9.6,
grant pg_stat_scan_tables and access to pg_stat_activity individually.
No write permissions are needed.
What's Next?​
- Create Dashboards: Explore pre-built dashboards or build your own. See Create Your First Dashboard
- Monitor More Components: Add monitoring for MySQL, MongoDB, and other components
- Go Deeper: Start with the Advanced monitoring guide for query-level statistics and per-table metrics
- Analyze in Scout: Get
deep PostgreSQL analysis with pgX
- correlate query performance with application traces and infrastructure metrics
Related Guides​
- PostgreSQL Advanced
- Deeper query and table-level monitoring
- OTel Collector Configuration — Advanced collector configuration
- Docker Compose Setup — Run the Collector locally
- MongoDB Monitoring - Alternative database monitoring