tazzz.ru · сводный отчёт · 26–28 августа 2026

Три дня против потолков

Сервер держал около 100 одновременных гостей и на четырёхстах отказывал; живой поиск упирался в 5 сессий, один мёртвый прокси и одну площадку без запасного канала. За три дня прошли четыре волны работ — две очереди веб-производительности, «карточка как проекция» и программа «ёмкость без закупок» — плюс сутки живых проверок с починкой пяти площадок. Теперь 1600 гостей обслуживаются за 130 мс поверх непрерывной записи, а контрольный прогон отдал 133 карточки из 133 — без единой ошибки.

боевой код 09ab5b04 47 коммитов · 121 файл · +20 432 строки 5 выкаток в прод (CI стоит — вручную) 46 находок ревью + 2 вскрыла выкатка + 4 живые прогоны 19 новых тестов-оракулов

Потолок одновременных гостей

≈100→1600–2400

Верхняя ступень пройдена уже под непрерывной записью проекций (500 пересборок/мин).

800 гостей, p95 визита

19,2 с→42 мс

После очереди II; под записью — 45 мс. Ошибок 0 на всех ступенях.

Первый экран пользователя

1078 мс→0,6 мс

Лента «популярного» собирается фоном раз в 3 минуты, ручка отдаёт готовый снимок.

Карточка в ленте

7–38 КБ→1,4 КБ

50 полей вместо 133, одна обложка вместо галереи; страница выдачи — 2 SQL-запроса вместо 32.

Экономия детальных фетчей

44–62%

Дельта-«Свежие»: деталь тянется только новым машинам, остальное отдаётся из базы по сегодняшним курсам.

Контрольный прогон 28.08

133/133

Пять площадок через пользовательское API, все карточки с «под ключ», ноль ошибок в шести контейнерах.

26.08

Очередь I

Индекс витрины, Socket.IO отдельным процессом, микрокэш nginx, настройка Postgres. Потолок ≈100 → ≈500 гостей.

27.08 утро

Очередь II + справочник

Снимки вместо сборки на запрос, ленточная карточка. 800 гостей = 42 мс. Плюс 23 файла архитектурного справочника.

27.08 день

Очередь III: Ф1 + Э3

Спецификация → код → два ревью → выкатка → стресс: 1600 гостей OK поверх записи. Карточка стала проекцией.

27.08 вечер

Ёмкость без закупок

Аудит → спека → W0–W6 → четыре прохода ревью → Ф0 и выкатка на бой. Дельта заработала в первый же вечер.

28.08

Проверка боем

Семь итераций через пользовательское API: починены auto.ru, drom, che168, celery и перевод; один ложный след откачен.

01 · Ёмкость веба — очереди I и II

От отказа на четырёхстах — к 42 миллисекундам на восьмистах

Методика неизменна все три дня: виртуальный гость открывает главную (три запроса к API), ждёт 20 секунд и открывает снова; нагрузка подаётся с самого сервера прямо в backend, минуя nginx и его кэш. Критерий «держит» — p95 полного визита меньше 1,5 с и ноль ошибок.

Кривая ёмкости: p95 визита по ступеням нагрузки

Обе шкалы логарифмические. Четвёртая линия — отдельный, более жёсткий тест: те же гости поверх непрерывной записи проекций (дренаж 500 пересборок/мин).

до правок очередь I (26.08) очередь II (27.08) очередь III, под записью (27.08)
10 мс 100 мс 1 с 10 с SLA 1,5 с 100 200 400 800 1600 2400 одновременных гостей до правок · 100 гостей · p95 304 мс до правок · 200 гостей · p95 1509 мс до правок · 400 гостей · p95 24,6 с — отказ 24,6 с — отказ очередь I · 100 гостей · p95 190 мс очередь I · 200 гостей · p95 204 мс очередь I · 400 гостей · p95 539 мс очередь I · 800 гостей · p95 19,2 с — перегруз 19,2 с — перегруз очередь II · 100 гостей · p95 28 мс очередь II · 200 гостей · p95 29 мс очередь II · 400 гостей · p95 32 мс очередь II · 800 гостей · p95 42 мс — потолок не достигнут 42 мс под записью · 800 гостей · p95 45 мс под записью · 1600 гостей · p95 130 мс · ошибок 0 под записью · 2400 гостей · p95 3,05 с — перегруз по SLA, ошибок 0 130 мс 3,05 с перегруз, ошибок 0 p95 визита
На 800 гостях точки очередей II и III почти совпадают (42 против 45 мс): непрерывная запись проекций стоит вебу ~3 мс. На 2400 деградация без единого отказа; часть вины — генератор нагрузки на той же машине и gunicorn в конфигурации 1×4.
Те же числа таблицей: p95 полного визита
гостейдо правокочередь Iочередь IIочередь III, под записью
100304 мс190 мс28 мс—
2001509 мс204 мс29 мс—
40024 619 мс · отказ539 мс32 мс—
800—19 234 мс · перегруз42 мс45 мс
1600———130 мс · ошибок 0
2400———3050 мс · перегруз, ошибок 0

