Live data from Hacker News

Building APIs: A Comparison Between Cursor and Offset Pagination?

news.ycombinator.com

1–10 of 27 posts

Building APIs: A Comparison Between Cursor and Offset Pagination?

#1
I have been mostly relying on offsets for implementing paging. It is more straightforward in implementation but I find cursor based pagination more intuitive in usage; plus it has better support for real-time data but on the negative side you are forced to use infinite scroll which is not always the best solution.

I was curious if anybody has any experience with these two approaches in their projects? And what are some other advantages and disadvantages on this? [1]

[1] I wrote this piece a while back but was thinking of updating it further: https://betterprogramming.pub/building-apis-a-comparison-between-cursor-and-offset-pagination-88261e3885f8

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#2
When I’ve worked with APIs that implemented pagination using cursors (Square), I really loved the ease of integration with my app. Writing a while loop that checks if the cursor is populated was better/faster than checking if the number of results from the last request was less than the requested number of rows.

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#3
I've used cursor pagination on daemons doing large batch processing. It worked fine.

If you're using a cursor, you should be iterating the entire (or at least most of) the result set over the course of the connection. I wouldn't think they're appropriate to API's, because maintaining the cursor state between requests sounds painful.

Offset is the only way to paginate arbitrarily sorted data sets, unless you plan on going hard on indexing. The performance falls off as the offset grows. You'll also have issues with apparent 'duplicates' between pages.

Keyset pagination is preferred if the data is usually sorted in one way. Most infinite scrolling falls into this category. Each row has a unique 'counter', which is (usually) monotonic. To skip pages, filter rows whose counter is later than the last row in the current page. This is fast and has no duplicates. However, your sorting options are constrained, as you must have an index for each sort option.

Keyset and offset can be combined to eliminate the duplication defect of offset alone. The performance issue will still be there. This becomes an engineering decision - in most use cases, the large offset perf hits don't really happen (aside from abuse, which almost always happens).

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#4
> you are forced to use infinite scroll which is not always the best solution.

You're not forced to. You could instead Show "8964 Results Total: Previous 100 | Next 100 ... Last 100" which in practice is the same as pagination.

Sure the user can't click in the MIDDLE of the page set, but what kind of a use case is that, anyway.

Anyway, cursor offsets are fundamentally O(1), and offsets are fundamentally O(N) where N is the offset. As a younger programmer, I used to wonder what is this algorithmic magic the database uses, so it knows how to compute offset 10000 without sorting and looking at the first 10000 values I sort by. Well? No magic, it just hustles through it and delivers. But slowly.

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#5
post #3

I've used cursor pagination on daemons doing large batch processing. It worked fine. If you're using a cursor, you should be iterating the entire (or at least most of) the result set over the course of the connection. I wouldn't think they're appropriate to API's, because maintaining the cursor state between requests sounds painful. Offset is the only way to paginate arbitrarily sorted data sets, unless you plan on g…

Important to call out here, but thanks to GraphQL’s unfortunate habit of using words which already have established meanings, when a lot of people talk about “cursor-based pagination”, they actually mean keyset pagination and not something that requires a server to maintain a stateful cursor.

I’m guessing this is the meaning the OP was referring to.

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#6
post #4

> you are forced to use infinite scroll which is not always the best solution. You're not forced to. You could instead Show "8964 Results Total: Previous 100 | Next 100 ... Last 100" which in practice is the same as pagination. Sure the user can't click in the MIDDLE of the page set, but what kind of a use case is that, anyway. Anyway, cursor offsets are fundamentally O(1), and offsets are fundamentally O(N) where N…

A skip list would allow a user to skip to the Nth page, but I don't know of any database that offers one in the form of an index.

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#7
post #4

> you are forced to use infinite scroll which is not always the best solution. You're not forced to. You could instead Show "8964 Results Total: Previous 100 | Next 100 ... Last 100" which in practice is the same as pagination. Sure the user can't click in the MIDDLE of the page set, but what kind of a use case is that, anyway. Anyway, cursor offsets are fundamentally O(1), and offsets are fundamentally O(N) where N…

A skip list would allow a user to skip to the Nth page, but I don't know of any database that offers one in the form of an index.

We can technically offer the users Nth page with cursors+offset hybrid, if we're fine with the computational demands (smaller than pure offset, but bigger than just prev|next|last).

Let's say we want to offer the user 5 pages, 100 records per page:

prev (7) | next (9) | 10 | 11 | 12 | 13 | 14 | ... | last

If you click "13", you'd need to query with cursor for page 9 (i.e. id > last rec of page 8) , and offset 400 limit 100.

This way offset never get gigantic, because if you show up to 5 pages to the user, you need to offset up to 5 * 100 records at a given time (combined with the cursor).

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#8
Cursor based pagination is much better from a performance point of view against large amounts of data.

It's also sometimes called keyset pagination - there's a good article about that here: https://use-the-index-luke.com/no-offset

I implemented it in https://datasette.io - it's quite tricky to build if you want to support arbitrary sort orders.

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#9
Offset pagination is hard on the backends. You can distribute some of the sorting load (see: https://engineering.medallia.com/blog/posts/sorting-and-pagi... ), but it's tricky, and less efficient in any case.

Cursor based pagination is way better, since the backend aggregation bucket size is capped by the page size times the number of nodes used for a distributed sort (essentially you aggregate on page sized priority queues, one per node, and then aggregate them).

Re: Building APIs: A Comparison Between Cursor and Offset Pagination?

#10
post #8

Cursor based pagination is much better from a performance point of view against large amounts of data. It's also sometimes called keyset pagination - there's a good article about that here: https://use-the-index-luke.com/no-offset I implemented it in https://datasette.io - it's quite tricky to build if you want to support arbitrary sort orders.

I built a utility for Graphene + Django to do it properly with arbitrary sorts, though one does need to be careful about exposing sorts orders which aren’t backed by any indexes.
Post reply on HN