Пагинация API: почему полный экспорт каталога может пропускать товары
Удаление записи между страницами сдвигает выборку. На небольшом примере разбираем смещение, курсор, согласованный снимок и возобновление экспорта после сбоя.

В этой статье
Экспорт прочитал первые 100 товаров, затем следующие 100. Ошибок API нет, а 1 товар в результате пропущен. Между запросами из начала списка удалили запись: всё после неё сдвинулось. Переход «пропустить первые 100» теперь означает уже другую границу.
Для выгрузки меняющегося каталога нужно заранее определить две вещи: как продолжать чтение и какое состояние считать полным результатом. Устойчивый курсор помогает с первым. Для второго может понадобиться согласованный снимок или отдельная сверка изменений. Само наличие кнопки «следующая страница» не решает обе задачи.
Пропуск возникает даже при правильной сортировке
Возьмём учебный список идентификаторов 10, 20, 30, 40, 50, 60 и размер страницы три. Первый ответ содержит 10, 20, 30. Затем запись 20 удаляется. Список становится 10, 30, 40, 50, 60. Запрос со смещением три пропустит первые три текущие записи и вернёт 50, 60. Идентификатор 40 не попал ни в один ответ.
Если перед второй страницей добавить запись в начало, возможен обратный эффект: уже прочитанный товар снова окажется за смещением. Удаление повторов на стороне получателя уберёт лишнюю копию, но не восстановит пропущенную запись. Поэтому число ответов без ошибок недостаточно для приёмки выгрузки.
Даже на неизменном наборе требуется определённый порядок. Сортировка только по времени изменения не задаёт однозначную последовательность, если несколько товаров получили одинаковое время. Нужен дополнительный устойчивый признак для разрешения равенства. Документация PostgreSQL отдельно предупреждает: ограничение выборки без уникального порядка может возвращать непредсказуемые части результата. Это свойство запроса, а не гарантия конкретного API.
Курсор закрепляет границу продолжения
В простом учебном варианте данные идут по возрастающему неизменяемому идентификатору. После первой страницы запоминают последний прочитанный ключ 30 и просят следующие записи после него. Удаление 20 уже не сдвигает эту границу: следующий набор будет 40, 50, 60. Модель решает показанную проблему смещения.

Реальный API может выдавать непрозрачный токен продолжения. Его следует возвращать сервису в предусмотренном поле, сохраняя фильтры и направление обхода. Не пытайтесь извлечь из токена номер страницы или самостоятельно увеличить его. Формат, срок жизни и поведение после изменения данных определяет сервис.
У разных интерфейсов отличаются и признаки конца. Это может быть отсутствие следующего токена или отдельный флаг. Число записей меньше запрошенного лимита само по себе не всегда означает конец. В рекомендациях Google по проектированию API допустима неполная, в том числе пустая страница с продолжением. Правило остановки берут из контракта конкретного метода.
Курсор не замораживает каталог
Теперь после чтения ключа 30 появится новый товар с меньшим ключом либо изменится уже прочитанная карточка. Обычный проход «только после последнего ключа» эту перемену не увидит. Если сортировка построена по изменяемому полю, запись может пересечь границу в обе стороны. Поэтому обещание «курсор исключает любые пропуски» слишком широкое.
Для задачи «полный каталог на один момент» нужен механизм, который действительно фиксирует состояние: например, подготовленный сервером экспорт или поддерживаемый снимок с правилами хранения. Передавать одну и ту же дату в фильтре недостаточно, если записи всё равно меняются между запросами и сервис не обещает согласованность.
Для задачи «довести получателя до актуального состояния» допустима другая модель: первоначальная загрузка и последующее применение изменений с согласованной точки. Здесь отдельно решают, как передаются удаления, как долго хранится история и что делать при пропуске её срока. Полный снимок и поток изменений требуют разных критериев готовности.
После обрыва возобновляют подтверждённый участок
Сохранять позицию нужно вместе с пониманием, какие записи получатель уже устойчиво обработал. Если запомнить следующий курсор до записи текущей страницы, сбой оставит необработанные товары позади контрольной точки. Если сохранить курсор позднее, возможен повтор страницы; обработчик должен распознавать уже принятую версию объекта.
Один токен в журнале также не описывает всю выборку. Для возобновления нужны согласованные фильтры, сортировка, версия формата и контекст доступа. Если токен истёк, нельзя угадывать новый и продолжать с приблизительного места. Контракт должен предусматривать повторный снимок либо другой проверяемый способ восстановления.

