@delali/narsil 0.2.4-next.42 → 0.2.4-next.44
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/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/{chunk-GB2IIJJG.mjs → chunk-P4ELXVJQ.mjs} +2 -4
- package/dist/{chunk-MMSWEEZT.mjs → chunk-WASNWIEK.mjs} +1452 -373
- package/dist/{chunk-GRWFY2HI.mjs → chunk-XNCJFU2E.mjs} +239 -61
- package/dist/{chunk-WU2UZEVD.mjs → chunk-XWICBGYT.mjs} +1 -1
- package/dist/{client-BobVnoIK.d.ts → client-C5BkUqB8.d.ts} +26 -3
- package/dist/client.d.ts +8 -8
- package/dist/client.mjs +12 -6
- package/dist/{config-fLC4lbmg.d.ts → config-B-ZI-ft5.d.ts} +2 -2
- package/dist/{config-Byyl1QqH.d.ts → config-BZRExglL.d.ts} +20 -3
- package/dist/{direct-executor-MSJEVPZU.mjs → direct-executor-IMXNRS7O.mjs} +2 -2
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/etcd.mjs +1 -1
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/transport/grpc.mjs +1 -1
- package/dist/distribution.d.ts +24 -10
- package/dist/distribution.mjs +1721 -1129
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-BE45D5ZG.d.ts → engine-Cjr1uBLa.d.ts} +47 -24
- package/dist/index.browser.mjs +1668 -416
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +4 -4
- package/dist/{options-EXYFcem-.d.ts → options-BC68VPoE.d.ts} +10 -3
- package/dist/persistence/durability/checkpoint-worker.d.ts +3 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +4 -4
- package/dist/react.d.ts +6 -6
- package/dist/react.mjs +2 -4
- package/dist/{schema-ZRbk8BgH.d.ts → schema-B7juWg8Q.d.ts} +17 -6
- package/dist/{search-DHuXwJYk.d.ts → search-DDHjfXhJ.d.ts} +35 -11
- package/dist/server.d.ts +10 -10
- package/dist/server.mjs +30 -12
- package/dist/{tasks-CnMLpfk-.d.ts → tasks-Bn4R1NPN.d.ts} +1 -1
- package/dist/{types-B5Tsaalz.d.ts → types-DsSSjY4d.d.ts} +1 -1
- package/dist/workers/entry.mjs +2 -2
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ReplicationMessageTypes } from './chunk-JQUL2T6I.mjs';
|
|
2
|
-
import { MAX_INDEX_NAME_LENGTH } from './chunk-
|
|
3
|
-
import { getNestedValue, getLanguage, extractVectorFieldsFromSchema, createDirectExecutor, createPartitionRouter, clampRowCount, DEFAULT_PAGE_SIZE, validateSchema, validateVectorPromotion, collectQueryTermStats, createPartitionManager, createVectorIndex, flattenSchema, isTextFieldType, requireWithinResultWindow, normalizeSort, sortSignatureOf, requireSortableFields, fanOutQuery, applySorting, applyPagination, resolveProjection, tokenize, decodePageCursor, requireMatchingCursor, encodePageCursor, resolveIndexAnalysis, deserializePayloadV2, deserializePayloadV1,
|
|
2
|
+
import { MAX_INDEX_NAME_LENGTH } from './chunk-XWICBGYT.mjs';
|
|
3
|
+
import { getNestedValue, getLanguage, extractVectorFieldsFromSchema, createDirectExecutor, createPartitionRouter, clampRowCount, DEFAULT_PAGE_SIZE, validateSchema, validateVectorPromotion, collectQueryTermStats, createPartitionManager, createVectorIndex, flattenSchema, isTextFieldType, fnv1aBytes, requireWithinResultWindow, normalizeSort, sortSignatureOf, requireSortableFields, fanOutQuery, applySorting, applyPagination, resolveProjection, countsWithoutScores, fanOutMatchCount, tokenize, decodePageCursor, requireMatchingCursor, encodePageCursor, resolveIndexAnalysis, deserializePayloadV2, deserializePayloadV1, mergePartitionStats, fulltextMatches, mergeFacets, pruneStatsToQueryTerms, createPartitionIndex, fnv1a, createWorkerPool, partitionsIn, detectRuntime, isNodeMainThread, spawnNodeWorker, createSharedFrozenSegment, buildCompactedSegmentPayload, freezeSegmentShared, createFrozenSegment, deserializeMetadata, isCompositePartition, serializeMetadata, kWayMerge, validateRequiredFields, validateDocument, validateDocumentStrict } from './chunk-XNCJFU2E.mjs';
|
|
4
4
|
import { crc32, crc32Init, crc32Update, crc32Final } from './chunk-KMMJMDUA.mjs';
|
|
5
5
|
import { compareCodePoints, compareComparableKeys } from './chunk-BFIH7OV5.mjs';
|
|
6
6
|
import { generateId, createRequestId, isValidWorkerResponse } from './chunk-WTONLDU3.mjs';
|
|
7
|
-
import { ErrorCodes, NarsilError } from './chunk-
|
|
7
|
+
import { ErrorCodes, NarsilError } from './chunk-P4ELXVJQ.mjs';
|
|
8
8
|
import { encode, decode } from '@msgpack/msgpack';
|
|
9
9
|
|
|
10
10
|
// src/partitioning/rebalancer.ts
|
|
@@ -57,7 +57,7 @@ function createRebalancer() {
|
|
|
57
57
|
skipClone: true,
|
|
58
58
|
stopWordOverride: manager.analysis.stopWords,
|
|
59
59
|
customTokenizer: manager.analysis.customTokenizer,
|
|
60
|
-
collectSurfaces: manager.config.surfaceForms
|
|
60
|
+
collectSurfaces: manager.config.surfaceForms !== false
|
|
61
61
|
};
|
|
62
62
|
for (const partition of currentPartitions) {
|
|
63
63
|
const docIds = [...partition.docIds()];
|
|
@@ -347,7 +347,7 @@ function createAnalysisRebuildCoordinator(config, deps) {
|
|
|
347
347
|
async function rebuildNow(indexName, entry) {
|
|
348
348
|
const manager = deps.getManager(indexName);
|
|
349
349
|
if (manager === void 0) {
|
|
350
|
-
|
|
350
|
+
entry.run = null;
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
353
|
const partitionCount = manager.partitionCount;
|
|
@@ -399,6 +399,23 @@ function createAnalysisRebuildCoordinator(config, deps) {
|
|
|
399
399
|
const documentCount = deps.getManager(indexName)?.countDocuments() ?? 0;
|
|
400
400
|
await config?.onStaleAnalysis?.({ ...entry.index, documentCount }, () => enqueue2(indexName, entry));
|
|
401
401
|
}
|
|
402
|
+
async function review(indexName, entry) {
|
|
403
|
+
if (entry.run !== null || deps.getManager(indexName) === void 0) return;
|
|
404
|
+
try {
|
|
405
|
+
await announce(indexName, entry);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
deps.emit({
|
|
408
|
+
indexName,
|
|
409
|
+
status: "failed",
|
|
410
|
+
partitionsRebuilt: 0,
|
|
411
|
+
partitionCount: deps.getManager(indexName)?.partitionCount ?? 0,
|
|
412
|
+
error: toError(err)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
if (rebuildMode === "auto") {
|
|
416
|
+
void enqueue2(indexName, entry).catch(() => void 0);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
402
419
|
return {
|
|
403
420
|
markStale(index) {
|
|
404
421
|
const existing = stale.get(index.indexName);
|
|
@@ -413,23 +430,16 @@ function createAnalysisRebuildCoordinator(config, deps) {
|
|
|
413
430
|
isStale(indexName) {
|
|
414
431
|
return stale.has(indexName);
|
|
415
432
|
},
|
|
433
|
+
isRunning(indexName) {
|
|
434
|
+
return stale.get(indexName)?.run !== null && stale.get(indexName)?.run !== void 0;
|
|
435
|
+
},
|
|
436
|
+
review(indexName) {
|
|
437
|
+
const entry = stale.get(indexName);
|
|
438
|
+
return entry === void 0 ? Promise.resolve() : review(indexName, entry);
|
|
439
|
+
},
|
|
416
440
|
async reviewStaleIndexes() {
|
|
417
441
|
for (const [indexName, entry] of [...stale]) {
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
await announce(indexName, entry);
|
|
421
|
-
} catch (err) {
|
|
422
|
-
deps.emit({
|
|
423
|
-
indexName,
|
|
424
|
-
status: "failed",
|
|
425
|
-
partitionsRebuilt: 0,
|
|
426
|
-
partitionCount: deps.getManager(indexName)?.partitionCount ?? 0,
|
|
427
|
-
error: toError(err)
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
if (rebuildMode === "auto") {
|
|
431
|
-
void enqueue2(indexName, entry).catch(() => void 0);
|
|
432
|
-
}
|
|
442
|
+
await review(indexName, entry);
|
|
433
443
|
}
|
|
434
444
|
},
|
|
435
445
|
rebuild(indexName) {
|
|
@@ -952,6 +962,9 @@ function writeHeader(header) {
|
|
|
952
962
|
}
|
|
953
963
|
return buffer;
|
|
954
964
|
}
|
|
965
|
+
function hasNrslMagic(data) {
|
|
966
|
+
return data.length >= MAGIC_BYTES.length && data[0] === MAGIC_BYTES[0] && data[1] === MAGIC_BYTES[1] && data[2] === MAGIC_BYTES[2] && data[3] === MAGIC_BYTES[3];
|
|
967
|
+
}
|
|
955
968
|
function readHeader(data) {
|
|
956
969
|
if (data.length < HEADER_SIZE) {
|
|
957
970
|
throw new NarsilError(
|
|
@@ -987,6 +1000,7 @@ function readHeader(data) {
|
|
|
987
1000
|
// src/serialization/envelope.ts
|
|
988
1001
|
var CURRENT_ENVELOPE_VERSION = 1;
|
|
989
1002
|
var SNAPSHOT_ENVELOPE_VERSION = 2;
|
|
1003
|
+
var INDEX_SNAPSHOT_ENVELOPE_VERSION = 3;
|
|
990
1004
|
function parseEngineVersion(version) {
|
|
991
1005
|
const parts = version.split(".");
|
|
992
1006
|
return [parseInt(parts[0] ?? "0", 10), parseInt(parts[1] ?? "0", 10), parseInt(parts[2] ?? "0", 10)];
|
|
@@ -1131,6 +1145,19 @@ function concatEnvelopeParts(parts) {
|
|
|
1131
1145
|
async function unpackEnvelopeBytes(data) {
|
|
1132
1146
|
return unpackEnvelope(data, SNAPSHOT_ENVELOPE_VERSION);
|
|
1133
1147
|
}
|
|
1148
|
+
async function packIndexSnapshotEnvelope(payloadBytes) {
|
|
1149
|
+
return packEnvelope(payloadBytes, { checksum: true, envelopeFormatVersion: INDEX_SNAPSHOT_ENVELOPE_VERSION });
|
|
1150
|
+
}
|
|
1151
|
+
async function unpackIndexSnapshotEnvelope(data) {
|
|
1152
|
+
const { header, payloadBytes } = await unpackEnvelope(data, INDEX_SNAPSHOT_ENVELOPE_VERSION);
|
|
1153
|
+
if (header.envelopeFormatVersion !== INDEX_SNAPSHOT_ENVELOPE_VERSION) {
|
|
1154
|
+
throw new NarsilError(
|
|
1155
|
+
ErrorCodes.ENVELOPE_VERSION_MISMATCH,
|
|
1156
|
+
`Expected an index snapshot envelope of format v${INDEX_SNAPSHOT_ENVELOPE_VERSION}, received format v${header.envelopeFormatVersion}`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
return payloadBytes;
|
|
1160
|
+
}
|
|
1134
1161
|
async function writeMetadataEnvelope(meta, options = {}) {
|
|
1135
1162
|
const payloadBytes = serializeMetadata(meta);
|
|
1136
1163
|
return packEnvelope(payloadBytes, options);
|
|
@@ -1818,6 +1845,9 @@ function mergeTimeOrderedSegments(ordered, fallback) {
|
|
|
1818
1845
|
validateMergedPartition(merged);
|
|
1819
1846
|
return merged;
|
|
1820
1847
|
}
|
|
1848
|
+
function countLiveDocuments(ordered) {
|
|
1849
|
+
return resolveWinners(ordered).size;
|
|
1850
|
+
}
|
|
1821
1851
|
function resolveWinners(ordered) {
|
|
1822
1852
|
const winner = /* @__PURE__ */ new Map();
|
|
1823
1853
|
for (let segIndex = 0; segIndex < ordered.length; segIndex += 1) {
|
|
@@ -3310,6 +3340,7 @@ async function writeSegmentedCheckpoint(input) {
|
|
|
3310
3340
|
});
|
|
3311
3341
|
}
|
|
3312
3342
|
const partitions = [];
|
|
3343
|
+
let wholeDocumentCount = input.wholePartitionPayload === void 0 ? null : 0;
|
|
3313
3344
|
for (const target of input.targets) {
|
|
3314
3345
|
const priorPartition = priorManifest?.partitions.find((p) => p.partitionId === target.partitionId);
|
|
3315
3346
|
const priorSeqNo = snapshotCheckpointFor(priorManifest?.checkpoint ?? [], target.partitionId);
|
|
@@ -3320,15 +3351,13 @@ async function writeSegmentedCheckpoint(input) {
|
|
|
3320
3351
|
priorSeqNo,
|
|
3321
3352
|
target.lastSeqNo
|
|
3322
3353
|
);
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
)
|
|
3331
|
-
);
|
|
3354
|
+
if (input.wholePartitionPayload === void 0) {
|
|
3355
|
+
partitions.push(await writePartition(context, target.partitionId, priorPartition, entries));
|
|
3356
|
+
} else {
|
|
3357
|
+
const whole = input.wholePartitionPayload(target.partitionId);
|
|
3358
|
+
wholeDocumentCount = (wholeDocumentCount ?? 0) + whole.docCount;
|
|
3359
|
+
partitions.push(await writeWholePartition(context, target.partitionId, priorPartition, whole, entries));
|
|
3360
|
+
}
|
|
3332
3361
|
}
|
|
3333
3362
|
carryForwardUncheckpointedPartitions(priorManifest, input.targets, partitions, checkpointByPartition);
|
|
3334
3363
|
const manifest = {
|
|
@@ -3340,6 +3369,7 @@ async function writeSegmentedCheckpoint(input) {
|
|
|
3340
3369
|
const parts = await encodeSegmentManifest(manifest);
|
|
3341
3370
|
await directory.atomicWrite(manifestKey(indexName), [parts.header, parts.payload]);
|
|
3342
3371
|
await collectGarbage(directory, indexName, priorManifest, manifest);
|
|
3372
|
+
return wholeDocumentCount;
|
|
3343
3373
|
}
|
|
3344
3374
|
async function writePartition(context, partitionId, priorPartition, entries) {
|
|
3345
3375
|
let segments = priorPartition ? [...priorPartition.segments] : [];
|
|
@@ -3459,9 +3489,9 @@ async function writeIndexCheckpoint(input) {
|
|
|
3459
3489
|
const { directory, metadata, targets, compactionThreshold, manager } = input;
|
|
3460
3490
|
const offloaded = !input.fromMemory && input.canOffload && metadata.tokenizer === void 0 && metadata.stopWords === void 0 && await runCheckpointOnWorker({ root: directory.root, metadata, targets, compactionThreshold });
|
|
3461
3491
|
if (offloaded) {
|
|
3462
|
-
return;
|
|
3492
|
+
return null;
|
|
3463
3493
|
}
|
|
3464
|
-
|
|
3494
|
+
return writeSegmentedCheckpoint({
|
|
3465
3495
|
directory,
|
|
3466
3496
|
metadata,
|
|
3467
3497
|
targets,
|
|
@@ -3493,9 +3523,171 @@ function createSeqOwner(startSeqNo, primaryTerm = SINGLE_NODE_PRIMARY_TERM) {
|
|
|
3493
3523
|
};
|
|
3494
3524
|
}
|
|
3495
3525
|
|
|
3526
|
+
// src/persistence/durability/checkpoint-run.ts
|
|
3527
|
+
function toError2(error) {
|
|
3528
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
3529
|
+
}
|
|
3530
|
+
async function runDurableCheckpoint(input) {
|
|
3531
|
+
const manager = input.hooks.getManager(input.indexName);
|
|
3532
|
+
if (manager === void 0) {
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
const metadata = input.hooks.buildMetadata(input.indexName);
|
|
3536
|
+
if (metadata === void 0) {
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3539
|
+
const targets = [];
|
|
3540
|
+
const documentCount = manager.countDocuments();
|
|
3541
|
+
for (let i = 0; i < manager.partitionCount; i += 1) {
|
|
3542
|
+
const partition = input.indexState.partitions.get(i);
|
|
3543
|
+
targets.push({
|
|
3544
|
+
partitionId: i,
|
|
3545
|
+
lastSeqNo: partition?.appliedSeqNo ?? 0,
|
|
3546
|
+
primaryTerm: partition?.seqOwner.primaryTerm ?? SINGLE_NODE_PRIMARY_TERM
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
for (let i = 0; i < manager.partitionCount; i += 1) {
|
|
3550
|
+
const partition = input.indexState.partitions.get(i);
|
|
3551
|
+
if (partition === void 0) {
|
|
3552
|
+
continue;
|
|
3553
|
+
}
|
|
3554
|
+
try {
|
|
3555
|
+
await partition.walWriter.commit();
|
|
3556
|
+
} catch (error) {
|
|
3557
|
+
partition.failed = toError2(error);
|
|
3558
|
+
input.markFatal(partition.failed);
|
|
3559
|
+
throw partition.failed;
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
const writtenDocumentCount = await writeIndexCheckpoint({
|
|
3563
|
+
directory: input.directory,
|
|
3564
|
+
metadata,
|
|
3565
|
+
targets,
|
|
3566
|
+
compactionThreshold: input.compactionThreshold,
|
|
3567
|
+
manager,
|
|
3568
|
+
canOffload: input.canOffload,
|
|
3569
|
+
fromMemory: input.fromMemory
|
|
3570
|
+
});
|
|
3571
|
+
const checkpointDocumentCount = writtenDocumentCount ?? documentCount;
|
|
3572
|
+
await input.queueMetadataWrite(input.indexName, async () => {
|
|
3573
|
+
const checkpointMetadata = input.hooks.buildMetadata(input.indexName, checkpointDocumentCount);
|
|
3574
|
+
if (checkpointMetadata === void 0) {
|
|
3575
|
+
return;
|
|
3576
|
+
}
|
|
3577
|
+
const bytes = await writeMetadataEnvelope(checkpointMetadata, { checksum: true });
|
|
3578
|
+
await input.directory.atomicWrite(`${input.indexName}/meta`, bytes);
|
|
3579
|
+
input.hooks.recordCheckpoint?.(input.indexName, checkpointDocumentCount, manager.partitionCount);
|
|
3580
|
+
});
|
|
3581
|
+
await truncateCoveredSegments(input.directory, input.indexName, targets);
|
|
3582
|
+
await reclaimWalBeyondCount(
|
|
3583
|
+
input.directory,
|
|
3584
|
+
input.indexName,
|
|
3585
|
+
manager.partitionCount,
|
|
3586
|
+
input.indexState.partitions,
|
|
3587
|
+
input.markFatal
|
|
3588
|
+
);
|
|
3589
|
+
input.indexState.mutationsSinceCheckpoint = 0;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
// src/persistence/durability/manager-state.ts
|
|
3593
|
+
async function drainIndexStateForUnload(indexState, pendingMetadataWrite, closeWalWriter) {
|
|
3594
|
+
indexState.unloading = true;
|
|
3595
|
+
try {
|
|
3596
|
+
if (indexState.checkpointInFlight !== null) await indexState.checkpointInFlight;
|
|
3597
|
+
await pendingMetadataWrite;
|
|
3598
|
+
for (const partition of indexState.partitions.values()) {
|
|
3599
|
+
await closeWalWriter(partition.walWriter.close());
|
|
3600
|
+
}
|
|
3601
|
+
} catch (error) {
|
|
3602
|
+
indexState.unloading = false;
|
|
3603
|
+
throw error;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
// src/persistence/durability/checkpoint-count.ts
|
|
3608
|
+
async function countCheckpointDocuments(directory, indexName) {
|
|
3609
|
+
const manifest = await readSegmentManifest(directory, indexName);
|
|
3610
|
+
if (manifest !== null) {
|
|
3611
|
+
let total = 0;
|
|
3612
|
+
for (const partition of manifest.partitions) {
|
|
3613
|
+
const ordered = [];
|
|
3614
|
+
for (const segment of partition.segments) {
|
|
3615
|
+
ordered.push(await readSegmentContents(directory, segment.key));
|
|
3616
|
+
}
|
|
3617
|
+
total += countLiveDocuments(ordered);
|
|
3618
|
+
}
|
|
3619
|
+
return total;
|
|
3620
|
+
}
|
|
3621
|
+
const bytes = await directory.read(legacySnapshotKey(indexName));
|
|
3622
|
+
if (bytes === null) {
|
|
3623
|
+
return 0;
|
|
3624
|
+
}
|
|
3625
|
+
return countSnapshotBundleDocuments(bytes);
|
|
3626
|
+
}
|
|
3627
|
+
async function countSnapshotBundleDocuments(bytes) {
|
|
3628
|
+
const bundle = await decodeSnapshotBundle(bytes);
|
|
3629
|
+
let total = 0;
|
|
3630
|
+
for (const partitionBytes of bundle.partitions) {
|
|
3631
|
+
total += Object.keys(deserializePayloadV2(partitionBytes).documents).length;
|
|
3632
|
+
}
|
|
3633
|
+
return total;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
// src/persistence/durability/recover-index.ts
|
|
3637
|
+
async function recoverPersistedIndex(deps, indexName, metadataOnly) {
|
|
3638
|
+
const { directory, hooks } = deps;
|
|
3639
|
+
const metadata = await loadMetadata(directory, indexName);
|
|
3640
|
+
if (metadata === null) {
|
|
3641
|
+
return;
|
|
3642
|
+
}
|
|
3643
|
+
let derivedCount;
|
|
3644
|
+
if (metadataOnly && metadata.documentCount === void 0) {
|
|
3645
|
+
try {
|
|
3646
|
+
derivedCount = await countCheckpointDocuments(directory, indexName);
|
|
3647
|
+
metadata.documentCount = derivedCount;
|
|
3648
|
+
} catch {
|
|
3649
|
+
derivedCount = void 0;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
await hooks.createIndexFromMetadata(metadata, !metadataOnly);
|
|
3653
|
+
if (metadataOnly) {
|
|
3654
|
+
if (derivedCount !== void 0) {
|
|
3655
|
+
await deps.queueMetadataWrite(indexName, async () => {
|
|
3656
|
+
const upgraded = hooks.buildMetadata(indexName, derivedCount);
|
|
3657
|
+
if (upgraded === void 0) {
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
const bytes = await writeMetadataEnvelope(upgraded, { checksum: true });
|
|
3661
|
+
await directory.atomicWrite(`${indexName}/meta`, bytes);
|
|
3662
|
+
}).catch(() => void 0);
|
|
3663
|
+
}
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
const manager = hooks.getManager(indexName);
|
|
3667
|
+
if (manager === void 0) {
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
const replayDeps = {
|
|
3671
|
+
manager,
|
|
3672
|
+
vectorFieldPaths: hooks.getVectorFieldPaths(indexName),
|
|
3673
|
+
vectorIndexes: hooks.getVectorIndexes(indexName)
|
|
3674
|
+
};
|
|
3675
|
+
const checkpoint = await loadSnapshot(directory, indexName, replayDeps);
|
|
3676
|
+
const manifest = await readSegmentManifest(directory, indexName);
|
|
3677
|
+
if (manifest !== null) {
|
|
3678
|
+
await reclaimOrphanedSegments(directory, indexName, manifest);
|
|
3679
|
+
}
|
|
3680
|
+
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId += 1) {
|
|
3681
|
+
const fromSeqNo = snapshotCheckpointFor(checkpoint, partitionId);
|
|
3682
|
+
const { highestSeqNo } = await replayWal(directory, indexName, partitionId, fromSeqNo, replayDeps);
|
|
3683
|
+
deps.registerPartition(indexName, partitionId, highestSeqNo);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3496
3687
|
// src/persistence/durability/types.ts
|
|
3497
3688
|
var DEFAULT_CHECKPOINT_INTERVAL_MS = 3e5;
|
|
3498
3689
|
var DEFAULT_CHECKPOINT_MUTATION_THRESHOLD = 1e5;
|
|
3690
|
+
var DEFAULT_ASYNC_FLUSH_INTERVAL_MS = 1e3;
|
|
3499
3691
|
|
|
3500
3692
|
// src/persistence/durability/group-commit.ts
|
|
3501
3693
|
function createGroupCommitCoordinator(sync) {
|
|
@@ -3741,7 +3933,6 @@ function createWalWriter(directory, config) {
|
|
|
3741
3933
|
}
|
|
3742
3934
|
|
|
3743
3935
|
// src/persistence/durability/manager.ts
|
|
3744
|
-
var DEFAULT_ASYNC_FLUSH_INTERVAL_MS = 1e3;
|
|
3745
3936
|
function createDurabilityManager(config, hooks, directoryOverride) {
|
|
3746
3937
|
const directory = createDurableDirectory(config.directory ?? "");
|
|
3747
3938
|
const canOffloadCheckpoint = directoryOverride === void 0;
|
|
@@ -3772,20 +3963,20 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3772
3963
|
}
|
|
3773
3964
|
hooks.onFatalError(error);
|
|
3774
3965
|
}
|
|
3775
|
-
function
|
|
3966
|
+
function toError4(err) {
|
|
3776
3967
|
return err instanceof Error ? err : new Error(String(err));
|
|
3777
3968
|
}
|
|
3778
3969
|
async function closeWriterReportingFailure(closePromise) {
|
|
3779
3970
|
try {
|
|
3780
3971
|
await closePromise;
|
|
3781
3972
|
} catch (err) {
|
|
3782
|
-
markFatal(
|
|
3973
|
+
markFatal(toError4(err));
|
|
3783
3974
|
}
|
|
3784
3975
|
}
|
|
3785
3976
|
function getOrCreateIndexState(indexName) {
|
|
3786
3977
|
let state = indexes.get(indexName);
|
|
3787
3978
|
if (state === void 0) {
|
|
3788
|
-
state = { partitions: /* @__PURE__ */ new Map(), mutationsSinceCheckpoint: 0, checkpointInFlight: null };
|
|
3979
|
+
state = { partitions: /* @__PURE__ */ new Map(), mutationsSinceCheckpoint: 0, checkpointInFlight: null, unloading: false };
|
|
3789
3980
|
indexes.set(indexName, state);
|
|
3790
3981
|
}
|
|
3791
3982
|
return state;
|
|
@@ -3848,7 +4039,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3848
4039
|
try {
|
|
3849
4040
|
await partition.walWriter.commit();
|
|
3850
4041
|
} catch (err) {
|
|
3851
|
-
partition.failed =
|
|
4042
|
+
partition.failed = toError4(err);
|
|
3852
4043
|
markFatal(partition.failed);
|
|
3853
4044
|
}
|
|
3854
4045
|
}
|
|
@@ -3862,7 +4053,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3862
4053
|
try {
|
|
3863
4054
|
await checkpointIndex(indexName);
|
|
3864
4055
|
} catch (err) {
|
|
3865
|
-
markFatal(
|
|
4056
|
+
markFatal(toError4(err));
|
|
3866
4057
|
}
|
|
3867
4058
|
}
|
|
3868
4059
|
}
|
|
@@ -3879,7 +4070,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3879
4070
|
}
|
|
3880
4071
|
async function checkpointIndex(indexName, fromMemory = false) {
|
|
3881
4072
|
const indexState = indexes.get(indexName);
|
|
3882
|
-
if (indexState === void 0) {
|
|
4073
|
+
if (indexState === void 0 || indexState.unloading) {
|
|
3883
4074
|
return;
|
|
3884
4075
|
}
|
|
3885
4076
|
if (indexState.checkpointInFlight !== null) {
|
|
@@ -3895,82 +4086,46 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3895
4086
|
return run;
|
|
3896
4087
|
}
|
|
3897
4088
|
async function performCheckpoint(indexName, indexState, fromMemory = false) {
|
|
3898
|
-
|
|
3899
|
-
if (manager === void 0) {
|
|
3900
|
-
return;
|
|
3901
|
-
}
|
|
3902
|
-
const metadata = hooks.buildMetadata(indexName);
|
|
3903
|
-
if (metadata === void 0) {
|
|
3904
|
-
return;
|
|
3905
|
-
}
|
|
3906
|
-
const targets = [];
|
|
3907
|
-
for (let i = 0; i < manager.partitionCount; i += 1) {
|
|
3908
|
-
const partition = indexState.partitions.get(i);
|
|
3909
|
-
targets.push({
|
|
3910
|
-
partitionId: i,
|
|
3911
|
-
lastSeqNo: partition?.appliedSeqNo ?? 0,
|
|
3912
|
-
primaryTerm: partition?.seqOwner.primaryTerm ?? SINGLE_NODE_PRIMARY_TERM
|
|
3913
|
-
});
|
|
3914
|
-
if (partition !== void 0) {
|
|
3915
|
-
try {
|
|
3916
|
-
await partition.walWriter.commit();
|
|
3917
|
-
} catch (err) {
|
|
3918
|
-
partition.failed = toError3(err);
|
|
3919
|
-
markFatal(partition.failed);
|
|
3920
|
-
throw partition.failed;
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
await writeIndexCheckpoint({
|
|
4089
|
+
await runDurableCheckpoint({
|
|
3925
4090
|
directory,
|
|
3926
|
-
|
|
3927
|
-
|
|
4091
|
+
hooks,
|
|
4092
|
+
indexName,
|
|
4093
|
+
indexState,
|
|
3928
4094
|
compactionThreshold,
|
|
3929
|
-
manager,
|
|
3930
4095
|
canOffload: canOffloadCheckpoint,
|
|
3931
|
-
fromMemory
|
|
4096
|
+
fromMemory,
|
|
4097
|
+
queueMetadataWrite,
|
|
4098
|
+
markFatal
|
|
3932
4099
|
});
|
|
3933
|
-
await truncateCoveredSegments(directory, indexName, targets);
|
|
3934
|
-
await reclaimWalBeyondCount(directory, indexName, manager.partitionCount, indexState.partitions, markFatal);
|
|
3935
|
-
indexState.mutationsSinceCheckpoint = 0;
|
|
3936
4100
|
}
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
vectorIndexes: hooks.getVectorIndexes(indexName)
|
|
3951
|
-
};
|
|
3952
|
-
const checkpoint = await loadSnapshot(directory, indexName, deps);
|
|
3953
|
-
const manifest = await readSegmentManifest(directory, indexName);
|
|
3954
|
-
if (manifest !== null) {
|
|
3955
|
-
await reclaimOrphanedSegments(directory, indexName, manifest);
|
|
3956
|
-
}
|
|
3957
|
-
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId += 1) {
|
|
3958
|
-
const fromSeqNo = snapshotCheckpointFor(checkpoint, partitionId);
|
|
3959
|
-
const { highestSeqNo } = await replayWal(directory, indexName, partitionId, fromSeqNo, deps);
|
|
3960
|
-
getOrCreatePartition(indexName, partitionId, highestSeqNo);
|
|
3961
|
-
}
|
|
4101
|
+
function recoverIndex(indexName, metadataOnly = false) {
|
|
4102
|
+
return recoverPersistedIndex(
|
|
4103
|
+
{
|
|
4104
|
+
directory,
|
|
4105
|
+
hooks,
|
|
4106
|
+
registerPartition: (name, partitionId, startSeqNo) => {
|
|
4107
|
+
getOrCreatePartition(name, partitionId, startSeqNo);
|
|
4108
|
+
},
|
|
4109
|
+
queueMetadataWrite
|
|
4110
|
+
},
|
|
4111
|
+
indexName,
|
|
4112
|
+
metadataOnly
|
|
4113
|
+
);
|
|
3962
4114
|
}
|
|
3963
4115
|
return {
|
|
3964
4116
|
isActive() {
|
|
3965
4117
|
return true;
|
|
3966
4118
|
},
|
|
3967
|
-
async recover() {
|
|
4119
|
+
async recover(metadataOnly = false) {
|
|
3968
4120
|
const names = await listPersistedIndexes(directory);
|
|
3969
4121
|
for (const indexName of names) {
|
|
3970
|
-
await recoverIndex(indexName);
|
|
4122
|
+
await recoverIndex(indexName, metadataOnly);
|
|
3971
4123
|
}
|
|
3972
4124
|
startCheckpointTimer();
|
|
3973
4125
|
},
|
|
4126
|
+
recoverIndex(indexName) {
|
|
4127
|
+
return recoverIndex(indexName);
|
|
4128
|
+
},
|
|
3974
4129
|
highestPersistedSeqNo(indexName, partitionId) {
|
|
3975
4130
|
return indexes.get(indexName)?.partitions.get(partitionId)?.appliedSeqNo ?? 0;
|
|
3976
4131
|
},
|
|
@@ -3994,7 +4149,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3994
4149
|
const entry = buildMutationEntry(record, allocatedSeqNo);
|
|
3995
4150
|
await partition.walWriter.append(entry);
|
|
3996
4151
|
} catch (err) {
|
|
3997
|
-
partition.failed =
|
|
4152
|
+
partition.failed = toError4(err);
|
|
3998
4153
|
markFatal(partition.failed);
|
|
3999
4154
|
throw partition.failed;
|
|
4000
4155
|
}
|
|
@@ -4006,7 +4161,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
4006
4161
|
try {
|
|
4007
4162
|
await partition.walWriter.commit();
|
|
4008
4163
|
} catch (err) {
|
|
4009
|
-
partition.failed =
|
|
4164
|
+
partition.failed = toError4(err);
|
|
4010
4165
|
markFatal(partition.failed);
|
|
4011
4166
|
throw partition.failed;
|
|
4012
4167
|
}
|
|
@@ -4016,7 +4171,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
4016
4171
|
startAsyncFlushTimer();
|
|
4017
4172
|
if (indexState.mutationsSinceCheckpoint >= checkpointMutationThreshold) {
|
|
4018
4173
|
void checkpointIndex(record.indexName).catch((err) => {
|
|
4019
|
-
markFatal(
|
|
4174
|
+
markFatal(toError4(err));
|
|
4020
4175
|
});
|
|
4021
4176
|
}
|
|
4022
4177
|
return allocatedSeqNo;
|
|
@@ -4046,15 +4201,23 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
4046
4201
|
async removeIndex(indexName) {
|
|
4047
4202
|
const indexState = indexes.get(indexName);
|
|
4048
4203
|
if (indexState !== void 0) {
|
|
4049
|
-
|
|
4050
|
-
await closeWriterReportingFailure(partition.walWriter.close());
|
|
4051
|
-
}
|
|
4204
|
+
await drainIndexStateForUnload(indexState, metadataWrites.get(indexName), closeWriterReportingFailure);
|
|
4052
4205
|
indexes.delete(indexName);
|
|
4206
|
+
metadataWrites.delete(indexName);
|
|
4053
4207
|
}
|
|
4054
4208
|
for (const key of await directory.list(`${indexName}/`)) {
|
|
4055
4209
|
await directory.remove(key);
|
|
4056
4210
|
}
|
|
4057
4211
|
},
|
|
4212
|
+
async unloadIndex(indexName) {
|
|
4213
|
+
const indexState = indexes.get(indexName);
|
|
4214
|
+
if (indexState === void 0) {
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
await drainIndexStateForUnload(indexState, metadataWrites.get(indexName), (close) => close);
|
|
4218
|
+
indexes.delete(indexName);
|
|
4219
|
+
metadataWrites.delete(indexName);
|
|
4220
|
+
},
|
|
4058
4221
|
async shutdown() {
|
|
4059
4222
|
shuttingDown = true;
|
|
4060
4223
|
if (checkpointTimer !== null) {
|
|
@@ -4093,7 +4256,13 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4093
4256
|
function getOrCreateIndexState(indexName) {
|
|
4094
4257
|
let state = indexes.get(indexName);
|
|
4095
4258
|
if (state === void 0) {
|
|
4096
|
-
state = {
|
|
4259
|
+
state = {
|
|
4260
|
+
mutationsSinceCheckpoint: 0,
|
|
4261
|
+
checkpointInFlight: null,
|
|
4262
|
+
applyChain: Promise.resolve(),
|
|
4263
|
+
unloading: false,
|
|
4264
|
+
appliedSeqNoByPartition: /* @__PURE__ */ new Map()
|
|
4265
|
+
};
|
|
4097
4266
|
indexes.set(indexName, state);
|
|
4098
4267
|
}
|
|
4099
4268
|
return state;
|
|
@@ -4124,10 +4293,11 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4124
4293
|
if (manager === void 0 || metadata === void 0) {
|
|
4125
4294
|
return;
|
|
4126
4295
|
}
|
|
4296
|
+
const documentCount = manager.countDocuments();
|
|
4127
4297
|
const seqNoByPartition = /* @__PURE__ */ new Map();
|
|
4128
4298
|
const primaryTermByPartition = /* @__PURE__ */ new Map();
|
|
4129
4299
|
for (let i = 0; i < manager.partitionCount; i += 1) {
|
|
4130
|
-
seqNoByPartition.set(i, 0);
|
|
4300
|
+
seqNoByPartition.set(i, indexState.appliedSeqNoByPartition.get(i) ?? 0);
|
|
4131
4301
|
primaryTermByPartition.set(i, SINGLE_NODE_PRIMARY_TERM);
|
|
4132
4302
|
}
|
|
4133
4303
|
const { parts } = await buildSnapshotBundleBytes({
|
|
@@ -4143,6 +4313,13 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4143
4313
|
primaryTermByPartition
|
|
4144
4314
|
});
|
|
4145
4315
|
await adapter.save(snapshotStorageKey(indexName), concatEnvelopeParts(parts));
|
|
4316
|
+
await queueMetadataWrite(indexName, async () => {
|
|
4317
|
+
const checkpointMetadata = hooks.buildMetadata(indexName, documentCount);
|
|
4318
|
+
if (checkpointMetadata === void 0) return;
|
|
4319
|
+
const metadataBytes = await writeMetadataEnvelope(checkpointMetadata, { checksum: true });
|
|
4320
|
+
await adapter.save(metadataKey(indexName), metadataBytes);
|
|
4321
|
+
hooks.recordCheckpoint?.(indexName, documentCount, manager.partitionCount);
|
|
4322
|
+
});
|
|
4146
4323
|
indexState.mutationsSinceCheckpoint = 0;
|
|
4147
4324
|
if (publisher !== void 0) {
|
|
4148
4325
|
const partitionIds = [];
|
|
@@ -4152,15 +4329,18 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4152
4329
|
await publisher.publishPartitions(indexName, partitionIds);
|
|
4153
4330
|
}
|
|
4154
4331
|
}
|
|
4155
|
-
async function checkpointIndex(indexName) {
|
|
4332
|
+
async function checkpointIndex(indexName, forceFresh = false) {
|
|
4156
4333
|
const indexState = indexes.get(indexName);
|
|
4157
|
-
if (indexState === void 0) {
|
|
4334
|
+
if (indexState === void 0 || indexState.unloading) {
|
|
4158
4335
|
return;
|
|
4159
4336
|
}
|
|
4160
4337
|
if (indexState.checkpointInFlight !== null) {
|
|
4161
|
-
|
|
4338
|
+
await indexState.checkpointInFlight;
|
|
4339
|
+
if (!forceFresh) {
|
|
4340
|
+
return;
|
|
4341
|
+
}
|
|
4162
4342
|
}
|
|
4163
|
-
const run = performCheckpoint(indexName, indexState).finally(() => {
|
|
4343
|
+
const run = indexState.applyChain.then(() => performCheckpoint(indexName, indexState)).finally(() => {
|
|
4164
4344
|
indexState.checkpointInFlight = null;
|
|
4165
4345
|
});
|
|
4166
4346
|
indexState.checkpointInFlight = run;
|
|
@@ -4175,13 +4355,35 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4175
4355
|
);
|
|
4176
4356
|
return queued;
|
|
4177
4357
|
}
|
|
4178
|
-
async function recoverIndex(indexName) {
|
|
4358
|
+
async function recoverIndex(indexName, metadataOnly = false) {
|
|
4179
4359
|
const metaBytes = await adapter.load(metadataKey(indexName));
|
|
4180
4360
|
if (metaBytes === null) {
|
|
4181
4361
|
return;
|
|
4182
4362
|
}
|
|
4183
4363
|
const { metadata } = await readMetadataEnvelope(metaBytes);
|
|
4184
|
-
|
|
4364
|
+
let derivedCount;
|
|
4365
|
+
if (metadataOnly && metadata.documentCount === void 0) {
|
|
4366
|
+
try {
|
|
4367
|
+
derivedCount = await countStoredBundleDocuments(indexName);
|
|
4368
|
+
metadata.documentCount = derivedCount;
|
|
4369
|
+
} catch {
|
|
4370
|
+
derivedCount = void 0;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
await hooks.createIndexFromMetadata(metadata, !metadataOnly);
|
|
4374
|
+
if (metadataOnly) {
|
|
4375
|
+
if (derivedCount !== void 0) {
|
|
4376
|
+
await queueMetadataWrite(indexName, async () => {
|
|
4377
|
+
const upgraded = hooks.buildMetadata(indexName, derivedCount);
|
|
4378
|
+
if (upgraded === void 0) {
|
|
4379
|
+
return;
|
|
4380
|
+
}
|
|
4381
|
+
const bytes = await writeMetadataEnvelope(upgraded, { checksum: true });
|
|
4382
|
+
await adapter.save(metadataKey(indexName), bytes);
|
|
4383
|
+
}).catch(() => void 0);
|
|
4384
|
+
}
|
|
4385
|
+
return;
|
|
4386
|
+
}
|
|
4185
4387
|
const manager = hooks.getManager(indexName);
|
|
4186
4388
|
if (manager === void 0) {
|
|
4187
4389
|
return;
|
|
@@ -4190,33 +4392,58 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4190
4392
|
if (snapshotBytes === null) {
|
|
4191
4393
|
return;
|
|
4192
4394
|
}
|
|
4193
|
-
await loadSnapshotBundleBytes(snapshotBytes, {
|
|
4395
|
+
const checkpoint = await loadSnapshotBundleBytes(snapshotBytes, {
|
|
4194
4396
|
manager,
|
|
4195
4397
|
vectorFieldPaths: hooks.getVectorFieldPaths(indexName),
|
|
4196
4398
|
vectorIndexes: hooks.getVectorIndexes(indexName)
|
|
4197
4399
|
});
|
|
4400
|
+
const indexState = getOrCreateIndexState(indexName);
|
|
4401
|
+
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId += 1) {
|
|
4402
|
+
const persistedSeqNo = snapshotCheckpointFor(checkpoint, partitionId);
|
|
4403
|
+
if (persistedSeqNo > 0) {
|
|
4404
|
+
indexState.appliedSeqNoByPartition.set(partitionId, persistedSeqNo);
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
async function countStoredBundleDocuments(indexName) {
|
|
4409
|
+
const snapshotBytes = await adapter.load(snapshotStorageKey(indexName));
|
|
4410
|
+
if (snapshotBytes === null) {
|
|
4411
|
+
return 0;
|
|
4412
|
+
}
|
|
4413
|
+
return countSnapshotBundleDocuments(snapshotBytes);
|
|
4198
4414
|
}
|
|
4199
4415
|
return {
|
|
4200
4416
|
isActive() {
|
|
4201
4417
|
return true;
|
|
4202
4418
|
},
|
|
4203
|
-
async recover() {
|
|
4419
|
+
async recover(metadataOnly = false) {
|
|
4204
4420
|
const keys = await adapter.list("");
|
|
4205
4421
|
for (const key of keys) {
|
|
4206
4422
|
if (key.endsWith("/meta")) {
|
|
4207
|
-
await recoverIndex(key.slice(0, -"/meta".length));
|
|
4423
|
+
await recoverIndex(key.slice(0, -"/meta".length), metadataOnly);
|
|
4208
4424
|
}
|
|
4209
4425
|
}
|
|
4210
4426
|
startCheckpointTimer();
|
|
4211
4427
|
},
|
|
4212
|
-
|
|
4213
|
-
return
|
|
4428
|
+
recoverIndex(indexName) {
|
|
4429
|
+
return recoverIndex(indexName);
|
|
4430
|
+
},
|
|
4431
|
+
highestPersistedSeqNo(indexName, partitionId) {
|
|
4432
|
+
return indexes.get(indexName)?.appliedSeqNoByPartition.get(partitionId) ?? 0;
|
|
4214
4433
|
},
|
|
4215
4434
|
async recordMutation(record) {
|
|
4216
4435
|
const indexState = getOrCreateIndexState(record.indexName);
|
|
4217
|
-
const buffered = indexState.applyChain.then(() =>
|
|
4218
|
-
|
|
4219
|
-
|
|
4436
|
+
const buffered = indexState.applyChain.then(async () => {
|
|
4437
|
+
await record.apply();
|
|
4438
|
+
const appliedSeqNo2 = (indexState.appliedSeqNoByPartition.get(record.partitionId) ?? 0) + 1;
|
|
4439
|
+
indexState.appliedSeqNoByPartition.set(record.partitionId, appliedSeqNo2);
|
|
4440
|
+
return appliedSeqNo2;
|
|
4441
|
+
});
|
|
4442
|
+
indexState.applyChain = buffered.then(
|
|
4443
|
+
() => void 0,
|
|
4444
|
+
() => void 0
|
|
4445
|
+
);
|
|
4446
|
+
const appliedSeqNo = await buffered;
|
|
4220
4447
|
indexState.mutationsSinceCheckpoint += 1;
|
|
4221
4448
|
startCheckpointTimer();
|
|
4222
4449
|
if (indexState.mutationsSinceCheckpoint >= checkpointMutationThreshold) {
|
|
@@ -4224,7 +4451,7 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4224
4451
|
hooks.onFatalError(err instanceof Error ? err : new Error(String(err)));
|
|
4225
4452
|
});
|
|
4226
4453
|
}
|
|
4227
|
-
return
|
|
4454
|
+
return appliedSeqNo;
|
|
4228
4455
|
},
|
|
4229
4456
|
persistMetadata(indexName) {
|
|
4230
4457
|
return queueMetadataWrite(indexName, async () => {
|
|
@@ -4240,17 +4467,49 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4240
4467
|
checkpoint(indexName) {
|
|
4241
4468
|
return checkpointIndex(indexName);
|
|
4242
4469
|
},
|
|
4470
|
+
checkpointFromMemory(indexName) {
|
|
4471
|
+
return checkpointIndex(indexName, true);
|
|
4472
|
+
},
|
|
4243
4473
|
async checkpointAll() {
|
|
4244
4474
|
for (const indexName of [...indexes.keys()]) {
|
|
4245
4475
|
await checkpointIndex(indexName);
|
|
4246
4476
|
}
|
|
4247
4477
|
},
|
|
4248
4478
|
async removeIndex(indexName) {
|
|
4479
|
+
const indexState = indexes.get(indexName);
|
|
4480
|
+
if (indexState !== void 0) {
|
|
4481
|
+
indexState.unloading = true;
|
|
4482
|
+
try {
|
|
4483
|
+
await indexState.applyChain;
|
|
4484
|
+
if (indexState.checkpointInFlight !== null) await indexState.checkpointInFlight;
|
|
4485
|
+
await metadataWrites.get(indexName);
|
|
4486
|
+
} catch (error) {
|
|
4487
|
+
indexState.unloading = false;
|
|
4488
|
+
throw error;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4249
4491
|
indexes.delete(indexName);
|
|
4492
|
+
metadataWrites.delete(indexName);
|
|
4250
4493
|
for (const key of await adapter.list(`${indexName}/`)) {
|
|
4251
4494
|
await adapter.delete(key);
|
|
4252
4495
|
}
|
|
4253
4496
|
},
|
|
4497
|
+
async unloadIndex(indexName) {
|
|
4498
|
+
const indexState = indexes.get(indexName);
|
|
4499
|
+
if (indexState !== void 0) {
|
|
4500
|
+
indexState.unloading = true;
|
|
4501
|
+
try {
|
|
4502
|
+
await indexState.applyChain;
|
|
4503
|
+
if (indexState.checkpointInFlight !== null) await indexState.checkpointInFlight;
|
|
4504
|
+
await metadataWrites.get(indexName);
|
|
4505
|
+
} catch (error) {
|
|
4506
|
+
indexState.unloading = false;
|
|
4507
|
+
throw error;
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
indexes.delete(indexName);
|
|
4511
|
+
metadataWrites.delete(indexName);
|
|
4512
|
+
},
|
|
4254
4513
|
async reloadIndex(indexName) {
|
|
4255
4514
|
const indexState = getOrCreateIndexState(indexName);
|
|
4256
4515
|
const reload = indexState.applyChain.then(() => recoverIndex(indexName));
|
|
@@ -4274,21 +4533,22 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4274
4533
|
}
|
|
4275
4534
|
|
|
4276
4535
|
// src/engine/durability-integration.ts
|
|
4277
|
-
function buildMetadata(indexName, hooks) {
|
|
4536
|
+
function buildMetadata(indexName, hooks, documentCount) {
|
|
4278
4537
|
const config = hooks.getIndexConfig(indexName);
|
|
4279
4538
|
const manager = hooks.getManager(indexName);
|
|
4280
|
-
if (config === void 0
|
|
4539
|
+
if (config === void 0) {
|
|
4281
4540
|
return void 0;
|
|
4282
4541
|
}
|
|
4283
4542
|
const metadata = {
|
|
4284
4543
|
indexName,
|
|
4285
4544
|
schema: config.schema,
|
|
4286
4545
|
language: config.language,
|
|
4287
|
-
partitionCount: manager.partitionCount,
|
|
4546
|
+
partitionCount: manager?.partitionCount ?? config.partitionCount,
|
|
4288
4547
|
bm25Params: { k1: config.k1, b: config.b },
|
|
4289
4548
|
createdAt: Date.now(),
|
|
4290
4549
|
engineVersion: VERSION
|
|
4291
4550
|
};
|
|
4551
|
+
metadata.documentCount = documentCount ?? config.documentCount;
|
|
4292
4552
|
if (config.embedding) {
|
|
4293
4553
|
metadata.embedding = config.embedding;
|
|
4294
4554
|
}
|
|
@@ -4338,8 +4598,9 @@ function createDurabilityIntegration(tier, hooks) {
|
|
|
4338
4598
|
getManager: hooks.getManager,
|
|
4339
4599
|
getVectorFieldPaths: hooks.getVectorFieldPaths,
|
|
4340
4600
|
getVectorIndexes: hooks.getVectorIndexes,
|
|
4341
|
-
buildMetadata: (indexName) => buildMetadata(indexName, hooks),
|
|
4601
|
+
buildMetadata: (indexName, documentCount) => buildMetadata(indexName, hooks, documentCount),
|
|
4342
4602
|
createIndexFromMetadata: hooks.createIndexFromMetadata,
|
|
4603
|
+
recordCheckpoint: hooks.recordCheckpoint,
|
|
4343
4604
|
onFatalError: hooks.onFatalError
|
|
4344
4605
|
};
|
|
4345
4606
|
const manager = tier.kind === "wal" ? createDurabilityManager(tier.config, managerHooks) : createSnapshotOnlyManager(tier.adapter, tier.config, managerHooks, hooks.checkpointPublisher);
|
|
@@ -4378,9 +4639,9 @@ function createDurabilityFromTier(tier, wiring) {
|
|
|
4378
4639
|
}
|
|
4379
4640
|
return createDurabilityIntegration(tier, {
|
|
4380
4641
|
checkpointPublisher: { publishPartitions: wiring.publishCheckpointedPartitions },
|
|
4381
|
-
getManager: (indexName) => wiring.indexRegistry.has(indexName) ? wiring.
|
|
4642
|
+
getManager: (indexName) => wiring.indexRegistry.has(indexName) ? wiring.getManager(indexName) : void 0,
|
|
4382
4643
|
getVectorFieldPaths: (indexName) => wiring.indexRegistry.get(indexName)?.vectorFieldPaths ?? /* @__PURE__ */ new Set(),
|
|
4383
|
-
getVectorIndexes: (indexName) => wiring.indexRegistry.has(indexName) ? wiring.
|
|
4644
|
+
getVectorIndexes: (indexName) => (wiring.indexRegistry.has(indexName) ? wiring.getManager(indexName)?.getVectorIndexes() : void 0) ?? /* @__PURE__ */ new Map(),
|
|
4384
4645
|
getIndexConfig: (indexName) => {
|
|
4385
4646
|
const entry = wiring.indexRegistry.get(indexName);
|
|
4386
4647
|
if (entry === void 0) {
|
|
@@ -4393,6 +4654,8 @@ function createDurabilityFromTier(tier, wiring) {
|
|
|
4393
4654
|
return {
|
|
4394
4655
|
...entry.indexUuid !== null ? { indexUuid: entry.indexUuid } : {},
|
|
4395
4656
|
...entry.heldPartitions !== null ? { heldPartitions: entry.heldPartitions } : {},
|
|
4657
|
+
documentCount: entry.documentCount,
|
|
4658
|
+
partitionCount: entry.partitionCount,
|
|
4396
4659
|
schema: flattenSchema(entry.config.schema),
|
|
4397
4660
|
language: entry.language.name,
|
|
4398
4661
|
k1: entry.config.bm25?.k1 ?? 1.2,
|
|
@@ -4411,8 +4674,399 @@ function createDurabilityFromTier(tier, wiring) {
|
|
|
4411
4674
|
...entry.config.vectorPromotion !== void 0 ? { vectorPromotion: entry.config.vectorPromotion } : {}
|
|
4412
4675
|
};
|
|
4413
4676
|
},
|
|
4414
|
-
createIndexFromMetadata: wiring.createIndexFromMetadata,
|
|
4415
|
-
|
|
4677
|
+
createIndexFromMetadata: wiring.createIndexFromMetadata,
|
|
4678
|
+
recordCheckpoint: wiring.recordCheckpoint,
|
|
4679
|
+
onFatalError: wiring.emitFatalError
|
|
4680
|
+
});
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
// src/engine/index-state/entry.ts
|
|
4684
|
+
var DEFAULT_MAX_REOPEN_WAITERS = 64;
|
|
4685
|
+
var MAX_REOPEN_FAILURES = 5;
|
|
4686
|
+
var MIN_SWEEP_INTERVAL_MS = 100;
|
|
4687
|
+
var MAX_SWEEP_INTERVAL_MS = 6e4;
|
|
4688
|
+
function createLifecycleEntry(state) {
|
|
4689
|
+
return {
|
|
4690
|
+
state,
|
|
4691
|
+
lastAccessAt: Date.now(),
|
|
4692
|
+
activeOperations: 0,
|
|
4693
|
+
reopenCount: 0,
|
|
4694
|
+
reopenFailures: 0,
|
|
4695
|
+
nextReopenAt: 0,
|
|
4696
|
+
cachedError: null,
|
|
4697
|
+
reopenPromise: null,
|
|
4698
|
+
waiterCount: 0,
|
|
4699
|
+
closePromise: null,
|
|
4700
|
+
dropPromise: null,
|
|
4701
|
+
drainResolvers: /* @__PURE__ */ new Set()
|
|
4702
|
+
};
|
|
4703
|
+
}
|
|
4704
|
+
function validateLimit(name, value, allowZero = false) {
|
|
4705
|
+
if (value === void 0) return;
|
|
4706
|
+
const minimum = allowZero ? 0 : 1;
|
|
4707
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
4708
|
+
const requirement = allowZero ? "a non-negative safe integer" : "a positive safe integer";
|
|
4709
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `${name} must be ${requirement}`, {
|
|
4710
|
+
field: name,
|
|
4711
|
+
value
|
|
4712
|
+
});
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
function cachedRecoveryError(error, indexName) {
|
|
4716
|
+
if (error instanceof NarsilError) return error;
|
|
4717
|
+
return new NarsilError(ErrorCodes.PERSISTENCE_LOAD_FAILED, `Index "${indexName}" could not be reopened`, {
|
|
4718
|
+
indexName,
|
|
4719
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
// src/engine/index-state/index.ts
|
|
4724
|
+
function createIndexStateCoordinator(config, callbacks) {
|
|
4725
|
+
validateLimit("lifecycle.idleTimeoutMs", config?.idleTimeoutMs);
|
|
4726
|
+
validateLimit("lifecycle.maxOpenIndexes", config?.maxOpenIndexes);
|
|
4727
|
+
validateLimit("lifecycle.maxOpenBytes", config?.maxOpenBytes);
|
|
4728
|
+
validateLimit("lifecycle.maxReopenWaiters", config?.maxReopenWaiters, true);
|
|
4729
|
+
const entries = /* @__PURE__ */ new Map();
|
|
4730
|
+
const waiterLimit = config?.maxReopenWaiters ?? DEFAULT_MAX_REOPEN_WAITERS;
|
|
4731
|
+
let sweepTimer = null;
|
|
4732
|
+
function entryOf(indexName) {
|
|
4733
|
+
const entry = entries.get(indexName);
|
|
4734
|
+
if (entry === void 0) {
|
|
4735
|
+
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `Index "${indexName}" does not exist`, { indexName });
|
|
4736
|
+
}
|
|
4737
|
+
return entry;
|
|
4738
|
+
}
|
|
4739
|
+
function publicState(entry) {
|
|
4740
|
+
return entry.state === "closing" || entry.state === "dropping" ? "open" : entry.state;
|
|
4741
|
+
}
|
|
4742
|
+
async function waitForDrain(entry) {
|
|
4743
|
+
if (entry.activeOperations === 0) return;
|
|
4744
|
+
await new Promise((resolve) => entry.drainResolvers.add(resolve));
|
|
4745
|
+
}
|
|
4746
|
+
function markClosed(entry) {
|
|
4747
|
+
entry.state = "closed";
|
|
4748
|
+
entry.cachedError = null;
|
|
4749
|
+
entry.reopenFailures = 0;
|
|
4750
|
+
}
|
|
4751
|
+
function releaseEntry(entry) {
|
|
4752
|
+
entry.activeOperations -= 1;
|
|
4753
|
+
if (entry.activeOperations !== 0) return;
|
|
4754
|
+
for (const resolve of entry.drainResolvers) resolve();
|
|
4755
|
+
entry.drainResolvers.clear();
|
|
4756
|
+
}
|
|
4757
|
+
async function waitForTransition(indexName, entry, transition) {
|
|
4758
|
+
if (entry.waiterCount >= waiterLimit) {
|
|
4759
|
+
throw new NarsilError(
|
|
4760
|
+
ErrorCodes.INDEX_REOPEN_CAPACITY_EXHAUSTED,
|
|
4761
|
+
`Index "${indexName}" has reached its reopen waiter limit`,
|
|
4762
|
+
{ indexName, maximum: waiterLimit }
|
|
4763
|
+
);
|
|
4764
|
+
}
|
|
4765
|
+
entry.waiterCount += 1;
|
|
4766
|
+
try {
|
|
4767
|
+
await transition;
|
|
4768
|
+
} finally {
|
|
4769
|
+
entry.waiterCount -= 1;
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
async function awaitPendingTransitions(indexName) {
|
|
4773
|
+
let entry = entryOf(indexName);
|
|
4774
|
+
while (true) {
|
|
4775
|
+
if (entry.state === "dropping" && entry.dropPromise !== null) {
|
|
4776
|
+
await entry.dropPromise;
|
|
4777
|
+
entry = entryOf(indexName);
|
|
4778
|
+
continue;
|
|
4779
|
+
}
|
|
4780
|
+
if (entry.state === "closing" && entry.closePromise !== null) {
|
|
4781
|
+
await waitForTransition(
|
|
4782
|
+
indexName,
|
|
4783
|
+
entry,
|
|
4784
|
+
entry.closePromise.catch(() => void 0)
|
|
4785
|
+
);
|
|
4786
|
+
entry = entryOf(indexName);
|
|
4787
|
+
continue;
|
|
4788
|
+
}
|
|
4789
|
+
return entry;
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
async function reopen(indexName, explicit) {
|
|
4793
|
+
const entry = await awaitPendingTransitions(indexName);
|
|
4794
|
+
if (entry.state === "open") return;
|
|
4795
|
+
if (explicit) {
|
|
4796
|
+
entry.reopenFailures = 0;
|
|
4797
|
+
entry.nextReopenAt = 0;
|
|
4798
|
+
entry.cachedError = null;
|
|
4799
|
+
if (entry.state === "reopen-failed") entry.state = "closed";
|
|
4800
|
+
}
|
|
4801
|
+
if (entry.state === "reopen-failed") throw entry.cachedError;
|
|
4802
|
+
if (entry.reopenPromise !== null) {
|
|
4803
|
+
await waitForTransition(indexName, entry, entry.reopenPromise);
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
if (!explicit && entry.cachedError !== null && Date.now() < entry.nextReopenAt) {
|
|
4807
|
+
throw entry.cachedError;
|
|
4808
|
+
}
|
|
4809
|
+
const run = callbacks.reopen(indexName).then(() => {
|
|
4810
|
+
entry.state = "open";
|
|
4811
|
+
entry.reopenCount += 1;
|
|
4812
|
+
entry.reopenFailures = 0;
|
|
4813
|
+
entry.nextReopenAt = 0;
|
|
4814
|
+
entry.cachedError = null;
|
|
4815
|
+
}).catch((error) => {
|
|
4816
|
+
entry.reopenFailures += 1;
|
|
4817
|
+
entry.cachedError = cachedRecoveryError(error, indexName);
|
|
4818
|
+
entry.nextReopenAt = Date.now() + 100 * 2 ** (entry.reopenFailures - 1);
|
|
4819
|
+
entry.state = entry.reopenFailures >= MAX_REOPEN_FAILURES ? "reopen-failed" : "closed";
|
|
4820
|
+
throw entry.cachedError;
|
|
4821
|
+
}).finally(() => {
|
|
4822
|
+
entry.reopenPromise = null;
|
|
4823
|
+
});
|
|
4824
|
+
entry.reopenPromise = run;
|
|
4825
|
+
await run;
|
|
4826
|
+
}
|
|
4827
|
+
async function closeEntry(indexName, automatic) {
|
|
4828
|
+
let entry = entryOf(indexName);
|
|
4829
|
+
while (true) {
|
|
4830
|
+
if (entry.state === "dropping" && entry.dropPromise !== null) {
|
|
4831
|
+
if (automatic) return false;
|
|
4832
|
+
await entry.dropPromise;
|
|
4833
|
+
entry = entryOf(indexName);
|
|
4834
|
+
continue;
|
|
4835
|
+
}
|
|
4836
|
+
if (entry.reopenPromise !== null) {
|
|
4837
|
+
if (automatic) return false;
|
|
4838
|
+
await entry.reopenPromise.catch(() => void 0);
|
|
4839
|
+
entry = entryOf(indexName);
|
|
4840
|
+
continue;
|
|
4841
|
+
}
|
|
4842
|
+
break;
|
|
4843
|
+
}
|
|
4844
|
+
if (entry.state === "closed" || entry.state === "reopen-failed") return true;
|
|
4845
|
+
if (entry.state === "closing" && entry.closePromise !== null) {
|
|
4846
|
+
if (!automatic) await entry.closePromise;
|
|
4847
|
+
return !automatic;
|
|
4848
|
+
}
|
|
4849
|
+
if (automatic && (entry.activeOperations > 0 || !callbacks.canCloseAutomatically(indexName))) return false;
|
|
4850
|
+
entry.state = "closing";
|
|
4851
|
+
let irreversible = false;
|
|
4852
|
+
const run = (async () => {
|
|
4853
|
+
await waitForDrain(entry);
|
|
4854
|
+
await callbacks.close(indexName, () => {
|
|
4855
|
+
irreversible = true;
|
|
4856
|
+
});
|
|
4857
|
+
markClosed(entry);
|
|
4858
|
+
})().catch((error) => {
|
|
4859
|
+
if (irreversible) markClosed(entry);
|
|
4860
|
+
else entry.state = "open";
|
|
4861
|
+
throw error;
|
|
4862
|
+
}).finally(() => {
|
|
4863
|
+
entry.closePromise = null;
|
|
4864
|
+
});
|
|
4865
|
+
entry.closePromise = run;
|
|
4866
|
+
if (automatic) {
|
|
4867
|
+
await run.catch(() => void 0);
|
|
4868
|
+
return publicState(entry) === "closed";
|
|
4869
|
+
}
|
|
4870
|
+
await run;
|
|
4871
|
+
return true;
|
|
4872
|
+
}
|
|
4873
|
+
async function dropEntry(indexName, action) {
|
|
4874
|
+
let entry = entryOf(indexName);
|
|
4875
|
+
while (true) {
|
|
4876
|
+
if (entry.dropPromise !== null) {
|
|
4877
|
+
await entry.dropPromise;
|
|
4878
|
+
return;
|
|
4879
|
+
}
|
|
4880
|
+
if (entry.reopenPromise !== null) {
|
|
4881
|
+
await entry.reopenPromise.catch(() => void 0);
|
|
4882
|
+
entry = entryOf(indexName);
|
|
4883
|
+
continue;
|
|
4884
|
+
}
|
|
4885
|
+
if (entry.closePromise !== null) {
|
|
4886
|
+
await entry.closePromise.catch(() => void 0);
|
|
4887
|
+
entry = entryOf(indexName);
|
|
4888
|
+
continue;
|
|
4889
|
+
}
|
|
4890
|
+
break;
|
|
4891
|
+
}
|
|
4892
|
+
const previousState = entry.state;
|
|
4893
|
+
entry.state = "dropping";
|
|
4894
|
+
const run = (async () => {
|
|
4895
|
+
await waitForDrain(entry);
|
|
4896
|
+
await action();
|
|
4897
|
+
entries.delete(indexName);
|
|
4898
|
+
})().catch((error) => {
|
|
4899
|
+
entry.state = previousState;
|
|
4900
|
+
throw error;
|
|
4901
|
+
}).finally(() => {
|
|
4902
|
+
entry.dropPromise = null;
|
|
4903
|
+
});
|
|
4904
|
+
entry.dropPromise = run;
|
|
4905
|
+
await run;
|
|
4906
|
+
}
|
|
4907
|
+
function exceedsLimits() {
|
|
4908
|
+
let open = 0;
|
|
4909
|
+
let bytes = 0;
|
|
4910
|
+
for (const [name, entry] of entries) {
|
|
4911
|
+
if (entry.state !== "open") continue;
|
|
4912
|
+
open += 1;
|
|
4913
|
+
bytes += callbacks.estimateBytes(name);
|
|
4914
|
+
}
|
|
4915
|
+
return config?.maxOpenIndexes !== void 0 && open > config.maxOpenIndexes || config?.maxOpenBytes !== void 0 && bytes > config.maxOpenBytes;
|
|
4916
|
+
}
|
|
4917
|
+
async function enforceLimits(protectedName) {
|
|
4918
|
+
if (config?.maxOpenIndexes === void 0 && config?.maxOpenBytes === void 0) return;
|
|
4919
|
+
while (exceedsLimits()) {
|
|
4920
|
+
const candidates = [...entries].filter(([name, entry]) => name !== protectedName && entry.state === "open" && entry.activeOperations === 0).sort((left, right) => left[1].lastAccessAt - right[1].lastAccessAt);
|
|
4921
|
+
const candidate = candidates[0];
|
|
4922
|
+
if (candidate === void 0 || !await closeEntry(candidate[0], true)) return;
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
async function sweep() {
|
|
4926
|
+
const timeout = config?.idleTimeoutMs;
|
|
4927
|
+
if (timeout !== void 0 && timeout > 0) {
|
|
4928
|
+
const cutoff = Date.now() - timeout;
|
|
4929
|
+
for (const [name, entry] of entries) {
|
|
4930
|
+
if (entry.state === "open" && entry.lastAccessAt <= cutoff) await closeEntry(name, true);
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
await enforceLimits();
|
|
4934
|
+
}
|
|
4935
|
+
if (config !== void 0) {
|
|
4936
|
+
const requested = config.idleTimeoutMs === void 0 ? MAX_SWEEP_INTERVAL_MS : config.idleTimeoutMs / 2;
|
|
4937
|
+
const interval = Math.min(MAX_SWEEP_INTERVAL_MS, Math.max(MIN_SWEEP_INTERVAL_MS, requested));
|
|
4938
|
+
sweepTimer = setInterval(() => void sweep().catch(() => void 0), interval);
|
|
4939
|
+
if (typeof sweepTimer.unref === "function") sweepTimer.unref();
|
|
4940
|
+
}
|
|
4941
|
+
return {
|
|
4942
|
+
async registerOpen(indexName) {
|
|
4943
|
+
entries.set(indexName, createLifecycleEntry("open"));
|
|
4944
|
+
await enforceLimits(indexName);
|
|
4945
|
+
},
|
|
4946
|
+
registerClosed(indexName) {
|
|
4947
|
+
entries.set(indexName, createLifecycleEntry("closed"));
|
|
4948
|
+
},
|
|
4949
|
+
forget(indexName) {
|
|
4950
|
+
entries.delete(indexName);
|
|
4951
|
+
},
|
|
4952
|
+
async acquire(indexName, markActive = true) {
|
|
4953
|
+
let entry = entryOf(indexName);
|
|
4954
|
+
const opened = entry.state !== "open";
|
|
4955
|
+
await reopen(indexName, false);
|
|
4956
|
+
entry = entryOf(indexName);
|
|
4957
|
+
while (entry.state !== "open") {
|
|
4958
|
+
await reopen(indexName, false);
|
|
4959
|
+
entry = entryOf(indexName);
|
|
4960
|
+
}
|
|
4961
|
+
if (markActive) entry.lastAccessAt = Date.now();
|
|
4962
|
+
entry.activeOperations += 1;
|
|
4963
|
+
try {
|
|
4964
|
+
if (opened) await enforceLimits(indexName);
|
|
4965
|
+
} catch (error) {
|
|
4966
|
+
releaseEntry(entry);
|
|
4967
|
+
throw error;
|
|
4968
|
+
}
|
|
4969
|
+
let released = false;
|
|
4970
|
+
return () => {
|
|
4971
|
+
if (released) return;
|
|
4972
|
+
released = true;
|
|
4973
|
+
releaseEntry(entry);
|
|
4974
|
+
};
|
|
4975
|
+
},
|
|
4976
|
+
async open(indexName) {
|
|
4977
|
+
await reopen(indexName, true);
|
|
4978
|
+
let entry = entryOf(indexName);
|
|
4979
|
+
while (entry.state !== "open") {
|
|
4980
|
+
await reopen(indexName, true);
|
|
4981
|
+
entry = entryOf(indexName);
|
|
4982
|
+
}
|
|
4983
|
+
entry.lastAccessAt = Date.now();
|
|
4984
|
+
entry.activeOperations += 1;
|
|
4985
|
+
try {
|
|
4986
|
+
await enforceLimits(indexName);
|
|
4987
|
+
} finally {
|
|
4988
|
+
releaseEntry(entry);
|
|
4989
|
+
}
|
|
4990
|
+
},
|
|
4991
|
+
async close(indexName) {
|
|
4992
|
+
await closeEntry(indexName, false);
|
|
4993
|
+
},
|
|
4994
|
+
drop(indexName, action) {
|
|
4995
|
+
return dropEntry(indexName, action);
|
|
4996
|
+
},
|
|
4997
|
+
stateOf(indexName) {
|
|
4998
|
+
return publicState(entryOf(indexName));
|
|
4999
|
+
},
|
|
5000
|
+
reopenCount(indexName) {
|
|
5001
|
+
return entryOf(indexName).reopenCount;
|
|
5002
|
+
},
|
|
5003
|
+
counts() {
|
|
5004
|
+
let open = 0;
|
|
5005
|
+
let closed = 0;
|
|
5006
|
+
let reopens = 0;
|
|
5007
|
+
for (const entry of entries.values()) {
|
|
5008
|
+
if (entry.state === "open" || entry.state === "closing") open += 1;
|
|
5009
|
+
else closed += 1;
|
|
5010
|
+
reopens += entry.reopenCount;
|
|
5011
|
+
}
|
|
5012
|
+
return { open, closed, reopens };
|
|
5013
|
+
},
|
|
5014
|
+
dispose() {
|
|
5015
|
+
if (sweepTimer !== null) clearInterval(sweepTimer);
|
|
5016
|
+
sweepTimer = null;
|
|
5017
|
+
entries.clear();
|
|
5018
|
+
}
|
|
5019
|
+
};
|
|
5020
|
+
}
|
|
5021
|
+
|
|
5022
|
+
// src/engine/index-state-wiring.ts
|
|
5023
|
+
function wireIndexState(wiring) {
|
|
5024
|
+
const { durability, executor, orchestrator, analysisRebuild, indexRegistry, rebalancingIndexes } = wiring;
|
|
5025
|
+
return createIndexStateCoordinator(wiring.config, {
|
|
5026
|
+
async reopen(indexName) {
|
|
5027
|
+
if (durability === null) {
|
|
5028
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "An index needs durability before it can reopen");
|
|
5029
|
+
}
|
|
5030
|
+
try {
|
|
5031
|
+
await durability.manager.recoverIndex(indexName);
|
|
5032
|
+
await analysisRebuild.review(indexName);
|
|
5033
|
+
await orchestrator.openIndex(indexName);
|
|
5034
|
+
await wiring.onOpen?.(indexName);
|
|
5035
|
+
} catch (error) {
|
|
5036
|
+
await orchestrator.closeIndex(indexName).catch(() => void 0);
|
|
5037
|
+
await durability.manager.unloadIndex(indexName).catch(() => void 0);
|
|
5038
|
+
if (executor.getManager(indexName) !== void 0) executor.dropIndex(indexName);
|
|
5039
|
+
throw error;
|
|
5040
|
+
}
|
|
5041
|
+
},
|
|
5042
|
+
async close(indexName, markIrreversible) {
|
|
5043
|
+
if (durability === null) {
|
|
5044
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "An index needs durability before it can close");
|
|
5045
|
+
}
|
|
5046
|
+
const manager = wiring.requireManager(indexName);
|
|
5047
|
+
const documentCount = manager.countDocuments();
|
|
5048
|
+
const partitionCount = manager.partitionCount;
|
|
5049
|
+
await (durability.manager.checkpointFromMemory?.(indexName) ?? durability.manager.checkpoint(indexName));
|
|
5050
|
+
await orchestrator.closeIndex(indexName);
|
|
5051
|
+
await durability.manager.unloadIndex(indexName);
|
|
5052
|
+
markIrreversible();
|
|
5053
|
+
try {
|
|
5054
|
+
await wiring.onClose?.(indexName);
|
|
5055
|
+
} finally {
|
|
5056
|
+
if (executor.getManager(indexName) !== void 0) executor.dropIndex(indexName);
|
|
5057
|
+
const entry = indexRegistry.get(indexName);
|
|
5058
|
+
if (entry !== void 0) {
|
|
5059
|
+
entry.documentCount = documentCount;
|
|
5060
|
+
entry.partitionCount = partitionCount;
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
},
|
|
5064
|
+
canCloseAutomatically(indexName) {
|
|
5065
|
+
return !rebalancingIndexes.has(indexName) && !analysisRebuild.isRunning(indexName) && !orchestrator.isIndexBusy(indexName);
|
|
5066
|
+
},
|
|
5067
|
+
estimateBytes(indexName) {
|
|
5068
|
+
return executor.getManager(indexName)?.estimateMemoryBytes() ?? 0;
|
|
5069
|
+
}
|
|
4416
5070
|
});
|
|
4417
5071
|
}
|
|
4418
5072
|
|
|
@@ -4441,7 +5095,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4441
5095
|
const reloadChains = /* @__PURE__ */ new Map();
|
|
4442
5096
|
let broadcastTimer = null;
|
|
4443
5097
|
let stopped = false;
|
|
4444
|
-
function
|
|
5098
|
+
function toError4(err) {
|
|
4445
5099
|
return err instanceof Error ? err : new Error(String(err));
|
|
4446
5100
|
}
|
|
4447
5101
|
function pruneStaleForeignStats(entries) {
|
|
@@ -4454,7 +5108,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4454
5108
|
}
|
|
4455
5109
|
function scheduleReload(indexName) {
|
|
4456
5110
|
const chain = reloadChains.get(indexName) ?? Promise.resolve();
|
|
4457
|
-
const next = chain.then(() => reloadIndex(indexName)).catch((err) => onError(
|
|
5111
|
+
const next = chain.then(() => reloadIndex(indexName)).catch((err) => onError(toError4(err)));
|
|
4458
5112
|
reloadChains.set(indexName, next);
|
|
4459
5113
|
}
|
|
4460
5114
|
function storeForeignStats(indexName, sourceInstanceId, stats) {
|
|
@@ -4480,7 +5134,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4480
5134
|
}
|
|
4481
5135
|
storeForeignStats(event.indexName, event.instanceId, event.stats);
|
|
4482
5136
|
} catch (err) {
|
|
4483
|
-
onError(
|
|
5137
|
+
onError(toError4(err));
|
|
4484
5138
|
}
|
|
4485
5139
|
}
|
|
4486
5140
|
async function publishOwnStatistics() {
|
|
@@ -4502,7 +5156,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4502
5156
|
}
|
|
4503
5157
|
});
|
|
4504
5158
|
} catch (err) {
|
|
4505
|
-
onError(
|
|
5159
|
+
onError(toError4(err));
|
|
4506
5160
|
}
|
|
4507
5161
|
}
|
|
4508
5162
|
}
|
|
@@ -4521,7 +5175,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4521
5175
|
sourceInstanceId: instanceId
|
|
4522
5176
|
});
|
|
4523
5177
|
} catch (err) {
|
|
4524
|
-
onError(
|
|
5178
|
+
onError(toError4(err));
|
|
4525
5179
|
}
|
|
4526
5180
|
},
|
|
4527
5181
|
broadcastStats(indexName) {
|
|
@@ -4566,7 +5220,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4566
5220
|
try {
|
|
4567
5221
|
await adapter.shutdown();
|
|
4568
5222
|
} catch (err) {
|
|
4569
|
-
onError(
|
|
5223
|
+
onError(toError4(err));
|
|
4570
5224
|
}
|
|
4571
5225
|
}
|
|
4572
5226
|
};
|
|
@@ -4608,7 +5262,7 @@ async function transferIndexToPool(indexName, pool, config, manager) {
|
|
|
4608
5262
|
}
|
|
4609
5263
|
|
|
4610
5264
|
// src/engine/orchestration/eligibility.ts
|
|
4611
|
-
function
|
|
5265
|
+
function toError3(value) {
|
|
4612
5266
|
return value instanceof Error ? value : new Error(String(value));
|
|
4613
5267
|
}
|
|
4614
5268
|
function isDeterministicFailure(error) {
|
|
@@ -4946,14 +5600,34 @@ async function awaitCompactions(state) {
|
|
|
4946
5600
|
var DEFAULT_BACKPRESSURE_LIMIT = 100;
|
|
4947
5601
|
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
4948
5602
|
var SHUTDOWN_TIMEOUT = 5e3;
|
|
5603
|
+
var CONSECUTIVE_TIMEOUTS_BEFORE_DEATH = 3;
|
|
5604
|
+
function errorFromEventLike(event) {
|
|
5605
|
+
if (event instanceof Error) {
|
|
5606
|
+
return event;
|
|
5607
|
+
}
|
|
5608
|
+
if (typeof event === "object" && event !== null) {
|
|
5609
|
+
const detail = event;
|
|
5610
|
+
if (detail.error instanceof Error) {
|
|
5611
|
+
return detail.error;
|
|
5612
|
+
}
|
|
5613
|
+
if (typeof detail.message === "string" && detail.message.length > 0) {
|
|
5614
|
+
return new Error(detail.message);
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
return new Error("The worker reported an error event carrying no detail");
|
|
5618
|
+
}
|
|
4949
5619
|
function createWorkerExecutor(worker, config) {
|
|
4950
|
-
const backpressureLimit = DEFAULT_BACKPRESSURE_LIMIT;
|
|
4951
|
-
const requestTimeout = DEFAULT_REQUEST_TIMEOUT;
|
|
5620
|
+
const backpressureLimit = config?.backpressureLimit ?? DEFAULT_BACKPRESSURE_LIMIT;
|
|
5621
|
+
const requestTimeout = config?.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
4952
5622
|
const pending = /* @__PURE__ */ new Map();
|
|
5623
|
+
let deathError = null;
|
|
5624
|
+
let shutdownRequested = false;
|
|
5625
|
+
let consecutiveTimeouts = 0;
|
|
4953
5626
|
function processResponse(msg) {
|
|
4954
5627
|
if (!isValidWorkerResponse(msg)) {
|
|
4955
5628
|
return;
|
|
4956
5629
|
}
|
|
5630
|
+
consecutiveTimeouts = 0;
|
|
4957
5631
|
const response = msg;
|
|
4958
5632
|
const entry = pending.get(response.requestId);
|
|
4959
5633
|
if (!entry) {
|
|
@@ -4967,15 +5641,36 @@ function createWorkerExecutor(worker, config) {
|
|
|
4967
5641
|
entry.resolve(response.data);
|
|
4968
5642
|
}
|
|
4969
5643
|
}
|
|
5644
|
+
function handleDeath(cause) {
|
|
5645
|
+
if (deathError !== null) {
|
|
5646
|
+
return;
|
|
5647
|
+
}
|
|
5648
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
5649
|
+
deathError = new NarsilError(ErrorCodes.WORKER_CRASHED, `Worker died: ${message}`);
|
|
5650
|
+
for (const [id, entry] of pending) {
|
|
5651
|
+
clearTimeout(entry.timeoutId);
|
|
5652
|
+
entry.reject(deathError);
|
|
5653
|
+
pending.delete(id);
|
|
5654
|
+
}
|
|
5655
|
+
if (!shutdownRequested) {
|
|
5656
|
+
config?.onDeath?.(cause instanceof Error ? cause : new Error(message));
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
4970
5659
|
if (typeof worker.on === "function") {
|
|
4971
5660
|
worker.on("message", (msg) => processResponse(msg));
|
|
5661
|
+
worker.on("error", (cause) => handleDeath(cause));
|
|
5662
|
+
worker.on("exit", (code) => handleDeath(new Error(`Worker exited with code ${String(code)}`)));
|
|
4972
5663
|
} else if (typeof worker.addEventListener === "function") {
|
|
4973
5664
|
worker.addEventListener("message", (event) => {
|
|
4974
5665
|
const msg = event.data;
|
|
4975
5666
|
processResponse(msg);
|
|
4976
5667
|
});
|
|
5668
|
+
worker.addEventListener("error", (event) => handleDeath(errorFromEventLike(event)));
|
|
4977
5669
|
}
|
|
4978
5670
|
function execute(action) {
|
|
5671
|
+
if (deathError !== null) {
|
|
5672
|
+
return Promise.reject(deathError);
|
|
5673
|
+
}
|
|
4979
5674
|
if (pending.size >= backpressureLimit) {
|
|
4980
5675
|
return Promise.reject(
|
|
4981
5676
|
new NarsilError(ErrorCodes.WORKER_BUSY, `Backpressure limit of ${backpressureLimit} pending requests reached`)
|
|
@@ -4987,6 +5682,10 @@ function createWorkerExecutor(worker, config) {
|
|
|
4987
5682
|
const timeoutId = setTimeout(() => {
|
|
4988
5683
|
pending.delete(requestId);
|
|
4989
5684
|
reject(new NarsilError(ErrorCodes.WORKER_TIMEOUT, `Request ${requestId} timed out after ${requestTimeout}ms`));
|
|
5685
|
+
consecutiveTimeouts += 1;
|
|
5686
|
+
if (consecutiveTimeouts >= CONSECUTIVE_TIMEOUTS_BEFORE_DEATH) {
|
|
5687
|
+
handleDeath(new Error(`no answer to ${CONSECUTIVE_TIMEOUTS_BEFORE_DEATH} consecutive requests`));
|
|
5688
|
+
}
|
|
4990
5689
|
}, requestTimeout);
|
|
4991
5690
|
pending.set(requestId, {
|
|
4992
5691
|
resolve,
|
|
@@ -4997,6 +5696,10 @@ function createWorkerExecutor(worker, config) {
|
|
|
4997
5696
|
});
|
|
4998
5697
|
}
|
|
4999
5698
|
async function shutdown() {
|
|
5699
|
+
shutdownRequested = true;
|
|
5700
|
+
if (deathError !== null) {
|
|
5701
|
+
return;
|
|
5702
|
+
}
|
|
5000
5703
|
const requestId = createRequestId();
|
|
5001
5704
|
const shutdownAction = { type: "shutdown", requestId };
|
|
5002
5705
|
const shutdownPromise = new Promise((resolve, reject) => {
|
|
@@ -5038,15 +5741,15 @@ async function createWorkerFactory(entryPoint) {
|
|
|
5038
5741
|
const resolvedEntry = resolveEntryPoint();
|
|
5039
5742
|
if (runtime.supportsWorkerThreads) {
|
|
5040
5743
|
const workerThreadsModule = await import('worker_threads');
|
|
5041
|
-
return function nodeFactory(_workerId) {
|
|
5744
|
+
return function nodeFactory(_workerId, onDeath) {
|
|
5042
5745
|
const instance = new workerThreadsModule.Worker(new URL(resolvedEntry));
|
|
5043
|
-
return createWorkerExecutor(instance);
|
|
5746
|
+
return createWorkerExecutor(instance, { onDeath });
|
|
5044
5747
|
};
|
|
5045
5748
|
}
|
|
5046
5749
|
if (runtime.supportsWebWorkers) {
|
|
5047
|
-
return function webFactory(_workerId) {
|
|
5750
|
+
return function webFactory(_workerId, onDeath) {
|
|
5048
5751
|
const instance = new Worker(resolvedEntry, { type: "module" });
|
|
5049
|
-
return createWorkerExecutor(instance);
|
|
5752
|
+
return createWorkerExecutor(instance, { onDeath });
|
|
5050
5753
|
};
|
|
5051
5754
|
}
|
|
5052
5755
|
throw new NarsilError(ErrorCodes.WORKER_CRASHED, `No worker support available on runtime "${runtime.runtime}"`);
|
|
@@ -5102,7 +5805,13 @@ async function runPromotion(state, reason) {
|
|
|
5102
5805
|
return;
|
|
5103
5806
|
}
|
|
5104
5807
|
const factory = await createWorkerFactory();
|
|
5105
|
-
const pool = createWorkerPool({
|
|
5808
|
+
const pool = createWorkerPool({
|
|
5809
|
+
count: state.config?.workers?.count,
|
|
5810
|
+
workerFactory: factory,
|
|
5811
|
+
onWorkerCrash(workerId, indexNames, error) {
|
|
5812
|
+
state.callbacks?.onWorkerCrash?.(workerId, indexNames, error);
|
|
5813
|
+
}
|
|
5814
|
+
});
|
|
5106
5815
|
for (const name of promotable) {
|
|
5107
5816
|
pool.addIndexToAll(name);
|
|
5108
5817
|
}
|
|
@@ -5139,7 +5848,7 @@ async function promoteBeforeBatch(state, indexName, incomingCount) {
|
|
|
5139
5848
|
if (!result.shouldPromote) return;
|
|
5140
5849
|
state.promotionInProgress = true;
|
|
5141
5850
|
const run = runPromotion(state, result.reason).catch((err) => {
|
|
5142
|
-
const error =
|
|
5851
|
+
const error = toError3(err);
|
|
5143
5852
|
state.promotionBlocked = isDeterministicFailure(error);
|
|
5144
5853
|
state.promotionInProgress = false;
|
|
5145
5854
|
state.callbacks?.onPromotionFailure?.(result.reason, error, !state.promotionBlocked);
|
|
@@ -5160,7 +5869,7 @@ async function checkPromotion(state) {
|
|
|
5160
5869
|
state.promotionInProgress = true;
|
|
5161
5870
|
setTimeout(() => {
|
|
5162
5871
|
const run = runPromotion(state, result.reason).catch((err) => {
|
|
5163
|
-
const error =
|
|
5872
|
+
const error = toError3(err);
|
|
5164
5873
|
state.promotionBlocked = isDeterministicFailure(error);
|
|
5165
5874
|
state.promotionInProgress = false;
|
|
5166
5875
|
state.callbacks?.onPromotionFailure?.(result.reason, error, !state.promotionBlocked);
|
|
@@ -5330,6 +6039,56 @@ function createWorkerOrchestrator(config, executor, promoter, indexRegistry, cal
|
|
|
5330
6039
|
state.segmentLedger.clear();
|
|
5331
6040
|
}
|
|
5332
6041
|
}
|
|
6042
|
+
async function awaitPromotionIdle() {
|
|
6043
|
+
while (state.promotionInProgress) {
|
|
6044
|
+
const run = state.promotionRun;
|
|
6045
|
+
if (run !== null) {
|
|
6046
|
+
await run;
|
|
6047
|
+
} else {
|
|
6048
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
async function closeIndex(indexName) {
|
|
6053
|
+
await awaitPromotionIdle();
|
|
6054
|
+
await awaitReplicationIdle(state, indexName);
|
|
6055
|
+
await awaitCompactions(state);
|
|
6056
|
+
const pool = state.workerPool;
|
|
6057
|
+
if (pool !== null && state.promotedIndexes.has(indexName)) {
|
|
6058
|
+
const outcomes = await Promise.allSettled(
|
|
6059
|
+
pool.getAllExecutors().map((worker) => worker.execute({ type: "dropIndex", indexName, requestId: `close-${indexName}` }))
|
|
6060
|
+
);
|
|
6061
|
+
const failure = outcomes.find((outcome) => outcome.status === "rejected");
|
|
6062
|
+
if (failure?.status === "rejected") {
|
|
6063
|
+
const entry = indexRegistry.get(indexName);
|
|
6064
|
+
const manager = executor.getManager(indexName);
|
|
6065
|
+
if (entry !== void 0 && manager !== void 0) {
|
|
6066
|
+
try {
|
|
6067
|
+
await transferIndexToPool(indexName, pool, entry.config, manager);
|
|
6068
|
+
} catch (repairError) {
|
|
6069
|
+
throw new NarsilError(
|
|
6070
|
+
ErrorCodes.WORKER_CRASHED,
|
|
6071
|
+
`Worker copies for index "${indexName}" could not be restored after close failed`,
|
|
6072
|
+
{
|
|
6073
|
+
indexName,
|
|
6074
|
+
closeError: failure.reason instanceof Error ? failure.reason.message : String(failure.reason),
|
|
6075
|
+
repairError: repairError instanceof Error ? repairError.message : String(repairError)
|
|
6076
|
+
}
|
|
6077
|
+
);
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
6080
|
+
if (failure.reason instanceof NarsilError) throw failure.reason;
|
|
6081
|
+
throw new NarsilError(ErrorCodes.WORKER_CRASHED, `A worker could not close index "${indexName}"`, {
|
|
6082
|
+
indexName,
|
|
6083
|
+
cause: failure.reason instanceof Error ? failure.reason.message : String(failure.reason)
|
|
6084
|
+
});
|
|
6085
|
+
}
|
|
6086
|
+
pool.removeIndex(indexName);
|
|
6087
|
+
}
|
|
6088
|
+
state.promotedIndexes.delete(indexName);
|
|
6089
|
+
state.replicationQueues.delete(indexName);
|
|
6090
|
+
state.segmentLedger.delete(indexName);
|
|
6091
|
+
}
|
|
5333
6092
|
async function replicate(action) {
|
|
5334
6093
|
await replicateToWorkers(state, action);
|
|
5335
6094
|
if (action.type === "attachSegments") {
|
|
@@ -5342,6 +6101,9 @@ function createWorkerOrchestrator(config, executor, promoter, indexRegistry, cal
|
|
|
5342
6101
|
replicateToWorkers: replicate,
|
|
5343
6102
|
awaitReplication: (indexName) => awaitReplicationIdle(state, indexName),
|
|
5344
6103
|
awaitCompactions: () => awaitCompactions(state),
|
|
6104
|
+
openIndex: (indexName) => resyncIndex(indexName, true),
|
|
6105
|
+
closeIndex,
|
|
6106
|
+
isIndexBusy: (indexName) => state.promotionInProgress || state.replicationQueues.has(indexName) || state.compactionsInFlight.has(indexName),
|
|
5345
6107
|
buildSegments: (requests) => buildSegments(state, requests),
|
|
5346
6108
|
segmentBuildConcurrency: (indexName) => segmentBuildConcurrency(state, indexName),
|
|
5347
6109
|
searchViaWorker: (indexName, params, globalStats, partitionIds) => searchViaWorker(state, indexName, params, globalStats, partitionIds),
|
|
@@ -5353,6 +6115,11 @@ function createWorkerOrchestrator(config, executor, promoter, indexRegistry, cal
|
|
|
5353
6115
|
};
|
|
5354
6116
|
}
|
|
5355
6117
|
|
|
6118
|
+
// src/engine/vector-fields.ts
|
|
6119
|
+
function getVectorFieldPaths(schema) {
|
|
6120
|
+
return new Set(extractVectorFieldsFromSchema(schema).keys());
|
|
6121
|
+
}
|
|
6122
|
+
|
|
5356
6123
|
// src/engine/watermark.ts
|
|
5357
6124
|
function createWatermarkNotifier(deps) {
|
|
5358
6125
|
const latchedCapacity = /* @__PURE__ */ new Map();
|
|
@@ -5388,10 +6155,7 @@ function createWatermarkNotifier(deps) {
|
|
|
5388
6155
|
}
|
|
5389
6156
|
|
|
5390
6157
|
// src/engine/core.ts
|
|
5391
|
-
function
|
|
5392
|
-
return new Set(extractVectorFieldsFromSchema(schema).keys());
|
|
5393
|
-
}
|
|
5394
|
-
function createEngineCore(config) {
|
|
6158
|
+
function createEngineCore(config, hooks) {
|
|
5395
6159
|
const executor = createDirectExecutor();
|
|
5396
6160
|
const promoter = createExecutionPromoter({
|
|
5397
6161
|
perIndexThreshold: config?.workers?.promotionThreshold,
|
|
@@ -5423,6 +6187,9 @@ function createEngineCore(config) {
|
|
|
5423
6187
|
if (handlers) {
|
|
5424
6188
|
for (const handler of handlers) handler({ workerCount, reason });
|
|
5425
6189
|
}
|
|
6190
|
+
void Promise.resolve(pluginRegistry.runHook("onWorkerPromote", { workerCount, reason })).catch((err) => {
|
|
6191
|
+
console.warn("onWorkerPromote plugin hook failed:", err instanceof Error ? err.message : String(err));
|
|
6192
|
+
});
|
|
5426
6193
|
},
|
|
5427
6194
|
onPromotionFailure(reason, error, retryable) {
|
|
5428
6195
|
const handlers = eventHandlers.get("workerPromoteFailure");
|
|
@@ -5431,6 +6198,14 @@ function createEngineCore(config) {
|
|
|
5431
6198
|
return;
|
|
5432
6199
|
}
|
|
5433
6200
|
for (const handler of handlers) handler({ reason, error, retryable });
|
|
6201
|
+
},
|
|
6202
|
+
onWorkerCrash(workerId, indexNames, error) {
|
|
6203
|
+
const handlers = eventHandlers.get("workerCrash");
|
|
6204
|
+
if (!handlers || handlers.size === 0) {
|
|
6205
|
+
console.warn(`Worker ${workerId} crashed:`, error);
|
|
6206
|
+
return;
|
|
6207
|
+
}
|
|
6208
|
+
for (const handler of handlers) handler({ workerId, indexNames, error });
|
|
5434
6209
|
}
|
|
5435
6210
|
});
|
|
5436
6211
|
const rebalancer = createRebalancer();
|
|
@@ -5464,8 +6239,12 @@ function createEngineCore(config) {
|
|
|
5464
6239
|
}
|
|
5465
6240
|
return manager;
|
|
5466
6241
|
}
|
|
5467
|
-
async function createIndexFromMetadata(metadata) {
|
|
5468
|
-
|
|
6242
|
+
async function createIndexFromMetadata(metadata, loadData) {
|
|
6243
|
+
const existing = indexRegistry.get(metadata.indexName);
|
|
6244
|
+
if (existing !== void 0) {
|
|
6245
|
+
if (loadData && executor.getManager(metadata.indexName) === void 0) {
|
|
6246
|
+
executor.createIndex(metadata.indexName, existing.config, existing.language);
|
|
6247
|
+
}
|
|
5469
6248
|
return;
|
|
5470
6249
|
}
|
|
5471
6250
|
const indexConfig = reconstructSchemaFromMetadata(metadata);
|
|
@@ -5494,7 +6273,7 @@ function createEngineCore(config) {
|
|
|
5494
6273
|
}
|
|
5495
6274
|
}
|
|
5496
6275
|
try {
|
|
5497
|
-
executor.createIndex(metadata.indexName, indexConfig, language);
|
|
6276
|
+
if (loadData) executor.createIndex(metadata.indexName, indexConfig, language);
|
|
5498
6277
|
} catch (err) {
|
|
5499
6278
|
if (err instanceof NarsilError && err.code === ErrorCodes.CONFIG_INVALID) {
|
|
5500
6279
|
throw new NarsilError(err.code, `Recovery of index "${metadata.indexName}" failed: ${err.message}`, {
|
|
@@ -5512,8 +6291,12 @@ function createEngineCore(config) {
|
|
|
5512
6291
|
embeddingAdapterName: adapterName,
|
|
5513
6292
|
vectorFieldPaths: getVectorFieldPaths(indexConfig.schema),
|
|
5514
6293
|
indexUuid: metadata.indexUuid ?? null,
|
|
5515
|
-
heldPartitions: metadata.heldPartitions ?? null
|
|
6294
|
+
heldPartitions: metadata.heldPartitions ?? null,
|
|
6295
|
+
documentCount: metadata.documentCount ?? 0,
|
|
6296
|
+
partitionCount: metadata.partitionCount
|
|
5516
6297
|
});
|
|
6298
|
+
if (loadData) await indexState.registerOpen(metadata.indexName);
|
|
6299
|
+
else indexState.registerClosed(metadata.indexName);
|
|
5517
6300
|
if (metadata.analysisRevision !== language.revision) {
|
|
5518
6301
|
analysisRebuild.markStale({
|
|
5519
6302
|
indexName: metadata.indexName,
|
|
@@ -5524,9 +6307,12 @@ function createEngineCore(config) {
|
|
|
5524
6307
|
}
|
|
5525
6308
|
}
|
|
5526
6309
|
const durabilityTier = config !== void 0 ? resolveDurabilityTier(config) : null;
|
|
6310
|
+
if (config?.lifecycle !== void 0 && durabilityTier === null) {
|
|
6311
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "Index lifecycle settings require durability");
|
|
6312
|
+
}
|
|
5527
6313
|
let invalidation = null;
|
|
5528
6314
|
const durability = createDurabilityFromTier(durabilityTier, {
|
|
5529
|
-
|
|
6315
|
+
getManager: (indexName) => executor.getManager(indexName),
|
|
5530
6316
|
indexRegistry,
|
|
5531
6317
|
createIndexFromMetadata,
|
|
5532
6318
|
emitFatalError(error) {
|
|
@@ -5535,7 +6321,13 @@ function createEngineCore(config) {
|
|
|
5535
6321
|
for (const handler of handlers) handler({ error });
|
|
5536
6322
|
}
|
|
5537
6323
|
},
|
|
5538
|
-
publishCheckpointedPartitions: (indexName, partitions) => invalidation?.publishPartitions(indexName, partitions) ?? Promise.resolve()
|
|
6324
|
+
publishCheckpointedPartitions: (indexName, partitions) => invalidation?.publishPartitions(indexName, partitions) ?? Promise.resolve(),
|
|
6325
|
+
recordCheckpoint(indexName, documentCount, partitionCount) {
|
|
6326
|
+
const entry = indexRegistry.get(indexName);
|
|
6327
|
+
if (entry === void 0) return;
|
|
6328
|
+
entry.documentCount = documentCount;
|
|
6329
|
+
entry.partitionCount = partitionCount;
|
|
6330
|
+
}
|
|
5539
6331
|
});
|
|
5540
6332
|
invalidation = createInvalidationFromConfig(config, durabilityTier?.kind ?? null, {
|
|
5541
6333
|
getManager: (indexName) => executor.getManager(indexName) ?? void 0,
|
|
@@ -5581,6 +6373,18 @@ function createEngineCore(config) {
|
|
|
5581
6373
|
resyncIndex: (indexName, wasPromoted) => orchestrator.resyncIndex(indexName, wasPromoted),
|
|
5582
6374
|
durabilityManager: durability?.manager ?? null
|
|
5583
6375
|
});
|
|
6376
|
+
const indexState = wireIndexState({
|
|
6377
|
+
config: config?.lifecycle,
|
|
6378
|
+
durability,
|
|
6379
|
+
executor,
|
|
6380
|
+
orchestrator,
|
|
6381
|
+
analysisRebuild,
|
|
6382
|
+
indexRegistry,
|
|
6383
|
+
rebalancingIndexes,
|
|
6384
|
+
requireManager,
|
|
6385
|
+
onClose: hooks?.onIndexClose,
|
|
6386
|
+
onOpen: hooks?.onIndexOpen
|
|
6387
|
+
});
|
|
5584
6388
|
const mutationCtx = {
|
|
5585
6389
|
executor,
|
|
5586
6390
|
pluginRegistry,
|
|
@@ -5634,6 +6438,7 @@ function createEngineCore(config) {
|
|
|
5634
6438
|
bufferIfRebalancing,
|
|
5635
6439
|
watermarkNotifier,
|
|
5636
6440
|
analysisRebuild,
|
|
6441
|
+
indexState,
|
|
5637
6442
|
mutationCtx,
|
|
5638
6443
|
rebalanceCtx
|
|
5639
6444
|
};
|
|
@@ -5792,8 +6597,11 @@ async function createEngineIndex(core, config, name, indexConfig, indexUuid) {
|
|
|
5792
6597
|
embeddingAdapterName,
|
|
5793
6598
|
vectorFieldPaths: getVectorFieldPaths(indexConfig.schema),
|
|
5794
6599
|
indexUuid: indexUuid ?? null,
|
|
5795
|
-
heldPartitions: null
|
|
6600
|
+
heldPartitions: null,
|
|
6601
|
+
documentCount: 0,
|
|
6602
|
+
partitionCount: indexConfig.partitions?.maxPartitions ?? 1
|
|
5796
6603
|
});
|
|
6604
|
+
await core.indexState.registerOpen(name);
|
|
5797
6605
|
if (core.durability) {
|
|
5798
6606
|
await core.durability.manager.persistMetadata(name);
|
|
5799
6607
|
}
|
|
@@ -5802,13 +6610,21 @@ async function createEngineIndex(core, config, name, indexConfig, indexUuid) {
|
|
|
5802
6610
|
async function dropEngineIndex(core, name) {
|
|
5803
6611
|
core.guardShutdown();
|
|
5804
6612
|
const entry = core.requireIndex(name);
|
|
5805
|
-
core.
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
6613
|
+
await core.indexState.drop(name, async () => {
|
|
6614
|
+
await core.orchestrator.closeIndex(name);
|
|
6615
|
+
if (core.durability) {
|
|
6616
|
+
try {
|
|
6617
|
+
await core.durability.manager.removeIndex(name);
|
|
6618
|
+
} catch (error) {
|
|
6619
|
+
await core.orchestrator.openIndex(name).catch(() => void 0);
|
|
6620
|
+
throw error;
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
if (core.executor.getManager(name) !== void 0) core.executor.dropIndex(name);
|
|
6624
|
+
core.indexRegistry.delete(name);
|
|
6625
|
+
core.watermarkNotifier.forget(name);
|
|
6626
|
+
core.analysisRebuild.clearStale(name);
|
|
6627
|
+
});
|
|
5812
6628
|
await core.pluginRegistry.runHook("onIndexDrop", { indexName: name, config: entry.config });
|
|
5813
6629
|
}
|
|
5814
6630
|
function registerEngineEmbeddingAdapter(core, name, adapter) {
|
|
@@ -5843,6 +6659,7 @@ function registerEngineEmbeddingAdapter(core, name, adapter) {
|
|
|
5843
6659
|
// src/engine/lifecycle.ts
|
|
5844
6660
|
async function shutdownEngine(core) {
|
|
5845
6661
|
const { executor, durability, indexRegistry, eventHandlers, orchestrator } = core;
|
|
6662
|
+
core.indexState.dispose();
|
|
5846
6663
|
for (const [name] of indexRegistry) {
|
|
5847
6664
|
const manager = executor.getManager(name);
|
|
5848
6665
|
if (manager) {
|
|
@@ -7389,6 +8206,15 @@ async function executeRebalance(manager, indexName, targetPartitionCount, ctx) {
|
|
|
7389
8206
|
}
|
|
7390
8207
|
}
|
|
7391
8208
|
});
|
|
8209
|
+
try {
|
|
8210
|
+
await ctx.pluginRegistry.runHook("onPartitionSplit", {
|
|
8211
|
+
indexName,
|
|
8212
|
+
oldPartitionCount: oldCount,
|
|
8213
|
+
newPartitionCount: targetPartitionCount
|
|
8214
|
+
});
|
|
8215
|
+
} catch (hookErr) {
|
|
8216
|
+
console.warn("onPartitionSplit plugin hook failed:", hookErr instanceof Error ? hookErr.message : String(hookErr));
|
|
8217
|
+
}
|
|
7392
8218
|
await replayQueued(manager, indexName, waq, ctx);
|
|
7393
8219
|
if (ctx.durabilityManager) {
|
|
7394
8220
|
await ctx.durabilityManager.persistMetadata(indexName);
|
|
@@ -7603,7 +8429,7 @@ async function createSnapshot(manager, entry) {
|
|
|
7603
8429
|
const config = entry.config;
|
|
7604
8430
|
const bm25 = config.bm25;
|
|
7605
8431
|
const { encode: encode9 } = await import('@msgpack/msgpack');
|
|
7606
|
-
|
|
8432
|
+
const payload = encode9({
|
|
7607
8433
|
version: 2,
|
|
7608
8434
|
schema: config.schema,
|
|
7609
8435
|
language: entry.language.name,
|
|
@@ -7633,15 +8459,17 @@ async function createSnapshot(manager, entry) {
|
|
|
7633
8459
|
partitions: partitionBuffers,
|
|
7634
8460
|
vectorIndexes: vectorPayloads
|
|
7635
8461
|
});
|
|
8462
|
+
return packIndexSnapshotEnvelope(payload);
|
|
7636
8463
|
}
|
|
7637
8464
|
async function restoreFromSnapshot(indexName, data, deps) {
|
|
7638
8465
|
if (!(data instanceof Uint8Array)) {
|
|
7639
8466
|
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Snapshot data must be a Uint8Array");
|
|
7640
8467
|
}
|
|
8468
|
+
const payloadBytes = hasNrslMagic(data) ? await unpackIndexSnapshotEnvelope(data) : data;
|
|
7641
8469
|
const { decode: decode9 } = await import('@msgpack/msgpack');
|
|
7642
8470
|
let decoded;
|
|
7643
8471
|
try {
|
|
7644
|
-
decoded = decode9(
|
|
8472
|
+
decoded = decode9(payloadBytes);
|
|
7645
8473
|
} catch (err) {
|
|
7646
8474
|
throw new NarsilError(
|
|
7647
8475
|
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
@@ -7723,8 +8551,12 @@ async function restoreFromSnapshot(indexName, data, deps) {
|
|
|
7723
8551
|
embeddingAdapterName: adapterName,
|
|
7724
8552
|
vectorFieldPaths,
|
|
7725
8553
|
indexUuid: null,
|
|
7726
|
-
heldPartitions: null
|
|
8554
|
+
heldPartitions: null,
|
|
8555
|
+
documentCount: 0,
|
|
8556
|
+
partitionCount: envelope.partitions.length
|
|
7727
8557
|
});
|
|
8558
|
+
await deps.indexState.registerOpen(indexName);
|
|
8559
|
+
const release = await deps.indexState.acquire(indexName, false);
|
|
7728
8560
|
try {
|
|
7729
8561
|
const manager = deps.requireManager(indexName);
|
|
7730
8562
|
while (manager.partitionCount < envelope.partitions.length) {
|
|
@@ -7744,26 +8576,36 @@ async function restoreFromSnapshot(indexName, data, deps) {
|
|
|
7744
8576
|
}
|
|
7745
8577
|
}
|
|
7746
8578
|
}
|
|
8579
|
+
const entry = deps.indexRegistry.get(indexName);
|
|
8580
|
+
if (entry !== void 0) {
|
|
8581
|
+
entry.documentCount = manager.countDocuments();
|
|
8582
|
+
entry.partitionCount = manager.partitionCount;
|
|
8583
|
+
}
|
|
8584
|
+
if (deps.durability) {
|
|
8585
|
+
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId++) {
|
|
8586
|
+
for (const docId of manager.getPartition(partitionId).docIds()) {
|
|
8587
|
+
const document = manager.get(docId);
|
|
8588
|
+
if (document !== void 0) {
|
|
8589
|
+
await deps.durability.recordInsertOrUpdate(indexName, docId, document, noopApply2);
|
|
8590
|
+
}
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
8593
|
+
await deps.durability.manager.persistMetadata(indexName);
|
|
8594
|
+
await deps.durability.manager.checkpoint(indexName);
|
|
8595
|
+
}
|
|
7747
8596
|
} catch (err) {
|
|
7748
8597
|
try {
|
|
7749
8598
|
deps.executor.dropIndex(indexName);
|
|
7750
|
-
deps.indexRegistry.delete(indexName);
|
|
7751
8599
|
} catch (_) {
|
|
7752
8600
|
}
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId++) {
|
|
7758
|
-
for (const docId of manager.getPartition(partitionId).docIds()) {
|
|
7759
|
-
const document = manager.get(docId);
|
|
7760
|
-
if (document !== void 0) {
|
|
7761
|
-
await deps.durability.recordInsertOrUpdate(indexName, docId, document, noopApply2);
|
|
7762
|
-
}
|
|
7763
|
-
}
|
|
8601
|
+
deps.indexRegistry.delete(indexName);
|
|
8602
|
+
deps.indexState.forget(indexName);
|
|
8603
|
+
if (deps.durability) {
|
|
8604
|
+
await deps.durability.manager.removeIndex(indexName).catch(() => void 0);
|
|
7764
8605
|
}
|
|
7765
|
-
|
|
7766
|
-
|
|
8606
|
+
throw err;
|
|
8607
|
+
} finally {
|
|
8608
|
+
release();
|
|
7767
8609
|
}
|
|
7768
8610
|
}
|
|
7769
8611
|
function noopApply2() {
|
|
@@ -7836,6 +8678,137 @@ function readProcessMemory() {
|
|
|
7836
8678
|
}
|
|
7837
8679
|
}
|
|
7838
8680
|
|
|
8681
|
+
// src/search/cursor-binding.ts
|
|
8682
|
+
var ABSENT_TAG = 0;
|
|
8683
|
+
var NULL_TAG = 1;
|
|
8684
|
+
var FALSE_TAG = 2;
|
|
8685
|
+
var TRUE_TAG = 3;
|
|
8686
|
+
var NUMBER_TAG = 4;
|
|
8687
|
+
var STRING_TAG = 5;
|
|
8688
|
+
var LIST_TAG = 6;
|
|
8689
|
+
var MAP_TAG = 7;
|
|
8690
|
+
var encoder = new TextEncoder();
|
|
8691
|
+
function ensureCapacity(stream, extra) {
|
|
8692
|
+
if (stream.length + extra <= stream.bytes.length) return;
|
|
8693
|
+
const grown = new Uint8Array(Math.max(stream.bytes.length * 2, stream.length + extra));
|
|
8694
|
+
grown.set(stream.bytes.subarray(0, stream.length));
|
|
8695
|
+
stream.bytes = grown;
|
|
8696
|
+
}
|
|
8697
|
+
function writeByte(stream, value) {
|
|
8698
|
+
ensureCapacity(stream, 1);
|
|
8699
|
+
stream.bytes[stream.length] = value;
|
|
8700
|
+
stream.length += 1;
|
|
8701
|
+
}
|
|
8702
|
+
function writeUint32(stream, value) {
|
|
8703
|
+
ensureCapacity(stream, 4);
|
|
8704
|
+
stream.bytes[stream.length] = value >>> 24 & 255;
|
|
8705
|
+
stream.bytes[stream.length + 1] = value >>> 16 & 255;
|
|
8706
|
+
stream.bytes[stream.length + 2] = value >>> 8 & 255;
|
|
8707
|
+
stream.bytes[stream.length + 3] = value & 255;
|
|
8708
|
+
stream.length += 4;
|
|
8709
|
+
}
|
|
8710
|
+
var numberBytes = new DataView(new ArrayBuffer(8));
|
|
8711
|
+
function writeNumber(stream, value) {
|
|
8712
|
+
numberBytes.setFloat64(0, value, false);
|
|
8713
|
+
ensureCapacity(stream, 8);
|
|
8714
|
+
for (let i = 0; i < 8; i++) {
|
|
8715
|
+
stream.bytes[stream.length + i] = numberBytes.getUint8(i);
|
|
8716
|
+
}
|
|
8717
|
+
stream.length += 8;
|
|
8718
|
+
}
|
|
8719
|
+
function writeString(stream, value) {
|
|
8720
|
+
const encoded = encoder.encode(value);
|
|
8721
|
+
writeUint32(stream, encoded.length);
|
|
8722
|
+
ensureCapacity(stream, encoded.length);
|
|
8723
|
+
stream.bytes.set(encoded, stream.length);
|
|
8724
|
+
stream.length += encoded.length;
|
|
8725
|
+
}
|
|
8726
|
+
function writeValue(stream, value) {
|
|
8727
|
+
if (value === void 0) {
|
|
8728
|
+
writeByte(stream, ABSENT_TAG);
|
|
8729
|
+
return;
|
|
8730
|
+
}
|
|
8731
|
+
if (value === null) {
|
|
8732
|
+
writeByte(stream, NULL_TAG);
|
|
8733
|
+
return;
|
|
8734
|
+
}
|
|
8735
|
+
if (typeof value === "boolean") {
|
|
8736
|
+
writeByte(stream, value ? TRUE_TAG : FALSE_TAG);
|
|
8737
|
+
return;
|
|
8738
|
+
}
|
|
8739
|
+
if (typeof value === "number") {
|
|
8740
|
+
writeByte(stream, NUMBER_TAG);
|
|
8741
|
+
writeNumber(stream, value);
|
|
8742
|
+
return;
|
|
8743
|
+
}
|
|
8744
|
+
if (typeof value === "string") {
|
|
8745
|
+
writeByte(stream, STRING_TAG);
|
|
8746
|
+
writeString(stream, value);
|
|
8747
|
+
return;
|
|
8748
|
+
}
|
|
8749
|
+
if (Array.isArray(value)) {
|
|
8750
|
+
writeByte(stream, LIST_TAG);
|
|
8751
|
+
writeUint32(stream, value.length);
|
|
8752
|
+
for (const member of value) {
|
|
8753
|
+
writeValue(stream, member ?? null);
|
|
8754
|
+
}
|
|
8755
|
+
return;
|
|
8756
|
+
}
|
|
8757
|
+
const record = value;
|
|
8758
|
+
const keys = Object.keys(record).sort(compareCodePoints);
|
|
8759
|
+
writeByte(stream, MAP_TAG);
|
|
8760
|
+
writeUint32(stream, keys.length);
|
|
8761
|
+
for (const key of keys) {
|
|
8762
|
+
writeByte(stream, STRING_TAG);
|
|
8763
|
+
writeString(stream, key);
|
|
8764
|
+
writeValue(stream, record[key] ?? null);
|
|
8765
|
+
}
|
|
8766
|
+
}
|
|
8767
|
+
function boundVectorOf(vector) {
|
|
8768
|
+
if (vector === void 0) return void 0;
|
|
8769
|
+
const bound = { ...vector };
|
|
8770
|
+
if (vector.value !== void 0) {
|
|
8771
|
+
bound.value = Array.from(vector.value, Math.fround);
|
|
8772
|
+
}
|
|
8773
|
+
if (vector.text !== void 0) {
|
|
8774
|
+
delete bound.value;
|
|
8775
|
+
}
|
|
8776
|
+
return bound;
|
|
8777
|
+
}
|
|
8778
|
+
var FILTERS_SLOT = 2;
|
|
8779
|
+
var BOUND_SLOT_COUNT = 15;
|
|
8780
|
+
function bindingOf(values) {
|
|
8781
|
+
const stream = { bytes: new Uint8Array(256), length: 0 };
|
|
8782
|
+
for (const value of values) {
|
|
8783
|
+
writeValue(stream, value);
|
|
8784
|
+
}
|
|
8785
|
+
return fnv1aBytes(stream.bytes.subarray(0, stream.length)).toString(16).padStart(8, "0");
|
|
8786
|
+
}
|
|
8787
|
+
function queryBindingOf(params) {
|
|
8788
|
+
return bindingOf([
|
|
8789
|
+
params.term,
|
|
8790
|
+
params.fields,
|
|
8791
|
+
params.filters,
|
|
8792
|
+
params.boost,
|
|
8793
|
+
params.minScore,
|
|
8794
|
+
params.termMatch,
|
|
8795
|
+
params.tolerance,
|
|
8796
|
+
params.prefixLength,
|
|
8797
|
+
params.prefix,
|
|
8798
|
+
params.exact,
|
|
8799
|
+
params.pinned,
|
|
8800
|
+
params.mode,
|
|
8801
|
+
params.hybrid,
|
|
8802
|
+
boundVectorOf(params.vector),
|
|
8803
|
+
params.scoring ?? "local"
|
|
8804
|
+
]);
|
|
8805
|
+
}
|
|
8806
|
+
function listBindingOf(filters) {
|
|
8807
|
+
const values = new Array(BOUND_SLOT_COUNT).fill(void 0);
|
|
8808
|
+
values[FILTERS_SLOT] = filters;
|
|
8809
|
+
return bindingOf(values);
|
|
8810
|
+
}
|
|
8811
|
+
|
|
7839
8812
|
// src/engine/list-documents/index.ts
|
|
7840
8813
|
function clampListLimit(limit) {
|
|
7841
8814
|
const clamped = Math.max(1, clampLimit(limit));
|
|
@@ -7870,7 +8843,7 @@ function collectCandidates(partition, anchor, matches, wanted, out) {
|
|
|
7870
8843
|
taken++;
|
|
7871
8844
|
}
|
|
7872
8845
|
}
|
|
7873
|
-
function pageInIdOrder(partitions, cursor, filtered, limit) {
|
|
8846
|
+
function pageInIdOrder(partitions, cursor, filtered, limit, binding) {
|
|
7874
8847
|
const anchor = cursor === null ? null : cursor.anchor;
|
|
7875
8848
|
const candidates = [];
|
|
7876
8849
|
for (let index = 0; index < partitions.length; index++) {
|
|
@@ -7885,10 +8858,10 @@ function pageInIdOrder(partitions, cursor, filtered, limit) {
|
|
|
7885
8858
|
candidates.sort(compareCodePoints);
|
|
7886
8859
|
const hasMore = candidates.length > limit;
|
|
7887
8860
|
const ids = hasMore ? candidates.slice(0, limit) : candidates;
|
|
7888
|
-
const nextCursor = hasMore && ids.length > 0 ? { anchor: ids[ids.length - 1], score: null, sortKey: null, sortSignature: null } : null;
|
|
8861
|
+
const nextCursor = hasMore && ids.length > 0 ? { anchor: ids[ids.length - 1], score: null, sortKey: null, sortSignature: null, binding } : null;
|
|
7889
8862
|
return { ids, nextCursor };
|
|
7890
8863
|
}
|
|
7891
|
-
function pageInSortOrder(partitions, cursor, filtered, limit, sort, signature, schema) {
|
|
8864
|
+
function pageInSortOrder(partitions, cursor, filtered, limit, sort, signature, schema, binding) {
|
|
7892
8865
|
const normalized = normalizeSort(sort);
|
|
7893
8866
|
const fields = normalized.map((entry) => entry.field);
|
|
7894
8867
|
const directions = normalized.map((entry) => entry.direction);
|
|
@@ -7911,7 +8884,7 @@ function pageInSortOrder(partitions, cursor, filtered, limit, sort, signature, s
|
|
|
7911
8884
|
const hasMore = merged.length > limit;
|
|
7912
8885
|
const page = hasMore ? merged.slice(0, limit) : merged;
|
|
7913
8886
|
const last = page[page.length - 1];
|
|
7914
|
-
const nextCursor = hasMore && last !== void 0 ? { anchor: last.id, score: null, sortKey: last.key, sortSignature: signature } : null;
|
|
8887
|
+
const nextCursor = hasMore && last !== void 0 ? { anchor: last.id, score: null, sortKey: last.key, sortSignature: signature, binding } : null;
|
|
7915
8888
|
return { ids: page.map((entry) => entry.id), nextCursor };
|
|
7916
8889
|
}
|
|
7917
8890
|
function countAcrossPartitions(manager, partitionIds, partitions) {
|
|
@@ -7930,15 +8903,16 @@ function executeListDocuments(params, context) {
|
|
|
7930
8903
|
const limit = clampListLimit(params.limit);
|
|
7931
8904
|
const signature = sortSignatureOf(params.sort);
|
|
7932
8905
|
requireSortableFields(params.sort, schema);
|
|
8906
|
+
const binding = listBindingOf(params.filters);
|
|
7933
8907
|
let cursor = null;
|
|
7934
8908
|
if (params.cursor !== void 0) {
|
|
7935
8909
|
cursor = decodePageCursor(params.cursor);
|
|
7936
|
-
requireMatchingCursor(cursor, params.cursor, signature, false);
|
|
8910
|
+
requireMatchingCursor(cursor, params.cursor, signature, false, binding);
|
|
7937
8911
|
}
|
|
7938
8912
|
const partitions = context.partitionIds === void 0 ? manager.getAllPartitions() : context.partitionIds.map((partitionId) => manager.partitionAt(partitionId)).filter((partition) => partition !== void 0);
|
|
7939
8913
|
const filtered = params.filters === void 0 ? null : collectFilterMatches(partitions, params.filters, schema);
|
|
7940
8914
|
const total = filtered === null ? countAcrossPartitions(manager, context.partitionIds, partitions) : filtered.total;
|
|
7941
|
-
const page = params.sort === void 0 || signature === null ? pageInIdOrder(partitions, cursor, filtered, limit) : pageInSortOrder(partitions, cursor, filtered, limit, params.sort, signature, schema);
|
|
8915
|
+
const page = params.sort === void 0 || signature === null ? pageInIdOrder(partitions, cursor, filtered, limit, binding) : pageInSortOrder(partitions, cursor, filtered, limit, params.sort, signature, schema, binding);
|
|
7942
8916
|
const projection = resolveProjection(params.document);
|
|
7943
8917
|
const documents = [];
|
|
7944
8918
|
for (const docId of page.ids) {
|
|
@@ -8006,18 +8980,14 @@ function applyGrouping(hits, group, getDocument) {
|
|
|
8006
8980
|
}
|
|
8007
8981
|
const result = { values: entry.values, hits: groupHits };
|
|
8008
8982
|
if (group.reduce) {
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
;
|
|
8018
|
-
result.reduced = accumulator;
|
|
8019
|
-
} catch (err) {
|
|
8020
|
-
result.reducerError = err instanceof Error ? err.message : String(err);
|
|
8983
|
+
const folded = foldGroupReducer(
|
|
8984
|
+
group.reduce,
|
|
8985
|
+
groupHits.map((hit) => ({ document: getDocument(hit.id), score: hit.score ?? 0 }))
|
|
8986
|
+
);
|
|
8987
|
+
if (folded.reducerError !== void 0) {
|
|
8988
|
+
result.reducerError = folded.reducerError;
|
|
8989
|
+
} else {
|
|
8990
|
+
result.reduced = folded.reduced;
|
|
8021
8991
|
}
|
|
8022
8992
|
}
|
|
8023
8993
|
groups.push(result);
|
|
@@ -8030,19 +9000,49 @@ function applyGrouping(hits, group, getDocument) {
|
|
|
8030
9000
|
const bId = b.hits.length > 0 ? b.hits[0].id : "";
|
|
8031
9001
|
return compareCodePoints(aId, bId);
|
|
8032
9002
|
});
|
|
9003
|
+
if (group.limit !== void 0 && Number.isFinite(group.limit) && group.limit >= 0) {
|
|
9004
|
+
const cap = Math.floor(group.limit);
|
|
9005
|
+
if (groups.length > cap) {
|
|
9006
|
+
groups.length = cap;
|
|
9007
|
+
}
|
|
9008
|
+
}
|
|
8033
9009
|
return groups;
|
|
8034
9010
|
}
|
|
9011
|
+
function foldGroupReducer(reduce, rows) {
|
|
9012
|
+
try {
|
|
9013
|
+
let accumulator = reduce.initialValue();
|
|
9014
|
+
for (const row of rows) {
|
|
9015
|
+
if (row.document !== void 0) {
|
|
9016
|
+
accumulator = reduce.reducer(accumulator, row.document, row.score);
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
return { reduced: accumulator };
|
|
9020
|
+
} catch (err) {
|
|
9021
|
+
return { reducerError: err instanceof Error ? err.message : String(err) };
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
8035
9024
|
|
|
8036
9025
|
// src/search/pinning.ts
|
|
9026
|
+
function dedupePinnedEntries(pinned) {
|
|
9027
|
+
const deduped = [];
|
|
9028
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9029
|
+
for (const entry of pinned) {
|
|
9030
|
+
if (seen.has(entry.docId)) continue;
|
|
9031
|
+
seen.add(entry.docId);
|
|
9032
|
+
deduped.push(entry);
|
|
9033
|
+
}
|
|
9034
|
+
return deduped;
|
|
9035
|
+
}
|
|
8037
9036
|
function applyPinning(hits, pinned, resolveDoc) {
|
|
8038
9037
|
const result = hits.slice();
|
|
8039
|
-
const
|
|
9038
|
+
const deduped = dedupePinnedEntries(pinned);
|
|
9039
|
+
const pinnedDocIds = new Set(deduped.map((entry) => entry.docId));
|
|
8040
9040
|
for (let i = result.length - 1; i >= 0; i--) {
|
|
8041
9041
|
if (pinnedDocIds.has(result[i].id)) {
|
|
8042
9042
|
result.splice(i, 1);
|
|
8043
9043
|
}
|
|
8044
9044
|
}
|
|
8045
|
-
const sorted =
|
|
9045
|
+
const sorted = deduped.sort((a, b) => a.position - b.position);
|
|
8046
9046
|
for (const entry of sorted) {
|
|
8047
9047
|
const doc = resolveDoc(entry.docId);
|
|
8048
9048
|
if (!doc) continue;
|
|
@@ -8236,17 +9236,7 @@ function applyHighlights(hits, params, language, analysis) {
|
|
|
8236
9236
|
|
|
8237
9237
|
// src/engine/query/shared.ts
|
|
8238
9238
|
function partitionsFor(manager, partitionIds) {
|
|
8239
|
-
|
|
8240
|
-
return manager.getAllPartitions();
|
|
8241
|
-
}
|
|
8242
|
-
const partitions = [];
|
|
8243
|
-
for (const partitionId of partitionIds) {
|
|
8244
|
-
const partition = manager.partitionAt(partitionId);
|
|
8245
|
-
if (partition !== void 0) {
|
|
8246
|
-
partitions.push(partition);
|
|
8247
|
-
}
|
|
8248
|
-
}
|
|
8249
|
-
return partitions;
|
|
9239
|
+
return partitionsIn(manager, partitionIds);
|
|
8250
9240
|
}
|
|
8251
9241
|
function coverageFor(manager, partitionIds) {
|
|
8252
9242
|
if (partitionIds === void 0) {
|
|
@@ -8340,7 +9330,7 @@ function executeSortedQueryPage(params, context, limit, offset, sortSignature) {
|
|
|
8340
9330
|
let anchorId = null;
|
|
8341
9331
|
if (params.searchAfter !== void 0) {
|
|
8342
9332
|
const decoded = decodePageCursor(params.searchAfter);
|
|
8343
|
-
requireMatchingCursor(decoded, params.searchAfter, sortSignature, true);
|
|
9333
|
+
requireMatchingCursor(decoded, params.searchAfter, sortSignature, true, context.cursorBinding);
|
|
8344
9334
|
anchorKey = decoded.sortKey;
|
|
8345
9335
|
anchorId = decoded.anchor;
|
|
8346
9336
|
}
|
|
@@ -8385,7 +9375,13 @@ function executeSortedQueryPage(params, context, limit, offset, sortSignature) {
|
|
|
8385
9375
|
const last = page[page.length - 1];
|
|
8386
9376
|
let cursor;
|
|
8387
9377
|
if (hasMore && last !== void 0) {
|
|
8388
|
-
cursor = encodePageCursor({
|
|
9378
|
+
cursor = encodePageCursor({
|
|
9379
|
+
anchor: last.id,
|
|
9380
|
+
score: null,
|
|
9381
|
+
sortKey: last.key,
|
|
9382
|
+
sortSignature,
|
|
9383
|
+
binding: context.cursorBinding
|
|
9384
|
+
});
|
|
8389
9385
|
}
|
|
8390
9386
|
return {
|
|
8391
9387
|
hits: page.map((entry) => ({ id: entry.id, document: void 0 })),
|
|
@@ -8671,7 +9667,7 @@ async function executeQuery(params, context) {
|
|
|
8671
9667
|
if (params.group) {
|
|
8672
9668
|
groups = applyGrouping(hits, params.group, (docId) => manager.getRef(docId));
|
|
8673
9669
|
}
|
|
8674
|
-
if (params.pinned) {
|
|
9670
|
+
if (params.pinned && params.searchAfter === void 0 && context.partitionIds === void 0) {
|
|
8675
9671
|
hits = applyPinning(hits, params.pinned, (docId) => {
|
|
8676
9672
|
const doc = manager.getRef(docId);
|
|
8677
9673
|
if (!doc) return void 0;
|
|
@@ -8686,7 +9682,16 @@ async function executeQuery(params, context) {
|
|
|
8686
9682
|
sortKeyOf
|
|
8687
9683
|
};
|
|
8688
9684
|
}
|
|
8689
|
-
const
|
|
9685
|
+
const pinnedIds = params.pinned !== void 0 && params.searchAfter === void 0 ? new Set(params.pinned.map((entry) => entry.docId)) : void 0;
|
|
9686
|
+
const paged = applyPagination(
|
|
9687
|
+
hits,
|
|
9688
|
+
limit,
|
|
9689
|
+
offset,
|
|
9690
|
+
context.cursorBinding,
|
|
9691
|
+
params.searchAfter,
|
|
9692
|
+
sortContext,
|
|
9693
|
+
pinnedIds
|
|
9694
|
+
);
|
|
8690
9695
|
paginated = paged.paginated;
|
|
8691
9696
|
nextCursor = paged.nextCursor;
|
|
8692
9697
|
count = fanOutResult.totalMatched;
|
|
@@ -8755,6 +9760,11 @@ async function executePreflight(params, context) {
|
|
|
8755
9760
|
} else if (isHybridMode && hasGlobalVectorIndex) {
|
|
8756
9761
|
const result = await executeHybridSearch(params, context, preflightLimit, preflightOffset);
|
|
8757
9762
|
totalMatched = result.totalMatched;
|
|
9763
|
+
} else if (countsWithoutScores(params)) {
|
|
9764
|
+
totalMatched = fanOutMatchCount(manager, params, language, config.schema, {
|
|
9765
|
+
searchOptions: searchOptionsFor(manager),
|
|
9766
|
+
partitionIds: context.partitionIds
|
|
9767
|
+
});
|
|
8758
9768
|
} else {
|
|
8759
9769
|
const scoring = scoringConfigFor(params, context);
|
|
8760
9770
|
const workerResult = workerSearch ? await workerSearch(indexName, params, broadcastStatsForWorker(params, context, scoring), context.partitionIds) : null;
|
|
@@ -8891,105 +9901,132 @@ function broadcastStatsFor(core, options) {
|
|
|
8891
9901
|
}
|
|
8892
9902
|
async function runEngineQuery(core, indexName, params, options) {
|
|
8893
9903
|
core.guardShutdown();
|
|
8894
|
-
const
|
|
8895
|
-
const manager = core.requireManager(indexName);
|
|
8896
|
-
const resolvedParams = await resolveVectorText(
|
|
8897
|
-
scopedParams(params, options),
|
|
8898
|
-
entry.embeddingAdapter,
|
|
8899
|
-
core.abortController.signal,
|
|
8900
|
-
entry.embeddingAdapterName
|
|
8901
|
-
);
|
|
8902
|
-
await core.pluginRegistry.runHook("beforeSearch", { indexName, params: resolvedParams });
|
|
8903
|
-
const workerSearch = core.orchestrator.isPromoted() ? core.orchestrator.searchViaWorker.bind(core.orchestrator) : void 0;
|
|
8904
|
-
const result = await executeQuery(resolvedParams, {
|
|
8905
|
-
manager,
|
|
8906
|
-
language: entry.language,
|
|
8907
|
-
config: entry.config,
|
|
8908
|
-
workerSearch,
|
|
8909
|
-
indexName,
|
|
8910
|
-
broadcastStats: broadcastStatsFor(core, options),
|
|
8911
|
-
partitionIds: options?.partitionIds
|
|
8912
|
-
});
|
|
9904
|
+
const release = await core.indexState.acquire(indexName);
|
|
8913
9905
|
try {
|
|
8914
|
-
|
|
9906
|
+
const entry = core.requireIndex(indexName);
|
|
9907
|
+
const manager = core.requireManager(indexName);
|
|
9908
|
+
const resolvedParams = await resolveVectorText(
|
|
9909
|
+
scopedParams(params, options),
|
|
9910
|
+
entry.embeddingAdapter,
|
|
9911
|
+
core.abortController.signal,
|
|
9912
|
+
entry.embeddingAdapterName
|
|
9913
|
+
);
|
|
9914
|
+
await core.pluginRegistry.runHook("beforeSearch", { indexName, params: resolvedParams });
|
|
9915
|
+
const workerSearch = core.orchestrator.isPromoted() ? core.orchestrator.searchViaWorker.bind(core.orchestrator) : void 0;
|
|
9916
|
+
const result = await executeQuery(resolvedParams, {
|
|
9917
|
+
manager,
|
|
9918
|
+
language: entry.language,
|
|
9919
|
+
config: entry.config,
|
|
9920
|
+
workerSearch,
|
|
8915
9921
|
indexName,
|
|
8916
|
-
|
|
8917
|
-
|
|
9922
|
+
broadcastStats: broadcastStatsFor(core, options),
|
|
9923
|
+
partitionIds: options?.partitionIds,
|
|
9924
|
+
cursorBinding: queryBindingOf(params)
|
|
8918
9925
|
});
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
9926
|
+
try {
|
|
9927
|
+
await core.pluginRegistry.runHook("afterSearch", {
|
|
9928
|
+
indexName,
|
|
9929
|
+
params: resolvedParams,
|
|
9930
|
+
results: result
|
|
9931
|
+
});
|
|
9932
|
+
} catch (err) {
|
|
9933
|
+
console.warn("afterSearch plugin hook error:", err);
|
|
9934
|
+
}
|
|
9935
|
+
if (core.analysisRebuild.isStale(indexName)) {
|
|
9936
|
+
result.analysisStale = true;
|
|
9937
|
+
}
|
|
9938
|
+
return result;
|
|
9939
|
+
} finally {
|
|
9940
|
+
release();
|
|
8924
9941
|
}
|
|
8925
|
-
return result;
|
|
8926
9942
|
}
|
|
8927
9943
|
async function runEnginePreflight(core, indexName, params, options) {
|
|
8928
9944
|
core.guardShutdown();
|
|
8929
|
-
const
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
9945
|
+
const release = await core.indexState.acquire(indexName);
|
|
9946
|
+
try {
|
|
9947
|
+
const entry = core.requireIndex(indexName);
|
|
9948
|
+
const manager = core.requireManager(indexName);
|
|
9949
|
+
const resolvedParams = await resolveVectorText(
|
|
9950
|
+
scopedParams(params, options),
|
|
9951
|
+
entry.embeddingAdapter,
|
|
9952
|
+
core.abortController.signal,
|
|
9953
|
+
entry.embeddingAdapterName
|
|
9954
|
+
);
|
|
9955
|
+
const workerSearch = core.orchestrator.isPromoted() ? core.orchestrator.searchViaWorker.bind(core.orchestrator) : void 0;
|
|
9956
|
+
const result = await executePreflight(resolvedParams, {
|
|
9957
|
+
manager,
|
|
9958
|
+
language: entry.language,
|
|
9959
|
+
config: entry.config,
|
|
9960
|
+
workerSearch,
|
|
9961
|
+
indexName,
|
|
9962
|
+
broadcastStats: broadcastStatsFor(core, options),
|
|
9963
|
+
partitionIds: options?.partitionIds,
|
|
9964
|
+
cursorBinding: queryBindingOf(params)
|
|
9965
|
+
});
|
|
9966
|
+
if (core.analysisRebuild.isStale(indexName)) {
|
|
9967
|
+
result.analysisStale = true;
|
|
9968
|
+
}
|
|
9969
|
+
return result;
|
|
9970
|
+
} finally {
|
|
9971
|
+
release();
|
|
8949
9972
|
}
|
|
8950
|
-
return result;
|
|
8951
9973
|
}
|
|
8952
9974
|
async function runEngineSuggest(core, indexName, params, partitionIds) {
|
|
8953
9975
|
core.guardShutdown();
|
|
8954
|
-
const
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
9976
|
+
const release = await core.indexState.acquire(indexName);
|
|
9977
|
+
try {
|
|
9978
|
+
const result = executeSuggest(
|
|
9979
|
+
core.requireManager(indexName),
|
|
9980
|
+
core.requireIndex(indexName).language,
|
|
9981
|
+
params,
|
|
9982
|
+
partitionIds
|
|
9983
|
+
);
|
|
9984
|
+
if (core.analysisRebuild.isStale(indexName)) {
|
|
9985
|
+
result.analysisStale = true;
|
|
9986
|
+
}
|
|
9987
|
+
return result;
|
|
9988
|
+
} finally {
|
|
9989
|
+
release();
|
|
8962
9990
|
}
|
|
8963
|
-
return result;
|
|
8964
9991
|
}
|
|
8965
|
-
function runEngineQueryStats(core, indexName, terms, partitionIds) {
|
|
9992
|
+
async function runEngineQueryStats(core, indexName, terms, partitionIds) {
|
|
8966
9993
|
core.guardShutdown();
|
|
8967
|
-
const
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
manager
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
9994
|
+
const release = await core.indexState.acquire(indexName);
|
|
9995
|
+
try {
|
|
9996
|
+
const entry = core.requireIndex(indexName);
|
|
9997
|
+
const manager = core.requireManager(indexName);
|
|
9998
|
+
const stats = collectQueryTermStats(
|
|
9999
|
+
manager,
|
|
10000
|
+
terms.join(" "),
|
|
10001
|
+
entry.language,
|
|
10002
|
+
{
|
|
10003
|
+
stopWords: manager.analysis.stopWords,
|
|
10004
|
+
customTokenizer: manager.analysis.customTokenizer
|
|
10005
|
+
},
|
|
10006
|
+
partitionIds
|
|
10007
|
+
);
|
|
10008
|
+
return {
|
|
10009
|
+
totalDocuments: stats.totalDocuments,
|
|
10010
|
+
docFrequencies: stats.docFrequencies,
|
|
10011
|
+
totalFieldLengths: stats.totalFieldLengths
|
|
10012
|
+
};
|
|
10013
|
+
} finally {
|
|
10014
|
+
release();
|
|
10015
|
+
}
|
|
8984
10016
|
}
|
|
8985
10017
|
async function runEngineListDocuments(core, indexName, params, partitionIds) {
|
|
8986
10018
|
core.guardShutdown();
|
|
8987
|
-
const
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
10019
|
+
const release = await core.indexState.acquire(indexName);
|
|
10020
|
+
try {
|
|
10021
|
+
const entry = core.requireIndex(indexName);
|
|
10022
|
+
return executeListDocuments(params, {
|
|
10023
|
+
manager: core.requireManager(indexName),
|
|
10024
|
+
schema: entry.config.schema,
|
|
10025
|
+
partitionIds
|
|
10026
|
+
});
|
|
10027
|
+
} finally {
|
|
10028
|
+
release();
|
|
10029
|
+
}
|
|
8993
10030
|
}
|
|
8994
10031
|
|
|
8995
10032
|
// src/narsil/operations.ts
|
|
@@ -9009,6 +10046,16 @@ function createNarsilFromCore(core, config) {
|
|
|
9009
10046
|
mutationCtx,
|
|
9010
10047
|
rebalanceCtx
|
|
9011
10048
|
} = core;
|
|
10049
|
+
async function withOpenIndex(indexName, action) {
|
|
10050
|
+
guardShutdown();
|
|
10051
|
+
requireIndex(indexName);
|
|
10052
|
+
const release = await core.indexState.acquire(indexName);
|
|
10053
|
+
try {
|
|
10054
|
+
return await action();
|
|
10055
|
+
} finally {
|
|
10056
|
+
release();
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
9012
10059
|
const narsil = {
|
|
9013
10060
|
createIndex(name, indexConfig) {
|
|
9014
10061
|
return createEngineIndex(core, config, name, indexConfig);
|
|
@@ -9019,15 +10066,27 @@ function createNarsilFromCore(core, config) {
|
|
|
9019
10066
|
dropIndex(name) {
|
|
9020
10067
|
return dropEngineIndex(core, name);
|
|
9021
10068
|
},
|
|
10069
|
+
open(indexName) {
|
|
10070
|
+
guardShutdown();
|
|
10071
|
+
requireIndex(indexName);
|
|
10072
|
+
return core.indexState.open(indexName);
|
|
10073
|
+
},
|
|
10074
|
+
close(indexName) {
|
|
10075
|
+
guardShutdown();
|
|
10076
|
+
requireIndex(indexName);
|
|
10077
|
+
return core.indexState.close(indexName);
|
|
10078
|
+
},
|
|
9022
10079
|
listIndexes() {
|
|
9023
10080
|
const infos = [];
|
|
9024
10081
|
for (const [name, entry] of indexRegistry) {
|
|
9025
10082
|
const manager = executor.getManager(name);
|
|
9026
10083
|
infos.push({
|
|
9027
10084
|
name,
|
|
9028
|
-
documentCount: manager?.countDocuments() ??
|
|
9029
|
-
partitionCount: manager?.partitionCount ??
|
|
10085
|
+
documentCount: manager?.countDocuments() ?? entry.documentCount,
|
|
10086
|
+
partitionCount: manager?.partitionCount ?? entry.partitionCount,
|
|
9030
10087
|
language: entry.language.name,
|
|
10088
|
+
state: core.indexState.stateOf(name),
|
|
10089
|
+
reopenCount: core.indexState.reopenCount(name),
|
|
9031
10090
|
...core.analysisRebuild.isStale(name) ? { analysisStale: true } : {}
|
|
9032
10091
|
});
|
|
9033
10092
|
}
|
|
@@ -9038,8 +10097,8 @@ function createNarsilFromCore(core, config) {
|
|
|
9038
10097
|
const entry = requireIndex(indexName);
|
|
9039
10098
|
const manager = executor.getManager(indexName);
|
|
9040
10099
|
return {
|
|
9041
|
-
documentCount: manager?.countDocuments() ??
|
|
9042
|
-
partitionCount: manager?.partitionCount ??
|
|
10100
|
+
documentCount: manager?.countDocuments() ?? entry.documentCount,
|
|
10101
|
+
partitionCount: manager?.partitionCount ?? entry.partitionCount,
|
|
9043
10102
|
estimatedMemoryBytes: manager?.estimateMemoryBytes() ?? 0,
|
|
9044
10103
|
language: entry.language.name,
|
|
9045
10104
|
schema: entry.config.schema
|
|
@@ -9051,47 +10110,48 @@ function createNarsilFromCore(core, config) {
|
|
|
9051
10110
|
return executor.getManager(indexName)?.getPartitionStats() ?? [];
|
|
9052
10111
|
},
|
|
9053
10112
|
insert(indexName, document, docId, options) {
|
|
9054
|
-
return insertDocument(mutationCtx, indexName, document, docId, options);
|
|
10113
|
+
return withOpenIndex(indexName, () => insertDocument(mutationCtx, indexName, document, docId, options));
|
|
9055
10114
|
},
|
|
9056
10115
|
insertBatch(indexName, documents, options) {
|
|
9057
|
-
return insertDocumentBatch(mutationCtx, indexName, documents, options);
|
|
10116
|
+
return withOpenIndex(indexName, () => insertDocumentBatch(mutationCtx, indexName, documents, options));
|
|
9058
10117
|
},
|
|
9059
10118
|
remove(indexName, docId) {
|
|
9060
|
-
return removeDocument(mutationCtx, indexName, docId);
|
|
10119
|
+
return withOpenIndex(indexName, () => removeDocument(mutationCtx, indexName, docId));
|
|
9061
10120
|
},
|
|
9062
10121
|
removeBatch(indexName, docIds) {
|
|
9063
|
-
return removeDocumentBatch(mutationCtx, indexName, docIds);
|
|
10122
|
+
return withOpenIndex(indexName, () => removeDocumentBatch(mutationCtx, indexName, docIds));
|
|
9064
10123
|
},
|
|
9065
10124
|
update(indexName, docId, document) {
|
|
9066
|
-
return updateDocument(mutationCtx, indexName, docId, document);
|
|
10125
|
+
return withOpenIndex(indexName, () => updateDocument(mutationCtx, indexName, docId, document));
|
|
9067
10126
|
},
|
|
9068
10127
|
updateBatch(indexName, updates) {
|
|
9069
|
-
return updateDocumentBatch(mutationCtx, indexName, updates);
|
|
10128
|
+
return withOpenIndex(indexName, () => updateDocumentBatch(mutationCtx, indexName, updates));
|
|
9070
10129
|
},
|
|
9071
10130
|
async get(indexName, docId) {
|
|
9072
|
-
|
|
9073
|
-
requireIndex(indexName);
|
|
9074
|
-
return executor.execute({ type: "get", indexName, docId, requestId: docId });
|
|
10131
|
+
return withOpenIndex(indexName, () => executor.execute({ type: "get", indexName, docId, requestId: docId }));
|
|
9075
10132
|
},
|
|
9076
10133
|
async getMultiple(indexName, docIds) {
|
|
9077
10134
|
guardShutdown();
|
|
9078
10135
|
requireIndex(indexName);
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
const
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
10136
|
+
return withOpenIndex(indexName, async () => {
|
|
10137
|
+
const result = /* @__PURE__ */ new Map();
|
|
10138
|
+
for (const docId of docIds) {
|
|
10139
|
+
const doc = await executor.execute({
|
|
10140
|
+
type: "get",
|
|
10141
|
+
indexName,
|
|
10142
|
+
docId,
|
|
10143
|
+
requestId: docId
|
|
10144
|
+
});
|
|
10145
|
+
if (doc !== void 0) result.set(docId, doc);
|
|
10146
|
+
}
|
|
10147
|
+
return result;
|
|
10148
|
+
});
|
|
9085
10149
|
},
|
|
9086
10150
|
async has(indexName, docId) {
|
|
9087
|
-
|
|
9088
|
-
requireIndex(indexName);
|
|
9089
|
-
return executor.execute({ type: "has", indexName, docId, requestId: docId });
|
|
10151
|
+
return withOpenIndex(indexName, () => executor.execute({ type: "has", indexName, docId, requestId: docId }));
|
|
9090
10152
|
},
|
|
9091
10153
|
async countDocuments(indexName) {
|
|
9092
|
-
|
|
9093
|
-
requireIndex(indexName);
|
|
9094
|
-
return executor.execute({ type: "count", indexName, requestId: indexName });
|
|
10154
|
+
return withOpenIndex(indexName, () => executor.execute({ type: "count", indexName, requestId: indexName }));
|
|
9095
10155
|
},
|
|
9096
10156
|
async listDocuments(indexName, params) {
|
|
9097
10157
|
return runEngineListDocuments(core, indexName, params ?? {});
|
|
@@ -9108,15 +10168,15 @@ function createNarsilFromCore(core, config) {
|
|
|
9108
10168
|
async rebuildAnalysis(indexName) {
|
|
9109
10169
|
guardShutdown();
|
|
9110
10170
|
requireIndex(indexName);
|
|
9111
|
-
await core.analysisRebuild.rebuild(indexName);
|
|
10171
|
+
await withOpenIndex(indexName, () => core.analysisRebuild.rebuild(indexName));
|
|
9112
10172
|
},
|
|
9113
10173
|
async snapshot(indexName) {
|
|
9114
10174
|
guardShutdown();
|
|
9115
|
-
return createSnapshot(requireManager(indexName), requireIndex(indexName));
|
|
10175
|
+
return withOpenIndex(indexName, () => createSnapshot(requireManager(indexName), requireIndex(indexName)));
|
|
9116
10176
|
},
|
|
9117
10177
|
async restore(indexName, data) {
|
|
9118
10178
|
guardShutdown();
|
|
9119
|
-
|
|
10179
|
+
await restoreFromSnapshot(indexName, data, {
|
|
9120
10180
|
executor,
|
|
9121
10181
|
indexRegistry,
|
|
9122
10182
|
getVectorFieldPaths,
|
|
@@ -9126,66 +10186,74 @@ function createNarsilFromCore(core, config) {
|
|
|
9126
10186
|
embeddingAdapters: core.embeddingAdapters,
|
|
9127
10187
|
defaultEmbeddingAdapter: config?.embedding ?? null,
|
|
9128
10188
|
markAnalysisStale: core.analysisRebuild.markStale,
|
|
9129
|
-
clearAnalysisStale: core.analysisRebuild.clearStale
|
|
10189
|
+
clearAnalysisStale: core.analysisRebuild.clearStale,
|
|
10190
|
+
indexState: core.indexState
|
|
9130
10191
|
});
|
|
9131
10192
|
},
|
|
9132
10193
|
async checkpoint(indexName) {
|
|
9133
10194
|
guardShutdown();
|
|
9134
10195
|
requireIndex(indexName);
|
|
9135
|
-
|
|
9136
|
-
await durability.manager.checkpoint(indexName);
|
|
9137
|
-
}
|
|
10196
|
+
await withOpenIndex(indexName, async () => {
|
|
10197
|
+
if (durability) await durability.manager.checkpoint(indexName);
|
|
10198
|
+
});
|
|
9138
10199
|
},
|
|
9139
10200
|
async clear(indexName) {
|
|
9140
10201
|
guardShutdown();
|
|
9141
10202
|
requireIndex(indexName);
|
|
9142
|
-
await
|
|
9143
|
-
|
|
9144
|
-
|
|
10203
|
+
await withOpenIndex(indexName, async () => {
|
|
10204
|
+
await executor.execute({ type: "clear", indexName, requestId: indexName });
|
|
10205
|
+
await orchestrator.replicateToWorkers({ type: "clear", indexName, requestId: `replicate-clear-${indexName}` });
|
|
10206
|
+
core.watermarkNotifier.forget(indexName);
|
|
10207
|
+
});
|
|
9145
10208
|
},
|
|
9146
10209
|
async rebalance(indexName, targetPartitionCount) {
|
|
9147
10210
|
guardShutdown();
|
|
9148
10211
|
requireIndex(indexName);
|
|
9149
|
-
return
|
|
10212
|
+
return withOpenIndex(
|
|
10213
|
+
indexName,
|
|
10214
|
+
() => executeRebalance(requireManager(indexName), indexName, targetPartitionCount, rebalanceCtx)
|
|
10215
|
+
);
|
|
9150
10216
|
},
|
|
9151
10217
|
async updatePartitionConfig(indexName, partitionConfig) {
|
|
9152
10218
|
guardShutdown();
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
9165
|
-
`maxPartitions (${partitionConfig.maxPartitions}) is less than the current partition count (${manager.partitionCount})`,
|
|
9166
|
-
{ maxPartitions: partitionConfig.maxPartitions, partitionCount: manager.partitionCount }
|
|
9167
|
-
);
|
|
9168
|
-
}
|
|
9169
|
-
const currentDocCount = manager.countDocuments();
|
|
9170
|
-
const newMaxDocs = partitionConfig.maxDocsPerPartition ?? entry.config.partitions?.maxDocsPerPartition;
|
|
9171
|
-
if (newMaxDocs !== void 0) {
|
|
9172
|
-
const newTotalCapacity = newMaxDocs * manager.partitionCount;
|
|
9173
|
-
if (newTotalCapacity < currentDocCount) {
|
|
10219
|
+
return withOpenIndex(indexName, async () => {
|
|
10220
|
+
const entry = requireIndex(indexName);
|
|
10221
|
+
const manager = requireManager(indexName);
|
|
10222
|
+
if (rebalancingIndexes.has(indexName)) {
|
|
10223
|
+
throw new NarsilError(
|
|
10224
|
+
ErrorCodes.PARTITION_REBALANCING_BACKPRESSURE,
|
|
10225
|
+
`Index "${indexName}" is currently being rebalanced`
|
|
10226
|
+
);
|
|
10227
|
+
}
|
|
10228
|
+
validatePartitionConfig(partitionConfig);
|
|
10229
|
+
if (partitionConfig.maxPartitions !== void 0 && partitionConfig.maxPartitions < manager.partitionCount) {
|
|
9174
10230
|
throw new NarsilError(
|
|
9175
10231
|
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
9176
|
-
`
|
|
9177
|
-
{
|
|
10232
|
+
`maxPartitions (${partitionConfig.maxPartitions}) is less than the current partition count (${manager.partitionCount})`,
|
|
10233
|
+
{ maxPartitions: partitionConfig.maxPartitions, partitionCount: manager.partitionCount }
|
|
9178
10234
|
);
|
|
9179
10235
|
}
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
10236
|
+
const currentDocCount = manager.countDocuments();
|
|
10237
|
+
const newMaxDocs = partitionConfig.maxDocsPerPartition ?? entry.config.partitions?.maxDocsPerPartition;
|
|
10238
|
+
if (newMaxDocs !== void 0) {
|
|
10239
|
+
const newTotalCapacity = newMaxDocs * manager.partitionCount;
|
|
10240
|
+
if (newTotalCapacity < currentDocCount) {
|
|
10241
|
+
throw new NarsilError(
|
|
10242
|
+
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
10243
|
+
`New capacity (${newTotalCapacity}) is less than current document count (${currentDocCount})`,
|
|
10244
|
+
{ newTotalCapacity, currentDocCount }
|
|
10245
|
+
);
|
|
10246
|
+
}
|
|
10247
|
+
}
|
|
10248
|
+
if (!entry.config.partitions) entry.config.partitions = {};
|
|
10249
|
+
if (partitionConfig.maxDocsPerPartition !== void 0)
|
|
10250
|
+
entry.config.partitions.maxDocsPerPartition = partitionConfig.maxDocsPerPartition;
|
|
10251
|
+
if (partitionConfig.maxPartitions !== void 0)
|
|
10252
|
+
entry.config.partitions.maxPartitions = partitionConfig.maxPartitions;
|
|
10253
|
+
if (partitionConfig.watermark !== void 0) entry.config.partitions.watermark = partitionConfig.watermark;
|
|
10254
|
+
if (durability) await durability.manager.persistMetadata(indexName);
|
|
10255
|
+
core.watermarkNotifier.check(indexName);
|
|
10256
|
+
});
|
|
9189
10257
|
},
|
|
9190
10258
|
async getMemoryStats() {
|
|
9191
10259
|
let estimatedIndexBytes = 0;
|
|
@@ -9193,22 +10261,33 @@ function createNarsilFromCore(core, config) {
|
|
|
9193
10261
|
estimatedIndexBytes += executor.getManager(name)?.estimateMemoryBytes() ?? 0;
|
|
9194
10262
|
}
|
|
9195
10263
|
const workers = await orchestrator.getWorkerMemoryStats();
|
|
9196
|
-
|
|
10264
|
+
const lifecycleCounts = core.indexState.counts();
|
|
10265
|
+
return {
|
|
10266
|
+
process: readProcessMemory(),
|
|
10267
|
+
estimatedIndexBytes,
|
|
10268
|
+
openIndexCount: lifecycleCounts.open,
|
|
10269
|
+
closedIndexCount: lifecycleCounts.closed,
|
|
10270
|
+
reopenCount: lifecycleCounts.reopens,
|
|
10271
|
+
workers
|
|
10272
|
+
};
|
|
9197
10273
|
},
|
|
9198
10274
|
async compactVectors(indexName, fieldName) {
|
|
9199
10275
|
guardShutdown();
|
|
9200
10276
|
requireIndex(indexName);
|
|
9201
|
-
|
|
10277
|
+
await withOpenIndex(indexName, async () => {
|
|
10278
|
+
compactVectors(requireManager(indexName), indexName, fieldName);
|
|
10279
|
+
});
|
|
9202
10280
|
},
|
|
9203
10281
|
async optimizeVectors(indexName, fieldName) {
|
|
9204
10282
|
guardShutdown();
|
|
9205
10283
|
requireIndex(indexName);
|
|
9206
|
-
await optimizeVectors(requireManager(indexName), indexName, fieldName);
|
|
10284
|
+
await withOpenIndex(indexName, () => optimizeVectors(requireManager(indexName), indexName, fieldName));
|
|
9207
10285
|
},
|
|
9208
10286
|
vectorMaintenanceStatus(indexName) {
|
|
9209
10287
|
guardShutdown();
|
|
9210
10288
|
requireIndex(indexName);
|
|
9211
|
-
|
|
10289
|
+
const manager = executor.getManager(indexName);
|
|
10290
|
+
return manager === void 0 ? [] : getVectorMaintenanceStatus(manager);
|
|
9212
10291
|
},
|
|
9213
10292
|
on(event, handler) {
|
|
9214
10293
|
const key = event;
|
|
@@ -9241,16 +10320,16 @@ function createNarsilFromCore(core, config) {
|
|
|
9241
10320
|
async function createNarsil(config) {
|
|
9242
10321
|
const core = createEngineCore(config);
|
|
9243
10322
|
if (core.durability) {
|
|
9244
|
-
await core.durability.manager.recover();
|
|
10323
|
+
await core.durability.manager.recover(config?.lifecycle !== void 0);
|
|
9245
10324
|
}
|
|
9246
10325
|
if (core.invalidation) {
|
|
9247
10326
|
await core.invalidation.start();
|
|
9248
10327
|
}
|
|
9249
|
-
await core.analysisRebuild.reviewStaleIndexes();
|
|
10328
|
+
if (config?.lifecycle === void 0) await core.analysisRebuild.reviewStaleIndexes();
|
|
9250
10329
|
return createNarsilFromCore(core, config);
|
|
9251
10330
|
}
|
|
9252
10331
|
|
|
9253
10332
|
// src/index.ts
|
|
9254
10333
|
var VERSION = "0.1.0";
|
|
9255
10334
|
|
|
9256
|
-
export { DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, VERSION, WIRE_BATCH_BUDGET, applyDeleteEntry, applyIndexEntry, chunkByBudget, clampLimit, computeEntryChecksum, createAckMessage, createDurableDirectory, createEngineCore, createEngineIndex, createEntryBatchMessage, createEntryMessage, createForwardBatchMessage, createForwardBatchResultMessage, createForwardMessage, createInsyncAddMessage, createInsyncConfirmMessage, createInsyncRemoveMessage, createNarsil, createNarsilFromCore, now, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };
|
|
10335
|
+
export { DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, VERSION, WIRE_BATCH_BUDGET, applyDeleteEntry, applyIndexEntry, chunkByBudget, clampLimit, computeEntryChecksum, createAckMessage, createDurableDirectory, createEngineCore, createEngineIndex, createEntryBatchMessage, createEntryMessage, createForwardBatchMessage, createForwardBatchResultMessage, createForwardMessage, createInsyncAddMessage, createInsyncConfirmMessage, createInsyncRemoveMessage, createNarsil, createNarsilFromCore, dedupePinnedEntries, foldGroupReducer, listBindingOf, now, queryBindingOf, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };
|