Stop refilling parts of binaries that are already a file
Test and deploy / test (push) Successful in 52s
Test and deploy / deploy (push) Successful in 1m23s

Crossposts, reposts and backfill ranges keep bringing articles of files
that worker:file already created. Ingest inserted their header parts again,
and GC's copied-parts pass (which searched all binaries for parts on every
batch) removed only 150k-450k per sweep: parts grew to 68M rows, 56M of
them belonging to finished files, and the 23 GB table paused the backfill.

- Ingest skips header parts (no per-part CRC) for binaries with a file_id.
- GC drops copied parts with a cursor over binaries, 2000 per statement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-04 02:53:11 +02:00
1 parent d3e774a489
commit 2178847513
3 files changed
+60 -14

No files matched your search

+16 -3
View File
@@ -260,6 +260,17 @@ class IngestService {
for (const [key, id] of ids) idsByKey.set(key, id)
}
// A binary that is a file already has its segment list in `files`; its
// header parts were dropped then. Crossposts in other groups, reposts
// and backfill ranges keep bringing the same articles, and inserting
// them again grew `parts` to 56M rows nobody reads (LOG.md 2026-10-04).
const done = new Set<number>()
const ids = [...idsByKey.values()]
for (const chunk of chunks(ids, BINARY_CHUNK)) {
const { rows } = await knex.raw('SELECT id FROM binaries WHERE id = ANY(?) AND file_id IS NOT NULL', [chunk])
for (const row of rows) done.add(Number(row.id))
}
const partRows = segments
.map((segment) => ({
binary_id: idsByKey.get(segment.fileKey)!,
@@ -272,10 +283,12 @@ class IngestService {
file_crc32: segment.fileCrc32 ?? null,
md5_16k: segment.md5_16k ?? null,
}))
const weakRows = partRows.filter((_, i) => segments[i].weakIdentity)
const weakRows = partRows.filter((row, i) => segments[i].weakIdentity && !(done.has(row.binary_id) && row.pcrc32 === null))
// Body-derived parts (with a per-part CRC) are kept, as worker:file does.
const newParts = partRows.filter((row) => !(done.has(row.binary_id) && row.pcrc32 === null))
partRows.sort((a, b) => a.binary_id - b.binary_id || a.number - b.number)
for (const chunk of chunks(partRows, PART_CHUNK)) {
newParts.sort((a, b) => a.binary_id - b.binary_id || a.number - b.number)
for (const chunk of chunks(newParts, PART_CHUNK)) {
await knex('parts').insert(chunk).onConflict(['binary_id', 'number']).ignore()
}