66 lines
2.8 KiB
Python
66 lines
2.8 KiB
Python
"""B6.4 — indexes on the foreign keys that queries actually filter by
|
|
|
|
The database has 34 foreign keys with no supporting index. PostgreSQL does not
|
|
create one automatically, and the cost is invisible on a seeded database: a
|
|
sequential scan over five rows is instant, over five hundred thousand it is not.
|
|
|
|
This adds indexes only where a measured query path filters or joins on the
|
|
column — the ones surfaced by the B6.3 query-budget work. Indexing all 34 would
|
|
add write cost for paths nothing traverses; the remainder are listed in
|
|
`tests/probes/test_index_coverage.py` so they stay visible rather than
|
|
forgotten.
|
|
|
|
**Deployment note.** `CREATE INDEX` takes a lock that blocks writes for the
|
|
duration. On DocQube's current row counts that is brief, but on a large
|
|
`drive_files` it will not be — run these with `CONCURRENTLY` outside a
|
|
transaction if the table has grown. Alembic cannot do that inside its own
|
|
transaction, which is why this migration is written to be easy to translate.
|
|
|
|
Revision ID: b6_4_index_review
|
|
Revises: b1_3_row_level_security
|
|
"""
|
|
|
|
from alembic import op
|
|
|
|
revision = "b6_4_index_review"
|
|
down_revision = "b1_3_row_level_security"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
# (index name, table, columns) — each justified by a query path measured in
|
|
# tests/characterization/test_query_budget.py.
|
|
INDEXES = [
|
|
# Every folder listing filters files by folder.
|
|
("ix_drive_files_folder_id", "drive_files", "folder_id"),
|
|
("ix_drive_files_owner_id", "drive_files", "owner_id"),
|
|
# Hierarchy walks in get_effective_role and breadcrumbs.
|
|
("ix_drive_folders_parent_id", "drive_folders", "parent_id"),
|
|
("ix_drive_folders_owner_id", "drive_folders", "owner_id"),
|
|
# The batched star lookup added in B6.3 filters on all three.
|
|
(
|
|
"ix_drive_stars_user_resource",
|
|
"drive_stars",
|
|
"user_id, resource_type, resource_id",
|
|
),
|
|
# Version history, loaded per file.
|
|
("ix_drive_file_versions_file_id", "drive_file_versions", "file_id"),
|
|
("ix_project_versions_project_id", "project_versions", "project_id"),
|
|
# Permission resolution joins accesses on every authenticated request.
|
|
("ix_role_accesses_access_id", "role_accesses", "access_id"),
|
|
# Signing looks requests up by the file they belong to.
|
|
("ix_signing_requests_drive_file_id", "signing_requests", "drive_file_id"),
|
|
# Comments and activity are read per resource.
|
|
("ix_drive_comments_author_id", "drive_comments", "author_id"),
|
|
("ix_drive_activities_actor_id", "drive_activities", "actor_id"),
|
|
]
|
|
|
|
|
|
def upgrade() -> None:
|
|
for name, table, columns in INDEXES:
|
|
op.execute(f"CREATE INDEX IF NOT EXISTS {name} ON {table} ({columns})")
|
|
|
|
|
|
def downgrade() -> None:
|
|
for name, _table, _columns in INDEXES:
|
|
op.execute(f"DROP INDEX IF EXISTS {name}")
|