
Введение
Коллеги, при разработке приложений, мы каждый день сталкиваемся с потребностью в гибком хранении информации (обновлении, поиске по ней, и т.д.). Класс продуктов, которые решают этот круг задач, как все мы знаем — Базы данных. Но что это такое в нашем понимании? У многих «база данных» твердо ассоциируется с MySQL, таблицами и SQL-запросами. И это устраивает до определенного момента. Действительно, реляционные базы данных дают массу преимуществ в работе: поскольку данные имеют сильную связанность, не нужно контролировать целостность базы данных. Используя простой под-запрос можно выбрать количество комментариев к каждому посту в блоге. Используя JOIN нетрудно делать сложные связанные выборки и получать данные сразу о нескольких сущностях.
Масштабировали, масштабировали, да не вымасштабировали
Однако, когда вдруг понимаешь, что одного сервера перестает хватать, и хочется раскидать базу на несколько физических машин, то первое что предлагается — master-slave репликация, при которой запись идёт на одну машину, а чтение — с нескольких. Но при достаточно большом количестве изменений в скором времени master только и будет делать что раздавать логи, и придётся прибегнуть к изощренной настройке, чтобы каждый узел имел один шефствующий сервер (master) и мог иметь несколько подчиненных серверов (slave). Данная схема весьма сложна в реализации и содержит single point of failure (т.е. при выходе из строя одного сервера, все его подчиненные стремительно превращаются в динозавров). Опять же, все эти ухищрения позволят увеличить лишь количество чтений. А если для внесения изменений в состояние базы данных не хватает мощности одного сервера, то приходится распределять нагрузку, храня разные таблицы на разных серверах. Но тогда потеряется связанность. Или приходится разбивать таблицу на несколько частей, храня их в разных местах, согласно заданному закону (например по ID), однако это унесет в могилу прелести JOIN. Чем дальше мы пытаемся масштабировать реляционные базы данных, тем большим удобством мы за это платим. Например, при использовании master-master, мы заплатим auto increment'ами.
Спасут ли нас MemcacheDB и Redis?
Подобные key-value решения существуют довольно давно. На мой взгляд, MemcacheDB это поделка, паразитирующая на добром имени замечательного продукта: данные там абсолютно не связаны между собой, мы можем лишь проводить операции над значением зная ключ. Я потратил уйму времени на написание инструментария, позволяющего выгодно работать с key-value БД вроде MemcacheDB, и это даже работает, однако пришел к тому что простые насущные задачи решаются настолько криво, что невольно думаешь в сторону реляционки: например, нету даже элементарной реализации времени жизни объекта (TTL), т.е. нельзя взять и удалить сессии старше месяца. Это не доставляет.
Авторы Redis пошли чуть дальше в своих извращенных фантазиях и сделали атомарные списки и set'ы в ключах, однако не сильно продвинулись в облегчении жизни программисту.
Более того, всё еще приходится вручную обслуживать кеш ключей, т.е. сохранять нужные ключи в Memcached и брать их из него, что создает уйму проблем с синхронизацией. При этом также отсутствует атомарность операций: возникает гонка между получением объекта и записью в кеш, CAS демотивирует нас своей производительностью.
Что делать если хотим каталог товаров по типу Яндекс.Маркета?
Допустим, мы хотим сделать каталог, в котором осуществляется поиск по таким параметрам как цена, рейтинг, кратность зума у фотоаппарата и количество передач у велосипеда. Допустим, мы используем реляционную базу данных типа MySQL. Что мы должны сделать? Мы должны либо создать таблицу goods, в которой будет как кратность зума, так и количество передач для велосипеда, и к примеру, показатель гибкости щетины зубной щетки (в этом случае мы упремся в лимит полей для таблицы, или потеряем на дисковом пространстве, скорости, и удобстве), либо мы должны завести табличку вида good_id, key, value и делать страшные JOIN'ы для выборки и поиска, не заикаясь о масштабировании.
Еще можно натравить на это дело Sphinx или что-нибудь похлеще, однако это скорее всего будет выглядеть как забивание гвоздей ноутбуком.
У реляционок налицо серьезная родовая травма.
MongoDB говорите?
MongoDB — резкая как понос объектно-документарная база данных. Идеологически это некий симбиоз между привычной реляционной БД и key-value хранилищем, и на мой взгляд, весьма удачный. С одной стороны, она позволяет делать очень быстрые операции над объектом, зная его идентификатор, а с другой, предоставляет мощнейший инструмент для сложных взаимодействий.
Коллекция — именованное множество объектов, при этом один объект принадлежит лишь одной коллекции.
Объект — совокупность свойств, включая уникальный идентификатор _id.
Свойство — совокупность названия и соответствующего ему типа и значения.
Типы свойств — строка, целое число, число с плавающей точкой, массив, объект, бинарная строка, байт, символ, дата, boolean, null.
Поддерживаются операции выборки (count, group, MapReduce...), вставки, изменения и удаления. Связей между объектами нет, объекты могут лишь хранить другие объекты в свойствах. Поддерживаются как уникальные, так и композитные индексы. Индексы можно накладывать на свойства вложенных объектов.
Поддерживается репликация (даже подразумевается), реализован fail-over.
Реализован MapReduce и шардинг.
Поскольку объекты могут иметь произвольный набор свойств, для каталога достаточно создать коллекцию goods и складывать туда объекты. При этом поиск будет вестись по индексам.
Резкость MongoDB ярко выражена на insert'ах, они происходят ну очень быстро. Кстати, приятно что формат хранения и формат передачи объектов по сети один и тот же, так что для выборки какого-то объекта надо всего лишь найти его позицию по индексу и вернуть клиенту кусок файла определенной длины — никакой абстракции над storage engine.
В качестве уникального идентификатора используется не auto-increment'ное поле, а 12-байтное уникальное число, генерируемое на клиенте. Таким образом, во-первых нет проблемы с синхронизацией реплик, т.е. можно независимо делать вставки на две разные машины, и конфликта не возникнет. Во-вторых, не будет ерунды с переполнением целого числа, ну и после пересоздания базы данных, поисковики не будут адресовать на новые статьи по старым ссылкам.
MongoDB + Memcached? Покруче чем Бонни и Клайд!
С базой данных более-менее разобрались, теперь подумаем как нам всё это дело кешировать, ведь нам же хочется отдавать горячие пирожки со скоростью Memcached!
Во-первых, кеширование самих объектов. Как многие успели догадаться, или узнать из опыта — в большинстве случаев операция выборки объекта по ID является самой частой. Например, выборка объекта пользователя, выборки этого поста из базы Хабрахабра (аминь), и т. д.
Как мы уже выяснили выше, перекладывать сей труд на приложение неразумно, т.к. мы должны были бы нагородить огород распределенных блокировок. Пойдем другим путем, напишем асинхронное приложение, которое подключится к MongoDB и, прикинувшись slave'ом, будет получать лог изменений, и сбрасывать изменения в Memcached (если объект содержит ключ в свойстве _key). Поскольку приложение асинхронно, это будет происходить быстро, однако гонки возникать не будет. Написать несложно, моя реализация
тут. Более того, я еще присобачил туда отправку изменений серверу событий, так что мне стоит только изменить объект откуда угодно (да хоть из консоли), как он тут же будет передан всем подписанным на него клиентам.
Кеширование запросов и девалидация кеша должна работать несколько иначе. Существует несколько подходов:
- Кешировать на определенное время. Если запрос один и тот же или их не так много, то можно обновлять кеш не чаще раза в секунду — это уже значительно снизит нагрузку.
- Удалять кеш по требованию приложения. Подход довольно муторный, но имеющий право на жизнь.
- Использовать сервис блокировок для того, чтобы не делать одну работу два раза. Однозначно — это добро.
Минусы с которыми придется столкнуться.
- MongoDB — продукт довольно молодой, и в нем встречаются баги (бывает Segmentation fault, core dumped), появляются новые фичи, и т.д. Я использую его в продакшене, но с осторожностью. Однако, несомненным плюсом является высокий темп разработки (проект пишут не только волонтеры, но и компания людей на полной занятости), так что вы вполне можете рассчитывать на быстрый багфикс, помощь в решении ваших проблем и реализацию ваших идей (если конечно они хорошие). Также доступна коммерческая поддержка.
- Накладные расходы на хранение названий свойств.
- По-умолчанию максимальный размер объекта — 4 мегабайта.
- На 32-битных машинах, максимальный размер одной базы данных — 2 гигабайта.
Finita la comedia!
Страничка проекта —
MongoDB.
Найденный бенчмарк
MongoDB vs MySQL.
Эта статья рискует положить начало циклу о MongoDB. В следующий раз я поподробнее расскажу о шардинге, MapReduce, и приведу живой пример.
Друзья, спасибо за внимание! Буду рад конструктивным комментариям.
Опубликовано продолжение!
MongoDB — варим хороший кофе
комментарии (96)
какую нибудь типовую задачу решить с замерами
сделайте сами
выложите все сорсы
тема вполне актуальна
Мускул это все же не самая скоростная БД…
Вы просто не умеете её готовить :) Просто все перечисленные субд хороши в своей стихии.
mysql > load data infile = 1млн строк за 4-10 секунд — как вам такая скорость?
Это конечно самый утрированный пример, но все жё — есть разные методы оптимизации и прежде всего лучше исходить из задачи и её ограничений (читать условий)
—
PS. если тц опубликует сорцы бенч'а (который возможно сделает) — то думаю найдутся добровольцы самостоятельно прогнать по другим субд и поделиться результатами. Так что — пусть покажет нам — скорость! ;)
Вообще-то разработчики MySQL ставили себе в цель именно скорость работы. Поэтому многие вещи до сих пор не поддерживаются.
«типовые задачи» в скорости могут отличаться в сотни и тысячи раз, как с преимуществом у key-value баз данных, так и у реляционных.
На маленьких проектах разница в скорости будет на уровне погрешности измерений (нереляционные базы долго запрягают, но быстро едут, а реляционные быстро запрягают, но туго ездят), а на больших сравнивать банально нечего — либо нереляционные нельзя применять, либо реляционные не тянут и 1% нагрузки от того, что дают нереляционные.
Есть несколько потоков, читающие и пишущие в базу.
Структура базы — две таблицы. Одна — список сущностей (около 200.000 на тот момент было). Вторая — история обработки сущностей (около миллиона).
Потоки брали сущности из базы (select from table1), блокировали их (update table1), какое-то время работали с ними, дописывали во вторую таблицу логи (insert into table2), потом обновляли состояние сущности (update table1) на основании истории (select from table2).
Пока использовался mysql, больше 6-8 запустить было невозможно, mysql отъедал одно ядро полностью и дальше не давал рости.
Был так же web-interface для просмотра статистики, запросы на котором (2-3 запроса по хорошим ключам) отрабатывали секунд по 20. С него так же генерилось небольшое количество insert/update (порядка 1-2 в минуту), которые так же отрабатывали с дикими тормозами.
В случае с couchdb, его не видно в top, процессов запущено около 30, они грузят полностью 4 ядра и больше потестировать не могу. При этом, база выросла в несколько раз (количество сущностей — около 1.700.000).
Организована couchdb так: один документ — одна сущность из table1, у каждой из них история в виде списка в одном из полей.
Веб-интерфейс теперь отрабатывает мгновенно.
И тогда по CouchDB сразу вопрос — а запросы в виде материализованных видов не напрягают? Каждый раз их определять на мой взгляд несколько муторно по сравнению с приснопамятным SQL или запросами к Mongo.
например тот же пример с " каталог товаров по типу Яндекс.Маркета".
Не знаю ваших задач, но думаю, можно смело пересаживаться.
если вам нужно что-то с большим числом транзакций, то MongoDB вам скорее всего не подойдет
Спасибо, жду продолжения.
Но как дела со скоростью сложных выборок?
Не могу придумать задачи, где хватит простой kv-базы. Обычно бывает так что нужны простые «by id» операции с коллекцией, ну и элементарный постраничный вывод с сортировкой по ключу. Вряд ли разумно ради этого использовать реляционную СУБД, со всей её беспощадностью.
CouchDB vs. MongoDB Benchmark
В конце концов РДБ могут работать с XML данными.
Не уловил мысль про изменчивые атрибуты и быстрый доступ к полю…
mysql> SELECT CONCAT('\n\n',
-> GROUP_CONCAT(' ', name, '\n' SEPARATOR ''),
-> '') AS xmldoc
-> FROM cities\G
Оставлю это без комментариев :-)
> Подозреваю, что не больше размера ОЗУ.
Вы заблуждаетесь, я знаю людей, которые хранят свыше 200 гигабайт в одной коллекции, и всё замечательно и быстро работает. Посмотрите хотя бы бенчмарк по ссылке.
есть целый выводок xml баз данных и это тоже далеко не новость.
Все эти проекты заняли свое место но мейнстримом не стали да и не станут.
Вообще уход от реляционных бд это шаг назад на несколько десятков лет. И не все так хорошо в нереляционных базах как оно хотелось бы. Нужно 10 раз думать предже чем начать приманять подобные вещи на практике.
Ради спортивного интереса последнее время ковыряю CouchDB. В течении пары недель думаю выжму из себя несколько статей на эту тему с живыми примерами и может быть тестами.
Воистину страшная!
После работы с этим чудом инженерной мысли я с большим предубеждением воспринимаю все «самопальные» (то есть не имеющие ~100К серьезных внедрений) базы данных. Как почитаешь про ZODB так все выглядит очень гладко, но на практике как обычно «забыли про овраги». При росте базе (всего ~10K записей) начались серьезные тормоза, кроме того база, заявленная как очень надежная, постоянно сыпалась почище myisam, хуже того — средств восстановления для нее нет.
После работы с этим зопом я понял почему его так назвали.
А «геморрой — это зопяная боль».
да она коммерческая — но уже десятки лет ( а точнее раньше самой каше ) были и есть M системы ( а каше ей и является )
и были умники которые говорили что реляционки теперь не нужны — сам был среди них.
а теперь оглянитесь и посмотрите на чем сделана большая часть проектов.
Ага, какже. Каменты деревьями нереляционная база данных конечно построит сама ))) Гемороя будет в лучшем случае столько же.
> JSON куда более природен для восприятия человеком, чем линейные выборки из реляционных БД.
Как сказать, у меня в мозгу к сожалению не крутиться V8 или greaseMonkey чтоб json был понятней
JS-движки в качестве бэкендов применяются по умолчанию видимо потому, что JSON — это родной формат JS (JavascriptObjectNotation).
Вообще JSON он на то и JSON, чтоб позволять хранить документ поста вместе с деревом комментариев и строить вообще ничего не придется. Но с таким подходом возникают уже другие проблемы, например, «одновременное» добавление комментариев, что по сути будет означать «одновременное» редактирование документа поста.
У меня она ассоциируется с Oracle и Microsoft SQL Server.
1. почему именно MongoDB, а не Redis, например? ну, то есть ты (ничего, если на «ты»? заранее прошу прощения) смотрел сравнивал и выбрал в итоге mongo, или просто именно монго попалась под руку?
2. а какая примерно нагрузка на проект, и сколько приходится на mongo?
3. я так понял (код, правда, читал совсем бегло), что внутри phpdaemon :: appInstance кэшируются результаты из memcached, а сам memcached кэширует монгу. то есть слоёв кэширования два. зачем столько?
и есть ли профит между mongo+memcached и просто mongo?
2. Пока небольшая, однако тесты оптимистичные. Намного лучше чем на MySQL (с нее слезаю), хотя поверь, я далеко не новичок в MySQL.
3. phpDaemon::MongoNode читает в реальном времени бинарной лог Mongo и бросает в memcached объекты в которых есть _key.
Безусловно есть. Нельзя сравнивать производительность кластера memcached и любой другой базы данных, которая хранит данные на диске. Хотя я кеширование использую только для небольших горячих объектов которых в сумме метров на 10, их целесообразнее дергать из памяти.
И что с этого? Там используется асинхронный способ работы с данными
С другой стороны, непонятно, зачем нагружать редис так, что нужны все 8 ядер? Этоже kv- _store_. Т.е. основное предназначение сохранять данные, а обрабатывать их должно приложение, которое уже может работать на нескольких потоках.
Скоро выложу чат который держит сотни тысяч пользователей одновременно, как ejabberd прям :-)
общение риал тайм писал на Си как демон… это да. Но если надо потом что то еще делать с данными то без БД не обойтись.
Вообщем как я ни раз говорил все зависит от ситуации…
Да, термин мой, просто на мой взгляд, она и не объектная (это все же немного другое) и не документарная (не BigTable). Кстати я планирую написать парсер SQL-запросов и конвертатор в Mongo-запросы на лету: то есть просто подключаете этот слой и делаете например SELECT * FROM collection WHERE x > 1 AND y < 5. По большому счету ничего сложного.
Осталось понять насколько хорошо это будет работать. Все же делалось django с реляционными бэкендами, насколько хорошо оно будет работать с нераляционным? Конечно, делали его люди талантливые, и написано довольно абстрактно от хранилища. Но все же хотелось бы реальных тестов, насколько одно к другому подходит. Не вылезет ли там гигантский оверхед, который убьет затею на корню?
А как эта проблема решена в монго? Он позволяет сделать некий JOIN данных между разными шардами?
Это тема второй, более подробной статьи на эту тему. Там я рассмотрю все эти аспекты вполне детально.
blog.knopkodav.ru/2009/10/mongodb-vs-mysql.html
Специально ни тот ни другой не настраивал, всё, можно сказать, из коробки.
Что тут не так?
Создайте пустую коллекцию изначально, а потом протестируйте ;-) В init достаточно делать «mongo.remove».
mongo = mongo_db.collection(«beta»)
mongo.remove
и всё равно:
$ ruby mymongo.rb user system total real mysql: 0.300000 0.100000 0.400000 ( 1.754915) mongodb: 5.910000 0.230000 6.140000 ( 6.880819):-/
однако реалицонность можно сделать нормально только с РСУБД.
Хорошо что есть и те, и те :)
А ещё лучше то, что есть такие как MySQL и TokyoCabinet
А может 24-байтное?