perf: add DB performance indexes and fix N+1 query in backtest listing
Add 10 indexes across prices, etf_prices, financials, valuations, holdings, transactions, signals, portfolio_snapshots, and etfs tables. Fix N+1 query in list_backtests by eager-loading backtest results with joinedload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4483f6e4ba
commit
b80feb7176
49
backend/alembic/versions/add_performance_indexes.py
Normal file
49
backend/alembic/versions/add_performance_indexes.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"""add performance indexes
|
||||||
|
|
||||||
|
Revision ID: b7c8d9e0f1a2
|
||||||
|
Revises: 606a5011f84f
|
||||||
|
Create Date: 2026-03-18 22:00:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'b7c8d9e0f1a2'
|
||||||
|
down_revision: Union[str, None] = '606a5011f84f'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# Tier 1: backtest/strategy performance
|
||||||
|
op.create_index('idx_prices_ticker_date', 'prices', ['ticker', sa.text('date DESC')])
|
||||||
|
op.create_index('idx_etf_prices_ticker_date', 'etf_prices', ['ticker', sa.text('date DESC')])
|
||||||
|
op.create_index('idx_financials_ticker_base_date', 'financials', ['ticker', sa.text('base_date DESC')])
|
||||||
|
op.create_index('idx_valuations_ticker_base_date', 'valuations', ['ticker', sa.text('base_date DESC')])
|
||||||
|
|
||||||
|
# Tier 2: portfolio queries
|
||||||
|
op.create_index('idx_holdings_portfolio_id', 'holdings', ['portfolio_id'])
|
||||||
|
op.create_index('idx_transactions_portfolio_id_executed_at', 'transactions', ['portfolio_id', sa.text('executed_at DESC')])
|
||||||
|
op.create_index('idx_signals_date_status', 'signals', [sa.text('date DESC'), 'status'])
|
||||||
|
op.create_index('idx_snapshots_portfolio_date', 'portfolio_snapshots', ['portfolio_id', sa.text('snapshot_date DESC')])
|
||||||
|
|
||||||
|
# Tier 3: ETF filters
|
||||||
|
op.create_index('idx_etf_asset_class', 'etfs', ['asset_class'])
|
||||||
|
op.create_index('idx_etf_price_date', 'etf_prices', [sa.text('date DESC')])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_index('idx_etf_price_date', table_name='etf_prices')
|
||||||
|
op.drop_index('idx_etf_asset_class', table_name='etfs')
|
||||||
|
op.drop_index('idx_snapshots_portfolio_date', table_name='portfolio_snapshots')
|
||||||
|
op.drop_index('idx_signals_date_status', table_name='signals')
|
||||||
|
op.drop_index('idx_transactions_portfolio_id_executed_at', table_name='transactions')
|
||||||
|
op.drop_index('idx_holdings_portfolio_id', table_name='holdings')
|
||||||
|
op.drop_index('idx_valuations_ticker_base_date', table_name='valuations')
|
||||||
|
op.drop_index('idx_financials_ticker_base_date', table_name='financials')
|
||||||
|
op.drop_index('idx_etf_prices_ticker_date', table_name='etf_prices')
|
||||||
|
op.drop_index('idx_prices_ticker_date', table_name='prices')
|
||||||
@ -4,7 +4,7 @@ Backtest API endpoints.
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session, joinedload
|
||||||
|
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.api.deps import CurrentUser
|
from app.api.deps import CurrentUser
|
||||||
@ -62,6 +62,7 @@ async def list_backtests(
|
|||||||
"""List all backtests for current user."""
|
"""List all backtests for current user."""
|
||||||
backtests = (
|
backtests = (
|
||||||
db.query(Backtest)
|
db.query(Backtest)
|
||||||
|
.options(joinedload(Backtest.result))
|
||||||
.filter(Backtest.user_id == current_user.id)
|
.filter(Backtest.user_id == current_user.id)
|
||||||
.order_by(Backtest.created_at.desc())
|
.order_by(Backtest.created_at.desc())
|
||||||
.all()
|
.all()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user