Skip to content

perf(api): DB-level pagination + drop N+1 message-count + adapter logging (Batch B)#2875

Merged
mikim merged 2 commits into
mainfrom
claude/pr2-backend-hardening
Jun 26, 2026
Merged

perf(api): DB-level pagination + drop N+1 message-count + adapter logging (Batch B)#2875
mikim merged 2 commits into
mainfrom
claude/pr2-backend-hardening

Conversation

@mikim

@mikim mikim commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary — Backend hardening (Batch B)

Addresses the verified high/medium backend findings from the codebase health assessment.

1. DB-level pagination (/trends, /ideas?status=)

Both endpoints fetched limit + offset rows and sliced in memory. Now offset/limit are pushed into the SQL query, and total comes from a COUNT instead of the page size.

  • TrendRepository: added offset to get_latest/get_by_category/get_all, and new count_by_category / count_by_period.
  • IdeaRepository.get_by_status: added offset.

2. Removed N+1 in /debates

DebateSession.to_dict() called len(self.messages), lazy-loading every session's full message list just to count — one extra query (and full row fetch) per session in the list. Now:

  • DebateRepository.count_messages_for_sessions() returns all counts in one COUNT ... GROUP BY.
  • to_dict(message_count=...) takes the count from the caller and never triggers a relationship load.

3. Adapter logging

10 signal adapters used print() for fetch errors. They now use a module logger so errors flow through the logging infrastructure (consistent with the rest of the codebase).

Tests

  • tests/test_pagination.py (7 new): offset pages don't overlap, total is a COUNT (not page size) for category/period, idea-status paging, the bulk message-count (incl. empty input + zero-message sessions), and /debates message_count.
  • Local gate: ruff + black clean, 196 tests pass.

Notes

  • Pure backend; no schema change, no API contract change (same response shapes).
  • Dependency floors (anthropic/fastapi/uvicorn) were already modernized in earlier PRs.

🤖 Generated with Claude Code

mikim and others added 2 commits June 26, 2026 19:17
Backend hardening (Batch B):

- DB-level pagination: /trends and /ideas?status= pushed offset/limit into
  the query instead of fetching `limit+offset` rows and slicing in memory.
  Added TrendRepository.{count_by_category,count_by_period} and offset params
  to get_latest/get_by_category/get_all and IdeaRepository.get_by_status, so
  `total` is a COUNT rather than the page size.

- Removed N+1 in the /debates list: DebateSession.to_dict() no longer
  lazy-loads `self.messages` just to count them. The endpoint fetches all
  counts in one DebateRepository.count_messages_for_sessions() COUNT...GROUP BY
  and passes message_count into to_dict().

- Signal adapters (10 files) now log via a module logger instead of print(),
  so per-adapter fetch errors flow through the logging infrastructure.

- tests/test_pagination.py: covers offset paging (no overlap), COUNT-based
  totals, the bulk message-count, and the /debates message_count.

Verified: ruff + black clean, 196 tests pass (incl. 7 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial review caught a regression: after to_dict() stopped lazy-loading
messages (defaulting message_count to 0), the two nested-debate serializations
in get_idea_detail still called to_dict() without a count, so /ideas/{id}
reported message_count: 0 despite the messages being present. Pass
message_count=len(messages) at both sites; add a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mikim mikim merged commit 388a533 into main Jun 26, 2026
3 checks passed
@mikim mikim deleted the claude/pr2-backend-hardening branch June 26, 2026 10:39
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.

1 participant