Write & query performance (Beta)

The honest version first: for raw indexed search and filtering, a specialized engine like Elasticsearch (or a native C++ columnar store) will be faster than Aito, and we don't try to beat them at it. Aito's job on this axis is to be fast enough to serve interactive queries while carrying a prediction layer those engines don't have. This page states where Aito actually lands β€” including a direct Elasticsearch head-to-head.

What we measure

  • Query / search latency β€” filter and full-text queries over live data.
  • Predict / recommend / relate latency β€” the inference layer; there is no equivalent in a search engine, so it's an Aito-only number, not a comparison.
  • Write / ingest β€” how fast new rows become queryable.
  • Scaling β€” how each of the above moves as the table grows (measured 1k β†’ 100k today, with the 10M point to follow).

Query & search latency

Filter and full-text queries resolve out of the memory-mapped inverted index. Here is a direct head-to-head against Elasticsearch 8.15 on identical data and queries β€” the same 100k flat invoice rows loaded into Aito v1, Aito v2 and Elasticsearch, each queried through one shared HTTP client so client overhead is identical. Warm p50 latency (all three return the same hit counts):

QueryAito v1Aito v2Elasticsearch
Exact filter (company = N)10 ms9 ms3 ms
Full-text (description match)7 ms35 ms3 ms
Filter + full-text5 ms6 ms3 ms

Read it straight: Elasticsearch is fastest on every query β€” a couple of milliseconds, roughly 2–3Γ— quicker than Aito on filters. That is the expected result and exactly the one we promised β€” a specialized search engine beats us on raw indexed search. Aito still answers in single-digit to low-double-digit milliseconds β€” interactive β€” over the same index it uses for prediction. (Aito v2's full-text row is the outlier; the rep2 engine's full-text scan is a known beta slow spot, tracked separately β€” v1's full-text stays fast.)

The point isn't the milliseconds, it's that Aito stays interactive on the search axis and carries the calibrated-prediction layer a search engine doesn't have β€” see the comparison that matters. These are single-machine committed baselines (Aito in-JVM, ES in Docker on the same host); read the ratio, not the absolute milliseconds.

Prediction latency β€” v2 vs v1 (the part search engines don't have)

This is where the milliseconds buy something a search index can't do at all β€” a calibrated, explained prediction over your live data, no model to train or serve. Here is the v2 (rep2 / CollectionDb) engine measured head-to-head against the production v1 (TableDb) engine on the 10M-row linked-invoice benchmark (predicting processor / acceptor / glCode), warm steady state:

Predict targetv2 accuracyv2 latencyv1 accuracyv1 latency
acceptor0.88~145 ms0.88~157 ms
glCode0.72~106 ms0.81~110 ms
processor0.56~426 ms0.69~202 ms

On acceptor, v2 already matches the mature engine's accuracy and is a touch faster; on glCode it's close on both; processor β€” a high-cardinality cross-table link prediction β€” is the one target where v2 still trails on both. That path runs the cross-table reexpression (whitening) step v1 has heavily tuned and v2 hasn't yet, so it's the concentrated, understood gap, not an engine-wide one. Each predict ranks hundreds of candidates and returns a $why factor tree.

Warm-up β€” first query, cold

The first query per target pays JIT, file-cache miss and dictionary first-touch. v2's memory-mapped columnar reads make cold-start markedly faster on the two lower-cardinality targets (no big heap structures to materialize on first touch); processor is again the exception:

Predict targetv2 first queryv1 first query
acceptor~540 ms~2009 ms
glCode~317 ms~973 ms
processor~9108 ms~6448 ms

Memory & disk

v2 is built lean, which matters as much as speed when packing many tenants on a box:

  • Memory-mapped, zero-allocation reads β€” a lookup reads straight out of the mapped column file, not a heap-materialized structure, so the resident footprint for a given dataset is much lower than the row-oriented engine's.
  • Compact on disk β€” a binary columnar format with adaptive byte widths; the linkage structure alone is ~2.6Γ— smaller than the naΓ―ve layout.
  • Cheaper to build β€” rebuilding a table's optimized state uses ~6Γ— less working memory (and is ~2Γ— faster) than the previous path.

Β«verifyΒ» The ~2.6Γ— / ~6Γ— figures are committed component-level measurements, not a whole-engine "v2 used X MB / Y GB vs v1" head-to-head. Measure resident memory and on-disk size for the same dataset under both engines before publishing a single memory/disk headline.

Scaling

Predict latency stays sub-linear in the data: two orders of magnitude more rows cost well under a doubling of latency. Warm steady-state predict latency (mean across the processor, acceptor and GL-code targets) on the linked-invoice benchmark, measured at three scales:

RowsWarm predict latency (mean of 3 targets)
1k203 ms
10k273 ms
100k328 ms

From 1k to 100k β€” 100Γ— the data β€” latency rises only a fraction: the columnar, memory-mapped layout amortizes per-query overhead better at higher row counts, so the cost of a prediction is dominated by the candidate ranking, not by scanning the table. That's why the gap to a row-oriented engine widens in Aito's favour as data grows rather than shrinking. Query latency scales with index size as expected.

These figures are a committed baseline measured on a single dev machine β€” read the shape (sub-linear), not the absolute milliseconds, which move with hardware. The numbers regenerate from the InvoicePerf harness at docs-build time (invoice-table-* metrics), so they can't silently drift. The 10M point and a paired v2-engine column are the next additions; the v2 predict path is currently heavier (its layered cursor stack is the active tuning target), so this table reports the mature v1 engine until the v2 multi-scale run is in.

Writes

Writes are incremental: inserted rows are queryable and usable as prediction evidence immediately β€” there is no separate reindex or model-retraining step (contrast the accuracy suites, where every ML baseline needs a full refit to absorb new data). Batched ingest uses a back-pressured write path with deferred segment merging so read latency stays stable under load.

Measured v2 ingest on the linked-invoice benchmark (batched 2,000-row commits through /api/v2):

Metricv2 ingest
Throughput21,716 rows/s
Per-commit latency (p50)90 ms
Per-commit latency (p90)107 ms

Deferred segment merging β€” the default write path β€” roughly triples ingest throughput versus eager merging (measured ~2.85Γ— in the committed A/B) by batching the index rebuild instead of paying it on every commit.

Like the scaling table, these are single-machine committed baselines generated from the BookInvoiceIngestBench snapshot β€” read the magnitude (tens of thousands of rows/s, sub-100 ms commits), not the exact figure. Write throughput is markedly more sensitive to machine load than query latency: on a contended box the same ingest measures an order of magnitude slower, so the baseline is captured on an unloaded run.

The comparison that matters

Against a specialized search engine, expect Aito to be competitive but not category-leading on raw query speed, and incomparable on prediction β€” because the search engine simply doesn't predict. The evaluation you're really making isn't "Aito vs Elasticsearch on QPS"; it's "one system that searches and predicts over live data with no training pipeline" vs "a fast search engine bolted to a separately-trained, separately-served ML stack." On the first axis you trade some raw speed; on the second you remove an entire moving part.

Beta. The query/Elasticsearch, scaling and write-throughput tables are generated from committed InvoicePerf / BookInvoiceIngestBench / BookInvoiceEsBench metrics and regenerate at docs-build time, so they can't drift. The per-target prediction-latency figures are the last still transcribed from a 10M run; moving them onto the same generated path is the remaining item.