Skip to content

api: make readiness reflect database availability without restarting on database outages #727

Open

Description

@Baylem

After a database outage, the API continues to report healthy readiness while protected requests fail session lookup. Kubernetes can keep routing traffic to an unusable API.

Priority: High. Evidence: static inspection of upstream 04e41075cafc4c1a99bcb3723b0a8745ee810957 (2026-10-16). The failure scenarios below have not been reproduced in a running cluster.

Evidence and scenario

Start the API with PostgreSQL, interrupt database connectivity, and compare readiness with authenticated API requests. Existing game pods and their Kubernetes controllers are a separate availability domain; this report concerns the panel API.

Recommended fix

Add a separate bounded /readyz check. A minimal connectivity check in the existing router is:

r.Get("/readyz", func(w http.ResponseWriter, req *http.Request) {
    ctx, cancel := context.WithTimeout(req.Context(), 2*time.Second)
    defer cancel()
    if err := store.DB.PingContext(ctx); err != nil {
        http.Error(w, "not ready", http.StatusServiceUnavailable)
        return
    }
    w.WriteHeader(http.StatusOK)
})

Keep process liveness on /healthz and move the readiness probe to /readyz with a probe timeout longer than the DB-check deadline. Do not restart otherwise healthy API processes during an external database outage. Consider a bounded health-check cache or dedicated connection budget if probe traffic competes with the single SQLite connection.

Distinguish transient store failures from invalid sessions internally and return a generic service-unavailable response where appropriate, without exposing credentials, database addresses, or account existence. Preserve fail-closed authorization. Export dependency availability and recovery signals.

Acceptance criteria

  • A database outage makes readiness fail within a documented bounded interval while liveness remains healthy.
  • Recovery restores readiness and authenticated access without requiring an API restart.
  • A slow connection attempt cannot leave health handlers or probe work accumulating.
  • Invalid sessions remain unauthorized; DB outages do not grant access or reveal sensitive error details.
  • CI exercises the PostgreSQL outage/recovery path and the normal SQLite path.

Related: #707 adds PostgreSQL-backed API tests and provides a fixture for this failure-mode coverage.

Activity

  1. ValgulNecron commented on Oct 20, 2026

    @ValgulNecron
    Collaborator

    Thanks, partly confirmed on master: both probes hit /healthz, which never touches the store (api/cmd/main.go:316-318, charts/gameplane/templates/api.yaml:501-502), so readiness doesn't reflect a database outage. Liveness already doesn't consult the database, so the "don't restart on DB outage" part already holds today. During an outage, session-store errors become 401 (api/internal/auth/sessions.go:135-137), which looks like a logout to users.

    Go ahead with a PR:

    • Add /readyz with a bounded PingContext and point the readiness probe at it; keep liveness on /healthz. On SQLite the pool is one connection (api/internal/db/db.go:66), so keep the ping cheap and time-bounded.
    • Return 503 instead of 401 for transient session-store errors in Authenticate, with no detail in the response body. Access is still denied, but clients can tell an outage from a real logout.
    • Keep /login and unauthenticated responses generic, as the login-privacy rule requires.
  2. Baylem commented on Oct 25, 2026

    @Baylem
    MemberAuthor

    Implemented in #758: bounded database readiness is separate from process liveness, transient session-store failures return a generic 503, and invalid sessions remain unauthorized. The follow-up blocks public /readyz at dashboard nginx. CI covers database outage and recovery without restarting the API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions