From 4ef8bc818bf43dfe61ac4c3bb1889512358e26de Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 27 Aug 2026 18:47:09 +0300 Subject: [PATCH] =?UTF-8?q?ci(tradein):=20=D0=B3=D0=B5=D0=B9=D1=82=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=BE=D0=B2=20=E2=80=94=20?= =?UTF-8?q?=D0=B4=D0=B5=D0=BF=D0=BB=D0=BE=D0=B9=20=D0=BB=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B8=D1=85=20=D1=83=D0=B6=D0=B5=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Оборванный CREATE INDEX CONCURRENTLY оставляет индекс с indisvalid=false: планировщик им не пользуется, ошибки нет, а re-run миграции с IF NOT EXISTS видит его как существующий и молча пропускает. До сих пор это ловил только шаг 3b деплоя — то есть после раскатки на прод, ценой красного деплоя и ручного DROP INDEX CONCURRENTLY в окне. Тот же запрос теперь стоит в CI сразу после сборки схемы из 252 миграций. Проверка в деплое ОСТАЁТСЯ: CI собирает схему с нуля и по построению не может воспроизвести оборванный CIC на живой базе — это разные детекторы, не дубликаты. CI ловит миграцию, которая рождает невалидный индекс из чистой схемы; деплой ловит след аварии на проде. Оба пути проверены на реальной базе (prod tradein-postgres): штатный предикат → invalid=0, гейт зелёный; инвертированный → 358, ветка падения срабатывает и печатает список idx/tbl. Refs #2990 --- .forgejo/workflows/ci-tradein.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.forgejo/workflows/ci-tradein.yml b/.forgejo/workflows/ci-tradein.yml index d5764313..e724c832 100644 --- a/.forgejo/workflows/ci-tradein.yml +++ b/.forgejo/workflows/ci-tradein.yml @@ -191,6 +191,31 @@ jobs: done echo "✓ схема собрана: $(docker exec "$CI_PG" psql -U tradein -d tradein -tAc \ "SELECT count(*) FROM information_schema.tables WHERE table_schema='public'") таблиц" + # Гейт невалидных индексов (#2990). Оборванный CREATE INDEX CONCURRENTLY + # оставляет индекс с indisvalid=false: планировщик им не пользуется, + # ошибки нет, а re-run миграции с IF NOT EXISTS видит его как + # существующий и молча пропускает. Тот же запрос стоит в деплое + # (deploy-tradein.yml, шаг 3b) — там он ловит битые индексы на живом + # проде; здесь он ловит миграцию, которая рождает невалидный индекс + # прямо из чистой схемы, до раскатки. + invalid=$(docker exec "$CI_PG" psql -U tradein -d tradein -tAc "SELECT count(*) + FROM pg_index i + JOIN pg_class c ON c.oid = i.indexrelid + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE NOT i.indisvalid + AND n.nspname NOT IN ('pg_catalog', 'information_schema')") \ + || { echo "::error::не удалось прочитать pg_index"; exit 1; } + if [ "${invalid:-0}" != "0" ]; then + echo "::error::после применения миграций невалидных индексов: $invalid" + docker exec "$CI_PG" psql -U tradein -d tradein -c "SELECT i.indexrelid::regclass AS idx, i.indrelid::regclass AS tbl + FROM pg_index i + JOIN pg_class c ON c.oid = i.indexrelid + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE NOT i.indisvalid + AND n.nspname NOT IN ('pg_catalog', 'information_schema')" 2>&1 || true + exit 1 + fi + echo "✓ невалидных индексов нет" - name: Install uv # Официальный standalone-инсталлер. НЕ astral-sh/setup-uv — он ломается