gptml
Сервис генерации на тысячи заявок в день. Написал воркеры, которые разбирают очередь круглосуточно, и нашёл, почему всё вставало: база в одном файле на 108 МБ.
- база начинает копиться в один файл, очистки нет
- первая правка скрипта
- второй воркер сдан
- третий воркер: работают все дорожки
- пакет из 14 пунктов
- диск 76%. Первое предупреждение
- ночью сервис лежит, подняли
- вечная загрузка: три причины сразу
- диск 85%. Второе предупреждение
- сайт упал, поднят в тот же день. Третье предупреждение
- выполненные заявки массово уходят в ошибочные
- 121 ошибка за день, текст ошибки пуст у всех
61 237 записей в одном JSON-файле, замер 1 сентября. Снаружи это выглядело как сбой воркера.
$ cat README
Платный сервис генерации картинок, видео и озвучки. Заявка встаёт в очередь на сайте, воркер её забирает, передаёт во внешний сервис и возвращает файл. Линия идёт круглосуточно, тысячи заявок в день, и при простое у владельцев сразу копятся жалобы клиентов.
- роль
- Воркеры, бэкенд и админка, выкладка на сервер, разбор аварий и ёмкости
- кто ещё
- Основу сайта сделал прошлый программист. Партнёры ставили задачи, описывали сценарии ошибок и тестировали под нагрузкой, большую часть багов нашли они
- когда
- май – сентябрь 2026
- стек
- Python, Next.js, TypeScript, pm2, cron, внешний API генераций
- сайт
- ai.gptml.ru
$ cat дорожки.conf
Три воркера, четыре дорожки, 36 сборок
Первый воркер довёл из чужого скрипта, второй и третий написал. 21 мая отключённая модель продолжала уходить в работу. Я показал, что отбор живёт на сервере, а не в воркере, и попросил доступ. С этого дня работаю со всей системой.
| дорожка | потоки | таймаут |
|---|---|---|
| фото | ▮▮ 2 | ██████████ 300 с |
| видео 1 | ▮▮ 2 | ████████████████████ 600 с |
| видео 2 | ▮ 1 | ████████████████████████ 720 с |
| апскейл | ▮▮ 2 | ████████████████████ 600 с |
PENDINGPROCESSINGSUCCESSERRORЗаявка больше не висит. Сбой закрывается понятным текстом, и воркер идёт дальше. У каждой модели свои сценарии отказа, партнёры описали их сами.
Ошибка генерации. Время ожидания сервера истекло. Попробуйте снова
Виноват не тот, на кого думают
Шесть жалоб за четыре месяца. Выберите команду, и терминал покажет, что было на самом деле.
31 августа – 1 сентября «воркер сломался»
- жалоба
- Выполненные заявки массово уходят в ошибочные. Результат отправлен, а в кабинете 20–30 минут висит генерация, пишет партнёр.
- след 1
- У всех 121 ошибочной заявки за день пустой текст ошибки. Внешние сервисы отказывают с текстом, значит, это таймаут на сайте.
- след 2
- Лог залит ошибкой блокировки отложенных результатов, хотя функция выключена. Механизм просто не успевает взять блокировку.
- след 3
- У сервиса генерации результат есть, до сервера он не дошёл. Воркер работал честно, сервер не принимал его «готово» вовремя.
- причина
- База генераций это один JSON-файл на 108 МБ и 61 237 записей, копится с 28 апреля без очистки. Каждое сохранение переписывает его целиком, и сервер замирает на секунды.
- итог
- Исправил в тот же день. Гарантию дал точную: от этой причины да, от внешних сюрпризов нет. Дальше написал уборщика.
13 сентября «логи чистятся через час»
- жалоба
- Пользователи массово пишут, что история генераций исчезает через несколько часов, у кого-то через час.
- проверил
- Сами за часы генерации не удаляются, это видно по логам сервера.
- причина
- Хранение 3 дня, при нехватке места уборщик срезает до 2. В субботу в 16:30 порог сработал, и разом ушло около 4,5 тыс. генераций. А 11 сентября кто-то вручную удалил около 16 ГБ.
- итог
- Система работала как задумано, но порог считался на меньший объём. Считали 9–14 ГБ в сутки, в пятницу набежал 21 ГБ.
21–23 июля «режим «оба» не работает»
- жалоба
- Партнёр пишет, что заявки не идут в воркеры и API вместе.
- проверил
- 21 июля прошёл всю цепочку на проде: переключение, 10 минут в режиме «оба», живая заявка. Вывод: режим просто не был включён, все 1 309 заявок ушли воркерам.
- поворот
- 23 июля партнёр пришёл снова и оказался прав. Первая проверка проблему не поймала, поймала нагрузка.
- причина
- Запись счётчика очереди перезаписывала выбранный режим обратно на «воркеры».
- итог
- Блокировка параллельных записей, 27 тестов, 80 параллельных распределений. Попробовать можно на следующем экране.
16–20 июля «вечная загрузка»
- жалоба
- Результат готов, а у пользователя крутится загрузка, помогает только очистка кеша. Потом перестаёт работать кнопка входа.
- причина
- Три вещи сразу. Страница входа кешировалась на год и ссылалась на уже удалённый JS-файл. В хранилище генераций шла гонка записей, 360 ошибок в логах. Истёкшая сессия оставляла бесконечный опрос.
- сделал
- Вход больше не кешируется, старый файл восстановлен, чтобы никому не чистить кеш. Запись стала последовательной и атомарной. Сессия ведёт на свежую страницу входа.
- итог
- Партнёр написал, что жалоб нет и стало гораздо лучше. Через день у двух человек проявилось снова, он сам назвал это нечастым сценарием.
4–5 июня «заявка закрылась сама»
- жалоба
- Заявка закрывается ошибкой на пустом месте.
- причина
- В тексте заявки стояло слово «ошибка», и воркер принимал его за настоящий отказ. Гипотезу подсказал партнёр.
- итог
- Исправлено. Теперь слово в запросе пользователя заявку не закрывает.
9 июля «мы же уехали со старой базы»
- жалоба
- Партнёры считают, что перенос базы закончен.
- проверил
- Делал пакет из 14 пунктов и увидел, что заявки всё ещё идут через старый внешний сервис.
- итог
- Перенос оказался выполнен наполовину. Нашёл и довёл.
$ режим --пример
Воркер, API, воркер, API
Заявки идут в воркеры, во внешний API или через одну, режим переключается в админке. Включите нагрузку, и увидите, что ломалось 23 июля: на спокойном проде этого не воспроизвести.
Режим: оба. Заявки чередуются.
Пример, настоящих заявок здесь нет. Настоящая починка: блокировка параллельных записей, 27 тестов, 80 параллельных распределений.
*/30 * * * *
Уборщик
Встроенная очистка удаляла результат и постер, а референс не трогала, поэтому при чистке всплывали файлы прошлого месяца. К 7 сентября заработал уборщик: раз в 30 минут, по понедельникам ещё и кеши.
- сносит файлы, на которые не ссылается ни одна запись, в трёх каталогах
- ждёт 6 часов: свежий файл мог ещё не попасть в базу. В первом прогоне так отложился 291 файл
- закрывает заявки в обработке старше суток, иначе они висят в кабинете вечно
- режет срок хранения 3 → 2 → 1 день при базе больше 55 МБ или диске выше 85%, пока диск не дойдёт до 75%
$ df -h /
Шесть письменных предупреждений
76%19 ГБ свободно, 31 ГБ генераций
85%порог
82%лог ошибок 361 МБ без ротации
- 1/6
Диск 76%. Советую внешнее хранилище.
- 2/6
Диск 85%.
- 3/6
После падения сайта предлагаю автоочистку и автоподъём.
- 4/6
В разборе аварии: база растёт без очистки, около 3 000 генераций в день.
- 5/6
Лог ошибок 361 МБ без ротации, диск 82%.
- 6/6
Предлагаю отдельное S3-хранилище вместо расширения памяти.
Что будет дальше
3 днярасчёт
Считали 9–14 ГБ файлов в сутки. Под этот объём и настроен порог, хранение 3 дня.
2 днязамер
В пятницу набежал 21 ГБ при диске 79 ГБ. Три дня истории не помещаются. После обрезки диск снова доходит до 85% меньше чем за сутки. Каждая обрезка перезапускает приложение на 2 секунды: 46 ошибок 502, данные и новые заявки не страдают.
1 деньпрогноз
Перестанут вмещаться и 2 дня, история сожмётся до 1 дня. Тогда «через несколько часов» станет почти правдой.
Партнёры решили расширять память. На следующий день я предложил отдельное хранилище, партнёр согласился присмотреться. Внедрения пока нет.
$ проверка --прод
Чужие заявки не трогал
Приёмка пакета из 14 пунктов шла на живом проде с деньгами чужих пользователей. Каждый пункт закрывал доказательством, а свои ошибки называл своими.
PASSСлужебный тег не уходит в запрос
Запросил у внешнего API параметры своей же задачи и показал чистый текст.
PASSЗаявка от воркера
В очереди стояли 3 заявки реальных пользователей, перехватывать их я не стал. Проверил чистую функцию сборки: те же байты, никакого риска для клиента.
PASSТеги в списке генераций
Смотрел отрисованную страницу. В сыром ответе теги у 9 из 10 заявок, на экране ни у одной.
PASSВложение и перегенерация
Сквозной прогон через настоящий эндпоинт в обоих режимах. Тестовую копию удалил за секунды, чтобы её не взял настоящий воркер.
FAILМой регресс, 11 июля
Тексты ошибок воркеров пропали. Написал прямо, что это мой фикс статусов, объяснил механизм и показал настоящей ошибкой на проде, что исправлено.
FAILГолоса озвучки
В прошлый раз проверил вживую один голос, и это была моя вина. Прогнал все 17 по живому API: 16 принято, один убран, в каталоге 36 проверенных.
Партнёр подтвердил по времени: с 16:02 заявки пошли чисто.
Воркеры работают круглосуточно и бьют железно, как молоток.
Партнёр, совладелец сервиса, август 2026. Пересказ из переписки. Партнёры и сами сильные тестировщики, качество воркеров это и их заслуга.
$ exit
Настоящая проблема часто не там, где её видит клиент. Мегабайты, записи и число ошибок 502 в отчёте работают лучше объяснений. А про масштаб лучше писать заранее, с цифрами: про внешнее хранилище я говорил ещё в июле.