IO Guardian - Database Availability System
Purpose
The IO Guardian system ensures that services across the infrastructure are aware of the availability of centralized databases hosted on the Database Coordinator. It provides graceful startup and shutdown coordination between the database host and dependent services on other servers.
Entry Point
- Main file:
modules/nixos/server/database/guardian.nix
Architecture / Services / Scope
The system consists of two components:
-
Guardian Server (runs on client servers)
- WebSocket server that listens for commands from the coordinator
- Secures connections with a pre-shared key (PSK) from the
DB_GUARDIAN_PSKsecret - Executes drain/undrain commands by controlling
db-databases.target
-
Guardian Client (runs on the Database Coordinator)
- WebSocket client that connects to all guardian servers
- Sends
undraincommand after databases are online (start dependent services) - Sends
draincommand before database shutdown (stop dependent services)
How It Works
System Startup
- Client servers boot and run
wait-for-db-databases.service - This service waits (with retries) until PostgreSQL and Redis on the Database Coordinator are reachable
- Once databases are confirmed available, the service completes
- The
db-databases.targetis now ready to be activated - When the Database Coordinator’s
db-database-coordinator.servicestarts, it sendsundrainto all clients - Clients start
db-databases.target, which starts all dependent services
Database Shutdown (Graceful Drain)
- When
db-database-coordinator.servicestops (before databases stop) - It connects to all guardian servers via WebSocket
- Sends
draincommand to each server - Guardian servers stop
db-databases.target - Dependent services stop gracefully before databases go down
Database Startup (Undrain)
- When databases come online on the Database Coordinator
db-database-coordinator.servicestarts- It sends
undraincommand to all guardian servers - Guardian servers start
db-databases.target - All dependent services start
Systemd Units
On Client Servers
| Unit | Type | Description |
|---|---|---|
db-guardian.service | simple | WebSocket server for receiving commands |
db-databases.target | target | Represents “databases are online” |
wait-for-db-databases.service | oneshot | Waits for databases at boot (runs once) |
On the Database Coordinator
| Unit | Type | Description |
|---|---|---|
db-database-coordinator.service | oneshot | Sends undrain on start, drain on stop |
Protocol Reference
The guardian uses a simple JSON-based WebSocket protocol:
Authentication
Client sends:
{ "type": "auth", "key": "<psk>" }
Server responds:
{ "type": "auth", "status": "ok", "message": "Authentication successful" }
Commands
Coordinator sends one of the supported actions (drain, undrain, or ping):
{ "type": "command", "action": "drain" }
Server responds:
{ "type": "response", "action": "<action>", "status": "ok", "message": "..." }
Secrets
Communication is secured using a Pre-Shared Key (PSK) that must be at least 32 characters. All WebSocket connections must authenticate with this key before commands are accepted.
Declared secrets
| Secret key | Owner | Group | Restart unit | Purpose |
|---|---|---|---|---|
DB_GUARDIAN_PSK | root | root | db-guardian.service | Authenticate DB Guardian WebSocket API |
Operational Notes / Assumptions
Configuration
Port: The guardian WebSocket server listens on port 9876 by default. This port is automatically opened to local subnets on servers with database dependencies.
Dependent Services: Dependent Services will be automatically populated with service names where there is a systemd.service.<name> defined from the names in server.database.postgres or server.database.redis.
To manually add a service bind to the database availability target, add it to the server.database.dependentServices option.
Services listed here will start only when db-databases.target is active, stop when it stops, and restart when the target restarts.
Troubleshooting
Checking Guardian Status
On client servers:
systemctl status db-guardian.service
systemctl status db-databases.target
systemctl status wait-for-db-databases.service
journalctl -u db-guardian.service -f
On the Database Coordinator:
systemctl status db-database-coordinator.service
journalctl -u db-database-coordinator.service
Manual Commands
To manually start/stop dependent services on a client:
systemctl start db-databases.target
systemctl stop db-databases.target
Common Issues
Guardian server won’t start:
- Check that
DB_GUARDIAN_PSKsecret is properly configured - Verify the SOPS decryption is working and the secret is non-empty:
test -s /run/secrets/DB_GUARDIAN_PSK
Services not starting after boot:
- Check wait service logs:
journalctl -u wait-for-db-databases.service - Verify network connectivity to the Database Coordinator on ports 5432 (Postgres) and 6379 (Redis)
- Ensure the Database Coordinator has sent the undrain command
Authentication failures in logs:
- Ensure the same PSK is deployed to all servers
- Re-encrypt secrets if the key was changed