Skip to main content
View rawEdit

Architecture

Deep-dive into PostgresAI monitoring system components and data flow.

System overview​

┌─────────────────────────────────────────────────────────────────────┐
│ PostgresAI Monitoring │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │ │
│ │ Cluster A │ │ Cluster B │ │ Cluster C │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ pgwatch │ │
│ │ (collector)│ │
│ └──────┬──────┘ │
│ │ Prometheus format │
│ ┌──────▼────────┐ │
│ │VictoriaMetrics│ │
│ │ (storage) │ │
│ └──────┬────────┘ │
│ │ PromQL │
│ ┌──────▼────────┐ │
│ │ Grafana │ │
│ │(visualization)│ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

Components​

pgwatch — Metrics collector​

Purpose: Collect PostgreSQL metrics and expose in Prometheus format

Key functions:

  • Execute SQL queries against PostgreSQL
  • Transform results to Prometheus metrics
  • Expose the /pgwatch metrics endpoint on :9091 (Prometheus sink)

Configuration:

SettingDefaultDescription
Scrape interval30sVictoriaMetrics scrapes the pgwatch-prometheus job every 30s (the 15s global default is overridden for this job; the separate query-info job — metrics_path: /query_info_metrics — runs every 300s)
Collection intervalper-metricEach metric group has its own interval in metrics.yml (most 30s; pg_stat_activity/wait_events 15s)

Collected data sources:

ViewMetrics
pg_stat_statementsQuery performance
pg_stat_activitySession state
wait_events (from pg_stat_activity)Wait event sampling
pg_stat_all_tables / table_statsTable access patterns
pg_stat_all_indexesIndex usage
db_stats (from pg_stat_database)Database-level stats
bgwriterCheckpoint behavior

VictoriaMetrics — time-series database​

Purpose: Store and query metrics

Key functions:

  • Ingest metrics from pgwatch
  • Compress and store time-series data
  • Execute PromQL queries

Performance characteristics:

AspectVictoriaMetricsPrometheus
Compression10x betterBaseline
Query speed2-5x fasterBaseline
Memory usage3-5x lowerBaseline
High availabilityBuilt-in clusteringFederation

Storage model:

In this deployment VictoriaMetrics is started with -storageDataPath=/victoria-metrics-data, and the victoria_metrics_data Docker volume is mounted at /victoria-metrics-data. The on-disk layout under that path follows VictoriaMetrics' standard structure (recent vs. historical data parts, a label index, and optional snapshots).

Grafana — Visualization​

Purpose: Dashboard and alerting UI

Key functions:

  • Render time-series charts
  • Dashboard templating with variables
  • Unified alerting

Dashboard structure:

PostgresAI dashboards:
├── 01. Node overview (cluster-level)
├── 02. Query analysis (top-N queries)
├── 03. Single query (query deep-dive)
├── 04. Wait events (session analysis)
├── 05. Backups (WAL archiving)
├── 06. Replication (lag monitoring)
├── 07. Autovacuum (vacuum status)
├── 08. Table stats (table analysis)
├── 09. Single table (table deep-dive)
├── 10. Index health (index analysis)
├── 11. Single index (index deep-dive)
├── 12. SLRU (cache stats)
├── 13. Lock contention (lock waits)
├── 14. I/O statistics (pg_stat_io, PG16+)
└── Self-monitoring (stack health)

instance-jobs — outbound collection channel (optional)​

Purpose: Let PostgresAI request data from the monitoring host without opening any inbound connection to it. New in 0.17, off by default.

How it works:

  • Polls the PostgresAI platform over outbound HTTPS for work
  • Runs each job against the local VictoriaMetrics (for example, a PromQL query sent with postgresai promql)
  • Posts the result back, then waits for the next poll

The container is gated by the instance-jobs compose profile. Enable it with postgresai mon local-install --instance-jobs (disable with --no-instance-jobs), which writes COMPOSE_PROFILES=instance-jobs to .env so every later mon command covers it; an upgrade never turns it on or off. It authenticates with the API key in .pgwatch-config, mounted read-only, and runs with a read-only filesystem and no Linux capabilities. PostgresAI must also enable the channel on the platform side; until then a running container receives no work. See Outbound collection channel.

Data flow​

Collection flow​

1. pgwatch connects to PostgreSQL
└── Uses monitoring user credentials
└── Executes metric collection queries

2. Query results transformed to metrics
└── Column values → metric values
└── Column names → labels

3. Metrics exposed on the `/pgwatch` endpoint (`:9091`)
└── Prometheus exposition format
└── Timestamp attached

4. VictoriaMetrics scrapes the pgwatch-prometheus sink
└── HTTP GET pgwatch-prometheus:9091/pgwatch
└── `pgwatch-prometheus` job scrape_interval: 30s (scrape_timeout 25s)

5. Metrics stored in VictoriaMetrics
└── Compressed time-series storage
└── Indexed by labels

Query flow​

1. User opens Grafana dashboard
└── Dashboard loads panel queries

2. Grafana sends PromQL to VictoriaMetrics
└── Variables substituted
└── Time range applied

3. VictoriaMetrics executes query
└── Index lookup by labels
└── Data retrieval from storage
└── Aggregation/calculation

4. Results returned to Grafana
└── Time series data
└── Rendered as charts

Metric naming​

Convention​

pgwatch exports series as pgwatch_<metric-group>_<column>. The Prometheus metric type is driven by each metric group's gauges: list in config/pgwatch-prometheus/metrics.yml: a column is emitted as a Prometheus gauge only if its group lists it (or uses gauges: ['*']); otherwise it is emitted as a counter. Note this is the exported type, not the PostgreSQL semantics — the db_stats and pg_stat_statements groups use gauges: ['*'] / explicit gauge lists, so their cumulative columns (e.g. xact_commit, exec_time_total) are exported as gauges even though they only ever increase. Cumulative columns in the pg_stat_database family are also not _total-suffixed.

