Ошибки выбора MongoDB в качестве основной БД в стартапе

в 22:20, , рубрики: mongodb, python, базы, опыт, ошибки и грабли, Программирование, Проектирование и рефакторинг, проектирование систем, рефакторинг

В этой статье я хочу рассказать о своих ошибках, которые я допустил, когда писал сервис, у которого MongoDB была основной БД для хранения пользовательских данных (да и не только, но об этом ниже).

Я ни в коем случае не считаю, что MongoDB это плохая БД и ее не нужно использовать. Более того, я считаю, что только мои кривые руки завели меня в ситуацию, из которой пришлось выходить переписыванием сервиса под другую БД (ушел на Postgres и кайфую).

Тем не менее, нельзя знать всего и чтение документации не спасает от катастроф во время самой реализации проекта. Особенно, если ваши ожидания от инструмента разошлись с реальностью.

На мой взгляд, маркетологи MongoDB приукрасили области применениях БД на своем сайте. MongoDB не универсальная. Далеко не универсальная и даже не пытайтесь на нее смотреть как на решение всех ваших проблем.

Не использовал ORM или хотя бы DTO

Начну с первой пули калибра 155 мм в ногу. Если после чтения этого блока захочется смеяться и говорить, что "ну это же очевидно", то поверьте мне, я не один такой наивный.

Используя MongoDB очень легко можно наговнокодить, если очень торопиться и не тратить время на написание dataclass или DTO.

Когда я начал писать, я взял pymongo. Без всего. Это показалось мне очень удобным: создал клиента, подцепил конфигурацию и вперед. Можно быстро обращаться к коллекциям и писать в них вообще не думая о схемах данных. Безусловно, если вы один разработчик, то в начале вам не о чем беспокоиться. Сервис маленький, вы один. Весь проект помещается в вашу голову. Какие тут могут быть проблемы?

Через месяц-два вы все-таки начнете допускать ошибки в именах полей. Первый раз это будет выглядеть как простая опечатка, которую вы ищете полчаса. "Почему же это поле не записалось?". Ааа, потому что я чудак, вот почему: user_email вместо просто email. Хорошо, если вы это заметите раньше, чем эта ошибка уедет на продакшн и часть данных создастся с неверным полем. Если это случилось: извольте написать миграцию и перенести данные. Опять-таки: хорошо, если это всего лишь простая ошибка в поле и она не связана с типом данных.

Ошибку я попытался исправить поменяв PyMongo на MongoEngine. Спойлер: сильно легче не стало.

Используйте классы или контейнеры для данных. Пишите валидации данных. Все это очевидно, но уже постфактум. И тесты пишите.

Верил, что без миграций и схем данных в MongoDB будет легче

По-началу кажется, что "инкрементить ценность проекта" будет легче без схем БД. Нет, нет и еще раз нет.

Миграции для развивающегося сервиса это не только про переименовывание полей или добавление новых. Это еще и про целостность и понятность данных. В MongoDB можно записать в одну коллекцию самые разные по структуре документы. Они могут быть вообще разные, ни одним полем (ну кроме id) не похожими друг на друга. И это очень большая проблема.

Я уже молчу про то, чтобы сделать быструю DSL на основе схемы данных, потому что... а нет ее этой вашей схемы данных. Если вам приспичит что-то переделать, то вам придется строить очень сложный запрос (я в итоге честно нагуглил, когда после второго дня сдался), чтобы собрать структуру данных сложного документа, который наполнялся в течение почти года.

Схемы данных — это порядок и дисциплина. Схемы данных — это валидация. Это возможность быстро окинуть данные проекта взглядом и понять как устроен ваша модель или объект. Без схем оооочень больно. Если сюда еще прибавить мою первую ошибку, то представьте как у меня горела задница, когда я переносил все в SQL.

Стрелял в ногу типами данных

Инт или стринг? MongoDB пофигу. У вас может быть два документа с одним и тем же полем, скажем, age, но если на стороне кода приложения ошиблись, то ловите эксепшены. Тысячи эксепшенов и громкий мат из вашего рта среди ночи. Без тестов или DTO в спешке, которая присуща почти любому стартапу, вы будете постоянно ловить эту проблему. А ваша любимая IDE этого может и не заметить (указывайте типы в ваших функциях, ага, знаю).

Думал, что MongoDB быстрая и будет такой всегда

MongoDB действительно может быть быстрой - когда нужно тупо писать и не думать о том, что когда-нибудь придется читать данные или, о боже, делать сложные запросы, где вы захотите сделать aggregate или имитировать join нескольких таблиц коллекций.

Вы где-то читали, что MongoDB экономная в плане потребления ОЗУ? Это вранье. С ростом данных MongoDB будет жрать много ОЗУ. Это зависит сразу от двух факторов: кол-во данных в БД и какого рода операции вы захотите делать.

JOIN это очень дорого, а query по вложенным документам - это гарантия сломать мозги

Авторы MongoDB рекомендуют не использовать монговский JOIN. Он там как бы есть, но он говно. Правильно делать один документ и в нем хранить все связанные данные. Все как бы здорово, но попробуйте потом сделать query по этим самым данным. Сложный query, подразумевающий исключения и логику "или". Это больно, как орбит дверная ручка.

