База данных - ключевая часть практически любой инфраструктуры. Ее потеря может привести к потере всего бизнеса. Обеспечить бесперебойную работу базы сложно, для этого требуются квалифицированные администраторы, умеющие хорошо настраивать, мониторить, бекапить, восстанавливать и масштабировать.
Задача настолько не тривиальная, что компании стараются свести свое участие в управлении базами к минимуму. Там где возможно, используются готовые облачные решения от Amazon, Google, DigitalOcean и других. Облачная база данных предоставляется как сервис, который умеет автоматически делать бекапы и восстанавливать их, обеспечивать отказоустойчивость за счет автоматического переключения на запасную копию базы и многое другое.
Даже если база данных управляется самостоятельно, она все равно должна находиться на выделенном сервере. Почему это важно:
- База данных конкурирует с приложением за ресурсы сервера и в итоге тормозят все
- У баз данных свой тип нагрузки, под который нужны сервера со специфическими характеристиками
- К базам данных предъявляются гораздо более высокие стандарты по надежности и безопасности
- Программисты любят ходить на сервера с кодом и могут случайно удалить базу данных
- Без этого невозможно горизонтальное масштабирования
Миграции
Миграции базы данных это изменения ее схемы и данных, которые возникают во время разработки. Когда разработчику нужно добавить, удалить или изменить поля в таблице, он создает миграцию. Создание таблицы это тоже миграции, как и любые другие действия над базой данных.
CREATE TABLE users (
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
first_name VARCHAR(100),
email VARCHAR(100)
);
Миграции часто хранят в sql-файлах. Некоторые фреймворки вместо SQL дают свой собственный язык, который упрощает их написание:
# Во время работы превратится в SQL
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
Во время деплоя, новые миграции "накатываются" на базу данных с помощью команды используемого фреймворка:
bin/rails db:migrate
Бывают ситуации, когда миграции нужно откатить. Например, во время разработки, когда разработчик написал и выполнил миграцию, он, вдруг, понимает что ошибся. В таком случае он может выполнить откат миграций:
# Обычно команда отката откатывает только последнюю миграцию
bin/rails db:rollback
# Чтобы откатить еще раз, нужно повторить команду
bin/rails db:rollback
Откат миграций не работает сам по себе. Для отката нужно знать как это сделать. Для этого каждая миграция состоит из двух частей: up, в которой описывается движение вперед и down, в которой описывается движение назад:
# Меняем тип колонки price с integer на string
class ChangeProductsPrice < ActiveRecord::Migration[7.1]
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
Но нужно учитывать, что миграции могут приводит к потере данных. Удаление колонок и таблиц - очень опасные операции, которые надо выполнять с большой осторожностью.
Деплой
В какой момент применять миграции? Обычно, миграции применяют во время запуска новой версии приложения в конце деплоя. Это гарантирует минимальное время простоя. Весь процесс выглядит так:
- Приложение устанавливается на все сервера
- Останавливается старая версия приложения
- Накатываются миграции
- Запускается новая версия приложения
Почему миграции накатываются после остановки старой версии, а не до? Миграции могут ломать работу старого кода. Например удаление таблицы, которая использовалась раньше, но после деплоя будет не нужна. Если накатывать такие миграции до остановки, то пользователи увидят ошибки 500, что плохо.
С другой стороны, иногда миграции накатываются очень долго, десятки минут, часы и даже дни. В таком случае их выполняют до деплоя. Но как это сделать, если возникает ситуация с удалением таблицы как было описано выше? А что с откатами миграций при ошибках после или во время деплоя? А как восстановить данные?
Все эти вопросы связаны между собой. Они появляются из-за нарушения обратной совместимости схемы базы данных. То есть новая схема базы данных работает с новой версией приложения и не работает со старой. Единственный способ разрешить эту ситуацию - не ломать обратную совместимость. Вносить только такие изменения, которые расширяют текущую схему, оставляя ее рабочей для предыдущей версии. Как? Не переименовывать, не удалять, менять колонки только в сторону расширения, а не сужения. Увеличивать размер текстового поля можно, уменьшать нельзя.
При таком подходе большая часть проблем уходит целиком:
- Деплой предыдущей версии приложения не требует отката миграций
- Не возникает конфликтов между старой и новой версией
- Данные никогда не теряются
Даже уменьшается время деплоя, за счет другого порядка действий:
- Приложение устанавливается на все сервера
- Накатываются миграции
- Останавливается старая версия приложения
- Запускается новая версия приложения
Zero Downtime Deploy
Бонусом к сказанному выше, мы получаем возможность выполнять деплой без остановки работы. Вы замечали, что крупные сервисы постоянно обновляются, но никогда не останавливают свою работу? Как это технически возможно?
Теперь мы можем ответить на этот вопрос. Для деплоя без остановки, не нужно останавливать старую версию приложения. Сначала поднимается новая и, только, затем выключается текущая. Представьте что у нас есть приложение, в котором есть база данных, балансировщик и две машины с приложением. Вот один из вариантов деплоя:
- Скачиваем новую версию приложения на сервера
- Выполняем миграции с любого из серверов
- Обновляем первый сервер
- Выводим сервер из под балансировщика, чтобы он не отправлял на него запросы
- Останавливаем текущую и запускаем новую версию приложения
- Добавляем сервер в балансировщик
- Повторяем тоже самое со вторым сервером
Без правильной работы с базой данных, деплой без остановки сервиса был бы невозможен.
Самостоятельная работа
-
Создайте базу данных в облачном хостинге
-
Руководствуясь ридми, создайте и задеплойте js-fastify-blog с PostgreSQL в качестве базы данных
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.