Райское наслаждение от MongoDB
На выходных сортировал свою библиотечку и надолго задержался на книге «MongoDB в действии». Кидать ее в раздел SQL было бы неправильно, так как Mongo — это как раз NoSQL, а создавать новый раздел из-за одной книги не хотелось.
Я открыл книгу и стал листать. В конце предисловия была фраза, которая меня сначала насмешила, а потом заинтриговала:
«Многие разработчики признавались, что при работе с ней [с Mongo] испытывают ощущения чуда и даже счастья».
Я подумал, что и мне тоже чудо не помешает и уж тем более дополнительная порция счастья. И начал изучать, что же такое Mongo.
Что же такое Mongo?
Оказывается, это СУБД, которая в отличии от MySQL/SQLite/PostgreSQL и прочих SQL ориентирована на работу не с табличным представлением данных, а на работу с документами. Чтобы было понятнее, давайте сначала рассмотрим работу табличной СУБД на примере Либератума.
Как хранятся данные в MySQL на примере работы Drupal
Либератум работает на движке Drupal, который весьма интенсивно работает с MySQL. Для того, чтобы вы имели возможность увидеть произвольную web-страницу, Drupal проделывает следующее:
1. Из адреса страницы вычленяется виртуальный адрес и создается запрос к таблице url_alias, чтобы получить внутренний идентификатор материала (номер узла в терминологии Drupal).
Например, пользователь запрашивает страницу http://liberatum.ru/exclusive/windows-10-chto-novogo. Тут же из адреса вычленятся необходимая для идентификации документа часть и делается запрос:
SELECT src FROM url_alias WHERE dst='exclusive/windows-10-chto-novogo'
Ответ будет таким:
node/26254
2. Теперь по идентификатору узла (26254) можно извлечь заголовок статьи и идентификатор автора. Эти данные хранятся в таблице node:
select title,uid from node where nid=26254
Ответ:
title | uid |
---|---|
Что нового в Windows 10 | 1 |
В самой таблице node тело документа... отсутствует. Дело в том, что Drupal позволяет работать с ревизиями. То есть, каждая статья может иметь несколько версий. Поэтому для ревизий выделена отдельная таблица node_revision. В ней по идентификатору узла (nid) можно найти все версии данного документа и выбрать актуальную. Именно в этой таблице содержится тело документа и его анонс. Делаем еще один запрос:
select body,teaser,vid from node_revision where nid=26254
Если получаем несколько копий, то выбираем последнюю. Думаете все, можно собрать и показать готовую страницу? Нет.
Потребуется определить имя пользователя по его идентификатору (uid). Это запрос к таблице users. Потом нам потребуется найти все теги к статье. Это вообще отдельная история. Теги хуже всего вписываются в табличную модель представления данных, поэтому их хранение организовано через хитро закрученную жопу. Сейчас Билл Гейтс покажет вам как именно:
Спасибо, Билл!
Достаточно сказать, что для хранения тегов используется сразу 7 разных таблиц, а для их получения делаются запросы с объединением. Минус в том, что объединение таблиц — одна из самых медленных операций. Вот эти таблицы:
- term_data;
- term_hierarchy;
- term_node;
- term_relation;
- term_synonym;
- vocabulary;
- vocabulary_node_types.
Хорошо, получили теги, но теперь-то всё? Опять нет. Нам нужно получить количество просмотров документа. Берется оно из таблицы node_counter (обновление этого значения пока не рассматриваем, но его нужно увеличивать каждый раз, когда пользователь запрашивает этот узел, а это еще один запрос). Потом нам надо из таблицы node_access проверить, а имеет ли вообще пользователь право просматривать этот документ. Всё? Опять нет!
Комментарии. Для них есть отдельная таблица comments. Работать с комментариями на первый взгляд просто и тут видны достоинства табличного представления данных. Мы можем получить одним запросом сразу все комментарии к документу:
select cid,uid,comment from comments where nid=26254
Получаем сами комментарии, идентификатор комментария (он нужен для ссылки) и идентификатор пользователя. Ой, постойте, как идентификатор пользователя? Неужели опять запросы к таблице users? А как же иначе получить не номер, а юзернэйм?
А ведь помимо страницы с текстом есть еще боковые блоки с:
- новыми материалами;
- с самым читаемым;
- с последними комментариями;
- со статьями на похожие темы;
- и т.п.
Даже переводы интерфейса хранятся в отдельной таблице. Нет, сразу в двух отдельных таблицах: locales_source и locales_target.
Почему Drupal мертв
Так как вы думаете, сколько всего MySQL приходится обработать запросов, чтобы предоставить данные для сборки всего одной страницы? От 300 до 1000 и больше. Да, с помощью хаков можно и нужно снизить количество запросов до минимума, но для этого нужно обладать глубокими знаниями Drupal и эту хакнутую копию движка потом будет очень трудно обновлять.
Кто-то может сказать, что ничего страшного не происходит. Ведь СУБД на то и СУБД, чтобы трудиться. Какая разница, 5 запросов на страницу или 500? Для программиста это совершенно неважно. Для пользователя тоже — ну подождет на секунду или две дольше... И такая точка зрения имела право на существование, но только до одного очень важного момента — пока поисковики не стали считать время отдачи страницы одним из важнейших факторов ранжирования. Можно перефразировать это так: поисковик никогда не даст на сайт больше посетителей, чем сайт сможет обработать.
Drupal всегда преподносился как CMS (система управления контентом), которая по максимуму использует возможности СУБД. Такой подход в сочетании с ориентированностью этих СУБД на работу с табличными данными привел к неприемлемым накладным расходам. Поэтому, выбирая Drupal вы либо негласно соглашаетесь с тем, что ваш сайт будет иметь непробиваемый потолок популярности, либо вы готовы к постоянным высоким тратам на высококлассных специалистов, либо у вас неограниченное количество денег на аппаратное обеспечение.
В Drupal 8 нам обещают переход на ООП и следует полагать, что появится и ORM, который еще сильнее усугубит ситуацию.
Можно ли испытывать в таких условиях ощущения чуда и даже счастья? Но что-то я увлекся похоронами любимой CMS. Наша задача не в том, чтобы поглубже закопать усопшего, а в том, чтобы показать, что табличная ориентированность не очень подходит для web-использования. Все же основа web — страница.
Mongo — один запрос на страницу
Разработчики Mongo выбрали другой подход. Вместо хранения кучи строк, раскиданных по разным таблицам, пользователь может сохранять, искать и извлекать документы целиком. Под документами подразумевается не ODF и даже не DOCX, а некие сущности на языке JSON. Например, на JSON можно описать документ, состоящий из заголовка, тела статьи, имени автора, туда же можно поместить теги и комментарии. Одна web-страница — один документ — один запрос. Конечно, так делать не рекомендуется, но теоретическая возможность есть. Другие приятные преимущества Mongo:
- нет таблиц — нет необходимости описывать схему базы данных;
- нет схемы базы данных — можно менять структуру документов как угодно и не предупреждать об этом СУБД;
- эта СУБД появилась относительно недавно и избавлена от старческих болезней MySQL/PostgreSQL и т.п. Например, Mongo изначально рассчитана на облачное применение и легко масштабируется как вертикально, так и горизонтально;
- и т.д.
Работать с Mongo приятно. Вот примеры на Ruby:
Подключение к базе:
client = MongoClient.new
Пароли, имена пользователей и т.п. не нужны, хотя при необходимости можно задать и их.
Выбор базы данных:
db = client.db("dbname")
Выбор коллекции (совокупность документов одного типа):
coll = db.collection("collectionName")
Создание документа и помещение его в базу:
doc = Hash.new
doc["pid"] = 1
doc["title"] = "Заголовок статьи"
doc["body"] = "Тело статьи"
doc["path"] = "/path/to/page"
coll.insert(doc)
Извлечение документа по номеру статьи (pid):
coll.find("pid" => 1)
Следует отметить, что разработчики Mongo постарались сделать работу с базой максимально приятной для программистов. Например, зачем заставлять программиста создавать базу данных, если ее можно создать автоматически, когда появится запрос на подключение к ней. Или зачем заставлять программиста создавать коллекцию, если ее можно создать за него автоматически, сразу же, как только придет запрос на вставку новых данных в несуществующую ранее коллекцию. Программисту остается написать всего несколько строчек кода и можно отправляться пинать балду. В это время его коллеги, работающие с SQL-базами данных, будут вынуждены придумывать схемы размещения данных и постоянно актуализировать их с помощью миграций. А еще любители SQL должны насиловать свой мозг, изобретая хитрые многоэтажные объединения нескольких таблиц с помощью вложенных запросов и тормозных операций типа JOIN. Зачем все это? Программист, использующий Mongo напишет всего пару строк кода и пойдет в бухгалтерию за зарплатой.
Все ли так прекрасно и где вообще тесты?
В следующей части статьи о райском наслаждении от Mongo попытаемся сконвертировать базу данных Либератума из MySQL в MongoDB, запилим тестовый Либератум и сравним время генерации страниц. Чтобы было интереснее, для сравнения возьмем еще и SQLite.
Комментарии
Чингачгук
7 ноября, 2014 - 22:54
Спасибо за статью. Нашел ее через гугл, зашел на сайт, прочитал, хотел уже искать продолжение, но посмотрел на дату — статья опубликована 20 минут назад. Жду продолжения.
pomodor
7 ноября, 2014 - 23:10
Месяц и 20 минут назад. Продолжение будет, уже есть что рассказать по теме.
Комментировать