fix: prolong and track shared request queue locks to prevent duplicate processing - #1062
fix: prolong and track shared request queue locks to prevent duplicate processing#1062vdusek wants to merge 6 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1062 +/- ##
==========================================
- Coverage 91.89% 91.87% -0.02%
==========================================
Files 51 51
Lines 3232 3274 +42
==========================================
+ Hits 2970 3008 +38
- Misses 262 266 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Btw. this is a different case than #887 |
| # Prolong the lock so the consumer gets a full lock window starting now instead of sharing the batch lock | ||
| # acquired when the head was listed. If the lock can no longer be (re)acquired, skip the request. | ||
| try: | ||
| lock_info = await self._api_client.prolong_request_lock( |
There was a problem hiding this comment.
What about the cost increase? Won't this lead to each request being locked at least twice? Maybe we could lock only if the remaining time is something very small?
I will run benchmarks with this change to quantify the cost increase. Maybe it is not serious
There was a problem hiding this comment.
Let's wait for them to finish:
Without change: https://github.com/apify/actor-benchmarks/actions/runs/29900513281
With change: https://github.com/apify/actor-benchmarks/actions/runs/29900596790
There was a problem hiding this comment.
Summary for the repeated 10 benchmark runs
Without change:
Overall benchmark of all runs, :aggregated_benchmark=Actor: parsel-crawler-benchmark, Valid results: 2013.6, Runtime: 59.6927 s, Costs: 0.1262127994999134 USD
With change:
Overall benchmark of all runs, :aggregated_benchmark=Actor: parsel-crawler-benchmark, Valid results: 2013.3, Runtime: 61.2107 s, Costs: 0.14725829787564015 USD,
There was a problem hiding this comment.
Around 16 % cost increase seems pretty high to me. Maybe some optimization could be used to mitigate this increase.
How is it done in JS SDK?
There was a problem hiding this comment.
What about the cost increase? Won't this lead to each request being locked at least twice?
Yes, it should be one extra RQ write per request. The 16% seems expected.
However, IMO better to be a bit more expensive and correct than cheaper and broken.
Maybe we could lock only if the remaining time is something very small?
See the new commit. The lock is now prolonged only when its remaining windows are below half of the lock time.
How is it done in the JS SDK?
It prolongs unconditionally on every fetch.
So please see the current state and try to execute the benchmarks again.
The shared request-queue client (
access='shared') locked a batch of up to 25 requests for 180 s inlist_and_lock_head, but never tracked, prolonged, or released those locks. As a batch drained slowly, locks expired mid-flight and requests got processed twice - by another consumer that re-locked an expired request, or by a second worker in this process after the request was re-listed. That defeats the class's documented "request locking to prevent duplicate processing" guarantee (double charges in PPE, duplicate dataset items).Changes:
lock_expires_atper cached head entry (the API returned it, we discarded it)._requests_in_progress: skip them when re-listing the head and when fetching, and keepis_finishedfalse while any is outstanding.fetch_next_request, skip candidates whose lock already lapsed, and prolong the lock only when its remaining window is below half the lock time (_MIN_LOCK_TIME_AT_HANDOFF, 90 s). A request handed out from a fresh batch lock needs no prolong, so the common path adds no API write and shared mode stays at 3 writes per request.Out of scope: handlers outliving the lock window would need a background refresh loop, and deleting leftover head locks on shutdown has no
RequestQueueClienthook to attach to (they lapse within the lock TTL anyway).✍️ Drafted by Claude Code