Пробуждение баз данных без мёртвых соединений
Опубликованные приложения в Mythex и их базы данных засыпают, когда ими никто не пользуется. Когда приложение просыпалось, его первый запрос мог висеть 10–12 секунд, потому что приложение пыталось переиспользовать соединения с базой, которые умерли, пока всё спало. Теперь мы даём приложениям на Spring Boot настройки базы, устойчивые ко сну, и просим агента строить приложения на любом стеке так же. На платформе хостинга время первого запроса после пробуждения сократилось с 12,7 секунды до 2,3 секунды.
Почему приложения и базы засыпают
Опубликованное приложение Mythex работает на машине, которая приостанавливается после нескольких минут простоя, а его база данных — это бессерверный Postgres, который засыпает после пяти минут простоя и просыпается по следующему запросу. Именно это делает тихое приложение дешёвым: вы платите за то, что база фактически использовала, а спящее приложение не тратит ресурсы на вычисления. Об оплате мы писали в статье оплата баз данных за то, что они реально используют.
Проблема в том, что эти два компонента засыпают по отдельности и по-разному понимают, что такое соединение.
Симптом: медленный первый визит
Большинство серверных приложений держат пул соединений: несколько открытых соединений с базой, которые ждут наготове, чтобы каждый запрос не платил за открытие нового. Для приложения, которое всегда работает, это правильная схема.
Вот что происходит со сном:
- Приложение работает, в пуле несколько простаивающих соединений.
- Никто не заходит. Машина приложения приостанавливается — замирает со своим пулом.
- База данных засыпает и разрывает свою сторону каждого соединения.
- Приходит посетитель. Приложение просыпается ровно в том состоянии, в каком было, всё ещё держа соединения, о которых база уже не знает.
- Пул выдаёт одно из этих соединений. Чтобы понять, что оно мёртвое, нужно его попробовать и дождаться — и только потом отказаться от него.
В приложении Spring Boot проверка мёртвых соединений блокировала первый запрос примерно на 10 секунд, после чего тот падал с ошибкой «Connection is not available». Для посетителя сайт просто зависал, а потом, возможно, показывал ошибку.
Почему хуже всего пришлось Spring-приложениям
Spring Boot — популярный Java-фреймворк, и по умолчанию он использует пул соединений HikariCP. Spring может настраивать этот пул через параметры, в том числе задавая, сколько простаивающих соединений держать.
Но Mythex выдаёт каждому приложению базу данных в виде одной переменной DATABASE_URL вида postgresql://user:pass@host/db. Spring не может напрямую прочитать этот формат. Поэтому Spring-приложения на Mythex создавали свой пул прямо в коде, разбирая этот URL, а пул, собранный вручную, игнорирует все настройки spring.datasource.*. Даже если бы кто-то знал правильные настройки пула, применить их было бы негде.
Исправление, часть 1: настройки, которые понимает Spring
Теперь, когда у проекта есть база, мы также выдаём переменные окружения, которые Spring читает нативно:
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAMEиSPRING_DATASOURCE_PASSWORD, полученные изDATABASE_URL(с обязательным SSL, если в URL не сказано иное).- Настройки пула, которые переживают сон:
| Настройка | Значение | Почему |
|---|---|---|
| Минимум простаивающих соединений | 0 | Между запросами ничего не держать открытым, поэтому нечему устаревать |
| Тайм-аут простоя | 30 секунд | Быстро закрывать неиспользуемые соединения |
| Максимальное время жизни | 2 минуты | Закрывать каждое соединение, прежде чем оно состарится |
| Тайм-аут соединения | 5 секунд | Не держать запрос долго, если база медленно отвечает |
Поскольку простаивающих соединений не держится, проснувшееся приложение открывает одно новое соединение, а не перебирает мёртвые.
Когда эти переменные есть, Spring сам настраивает пул, и настройки применяются. Всё, что проект задал сам, всегда побеждает; мы заполняем только то, чего не хватает. Не-Spring приложения эти переменные игнорируют.
Деталь, из-за которой всё почти не сработало
Spring сопоставляет переменные окружения с настройками по правилу, которое легко перепутать. Настройку minimum-idle нужно записывать как SPRING_DATASOURCE_HIKARI_MINIMUMIDLE — дефисы убираются, слова слитно. Если записать её как SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE, что выглядит естественнее, она сопоставится с другой, несуществующей настройкой (minimum.idle) и будет тихо проигнорирована.
Такая ошибка проходит любую проверку и ничего не меняет в продакшене. Поэтому тест проверяет и то, что значения заданы, и то, что ни одна переменная не записана в форме с подчёркиваниями.
Исправление, часть 2: все остальные стеки
Spring — не единственный фреймворк с пулом, и мы не можем заранее настроить каждую библиотеку на каждом языке. Поэтому AI-агент Mythex, который пишет ваше приложение, теперь получает постоянную инструкцию для любого стека: опубликованные приложения и их базы засыпают при простое, поэтому соединение, которое было открыто, на следующем запросе может оказаться мёртвым. Агент должен:
- не держать открытыми простаивающие соединения с базой или проверять каждое перед использованием,
- однократно повторять запрос при ошибке соединения и
- использовать настройки базы, которые платформа передаёт в окружении, а не создавать собственный пул, разбирая
DATABASE_URL.
Последний пункт замыкает цикл с частью 1: приложение, построенное на предоставленных настройках, автоматически получает конфигурацию, устойчивую ко сну.
Результат
По измерениям на платформе хостинга, где мы запускаем опубликованные приложения, время первого запроса после пробуждения сократилось с 12,7 секунды до 2,3 секунды.
Что мы из этого вынесли
- Каждый слой, который засыпает, должен соглашаться, что переживёт сон. Замёрзшее приложение и спящая база каждая сделали разумный выбор. Вместе же приложение проснулось, держа состояние, которое уже не соответствовало действительности.
- Значения по умолчанию, рассчитанные на серверы, которые всегда работают, не подходят приложениям, которые масштабируются до нуля. Держать соединения тёплыми — хорошая идея, пока всё вокруг них не остыло.
- Отдавайте фреймворкам данные в их собственном формате. Один URL был простым для нас, но заставлял каждое Spring-приложение вручную собирать пул, который игнорировал собственную конфигурацию.
- Проверяйте, что настройка записана так, как её читает фреймворк. Правильное значение под неправильным именем ничего не делает — и никак об этом не сообщает.
Если вы впервые работаете с базами данных в Mythex, см. документацию по базам данных и наше объяснение что такое Postgres.