Почему Grafana тормозит в Kubernetes: проблема SQLite на сетевой файловой системе
Симптомы деградации и неожиданные сбои
В один из рабочих дней система мониторинга Grafana начинает вести себя аномально медленно. Загрузка дашбордов занимает более 5 секунд, API-запросы отваливаются по таймауту, а один из подов приложения уходит в циклическую перезагрузку (CrashLoopBackOff). При изучении системных логов обнаруживаются сотни ошибок вида level=error msg="database is locked", level=error msg="Datasource provisioning error: database is locked" и level=warn msg="saving alert rule state: database is locked". Все операции записи — от создания новых дашбордов и настройки алертов до выполнения фоновых задач очистки сессий — начинают сбоить.
Типичная конфигурация, при которой возникает этот сбой, на первый взгляд выглядит вполне профессионально: три реплики Grafana версии 12.x запущены в кластере Kubernetes под управлением HorizontalPodAutoscaler (HPA). В качестве общего хранилища используется постоянный том на сетевой файловой системе Amazon EFS, что позволяет всем подам одновременно читать и писать общие конфигурационные данные. Однако, как показывает практика эксплуатации, практически каждый элемент этой архитектуры в корне ошибочен.
Архитектура по умолчанию: встроенная база данных
Многие системные администраторы упускают из виду важный нюанс: по умолчанию Grafana использует встроенную встраиваемую базу данных SQLite. Все конфигурационные данные, настройки пользователей, права доступа и параметры дашбордов хранятся в единственном файле по пути /var/lib/grafana/grafana.db.
Для одиночного инстанса SQLite подходит идеально — это быстрая, надежная и не требующая администрирования база данных. Но она имеет фундаментальное ограничение: SQLite является базой данных с одним активным писателем. Множество параллельных процессов могут одновременно выполнять запросы на чтение (SELECT), но в любой конкретный момент времени вносить изменения (INSERT, UPDATE, DELETE) имеет право только один процесс. Для предотвращения повреждения данных SQLite использует механизм блокировок на уровне файлов. Когда этот файл размещается на сетевой файловой системе и к нему обращаются несколько распределенных подов, база данных пытается координировать конкурентные записи через сеть, что приводит к критическим сбоям.
Сетевой барьер: в чем проблема EFS и NFS
Сервис Amazon EFS представляет собой управляемую сетевую файловую систему по протоколу NFSv4.1. Сетевые хранилища подходят для конфигурационных файлов, медиа или логов, но не для транзакционных баз данных. Официальная документация SQLite прямо предупреждает, что сетевые файловые системы часто некорректно или слишком медленно обрабатывают блокировки файлов.
Протокол NFSv4.1 реализует байтовые блокировки корректно, однако их выполнение занимает много времени. Каждая операция блокировки и сброса буферов на диск (fsync) требует сетевого кругового пути (network round-trip). На локальном блочном диске (EBS) эти операции занимают менее 1 миллисекунды. На сетевом диске EFS задержка составляет от 10 до 50 миллисекунд. В результате простая транзакция записи в SQLite, проходящая локально за 2 миллисекунды, на сетевой EFS растягивается до 40–200 миллисекунд:
- Получение блокировки на запись: 10–50 мс (сеть).
- Буферизованная запись изменений: быстро.
- Сброс журнала на диск (
fsync): 10–50 мс (сеть). - Запись данных в основной файл базы: быстро.
- Сброс основного файла на диск (
fsync): 10–50 мс (сеть). - Освобождение блокировки записи: 10–50 мс (сеть).
Когда три пода Grafana пытаются одновременно писать в базу (например, сохраняя состояние алертов), потоки выстраиваются в очередь. Превышается лимит ожидания SQLite (busy timeout), и система начинает выдавать ошибки блокировки базы данных.
Ловушка автоматического масштабирования (HPA)
Использование HorizontalPodAutoscaler (автоскейлера подов) усугубляет проблему. В описываемой конфигурации HPA был настроен на масштабирование от 1 до 3 реплик при достижении 60% использования оперативной памяти.
Из-за задержек при блокировке базы данных SQLite в памяти подов начали скапливаться ожидавших своей очереди потоки выполнения (goroutines). Потребление памяти каждым подом выросло до 800 МБ при лимите в 1 ГБ (80% использования). Автоскейлер зафиксировал превышение порога в 60% и увеличил число реплик до трех. Рост числа подов привел к увеличению числа параллельных процессов, пытающихся писать в SQLite. Конкуренция за сетевую блокировку выросла лавинообразно, потоки стали скапливаться еще быстрее, потребляя память и провоцируя дальнейшее масштабирование. Возник замкнутый круг деградации системы.
Штраф за редкий доступ (Infrequent Access)
Дополнительной неочевидной проблемой EFS является функция экономии затрат Infrequent Access (IA). Файлы, к которым не обращались в течение установленного времени (например, 7 дней), автоматически перемещаются на более дешевый уровень хранения.
Чтение файлов из уровня IA сопровождается дополнительной задержкой в 100–200 миллисекунд при первом обращении. Поскольку SQLite при запуске Grafana выполняет проверку и миграцию таблиц, обращение к холодным файлам базы данных из уровня IA приводит к критическому замедлению старта подов. При этом задержка не отображается в метриках Kubernetes, проявляясь как случайное зависание процесса запуска.
Решение: локальные диски EBS и одиночная реплика
Наиболее простым решением для устранения проблемы является отказ от EFS в пользу локального блочного хранилища EBS (ReadWriteOnce) с одновременным сокращением числа реплик Grafana до одной.
После перехода на EBS подов потребление памяти снизилось на 60%, так как исчезли зависшие в ожидании блокировок потоки. Время запуска одиночного пода сократилось до 7 секунд. Для большинства команд кратковременная недоступность мониторинга при перезапуске единственного подом является вполне приемлемым компромиссом по сравнению со случайными сбоями распределенной конфигурации на сетевом диске.
Высокая доступность: как сделать HA правильно
Если бизнес-требования обязывают поддерживать высокую доступность (High Availability) Grafana с нулевым временем простоя, использовать сетевую файловую систему для SQLite нельзя. Правильная архитектура высокой доступности требует перехода на внешнюю клиент-серверную систему управления базами данных.
Grafana официально поддерживает работу с PostgreSQL и MySQL. При такой конфигурации:
- Несколько реплик Grafana могут одновременно читать и писать данные в общую базу данных без взаимных блокировок файлов.
- База данных администрируется независимо (например, через AWS RDS Multi-AZ с автоматической репликацией и резервированием).
- Подам Grafana больше не требуется общая сетевая файловая система — они используют быстрые локальные диски для временного кэширования.
- Горизонтальное масштабирование реплик начинает работать корректно и действительно помогает распределять входящую нагрузку.

