Skip to content

Tolerate read-only database in API and content worker heartbeats - #7914

Open
lucasc017 wants to merge 1 commit into
pulp:mainfrom
lucasc017:AAP-80698-read-only-db-heartbeat
Open

Tolerate read-only database in API and content worker heartbeats#7914
lucasc017 wants to merge 1 commit into
pulp:mainfrom
lucasc017:AAP-80698-read-only-db-heartbeat

Conversation

@lucasc017

@lucasc017 lucasc017 commented Jul 27, 2026

Copy link
Copy Markdown

Summary

  • API and content workers now gracefully handle a read-only database (PostgreSQL SQLSTATE 25006) by skipping heartbeat writes instead of crash-looping
  • Workers continue serving read traffic (collection downloads, EE pulls, API GETs) during the read-only window and resume normal heartbeats once the database becomes writable
  • Adds _is_read_only_db_error() helper to detect read-only transaction errors across the psycopg __cause__ chain

Context

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.py and pulpcore/content/__init__.py):

  1. StartupAppStatus.objects.create() INSERT fails → set app_status = None, retry on next heartbeat cycle
  2. Heartbeatsave_heartbeat() UPDATE fails → log warning, skip this cycle
  3. Shutdownapp_status.delete() DELETE fails → log info, continue cleanup

The task worker (pulpcore/tasking/worker.py) already handles heartbeat failures gracefully and is unaffected.

Fixes: AAP-80698

Test plan

  • Unit tests for _is_read_only_db_error() helper (pgcode, sqlstate, no-cause, wrong-code, nested-cause)
  • Unit tests for API worker heartbeat (read-only skip, real DB error still fatal, retry create when app_status is None, recovery when DB becomes writable)
  • Unit tests for API worker run() cleanup (tolerates DB error on delete, skips when no app_status)
  • Unit tests for content worker heartbeat (read-only asave_heartbeat skip, read-only acreate skip)
  • Existing heartbeat tests still pass (InterfaceError, OperationalError)
  • Manual verification: switch pgbouncer to read-only endpoint in stage — API pods should remain up

🤖 Generated with Claude Code

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>
@mdellweg

Copy link
Copy Markdown
Member

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:
https://pulpproject.org/pulpcore/docs/dev/learn/plugin-concepts/?h=zero#zero-downtime-upgrades

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants