Documentation
Observability, CI/CD, DR & Auto-scaling
Prometheus/Grafana, CI/CD, disaster recovery, backups, and auto-scaling.
11. Observability (Metrics, Logs, Tracing)
Metrics (Prometheus + Grafana)
Stackhouse exposes /v1/stats → Prometheus scrapes every 15s.
Key metrics: stackhouse_requests_total{method,path,status}, stackhouse_request_duration_seconds (histogram), stackhouse_active_connections, stackhouse_db_query_duration_seconds, stackhouse_cache_hit_rate, stackhouse_rate_limited_requests_total, stackhouse_auth_failures_total, container_{cpu,memory}_usage (from cAdvisor).
Alerting rules (Alertmanager → PagerDuty):
- p99 latency >500ms for 5 minutes
- Error rate >5% for 2 minutes
- DB connection pool utilization >80%
- Pod restart count >3 in 10 minutes
- Disk usage >80%
Logs (Structured JSON → Log Aggregator)
Format: {"ts":"2026-03-24T10:00:00Z","level":"info","method":"POST","path":"/v1/push/users","status":201,"latency_ms":12,"user_id":1234}
Log levels: ERROR → WARN → INFO → DEBUG (configurable). Separate log files:
stackhouse.log(all application logs)error.log(ERROR level only)query.log(slow queries >100ms)
Shipping: Fluentd/Vector → Elasticsearch/Loki/Datadog.
Tracing (OpenTelemetry → Jaeger/Tempo)
Every request gets an X-Request-ID header. Traces: HTTP request → DB query → Cache → Response. Useful for finding bottlenecks and debugging slow requests.
Provider Observability Options
| Feature | AWS | GCP | Hetzner | Hostinger | Heroku |
|---|---|---|---|---|---|
| Native metrics | CloudWatch | Cloud Monitoring | ❌ self | ❌ self | Basic |
| Log aggregation | CloudWatch Logs | Cloud Logging | Self (ELK) | Self | Logplex |
| APM/Tracing | X-Ray | Cloud Trace | Self (Jaeger) | Self | ❌ |
| Cost/month | ~$50-200 | ~$30-100 | ~$20 self-hosted | ~$20 | $0-75 |
| Alerting | CloudWatch Alerts | Cloud Alerting | Grafana | Grafana | Papertrail |
12. CI/CD Pipeline
Rollback trigger: error rate >10% OR p99 latency >2s after deploy → kubectl rollout undo deployment/stackhouse
Docker Build — Multi-stage for Minimal Image
# Build stage (cargo build)
FROM rust:1.82 AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN cargo fetch
COPY src/ ./src/
RUN cargo build --release --bin stackhouse
# Runtime stage (minimal image)
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/stackhouse /usr/local/bin/stackhouse
EXPOSE 3000
USER 1000 # Non-root
ENTRYPOINT ["stackhouse"]
# Final image size: ~50MB vs 2GB+ for full Rust image13. Disaster Recovery & Backups
Backup Strategy
RPO = Recovery Point Objective (max data loss acceptable). RTO = Recovery Time Objective (max downtime acceptable).
Target: RPO <5 minutes, RTO <30 minutes.
Backup types:
Current-state note (2026-08-18)
Item 1 describes standard PostgreSQL WAL archiving as a scaling target — real
for Postgres in general, but not something Stackhouse's own code sets up today.
The original items 2–3 here described a "Stackhouse LSM Checkpoints"/SSTable
backup pipeline that doesn't exist — Stackhouse has no custom storage engine.
They've been replaced below with what actually exists in stackhouse/src/storage/backups.rs.
- PostgreSQL WAL Archiving (continuous, scaling target) — WAL segments shipped to object storage; would enable PITR to any second within the retention window; not wired into Stackhouse's own code today (external Postgres tooling would provide this)
- Application-level logical backups (
stackhouse_backupstable +/v1/admin/backups/*, real, implemented today) —BackupManagerrunsSELECT * FROM "<table>"per table and writes the result to a backup file; restore and delete are exposed viaPOST /v1/admin/backups/:id/restoreandDELETE /v1/admin/backups/:id. This is a logical snapshot mechanism, not WAL-based — no continuous point-in-time granularity between snapshots. PitrService(stackhouse/src/storage/backups/pitr.rs, real WAL-replay logic, but not wired to any HTTP route) — implements real point-in-time restore by replaying WAL from a logical replication slot to a target timestamp. The logic exists and works when called directly, but there is no/v1/admin/*(or any other) endpoint that invokes it today, so it isn't reachable in a running server.- Object Storage Versioning — all file uploads versioned; 30-day version retention; cross-region replication for critical buckets (scaling target, not verified against current object-storage code)
Recovery scenarios:
| Scenario | Response | RTO / RPO |
|---|---|---|
| Pod crashes | Kubernetes restarts pod (<30s), no data loss; health check removes bad pod immediately | <30s |
| DB primary fails | Automatic failover to read replica; PgBouncer reconnects to new primary; brief write unavailability | ~60s RTO |
| Region outage | DNS failover to backup region (manual or Route 53) | RTO 15-30 min · RPO up to 5 min (last WAL archive) |
| Data corruption | PITR restore to pre-corruption timestamp | RTO 30-60 min |
14. Auto-scaling Strategy
Horizontal Pod Autoscaler Logic
Metrics collected every 15 seconds: CPU utilization per pod, memory utilization per pod, requests per second (custom metric via Prometheus adapter), WebSocket connection count.
Scale up (any of)
CPU >60% average across pods for 2 minutes Memory >70% average for 2 minutes RPS >800 per pod for 1 minute Pending requests queue >100
Scale down (all of)
CPU <30% average for 10 minutes (cool-down period) Memory <40% for 10 minutes RPS <200 per pod for 10 minutes
Pod count limits:
| Pool | Min | Max |
|---|---|---|
| API pods | 3 | 30 |
| AI pods | 1 | 10 |
| Realtime pods | 2 | 15 |
| DB pods | 1 | 1 (vertical scale instead) |
Scale simulation at 100k users:
| Users | Concurrent | RPS | API Pods | DB Conns | Redis |
|---|---|---|---|---|---|
| 10k | 1,000 | 100 | 3 | 15 | 512MB |
| 50k | 5,000 | 500 | 7 | 30 | 2GB |
| 100k | 10,000 | 1,000 | 13 | 50 | 4GB |
| 200k | 20,000 | 2,000 | 26 | 50* | 8GB |
* PgBouncer limit
Cluster Autoscaler (Node-level)
Node pool config: min nodes 3 (HA, spread across 3 zones) · max nodes 10 (cost ceiling) · node type e2-standard-4 (4vCPU, 16GB) on GCP