fix(devops): дать trade-in связность с redis, потом уже задать REDIS_URL (#2709) #2763

Merged
bot-backend merged 1 commit from fix/2709-redis-connectivity into main 2026-08-06 22:37:25 +00:00
Collaborator

Что оказалось на самом деле

Issue прав в диагнозе: это не «забыли переменную», а отсутствующая связность. Подтверждено:

gendesign-redis-1  networks: gendesign_default
tradein-backend    networks: gendesign_shared, tradein-net     <- общей сети НЕТ

Цена ошибки — измерена, а не оценена

Тем же клиентом, что в app/services/cache.py (socket_timeout=2.0, socket_connect_timeout=2.0), изнутри tradein-backend:

адрес время одного GET исход
localhost:6379 — как сейчас 16.6 мс ConnectionError
имя не резолвится (NXDOMAIN) 123.5 мс ConnectionError
имя резолвится, но не отвечает 2003.7 мс TimeoutError

Предупреждение issue подтверждено буквально: третья строка ×2 (GET+SET) = ~4 с на запрос, молча, потому что get/set глотают исключение в logger.warning и отдают обычный промах.

Побочно уточнение: NXDOMAIN тоже не бесплатен (123 мс, а не 0) — то есть «просто задать имя, которого нет» тоже стоило бы ~250 мс на запрос.

Порядок соблюдён буквально: сначала сеть, потом переменная

Связность проверена на проде throwaway-контейнером до того, как переменная где-либо появилась:

docker run -d --network gendesign_shared --network-alias gendesign-redis redis:7-alpine
docker exec tradein-backend python ...  ->  PING 19.00 ms | SET db1 0.35 ms | GET db1 0.31 ms -> hello
docker rm -f ...                        ->  removed
gendesign-redis-1 после проверки        ->  networks: gendesign_default (не тронут), db0=2166 ключей

Решение и почему именно оно

Ввести существующий redis в gendesign_shared (алиас gendesign-redis), а не заводить свой в стеке trade-in.

Решило не изящество, а факт из deploy-tradein.yml:588:

docker compose -p gendesign-tradein -f docker-compose.prod.yml up -d --no-deps $SERVICES
SERVICES="browser backend frontend tgbot"   # + scraper; список ЗАХАРДКОЖЕН

Новый сервис в tradein-compose в этот список не попадает, а --no-deps отменяет depends_on. То есть свой redis никогда бы не стартовал, а REDIS_URL указывал бы в пустоту — ровно та ловушка, которую issue просит не создать. Починка требует правки deploy-tradein.yml, который заморожен (#2680 ждёт человека).

Отдельный номер БД, как требуется: db1 (db0 — celery-брокер gendesign, 2166 ключей; db2 — glitchtip).

Время ответа поиска до/после — и почему «после» не изменится

Замер до (внутри контейнера, 3 прогона × 5 наборов параметров):

запрос найдено время
ЕКБ центр r=2000 1217 214 / 243 мс (первый — 638)
ЕКБ центр r=5000, 2к 1735 140 / 150 мс
адрес «Ленина» 162 128 / 135 мс
r=10000, page 50 805 163 / 186 мс
r=20000, page 100 34407 146 / 152 мс

Медиана ~155 мс, холодный старт ~650 мс. Попадание в кэш заменило бы это на ~0.3 мс + десериализация.

Но выигрыша не будет, и это главный результат замера:

Caddy access log, окно 16.2 суток, 25 163 запроса:
  /trade-in/api/v1/search        ->  0 запросов
  (для сравнения: support/unread 806, geocode/suggest 261, me 194, auth/login 61)
frontend: ни одного вызова /api/v1/search во всём tradein-mvp/frontend/src

Роут смонтирован и отвечает 200, но его никто не вызывает. SearchCache — единственный потребитель redis_url — обслуживает мёртвый эндпоинт.

Зачем тогда мержить

  1. Ловушка снимается насовсем. Пока связности нет, любой, кто «починит переменную» по инерции, получит 4 с на запрос. Теперь имя резолвится и отвечает за 0.3 мс.
  2. #2665 получает хранилище с проверенной доступностью — issue #2709 сам на это указывает: «потолок темпа логинов нельзя строить на хранилище, доступность которого не проверена». Логин живой (61 запрос за 16 суток), в отличие от поиска.

Если бы не эти два пункта, честным вариантом было бы удалить SearchCache.

Известный потолок

maxmemory=0 / noeviction на инстансе намеренно не меняю: allkeys-lru на общем инстансе вытеснял бы поставленные в очередь celery-таски. Значит trade-in обязан ставить TTL на каждый ключ — он ставит (SET ... ex=ttl). Записал это в комментарий рядом с сервисом.

Гонка двух пайплайнов (осознанно принята)

Мерж триггерит deploy.yml (корневой compose) и deploy-tradein.yml (tradein compose) одновременно. Если tradein доедет первым, gendesign-redis ещё не резолвится → 123 мс на GET вместо 0.3. Окно — минуты, самолечится, и при 0 запросов к /search эффект нулевой. Ямы на 2 с здесь быть не может: она требует имени, которое резолвится и молчит, а до сетевой правки имя не резолвится вовсе.

Test plan

  • docker compose config на обоих файлах — валидны; redis резолвится в default + shared(aliases: gendesign-redis), backend/worker/beat/glitchtip не потеряли default (брокер цел)
  • связность проверена на проде ДО задания переменной (throwaway, см. выше)
  • цена всех трёх режимов отказа измерена реальным клиентом
  • базовое время поиска снято до правки
  • после деплоя: REDIS_URL виден в контейнере, redis-cli -n 1 отвечает из tradein-backend
  • после деплоя: фактическое попадание в кэш (cache_hit: true + падение elapsed_ms), а не «переменная задана»
  • после деплоя: db0 не затронут, celery-брокер жив

Refs #2709, #2674, #2665

## Что оказалось на самом деле Issue прав в диагнозе: это не «забыли переменную», а отсутствующая связность. Подтверждено: ``` gendesign-redis-1 networks: gendesign_default tradein-backend networks: gendesign_shared, tradein-net <- общей сети НЕТ ``` ## Цена ошибки — измерена, а не оценена Тем же клиентом, что в `app/services/cache.py` (`socket_timeout=2.0, socket_connect_timeout=2.0`), изнутри `tradein-backend`: | адрес | время одного GET | исход | |---|---:|---| | `localhost:6379` — как сейчас | **16.6 мс** | ConnectionError | | имя не резолвится (NXDOMAIN) | **123.5 мс** | ConnectionError | | имя резолвится, но не отвечает | **2003.7 мс** | TimeoutError | Предупреждение issue подтверждено буквально: третья строка ×2 (GET+SET) = ~4 с на запрос, **молча**, потому что `get`/`set` глотают исключение в `logger.warning` и отдают обычный промах. Побочно уточнение: NXDOMAIN тоже **не бесплатен** (123 мс, а не 0) — то есть «просто задать имя, которого нет» тоже стоило бы ~250 мс на запрос. ## Порядок соблюдён буквально: сначала сеть, потом переменная Связность проверена на проде throwaway-контейнером **до того, как переменная где-либо появилась**: ``` docker run -d --network gendesign_shared --network-alias gendesign-redis redis:7-alpine docker exec tradein-backend python ... -> PING 19.00 ms | SET db1 0.35 ms | GET db1 0.31 ms -> hello docker rm -f ... -> removed gendesign-redis-1 после проверки -> networks: gendesign_default (не тронут), db0=2166 ключей ``` ## Решение и почему именно оно **Ввести существующий redis в `gendesign_shared`** (алиас `gendesign-redis`), а не заводить свой в стеке trade-in. Решило не изящество, а факт из `deploy-tradein.yml:588`: ``` docker compose -p gendesign-tradein -f docker-compose.prod.yml up -d --no-deps $SERVICES SERVICES="browser backend frontend tgbot" # + scraper; список ЗАХАРДКОЖЕН ``` Новый сервис в tradein-compose в этот список не попадает, а `--no-deps` отменяет `depends_on`. То есть свой `redis` **никогда бы не стартовал**, а `REDIS_URL` указывал бы в пустоту — ровно та ловушка, которую issue просит не создать. Починка требует правки `deploy-tradein.yml`, который заморожен (#2680 ждёт человека). Отдельный номер БД, как требуется: **db1** (db0 — celery-брокер gendesign, 2166 ключей; db2 — glitchtip). ## Время ответа поиска до/после — и почему «после» не изменится Замер до (внутри контейнера, 3 прогона × 5 наборов параметров): | запрос | найдено | время | |---|---:|---:| | ЕКБ центр r=2000 | 1217 | 214 / 243 мс (первый — 638) | | ЕКБ центр r=5000, 2к | 1735 | 140 / 150 мс | | адрес «Ленина» | 162 | 128 / 135 мс | | r=10000, page 50 | 805 | 163 / 186 мс | | r=20000, page 100 | 34407 | 146 / 152 мс | Медиана ~155 мс, холодный старт ~650 мс. Попадание в кэш заменило бы это на ~0.3 мс + десериализация. **Но выигрыша не будет, и это главный результат замера:** ``` Caddy access log, окно 16.2 суток, 25 163 запроса: /trade-in/api/v1/search -> 0 запросов (для сравнения: support/unread 806, geocode/suggest 261, me 194, auth/login 61) frontend: ни одного вызова /api/v1/search во всём tradein-mvp/frontend/src ``` Роут смонтирован и отвечает 200, но его **никто не вызывает**. `SearchCache` — единственный потребитель `redis_url` — обслуживает мёртвый эндпоинт. ## Зачем тогда мержить 1. **Ловушка снимается насовсем.** Пока связности нет, любой, кто «починит переменную» по инерции, получит 4 с на запрос. Теперь имя резолвится и отвечает за 0.3 мс. 2. **#2665 получает хранилище с проверенной доступностью** — issue #2709 сам на это указывает: «потолок темпа логинов нельзя строить на хранилище, доступность которого не проверена». Логин живой (61 запрос за 16 суток), в отличие от поиска. Если бы не эти два пункта, честным вариантом было бы удалить `SearchCache`. ## Известный потолок `maxmemory=0 / noeviction` на инстансе намеренно **не меняю**: `allkeys-lru` на общем инстансе вытеснял бы поставленные в очередь celery-таски. Значит trade-in обязан ставить TTL на каждый ключ — он ставит (`SET ... ex=ttl`). Записал это в комментарий рядом с сервисом. ## Гонка двух пайплайнов (осознанно принята) Мерж триггерит `deploy.yml` (корневой compose) и `deploy-tradein.yml` (tradein compose) одновременно. Если tradein доедет первым, `gendesign-redis` ещё не резолвится → 123 мс на GET вместо 0.3. Окно — минуты, самолечится, и при 0 запросов к `/search` эффект нулевой. Ямы на 2 с здесь быть не может: она требует имени, которое резолвится и молчит, а до сетевой правки имя не резолвится вовсе. ## Test plan - [x] `docker compose config` на обоих файлах — валидны; redis резолвится в `default + shared(aliases: gendesign-redis)`, backend/worker/beat/glitchtip **не потеряли** `default` (брокер цел) - [x] связность проверена на проде ДО задания переменной (throwaway, см. выше) - [x] цена всех трёх режимов отказа измерена реальным клиентом - [x] базовое время поиска снято до правки - [ ] после деплоя: `REDIS_URL` виден в контейнере, `redis-cli -n 1` отвечает из tradein-backend - [ ] после деплоя: фактическое попадание в кэш (`cache_hit: true` + падение `elapsed_ms`), а не «переменная задана» - [ ] после деплоя: db0 не затронут, celery-брокер жив Refs #2709, #2674, #2665
bot-backend added 1 commit 2026-08-06 22:35:53 +00:00
fix(devops): дать trade-in связность с redis, потом уже задать REDIS_URL (#2709)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 7s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
49a3962e6b
Проблема была не в незаданной переменной, а в отсутствующей связности:
gendesign-redis-1 жил только в gendesign_default, tradein-backend — в
gendesign_shared + tradein-net. Общей сети нет → redis не резолвился ни под
каким именем. Вводим redis в gendesign_shared под алиасом gendesign-redis
(тот же приём, что уже применён к postgres) и только после этого задаём
REDIS_URL на db1 (db0 занят celery-брокером gendesign, db2 — glitchtip).

Порядок «сначала сеть, потом переменная» соблюдён буквально: связность
проверена на проде throwaway-контейнером ДО того, как переменная где-либо
появилась (из tradein-backend: PING 19 мс, SET 0.35 мс, GET 0.31 мс, db1).

Цена ошибки измерена тем же клиентом, что в app/services/cache.py:
localhost-отказ 16.6 мс (как было), NXDOMAIN 123.5 мс, а имя, которое
резолвится и не отвечает — 2003.7 мс. Последнее и есть та молчаливая яма,
про которую предупреждает issue: ×2 (GET+SET) на каждый запрос, без ошибки
наверх, потому что get/set глотают исключение.

Свой redis в стеке trade-in НЕ добавлен осознанно: deploy-tradein.yml
поднимает стек как `up -d --no-deps $SERVICES` с ЗАХАРДКОЖЕННЫМ списком
(browser backend frontend tgbot [scraper]). Новый сервис туда не попадает и
`--no-deps` его не подтянет — контейнер никогда бы не стартовал, а
REDIS_URL указывал бы в пустоту. Правка списка = правка deploy-tradein.yml,
который сейчас заморожен (#2680).

Ожидаемый выигрыш по скорости — НУЛЕВОЙ, и это измерено, а не оценено:
POST /api/v1/search за 16.2 суток логов Caddy получил 0 запросов, и во
фронтенде нет ни одного вызова этого роута. Реальная ценность правки —
снять заряженную ловушку и дать #2665 (потолок темпа логинов) хранилище с
ПРОВЕРЕННОЙ доступностью, чего issue прямо и требует.

Refs #2709, #2674, #2665
bot-backend merged commit 8def690b00 into main 2026-08-06 22:37:25 +00:00
bot-backend deleted branch fix/2709-redis-connectivity 2026-08-06 22:37:26 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2763
No description provided.