Очередь I — инфраструктура (26.08, восемь правок)

Диагноз подтвердился полностью: отказ наступал не от нехватки железа, а потому что весь веб-слой был заперт в одном процессе. Postgres при перегрузе простаивал на 5–10%.

ПравкаБылоСтало
Индекс витрины под фактическую сортировку693 мс70 мс (Index Only Scan 0,19 мс)
Socket.IO — отдельный сервис, HTTP — два воркера1 ядро2 ядра
Микрокэш публичных ручек в nginxнет30 с, мусорные параметры схлопнуты
Общий пул соединений с Redis2,35 мс0,53 мс на чтение снимка
Счётчик фонда — из снимка вместо COUNT44,6 мсиз снимка
Postgres: буферы, autovacuum горячих таблиц, advisory-лок на стартовый create_all512 МБ1 ГБ; статистика планировщика собрана (62 из 81 таблицы жили без неё)
Троттлинг подбора пароля — общий на процессы10/мин на воркер5/мин на машину, 429 вместо 503
Диск: снят кэш сборки Dockerзанято 81%57% (−19,9 ГБ)

Очередь II — точечные правки кода (27.08 утро)

Главная идея: ничего не собирать в момент запроса. Всё, что одинаково для всех, собирается фоном и лежит готовыми байтами.

Что мерилиБылоСталоЧем починено
«Популярное», первый экран, 40 карточек1078 мс · 17 SQL · 711 КБ0,6 мс · 0 SQL · 67 КБснимок в Redis, сборка ушла в beat-задачу
Персональные рекомендации≈1,1 с73 мс · 0 SQLранжирование того же снимка в памяти
Снимок витрины для первой отрисовки14,2 мс · 846 КБ0,4 мс · 61 КБготовые байты вместо двойной перекодировки
Запросов SQL на страницу выдачи322убран безусловный N+1 по raw_json; тест стережёт бюджет запросов
Карточка в ленте7,0–38,3 КБ · 133 поля1,4 КБ · 50 полейкомпактная форма: одна обложка вместо галереи
Визит гостя, работа Python79 мс14 мссумма правок; через nginx большинство запросов до Python не доходит
Показы лотовв GET-ручкахотдельный эндпоинтпоказ считается по факту отрисовки — честнее и не теряется в кэше

02 · Очередь III — карточка как проекция

Выдача перестала собираться на лету

Каждая карточка исторически собиралась в момент запроса из тяжёлой строки parsed_cars (143 колонки, TOAST-поля по сотням килобайт). Очередь III переворачивает модель: карточка — это материализованная проекция, которую пересобирает единственный писатель при каждом изменении машины, а чтение — это отдача готового.

Как устроено

  • Таблицы card_projections (две формы: лента 1,5 КБ и полная 10,5 КБ) и card_rebuild_outbox — очередь пересборки прямо в PostgreSQL, без нового брокера.
  • Канон сборки — боевые сериализаторы serialize_car/to_feed_card: проекция байт-в-байт равна сегодняшнему ответу API. Паритет стережёт оракул по всем 8 площадкам.
  • Два контура инвалидации: синхронные писатели собирают проекцию в той же транзакции, асинхронные (фото, переводы, мощность, город) кладут строку в outbox — beat-дренаж раз в минуту пересобирает. Недельный reconcile — страховка от пропущенного писателя.
  • Версионирование src_updated_at + условный UPSERT: медленная пересборка не может затереть более свежую.
  • Отказоустойчивость: SAVEPOINT вокруг каждой сборки (ошибка БД не рвёт транзакцию писателя), сеть — только вне транзакций, dead-letter после 10 попыток, бюджет дренажа 45 с.

Диета конфигураций (Э3)

