Repository navigation
fix(purchase): pass RequestedAt so Redshift reserved-node buys work with the new dedupe - #1138
Conversation
…s.RequestedAt Redshift purchases with an idempotency token now require RequestedAt to bound the DescribeReservedNodes dedupe window. Only pkg and providers/aws move; azure and gcp keep their pin. Must ship with the execution.go change that sets RequestedAt, or tokenized Redshift buys refuse. Part of #702 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
go's Redshift purchase dedupe bounds its DescribeReservedNodes match window with PurchaseOptions.RequestedAt and refuses a tokenized buy without it. Set it to the UTC dispatch time immediately before the single PurchaseCommitment call, for all providers (others ignore it). Tests cover a two-account fan-out for EC2 and Redshift and the empty-token path. Part of #702 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
go's Redshift match window (start >= RequestedAt-5min) has no upper bound, so a same-tuple purchase shortly after a completed one adopts the earlier node without buying, and an unknown outcome followed by a new same-tuple purchase outside the window can double-buy. Redshift re-drive stays refused. Part of #702 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 5 billable files and costs up to $1.25.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 25 minutes for your next included review. View limit details
Comment |
…doption Review of the RequestedAt change found three gaps worth tracking: same-tuple purchases in parallel or sequence are matched by tuple only, adoption of an existing node is recorded as an ordinary purchase, and RequestedAt is stamped per dispatch rather than per lineage (safe only while re-drive is refused). Part of #702 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge gate at
|
…s RequestedAt (#1762) * feat(config): add purchase_executions.first_dispatch_at Part of #702 (Redshift re-drive idempotency, PR1 commit 1 of 3). - Migration 000158 adds a nullable first_dispatch_at timestamptz. - PurchaseExecution.FirstDispatchAt is server-only (json:"-") and is written once at INSERT; the upsert's DO UPDATE SET leaves it out, so a stale in-memory copy can never clear or move it. - Every full-row projection and the shared scanner read it. Nothing sets it yet; a later commit stamps it before the first dispatch and passes it to the library as PurchaseOptions.RequestedAt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(config): stamp the first dispatch of a purchase lineage Part of #702 (Redshift re-drive idempotency, PR1 commit 2 of 3). StampFirstDispatchTx sets first_dispatch_at on the claimed running row in the caller's transaction and returns it with a DB-clock horizon check: - COALESCE keeps an existing stamp, so a re-drive sees the same time. - A fresh stamp is minted only at attempt 0, or when the predecessor ended no_submission at attempt 0. A retry of a possibly submitted lineage with no stamp gets none, which callers treat as a refusal. - Taking the caller's transaction lets the dispatch gate stamp and check conflicts atomically, so a rollback undoes the mint. Not called yet; the next commit wires it into dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(purchase): pass the first-dispatch stamp to Redshift buys Part of #702 (Redshift re-drive idempotency, PR1 commit 3 of 3). executeAndFinalize stamps first_dispatch_at for an execution with a selected Redshift rec, after the pre-execution refusals and before the provider call, and the stamp becomes PurchaseOptions.RequestedAt in place of the per-dispatch time.Now() from #1138. A re-drive of the same lineage therefore hands the library's DescribeReservedNodes dedupe the same window start as the first dispatch. - Fan-out per-account rows inherit the root stamp by struct copy and are never stamped themselves. - A store error at attempt 0 fails before submission. A missing stamp on a later attempt is a plain error, so a possibly submitted lineage is never re-stamped. - A Redshift rec that reaches the provider without a stamp is refused instead of buying with a zero RequestedAt. Redshift re-drive and retry stay refused in this PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(db): renumber the first_dispatch_at migration to 000154 Part of #702. Migration numbers are now assigned at merge time as the next free number on main (000153 is the highest today). Rename only: the migration SQL is unchanged; its test and one comment follow the number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Part of #702. Restores Redshift reserved-node purchases with the library dedupe from LeanerCloud/cloud-commitments-go#515, while Redshift re-drive and retry stay refused. This is option A from the scope question on #702; the retry half stays open there.
08fe7bafchore(deps): bumpsproviders/awstoaeb8a6f7ff51andpkgto541e05e5fd29, which aws requires forPurchaseOptions.RequestedAt. azure and gcp stay at3def3f898617.6db77b04fix(purchase):executeSinglePurchasesetsopts.RequestedAt = time.Now().UTC()right before the singlePurchaseCommitmentcall. Every dispatch path (scheduler, approve, execute-now, scheduled fire, fan-out per account) goes through it.RequestedAt, and the platform always sends a token. The two commits must ship together.cba71250docs(known-issues):known_issues/39_redshift_dedupe_window.mdrecords the unbounded match window. A same account/region/offering/node-count purchase within about 5 minutes of a completed one adopts the earlier node without buying. An unknown outcome followed by a new same-tuple purchase outside the window can double-buy.Why the go caller contract holds
go requires that no second same-tuple Redshift purchase is dispatched while an earlier lineage can still be re-driven. Redshift re-drive and retry remain refused on every path, and this PR does not touch them:
awsPurchaseHasNoDuplicateGuard/recRedriveRefusalReason(RecoverStrandedApprovals);armedRedriveRefusal(pre-existing armed successors);Behaviour changes that ride along with the library bump
Redshift: pre-buy dedupe uses
DescribeReservedNodes(already granted) instead ofDescribeTags(never granted), and node tagging is dropped. Today every Redshift buy is refused before purchase; after this PR, first-dispatch buys go through again. Two concurrent same-tuple purchases are guarded only by go's check-then-act matcher, the per-row execution claim and suppressions.RDS: the offering lookup now refuses at the pagination cap even when a match was found (go#303), so an RDS buy can newly fail with "cannot be proven unique".
RDS:
isLicensedEnginenow also matches the Cost Explorer spelling "sql server" (go#302), so those recommendations are refused like other licensed engines.Savings Plans: the plan type, term and payment are validated before a Cost Explorer
OfferingIDis trusted. This fails loud on a mismatch.Recommendations: Cost Explorer parsing now fails loud.
EstimatedBreakEvenInMonthsdrops that recommendation detail instead of defaulting, and a parsed value is stored inBreakEvenMonths.Rate limiter:
RateLimiter.Waitreturns at once on an already-cancelled context.Adopted nodes look like purchases: when the library adopts an existing node (a guard match, or recovery after an unknown outcome), it sets
PurchaseResult.ExistingCommitment. The platform does not read that field yet, so an adopted node is recorded as an ordinary successful purchase.known_issues/39records this, together with the concurrent same-tuple case.No behaviour change: ElastiCache moves its guard into
internal/purchaseguardwith the same logic. EC2, MemoryDB and OpenSearch have no service code changes between the two versions. Thepkgbreak-even and scorer changes have no consumer in this repo.Verification
Results are posted as PR comments.
🤖 Generated with Claude Code