@gscdump/engine 0.33.3 → 0.33.4

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.
@@ -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 the day-capped windows from `windowOffset` until `deadlineMs`,
381
- * writes that batch's rows to a STAGING partial parquet (windows are disjoint by
382
- * date, so partials never overlap), and returns `{ done:false, nextWindowOffset }`
383
- * for the caller to re-enqueue. When the last window is built it MERGES every
384
- * staging partial into the canonical `query_canonical_daily` parquet + envelope
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 staging keys and the final rollup key).
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",
@@ -785,39 +787,54 @@ async function rebuildCanonicalDailyResumable(opts) {
785
787
  const sortKey = queryCanonicalDailyRollup.parquetSortKey;
786
788
  const batchRows = [];
787
789
  let i = windowOffset;
788
- for (; i < windowsTotal; i++) {
790
+ let page = startPageOffset;
791
+ let nextWindowOffset = windowsTotal;
792
+ let nextPageOffset = 0;
793
+ windowLoop: for (; i < windowsTotal; i++) {
789
794
  const w = windows[i];
790
- const winRows = await runPagedQuery({
791
- engine,
792
- ctx,
793
- table: "queries",
794
- ...sType,
795
- fileSets: {
796
- FILES: {
797
- table: "queries",
798
- partitions: w.partitions
795
+ const coreSql = sqlFor(w);
796
+ for (;;) {
797
+ const result = await engine.runSQL({
798
+ ctx,
799
+ table: "queries",
800
+ ...sType,
801
+ fileSets: {
802
+ FILES: {
803
+ table: "queries",
804
+ partitions: w.partitions
805
+ },
806
+ ...extraFileSets
799
807
  },
800
- ...extraFileSets
801
- },
802
- coreSql: sqlFor(w),
803
- orderBy: "date, query_canonical",
804
- pageRows: ROLLUP_PAGE_ROWS_DAILY
805
- });
806
- for (const r of winRows) batchRows.push(mapDailyRow(r));
808
+ sql: `${coreSql}\nORDER BY date, query_canonical\nLIMIT ${pageRows} OFFSET ${page}`
809
+ });
810
+ for (const r of result.rows) batchRows.push(mapDailyRow(r));
811
+ const windowDone = result.rows.length < pageRows;
812
+ page += pageRows;
813
+ if (windowDone) {
814
+ page = 0;
815
+ break;
816
+ }
817
+ if (Date.now() > deadlineMs) {
818
+ nextWindowOffset = i;
819
+ nextPageOffset = page;
820
+ break windowLoop;
821
+ }
822
+ }
807
823
  if (Date.now() > deadlineMs) {
808
- i++;
824
+ nextWindowOffset = i + 1;
825
+ nextPageOffset = 0;
809
826
  break;
810
827
  }
811
828
  }
812
- const nextWindowOffset = i;
813
- const partKey = rollupParquetKey(ctx, `${CANONICAL_DAILY_PART_STEM}__w${windowOffset}`, builtAt, searchType);
829
+ const partKey = rollupParquetKey(ctx, `${CANONICAL_DAILY_PART_STEM}__w${windowOffset}_p${startPageOffset}`, builtAt, searchType);
814
830
  await dataSource.write(partKey, encodeRowsToParquetFlex(batchRows, {
815
831
  columns: cols,
816
832
  sortKey
817
833
  }));
818
- if (nextWindowOffset < windowsTotal) return {
834
+ if (nextWindowOffset < windowsTotal || nextPageOffset > 0) return {
819
835
  done: false,
820
836
  nextWindowOffset,
837
+ nextPageOffset,
821
838
  windowsTotal,
822
839
  windowsBuilt: nextWindowOffset - windowOffset,
823
840
  rowsWritten: batchRows.length
@@ -839,6 +856,7 @@ async function rebuildCanonicalDailyResumable(opts) {
839
856
  return {
840
857
  done: true,
841
858
  nextWindowOffset,
859
+ nextPageOffset,
842
860
  windowsTotal,
843
861
  windowsBuilt: nextWindowOffset - windowOffset,
844
862
  rowsWritten: batchRows.length
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine",
3
3
  "type": "module",
4
- "version": "0.33.3",
4
+ "version": "0.33.4",
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
- "@gscdump/contracts": "0.33.3",
185
- "gscdump": "0.33.3"
184
+ "@gscdump/contracts": "0.33.4",
185
+ "gscdump": "0.33.4"
186
186
  },
187
187
  "devDependencies": {
188
188
  "@duckdb/duckdb-wasm": "^1.32.0",