@soulcraft/cor 3.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +16 -0
- package/README.md +328 -0
- package/dist/aggregation/NativeAggregationEngine.d.ts +118 -0
- package/dist/aggregation/NativeAggregationEngine.js +186 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +236 -0
- package/dist/graph/GraphAccelerationAdapter.d.ts +153 -0
- package/dist/graph/GraphAccelerationAdapter.js +326 -0
- package/dist/graph/NativeGraphAdjacencyIndex.d.ts +286 -0
- package/dist/graph/NativeGraphAdjacencyIndex.js +944 -0
- package/dist/hnsw/AdaptiveDiskAnnModeSelector.d.ts +187 -0
- package/dist/hnsw/AdaptiveDiskAnnModeSelector.js +313 -0
- package/dist/hnsw/NativeDiskAnnWrapper.d.ts +489 -0
- package/dist/hnsw/NativeDiskAnnWrapper.js +1456 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +29 -0
- package/dist/legacyLayoutGuard.d.ts +93 -0
- package/dist/legacyLayoutGuard.js +237 -0
- package/dist/license/constants.d.ts +30 -0
- package/dist/license/constants.js +30 -0
- package/dist/license/onlineValidator.d.ts +43 -0
- package/dist/license/onlineValidator.js +225 -0
- package/dist/license/types.d.ts +39 -0
- package/dist/license/types.js +5 -0
- package/dist/license.d.ts +64 -0
- package/dist/license.js +165 -0
- package/dist/migration/MigrationCoordinator.d.ts +171 -0
- package/dist/migration/MigrationCoordinator.js +249 -0
- package/dist/migration/indexEpochGuard.d.ts +163 -0
- package/dist/migration/indexEpochGuard.js +202 -0
- package/dist/native/NativeEmbeddingEngine.d.ts +79 -0
- package/dist/native/NativeEmbeddingEngine.js +318 -0
- package/dist/native/NativeRoaringBitmap32.d.ts +114 -0
- package/dist/native/NativeRoaringBitmap32.js +221 -0
- package/dist/native/ffi.d.ts +20 -0
- package/dist/native/ffi.js +48 -0
- package/dist/native/idMapperTestSupport.d.ts +67 -0
- package/dist/native/idMapperTestSupport.js +112 -0
- package/dist/native/index.d.ts +49 -0
- package/dist/native/index.js +87 -0
- package/dist/native/napi.d.ts +21 -0
- package/dist/native/napi.js +88 -0
- package/dist/native/types.d.ts +1298 -0
- package/dist/native/types.js +16 -0
- package/dist/plugin.d.ts +50 -0
- package/dist/plugin.js +388 -0
- package/dist/providerContracts.d.ts +277 -0
- package/dist/providerContracts.js +38 -0
- package/dist/resource/OsMemoryProbe.d.ts +175 -0
- package/dist/resource/OsMemoryProbe.js +206 -0
- package/dist/resource/ResourceManager.d.ts +491 -0
- package/dist/resource/ResourceManager.js +960 -0
- package/dist/utils/ColumnManifest.d.ts +97 -0
- package/dist/utils/ColumnManifest.js +129 -0
- package/dist/utils/NativeColumnStore.d.ts +284 -0
- package/dist/utils/NativeColumnStore.js +685 -0
- package/dist/utils/NativeMetadataIndex.d.ts +882 -0
- package/dist/utils/NativeMetadataIndex.js +2631 -0
- package/dist/utils/NativeUnifiedCache.d.ts +87 -0
- package/dist/utils/NativeUnifiedCache.js +273 -0
- package/dist/utils/binaryIdMapperFactory.d.ts +59 -0
- package/dist/utils/binaryIdMapperFactory.js +94 -0
- package/dist/utils/collation.d.ts +30 -0
- package/dist/utils/collation.js +40 -0
- package/dist/utils/columnStoreTypes.d.ts +161 -0
- package/dist/utils/columnStoreTypes.js +29 -0
- package/dist/utils/nativeBinaryEntityIdMapper.d.ts +211 -0
- package/dist/utils/nativeBinaryEntityIdMapper.js +381 -0
- package/dist/utils/nativeEntityIdMapper.d.ts +111 -0
- package/dist/utils/nativeEntityIdMapper.js +170 -0
- package/docs/ADR-002-diskann-100-percent-rust.md +294 -0
- package/docs/ADR-003-semantic-time-travel.md +100 -0
- package/docs/aggregation.md +251 -0
- package/docs/comparison.md +162 -0
- package/docs/deployment-limits.md +87 -0
- package/docs/diskann.md +184 -0
- package/docs/migration-3.0.md +396 -0
- package/docs/performance-budget.md +274 -0
- package/docs/performance.md +117 -0
- package/docs/scaling.md +439 -0
- package/docs/snapshot-safety.md +246 -0
- package/docs/u64-id-space.md +214 -0
- package/docs/verification-report.md +670 -0
- package/native/brainy-native.node +0 -0
- package/native/index.d.ts +3916 -0
- package/package.json +92 -0
- package/scripts/migrate-cortex-2x-to-3x.mjs +970 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Performance Budget
|
|
3
|
+
slug: cor/performance-budget
|
|
4
|
+
public: true
|
|
5
|
+
category: cor
|
|
6
|
+
template: concept
|
|
7
|
+
order: 11
|
|
8
|
+
description: The latency budget cor commits to for find() queries at 1M, 10M, and 100M scale, enforced by CI regression gates and grounded in the triple-intelligence workload that defines the brainy + cor stack.
|
|
9
|
+
next:
|
|
10
|
+
- cor/snapshot-safety
|
|
11
|
+
- cor/verification-report
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Performance Budget
|
|
15
|
+
|
|
16
|
+
This document is a contract. The latency targets below are
|
|
17
|
+
CI-enforced regression gates, not aspirations. Code that fails a
|
|
18
|
+
gate does not ship.
|
|
19
|
+
|
|
20
|
+
## Why a budget matters
|
|
21
|
+
|
|
22
|
+
Brainy + cor is the only database that answers a single query
|
|
23
|
+
combining:
|
|
24
|
+
|
|
25
|
+
- **Metadata filter** — structured `where` over indexed fields
|
|
26
|
+
(LSM metadata index with Roaring bitmap evaluation)
|
|
27
|
+
- **Graph traversal** — multi-hop relationship walks with verb-type
|
|
28
|
+
selectivity (graph adjacency LSM with verb endpoint lookups)
|
|
29
|
+
- **Vector similarity** — semantic search over the entire corpus
|
|
30
|
+
(Adaptive DiskANN: Mode 1 for in-memory recall, hybrid for
|
|
31
|
+
PQ-compressed billion-scale)
|
|
32
|
+
|
|
33
|
+
No pure vector database (Pinecone, Weaviate, Qdrant), pure graph
|
|
34
|
+
database (Neo4j, Memgraph), or document database (MongoDB,
|
|
35
|
+
Elasticsearch) does this. PostgreSQL with pgvector gets two of
|
|
36
|
+
three with a multi-second join cost. The combined query is the
|
|
37
|
+
differentiator and it is only differentiating if it is sub-
|
|
38
|
+
millisecond at billion scale.
|
|
39
|
+
|
|
40
|
+
The budget locks in that property as a release criterion.
|
|
41
|
+
|
|
42
|
+
## The budget
|
|
43
|
+
|
|
44
|
+
All measurements are p50 / p99 over 100 queries on a single 32-
|
|
45
|
+
core box (workstation tier: i9-14900HX / 64 GB / NVMe Gen 4;
|
|
46
|
+
billion-tier: bxl9000 / Ryzen 9 7950X3D / 184 GB / NVMe). Queries
|
|
47
|
+
are drawn from a representative workload mix described in
|
|
48
|
+
*Methodology* below.
|
|
49
|
+
|
|
50
|
+
### Read path budget
|
|
51
|
+
|
|
52
|
+
| Query shape | 1 M entities | 10 M | 100 M |
|
|
53
|
+
|---|---:|---:|---:|
|
|
54
|
+
| Filter-only `find({where})` p50 | ≤ 0.5 ms | ≤ 1 ms | ≤ 3 ms |
|
|
55
|
+
| Filter-only p99 | ≤ 2 ms | ≤ 5 ms | ≤ 15 ms |
|
|
56
|
+
| Vector-only `find({vector})` p50 | ≤ 1 ms | ≤ 2 ms | ≤ 5 ms |
|
|
57
|
+
| Vector-only p99 | ≤ 3 ms | ≤ 6 ms | ≤ 15 ms |
|
|
58
|
+
| Hybrid `find({where, vector})` p50 | ≤ 2 ms | ≤ 4 ms | ≤ 10 ms |
|
|
59
|
+
| Hybrid p99 | ≤ 8 ms | ≤ 15 ms | ≤ 30 ms |
|
|
60
|
+
| 1-hop `find({where, hop})` p50 | ≤ 1.5 ms | ≤ 3 ms | ≤ 8 ms |
|
|
61
|
+
| 2-hop `find({where, hop, hop})` p50 | ≤ 3 ms | ≤ 6 ms | ≤ 15 ms |
|
|
62
|
+
| 2-hop p99 | ≤ 12 ms | ≤ 25 ms | ≤ 50 ms |
|
|
63
|
+
| Hybrid + 2-hop p50 | ≤ 5 ms | ≤ 10 ms | ≤ 25 ms |
|
|
64
|
+
|
|
65
|
+
### Cross-subsystem ID resolution budget
|
|
66
|
+
|
|
67
|
+
`find()` returns UUID strings to JS, so every result row pays the
|
|
68
|
+
int→UUID resolution. Batched APIs are mandatory; per-call APIs are
|
|
69
|
+
not part of the hot path.
|
|
70
|
+
|
|
71
|
+
| Operation | Budget |
|
|
72
|
+
|---|---:|
|
|
73
|
+
| `getOrAssignBatch(uuids, len=1K)` | ≤ 100 μs |
|
|
74
|
+
| `intsToUuidsBatch(ints, len=1K)` | ≤ 80 μs |
|
|
75
|
+
| `intsToUuidsBatch(ints, len=10K)` | ≤ 500 μs |
|
|
76
|
+
| `getNeighborsBatch(int_ids, len=1K)` | ≤ 150 μs |
|
|
77
|
+
| `getVerbsBatch(verb_ids, len=1K)` | ≤ 120 μs |
|
|
78
|
+
|
|
79
|
+
### Write path budget
|
|
80
|
+
|
|
81
|
+
| Operation | 1 M | 10 M | 100 M |
|
|
82
|
+
|---|---:|---:|---:|
|
|
83
|
+
| Single `add(noun)` p50 | ≤ 0.3 ms | ≤ 0.3 ms | ≤ 0.5 ms |
|
|
84
|
+
| Single `add(verb)` p50 | ≤ 0.2 ms | ≤ 0.2 ms | ≤ 0.3 ms |
|
|
85
|
+
| Batched `add` (1K) p50 | ≤ 20 ms | ≤ 20 ms | ≤ 30 ms |
|
|
86
|
+
| Sustained insert throughput (32-core box) | ≥ 50 K/s | ≥ 30 K/s | ≥ 20 K/s |
|
|
87
|
+
|
|
88
|
+
### Recall floor
|
|
89
|
+
|
|
90
|
+
Recall is a correctness property, but it has a budget too: a query
|
|
91
|
+
that returns the wrong answer in 0.5 ms is worse than one that
|
|
92
|
+
takes 1 ms and returns the right one.
|
|
93
|
+
|
|
94
|
+
| Workload | Mode | Recall@10 floor |
|
|
95
|
+
|---|---|---:|
|
|
96
|
+
| Production embeddings (dim ≥ 384, clustered) | Mode 1 in-memory | ≥ 0.97 |
|
|
97
|
+
| Production embeddings | Mode 2 hybrid (PQ16) | ≥ 0.95 |
|
|
98
|
+
| SIFT canonical (dim 128, BIGANN ground truth) | Mode 1 in-memory | ≥ 0.98 |
|
|
99
|
+
| SIFT canonical | Mode 2 hybrid | ≥ 0.55 (PQ16 is adversarial here — operators tune via `recall` preset) |
|
|
100
|
+
|
|
101
|
+
## How the budget is enforced
|
|
102
|
+
|
|
103
|
+
Every gate in the table corresponds to a regression test in
|
|
104
|
+
`src/native/findPerfGates.test.ts` (to be added; tracked under
|
|
105
|
+
the Cor 3.0 release plan). The test pattern follows the
|
|
106
|
+
existing `lsmPerfGates.test.ts`:
|
|
107
|
+
|
|
108
|
+
1. Build a corpus at the target scale with a representative
|
|
109
|
+
field distribution (50 fields, mixed cardinality, ~10 verbs
|
|
110
|
+
per noun, dim 384 vectors with clustered structure).
|
|
111
|
+
2. Warm the page cache with one pass over the query set.
|
|
112
|
+
3. Run 100 queries per shape, record p50 and p99.
|
|
113
|
+
4. Assert p50 ≤ budget × 1.0 and p99 ≤ budget × 1.2 (20 %
|
|
114
|
+
headroom on p99 absorbs CI variance).
|
|
115
|
+
|
|
116
|
+
A red gate is a release blocker. The test prints the measured
|
|
117
|
+
distribution so the regression is bisectable to a commit.
|
|
118
|
+
|
|
119
|
+
## Methodology
|
|
120
|
+
|
|
121
|
+
### Workload mix
|
|
122
|
+
|
|
123
|
+
The representative workload is a 70 / 20 / 10 split:
|
|
124
|
+
|
|
125
|
+
- 70 % filter-driven queries (`where` with 1-3 predicates,
|
|
126
|
+
selectivity from 0.1 % to 10 %)
|
|
127
|
+
- 20 % vector-driven queries (vector search with optional
|
|
128
|
+
metadata pre-filter)
|
|
129
|
+
- 10 % graph-driven queries (1- or 2-hop walks with verb-type
|
|
130
|
+
selectivity)
|
|
131
|
+
|
|
132
|
+
The mix tracks the access pattern Soulcraft Memory observes in
|
|
133
|
+
production: most queries are structured browse / filter; vector
|
|
134
|
+
search is the discovery path; graph hops are the relationship
|
|
135
|
+
expansion.
|
|
136
|
+
|
|
137
|
+
### Hardware floor
|
|
138
|
+
|
|
139
|
+
The budget is anchored on the cor 3.0 design floor — a 32-
|
|
140
|
+
thread Intel Core i9-14900HX with 64 GB DDR5 and NVMe Gen 4 SSD.
|
|
141
|
+
That's roughly a $2K commodity workstation; production targets
|
|
142
|
+
sit on top of it. The bxl9000 measurements (Ryzen 9 7950X3D, 184
|
|
143
|
+
GB) are the billion-tier ceiling. Numbers from cloud VMs are
|
|
144
|
+
typically 1.3–1.8× slower for the same work because of memory
|
|
145
|
+
bandwidth and NVMe queue depth.
|
|
146
|
+
|
|
147
|
+
### What "p50" and "p99" mean here
|
|
148
|
+
|
|
149
|
+
- **p50** is the median measurement over 100 query repetitions
|
|
150
|
+
with a warm page cache. It represents the steady-state cost.
|
|
151
|
+
- **p99** is the 99th percentile and represents the worst typical
|
|
152
|
+
query — page-cache miss, garbage collection pause, or a
|
|
153
|
+
background msync.
|
|
154
|
+
- We do not publish a p99.9 budget. p99.9 measurements are
|
|
155
|
+
dominated by external noise (kernel scheduling, NVMe firmware
|
|
156
|
+
latency spikes) and are not reproducible across runs.
|
|
157
|
+
|
|
158
|
+
## The hot paths inside `find()`
|
|
159
|
+
|
|
160
|
+
A unified `find({where, vector, hop, orderBy, limit, offset})`
|
|
161
|
+
query executes against three cor subsystems in sequence. Each
|
|
162
|
+
subsystem has a sub-budget that rolls into the totals above.
|
|
163
|
+
|
|
164
|
+
1. **Filter pushdown** — `metadataIndex.getIdsForFilter(field,
|
|
165
|
+
value)` returns a Roaring bitmap of matching entity ints. AND
|
|
166
|
+
/ OR / NOT combinators are bitmap operations. Sub-budget:
|
|
167
|
+
≤ 0.3 ms for a 3-predicate AND at 100 M.
|
|
168
|
+
2. **Vector search** — `NativeDiskAnnWrapper.searchWithFilter`
|
|
169
|
+
walks the Vamana graph, prefilters candidates against the
|
|
170
|
+
metadata bitmap, scores by distance. Sub-budget: ≤ 1 ms at
|
|
171
|
+
1 M (Mode 1), ≤ 4 ms at 100 M (hybrid).
|
|
172
|
+
3. **Graph hop expansion** — `graphIndex.getNeighbors(int_id,
|
|
173
|
+
verb_type)` returns a Roaring bitmap of neighbor ints. Sub-
|
|
174
|
+
budget: ≤ 0.3 ms per hop at 100 M.
|
|
175
|
+
4. **Cross-subsystem ID resolution** — `intsToUuidsBatch` over
|
|
176
|
+
the final result set. Sub-budget: ≤ 0.1 ms per 1 K results.
|
|
177
|
+
|
|
178
|
+
The sub-budgets sum to within the top-level budget when the
|
|
179
|
+
query plan is correct. They blow the budget when the plan is
|
|
180
|
+
wrong (e.g. vector search runs over the whole corpus when a
|
|
181
|
+
1 %-selective filter could prune it first). The query planner
|
|
182
|
+
described in *Investments* below is what keeps the plan correct.
|
|
183
|
+
|
|
184
|
+
## The cor 3.0 performance investments
|
|
185
|
+
|
|
186
|
+
The shadow-page rework (G4.1, G4.4, G4.2 redo per the snapshot
|
|
187
|
+
safety contract) sets the substrate. The following investments
|
|
188
|
+
build on it to reach the budget at billion scale:
|
|
189
|
+
|
|
190
|
+
1. **Cross-subsystem query planner.** Today `find()` runs filter
|
|
191
|
+
→ vector → graph in a fixed order. The planner uses
|
|
192
|
+
`metadataIndex.getCountForCriteria` for cardinality estimates
|
|
193
|
+
and picks the lowest-cost order per query (e.g. a 0.01 %-
|
|
194
|
+
selective filter goes first, prunes 99.99 % of vector work).
|
|
195
|
+
PostgreSQL does this. Pure vector DBs don't.
|
|
196
|
+
|
|
197
|
+
2. **Batched cross-subsystem APIs.** Every napi boundary call
|
|
198
|
+
pays ~5 μs of overhead. Resolving 10 K UUIDs one-at-a-time
|
|
199
|
+
costs 50 ms in napi alone, before any real work. Batched
|
|
200
|
+
variants (`intsToUuidsBatch`, `getNeighborsBatch`,
|
|
201
|
+
`getVerbsBatchCached`) amortise that to ~0.5 μs per item.
|
|
202
|
+
The hot path uses batches exclusively.
|
|
203
|
+
|
|
204
|
+
3. **Lock-free delta cache reads.** The LSM delta cache (added
|
|
205
|
+
in G4.3, applied in G4.1 + G4.4) currently sits behind a
|
|
206
|
+
`Mutex`. A sharded `DashMap` or `ArcSwap<Arc<HashMap>>` MVCC
|
|
207
|
+
pattern lifts the read-side lock entirely, lets concurrent
|
|
208
|
+
`find()` calls scale linearly with cores.
|
|
209
|
+
|
|
210
|
+
4. **SIMD bitmap operations.** CRoaring uses portable
|
|
211
|
+
intersect/union; modern AVX-512 popcnt and `vpternlogq`
|
|
212
|
+
kernels are ~3–5× faster on bitmap-heavy filter combinations.
|
|
213
|
+
|
|
214
|
+
5. **DiskANN predicate pushdown.** The Vamana beam search
|
|
215
|
+
currently scores every neighbor of every visited node. With a
|
|
216
|
+
metadata-bitmap predicate passed into the search engine,
|
|
217
|
+
pruned nodes are skipped during the walk, not after. Massive
|
|
218
|
+
win for high-selectivity hybrid queries.
|
|
219
|
+
|
|
220
|
+
6. **Pipelined result resolution.** DiskANN currently returns all
|
|
221
|
+
top-K candidates before resolution starts. Pipelining the
|
|
222
|
+
resolution (`intsToUuidsBatch` over the first 10 candidates
|
|
223
|
+
while the walk continues) hides the resolution latency
|
|
224
|
+
entirely.
|
|
225
|
+
|
|
226
|
+
7. **Zero-allocation hot path.** `find()` allocates intermediate
|
|
227
|
+
`Vec<u64>` and `HashSet<String>` per query. A bumpalo-style
|
|
228
|
+
arena allocator (reset per query, no per-element free)
|
|
229
|
+
removes the allocation pressure and the GC pause.
|
|
230
|
+
|
|
231
|
+
Each investment ships with the gates above as the proof point.
|
|
232
|
+
|
|
233
|
+
## Forbidden patterns
|
|
234
|
+
|
|
235
|
+
The performance contract has anti-patterns the way the snapshot
|
|
236
|
+
safety contract does:
|
|
237
|
+
|
|
238
|
+
1. **Per-row napi calls on the hot path.** Anything in `find()`
|
|
239
|
+
that does `for (id of ids) { resolve(id) }` over napi is
|
|
240
|
+
incorrect. Use a batch.
|
|
241
|
+
|
|
242
|
+
2. **Lock acquisition on every read.** A `Mutex` on the read
|
|
243
|
+
path costs at minimum a CAS on every call. At 100 K QPS with
|
|
244
|
+
contention, this is a bottleneck before the first real
|
|
245
|
+
operation runs. Use `ArcSwap` or per-shard locking.
|
|
246
|
+
|
|
247
|
+
3. **Synchronous compactions on the hot write path.** Compaction
|
|
248
|
+
is a background operation. A write that blocks for the
|
|
249
|
+
duration of a compaction is a p99 cliff; we currently run
|
|
250
|
+
compaction synchronously in G4.3's `ShadowPageEndpointsStore`
|
|
251
|
+
(acceptable at 3.0 launch scale) but the pattern is
|
|
252
|
+
explicitly recognised as a future fix.
|
|
253
|
+
|
|
254
|
+
4. **String allocation in the inner loop.** UUIDs are converted
|
|
255
|
+
to strings for the JS boundary, but the conversion has to
|
|
256
|
+
happen once per result row, not per intermediate set. Carry
|
|
257
|
+
integer ids through the planner; convert at the egress.
|
|
258
|
+
|
|
259
|
+
5. **Quadratic scans where indexed lookups exist.** Filter
|
|
260
|
+
evaluation that walks all entities checking each against a
|
|
261
|
+
predicate, instead of consulting the metadata index, is the
|
|
262
|
+
hidden cliff at scale. Every `where` clause is index-
|
|
263
|
+
accelerated or it's a release blocker.
|
|
264
|
+
|
|
265
|
+
## See also
|
|
266
|
+
|
|
267
|
+
- `docs/snapshot-safety.md` — the snapshot safety contract this
|
|
268
|
+
performance work is co-designed with.
|
|
269
|
+
- `docs/verification-report.md` — measured baseline numbers
|
|
270
|
+
(SIFT1M / SIFT10M / 1M synthetic, AVX-512 baseline) that the
|
|
271
|
+
budget is anchored on.
|
|
272
|
+
- `docs/comparison.md` — comparison of brainy + cor against
|
|
273
|
+
pure vector / pure graph / pure document databases on the
|
|
274
|
+
triple-intelligence query.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cor Performance
|
|
3
|
+
slug: cor/performance
|
|
4
|
+
public: true
|
|
5
|
+
category: cor
|
|
6
|
+
template: guide
|
|
7
|
+
order: 1
|
|
8
|
+
description: Measured benchmarks for SIMD distance calculations, vector quantization, aggregation, and column-store orderBy sort, plus the cross-language consistency guarantee. Run yourself with npm run bench.
|
|
9
|
+
next:
|
|
10
|
+
- cor/comparison
|
|
11
|
+
- cor/aggregation
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Cor Performance
|
|
15
|
+
|
|
16
|
+
All benchmarks measured using `vitest bench` on the included benchmark suite. Run yourself with:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm run bench
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Distance Calculations
|
|
23
|
+
|
|
24
|
+
384-dimensional vectors (all-MiniLM-L6-v2 embedding size).
|
|
25
|
+
|
|
26
|
+
| Operation | Throughput | Mean Latency | P99 |
|
|
27
|
+
|-----------|-----------|-------------|-----|
|
|
28
|
+
| `cosineDistance` (single pair) | 45,965 ops/s | 21.8 μs | 34.1 μs |
|
|
29
|
+
| `euclideanDistance` (single pair) | 46,320 ops/s | 21.6 μs | — |
|
|
30
|
+
| `cosineDistanceSq8` (quantized pair) | 3,715,855 ops/s | 0.27 μs | 0.3 μs |
|
|
31
|
+
| `cosineDistanceSq8Batch` (1K vectors) | 2,142 ops/s | 467 μs | 1.1 ms |
|
|
32
|
+
| `cosineDistanceBatch` (1K vectors) | 77 ops/s | 13.0 ms | — |
|
|
33
|
+
|
|
34
|
+
**Key insight**: SQ8 quantized distance is **81x faster** than full-precision for single-pair comparisons. For batch operations, SQ8 batch is **28x faster** than full-precision batch.
|
|
35
|
+
|
|
36
|
+
### Quantization
|
|
37
|
+
|
|
38
|
+
| Operation | Throughput | Mean Latency |
|
|
39
|
+
|-----------|-----------|-------------|
|
|
40
|
+
| `quantizeSq8` (384-dim) | 74,020 ops/s | 13.5 μs |
|
|
41
|
+
| `dequantizeSq8` (384-dim) | 53,440 ops/s | 18.7 μs |
|
|
42
|
+
|
|
43
|
+
## Aggregation Engine
|
|
44
|
+
|
|
45
|
+
| Operation | Throughput | Mean Latency |
|
|
46
|
+
|-----------|-----------|-------------|
|
|
47
|
+
| `incrementalUpdate` (1K entities, 3 metrics) | 809 ops/s | 1.2 ms |
|
|
48
|
+
| `rebuildAggregate` (10K entities, 5 groups) | 475 ops/s | 2.1 ms |
|
|
49
|
+
| `rebuildAggregate` (100K entities, Rayon) | 66 ops/s | 15.2 ms |
|
|
50
|
+
| `queryAggregate` (1K groups, sort + paginate) | 986 ops/s | 1.0 ms |
|
|
51
|
+
| `computeGroupKey` (10K entities, time bucketing) | 146 ops/s | 6.8 ms |
|
|
52
|
+
|
|
53
|
+
Rebuild scales linearly with entity count. Rayon parallelism activates above 1,000 entities.
|
|
54
|
+
|
|
55
|
+
## Column Store — orderBy Sort
|
|
56
|
+
|
|
57
|
+
The native column store backs Brainy's `find({ orderBy, limit })`. Its `sortTopK`
|
|
58
|
+
(O(N log K) k-way merge over sorted segments) scales near-linearly with entity
|
|
59
|
+
count.
|
|
60
|
+
|
|
61
|
+
| Operation | Entities | Mean Latency |
|
|
62
|
+
|-----------|---------:|-------------|
|
|
63
|
+
| `sortTopK` (k=100) | 1,000 | ~0.028 ms |
|
|
64
|
+
| `sortTopK` (k=100) | 10,000 | ~0.21 ms |
|
|
65
|
+
| `sortTopK` (k=100) | 100,000 | ~3.3 ms |
|
|
66
|
+
|
|
67
|
+
MEASURED on AMD Ryzen 9 7950X3D, Node 22, release native (`src/benchmarks/scaling.bench.test.ts`).
|
|
68
|
+
Growth from 1k→100k is ~125x against a 100x linear ideal — emphatically not the
|
|
69
|
+
~10,000x of an O(N²) sort. `scaling.bench.test.ts` runs in CI and fails the build
|
|
70
|
+
if the growth ratio ever turns super-linear.
|
|
71
|
+
|
|
72
|
+
## Cross-Language Consistency
|
|
73
|
+
|
|
74
|
+
Cor is a pure accelerator: every native result is byte-for-byte equal to the
|
|
75
|
+
Brainy JavaScript baseline it replaces, so query results are identical with or
|
|
76
|
+
without the plugin installed. This is enforced by a 104-test cross-language parity
|
|
77
|
+
suite (`src/native/crossLanguageParity.test.ts`) covering tokenization, value
|
|
78
|
+
normalization (the UTF-16/ASCII boundary), code-point string collation, SQ8
|
|
79
|
+
quantized distance, top-K ranking, and roaring/msgpack round-trips against
|
|
80
|
+
Brainy's golden outputs.
|
|
81
|
+
|
|
82
|
+
## Serialization
|
|
83
|
+
|
|
84
|
+
| Operation | Throughput | Mean Latency |
|
|
85
|
+
|-----------|-----------|-------------|
|
|
86
|
+
| `msgpackEncode` (1K entities) | 235 ops/s | 4.3 ms |
|
|
87
|
+
| `msgpackDecode` (1K entities) | 355 ops/s | 2.8 ms |
|
|
88
|
+
| `msgpackEncodeBatch` (1K entities) | 290 ops/s | 3.4 ms |
|
|
89
|
+
| `msgpackDecodeBatch` (1K entities) | 356 ops/s | 2.8 ms |
|
|
90
|
+
| `RoaringBitmap32.create` (10K elements) | 3,067 ops/s | 326 μs |
|
|
91
|
+
| `RoaringBitmap32.serialize` (10K elements) | 361,258 ops/s | 2.8 μs |
|
|
92
|
+
| `RoaringBitmap32.deserialize` (10K elements) | 548,714 ops/s | 1.8 μs |
|
|
93
|
+
| `RoaringBitmap32.and` (two 10K bitmaps) | 1,379 ops/s | 725 μs |
|
|
94
|
+
| `RoaringBitmap32.or` (two 10K bitmaps) | 1,219 ops/s | 820 μs |
|
|
95
|
+
| `encodeConnections` (1K lists) | 913 ops/s | 1.1 ms |
|
|
96
|
+
| `decodeConnections` (1K lists) | 1,078 ops/s | 928 μs |
|
|
97
|
+
|
|
98
|
+
## Hardware Recommendations
|
|
99
|
+
|
|
100
|
+
- **CPU**: Multi-core for Rayon parallel rebuild. x86_64 for SIMD distance.
|
|
101
|
+
- **Memory**: Cor native allocations are tracked and reported via the cache subsystem.
|
|
102
|
+
- **Storage**: SSD (NVMe ideal) for the memory-mapped HNSW index and graph-adjacency SSTables — the kernel keeps the hot working set in RAM and pages the rest from disk. See [Scaling & Resource Management](./scaling.md).
|
|
103
|
+
|
|
104
|
+
## Running Benchmarks
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Run all benchmarks
|
|
108
|
+
npm run bench
|
|
109
|
+
|
|
110
|
+
# Run specific benchmark
|
|
111
|
+
npx vitest bench src/benchmarks/distance.bench.ts
|
|
112
|
+
|
|
113
|
+
# Run with verbose output
|
|
114
|
+
npx vitest bench --reporter=verbose
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Benchmarks use vitest bench mode and run multiple iterations to produce stable statistics. Results include Hz (ops/sec), min, max, mean, P75, P99, P99.5, P99.9, and relative margin of error (RME).
|