У DongCheDi в properties_json хранился сырец API: 286–303 КБ на строку, где карточке нужны три ключа. Белый список читаемого множества (проверен полным grep потребителей и снапшот-оракулом «до/после байт-в-байт») сжимает строку в 13,5 раза. Новые парсы уже пишут диету; пережатие истории + lz4 + VACUUM FULL ждут окна владельца: таблица 4460 МБ → прогноз 0,7–0,9 ГБ, и база целиком помещается в кэш ОС.

Попутная находка: lz4 сам по себе цели не берёт — на этих JSON он даёт 3,3×, столько же, сколько текущий pglz. Выигрыш только в связке с диетой.

Что показал стресс записи

Конвейер сохранения прогнан реплеем 150 боевых payload-ов на стенде; для справки — пик прода за 14 дней всего 42 сейва/мин.

Фаза (3 писателя параллельно)rpsp50p95Ошибок
insert, проекции выключены21412,7 мс19,9 мс0
insert, проекции включены14618,3 мс35,1 мс0
update, проекции включены21213,3 мс21,3 мс0
update + конкурентный дренаж outbox17216,6 мс27,2 мс0

Цена синхронной сборки — +6–8 мс на сейв (~+6% к боевому базлайну), запас над пиком кампаний ≥200×. В «грязных» прогонах дренаж успел сделать 7500–10 500 пересборок против сейвов тех же машин: 0 дедлоков, консистентность 150/150. Под гостевым стрессом дренаж держал темп 500 сборок за 5,5–8,7 с на прогон, outbox 5000 → 0 за 10 минут.

Выкачено в прод 27.08 ~13:45 UTC. Миграция прогнана до пересоздания контейнеров одноразовым контейнером — окно «код без таблиц» схлопнуто в ноль. Смоук на бою зелёный: sync-контур собрал проекцию в той же транзакции реального перепарса, async-контур — через outbox; ноль трейсбеков, сайт отвечал за 47 мс.
Находка, поменявшая план: fan-out деталей (эпик Э2), который спецификация предлагала «включить», на проде уже включён через env — спека писалась от дефолтов кода. Потолок держит единственная ручка MAX_CONCURRENT_SEARCHES. Первый реальный ограничитель ёмкости — не код, а прокси-пул: из этого выросла вся следующая программа.

Из пяти эпиков очереди III два в проде (Э1 Ф1, Э3 для новых парсов), по трём собраны данные и решения за владельцем: SSD вместо HDD (диск — QEMU HARDDISK, 102 МБ/с), окно пережатия истории, вынос соседнего проекта horeca (~450 МБ RAM, данных меньше 200 МБ — переезд на минуты).

03 · Программа «ёмкость без закупок» — W0–W6

Потолок 240 поисков в час — и путь к 600 без покупок

Аудит 27.08 (все пункты проверены живьём) показал: комфортная ёмкость ~150 поисков/час, жёсткий потолок ~240/час — и упирается он не в железо. Уникальных платных IP всего пять, из них один мёртв (а система этого не видела — счётчики были нулями); che168 ходил напрямую с IP сервера — единая точка отказа; детальный слот на 50–65% занят внутренней работой: записью в БД, LLM-расчётом мощности, лишними запросами. При этом 39–54% машин живого поиска уже лежали в базе — и парсились заново.

Ответ — семь потоков, все против внутренних потерь: W0 однострочники (мёртвый прокси выключен, che168 переведён на 3 живых маршрута, потолок сессий 5→8, wal_compression); W1 асинхронный коммит + дифф-апсерт (повторный сейв не переписывает нетронутые TOAST-колонки и индексы); W2 LLM-расчёт мощности вынесен из детального слота в фоновое дообогащение; W3 диета запросов детали encar (осмотр — параллельно карточке, DIRECT только на 407); W4 флот прокси — реестр здоровья, AIMD-темп, маршрутный предохранитель; W5 дельта-«Свежие» — деталь только новым машинам, знакомым пересчитывается «под ключ» по сегодняшним курсам; W6 канарейка threads-пула воркера деталей.

Дельта-«Свежие»: сколько детальных фетчей уходит

SQL по боевым сессиям за 60 дней. Контур — доля машин поиска, уже лежащих в базе; заливка — чистая экономия фетчей с учётом порога ревалидации 14 дней.

