Documentationpgraft Documentation
Monitoring and Observability
Monitoring Fundamentals
- Cluster health: Track leader election, node connectivity, and quorum status.
- Replication lag: Monitor Raft log replication and corrective actions.
- Performance metrics: Heartbeats, elections, message throughput, worker uptime.
- Resource usage: Disk availability for WAL/logs, memory pressure, snapshot retention.
- Operational events: Node additions/removals, failovers, configuration changes.
Status Views and Health Functions
Cluster Overview
Primary function for monitoring leadership, quorum, and message counters from any node.
Cluster status snapshot
-- Comprehensive cluster status
SELECT * FROM pgraft_get_cluster_status();
-- Columns include:
-- node_id, current_term, leader_id, state, num_nodes,
-- messages_processed, heartbeats_sent, elections_triggeredHealth indicators: All nodes should agree on
leader_id and current_term, only one node reports state = 'leader', and elections_triggered should rarely increase.Node & Worker Visibility
Registered members
-- View cluster membership
SELECT * FROM pgraft_get_nodes();
-- During partitions use Raft source of truth
SELECT pgraft_get_nodes_from_raft();Leader detection
SELECT pgraft_get_leader() AS leader_id,
pgraft_is_leader() AS am_i_leader,
pgraft_get_term() AS current_term;Worker status
SELECT * FROM pgraft_worker_status;
-- Columns: state, last_tick, uptime_secondsLog Replication Monitoring
Replication Status
Track follower lag and match indexes to keep quorum healthy under load.
Follower replication health
SELECT node_id,
match_index,
commit_index,
lag_entries,
state
FROM pgraft_log_get_replication_status();Alert guidance:
lag_entries > 100 (warning), > 1000 (critical), orstate = 'stalled' requires immediate remediation.Log & Snapshot Metrics
Log volume
SELECT first_index,
last_index,
commit_index,
applied_index,
total_entries,
disk_usage_mb
FROM pgraft_log_get_stats();Growth analysis
SELECT total_entries,
disk_usage_mb,
(disk_usage_mb::float / NULLIF(total_entries, 0)) * 1024 AS avg_entry_kb
FROM pgraft_log_get_stats();Force follower resync
-- Execute on lagging follower
SELECT pgraft_log_sync_with_leader();Key-Value Store Monitoring
Usage & Capacity
KV statistics
SELECT total_keys,
total_size_bytes,
oldest_key_age,
newest_key_age,
avg_value_size
FROM pgraft_kv_get_stats();KV status
SELECT keys_count,
values_size_mb,
compaction_count,
last_compact_at
FROM pgraft_kv_status;Health & Trend Checks
End-to-end KV health
DO $$
DECLARE
test_key TEXT := 'health_check_' || extract(epoch FROM now());
test_value TEXT := 'test_value_' || md5(random()::text);
roundtrip TEXT;
BEGIN
PERFORM pgraft_kv_put(test_key, test_value);
SELECT pgraft_kv_get(test_key) INTO roundtrip;
IF roundtrip != test_value THEN
RAISE EXCEPTION 'KV health check failed';
END IF;
PERFORM pgraft_kv_delete(test_key);
END;
$$;30-day growth trend
WITH daily_growth AS (
SELECT date_trunc('day', created_at) AS day,
COUNT(*) AS keys_added,
SUM(pg_column_size(value)) AS bytes_added
FROM pgraft.kv
WHERE created_at > now() - interval '30 days'
GROUP BY 1
)
SELECT day,
keys_added,
bytes_added / 1024 / 1024 AS mb_added,
SUM(keys_added) OVER (ORDER BY day) AS cumulative_keys,
SUM(bytes_added) OVER (ORDER BY day) / 1024 / 1024 AS cumulative_mb
FROM daily_growth
ORDER BY day DESC;Performance & Election Metrics
Message Throughput
Messages per heartbeat
SELECT node_id,
messages_processed,
heartbeats_sent,
messages_processed / GREATEST(heartbeats_sent, 1) AS msg_per_heartbeat
FROM pgraft_get_cluster_status();Historical rate (requires sampling)
WITH current AS (
SELECT *
FROM pgraft_metrics_history
ORDER BY recorded_at DESC
LIMIT 1
),
previous AS (
SELECT *
FROM pgraft_metrics_history
WHERE recorded_at <= now() - interval '1 hour'
ORDER BY recorded_at DESC
LIMIT 1
)
SELECT c.node_id,
(c.messages_processed - p.messages_processed)
/ EXTRACT(EPOCH FROM (c.recorded_at - p.recorded_at)) AS messages_per_second,
(c.heartbeats_sent - p.heartbeats_sent)
/ EXTRACT(EPOCH FROM (c.recorded_at - p.recorded_at)) AS heartbeats_per_second
FROM current c
JOIN previous p USING (node_id);Election Stability
Election rate
SELECT node_id,
state,
elections_triggered,
current_term,
elections_triggered::float / GREATEST(current_term, 1) AS elections_per_term
FROM pgraft_get_cluster_status();Investigate immediately when:
elections_per_term > 2,current_term spikes unexpectedly, or multiple nodes report state = 'candidate'.Snapshot hygiene
# Inspect snapshot directory for growth and cadence
ls -lh /var/lib/postgresql/pgraft/snapshots/ | tail -10
# Check configured snapshot threshold
psql -c "SHOW pgraft.snapshot_threshold;"System Monitoring Views
etcd-Compatible Views
Endpoints & health
SELECT * FROM pgraft.member_list;
SELECT * FROM pgraft.endpoint_health;
SELECT * FROM pgraft.cluster_health;
SELECT * FROM pgraft.cluster_info;Cluster State Summary
Create summary view
CREATE OR REPLACE VIEW pgraft_health_summary AS
SELECT CURRENT_TIMESTAMP AS checked_at,
COUNT(*) AS total_nodes,
COUNT(*) FILTER (WHERE state = 'leader') AS num_leaders,
COUNT(*) FILTER (WHERE state = 'follower') AS num_followers,
COUNT(*) FILTER (WHERE state = 'candidate') AS num_candidates,
MAX(term) AS current_term,
BOOL_AND(leader) AS has_leader
FROM pgraft_cluster_state;Query summary
SELECT * FROM pgraft_health_summary;Alerting & Automation
Database Health Check
Comprehensive PL/pgSQL check
CREATE OR REPLACE FUNCTION pgraft_health_check()
RETURNS TABLE(check_name TEXT, status TEXT, message TEXT, severity TEXT) AS $$
DECLARE
v_status pgraft_get_cluster_status%ROWTYPE;
BEGIN
SELECT * INTO v_status FROM pgraft_get_cluster_status() LIMIT 1;
RETURN QUERY
SELECT 'leader_election',
CASE
WHEN (SELECT COUNT(*) FROM pgraft_cluster_state WHERE state = 'leader') = 0 THEN 'CRITICAL'
WHEN (SELECT COUNT(*) FROM pgraft_cluster_state WHERE state = 'leader') > 1 THEN 'CRITICAL'
ELSE 'OK'
END,
format('Leader ID %s', v_status.leader_id),
'info';
RETURN QUERY
SELECT 'quorum',
CASE WHEN v_status.num_nodes < 3 THEN 'WARNING' ELSE 'OK' END,
format('%s nodes in cluster', v_status.num_nodes),
CASE WHEN v_status.num_nodes < 3 THEN 'warning' ELSE 'info' END;
RETURN QUERY
SELECT 'election_stability',
CASE WHEN v_status.elections_triggered::float / GREATEST(v_status.current_term, 1) > 2 THEN 'WARNING' ELSE 'OK' END,
format('%s elections across %s terms', v_status.elections_triggered, v_status.current_term),
CASE WHEN v_status.elections_triggered::float / GREATEST(v_status.current_term, 1) > 2 THEN 'warning' ELSE 'info' END;
RETURN QUERY
SELECT 'replication_lag',
CASE WHEN EXISTS (SELECT 1 FROM pgraft_log_get_replication_status() WHERE lag_entries > 100) THEN 'WARNING' ELSE 'OK' END,
CASE WHEN EXISTS (SELECT 1 FROM pgraft_log_get_replication_status() WHERE lag_entries > 100) THEN 'Followers lagging > 100 entries' ELSE 'All followers up to date' END,
CASE WHEN EXISTS (SELECT 1 FROM pgraft_log_get_replication_status() WHERE lag_entries > 100) THEN 'warning' ELSE 'info' END;
END;
$$ LANGUAGE plpgsql;Execute health check
SELECT * FROM pgraft_health_check();Automation Scripts
Prometheus exporter
#!/usr/bin/env bash
cat <<'EOF'
# HELP pgraft_cluster_nodes Total number of nodes in cluster
# TYPE pgraft_cluster_nodes gauge
pgraft_cluster_nodes $(psql -t -c "SELECT num_nodes FROM pgraft_get_cluster_status();")
# HELP pgraft_is_leader Whether this node is the leader (1=yes, 0=no)
# TYPE pgraft_is_leader gauge
pgraft_is_leader $(psql -t -c "SELECT CASE WHEN pgraft_is_leader() THEN 1 ELSE 0 END;")
# HELP pgraft_current_term Current Raft election term
# TYPE pgraft_current_term counter
pgraft_current_term $(psql -t -c "SELECT pgraft_get_term();")
# HELP pgraft_messages_processed Total Raft messages processed
# TYPE pgraft_messages_processed counter
pgraft_messages_processed $(psql -t -c "SELECT messages_processed FROM pgraft_get_cluster_status();")
# HELP pgraft_elections_triggered Total elections triggered
# TYPE pgraft_elections_triggered counter
pgraft_elections_triggered $(psql -t -c "SELECT elections_triggered FROM pgraft_get_cluster_status();")
# HELP pgraft_log_entries Total log entries
# TYPE pgraft_log_entries gauge
pgraft_log_entries $(psql -t -c "SELECT total_entries FROM pgraft_log_get_stats();")
# HELP pgraft_log_disk_mb Log disk usage in megabytes
# TYPE pgraft_log_disk_mb gauge
pgraft_log_disk_mb $(psql -t -c "SELECT disk_usage_mb FROM pgraft_log_get_stats();")
# HELP pgraft_kv_keys Total KV store keys
# TYPE pgraft_kv_keys gauge
pgraft_kv_keys $(psql -t -c "SELECT total_keys FROM pgraft_kv_get_stats();")
EOFEmail alert driver
#!/usr/bin/env bash
ALERT_EMAIL="ops@example.com"
LOG_FILE="/var/log/pgraft-alerts.log"
HEALTH_CHECK=$(psql -t -c "
SELECT string_agg(
check_name || ': ' || status || ' - ' || message,
E'\n'
)
FROM pgraft_health_check()
WHERE severity IN ('warning', 'critical');
")
if [[ -n "$HEALTH_CHECK" ]]; then
echo "$(date --iso-8601=seconds): ALERT - $HEALTH_CHECK" >> "$LOG_FILE"
printf '%s
' "$HEALTH_CHECK" | mail -s "pgraft Cluster Alert" "$ALERT_EMAIL"
fi
MAX_LAG=$(psql -t -c "SELECT COALESCE(MAX(lag_entries), 0) FROM pgraft_log_get_replication_status();")
if [[ "$MAX_LAG" -gt 1000 ]]; then
echo "$(date --iso-8601=seconds): ALERT - Replication lag: $MAX_LAG entries" >> "$LOG_FILE"
printf 'Critical replication lag: %s entries
' "$MAX_LAG" | mail -s "pgraft Replication Lag Alert" "$ALERT_EMAIL"
fiDashboards & Visualization
Sample Grafana queries
-- Cluster overview
SELECT num_nodes AS "Total Nodes",
(SELECT COUNT(*) FROM pgraft_cluster_state WHERE state = 'leader') AS "Leaders",
(SELECT COUNT(*) FROM pgraft_cluster_state WHERE state = 'follower') AS "Followers",
current_term AS "Current Term"
FROM pgraft_get_cluster_status();
-- Replication lag panel
SELECT node_id AS "Node",
lag_entries AS "Lag (entries)",
state AS "Status"
FROM pgraft_log_get_replication_status()
ORDER BY lag_entries DESC;
-- KV store growth
SELECT total_keys AS "Total Keys",
total_size_bytes / 1024 / 1024 AS "Size (MB)",
avg_value_size / 1024 AS "Avg Value (KB)"
FROM pgraft_kv_get_stats();Log Analysis & Debugging
PostgreSQL Logs
Tail and filter
sudo tail -f /var/log/postgresql/postgresql-17-main.log | grep -i pgraft
sudo grep -i "pgraft.*error" /var/log/postgresql/postgresql-17-main.log
sudo grep -i "pgraft.*election" /var/log/postgresql/postgresql-17-main.log | tail -20
sudo grep -E "pgraft.*(add_node|remove_node)" /var/log/postgresql/postgresql-17-main.logDebug Mode
Toggle verbose tracing
SELECT pgraft_set_debug(true);
-- Perform operations requiring deep tracing
SELECT pgraft_set_debug(false);Inspect debug output
sudo tail -100 /var/log/postgresql/postgresql-17-main.log | grep "DEBUG.*pgraft"Best Practices Checklist
- Continuous monitoring: Execute health checks every 1–5 minutes via cron or agents.
- Historical retention: Store metrics in a time-series database for capacity planning and regression detection.
- Alert thresholds: Leader absence (critical), replication lag > 100 entries (warning) / > 1000 (critical), rising election rates, node count mismatches.
- Dashboard coverage: Visualize leadership, replication health, log throughput, and KV growth trends.
- Log hygiene: Retain PostgreSQL logs for at least seven days and forward them to centralized logging.
- Capacity planning: Track disk usage for WAL, snapshots, and KV state to avoid emergency pruning.
- Regular rehearsal: Test failover and replica catch-up quarterly to validate tooling and runbooks.
Next Steps
- Troubleshooting Guide – Diagnose and resolve common incidents.
- Configuration Reference – Review every pgraft GUC and tuning option.
- Cluster Management – Node lifecycle, rolling maintenance, and failovers.
- SQL API Reference – Complete catalog of functions and views.