Rebuild: index lookups for the merger's neighbours and the prefetcher's ranges
The merger's neighbour query matched "end_id = ? OR start_id = ?", which no index serves: it read every range of the group per file, 5.5 s a round and most of Postgres's time in an end-to-end run. Now the range holding the article before and after, by (group_id, start_id): 0.08 ms. The prefetcher's leased and started ranges (every 2 s) get partial indexes: 15-25 ms -> <0.1 ms with 145k planned ranges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
583ea1ded6
commit
2b4f673ddb
2 files changed
+28
-5
No files matched your search
@@ -120,17 +120,22 @@ class FragmentMerger {
|
||||
})
|
||||
const spans = lists.map(span)
|
||||
const groups = [...new Set(spans.map((s) => s.group))]
|
||||
// The range holding the article before and the one after, each by the (group_id, start_id) index:
|
||||
// "end_id = ? OR start_id = ?" read every range of the group per file (5.5 s a round; LOG.md 2026-10-09).
|
||||
const { rows } = await db.rawQuery(
|
||||
`SELECT r.group_id, r.start_id, r.end_id, r.status FROM ranges r
|
||||
JOIN unnest(?::int[], ?::bigint[], ?::bigint[]) AS s(group_id, before_end, after_start)
|
||||
ON r.group_id = s.group_id AND (r.end_id = s.before_end OR r.start_id = s.after_start)`,
|
||||
`SELECT s.group_id, s.before_end, s.after_start, b.status AS before, a.status AS after
|
||||
FROM unnest(?::int[], ?::bigint[], ?::bigint[]) AS s(group_id, before_end, after_start)
|
||||
LEFT JOIN LATERAL (SELECT r.status FROM ranges r WHERE r.group_id = s.group_id AND r.start_id <= s.before_end AND r.end_id >= s.before_end
|
||||
ORDER BY r.start_id DESC LIMIT 1) b ON true
|
||||
LEFT JOIN LATERAL (SELECT r.status FROM ranges r WHERE r.group_id = s.group_id AND r.start_id <= s.after_start AND r.end_id >= s.after_start
|
||||
ORDER BY r.start_id DESC LIMIT 1) a ON true`,
|
||||
[spans.map((s) => s.group), spans.map((s) => s.first - 1), spans.map((s) => s.last + 1)]
|
||||
)
|
||||
const byEnd = new Map<string, string>()
|
||||
const byStart = new Map<string, string>()
|
||||
for (const r of rows) {
|
||||
byEnd.set(`${r.group_id}:${r.end_id}`, r.status)
|
||||
byStart.set(`${r.group_id}:${r.start_id}`, r.status)
|
||||
if (r.before !== null) byEnd.set(`${r.group_id}:${r.before_end}`, r.before)
|
||||
if (r.after !== null) byStart.set(`${r.group_id}:${r.after_start}`, r.after)
|
||||
}
|
||||
const lows = new Map((await db.from('groups').whereIn('id', groups).select('id', 'low')).map((g) => [Number(g.id), g.low === null ? null : Number(g.low)]))
|
||||
return (list: (typeof lists)[number]) => {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { BaseSchema } from '@adonisjs/lucid/schema'
|
||||
|
||||
/**
|
||||
* The prefetcher's ranges every 2 s (REBUILD.md, prefetch): the leased ones
|
||||
* and the planned ones it started spooling. Without these it read all
|
||||
* ranges each time (~15–25 ms with 145k planned; LOG.md 2026-10-09).
|
||||
*/
|
||||
export default class extends BaseSchema {
|
||||
async up() {
|
||||
this.schema.raw(`CREATE INDEX IF NOT EXISTS ranges_leased_idx ON ranges (leased_at) WHERE status = 'leased'`)
|
||||
this.schema.raw(`CREATE INDEX IF NOT EXISTS ranges_spooling_idx ON ranges (priority, id) WHERE status = 'planned' AND spooled_chunks > 0`)
|
||||
}
|
||||
|
||||
async down() {
|
||||
this.schema.raw(`DROP INDEX IF EXISTS ranges_spooling_idx`)
|
||||
this.schema.raw(`DROP INDEX IF EXISTS ranges_leased_idx`)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user