Обновление
Перед началом: сделайте резервную копию
Обновление применяет миграции схемы баз данных — это необратимо (обратных миграций «на всякий случай» никто не запускает автоматически). Прежде чем обновлять, снимите бэкап и PostgreSQL, и ClickHouse — см. Резервное копирование и восстановление. Не пропускайте этот шаг, даже если обновление кажется мелким.
Что меняется при обновлении с версий до 0.4.2: детали алертов уходят не всем
Раньше решение «слать ли в уведомление текст ошибки» принималось по типу канала: Telegram и вебхук считались внешними, почта — своей, и на любой почтовый адрес уезжал полный текст. Теперь оно принимается по получателю: доверенным считается адрес на хосте вашего инстанса, на домене из GOTCHA_TRUSTED_RECIPIENTS или во внутренней сети.
Что это значит на практике:
- почта на публичном сервисе (
@gmail.com,@yandex.ruи т. п.) больше не получает текст ошибки — только ссылку на проблему в интерфейсе; - почта на домене вашей организации, если этот домен отличается от хоста инстанса, тоже перестанет получать детали, пока вы не укажете его в
GOTCHA_TRUSTED_RECIPIENTS; - вебхук на вашу внутреннюю инфраструктуру теперь, наоборот, детали получает — раньше для этого приходилось включать
GOTCHA_EXTERNAL_CHANNEL_DETAILS_ENABLEDглобально, открывая заодно и Telegram.
Если ваша почта живёт на домене организации, добавьте его перед обновлением:
GOTCHA_TRUSTED_RECIPIENTS=corp.example
Проверить, что применилось, можно по логу старта — строка alert details: sent only to trusted recipients перечисляет действующий список. Подробности — в разделе Приватность и 152-ФЗ.
Разовое действие при обновлении с версий до 0.4.2: перевыпустите секреты каналов
До этой версии секрет канала доставки (bot-токен Telegram, ключ подписи вебхука) клался в очередь уведомлений notification_outbox в открытом виде — колонка payload это обычный jsonb, и шифрование alert_channels.secret этим обесценивалось. Миграция 0025 вычищает такие значения из очереди при обновлении, а новый код их туда больше не кладёт: секрет достаётся по идентификатору канала в момент отправки.
Но миграция чистит только живую базу. Если инстанс работал на старой версии, эти секреты лежат открытым текстом во всех резервных копиях PostgreSQL, снятых до обновления, — а бэкапы вы, надеемся, снимаете регулярно. Поэтому после обновления:
- Перевыпустите bot-токены Telegram у ботов, которых использует Gotcha (
/revokeв @BotFather, затем новый токен в канал). - Смените ключи подписи вебхуков на принимающей стороне и в канале.
- Если старые бэкапы больше не нужны — удалите их; если нужны, храните на том же уровне защиты, что и секреты.
Пропустить шаг можно только если каналов доставки у вас не было вовсе.
Что меняется при обновлении с версий до 0.4.2: исключённые участники теряют доступ через команды
Раньше исключение участника из организации не трогало его членство в командах, и он продолжал иметь доступ к проектам любой команды, в которую входил, — даже перестав быть участником самой организации. Миграция 0029 чинит это на уровне схемы: членство в команде больше не может существовать без членства в организации.
Применение миграции делает две вещи:
- удаляет уже накопившиеся висячие членства (участник давно не в организации, но всё ещё числится в одной из её команд) — число удалённых строк миграция печатает в свой лог;
- закрепляет правило в самой базе: исключение участника из организации автоматически каскадом снимает его членство в командах, так что расхождение больше не накопится снова.
Если в вашей организации кого-то в прошлом исключили, но доступ к проекту через команду у него сохранялся, — после этого обновления доступ пропадёт. Это и есть работающее исправление, но выглядеть оно может как поломка, если никто не ждал такого эффекта: стоит проверить перед обновлением, у кого сейчас есть доступ через команды, — или быть готовым объяснить это постфактум.
Эта миграция помечена backward-compatible: no. После её применения гейт схемы откажется запускать бинарь предыдущей версии на этой базе (см. «Откат назад» ниже) — обычный путь отката для неё недоступен, только восстановление бэкапа, снятого заранее.
Ссылок-приглашений, выданных до обновления, это не касается: их токены продолжают работать как прежде.
Что меняется при обновлении с версий до 0.4.2: длительности в карточках регрессий показывались завышенными в тысячу раз
Значения длительности регрессии производительности до этой версии записывались в микросекундах, а все, кто их читал, — карточки регрессий, письма-уведомления, вебхуки и Telegram — трактовали число как миллисекунды: реальный эндпойнт в 200 мс показывался как 200 секунд. Web-vital-метрики (lcp, fcp, ttfb, inp) это не затрагивало — не в том формате была записана только duration. Та же путаница мешала и абсолютному порогу, который отсекает ложные тревоги на малых значениях: он не срабатывал, потому что даже небольшая по факту регрессия выглядела огромной.
Миграция 0030 делит каждое сохранённое значение duration на 1000, приводя уже накопленные строки к единице, которой теперь пользуется остальная система. Никаких ручных действий не требуется — пересчёт выполняется автоматически как часть обновления, точно так же, как любая другая миграция. Откат домножает те же строки обратно, возвращая прежние числа с той точностью, которую допускает арифметика с плавающей точкой (значения, кратные 1000, восстанавливаются точно; у прочих может отличаться далёкий десятичный знак) — сверять вручную нечего в обоих направлениях.
Что меняется при обновлении с версий до 0.4.2: новые индексы этого обновления строятся без блокировки — но проверьте их после обновления
Это обновление разом добавляет двадцать четыре новых индекса на разные таблицы, тремя партиями: шесть — под часовую чистку сущностей (чистильщик, internal/telemetry/entity_janitor.go, чистит issues, perf_issues, incidents, perf_regressions, profile_regressions и metric_incidents по времени последнего появления либо закрытия — ни один существующий индекс этих таблиц не подходил под такой фильтр, каждый проход был полным сканом всей таблицы); шестнадцать — под внешние ключи, которые раньше были без индекса с нужной стороны (замедляет каскадные удаления и JOIN по ним); и два — под поиск подстроки в списке проблем (GIN-индексы по триграммам на issues.title/issues.culprit).
Такое количество новых индексов на диске не бесплатно — ожидайте заметного роста занятого места, особенно от двух GIN-индексов по триграммам: индексы такого типа на текстовых колонках обычно весят заметную долю от размера самой колонки. Насколько именно вырастет том — зависит от объёма ваших данных, отдельно не измеряли; после обновления последите за метриками свободного места (gotcha_storage_free_bytes/gotcha_storage_total_bytes/gotcha_storage_used_bytes, см. Мониторинг gotcha).
Все двадцать четыре индекса строятся через CREATE INDEX CONCURRENTLY — это осознанный выбор: обычный CREATE INDEX держит таблицу заблокированной на запись на всё время построения, а на боевом объёме это остановило бы приём событий на ощутимое время. CONCURRENTLY этой блокировки не ставит, но платит за это одной операционной особенностью.
Если построение индекса прервётся (обрыв соединения с базой, нехватка памяти, ручная отмена, перезапуск процесса посреди миграции) — PostgreSQL не убирает недостроенный индекс, а оставляет его в каталоге со статусом «недействителен». При повторном применении той же миграции CREATE INDEX CONCURRENTLY IF NOT EXISTS увидит объект с таким именем и молча пропустит создание — не станет чинить недействительный индекс. Миграция отчитается об успехе, гейт схемы будет зелёным, а рабочего индекса не будет: планировщик недействительные индексы игнорирует, и находка, ради которой заведён конкретный индекс, останется незакрытой без единого признака, что что-то не так.
Поэтому после обновления, независимо от того, прошло ли оно на вид гладко, проверьте:
SELECT indexrelid::regclass, indisvalid FROM pg_index WHERE NOT indisvalid;
Если список непуст — любой из двадцати четырёх индексов этого обновления, попавший в него, недостроен и не будет использован планировщиком (не только шесть индексов чистильщика — то же относится к индексам под внешние ключи и под поиск, добавленным этим же обновлением; список специально не перечисляется здесь по именам, чтобы не устареть при следующей миграции). Почините так:
DROP INDEX CONCURRENTLY <имя_индекса>;
и заново примените миграции (перезапуск с GOTCHA_AUTO_MIGRATE_ENABLED=true либо повторный запуск с --migrate-only) — на этот раз IF NOT EXISTS не увидит объекта с таким именем и построит индекс с нуля.
Что меняется при обновлении: типы у DSN-ключей приёма
Начиная с этого обновления у DSN-ключа есть тип (browser/server/agent),
ограничивающий, какую телеметрию этим ключом можно прислать — полный разбор
на странице Ключи приёма. Для существующей установки это
обновление ничего не ломает:
- все ключи, выпущенные до обновления, продолжают работать без каких-либо
действий с вашей стороны — они автоматически получают тип
legacyс полным допуском, бессрочно; - в настройках проекта такие ключи помечены бейджем «Без типа» — это не ошибка и не повод срочно что-то менять;
- новые проекты, созданные уже после обновления, получают сразу три ключа —
по одному на
browser/server/agent— вместо одного общего; - хост после обновления по-прежнему регистрируется автоматически, но
только экспортом с ключа типа
agent(или со старого ключа без типа); экспорт метрик с ключом другого типа принимается, как и раньше, а хост по нему просто не заводится — если у вас уже стоит агент или коллектор со старого шаблона конфига, ничего переделывать не нужно: он использует тот же ключ, что и до обновления.
Развести источники по новым типизированным ключам, не останавливая приём, — отдельная и необязательная задача; порядок действий — на странице Ключи приёма.
Что меняется при обновлении с версий до 0.23.0: десять переменных окружения переименованы
Релиз v0.23.0 («контрактная уборка») переименовал десять серверных переменных окружения. Переменная под прежним именем с непустым значением роняет старт с явным сообщением вида «старое имя → новое», а не тихо подменяется дефолтом — эта проверка не имеет срока давности и продолжает действовать на любой, сколь угодно старой установке.
| Было | Стало |
|---|---|
GOTCHA_METRIC_EVAL_INTERVAL | GOTCHA_METRIC_EVAL_INTERVAL_SECONDS |
GOTCHA_PROFILE_EVAL_INTERVAL | GOTCHA_PROFILE_EVAL_INTERVAL_SECONDS |
GOTCHA_HOST_EVAL_INTERVAL | GOTCHA_HOST_EVAL_INTERVAL_SECONDS |
GOTCHA_SLO_EVAL_INTERVAL | GOTCHA_SLO_EVAL_INTERVAL_SECONDS |
GOTCHA_ESCALATION_INTERVAL | GOTCHA_ESCALATION_INTERVAL_SECONDS |
GOTCHA_RETENTION_DAYS | GOTCHA_EVENT_RETENTION_DAYS |
GOTCHA_SERVER_URL | GOTCHA_PROBE_SERVER_URL |
GOTCHA_INGEST_RATE_LIMIT | GOTCHA_INGEST_RATE_PER_SEC |
GOTCHA_AGENT_DIST_DIR | GOTCHA_DIST_DIR |
GOTCHA_AGENT_DIST_RATE_PER_MIN | GOTCHA_DIST_RATE_PER_MIN |
Если вы обновляетесь с версии старше v0.23.0 — пройдите по всем местам, где
заданы эти переменные, тем же порядком, что описан ниже для волны заморозки
контракта перед 1.0: .env, юниты systemd, .env выносных проб на других
хостах.
Что меняется при обновлении: семнадцать переменных окружения переименованы
Это обновление переименовывает семнадцать серверных переменных окружения — единица измерения, модификатор и подсистема теперь честно читаются из имени, а не из соседства с ним. Переменная под прежним именем с непустым значением роняет старт с явным сообщением вида «старое имя → новое», а не тихо подменяется дефолтом.
| Было | Стало |
|---|---|
GOTCHA_ADDR | GOTCHA_LISTEN_ADDR |
GOTCHA_LOG_LEVEL | GOTCHA_LOGGING_LEVEL |
GOTCHA_LOG_FORMAT | GOTCHA_LOGGING_FORMAT |
GOTCHA_LOCAL_REGION | GOTCHA_UPTIME_LOCAL_REGION |
GOTCHA_REGISTRATION | GOTCHA_REGISTRATION_MODE |
GOTCHA_EXPORT_TTL_HOURS | GOTCHA_EXPORT_RETENTION_HOURS |
GOTCHA_SCRUB_KEYS | GOTCHA_SCRUB_DENY_KEYS |
GOTCHA_SCRUB_ALLOW_KEYS | GOTCHA_SCRUB_KEEP_KEYS |
GOTCHA_RUN_EVALUATORS | GOTCHA_EVALUATORS_ENABLED |
GOTCHA_AUTO_MIGRATE | GOTCHA_AUTO_MIGRATE_ENABLED |
GOTCHA_ALLOW_INSECURE_SECRET | GOTCHA_SECRET_KEY_ALLOW_INSECURE |
GOTCHA_MAX_BUFFER_BYTES | GOTCHA_MAX_WRITER_BUFFER_BYTES |
GOTCHA_MAX_QUEUE_BYTES | GOTCHA_MAX_INGEST_QUEUE_BYTES |
GOTCHA_PROBE_TOKEN | GOTCHA_PROBE_KEY |
GOTCHA_EXTERNAL_CHANNEL_DETAILS | GOTCHA_EXTERNAL_CHANNEL_DETAILS_ENABLED |
GOTCHA_OIDC_NAME | GOTCHA_OIDC_DISPLAY_NAME |
GOTCHA_PURGE_RECONCILE_HOURS | GOTCHA_PROJECT_PURGE_RECONCILE_HOURS |
После обновления сервера пройдите по всем местам, где у вас заданы
переменные GOTCHA_*, а не только по .env рядом с docker-compose.yml:
юниты systemd, отдельные .env-файлы CI/CD и, отдельно, .env выносных
проб (--mode=probe) на других хостах — апгрейд сервера до них физически
не дотягивается, они продолжат жить со старыми именами, пока их не обновят
вручную.
Самый заметный случай этого радиуса — переменная токена выносной пробы:
её новое имя — GOTCHA_PROBE_KEY (строка «было → стало» — в таблице выше).
Уже зарегистрированные и работающие пробы держат
прежнее имя в окружении своего хоста. Перезапустите их с переменной под
новым именем сразу после обновления центрального сервера — иначе при
следующем перезапуске пробы (обновление образа, перезагрузка хоста и т.п.)
она уйдёт в отказ старта вместо повторного подключения, а не продолжит
молча работать со старым значением. См. Пробы и
Конфигурацию.
Тот же радиус и у трёх переменных окружения самого агента gotcha-agent —
одна со сменой типа: интервал сбора теперь целое число секунд, а не
duration-строка вида «30s». Агент — тоже отдельный процесс на удалённом
хосте, куда апгрейд сервера не дотягивается: если
/etc/gotcha-agent/gotcha-agent.env хранит их под прежними именами,
ближайший запуск команды обновления откажет с сообщением
config check failed — установщик проверяет конфиг новым бинарём
(--check) раньше, чем трогает systemd-юнит, так что старый агент
продолжит спокойно работать на прежнем бинаре, пока вы не поправите имена в
файле вручную (по таблице ниже) и не запустите команду обновления ещё раз.
Актуальные имена и диапазоны — в справочнике переменных на странице
Хосты.
| Было | Стало |
|---|---|
GOTCHA_AGENT_INTERVAL | GOTCHA_AGENT_INTERVAL_SECONDS |
GOTCHA_AGENT_KEY | GOTCHA_AGENT_INGEST_KEY |
GOTCHA_AGENT_TLS_SKIP_VERIFY | GOTCHA_AGENT_TLS_INSECURE_SKIP_VERIFY |
Что меняется при обновлении: переменные compose и сборки переименованы
Той же волной переименовываются ещё одиннадцать переменных — восемь
переменных подстановки Docker Compose (пароли и потолки памяти контейнеров
баз, потолок памяти и MTU сети контейнера приложения, публикуемый порт и
адрес бинда) получают префикс GOTCHA_COMPOSE_, а три переменные, которыми
Makefile передаёт версию сборки в docker-compose.yml (DOCKER_BUILD_ENV),
— префикс GOTCHA_BUILD_. Описание самих переменных и их дефолтов — в
Конфигурации, разделы «Переменные только для
compose» и «Переменные только для сборки».
| Было | Стало |
|---|---|
GOTCHA_PG_PASSWORD | GOTCHA_COMPOSE_PG_PASSWORD |
GOTCHA_CH_PASSWORD | GOTCHA_COMPOSE_CH_PASSWORD |
GOTCHA_PG_MEM_LIMIT | GOTCHA_COMPOSE_PG_MEM_LIMIT |
GOTCHA_CH_MEM_LIMIT | GOTCHA_COMPOSE_CH_MEM_LIMIT |
GOTCHA_MEM_LIMIT | GOTCHA_COMPOSE_MEM_LIMIT |
GOTCHA_NET_MTU | GOTCHA_COMPOSE_NET_MTU |
GOTCHA_PORT | GOTCHA_COMPOSE_PORT |
GOTCHA_BIND | GOTCHA_COMPOSE_BIND |
GOTCHA_VERSION | GOTCHA_BUILD_VERSION |
GOTCHA_COMMIT | GOTCHA_BUILD_COMMIT |
GOTCHA_DATE | GOTCHA_BUILD_DATE |
В отличие от серверных и агентских переменных, эти одиннадцать не читает
вообще никакой процесс gotcha — их видит только сам Docker Compose
(подстановка ${...} в compose-файле) и make. Страховка при этом та же:
docker-compose.yml подключает .env целиком (env_file), так что
устаревшее имя в .env всё равно попадает в окружение процесса gotcha и
ловится тем же отказом старта «старое имя → новое», хотя сам процесс эти
переменные не читает и никогда не читал.
Если вы задаёте эти переменные не через .env, а прямо в блоке
environment:/build.args compose-файла или отдельно передаёте их в
make (например, make up GOTCHA_COMPOSE_PORT=...), переименуйте их там
же вручную по таблице выше — отказ старта такую правку не поймает, потому
что до окружения процесса gotcha она никогда не доходит.
Что меняется при обновлении: конфигурация с опечаткой больше не стартует
До этого обновления часть неверных значений GOTCHA_* принималась молча —
либо откатывалась на дефолт пакета, либо всплывала не на старте, а
где-то дальше, при первом реальном использовании (первая отправка почты,
первое подключение к базе, первая выгрузка). Теперь всё из списка ниже —
отказ старта, с именем переменной в сообщении:
- мусор в булевой переменной (
GOTCHA_SCRUB_IP=tureи подобное) — раньше молча выключал настройку; GOTCHA_MAX_WRITER_BUFFER_BYTES=0илиGOTCHA_MAX_INGEST_QUEUE_BYTES=0— раньше откатывались на дефолт пакета; явный0(как и отрицательное значение) теперь отказ старта;- нераспознанный
GOTCHA_LOGGING_LEVEL/GOTCHA_LOGGING_FORMAT— раньше молча откатывался наinfo/text; GOTCHA_SMTP_PORTвне диапазона1..65535— теперь безусловный отказ, даже еслиGOTCHA_SMTP_HOSTне задан;- четвёрка лимитов выгрузок (
GOTCHA_EXPORT_MAX_ROWS,_MAX_BYTES,_DISK_BUDGET_BYTES,_RETENTION_HOURS) — теперь проверяется на старте, а не глотается вslog.Warnпри первом использовании; GOTCHA_PG_DSN/GOTCHA_CH_DSN— теперь разбираются (без подключения) на старте, опечатка всплывает сразу, а не на первом обращении к базе;- пробельное (но непустое) значение
GOTCHA_SECRET_KEY/GOTCHA_PG_DSN/GOTCHA_CH_DSN— раньше откатывалось на дефолт; GOTCHA_SECRET_KEYтеперь обрезается по краям от пробелов — раньше бралось дословно, вместе с пробелом. Если ваш ключ хоть раз копировался с хвостовым пробелом, всё, что зашифровано под ним at-rest (секреты каналов доставки, client secret SSO), после обновления перестаёт расшифровываться молча — без единой ошибки на старте. Симптом: алерты в Telegram/webhook перестают уходить, вход по SSO ломается. Путь восстановления: задайтеGOTCHA_SECRET_KEY_PREVравным старому значению ключа буквально, вместе с пробелом (эта переменная, в отличие отGOTCHA_SECRET_KEY, читается дословно, специально ради такого восстановления), рядом с новымGOTCHA_SECRET_KEY— приложение расшифрует и старым, и новым ключом, зашифрует уже новым. После перезапуска с новым ключом уберитеGOTCHA_SECRET_KEY_PREV. Подробности процедуры ротации — в «Приватность и 152-ФЗ».
Пред-полётная проверка. Прежде чем обновлять боевой инстанс, стоит
заранее проверить, что новый бинарь примет ваш .env без единой находки —
но --migrate-only не просто проверяет конфиг: пройдя проверки, он
подключается к базе и реально применяет к ней миграции схемы. Прогоните
эту проверку на стенде, а не на проде, — той же командой, которой ниже
пользуется раздел «Раздельное применение миграций» для инициализации схемы:
docker compose --env-file .env run --rm --no-deps gotcha --migrate-only
Разбор и все проверки конфига (весь список выше, плюс переименования из
разделов над этим) выполняются раньше, чем приложение открывает хоть одно
соединение с базой, — если в .env есть находка, эта команда завершится
ненулевым кодом и одной строкой ERROR ... GOTCHA_ИМЯ: ... до применения
миграций к стенду. Нулевой код означает не только «конфиг чист» — за ним
уже последовало реальное применение миграций к базе, на которую указала
команда (см. ниже, куда именно).
Куда именно уезжают миграции. В штатном docker-compose.yml этого
репозитория GOTCHA_PG_DSN/GOTCHA_CH_DSN сервиса gotcha заданы прямо в
блоке environment: и указывают на соседние postgres/clickhouse этого
же compose-проекта — этот блок перекрывает те же имена из .env
(см. комментарий над ним в файле), так что команда выше физически не может
достучаться до прод-БД через скопированный в .env прод-DSN: она всегда
идёт в локальные контейнеры конкретно ТОГО стенда, на котором её запустили.
Это не освобождает от осторожности в двух случаях:
- если вы запускаете эту команду не через штатный
docker-compose.yml, а бинарём напрямую (gotcha --migrate-onlyс.env, экспортированным в окружение процесса) — тогдаGOTCHA_PG_DSN/GOTCHA_CH_DSNберутся из.envбуквально, без чьего-либо перекрытия, и прод-DSN в скопированном.envуедет ровно туда, куда указывает; - если стенд, где выполняется команда, — не отдельный
docker-compose.yml, а тот же самый прод-проект (тот же хост, тот же compose-стек) — тогда «локальные контейнеры этого стенда» и есть прод-база.
Если вам нужно направить пред-полётную проверку на конкретную стендовую
базу вместо локальных контейнеров compose-проекта (например, на общий
стендовый инстанс PostgreSQL/ClickHouse), перекройте оба DSN явно флагом
-e — он имеет более высокий приоритет, чем environment: в
docker-compose.yml (проверено: -e реально побеждает и --env-file, и
блок environment: сервиса):
docker compose --env-file .env run --rm --no-deps \
-e GOTCHA_PG_DSN='postgres://user:pass@staging-pg:5432/gotcha_staging?sslmode=disable' \
-e GOTCHA_CH_DSN='clickhouse://user:pass@staging-ch:9000/gotcha_staging' \
gotcha --migrate-only
Никогда не подставляйте сюда боевой DSN — -e перекрывает штатную
защиту docker-compose.yml буквально, и команда применит миграции к той
базе, которую вы укажете, без дополнительного подтверждения.
Обычное обновление (один сервер, режим --mode=all)
Если вы используете штатный docker-compose.yml как есть (одна реплика приложения, --mode=all) — это самый частый случай для self-hosted установки:
cd gotcha # папка с docker-compose.yml
git pull
make up-rebuild
Разбор:
git pull— подтягивает новый код из репозитория.make up-rebuild— пересобирает образ приложенияgotchaс обновлённым кодом и пересоздаёт контейнер (Postgres/ClickHouse используют готовые официальные образы, их специально пересобирать не нужно — Compose скачает нужную версию сам, если она изменилась вdocker-compose.yml). При старте (это стандартное поведение,GOTCHA_AUTO_MIGRATE_ENABLED=trueпо умолчанию) приложение само применяет недостающие миграции схемы к PostgreSQL и ClickHouse, прежде чем начать принимать запросы — отдельно ничего делать не нужно.
Собирайте через make, а не голым docker compose build: только make вычисляет git-версию и вшивает её в бинарь. Образ, собранный compose-командами напрямую, работает, но представляется как «no build metadata» в /version, на странице «О программе» и в метрике gotcha_build_info — вы теряете возможность проверить, что развёрнуто именно то, что вы думаете.
Если хотите обновить только образ без пересборки из исходников (например, вы используете готовый образ из реестра, а не собираете из git) — используйте docker compose pull + docker compose up -d.
Что означает автоматическое применение миграций
По умолчанию (GOTCHA_AUTO_MIGRATE_ENABLED=true) при каждом старте приложение проверяет версию схемы в базе и, если она отстаёт от версии, «зашитой» в бинарник, применяет недостающие миграции автоматически, прежде чем открыть порт. Это удобно для типовой установки «один сервер, один процесс» — обновление сводится к трём командам выше.
Раздельное применение миграций (несколько реплик приложения)
Если вы запускаете несколько процессов gotcha одновременно (например, отдельно --mode=ingest и --mode=web, или несколько реплик за балансировщиком — сценарий продвинутой эксплуатации, выходящий за рамки штатного docker-compose.yml), автоматические миграции при старте каждой реплики опасны: несколько процессов могут попытаться применить миграции одновременно. Для этого случая:
Установите
GOTCHA_AUTO_MIGRATE_ENABLED=falseдля всех реплик.Перед запуском реплик выполните миграции один раз — флагом
--migrate-only:docker compose run --rm --no-deps gotcha --migrate-onlyЭтот запуск применяет схему и завершается с кодом 0, не открывая HTTP-порт и не поднимая ни приёма, ни аптайма, — то есть годится как init-job. Флаг сам включает применение миграций, поэтому
GOTCHA_AUTO_MIGRATE_ENABLED=falseв окружении реплик ему не мешает. С--mode=probeон несовместим и об этом скажет: проба вообще не открывает соединение с базой, и молча выйти нулём означало бы соврать, что схема применена.После этого запустите (или перезапустите) все реплики с
GOTCHA_AUTO_MIGRATE_ENABLED=false— они проверят, что схема БД совпадает со встроенной версией, и откажутся стартовать, если это не так (это защита от «тихого» приёма данных в устаревшую схему — лучше явный отказ при старте, чем молчаливые ошибки на каждой вставке).
Для штатного docker-compose.yml из этого репозитория (один сервис gotcha в режиме all) раздельные миграции не нужны — используйте обычный сценарий обновления выше.
Если миграция оборвалась (dirty)
Если процесс убит во время миграции (отключение питания, docker kill, OOM), инструмент миграций оставляет схему помеченной dirty: «миграция N началась и не подтвердила завершение». С этого момента приложение отказывается стартовать — намеренно: оно не может знать, применилась миграция N наполовину или целиком, — и ошибка старта называет застрявшую версию:
docker compose logs gotcha
# ... база в состоянии dirty на версии N ...
Сначала посмотрите, что миграция N на самом деле сделала со схемой (файлы миграций лежат в репозитории в internal/db/migrations/, по номерам). Затем снимите флаг с той версией, которая соответствует реальности:
# Миграция N успела примениться (или вы доделали её руками) — остаёмся на N:
docker compose run --rm gotcha --migrate-force=N
# Вы откатили миграцию N руками — шаг назад на N-1:
docker compose run --rm gotcha --migrate-force=N-1
Для схемы ClickHouse флаг называется --migrate-force-ch=N; какая из баз застряла — говорит текст ошибки старта. Принимаются только эти два номера: всё остальное трактуется как опечатка, потому что молча сдвинуло бы точку отсчёта всех будущих миграций.
--migrate-force не доделывает миграцию — он только снимает признак «не завершена». Если миграция N применилась наполовину, а вы сняли флаг на N не проверив, следующий старт спокойно поедет по схеме без половины миграции N — и каждая вставка в недостающую часть будет падать. Сначала проверить схему, потом снять флаг, потом docker compose up -d — оставшиеся миграции (N+1 и дальше) приложение применит само.
Откат назад
Миграции схемы в Gotcha написаны как «вперёд»: откатывать саму схему назад продукт не умеет. А вот откатить бинарь, оставив схему как есть, можно — если миграции, применённые новой версией, были аддитивными.
Каждая миграция несёт признак обратной совместимости, и при её применении признак записывается в базу (таблица schema_compat). Старая версия читает его при старте и решает сама:
| Что применяла новая версия | Что делает старый бинарь при старте |
|---|---|
| Только аддитивные миграции (новые таблицы, колонки с умолчанием, индексы) | стартует и работает; в лог идёт предупреждение с перечнем версий |
| Есть хотя бы одна ломающая (удаление или переименование колонки, смена типа) | отказывается стартовать и называет версию, которая мешает |
О версии нет записи в schema_compat | отказывается стартовать: признак неизвестен, а рисковать данными нельзя |
Порядок действий:
Откатите
.envвместе с бинарём. Обязательно для версий ниже v0.34.0: этот релиз переименовал семнадцать серверных переменных (раздел выше), и старый бинарь новых имён не знает — он не откажется стартовать, а тихо возьмёт дефолт по переменной, которой в.envдля него уже нет. Самые опасные из семнадцати:GOTCHA_REGISTRATION→GOTCHA_REGISTRATION_MODE— старый бинарь не видит запрет и открывает публичную регистрацию;GOTCHA_SCRUB_KEYS→GOTCHA_SCRUB_DENY_KEYS— скрабинг PII в логах и событиях перестаёт действовать;GOTCHA_RETENTION_DAYS→GOTCHA_EVENT_RETENTION_DAYS— ретеншен молча падает к дефолту 90 дней, и более старые данные начинают удаляться;GOTCHA_ADDR→GOTCHA_LISTEN_ADDR— адрес прослушивания уезжает на дефолт.
Полный список переименований всех трёх волн (v0.23.0, v0.34.0, агентские и compose-переменные) —
internal/envcontract/renamed.goв репозитории.Откатите приложение — переключитесь на предыдущий коммит/тег и пересоберите:
git checkout <предыдущий-тег-или-коммит> make up-rebuildПосмотрите лог старта. Строка вида «схема версии N впереди встроенной M; версия … помечена обратно-совместимой, работаем на ней» означает, что откат удался и инстанс работает на более новой схеме. Это штатный режим, но временный: доведите ситуацию до конца — либо вернитесь на новую версию, либо восстановите базу из бэкапа, снятого перед обновлением.
Если старт прерван с сообщением о несовместимой схеме — откат бинаря невозможен: восстановите резервную копию, снятую перед обновлением (см. Резервное копирование и восстановление), и поднимите на ней предыдущую версию.
Пункт 4 предполагает, что вы увидите именно это сообщение. У бинарей, выпущенных до версии 1.0, при настройках по умолчанию (GOTCHA_AUTO_MIGRATE_ENABLED=true — так у всех, кто не выключал автомиграцию вручную) это не так: автомиграция запускается раньше проверки на опережение схемы, а библиотека миграций на схеме впереди своего набора не завершается тихо — падает с ошибкой вида no migration found for version N: read down for version N ... file does not exist. Итог тот же: откат бинаря невозможен, нужен бэкап, — но вместо внятного сообщения о несовместимости вы увидите сырую ошибку библиотеки, где нет ни слова ни про схему, ни про откат, ни про бэкап. Начиная с версии 1.0 проверка на опережение схемы выполняется до попытки автомиграции, и сообщение — то, что описано в пункте 4.
Признаки совместимости появились не с первого выпуска — какой именно их принёс, указано в CHANGELOG.ru.md репозитория. Откатиться через то обновление нельзя: версии схемы, применённые более ранними выпусками, признака не несут, и старт на них будет запрещён.
Второе ограничение — то, о чём schema_compat вообще не знает: секреты. Начиная с 0.25.0 всё, что шифруется в базе (клиентские секреты SSO-провайдеров, секреты каналов оповещения — токен Telegram-бота и HMAC-ключ вебхука, заголовки HTTP-мониторов), хранится в конверте enc:v2:<id-ключа>:…, и бэкфилл в этот формат выполняется при каждом старте: всё, что бинарь смог прочитать, пересохраняется под текущим ключом. Из-за этого откатываться ниже версии 0.25.0 нельзя вовсе — не «не рекомендуется», а именно нельзя: после первого же старта 0.25.0 или новее признак совместимости такой откат не запрещает (гейт видит номер версии схемы, а не формат данных в её колонках), старый бинарь стартует как ни в чём не бывало, но конверт enc:v2: он не распознаёт как шифротекст — считает значение секретом в открытом виде и отдаёт наружу как есть. Доставка алертов и вход через SSO ломаются молча, без единой строки в логе старта. Единственный путь ниже v0.25.0 — восстановление бэкапа, снятого до обновления: откатом одного бинаря это не сделать.
Именно поэтому шаг «сделайте бэкап перед обновлением» в начале этой страницы остаётся обязательным: часть откатов возможна без него, но не все.
Агенты на хостах обновляются отдельно
Обновление инстанса не трогает gotcha-agent, установленные на серверах: они продолжают слать метрики старой версией. Карточка хоста покажет бейдж «Есть обновление» с готовой командой — обновление это та же команда установки без переменных окружения, выполненная на хосте (см. Хосты). Старый агент остаётся совместимым: имена метрик и протокол не менялись, обновление нужно ради исправлений в самом агенте. Переименование переменных окружения самого агента этим обновлением описано в предыдущем разделе.
Проверка после обновления
docker compose ps
curl -sf http://localhost:59080/readyz
docker compose ps — все контейнеры должны быть Up (healthy), включая gotcha. /readyz должен вернуть {"status":"ready",…} с кодом 200. Дополнительно посмотрите логи на предмет ошибок при старте:
docker compose logs --tail=100 gotcha
Строка applying migrations, за которой не следует сообщение об ошибке — миграции прошли успешно. Затем откройте интерфейс в браузере и убедитесь, что вы видите свои организации, проекты и данные.
Что дальше
- Резервное копирование и восстановление.
- Конфигурация — полный справочник переменных, включая
GOTCHA_AUTO_MIGRATE_ENABLED. - Установка.