Не используйте MongoDB

Раз за разом встречаю case, в котором разработчики сначала выбрали MongoDB, а спустя 3..5 лет привычно слизывают иголки кактуса. При этом после стольких лет эксплуатации миграция в другую БД будет ещё большим кактусом, потому… так и живут, в общем.
Так вот, если выбор MongoDB вам кажется хорошей идеей (в контексте большого, живущего долго и обильно сервиса), подумайте ещё раз. И ещё. И ещё раз пять, обсудив желание с теми, кто уже получил достаточно долгий опыт. Убедитесь, что вы и в самом деле понимаете, во что вляпываетесь. Только тогда используйте.
А если вы привыкли к инструментам и возможностям большой тройки RDBMS (Oracle, PostgreSQL, MySQL), откажитесь от «хорошей идеи» сразу, минуя стадию размышлений и обсуждений.

Сначала о хорошем.
MongoDB отлично подходит для хранения и извлечения древовидных структур данных. Иначе говоря, ровно для того, от чего первоначально поднялся hype — откуда-то прилетает развесистый JSON, а вы его целиком после минимальной конвертации фигак-с в базу, затем целиком фигак-с из базы. Пишется довольно быстро, забирается ещё быстрее. Уря-уря.
А вот дальше всё не так хорошо. Накидаю разбросом.

Умеете ли вы и ваши разработчики жить в условиях анархии? Это когда ограничений нет не только у гениальности и сознательности, но также у глупости, невежества и пофигизма. У MongoDB нет схемы на уровне базы данных. В терминах RDBMS… скажем, у вас таблицы без FK, триггеров, без типов данных. Зато есть разработчики, которые хотят на ручки и смузи, чтобы было меньше работы. Скучной, нудной, требующей кропотливости и тщательности. Ну не любят нежные зайки таблицы проектировать и по крупицам что-нибудь проверять. А тут такая щедрая MongoDB.
Прошло 3 года. У вас 50GB data size, 50 collections и 100M objects. Вася уволился в первый год. Петя ушёл в запой на второй год. Игнат в третий год устроил рефакторинг (лучше б пельмени лепил). Вопреки всем рекомендациям и увещеваниям у коллекций есть «виртуальные» связи между собою (те же foreign keys, но без них). Угадайте, насколько целостными являются данные? Угадайте, какие варианты значений могут быть у какого-нибудь поля (попутно угадайте, всегда ли это поле вообще есть)? Именно угадайте. А если вам не нравится угадывать количество дыма без огня в production, вы озадачитесь мегатонной кода, который обойдёт каждый документ, заглянет в каждое поле и сверит хотя бы варианты «у нас нет яблока в ведре гвоздей». Прогноз результата зависит от того, насколько вы доверчивы и наивны. Разработчики обязательно всё делали хорошо, правильно и не в режиме «быстро-быстро говнякаем, нам надо срочно в прод». Конечно. Как иначе-то.

Любите ли вы транзакции? Нет? MongoDB тоже не любит. Безусловно, можно подкрасить глаза кровью и на основе Atomicity and Transactions соорудить что-нибудь похожее, но к нормальным транзакциям вы только приблизитесь.
Чревато это весьма занятными эффектами, когда в процессе записи чего-нибудь в две разные коллекции ваше приложение ляжет. Или MongoDB ляжет. Или сеть пропадёт. В общем, произойдёт событие, которое прервёт запись десяти примет преступника в розыске на второй примете. Есть люди, которых это не волнует. Как привык уже думать, у каждого своя трактовка качества продукта. Зато те, кто озадачится темой «чё дальше делать», обнаружат, что механизмов rollback’а нет. И либо те же разработчики (вспомним, эти ребята не хотели тратить время на рутину) будут костылить в приложении откаты и докаты данных с попутной проверкой целостности, либо живите на мине с часиками. Однажды стопудово рванёт.

Выразительность языка запросов в MongoDB несколько хромает. Как и эстетика. Верю, что кому-нибудь может нравиться {size: {$ne: 0}} вместо size <> 0, но общего с такими людьми иметь не хочется.
Ладно, эстетика. Самый сладкий צימעס в том, как удобно работать с большими запросами. У вас адовый JSON на экран. Месиво из фигурных скобок, долларов и двоеточий. Удобно грепать. Удобно парсить глазами. Удобно писать меленькие конструкторы для запросов (как всё ещё пишут иногда для SQL). Шучу. Удобство нулевое.
Что любопытно, несколько лет назад в сети был движняк, показывающий примеры, как няшно выглядят запросы MongoDB в сравнении с SQL. Мол, вот тут одна строчка вместо этого ужас-ужас, пользуйтесь нашим сахарком. И да, в тех примерах вроде бы всё верно. Вот только существует масса примеров с обратной стороны, когда привычный и семантически эквивалентный SQL-запрос превращается в неудобоваримый ужас-ужас.

Недавно один товарищ не смог ответить на простой вопрос: что такое [де]нормализованные данные? Так вот разницу между этими состояниями легко можно познать в процессе миграции из одного в другое. Нормализованные (если поняли, что несколько ошиблись в выбором RDBMS и надо другое) катнуть в какую-нибудь NoSQL достаточно просто. А вот из MongoDB в SomethingSQL… ха-ха, в цирке смешно уже не будет никогда. Нет, правда. Меня всякий раз так радуют восхищённые ребята, лишившие себя нужды вносить строгость в виде схем данных, проверок и прочего. Счастья им и здоровья, ждёт море открытий чудных.

Оно жрёт память. Как не в себя. Что RAM, что HDD/SSD. И если на винт ещё как-то ладно бы (всё равно бывает грустно, впрочем), то куда ж ты, зараза такая, перевариваешь оперативку? Начинаешь гуглить. Начинаешь читать совсем уж дебри мануалов. И обнаруживается, что даже WiredTiger всё, везде и всегда кладёт в RAM. Ну а чё? Кто в школу для взрослых ходил, знает, что это прям вот почти самая быстрая доступная память (не в регистры процессора же класть), давайте использовать её. Использует. Так, что у OOM killer порою глаз дёргается. Я, конечно, могу у WT ограничить cacheSizeGB, но станет ли MongoDB хорошо? Обжора, которому желудок ушили, вряд ли радостен.
На дисках, всё-таки к слову, тоже могло быть шоколаднее. Живёт базка. Кушает винт. Кушает, кушает. Решаете вы в коллекции на 500M документов грохнуть половину. Грохаете. Уря? Неть. MongoDB не отпустит место на винте. Чтобы отпустила, надо выполнить compact, который блокирует всё, до чего дотянется. Запланируйте даунтайм всего, это же так интересно.

Отдельное удовольствие причиняет поиск и устранение причин, по которым запрос работает медленно. Тут не могу удержаться и не упомянуть прекрасный принцип, по которому WiredTiger решает, как всё-таки выполнять запрос (индексы и т.д.) — он его сначала выполняет.
Так вот. Даже если предположить, что вы научились правильно читать результат explain, дальше-то что? Если в RDBMS особенно упорные бойцы вспомнят реляционную алгебру, достанут пыльный том Дейта и пятистраничный запрос зажмут в тиски оптимизации, то MongoDB-бойцы как выкрутятся? Обычно выкручиваются прям вот бодро. Сначала создают дополнительные индексы. Ну вдруг поможет. Не помогает. Потом разбивают запрос на несколько запросов (один фиг привычно по временам, когда в MongoDB ничего близко рядом с JOIN не было). Не помогает. Потом дробят свои милые толстеееенные документы с десятками уровнями вложенности на менее вложенные. Чуть-чуть помогает, но подозрительно напоминает схему RDBMS. Потом топятся в ближайшем пруду.
Ибо запихнуть вековые деревья в базу ума большого не надо. А вот обеспечить быстрый поиск с какой-нибудь агрегацией по этим деревьям — оно порою до кандидатских доходит. Но вы же хотели не кандидатскую, а пыщ-пыщ и ололо, да?

В финале не могу пройти мимо механизма локов. Очень советую почитать FAQ: Concurrency, очень. Обратите внимание на ситуации, в которых лочится вся база. Прочтите страницу ещё раз. Потом обратите внимание на то, как лочатся документы (они ведь у вас толстенькие и вы частенько внутри документа добавляете / удаляете, да?). Ещё раз перечитайте. И ещё. До полного просветления. Даже пересказывать не буду, это надо пробовать лично.

Существуют проекты, у которых получилось приготовить MongoDB правильно, при этом в базе прям вот хотя бы middle data, над которой middle load. Там диктатура, сплошь настоящие senior’ы и всё по заветам. Такие проекты рапортуют об успехах, хоть сейчас разводись и женись на MongoDB. Но чёт вот наблюдение за действительностью (от личного опыта до яростных репортов в рассылках) показывает, что в общем случае MongoDB сначала надо учиться готовить. Учиться истово, системно, долго. С умным анализом, с прогнозами в горизонт до пяти лет. Тут можно закрыть глаза и пофантазировать о том, как разработчики читают учебники с мануалами, грызут курсы MDBU, да ещё и сертификацию сдают… Ага.
Уф. Ну вроде по верхам вот так. Удачи отважным.

Азы проектирования схемы данных

Для того, чтобы быть нормальным разработчиком, надо (уверен) среди прочего принимать и понимать следующие истины:

  • Программы создаются для работы с данными. Без данных нет программы, нет пользы. Данные — основа всего.
  • Без хорошо спроектированной схемы данных в базе данных говорить о качестве продукта нет смысла.
  • Проектирование — задача, при решении которой учитываются не только текущие и сиюминутные требования, но также и требования, что могут возникнуть.

Искренне считаю, что у желающего оспорить эти три утверждения как были проблемы, так и будут.

Также на фоне современности с её NoSQL следует понимать и такое: у вас нет schemaless database, оставьте этот термин маркетологам. Ещё на заре изучения таких баз в учебниках писали, что схема с database level перешла на application level. Перешла. Не пропала. А если вы с азартом используете ту же MongoDB в режиме грабьубивай, в итоге обязательно окажется, что грабилиубивали себя же. Исключений не видел, зато читал миллиард рассказов о том, как всё заканчивалось плохо.
И последнее стратегическое правило. Относится к голове. Вы проектируете контейнер для данных, который позволит удобно оперировать этими данными. Вы НЕ решаете конкретную задачу. НИКОГДА, блин, не решайте только одну конкретную задачу в этой области. Вот аналогией ща.

Дано: деревенская семья выращивает груши, нуждается в ящиках для хранения и транспортировки. Вы такой ящик проектируете. Что разумное существо прикинет?
Во-первых, делать ящик именно для груш… нет. Завтра посадят яблони, послезавтра персики. И что, устраиваем зоопарк и конкурсы проектировщиков? Помните, что источники данных для ваших таблиц / коллекций со временем будут меняться. Не кардинально, но шаг влево-вправо всегда будет. Потому так же шатайте требования, старайтесь проектировать хранение для класса (фрукты), а не для экземпляра (груши).
Во-вторых, фруктов будет много. То, что сейчас вам кажется (или по ТЗ), что ящика достаточно… ваше «кажется» значит ровно ноль. Делайте ящик с учётом следующего: груш будет миллион, яблок пара сотен тысяч, и даже два мешка огурцов сбоку. Сегодня выращивает одна семья, завтра колхоз, послезавтра корпорация (в контексте ящиков это означает, что следует предусмотреть удобство хранения ящиков на складах, транспортировку сухогрузами и т.д.). Ваше решение должно быть масштабируемым «горизонтально». Типичная и обычная ошибка проектировщика заключается в недооценке количества данных. В худшем случае это приводит к написанию приложения с нуля. В обычном случае это приводит к нагромождению костылей, лишь бы запрос работал не сутки. Проверяйте свои идеи на миллионах, десятках миллионов строк / документов. Влейте синтетические данные и доведите базу до смерти, чтобы выяснить реальный практический предел. Он же и будет пределом применимости того, что вы сделали.
В-третьих, поставьте себя на место крестьянина. Для чего ему нужен ящик? Какие операции будут проводиться в первую очередь? Поднять, поставить, перенести, утилизировать, починить. Какие операции могут понадобиться? До чего крестьянин / колхоз / корпорация могут дойти через год? А через два? Так же и с базой. Как минимум, надо учесть все базовые кейсы (тут уже зависит от вашей головы, опыта и знаний). Не должно быть проблемой отработать MIN / MAX. Не должны JOIN’ы с поставщиками яблок выполняться часами. Фруктовая сумма за пять лет не должна укладывать базу на пол. Поймите следующее: были бы данные, а запросы найдутся. То, что сейчас крестьянин не будет делать аналитические срезы по грушам, вовсе не значит, что в будущем аналитики корпорации не захотят годовых отчётов. Ваша задача в том, чтобы это уже сейчас работало быстро и правильно. Тех, кто говорит «этого не будет», к проектированию не подпускайте. Будет ВСЁ.
Резюмирую:

  • Проектировать под решение класса задач, а не задачи.
  • Проектировать масштабируемое решение.
  • Проектировать не вещь в себе, но с учётом кейсов как обычных [для всех баз], так и текущих пользовательских, так и будущих.

