@gscdump/engine 0.33.8 → 0.33.10

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,10 +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
404
  pageRows?: number; /** Cap each input window's day span (default `DAILY_MAX_WINDOW_DAYS`). */
406
- maxWindowDays?: number;
405
+ maxWindowDays?: number; /** Split each date window by raw-query hash before grouping (1 = no sharding). */
406
+ shardCount?: number;
407
407
  deadlineMs: number;
408
408
  }): Promise<{
409
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) {
@@ -767,6 +770,7 @@ async function rebuildCanonicalDailyResumable(opts) {
767
770
  const startPageOffset = opts.pageOffset ?? 0;
768
771
  const pageRows = opts.pageRows ?? 7e4;
769
772
  const maxWindowDays = opts.maxWindowDays ?? 7;
773
+ const shardCount = Math.max(1, Math.floor(opts.shardCount ?? 1));
770
774
  const windows = planRollupWindows((await engine.listPartitions({
771
775
  ctx,
772
776
  table: "queries",
@@ -803,8 +807,11 @@ async function rebuildCanonicalDailyResumable(opts) {
803
807
  };
804
808
  for (; i < windowsTotal; i++) {
805
809
  const w = windows[i];
806
- const coreSql = sqlFor(w);
807
- for (;;) {
810
+ for (; page < shardCount;) {
811
+ const coreSql = sqlFor(w, {
812
+ index: page,
813
+ count: shardCount
814
+ });
808
815
  const result = await engine.runSQL({
809
816
  ctx,
810
817
  table: "queries",
@@ -816,15 +823,11 @@ async function rebuildCanonicalDailyResumable(opts) {
816
823
  },
817
824
  ...extraFileSets
818
825
  },
819
- sql: `${coreSql}\nORDER BY date, query_canonical\nLIMIT ${pageRows} OFFSET ${page}`
826
+ sql: `${coreSql}\nORDER BY date, query_canonical\nLIMIT ${pageRows}`
820
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`);
821
829
  await flushPage(i, page, result.rows.map(mapDailyRow));
822
- const windowDone = result.rows.length < pageRows;
823
- page += pageRows;
824
- if (windowDone) {
825
- page = 0;
826
- break;
827
- }
830
+ page += 1;
828
831
  if (Date.now() > deadlineMs) {
829
832
  nextWindowOffset = i;
830
833
  nextPageOffset = page;
@@ -833,6 +836,7 @@ async function rebuildCanonicalDailyResumable(opts) {
833
836
  }
834
837
  }
835
838
  if (pausedMidWindow) break;
839
+ page = 0;
836
840
  if (Date.now() > deadlineMs) {
837
841
  nextWindowOffset = i + 1;
838
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.8",
4
+ "version": "0.33.10",
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": "0.33.8",
185
- "@gscdump/contracts": "0.33.8"
184
+ "@gscdump/contracts": "0.33.10",
185
+ "gscdump": "0.33.10"
186
186
  },
187
187
  "devDependencies": {
188
188
  "@duckdb/duckdb-wasm": "^1.32.0",