@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,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: U64 Entity IDs
|
|
3
|
+
slug: cor/u64-id-space
|
|
4
|
+
public: true
|
|
5
|
+
category: cor
|
|
6
|
+
template: guide
|
|
7
|
+
order: 6
|
|
8
|
+
description: How to opt into Cor's 64-bit entity IdSpace for brains above 4.29 B entities. Same APIs, two wire widths — pick once at create time, then everything in cor routes through the right type.
|
|
9
|
+
next:
|
|
10
|
+
- cor/scaling
|
|
11
|
+
- cor/diskann
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# U64 Entity IDs
|
|
15
|
+
|
|
16
|
+
Cor maps every entity UUID to a compact integer id internally. Two
|
|
17
|
+
parts of the stack key off that integer — the
|
|
18
|
+
[roaring bitmaps](./performance.md) inside the metadata index, and the
|
|
19
|
+
mmap-backed `BinaryEntityIdMapper` that persists the UUID ↔ int
|
|
20
|
+
mapping at billion scale. Both of those layers historically used
|
|
21
|
+
32-bit ints (`Roaring32`, `u32`), which caps a single brain at
|
|
22
|
+
**4 294 967 295 entities** — about 4.29 B.
|
|
23
|
+
|
|
24
|
+
That ceiling is fine for almost every workload. It is *not* fine for
|
|
25
|
+
the corpora cor 3.0 targets: the design point of the release is "1
|
|
26
|
+
B vectors on a single $1000 box", and the long tail goes well past 4
|
|
27
|
+
B. So cor 3.0 adds a second IdSpace — `'u64'` — that lifts the
|
|
28
|
+
ceiling to 2⁶⁴ - 1 (≈ 1.84 × 10¹⁹) while keeping every public API the
|
|
29
|
+
same.
|
|
30
|
+
|
|
31
|
+
## Picking an IdSpace
|
|
32
|
+
|
|
33
|
+
**Cor 3.0's default is `'u64'`** — there's nothing to configure for new brains. You only need to think about this if you specifically want a brainy-7.x-compatible 4.29 B-capped brain.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { BrainyData } from '@soulcraft/brainy'
|
|
37
|
+
import { register as registerCor } from '@soulcraft/cor'
|
|
38
|
+
|
|
39
|
+
// Cor 3.0 production default — no config needed for new brains.
|
|
40
|
+
const brain = new BrainyData({
|
|
41
|
+
storage: { type: 'filesystem', rootDirectory: '/data/idx' },
|
|
42
|
+
})
|
|
43
|
+
await registerCor(brain)
|
|
44
|
+
await brain.init()
|
|
45
|
+
// brain is u64 internally — supports up to 2^64 - 1 entities.
|
|
46
|
+
|
|
47
|
+
// Explicit opt-in to u32 (brainy 7.x compatible mode):
|
|
48
|
+
const brainCompat = new BrainyData({
|
|
49
|
+
storage: { type: 'filesystem', rootDirectory: '/data/legacy-brain' },
|
|
50
|
+
entityIdMapper: { idSpace: 'u32' },
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use **U64** (default) when:
|
|
55
|
+
|
|
56
|
+
- You're starting a new brain on cor 3.0 — it's the default for a reason.
|
|
57
|
+
- You expect any chance of exceeding 4.29 B entities over the brain's lifetime.
|
|
58
|
+
- Brain holds high-fanout graphs where the verb-int count can grow faster than the entity-int count.
|
|
59
|
+
|
|
60
|
+
Use **U32** (explicit opt-in) when:
|
|
61
|
+
|
|
62
|
+
- You're restoring a brainy 7.x snapshot whose on-disk format was u32 and you want bit-identical compatibility with a brainy 7.x reader.
|
|
63
|
+
- You're running a cortex 2.x-compatible deployment that interops with old tooling.
|
|
64
|
+
- You need byte-identical wire format with cortex 2.x — existing `.cidx` segments and chunked metadata JSON envelopes are unchanged.
|
|
65
|
+
|
|
66
|
+
Use **U64** when:
|
|
67
|
+
|
|
68
|
+
- The brain may exceed 4.29 B entities.
|
|
69
|
+
- You're starting fresh and want headroom — the U64 wire format has
|
|
70
|
+
~negligible overhead at small scale and is forward-compatible with
|
|
71
|
+
every cor 3.x release.
|
|
72
|
+
- You want to use the BigInt napi sibling methods directly (e.g. when
|
|
73
|
+
a downstream system already speaks `bigint` and you want to skip
|
|
74
|
+
the safe-integer guard).
|
|
75
|
+
|
|
76
|
+
The two modes are **not interchangeable on the same files**. You
|
|
77
|
+
cannot escalate a U32 brain to U64 in place — the on-disk header is
|
|
78
|
+
authoritative and any mismatched config is rejected as a hard error
|
|
79
|
+
at open time. Migrating an existing U32 brain to U64 is a re-export
|
|
80
|
+
+ re-ingest into a new brain.
|
|
81
|
+
|
|
82
|
+
## No sizing config — the id mapper auto-grows
|
|
83
|
+
|
|
84
|
+
You never size the id-mapper files. Both back the brain's UUID ↔ int
|
|
85
|
+
mapping with mmap-backed sparse files that **grow themselves at
|
|
86
|
+
compaction** as the brain fills:
|
|
87
|
+
|
|
88
|
+
- **`int_to_uuid.bin`** is direct-indexed (`slot = header + int × 24`).
|
|
89
|
+
When the highest assigned int approaches the file's slot capacity,
|
|
90
|
+
the next compaction rebuilds the base at a geometrically larger size
|
|
91
|
+
(×2, with headroom). No fixed entity ceiling.
|
|
92
|
+
- **`uuid_to_int.mkv`** is an extendible hash. Its directory
|
|
93
|
+
auto-doubles up to the architectural maximum depth (32), and
|
|
94
|
+
compaction enlarges the bucket region on demand — with a
|
|
95
|
+
retry-on-overflow loop so even highly-skewed key sets never hit a
|
|
96
|
+
premature cap.
|
|
97
|
+
|
|
98
|
+
Growth rides the crash-safe shadow-page compaction swap (a new base is
|
|
99
|
+
built, the head pointer is swapped atomically, the old base is
|
|
100
|
+
unlinked), so there is no remap-while-readers-active hazard and a
|
|
101
|
+
crash mid-grow recovers cleanly. The files are **sparse**: the
|
|
102
|
+
declared size costs no real disk until the slots are actually written,
|
|
103
|
+
and the modest defaults keep tiny / multi-tenant brains small.
|
|
104
|
+
|
|
105
|
+
**Practical ceiling:** a single brain scales to ~51 B entities
|
|
106
|
+
(`max_global_depth=32` at the 16-entry bucket capacity) with **zero
|
|
107
|
+
configuration**. Past that — i.e. beyond the documented single-box
|
|
108
|
+
content ceiling — cor raises a clear "shard the brain" error
|
|
109
|
+
rather than silently degrading; horizontal scaling across brains is
|
|
110
|
+
the architecture at that point (see [why-cor](./why-cor.md)).
|
|
111
|
+
|
|
112
|
+
## What changes on the wire
|
|
113
|
+
|
|
114
|
+
- **`int_to_uuid.bin`** (binary mapper): v1 header for U32, v2 header
|
|
115
|
+
for U64. The header carries the IdSpace; the file is self-
|
|
116
|
+
describing.
|
|
117
|
+
- **`.cidx` column-store segments**: flags byte's `FLAG_U64_IDS` bit
|
|
118
|
+
(0x01) is set; the entity-id column is `u64` little-endian. The
|
|
119
|
+
cross-language fixture in
|
|
120
|
+
`src/native/cidxU64WireFormatFixture.test.ts` locks the byte layout
|
|
121
|
+
via SHA-256.
|
|
122
|
+
- **Metadata chunk JSON envelope**: U64 chunks get a v2 envelope with
|
|
123
|
+
`"version": 2` and `"idSpace": "u64"` fields. U32 chunks emit the
|
|
124
|
+
v1-compatible envelope (no extra fields) for backwards compatibility.
|
|
125
|
+
- **`PostingList`** (in-memory representation): the U32 brain uses
|
|
126
|
+
`croaring::Bitmap` (Roaring32); the U64 brain uses `croaring::Treemap`
|
|
127
|
+
(Roaring64). Both expose the same algebra (`union_assign`,
|
|
128
|
+
`intersect_assign`, `difference_assign`, `iter_u64`,
|
|
129
|
+
`cardinality`), so every query path is variant-agnostic.
|
|
130
|
+
|
|
131
|
+
## What changes in the API
|
|
132
|
+
|
|
133
|
+
Nothing, for the common case. Every wrapper method that returned a
|
|
134
|
+
`number` continues to return a `number`. Two refinements:
|
|
135
|
+
|
|
136
|
+
1. **`getAllIntIds()` throws in U64 mode.** A materialised `number[]`
|
|
137
|
+
at billion scale would risk OOM, and U64 brains exist precisely
|
|
138
|
+
because the entity count is large. Use the streaming BigInt
|
|
139
|
+
iterator on the underlying native binding.
|
|
140
|
+
2. **`EntityIdSpaceExceeded`** is thrown when a U64 brain's number-
|
|
141
|
+
typed method (e.g. `getOrAssign(uuid): number`) would return an
|
|
142
|
+
int above `Number.MAX_SAFE_INTEGER` (2⁵³ - 1 ≈ 9.007 PB of
|
|
143
|
+
entities). At that point you must switch to the BigInt sibling
|
|
144
|
+
methods for the full u64 range:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const big: bigint = mapper.getOrAssignBig(uuid)
|
|
148
|
+
const uuidBack: string | undefined = mapper.getUuidBig(big)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The BigInt siblings work in **both** modes. Use them in any code
|
|
152
|
+
path that crosses the U32 → U64 escalation point so it doesn't
|
|
153
|
+
need to branch on `getIdSpace()`.
|
|
154
|
+
|
|
155
|
+
## BigInt sibling surface (cor 3.0)
|
|
156
|
+
|
|
157
|
+
| Number-typed | BigInt sibling | Returns |
|
|
158
|
+
|:---|:---|:---|
|
|
159
|
+
| `getOrAssign(uuid: string): number` | `getOrAssignBig(uuid: string): bigint` | The allocated int |
|
|
160
|
+
| `getInt(uuid: string): number \| undefined` | `getIntBig(uuid: string): bigint \| undefined` | The int for `uuid`, or undefined |
|
|
161
|
+
| `getUuid(int: number): string \| undefined` | `getUuidBig(int: bigint): string \| undefined` | The UUID for `int` |
|
|
162
|
+
| `get size(): number` | `sizeBig(): bigint` | Live (non-tombstone) entry count |
|
|
163
|
+
| — | `nextIntBig(): bigint` | Largest int ever assigned + 1 |
|
|
164
|
+
|
|
165
|
+
The BigInt methods are mode-independent: they work in U32 brains too,
|
|
166
|
+
and return the same values widened to `bigint`. Mixing
|
|
167
|
+
`getOrAssign(uuid)` and `getOrAssignBig(uuid)` for the same UUID
|
|
168
|
+
returns the same underlying int.
|
|
169
|
+
|
|
170
|
+
## Brainy 8.0 lockstep — `EntityIdSpaceExceeded` (JS fallback)
|
|
171
|
+
|
|
172
|
+
Brainy 8.0 ships the JS-fallback half of the contract. Without
|
|
173
|
+
cor installed, brainy uses an in-RAM `EntityIdMapper` that also
|
|
174
|
+
caps at u32::MAX — but the JS fallback can't widen to u64 (the
|
|
175
|
+
bitmap layer is `Roaring32` end-to-end on the JS side). So brainy
|
|
176
|
+
8.0's mapper throws `EntityIdSpaceExceeded` (in
|
|
177
|
+
`@soulcraft/brainy/internals`) at the ceiling and points the caller
|
|
178
|
+
at cor's `idSpace: 'u64'` mode as the migration path.
|
|
179
|
+
|
|
180
|
+
The two errors are siblings at different layers:
|
|
181
|
+
|
|
182
|
+
- **brainy's** `EntityIdSpaceExceeded` (`U32_ENTITY_ID_MAX` = 2³² − 1):
|
|
183
|
+
the JS-only path hit the bitmap-width ceiling — install cor.
|
|
184
|
+
- **cor's** `EntityIdSpaceExceeded` (`Number.MAX_SAFE_INTEGER`
|
|
185
|
+
= 2⁵³ − 1): a U64 brain's number-typed method overflowed JS's
|
|
186
|
+
safe-integer range — switch to BigInt siblings.
|
|
187
|
+
|
|
188
|
+
## Wire-format parity gates
|
|
189
|
+
|
|
190
|
+
Two hash-locked fixtures lock the format across reader
|
|
191
|
+
implementations:
|
|
192
|
+
|
|
193
|
+
- **`.cidx` U64 segments** —
|
|
194
|
+
`src/native/cidxU64WireFormatFixture.test.ts`. Three SHA-256
|
|
195
|
+
hashes (numeric / float / string segments) lock the byte layout.
|
|
196
|
+
Brainy 8.0's reader targets the same fixtures.
|
|
197
|
+
- **`PostingList` cross-language** — round-tripped via the napi
|
|
198
|
+
`parityWriteU64NumericSegment` / `parityReadSegmentEntityIdsBig`
|
|
199
|
+
pair. Same write path produces same hash across 100 stress
|
|
200
|
+
iterations (see `src/native/cor30Stress.test.ts`).
|
|
201
|
+
|
|
202
|
+
If a hash drifts, do **not** silently update the constant — root-
|
|
203
|
+
cause the byte diff first. A non-deterministic writer or a
|
|
204
|
+
half-applied format change is exactly the kind of bug those fixtures
|
|
205
|
+
exist to catch.
|
|
206
|
+
|
|
207
|
+
## See also
|
|
208
|
+
|
|
209
|
+
- [Performance](./performance.md) — measured roaring-bitmap ops + SIMD
|
|
210
|
+
distance speedups.
|
|
211
|
+
- [Scaling](./scaling.md) — multi-tenant density + the 1 B operational
|
|
212
|
+
ceiling.
|
|
213
|
+
- [DiskANN](./diskann.md) — the search engine that consumes these
|
|
214
|
+
entity ids at billion scale.
|