Skip to content

Order the account change feed by (xid, seq) like the deployment feed (parity with adcp-client#3289) #1825

Closed

Description

@bokelley

Problem

The Python account change feed (changes_after(account_id=...) in adcp_reporting_storage.ledger.change_feed) orders by seq and, since #1483, truncates each page before the first in-flight change. The JS SDK replaced that design in adcp-client #3289 (closes adcp-client #3283): every feed, account-scoped or deployment-wide, orders by (xid, seq) behind xid < pg_snapshot_xmin(pg_current_snapshot()), as the deployment feed already does here. Truncation depended on the account lock keeping seq in commit order and on every writer taking the same lock key; ordering by (xid, seq) needs neither. Both SDKs read the same shared adcp_reporting_changes tables, so the feed contract has to match.

Mirror (JS: adcontextprotocol/adcp-client#3289)

  • Account page: WHERE account_id = %s AND (xid, seq) > (%s::text::xid8, %s::bigint) AND xid < pg_snapshot_xmin(pg_current_snapshot()) ... ORDER BY xid, seq, replacing the fix(reporting): truncate account feed pages at the first in-flight change and match JS backoff #1483 truncation.
  • The account cursor carries xid. A legacy seq-only cursor (S) is mapped to (X, S) with X = LEAST(snapshot xmin, oldest visible xid among the account's changes with seq > S), so nothing is skipped. The cost is redelivery. The page returns a new-format cursor even when empty.
  • pruned_through_seq stays and gives exact legacy-cursor expiry (a legacy cursor expires if any change with a higher seq was pruned). The account horizon gains pruned_through_xid and pruned_through_xid_seq.
  • Consumer holds, prune_changes and retire_expired_periods compare (xid, seq); prune victims are taken in (xid, seq) order. A consumer row with NULL cursor_xid (saved before the upgrade) holds by seq until its next save.
  • Add the (account_id, xid, seq) index idempotently (the old (account_id, seq) index stays, it serves the legacy mapping). Cursor seq and xid are range-checked so out-of-range values fail as ValueError, not a database error.
  • Migration is idempotent and advisory-locked; the adcp_reporting_feed_consumers check constraint is replaced guardedly.

Python-specific: the feed table here is the shared adcp_reporting_changes, not reporting_ledger_changes (the buyer-facing protocol feed), which this issue does not touch.

Follow-up to #1483 (replaces its page truncation). Part of the JS parity set; #1492 is intentionally not part of this.

Activity

  1. added
    enhancementNew feature or request
    no-triageSkip the Claude triage bot — human or designated agent will handle this issue
    pythonPull requests that update python code
    on Nov 3, 2026
  2. added a commit that references this issue on Nov 3, 2026
    2d15a97
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

    enhancementNew feature or requestno-triageSkip the Claude triage bot — human or designated agent will handle this issuepythonPull requests that update python code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions