Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як віддавати великі обсяги даних з API потоково: NDJSON, стримінг JSON і SSE?

Звичайна відповідь Model::all()->toJson() будує весь JSON у пам'яті перед відправкою. Для експорту на сотні тисяч записів це гігабайти пам'яті й хвилини тиші, поки клієнт чекає першого байта.

Потокова відповідь відправляє дані частинами, щойно вони готові.

1. Потоковий JSON у Laravel:

return response()->streamJson([
    'orders' => Order::query()->with('items')->lazyById(500),
]);

streamJson серіалізує й відправляє елементи по одному, а lazyById читає з бази порціями - пам'ять не росте з кількістю записів. Клієнт отримує звичайний валідний JSON.

2. NDJSON (JSON Lines) - один JSON-об'єкт на рядок:

{"id":1,"total":"150.00"}
{"id":2,"total":"80.50"}
return response()->stream(function () {
    foreach (Order::query()->lazyById(500) as $order) {
        echo json_encode(OrderResource::make($order)->resolve()), "\n";
        flush();
    }
}, 200, ['Content-Type' => 'application/x-ndjson']);

Перевага - клієнт може обробляти записи, не дочекавшись кінця: читати рядок, розбирати, обробляти. Звичайний JSON-масив неможливо розібрати стандартним JSON.parse до кінця відповіді.

3. Server-Sent Events - для подій, що з'являються з часом (прогрес, сповіщення, відповідь LLM по токенах):

return response()->eventStream(function () {
    foreach ($this->generateAnswer() as $chunk) {
        yield new StreamedEvent(event: 'chunk', data: $chunk);
    }
});

Що враховувати в продакшені:

  • буферизація по дорозі: Nginx (proxy_buffering), PHP output buffering, стиснення, CDN можуть накопичувати дані й віддати все наприкінці. Для потокових маршрутів - X-Accel-Buffering: no чи окреме налаштування;
  • помилка посередині потоку: статус 200 уже відправлено. Клієнт має розпізнати обірвану відповідь (невалідний JSON, відсутній завершальний маркер), а сервер - логувати;
  • воркер зайнятий весь час відправки - для PHP-FPM довгі потоки дорогі; тривалі експорти краще генерувати чергою у файл і віддавати посилання;
  • тайм-аути проксі мають покривати тривалість передачі;
  • Content-Length невідомий - використовується chunked-передача (HTTP/1.1) чи кадри HTTP/2.

Альтернатива для великих експортів: асинхронна генерація файлу (CSV, NDJSON у сховищі S3/R2) і тимчасове підписане посилання на завантаження - сервер API не тримає з'єднання хвилинами.

Докладніше в документації: Laravel: потокові JSON-відповіді

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання