fix(costs): eliminate N+1 in /tasks endpoint (#750)#830
Conversation
The costs /tasks endpoint opened a new SQLite connection per row for title lookup and ran a dominant-agent subquery per task — ~1000 connection opens + ~2000 queries at limit=1000. - tasks.get_titles(): batch title resolution in one WHERE id IN (...) query, replacing per-row tasks.get() in _query_top_tasks. - get_top_tasks_by_cost(): fold the dominant-agent lookup into a single windowed CTE (ROW_NUMBER over per-task agent call counts), removing the per-task subquery. Ties now break on agent_id for determinism. Query count is O(1) regardless of limit. Behavior preserved; adds tests for dominant-agent selection and batch title lookup.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 51 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdded a batch ChangesN+1 Query Elimination for Costs Tasks Endpoint
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Endpoint as /api/v2/costs/tasks
participant Router as costs_v2._query_top_tasks
participant Repo as token_repository.get_top_tasks_by_cost
participant Tasks as tasks.get_titles
participant DB as SQLite
Endpoint->>Repo: request top tasks by cost
Repo->>DB: single CTE query with ROW_NUMBER() per task
DB-->>Repo: rows with dominant agent_id
Repo-->>Router: top tasks with agent_id
Router->>Tasks: get_titles(workspace, all task_ids)
Tasks->>DB: SELECT title WHERE id IN (...)
DB-->>Tasks: matching titles
Tasks-->>Router: {id: title} map
Router-->>Endpoint: task entries with titles and dominant agents
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Review This PR replaces the N+1 title lookup ( Findings
Nothing else stood out — the SQL parameter binding order for the two CTEs is correct, the |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
codeframe/platform_store/repositories/token_repository.py (1)
406-433: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider adding an index on
token_usage(task_id, timestamp).Both
per_taskandagent_rankedfilter/group ontask_id/timestampover the full table for every call. Without a supporting index, this remains a full table scan even though the query count is now O(1); an index would materially help astoken_usagegrows.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@codeframe/platform_store/repositories/token_repository.py` around lines 406 - 433, The query in token_repository’s per_task and agent_ranked CTEs still scans token_usage by task_id and timestamp on every call, so add a supporting index on token_usage(task_id, timestamp) in the repository’s schema/migration setup. Update the token_repository data model or migration logic where token_usage indexes are defined so these filters and groupings can use the index efficiently without changing the query behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@codeframe/ui/routers/costs_v2.py`:
- Around line 226-243: The batch title lookup failure in the costs_v2 flow is
logged at debug even though it has the same fallback behavior as the
sqlite3.Error branch. Update the exception handler around
tasks_module.get_titles in the title-resolution logic to log at warning instead
of debug, keeping exc_info=True and the same contextual message so operators can
detect systemic lookup failures consistently.
---
Nitpick comments:
In `@codeframe/platform_store/repositories/token_repository.py`:
- Around line 406-433: The query in token_repository’s per_task and agent_ranked
CTEs still scans token_usage by task_id and timestamp on every call, so add a
supporting index on token_usage(task_id, timestamp) in the repository’s
schema/migration setup. Update the token_repository data model or migration
logic where token_usage indexes are defined so these filters and groupings can
use the index efficiently without changing the query behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b1714524-acd6-4a6d-9e64-c733163b5a37
📒 Files selected for processing (4)
codeframe/core/tasks.pycodeframe/platform_store/repositories/token_repository.pycodeframe/ui/routers/costs_v2.pytests/ui/test_costs_v2.py
Demo — acceptance criteria verified with outcome evidenceInstrumented
Mapping to acceptance criteria:
Pre-fix these numbers grew linearly with the number of tasks; post-fix they are flat. |
A batch failure degrades every row's title to a placeholder, so surface it at warning level for parity with the sqlite3.Error path above.
|
Applied — bumped the batch title-lookup failure log to |
|
Tip For best results, initiate chat on the files or code changes.
٩(◕‿◕)۶ |
|
Follow-up review Only one commit landed since my last review (c2b66a9): the batch title-lookup failure log level bumped from
Also noting CodeRabbit's nitpick (an index on Nothing new to add beyond these three points plus the index nitpick — the core approach (batched |
Closes #750
Problem
The costs
/tasksendpoint was O(N) in queries againstlimit(capped at 1000):costs_v2.py):_query_top_taskscalledtasks.get()per row — each opening a new SQLite connection — for title lookup.token_repository.py):get_top_tasks_by_costran a dominant-agent subquery per task.At
limit=1000(the badge-map case) that's ~1000 connection opens + ~2000 queries for a single dashboard request.Fix
tasks.get_titles(workspace, ids)— batch title resolution in oneWHERE id IN (...)query;_query_top_tasksnow calls it once instead of loopingtasks.get().get_top_tasks_by_cost— dominant agent per task is picked with a single windowed CTE (ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY COUNT(*) DESC, agent_id)), replacing the per-task subquery.Query count is now O(1) regardless of
limit— meeting the acceptance criteria (batched title lookup, dominant-agent folded into one windowed query).Behavior
agent_idascending) where it was previously "arbitrary" — a strict improvement.Tests
tasks.get_titlesbatch/omit-missing/empty-input cases.tests/ui/test_costs_v2.pypass; ruff clean.Known limitations
IN (...)batch relies on SQLite's 32766 bound-variable ceiling (3.32+);limitcaps at 1000 so no chunking needed (noted in a code comment).Summary by CodeRabbit
New Features
Bug Fixes
Tests