@delali/narsil 0.3.1-next.71 → 0.3.1-next.73
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/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/{capabilities-Dt-7B4Wz.d.ts → capabilities-CYeAWYxQ.d.ts} +19 -4
- package/dist/{chunk-3JGWULDF.mjs → chunk-2WLD2NFW.mjs} +5 -5
- package/dist/{chunk-LMO5TJUS.mjs → chunk-3YKWSO54.mjs} +642 -196
- package/dist/{chunk-HWQLTLNG.mjs → chunk-4MQ5F4IL.mjs} +1 -1
- package/dist/{chunk-FVLQ63B7.mjs → chunk-AFKH4ZK3.mjs} +30 -6
- package/dist/{chunk-653BNMAF.mjs → chunk-AK3BLGCI.mjs} +195 -25
- package/dist/{chunk-IOR2K26O.mjs → chunk-CVFAFZSN.mjs} +8000 -4391
- package/dist/{chunk-USKFU7J5.mjs → chunk-I64LXKO3.mjs} +1 -1
- package/dist/{chunk-OI42GWLR.mjs → chunk-LJYTW7GT.mjs} +57 -11
- package/dist/{chunk-2STVUTNA.mjs → chunk-LPBWCHBE.mjs} +194 -13
- package/dist/{chunk-YNVIUGF5.mjs → chunk-PU43WDY4.mjs} +4 -2
- package/dist/{chunk-M76V3OGJ.mjs → chunk-THMSFOJO.mjs} +1289 -1405
- package/dist/{chunk-YJDQ7NLA.mjs → chunk-WVDFG45M.mjs} +93 -95
- package/dist/chunk-XW25X6AU.mjs +614 -0
- package/dist/chunk-ZKRV5M2P.mjs +75 -0
- package/dist/{chunk-P4DSQ6M4.mjs → chunk-ZULSEZCE.mjs} +68 -28
- package/dist/{client-COek5GXK.d.ts → client-D698E5ge.d.ts} +77 -26
- package/dist/client.d.ts +9 -9
- package/dist/client.mjs +45 -22
- package/dist/{config-rBpbgTw8.d.ts → config-Bv8etwUZ.d.ts} +11 -4
- package/dist/{config-Dfanyqqt.d.ts → config-QXU3HMpq.d.ts} +26 -2
- package/dist/direct-executor-5SRDFZAS.mjs +13 -0
- package/dist/distribution/coordinator/etcd.d.ts +29 -16
- package/dist/distribution/coordinator/etcd.mjs +163 -76
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/coordinator/in-memory.mjs +1 -1
- package/dist/distribution/transport/grpc.d.ts +2 -2
- package/dist/distribution/transport/grpc.mjs +22 -2
- package/dist/distribution/transport/in-memory.d.ts +2 -2
- package/dist/distribution/transport/in-memory.mjs +6 -3
- package/dist/distribution/transport/tcp.d.ts +2 -2
- package/dist/distribution/transport/tcp.mjs +40 -16
- package/dist/distribution.d.ts +9 -9
- package/dist/distribution.mjs +682 -388
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-igk0aMpQ.d.ts → engine-D_OzKZ-6.d.ts} +97 -188
- package/dist/index.browser.mjs +11671 -7427
- package/dist/index.d.ts +27 -8
- package/dist/index.mjs +13 -14
- package/dist/invalidation/filesystem.mjs +1 -1
- package/dist/{memory-C7UMCMH3.d.ts → memory-B4jMmygw.d.ts} +279 -27
- package/dist/pattern-Cledccbl.d.ts +18 -0
- package/dist/pattern.d.ts +22 -0
- package/dist/pattern.mjs +7 -0
- package/dist/persistence/durability/checkpoint-worker.d.ts +41 -17
- package/dist/persistence/durability/checkpoint-worker.mjs +24 -16
- package/dist/react.d.ts +7 -7
- package/dist/react.mjs +1 -1
- package/dist/{schema-Bgzr4g7b.d.ts → schema-BBOCdhND.d.ts} +9 -7
- package/dist/server.d.ts +11 -11
- package/dist/server.mjs +213 -34
- package/dist/{tasks-BZt_zes6.d.ts → tasks-DpgCpW1x.d.ts} +3 -3
- package/dist/{thread-JEVAYETR.mjs → thread-3LNVA43V.mjs} +94 -27
- package/dist/{types-BQDlIvfS.d.ts → types-B3b68nfF.d.ts} +1 -1
- package/dist/{types-gOUqhs2F.d.ts → types-BiUmaiWT.d.ts} +1 -1
- package/dist/{types-D5OwhtsQ.d.ts → types-Cb9kiY5P.d.ts} +1 -1
- package/dist/vector/search-worker.d.ts +1 -1
- package/dist/vector/search-worker.mjs +4 -4
- package/dist/workers/entry.mjs +4 -4
- package/package.json +15 -7
- package/dist/chunk-3L4U6EJQ.mjs +0 -49
- package/dist/chunk-GIVAGD3Q.mjs +0 -25
- package/dist/chunk-IFBKLBGB.mjs +0 -7
- package/dist/direct-executor-GYJEKNV5.mjs +0 -12
- package/dist/serialization/crc32-worker.d.ts +0 -20
- package/dist/serialization/crc32-worker.mjs +0 -62
package/dist/distribution.mjs
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import { MAX_MESSAGE_SIZE_BYTES } from './chunk-UNEEFTEU.mjs';
|
|
2
|
-
import { validateIndexName, indexConfigKey, indexSettingsOf, putIndexMetadata,
|
|
3
|
-
import { requireKnownConfig, resolveRunnableConfig, createEngineCore, startEngineCore, createNarsilFromCore, runEngineQueryStats, runEngineListDocuments, runEngineSuggest, runEnginePreflight, runEngineQuery, createEngineIndex, applyIndexEntry, applyDeleteEntry, validateEntryBatchPayload, createAckMessage, validateEntryPayload, validateForwardBatchPayload, createForwardBatchResultMessage, createForwardBatchMessage, validateForwardBatchResultPayload, createForwardMessage, validateReplicationEntry, computeEntryChecksum, createEntryMessage, createEntryBatchMessage, createInsyncConfirmMessage, validateAckPayload, createInsyncRemoveMessage, validateInsyncConfirmPayload, createInsyncAddMessage } from './chunk-
|
|
2
|
+
import { validateIndexName, indexConfigKey, withPartitionCount, indexSettingsOf, putIndexMetadata, getIndexMetadata, getClusterIndexConfig } from './chunk-2WLD2NFW.mjs';
|
|
3
|
+
import { requireKnownConfig, resolveRunnableConfig, createEngineCore, startEngineCore, createNarsilFromCore, runEngineQueryStats, runEngineListDocuments, runEngineSuggest, runEnginePreflight, runEngineQuery, createEngineIndex, buildArchiveFromMemory, indexMetadataOf, encodeIndexConfiguration, archiveStream, applyIndexEntry, applyDeleteEntry, requireConfigurationWithinSizeLimit, validateEntryBatchPayload, createAckMessage, validateEntryPayload, validateForwardBatchPayload, createForwardBatchResultMessage, memoryCheckpointStore, readSnapshotArchive, createByteReader, openChecksummedEnvelope, loadPartitionSegments, readListedFile, createForwardBatchMessage, validateForwardBatchResultPayload, createForwardMessage, validateReplicationEntry, crc32Init, crc32Final, crc32Update, computeEntryChecksum, createEntryMessage, createEntryBatchMessage, createInsyncConfirmMessage, validateAckPayload, createInsyncRemoveMessage, validateInsyncConfirmPayload, createInsyncAddMessage } from './chunk-CVFAFZSN.mjs';
|
|
4
4
|
import { ReplicationMessageTypes, TransportErrorCodes, ClusterMessageTypes, QueryMessageTypes, TransportError } from './chunk-GCM3JFVA.mjs';
|
|
5
|
-
import { providedDocId, MAX_PARTITION_COUNT, MAX_REPLICATION_FACTOR, WIRE_BATCH_BUDGET } from './chunk-
|
|
6
|
-
import { generateId,
|
|
7
|
-
import { getLanguage } from './chunk-
|
|
8
|
-
import { now, listBindingOf, queryBindingOf, hitsKeptPerGroup, clampLimit, resolveHybridFusion, dedupePinnedEntries, foldGroupReducer } from './chunk-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { validateSchema, DEFAULT_PAGE_SIZE, MAX_SORT_FIELDS, MAX_CURSOR_LENGTH, MAX_TOLERANCE, MAX_PREFIX_LENGTH, RESULT_WINDOW, MAX_NESTING_DEPTH } from './chunk-653BNMAF.mjs';
|
|
5
|
+
import { providedDocId, MAX_PARTITION_COUNT, MAX_REPLICATION_FACTOR, WIRE_BATCH_BUDGET } from './chunk-AFKH4ZK3.mjs';
|
|
6
|
+
import { generateId, decodeVectorFilePayload, bytesToVectors, isDead, FILTER_EXPRESSION_KEYS, FIELD_FILTER_OPERATORS } from './chunk-THMSFOJO.mjs';
|
|
7
|
+
import { getLanguage } from './chunk-4MQ5F4IL.mjs';
|
|
8
|
+
import { now, listBindingOf, queryBindingOf, hitsKeptPerGroup, clampLimit, resolveHybridFusion, dedupePinnedEntries, foldGroupReducer } from './chunk-ZULSEZCE.mjs';
|
|
9
|
+
import { oversampledShardSize, resolveProjection, clampRowCount, sortSignatureOf, requireMatchingCursor, decodePageCursor, normalizeSort, sortModesOf, encodePageCursor, applyProjection, readSortValues, requireWithinResultWindow, fnv1a, sortSignatureEntry, MAX_FILTER_DEPTH, filterTestCount, MAX_FILTER_TESTS, fnv1aUint32s, mergePartitionStats, MAX_FILTER_ARRAY_SIZE } from './chunk-3YKWSO54.mjs';
|
|
10
|
+
import { DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, validateSchema, requireCreatableSchema, DEFAULT_PAGE_SIZE, encodeDocument, MAX_SUGGEST_SCATTER_LIMIT, MAX_INDEX_NAME_LENGTH, MAX_SORT_FIELDS, MAX_DOC_ID_LENGTH, MAX_CURSOR_LENGTH, MAX_TOLERANCE, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, INDEX_NAME_PATTERN, MAX_PREFIX_LENGTH, RESULT_WINDOW, MAX_STORED_VECTOR_DIMENSION, MIN_POLYGON_POINTS, isLatitude, MAX_LATITUDE_DEGREES, isLongitude, MAX_LONGITUDE_DEGREES, MAX_NESTING_DEPTH } from './chunk-AK3BLGCI.mjs';
|
|
12
11
|
import './chunk-TGP5M5R7.mjs';
|
|
13
|
-
import './chunk-
|
|
12
|
+
import './chunk-WVDFG45M.mjs';
|
|
14
13
|
import { MIN_OVERSAMPLE } from './chunk-ZJ4CSAB2.mjs';
|
|
15
|
-
import './chunk-
|
|
16
|
-
import
|
|
17
|
-
import './chunk-
|
|
14
|
+
import './chunk-PU43WDY4.mjs';
|
|
15
|
+
import './chunk-XW25X6AU.mjs';
|
|
16
|
+
import './chunk-ZKRV5M2P.mjs';
|
|
18
17
|
import './chunk-U2BKYEC7.mjs';
|
|
19
|
-
import { compareCodePoints, toComparableSortValue, compareComparableKeys, isSortMode, SORT_MODES, compareSortValues } from './chunk-
|
|
18
|
+
import { compareCodePoints, toComparableSortValue, compareComparableKeys, isSortMode, SORT_MODES, exceedsCodePoints, compareSortValues } from './chunk-LJYTW7GT.mjs';
|
|
20
19
|
import './chunk-POWOTUKJ.mjs';
|
|
21
|
-
import { ErrorCodes, NarsilError, describeError } from './chunk-
|
|
20
|
+
import { ErrorCodes, NarsilError, describeError } from './chunk-I64LXKO3.mjs';
|
|
22
21
|
import { encode, decode } from '@msgpack/msgpack';
|
|
22
|
+
import { randomUUID } from 'crypto';
|
|
23
|
+
import { openSync, writeSync, closeSync, rmSync, readSync } from 'fs';
|
|
24
|
+
import { mkdtemp, rm, mkdir } from 'fs/promises';
|
|
25
|
+
import { tmpdir } from 'os';
|
|
26
|
+
import { join } from 'path';
|
|
23
27
|
|
|
24
28
|
// src/distribution/cluster/constants.ts
|
|
25
29
|
var LEADERSHIP_IMBALANCE_THRESHOLD = 2;
|
|
@@ -1249,7 +1253,6 @@ var REPLICATION_ENTRY_FIXED_OVERHEAD_BYTES = 40;
|
|
|
1249
1253
|
var DEFAULT_LOG_RETENTION_BYTES = 268435456;
|
|
1250
1254
|
var INSYNC_CAS_ATTEMPTS = 5;
|
|
1251
1255
|
var SNAPSHOT_CHUNK_SIZE = 65536;
|
|
1252
|
-
var MAX_SNAPSHOT_SIZE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
1253
1256
|
|
|
1254
1257
|
// src/distribution/replication/insync.ts
|
|
1255
1258
|
var REFUSE = { outcome: "refuse" };
|
|
@@ -2514,6 +2517,119 @@ function createDataNodeLifecycle(config) {
|
|
|
2514
2517
|
};
|
|
2515
2518
|
return handle;
|
|
2516
2519
|
}
|
|
2520
|
+
var SPOOL_FILE_PREFIX = "narsil-snapshot-";
|
|
2521
|
+
var DURABLE_SPOOL_DIRECTORY = ".snapshot-transfer";
|
|
2522
|
+
var TEMPORARY_SPOOL_DIRECTORY_PREFIX = "narsil-snapshot-transfer-";
|
|
2523
|
+
var OWNER_ONLY_FILE_MODE = 384;
|
|
2524
|
+
var OWNER_ONLY_DIRECTORY_MODE = 448;
|
|
2525
|
+
function defaultSpoolDirectory() {
|
|
2526
|
+
return tmpdir();
|
|
2527
|
+
}
|
|
2528
|
+
function spoolFilePath(directory) {
|
|
2529
|
+
return join(directory, `${SPOOL_FILE_PREFIX}${randomUUID()}`);
|
|
2530
|
+
}
|
|
2531
|
+
async function* readSpoolFile(path) {
|
|
2532
|
+
const descriptor = openSync(path, "r");
|
|
2533
|
+
try {
|
|
2534
|
+
for (; ; ) {
|
|
2535
|
+
const buffer = new Uint8Array(SNAPSHOT_CHUNK_SIZE);
|
|
2536
|
+
const bytesRead = readSync(descriptor, buffer, 0, buffer.byteLength, null);
|
|
2537
|
+
if (bytesRead === 0) return;
|
|
2538
|
+
yield bytesRead === buffer.byteLength ? buffer : buffer.subarray(0, bytesRead);
|
|
2539
|
+
}
|
|
2540
|
+
} finally {
|
|
2541
|
+
closeSync(descriptor);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
function spooledFile(path, totalBytes, checksum) {
|
|
2545
|
+
let discarded = false;
|
|
2546
|
+
return {
|
|
2547
|
+
totalBytes,
|
|
2548
|
+
checksum,
|
|
2549
|
+
read: () => readSpoolFile(path),
|
|
2550
|
+
async discard() {
|
|
2551
|
+
if (discarded) return;
|
|
2552
|
+
discarded = true;
|
|
2553
|
+
removeQuietly(path);
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
function openSnapshotSpoolWriter(directory) {
|
|
2558
|
+
const path = spoolFilePath(directory);
|
|
2559
|
+
let descriptor = openSync(path, "wx", OWNER_ONLY_FILE_MODE);
|
|
2560
|
+
let checksumState = crc32Init();
|
|
2561
|
+
let totalBytes = 0;
|
|
2562
|
+
const close = () => {
|
|
2563
|
+
if (descriptor === null) return;
|
|
2564
|
+
const closing = descriptor;
|
|
2565
|
+
descriptor = null;
|
|
2566
|
+
closeSync(closing);
|
|
2567
|
+
};
|
|
2568
|
+
return {
|
|
2569
|
+
write(bytes) {
|
|
2570
|
+
if (descriptor === null) {
|
|
2571
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "The snapshot spool file is already closed", {
|
|
2572
|
+
path
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
let offset = 0;
|
|
2576
|
+
while (offset < bytes.byteLength) {
|
|
2577
|
+
offset += writeSync(descriptor, bytes, offset, bytes.byteLength - offset);
|
|
2578
|
+
}
|
|
2579
|
+
checksumState = crc32Update(checksumState, bytes);
|
|
2580
|
+
totalBytes += bytes.byteLength;
|
|
2581
|
+
},
|
|
2582
|
+
checksum() {
|
|
2583
|
+
return crc32Final(checksumState);
|
|
2584
|
+
},
|
|
2585
|
+
finish() {
|
|
2586
|
+
close();
|
|
2587
|
+
return spooledFile(path, totalBytes, crc32Final(checksumState));
|
|
2588
|
+
},
|
|
2589
|
+
discard() {
|
|
2590
|
+
try {
|
|
2591
|
+
close();
|
|
2592
|
+
} finally {
|
|
2593
|
+
removeQuietly(path);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
function removeQuietly(path) {
|
|
2599
|
+
try {
|
|
2600
|
+
rmSync(path, { force: true });
|
|
2601
|
+
} catch (_) {
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
async function spoolSnapshotStream(directory, stream) {
|
|
2605
|
+
const reader = stream.getReader();
|
|
2606
|
+
let writer = null;
|
|
2607
|
+
try {
|
|
2608
|
+
writer = openSnapshotSpoolWriter(directory);
|
|
2609
|
+
for (; ; ) {
|
|
2610
|
+
const step = await reader.read();
|
|
2611
|
+
if (step.done) break;
|
|
2612
|
+
if (step.value.byteLength > 0) writer.write(step.value);
|
|
2613
|
+
}
|
|
2614
|
+
const spooled = writer.finish();
|
|
2615
|
+
writer = null;
|
|
2616
|
+
return spooled;
|
|
2617
|
+
} catch (err) {
|
|
2618
|
+
writer?.discard();
|
|
2619
|
+
await reader.cancel().catch(() => void 0);
|
|
2620
|
+
throw err;
|
|
2621
|
+
} finally {
|
|
2622
|
+
reader.releaseLock();
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
async function openSnapshotSpoolDirectory(durabilityDirectory) {
|
|
2626
|
+
const path = durabilityDirectory === void 0 ? await mkdtemp(join(tmpdir(), TEMPORARY_SPOOL_DIRECTORY_PREFIX)) : join(durabilityDirectory, DURABLE_SPOOL_DIRECTORY);
|
|
2627
|
+
if (durabilityDirectory !== void 0) {
|
|
2628
|
+
await rm(path, { recursive: true, force: true });
|
|
2629
|
+
await mkdir(path, { recursive: true, mode: OWNER_ONLY_DIRECTORY_MODE });
|
|
2630
|
+
}
|
|
2631
|
+
return { path, dispose: () => rm(path, { recursive: true, force: true }) };
|
|
2632
|
+
}
|
|
2517
2633
|
|
|
2518
2634
|
// src/distribution/cluster-node/local-index-gone.ts
|
|
2519
2635
|
function localIndexIsGone(err) {
|
|
@@ -2581,6 +2697,19 @@ function fail(state, code, message, details = {}) {
|
|
|
2581
2697
|
return;
|
|
2582
2698
|
}
|
|
2583
2699
|
state.failure = { ok: false, code, message, details };
|
|
2700
|
+
releaseSpool(state);
|
|
2701
|
+
}
|
|
2702
|
+
function releaseSpool(state) {
|
|
2703
|
+
const spool = state.spool;
|
|
2704
|
+
if (spool === null) return;
|
|
2705
|
+
state.spool = null;
|
|
2706
|
+
spool.discard();
|
|
2707
|
+
}
|
|
2708
|
+
function spoolFailure(state, err) {
|
|
2709
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
2710
|
+
fail(state, ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, `the snapshot could not be written to local storage: ${cause}`, {
|
|
2711
|
+
cause
|
|
2712
|
+
});
|
|
2584
2713
|
}
|
|
2585
2714
|
function applyErrorEnvelope(state, record) {
|
|
2586
2715
|
const code = typeof record.code === "string" && record.code.length > 0 ? record.code : "UNKNOWN_ERROR";
|
|
@@ -2631,19 +2760,17 @@ function applyStartPayload(state, record) {
|
|
|
2631
2760
|
fail(state, ErrorCodes.SNAPSHOT_SYNC_HEADER_INVALID, "totalBytes must be a non-negative integer");
|
|
2632
2761
|
return;
|
|
2633
2762
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
});
|
|
2763
|
+
try {
|
|
2764
|
+
state.spool = openSnapshotSpoolWriter(state.expectation.spoolDirectory);
|
|
2765
|
+
} catch (err) {
|
|
2766
|
+
spoolFailure(state, err);
|
|
2639
2767
|
return;
|
|
2640
2768
|
}
|
|
2641
2769
|
state.header = header;
|
|
2642
2770
|
state.totalBytes = start.totalBytes;
|
|
2643
|
-
state.assembled = new Uint8Array(start.totalBytes);
|
|
2644
2771
|
}
|
|
2645
2772
|
function applyChunkPayload(state, payload) {
|
|
2646
|
-
if (state.totalBytes === null || state.
|
|
2773
|
+
if (state.totalBytes === null || state.header === null) {
|
|
2647
2774
|
fail(state, ErrorCodes.SNAPSHOT_SYNC_FRAME_INVALID, "received chunk before SNAPSHOT_START");
|
|
2648
2775
|
return;
|
|
2649
2776
|
}
|
|
@@ -2697,7 +2824,16 @@ function applyChunkPayload(state, payload) {
|
|
|
2697
2824
|
});
|
|
2698
2825
|
return;
|
|
2699
2826
|
}
|
|
2700
|
-
state.
|
|
2827
|
+
if (state.spool === null) {
|
|
2828
|
+
fail(state, ErrorCodes.SNAPSHOT_SYNC_FRAME_INVALID, "received chunk after the snapshot stream closed");
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
try {
|
|
2832
|
+
state.spool.write(payload.data);
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
spoolFailure(state, err);
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2701
2837
|
state.accumulatedBytes = nextBytes;
|
|
2702
2838
|
state.expectedNextOffset = payload.offset + payload.data.byteLength;
|
|
2703
2839
|
}
|
|
@@ -2775,7 +2911,7 @@ function createSnapshotStreamState(expectation) {
|
|
|
2775
2911
|
expectation,
|
|
2776
2912
|
header: null,
|
|
2777
2913
|
totalBytes: null,
|
|
2778
|
-
|
|
2914
|
+
spool: null,
|
|
2779
2915
|
accumulatedBytes: 0,
|
|
2780
2916
|
expectedNextOffset: 0,
|
|
2781
2917
|
endPayload: null,
|
|
@@ -2830,11 +2966,20 @@ function handleIncomingSnapshotRecord(state, record) {
|
|
|
2830
2966
|
}
|
|
2831
2967
|
fail(state, ErrorCodes.SNAPSHOT_SYNC_FRAME_INVALID, "unrecognised snapshot frame shape");
|
|
2832
2968
|
}
|
|
2969
|
+
function abandonSnapshotStream(state) {
|
|
2970
|
+
fail(state, ErrorCodes.SNAPSHOT_SYNC_ABORTED, "the snapshot stream was abandoned before it finished");
|
|
2971
|
+
releaseSpool(state);
|
|
2972
|
+
}
|
|
2833
2973
|
function finalizeSnapshotStream(state) {
|
|
2974
|
+
const result = settleSnapshotStream(state);
|
|
2975
|
+
if (!result.ok) releaseSpool(state);
|
|
2976
|
+
return result;
|
|
2977
|
+
}
|
|
2978
|
+
function settleSnapshotStream(state) {
|
|
2834
2979
|
if (state.failure !== null) {
|
|
2835
2980
|
return state.failure;
|
|
2836
2981
|
}
|
|
2837
|
-
if (state.header === null || state.totalBytes === null
|
|
2982
|
+
if (state.header === null || state.totalBytes === null) {
|
|
2838
2983
|
return {
|
|
2839
2984
|
ok: false,
|
|
2840
2985
|
code: ErrorCodes.SNAPSHOT_SYNC_CHUNK_MISSING,
|
|
@@ -2866,9 +3011,28 @@ function finalizeSnapshotStream(state) {
|
|
|
2866
3011
|
}
|
|
2867
3012
|
};
|
|
2868
3013
|
}
|
|
3014
|
+
const spool = state.spool;
|
|
3015
|
+
if (spool === null) {
|
|
3016
|
+
return {
|
|
3017
|
+
ok: false,
|
|
3018
|
+
code: ErrorCodes.SNAPSHOT_SYNC_FRAME_INVALID,
|
|
3019
|
+
message: "the snapshot stream was already settled",
|
|
3020
|
+
details: { indexName: state.expectation.indexName }
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
const computed = spool.checksum();
|
|
3024
|
+
if (computed !== state.header.checksum) {
|
|
3025
|
+
return {
|
|
3026
|
+
ok: false,
|
|
3027
|
+
code: ErrorCodes.SNAPSHOT_SYNC_CHECKSUM_MISMATCH,
|
|
3028
|
+
message: "computed checksum does not match header checksum",
|
|
3029
|
+
details: { computed, header: state.header.checksum }
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
state.spool = null;
|
|
2869
3033
|
return {
|
|
2870
3034
|
ok: true,
|
|
2871
|
-
|
|
3035
|
+
snapshot: spool.finish(),
|
|
2872
3036
|
header: state.header,
|
|
2873
3037
|
end: state.endPayload
|
|
2874
3038
|
};
|
|
@@ -3079,7 +3243,11 @@ async function fetchSnapshotFromTarget(indexName, partitionId, target, deadline,
|
|
|
3079
3243
|
requestId: generateId(),
|
|
3080
3244
|
payload: encode(requestPayload)
|
|
3081
3245
|
};
|
|
3082
|
-
const streamState = createSnapshotStreamState({
|
|
3246
|
+
const streamState = createSnapshotStreamState({
|
|
3247
|
+
indexName,
|
|
3248
|
+
partitionId: null,
|
|
3249
|
+
spoolDirectory: deps.spoolDirectory ?? defaultSpoolDirectory()
|
|
3250
|
+
});
|
|
3083
3251
|
const remainingMs = deadline - Date.now();
|
|
3084
3252
|
if (remainingMs <= 0) {
|
|
3085
3253
|
return {
|
|
@@ -3095,6 +3263,7 @@ async function fetchSnapshotFromTarget(indexName, partitionId, target, deadline,
|
|
|
3095
3263
|
});
|
|
3096
3264
|
await withDeadline(streamWork, remainingMs, indexName, "stream");
|
|
3097
3265
|
} catch (err) {
|
|
3266
|
+
abandonSnapshotStream(streamState);
|
|
3098
3267
|
if (err instanceof NarsilError && err.code === ErrorCodes.SNAPSHOT_SYNC_TIMEOUT) {
|
|
3099
3268
|
return toStreamFailure(err, ErrorCodes.SNAPSHOT_SYNC_TIMEOUT, "snapshot stream deadline exceeded");
|
|
3100
3269
|
}
|
|
@@ -3103,20 +3272,7 @@ async function fetchSnapshotFromTarget(indexName, partitionId, target, deadline,
|
|
|
3103
3272
|
}
|
|
3104
3273
|
return toStreamFailure(err, ErrorCodes.SNAPSHOT_SYNC_PRIMARY_ERROR, "snapshot fetch raised non-transport error");
|
|
3105
3274
|
}
|
|
3106
|
-
|
|
3107
|
-
if (!result.ok) {
|
|
3108
|
-
return result;
|
|
3109
|
-
}
|
|
3110
|
-
const computedChecksum = crc32(result.bytes);
|
|
3111
|
-
if (computedChecksum !== result.header.checksum) {
|
|
3112
|
-
return {
|
|
3113
|
-
ok: false,
|
|
3114
|
-
code: ErrorCodes.SNAPSHOT_SYNC_CHECKSUM_MISMATCH,
|
|
3115
|
-
message: "computed checksum does not match header checksum",
|
|
3116
|
-
details: { computed: computedChecksum, header: result.header.checksum }
|
|
3117
|
-
};
|
|
3118
|
-
}
|
|
3119
|
-
return result;
|
|
3275
|
+
return finalizeSnapshotStream(streamState);
|
|
3120
3276
|
}
|
|
3121
3277
|
async function withDeadline(work, ms, indexName, phase) {
|
|
3122
3278
|
let timer = null;
|
|
@@ -3265,9 +3421,9 @@ function surfaceAborted(deps, indexName, primaryNodeId) {
|
|
|
3265
3421
|
})
|
|
3266
3422
|
);
|
|
3267
3423
|
}
|
|
3268
|
-
async function executeEngineRestore(engine, indexName, primaryNodeId,
|
|
3424
|
+
async function executeEngineRestore(engine, indexName, primaryNodeId, source, remainingMs) {
|
|
3269
3425
|
try {
|
|
3270
|
-
await withDeadline(engine.restore(indexName,
|
|
3426
|
+
await withDeadline(engine.restore(indexName, source), remainingMs, indexName, "restore");
|
|
3271
3427
|
return null;
|
|
3272
3428
|
} catch (err) {
|
|
3273
3429
|
if (err instanceof NarsilError && err.code === ErrorCodes.SNAPSHOT_SYNC_TIMEOUT) {
|
|
@@ -3553,9 +3709,9 @@ function validateSyncEntryForApply(entry, indexName, partitionId, expectedSeqNo,
|
|
|
3553
3709
|
}
|
|
3554
3710
|
return null;
|
|
3555
3711
|
}
|
|
3556
|
-
function createLiveSyncFrameState(indexName, partitionId) {
|
|
3712
|
+
function createLiveSyncFrameState(indexName, partitionId, spoolDirectory) {
|
|
3557
3713
|
return {
|
|
3558
|
-
snapshotState: createSnapshotStreamState({ indexName, partitionId }),
|
|
3714
|
+
snapshotState: createSnapshotStreamState({ indexName, partitionId, spoolDirectory }),
|
|
3559
3715
|
syncEntries: [],
|
|
3560
3716
|
sawSnapshotFrame: false,
|
|
3561
3717
|
error: null
|
|
@@ -3657,35 +3813,25 @@ async function applyLiveIncrementalSync(indexName, partitionId, target, entriesP
|
|
|
3657
3813
|
async function applyLiveSnapshotSync(state, entry, indexName, partitionId, target, frameState, coordinatorConfig, partitionCount, deadline, deps) {
|
|
3658
3814
|
const finalized = finalizeSnapshotStream(frameState.snapshotState);
|
|
3659
3815
|
if (!finalized.ok) {
|
|
3660
|
-
return { ok: false, error: streamFailureToError(finalized) };
|
|
3816
|
+
return { ok: false, error: streamFailureToError({ ...finalized, details: { ...finalized.details, target } }) };
|
|
3661
3817
|
}
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3818
|
+
let restoreError;
|
|
3819
|
+
try {
|
|
3820
|
+
restoreError = await restoreLiveSnapshotPartition(
|
|
3821
|
+
state,
|
|
3822
|
+
entry,
|
|
3823
|
+
indexName,
|
|
3824
|
+
partitionId,
|
|
3825
|
+
target,
|
|
3826
|
+
finalized.snapshot.read(),
|
|
3827
|
+
coordinatorConfig,
|
|
3828
|
+
partitionCount,
|
|
3829
|
+
deadline,
|
|
3830
|
+
deps
|
|
3831
|
+
);
|
|
3832
|
+
} finally {
|
|
3833
|
+
await finalized.snapshot.discard();
|
|
3676
3834
|
}
|
|
3677
|
-
const restoreError = await restoreLiveSnapshotPartition(
|
|
3678
|
-
state,
|
|
3679
|
-
entry,
|
|
3680
|
-
indexName,
|
|
3681
|
-
partitionId,
|
|
3682
|
-
target,
|
|
3683
|
-
finalized.bytes,
|
|
3684
|
-
coordinatorConfig,
|
|
3685
|
-
partitionCount,
|
|
3686
|
-
deadline,
|
|
3687
|
-
deps
|
|
3688
|
-
);
|
|
3689
3835
|
if (restoreError !== null) {
|
|
3690
3836
|
return { ok: false, error: restoreError };
|
|
3691
3837
|
}
|
|
@@ -3709,7 +3855,7 @@ async function applyLiveSnapshotSync(state, entry, indexName, partitionId, targe
|
|
|
3709
3855
|
snapshotHeader: finalized.header
|
|
3710
3856
|
};
|
|
3711
3857
|
}
|
|
3712
|
-
async function restoreLiveSnapshotPartition(state, entry, indexName, partitionId, primaryNodeId,
|
|
3858
|
+
async function restoreLiveSnapshotPartition(state, entry, indexName, partitionId, primaryNodeId, source, coordinatorConfig, partitionCount, deadline, deps) {
|
|
3713
3859
|
const remainingMs = deadline - Date.now();
|
|
3714
3860
|
if (remainingMs <= 0) {
|
|
3715
3861
|
return new NarsilError(
|
|
@@ -3733,7 +3879,7 @@ async function restoreLiveSnapshotPartition(state, entry, indexName, partitionId
|
|
|
3733
3879
|
}
|
|
3734
3880
|
try {
|
|
3735
3881
|
await withDeadline(
|
|
3736
|
-
deps.restoreReplicationPartition(indexName, partitionId,
|
|
3882
|
+
deps.restoreReplicationPartition(indexName, partitionId, source, coordinatorConfig, partitionCount),
|
|
3737
3883
|
remainingMs,
|
|
3738
3884
|
indexName,
|
|
3739
3885
|
"partition-restore"
|
|
@@ -3925,6 +4071,27 @@ async function syncFromAnyTarget(state, entry, indexName, partitionId, primaryNo
|
|
|
3925
4071
|
return { ok: false, error: lastError };
|
|
3926
4072
|
}
|
|
3927
4073
|
async function syncFromTarget(state, entry, indexName, partitionId, target, coordinatorConfig, partitionCount, deadline, deps, abortCheck) {
|
|
4074
|
+
const spoolDirectory = deps.snapshotSpoolDirectory ?? defaultSpoolDirectory();
|
|
4075
|
+
const frameState = createLiveSyncFrameState(indexName, partitionId, spoolDirectory);
|
|
4076
|
+
try {
|
|
4077
|
+
return await syncFrameStateFromTarget(
|
|
4078
|
+
state,
|
|
4079
|
+
entry,
|
|
4080
|
+
indexName,
|
|
4081
|
+
partitionId,
|
|
4082
|
+
target,
|
|
4083
|
+
coordinatorConfig,
|
|
4084
|
+
partitionCount,
|
|
4085
|
+
deadline,
|
|
4086
|
+
deps,
|
|
4087
|
+
abortCheck,
|
|
4088
|
+
frameState
|
|
4089
|
+
);
|
|
4090
|
+
} finally {
|
|
4091
|
+
abandonSnapshotStream(frameState.snapshotState);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
async function syncFrameStateFromTarget(state, entry, indexName, partitionId, target, coordinatorConfig, partitionCount, deadline, deps, abortCheck, frameState) {
|
|
3928
4095
|
const logState = readLocalLogState(indexName, partitionId, deps);
|
|
3929
4096
|
const requestPayload = {
|
|
3930
4097
|
indexName,
|
|
@@ -3938,7 +4105,6 @@ async function syncFromTarget(state, entry, indexName, partitionId, target, coor
|
|
|
3938
4105
|
requestId: generateId(),
|
|
3939
4106
|
payload: encode(requestPayload)
|
|
3940
4107
|
};
|
|
3941
|
-
const frameState = createLiveSyncFrameState(indexName, partitionId);
|
|
3942
4108
|
const remainingMs = deadline - Date.now();
|
|
3943
4109
|
if (remainingMs <= 0) {
|
|
3944
4110
|
return {
|
|
@@ -4170,7 +4336,7 @@ async function fetchSchemaAndPrepare(entry, indexName, primaryNodeId, deps) {
|
|
|
4170
4336
|
}
|
|
4171
4337
|
return configResult.config.schema;
|
|
4172
4338
|
}
|
|
4173
|
-
async function applyRestore(state, entry, indexName, primaryNodeId,
|
|
4339
|
+
async function applyRestore(state, entry, indexName, primaryNodeId, source, coordinatorSchema, deadline, deps) {
|
|
4174
4340
|
const remainingMs = deadline - Date.now();
|
|
4175
4341
|
if (remainingMs <= 0) {
|
|
4176
4342
|
surfaceError(
|
|
@@ -4189,7 +4355,7 @@ async function applyRestore(state, entry, indexName, primaryNodeId, bytes, coord
|
|
|
4189
4355
|
surfaceAborted(deps, indexName, primaryNodeId);
|
|
4190
4356
|
return false;
|
|
4191
4357
|
}
|
|
4192
|
-
const restoreError = await executeEngineRestore(deps.engine, indexName, primaryNodeId,
|
|
4358
|
+
const restoreError = await executeEngineRestore(deps.engine, indexName, primaryNodeId, source, remainingMs);
|
|
4193
4359
|
if (restoreError !== null) {
|
|
4194
4360
|
surfaceError(deps, indexName, primaryNodeId, restoreError);
|
|
4195
4361
|
return false;
|
|
@@ -4310,7 +4476,11 @@ async function executeBootstrapSync(state, entry, indexName, partitionId, primar
|
|
|
4310
4476
|
surfaceError(deps, indexName, primaryNodeId, targetsResult);
|
|
4311
4477
|
return false;
|
|
4312
4478
|
}
|
|
4313
|
-
const fetchDeps = {
|
|
4479
|
+
const fetchDeps = {
|
|
4480
|
+
transport: deps.transport,
|
|
4481
|
+
sourceNodeId: deps.sourceNodeId,
|
|
4482
|
+
spoolDirectory: deps.snapshotSpoolDirectory
|
|
4483
|
+
};
|
|
4314
4484
|
const fetchResult = await fetchSnapshotFromAnyTarget(
|
|
4315
4485
|
indexName,
|
|
4316
4486
|
primaryNodeId,
|
|
@@ -4337,20 +4507,25 @@ async function executeBootstrapSync(state, entry, indexName, partitionId, primar
|
|
|
4337
4507
|
);
|
|
4338
4508
|
return false;
|
|
4339
4509
|
}
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4510
|
+
let restoreSucceeded;
|
|
4511
|
+
try {
|
|
4512
|
+
if (abortCheck()) {
|
|
4513
|
+
surfaceAborted(deps, indexName, primaryNodeId);
|
|
4514
|
+
return false;
|
|
4515
|
+
}
|
|
4516
|
+
restoreSucceeded = await applyRestore(
|
|
4517
|
+
state,
|
|
4518
|
+
entry,
|
|
4519
|
+
indexName,
|
|
4520
|
+
primaryNodeId,
|
|
4521
|
+
fetchResult.snapshot.read(),
|
|
4522
|
+
schemaResult,
|
|
4523
|
+
deadline,
|
|
4524
|
+
deps
|
|
4525
|
+
);
|
|
4526
|
+
} finally {
|
|
4527
|
+
await fetchResult.snapshot.discard();
|
|
4343
4528
|
}
|
|
4344
|
-
const restoreSucceeded = await applyRestore(
|
|
4345
|
-
state,
|
|
4346
|
-
entry,
|
|
4347
|
-
indexName,
|
|
4348
|
-
primaryNodeId,
|
|
4349
|
-
fetchResult.bytes,
|
|
4350
|
-
schemaResult,
|
|
4351
|
-
deadline,
|
|
4352
|
-
deps
|
|
4353
|
-
);
|
|
4354
4529
|
if (!restoreSucceeded) {
|
|
4355
4530
|
return false;
|
|
4356
4531
|
}
|
|
@@ -5214,173 +5389,40 @@ async function applyLocalReplicationEntry(core, entry) {
|
|
|
5214
5389
|
}
|
|
5215
5390
|
}
|
|
5216
5391
|
|
|
5217
|
-
// src/distribution/cluster-node/
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
const runnableConfig = await resolveRunnableConfig(config);
|
|
5221
|
-
const core = createEngineCore(runnableConfig, hooks, "partition");
|
|
5222
|
-
await startEngineCore(core, runnableConfig);
|
|
5223
|
-
core.orchestrator.shareMainThread();
|
|
5224
|
-
const engine = createNarsilFromCore(core, runnableConfig);
|
|
5225
|
-
const heldPartitions = createHeldPartitionRecord(core);
|
|
5226
|
-
return Object.assign(engine, {
|
|
5227
|
-
createIndex: (name, indexConfig) => createEngineIndex(core, runnableConfig, name, indexConfig, { adapterBinding: "deferred" }),
|
|
5228
|
-
createIndexWithUuid: (name, indexConfig, indexUuid) => createEngineIndex(core, runnableConfig, name, indexConfig, { indexUuid }),
|
|
5229
|
-
acquireIndexForReplication: (indexName) => core.indexState.acquire(indexName, false),
|
|
5230
|
-
indexUuidOf: (indexName) => core.indexRegistry.get(indexName)?.indexUuid,
|
|
5231
|
-
stampIndexUuid: (indexName, indexUuid) => stampIndexUuid(core, indexName, indexUuid),
|
|
5232
|
-
highestPersistedSeqNoOf: (indexName, partitionId) => core.durability?.manager.highestPersistedSeqNo(indexName, partitionId) ?? 0,
|
|
5233
|
-
heldPartitionsOf: (indexName) => heldPartitions.held(indexName),
|
|
5234
|
-
recordHeldPartition: (indexName, partitionId) => heldPartitions.record(indexName, partitionId),
|
|
5235
|
-
forgetHeldPartition: (indexName, partitionId) => heldPartitions.forget(indexName, partitionId),
|
|
5236
|
-
applyReplicationEntry: (entry) => applyLocalReplicationEntry(core, entry),
|
|
5237
|
-
serializeReplicationPartition: (indexName, partitionId) => serializeReplicationPartition(core, indexName, partitionId),
|
|
5238
|
-
restoreReplicationPartition: (indexName, partitionId, bytes, indexConfig, partitionCount) => restoreReplicationPartition(core, engine, indexName, partitionId, bytes, indexConfig, partitionCount),
|
|
5239
|
-
queryPartitions: (indexName, params, partitionIds, globalStats) => runEngineQuery(core, indexName, params, { partitionIds, globalStats }),
|
|
5240
|
-
preflightPartitions: (indexName, params, partitionIds) => runEnginePreflight(core, indexName, params, { partitionIds }),
|
|
5241
|
-
suggestPartitions: (indexName, params, partitionIds) => runEngineSuggest(core, indexName, params, partitionIds),
|
|
5242
|
-
listPartitions: (indexName, params, partitionIds) => runEngineListDocuments(core, indexName, params, partitionIds),
|
|
5243
|
-
collectQueryStats: (indexName, terms, partitionIds) => runEngineQueryStats(core, indexName, terms, partitionIds),
|
|
5244
|
-
partitionStatsForRead: async (indexName) => {
|
|
5245
|
-
const release = await core.indexState.acquire(indexName);
|
|
5246
|
-
try {
|
|
5247
|
-
return core.requireManager(indexName).getPartitionStats();
|
|
5248
|
-
} finally {
|
|
5249
|
-
release();
|
|
5250
|
-
}
|
|
5251
|
-
}
|
|
5252
|
-
});
|
|
5253
|
-
}
|
|
5254
|
-
async function stampIndexUuid(core, indexName, indexUuid) {
|
|
5255
|
-
const entry = core.indexRegistry.get(indexName);
|
|
5256
|
-
if (entry === void 0 || entry.indexUuid === indexUuid) {
|
|
5257
|
-
return;
|
|
5258
|
-
}
|
|
5259
|
-
core.indexRegistry.set(indexName, { ...entry, indexUuid });
|
|
5260
|
-
if (core.durability !== null) {
|
|
5261
|
-
await core.durability.manager.persistMetadata(indexName);
|
|
5262
|
-
}
|
|
5263
|
-
}
|
|
5264
|
-
async function serializeReplicationPartition(core, indexName, partitionId) {
|
|
5265
|
-
core.guardShutdown();
|
|
5266
|
-
const release = await core.indexState.acquire(indexName, false);
|
|
5267
|
-
try {
|
|
5268
|
-
return core.requireManager(indexName).serializePartitionToBytes(partitionId);
|
|
5269
|
-
} finally {
|
|
5270
|
-
release();
|
|
5271
|
-
}
|
|
5392
|
+
// src/distribution/cluster-node/replication-index.ts
|
|
5393
|
+
function isNestedSchema(value) {
|
|
5394
|
+
return typeof value === "object" && value !== null;
|
|
5272
5395
|
}
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
partition = deserializePayloadV2(bytes);
|
|
5280
|
-
} catch (err) {
|
|
5281
|
-
const cause = err instanceof Error ? err.message : String(err);
|
|
5282
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, `partition snapshot decode failed: ${cause}`, {
|
|
5283
|
-
indexName,
|
|
5284
|
-
partitionId,
|
|
5285
|
-
cause
|
|
5286
|
-
});
|
|
5287
|
-
}
|
|
5288
|
-
validatePartitionSnapshotPayload(indexName, partitionId, partitionCount, schema, partition);
|
|
5289
|
-
const trackPositions = core.indexRegistry.get(indexName)?.config.trackPositions ?? true;
|
|
5290
|
-
createPartitionIndex(partitionId, trackPositions).deserialize(partition, schema);
|
|
5291
|
-
let release = core.indexRegistry.has(indexName) ? await core.indexState.acquire(indexName, false) : null;
|
|
5292
|
-
try {
|
|
5293
|
-
const restoreIndex = await ensureReplicationIndex(
|
|
5294
|
-
core,
|
|
5295
|
-
engine,
|
|
5296
|
-
indexName,
|
|
5297
|
-
indexConfig,
|
|
5298
|
-
partition.language,
|
|
5299
|
-
partitionCount
|
|
5300
|
-
);
|
|
5301
|
-
if (release === null) release = await core.indexState.acquire(indexName, false);
|
|
5302
|
-
if (restoreIndex.created) {
|
|
5303
|
-
await core.orchestrator.replicateToWorkers({
|
|
5304
|
-
type: "createIndex",
|
|
5305
|
-
indexName,
|
|
5306
|
-
config: restoreIndex.config,
|
|
5307
|
-
requestId: `replicate-partition-index-${indexName}`
|
|
5308
|
-
});
|
|
5396
|
+
function flattenSchemaInto(schema, prefix, flat) {
|
|
5397
|
+
for (const [field, value] of Object.entries(schema)) {
|
|
5398
|
+
const path = prefix.length === 0 ? field : `${prefix}.${field}`;
|
|
5399
|
+
if (isNestedSchema(value)) {
|
|
5400
|
+
flattenSchemaInto(value, path, flat);
|
|
5401
|
+
continue;
|
|
5309
5402
|
}
|
|
5310
|
-
|
|
5311
|
-
manager.deserializePartition(partitionId, partition);
|
|
5312
|
-
await core.orchestrator.replicateToWorkers({
|
|
5313
|
-
type: "deserialize",
|
|
5314
|
-
indexName,
|
|
5315
|
-
partitionId,
|
|
5316
|
-
data: partition,
|
|
5317
|
-
requestId: `replicate-partition-restore-${indexName}-${partitionId}`
|
|
5318
|
-
});
|
|
5319
|
-
} finally {
|
|
5320
|
-
release?.();
|
|
5403
|
+
flat[path] = value;
|
|
5321
5404
|
}
|
|
5322
5405
|
}
|
|
5323
|
-
function
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
partitionId
|
|
5328
|
-
});
|
|
5329
|
-
}
|
|
5330
|
-
if (!Number.isInteger(partitionCount) || partitionCount <= 0 || partitionCount > MAX_PARTITION_COUNT) {
|
|
5331
|
-
throw new NarsilError(
|
|
5332
|
-
ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED,
|
|
5333
|
-
`partitionCount must be an integer between 1 and ${MAX_PARTITION_COUNT}`,
|
|
5334
|
-
{ indexName, partitionCount }
|
|
5335
|
-
);
|
|
5336
|
-
}
|
|
5337
|
-
if (partitionId >= partitionCount) {
|
|
5338
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partitionId is outside partitionCount", {
|
|
5339
|
-
indexName,
|
|
5340
|
-
partitionId,
|
|
5341
|
-
partitionCount
|
|
5342
|
-
});
|
|
5343
|
-
}
|
|
5406
|
+
function flattenSchema(schema) {
|
|
5407
|
+
const flat = {};
|
|
5408
|
+
flattenSchemaInto(schema, "", flat);
|
|
5409
|
+
return flat;
|
|
5344
5410
|
}
|
|
5345
|
-
function
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
receivedIndexName: partition.indexName
|
|
5350
|
-
});
|
|
5351
|
-
}
|
|
5352
|
-
if (partition.partitionId !== partitionId) {
|
|
5353
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot partitionId mismatch", {
|
|
5354
|
-
indexName,
|
|
5355
|
-
expectedPartitionId: partitionId,
|
|
5356
|
-
receivedPartitionId: partition.partitionId
|
|
5357
|
-
});
|
|
5358
|
-
}
|
|
5359
|
-
if (partition.totalPartitions !== partitionCount) {
|
|
5360
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot partition count mismatch", {
|
|
5361
|
-
indexName,
|
|
5362
|
-
partitionId,
|
|
5363
|
-
expectedPartitionCount: partitionCount,
|
|
5364
|
-
receivedPartitionCount: partition.totalPartitions
|
|
5365
|
-
});
|
|
5366
|
-
}
|
|
5367
|
-
const expectedSchema = flattenSchema(schema);
|
|
5368
|
-
for (const [field, expectedType] of Object.entries(expectedSchema)) {
|
|
5369
|
-
if (partition.schema[field] !== expectedType) {
|
|
5370
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot schema mismatch", {
|
|
5411
|
+
function requireSameFlatSchema(indexName, expected, actual, what) {
|
|
5412
|
+
for (const [field, expectedType] of Object.entries(expected)) {
|
|
5413
|
+
if (actual[field] !== expectedType) {
|
|
5414
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, `${what} schema mismatch`, {
|
|
5371
5415
|
indexName,
|
|
5372
|
-
partitionId,
|
|
5373
5416
|
field,
|
|
5374
5417
|
expected: expectedType,
|
|
5375
|
-
received:
|
|
5418
|
+
received: actual[field] ?? "(absent)"
|
|
5376
5419
|
});
|
|
5377
5420
|
}
|
|
5378
5421
|
}
|
|
5379
|
-
for (const field of Object.keys(
|
|
5380
|
-
if (!(field in
|
|
5381
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED,
|
|
5422
|
+
for (const field of Object.keys(actual)) {
|
|
5423
|
+
if (!(field in expected)) {
|
|
5424
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, `${what} schema contains extra field`, {
|
|
5382
5425
|
indexName,
|
|
5383
|
-
partitionId,
|
|
5384
5426
|
field
|
|
5385
5427
|
});
|
|
5386
5428
|
}
|
|
@@ -5412,7 +5454,12 @@ async function ensureReplicationIndex(core, engine, indexName, coordinatorConfig
|
|
|
5412
5454
|
receivedLanguage: language.name
|
|
5413
5455
|
});
|
|
5414
5456
|
}
|
|
5415
|
-
|
|
5457
|
+
requireSameFlatSchema(
|
|
5458
|
+
indexName,
|
|
5459
|
+
flattenSchema(coordinatorConfig.schema),
|
|
5460
|
+
flattenSchema(entry.config.schema),
|
|
5461
|
+
"local index"
|
|
5462
|
+
);
|
|
5416
5463
|
const manager = core.requireManager(indexName);
|
|
5417
5464
|
if (manager.partitionCount > partitionCount) {
|
|
5418
5465
|
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "local index has more partitions than allocation", {
|
|
@@ -5431,45 +5478,222 @@ async function ensureReplicationIndex(core, engine, indexName, coordinatorConfig
|
|
|
5431
5478
|
}
|
|
5432
5479
|
return { config: entry.config, created };
|
|
5433
5480
|
}
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5481
|
+
|
|
5482
|
+
// src/distribution/cluster-node/partition-archive.ts
|
|
5483
|
+
var VECTOR_FILE_ENVELOPE_VERSION = 2;
|
|
5484
|
+
async function archiveReplicationPartition(core, indexName, partitionId, lastSeqNo) {
|
|
5485
|
+
core.guardShutdown();
|
|
5486
|
+
const release = await core.indexState.acquire(indexName, false);
|
|
5487
|
+
try {
|
|
5488
|
+
const entry = core.requireIndex(indexName);
|
|
5489
|
+
const manager = core.requireManager(indexName);
|
|
5490
|
+
let captured = 0;
|
|
5491
|
+
const checkpoint = await buildArchiveFromMemory({
|
|
5492
|
+
indexName,
|
|
5493
|
+
metadata: indexMetadataOf(indexName, entry, manager.partitionCount),
|
|
5494
|
+
manager,
|
|
5495
|
+
vectorIndexes: manager.getVectorIndexes(),
|
|
5496
|
+
appliedSeqNo: () => {
|
|
5497
|
+
captured = lastSeqNo();
|
|
5498
|
+
return captured;
|
|
5499
|
+
},
|
|
5500
|
+
partitionId
|
|
5501
|
+
});
|
|
5502
|
+
const config = await encodeIndexConfiguration(entry);
|
|
5503
|
+
return { stream: archiveStream({ ...checkpoint, config, sourceIndexName: indexName }), lastSeqNo: captured };
|
|
5504
|
+
} finally {
|
|
5505
|
+
release();
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
function restoreFailed(message, details) {
|
|
5509
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, message, details);
|
|
5510
|
+
}
|
|
5511
|
+
function requireRestoreTarget(indexName, partitionId, partitionCount) {
|
|
5512
|
+
if (!Number.isInteger(partitionId) || partitionId < 0) {
|
|
5513
|
+
restoreFailed("partitionId must be a non-negative integer", { indexName, partitionId });
|
|
5514
|
+
}
|
|
5515
|
+
if (!Number.isInteger(partitionCount) || partitionCount <= 0 || partitionCount > MAX_PARTITION_COUNT) {
|
|
5516
|
+
restoreFailed(`partitionCount must be an integer between 1 and ${MAX_PARTITION_COUNT}`, {
|
|
5517
|
+
indexName,
|
|
5518
|
+
partitionCount
|
|
5519
|
+
});
|
|
5520
|
+
}
|
|
5521
|
+
if (partitionId >= partitionCount) {
|
|
5522
|
+
restoreFailed("partitionId is outside partitionCount", { indexName, partitionId, partitionCount });
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
async function receiveArchive(indexName, partitionId, source, schema) {
|
|
5526
|
+
const store = memoryCheckpointStore();
|
|
5527
|
+
let language = "";
|
|
5528
|
+
let received = null;
|
|
5529
|
+
await readSnapshotArchive(createByteReader(source), {
|
|
5530
|
+
config: async (bytes) => {
|
|
5531
|
+
const { payload } = await openChecksummedEnvelope(bytes, {
|
|
5532
|
+
envelopeFormatVersion: 5,
|
|
5533
|
+
what: "index configuration",
|
|
5534
|
+
allowsCompression: true
|
|
5535
|
+
});
|
|
5536
|
+
const raw = decode(payload);
|
|
5537
|
+
if (typeof raw.language !== "string") restoreFailed("partition archive names no language", { indexName });
|
|
5538
|
+
language = raw.language;
|
|
5539
|
+
},
|
|
5540
|
+
manifest: async (manifest, sourceName) => {
|
|
5541
|
+
const listed = manifest.partitions.map((partition) => partition.partitionId);
|
|
5542
|
+
if (listed.length !== 1 || listed[0] !== partitionId) {
|
|
5543
|
+
restoreFailed("partition archive lists other partitions", { indexName, partitionId, listed });
|
|
5544
|
+
}
|
|
5545
|
+
if (manifest.vectors.some((field) => field.graphParts.length > 0)) {
|
|
5546
|
+
restoreFailed("partition archive holds a graph", { indexName, partitionId });
|
|
5547
|
+
}
|
|
5548
|
+
requireSameFlatSchema(indexName, schema, manifest.schema, "partition snapshot");
|
|
5549
|
+
const rename = (key) => `${indexName}/${key.slice(sourceName.length + 1)}`;
|
|
5550
|
+
received = {
|
|
5551
|
+
...manifest,
|
|
5552
|
+
partitions: manifest.partitions.map((partition) => ({
|
|
5553
|
+
...partition,
|
|
5554
|
+
segments: partition.segments.map((segment) => ({ ...segment, key: rename(segment.key) }))
|
|
5555
|
+
})),
|
|
5556
|
+
vectors: manifest.vectors.map((field) => ({
|
|
5557
|
+
...field,
|
|
5558
|
+
files: field.files.map((file) => ({ ...file, key: rename(file.key) }))
|
|
5559
|
+
}))
|
|
5560
|
+
};
|
|
5561
|
+
},
|
|
5562
|
+
file: async (name, _ref, bytes) => store.write(`${indexName}/${name}`, [bytes])
|
|
5563
|
+
});
|
|
5564
|
+
if (received === null) restoreFailed("partition archive holds no manifest", { indexName, partitionId });
|
|
5565
|
+
return { language, manifest: received, store };
|
|
5566
|
+
}
|
|
5567
|
+
function emptyPartition(manager, partitionId) {
|
|
5568
|
+
return {
|
|
5569
|
+
indexName: manager.indexName,
|
|
5570
|
+
partitionId,
|
|
5571
|
+
totalPartitions: manager.partitionCount,
|
|
5572
|
+
language: manager.language.name,
|
|
5573
|
+
schema: {},
|
|
5574
|
+
docCount: 0,
|
|
5575
|
+
avgDocLength: 0,
|
|
5576
|
+
documents: {},
|
|
5577
|
+
invertedIndex: {},
|
|
5578
|
+
fieldIndexes: { numeric: {}, boolean: {}, enum: {}, geopoint: {} },
|
|
5579
|
+
statistics: { totalDocuments: 0, totalFieldLengths: {}, averageFieldLengths: {}, docFrequencies: {} }
|
|
5580
|
+
};
|
|
5581
|
+
}
|
|
5582
|
+
async function replacePartition(manager, partitionId, archive) {
|
|
5583
|
+
const vectorIndexes = manager.getVectorIndexes();
|
|
5584
|
+
const held = [...manager.getPartition(partitionId).docIds()];
|
|
5585
|
+
for (const vectorIndex of vectorIndexes.values()) {
|
|
5586
|
+
for (const docId of held) if (vectorIndex.has(docId)) vectorIndex.remove(docId);
|
|
5587
|
+
}
|
|
5588
|
+
manager.deserializePartition(partitionId, emptyPartition(manager, partitionId));
|
|
5589
|
+
const entry = archive.manifest.partitions[0];
|
|
5590
|
+
if (entry !== void 0) {
|
|
5591
|
+
await loadPartitionSegments(archive.store, entry, manager, {
|
|
5592
|
+
schema: archive.manifest.schema,
|
|
5593
|
+
decodesEveryDocument: true
|
|
5594
|
+
});
|
|
5595
|
+
}
|
|
5596
|
+
for (const field of archive.manifest.vectors) {
|
|
5597
|
+
const vectorIndex = vectorIndexes.get(field.fieldPath);
|
|
5598
|
+
if (vectorIndex === void 0) continue;
|
|
5599
|
+
for (const file of field.files) {
|
|
5600
|
+
const bytes = await readListedFile(archive.store, file.key, { bytes: file.bytes, what: "vector file" });
|
|
5601
|
+
const { payload } = await openChecksummedEnvelope(bytes, {
|
|
5602
|
+
envelopeFormatVersion: VECTOR_FILE_ENVELOPE_VERSION,
|
|
5603
|
+
what: `vector file "${file.key}"`,
|
|
5604
|
+
allowsCompression: false
|
|
5444
5605
|
});
|
|
5606
|
+
const decoded = decodeVectorFilePayload(decode(payload));
|
|
5607
|
+
const vectors = bytesToVectors(decoded.vectors);
|
|
5608
|
+
for (let i = 0; i < decoded.docIds.length; i++) {
|
|
5609
|
+
if (isDead(file.dead, i)) continue;
|
|
5610
|
+
const vector = vectors.slice(i * decoded.dimension, (i + 1) * decoded.dimension);
|
|
5611
|
+
vectorIndex.insert(decoded.docIds[i], vector, partitionId);
|
|
5612
|
+
}
|
|
5445
5613
|
}
|
|
5614
|
+
vectorIndex.scheduleBuild();
|
|
5446
5615
|
}
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5616
|
+
}
|
|
5617
|
+
async function restoreReplicationPartition(core, engine, indexName, partitionId, source, indexConfig, partitionCount) {
|
|
5618
|
+
core.guardShutdown();
|
|
5619
|
+
requireRestoreTarget(indexName, partitionId, partitionCount);
|
|
5620
|
+
const archive = await receiveArchive(indexName, partitionId, source, flattenSchema(indexConfig.schema));
|
|
5621
|
+
let release = core.indexRegistry.has(indexName) ? await core.indexState.acquire(indexName, false) : null;
|
|
5622
|
+
try {
|
|
5623
|
+
const restoreIndex = await ensureReplicationIndex(
|
|
5624
|
+
core,
|
|
5625
|
+
engine,
|
|
5626
|
+
indexName,
|
|
5627
|
+
indexConfig,
|
|
5628
|
+
archive.language,
|
|
5629
|
+
partitionCount
|
|
5630
|
+
);
|
|
5631
|
+
if (release === null) release = await core.indexState.acquire(indexName, false);
|
|
5632
|
+
if (restoreIndex.created) {
|
|
5633
|
+
await core.orchestrator.replicateToWorkers({
|
|
5634
|
+
type: "createIndex",
|
|
5450
5635
|
indexName,
|
|
5451
|
-
|
|
5636
|
+
config: restoreIndex.config,
|
|
5637
|
+
requestId: `replicate-partition-index-${indexName}`
|
|
5452
5638
|
});
|
|
5453
5639
|
}
|
|
5640
|
+
const wasScaledOut = core.orchestrator.desyncIndex(indexName);
|
|
5641
|
+
try {
|
|
5642
|
+
await replacePartition(core.requireManager(indexName), partitionId, archive);
|
|
5643
|
+
} finally {
|
|
5644
|
+
await core.orchestrator.resyncIndex(indexName, wasScaledOut);
|
|
5645
|
+
}
|
|
5646
|
+
} finally {
|
|
5647
|
+
release?.();
|
|
5454
5648
|
}
|
|
5455
5649
|
}
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5650
|
+
|
|
5651
|
+
// src/distribution/cluster-node/local-engine.ts
|
|
5652
|
+
async function createClusterLocalEngine(config, hooks) {
|
|
5653
|
+
requireKnownConfig(config);
|
|
5654
|
+
const runnableConfig = await resolveRunnableConfig(config);
|
|
5655
|
+
const core = createEngineCore(runnableConfig, hooks, "partition");
|
|
5656
|
+
await startEngineCore(core, runnableConfig);
|
|
5657
|
+
core.orchestrator.shareMainThread();
|
|
5658
|
+
const engine = createNarsilFromCore(core, runnableConfig);
|
|
5659
|
+
const heldPartitions = createHeldPartitionRecord(core);
|
|
5660
|
+
return Object.assign(engine, {
|
|
5661
|
+
createIndex: (name, indexConfig) => createEngineIndex(core, runnableConfig, name, indexConfig, { adapterBinding: "deferred" }),
|
|
5662
|
+
createIndexWithUuid: (name, indexConfig, indexUuid) => createEngineIndex(core, runnableConfig, name, indexConfig, { indexUuid }),
|
|
5663
|
+
acquireIndexForReplication: (indexName) => core.indexState.acquire(indexName, false),
|
|
5664
|
+
indexUuidOf: (indexName) => core.indexRegistry.get(indexName)?.indexUuid,
|
|
5665
|
+
stampIndexUuid: (indexName, indexUuid) => stampIndexUuid(core, indexName, indexUuid),
|
|
5666
|
+
highestPersistedSeqNoOf: (indexName, partitionId) => core.durability?.manager.highestPersistedSeqNo(indexName, partitionId) ?? 0,
|
|
5667
|
+
heldPartitionsOf: (indexName) => heldPartitions.held(indexName),
|
|
5668
|
+
recordHeldPartition: (indexName, partitionId) => heldPartitions.record(indexName, partitionId),
|
|
5669
|
+
forgetHeldPartition: (indexName, partitionId) => heldPartitions.forget(indexName, partitionId),
|
|
5670
|
+
applyReplicationEntry: (entry) => applyLocalReplicationEntry(core, entry),
|
|
5671
|
+
archiveReplicationPartition: (indexName, partitionId, lastSeqNo) => archiveReplicationPartition(core, indexName, partitionId, lastSeqNo),
|
|
5672
|
+
restoreReplicationPartition: (indexName, partitionId, source, indexConfig, partitionCount) => restoreReplicationPartition(core, engine, indexName, partitionId, source, indexConfig, partitionCount),
|
|
5673
|
+
queryPartitions: (indexName, params, partitionIds, globalStats) => runEngineQuery(core, indexName, params, { partitionIds, globalStats }),
|
|
5674
|
+
preflightPartitions: (indexName, params, partitionIds) => runEnginePreflight(core, indexName, params, { partitionIds }),
|
|
5675
|
+
suggestPartitions: (indexName, params, partitionIds) => runEngineSuggest(core, indexName, params, partitionIds),
|
|
5676
|
+
listPartitions: (indexName, params, partitionIds) => runEngineListDocuments(core, indexName, params, partitionIds),
|
|
5677
|
+
collectQueryStats: (indexName, terms, partitionIds) => runEngineQueryStats(core, indexName, terms, partitionIds),
|
|
5678
|
+
partitionStatsForRead: async (indexName) => {
|
|
5679
|
+
const release = await core.indexState.acquire(indexName);
|
|
5680
|
+
try {
|
|
5681
|
+
return core.requireManager(indexName).getPartitionStats();
|
|
5682
|
+
} finally {
|
|
5683
|
+
release();
|
|
5684
|
+
}
|
|
5467
5685
|
}
|
|
5468
|
-
|
|
5469
|
-
}
|
|
5686
|
+
});
|
|
5470
5687
|
}
|
|
5471
|
-
function
|
|
5472
|
-
|
|
5688
|
+
async function stampIndexUuid(core, indexName, indexUuid) {
|
|
5689
|
+
const entry = core.indexRegistry.get(indexName);
|
|
5690
|
+
if (entry === void 0 || entry.indexUuid === indexUuid) {
|
|
5691
|
+
return;
|
|
5692
|
+
}
|
|
5693
|
+
core.indexRegistry.set(indexName, { ...entry, indexUuid });
|
|
5694
|
+
if (core.durability !== null) {
|
|
5695
|
+
await core.durability.manager.persistMetadata(indexName);
|
|
5696
|
+
}
|
|
5473
5697
|
}
|
|
5474
5698
|
|
|
5475
5699
|
// src/distribution/cluster-node/local-index-reconcile.ts
|
|
@@ -5582,6 +5806,25 @@ async function authorizeSnapshotRequest(coordinator, indexName, sourceId) {
|
|
|
5582
5806
|
}
|
|
5583
5807
|
|
|
5584
5808
|
// src/distribution/cluster-node/snapshot-cache.ts
|
|
5809
|
+
function soleSnapshotLease(build) {
|
|
5810
|
+
return { build, release: () => build.discard() };
|
|
5811
|
+
}
|
|
5812
|
+
function sharedLease(entry, build) {
|
|
5813
|
+
let released = false;
|
|
5814
|
+
return {
|
|
5815
|
+
build,
|
|
5816
|
+
async release() {
|
|
5817
|
+
if (released) return;
|
|
5818
|
+
released = true;
|
|
5819
|
+
entry.holders -= 1;
|
|
5820
|
+
if (entry.holders === 0) await build.discard();
|
|
5821
|
+
}
|
|
5822
|
+
};
|
|
5823
|
+
}
|
|
5824
|
+
async function joinSnapshotBuild(entry) {
|
|
5825
|
+
entry.holders += 1;
|
|
5826
|
+
return sharedLease(entry, await entry.promise);
|
|
5827
|
+
}
|
|
5585
5828
|
function createSnapshotCacheState(maxConcurrent = DEFAULT_MAX_CONCURRENT_SNAPSHOTS, maxPerSource = DEFAULT_MAX_PER_SOURCE_SNAPSHOTS, maxStreamsPerIndex = DEFAULT_MAX_STREAMS_PER_INDEX) {
|
|
5586
5829
|
if (!Number.isInteger(maxConcurrent) || maxConcurrent <= 0) {
|
|
5587
5830
|
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "maxConcurrent must be a positive integer", {
|
|
@@ -5669,7 +5912,7 @@ function releaseStreamSlot(state, handle) {
|
|
|
5669
5912
|
async function acquireSnapshotBuild(state, indexName, builder) {
|
|
5670
5913
|
const existing = state.cache.get(indexName);
|
|
5671
5914
|
if (existing !== void 0) {
|
|
5672
|
-
return existing
|
|
5915
|
+
return joinSnapshotBuild(existing);
|
|
5673
5916
|
}
|
|
5674
5917
|
if (state.active >= state.maxConcurrent) {
|
|
5675
5918
|
throw new NarsilError(
|
|
@@ -5692,7 +5935,7 @@ async function acquireSnapshotBuild(state, indexName, builder) {
|
|
|
5692
5935
|
state.active = Math.max(0, state.active - 1);
|
|
5693
5936
|
};
|
|
5694
5937
|
const rawBuild = (async () => builder())();
|
|
5695
|
-
const entry = { promise: rawBuild };
|
|
5938
|
+
const entry = { promise: rawBuild, holders: 0 };
|
|
5696
5939
|
entry.promise = rawBuild.then(
|
|
5697
5940
|
(result) => {
|
|
5698
5941
|
clearEntry(entry);
|
|
@@ -5704,7 +5947,7 @@ async function acquireSnapshotBuild(state, indexName, builder) {
|
|
|
5704
5947
|
}
|
|
5705
5948
|
);
|
|
5706
5949
|
state.cache.set(indexName, entry);
|
|
5707
|
-
return entry
|
|
5950
|
+
return joinSnapshotBuild(entry);
|
|
5708
5951
|
}
|
|
5709
5952
|
var SNAPSHOT_SYNC_ERROR_TYPE = `${ReplicationMessageTypes.SNAPSHOT_SYNC_REQUEST}.error`;
|
|
5710
5953
|
function createSingleResponseSink(respond) {
|
|
@@ -5718,8 +5961,7 @@ function createSingleResponseSink(respond) {
|
|
|
5718
5961
|
return sink;
|
|
5719
5962
|
}
|
|
5720
5963
|
async function streamSnapshotToReplica(sink, nodeId, requestId, indexName, build, metadata, options = {}) {
|
|
5721
|
-
const
|
|
5722
|
-
const totalBytes = snapshotBytes.byteLength;
|
|
5964
|
+
const totalBytes = build.totalBytes;
|
|
5723
5965
|
const checksum = build.checksum;
|
|
5724
5966
|
const header = {
|
|
5725
5967
|
lastSeqNo: metadata.lastSeqNo,
|
|
@@ -5735,22 +5977,23 @@ async function streamSnapshotToReplica(sink, nodeId, requestId, indexName, build
|
|
|
5735
5977
|
await yieldToEventLoop();
|
|
5736
5978
|
let offset = 0;
|
|
5737
5979
|
let lastYieldAt = now2();
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5980
|
+
for await (const source of build.read()) {
|
|
5981
|
+
for (let from = 0; from < source.byteLength; from += SNAPSHOT_CHUNK_SIZE) {
|
|
5982
|
+
const chunk = source.subarray(from, Math.min(from + SNAPSHOT_CHUNK_SIZE, source.byteLength));
|
|
5983
|
+
const chunkPayload = {
|
|
5984
|
+
partitionId: metadata.partitionId,
|
|
5985
|
+
indexName,
|
|
5986
|
+
offset,
|
|
5987
|
+
data: chunk
|
|
5988
|
+
};
|
|
5989
|
+
const chunkBytes = encode(chunkPayload);
|
|
5990
|
+
assertMessageWithinLimit(chunkBytes, "SNAPSHOT_CHUNK");
|
|
5991
|
+
await respondMessage(sink, ReplicationMessageTypes.SNAPSHOT_CHUNK, nodeId, requestId, chunkBytes);
|
|
5992
|
+
offset += chunk.byteLength;
|
|
5993
|
+
if (offset < totalBytes && now2() - lastYieldAt >= SNAPSHOT_CHUNK_YIELD_INTERVAL_MS) {
|
|
5994
|
+
await yieldToEventLoop();
|
|
5995
|
+
lastYieldAt = now2();
|
|
5996
|
+
}
|
|
5754
5997
|
}
|
|
5755
5998
|
}
|
|
5756
5999
|
await yieldToEventLoop();
|
|
@@ -5831,6 +6074,26 @@ async function defaultSnapshotHeaderMetadataProvider(coordinator, indexName) {
|
|
|
5831
6074
|
lastSeqNo: SNAPSHOT_HEADER_SENTINEL_SEQNO
|
|
5832
6075
|
};
|
|
5833
6076
|
}
|
|
6077
|
+
async function nodeSnapshotHeaderMetadata(deps, indexName, partitionId) {
|
|
6078
|
+
const fallback = await defaultSnapshotHeaderMetadataProvider(deps.coordinator, indexName);
|
|
6079
|
+
if (partitionId === null) {
|
|
6080
|
+
return fallback;
|
|
6081
|
+
}
|
|
6082
|
+
const localIndex = deps.engine.listIndexes().find((index) => index.name === indexName);
|
|
6083
|
+
const release = localIndex !== void 0 ? await deps.engine.acquireIndexForReplication(indexName) : null;
|
|
6084
|
+
try {
|
|
6085
|
+
const allocation = await deps.coordinator.getAllocation(indexName);
|
|
6086
|
+
const assignment = allocation?.assignments.get(partitionId);
|
|
6087
|
+
const log = deps.getReplicationLog(indexName, partitionId);
|
|
6088
|
+
return {
|
|
6089
|
+
partitionId,
|
|
6090
|
+
primaryTerm: assignment?.primaryTerm ?? fallback.primaryTerm,
|
|
6091
|
+
lastSeqNo: log.newestSeqNo ?? 0
|
|
6092
|
+
};
|
|
6093
|
+
} finally {
|
|
6094
|
+
release?.();
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
5834
6097
|
async function resolveHeaderMetadata(deps, indexName, partitionId) {
|
|
5835
6098
|
const provider = deps.resolveHeaderMetadata;
|
|
5836
6099
|
if (provider === void 0) {
|
|
@@ -5860,55 +6123,56 @@ async function acquireAndStream(message, sink, request, deps, options) {
|
|
|
5860
6123
|
return;
|
|
5861
6124
|
}
|
|
5862
6125
|
try {
|
|
5863
|
-
let
|
|
6126
|
+
let lease;
|
|
5864
6127
|
try {
|
|
5865
6128
|
const buildForRequest = async () => buildSnapshotForRequest(deps, indexName, options);
|
|
5866
|
-
|
|
6129
|
+
lease = options.disableBuildCache === true ? soleSnapshotLease(await buildForRequest()) : await acquireSnapshotBuild(deps.state, indexName, buildForRequest);
|
|
5867
6130
|
} catch (err) {
|
|
5868
6131
|
const code = err instanceof NarsilError ? err.code : ErrorCodes.SNAPSHOT_SYNC_SNAPSHOT_FAILED;
|
|
5869
6132
|
const errMessage = err instanceof Error ? err.message : String(err);
|
|
5870
6133
|
await respondError(sink, deps.nodeId, message.requestId, code, errMessage);
|
|
5871
6134
|
return;
|
|
5872
6135
|
}
|
|
5873
|
-
if (build.bytes.byteLength > MAX_SNAPSHOT_SIZE_BYTES) {
|
|
5874
|
-
await respondError(
|
|
5875
|
-
sink,
|
|
5876
|
-
deps.nodeId,
|
|
5877
|
-
message.requestId,
|
|
5878
|
-
ErrorCodes.SNAPSHOT_SYNC_TOO_LARGE,
|
|
5879
|
-
`Snapshot size ${build.bytes.byteLength} exceeds the ${MAX_SNAPSHOT_SIZE_BYTES} byte limit`
|
|
5880
|
-
);
|
|
5881
|
-
return;
|
|
5882
|
-
}
|
|
5883
|
-
let streamHandle;
|
|
5884
6136
|
try {
|
|
5885
|
-
|
|
5886
|
-
} catch (err) {
|
|
5887
|
-
const code = err instanceof NarsilError ? err.code : ErrorCodes.SNAPSHOT_SYNC_CAPACITY_EXHAUSTED;
|
|
5888
|
-
const errMessage = err instanceof Error ? err.message : String(err);
|
|
5889
|
-
await respondError(sink, deps.nodeId, message.requestId, code, errMessage);
|
|
5890
|
-
return;
|
|
5891
|
-
}
|
|
5892
|
-
try {
|
|
5893
|
-
const metadata = options.metadata ?? await resolveHeaderMetadata(deps, indexName, request.partitionId ?? null);
|
|
5894
|
-
await streamSnapshotToReplica(sink, deps.nodeId, message.requestId, indexName, build, metadata, {
|
|
5895
|
-
closeOnEnd: options.closeOnEnd
|
|
5896
|
-
});
|
|
5897
|
-
await options.afterSnapshot?.(sink);
|
|
5898
|
-
if (options.closeOnEnd === false) {
|
|
5899
|
-
sink.closed = true;
|
|
5900
|
-
}
|
|
6137
|
+
await streamLeasedSnapshot(message, sink, request, deps, options, lease.build);
|
|
5901
6138
|
} finally {
|
|
5902
|
-
|
|
6139
|
+
await lease.release();
|
|
5903
6140
|
}
|
|
5904
6141
|
} finally {
|
|
5905
6142
|
releaseSourceSlot(deps.state, sourceHandle);
|
|
5906
6143
|
}
|
|
5907
6144
|
}
|
|
6145
|
+
async function streamLeasedSnapshot(message, sink, request, deps, options, build) {
|
|
6146
|
+
const { indexName } = request;
|
|
6147
|
+
let streamHandle;
|
|
6148
|
+
try {
|
|
6149
|
+
streamHandle = acquireStreamSlot(deps.state, indexName);
|
|
6150
|
+
} catch (err) {
|
|
6151
|
+
const code = err instanceof NarsilError ? err.code : ErrorCodes.SNAPSHOT_SYNC_CAPACITY_EXHAUSTED;
|
|
6152
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
6153
|
+
await respondError(sink, deps.nodeId, message.requestId, code, errMessage);
|
|
6154
|
+
return;
|
|
6155
|
+
}
|
|
6156
|
+
try {
|
|
6157
|
+
const resolved = options.metadata ?? await resolveHeaderMetadata(deps, indexName, request.partitionId ?? null);
|
|
6158
|
+
const metadata = build.lastSeqNo === void 0 ? resolved : { ...resolved, lastSeqNo: build.lastSeqNo };
|
|
6159
|
+
await streamSnapshotToReplica(sink, deps.nodeId, message.requestId, indexName, build, metadata, {
|
|
6160
|
+
closeOnEnd: options.closeOnEnd
|
|
6161
|
+
});
|
|
6162
|
+
await options.afterSnapshot?.(sink, build);
|
|
6163
|
+
if (options.closeOnEnd === false) {
|
|
6164
|
+
sink.closed = true;
|
|
6165
|
+
}
|
|
6166
|
+
} finally {
|
|
6167
|
+
releaseStreamSlot(deps.state, streamHandle);
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
5908
6170
|
async function buildSnapshot(deps, indexName) {
|
|
5909
|
-
let bytes;
|
|
5910
6171
|
try {
|
|
5911
|
-
|
|
6172
|
+
return await spoolSnapshotStream(
|
|
6173
|
+
deps.spoolDirectory ?? defaultSpoolDirectory(),
|
|
6174
|
+
await deps.engine.snapshot(indexName)
|
|
6175
|
+
);
|
|
5912
6176
|
} catch (err) {
|
|
5913
6177
|
if (err instanceof NarsilError) {
|
|
5914
6178
|
throw err;
|
|
@@ -5919,8 +6183,6 @@ async function buildSnapshot(deps, indexName) {
|
|
|
5919
6183
|
cause: errMessage
|
|
5920
6184
|
});
|
|
5921
6185
|
}
|
|
5922
|
-
const checksum = bytes.byteLength <= MAX_SNAPSHOT_SIZE_BYTES ? crc32(bytes) : 0;
|
|
5923
|
-
return { bytes, checksum };
|
|
5924
6186
|
}
|
|
5925
6187
|
function buildSnapshotForRequest(deps, indexName, options) {
|
|
5926
6188
|
if (options.buildSnapshot !== void 0) {
|
|
@@ -6104,7 +6366,6 @@ var MAX_FACETS = 64;
|
|
|
6104
6366
|
var MAX_FACET_RANGES = 1e3;
|
|
6105
6367
|
var MAX_FETCH_DOCUMENT_IDS = 1e4;
|
|
6106
6368
|
var MAX_FILTER_FIELDS = 256;
|
|
6107
|
-
var MAX_FILTER_ARRAY_SIZE = 65536;
|
|
6108
6369
|
var MAX_FILTER_STRING_LENGTH = 1024;
|
|
6109
6370
|
var MAX_LIMIT = 1e4;
|
|
6110
6371
|
var MAX_OFFSET = 1e4;
|
|
@@ -6130,7 +6391,6 @@ var MAX_FACET_VALUE_LENGTH = 1024;
|
|
|
6130
6391
|
var MAX_GROUPS_PER_RESPONSE = 65536;
|
|
6131
6392
|
var MAX_FACET_SIZE = 1e3;
|
|
6132
6393
|
var DEFAULT_MAX_FACET_BUCKETS = 100;
|
|
6133
|
-
var MAX_VECTOR_DIMENSION = 8192;
|
|
6134
6394
|
var MAX_VECTOR_TEXT_LENGTH = 16384;
|
|
6135
6395
|
|
|
6136
6396
|
// src/distribution/query/validators/common.ts
|
|
@@ -6337,18 +6597,16 @@ function validateFetchResultPayload(decoded) {
|
|
|
6337
6597
|
|
|
6338
6598
|
// src/distribution/query/validators/filters.ts
|
|
6339
6599
|
var GEO_UNITS = ["km", "mi", "m"];
|
|
6600
|
+
function throwTooLong(value, fieldLabel, errorCode) {
|
|
6601
|
+
throwInvalid(
|
|
6602
|
+
errorCode,
|
|
6603
|
+
`Invalid payload: "${fieldLabel}" exceeds the maximum length of ${MAX_FILTER_STRING_LENGTH} code points`,
|
|
6604
|
+
{ length: [...value].length, limit: MAX_FILTER_STRING_LENGTH }
|
|
6605
|
+
);
|
|
6606
|
+
}
|
|
6340
6607
|
function validateScalarPrimitive(value, fieldLabel, errorCode) {
|
|
6341
6608
|
if (typeof value === "string") {
|
|
6342
|
-
if (value
|
|
6343
|
-
throwInvalid(
|
|
6344
|
-
errorCode,
|
|
6345
|
-
`Invalid payload: "${fieldLabel}" exceeds maximum length of ${MAX_FILTER_STRING_LENGTH}`,
|
|
6346
|
-
{
|
|
6347
|
-
length: value.length,
|
|
6348
|
-
limit: MAX_FILTER_STRING_LENGTH
|
|
6349
|
-
}
|
|
6350
|
-
);
|
|
6351
|
-
}
|
|
6609
|
+
if (exceedsCodePoints(value, MAX_FILTER_STRING_LENGTH)) throwTooLong(value, fieldLabel, errorCode);
|
|
6352
6610
|
return value;
|
|
6353
6611
|
}
|
|
6354
6612
|
if (typeof value === "number") {
|
|
@@ -6366,12 +6624,7 @@ function validateBoundedString(value, fieldLabel, errorCode) {
|
|
|
6366
6624
|
if (typeof value !== "string") {
|
|
6367
6625
|
throwInvalid(errorCode, `Invalid payload: "${fieldLabel}" must be a string`);
|
|
6368
6626
|
}
|
|
6369
|
-
if (value
|
|
6370
|
-
throwInvalid(errorCode, `Invalid payload: "${fieldLabel}" exceeds maximum length of ${MAX_FILTER_STRING_LENGTH}`, {
|
|
6371
|
-
length: value.length,
|
|
6372
|
-
limit: MAX_FILTER_STRING_LENGTH
|
|
6373
|
-
});
|
|
6374
|
-
}
|
|
6627
|
+
if (exceedsCodePoints(value, MAX_FILTER_STRING_LENGTH)) throwTooLong(value, fieldLabel, errorCode);
|
|
6375
6628
|
return value;
|
|
6376
6629
|
}
|
|
6377
6630
|
function validateNumericOperand(value, fieldLabel, errorCode) {
|
|
@@ -6380,6 +6633,20 @@ function validateNumericOperand(value, fieldLabel, errorCode) {
|
|
|
6380
6633
|
}
|
|
6381
6634
|
return value;
|
|
6382
6635
|
}
|
|
6636
|
+
function validateCoordinates(lat, lon, fieldLabel, errorCode) {
|
|
6637
|
+
if (!isLatitude(validateNumericOperand(lat, `${fieldLabel}.lat`, errorCode))) {
|
|
6638
|
+
throwInvalid(
|
|
6639
|
+
errorCode,
|
|
6640
|
+
`Invalid payload: "${fieldLabel}.lat" must be from -${MAX_LATITUDE_DEGREES} to ${MAX_LATITUDE_DEGREES}`
|
|
6641
|
+
);
|
|
6642
|
+
}
|
|
6643
|
+
if (!isLongitude(validateNumericOperand(lon, `${fieldLabel}.lon`, errorCode))) {
|
|
6644
|
+
throwInvalid(
|
|
6645
|
+
errorCode,
|
|
6646
|
+
`Invalid payload: "${fieldLabel}.lon" must be from -${MAX_LONGITUDE_DEGREES} to ${MAX_LONGITUDE_DEGREES}`
|
|
6647
|
+
);
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6383
6650
|
function validateBooleanOperand(value, fieldLabel, errorCode) {
|
|
6384
6651
|
if (typeof value !== "boolean") {
|
|
6385
6652
|
throwInvalid(errorCode, `Invalid payload: "${fieldLabel}" must be a boolean`);
|
|
@@ -6440,8 +6707,7 @@ function validateGeoRadius(value, fieldLabel, errorCode) {
|
|
|
6440
6707
|
if (!isRecord2(value)) {
|
|
6441
6708
|
throwInvalid(errorCode, `Invalid payload: "${fieldLabel}" must be an object`);
|
|
6442
6709
|
}
|
|
6443
|
-
|
|
6444
|
-
validateNumericOperand(value.lon, `${fieldLabel}.lon`, errorCode);
|
|
6710
|
+
validateCoordinates(value.lat, value.lon, fieldLabel, errorCode);
|
|
6445
6711
|
validateNumericOperand(value.distance, `${fieldLabel}.distance`, errorCode);
|
|
6446
6712
|
if (value.distance < 0) {
|
|
6447
6713
|
throwInvalid(errorCode, `Invalid payload: "${fieldLabel}.distance" must be non-negative`);
|
|
@@ -6483,8 +6749,7 @@ function validateGeoPolygon(value, fieldLabel, errorCode) {
|
|
|
6483
6749
|
if (!isRecord2(point)) {
|
|
6484
6750
|
throwInvalid(errorCode, `Invalid payload: "${fieldLabel}.points[${i}]" must be an object`);
|
|
6485
6751
|
}
|
|
6486
|
-
|
|
6487
|
-
validateNumericOperand(point.lon, `${fieldLabel}.points[${i}].lon`, errorCode);
|
|
6752
|
+
validateCoordinates(point.lat, point.lon, `${fieldLabel}.points[${i}]`, errorCode);
|
|
6488
6753
|
}
|
|
6489
6754
|
if (value.inside !== void 0) {
|
|
6490
6755
|
validateBooleanOperand(value.inside, `${fieldLabel}.inside`, errorCode);
|
|
@@ -6504,10 +6769,14 @@ function validateLeafOperator(opKey, opValue, leafLabel, errorCode) {
|
|
|
6504
6769
|
validateBetween(opValue, opLabel, errorCode);
|
|
6505
6770
|
return;
|
|
6506
6771
|
}
|
|
6507
|
-
if (opKey === "startsWith" || opKey === "endsWith") {
|
|
6772
|
+
if (opKey === "startsWith" || opKey === "endsWith" || opKey === "contains") {
|
|
6508
6773
|
validateBoundedString(opValue, opLabel, errorCode);
|
|
6509
6774
|
return;
|
|
6510
6775
|
}
|
|
6776
|
+
if (opKey === "caseFold") {
|
|
6777
|
+
validateBooleanOperand(opValue, opLabel, errorCode);
|
|
6778
|
+
return;
|
|
6779
|
+
}
|
|
6511
6780
|
if (opKey === "in" || opKey === "nin") {
|
|
6512
6781
|
validateStringList(opValue, opLabel, errorCode);
|
|
6513
6782
|
return;
|
|
@@ -6625,6 +6894,18 @@ function validateFilterExpression(value, fieldLabel, errorCode = SEARCH_INVALID_
|
|
|
6625
6894
|
validateFilterExpression(clauseValue, `${fieldLabel}.not`, errorCode, depth + 1);
|
|
6626
6895
|
}
|
|
6627
6896
|
}
|
|
6897
|
+
if (depth > 0) return;
|
|
6898
|
+
const tests = filterTestCount(value);
|
|
6899
|
+
if (tests > MAX_FILTER_TESTS) {
|
|
6900
|
+
throwInvalid(
|
|
6901
|
+
errorCode,
|
|
6902
|
+
`Invalid payload: "${fieldLabel}" holds ${tests} tests, above the maximum of ${MAX_FILTER_TESTS}`,
|
|
6903
|
+
{
|
|
6904
|
+
tests,
|
|
6905
|
+
limit: MAX_FILTER_TESTS
|
|
6906
|
+
}
|
|
6907
|
+
);
|
|
6908
|
+
}
|
|
6628
6909
|
}
|
|
6629
6910
|
|
|
6630
6911
|
// src/distribution/query/validators/search-options.ts
|
|
@@ -6845,11 +7126,11 @@ function validateVectorParams(value) {
|
|
|
6845
7126
|
);
|
|
6846
7127
|
}
|
|
6847
7128
|
const dims = value.value.length;
|
|
6848
|
-
if (dims === 0 || dims >
|
|
7129
|
+
if (dims === 0 || dims > MAX_STORED_VECTOR_DIMENSION) {
|
|
6849
7130
|
throwInvalid(
|
|
6850
7131
|
CONFIG_INVALID,
|
|
6851
|
-
`Invalid SearchPayload: "params.vector.value" length must be between 1 and ${
|
|
6852
|
-
{ length: dims, limit:
|
|
7132
|
+
`Invalid SearchPayload: "params.vector.value" length must be between 1 and ${MAX_STORED_VECTOR_DIMENSION}`,
|
|
7133
|
+
{ length: dims, limit: MAX_STORED_VECTOR_DIMENSION }
|
|
6853
7134
|
);
|
|
6854
7135
|
}
|
|
6855
7136
|
for (let i = 0; i < dims; i++) {
|
|
@@ -8051,6 +8332,8 @@ function toWireSortValue2(value) {
|
|
|
8051
8332
|
if (typeof value === "boolean") return value;
|
|
8052
8333
|
return null;
|
|
8053
8334
|
}
|
|
8335
|
+
|
|
8336
|
+
// src/distribution/replication/sync-primary.ts
|
|
8054
8337
|
function decideSyncTier(log, replicaLastSeqNo) {
|
|
8055
8338
|
const oldest = log.oldestSeqNo;
|
|
8056
8339
|
if (oldest === void 0) {
|
|
@@ -8172,7 +8455,8 @@ async function handleSyncRequestMessage(message, respond, deps) {
|
|
|
8172
8455
|
engine: deps.engine,
|
|
8173
8456
|
coordinator: deps.coordinator,
|
|
8174
8457
|
state: deps.snapshotSyncState,
|
|
8175
|
-
resolveHeaderMetadata: deps.resolveHeaderMetadata
|
|
8458
|
+
resolveHeaderMetadata: deps.resolveHeaderMetadata,
|
|
8459
|
+
spoolDirectory: deps.snapshotSpoolDirectory
|
|
8176
8460
|
},
|
|
8177
8461
|
{
|
|
8178
8462
|
metadata: {
|
|
@@ -8183,11 +8467,16 @@ async function handleSyncRequestMessage(message, respond, deps) {
|
|
|
8183
8467
|
closeOnEnd: false,
|
|
8184
8468
|
disableBuildCache: true,
|
|
8185
8469
|
buildSnapshot: async () => {
|
|
8186
|
-
const
|
|
8187
|
-
|
|
8470
|
+
const archive = await deps.engine.archiveReplicationPartition(
|
|
8471
|
+
request.indexName,
|
|
8472
|
+
request.partitionId,
|
|
8473
|
+
() => log.localLogEnd
|
|
8474
|
+
);
|
|
8475
|
+
const directory = deps.snapshotSpoolDirectory ?? defaultSpoolDirectory();
|
|
8476
|
+
return { ...await spoolSnapshotStream(directory, archive.stream), lastSeqNo: archive.lastSeqNo };
|
|
8188
8477
|
},
|
|
8189
|
-
afterSnapshot: async (trailingSink) => {
|
|
8190
|
-
const entries = log.getEntriesFrom(snapshotSeqNo + 1);
|
|
8478
|
+
afterSnapshot: async (trailingSink, build) => {
|
|
8479
|
+
const entries = log.getEntriesFrom((build.lastSeqNo ?? snapshotSeqNo) + 1);
|
|
8191
8480
|
await sendSyncEntriesResponse(message, trailingSink, deps, entries);
|
|
8192
8481
|
}
|
|
8193
8482
|
}
|
|
@@ -8303,7 +8592,7 @@ async function forwardInsertToRemote(indexName, document, docId, primaryNodeId,
|
|
|
8303
8592
|
indexName,
|
|
8304
8593
|
documentId: docId,
|
|
8305
8594
|
operation: "insert",
|
|
8306
|
-
document:
|
|
8595
|
+
document: encodeDocument(document),
|
|
8307
8596
|
updateFields: null
|
|
8308
8597
|
};
|
|
8309
8598
|
const message = createForwardMessage(payload, deps.nodeId);
|
|
@@ -8323,7 +8612,7 @@ async function forwardUpdateToRemote(indexName, document, docId, primaryNodeId,
|
|
|
8323
8612
|
indexName,
|
|
8324
8613
|
documentId: docId,
|
|
8325
8614
|
operation: "update",
|
|
8326
|
-
document:
|
|
8615
|
+
document: encodeDocument(document),
|
|
8327
8616
|
updateFields: null
|
|
8328
8617
|
};
|
|
8329
8618
|
const message = createForwardMessage(payload, deps.nodeId);
|
|
@@ -8349,6 +8638,8 @@ async function sendToNode(nodeId, message, deps) {
|
|
|
8349
8638
|
details: { targetNodeId: nodeId }
|
|
8350
8639
|
});
|
|
8351
8640
|
}
|
|
8641
|
+
|
|
8642
|
+
// src/distribution/cluster-node/write-routing/replication.ts
|
|
8352
8643
|
async function assertPrimaryWriteAuthority(entry, deps) {
|
|
8353
8644
|
const table = await deps.coordinator.getAllocation(entry.indexName);
|
|
8354
8645
|
const currentAssignment = table?.assignments.get(entry.partitionId);
|
|
@@ -8378,7 +8669,7 @@ function appendIndexReplicationEntry(indexName, partitionId, assignment, documen
|
|
|
8378
8669
|
partitionId,
|
|
8379
8670
|
indexName,
|
|
8380
8671
|
documentId,
|
|
8381
|
-
document:
|
|
8672
|
+
document: encodeDocument(document)
|
|
8382
8673
|
});
|
|
8383
8674
|
}
|
|
8384
8675
|
function appendDeleteReplicationEntry(indexName, partitionId, assignment, documentId, deps) {
|
|
@@ -8862,7 +9153,7 @@ function toWireOperation(item) {
|
|
|
8862
9153
|
return {
|
|
8863
9154
|
documentId: item.documentId,
|
|
8864
9155
|
operation: item.operation,
|
|
8865
|
-
document: item.document === null ? null :
|
|
9156
|
+
document: item.document === null ? null : encodeDocument(item.document),
|
|
8866
9157
|
updateFields: null
|
|
8867
9158
|
};
|
|
8868
9159
|
}
|
|
@@ -9269,9 +9560,14 @@ function validateCreateIndexOptions(partitionCount, replicationFactor) {
|
|
|
9269
9560
|
async function routeCreateIndex(name, requestedConfig, options, coordinator, engine) {
|
|
9270
9561
|
validateIndexName(name);
|
|
9271
9562
|
const config = { ...requestedConfig, schema: validateSchema(requestedConfig.schema) };
|
|
9563
|
+
requireCreatableSchema(config.schema);
|
|
9272
9564
|
const partitionCount = options?.partitionCount ?? DEFAULT_PARTITION_COUNT;
|
|
9273
9565
|
const replicationFactor = options?.replicationFactor ?? DEFAULT_REPLICATION_FACTOR;
|
|
9274
9566
|
validateCreateIndexOptions(partitionCount, replicationFactor);
|
|
9567
|
+
requireConfigurationWithinSizeLimit(
|
|
9568
|
+
withPartitionCount(config, partitionCount),
|
|
9569
|
+
getLanguage(config.language ?? "english")
|
|
9570
|
+
);
|
|
9275
9571
|
const constraints = {
|
|
9276
9572
|
zoneAwareness: false,
|
|
9277
9573
|
zoneAttribute: "zone",
|
|
@@ -9552,7 +9848,8 @@ function createDataNodeHandler(deps) {
|
|
|
9552
9848
|
engine: deps.engine,
|
|
9553
9849
|
coordinator: deps.coordinator,
|
|
9554
9850
|
state: deps.snapshotSyncState,
|
|
9555
|
-
resolveHeaderMetadata: deps.resolveHeaderMetadata
|
|
9851
|
+
resolveHeaderMetadata: deps.resolveHeaderMetadata,
|
|
9852
|
+
spoolDirectory: deps.snapshotSpoolDirectory
|
|
9556
9853
|
});
|
|
9557
9854
|
return;
|
|
9558
9855
|
}
|
|
@@ -9824,6 +10121,7 @@ var QUERY_REJECTION_CODES = /* @__PURE__ */ new Set([
|
|
|
9824
10121
|
ErrorCodes.SEARCH_INVALID_MODE,
|
|
9825
10122
|
ErrorCodes.SEARCH_INVALID_CURSOR,
|
|
9826
10123
|
ErrorCodes.SEARCH_RESULT_WINDOW_EXCEEDED,
|
|
10124
|
+
ErrorCodes.SEARCH_WORK_CAP_EXCEEDED,
|
|
9827
10125
|
ErrorCodes.VECTOR_DIMENSION_MISMATCH,
|
|
9828
10126
|
ErrorCodes.CONFIG_INVALID
|
|
9829
10127
|
]);
|
|
@@ -11506,6 +11804,10 @@ async function createClusterNode(config) {
|
|
|
11506
11804
|
onIndexClose: (indexName) => deleteIndexReplicationLogs(replicationLogs, indexName)
|
|
11507
11805
|
});
|
|
11508
11806
|
localEngine = engine;
|
|
11807
|
+
const snapshotSpool = await openSnapshotSpoolDirectory(config.engine?.durability?.directory).catch(async (err) => {
|
|
11808
|
+
await engine.shutdown();
|
|
11809
|
+
throw err;
|
|
11810
|
+
});
|
|
11509
11811
|
function replicationLogKey2(indexName, partitionId) {
|
|
11510
11812
|
return replicationLogKey(indexName, partitionId);
|
|
11511
11813
|
}
|
|
@@ -11565,7 +11867,8 @@ async function createClusterNode(config) {
|
|
|
11565
11867
|
resetReplicationLog: seedReplicationLog2,
|
|
11566
11868
|
applyReplicationEntry: engine.applyReplicationEntry,
|
|
11567
11869
|
restoreReplicationPartition: engine.restoreReplicationPartition,
|
|
11568
|
-
onError: forwardOnError
|
|
11870
|
+
onError: forwardOnError,
|
|
11871
|
+
snapshotSpoolDirectory: snapshotSpool.path
|
|
11569
11872
|
};
|
|
11570
11873
|
const primaryPartitionDeps = {
|
|
11571
11874
|
engine,
|
|
@@ -11697,6 +12000,7 @@ async function createClusterNode(config) {
|
|
|
11697
12000
|
coordinator: config.coordinator,
|
|
11698
12001
|
writeDeps,
|
|
11699
12002
|
snapshotSyncState: snapshotSyncHandlerState,
|
|
12003
|
+
snapshotSpoolDirectory: snapshotSpool.path,
|
|
11700
12004
|
resolveHeaderMetadata: resolveSnapshotHeaderMetadata,
|
|
11701
12005
|
isBootstrapSynced: (indexName, partitionId) => hasCompletedBootstrapSync(bootstrapSyncState, indexName, partitionId)
|
|
11702
12006
|
});
|
|
@@ -11728,28 +12032,16 @@ async function createClusterNode(config) {
|
|
|
11728
12032
|
}
|
|
11729
12033
|
await Promise.all([...heldPartitionWrites]);
|
|
11730
12034
|
await engine.shutdown();
|
|
12035
|
+
await snapshotSpool.dispose();
|
|
11731
12036
|
}
|
|
11732
12037
|
};
|
|
11733
12038
|
return node;
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
const release = localIndex !== void 0 ? await engine.acquireIndexForReplication(indexName) : null;
|
|
11741
|
-
try {
|
|
11742
|
-
const allocation = await config.coordinator.getAllocation(indexName);
|
|
11743
|
-
const assignment = allocation?.assignments.get(partitionId);
|
|
11744
|
-
const log = getReplicationLog2(indexName, partitionId);
|
|
11745
|
-
return {
|
|
11746
|
-
partitionId,
|
|
11747
|
-
primaryTerm: assignment?.primaryTerm ?? fallback.primaryTerm,
|
|
11748
|
-
lastSeqNo: log.newestSeqNo ?? 0
|
|
11749
|
-
};
|
|
11750
|
-
} finally {
|
|
11751
|
-
release?.();
|
|
11752
|
-
}
|
|
12039
|
+
function resolveSnapshotHeaderMetadata(indexName, partitionId) {
|
|
12040
|
+
return nodeSnapshotHeaderMetadata(
|
|
12041
|
+
{ coordinator: config.coordinator, engine, getReplicationLog: getReplicationLog2 },
|
|
12042
|
+
indexName,
|
|
12043
|
+
partitionId
|
|
12044
|
+
);
|
|
11753
12045
|
}
|
|
11754
12046
|
}
|
|
11755
12047
|
|
|
@@ -11852,6 +12144,8 @@ function clusterNodeEngine(node, options) {
|
|
|
11852
12144
|
rebuildAnalysis: () => unsupported("rebuildAnalysis"),
|
|
11853
12145
|
snapshot: () => unsupported("snapshot"),
|
|
11854
12146
|
restore: () => unsupported("restore"),
|
|
12147
|
+
exportDocuments: () => unsupported("exportDocuments"),
|
|
12148
|
+
importDocumentExport: () => unsupported("importDocumentExport"),
|
|
11855
12149
|
rebalance: () => unsupported("rebalance"),
|
|
11856
12150
|
updatePartitionConfig: () => unsupported("updatePartitionConfig"),
|
|
11857
12151
|
compactVectors: () => unsupported("compactVectors"),
|