SQL против NoSQL
Реляционка держит связи через ACID и схему; семейства NoSQL (документное, KV, wide-column, граф) меняют джойны и сильную согласованность на раскладку под паттерны доступа со scale-out. Senior-выбор — какую цену ты можешь платить, а не что моднее.
Команда запустила соцпродукт на документной базе с одной озвученной причиной: «не хотим возиться с миграциями». Год было блаженство — выкатил фичу, добавил поле, никакого ALTER TABLE. Потом продукт задал вопрос, которого запуск не предусматривал: кто на кого подписан и что в тренде среди их подписок? Это обход графа и агрегация по документам, и хранилище не умело ни того, ни другого, не вытащив половину датасета в приложение и не соединив его руками. Схема, которую они «избежали», не исчезла — она переехала из базы в тысячу хрупких путей кода, каждый из которых обязан помнить форму. Они не выбрали базу; они отложили решение, и счёт пришёл с процентами.
Реляционный дефолт и что он даёт
Через десять минут ты будешь знать, какое именно свойство базы превращает отложенное решение в юридический риск — и какое можно безопасно разменять.
Реляционная база (Postgres, MySQL) хранит данные как таблицы типизированных строк, и связи между ними — первоклассны: ты нормализуешь, чтобы убрать дублирование, а потом собираешь обратно джойнами во время запроса. Движок держит схему (колонки, типы, ограничения) и даёт ACID-транзакции — атомарные «всё или ничего» записи, согласованность против твоих ограничений, изоляцию параллельных транзакций, долговечный коммит. Это не legacy-балласт; это контракт. База гарантирует, что перевод, списывающий с одного счёта и зачисляющий на другой, либо полностью происходит, либо полностью нет; что внешний ключ никогда не висит в пустоту; что две параллельные брони не схватят последнее место одновременно. Этот контракт и делает реляционку правильным дефолтом для всего, где деньги, остатки или важные связи — ты пишешь инвариант один раз, и движок держит его для каждого пути кода навсегда.
Цена в том, что реляционная модель предполагает гибкие, произвольные вопросы — JOIN чего угодно во время чтения — и эта гибкость стоит на масштабе: джойны по шардированному датасету дороги (строки лежат на разных машинах), а один primary, обслуживающий все записи, имеет потолок (урок из 04-data-distribution/02-sharding-and-partitioning). Реляционка отлична, пока объём записей или размер данных не выходят за пределы того, что держит один primary плюс read-реплики.
Семейства NoSQL: четыре разные ставки
Прежде чем потянуться к NoSQL, спроси себя: какой именно паттерн доступа делает реляционный движок не тем инструментом? Ответ определяет нужное семейство — они не взаимозаменяемы.
«NoSQL» — это не одно. Это четыре семейства, каждое оптимизировано под свою форму доступа:
- Документное (MongoDB, DocumentDB) — хранит самодостаточные JSON-подобные документы. Ты денормализуешь: вкладываешь связанные данные внутрь одного документа, чтобы чтение было одним lookup, без джойна. Хорошо, когда сущность читается целиком (страница товара, профиль пользователя).
- Ключ-значение (DynamoDB в простейшем режиме, Redis) — гигантская хеш-карта: дал ключ, получил блоб. Простейшая, быстрейшая модель; никаких запросов внутрь значения. Хорошо для сессий, корзин, lookup по известному id.
- Wide-column (Cassandra, Bigtable) — строки по ключу партиции с разреженными гибкими колонками; построены, чтобы впитывать огромную пропускную способность записи по кластеру без единого primary. Хорошо для лент и пожарных шлангов событий с метками времени.
- Граф (Neptune, Neo4j) — узлы и рёбра как первоклассные граждане, так что обходы («друзья друзей, кому нравится X») дёшевы. Хорошо для социальных графов, схем мошенничества, путей рекомендаций — ровно тот вопрос, на который команда из вступления не смогла ответить.
Смотри на этот список и замечай общее: каждое семейство выигрывает на конкретной, известной форме чтения и проигрывает, когда ты задаёшь вопрос, под который оно не проектировалось.
Объединяющая тема: хранилища NoSQL построены, чтобы масштабироваться вширь на commodity-кластерах и быть быстрыми для известного паттерна доступа, отказываясь от способности реляционного движка отвечать на вопросы, которые ты не запланировал. AWS описывает семейство так же — purpose-built движки, гибкие схемы, горизонтальный масштаб — в обмен на ослабление части гарантий согласованности, которые держит реляционный движок.
ACID против BASE: согласованность, которой ты торгуешь
Самое глубокое отличие — не форма данных, а контракт согласованности. Реляционные системы целятся в ACID. Многие распределённые NoSQL вместо этого предлагают BASE: Basically Available (в основном доступна), Soft state (мягкое состояние), Eventually consistent (в итоге согласована). При BASE запись может быть видна не каждому читателю сразу; реплики сходятся «в итоге» (обычно за миллисекунды). Это не неряшливость — это прямое следствие CAP/PACELC (урок из 04-data-distribution/04-cap-and-pacelc): хранилище, остающееся доступным при сетевом разделении, не может быть ещё и сильно согласованным. NoSQL-хранилища, ставящие доступность и устойчивость к разделению выше, выбирают итоговую согласованность намеренно, и многие (DynamoDB, Cassandra) дают включить более сильную согласованность на запрос за надбавку к задержке и стоимости.
Так что вопрос SQL-против-NoSQL на самом деле: терпит ли твоя предметная область итоговую согласованность на этих данных? Счётчик «лайков», устаревший на 200 мс, — нормально. Баланс счёта, устаревший на 200 мс во время снятия, — это иск.
▸Почему это работает
Почему «взяли Mongo, чтобы избежать схем» так надёжно бьёт обратно? Потому что schema-on-write (проверка схемы при записи, реляционка) и schema-on-read (проверка схемы при чтении, документная база) не убирают схему — они переносят того, кто её держит. При schema-on-write база отвергает кривые данные на входе, один раз. При schema-on-read каждый читатель обязан защитно обрабатывать любую историческую форму, которую данные когда-либо принимали: документ из v1 без поля country, v2, где country была строкой, v3, где она стала объектом. Через пять лет и сорок деплоев твоя «бессхемная» коллекция имеет дюжину неявных форм, а логика валидации размазана по кодовой базе вместо одного объявленного места. Гибкость на записи — это заём под чтение, и проценты платит каждый инженер, кто потом трогает данные.
Дизайн под паттерны доступа: настоящий навык NoSQL
Хорошо использовать NoSQL — это не «реляционка без джойнов», а перевёрнутый процесс проектирования. В реляционном моделировании ты проектируешь данные (нормализуешь в чистые сущности), а потом пишешь любые нужные запросы. В NoSQL-моделировании ты проектируешь запросы первыми: перечисляешь каждый паттерн доступа, который будет у приложения, и затем формируешь хранение так, чтобы каждый был одним дешёвым lookup. Руководство AWS по DynamoDB прямо об этом — ты моделируешь таблицу вокруг паттернов доступа, часто схлопывая много типов сущностей в одну таблицу с тщательно выбранными ключами партиции и сортировки, чтобы запрос возвращал ровно то, что нужно одному экрану, уже соединённым.
Это по-настоящему мощно — так хранилище отдаёт персональную ленту сотням миллионов пользователей с задержкой в единицы миллисекунд. Но у этого жёсткий край: оно работает только если ты знаешь паттерны доступа заранее, и жестоко наказывает, когда появляется новый. Новый запрос, не совпадающий с дизайном ключей, вынуждает либо дорогой полный скан, либо вторичный индекс (больше стоимости, больше итоговой согласованности), либо миграцию данных. Гибкость реляционки — ровно то, чем NoSQL торгует, поэтому NoSQL блестит на хорошо понятных высоконагруженных паттернах и больно кусает на меняющихся, исследовательских продуктах.
Полиглот-персистентность: перестань выбирать одно
Зрелый ответ на «SQL или NoSQL?» обычно — оба. Полиглот-персистентность означает использование более одного хранилища в одной системе, каждое — под то, в чём оно лучше: реляционный primary под заказы и деньги (ACID), key-value кеш под сессии, wide-column под пожарный шланг событий, поисковый движок под полнотекст, граф под социальные рёбра. Цена — операционная: больше систем запускать, мониторить и держать в синхроне, а опасная часть — держать данные согласованными между хранилищами (проблема двойной записи, разбирается в 03-time-series-and-search). Но альтернатива — гнать каждую нагрузку через один движок — гарантирует, что хотя бы для половины из них ты используешь не тот инструмент. Senior-дизайн — про композицию хранилищ, а не коронацию одного.
Финтех-стартап строит леджер, записывающий каждый дебет и кредит между счетами. Схема фиксирована и понятна. Нужны атомарные многострочные транзакции (дебет A, кредит B) и строгая консистентность каждого чтения. Какое хранилище подходит?
▸Частая ошибка
Самая дорогая ошибка SQL-против-NoSQL — выбор под масштаб, которого у тебя ещё нет. Команда читает, что Cassandra отдаёт миллионы записей в секунду, и тянется к ней в первый день — когда у них 50 записей в секунду и ноль понимания своих паттернов доступа. Они купили операционное бремя и жёсткость под паттерны горизонтального хранилища ради проблемы, которой не будет годами, отдав при этом джойны, транзакции и гибкость запросов, которые ранней стадии продукта нужны больше всего (его требования меняются еженедельно). Честный дефолт — одна реляционная база, пока измеренное узкое место — реальный потолок записи, реальная стена задержки — не назовёт конкретное NoSQL-хранилище, которое его решает. Преждевременный NoSQL — это преждевременная оптимизация в одежде модного слова.
Платёжная фича должна гарантировать, что списание с одного счёта и зачисление на другой либо оба происходят, либо ни одно, без промежуточного состояния, видимого другим транзакциям. Какое свойство хранилища тут не обсуждается?
Команда выбрала документную базу «чтобы избежать схем». Два года спустя каждый путь чтения усеян проверками на отсутствующие и разнотипные поля. Что на самом деле произошло?
В реляционном моделировании ты проектируешь данные первыми и запрашиваешь свободно; в NoSQL ты переворачиваешь это — проектируешь _______ первыми, затем формируешь хранение так, чтобы каждый был одним дешёвым lookup, поэтому новый незапланированный дорог.
- 01Назови четыре семейства NoSQL и форму доступа, под которую построено каждое.
- 02В чём обмен ACID-против-BASE и как CAP его навязывает?
- 03Почему дизайн под паттерны доступа режет в обе стороны и что такое полиглот-персистентность?
Реляционка — правильный дефолт: схема и ACID-транзакции позволяют объявить инвариант один раз и заставить движок держать его для каждого пути кода навсегда — это необходимо для денег, остатков и важных связей. Её цена — джойны по шардированному датасету дороги, а единственный primary записи имеет потолок. NoSQL — четыре разные ставки: документное (денормализация, чтение целиком), ключ-значение (хеш-карта, lookup по id), wide-column (огромная пропускная способность записи, без единого primary) и граф (дешёвые обходы) — все построены масштабироваться вширь и быть быстрыми для известного паттерна доступа ценой произвольной гибкости запросов и, обычно, сильной согласованности (BASE, навязанной CAP). Провал «взяли Mongo, чтобы избежать схем» — маркер: схема не исчезает, а переезжает из одной проверки на записи в защитную перепроверку в каждом читателе. Настоящий навык NoSQL — дизайн под паттерны доступа — мощный на известном масштабе, больной при меняющихся паттернах. Senior-ответ редко — одно хранилище: полиглот-персистентность компонует правильный движок под каждую нагрузку, платя операционной поверхностью и межхранилищной проблемой двойной записи — выбирая по измеренной цене, а не по тому, какая база в моде. Теперь, когда на ревью дизайна прозвучит «SQL или NoSQL?», твой первый ход — назвать конкретный паттерн доступа и допустимость итоговой согласованности: эти два ответа сами выберут хранилище.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.