Отдельная и часто забываемая тема — удобство интеграции и сопровождения. Вы спроектируете отличную схему данных и получите через год ворох проклятий, если забудете, что в мире существует нужда в миграции данных, в удобстве для ORM, в дампах (их размере), в создании и в удалении схемы. Также существуют некоторые гигиенические нормы вроде нормализации. Вас очень будут любить разработчики, которые через год обнаружат, что в ряде таблиц данные рассинхронизировались и потому в одном случае у Пети два сына, а в другом собачка. Такими косяками обычно страдают не занимавшиеся разработкой проектировщики. Не надо такими быть.

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

ClickHouse: Зачем

Два англичанина ловят рыбу в Темзе. У одного дергается поплавок, он подсекает и вытаскивает прелестную русалку. Полюбовавшись ею, снимает русалку с крючка и бросает обратно в воду. Второй удивляется:
Но почему?
Но как?

ClickHouse — штука коварная. В первый час мануалов и докладов думаешь «ого… ого! дайте две! нет, три! десять!» Потом узнаёшь про ограничения и глубоко задумываешься и впадаешь в уныние.
Самое важное, что следует запомнить и понять — эта база данных делалась не для вас и не для ваших задач. Заказчик и потребитель, как понимаю, — Яндекс.Метрика. Потому на все вопросы категории «а почему нет такой-то фичи?» ответ один: потому, что это не требуется Яндекс.Метрике. Весь список ограничений ниже упирается в этот ответ. А вот если ваша задача схожа с их задачей (наливать жополиард логов и натравливать аналитиков), всё будет хорошо.
Потому не получится взять свою базу, лежащую в каком-нибудь MySQL / PostgreSQL / OracleSQL, и плавно перелить в ClickHouse. Хоть убейтесь. И логику вашего приложения не получится так перенести. Вообще ничего не получится сделать плавно. Схемы данных надо будет заново спроектировать, приложения заново написать.

Во-первых, в ClickHouse используется свой диалект SQL. Можете выкинуть все свои ORM, например. А если возникнет светлая идея пропатчить и подпилить, эту идею тоже выкидывайте, т.к. не просто диалект SQL, но очень обрезанный диалект.
Во-вторых, судя по замерам тех, кто уже попробовал, и в самом деле заливать данные надо пачками (от тысячи строк за операцию), иначе всё работает очччень медленно. Соответственно, перед базой требуется костылить буферизацию. Если игнорировать это правило, можно получить здорово просевшую и не отвечающую на запросы базу. А, ну и да, рекомендуют не более одного такого INSERT в секунду.
В-третьих, нет UPDATE и DELETE. Единственный прямой способ удалять записи заключается в удалении целых разделов записей помесячно (через DROP PARTITION). Соответственно, все варианты запросов, в которых могло фигурировать изменение данных, тоже не работают. Жалею, что не был на встречах, на которых будущие авторы ClickHouse говорят друг другу что-то типа «неее, ну нам не надо изменять данные, фича явно не первой нужды». Правда, вроде как хоть какая поддержка заявлена на реализацию в 2018 году. Но вообще жесть, конечно. В базу можно заливать только стабилизированные данные, например, логи. А если вы собираетесь лить статистику от того же Яндекс.Директа (которая может уточняться в любой момент, особенно последние три дня), привет костылям.
В-четвёртых, нет NULL. Вернее, их нет в привычном виде. Будут в конце 2017 года. И снова вызывает некоторое недоумение эта лакуна. Словно сознательно выпиливали из ТЗ ровно то, что помогло бы мигрировать на ClickHouse. Хотите большего веселья? Ахаха, у нас нет NULL!
В-пятых, нет коррелированных подзапросов. Учтите, если у вас работа с базой сложнее среднего. С другой стороны, over dofiga приложений с базой работают на уровне «сохрани яблочко, верни яблочко», потому недостаток для тех, кто.
В-шестых, репликация есть, но (если не углубляться в детали) на уровне данных, а не схемы. Иными словами и привычными терминами, если на master дропнут таблицу, slave и не почешется (на деле в ClickHouse репликация multi-master, потому slave тут для упрощения). Также занятный нюанс: репликация через ZooKeeper. Если он вдруг ляжет, ваши таблицы превратятся в read-only тыкву.
В-седьмых, множество других ограничений. Если вы используете движок таблиц, отличный от *MergeTree, внимательно читайте мануал, часть фич (от индексов до ALTER) может отсутствовать. Если используете USING, внимательно читайте мануал, многое обрезано. UNION DISTINCT не поддерживается. В общем, хождение по минному полю.

Такие вот дела. Как обычно, полного счастья не бывает.
Хочется надеяться, что быстрым ClickHouse получился не потому, что авторы отказались от того, что есть в других базах и вместо «мы сделали самую быструю базу» надо читать «мы сделали самое быстрое чтение неизменяемых данных». Хочется поиграть в ван Винкля и проснуться эдак в 2019 году, когда Яндекс и сообщество превратят ClickHouse в более универсальный продукт. Но нет. Надо сейчас, потому посты о вкусе кактуса не за горами.

ClickHouse: Начало

Предположим, у вас есть данные. Большие данные. Ну или не очень большие — сто миллионов документов с парой сотен свойств уже норма. Норма также и то, что вы немножко утомитесь делать какую-либо быструю аналитику / агрегацию этих данных в «обычных базах данных». В необычных тоже утомитесь, если речь идёт о миллиардах документов / записей. И тут на сцену выходит ClickHouse.
Большая часть ссылок есть на сайте ClickHouse, часть я выловил гуглением. Выловил больше, но отфильтровал бесполезные (слишком мало информации или слишком маркетинговая или слишком клон другого текста).

Пока с мануалами не очень, есть только один официальный.
Статей особо нет, книг нет, ничего альтернативного толком нет.
Мануал — очевидно, must read. Кто мануалы не читает, сам себе буратинка.
Репозиторий — стоит посматривать, чтобы понимать, какие баги, куда идёт прогресс, ну и упороться в плюсовый исходник, если спать не хочется.
Google Groups — в среднем несколько сообщений в день, есть интересное. Хороший запасной вариант поиска решения проблем и ответов на первые вопросы.
wiki/ClickHouse — можно сказать, заметно более развёрнутый вариант этого поста, попутно пересказ части документации. Больше ссылок.
stackoverflow.com/clickhouse — очень вялое место без заметного движняка. Десятка три вопросов, большинство с одним ответом.

Каких-либо особо полезных для разработчика видео тоже в открытом доступе пока не существует. Вот ваще ничего, прямо скажем. С трудом наскрёб четыре ролика.
ClickHouse — как сделать самую быструю распределённую аналитическую СУБД — ноябрь 2016 года, часовой доклад от одного из авторов о том, почему и зачем придуман и сделан ClickHouse. Посмотреть стоит для того, чтобы не видеть в ClickHouse ещё одну RDBMS или Cassandra или Hadoop. Также немного истории, архитектуры, бенчмарков и т.п.
ClickHouse — прошлое, настоящее, будущее — март 2017 года, 40 минут основной коммитер ClickHouse докладывает об успехах разработки за прошедший квартал. Можно не смотреть, видео с практической точки бесполезно, но можно и посмотреть, зарядиться позитивом.
ClickHouse визуально: Быстрый и наглядный анализ данных в Tabix — апрель 2017 года. 33 минуты смеси из 1) описания проблемы, 2) истории успеха, 3) беглый обзор фронтов к ClickHouse, 4) описание своего решения. Хороший, годный доклад.
ClickHouse Yandex — Анализируем данные — январь 2017 года, Сигач(е|ё)в А. в Орле делится опытом перехода с MySQL на ClickHouse, всего 25 минут. Больше подходит в истории успеха, обычный для многих кейс — привычные базы не справляются даже с объёмами средней руки (пусть даже они смешные на фоне big data от больших игроков), а надо гонять миллионы строк, при этом не раскатывать мегатонну серверов. Скучновато, лаконично, но на безрыбье.

Истории успеха — специальный жанр «документации». В нём можно найти как мотивацию с аргументацией (вот! вот! вот у чуваков такой же кейс, у них всё получилось, а мы живём на гуано!), так и описание граблей, по которым прошлись первограблепроходцы. И если вам мало того, что на базе ClickHouse живёт Яндекс.Метрика, то вот ещё истории.
Переезжаем на Yandex ClickHouse — декабрь 2016 года, доклад на HighLoad++. Если сподручнее читать, на Хабре есть расшифровка.
О ClickHouse — 140 миллионов записей. Сравнение с InfluxDB и с PostgreSQL. В финале три полезные ремарки о граблях.
Как запустить ClickHouse своими силами и выиграть джекпот — десятки миллиардов записей. Сравнение с InfluxDB, Cassandra и Druid. Примеры работы, упоминание своего софта и т.д.

PS. Позабавила статья. Хороший пример того, как из обычной практики обычных разработчиков / девопсов (ну и текст обычный — развернуть софт) может появиться вот прям СТАТЬЯ за тройным авторством — [Феофантов К.В., Терских М.Г., Афанасьев Г.И. Развертывание кластера для хранения и обработки статистики с помощью Yandex Clickhouse // Современные научные исследования и инновации. 2016. № 12]. Может, кому полезным будет зачем-то.