@gscdump/engine 0.33.3 → 0.33.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rollups.d.mts +19 -8
- package/dist/rollups.mjs +59 -29
- package/package.json +3 -3
package/dist/rollups.d.mts
CHANGED
|
@@ -377,14 +377,22 @@ declare const queryCanonicalDailyRollup: RollupDef;
|
|
|
377
377
|
* Resumable, cross-invocation build of `query_canonical_daily` for a high-
|
|
378
378
|
* cardinality site whose full windowed build exceeds one job reservation (300s).
|
|
379
379
|
*
|
|
380
|
-
* Each call builds
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* (the read path is unchanged — still one rollup file) and deletes the staging,
|
|
380
|
+
* Each call builds from `(windowOffset, pageOffset)` until `deadlineMs`, writes
|
|
381
|
+
* that batch's rows to a PART parquet, and returns `{ done:false, nextWindowOffset,
|
|
382
|
+
* nextPageOffset }` for the caller to re-enqueue. When the last window is fully
|
|
383
|
+
* paged it publishes a multi-file envelope listing every part (parts are disjoint
|
|
384
|
+
* by `(query_canonical, date)`, so the read path just unions them — no merge),
|
|
386
385
|
* returning `{ done:true }`. `builtAt` MUST be stable across the continuation chain
|
|
387
|
-
* (it versions both the
|
|
386
|
+
* (it versions both the part keys and the final rollup key).
|
|
387
|
+
*
|
|
388
|
+
* INTRA-WINDOW resumability: the deadline is checked between OUTPUT PAGES, not just
|
|
389
|
+
* between windows. A single high-cardinality window's paged aggregation can exceed
|
|
390
|
+
* one 300s reservation on its own; checking only between windows let that window
|
|
391
|
+
* run unbounded and stale-reservation-loop forever (huuto.net/comparaja.pt never
|
|
392
|
+
* completed window 0). `pageOffset` lets a continuation resume the SAME window at
|
|
393
|
+
* the next page, so no single invocation runs past the deadline by more than one
|
|
394
|
+
* page. Parts are keyed by `(windowOffset, pageOffset)` so a mid-window pause and
|
|
395
|
+
* its continuation write disjoint, non-colliding files.
|
|
388
396
|
*/
|
|
389
397
|
declare function rebuildCanonicalDailyResumable(opts: {
|
|
390
398
|
engine: RollupEngine;
|
|
@@ -392,11 +400,14 @@ declare function rebuildCanonicalDailyResumable(opts: {
|
|
|
392
400
|
dataSource: DataSource;
|
|
393
401
|
searchType?: SearchType;
|
|
394
402
|
builtAt: number;
|
|
395
|
-
windowOffset: number;
|
|
403
|
+
windowOffset: number; /** Resume the `windowOffset` window at this output-page offset (0 = window start). */
|
|
404
|
+
pageOffset?: number; /** Output rows per page (default `ROLLUP_PAGE_ROWS_DAILY`). Injectable for tests. */
|
|
405
|
+
pageRows?: number;
|
|
396
406
|
deadlineMs: number;
|
|
397
407
|
}): Promise<{
|
|
398
408
|
done: boolean;
|
|
399
409
|
nextWindowOffset: number;
|
|
410
|
+
nextPageOffset: number;
|
|
400
411
|
windowsTotal: number;
|
|
401
412
|
windowsBuilt: number;
|
|
402
413
|
rowsWritten: number;
|
package/dist/rollups.mjs
CHANGED
|
@@ -764,6 +764,8 @@ function mapDailyRow(r) {
|
|
|
764
764
|
async function rebuildCanonicalDailyResumable(opts) {
|
|
765
765
|
const { engine, ctx, dataSource, searchType, builtAt, windowOffset, deadlineMs } = opts;
|
|
766
766
|
const sType = searchType !== void 0 ? { searchType } : {};
|
|
767
|
+
const startPageOffset = opts.pageOffset ?? 0;
|
|
768
|
+
const pageRows = opts.pageRows ?? 7e4;
|
|
767
769
|
const windows = planRollupWindows((await engine.listPartitions({
|
|
768
770
|
ctx,
|
|
769
771
|
table: "queries",
|
|
@@ -783,54 +785,81 @@ async function rebuildCanonicalDailyResumable(opts) {
|
|
|
783
785
|
const sqlFor = dailyWindowSqlFor(useDim, canonExpr);
|
|
784
786
|
const cols = queryCanonicalDailyRollup.parquetColumns;
|
|
785
787
|
const sortKey = queryCanonicalDailyRollup.parquetSortKey;
|
|
786
|
-
const batchRows = [];
|
|
787
788
|
let i = windowOffset;
|
|
788
|
-
|
|
789
|
+
let page = startPageOffset;
|
|
790
|
+
let nextWindowOffset = windowsTotal;
|
|
791
|
+
let nextPageOffset = 0;
|
|
792
|
+
let rowsWritten = 0;
|
|
793
|
+
const flushPage = async (windowIdx, pageOffset, rows) => {
|
|
794
|
+
if (rows.length === 0) return;
|
|
795
|
+
const key = rollupParquetKey(ctx, `${CANONICAL_DAILY_PART_STEM}__w${windowIdx}_p${pageOffset}`, builtAt, searchType);
|
|
796
|
+
await dataSource.write(key, encodeRowsToParquetFlex(rows, {
|
|
797
|
+
columns: cols,
|
|
798
|
+
sortKey
|
|
799
|
+
}));
|
|
800
|
+
rowsWritten += rows.length;
|
|
801
|
+
};
|
|
802
|
+
windowLoop: for (; i < windowsTotal; i++) {
|
|
789
803
|
const w = windows[i];
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
804
|
+
const coreSql = sqlFor(w);
|
|
805
|
+
for (;;) {
|
|
806
|
+
const result = await engine.runSQL({
|
|
807
|
+
ctx,
|
|
808
|
+
table: "queries",
|
|
809
|
+
...sType,
|
|
810
|
+
fileSets: {
|
|
811
|
+
FILES: {
|
|
812
|
+
table: "queries",
|
|
813
|
+
partitions: w.partitions
|
|
814
|
+
},
|
|
815
|
+
...extraFileSets
|
|
799
816
|
},
|
|
800
|
-
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
pageRows
|
|
805
|
-
|
|
806
|
-
|
|
817
|
+
sql: `${coreSql}\nORDER BY date, query_canonical\nLIMIT ${pageRows} OFFSET ${page}`
|
|
818
|
+
});
|
|
819
|
+
await flushPage(i, page, result.rows.map(mapDailyRow));
|
|
820
|
+
const windowDone = result.rows.length < pageRows;
|
|
821
|
+
page += pageRows;
|
|
822
|
+
if (windowDone) {
|
|
823
|
+
page = 0;
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
if (Date.now() > deadlineMs) {
|
|
827
|
+
nextWindowOffset = i;
|
|
828
|
+
nextPageOffset = page;
|
|
829
|
+
break windowLoop;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
807
832
|
if (Date.now() > deadlineMs) {
|
|
808
|
-
i
|
|
833
|
+
nextWindowOffset = i + 1;
|
|
834
|
+
nextPageOffset = 0;
|
|
809
835
|
break;
|
|
810
836
|
}
|
|
811
837
|
}
|
|
812
|
-
|
|
813
|
-
const partKey = rollupParquetKey(ctx, `${CANONICAL_DAILY_PART_STEM}__w${windowOffset}`, builtAt, searchType);
|
|
814
|
-
await dataSource.write(partKey, encodeRowsToParquetFlex(batchRows, {
|
|
815
|
-
columns: cols,
|
|
816
|
-
sortKey
|
|
817
|
-
}));
|
|
818
|
-
if (nextWindowOffset < windowsTotal) return {
|
|
838
|
+
if (nextWindowOffset < windowsTotal || nextPageOffset > 0) return {
|
|
819
839
|
done: false,
|
|
820
840
|
nextWindowOffset,
|
|
841
|
+
nextPageOffset,
|
|
821
842
|
windowsTotal,
|
|
822
843
|
windowsBuilt: nextWindowOffset - windowOffset,
|
|
823
|
-
rowsWritten
|
|
844
|
+
rowsWritten
|
|
824
845
|
};
|
|
825
846
|
const partPrefixDir = rollupParquetKey(ctx, CANONICAL_DAILY_PART_STEM, builtAt, searchType).replace(/[^/]*$/, "");
|
|
826
847
|
const partKeys = (await dataSource.list(partPrefixDir)).filter((k) => k.includes(`${CANONICAL_DAILY_PART_STEM}__w`) && k.endsWith(`__v${builtAt}.parquet`)).sort();
|
|
848
|
+
if (partKeys.length === 0) {
|
|
849
|
+
const emptyKey = rollupParquetKey(ctx, `${CANONICAL_DAILY_PART_STEM}__w0_p0`, builtAt, searchType);
|
|
850
|
+
await dataSource.write(emptyKey, encodeRowsToParquetFlex([], {
|
|
851
|
+
columns: cols,
|
|
852
|
+
sortKey
|
|
853
|
+
}));
|
|
854
|
+
partKeys.push(emptyKey);
|
|
855
|
+
}
|
|
827
856
|
const envelope = {
|
|
828
857
|
version: 1,
|
|
829
858
|
id: CANONICAL_DAILY_ROLLUP_FINAL_ID,
|
|
830
859
|
builtAt,
|
|
831
860
|
windowDays: queryCanonicalDailyRollup.windowDays,
|
|
832
861
|
payload: {
|
|
833
|
-
parquetKey: partKeys[0]
|
|
862
|
+
parquetKey: partKeys[0],
|
|
834
863
|
parquetKeys: partKeys,
|
|
835
864
|
rowCount: 0
|
|
836
865
|
}
|
|
@@ -839,9 +868,10 @@ async function rebuildCanonicalDailyResumable(opts) {
|
|
|
839
868
|
return {
|
|
840
869
|
done: true,
|
|
841
870
|
nextWindowOffset,
|
|
871
|
+
nextPageOffset,
|
|
842
872
|
windowsTotal,
|
|
843
873
|
windowsBuilt: nextWindowOffset - windowOffset,
|
|
844
|
-
rowsWritten
|
|
874
|
+
rowsWritten
|
|
845
875
|
};
|
|
846
876
|
}
|
|
847
877
|
const indexingMetadataRollup = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/engine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.33.
|
|
4
|
+
"version": "0.33.5",
|
|
5
5
|
"description": "Append-only Parquet/DuckDB storage engine + planner + adapters for the gscdump pipeline. Node + edge runtimes; opt-in heavy peers.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -181,8 +181,8 @@
|
|
|
181
181
|
"dependencies": {
|
|
182
182
|
"drizzle-orm": "1.0.0-rc.3",
|
|
183
183
|
"proper-lockfile": "^4.1.2",
|
|
184
|
-
"
|
|
185
|
-
"gscdump": "0.33.
|
|
184
|
+
"gscdump": "0.33.5",
|
|
185
|
+
"@gscdump/contracts": "0.33.5"
|
|
186
186
|
},
|
|
187
187
|
"devDependencies": {
|
|
188
188
|
"@duckdb/duckdb-wasm": "^1.32.0",
|