- ensureDeployment: Strategy Recreate (not RollingUpdate) — prevents H2 file lock when two pods mount same PVC simultaneously during rollout restart - preStop: sleep 3 — graceful H2 shutdown before SIGTERM - livenessProbe timeoutSeconds: 3 — prevents false positive on GC pause - terminationGracePeriodSeconds: 15 - doc: thinking log, ERR-SQS-06, progress.md, architecture SVG schema
5.5 KiB
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:
- Kubernetes добавляет аннотацию
restartedAt→ меняется template → начинается rollout - Стратегия
RollingUpdate(maxSurge=25%, maxUnavailable=25%) → для replicas=1:- maxSurge=1 (ceil 0.25) → Kubernetes поднимает НОВЫЙ pod
- maxUnavailable=0 (floor 0.25) → старый pod ЕЩЁ ЖИВА
- PVC
ReadWriteOnce— допускает mount с нескольких pod на ОДНОЙ НОДЕ (это не ReadWriteOncePod) - Оба pod монтируют один PVC → оба пытаются открыть
/data/elasticmq.mv.db - Старый ElasticMQ держит
FileChannel.lock()→ новый ElasticMQ получаетMVStoreException: The file is locked - Persistence actor (SqlQueuePersistenceActor) в новом pod падает → dead letters
- Старый pod убивается (readinessProbe eventual fail) → lock освобождается — но поздно
- SQS REST server работает (port 9324 слушает), но WRITE-операции (SendMessage) зависают — actor мёртв
Решение — 3 изменения в ensureDeployment
-
Strategy: Recreate (вместо RollingUpdate)
- Kubernetes СНАЧАЛА убивает старый pod, ПОТОМ поднимает новый
- Два pod НИКОГДА не работают одновременно → lock невозможен
- Downtime ~25-30 секунд (JVM startup) — допустимо для мультитенант SQS
-
preStop hook: sleep 3
- При SIGTERM JVM начинает shutdown
sleep 3даёт H2 время наfsync+FileChannel.close()- Без preStop: Kubernetes может убить pod раньше чем H2 закончит flush
-
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 одновременно работают? Какая стратегия деплоя?