Skip to content

Async search iteration yields one event-loop turn per row #3241

Open

Description

@Ethan-Arrowood

Problem

Iterating a search asynchronously (for await (const row of Table.search(...))) yields to the event loop once per row or index entry: each entry resolves through new Promise((resolve) => setImmediate(...)). On main at 5ed056a this applies to:

  • primary-key range scans, filtered and unfiltered (resources/search.ts:1117-1141);
  • filtered secondary-index scans (resources/search.ts:1278-1295, and :1316).

The comment states the intent, "so that scanning queries do not hog resources". But one turn per row costs far more than the work it interleaves. For a 5,000-entry filtered index scan, #3240 measured about 70 ms of wall time and 16 ms of CPU per search, against about 2.5 ms for the same rows read through bounded ranges that do not yield. The count path already batches its yields: COUNT_YIELD_INTERVAL = 2_048 (resources/Table.ts:281, :6074).

So every async scan pays a Promise and an event-loop turn per row, and its latency stretches with every other request in the worker. Nothing reports it.

I found this while benchmarking the ecommerce-store reference application on the bench dataset, where the quote endpoint scans a 5,000-row promotions table on every request (findings). #3240 removes that particular scan for in; this issue covers every scan that still has to run.

Suggested fix

Yield every N entries, reusing COUNT_YIELD_INTERVAL, or once a time budget of about 1-2 ms has passed since the last yield. That keeps the fairness intent at a fraction of the cost. Synchronous iteration is unchanged.

Acceptance

  • An async scan of N rows yields O(N / interval) times, not N. A unit test counts the Immediates (for example with async_hooks).
  • A long scan does not raise the latency of concurrent requests in the same worker beyond the yield budget (a fairness test).

sent with Claude Opus 5.5

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    Priority

    P2

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions