@delali/narsil 0.2.3-next.34 → 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/README.md +2 -2
- 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-GN7KF675.mjs → chunk-MMSWEEZT.mjs} +88 -164
- package/dist/{chunk-GODECMDT.mjs → chunk-PM52SMBT.mjs} +1 -1
- package/dist/chunk-WU2UZEVD.mjs +137 -0
- package/dist/{client-DQHB_swQ.d.ts → client-BobVnoIK.d.ts} +2 -2
- package/dist/client.d.ts +8 -4
- package/dist/client.mjs +2 -0
- package/dist/{config-ChZ7lfqc.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 -108
- package/dist/distribution.mjs +1328 -370
- package/dist/embeddings/openai.mjs +1 -1
- package/dist/{engine-DScGlT2Z.d.ts → engine-BE45D5ZG.d.ts} +3 -3
- package/dist/index.browser.mjs +90 -24
- 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-CQ-9bKxo.d.ts → search-DHuXwJYk.d.ts} +29 -2
- 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, 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
|
|
@@ -237,8 +248,18 @@ function countNodeAssignments(assignments) {
|
|
|
237
248
|
}
|
|
238
249
|
return counts;
|
|
239
250
|
}
|
|
251
|
+
function countPrimaryAssignments(assignments) {
|
|
252
|
+
const counts = /* @__PURE__ */ new Map();
|
|
253
|
+
for (const assignment of assignments.values()) {
|
|
254
|
+
if (assignment.primary !== null) {
|
|
255
|
+
counts.set(assignment.primary, (counts.get(assignment.primary) ?? 0) + 1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return counts;
|
|
259
|
+
}
|
|
240
260
|
function computeNodeWeights(nodes, assignments) {
|
|
241
261
|
const assignmentCounts = countNodeAssignments(assignments);
|
|
262
|
+
const primaryCounts = countPrimaryAssignments(assignments);
|
|
242
263
|
let totalCapacity = 0;
|
|
243
264
|
for (const node of nodes) {
|
|
244
265
|
totalCapacity += node.capacity.memoryBytes;
|
|
@@ -247,12 +268,14 @@ function computeNodeWeights(nodes, assignments) {
|
|
|
247
268
|
const weights = [];
|
|
248
269
|
for (const node of nodes) {
|
|
249
270
|
const partitionCount = assignmentCounts.get(node.nodeId) ?? 0;
|
|
271
|
+
const primaryCount = primaryCounts.get(node.nodeId) ?? 0;
|
|
250
272
|
const normalizedCapacity = averageCapacity > 0 ? node.capacity.memoryBytes / averageCapacity : 1;
|
|
251
|
-
const weight = partitionCount / normalizedCapacity;
|
|
252
273
|
weights.push({
|
|
253
274
|
nodeId: node.nodeId,
|
|
254
|
-
weight,
|
|
275
|
+
weight: partitionCount / normalizedCapacity,
|
|
255
276
|
partitionCount,
|
|
277
|
+
primaryWeight: primaryCount / normalizedCapacity,
|
|
278
|
+
primaryCount,
|
|
256
279
|
capacity: node.capacity.memoryBytes
|
|
257
280
|
});
|
|
258
281
|
}
|
|
@@ -271,6 +294,11 @@ function findBestNode(candidates, weights, deciders, context) {
|
|
|
271
294
|
const sorted = [...candidates].sort((a, b) => {
|
|
272
295
|
const wA = weightByNodeId.get(a);
|
|
273
296
|
const wB = weightByNodeId.get(b);
|
|
297
|
+
if (context.role === "primary") {
|
|
298
|
+
const primaryA = wA?.primaryWeight ?? 0;
|
|
299
|
+
const primaryB = wB?.primaryWeight ?? 0;
|
|
300
|
+
if (primaryA !== primaryB) return primaryA - primaryB;
|
|
301
|
+
}
|
|
274
302
|
const weightA = wA?.weight ?? 0;
|
|
275
303
|
const weightB = wB?.weight ?? 0;
|
|
276
304
|
if (weightA !== weightB) return weightA - weightB;
|
|
@@ -380,6 +408,90 @@ function initialAllocate(nodes, indexName, partitionCount, replicationFactor, co
|
|
|
380
408
|
};
|
|
381
409
|
}
|
|
382
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
|
+
|
|
419
|
+
// src/distribution/cluster/allocator/leadership.ts
|
|
420
|
+
var LEADERSHIP_IMBALANCE_THRESHOLD = 2;
|
|
421
|
+
function loadOf(nodeId, counts, shares) {
|
|
422
|
+
const share = shares.get(nodeId);
|
|
423
|
+
return (counts.get(nodeId) ?? 0) / (share === void 0 || share <= 0 ? 1 : share);
|
|
424
|
+
}
|
|
425
|
+
function heaviestLeader(shares, counts, withoutHandover) {
|
|
426
|
+
let heaviest;
|
|
427
|
+
for (const nodeId of shares.keys()) {
|
|
428
|
+
if (withoutHandover.has(nodeId)) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (heaviest === void 0 || loadOf(nodeId, counts, shares) > loadOf(heaviest, counts, shares)) {
|
|
432
|
+
heaviest = nodeId;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return heaviest;
|
|
436
|
+
}
|
|
437
|
+
function findSwap(assignments, heaviest, counts, shares) {
|
|
438
|
+
const heaviestLoad = loadOf(heaviest, counts, shares);
|
|
439
|
+
let best = null;
|
|
440
|
+
for (const assignment of assignments.values()) {
|
|
441
|
+
if (assignment.state !== "ACTIVE" || assignment.primary !== heaviest) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
for (const candidateNodeId of assignment.inSyncSet) {
|
|
445
|
+
const candidateLoad = loadOf(candidateNodeId, counts, shares);
|
|
446
|
+
if (heaviestLoad - candidateLoad < LEADERSHIP_IMBALANCE_THRESHOLD) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (best === null || candidateLoad < best.candidateLoad) {
|
|
450
|
+
best = { assignment, candidateNodeId, candidateLoad };
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return best;
|
|
455
|
+
}
|
|
456
|
+
function applySwap(swap, previousPrimary) {
|
|
457
|
+
const { assignment, candidateNodeId } = swap;
|
|
458
|
+
assignment.primary = candidateNodeId;
|
|
459
|
+
assignment.replicas = [...assignment.replicas.filter((nodeId) => nodeId !== candidateNodeId), previousPrimary];
|
|
460
|
+
assignment.inSyncSet = [
|
|
461
|
+
...assignment.inSyncSet.filter((nodeId) => nodeId !== candidateNodeId && nodeId !== previousPrimary),
|
|
462
|
+
previousPrimary
|
|
463
|
+
];
|
|
464
|
+
assignment.primaryTerm += 1;
|
|
465
|
+
}
|
|
466
|
+
function rebalanceLeadership(assignments, shares) {
|
|
467
|
+
const withoutHandover = /* @__PURE__ */ new Set();
|
|
468
|
+
for (let swaps = 0; swaps < assignments.size; swaps++) {
|
|
469
|
+
const counts = countPrimaryAssignments(assignments);
|
|
470
|
+
const heaviest = heaviestLeader(shares, counts, withoutHandover);
|
|
471
|
+
if (heaviest === void 0) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const swap = findSwap(assignments, heaviest, counts, shares);
|
|
475
|
+
if (swap === null) {
|
|
476
|
+
withoutHandover.add(heaviest);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
applySwap(swap, heaviest);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function capacityShares(nodes) {
|
|
483
|
+
const shares = /* @__PURE__ */ new Map();
|
|
484
|
+
let totalCapacity = 0;
|
|
485
|
+
for (const node of nodes) {
|
|
486
|
+
totalCapacity += node.capacity.memoryBytes;
|
|
487
|
+
}
|
|
488
|
+
const averageCapacity = nodes.length > 0 ? totalCapacity / nodes.length : 0;
|
|
489
|
+
for (const node of nodes) {
|
|
490
|
+
shares.set(node.nodeId, averageCapacity > 0 ? node.capacity.memoryBytes / averageCapacity : 1);
|
|
491
|
+
}
|
|
492
|
+
return shares;
|
|
493
|
+
}
|
|
494
|
+
|
|
383
495
|
// src/distribution/cluster/allocator/rebalance.ts
|
|
384
496
|
function cloneAssignments(assignments) {
|
|
385
497
|
const cloned = /* @__PURE__ */ new Map();
|
|
@@ -390,20 +502,55 @@ function cloneAssignments(assignments) {
|
|
|
390
502
|
inSyncSet: [...assignment.inSyncSet],
|
|
391
503
|
commitPoint: assignment.commitPoint,
|
|
392
504
|
state: assignment.state,
|
|
393
|
-
primaryTerm: assignment.primaryTerm
|
|
505
|
+
primaryTerm: assignment.primaryTerm,
|
|
506
|
+
...assignment.lastHolders !== void 0 ? { lastHolders: [...assignment.lastHolders] } : {},
|
|
507
|
+
...assignment.unassignedReason !== void 0 ? { unassignedReason: assignment.unassignedReason } : {}
|
|
394
508
|
});
|
|
395
509
|
}
|
|
396
510
|
return cloned;
|
|
397
511
|
}
|
|
398
|
-
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
|
+
}
|
|
399
520
|
assignment.primary = null;
|
|
400
521
|
assignment.replicas = [];
|
|
401
522
|
assignment.inSyncSet = [];
|
|
523
|
+
assignment.lastHolders = [...holders].sort(compareCodePoints);
|
|
402
524
|
assignment.state = "UNASSIGNED";
|
|
403
525
|
}
|
|
404
|
-
function
|
|
526
|
+
function primaryLoadOf(nodeId, primaryCounts, shares) {
|
|
527
|
+
const share = shares.get(nodeId);
|
|
528
|
+
return (primaryCounts.get(nodeId) ?? 0) / (share === void 0 || share <= 0 ? 1 : share);
|
|
529
|
+
}
|
|
530
|
+
function leastLoadedCandidate(candidates, primaryCounts, shares) {
|
|
531
|
+
let best;
|
|
532
|
+
for (const candidate of candidates) {
|
|
533
|
+
if (best === void 0) {
|
|
534
|
+
best = candidate;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const bestLoad = primaryLoadOf(best, primaryCounts, shares);
|
|
538
|
+
const candidateLoad = primaryLoadOf(candidate, primaryCounts, shares);
|
|
539
|
+
if (candidateLoad < bestLoad || candidateLoad === bestLoad && candidate < best) {
|
|
540
|
+
best = candidate;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return best;
|
|
544
|
+
}
|
|
545
|
+
function handleLostNodes(assignments, activeNodeIds, shares) {
|
|
546
|
+
const primaryCounts = countPrimaryAssignments(assignments);
|
|
405
547
|
for (const assignment of assignments.values()) {
|
|
406
|
-
|
|
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);
|
|
407
554
|
if (primaryWasLost) {
|
|
408
555
|
assignment.primary = null;
|
|
409
556
|
}
|
|
@@ -411,16 +558,21 @@ function handleLostNodes(assignments, activeNodeIds) {
|
|
|
411
558
|
assignment.inSyncSet = assignment.inSyncSet.filter((id) => activeNodeIds.has(id));
|
|
412
559
|
if (!primaryWasLost) {
|
|
413
560
|
if (assignment.primary === null) {
|
|
414
|
-
markUnassigned(assignment);
|
|
561
|
+
markUnassigned(assignment, lastPrimary, lastInSyncSet);
|
|
415
562
|
}
|
|
416
563
|
continue;
|
|
417
564
|
}
|
|
418
565
|
const inSyncCandidates = assignment.replicas.filter((id) => assignment.inSyncSet.includes(id));
|
|
419
566
|
if (inSyncCandidates.length === 0) {
|
|
420
|
-
markUnassigned(assignment);
|
|
567
|
+
markUnassigned(assignment, lastPrimary, lastInSyncSet);
|
|
421
568
|
continue;
|
|
422
569
|
}
|
|
423
|
-
const promoted = inSyncCandidates
|
|
570
|
+
const promoted = leastLoadedCandidate(inSyncCandidates, primaryCounts, shares);
|
|
571
|
+
if (promoted === void 0) {
|
|
572
|
+
markUnassigned(assignment, lastPrimary, lastInSyncSet);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
primaryCounts.set(promoted, (primaryCounts.get(promoted) ?? 0) + 1);
|
|
424
576
|
assignment.primary = promoted;
|
|
425
577
|
assignment.replicas = assignment.replicas.filter((id) => id !== promoted);
|
|
426
578
|
assignment.inSyncSet = assignment.inSyncSet.filter((id) => id !== promoted);
|
|
@@ -453,7 +605,16 @@ function fillReplicaSlots(assignments, sortedNodes, nodeMap, replicationFactor,
|
|
|
453
605
|
}
|
|
454
606
|
}
|
|
455
607
|
}
|
|
456
|
-
function
|
|
608
|
+
function slotWeightOf(nodeId, shares) {
|
|
609
|
+
const share = shares.get(nodeId);
|
|
610
|
+
return 1 / (share === void 0 || share <= 0 ? 1 : share);
|
|
611
|
+
}
|
|
612
|
+
function moveNarrowsGap(mostLoaded, leastLoaded, shares) {
|
|
613
|
+
const gap = mostLoaded.weight - leastLoaded.weight;
|
|
614
|
+
const afterMove = Math.abs(gap - slotWeightOf(mostLoaded.nodeId, shares) - slotWeightOf(leastLoaded.nodeId, shares));
|
|
615
|
+
return afterMove < gap;
|
|
616
|
+
}
|
|
617
|
+
function rebalanceForBalance(assignments, sortedNodes, nodeMap, constraints, deciders, shares) {
|
|
457
618
|
let totalSlots = 0;
|
|
458
619
|
for (const assignment of assignments.values()) {
|
|
459
620
|
if (assignment.primary !== null) totalSlots++;
|
|
@@ -469,35 +630,73 @@ function rebalanceForBalance(assignments, sortedNodes, nodeMap, constraints, dec
|
|
|
469
630
|
if (mostLoaded.weight - leastLoaded.weight < REBALANCE_THRESHOLD) {
|
|
470
631
|
break;
|
|
471
632
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (moved) break;
|
|
475
|
-
const replicaIndex = assignment.replicas.indexOf(mostLoaded.nodeId);
|
|
476
|
-
if (replicaIndex >= 0) {
|
|
477
|
-
const removedReplica = assignment.replicas[replicaIndex];
|
|
478
|
-
assignment.replicas.splice(replicaIndex, 1);
|
|
479
|
-
const nodeAssignmentCounts = countNodeAssignments(assignments);
|
|
480
|
-
const moveContext = {
|
|
481
|
-
partitionId,
|
|
482
|
-
role: "replica",
|
|
483
|
-
currentAssignment: assignment,
|
|
484
|
-
allAssignments: assignments,
|
|
485
|
-
nodeAssignmentCounts,
|
|
486
|
-
nodes: nodeMap,
|
|
487
|
-
constraints
|
|
488
|
-
};
|
|
489
|
-
const target = findBestNode([leastLoaded.nodeId], weights, deciders, moveContext);
|
|
490
|
-
if (target !== null) {
|
|
491
|
-
assignment.replicas.push(target);
|
|
492
|
-
moved = true;
|
|
493
|
-
} else {
|
|
494
|
-
assignment.replicas.splice(replicaIndex, 0, removedReplica);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
633
|
+
if (!moveNarrowsGap(mostLoaded, leastLoaded, shares)) {
|
|
634
|
+
break;
|
|
497
635
|
}
|
|
636
|
+
const move = {
|
|
637
|
+
fromNodeId: mostLoaded.nodeId,
|
|
638
|
+
toNodeId: leastLoaded.nodeId,
|
|
639
|
+
weights,
|
|
640
|
+
nodeMap,
|
|
641
|
+
constraints,
|
|
642
|
+
deciders
|
|
643
|
+
};
|
|
644
|
+
const laggingFirst = moveOneReplica(assignments, move, true);
|
|
645
|
+
const moved = laggingFirst || moveOneReplica(assignments, move, false);
|
|
498
646
|
if (!moved) break;
|
|
499
647
|
}
|
|
500
648
|
}
|
|
649
|
+
function moveOneReplica(assignments, move, laggingOnly) {
|
|
650
|
+
for (const [partitionId, assignment] of assignments) {
|
|
651
|
+
const replicaIndex = assignment.replicas.indexOf(move.fromNodeId);
|
|
652
|
+
if (replicaIndex < 0) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (laggingOnly && assignment.inSyncSet.includes(move.fromNodeId)) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const removedReplica = assignment.replicas[replicaIndex];
|
|
659
|
+
assignment.replicas.splice(replicaIndex, 1);
|
|
660
|
+
const moveContext = {
|
|
661
|
+
partitionId,
|
|
662
|
+
role: "replica",
|
|
663
|
+
currentAssignment: assignment,
|
|
664
|
+
allAssignments: assignments,
|
|
665
|
+
nodeAssignmentCounts: countNodeAssignments(assignments),
|
|
666
|
+
nodes: move.nodeMap,
|
|
667
|
+
constraints: move.constraints
|
|
668
|
+
};
|
|
669
|
+
const target = findBestNode([move.toNodeId], move.weights, move.deciders, moveContext);
|
|
670
|
+
if (target === null) {
|
|
671
|
+
assignment.replicas.splice(replicaIndex, 0, removedReplica);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
assignment.replicas.push(target);
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
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
|
+
}
|
|
689
|
+
function pruneInSyncSets(assignments) {
|
|
690
|
+
for (const assignment of assignments.values()) {
|
|
691
|
+
if (assignment.state === "UNASSIGNED") {
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
const stale = assignment.inSyncSet.some((nodeId) => !assignment.replicas.includes(nodeId));
|
|
695
|
+
if (stale) {
|
|
696
|
+
assignment.inSyncSet = assignment.inSyncSet.filter((nodeId) => assignment.replicas.includes(nodeId));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
501
700
|
function rebalanceAllocate(nodes, currentTable, constraints, deciders) {
|
|
502
701
|
const sortedNodes = [...nodes].sort((a, b) => {
|
|
503
702
|
if (a.nodeId < b.nodeId) return -1;
|
|
@@ -510,9 +709,13 @@ function rebalanceAllocate(nodes, currentTable, constraints, deciders) {
|
|
|
510
709
|
nodeMap.set(node.nodeId, node);
|
|
511
710
|
}
|
|
512
711
|
const assignments = cloneAssignments(currentTable.assignments);
|
|
513
|
-
|
|
712
|
+
const shares = capacityShares(sortedNodes);
|
|
713
|
+
handleLostNodes(assignments, activeNodeIds, shares);
|
|
514
714
|
fillReplicaSlots(assignments, sortedNodes, nodeMap, currentTable.replicationFactor, constraints, deciders);
|
|
515
|
-
rebalanceForBalance(assignments, sortedNodes, nodeMap, constraints, deciders);
|
|
715
|
+
rebalanceForBalance(assignments, sortedNodes, nodeMap, constraints, deciders, shares);
|
|
716
|
+
rebalanceLeadership(assignments, shares);
|
|
717
|
+
pruneInSyncSets(assignments);
|
|
718
|
+
clearRecoveryStateOnServedPartitions(assignments);
|
|
516
719
|
const warnings = collectReplicationWarnings(assignments, currentTable.replicationFactor);
|
|
517
720
|
return {
|
|
518
721
|
table: {
|
|
@@ -561,7 +764,268 @@ function allocate(nodes, currentTable, indexName, partitionCount, replicationFac
|
|
|
561
764
|
return rebalanceAllocate(dataNodes, currentTable, constraints, deciders);
|
|
562
765
|
}
|
|
563
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
|
+
|
|
564
1025
|
// src/distribution/cluster/controller/event-loop/allocation.ts
|
|
1026
|
+
function holdsDataNode(nodes) {
|
|
1027
|
+
return nodes.some((node) => node.roles.includes("data"));
|
|
1028
|
+
}
|
|
565
1029
|
var ALLOCATION_CAS_ATTEMPTS = 5;
|
|
566
1030
|
var ALLOCATION_RETRY_DELAY_MS = 1e3;
|
|
567
1031
|
var TEARDOWN_CAS_ATTEMPTS = 5;
|
|
@@ -603,12 +1067,29 @@ async function runAllocatorForIndex(coordinator, indexName, nodes, isActive, att
|
|
|
603
1067
|
{ indexName, attempts: ALLOCATION_CAS_ATTEMPTS }
|
|
604
1068
|
);
|
|
605
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
|
+
}
|
|
606
1087
|
async function runAllocatorForAllIndexes(state, coordinator, isActive, onError) {
|
|
607
1088
|
if (!isActive()) {
|
|
608
1089
|
return;
|
|
609
1090
|
}
|
|
610
1091
|
const nodes = await coordinator.listNodes();
|
|
611
|
-
if (nodes
|
|
1092
|
+
if (!holdsDataNode(nodes) || !isActive()) {
|
|
612
1093
|
return;
|
|
613
1094
|
}
|
|
614
1095
|
let anyIndexFailed = false;
|
|
@@ -618,6 +1099,7 @@ async function runAllocatorForAllIndexes(state, coordinator, isActive, onError)
|
|
|
618
1099
|
}
|
|
619
1100
|
try {
|
|
620
1101
|
await runAllocatorForIndex(coordinator, indexName, nodes, isActive);
|
|
1102
|
+
await recoverIndexPartitions(state, coordinator, indexName, nodes, isActive);
|
|
621
1103
|
} catch (error) {
|
|
622
1104
|
anyIndexFailed = true;
|
|
623
1105
|
if (onError !== void 0) {
|
|
@@ -653,7 +1135,7 @@ async function handleSchemaCreated(indexName, coordinator, knownIndexes, isActiv
|
|
|
653
1135
|
return;
|
|
654
1136
|
}
|
|
655
1137
|
const nodes = await coordinator.listNodes();
|
|
656
|
-
if (nodes
|
|
1138
|
+
if (!holdsDataNode(nodes) || !isActive()) {
|
|
657
1139
|
return;
|
|
658
1140
|
}
|
|
659
1141
|
const storedTable = await coordinator.getAllocation(indexName);
|
|
@@ -744,14 +1226,6 @@ async function handleSchemaEvent(event, coordinator, state, isActive, onError) {
|
|
|
744
1226
|
await tearDownAllocation(event.indexName, coordinator, state, isActive, 0, onError);
|
|
745
1227
|
}
|
|
746
1228
|
}
|
|
747
|
-
|
|
748
|
-
// src/distribution/payload-guards.ts
|
|
749
|
-
function isRecord(value) {
|
|
750
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
751
|
-
}
|
|
752
|
-
function isValidInteger(value) {
|
|
753
|
-
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
754
|
-
}
|
|
755
1229
|
var INSYNC_CAS_ATTEMPTS = 5;
|
|
756
1230
|
var REFUSE = { outcome: "refuse" };
|
|
757
1231
|
var ACCEPT = { outcome: "accept" };
|
|
@@ -807,6 +1281,9 @@ async function requestInsyncRemoval(indexName, partitionId, replicaNodeId, prima
|
|
|
807
1281
|
}
|
|
808
1282
|
function handleInsyncRemoval(payload, coordinator) {
|
|
809
1283
|
return updateInsyncSet(payload, coordinator, (assignment) => {
|
|
1284
|
+
if (assignment.state === "UNASSIGNED") {
|
|
1285
|
+
return REFUSE;
|
|
1286
|
+
}
|
|
810
1287
|
if (!assignment.inSyncSet.includes(payload.replicaNodeId)) {
|
|
811
1288
|
return ACCEPT;
|
|
812
1289
|
}
|
|
@@ -940,15 +1417,6 @@ async function handleBootstrapCompleteMessage(message, respond, coordinator, con
|
|
|
940
1417
|
});
|
|
941
1418
|
}
|
|
942
1419
|
var BOOTSTRAP_CAS_ATTEMPTS = 5;
|
|
943
|
-
function ensurePrimaryInSyncSet(assignment, inSyncSet) {
|
|
944
|
-
if (assignment.primary === null) {
|
|
945
|
-
return inSyncSet;
|
|
946
|
-
}
|
|
947
|
-
if (inSyncSet.includes(assignment.primary)) {
|
|
948
|
-
return inSyncSet;
|
|
949
|
-
}
|
|
950
|
-
return [assignment.primary, ...inSyncSet];
|
|
951
|
-
}
|
|
952
1420
|
async function processBootstrapComplete(payload, coordinator) {
|
|
953
1421
|
for (let attempt = 0; attempt < BOOTSTRAP_CAS_ATTEMPTS; attempt++) {
|
|
954
1422
|
const table = await coordinator.getAllocation(payload.indexName);
|
|
@@ -968,13 +1436,10 @@ async function processBootstrapComplete(payload, coordinator) {
|
|
|
968
1436
|
if (assignment.state !== "INITIALISING") {
|
|
969
1437
|
return false;
|
|
970
1438
|
}
|
|
971
|
-
const
|
|
1439
|
+
const served = { ...assignment, state: "ACTIVE" };
|
|
1440
|
+
delete served.lastHolders;
|
|
972
1441
|
const updatedAssignments = new Map(table.assignments);
|
|
973
|
-
updatedAssignments.set(payload.partitionId,
|
|
974
|
-
...assignment,
|
|
975
|
-
state: "ACTIVE",
|
|
976
|
-
inSyncSet: ensurePrimaryInSyncSet(assignment, baseInSyncSet)
|
|
977
|
-
});
|
|
1442
|
+
updatedAssignments.set(payload.partitionId, served);
|
|
978
1443
|
const written = await coordinator.putAllocation(
|
|
979
1444
|
payload.indexName,
|
|
980
1445
|
{ ...table, version: table.version + 1, assignments: updatedAssignments },
|
|
@@ -1086,7 +1551,7 @@ function handleInsyncRemoveMessage(state, message, respond, coordinator, nodeId,
|
|
|
1086
1551
|
await respond(createInsyncConfirmMessage(confirmPayload, nodeId, message.requestId));
|
|
1087
1552
|
});
|
|
1088
1553
|
}
|
|
1089
|
-
function handleInsyncAddMessage(state, message, respond, coordinator, nodeId, isActive) {
|
|
1554
|
+
function handleInsyncAddMessage(state, message, respond, coordinator, nodeId, isActive, onError) {
|
|
1090
1555
|
const decoded = decodeMessage(message);
|
|
1091
1556
|
const payload = validateInsyncAddPayload(decoded);
|
|
1092
1557
|
if (payload === null) {
|
|
@@ -1106,6 +1571,9 @@ function handleInsyncAddMessage(state, message, respond, coordinator, nodeId, is
|
|
|
1106
1571
|
}
|
|
1107
1572
|
const confirmPayload = await handleInsyncAdmission(payload, coordinator);
|
|
1108
1573
|
await respond(createInsyncConfirmMessage(confirmPayload, nodeId, message.requestId));
|
|
1574
|
+
if (confirmPayload.accepted && isActive()) {
|
|
1575
|
+
scheduleDebouncedAllocation(state, coordinator, isActive, onError);
|
|
1576
|
+
}
|
|
1109
1577
|
});
|
|
1110
1578
|
}
|
|
1111
1579
|
function handleQueuedBootstrapComplete(state, message, respond, coordinator, nodeId, isActive) {
|
|
@@ -1126,7 +1594,9 @@ function createEventLoopState(initialIndexNames) {
|
|
|
1126
1594
|
knownIndexes: new Set(initialIndexNames),
|
|
1127
1595
|
debounceTimer: null,
|
|
1128
1596
|
teardownTimers: /* @__PURE__ */ new Map(),
|
|
1129
|
-
insyncQueue: Promise.resolve()
|
|
1597
|
+
insyncQueue: Promise.resolve(),
|
|
1598
|
+
transport: null,
|
|
1599
|
+
controllerNodeId: null
|
|
1130
1600
|
};
|
|
1131
1601
|
}
|
|
1132
1602
|
function clearEventLoopWatchers(state) {
|
|
@@ -1155,6 +1625,8 @@ function clearEventLoopWatchers(state) {
|
|
|
1155
1625
|
// src/distribution/cluster/controller/event-loop/index.ts
|
|
1156
1626
|
async function startEventLoop(state, coordinator, transport, nodeId, isActive, onError) {
|
|
1157
1627
|
clearEventLoopWatchers(state);
|
|
1628
|
+
state.transport = transport;
|
|
1629
|
+
state.controllerNodeId = nodeId;
|
|
1158
1630
|
try {
|
|
1159
1631
|
state.unwatchNodes = await coordinator.watchNodes((_event) => {
|
|
1160
1632
|
scheduleDebouncedAllocation(state, coordinator, isActive, onError);
|
|
@@ -1173,7 +1645,7 @@ async function startEventLoop(state, coordinator, transport, nodeId, isActive, o
|
|
|
1173
1645
|
if (message.type === ReplicationMessageTypes.INSYNC_REMOVE) {
|
|
1174
1646
|
handleInsyncRemoveMessage(state, message, respond, coordinator, nodeId, isActive);
|
|
1175
1647
|
} else if (message.type === ReplicationMessageTypes.INSYNC_ADD) {
|
|
1176
|
-
handleInsyncAddMessage(state, message, respond, coordinator, nodeId, isActive);
|
|
1648
|
+
handleInsyncAddMessage(state, message, respond, coordinator, nodeId, isActive, onError);
|
|
1177
1649
|
} else if (message.type === ClusterMessageTypes.BOOTSTRAP_COMPLETE) {
|
|
1178
1650
|
handleQueuedBootstrapComplete(state, message, respond, coordinator, nodeId, isActive);
|
|
1179
1651
|
}
|
|
@@ -1187,13 +1659,21 @@ async function startEventLoop(state, coordinator, transport, nodeId, isActive, o
|
|
|
1187
1659
|
|
|
1188
1660
|
// src/distribution/cluster/controller/index.ts
|
|
1189
1661
|
function createController(config) {
|
|
1190
|
-
const { nodeId, coordinator, transport, leaseTtlMs, standbyRetryMs, knownIndexNames, onError } = config;
|
|
1662
|
+
const { nodeId, coordinator, transport, leaseTtlMs, standbyRetryMs, knownIndexNames, onError, onElectionError } = config;
|
|
1191
1663
|
let electionState = createElectionState();
|
|
1192
1664
|
let eventLoopState = createEventLoopState(knownIndexNames);
|
|
1193
1665
|
let stopped = false;
|
|
1194
1666
|
function isActive() {
|
|
1195
1667
|
return electionState.active;
|
|
1196
1668
|
}
|
|
1669
|
+
function standBy() {
|
|
1670
|
+
if (stopped) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
scheduleStandbyRetry(electionState, standbyRetryMs, () => {
|
|
1674
|
+
void runElection();
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1197
1677
|
function stepDown() {
|
|
1198
1678
|
if (!electionState.active) {
|
|
1199
1679
|
return;
|
|
@@ -1201,12 +1681,7 @@ function createController(config) {
|
|
|
1201
1681
|
electionState.active = false;
|
|
1202
1682
|
clearElectionTimers(electionState);
|
|
1203
1683
|
clearEventLoopWatchers(eventLoopState);
|
|
1204
|
-
|
|
1205
|
-
scheduleStandbyRetry(electionState, standbyRetryMs, () => {
|
|
1206
|
-
tryElection().catch(() => {
|
|
1207
|
-
});
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1684
|
+
standBy();
|
|
1210
1685
|
}
|
|
1211
1686
|
async function becomeActive() {
|
|
1212
1687
|
if (electionState.active) {
|
|
@@ -1232,18 +1707,38 @@ function createController(config) {
|
|
|
1232
1707
|
throw error;
|
|
1233
1708
|
}
|
|
1234
1709
|
}
|
|
1235
|
-
async function
|
|
1710
|
+
async function standForElection() {
|
|
1236
1711
|
if (stopped) {
|
|
1237
1712
|
return;
|
|
1238
1713
|
}
|
|
1239
1714
|
const acquired = await tryAcquireLease(coordinator, nodeId, leaseTtlMs);
|
|
1715
|
+
if (stopped) {
|
|
1716
|
+
if (acquired) {
|
|
1717
|
+
await releaseLease(coordinator);
|
|
1718
|
+
}
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1240
1721
|
if (acquired) {
|
|
1241
1722
|
await becomeActive();
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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();
|
|
1247
1742
|
}
|
|
1248
1743
|
}
|
|
1249
1744
|
const controller = {
|
|
@@ -1252,7 +1747,13 @@ function createController(config) {
|
|
|
1252
1747
|
},
|
|
1253
1748
|
async start() {
|
|
1254
1749
|
stopped = false;
|
|
1255
|
-
|
|
1750
|
+
clearStandbyTimer(electionState);
|
|
1751
|
+
try {
|
|
1752
|
+
await standForElection();
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
standBy();
|
|
1755
|
+
throw error;
|
|
1756
|
+
}
|
|
1256
1757
|
},
|
|
1257
1758
|
async stop() {
|
|
1258
1759
|
stopped = true;
|
|
@@ -1476,7 +1977,7 @@ function createBootstrapState(indexName, partitionId, primaryNodeId) {
|
|
|
1476
1977
|
};
|
|
1477
1978
|
}
|
|
1478
1979
|
|
|
1479
|
-
// src/distribution/cluster/node-lifecycle/allocation-watcher.ts
|
|
1980
|
+
// src/distribution/cluster/node-lifecycle/allocation-watcher/state.ts
|
|
1480
1981
|
function partitionKey(indexName, partitionId) {
|
|
1481
1982
|
return `${indexName}:${partitionId}`;
|
|
1482
1983
|
}
|
|
@@ -1486,40 +1987,137 @@ function createAllocationWatcherState() {
|
|
|
1486
1987
|
debounceTimer: null,
|
|
1487
1988
|
activeBootstraps: /* @__PURE__ */ new Map(),
|
|
1488
1989
|
trackedPartitions: /* @__PURE__ */ new Map(),
|
|
1990
|
+
pendingPartitions: /* @__PURE__ */ new Set(),
|
|
1489
1991
|
pendingTables: /* @__PURE__ */ new Map(),
|
|
1490
1992
|
restartWaiters: /* @__PURE__ */ new Map(),
|
|
1993
|
+
retainedPartitions: /* @__PURE__ */ new Set(),
|
|
1994
|
+
indexesSeededFromRecord: /* @__PURE__ */ new Set(),
|
|
1491
1995
|
stopped: false
|
|
1492
1996
|
};
|
|
1493
1997
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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);
|
|
1498
2046
|
}
|
|
1499
|
-
|
|
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);
|
|
1500
2057
|
});
|
|
1501
|
-
state.unwatchAllocation = unwatchAllocation;
|
|
1502
2058
|
}
|
|
1503
|
-
function
|
|
1504
|
-
|
|
1505
|
-
if (
|
|
1506
|
-
|
|
2059
|
+
function waitBeforeBootstrapRestart(state, config) {
|
|
2060
|
+
const delayMs = Math.min(config.bootstrapRetryMaxMs, config.bootstrapRetryBaseMs);
|
|
2061
|
+
if (delayMs <= 0) {
|
|
2062
|
+
return Promise.resolve();
|
|
1507
2063
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
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);
|
|
1514
2088
|
}
|
|
1515
|
-
|
|
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);
|
|
1516
2111
|
}
|
|
2112
|
+
|
|
2113
|
+
// src/distribution/cluster/node-lifecycle/allocation-watcher/table.ts
|
|
1517
2114
|
function processAllocationChange(state, config, table) {
|
|
1518
2115
|
if (state.stopped) {
|
|
1519
2116
|
return;
|
|
1520
2117
|
}
|
|
1521
2118
|
const nodeId = config.registration.nodeId;
|
|
1522
2119
|
const currentKeys = /* @__PURE__ */ new Set();
|
|
2120
|
+
seedRetainedFromRecord(state, config, table.indexName);
|
|
1523
2121
|
for (const [partitionId, assignment] of table.assignments) {
|
|
1524
2122
|
if (!isNodeAssigned(assignment, nodeId)) {
|
|
1525
2123
|
continue;
|
|
@@ -1540,8 +2138,34 @@ function processAllocationChange(state, config, table) {
|
|
|
1540
2138
|
primaryNodeId: assignment.primary,
|
|
1541
2139
|
primaryTerm: assignment.primaryTerm
|
|
1542
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;
|
|
1543
2167
|
}
|
|
1544
|
-
|
|
2168
|
+
return assignment.primary === nodeId || assignment.inSyncSet.includes(nodeId);
|
|
1545
2169
|
}
|
|
1546
2170
|
function isNodeAssigned(assignment, nodeId) {
|
|
1547
2171
|
if (assignment.primary === nodeId) {
|
|
@@ -1574,6 +2198,10 @@ function handleExistingAssignment(state, config, existing, assignment, nodeId) {
|
|
|
1574
2198
|
}
|
|
1575
2199
|
return;
|
|
1576
2200
|
}
|
|
2201
|
+
if (promotedBackAtHigherTerm(existing, assignment, nodeId)) {
|
|
2202
|
+
startBootstrap(state, config, existing.indexName, existing.partitionId, nodeId);
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
1577
2205
|
if (assignment.primary !== null && replicaNeedsResync(state, existing, assignment, nodeId)) {
|
|
1578
2206
|
startBootstrap(state, config, existing.indexName, existing.partitionId, assignment.primary);
|
|
1579
2207
|
}
|
|
@@ -1593,138 +2221,111 @@ function replicaNeedsResync(state, existing, assignment, nodeId) {
|
|
|
1593
2221
|
}
|
|
1594
2222
|
return !state.activeBootstraps.has(partitionKey(existing.indexName, existing.partitionId));
|
|
1595
2223
|
}
|
|
1596
|
-
function
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
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;
|
|
1601
2231
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
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;
|
|
1617
2266
|
}
|
|
1618
|
-
if (
|
|
1619
|
-
state.
|
|
1620
|
-
|
|
2267
|
+
if (currentKeys.has(key)) {
|
|
2268
|
+
state.retainedPartitions.delete(key);
|
|
2269
|
+
continue;
|
|
1621
2270
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
state.activeBootstraps.delete(key);
|
|
2271
|
+
if (!copyHasNoFurtherUse(table, partitionId, nodeId)) {
|
|
2272
|
+
continue;
|
|
1625
2273
|
}
|
|
1626
|
-
state.
|
|
1627
|
-
|
|
1628
|
-
|
|
2274
|
+
state.retainedPartitions.delete(key);
|
|
2275
|
+
if (config.onRemovePartition !== void 0) {
|
|
2276
|
+
config.onRemovePartition(table.indexName, partitionId);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
1629
2279
|
}
|
|
1630
|
-
function
|
|
2280
|
+
function removeDroppedPartitions(state, config, table, currentKeys) {
|
|
1631
2281
|
for (const [key, tracked] of state.trackedPartitions) {
|
|
1632
|
-
if (tracked.indexName !== indexName) {
|
|
2282
|
+
if (tracked.indexName !== table.indexName) {
|
|
1633
2283
|
continue;
|
|
1634
2284
|
}
|
|
1635
2285
|
if (currentKeys.has(key)) {
|
|
1636
2286
|
continue;
|
|
1637
2287
|
}
|
|
1638
2288
|
state.trackedPartitions.delete(key);
|
|
2289
|
+
state.pendingPartitions.delete(key);
|
|
1639
2290
|
const activeBootstrap = state.activeBootstraps.get(key);
|
|
1640
2291
|
if (activeBootstrap !== void 0) {
|
|
1641
2292
|
abortBootstrapState(activeBootstrap);
|
|
1642
2293
|
state.activeBootstraps.delete(key);
|
|
1643
2294
|
}
|
|
2295
|
+
if (keepsCopyForRecovery(table, tracked.partitionId, config.registration.nodeId)) {
|
|
2296
|
+
state.retainedPartitions.add(key);
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
state.retainedPartitions.delete(key);
|
|
1644
2300
|
if (config.onRemovePartition !== void 0) {
|
|
1645
2301
|
config.onRemovePartition(tracked.indexName, tracked.partitionId);
|
|
1646
2302
|
}
|
|
1647
2303
|
}
|
|
1648
2304
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
state.
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
state.debounceTimer = null;
|
|
1658
|
-
}
|
|
1659
|
-
for (const [timer, resolve] of state.restartWaiters) {
|
|
1660
|
-
clearTimeout(timer);
|
|
1661
|
-
resolve();
|
|
1662
|
-
}
|
|
1663
|
-
state.restartWaiters.clear();
|
|
1664
|
-
for (const bootstrapState of state.activeBootstraps.values()) {
|
|
1665
|
-
abortBootstrapState(bootstrapState);
|
|
1666
|
-
}
|
|
1667
|
-
state.activeBootstraps.clear();
|
|
1668
|
-
state.trackedPartitions.clear();
|
|
1669
|
-
state.pendingTables.clear();
|
|
1670
|
-
}
|
|
1671
|
-
function processInitialAllocations(state, config, tables) {
|
|
1672
|
-
for (const table of tables) {
|
|
1673
|
-
processAllocationChange(state, config, table);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
function waitBeforeBootstrapRestart(state, config) {
|
|
1677
|
-
const delayMs = Math.min(config.bootstrapRetryMaxMs, config.bootstrapRetryBaseMs);
|
|
1678
|
-
if (delayMs <= 0) {
|
|
1679
|
-
return Promise.resolve();
|
|
1680
|
-
}
|
|
1681
|
-
return new Promise((resolve) => {
|
|
1682
|
-
const timer = setTimeout(() => {
|
|
1683
|
-
state.restartWaiters.delete(timer);
|
|
1684
|
-
resolve();
|
|
1685
|
-
}, delayMs);
|
|
1686
|
-
timer.unref?.();
|
|
1687
|
-
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);
|
|
1688
2313
|
});
|
|
2314
|
+
state.unwatchAllocation = unwatchAllocation;
|
|
1689
2315
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
await waitBeforeBootstrapRestart(state, config);
|
|
1695
|
-
if (state.stopped) {
|
|
1696
|
-
return;
|
|
2316
|
+
function scheduleProcessAllocation(state, config, table) {
|
|
2317
|
+
state.pendingTables.set(table.indexName, table);
|
|
2318
|
+
if (state.debounceTimer !== null) {
|
|
2319
|
+
clearTimeout(state.debounceTimer);
|
|
1697
2320
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
config.onError(error);
|
|
1705
|
-
}
|
|
1706
|
-
if (!state.stopped) {
|
|
1707
|
-
void restartBootstrapWhileOutOfSync(state, config, indexName, partitionId);
|
|
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);
|
|
1708
2327
|
}
|
|
1709
|
-
|
|
1710
|
-
}
|
|
1711
|
-
if (state.stopped || table === null) {
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
|
-
const assignment = table.assignments.get(partitionId);
|
|
1715
|
-
if (assignment === void 0 || assignment.primary === null || assignment.primary === nodeId) {
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
|
-
if (!assignment.replicas.includes(nodeId) || assignment.inSyncSet.includes(nodeId)) {
|
|
1719
|
-
return;
|
|
1720
|
-
}
|
|
1721
|
-
if (assignment.state !== "ACTIVE" && assignment.state !== "INITIALISING" && assignment.state !== "MIGRATING") {
|
|
1722
|
-
return;
|
|
1723
|
-
}
|
|
1724
|
-
if (state.activeBootstraps.has(partitionKey(indexName, partitionId))) {
|
|
1725
|
-
return;
|
|
1726
|
-
}
|
|
1727
|
-
startBootstrap(state, config, indexName, partitionId, assignment.primary);
|
|
2328
|
+
}, config.allocationDebounceMs);
|
|
1728
2329
|
}
|
|
1729
2330
|
|
|
1730
2331
|
// src/distribution/cluster/node-lifecycle/heartbeat.ts
|
|
@@ -1756,6 +2357,7 @@ async function stopRegistrationHeartbeat(state) {
|
|
|
1756
2357
|
// src/distribution/cluster/node-lifecycle/join.ts
|
|
1757
2358
|
async function joinCluster(config, watcherState, heartbeatState) {
|
|
1758
2359
|
await config.coordinator.registerNode(config.registration);
|
|
2360
|
+
config.onRegistered?.();
|
|
1759
2361
|
startRegistrationHeartbeat(heartbeatState, config);
|
|
1760
2362
|
const initialTables = await loadInitialAllocations(config);
|
|
1761
2363
|
processInitialAllocations(watcherState, config, initialTables);
|
|
@@ -1763,7 +2365,8 @@ async function joinCluster(config, watcherState, heartbeatState) {
|
|
|
1763
2365
|
}
|
|
1764
2366
|
async function loadInitialAllocations(config) {
|
|
1765
2367
|
const tables = [];
|
|
1766
|
-
|
|
2368
|
+
const indexNames = /* @__PURE__ */ new Set([...config.knownIndexNames, ...await config.coordinator.listSchemas()]);
|
|
2369
|
+
for (const indexName of indexNames) {
|
|
1767
2370
|
const table = await config.coordinator.getAllocation(indexName);
|
|
1768
2371
|
if (table !== null) {
|
|
1769
2372
|
tables.push(table);
|
|
@@ -1787,6 +2390,7 @@ var DEFAULT_NODE_LIFECYCLE_CONFIG = {
|
|
|
1787
2390
|
// src/distribution/cluster/node-lifecycle/index.ts
|
|
1788
2391
|
function createDataNodeLifecycle(config) {
|
|
1789
2392
|
let status = "stopped";
|
|
2393
|
+
let registered = false;
|
|
1790
2394
|
let watcherState = createAllocationWatcherState();
|
|
1791
2395
|
const heartbeatState = createRegistrationHeartbeatState();
|
|
1792
2396
|
let operationLock = Promise.resolve();
|
|
@@ -1813,6 +2417,12 @@ function createDataNodeLifecycle(config) {
|
|
|
1813
2417
|
get nodeId() {
|
|
1814
2418
|
return config.registration.nodeId;
|
|
1815
2419
|
},
|
|
2420
|
+
get registered() {
|
|
2421
|
+
return registered;
|
|
2422
|
+
},
|
|
2423
|
+
get pendingPartitionCount() {
|
|
2424
|
+
return watcherState.pendingPartitions.size;
|
|
2425
|
+
},
|
|
1816
2426
|
join() {
|
|
1817
2427
|
return withLock(async () => {
|
|
1818
2428
|
if (status === "active" || status === "joining") {
|
|
@@ -1831,10 +2441,21 @@ function createDataNodeLifecycle(config) {
|
|
|
1831
2441
|
}
|
|
1832
2442
|
status = "joining";
|
|
1833
2443
|
try {
|
|
1834
|
-
await joinCluster(
|
|
2444
|
+
await joinCluster(
|
|
2445
|
+
{
|
|
2446
|
+
...config,
|
|
2447
|
+
onRegistered: () => {
|
|
2448
|
+
registered = true;
|
|
2449
|
+
config.onRegistered?.();
|
|
2450
|
+
}
|
|
2451
|
+
},
|
|
2452
|
+
watcherState,
|
|
2453
|
+
heartbeatState
|
|
2454
|
+
);
|
|
1835
2455
|
status = "active";
|
|
1836
2456
|
} catch (error) {
|
|
1837
2457
|
await stopRegistrationHeartbeat(heartbeatState);
|
|
2458
|
+
registered = false;
|
|
1838
2459
|
status = "stopped";
|
|
1839
2460
|
throw error;
|
|
1840
2461
|
}
|
|
@@ -1851,6 +2472,7 @@ function createDataNodeLifecycle(config) {
|
|
|
1851
2472
|
try {
|
|
1852
2473
|
await leaveCluster(config);
|
|
1853
2474
|
} finally {
|
|
2475
|
+
registered = false;
|
|
1854
2476
|
status = "stopped";
|
|
1855
2477
|
watcherState = createAllocationWatcherState();
|
|
1856
2478
|
}
|
|
@@ -1869,6 +2491,7 @@ function createDataNodeLifecycle(config) {
|
|
|
1869
2491
|
} catch (_) {
|
|
1870
2492
|
}
|
|
1871
2493
|
}
|
|
2494
|
+
registered = false;
|
|
1872
2495
|
status = "shutdown";
|
|
1873
2496
|
watcherState = createAllocationWatcherState();
|
|
1874
2497
|
});
|
|
@@ -1925,6 +2548,9 @@ async function nodeHasOtherPartitions(indexName, removedPartitionId, deps) {
|
|
|
1925
2548
|
if (assignment.replicas.includes(deps.nodeId)) {
|
|
1926
2549
|
return true;
|
|
1927
2550
|
}
|
|
2551
|
+
if (holdsLastCopy(assignment, deps.nodeId)) {
|
|
2552
|
+
return true;
|
|
2553
|
+
}
|
|
1928
2554
|
}
|
|
1929
2555
|
return false;
|
|
1930
2556
|
}
|
|
@@ -3679,6 +4305,22 @@ async function executeBootstrapSync(state, entry, indexName, partitionId, primar
|
|
|
3679
4305
|
return true;
|
|
3680
4306
|
}
|
|
3681
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
|
+
|
|
3682
4324
|
// src/distribution/cluster-node/write-routing/assignment.ts
|
|
3683
4325
|
function resolvePartitionId(docId, partitionCount) {
|
|
3684
4326
|
return fnv1a(docId) % partitionCount;
|
|
@@ -3701,8 +4343,8 @@ function requireAssignedPrimary(assignment, indexName, partitionId) {
|
|
|
3701
4343
|
return assignment;
|
|
3702
4344
|
}
|
|
3703
4345
|
async function resolvePrimaryAssignment(indexName, docId, deps, requireLocalPrimary) {
|
|
3704
|
-
const table = await deps.coordinator
|
|
3705
|
-
if (table === null
|
|
4346
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
4347
|
+
if (table === null) {
|
|
3706
4348
|
if (requireLocalPrimary) {
|
|
3707
4349
|
throw new NarsilError(
|
|
3708
4350
|
ErrorCodes.QUERY_ROUTING_FAILED,
|
|
@@ -3829,7 +4471,7 @@ async function sendToController(payload, controllerNodeId, deps) {
|
|
|
3829
4471
|
}
|
|
3830
4472
|
return false;
|
|
3831
4473
|
}
|
|
3832
|
-
function
|
|
4474
|
+
function withTimeout2(work, timeoutMs, fallback) {
|
|
3833
4475
|
return new Promise((resolve) => {
|
|
3834
4476
|
const timer = setTimeout(() => resolve(fallback), timeoutMs);
|
|
3835
4477
|
timer.unref?.();
|
|
@@ -3884,9 +4526,9 @@ async function proposeAdmission(state, deps, indexName, partitionId, assignment,
|
|
|
3884
4526
|
appliedSeqNo: cursor.appliedSeqNo,
|
|
3885
4527
|
commitPoint: log.commitPoint
|
|
3886
4528
|
};
|
|
3887
|
-
const accepted = await
|
|
4529
|
+
const accepted = await withTimeout2(sendToController(payload, controllerNodeId, deps), ADMISSION_TIMEOUT_MS, false);
|
|
3888
4530
|
if (accepted && cursor.syncEpoch !== proposedEpoch) {
|
|
3889
|
-
await
|
|
4531
|
+
await withTimeout2(withdrawAdmission(payload, controllerNodeId, deps), ADMISSION_TIMEOUT_MS, void 0);
|
|
3890
4532
|
}
|
|
3891
4533
|
} finally {
|
|
3892
4534
|
clearPendingAdmission(state, indexName, partitionId, replicaNodeId);
|
|
@@ -4094,6 +4736,114 @@ async function stopCatchUpPump(state) {
|
|
|
4094
4736
|
}
|
|
4095
4737
|
}
|
|
4096
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
|
+
|
|
4097
4847
|
// src/distribution/cluster-node/local-engine.ts
|
|
4098
4848
|
async function createClusterLocalEngine(config) {
|
|
4099
4849
|
const core = createEngineCore(config);
|
|
@@ -4105,10 +4855,15 @@ async function createClusterLocalEngine(config) {
|
|
|
4105
4855
|
}
|
|
4106
4856
|
await core.analysisRebuild.reviewStaleIndexes();
|
|
4107
4857
|
const engine = createNarsilFromCore(core, config);
|
|
4858
|
+
const heldPartitions = createHeldPartitionRecord(core);
|
|
4108
4859
|
return Object.assign(engine, {
|
|
4109
4860
|
createIndexWithUuid: (name, indexConfig, indexUuid) => createEngineIndex(core, config, name, indexConfig, indexUuid),
|
|
4110
4861
|
indexUuidOf: (indexName) => core.indexRegistry.get(indexName)?.indexUuid,
|
|
4111
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),
|
|
4112
4867
|
applyReplicationEntry: (entry) => applyReplicationEntry(core, entry),
|
|
4113
4868
|
serializeReplicationPartition: (indexName, partitionId) => serializeReplicationPartition(core, indexName, partitionId),
|
|
4114
4869
|
restoreReplicationPartition: (indexName, partitionId, bytes, schema, partitionCount) => restoreReplicationPartition(core, engine, indexName, partitionId, bytes, schema, partitionCount),
|
|
@@ -4950,6 +5705,28 @@ async function streamValidatedSnapshotRequest(message, sink, request, deps, opti
|
|
|
4950
5705
|
}
|
|
4951
5706
|
await acquireAndStream(message, sink, request, deps, options);
|
|
4952
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
|
+
}
|
|
4953
5730
|
|
|
4954
5731
|
// src/distribution/query/validators/common.ts
|
|
4955
5732
|
var MAX_PARTITION_IDS = MAX_PARTITION_COUNT;
|
|
@@ -6293,7 +7070,8 @@ function distributedResultToLocal(result, documents = /* @__PURE__ */ new Map())
|
|
|
6293
7070
|
count: result.totalHits,
|
|
6294
7071
|
elapsed: 0,
|
|
6295
7072
|
cursor: result.cursor ?? void 0,
|
|
6296
|
-
facets: result.facets !== null ? convertWireFacetsToLocal(result.facets, result.facetErrorBounds) : void 0
|
|
7073
|
+
facets: result.facets !== null ? convertWireFacetsToLocal(result.facets, result.facetErrorBounds) : void 0,
|
|
7074
|
+
coverage: result.coverage
|
|
6297
7075
|
};
|
|
6298
7076
|
}
|
|
6299
7077
|
function convertLocalSortToWire(sort) {
|
|
@@ -6601,6 +7379,13 @@ async function handleSyncRequestMessage(message, respond, deps) {
|
|
|
6601
7379
|
}
|
|
6602
7380
|
);
|
|
6603
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
|
+
}
|
|
6604
7389
|
if (message.sourceId !== deps.nodeId) {
|
|
6605
7390
|
recordReplicaPosition(
|
|
6606
7391
|
deps.writeDeps.catchUp,
|
|
@@ -6661,6 +7446,55 @@ async function sendSyncEntriesResponse(message, respond, deps, entries) {
|
|
|
6661
7446
|
});
|
|
6662
7447
|
}
|
|
6663
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
|
+
|
|
6664
7498
|
// src/distribution/cluster-node/types/defaults.ts
|
|
6665
7499
|
var DEFAULT_PARTITION_COUNT = 5;
|
|
6666
7500
|
var DEFAULT_REPLICATION_FACTOR = 1;
|
|
@@ -7540,8 +8374,8 @@ function collectGroupFailure(failed, docIds, err, indexName, fallbackCode) {
|
|
|
7540
8374
|
}
|
|
7541
8375
|
}
|
|
7542
8376
|
async function routeInsertBatch(indexName, documents, deps, options) {
|
|
7543
|
-
const table = await deps.coordinator
|
|
7544
|
-
if (table === null
|
|
8377
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
8378
|
+
if (table === null) {
|
|
7545
8379
|
return deps.engine.insertBatch(indexName, documents, options);
|
|
7546
8380
|
}
|
|
7547
8381
|
const withIds = documents.map((doc) => ({
|
|
@@ -7579,8 +8413,8 @@ async function routeInsertBatch(indexName, documents, deps, options) {
|
|
|
7579
8413
|
return { succeeded, failed };
|
|
7580
8414
|
}
|
|
7581
8415
|
async function routeRemoveBatch(indexName, docIds, deps) {
|
|
7582
|
-
const table = await deps.coordinator
|
|
7583
|
-
if (table === null
|
|
8416
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
8417
|
+
if (table === null) {
|
|
7584
8418
|
return deps.engine.removeBatch(indexName, docIds);
|
|
7585
8419
|
}
|
|
7586
8420
|
const routed = routeBatchItems(table, indexName, docIds, (docId) => docId, deps.nodeId);
|
|
@@ -7608,8 +8442,8 @@ async function routeRemoveBatch(indexName, docIds, deps) {
|
|
|
7608
8442
|
return { succeeded, failed };
|
|
7609
8443
|
}
|
|
7610
8444
|
async function routeUpdateBatch(indexName, updates, deps) {
|
|
7611
|
-
const table = await deps.coordinator
|
|
7612
|
-
if (table === null
|
|
8445
|
+
const table = await routableAllocation(deps.coordinator, indexName);
|
|
8446
|
+
if (table === null) {
|
|
7613
8447
|
return deps.engine.updateBatch(indexName, updates);
|
|
7614
8448
|
}
|
|
7615
8449
|
const routed = routeBatchItems(table, indexName, updates, (update) => update.docId, deps.nodeId);
|
|
@@ -7710,6 +8544,12 @@ async function routeCreateIndex(name, config, options, coordinator, engine) {
|
|
|
7710
8544
|
}
|
|
7711
8545
|
throw createErr;
|
|
7712
8546
|
}
|
|
8547
|
+
await waitForServingAllocation(
|
|
8548
|
+
coordinator,
|
|
8549
|
+
name,
|
|
8550
|
+
partitionCount,
|
|
8551
|
+
options?.waitForServingMs ?? DEFAULT_CREATE_INDEX_WAIT_MS
|
|
8552
|
+
);
|
|
7713
8553
|
}
|
|
7714
8554
|
async function withdrawPartialIndex(name, coordinator, engine) {
|
|
7715
8555
|
try {
|
|
@@ -7987,6 +8827,9 @@ function createDataNodeHandler(deps) {
|
|
|
7987
8827
|
case QueryMessageTypes.PREFLIGHT:
|
|
7988
8828
|
await handlePreflight(message, respond, deps);
|
|
7989
8829
|
return;
|
|
8830
|
+
case ClusterMessageTypes.PARTITION_STORES:
|
|
8831
|
+
await handlePartitionStores(message, respond, deps);
|
|
8832
|
+
return;
|
|
7990
8833
|
default:
|
|
7991
8834
|
return;
|
|
7992
8835
|
}
|
|
@@ -8242,22 +9085,32 @@ async function fanOutSearch(indexName, params, globalStats, facetShardSize, node
|
|
|
8242
9085
|
}
|
|
8243
9086
|
return Promise.all(promises);
|
|
8244
9087
|
}
|
|
8245
|
-
|
|
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
|
+
}
|
|
8246
9104
|
let queriedPartitions = 0;
|
|
8247
9105
|
let timedOutPartitions = 0;
|
|
8248
|
-
let failedPartitions =
|
|
8249
|
-
for (const
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
timedOutPartitions += partitionCount;
|
|
8257
|
-
break;
|
|
8258
|
-
case "failed":
|
|
8259
|
-
failedPartitions += partitionCount;
|
|
8260
|
-
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;
|
|
8261
9114
|
}
|
|
8262
9115
|
}
|
|
8263
9116
|
return {
|
|
@@ -8275,7 +9128,7 @@ async function sendWithTimeout(transport, targets, message, timeout, validate) {
|
|
|
8275
9128
|
timerId.unref();
|
|
8276
9129
|
}
|
|
8277
9130
|
});
|
|
8278
|
-
const sendPromise =
|
|
9131
|
+
const sendPromise = sendToFirstReachableTarget2(transport, targets, message).then((response) => {
|
|
8279
9132
|
const decoded = decode(response.payload);
|
|
8280
9133
|
return validate(decoded);
|
|
8281
9134
|
});
|
|
@@ -8294,7 +9147,7 @@ async function resolveTargets(nodeId, deps) {
|
|
|
8294
9147
|
const targets = await deps.resolveNodeTargets(nodeId);
|
|
8295
9148
|
return targets.length > 0 ? targets : [nodeId];
|
|
8296
9149
|
}
|
|
8297
|
-
async function
|
|
9150
|
+
async function sendToFirstReachableTarget2(transport, targets, message) {
|
|
8298
9151
|
let lastError;
|
|
8299
9152
|
for (const target of targets) {
|
|
8300
9153
|
try {
|
|
@@ -8594,9 +9447,11 @@ async function distributedQuery(indexName, params, deps, config, selector) {
|
|
|
8594
9447
|
const routing = selectReplicasForQuery(allocationTable, randomSelector);
|
|
8595
9448
|
const totalPartitions = allocationTable.assignments.size;
|
|
8596
9449
|
if (routing.unavailablePartitions.length > 0 && !resolvedConfig.allowPartialResults) {
|
|
8597
|
-
throw new NarsilError(
|
|
8598
|
-
|
|
8599
|
-
|
|
9450
|
+
throw new NarsilError(
|
|
9451
|
+
ErrorCodes.QUERY_PARTIAL_FAILURE,
|
|
9452
|
+
"No active replica serves one or more partitions, and this node refuses partial results",
|
|
9453
|
+
{ unavailablePartitions: routing.unavailablePartitions }
|
|
9454
|
+
);
|
|
8600
9455
|
}
|
|
8601
9456
|
const isHybrid = params.hybrid !== null && params.term !== null && params.vector !== null;
|
|
8602
9457
|
if (isHybrid && params.searchAfter !== null) {
|
|
@@ -8658,7 +9513,7 @@ async function executeSingleFanOut(indexName, params, limit, offset, facetShardS
|
|
|
8658
9513
|
if (sortFields !== null) {
|
|
8659
9514
|
failOutcomesMissingSortValues(outcomes);
|
|
8660
9515
|
}
|
|
8661
|
-
const coverage = buildCoverage(totalPartitions, routing.unavailablePartitions
|
|
9516
|
+
const coverage = buildCoverage(totalPartitions, routing.unavailablePartitions, outcomes);
|
|
8662
9517
|
if (!config.allowPartialResults) {
|
|
8663
9518
|
const failedCount = coverage.timedOutPartitions + coverage.failedPartitions;
|
|
8664
9519
|
if (failedCount > 0) {
|
|
@@ -8732,9 +9587,7 @@ async function executeHybridQuery(indexName, params, limit, offset, facetShardSi
|
|
|
8732
9587
|
fanOutSearch(indexName, textParams, textGlobalStats, facetShardSize, routing.nodeToPartitions, deps, config),
|
|
8733
9588
|
fanOutSearch(indexName, vectorParams, null, null, routing.nodeToPartitions, deps, config)
|
|
8734
9589
|
]);
|
|
8735
|
-
const
|
|
8736
|
-
const vectorCoverage = buildCoverage(totalPartitions, routing.unavailablePartitions.length, vectorOutcomes);
|
|
8737
|
-
const coverage = worstCaseCoverage(textCoverage, vectorCoverage);
|
|
9590
|
+
const coverage = buildCoverage(totalPartitions, routing.unavailablePartitions, textOutcomes, vectorOutcomes);
|
|
8738
9591
|
if (!config.allowPartialResults) {
|
|
8739
9592
|
const failedCount = coverage.timedOutPartitions + coverage.failedPartitions;
|
|
8740
9593
|
if (failedCount > 0) {
|
|
@@ -8796,29 +9649,6 @@ async function executeHybridQuery(indexName, params, limit, offset, facetShardSi
|
|
|
8796
9649
|
coverage
|
|
8797
9650
|
};
|
|
8798
9651
|
}
|
|
8799
|
-
function worstCaseCoverage(a, b) {
|
|
8800
|
-
const failed = Math.max(a.failedPartitions, b.failedPartitions);
|
|
8801
|
-
const timedOut = Math.max(a.timedOutPartitions, b.timedOutPartitions);
|
|
8802
|
-
const queried = Math.max(0, a.totalPartitions - failed - timedOut);
|
|
8803
|
-
return {
|
|
8804
|
-
totalPartitions: a.totalPartitions,
|
|
8805
|
-
queriedPartitions: queried,
|
|
8806
|
-
timedOutPartitions: timedOut,
|
|
8807
|
-
failedPartitions: failed
|
|
8808
|
-
};
|
|
8809
|
-
}
|
|
8810
|
-
async function activeAllocation(deps, indexName) {
|
|
8811
|
-
const allocation = await deps.config.coordinator.getAllocation(indexName);
|
|
8812
|
-
if (allocation === null || allocation.assignments.size === 0) {
|
|
8813
|
-
return null;
|
|
8814
|
-
}
|
|
8815
|
-
for (const [, assignment] of allocation.assignments) {
|
|
8816
|
-
if (assignment.state === "ACTIVE") {
|
|
8817
|
-
return allocation;
|
|
8818
|
-
}
|
|
8819
|
-
}
|
|
8820
|
-
return null;
|
|
8821
|
-
}
|
|
8822
9652
|
function strictScatterGroups(allocation, indexName) {
|
|
8823
9653
|
const routing = selectReplicasForQuery(allocation, randomSelector);
|
|
8824
9654
|
if (routing.unavailablePartitions.length > 0) {
|
|
@@ -8830,8 +9660,30 @@ function strictScatterGroups(allocation, indexName) {
|
|
|
8830
9660
|
}
|
|
8831
9661
|
return Array.from(routing.nodeToPartitions.entries(), ([nodeId, partitionIds]) => ({ nodeId, partitionIds }));
|
|
8832
9662
|
}
|
|
9663
|
+
var READ_FAILURE_BY_TRANSPORT_CODE = {
|
|
9664
|
+
[TransportErrorCodes.CONNECT_FAILED]: ErrorCodes.QUERY_NO_ACTIVE_REPLICA,
|
|
9665
|
+
[TransportErrorCodes.PEER_UNAVAILABLE]: ErrorCodes.QUERY_NO_ACTIVE_REPLICA,
|
|
9666
|
+
[TransportErrorCodes.TIMEOUT]: ErrorCodes.QUERY_NODE_TIMEOUT,
|
|
9667
|
+
[TransportErrorCodes.MESSAGE_TOO_LARGE]: ErrorCodes.QUERY_ROUTING_FAILED,
|
|
9668
|
+
[TransportErrorCodes.DECODE_FAILED]: ErrorCodes.QUERY_ROUTING_FAILED
|
|
9669
|
+
};
|
|
9670
|
+
function readFailure(error, targetNodeId, indexName) {
|
|
9671
|
+
if (error instanceof NarsilError) {
|
|
9672
|
+
return error;
|
|
9673
|
+
}
|
|
9674
|
+
if (error instanceof TransportError) {
|
|
9675
|
+
return new NarsilError(
|
|
9676
|
+
READ_FAILURE_BY_TRANSPORT_CODE[error.code],
|
|
9677
|
+
`Node '${targetNodeId}' did not answer a read request for index '${indexName}'`,
|
|
9678
|
+
{ indexName, targetNodeId, transportCode: error.code }
|
|
9679
|
+
);
|
|
9680
|
+
}
|
|
9681
|
+
return error;
|
|
9682
|
+
}
|
|
8833
9683
|
async function sendReadRequest(deps, targetNodeId, message, indexName, validate) {
|
|
8834
|
-
const response = await sendToNode2(deps.config, targetNodeId, message)
|
|
9684
|
+
const response = await sendToNode2(deps.config, targetNodeId, message).catch((error) => {
|
|
9685
|
+
throw readFailure(error, targetNodeId, indexName);
|
|
9686
|
+
});
|
|
8835
9687
|
const decoded = decode(response.payload);
|
|
8836
9688
|
if (response.type.endsWith(".error") || response.type === "error" || decoded.error === true) {
|
|
8837
9689
|
throw new NarsilError(
|
|
@@ -8890,7 +9742,7 @@ async function gatherPartitionCounts(deps, indexName, allocation) {
|
|
|
8890
9742
|
return { countsByPartition, language };
|
|
8891
9743
|
}
|
|
8892
9744
|
async function countCluster(deps, indexName) {
|
|
8893
|
-
const allocation = await
|
|
9745
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
8894
9746
|
if (allocation === null) {
|
|
8895
9747
|
return deps.engine.countDocuments(indexName);
|
|
8896
9748
|
}
|
|
@@ -8902,7 +9754,7 @@ async function countCluster(deps, indexName) {
|
|
|
8902
9754
|
return total;
|
|
8903
9755
|
}
|
|
8904
9756
|
async function statsCluster(deps, indexName) {
|
|
8905
|
-
const allocation = await
|
|
9757
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
8906
9758
|
if (allocation === null) {
|
|
8907
9759
|
return deps.engine.getStats(indexName);
|
|
8908
9760
|
}
|
|
@@ -8928,7 +9780,7 @@ async function statsCluster(deps, indexName) {
|
|
|
8928
9780
|
};
|
|
8929
9781
|
}
|
|
8930
9782
|
async function partitionStatsCluster(deps, indexName) {
|
|
8931
|
-
const allocation = await
|
|
9783
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
8932
9784
|
if (allocation === null) {
|
|
8933
9785
|
return deps.engine.getPartitionStats(indexName);
|
|
8934
9786
|
}
|
|
@@ -8982,7 +9834,7 @@ async function gatherLocalPage(deps, indexName, payload, partitionIds, sortField
|
|
|
8982
9834
|
return { entries, total: result.total, hasMore: result.cursor !== null };
|
|
8983
9835
|
}
|
|
8984
9836
|
async function listCluster(deps, indexName, params) {
|
|
8985
|
-
const allocation = await
|
|
9837
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
8986
9838
|
if (allocation === null) {
|
|
8987
9839
|
return deps.engine.listDocuments(indexName, params);
|
|
8988
9840
|
}
|
|
@@ -9069,7 +9921,7 @@ function suggestNodeLimit(clientLimit) {
|
|
|
9069
9921
|
return Math.min(oversampled, MAX_SUGGEST_SCATTER_LIMIT);
|
|
9070
9922
|
}
|
|
9071
9923
|
async function suggestCluster(deps, indexName, params) {
|
|
9072
|
-
const allocation = await
|
|
9924
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9073
9925
|
if (allocation === null) {
|
|
9074
9926
|
return deps.engine.suggest(indexName, params);
|
|
9075
9927
|
}
|
|
@@ -9113,7 +9965,7 @@ async function suggestCluster(deps, indexName, params) {
|
|
|
9113
9965
|
return result;
|
|
9114
9966
|
}
|
|
9115
9967
|
async function preflightCluster(deps, indexName, params) {
|
|
9116
|
-
const allocation = await
|
|
9968
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9117
9969
|
if (allocation === null) {
|
|
9118
9970
|
return deps.engine.preflight(indexName, params);
|
|
9119
9971
|
}
|
|
@@ -9148,14 +10000,27 @@ async function preflightCluster(deps, indexName, params) {
|
|
|
9148
10000
|
|
|
9149
10001
|
// src/distribution/cluster-node/reads/index.ts
|
|
9150
10002
|
async function readClusterDocuments(deps, indexName, docIds) {
|
|
9151
|
-
const allocation = await
|
|
10003
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9152
10004
|
if (allocation === null) {
|
|
9153
10005
|
return deps.engine.getMultiple(indexName, docIds);
|
|
9154
10006
|
}
|
|
9155
10007
|
return readDistributedDocuments(deps.config, deps.nodeId, deps.engine, indexName, docIds, allocation);
|
|
9156
10008
|
}
|
|
10009
|
+
function routingConfig(query) {
|
|
10010
|
+
if (query === void 0) {
|
|
10011
|
+
return void 0;
|
|
10012
|
+
}
|
|
10013
|
+
const config = {};
|
|
10014
|
+
if (query.allowPartialResults !== void 0) {
|
|
10015
|
+
config.allowPartialResults = query.allowPartialResults;
|
|
10016
|
+
}
|
|
10017
|
+
if (query.partitionTimeout !== void 0) {
|
|
10018
|
+
config.partitionTimeout = query.partitionTimeout;
|
|
10019
|
+
}
|
|
10020
|
+
return config;
|
|
10021
|
+
}
|
|
9157
10022
|
async function queryCluster(deps, indexName, params) {
|
|
9158
|
-
const allocation = await
|
|
10023
|
+
const allocation = await routableAllocation(deps.config.coordinator, indexName);
|
|
9159
10024
|
if (allocation === null) {
|
|
9160
10025
|
return deps.engine.query(indexName, params);
|
|
9161
10026
|
}
|
|
@@ -9166,7 +10031,7 @@ async function queryCluster(deps, indexName, params) {
|
|
|
9166
10031
|
getAllocation: (idx) => deps.config.coordinator.getAllocation(idx),
|
|
9167
10032
|
resolveNodeTargets: deps.resolveNodeTargets
|
|
9168
10033
|
};
|
|
9169
|
-
const distributed = await distributedQuery(indexName, wireParams, queryDeps);
|
|
10034
|
+
const distributed = await distributedQuery(indexName, wireParams, queryDeps, routingConfig(deps.config.query));
|
|
9170
10035
|
const documents = await fetchDistributedDocuments(
|
|
9171
10036
|
deps.config,
|
|
9172
10037
|
deps.nodeId,
|
|
@@ -9182,7 +10047,7 @@ async function queryCluster(deps, indexName, params) {
|
|
|
9182
10047
|
// src/distribution/cluster-node/clear.ts
|
|
9183
10048
|
var CLEAR_PAGE_SIZE = 1e3;
|
|
9184
10049
|
async function clearCluster(readDeps, writeDeps, indexName) {
|
|
9185
|
-
const allocation = await
|
|
10050
|
+
const allocation = await routableAllocation(readDeps.config.coordinator, indexName);
|
|
9186
10051
|
if (allocation === null) {
|
|
9187
10052
|
return readDeps.engine.clear(indexName);
|
|
9188
10053
|
}
|
|
@@ -9292,6 +10157,53 @@ function createClusterNodeOperations(deps) {
|
|
|
9292
10157
|
};
|
|
9293
10158
|
}
|
|
9294
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
|
+
|
|
9295
10207
|
// src/distribution/replication/types.ts
|
|
9296
10208
|
var DEFAULT_LOG_RETENTION_BYTES = 268435456;
|
|
9297
10209
|
|
|
@@ -9527,14 +10439,29 @@ function seedReplicationLog(replicationLogs, indexName, partitionId, startSeqNo,
|
|
|
9527
10439
|
})
|
|
9528
10440
|
);
|
|
9529
10441
|
}
|
|
9530
|
-
|
|
9531
|
-
// src/distribution/cluster-node/transport-listener.ts
|
|
9532
10442
|
var CONTROLLER_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
9533
10443
|
ReplicationMessageTypes.INSYNC_ADD,
|
|
9534
10444
|
ReplicationMessageTypes.INSYNC_REMOVE,
|
|
9535
10445
|
ClusterMessageTypes.BOOTSTRAP_COMPLETE
|
|
9536
10446
|
]);
|
|
9537
|
-
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) {
|
|
9538
10465
|
let controllerHandler = null;
|
|
9539
10466
|
const transport = {
|
|
9540
10467
|
send(target, message) {
|
|
@@ -9561,9 +10488,17 @@ function createMultiplexedControllerTransport(baseTransport) {
|
|
|
9561
10488
|
createHandler(dataHandler) {
|
|
9562
10489
|
return async (message, respond) => {
|
|
9563
10490
|
if (CONTROLLER_MESSAGE_TYPES.has(message.type)) {
|
|
9564
|
-
if (controllerHandler
|
|
9565
|
-
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;
|
|
9566
10500
|
}
|
|
10501
|
+
await controllerHandler(message, createSingleResponseRespond(respond));
|
|
9567
10502
|
return;
|
|
9568
10503
|
}
|
|
9569
10504
|
await dataHandler(message, respond);
|
|
@@ -9584,6 +10519,7 @@ function createSingleResponseRespond(respond) {
|
|
|
9584
10519
|
|
|
9585
10520
|
// src/distribution/cluster-node/validate-config.ts
|
|
9586
10521
|
var MAX_WAIT_FOR_ACTIVE_REPLICAS = 255;
|
|
10522
|
+
var MIN_CONTROLLER_LEASE_TTL_MS = 1e3;
|
|
9587
10523
|
function validateClusterNodeConfig(config) {
|
|
9588
10524
|
if (config.address.length === 0) {
|
|
9589
10525
|
throw new NarsilError(ErrorCodes.CONFIG_INVALID, "ClusterNodeConfig.address must not be empty");
|
|
@@ -9627,6 +10563,42 @@ function validateClusterNodeConfig(config) {
|
|
|
9627
10563
|
);
|
|
9628
10564
|
}
|
|
9629
10565
|
}
|
|
10566
|
+
if (config.query !== void 0) {
|
|
10567
|
+
const { allowPartialResults, partitionTimeout } = config.query;
|
|
10568
|
+
if (allowPartialResults !== void 0 && typeof allowPartialResults !== "boolean") {
|
|
10569
|
+
throw new NarsilError(
|
|
10570
|
+
ErrorCodes.CONFIG_INVALID,
|
|
10571
|
+
"ClusterNodeConfig.query.allowPartialResults must be a boolean",
|
|
10572
|
+
{
|
|
10573
|
+
allowPartialResults
|
|
10574
|
+
}
|
|
10575
|
+
);
|
|
10576
|
+
}
|
|
10577
|
+
if (partitionTimeout !== void 0 && (!Number.isInteger(partitionTimeout) || partitionTimeout <= 0)) {
|
|
10578
|
+
throw new NarsilError(
|
|
10579
|
+
ErrorCodes.CONFIG_INVALID,
|
|
10580
|
+
"ClusterNodeConfig.query.partitionTimeout must be a positive integer",
|
|
10581
|
+
{ partitionTimeout }
|
|
10582
|
+
);
|
|
10583
|
+
}
|
|
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
|
+
}
|
|
9630
10602
|
}
|
|
9631
10603
|
|
|
9632
10604
|
// src/distribution/cluster-node/index.ts
|
|
@@ -9652,13 +10624,8 @@ async function createClusterNode(config) {
|
|
|
9652
10624
|
async function resolveNodeTargets3(targetNodeId) {
|
|
9653
10625
|
return resolveNodeTargets2(config, targetNodeId);
|
|
9654
10626
|
}
|
|
9655
|
-
const forwardOnError = (
|
|
9656
|
-
|
|
9657
|
-
return;
|
|
9658
|
-
}
|
|
9659
|
-
const wrapped = error instanceof Error ? error : new Error(String(error));
|
|
9660
|
-
config.onError(wrapped);
|
|
9661
|
-
};
|
|
10627
|
+
const forwardOnError = createErrorForwarder(config.onError);
|
|
10628
|
+
const forwardAllocationError = createAllocationErrorForwarder(forwardOnError);
|
|
9662
10629
|
const registration = {
|
|
9663
10630
|
nodeId,
|
|
9664
10631
|
address: config.address,
|
|
@@ -9669,7 +10636,7 @@ async function createClusterNode(config) {
|
|
|
9669
10636
|
};
|
|
9670
10637
|
const hasDataRole = roles.includes("data");
|
|
9671
10638
|
const hasControllerRole = roles.includes("controller");
|
|
9672
|
-
const controllerTransport = hasDataRole
|
|
10639
|
+
const controllerTransport = hasDataRole ? createMultiplexedControllerTransport(config.transport, nodeId) : null;
|
|
9673
10640
|
const writeDeps = {
|
|
9674
10641
|
nodeId,
|
|
9675
10642
|
coordinator: config.coordinator,
|
|
@@ -9683,9 +10650,16 @@ async function createClusterNode(config) {
|
|
|
9683
10650
|
waitForActiveReplicas: config.replication?.waitForActiveReplicas
|
|
9684
10651
|
};
|
|
9685
10652
|
const orphanedIndexes = /* @__PURE__ */ new Set();
|
|
9686
|
-
|
|
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
|
+
}
|
|
9687
10659
|
let controller = null;
|
|
9688
10660
|
let unregisterHandler = null;
|
|
10661
|
+
let unregisterNotReadyHandler = null;
|
|
10662
|
+
let serving = false;
|
|
9689
10663
|
let isShutdown = false;
|
|
9690
10664
|
let activeOps = 0;
|
|
9691
10665
|
let drainResolve = null;
|
|
@@ -9701,76 +10675,59 @@ async function createClusterNode(config) {
|
|
|
9701
10675
|
restoreReplicationPartition: engine.restoreReplicationPartition,
|
|
9702
10676
|
onError: forwardOnError
|
|
9703
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
|
+
};
|
|
9704
10686
|
async function bootstrapPartition2(indexName, partitionId, primaryNodeId) {
|
|
9705
|
-
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);
|
|
9706
10688
|
if (succeeded) {
|
|
9707
10689
|
await adoptClusterIdentity(engine, config.coordinator, indexName);
|
|
10690
|
+
await engine.recordHeldPartition(indexName, partitionId);
|
|
9708
10691
|
}
|
|
9709
10692
|
return succeeded;
|
|
9710
10693
|
}
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
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
|
|
9733
10718
|
});
|
|
9734
|
-
} catch (error) {
|
|
9735
|
-
if (!(error instanceof NarsilError) || error.code !== ErrorCodes.INDEX_ALREADY_EXISTS) {
|
|
9736
|
-
forwardOnError(error);
|
|
9737
|
-
return false;
|
|
9738
|
-
}
|
|
9739
10719
|
}
|
|
9740
|
-
|
|
9741
|
-
}
|
|
9742
|
-
if (hasDataRole) {
|
|
9743
|
-
lifecycle = createDataNodeLifecycle({
|
|
9744
|
-
registration,
|
|
9745
|
-
coordinator: config.coordinator,
|
|
9746
|
-
transport: config.transport,
|
|
9747
|
-
knownIndexNames: [],
|
|
9748
|
-
bootstrapRetryBaseMs: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapRetryBaseMs,
|
|
9749
|
-
bootstrapRetryMaxMs: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapRetryMaxMs,
|
|
9750
|
-
bootstrapMaxRetries: DEFAULT_NODE_LIFECYCLE_CONFIG.bootstrapMaxRetries,
|
|
9751
|
-
allocationDebounceMs: DEFAULT_NODE_LIFECYCLE_CONFIG.allocationDebounceMs,
|
|
9752
|
-
nodeHeartbeatIntervalMs: DEFAULT_NODE_LIFECYCLE_CONFIG.nodeHeartbeatIntervalMs,
|
|
9753
|
-
onBootstrapPartition: bootstrapPartition2,
|
|
9754
|
-
onRemovePartition: (indexName, partitionId) => {
|
|
9755
|
-
clearBootstrapSyncIndex(bootstrapSyncState, indexName, partitionId);
|
|
9756
|
-
replicationLogs.delete(replicationLogKey2(indexName, partitionId));
|
|
9757
|
-
void cleanupRemovedPartition(indexName, partitionId, {
|
|
9758
|
-
engine,
|
|
9759
|
-
coordinator: config.coordinator,
|
|
9760
|
-
nodeId,
|
|
9761
|
-
onError: forwardOnError
|
|
9762
|
-
});
|
|
9763
|
-
}
|
|
9764
|
-
});
|
|
9765
|
-
}
|
|
10720
|
+
});
|
|
9766
10721
|
if (hasControllerRole) {
|
|
9767
10722
|
controller = createController({
|
|
9768
10723
|
nodeId,
|
|
9769
10724
|
coordinator: config.coordinator,
|
|
9770
10725
|
transport: controllerTransport?.transport ?? config.transport,
|
|
9771
|
-
leaseTtlMs: DEFAULT_CONTROLLER_CONFIG.leaseTtlMs,
|
|
9772
|
-
standbyRetryMs: DEFAULT_CONTROLLER_CONFIG.standbyRetryMs,
|
|
9773
|
-
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
|
|
9774
10731
|
});
|
|
9775
10732
|
}
|
|
9776
10733
|
function guardShutdown() {
|
|
@@ -9795,21 +10752,16 @@ async function createClusterNode(config) {
|
|
|
9795
10752
|
}
|
|
9796
10753
|
}
|
|
9797
10754
|
const readDeps = { config, nodeId, engine, resolveNodeTargets: resolveNodeTargets3 };
|
|
9798
|
-
const cluster = {
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
return false;
|
|
9809
|
-
}
|
|
9810
|
-
return controller.isActive;
|
|
9811
|
-
}
|
|
9812
|
-
};
|
|
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
|
+
});
|
|
9813
10765
|
const node = {
|
|
9814
10766
|
get nodeId() {
|
|
9815
10767
|
return nodeId;
|
|
@@ -9843,9 +10795,10 @@ async function createClusterNode(config) {
|
|
|
9843
10795
|
orphanedIndexes.add(indexName);
|
|
9844
10796
|
}
|
|
9845
10797
|
}
|
|
9846
|
-
if (
|
|
9847
|
-
await
|
|
10798
|
+
if (hasDataRole) {
|
|
10799
|
+
unregisterNotReadyHandler = await config.transport.listen(createNotReadyHandler(nodeId));
|
|
9848
10800
|
}
|
|
10801
|
+
await lifecycle.join();
|
|
9849
10802
|
if (controller !== null) {
|
|
9850
10803
|
await controller.start();
|
|
9851
10804
|
}
|
|
@@ -9863,6 +10816,7 @@ async function createClusterNode(config) {
|
|
|
9863
10816
|
unregisterHandler = await config.transport.listen(listener);
|
|
9864
10817
|
startCatchUpPump(writeDeps.catchUp, writeDeps);
|
|
9865
10818
|
}
|
|
10819
|
+
serving = true;
|
|
9866
10820
|
},
|
|
9867
10821
|
async shutdown() {
|
|
9868
10822
|
await stopCatchUpPump(writeDeps.catchUp);
|
|
@@ -9870,6 +10824,7 @@ async function createClusterNode(config) {
|
|
|
9870
10824
|
return;
|
|
9871
10825
|
}
|
|
9872
10826
|
isShutdown = true;
|
|
10827
|
+
serving = false;
|
|
9873
10828
|
if (activeOps > 0) {
|
|
9874
10829
|
await new Promise((resolve) => {
|
|
9875
10830
|
drainResolve = resolve;
|
|
@@ -9879,12 +10834,15 @@ async function createClusterNode(config) {
|
|
|
9879
10834
|
unregisterHandler();
|
|
9880
10835
|
unregisterHandler = null;
|
|
9881
10836
|
}
|
|
9882
|
-
if (
|
|
9883
|
-
|
|
10837
|
+
if (unregisterNotReadyHandler !== null) {
|
|
10838
|
+
unregisterNotReadyHandler();
|
|
10839
|
+
unregisterNotReadyHandler = null;
|
|
9884
10840
|
}
|
|
10841
|
+
await lifecycle.shutdown();
|
|
9885
10842
|
if (controller !== null) {
|
|
9886
10843
|
await controller.shutdown();
|
|
9887
10844
|
}
|
|
10845
|
+
await Promise.all([...heldPartitionWrites]);
|
|
9888
10846
|
await engine.shutdown();
|
|
9889
10847
|
}
|
|
9890
10848
|
};
|