info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
GitLab uses two primary types of pagination: **offset** and **keyset**
(sometimes called cursor-based) pagination.
The GraphQL API mainly uses keyset pagination, falling back to offset pagination when needed.
### Offset pagination
This is the traditional, page-by-page pagination, that is most common,
and used across much of GitLab. You can recognize it by
a list of page numbers near the bottom of a page, which, when clicked,
take you to that page of results.
For example, when you click **Page 100**, we send `100` to the
backend. For example, if each page has say 20 items, the
backend calculates `20 * 100 = 2000`,
and it queries the database by offsetting (skipping) the first 2000
records and pulls the next 20.
```plaintext
page number * page size = where to find my records
```
There are a couple of problems with this:
- Performance. When we query for page 100 (which gives an offset of
2000), then the database has to scan through the table to that
specific offset, and then pick up the next 20 records. As the offset
increases, the performance degrades quickly.
Read more in
[The SQL I Love <3. Efficient pagination of a table with 100M records](http://allyouneedisbackend.com/blog/2017/09/24/the-sql-i-love-part-1-scanning-large-table/).
- Data stability. When you get the 20 items for page 100 (at
offset 2000), GitLab shows those 20 items. If someone then
deletes or adds records in page 99 or before, the items at
offset 2000 become a different set of items. You can even get into a
situation where, when paginating, you could skip over items,
because the list keeps changing.
Read more in
[Pagination: You're (Probably) Doing It Wrong](https://coderwall.com/p/lkcaag/pagination-you-re-probably-doing-it-wrong).
### Keyset pagination
Given any specific record, if you know how to calculate what comes
after it, you can query the database for those specific records.
For example, suppose you have a list of issues sorted by creation date.
If you know the first item on a page has a specific date (say Jan 1), you can ask
for all records that were created after that date and take the first 20.
It no longer matters if many are deleted or added, as you always ask for
the ones after that date, and so get the correct items.
Unfortunately, there is no easy way to know if the issue created
on Jan 1 is on page 20 or page 100.
Some of the benefits and tradeoffs of keyset pagination are
There may be times where you need to return data through the GitLab API that is stored in
another system. In these cases you may have to paginate a third-party's API.
An example of this is with our [Error Tracking](../../operations/error_tracking.md) implementation,
where we proxy [Sentry errors](../../operations/error_tracking.md#sentry-error-tracking) through
the GitLab API. We do this by calling the Sentry API which enforces its own pagination rules.
This means we cannot access the collection within GitLab to perform our own custom pagination.
For consistency, we manually set the pagination cursors based on values returned by the external API, using `Gitlab::Graphql::ExternallyPaginatedArray.new(previous_cursor, next_cursor, *items)`.
You can see an example implementation in the following files:
- [`types/error__tracking/sentry_error_collection_type.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/error_tracking/sentry_error_collection_type.rb) which adds an extension to `field :errors`.
- [`resolvers/error_tracking/sentry_errors_resolver.rb`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb) which returns the data from the resolver.