Files
anti-plagiarism/docs/DR-HA.md
jze9 e4ca4a7150
All checks were successful
Deploy / test (push) Successful in 2m59s
Deploy / deploy (push) Successful in 3s
docs: сервер эмбеддингов переехал на .1.40 + честные итоги проверок
Прод переключён на новый сервер эмбеддингов (VM 210 «embedding-cpu»,
192.168.1.40, хост pve2). Ключевое, чего не было в исходном плане переключения:
OLLAMA_URL живёт в Infisical, и deploy.sh генерирует .env из него на каждом
запуске — правка .env на сервере откатилась бы первым же деплоем.

Совместимость векторов проверена прямым сравнением, а не на слово: косинус
0.9999997, расхождение 1e-4 (округление AVX2 против AVX-512) — переиндексация
93 тыс. векторов не понадобилась.

Документация приведена в соответствие с фактами:
- ARCHITECTURE/DIAGRAM/README/CREDENTIALS: новый сервер, старый помечен как
  выведенный; убрано упоминание CUDA — GPU в проекте нет;
- DR-HA: эмбеддинги больше не висят на хосте .254, чьё падение 28.08 разом
  унесло брокер, прокси и секреты;
- INGESTION: исправлено собственное враньё про КиберЛенинку — «48 часов на
  100 тысяч» опровергнуто практикой, сайт блокирует выкачку после ~130 статей;
  снапшот OpenAlex вычеркнут как путь к миллионам (там только метаданные).

bulk_ingest_pmc.py: снята пометка «не закончен» — бага не было, первый прогон
упал уже после успешной вставки, а второй корректно пропустил дубли. Проверено:
100 статей, 183 090 отпечатков. Реальный темп 0.3 ст/с записан честно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 17:06:41 +05:00

8.1 KiB
Raw Blame History

Отказоустойчивость и восстановление (HA / DR)

Актуальная топология: PostgreSQL на выделенном LXC 1.38, Redis на 1.35, RabbitMQ на 192.168.20.82, app+ES на 1.32. Это не docker-compose, поэтому HA для БД/кэша делается на уровне Proxmox/LXC, а не в этом репозитории.

1. Бэкапы (сделано)

pg_dump → gzip → MinIO backups/pg/, cron ежедневно 03:00 на 1.38, ротация 14 дней.

2. Проверка восстановимости (сделано — было слепой зоной)

Бэкап без проверенного восстановления = отсутствие бэкапа. Скрипт scripts/ops/pg_restore_verify.sh берёт последний дамп из MinIO, restore в эфемерный postgres:16 и проверяет ключевые таблицы. Прод не трогает.

bash scripts/ops/pg_restore_verify.sh            # креды из .env

Рекомендация: cron раз в неделю на 1.32, алерт (как в antiplag_monitor) при ненулевом коде.

3. HA PostgreSQL — потоковая репликация (требует новый LXC)

Провижн реплики — задача на Proxmox (нужен ещё один LXC, напр. 1.39). Шаги:

  1. Primary (1.38) postgresql.conf: wal_level=replica, max_wal_senders=5, wal_keep_size=1GB; pg_hba.conf: строка replication для IP реплики.
  2. Replica: pg_basebackup -h 1.38 -U replicator -D $PGDATA -R (создаёт standby.signal + primary_conninfo), затем старт — догоняет primary по WAL.
  3. Failover: ручной pg_ctl promote на реплике + переключение POSTGRES_HOST в Infisical (окружение prod — не в .env на сервере напрямую, его перезапишет следующий деплой, см. ARCHITECTURE.md §10) и передеплой/рестарт сервисов (или автоматизация через Patroni + etcd — если нужен авто-failover).
  4. Проверка лага: SELECT * FROM pg_stat_replication на primary.

4. HA Redis — реплика + Sentinel (требует новый LXC)

