Tolerate read-only database in API and content worker heartbeats - #7914
Tolerate read-only database in API and content worker heartbeats#7914lucasc017 wants to merge 1 commit into
Conversation
During zero-downtime deployments the database may temporarily switch to read-only mode. The heartbeat mechanism treated all write failures as fatal, causing API and content pods to crash-loop even though they could still serve read traffic (collection downloads, EE pulls, API GETs). Detect PostgreSQL SQLSTATE 25006 (ReadOnlySqlTransaction) and skip the heartbeat write instead of killing the worker. When the database becomes writable again, normal heartbeat behaviour resumes automatically. Fixes: AAP-80698 AI-ASSISTED-BY: Claude Opus 4.6 (1M context) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
From an earlier discussion on chat: "The heartbeat is only successful if the timestamp in the database was updated, because other components will read that timestamps and draw conclusions from it." This approach is unsafe. Also there is consideration for zero downtime upgrades in a live database: |
|
Hey @mdellweg Would it work to add a grace period and track when the worker first hits a read-only error, and if it's been read-only for longer than API_APP_TTL, crash anyway? If API_APP_TTL is too short for this window, could we make a new variable? |
Summary
25006) by skipping heartbeat writes instead of crash-looping_is_read_only_db_error()helper to detect read-only transaction errors across the psycopg__cause__chainContext
Galaxy.ansible.com's zero-downtime deployment temporarily switches the database to read-only. After pulpcore commit
3e8183bcc("Mark a failed heartbeat as a catastrophic failure"), heartbeat write failures became fatal — causing API and content pods to crash-loop during this window even though they could still serve read requests.Three crash sites are fixed in each of the two affected entrypoints (
pulpcore/app/entrypoint.pyandpulpcore/content/__init__.py):AppStatus.objects.create()INSERT fails → setapp_status = None, retry on next heartbeat cyclesave_heartbeat()UPDATE fails → log warning, skip this cycleapp_status.delete()DELETE fails → log info, continue cleanupThe task worker (
pulpcore/tasking/worker.py) already handles heartbeat failures gracefully and is unaffected.Fixes: AAP-80698
Test plan
_is_read_only_db_error()helper (pgcode, sqlstate, no-cause, wrong-code, nested-cause)app_statusis None, recovery when DB becomes writable)run()cleanup (tolerates DB error on delete, skips when noapp_status)asave_heartbeatskip, read-onlyacreateskip)🤖 Generated with Claude Code