В тестовой копии полезно пройти небольшой известный набор и между страницами удалить раннюю запись, добавить новую, изменить поле сортировки и прервать сохранение страницы. Сверяют идентификаторы и версии, а не только итоговое количество: один пропуск и один повтор могут дать красивую, но неверную сумму.
В этой статье проверен учебный пример со списком, а не работа конкретного модуля 1С-Битрикс. При выборе обмена запросите у разработчика точный ответ: какое состояние выгружается, по какому признаку продолжается обход и как подтверждается его полнота после сбоя. По этим трём условиям можно принимать результат. Последняя полученная страница сама по себе такого подтверждения не даёт.
The export read the first 100 items, then the next 100. There are no API errors, yet 1 item was skipped in the result. A record was removed between requests from the start of the list: everything after it shifted. The "skip first 100" transition now refers to a different boundary.
For exporting a changing catalog, you must determine two things in advance: how to continue reading and what state counts as a complete result. A stable cursor helps with the first. For the second, a consistent snapshot or separate change verification may be required. The mere presence of a "next page" button does not solve both tasks.
Skipping occurs even with correct sorting
Take a sample list of identifiers 10, 20, 30, 40, 50, 60 with a page size of three. The first response contains 10, 20, 30. Then record 20 is deleted. The list becomes 10, 30, 40, 50, 60. A request with an offset of three skips the first three current records and returns 50, 60. Identifier 40 did not appear in any response.
If a record is inserted at the beginning before the second page, the opposite effect may occur: an already read product may end up shifted. Removing duplicates on the receiver side will eliminate the extra copy but will not restore the skipped record. Therefore, the number of error-free responses is insufficient for acceptance testing of the export.
Even with an unchanged dataset, a specific order is required. Sorting solely by modification time does not establish a unique sequence if multiple products share the same timestamp. An additional stable attribute is needed to resolve ties. PostgreSQL documentation explicitly warns that limiting a query without a unique order may return unpredictable portions of the result. This is a property of the query, not a guarantee of a specific API.
A cursor locks the continuation boundary
In a simple educational variant, data is ordered by an increasing immutable identifier. After the first page, the last read key 30 is remembered, and subsequent records are requested after that key. Removing 20 no longer shifts this boundary: the next set will be 40, 50, 60. This model resolves the demonstrated shifting problem.

A real API may return an opaque continuation token. It should be returned to the service in the designated field, preserving filters and traversal direction. Do not attempt to extract a page number from the token or increment it manually. The format, lifetime, and behavior after data changes are defined by the service.
Different interfaces also differ in their end-of-stream indicators. This may be the absence of a next token or a separate flag. A record count lower than the requested limit does not always signify the end. Google's API design guidelines allow for incomplete, including empty, pages with continuation. The stop condition is derived from the specific method's contract.
A cursor does not freeze the catalog
Now, after reading the key 30, a new product with a smaller key may appear, or an already read card may change. A standard pass "only after the last key" will not detect this change. If sorting is based on a mutable field, a record may cross the boundary in either direction. Therefore, the promise that "cursor-based pagination excludes any skips" is too broad.
For the task of "full catalog at a single point in time," a mechanism that truly captures state is required: for example, a server-prepared export or a supported snapshot with retention rules. Passing the same date in a filter is insufficient if records still change between requests and the service does not promise consistency.
For the task of bringing the recipient to the current state, an alternative model is acceptable: an initial load followed by applying changes from a consistent point. Here, decisions are made separately regarding how deletions are handled, how long history is retained, and what to do when the retention period expires. A full snapshot and a stream of changes require different readiness criteria.
After a break, resume the confirmed segment
The position must be saved along with an understanding of which records the recipient has already reliably processed. If the next cursor is saved before the current page's records, a failure will leave unprocessed items behind the checkpoint. If the cursor is saved later, a page may be repeated; the handler must recognize the already accepted version of the object.
A single token in the log does not describe the entire selection. Resumption requires consistent filters, sorting, format version, and access context. If the token expires, one cannot guess a new one and continue from an approximate location. The contract must provide for a full snapshot or another verifiable recovery method.

In a test copy, it is useful to process a small known set and, between pages, delete an early record, add a new one, change a sort field, and interrupt page saving. Identifiers and versions are compared, not just the final count: one omission and one repetition can yield a beautiful but incorrect total.
This article examines a sample list implementation, not a specific 1C-Bitrix module. When requesting an exchange, ask the developer for a precise answer: what state is exported, what criterion determines the continuation of the traversal, and how completeness is confirmed after a failure. These three conditions allow you to validate the result. The last page received alone does not provide such confirmation.





Обсуждение 0
Делись опытом и задавай вопросы. Комментарии без ссылок появляются после проверки редактором.
Пока никто не написал. Начни обсуждение.