чистая экономия детальных фетчей машин уже в БД
dongchedi dongchedi · уже в БД: 68% машин живых поисков 68% dongchedi · чистая экономия фетчей ≈44% (68% × 65% в пороге 14 дней) ≈44% encar encar · уже в БД: 80% машин живых поисков 80% encar · чистая экономия фетчей ≈62% (80% × 77% в пороге 14 дней) ≈62% che168 che168 · уже в БД: 46% машин живых поисков 46% che168 · чистая экономия фетчей ≈46% (все попадания моложе порога) ≈46% 0% 50% 100%
У che168 все попадания моложе порога (медиана возраста — 0 дней), поэтому контур и заливка совпадают. Медиана dongchedi — 13,5 дня, вплотную к порогу 14: «сделать посвежее» обрушило бы его экономию. Гейт приёмки судит по падению детальных задач на сессию, а не по абсолютной доле — она меряет состав поисков, не механизм.

Замер, который поменял представление о «медленной записи»

Спецификация выводила W1 из посылки «сейв 1,8–4,8 с, потому что HDD и fsync». Живой замер посылку опроверг: коммит стоит 2,3 мс — это 0,2% сейва. Настоящая причина оказалась в другом: --max-memory-per-child=150 МБ добивает процесс-ребёнок практически после каждой задачи (87 из 95 процессов прожили ровно одну), и каждый сейв новой машины заново платит ~1,1 с за холодный справочник каталога.

Из чего состоит медиана сейва — 1425 мс

холодный справочник каталога · 1079 мс — процесс умирает после каждой задачи и греет кэш заново холодный каталог — 1079 мс прочий холодный старт и накладные · ≈330 мс прочее ≈330 мс сама работа: upsert 7,6 + проекция 8,8 + кузов 0,8 · 15–17 мс сама работа — 16 мс Тот же прогон под нагрузкой (LA 7,4; воркер 333% CPU) — те же 15,4 мс: конкуренция за CPU ни при чём. Коммит — 2,3 мс (0,2%): посылка «виноват fsync на HDD» не подтвердилась.
Следствия: от W1 не ждать ускорения сейва (его дифф-апсерт всё равно полезен — меньше WAL, TOAST и индексов), ценность W6 выше, чем считала спека, а самый дешёвый рычаг — поднять лимит памяти ребёнка, чтобы кэши амортизировались. Прогрев каталога перенесён в старт процесса.

Как включали

Ф0 выполнен на бою 27.08 с явного разрешения владельца: мёртвый прокси выключен, che168 получил три живых маршрута вставкой в proxies (креды не покидали сервер), потолок сессий 8, wal_compression включён без рестарта. Смоук сразу: che168 — 24 карточки с «под ключ» (раньше площадка зависела от прямого канала), два поиска одновременно без очереди.

Код W1–W6 выкачен вечером 27.08. Проверка живыми запросами в тот же вечер: encar 50/50 карточек с ценой, che168 23/24 — из них 23 отданы дельтой без детального фетча, dongchedi 19/20; ошибок 0, пауз площадок 0. Счётчики здоровья прокси впервые перестали быть нулями и сразу показали правду о маршрутах.

Выкатка вскрыла два дефекта, которых не нашли ни тесты, ни пять проходов ревью: прогрев каталога в worker_process_init при 24 одновременно стартующих детях выводил их за таймаут Celery — пул убивал детей по кругу (убран; правильное лечение — прогрев в родителе до форка); и минутная проба флота стучалась в www.che168.com, который через прокси не отвечает, — каждую минуту метила три живых маршрута мёртвыми (теперь строит подписанный URL тем же билдером, что и скрапер).

04 · Транспорты площадок — 28.08

Семь итераций живых проверок — и пять площадок в строю

Весь день 28.08 — цикл «прогон через пользовательское API → разбор → починка → прогон»: токены не покидали сервер, запросы шли изнутри контейнера. Итог на боевом коде 09ab5b04:

Контрольный прогон, живой поиск
ПлощадкаКарточекС «под ключ»Примечание
encar50/5050426 машин за два часа — ни одного корейского иероглифа в карточках
che16824/2424держится на дельте; сессию мерить не короче 7 минут
dongchedi20/20201 incomplete — настоящий пробел источника
drom20/20202 incomplete — в объявлении нет топлива/коробки
auto.ru19/1919первый успешный живой парс с 19.08
итого133/133133ошибок в шести контейнерах — ноль
Открытый риск: три физических IP делятся между che168, dongchedi и encar. Залп из пяти площадок сажает именно che168 (счётчики флота: успех ~4% при конкуренции) — площадку спасает дельта. Это первое место, где деньги на прокси дали бы прямой прирост.

05 · Как это делалось — ревью и проверка боем

Каждое утверждение — замером

Рабочий цикл каждой волны: спецификация → код → многопроходное ревью (включая multi-agent на максимальной строгости) → тесты-оракулы → выкатка → смоук живым API → стресс. Урок очереди I, задавший тон: три «очевидных улучшения» оказались ухудшениями — поймали их только измерением.

