Skip to content

[Fix] Multi-threaded pipe bottleneck #493

Description

@noel-improv

Package

lexical-graph

Problem statement

extract() fans documents out to N workers, then pulls everything back through a single generator chain in the parent (lexical_graph_index.py:498 and :503):

nodes | extraction_pipeline | Pipe(handler.accept) | build_pipeline | sink

Pipe is plain generator composition with no threads, so extraction scales with workers but every document afterwards passes through one stage, one at a time. Eight times the workers returns 1.58x, converging near 33 docs/min.

At 1M documents that works out to roughly 21 days no matter how large the fleet, against a Bedrock quota floor of about 1.5 days.

Evidence

Two runs sit behind this. The 33 docs/min ceiling comes from the worker sweep: 2,000 documents, 2/4/8/16 workers at 16 threads each, on-demand, ml.m5.12xlarge. The rows below come from the single-worker runs: 500 documents, one worker, on-demand, ml.m5.4xlarge. Each run splits into a model phase and a drain phase, and the drain costs about 1.4 s per document.

Variable changed Drain docs/min
16 / 32 / 64 threads 13.10 / 11.43 / 11.72 min 38 / 44 / 43
local disk instead of S3 14.1 min 35.5
Nova 2 Lite instead of Sonnet 4.6 12.7 min 39.4

Removing S3 made it slower, which rules out upload cost. And the drain hardly moves between two models whose extraction phases differ by 2.8x.

Four candidates sit in that stage, none of them timed separately: extraction_monitor (:496, present only when a progress monitor is passed), Pipe(handler.accept), build_pipeline (created with num_workers=1 hardcoded at :490 and running only a NullBuilder), and sink.

Proposed solution

Time the four stages first, which is about ten lines. Two earlier attempts to fix this by reasoning from the code targeted the wrong component. One raised the uploader's effective thread count from the default of 4 to 64 and measured 1.15x end to end, which is real but leaves the drain untouched.

Then, in increasing order of effort:

  1. Raise num_workers on the build pipeline (:490). One line, resting on the untested assumption that it's free.
  2. Give the handler stage a bounded reorder window. It has to yield in order and only once writes are durable.
  3. Remove the funnel entirely so workers write their own output rather than returning documents to the parent. This is the only option that scales with worker count.

Single run per configuration. The 13.10 / 11.43 / 11.72 thread row is a 13% spread with no repeat, so read it as "does not improve" rather than as a flat line. The local-disk and cross-model rows are the stronger evidence: both change the drain by less than the upstream phase they alter.

Activity

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

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions