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
@@ -42,17 +42,29 @@ export const insertLimits = { rows: 2000 }
|
||||
class FileStore {
|
||||
/**
|
||||
* Inserts files, skipping those whose `file_key` exists. Returns the rows
|
||||
* that were inserted (id and filename). `files` is partitioned by id
|
||||
* that were inserted (id, filename, file_key). `files` is partitioned by id
|
||||
* (CONCEPT.md D39) and each partition has its own unique file_key index:
|
||||
* keys already in any partition are looked up first; two writers adding
|
||||
* the same new key at once meet in the newest partition's index.
|
||||
* file_keys (a 64-bit hash of every key, filled by the insert trigger) says
|
||||
* which keys may exist, and only those are looked up in the partitions; two
|
||||
* writers adding the same new key at once meet in the newest partition's index.
|
||||
*/
|
||||
async insert(files: NewFile[], client: Pick<QueryClientContract, 'knexQuery'> = db): Promise<{ id: number; filename: string }[]> {
|
||||
async insert(
|
||||
files: NewFile[],
|
||||
client: Pick<QueryClientContract, 'knexQuery' | 'rawQuery'> = db
|
||||
): Promise<{ id: number; filename: string; file_key: string | null }[]> {
|
||||
if (files.length === 0) return []
|
||||
const keys = [...new Set(files.map((file) => file.file_key).filter((key): key is string => typeof key === 'string'))]
|
||||
const known = new Set<string>()
|
||||
for (let i = 0; i < keys.length; i += 5000) {
|
||||
const found = await client.knexQuery().from('files').whereIn('file_key', keys.slice(i, i + 5000)).select('file_key')
|
||||
const chunk = keys.slice(i, i + 5000)
|
||||
const { rows } = await client.rawQuery(
|
||||
`SELECT k.key FROM unnest(?::text[]) AS k(key)
|
||||
WHERE EXISTS (SELECT 1 FROM file_keys f WHERE f.key_hash = hashtextextended(k.key, 0))`,
|
||||
[chunk]
|
||||
)
|
||||
const maybe = rows.map((row: { key: string }) => row.key)
|
||||
if (maybe.length === 0) continue
|
||||
const found = await client.knexQuery().from('files').whereIn('file_key', maybe).select('file_key')
|
||||
for (const row of found) known.add(row.file_key)
|
||||
}
|
||||
const seen = new Set<string>()
|
||||
@@ -70,10 +82,10 @@ class FileStore {
|
||||
})
|
||||
if (rows.length === 0) return []
|
||||
// A statement takes at most 65,535 parameters: ~5,400 rows of 12 columns.
|
||||
const inserted: { id: number; filename: string }[] = []
|
||||
const inserted: { id: number; filename: string; file_key: string | null }[] = []
|
||||
for (let i = 0; i < rows.length; i += insertLimits.rows) {
|
||||
const chunk = await client.knexQuery().table('files').insert(rows.slice(i, i + insertLimits.rows)).onConflict().ignore().returning(['id', 'filename'])
|
||||
for (const row of chunk) inserted.push({ id: Number(row.id), filename: row.filename })
|
||||
const chunk = await client.knexQuery().table('files').insert(rows.slice(i, i + insertLimits.rows)).onConflict().ignore().returning(['id', 'filename', 'file_key'])
|
||||
for (const row of chunk) inserted.push({ id: Number(row.id), filename: row.filename, file_key: row.file_key ?? null })
|
||||
}
|
||||
return inserted
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user