Аналитика — тот еще цирк

Собственно, эта проблема исходит из предыдущей. Посчитать что-то сложнее, чем просто total() - нужно очень сильно заморочиться. Ответить на простые вопросы типа "сколько у нас объектов N для пользователей B, которые входят в категории C,W,Z и делали вот такие-то вещи" в MongoDB превращаются в многоэтажные нечитаемые JSON-подобные запросы, которые трудно поддерживать. Запросы нечитаемые. Можно, конечно же, научить обезьяну курить, но какой в этом смысл? MongoDB не для аналитических данных.

Поиск это тоже цирк с двуногими конями

На сайте MongoDB есть сладкие примеры инвентарок, магазина, коллекций музыки или книг. Они такие классные и такие в вакууме, что в моменте, когда вам нужно реализовать поиск с разными условиями, то волосы на жопе шевелятся. То, что в SQL делается легко даже новичками, в MongoDB превращается в адский ад.

Я тоже думал, что поиск будет легко реализуемым. Таким же простым, как писать запросы на запись без миграций, схем и всего, чем обычно пугают сравнивая SQL и NoSQL базы данных.

Я пробовал уйти от проблем с поиском путем разделения больших документов на отдельные коллекции. Ну знаете, выделить из user его список загруженных документов перенеся их в отдельную коллекцию files. Но это подразумевает, что вам нужно его обратно джойнить, когда он вам потребуется. Но чем больше одновременных запросов к БД, тем ей больнее -- потребление ОЗУ растет как на дрожжах.

Поиск по MongoDB это дорого, сложно и не имеет никакого сравнения с простотой SQL.

От чего еще болело

Одним списком, чтобы не вставать дважды.

  • Рецептов и готовых решений мало или вообще нет

  • Расчеты и встроенные процедуры — забудьте. Без опыта JS это сложнее SQL в разы и дорого

  • Нормальных бесплатных GUI нет (несмотря на то, что я умею в терминал и люблю консольные приложения). Все глубоко урезанные (привет, Robo3T) и все равно заставляют писать запросы руками, которые вновь подчеркну, далеко не сахар как SQL

  • Поддержка платная и оооочень дурная (кто пользовался, тот знает)

Так для чего MongoDB реально подходит?

Все-таки инструментом пользуются. Здесь я перечислил то, как я вижу правильное назначение для MongoDB:

  1. Логи. Старые добрые логи, которые пишут потоком из разных сервисов и читают раз в полгода, если случился инцидент.

  2. Данные, по которым вы никогда не будете искать дальше самого верхнего уровня. Максимум: взять какой-нибудь документ по его ID или другому полю (например, user_id, который вы до этого взяли из другой БД).

  3. Данные, которые вы будете писать большую часть времени разом и одним документом. Редко когда придется менять отдельные поля.

  4. Кэш большого объема, для удобства разбитый по отдельным полям. Его быстро можно прочитать, легко инвалидировать. Он в виде структурированного документа может быть сразу использован вашим приложением.

  5. Сессии (хотя вопрос скорости работы все еще остается). Аналогично кэшу. Создали документ, бросили его в MongoDB и взяли через какое-то время одному-двум полям. Без глубокого поиска. Максимально просто. MongoDB, кстати, удобно шардится и реплицируется - это прям реально ее преимущество.

  6. Очереди. Точнее, можно использовать MongoDB как БД для хранения очередей данных для последующей обработки. Аналогично кэшу и сессиям MongoDB дает возможность хранить структурированный документ с данными очереди.

Как видите, прежде всего я пишу о вещах, которые не подразумевают поиска по данным в MongoDB и не будут требовать соединять, агрегировать или как-то еще извращаться.

Итоги

Все ошибки, которые я допустил, умножались на кол-во разработчиков, которые приходили в проект и что-то дописывали от себя. Иногда придерживаясь уже написанного, а иногда добавляя свое творчество в зависимости от своего опыта. В результате ком проблем рос и в какой-то момент добавлять новые фичи и выполнять отладку стало просто невыносимо. С учетом, что это далеко не первый проект, я сам был в шоке от того, как я в этой ситуации оказался.

Отсутствие схемы данных — это не преимущество MongoDB, а громадный недостаток. Невозможность делать нормальные джойны (подчеркиваю: нормальные, а не монговские с конским оверхедом) это тоже проблема, которая не позволяет разбить БД на логические блоки (или, проще говоря, навести порядок). Любой проект можно уложить в любую популярную SQL БД, а вот в MongoDB далеко не все.

Кто-то может сказать, что я неосилятор и вообще RTFM. Возможно, но я не хочу бороться и подгонять инструмент, я хочу его использовать и быть уверенным, что он меня не подведет, а я буду точно знать что он может и чего не может. Мне хватает опыта понять, что нет серебряной пули. Думаю, что я повелся на сладкие речи маркетологов MongoDB и решил, что она поможет стартапу быстрее развиваться. Результат: эта статья и +10 к опыту.

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

Главное, если вы будете также как я наивны и поймете, в чем ваша ошибка, поделитесь вашими косяками с окружающими — может быть кто-то уже готов прыгнуть со скалы, но передумает прочитав что-то подобное.

Автор: Vladimir

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js