Skip to content

add change store extension for per-URI request claims#152

Merged
behinddwalls merged 7 commits into
mainfrom
preetam/changestore-extension
May 21, 2026
Merged

add change store extension for per-URI request claims#152
behinddwalls merged 7 commits into
mainfrom
preetam/changestore-extension

Conversation

@behinddwalls
Copy link
Copy Markdown
Collaborator

@behinddwalls behinddwalls commented May 7, 2026

Summary

Introduce a dedicated extension that tracks (URI, request_id) claims with mutable
metadata, scoped to queue. Foundation for cross-request URI overlap detection;
no consumers in this commit.

  • entity/change_record.go: immutable identity (URI, RequestID), mutable metadata
  • extension/changestore/: ChangeStore interface, mysql impl, mock, schema
  • INSERT IGNORE on writes for retry idempotency on (uri, request_id)
  • FindOverlapping is single-table; callers do their own liveness check
  • integration test under test/integration/extension/changestore/mysql/
  • e2e suite applies the change schema (harmless no-op until consumers land)

Test Plan

Issues

Stack

  1. @ add change store extension for per-URI request claims #152
  2. duplicate detection via change store #144

Comment thread entity/change_record.go Outdated
Comment thread extension/changestore/mysql/schema/change.sql Outdated
Comment thread extension/changestore/mysql/schema/change.sql Outdated
Comment thread extension/changestore/change_store.go Outdated
Comment thread extension/changestore/change_store.go Outdated
Comment thread extension/changestore/mysql/change_store.go Outdated
Comment thread extension/changestore/mysql/change_store.go Outdated
@github-actions github-actions Bot changed the base branch from preetam/fix-land-api to main May 7, 2026 22:36
@github-actions github-actions Bot force-pushed the preetam/changestore-extension branch from 1f6589e to 6057c35 Compare May 7, 2026 22:36
Introduce a dedicated extension that tracks (URI, request_id) claims with mutable
metadata, scoped to queue. Foundation for cross-request URI overlap detection;
no consumers in this commit.

- entity/change_record.go: immutable identity (URI, RequestID), mutable metadata
- extension/changestore/: ChangeStore interface, mysql impl, mock, schema
- INSERT IGNORE on writes for retry idempotency on (uri, request_id)
- FindOverlapping is single-table; callers do their own liveness check
- integration test under test/integration/extension/changestore/mysql/
- e2e suite applies the change schema (harmless no-op until consumers land)
The e2e suite_test applies the change schema, but the bazel test target
was missing //extension/changestore/mysql/schema in its data list, so
runfiles didn't ship the .sql file and ApplySchema couldn't find it.
… API

- schema: PRIMARY KEY (queue, uri, request_id) — queue-scoped lookups become
  PK-prefix scans and the table is shardable by queue. Comment in the schema
  explains why request_id stays in the PK (concurrent claims by different
  requests coexist as distinct rows; same-request retries collide on the PK).
- schema: metadata JSON NOT NULL. The mysql impl writes '{}' for empty
  metadata so callers don't need to know about the column constraint.
- interface: drop excludeRequestID from FindOverlapping. Callers that want to
  skip self filter the result by RequestID themselves. Documented Create's
  batch atomicity (single multi-row INSERT, all-or-nothing).
- mysql: FindOverlapping query now leads with `WHERE queue = ?` to align with
  the new PK order.
- entity: expanded RequestID/Queue field comments to explain their PK roles.
- README: documents the new key shape and metadata semantics.
- integration tests: drop the 4th arg, replace TestFindOverlapping_ExcludesSelf
  with a test that asserts the store does NOT exclude self, and add an
  assertion that empty metadata round-trips as '{}'.
