From 746aaa48442c5ad1df6cb2d5605cce27b9c6e4c2 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Mon, 12 Oct 2026 22:57:35 -0400 Subject: [PATCH] Index conversation, message, memory and artifact foreign keys Postgres had only primary keys on these tables, so loading a conversation, listing conversations, checking for a first message on every send, and fetching memories/artifacts were sequential scans. Migration generated with aerich; index-only apart from the no-op JSONB re-casts aerich emits on every run (as in migrations 16 and 18), which were checked not to rewrite tables. Upgrade and downgrade verified on a throwaway pgvector database. Co-Authored-By: Claude Opus 5.5 --- blueprints/artifacts/models.py | 6 +- blueprints/conversation/models.py | 6 +- .../19_20261012225640_add_fk_indexes.py | 142 ++++++++++++++++++ 3 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 migrations/models/19_20261012225640_add_fk_indexes.py diff --git a/blueprints/artifacts/models.py b/blueprints/artifacts/models.py index ef61659..9344cc1 100644 --- a/blueprints/artifacts/models.py +++ b/blueprints/artifacts/models.py @@ -8,13 +8,17 @@ class Artifact(Model): id = fields.UUIDField(primary_key=True) user = fields.ForeignKeyField("models.User", related_name="artifacts") conversation = fields.ForeignKeyField( - "models.Conversation", related_name="artifacts", on_delete=fields.CASCADE + "models.Conversation", + related_name="artifacts", + on_delete=fields.CASCADE, + db_index=True, ) message = fields.ForeignKeyField( "models.ConversationMessage", related_name="artifacts", null=True, on_delete=fields.SET_NULL, + db_index=True, ) tool = fields.CharField(max_length=255) kind = fields.CharField(max_length=64) diff --git a/blueprints/conversation/models.py b/blueprints/conversation/models.py index 309bc84..69e8c8e 100644 --- a/blueprints/conversation/models.py +++ b/blueprints/conversation/models.py @@ -26,6 +26,8 @@ class Conversation(Model): class Meta: table = "conversations" + # Conversation list: a user's conversations, most recent first. + indexes = (("user_id", "updated_at"),) class ConversationMessage(Model): @@ -41,11 +43,13 @@ class ConversationMessage(Model): class Meta: table = "conversation_messages" + # Loading a conversation's history in order. + indexes = (("conversation_id", "created_at"),) class UserMemory(Model): id = fields.UUIDField(primary_key=True) - user = fields.ForeignKeyField("models.User", related_name="memories") + user = fields.ForeignKeyField("models.User", related_name="memories", db_index=True) content = fields.TextField() created_at = fields.DatetimeField(auto_now_add=True) updated_at = fields.DatetimeField(auto_now=True) diff --git a/migrations/models/19_20261012225640_add_fk_indexes.py b/migrations/models/19_20261012225640_add_fk_indexes.py new file mode 100644 index 0000000..ef78f05 --- /dev/null +++ b/migrations/models/19_20261012225640_add_fk_indexes.py @@ -0,0 +1,142 @@ +from tortoise import BaseDBAsyncClient + +RUN_IN_TRANSACTION = True + + +async def upgrade(db: BaseDBAsyncClient) -> str: + return """ + ALTER TABLE "users" ALTER COLUMN "ldap_groups" TYPE JSONB USING "ldap_groups"::JSONB; + ALTER TABLE "beli_restaurants" ALTER COLUMN "cuisine" TYPE JSONB USING "cuisine"::JSONB; + ALTER TABLE "beli_restaurants" ALTER COLUMN "tags" TYPE JSONB USING "tags"::JSONB; + ALTER TABLE "artifacts" ALTER COLUMN "data" TYPE JSONB USING "data"::JSONB; + ALTER TABLE "artifacts" ALTER COLUMN "source_ref" TYPE JSONB USING "source_ref"::JSONB; + ALTER TABLE "ynab_changes" ALTER COLUMN "changes" TYPE JSONB USING "changes"::JSONB; + CREATE INDEX IF NOT EXISTS "idx_conversatio_user_id_1f0c4c" ON "conversations" ("user_id", "updated_at"); + CREATE INDEX IF NOT EXISTS "idx_conversatio_convers_d1744c" ON "conversation_messages" ("conversation_id", "created_at"); + CREATE INDEX IF NOT EXISTS "idx_user_memori_user_id_8278f0" ON "user_memories" ("user_id"); + CREATE INDEX IF NOT EXISTS "idx_artifacts_message_d8728a" ON "artifacts" ("message_id"); + CREATE INDEX IF NOT EXISTS "idx_artifacts_convers_188f03" ON "artifacts" ("conversation_id");""" + + +async def downgrade(db: BaseDBAsyncClient) -> str: + return """ + DROP INDEX IF EXISTS "idx_conversatio_convers_d1744c"; + DROP INDEX IF EXISTS "idx_conversatio_user_id_1f0c4c"; + DROP INDEX IF EXISTS "idx_user_memori_user_id_8278f0"; + DROP INDEX IF EXISTS "idx_artifacts_convers_188f03"; + DROP INDEX IF EXISTS "idx_artifacts_message_d8728a"; + ALTER TABLE "users" ALTER COLUMN "ldap_groups" TYPE JSONB USING "ldap_groups"::JSONB; + ALTER TABLE "artifacts" ALTER COLUMN "data" TYPE JSONB USING "data"::JSONB; + ALTER TABLE "artifacts" ALTER COLUMN "source_ref" TYPE JSONB USING "source_ref"::JSONB; + ALTER TABLE "ynab_changes" ALTER COLUMN "changes" TYPE JSONB USING "changes"::JSONB; + ALTER TABLE "beli_restaurants" ALTER COLUMN "cuisine" TYPE JSONB USING "cuisine"::JSONB; + ALTER TABLE "beli_restaurants" ALTER COLUMN "tags" TYPE JSONB USING "tags"::JSONB;""" + + +MODELS_STATE = ( + "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" +) -- 2.52.0