Прямой ответ
Пакетный (batch) API выполняет несколько операций в одном HTTP-запросе. Он устраняет главную проблему массовых операций — накопление задержки на сетевых обменах: 500 запросов по 30 миллисекунд дают 15 секунд, а один пакетный запрос выполняется за доли секунды.
Запрос содержит массив операций:
POST /api/v1/orders/batch
Content-Type: application/json
{
"operations": [
{"key": "a1", "method": "PATCH", "id": 1001, "data": {"status": "shipped"}},
{"key": "a2", "method": "PATCH", "id": 1002, "data": {"status": "shipped"}},
{"key": "a3", "method": "DELETE", "id": 1003}
]
}
Ключевое отличие от обычного API — частичный отказ. Ответ содержит статус по каждому элементу, поскольку одни операции могут succeed, а другие — нет:
{
"results": [
{"key": "a1", "status": 200},
{"key": "a2", "status": 409, "error": "status_transition_forbidden"},
{"key": "a3", "status": 204}
]
}
Общий код ответа 207 (Multi-Status) отражает смешанный результат. Альтернатива — «всё или ничего» в одной транзакции, но она подходит не всем случаям: отмена 499 успешных операций из-за одной ошибки обычно нежелательна.
Ограничение размера обязательно. Без него пакет на 100 тысяч элементов приводит к исчерпанию памяти и таймауту:
MAX_BATCH_SIZE = 500
if len(request.operations) > MAX_BATCH_SIZE:
raise HTTPException(413, "batch too large")
Идемпотентность задаётся на уровне элемента, а не запроса. Ключ a1 позволяет безопасно повторить пакет после обрыва соединения: уже выполненные операции распознаются и не выполняются дважды; о механизме — в статье про идемпотентность API.
Поведение при ошибках и повторе
Пакетная операция требует явного описания поведения в пограничных случаях, иначе клиент вынужден угадывать, что произошло.
Обрыв соединения во время обработки. Сервер мог выполнить часть операций, а клиент не получил ответа. Повтор пакета безопасен только при наличии ключей идемпотентности: без них операции выполняются дважды.
Таймаут на клиенте. Клиент прекращает ожидание, но сервер продолжает обработку. Повторная отправка того же пакета приводит к дублированию, если идемпотентность не реализована.
Отклонение всего пакета. При ошибке формата пакет отклоняется целиком до начала обработки. Клиент получает понятную ошибку и может исправить запрос, не разбираясь, что уже применилось.
Слишком большой пакет. Ответ 413 с указанием предельного размера позволяет клиенту разделить пакет, а не подбирать размер перебором.
Частичное ограничение частоты. Если пакет превышает оставшуюся квоту, отклоняется весь пакет с указанием доступного объёма. Частичное выполнение усложняет повтор.
| Ситуация | Ожидаемое поведение |
|---|---|
| Обрыв соединения | Повтор безопасен при ключах идемпотентности |
| Ошибка формата | Отклонение всего пакета до обработки |
| Превышение размера | 413 с указанием предела |
| Исчерпание квоты | Отклонение пакета целиком |
Практика: проектирование пакетного API
1. Ограничьте размер пакета. Значение выбирается исходя из времени обработки: пакет должен выполняться быстрее таймаута клиента; о бюджете ожидания — в статье про таймаут-бюджет.
2. Возвращайте статус по каждому элементу. Клиент должен знать, какие операции выполнены, чтобы повторить только неудачные.
3. Задайте ключ идемпотентности для элемента. Повтор пакета после обрыва соединения не должен дублировать операции.
4. Выполняйте пакетно на уровне базы. Один запрос с WHERE id = ANY(...) вместо 500 отдельных; о проблеме цикла запросов — в статье про N+1.
5. Ограничивайте частоту по элементам, а не по запросам. Один пакет из 500 операций — это 500 единиц нагрузки; о подходах — в статье про rate limiting.
6. Для больших объёмов переходите на асинхронную обработку. Пакет свыше нескольких тысяч элементов принимается и возвращается идентификатор задачи, а результат запрашивается отдельно.
7. Фиксируйте частичный отказ в документации. Поведение при смешанном результате — часть контракта, а не деталь реализации; о контрактах — в статье про OpenAPI.
8. Проверяйте операции до выполнения. Ошибка формата в одном элементе должна приводить к отклонению всего пакета до начала обработки, иначе клиент получает частично применённый пакет, который сложно повторить.
9. Измеряйте время обработки пакета. Метрика длительности по размеру пакета помогает выбрать предельный размер и заранее увидеть деградацию до срабатывания таймаута клиента.
10. Сохраняйте порядок результатов. Ответ должен содержать результаты в том же порядке, в каком пришли операции, либо включать ключ элемента. Без этого клиент не может сопоставить ошибку с операцией.
Рекомендации:
- Не делайте пакетную операцию транзакционной без необходимости. Отказ одного элемента не должен отменять остальные, если бизнес-логика этого не требует.
- Валидируйте пакет до выполнения. Ошибка формата в одном элементе не должна приводить к частичному выполнению.
- Логируйте результат по элементам. Разбор инцидента требует знания, какие операции прошли, а какие нет.
- Измеряйте время обработки пакета. Метрика помогает выбрать предельный размер и обнаружить деградацию.
- Предусмотрите повтор неудачных элементов. Клиент повторяет только операции с ошибкой, поэтому ответ должен содержать достаточно данных для повторной отправки.
Практический сценарий: синхронизация 20 тысяч записей вместо 20 тысяч запросов
Контекст: внешний клиент синхронизировал статусы заказов циклом отдельных запросов. Синхронизация занимала 12 минут и регулярно прерывалась по таймауту.
- Диагностика. Замер показал, что 95 процентов времени уходит на сетевые обмены: обработка одного запроса занимала 4 миллисекунды, а обмен — 32. Ограничение частоты срабатывало, и клиент добавлял паузы, увеличивая общее время.
- Причина. Пакетный интерфейс отсутствовал, поэтому клиент был вынужден выполнять операцию поэлементно, а ограничение частоты считало каждый запрос.
- Действия. (а) Добавили пакетный эндпоинт с предельным размером 500 операций; (б) ответ содержит статус по каждому элементу с ключом идемпотентности; (в) обработку реализовали одним запросом с
WHERE id = ANY(...)и последующим групповым обновлением; (г) ограничение частоты перевели на учёт элементов, чтобы пакет из 500 операций расходовал 500 единиц квоты; (д) для объёмов свыше 10 тысяч записей добавили асинхронный режим с идентификатором задачи. - Результат. Синхронизация 20 тысяч записей сократилась с 12 минут до 40 секунд, а прерывания по таймауту прекратились благодаря размеру пакета, укладывающемуся в бюджет ожидания.
Вывод: пакетный API устраняет накладные расходы сетевых обменов, но требует явного проектирования частичного отказа, ограничения размера и идемпотентности по элементам. Без этих элементов он превращается в источник неопределённости: клиент не знает, что выполнено.
FAQ
Какой код ответа возвращать при частичном отказе? 207 Multi-Status со статусом по каждому элементу либо 200 с телом, содержащим результаты. Код 500 при частично успешной операции вводит клиента в заблуждение.
Должна ли пакетная операция быть транзакцией? Только если бизнес-логика требует атомарности. В остальных случаях частичное выполнение полезнее полной отмены.
Как ограничивать частоту для пакетов? По числу элементов, а не по числу запросов. Иначе один большой пакет обходит ограничение.
Когда пакетный API заменить асинхронной задачей? Когда объём операций не укладывается в таймаут клиента. Приём пакета и возврат идентификатора задачи снимает ограничение по времени.
Источники: RFC 7807 (описание ошибок), Google Drive Batch API, JSON:API Atomic Operations — ссылки в шапке.