Skip to content

chore: sql perf check #35

Open
tac0turtle wants to merge 1 commit intomainfrom
marko/sql
Open

chore: sql perf check #35
tac0turtle wants to merge 1 commit intomainfrom
marko/sql

Conversation

@tac0turtle
Copy link
Contributor

@tac0turtle tac0turtle commented Mar 19, 2026

Overview

Summary by CodeRabbit

  • New Features

    • Enhanced transaction list pagination with "First" and "Last" page navigation buttons for improved browsing.
  • Performance Improvements

    • Optimized transaction list queries for faster load times and improved responsiveness.

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

This PR implements cursor-based pagination for the transactions list endpoint, replacing offset-based pagination. Changes include backend handler updates with new cursor parameters (before_block, before_index, after_block, after_index, last_page), a database index for performance optimization, and frontend updates to manage cursor state and pagination navigation.

Changes

Cohort / File(s) Summary
Backend API Handler
backend/crates/atlas-server/src/api/handlers/transactions.rs
Replaced Query<Pagination> with Query<TransactionListParams> struct containing cursor fields and boolean flags. Implemented multiple conditional query paths: fetches older rows via (block_number, block_index) < ($1, $2) DESC, newer rows via (block_number, block_index) > ($1, $2) ASC-then-reversed, oldest page when last_page=true, and newest page otherwise. Added hard cap limit.min(100) for all paging queries.
Database Schema
backend/migrations/20240109000001_cursor_pagination_indexes.sql
Added composite index on transactions(block_number DESC, block_index DESC) to enable efficient indexed seeks for cursor-based pagination.
Frontend API Client
frontend/src/api/transactions.ts
Extended GetTransactionsParams interface with cursor pagination fields (before_block, before_index, after_block, after_index, last_page). Updated getTransactions to forward these parameters to the backend API request, normalizing last_page as undefined when false.
Frontend Transaction Page
frontend/src/pages/TransactionsPage.tsx
Introduced cursor state holding server pagination markers and replaced button handlers with goFirst/goPrev/goNext/goLast functions that update cursor based on first/last transaction block coordinates. Updated Next/Last button disabled conditions from equality check to >= comparison.

Sequence Diagram

sequenceDiagram
    actor User
    participant TransactionsPage as TransactionsPage
    participant GetTransactions as getTransactions()
    participant BackendAPI as Backend API
    participant Database as Database

    User->>TransactionsPage: Click Next/Prev/First/Last
    activate TransactionsPage
    TransactionsPage->>TransactionsPage: Update cursor state<br/>(block_number, block_index)
    TransactionsPage->>GetTransactions: Call with cursor params<br/>(after_block, after_index, etc.)
    deactivate TransactionsPage
    
    activate GetTransactions
    GetTransactions->>BackendAPI: POST /transactions<br/>with cursor parameters
    deactivate GetTransactions
    
    activate BackendAPI
    alt after_block & after_index provided
        BackendAPI->>Database: SELECT * WHERE<br/>(block_number, block_index) > (?, ?)<br/>ORDER BY ASC
    else before_block & before_index provided
        BackendAPI->>Database: SELECT * WHERE<br/>(block_number, block_index) < (?, ?)<br/>ORDER BY DESC
    else last_page = true
        BackendAPI->>Database: SELECT * (oldest)<br/>ORDER BY ASC then reverse
    else default
        BackendAPI->>Database: SELECT * (newest)<br/>ORDER BY DESC
    end
    deactivate BackendAPI
    
    activate Database
    Database-->>BackendAPI: Return transactions<br/>with limit ≤ 100
    deactivate Database
    
    activate BackendAPI
    BackendAPI-->>GetTransactions: JSON response with<br/>paginated results
    deactivate BackendAPI
    
    activate GetTransactions
    GetTransactions-->>TransactionsPage: Transactions data
    deactivate GetTransactions
    
    activate TransactionsPage
    TransactionsPage->>TransactionsPage: Extract first/last<br/>block coordinates
    TransactionsPage->>TransactionsPage: Render updated<br/>transaction list
    deactivate TransactionsPage
    
    TransactionsPage-->>User: Display transactions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops of joy through indexed rows,
Cursor-bound, the data flows,
No more offset's wasteful way,
Block by block, we leap and sway,
Pagination swift and keen—
Fastest fetches ever seen!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'chore: sql perf check' is vague and generic, using non-descriptive terms that do not convey meaningful information about the substantial changes to transaction pagination across backend, frontend, and database layers. Consider a more specific title that reflects the main change, such as 'Implement cursor-based pagination for transactions endpoint' or 'Migrate transactions from offset-based to cursor-based pagination'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch marko/sql
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
backend/crates/atlas-server/src/api/handlers/transactions.rs (1)

