Skip to content

Commit a103b06

Browse files
committed
fix: resume deps ingest with original export settings (CM-1296)
Signed-off-by: Uroš Marolt <uros@marolt.me>
1 parent dfafcbe commit a103b06

4 files changed

Lines changed: 41 additions & 5 deletions

File tree

services/apps/packages_worker/src/deps-dev/activities/bqExportToGcs.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export interface BqExportToGcsInput {
3232
// is replaced by a server-side maximumBytesBilled cap, since a dry-run only validates the first
3333
// statement and cannot predict the WHILE loop's total scan. See ADR-0004.
3434
isScript?: boolean
35+
// Fill-constraints run (package_dependencies only): the export is a full Option-A scan but the
36+
// downstream merge upserts version_constraint (ON CONFLICT DO UPDATE) instead of DO NOTHING. Full
37+
// and fill produce identical parquet, so sync_mode alone can't tell them apart — persist it in the
38+
// job meta so a --resume-job run reprocesses the export with the correct merge instead of silently
39+
// reverting to DO NOTHING (which skips the backfill).
40+
isFill?: boolean
3541
}
3642

3743
export interface BqExportToGcsOutput {
@@ -53,6 +59,7 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
5359
exportName,
5460
ecosystems,
5561
isScript,
62+
isFill,
5663
} = input
5764

5865
// Named exports use a stable GCS path independent of runId so they survive across bootstrap runs.
@@ -115,6 +122,7 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
115122
tableRowCounts: {
116123
'bq:export': 0,
117124
...(ecosystems ? { 'meta:ecosystems': ecosystems } : {}),
125+
...(isFill ? { 'meta:fill': 1 } : {}),
118126
},
119127
})
120128
return { gcsPrefix: namedPrefix, rowCount: 0, bqBytesBilled: 0, jobId }
@@ -212,9 +220,13 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
212220
const provisionalDate = snapshotAt ? new Date(snapshotAt) : null
213221
const jobId = await createIngestJob(qx, jobKind, syncMode, provisionalDate, exportName)
214222

215-
// H7: mark exporting before we start the BQ job; store ecosystems filter in table_row_counts JSONB.
223+
// H7: mark exporting before we start the BQ job; store ecosystems filter + fill flag in the
224+
// table_row_counts JSONB so --resume-job can restore the original export's settings.
225+
const exportMeta: Record<string, string | number | string[]> = {}
226+
if (ecosystems) exportMeta['meta:ecosystems'] = ecosystems
227+
if (isFill) exportMeta['meta:fill'] = 1
216228
await markJobStatus(qx, jobId, 'exporting', {
217-
...(ecosystems ? { tableRowCounts: { 'meta:ecosystems': ecosystems } } : {}),
229+
...(Object.keys(exportMeta).length > 0 ? { tableRowCounts: exportMeta } : {}),
218230
})
219231

220232
// From here the row is 'exporting'; any BQ failure (incl. script-mode maximumBytesBilled aborts)

services/apps/packages_worker/src/deps-dev/activities/getResumeExport.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export interface GetResumeExportOutput {
1515
progressDone: number
1616
progressTotal: number
1717
rowCountPg: number
18+
ecosystems: string[] | null
19+
fill: boolean
1820
}
1921

2022
// Pure fetch of a prior job's resume-relevant fields (export path, status, file-load progress,
@@ -38,5 +40,7 @@ export async function getResumeExport(
3840
progressDone: job.progressDone,
3941
progressTotal: job.progressTotal,
4042
rowCountPg: job.rowCountPg,
43+
ecosystems: job.ecosystems,
44+
fill: job.fill,
4145
}
4246
}

services/apps/packages_worker/src/deps-dev/workflows/ingestDependencies.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ export async function ingestDependencies(opts: {
183183
// and restart the chunk loop where its staging load left off. Idempotent modes only (see gate).
184184
resumeJobId?: number
185185
}): Promise<{ rowCountBq: number }> {
186-
const ecosystems = opts.ecosystems ?? DEPS_DEFAULT_ECOSYSTEMS
187-
const isFill = opts.fillConstraints === true
186+
// let: on resume these are overridden with the prior export's stored settings (see resume block).
187+
let ecosystems = opts.ecosystems ?? DEPS_DEFAULT_ECOSYSTEMS
188+
let isFill = opts.fillConstraints === true
188189
// Fill mode forces Option A — Option B selects NULL for version_constraint, making the fill a no-op.
189190
const tableOption = isFill ? 'A' : (opts.depsTableOption ?? 'A')
190191
// Full/fill scan the *Latest views; incremental reads the `today` partition. Drives both the SQL
@@ -234,6 +235,13 @@ export async function ingestDependencies(opts: {
234235
exportResult = { jobId: prior.jobId, gcsPrefix: prior.gcsPrefix, rowCount: 0 }
235236
resumeProgressDone = prior.progressDone
236237
resumeRowCountPg = prior.rowCountPg
238+
// Reprocess the export with its ORIGINAL settings, not the current CLI opts. A narrower
239+
// ecosystems list would drop versions-lookup rows (deps silently unmerged); the wrong fill flag
240+
// would pick ON CONFLICT DO NOTHING and skip the version_constraint backfill. Pre-meta jobs (no
241+
// stored ecosystems) fall back to all ecosystems — a superset lookup never drops rows. fill is
242+
// OR'd with the current flag so an explicit --fill-constraints still works for pre-meta jobs.
243+
ecosystems = prior.ecosystems ?? DEPS_DEFAULT_ECOSYSTEMS
244+
isFill = prior.fill || isFill
237245
} else {
238246
// Guard against corrupt deps.dev resolved-graph snapshots BEFORE the (multi-hour) export.
239247
// Skip when reusing a prior export — we're re-importing already-validated parquet, not
@@ -284,6 +292,8 @@ export async function ingestDependencies(opts: {
284292
reuseExports: opts.reuseExports,
285293
exportName: opts.exportName,
286294
ecosystems,
295+
// Persist fill in the job meta so a later --resume-job restores the upsert merge (see resume block).
296+
isFill,
287297
})
288298
}
289299

services/libs/data-access-layer/src/osspckgs/ingestJobs.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,20 @@ export async function getIngestJobForResume(
104104
progressDone: number
105105
progressTotal: number
106106
rowCountPg: number
107+
// Original export parameters, so resume reprocesses the export with the SAME settings rather than
108+
// whatever the current CLI opts happen to be: ecosystems (drives the versions-lookup filter) and
109+
// fill (drives the merge SQL — fill upserts version_constraint, non-fill is ON CONFLICT DO NOTHING).
110+
// ecosystems is null for jobs exported before the meta key existed; fill defaults to false.
111+
ecosystems: string[] | null
112+
fill: boolean
107113
} | null> {
108114
const row = await qx.selectOneOrNone(
109115
`
110116
SELECT id, job_kind, status, sync_mode, gcs_prefix, row_count_pg,
111117
COALESCE((table_row_counts->>'progress:done')::int, 0) AS progress_done,
112-
COALESCE((table_row_counts->>'progress:total')::int, 0) AS progress_total
118+
COALESCE((table_row_counts->>'progress:total')::int, 0) AS progress_total,
119+
table_row_counts->'meta:ecosystems' AS ecosystems,
120+
COALESCE((table_row_counts->>'meta:fill')::bool, false) AS fill
113121
FROM osspckgs_ingest_jobs
114122
WHERE id = $(id)
115123
`,
@@ -125,6 +133,8 @@ export async function getIngestJobForResume(
125133
progressDone: Number(row.progress_done ?? 0),
126134
progressTotal: Number(row.progress_total ?? 0),
127135
rowCountPg: Number(row.row_count_pg ?? 0),
136+
ecosystems: (row.ecosystems as string[] | null) ?? null,
137+
fill: Boolean(row.fill),
128138
}
129139
: null
130140
}

0 commit comments

Comments
 (0)