Где ловились дефекты

найдено ревью до выкатки вскрыто боем
ревью очереди I очередь I · 11 замечаний, из них две тихие поломки в самих исправлениях 11 очередь III, глубокое очередь III · глубокое multi-agent ревью: 8 подтверждённых дефектов 8 очередь III, финальное очередь III · финальный проход на максимальной строгости: ещё 7, включая две гонки 7 W1–W6, четыре прохода программа ёмкости · четыре прохода ревью: 20 находок, из них две порчи данных и одна потеря карточек 20 вскрыла выкатка 27.08 выкатка · 2 дефекта, которых не нашли ни тесты, ни пять проходов ревью 2 живые прогоны 28.08 семь итераций через пользовательское API · 4 починки + 1 откат ложного следа 5 0 10 20 находок
Ревью до выкатки сняло 46 дефектов — среди них две порчи данных, потеря карточек из выдачи и класс «SELECT через ORM держит транзакцию сквозь сетевые паузы, а прод рвёт её по таймауту». Но два дефекта дожили до боя, а четыре проявились только на живом трафике: ревью не заменяет прогона, прогон — ревью.

Самые дорогие находки: дельта-пересчёт сохранялся фронтовым payload-ом и записал бы русские значения в нативные колонки (порча данных); дельта-карточка эмитилась через канал, который глушат фильтры, — машина исчезала из выдачи; encar-скрапер делил один curl-хендл между потоками — причём, как выяснилось, делал это ещё до программы; «под ключ» дельта-карточки считался по курсам двухнедельной давности и соседствовал бы в одной выдаче со свежепересчитанными.

Под всё это написано 19 новых тестов-оракулов (паритет проекции по 8 площадкам, карта писателей, четыре ветки дельты, AIMD флота, диета encar…) — только по программе ёмкости 176 проверок; регрессы гонялись и на sqlite, и на настоящем PostgreSQL 16.

06 · От чего отказались

Решения, которые не выжили — и почему

Переключатели потоков W1–W6 решение владельца

Спецификация требовала флаг на каждый поток и фазовое включение. Владелец отказался держать в коде две версии одного и того же — все шесть выключателей и старые ветки удалены, остался один путь. Цена: выкатка сразу меняет поведение по всем потокам, откат — только откатом кода. Попутно вскрылось, что «случайное перемешивание прокси», которое сносили за ненадобностью, было защитой от очереди сессий к первому адресу — вернули как разрыв ничьей.

Прямой канал che168 последней ступенью откачено после боя

Выглядело спасением: HTTP 200 за 0,7 с. Оказалось, эти 29 181 байт — страница JS-челленджа, а не JSON, и ровно столько же отдают «живые» маршруты. Урок: у che168 о живости нельзя судить по коду и размеру ответа — только по тому, распарсился ли JSON.

Прогрев каталога в worker_process_init убрано за 2 минуты

24 одновременно стартующих ребёнка выходили за таймаут «UP» — пул убивал их по кругу, детали вставали. Правильное лечение — прогрев в родителе до форка (copy-on-write); предупреждение оставлено в коде на месте удалённого блока.

md5-гард raw_json из спецификации W1 заменено лучшим

Вместо сравнения хэша одной колонки — дифф-апсерт всех: не пишется ни одна колонка, чьё значение не изменилось. Сравнение бесплатное (строка уже загружена), а выигрыш шире — TOAST и 20 индексов не трогаются при повторных сейвах.

«Эмитить карточку без „под ключ“ через 60 с» из спеки W2 противоречило инварианту

Та же спецификация инвариантом И1 запрещает карточки без цены. На отказе дообогащения машина получает честный сегодняшний карантин; «ждёт дольше 60 с» осталось сигналом в колокольчик админки — с троттлингом, чтобы не заливать ленту событий.

Строгое сравнение цены в дельте не работало на dongchedi

Листинг отдаёт цену в 万 с шагом 100 ¥, деталь — точное число в фэнях: строгое равенство почти всегда ложно и вернуло бы в детальный пул ровно то, что экономим. Стало три состояния: совпало / округление ≤0,5% / изменилось — и коридор доверия против разъехавшихся единиц.

lz4 как самостоятельное решение для конфигураций не даёт цели

Замер на боевых строках: 3,3× — столько же, сколько текущий pglz. В план вошёл только в связке с диетой белого списка (13,5×).

