Skip to main content

Cursor-Based Pagination

When working with large audit tables, traditional offset pagination becomes slow and inefficient. Cursor-based pagination is a better alternative for high-volume datasets.

This guide explains how to use cursor pagination with Ledgerly entries.


Why Cursor Pagination?

Offset pagination:

LedgerEntry::latest()->paginate(50);

Problems with large tables:

  • Slow queries at high offsets
  • Increasing query cost
  • Inconsistent results if rows change

Cursor pagination avoids these issues by using a stable reference point.


Basic Example

Using Laravel’s built-in cursor pagination:

use Ledgerly\Core\Models\LedgerEntry;

$entries = LedgerEntry::orderBy('id')->cursorPaginate(50);

This retrieves the next set of entries based on the last seen record rather than an offset.


Displaying Results in a Controller

Example:

public function index()
{
$entries = LedgerEntry::orderBy('id')->cursorPaginate(50);

return view('entries.index', compact('entries'));
}

Blade example:

@foreach ($entries as $entry)
{{ $entry->action }}
@endforeach

{{ $entries->links() }}

Cursor Pagination for Timelines

Example timeline query:

LedgerEntry::forTarget($invoice)
->orderBy('id')
->cursorPaginate(50);

This works efficiently even with millions of rows.


Cursor Pagination by Date

Example:

LedgerEntry::whereDate('created_at', today())
->orderBy('id')
->cursorPaginate(100);

Cursor pagination works best with a stable ordering column like id or created_at.


Choosing the Ordering Column

Recommended:

  • id (best)
  • created_at (acceptable if indexed)

Avoid:

  • non-indexed columns
  • computed columns

A stable ordering column ensures reliable pagination.


API Example

Cursor pagination works well in APIs:

return LedgerEntry::orderBy('id')
->cursorPaginate(50)
->toArray();

Response will include a cursor token instead of page numbers.


Cursor vs. Offset Pagination

FeatureOffsetCursor
Large tablesSlowFast
Stable resultsNoYes
Page numbersYesNo
API performanceModerateExcellent

When to Use Cursor Pagination

Recommended for:

  • Activity timelines
  • Audit logs
  • Infinite scroll interfaces
  • APIs with large datasets

Offset pagination is acceptable for:

  • small tables
  • admin panels with limited data

Performance Tips

For best performance:

  • Always index the ordering column
  • Prefer ordering by id
  • Combine with filtering by date range

Example:

LedgerEntry::where('severity', 'error')
->orderBy('id')
->cursorPaginate(50);

Summary

Cursor pagination:

  • Scales better than offset pagination
  • Keeps queries fast on large tables
  • Works well with timelines and APIs

For large audit logs, cursor pagination is strongly recommended.