Skip to content

Readiness stays 500 for 3h after a transient DB error during boot (TrackerScriptCache refresh_all never retried) #6647

Open

Description

@rosnovsky

Past Issues Searched

Searched for TrackerScriptCache / tracker_script_cache / readiness; nothing matching.

Version

Community Edition v3.2.1 (also checked master: same code in lib/plausible/cache.ex and system_controller.ex).

What happened

A single transient DB error during boot makes /api/health (readiness) return 500 for 3 hours, while the app otherwise serves traffic fine. Under Kubernetes with the documented health probe this becomes an infinite restart loop.

Sequence, from logs:

  1. At boot Plausible.Cache.Warmer runs refresh_all for every cache at once, plus Oban starts. With a 10-connection pool and a DB a few hundred ms away, the pool hit DBConnection.ConnectionError ... queue_timeout after ~6s.
  2. Plausible.Cache.refresh/3 catches that and only logs Error refreshing 'tracker_script_cache' - ... (cache.ex ~L233). The cache stays empty.
  3. TrackerScriptCache is in @critical_caches for readiness. ready?/1 returns false because size() == 0 but count_all() == 9. Readiness responds 500 {"sessions":"ok","postgres":"ok","clickhouse":"ok","sites_cache":null}.
  4. The next refresh_all for tracker_script_cache is scheduled ~3 hours out (interval 10805730 in the warmer init log). refresh_updated_recently runs every 30s but only picks up rows updated since the last run, so it never repairs a cache that missed the initial load.
  5. If the deployment's startup/liveness probe points at /api/health, the pod is killed long before 3h elapse, and the cycle repeats on every boot. I saw 56 consecutive restarts.

Plausible.Site.Cache and Plausible.Shield.IPRuleCache have the same shape but shorter refresh_all intervals (15 min / 3 min), so they self-heal before anyone notices.

Expected

A failed boot-time refresh_all should retry (short backoff) instead of waiting for the next multi-hour cycle, or refresh_updated_recently should fall back to a full refresh while ready?/1 is false.

Workaround

bin/plausible rpc 'Plausible.Site.TrackerScriptCache.refresh_all()' inside the container makes readiness green immediately. For Kubernetes, point startup/liveness at /api/system/health/live and keep /api/health for readiness only.

Activity

  1. deepanshupal commented on Sep 18, 2026

    @deepanshupal

    Confirmed on current master (16120d0a). The three-hour failure window follows directly from the supervision setup: TrackerScriptCache.All runs every 180 minutes, while TrackerScriptCache.RecentlyUpdated runs every 30 seconds but only queries rows updated in the last 15 minutes. Plausible.Cache.refresh/3 catches the boot DB failure and always returns :ok, so Plausible.Cache.Warmer cannot distinguish a failed initial fill from a successful cycle and schedules the normal interval.

    The smallest shared fix is probably to make cache refresh success observable to the warmer and retry a failed cycle with bounded backoff. Changing refresh_updated_recently to call refresh_all whenever ready? is false would self-heal this cache, but it also means the 30-second warmer can repeatedly issue a full-table query during a persistent DB incident and causes ready? to run another source count query. A warmer-level retry avoids that and covers every cache with the same swallowed-failure shape, but it changes the existing refresh_all/1 :: :ok contract.

    I can add a deterministic Plausible.Cache.Warmer regression with an injected warmer function that fails once and records the retry timing, then implement a bounded retry result without needing Postgres timing. Before doing that: do maintainers prefer the shared warmer contract (:ok | {:error, reason} + short retry) or a TrackerScriptCache-specific empty-cache fallback in refresh_updated_recently/1?

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