perf: replace N+1 hydration with SQL aggregates on active repos and users pages#253
Merged
Merged
Conversation
…sers pages Eliminates three N+1 query patterns identified in #247: 1. Active Repos page: replace pushStore.find(limit=5000) + per-record hydration with a single GROUP BY aggregate via PushStore.summarizeByRepo(). JdbcPushStore implements this as one SQL query; default interface implementation falls back to find() for InMemory/Mongo. 2. Users list: replace per-user countPushesByStatus() loop (1 + N×3 queries) with a single aggregate via PushStore.countPushStatusByUser(). UserController.list() loads all push status counts in one query and distributes them to UserSummary objects without touching push records. toDetail() now uses PushStore.countByStatus(PushQuery) instead of fetching and hydrating all push records for the user. 3. Push page status counts: add GET /api/push/counts endpoint that returns a Map<String, Long> from a single GROUP BY query. Accepts the same filter params as the list endpoint (repo, user, search) so tab counts reflect the current filter set. Updates PushList.tsx to call this endpoint instead of firing one request per status value. Also extracts the WHERE clause builder in JdbcPushStore into a shared private method reused by find(), countByStatus(), and the existing query path. closes #247 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pushStore.find(limit=5000)+ per-record hydration with a singleGROUP BYaggregate (PushStore.summarizeByRepo()). At 130 records × 3 child queries × 8ms RTT this was ~3s; now one query.countPushesByStatus()loop (1 + N×3 queries) with a singleSELECT resolved_user, status, COUNT(*) GROUP BYviaPushStore.countPushStatusByUser().GET /api/users/{username}also updated viacountByStatus(query).GET /api/push/countsreturningMap<String, Long>from oneGROUP BY statusquery.PushList.tsxpreviously fired 7 separate fully-hydrated requests (one per status) just to get.length; now one lightweight call. Counts also update when repo/user filters change.The WHERE clause builder in
JdbcPushStorewas extracted into a shared private method reused byfind()andcountByStatus(). Default interface implementations onPushStorekeepInMemoryPushStoreandMongoPushStorecorrect without changes.Test plan
PushControllerTest,RepoControllerTestupdated and passingPushControllerTest$Countstests cover the/api/push/countsendpointcloses #247
🤖 Generated with Claude Code