Skip to content

perf(admin): scope admin user listing aggregates to the requested page (#480) - #588

Open
Lyrapower wants to merge 1 commit into
arflexx:mainfrom
Lyrapower:fix/issue-480-admin-users-page-scoped-aggregates
Open

Lyrapower wants to merge 1 commit into
arflexx:mainfrom
Lyrapower:fix/issue-480-admin-users-page-scoped-aggregates

Conversation

@Lyrapower

Copy link
Copy Markdown

Description

Closes #480

GET /api/v1/admin/users no longer issues per-user queries — the correlated subqueries were replaced by grouped LEFT JOIN aggregates in #256 (97c848a). What remained was the other half of the problem described in this issue: each request still aggregated every row of transactions and trade_offers, so latency and DB load kept growing with total platform activity even though only one page of users is returned.

Change

server/src/routes/admin.ts — the page of users is selected first in a page_users CTE, and both aggregates are restricted to that page with a batched IN (SELECT id FROM page_users):

  • balance: transactions WHERE user_id IN (page) — uses the index on transactions(user_id) (migration 010)
  • trade count: trade_offers WHERE seller_id IN (page) UNION ALL buyer_id IN (page) — uses the seller/buyer indexes (migration 010)

Still exactly two queries per request (page + total count) regardless of page size. Response shape, ordering (created_at DESC), and pagination metadata are unchanged.

Verification

Equivalence and performance on real Postgres 16 (20k users, ~500k transactions, ~100k trade offers, with the migration-010 indexes): old and new queries return byte-identical results for pages (limit, offset) = (20,0), (20,20), (50,0), (1,0), (7,13), (50,1000), (100,19950) and an out-of-range page. EXPLAIN ANALYZE for a 20-user page: 312.7 ms → 7.9 ms.

Tests (server/src/routes/admin.test.ts, new describe block):

  • query count is identical for a 1-user page and a 50-user page (auth 2 + page 1 + count 1 = 4); any unexpected/per-user query fails the test
  • empty page returns the same query count and correct pagination
  • response keys and pagination metadata (page, limit, total, totalPages) unchanged; users with no activity still get 0
  • the page query passes [limit, offset] and scopes both aggregates to page_users
  • mutation check: with the previous SQL restored, the 4 new tests fail; with this change, all 24 tests in the file pass

Note on CI

main currently fails tsc --noEmit because of src/services/notifications.ts / src/__tests__/notificationTemplates.test.ts (missing renderTemplate export, undefined event/payload/renderTradeDisputeOpened). That breaks compilation of admin.test.ts under ts-jest on main too, so I ran the admin suite with ts-jest diagnostics turned off. This PR adds no new type errors (tsc --noEmit errors are all in those two notification files).

Disclosure: I use AI assistance. I ran the tests and reviewed every change before submitting.

Select the page of users first and restrict the balance and trade-count
aggregates to it with a batched IN, so the listing no longer aggregates
all transactions/trade_offers per request. Still two queries per page;
response shape and pagination unchanged. Adds query-count regression tests.

Closes arflexx#480

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLxKZmAHvHzCQSoiPAXVMq
@Lyrapower
Lyrapower requested a review from dark-sarge as a code owner October 22, 2026 18:18
@Lyrapower

Copy link
Copy Markdown
Author

Hi! Sorry for the timing — this PR went up a few hours after Wave 10 closed. The work was ready, but my build environment was down overnight and I missed the cutoff. Would you be open to rolling #480 over to the next Wave? Happy to address any review feedback in the meantime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Backend] - Fix N+1 Query in Admin User Listing

1 participant