Repository navigation
Conversation
Deploying control-layer with
|
| Latest commit: |
c32232d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://85a612c4.control-layer.pages.dev |
| Branch Preview URL: | https://feat-dw-992-single-clock-ret.control-layer.pages.dev |
92ebe5e to
9580636
Compare
|
Running ultrareview automatically — this PR appears to change concurrency-sensitive code such as locks, queues, or retries, where a missed race may only surface under production load, so a deeper multi-pass review is worth running. I'll post findings when complete. |
There was a problem hiding this comment.
Ultrareview ran automatically — this PR appears to change concurrency-sensitive code such as locks, queues, or retries, where a missed race may only surface under production load, so a deeper multi-pass review is worth running.
All reported issues were addressed across 26 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
9580636 to
6c61442
Compare
There was a problem hiding this comment.
All reported issues were addressed across 4 reviewed files. 1 file intentionally excluded from review.
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
Requires human review: Retention redesign: one creation-anchored clock from max_expiry_seconds, partition retirement, retry cut-off, new index; drops content and changes retry API. Requires human sign-off for data deletion and request-path behavior change.
View guided diff | Turn on auto-fix | Re-trigger cubic
…(DW-992) Both content families now drop a weekly partition for one reason: the week is past its retention period, measured from the week itself, and no batch created in it (or reading input uploaded in it) is still running. Before, three loops ran in sequence. An archive week waited 28 days after the *last* batch in it froze, so one retry weeks later held every other batch's completions (the week of 24 Aug reached 65 days). Its completion stamped retention_expired_at on every batch in the week in one statement inside the drop transaction; at a million batches that outgrew the 30 s statement timeout, the transaction rolled back drop and all, and the week of 14 Sep has retried every 30 s since 19 Oct. Only that stamp released input files, which an hourly sweep then re-examined, four million at a time, to expire none; only expired files released a template week. The sweep was the writer that stalled uploads behind weekly partition attach. The stamp, the sweep, its cursor types and daemon phase, and the per-file scans in the template candidate are gone. The guard that remains reads unfinished batches through a new partial index on (created_at) WHERE counts_frozen_at IS NULL AND deleted_at IS NULL: a few dozen rows. A finished batch holds nothing open; its results do not need its templates. Batch creation and retry additionally require the input's template week to be active, so a request is never populated from content that is gone. Results and input files report expiry from the expires_at they carry. Startup warns when either retention period is shorter than the expiry the API shows, since content would then go before its stated date. The retention keys are unchanged. Set both to the same value for one clock; the oldest row in a dropped week is 7 + N days old.
Review fixes for the creation-anchored retention clock. A retry makes a batch unfinished again, and an unfinished batch holds its weeks open, so a batch can no longer be retried once it is within one day of either retention period (results from batch creation, input from file upload). Weeks drop no earlier than week start + 7 days + N, so the one-day margin keeps the cut-off before the drop. The limits come from the same daemon retention settings, and apply only while that family's retirement is enabled. Refusals map to 400 instead of 500. Retrying an archived batch now share-locks its archive week and refuses unless it is active; before, the move-back could read a dropped partition and reset the batch over completions that were gone. Creation and both retry paths share-lock the input's template week, and claim() re-runs the family's unfinished-batch guard in a fresh statement after locking the bucket row and before fencing it. Under READ COMMITTED that statement sees a batch committed while the claim waited, closing the window between the candidate query and the fence. The template guard also bounds batches.created_at from the week start so it range-scans the partial index. The startup warnings move into a helper with a test of the comparison direction.
… (DW-992) Retention was configured separately from the expiry the API shows, so the two could disagree and content could go before a file's expires_at. Both weekly families now drop a week once it is R days past its end, where R is batches.files.max_expiry_seconds rounded up to whole days. Every file's expires_at is at most created_at + max_expiry_seconds, which is at most the week's end plus R, so the shown date can never outrun the content and the startup warnings have nothing left to check. batch_archive_retention_days and template_retention_days still parse but are ignored, with a deprecation warning at startup. The fusillade retention config takes one content_retention_days instead of two. The retry age cut-off uses the same R: while either family's retirement is enabled, a batch cannot be retried once it, or its input file, is older than R minus one day.
… drop (DW-992) Dropping the per-week stamp also dropped the only thing that took explicitly deleted, archived batches out of the erasure purge's tombstone set, so that set only grew and every purge tick probed each member. Archive retirement now stamps retention_expired_at on the week's deleted batches alone, read through idx_batches_explicitly_deleted; finished batches stay untouched, so the write is small however large the week. Also document the two retirement switches by name, and the retry-age and retirement refusals in the retry endpoints' 400 descriptions, and say that content retention follows max_expiry_seconds.
…cs (DW-992) Retrying specific requests refuses with 400 only at the retry age cut-off; a request whose input has been retired is left out and the batch is returned with 200. The archive retirement module doc still said nothing is stamped at retirement, but deleted batches now are.
6ca5fb9 to
c32232d
Compare
Second layer of the DW-992 stack, on top of #2113. This is the change that replaces the retention design; the first layer made the API honest about
expires_atand validates the file expiry settings everywhere.Why
Three loops ran in sequence to delete batch content, and each had a failure mode of its own:
retention_expired_aton every batch in the week, in one statement inside the drop transaction. A large week can exceed the statement timeout; the transaction rolls back, drop included, and the engine retries on every tick, so that week never completes and every later week queues behind it.fileswriter that stalled uploads behind weekly partition attach (DW-950).Retention was also configured separately from the expiry the API shows, so the two could disagree and content could go before the date a file reported.
What
One clock, one period
Both weekly families (batch archive and request templates) drop a partition for one reason: the week is past the retention period R, measured from the week itself, and no batch created in it (archive) or reading input uploaded in it (templates) is still running.
R is derived from
batches.files.max_expiry_seconds, rounded up to whole days. Every file'sexpires_atis at mostcreated_at + max_expiry_seconds, which is at most the week's end plus R, so the date the API shows can never outrun the content. There is no separate retention setting and nothing left to warn about.batch_archive_retirement.rs: candidate anchored on the week.completion_sqlnow stampsretention_expired_atonly on the week's explicitly deleted batches, read throughidx_batches_explicitly_deleted, so they leave the erasure purge's tombstone set once their archived rows are dropped. Finished batches are not written, so the statement stays small however large the week.template_retirement.rs: candidate anchored on the week with the unfinished-batch guard; the two per-file scans removed. A dedicated (file-less) template row still fails the gate closed. The guard also boundsbatches.created_atfrom the week start (a batch is always created after its input), so it range-scans the new index.batches (created_at) WHERE counts_frozen_at IS NULL AND deleted_at IS NULL, a new partial index that holds only the batches still running. Three-file concurrent migration (create / reindex / validate) with down files and checksum entries.expire_file_content,FileExpiryCursor/FileExpiryStep, the daemon's hourly phase and constants) is deleted acrossfusillade-core,fusillade-arsenalandfusillade. Its metric,fusillade_file_content_expired_total, is removed; any dashboard or alert on it will flat-line and should be dropped.active, so a request is never populated from content that is gone.Deprecated settings
batch_daemon.batch_archive_retention_daysandbatch_daemon.template_retention_daysare deprecated and ignored. They still parse so existing values files keep loading, and dwctl logs a warning at startup when either is set. Operators should remove them. For a deployment that set them, the effective retention period may change to the one derived frommax_expiry_seconds.In the
fusilladecrate,RetentionMaintenanceConfigtakes onewith_content_retention_daysin place of the two per-family setters.Nothing reads
batches.retention_expired_atorfiles.retention_expired_atany more except the orphan purge, which uses them to tell explicit deletes apart. Archive retirement still sets the batch column on deleted batches (see above), and the columns stay.Retry age cut-off
A retry makes a batch unfinished again, and an unfinished batch holds its weeks open. To keep that hold short and predictable, a batch cannot be retried after a certain age. While content retirement is enabled, retry (whole batch, or selected requests) is refused with a 400 once
now > created_at + R - 1 day, counted from the batch's creation and from its input file's upload, whichever is earlier (a retry re-reads the input templates).A week drops no earlier than
week_start + 7 days + R, and everything in it was created at or afterweek_start, so the one-day margin always puts the cut-off before the partition can drop, never after.Races between writers and retirement
request_template_bucketsrowFOR SHARE(alongside the existingFOR UPDATEon the file).batch_archive_bucketsrowFOR SHARE(asarchive_batchdoes) and is refused unless the week isactive. Without this, the move-back would read a dropped partition, find nothing, and reset the batch over completions that are gone.claim()now re-runs the family's unfinished-batch guard (FamilySpec::recheck_sql) in a fresh statement after locking the bucket rowFOR UPDATEand before fencing it. Under READ COMMITTED that statement sees a batch committed while the claim waited for the lock; if one is found the claim backs off. A writer that arrives after the fence waits for it and then reads the week as no longer active.archive_bucket. These differ only for a batch archived into a later week because its creation week was already fenced; the retry archive-week check and the age cut-off bound that case (documented inbatch_archive_retirement.rs).Rollout notes
batch_archive_retention_daysandtemplate_retention_daysfrom values files; until then each logs a deprecation warning at startup. Check thatmax_expiry_secondsis the retention you want, since it now sets it.Tests
retiringorretired, and succeed when it isactive; an archived batch's retry is refused when its archive week isretiringorretired, without resetting the batch.max_expiry_secondsup to whole days, and the deprecated keys still parse.just test rustpasses.