@gscdump/engine 0.31.8 → 0.31.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.
@@ -364,6 +364,34 @@ declare const queryCanonicalVariantsRollup: RollupDef;
364
364
  * `RunOptimizedQueryOptions.canonicalSource`).
365
365
  */
366
366
  declare const queryCanonicalDailyRollup: RollupDef;
367
+ /**
368
+ * Resumable, cross-invocation build of `query_canonical_daily` for a high-
369
+ * cardinality site whose full windowed build exceeds one job reservation (300s).
370
+ *
371
+ * Each call builds the day-capped windows from `windowOffset` until `deadlineMs`,
372
+ * writes that batch's rows to a STAGING partial parquet (windows are disjoint by
373
+ * date, so partials never overlap), and returns `{ done:false, nextWindowOffset }`
374
+ * for the caller to re-enqueue. When the last window is built it MERGES every
375
+ * staging partial into the canonical `query_canonical_daily` parquet + envelope
376
+ * (the read path is unchanged — still one rollup file) and deletes the staging,
377
+ * returning `{ done:true }`. `builtAt` MUST be stable across the continuation chain
378
+ * (it versions both the staging keys and the final rollup key).
379
+ */
380
+ declare function rebuildCanonicalDailyResumable(opts: {
381
+ engine: RollupEngine;
382
+ ctx: TenantCtx;
383
+ dataSource: DataSource;
384
+ searchType?: SearchType;
385
+ builtAt: number;
386
+ windowOffset: number;
387
+ deadlineMs: number;
388
+ }): Promise<{
389
+ done: boolean;
390
+ nextWindowOffset: number;
391
+ windowsTotal: number;
392
+ windowsBuilt: number;
393
+ rowsWritten: number;
394
+ }>;
367
395
  /**
368
396
  * Aggregates the per-URL Indexing API metadata entity store (populated by
369
397
  * `gscdump entities indexing snapshot`) into daily counts of `URL_UPDATED`
@@ -449,4 +477,4 @@ declare const DEFAULT_ROLLUPS: readonly RollupDef[];
449
477
  * (CLI: `gscdump rollups --with-canonical`).
450
478
  */
451
479
  declare const CANONICAL_ROLLUPS: readonly RollupDef[];
452
- export { CANONICAL_ROLLUPS, DAILY_MAX_WINDOW_DAYS, DEFAULT_ROLLUPS, ParquetRollupPointer, ROLLUP_PAGE_ROWS, ROLLUP_PAGE_ROWS_DAILY, ROLLUP_PAGE_ROWS_WIDE, RebuildDailyFromHourlyOptions, RebuildRollupResult, RebuildRollupsOptions, RollupBucket, RollupCtx, RollupDef, RollupEngine, RollupEnvelope, WINDOW_BYTE_BUDGET, dailyTotalsRollup, indexPercentRollup, indexingHealthRollup, indexingMetadataRollup, partitionDaySpan, partitionsInRange, planRollupWindows, queryCanonicalDailyRollup, queryCanonicalVariantsRollup, readLatestRollup, rebuildDailyFromHourly, rebuildRollups, rollupKey, rollupParquetKey, runWindowed, sitemapChanges28dRollup, sitemapHealthRollup, topCountries28dRollup, topKeywords28dParquetRollup, topKeywords28dRollup, topPages28dRollup, weeklyTotalsRollup };
480
+ export { CANONICAL_ROLLUPS, DAILY_MAX_WINDOW_DAYS, DEFAULT_ROLLUPS, ParquetRollupPointer, ROLLUP_PAGE_ROWS, ROLLUP_PAGE_ROWS_DAILY, ROLLUP_PAGE_ROWS_WIDE, RebuildDailyFromHourlyOptions, RebuildRollupResult, RebuildRollupsOptions, RollupBucket, RollupCtx, RollupDef, RollupEngine, RollupEnvelope, WINDOW_BYTE_BUDGET, dailyTotalsRollup, indexPercentRollup, indexingHealthRollup, indexingMetadataRollup, partitionDaySpan, partitionsInRange, planRollupWindows, queryCanonicalDailyRollup, queryCanonicalVariantsRollup, readLatestRollup, rebuildCanonicalDailyResumable, rebuildDailyFromHourly, rebuildRollups, rollupKey, rollupParquetKey, runWindowed, sitemapChanges28dRollup, sitemapHealthRollup, topCountries28dRollup, topKeywords28dParquetRollup, topKeywords28dRollup, topPages28dRollup, weeklyTotalsRollup };
package/dist/rollups.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./_chunks/layout.mjs";
2
2
  import { engineErrors } from "./errors.mjs";