W5b — коалесценция дублирующихся поисков отложено владельцем

Вместе с ней отложен и подъём потолка сессий 8 → 12: сначала неделя наблюдения за Ф1.

Поднять random_page_cost под «логику» опровергнуто замером

Диск — HDD, поднять стоимость случайного чтения «логично». Проверка на 8 горячих запросах: один план переворачивается в худшую сторону, 9,5 мс → 47 мс — случайное чтение оплачивает страничный кэш ОС, а не диск.

Бэкфилл проекций на весь фонд отложено до SSD

480 тысяч строк по HDD — не сейчас. Код готов и выключен; включается одной переменной после переезда на SSD (вопрос владельцу B).

07 · Объём, инструменты, бюджет

Что и чем построено

Объём за три дня: 47 коммитов, 121 файл, +20 432 строки

документация документация · 28 файлов · +11 264 строки 11 264 код backend продуктовый код · 62 файла · +5 497 / −701 строк 5 497 тесты-оракулы тесты · 19 новых файлов · +3 078 строк 3 078 инфраструктура compose, nginx, миграции окружения · 12 файлов · +593 строки 593 Из документации 9 599 строк — справочник docs/agent-knowledge (23 файла): от топологии и БД до инвариантов и легаси-ловушек; плюс спека очереди III и журналы.

Что использовали

  • PostgreSQL как очередь и как защита: outbox прямо в БД (общий движок для карточек и радара — без нового брокера), SAVEPOINT-класс, advisory-локи, условный UPSERT по версии источника, synchronous_commit=off на транзакцию.
  • Снимки вместо сборки: beat-задачи собирают витрину, «популярное» и проекции карточек; ручки отдают готовые байты.
  • Резидентный VPN-пул (тот же, что возил Avito) — теперь транспорт auto.ru и последняя ступень drom.
  • Замер без мутаций: подмена модулей через stdin в docker exec, откатываемые транзакции, SQL по прошедшим сессиям — прод читали, не трогая.
  • Свой нагрузочный стенд: users_sim.py (открытая модель гостей), реплей боевых payload-ов для конвейера записи, монитор железа.
  • Multi-agent код-ревью на повышенной строгости — семь проходов суммарно по трём волнам.

Бюджет LLM — жёсткое ограничение

Сутки живых проверок стоили $0,35: 1148 вызовов пакетного перевода (gemini-3-flash, 431 тыс. токенов) и 49 расчётов мощности (deepseek). Остаток на OpenRouter — $0,03, то есть один-два китайских поиска.

Поэтому проверки транспорта переведены на бесплатные площадки (drom и auto.ru — русский текст, перевод не нужен), а оракулы гоняются внутри контейнера без сети. Кончившиеся деньги пайплайн переживает сам — проверено по коду: 402 → брейкер → строки добирает Google; карточки не теряются, падает только качество китайских комплектаций и корейского осмотра. Килсвитч: TRANSLATION_BATCH_LLM=0.

08 · Что дальше

Решения, которые ждут владельца

ВопросЧто даст
A. Окно на пережатие истории конфигураций (lz4 + бэкфилл диеты ~2 ч + VACUUM FULL)таблица 4460 МБ → 0,7–0,9 ГБ; база помещается в кэш ОС
B. SSD на месте или новый серверслучайное чтение 3 мс → ~0,3 мс; открывает бэкфилл проекций на весь фонд
C. Куда выносить horeca (450 МБ RAM на боевой машине)весь сервер — под tazzz; переезд на минуты копирования
D. Пул тестовых учёток для приёмки сквозь HTTPприёмка G1/G2 на живых ручках, а не сервисных функциях
E. Биллинг GitHub Actions или self-hosted runnerCI вместо ручных выкаток (сборка и так идёт на этом же сервере)
Поднять CELERY_MAX_MEM_KB 150 → ~300 МБсамый дешёвый рычаг на слот-время: дети перестанут умирать после каждой задачи
Пополнить OpenRouter; мёртвый прокси .165 — провайдеру; европейский residential для mobile.deвернуть китайский перевод в полный ярус; +1 маршрут флоту; разблокировать mobile.de
Через неделю наблюдения — потолок сессий 8 → 12 и окно канарейки threads-пуласледующий шаг к расчётным 600+ поискам/час

Наблюдение первых суток идёт по добавленной телеметрии: лаг outbox проекций (алерт при >5 мин), доля дельты в сессиях «Свежих», очередь дообогащения, кулдауны маршрутов che168, MemAvailable в пиках.