Description
Section 1.6 and the Heroku API reference both recommend using Range
for pagination.
Using the Range
/Next-Range
mechanism from HTTP accidentally exposes in the API that the data is (likely) obtained by computing the requested data within a selected window; for instance, for a query that exposes data from a SQL database:
SELECT * FROM table WHERE condition ORDER BY field LIMIT (#page * size), size
Moreover, this implementation pattern cannot provide a consistent view of the data when concurrent actions can introduce new elements at arbitrary indexes. Consider the following sequence of events:
- client requests the data, gets a partial answer with the first 10 elements and
Next-Range: 20 ..
; let's call the elementse0
toe9
; - a concurrent query triggers the insertion of elements
x
andy
at indexes 9 and 14; - the client requests the next page, and receives
e10 = e9
,e11
...e19
:e9
has been seen twice by the client (unless you use id-based pagination), and it sawy
but notx
(despite them having been added simultaneously, regardless of whether you use id-based pagination).
The right solution is to provide the API client with a consistent view of the data; for APIs returning results of DB queries, this is easily done using either cursors or materialized views, the second having the additional advantage of supporting later refinement queries.
At the API level, this should be materialized using the Link
header, with (at least) a reference tagged rel=next
. Using Link
allows the API implementer to store there whatever is required to designate the right query result (for instance, a DB cursor id).