Evidence ledger
One row per claim in The cursor is a lease, not a bookmark: paginating the unbounded list: who published it, what grade it carries, when it was written, when the link was last checked, and the quote or figure it rests on. Nothing in the guide is cited from memory, so anything not in this table is not in the guide.
One row per claim. Tier grades follow the skill's hierarchy (postmortem > source > adr >
casestudy > paper/talk > blog > vendor). Checked dates are when the URL was fetched in
this session. This session's network egress allowed code hosts (github.com, gitlab.com,
raw.githubusercontent.com) and a few registry and documentation hosts; engineering-blog
platforms, USENIX, arXiv and video hosts were refused by the egress proxy, so the guide is built
from the repository-native and vendor-documentation record and discloses that.
| # | Org | Title | Tier | Published | Checked | URL | Claim I take from it | Supporting quote or figure |
|---|---|---|---|---|---|---|---|---|
| 1 | GitLab | Production incident 5843: silent change to offset pagination limit impacted a customer | postmortem | 2021-11-02 | 2026-10-01 | https://gitlab.com/gitlab-com/gl-infra/production/-/issues/5843 | Tightening an offset limit behind a feature flag broke a paying customer's tooling; the pagination contract is an API contract. | "The feature flag lower_relation_max_count_limit has been temporarily set to false, while the affected customer re-writes their tooling to deal with the lack of total count & page information in our billable_members API responses." |
| 2 | Kubernetes | Issue 98423: kube-apiserver high memory usage on pending pods storm | postmortem | 2021-01-26 | 2026-10-01 | https://github.com/kubernetes/kubernetes/issues/98423 | An operator's production record of list-driven memory explosion: ~90 GB apiserver spike and OOM during a 400-node upscale with thousands of pending pods. | "The kube-apiserver consumed memory spiked to ~90G the moment we upscaled the cluster with +400 nodes while having thousands of pending pods." |
| 3 | Kubernetes | Issue 114276: apiserver builds up high memory after serving a few large LIST requests | source | 2022-12-04 | 2026-10-01 | https://github.com/kubernetes/kubernetes/issues/114276 | Reproduction with numbers: twenty sequential ~50 MiB LIST responses leave the apiserver holding 1.5 GiB. | "The apiserver process has 1.5 GIB memory usage after sequentially serving a few large LIST requests (about 50MiB in response size)." |
| 4 | Kubernetes | Design proposal: consistent API chunking | adr | merged 2017-08-29 | 2026-10-01 | https://github.com/kubernetes/design-proposals-archive/blob/main/api-machinery/api-chunking.md | The continuation-token design: opaque token encoding a snapshot version plus a position, served natively by the store's range reads. | "Our primary consistent store etcd3 offers support for efficient chunking with minimal overhead, and mechanisms exist for other potential future stores such as SQL databases or Consul to also implement a simple form of consistent chunking." |
| 5 | Kubernetes | community PR 896: design for consistent API chunking | adr | opened 2017-08-11, merged 2017-08-29 | 2026-10-01 | https://github.com/kubernetes/community/pull/896 | The recorded design argument: tokens must be opaque, and a reviewer objected that tokens embedding store keys could leak names the caller cannot see. | Review record: clients "must consider the continue token as opaque"; reviewer raised that continue tokens containing etcd keys could expose namespace and object names to users lacking permission. |
| 6 | Kubernetes | KEP-3157: watch list, streaming lists | adr | KEP in kubernetes/enhancements, checked state | 2026-10-01 | https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/3157-watch-list/README.md | Even paginated LISTs cost about five times the etcd response in temporary memory, and the project's answer is to stop paginating and stream instead. | "The bottom line is around O(5*the_response_from_etcd) of temporary memory consumption. Neither priority and fairness nor Golang garbage collection is able to protect the system from exhausting memory." |
| 7 | Kubernetes | KEP-3157: watch list, streaming lists | adr | KEP in kubernetes/enhancements, checked state | 2026-10-01 | https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/3157-watch-list/README.md | The measured goal: from O(watchers × page-size × object-size × 5) down to a ~2 MB constant per watcher. | "considerably reduce (temporary) memory footprint of LISTs, down from O(watchers*page-size*object-size*5) to O(watchers*constant), constant around 2 MB." |
| 8 | Kubernetes | Docs: API concepts (list pagination semantics) | vendor | current, checked state | 2026-10-01 | https://github.com/kubernetes/website/blob/main/content/en/docs/reference/using-api/api-concepts.md | A continue token is a lease, not a bookmark: it expires in about five minutes and the server answers 410 Gone, offering an inconsistent restart. | "a continue token will expire after a short amount of time (by default 5 minutes) and return a 410 Gone if more results cannot be returned" |
| 9 | Kubernetes | kubectl get source | source | current master | 2026-10-01 | https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/kubectl/pkg/cmd/get/get.go | The flagship client pages by default: chunk size 500. | "NewGetOptions returns a GetOptions with default chunk size 500." |
| 10 | Elasticsearch | IndexSettings.java | source | current main | 2026-10-01 | https://github.com/elastic/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/index/IndexSettings.java | The default refusal point for offset-style paging is 10,000 hits per index. | MAX_RESULT_WINDOW_SETTING = Setting.intSetting("index.max_result_window", 10000, 1, ...) |
| 11 | Elasticsearch | Docs: paginate search results | vendor | current main | 2026-10-01 | https://github.com/elastic/elasticsearch/blob/main/docs/reference/elasticsearch/rest-apis/paginate-search-results.md | Why the window exists: every shard must materialise all previous pages, so depth converts directly into memory and CPU on every shard. | "Each shard must load its requested hits and the hits for any previous pages into memory. For deep pages or large sets of results, these operations can significantly increase memory and CPU usage, resulting in degraded performance or node failures." |
| 12 | Elasticsearch | Docs: paginate search results | vendor | current main | 2026-10-01 | https://github.com/elastic/elasticsearch/blob/main/docs/reference/elasticsearch/rest-apis/paginate-search-results.md | The sanctioned deep walk is search_after plus a point-in-time, because a refresh between pages reorders results. | "If a refresh occurs between these requests, the order of your results may change, causing inconsistent results across pages. To prevent this, you can create a point in time (PIT) to preserve the current index state over your searches." |
| 13 | GitLab | Issue 8754: Elasticsearch results jumping to last page gives HTTP 500 | source | 2018-12-07 | 2026-10-01 | https://gitlab.com/gitlab-org/gitlab/-/issues/8754 | GitLab.com hit the 10,000 window in production search; the team's reading was to fix pagination, not raise the window. | "Result window is too large, from + size must be less than or equal to: [10000] but was [36920]. ... setting index.max_result_window to a higher value probably won't perform well so we should really be looking at implementing an efficient pagination strategy." |
| 14 | GitLab | MR 251701: cap search pagination at the Elasticsearch result window (closed unmerged) | source | opened 2026-08-24, closed 2026-09-18 unmerged | 2026-10-01 | https://gitlab.com/gitlab-org/gitlab/-/merge_requests/251701 | Eight years after issue 8754 the same window still surfaces as user-facing 500s, and a capping fix was attempted and closed without merging; the close reason is not visible to this session. | "Searching merge requests past the Elasticsearch result window returns 400 Bad Request from the indexer, which surfaces to the user as a 500" |
| 15 | GitLab | MR 256256: encode an offset in policy store pagination cursors (closed unmerged) | source | opened 2026-09-17, closed 2026-09-18 unmerged | 2026-10-01 | https://gitlab.com/gitlab-org/gitlab/-/merge_requests/256256 | A recorded cursor-design bug: a cursor that encodes a page number silently depends on the previous request's page size, skipping or repeating rows when the client changes it. | "The cursor encoded a page number, so the rows a cursor pointed at depended on the page size used for the previous request. A client that changed first mid-walk skipped or repeated policies" |
| 16 | GitLab | Docs: REST API pagination | vendor | current master | 2026-10-01 | https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/api/rest/_index.md | Keyset is positioned as the scale path: offset was deprecated on the users endpoint in 16.5 and keyset is enforced there beyond 50,000 records since 17.0. | "The users endpoint enforces keyset-based pagination when the number of requested records is greater than 50,000 in GitLab 17.0." and "runtime is independent of the size of the collection." |
| 17 | GitLab | Docs: REST API pagination (response headers) | vendor | current master | 2026-10-01 | https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/api/rest/_index.md | The count, not the page, is what gets rationed: beyond 10,000 records GitLab stops sending totals and the last-page link. | "For performance reasons, if a query returns more than 10,000 records, GitLab doesn't return the following headers: x-total, x-total-pages, rel=\"last\" link" |
| 18 | GitLab | Development guidelines: pagination | adr | current master | 2026-10-01 | https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/database/pagination_guidelines.md | The internal engineering rule: offset cost grows with the page number, which makes it unsuitable for large tables; keyset reads only the page it returns. | "When requesting a large page number, the database needs to read PAGE * PAGE_SIZE rows. This makes offset pagination unsuitable for large database tables." and "this query read only 5 rows (offset-based pagination would read 10 rows)" |
| 18b | GitLab | Development guidelines: pagination | adr | current master | 2026-10-01 | https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/database/pagination_guidelines.md | Switching schemes is formally a breaking change, and keyset cannot express page numbers, so UX decides as much as the database does. | "Removing one type of pagination entirely is a breaking change." and "Keyset pagination cannot provide page numbers because the paging logic might depend on different columns." |
| 19 | GitHub | Docs: using pagination in the REST API | vendor | current main | 2026-10-01 | https://github.com/github/docs/blob/main/content/rest/using-the-rest-api/using-pagination-in-the-rest-api.md | The navigation contract is the Link header, with rel next, prev, first and last supplied by the server. | "You can use the link header from the response to request additional pages of data." Example shows rel=\"last\" pointing at page 515. |
| 20 | GitHub | Docs: REST search API | vendor | current main | 2026-10-01 | https://github.com/github/docs/blob/main/content/rest/search/search.md | Search is capped outright: the API serves at most the first 1,000 results of any query. | "the GitHub REST API provides up to 1,000 results for each search." |
| 21 | Mastodon | API guidelines: pagination | vendor | current main | 2026-10-01 | https://github.com/mastodon/documentation/blob/main/content/en/api/guidelines.md | A third shape in the wild: cursoring by entity ID (max_id, min_id, since_id) with Link headers, no offsets and no totals. | "Many API methods allow you to paginate for more information, using parameters such as limit, max_id, min_id, and since_id." |
| 22 | PostgreSQL | Docs: LIMIT and OFFSET (queries.sgml) | vendor | current master | 2026-10-01 | https://github.com/postgres/postgres/blob/master/doc/src/sgml/queries.sgml | The storage engine's own statement of offset cost: skipped rows are still computed. | "The rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large OFFSET might be inefficient." |
| 23 | npm | registry API docs (search endpoint) | vendor | current main | 2026-10-01 | https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md | A deliberately offset-paginated API at registry scale, with a small hard page cap: size default 20, max 250, offset via from. | "size ... how many results should be returned (default 20, max 250)"; "from ... offset to return results from" |
| 24 | npm | live search endpoint record | source | fetched 2026-10-01 | 2026-10-01 | https://registry.npmjs.org/-/v1/search?text=kubernetes&size=3&from=3 | The live endpoint honours from/size and returns a total (6,364 for this query on the check date), so offset plus counts is a working choice at bounded result sizes. | Response JSON: "total": 6364 with 3 objects returned at offset 3. |
| 25 | Docker Hub | live tags endpoint record | source | fetched 2026-10-01 | 2026-10-01 | https://hub.docker.com/v2/repositories/library/python/tags/?page_size=2 | A page-number API that hands the client a ready-made next URL and a full count (3,923 tags on the check date). | Response JSON: "count": 3923, "next": "https://hub.docker.com/v2/repositories/library/python/tags/?page=2&page_size=2" |
Sources located but not usable in this session
Ahmet Alp Balkan's measured write-up of Kubernetes list performance (ahmet.im), the Kubernetes blog post on API streaming (kubernetes.io), engineering-blog accounts of cursor pagination at Slack, Shopify and Stripe, and all conference talks and papers on the topic were located via search, but their hosts were refused by this session's egress proxy. None of them is cited and none of their content is used; the gap is disclosed in the guide. The guide therefore contains no blog, paper or talk tier sources, and its two postmortems are the incident records publicly readable on the code hosts.