pgwatch_<metric-group>_<column>

Examples (Type = the exporter's emitted Prometheus type):
pgwatch_db_stats_xact_commit # Gauge (transactions committed; db_stats uses gauges: ['*'])
pgwatch_db_stats_numbackends # Gauge (current backends)
pgwatch_pg_stat_statements_exec_time_total # Gauge (total exec time, ms; listed in pg_stat_statements gauges)

Labels​

The cluster label is cluster (set from custom_tags.cluster). cluster_name is only the Grafana template variable; dashboard filters select with cluster="$cluster_name".

pgwatch_<metric-group>_<column>{
cluster="production",
node_name="primary",
datname="myapp",
schemaname="public",
relname="users"
}

Storage requirements​

Calculation​

Storage = metrics_per_second × bytes_per_sample × retention_seconds

Typical values:
- metrics_per_second: 50-200 per database
- bytes_per_sample: 3-5 bytes (VictoriaMetrics compressed)
- retention: 1,209,600 seconds (14 days)

Example: 5 databases, 14-day retention
= 5 × 100 × 1.5 × 1,209,600
= 907,200,000 bytes ≈ 907 MB (≈ 865 MiB)

Scaling factors​

FactorImpact
More databasesLinear increase
More tables/indexesSublinear (only active tracked)
Longer retentionLinear increase
Shorter scrape intervalLinear increase

High availability​

HA architecture​

┌─────────────────────────────────────────────────────┐
│ Load Balancer │
└────────────────────────┬────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│pgwatch-1│ │pgwatch-2│ │pgwatch-3│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└───────────────┼───────────────┘
│ remote_write
│
┌────────────────────▼────────────────────┐
│ VictoriaMetrics Cluster │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │vmstorage1│ │vmstorage2│ │vmstorage3│ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────┬────────────────────┘
│
┌────▼────┐
│ Grafana │
│ (HA) │
└─────────┘

Failure modes​

Component failureImpactRecovery
Single pgwatchPartial data lossAutomatic failover
Single vmstorageNo data loss (replication)Automatic
All pgwatchCollection stopsManual restart
All vmstorageQuery unavailableRestore from backup
GrafanaUI unavailableLoad balancer failover

Data privacy — metadata only​

PostgresAI monitoring collects only database metadata — no actual data or query parameters are ever accessed.

Collected data types​

Data typeExampleStorage location
Database statisticsConnections, transactions, cache hit ratioPrometheus (VictoriaMetrics)
Normalized queriesselect * from users where id = $1PostgreSQL sink
Wait eventsCPU, IO, Lock, LWLockPrometheus (VictoriaMetrics)
Table statisticsRow count, dead tuples, last vacuumPrometheus (VictoriaMetrics)
Index statisticsSize, scans, tuples readPrometheus (VictoriaMetrics)
Column statisticsFrom pg_statistic for bloat estimatesPrometheus (VictoriaMetrics)

NOT collected​

  • Actual table data (row contents)
  • Query parameter values ($1, $2 remain as placeholders)
  • Application secrets or credentials
  • Connection passwords

Metric definitions​

Review exactly what is collected:

Verify monitoring database role and its permissions​

# See exact SQL for creating monitoring role
npx postgresai@latest prepare-db --print-sql

Security architecture​

Network​

┌─────────────────────────────────────────────────────┐
│ DMZ / Public │
│ │
│ ┌─────────┐ │
│ │ Grafana │ ← HTTPS (443) │
│ └────┬────┘ │
└─────────────────────────┼───────────────────────────┘
│
┌─────────────────────────┼───────────────────────────┐
│ Internal Network │
│ │ │
│ ┌──────────────────────▼─────────────────────────┐ │
│ │ VictoriaMetrics (sink-prometheus) │ │
│ │ (port 9090 internal, host 59090) │ │
│ └──────────────────────┬─────────────────────────┘ │
│ scrapes pgwatch-prometheus:9091/pgwatch │
│ ┌──────────────────────▼─────────────────────────┐ │
│ │ pgwatch (pgwatch-postgres, pgwatch-prometheus)│ │
│ │ metrics scraped on pgwatch-prometheus:9091 │ │
│ │ (web/health ports 8080/8089 are internal) │ │
│ └──────────────────────┬─────────────────────────┘ │
│ │ │
└─────────────────────────┬───────────────────────────┘
│
┌─────────────────────────┼───────────────────────────┐
│ Database Network │
│ │ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │PostgreSQL│ │PostgreSQL│ │PostgreSQL│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────┘

Credentials​

ComponentCredential typeStorage
PostgreSQLPasswordEnvironment variable / secret
VictoriaMetricsBasic auth; admin-endpoint keys (0.17+).env, passed to the container as env vars; its entrypoint writes them to files for VictoriaMetrics
GrafanaOAuth/LDAPDatabase

Performance characteristics​

Collection overhead​

Frequencies below are the full preset intervals from config/pgwatch-prometheus/metrics.yml (see the per-metric note above):

Metric typeQuery costFrequency
pg_stat_database (db_stats)Low30s
pg_stat_statementsMedium30s
pg_stat_all_tables / table_statsMedium30s
Bloat estimation (pg_table_bloat, pg_btree_bloat)High7200s (2h)

Query performance​

Query typeTypical latency
Instant query10-100ms
Range query (1h)50-200ms
Range query (24h)200-500ms
Range query (7d)500ms-2s

Resource usage​

ComponentCPUMemoryDisk I/O
pgwatchLow256 MiBMinimal
VictoriaMetricsMedium2 GiB+Medium
GrafanaLow512 MiBLow