The post has been translated automatically. Original language: Russian
Topic
To change the database schema on an empty project is one line. Changing it on a running service with live users is a separate task, where an error costs several minutes of unavailability or lost data.
What is dangerous on large tables
Adding a column with a default value. In older versions of the DBMS, this is a rewrite of the entire table under lock. New ones are often safe, but you need to check your version.
Creating an index in the usual way. The table is locked for writing until the end of the operation. Almost everywhere there is an option with parallel construction, and it is needed.
Changing the column type. As a rule, it means a complete rewrite of the data.
Deleting or renaming a column at the same time as the code is being deployed. At the time of the rollout, some instances are running on the old version and accessing what is no longer there.
The fourth point is the most underestimated. The problem is not the migration itself, but the fact that the code and schema are not changing at the same time.
A multi-step deployment scheme
To rename a column, the sequence looks like this.
Add a new column without deleting anything.
Embed code that writes to both places and reads from the old one.
Fill in a new column for existing rows in batches rather than a single query.
Switch the reading to a new column and make sure everything is working.
Stop writing to the old one and only then delete it, in a separate release.
Five steps instead of one, but at each stage you can roll back and at no point is there an incompatibility between the code and the schema.
Rules that save nerves
Fill in data with batch files with pauses. One UPDATE of ten million rows creates a long transaction, bloats the log and interferes with the rest of the requests.
Set the timeout to lock. Migrations that couldn't take the lock quickly should drop instead of queuing up and stopping all traffic.
Check migration against copies of real data. On a test database of thousands of lines, everything is always fast.
Keep migrations reversible where possible, and know the rollback plan in advance if not.
Do not combine schema changes and logic changes in one release. If there is a problem, it will be unclear what to roll back.
The main thing
Secure migration is not about tricky SQL, but about the fact that the schema and code are always compatible in both directions. Once this condition is met, the deployment ceases to be an event that the team prepares for in the evening after the traffic.
Do you do migrations during business hours or still at night?
Очередь ставят, чтобы разгрузить основной поток: письма, отчёты, обработка файлов уходят в фон, пользователь не ждёт. Работает это ровно до первого сбоя, после которого выясняется, что сообщение потеряно, обработано дважды или заблокировало всю очередь.
Четыре типичных сценария сбоя
Сообщение обработано дважды. Потребитель успел выполнить работу, но не успел подтвердить получение. Брокер считает сообщение необработанным и выдаёт его снова. Пользователь получает два письма или два списания.
Сообщение потеряно. Подтверждение отправлено сразу при получении, а обработка упала после. Брокер уже считает задачу выполненной.
Одно сообщение блокирует остальные. Задача падает, возвращается в очередь, снова падает, и цикл повторяется бесконечно, забирая ресурсы потребителя.
Порядок нарушен. Событие «заказ отменён» обработано раньше, чем «заказ создан», потому что обрабатывали их разные потребители с разной скоростью.
Что с этим делают
Подтверждать после обработки, а не до. Тогда при падении сообщение вернётся, а не исчезнет.
Делать обработчики идемпотентными. Ключ операции хранится, повторное сообщение с тем же ключом просто игнорируется. Это единственная надёжная защита от дублей, потому что доставка ровно один раз в распределённой системе практически недостижима.
Ограничить число повторов и завести отдельную очередь для неудачных сообщений. Сообщение после нескольких попыток уходит туда и не мешает остальным, а команда разбирает его отдельно.
Не рассчитывать на глобальный порядок. Если порядок важен, его обеспечивают в пределах одного ключа: все события по конкретному заказу идут в один раздел или к одному потребителю.
Держать сообщения маленькими. В очередь идёт идентификатор и минимум данных, а не файл целиком. Тело берётся из хранилища при обработке.
За чем стоит следить
Три метрики закрывают почти всё: длина очереди, возраст самого старого необработанного сообщения и размер очереди с неудачными сообщениями. Длина очереди сама по себе мало о чём говорит, а вот возраст сообщения сразу показывает, справляются потребители или отстают.
Отдельно про очередь неудачных сообщений: она бесполезна, если в неё никто не смотрит. Нужен алерт на её наполнение и понятный способ переотправить исправленные сообщения обратно.
Итог
Очередь не убирает сложность, она переносит её в другое место. Взамен вы получаете устойчивость к пиковой нагрузке и независимость сервисов, но платите идемпотентностью, обработкой повторов и отдельным мониторингом. Это осознанный обмен, и лучше сделать его заранее, чем во время инцидента.
А как вы боретесь с дублями: ключ идемпотентности, проверка состояния перед действием или полагаетесь на брокера?
Изменить схему базы на пустом проекте это одна строка. Изменить её на работающем сервисе с живыми пользователями это отдельная задача, где ошибка стоит нескольких минут недоступности или потерянных данных.
Что опасно на больших таблицах
Добавление колонки со значением по умолчанию. В старых версиях СУБД это перезапись всей таблицы под блокировкой. В новых часто безопасно, но проверять нужно именно свою версию.
Создание индекса обычным способом. Таблица блокируется на запись до конца операции. Почти везде есть вариант с параллельным построением, он и нужен.
Изменение типа колонки. Как правило означает полную перезапись данных.
Удаление или переименование колонки одновременно с деплоем кода. В момент раскатки часть инстансов работает на старой версии и обращается к тому, чего уже нет.
Четвёртый пункт самый недооценённый. Проблема не в самой миграции, а в том, что код и схема меняются не одновременно.
Схема развёртывания в несколько шагов
Для переименования колонки последовательность выглядит так.
Добавить новую колонку, ничего не удаляя.
Задеплоить код, который пишет в оба места и читает из старого.
Заполнить новую колонку для существующих строк пачками, а не одним запросом.
Переключить чтение на новую колонку и убедиться, что всё работает.
Прекратить запись в старую и только после этого удалить её, отдельным релизом.
Пять шагов вместо одного, зато на каждом этапе можно откатиться и ни в один момент нет несовместимости между кодом и схемой.
Правила, которые экономят нервы
Заполнять данные батчами с паузами. Один UPDATE на десять миллионов строк создаёт длинную транзакцию, раздувает журнал и мешает остальным запросам.
Ставить таймаут на блокировку. Миграция, которая не смогла быстро взять блокировку, должна упасть, а не встать в очередь и остановить весь трафик.
Проверять миграцию на копии реальных данных. На тестовой базе из тысячи строк всё быстро всегда.
Держать миграции обратимыми там, где это возможно, и заранее знать план откага, если нет.
Не совмещать в одном релизе изменение схемы и изменение логики. При проблеме будет непонятно, что откатывать.
Главное
Безопасная миграция это не про хитрый SQL, а про то, что схема и код всегда совместимы в обе стороны. Как только это условие соблюдено, деплой перестаёт быть событием, к которому команда готовится вечером после трафика.
А вы делаете миграции в рабочее время или всё ещё по ночам?