Skip to content

fix(preprod): run size comparisons outside one enclosing transaction - #130338

Open
ldelvoye wants to merge 17 commits into
ldelvoye/ref/preprod-size-tasks-splitfrom
ldelvoye/fix/preprod-compare-transaction
Open

ldelvoye wants to merge 17 commits into
ldelvoye/ref/preprod-size-tasks-splitfrom
ldelvoye/fix/preprod-compare-transaction

Conversation

@ldelvoye

@ldelvoye ldelvoye commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

resolves INFRENG-977

Runs the preprod size comparison task without holding a database transaction across the file downloads, the diff and the result uploads.

The task used to wrap the whole run in one transaction, open for up to its 120 s deadline, with a savepoint per write inside it. It now computes and uploads every result first and records the rows in one short transaction at the end, so a run is still all-or-nothing: a failure or a deadline leaves no rows, as before. Worth weighing in review: two runs that overlap on the same pair now both compute the diff where the second used to block on the first's transaction, and the one that loses deletes its own result file. The manual and monitor paths claim their row with a conditional update for the same reason.

The task held a worker transaction open for the whole run, up to its
120 s deadline, pinning xmin on the default primary. Each PENDING row,
state change and file upload now commits on its own, and a comparison
whose diff or upload raises is marked FAILED so it can be retried.
@linear-code

linear-code Bot commented Nov 6, 2026

Copy link
Copy Markdown

INFRENG-977

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Nov 6, 2026
Recording each comparison as it finished left PENDING, PROCESSING and
FAILED rows behind when a run failed, and neither the sweeper nor a
member's compare request recovers those. The run now computes every
result first and records them in one short transaction, so a failed run
leaves no rows, as it did before.
A deadline landing while the recording transaction commits left SUCCESS
rows pointing at deleted files. Cleanup now checks for a referencing row
first. The single-comparison path claims its row with a conditional
update, so it cannot overwrite a result an overlapping run recorded.
The old task dispatched the status check and PR comment inside its
transaction, so a deadline landing on the COMMIT itself never lost them.
Dispatching after the recording transaction reopened that window.
The single-comparison helper marked its row PROCESSING and later SUCCESS
without checking the row was still PENDING, so two overlapping runs
could overwrite each other. It also uploaded the result inside the
transaction that records it, and left the file behind when that write
failed.
@ldelvoye
ldelvoye changed the base branch from master to ldelvoye/fix/preprod-manual-compare-claim November 9, 2026 22:13
@ldelvoye
ldelvoye added this pull request to stack #130530 November 9, 2026 22:13
@ldelvoye
ldelvoye removed this pull request from stack #130530 November 9, 2026 22:16
@ldelvoye
ldelvoye changed the base branch from ldelvoye/fix/preprod-manual-compare-claim to master November 9, 2026 22:16
@ldelvoye
ldelvoye changed the base branch from master to ldelvoye/ref/preprod-size-tasks-split November 9, 2026 23:18
@ldelvoye
ldelvoye added this pull request to stack #130548 November 9, 2026 23:18
@ldelvoye
ldelvoye marked this pull request as ready for review November 10, 2026 22:40
@ldelvoye
ldelvoye requested a review from a team as a code owner November 10, 2026 22:40
@ldelvoye
ldelvoye requested a review from a team November 10, 2026 22:40
ldelvoye

This comment was marked as resolved.

The timeout sweeper fails PROCESSING comparisons by date_updated, and
the claim's .update() skips auto_now, so a stale PENDING row would be
swept mid-run if the claim stopped setting it.
@ldelvoye

Copy link
Copy Markdown
Member Author

bot: diagram the transaction scope before and after this change

@ldelvoye

Copy link
Copy Markdown
Member Author

🌱 Before this change the whole automatic run sat inside one transaction. Now the downloads, diffs and uploads run with no transaction open, and only the row writes share one short transaction at the end.

Before, run_size_analysis_comparisons wrapped the whole comparison loop in transaction.atomic. Every comparison's get_or_create, _mark_processing and _record_success ran inside it, and _record_success included the File create and putfile. Each of those nested atomics became a savepoint, and the outer transaction stayed open for up to the task's 120 s deadline.

After, comparison_run.py splits the run into three phases:

  • comparison_run.py:40-48: _prepare_size_analysis_comparison runs once per comparison in autocommit. It reads the existing row, downloads both analysis files, runs compare_size_analysis and uploads the result under a provisional name.
  • comparison_run.py:55-72: one short transaction.atomic runs bulk_create(ignore_conflicts=True) and then, per row in sorted order, select_for_update, renames the file and sets SUCCESS or FAILED. It then dispatches the follow-up tasks before the commit. It opens no savepoints.
  • comparison_run.py:73-75: the finally runs after the transaction and deletes any result file that no committed row references.

The manual and monitor path, run_size_analysis_comparison, never ran inside an enclosing transaction. Its PROCESSING claim is now a conditional UPDATE in autocommit (comparison_run.py:315-320), and the upload runs outside any transaction. Only the SUCCESS write keeps its own short atomic (comparison_run.py:337-340).

The head has moved to 278bf73 since the review, but that commit only adds a test. comparison_run.py is unchanged, so the line numbers above still hold.

Two panels on one time axis: before, a single transaction spans both comparisons and the dispatch, with three savepoints per comparison; after, the comparisons run with no transaction and no savepoints, and only the final record-and-dispatch step sits in a short transaction, followed by file cleanup.

This message was generated by a bot; every message tagged 🌱 is from it. Reply with bot: to ask it something.

@jamieQ jamieQ left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes generally make sense, though personally I find it somewhat harder to reason about now 😅. I guess that's the nature of distributed systems though... Left a number of thoughts for your consideration. Also, as a slight aside, the "success criteria" in the linked ticket doesn't seem like it totally matches the implementation (e.g. pending rows created before side effects & don't roll back completed work).

)

provisional_file_name = f"{head_size_metric.id}-{base_size_metric.id}"
result_file = _store_size_comparison_results(provisional_file_name, comparison_results)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's too much of an edge case to be worth dealing with, but it seems like if the task raised an exception after we create the file but before it's saved in the result_files array in the caller we'd forget to clean it up. Something similar maybe exists for the run_size_analysis_comparison flow too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely an edge case and you're right run_size_analysis_comparison has that gap. Adding a sweep job that deletes orphaned comparison files is the proper way to do it IMO. I think it's fine to ship for now, the loss is just tiny data growth for now.

A failing delete in the upload's except block replaced the original
error. Cleanup now goes through the helper that logs and swallows its
own failures. Also gives the lost-race skip its own log event, makes
the prepared-comparison type private, and covers a deadline that lands
inside the recording transaction.

This branch was successfully deployed

1 active deployment
Preview — a7265615 Deployed Nov 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants