Files
usenet-indexer/database/migrations/1770000004000_posters.ts
T
Daan MeijerandClaude Opus 5.5 c0e00f1707
Test and deploy / test (push) Successful in 56s
Test and deploy / edge (push) Successful in 1m59s
Test and deploy / deploy (push) Successful in 5m20s
Poster search through a posters table and releases_poster_newest_idx; complete releases are final; the tail before everything (-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:25:30 +02:00

25 lines
1.4 KiB
TypeScript

import { BaseSchema } from '@adonisjs/lucid/schema'
/**
* Poster search (LOG.md 2026-10-16 (70)): "poster ILIKE '%…%'" on releases had no index, so it walked all 50M releases
* newest first; a poster with few releases took 30+ minutes. posters holds each poster once (3M, many random per file
* of an obfuscated upload) with a trigram index; releases_poster_newest_idx gives a poster's releases newest first.
* Both by the poster's first 200 characters: a spam poster of 3,024 bytes is over a btree entry's 2,704. The batch store
* adds new posters. Homelab built the releases index by hand first (concurrently); a no-op there.
*/
export default class extends BaseSchema {
static disableTransactions = true
async up() {
this.schema.raw(`CREATE TABLE IF NOT EXISTS posters (poster text PRIMARY KEY)`)
this.schema.raw(`INSERT INTO posters (poster) SELECT DISTINCT left(poster, 200) FROM releases WHERE poster IS NOT NULL ON CONFLICT DO NOTHING`)
this.schema.raw(`CREATE INDEX IF NOT EXISTS posters_trgm ON posters USING gin (poster gin_trgm_ops)`)
this.schema.raw(`CREATE INDEX CONCURRENTLY IF NOT EXISTS releases_poster_newest_idx ON releases (left(poster, 200), posted_at DESC NULLS LAST, id DESC)`)
}
async down() {
this.schema.raw(`DROP INDEX CONCURRENTLY IF EXISTS releases_poster_newest_idx`)
this.schema.raw(`DROP TABLE IF EXISTS posters`)
}
}