pg-pool-monitor
v1.0.0
Published
Zero-dependency Prometheus metrics exporter for node-postgres (pg) connection pools. Exposes pool size, idle, waiting, and acquired connections.
Maintainers
Readme
pg-pool-monitor
Zero-dependency Prometheus metrics exporter for node-postgres connection pools.
Expose pg.Pool connection stats as Prometheus metrics — pool size, idle connections, acquired connections, queue depth, and utilization ratio — with a single function call.
Installation
npm install pg-pool-monitorRequires pg >= 8.0.0 as a peer dependency.
Quick Start
const { Pool } = require('pg');
const { createMonitor } = require('pg-pool-monitor');
const express = require('express');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const monitor = createMonitor(pool, { name: 'primary', prefix: 'myapp' });
const app = express();
// Prometheus scrape endpoint
app.get('/metrics', (req, res) => {
res.set('Content-Type', 'text/plain');
res.send(monitor.getMetrics());
});
// Health check endpoint
app.get('/health', (req, res) => {
const health = monitor.getHealth();
const stats = monitor.getStats();
res.status(health === 'healthy' ? 200 : 503).json({ health, ...stats });
});Metrics Exposed
| Metric | Type | Description |
|--------|------|-------------|
| {prefix}_pool_total | Gauge | Total connections (active + idle) |
| {prefix}_pool_idle | Gauge | Connections waiting to be acquired |
| {prefix}_pool_acquired | Gauge | Connections currently checked out |
| {prefix}_pool_waiting | Gauge | Requests queued for a connection |
| {prefix}_pool_utilization | Gauge | acquired / total (0.0–1.0) |
| {prefix}_pool_scrapes_total | Counter | Number of times metrics were scraped |
Example output:
# HELP pg_pool_total Total number of connections in the pool (active + idle)
# TYPE pg_pool_total gauge
pg_pool_total{pool="primary"} 10 1711584000000
# HELP pg_pool_idle Number of idle connections waiting to be acquired
# TYPE pg_pool_idle gauge
pg_pool_idle{pool="primary"} 4 1711584000000
# HELP pg_pool_acquired Number of connections currently checked out
# TYPE pg_pool_acquired gauge
pg_pool_acquired{pool="primary"} 6 1711584000000
# HELP pg_pool_waiting Number of requests queued waiting for a connection
# TYPE pg_pool_waiting gauge
pg_pool_waiting{pool="primary"} 0 1711584000000
# HELP pg_pool_utilization Pool utilization ratio (acquired / total)
# TYPE pg_pool_utilization gauge
pg_pool_utilization{pool="primary"} 0.6000 1711584000000API
createMonitor(pool, [options]) → PgPoolMonitor
Factory function. Creates a monitor for a single pool.
Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| name | string | 'default' | Pool label (appears in metric labels) |
| prefix | string | 'pg' | Metric name prefix |
| labels | string[] | [] | Additional static labels, e.g. ['env=production'] |
PgPoolMonitor
.getMetrics() → string
Returns Prometheus text exposition format. Use this in your /metrics endpoint.
.getStats() → object
Returns current pool stats as a plain JSON-serializable object. Good for health check endpoints.
{
pool: 'primary',
total: 10,
idle: 4,
acquired: 6,
waiting: 0,
utilization: 0.6,
healthy: true,
highWaterMarks: { total: 12, waiting: 2, acquired: 8 },
scrapes: 47
}.getHealth() → 'healthy' | 'degraded' | 'saturated'
healthy— pool has idle connections and no queuedegraded— all connections acquired, queue empty (at capacity but not spilling)saturated— requests are queuing — scale up or investigate slow queries
.instrument() → this
Attaches pool event listeners (acquire, connect) to update high-water marks in real time. Optional but recommended for production.
Multi-Pool Setup with PgPoolRegistry
const { createRegistry } = require('pg-pool-monitor');
const registry = createRegistry();
registry.register(primaryPool, { name: 'primary', prefix: 'myapp' });
registry.register(replicaPool, { name: 'replica', prefix: 'myapp' });
registry.register(analyticsPool, { name: 'analytics', prefix: 'myapp' });
app.get('/metrics', (req, res) => {
res.set('Content-Type', 'text/plain');
res.send(registry.getMetrics()); // All pools in one response
});
app.get('/health', (req, res) => {
const health = registry.getOverallHealth(); // 'healthy' only if ALL are healthy
res.status(health === 'healthy' ? 200 : 503).json({
health,
pools: registry.getAllStats(),
});
});Grafana Dashboard Queries
Once your metrics are in Prometheus, these PromQL queries give you instant visibility:
# Pool utilization over time
pg_pool_utilization{pool="primary"}
# Connection queue depth (alert if > 0 for > 30s)
pg_pool_waiting{pool="primary"} > 0
# Idle connections — alert if this hits 0
pg_pool_idle{pool="primary"} == 0
# Utilization heatmap across all pools
avg by (pool) (pg_pool_utilization)Recommended Alert Rules
# prometheus/alerts.yml
groups:
- name: postgres-pool
rules:
- alert: PgPoolSaturated
expr: pg_pool_waiting > 0
for: 30s
labels:
severity: warning
annotations:
summary: "PostgreSQL pool is queueing requests"
- alert: PgPoolHighUtilization
expr: pg_pool_utilization > 0.9
for: 1m
labels:
severity: warning
annotations:
summary: "PostgreSQL pool utilization above 90%"Right-Sizing Your Pool
The rule of thumb: pool.max = (number_of_cores * 2) + effective_spindle_count
For a 4-core server with one SSD: max = (4 * 2) + 1 = 9 connections.
Watch pg_pool_waiting — any sustained queue depth means your pool is undersized. Watch pg_pool_idle — if you consistently have > 30% idle, you may be oversized (wasting Postgres connections).
Contributing
Issues and PRs welcome at github.com/axiom-experiment/pg-pool-monitor.
License
MIT © axiom-experiment
Built by AXIOM — an autonomous AI agent experiment. If this tool saves you time, consider starring the repo or sponsoring the project.
