querysub 0.430.0 → 0.431.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.430.0",
3
+ "version": "0.431.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -80,7 +80,11 @@ interface DataSettings {
80
80
  | 1
81
81
  // Add transactionPaths support
82
82
  | 2
83
+ // Separate buffers for each data type with bufferMap
84
+ | 3
83
85
  );
86
+ // Parallel map with buffers defining their type (for version 3+)
87
+ bufferMap?: ("pathValues" | "readLocks" | "transactionPaths" | "values" | "strings")[];
84
88
  }
85
89
 
86
90
  interface PathValueStructure {
@@ -454,14 +458,16 @@ class PathValueSerializer {
454
458
  compress?: boolean;
455
459
  singleBuffer?: boolean;
456
460
  stripSource?: boolean;
461
+ noTransactionPaths?: boolean;
457
462
  }): Promise<Buffer[]> {
458
- const version = 2;
463
+ const version = 3;
459
464
  let settings: DataSettings = {
460
465
  valueCount: values.length,
461
466
  noLocks: config?.noLocks,
462
467
  compression: config?.compress ? "lz4" : undefined,
463
468
  stripSource: config?.stripSource,
464
469
  version,
470
+ bufferMap: [],
465
471
  };
466
472
 
467
473
  let totalSize = 0;
@@ -469,18 +475,37 @@ class PathValueSerializer {
469
475
  let outputBuffers: Buffer[] = [];
470
476
  let currentBuffer = Buffer.alloc(currentBufferDefaultSize);
471
477
  let currentBufferPos = 0;
478
+ let currentBufferType: "pathValues" | "readLocks" | "transactionPaths" | "values" | undefined;
479
+
472
480
  function ensureBytes(count: number) {
473
481
  if (currentBufferPos + count < currentBuffer.length) return;
474
482
  if (currentBufferPos !== currentBuffer.length) {
475
483
  currentBuffer = currentBuffer.slice(0, currentBufferPos);
476
484
  }
477
485
  outputBuffers.push(currentBuffer);
486
+ if (currentBufferType) {
487
+ settings.bufferMap!.push(currentBufferType);
488
+ }
478
489
  totalSize += currentBufferPos;
479
490
  currentBufferDefaultSize = Math.min(MAX_BUFFER_SIZE, Math.max(currentBufferDefaultSize, totalSize * 0.25));
480
491
  currentBuffer = Buffer.alloc(Math.max(currentBufferDefaultSize, count));
481
492
  currentBufferPos = 0;
482
493
  }
483
494
 
495
+ function startNewBuffer(bufferType: "pathValues" | "readLocks" | "transactionPaths" | "values") {
496
+ if (currentBufferPos > 0) {
497
+ outputBuffers.push(currentBuffer.slice(0, currentBufferPos));
498
+ if (currentBufferType) {
499
+ settings.bufferMap!.push(currentBufferType);
500
+ }
501
+ }
502
+ currentBufferType = bufferType;
503
+ totalSize += currentBufferPos;
504
+ currentBufferDefaultSize = Math.min(MAX_BUFFER_SIZE, Math.max(currentBufferDefaultSize, totalSize * 0.25));
505
+ currentBuffer = Buffer.alloc(currentBufferDefaultSize);
506
+ currentBufferPos = 0;
507
+ }
508
+
484
509
  let stringDictionary = new Map<string, number>();
485
510
  let writer: Writer = {
486
511
  writeFloat64(value) {
@@ -506,21 +531,6 @@ class PathValueSerializer {
506
531
  writer.writeFloat64(index);
507
532
  },
508
533
  writeBuffer(value) {
509
- // NOTE: I think the inplace optimization is actually slower? We needed to add a length prefix
510
- // buffer anyways, and currentBufferDefaultSize would drift upwards, resulting
511
- // in allocating 1GB, slicing the first 8 bytes, adding the large in place buffer,
512
- // allocating 1GB, etc.
513
- // Special case, very large buffers. Don't copy them, just add the whole buffer
514
- // if (value.length > INPLACE_BUFFER_THRESHOLD || value.length > currentBuffer.length) {
515
- // writer.writeFloat64(value.length);
516
- // if (currentBufferPos > 0) {
517
- // outputBuffers.push(currentBuffer.slice(0, currentBufferPos));
518
- // currentBuffer = Buffer.alloc(currentBufferDefaultSize);
519
- // currentBufferPos = 0;
520
- // }
521
- // outputBuffers.push(value);
522
- // return;
523
- // }
524
534
  writer.writeFloat64(value.length);
525
535
  ensureBytes(value.length);
526
536
  value.copy(currentBuffer, currentBufferPos);
@@ -528,8 +538,6 @@ class PathValueSerializer {
528
538
  },
529
539
  };
530
540
 
531
- outputBuffers.push(Buffer.from(JSON.stringify(settings)));
532
-
533
541
  // Break values up into groups of 10K, so we don't lock up the main thread
534
542
  let valueGroups: PathValue[][] = [];
535
543
  const VALUE_GROUP_SIZE = 10_000;
@@ -537,19 +545,30 @@ class PathValueSerializer {
537
545
  valueGroups.push(values.slice(i, i + VALUE_GROUP_SIZE));
538
546
  }
539
547
 
548
+ startNewBuffer("pathValues");
540
549
  for (let values of valueGroups) {
541
550
  this.pathValuesWrite(writer, values, settings);
542
551
  await delay("afterPaint");
543
552
  }
553
+
544
554
  if (!settings.noLocks) {
545
- // TODO: Support breaking locks up
555
+ startNewBuffer("readLocks");
546
556
  this.readLocksWrite(writer, values.map(x => x.locks));
547
557
  }
548
- this.transactionPathsWrite(writer, values.map(x => x.transactionPaths));
558
+
559
+ if (!config?.noTransactionPaths) {
560
+ startNewBuffer("transactionPaths");
561
+ this.transactionPathsWrite(writer, values.map(x => x.transactionPaths));
562
+ }
563
+
564
+ startNewBuffer("values");
549
565
  await this.valuesWrite(writer, valueGroups);
550
566
 
551
567
  if (currentBufferPos > 0) {
552
568
  outputBuffers.push(currentBuffer.slice(0, currentBufferPos));
569
+ if (currentBufferType) {
570
+ settings.bufferMap!.push(currentBufferType);
571
+ }
553
572
  }
554
573
 
555
574
  // NOTE: The dictionary is ordered, so we don't need to sort by the values or anything
@@ -570,45 +589,41 @@ class PathValueSerializer {
570
589
  curCount += str.length;
571
590
  }
572
591
 
573
- let stringBuffers: Buffer[] = [];
574
- stringBuffers.push(asBuffer(new Float64Array([stringParts.length])));
575
592
  for (let strings of stringParts) {
576
- stringBuffers.push(StringSerialize.serializeStrings(strings));
593
+ let stringBuffer = StringSerialize.serializeStrings(strings);
594
+ settings.bufferMap!.push("strings");
595
+ outputBuffers.push(stringBuffer);
577
596
  await delay("paintLoop");
578
597
  }
579
- // Add reversed, so they can be read in backwards order
580
- stringBuffers.reverse();
581
- outputBuffers.push(...stringBuffers);
582
598
 
583
599
  if (settings.compression === "lz4") {
584
- let compressedBuffers = await unblockLoop(outputBuffers.slice(1), x => LZ4.compress(x));
585
- let compressedOutputBuffers = [outputBuffers[0], ...compressedBuffers];
600
+ let compressedBuffers = await unblockLoop(outputBuffers, x => LZ4.compress(x));
586
601
 
587
602
  // If the compress factor is less than a threshold, use the uncompressed buffers
588
603
  let uncompressedSize = outputBuffers.reduce((total, x) => total + x.length, 0);
589
- let compressedSize = compressedOutputBuffers.reduce((total, x) => total + x.length, 0);
604
+ let compressedSize = compressedBuffers.reduce((total, x) => total + x.length, 0);
590
605
  if (compressedSize / uncompressedSize < MIN_COMPRESS_FACTOR) {
591
- outputBuffers = compressedOutputBuffers;
606
+ outputBuffers = compressedBuffers;
592
607
  } else {
593
608
  settings.compression = undefined;
594
- outputBuffers[0] = Buffer.from(JSON.stringify(settings));
595
609
  }
596
610
  } else if (settings.compression === "gzip") {
597
611
  // NOTE: Due to how the LZ77 window works merging buffers probably won't reduce the size by that much.
598
- let compressedBuffers = await Promise.all(outputBuffers.slice(1).map(x => Zip.gzip(x, 1)));
599
- let compressedOutputBuffers = [outputBuffers[0], ...compressedBuffers];
612
+ let compressedBuffers = await Promise.all(outputBuffers.map(x => Zip.gzip(x, 1)));
600
613
 
601
614
  // If the compress factor is less than a threshold, use the uncompressed buffers
602
615
  let uncompressedSize = outputBuffers.reduce((total, x) => total + x.length, 0);
603
- let compressedSize = compressedOutputBuffers.reduce((total, x) => total + x.length, 0);
616
+ let compressedSize = compressedBuffers.reduce((total, x) => total + x.length, 0);
604
617
  if (compressedSize / uncompressedSize < MIN_COMPRESS_FACTOR) {
605
- outputBuffers = compressedOutputBuffers;
618
+ outputBuffers = compressedBuffers;
606
619
  } else {
607
620
  settings.compression = undefined;
608
- outputBuffers[0] = Buffer.from(JSON.stringify(settings));
609
621
  }
610
622
  }
611
623
 
624
+ // Add settings buffer at the beginning
625
+ outputBuffers.unshift(Buffer.from(JSON.stringify(settings)));
626
+
612
627
  if (config?.singleBuffer) {
613
628
  let sizes = Buffer.from(new Float64Array([outputBuffers.length, ...outputBuffers.map(x => x.length)]).buffer);
614
629
  outputBuffers.unshift(sizes);
@@ -665,6 +680,23 @@ class PathValueSerializer {
665
680
  buffers = await Zip.gunzipBatch(buffers);
666
681
  }
667
682
 
683
+ if (version >= 3) {
684
+ return this.deserializeV3(buffers, settings, config);
685
+ } else {
686
+ return this.deserializeV0toV2(buffers, settings, config);
687
+ }
688
+ }
689
+
690
+ /** @deprecated We shouldn't need this code. If we ever have to modify the file again, just delete this function entirely. As soon as we rerun a single GC, all the code gets updated to the latest version, So, even as of writing this, we don't have anything that uses the old versions. Except for old snapshots, which we'll never use. */
691
+ @measureFnc
692
+ private async deserializeV0toV2(buffers: Buffer[], settings: DataSettings, config?: {
693
+ fullDeserialize?: boolean;
694
+ skipStrings?: boolean;
695
+ skipValues?: boolean;
696
+ skipTransactionPaths?: boolean;
697
+ }): Promise<PathValue[]> {
698
+ let version = settings.version || 0;
699
+
668
700
  function getLastBuffer(): Buffer {
669
701
  let last = buffers.pop();
670
702
  if (!last) {
@@ -754,8 +786,6 @@ class PathValueSerializer {
754
786
  let readLocks = settings.noLocks ? Array(settings.valueCount).fill([]) : this.readLocksRead(reader);
755
787
 
756
788
  let transactionPaths: (string[] | undefined)[] = [];
757
- // Skip transactions if they at all seem like they want to skip anything, as transactions are not very important. And how are you going to use transaction paths if you don't have values or if you don't have strings?
758
- // - Oh, but I think skip values just makes the values lazy. So that's normal. We still want transactions even if the values are lazy.
759
789
  if (version > 1 && !config?.skipStrings && !config?.skipTransactionPaths) {
760
790
  transactionPaths = this.transactionPathsRead(reader);
761
791
  }
@@ -793,6 +823,167 @@ class PathValueSerializer {
793
823
  return pathValues;
794
824
  }
795
825
 
826
+ @measureFnc
827
+ private async deserializeV3(buffers: Buffer[], settings: DataSettings, config?: {
828
+ fullDeserialize?: boolean;
829
+ skipStrings?: boolean;
830
+ skipValues?: boolean;
831
+ skipTransactionPaths?: boolean;
832
+ }): Promise<PathValue[]> {
833
+ let version = settings.version || 0;
834
+ let bufferMap = settings.bufferMap!;
835
+
836
+ // Find string buffers and deserialize them
837
+ let strings: string[] | undefined;
838
+ if (!config?.skipStrings) {
839
+ let stringBufferIndexes: number[] = [];
840
+ for (let i = 0; i < bufferMap.length; i++) {
841
+ if (bufferMap[i] === "strings") {
842
+ stringBufferIndexes.push(i);
843
+ }
844
+ }
845
+
846
+ let stringArrays: string[][] = [];
847
+ for (let bufferIndex of stringBufferIndexes) {
848
+ let stringBuffer = buffers[bufferIndex];
849
+ let obj = StringSerialize.deserializeStringsLazy(stringBuffer);
850
+ while (true) {
851
+ let nextStrings = obj.getNextStrings();
852
+ if (!nextStrings) {
853
+ break;
854
+ }
855
+ stringArrays.push(nextStrings);
856
+ if (stringArrays.length > 1) {
857
+ await delay("paintLoop");
858
+ }
859
+ }
860
+ if (stringBufferIndexes.length > 1) {
861
+ await delay("paintLoop");
862
+ }
863
+ }
864
+ strings = stringArrays.flat();
865
+ }
866
+
867
+ // Create a generic reader that can read from specific buffer types
868
+ function createReaderForBufferType(bufferType: "pathValues" | "readLocks" | "transactionPaths" | "values"): Reader {
869
+ let targetBufferIndexes: number[] = [];
870
+ for (let i = 0; i < bufferMap.length; i++) {
871
+ if (bufferMap[i] === bufferType) {
872
+ targetBufferIndexes.push(i);
873
+ }
874
+ }
875
+
876
+ let currentBufferIndexInList = 0;
877
+ let bufferPos = 0;
878
+
879
+ function ensureBytes(count: number) {
880
+ if (currentBufferIndexInList >= targetBufferIndexes.length) {
881
+ throw new Error(`No more buffers available for buffer type ${bufferType}`);
882
+ }
883
+ let currentBufferIndex = targetBufferIndexes[currentBufferIndexInList];
884
+ if (bufferPos + count <= buffers[currentBufferIndex].length) return;
885
+ if (bufferPos !== buffers[currentBufferIndex].length) {
886
+ throw new Error(`Reading from two buffers at once. This is unexpected.`);
887
+ }
888
+ currentBufferIndexInList++;
889
+ bufferPos = 0;
890
+ }
891
+
892
+ return {
893
+ readFloat64() {
894
+ ensureBytes(8);
895
+ let currentBufferIndex = targetBufferIndexes[currentBufferIndexInList];
896
+ let result = buffers[currentBufferIndex].readDoubleLE(bufferPos);
897
+ bufferPos += 8;
898
+ return result;
899
+ },
900
+ readByte() {
901
+ ensureBytes(1);
902
+ let currentBufferIndex = targetBufferIndexes[currentBufferIndexInList];
903
+ let result = buffers[currentBufferIndex][bufferPos];
904
+ bufferPos++;
905
+ return result;
906
+ },
907
+ readString() {
908
+ let index = this.readFloat64();
909
+ if (!strings) {
910
+ return "";
911
+ }
912
+ if (index < 0 || index >= strings.length) {
913
+ throw new Error(`Invalid string index ${index}, expected 0 <= index < ${strings.length}`);
914
+ }
915
+ return strings[index];
916
+ },
917
+ readBuffer() {
918
+ let length = this.readFloat64();
919
+ ensureBytes(length);
920
+ let currentBufferIndex = targetBufferIndexes[currentBufferIndexInList];
921
+ let result = buffers[currentBufferIndex].slice(bufferPos, bufferPos + length);
922
+ bufferPos += length;
923
+ return result;
924
+ },
925
+ };
926
+ }
927
+
928
+ let partialValues: PathValueStructure[] = [];
929
+ let readLocks: ReadLock[][] = [];
930
+ let transactionPaths: (string[] | undefined)[] = [];
931
+ let values: unknown[] = [];
932
+ let valuesAreLazy: boolean[] = [];
933
+
934
+ // Read pathValues
935
+ if (bufferMap.includes("pathValues")) {
936
+ let reader = createReaderForBufferType("pathValues");
937
+ partialValues = this.pathValuesRead(reader, settings.valueCount);
938
+ }
939
+
940
+ // Read readLocks
941
+ if (!settings.noLocks && bufferMap.includes("readLocks")) {
942
+ let reader = createReaderForBufferType("readLocks");
943
+ readLocks = this.readLocksRead(reader);
944
+ } else {
945
+ readLocks = Array(settings.valueCount).fill([]);
946
+ }
947
+
948
+ // Read transactionPaths
949
+ if (!config?.skipStrings && !config?.skipTransactionPaths && bufferMap.includes("transactionPaths")) {
950
+ let reader = createReaderForBufferType("transactionPaths");
951
+ transactionPaths = this.transactionPathsRead(reader);
952
+ }
953
+
954
+ // Read values
955
+ if (!config?.skipValues && bufferMap.includes("values")) {
956
+ let reader = createReaderForBufferType("values");
957
+ let valuesReadObj = this.valuesRead(reader, settings.valueCount, !config?.fullDeserialize);
958
+ values = valuesReadObj.values;
959
+ valuesAreLazy = valuesReadObj.valuesAreLazy;
960
+ }
961
+
962
+ let pathValues: PathValue[] = [];
963
+ for (let i = 0; i < settings.valueCount; i++) {
964
+ let partialValue = partialValues[i];
965
+ pathValues.push({
966
+ path: partialValue.path,
967
+ time: partialValue.time,
968
+
969
+ locks: readLocks[i],
970
+ value: values[i],
971
+ isValueLazy: valuesAreLazy[i] || false,
972
+
973
+ canGCValue: partialValue.canGCValue,
974
+ event: partialValue.event,
975
+ isTransparent: partialValue.isTransparent,
976
+ valid: partialValue.valid,
977
+ source: partialValue.source,
978
+ lockCount: partialValue.lockCount,
979
+ updateCount: partialValue.updateCount,
980
+ transactionPaths: transactionPaths[i],
981
+ });
982
+ }
983
+
984
+ return pathValues;
985
+ }
986
+
796
987
  lazyValues = new WeakMap<{}, Buffer>();
797
988
  addLazyValue(buf: Buffer) {
798
989
  // NOTE: Using an object makes garbage collection MUCH easier, basically eliminating leaks (and...
@@ -128,7 +128,13 @@ export class PathValueArchives {
128
128
  values = await this.filterForDiskStorage(values);
129
129
  }
130
130
 
131
- let buffers = await pathValueSerializer.serialize(values, { noLocks: true, compress: getCompressNetwork(), singleBuffer: true });
131
+ let buffers = await pathValueSerializer.serialize(values, {
132
+ noLocks: true,
133
+ compress: getCompressNetwork(),
134
+ singleBuffer: true,
135
+ // NOTE: The system natively handles the concept of knowing not to do a commit if it doesn't have any value. The only reason transactions are required is we might have a value, but it might not be the latest value. However, if we're writing to the disk, we're not going to have an even older value. Anything we write to the disk is going to be the oldest value that we'll receive.
136
+ noTransactionPaths: true
137
+ });
132
138
  let data = buffers[0];
133
139
 
134
140
  let minTime = values[0].time.time;
package/test.ts CHANGED
@@ -7,7 +7,7 @@ import { getControllerNodeIdList } from "./src/-g-core-values/NodeCapabilities";
7
7
  import { delay } from "socket-function/src/batching";
8
8
  import { green, yellow } from "socket-function/src/formatting/logColors";
9
9
  import { Querysub, t } from "./src/4-querysub/Querysub";
10
- import { pathValueArchives } from "./src/0-path-value-core/pathValueArchives";
10
+ import { archives, pathValueArchives } from "./src/0-path-value-core/pathValueArchives";
11
11
  import { getAllAuthoritySpec } from "./src/0-path-value-core/PathRouterServerAuthoritySpec";
12
12
  import { deploySchema } from "./src/4-deploy/deploySchema";
13
13
  import { getDomain } from "./src/config";
@@ -17,6 +17,8 @@ import { RemoteWatcher } from "./src/1-path-client/RemoteWatcher";
17
17
  import { PathRouter } from "./src/0-path-value-core/PathRouter";
18
18
  import { shutdown } from "./src/diagnostics/periodic";
19
19
  import { getShardPrefixes } from "./src/0-path-value-core/ShardPrefixes";
20
+ import { PathValue, epochTime } from "./src/0-path-value-core/pathValueCore";
21
+ import { pathValueSerializer } from "./src/-h-path-value-serialize/PathValueSerializer";
20
22
 
21
23
  let tempTestSchema = Querysub.createSchema({
22
24
  value: t.number,
@@ -28,13 +30,14 @@ let tempTestSchema = Querysub.createSchema({
28
30
  });
29
31
 
30
32
  async function main() {
31
-
32
- let prefixes = await getShardPrefixes();
33
- for (let prefix of prefixes) {
34
- console.log(prefix);
35
- }
36
33
  // await Querysub.hostService("test");
37
34
 
35
+ // let testValues: PathValue[] = [];
36
+ // let buffers = await pathValueSerializer.serialize(testValues);
37
+ // let values = await pathValueSerializer.deserialize(buffers);
38
+
39
+ let values = await pathValueArchives.loadValues(await getAllAuthoritySpec());
40
+
38
41
  // let path = getProxyPath(() => tempTestSchema.data().value);
39
42
  // console.log({ path });
40
43
  // let authorities = PathRouter.getAllAuthorities(path);