← Все проекты уровня 9
Уровень 9 · Фоновые заданияВариант B

Импорт данных

Долгий CSV-импорт с построчными ошибками, ограниченным файлом и документированной политикой частичного результата.

Техническое задание

Модель данных и назначение

Реализуйте асинхронный CSV-импорт на Go с PostgreSQL, миграциями и сырым SQL без ORM. POST /api/imports принимает multipart/form-data с одним файлом размером не более 50 MiB; превышение обнаруживается потоково и возвращает 413 до принятия job. Поддерживается CSV UTF-8 с обязательными колонками source_id,name,email,amount_minor; порядок колонок не имеет значения, повтор заголовка или отсутствие/неизвестная колонка дают 400 до создания задания. Импортируемая запись содержит уникальный source_id, name, нормализованный email и целое amount_minor от 1 до 1,000,000,000,000 копеек. Строки с пустыми обязательными значениями, некорректным integer или повтором source_id внутри файла регистрируются как row errors с номером строки, кодом и сообщением без полного содержимого строки.

API, проверки и эксплуатация

Документированная политика — частичный commit по строкам: валидные строки записываются, невалидные пропускаются. Содержимое исходного файла сохраняется в PostgreSQL до ответа 202. Все успешные строки одной job применяются в одной транзакции после разбора; при инфраструктурной ошибке транзакция откатывается, задание остаётся доступным для восстановления после перезапуска и может быть повторено. source_id длиной 1–128 символов уникален во всей таблице и исключает дубли. Повторная обработка той же job не дублирует записи; новый файл с тем же source_id возвращает строку как skipped_duplicate, не заменяя ранее импортированную запись. Job содержит id, state pending/running/completed/failed/cancelled, progress целым числом 0–100, counters total/created/skipped/error, номер попытки, timestamps и ссылку на результат ошибок.

POST отвечает 202 с id, GET /api/imports/{id} отдаёт состояние и счётчики; GET /api/imports/{id}/errors возвращает строки ошибок с limit/cursor и сортировкой по row_number. POST /api/imports/{id}/cancel прекращает чтение между строками. До фиксации транзакции все изменения откатываются и job становится cancelled без внесённых строк; если commit уже выполнен, job остаётся completed, а отмена возвращает 409. Таким образом cancelled никогда не содержит применённые строки. После рестарта pending и прерванные running jobs подхватываются из БД, захват очереди атомарен, число исполнителей настраивается от 1 до 8, по умолчанию 2. Максимум 3 попытки: паузы 10 и 30 секунд; каждая попытка длится не более 300 секунд. Прогресс — целое 0–100. Временные сбои повторяются, ошибки данных нет; после третьей ошибки job становится failed. Результат ошибок и завершённое состояние сохраняются при рестарте. Размер CSV и длина полей имеют пределы, заголовки и кавычки разбираются стандартным CSV-парсером. Ошибки API имеют единый формат и статусы 400/404/409/413. Контексты SQL имеют тайм-аут и отмену. Логи не содержат строк CSV, email или имя; данные не выводятся в сообщения ошибок. Индексы обеспечивают уникальность и просмотр заданий/ошибок без N+1.

Структура программы

Код разделён на небольшие internal-пакеты: internal/httpapi принимает файл и формирует DTO, internal/imports проверяет CSV и частичную политику, internal/jobs хранит состояние, internal/workers читает файл и выполняет транзакцию. main.go связывает сервер и исполнителей; отмена передаётся через ctx, функции не вызывают os.Exit.

Критерии готовности

Ожидаемый результат

Поставка

  • Запускаемый Go REST API с PostgreSQL-миграциями принимает CSV до 50 MiB и применяет валидные строки по документированной политике частичного импорта.
  • README перечисляет четыре заголовка CSV, диапазон amount_minor, коды ошибок и состояние транзакции при отмене.
  • Офлайн-тесты используют временные CSV-файлы, локальную базу и управляемую имитацию сбоя транзакции.

Приёмка

  • Файл размером 50 MiB принимается, а файл больше лимита получает 413 без создания job.
  • Отсутствующий source_id или значение amount_minor вне 1..1e12 отклоняет заголовок/строку с точным номером и кодом.
  • Повтор source_id не создаёт дубль; повторная обработка одной job не меняет число созданных строк.
  • Отмена до commit откатывает все вставки и даёт cancelled; после commit job остаётся completed, cancel возвращает 409.
  • Число исполнителей настраивается от 1 до 8, по умолчанию 2; выполняется не более трёх попыток с паузами 10 и 30 секунд и временем выполнения до 300 секунд. Результат переживает перезапуск.
  • README показывает зависимости; разбор CSV и row-политика тестируются без HTTP/БД, транзакции импорта — отдельными SQL-сценариями.