collections: listing answers channel and download queries from covering indexes (#740)
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Controller Tests / controller-tests (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled

The listing still read every channel row (for the summary columns) and every download
row of the listed Collections (for the minimum frequency). Both tables store yt-dlp
info_json, so rows are tens of KB and each sits on its own scattered page; selecting
fewer columns did not help because the seek per row is the cost. From a cold disk
cache that was 5 s per table, 10+ s total, on a large library.

Two covering indexes, channel (collection_id, video_count, total_size) and
download (collection_id, frequency), answer both queries without touching a row. The
listing no longer loads Download objects at all; it computes min_download_frequency
with one grouped query and drops the unused `downloads` array from listing rows (the
detail endpoints keep it; the list views only read min_download_frequency).

Adds a models-vs-migrations test (alembic check) so an index or column added to a
model without a migration, or vice versa, fails the suite.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
lrnselfreliance 2026-09-27 12:53:23 -06:00 • committed by Roland
parent 20be5dfc27
commit 5c78aa332d
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
6 changed files with 101 additions and 13 deletions

View file

@ -593,6 +593,8 @@ class Channel(ModelHelper, Base):
__tablename__ = 'channel'
__table_args__ = (
Index('channel_minimum_frequency_idx', 'minimum_frequency'),
# Covers the Collection listing's summary query; channel rows are wide (info_json).
Index('channel_collection_summary_idx', 'collection_id', 'video_count', 'total_size'),
Index('channel_source_id', 'source_id'),
Index('channel_total_size_idx', 'total_size'),
Index('channel_video_count_idx', 'video_count'),