Skip to content

Commit e2a7a09

Browse files
authored
update indexing benchmarks, make sure the stats are consistent (#138)
1 parent fc9fa33 commit e2a7a09

51 files changed

Lines changed: 12293 additions & 150 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,16 @@ __marimo__/
220220
# benchmark datasets and results (large, generated)
221221
benchmarks/*/datasets/
222222
benchmarks/*/results/
223+
!benchmarks/realtime-data-indexing/results/
224+
benchmarks/realtime-data-indexing/results/.rerun.lock
225+
# Keep raw stdout/stderr logs local; commit only benchmark measurements/reports.
226+
benchmarks/realtime-data-indexing/results/**/*.txt.gz
227+
benchmarks/realtime-data-indexing/results/previous/
228+
benchmarks/realtime-data-indexing/results/diagnostics/
229+
# Local outputs regenerated from the committed measurements.
230+
benchmarks/realtime-data-indexing/results/*-memory.png
231+
benchmarks/realtime-data-indexing/results/table.md
232+
benchmarks/realtime-data-indexing/results/consistency-check.json
223233
benchmarks/*/compat/*.so
224234

225235
# Runtime working directory of `serviette demo` (regenerable: corpus copy,

‎README.md‎

Lines changed: 59 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ organisations such as
7676
[La Poste](https://pathway.com/success-stories/la-poste),
7777
[Transdev](https://pathway.com/success-stories/transdev) and
7878
[CMA CGM](https://pathway.com/success-stories/cma-cgm).
79-
serviette's own numbers are measured, not estimated: indexing
80-
up to 50 GB of text (26 million pages, 17 million files) in constant memory,
81-
retrieval accuracy on a public benchmark (FRAMES), eight vector databases
82-
under integration tests against real instances — all reproducible from this
83-
repository.
79+
serviette's benchmarks cover indexing up to 50 GB of text (26 million pages,
80+
17 million files), with indexer memory measured throughout the run, and
81+
retrieval accuracy on a public benchmark (FRAMES). Eight vector databases
82+
are under integration tests against real instances. Scripts, recorded results
83+
and measurement limitations are available in this repository.
8484

8585
**Where it stands today.** It answers one question at a time, from the
8686
current state of the documents. Conversation memory, user accounts and a
@@ -207,62 +207,67 @@ every byte below is extracted text (a PDF collection with the same text
207207
content would weigh several times more) —
208208
see [benchmarks/realtime-data-indexing](https://github.com/pathwaycom/serviette/tree/main/benchmarks/realtime-data-indexing):
209209

210-
| corpus | ≈ pages | files | chunks | indexing time | peak memory (PSS) | in Qdrant |
210+
| corpus target | ≈ pages | files | chunks (approx.) | indexing time | peak memory (PSS, GiB) | in Qdrant (GiB) |
211211
|---|---|---|---|---|---|---|
212-
| 100 MB | 52 000 | 12 969 | 66 136 | 39 s | 6.6 GB | 0.6 GB |
213-
| 1 GB | 524 000 | 240 516 | 836 595 | 4.8 min | 6.9 GB | 2.2 GB |
214-
| 3 GB | 1 573 000 | 841 890 | 2 703 850 | 15 min | 7.3 GB | 6.0 GB |
215-
| 10 GB | 5 243 000 | 3 423 359 | 10 093 514 | 58 min | 7.7 GB | 20.8 GB |
216-
| 30 GB | 15 729 000 | 9 202 620 | 29 817 294 | 2.9 h | 10.1 GB | 61.3 GB |
217-
| 50 GB | 26 214 000 | 17 083 603 | 53 913 774 | 5.5 h | 13.1 GB | 107.9 GB |
218-
219-
Documents flow through the pipeline rather than accumulating in it, so
220-
what stays in memory is short and worth spelling out.
221-
222-
**Grows with the corpus — one thing.** The file-watch index: to detect live
223-
edits and deletions, the indexer keeps a record (path, mtime, size, owner)
224-
per watched file. Measured cost: **~318 bytes per file** (paths of typical
225-
length; ±20% with the hash-table's load factor), verified from 13 thousand
226-
to 17 million files (right-hand plot: six corpus sizes against one fitted
227-
line). It scales with the *number of files*, not bytes: the same corpus
228-
packed into fewer, larger files costs proportionally less.
229-
230-
**Constant, regardless of corpus size.** The embedding stack (PyTorch
231-
runtime + model, per worker), the engine baseline (~200 MB per process),
232-
connector machinery (~0.4 GB), and working buffers that reach a plateau in
233-
the first minutes of a run and stay there — identical on 3 GB and 10 GB.
234-
235-
**On disk, not in memory.** Parsed-text cache, persistence snapshots, and
236-
the embeddings themselves (in the vector database). That is why the curves
237-
plateau: a **500× larger corpus costs 2.5× the memory** — and the growth
238-
that remains is the file-watch index above, i.e. the corpus in fewer files
239-
would cost less. Indexing time scales linearly with bytes throughout.
212+
| 100 MB | 52 431 | 12 969 | 53 651 | 44 s | 6.86 | 0.61 |
213+
| 1 GB | 524 288 | 240 516 | 604 299 | 4.9 min | 6.82 | 1.85 |
214+
| 3 GB | 1 572 864 | 841 890 | 1 888 846 | 15.4 min | 7.18 | 4.89 |
215+
| 10 GB | 5 242 884 | 3 423 359 | 6 761 214 | 1.0 h | 8.16 | 16.07 |
216+
| 30 GB | 15 728 646 | 9 202 620 | 20 933 962 | 3.1 h | 10.40 | 48.57 |
217+
| 50 GB | 26 214 382 | 17 083 603 | 37 472 786 | 6.0 h | 14.65 | 84.45 |
218+
219+
The table and both figures come from the same six runs. Raw summaries,
220+
memory samples, per-process snapshots and resolved configurations are in
221+
[results](benchmarks/realtime-data-indexing/results/README.md).
222+
One final run per size is published; run-to-run variability was not measured.
223+
Memory and disk sizes use GiB; input labels are nominal targets
224+
(1 GB = 1,000 MiB of extracted text). A page is approximately 2,000 bytes.
225+
Chunks are Qdrant's approximate count at completion; the per-size curves
226+
retain the maximum observed count used by the completion detector.
227+
228+
Documents flow through the pipeline rather than accumulating in it.
229+
The file-watch index retains metadata per watched file to detect edits and
230+
deletions, so its cost depends on file count. The right panel estimates this
231+
cost from the connector worker's extra private memory: the fitted slope is
232+
**~361 bytes per file**. This proxy also includes unequal worker
233+
buffers; it does not isolate individual allocations.
234+
235+
The embedding runtime, engine and working buffers account for the worker
236+
baseline. The curves show how total memory evolves during indexing,
237+
including any steps as file-watch metadata and buffers grow.
238+
Parsed-text cache, persistence snapshots and stored embeddings live on disk.
239+
Across these runs a **500× larger input target costs 2.1× the peak PSS**.
240+
Both file count and text volume change between sizes; this comparison does
241+
not establish constant memory usage.
240242

241243
<p align="center">
242-
<img src="https://raw.githubusercontent.com/pathwaycom/serviette/main/docs/assets/bench-memory.png" alt="Left: indexer PSS over time for corpora from 100 MB to 50 GB, from the run before the allocator tuning; every curve reaches its plateau in the first minutes and holds it for hours. Right: connector-worker extra memory across six corpus sizes follows ~318 bytes per watched file" width="100%">
244+
<img src="docs/assets/bench-memory.png" alt="Measured indexer PSS over time for all six corpora; legends use the same peaks as the table. Right: estimated file-watch memory versus file count, fitted at approximately 361 bytes per file." width="100%">
243245
</p>
244-
<p align="center"><em>Curves from the run <strong>before</strong> the allocator tuning (peaks 6.6–16.4 GB); the table above has the final numbers from the tuned rerun (6.6–13.1 GB). The shape is the point: each curve reaches its plateau in the first minutes and holds it for the rest of the run — 5.5 hours for 50 GB.</em></p>
245-
246-
The peak itself is dominated by the embedding stack, not the engine — a
247-
Pathway worker process is ~200 MB; the rest is the price of running
248-
embeddings locally (8 × PyTorch runtime + model), i.e. of paying no
249-
per-token API fees. Fewer workers or an API embedder shrink it accordingly.
246+
<p align="center"><em>All curves and table values come from the same runs with allocator background purging enabled. File-watch memory is estimated from rank-0 private-memory excess over the median of the other workers.</em></p>
250247

251248
<p align="center">
252-
<img src="https://raw.githubusercontent.com/pathwaycom/serviette/main/docs/assets/bench-memory-breakdown.png" alt="Breakdown of the 10 GB corpus peak as measured in the run before the allocator tuning (8.8 GB): three quarters is the local PyTorch embedding stack across 8 workers; file-watch metadata is about 1.1 GB; supervisors and shared code make up the rest" width="85%">
249+
<img src="docs/assets/bench-memory-breakdown.png" alt="Breakdown at the 10 GB run's 8.16 GiB PSS peak: estimated local-embedder private and shared memory, estimated file-watch memory, remaining private memory and other shared pages." width="85%">
253250
</p>
254-
<p align="center"><em>Same earlier run (8.8 GB peak on the 10 GB corpus; 7.7 GB after tuning). The proportions are what matters here: the embedding stack dominates, the engine and the file-watch index are small.</em></p>
255-
256-
Memory is measured as PSS (proportional set size) summed over the container:
257-
shared pages — e.g. the PyTorch libraries mapped by every worker — are
258-
counted once, not once per process. Setup: 96-core CPU host, streaming mode,
259-
8 worker processes, local `static-retrieval-mrl-en-v1` embeddings (no API
260-
calls; Matryoshka-truncated to 256 dims), 512-token chunks, Qdrant, and
261-
jemalloc's `background_thread` purging enabled in the indexer containers
262-
(measured free; it keeps idle workers from retaining freed pages). Numbers
263-
were measured on a nightly Pathway build whose engine matches the released
264-
wheel (pathway ≥ 0.32.1 — what the benchmark's docker image and the
265-
Development section install), so they are reproducible as-is.
251+
<p align="center"><em>Attribution at the sampled 10 GB peak. Local-embedder memory is estimated from a separate eight-process initialization diagnostic and library mappings; libraries already needed without the embedder remain in the residual. The 3.27 GiB estimate without the local embedder is calculated by subtraction, not measured in an external-embedder indexing run. It excludes the embedding service and does not measure client overhead or changes in working buffers. Shared pages are accounted for proportionally through PSS.</em></p>
252+
253+
Setup: 96-logical-CPU host (see the saved CPU model),
254+
streaming mode, 8 worker processes with 12 OpenMP threads each,
255+
local `static-retrieval-mrl-en-v1` embeddings, Matryoshka-truncated to 256 dims,
256+
512-token chunks, Qdrant, and jemalloc `background_thread:true`.
257+
No embedding API calls. Installed versions: serviette 0.1.7,
258+
Pathway 0.33.0, PyTorch 2.14.1,
259+
sentence-transformers 6.1.0.
260+
Peak memory is the maximum sampled PSS summed over the indexer container.
261+
Qdrant and API-server memory, unmapped filesystem cache and kernel memory
262+
are outside that measurement; it is not a RAM requirement for the whole stack.
263+
Brief peaks between samples may be missed.
264+
265+
Indexing time is an estimate ending at the last increase in the running
266+
maximum of Qdrant's approximate chunk count. That moment can precede the
267+
final write; output-completeness checks do not revise the timestamp or
268+
measure when the backend's search index is ready. The recorded environment,
269+
cache conditions and limits on reproducing it are described in
270+
[the measurement notes](benchmarks/realtime-data-indexing/results/README.md).
266271

267272
### Retrieval accuracy (FRAMES)
268273

0 commit comments

Comments
 (0)