Add indexes on high-traffic foreign keys and sort columns

Queries throughout the codebase filter on generations.profile_id,
generations.status, generations.created_at, story_items.story_id,
story_items.generation_id, generation_versions.generation_id, and
profile_samples.profile_id with every request. Without indexes SQLite
falls back to a full table scan; as history grows (hundreds or thousands
of generations) these scans become the dominant latency.

Changes:
- Add index=True on the most-queried FK and sort columns in models.py so
  new installs get them from Base.metadata.create_all
- Add _migrate_add_indexes() called from run_migrations() so existing
  installs get the same indexes on next startup (uses CREATE INDEX IF
  NOT EXISTS — idempotent, <10 ms on any realistic dataset)
This commit is contained in:
Will Anderson
2026-10-04 00:00:41 +00:00
committed by capy-ai-staging[bot]
parent ccc092bad0
commit b97ffbfba9
2 changed files with 51 additions and 10 deletions
+10 -10
View File
@@ -3,7 +3,7 @@
from datetime import datetime
import uuid
from sqlalchemy import Column, String, Integer, Float, DateTime, Text, ForeignKey, Boolean, JSON
from sqlalchemy import Column, Index, String, Integer, Float, DateTime, Text, ForeignKey, Boolean, JSON
from sqlalchemy.ext.declarative import declarative_base
from ..utils.capture_chords import (
@@ -54,7 +54,7 @@ class ProfileSample(Base):
__tablename__ = "profile_samples"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
profile_id = Column(String, ForeignKey("profiles.id"), nullable=False)
profile_id = Column(String, ForeignKey("profiles.id"), nullable=False, index=True)
audio_path = Column(String, nullable=False)
reference_text = Column(Text, nullable=False)
@@ -65,7 +65,7 @@ class Generation(Base):
__tablename__ = "generations"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
profile_id = Column(String, ForeignKey("profiles.id"), nullable=False)
profile_id = Column(String, ForeignKey("profiles.id"), nullable=False, index=True)
text = Column(Text, nullable=False)
language = Column(String, default="en")
audio_path = Column(String, nullable=True)
@@ -74,7 +74,7 @@ class Generation(Base):
instruct = Column(Text)
engine = Column(String, default="qwen")
model_size = Column(String, nullable=True)
status = Column(String, default="completed")
status = Column(String, default="completed", index=True)
error = Column(Text, nullable=True)
is_favorited = Column(Boolean, default=False)
# Origin of this generation — "manual" for plain /generate calls,
@@ -82,7 +82,7 @@ class Generation(Base):
# profile's personality LLM before TTS. Future sources (bulk import,
# agent replies, etc.) can extend this.
source = Column(String, nullable=False, default="manual")
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=datetime.utcnow, index=True)
class Story(Base):
@@ -103,8 +103,8 @@ class StoryItem(Base):
__tablename__ = "story_items"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
story_id = Column(String, ForeignKey("stories.id"), nullable=False)
generation_id = Column(String, ForeignKey("generations.id"), nullable=False)
story_id = Column(String, ForeignKey("stories.id"), nullable=False, index=True)
generation_id = Column(String, ForeignKey("generations.id"), nullable=False, index=True)
version_id = Column(String, ForeignKey("generation_versions.id"), nullable=True)
start_time_ms = Column(Integer, nullable=False, default=0)
track = Column(Integer, nullable=False, default=0)
@@ -132,7 +132,7 @@ class GenerationVersion(Base):
__tablename__ = "generation_versions"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
generation_id = Column(String, ForeignKey("generations.id"), nullable=False)
generation_id = Column(String, ForeignKey("generations.id"), nullable=False, index=True)
label = Column(String, nullable=False)
audio_path = Column(String, nullable=False)
effects_chain = Column(Text, nullable=True)
@@ -172,7 +172,7 @@ class ChannelDeviceMapping(Base):
__tablename__ = "channel_device_mappings"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
channel_id = Column(String, ForeignKey("audio_channels.id"), nullable=False)
channel_id = Column(String, ForeignKey("audio_channels.id"), nullable=False, index=True)
device_id = Column(String, nullable=False)
@@ -300,4 +300,4 @@ class Capture(Base):
stt_model = Column(String, nullable=True)
llm_model = Column(String, nullable=True)
refinement_flags = Column(Text, nullable=True) # JSON blob
created_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=datetime.utcnow, index=True)