@soulcraft/cor 3.0.3 → 3.0.4
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.
|
@@ -470,7 +470,7 @@ export class NativeDiskAnnWrapper {
|
|
|
470
470
|
*/
|
|
471
471
|
async hydrateDeltaFromStorage(dim) {
|
|
472
472
|
// Best-effort: only when the storage adapter supports bulk noun reads
|
|
473
|
-
// (production FileSystemStorage
|
|
473
|
+
// (production brainy FileSystemStorage does; some partial
|
|
474
474
|
// adapters used in direct-construction tests do not). When absent, the
|
|
475
475
|
// caller falls back to the in-memory rebuild.
|
|
476
476
|
if (typeof this.storage?.getNouns !== 'function')
|
|
@@ -35,7 +35,8 @@ import type { ColumnStoreProvider, EntityIdMapperLike } from './columnStoreTypes
|
|
|
35
35
|
import { ValueType } from './columnStoreTypes.js';
|
|
36
36
|
import { RoaringBitmap32 } from '../native/NativeRoaringBitmap32.js';
|
|
37
37
|
/**
|
|
38
|
-
* @description The subset of
|
|
38
|
+
* @description The subset of brainy's `FileSystemStorage` (whose index files cor
|
|
39
|
+
* memory-maps) that the column
|
|
39
40
|
* store needs for billion-scale raw segment persistence. cor builds against
|
|
40
41
|
* published `@soulcraft/brainy`, whose `StorageAdapter` type does not yet
|
|
41
42
|
* declare these binary-blob methods, so we narrow the adapter to this local
|
|
@@ -130,7 +130,7 @@ export class NativeColumnStore {
|
|
|
130
130
|
if (!isBinaryBlobStorage(storage)) {
|
|
131
131
|
throw new Error('NativeColumnStore requires a storage adapter with binary-blob support ' +
|
|
132
132
|
'(saveBinaryBlob/loadBinaryBlob/deleteBinaryBlob/getBinaryBlobPath). ' +
|
|
133
|
-
'Use
|
|
133
|
+
'Use brainy FileSystemStorage or another adapter implementing BinaryBlobStorage.');
|
|
134
134
|
}
|
|
135
135
|
this.storage = storage;
|
|
136
136
|
// Discover fields by listing manifest files under the column-index prefix.
|
|
@@ -405,13 +405,31 @@ export class MetadataIndexManager {
|
|
|
405
405
|
// Resolve the metadata directory for the persistent engine, mirroring
|
|
406
406
|
// wireIdMapper's filesystem guard. `getBinaryBlobPath` returns null on
|
|
407
407
|
// non-local adapters → fall back to the in-RAM engine.
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
const getBlobPath = blobStorage.getBinaryBlobPath;
|
|
409
|
+
const hasBlobPath = typeof getBlobPath === 'function';
|
|
410
|
+
const metaDir = hasBlobPath ? getBlobPath.call(blobStorage, '_metadata') : null;
|
|
411
411
|
if (metaDir === null) {
|
|
412
412
|
// Fallback: non-persistent in-RAM engine (the legacy `new` path).
|
|
413
413
|
// U64 IdSpace matches the cor 3.0 production default + the metadata
|
|
414
414
|
// index core constructed above.
|
|
415
|
+
//
|
|
416
|
+
// Two very different situations land here — do NOT let the durable one
|
|
417
|
+
// pass silently (the invisible-degrade the whole GA bar forbids):
|
|
418
|
+
// - method ABSENT → a genuinely non-local adapter (cloud/in-memory);
|
|
419
|
+
// an in-RAM metadata engine is correct. Quiet.
|
|
420
|
+
// - method PRESENT but returned null for '_metadata' → a filesystem-
|
|
421
|
+
// class adapter that should have yielded a path but didn't. The
|
|
422
|
+
// metadata index will run WITHOUT on-disk durability — every restart
|
|
423
|
+
// rebuilds from canonical (the 48s-per-boot class). Announce it LOUD
|
|
424
|
+
// so an operator sees it instead of silently paying the cost forever.
|
|
425
|
+
if (hasBlobPath) {
|
|
426
|
+
prodLog.error('[NativeMetadataIndex] getBinaryBlobPath("_metadata") returned null on ' +
|
|
427
|
+
'an adapter that implements it — the metadata LSM is running IN-RAM, ' +
|
|
428
|
+
'NOT persisted. Every restart will rebuild the index from canonical ' +
|
|
429
|
+
'(slow cold opens). This is a durability misconfiguration, not normal ' +
|
|
430
|
+
'operation — check the storage adapter/path. Reachable durable state is ' +
|
|
431
|
+
'the "restart = warm" contract.');
|
|
432
|
+
}
|
|
415
433
|
this.lsmEngine = new bindings.NativeLsmEngine({ idSpace: 'u64' });
|
|
416
434
|
this.lsmManifest = null;
|
|
417
435
|
this.lsmMetaDir = null;
|
|
@@ -1040,6 +1058,30 @@ export class MetadataIndexManager {
|
|
|
1040
1058
|
'NO rebuild.');
|
|
1041
1059
|
return;
|
|
1042
1060
|
}
|
|
1061
|
+
// **Tripwire — a rebuild here on a PREVIOUSLY-HEALTHY brain is a bug,
|
|
1062
|
+
// not routine.** A rebuild is legitimate exactly twice: a fresh brain
|
|
1063
|
+
// (no marker) and a 7.x→8.0 upgrade (stale marker) — both have no
|
|
1064
|
+
// durable derived state yet. But if the on-disk brain-format marker is
|
|
1065
|
+
// present AND current, an 8.x/3.x already fully built and STAMPED this
|
|
1066
|
+
// brain's indexes; their absence now means the field registry + LSM
|
|
1067
|
+
// both failed to persist/reload (the "restart = milliseconds" contract
|
|
1068
|
+
// broken — every boot pays an O(N) rebuild). That must never be
|
|
1069
|
+
// silent: surface it at ERROR with the entity count so it's caught the
|
|
1070
|
+
// first time, not after a customer measures a 48s boot.
|
|
1071
|
+
try {
|
|
1072
|
+
const marker = await readOnDiskBrainFormat(this.storage);
|
|
1073
|
+
if (!isEpochStale(marker)) {
|
|
1074
|
+
prodLog.error(`[NativeMetadataIndex] DURABILITY TRIPWIRE: rebuilding ${probe.totalCount ?? 'unknown'} ` +
|
|
1075
|
+
'entities on a brain whose format marker is present + current — the ' +
|
|
1076
|
+
'derived indexes (field registry AND LSM) should already be on disk. ' +
|
|
1077
|
+
'This is an unexpected full rebuild on a previously-healthy brain ' +
|
|
1078
|
+
'(the every-boot-rebuild class); please report it with the brain layout. ' +
|
|
1079
|
+
'Data is safe (canonical entities intact); only startup time is affected.');
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
catch {
|
|
1083
|
+
// marker read failed — fall through to the normal rebuild path
|
|
1084
|
+
}
|
|
1043
1085
|
console.warn(`[NativeMetadataIndex] Field registry missing but ${probe.totalCount ?? 'unknown'} entities exist — rebuilding index`);
|
|
1044
1086
|
await this.rebuild();
|
|
1045
1087
|
return;
|
package/docs/migration-3.0.md
CHANGED
|
@@ -355,7 +355,7 @@ Memory is the canonical first 3.0 consumer (production on Brainy 7.31.6 + Cortex
|
|
|
355
355
|
|
|
356
356
|
**Memory-specific gotchas:**
|
|
357
357
|
|
|
358
|
-
-
|
|
358
|
+
- **Storage** is Brainy's standard `filesystem` adapter (there is no separate "mmap" storage class to configure — cor memory-maps its own index files under whatever filesystem path you give Brainy). cor 3.0's DiskANN auto-engagement WILL fire here, which is what you want.
|
|
359
359
|
- **Per-user brain cold start** was 8.3 s on Brainy 7.31.3 + Cortex 2.7.0 (per the BRAINY-COR-MMAP-VECTOR-NOT-WIRED thread). On 8.0 + 3.0 with DiskANN + the cor shadow-page LSM, cold start should drop substantially — Adaptive DiskANN auto-selects `in-memory` mode at the per-user scale (each user is currently ≤ 1 K memories), giving sub-millisecond reads with no warm-up. Re-measure after upgrade and update the production-impact section of that thread.
|
|
360
360
|
- **`db.asOf(g)` / `db.with(...)`** time-travel reads are now available — if Memory wants to surface "what did this user remember last week", that's a feature you can build on top of brainy 8.0's historical query surface without any cor-side work.
|
|
361
361
|
- **No external Memory user has 1 M+ memories yet**, so the migration window is operationally cheap; do it during the morning low-traffic window per current ops practice.
|
package/docs/scaling.md
CHANGED
|
@@ -104,9 +104,11 @@ are evicted before small active ones).
|
|
|
104
104
|
|
|
105
105
|
### Memory-mapped storage
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
You use Brainy's standard `filesystem` storage — there is no separate
|
|
108
|
+
storage class to choose. Cor's native engines memory-map their own index
|
|
109
|
+
files (vectors, graph adjacency, metadata LSM) via `memmap2`, letting the
|
|
110
|
+
Linux kernel manage which pages stay in RAM. Zero-copy binary I/O comes
|
|
111
|
+
from the native layer, not from a special adapter.
|
|
110
112
|
|
|
111
113
|
What mmap provides:
|
|
112
114
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/cor",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Native Rust acceleration for Brainy \u2014 SIMD distance, vector quantization, zero-copy mmap, native embeddings. Free tier for storage, Pro license for compute acceleration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|