3
- import { encodeRowsToParquetFlex } from "./adapters/hyparquet.mjs";
3
+ import { decodeParquetToRows, encodeRowsToParquetFlex } from "./adapters/hyparquet.mjs";
4
4
  import { createIndexingMetadataStore, createQueryDimStore, createSitemapStore, inspectionParquetKey, sitemapUrlsIndexPrefix } from "./_chunks/entities.mjs";
5
5
  import { MS_PER_DAY } from "gscdump";
6
6
  function rollupPrefix(ctx, searchType) {
@@ -713,37 +713,135 @@ const queryCanonicalDailyRollup = {
713
713
  pageRows: ROLLUP_PAGE_ROWS_DAILY
714
714
  },
715
715
  maxWindowDays: 7,
716
- sqlFor: useDim ? (w) => `
717
- SELECT
718
- ${canonExpr} AS query_canonical,
719
- CAST(q.date AS VARCHAR) AS date,
720
- SUM(q.clicks)::BIGINT AS clicks,
721
- SUM(q.impressions)::BIGINT AS impressions,
722
- SUM(q.sum_position)::DOUBLE AS sum_position
723
- FROM read_parquet({{FILES}}, union_by_name = true) q
724
- LEFT JOIN read_parquet({{QUERY_DIM}}, union_by_name = true) qd ON q.query = qd.query
725
- WHERE q.date >= '${w.start}' AND q.date <= '${w.end}'
726
- GROUP BY ${canonExpr}, q.date
727
- ` : (w) => `
728
- SELECT
729
- ${canonExpr} AS query_canonical,
730
- CAST(date AS VARCHAR) AS date,
731
- SUM(clicks)::BIGINT AS clicks,
732
- SUM(impressions)::BIGINT AS impressions,
733
- SUM(sum_position)::DOUBLE AS sum_position
734
- FROM read_parquet({{FILES}}, union_by_name = true)
735
- WHERE date >= '${w.start}' AND date <= '${w.end}'
736
- GROUP BY ${canonExpr}, date
737
- `
738
- })).map((r) => ({
739
- query_canonical: String(r.query_canonical),
740
- date: String(r.date),
741
- clicks: BigInt(r.clicks),
742
- impressions: BigInt(r.impressions),
743
- sum_position: Number(r.sum_position)
744
- }));
716
+ sqlFor: dailyWindowSqlFor(useDim, canonExpr)
717
+ })).map(mapDailyRow);
745
718
  }
746
719
  };
