Перейти к основному содержимому

Базы данных и миграции

Как класть данные в реляционную базу и доставать их обратно без сюрпризов — и как менять её структуру, когда в 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, но список небезопасных операций и безопасных замен к ним — это знание о базах данных, а не о фреймворке.