@delali/narsil 0.2.4-next.43 → 0.2.4-next.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/filesystem.mjs +1 -1
- package/dist/adapters/indexeddb.mjs +1 -1
- package/dist/{chunk-TLRUUMVE.mjs → chunk-P4ELXVJQ.mjs} +1 -0
- package/dist/{chunk-KEDL6FFZ.mjs → chunk-WASNWIEK.mjs} +1142 -327
- package/dist/{chunk-YGQMKTKG.mjs → chunk-XNCJFU2E.mjs} +121 -27
- package/dist/{chunk-O4Q5JYNK.mjs → chunk-XWICBGYT.mjs} +1 -1
- package/dist/{client-B5uO5jL1.d.ts → client-C5BkUqB8.d.ts} +25 -2
- package/dist/client.d.ts +7 -7
- package/dist/client.mjs +11 -2
- package/dist/{config-NgUCkc6a.d.ts → config-B-ZI-ft5.d.ts} +2 -2
- package/dist/{config-B09-cud9.d.ts → config-BZRExglL.d.ts} +20 -3
- package/dist/{direct-executor-7O23GEHE.mjs → direct-executor-IMXNRS7O.mjs} +2 -2
- package/dist/distribution/coordinator/etcd.d.ts +2 -2
- package/dist/distribution/coordinator/etcd.mjs +1 -1
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/transport/grpc.mjs +1 -1
- package/dist/distribution.d.ts +24 -10
- package/dist/distribution.mjs +1023 -864
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-CjLhdDIm.d.ts → engine-Cjr1uBLa.d.ts} +28 -6
- package/dist/index.browser.mjs +1237 -330
- package/dist/index.d.ts +9 -9
- package/dist/index.mjs +4 -4
- package/dist/{options-BakECW-r.d.ts → options-BC68VPoE.d.ts} +9 -2
- package/dist/persistence/durability/checkpoint-worker.d.ts +3 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.mjs +1 -0
- package/dist/{schema-DXOd96kx.d.ts → schema-B7juWg8Q.d.ts} +1 -1
- package/dist/{search-BGouJxH4.d.ts → search-DDHjfXhJ.d.ts} +14 -3
- package/dist/server.d.ts +8 -8
- package/dist/server.mjs +27 -6
- package/dist/{types-BTE33VZS.d.ts → types-DsSSjY4d.d.ts} +1 -1
- package/dist/workers/entry.mjs +2 -2
- package/package.json +1 -1
package/dist/distribution.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createEngineCore, createNarsilFromCore, runEngineQueryStats, runEngineListDocuments, runEngineSuggest, runEnginePreflight, runEngineQuery, createEngineIndex, applyIndexEntry, applyDeleteEntry, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, now, listBindingOf, validateEntryBatchPayload, createAckMessage, validateEntryPayload, validateForwardBatchPayload, createForwardBatchResultMessage, queryBindingOf, foldGroupReducer, clampLimit, createForwardBatchMessage, validateForwardBatchResultPayload, createForwardMessage, validateReplicationEntry, MAX_SUGGEST_SCATTER_LIMIT, chunkByBudget, WIRE_BATCH_BUDGET, dedupePinnedEntries, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, createEntryMessage,
|
|
1
|
+
import { createEngineCore, createNarsilFromCore, runEngineQueryStats, runEngineListDocuments, runEngineSuggest, runEnginePreflight, runEngineQuery, createEngineIndex, applyIndexEntry, applyDeleteEntry, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, now, listBindingOf, validateEntryBatchPayload, createAckMessage, validateEntryPayload, validateForwardBatchPayload, createForwardBatchResultMessage, queryBindingOf, foldGroupReducer, clampLimit, createForwardBatchMessage, validateForwardBatchResultPayload, createForwardMessage, validateReplicationEntry, MAX_SUGGEST_SCATTER_LIMIT, chunkByBudget, WIRE_BATCH_BUDGET, computeEntryChecksum, dedupePinnedEntries, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, createEntryMessage, createEntryBatchMessage, createInsyncConfirmMessage, validateAckPayload, createInsyncRemoveMessage, validateInsyncConfirmPayload, createInsyncAddMessage } from './chunk-WASNWIEK.mjs';
|
|
2
2
|
import { ReplicationMessageTypes, TransportErrorCodes, ClusterMessageTypes, QueryMessageTypes, TransportError, MAX_MESSAGE_SIZE_BYTES } from './chunk-JQUL2T6I.mjs';
|
|
3
|
-
import { validateIndexName, indexConfigKey, putIndexMetadata, getIndexMetadata, MAX_PARTITION_COUNT, MAX_INDEX_NAME_LENGTH } from './chunk-
|
|
4
|
-
import { deserializePayloadV2, createPartitionIndex, resolveProjection, clampRowCount, sortSignatureOf, requireMatchingCursor, decodePageCursor, normalizeSort, encodePageCursor, applyProjection, readSortValues, getLanguage, DEFAULT_PAGE_SIZE, requireWithinResultWindow, fnv1a, FILTER_EXPRESSION_KEYS, MAX_CURSOR_LENGTH, mergePartitionStats, RESULT_WINDOW, FIELD_FILTER_OPERATORS, MAX_NESTING_DEPTH } from './chunk-
|
|
3
|
+
import { validateIndexName, indexConfigKey, putIndexMetadata, getIndexMetadata, MAX_PARTITION_COUNT, MAX_INDEX_NAME_LENGTH } from './chunk-XWICBGYT.mjs';
|
|
4
|
+
import { deserializePayloadV2, createPartitionIndex, resolveProjection, clampRowCount, sortSignatureOf, requireMatchingCursor, decodePageCursor, normalizeSort, encodePageCursor, applyProjection, readSortValues, getLanguage, DEFAULT_PAGE_SIZE, requireWithinResultWindow, fnv1a, FILTER_EXPRESSION_KEYS, MAX_CURSOR_LENGTH, mergePartitionStats, RESULT_WINDOW, FIELD_FILTER_OPERATORS, MAX_NESTING_DEPTH } from './chunk-XNCJFU2E.mjs';
|
|
5
5
|
import './chunk-TGP5M5R7.mjs';
|
|
6
6
|
import './chunk-OBXS7OZM.mjs';
|
|
7
7
|
import './chunk-WDL632CN.mjs';
|
|
@@ -9,7 +9,7 @@ import { crc32 } from './chunk-KMMJMDUA.mjs';
|
|
|
9
9
|
import './chunk-U2BKYEC7.mjs';
|
|
10
10
|
import { compareCodePoints, toComparableSortValue, compareComparableKeys, compareSortValues } from './chunk-BFIH7OV5.mjs';
|
|
11
11
|
import { generateId } from './chunk-WTONLDU3.mjs';
|
|
12
|
-
import { ErrorCodes, NarsilError, describeError } from './chunk-
|
|
12
|
+
import { ErrorCodes, NarsilError, describeError } from './chunk-P4ELXVJQ.mjs';
|
|
13
13
|
import { encode, decode } from '@msgpack/msgpack';
|
|
14
14
|
|
|
15
15
|
// src/distribution/cluster/controller/types.ts
|
|
@@ -4618,7 +4618,7 @@ function batchBytes(entries) {
|
|
|
4618
4618
|
function replicaHasFallenOutOfRetention(log, cursor) {
|
|
4619
4619
|
const oldest = log.oldestSeqNo;
|
|
4620
4620
|
if (oldest === void 0) {
|
|
4621
|
-
return
|
|
4621
|
+
return cursor.appliedSeqNo < log.localLogEnd;
|
|
4622
4622
|
}
|
|
4623
4623
|
return cursor.appliedSeqNo + 1 < oldest;
|
|
4624
4624
|
}
|
|
@@ -4844,473 +4844,766 @@ async function writeHeldPartitions(core, indexName, next, indexesBehindOnDisk) {
|
|
|
4844
4844
|
indexesBehindOnDisk.delete(indexName);
|
|
4845
4845
|
}
|
|
4846
4846
|
|
|
4847
|
-
// src/distribution/
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
await core.invalidation.start();
|
|
4855
|
-
}
|
|
4856
|
-
await core.analysisRebuild.reviewStaleIndexes();
|
|
4857
|
-
const engine = createNarsilFromCore(core, config);
|
|
4858
|
-
const heldPartitions = createHeldPartitionRecord(core);
|
|
4859
|
-
return Object.assign(engine, {
|
|
4860
|
-
createIndexWithUuid: (name, indexConfig, indexUuid) => createEngineIndex(core, config, name, indexConfig, indexUuid),
|
|
4861
|
-
indexUuidOf: (indexName) => core.indexRegistry.get(indexName)?.indexUuid,
|
|
4862
|
-
stampIndexUuid: (indexName, indexUuid) => stampIndexUuid(core, indexName, indexUuid),
|
|
4863
|
-
highestPersistedSeqNoOf: (indexName, partitionId) => core.durability?.manager.highestPersistedSeqNo(indexName, partitionId) ?? 0,
|
|
4864
|
-
heldPartitionsOf: (indexName) => heldPartitions.held(indexName),
|
|
4865
|
-
recordHeldPartition: (indexName, partitionId) => heldPartitions.record(indexName, partitionId),
|
|
4866
|
-
forgetHeldPartition: (indexName, partitionId) => heldPartitions.forget(indexName, partitionId),
|
|
4867
|
-
applyReplicationEntry: (entry) => applyReplicationEntry(core, entry),
|
|
4868
|
-
serializeReplicationPartition: (indexName, partitionId) => serializeReplicationPartition(core, indexName, partitionId),
|
|
4869
|
-
restoreReplicationPartition: (indexName, partitionId, bytes, schema, partitionCount) => restoreReplicationPartition(core, engine, indexName, partitionId, bytes, schema, partitionCount),
|
|
4870
|
-
queryPartitions: (indexName, params, partitionIds, globalStats) => runEngineQuery(core, indexName, params, { partitionIds, globalStats }),
|
|
4871
|
-
preflightPartitions: (indexName, params, partitionIds) => runEnginePreflight(core, indexName, params, { partitionIds }),
|
|
4872
|
-
suggestPartitions: (indexName, params, partitionIds) => runEngineSuggest(core, indexName, params, partitionIds),
|
|
4873
|
-
listPartitions: (indexName, params, partitionIds) => runEngineListDocuments(core, indexName, params, partitionIds),
|
|
4874
|
-
collectQueryStats: (indexName, terms, partitionIds) => runEngineQueryStats(core, indexName, terms, partitionIds)
|
|
4875
|
-
});
|
|
4876
|
-
}
|
|
4877
|
-
async function stampIndexUuid(core, indexName, indexUuid) {
|
|
4878
|
-
const entry = core.indexRegistry.get(indexName);
|
|
4879
|
-
if (entry === void 0 || entry.indexUuid === indexUuid) {
|
|
4880
|
-
return;
|
|
4881
|
-
}
|
|
4882
|
-
core.indexRegistry.set(indexName, { ...entry, indexUuid });
|
|
4883
|
-
if (core.durability !== null) {
|
|
4884
|
-
await core.durability.manager.persistMetadata(indexName);
|
|
4885
|
-
}
|
|
4886
|
-
}
|
|
4887
|
-
async function serializeReplicationPartition(core, indexName, partitionId) {
|
|
4888
|
-
core.guardShutdown();
|
|
4889
|
-
return core.requireManager(indexName).serializePartitionToBytes(partitionId);
|
|
4890
|
-
}
|
|
4891
|
-
async function restoreReplicationPartition(core, engine, indexName, partitionId, bytes, schema, partitionCount) {
|
|
4892
|
-
core.guardShutdown();
|
|
4893
|
-
validatePartitionRestoreTarget(indexName, partitionId, partitionCount);
|
|
4894
|
-
let partition;
|
|
4895
|
-
try {
|
|
4896
|
-
partition = deserializePayloadV2(bytes);
|
|
4897
|
-
} catch (err) {
|
|
4898
|
-
const cause = err instanceof Error ? err.message : String(err);
|
|
4899
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, `partition snapshot decode failed: ${cause}`, {
|
|
4900
|
-
indexName,
|
|
4901
|
-
partitionId,
|
|
4902
|
-
cause
|
|
4903
|
-
});
|
|
4904
|
-
}
|
|
4905
|
-
validatePartitionSnapshotPayload(indexName, partitionId, partitionCount, schema, partition);
|
|
4906
|
-
const trackPositions = core.indexRegistry.get(indexName)?.config.trackPositions ?? true;
|
|
4907
|
-
createPartitionIndex(partitionId, trackPositions).deserialize(partition, schema);
|
|
4908
|
-
const restoreIndex = await ensureReplicationIndex(core, engine, indexName, schema, partition.language, partitionCount);
|
|
4909
|
-
if (restoreIndex.created) {
|
|
4910
|
-
await core.orchestrator.replicateToWorkers({
|
|
4911
|
-
type: "createIndex",
|
|
4912
|
-
indexName,
|
|
4913
|
-
config: restoreIndex.config,
|
|
4914
|
-
requestId: `replicate-partition-index-${indexName}`
|
|
4915
|
-
});
|
|
4916
|
-
}
|
|
4917
|
-
const manager = core.requireManager(indexName);
|
|
4918
|
-
manager.deserializePartition(partitionId, partition);
|
|
4919
|
-
await core.orchestrator.replicateToWorkers({
|
|
4920
|
-
type: "deserialize",
|
|
4921
|
-
indexName,
|
|
4922
|
-
partitionId,
|
|
4923
|
-
data: partition,
|
|
4924
|
-
requestId: `replicate-partition-restore-${indexName}-${partitionId}`
|
|
4925
|
-
});
|
|
4926
|
-
}
|
|
4927
|
-
function validatePartitionRestoreTarget(indexName, partitionId, partitionCount) {
|
|
4928
|
-
if (!Number.isInteger(partitionId) || partitionId < 0) {
|
|
4929
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partitionId must be a non-negative integer", {
|
|
4930
|
-
indexName,
|
|
4931
|
-
partitionId
|
|
4932
|
-
});
|
|
4933
|
-
}
|
|
4934
|
-
if (!Number.isInteger(partitionCount) || partitionCount <= 0 || partitionCount > MAX_PARTITION_COUNT) {
|
|
4935
|
-
throw new NarsilError(
|
|
4936
|
-
ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED,
|
|
4937
|
-
`partitionCount must be an integer between 1 and ${MAX_PARTITION_COUNT}`,
|
|
4938
|
-
{ indexName, partitionCount }
|
|
4939
|
-
);
|
|
4940
|
-
}
|
|
4941
|
-
if (partitionId >= partitionCount) {
|
|
4942
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partitionId is outside partitionCount", {
|
|
4943
|
-
indexName,
|
|
4944
|
-
partitionId,
|
|
4945
|
-
partitionCount
|
|
4946
|
-
});
|
|
4947
|
-
}
|
|
4847
|
+
// src/distribution/replication/types.ts
|
|
4848
|
+
var DEFAULT_LOG_RETENTION_BYTES = 268435456;
|
|
4849
|
+
|
|
4850
|
+
// src/distribution/replication/log.ts
|
|
4851
|
+
var ENTRY_FIXED_OVERHEAD_BYTES2 = 40;
|
|
4852
|
+
function estimateEntrySize(entry) {
|
|
4853
|
+
return ENTRY_FIXED_OVERHEAD_BYTES2 + entry.indexName.length + entry.documentId.length + (entry.document?.byteLength ?? 0);
|
|
4948
4854
|
}
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
});
|
|
4962
|
-
}
|
|
4963
|
-
if (partition.totalPartitions !== partitionCount) {
|
|
4964
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot partition count mismatch", {
|
|
4965
|
-
indexName,
|
|
4966
|
-
partitionId,
|
|
4967
|
-
expectedPartitionCount: partitionCount,
|
|
4968
|
-
receivedPartitionCount: partition.totalPartitions
|
|
4969
|
-
});
|
|
4970
|
-
}
|
|
4971
|
-
const expectedSchema = flattenSchema(schema);
|
|
4972
|
-
for (const [field, expectedType] of Object.entries(expectedSchema)) {
|
|
4973
|
-
if (partition.schema[field] !== expectedType) {
|
|
4974
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot schema mismatch", {
|
|
4975
|
-
indexName,
|
|
4976
|
-
partitionId,
|
|
4977
|
-
field,
|
|
4978
|
-
expected: expectedType,
|
|
4979
|
-
received: partition.schema[field] ?? "(absent)"
|
|
4980
|
-
});
|
|
4981
|
-
}
|
|
4855
|
+
var computeChecksum = computeEntryChecksum;
|
|
4856
|
+
function createReplicationLog(partitionId, config) {
|
|
4857
|
+
const retentionBytes = config?.logRetentionBytes ?? DEFAULT_LOG_RETENTION_BYTES;
|
|
4858
|
+
let nextSeqNo = config?.startSeqNo ?? 1;
|
|
4859
|
+
let localLogEnd = nextSeqNo - 1;
|
|
4860
|
+
let localLogEndPrimaryTerm = config?.lastPrimaryTerm ?? 0;
|
|
4861
|
+
let commitPoint = nextSeqNo - 1;
|
|
4862
|
+
let entries = [];
|
|
4863
|
+
let headIndex = 0;
|
|
4864
|
+
let totalSizeBytes = 0;
|
|
4865
|
+
function liveCount() {
|
|
4866
|
+
return entries.length - headIndex;
|
|
4982
4867
|
}
|
|
4983
|
-
|
|
4984
|
-
if (
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
partitionId,
|
|
4988
|
-
field
|
|
4989
|
-
});
|
|
4868
|
+
function compact() {
|
|
4869
|
+
if (headIndex > 0) {
|
|
4870
|
+
entries = entries.slice(headIndex);
|
|
4871
|
+
headIndex = 0;
|
|
4990
4872
|
}
|
|
4991
4873
|
}
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
await engine.createIndex(indexName, indexConfig);
|
|
5005
|
-
created = true;
|
|
5006
|
-
} catch (err) {
|
|
5007
|
-
if (!(err instanceof NarsilError) || err.code !== ErrorCodes.INDEX_ALREADY_EXISTS) {
|
|
5008
|
-
throw err;
|
|
4874
|
+
function findIndexBySeqNo(seqNo) {
|
|
4875
|
+
let low = headIndex;
|
|
4876
|
+
let high = entries.length - 1;
|
|
4877
|
+
while (low <= high) {
|
|
4878
|
+
const mid = low + high >>> 1;
|
|
4879
|
+
const midSeqNo = entries[mid].seqNo;
|
|
4880
|
+
if (midSeqNo < seqNo) {
|
|
4881
|
+
low = mid + 1;
|
|
4882
|
+
} else if (midSeqNo > seqNo) {
|
|
4883
|
+
high = mid - 1;
|
|
4884
|
+
} else {
|
|
4885
|
+
return mid;
|
|
5009
4886
|
}
|
|
5010
4887
|
}
|
|
4888
|
+
return low;
|
|
5011
4889
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
indexName,
|
|
5025
|
-
localPartitionCount: manager.partitionCount,
|
|
5026
|
-
allocationPartitionCount: partitionCount
|
|
5027
|
-
});
|
|
5028
|
-
}
|
|
5029
|
-
if (entry.config.partitions === void 0) {
|
|
5030
|
-
entry.config.partitions = { maxPartitions: partitionCount };
|
|
5031
|
-
} else if ((entry.config.partitions.maxPartitions ?? manager.partitionCount) < partitionCount) {
|
|
5032
|
-
entry.config.partitions.maxPartitions = partitionCount;
|
|
4890
|
+
function evictOldEntries() {
|
|
4891
|
+
while (liveCount() > 0 && totalSizeBytes > retentionBytes) {
|
|
4892
|
+
totalSizeBytes -= estimateEntrySize(entries[headIndex]);
|
|
4893
|
+
entries[headIndex] = null;
|
|
4894
|
+
headIndex += 1;
|
|
4895
|
+
}
|
|
4896
|
+
if (totalSizeBytes < 0) {
|
|
4897
|
+
totalSizeBytes = 0;
|
|
4898
|
+
}
|
|
4899
|
+
if (headIndex >= 256 || headIndex > liveCount()) {
|
|
4900
|
+
compact();
|
|
4901
|
+
}
|
|
5033
4902
|
}
|
|
5034
|
-
|
|
5035
|
-
|
|
4903
|
+
function assertEntryFitsRetention(entry) {
|
|
4904
|
+
const entrySize = ENTRY_FIXED_OVERHEAD_BYTES2 + entry.indexName.length + entry.documentId.length + (entry.document?.byteLength ?? 0);
|
|
4905
|
+
if (entrySize > retentionBytes) {
|
|
4906
|
+
throw new NarsilError(
|
|
4907
|
+
ErrorCodes.REPLICATION_LOG_FULL,
|
|
4908
|
+
`Replication log entry of ${entrySize} bytes exceeds the log retention limit of ${retentionBytes} bytes and cannot be buffered`,
|
|
4909
|
+
{ partitionId, entrySize, retentionBytes, seqNo: entry.seqNo }
|
|
4910
|
+
);
|
|
4911
|
+
}
|
|
5036
4912
|
}
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
for (const [field, expectedType] of Object.entries(expectedSchema)) {
|
|
5043
|
-
if (actualSchema[field] !== expectedType) {
|
|
5044
|
-
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "local index schema mismatch", {
|
|
5045
|
-
indexName,
|
|
5046
|
-
field,
|
|
5047
|
-
expected: expectedType,
|
|
5048
|
-
received: actualSchema[field] ?? "(absent)"
|
|
5049
|
-
});
|
|
4913
|
+
function storeEntry(entry) {
|
|
4914
|
+
entries.push(entry);
|
|
4915
|
+
if (entry.seqNo >= localLogEnd) {
|
|
4916
|
+
localLogEnd = entry.seqNo;
|
|
4917
|
+
localLogEndPrimaryTerm = entry.primaryTerm;
|
|
5050
4918
|
}
|
|
4919
|
+
totalSizeBytes += estimateEntrySize(entry);
|
|
4920
|
+
evictOldEntries();
|
|
4921
|
+
return { ...entry };
|
|
5051
4922
|
}
|
|
5052
|
-
|
|
5053
|
-
if (
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
});
|
|
4923
|
+
function readEntry(seqNo) {
|
|
4924
|
+
if (liveCount() === 0) return void 0;
|
|
4925
|
+
const idx = findIndexBySeqNo(seqNo);
|
|
4926
|
+
if (idx < entries.length && entries[idx].seqNo === seqNo) {
|
|
4927
|
+
return { ...entries[idx] };
|
|
5058
4928
|
}
|
|
4929
|
+
return void 0;
|
|
5059
4930
|
}
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
continue;
|
|
5072
|
-
}
|
|
5073
|
-
flat[path] = value;
|
|
4931
|
+
function verifyEntryChecksum(entry) {
|
|
4932
|
+
const expected = computeChecksum({
|
|
4933
|
+
seqNo: entry.seqNo,
|
|
4934
|
+
primaryTerm: entry.primaryTerm,
|
|
4935
|
+
operation: entry.operation,
|
|
4936
|
+
partitionId: entry.partitionId,
|
|
4937
|
+
indexName: entry.indexName,
|
|
4938
|
+
documentId: entry.documentId,
|
|
4939
|
+
document: entry.document
|
|
4940
|
+
});
|
|
4941
|
+
return expected === entry.checksum;
|
|
5074
4942
|
}
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
4943
|
+
return {
|
|
4944
|
+
append(partial) {
|
|
4945
|
+
assertEntryFitsRetention(partial);
|
|
4946
|
+
const seqNo = nextSeqNo;
|
|
4947
|
+
nextSeqNo += 1;
|
|
4948
|
+
const entryWithoutChecksum = {
|
|
4949
|
+
seqNo,
|
|
4950
|
+
primaryTerm: partial.primaryTerm,
|
|
4951
|
+
operation: partial.operation,
|
|
4952
|
+
partitionId,
|
|
4953
|
+
indexName: partial.indexName,
|
|
4954
|
+
documentId: partial.documentId,
|
|
4955
|
+
document: partial.document
|
|
4956
|
+
};
|
|
4957
|
+
const checksum = computeChecksum(entryWithoutChecksum);
|
|
4958
|
+
const entry = { ...entryWithoutChecksum, checksum };
|
|
4959
|
+
return storeEntry(entry);
|
|
4960
|
+
},
|
|
4961
|
+
appendCommitted(entry) {
|
|
4962
|
+
if (!Number.isSafeInteger(entry.seqNo) || entry.seqNo < 1) {
|
|
4963
|
+
throw new NarsilError(
|
|
4964
|
+
ErrorCodes.REPLICATION_ENTRY_INVALID,
|
|
4965
|
+
`Invalid replication sequence number ${entry.seqNo}`,
|
|
4966
|
+
{
|
|
4967
|
+
seqNo: entry.seqNo,
|
|
4968
|
+
partitionId
|
|
4969
|
+
}
|
|
4970
|
+
);
|
|
4971
|
+
}
|
|
4972
|
+
if (entry.partitionId !== partitionId) {
|
|
4973
|
+
throw new NarsilError(
|
|
4974
|
+
ErrorCodes.REPLICATION_ENTRY_INVALID,
|
|
4975
|
+
`Replication entry partition ${entry.partitionId} does not match log partition ${partitionId}`,
|
|
4976
|
+
{ entryPartitionId: entry.partitionId, logPartitionId: partitionId }
|
|
4977
|
+
);
|
|
4978
|
+
}
|
|
4979
|
+
const existing = readEntry(entry.seqNo);
|
|
4980
|
+
if (existing !== void 0) {
|
|
4981
|
+
if (existing.checksum !== entry.checksum) {
|
|
4982
|
+
throw new NarsilError(
|
|
4983
|
+
ErrorCodes.REPLICATION_ENTRY_INVALID,
|
|
4984
|
+
`Conflicting replication entry at sequence number ${entry.seqNo}`,
|
|
4985
|
+
{ seqNo: entry.seqNo, partitionId }
|
|
4986
|
+
);
|
|
4987
|
+
}
|
|
4988
|
+
return existing;
|
|
4989
|
+
}
|
|
4990
|
+
if (entry.seqNo !== nextSeqNo) {
|
|
4991
|
+
throw new NarsilError(
|
|
4992
|
+
ErrorCodes.REPLICATION_ENTRY_INVALID,
|
|
4993
|
+
`Out-of-order replication entry ${entry.seqNo}; expected ${nextSeqNo}`,
|
|
4994
|
+
{ seqNo: entry.seqNo, expectedSeqNo: nextSeqNo, partitionId }
|
|
4995
|
+
);
|
|
4996
|
+
}
|
|
4997
|
+
if (!verifyEntryChecksum(entry)) {
|
|
4998
|
+
throw new NarsilError(
|
|
4999
|
+
ErrorCodes.REPLICATION_ENTRY_CORRUPT,
|
|
5000
|
+
`Invalid checksum for replication entry ${entry.seqNo}`,
|
|
5001
|
+
{ seqNo: entry.seqNo, partitionId }
|
|
5002
|
+
);
|
|
5003
|
+
}
|
|
5004
|
+
assertEntryFitsRetention(entry);
|
|
5005
|
+
nextSeqNo = entry.seqNo + 1;
|
|
5006
|
+
return storeEntry({ ...entry });
|
|
5007
|
+
},
|
|
5008
|
+
getEntriesFrom(fromSeqNo) {
|
|
5009
|
+
if (liveCount() === 0) return [];
|
|
5010
|
+
const idx = findIndexBySeqNo(fromSeqNo);
|
|
5011
|
+
if (idx >= entries.length) return [];
|
|
5012
|
+
return entries.slice(idx).map((e) => ({ ...e }));
|
|
5013
|
+
},
|
|
5014
|
+
getEntry(seqNo) {
|
|
5015
|
+
return readEntry(seqNo);
|
|
5016
|
+
},
|
|
5017
|
+
verifyChecksum(entry) {
|
|
5018
|
+
return verifyEntryChecksum(entry);
|
|
5019
|
+
},
|
|
5020
|
+
get localLogEnd() {
|
|
5021
|
+
return localLogEnd;
|
|
5022
|
+
},
|
|
5023
|
+
get localLogEndPrimaryTerm() {
|
|
5024
|
+
return localLogEndPrimaryTerm;
|
|
5025
|
+
},
|
|
5026
|
+
get commitPoint() {
|
|
5027
|
+
return commitPoint;
|
|
5028
|
+
},
|
|
5029
|
+
advanceCommitPoint(seqNo) {
|
|
5030
|
+
const bounded = Math.min(seqNo, localLogEnd);
|
|
5031
|
+
if (bounded > commitPoint) {
|
|
5032
|
+
commitPoint = bounded;
|
|
5033
|
+
}
|
|
5034
|
+
},
|
|
5035
|
+
get oldestSeqNo() {
|
|
5036
|
+
if (liveCount() === 0) return void 0;
|
|
5037
|
+
return entries[headIndex].seqNo;
|
|
5038
|
+
},
|
|
5039
|
+
get newestSeqNo() {
|
|
5040
|
+
if (liveCount() === 0) return void 0;
|
|
5041
|
+
return entries[entries.length - 1].seqNo;
|
|
5042
|
+
},
|
|
5043
|
+
get entryCount() {
|
|
5044
|
+
return liveCount();
|
|
5045
|
+
},
|
|
5046
|
+
get sizeBytes() {
|
|
5047
|
+
return totalSizeBytes;
|
|
5048
|
+
},
|
|
5049
|
+
clear() {
|
|
5050
|
+
entries = [];
|
|
5051
|
+
headIndex = 0;
|
|
5052
|
+
totalSizeBytes = 0;
|
|
5053
|
+
localLogEnd = nextSeqNo - 1;
|
|
5054
|
+
commitPoint = nextSeqNo - 1;
|
|
5095
5055
|
}
|
|
5096
|
-
|
|
5097
|
-
}
|
|
5098
|
-
applyDeleteEntry(entry, manager, vecIndexes);
|
|
5099
|
-
await core.orchestrator.replicateToWorkers({
|
|
5100
|
-
type: "remove",
|
|
5101
|
-
indexName: entry.indexName,
|
|
5102
|
-
docId: entry.documentId,
|
|
5103
|
-
requestId: `replicate-entry-remove-${entry.indexName}-${entry.partitionId}-${entry.seqNo}`
|
|
5104
|
-
});
|
|
5056
|
+
};
|
|
5105
5057
|
}
|
|
5106
5058
|
|
|
5107
|
-
// src/distribution/cluster-node/
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
for (const index of deps.engine.listIndexes()) {
|
|
5111
|
-
const disposition = await reconcileOne(deps, index.name);
|
|
5112
|
-
dispositions.set(index.name, disposition);
|
|
5113
|
-
}
|
|
5114
|
-
return dispositions;
|
|
5059
|
+
// src/distribution/cluster-node/replication-logs.ts
|
|
5060
|
+
function replicationLogKey(indexName, partitionId) {
|
|
5061
|
+
return `${indexName}:${partitionId}`;
|
|
5115
5062
|
}
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
return;
|
|
5063
|
+
function getReplicationLog(replicationLogs, indexName, partitionId, replicationConfig) {
|
|
5064
|
+
const key = replicationLogKey(indexName, partitionId);
|
|
5065
|
+
let log = replicationLogs.get(key);
|
|
5066
|
+
if (log === void 0) {
|
|
5067
|
+
log = createReplicationLog(partitionId, { logRetentionBytes: replicationConfig?.logRetentionBytes });
|
|
5068
|
+
replicationLogs.set(key, log);
|
|
5123
5069
|
}
|
|
5124
|
-
|
|
5070
|
+
return log;
|
|
5125
5071
|
}
|
|
5126
|
-
function
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5072
|
+
function seedReplicationLog(replicationLogs, indexName, partitionId, startSeqNo, lastPrimaryTerm = 0, replicationConfig) {
|
|
5073
|
+
replicationLogs.set(
|
|
5074
|
+
replicationLogKey(indexName, partitionId),
|
|
5075
|
+
createReplicationLog(partitionId, {
|
|
5076
|
+
startSeqNo,
|
|
5077
|
+
lastPrimaryTerm,
|
|
5078
|
+
logRetentionBytes: replicationConfig?.logRetentionBytes
|
|
5079
|
+
})
|
|
5131
5080
|
);
|
|
5132
5081
|
}
|
|
5133
|
-
|
|
5134
|
-
const
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
clusterUuid = (await getIndexMetadata(deps.coordinator, indexName))?.indexUuid ?? null;
|
|
5138
|
-
} catch (error) {
|
|
5139
|
-
report(deps, indexName, `its metadata could not be read: ${describeError(error)}`);
|
|
5140
|
-
return "orphaned";
|
|
5141
|
-
}
|
|
5142
|
-
if (clusterUuid === null) {
|
|
5143
|
-
report(deps, indexName, "the coordinator holds no metadata for that name");
|
|
5144
|
-
return "orphaned";
|
|
5145
|
-
}
|
|
5146
|
-
if (localUuid === clusterUuid) {
|
|
5147
|
-
return "adopted";
|
|
5082
|
+
function deleteIndexReplicationLogs(replicationLogs, indexName) {
|
|
5083
|
+
const prefix = `${indexName}:`;
|
|
5084
|
+
for (const key of replicationLogs.keys()) {
|
|
5085
|
+
if (key.startsWith(prefix)) replicationLogs.delete(key);
|
|
5148
5086
|
}
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5087
|
+
}
|
|
5088
|
+
|
|
5089
|
+
// src/distribution/cluster-node/local-replication.ts
|
|
5090
|
+
async function seedReopenedPrimaryLogs(input) {
|
|
5091
|
+
const allocation = await input.coordinator.getAllocation(input.indexName);
|
|
5092
|
+
if (allocation === null) return;
|
|
5093
|
+
for (const partitionId of input.engine.heldPartitionsOf(input.indexName) ?? []) {
|
|
5094
|
+
const assignment = allocation.assignments.get(partitionId);
|
|
5095
|
+
if (assignment?.primary !== input.nodeId) continue;
|
|
5096
|
+
const floor = Math.max(input.engine.highestPersistedSeqNoOf(input.indexName, partitionId), assignment.commitPoint);
|
|
5097
|
+
seedReplicationLog(
|
|
5098
|
+
input.replicationLogs,
|
|
5099
|
+
input.indexName,
|
|
5100
|
+
partitionId,
|
|
5101
|
+
floor + 1,
|
|
5102
|
+
assignment.primaryTerm,
|
|
5103
|
+
input.replicationConfig
|
|
5104
|
+
);
|
|
5152
5105
|
}
|
|
5153
|
-
await deps.engine.dropIndex(indexName);
|
|
5154
|
-
return "superseded";
|
|
5155
5106
|
}
|
|
5156
|
-
function
|
|
5157
|
-
|
|
5158
|
-
|
|
5107
|
+
async function applyLocalReplicationEntry(core, entry) {
|
|
5108
|
+
core.guardShutdown();
|
|
5109
|
+
const release = await core.indexState.acquire(entry.indexName);
|
|
5110
|
+
try {
|
|
5111
|
+
const indexEntry = core.requireIndex(entry.indexName);
|
|
5112
|
+
const manager = core.requireManager(entry.indexName);
|
|
5113
|
+
const vecIndexes = manager.getVectorIndexes();
|
|
5114
|
+
if (entry.operation === "INDEX") {
|
|
5115
|
+
applyIndexEntry(entry, manager, indexEntry.vectorFieldPaths, vecIndexes);
|
|
5116
|
+
const appliedDocument = manager.get(entry.documentId);
|
|
5117
|
+
if (appliedDocument !== void 0) {
|
|
5118
|
+
await core.orchestrator.replicateToWorkers({
|
|
5119
|
+
type: "insert",
|
|
5120
|
+
indexName: entry.indexName,
|
|
5121
|
+
docId: entry.documentId,
|
|
5122
|
+
document: appliedDocument,
|
|
5123
|
+
requestId: `replicate-entry-insert-${entry.indexName}-${entry.partitionId}-${entry.seqNo}`
|
|
5124
|
+
});
|
|
5125
|
+
}
|
|
5126
|
+
return;
|
|
5127
|
+
}
|
|
5128
|
+
applyDeleteEntry(entry, manager, vecIndexes);
|
|
5129
|
+
await core.orchestrator.replicateToWorkers({
|
|
5130
|
+
type: "remove",
|
|
5131
|
+
indexName: entry.indexName,
|
|
5132
|
+
docId: entry.documentId,
|
|
5133
|
+
requestId: `replicate-entry-remove-${entry.indexName}-${entry.partitionId}-${entry.seqNo}`
|
|
5134
|
+
});
|
|
5135
|
+
} finally {
|
|
5136
|
+
release();
|
|
5159
5137
|
}
|
|
5160
|
-
deps.onError(
|
|
5161
|
-
new NarsilError(
|
|
5162
|
-
ErrorCodes.INDEX_ORPHANED,
|
|
5163
|
-
`Node '${deps.nodeId}' holds a local copy of index '${indexName}' the cluster does not claim, because ${reason}. The node keeps the data and serves none of it until an operator drops it.`,
|
|
5164
|
-
{ indexName, nodeId: deps.nodeId }
|
|
5165
|
-
)
|
|
5166
|
-
);
|
|
5167
5138
|
}
|
|
5168
5139
|
|
|
5169
|
-
// src/distribution/cluster-node/
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
allocation = await coordinator.getAllocation(indexName);
|
|
5175
|
-
} catch (err) {
|
|
5176
|
-
const errMessage = err instanceof Error ? err.message : String(err);
|
|
5177
|
-
return {
|
|
5178
|
-
outcome: "denied",
|
|
5179
|
-
code: ErrorCodes.SNAPSHOT_SYNC_ALLOCATION_UNAVAILABLE,
|
|
5180
|
-
reason: `allocation lookup failed: ${errMessage}`
|
|
5181
|
-
};
|
|
5140
|
+
// src/distribution/cluster-node/local-engine.ts
|
|
5141
|
+
async function createClusterLocalEngine(config, hooks) {
|
|
5142
|
+
const core = createEngineCore(config, hooks);
|
|
5143
|
+
if (core.durability !== null) {
|
|
5144
|
+
await core.durability.manager.recover(config?.lifecycle !== void 0);
|
|
5182
5145
|
}
|
|
5183
|
-
if (
|
|
5184
|
-
|
|
5185
|
-
outcome: "denied",
|
|
5186
|
-
code: ErrorCodes.SNAPSHOT_SYNC_UNAUTHORIZED,
|
|
5187
|
-
reason: `index '${indexName}' has no allocation`
|
|
5188
|
-
};
|
|
5146
|
+
if (core.invalidation !== null) {
|
|
5147
|
+
await core.invalidation.start();
|
|
5189
5148
|
}
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5149
|
+
if (config?.lifecycle === void 0) await core.analysisRebuild.reviewStaleIndexes();
|
|
5150
|
+
const engine = createNarsilFromCore(core, config);
|
|
5151
|
+
const heldPartitions = createHeldPartitionRecord(core);
|
|
5152
|
+
return Object.assign(engine, {
|
|
5153
|
+
createIndexWithUuid: (name, indexConfig, indexUuid) => createEngineIndex(core, config, name, indexConfig, indexUuid),
|
|
5154
|
+
acquireIndexForReplication: (indexName) => core.indexState.acquire(indexName, false),
|
|
5155
|
+
indexUuidOf: (indexName) => core.indexRegistry.get(indexName)?.indexUuid,
|
|
5156
|
+
stampIndexUuid: (indexName, indexUuid) => stampIndexUuid(core, indexName, indexUuid),
|
|
5157
|
+
highestPersistedSeqNoOf: (indexName, partitionId) => core.durability?.manager.highestPersistedSeqNo(indexName, partitionId) ?? 0,
|
|
5158
|
+
heldPartitionsOf: (indexName) => heldPartitions.held(indexName),
|
|
5159
|
+
recordHeldPartition: (indexName, partitionId) => heldPartitions.record(indexName, partitionId),
|
|
5160
|
+
forgetHeldPartition: (indexName, partitionId) => heldPartitions.forget(indexName, partitionId),
|
|
5161
|
+
applyReplicationEntry: (entry) => applyLocalReplicationEntry(core, entry),
|
|
5162
|
+
serializeReplicationPartition: (indexName, partitionId) => serializeReplicationPartition(core, indexName, partitionId),
|
|
5163
|
+
restoreReplicationPartition: (indexName, partitionId, bytes, schema, partitionCount) => restoreReplicationPartition(core, engine, indexName, partitionId, bytes, schema, partitionCount),
|
|
5164
|
+
queryPartitions: (indexName, params, partitionIds, globalStats) => runEngineQuery(core, indexName, params, { partitionIds, globalStats }),
|
|
5165
|
+
preflightPartitions: (indexName, params, partitionIds) => runEnginePreflight(core, indexName, params, { partitionIds }),
|
|
5166
|
+
suggestPartitions: (indexName, params, partitionIds) => runEngineSuggest(core, indexName, params, partitionIds),
|
|
5167
|
+
listPartitions: (indexName, params, partitionIds) => runEngineListDocuments(core, indexName, params, partitionIds),
|
|
5168
|
+
collectQueryStats: (indexName, terms, partitionIds) => runEngineQueryStats(core, indexName, terms, partitionIds),
|
|
5169
|
+
partitionStatsForRead: async (indexName) => {
|
|
5170
|
+
const release = await core.indexState.acquire(indexName);
|
|
5171
|
+
try {
|
|
5172
|
+
return core.requireManager(indexName).getPartitionStats();
|
|
5173
|
+
} finally {
|
|
5174
|
+
release();
|
|
5175
|
+
}
|
|
5199
5176
|
}
|
|
5200
|
-
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
async function stampIndexUuid(core, indexName, indexUuid) {
|
|
5180
|
+
const entry = core.indexRegistry.get(indexName);
|
|
5181
|
+
if (entry === void 0 || entry.indexUuid === indexUuid) {
|
|
5182
|
+
return;
|
|
5201
5183
|
}
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
code: ErrorCodes.SNAPSHOT_SYNC_NOT_ASSIGNED,
|
|
5206
|
-
reason: `node '${sourceId}' is listed for index '${indexName}' but not in a bootstrappable state`
|
|
5207
|
-
};
|
|
5184
|
+
core.indexRegistry.set(indexName, { ...entry, indexUuid });
|
|
5185
|
+
if (core.durability !== null) {
|
|
5186
|
+
await core.durability.manager.persistMetadata(indexName);
|
|
5208
5187
|
}
|
|
5209
|
-
return {
|
|
5210
|
-
outcome: "denied",
|
|
5211
|
-
code: ErrorCodes.SNAPSHOT_SYNC_UNAUTHORIZED,
|
|
5212
|
-
reason: `node '${sourceId}' is not an assigned replica for index '${indexName}'`
|
|
5213
|
-
};
|
|
5214
5188
|
}
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "maxConcurrent must be a positive integer", {
|
|
5223
|
-
maxConcurrent
|
|
5224
|
-
});
|
|
5189
|
+
async function serializeReplicationPartition(core, indexName, partitionId) {
|
|
5190
|
+
core.guardShutdown();
|
|
5191
|
+
const release = await core.indexState.acquire(indexName, false);
|
|
5192
|
+
try {
|
|
5193
|
+
return core.requireManager(indexName).serializePartitionToBytes(partitionId);
|
|
5194
|
+
} finally {
|
|
5195
|
+
release();
|
|
5225
5196
|
}
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5197
|
+
}
|
|
5198
|
+
async function restoreReplicationPartition(core, engine, indexName, partitionId, bytes, schema, partitionCount) {
|
|
5199
|
+
core.guardShutdown();
|
|
5200
|
+
validatePartitionRestoreTarget(indexName, partitionId, partitionCount);
|
|
5201
|
+
let partition;
|
|
5202
|
+
try {
|
|
5203
|
+
partition = deserializePayloadV2(bytes);
|
|
5204
|
+
} catch (err) {
|
|
5205
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
5206
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, `partition snapshot decode failed: ${cause}`, {
|
|
5207
|
+
indexName,
|
|
5208
|
+
partitionId,
|
|
5209
|
+
cause
|
|
5229
5210
|
});
|
|
5230
5211
|
}
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5212
|
+
validatePartitionSnapshotPayload(indexName, partitionId, partitionCount, schema, partition);
|
|
5213
|
+
const trackPositions = core.indexRegistry.get(indexName)?.config.trackPositions ?? true;
|
|
5214
|
+
createPartitionIndex(partitionId, trackPositions).deserialize(partition, schema);
|
|
5215
|
+
let release = core.indexRegistry.has(indexName) ? await core.indexState.acquire(indexName, false) : null;
|
|
5216
|
+
try {
|
|
5217
|
+
const restoreIndex = await ensureReplicationIndex(
|
|
5218
|
+
core,
|
|
5219
|
+
engine,
|
|
5220
|
+
indexName,
|
|
5221
|
+
schema,
|
|
5222
|
+
partition.language,
|
|
5223
|
+
partitionCount
|
|
5224
|
+
);
|
|
5225
|
+
if (release === null) release = await core.indexState.acquire(indexName, false);
|
|
5226
|
+
if (restoreIndex.created) {
|
|
5227
|
+
await core.orchestrator.replicateToWorkers({
|
|
5228
|
+
type: "createIndex",
|
|
5229
|
+
indexName,
|
|
5230
|
+
config: restoreIndex.config,
|
|
5231
|
+
requestId: `replicate-partition-index-${indexName}`
|
|
5232
|
+
});
|
|
5233
|
+
}
|
|
5234
|
+
const manager = core.requireManager(indexName);
|
|
5235
|
+
manager.deserializePartition(partitionId, partition);
|
|
5236
|
+
await core.orchestrator.replicateToWorkers({
|
|
5237
|
+
type: "deserialize",
|
|
5238
|
+
indexName,
|
|
5239
|
+
partitionId,
|
|
5240
|
+
data: partition,
|
|
5241
|
+
requestId: `replicate-partition-restore-${indexName}-${partitionId}`
|
|
5234
5242
|
});
|
|
5243
|
+
} finally {
|
|
5244
|
+
release?.();
|
|
5235
5245
|
}
|
|
5236
|
-
return {
|
|
5237
|
-
cache: /* @__PURE__ */ new Map(),
|
|
5238
|
-
active: 0,
|
|
5239
|
-
perSource: /* @__PURE__ */ new Map(),
|
|
5240
|
-
streamActive: /* @__PURE__ */ new Map(),
|
|
5241
|
-
maxConcurrent,
|
|
5242
|
-
maxPerSource,
|
|
5243
|
-
maxStreamsPerIndex
|
|
5244
|
-
};
|
|
5245
|
-
}
|
|
5246
|
-
function sourceSlotKey(sourceId, indexName) {
|
|
5247
|
-
return `${sourceId}\0${indexName}`;
|
|
5248
5246
|
}
|
|
5249
|
-
function
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5247
|
+
function validatePartitionRestoreTarget(indexName, partitionId, partitionCount) {
|
|
5248
|
+
if (!Number.isInteger(partitionId) || partitionId < 0) {
|
|
5249
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partitionId must be a non-negative integer", {
|
|
5250
|
+
indexName,
|
|
5251
|
+
partitionId
|
|
5252
|
+
});
|
|
5253
|
+
}
|
|
5254
|
+
if (!Number.isInteger(partitionCount) || partitionCount <= 0 || partitionCount > MAX_PARTITION_COUNT) {
|
|
5253
5255
|
throw new NarsilError(
|
|
5254
|
-
ErrorCodes.
|
|
5255
|
-
`
|
|
5256
|
-
{
|
|
5256
|
+
ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED,
|
|
5257
|
+
`partitionCount must be an integer between 1 and ${MAX_PARTITION_COUNT}`,
|
|
5258
|
+
{ indexName, partitionCount }
|
|
5257
5259
|
);
|
|
5258
5260
|
}
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
+
if (partitionId >= partitionCount) {
|
|
5262
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partitionId is outside partitionCount", {
|
|
5263
|
+
indexName,
|
|
5264
|
+
partitionId,
|
|
5265
|
+
partitionCount
|
|
5266
|
+
});
|
|
5267
|
+
}
|
|
5261
5268
|
}
|
|
5262
|
-
function
|
|
5263
|
-
if (
|
|
5264
|
-
|
|
5269
|
+
function validatePartitionSnapshotPayload(indexName, partitionId, partitionCount, schema, partition) {
|
|
5270
|
+
if (partition.indexName !== indexName) {
|
|
5271
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot indexName mismatch", {
|
|
5272
|
+
expectedIndexName: indexName,
|
|
5273
|
+
receivedIndexName: partition.indexName
|
|
5274
|
+
});
|
|
5265
5275
|
}
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5276
|
+
if (partition.partitionId !== partitionId) {
|
|
5277
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot partitionId mismatch", {
|
|
5278
|
+
indexName,
|
|
5279
|
+
expectedPartitionId: partitionId,
|
|
5280
|
+
receivedPartitionId: partition.partitionId
|
|
5281
|
+
});
|
|
5270
5282
|
}
|
|
5271
|
-
if (
|
|
5272
|
-
|
|
5273
|
-
|
|
5283
|
+
if (partition.totalPartitions !== partitionCount) {
|
|
5284
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot partition count mismatch", {
|
|
5285
|
+
indexName,
|
|
5286
|
+
partitionId,
|
|
5287
|
+
expectedPartitionCount: partitionCount,
|
|
5288
|
+
receivedPartitionCount: partition.totalPartitions
|
|
5289
|
+
});
|
|
5274
5290
|
}
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5291
|
+
const expectedSchema = flattenSchema(schema);
|
|
5292
|
+
for (const [field, expectedType] of Object.entries(expectedSchema)) {
|
|
5293
|
+
if (partition.schema[field] !== expectedType) {
|
|
5294
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot schema mismatch", {
|
|
5295
|
+
indexName,
|
|
5296
|
+
partitionId,
|
|
5297
|
+
field,
|
|
5298
|
+
expected: expectedType,
|
|
5299
|
+
received: partition.schema[field] ?? "(absent)"
|
|
5300
|
+
});
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
for (const field of Object.keys(partition.schema)) {
|
|
5304
|
+
if (!(field in expectedSchema)) {
|
|
5305
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot schema contains extra field", {
|
|
5306
|
+
indexName,
|
|
5307
|
+
partitionId,
|
|
5308
|
+
field
|
|
5309
|
+
});
|
|
5310
|
+
}
|
|
5285
5311
|
}
|
|
5286
|
-
state.streamActive.set(indexName, current + 1);
|
|
5287
|
-
return { indexName, released: false };
|
|
5288
5312
|
}
|
|
5289
|
-
function
|
|
5290
|
-
|
|
5291
|
-
|
|
5313
|
+
async function ensureReplicationIndex(core, engine, indexName, schema, languageName, partitionCount) {
|
|
5314
|
+
const language = getLanguage(languageName);
|
|
5315
|
+
const existing = core.indexRegistry.get(indexName);
|
|
5316
|
+
let created = false;
|
|
5317
|
+
if (existing === void 0) {
|
|
5318
|
+
const indexConfig = {
|
|
5319
|
+
schema,
|
|
5320
|
+
language: language.name,
|
|
5321
|
+
partitions: { maxPartitions: partitionCount }
|
|
5322
|
+
};
|
|
5323
|
+
try {
|
|
5324
|
+
await engine.createIndex(indexName, indexConfig);
|
|
5325
|
+
created = true;
|
|
5326
|
+
} catch (err) {
|
|
5327
|
+
if (!(err instanceof NarsilError) || err.code !== ErrorCodes.INDEX_ALREADY_EXISTS) {
|
|
5328
|
+
throw err;
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5292
5331
|
}
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5332
|
+
const entry = core.requireIndex(indexName);
|
|
5333
|
+
if (entry.language.name !== language.name) {
|
|
5334
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "partition snapshot language mismatch", {
|
|
5335
|
+
indexName,
|
|
5336
|
+
expectedLanguage: entry.language.name,
|
|
5337
|
+
receivedLanguage: language.name
|
|
5338
|
+
});
|
|
5297
5339
|
}
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5340
|
+
validateExistingSchema(indexName, schema, entry.config.schema);
|
|
5341
|
+
const manager = core.requireManager(indexName);
|
|
5342
|
+
if (manager.partitionCount > partitionCount) {
|
|
5343
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "local index has more partitions than allocation", {
|
|
5344
|
+
indexName,
|
|
5345
|
+
localPartitionCount: manager.partitionCount,
|
|
5346
|
+
allocationPartitionCount: partitionCount
|
|
5347
|
+
});
|
|
5301
5348
|
}
|
|
5302
|
-
|
|
5303
|
-
}
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
if (existing !== void 0) {
|
|
5307
|
-
return existing.promise;
|
|
5349
|
+
if (entry.config.partitions === void 0) {
|
|
5350
|
+
entry.config.partitions = { maxPartitions: partitionCount };
|
|
5351
|
+
} else if ((entry.config.partitions.maxPartitions ?? manager.partitionCount) < partitionCount) {
|
|
5352
|
+
entry.config.partitions.maxPartitions = partitionCount;
|
|
5308
5353
|
}
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5354
|
+
while (manager.partitionCount < partitionCount) {
|
|
5355
|
+
manager.addPartition();
|
|
5356
|
+
}
|
|
5357
|
+
return { config: entry.config, created };
|
|
5358
|
+
}
|
|
5359
|
+
function validateExistingSchema(indexName, expected, actual) {
|
|
5360
|
+
const expectedSchema = flattenSchema(expected);
|
|
5361
|
+
const actualSchema = flattenSchema(actual);
|
|
5362
|
+
for (const [field, expectedType] of Object.entries(expectedSchema)) {
|
|
5363
|
+
if (actualSchema[field] !== expectedType) {
|
|
5364
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "local index schema mismatch", {
|
|
5365
|
+
indexName,
|
|
5366
|
+
field,
|
|
5367
|
+
expected: expectedType,
|
|
5368
|
+
received: actualSchema[field] ?? "(absent)"
|
|
5369
|
+
});
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
for (const field of Object.keys(actualSchema)) {
|
|
5373
|
+
if (!(field in expectedSchema)) {
|
|
5374
|
+
throw new NarsilError(ErrorCodes.SNAPSHOT_SYNC_RESTORE_FAILED, "local index schema contains extra field", {
|
|
5375
|
+
indexName,
|
|
5376
|
+
field
|
|
5377
|
+
});
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
function flattenSchema(schema) {
|
|
5382
|
+
const flat = {};
|
|
5383
|
+
flattenSchemaInto(schema, "", flat);
|
|
5384
|
+
return flat;
|
|
5385
|
+
}
|
|
5386
|
+
function flattenSchemaInto(schema, prefix, flat) {
|
|
5387
|
+
for (const [field, value] of Object.entries(schema)) {
|
|
5388
|
+
const path = prefix.length === 0 ? field : `${prefix}.${field}`;
|
|
5389
|
+
if (isNestedSchema(value)) {
|
|
5390
|
+
flattenSchemaInto(value, path, flat);
|
|
5391
|
+
continue;
|
|
5392
|
+
}
|
|
5393
|
+
flat[path] = value;
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
function isNestedSchema(value) {
|
|
5397
|
+
return typeof value === "object" && value !== null;
|
|
5398
|
+
}
|
|
5399
|
+
|
|
5400
|
+
// src/distribution/cluster-node/local-index-reconcile.ts
|
|
5401
|
+
async function reconcileLocalIndexes(deps) {
|
|
5402
|
+
const dispositions = /* @__PURE__ */ new Map();
|
|
5403
|
+
for (const index of deps.engine.listIndexes()) {
|
|
5404
|
+
const disposition = await reconcileOne(deps, index.name);
|
|
5405
|
+
dispositions.set(index.name, disposition);
|
|
5406
|
+
}
|
|
5407
|
+
return dispositions;
|
|
5408
|
+
}
|
|
5409
|
+
async function adoptClusterIdentity(engine, coordinator, indexName) {
|
|
5410
|
+
if (engine.indexUuidOf(indexName) !== null) {
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5413
|
+
const metadata = await getIndexMetadata(coordinator, indexName);
|
|
5414
|
+
if (metadata === null) {
|
|
5415
|
+
return;
|
|
5416
|
+
}
|
|
5417
|
+
await engine.stampIndexUuid(indexName, metadata.indexUuid);
|
|
5418
|
+
}
|
|
5419
|
+
function orphanedIndexError(nodeId, indexName) {
|
|
5420
|
+
return new NarsilError(
|
|
5421
|
+
ErrorCodes.INDEX_ORPHANED,
|
|
5422
|
+
`Node '${nodeId}' holds a local copy of index '${indexName}' the cluster does not claim, so it answers nothing for that name. Drop it to reclaim the space`,
|
|
5423
|
+
{ indexName, nodeId }
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
5426
|
+
async function reconcileOne(deps, indexName) {
|
|
5427
|
+
const localUuid = deps.engine.indexUuidOf(indexName);
|
|
5428
|
+
let clusterUuid;
|
|
5429
|
+
try {
|
|
5430
|
+
clusterUuid = (await getIndexMetadata(deps.coordinator, indexName))?.indexUuid ?? null;
|
|
5431
|
+
} catch (error) {
|
|
5432
|
+
report(deps, indexName, `its metadata could not be read: ${describeError(error)}`);
|
|
5433
|
+
return "orphaned";
|
|
5434
|
+
}
|
|
5435
|
+
if (clusterUuid === null) {
|
|
5436
|
+
report(deps, indexName, "the coordinator holds no metadata for that name");
|
|
5437
|
+
return "orphaned";
|
|
5438
|
+
}
|
|
5439
|
+
if (localUuid === clusterUuid) {
|
|
5440
|
+
return "adopted";
|
|
5441
|
+
}
|
|
5442
|
+
if (localUuid === null || localUuid === void 0) {
|
|
5443
|
+
await deps.engine.stampIndexUuid(indexName, clusterUuid);
|
|
5444
|
+
return "adopted";
|
|
5445
|
+
}
|
|
5446
|
+
await deps.engine.dropIndex(indexName);
|
|
5447
|
+
return "superseded";
|
|
5448
|
+
}
|
|
5449
|
+
function report(deps, indexName, reason) {
|
|
5450
|
+
if (deps.onError === void 0) {
|
|
5451
|
+
return;
|
|
5452
|
+
}
|
|
5453
|
+
deps.onError(
|
|
5454
|
+
new NarsilError(
|
|
5455
|
+
ErrorCodes.INDEX_ORPHANED,
|
|
5456
|
+
`Node '${deps.nodeId}' holds a local copy of index '${indexName}' the cluster does not claim, because ${reason}. The node keeps the data and serves none of it until an operator drops it.`,
|
|
5457
|
+
{ indexName, nodeId: deps.nodeId }
|
|
5458
|
+
)
|
|
5459
|
+
);
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5462
|
+
// src/distribution/cluster-node/snapshot-auth.ts
|
|
5463
|
+
var BOOTSTRAPPABLE_STATES = /* @__PURE__ */ new Set(["ACTIVE", "INITIALISING", "MIGRATING"]);
|
|
5464
|
+
async function authorizeSnapshotRequest(coordinator, indexName, sourceId) {
|
|
5465
|
+
let allocation;
|
|
5466
|
+
try {
|
|
5467
|
+
allocation = await coordinator.getAllocation(indexName);
|
|
5468
|
+
} catch (err) {
|
|
5469
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
5470
|
+
return {
|
|
5471
|
+
outcome: "denied",
|
|
5472
|
+
code: ErrorCodes.SNAPSHOT_SYNC_ALLOCATION_UNAVAILABLE,
|
|
5473
|
+
reason: `allocation lookup failed: ${errMessage}`
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
if (allocation === null) {
|
|
5477
|
+
return {
|
|
5478
|
+
outcome: "denied",
|
|
5479
|
+
code: ErrorCodes.SNAPSHOT_SYNC_UNAUTHORIZED,
|
|
5480
|
+
reason: `index '${indexName}' has no allocation`
|
|
5481
|
+
};
|
|
5482
|
+
}
|
|
5483
|
+
let sawAssignment = false;
|
|
5484
|
+
for (const assignment of allocation.assignments.values()) {
|
|
5485
|
+
const isAssigned = assignment.primary === sourceId || assignment.replicas.includes(sourceId);
|
|
5486
|
+
if (!isAssigned) {
|
|
5487
|
+
continue;
|
|
5488
|
+
}
|
|
5489
|
+
sawAssignment = true;
|
|
5490
|
+
if (!BOOTSTRAPPABLE_STATES.has(assignment.state)) {
|
|
5491
|
+
continue;
|
|
5492
|
+
}
|
|
5493
|
+
return { outcome: "authorized" };
|
|
5494
|
+
}
|
|
5495
|
+
if (sawAssignment) {
|
|
5496
|
+
return {
|
|
5497
|
+
outcome: "denied",
|
|
5498
|
+
code: ErrorCodes.SNAPSHOT_SYNC_NOT_ASSIGNED,
|
|
5499
|
+
reason: `node '${sourceId}' is listed for index '${indexName}' but not in a bootstrappable state`
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
return {
|
|
5503
|
+
outcome: "denied",
|
|
5504
|
+
code: ErrorCodes.SNAPSHOT_SYNC_UNAUTHORIZED,
|
|
5505
|
+
reason: `node '${sourceId}' is not an assigned replica for index '${indexName}'`
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5508
|
+
|
|
5509
|
+
// src/distribution/cluster-node/snapshot-cache.ts
|
|
5510
|
+
var DEFAULT_MAX_CONCURRENT_SNAPSHOTS = 2;
|
|
5511
|
+
var DEFAULT_MAX_PER_SOURCE_SNAPSHOTS = 1;
|
|
5512
|
+
var DEFAULT_MAX_STREAMS_PER_INDEX = 4;
|
|
5513
|
+
function createSnapshotCacheState(maxConcurrent = DEFAULT_MAX_CONCURRENT_SNAPSHOTS, maxPerSource = DEFAULT_MAX_PER_SOURCE_SNAPSHOTS, maxStreamsPerIndex = DEFAULT_MAX_STREAMS_PER_INDEX) {
|
|
5514
|
+
if (!Number.isInteger(maxConcurrent) || maxConcurrent <= 0) {
|
|
5515
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "maxConcurrent must be a positive integer", {
|
|
5516
|
+
maxConcurrent
|
|
5517
|
+
});
|
|
5518
|
+
}
|
|
5519
|
+
if (!Number.isInteger(maxPerSource) || maxPerSource <= 0) {
|
|
5520
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "maxPerSource must be a positive integer", {
|
|
5521
|
+
maxPerSource
|
|
5522
|
+
});
|
|
5523
|
+
}
|
|
5524
|
+
if (!Number.isInteger(maxStreamsPerIndex) || maxStreamsPerIndex <= 0) {
|
|
5525
|
+
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "maxStreamsPerIndex must be a positive integer", {
|
|
5526
|
+
maxStreamsPerIndex
|
|
5527
|
+
});
|
|
5528
|
+
}
|
|
5529
|
+
return {
|
|
5530
|
+
cache: /* @__PURE__ */ new Map(),
|
|
5531
|
+
active: 0,
|
|
5532
|
+
perSource: /* @__PURE__ */ new Map(),
|
|
5533
|
+
streamActive: /* @__PURE__ */ new Map(),
|
|
5534
|
+
maxConcurrent,
|
|
5535
|
+
maxPerSource,
|
|
5536
|
+
maxStreamsPerIndex
|
|
5537
|
+
};
|
|
5538
|
+
}
|
|
5539
|
+
function sourceSlotKey(sourceId, indexName) {
|
|
5540
|
+
return `${sourceId}\0${indexName}`;
|
|
5541
|
+
}
|
|
5542
|
+
function acquireSourceSlot(state, sourceId, indexName) {
|
|
5543
|
+
const key = sourceSlotKey(sourceId, indexName);
|
|
5544
|
+
const current = state.perSource.get(key) ?? 0;
|
|
5545
|
+
if (current >= state.maxPerSource) {
|
|
5546
|
+
throw new NarsilError(
|
|
5547
|
+
ErrorCodes.SNAPSHOT_SYNC_CAPACITY_EXHAUSTED,
|
|
5548
|
+
`snapshot producer per-source cap reached for '${sourceId}' index '${indexName}'`,
|
|
5549
|
+
{ sourceId, indexName, cap: state.maxPerSource }
|
|
5550
|
+
);
|
|
5551
|
+
}
|
|
5552
|
+
state.perSource.set(key, current + 1);
|
|
5553
|
+
return { key, released: false };
|
|
5554
|
+
}
|
|
5555
|
+
function releaseSourceSlot(state, handle) {
|
|
5556
|
+
if (handle.released) {
|
|
5557
|
+
return;
|
|
5558
|
+
}
|
|
5559
|
+
handle.released = true;
|
|
5560
|
+
const current = state.perSource.get(handle.key);
|
|
5561
|
+
if (current === void 0) {
|
|
5562
|
+
return;
|
|
5563
|
+
}
|
|
5564
|
+
if (current <= 1) {
|
|
5565
|
+
state.perSource.delete(handle.key);
|
|
5566
|
+
return;
|
|
5567
|
+
}
|
|
5568
|
+
state.perSource.set(handle.key, current - 1);
|
|
5569
|
+
}
|
|
5570
|
+
function acquireStreamSlot(state, indexName) {
|
|
5571
|
+
const current = state.streamActive.get(indexName) ?? 0;
|
|
5572
|
+
if (current >= state.maxStreamsPerIndex) {
|
|
5573
|
+
throw new NarsilError(
|
|
5574
|
+
ErrorCodes.SNAPSHOT_SYNC_CAPACITY_EXHAUSTED,
|
|
5575
|
+
`snapshot stream fan-out cap reached for index '${indexName}'`,
|
|
5576
|
+
{ indexName, cap: state.maxStreamsPerIndex }
|
|
5577
|
+
);
|
|
5578
|
+
}
|
|
5579
|
+
state.streamActive.set(indexName, current + 1);
|
|
5580
|
+
return { indexName, released: false };
|
|
5581
|
+
}
|
|
5582
|
+
function releaseStreamSlot(state, handle) {
|
|
5583
|
+
if (handle.released) {
|
|
5584
|
+
return;
|
|
5585
|
+
}
|
|
5586
|
+
handle.released = true;
|
|
5587
|
+
const current = state.streamActive.get(handle.indexName);
|
|
5588
|
+
if (current === void 0) {
|
|
5589
|
+
return;
|
|
5590
|
+
}
|
|
5591
|
+
if (current <= 1) {
|
|
5592
|
+
state.streamActive.delete(handle.indexName);
|
|
5593
|
+
return;
|
|
5594
|
+
}
|
|
5595
|
+
state.streamActive.set(handle.indexName, current - 1);
|
|
5596
|
+
}
|
|
5597
|
+
async function acquireSnapshotBuild(state, indexName, builder) {
|
|
5598
|
+
const existing = state.cache.get(indexName);
|
|
5599
|
+
if (existing !== void 0) {
|
|
5600
|
+
return existing.promise;
|
|
5601
|
+
}
|
|
5602
|
+
if (state.active >= state.maxConcurrent) {
|
|
5603
|
+
throw new NarsilError(
|
|
5604
|
+
ErrorCodes.SNAPSHOT_SYNC_CAPACITY_EXHAUSTED,
|
|
5605
|
+
"snapshot producer capacity exhausted; retry later",
|
|
5606
|
+
{ active: state.active, max: state.maxConcurrent }
|
|
5314
5607
|
);
|
|
5315
5608
|
}
|
|
5316
5609
|
state.active += 1;
|
|
@@ -7429,7 +7722,7 @@ async function handleFetch(message, respond, deps) {
|
|
|
7429
7722
|
async function handleStats(message, respond, deps) {
|
|
7430
7723
|
const decoded = decode(message.payload);
|
|
7431
7724
|
const payload = validateStatsPayload(decoded);
|
|
7432
|
-
const resultPayload = deps.engine.collectQueryStats(
|
|
7725
|
+
const resultPayload = await deps.engine.collectQueryStats(
|
|
7433
7726
|
payload.indexName,
|
|
7434
7727
|
payload.terms,
|
|
7435
7728
|
payload.partitionIds
|
|
@@ -7470,7 +7763,7 @@ function convertLocalFacetsToWire2(facets) {
|
|
|
7470
7763
|
async function handleCount(message, respond, deps) {
|
|
7471
7764
|
const payload = validateCountPayload(decode(message.payload));
|
|
7472
7765
|
const requested = new Set(payload.partitionIds);
|
|
7473
|
-
const partitions = deps.engine.
|
|
7766
|
+
const partitions = (await deps.engine.partitionStatsForRead(payload.indexName)).filter((partition) => requested.has(partition.partitionId)).map((partition) => ({
|
|
7474
7767
|
partitionId: partition.partitionId,
|
|
7475
7768
|
documentCount: partition.documentCount,
|
|
7476
7769
|
estimatedMemoryBytes: partition.estimatedMemoryBytes
|
|
@@ -7620,59 +7913,65 @@ async function handleSyncRequestMessage(message, respond, deps) {
|
|
|
7620
7913
|
}
|
|
7621
7914
|
);
|
|
7622
7915
|
}
|
|
7623
|
-
|
|
7916
|
+
const localIndex = deps.engine.listIndexes().find((index) => index.name === request.indexName);
|
|
7917
|
+
if (localIndex === void 0) {
|
|
7624
7918
|
throw new NarsilError(
|
|
7625
7919
|
ErrorCodes.SNAPSHOT_SYNC_INDEX_NOT_FOUND,
|
|
7626
7920
|
`Node '${deps.nodeId}' holds no copy of index '${request.indexName}' yet, so it cannot serve a sync for partition ${request.partitionId}`,
|
|
7627
7921
|
{ indexName: request.indexName, partitionId: request.partitionId, primaryNodeId: deps.nodeId }
|
|
7628
7922
|
);
|
|
7629
7923
|
}
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
metadata: {
|
|
7660
|
-
partitionId: request.partitionId,
|
|
7661
|
-
lastSeqNo: snapshotSeqNo,
|
|
7662
|
-
primaryTerm: assignment.primaryTerm
|
|
7663
|
-
},
|
|
7664
|
-
closeOnEnd: false,
|
|
7665
|
-
disableBuildCache: true,
|
|
7666
|
-
buildSnapshot: async () => {
|
|
7667
|
-
const bytes = await deps.engine.serializeReplicationPartition(request.indexName, request.partitionId);
|
|
7668
|
-
return { bytes, checksum: crc32(bytes) };
|
|
7924
|
+
const release = await deps.engine.acquireIndexForReplication(request.indexName);
|
|
7925
|
+
try {
|
|
7926
|
+
if (message.sourceId !== deps.nodeId) {
|
|
7927
|
+
recordReplicaPosition(
|
|
7928
|
+
deps.writeDeps.catchUp,
|
|
7929
|
+
request.indexName,
|
|
7930
|
+
request.partitionId,
|
|
7931
|
+
message.sourceId,
|
|
7932
|
+
request.lastSeqNo
|
|
7933
|
+
);
|
|
7934
|
+
}
|
|
7935
|
+
const log = deps.writeDeps.getReplicationLog(request.indexName, request.partitionId);
|
|
7936
|
+
const tier = decideSyncTier(log, request.lastSeqNo);
|
|
7937
|
+
if (tier === "incremental") {
|
|
7938
|
+
await sendSyncEntriesResponse(message, respond, deps, log.getEntriesFrom(request.lastSeqNo + 1));
|
|
7939
|
+
return;
|
|
7940
|
+
}
|
|
7941
|
+
const snapshotSeqNo = log.localLogEnd;
|
|
7942
|
+
const sink = createSingleResponseSink(respond);
|
|
7943
|
+
await streamValidatedSnapshotRequest(
|
|
7944
|
+
message,
|
|
7945
|
+
sink,
|
|
7946
|
+
{ indexName: request.indexName, partitionId: request.partitionId },
|
|
7947
|
+
{
|
|
7948
|
+
nodeId: deps.nodeId,
|
|
7949
|
+
engine: deps.engine,
|
|
7950
|
+
coordinator: deps.coordinator,
|
|
7951
|
+
state: deps.snapshotSyncState,
|
|
7952
|
+
resolveHeaderMetadata: deps.resolveHeaderMetadata
|
|
7669
7953
|
},
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7954
|
+
{
|
|
7955
|
+
metadata: {
|
|
7956
|
+
partitionId: request.partitionId,
|
|
7957
|
+
lastSeqNo: snapshotSeqNo,
|
|
7958
|
+
primaryTerm: assignment.primaryTerm
|
|
7959
|
+
},
|
|
7960
|
+
closeOnEnd: false,
|
|
7961
|
+
disableBuildCache: true,
|
|
7962
|
+
buildSnapshot: async () => {
|
|
7963
|
+
const bytes = await deps.engine.serializeReplicationPartition(request.indexName, request.partitionId);
|
|
7964
|
+
return { bytes, checksum: crc32(bytes) };
|
|
7965
|
+
},
|
|
7966
|
+
afterSnapshot: async (trailingSink) => {
|
|
7967
|
+
const entries = log.getEntriesFrom(snapshotSeqNo + 1);
|
|
7968
|
+
await sendSyncEntriesResponse(message, trailingSink, deps, entries);
|
|
7969
|
+
}
|
|
7673
7970
|
}
|
|
7674
|
-
|
|
7675
|
-
|
|
7971
|
+
);
|
|
7972
|
+
} finally {
|
|
7973
|
+
release();
|
|
7974
|
+
}
|
|
7676
7975
|
}
|
|
7677
7976
|
async function sendSyncEntriesResponse(message, respond, deps, entries) {
|
|
7678
7977
|
const payload = {
|
|
@@ -10120,7 +10419,7 @@ async function gatherPartitionCounts(deps, indexName, allocation) {
|
|
|
10120
10419
|
groups.map(async (group) => {
|
|
10121
10420
|
if (group.nodeId === deps.nodeId) {
|
|
10122
10421
|
const requested = new Set(group.partitionIds);
|
|
10123
|
-
const partitions = deps.engine.
|
|
10422
|
+
const partitions = (await deps.engine.partitionStatsForRead(indexName)).filter((partition) => requested.has(partition.partitionId)).map((partition) => ({
|
|
10124
10423
|
partitionId: partition.partitionId,
|
|
10125
10424
|
documentCount: partition.documentCount,
|
|
10126
10425
|
estimatedMemoryBytes: partition.estimatedMemoryBytes
|
|
@@ -10198,7 +10497,7 @@ async function statsCluster(deps, indexName) {
|
|
|
10198
10497
|
async function partitionStatsCluster(deps, indexName) {
|
|
10199
10498
|
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
10200
10499
|
if (allocation === null) {
|
|
10201
|
-
return deps.engine.
|
|
10500
|
+
return deps.engine.partitionStatsForRead(indexName);
|
|
10202
10501
|
}
|
|
10203
10502
|
const { countsByPartition } = await gatherPartitionCounts(deps, indexName, allocation);
|
|
10204
10503
|
return Array.from(countsByPartition.entries()).map(([partitionId, entry]) => ({
|
|
@@ -10486,391 +10785,234 @@ async function clearCluster(readDeps, writeDeps, indexName) {
|
|
|
10486
10785
|
}
|
|
10487
10786
|
for (; ; ) {
|
|
10488
10787
|
const page = await listCluster(readDeps, indexName, { limit: CLEAR_PAGE_SIZE, document: false });
|
|
10489
|
-
if (page.documents.length === 0) {
|
|
10490
|
-
return;
|
|
10491
|
-
}
|
|
10492
|
-
const result = await routeRemoveBatch(
|
|
10493
|
-
indexName,
|
|
10494
|
-
page.documents.map((listed) => listed.id),
|
|
10495
|
-
writeDeps
|
|
10496
|
-
);
|
|
10497
|
-
if (result.succeeded.length === 0) {
|
|
10498
|
-
const firstFailure = result.failed[0];
|
|
10499
|
-
throw new NarsilError(
|
|
10500
|
-
ErrorCodes.QUERY_ROUTING_FAILED,
|
|
10501
|
-
`Clearing index '${indexName}' stalled: no document in the current page could be removed`,
|
|
10502
|
-
{
|
|
10503
|
-
indexName,
|
|
10504
|
-
remaining: page.total,
|
|
10505
|
-
firstFailureDocId: firstFailure?.docId,
|
|
10506
|
-
firstFailureCode: firstFailure?.error.code,
|
|
10507
|
-
firstFailureMessage: firstFailure?.error.message
|
|
10508
|
-
}
|
|
10509
|
-
);
|
|
10510
|
-
}
|
|
10511
|
-
}
|
|
10512
|
-
}
|
|
10513
|
-
|
|
10514
|
-
// src/distribution/cluster-node/node-operations.ts
|
|
10515
|
-
function createClusterNodeOperations(deps) {
|
|
10516
|
-
const { trackOp, readDeps, writeDeps, engine, coordinator } = deps;
|
|
10517
|
-
return {
|
|
10518
|
-
async createIndex(name, indexConfig, options) {
|
|
10519
|
-
return trackOp(name, () => routeCreateIndex(name, indexConfig, options, coordinator, engine));
|
|
10520
|
-
},
|
|
10521
|
-
async insert(indexName, document, docId, options) {
|
|
10522
|
-
return trackOp(indexName, () => routeInsert(indexName, document, docId, writeDeps, options));
|
|
10523
|
-
},
|
|
10524
|
-
async insertBatch(indexName, documents, options) {
|
|
10525
|
-
return trackOp(indexName, () => routeInsertBatch(indexName, documents, writeDeps, options));
|
|
10526
|
-
},
|
|
10527
|
-
async remove(indexName, docId) {
|
|
10528
|
-
return trackOp(indexName, () => routeRemove(indexName, docId, writeDeps));
|
|
10529
|
-
},
|
|
10530
|
-
async removeBatch(indexName, docIds) {
|
|
10531
|
-
return trackOp(indexName, () => routeRemoveBatch(indexName, docIds, writeDeps));
|
|
10532
|
-
},
|
|
10533
|
-
async update(indexName, docId, document) {
|
|
10534
|
-
return trackOp(indexName, () => routeUpdate(indexName, docId, document, writeDeps));
|
|
10535
|
-
},
|
|
10536
|
-
async updateBatch(indexName, updates) {
|
|
10537
|
-
return trackOp(indexName, () => routeUpdateBatch(indexName, updates, writeDeps));
|
|
10538
|
-
},
|
|
10539
|
-
async dropIndex(name) {
|
|
10540
|
-
return trackOp(null, async () => {
|
|
10541
|
-
await routeDropIndex(name, coordinator, engine);
|
|
10542
|
-
deps.forgetIndex(name);
|
|
10543
|
-
});
|
|
10544
|
-
},
|
|
10545
|
-
async clear(indexName) {
|
|
10546
|
-
return trackOp(indexName, () => clearCluster(readDeps, writeDeps, indexName));
|
|
10547
|
-
},
|
|
10548
|
-
async countDocuments(indexName) {
|
|
10549
|
-
return trackOp(indexName, () => countCluster(readDeps, indexName));
|
|
10550
|
-
},
|
|
10551
|
-
async listDocuments(indexName, params) {
|
|
10552
|
-
return trackOp(indexName, () => listCluster(readDeps, indexName, params ?? {}));
|
|
10553
|
-
},
|
|
10554
|
-
async suggest(indexName, params) {
|
|
10555
|
-
return trackOp(indexName, () => suggestCluster(readDeps, indexName, params));
|
|
10556
|
-
},
|
|
10557
|
-
async preflight(indexName, params) {
|
|
10558
|
-
return trackOp(indexName, () => preflightCluster(readDeps, indexName, params));
|
|
10559
|
-
},
|
|
10560
|
-
async getStats(indexName) {
|
|
10561
|
-
return trackOp(indexName, () => statsCluster(readDeps, indexName));
|
|
10562
|
-
},
|
|
10563
|
-
async getPartitionStats(indexName) {
|
|
10564
|
-
return trackOp(indexName, () => partitionStatsCluster(readDeps, indexName));
|
|
10565
|
-
},
|
|
10566
|
-
async checkpoint(indexName) {
|
|
10567
|
-
return trackOp(indexName, () => engine.checkpoint(indexName));
|
|
10568
|
-
},
|
|
10569
|
-
async getMemoryStats() {
|
|
10570
|
-
return trackOp(null, () => engine.getMemoryStats());
|
|
10571
|
-
},
|
|
10572
|
-
on(event, handler) {
|
|
10573
|
-
engine.on(event, handler);
|
|
10574
|
-
},
|
|
10575
|
-
off(event, handler) {
|
|
10576
|
-
engine.off(event, handler);
|
|
10577
|
-
},
|
|
10578
|
-
async query(indexName, params) {
|
|
10579
|
-
return trackOp(indexName, () => queryCluster(readDeps, indexName, params));
|
|
10580
|
-
},
|
|
10581
|
-
async get(indexName, docId) {
|
|
10582
|
-
return trackOp(indexName, async () => (await readClusterDocuments(readDeps, indexName, [docId])).get(docId));
|
|
10583
|
-
},
|
|
10584
|
-
async getMultiple(indexName, docIds) {
|
|
10585
|
-
return trackOp(indexName, () => readClusterDocuments(readDeps, indexName, docIds));
|
|
10586
|
-
},
|
|
10587
|
-
async has(indexName, docId) {
|
|
10588
|
-
return trackOp(indexName, async () => (await readClusterDocuments(readDeps, indexName, [docId])).has(docId));
|
|
10589
|
-
}
|
|
10590
|
-
};
|
|
10591
|
-
}
|
|
10592
|
-
|
|
10593
|
-
// src/distribution/cluster-node/primary-partition.ts
|
|
10594
|
-
async function preparePrimaryPartition(indexName, partitionId, deps) {
|
|
10595
|
-
const schema = await deps.coordinator.getSchema(indexName);
|
|
10596
|
-
if (schema === null) {
|
|
10597
|
-
return false;
|
|
10598
|
-
}
|
|
10599
|
-
const allocation = await deps.coordinator.getAllocation(indexName);
|
|
10600
|
-
if (allocation === null || allocation.assignments.size === 0) {
|
|
10601
|
-
return false;
|
|
10602
|
-
}
|
|
10603
|
-
const existing = deps.engine.listIndexes().find((index) => index.name === indexName);
|
|
10604
|
-
if (existing === void 0) {
|
|
10605
|
-
try {
|
|
10606
|
-
await deps.engine.createIndex(indexName, {
|
|
10607
|
-
schema,
|
|
10608
|
-
partitions: { maxPartitions: allocation.assignments.size }
|
|
10609
|
-
});
|
|
10610
|
-
} catch (error) {
|
|
10611
|
-
if (!(error instanceof NarsilError) || error.code !== ErrorCodes.INDEX_ALREADY_EXISTS) {
|
|
10612
|
-
deps.onError(error);
|
|
10613
|
-
return false;
|
|
10614
|
-
}
|
|
10615
|
-
}
|
|
10616
|
-
} else {
|
|
10617
|
-
const schemaError = validateRestoredSchema(deps.engine, indexName, deps.nodeId, schema);
|
|
10618
|
-
if (schemaError !== null) {
|
|
10619
|
-
deps.onError(schemaError);
|
|
10620
|
-
return false;
|
|
10621
|
-
}
|
|
10622
|
-
}
|
|
10623
|
-
resumeNumberingAboveOwnCopy(indexName, partitionId, allocation.assignments.get(partitionId), deps);
|
|
10624
|
-
return true;
|
|
10625
|
-
}
|
|
10626
|
-
function resumeNumberingAboveOwnCopy(indexName, partitionId, assignment, deps) {
|
|
10627
|
-
if (assignment === void 0) {
|
|
10628
|
-
return;
|
|
10629
|
-
}
|
|
10630
|
-
if (deps.replicationLogPosition(indexName, partitionId) > 0) {
|
|
10631
|
-
return;
|
|
10632
|
-
}
|
|
10633
|
-
const floor = Math.max(deps.engine.highestPersistedSeqNoOf(indexName, partitionId), assignment.commitPoint);
|
|
10634
|
-
if (floor <= 0) {
|
|
10635
|
-
return;
|
|
10636
|
-
}
|
|
10637
|
-
deps.seedReplicationLog(indexName, partitionId, floor + 1, assignment.primaryTerm);
|
|
10638
|
-
}
|
|
10639
|
-
|
|
10640
|
-
// src/distribution/replication/types.ts
|
|
10641
|
-
var DEFAULT_LOG_RETENTION_BYTES = 268435456;
|
|
10642
|
-
|
|
10643
|
-
// src/distribution/replication/log.ts
|
|
10644
|
-
var ENTRY_FIXED_OVERHEAD_BYTES2 = 40;
|
|
10645
|
-
function estimateEntrySize(entry) {
|
|
10646
|
-
return ENTRY_FIXED_OVERHEAD_BYTES2 + entry.indexName.length + entry.documentId.length + (entry.document?.byteLength ?? 0);
|
|
10647
|
-
}
|
|
10648
|
-
var computeChecksum = computeEntryChecksum;
|
|
10649
|
-
function createReplicationLog(partitionId, config) {
|
|
10650
|
-
const retentionBytes = config?.logRetentionBytes ?? DEFAULT_LOG_RETENTION_BYTES;
|
|
10651
|
-
let nextSeqNo = config?.startSeqNo ?? 1;
|
|
10652
|
-
let localLogEnd = nextSeqNo - 1;
|
|
10653
|
-
let localLogEndPrimaryTerm = config?.lastPrimaryTerm ?? 0;
|
|
10654
|
-
let commitPoint = nextSeqNo - 1;
|
|
10655
|
-
let entries = [];
|
|
10656
|
-
let headIndex = 0;
|
|
10657
|
-
let totalSizeBytes = 0;
|
|
10658
|
-
function liveCount() {
|
|
10659
|
-
return entries.length - headIndex;
|
|
10660
|
-
}
|
|
10661
|
-
function compact() {
|
|
10662
|
-
if (headIndex > 0) {
|
|
10663
|
-
entries = entries.slice(headIndex);
|
|
10664
|
-
headIndex = 0;
|
|
10665
|
-
}
|
|
10666
|
-
}
|
|
10667
|
-
function findIndexBySeqNo(seqNo) {
|
|
10668
|
-
let low = headIndex;
|
|
10669
|
-
let high = entries.length - 1;
|
|
10670
|
-
while (low <= high) {
|
|
10671
|
-
const mid = low + high >>> 1;
|
|
10672
|
-
const midSeqNo = entries[mid].seqNo;
|
|
10673
|
-
if (midSeqNo < seqNo) {
|
|
10674
|
-
low = mid + 1;
|
|
10675
|
-
} else if (midSeqNo > seqNo) {
|
|
10676
|
-
high = mid - 1;
|
|
10677
|
-
} else {
|
|
10678
|
-
return mid;
|
|
10679
|
-
}
|
|
10680
|
-
}
|
|
10681
|
-
return low;
|
|
10682
|
-
}
|
|
10683
|
-
function evictOldEntries() {
|
|
10684
|
-
while (liveCount() > 0 && totalSizeBytes > retentionBytes) {
|
|
10685
|
-
totalSizeBytes -= estimateEntrySize(entries[headIndex]);
|
|
10686
|
-
entries[headIndex] = null;
|
|
10687
|
-
headIndex += 1;
|
|
10688
|
-
}
|
|
10689
|
-
if (totalSizeBytes < 0) {
|
|
10690
|
-
totalSizeBytes = 0;
|
|
10691
|
-
}
|
|
10692
|
-
if (headIndex >= 256 || headIndex > liveCount()) {
|
|
10693
|
-
compact();
|
|
10694
|
-
}
|
|
10695
|
-
}
|
|
10696
|
-
function assertEntryFitsRetention(entry) {
|
|
10697
|
-
const entrySize = ENTRY_FIXED_OVERHEAD_BYTES2 + entry.indexName.length + entry.documentId.length + (entry.document?.byteLength ?? 0);
|
|
10698
|
-
if (entrySize > retentionBytes) {
|
|
10699
|
-
throw new NarsilError(
|
|
10700
|
-
ErrorCodes.REPLICATION_LOG_FULL,
|
|
10701
|
-
`Replication log entry of ${entrySize} bytes exceeds the log retention limit of ${retentionBytes} bytes and cannot be buffered`,
|
|
10702
|
-
{ partitionId, entrySize, retentionBytes, seqNo: entry.seqNo }
|
|
10703
|
-
);
|
|
10704
|
-
}
|
|
10705
|
-
}
|
|
10706
|
-
function storeEntry(entry) {
|
|
10707
|
-
entries.push(entry);
|
|
10708
|
-
if (entry.seqNo >= localLogEnd) {
|
|
10709
|
-
localLogEnd = entry.seqNo;
|
|
10710
|
-
localLogEndPrimaryTerm = entry.primaryTerm;
|
|
10711
|
-
}
|
|
10712
|
-
totalSizeBytes += estimateEntrySize(entry);
|
|
10713
|
-
evictOldEntries();
|
|
10714
|
-
return { ...entry };
|
|
10715
|
-
}
|
|
10716
|
-
function readEntry(seqNo) {
|
|
10717
|
-
if (liveCount() === 0) return void 0;
|
|
10718
|
-
const idx = findIndexBySeqNo(seqNo);
|
|
10719
|
-
if (idx < entries.length && entries[idx].seqNo === seqNo) {
|
|
10720
|
-
return { ...entries[idx] };
|
|
10721
|
-
}
|
|
10722
|
-
return void 0;
|
|
10723
|
-
}
|
|
10724
|
-
function verifyEntryChecksum(entry) {
|
|
10725
|
-
const expected = computeChecksum({
|
|
10726
|
-
seqNo: entry.seqNo,
|
|
10727
|
-
primaryTerm: entry.primaryTerm,
|
|
10728
|
-
operation: entry.operation,
|
|
10729
|
-
partitionId: entry.partitionId,
|
|
10730
|
-
indexName: entry.indexName,
|
|
10731
|
-
documentId: entry.documentId,
|
|
10732
|
-
document: entry.document
|
|
10733
|
-
});
|
|
10734
|
-
return expected === entry.checksum;
|
|
10735
|
-
}
|
|
10736
|
-
return {
|
|
10737
|
-
append(partial) {
|
|
10738
|
-
assertEntryFitsRetention(partial);
|
|
10739
|
-
const seqNo = nextSeqNo;
|
|
10740
|
-
nextSeqNo += 1;
|
|
10741
|
-
const entryWithoutChecksum = {
|
|
10742
|
-
seqNo,
|
|
10743
|
-
primaryTerm: partial.primaryTerm,
|
|
10744
|
-
operation: partial.operation,
|
|
10745
|
-
partitionId,
|
|
10746
|
-
indexName: partial.indexName,
|
|
10747
|
-
documentId: partial.documentId,
|
|
10748
|
-
document: partial.document
|
|
10749
|
-
};
|
|
10750
|
-
const checksum = computeChecksum(entryWithoutChecksum);
|
|
10751
|
-
const entry = { ...entryWithoutChecksum, checksum };
|
|
10752
|
-
return storeEntry(entry);
|
|
10753
|
-
},
|
|
10754
|
-
appendCommitted(entry) {
|
|
10755
|
-
if (!Number.isSafeInteger(entry.seqNo) || entry.seqNo < 1) {
|
|
10756
|
-
throw new NarsilError(
|
|
10757
|
-
ErrorCodes.REPLICATION_ENTRY_INVALID,
|
|
10758
|
-
`Invalid replication sequence number ${entry.seqNo}`,
|
|
10759
|
-
{
|
|
10760
|
-
seqNo: entry.seqNo,
|
|
10761
|
-
partitionId
|
|
10762
|
-
}
|
|
10763
|
-
);
|
|
10764
|
-
}
|
|
10765
|
-
if (entry.partitionId !== partitionId) {
|
|
10766
|
-
throw new NarsilError(
|
|
10767
|
-
ErrorCodes.REPLICATION_ENTRY_INVALID,
|
|
10768
|
-
`Replication entry partition ${entry.partitionId} does not match log partition ${partitionId}`,
|
|
10769
|
-
{ entryPartitionId: entry.partitionId, logPartitionId: partitionId }
|
|
10770
|
-
);
|
|
10771
|
-
}
|
|
10772
|
-
const existing = readEntry(entry.seqNo);
|
|
10773
|
-
if (existing !== void 0) {
|
|
10774
|
-
if (existing.checksum !== entry.checksum) {
|
|
10775
|
-
throw new NarsilError(
|
|
10776
|
-
ErrorCodes.REPLICATION_ENTRY_INVALID,
|
|
10777
|
-
`Conflicting replication entry at sequence number ${entry.seqNo}`,
|
|
10778
|
-
{ seqNo: entry.seqNo, partitionId }
|
|
10779
|
-
);
|
|
10788
|
+
if (page.documents.length === 0) {
|
|
10789
|
+
return;
|
|
10790
|
+
}
|
|
10791
|
+
const result = await routeRemoveBatch(
|
|
10792
|
+
indexName,
|
|
10793
|
+
page.documents.map((listed) => listed.id),
|
|
10794
|
+
writeDeps
|
|
10795
|
+
);
|
|
10796
|
+
if (result.succeeded.length === 0) {
|
|
10797
|
+
const firstFailure = result.failed[0];
|
|
10798
|
+
throw new NarsilError(
|
|
10799
|
+
ErrorCodes.QUERY_ROUTING_FAILED,
|
|
10800
|
+
`Clearing index '${indexName}' stalled: no document in the current page could be removed`,
|
|
10801
|
+
{
|
|
10802
|
+
indexName,
|
|
10803
|
+
remaining: page.total,
|
|
10804
|
+
firstFailureDocId: firstFailure?.docId,
|
|
10805
|
+
firstFailureCode: firstFailure?.error.code,
|
|
10806
|
+
firstFailureMessage: firstFailure?.error.message
|
|
10780
10807
|
}
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
ErrorCodes.REPLICATION_ENTRY_CORRUPT,
|
|
10793
|
-
`Invalid checksum for replication entry ${entry.seqNo}`,
|
|
10794
|
-
{ seqNo: entry.seqNo, partitionId }
|
|
10795
|
-
);
|
|
10796
|
-
}
|
|
10797
|
-
assertEntryFitsRetention(entry);
|
|
10798
|
-
nextSeqNo = entry.seqNo + 1;
|
|
10799
|
-
return storeEntry({ ...entry });
|
|
10808
|
+
);
|
|
10809
|
+
}
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
|
|
10813
|
+
// src/distribution/cluster-node/node-operations.ts
|
|
10814
|
+
function createClusterNodeOperations(deps) {
|
|
10815
|
+
const { trackOp, readDeps, writeDeps, engine, coordinator } = deps;
|
|
10816
|
+
return {
|
|
10817
|
+
async createIndex(name, indexConfig, options) {
|
|
10818
|
+
return trackOp(name, () => routeCreateIndex(name, indexConfig, options, coordinator, engine));
|
|
10800
10819
|
},
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
const idx = findIndexBySeqNo(fromSeqNo);
|
|
10804
|
-
if (idx >= entries.length) return [];
|
|
10805
|
-
return entries.slice(idx).map((e) => ({ ...e }));
|
|
10820
|
+
async insert(indexName, document, docId, options) {
|
|
10821
|
+
return trackOp(indexName, () => routeInsert(indexName, document, docId, writeDeps, options));
|
|
10806
10822
|
},
|
|
10807
|
-
|
|
10808
|
-
return
|
|
10823
|
+
async insertBatch(indexName, documents, options) {
|
|
10824
|
+
return trackOp(indexName, () => routeInsertBatch(indexName, documents, writeDeps, options));
|
|
10809
10825
|
},
|
|
10810
|
-
|
|
10811
|
-
return
|
|
10826
|
+
async remove(indexName, docId) {
|
|
10827
|
+
return trackOp(indexName, () => routeRemove(indexName, docId, writeDeps));
|
|
10812
10828
|
},
|
|
10813
|
-
|
|
10814
|
-
return
|
|
10829
|
+
async removeBatch(indexName, docIds) {
|
|
10830
|
+
return trackOp(indexName, () => routeRemoveBatch(indexName, docIds, writeDeps));
|
|
10815
10831
|
},
|
|
10816
|
-
|
|
10817
|
-
return
|
|
10832
|
+
async update(indexName, docId, document) {
|
|
10833
|
+
return trackOp(indexName, () => routeUpdate(indexName, docId, document, writeDeps));
|
|
10818
10834
|
},
|
|
10819
|
-
|
|
10820
|
-
return
|
|
10835
|
+
async updateBatch(indexName, updates) {
|
|
10836
|
+
return trackOp(indexName, () => routeUpdateBatch(indexName, updates, writeDeps));
|
|
10821
10837
|
},
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
}
|
|
10838
|
+
async dropIndex(name) {
|
|
10839
|
+
return deps.transitionIndex(name, async () => {
|
|
10840
|
+
await routeDropIndex(name, coordinator, engine);
|
|
10841
|
+
deps.forgetIndex(name);
|
|
10842
|
+
});
|
|
10827
10843
|
},
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
return entries[headIndex].seqNo;
|
|
10844
|
+
async clear(indexName) {
|
|
10845
|
+
return trackOp(indexName, () => clearCluster(readDeps, writeDeps, indexName));
|
|
10831
10846
|
},
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
return entries[entries.length - 1].seqNo;
|
|
10847
|
+
async countDocuments(indexName) {
|
|
10848
|
+
return trackOp(indexName, () => countCluster(readDeps, indexName));
|
|
10835
10849
|
},
|
|
10836
|
-
|
|
10837
|
-
return
|
|
10850
|
+
async listDocuments(indexName, params) {
|
|
10851
|
+
return trackOp(indexName, () => listCluster(readDeps, indexName, params ?? {}));
|
|
10838
10852
|
},
|
|
10839
|
-
|
|
10840
|
-
return
|
|
10853
|
+
async suggest(indexName, params) {
|
|
10854
|
+
return trackOp(indexName, () => suggestCluster(readDeps, indexName, params));
|
|
10841
10855
|
},
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10856
|
+
async preflight(indexName, params) {
|
|
10857
|
+
return trackOp(indexName, () => preflightCluster(readDeps, indexName, params));
|
|
10858
|
+
},
|
|
10859
|
+
async getStats(indexName) {
|
|
10860
|
+
return trackOp(indexName, () => statsCluster(readDeps, indexName));
|
|
10861
|
+
},
|
|
10862
|
+
async getPartitionStats(indexName) {
|
|
10863
|
+
return trackOp(indexName, () => partitionStatsCluster(readDeps, indexName));
|
|
10864
|
+
},
|
|
10865
|
+
async checkpoint(indexName) {
|
|
10866
|
+
return trackOp(indexName, () => engine.checkpoint(indexName));
|
|
10867
|
+
},
|
|
10868
|
+
async getMemoryStats() {
|
|
10869
|
+
return trackOp(null, () => engine.getMemoryStats());
|
|
10870
|
+
},
|
|
10871
|
+
on(event, handler) {
|
|
10872
|
+
engine.on(event, handler);
|
|
10873
|
+
},
|
|
10874
|
+
off(event, handler) {
|
|
10875
|
+
engine.off(event, handler);
|
|
10876
|
+
},
|
|
10877
|
+
async query(indexName, params) {
|
|
10878
|
+
return trackOp(indexName, () => queryCluster(readDeps, indexName, params));
|
|
10879
|
+
},
|
|
10880
|
+
async get(indexName, docId) {
|
|
10881
|
+
return trackOp(indexName, async () => (await readClusterDocuments(readDeps, indexName, [docId])).get(docId));
|
|
10882
|
+
},
|
|
10883
|
+
async getMultiple(indexName, docIds) {
|
|
10884
|
+
return trackOp(indexName, () => readClusterDocuments(readDeps, indexName, docIds));
|
|
10885
|
+
},
|
|
10886
|
+
async has(indexName, docId) {
|
|
10887
|
+
return trackOp(indexName, async () => (await readClusterDocuments(readDeps, indexName, [docId])).has(docId));
|
|
10848
10888
|
}
|
|
10849
10889
|
};
|
|
10850
10890
|
}
|
|
10851
10891
|
|
|
10852
|
-
// src/distribution/cluster-node/
|
|
10853
|
-
function
|
|
10854
|
-
|
|
10892
|
+
// src/distribution/cluster-node/operation-tracker.ts
|
|
10893
|
+
function createClusterOperationTracker(config) {
|
|
10894
|
+
let activeOperations = 0;
|
|
10895
|
+
let drainResolve = null;
|
|
10896
|
+
const activeByIndex = /* @__PURE__ */ new Map();
|
|
10897
|
+
const indexDrainResolvers = /* @__PURE__ */ new Map();
|
|
10898
|
+
const transitions = /* @__PURE__ */ new Map();
|
|
10899
|
+
function releaseIndex(indexName) {
|
|
10900
|
+
const remaining = (activeByIndex.get(indexName) ?? 1) - 1;
|
|
10901
|
+
if (remaining > 0) {
|
|
10902
|
+
activeByIndex.set(indexName, remaining);
|
|
10903
|
+
return;
|
|
10904
|
+
}
|
|
10905
|
+
activeByIndex.delete(indexName);
|
|
10906
|
+
for (const resolve of indexDrainResolvers.get(indexName) ?? []) resolve();
|
|
10907
|
+
indexDrainResolvers.delete(indexName);
|
|
10908
|
+
}
|
|
10909
|
+
async function track(indexName, action) {
|
|
10910
|
+
config.guard();
|
|
10911
|
+
if (indexName !== null) config.assertIndex(indexName);
|
|
10912
|
+
activeOperations += 1;
|
|
10913
|
+
let indexCounted = false;
|
|
10914
|
+
try {
|
|
10915
|
+
if (indexName !== null) {
|
|
10916
|
+
const transition2 = transitions.get(indexName);
|
|
10917
|
+
if (transition2 !== void 0) await transition2;
|
|
10918
|
+
config.assertIndex(indexName);
|
|
10919
|
+
activeByIndex.set(indexName, (activeByIndex.get(indexName) ?? 0) + 1);
|
|
10920
|
+
indexCounted = true;
|
|
10921
|
+
}
|
|
10922
|
+
return await action();
|
|
10923
|
+
} finally {
|
|
10924
|
+
if (indexName !== null && indexCounted) releaseIndex(indexName);
|
|
10925
|
+
activeOperations -= 1;
|
|
10926
|
+
if (activeOperations === 0 && drainResolve !== null) {
|
|
10927
|
+
drainResolve();
|
|
10928
|
+
drainResolve = null;
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
}
|
|
10932
|
+
async function waitForIndexDrain(indexName) {
|
|
10933
|
+
if ((activeByIndex.get(indexName) ?? 0) === 0) return;
|
|
10934
|
+
await new Promise((resolve) => {
|
|
10935
|
+
let resolvers = indexDrainResolvers.get(indexName);
|
|
10936
|
+
if (resolvers === void 0) {
|
|
10937
|
+
resolvers = /* @__PURE__ */ new Set();
|
|
10938
|
+
indexDrainResolvers.set(indexName, resolvers);
|
|
10939
|
+
}
|
|
10940
|
+
resolvers.add(resolve);
|
|
10941
|
+
});
|
|
10942
|
+
}
|
|
10943
|
+
function transition(indexName, action) {
|
|
10944
|
+
return track(null, async () => {
|
|
10945
|
+
const previous = transitions.get(indexName) ?? Promise.resolve();
|
|
10946
|
+
const run = previous.catch(() => void 0).then(async () => {
|
|
10947
|
+
await waitForIndexDrain(indexName);
|
|
10948
|
+
return action();
|
|
10949
|
+
});
|
|
10950
|
+
const tail = run.then(
|
|
10951
|
+
() => void 0,
|
|
10952
|
+
() => void 0
|
|
10953
|
+
);
|
|
10954
|
+
transitions.set(indexName, tail);
|
|
10955
|
+
try {
|
|
10956
|
+
return await run;
|
|
10957
|
+
} finally {
|
|
10958
|
+
if (transitions.get(indexName) === tail) transitions.delete(indexName);
|
|
10959
|
+
}
|
|
10960
|
+
});
|
|
10961
|
+
}
|
|
10962
|
+
async function drain() {
|
|
10963
|
+
if (activeOperations === 0) return;
|
|
10964
|
+
await new Promise((resolve) => {
|
|
10965
|
+
drainResolve = resolve;
|
|
10966
|
+
});
|
|
10967
|
+
}
|
|
10968
|
+
return { track, transition, drain };
|
|
10855
10969
|
}
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10970
|
+
|
|
10971
|
+
// src/distribution/cluster-node/primary-partition.ts
|
|
10972
|
+
async function preparePrimaryPartition(indexName, partitionId, deps) {
|
|
10973
|
+
const schema = await deps.coordinator.getSchema(indexName);
|
|
10974
|
+
if (schema === null) {
|
|
10975
|
+
return false;
|
|
10862
10976
|
}
|
|
10863
|
-
|
|
10977
|
+
const allocation = await deps.coordinator.getAllocation(indexName);
|
|
10978
|
+
if (allocation === null || allocation.assignments.size === 0) {
|
|
10979
|
+
return false;
|
|
10980
|
+
}
|
|
10981
|
+
const existing = deps.engine.listIndexes().find((index) => index.name === indexName);
|
|
10982
|
+
if (existing === void 0) {
|
|
10983
|
+
try {
|
|
10984
|
+
await deps.engine.createIndex(indexName, {
|
|
10985
|
+
schema,
|
|
10986
|
+
partitions: { maxPartitions: allocation.assignments.size }
|
|
10987
|
+
});
|
|
10988
|
+
} catch (error) {
|
|
10989
|
+
if (!(error instanceof NarsilError) || error.code !== ErrorCodes.INDEX_ALREADY_EXISTS) {
|
|
10990
|
+
deps.onError(error);
|
|
10991
|
+
return false;
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
} else {
|
|
10995
|
+
const schemaError = validateRestoredSchema(deps.engine, indexName, deps.nodeId, schema);
|
|
10996
|
+
if (schemaError !== null) {
|
|
10997
|
+
deps.onError(schemaError);
|
|
10998
|
+
return false;
|
|
10999
|
+
}
|
|
11000
|
+
}
|
|
11001
|
+
resumeNumberingAboveOwnCopy(indexName, partitionId, allocation.assignments.get(partitionId), deps);
|
|
11002
|
+
return true;
|
|
10864
11003
|
}
|
|
10865
|
-
function
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
)
|
|
11004
|
+
function resumeNumberingAboveOwnCopy(indexName, partitionId, assignment, deps) {
|
|
11005
|
+
if (assignment === void 0) {
|
|
11006
|
+
return;
|
|
11007
|
+
}
|
|
11008
|
+
if (deps.replicationLogPosition(indexName, partitionId) > 0) {
|
|
11009
|
+
return;
|
|
11010
|
+
}
|
|
11011
|
+
const floor = Math.max(deps.engine.highestPersistedSeqNoOf(indexName, partitionId), assignment.commitPoint);
|
|
11012
|
+
if (floor <= 0) {
|
|
11013
|
+
return;
|
|
11014
|
+
}
|
|
11015
|
+
deps.seedReplicationLog(indexName, partitionId, floor + 1, assignment.primaryTerm);
|
|
10874
11016
|
}
|
|
10875
11017
|
var CONTROLLER_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
10876
11018
|
ReplicationMessageTypes.INSYNC_ADD,
|
|
@@ -11041,10 +11183,25 @@ async function createClusterNode(config) {
|
|
|
11041
11183
|
const nodeId = config.nodeId ?? generateId();
|
|
11042
11184
|
const roles = config.roles ?? ["data", "coordinator", "controller"];
|
|
11043
11185
|
const capacity = config.capacity ?? DEFAULT_CAPACITY;
|
|
11044
|
-
const engine = await createClusterLocalEngine(config.engine);
|
|
11045
11186
|
const bootstrapSyncState = createBootstrapSyncState();
|
|
11046
11187
|
const snapshotSyncHandlerState = createSnapshotSyncHandlerState();
|
|
11047
11188
|
const replicationLogs = /* @__PURE__ */ new Map();
|
|
11189
|
+
let localEngine = null;
|
|
11190
|
+
const engine = await createClusterLocalEngine(config.engine, {
|
|
11191
|
+
async onIndexOpen(indexName) {
|
|
11192
|
+
if (localEngine === null) return;
|
|
11193
|
+
await seedReopenedPrimaryLogs({
|
|
11194
|
+
indexName,
|
|
11195
|
+
nodeId,
|
|
11196
|
+
engine: localEngine,
|
|
11197
|
+
coordinator: config.coordinator,
|
|
11198
|
+
replicationLogs,
|
|
11199
|
+
replicationConfig: config.replication
|
|
11200
|
+
});
|
|
11201
|
+
},
|
|
11202
|
+
onIndexClose: (indexName) => deleteIndexReplicationLogs(replicationLogs, indexName)
|
|
11203
|
+
});
|
|
11204
|
+
localEngine = engine;
|
|
11048
11205
|
function replicationLogKey2(indexName, partitionId) {
|
|
11049
11206
|
return replicationLogKey(indexName, partitionId);
|
|
11050
11207
|
}
|
|
@@ -11094,8 +11251,6 @@ async function createClusterNode(config) {
|
|
|
11094
11251
|
let unregisterNotReadyHandler = null;
|
|
11095
11252
|
let serving = false;
|
|
11096
11253
|
let isShutdown = false;
|
|
11097
|
-
let activeOps = 0;
|
|
11098
|
-
let drainResolve = null;
|
|
11099
11254
|
const bootstrapSyncDeps = {
|
|
11100
11255
|
engine,
|
|
11101
11256
|
coordinator: config.coordinator,
|
|
@@ -11168,22 +11323,14 @@ async function createClusterNode(config) {
|
|
|
11168
11323
|
throw new NarsilError(ErrorCodes.NODE_NOT_JOINED, `Cluster node '${nodeId}' has been shut down`, { nodeId });
|
|
11169
11324
|
}
|
|
11170
11325
|
}
|
|
11171
|
-
|
|
11172
|
-
guardShutdown
|
|
11173
|
-
|
|
11174
|
-
throw orphanedIndexError(nodeId, indexName);
|
|
11175
|
-
}
|
|
11176
|
-
activeOps++;
|
|
11177
|
-
try {
|
|
11178
|
-
return await fn();
|
|
11179
|
-
} finally {
|
|
11180
|
-
activeOps--;
|
|
11181
|
-
if (activeOps === 0 && drainResolve !== null) {
|
|
11182
|
-
drainResolve();
|
|
11183
|
-
drainResolve = null;
|
|
11184
|
-
}
|
|
11326
|
+
const operationTracker = createClusterOperationTracker({
|
|
11327
|
+
guard: guardShutdown,
|
|
11328
|
+
assertIndex(indexName) {
|
|
11329
|
+
if (orphanedIndexes.has(indexName)) throw orphanedIndexError(nodeId, indexName);
|
|
11185
11330
|
}
|
|
11186
|
-
}
|
|
11331
|
+
});
|
|
11332
|
+
const trackOp = operationTracker.track;
|
|
11333
|
+
const transitionIndex = operationTracker.transition;
|
|
11187
11334
|
const readDeps = { config, nodeId, engine, resolveNodeTargets: resolveNodeTargets3 };
|
|
11188
11335
|
const cluster = createClusterNamespace({
|
|
11189
11336
|
nodeId,
|
|
@@ -11202,13 +11349,17 @@ async function createClusterNode(config) {
|
|
|
11202
11349
|
get roles() {
|
|
11203
11350
|
return roles;
|
|
11204
11351
|
},
|
|
11352
|
+
open: (indexName) => trackOp(indexName, () => engine.open(indexName)),
|
|
11353
|
+
close: (indexName) => transitionIndex(indexName, () => engine.close(indexName)),
|
|
11205
11354
|
...createClusterNodeOperations({
|
|
11206
11355
|
trackOp,
|
|
11356
|
+
transitionIndex,
|
|
11207
11357
|
readDeps,
|
|
11208
11358
|
writeDeps,
|
|
11209
11359
|
engine,
|
|
11210
11360
|
coordinator: config.coordinator,
|
|
11211
11361
|
forgetIndex: (indexName) => {
|
|
11362
|
+
deleteIndexReplicationLogs(replicationLogs, indexName);
|
|
11212
11363
|
if (engine.listIndexes().every((index) => index.name !== indexName)) {
|
|
11213
11364
|
orphanedIndexes.delete(indexName);
|
|
11214
11365
|
}
|
|
@@ -11258,11 +11409,7 @@ async function createClusterNode(config) {
|
|
|
11258
11409
|
}
|
|
11259
11410
|
isShutdown = true;
|
|
11260
11411
|
serving = false;
|
|
11261
|
-
|
|
11262
|
-
await new Promise((resolve) => {
|
|
11263
|
-
drainResolve = resolve;
|
|
11264
|
-
});
|
|
11265
|
-
}
|
|
11412
|
+
await operationTracker.drain();
|
|
11266
11413
|
if (unregisterHandler !== null) {
|
|
11267
11414
|
unregisterHandler();
|
|
11268
11415
|
unregisterHandler = null;
|
|
@@ -11285,14 +11432,20 @@ async function createClusterNode(config) {
|
|
|
11285
11432
|
if (partitionId === null) {
|
|
11286
11433
|
return fallback;
|
|
11287
11434
|
}
|
|
11288
|
-
const
|
|
11289
|
-
const
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
partitionId
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11435
|
+
const localIndex = engine.listIndexes().find((index) => index.name === indexName);
|
|
11436
|
+
const release = localIndex !== void 0 ? await engine.acquireIndexForReplication(indexName) : null;
|
|
11437
|
+
try {
|
|
11438
|
+
const allocation = await config.coordinator.getAllocation(indexName);
|
|
11439
|
+
const assignment = allocation?.assignments.get(partitionId);
|
|
11440
|
+
const log = getReplicationLog2(indexName, partitionId);
|
|
11441
|
+
return {
|
|
11442
|
+
partitionId,
|
|
11443
|
+
primaryTerm: assignment?.primaryTerm ?? fallback.primaryTerm,
|
|
11444
|
+
lastSeqNo: log.newestSeqNo ?? 0
|
|
11445
|
+
};
|
|
11446
|
+
} finally {
|
|
11447
|
+
release?.();
|
|
11448
|
+
}
|
|
11296
11449
|
}
|
|
11297
11450
|
}
|
|
11298
11451
|
|
|
@@ -11306,6 +11459,12 @@ function unsupported(operation) {
|
|
|
11306
11459
|
}
|
|
11307
11460
|
function clusterNodeEngine(node, options) {
|
|
11308
11461
|
return {
|
|
11462
|
+
open(indexName) {
|
|
11463
|
+
return node.open(indexName);
|
|
11464
|
+
},
|
|
11465
|
+
close(indexName) {
|
|
11466
|
+
return node.close(indexName);
|
|
11467
|
+
},
|
|
11309
11468
|
async createIndex(name, config) {
|
|
11310
11469
|
return node.createIndex(name, config, options?.createIndex);
|
|
11311
11470
|
},
|