@gscdump/engine 0.31.8 → 0.31.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.
- package/dist/rollups.d.mts +38 -1
- package/dist/rollups.mjs +121 -30
- package/package.json +3 -3
package/dist/rollups.d.mts
CHANGED
|
@@ -123,6 +123,15 @@ interface RollupEnvelope<T = unknown> {
|
|
|
123
123
|
interface ParquetRollupPointer {
|
|
124
124
|
parquetKey: string;
|
|
125
125
|
rowCount: number;
|
|
126
|
+
/**
|
|
127
|
+
* MULTI-FILE rollup: when set, the rollup is the UNION of these parquet keys
|
|
128
|
+
* (disjoint by the grain's partition column, e.g. `date` for the resumable
|
|
129
|
+
* `query_canonical_daily` build). Readers MUST union all keys; `parquetKey`
|
|
130
|
+
* stays populated (the first part) for single-file readers. Avoids a JS
|
|
131
|
+
* merge/re-encode of the whole rollup — the scaling bottleneck for a
|
|
132
|
+
* cross-invocation resumable build.
|
|
133
|
+
*/
|
|
134
|
+
parquetKeys?: string[];
|
|
126
135
|
}
|
|
127
136
|
declare function rollupKey(ctx: TenantCtx, id: string, builtAt: number, searchType?: SearchType): string;
|
|
128
137
|
declare function rollupParquetKey(ctx: TenantCtx, id: string, builtAt: number, searchType?: SearchType): string;
|
|
@@ -364,6 +373,34 @@ declare const queryCanonicalVariantsRollup: RollupDef;
|
|
|
364
373
|
* `RunOptimizedQueryOptions.canonicalSource`).
|
|
365
374
|
*/
|
|
366
375
|
declare const queryCanonicalDailyRollup: RollupDef;
|
|
376
|
+
/**
|
|
377
|
+
* Resumable, cross-invocation build of `query_canonical_daily` for a high-
|
|
378
|
+
* cardinality site whose full windowed build exceeds one job reservation (300s).
|
|
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,
|
|
386
|
+
* returning `{ done:true }`. `builtAt` MUST be stable across the continuation chain
|
|
387
|
+
* (it versions both the staging keys and the final rollup key).
|
|
388
|
+
*/
|
|
389
|
+
declare function rebuildCanonicalDailyResumable(opts: {
|
|
390
|
+
engine: RollupEngine;
|
|
391
|
+
ctx: TenantCtx;
|
|
392
|
+
dataSource: DataSource;
|
|
393
|
+
searchType?: SearchType;
|
|
394
|
+
builtAt: number;
|
|
395
|
+
windowOffset: number;
|
|
396
|
+
deadlineMs: number;
|
|
397
|
+
}): Promise<{
|
|
398
|
+
done: boolean;
|
|
399
|
+
nextWindowOffset: number;
|
|
400
|
+
windowsTotal: number;
|
|
401
|
+
windowsBuilt: number;
|
|
402
|
+
rowsWritten: number;
|
|
403
|
+
}>;
|
|
367
404
|
/**
|
|
368
405
|
* Aggregates the per-URL Indexing API metadata entity store (populated by
|
|
369
406
|
* `gscdump entities indexing snapshot`) into daily counts of `URL_UPDATED`
|
|
@@ -449,4 +486,4 @@ declare const DEFAULT_ROLLUPS: readonly RollupDef[];
|
|
|
449
486
|
* (CLI: `gscdump rollups --with-canonical`).
|
|
450
487
|
*/
|
|
451
488
|
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 };
|
|
489
|
+
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
|
@@ -713,37 +713,128 @@ const queryCanonicalDailyRollup = {
|
|
|
713
713
|
pageRows: ROLLUP_PAGE_ROWS_DAILY
|
|
714
714
|
},
|
|
715
715
|
maxWindowDays: 7,
|
|
716
|
-
sqlFor: useDim
|
|
717
|
-
|
|
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
|
+
async function rebuildCanonicalDailyResumable(opts) {
|
|
754
|
+
const { engine, ctx, dataSource, searchType, builtAt, windowOffset, deadlineMs } = opts;
|
|
755
|
+
const sType = searchType !== void 0 ? { searchType } : {};
|
|
756
|
+
const windows = planRollupWindows((await engine.listPartitions({
|
|
757
|
+
ctx,
|
|
758
|
+
table: "queries",
|
|
759
|
+
...sType
|
|
760
|
+
})).map((p) => ({
|
|
761
|
+
partition: p.partition,
|
|
762
|
+
bytes: p.bytes
|
|
763
|
+
})), void 0, 7);
|
|
764
|
+
const windowsTotal = windows.length;
|
|
765
|
+
const dimStore = createQueryDimStore({ dataSource });
|
|
766
|
+
const useDim = await dimStore.loadMeta(ctx) !== null;
|
|
767
|
+
const canonExpr = useDim ? `COALESCE(qd.query_canonical, NULLIF(q.query_canonical, ''), q.query)` : `COALESCE(NULLIF(query_canonical, ''), query)`;
|
|
768
|
+
const extraFileSets = useDim ? { QUERY_DIM: {
|
|
769
|
+
table: "queries",
|
|
770
|
+
keys: [dimStore.parquetKey(ctx)]
|
|
771
|
+
} } : void 0;
|
|
772
|
+
const sqlFor = dailyWindowSqlFor(useDim, canonExpr);
|
|
773
|
+
const cols = queryCanonicalDailyRollup.parquetColumns;
|
|
774
|
+
const sortKey = queryCanonicalDailyRollup.parquetSortKey;
|
|
775
|
+
const batchRows = [];
|
|
776
|
+
let i = windowOffset;
|
|
777
|
+
for (; i < windowsTotal; i++) {
|
|
778
|
+
const w = windows[i];
|
|
779
|
+
const winRows = await runPagedQuery({
|
|
780
|
+
engine,
|
|
781
|
+
ctx,
|
|
782
|
+
table: "queries",
|
|
783
|
+
...sType,
|
|
784
|
+
fileSets: {
|
|
785
|
+
FILES: {
|
|
786
|
+
table: "queries",
|
|
787
|
+
partitions: w.partitions
|
|
788
|
+
},
|
|
789
|
+
...extraFileSets
|
|
790
|
+
},
|
|
791
|
+
coreSql: sqlFor(w),
|
|
792
|
+
orderBy: "date, query_canonical",
|
|
793
|
+
pageRows: ROLLUP_PAGE_ROWS_DAILY
|
|
794
|
+
});
|
|
795
|
+
for (const r of winRows) batchRows.push(mapDailyRow(r));
|
|
796
|
+
if (Date.now() > deadlineMs) {
|
|
797
|
+
i++;
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const nextWindowOffset = i;
|
|
802
|
+
const partKey = rollupParquetKey(ctx, `${CANONICAL_DAILY_PART_STEM}__w${windowOffset}`, builtAt, searchType);
|
|
803
|
+
await dataSource.write(partKey, encodeRowsToParquetFlex(batchRows, {
|
|
804
|
+
columns: cols,
|
|
805
|
+
sortKey
|
|
806
|
+
}));
|
|
807
|
+
if (nextWindowOffset < windowsTotal) return {
|
|
808
|
+
done: false,
|
|
809
|
+
nextWindowOffset,
|
|
810
|
+
windowsTotal,
|
|
811
|
+
windowsBuilt: nextWindowOffset - windowOffset,
|
|
812
|
+
rowsWritten: batchRows.length
|
|
813
|
+
};
|
|
814
|
+
const partPrefixDir = rollupParquetKey(ctx, CANONICAL_DAILY_PART_STEM, builtAt, searchType).replace(/[^/]*$/, "");
|
|
815
|
+
const partKeys = (await dataSource.list(partPrefixDir)).filter((k) => k.includes(`${CANONICAL_DAILY_PART_STEM}__w`) && k.endsWith(`__v${builtAt}.parquet`)).sort();
|
|
816
|
+
const envelope = {
|
|
817
|
+
version: 1,
|
|
818
|
+
id: CANONICAL_DAILY_ROLLUP_FINAL_ID,
|
|
819
|
+
builtAt,
|
|
820
|
+
windowDays: queryCanonicalDailyRollup.windowDays,
|
|
821
|
+
payload: {
|
|
822
|
+
parquetKey: partKeys[0] ?? partKey,
|
|
823
|
+
parquetKeys: partKeys,
|
|
824
|
+
rowCount: 0
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
await dataSource.write(rollupKey(ctx, CANONICAL_DAILY_ROLLUP_FINAL_ID, builtAt, searchType), new TextEncoder().encode(JSON.stringify(envelope)));
|
|
828
|
+
return {
|
|
829
|
+
done: true,
|
|
830
|
+
nextWindowOffset,
|
|
831
|
+
windowsTotal,
|
|
832
|
+
windowsBuilt: nextWindowOffset - windowOffset,
|
|
833
|
+
rowsWritten: batchRows.length
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
const CANONICAL_DAILY_ROLLUP_FINAL_ID = "query_canonical_daily";
|
|
837
|
+
const CANONICAL_DAILY_PART_STEM = "query_canonical_daily__part";
|
|
747
838
|
const indexingMetadataRollup = {
|
|
748
839
|
id: "indexing_metadata",
|
|
749
840
|
windowDays: null,
|
|
@@ -1056,4 +1147,4 @@ const DEFAULT_ROLLUPS = [
|
|
|
1056
1147
|
sitemapChanges28dRollup
|
|
1057
1148
|
];
|
|
1058
1149
|
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 };
|
|
1150
|
+
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.
|
|
4
|
+
"version": "0.31.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
|
-
"
|
|
185
|
-
"gscdump": "0.31.
|
|
184
|
+
"gscdump": "0.31.10",
|
|
185
|
+
"@gscdump/contracts": "0.31.10"
|
|
186
186
|
},
|
|
187
187
|
"devDependencies": {
|
|
188
188
|
"@duckdb/duckdb-wasm": "^1.32.0",
|