Skip to content

fix: commit multi-step CRUD workflows atomically - #714

Open
frgfm wants to merge 4 commits into
mainfrom
codex/atomic-crud-workflows
Open

frgfm wants to merge 4 commits into
mainfrom
codex/atomic-crud-workflows

Conversation

@frgfm

@frgfm frgfm commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Commit detection and alert workflows once, bulk-unlink detections, and serialize sequence assembly per pose. A transaction advisory lock covers bbox and continuity paths after uploads, before matching. Remove unused helper commit flags.

Workload p50 latency, ms (main → PR) Peak RSS, MiB (main → PR)
Ingest two boxes, serial 65.5 → 43.9 164.4 → 164.0
Two concurrent requests, same pose 79.4 → 51.0 164.5 → 164.5
Two concurrent requests, different poses 90.6 → 69.2 164.3 → 164.4
Delete sequence with 1,000 detections 5,422.8 → 39.1 164.1 → 163.1

Synthetic local PostgreSQL; three fresh workers/variant, one warmup then five rounds (15 serial or 30 concurrent requests). RSS is median worker VmHWM, including startup, excluding DB/client. Uploads are stubbed; memory is essentially unchanged.

Forced opposite-order scheduling reproduced 3/6 HTTP 500s before the lock, 0/6 after, comparing the previous PR refreshed onto main with this revision. This is a regression reproduction, not a normal error rate.

Review focus: same-pose DB work now queues; other poses use separate locks. Rollback releases the lock. S3 media is not rolled back with DB changes; continuity expiry during upload/wait can return 204 after upload.

Validation: 725 backend cases passed, one skipped; Ruff, Ty and CI pass. The existing 10 µs client timeout test passed on retry.

Comment thread src/tests/test_crud_transactions.py Fixed
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.91837% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.87%. Comparing base (219cfac) to head (db18820).

Files with missing lines Patch % Lines
src/app/api/api_v1/endpoints/alerts.py 75.00% 1 Missing ⚠️
src/app/api/api_v1/endpoints/detections.py 92.30% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #714      +/-   ##
==========================================
+ Coverage   94.70%   94.87%   +0.17%     
==========================================
  Files          60       60              
  Lines        3266     3278      +12     
==========================================
+ Hits         3093     3110      +17     
+ Misses        173      168       -5     
Flag Coverage Δ
backend 95.03% <95.91%> (+0.17%) ⬆️
client 91.30% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@frgfm
frgfm requested a review from MateoLostanlen October 15, 2026 12:47

@MateoLostanlen MateoLostanlen 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.

One commit per request is the right move and the rollback tests show it works. One blocker, one small nit.

Blocker: two requests for the same pose can block each other

With commit=False, the lock on a sequence row is now kept until the end of create_detection. If one frame touches sequence A then B, and another frame of the same pose touches B then A at the same time, Postgres kills one of them with deadlock detected. That request fails with a 500 and its image is already on S3. Rare, but new with this PR: the old code released the lock right after each update.

Easy fix: lock the pose for the whole transaction before the first write, e.g. SELECT pg_advisory_xact_lock(:pose_id). Requests for the same pose then wait for each other, and the lock goes away on commit or rollback.

Nit: bump_max_conf and enqueue_validation are now always called with commit=False. The default branch is the line Codecov flags. The flag can go.

Everything else looks good: each path ends with one commit or rolls back when the session closes, skipping refresh is safe with expire_on_commit=False, the bulk UPDATE in delete_sequence does the same thing as before, and I found no lock problem with the validation worker.

@frgfm
frgfm requested a review from MateoLostanlen October 23, 2026 15:15

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants