querysub 0.426.0 → 0.428.0
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/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { measureFnc } from "socket-function/src/profiling/measure";
|
|
|
10
10
|
import { getRoutingOverride, hasPrefixHash } from "./PathRouterRouteOverride";
|
|
11
11
|
import { sha256 } from "js-sha256";
|
|
12
12
|
import { rangesOverlap, removeRange } from "../rangeMath";
|
|
13
|
+
import { decodeParentFilter } from "./hackedPackedPathParentFiltering";
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
// Cases
|
|
@@ -57,6 +58,19 @@ function matchesPrefix(matcher: PrefixMatcher, path: string): boolean {
|
|
|
57
58
|
return false;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
61
|
+
if (getPathIndex(path, matcher.childKeyIndex) === undefined) return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
// Checks if they're asking for the exact prefix, which would mean that the children would be the direct children of this prefix matcher.
|
|
65
|
+
function isPrefixParent(matcher: PrefixMatcher, path: string): boolean {
|
|
66
|
+
if (!path.startsWith(matcher.prefix)) return false;
|
|
67
|
+
let partsA = getPathFromStr(matcher.originalPrefix);
|
|
68
|
+
let partsB = getPathFromStr(path);
|
|
69
|
+
for (let i = 0; i < partsA.length; i++) {
|
|
70
|
+
// Empty string is wildcard
|
|
71
|
+
if (partsA[i] === "") continue;
|
|
72
|
+
if (partsB[i] !== partsA[i]) return false;
|
|
73
|
+
}
|
|
60
74
|
return true;
|
|
61
75
|
}
|
|
62
76
|
/** Empty string path part becomes a wildcard */
|
|
@@ -535,11 +549,17 @@ export class PathRouter {
|
|
|
535
549
|
range: { start: number; end: number };
|
|
536
550
|
}[];
|
|
537
551
|
} {
|
|
552
|
+
let parentRange = decodeParentFilter(path) || {
|
|
553
|
+
start: 0,
|
|
554
|
+
end: 1,
|
|
555
|
+
};
|
|
556
|
+
path = hack_stripPackedPath(path);
|
|
538
557
|
let preferredNodeIds = new Set(config?.preferredNodeIds ?? []);
|
|
539
558
|
|
|
540
559
|
// If a prefix is a parent of path, then it is the same as matching just the path directly
|
|
541
560
|
// (If our prefix directly equals one of the other matches, then it's more complicated, As then, the child keys of path are what is hashed, and so all the children will have different routes, so we might match multiple nodes. The same thing if we're matching the remaining case, in which case it's a full path hash, so the child key matters, and again, different routes).
|
|
542
561
|
// - The different route case is how the FuntionRunner works, and without it large databases couldn't run functions. However, most applications won't directly use it.
|
|
562
|
+
// NOTE: The only own nodes flag is actually so we can access this before the topology finishes synchronizing. Because we want to be able to call get child read nodes from path value core for some really basic stuff. It also is what the caller wants, but that's not a good enough reason to add this check. The reason we have this check is because without it, the topology won't have finished synchronizing and startup won't work.
|
|
543
563
|
let allSources = config?.onlyOwnNodes ? [{ nodeId: getOwnNodeId(), authoritySpec: authorityLookup.getOurSpec() }] : authorityLookup.getTopologySync();
|
|
544
564
|
// Prefer our own node
|
|
545
565
|
sort(allSources, x => isOwnNodeId(x.nodeId) ? -1 : 1);
|
|
@@ -549,14 +569,13 @@ export class PathRouter {
|
|
|
549
569
|
|
|
550
570
|
|
|
551
571
|
// Direct prefix. This happens for things like calls and functions, it requires more advanced routing as it means we're going to route between multiple servers, but... it is important
|
|
552
|
-
let hasPrefix = allSources.filter(x => x.authoritySpec.prefixes.some(y => y
|
|
572
|
+
let hasPrefix = allSources.filter(x => x.authoritySpec.prefixes.some(y => isPrefixParent(y, path))).map(x => x.authoritySpec);
|
|
553
573
|
if (hasPrefix.length > 0) {
|
|
554
574
|
shuffle(hasPrefix, Math.random());
|
|
555
575
|
sort(hasPrefix, x => preferredNodeIds.has(x.nodeId) ? -1 : 1);
|
|
556
576
|
|
|
557
577
|
let missingRanges: { start: number; end: number }[] = [{
|
|
558
|
-
|
|
559
|
-
end: 1,
|
|
578
|
+
...parentRange
|
|
560
579
|
}];
|
|
561
580
|
let usedParts: {
|
|
562
581
|
nodeId: string;
|
|
@@ -585,7 +604,7 @@ export class PathRouter {
|
|
|
585
604
|
let nestedMatches = allSources.filter(x => {
|
|
586
605
|
// There's nested prefixes, so if we match any prefix explicitly, we can't just take one of the previous prefixes because that isn't how the hashing will work.
|
|
587
606
|
// - This happens if it's a direct match, but one of the shards is down, in which case we can't get a full match.
|
|
588
|
-
if (x.authoritySpec.prefixes.some(y => y
|
|
607
|
+
if (x.authoritySpec.prefixes.some(y => isPrefixParent(y, path))) return false;
|
|
589
608
|
|
|
590
609
|
// If our path, which we're going to read the children of, is the child of another path, then it means in that other path, the child key will be known to us constant, and so we're going to match exactly one authority.
|
|
591
610
|
return (
|
|
@@ -645,13 +664,11 @@ export class PathRouter {
|
|
|
645
664
|
}
|
|
646
665
|
|
|
647
666
|
|
|
648
|
-
|
|
649
|
-
|
|
650
667
|
if (!config?.onlyOwnNodes) {
|
|
651
668
|
// TODO: We could maybe match a partial match. However, even that is suspect. The site being partially broken is almost worse than it being completely broken. We should just get ALL the shards running again...
|
|
652
669
|
|
|
653
670
|
// NOTE: We *could* actually synchronize it even if it doesn't have a prefix shard as we can fall back to just the full path sharding. However, it becomes very complicated if we want a specific range, and then it becomes complicated if it then switches to prefix hashing (With the nodes that were using the full path hashing slowly going away). AND... key synchronization IS slow, so it's good to discourage it in general.
|
|
654
|
-
console.error(`Want to sync a prefix which is not under an existing prefix, nor equal to a prefix. 1) The servers are down. 2) Don't access the .keys() 3) call addRoutingPrefixForDeploy to add a route/parent route explicitly (as is done in PathFunctionRunner.ts). Path: ${JSON.stringify(path)}`, { path, allSources });
|
|
671
|
+
console.error(`Want to sync a prefix which is not under an existing prefix, nor equal to a prefix. 1) The servers are down. 2) Don't access the .keys() 3) call addRoutingPrefixForDeploy to add a route/parent route explicitly (as is done in PathFunctionRunner.ts). Path: ${JSON.stringify(path)}`, { path, allSources: allSources.map(x => x.authoritySpec) });
|
|
655
672
|
}
|
|
656
673
|
return { nodes: [] };
|
|
657
674
|
}
|
|
@@ -369,8 +369,10 @@ class PathWatcher {
|
|
|
369
369
|
|
|
370
370
|
// initialTriggers.parentPaths => triggerPaths
|
|
371
371
|
for (let parentPath of initialTriggers.parentPaths) {
|
|
372
|
-
|
|
372
|
+
// NOTE: We have to trigger all of the values without the hacked path restriction. This should be safe, if a little bit inefficient, because we're still not going to send the values to watchers who aren't watching them. But we need to trigger all of it because it might be the case that we had synchronized 99% of the path and someone was watching 100% of the path and we just finished synchronizing the last 1%. So the parent path trigger that we received only has 1%, but we need to send everything. And it's too complicated to determine exactly what we need to send as in the intersection of all the watchers. So it's easier to just trigger all the value paths and what they're watching will naturally be sent.
|
|
373
|
+
let valuePaths = authorityStorage.getPathsFromParent(hack_stripPackedPath(parentPath));
|
|
373
374
|
for (let valuePath of valuePaths || []) {
|
|
375
|
+
if (!authorityStorage.isSynced(valuePath)) continue;
|
|
374
376
|
// IMPORTANT! Add all the values to the initial triggers so then later we not only trigger them but know their initial trigger so we can make sure that the is initial trigger logic for each value runs as well.
|
|
375
377
|
initialTriggers.values.add(valuePath);
|
|
376
378
|
triggerPaths.add(valuePath);
|
|
@@ -378,7 +380,9 @@ class PathWatcher {
|
|
|
378
380
|
|
|
379
381
|
let latestParentWatches = this.parentWatchers.get(hack_stripPackedPath(parentPath));
|
|
380
382
|
if (!latestParentWatches) continue;
|
|
381
|
-
for (let { watchers } of latestParentWatches
|
|
383
|
+
for (let [packedPath, { watchers }] of latestParentWatches) {
|
|
384
|
+
// ONLY trigger, if packedPath is fully synchronized
|
|
385
|
+
if (!authorityStorage.isParentSynced(packedPath)) continue;
|
|
382
386
|
for (let watcher of watchers) {
|
|
383
387
|
if (onlyTriggerNodeId && watcher !== onlyTriggerNodeId) continue;
|
|
384
388
|
let changes = changedPerCallbacks.get(watcher);
|
|
@@ -386,7 +390,7 @@ class PathWatcher {
|
|
|
386
390
|
changes = { values: new Set(), initialTriggers: { values: new Set(), parentPaths: new Set() } };
|
|
387
391
|
changedPerCallbacks.set(watcher, changes);
|
|
388
392
|
}
|
|
389
|
-
changes.initialTriggers.parentPaths.add(
|
|
393
|
+
changes.initialTriggers.parentPaths.add(packedPath);
|
|
390
394
|
}
|
|
391
395
|
}
|
|
392
396
|
}
|
|
@@ -609,11 +609,12 @@ class AuthorityPathValueStorage {
|
|
|
609
609
|
return false;
|
|
610
610
|
}
|
|
611
611
|
public isParentSynced(path: string) {
|
|
612
|
+
let originalPath = path;
|
|
612
613
|
let range = decodeParentFilter(path);
|
|
613
614
|
path = hack_stripPackedPath(path);
|
|
614
615
|
if (PathRouter.isLocalPath(path)) return true;
|
|
615
616
|
|
|
616
|
-
let synced = this.parentsSynced.get(path);
|
|
617
|
+
let synced = this.parentsSynced.get(originalPath) || this.parentsSynced.get(path);
|
|
617
618
|
if (synced === true) return true;
|
|
618
619
|
// See if the ranges received so far cover the requested range. If we only request a partial range, then this will always be the case. We'll never fully synchronize the path.
|
|
619
620
|
if (synced && range) {
|
|
@@ -626,24 +627,11 @@ class AuthorityPathValueStorage {
|
|
|
626
627
|
|
|
627
628
|
// Self authority check
|
|
628
629
|
{
|
|
629
|
-
let nodes = PathRouter.getChildReadNodes(
|
|
630
|
-
if (nodes.nodes.length > 0) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
return true;
|
|
635
|
-
}
|
|
636
|
-
// Might be a partial authority
|
|
637
|
-
if (range) {
|
|
638
|
-
let selfNodes = nodes.nodes.filter(x => isOwnNodeId(x.nodeId));
|
|
639
|
-
let missingRanges: { start: number; end: number }[] = [{ start: range.start, end: range.end }];
|
|
640
|
-
for (let range of selfNodes) {
|
|
641
|
-
removeRange(missingRanges, range.range);
|
|
642
|
-
}
|
|
643
|
-
if (missingRanges.length === 0) {
|
|
644
|
-
return true;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
630
|
+
let nodes = PathRouter.getChildReadNodes(originalPath, { onlyOwnNodes: true });
|
|
631
|
+
if (nodes.nodes.length > 0 && nodes.nodes.every(x => isOwnNodeId(x.nodeId))) {
|
|
632
|
+
// If we're the self authority of it, it's not going to change, so we can just cache it safely.
|
|
633
|
+
this.parentsSynced.set(originalPath, true);
|
|
634
|
+
return true;
|
|
647
635
|
}
|
|
648
636
|
}
|
|
649
637
|
|