# 2026-04-09 — Thinking Log **Агент:** GitHub Copilot (Claude Opus 4) --- ## Анализ H2 file lock — корневая причина ### Симптом При каждом `kubectl rollout restart` ElasticMQ стартует, но `SendMessage` зависает навсегда. В логах: `The file is locked: /data/elasticmq.mv.db [2.2.224/7]`, затем `dead letters` и `AskTimeoutException` на SendMessage. ### Ошибочная гипотеза (v0.1.10) Предположил что JVM не освобождает JDBC-level lock при crash → добавил `FILE_LOCK=NO` в JDBC URI. **Это было НЕПРАВИЛЬНО.** `FILE_LOCK=NO` отключает только JDBC soft-lock. H2 MVStore использует `java.nio.FileChannel.lock()` — это OS-level file lock, не зависящий от JDBC параметров. ### Почему "работало" после каждой чистки После `rm /data/elasticmq.mv.db` + restart — файл создаётся заново, lock отсутствует. Но при следующем rollout restart проблема возвращается. ### Корневая причина (найдена 2026-04-09) **Deployment strategy: `RollingUpdate` + PVC: `ReadWriteOnce`** Цепочка событий при `kubectl rollout restart`: 1. Kubernetes добавляет аннотацию `restartedAt` → меняется template → начинается rollout 2. Стратегия `RollingUpdate` (maxSurge=25%, maxUnavailable=25%) → для replicas=1: - maxSurge=1 (ceil 0.25) → Kubernetes поднимает НОВЫЙ pod - maxUnavailable=0 (floor 0.25) → старый pod ЕЩЁ ЖИВА 3. PVC `ReadWriteOnce` — допускает mount с нескольких pod на ОДНОЙ НОДЕ (это не ReadWriteOncePod) 4. Оба pod монтируют один PVC → оба пытаются открыть `/data/elasticmq.mv.db` 5. Старый ElasticMQ держит `FileChannel.lock()` → новый ElasticMQ получает `MVStoreException: The file is locked` 6. Persistence actor (SqlQueuePersistenceActor) в новом pod падает → dead letters 7. Старый pod убивается (readinessProbe eventual fail) → lock освобождается — но поздно 8. SQS REST server работает (port 9324 слушает), но WRITE-операции (SendMessage) зависают — actor мёртв ### Решение — 3 изменения в ensureDeployment 1. **Strategy: Recreate** (вместо RollingUpdate) - Kubernetes СНАЧАЛА убивает старый pod, ПОТОМ поднимает новый - Два pod НИКОГДА не работают одновременно → lock невозможен - Downtime ~25-30 секунд (JVM startup) — допустимо для мультитенант SQS 2. **preStop hook: sleep 3** - При SIGTERM JVM начинает shutdown - `sleep 3` даёт H2 время на `fsync` + `FileChannel.close()` - Без preStop: Kubernetes может убить pod раньше чем H2 закончит flush 3. **livenessProbe timeoutSeconds: 1 → 3** - JVM стартует за 20-23 секунды - initialDelaySeconds=5 + failureThreshold=5 × period=10 = 55 сек запас — хватает для старта - НО: `timeoutSeconds=1` — если GC pause > 1 сек → liveness fail → unnecessary restart → CrashLoopBackOff - Поднимаем до 3 секунд. GC pause > 3 сек — это уже реальная проблема которую стоит рестартить ## Дополнительные обнаруженные проблемы ### /_next/ и /queues/ ingress — глобальные (архитектурная) Пути `/_next/` и `/queues/` на хосте `sqs.kube5s.ru` общие. При двух тенантах с `enableUI=true` — конфликт ingress. **Решение отложено** — пока один тенант с UI. При мультитенант UI → нужен отдельный хост per tenant. ### imagePullPolicy: Always на UI `softwaremill/elasticmq-ui:latest` + `Always` → upstream может сломать при обновлении. **Пока оставляем** — будем пинить версию когда стабилизируем. ### memory limit 512Mi vs Xmx 384m `-Xmx384m` + JVM overhead ~150 МБ = ~534 МБ > limit 512 Mi. OOMKill возможен при нагрузке. **Пока оставляем** — в idle не стреляет. Учтём при нагрузочном тестировании. ## Самоанализ ошибки Почему неправильно решил в v0.1.10: - Увидел `The file is locked` → сразу искал H2-настройки → нашёл `FILE_LOCK=NO` - НЕ проверил deployment strategy (RollingUpdate — default в Kubernetes) - НЕ проверил ReadWriteOnce behavior (допускает multi-pod на одной ноде) - НЕ проверил что происходит при rollout (два pod одновременно) - Лечил симптом (lock message) вместо причины (concurrent access) **Вывод:** при любой ошибке связанной с persistence/lock/state — ПЕРВЫМ делом проверять: кто ещё имеет доступ к файлу? Сколько pod одновременно работают? Какая стратегия деплоя?