# Остановка и миграция: раздельные бюджеты и следы в логе **Приоритет:** средний Две находки эксплуатационного и идиоматического проходов ревью каталога. Обе существовали и раньше, но достижимыми их сделал первый маршрут чтения: `GET /api/v1/metrics` — первый обработчик, способный законно работать заметное время. **Бюджет остановки один на оба этапа.** `shutdownCtx` в `runServe` передаётся и в `srv.Shutdown`, и в ожидание фонового воркера. `Shutdown` ждёт, пока обработчики вернутся; контексты обработчиков он при этом не отменяет (`BaseContext` не задан), так что долгий запрос каталога может съесть бюджет целиком. Дальше `select` видит два готовых случая и выбирает равновероятно: база закрывается или нет от запуска к запуску, а в лог уходит `shutdown budget exceeded stage=fold-worker` — обвинение воркеру, который бюджета не превышал. Цена именно в диагнозе: этот `WARN` означает «доставка осталась `pending`, данные под вопросом», и ложное срабатывание обесценивает настоящее. Чинится двумя движениями: собственный `context.WithTimeout` второму этапу вместо исчерпанного первого, и `BaseContext`, производный от контекста жизненного цикла, чтобы долгий запрос об остановке узнавал. **Цена этой ветки выросла** (change `cena-chitayushchego-marshruta`): база в ней не закрывается, а значит не закрывается и закреплённое соединение версии витрины — последнего соединения к базе не наступает, SQLite не делает финального чекпойнта, и рядом с базой остаётся неразобранный `-wal` до 64 МиБ. Данные целы (следующее открытие проиграет журнал), но файл базы в этом состоянии нельзя переносить без его `-wal`. Обвинение в логе при этом стало честнее: этап называется `background`, а не `fold-worker`, потому что ждут двоих. **Миграция молчит и не прерывается штатной остановкой.** `store.migrate` не пишет ни одной записи — ни «начал», ни «закончил», ни длительность, — а первая строка в логе появляется уже после успешного открытия базы. Если миграция идёт долго, владелец не отличит «ещё мигрирует» от «зависло» и от «упало»: тишина одинакова во всех трёх случаях. Плюс `migrate` работает на `context.Background()`, то есть `SIGTERM` она не видит и ждать придётся 30-секундного `SIGKILL`. Порчи данных при этом нет: goose оборачивает миграцию в транзакцию, обрыв откатывает её целиком, и следующий старт повторяет с нуля. Замер на синтетической копии годового объёма (260 тысяч объектов, 483 МБ): `CREATE INDEX` миграции `00009` — 297 мс тёплым кешем. То есть сегодня окно тишины — доли секунды; опасность в том, что оно растёт вместе с витриной незаметно. Готово, когда `WARN` о превышении бюджета называет виновный этап честно, а в логе старта видно, что миграции накатывались и сколько это заняло. Связано: `cmd/healthlog/serve.go`, `internal/store/store.go`, change `2026-08-02-cena-chitayushchego-marshruta` (архив).