A full round took 35-38 min of each hour and grew with every inspection the prober added. Triggers note changed releases in judge_dirty; the judge judges those (and releases a file hash of theirs tipped to repeated) by every rule, and every release in a daily full round for what time changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
7.2 KiB
TypeScript
120 lines
7.2 KiB
TypeScript
import { BaseSchema } from '@adonisjs/lucid/schema'
|
||
|
||
/**
|
||
* The junk judge by what changed (LOG.md 2026-10-19 (83)): a full round over every release took 35–38 min of each
|
||
* hour, longer with every inspection the prober added. Triggers note each release whose facts a rule reads changed
|
||
* (judge_dirty: its probes' statuses, inspections, file hashes, passwords, spots, par2 sets, merges, its own judged
|
||
* columns); the judge judges those every few minutes, and every release in a daily full round (judge_state: when the
|
||
* last one was, by which rule set), its matches gathered in judge_full. judge_dirty is unlogged: what a crash loses
|
||
* the next full round judges.
|
||
*/
|
||
export default class extends BaseSchema {
|
||
async up() {
|
||
this.schema.raw(`CREATE UNLOGGED TABLE IF NOT EXISTS judge_dirty (release_id bigint NOT NULL)`)
|
||
this.schema.raw(`CREATE UNLOGGED TABLE IF NOT EXISTS judge_full (release_id bigint PRIMARY KEY, rule varchar(16) NOT NULL)`)
|
||
this.schema.raw(
|
||
`CREATE TABLE IF NOT EXISTS judge_state (id smallint PRIMARY KEY DEFAULT 1 CHECK (id = 1), full_at timestamptz NOT NULL, version integer NOT NULL)`
|
||
)
|
||
// The verdicts are an hourly full round's: the next full one a day from now.
|
||
this.schema.raw(`INSERT INTO judge_state (full_at, version) VALUES (now(), 1) ON CONFLICT DO NOTHING`)
|
||
|
||
// Rows of a table with release_id: the releases of the rows inserted, deleted, or updated (both, when moved).
|
||
this.schema.raw(`
|
||
CREATE OR REPLACE FUNCTION judge_dirty_by_release_id() RETURNS trigger LANGUAGE plpgsql AS $$
|
||
BEGIN
|
||
IF TG_OP IN ('INSERT', 'UPDATE') THEN
|
||
INSERT INTO judge_dirty (release_id) SELECT DISTINCT release_id FROM new_rows WHERE release_id IS NOT NULL;
|
||
END IF;
|
||
IF TG_OP IN ('UPDATE', 'DELETE') THEN
|
||
INSERT INTO judge_dirty (release_id) SELECT DISTINCT release_id FROM old_rows WHERE release_id IS NOT NULL;
|
||
END IF;
|
||
RETURN NULL;
|
||
END $$`)
|
||
// probe_candidates: only a status that counts for the dead rule, or a candidate moved by a merge (not the claims'
|
||
// 'taken', the settler's ready).
|
||
this.schema.raw(`
|
||
CREATE OR REPLACE FUNCTION judge_dirty_probes() RETURNS trigger LANGUAGE plpgsql AS $$
|
||
BEGIN
|
||
INSERT INTO judge_dirty (release_id)
|
||
SELECT DISTINCT unnest(ARRAY[n.release_id, o.release_id]) FROM new_rows n JOIN old_rows o ON o.id = n.id
|
||
WHERE n.release_id IS DISTINCT FROM o.release_id
|
||
OR (n.status IS DISTINCT FROM o.status AND (n.status IN ('done', 'missing', 'error') OR o.status IN ('done', 'missing', 'error')));
|
||
RETURN NULL;
|
||
END $$`)
|
||
this.schema.raw(`
|
||
CREATE OR REPLACE FUNCTION judge_dirty_merges() RETURNS trigger LANGUAGE plpgsql AS $$
|
||
BEGIN
|
||
INSERT INTO judge_dirty (release_id) SELECT DISTINCT into_id FROM new_rows;
|
||
RETURN NULL;
|
||
END $$`)
|
||
this.schema.raw(`
|
||
CREATE OR REPLACE FUNCTION judge_dirty_releases() RETURNS trigger LANGUAGE plpgsql AS $$
|
||
BEGIN
|
||
IF TG_OP = 'INSERT' THEN
|
||
INSERT INTO judge_dirty (release_id) SELECT id FROM new_rows;
|
||
ELSIF TG_OP = 'DELETE' THEN
|
||
INSERT INTO judge_dirty (release_id) SELECT id FROM old_rows;
|
||
ELSE
|
||
INSERT INTO judge_dirty (release_id) VALUES (NEW.id);
|
||
END IF;
|
||
RETURN NULL;
|
||
END $$`)
|
||
|
||
for (const table of ['release_inspections', 'file_hashes', 'release_passwords', 'release_spots', 'par2_sets']) {
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_insert ON ${table}`)
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_update ON ${table}`)
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_delete ON ${table}`)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_insert AFTER INSERT ON ${table} REFERENCING NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()`
|
||
)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_update AFTER UPDATE ON ${table} REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()`
|
||
)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_delete AFTER DELETE ON ${table} REFERENCING OLD TABLE AS old_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()`
|
||
)
|
||
}
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_update ON probe_candidates`)
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_delete ON probe_candidates`)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_update AFTER UPDATE ON probe_candidates REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_probes()`
|
||
)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_delete AFTER DELETE ON probe_candidates REFERENCING OLD TABLE AS old_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_by_release_id()`
|
||
)
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_insert ON release_merges`)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_insert AFTER INSERT ON release_merges REFERENCING NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_merges()`
|
||
)
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_insert ON releases`)
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_update ON releases`)
|
||
this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_delete ON releases`)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_insert AFTER INSERT ON releases REFERENCING NEW TABLE AS new_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_releases()`
|
||
)
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_delete AFTER DELETE ON releases REFERENCING OLD TABLE AS old_rows FOR EACH STATEMENT EXECUTE FUNCTION judge_dirty_releases()`
|
||
)
|
||
// Per row, and only when a column a rule reads changed: the tail and the settler update releases all the time.
|
||
this.schema.raw(
|
||
`CREATE TRIGGER judge_dirty_update AFTER UPDATE ON releases FOR EACH ROW
|
||
WHEN (OLD.name IS DISTINCT FROM NEW.name OR OLD.poster IS DISTINCT FROM NEW.poster OR OLD.source IS DISTINCT FROM NEW.source
|
||
OR OLD.file_total IS DISTINCT FROM NEW.file_total OR OLD.passworded IS DISTINCT FROM NEW.passworded
|
||
OR OLD.settled_at IS DISTINCT FROM NEW.settled_at OR OLD.readable IS DISTINCT FROM NEW.readable
|
||
OR OLD.inner_name IS DISTINCT FROM NEW.inner_name OR OLD.group_id IS DISTINCT FROM NEW.group_id
|
||
OR OLD.first_article IS DISTINCT FROM NEW.first_article OR OLD.last_article IS DISTINCT FROM NEW.last_article
|
||
OR OLD.posted_at IS DISTINCT FROM NEW.posted_at OR OLD.last_posted_at IS DISTINCT FROM NEW.last_posted_at
|
||
OR OLD.complete IS DISTINCT FROM NEW.complete)
|
||
EXECUTE FUNCTION judge_dirty_releases()`
|
||
)
|
||
}
|
||
|
||
async down() {
|
||
for (const table of ['release_inspections', 'file_hashes', 'release_passwords', 'release_spots', 'par2_sets', 'probe_candidates', 'release_merges', 'releases']) {
|
||
for (const event of ['insert', 'update', 'delete']) this.schema.raw(`DROP TRIGGER IF EXISTS judge_dirty_${event} ON ${table}`)
|
||
}
|
||
this.schema.raw(`DROP FUNCTION IF EXISTS judge_dirty_by_release_id(), judge_dirty_probes(), judge_dirty_merges(), judge_dirty_releases()`)
|
||
this.schema.raw(`DROP TABLE IF EXISTS judge_dirty, judge_full, judge_state`)
|
||
}
|
||
}
|