@delali/narsil 0.2.4-next.48 → 0.2.4-next.49
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/README.md +2 -2
- package/dist/adapters/indexeddb.mjs +15 -10
- package/dist/{chunk-BFIH7OV5.mjs → chunk-2GWVQWGA.mjs} +3 -1
- package/dist/{chunk-UZTT2BKQ.mjs → chunk-56PJBIJ6.mjs} +891 -542
- package/dist/chunk-CBLS46IE.mjs +10 -0
- package/dist/{chunk-JQUL2T6I.mjs → chunk-GCM3JFVA.mjs} +1 -2
- package/dist/{chunk-4COAUBLJ.mjs → chunk-HGJC5L65.mjs} +4 -7
- package/dist/{chunk-DFZDV5FV.mjs → chunk-JXHUHFJ5.mjs} +27 -14
- package/dist/{chunk-XWICBGYT.mjs → chunk-KGRYQVZ5.mjs} +2 -5
- package/dist/{chunk-EV45L35L.mjs → chunk-M6JGMKA5.mjs} +391 -183
- package/dist/{chunk-PM52SMBT.mjs → chunk-OOSIDHA3.mjs} +2 -1
- package/dist/chunk-ORBLD3EL.mjs +6 -0
- package/dist/chunk-OWCFK42Q.mjs +30 -0
- package/dist/{chunk-WTONLDU3.mjs → chunk-RDGXYRF4.mjs} +1 -0
- package/dist/chunk-UNEEFTEU.mjs +7 -0
- package/dist/chunk-YEO6G2NG.mjs +6 -0
- package/dist/{client-C5BkUqB8.d.ts → client-DX7xPcNA.d.ts} +1 -1
- package/dist/client.d.ts +5 -5
- package/dist/client.mjs +224 -222
- package/dist/{config-B-ZI-ft5.d.ts → config-B41W23Us.d.ts} +1 -1
- package/dist/{config-BZRExglL.d.ts → config-Co-zAXqO.d.ts} +27 -11
- package/dist/direct-executor-HRTDWMVB.mjs +9 -0
- package/dist/distribution/coordinator/etcd.mjs +2 -3
- package/dist/distribution/coordinator/in-memory.mjs +2 -2
- package/dist/distribution/transport/grpc.mjs +7 -9
- package/dist/distribution/transport/in-memory.mjs +2 -1
- package/dist/distribution/transport/tcp.mjs +3 -2
- package/dist/distribution.d.ts +5 -5
- package/dist/distribution.mjs +121 -97
- package/dist/embeddings/openai.mjs +5 -3
- package/dist/{engine-Cjr1uBLa.d.ts → engine-CABZg25p.d.ts} +12 -8
- package/dist/index.browser.mjs +1514 -894
- package/dist/index.d.ts +5 -5
- package/dist/index.mjs +9 -8
- package/dist/invalidation/broadcast-channel.mjs +2 -1
- package/dist/invalidation/filesystem.mjs +3 -4
- package/dist/{options-BC68VPoE.d.ts → options-Dtk99qUs.d.ts} +1 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +9 -8
- package/dist/react.d.ts +3 -3
- package/dist/react.mjs +7 -5
- package/dist/{search-DDHjfXhJ.d.ts → search-FjWhZN3f.d.ts} +20 -3
- package/dist/server.d.ts +6 -6
- package/dist/server.mjs +43 -30
- package/dist/vector/hnsw-build-worker.mjs +2 -4
- package/dist/vector/search-worker.mjs +3 -4
- package/dist/workers/entry.mjs +2 -2
- package/package.json +1 -1
- package/dist/direct-executor-EOTFGXTO.mjs +0 -8
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { ReplicationMessageTypes } from './chunk-
|
|
2
|
-
import { MAX_INDEX_NAME_LENGTH } from './chunk-
|
|
3
|
-
import { getNestedValue, getLanguage, extractVectorFieldsFromSchema,
|
|
1
|
+
import { ReplicationMessageTypes } from './chunk-GCM3JFVA.mjs';
|
|
2
|
+
import { WIRE_BATCH_BUDGET, DEFAULT_LIMIT, MAX_INDEX_NAME_LENGTH, INDEX_NAME_PATTERN, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, MAX_SUGGEST_SCATTER_LIMIT, STATISTICS_BROADCAST_INTERVAL_MS, MAX_SWEEP_INTERVAL_MS, DEFAULT_OFFSET, MIN_SWEEP_INTERVAL_MS, FOREIGN_STATS_TTL_MS, MIN_DOCUMENTS_FOR_SEGMENTS, BATCH_CHUNK_SIZE, MAX_DOC_ID_LENGTH } from './chunk-OWCFK42Q.mjs';
|
|
3
|
+
import { getNestedValue, getLanguage, extractVectorFieldsFromSchema, clampRowCount, splitWorkerBudget, resolveWorkerCount, createDirectExecutor, createPartitionRouter, validateSchema, validateVectorPromotion, collectQueryTermStats, createPartitionManager, createVectorIndex, detectRuntime, 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, partitionsIn, isNodeMainThread, spawnNodeWorker, CHECKSUM_TIMEOUT_MS, CHECKSUM_YIELD_CHUNK_BYTES, createSharedFrozenSegment, buildCompactedSegmentPayload, freezeSegmentShared, createFrozenSegment, isCompositePartition, deserializeMetadata, serializeMetadata, createWorkerPool, WORKER_RESOURCE_LIMITS, DEFAULT_BACKPRESSURE_LIMIT, DEFAULT_REQUEST_TIMEOUT_MS, SHUTDOWN_TIMEOUT_MS, CONSECUTIVE_TIMEOUTS_BEFORE_DEATH, encodeSegmentState, validateRequiredFields, kWayMerge, validateDocument, validateDocumentStrict } from './chunk-M6JGMKA5.mjs';
|
|
4
4
|
import { crc32, crc32Init, crc32Update, crc32Final } from './chunk-KMMJMDUA.mjs';
|
|
5
|
-
import { compareCodePoints, compareComparableKeys } from './chunk-
|
|
6
|
-
import { generateId, createRequestId, isValidWorkerResponse } from './chunk-
|
|
5
|
+
import { compareCodePoints, compareComparableKeys } from './chunk-2GWVQWGA.mjs';
|
|
6
|
+
import { generateId, createRequestId, isValidWorkerResponse } from './chunk-RDGXYRF4.mjs';
|
|
7
7
|
import { ErrorCodes, NarsilError } from './chunk-P4ELXVJQ.mjs';
|
|
8
8
|
import { encode, decode } from '@msgpack/msgpack';
|
|
9
9
|
|
|
10
|
+
// src/partitioning/constants.ts
|
|
11
|
+
var REBALANCE_CHUNK_SIZE = 1e3;
|
|
12
|
+
var DEFAULT_WRITE_AHEAD_QUEUE_MAX_SIZE = 1e4;
|
|
13
|
+
|
|
10
14
|
// src/partitioning/rebalancer.ts
|
|
11
|
-
var CHUNK_SIZE = 1e3;
|
|
12
15
|
function createRebalancer() {
|
|
13
16
|
const activeManagers = /* @__PURE__ */ new WeakSet();
|
|
14
17
|
let activeCount = 0;
|
|
@@ -71,7 +74,7 @@ function createRebalancer() {
|
|
|
71
74
|
documentsProcessed++;
|
|
72
75
|
}
|
|
73
76
|
chunkFill++;
|
|
74
|
-
if (chunkFill >=
|
|
77
|
+
if (chunkFill >= REBALANCE_CHUNK_SIZE) {
|
|
75
78
|
chunkFill = 0;
|
|
76
79
|
onProgress?.({
|
|
77
80
|
phase: "moving",
|
|
@@ -269,44 +272,6 @@ function validateRequiredFieldsInSchema(required, schema) {
|
|
|
269
272
|
}
|
|
270
273
|
}
|
|
271
274
|
|
|
272
|
-
// src/workers/promoter.ts
|
|
273
|
-
var DEFAULT_PER_INDEX_THRESHOLD = 1e4;
|
|
274
|
-
var DEFAULT_TOTAL_THRESHOLD = 5e4;
|
|
275
|
-
function createExecutionPromoter(config) {
|
|
276
|
-
const perIndexThreshold = config?.perIndexThreshold ?? DEFAULT_PER_INDEX_THRESHOLD;
|
|
277
|
-
const totalThreshold = config?.totalThreshold ?? DEFAULT_TOTAL_THRESHOLD;
|
|
278
|
-
let promoted = false;
|
|
279
|
-
function check(indexes) {
|
|
280
|
-
if (promoted) {
|
|
281
|
-
return { shouldPromote: false, reason: "" };
|
|
282
|
-
}
|
|
283
|
-
let totalDocuments = 0;
|
|
284
|
-
for (const [indexName, stats] of indexes) {
|
|
285
|
-
if (stats.documentCount >= perIndexThreshold) {
|
|
286
|
-
return {
|
|
287
|
-
shouldPromote: true,
|
|
288
|
-
reason: `Index "${indexName}" has ${stats.documentCount} documents, exceeding the per-index threshold of ${perIndexThreshold}`
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
totalDocuments += stats.documentCount;
|
|
292
|
-
}
|
|
293
|
-
if (totalDocuments >= totalThreshold) {
|
|
294
|
-
return {
|
|
295
|
-
shouldPromote: true,
|
|
296
|
-
reason: `Total document count of ${totalDocuments} exceeds the total threshold of ${totalThreshold}`
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
return { shouldPromote: false, reason: "" };
|
|
300
|
-
}
|
|
301
|
-
function markPromoted() {
|
|
302
|
-
promoted = true;
|
|
303
|
-
}
|
|
304
|
-
function isPromoted() {
|
|
305
|
-
return promoted;
|
|
306
|
-
}
|
|
307
|
-
return { check, markPromoted, isPromoted };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
275
|
// src/engine/analysis-rebuild.ts
|
|
311
276
|
function yieldToEventLoop() {
|
|
312
277
|
return new Promise((resolve) => {
|
|
@@ -383,7 +348,7 @@ function createAnalysisRebuildCoordinator(config, deps) {
|
|
|
383
348
|
throw error;
|
|
384
349
|
}
|
|
385
350
|
}
|
|
386
|
-
function
|
|
351
|
+
function enqueue(indexName, entry) {
|
|
387
352
|
if (entry.run !== null) {
|
|
388
353
|
return entry.run;
|
|
389
354
|
}
|
|
@@ -397,7 +362,7 @@ function createAnalysisRebuildCoordinator(config, deps) {
|
|
|
397
362
|
}
|
|
398
363
|
async function announce(indexName, entry) {
|
|
399
364
|
const documentCount = deps.getManager(indexName)?.countDocuments() ?? 0;
|
|
400
|
-
await config?.onStaleAnalysis?.({ ...entry.index, documentCount }, () =>
|
|
365
|
+
await config?.onStaleAnalysis?.({ ...entry.index, documentCount }, () => enqueue(indexName, entry));
|
|
401
366
|
}
|
|
402
367
|
async function review(indexName, entry) {
|
|
403
368
|
if (entry.run !== null || deps.getManager(indexName) === void 0) return;
|
|
@@ -413,7 +378,7 @@ function createAnalysisRebuildCoordinator(config, deps) {
|
|
|
413
378
|
});
|
|
414
379
|
}
|
|
415
380
|
if (rebuildMode === "auto") {
|
|
416
|
-
void
|
|
381
|
+
void enqueue(indexName, entry).catch(() => void 0);
|
|
417
382
|
}
|
|
418
383
|
}
|
|
419
384
|
return {
|
|
@@ -447,7 +412,7 @@ function createAnalysisRebuildCoordinator(config, deps) {
|
|
|
447
412
|
if (entry === void 0) {
|
|
448
413
|
return Promise.resolve();
|
|
449
414
|
}
|
|
450
|
-
return
|
|
415
|
+
return enqueue(indexName, entry);
|
|
451
416
|
}
|
|
452
417
|
};
|
|
453
418
|
}
|
|
@@ -532,6 +497,15 @@ function resolveDurabilityTier(config) {
|
|
|
532
497
|
return { kind: "snapshot", config: {}, adapter: config.persistence };
|
|
533
498
|
}
|
|
534
499
|
|
|
500
|
+
// src/persistence/durability/constants.ts
|
|
501
|
+
var DEFAULT_CHECKPOINT_INTERVAL_MS = 3e5;
|
|
502
|
+
var DEFAULT_CHECKPOINT_MUTATION_THRESHOLD = 1e5;
|
|
503
|
+
var DEFAULT_ASYNC_FLUSH_INTERVAL_MS = 1e3;
|
|
504
|
+
var DEFAULT_SEGMENT_MAX_BYTES = 67108864;
|
|
505
|
+
var DEFAULT_COMPACTION_THRESHOLD = 12;
|
|
506
|
+
var CHECKPOINT_WORKER_TIMEOUT_MS = 6e5;
|
|
507
|
+
var CHECKPOINT_TIMEOUT_RECOVERY_BACKOFF_MS = 50;
|
|
508
|
+
|
|
535
509
|
// src/persistence/durability/node-fs.ts
|
|
536
510
|
var cachedFs = null;
|
|
537
511
|
var cachedPath = null;
|
|
@@ -809,8 +783,6 @@ function buildEntry(fields) {
|
|
|
809
783
|
}
|
|
810
784
|
|
|
811
785
|
// src/serialization/checksum-dispatch.ts
|
|
812
|
-
var CHECKSUM_TIMEOUT_MS = 12e4;
|
|
813
|
-
var YIELD_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
814
786
|
var workerUsable = true;
|
|
815
787
|
var mainThreadResolved = false;
|
|
816
788
|
var onMainThread = true;
|
|
@@ -844,8 +816,8 @@ function yieldToEventLoop2() {
|
|
|
844
816
|
}
|
|
845
817
|
async function chunkedChecksum(payload) {
|
|
846
818
|
let state = crc32Init();
|
|
847
|
-
for (let offset = 0; offset < payload.length; offset +=
|
|
848
|
-
const end = Math.min(offset +
|
|
819
|
+
for (let offset = 0; offset < payload.length; offset += CHECKSUM_YIELD_CHUNK_BYTES) {
|
|
820
|
+
const end = Math.min(offset + CHECKSUM_YIELD_CHUNK_BYTES, payload.length);
|
|
849
821
|
state = crc32Update(state, payload.subarray(offset, end));
|
|
850
822
|
if (end < payload.length) {
|
|
851
823
|
await yieldToEventLoop2();
|
|
@@ -1437,8 +1409,6 @@ function formatSegmentStart(startSeqNo) {
|
|
|
1437
1409
|
}
|
|
1438
1410
|
|
|
1439
1411
|
// src/persistence/durability/checkpoint-worker-dispatch.ts
|
|
1440
|
-
var CHECKPOINT_WORKER_TIMEOUT_MS = 6e5;
|
|
1441
|
-
var TIMEOUT_RECOVERY_BACKOFF_MS = 50;
|
|
1442
1412
|
var workerUsable2 = true;
|
|
1443
1413
|
var pooledWorker = null;
|
|
1444
1414
|
var workerBusy = false;
|
|
@@ -1551,7 +1521,7 @@ async function runCheckpointOnWorker(request) {
|
|
|
1551
1521
|
return true;
|
|
1552
1522
|
}
|
|
1553
1523
|
if (outcome.timedOut) {
|
|
1554
|
-
await delay(
|
|
1524
|
+
await delay(CHECKPOINT_TIMEOUT_RECOVERY_BACKOFF_MS);
|
|
1555
1525
|
}
|
|
1556
1526
|
return false;
|
|
1557
1527
|
} finally {
|
|
@@ -1571,7 +1541,6 @@ function terminateCheckpointWorker() {
|
|
|
1571
1541
|
}
|
|
1572
1542
|
|
|
1573
1543
|
// src/persistence/durability/segment/layout.ts
|
|
1574
|
-
var DEFAULT_COMPACTION_THRESHOLD = 12;
|
|
1575
1544
|
var SEGMENT_ID_WIDTH = 16;
|
|
1576
1545
|
function manifestKey(indexName) {
|
|
1577
1546
|
return `${indexName}/manifest`;
|
|
@@ -2513,37 +2482,6 @@ function setNestedValue(obj, path, value) {
|
|
|
2513
2482
|
}
|
|
2514
2483
|
current[finalSegment] = value;
|
|
2515
2484
|
}
|
|
2516
|
-
|
|
2517
|
-
// src/distribution/chunking.ts
|
|
2518
|
-
var WIRE_BATCH_BUDGET = {
|
|
2519
|
-
maxCount: 1e3,
|
|
2520
|
-
maxBytes: 8388608,
|
|
2521
|
-
overheadBytes: 256
|
|
2522
|
-
};
|
|
2523
|
-
function chunkByBudget(items, budget) {
|
|
2524
|
-
const chunks = [];
|
|
2525
|
-
let current = [];
|
|
2526
|
-
let currentBytes = 0;
|
|
2527
|
-
for (const item of items) {
|
|
2528
|
-
const itemBytes = budget.payloadBytesOf(item) + budget.overheadBytes;
|
|
2529
|
-
const previous = current[current.length - 1];
|
|
2530
|
-
const breaksRun = previous !== void 0 && budget.breaksRun?.(item, previous) === true;
|
|
2531
|
-
const overflows = current.length >= budget.maxCount || current.length > 0 && currentBytes + itemBytes > budget.maxBytes;
|
|
2532
|
-
if (breaksRun || overflows) {
|
|
2533
|
-
chunks.push(current);
|
|
2534
|
-
current = [];
|
|
2535
|
-
currentBytes = 0;
|
|
2536
|
-
}
|
|
2537
|
-
current.push(item);
|
|
2538
|
-
currentBytes += itemBytes;
|
|
2539
|
-
}
|
|
2540
|
-
if (current.length > 0) {
|
|
2541
|
-
chunks.push(current);
|
|
2542
|
-
}
|
|
2543
|
-
return chunks;
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
// src/distribution/replication/codec.ts
|
|
2547
2485
|
function createEntryMessage(entry, sourceId) {
|
|
2548
2486
|
const payload = { entry };
|
|
2549
2487
|
return {
|
|
@@ -3266,7 +3204,10 @@ async function writePartitionVectors(input) {
|
|
|
3266
3204
|
}
|
|
3267
3205
|
const vectorIndexes = /* @__PURE__ */ new Map();
|
|
3268
3206
|
for (const [fieldPath, dimension] of input.vectorFields) {
|
|
3269
|
-
vectorIndexes.set(
|
|
3207
|
+
vectorIndexes.set(
|
|
3208
|
+
fieldPath,
|
|
3209
|
+
createVectorIndex(fieldPath, dimension, input.config.vectorPromotion, { enabled: false })
|
|
3210
|
+
);
|
|
3270
3211
|
}
|
|
3271
3212
|
for (const ref of input.priorVectors) {
|
|
3272
3213
|
const vecIndex = vectorIndexes.get(ref.fieldPath);
|
|
@@ -3684,11 +3625,6 @@ async function recoverPersistedIndex(deps, indexName, metadataOnly) {
|
|
|
3684
3625
|
}
|
|
3685
3626
|
}
|
|
3686
3627
|
|
|
3687
|
-
// src/persistence/durability/types.ts
|
|
3688
|
-
var DEFAULT_CHECKPOINT_INTERVAL_MS = 3e5;
|
|
3689
|
-
var DEFAULT_CHECKPOINT_MUTATION_THRESHOLD = 1e5;
|
|
3690
|
-
var DEFAULT_ASYNC_FLUSH_INTERVAL_MS = 1e3;
|
|
3691
|
-
|
|
3692
3628
|
// src/persistence/durability/group-commit.ts
|
|
3693
3629
|
function createGroupCommitCoordinator(sync) {
|
|
3694
3630
|
let inFlight = null;
|
|
@@ -3762,7 +3698,6 @@ async function createMarkerWriter(directory, indexName, partitionId) {
|
|
|
3762
3698
|
}
|
|
3763
3699
|
|
|
3764
3700
|
// src/persistence/durability/wal-writer.ts
|
|
3765
|
-
var DEFAULT_SEGMENT_MAX_BYTES = 67108864;
|
|
3766
3701
|
var SEGMENT_TAIL_PATTERN = /^\d{16}$/;
|
|
3767
3702
|
function segmentKey2(indexName, partitionId, startSeqNo) {
|
|
3768
3703
|
const padded = startSeqNo.toString().padStart(16, "0");
|
|
@@ -4680,11 +4615,11 @@ function createDurabilityFromTier(tier, wiring) {
|
|
|
4680
4615
|
});
|
|
4681
4616
|
}
|
|
4682
4617
|
|
|
4683
|
-
// src/engine/index-state/
|
|
4618
|
+
// src/engine/index-state/constants.ts
|
|
4684
4619
|
var DEFAULT_MAX_REOPEN_WAITERS = 64;
|
|
4685
4620
|
var MAX_REOPEN_FAILURES = 5;
|
|
4686
|
-
|
|
4687
|
-
|
|
4621
|
+
|
|
4622
|
+
// src/engine/index-state/entry.ts
|
|
4688
4623
|
function createLifecycleEntry(state) {
|
|
4689
4624
|
return {
|
|
4690
4625
|
state,
|
|
@@ -4958,7 +4893,10 @@ function createIndexStateCoordinator(config, callbacks) {
|
|
|
4958
4893
|
await reopen(indexName, false);
|
|
4959
4894
|
entry = entryOf(indexName);
|
|
4960
4895
|
}
|
|
4961
|
-
if (markActive)
|
|
4896
|
+
if (markActive) {
|
|
4897
|
+
entry.lastAccessAt = Date.now();
|
|
4898
|
+
callbacks.onAccess?.(indexName);
|
|
4899
|
+
}
|
|
4962
4900
|
entry.activeOperations += 1;
|
|
4963
4901
|
try {
|
|
4964
4902
|
if (opened) await enforceLimits(indexName);
|
|
@@ -5061,6 +4999,9 @@ function wireIndexState(wiring) {
|
|
|
5061
4999
|
}
|
|
5062
5000
|
}
|
|
5063
5001
|
},
|
|
5002
|
+
onAccess(indexName) {
|
|
5003
|
+
wiring.onAccess?.(indexName);
|
|
5004
|
+
},
|
|
5064
5005
|
canCloseAutomatically(indexName) {
|
|
5065
5006
|
return !rebalancingIndexes.has(indexName) && !analysisRebuild.isRunning(indexName) && !orchestrator.isIndexBusy(indexName);
|
|
5066
5007
|
},
|
|
@@ -5071,8 +5012,6 @@ function wireIndexState(wiring) {
|
|
|
5071
5012
|
}
|
|
5072
5013
|
|
|
5073
5014
|
// src/engine/invalidation.ts
|
|
5074
|
-
var STATISTICS_BROADCAST_INTERVAL_MS = 5e3;
|
|
5075
|
-
var FOREIGN_STATS_TTL_MS = 6e4;
|
|
5076
5015
|
function createInvalidationFromConfig(config, tierKind, deps) {
|
|
5077
5016
|
if (config?.invalidation === void 0) {
|
|
5078
5017
|
return null;
|
|
@@ -5226,17 +5165,99 @@ function createInvalidationIntegration(deps) {
|
|
|
5226
5165
|
};
|
|
5227
5166
|
}
|
|
5228
5167
|
|
|
5168
|
+
// src/core/partition/frozen/share.ts
|
|
5169
|
+
function shareFrozenSegment(segment) {
|
|
5170
|
+
if (segment.sharedSnapshot !== null) {
|
|
5171
|
+
return { snapshot: segment.sharedSnapshot, tombstonedDocIds: segment.tombstonedDocIds() };
|
|
5172
|
+
}
|
|
5173
|
+
const payload = encodeSegmentState(segment);
|
|
5174
|
+
const documents = payload.docIds.map((docId) => {
|
|
5175
|
+
const stored = segment.docStore.get(docId);
|
|
5176
|
+
if (stored === void 0) {
|
|
5177
|
+
throw new NarsilError(ErrorCodes.PARTITION_CORRUPTED, `Document "${docId}" vanished while sharing a segment`, {
|
|
5178
|
+
docId
|
|
5179
|
+
});
|
|
5180
|
+
}
|
|
5181
|
+
return stored.fields;
|
|
5182
|
+
});
|
|
5183
|
+
const snapshot = freezeSegmentShared(payload, documents, segment.segmentId);
|
|
5184
|
+
return snapshot === null ? null : { snapshot, tombstonedDocIds: [] };
|
|
5185
|
+
}
|
|
5186
|
+
|
|
5229
5187
|
// src/engine/worker-resync.ts
|
|
5188
|
+
function captureComposite(manager, partitionId) {
|
|
5189
|
+
const partition = manager.getPartition(partitionId);
|
|
5190
|
+
if (!isCompositePartition(partition)) return null;
|
|
5191
|
+
const segmentIds = partition.frozenSegmentSizes().map((size) => size.segmentId);
|
|
5192
|
+
const frozen = [];
|
|
5193
|
+
for (const segment of partition.frozenSegmentsById(segmentIds)) {
|
|
5194
|
+
const shared = shareFrozenSegment(segment);
|
|
5195
|
+
if (shared === null) return null;
|
|
5196
|
+
frozen.push(shared);
|
|
5197
|
+
}
|
|
5198
|
+
const live = partition.live.serialize(
|
|
5199
|
+
manager.indexName,
|
|
5200
|
+
manager.partitionCount,
|
|
5201
|
+
manager.language.name,
|
|
5202
|
+
manager.schema
|
|
5203
|
+
);
|
|
5204
|
+
return { partitionId, live, frozen };
|
|
5205
|
+
}
|
|
5206
|
+
function capturePartitions(manager) {
|
|
5207
|
+
const captured = [];
|
|
5208
|
+
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId++) {
|
|
5209
|
+
captured.push(
|
|
5210
|
+
captureComposite(manager, partitionId) ?? {
|
|
5211
|
+
partitionId,
|
|
5212
|
+
live: manager.serializePartition(partitionId),
|
|
5213
|
+
frozen: []
|
|
5214
|
+
}
|
|
5215
|
+
);
|
|
5216
|
+
}
|
|
5217
|
+
return captured;
|
|
5218
|
+
}
|
|
5219
|
+
async function sendPartition(indexName, executors, captured) {
|
|
5220
|
+
await Promise.all(
|
|
5221
|
+
executors.map(
|
|
5222
|
+
(workerExecutor) => workerExecutor.execute({
|
|
5223
|
+
type: "deserialize",
|
|
5224
|
+
indexName,
|
|
5225
|
+
partitionId: captured.partitionId,
|
|
5226
|
+
data: captured.live,
|
|
5227
|
+
requestId: `resync-${indexName}-${captured.partitionId}`
|
|
5228
|
+
})
|
|
5229
|
+
)
|
|
5230
|
+
);
|
|
5231
|
+
if (captured.frozen.length === 0) return;
|
|
5232
|
+
const segments = captured.frozen.map((shared) => ({
|
|
5233
|
+
partitionId: captured.partitionId,
|
|
5234
|
+
snapshot: shared.snapshot,
|
|
5235
|
+
tombstonedDocIds: shared.tombstonedDocIds
|
|
5236
|
+
}));
|
|
5237
|
+
await Promise.all(
|
|
5238
|
+
executors.map(
|
|
5239
|
+
(workerExecutor) => workerExecutor.execute({
|
|
5240
|
+
type: "attachSegments",
|
|
5241
|
+
indexName,
|
|
5242
|
+
segments,
|
|
5243
|
+
requestId: `resync-attach-${indexName}-${captured.partitionId}`
|
|
5244
|
+
})
|
|
5245
|
+
)
|
|
5246
|
+
);
|
|
5247
|
+
}
|
|
5230
5248
|
async function transferIndexToPool(indexName, pool, config, manager) {
|
|
5231
|
-
|
|
5249
|
+
await loadIndexOntoWorkers(indexName, pool.getAllExecutors(), config, manager, () => pool.addIndexToAll(indexName));
|
|
5250
|
+
}
|
|
5251
|
+
async function loadIndexOntoWorkers(indexName, executors, config, manager, onDropped) {
|
|
5252
|
+
const captured = capturePartitions(manager);
|
|
5232
5253
|
await Promise.allSettled(
|
|
5233
|
-
|
|
5254
|
+
executors.map(
|
|
5234
5255
|
(workerExecutor) => workerExecutor.execute({ type: "dropIndex", indexName, requestId: `resync-drop-${indexName}` })
|
|
5235
5256
|
)
|
|
5236
5257
|
);
|
|
5237
|
-
|
|
5258
|
+
onDropped?.();
|
|
5238
5259
|
await Promise.all(
|
|
5239
|
-
|
|
5260
|
+
executors.map(
|
|
5240
5261
|
(workerExecutor) => workerExecutor.execute({
|
|
5241
5262
|
type: "createIndex",
|
|
5242
5263
|
indexName,
|
|
@@ -5245,22 +5266,25 @@ async function transferIndexToPool(indexName, pool, config, manager) {
|
|
|
5245
5266
|
})
|
|
5246
5267
|
)
|
|
5247
5268
|
);
|
|
5248
|
-
for (
|
|
5249
|
-
|
|
5250
|
-
await Promise.all(
|
|
5251
|
-
allExecutors.map(
|
|
5252
|
-
(workerExecutor) => workerExecutor.execute({
|
|
5253
|
-
type: "deserialize",
|
|
5254
|
-
indexName,
|
|
5255
|
-
partitionId: i,
|
|
5256
|
-
data: serialized,
|
|
5257
|
-
requestId: `resync-${indexName}-${i}`
|
|
5258
|
-
})
|
|
5259
|
-
)
|
|
5260
|
-
);
|
|
5269
|
+
for (const partition of captured) {
|
|
5270
|
+
await sendPartition(indexName, executors, partition);
|
|
5261
5271
|
}
|
|
5262
5272
|
}
|
|
5263
5273
|
|
|
5274
|
+
// src/engine/orchestration/constants.ts
|
|
5275
|
+
var DEFAULT_COPY_THRESHOLD = 1e3;
|
|
5276
|
+
var DEFAULT_COPY_IDLE_TIMEOUT_MS = 3e5;
|
|
5277
|
+
var COMPACTION_SEGMENT_TRIGGER = 8;
|
|
5278
|
+
var IDLE_MERGE_DELAY_MS = 1e3;
|
|
5279
|
+
var LIVE_TAIL_FREEZE_FLOOR = MIN_DOCUMENTS_FOR_SEGMENTS;
|
|
5280
|
+
var LIVE_TAIL_FLUSH_DOCUMENTS = 5e3;
|
|
5281
|
+
var MAX_PENDING_REPLICATION_DOCUMENTS = 2e4;
|
|
5282
|
+
var REPLICATION_WINDOW = 32;
|
|
5283
|
+
var LOAD_BUFFER_YIELD_INTERVAL = 1e3;
|
|
5284
|
+
var MAIN_COPY_LONE_QUERY_DOCUMENTS = 5e4;
|
|
5285
|
+
var POOL_RESTART_DELAY_MS = 1e3;
|
|
5286
|
+
var POOL_RESTART_DELAY_MAX_MS = 6e4;
|
|
5287
|
+
|
|
5264
5288
|
// src/engine/orchestration/eligibility.ts
|
|
5265
5289
|
function toError3(value) {
|
|
5266
5290
|
return value instanceof Error ? value : new Error(String(value));
|
|
@@ -5309,7 +5333,7 @@ function workerIneligibility(indexName, config, bootstrapModule) {
|
|
|
5309
5333
|
function reportIneligible(state, indexName, error) {
|
|
5310
5334
|
if (state.reportedIneligible.has(indexName)) return;
|
|
5311
5335
|
state.reportedIneligible.add(indexName);
|
|
5312
|
-
state.callbacks?.
|
|
5336
|
+
state.callbacks?.onCopyLoadFailure?.("index-excluded", error, false);
|
|
5313
5337
|
}
|
|
5314
5338
|
function eligibleIndexNames(state) {
|
|
5315
5339
|
const names = [];
|
|
@@ -5323,33 +5347,26 @@ function eligibleIndexNames(state) {
|
|
|
5323
5347
|
}
|
|
5324
5348
|
return names;
|
|
5325
5349
|
}
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5350
|
+
|
|
5351
|
+
// src/engine/orchestration/turn.ts
|
|
5352
|
+
function afterCurrentTurn(callback) {
|
|
5353
|
+
if (typeof setImmediate === "function") {
|
|
5354
|
+
setImmediate(callback);
|
|
5355
|
+
return;
|
|
5331
5356
|
}
|
|
5332
|
-
|
|
5357
|
+
setTimeout(callback, 0);
|
|
5358
|
+
}
|
|
5359
|
+
function yieldToEventLoop3() {
|
|
5360
|
+
return new Promise((resolve) => afterCurrentTurn(resolve));
|
|
5333
5361
|
}
|
|
5334
5362
|
|
|
5335
5363
|
// src/engine/orchestration/replication.ts
|
|
5336
|
-
var MAX_PENDING_REPLICATION_DOCUMENTS = 2e4;
|
|
5337
5364
|
async function dispatchToWorkers(state, action) {
|
|
5338
5365
|
const pool = state.workerPool;
|
|
5339
5366
|
if (!pool) return;
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
reportIneligible(state, action.indexName, ineligibility);
|
|
5344
|
-
return;
|
|
5345
|
-
}
|
|
5346
|
-
pool.addIndexToAll(action.indexName);
|
|
5347
|
-
state.promotedIndexes.add(action.indexName);
|
|
5348
|
-
} else if ("indexName" in action && !state.promotedIndexes.has(action.indexName)) {
|
|
5349
|
-
return;
|
|
5350
|
-
}
|
|
5351
|
-
const allExecutors = pool.getAllExecutors();
|
|
5352
|
-
const results = await Promise.allSettled(allExecutors.map((workerExecutor) => workerExecutor.execute(action)));
|
|
5367
|
+
const executors = "indexName" in action ? pool.executorsHolding(action.indexName) : pool.getAllExecutors();
|
|
5368
|
+
if ("indexName" in action && !state.scaledOutIndexes.has(action.indexName)) return;
|
|
5369
|
+
const results = await Promise.allSettled(executors.map((workerExecutor) => workerExecutor.execute(action)));
|
|
5353
5370
|
for (const result of results) {
|
|
5354
5371
|
if (result.status === "rejected") {
|
|
5355
5372
|
if (action.type === "insert" && alreadyPresentOnWorker(result.reason)) {
|
|
@@ -5417,6 +5434,13 @@ function recordSegmentBroadcast(state, action) {
|
|
|
5417
5434
|
}
|
|
5418
5435
|
return;
|
|
5419
5436
|
}
|
|
5437
|
+
if (action.type === "freezeLiveTail") {
|
|
5438
|
+
partitionLedger(state, action.indexName, action.partitionId).push({
|
|
5439
|
+
segmentId: action.snapshot.segmentId,
|
|
5440
|
+
documentCount: action.snapshot.documentCount
|
|
5441
|
+
});
|
|
5442
|
+
return;
|
|
5443
|
+
}
|
|
5420
5444
|
if (action.type === "swapSegments") {
|
|
5421
5445
|
const entries = partitionLedger(state, action.indexName, action.partitionId);
|
|
5422
5446
|
const dropped = new Set(action.dropSegmentIds);
|
|
@@ -5429,48 +5453,71 @@ function recordSegmentBroadcast(state, action) {
|
|
|
5429
5453
|
function queueFor(state, indexName) {
|
|
5430
5454
|
let queue = state.replicationQueues.get(indexName);
|
|
5431
5455
|
if (queue === void 0) {
|
|
5432
|
-
queue = { tail: Promise.resolve(), pendingActions: 0, pendingDocuments: 0 };
|
|
5456
|
+
queue = { tail: Promise.resolve(), inFlight: [], pendingActions: 0, pendingDocuments: 0, drainWaiters: [] };
|
|
5433
5457
|
state.replicationQueues.set(indexName, queue);
|
|
5434
5458
|
}
|
|
5435
5459
|
return queue;
|
|
5436
5460
|
}
|
|
5437
|
-
function
|
|
5461
|
+
function dispatchGate(queue) {
|
|
5462
|
+
if (queue.inFlight.length < REPLICATION_WINDOW) return Promise.resolve();
|
|
5463
|
+
return queue.inFlight[queue.inFlight.length - REPLICATION_WINDOW];
|
|
5464
|
+
}
|
|
5465
|
+
function enqueueReplication(state, indexName, action) {
|
|
5466
|
+
const weight = documentWeight(action);
|
|
5467
|
+
recordSegmentBroadcast(state, action);
|
|
5438
5468
|
const queue = queueFor(state, indexName);
|
|
5439
5469
|
queue.pendingActions += 1;
|
|
5440
5470
|
queue.pendingDocuments += weight;
|
|
5441
|
-
|
|
5471
|
+
const run = dispatchGate(queue).then(async () => {
|
|
5442
5472
|
try {
|
|
5443
5473
|
await dispatchToWorkers(state, action);
|
|
5444
5474
|
} catch (err) {
|
|
5445
5475
|
console.warn("Worker replication failed:", err);
|
|
5446
5476
|
} finally {
|
|
5477
|
+
const at = queue.inFlight.indexOf(run);
|
|
5478
|
+
if (at !== -1) queue.inFlight.splice(at, 1);
|
|
5447
5479
|
queue.pendingActions -= 1;
|
|
5448
5480
|
queue.pendingDocuments -= weight;
|
|
5481
|
+
if (queue.pendingDocuments < MAX_PENDING_REPLICATION_DOCUMENTS && queue.drainWaiters.length > 0) {
|
|
5482
|
+
for (const resume of queue.drainWaiters.splice(0)) resume();
|
|
5483
|
+
}
|
|
5449
5484
|
if (queue.pendingActions === 0 && state.replicationQueues.get(indexName) === queue) {
|
|
5450
5485
|
state.replicationQueues.delete(indexName);
|
|
5451
5486
|
}
|
|
5452
5487
|
}
|
|
5453
5488
|
});
|
|
5489
|
+
queue.inFlight.push(run);
|
|
5490
|
+
queue.tail = run;
|
|
5454
5491
|
}
|
|
5455
5492
|
async function replicateToWorkers(state, action) {
|
|
5456
|
-
if (
|
|
5457
|
-
state.
|
|
5493
|
+
if (!("indexName" in action)) {
|
|
5494
|
+
if (state.workerPool === null) return;
|
|
5495
|
+
await awaitReplicationIdle(state);
|
|
5496
|
+
await dispatchToWorkers(state, action);
|
|
5458
5497
|
return;
|
|
5459
5498
|
}
|
|
5460
|
-
if (state.workerPool === null) return;
|
|
5461
5499
|
const detached = detachCallerDocuments(action);
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5500
|
+
const loading = state.copyLoadBuffers.get(action.indexName);
|
|
5501
|
+
if (loading !== void 0) {
|
|
5502
|
+
queueForCopies(state, detached);
|
|
5503
|
+
if (loading.length % LOAD_BUFFER_YIELD_INTERVAL === 0) await yieldToEventLoop3();
|
|
5465
5504
|
return;
|
|
5466
5505
|
}
|
|
5467
|
-
const
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5506
|
+
const queue = state.replicationQueues.get(action.indexName);
|
|
5507
|
+
if (queue !== void 0 && queue.pendingDocuments + documentWeight(detached) > MAX_PENDING_REPLICATION_DOCUMENTS) {
|
|
5508
|
+
await new Promise((resume) => queue.drainWaiters.push(resume));
|
|
5509
|
+
}
|
|
5510
|
+
queueForCopies(state, detached);
|
|
5511
|
+
}
|
|
5512
|
+
function queueForCopies(state, action) {
|
|
5513
|
+
if (!("indexName" in action)) return;
|
|
5514
|
+
const loading = state.copyLoadBuffers.get(action.indexName);
|
|
5515
|
+
if (loading !== void 0) {
|
|
5516
|
+
loading.push(action);
|
|
5517
|
+
return;
|
|
5471
5518
|
}
|
|
5472
|
-
|
|
5473
|
-
|
|
5519
|
+
if (state.workerPool === null || !state.scaledOutIndexes.has(action.indexName)) return;
|
|
5520
|
+
enqueueReplication(state, action.indexName, action);
|
|
5474
5521
|
}
|
|
5475
5522
|
async function awaitReplicationIdle(state, indexName) {
|
|
5476
5523
|
if (indexName !== void 0) {
|
|
@@ -5483,63 +5530,101 @@ async function awaitReplicationIdle(state, indexName) {
|
|
|
5483
5530
|
await Promise.all(tails);
|
|
5484
5531
|
}
|
|
5485
5532
|
}
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5533
|
+
|
|
5534
|
+
// src/engine/orchestration/live-tail.ts
|
|
5535
|
+
function liveTailCount(manager, partitionId) {
|
|
5536
|
+
if (partitionId >= manager.partitionCount) return 0;
|
|
5537
|
+
const partition = manager.getPartition(partitionId);
|
|
5538
|
+
return isCompositePartition(partition) ? partition.live.count() : partition.count();
|
|
5539
|
+
}
|
|
5540
|
+
function freezeTail(state, indexName, manager, partitionId) {
|
|
5541
|
+
const segment = manager.freezeLiveTail(partitionId, (payload, documents) => {
|
|
5542
|
+
const snapshot = freezeSegmentShared(payload, documents);
|
|
5543
|
+
return snapshot === null ? null : createSharedFrozenSegment(snapshot);
|
|
5544
|
+
});
|
|
5545
|
+
if (segment === null || segment.sharedSnapshot === null) return;
|
|
5546
|
+
queueForCopies(state, {
|
|
5547
|
+
type: "freezeLiveTail",
|
|
5548
|
+
indexName,
|
|
5549
|
+
partitionId,
|
|
5550
|
+
snapshot: segment.sharedSnapshot,
|
|
5551
|
+
requestId: createRequestId()
|
|
5552
|
+
});
|
|
5553
|
+
}
|
|
5554
|
+
function freezeLiveTail(state, indexName, manager, partitionId, floor) {
|
|
5555
|
+
if (!state.scaledOutIndexes.has(indexName)) return;
|
|
5556
|
+
if (liveTailCount(manager, partitionId) < floor) return;
|
|
5557
|
+
try {
|
|
5558
|
+
freezeTail(state, indexName, manager, partitionId);
|
|
5559
|
+
} catch (err) {
|
|
5560
|
+
console.warn(`Freezing the live tail of partition ${partitionId} of "${indexName}" failed:`, err);
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
function flushGrownTails(state, indexName) {
|
|
5564
|
+
const manager = state.executor.getManager(indexName);
|
|
5565
|
+
if (manager === void 0) return;
|
|
5566
|
+
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId++) {
|
|
5567
|
+
freezeLiveTail(state, indexName, manager, partitionId, LIVE_TAIL_FLUSH_DOCUMENTS);
|
|
5491
5568
|
}
|
|
5492
5569
|
}
|
|
5493
5570
|
|
|
5494
5571
|
// src/engine/orchestration/compaction.ts
|
|
5495
|
-
var COMPACTION_SEGMENT_TRIGGER = 8;
|
|
5496
5572
|
function smallestSegmentIds(entries) {
|
|
5497
5573
|
return [...entries].sort((a, b) => a.documentCount - b.documentCount).slice(0, COMPACTION_SEGMENT_TRIGGER).map((entry) => entry.segmentId);
|
|
5498
5574
|
}
|
|
5575
|
+
function pickSegmentIds(entries, policy) {
|
|
5576
|
+
if (policy === "idle") {
|
|
5577
|
+
return entries.length >= 2 ? entries.map((entry) => entry.segmentId) : null;
|
|
5578
|
+
}
|
|
5579
|
+
return entries.length >= COMPACTION_SEGMENT_TRIGGER ? smallestSegmentIds(entries) : null;
|
|
5580
|
+
}
|
|
5499
5581
|
function mainCompositeOf(manager, partitionId) {
|
|
5500
5582
|
if (partitionId >= manager.partitionCount) return null;
|
|
5501
5583
|
const partition = manager.getPartition(partitionId);
|
|
5502
5584
|
return isCompositePartition(partition) ? partition : null;
|
|
5503
5585
|
}
|
|
5504
|
-
function candidateSegmentIds(state, indexName, manager, partitionId) {
|
|
5586
|
+
function candidateSegmentIds(state, indexName, manager, partitionId, policy) {
|
|
5505
5587
|
const composite = mainCompositeOf(manager, partitionId);
|
|
5506
5588
|
if (composite !== null) {
|
|
5507
5589
|
const sizes = composite.frozenSegmentSizes();
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5590
|
+
const picks = pickSegmentIds(
|
|
5591
|
+
sizes.map((size) => ({ segmentId: size.segmentId, documentCount: size.liveDocumentCount })),
|
|
5592
|
+
policy
|
|
5593
|
+
);
|
|
5594
|
+
if (picks !== null) return picks;
|
|
5513
5595
|
}
|
|
5514
5596
|
const ledger = state.segmentLedger.get(indexName)?.get(partitionId);
|
|
5515
|
-
|
|
5516
|
-
return smallestSegmentIds(ledger);
|
|
5517
|
-
}
|
|
5518
|
-
return null;
|
|
5597
|
+
return ledger === void 0 ? null : pickSegmentIds(ledger, policy);
|
|
5519
5598
|
}
|
|
5520
5599
|
function mainHoldsAll(composite, segmentIds) {
|
|
5521
5600
|
if (composite === null) return false;
|
|
5522
5601
|
const held = new Set(composite.frozenSegmentSizes().map((size) => size.segmentId));
|
|
5523
5602
|
return segmentIds.every((segmentId) => held.has(segmentId));
|
|
5524
5603
|
}
|
|
5525
|
-
async function
|
|
5526
|
-
await awaitReplicationIdle(state, indexName);
|
|
5527
|
-
let snapshot = null;
|
|
5604
|
+
async function compactOnWorker(state, indexName, partitionId, segmentIds) {
|
|
5528
5605
|
const pool = state.workerPool;
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
}
|
|
5540
|
-
|
|
5541
|
-
|
|
5606
|
+
if (pool === null || !state.scaledOutIndexes.has(indexName)) return null;
|
|
5607
|
+
const lease = pool.leaseLeastBusy();
|
|
5608
|
+
if (lease === null) return null;
|
|
5609
|
+
try {
|
|
5610
|
+
return await lease.executor.execute({
|
|
5611
|
+
type: "compactSegments",
|
|
5612
|
+
indexName,
|
|
5613
|
+
partitionId,
|
|
5614
|
+
segmentIds: [...segmentIds],
|
|
5615
|
+
requestId: createRequestId()
|
|
5616
|
+
});
|
|
5617
|
+
} catch (err) {
|
|
5618
|
+
console.warn("Worker segment compaction failed, compacting on the main thread:", err);
|
|
5619
|
+
return null;
|
|
5620
|
+
} finally {
|
|
5621
|
+
lease.release();
|
|
5542
5622
|
}
|
|
5623
|
+
}
|
|
5624
|
+
async function compactPartitionSegments2(state, indexName, manager, partitionId, segmentIds, policy) {
|
|
5625
|
+
await awaitReplicationIdle(state, indexName);
|
|
5626
|
+
if (policy === "idle" && !state.scaledOutIndexes.has(indexName)) return false;
|
|
5627
|
+
let snapshot = await compactOnWorker(state, indexName, partitionId, segmentIds);
|
|
5543
5628
|
const composite = mainCompositeOf(manager, partitionId);
|
|
5544
5629
|
const holdsAll = mainHoldsAll(composite, segmentIds);
|
|
5545
5630
|
let replacement = null;
|
|
@@ -5566,30 +5651,52 @@ async function compactPartitionSegments2(state, indexName, manager, partitionId,
|
|
|
5566
5651
|
}
|
|
5567
5652
|
return true;
|
|
5568
5653
|
}
|
|
5569
|
-
async function compactIndexSegments(state, indexName) {
|
|
5654
|
+
async function compactIndexSegments(state, indexName, policy) {
|
|
5570
5655
|
const manager = state.executor.getManager(indexName);
|
|
5571
5656
|
if (!manager) return;
|
|
5572
5657
|
const partitionIds = /* @__PURE__ */ new Set();
|
|
5573
5658
|
for (let partitionId = 0; partitionId < manager.partitionCount; partitionId++) partitionIds.add(partitionId);
|
|
5574
5659
|
for (const partitionId of state.segmentLedger.get(indexName)?.keys() ?? []) partitionIds.add(partitionId);
|
|
5575
5660
|
for (const partitionId of partitionIds) {
|
|
5576
|
-
|
|
5661
|
+
if (policy === "idle") {
|
|
5662
|
+
await awaitReplicationIdle(state, indexName);
|
|
5663
|
+
freezeLiveTail(state, indexName, manager, partitionId, LIVE_TAIL_FREEZE_FLOOR);
|
|
5664
|
+
}
|
|
5665
|
+
let picks = candidateSegmentIds(state, indexName, manager, partitionId, policy);
|
|
5577
5666
|
while (picks !== null) {
|
|
5578
|
-
const compacted = await compactPartitionSegments2(state, indexName, manager, partitionId, picks);
|
|
5667
|
+
const compacted = await compactPartitionSegments2(state, indexName, manager, partitionId, picks, policy);
|
|
5579
5668
|
if (!compacted) break;
|
|
5580
|
-
picks = candidateSegmentIds(state, indexName, manager, partitionId);
|
|
5669
|
+
picks = candidateSegmentIds(state, indexName, manager, partitionId, policy);
|
|
5581
5670
|
}
|
|
5582
5671
|
}
|
|
5583
5672
|
}
|
|
5584
|
-
function maybeCompactSegments(state, indexName) {
|
|
5585
|
-
if (state.compactionsInFlight.has(indexName))
|
|
5586
|
-
|
|
5673
|
+
function maybeCompactSegments(state, indexName, policy = "ingest") {
|
|
5674
|
+
if (state.compactionsInFlight.has(indexName)) {
|
|
5675
|
+
if (policy === "idle") scheduleIdleMerge(state, indexName);
|
|
5676
|
+
return;
|
|
5677
|
+
}
|
|
5678
|
+
const run = compactIndexSegments(state, indexName, policy).catch((err) => {
|
|
5587
5679
|
console.warn("Segment compaction failed:", err);
|
|
5588
5680
|
}).finally(() => {
|
|
5589
5681
|
state.compactionsInFlight.delete(indexName);
|
|
5590
5682
|
});
|
|
5591
5683
|
state.compactionsInFlight.set(indexName, run);
|
|
5592
5684
|
}
|
|
5685
|
+
function scheduleIdleMerge(state, indexName) {
|
|
5686
|
+
cancelIdleMerge(state, indexName);
|
|
5687
|
+
const timer = setTimeout(() => {
|
|
5688
|
+
state.idleMergeTimers.delete(indexName);
|
|
5689
|
+
maybeCompactSegments(state, indexName, "idle");
|
|
5690
|
+
}, IDLE_MERGE_DELAY_MS);
|
|
5691
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5692
|
+
state.idleMergeTimers.set(indexName, timer);
|
|
5693
|
+
}
|
|
5694
|
+
function cancelIdleMerge(state, indexName) {
|
|
5695
|
+
const timer = state.idleMergeTimers.get(indexName);
|
|
5696
|
+
if (timer === void 0) return;
|
|
5697
|
+
clearTimeout(timer);
|
|
5698
|
+
state.idleMergeTimers.delete(indexName);
|
|
5699
|
+
}
|
|
5593
5700
|
async function awaitCompactions(state) {
|
|
5594
5701
|
while (state.compactionsInFlight.size > 0) {
|
|
5595
5702
|
await Promise.all([...state.compactionsInFlight.values()]);
|
|
@@ -5597,10 +5704,6 @@ async function awaitCompactions(state) {
|
|
|
5597
5704
|
}
|
|
5598
5705
|
|
|
5599
5706
|
// src/workers/worker-executor.ts
|
|
5600
|
-
var DEFAULT_BACKPRESSURE_LIMIT = 100;
|
|
5601
|
-
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
5602
|
-
var SHUTDOWN_TIMEOUT = 5e3;
|
|
5603
|
-
var CONSECUTIVE_TIMEOUTS_BEFORE_DEATH = 3;
|
|
5604
5707
|
function errorFromEventLike(event) {
|
|
5605
5708
|
if (event instanceof Error) {
|
|
5606
5709
|
return event;
|
|
@@ -5618,7 +5721,7 @@ function errorFromEventLike(event) {
|
|
|
5618
5721
|
}
|
|
5619
5722
|
function createWorkerExecutor(worker, config) {
|
|
5620
5723
|
const backpressureLimit = config?.backpressureLimit ?? DEFAULT_BACKPRESSURE_LIMIT;
|
|
5621
|
-
const requestTimeout = config?.requestTimeout ??
|
|
5724
|
+
const requestTimeout = config?.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
5622
5725
|
const pending = /* @__PURE__ */ new Map();
|
|
5623
5726
|
let deathError = null;
|
|
5624
5727
|
let shutdownRequested = false;
|
|
@@ -5706,7 +5809,7 @@ function createWorkerExecutor(worker, config) {
|
|
|
5706
5809
|
const timeoutId = setTimeout(() => {
|
|
5707
5810
|
pending.delete(requestId);
|
|
5708
5811
|
reject(new NarsilError(ErrorCodes.WORKER_TIMEOUT, "Shutdown timed out"));
|
|
5709
|
-
},
|
|
5812
|
+
}, SHUTDOWN_TIMEOUT_MS);
|
|
5710
5813
|
pending.set(requestId, {
|
|
5711
5814
|
resolve: () => resolve(),
|
|
5712
5815
|
reject,
|
|
@@ -5742,7 +5845,9 @@ async function createWorkerFactory(entryPoint) {
|
|
|
5742
5845
|
if (runtime.supportsWorkerThreads) {
|
|
5743
5846
|
const workerThreadsModule = await import('worker_threads');
|
|
5744
5847
|
return function nodeFactory(_workerId, onDeath) {
|
|
5745
|
-
const instance = new workerThreadsModule.Worker(new URL(resolvedEntry)
|
|
5848
|
+
const instance = new workerThreadsModule.Worker(new URL(resolvedEntry), {
|
|
5849
|
+
resourceLimits: WORKER_RESOURCE_LIMITS
|
|
5850
|
+
});
|
|
5746
5851
|
return createWorkerExecutor(instance, { onDeath });
|
|
5747
5852
|
};
|
|
5748
5853
|
}
|
|
@@ -5755,136 +5860,323 @@ async function createWorkerFactory(entryPoint) {
|
|
|
5755
5860
|
throw new NarsilError(ErrorCodes.WORKER_CRASHED, `No worker support available on runtime "${runtime.runtime}"`);
|
|
5756
5861
|
}
|
|
5757
5862
|
|
|
5758
|
-
// src/engine/orchestration/
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5863
|
+
// src/engine/orchestration/repair.ts
|
|
5864
|
+
var COPY_RESTART_REASON = "A request arrived after every worker crashed and the restart delay passed";
|
|
5865
|
+
function nextRestartDelay(state) {
|
|
5866
|
+
const delay2 = state.poolRetryDelayMs;
|
|
5867
|
+
state.poolRetryDelayMs = Math.min(delay2 * 2, POOL_RESTART_DELAY_MAX_MS);
|
|
5868
|
+
return delay2;
|
|
5869
|
+
}
|
|
5870
|
+
function deferPoolRestart(state) {
|
|
5871
|
+
state.poolRetryAt = Date.now() + nextRestartDelay(state);
|
|
5872
|
+
}
|
|
5873
|
+
function retirePool(state, pool) {
|
|
5874
|
+
if (state.workerPool !== pool) return;
|
|
5875
|
+
state.workerPool = null;
|
|
5876
|
+
cancelRepair(state);
|
|
5877
|
+
deferPoolRestart(state);
|
|
5878
|
+
for (const indexName of state.scaledOutIndexes) state.droppedCopies.set(indexName, COPY_RESTART_REASON);
|
|
5879
|
+
state.scaledOutIndexes.clear();
|
|
5880
|
+
state.segmentLedger.clear();
|
|
5881
|
+
void pool.shutdown().catch(() => void 0);
|
|
5882
|
+
}
|
|
5883
|
+
function handleWorkerCrash(state, pool, workerId, indexNames, error) {
|
|
5884
|
+
state.callbacks?.onWorkerCrash?.(workerId, indexNames, error);
|
|
5885
|
+
if (pool.getAllExecutors().length === 0) {
|
|
5886
|
+
retirePool(state, pool);
|
|
5887
|
+
return;
|
|
5768
5888
|
}
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5889
|
+
scheduleRepair(state, pool);
|
|
5890
|
+
}
|
|
5891
|
+
function scheduleRepair(state, pool) {
|
|
5892
|
+
if (state.workerPool !== pool || state.repairTimer !== null || state.poolRepair !== null) return;
|
|
5893
|
+
const timer = setTimeout(() => {
|
|
5894
|
+
state.repairTimer = null;
|
|
5895
|
+
void repairPool(state, pool);
|
|
5896
|
+
}, state.poolRetryDelayMs);
|
|
5897
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5898
|
+
state.repairTimer = timer;
|
|
5899
|
+
}
|
|
5900
|
+
function cancelRepair(state) {
|
|
5901
|
+
if (state.repairTimer === null) return;
|
|
5902
|
+
clearTimeout(state.repairTimer);
|
|
5903
|
+
state.repairTimer = null;
|
|
5904
|
+
}
|
|
5905
|
+
async function loadOntoReplacement(state, replacement, indexName) {
|
|
5906
|
+
const entry = state.indexRegistry.get(indexName);
|
|
5907
|
+
const manager = state.executor.getManager(indexName);
|
|
5908
|
+
if (entry === void 0 || manager === void 0 || !state.scaledOutIndexes.has(indexName)) return;
|
|
5909
|
+
const buffered = [];
|
|
5910
|
+
state.copyLoadBuffers.set(indexName, buffered);
|
|
5911
|
+
try {
|
|
5912
|
+
await loadIndexOntoWorkers(indexName, [replacement.executor], entry.config, manager);
|
|
5913
|
+
replacement.hold(indexName);
|
|
5914
|
+
} finally {
|
|
5915
|
+
state.copyLoadBuffers.delete(indexName);
|
|
5916
|
+
for (const action of buffered) enqueueReplication(state, indexName, action);
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
async function replaceWorker(state, pool, workerId) {
|
|
5920
|
+
const replacement = pool.spawnReplacement(workerId);
|
|
5921
|
+
if (replacement === null) return;
|
|
5922
|
+
try {
|
|
5923
|
+
if (state.bootstrapModule !== void 0) {
|
|
5924
|
+
await replacement.executor.execute({
|
|
5925
|
+
type: "bootstrap",
|
|
5926
|
+
moduleUrl: state.bootstrapModule,
|
|
5927
|
+
requestId: createRequestId()
|
|
5928
|
+
});
|
|
5929
|
+
}
|
|
5930
|
+
for (const indexName of [...state.scaledOutIndexes]) {
|
|
5931
|
+
await loadOntoReplacement(state, replacement, indexName);
|
|
5932
|
+
}
|
|
5933
|
+
replacement.admit();
|
|
5934
|
+
} catch (err) {
|
|
5935
|
+
replacement.abandon();
|
|
5936
|
+
throw err;
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
async function replaceDeadWorkers(state, pool) {
|
|
5940
|
+
for (const workerId of pool.deadWorkerIds()) {
|
|
5941
|
+
if (state.workerPool !== pool) return;
|
|
5942
|
+
await replaceWorker(state, pool, workerId);
|
|
5943
|
+
}
|
|
5944
|
+
state.poolRetryDelayMs = POOL_RESTART_DELAY_MS;
|
|
5945
|
+
}
|
|
5946
|
+
function repairPool(state, pool) {
|
|
5947
|
+
if (state.workerPool !== pool) return Promise.resolve();
|
|
5948
|
+
if (state.poolRepair !== null) return state.poolRepair;
|
|
5949
|
+
const run = replaceDeadWorkers(state, pool).catch((err) => {
|
|
5950
|
+
nextRestartDelay(state);
|
|
5951
|
+
console.warn("Replacing a crashed worker failed:", err);
|
|
5952
|
+
}).finally(() => {
|
|
5953
|
+
state.poolRepair = null;
|
|
5954
|
+
if (pool.deadWorkerIds().length > 0) scheduleRepair(state, pool);
|
|
5955
|
+
});
|
|
5956
|
+
state.poolRepair = run;
|
|
5957
|
+
return run;
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5960
|
+
// src/engine/orchestration/scale-out.ts
|
|
5961
|
+
var COPY_RELOAD_REASON = "A request arrived after an idle spell dropped the worker copies";
|
|
5962
|
+
function copiesAllowed(state) {
|
|
5963
|
+
if (!state.workersEnabled || state.scaleOutBlocked) return false;
|
|
5964
|
+
return state.workerPool !== null || Date.now() >= state.poolRetryAt;
|
|
5965
|
+
}
|
|
5966
|
+
async function startPool(state) {
|
|
5967
|
+
eligibleIndexNames(state);
|
|
5968
|
+
const factory = await createWorkerFactory();
|
|
5969
|
+
let started = null;
|
|
5970
|
+
const pool = createWorkerPool({
|
|
5971
|
+
count: state.keywordWorkerCount,
|
|
5972
|
+
workerFactory: factory,
|
|
5973
|
+
onWorkerCrash(workerId, indexNames, error) {
|
|
5974
|
+
if (started !== null) handleWorkerCrash(state, started, workerId, indexNames, error);
|
|
5975
|
+
}
|
|
5976
|
+
});
|
|
5977
|
+
started = pool;
|
|
5978
|
+
try {
|
|
5979
|
+
pool.spawnAll();
|
|
5980
|
+
if (state.bootstrapModule !== void 0) {
|
|
5981
|
+
const moduleUrl = state.bootstrapModule;
|
|
5786
5982
|
await Promise.all(
|
|
5787
|
-
|
|
5788
|
-
(workerExecutor) => workerExecutor.execute({
|
|
5789
|
-
type: "deserialize",
|
|
5790
|
-
indexName: name,
|
|
5791
|
-
partitionId,
|
|
5792
|
-
data: serialized,
|
|
5793
|
-
requestId: `promote-sync-${name}-${partitionId}`
|
|
5794
|
-
})
|
|
5983
|
+
pool.getAllExecutors().map(
|
|
5984
|
+
(workerExecutor) => workerExecutor.execute({ type: "bootstrap", moduleUrl, requestId: "copies-bootstrap" })
|
|
5795
5985
|
)
|
|
5796
5986
|
);
|
|
5797
5987
|
}
|
|
5988
|
+
} catch (err) {
|
|
5989
|
+
await pool.shutdown().catch(() => void 0);
|
|
5990
|
+
throw err;
|
|
5798
5991
|
}
|
|
5992
|
+
return pool;
|
|
5799
5993
|
}
|
|
5800
|
-
async function
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5994
|
+
async function ensurePool(state) {
|
|
5995
|
+
if (state.workerPool !== null) return state.workerPool;
|
|
5996
|
+
if (state.poolStart === null) {
|
|
5997
|
+
state.poolStart = startPool(state).then(
|
|
5998
|
+
(pool) => {
|
|
5999
|
+
state.workerPool = pool;
|
|
6000
|
+
state.poolStart = null;
|
|
6001
|
+
return pool;
|
|
6002
|
+
},
|
|
6003
|
+
(err) => {
|
|
6004
|
+
state.poolStart = null;
|
|
6005
|
+
deferPoolRestart(state);
|
|
6006
|
+
throw err;
|
|
5813
6007
|
}
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
6008
|
+
);
|
|
6009
|
+
}
|
|
6010
|
+
return state.poolStart;
|
|
6011
|
+
}
|
|
6012
|
+
function documentCountOf(state, indexName) {
|
|
6013
|
+
return state.executor.getManager(indexName)?.countDocuments() ?? 0;
|
|
6014
|
+
}
|
|
6015
|
+
function copyThresholdReason(state, indexName) {
|
|
6016
|
+
return `Index "${indexName}" holds ${documentCountOf(state, indexName)} documents, reaching the copy threshold of ${state.copyThreshold}`;
|
|
6017
|
+
}
|
|
6018
|
+
function indexReadyForCopies(state, indexName, incomingCount = 0) {
|
|
6019
|
+
if (!copiesAllowed(state)) return false;
|
|
6020
|
+
if (state.scaledOutIndexes.has(indexName) || state.desyncedIndexes.has(indexName)) return false;
|
|
6021
|
+
if (state.copyTransitions.has(indexName)) return false;
|
|
6022
|
+
if (state.callbacks?.shouldDeferCopies?.()) return false;
|
|
6023
|
+
const entry = state.indexRegistry.get(indexName);
|
|
6024
|
+
if (entry === void 0 || state.executor.getManager(indexName) === void 0) return false;
|
|
6025
|
+
if (documentCountOf(state, indexName) + incomingCount < state.copyThreshold) return false;
|
|
6026
|
+
const ineligibility = workerIneligibility(indexName, entry.config, state.bootstrapModule);
|
|
6027
|
+
if (ineligibility !== null) {
|
|
6028
|
+
reportIneligible(state, indexName, ineligibility);
|
|
6029
|
+
return false;
|
|
6030
|
+
}
|
|
6031
|
+
return true;
|
|
6032
|
+
}
|
|
6033
|
+
async function loadCopies(state, indexName, reason) {
|
|
6034
|
+
const entry = state.indexRegistry.get(indexName);
|
|
6035
|
+
const manager = state.executor.getManager(indexName);
|
|
6036
|
+
if (entry === void 0 || manager === void 0) return;
|
|
6037
|
+
const ineligibility = workerIneligibility(indexName, entry.config, state.bootstrapModule);
|
|
6038
|
+
if (ineligibility !== null) {
|
|
6039
|
+
reportIneligible(state, indexName, ineligibility);
|
|
6040
|
+
return;
|
|
6041
|
+
}
|
|
6042
|
+
const pool = await ensurePool(state);
|
|
6043
|
+
if (state.executor.getManager(indexName) === void 0) return;
|
|
6044
|
+
const dropReason = state.droppedCopies.get(indexName);
|
|
6045
|
+
const reload = state.droppedCopies.delete(indexName);
|
|
6046
|
+
const buffered = [];
|
|
6047
|
+
state.copyLoadBuffers.set(indexName, buffered);
|
|
6048
|
+
try {
|
|
6049
|
+
await transferIndexToPool(indexName, pool, entry.config, manager);
|
|
6050
|
+
state.scaledOutIndexes.add(indexName);
|
|
6051
|
+
state.lastAccessAt.set(indexName, Date.now());
|
|
6052
|
+
state.poolRetryDelayMs = POOL_RESTART_DELAY_MS;
|
|
6053
|
+
for (const action of buffered) enqueueReplication(state, indexName, action);
|
|
5828
6054
|
} catch (err) {
|
|
5829
|
-
state.
|
|
6055
|
+
if (dropReason !== void 0) state.droppedCopies.set(indexName, dropReason);
|
|
5830
6056
|
throw err;
|
|
5831
6057
|
} finally {
|
|
5832
|
-
state.
|
|
5833
|
-
|
|
6058
|
+
state.copyLoadBuffers.delete(indexName);
|
|
6059
|
+
}
|
|
6060
|
+
if (reload) state.copyReloadCounts.set(indexName, (state.copyReloadCounts.get(indexName) ?? 0) + 1);
|
|
6061
|
+
scheduleIdleMerge(state, indexName);
|
|
6062
|
+
state.callbacks?.onCopiesLoaded?.(pool.workerCount, reason);
|
|
6063
|
+
}
|
|
6064
|
+
async function loadAfter(previous, state, indexName, reason) {
|
|
6065
|
+
if (previous !== void 0) await previous.done;
|
|
6066
|
+
if (state.poolRepair !== null) await state.poolRepair;
|
|
6067
|
+
if (!copiesAllowed(state)) return;
|
|
6068
|
+
if (state.scaledOutIndexes.has(indexName) || state.desyncedIndexes.has(indexName)) return;
|
|
6069
|
+
await loadCopies(state, indexName, reason);
|
|
6070
|
+
}
|
|
6071
|
+
function scaleOutIndex(state, indexName, reason) {
|
|
6072
|
+
const previous = state.copyTransitions.get(indexName);
|
|
6073
|
+
if (previous !== void 0 && previous.kind !== "drop") return previous.done;
|
|
6074
|
+
const kind = state.droppedCopies.has(indexName) ? "reload" : "load";
|
|
6075
|
+
const done = loadAfter(previous, state, indexName, reason).catch((err) => {
|
|
6076
|
+
const error = toError3(err);
|
|
6077
|
+
state.scaleOutBlocked = isDeterministicFailure(error);
|
|
6078
|
+
state.callbacks?.onCopyLoadFailure?.(reason, error, !state.scaleOutBlocked);
|
|
6079
|
+
}).finally(() => {
|
|
6080
|
+
if (state.copyTransitions.get(indexName)?.done === done) state.copyTransitions.delete(indexName);
|
|
6081
|
+
});
|
|
6082
|
+
state.copyTransitions.set(indexName, { kind, done });
|
|
6083
|
+
return done;
|
|
6084
|
+
}
|
|
6085
|
+
async function scaleOutReadyIndexes(state) {
|
|
6086
|
+
if (!copiesAllowed(state)) return;
|
|
6087
|
+
for (const indexName of state.indexRegistry.keys()) {
|
|
6088
|
+
if (!indexReadyForCopies(state, indexName)) continue;
|
|
6089
|
+
const reason = copyThresholdReason(state, indexName);
|
|
6090
|
+
setTimeout(() => {
|
|
6091
|
+
void scaleOutIndex(state, indexName, reason);
|
|
6092
|
+
}, 0);
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
async function scaleOutBeforeBatch(state, indexName, incomingCount) {
|
|
6096
|
+
if (incomingCount <= 0) return;
|
|
6097
|
+
const pending = state.copyTransitions.get(indexName);
|
|
6098
|
+
if (pending !== void 0) {
|
|
6099
|
+
if (pending.kind === "load") await pending.done;
|
|
6100
|
+
return;
|
|
5834
6101
|
}
|
|
6102
|
+
if (!indexReadyForCopies(state, indexName, incomingCount)) return;
|
|
6103
|
+
await scaleOutIndex(state, indexName, copyThresholdReason(state, indexName));
|
|
5835
6104
|
}
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
6105
|
+
|
|
6106
|
+
// src/engine/orchestration/idle.ts
|
|
6107
|
+
function isIndexBusy(state, indexName) {
|
|
6108
|
+
return state.poolRepair !== null || state.copyTransitions.has(indexName) || state.replicationQueues.has(indexName) || state.compactionsInFlight.has(indexName);
|
|
6109
|
+
}
|
|
6110
|
+
function noteAccess(state, indexName) {
|
|
6111
|
+
state.lastAccessAt.set(indexName, Date.now());
|
|
6112
|
+
if (!copiesAllowed(state)) return;
|
|
6113
|
+
const reloadReason = state.droppedCopies.get(indexName);
|
|
6114
|
+
if (reloadReason !== void 0) {
|
|
6115
|
+
const transition = state.copyTransitions.get(indexName);
|
|
6116
|
+
if (transition !== void 0 && transition.kind !== "drop") return;
|
|
6117
|
+
void scaleOutIndex(state, indexName, reloadReason);
|
|
5840
6118
|
return;
|
|
5841
6119
|
}
|
|
5842
|
-
if (state
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
const
|
|
5848
|
-
if (
|
|
5849
|
-
state
|
|
5850
|
-
const
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
const
|
|
5857
|
-
|
|
6120
|
+
if (indexReadyForCopies(state, indexName)) {
|
|
6121
|
+
void scaleOutIndex(state, indexName, copyThresholdReason(state, indexName));
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
async function releaseCopies(state, indexName) {
|
|
6125
|
+
const pool = state.workerPool;
|
|
6126
|
+
if (pool === null) return;
|
|
6127
|
+
await awaitReplicationIdle(state, indexName);
|
|
6128
|
+
const cutoff = Date.now() - state.copyIdleTimeoutMs;
|
|
6129
|
+
if ((state.lastAccessAt.get(indexName) ?? Date.now()) > cutoff) return;
|
|
6130
|
+
if (!state.scaledOutIndexes.has(indexName) || state.compactionsInFlight.has(indexName)) return;
|
|
6131
|
+
state.scaledOutIndexes.delete(indexName);
|
|
6132
|
+
state.droppedCopies.set(indexName, COPY_RELOAD_REASON);
|
|
6133
|
+
cancelIdleMerge(state, indexName);
|
|
6134
|
+
const outcomes = await Promise.allSettled(
|
|
6135
|
+
pool.executorsHolding(indexName).map((worker) => worker.execute({ type: "dropIndex", indexName, requestId: `idle-drop-${indexName}` }))
|
|
6136
|
+
);
|
|
6137
|
+
for (const outcome of outcomes) {
|
|
6138
|
+
if (outcome.status === "rejected") console.warn("Dropping an idle worker copy failed:", outcome.reason);
|
|
6139
|
+
}
|
|
6140
|
+
pool.removeIndex(indexName);
|
|
6141
|
+
state.segmentLedger.delete(indexName);
|
|
6142
|
+
}
|
|
6143
|
+
async function dropIdleCopies(state, indexName) {
|
|
6144
|
+
const previous = state.copyTransitions.get(indexName);
|
|
6145
|
+
if (previous !== void 0) return;
|
|
6146
|
+
const done = releaseCopies(state, indexName).catch((err) => {
|
|
6147
|
+
console.warn("Dropping idle worker copies failed:", err);
|
|
6148
|
+
}).finally(() => {
|
|
6149
|
+
if (state.copyTransitions.get(indexName)?.done === done) state.copyTransitions.delete(indexName);
|
|
5858
6150
|
});
|
|
5859
|
-
state.
|
|
5860
|
-
await
|
|
5861
|
-
}
|
|
5862
|
-
async function
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
6151
|
+
state.copyTransitions.set(indexName, { kind: "drop", done });
|
|
6152
|
+
await done;
|
|
6153
|
+
}
|
|
6154
|
+
async function sweepIdleCopies(state) {
|
|
6155
|
+
const cutoff = Date.now() - state.copyIdleTimeoutMs;
|
|
6156
|
+
for (const indexName of [...state.scaledOutIndexes]) {
|
|
6157
|
+
if ((state.lastAccessAt.get(indexName) ?? Date.now()) > cutoff) continue;
|
|
6158
|
+
if (isIndexBusy(state, indexName)) continue;
|
|
6159
|
+
await dropIdleCopies(state, indexName);
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
function startIdleSweep(state) {
|
|
6163
|
+
if (!state.workersEnabled || state.idleSweep !== null) return;
|
|
6164
|
+
const interval = Math.min(MAX_SWEEP_INTERVAL_MS, Math.max(MIN_SWEEP_INTERVAL_MS, state.copyIdleTimeoutMs / 2));
|
|
6165
|
+
const timer = setInterval(() => void sweepIdleCopies(state).catch(() => void 0), interval);
|
|
6166
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
6167
|
+
state.idleSweep = timer;
|
|
6168
|
+
}
|
|
6169
|
+
function stopIdleSweep(state) {
|
|
6170
|
+
if (state.idleSweep === null) return;
|
|
6171
|
+
clearInterval(state.idleSweep);
|
|
6172
|
+
state.idleSweep = null;
|
|
5881
6173
|
}
|
|
5882
6174
|
|
|
5883
6175
|
// src/engine/orchestration/search.ts
|
|
5884
|
-
function
|
|
5885
|
-
const assignments = Array.from({ length:
|
|
5886
|
-
for (let
|
|
5887
|
-
assignments[
|
|
6176
|
+
function partitionsPerLease(scope, leaseCount) {
|
|
6177
|
+
const assignments = Array.from({ length: leaseCount }, () => []);
|
|
6178
|
+
for (let at = 0; at < scope.length; at++) {
|
|
6179
|
+
assignments[at % leaseCount].push(scope[at]);
|
|
5888
6180
|
}
|
|
5889
6181
|
return assignments;
|
|
5890
6182
|
}
|
|
@@ -5902,67 +6194,76 @@ function mergeWorkerResults(results) {
|
|
|
5902
6194
|
facets: workerFacets.length > 0 ? mergeFacets(workerFacets) : void 0
|
|
5903
6195
|
};
|
|
5904
6196
|
}
|
|
6197
|
+
function scoresPerPartition(state, indexName, params, globalStats) {
|
|
6198
|
+
const mode = params.scoring ?? state.indexRegistry.get(indexName)?.config.defaultScoring ?? "local";
|
|
6199
|
+
if (mode === "local") return true;
|
|
6200
|
+
return mode === "broadcast" && globalStats !== void 0;
|
|
6201
|
+
}
|
|
6202
|
+
function queryAction(indexName, params, globalStats, partitionIds) {
|
|
6203
|
+
return {
|
|
6204
|
+
type: "query",
|
|
6205
|
+
indexName,
|
|
6206
|
+
params,
|
|
6207
|
+
requestId: createRequestId(),
|
|
6208
|
+
...partitionIds !== void 0 ? { partitionIds } : {},
|
|
6209
|
+
...globalStats !== void 0 ? { globalStats } : {}
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
async function runSplit(leases, scope, indexName, params, globalStats) {
|
|
6213
|
+
const assignments = partitionsPerLease(scope, leases.length);
|
|
6214
|
+
const results = await Promise.all(
|
|
6215
|
+
assignments.map(
|
|
6216
|
+
(partitionIds, at) => leases[at].executor.execute(queryAction(indexName, params, globalStats, partitionIds)).finally(() => leases[at].release())
|
|
6217
|
+
)
|
|
6218
|
+
);
|
|
6219
|
+
return mergeWorkerResults(results);
|
|
6220
|
+
}
|
|
6221
|
+
function takeMainCopyTurn(state) {
|
|
6222
|
+
if (state.mainCopyTurnTaken) return false;
|
|
6223
|
+
state.mainCopyTurnTaken = true;
|
|
6224
|
+
afterCurrentTurn(() => {
|
|
6225
|
+
state.mainCopyTurnTaken = false;
|
|
6226
|
+
});
|
|
6227
|
+
return true;
|
|
6228
|
+
}
|
|
6229
|
+
function answersFasterOnMainCopy(state, pool, manager) {
|
|
6230
|
+
if (pool.queriesInFlight() > 0) return false;
|
|
6231
|
+
if (manager.countDocuments() > MAIN_COPY_LONE_QUERY_DOCUMENTS) return false;
|
|
6232
|
+
return takeMainCopyTurn(state);
|
|
6233
|
+
}
|
|
5905
6234
|
async function searchViaWorker(state, indexName, params, globalStats, partitionIds) {
|
|
5906
6235
|
const pool = state.workerPool;
|
|
5907
6236
|
if (!pool) return null;
|
|
5908
|
-
if (!state.
|
|
5909
|
-
if (state.awaitingBufferedWrites.has(indexName)) return null;
|
|
6237
|
+
if (!state.scaledOutIndexes.has(indexName) || state.copyLoadBuffers.has(indexName)) return null;
|
|
5910
6238
|
const pendingReplication = state.replicationQueues.get(indexName);
|
|
5911
6239
|
if (pendingReplication !== void 0 && pendingReplication.pendingActions > 0) return null;
|
|
5912
6240
|
const manager = state.executor.getManager(indexName);
|
|
5913
6241
|
if (!manager) return null;
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
const
|
|
5917
|
-
|
|
5918
|
-
try {
|
|
5919
|
-
return await allExecutors[0].execute({
|
|
5920
|
-
type: "query",
|
|
5921
|
-
indexName,
|
|
5922
|
-
params,
|
|
5923
|
-
requestId: createRequestId(),
|
|
5924
|
-
...partitionIds !== void 0 ? { partitionIds } : {},
|
|
5925
|
-
...stats
|
|
5926
|
-
});
|
|
5927
|
-
} catch (err) {
|
|
5928
|
-
console.warn("Worker search failed, falling back to local:", err);
|
|
5929
|
-
return null;
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
const assignments = partitionsPerWorker(manager.partitionCount, allExecutors.length);
|
|
5933
|
-
const scopedAssignments = partitionIds === void 0 ? assignments : assignments.map((assigned) => assigned.filter((partitionId) => partitionIds.includes(partitionId)));
|
|
5934
|
-
const activeAssignments = [];
|
|
5935
|
-
for (let index = 0; index < allExecutors.length; index++) {
|
|
5936
|
-
if (scopedAssignments[index].length > 0) {
|
|
5937
|
-
activeAssignments.push({ executor: allExecutors[index], partitionIds: scopedAssignments[index] });
|
|
5938
|
-
}
|
|
5939
|
-
}
|
|
5940
|
-
if (activeAssignments.length === 0) {
|
|
5941
|
-
return { scored: [], totalMatched: 0 };
|
|
5942
|
-
}
|
|
6242
|
+
if (answersFasterOnMainCopy(state, pool, manager)) return null;
|
|
6243
|
+
const scope = partitionIds ?? Array.from({ length: manager.partitionCount }, (_, partitionId) => partitionId);
|
|
6244
|
+
const idle = pool.leaseIdle(scope.length);
|
|
6245
|
+
const leases = [];
|
|
5943
6246
|
try {
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
})
|
|
5954
|
-
)
|
|
5955
|
-
);
|
|
5956
|
-
return mergeWorkerResults(results);
|
|
6247
|
+
if (idle.length >= 2 && scoresPerPartition(state, indexName, params, globalStats)) {
|
|
6248
|
+
leases.push(...idle);
|
|
6249
|
+
return await runSplit(leases, scope, indexName, params, globalStats);
|
|
6250
|
+
}
|
|
6251
|
+
for (const lease2 of idle.slice(1)) lease2.release();
|
|
6252
|
+
const lease = idle[0] ?? (takeMainCopyTurn(state) ? null : pool.leaseLeastBusy());
|
|
6253
|
+
if (lease === null) return null;
|
|
6254
|
+
leases.push(lease);
|
|
6255
|
+
return await lease.executor.execute(queryAction(indexName, params, globalStats, partitionIds));
|
|
5957
6256
|
} catch (err) {
|
|
5958
|
-
console.warn("
|
|
6257
|
+
console.warn("Worker search failed, falling back to local:", err);
|
|
5959
6258
|
return null;
|
|
6259
|
+
} finally {
|
|
6260
|
+
for (const lease of leases) lease.release();
|
|
5960
6261
|
}
|
|
5961
6262
|
}
|
|
5962
6263
|
|
|
5963
6264
|
// src/engine/orchestration/segments.ts
|
|
5964
6265
|
function segmentBuildConcurrency(state, indexName) {
|
|
5965
|
-
if (!state.
|
|
6266
|
+
if (!state.scaledOutIndexes.has(indexName)) return 0;
|
|
5966
6267
|
return state.workerPool?.workerCount ?? 0;
|
|
5967
6268
|
}
|
|
5968
6269
|
async function buildSegments(state, requests) {
|
|
@@ -5979,44 +6280,55 @@ async function buildSegments(state, requests) {
|
|
|
5979
6280
|
}
|
|
5980
6281
|
|
|
5981
6282
|
// src/engine/orchestration/index.ts
|
|
5982
|
-
function
|
|
6283
|
+
function workersEnabledByDefault() {
|
|
6284
|
+
return detectRuntime().runtime !== "browser";
|
|
6285
|
+
}
|
|
6286
|
+
function copyIdleTimeoutBeforeClose(closeAfterMs) {
|
|
6287
|
+
return closeAfterMs === void 0 ? DEFAULT_COPY_IDLE_TIMEOUT_MS : Math.min(DEFAULT_COPY_IDLE_TIMEOUT_MS, closeAfterMs);
|
|
6288
|
+
}
|
|
6289
|
+
function createWorkerOrchestrator(config, executor, indexRegistry, callbacks) {
|
|
5983
6290
|
const state = {
|
|
5984
6291
|
config,
|
|
5985
6292
|
executor,
|
|
5986
|
-
promoter,
|
|
5987
6293
|
indexRegistry,
|
|
5988
6294
|
callbacks,
|
|
5989
|
-
workersEnabled: config?.workers?.enabled
|
|
6295
|
+
workersEnabled: config?.workers?.enabled ?? workersEnabledByDefault(),
|
|
6296
|
+
keywordWorkerCount: splitWorkerBudget(resolveWorkerCount(config?.workers?.count)).keyword,
|
|
6297
|
+
copyThreshold: config?.workers?.promotionThreshold ?? DEFAULT_COPY_THRESHOLD,
|
|
6298
|
+
copyIdleTimeoutMs: config?.workers?.idleTimeoutMs ?? copyIdleTimeoutBeforeClose(config?.lifecycle?.idleTimeoutMs),
|
|
5990
6299
|
bootstrapModule: config?.workers?.bootstrapModule,
|
|
5991
|
-
promotionBuffer: [],
|
|
5992
|
-
awaitingBufferedWrites: /* @__PURE__ */ new Set(),
|
|
5993
6300
|
reportedIneligible: /* @__PURE__ */ new Set(),
|
|
5994
|
-
|
|
6301
|
+
scaledOutIndexes: /* @__PURE__ */ new Set(),
|
|
6302
|
+
desyncedIndexes: /* @__PURE__ */ new Set(),
|
|
6303
|
+
copyLoadBuffers: /* @__PURE__ */ new Map(),
|
|
6304
|
+
copyTransitions: /* @__PURE__ */ new Map(),
|
|
6305
|
+
droppedCopies: /* @__PURE__ */ new Map(),
|
|
6306
|
+
lastAccessAt: /* @__PURE__ */ new Map(),
|
|
6307
|
+
copyReloadCounts: /* @__PURE__ */ new Map(),
|
|
5995
6308
|
replicationQueues: /* @__PURE__ */ new Map(),
|
|
5996
6309
|
segmentLedger: /* @__PURE__ */ new Map(),
|
|
5997
6310
|
compactionsInFlight: /* @__PURE__ */ new Map(),
|
|
6311
|
+
idleMergeTimers: /* @__PURE__ */ new Map(),
|
|
5998
6312
|
workerPool: null,
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6313
|
+
poolStart: null,
|
|
6314
|
+
poolRetryAt: 0,
|
|
6315
|
+
poolRetryDelayMs: POOL_RESTART_DELAY_MS,
|
|
6316
|
+
poolRepair: null,
|
|
6317
|
+
mainCopyTurnTaken: false,
|
|
6318
|
+
repairTimer: null,
|
|
6319
|
+
scaleOutBlocked: false,
|
|
6320
|
+
idleSweep: null
|
|
6002
6321
|
};
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
if (!
|
|
6012
|
-
|
|
6013
|
-
const manager = executor.getManager(indexName);
|
|
6014
|
-
if (!manager) return;
|
|
6015
|
-
state.promotedIndexes.delete(indexName);
|
|
6016
|
-
await awaitReplicationIdle(state, indexName);
|
|
6017
|
-
state.segmentLedger.delete(indexName);
|
|
6018
|
-
await transferIndexToPool(indexName, pool, entry.config, manager);
|
|
6019
|
-
state.promotedIndexes.add(indexName);
|
|
6322
|
+
startIdleSweep(state);
|
|
6323
|
+
async function resyncIndex(indexName, wasScaledOut) {
|
|
6324
|
+
state.desyncedIndexes.delete(indexName);
|
|
6325
|
+
if (!wasScaledOut) return;
|
|
6326
|
+
await scaleOutIndex(state, indexName, copyThresholdReason(state, indexName));
|
|
6327
|
+
}
|
|
6328
|
+
async function openIndex(indexName) {
|
|
6329
|
+
state.droppedCopies.delete(indexName);
|
|
6330
|
+
if (!indexReadyForCopies(state, indexName)) return;
|
|
6331
|
+
await scaleOutIndex(state, indexName, copyThresholdReason(state, indexName));
|
|
6020
6332
|
}
|
|
6021
6333
|
async function getWorkerMemoryStats() {
|
|
6022
6334
|
if (!state.workerPool) return [];
|
|
@@ -6028,93 +6340,222 @@ function createWorkerOrchestrator(config, executor, promoter, indexRegistry, cal
|
|
|
6028
6340
|
external: report.external
|
|
6029
6341
|
}));
|
|
6030
6342
|
}
|
|
6343
|
+
function workerCopies() {
|
|
6344
|
+
const reports = [];
|
|
6345
|
+
for (const indexName of indexRegistry.keys()) {
|
|
6346
|
+
reports.push({
|
|
6347
|
+
indexName,
|
|
6348
|
+
scaledOut: state.scaledOutIndexes.has(indexName),
|
|
6349
|
+
reloadCount: state.copyReloadCounts.get(indexName) ?? 0
|
|
6350
|
+
});
|
|
6351
|
+
}
|
|
6352
|
+
return reports;
|
|
6353
|
+
}
|
|
6031
6354
|
async function shutdown() {
|
|
6355
|
+
stopIdleSweep(state);
|
|
6356
|
+
cancelRepair(state);
|
|
6357
|
+
for (const indexName of [...state.idleMergeTimers.keys()]) cancelIdleMerge(state, indexName);
|
|
6358
|
+
await Promise.allSettled([...state.copyTransitions.values()].map((transition) => transition.done));
|
|
6359
|
+
if (state.poolRepair !== null) await state.poolRepair;
|
|
6360
|
+
if (state.poolStart !== null) await state.poolStart.catch(() => void 0);
|
|
6032
6361
|
if (state.workerPool) {
|
|
6033
6362
|
await awaitCompactions(state);
|
|
6034
6363
|
await awaitReplicationIdle(state);
|
|
6035
6364
|
await state.workerPool.shutdown();
|
|
6036
6365
|
state.workerPool = null;
|
|
6037
|
-
state.
|
|
6366
|
+
state.scaledOutIndexes.clear();
|
|
6038
6367
|
state.replicationQueues.clear();
|
|
6039
6368
|
state.segmentLedger.clear();
|
|
6040
6369
|
}
|
|
6041
6370
|
}
|
|
6042
|
-
async function
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
await
|
|
6371
|
+
async function restoreAfterFailedClose(indexName, failure) {
|
|
6372
|
+
const pool = state.workerPool;
|
|
6373
|
+
const entry = indexRegistry.get(indexName);
|
|
6374
|
+
const manager = executor.getManager(indexName);
|
|
6375
|
+
if (pool !== null && entry !== void 0 && manager !== void 0) {
|
|
6376
|
+
try {
|
|
6377
|
+
await transferIndexToPool(indexName, pool, entry.config, manager);
|
|
6378
|
+
state.scaledOutIndexes.add(indexName);
|
|
6379
|
+
} catch (repairError) {
|
|
6380
|
+
throw new NarsilError(
|
|
6381
|
+
ErrorCodes.WORKER_CRASHED,
|
|
6382
|
+
`Worker copies for index "${indexName}" could not be restored after close failed`,
|
|
6383
|
+
{
|
|
6384
|
+
indexName,
|
|
6385
|
+
closeError: failure.reason instanceof Error ? failure.reason.message : String(failure.reason),
|
|
6386
|
+
repairError: repairError instanceof Error ? repairError.message : String(repairError)
|
|
6387
|
+
}
|
|
6388
|
+
);
|
|
6049
6389
|
}
|
|
6050
6390
|
}
|
|
6391
|
+
if (failure.reason instanceof NarsilError) throw failure.reason;
|
|
6392
|
+
throw new NarsilError(ErrorCodes.WORKER_CRASHED, `A worker could not close index "${indexName}"`, {
|
|
6393
|
+
indexName,
|
|
6394
|
+
cause: failure.reason instanceof Error ? failure.reason.message : String(failure.reason)
|
|
6395
|
+
});
|
|
6051
6396
|
}
|
|
6052
6397
|
async function closeIndex(indexName) {
|
|
6053
|
-
|
|
6398
|
+
if (state.poolRepair !== null) await state.poolRepair;
|
|
6399
|
+
const transition = state.copyTransitions.get(indexName);
|
|
6400
|
+
if (transition !== void 0) await transition.done;
|
|
6054
6401
|
await awaitReplicationIdle(state, indexName);
|
|
6055
6402
|
await awaitCompactions(state);
|
|
6403
|
+
cancelIdleMerge(state, indexName);
|
|
6056
6404
|
const pool = state.workerPool;
|
|
6057
|
-
if (pool !== null && state.
|
|
6405
|
+
if (pool !== null && state.scaledOutIndexes.has(indexName)) {
|
|
6406
|
+
state.scaledOutIndexes.delete(indexName);
|
|
6058
6407
|
const outcomes = await Promise.allSettled(
|
|
6059
|
-
pool.
|
|
6408
|
+
pool.executorsHolding(indexName).map((worker) => worker.execute({ type: "dropIndex", indexName, requestId: `close-${indexName}` }))
|
|
6060
6409
|
);
|
|
6061
6410
|
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
|
-
}
|
|
6411
|
+
if (failure?.status === "rejected") await restoreAfterFailedClose(indexName, failure);
|
|
6086
6412
|
pool.removeIndex(indexName);
|
|
6087
6413
|
}
|
|
6088
|
-
state.
|
|
6414
|
+
state.droppedCopies.delete(indexName);
|
|
6415
|
+
state.desyncedIndexes.delete(indexName);
|
|
6416
|
+
state.lastAccessAt.delete(indexName);
|
|
6089
6417
|
state.replicationQueues.delete(indexName);
|
|
6090
6418
|
state.segmentLedger.delete(indexName);
|
|
6091
6419
|
}
|
|
6092
6420
|
async function replicate(action) {
|
|
6093
6421
|
await replicateToWorkers(state, action);
|
|
6094
|
-
if (
|
|
6095
|
-
|
|
6422
|
+
if (!("indexName" in action)) return;
|
|
6423
|
+
if (action.type === "attachSegments") maybeCompactSegments(state, action.indexName);
|
|
6424
|
+
if (action.type === "insert" || action.type === "update") flushGrownTails(state, action.indexName);
|
|
6425
|
+
if (state.scaledOutIndexes.has(action.indexName) || state.copyLoadBuffers.has(action.indexName)) {
|
|
6426
|
+
scheduleIdleMerge(state, action.indexName);
|
|
6096
6427
|
}
|
|
6097
6428
|
}
|
|
6098
6429
|
return {
|
|
6099
|
-
|
|
6100
|
-
|
|
6430
|
+
scaleOutReadyIndexes: () => scaleOutReadyIndexes(state),
|
|
6431
|
+
scaleOutBeforeBatch: (indexName, incomingCount) => scaleOutBeforeBatch(state, indexName, incomingCount),
|
|
6101
6432
|
replicateToWorkers: replicate,
|
|
6102
6433
|
awaitReplication: (indexName) => awaitReplicationIdle(state, indexName),
|
|
6103
6434
|
awaitCompactions: () => awaitCompactions(state),
|
|
6104
|
-
openIndex
|
|
6435
|
+
openIndex,
|
|
6105
6436
|
closeIndex,
|
|
6106
|
-
isIndexBusy: (indexName) => state
|
|
6437
|
+
isIndexBusy: (indexName) => isIndexBusy(state, indexName),
|
|
6107
6438
|
buildSegments: (requests) => buildSegments(state, requests),
|
|
6108
6439
|
segmentBuildConcurrency: (indexName) => segmentBuildConcurrency(state, indexName),
|
|
6109
6440
|
searchViaWorker: (indexName, params, globalStats, partitionIds) => searchViaWorker(state, indexName, params, globalStats, partitionIds),
|
|
6110
|
-
|
|
6111
|
-
desyncIndex: (indexName) =>
|
|
6441
|
+
hasWorkerPool: () => state.workerPool !== null,
|
|
6442
|
+
desyncIndex: (indexName) => {
|
|
6443
|
+
state.desyncedIndexes.add(indexName);
|
|
6444
|
+
return state.scaledOutIndexes.delete(indexName);
|
|
6445
|
+
},
|
|
6112
6446
|
resyncIndex,
|
|
6447
|
+
noteAccess: (indexName) => noteAccess(state, indexName),
|
|
6448
|
+
workerCopies,
|
|
6113
6449
|
getWorkerMemoryStats,
|
|
6114
6450
|
shutdown
|
|
6115
6451
|
};
|
|
6116
6452
|
}
|
|
6117
6453
|
|
|
6454
|
+
// src/engine/validation.ts
|
|
6455
|
+
function now() {
|
|
6456
|
+
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
6457
|
+
return performance.now();
|
|
6458
|
+
}
|
|
6459
|
+
return Date.now();
|
|
6460
|
+
}
|
|
6461
|
+
function validateIndexName(name) {
|
|
6462
|
+
if (!name || name.length === 0) {
|
|
6463
|
+
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, "Index name must not be empty", { indexName: name });
|
|
6464
|
+
}
|
|
6465
|
+
if (name.length > MAX_INDEX_NAME_LENGTH) {
|
|
6466
|
+
throw new NarsilError(
|
|
6467
|
+
ErrorCodes.INDEX_NOT_FOUND,
|
|
6468
|
+
`Index name must not exceed ${MAX_INDEX_NAME_LENGTH} characters`,
|
|
6469
|
+
{
|
|
6470
|
+
indexName: name,
|
|
6471
|
+
length: name.length
|
|
6472
|
+
}
|
|
6473
|
+
);
|
|
6474
|
+
}
|
|
6475
|
+
if (!INDEX_NAME_PATTERN.test(name)) {
|
|
6476
|
+
throw new NarsilError(
|
|
6477
|
+
ErrorCodes.INDEX_NOT_FOUND,
|
|
6478
|
+
`Index name "${name}" contains invalid characters; use alphanumeric, dots, hyphens, and underscores only`,
|
|
6479
|
+
{ indexName: name }
|
|
6480
|
+
);
|
|
6481
|
+
}
|
|
6482
|
+
if (name.includes("..")) {
|
|
6483
|
+
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `Index name "${name}" must not contain ".."`, { indexName: name });
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
function validateDocId(docId) {
|
|
6487
|
+
if (!docId || docId.length === 0) {
|
|
6488
|
+
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not be empty", { docId });
|
|
6489
|
+
}
|
|
6490
|
+
if (docId.length > MAX_DOC_ID_LENGTH) {
|
|
6491
|
+
throw new NarsilError(
|
|
6492
|
+
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
6493
|
+
`Document ID must not exceed ${MAX_DOC_ID_LENGTH} characters`,
|
|
6494
|
+
{
|
|
6495
|
+
docId,
|
|
6496
|
+
length: docId.length
|
|
6497
|
+
}
|
|
6498
|
+
);
|
|
6499
|
+
}
|
|
6500
|
+
if (docId.includes("\0")) {
|
|
6501
|
+
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not contain null bytes", { docId });
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
function validatePartitionConfig(partitions) {
|
|
6505
|
+
const { maxDocsPerPartition, maxPartitions, watermark } = partitions;
|
|
6506
|
+
if (maxDocsPerPartition !== void 0 && (!Number.isInteger(maxDocsPerPartition) || maxDocsPerPartition < 1)) {
|
|
6507
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.maxDocsPerPartition must be a positive integer", {
|
|
6508
|
+
maxDocsPerPartition
|
|
6509
|
+
});
|
|
6510
|
+
}
|
|
6511
|
+
if (maxPartitions !== void 0 && (!Number.isInteger(maxPartitions) || maxPartitions < 1)) {
|
|
6512
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.maxPartitions must be a positive integer", {
|
|
6513
|
+
maxPartitions
|
|
6514
|
+
});
|
|
6515
|
+
}
|
|
6516
|
+
if (watermark !== void 0 && (typeof watermark !== "number" || !(watermark > 0) || watermark > 1)) {
|
|
6517
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.watermark must be above 0 and at most 1", {
|
|
6518
|
+
watermark
|
|
6519
|
+
});
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6522
|
+
function requirePositiveSafeInteger(field, value) {
|
|
6523
|
+
if (value === void 0) return;
|
|
6524
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
6525
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, `${field} must be a positive safe integer`, { field, value });
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
function validateWorkerConfig(workers, lifecycle) {
|
|
6529
|
+
if (workers === void 0) return;
|
|
6530
|
+
requirePositiveSafeInteger("workers.count", workers.count);
|
|
6531
|
+
requirePositiveSafeInteger("workers.promotionThreshold", workers.promotionThreshold);
|
|
6532
|
+
requirePositiveSafeInteger("workers.idleTimeoutMs", workers.idleTimeoutMs);
|
|
6533
|
+
const closeAfter = lifecycle?.idleTimeoutMs;
|
|
6534
|
+
if (workers.idleTimeoutMs !== void 0 && closeAfter !== void 0 && workers.idleTimeoutMs > closeAfter) {
|
|
6535
|
+
throw new NarsilError(
|
|
6536
|
+
ErrorCodes.CONFIG_INVALID,
|
|
6537
|
+
"workers.idleTimeoutMs must be at most lifecycle.idleTimeoutMs, so that an index drops its copies before it closes",
|
|
6538
|
+
{ workersIdleTimeoutMs: workers.idleTimeoutMs, lifecycleIdleTimeoutMs: closeAfter }
|
|
6539
|
+
);
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6542
|
+
function validateBM25Params(bm25) {
|
|
6543
|
+
if (bm25 === void 0) return;
|
|
6544
|
+
const { k1, b } = bm25;
|
|
6545
|
+
if (k1 !== void 0 && (!Number.isFinite(k1) || k1 < 0)) {
|
|
6546
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "bm25.k1 must be a finite number at or above 0", { k1 });
|
|
6547
|
+
}
|
|
6548
|
+
if (b !== void 0 && (!Number.isFinite(b) || b < 0 || b > 1)) {
|
|
6549
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "bm25.b must be between 0 and 1", { b });
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
function clampLimit(limit) {
|
|
6553
|
+
return clampRowCount(limit, DEFAULT_LIMIT);
|
|
6554
|
+
}
|
|
6555
|
+
function clampOffset(offset) {
|
|
6556
|
+
return clampRowCount(offset, DEFAULT_OFFSET);
|
|
6557
|
+
}
|
|
6558
|
+
|
|
6118
6559
|
// src/engine/vector-fields.ts
|
|
6119
6560
|
function getVectorFieldPaths(schema) {
|
|
6120
6561
|
return new Set(extractVectorFieldsFromSchema(schema).keys());
|
|
@@ -6156,10 +6597,13 @@ function createWatermarkNotifier(deps) {
|
|
|
6156
6597
|
|
|
6157
6598
|
// src/engine/core.ts
|
|
6158
6599
|
function createEngineCore(config, hooks) {
|
|
6159
|
-
|
|
6160
|
-
const
|
|
6161
|
-
|
|
6162
|
-
|
|
6600
|
+
validateWorkerConfig(config?.workers, config?.lifecycle);
|
|
6601
|
+
const vectorWorkerCount = splitWorkerBudget(resolveWorkerCount(config?.workers?.count)).vector;
|
|
6602
|
+
const executor = createDirectExecutor({
|
|
6603
|
+
vectorWorkerCopies: {
|
|
6604
|
+
enabled: (config?.workers?.enabled ?? workersEnabledByDefault()) && vectorWorkerCount > 0,
|
|
6605
|
+
count: vectorWorkerCount
|
|
6606
|
+
}
|
|
6163
6607
|
});
|
|
6164
6608
|
const pluginRegistry = createPluginRegistry();
|
|
6165
6609
|
if (config?.plugins) {
|
|
@@ -6178,11 +6622,11 @@ function createEngineCore(config, hooks) {
|
|
|
6178
6622
|
const shutdownState = { isShutdown: false };
|
|
6179
6623
|
const abortController = new AbortController();
|
|
6180
6624
|
const rebalancingIndexes = /* @__PURE__ */ new Set();
|
|
6181
|
-
const orchestrator = createWorkerOrchestrator(config, executor,
|
|
6182
|
-
|
|
6625
|
+
const orchestrator = createWorkerOrchestrator(config, executor, indexRegistry, {
|
|
6626
|
+
shouldDeferCopies() {
|
|
6183
6627
|
return rebalancingIndexes.size > 0;
|
|
6184
6628
|
},
|
|
6185
|
-
|
|
6629
|
+
onCopiesLoaded(workerCount, reason) {
|
|
6186
6630
|
const handlers = eventHandlers.get("workerPromote");
|
|
6187
6631
|
if (handlers) {
|
|
6188
6632
|
for (const handler of handlers) handler({ workerCount, reason });
|
|
@@ -6191,10 +6635,10 @@ function createEngineCore(config, hooks) {
|
|
|
6191
6635
|
console.warn("onWorkerPromote plugin hook failed:", err instanceof Error ? err.message : String(err));
|
|
6192
6636
|
});
|
|
6193
6637
|
},
|
|
6194
|
-
|
|
6638
|
+
onCopyLoadFailure(reason, error, retryable) {
|
|
6195
6639
|
const handlers = eventHandlers.get("workerPromoteFailure");
|
|
6196
6640
|
if (!handlers || handlers.size === 0) {
|
|
6197
|
-
console.warn(`
|
|
6641
|
+
console.warn(`Loading worker copies failed (${reason}):`, error);
|
|
6198
6642
|
return;
|
|
6199
6643
|
}
|
|
6200
6644
|
for (const handler of handlers) handler({ reason, error, retryable });
|
|
@@ -6383,7 +6827,8 @@ function createEngineCore(config, hooks) {
|
|
|
6383
6827
|
rebalancingIndexes,
|
|
6384
6828
|
requireManager,
|
|
6385
6829
|
onClose: hooks?.onIndexClose,
|
|
6386
|
-
onOpen: hooks?.onIndexOpen
|
|
6830
|
+
onOpen: hooks?.onIndexOpen,
|
|
6831
|
+
onAccess: (indexName) => orchestrator.noteAccess(indexName)
|
|
6387
6832
|
});
|
|
6388
6833
|
const mutationCtx = {
|
|
6389
6834
|
executor,
|
|
@@ -6417,7 +6862,6 @@ function createEngineCore(config, hooks) {
|
|
|
6417
6862
|
};
|
|
6418
6863
|
return {
|
|
6419
6864
|
executor,
|
|
6420
|
-
promoter,
|
|
6421
6865
|
pluginRegistry,
|
|
6422
6866
|
durability,
|
|
6423
6867
|
invalidation,
|
|
@@ -6444,96 +6888,6 @@ function createEngineCore(config, hooks) {
|
|
|
6444
6888
|
};
|
|
6445
6889
|
}
|
|
6446
6890
|
|
|
6447
|
-
// src/engine/validation.ts
|
|
6448
|
-
var INDEX_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
6449
|
-
var MAX_DOC_ID_LENGTH = 512;
|
|
6450
|
-
var BATCH_CHUNK_SIZE = 1e3;
|
|
6451
|
-
var DEFAULT_LIMIT = DEFAULT_PAGE_SIZE;
|
|
6452
|
-
var DEFAULT_OFFSET = 0;
|
|
6453
|
-
function now() {
|
|
6454
|
-
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
6455
|
-
return performance.now();
|
|
6456
|
-
}
|
|
6457
|
-
return Date.now();
|
|
6458
|
-
}
|
|
6459
|
-
function validateIndexName(name) {
|
|
6460
|
-
if (!name || name.length === 0) {
|
|
6461
|
-
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, "Index name must not be empty", { indexName: name });
|
|
6462
|
-
}
|
|
6463
|
-
if (name.length > MAX_INDEX_NAME_LENGTH) {
|
|
6464
|
-
throw new NarsilError(
|
|
6465
|
-
ErrorCodes.INDEX_NOT_FOUND,
|
|
6466
|
-
`Index name must not exceed ${MAX_INDEX_NAME_LENGTH} characters`,
|
|
6467
|
-
{
|
|
6468
|
-
indexName: name,
|
|
6469
|
-
length: name.length
|
|
6470
|
-
}
|
|
6471
|
-
);
|
|
6472
|
-
}
|
|
6473
|
-
if (!INDEX_NAME_PATTERN.test(name)) {
|
|
6474
|
-
throw new NarsilError(
|
|
6475
|
-
ErrorCodes.INDEX_NOT_FOUND,
|
|
6476
|
-
`Index name "${name}" contains invalid characters; use alphanumeric, dots, hyphens, and underscores only`,
|
|
6477
|
-
{ indexName: name }
|
|
6478
|
-
);
|
|
6479
|
-
}
|
|
6480
|
-
if (name.includes("..")) {
|
|
6481
|
-
throw new NarsilError(ErrorCodes.INDEX_NOT_FOUND, `Index name "${name}" must not contain ".."`, { indexName: name });
|
|
6482
|
-
}
|
|
6483
|
-
}
|
|
6484
|
-
function validateDocId(docId) {
|
|
6485
|
-
if (!docId || docId.length === 0) {
|
|
6486
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not be empty", { docId });
|
|
6487
|
-
}
|
|
6488
|
-
if (docId.length > MAX_DOC_ID_LENGTH) {
|
|
6489
|
-
throw new NarsilError(
|
|
6490
|
-
ErrorCodes.DOC_VALIDATION_FAILED,
|
|
6491
|
-
`Document ID must not exceed ${MAX_DOC_ID_LENGTH} characters`,
|
|
6492
|
-
{
|
|
6493
|
-
docId,
|
|
6494
|
-
length: docId.length
|
|
6495
|
-
}
|
|
6496
|
-
);
|
|
6497
|
-
}
|
|
6498
|
-
if (docId.includes("\0")) {
|
|
6499
|
-
throw new NarsilError(ErrorCodes.DOC_VALIDATION_FAILED, "Document ID must not contain null bytes", { docId });
|
|
6500
|
-
}
|
|
6501
|
-
}
|
|
6502
|
-
function validatePartitionConfig(partitions) {
|
|
6503
|
-
const { maxDocsPerPartition, maxPartitions, watermark } = partitions;
|
|
6504
|
-
if (maxDocsPerPartition !== void 0 && (!Number.isInteger(maxDocsPerPartition) || maxDocsPerPartition < 1)) {
|
|
6505
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.maxDocsPerPartition must be a positive integer", {
|
|
6506
|
-
maxDocsPerPartition
|
|
6507
|
-
});
|
|
6508
|
-
}
|
|
6509
|
-
if (maxPartitions !== void 0 && (!Number.isInteger(maxPartitions) || maxPartitions < 1)) {
|
|
6510
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.maxPartitions must be a positive integer", {
|
|
6511
|
-
maxPartitions
|
|
6512
|
-
});
|
|
6513
|
-
}
|
|
6514
|
-
if (watermark !== void 0 && (typeof watermark !== "number" || !(watermark > 0) || watermark > 1)) {
|
|
6515
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "partitions.watermark must be above 0 and at most 1", {
|
|
6516
|
-
watermark
|
|
6517
|
-
});
|
|
6518
|
-
}
|
|
6519
|
-
}
|
|
6520
|
-
function validateBM25Params(bm25) {
|
|
6521
|
-
if (bm25 === void 0) return;
|
|
6522
|
-
const { k1, b } = bm25;
|
|
6523
|
-
if (k1 !== void 0 && (!Number.isFinite(k1) || k1 < 0)) {
|
|
6524
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "bm25.k1 must be a finite number at or above 0", { k1 });
|
|
6525
|
-
}
|
|
6526
|
-
if (b !== void 0 && (!Number.isFinite(b) || b < 0 || b > 1)) {
|
|
6527
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "bm25.b must be between 0 and 1", { b });
|
|
6528
|
-
}
|
|
6529
|
-
}
|
|
6530
|
-
function clampLimit(limit) {
|
|
6531
|
-
return clampRowCount(limit, DEFAULT_LIMIT);
|
|
6532
|
-
}
|
|
6533
|
-
function clampOffset(offset) {
|
|
6534
|
-
return clampRowCount(offset, DEFAULT_OFFSET);
|
|
6535
|
-
}
|
|
6536
|
-
|
|
6537
6891
|
// src/engine/index-lifecycle.ts
|
|
6538
6892
|
async function createEngineIndex(core, config, name, indexConfig, indexUuid) {
|
|
6539
6893
|
core.guardShutdown();
|
|
@@ -7144,7 +7498,7 @@ async function insertDocument(ctx, indexName, document, docId, options) {
|
|
|
7144
7498
|
}
|
|
7145
7499
|
}
|
|
7146
7500
|
ctx.checkWatermark(indexName);
|
|
7147
|
-
await ctx.orchestrator.
|
|
7501
|
+
await ctx.orchestrator.scaleOutReadyIndexes();
|
|
7148
7502
|
return resolvedDocId;
|
|
7149
7503
|
}
|
|
7150
7504
|
|
|
@@ -7298,7 +7652,6 @@ async function applyAdmittedDocuments(ctx, indexName, admitted, options, failed)
|
|
|
7298
7652
|
}
|
|
7299
7653
|
|
|
7300
7654
|
// src/engine/mutations/segment-replication.ts
|
|
7301
|
-
var MIN_DOCUMENTS_FOR_SEGMENTS = 64;
|
|
7302
7655
|
function shardCount(documentCount, workers) {
|
|
7303
7656
|
if (workers <= 1) return 1;
|
|
7304
7657
|
return Math.max(1, Math.min(workers, Math.ceil(documentCount / MIN_DOCUMENTS_FOR_SEGMENTS)));
|
|
@@ -7558,7 +7911,7 @@ async function insertBatchViaSegments(ctx, indexName, documents, options) {
|
|
|
7558
7911
|
vecIndexes.get(fieldPath)?.scheduleBuild();
|
|
7559
7912
|
}
|
|
7560
7913
|
ctx.checkWatermark(indexName);
|
|
7561
|
-
await ctx.orchestrator.
|
|
7914
|
+
await ctx.orchestrator.scaleOutReadyIndexes();
|
|
7562
7915
|
return { succeeded: outcome.succeeded, failed };
|
|
7563
7916
|
}
|
|
7564
7917
|
|
|
@@ -7566,7 +7919,7 @@ async function insertBatchViaSegments(ctx, indexName, documents, options) {
|
|
|
7566
7919
|
async function insertDocumentBatch(ctx, indexName, documents, options) {
|
|
7567
7920
|
ctx.guardShutdown();
|
|
7568
7921
|
const entry = ctx.requireIndex(indexName);
|
|
7569
|
-
await ctx.orchestrator.
|
|
7922
|
+
await ctx.orchestrator.scaleOutBeforeBatch(indexName, documents.length);
|
|
7570
7923
|
if (documents.length >= MIN_DOCUMENTS_FOR_SEGMENTS && !ctx.isRebalancing(indexName) && ctx.orchestrator.segmentBuildConcurrency(indexName) > 0) {
|
|
7571
7924
|
return insertBatchViaSegments(ctx, indexName, documents, options);
|
|
7572
7925
|
}
|
|
@@ -7720,7 +8073,7 @@ async function insertDocumentBatch(ctx, indexName, documents, options) {
|
|
|
7720
8073
|
}
|
|
7721
8074
|
}
|
|
7722
8075
|
ctx.checkWatermark(indexName);
|
|
7723
|
-
await ctx.orchestrator.
|
|
8076
|
+
await ctx.orchestrator.scaleOutReadyIndexes();
|
|
7724
8077
|
return { succeeded, failed };
|
|
7725
8078
|
}
|
|
7726
8079
|
|
|
@@ -7995,7 +8348,7 @@ async function updateDocumentBatch(ctx, indexName, updates) {
|
|
|
7995
8348
|
}
|
|
7996
8349
|
|
|
7997
8350
|
// src/partitioning/write-ahead-queue.ts
|
|
7998
|
-
function createWriteAheadQueue(maxSize =
|
|
8351
|
+
function createWriteAheadQueue(maxSize = DEFAULT_WRITE_AHEAD_QUEUE_MAX_SIZE) {
|
|
7999
8352
|
const entries = [];
|
|
8000
8353
|
const docStates = /* @__PURE__ */ new Map();
|
|
8001
8354
|
let nextSequence = 1;
|
|
@@ -9830,11 +10183,6 @@ async function resolveVectorText(params, embeddingAdapter, signal, embeddingAdap
|
|
|
9830
10183
|
}
|
|
9831
10184
|
|
|
9832
10185
|
// src/engine/suggest.ts
|
|
9833
|
-
var DEFAULT_SUGGEST_LIMIT = 10;
|
|
9834
|
-
var MAX_SUGGEST_LIMIT = 100;
|
|
9835
|
-
var SUGGEST_OVERSAMPLE_FACTOR = 1.5;
|
|
9836
|
-
var SUGGEST_OVERSAMPLE_PADDING = 10;
|
|
9837
|
-
var MAX_SUGGEST_SCATTER_LIMIT = Math.ceil(MAX_SUGGEST_LIMIT * SUGGEST_OVERSAMPLE_FACTOR) + SUGGEST_OVERSAMPLE_PADDING;
|
|
9838
10186
|
function executeSuggest(manager, language, params, partitionIds) {
|
|
9839
10187
|
const t0 = performance.now();
|
|
9840
10188
|
const ceiling = partitionIds === void 0 ? MAX_SUGGEST_LIMIT : MAX_SUGGEST_SCATTER_LIMIT;
|
|
@@ -9912,7 +10260,7 @@ async function runEngineQuery(core, indexName, params, options) {
|
|
|
9912
10260
|
entry.embeddingAdapterName
|
|
9913
10261
|
);
|
|
9914
10262
|
await core.pluginRegistry.runHook("beforeSearch", { indexName, params: resolvedParams });
|
|
9915
|
-
const workerSearch = core.orchestrator.
|
|
10263
|
+
const workerSearch = core.orchestrator.hasWorkerPool() ? core.orchestrator.searchViaWorker.bind(core.orchestrator) : void 0;
|
|
9916
10264
|
const result = await executeQuery(resolvedParams, {
|
|
9917
10265
|
manager,
|
|
9918
10266
|
language: entry.language,
|
|
@@ -9952,7 +10300,7 @@ async function runEnginePreflight(core, indexName, params, options) {
|
|
|
9952
10300
|
core.abortController.signal,
|
|
9953
10301
|
entry.embeddingAdapterName
|
|
9954
10302
|
);
|
|
9955
|
-
const workerSearch = core.orchestrator.
|
|
10303
|
+
const workerSearch = core.orchestrator.hasWorkerPool() ? core.orchestrator.searchViaWorker.bind(core.orchestrator) : void 0;
|
|
9956
10304
|
const result = await executePreflight(resolvedParams, {
|
|
9957
10305
|
manager,
|
|
9958
10306
|
language: entry.language,
|
|
@@ -10268,6 +10616,7 @@ function createNarsilFromCore(core, config) {
|
|
|
10268
10616
|
openIndexCount: lifecycleCounts.open,
|
|
10269
10617
|
closedIndexCount: lifecycleCounts.closed,
|
|
10270
10618
|
reopenCount: lifecycleCounts.reopens,
|
|
10619
|
+
workerCopies: orchestrator.workerCopies(),
|
|
10271
10620
|
workers
|
|
10272
10621
|
};
|
|
10273
10622
|
},
|
|
@@ -10332,4 +10681,4 @@ async function createNarsil(config) {
|
|
|
10332
10681
|
// src/index.ts
|
|
10333
10682
|
var VERSION = "0.1.0";
|
|
10334
10683
|
|
|
10335
|
-
export {
|
|
10684
|
+
export { VERSION, applyDeleteEntry, applyIndexEntry, clampLimit, computeEntryChecksum, createAckMessage, createDurableDirectory, createEngineCore, createEngineIndex, createEntryBatchMessage, createEntryMessage, createForwardBatchMessage, createForwardBatchResultMessage, createForwardMessage, createInsyncAddMessage, createInsyncConfirmMessage, createInsyncRemoveMessage, createNarsil, createNarsilFromCore, dedupePinnedEntries, foldGroupReducer, listBindingOf, now, queryBindingOf, runEngineListDocuments, runEnginePreflight, runEngineQuery, runEngineQueryStats, runEngineSuggest, validateAckPayload, validateEntryBatchPayload, validateEntryPayload, validateForwardBatchPayload, validateForwardBatchResultPayload, validateInsyncConfirmPayload, validateReplicationEntry, writeSegmentedCheckpoint };
|