@delali/narsil 0.2.3-next.35 → 0.2.3-next.36
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-EMI63EXP.mjs → chunk-GB2IIJJG.mjs} +2 -0
- package/dist/{chunk-RO4EFWE6.mjs → chunk-GRWFY2HI.mjs} +10 -1
- package/dist/{chunk-36ER4WCG.mjs → chunk-JQUL2T6I.mjs} +2 -1
- package/dist/{chunk-OOA7OBLA.mjs → chunk-MMSWEEZT.mjs} +69 -163
- package/dist/{chunk-GODECMDT.mjs → chunk-PM52SMBT.mjs} +1 -1
- package/dist/chunk-WU2UZEVD.mjs +137 -0
- package/dist/{client-DAJ_UHOa.d.ts → client-BobVnoIK.d.ts} +2 -2
- package/dist/client.d.ts +8 -4
- package/dist/client.mjs +2 -0
- package/dist/{config-CzTJJili.d.ts → config-Byyl1QqH.d.ts} +2 -2
- package/dist/config-fLC4lbmg.d.ts +194 -0
- package/dist/{direct-executor-YARCDMJF.mjs → direct-executor-MSJEVPZU.mjs} +2 -2
- package/dist/distribution/coordinator/etcd.d.ts +5 -7
- package/dist/distribution/coordinator/etcd.mjs +27 -8
- package/dist/distribution/coordinator/in-memory.d.ts +2 -2
- package/dist/distribution/transport/grpc.mjs +3 -3
- package/dist/distribution/transport/in-memory.mjs +1 -1
- package/dist/distribution/transport/tcp.mjs +2 -2
- package/dist/distribution.d.ts +10 -132
- package/dist/distribution.mjs +1089 -339
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-DEtRLYpB.d.ts → engine-BE45D5ZG.d.ts} +3 -3
- package/dist/index.browser.mjs +71 -23
- package/dist/index.d.ts +7 -7
- package/dist/index.mjs +5 -4
- package/dist/{options-BORJg5XB.d.ts → options-EXYFcem-.d.ts} +9 -0
- package/dist/persistence/durability/checkpoint-worker.d.ts +3 -1
- package/dist/persistence/durability/checkpoint-worker.mjs +5 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.mjs +2 -0
- package/dist/{schema-emTQfEiX.d.ts → schema-ZRbk8BgH.d.ts} +1 -0
- package/dist/{search-Dt4dkzpf.d.ts → search-DHuXwJYk.d.ts} +5 -1
- package/dist/server.d.ts +9 -5
- package/dist/server.mjs +101 -7
- package/dist/{types-BhdINMtE.d.ts → types-B5Tsaalz.d.ts} +19 -2
- package/dist/workers/entry.mjs +2 -2
- package/package.json +1 -1
package/dist/distribution.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createEngineCore, createNarsilFromCore, runEngineQueryStats, runEngineListDocuments, runEngineSuggest, runEnginePreflight, runEngineQuery, createEngineIndex, applyIndexEntry, applyDeleteEntry, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, now,
|
|
2
|
-
import { ReplicationMessageTypes, TransportErrorCodes, ClusterMessageTypes, QueryMessageTypes, TransportError, MAX_MESSAGE_SIZE_BYTES } from './chunk-
|
|
3
|
-
import {
|
|
1
|
+
import { createEngineCore, createNarsilFromCore, runEngineQueryStats, runEngineListDocuments, runEngineSuggest, runEnginePreflight, runEngineQuery, createEngineIndex, applyIndexEntry, applyDeleteEntry, DEFAULT_SUGGEST_LIMIT, MAX_SUGGEST_LIMIT, now, validateEntryBatchPayload, createAckMessage, validateEntryPayload, validateForwardBatchPayload, createForwardBatchResultMessage, clampLimit, createForwardBatchMessage, validateForwardBatchResultPayload, createForwardMessage, validateReplicationEntry, MAX_SUGGEST_SCATTER_LIMIT, chunkByBudget, WIRE_BATCH_BUDGET, SUGGEST_OVERSAMPLE_FACTOR, SUGGEST_OVERSAMPLE_PADDING, createEntryMessage, computeEntryChecksum, createEntryBatchMessage, createInsyncConfirmMessage, validateAckPayload, createInsyncRemoveMessage, validateInsyncConfirmPayload, createInsyncAddMessage } from './chunk-MMSWEEZT.mjs';
|
|
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-WU2UZEVD.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, FIELD_FILTER_OPERATORS, MAX_NESTING_DEPTH } from './chunk-GRWFY2HI.mjs';
|
|
4
5
|
import './chunk-TGP5M5R7.mjs';
|
|
5
6
|
import './chunk-OBXS7OZM.mjs';
|
|
6
7
|
import './chunk-WDL632CN.mjs';
|
|
@@ -8,7 +9,7 @@ import { crc32 } from './chunk-KMMJMDUA.mjs';
|
|
|
8
9
|
import './chunk-U2BKYEC7.mjs';
|
|
9
10
|
import { compareCodePoints, toComparableSortValue, compareComparableKeys, compareSortValues } from './chunk-BFIH7OV5.mjs';
|
|
10
11
|
import { generateId } from './chunk-WTONLDU3.mjs';
|
|
11
|
-
import { ErrorCodes, NarsilError, describeError } from './chunk-
|
|
12
|
+
import { ErrorCodes, NarsilError, describeError } from './chunk-GB2IIJJG.mjs';
|
|
12
13
|
import { encode, decode } from '@msgpack/msgpack';
|
|
13
14
|
|
|
14
15
|
// src/distribution/cluster/controller/types.ts
|
|
@@ -26,6 +27,12 @@ function createElectionState() {
|
|
|
26
27
|
standbyTimer: null
|
|
27
28
|
};
|
|
28
29
|
}
|
|
30
|
+
function clearStandbyTimer(state) {
|
|
31
|
+
if (state.standbyTimer !== null) {
|
|
32
|
+
clearTimeout(state.standbyTimer);
|
|
33
|
+
state.standbyTimer = null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
29
36
|
function clearElectionTimers(state) {
|
|
30
37
|
if (state.renewalTimer !== null) {
|
|
31
38
|
clearInterval(state.renewalTimer);
|
|
@@ -58,7 +65,11 @@ function startRenewalInterval(state, coordinator, nodeId, ttlMs, onLeaseLost) {
|
|
|
58
65
|
}, intervalMs);
|
|
59
66
|
}
|
|
60
67
|
function scheduleStandbyRetry(state, retryMs, onRetry) {
|
|
61
|
-
state
|
|
68
|
+
clearStandbyTimer(state);
|
|
69
|
+
state.standbyTimer = setTimeout(() => {
|
|
70
|
+
state.standbyTimer = null;
|
|
71
|
+
onRetry();
|
|
72
|
+
}, retryMs);
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
// src/distribution/cluster/allocator/deciders/balance.ts
|
|
@@ -397,15 +408,26 @@ function initialAllocate(nodes, indexName, partitionCount, replicationFactor, co
|
|
|
397
408
|
};
|
|
398
409
|
}
|
|
399
410
|
|
|
411
|
+
// src/distribution/cluster/last-holders.ts
|
|
412
|
+
function lastHoldersOf(assignment) {
|
|
413
|
+
return assignment.lastHolders ?? [];
|
|
414
|
+
}
|
|
415
|
+
function holdsLastCopy(assignment, nodeId) {
|
|
416
|
+
return lastHoldersOf(assignment).includes(nodeId);
|
|
417
|
+
}
|
|
418
|
+
|
|
400
419
|
// src/distribution/cluster/allocator/leadership.ts
|
|
401
420
|
var LEADERSHIP_IMBALANCE_THRESHOLD = 2;
|
|
402
421
|
function loadOf(nodeId, counts, shares) {
|
|
403
422
|
const share = shares.get(nodeId);
|
|
404
423
|
return (counts.get(nodeId) ?? 0) / (share === void 0 || share <= 0 ? 1 : share);
|
|
405
424
|
}
|
|
406
|
-
function heaviestLeader(shares, counts) {
|
|
425
|
+
function heaviestLeader(shares, counts, withoutHandover) {
|
|
407
426
|
let heaviest;
|
|
408
427
|
for (const nodeId of shares.keys()) {
|
|
428
|
+
if (withoutHandover.has(nodeId)) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
409
431
|
if (heaviest === void 0 || loadOf(nodeId, counts, shares) > loadOf(heaviest, counts, shares)) {
|
|
410
432
|
heaviest = nodeId;
|
|
411
433
|
}
|
|
@@ -435,19 +457,24 @@ function applySwap(swap, previousPrimary) {
|
|
|
435
457
|
const { assignment, candidateNodeId } = swap;
|
|
436
458
|
assignment.primary = candidateNodeId;
|
|
437
459
|
assignment.replicas = [...assignment.replicas.filter((nodeId) => nodeId !== candidateNodeId), previousPrimary];
|
|
438
|
-
assignment.inSyncSet =
|
|
460
|
+
assignment.inSyncSet = [
|
|
461
|
+
...assignment.inSyncSet.filter((nodeId) => nodeId !== candidateNodeId && nodeId !== previousPrimary),
|
|
462
|
+
previousPrimary
|
|
463
|
+
];
|
|
439
464
|
assignment.primaryTerm += 1;
|
|
440
465
|
}
|
|
441
466
|
function rebalanceLeadership(assignments, shares) {
|
|
467
|
+
const withoutHandover = /* @__PURE__ */ new Set();
|
|
442
468
|
for (let swaps = 0; swaps < assignments.size; swaps++) {
|
|
443
469
|
const counts = countPrimaryAssignments(assignments);
|
|
444
|
-
const heaviest = heaviestLeader(shares, counts);
|
|
470
|
+
const heaviest = heaviestLeader(shares, counts, withoutHandover);
|
|
445
471
|
if (heaviest === void 0) {
|
|
446
472
|
return;
|
|
447
473
|
}
|
|
448
474
|
const swap = findSwap(assignments, heaviest, counts, shares);
|
|
449
475
|
if (swap === null) {
|
|
450
|
-
|
|
476
|
+
withoutHandover.add(heaviest);
|
|
477
|
+
continue;
|
|
451
478
|
}
|
|
452
479
|
applySwap(swap, heaviest);
|
|
453
480
|
}
|
|
@@ -475,15 +502,25 @@ function cloneAssignments(assignments) {
|
|
|
475
502
|
inSyncSet: [...assignment.inSyncSet],
|
|
476
503
|
commitPoint: assignment.commitPoint,
|
|
477
504
|
state: assignment.state,
|
|
478
|
-
primaryTerm: assignment.primaryTerm
|
|
505
|
+
primaryTerm: assignment.primaryTerm,
|
|
506
|
+
...assignment.lastHolders !== void 0 ? { lastHolders: [...assignment.lastHolders] } : {},
|
|
507
|
+
...assignment.unassignedReason !== void 0 ? { unassignedReason: assignment.unassignedReason } : {}
|
|
479
508
|
});
|
|
480
509
|
}
|
|
481
510
|
return cloned;
|
|
482
511
|
}
|
|
483
|
-
function markUnassigned(assignment) {
|
|
512
|
+
function markUnassigned(assignment, lastPrimary, lastInSyncSet) {
|
|
513
|
+
const holders = new Set(lastHoldersOf(assignment));
|
|
514
|
+
for (const nodeId of lastInSyncSet) {
|
|
515
|
+
holders.add(nodeId);
|
|
516
|
+
}
|
|
517
|
+
if (lastPrimary !== null) {
|
|
518
|
+
holders.add(lastPrimary);
|
|
519
|
+
}
|
|
484
520
|
assignment.primary = null;
|
|
485
521
|
assignment.replicas = [];
|
|
486
522
|
assignment.inSyncSet = [];
|
|
523
|
+
assignment.lastHolders = [...holders].sort(compareCodePoints);
|
|
487
524
|
assignment.state = "UNASSIGNED";
|
|
488
525
|
}
|
|
489
526
|
function primaryLoadOf(nodeId, primaryCounts, shares) {
|
|
@@ -508,7 +545,12 @@ function leastLoadedCandidate(candidates, primaryCounts, shares) {
|
|
|
508
545
|
function handleLostNodes(assignments, activeNodeIds, shares) {
|
|
509
546
|
const primaryCounts = countPrimaryAssignments(assignments);
|
|
510
547
|
for (const assignment of assignments.values()) {
|
|
511
|
-
|
|
548
|
+
if (assignment.state === "UNASSIGNED") {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const lastPrimary = assignment.primary;
|
|
552
|
+
const lastInSyncSet = [...assignment.inSyncSet];
|
|
553
|
+
const primaryWasLost = lastPrimary !== null && !activeNodeIds.has(lastPrimary);
|
|
512
554
|
if (primaryWasLost) {
|
|
513
555
|
assignment.primary = null;
|
|
514
556
|
}
|
|
@@ -516,18 +558,18 @@ function handleLostNodes(assignments, activeNodeIds, shares) {
|
|
|
516
558
|
assignment.inSyncSet = assignment.inSyncSet.filter((id) => activeNodeIds.has(id));
|
|
517
559
|
if (!primaryWasLost) {
|
|
518
560
|
if (assignment.primary === null) {
|
|
519
|
-
markUnassigned(assignment);
|
|
561
|
+
markUnassigned(assignment, lastPrimary, lastInSyncSet);
|
|
520
562
|
}
|
|
521
563
|
continue;
|
|
522
564
|
}
|
|
523
565
|
const inSyncCandidates = assignment.replicas.filter((id) => assignment.inSyncSet.includes(id));
|
|
524
566
|
if (inSyncCandidates.length === 0) {
|
|
525
|
-
markUnassigned(assignment);
|
|
567
|
+
markUnassigned(assignment, lastPrimary, lastInSyncSet);
|
|
526
568
|
continue;
|
|
527
569
|
}
|
|
528
570
|
const promoted = leastLoadedCandidate(inSyncCandidates, primaryCounts, shares);
|
|
529
571
|
if (promoted === void 0) {
|
|
530
|
-
markUnassigned(assignment);
|
|
572
|
+
markUnassigned(assignment, lastPrimary, lastInSyncSet);
|
|
531
573
|
continue;
|
|
532
574
|
}
|
|
533
575
|
primaryCounts.set(promoted, (primaryCounts.get(promoted) ?? 0) + 1);
|
|
@@ -634,8 +676,21 @@ function moveOneReplica(assignments, move, laggingOnly) {
|
|
|
634
676
|
}
|
|
635
677
|
return false;
|
|
636
678
|
}
|
|
679
|
+
function clearRecoveryStateOnServedPartitions(assignments) {
|
|
680
|
+
for (const assignment of assignments.values()) {
|
|
681
|
+
if (assignment.state !== "UNASSIGNED") {
|
|
682
|
+
delete assignment.unassignedReason;
|
|
683
|
+
}
|
|
684
|
+
if (assignment.state === "ACTIVE") {
|
|
685
|
+
delete assignment.lastHolders;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
637
689
|
function pruneInSyncSets(assignments) {
|
|
638
690
|
for (const assignment of assignments.values()) {
|
|
691
|
+
if (assignment.state === "UNASSIGNED") {
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
639
694
|
const stale = assignment.inSyncSet.some((nodeId) => !assignment.replicas.includes(nodeId));
|
|
640
695
|
if (stale) {
|
|
641
696
|
assignment.inSyncSet = assignment.inSyncSet.filter((nodeId) => assignment.replicas.includes(nodeId));
|
|
@@ -660,6 +715,7 @@ function rebalanceAllocate(nodes, currentTable, constraints, deciders) {
|
|
|
660
715
|
rebalanceForBalance(assignments, sortedNodes, nodeMap, constraints, deciders, shares);
|
|
661
716
|
rebalanceLeadership(assignments, shares);
|
|
662
717
|
pruneInSyncSets(assignments);
|
|
718
|
+
clearRecoveryStateOnServedPartitions(assignments);
|
|
663
719
|
const warnings = collectReplicationWarnings(assignments, currentTable.replicationFactor);
|
|
664
720
|
return {
|
|
665
721
|
table: {
|
|
@@ -708,7 +764,268 @@ function allocate(nodes, currentTable, indexName, partitionCount, replicationFac
|
|
|
708
764
|
return rebalanceAllocate(dataNodes, currentTable, constraints, deciders);
|
|
709
765
|
}
|
|
710
766
|
|
|
767
|
+
// src/distribution/payload-guards.ts
|
|
768
|
+
function isRecord(value) {
|
|
769
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
770
|
+
}
|
|
771
|
+
function isValidInteger(value) {
|
|
772
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/distribution/cluster/partition-stores.ts
|
|
776
|
+
function validatePartitionStoresPayload(decoded) {
|
|
777
|
+
if (!isRecord(decoded)) {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
if (typeof decoded.indexName !== "string" || decoded.indexName.length === 0) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
return { indexName: decoded.indexName };
|
|
784
|
+
}
|
|
785
|
+
function validatePartitionStoresResultPayload(decoded) {
|
|
786
|
+
if (!isRecord(decoded)) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
if (typeof decoded.indexName !== "string" || decoded.indexName.length === 0) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
if (decoded.indexUuid !== null && typeof decoded.indexUuid !== "string") {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
if (!Array.isArray(decoded.partitionIds)) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
const partitionIds = [];
|
|
799
|
+
for (const partitionId of decoded.partitionIds) {
|
|
800
|
+
if (typeof partitionId !== "number" || !Number.isInteger(partitionId) || partitionId < 0) {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
partitionIds.push(partitionId);
|
|
804
|
+
}
|
|
805
|
+
return { indexName: decoded.indexName, indexUuid: decoded.indexUuid, partitionIds };
|
|
806
|
+
}
|
|
807
|
+
function createPartitionStoresMessage(payload, sourceNodeId, requestId) {
|
|
808
|
+
return {
|
|
809
|
+
type: ClusterMessageTypes.PARTITION_STORES,
|
|
810
|
+
sourceId: sourceNodeId,
|
|
811
|
+
requestId,
|
|
812
|
+
payload: encode(payload)
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function createPartitionStoresResultMessage(result, sourceNodeId, requestId) {
|
|
816
|
+
return {
|
|
817
|
+
type: ClusterMessageTypes.PARTITION_STORES,
|
|
818
|
+
sourceId: sourceNodeId,
|
|
819
|
+
requestId,
|
|
820
|
+
payload: encode(result)
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/distribution/cluster/controller/event-loop/unassigned-recovery.ts
|
|
825
|
+
var RECOVERY_CAS_ATTEMPTS = 5;
|
|
826
|
+
var PARTITION_STORES_TIMEOUT_MS = 5e3;
|
|
827
|
+
function liveHoldersOfUnassignedPartitions(table, liveNodeIds) {
|
|
828
|
+
const holders = /* @__PURE__ */ new Set();
|
|
829
|
+
for (const assignment of table.assignments.values()) {
|
|
830
|
+
if (assignment.state !== "UNASSIGNED") {
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
for (const nodeId of lastHoldersOf(assignment)) {
|
|
834
|
+
if (liveNodeIds.has(nodeId)) {
|
|
835
|
+
holders.add(nodeId);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return [...holders].sort(compareCodePoints);
|
|
840
|
+
}
|
|
841
|
+
function hasRecoverablePartition(table) {
|
|
842
|
+
for (const assignment of table.assignments.values()) {
|
|
843
|
+
if (assignment.state === "UNASSIGNED" && lastHoldersOf(assignment).length > 0) {
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
function reasonFor(assignment, liveNodeIds, answers) {
|
|
850
|
+
const holders = lastHoldersOf(assignment);
|
|
851
|
+
if (holders.length === 0) {
|
|
852
|
+
return void 0;
|
|
853
|
+
}
|
|
854
|
+
if (holders.some((nodeId) => !liveNodeIds.has(nodeId))) {
|
|
855
|
+
return "HOLDER_OFFLINE";
|
|
856
|
+
}
|
|
857
|
+
const given = holders.map((nodeId) => answers.get(nodeId));
|
|
858
|
+
if (given.some((answer) => answer === void 0 || answer.kind === "unreachable")) {
|
|
859
|
+
return "HOLDER_UNREACHABLE";
|
|
860
|
+
}
|
|
861
|
+
if (given.some((answer) => answer?.kind === "identityMismatch")) {
|
|
862
|
+
return "HOLDER_IDENTITY_MISMATCH";
|
|
863
|
+
}
|
|
864
|
+
return "HOLDER_WITHOUT_DATA";
|
|
865
|
+
}
|
|
866
|
+
function targetsFor(deps, targetNodeId) {
|
|
867
|
+
const targets = [targetNodeId];
|
|
868
|
+
const address = deps.addressByNodeId.get(targetNodeId);
|
|
869
|
+
if (address !== void 0 && address.length > 0 && address !== targetNodeId) {
|
|
870
|
+
targets.push(address);
|
|
871
|
+
}
|
|
872
|
+
return targets;
|
|
873
|
+
}
|
|
874
|
+
function withTimeout(work, timeoutMs, fallback) {
|
|
875
|
+
return new Promise((resolve) => {
|
|
876
|
+
const timer = setTimeout(() => resolve(fallback), timeoutMs);
|
|
877
|
+
timer.unref?.();
|
|
878
|
+
work.then((value) => {
|
|
879
|
+
clearTimeout(timer);
|
|
880
|
+
resolve(value);
|
|
881
|
+
}).catch(() => {
|
|
882
|
+
clearTimeout(timer);
|
|
883
|
+
resolve(fallback);
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
async function sendToFirstReachableTarget(deps, targetNodeId, message) {
|
|
888
|
+
for (const target of targetsFor(deps, targetNodeId)) {
|
|
889
|
+
const response = await withTimeout(deps.transport.send(target, message), PARTITION_STORES_TIMEOUT_MS, null);
|
|
890
|
+
if (response !== null) {
|
|
891
|
+
return response;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
async function askNodeForStores(deps, targetNodeId, indexName, indexUuid) {
|
|
897
|
+
const message = createPartitionStoresMessage({ indexName }, deps.controllerNodeId, generateId());
|
|
898
|
+
const response = await sendToFirstReachableTarget(deps, targetNodeId, message);
|
|
899
|
+
if (response === null) {
|
|
900
|
+
return { kind: "unreachable" };
|
|
901
|
+
}
|
|
902
|
+
let answer;
|
|
903
|
+
try {
|
|
904
|
+
answer = validatePartitionStoresResultPayload(decode(response.payload));
|
|
905
|
+
} catch (_) {
|
|
906
|
+
return { kind: "unreachable" };
|
|
907
|
+
}
|
|
908
|
+
if (answer === null || answer.indexName !== indexName) {
|
|
909
|
+
return { kind: "unreachable" };
|
|
910
|
+
}
|
|
911
|
+
if (answer.indexUuid !== indexUuid) {
|
|
912
|
+
return { kind: "identityMismatch" };
|
|
913
|
+
}
|
|
914
|
+
return { kind: "held", partitionIds: new Set(answer.partitionIds) };
|
|
915
|
+
}
|
|
916
|
+
function promoteAssignment(assignment, nodeId) {
|
|
917
|
+
const promoted = {
|
|
918
|
+
...assignment,
|
|
919
|
+
primary: nodeId,
|
|
920
|
+
replicas: [],
|
|
921
|
+
inSyncSet: [],
|
|
922
|
+
primaryTerm: assignment.primaryTerm + 1,
|
|
923
|
+
state: "INITIALISING"
|
|
924
|
+
};
|
|
925
|
+
delete promoted.unassignedReason;
|
|
926
|
+
return promoted;
|
|
927
|
+
}
|
|
928
|
+
function holderThatAnswered(assignment, partitionId, answers) {
|
|
929
|
+
const holders = [...lastHoldersOf(assignment)].sort(compareCodePoints);
|
|
930
|
+
return holders.find((nodeId) => {
|
|
931
|
+
const answer = answers.get(nodeId);
|
|
932
|
+
return answer?.kind === "held" && answer.partitionIds.has(partitionId);
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function rewriteAssignment(assignment, partitionId, liveNodeIds, answers) {
|
|
936
|
+
const holder = holderThatAnswered(assignment, partitionId, answers);
|
|
937
|
+
if (holder !== void 0) {
|
|
938
|
+
return { assignment: promoteAssignment(assignment, holder), promoted: true };
|
|
939
|
+
}
|
|
940
|
+
const reason = reasonFor(assignment, liveNodeIds, answers);
|
|
941
|
+
if (reason === assignment.unassignedReason) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
if (reason === void 0) {
|
|
945
|
+
const cleared = { ...assignment };
|
|
946
|
+
delete cleared.unassignedReason;
|
|
947
|
+
return { assignment: cleared, promoted: false };
|
|
948
|
+
}
|
|
949
|
+
return { assignment: { ...assignment, unassignedReason: reason }, promoted: false };
|
|
950
|
+
}
|
|
951
|
+
async function writeRecoveryOutcome(deps, indexName, liveNodeIds, answers) {
|
|
952
|
+
for (let attempt = 0; attempt < RECOVERY_CAS_ATTEMPTS; attempt++) {
|
|
953
|
+
if (!deps.isActive()) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
const table = await deps.coordinator.getAllocation(indexName);
|
|
957
|
+
if (table === null) {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
const rewritten = /* @__PURE__ */ new Map();
|
|
961
|
+
let anyPromoted = false;
|
|
962
|
+
for (const [partitionId, assignment] of table.assignments) {
|
|
963
|
+
if (assignment.state !== "UNASSIGNED") {
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
const outcome = rewriteAssignment(assignment, partitionId, liveNodeIds, answers);
|
|
967
|
+
if (outcome === null) {
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
rewritten.set(partitionId, outcome.assignment);
|
|
971
|
+
anyPromoted = anyPromoted || outcome.promoted;
|
|
972
|
+
}
|
|
973
|
+
if (rewritten.size === 0) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
const assignments = new Map(table.assignments);
|
|
977
|
+
for (const [partitionId, assignment] of rewritten) {
|
|
978
|
+
assignments.set(partitionId, assignment);
|
|
979
|
+
}
|
|
980
|
+
if (!deps.isActive()) {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
const written = await deps.coordinator.putAllocation(
|
|
984
|
+
indexName,
|
|
985
|
+
{ ...table, version: table.version + 1, assignments },
|
|
986
|
+
table.version
|
|
987
|
+
);
|
|
988
|
+
if (written) {
|
|
989
|
+
return anyPromoted;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
async function recoverUnassignedPartitions(coordinator, transport, indexName, controllerNodeId, nodes, isActive) {
|
|
995
|
+
const addressByNodeId = new Map(nodes.map((node) => [node.nodeId, node.address]));
|
|
996
|
+
const liveNodeIds = new Set(addressByNodeId.keys());
|
|
997
|
+
const deps = { coordinator, transport, controllerNodeId, addressByNodeId, isActive };
|
|
998
|
+
if (!isActive()) {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
const table = await coordinator.getAllocation(indexName);
|
|
1002
|
+
if (table === null) {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
if (!hasRecoverablePartition(table) || !isActive()) {
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
const answers = /* @__PURE__ */ new Map();
|
|
1009
|
+
const candidateNodeIds = liveHoldersOfUnassignedPartitions(table, liveNodeIds);
|
|
1010
|
+
if (candidateNodeIds.length > 0) {
|
|
1011
|
+
const metadata = await getIndexMetadata(coordinator, indexName);
|
|
1012
|
+
if (metadata === null || !isActive()) {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
const given = await Promise.all(
|
|
1016
|
+
candidateNodeIds.map((nodeId) => askNodeForStores(deps, nodeId, indexName, metadata.indexUuid))
|
|
1017
|
+
);
|
|
1018
|
+
for (let index = 0; index < candidateNodeIds.length; index += 1) {
|
|
1019
|
+
answers.set(candidateNodeIds[index], given[index]);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return writeRecoveryOutcome(deps, indexName, liveNodeIds, answers);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
711
1025
|
// src/distribution/cluster/controller/event-loop/allocation.ts
|
|
1026
|
+
function holdsDataNode(nodes) {
|
|
1027
|
+
return nodes.some((node) => node.roles.includes("data"));
|
|
1028
|
+
}
|
|
712
1029
|
var ALLOCATION_CAS_ATTEMPTS = 5;
|
|
713
1030
|
var ALLOCATION_RETRY_DELAY_MS = 1e3;
|
|
714
1031
|
var TEARDOWN_CAS_ATTEMPTS = 5;
|
|
@@ -750,12 +1067,29 @@ async function runAllocatorForIndex(coordinator, indexName, nodes, isActive, att
|
|
|
750
1067
|
{ indexName, attempts: ALLOCATION_CAS_ATTEMPTS }
|
|
751
1068
|
);
|
|
752
1069
|
}
|
|
1070
|
+
async function recoverIndexPartitions(state, coordinator, indexName, nodes, isActive) {
|
|
1071
|
+
const { transport, controllerNodeId } = state;
|
|
1072
|
+
if (transport === null || controllerNodeId === null) {
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const promoted = await recoverUnassignedPartitions(
|
|
1076
|
+
coordinator,
|
|
1077
|
+
transport,
|
|
1078
|
+
indexName,
|
|
1079
|
+
controllerNodeId,
|
|
1080
|
+
nodes,
|
|
1081
|
+
isActive
|
|
1082
|
+
);
|
|
1083
|
+
if (promoted) {
|
|
1084
|
+
await runAllocatorForIndex(coordinator, indexName, nodes, isActive);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
753
1087
|
async function runAllocatorForAllIndexes(state, coordinator, isActive, onError) {
|
|
754
1088
|
if (!isActive()) {
|
|
755
1089
|
return;
|
|
756
1090
|
}
|
|
757
1091
|
const nodes = await coordinator.listNodes();
|
|
758
|
-
if (nodes
|
|
1092
|
+
if (!holdsDataNode(nodes) || !isActive()) {
|
|
759
1093
|
return;
|
|
760
1094
|
}
|
|
761
1095
|
let anyIndexFailed = false;
|
|
@@ -765,6 +1099,7 @@ async function runAllocatorForAllIndexes(state, coordinator, isActive, onError)
|
|
|
765
1099
|
}
|
|
766
1100
|
try {
|
|
767
1101
|
await runAllocatorForIndex(coordinator, indexName, nodes, isActive);
|
|
1102
|
+
await recoverIndexPartitions(state, coordinator, indexName, nodes, isActive);
|
|
768
1103
|
} catch (error) {
|
|
769
1104
|
anyIndexFailed = true;
|
|
770
1105
|
if (onError !== void 0) {
|
|
@@ -800,7 +1135,7 @@ async function handleSchemaCreated(indexName, coordinator, knownIndexes, isActiv
|
|
|
800
1135
|
return;
|
|
801
1136
|
}
|
|
802
1137
|
const nodes = await coordinator.listNodes();
|
|
803
|
-
if (nodes
|
|
1138
|
+
if (!holdsDataNode(nodes) || !isActive()) {
|
|
804
1139
|
return;
|
|
805
1140
|
}
|
|
806
1141
|
const storedTable = await coordinator.getAllocation(indexName);
|
|
@@ -891,14 +1226,6 @@ async function handleSchemaEvent(event, coordinator, state, isActive, onError) {
|
|
|
891
1226
|
await tearDownAllocation(event.indexName, coordinator, state, isActive, 0, onError);
|
|
892
1227
|
}
|
|
893
1228
|
}
|
|
894
|
-
|
|
895
|
-
// src/distribution/payload-guards.ts
|
|
896
|
-
function isRecord(value) {
|
|
897
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
898
|
-
}
|
|
899
|
-
function isValidInteger(value) {
|
|
900
|
-
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
901
|
-
}
|
|
902
1229
|
var INSYNC_CAS_ATTEMPTS = 5;
|
|
903
1230
|
var REFUSE = { outcome: "refuse" };
|
|
904
1231
|
var ACCEPT = { outcome: "accept" };
|
|
@@ -954,6 +1281,9 @@ async function requestInsyncRemoval(indexName, partitionId, replicaNodeId, prima
|
|
|
954
1281
|
}
|
|
955
1282
|
function handleInsyncRemoval(payload, coordinator) {
|
|
956
1283
|
return updateInsyncSet(payload, coordinator, (assignment) => {
|
|
1284
|
+
if (assignment.state === "UNASSIGNED") {
|
|
1285
|
+
return REFUSE;
|
|
1286
|
+
}
|
|
957
1287
|
if (!assignment.inSyncSet.includes(payload.replicaNodeId)) {
|
|
958
1288
|
return ACCEPT;
|
|
959
1289
|
}
|
|
@@ -1106,8 +1436,10 @@ async function processBootstrapComplete(payload, coordinator) {
|
|
|
1106
1436
|
if (assignment.state !== "INITIALISING") {
|
|
1107
1437
|
return false;
|
|
1108
1438
|
}
|
|
1439
|
+
const served = { ...assignment, state: "ACTIVE" };
|
|
1440
|
+
delete served.lastHolders;
|
|
1109
1441
|
const updatedAssignments = new Map(table.assignments);
|
|
1110
|
-
updatedAssignments.set(payload.partitionId,
|
|
1442
|
+
updatedAssignments.set(payload.partitionId, served);
|
|
1111
1443
|
const written = await coordinator.putAllocation(
|
|
1112
1444
|
payload.indexName,
|
|
1113
1445
|
{ ...table, version: table.version + 1, assignments: updatedAssignments },
|
|
@@ -1262,7 +1594,9 @@ function createEventLoopState(initialIndexNames) {
|
|
|
1262
1594
|
knownIndexes: new Set(initialIndexNames),
|
|
1263
1595
|
debounceTimer: null,
|
|
1264
1596
|
teardownTimers: /* @__PURE__ */ new Map(),
|
|
1265
|
-
insyncQueue: Promise.resolve()
|
|
1597
|
+
insyncQueue: Promise.resolve(),
|
|
1598
|
+
transport: null,
|
|
1599
|
+
controllerNodeId: null
|
|
1266
1600
|
};
|
|
1267
1601
|
}
|
|
1268
1602
|
function clearEventLoopWatchers(state) {
|
|
@@ -1291,6 +1625,8 @@ function clearEventLoopWatchers(state) {
|
|
|
1291
1625
|
// src/distribution/cluster/controller/event-loop/index.ts
|
|
1292
1626
|
async function startEventLoop(state, coordinator, transport, nodeId, isActive, onError) {
|
|
1293
1627
|
clearEventLoopWatchers(state);
|
|
1628
|
+
state.transport = transport;
|
|
1629
|
+
state.controllerNodeId = nodeId;
|
|
1294
1630
|
try {
|
|
1295
1631
|
state.unwatchNodes = await coordinator.watchNodes((_event) => {
|
|
1296
1632
|
scheduleDebouncedAllocation(state, coordinator, isActive, onError);
|
|
@@ -1323,13 +1659,21 @@ async function startEventLoop(state, coordinator, transport, nodeId, isActive, o
|
|
|
1323
1659
|
|
|
1324
1660
|
// src/distribution/cluster/controller/index.ts
|
|
1325
1661
|
function createController(config) {
|
|
1326
|
-
const { nodeId, coordinator, transport, leaseTtlMs, standbyRetryMs, knownIndexNames, onError } = config;
|
|
1662
|
+
const { nodeId, coordinator, transport, leaseTtlMs, standbyRetryMs, knownIndexNames, onError, onElectionError } = config;
|
|
1327
1663
|
let electionState = createElectionState();
|
|
1328
1664
|
let eventLoopState = createEventLoopState(knownIndexNames);
|
|
1329
1665
|
let stopped = false;
|
|
1330
1666
|
function isActive() {
|
|
1331
1667
|
return electionState.active;
|
|
1332
1668
|
}
|
|
1669
|
+
function standBy() {
|
|
1670
|
+
if (stopped) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
scheduleStandbyRetry(electionState, standbyRetryMs, () => {
|
|
1674
|
+
void runElection();
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1333
1677
|
function stepDown() {
|
|
1334
1678
|
if (!electionState.active) {
|
|
1335
1679
|
return;
|
|
@@ -1337,12 +1681,7 @@ function createController(config) {
|
|
|
1337
1681
|
electionState.active = false;
|
|
1338
1682
|
clearElectionTimers(electionState);
|
|
1339
1683
|
clearEventLoopWatchers(eventLoopState);
|
|
1340
|
-
|
|
1341
|
-
scheduleStandbyRetry(electionState, standbyRetryMs, () => {
|
|
1342
|
-
tryElection().catch(() => {
|
|
1343
|
-
});
|
|
1344
|
-
});
|
|
1345
|
-
}
|
|
1684
|
+
standBy();
|
|
1346
1685
|
}
|
|
1347
1686
|
async function becomeActive() {
|
|
1348
1687
|
if (electionState.active) {
|
|
@@ -1368,18 +1707,38 @@ function createController(config) {
|
|
|
1368
1707
|
throw error;
|
|
1369
1708
|
}
|
|
1370
1709
|
}
|
|
1371
|
-
async function
|
|
1710
|
+
async function standForElection() {
|
|
1372
1711
|
if (stopped) {
|
|
1373
1712
|
return;
|
|
1374
1713
|
}
|
|
1375
1714
|
const acquired = await tryAcquireLease(coordinator, nodeId, leaseTtlMs);
|
|
1715
|
+
if (stopped) {
|
|
1716
|
+
if (acquired) {
|
|
1717
|
+
await releaseLease(coordinator);
|
|
1718
|
+
}
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1376
1721
|
if (acquired) {
|
|
1377
1722
|
await becomeActive();
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
standBy();
|
|
1726
|
+
}
|
|
1727
|
+
function reportElectionFailure(error) {
|
|
1728
|
+
if (onElectionError === void 0) {
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
try {
|
|
1732
|
+
onElectionError(error);
|
|
1733
|
+
} catch (_) {
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
async function runElection() {
|
|
1737
|
+
try {
|
|
1738
|
+
await standForElection();
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
reportElectionFailure(error);
|
|
1741
|
+
standBy();
|
|
1383
1742
|
}
|
|
1384
1743
|
}
|
|
1385
1744
|
const controller = {
|
|
@@ -1388,7 +1747,13 @@ function createController(config) {
|
|
|
1388
1747
|
},
|
|
1389
1748
|
async start() {
|
|
1390
1749
|
stopped = false;
|
|
1391
|
-
|
|
1750
|
+
clearStandbyTimer(electionState);
|
|
1751
|
+
try {
|
|
1752
|
+
await standForElection();
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
standBy();
|
|
1755
|
+
throw error;
|
|
1756
|
+
}
|
|
1392
1757
|
},
|
|
1393
1758
|
async stop() {
|
|
1394
1759
|
stopped = true;
|
|
@@ -1612,7 +1977,7 @@ function createBootstrapState(indexName, partitionId, primaryNodeId) {
|
|
|
1612
1977
|
};
|
|
1613
1978
|
}
|
|
1614
1979
|
|
|
1615
|
-
// src/distribution/cluster/node-lifecycle/allocation-watcher.ts
|
|
1980
|
+
// src/distribution/cluster/node-lifecycle/allocation-watcher/state.ts
|
|
1616
1981
|
function partitionKey(indexName, partitionId) {
|
|
1617
1982
|
return `${indexName}:${partitionId}`;
|
|
1618
1983
|
}
|
|
@@ -1622,40 +1987,137 @@ function createAllocationWatcherState() {
|
|
|
1622
1987
|
debounceTimer: null,
|
|
1623
1988
|
activeBootstraps: /* @__PURE__ */ new Map(),
|
|
1624
1989
|
trackedPartitions: /* @__PURE__ */ new Map(),
|
|
1990
|
+
pendingPartitions: /* @__PURE__ */ new Set(),
|
|
1625
1991
|
pendingTables: /* @__PURE__ */ new Map(),
|
|
1626
1992
|
restartWaiters: /* @__PURE__ */ new Map(),
|
|
1993
|
+
retainedPartitions: /* @__PURE__ */ new Set(),
|
|
1994
|
+
indexesSeededFromRecord: /* @__PURE__ */ new Set(),
|
|
1627
1995
|
stopped: false
|
|
1628
1996
|
};
|
|
1629
1997
|
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1998
|
+
function stopAllocationWatcher(state) {
|
|
1999
|
+
state.stopped = true;
|
|
2000
|
+
if (state.unwatchAllocation !== null) {
|
|
2001
|
+
state.unwatchAllocation();
|
|
2002
|
+
state.unwatchAllocation = null;
|
|
2003
|
+
}
|
|
2004
|
+
if (state.debounceTimer !== null) {
|
|
2005
|
+
clearTimeout(state.debounceTimer);
|
|
2006
|
+
state.debounceTimer = null;
|
|
2007
|
+
}
|
|
2008
|
+
for (const [timer, resolve] of state.restartWaiters) {
|
|
2009
|
+
clearTimeout(timer);
|
|
2010
|
+
resolve();
|
|
2011
|
+
}
|
|
2012
|
+
state.restartWaiters.clear();
|
|
2013
|
+
for (const bootstrapState of state.activeBootstraps.values()) {
|
|
2014
|
+
abortBootstrapState(bootstrapState);
|
|
2015
|
+
}
|
|
2016
|
+
state.activeBootstraps.clear();
|
|
2017
|
+
state.trackedPartitions.clear();
|
|
2018
|
+
state.pendingPartitions.clear();
|
|
2019
|
+
state.retainedPartitions.clear();
|
|
2020
|
+
state.indexesSeededFromRecord.clear();
|
|
2021
|
+
state.pendingTables.clear();
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/distribution/cluster/node-lifecycle/allocation-watcher/bootstrap-restart.ts
|
|
2025
|
+
function startBootstrap(state, config, indexName, partitionId, primaryNodeId) {
|
|
2026
|
+
const key = partitionKey(indexName, partitionId);
|
|
2027
|
+
const existingBootstrap = state.activeBootstraps.get(key);
|
|
2028
|
+
if (existingBootstrap !== void 0) {
|
|
2029
|
+
abortBootstrapState(existingBootstrap);
|
|
2030
|
+
}
|
|
2031
|
+
const bootstrapState = createBootstrapState(indexName, partitionId, primaryNodeId);
|
|
2032
|
+
state.activeBootstraps.set(key, bootstrapState);
|
|
2033
|
+
bootstrapPartition(
|
|
2034
|
+
bootstrapState,
|
|
2035
|
+
config.coordinator,
|
|
2036
|
+
config.transport,
|
|
2037
|
+
config.registration.nodeId,
|
|
2038
|
+
config.bootstrapRetryBaseMs,
|
|
2039
|
+
config.bootstrapRetryMaxMs,
|
|
2040
|
+
config.bootstrapMaxRetries,
|
|
2041
|
+
config.onBootstrapPartition,
|
|
2042
|
+
config.onError
|
|
2043
|
+
).then((succeeded) => {
|
|
2044
|
+
if (state.activeBootstraps.get(key) === bootstrapState) {
|
|
2045
|
+
state.activeBootstraps.delete(key);
|
|
1634
2046
|
}
|
|
1635
|
-
|
|
2047
|
+
if (!succeeded) {
|
|
2048
|
+
state.trackedPartitions.delete(key);
|
|
2049
|
+
void restartBootstrapWhileOutOfSync(state, config, indexName, partitionId);
|
|
2050
|
+
}
|
|
2051
|
+
}).catch(() => {
|
|
2052
|
+
if (state.activeBootstraps.get(key) === bootstrapState) {
|
|
2053
|
+
state.activeBootstraps.delete(key);
|
|
2054
|
+
}
|
|
2055
|
+
state.trackedPartitions.delete(key);
|
|
2056
|
+
void restartBootstrapWhileOutOfSync(state, config, indexName, partitionId);
|
|
1636
2057
|
});
|
|
1637
|
-
state.unwatchAllocation = unwatchAllocation;
|
|
1638
2058
|
}
|
|
1639
|
-
function
|
|
1640
|
-
|
|
1641
|
-
if (
|
|
1642
|
-
|
|
2059
|
+
function waitBeforeBootstrapRestart(state, config) {
|
|
2060
|
+
const delayMs = Math.min(config.bootstrapRetryMaxMs, config.bootstrapRetryBaseMs);
|
|
2061
|
+
if (delayMs <= 0) {
|
|
2062
|
+
return Promise.resolve();
|
|
1643
2063
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
2064
|
+
return new Promise((resolve) => {
|
|
2065
|
+
const timer = setTimeout(() => {
|
|
2066
|
+
state.restartWaiters.delete(timer);
|
|
2067
|
+
resolve();
|
|
2068
|
+
}, delayMs);
|
|
2069
|
+
timer.unref?.();
|
|
2070
|
+
state.restartWaiters.set(timer, resolve);
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
async function restartBootstrapWhileOutOfSync(state, config, indexName, partitionId) {
|
|
2074
|
+
if (state.stopped) {
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
await waitBeforeBootstrapRestart(state, config);
|
|
2078
|
+
if (state.stopped) {
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
const nodeId = config.registration.nodeId;
|
|
2082
|
+
let table;
|
|
2083
|
+
try {
|
|
2084
|
+
table = await config.coordinator.getAllocation(indexName);
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
if (config.onError !== void 0) {
|
|
2087
|
+
config.onError(error);
|
|
1650
2088
|
}
|
|
1651
|
-
|
|
2089
|
+
if (!state.stopped) {
|
|
2090
|
+
void restartBootstrapWhileOutOfSync(state, config, indexName, partitionId);
|
|
2091
|
+
}
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
if (state.stopped || table === null) {
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
const assignment = table.assignments.get(partitionId);
|
|
2098
|
+
if (assignment === void 0 || assignment.primary === null || assignment.primary === nodeId) {
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
if (!assignment.replicas.includes(nodeId) || assignment.inSyncSet.includes(nodeId)) {
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (assignment.state !== "ACTIVE" && assignment.state !== "INITIALISING" && assignment.state !== "MIGRATING") {
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
if (state.activeBootstraps.has(partitionKey(indexName, partitionId))) {
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
startBootstrap(state, config, indexName, partitionId, assignment.primary);
|
|
1652
2111
|
}
|
|
2112
|
+
|
|
2113
|
+
// src/distribution/cluster/node-lifecycle/allocation-watcher/table.ts
|
|
1653
2114
|
function processAllocationChange(state, config, table) {
|
|
1654
2115
|
if (state.stopped) {
|
|
1655
2116
|
return;
|
|
1656
2117
|
}
|
|
1657
2118
|
const nodeId = config.registration.nodeId;
|
|
1658
2119
|
const currentKeys = /* @__PURE__ */ new Set();
|
|
2120
|
+
seedRetainedFromRecord(state, config, table.indexName);
|
|
1659
2121
|
for (const [partitionId, assignment] of table.assignments) {
|
|
1660
2122
|
if (!isNodeAssigned(assignment, nodeId)) {
|
|
1661
2123
|
continue;
|
|
@@ -1676,8 +2138,34 @@ function processAllocationChange(state, config, table) {
|
|
|
1676
2138
|
primaryNodeId: assignment.primary,
|
|
1677
2139
|
primaryTerm: assignment.primaryTerm
|
|
1678
2140
|
});
|
|
2141
|
+
if (servesPartition(assignment, nodeId)) {
|
|
2142
|
+
state.pendingPartitions.delete(key);
|
|
2143
|
+
} else {
|
|
2144
|
+
state.pendingPartitions.add(key);
|
|
2145
|
+
}
|
|
2146
|
+
if (config.onHoldPartition !== void 0 && alreadyHoldsData(assignment, nodeId)) {
|
|
2147
|
+
config.onHoldPartition(table.indexName, partitionId);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
removeDroppedPartitions(state, config, table, currentKeys);
|
|
2151
|
+
releaseSupersededCopies(state, config, table, currentKeys);
|
|
2152
|
+
}
|
|
2153
|
+
function processInitialAllocations(state, config, tables) {
|
|
2154
|
+
for (const table of tables) {
|
|
2155
|
+
processAllocationChange(state, config, table);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
function servesPartition(assignment, nodeId) {
|
|
2159
|
+
if (assignment.state === "INITIALISING" || assignment.state === "UNASSIGNED") {
|
|
2160
|
+
return false;
|
|
2161
|
+
}
|
|
2162
|
+
return assignment.primary === nodeId || assignment.inSyncSet.includes(nodeId);
|
|
2163
|
+
}
|
|
2164
|
+
function alreadyHoldsData(assignment, nodeId) {
|
|
2165
|
+
if (assignment.state !== "ACTIVE") {
|
|
2166
|
+
return false;
|
|
1679
2167
|
}
|
|
1680
|
-
|
|
2168
|
+
return assignment.primary === nodeId || assignment.inSyncSet.includes(nodeId);
|
|
1681
2169
|
}
|
|
1682
2170
|
function isNodeAssigned(assignment, nodeId) {
|
|
1683
2171
|
if (assignment.primary === nodeId) {
|
|
@@ -1710,6 +2198,10 @@ function handleExistingAssignment(state, config, existing, assignment, nodeId) {
|
|
|
1710
2198
|
}
|
|
1711
2199
|
return;
|
|
1712
2200
|
}
|
|
2201
|
+
if (promotedBackAtHigherTerm(existing, assignment, nodeId)) {
|
|
2202
|
+
startBootstrap(state, config, existing.indexName, existing.partitionId, nodeId);
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
1713
2205
|
if (assignment.primary !== null && replicaNeedsResync(state, existing, assignment, nodeId)) {
|
|
1714
2206
|
startBootstrap(state, config, existing.indexName, existing.partitionId, assignment.primary);
|
|
1715
2207
|
}
|
|
@@ -1729,138 +2221,111 @@ function replicaNeedsResync(state, existing, assignment, nodeId) {
|
|
|
1729
2221
|
}
|
|
1730
2222
|
return !state.activeBootstraps.has(partitionKey(existing.indexName, existing.partitionId));
|
|
1731
2223
|
}
|
|
1732
|
-
function
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2224
|
+
function promotedBackAtHigherTerm(existing, assignment, nodeId) {
|
|
2225
|
+
return assignment.primary === nodeId && assignment.state === "INITIALISING" && assignment.primaryTerm > existing.primaryTerm;
|
|
2226
|
+
}
|
|
2227
|
+
function keepsCopyForRecovery(table, partitionId, nodeId) {
|
|
2228
|
+
const assignment = table.assignments.get(partitionId);
|
|
2229
|
+
if (assignment === void 0) {
|
|
2230
|
+
return false;
|
|
1737
2231
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
2232
|
+
return holdsLastCopy(assignment, nodeId);
|
|
2233
|
+
}
|
|
2234
|
+
function seedRetainedFromRecord(state, config, indexName) {
|
|
2235
|
+
if (config.retainedPartitionIds === void 0 || state.indexesSeededFromRecord.has(indexName)) {
|
|
2236
|
+
return;
|
|
2237
|
+
}
|
|
2238
|
+
state.indexesSeededFromRecord.add(indexName);
|
|
2239
|
+
for (const partitionId of config.retainedPartitionIds(indexName)) {
|
|
2240
|
+
state.retainedPartitions.add(partitionKey(indexName, partitionId));
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
function retainedPartitionIdOf(key, indexName) {
|
|
2244
|
+
if (!key.startsWith(`${indexName}:`)) {
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
const partitionId = Number(key.slice(indexName.length + 1));
|
|
2248
|
+
return Number.isInteger(partitionId) ? partitionId : null;
|
|
2249
|
+
}
|
|
2250
|
+
function copyHasNoFurtherUse(table, partitionId, nodeId) {
|
|
2251
|
+
if (table.assignments.size === 0) {
|
|
2252
|
+
return true;
|
|
2253
|
+
}
|
|
2254
|
+
const assignment = table.assignments.get(partitionId);
|
|
2255
|
+
if (assignment === void 0) {
|
|
2256
|
+
return false;
|
|
2257
|
+
}
|
|
2258
|
+
return assignment.state === "ACTIVE" && assignment.primary !== null && assignment.primary !== nodeId;
|
|
2259
|
+
}
|
|
2260
|
+
function releaseSupersededCopies(state, config, table, currentKeys) {
|
|
2261
|
+
const nodeId = config.registration.nodeId;
|
|
2262
|
+
for (const key of [...state.retainedPartitions]) {
|
|
2263
|
+
const partitionId = retainedPartitionIdOf(key, table.indexName);
|
|
2264
|
+
if (partitionId === null) {
|
|
2265
|
+
continue;
|
|
1753
2266
|
}
|
|
1754
|
-
if (
|
|
1755
|
-
state.
|
|
1756
|
-
|
|
2267
|
+
if (currentKeys.has(key)) {
|
|
2268
|
+
state.retainedPartitions.delete(key);
|
|
2269
|
+
continue;
|
|
1757
2270
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
state.activeBootstraps.delete(key);
|
|
2271
|
+
if (!copyHasNoFurtherUse(table, partitionId, nodeId)) {
|
|
2272
|
+
continue;
|
|
1761
2273
|
}
|
|
1762
|
-
state.
|
|
1763
|
-
|
|
1764
|
-
|
|
2274
|
+
state.retainedPartitions.delete(key);
|
|
2275
|
+
if (config.onRemovePartition !== void 0) {
|
|
2276
|
+
config.onRemovePartition(table.indexName, partitionId);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
1765
2279
|
}
|
|
1766
|
-
function
|
|
2280
|
+
function removeDroppedPartitions(state, config, table, currentKeys) {
|
|
1767
2281
|
for (const [key, tracked] of state.trackedPartitions) {
|
|
1768
|
-
if (tracked.indexName !== indexName) {
|
|
2282
|
+
if (tracked.indexName !== table.indexName) {
|
|
1769
2283
|
continue;
|
|
1770
2284
|
}
|
|
1771
2285
|
if (currentKeys.has(key)) {
|
|
1772
2286
|
continue;
|
|
1773
2287
|
}
|
|
1774
2288
|
state.trackedPartitions.delete(key);
|
|
2289
|
+
state.pendingPartitions.delete(key);
|
|
1775
2290
|
const activeBootstrap = state.activeBootstraps.get(key);
|
|
1776
2291
|
if (activeBootstrap !== void 0) {
|
|
1777
2292
|
abortBootstrapState(activeBootstrap);
|
|
1778
2293
|
state.activeBootstraps.delete(key);
|
|
1779
2294
|
}
|
|
2295
|
+
if (keepsCopyForRecovery(table, tracked.partitionId, config.registration.nodeId)) {
|
|
2296
|
+
state.retainedPartitions.add(key);
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
state.retainedPartitions.delete(key);
|
|
1780
2300
|
if (config.onRemovePartition !== void 0) {
|
|
1781
2301
|
config.onRemovePartition(tracked.indexName, tracked.partitionId);
|
|
1782
2302
|
}
|
|
1783
2303
|
}
|
|
1784
2304
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
state.
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
state.debounceTimer = null;
|
|
1794
|
-
}
|
|
1795
|
-
for (const [timer, resolve] of state.restartWaiters) {
|
|
1796
|
-
clearTimeout(timer);
|
|
1797
|
-
resolve();
|
|
1798
|
-
}
|
|
1799
|
-
state.restartWaiters.clear();
|
|
1800
|
-
for (const bootstrapState of state.activeBootstraps.values()) {
|
|
1801
|
-
abortBootstrapState(bootstrapState);
|
|
1802
|
-
}
|
|
1803
|
-
state.activeBootstraps.clear();
|
|
1804
|
-
state.trackedPartitions.clear();
|
|
1805
|
-
state.pendingTables.clear();
|
|
1806
|
-
}
|
|
1807
|
-
function processInitialAllocations(state, config, tables) {
|
|
1808
|
-
for (const table of tables) {
|
|
1809
|
-
processAllocationChange(state, config, table);
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
function waitBeforeBootstrapRestart(state, config) {
|
|
1813
|
-
const delayMs = Math.min(config.bootstrapRetryMaxMs, config.bootstrapRetryBaseMs);
|
|
1814
|
-
if (delayMs <= 0) {
|
|
1815
|
-
return Promise.resolve();
|
|
1816
|
-
}
|
|
1817
|
-
return new Promise((resolve) => {
|
|
1818
|
-
const timer = setTimeout(() => {
|
|
1819
|
-
state.restartWaiters.delete(timer);
|
|
1820
|
-
resolve();
|
|
1821
|
-
}, delayMs);
|
|
1822
|
-
timer.unref?.();
|
|
1823
|
-
state.restartWaiters.set(timer, resolve);
|
|
2305
|
+
|
|
2306
|
+
// src/distribution/cluster/node-lifecycle/allocation-watcher/index.ts
|
|
2307
|
+
async function startAllocationWatcher(state, config) {
|
|
2308
|
+
const unwatchAllocation = await config.coordinator.watchAllocation((event) => {
|
|
2309
|
+
if (state.stopped) {
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
scheduleProcessAllocation(state, config, event.table);
|
|
1824
2313
|
});
|
|
2314
|
+
state.unwatchAllocation = unwatchAllocation;
|
|
1825
2315
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
await waitBeforeBootstrapRestart(state, config);
|
|
1831
|
-
if (state.stopped) {
|
|
1832
|
-
return;
|
|
1833
|
-
}
|
|
1834
|
-
const nodeId = config.registration.nodeId;
|
|
1835
|
-
let table;
|
|
1836
|
-
try {
|
|
1837
|
-
table = await config.coordinator.getAllocation(indexName);
|
|
1838
|
-
} catch (error) {
|
|
1839
|
-
if (config.onError !== void 0) {
|
|
1840
|
-
config.onError(error);
|
|
1841
|
-
}
|
|
1842
|
-
if (!state.stopped) {
|
|
1843
|
-
void restartBootstrapWhileOutOfSync(state, config, indexName, partitionId);
|
|
1844
|
-
}
|
|
1845
|
-
return;
|
|
1846
|
-
}
|
|
1847
|
-
if (state.stopped || table === null) {
|
|
1848
|
-
return;
|
|
1849
|
-
}
|
|
1850
|
-
const assignment = table.assignments.get(partitionId);
|
|
1851
|
-
if (assignment === void 0 || assignment.primary === null || assignment.primary === nodeId) {
|
|
1852
|
-
return;
|
|
1853
|
-
}
|
|
1854
|
-
if (!assignment.replicas.includes(nodeId) || assignment.inSyncSet.includes(nodeId)) {
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
if (assignment.state !== "ACTIVE" && assignment.state !== "INITIALISING" && assignment.state !== "MIGRATING") {
|
|
1858
|
-
return;
|
|
1859
|
-
}
|
|
1860
|
-
if (state.activeBootstraps.has(partitionKey(indexName, partitionId))) {
|
|
1861
|
-
return;
|
|
2316
|
+
function scheduleProcessAllocation(state, config, table) {
|
|
2317
|
+
state.pendingTables.set(table.indexName, table);
|
|
2318
|
+
if (state.debounceTimer !== null) {
|
|
2319
|
+
clearTimeout(state.debounceTimer);
|
|
1862
2320
|
}
|
|
1863
|
-
|
|
2321
|
+
state.debounceTimer = setTimeout(() => {
|
|
2322
|
+
state.debounceTimer = null;
|
|
2323
|
+
const tablesToProcess = Array.from(state.pendingTables.values());
|
|
2324
|
+
state.pendingTables.clear();
|
|
2325
|
+
for (const pending of tablesToProcess) {
|
|
2326
|
+
processAllocationChange(state, config, pending);
|
|
2327
|
+
}
|
|
2328
|
+
}, config.allocationDebounceMs);
|
|
1864
2329
|
}
|
|
1865
2330
|
|
|
1866
2331
|
// src/distribution/cluster/node-lifecycle/heartbeat.ts
|
|
@@ -1892,6 +2357,7 @@ async function stopRegistrationHeartbeat(state) {
|
|
|
1892
2357
|
// src/distribution/cluster/node-lifecycle/join.ts
|
|
1893
2358
|
async function joinCluster(config, watcherState, heartbeatState) {
|
|
1894
2359
|
await config.coordinator.registerNode(config.registration);
|
|
2360
|
+
config.onRegistered?.();
|
|
1895
2361
|
startRegistrationHeartbeat(heartbeatState, config);
|
|
1896
2362
|
const initialTables = await loadInitialAllocations(config);
|
|
1897
2363
|
processInitialAllocations(watcherState, config, initialTables);
|
|
@@ -1899,7 +2365,8 @@ async function joinCluster(config, watcherState, heartbeatState) {
|
|
|
1899
2365
|
}
|
|
1900
2366
|
async function loadInitialAllocations(config) {
|
|
1901
2367
|
const tables = [];
|
|
1902
|
-
|
|
2368
|
+
const indexNames = /* @__PURE__ */ new Set([...config.knownIndexNames, ...await config.coordinator.listSchemas()]);
|
|
2369
|
+
for (const indexName of indexNames) {
|
|
1903
2370
|
const table = await config.coordinator.getAllocation(indexName);
|
|
1904
2371
|
if (table !== null) {
|
|
1905
2372
|
tables.push(table);
|
|
@@ -1923,6 +2390,7 @@ var DEFAULT_NODE_LIFECYCLE_CONFIG = {
|
|
|
1923
2390
|
// src/distribution/cluster/node-lifecycle/index.ts
|
|
1924
2391
|
function createDataNodeLifecycle(config) {
|
|
1925
2392
|
let status = "stopped";
|
|
2393
|
+
let registered = false;
|
|
1926
2394
|
let watcherState = createAllocationWatcherState();
|
|
1927
2395
|
const heartbeatState = createRegistrationHeartbeatState();
|
|
1928
2396
|
let operationLock = Promise.resolve();
|
|
@@ -1949,6 +2417,12 @@ function createDataNodeLifecycle(config) {
|
|
|
1949
2417
|
get nodeId() {
|
|
1950
2418
|
return config.registration.nodeId;
|
|
1951
2419
|
},
|
|
2420
|
+
get registered() {
|
|
2421
|
+
return registered;
|
|
2422
|
+
},
|
|
2423
|
+
get pendingPartitionCount() {
|
|
2424
|
+
return watcherState.pendingPartitions.size;
|
|
2425
|
+
},
|
|
1952
2426
|
join() {
|
|
1953
2427
|
return withLock(async () => {
|
|
1954
2428
|
if (status === "active" || status === "joining") {
|
|
@@ -1967,10 +2441,21 @@ function createDataNodeLifecycle(config) {
|
|
|
1967
2441
|
}
|
|
1968
2442
|
status = "joining";
|
|
1969
2443
|
try {
|
|
1970
|
-
await joinCluster(
|
|
2444
|
+
await joinCluster(
|
|
2445
|
+
{
|
|
2446
|
+
...config,
|
|
2447
|
+
onRegistered: () => {
|
|
2448
|
+
registered = true;
|
|
2449
|
+
config.onRegistered?.();
|
|
2450
|
+
}
|
|
2451
|
+
},
|
|
2452
|
+
watcherState,
|
|
2453
|
+
heartbeatState
|
|
2454
|
+
);
|
|
1971
2455
|
status = "active";
|
|
1972
2456
|
} catch (error) {
|
|
1973
2457
|
await stopRegistrationHeartbeat(heartbeatState);
|
|
2458
|
+
registered = false;
|
|
1974
2459
|
status = "stopped";
|
|
1975
2460
|
throw error;
|
|
1976
2461
|
}
|
|
@@ -1987,6 +2472,7 @@ function createDataNodeLifecycle(config) {
|
|
|
1987
2472
|
try {
|
|
1988
2473
|
await leaveCluster(config);
|
|
1989
2474
|
} finally {
|
|
2475
|
+
registered = false;
|
|
1990
2476
|
status = "stopped";
|
|
1991
2477
|
watcherState = createAllocationWatcherState();
|
|
1992
2478
|
}
|
|
@@ -2005,6 +2491,7 @@ function createDataNodeLifecycle(config) {
|
|
|
2005
2491
|
} catch (_) {
|
|
2006
2492
|
}
|
|
2007
2493
|
}
|
|
2494
|
+
registered = false;
|
|
2008
2495
|
status = "shutdown";
|
|
2009
2496
|
watcherState = createAllocationWatcherState();
|
|
2010
2497
|
});
|
|
@@ -2061,6 +2548,9 @@ async function nodeHasOtherPartitions(indexName, removedPartitionId, deps) {
|
|
|
2061
2548
|
if (assignment.replicas.includes(deps.nodeId)) {
|
|
2062
2549
|
return true;
|
|
2063
2550
|
}
|
|
2551
|
+
if (holdsLastCopy(assignment, deps.nodeId)) {
|
|
2552
|
+
return true;
|
|
2553
|
+
}
|
|
2064
2554
|
}
|
|
2065
2555
|
return false;
|
|
2066
2556
|
}
|
|
@@ -3815,6 +4305,22 @@ async function executeBootstrapSync(state, entry, indexName, partitionId, primar
|
|
|
3815
4305
|
return true;
|
|
3816
4306
|
}
|
|
3817
4307
|
|
|
4308
|
+
// src/distribution/cluster-node/routable-allocation.ts
|
|
4309
|
+
async function routableAllocation(coordinator, indexName) {
|
|
4310
|
+
const table = await coordinator.getAllocation(indexName);
|
|
4311
|
+
if (table !== null && table.assignments.size > 0) {
|
|
4312
|
+
return table;
|
|
4313
|
+
}
|
|
4314
|
+
if (await getIndexMetadata(coordinator, indexName) === null) {
|
|
4315
|
+
return null;
|
|
4316
|
+
}
|
|
4317
|
+
throw new NarsilError(
|
|
4318
|
+
ErrorCodes.QUERY_ROUTING_FAILED,
|
|
4319
|
+
`The cluster owns index '${indexName}' and holds no allocation table for it yet, so this node cannot place a read or a write`,
|
|
4320
|
+
{ indexName }
|
|
4321
|
+
);
|
|
4322
|
+
}
|
|
4323
|
+
|
|
3818
4324
|
// src/distribution/cluster-node/write-routing/assignment.ts
|
|
3819
4325
|
function resolvePartitionId(docId, partitionCount) {
|
|
3820
4326
|
return fnv1a(docId) % partitionCount;
|
|
@@ -3837,8 +4343,8 @@ function requireAssignedPrimary(assignment, indexName, partitionId) {
|
|
|
3837
4343
|
return assignment;
|
|
3838
4344
|
}
|
|
3839
4345
|
async function resolvePrimaryAssignment(indexName, docId, deps, requireLocalPrimary) {
|
|
3840
|
-
const table = await deps.coordinator
|
|
3841
|
-
if (table === null
|
|
4346
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
4347
|
+
if (table === null) {
|
|
3842
4348
|
if (requireLocalPrimary) {
|
|
3843
4349
|
throw new NarsilError(
|
|
3844
4350
|
ErrorCodes.QUERY_ROUTING_FAILED,
|
|
@@ -3965,7 +4471,7 @@ async function sendToController(payload, controllerNodeId, deps) {
|
|
|
3965
4471
|
}
|
|
3966
4472
|
return false;
|
|
3967
4473
|
}
|
|
3968
|
-
function
|
|
4474
|
+
function withTimeout2(work, timeoutMs, fallback) {
|
|
3969
4475
|
return new Promise((resolve) => {
|
|
3970
4476
|
const timer = setTimeout(() => resolve(fallback), timeoutMs);
|
|
3971
4477
|
timer.unref?.();
|
|
@@ -4020,9 +4526,9 @@ async function proposeAdmission(state, deps, indexName, partitionId, assignment,
|
|
|
4020
4526
|
appliedSeqNo: cursor.appliedSeqNo,
|
|
4021
4527
|
commitPoint: log.commitPoint
|
|
4022
4528
|
};
|
|
4023
|
-
const accepted = await
|
|
4529
|
+
const accepted = await withTimeout2(sendToController(payload, controllerNodeId, deps), ADMISSION_TIMEOUT_MS, false);
|
|
4024
4530
|
if (accepted && cursor.syncEpoch !== proposedEpoch) {
|
|
4025
|
-
await
|
|
4531
|
+
await withTimeout2(withdrawAdmission(payload, controllerNodeId, deps), ADMISSION_TIMEOUT_MS, void 0);
|
|
4026
4532
|
}
|
|
4027
4533
|
} finally {
|
|
4028
4534
|
clearPendingAdmission(state, indexName, partitionId, replicaNodeId);
|
|
@@ -4230,6 +4736,114 @@ async function stopCatchUpPump(state) {
|
|
|
4230
4736
|
}
|
|
4231
4737
|
}
|
|
4232
4738
|
|
|
4739
|
+
// src/distribution/cluster-node/cluster-namespace.ts
|
|
4740
|
+
function readinessOf(deps) {
|
|
4741
|
+
if (deps.isShutdown()) {
|
|
4742
|
+
return "LEAVING";
|
|
4743
|
+
}
|
|
4744
|
+
const lifecycle = deps.lifecycle();
|
|
4745
|
+
if (lifecycle.status === "leaving" || lifecycle.status === "shutdown") {
|
|
4746
|
+
return "LEAVING";
|
|
4747
|
+
}
|
|
4748
|
+
if (!lifecycle.registered) {
|
|
4749
|
+
return "STARTING";
|
|
4750
|
+
}
|
|
4751
|
+
if (lifecycle.status !== "active" || !deps.isServing() || lifecycle.pendingPartitionCount > 0) {
|
|
4752
|
+
return "JOINING";
|
|
4753
|
+
}
|
|
4754
|
+
return "SERVING";
|
|
4755
|
+
}
|
|
4756
|
+
function createClusterNamespace(deps) {
|
|
4757
|
+
return {
|
|
4758
|
+
async getAllocation(indexName) {
|
|
4759
|
+
return deps.trackOp(null, () => deps.coordinator.getAllocation(indexName));
|
|
4760
|
+
},
|
|
4761
|
+
getNodeInfo() {
|
|
4762
|
+
return { nodeId: deps.nodeId, roles: [...deps.roles], status: deps.lifecycle().status };
|
|
4763
|
+
},
|
|
4764
|
+
isControllerActive() {
|
|
4765
|
+
return deps.controller()?.isActive ?? false;
|
|
4766
|
+
},
|
|
4767
|
+
getReadiness() {
|
|
4768
|
+
return readinessOf(deps);
|
|
4769
|
+
},
|
|
4770
|
+
async listNodes() {
|
|
4771
|
+
return deps.trackOp(null, () => deps.coordinator.listNodes());
|
|
4772
|
+
},
|
|
4773
|
+
async getControllerNodeId() {
|
|
4774
|
+
return deps.trackOp(null, () => deps.coordinator.getLeaseHolder(CONTROLLER_LEASE_KEY));
|
|
4775
|
+
}
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
// src/distribution/cluster-node/error-forwarding.ts
|
|
4780
|
+
function createErrorForwarder(onError) {
|
|
4781
|
+
return (error) => {
|
|
4782
|
+
if (onError === void 0) {
|
|
4783
|
+
return;
|
|
4784
|
+
}
|
|
4785
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
4786
|
+
};
|
|
4787
|
+
}
|
|
4788
|
+
function createAllocationErrorForwarder(forwardOnError) {
|
|
4789
|
+
return (indexName, error) => {
|
|
4790
|
+
const code = error instanceof NarsilError ? error.code : ErrorCodes.ALLOCATION_FAILED;
|
|
4791
|
+
const details = error instanceof NarsilError ? error.details : {};
|
|
4792
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4793
|
+
forwardOnError(
|
|
4794
|
+
new NarsilError(code, `The controller could not allocate index '${indexName}': ${message}`, {
|
|
4795
|
+
...details,
|
|
4796
|
+
indexName
|
|
4797
|
+
})
|
|
4798
|
+
);
|
|
4799
|
+
};
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// src/distribution/cluster-node/held-partitions.ts
|
|
4803
|
+
function createHeldPartitionRecord(core) {
|
|
4804
|
+
const writesByIndex = /* @__PURE__ */ new Map();
|
|
4805
|
+
const indexesBehindOnDisk = /* @__PURE__ */ new Set();
|
|
4806
|
+
function queueWrite(indexName, next) {
|
|
4807
|
+
const previous = writesByIndex.get(indexName) ?? Promise.resolve();
|
|
4808
|
+
const write = previous.then(() => writeHeldPartitions(core, indexName, next, indexesBehindOnDisk));
|
|
4809
|
+
writesByIndex.set(
|
|
4810
|
+
indexName,
|
|
4811
|
+
write.catch(() => void 0)
|
|
4812
|
+
);
|
|
4813
|
+
return write;
|
|
4814
|
+
}
|
|
4815
|
+
return {
|
|
4816
|
+
record(indexName, partitionId) {
|
|
4817
|
+
return queueWrite(indexName, (held) => held.includes(partitionId) ? null : [...held, partitionId]);
|
|
4818
|
+
},
|
|
4819
|
+
forget(indexName, partitionId) {
|
|
4820
|
+
return queueWrite(indexName, (held) => held.includes(partitionId) ? held.filter((id) => id !== partitionId) : null);
|
|
4821
|
+
},
|
|
4822
|
+
held(indexName) {
|
|
4823
|
+
return core.indexRegistry.get(indexName)?.heldPartitions ?? void 0;
|
|
4824
|
+
}
|
|
4825
|
+
};
|
|
4826
|
+
}
|
|
4827
|
+
async function writeHeldPartitions(core, indexName, next, indexesBehindOnDisk) {
|
|
4828
|
+
const entry = core.indexRegistry.get(indexName);
|
|
4829
|
+
if (entry === void 0) {
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4832
|
+
const updated = next(entry.heldPartitions ?? []);
|
|
4833
|
+
if (updated === null && !indexesBehindOnDisk.has(indexName)) {
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
if (updated !== null) {
|
|
4837
|
+
core.indexRegistry.set(indexName, { ...entry, heldPartitions: [...updated].sort((a, b) => a - b) });
|
|
4838
|
+
}
|
|
4839
|
+
if (core.durability === null) {
|
|
4840
|
+
return;
|
|
4841
|
+
}
|
|
4842
|
+
indexesBehindOnDisk.add(indexName);
|
|
4843
|
+
await core.durability.manager.persistMetadata(indexName);
|
|
4844
|
+
indexesBehindOnDisk.delete(indexName);
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4233
4847
|
// src/distribution/cluster-node/local-engine.ts
|
|
4234
4848
|
async function createClusterLocalEngine(config) {
|
|
4235
4849
|
const core = createEngineCore(config);
|
|
@@ -4241,10 +4855,15 @@ async function createClusterLocalEngine(config) {
|
|
|
4241
4855
|
}
|
|
4242
4856
|
await core.analysisRebuild.reviewStaleIndexes();
|
|
4243
4857
|
const engine = createNarsilFromCore(core, config);
|
|
4858
|
+
const heldPartitions = createHeldPartitionRecord(core);
|
|
4244
4859
|
return Object.assign(engine, {
|
|
4245
4860
|
createIndexWithUuid: (name, indexConfig, indexUuid) => createEngineIndex(core, config, name, indexConfig, indexUuid),
|
|
4246
4861
|
indexUuidOf: (indexName) => core.indexRegistry.get(indexName)?.indexUuid,
|
|
4247
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),
|
|
4248
4867
|
applyReplicationEntry: (entry) => applyReplicationEntry(core, entry),
|
|
4249
4868
|
serializeReplicationPartition: (indexName, partitionId) => serializeReplicationPartition(core, indexName, partitionId),
|
|
4250
4869
|
restoreReplicationPartition: (indexName, partitionId, bytes, schema, partitionCount) => restoreReplicationPartition(core, engine, indexName, partitionId, bytes, schema, partitionCount),
|
|
@@ -5086,6 +5705,28 @@ async function streamValidatedSnapshotRequest(message, sink, request, deps, opti
|
|
|
5086
5705
|
}
|
|
5087
5706
|
await acquireAndStream(message, sink, request, deps, options);
|
|
5088
5707
|
}
|
|
5708
|
+
function describeLocalCopy(deps, indexName) {
|
|
5709
|
+
const absent = { indexName, indexUuid: null, partitionIds: [] };
|
|
5710
|
+
if (!deps.engine.listIndexes().some((index) => index.name === indexName)) {
|
|
5711
|
+
return absent;
|
|
5712
|
+
}
|
|
5713
|
+
const indexUuid = deps.engine.indexUuidOf(indexName);
|
|
5714
|
+
if (indexUuid === null || indexUuid === void 0) {
|
|
5715
|
+
return absent;
|
|
5716
|
+
}
|
|
5717
|
+
const held = new Set(deps.engine.heldPartitionsOf(indexName) ?? []);
|
|
5718
|
+
for (const partition of deps.engine.getPartitionStats(indexName)) {
|
|
5719
|
+
if (partition.documentCount > 0) {
|
|
5720
|
+
held.add(partition.partitionId);
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
return { indexName, indexUuid, partitionIds: [...held].sort((left, right) => left - right) };
|
|
5724
|
+
}
|
|
5725
|
+
async function handlePartitionStores(message, respond, deps) {
|
|
5726
|
+
const payload = validatePartitionStoresPayload(decode(message.payload));
|
|
5727
|
+
const result = payload === null ? { indexName: "", indexUuid: null, partitionIds: [] } : describeLocalCopy(deps, payload.indexName);
|
|
5728
|
+
await respond(createPartitionStoresResultMessage(result, deps.nodeId, message.requestId));
|
|
5729
|
+
}
|
|
5089
5730
|
|
|
5090
5731
|
// src/distribution/query/validators/common.ts
|
|
5091
5732
|
var MAX_PARTITION_IDS = MAX_PARTITION_COUNT;
|
|
@@ -6738,6 +7379,13 @@ async function handleSyncRequestMessage(message, respond, deps) {
|
|
|
6738
7379
|
}
|
|
6739
7380
|
);
|
|
6740
7381
|
}
|
|
7382
|
+
if (!deps.engine.listIndexes().some((index) => index.name === request.indexName)) {
|
|
7383
|
+
throw new NarsilError(
|
|
7384
|
+
ErrorCodes.SNAPSHOT_SYNC_INDEX_NOT_FOUND,
|
|
7385
|
+
`Node '${deps.nodeId}' holds no copy of index '${request.indexName}' yet, so it cannot serve a sync for partition ${request.partitionId}`,
|
|
7386
|
+
{ indexName: request.indexName, partitionId: request.partitionId, primaryNodeId: deps.nodeId }
|
|
7387
|
+
);
|
|
7388
|
+
}
|
|
6741
7389
|
if (message.sourceId !== deps.nodeId) {
|
|
6742
7390
|
recordReplicaPosition(
|
|
6743
7391
|
deps.writeDeps.catchUp,
|
|
@@ -6798,6 +7446,55 @@ async function sendSyncEntriesResponse(message, respond, deps, entries) {
|
|
|
6798
7446
|
});
|
|
6799
7447
|
}
|
|
6800
7448
|
|
|
7449
|
+
// src/distribution/cluster-node/allocation-wait.ts
|
|
7450
|
+
var DEFAULT_CREATE_INDEX_WAIT_MS = 3e4;
|
|
7451
|
+
function everyPartitionActive(table, partitionCount) {
|
|
7452
|
+
if (table === null || table.assignments.size < partitionCount) {
|
|
7453
|
+
return false;
|
|
7454
|
+
}
|
|
7455
|
+
for (const assignment of table.assignments.values()) {
|
|
7456
|
+
if (assignment.state !== "ACTIVE") {
|
|
7457
|
+
return false;
|
|
7458
|
+
}
|
|
7459
|
+
}
|
|
7460
|
+
return true;
|
|
7461
|
+
}
|
|
7462
|
+
async function allocationPossible(coordinator) {
|
|
7463
|
+
const nodes = await coordinator.listNodes();
|
|
7464
|
+
return nodes.some((node) => node.roles.includes("controller")) && nodes.some((node) => node.roles.includes("data"));
|
|
7465
|
+
}
|
|
7466
|
+
async function waitForServingAllocation(coordinator, indexName, partitionCount, timeoutMs) {
|
|
7467
|
+
if (timeoutMs <= 0) {
|
|
7468
|
+
return;
|
|
7469
|
+
}
|
|
7470
|
+
let settle = () => void 0;
|
|
7471
|
+
const outcome = new Promise((resolve) => {
|
|
7472
|
+
settle = resolve;
|
|
7473
|
+
});
|
|
7474
|
+
const deadline = setTimeout(settle, timeoutMs);
|
|
7475
|
+
deadline.unref?.();
|
|
7476
|
+
let unwatch = () => void 0;
|
|
7477
|
+
try {
|
|
7478
|
+
unwatch = await coordinator.watchAllocation((event) => {
|
|
7479
|
+
if (event.indexName === indexName && everyPartitionActive(event.table, partitionCount)) {
|
|
7480
|
+
settle();
|
|
7481
|
+
}
|
|
7482
|
+
});
|
|
7483
|
+
if (everyPartitionActive(await coordinator.getAllocation(indexName), partitionCount)) {
|
|
7484
|
+
return;
|
|
7485
|
+
}
|
|
7486
|
+
if (!await allocationPossible(coordinator)) {
|
|
7487
|
+
return;
|
|
7488
|
+
}
|
|
7489
|
+
await outcome;
|
|
7490
|
+
} catch (_) {
|
|
7491
|
+
return;
|
|
7492
|
+
} finally {
|
|
7493
|
+
clearTimeout(deadline);
|
|
7494
|
+
unwatch();
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
|
|
6801
7498
|
// src/distribution/cluster-node/types/defaults.ts
|
|
6802
7499
|
var DEFAULT_PARTITION_COUNT = 5;
|
|
6803
7500
|
var DEFAULT_REPLICATION_FACTOR = 1;
|
|
@@ -7677,8 +8374,8 @@ function collectGroupFailure(failed, docIds, err, indexName, fallbackCode) {
|
|
|
7677
8374
|
}
|
|
7678
8375
|
}
|
|
7679
8376
|
async function routeInsertBatch(indexName, documents, deps, options) {
|
|
7680
|
-
const table = await deps.coordinator
|
|
7681
|
-
if (table === null
|
|
8377
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
8378
|
+
if (table === null) {
|
|
7682
8379
|
return deps.engine.insertBatch(indexName, documents, options);
|
|
7683
8380
|
}
|
|
7684
8381
|
const withIds = documents.map((doc) => ({
|
|
@@ -7716,8 +8413,8 @@ async function routeInsertBatch(indexName, documents, deps, options) {
|
|
|
7716
8413
|
return { succeeded, failed };
|
|
7717
8414
|
}
|
|
7718
8415
|
async function routeRemoveBatch(indexName, docIds, deps) {
|
|
7719
|
-
const table = await deps.coordinator
|
|
7720
|
-
if (table === null
|
|
8416
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
8417
|
+
if (table === null) {
|
|
7721
8418
|
return deps.engine.removeBatch(indexName, docIds);
|
|
7722
8419
|
}
|
|
7723
8420
|
const routed = routeBatchItems(table, indexName, docIds, (docId) => docId, deps.nodeId);
|
|
@@ -7745,8 +8442,8 @@ async function routeRemoveBatch(indexName, docIds, deps) {
|
|
|
7745
8442
|
return { succeeded, failed };
|
|
7746
8443
|
}
|
|
7747
8444
|
async function routeUpdateBatch(indexName, updates, deps) {
|
|
7748
|
-
const table = await deps.coordinator
|
|
7749
|
-
if (table === null
|
|
8445
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
8446
|
+
if (table === null) {
|
|
7750
8447
|
return deps.engine.updateBatch(indexName, updates);
|
|
7751
8448
|
}
|
|
7752
8449
|
const routed = routeBatchItems(table, indexName, updates, (update) => update.docId, deps.nodeId);
|
|
@@ -7847,6 +8544,12 @@ async function routeCreateIndex(name, config, options, coordinator, engine) {
|
|
|
7847
8544
|
}
|
|
7848
8545
|
throw createErr;
|
|
7849
8546
|
}
|
|
8547
|
+
await waitForServingAllocation(
|
|
8548
|
+
coordinator,
|
|
8549
|
+
name,
|
|
8550
|
+
partitionCount,
|
|
8551
|
+
options?.waitForServingMs ?? DEFAULT_CREATE_INDEX_WAIT_MS
|
|
8552
|
+
);
|
|
7850
8553
|
}
|
|
7851
8554
|
async function withdrawPartialIndex(name, coordinator, engine) {
|
|
7852
8555
|
try {
|
|
@@ -8124,6 +8827,9 @@ function createDataNodeHandler(deps) {
|
|
|
8124
8827
|
case QueryMessageTypes.PREFLIGHT:
|
|
8125
8828
|
await handlePreflight(message, respond, deps);
|
|
8126
8829
|
return;
|
|
8830
|
+
case ClusterMessageTypes.PARTITION_STORES:
|
|
8831
|
+
await handlePartitionStores(message, respond, deps);
|
|
8832
|
+
return;
|
|
8127
8833
|
default:
|
|
8128
8834
|
return;
|
|
8129
8835
|
}
|
|
@@ -8379,22 +9085,32 @@ async function fanOutSearch(indexName, params, globalStats, facetShardSize, node
|
|
|
8379
9085
|
}
|
|
8380
9086
|
return Promise.all(promises);
|
|
8381
9087
|
}
|
|
8382
|
-
|
|
9088
|
+
var OUTCOME_SEVERITY = { success: 0, timeout: 1, failed: 2 };
|
|
9089
|
+
function buildCoverage(totalPartitions, unavailablePartitions, ...outcomeSets) {
|
|
9090
|
+
const worstByPartition = /* @__PURE__ */ new Map();
|
|
9091
|
+
for (const partitionId of unavailablePartitions) {
|
|
9092
|
+
worstByPartition.set(partitionId, "failed");
|
|
9093
|
+
}
|
|
9094
|
+
for (const outcomes of outcomeSets) {
|
|
9095
|
+
for (const outcome of outcomes) {
|
|
9096
|
+
for (const partitionId of outcome.partitionIds) {
|
|
9097
|
+
const worstSoFar = worstByPartition.get(partitionId);
|
|
9098
|
+
if (worstSoFar === void 0 || OUTCOME_SEVERITY[outcome.status] > OUTCOME_SEVERITY[worstSoFar]) {
|
|
9099
|
+
worstByPartition.set(partitionId, outcome.status);
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
8383
9104
|
let queriedPartitions = 0;
|
|
8384
9105
|
let timedOutPartitions = 0;
|
|
8385
|
-
let failedPartitions =
|
|
8386
|
-
for (const
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
timedOutPartitions += partitionCount;
|
|
8394
|
-
break;
|
|
8395
|
-
case "failed":
|
|
8396
|
-
failedPartitions += partitionCount;
|
|
8397
|
-
break;
|
|
9106
|
+
let failedPartitions = 0;
|
|
9107
|
+
for (const status of worstByPartition.values()) {
|
|
9108
|
+
if (status === "success") {
|
|
9109
|
+
queriedPartitions += 1;
|
|
9110
|
+
} else if (status === "timeout") {
|
|
9111
|
+
timedOutPartitions += 1;
|
|
9112
|
+
} else {
|
|
9113
|
+
failedPartitions += 1;
|
|
8398
9114
|
}
|
|
8399
9115
|
}
|
|
8400
9116
|
return {
|
|
@@ -8412,7 +9128,7 @@ async function sendWithTimeout(transport, targets, message, timeout, validate) {
|
|
|
8412
9128
|
timerId.unref();
|
|
8413
9129
|
}
|
|
8414
9130
|
});
|
|
8415
|
-
const sendPromise =
|
|
9131
|
+
const sendPromise = sendToFirstReachableTarget2(transport, targets, message).then((response) => {
|
|
8416
9132
|
const decoded = decode(response.payload);
|
|
8417
9133
|
return validate(decoded);
|
|
8418
9134
|
});
|
|
@@ -8431,7 +9147,7 @@ async function resolveTargets(nodeId, deps) {
|
|
|
8431
9147
|
const targets = await deps.resolveNodeTargets(nodeId);
|
|
8432
9148
|
return targets.length > 0 ? targets : [nodeId];
|
|
8433
9149
|
}
|
|
8434
|
-
async function
|
|
9150
|
+
async function sendToFirstReachableTarget2(transport, targets, message) {
|
|
8435
9151
|
let lastError;
|
|
8436
9152
|
for (const target of targets) {
|
|
8437
9153
|
try {
|
|
@@ -8797,7 +9513,7 @@ async function executeSingleFanOut(indexName, params, limit, offset, facetShardS
|
|
|
8797
9513
|
if (sortFields !== null) {
|
|
8798
9514
|
failOutcomesMissingSortValues(outcomes);
|
|
8799
9515
|
}
|
|
8800
|
-
const coverage = buildCoverage(totalPartitions, routing.unavailablePartitions
|
|
9516
|
+
const coverage = buildCoverage(totalPartitions, routing.unavailablePartitions, outcomes);
|
|
8801
9517
|
if (!config.allowPartialResults) {
|
|
8802
9518
|
const failedCount = coverage.timedOutPartitions + coverage.failedPartitions;
|
|
8803
9519
|
if (failedCount > 0) {
|
|
@@ -8871,9 +9587,7 @@ async function executeHybridQuery(indexName, params, limit, offset, facetShardSi
|
|
|
8871
9587
|
fanOutSearch(indexName, textParams, textGlobalStats, facetShardSize, routing.nodeToPartitions, deps, config),
|
|
8872
9588
|
fanOutSearch(indexName, vectorParams, null, null, routing.nodeToPartitions, deps, config)
|
|
8873
9589
|
]);
|
|
8874
|
-
const
|
|
8875
|
-
const vectorCoverage = buildCoverage(totalPartitions, routing.unavailablePartitions.length, vectorOutcomes);
|
|
8876
|
-
const coverage = worstCaseCoverage(textCoverage, vectorCoverage);
|
|
9590
|
+
const coverage = buildCoverage(totalPartitions, routing.unavailablePartitions, textOutcomes, vectorOutcomes);
|
|
8877
9591
|
if (!config.allowPartialResults) {
|
|
8878
9592
|
const failedCount = coverage.timedOutPartitions + coverage.failedPartitions;
|
|
8879
9593
|
if (failedCount > 0) {
|
|
@@ -8935,32 +9649,6 @@ async function executeHybridQuery(indexName, params, limit, offset, facetShardSi
|
|
|
8935
9649
|
coverage
|
|
8936
9650
|
};
|
|
8937
9651
|
}
|
|
8938
|
-
function worstCaseCoverage(a, b) {
|
|
8939
|
-
const failed = Math.max(a.failedPartitions, b.failedPartitions);
|
|
8940
|
-
const timedOut = Math.max(a.timedOutPartitions, b.timedOutPartitions);
|
|
8941
|
-
const queried = Math.max(0, a.totalPartitions - failed - timedOut);
|
|
8942
|
-
return {
|
|
8943
|
-
totalPartitions: a.totalPartitions,
|
|
8944
|
-
queriedPartitions: queried,
|
|
8945
|
-
timedOutPartitions: timedOut,
|
|
8946
|
-
failedPartitions: failed
|
|
8947
|
-
};
|
|
8948
|
-
}
|
|
8949
|
-
function servesAnyPartition(allocation) {
|
|
8950
|
-
if (allocation === null || allocation.assignments.size === 0) {
|
|
8951
|
-
return false;
|
|
8952
|
-
}
|
|
8953
|
-
for (const [, assignment] of allocation.assignments) {
|
|
8954
|
-
if (assignment.state === "ACTIVE") {
|
|
8955
|
-
return true;
|
|
8956
|
-
}
|
|
8957
|
-
}
|
|
8958
|
-
return false;
|
|
8959
|
-
}
|
|
8960
|
-
async function activeAllocation(deps, indexName) {
|
|
8961
|
-
const allocation = await deps.config.coordinator.getAllocation(indexName);
|
|
8962
|
-
return servesAnyPartition(allocation) ? allocation : null;
|
|
8963
|
-
}
|
|
8964
9652
|
function strictScatterGroups(allocation, indexName) {
|
|
8965
9653
|
const routing = selectReplicasForQuery(allocation, randomSelector);
|
|
8966
9654
|
if (routing.unavailablePartitions.length > 0) {
|
|
@@ -9054,7 +9742,7 @@ async function gatherPartitionCounts(deps, indexName, allocation) {
|
|
|
9054
9742
|
return { countsByPartition, language };
|
|
9055
9743
|
}
|
|
9056
9744
|
async function countCluster(deps, indexName) {
|
|
9057
|
-
const allocation = await
|
|
9745
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9058
9746
|
if (allocation === null) {
|
|
9059
9747
|
return deps.engine.countDocuments(indexName);
|
|
9060
9748
|
}
|
|
@@ -9066,7 +9754,7 @@ async function countCluster(deps, indexName) {
|
|
|
9066
9754
|
return total;
|
|
9067
9755
|
}
|
|
9068
9756
|
async function statsCluster(deps, indexName) {
|
|
9069
|
-
const allocation = await
|
|
9757
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9070
9758
|
if (allocation === null) {
|
|
9071
9759
|
return deps.engine.getStats(indexName);
|
|
9072
9760
|
}
|
|
@@ -9092,7 +9780,7 @@ async function statsCluster(deps, indexName) {
|
|
|
9092
9780
|
};
|
|
9093
9781
|
}
|
|
9094
9782
|
async function partitionStatsCluster(deps, indexName) {
|
|
9095
|
-
const allocation = await
|
|
9783
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9096
9784
|
if (allocation === null) {
|
|
9097
9785
|
return deps.engine.getPartitionStats(indexName);
|
|
9098
9786
|
}
|
|
@@ -9146,7 +9834,7 @@ async function gatherLocalPage(deps, indexName, payload, partitionIds, sortField
|
|
|
9146
9834
|
return { entries, total: result.total, hasMore: result.cursor !== null };
|
|
9147
9835
|
}
|
|
9148
9836
|
async function listCluster(deps, indexName, params) {
|
|
9149
|
-
const allocation = await
|
|
9837
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9150
9838
|
if (allocation === null) {
|
|
9151
9839
|
return deps.engine.listDocuments(indexName, params);
|
|
9152
9840
|
}
|
|
@@ -9233,7 +9921,7 @@ function suggestNodeLimit(clientLimit) {
|
|
|
9233
9921
|
return Math.min(oversampled, MAX_SUGGEST_SCATTER_LIMIT);
|
|
9234
9922
|
}
|
|
9235
9923
|
async function suggestCluster(deps, indexName, params) {
|
|
9236
|
-
const allocation = await
|
|
9924
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9237
9925
|
if (allocation === null) {
|
|
9238
9926
|
return deps.engine.suggest(indexName, params);
|
|
9239
9927
|
}
|
|
@@ -9277,7 +9965,7 @@ async function suggestCluster(deps, indexName, params) {
|
|
|
9277
9965
|
return result;
|
|
9278
9966
|
}
|
|
9279
9967
|
async function preflightCluster(deps, indexName, params) {
|
|
9280
|
-
const allocation = await
|
|
9968
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9281
9969
|
if (allocation === null) {
|
|
9282
9970
|
return deps.engine.preflight(indexName, params);
|
|
9283
9971
|
}
|
|
@@ -9312,7 +10000,7 @@ async function preflightCluster(deps, indexName, params) {
|
|
|
9312
10000
|
|
|
9313
10001
|
// src/distribution/cluster-node/reads/index.ts
|
|
9314
10002
|
async function readClusterDocuments(deps, indexName, docIds) {
|
|
9315
|
-
const allocation = await
|
|
10003
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9316
10004
|
if (allocation === null) {
|
|
9317
10005
|
return deps.engine.getMultiple(indexName, docIds);
|
|
9318
10006
|
}
|
|
@@ -9331,23 +10019,11 @@ function routingConfig(query) {
|
|
|
9331
10019
|
}
|
|
9332
10020
|
return config;
|
|
9333
10021
|
}
|
|
9334
|
-
async function queryLocalCopy(deps, indexName, params, allocation) {
|
|
9335
|
-
const result = await deps.engine.query(indexName, params);
|
|
9336
|
-
if (allocation === null || allocation.assignments.size === 0) {
|
|
9337
|
-
return result;
|
|
9338
|
-
}
|
|
9339
|
-
const totalPartitions = allocation.assignments.size;
|
|
9340
|
-
return {
|
|
9341
|
-
...result,
|
|
9342
|
-
coverage: { totalPartitions, queriedPartitions: 0, timedOutPartitions: 0, failedPartitions: totalPartitions }
|
|
9343
|
-
};
|
|
9344
|
-
}
|
|
9345
10022
|
async function queryCluster(deps, indexName, params) {
|
|
9346
|
-
const
|
|
9347
|
-
if (
|
|
9348
|
-
return
|
|
10023
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
10024
|
+
if (allocation === null) {
|
|
10025
|
+
return deps.engine.query(indexName, params);
|
|
9349
10026
|
}
|
|
9350
|
-
const allocation = table;
|
|
9351
10027
|
const wireParams = localParamsToWire(params);
|
|
9352
10028
|
const queryDeps = {
|
|
9353
10029
|
transport: deps.config.transport,
|
|
@@ -9371,7 +10047,7 @@ async function queryCluster(deps, indexName, params) {
|
|
|
9371
10047
|
// src/distribution/cluster-node/clear.ts
|
|
9372
10048
|
var CLEAR_PAGE_SIZE = 1e3;
|
|
9373
10049
|
async function clearCluster(readDeps, writeDeps, indexName) {
|
|
9374
|
-
const allocation = await
|
|
10050
|
+
const allocation = await routableAllocation(readDeps.config.coordinator, indexName);
|
|
9375
10051
|
if (allocation === null) {
|
|
9376
10052
|
return readDeps.engine.clear(indexName);
|
|
9377
10053
|
}
|
|
@@ -9481,6 +10157,53 @@ function createClusterNodeOperations(deps) {
|
|
|
9481
10157
|
};
|
|
9482
10158
|
}
|
|
9483
10159
|
|
|
10160
|
+
// src/distribution/cluster-node/primary-partition.ts
|
|
10161
|
+
async function preparePrimaryPartition(indexName, partitionId, deps) {
|
|
10162
|
+
const schema = await deps.coordinator.getSchema(indexName);
|
|
10163
|
+
if (schema === null) {
|
|
10164
|
+
return false;
|
|
10165
|
+
}
|
|
10166
|
+
const allocation = await deps.coordinator.getAllocation(indexName);
|
|
10167
|
+
if (allocation === null || allocation.assignments.size === 0) {
|
|
10168
|
+
return false;
|
|
10169
|
+
}
|
|
10170
|
+
const existing = deps.engine.listIndexes().find((index) => index.name === indexName);
|
|
10171
|
+
if (existing === void 0) {
|
|
10172
|
+
try {
|
|
10173
|
+
await deps.engine.createIndex(indexName, {
|
|
10174
|
+
schema,
|
|
10175
|
+
partitions: { maxPartitions: allocation.assignments.size }
|
|
10176
|
+
});
|
|
10177
|
+
} catch (error) {
|
|
10178
|
+
if (!(error instanceof NarsilError) || error.code !== ErrorCodes.INDEX_ALREADY_EXISTS) {
|
|
10179
|
+
deps.onError(error);
|
|
10180
|
+
return false;
|
|
10181
|
+
}
|
|
10182
|
+
}
|
|
10183
|
+
} else {
|
|
10184
|
+
const schemaError = validateRestoredSchema(deps.engine, indexName, deps.nodeId, schema);
|
|
10185
|
+
if (schemaError !== null) {
|
|
10186
|
+
deps.onError(schemaError);
|
|
10187
|
+
return false;
|
|
10188
|
+
}
|
|
10189
|
+
}
|
|
10190
|
+
resumeNumberingAboveOwnCopy(indexName, partitionId, allocation.assignments.get(partitionId), deps);
|
|
10191
|
+
return true;
|
|
10192
|
+
}
|
|
10193
|
+
function resumeNumberingAboveOwnCopy(indexName, partitionId, assignment, deps) {
|
|
10194
|
+
if (assignment === void 0) {
|
|
10195
|
+
return;
|
|
10196
|
+
}
|
|
10197
|
+
if (deps.replicationLogPosition(indexName, partitionId) > 0) {
|
|
10198
|
+
return;
|
|
10199
|
+
}
|
|
10200
|
+
const floor = Math.max(deps.engine.highestPersistedSeqNoOf(indexName, partitionId), assignment.commitPoint);
|
|
10201
|
+
if (floor <= 0) {
|
|
10202
|
+
return;
|
|
10203
|
+
}
|
|
10204
|
+
deps.seedReplicationLog(indexName, partitionId, floor + 1, assignment.primaryTerm);
|
|
10205
|
+
}
|
|
10206
|
+
|
|
9484
10207
|
// src/distribution/replication/types.ts
|
|
9485
10208
|
var DEFAULT_LOG_RETENTION_BYTES = 268435456;
|
|
9486
10209
|
|
|
@@ -9716,14 +10439,29 @@ function seedReplicationLog(replicationLogs, indexName, partitionId, startSeqNo,
|
|
|
9716
10439
|
})
|
|
9717
10440
|
);
|
|
9718
10441
|
}
|
|
9719
|
-
|
|
9720
|
-
// src/distribution/cluster-node/transport-listener.ts
|
|
9721
10442
|
var CONTROLLER_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
9722
10443
|
ReplicationMessageTypes.INSYNC_ADD,
|
|
9723
10444
|
ReplicationMessageTypes.INSYNC_REMOVE,
|
|
9724
10445
|
ClusterMessageTypes.BOOTSTRAP_COMPLETE
|
|
9725
10446
|
]);
|
|
9726
|
-
function
|
|
10447
|
+
function refuseMessage(nodeId, message, respond, code, text) {
|
|
10448
|
+
return respond({
|
|
10449
|
+
type: `${message.type}.error`,
|
|
10450
|
+
sourceId: nodeId,
|
|
10451
|
+
requestId: message.requestId,
|
|
10452
|
+
payload: encode({ error: true, code, message: text })
|
|
10453
|
+
});
|
|
10454
|
+
}
|
|
10455
|
+
function createNotReadyHandler(nodeId) {
|
|
10456
|
+
return (message, respond) => refuseMessage(
|
|
10457
|
+
nodeId,
|
|
10458
|
+
message,
|
|
10459
|
+
respond,
|
|
10460
|
+
ErrorCodes.NODE_NOT_READY,
|
|
10461
|
+
`Node '${nodeId}' has yet to finish joining the cluster, so it serves nothing`
|
|
10462
|
+
);
|
|
10463
|
+
}
|
|
10464
|
+
function createMultiplexedControllerTransport(baseTransport, nodeId) {
|
|
9727
10465
|
let controllerHandler = null;
|
|
9728
10466
|
const transport = {
|
|
9729
10467
|
send(target, message) {
|
|
@@ -9750,9 +10488,17 @@ function createMultiplexedControllerTransport(baseTransport) {
|
|
|
9750
10488
|
createHandler(dataHandler) {
|
|
9751
10489
|
return async (message, respond) => {
|
|
9752
10490
|
if (CONTROLLER_MESSAGE_TYPES.has(message.type)) {
|
|
9753
|
-
if (controllerHandler
|
|
9754
|
-
await
|
|
10491
|
+
if (controllerHandler === null) {
|
|
10492
|
+
await refuseMessage(
|
|
10493
|
+
nodeId,
|
|
10494
|
+
message,
|
|
10495
|
+
respond,
|
|
10496
|
+
ErrorCodes.NODE_NOT_CONTROLLER,
|
|
10497
|
+
`Node '${nodeId}' holds no controller lease, so it answers no controller message`
|
|
10498
|
+
);
|
|
10499
|
+
return;
|
|
9755
10500
|
}
|
|
10501
|
+
await controllerHandler(message, createSingleResponseRespond(respond));
|
|
9756
10502
|
return;
|
|
9757
10503
|
}
|
|
9758
10504
|
await dataHandler(message, respond);
|
|
@@ -9773,6 +10519,7 @@ function createSingleResponseRespond(respond) {
|
|
|
9773
10519
|
|
|
9774
10520
|
// src/distribution/cluster-node/validate-config.ts
|
|
9775
10521
|
var MAX_WAIT_FOR_ACTIVE_REPLICAS = 255;
|
|
10522
|
+
var MIN_CONTROLLER_LEASE_TTL_MS = 1e3;
|
|
9776
10523
|
function validateClusterNodeConfig(config) {
|
|
9777
10524
|
if (config.address.length === 0) {
|
|
9778
10525
|
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "ClusterNodeConfig.address must not be empty");
|
|
@@ -9835,6 +10582,23 @@ function validateClusterNodeConfig(config) {
|
|
|
9835
10582
|
);
|
|
9836
10583
|
}
|
|
9837
10584
|
}
|
|
10585
|
+
if (config.controller !== void 0) {
|
|
10586
|
+
const { leaseTtlMs, standbyRetryMs } = config.controller;
|
|
10587
|
+
if (leaseTtlMs !== void 0 && (!Number.isInteger(leaseTtlMs) || leaseTtlMs < MIN_CONTROLLER_LEASE_TTL_MS)) {
|
|
10588
|
+
throw new NarsilError(
|
|
10589
|
+
ErrorCodes.CONFIG_INVALID,
|
|
10590
|
+
`ClusterNodeConfig.controller.leaseTtlMs must be an integer of at least ${MIN_CONTROLLER_LEASE_TTL_MS}`,
|
|
10591
|
+
{ leaseTtlMs }
|
|
10592
|
+
);
|
|
10593
|
+
}
|
|
10594
|
+
if (standbyRetryMs !== void 0 && (!Number.isInteger(standbyRetryMs) || standbyRetryMs <= 0)) {
|
|
10595
|
+
throw new NarsilError(
|
|
10596
|
+
ErrorCodes.CONFIG_INVALID,
|
|
10597
|
+
"ClusterNodeConfig.controller.standbyRetryMs must be a positive integer",
|
|
10598
|
+
{ standbyRetryMs }
|
|
10599
|
+
);
|
|
10600
|
+
}
|
|
10601
|
+
}
|
|
9838
10602
|
}
|
|
9839
10603
|
|
|
9840
10604
|
// src/distribution/cluster-node/index.ts
|
|
@@ -9860,13 +10624,8 @@ async function createClusterNode(config) {
|
|
|
9860
10624
|
async function resolveNodeTargets3(targetNodeId) {
|
|
9861
10625
|
return resolveNodeTargets2(config, targetNodeId);
|
|
9862
10626
|
}
|
|
9863
|
-
const forwardOnError = (
|
|
9864
|
-
|
|
9865
|
-
return;
|
|
9866
|
-
}
|
|
9867
|
-
const wrapped = error instanceof Error ? error : new Error(String(error));
|
|
9868
|
-
config.onError(wrapped);
|
|
9869
|
-
};
|
|
10627
|
+
const forwardOnError = createErrorForwarder(config.onError);
|
|
10628
|
+
const forwardAllocationError = createAllocationErrorForwarder(forwardOnError);
|
|
9870
10629
|
const registration = {
|
|
9871
10630
|
nodeId,
|
|
9872
10631
|
address: config.address,
|
|
@@ -9877,7 +10636,7 @@ async function createClusterNode(config) {
|
|
|
9877
10636
|
};
|
|
9878
10637
|
const hasDataRole = roles.includes("data");
|
|
9879
10638
|
const hasControllerRole = roles.includes("controller");
|
|
9880
|
-
const controllerTransport = hasDataRole
|
|
10639
|
+
const controllerTransport = hasDataRole ? createMultiplexedControllerTransport(config.transport, nodeId) : null;
|
|
9881
10640
|
const writeDeps = {
|
|
9882
10641
|
nodeId,
|
|
9883
10642
|
coordinator: config.coordinator,
|
|
@@ -9891,9 +10650,16 @@ async function createClusterNode(config) {
|
|
|
9891
10650
|
waitForActiveReplicas: config.replication?.waitForActiveReplicas
|
|
9892
10651
|
};
|
|
9893
10652
|
const orphanedIndexes = /* @__PURE__ */ new Set();
|
|
9894
|
-
|
|
10653
|
+
const heldPartitionWrites = /* @__PURE__ */ new Set();
|
|
10654
|
+
function trackHeldPartitionWrite(write) {
|
|
10655
|
+
const settled = write.catch(forwardOnError);
|
|
10656
|
+
heldPartitionWrites.add(settled);
|
|
10657
|
+
void settled.finally(() => heldPartitionWrites.delete(settled));
|
|
10658
|
+
}
|
|
9895
10659
|
let controller = null;
|
|
9896
10660
|
let unregisterHandler = null;
|
|
10661
|
+
let unregisterNotReadyHandler = null;
|
|
10662
|
+
let serving = false;
|
|
9897
10663
|
let isShutdown = false;
|
|
9898
10664
|
let activeOps = 0;
|
|
9899
10665
|
let drainResolve = null;
|
|
@@ -9909,76 +10675,59 @@ async function createClusterNode(config) {
|
|
|
9909
10675
|
restoreReplicationPartition: engine.restoreReplicationPartition,
|
|
9910
10676
|
onError: forwardOnError
|
|
9911
10677
|
};
|
|
10678
|
+
const primaryPartitionDeps = {
|
|
10679
|
+
engine,
|
|
10680
|
+
coordinator: config.coordinator,
|
|
10681
|
+
nodeId,
|
|
10682
|
+
seedReplicationLog: seedReplicationLog2,
|
|
10683
|
+
replicationLogPosition: (indexName, partitionId) => getReplicationLog2(indexName, partitionId).newestSeqNo ?? 0,
|
|
10684
|
+
onError: forwardOnError
|
|
10685
|
+
};
|
|
9912
10686
|
async function bootstrapPartition2(indexName, partitionId, primaryNodeId) {
|
|
9913
|
-
const succeeded = primaryNodeId === nodeId ? await preparePrimaryPartition(indexName) : await runBootstrapSync(bootstrapSyncState, indexName, partitionId, primaryNodeId, bootstrapSyncDeps);
|
|
10687
|
+
const succeeded = primaryNodeId === nodeId ? await preparePrimaryPartition(indexName, partitionId, primaryPartitionDeps) : await runBootstrapSync(bootstrapSyncState, indexName, partitionId, primaryNodeId, bootstrapSyncDeps);
|
|
9914
10688
|
if (succeeded) {
|
|
9915
10689
|
await adoptClusterIdentity(engine, config.coordinator, indexName);
|
|
10690
|
+
await engine.recordHeldPartition(indexName, partitionId);
|
|
9916
10691
|
}
|
|
9917
10692
|
return succeeded;
|
|
9918
10693
|
}
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
10694
|
+
const lifecycle = createDataNodeLifecycle({
|
|
10695
|
+
registration,
|
|
10696
|
+
coordinator: config.coordinator,
|
|
10697
|
+
transport: config.transport,
|
|
10698
|
+
knownIndexNames: [],
|
|
10699
|
+
bootstrapRetryBaseMs: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapRetryBaseMs,
|
|
10700
|
+
bootstrapRetryMaxMs: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapRetryMaxMs,
|
|
10701
|
+
bootstrapMaxRetries: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapMaxRetries,
|
|
10702
|
+
allocationDebounceMs: DEFAULT_NODE_LIFECYCLE_CONFIG.allocationDebounceMs,
|
|
10703
|
+
nodeHeartbeatIntervalMs: DEFAULT_NODE_LIFECYCLE_CONFIG.nodeHeartbeatIntervalMs,
|
|
10704
|
+
onBootstrapPartition: bootstrapPartition2,
|
|
10705
|
+
onHoldPartition: (indexName, partitionId) => {
|
|
10706
|
+
trackHeldPartitionWrite(engine.recordHeldPartition(indexName, partitionId));
|
|
10707
|
+
},
|
|
10708
|
+
retainedPartitionIds: (indexName) => engine.heldPartitionsOf(indexName) ?? [],
|
|
10709
|
+
onRemovePartition: (indexName, partitionId) => {
|
|
10710
|
+
clearBootstrapSyncIndex(bootstrapSyncState, indexName, partitionId);
|
|
10711
|
+
replicationLogs.delete(replicationLogKey2(indexName, partitionId));
|
|
10712
|
+
trackHeldPartitionWrite(engine.forgetHeldPartition(indexName, partitionId));
|
|
10713
|
+
void cleanupRemovedPartition(indexName, partitionId, {
|
|
10714
|
+
engine,
|
|
10715
|
+
coordinator: config.coordinator,
|
|
10716
|
+
nodeId,
|
|
10717
|
+
onError: forwardOnError
|
|
9941
10718
|
});
|
|
9942
|
-
} catch (error) {
|
|
9943
|
-
if (!(error instanceof NarsilError) || error.code !== ErrorCodes.INDEX_ALREADY_EXISTS) {
|
|
9944
|
-
forwardOnError(error);
|
|
9945
|
-
return false;
|
|
9946
|
-
}
|
|
9947
10719
|
}
|
|
9948
|
-
|
|
9949
|
-
}
|
|
9950
|
-
if (hasDataRole) {
|
|
9951
|
-
lifecycle = createDataNodeLifecycle({
|
|
9952
|
-
registration,
|
|
9953
|
-
coordinator: config.coordinator,
|
|
9954
|
-
transport: config.transport,
|
|
9955
|
-
knownIndexNames: [],
|
|
9956
|
-
bootstrapRetryBaseMs: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapRetryBaseMs,
|
|
9957
|
-
bootstrapRetryMaxMs: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapRetryMaxMs,
|
|
9958
|
-
bootstrapMaxRetries: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapMaxRetries,
|
|
9959
|
-
allocationDebounceMs: DEFAULT_NODE_LIFECYCLE_CONFIG.allocationDebounceMs,
|
|
9960
|
-
nodeHeartbeatIntervalMs: DEFAULT_NODE_LIFECYCLE_CONFIG.nodeHeartbeatIntervalMs,
|
|
9961
|
-
onBootstrapPartition: bootstrapPartition2,
|
|
9962
|
-
onRemovePartition: (indexName, partitionId) => {
|
|
9963
|
-
clearBootstrapSyncIndex(bootstrapSyncState, indexName, partitionId);
|
|
9964
|
-
replicationLogs.delete(replicationLogKey2(indexName, partitionId));
|
|
9965
|
-
void cleanupRemovedPartition(indexName, partitionId, {
|
|
9966
|
-
engine,
|
|
9967
|
-
coordinator: config.coordinator,
|
|
9968
|
-
nodeId,
|
|
9969
|
-
onError: forwardOnError
|
|
9970
|
-
});
|
|
9971
|
-
}
|
|
9972
|
-
});
|
|
9973
|
-
}
|
|
10720
|
+
});
|
|
9974
10721
|
if (hasControllerRole) {
|
|
9975
10722
|
controller = createController({
|
|
9976
10723
|
nodeId,
|
|
9977
10724
|
coordinator: config.coordinator,
|
|
9978
10725
|
transport: controllerTransport?.transport ?? config.transport,
|
|
9979
|
-
leaseTtlMs: DEFAULT_CONTROLLER_CONFIG.leaseTtlMs,
|
|
9980
|
-
standbyRetryMs: DEFAULT_CONTROLLER_CONFIG.standbyRetryMs,
|
|
9981
|
-
knownIndexNames: []
|
|
10726
|
+
leaseTtlMs: config.controller?.leaseTtlMs ?? DEFAULT_CONTROLLER_CONFIG.leaseTtlMs,
|
|
10727
|
+
standbyRetryMs: config.controller?.standbyRetryMs ?? DEFAULT_CONTROLLER_CONFIG.standbyRetryMs,
|
|
10728
|
+
knownIndexNames: [],
|
|
10729
|
+
onError: forwardAllocationError,
|
|
10730
|
+
onElectionError: forwardOnError
|
|
9982
10731
|
});
|
|
9983
10732
|
}
|
|
9984
10733
|
function guardShutdown() {
|
|
@@ -10003,21 +10752,16 @@ async function createClusterNode(config) {
|
|
|
10003
10752
|
}
|
|
10004
10753
|
}
|
|
10005
10754
|
const readDeps = { config, nodeId, engine, resolveNodeTargets: resolveNodeTargets3 };
|
|
10006
|
-
const cluster = {
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
return false;
|
|
10017
|
-
}
|
|
10018
|
-
return controller.isActive;
|
|
10019
|
-
}
|
|
10020
|
-
};
|
|
10755
|
+
const cluster = createClusterNamespace({
|
|
10756
|
+
nodeId,
|
|
10757
|
+
roles,
|
|
10758
|
+
coordinator: config.coordinator,
|
|
10759
|
+
lifecycle: () => lifecycle,
|
|
10760
|
+
controller: () => controller,
|
|
10761
|
+
isShutdown: () => isShutdown,
|
|
10762
|
+
isServing: () => serving,
|
|
10763
|
+
trackOp
|
|
10764
|
+
});
|
|
10021
10765
|
const node = {
|
|
10022
10766
|
get nodeId() {
|
|
10023
10767
|
return nodeId;
|
|
@@ -10051,9 +10795,10 @@ async function createClusterNode(config) {
|
|
|
10051
10795
|
orphanedIndexes.add(indexName);
|
|
10052
10796
|
}
|
|
10053
10797
|
}
|
|
10054
|
-
if (
|
|
10055
|
-
await
|
|
10798
|
+
if (hasDataRole) {
|
|
10799
|
+
unregisterNotReadyHandler = await config.transport.listen(createNotReadyHandler(nodeId));
|
|
10056
10800
|
}
|
|
10801
|
+
await lifecycle.join();
|
|
10057
10802
|
if (controller !== null) {
|
|
10058
10803
|
await controller.start();
|
|
10059
10804
|
}
|
|
@@ -10071,6 +10816,7 @@ async function createClusterNode(config) {
|
|
|
10071
10816
|
unregisterHandler = await config.transport.listen(listener);
|
|
10072
10817
|
startCatchUpPump(writeDeps.catchUp, writeDeps);
|
|
10073
10818
|
}
|
|
10819
|
+
serving = true;
|
|
10074
10820
|
},
|
|
10075
10821
|
async shutdown() {
|
|
10076
10822
|
await stopCatchUpPump(writeDeps.catchUp);
|
|
@@ -10078,6 +10824,7 @@ async function createClusterNode(config) {
|
|
|
10078
10824
|
return;
|
|
10079
10825
|
}
|
|
10080
10826
|
isShutdown = true;
|
|
10827
|
+
serving = false;
|
|
10081
10828
|
if (activeOps > 0) {
|
|
10082
10829
|
await new Promise((resolve) => {
|
|
10083
10830
|
drainResolve = resolve;
|
|
@@ -10087,12 +10834,15 @@ async function createClusterNode(config) {
|
|
|
10087
10834
|
unregisterHandler();
|
|
10088
10835
|
unregisterHandler = null;
|
|
10089
10836
|
}
|
|
10090
|
-
if (
|
|
10091
|
-
|
|
10837
|
+
if (unregisterNotReadyHandler !== null) {
|
|
10838
|
+
unregisterNotReadyHandler();
|
|
10839
|
+
unregisterNotReadyHandler = null;
|
|
10092
10840
|
}
|
|
10841
|
+
await lifecycle.shutdown();
|
|
10093
10842
|
if (controller !== null) {
|
|
10094
10843
|
await controller.shutdown();
|
|
10095
10844
|
}
|
|
10845
|
+
await Promise.all([...heldPartitionWrites]);
|
|
10096
10846
|
await engine.shutdown();
|
|
10097
10847
|
}
|
|
10098
10848
|
};
|