From 073977286f62250115d247fc4feeaf9ae27ff0c2 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Sat, 10 Oct 2026 16:32:21 +0300 Subject: [PATCH] docs(history): log Step 3 HA implementation, measurements and open questions --- HISTORY/2026-10-10-session-log.md | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/HISTORY/2026-10-10-session-log.md b/HISTORY/2026-10-10-session-log.md index ad564c4..61b3997 100644 --- a/HISTORY/2026-10-10-session-log.md +++ b/HISTORY/2026-10-10-session-log.md @@ -127,6 +127,31 @@ - Проверено: batch в пустую очередь пережил рестарт. - Billing — предусмотрен (модуль есть), но выключен (`BILLING_PG_HOST` не задан). Решение: включить позже. +### 2.16. Шаг 3 — HA active/passive (leader-election) +- Выбран **вариант C** (active/passive через Redis-лок) вместо полного выноса состояния (A) — + обоснование: причина простоя — недоступность ВМ, а не CPU; C стоит ~200 строк и не трогает ядро + очередей. Подробно: `doc/thinking/state-extraction-and-ha-2026-10-10.md`. +- Код (коммит `bc00643`): + - `app/persistence/redis.go`: `TryAcquireLock` (SETNX+PX), `RefreshLock`/`ReleaseLock` + (Lua: только если значение наше). При `Client == nil` — всегда `true` (мы одни). + - `app/leader/leader.go`: `Elector` — цикл «продлить/взять лок» каждые 3 с, TTL 10 с, `IsLeader()`. + - `app/router/router.go`: `/ready` (в обоих плоскостях) — 200 если готов, 503 если пассивный; + поведение задаётся пакетной `ReadyFunc`. + - `app/cmd/goaws.go`: `--leader-election` (или env `SHARED_SQS_LEADER_ELECTION=true`), + `--leader-ttl`, `--leader-interval`; `ReadyFunc` ставится только при включении; + лок отпускается при graceful shutdown. **По умолчанию выключено — поведение не изменилось.** +- **Замеры (не гипотезы), ВМ 213, два временных инстанса 4200/4210:** + - одна копия → `/ready` `200`, вторая → `503` (split-brain нет); `redis-cli get ssq:leader` + = `-` лидера; + - `kill -9` лидера → пассивный стал лидером, `/ready` → `200`, перехват занял **~12 с** + (максимум по формуле `ttl+interval` = 13 с); + - прод-API после деплоя: `list-queues` / `send-message` / `receive-message` через + `https://sqs.sinefo.ru` — OK; временные инстансы остановлены, ключ `ssq:leader` удалён. +- **В проде выключено**: для включения нужны общий Redis + вторая ВМ + LB с проверкой `/ready` + (иначе пассивный получит трафик и вернёт «пустые» очереди — главный риск варианта C, описан в доке). +- Док (коммит `865db49`): почему C, механика, замеры, ограничения, пошаговое включение, откат, + путь к варианту A (5 шагов) и триггеры перехода. + ## 3. Грабли (для повторного использования) 1. **`proxy_set_header Host $host` теряет порт** → SigV4 `SignatureDoesNotMatch`. @@ -184,4 +209,10 @@ pkill -x shared-sqs 1. Клиенты без явного `endpoint_url` — есть ли такие (вопрос №5 мануала). `QueueUrl` уже корректный. 2. Egress ВМ: `curl https://api.ipify.org` с ВМ вернул пусто — влияет на billing (PG) и JWT-API. -3. Дальше по мануалу: Шаг 2 (разбиение data-plane/control-plane), Шаг 3 (вынос состояния). +3. `SaveQueue` (метаданные очереди) всё ещё пишется **асинхронно** — при failover новый лидер + может не увидеть очередь, созданную незадолго до падения. Первый шаг к варианту A + (см. §8 дока по HA) — сделать синхронно, как `SaveMessagesSync`. +4. Для включения HA нужны: **общий Redis** (сейчас только локальный на 213 — SPOF), + вторая ВМ, LB с проверкой `/ready`. Пока всё это не готово — флаг выключен. +5. Дальше по мануалу: обновить устаревший `MANUAL-NEXT-2026-10-08.md` (ещё говорит «Шаг 1 — проверить + гипотезу» и «вопрос по домену открыт»); решить вопрос push в origin (18 коммитов впереди).