Seed batches take file ids from the insert; merge split uploads hourly
SeedService looked up every file of a batch across all files partitions after inserting it (15% of Postgres' active time); the merge job read every release on each GC sweep. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b2cc75fdd0
commit
23bbdcc5ee
4 files changed
+24
-7
No files matched your search
@@ -58,9 +58,16 @@ class SeedService {
|
||||
result.files = inserted.length
|
||||
result.existing = rows.length - inserted.length
|
||||
|
||||
// File ids for the binaries below (files queued for inspection only): new ones from the insert,
|
||||
// existing ones looked up. Looking up every key of the batch probed all files partitions again,
|
||||
// 15% of Postgres' active time (LOG.md 2026-10-06 (12)).
|
||||
const fileIds = new Map<string, number>()
|
||||
for (const row of await db.from('files').whereIn('file_key', rows.map((r) => r.file_key)).select('id', 'file_key')) {
|
||||
fileIds.set(row.file_key, Number(row.id))
|
||||
for (const row of inserted) if (row.file_key) fileIds.set(row.file_key, row.id)
|
||||
const missing = files.filter((file) => !fileIds.has(file.fileKey) && ContentService.isCandidate(file.name)).map((file) => file.fileKey)
|
||||
for (let i = 0; i < missing.length; i += 5000) {
|
||||
for (const row of await db.from('files').whereIn('file_key', missing.slice(i, i + 5000)).select('id', 'file_key')) {
|
||||
fileIds.set(row.file_key, Number(row.id))
|
||||
}
|
||||
}
|
||||
// A completed binary, as worker:file would have left behind, only for files queued for content
|
||||
// inspection: par2 naming (D16) needs the fingerprint inspection stores on it. For the others
|
||||
@@ -97,9 +104,9 @@ class SeedService {
|
||||
}
|
||||
|
||||
// As worker:file does for a new file: an SFV posted earlier may list it (D34), then inspection.
|
||||
const rowById = new Map(rows.map((row) => [fileIds.get(row.file_key), row]))
|
||||
const rowByKey = new Map(rows.map((row) => [row.file_key, row]))
|
||||
const added = inserted.flatMap((file) => {
|
||||
const row = rowById.get(file.id)
|
||||
const row = file.file_key ? rowByKey.get(file.file_key) : undefined
|
||||
return row ? [{ id: file.id, name: row.filename, poster: row.poster, date: row.date, release_id: row.release_id }] : []
|
||||
})
|
||||
await SfvService.claimAll(added)
|
||||
|
||||
@@ -34,6 +34,8 @@ export default class GcScheduler extends BaseCommand {
|
||||
|
||||
/** When release completeness was last recomputed (it runs less often than the sweep). */
|
||||
private completenessAt = 0
|
||||
/** When split uploads were last merged. */
|
||||
private mergedAt = 0
|
||||
|
||||
public async run() {
|
||||
this.logger.info(
|
||||
@@ -115,9 +117,13 @@ export default class GcScheduler extends BaseCommand {
|
||||
)
|
||||
}
|
||||
|
||||
// Uploads split per poster (CONCEPT.md D35); the releases this empties go in a later sweep.
|
||||
const merged = await ReleaseMergeService.mergeSplitPosts({ dryRun: this.dryRun })
|
||||
if (merged.releases) this.logger.info(`Split uploads: ${this.verb('merge', 'merged')} ${merged.releases} releases into ${merged.names}.`)
|
||||
// Uploads split per poster (CONCEPT.md D35); the releases this empties go in a later sweep. It reads
|
||||
// every release and its files: hourly, not every sweep (LOG.md 2026-10-06 (12)).
|
||||
if (this.once || Date.now() - this.mergedAt >= gcConfig.mergeIntervalMs) {
|
||||
this.mergedAt = Date.now()
|
||||
const merged = await ReleaseMergeService.mergeSplitPosts({ dryRun: this.dryRun })
|
||||
if (merged.releases) this.logger.info(`Split uploads: ${this.verb('merge', 'merged')} ${merged.releases} releases into ${merged.names}.`)
|
||||
}
|
||||
|
||||
await this.rebuildIndexes()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ export default {
|
||||
/** How often gc:scheduler recomputes the completeness of releases that changed (LOG.md 2026-10-06 (6)). */
|
||||
releaseCompleteIntervalMs: env.get('RELEASE_COMPLETE_INTERVAL_MS', 30 * 60 * 1000),
|
||||
|
||||
/** How often gc:scheduler merges uploads split per poster (D35); it reads every release. */
|
||||
mergeIntervalMs: env.get('GC_MERGE_INTERVAL_MS', 60 * 60 * 1000),
|
||||
|
||||
/** Time per sweep for copying old file keys into file_keys until it's complete (LOG.md 2026-10-06 (7)). */
|
||||
fileKeysFillMs: env.get('FILE_KEYS_FILL_SECONDS', 90) * 1000,
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ export default await Env.create(new URL('../', import.meta.url), {
|
||||
RELEASE_COMPLETE_INTERVAL_MS: Env.schema.number.optional(),
|
||||
FILE_KEYS_FILL_SECONDS: Env.schema.number.optional(),
|
||||
GC_AWAIT_INSPECTION_SECONDS: Env.schema.number.optional(),
|
||||
GC_MERGE_INTERVAL_MS: Env.schema.number.optional(),
|
||||
ARCHIVE_WALK_MAX_SEGMENTS: Env.schema.number.optional(),
|
||||
DEFERRED_SETTLE_MS: Env.schema.number.optional(),
|
||||
DEFERRED_EXPAND_MAX_PARTS: Env.schema.number.optional(),
|
||||
|
||||
Reference in new issue
Block a user