Files
usenet-indexer/app/services/FilePartitions.ts
T
Daan MeijerandClaude Opus 5.5 f1773744c0
Test and deploy / test (push) Successful in 54s
Test and deploy / deploy (push) Successful in 7m42s
Partition files by age (id)
New rows went into free space scattered through the 9.6 GB heap on the HDD
(left behind by inspection's updates): one random HDD read per insert, and
seed batches took a median 13 s to store. The existing table becomes the
first partition as it is (a CHECK scan, no rewrite; indexes, its foreign
key and the triggers are taken over by the partitioned parent); new
partitions of 4M ids go in the same tablespace and stay in memory while
they fill.

- Every partition has a unique file_key index; FileStore.insert looks keys
  up in all partitions first (a key can't be unique across partitions).
- FilePartitions.ensure() (gc:scheduler) keeps two partitions ahead.
- Stats count files from the partitions; seed:node waits ~20 minutes for
  the API before giving a range up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:57:51 +02:00

40 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import db from '@adonisjs/lucid/services/db'
import logger from '@adonisjs/core/services/logger'
/** Ids per partition (as the migration that partitioned files: …150). */
export const FILE_PARTITION_SIZE = 4_000_000
/** Ids kept covered beyond the newest file: two partitions. */
const AHEAD = 2 * FILE_PARTITION_SIZE
/**
* `files` is partitioned by id (CONCEPT.md D39). An insert with an id no
* partition covers fails, so gc:scheduler calls ensure() every sweep to keep
* two partitions ready ahead of the newest id. Each new partition gets its
* own unique index on file_key (FileStore.insert checks the older ones),
* in the parent's tablespace (the HDD on homelab).
*/
class FilePartitions {
async ensure(): Promise<string[]> {
const created: string[] = []
while (true) {
const { rows } = await db.rawQuery(
`SELECT (SELECT coalesce(max(id), 0) FROM files) AS max_id,
-- ''{0,1}, not ''?: knex reads ? as a binding.
max((regexp_match(pg_get_expr(c.relpartbound, c.oid), 'TO \\(''{0,1}(\\d+)''{0,1}\\)'))[1]::bigint) AS upper
FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid
WHERE i.inhparent = 'files'::regclass`
)
const maxId = Number(rows[0].max_id)
const upper = Number(rows[0].upper)
if (upper - maxId >= AHEAD) return created
const name = `files_p${upper}`
await db.rawQuery(`CREATE TABLE ${name} PARTITION OF files FOR VALUES FROM (${upper}) TO (${upper + FILE_PARTITION_SIZE})`)
await db.rawQuery(`ALTER TABLE ${name} ADD CONSTRAINT ${name}_file_key_unique UNIQUE (file_key)`)
logger.info(`files: partition ${name} created (ids ${upper}–${upper + FILE_PARTITION_SIZE - 1}).`)
created.push(name)
}
}
}
export default new FilePartitions()