Skip to content

perf(memory-storage): avoid O(n) deque scan on forefront re-add#2074

Open
anxkhn wants to merge 2 commits into
apify:masterfrom
anxkhn:perf/memory-rq-forefront-reposition
Open

perf(memory-storage): avoid O(n) deque scan on forefront re-add#2074
anxkhn wants to merge 2 commits into
apify:masterfrom
anxkhn:perf/memory-rq-forefront-reposition

Conversation

@anxkhn

@anxkhn anxkhn commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

MemoryRequestQueueClient.add_batch_of_requests repositioned an already-pending request with deque.remove, making each forefront re-add O(n) and a batch of re-adds O(n²).

This change replaces the pending deque with an OrderedDict keyed by request unique key. Forefront re-adds now reposition requests in constant time without creating stale entries, while regular re-adds preserve position and update the stored request object. Reclaimed request updates are also reflected in the lookup index, and is_empty remains side-effect free.

Forefront LIFO ordering, regular FIFO ordering, deduplication, and metadata counts are preserved.

This is the memory-client counterpart to #2011.

Issues

Testing

  • Added coverage for repeated forefront re-adds without pending storage growth.
  • Added coverage for ordering, deduplication, regular re-add updates, and reclaiming a modified request after forefront repositioning.
  • uv run ruff format --check src/crawlee/storage_clients/_memory/_request_queue_client.py tests/unit/storage_clients/_memory/test_memory_rq_client.py
  • uv run ruff check src/crawlee/storage_clients/_memory/_request_queue_client.py tests/unit/storage_clients/_memory/test_memory_rq_client.py
  • uv run poe type-check
  • uv run pytest tests/unit/storage_clients/_memory/test_memory_rq_client.py tests/unit/storages/test_request_queue.py tests/unit/storages/test_request_manager_tandem.py (300 passed, 2 skipped)

Checklist

  • CI passed

vdusek

This comment was marked as outdated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR optimizes the in-memory RequestQueue client by eliminating the O(n) deque.remove scan when re-adding already-pending requests with forefront=True, replacing it with an appendleft + “lazy stale entry” skipping approach in fetch_next_request() / is_empty().

Changes:

  • Reworked MemoryRequestQueueClient.add_batch_of_requests() so forefront re-adds of pending requests no longer scan/remove from the pending deque.
  • Added stale-entry skipping in fetch_next_request() and front-pruning in is_empty() to support the new “tombstone” behavior.
  • Added regression tests to guard the performance fix and ordering/dedup semantics.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/crawlee/storage_clients/_memory/_request_queue_client.py Implements tombstone-based forefront repositioning and stale-entry skipping/pruning logic.
tests/unit/storage_clients/_memory/test_memory_rq_client.py Adds regression tests validating no deque.remove usage and preserves order/dedup semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/crawlee/storage_clients/_memory/_request_queue_client.py Outdated
Comment thread src/crawlee/storage_clients/_memory/_request_queue_client.py Outdated
Comment thread src/crawlee/storage_clients/_memory/_request_queue_client.py Outdated
Comment thread src/crawlee/storage_clients/_memory/_request_queue_client.py Outdated
Comment thread src/crawlee/storage_clients/_memory/_request_queue_client.py Outdated
Comment thread tests/unit/storage_clients/_memory/test_memory_rq_client.py
Comment thread tests/unit/storage_clients/_memory/test_memory_rq_client.py Outdated
anxkhn added 2 commits July 16, 2026 11:10
`MemoryRequestQueueClient.add_batch_of_requests` repositioned an
already-pending request to the forefront with `deque.remove`, a linear
scan of the whole pending deque. Re-adding a batch of K already-pending
requests to a deque of N was O(K*N). Forefront re-adds are a real path:
tiered-proxy retries set `request.forefront = True` and re-enqueue while
the request is still pending.

The forefront reposition now just `appendleft`s the request and leaves
the old entry in place. The new object is registered in
`_requests_by_unique_key`, superseding the old one, and
`fetch_next_request` and `is_empty` skip entries whose object is no
longer the one registered for their unique key. Because a forefront
reposition always enqueues the live copy ahead of the entry it
supersedes, `is_empty` can safely prune stale entries from the front.

Only forefront re-adds create such tombstones. A regular re-add of an
already-pending request leaves both the deque entry and its registered
object untouched, so the identity check never misfires and no request is
dropped. Forefront-LIFO / regular-FIFO order and deduplication are
unchanged.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Replace lazy deque tombstones with an ordered mapping so forefront repositioning remains constant-time without stale-entry growth. Keep request updates on regular re-adds, update reclaimed request instances in the index, and leave is_empty side-effect free.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
@anxkhn
anxkhn force-pushed the perf/memory-rq-forefront-reposition branch from d2ca2a0 to 02429c4 Compare July 16, 2026 05:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants