Виды задач и разбиение
Создайте платформу вычислений на Go. Поддерживается одна детерминированная операция: статистика CSV-набора (количество строк, сумма и минимум/максимум числовой колонки). Запрос содержит CSV до 50 MiB, колонку, delimiter и 1–64 chunks. CSV разбирается с поддержкой quoted multiline полей, chunks считаются по логическим CSV-записям, а не физическим строкам. Sum — checked int64; overflow завершает job как failed, пустой набор даёт count=0, sum=0, min=null, max=null. Части охватывают последовательные диапазоны CSV-записей; их ID строится из job ID и номера диапазона. Пустые числа пропускаются, нечисловое значение завершает chunk как invalid_input с номером строки. Сводка части содержит count, sum, min, max и digest; при объединении проверяются порядок частей и одна принятая сводка на диапазон.
REST и состояние
Локальный seed создаёт compute_user с токеном в Authorization header; job доступны только создавшему их пользователю и локальному оператору. POST /api/v1/jobs создаёт задачу по idempotency key; одинаковые payload и key возвращают прежний job, изменившийся файл или параметры дают 409. GET /jobs/{id} возвращает queued/running/merging/succeeded/failed/cancelled, общий progress, количество завершённых chunks и итог. GET /jobs/{id}/chunks/{chunk_id} показывает attempt и state. POST /jobs/{id}/cancel останавливает незапущенные chunks и направляет отмену работающим; POST /jobs/{id}/retry повторяет recoverable chunks только для failed job. Один job имеет revision и неизменяемый input digest. Итог доступен лишь после успешного объединения всех частей; частичный результат отмечен отдельно и не считается success. При отмене во время merge первой фиксируется либо отмена без результата, либо объединение со статусом succeeded.
Исполнители и восстановление
PostgreSQL хранит задания, фрагменты, попытки, права обработки, heartbeat, контрольные позиции, частичные и итоговый результаты, outbox и аудит. Координатор и два исполнителя запускаются в Docker Compose. Исполнитель берёт до двух фрагментов; общий предел — два на исполнителя, максимум 8. Право обработки действует 30 секунд с heartbeat каждые 10 секунд; по истечении повторяется только восстанавливаемый фрагмент. Невалидный вход не повторяется, временная ошибка чтения каталога допускает два повтора, гибель исполнителя — один. Фрагмент, результат и контрольная позиция фиксируются одной транзакцией с проверкой поколения. Очередь — максимум 256 фрагментов; новая задача, не помещающаяся целиком, получает 429 без частичной записи. Архив хранится отдельно; временные файлы до 100 MiB удаляются через сутки. SQL timeout 3 секунды; команды пользователя не запускаются.
Структура программы
Координатор и исполнители запускаются раздельно; ядро делит записи, считает и объединяет сводки, транспорт и SQL-хранилище остаются внешними границами. main.go только загружает конфигурацию, связывает зависимости и управляет запуском. Пакеты без циклов, общих utils и интерфейсов без потребителя. README показывает зависимости; тесты ядра без HTTP/БД, интеграционные отдельно.
Наблюдаемость и приёмка
Каждый API ответ содержит trace_id, связанный в журналах с job_id, chunk_id, attempt_id и worker_id. Метрики отображают очередь chunks, прогресс job, возраст права обработки, heartbeat, повторы, время merge и пиковую память. Compose запускает два исполнителя и координатор; тестовый набор содержит пустые значения, отрицательные числа, последние неполные chunk, malformed row и повторные запросы. Тесты завершают исполнителя после вычисления до фиксации результата, запускают второго исполнителя и проверяют защиту от устаревшего исполнителя; отдельно останавливают координатор до merge и после фиксации. Пиковая подача, превышающая 256 chunks, проверяет атомарное отклонение новой job. Критерии готовности: итоговая сумма и min/max совпадают с последовательным контрольным подсчётом, прогресс монотонен, каждое завершённое задание имеет одну принятую сводку на chunk, потерянное право обработки восстановлено максимум за 40 секунд, cancel завершён и не публикует частичный итог. Воспроизводимый отчёт обрабатывает 1 000 000 строк, фиксирует время, пиковую память, chunks и конфигурацию, а затем завершает исполнителя до фиксации chunk и показывает восстановление и связанные идентификаторы трассировки.