[Из песочницы] Антипаттерны работы с базами данных

Привет, Хабр! Представляю вашему вниманию перевод статьи «Database: Anti-Patterns» автора Sergey Kozlov.

Если вы храните данные, это критически важная часть вашего приложения. Можно легко и быстро задеплоить исправление бага на новом сайте знакомств, чтобы фермер Джо из северного Техаса наконец-то смог прочитать последнее сообщение от любовницы по переписке и узнать, что ей нравятся лысые мужчины. Но упаси вас бог потерять или испортить пользовательские данные.

image
Кремниевая долина, 2 сезон, 8 серия.

Тем не менее, многие разработчики не вполне понимают эту простую истину. Я не так уж много лет профессионально программирую, но уже повидал много, очень много ошибок, которые совершают люди, работая с БД.

Вот лишь те, что сразу приходят в голову.

Отсутствие бэкапов


image

«Делайте бэкапы» — одно из таких правил (вроде «не работай под рутом» или «пристегивайте ремни»), с которыми многие из нас согласны, но не следуют им, надеясь, что плохие вещи случаются с другими, а не с нами.

Кстати, если вы не тестируете восстановление из бэкапов, можете считать, что бэкапов у вас нет вообще. Учитесь на чужих ошибках:

Другими словами, из пяти задеплоенных техник резервного копирования / репликации ни одна не работает надежно или не настроена. В конце концов, мы восстановили данные из бэкапа, сделанного 6 часов назад.
Мы потеряли данные базы за 6 часов (issues, merge requests, пользователи, комментарии, сниппеты и др.) с GitLab.com.

NoSQL


Бывает, что у ваших пользователей слишком много контента для взрослых и они слишком часто его смотрят объем данных слишком велик или нагрузка слишком высока, чтобы реляционная БД могла справиться. Это тот случай, когда в игру вступают NoSQL-технологии. Софтверные гиганты вроде Google знакомы с этим не понаслышке.

Но вы не Google. Несколько сотен гигабайт — это не «big data», а 1000 комментариев в день — не «high load». Скорее всего, для ваших данных вполне достаточно PostgreSQL. Смотрите: он даже поддерживает JSON и умеет его индексировать.

Бросьте, вы серьезно хотите пожертвовать надежной структурой ради фич, которые вам не нужны и — посмотрим правде в глаза — никогда не будут нужны? Вы не станете новым Гуглом — у вас просто будет бардак в базе.

Слишком свободная схема


Это более актуально для NoSQL, но пользователи реляционных СУБД зачастую забывают или ленятся создавать все нужные ограничения. Из-за ошибки в коде приложения NULL может быть сохранен там, где ожидается осмысленное значение, или может быть создана ссылка на отсутствующую запись. Впоследствии вы это замечаете и исправляете код, но понятия не имеете, как исправить данные.

Естественные первичные ключи


Представим, что мы хотим хранить пользователей, у каждого из которых должен быть уникальный e-mail. Самое очевидное решение — создать таблицу user с колонкой email, которая будет еще и первичным ключом.

К сожалению, естественный ключ может стать неприемлемым в качестве первичного при изменении требований (а они меняются постоянно). Сегодня PRIMARY KEY(email) работает, а завтра мы решаем добавить регистрацию через Facebook и сделать e-mail необязательным. Что лучше: генерировать уникальные адреса и добавить флаг, обозначающий фиктивный e-mail, или изменить первичный ключ, все внешние ключи, которые ссылаются на user, и т.д., и т.п.? Нам не пришлось бы выбирать меньшее из зол, если бы мы просто использовали суррогатный первичный ключ.

Логика в хранимках


Мне это не нравится по двум причинам:

  1. Код приложения обычно намного проще обновить, чем схему БД.
  2. Все эти PL SQL-ли напоминают мне Паскаль, и они такие же уродливые.


Миграционные скрипты, специфичные для конкретных сред


Я знаю, что иногда выбора нет, но в целом лучше стараться, чтобы все среды (dev, test, prod и т.д.) были как можно более похожими. Чем больше разница между средами, тем больше шансов допустить ошибку и обнаружить это только на проде.

Обычно даже DML-скрипты могут быть универсальными. Разные схемы же чаще всего — чистое зло.

Поэтому, когда я вижу в Liquibase-скриптах метки (labels), специфичные для конкретных сред, мне хочется убивать.

Толерантные миграционные скрипты


IF NOT EXISTS и подобные вещи в DDL не нужны, если во всех средах одинаковые схемы, но могут маскировать ошибки. Если во время обновления БД случается что-то неожиданное, я предпочитаю узнать об этом и исправить как можно скорее, а не ломать голову неделю спустя, как исправить бардак.

Неатомарные обновления


Допустим, вы запустили changeset на продакшн-базе, и миграция не прошла успешно. Вы что-нибудь исправляете и хотите попробовать снова. Получится ли? Что если одни операции changeset-а закоммитились, а другие нет?

Вы можете заметить, что на самом деле это история о том, что changeset-ы должны быть идемпотентными, и будете правы.

К сожалению, многие разработчики, думая об идемпотентности, используют IF NOT EXISTS или типа того. В предыдущем разделе я объяснил, почему это зло.

Вместо этого делайте changeset-ы атомарными. Тогда в случае ошибки совершенные изменения будут откачены и у вас не будет проблем с последующим применением этого changeset-а.

Но будьте внимательны, рассчитывая на транзакции. Например, поддержка DDL-выражений в транзакциях MySQL темна и полна ужасов, так что я всегда создаю отдельный changeset для каждого DDL-выражения, когда пишу Liquibase-скрипты для MySQL.

А какие антипаттерны видели вы?

© Habrahabr.ru