Базы данных и миграции
Как класть данные в реляционную базу и доставать их обратно без сюрпризов — и как менять её структуру, когда в production уже лежат строки, рассчитанные на прежнюю.
Почему это важно. База данных — единственная часть системы, которая помнит. Плохой запрос деградирует постепенно ровно до того момента, когда перестаёт; плохая миграция роняет сервис в момент запуска, на самой большой таблице и на глазах у всех.
Что нужно понимать
- Что запрос делает на самом деле, а не что подразумевает ORM
- По какой колонке идёт фильтрация и есть ли на ней индекс
- Сколько запросов порождает один экран
- Что миграция делает с таблицей, в которую прямо сейчас пишут
- Какие гарантии транзакция действительно даёт, а какие вы ей приписали
Основные темы
Запросы
- Фильтрация, соединения и агрегация — и где всё это происходит: в базе или в приложении
- Проблема N+1 и умение заметить её до того, как строк станет много
- Eager и lazy loading: платить только за то, что действительно используется
- Чтение плана запроса — хотя бы настолько, чтобы разглядеть последовательное сканирование
Индексы
- Чего индекс стоит при записи и что экономит при чтении
- Составные индексы и почему порядок колонок имеет значение
- Индексы, которыми никто не пользуется, и как их найти
- Уникальность как ограничение в базе, а не как проверка в коде
Транзакции
- Атомарность на практике: что откатывается, а что нет
- Уровни изоляции — коротко — и аномалии, которые каждый из них допускает
- Взаимные блокировки и привычка держать транзакции короткими
- Работа, которой не место внутри транзакции: отправка писем, вызовы внешнего API
Миграции
- Только вперёд: откат — это тоже новая миграция
- Сначала добавляем: добавить, заполнить, переключить, удалить — каждый шаг отдельным деплоем
- Долгие миграции на больших таблицах и блокировки
- Миграции, которые воспроизводятся с пустой базы
Уровни
| Уровень | Как это выглядит |
|---|---|
| Junior | Пишет запросы, которые возвращают нужные строки. Запускает миграции, сгенерированные инструментами. |
| Middle | Замечает N+1, добавляет индексы под реальные шаблоны доступа, держит транзакции корректными и короткими. |
| Senior | Читает планы запросов, проектирует многошаговые миграции, безопасные на живых данных, и понимает, какую работу стоит отдать базе. |
Практика
Для начала
-
Посчитайте запросы Залогируйте каждый запрос, который делает один экран. Объясните назначение каждого и уберите те, которые объяснить не смогли.
-
Исправьте N+1 Найдите цикл, который выполняет по запросу на каждый элемент, и замените его одним запросом.
-
Добавьте индекс, который что-то меняет Найдите медленную фильтрацию, добавьте индекс и измерьте до и после.
Глубже
-
Прочитайте план Запустите
EXPLAIN ANALYZEна самом медленном своём запросе и объясните каждую строку вывода. -
Мигрируйте по шагам Переименуйте колонку за несколько деплоев, не сломав работающее приложение.
-
Заполните данные безопасно Заполните новую колонку в большой таблице пачками, не заблокировав её.
Проверьте себя
- Какой запрос в вашем приложении самый медленный и откуда вы это знаете?
- Какими из ваших индексов ни разу не воспользовались?
- Что держит открытым ваша самая длинная транзакция и как долго?
- Какая миграция из вашей истории упала бы на таблице в десять миллионов строк?
- Что произойдёт, если миграция оборвётся на середине?
- Где в приложении вы делаете работу, которую база сделала бы быстрее?
Материалы
- Use The Index, Luke — целая бесплатная книга об индексах, написанная для разработчиков, а не для администраторов баз данных. Самое выгодное чтение из этого списка.
- PostgreSQL documentation —
главы про индексы, изоляцию транзакций и
EXPLAIN. Для справочника написано на удивление хорошо. - Designing Data-Intensive Applications — Клеппман о том, что на самом деле гарантируют движки хранения и транзакции и где распределённые системы ломают эти предположения.
- Serverpod: database migrations — как миграции генерируются и применяются в Dart-бэкенде, включая процедуру восстановления, когда они разъезжаются.
- Strong Migrations — написано для Rails, но список небезопасных операций и безопасных замен к ним — это знание о базах данных, а не о фреймворке.