@behinddwalls behinddwalls force-pushed the preetam/changestore-extension branch from 6057c35 to ff53ce6 Compare May 8, 2026 06:25
@behinddwalls behinddwalls requested a review from sbalabanov May 8, 2026 06:25
The previous interface assumed a SQL-style backend that gives batch atomicity
and multi-key WHERE IN queries for free, which over-constrains alternatives
like DynamoDB or Bigtable. Move to single-record, single-URI primitives that
any backend can implement cheaply; callers loop when they have multiple URIs
(typically 1-5 per request).

- Create(record ChangeRecord) — was Create([]ChangeRecord). No batch
  atomicity contract; same-PK conflict is INSERT IGNORE on the mysql impl,
  callers retry whole loops to converge.
- GetByURI(queue, uri) — was FindOverlapping(queue, []uris). Returns every
  ChangeRecord for the (queue, uri) pair; caller loops over its URIs and
  filters by RequestID for self-exclusion.
- README and integration tests updated for the new shape.
Captures the design lesson from PR #152: interfaces should be shaped for
the technology space (SQL, KV, document, queues, RPC, …), not for the one
implementation we're adding. Lists common over-constraints to avoid (batch
atomicity, multi-key queries, server-side filters, cross-entity
transactions, strict ordering / exactly-once, sync low-latency assumptions)
and proposes the test: 'could DynamoDB / Kafka / Bigtable / a remote RPC /
an in-memory map satisfy this signature without contortion?'
Mirrors the request-store pattern (RequestStore.UpdateState takes oldVersion
+ newVersion). The schema gains 'version INT NOT NULL'; ChangeRecord gains
'Version int32'; mysql impl writes/reads it. No UpdateMetadata method yet —
added when the first metadata-enrichment writer arrives.
@behinddwalls behinddwalls enabled auto-merge May 21, 2026 05:26
@behinddwalls behinddwalls added this pull request to the merge queue May 21, 2026
Merged via the queue into main with commit 89e81b1 May 21, 2026
13 checks passed
behinddwalls added a commit that referenced this pull request May 21, 2026
## Summary
Stacked on top of #152 (change store extension).

Detects duplicate land requests by reading per-URI claims from the new
change store. The `start` controller writes a claim row for each URI on
the request; `validate` reads the change store and rejects requests
whose URIs overlap with another in-flight request in the same queue.

- **start.go** — after persisting the request, claim each URI in the
change store. `INSERT IGNORE` makes this idempotent on queue redelivery
(same `request_id` retry is a no-op).
- **validate.go** — query the change store for any row with an
overlapping URI in the same queue (excluding self). For each unique
candidate `request_id` returned, look up the request and skip
terminal/orphan owners. If any live owner remains, reject as a user
error naming the conflicting `request_id`.
- Two single-table queries per check, no cross-store SQL joins.
- Stop wrapping every storage error as `Retryable`; default is plain
`fmt.Errorf` per the `core/errs` framework + #134.
- Wires `changeStore` into both controllers in
`example/server/orchestrator/main.go`.

## Behavior

- **Overlap (not full-set equality):** the change store returns rows for
any URI in the new request that matches a row from another in-flight
request, so partial-overlap is rejected — addresses @sbalabanov's review
comment.
- **Liveness:** terminal-state owners and orphans (whose request row is
gone) are skipped, so stale claims don't block new requests
indefinitely.
- **Retries:** `start` tolerates `ErrAlreadyExists` from request
creation as a same-id replay; the change store's `INSERT IGNORE` is
idempotent for the same reason.

## Test Plan
- Unit tests for `start` and `validate` cover happy path,
overlap-with-live, overlap-with-terminal-skipped,
overlap-with-orphan-skipped, multi-URI-same-owner deduped, and
infra-error propagation.
- Existing integration tests on `extension/storage/mysql` continue to
pass after `GetActiveByQueue` removal.
- Change store integration test (in #152) exercises the MySQL impl
against a real DB.

## Issues

---------

Co-authored-by: Preetam Dwivedi <preetam@uber.com>
Co-authored-by: Preetam Dwivedi <behinddwalls@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants