Operations
This guide covers monitoring and maintaining HoloMUSH in production. For the metric set, health-check endpoints, extensions, and log-location lookups, see the Monitoring reference.
Health Checks
Section titled “Health Checks”Both core and gateway expose health endpoints (see Health-check endpoints).
Core (default port 9100):
curl http://localhost:9100/healthz/readinessGateway (default port 9101):
curl http://localhost:9101/healthz/readinessStatus Command
Section titled “Status Command”Check overall system health:
holomush statusThis queries both core and gateway health endpoints via gRPC.
Prometheus Metrics
Section titled “Prometheus Metrics”HoloMUSH exposes Prometheus metrics at /metrics (see the
metric catalog). Point
your scraper at the core and gateway endpoints:
scrape_configs: - job_name: holomush-core static_configs: - targets: ["localhost:9100"]
- job_name: holomush-gateway static_configs: - targets: ["localhost:9101"]Enable pg_stat_statements
Section titled “Enable pg_stat_statements”HoloMUSH requires pg_trgm and optionally uses pg_stat_statements (see
PostgreSQL extensions).
For query performance monitoring, enable pg_stat_statements:
# postgresql.confshared_preload_libraries = 'pg_stat_statements'pg_stat_statements.track = allRestart PostgreSQL after configuration changes.
Query Performance Monitoring
Section titled “Query Performance Monitoring”The thresholds for the columns below are in Query-performance metrics.
Slowest Queries
Section titled “Slowest Queries”Find queries consuming the most time:
SELECT calls, round(total_exec_time::numeric, 2) as total_ms, round(mean_exec_time::numeric, 2) as mean_ms, round(stddev_exec_time::numeric, 2) as stddev_ms, rows, queryFROM pg_stat_statementsORDER BY total_exec_time DESCLIMIT 10;High I/O Queries
Section titled “High I/O Queries”Identify queries causing disk reads:
SELECT calls, shared_blks_hit + shared_blks_read as total_blocks, round(100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0), 2) as cache_hit_pct, queryFROM pg_stat_statementsWHERE shared_blks_hit + shared_blks_read > 0ORDER BY shared_blks_read DESCLIMIT 10;Most Frequent Queries
Section titled “Most Frequent Queries”Find queries called most often:
SELECT calls, round(total_exec_time::numeric, 2) as total_ms, round(mean_exec_time::numeric, 2) as mean_ms, queryFROM pg_stat_statementsORDER BY calls DESCLIMIT 10;Reset Statistics
Section titled “Reset Statistics”Clear accumulated statistics:
SELECT pg_stat_statements_reset();Connection Monitoring
Section titled “Connection Monitoring”Active Connections
Section titled “Active Connections”View current database connections:
SELECT datname, usename, application_name, client_addr, state, query_start, now() - query_start as query_durationFROM pg_stat_activityWHERE datname = 'holomush'ORDER BY query_start;Connection Counts
Section titled “Connection Counts”Connections grouped by state:
SELECT state, count(*)FROM pg_stat_activityWHERE datname = 'holomush'GROUP BY state;JetStream Consumer Health
Section titled “JetStream Consumer Health”HoloMUSH uses an embedded NATS JetStream server for real-time event delivery.
Check consumer state via the NATS monitoring port (default: disabled; set
event_bus.monitor_port in config to enable):
# List stream info (requires nats CLI and monitoring port enabled)nats stream info EVENTS --server nats://localhost:<monitor_port>
# Check audit projection consumer lagnats consumer info EVENTS host_audit_projectionThe audit_projection_lag_seconds Prometheus metric alerts at > 5s lag. The
embedded server does not open a network port by default (DontListen: true).
Index Health
Section titled “Index Health”Unused Indexes
Section titled “Unused Indexes”Find indexes that may be candidates for removal:
SELECT schemaname, relname as table_name, indexrelname as index_name, idx_scan, pg_size_pretty(pg_relation_size(indexrelid)) as index_sizeFROM pg_stat_user_indexesWHERE idx_scan = 0ORDER BY pg_relation_size(indexrelid) DESC;Index Usage
Section titled “Index Usage”Verify indexes are being used:
SELECT relname as table_name, round(100.0 * idx_scan / nullif(seq_scan + idx_scan, 0), 2) as index_usage_pct, seq_scan, idx_scanFROM pg_stat_user_tablesWHERE seq_scan + idx_scan > 0ORDER BY seq_scan DESC;Table Statistics
Section titled “Table Statistics”Table Sizes
Section titled “Table Sizes”Monitor table and index sizes:
SELECT relname as table_name, pg_size_pretty(pg_total_relation_size(relid)) as total_size, pg_size_pretty(pg_relation_size(relid)) as table_size, pg_size_pretty(pg_indexes_size(relid)) as index_size, n_live_tup as live_rows, n_dead_tup as dead_rowsFROM pg_stat_user_tablesORDER BY pg_total_relation_size(relid) DESC;Tables Needing Vacuum
Section titled “Tables Needing Vacuum”Identify tables with dead tuples:
SELECT relname, n_dead_tup, last_vacuum, last_autovacuumFROM pg_stat_user_tablesWHERE n_dead_tup > 1000ORDER BY n_dead_tup DESC;Backup and Recovery
Section titled “Backup and Recovery”PostgreSQL Backups
Section titled “PostgreSQL Backups”Use pg_dump for logical backups:
pg_dump -U holomush -h localhost holomush > backup.sqlFor production, consider:
- Continuous archiving with WAL shipping
- Point-in-time recovery (PITR)
- Streaming replication for high availability
Restore
Section titled “Restore”Restore from a logical backup:
psql -U holomush -h localhost holomush < backup.sqlLog Management
Section titled “Log Management”Logs go to stdout per component (see Log locations).
Log Aggregation
Section titled “Log Aggregation”For production, aggregate logs with:
- Loki - Grafana’s log aggregation
- Elasticsearch - Full-text search
- CloudWatch - AWS managed logging
Docker Logging
Section titled “Docker Logging”View container logs:
docker compose logs -f coredocker compose logs -f gatewayTroubleshooting
Section titled “Troubleshooting”Connection Issues
Section titled “Connection Issues”Symptom: Gateway cannot connect to core.
Check:
- Core is running:
holomush status - Network connectivity:
nc -zv localhost 9000 - Firewall rules allow traffic
- TLS certificates are valid
Database Connection Failed
Section titled “Database Connection Failed”Symptom: Core fails with database errors.
Check:
- PostgreSQL is running:
pg_isready -h localhost DATABASE_URLis correct- User has permissions:
\duin psql - Database exists:
\lin psql
High Memory Usage
Section titled “High Memory Usage”Possible causes:
- Too many active sessions
- Large query result sets
- Plugin memory leaks
Investigate:
# Check active sessionscurl http://localhost:9100/metrics | grep sessions_active
# Check PostgreSQL connectionspsql -c "SELECT count(*) FROM pg_stat_activity WHERE datname='holomush';"Slow Queries
Section titled “Slow Queries”Investigate:
- Enable
pg_stat_statements(see above) - Check for missing indexes
- Analyze query plans with
EXPLAIN ANALYZE
-- Replace <ulid> with an actual location ULID from your databaseEXPLAIN ANALYZE SELECT * FROM events WHERE stream = 'location:<ulid>' ORDER BY id DESC LIMIT 100;Alias Database-Cache Inconsistency
Section titled “Alias Database-Cache Inconsistency”Symptom: Log message with severity=critical containing
“alias rollback failed - database-cache inconsistency”.
Monitoring: Alert on the holomush_alias_rollback_failures_total metric.
# Prometheus alert rule- alert: AliasRollbackFailure expr: increase(holomush_alias_rollback_failures_total[5m]) > 0 for: 0m labels: severity: critical annotations: summary: "Alias database-cache inconsistency detected" description: "A rollback failure has left aliases in an inconsistent state."Cause: During alias creation, the database write succeeded but the cache update failed (e.g., circular reference detected). The subsequent rollback (database delete) also failed, leaving the alias in the database but not in the cache.
Impact:
- The alias exists in the database but is not loaded into cache
- On server restart, the alias will be loaded and may cause issues
- The alias may conflict with other aliases or commands
Recovery:
-
Identify the problematic alias from the log message (check
aliasandplayer_idfields) -
For player aliases, delete from database:
DELETE FROM player_aliasesWHERE player_id = '<player_id>' AND alias = '<alias_name>'; -
For system aliases, delete from database:
DELETE FROM system_aliases WHERE alias = '<alias_name>'; -
Verify cleanup:
-- Check player aliasesSELECT * FROM player_aliases WHERE alias = '<alias_name>';-- Check system aliasesSELECT * FROM system_aliases WHERE alias = '<alias_name>'; -
No restart required - the cache does not contain the alias, so removing it from the database prevents it from being loaded on the next restart.
Prevention: This failure typically occurs when database connectivity is degraded. Monitor database connection health and ensure proper connection pooling configuration.
Next Steps
Section titled “Next Steps”- Monitoring reference - Metrics, endpoints, extensions, log locations
- Configuration - Adjust server settings
- Installation - Deployment options