From 50217d38d0842b2a1fe47bf3063833272c31d8f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 19 Jun 2026 11:43:56 +0900 Subject: [PATCH] perf: index owner-scoped email timelines --- .jules/bolt.md | 4 ++++ backend/db/models.py | 6 ++++++ backend/scripts/bootstrap_db.py | 4 ++++ backend/tests/test_bootstrap_db.py | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/.jules/bolt.md b/.jules/bolt.md index c548e59bd..e5c62d79f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -50,3 +50,7 @@ ## 2025-06-15 - Optimize O(n) email threading dictionary lookups with defaultdict **Learning:** When organizing pre-sorted collections by groups, explicit dictionary membership and date tracking inside a tight loop creates unnecessary CPU overhead. Because SQL `ORDER BY` combined with Python sorting guarantees an oldest-to-newest ordering, tracking the 'most recent' item per group simplifies to blindly overwriting the dictionary key. **Action:** Use `collections.defaultdict` for tracking accumulators (`int`, `list`) and take advantage of implicit data ordering to eliminate branch conditions and explicit `.get()` checks in grouping loops. + +## 2026-06-19 - Email owner/date lookup index +**Learning:** Inbox and reply-wait queries commonly scope `email_records` by `user_id` and `organization_id` before ordering or filtering by `date`, so separate single-column indexes still leave the planner with avoidable sort/filter work. +**Action:** Keep `ix_email_records_owner_date` on `(user_id, organization_id, date)` in both the SQLAlchemy model and bootstrap backfill SQL when optimizing owner-scoped email timelines. diff --git a/backend/db/models.py b/backend/db/models.py index 5742a3c76..c9a95242e 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -331,6 +331,12 @@ class Email(Base): "message_id", name="uq_emails_owner_message_id", ), + Index( + "ix_email_records_owner_date", + "user_id", + "organization_id", + "date", + ), ) id: Mapped[int] = mapped_column(primary_key=True) diff --git a/backend/scripts/bootstrap_db.py b/backend/scripts/bootstrap_db.py index f61ab371c..b241380ab 100644 --- a/backend/scripts/bootstrap_db.py +++ b/backend/scripts/bootstrap_db.py @@ -145,6 +145,10 @@ def _get_create_indexes_statements() -> list[Executable]: "CREATE INDEX IF NOT EXISTS ix_email_records_organization_id " "ON email_records (organization_id)" ), + text( + "CREATE INDEX IF NOT EXISTS ix_email_records_owner_date " + "ON email_records (user_id, organization_id, date)" + ), text( "CREATE INDEX IF NOT EXISTS ix_sender_relationships_owner_source " "ON sender_relationships " diff --git a/backend/tests/test_bootstrap_db.py b/backend/tests/test_bootstrap_db.py index 84fb5079a..3b31b6d12 100644 --- a/backend/tests/test_bootstrap_db.py +++ b/backend/tests/test_bootstrap_db.py @@ -10,6 +10,7 @@ from db.models import ( CalendarWritebackSource, ConnectorSignalEvent, + Email, ProjectFolder, SecurityAuditEvent, SenderRelationship, @@ -103,6 +104,11 @@ def test_schema_backfill_adds_email_indexes(monkeypatch): "create index if not exists ix_email_records_organization_id" in statement for statement in statements ) + assert any( + "create index if not exists ix_email_records_owner_date" in statement + and "user_id, organization_id, date" in statement + for statement in statements + ) assert any( "drop index if exists ix_email_records_message_id" in statement for statement in statements @@ -414,6 +420,19 @@ def test_sender_relationship_model_declares_source_unique_index(): assert "source_thread_id" in expression_text +def test_email_model_declares_owner_date_index(): + indexes = {index.name: index for index in Email.__table__.indexes} + + owner_date_index = indexes["ix_email_records_owner_date"] + + assert owner_date_index.unique is False + assert [column.name for column in owner_date_index.columns] == [ + "user_id", + "organization_id", + "date", + ] + + def test_tenant_config_model_declares_owner_scope_unique_index(): indexes = {index.name: index for index in TenantConfig.__table__.indexes}