Pull to refresh

Особенности использования MongoDB

Reading time4 min
Views28K


Чуть больше года назад меня попросили поучаствовать в развитии одной известной в узких кругах (но не всегда с хорошей стороны) социальной сети. В то время я уже был фанатом языка Haxe, поэтому с тем на чём писать вопросов не возникало. А вот с БД они появились. Опыт использования MS SQL Server и MySQL говорили о том, что когда дело касается больших объёмов информации, то порой случаются сложности (практически невозможным становится изменение структуры БД, а когда-то быстрые запросы работают уже критически медленно). Посовещавшись с коллегами (у которых уже был опыт с Mongo), мы решили использовать именно эту СУБД. А о тех особенностях, которые всплыли в течение этого года я и расскажу ниже.

Стабильность работы


Через несколько месяцев использования появилась интересная проблема. Дело было так: я запустил построение индекса для одной из коллекций, затем, обнаружив, что индекс неверен, я позволил себе снять процесс через db.killOp(). После этого индекс формально появился, но при попытке использования ЛЮБОГО индекса (за исключением natural) в данной коллекции СУБД возвращала внутреннюю ошибку (срабатывал какой-то assert в её коде). Т.к. коллекция содержала почти 100 млн. записей, то запросы к ней стали невозможны и нам пришлось на несколько дней остановить сервис, пока мы переносили данные в новую коллекцию. А старая коллекция так и живёт в нашей базе — удалить её штатными средствами не получается.
В результате, сейчас у нас все коллекции, относящиеся к одному сервису, кладутся в отдельный instance СУБД, который в критической ситуации можно остановить и выправить, пожертвовав доступность лишь одного сервиса.

Запасные копии


Немало времени было потрачено и на то, чтобы решить, как выполнять резервное копирование информации. Первый приходящий на ум способ — создание набора реплик — показался мало подходящим в том смысле, что порой нужна не последняя копия БД на момент сбоя, а более ранняя. Кроме того, при снятии бэкапа, было бы неплохо не создавать дополнительной нагрузки на production. В результате пришли к использованию master-slave конфигурации, при которой slave время от времени бэкапится штатными средствами.
Хинт: чтобы избежать настройки безопасности через сертификаты, можно просто создать пользователя «repl» в БД local master и slave инстансов:

use local
db.addUser("repl", "mypassword")


Уникальные индексы


Привыкнув к относительной безопасности SQL, можно попасть в неприятности. У нас был такой казус: для одной из коллекций мы запустили построение уникального индекса по двум полям, одно из которых отсутствовало у большого количества записей, а другое регулярно повторялось. В результате нашей ошибки, СУБД начала удалять данные из коллекции. Вовремя поняв это, мы сняли процесс удаления по db.killOp() уже через несколько секунд, а информацию позже восстановили. Но осадок, как говорится, остался. Поэтому, как написано в руководстве: используйте dropDups:true с осторожностью!

Счётчики


Операции подсчёта количества элементов, подходящих под заданный фильтр, на больших объемах данных могут выполняться очень долго, если сравнивать с SQL. Всё дело в том, что Mongo не хранит в деревьях индексов количество подузлов. В результате, даже если ваш запрос полностью подпадает под индекс, СУБД вынуждена обойти все подходящие под фильтр записи (а ведь часто их немало). Так что будьте готовы хранить счётчики отдельно и менять их когда нужно. Либо иметь фоновые сервисы, которые будут пересчитывать значения счётчиков с заданное периодичностью. У нас используются оба способа, в зависимости от ситуации (первый — в простых случаях, таких как количество комментариев для картинки, а второй — для сложных, когда нужно хранить количество записей в коллекции, подпадающих под разные наборы условий).

Ротация лога


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

Безопасность


Без специальной настройки может легко оказаться так, что Mongo будет смотреть в интернет без пароля. После первичной настройки, не забудьте создать пользователя — администратора:

use admin
db.addUser("root", "mypassword")


Кроме того, помните, что у этой СУБД в сеть может смотреть ещё и http-страничка (см. опцию nohttpinterface) со статистикой.

Полнотекстовый поиск


В целом, он работает. Следует лишь помнить, что:

  1. языки (в частности русский) поддерживаются не совсем корректно — Mongo, например, пользуется рядом эвристических правил для детектирования окончаний, но, судя по всему, не хранит списков исключений или чего-то подобного, поэтому ничего особо умного от такого поиска не ждите;
  2. если под поисковый запрос подпадает много записей (вы искали распространённое слово) — будет плохо; а именно: запросы будут выполняться долго (в нашем случае — порядка 30 секунд и более) и на это трудно как-то повлиять без введения допущений (сделаем limit перед поиском — не найдёт часть данных — ну и ладно).
    Поэтому, хотя мы и используем полнотекстовый поиск в Mongo, но только потому что он идёт «из коробки». Если качественный поиск критичен — используйте что-то наподобие sphinx.


Выводы


В целом, я остался доволен нашим выбором: горизонтальная масштабируемость и гибкость документ-ориентированной модели перевешивают недостатки.
Сейчас наша социальная сеть функционирует на нескольких десятках серверов, при том, что сделанные нашей командой сервисы отлично держатся на одной машине для серверного кода (сам код написан на языке haxe с использованием веб-фреймворка HaQuery и скомпилирован в neko), двух машинах для production-БД и одной для slave-инстансов Mongo.
Используемая версия MongoDB — 2.4.5.

Надеюсь, если вы подумываете использовать MongoDB для своего проекта, эта статья поможет вам принять верное решение.
Tags:
Hubs:
+16
Comments21

Articles