Redis у нас — кэш/rate-limits/LSH-индекс (префикс antiplag_lsh). Потеря = деградация, не потеря данных задач (они в PG). Если нужна устойчивость:

  1. Второй Redis (реплика): replicaof 1.35 6379 + тот же requirepass.
  2. 3× Sentinel (на app-хостах): sentinel monitor antiplag 1.35 6379 2, авто-переключение мастера.
  3. Клиенты (Celery/кэш) → на Sentinel-aware подключение (redis.sentinel), либо оставить прямое подключение + ручной перевод REDIS_URL при аварии.

5. Единые точки отказа — статус

Компонент SPOF Митигация
PostgreSQL 1.38 да бэкап+restore-тест ✅; реплика — §3 (нужен LXC)
Redis 1.35 да graceful-фолбэк LSH в память ✅; реплика+Sentinel — §4
Векторный индекс было VECTOR_BACKEND=qdrant снимает (см. README) ✅
RabbitMQ .82 да мониторинг ловит падение ✅; кластер — по потребности
app/ES 1.32 да воркеры горизонтальны; ES single-node (для BM25 не критично)
Хост Proxmox .254 да, широкий эмбеддинги вынесены на pve2 ✅; брокер/прокси/секреты — нет, см. ниже

Гипервизор — самая широкая единая точка отказа. На хосте 192.168.20.254 одновременно живут RabbitMQ (.82), Infisical (.111) и CT 102 (.253) — фронтенд и реверс-прокси. С 31.08.2026 эмбеддинги отсюда вынесены: сервер embedding-cpu (192.168.1.40) стоит на другом физическом хосте (pve2, .1.37), так что падение .254 больше не уносит с собой L3. Его падение по-прежнему снимает сразу: приём и обработку задач (нет брокера), деплой (нет Infisical и Gitea) и весь публичный доступ (нет прокси) — при том что API, PostgreSQL, MinIO и сервер эмбеддингов продолжают работать. Проверено на практике 2026-08-28: хост перестал отвечать даже на ARP, всё перечисленное отвалилось разом, данные не пострадали. Разнести хотя бы прокси/брокер по разным физическим хостам — самая дешёвая мера; пока её нет, восстановление требует физического доступа к железу.

6. Известный операционный риск — RabbitMQ consumer_timeout vs долгие таски

Обнаружено на практике (2026-08-26): RabbitMQ по умолчанию рвёт канал, если consumer не сделал ack за 1800с. Любой Celery-таск с retry-логикой (rate-limit backoff у парсеров, ожидание зависшего внешнего сервиса вроде Ollama), суммарно занимающий дольше — брокер обрывает соединение раньше, чем таск успевает сдаться и подтвердиться, весь Celery-процесс падает (exitCode=1), Docker (restart: unless-stopped) поднимает его заново, недоставленное сообщение передоставляется — и цикл повторяется бесконечно, монополизируя весь пул воркера (было и на worker-indexer из-за backoff scripts/parsers/openalex.py, и на worker-gpu из-за зависшей Ollama на прежнем embedding-gpu; на новом сервере эмбеддингов зависание закрыто сторожевым таймером на самой машине).

Митигации (2026-08-27, сделано для worker-indexer):

  1. Бюджет времени прогона — PARSER_TIME_BUDGET_S (1500с) в worker-indexer/app/config.py: index.run_parser сам останавливается раньше дедлайна и помечает прогон partial вместо того, чтобы довести воркер до падения. Остаток дозаливается повторным запуском источника.
  2. worker_prefetch_multiplier=1 (worker-indexer/app/celery_app.py) — ключевое при массовой заливке. Таймаут отсчитывается от доставки сообщения, а не от начала выполнения: с дефолтным префетчем (4×concurrency) сотни поставленных в очередь долгих run_parser висят unacked и убивают канал на задачах, которые ещё даже не начинались.
  3. Retry-бэкоффы держим заведомо короче 1800с (MAX_RATE_LIMIT_RETRIES в openalex.py).

worker-gpu этих защит пока не имеет (там нет своих долгих retry-циклов, но есть зависание внешней Ollama — см. выше). Более глубокий общий фикс — поднять consumer_timeout на самом RabbitMQ (доступа для этого пока не заводили).