fed8b2eed7
Backend release / release (push) Waiting to run
Bandit Security Scan / bandit_scan (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / manifest (push) Blocked by required conditions
Build and push DocsGPT FE Docker image for development / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / manifest (push) Blocked by required conditions
Python linting / ruff (push) Waiting to run
Run python tests with pytest / Run tests and count coverage (3.12) (push) Waiting to run
React Widget Build / build (push) Waiting to run
58 lines
2.0 KiB
Python
58 lines
2.0 KiB
Python
"""0006 task_dedup lease columns — running-lease for in-flight tasks.
|
|
|
|
Without these, ``with_idempotency`` only short-circuits *completed*
|
|
rows. A late-ack redelivery (Redis ``visibility_timeout`` exceeded by a
|
|
long ingest, or a hung-but-alive worker) hands the same message to a
|
|
second worker; ``_claim_or_bump`` only bumped the attempt counter and
|
|
both workers ran the task body in parallel — duplicate vector writes,
|
|
duplicate token spend, duplicate webhook side effects.
|
|
|
|
``lease_owner_id`` + ``lease_expires_at`` turn that into an atomic
|
|
compare-and-swap. The wrapper claims a lease at entry, refreshes it via
|
|
a 30 s heartbeat thread, and finalises (which makes the lease moot via
|
|
``status='completed'``). A second worker hitting the same key sees a
|
|
fresh lease and ``self.retry(countdown=LEASE_TTL)``s instead of running.
|
|
A crashed worker's lease expires after ``LEASE_TTL`` seconds and the
|
|
next retry can claim it.
|
|
|
|
Revision ID: 0006_idempotency_lease
|
|
Revises: 0005_ingest_attempt_id
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
|
|
|
|
revision: str = "0006_idempotency_lease"
|
|
down_revision: Union[str, None] = "0005_ingest_attempt_id"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.execute(
|
|
"""
|
|
ALTER TABLE task_dedup
|
|
ADD COLUMN lease_owner_id TEXT,
|
|
ADD COLUMN lease_expires_at TIMESTAMPTZ;
|
|
"""
|
|
)
|
|
# Reconciler's stuck-pending sweep filters by
|
|
# ``(status='pending', lease_expires_at < now() - 60s, attempt_count >= 5)``.
|
|
# Partial index keeps the scan small even under heavy task throughput.
|
|
op.execute(
|
|
"CREATE INDEX task_dedup_pending_lease_idx "
|
|
"ON task_dedup (lease_expires_at) "
|
|
"WHERE status = 'pending';"
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.execute("DROP INDEX IF EXISTS task_dedup_pending_lease_idx;")
|
|
op.execute(
|
|
"ALTER TABLE task_dedup "
|
|
"DROP COLUMN IF EXISTS lease_expires_at, "
|
|
"DROP COLUMN IF EXISTS lease_owner_id;"
|
|
)
|