Skip to content

perf: let worker.stream take chunks as they arrive - #1237

Open
ovflowd wants to merge 1 commit into
mainfrom
perf/stream-worker
Open

ovflowd wants to merge 1 commit into
mainfrom
perf/stream-worker

Conversation

@ovflowd

@ovflowd ovflowd commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Description

This PR lets worker.stream take an async iterable of batches as well as an array, starting each batch's work as it arrives. It's the first part of a stack that splits #1205, which lets generators start on their dependency's first chunk instead of waiting for all of it.

  • With a streamed input it keeps only a few chunks per thread in flight (CHUNKS_PER_THREAD, 3), so generators running at once share the worker pool instead of one filling it, and it yields results in the order the chunks were submitted.
  • Piscina runs with stricterFIFO. By default it puts a task it can't hand out yet at the back of its queue, so early heavy tasks like module pages ran last.
  • toBatches gives an input that is whole or streamed as batches, and createSignal is the wake-up the stream waits on.

Nothing passes worker.stream a stream yet, and an input that arrives whole, as it does with one thread, behaves as before.

Validation

Added tests in parallel.test.mjs for the streamed input: the limit on chunks in flight and its refill, the order tasks start in, a failure reading the batches arriving after the chunks before it, a single thread running each batch as it arrives, and no batches. toBatches has its own. Built on 12 of Node.js's API docs at 1 and 4 threads, the output is byte-identical to main's.

Related Issues

Refs: #1205

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run node --run test and all tests passed.
  • I have check code formatting with node --run format:check & node --run lint.
  • I've covered new added functionality with unit tests if necessary.

worker.stream needed all of its input before it started any work. It
now also takes an async iterable of batches, as a streamed input yields
them, and starts each batch's work as it arrives. An input that arrives
whole, as it does with one thread, works as before.

Only a few chunks per thread are started at a time, and results are
yielded in the order the chunks were submitted. That keeps generators
that run at once from filling the pool with their own chunks. Piscina
runs with stricterFIFO for it: by default it puts a task it can't hand
out yet at the back of its queue, so early heavy tasks ran last.

toBatches gives an input that is whole or streamed as batches, and
createSignal is the wake-up the stream waits on.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Sonnet 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
api-docs-tooling Ready Ready Preview Nov 3, 2026 7:35pm UTC

Request Review

@ovflowd
ovflowd added this pull request to stack #1244 November 3, 2026 19:34
@ovflowd ovflowd changed the title perf/stream worker perf: let worker.stream take chunks as they arrive Nov 3, 2026
@codecov

codecov Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86878% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.21%. Comparing base (c8b228e) to head (e677946).

Files with missing lines Patch % Lines
packages/core/src/threading/parallel.mjs 97.97% 3 Missing ⚠️
...ges/core/src/threading/__tests__/parallel.test.mjs 99.53% 1 Missing ⚠️
...kages/core/src/utils/__tests__/generators.test.mjs 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1237      +/-   ##
==========================================
+ Coverage   94.14%   94.21%   +0.06%     
==========================================
  Files         291      291              
  Lines       28950    29366     +416     
  Branches     2944     2985      +41     
==========================================
+ Hits        27256    27666     +410     
- Misses       1672     1678       +6     
  Partials       22       22              

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

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

api-links Generator

Performance estimate (single CI run)

  • Generation time: 3.8% slower (1.05 s → 1.09 s)
  • Peak memory: 1.8% lower (293.49 MB → 288.09 MB)

json Generator

Performance estimate (single CI run)

  • Generation time: 32.9% faster (9.48 s → 6.36 s)
  • Peak memory: 3.0% lower (997.38 MB → 967.73 MB)

legacy-html Generator

Performance estimate (single CI run)

  • Generation time: 38.3% slower (12.36 s → 17.10 s)
  • Peak memory: 5.9% lower (1.52 GB → 1.43 GB)

legacy-json Generator

Performance estimate (single CI run)

  • Generation time: 31.3% slower (7.23 s → 9.49 s)
  • Peak memory: 2.7% higher (934.98 MB → 960.02 MB)

llms-txt Generator

Performance estimate (single CI run)

  • Generation time: 4.2% faster (8.43 s → 8.08 s)
  • Peak memory: 6.1% higher (864.12 MB → 916.88 MB)

orama-db Generator

Performance estimate (single CI run)

  • Generation time: 32.4% faster (8.82 s → 5.96 s)
  • Peak memory: 7.1% higher (992.37 MB → 1.04 GB)

web Generator

Performance estimate (single CI run)

  • Generation time: 6.1% slower (23.90 s → 25.36 s)
  • Peak memory: 0.1% higher (1.48 GB → 1.48 GB)

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅

View logs ↗
No Preview URL. Enable ↗ e677946 2026-11-03T19:34:52.456Z View logs ↗

@ovflowd
ovflowd marked this pull request as ready for review November 3, 2026 20:39
@ovflowd
ovflowd requested a review from a team as a code owner November 3, 2026 20:39
let doneReading = false;

// Notified as chunks start, and once every batch is read
const changed = createSignal();

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.

The waiting queue + settle/start/add + signal feels heavy. Could this just backpressure in one enqueue (await while at the in-flight limit) and wake the consumer with an inline resolvers pair?

}

const chunks = createChunks(items.length, chunkSize);
const batches = toBatches(items);

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.

Nit: streamed ? items : [items] here would drop the need for toBatches.

'@doc-kit/core': minor
---

`worker.stream` accepts an async iterable of batches, starting each one's work as it arrives, as well as an array. It keeps only a few chunks per thread in flight, so generators running at once share the worker pool, starts them in the order they were submitted and yields their results in that order. `toBatches`, from `@doc-kit/core/utils/generators.mjs`, gives an input that is either whole or streamed as batches.

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.

If toBatches goes away, drop the mention from the changeset too.

This branch was successfully deployed

1 active deployment
Preview – api-docs-tooling — e6779464 Deployed Nov 3, 2026 by vercel[bot]
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.

2 participants