Add calendar_events indexes and optimize dashboard queries
Migration 054: three indexes on calendar_events table: - (calendar_id, start_datetime) for range queries - (parent_event_id) for recurrence bulk operations - (calendar_id, is_starred, start_datetime) for starred widget Dashboard: replaced correlated subquery with single materialized list fetch for user_calendar_ids in both /dashboard and /upcoming handlers — eliminates 2 redundant subquery evaluations per request. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3e738b18d4
commit
e12687ca6f
36
backend/alembic/versions/054_add_calendar_event_indexes.py
Normal file
36
backend/alembic/versions/054_add_calendar_event_indexes.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""Add performance indexes to calendar_events
|
||||
|
||||
Revision ID: 054
|
||||
Revises: 053
|
||||
"""
|
||||
from alembic import op
|
||||
|
||||
revision = "054"
|
||||
down_revision = "053"
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Covers range queries in dashboard today's events and events list
|
||||
op.create_index(
|
||||
"ix_calendar_events_calendar_start",
|
||||
"calendar_events",
|
||||
["calendar_id", "start_datetime"],
|
||||
)
|
||||
# Covers bulk DELETE on recurrence edit/regeneration and sibling lookups
|
||||
op.create_index(
|
||||
"ix_calendar_events_parent_event_id",
|
||||
"calendar_events",
|
||||
["parent_event_id"],
|
||||
)
|
||||
# Covers starred widget query (calendar_id + is_starred + start_datetime)
|
||||
op.create_index(
|
||||
"ix_calendar_events_calendar_starred_start",
|
||||
"calendar_events",
|
||||
["calendar_id", "is_starred", "start_datetime"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index("ix_calendar_events_calendar_starred_start", table_name="calendar_events")
|
||||
op.drop_index("ix_calendar_events_parent_event_id", table_name="calendar_events")
|
||||
op.drop_index("ix_calendar_events_calendar_start", table_name="calendar_events")
|
||||
@ -35,8 +35,12 @@ async def get_dashboard(
|
||||
today = client_date or date.today()
|
||||
upcoming_cutoff = today + timedelta(days=current_settings.upcoming_days)
|
||||
|
||||
# Subquery: calendar IDs belonging to this user (for event scoping)
|
||||
user_calendar_ids = select(Calendar.id).where(Calendar.user_id == current_user.id)
|
||||
# Fetch calendar IDs once as a plain list — PostgreSQL handles IN (1,2,3) more
|
||||
# efficiently than re-evaluating a correlated subquery for each of the 3 queries below.
|
||||
calendar_id_result = await db.execute(
|
||||
select(Calendar.id).where(Calendar.user_id == current_user.id)
|
||||
)
|
||||
user_calendar_ids = [row[0] for row in calendar_id_result.all()]
|
||||
|
||||
# Today's events (exclude parent templates — they are hidden, children are shown)
|
||||
today_start = datetime.combine(today, datetime.min.time())
|
||||
@ -173,8 +177,11 @@ async def get_upcoming(
|
||||
overdue_floor = today - timedelta(days=30)
|
||||
overdue_floor_dt = datetime.combine(overdue_floor, datetime.min.time())
|
||||
|
||||
# Subquery: calendar IDs belonging to this user
|
||||
user_calendar_ids = select(Calendar.id).where(Calendar.user_id == current_user.id)
|
||||
# Fetch calendar IDs once as a plain list (same rationale as /dashboard handler)
|
||||
calendar_id_result = await db.execute(
|
||||
select(Calendar.id).where(Calendar.user_id == current_user.id)
|
||||
)
|
||||
user_calendar_ids = [row[0] for row in calendar_id_result.all()]
|
||||
|
||||
# Build queries — include overdue todos (up to 30 days back) and snoozed reminders
|
||||
todos_query = select(Todo).where(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user