"""image_record.siglip_embedding: HNSW cosine index for "more like this" Revision ID: 0036 Revises: 0035 Create Date: 2026-06-04 Gallery Phase 3 (visual similarity search) ranks images by `siglip_embedding.cosine_distance(source_embedding)`. Without an index that's a sequential scan computing a 1152-dim distance for every row — fine at small scale, but it grows linearly with the library. Add an HNSW index with `vector_cosine_ops` so the top-N nearest search is sub-50ms ANN. 1152 dims is under pgvector's 2000-dim HNSW limit, so HNSW (no training, better recall than IVFFlat) is the right choice. ONE-TIME COST: building the index over the existing embeddings (~57k vectors on the operator's library) locks image_record for ~30-60s during this migration on deploy — acceptable for a single-operator homelab. NULL embeddings (videos / not-yet-embedded rows) are simply not indexed. """ from typing import Sequence, Union from alembic import op revision: str = "0036" down_revision: Union[str, None] = "0035" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # Raw SQL: alembic's create_index doesn't express the `USING hnsw (... # vector_cosine_ops)` access-method + opclass cleanly. Must match the # query's cosine_distance operator class to be usable by the planner. op.execute( "CREATE INDEX ix_image_record_siglip_hnsw " "ON image_record USING hnsw (siglip_embedding vector_cosine_ops)" ) def downgrade() -> None: op.drop_index("ix_image_record_siglip_hnsw", table_name="image_record")