720
+ function dailyWindowSqlFor(useDim, canonExpr) {
721
+ return useDim ? (w) => `
722
+ SELECT
723
+ ${canonExpr} AS query_canonical,
724
+ CAST(q.date AS VARCHAR) AS date,
725
+ SUM(q.clicks)::BIGINT AS clicks,
726
+ SUM(q.impressions)::BIGINT AS impressions,
727
+ SUM(q.sum_position)::DOUBLE AS sum_position
728
+ FROM read_parquet({{FILES}}, union_by_name = true) q
729
+ LEFT JOIN read_parquet({{QUERY_DIM}}, union_by_name = true) qd ON q.query = qd.query
730
+ WHERE q.date >= '${w.start}' AND q.date <= '${w.end}'
731
+ GROUP BY ${canonExpr}, q.date
732
+ ` : (w) => `
733
+ SELECT
734
+ ${canonExpr} AS query_canonical,
735
+ CAST(date AS VARCHAR) AS date,
736
+ SUM(clicks)::BIGINT AS clicks,
737
+ SUM(impressions)::BIGINT AS impressions,
738
+ SUM(sum_position)::DOUBLE AS sum_position
739
+ FROM read_parquet({{FILES}}, union_by_name = true)
740
+ WHERE date >= '${w.start}' AND date <= '${w.end}'
741
+ GROUP BY ${canonExpr}, date
742
+ `;
743
+ }
744
+ function mapDailyRow(r) {
745
+ return {
746
+ query_canonical: String(r.query_canonical),
747
+ date: String(r.date),
748
+ clicks: BigInt(r.clicks),
749
+ impressions: BigInt(r.impressions),
750
+ sum_position: Number(r.sum_position)
751
+ };
752
+ }
753
+ const CANONICAL_DAILY_STAGING_STEM = "query_canonical_daily__staging";
754
+ async function rebuildCanonicalDailyResumable(opts) {
755
+ const { engine, ctx, dataSource, searchType, builtAt, windowOffset, deadlineMs } = opts;
756
+ const sType = searchType !== void 0 ? { searchType } : {};
757
+ const windows = planRollupWindows((await engine.listPartitions({
758
+ ctx,
759
+ table: "queries",
760
+ ...sType
761
+ })).map((p) => ({
762
+ partition: p.partition,
763
+ bytes: p.bytes
764
+ })), void 0, 7);
765
+ const windowsTotal = windows.length;
766
+ const dimStore = createQueryDimStore({ dataSource });
767
+ const useDim = await dimStore.loadMeta(ctx) !== null;
768
+ const canonExpr = useDim ? `COALESCE(qd.query_canonical, NULLIF(q.query_canonical, ''), q.query)` : `COALESCE(NULLIF(query_canonical, ''), query)`;
769
+ const extraFileSets = useDim ? { QUERY_DIM: {
770
+ table: "queries",
771
+ keys: [dimStore.parquetKey(ctx)]
772
+ } } : void 0;
773
+ const sqlFor = dailyWindowSqlFor(useDim, canonExpr);
774
+ const cols = queryCanonicalDailyRollup.parquetColumns;
775
+ const sortKey = queryCanonicalDailyRollup.parquetSortKey;
776
+ const batchRows = [];
777
+ let i = windowOffset;
778
+ for (; i < windowsTotal; i++) {
779
+ const w = windows[i];
780
+ const winRows = await runPagedQuery({
781
+ engine,
782
+ ctx,
783
+ table: "queries",
784
+ ...sType,
785
+ fileSets: {
786
+ FILES: {
787
+ table: "queries",
788
+ partitions: w.partitions
789
+ },
790
+ ...extraFileSets
791
+ },
792
+ coreSql: sqlFor(w),
793
+ orderBy: "date, query_canonical",
794
+ pageRows: ROLLUP_PAGE_ROWS_DAILY
795
+ });
796
+ for (const r of winRows) batchRows.push(mapDailyRow(r));
797
+ if (Date.now() > deadlineMs) {
798
+ i++;
799
+ break;
800
+ }
801
+ }
802
+ const nextWindowOffset = i;
803
+ const stagingKey = rollupParquetKey(ctx, `${CANONICAL_DAILY_STAGING_STEM}__w${windowOffset}`, builtAt, searchType);
804
+ await dataSource.write(stagingKey, encodeRowsToParquetFlex(batchRows, {
805
+ columns: cols,
806
+ sortKey
807
+ }));
808
+ if (nextWindowOffset < windowsTotal) return {
809
+ done: false,
810
+ nextWindowOffset,
811
+ windowsTotal,
812
+ windowsBuilt: nextWindowOffset - windowOffset,
813
+ rowsWritten: batchRows.length
814
+ };
815
+ const stagingPrefix = rollupParquetKey(ctx, `${CANONICAL_DAILY_STAGING_STEM}__`, builtAt, searchType).replace(/__v\d+\.parquet$/, "");
816
+ const stagingKeys = (await dataSource.list(stagingPrefix.replace(/[^/]*$/, ""))).filter((k) => k.includes(`${CANONICAL_DAILY_STAGING_STEM}__w`) && k.includes(`__v${builtAt}.parquet`));
817
+ const merged = [];
818
+ for (const k of stagingKeys) merged.push(...await decodeParquetToRows(await dataSource.read(k), { columns: cols.map((c) => c.name) }));
819
+ const parquetKey = rollupParquetKey(ctx, CANONICAL_DAILY_ROLLUP_FINAL_ID, builtAt, searchType);
820
+ await dataSource.write(parquetKey, encodeRowsToParquetFlex(merged, {
821
+ columns: cols,
822
+ sortKey
823
+ }));
824
+ const envelope = {
825
+ version: 1,
826
+ id: CANONICAL_DAILY_ROLLUP_FINAL_ID,
827
+ builtAt,
828
+ windowDays: queryCanonicalDailyRollup.windowDays,
829
+ payload: {
830
+ parquetKey,
831
+ rowCount: merged.length
832
+ }
833
+ };
834
+ await dataSource.write(rollupKey(ctx, CANONICAL_DAILY_ROLLUP_FINAL_ID, builtAt, searchType), new TextEncoder().encode(JSON.stringify(envelope)));
835
+ await dataSource.delete(stagingKeys);
836
+ return {
837
+ done: true,
838
+ nextWindowOffset,
839
+ windowsTotal,
840
+ windowsBuilt: nextWindowOffset - windowOffset,
841
+ rowsWritten: merged.length
842
+ };
843
+ }
844
+ const CANONICAL_DAILY_ROLLUP_FINAL_ID = "query_canonical_daily";
747
845
  const indexingMetadataRollup = {
748
846
  id: "indexing_metadata",
749
847
  windowDays: null,
@@ -1056,4 +1154,4 @@ const DEFAULT_ROLLUPS = [
1056
1154
  sitemapChanges28dRollup
1057
1155
  ];
1058
1156
  const CANONICAL_ROLLUPS = [queryCanonicalVariantsRollup, queryCanonicalDailyRollup];
1059
- export { CANONICAL_ROLLUPS, DAILY_MAX_WINDOW_DAYS, DEFAULT_ROLLUPS, ROLLUP_PAGE_ROWS, ROLLUP_PAGE_ROWS_DAILY, ROLLUP_PAGE_ROWS_WIDE, WINDOW_BYTE_BUDGET, dailyTotalsRollup, indexPercentRollup, indexingHealthRollup, indexingMetadataRollup, partitionDaySpan, partitionsInRange, planRollupWindows, queryCanonicalDailyRollup, queryCanonicalVariantsRollup, readLatestRollup, rebuildDailyFromHourly, rebuildRollups, rollupKey, rollupParquetKey, runWindowed, sitemapChanges28dRollup, sitemapHealthRollup, topCountries28dRollup, topKeywords28dParquetRollup, topKeywords28dRollup, topPages28dRollup, weeklyTotalsRollup };
1157
+ export { CANONICAL_ROLLUPS, DAILY_MAX_WINDOW_DAYS, DEFAULT_ROLLUPS, ROLLUP_PAGE_ROWS, ROLLUP_PAGE_ROWS_DAILY, ROLLUP_PAGE_ROWS_WIDE, WINDOW_BYTE_BUDGET, dailyTotalsRollup, indexPercentRollup, indexingHealthRollup, indexingMetadataRollup, partitionDaySpan, partitionsInRange, planRollupWindows, queryCanonicalDailyRollup, queryCanonicalVariantsRollup, readLatestRollup, rebuildCanonicalDailyResumable, rebuildDailyFromHourly, rebuildRollups, rollupKey, rollupParquetKey, runWindowed, sitemapChanges28dRollup, sitemapHealthRollup, topCountries28dRollup, topKeywords28dParquetRollup, topKeywords28dRollup, topPages28dRollup, weeklyTotalsRollup };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine",
3
3
  "type": "module",
4
- "version": "0.31.8",
4
+ "version": "0.31.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.31.8",
185
- "gscdump": "0.31.8"
184
+ "gscdump": "0.31.9",
185
+ "@gscdump/contracts": "0.31.9"
186
186
  },
187
187
  "devDependencies": {
188
188
  "@duckdb/duckdb-wasm": "^1.32.0",