Seed plans fill every uncovered gap, with priorities; cheaper ingest and file storing
- seed:plan plans every part of a group's history no range covers (it started below the lowest known range and left a.b.ath's 13 most recent months out); seed_ranges.priority orders leases (seed:plan --priority). - binaries.part_count, kept by the parts insert in the same statement: claimCompleted compares it instead of counting parts every batch. - file_keys: 64-bit hashes of all file keys; FileStore.insert asks it before probing every files partition. - worker:file stores 100 completed binaries per job with set-based queries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
61b683a71f
commit
693e6c3244
14 files changed
+473
-132
No files matched your search
@@ -85,6 +85,9 @@ async function upsertKeyed(
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Completed binaries per worker:file job. */
|
||||
const FILE_BATCH = 100
|
||||
|
||||
/** Size-only matching below this is too ambiguous (nfo, sfv, small volumes). */
|
||||
export const PAR2_SIZE_ONLY_MIN = 1024 * 1024
|
||||
/** A par2 set and its files are posted together; don't match across days. */
|
||||
@@ -187,8 +190,29 @@ class IngestService {
|
||||
const newParts = partRows.filter((row) => !(done.has(row.binary_id) && row.pcrc32 === null))
|
||||
|
||||
newParts.sort((a, b) => a.binary_id - b.binary_id || a.number - b.number)
|
||||
// Parts and the binaries' stored-part counts in one statement, so the count can't miss a
|
||||
// part (LOG.md 2026-10-06 (7)); claimCompleted compares it instead of counting parts. NULL
|
||||
// (binaries from before part_count) is counted once: the statement's own rows aren't in
|
||||
// its snapshot, so they're added on top. Concurrent batches can lock binaries in a
|
||||
// different order: a deadlock rolls the whole statement back, and it is retried.
|
||||
for (const chunk of chunks(newParts, PART_CHUNK)) {
|
||||
await knex('parts').insert(chunk).onConflict(['binary_id', 'number']).ignore()
|
||||
const insert = knex('parts').insert(chunk).onConflict(['binary_id', 'number']).ignore().returning('binary_id').toSQL()
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
try {
|
||||
await knex.raw(
|
||||
`WITH added AS (${insert.sql}),
|
||||
counted AS (SELECT binary_id, count(*)::int AS n FROM added GROUP BY binary_id)
|
||||
UPDATE binaries b SET part_count = CASE
|
||||
WHEN b.part_count IS NULL THEN (SELECT count(*) FROM parts p WHERE p.binary_id = b.id) + c.n
|
||||
ELSE b.part_count + c.n END
|
||||
FROM counted c WHERE b.id = c.binary_id`,
|
||||
insert.bindings as any[]
|
||||
)
|
||||
break
|
||||
} catch (error: any) {
|
||||
if (error.code !== '40P01' || attempt >= 5) throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (weakRows.length > 0) await this.flagConflicts(db.connection(), weakRows)
|
||||
@@ -366,7 +390,7 @@ class IngestService {
|
||||
const result = await db.rawQuery(
|
||||
`UPDATE binaries b SET completed_at = now()
|
||||
WHERE b.id = ANY(?) AND b.completed_at IS NULL AND b.conflicted_at IS NULL
|
||||
AND (SELECT count(*) FROM parts p WHERE p.binary_id = b.id) >= b.total_parts
|
||||
AND coalesce(b.part_count, (SELECT count(*) FROM parts p WHERE p.binary_id = b.id)) >= b.total_parts
|
||||
RETURNING b.id, b.file_key`,
|
||||
[binaryIds]
|
||||
)
|
||||
@@ -378,14 +402,14 @@ class IngestService {
|
||||
|
||||
async enqueue(completed: CompletedBinary[]) {
|
||||
if (completed.length === 0) return
|
||||
// jobId = file_key: BullMQ ignores an add() for an id that already exists.
|
||||
await QueueService.fileQueue.addBulk(
|
||||
completed.map(({ id, fileKey }) => ({
|
||||
name: 'process-file',
|
||||
data: { binaryId: id },
|
||||
opts: { jobId: fileKey },
|
||||
}))
|
||||
)
|
||||
// A job per FILE_BATCH binaries: worker:file stores them with a few set-based queries (LOG.md 2026-10-06 (7)).
|
||||
// jobId from the first file_key: BullMQ ignores an add() for an id that already exists.
|
||||
const jobs = []
|
||||
for (let i = 0; i < completed.length; i += FILE_BATCH) {
|
||||
const batch = completed.slice(i, i + FILE_BATCH)
|
||||
jobs.push({ name: 'process-files', data: { binaryIds: batch.map((b) => b.id) }, opts: { jobId: `${batch[0].fileKey}-${batch.length}` } })
|
||||
}
|
||||
await QueueService.fileQueue.addBulk(jobs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user