Сценарий · обработка данных

Serverless-обработка CSV: парсинг, трансформация и загрузка больших файлов

Принимайте загрузку CSV по HTTP, сразу возвращайте 202 и обрабатывайте файл в фоновом пайплайне. Парсите миллионы строк, валидируйте и трансформируйте данные, делайте батчевый upsert с ключами идемпотентности и уведомляйте пользователей о завершении импорта — всё за пределами окна HTTP-таймаута.

Обновлено: 2026-06-28

Суть ответа

Serverless-обработка CSV: парсинг, трансформация и загрузка больших файлов. HTTP-обработчик проверяет ссылку на файл (URL или ключ в хранилище), сохраняет метаданные задачи и возвращает 202 с ID задачи. Шаг пайплайна читает CSV по частям, делает идемпотентный upsert по ID строки и записывает прогресс, чтобы можно было продолжить с места остановки.

Когда подходит

  • Импорт клиентских данных, загрузка товарных каталогов и массовые миграции пользователей
  • Регулярные CSV-выгрузки из внешних систем, которые нужно обрабатывать ночью или по триггеру

На что обратить внимание

  • Даже со стриминговым парсером HTTP-обработчик должен оставаться открытым, пока строки записываются. Замедление базы посреди импорта приводит к таймауту на шлюзе, клиент ретраит — и вы получаете дубли строк, если идемпотентность не была заложена с самого начала.
  • Управление памятью сложнее, чем кажется: «стриминговый» парсинг CSV, который копит проверенные строки в памяти для массовой вставки, всё равно загружает весь файл в RAM до записи.

Почему обработка CSV ломает синхронные HTTP-обработчики

  • Файлы на 100 000+ строк парсятся и вставляются 30–300 секунд — далеко за пределами таймаутов шлюза
  • Обработка внутри запроса держит HTTP-соединение открытым — клиенты отваливаются по таймауту или ретраят, создавая двойные импорты
  • Давление на память: загрузка CSV на 50 МБ в serverless-функцию одним проходом приводит к OOM на маленьких рантаймах

Импорт CSV — один из самых частых паттернов, которые не помещаются в синхронные HTTP-обработчики. Размер файла непредсказуем, время парсинга и валидации растёт линейно с числом строк, а любой сбой посреди импорта без идемпотентности оставляет частичные данные, из которых трудно восстановиться.

Почему стриминг CSV в HTTP-обработчике хрупок

Даже со стриминговым парсером HTTP-обработчик должен оставаться открытым, пока строки записываются. Замедление базы посреди импорта приводит к таймауту на шлюзе, клиент ретраит — и вы получаете дубли строк, если идемпотентность не была заложена с самого начала.

Управление памятью сложнее, чем кажется: «стриминговый» парсинг CSV, который копит проверенные строки в памяти для массовой вставки, всё равно загружает весь файл в RAM до записи.

HTTP принимает, пайплайн обрабатывает по частям

HTTP-обработчик проверяет ссылку на файл (URL или ключ в хранилище), сохраняет метаданные задачи и возвращает 202 с ID задачи. Шаг пайплайна читает CSV по частям, делает идемпотентный upsert по ID строки и записывает прогресс, чтобы можно было продолжить с места остановки.

Длинные CSV можно обрабатывать несколькими шагами пайплайна — разбить по диапазонам строк, разветвить параллельно, собрать в итоговом шаге. У каждого шага свой бюджет таймаута; сбой в одном шаге ретраит только его, без перезапуска с первой строки.

Паттерны serverless-обработки CSV

Обработка по частям с продолжением

Разбивайте большие CSV на шаги пайплайна по диапазонам строк. Сбой на строке 80 000 продолжается с этой контрольной точки, а не с первой строки.

Параллельный батчевый upsert

Разветвляйте несколько шагов пайплайна, чтобы обрабатывать диапазоны строк параллельно, — общее время импорта больших файлов сокращается.

Идемпотентный upsert строк

Используйте стабильный идентификатор строки (внешний ID или хэш строки) как ключ upsert. Повторный импорт того же файла даёт то же состояние базы.

Отслеживание прогресса и уведомление

HTTP-обработчик возвращает ID задачи. Клиент опрашивает эндпоинт статуса; финальный шаг пайплайна уведомляет вебхуком или письмом по завершении.

Поток serverless-импорта CSV

1

HTTP-обработчик принимает ссылку на файл, возвращает 202

Проверьте URL файла или ключ в хранилище. Сохраните запись задачи со status=pending. Запустите пайплайн со ссылкой на файл и ID задачи.

2

Шаг пайплайна читает и обрабатывает CSV

Скачайте и распарсите CSV в шаге пайплайна. Делайте upsert строк батчами по 500–1000 с ключами идемпотентности. Обновляйте прогресс в записи задачи.

3

Финальный шаг уведомляет

После последнего батча отметьте задачу завершённой и уведомите пользователя письмом, вебхуком или обновлением статуса.

Пайплайн импорта CSV по частям

HTTP-обработчик сразу возвращает 202 и запускает обработчик CSV как durable-инстанс через global.durable.startNew — обычную функцию, которая переживает рестарты и адресуется по id. instanceId делает старт идемпотентным; для очень больших файлов разветвите работу на несколько инстансов по диапазонам строк.

api/import-csv.mjs (HTTP-обработчик)
export async function handler(event) {
  const { fileUrl, importId } = JSON.parse(event.body || '{}');
  if (!fileUrl || !importId) return { statusCode: 400, body: JSON.stringify({ error: 'fileUrl and importId required' }) };
  await db.imports.create({ id: importId, status: 'pending', fileUrl });
  await global.durable.startNew('process-csv', undefined, { fileUrl, importId });
  return { statusCode: 202, body: JSON.stringify({ importId, status: 'pending' }) };
}
jobs/process-csv.mjs (durable-функция)
import { parse } from 'csv-parse/sync';

export async function handler(event) {
  const { fileUrl, importId } = event; // the input passed to startNew()
  const csvText = await fetch(fileUrl).then((r) => r.text());
  const rows = parse(csvText, { columns: true, skip_empty_lines: true });
  let inserted = 0;
  for (const batch of chunk(rows, 500)) {
    // Upsert by external_id — idempotent on retry
    await db.records.upsertBatch(batch.map((r) => ({ ...r, importId })));
    inserted += batch.length;
  }
  await db.imports.update(importId, { status: 'done', rowCount: rows.length });
  return { importId, rows: rows.length, inserted };
}

Используйте serverless-обработку CSV для

Когда это уместно

  • Импорт клиентских данных, загрузка товарных каталогов и массовые миграции пользователей
  • Регулярные CSV-выгрузки из внешних систем, которые нужно обрабатывать ночью или по триггеру

Когда лучше выбрать другое

  • Крошечные CSV до 1000 строк, которые обрабатываются быстрее 5 секунд, — оставьте их синхронными, отлаживать проще

Частые вопросы

Как обрабатывать ошибки валидации CSV?

Собирайте ошибки валидации по строкам и сохраняйте их в записи задачи импорта. По завершении возвращайте сводку (валидные строки, невалидные строки, список ошибок). Пусть клиент решает — принять частичный импорт или сначала исправить ошибки.

Как продолжить упавший импорт?

Храните смещение последней успешно обработанной строки в записи задачи. Перезапустите пайплайн с этим смещением — шаг пропустит уже обработанные строки. Для надёжности делайте upsert по внешнему ID.