49-79: Consider validating partial cursor parameters.

If a client sends before_block without before_index (or vice versa), the current logic silently falls through to the next branch, potentially returning unexpected results. While the frontend always sends both or neither, the API could be more defensive.

🛡️ Optional: reject partial cursor parameters
// At the start of list_transactions, after getting `limit`:
let has_before = params.before_block.is_some() || params.before_index.is_some();
let has_after = params.after_block.is_some() || params.after_index.is_some();

if has_before && (params.before_block.is_none() || params.before_index.is_none()) {
    return Err(AtlasError::BadRequest("Both before_block and before_index are required".into()).into());
}
if has_after && (params.after_block.is_none() || params.after_index.is_none()) {
    return Err(AtlasError::BadRequest("Both after_block and after_index are required".into()).into());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/crates/atlas-server/src/api/handlers/transactions.rs` around lines 49
- 79, In list_transactions validate partial cursor parameters: detect when
either params.before_block or params.before_index is present without its partner
(and likewise for params.after_block/params.after_index) and return an
AtlasError::BadRequest (or appropriate error) instead of falling through; add
checks after computing limit that compute has_before/has_after from params and
if has_before && (params.before_block.is_none() ||
params.before_index.is_none()) or similarly for has_after then return
Err(AtlasError::BadRequest("Both before_block and before_index are
required".into()).into()) so the SQL branches only run when both cursor
components are provided.
frontend/src/pages/TransactionsPage.tsx (1)

148-162: Consider guarding against rapid navigation race conditions.

The navigation handlers correctly derive cursor values from the current result set. The guards (if (f), if (l), if (pagination)) handle empty states well.

One edge case to consider: if the user clicks navigation buttons rapidly before results load, the cursor derived from stale transactions data could cause unexpected jumps. This is a minor UX concern rather than a correctness bug, since the state will eventually stabilize.

🛡️ Optional: disable nav buttons while loading
   const goFirst = () => { setCursor({}); setPage(1); };
   const goPrev = () => {
+    if (loading) return;
     const f = transactions[0];
     if (f) setCursor({ afterBlock: f.block_number, afterIndex: f.block_index });
     setPage((p) => Math.max(1, p - 1));
   };
   const goNext = () => {
+    if (loading) return;
     const l = transactions[transactions.length - 1];
     if (l) setCursor({ beforeBlock: l.block_number, beforeIndex: l.block_index });
     setPage((p) => p + 1);
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/TransactionsPage.tsx` around lines 148 - 162, Navigation
handlers (goFirst/goPrev/goNext/goLast) can derive stale cursors if clicked
rapidly before new results load; guard them with the component's loading state
(e.g., transactionsLoading or isLoading) so they no-op while a fetch is
in-flight. Update goPrev/goNext/goFirst/goLast to early-return when loading is
true, and keep using setCursor and setPage only when not loading; reference the
existing handlers (goPrev, goNext, goFirst, goLast), the transactions array,
pagination object, and setCursor/setPage to locate where to add the guard.
Ensure the loading boolean used matches the query hook/state already in this
component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/crates/atlas-server/src/api/handlers/transactions.rs`:
- Around line 49-79: In list_transactions validate partial cursor parameters:
detect when either params.before_block or params.before_index is present without
its partner (and likewise for params.after_block/params.after_index) and return
an AtlasError::BadRequest (or appropriate error) instead of falling through; add
checks after computing limit that compute has_before/has_after from params and
if has_before && (params.before_block.is_none() ||
params.before_index.is_none()) or similarly for has_after then return
Err(AtlasError::BadRequest("Both before_block and before_index are
required".into()).into()) so the SQL branches only run when both cursor
components are provided.

In `@frontend/src/pages/TransactionsPage.tsx`:
- Around line 148-162: Navigation handlers (goFirst/goPrev/goNext/goLast) can
derive stale cursors if clicked rapidly before new results load; guard them with
the component's loading state (e.g., transactionsLoading or isLoading) so they
no-op while a fetch is in-flight. Update goPrev/goNext/goFirst/goLast to
early-return when loading is true, and keep using setCursor and setPage only
when not loading; reference the existing handlers (goPrev, goNext, goFirst,
goLast), the transactions array, pagination object, and setCursor/setPage to
locate where to add the guard. Ensure the loading boolean used matches the query
hook/state already in this component.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d454fcb-6a99-4ed9-aa23-5598e99714b3

📥 Commits

Reviewing files that changed from the base of the PR and between 1c00648 and f11a2ba.

📒 Files selected for processing (4)
  • backend/crates/atlas-server/src/api/handlers/transactions.rs
  • backend/migrations/20240109000001_cursor_pagination_indexes.sql
  • frontend/src/api/transactions.ts
  • frontend/src/pages/TransactionsPage.tsx

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