@prisma/streams-server 0.1.1 → 0.1.3
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/CONTRIBUTING.md +8 -0
- package/package.json +2 -1
- package/src/app.ts +290 -17
- package/src/app_core.ts +1833 -698
- package/src/app_local.ts +144 -4
- package/src/auto_tune.ts +62 -0
- package/src/bootstrap.ts +159 -1
- package/src/concurrency_gate.ts +108 -0
- package/src/config.ts +116 -14
- package/src/db/db.ts +1201 -131
- package/src/db/schema.ts +308 -8
- package/src/foreground_activity.ts +55 -0
- package/src/index/indexer.ts +254 -124
- package/src/index/lexicon_file_cache.ts +261 -0
- package/src/index/lexicon_format.ts +93 -0
- package/src/index/lexicon_indexer.ts +789 -0
- package/src/index/secondary_indexer.ts +824 -0
- package/src/index/secondary_schema.ts +105 -0
- package/src/ingest.ts +10 -12
- package/src/manifest.ts +143 -8
- package/src/memory.ts +183 -8
- package/src/metrics.ts +15 -29
- package/src/metrics_emitter.ts +26 -3
- package/src/notifier.ts +121 -5
- package/src/objectstore/accounting.ts +92 -0
- package/src/objectstore/mock_r2.ts +1 -1
- package/src/objectstore/r2.ts +17 -1
- package/src/profiles/evlog/schema.ts +234 -0
- package/src/profiles/evlog.ts +299 -0
- package/src/profiles/generic.ts +47 -0
- package/src/profiles/index.ts +205 -0
- package/src/profiles/metrics/block_format.ts +109 -0
- package/src/profiles/metrics/normalize.ts +366 -0
- package/src/profiles/metrics/schema.ts +319 -0
- package/src/profiles/metrics.ts +85 -0
- package/src/profiles/profile.ts +225 -0
- package/src/{touch/engine.ts → profiles/stateProtocol/changes.ts} +3 -20
- package/src/profiles/stateProtocol/routes.ts +389 -0
- package/src/profiles/stateProtocol/types.ts +6 -0
- package/src/profiles/stateProtocol/validation.ts +51 -0
- package/src/profiles/stateProtocol.ts +100 -0
- package/src/read_filter.ts +468 -0
- package/src/reader.ts +2151 -164
- package/src/runtime/host_runtime.ts +5 -0
- package/src/runtime_memory.ts +200 -0
- package/src/runtime_memory_sampler.ts +235 -0
- package/src/schema/read_json.ts +43 -0
- package/src/schema/registry.ts +563 -59
- package/src/search/agg_format.ts +638 -0
- package/src/search/aggregate.ts +389 -0
- package/src/search/binary/codec.ts +162 -0
- package/src/search/binary/docset.ts +67 -0
- package/src/search/binary/restart_strings.ts +181 -0
- package/src/search/binary/varint.ts +34 -0
- package/src/search/bitset.ts +19 -0
- package/src/search/col_format.ts +382 -0
- package/src/search/col_runtime.ts +59 -0
- package/src/search/column_encoding.ts +43 -0
- package/src/search/companion_file_cache.ts +319 -0
- package/src/search/companion_format.ts +313 -0
- package/src/search/companion_manager.ts +1086 -0
- package/src/search/companion_plan.ts +218 -0
- package/src/search/fts_format.ts +423 -0
- package/src/search/fts_runtime.ts +333 -0
- package/src/search/query.ts +875 -0
- package/src/search/schema.ts +245 -0
- package/src/segment/cache.ts +93 -2
- package/src/segment/cached_segment.ts +89 -0
- package/src/segment/format.ts +108 -36
- package/src/segment/segmenter.ts +79 -5
- package/src/segment/segmenter_worker.ts +35 -6
- package/src/segment/segmenter_workers.ts +42 -12
- package/src/server.ts +150 -36
- package/src/sqlite/adapter.ts +185 -14
- package/src/sqlite/runtime_stats.ts +163 -0
- package/src/stats.ts +3 -3
- package/src/stream_size_reconciler.ts +100 -0
- package/src/touch/canonical_change.ts +7 -0
- package/src/touch/live_metrics.ts +94 -64
- package/src/touch/live_templates.ts +15 -1
- package/src/touch/manager.ts +166 -88
- package/src/touch/{interpreter_worker.ts → processor_worker.ts} +19 -14
- package/src/touch/spec.ts +95 -92
- package/src/touch/touch_journal.ts +4 -0
- package/src/touch/worker_pool.ts +8 -14
- package/src/touch/worker_protocol.ts +3 -3
- package/src/uploader.ts +77 -6
- package/src/util/bloom256.ts +2 -2
- package/src/util/byte_lru.ts +73 -0
- package/src/util/lru.ts +8 -0
- package/src/util/stream_paths.ts +19 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
import { zstdCompressSync, zstdDecompressSync } from "node:zlib";
|
|
3
|
+
import { BinaryCursor, BinaryPayloadError, BinaryWriter, concatBytes, readF64, readI64, readU16, readU32 } from "./binary/codec";
|
|
4
|
+
import { RestartStringTableView, encodeRestartStringTable } from "./binary/restart_strings";
|
|
5
|
+
import { readUVarint, readZigZagVarint, writeUVarint, writeZigZagVarint } from "./binary/varint";
|
|
6
|
+
import type { SearchCompanionPlan, SearchCompanionPlanRollup } from "./companion_plan";
|
|
7
|
+
|
|
8
|
+
export type AggSummaryState = {
|
|
9
|
+
count: number;
|
|
10
|
+
sum: number;
|
|
11
|
+
min: number | null;
|
|
12
|
+
max: number | null;
|
|
13
|
+
histogram?: Record<string, number>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type AggMeasureState =
|
|
17
|
+
| { kind: "count"; value: number }
|
|
18
|
+
| { kind: "summary"; summary: AggSummaryState };
|
|
19
|
+
|
|
20
|
+
export type AggWindowGroup = {
|
|
21
|
+
dimensions: Record<string, string | null>;
|
|
22
|
+
measures: Record<string, AggMeasureState>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type AggIntervalInput = {
|
|
26
|
+
interval_ms: number;
|
|
27
|
+
windows: Array<{
|
|
28
|
+
start_ms: number;
|
|
29
|
+
groups: AggWindowGroup[];
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type AggRollupInput = {
|
|
34
|
+
intervals: Record<string, AggIntervalInput>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type AggSectionInput = {
|
|
38
|
+
rollups: Record<string, AggRollupInput>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const ROLLUP_DIR_ENTRY_BYTES = 12;
|
|
42
|
+
const INTERVAL_DIR_ENTRY_BYTES = 12;
|
|
43
|
+
const DIM_DIR_ENTRY_BYTES = 20;
|
|
44
|
+
const MEASURE_DIR_ENTRY_BYTES = 52;
|
|
45
|
+
const AGG_INTERVAL_COMPRESSION_NONE = 0;
|
|
46
|
+
const AGG_INTERVAL_COMPRESSION_ZSTD = 1;
|
|
47
|
+
|
|
48
|
+
const MEASURE_KIND_COUNT = 1;
|
|
49
|
+
const MEASURE_KIND_SUMMARY = 2;
|
|
50
|
+
|
|
51
|
+
type RollupDirEntry = {
|
|
52
|
+
rollupOrdinal: number;
|
|
53
|
+
intervalCount: number;
|
|
54
|
+
intervalsOffset: number;
|
|
55
|
+
intervalsLength: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type IntervalDirEntry = {
|
|
59
|
+
intervalOrdinal: number;
|
|
60
|
+
compression: number;
|
|
61
|
+
payloadOffset: number;
|
|
62
|
+
payloadLength: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type IntervalPayload = {
|
|
66
|
+
intervalOrdinal: number;
|
|
67
|
+
compression: number;
|
|
68
|
+
payload: Uint8Array;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type AggFormatError = { kind: "invalid_agg_segment"; message: string };
|
|
72
|
+
|
|
73
|
+
function invalidAgg<T = never>(message: string): Result<T, AggFormatError> {
|
|
74
|
+
return Result.err({ kind: "invalid_agg_segment", message });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class AggIntervalView {
|
|
78
|
+
private decoded:
|
|
79
|
+
| {
|
|
80
|
+
windowStarts: bigint[];
|
|
81
|
+
windowGroupOffsets: Uint32Array;
|
|
82
|
+
dimensions: Array<{ name: string; dict: string[]; ordinals: Uint32Array }>;
|
|
83
|
+
measures: Array<{
|
|
84
|
+
name: string;
|
|
85
|
+
kind: AggMeasureState["kind"];
|
|
86
|
+
countValues: Uint32Array;
|
|
87
|
+
sumValues?: Float64Array;
|
|
88
|
+
minValues?: Float64Array;
|
|
89
|
+
maxValues?: Float64Array;
|
|
90
|
+
histogramOffsets?: Uint32Array;
|
|
91
|
+
histogramData?: Uint8Array;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
| null = null;
|
|
95
|
+
private intervalBytes: Uint8Array | null = null;
|
|
96
|
+
|
|
97
|
+
constructor(
|
|
98
|
+
readonly rollupName: string,
|
|
99
|
+
readonly intervalMs: number,
|
|
100
|
+
private readonly rollupPlan: SearchCompanionPlanRollup,
|
|
101
|
+
private readonly bytes: Uint8Array,
|
|
102
|
+
private readonly compression: number,
|
|
103
|
+
private readonly plan: SearchCompanionPlan
|
|
104
|
+
) {}
|
|
105
|
+
|
|
106
|
+
forEachGroupInRange(
|
|
107
|
+
startMsInclusive: number,
|
|
108
|
+
endMsExclusive: number,
|
|
109
|
+
visit: (windowStartMs: number, group: AggWindowGroup) => void
|
|
110
|
+
): void {
|
|
111
|
+
const decoded = this.decode();
|
|
112
|
+
const startIndex = lowerBoundBigint(decoded.windowStarts, BigInt(startMsInclusive));
|
|
113
|
+
const endIndex = lowerBoundBigint(decoded.windowStarts, BigInt(endMsExclusive));
|
|
114
|
+
for (let windowIndex = startIndex; windowIndex < endIndex; windowIndex++) {
|
|
115
|
+
const windowStartMs = Number(decoded.windowStarts[windowIndex]!);
|
|
116
|
+
const groupStart = decoded.windowGroupOffsets[windowIndex]!;
|
|
117
|
+
const groupEnd = decoded.windowGroupOffsets[windowIndex + 1]!;
|
|
118
|
+
for (let groupIndex = groupStart; groupIndex < groupEnd; groupIndex++) {
|
|
119
|
+
const dimensions: Record<string, string | null> = {};
|
|
120
|
+
for (const dimension of decoded.dimensions) {
|
|
121
|
+
const ordinal = dimension.ordinals[groupIndex] ?? 0;
|
|
122
|
+
dimensions[dimension.name] = ordinal === 0 ? null : (dimension.dict[ordinal - 1] ?? null);
|
|
123
|
+
}
|
|
124
|
+
const measures: Record<string, AggMeasureState> = {};
|
|
125
|
+
for (const measure of decoded.measures) {
|
|
126
|
+
if (measure.kind === "count") {
|
|
127
|
+
measures[measure.name] = { kind: "count", value: measure.countValues[groupIndex] ?? 0 };
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
measures[measure.name] = {
|
|
131
|
+
kind: "summary",
|
|
132
|
+
summary: {
|
|
133
|
+
count: measure.countValues[groupIndex] ?? 0,
|
|
134
|
+
sum: measure.sumValues?.[groupIndex] ?? 0,
|
|
135
|
+
min: decodeNullableNumber(measure.minValues?.[groupIndex]),
|
|
136
|
+
max: decodeNullableNumber(measure.maxValues?.[groupIndex]),
|
|
137
|
+
histogram: decodeHistogram(measure.histogramOffsets, measure.histogramData, groupIndex),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
visit(windowStartMs, { dimensions, measures });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private decode() {
|
|
147
|
+
if (this.decoded) return this.decoded;
|
|
148
|
+
const intervalBytes = this.getIntervalBytes();
|
|
149
|
+
const cursor = new BinaryCursor(intervalBytes);
|
|
150
|
+
const windowCount = cursor.readU32();
|
|
151
|
+
const groupCount = cursor.readU32();
|
|
152
|
+
const dimCount = cursor.readU16();
|
|
153
|
+
const measureCount = cursor.readU16();
|
|
154
|
+
const windowStartsOffset = cursor.readU32();
|
|
155
|
+
const windowStartsLength = cursor.readU32();
|
|
156
|
+
const windowOffsetsOffset = cursor.readU32();
|
|
157
|
+
const windowOffsetsLength = cursor.readU32();
|
|
158
|
+
const dimDirOffset = cursor.readU32();
|
|
159
|
+
const dimDirLength = cursor.readU32();
|
|
160
|
+
const measureDirOffset = cursor.readU32();
|
|
161
|
+
const measureDirLength = cursor.readU32();
|
|
162
|
+
|
|
163
|
+
const windowStartBytes = slicePayload(intervalBytes, windowStartsOffset, windowStartsLength, "invalid .agg2 window starts");
|
|
164
|
+
const windowStarts: bigint[] = [];
|
|
165
|
+
for (let offset = 0; offset + 8 <= windowStartBytes.byteLength; offset += 8) {
|
|
166
|
+
windowStarts.push(readI64(windowStartBytes, offset));
|
|
167
|
+
}
|
|
168
|
+
const windowGroupOffsetsBytes = slicePayload(intervalBytes, windowOffsetsOffset, windowOffsetsLength, "invalid .agg2 window offsets");
|
|
169
|
+
const windowGroupOffsets = decodeU32Array(windowGroupOffsetsBytes);
|
|
170
|
+
|
|
171
|
+
const dimensions: Array<{ name: string; dict: string[]; ordinals: Uint32Array }> = [];
|
|
172
|
+
for (let index = 0; index < dimCount; index++) {
|
|
173
|
+
const entryOffset = dimDirOffset + index * DIM_DIR_ENTRY_BYTES;
|
|
174
|
+
const fieldOrdinal = readU16(intervalBytes, entryOffset);
|
|
175
|
+
const dictOffset = readU32(intervalBytes, entryOffset + 4);
|
|
176
|
+
const dictLength = readU32(intervalBytes, entryOffset + 8);
|
|
177
|
+
const ordinalsOffset = readU32(intervalBytes, entryOffset + 12);
|
|
178
|
+
const ordinalsLength = readU32(intervalBytes, entryOffset + 16);
|
|
179
|
+
const fieldName = this.plan.fields.find((field) => field.ordinal === fieldOrdinal)?.name;
|
|
180
|
+
if (!fieldName) continue;
|
|
181
|
+
const dictBytes = slicePayload(intervalBytes, dictOffset, dictLength, "invalid .agg2 dim dict");
|
|
182
|
+
const ordinalBytes = slicePayload(intervalBytes, ordinalsOffset, ordinalsLength, "invalid .agg2 dim ordinals");
|
|
183
|
+
dimensions.push({
|
|
184
|
+
name: fieldName,
|
|
185
|
+
dict: new RestartStringTableView(dictBytes).terms(),
|
|
186
|
+
ordinals: decodeU32Array(ordinalBytes),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const measures: Array<{
|
|
191
|
+
name: string;
|
|
192
|
+
kind: AggMeasureState["kind"];
|
|
193
|
+
countValues: Uint32Array;
|
|
194
|
+
sumValues?: Float64Array;
|
|
195
|
+
minValues?: Float64Array;
|
|
196
|
+
maxValues?: Float64Array;
|
|
197
|
+
histogramOffsets?: Uint32Array;
|
|
198
|
+
histogramData?: Uint8Array;
|
|
199
|
+
}> = [];
|
|
200
|
+
for (let index = 0; index < measureCount; index++) {
|
|
201
|
+
const entryOffset = measureDirOffset + index * MEASURE_DIR_ENTRY_BYTES;
|
|
202
|
+
const measureOrdinal = readU16(intervalBytes, entryOffset);
|
|
203
|
+
const kindCode = intervalBytes[entryOffset + 2]!;
|
|
204
|
+
const countOffset = readU32(intervalBytes, entryOffset + 4);
|
|
205
|
+
const countLength = readU32(intervalBytes, entryOffset + 8);
|
|
206
|
+
const sumOffset = readU32(intervalBytes, entryOffset + 12);
|
|
207
|
+
const sumLength = readU32(intervalBytes, entryOffset + 16);
|
|
208
|
+
const minOffset = readU32(intervalBytes, entryOffset + 20);
|
|
209
|
+
const minLength = readU32(intervalBytes, entryOffset + 24);
|
|
210
|
+
const maxOffset = readU32(intervalBytes, entryOffset + 28);
|
|
211
|
+
const maxLength = readU32(intervalBytes, entryOffset + 32);
|
|
212
|
+
const histOffsetsOffset = readU32(intervalBytes, entryOffset + 36);
|
|
213
|
+
const histOffsetsLength = readU32(intervalBytes, entryOffset + 40);
|
|
214
|
+
const histDataOffset = readU32(intervalBytes, entryOffset + 44);
|
|
215
|
+
const histDataLength = readU32(intervalBytes, entryOffset + 48);
|
|
216
|
+
const measurePlan = this.rollupPlan.measures.find((measure) => measure.ordinal === measureOrdinal);
|
|
217
|
+
if (!measurePlan) continue;
|
|
218
|
+
const countBytes = slicePayload(intervalBytes, countOffset, countLength, "invalid .agg2 measure count column");
|
|
219
|
+
const measure = {
|
|
220
|
+
name: measurePlan.name,
|
|
221
|
+
kind: kindCode === MEASURE_KIND_SUMMARY ? ("summary" as const) : ("count" as const),
|
|
222
|
+
countValues: decodeU32Array(countBytes),
|
|
223
|
+
};
|
|
224
|
+
if (measure.kind === "summary") {
|
|
225
|
+
const sumBytes = slicePayload(intervalBytes, sumOffset, sumLength, "invalid .agg2 sum column");
|
|
226
|
+
const minBytes = slicePayload(intervalBytes, minOffset, minLength, "invalid .agg2 min column");
|
|
227
|
+
const maxBytes = slicePayload(intervalBytes, maxOffset, maxLength, "invalid .agg2 max column");
|
|
228
|
+
const histOffsetsBytes = histOffsetsLength > 0 ? slicePayload(intervalBytes, histOffsetsOffset, histOffsetsLength, "invalid .agg2 histogram offsets") : new Uint8Array();
|
|
229
|
+
measures.push({
|
|
230
|
+
...measure,
|
|
231
|
+
sumValues: decodeF64Array(sumBytes),
|
|
232
|
+
minValues: decodeF64Array(minBytes),
|
|
233
|
+
maxValues: decodeF64Array(maxBytes),
|
|
234
|
+
histogramOffsets:
|
|
235
|
+
histOffsetsBytes.byteLength > 0 ? decodeU32Array(histOffsetsBytes) : undefined,
|
|
236
|
+
histogramData: histDataLength > 0 ? slicePayload(intervalBytes, histDataOffset, histDataLength, "invalid .agg2 histogram data") : undefined,
|
|
237
|
+
});
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
measures.push(measure);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.decoded = { windowStarts, windowGroupOffsets, dimensions, measures };
|
|
244
|
+
return this.decoded;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private getIntervalBytes(): Uint8Array {
|
|
248
|
+
if (this.intervalBytes) return this.intervalBytes;
|
|
249
|
+
if (this.compression === AGG_INTERVAL_COMPRESSION_NONE) {
|
|
250
|
+
this.intervalBytes = this.bytes;
|
|
251
|
+
return this.intervalBytes;
|
|
252
|
+
}
|
|
253
|
+
if (this.compression !== AGG_INTERVAL_COMPRESSION_ZSTD) {
|
|
254
|
+
throw new BinaryPayloadError(`unsupported .agg2 interval compression ${this.compression}`);
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
this.intervalBytes = new Uint8Array(zstdDecompressSync(this.bytes));
|
|
258
|
+
} catch (error: unknown) {
|
|
259
|
+
throw new BinaryPayloadError(`invalid .agg2 compressed interval payload: ${String((error as Error)?.message ?? error)}`);
|
|
260
|
+
}
|
|
261
|
+
return this.intervalBytes;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function lowerBoundBigint(values: bigint[], target: bigint): number {
|
|
266
|
+
let low = 0;
|
|
267
|
+
let high = values.length;
|
|
268
|
+
while (low < high) {
|
|
269
|
+
const mid = (low + high) >> 1;
|
|
270
|
+
if (values[mid]! < target) low = mid + 1;
|
|
271
|
+
else high = mid;
|
|
272
|
+
}
|
|
273
|
+
return low;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export class AggSectionView {
|
|
277
|
+
private readonly intervalByKey = new Map<string, AggIntervalView>();
|
|
278
|
+
|
|
279
|
+
constructor(intervals: AggIntervalView[]) {
|
|
280
|
+
for (const interval of intervals) {
|
|
281
|
+
this.intervalByKey.set(`${interval.rollupName}\u0000${interval.intervalMs}`, interval);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
getInterval(rollupName: string, intervalMs: number): AggIntervalView | null {
|
|
286
|
+
return this.intervalByKey.get(`${rollupName}\u0000${intervalMs}`) ?? null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function encodeAggSegmentCompanion(input: AggSectionInput, plan: SearchCompanionPlan): Uint8Array {
|
|
291
|
+
const rollupEntries = plan.rollups
|
|
292
|
+
.filter((rollup) => input.rollups[rollup.name])
|
|
293
|
+
.sort((a, b) => a.ordinal - b.ordinal)
|
|
294
|
+
.map((rollup) => {
|
|
295
|
+
const source = input.rollups[rollup.name]!;
|
|
296
|
+
const intervals = rollup.intervals
|
|
297
|
+
.filter((interval) => source.intervals[interval.name] || source.intervals[String(interval.ms)])
|
|
298
|
+
.sort((a, b) => a.ordinal - b.ordinal)
|
|
299
|
+
.map((interval) => ({
|
|
300
|
+
intervalOrdinal: interval.ordinal,
|
|
301
|
+
...compressIntervalPayload(
|
|
302
|
+
encodeIntervalPayload((source.intervals[interval.name] ?? source.intervals[String(interval.ms)])!, rollup, plan)
|
|
303
|
+
),
|
|
304
|
+
}));
|
|
305
|
+
return { rollup, intervals };
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const header = new BinaryWriter();
|
|
309
|
+
header.writeU16(rollupEntries.length);
|
|
310
|
+
header.writeU16(0);
|
|
311
|
+
|
|
312
|
+
const rollupDirStart = header.length;
|
|
313
|
+
const rollupDirBytes = ROLLUP_DIR_ENTRY_BYTES * rollupEntries.length;
|
|
314
|
+
const intervalDirStart = rollupDirStart + rollupDirBytes;
|
|
315
|
+
const totalIntervalDirBytes = rollupEntries.reduce((sum, entry) => sum + INTERVAL_DIR_ENTRY_BYTES * entry.intervals.length, 0);
|
|
316
|
+
let intervalDirOffset = intervalDirStart;
|
|
317
|
+
let payloadOffset = intervalDirStart + totalIntervalDirBytes;
|
|
318
|
+
const rollupDirs: RollupDirEntry[] = [];
|
|
319
|
+
const intervalDirBlobs: Uint8Array[] = [];
|
|
320
|
+
const intervalPayloadBlobs: Uint8Array[] = [];
|
|
321
|
+
|
|
322
|
+
for (const entry of rollupEntries) {
|
|
323
|
+
const intervalDir = new BinaryWriter();
|
|
324
|
+
const currentIntervalDirOffset = intervalDirOffset;
|
|
325
|
+
intervalDirOffset += INTERVAL_DIR_ENTRY_BYTES * entry.intervals.length;
|
|
326
|
+
const intervalDirs: IntervalDirEntry[] = [];
|
|
327
|
+
for (const interval of entry.intervals) {
|
|
328
|
+
intervalDirs.push({
|
|
329
|
+
intervalOrdinal: interval.intervalOrdinal,
|
|
330
|
+
compression: interval.compression,
|
|
331
|
+
payloadOffset,
|
|
332
|
+
payloadLength: interval.payload.byteLength,
|
|
333
|
+
});
|
|
334
|
+
payloadOffset += interval.payload.byteLength;
|
|
335
|
+
intervalPayloadBlobs.push(interval.payload);
|
|
336
|
+
}
|
|
337
|
+
for (const intervalDirEntry of intervalDirs) {
|
|
338
|
+
intervalDir.writeU16(intervalDirEntry.intervalOrdinal);
|
|
339
|
+
intervalDir.writeU8(intervalDirEntry.compression);
|
|
340
|
+
intervalDir.writeU8(0);
|
|
341
|
+
intervalDir.writeU32(intervalDirEntry.payloadOffset);
|
|
342
|
+
intervalDir.writeU32(intervalDirEntry.payloadLength);
|
|
343
|
+
}
|
|
344
|
+
const intervalDirBytesBlob = intervalDir.finish();
|
|
345
|
+
intervalDirBlobs.push(intervalDirBytesBlob);
|
|
346
|
+
rollupDirs.push({
|
|
347
|
+
rollupOrdinal: entry.rollup.ordinal,
|
|
348
|
+
intervalCount: entry.intervals.length,
|
|
349
|
+
intervalsOffset: currentIntervalDirOffset,
|
|
350
|
+
intervalsLength: intervalDirBytesBlob.byteLength,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const rollupDirWriter = new BinaryWriter();
|
|
355
|
+
for (const entry of rollupDirs) {
|
|
356
|
+
rollupDirWriter.writeU16(entry.rollupOrdinal);
|
|
357
|
+
rollupDirWriter.writeU16(entry.intervalCount);
|
|
358
|
+
rollupDirWriter.writeU32(entry.intervalsOffset);
|
|
359
|
+
rollupDirWriter.writeU32(entry.intervalsLength);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return concatBytes([header.finish(), rollupDirWriter.finish(), ...intervalDirBlobs, ...intervalPayloadBlobs]);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function decodeAggSegmentCompanionResult(bytes: Uint8Array, plan: SearchCompanionPlan): Result<AggSectionView, AggFormatError> {
|
|
366
|
+
try {
|
|
367
|
+
const cursor = new BinaryCursor(bytes);
|
|
368
|
+
const rollupCount = cursor.readU16();
|
|
369
|
+
cursor.readU16();
|
|
370
|
+
const intervals: AggIntervalView[] = [];
|
|
371
|
+
const rollupDirOffset = cursor.offset;
|
|
372
|
+
for (let index = 0; index < rollupCount; index++) {
|
|
373
|
+
const entryOffset = rollupDirOffset + index * ROLLUP_DIR_ENTRY_BYTES;
|
|
374
|
+
const rollupOrdinal = readU16(bytes, entryOffset);
|
|
375
|
+
const intervalCount = readU16(bytes, entryOffset + 2);
|
|
376
|
+
const intervalsOffset = readU32(bytes, entryOffset + 4);
|
|
377
|
+
const rollupPlan = plan.rollups.find((rollup) => rollup.ordinal === rollupOrdinal);
|
|
378
|
+
if (!rollupPlan) return invalidAgg(`missing .agg2 rollup ordinal ${rollupOrdinal}`);
|
|
379
|
+
for (let intervalIndex = 0; intervalIndex < intervalCount; intervalIndex++) {
|
|
380
|
+
const intervalEntryOffset = intervalsOffset + intervalIndex * INTERVAL_DIR_ENTRY_BYTES;
|
|
381
|
+
const intervalOrdinal = readU16(bytes, intervalEntryOffset);
|
|
382
|
+
const compression = bytes[intervalEntryOffset + 2] ?? AGG_INTERVAL_COMPRESSION_NONE;
|
|
383
|
+
const payloadOffset = readU32(bytes, intervalEntryOffset + 4);
|
|
384
|
+
const payloadLength = readU32(bytes, intervalEntryOffset + 8);
|
|
385
|
+
const intervalPlan = rollupPlan.intervals.find((interval) => interval.ordinal === intervalOrdinal);
|
|
386
|
+
if (!intervalPlan) return invalidAgg(`missing .agg2 interval ordinal ${intervalOrdinal}`);
|
|
387
|
+
intervals.push(
|
|
388
|
+
new AggIntervalView(
|
|
389
|
+
rollupPlan.name,
|
|
390
|
+
intervalPlan.ms,
|
|
391
|
+
rollupPlan,
|
|
392
|
+
slicePayload(bytes, payloadOffset, payloadLength, "invalid .agg2 interval payload"),
|
|
393
|
+
compression,
|
|
394
|
+
plan
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return Result.ok(new AggSectionView(intervals));
|
|
400
|
+
} catch (e: unknown) {
|
|
401
|
+
return invalidAgg(String((e as any)?.message ?? e));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function encodeIntervalPayload(input: AggIntervalInput, rollupPlan: SearchCompanionPlanRollup, plan: SearchCompanionPlan): Uint8Array {
|
|
406
|
+
const windows = [...input.windows].sort((a, b) => a.start_ms - b.start_ms);
|
|
407
|
+
const groups = windows.flatMap((window) => window.groups);
|
|
408
|
+
const windowGroupOffsets = new Uint32Array(windows.length + 1);
|
|
409
|
+
let groupOffset = 0;
|
|
410
|
+
for (let index = 0; index < windows.length; index++) {
|
|
411
|
+
windowGroupOffsets[index] = groupOffset;
|
|
412
|
+
groupOffset += windows[index]!.groups.length;
|
|
413
|
+
}
|
|
414
|
+
windowGroupOffsets[windows.length] = groupOffset;
|
|
415
|
+
|
|
416
|
+
const windowStartsWriter = new BinaryWriter();
|
|
417
|
+
for (const window of windows) windowStartsWriter.writeI64(BigInt(window.start_ms));
|
|
418
|
+
const windowOffsetsWriter = new BinaryWriter();
|
|
419
|
+
for (const offset of windowGroupOffsets) windowOffsetsWriter.writeU32(offset);
|
|
420
|
+
|
|
421
|
+
const dimensionPayloads = rollupPlan.dimension_ordinals.map((dimensionOrdinal) => {
|
|
422
|
+
const fieldName = plan.fields.find((field) => field.ordinal === dimensionOrdinal)?.name ?? null;
|
|
423
|
+
const values = groups.map((group) => (fieldName ? group.dimensions[fieldName] ?? null : null));
|
|
424
|
+
const dictionary = Array.from(new Set(values.filter((value): value is string => typeof value === "string"))).sort((a, b) => a.localeCompare(b));
|
|
425
|
+
const dictionaryIndex = new Map<string, number>();
|
|
426
|
+
dictionary.forEach((value, index) => dictionaryIndex.set(value, index + 1));
|
|
427
|
+
const ordinalsWriter = new BinaryWriter();
|
|
428
|
+
for (const value of values) ordinalsWriter.writeU32(value == null ? 0 : (dictionaryIndex.get(value) ?? 0));
|
|
429
|
+
return {
|
|
430
|
+
fieldOrdinal: dimensionOrdinal,
|
|
431
|
+
dict: encodeRestartStringTable(dictionary),
|
|
432
|
+
ordinals: ordinalsWriter.finish(),
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const measurePayloads = rollupPlan.measures.map((measurePlan) => {
|
|
437
|
+
const countWriter = new BinaryWriter();
|
|
438
|
+
const sumWriter = new BinaryWriter();
|
|
439
|
+
const minWriter = new BinaryWriter();
|
|
440
|
+
const maxWriter = new BinaryWriter();
|
|
441
|
+
const histOffsetsWriter = new BinaryWriter();
|
|
442
|
+
const histDataWriter = new BinaryWriter();
|
|
443
|
+
let histOffset = 0;
|
|
444
|
+
for (const group of groups) {
|
|
445
|
+
const state = group.measures[measurePlan.name];
|
|
446
|
+
if (!state || state.kind === "count") {
|
|
447
|
+
countWriter.writeU32(state?.kind === "count" ? state.value : 0);
|
|
448
|
+
if (measurePlan.kind !== "count") {
|
|
449
|
+
sumWriter.writeF64(0);
|
|
450
|
+
minWriter.writeF64(Number.NaN);
|
|
451
|
+
maxWriter.writeF64(Number.NaN);
|
|
452
|
+
histOffsetsWriter.writeU32(histOffset);
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
countWriter.writeU32(state.summary.count);
|
|
457
|
+
sumWriter.writeF64(state.summary.sum);
|
|
458
|
+
minWriter.writeF64(state.summary.min ?? Number.NaN);
|
|
459
|
+
maxWriter.writeF64(state.summary.max ?? Number.NaN);
|
|
460
|
+
histOffsetsWriter.writeU32(histOffset);
|
|
461
|
+
const histogramBlob = encodeHistogram(state.summary.histogram);
|
|
462
|
+
histDataWriter.writeBytes(histogramBlob);
|
|
463
|
+
histOffset += histogramBlob.byteLength;
|
|
464
|
+
}
|
|
465
|
+
if (measurePlan.kind === "summary") histOffsetsWriter.writeU32(histOffset);
|
|
466
|
+
return {
|
|
467
|
+
measureOrdinal: measurePlan.ordinal,
|
|
468
|
+
kind: measurePlan.kind === "count" ? MEASURE_KIND_COUNT : MEASURE_KIND_SUMMARY,
|
|
469
|
+
count: countWriter.finish(),
|
|
470
|
+
sum: measurePlan.kind !== "count" ? sumWriter.finish() : new Uint8Array(),
|
|
471
|
+
min: measurePlan.kind !== "count" ? minWriter.finish() : new Uint8Array(),
|
|
472
|
+
max: measurePlan.kind !== "count" ? maxWriter.finish() : new Uint8Array(),
|
|
473
|
+
histOffsets: measurePlan.kind !== "count" ? histOffsetsWriter.finish() : new Uint8Array(),
|
|
474
|
+
histData: measurePlan.kind !== "count" ? histDataWriter.finish() : new Uint8Array(),
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const header = new BinaryWriter();
|
|
479
|
+
header.writeU32(windows.length);
|
|
480
|
+
header.writeU32(groups.length);
|
|
481
|
+
header.writeU16(dimensionPayloads.length);
|
|
482
|
+
header.writeU16(measurePayloads.length);
|
|
483
|
+
header.writeU32(0);
|
|
484
|
+
header.writeU32(0);
|
|
485
|
+
header.writeU32(0);
|
|
486
|
+
header.writeU32(0);
|
|
487
|
+
header.writeU32(0);
|
|
488
|
+
header.writeU32(0);
|
|
489
|
+
header.writeU32(0);
|
|
490
|
+
header.writeU32(0);
|
|
491
|
+
|
|
492
|
+
const dimDirStart = 44;
|
|
493
|
+
const dimDirLength = DIM_DIR_ENTRY_BYTES * dimensionPayloads.length;
|
|
494
|
+
const measureDirStart = dimDirStart + dimDirLength;
|
|
495
|
+
const measureDirLength = MEASURE_DIR_ENTRY_BYTES * measurePayloads.length;
|
|
496
|
+
let payloadOffset = measureDirStart + measureDirLength;
|
|
497
|
+
|
|
498
|
+
const windowStartsOffset = payloadOffset;
|
|
499
|
+
const windowStartsBytes = windowStartsWriter.finish();
|
|
500
|
+
payloadOffset += windowStartsBytes.byteLength;
|
|
501
|
+
const windowOffsetsOffset = payloadOffset;
|
|
502
|
+
const windowOffsetsBytes = windowOffsetsWriter.finish();
|
|
503
|
+
payloadOffset += windowOffsetsBytes.byteLength;
|
|
504
|
+
|
|
505
|
+
const dimDirWriter = new BinaryWriter();
|
|
506
|
+
const dimBlobs: Uint8Array[] = [];
|
|
507
|
+
for (const dim of dimensionPayloads) {
|
|
508
|
+
const dictOffset = payloadOffset;
|
|
509
|
+
payloadOffset += dim.dict.byteLength;
|
|
510
|
+
const ordinalsOffset = payloadOffset;
|
|
511
|
+
payloadOffset += dim.ordinals.byteLength;
|
|
512
|
+
dimDirWriter.writeU16(dim.fieldOrdinal);
|
|
513
|
+
dimDirWriter.writeU16(0);
|
|
514
|
+
dimDirWriter.writeU32(dictOffset);
|
|
515
|
+
dimDirWriter.writeU32(dim.dict.byteLength);
|
|
516
|
+
dimDirWriter.writeU32(ordinalsOffset);
|
|
517
|
+
dimDirWriter.writeU32(dim.ordinals.byteLength);
|
|
518
|
+
dimBlobs.push(dim.dict, dim.ordinals);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const measureDirWriter = new BinaryWriter();
|
|
522
|
+
const measureBlobs: Uint8Array[] = [];
|
|
523
|
+
for (const measure of measurePayloads) {
|
|
524
|
+
const countOffset = payloadOffset;
|
|
525
|
+
payloadOffset += measure.count.byteLength;
|
|
526
|
+
const sumOffset = payloadOffset;
|
|
527
|
+
payloadOffset += measure.sum.byteLength;
|
|
528
|
+
const minOffset = payloadOffset;
|
|
529
|
+
payloadOffset += measure.min.byteLength;
|
|
530
|
+
const maxOffset = payloadOffset;
|
|
531
|
+
payloadOffset += measure.max.byteLength;
|
|
532
|
+
const histOffsetsOffset = payloadOffset;
|
|
533
|
+
payloadOffset += measure.histOffsets.byteLength;
|
|
534
|
+
const histDataOffset = payloadOffset;
|
|
535
|
+
payloadOffset += measure.histData.byteLength;
|
|
536
|
+
measureDirWriter.writeU16(measure.measureOrdinal);
|
|
537
|
+
measureDirWriter.writeU8(measure.kind);
|
|
538
|
+
measureDirWriter.writeU8(0);
|
|
539
|
+
measureDirWriter.writeU32(countOffset);
|
|
540
|
+
measureDirWriter.writeU32(measure.count.byteLength);
|
|
541
|
+
measureDirWriter.writeU32(sumOffset);
|
|
542
|
+
measureDirWriter.writeU32(measure.sum.byteLength);
|
|
543
|
+
measureDirWriter.writeU32(minOffset);
|
|
544
|
+
measureDirWriter.writeU32(measure.min.byteLength);
|
|
545
|
+
measureDirWriter.writeU32(maxOffset);
|
|
546
|
+
measureDirWriter.writeU32(measure.max.byteLength);
|
|
547
|
+
measureDirWriter.writeU32(histOffsetsOffset);
|
|
548
|
+
measureDirWriter.writeU32(measure.histOffsets.byteLength);
|
|
549
|
+
measureDirWriter.writeU32(histDataOffset);
|
|
550
|
+
measureDirWriter.writeU32(measure.histData.byteLength);
|
|
551
|
+
measureBlobs.push(measure.count, measure.sum, measure.min, measure.max, measure.histOffsets, measure.histData);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const out = new BinaryWriter();
|
|
555
|
+
out.writeU32(windows.length);
|
|
556
|
+
out.writeU32(groups.length);
|
|
557
|
+
out.writeU16(dimensionPayloads.length);
|
|
558
|
+
out.writeU16(measurePayloads.length);
|
|
559
|
+
out.writeU32(windowStartsOffset);
|
|
560
|
+
out.writeU32(windowStartsBytes.byteLength);
|
|
561
|
+
out.writeU32(windowOffsetsOffset);
|
|
562
|
+
out.writeU32(windowOffsetsBytes.byteLength);
|
|
563
|
+
out.writeU32(dimDirStart);
|
|
564
|
+
out.writeU32(dimDirLength);
|
|
565
|
+
out.writeU32(measureDirStart);
|
|
566
|
+
out.writeU32(measureDirLength);
|
|
567
|
+
out.writeBytes(dimDirWriter.finish());
|
|
568
|
+
out.writeBytes(measureDirWriter.finish());
|
|
569
|
+
out.writeBytes(windowStartsBytes);
|
|
570
|
+
out.writeBytes(windowOffsetsBytes);
|
|
571
|
+
for (const blob of dimBlobs) out.writeBytes(blob);
|
|
572
|
+
for (const blob of measureBlobs) out.writeBytes(blob);
|
|
573
|
+
return out.finish();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function encodeHistogram(histogram: Record<string, number> | undefined): Uint8Array {
|
|
577
|
+
const writer = new BinaryWriter();
|
|
578
|
+
const entries = Object.entries(histogram ?? {}).sort((a, b) => Number(a[0]) - Number(b[0]));
|
|
579
|
+
writeUVarint(writer, entries.length);
|
|
580
|
+
for (const [bucket, count] of entries) {
|
|
581
|
+
writeZigZagVarint(writer, Number(bucket));
|
|
582
|
+
writeUVarint(writer, Math.trunc(count));
|
|
583
|
+
}
|
|
584
|
+
return writer.finish();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function compressIntervalPayload(payload: Uint8Array): { compression: number; payload: Uint8Array } {
|
|
588
|
+
if (payload.byteLength === 0) {
|
|
589
|
+
return { compression: AGG_INTERVAL_COMPRESSION_NONE, payload };
|
|
590
|
+
}
|
|
591
|
+
const compressed = new Uint8Array(zstdCompressSync(payload));
|
|
592
|
+
if (compressed.byteLength >= payload.byteLength) {
|
|
593
|
+
return { compression: AGG_INTERVAL_COMPRESSION_NONE, payload };
|
|
594
|
+
}
|
|
595
|
+
return { compression: AGG_INTERVAL_COMPRESSION_ZSTD, payload: compressed };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function decodeHistogram(
|
|
599
|
+
offsets: Uint32Array | undefined,
|
|
600
|
+
data: Uint8Array | undefined,
|
|
601
|
+
groupIndex: number
|
|
602
|
+
): Record<string, number> | undefined {
|
|
603
|
+
if (!offsets || !data || groupIndex + 1 >= offsets.length) return undefined;
|
|
604
|
+
const start = offsets[groupIndex] ?? 0;
|
|
605
|
+
const end = offsets[groupIndex + 1] ?? start;
|
|
606
|
+
if (end <= start) return undefined;
|
|
607
|
+
const cursor = new BinaryCursor(data.subarray(start, end));
|
|
608
|
+
const entryCount = Number(readUVarint(cursor));
|
|
609
|
+
const histogram: Record<string, number> = {};
|
|
610
|
+
for (let index = 0; index < entryCount; index++) {
|
|
611
|
+
histogram[String(Number(readZigZagVarint(cursor)))] = Number(readUVarint(cursor));
|
|
612
|
+
}
|
|
613
|
+
return Object.keys(histogram).length > 0 ? histogram : undefined;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function decodeNullableNumber(value: number | undefined): number | null {
|
|
617
|
+
if (value == null || Number.isNaN(value)) return null;
|
|
618
|
+
return value;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function slicePayload(bytes: Uint8Array, offset: number, length: number, message: string): Uint8Array {
|
|
622
|
+
if (offset < 0 || length < 0 || offset + length > bytes.byteLength) {
|
|
623
|
+
throw new BinaryPayloadError(message);
|
|
624
|
+
}
|
|
625
|
+
return bytes.subarray(offset, offset + length);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function decodeU32Array(bytes: Uint8Array): Uint32Array {
|
|
629
|
+
const out = new Uint32Array(Math.floor(bytes.byteLength / 4));
|
|
630
|
+
for (let index = 0; index < out.length; index++) out[index] = readU32(bytes, index * 4);
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function decodeF64Array(bytes: Uint8Array): Float64Array {
|
|
635
|
+
const out = new Float64Array(Math.floor(bytes.byteLength / 8));
|
|
636
|
+
for (let index = 0; index < out.length; index++) out[index] = readF64(bytes, index * 8);
|
|
637
|
+
return out;
|
|
638
|
+
}
|