@gscdump/engine 0.33.7 → 0.33.9

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.
@@ -15,7 +15,7 @@ interface RollupEngine {
15
15
  runSQL: (opts: {
16
16
  ctx: TenantCtx;
17
17
  fileSets: Record<string, FileSetRef>;
18
- table?: import('@gscdump/engine/contracts').TableName;
18
+ table?: TableName$1;
19
19
  sql: string;
20
20
  params?: unknown[];
21
21
  /**
@@ -26,7 +26,7 @@ interface RollupEngine {
26
26
  */
27
27
  searchType?: SearchType;
28
28
  }) => Promise<{
29
- rows: import('@gscdump/engine/contracts').Row[];
29
+ rows: Row$1[];
30
30
  }>;
31
31
  /**
32
32
  * Read the live manifest for a (tenant, table[, searchType]) cohort —
@@ -37,7 +37,7 @@ interface RollupEngine {
37
37
  */
38
38
  listPartitions: (opts: {
39
39
  ctx: TenantCtx;
40
- table: import('@gscdump/engine/contracts').TableName;
40
+ table: TableName$1;
41
41
  searchType?: SearchType;
42
42
  }) => Promise<Array<{
43
43
  partition: string;
@@ -385,14 +385,13 @@ declare const queryCanonicalDailyRollup: RollupDef;
385
385
  * returning `{ done:true }`. `builtAt` MUST be stable across the continuation chain
386
386
  * (it versions both the part keys and the final rollup key).
387
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
+ * INTRA-WINDOW resumability: the deadline is checked between raw-query hash shards,
389
+ * not just between date windows. A single high-cardinality day can spend a full
390
+ * reservation inside one grouped/sorted aggregate before the deadline check gets
391
+ * control back. `pageOffset` is the next shard index for the current window, so a
392
+ * continuation resumes the SAME day at the next shard. Parts are keyed by
393
+ * `(windowOffset, pageOffset)`; multiple parts may contain the same canonical/date
394
+ * from different raw-query shards, and rollup reads sum over the union.
396
395
  */
397
396
  declare function rebuildCanonicalDailyResumable(opts: {
398
397
  engine: RollupEngine;
@@ -400,9 +399,11 @@ declare function rebuildCanonicalDailyResumable(opts: {
400
399
  dataSource: DataSource;
401
400
  searchType?: SearchType;
402
401
  builtAt: number;
403
- windowOffset: number; /** Resume the `windowOffset` window at this output-page offset (0 = window start). */
402
+ windowOffset: number; /** Resume the `windowOffset` window at this shard offset (0 = window start). */
404
403
  pageOffset?: number; /** Output rows per page (default `ROLLUP_PAGE_ROWS_DAILY`). Injectable for tests. */
405
- pageRows?: number;
404
+ pageRows?: number; /** Cap each input window's day span (default `DAILY_MAX_WINDOW_DAYS`). */
405
+ maxWindowDays?: number; /** Split each date window by raw-query hash before grouping (1 = no sharding). */
406
+ shardCount?: number;
406
407
  deadlineMs: number;
407
408
  }): Promise<{
408
409
  done: boolean;
package/dist/rollups.mjs CHANGED
@@ -728,8 +728,11 @@ const queryCanonicalDailyRollup = {
728
728
  })).map(mapDailyRow);
729
729
  }
730
730
  };
731
+ function canonicalDailyShardPredicate(queryExpr, shardIndex, shardCount) {
732
+ return shardCount <= 1 ? "" : ` AND (hash(${queryExpr}) % ${shardCount}) = ${shardIndex}`;
733
+ }
731
734
  function dailyWindowSqlFor(useDim, canonExpr) {
732
- return useDim ? (w) => `
735
+ return useDim ? (w, shard) => `
733
736
  SELECT
734
737
  ${canonExpr} AS query_canonical,
735
738
  CAST(q.date AS VARCHAR) AS date,
@@ -738,18 +741,18 @@ function dailyWindowSqlFor(useDim, canonExpr) {
738
741
  SUM(q.sum_position)::DOUBLE AS sum_position
739
742
  FROM read_parquet({{FILES}}, union_by_name = true) q
740
743
  LEFT JOIN read_parquet({{QUERY_DIM}}, union_by_name = true) qd ON q.query = qd.query
741
- WHERE q.date >= '${w.start}' AND q.date <= '${w.end}'
744
+ WHERE q.date >= '${w.start}' AND q.date <= '${w.end}'${canonicalDailyShardPredicate("q.query", shard?.index ?? 0, shard?.count ?? 1)}
742
745
  GROUP BY ${canonExpr}, q.date
743
- ` : (w) => `
746
+ ` : (w, shard) => `
744
747
  SELECT
745
748
  ${canonExpr} AS query_canonical,
746
- CAST(date AS VARCHAR) AS date,
747
- SUM(clicks)::BIGINT AS clicks,
748
- SUM(impressions)::BIGINT AS impressions,
749
- SUM(sum_position)::DOUBLE AS sum_position
750
- FROM read_parquet({{FILES}}, union_by_name = true)
751
- WHERE date >= '${w.start}' AND date <= '${w.end}'
752
- GROUP BY ${canonExpr}, date
749
+ CAST(q.date AS VARCHAR) AS date,
750
+ SUM(q.clicks)::BIGINT AS clicks,
751
+ SUM(q.impressions)::BIGINT AS impressions,
752
+ SUM(q.sum_position)::DOUBLE AS sum_position
753
+ FROM read_parquet({{FILES}}, union_by_name = true) q
754
+ WHERE q.date >= '${w.start}' AND q.date <= '${w.end}'${canonicalDailyShardPredicate("q.query", shard?.index ?? 0, shard?.count ?? 1)}
755
+ GROUP BY ${canonExpr}, q.date
753
756
  `;
754
757
  }
755
758
  function mapDailyRow(r) {
@@ -766,6 +769,8 @@ async function rebuildCanonicalDailyResumable(opts) {
766
769
  const sType = searchType !== void 0 ? { searchType } : {};
767
770
  const startPageOffset = opts.pageOffset ?? 0;
768
771
  const pageRows = opts.pageRows ?? 7e4;
772
+ const maxWindowDays = opts.maxWindowDays ?? 7;
773
+ const shardCount = Math.max(1, Math.floor(opts.shardCount ?? 1));
769
774
  const windows = planRollupWindows((await engine.listPartitions({
770
775
  ctx,
771
776
  table: "queries",
@@ -773,7 +778,7 @@ async function rebuildCanonicalDailyResumable(opts) {
773
778
  })).map((p) => ({
774
779
  partition: p.partition,
775
780
  bytes: p.bytes
776
- })), void 0, 7);
781
+ })), void 0, maxWindowDays);
777
782
  const windowsTotal = windows.length;
778
783
  const dimStore = createQueryDimStore({ dataSource });
779
784
  const useDim = await dimStore.loadMeta(ctx) !== null;
@@ -802,8 +807,11 @@ async function rebuildCanonicalDailyResumable(opts) {
802
807
  };
803
808
  for (; i < windowsTotal; i++) {
804
809
  const w = windows[i];
805
- const coreSql = sqlFor(w);
806
- for (;;) {
810
+ for (; page < shardCount;) {
811
+ const coreSql = sqlFor(w, {
812
+ index: page,
813
+ count: shardCount
814
+ });
807
815
  const result = await engine.runSQL({
808
816
  ctx,
809
817
  table: "queries",
@@ -815,15 +823,11 @@ async function rebuildCanonicalDailyResumable(opts) {
815
823
  },
816
824
  ...extraFileSets
817
825
  },
818
- sql: `${coreSql}\nORDER BY date, query_canonical\nLIMIT ${pageRows} OFFSET ${page}`
826
+ sql: `${coreSql}\nORDER BY date, query_canonical\nLIMIT ${pageRows}`
819
827
  });
828
+ if (result.rows.length >= pageRows) throw new Error(`query_canonical_daily shard overflow: window=${i} shard=${page}/${shardCount} returned >= ${pageRows} rows; increase shardCount or pageRows`);
820
829
  await flushPage(i, page, result.rows.map(mapDailyRow));
821
- const windowDone = result.rows.length < pageRows;
822
- page += pageRows;
823
- if (windowDone) {
824
- page = 0;
825
- break;
826
- }
830
+ page += 1;
827
831
  if (Date.now() > deadlineMs) {
828
832
  nextWindowOffset = i;
829
833
  nextPageOffset = page;
@@ -832,6 +836,7 @@ async function rebuildCanonicalDailyResumable(opts) {
832
836
  }
833
837
  }
834
838
  if (pausedMidWindow) break;
839
+ page = 0;
835
840
  if (Date.now() > deadlineMs) {
836
841
  nextWindowOffset = i + 1;
837
842
  nextPageOffset = 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine",
3
3
  "type": "module",
4
- "version": "0.33.7",
4
+ "version": "0.33.9",
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.7",
185
- "gscdump": "0.33.7"
184
+ "@gscdump/contracts": "0.33.9",
185
+ "gscdump": "0.33.9"
186
186
  },
187
187
  "devDependencies": {
188
188
  "@duckdb/duckdb-wasm": "^1.32.0",