From a65434cc67d01b33eeac9f6c88248878900b9f73 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 16 Jul 2026 15:59:59 -0400 Subject: [PATCH 1/2] perf(backend): index roms (platform_id, fs_size_bytes) GET /api/platforms and GET /api/stats both sum roms.fs_size_bytes but the column was not in any index, forcing full reads of the wide, JSON-heavy roms table (5-11s on large libraries). A composite index on (platform_id, fs_size_bytes) turns both sums into index-only scans. Fixes #3769 Generated-By: PostHog Code Task-Id: e24ff248-6329-4b70-a826-9a9fee21411e --- .../0097_roms_platform_fs_size_index.py | 40 +++++++++++++++++++ backend/models/rom.py | 3 ++ 2 files changed, 43 insertions(+) create mode 100644 backend/alembic/versions/0097_roms_platform_fs_size_index.py diff --git a/backend/alembic/versions/0097_roms_platform_fs_size_index.py b/backend/alembic/versions/0097_roms_platform_fs_size_index.py new file mode 100644 index 000000000..b46b1ad47 --- /dev/null +++ b/backend/alembic/versions/0097_roms_platform_fs_size_index.py @@ -0,0 +1,40 @@ +"""Add composite index on roms (platform_id, fs_size_bytes) + +GET /api/platforms and GET /api/stats both sum roms.fs_size_bytes (per platform +and across the whole library). fs_size_bytes was not part of any index, so the +database had to read actual rows from the very wide, JSON-heavy roms table, +taking 5-11s on large libraries. This composite index lets both sums resolve as +index-only scans that never touch the wide rows; the optimizer picks it for both +query shapes on its own. + +Revision ID: 0097_roms_platform_fs_size_index +Revises: 0096_fix_virtual_collections +Create Date: 2026-07-16 00:00:00.000000 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "0097_roms_platform_fs_size_index" +down_revision = "0096_fix_virtual_collections" +branch_labels = None +depends_on = None + +INDEX_NAME = "idx_roms_platform_fs_size" +INDEX_COLUMNS = ["platform_id", "fs_size_bytes"] + + +def upgrade() -> None: + with op.batch_alter_table("roms", schema=None) as batch_op: + batch_op.create_index( + INDEX_NAME, + INDEX_COLUMNS, + unique=False, + if_not_exists=True, + ) + + +def downgrade() -> None: + with op.batch_alter_table("roms", schema=None) as batch_op: + batch_op.drop_index(INDEX_NAME, if_exists=True) diff --git a/backend/models/rom.py b/backend/models/rom.py index 39a3c6413..719463c9d 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -268,6 +268,9 @@ class Rom(BaseModel): "tgdb_id", "id", ), + # Covering index so per-platform and whole-library SUM(fs_size_bytes) + # are index-only scans that never touch the wide roms rows + Index("idx_roms_platform_fs_size", "platform_id", "fs_size_bytes"), Index("idx_roms_name", "name"), Index("idx_roms_name_sort_key", "name_sort_key"), Index("idx_roms_igdb_id", "igdb_id"), From 3db9cde0842d0513c95313d47d0a46db2dd71e1c Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Thu, 16 Jul 2026 20:39:54 -0400 Subject: [PATCH 2/2] cleanup --- .../alembic/versions/0097_roms_platform_fs_size_index.py | 7 ------- backend/models/rom.py | 2 -- 2 files changed, 9 deletions(-) diff --git a/backend/alembic/versions/0097_roms_platform_fs_size_index.py b/backend/alembic/versions/0097_roms_platform_fs_size_index.py index b46b1ad47..b556e1cc6 100644 --- a/backend/alembic/versions/0097_roms_platform_fs_size_index.py +++ b/backend/alembic/versions/0097_roms_platform_fs_size_index.py @@ -1,12 +1,5 @@ """Add composite index on roms (platform_id, fs_size_bytes) -GET /api/platforms and GET /api/stats both sum roms.fs_size_bytes (per platform -and across the whole library). fs_size_bytes was not part of any index, so the -database had to read actual rows from the very wide, JSON-heavy roms table, -taking 5-11s on large libraries. This composite index lets both sums resolve as -index-only scans that never touch the wide rows; the optimizer picks it for both -query shapes on its own. - Revision ID: 0097_roms_platform_fs_size_index Revises: 0096_fix_virtual_collections Create Date: 2026-07-16 00:00:00.000000 diff --git a/backend/models/rom.py b/backend/models/rom.py index 719463c9d..4059bd2d0 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -268,8 +268,6 @@ class Rom(BaseModel): "tgdb_id", "id", ), - # Covering index so per-platform and whole-library SUM(fs_size_bytes) - # are index-only scans that never touch the wide roms rows Index("idx_roms_platform_fs_size", "platform_id", "fs_size_bytes"), Index("idx_roms_name", "name"), Index("idx_roms_name_sort_key", "name_sort_key"),