@delali/narsil 0.2.4-next.43 → 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-TLRUUMVE.mjs → chunk-P4ELXVJQ.mjs} +1 -0
- package/dist/{chunk-KEDL6FFZ.mjs → chunk-WASNWIEK.mjs} +1142 -327
- package/dist/{chunk-YGQMKTKG.mjs → chunk-XNCJFU2E.mjs} +121 -27
- package/dist/{chunk-O4Q5JYNK.mjs → chunk-XWICBGYT.mjs} +1 -1
- package/dist/{client-B5uO5jL1.d.ts → client-C5BkUqB8.d.ts} +25 -2
- package/dist/client.d.ts +7 -7
- package/dist/client.mjs +11 -2
- package/dist/{config-NgUCkc6a.d.ts → config-B-ZI-ft5.d.ts} +2 -2
- package/dist/{config-B09-cud9.d.ts → config-BZRExglL.d.ts} +20 -3
- package/dist/{direct-executor-7O23GEHE.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 +1023 -864
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-CjLhdDIm.d.ts → engine-Cjr1uBLa.d.ts} +28 -6
- package/dist/index.browser.mjs +1237 -330
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +4 -4
- package/dist/{options-BakECW-r.d.ts → options-BC68VPoE.d.ts} +9 -2
- package/dist/persistence/durability/checkpoint-worker.d.ts +3 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.mjs +1 -0
- package/dist/{schema-DXOd96kx.d.ts → schema-B7juWg8Q.d.ts} +1 -1
- package/dist/{search-BGouJxH4.d.ts → search-DDHjfXhJ.d.ts} +14 -3
- package/dist/server.d.ts +8 -8
- package/dist/server.mjs +27 -6
- package/dist/{types-BTE33VZS.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, fnv1aBytes, requireWithinResultWindow, normalizeSort, sortSignatureOf, requireSortableFields, fanOutQuery, applySorting, applyPagination, resolveProjection, countsWithoutScores, fanOutMatchCount, 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
|
|
@@ -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) {
|
|
@@ -1835,6 +1845,9 @@ function mergeTimeOrderedSegments(ordered, fallback) {
|
|
|
1835
1845
|
validateMergedPartition(merged);
|
|
1836
1846
|
return merged;
|
|
1837
1847
|
}
|
|
1848
|
+
function countLiveDocuments(ordered) {
|
|
1849
|
+
return resolveWinners(ordered).size;
|
|
1850
|
+
}
|
|
1838
1851
|
function resolveWinners(ordered) {
|
|
1839
1852
|
const winner = /* @__PURE__ */ new Map();
|
|
1840
1853
|
for (let segIndex = 0; segIndex < ordered.length; segIndex += 1) {
|
|
@@ -3327,6 +3340,7 @@ async function writeSegmentedCheckpoint(input) {
|
|
|
3327
3340
|
});
|
|
3328
3341
|
}
|
|
3329
3342
|
const partitions = [];
|
|
3343
|
+
let wholeDocumentCount = input.wholePartitionPayload === void 0 ? null : 0;
|
|
3330
3344
|
for (const target of input.targets) {
|
|
3331
3345
|
const priorPartition = priorManifest?.partitions.find((p) => p.partitionId === target.partitionId);
|
|
3332
3346
|
const priorSeqNo = snapshotCheckpointFor(priorManifest?.checkpoint ?? [], target.partitionId);
|
|
@@ -3337,15 +3351,13 @@ async function writeSegmentedCheckpoint(input) {
|
|
|
3337
3351
|
priorSeqNo,
|
|
3338
3352
|
target.lastSeqNo
|
|
3339
3353
|
);
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
)
|
|
3348
|
-
);
|
|
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
|
+
}
|
|
3349
3361
|
}
|
|
3350
3362
|
carryForwardUncheckpointedPartitions(priorManifest, input.targets, partitions, checkpointByPartition);
|
|
3351
3363
|
const manifest = {
|
|
@@ -3357,6 +3369,7 @@ async function writeSegmentedCheckpoint(input) {
|
|
|
3357
3369
|
const parts = await encodeSegmentManifest(manifest);
|
|
3358
3370
|
await directory.atomicWrite(manifestKey(indexName), [parts.header, parts.payload]);
|
|
3359
3371
|
await collectGarbage(directory, indexName, priorManifest, manifest);
|
|
3372
|
+
return wholeDocumentCount;
|
|
3360
3373
|
}
|
|
3361
3374
|
async function writePartition(context, partitionId, priorPartition, entries) {
|
|
3362
3375
|
let segments = priorPartition ? [...priorPartition.segments] : [];
|
|
@@ -3476,9 +3489,9 @@ async function writeIndexCheckpoint(input) {
|
|
|
3476
3489
|
const { directory, metadata, targets, compactionThreshold, manager } = input;
|
|
3477
3490
|
const offloaded = !input.fromMemory && input.canOffload && metadata.tokenizer === void 0 && metadata.stopWords === void 0 && await runCheckpointOnWorker({ root: directory.root, metadata, targets, compactionThreshold });
|
|
3478
3491
|
if (offloaded) {
|
|
3479
|
-
return;
|
|
3492
|
+
return null;
|
|
3480
3493
|
}
|
|
3481
|
-
|
|
3494
|
+
return writeSegmentedCheckpoint({
|
|
3482
3495
|
directory,
|
|
3483
3496
|
metadata,
|
|
3484
3497
|
targets,
|
|
@@ -3510,9 +3523,171 @@ function createSeqOwner(startSeqNo, primaryTerm = SINGLE_NODE_PRIMARY_TERM) {
|
|
|
3510
3523
|
};
|
|
3511
3524
|
}
|
|
3512
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
|
+
|
|
3513
3687
|
// src/persistence/durability/types.ts
|
|
3514
3688
|
var DEFAULT_CHECKPOINT_INTERVAL_MS = 3e5;
|
|
3515
3689
|
var DEFAULT_CHECKPOINT_MUTATION_THRESHOLD = 1e5;
|
|
3690
|
+
var DEFAULT_ASYNC_FLUSH_INTERVAL_MS = 1e3;
|
|
3516
3691
|
|
|
3517
3692
|
// src/persistence/durability/group-commit.ts
|
|
3518
3693
|
function createGroupCommitCoordinator(sync) {
|
|
@@ -3758,7 +3933,6 @@ function createWalWriter(directory, config) {
|
|
|
3758
3933
|
}
|
|
3759
3934
|
|
|
3760
3935
|
// src/persistence/durability/manager.ts
|
|
3761
|
-
var DEFAULT_ASYNC_FLUSH_INTERVAL_MS = 1e3;
|
|
3762
3936
|
function createDurabilityManager(config, hooks, directoryOverride) {
|
|
3763
3937
|
const directory = createDurableDirectory(config.directory ?? "");
|
|
3764
3938
|
const canOffloadCheckpoint = directoryOverride === void 0;
|
|
@@ -3789,20 +3963,20 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3789
3963
|
}
|
|
3790
3964
|
hooks.onFatalError(error);
|
|
3791
3965
|
}
|
|
3792
|
-
function
|
|
3966
|
+
function toError4(err) {
|
|
3793
3967
|
return err instanceof Error ? err : new Error(String(err));
|
|
3794
3968
|
}
|
|
3795
3969
|
async function closeWriterReportingFailure(closePromise) {
|
|
3796
3970
|
try {
|
|
3797
3971
|
await closePromise;
|
|
3798
3972
|
} catch (err) {
|
|
3799
|
-
markFatal(
|
|
3973
|
+
markFatal(toError4(err));
|
|
3800
3974
|
}
|
|
3801
3975
|
}
|
|
3802
3976
|
function getOrCreateIndexState(indexName) {
|
|
3803
3977
|
let state = indexes.get(indexName);
|
|
3804
3978
|
if (state === void 0) {
|
|
3805
|
-
state = { partitions: /* @__PURE__ */ new Map(), mutationsSinceCheckpoint: 0, checkpointInFlight: null };
|
|
3979
|
+
state = { partitions: /* @__PURE__ */ new Map(), mutationsSinceCheckpoint: 0, checkpointInFlight: null, unloading: false };
|
|
3806
3980
|
indexes.set(indexName, state);
|
|
3807
3981
|
}
|
|
3808
3982
|
return state;
|
|
@@ -3865,7 +4039,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3865
4039
|
try {
|
|
3866
4040
|
await partition.walWriter.commit();
|
|
3867
4041
|
} catch (err) {
|
|
3868
|
-
partition.failed =
|
|
4042
|
+
partition.failed = toError4(err);
|
|
3869
4043
|
markFatal(partition.failed);
|
|
3870
4044
|
}
|
|
3871
4045
|
}
|
|
@@ -3879,7 +4053,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3879
4053
|
try {
|
|
3880
4054
|
await checkpointIndex(indexName);
|
|
3881
4055
|
} catch (err) {
|
|
3882
|
-
markFatal(
|
|
4056
|
+
markFatal(toError4(err));
|
|
3883
4057
|
}
|
|
3884
4058
|
}
|
|
3885
4059
|
}
|
|
@@ -3896,7 +4070,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3896
4070
|
}
|
|
3897
4071
|
async function checkpointIndex(indexName, fromMemory = false) {
|
|
3898
4072
|
const indexState = indexes.get(indexName);
|
|
3899
|
-
if (indexState === void 0) {
|
|
4073
|
+
if (indexState === void 0 || indexState.unloading) {
|
|
3900
4074
|
return;
|
|
3901
4075
|
}
|
|
3902
4076
|
if (indexState.checkpointInFlight !== null) {
|
|
@@ -3912,82 +4086,46 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
3912
4086
|
return run;
|
|
3913
4087
|
}
|
|
3914
4088
|
async function performCheckpoint(indexName, indexState, fromMemory = false) {
|
|
3915
|
-
|
|
3916
|
-
if (manager === void 0) {
|
|
3917
|
-
return;
|
|
3918
|
-
}
|
|
3919
|
-
const metadata = hooks.buildMetadata(indexName);
|
|
3920
|
-
if (metadata === void 0) {
|
|
3921
|
-
return;
|
|
3922
|
-
}
|
|
3923
|
-
const targets = [];
|
|
3924
|
-
for (let i = 0; i < manager.partitionCount; i += 1) {
|
|
3925
|
-
const partition = indexState.partitions.get(i);
|
|
3926
|
-
targets.push({
|
|
3927
|
-
partitionId: i,
|
|
3928
|
-
lastSeqNo: partition?.appliedSeqNo ?? 0,
|
|
3929
|
-
primaryTerm: partition?.seqOwner.primaryTerm ?? SINGLE_NODE_PRIMARY_TERM
|
|
3930
|
-
});
|
|
3931
|
-
if (partition !== void 0) {
|
|
3932
|
-
try {
|
|
3933
|
-
await partition.walWriter.commit();
|
|
3934
|
-
} catch (err) {
|
|
3935
|
-
partition.failed = toError3(err);
|
|
3936
|
-
markFatal(partition.failed);
|
|
3937
|
-
throw partition.failed;
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
await writeIndexCheckpoint({
|
|
4089
|
+
await runDurableCheckpoint({
|
|
3942
4090
|
directory,
|
|
3943
|
-
|
|
3944
|
-
|
|
4091
|
+
hooks,
|
|
4092
|
+
indexName,
|
|
4093
|
+
indexState,
|
|
3945
4094
|
compactionThreshold,
|
|
3946
|
-
manager,
|
|
3947
4095
|
canOffload: canOffloadCheckpoint,
|
|
3948
|
-
fromMemory
|
|
4096
|
+
fromMemory,
|
|
4097
|
+
queueMetadataWrite,
|
|
4098
|
+
markFatal
|
|
3949
4099
|
});
|
|
3950
|
-
await truncateCoveredSegments(directory, indexName, targets);
|
|
3951
|
-
await reclaimWalBeyondCount(directory, indexName, manager.partitionCount, indexState.partitions, markFatal);
|
|
3952
|
-
indexState.mutationsSinceCheckpoint = 0;
|
|
3953
4100
|
}
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
vectorIndexes: hooks.getVectorIndexes(indexName)
|
|
3968
|
-
};
|
|
3969
|
-
const checkpoint = await loadSnapshot(directory, indexName, deps);
|
|
3970
|
-
const manifest = await readSegmentManifest(directory, indexName);
|
|
3971
|
-
if (manifest !== null) {
|
|
3972
|
-
await reclaimOrphanedSegments(directory, indexName, manifest);
|
|
3973
|
-
}
|
|
3974
|
-
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId += 1) {
|
|
3975
|
-
const fromSeqNo = snapshotCheckpointFor(checkpoint, partitionId);
|
|
3976
|
-
const { highestSeqNo } = await replayWal(directory, indexName, partitionId, fromSeqNo, deps);
|
|
3977
|
-
getOrCreatePartition(indexName, partitionId, highestSeqNo);
|
|
3978
|
-
}
|
|
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
|
+
);
|
|
3979
4114
|
}
|
|
3980
4115
|
return {
|
|
3981
4116
|
isActive() {
|
|
3982
4117
|
return true;
|
|
3983
4118
|
},
|
|
3984
|
-
async recover() {
|
|
4119
|
+
async recover(metadataOnly = false) {
|
|
3985
4120
|
const names = await listPersistedIndexes(directory);
|
|
3986
4121
|
for (const indexName of names) {
|
|
3987
|
-
await recoverIndex(indexName);
|
|
4122
|
+
await recoverIndex(indexName, metadataOnly);
|
|
3988
4123
|
}
|
|
3989
4124
|
startCheckpointTimer();
|
|
3990
4125
|
},
|
|
4126
|
+
recoverIndex(indexName) {
|
|
4127
|
+
return recoverIndex(indexName);
|
|
4128
|
+
},
|
|
3991
4129
|
highestPersistedSeqNo(indexName, partitionId) {
|
|
3992
4130
|
return indexes.get(indexName)?.partitions.get(partitionId)?.appliedSeqNo ?? 0;
|
|
3993
4131
|
},
|
|
@@ -4011,7 +4149,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
4011
4149
|
const entry = buildMutationEntry(record, allocatedSeqNo);
|
|
4012
4150
|
await partition.walWriter.append(entry);
|
|
4013
4151
|
} catch (err) {
|
|
4014
|
-
partition.failed =
|
|
4152
|
+
partition.failed = toError4(err);
|
|
4015
4153
|
markFatal(partition.failed);
|
|
4016
4154
|
throw partition.failed;
|
|
4017
4155
|
}
|
|
@@ -4023,7 +4161,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
4023
4161
|
try {
|
|
4024
4162
|
await partition.walWriter.commit();
|
|
4025
4163
|
} catch (err) {
|
|
4026
|
-
partition.failed =
|
|
4164
|
+
partition.failed = toError4(err);
|
|
4027
4165
|
markFatal(partition.failed);
|
|
4028
4166
|
throw partition.failed;
|
|
4029
4167
|
}
|
|
@@ -4033,7 +4171,7 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
4033
4171
|
startAsyncFlushTimer();
|
|
4034
4172
|
if (indexState.mutationsSinceCheckpoint >= checkpointMutationThreshold) {
|
|
4035
4173
|
void checkpointIndex(record.indexName).catch((err) => {
|
|
4036
|
-
markFatal(
|
|
4174
|
+
markFatal(toError4(err));
|
|
4037
4175
|
});
|
|
4038
4176
|
}
|
|
4039
4177
|
return allocatedSeqNo;
|
|
@@ -4063,15 +4201,23 @@ function createDurabilityManager(config, hooks, directoryOverride) {
|
|
|
4063
4201
|
async removeIndex(indexName) {
|
|
4064
4202
|
const indexState = indexes.get(indexName);
|
|
4065
4203
|
if (indexState !== void 0) {
|
|
4066
|
-
|
|
4067
|
-
await closeWriterReportingFailure(partition.walWriter.close());
|
|
4068
|
-
}
|
|
4204
|
+
await drainIndexStateForUnload(indexState, metadataWrites.get(indexName), closeWriterReportingFailure);
|
|
4069
4205
|
indexes.delete(indexName);
|
|
4206
|
+
metadataWrites.delete(indexName);
|
|
4070
4207
|
}
|
|
4071
4208
|
for (const key of await directory.list(`${indexName}/`)) {
|
|
4072
4209
|
await directory.remove(key);
|
|
4073
4210
|
}
|
|
4074
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
|
+
},
|
|
4075
4221
|
async shutdown() {
|
|
4076
4222
|
shuttingDown = true;
|
|
4077
4223
|
if (checkpointTimer !== null) {
|
|
@@ -4110,7 +4256,13 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4110
4256
|
function getOrCreateIndexState(indexName) {
|
|
4111
4257
|
let state = indexes.get(indexName);
|
|
4112
4258
|
if (state === void 0) {
|
|
4113
|
-
state = {
|
|
4259
|
+
state = {
|
|
4260
|
+
mutationsSinceCheckpoint: 0,
|
|
4261
|
+
checkpointInFlight: null,
|
|
4262
|
+
applyChain: Promise.resolve(),
|
|
4263
|
+
unloading: false,
|
|
4264
|
+
appliedSeqNoByPartition: /* @__PURE__ */ new Map()
|
|
4265
|
+
};
|
|
4114
4266
|
indexes.set(indexName, state);
|
|
4115
4267
|
}
|
|
4116
4268
|
return state;
|
|
@@ -4141,10 +4293,11 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4141
4293
|
if (manager === void 0 || metadata === void 0) {
|
|
4142
4294
|
return;
|
|
4143
4295
|
}
|
|
4296
|
+
const documentCount = manager.countDocuments();
|
|
4144
4297
|
const seqNoByPartition = /* @__PURE__ */ new Map();
|
|
4145
4298
|
const primaryTermByPartition = /* @__PURE__ */ new Map();
|
|
4146
4299
|
for (let i = 0; i < manager.partitionCount; i += 1) {
|
|
4147
|
-
seqNoByPartition.set(i, 0);
|
|
4300
|
+
seqNoByPartition.set(i, indexState.appliedSeqNoByPartition.get(i) ?? 0);
|
|
4148
4301
|
primaryTermByPartition.set(i, SINGLE_NODE_PRIMARY_TERM);
|
|
4149
4302
|
}
|
|
4150
4303
|
const { parts } = await buildSnapshotBundleBytes({
|
|
@@ -4160,6 +4313,13 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4160
4313
|
primaryTermByPartition
|
|
4161
4314
|
});
|
|
4162
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
|
+
});
|
|
4163
4323
|
indexState.mutationsSinceCheckpoint = 0;
|
|
4164
4324
|
if (publisher !== void 0) {
|
|
4165
4325
|
const partitionIds = [];
|
|
@@ -4169,15 +4329,18 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4169
4329
|
await publisher.publishPartitions(indexName, partitionIds);
|
|
4170
4330
|
}
|
|
4171
4331
|
}
|
|
4172
|
-
async function checkpointIndex(indexName) {
|
|
4332
|
+
async function checkpointIndex(indexName, forceFresh = false) {
|
|
4173
4333
|
const indexState = indexes.get(indexName);
|
|
4174
|
-
if (indexState === void 0) {
|
|
4334
|
+
if (indexState === void 0 || indexState.unloading) {
|
|
4175
4335
|
return;
|
|
4176
4336
|
}
|
|
4177
4337
|
if (indexState.checkpointInFlight !== null) {
|
|
4178
|
-
|
|
4338
|
+
await indexState.checkpointInFlight;
|
|
4339
|
+
if (!forceFresh) {
|
|
4340
|
+
return;
|
|
4341
|
+
}
|
|
4179
4342
|
}
|
|
4180
|
-
const run = performCheckpoint(indexName, indexState).finally(() => {
|
|
4343
|
+
const run = indexState.applyChain.then(() => performCheckpoint(indexName, indexState)).finally(() => {
|
|
4181
4344
|
indexState.checkpointInFlight = null;
|
|
4182
4345
|
});
|
|
4183
4346
|
indexState.checkpointInFlight = run;
|
|
@@ -4192,13 +4355,35 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4192
4355
|
);
|
|
4193
4356
|
return queued;
|
|
4194
4357
|
}
|
|
4195
|
-
async function recoverIndex(indexName) {
|
|
4358
|
+
async function recoverIndex(indexName, metadataOnly = false) {
|
|
4196
4359
|
const metaBytes = await adapter.load(metadataKey(indexName));
|
|
4197
4360
|
if (metaBytes === null) {
|
|
4198
4361
|
return;
|
|
4199
4362
|
}
|
|
4200
4363
|
const { metadata } = await readMetadataEnvelope(metaBytes);
|
|
4201
|
-
|
|
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
|
+
}
|
|
4202
4387
|
const manager = hooks.getManager(indexName);
|
|
4203
4388
|
if (manager === void 0) {
|
|
4204
4389
|
return;
|
|
@@ -4207,33 +4392,58 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4207
4392
|
if (snapshotBytes === null) {
|
|
4208
4393
|
return;
|
|
4209
4394
|
}
|
|
4210
|
-
await loadSnapshotBundleBytes(snapshotBytes, {
|
|
4395
|
+
const checkpoint = await loadSnapshotBundleBytes(snapshotBytes, {
|
|
4211
4396
|
manager,
|
|
4212
4397
|
vectorFieldPaths: hooks.getVectorFieldPaths(indexName),
|
|
4213
4398
|
vectorIndexes: hooks.getVectorIndexes(indexName)
|
|
4214
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);
|
|
4215
4414
|
}
|
|
4216
4415
|
return {
|
|
4217
4416
|
isActive() {
|
|
4218
4417
|
return true;
|
|
4219
4418
|
},
|
|
4220
|
-
async recover() {
|
|
4419
|
+
async recover(metadataOnly = false) {
|
|
4221
4420
|
const keys = await adapter.list("");
|
|
4222
4421
|
for (const key of keys) {
|
|
4223
4422
|
if (key.endsWith("/meta")) {
|
|
4224
|
-
await recoverIndex(key.slice(0, -"/meta".length));
|
|
4423
|
+
await recoverIndex(key.slice(0, -"/meta".length), metadataOnly);
|
|
4225
4424
|
}
|
|
4226
4425
|
}
|
|
4227
4426
|
startCheckpointTimer();
|
|
4228
4427
|
},
|
|
4229
|
-
|
|
4230
|
-
return
|
|
4428
|
+
recoverIndex(indexName) {
|
|
4429
|
+
return recoverIndex(indexName);
|
|
4430
|
+
},
|
|
4431
|
+
highestPersistedSeqNo(indexName, partitionId) {
|
|
4432
|
+
return indexes.get(indexName)?.appliedSeqNoByPartition.get(partitionId) ?? 0;
|
|
4231
4433
|
},
|
|
4232
4434
|
async recordMutation(record) {
|
|
4233
4435
|
const indexState = getOrCreateIndexState(record.indexName);
|
|
4234
|
-
const buffered = indexState.applyChain.then(() =>
|
|
4235
|
-
|
|
4236
|
-
|
|
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;
|
|
4237
4447
|
indexState.mutationsSinceCheckpoint += 1;
|
|
4238
4448
|
startCheckpointTimer();
|
|
4239
4449
|
if (indexState.mutationsSinceCheckpoint >= checkpointMutationThreshold) {
|
|
@@ -4241,7 +4451,7 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4241
4451
|
hooks.onFatalError(err instanceof Error ? err : new Error(String(err)));
|
|
4242
4452
|
});
|
|
4243
4453
|
}
|
|
4244
|
-
return
|
|
4454
|
+
return appliedSeqNo;
|
|
4245
4455
|
},
|
|
4246
4456
|
persistMetadata(indexName) {
|
|
4247
4457
|
return queueMetadataWrite(indexName, async () => {
|
|
@@ -4257,17 +4467,49 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4257
4467
|
checkpoint(indexName) {
|
|
4258
4468
|
return checkpointIndex(indexName);
|
|
4259
4469
|
},
|
|
4470
|
+
checkpointFromMemory(indexName) {
|
|
4471
|
+
return checkpointIndex(indexName, true);
|
|
4472
|
+
},
|
|
4260
4473
|
async checkpointAll() {
|
|
4261
4474
|
for (const indexName of [...indexes.keys()]) {
|
|
4262
4475
|
await checkpointIndex(indexName);
|
|
4263
4476
|
}
|
|
4264
4477
|
},
|
|
4265
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
|
+
}
|
|
4266
4491
|
indexes.delete(indexName);
|
|
4492
|
+
metadataWrites.delete(indexName);
|
|
4267
4493
|
for (const key of await adapter.list(`${indexName}/`)) {
|
|
4268
4494
|
await adapter.delete(key);
|
|
4269
4495
|
}
|
|
4270
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
|
+
},
|
|
4271
4513
|
async reloadIndex(indexName) {
|
|
4272
4514
|
const indexState = getOrCreateIndexState(indexName);
|
|
4273
4515
|
const reload = indexState.applyChain.then(() => recoverIndex(indexName));
|
|
@@ -4291,21 +4533,22 @@ function createSnapshotOnlyManager(adapter, config, hooks, publisher) {
|
|
|
4291
4533
|
}
|
|
4292
4534
|
|
|
4293
4535
|
// src/engine/durability-integration.ts
|
|
4294
|
-
function buildMetadata(indexName, hooks) {
|
|
4536
|
+
function buildMetadata(indexName, hooks, documentCount) {
|
|
4295
4537
|
const config = hooks.getIndexConfig(indexName);
|
|
4296
4538
|
const manager = hooks.getManager(indexName);
|
|
4297
|
-
if (config === void 0
|
|
4539
|
+
if (config === void 0) {
|
|
4298
4540
|
return void 0;
|
|
4299
4541
|
}
|
|
4300
4542
|
const metadata = {
|
|
4301
4543
|
indexName,
|
|
4302
4544
|
schema: config.schema,
|
|
4303
4545
|
language: config.language,
|
|
4304
|
-
partitionCount: manager.partitionCount,
|
|
4546
|
+
partitionCount: manager?.partitionCount ?? config.partitionCount,
|
|
4305
4547
|
bm25Params: { k1: config.k1, b: config.b },
|
|
4306
4548
|
createdAt: Date.now(),
|
|
4307
4549
|
engineVersion: VERSION
|
|
4308
4550
|
};
|
|
4551
|
+
metadata.documentCount = documentCount ?? config.documentCount;
|
|
4309
4552
|
if (config.embedding) {
|
|
4310
4553
|
metadata.embedding = config.embedding;
|
|
4311
4554
|
}
|
|
@@ -4355,8 +4598,9 @@ function createDurabilityIntegration(tier, hooks) {
|
|
|
4355
4598
|
getManager: hooks.getManager,
|
|
4356
4599
|
getVectorFieldPaths: hooks.getVectorFieldPaths,
|
|
4357
4600
|
getVectorIndexes: hooks.getVectorIndexes,
|
|
4358
|
-
buildMetadata: (indexName) => buildMetadata(indexName, hooks),
|
|
4601
|
+
buildMetadata: (indexName, documentCount) => buildMetadata(indexName, hooks, documentCount),
|
|
4359
4602
|
createIndexFromMetadata: hooks.createIndexFromMetadata,
|
|
4603
|
+
recordCheckpoint: hooks.recordCheckpoint,
|
|
4360
4604
|
onFatalError: hooks.onFatalError
|
|
4361
4605
|
};
|
|
4362
4606
|
const manager = tier.kind === "wal" ? createDurabilityManager(tier.config, managerHooks) : createSnapshotOnlyManager(tier.adapter, tier.config, managerHooks, hooks.checkpointPublisher);
|
|
@@ -4395,9 +4639,9 @@ function createDurabilityFromTier(tier, wiring) {
|
|
|
4395
4639
|
}
|
|
4396
4640
|
return createDurabilityIntegration(tier, {
|
|
4397
4641
|
checkpointPublisher: { publishPartitions: wiring.publishCheckpointedPartitions },
|
|
4398
|
-
getManager: (indexName) => wiring.indexRegistry.has(indexName) ? wiring.
|
|
4642
|
+
getManager: (indexName) => wiring.indexRegistry.has(indexName) ? wiring.getManager(indexName) : void 0,
|
|
4399
4643
|
getVectorFieldPaths: (indexName) => wiring.indexRegistry.get(indexName)?.vectorFieldPaths ?? /* @__PURE__ */ new Set(),
|
|
4400
|
-
getVectorIndexes: (indexName) => wiring.indexRegistry.has(indexName) ? wiring.
|
|
4644
|
+
getVectorIndexes: (indexName) => (wiring.indexRegistry.has(indexName) ? wiring.getManager(indexName)?.getVectorIndexes() : void 0) ?? /* @__PURE__ */ new Map(),
|
|
4401
4645
|
getIndexConfig: (indexName) => {
|
|
4402
4646
|
const entry = wiring.indexRegistry.get(indexName);
|
|
4403
4647
|
if (entry === void 0) {
|
|
@@ -4410,6 +4654,8 @@ function createDurabilityFromTier(tier, wiring) {
|
|
|
4410
4654
|
return {
|
|
4411
4655
|
...entry.indexUuid !== null ? { indexUuid: entry.indexUuid } : {},
|
|
4412
4656
|
...entry.heldPartitions !== null ? { heldPartitions: entry.heldPartitions } : {},
|
|
4657
|
+
documentCount: entry.documentCount,
|
|
4658
|
+
partitionCount: entry.partitionCount,
|
|
4413
4659
|
schema: flattenSchema(entry.config.schema),
|
|
4414
4660
|
language: entry.language.name,
|
|
4415
4661
|
k1: entry.config.bm25?.k1 ?? 1.2,
|
|
@@ -4429,10 +4675,401 @@ function createDurabilityFromTier(tier, wiring) {
|
|
|
4429
4675
|
};
|
|
4430
4676
|
},
|
|
4431
4677
|
createIndexFromMetadata: wiring.createIndexFromMetadata,
|
|
4678
|
+
recordCheckpoint: wiring.recordCheckpoint,
|
|
4432
4679
|
onFatalError: wiring.emitFatalError
|
|
4433
4680
|
});
|
|
4434
4681
|
}
|
|
4435
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
|
+
}
|
|
5070
|
+
});
|
|
5071
|
+
}
|
|
5072
|
+
|
|
4436
5073
|
// src/engine/invalidation.ts
|
|
4437
5074
|
var STATISTICS_BROADCAST_INTERVAL_MS = 5e3;
|
|
4438
5075
|
var FOREIGN_STATS_TTL_MS = 6e4;
|
|
@@ -4458,7 +5095,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4458
5095
|
const reloadChains = /* @__PURE__ */ new Map();
|
|
4459
5096
|
let broadcastTimer = null;
|
|
4460
5097
|
let stopped = false;
|
|
4461
|
-
function
|
|
5098
|
+
function toError4(err) {
|
|
4462
5099
|
return err instanceof Error ? err : new Error(String(err));
|
|
4463
5100
|
}
|
|
4464
5101
|
function pruneStaleForeignStats(entries) {
|
|
@@ -4471,7 +5108,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4471
5108
|
}
|
|
4472
5109
|
function scheduleReload(indexName) {
|
|
4473
5110
|
const chain = reloadChains.get(indexName) ?? Promise.resolve();
|
|
4474
|
-
const next = chain.then(() => reloadIndex(indexName)).catch((err) => onError(
|
|
5111
|
+
const next = chain.then(() => reloadIndex(indexName)).catch((err) => onError(toError4(err)));
|
|
4475
5112
|
reloadChains.set(indexName, next);
|
|
4476
5113
|
}
|
|
4477
5114
|
function storeForeignStats(indexName, sourceInstanceId, stats) {
|
|
@@ -4497,7 +5134,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4497
5134
|
}
|
|
4498
5135
|
storeForeignStats(event.indexName, event.instanceId, event.stats);
|
|
4499
5136
|
} catch (err) {
|
|
4500
|
-
onError(
|
|
5137
|
+
onError(toError4(err));
|
|
4501
5138
|
}
|
|
4502
5139
|
}
|
|
4503
5140
|
async function publishOwnStatistics() {
|
|
@@ -4519,7 +5156,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4519
5156
|
}
|
|
4520
5157
|
});
|
|
4521
5158
|
} catch (err) {
|
|
4522
|
-
onError(
|
|
5159
|
+
onError(toError4(err));
|
|
4523
5160
|
}
|
|
4524
5161
|
}
|
|
4525
5162
|
}
|
|
@@ -4538,7 +5175,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4538
5175
|
sourceInstanceId: instanceId
|
|
4539
5176
|
});
|
|
4540
5177
|
} catch (err) {
|
|
4541
|
-
onError(
|
|
5178
|
+
onError(toError4(err));
|
|
4542
5179
|
}
|
|
4543
5180
|
},
|
|
4544
5181
|
broadcastStats(indexName) {
|
|
@@ -4583,7 +5220,7 @@ function createInvalidationIntegration(deps) {
|
|
|
4583
5220
|
try {
|
|
4584
5221
|
await adapter.shutdown();
|
|
4585
5222
|
} catch (err) {
|
|
4586
|
-
onError(
|
|
5223
|
+
onError(toError4(err));
|
|
4587
5224
|
}
|
|
4588
5225
|
}
|
|
4589
5226
|
};
|
|
@@ -4625,7 +5262,7 @@ async function transferIndexToPool(indexName, pool, config, manager) {
|
|
|
4625
5262
|
}
|
|
4626
5263
|
|
|
4627
5264
|
// src/engine/orchestration/eligibility.ts
|
|
4628
|
-
function
|
|
5265
|
+
function toError3(value) {
|
|
4629
5266
|
return value instanceof Error ? value : new Error(String(value));
|
|
4630
5267
|
}
|
|
4631
5268
|
function isDeterministicFailure(error) {
|
|
@@ -5211,7 +5848,7 @@ async function promoteBeforeBatch(state, indexName, incomingCount) {
|
|
|
5211
5848
|
if (!result.shouldPromote) return;
|
|
5212
5849
|
state.promotionInProgress = true;
|
|
5213
5850
|
const run = runPromotion(state, result.reason).catch((err) => {
|
|
5214
|
-
const error =
|
|
5851
|
+
const error = toError3(err);
|
|
5215
5852
|
state.promotionBlocked = isDeterministicFailure(error);
|
|
5216
5853
|
state.promotionInProgress = false;
|
|
5217
5854
|
state.callbacks?.onPromotionFailure?.(result.reason, error, !state.promotionBlocked);
|
|
@@ -5232,7 +5869,7 @@ async function checkPromotion(state) {
|
|
|
5232
5869
|
state.promotionInProgress = true;
|
|
5233
5870
|
setTimeout(() => {
|
|
5234
5871
|
const run = runPromotion(state, result.reason).catch((err) => {
|
|
5235
|
-
const error =
|
|
5872
|
+
const error = toError3(err);
|
|
5236
5873
|
state.promotionBlocked = isDeterministicFailure(error);
|
|
5237
5874
|
state.promotionInProgress = false;
|
|
5238
5875
|
state.callbacks?.onPromotionFailure?.(result.reason, error, !state.promotionBlocked);
|
|
@@ -5402,6 +6039,56 @@ function createWorkerOrchestrator(config, executor, promoter, indexRegistry, cal
|
|
|
5402
6039
|
state.segmentLedger.clear();
|
|
5403
6040
|
}
|
|
5404
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
|
+
}
|
|
5405
6092
|
async function replicate(action) {
|
|
5406
6093
|
await replicateToWorkers(state, action);
|
|
5407
6094
|
if (action.type === "attachSegments") {
|
|
@@ -5414,6 +6101,9 @@ function createWorkerOrchestrator(config, executor, promoter, indexRegistry, cal
|
|
|
5414
6101
|
replicateToWorkers: replicate,
|
|
5415
6102
|
awaitReplication: (indexName) => awaitReplicationIdle(state, indexName),
|
|
5416
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),
|
|
5417
6107
|
buildSegments: (requests) => buildSegments(state, requests),
|
|
5418
6108
|
segmentBuildConcurrency: (indexName) => segmentBuildConcurrency(state, indexName),
|
|
5419
6109
|
searchViaWorker: (indexName, params, globalStats, partitionIds) => searchViaWorker(state, indexName, params, globalStats, partitionIds),
|
|
@@ -5425,6 +6115,11 @@ function createWorkerOrchestrator(config, executor, promoter, indexRegistry, cal
|
|
|
5425
6115
|
};
|
|
5426
6116
|
}
|
|
5427
6117
|
|
|
6118
|
+
// src/engine/vector-fields.ts
|
|
6119
|
+
function getVectorFieldPaths(schema) {
|
|
6120
|
+
return new Set(extractVectorFieldsFromSchema(schema).keys());
|
|
6121
|
+
}
|
|
6122
|
+
|
|
5428
6123
|
// src/engine/watermark.ts
|
|
5429
6124
|
function createWatermarkNotifier(deps) {
|
|
5430
6125
|
const latchedCapacity = /* @__PURE__ */ new Map();
|
|
@@ -5460,10 +6155,7 @@ function createWatermarkNotifier(deps) {
|
|
|
5460
6155
|
}
|
|
5461
6156
|
|
|
5462
6157
|
// src/engine/core.ts
|
|
5463
|
-
function
|
|
5464
|
-
return new Set(extractVectorFieldsFromSchema(schema).keys());
|
|
5465
|
-
}
|
|
5466
|
-
function createEngineCore(config) {
|
|
6158
|
+
function createEngineCore(config, hooks) {
|
|
5467
6159
|
const executor = createDirectExecutor();
|
|
5468
6160
|
const promoter = createExecutionPromoter({
|
|
5469
6161
|
perIndexThreshold: config?.workers?.promotionThreshold,
|
|
@@ -5547,8 +6239,12 @@ function createEngineCore(config) {
|
|
|
5547
6239
|
}
|
|
5548
6240
|
return manager;
|
|
5549
6241
|
}
|
|
5550
|
-
async function createIndexFromMetadata(metadata) {
|
|
5551
|
-
|
|
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
|
+
}
|
|
5552
6248
|
return;
|
|
5553
6249
|
}
|
|
5554
6250
|
const indexConfig = reconstructSchemaFromMetadata(metadata);
|
|
@@ -5577,7 +6273,7 @@ function createEngineCore(config) {
|
|
|
5577
6273
|
}
|
|
5578
6274
|
}
|
|
5579
6275
|
try {
|
|
5580
|
-
executor.createIndex(metadata.indexName, indexConfig, language);
|
|
6276
|
+
if (loadData) executor.createIndex(metadata.indexName, indexConfig, language);
|
|
5581
6277
|
} catch (err) {
|
|
5582
6278
|
if (err instanceof NarsilError && err.code === ErrorCodes.CONFIG_INVALID) {
|
|
5583
6279
|
throw new NarsilError(err.code, `Recovery of index "${metadata.indexName}" failed: ${err.message}`, {
|
|
@@ -5595,8 +6291,12 @@ function createEngineCore(config) {
|
|
|
5595
6291
|
embeddingAdapterName: adapterName,
|
|
5596
6292
|
vectorFieldPaths: getVectorFieldPaths(indexConfig.schema),
|
|
5597
6293
|
indexUuid: metadata.indexUuid ?? null,
|
|
5598
|
-
heldPartitions: metadata.heldPartitions ?? null
|
|
6294
|
+
heldPartitions: metadata.heldPartitions ?? null,
|
|
6295
|
+
documentCount: metadata.documentCount ?? 0,
|
|
6296
|
+
partitionCount: metadata.partitionCount
|
|
5599
6297
|
});
|
|
6298
|
+
if (loadData) await indexState.registerOpen(metadata.indexName);
|
|
6299
|
+
else indexState.registerClosed(metadata.indexName);
|
|
5600
6300
|
if (metadata.analysisRevision !== language.revision) {
|
|
5601
6301
|
analysisRebuild.markStale({
|
|
5602
6302
|
indexName: metadata.indexName,
|
|
@@ -5607,9 +6307,12 @@ function createEngineCore(config) {
|
|
|
5607
6307
|
}
|
|
5608
6308
|
}
|
|
5609
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
|
+
}
|
|
5610
6313
|
let invalidation = null;
|
|
5611
6314
|
const durability = createDurabilityFromTier(durabilityTier, {
|
|
5612
|
-
|
|
6315
|
+
getManager: (indexName) => executor.getManager(indexName),
|
|
5613
6316
|
indexRegistry,
|
|
5614
6317
|
createIndexFromMetadata,
|
|
5615
6318
|
emitFatalError(error) {
|
|
@@ -5618,7 +6321,13 @@ function createEngineCore(config) {
|
|
|
5618
6321
|
for (const handler of handlers) handler({ error });
|
|
5619
6322
|
}
|
|
5620
6323
|
},
|
|
5621
|
-
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
|
+
}
|
|
5622
6331
|
});
|
|
5623
6332
|
invalidation = createInvalidationFromConfig(config, durabilityTier?.kind ?? null, {
|
|
5624
6333
|
getManager: (indexName) => executor.getManager(indexName) ?? void 0,
|
|
@@ -5664,6 +6373,18 @@ function createEngineCore(config) {
|
|
|
5664
6373
|
resyncIndex: (indexName, wasPromoted) => orchestrator.resyncIndex(indexName, wasPromoted),
|
|
5665
6374
|
durabilityManager: durability?.manager ?? null
|
|
5666
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
|
+
});
|
|
5667
6388
|
const mutationCtx = {
|
|
5668
6389
|
executor,
|
|
5669
6390
|
pluginRegistry,
|
|
@@ -5717,6 +6438,7 @@ function createEngineCore(config) {
|
|
|
5717
6438
|
bufferIfRebalancing,
|
|
5718
6439
|
watermarkNotifier,
|
|
5719
6440
|
analysisRebuild,
|
|
6441
|
+
indexState,
|
|
5720
6442
|
mutationCtx,
|
|
5721
6443
|
rebalanceCtx
|
|
5722
6444
|
};
|
|
@@ -5875,8 +6597,11 @@ async function createEngineIndex(core, config, name, indexConfig, indexUuid) {
|
|
|
5875
6597
|
embeddingAdapterName,
|
|
5876
6598
|
vectorFieldPaths: getVectorFieldPaths(indexConfig.schema),
|
|
5877
6599
|
indexUuid: indexUuid ?? null,
|
|
5878
|
-
heldPartitions: null
|
|
6600
|
+
heldPartitions: null,
|
|
6601
|
+
documentCount: 0,
|
|
6602
|
+
partitionCount: indexConfig.partitions?.maxPartitions ?? 1
|
|
5879
6603
|
});
|
|
6604
|
+
await core.indexState.registerOpen(name);
|
|
5880
6605
|
if (core.durability) {
|
|
5881
6606
|
await core.durability.manager.persistMetadata(name);
|
|
5882
6607
|
}
|
|
@@ -5885,13 +6610,21 @@ async function createEngineIndex(core, config, name, indexConfig, indexUuid) {
|
|
|
5885
6610
|
async function dropEngineIndex(core, name) {
|
|
5886
6611
|
core.guardShutdown();
|
|
5887
6612
|
const entry = core.requireIndex(name);
|
|
5888
|
-
core.
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
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
|
+
});
|
|
5895
6628
|
await core.pluginRegistry.runHook("onIndexDrop", { indexName: name, config: entry.config });
|
|
5896
6629
|
}
|
|
5897
6630
|
function registerEngineEmbeddingAdapter(core, name, adapter) {
|
|
@@ -5926,6 +6659,7 @@ function registerEngineEmbeddingAdapter(core, name, adapter) {
|
|
|
5926
6659
|
// src/engine/lifecycle.ts
|
|
5927
6660
|
async function shutdownEngine(core) {
|
|
5928
6661
|
const { executor, durability, indexRegistry, eventHandlers, orchestrator } = core;
|
|
6662
|
+
core.indexState.dispose();
|
|
5929
6663
|
for (const [name] of indexRegistry) {
|
|
5930
6664
|
const manager = executor.getManager(name);
|
|
5931
6665
|
if (manager) {
|
|
@@ -7817,8 +8551,12 @@ async function restoreFromSnapshot(indexName, data, deps) {
|
|
|
7817
8551
|
embeddingAdapterName: adapterName,
|
|
7818
8552
|
vectorFieldPaths,
|
|
7819
8553
|
indexUuid: null,
|
|
7820
|
-
heldPartitions: null
|
|
8554
|
+
heldPartitions: null,
|
|
8555
|
+
documentCount: 0,
|
|
8556
|
+
partitionCount: envelope.partitions.length
|
|
7821
8557
|
});
|
|
8558
|
+
await deps.indexState.registerOpen(indexName);
|
|
8559
|
+
const release = await deps.indexState.acquire(indexName, false);
|
|
7822
8560
|
try {
|
|
7823
8561
|
const manager = deps.requireManager(indexName);
|
|
7824
8562
|
while (manager.partitionCount < envelope.partitions.length) {
|
|
@@ -7838,26 +8576,36 @@ async function restoreFromSnapshot(indexName, data, deps) {
|
|
|
7838
8576
|
}
|
|
7839
8577
|
}
|
|
7840
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
|
+
}
|
|
7841
8596
|
} catch (err) {
|
|
7842
8597
|
try {
|
|
7843
8598
|
deps.executor.dropIndex(indexName);
|
|
7844
|
-
deps.indexRegistry.delete(indexName);
|
|
7845
8599
|
} catch (_) {
|
|
7846
8600
|
}
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId++) {
|
|
7852
|
-
for (const docId of manager.getPartition(partitionId).docIds()) {
|
|
7853
|
-
const document = manager.get(docId);
|
|
7854
|
-
if (document !== void 0) {
|
|
7855
|
-
await deps.durability.recordInsertOrUpdate(indexName, docId, document, noopApply2);
|
|
7856
|
-
}
|
|
7857
|
-
}
|
|
8601
|
+
deps.indexRegistry.delete(indexName);
|
|
8602
|
+
deps.indexState.forget(indexName);
|
|
8603
|
+
if (deps.durability) {
|
|
8604
|
+
await deps.durability.manager.removeIndex(indexName).catch(() => void 0);
|
|
7858
8605
|
}
|
|
7859
|
-
|
|
7860
|
-
|
|
8606
|
+
throw err;
|
|
8607
|
+
} finally {
|
|
8608
|
+
release();
|
|
7861
8609
|
}
|
|
7862
8610
|
}
|
|
7863
8611
|
function noopApply2() {
|
|
@@ -9153,107 +9901,132 @@ function broadcastStatsFor(core, options) {
|
|
|
9153
9901
|
}
|
|
9154
9902
|
async function runEngineQuery(core, indexName, params, options) {
|
|
9155
9903
|
core.guardShutdown();
|
|
9156
|
-
const
|
|
9157
|
-
const manager = core.requireManager(indexName);
|
|
9158
|
-
const resolvedParams = await resolveVectorText(
|
|
9159
|
-
scopedParams(params, options),
|
|
9160
|
-
entry.embeddingAdapter,
|
|
9161
|
-
core.abortController.signal,
|
|
9162
|
-
entry.embeddingAdapterName
|
|
9163
|
-
);
|
|
9164
|
-
await core.pluginRegistry.runHook("beforeSearch", { indexName, params: resolvedParams });
|
|
9165
|
-
const workerSearch = core.orchestrator.isPromoted() ? core.orchestrator.searchViaWorker.bind(core.orchestrator) : void 0;
|
|
9166
|
-
const result = await executeQuery(resolvedParams, {
|
|
9167
|
-
manager,
|
|
9168
|
-
language: entry.language,
|
|
9169
|
-
config: entry.config,
|
|
9170
|
-
workerSearch,
|
|
9171
|
-
indexName,
|
|
9172
|
-
broadcastStats: broadcastStatsFor(core, options),
|
|
9173
|
-
partitionIds: options?.partitionIds,
|
|
9174
|
-
cursorBinding: queryBindingOf(params)
|
|
9175
|
-
});
|
|
9904
|
+
const release = await core.indexState.acquire(indexName);
|
|
9176
9905
|
try {
|
|
9177
|
-
|
|
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,
|
|
9178
9921
|
indexName,
|
|
9179
|
-
|
|
9180
|
-
|
|
9922
|
+
broadcastStats: broadcastStatsFor(core, options),
|
|
9923
|
+
partitionIds: options?.partitionIds,
|
|
9924
|
+
cursorBinding: queryBindingOf(params)
|
|
9181
9925
|
});
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
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();
|
|
9187
9941
|
}
|
|
9188
|
-
return result;
|
|
9189
9942
|
}
|
|
9190
9943
|
async function runEnginePreflight(core, indexName, params, options) {
|
|
9191
9944
|
core.guardShutdown();
|
|
9192
|
-
const
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
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();
|
|
9213
9972
|
}
|
|
9214
|
-
return result;
|
|
9215
9973
|
}
|
|
9216
9974
|
async function runEngineSuggest(core, indexName, params, partitionIds) {
|
|
9217
9975
|
core.guardShutdown();
|
|
9218
|
-
const
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
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();
|
|
9226
9990
|
}
|
|
9227
|
-
return result;
|
|
9228
9991
|
}
|
|
9229
|
-
function runEngineQueryStats(core, indexName, terms, partitionIds) {
|
|
9992
|
+
async function runEngineQueryStats(core, indexName, terms, partitionIds) {
|
|
9230
9993
|
core.guardShutdown();
|
|
9231
|
-
const
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
manager
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
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
|
+
}
|
|
9248
10016
|
}
|
|
9249
10017
|
async function runEngineListDocuments(core, indexName, params, partitionIds) {
|
|
9250
10018
|
core.guardShutdown();
|
|
9251
|
-
const
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
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
|
+
}
|
|
9257
10030
|
}
|
|
9258
10031
|
|
|
9259
10032
|
// src/narsil/operations.ts
|
|
@@ -9273,6 +10046,16 @@ function createNarsilFromCore(core, config) {
|
|
|
9273
10046
|
mutationCtx,
|
|
9274
10047
|
rebalanceCtx
|
|
9275
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
|
+
}
|
|
9276
10059
|
const narsil = {
|
|
9277
10060
|
createIndex(name, indexConfig) {
|
|
9278
10061
|
return createEngineIndex(core, config, name, indexConfig);
|
|
@@ -9283,15 +10066,27 @@ function createNarsilFromCore(core, config) {
|
|
|
9283
10066
|
dropIndex(name) {
|
|
9284
10067
|
return dropEngineIndex(core, name);
|
|
9285
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
|
+
},
|
|
9286
10079
|
listIndexes() {
|
|
9287
10080
|
const infos = [];
|
|
9288
10081
|
for (const [name, entry] of indexRegistry) {
|
|
9289
10082
|
const manager = executor.getManager(name);
|
|
9290
10083
|
infos.push({
|
|
9291
10084
|
name,
|
|
9292
|
-
documentCount: manager?.countDocuments() ??
|
|
9293
|
-
partitionCount: manager?.partitionCount ??
|
|
10085
|
+
documentCount: manager?.countDocuments() ?? entry.documentCount,
|
|
10086
|
+
partitionCount: manager?.partitionCount ?? entry.partitionCount,
|
|
9294
10087
|
language: entry.language.name,
|
|
10088
|
+
state: core.indexState.stateOf(name),
|
|
10089
|
+
reopenCount: core.indexState.reopenCount(name),
|
|
9295
10090
|
...core.analysisRebuild.isStale(name) ? { analysisStale: true } : {}
|
|
9296
10091
|
});
|
|
9297
10092
|
}
|
|
@@ -9302,8 +10097,8 @@ function createNarsilFromCore(core, config) {
|
|
|
9302
10097
|
const entry = requireIndex(indexName);
|
|
9303
10098
|
const manager = executor.getManager(indexName);
|
|
9304
10099
|
return {
|
|
9305
|
-
documentCount: manager?.countDocuments() ??
|
|
9306
|
-
partitionCount: manager?.partitionCount ??
|
|
10100
|
+
documentCount: manager?.countDocuments() ?? entry.documentCount,
|
|
10101
|
+
partitionCount: manager?.partitionCount ?? entry.partitionCount,
|
|
9307
10102
|
estimatedMemoryBytes: manager?.estimateMemoryBytes() ?? 0,
|
|
9308
10103
|
language: entry.language.name,
|
|
9309
10104
|
schema: entry.config.schema
|
|
@@ -9315,47 +10110,48 @@ function createNarsilFromCore(core, config) {
|
|
|
9315
10110
|
return executor.getManager(indexName)?.getPartitionStats() ?? [];
|
|
9316
10111
|
},
|
|
9317
10112
|
insert(indexName, document, docId, options) {
|
|
9318
|
-
return insertDocument(mutationCtx, indexName, document, docId, options);
|
|
10113
|
+
return withOpenIndex(indexName, () => insertDocument(mutationCtx, indexName, document, docId, options));
|
|
9319
10114
|
},
|
|
9320
10115
|
insertBatch(indexName, documents, options) {
|
|
9321
|
-
return insertDocumentBatch(mutationCtx, indexName, documents, options);
|
|
10116
|
+
return withOpenIndex(indexName, () => insertDocumentBatch(mutationCtx, indexName, documents, options));
|
|
9322
10117
|
},
|
|
9323
10118
|
remove(indexName, docId) {
|
|
9324
|
-
return removeDocument(mutationCtx, indexName, docId);
|
|
10119
|
+
return withOpenIndex(indexName, () => removeDocument(mutationCtx, indexName, docId));
|
|
9325
10120
|
},
|
|
9326
10121
|
removeBatch(indexName, docIds) {
|
|
9327
|
-
return removeDocumentBatch(mutationCtx, indexName, docIds);
|
|
10122
|
+
return withOpenIndex(indexName, () => removeDocumentBatch(mutationCtx, indexName, docIds));
|
|
9328
10123
|
},
|
|
9329
10124
|
update(indexName, docId, document) {
|
|
9330
|
-
return updateDocument(mutationCtx, indexName, docId, document);
|
|
10125
|
+
return withOpenIndex(indexName, () => updateDocument(mutationCtx, indexName, docId, document));
|
|
9331
10126
|
},
|
|
9332
10127
|
updateBatch(indexName, updates) {
|
|
9333
|
-
return updateDocumentBatch(mutationCtx, indexName, updates);
|
|
10128
|
+
return withOpenIndex(indexName, () => updateDocumentBatch(mutationCtx, indexName, updates));
|
|
9334
10129
|
},
|
|
9335
10130
|
async get(indexName, docId) {
|
|
9336
|
-
|
|
9337
|
-
requireIndex(indexName);
|
|
9338
|
-
return executor.execute({ type: "get", indexName, docId, requestId: docId });
|
|
10131
|
+
return withOpenIndex(indexName, () => executor.execute({ type: "get", indexName, docId, requestId: docId }));
|
|
9339
10132
|
},
|
|
9340
10133
|
async getMultiple(indexName, docIds) {
|
|
9341
10134
|
guardShutdown();
|
|
9342
10135
|
requireIndex(indexName);
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
const
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
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
|
+
});
|
|
9349
10149
|
},
|
|
9350
10150
|
async has(indexName, docId) {
|
|
9351
|
-
|
|
9352
|
-
requireIndex(indexName);
|
|
9353
|
-
return executor.execute({ type: "has", indexName, docId, requestId: docId });
|
|
10151
|
+
return withOpenIndex(indexName, () => executor.execute({ type: "has", indexName, docId, requestId: docId }));
|
|
9354
10152
|
},
|
|
9355
10153
|
async countDocuments(indexName) {
|
|
9356
|
-
|
|
9357
|
-
requireIndex(indexName);
|
|
9358
|
-
return executor.execute({ type: "count", indexName, requestId: indexName });
|
|
10154
|
+
return withOpenIndex(indexName, () => executor.execute({ type: "count", indexName, requestId: indexName }));
|
|
9359
10155
|
},
|
|
9360
10156
|
async listDocuments(indexName, params) {
|
|
9361
10157
|
return runEngineListDocuments(core, indexName, params ?? {});
|
|
@@ -9372,15 +10168,15 @@ function createNarsilFromCore(core, config) {
|
|
|
9372
10168
|
async rebuildAnalysis(indexName) {
|
|
9373
10169
|
guardShutdown();
|
|
9374
10170
|
requireIndex(indexName);
|
|
9375
|
-
await core.analysisRebuild.rebuild(indexName);
|
|
10171
|
+
await withOpenIndex(indexName, () => core.analysisRebuild.rebuild(indexName));
|
|
9376
10172
|
},
|
|
9377
10173
|
async snapshot(indexName) {
|
|
9378
10174
|
guardShutdown();
|
|
9379
|
-
return createSnapshot(requireManager(indexName), requireIndex(indexName));
|
|
10175
|
+
return withOpenIndex(indexName, () => createSnapshot(requireManager(indexName), requireIndex(indexName)));
|
|
9380
10176
|
},
|
|
9381
10177
|
async restore(indexName, data) {
|
|
9382
10178
|
guardShutdown();
|
|
9383
|
-
|
|
10179
|
+
await restoreFromSnapshot(indexName, data, {
|
|
9384
10180
|
executor,
|
|
9385
10181
|
indexRegistry,
|
|
9386
10182
|
getVectorFieldPaths,
|
|
@@ -9390,66 +10186,74 @@ function createNarsilFromCore(core, config) {
|
|
|
9390
10186
|
embeddingAdapters: core.embeddingAdapters,
|
|
9391
10187
|
defaultEmbeddingAdapter: config?.embedding ?? null,
|
|
9392
10188
|
markAnalysisStale: core.analysisRebuild.markStale,
|
|
9393
|
-
clearAnalysisStale: core.analysisRebuild.clearStale
|
|
10189
|
+
clearAnalysisStale: core.analysisRebuild.clearStale,
|
|
10190
|
+
indexState: core.indexState
|
|
9394
10191
|
});
|
|
9395
10192
|
},
|
|
9396
10193
|
async checkpoint(indexName) {
|
|
9397
10194
|
guardShutdown();
|
|
9398
10195
|
requireIndex(indexName);
|
|
9399
|
-
|
|
9400
|
-
await durability.manager.checkpoint(indexName);
|
|
9401
|
-
}
|
|
10196
|
+
await withOpenIndex(indexName, async () => {
|
|
10197
|
+
if (durability) await durability.manager.checkpoint(indexName);
|
|
10198
|
+
});
|
|
9402
10199
|
},
|
|
9403
10200
|
async clear(indexName) {
|
|
9404
10201
|
guardShutdown();
|
|
9405
10202
|
requireIndex(indexName);
|
|
9406
|
-
await
|
|
9407
|
-
|
|
9408
|
-
|
|
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
|
+
});
|
|
9409
10208
|
},
|
|
9410
10209
|
async rebalance(indexName, targetPartitionCount) {
|
|
9411
10210
|
guardShutdown();
|
|
9412
10211
|
requireIndex(indexName);
|
|
9413
|
-
return
|
|
10212
|
+
return withOpenIndex(
|
|
10213
|
+
indexName,
|
|
10214
|
+
() => executeRebalance(requireManager(indexName), indexName, targetPartitionCount, rebalanceCtx)
|
|
10215
|
+
);
|
|
9414
10216
|
},
|
|
9415
10217
|
async updatePartitionConfig(indexName, partitionConfig) {
|
|
9416
10218
|
guardShutdown();
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
9429
|
-
`maxPartitions (${partitionConfig.maxPartitions}) is less than the current partition count (${manager.partitionCount})`,
|
|
9430
|
-
{ maxPartitions: partitionConfig.maxPartitions, partitionCount: manager.partitionCount }
|
|
9431
|
-
);
|
|
9432
|
-
}
|
|
9433
|
-
const currentDocCount = manager.countDocuments();
|
|
9434
|
-
const newMaxDocs = partitionConfig.maxDocsPerPartition ?? entry.config.partitions?.maxDocsPerPartition;
|
|
9435
|
-
if (newMaxDocs !== void 0) {
|
|
9436
|
-
const newTotalCapacity = newMaxDocs * manager.partitionCount;
|
|
9437
|
-
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) {
|
|
9438
10230
|
throw new NarsilError(
|
|
9439
10231
|
ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
|
|
9440
|
-
`
|
|
9441
|
-
{
|
|
10232
|
+
`maxPartitions (${partitionConfig.maxPartitions}) is less than the current partition count (${manager.partitionCount})`,
|
|
10233
|
+
{ maxPartitions: partitionConfig.maxPartitions, partitionCount: manager.partitionCount }
|
|
9442
10234
|
);
|
|
9443
10235
|
}
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
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
|
+
});
|
|
9453
10257
|
},
|
|
9454
10258
|
async getMemoryStats() {
|
|
9455
10259
|
let estimatedIndexBytes = 0;
|
|
@@ -9457,22 +10261,33 @@ function createNarsilFromCore(core, config) {
|
|
|
9457
10261
|
estimatedIndexBytes += executor.getManager(name)?.estimateMemoryBytes() ?? 0;
|
|
9458
10262
|
}
|
|
9459
10263
|
const workers = await orchestrator.getWorkerMemoryStats();
|
|
9460
|
-
|
|
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
|
+
};
|
|
9461
10273
|
},
|
|
9462
10274
|
async compactVectors(indexName, fieldName) {
|
|
9463
10275
|
guardShutdown();
|
|
9464
10276
|
requireIndex(indexName);
|
|
9465
|
-
|
|
10277
|
+
await withOpenIndex(indexName, async () => {
|
|
10278
|
+
compactVectors(requireManager(indexName), indexName, fieldName);
|
|
10279
|
+
});
|
|
9466
10280
|
},
|
|
9467
10281
|
async optimizeVectors(indexName, fieldName) {
|
|
9468
10282
|
guardShutdown();
|
|
9469
10283
|
requireIndex(indexName);
|
|
9470
|
-
await optimizeVectors(requireManager(indexName), indexName, fieldName);
|
|
10284
|
+
await withOpenIndex(indexName, () => optimizeVectors(requireManager(indexName), indexName, fieldName));
|
|
9471
10285
|
},
|
|
9472
10286
|
vectorMaintenanceStatus(indexName) {
|
|
9473
10287
|
guardShutdown();
|
|
9474
10288
|
requireIndex(indexName);
|
|
9475
|
-
|
|
10289
|
+
const manager = executor.getManager(indexName);
|
|
10290
|
+
return manager === void 0 ? [] : getVectorMaintenanceStatus(manager);
|
|
9476
10291
|
},
|
|
9477
10292
|
on(event, handler) {
|
|
9478
10293
|
const key = event;
|
|
@@ -9505,12 +10320,12 @@ function createNarsilFromCore(core, config) {
|
|
|
9505
10320
|
async function createNarsil(config) {
|
|
9506
10321
|
const core = createEngineCore(config);
|
|
9507
10322
|
if (core.durability) {
|
|
9508
|
-
await core.durability.manager.recover();
|
|
10323
|
+
await core.durability.manager.recover(config?.lifecycle !== void 0);
|
|
9509
10324
|
}
|
|
9510
10325
|
if (core.invalidation) {
|
|
9511
10326
|
await core.invalidation.start();
|
|
9512
10327
|
}
|
|
9513
|
-
await core.analysisRebuild.reviewStaleIndexes();
|
|
10328
|
+
if (config?.lifecycle === void 0) await core.analysisRebuild.reviewStaleIndexes();
|
|
9514
10329
|
return createNarsilFromCore(core, config);
|
|
9515
10330
|
}
|
|
9516
10331
|
|