Приветствую бойцов невидимого бэкенда!
Вы уже почитали обзоры MongoDB. Вероятно, прошли отличные онлайн-курсы на university.mongodb.com. Конечно, у вас уже есть многообещающий проект-прототип с использованием MongoDB.
Что мы можем ждать от MongoDB на этом этапе?
- Удешевление хранилища — чтение с ведомых реплик экономит iops мастера, не требуется RAID, отказ одного диска не фатален.
- Повышаем скорость разработки — можно допустить бОльшую небрежность в проектировании структур данных, т.к. мы вполне можем все исправлять на работающем приложении.
- Повышаем отзывчивость приложения — независимо от разработки, легко увеличить число ведущих реплик или количество шардов, чтобы компенсировать возросшую нагрузку на приложение.
- Повышаем надежность приложения — независимо от разработки, убираем единую точку отказа.
И вот, вы готовы ввязаться в бой — выпустить проект на публику.
О чем статья
Это попытка обобщить свой опыт разработки с MongoDB. Мое первое практическое знакомство с этой СУБД состоялось в 2010 году в виде проекта-прототипа. После прохождения первых онлайн-курсов 10gen я уже уверенно применял MongoDB параллельно с SQL, но только на вспомогательных и исследовательских проектах.
С 2013 года я работаю в компании Smartcat на должности ведущего разработчика. Все новые проекты мы начинали только с использованием MongoDB. Большая часть разработки у нас ведется на C#, но есть проекты и на других языках. Во всех проектах я либо сам проектировал схему данных, либо активно консультировал коллег. На протяжении всего времени жизни проектов я наблюдал за характером использования СУБД. В 2015 году основной проект сайта Smartcat был плавно переведен с MSSQL на MongoDB.
Это описание того, что надо учесть в разработке, чтобы проект был гибок в масштабировании.
Можно попасть в ситуацию, когда есть успешно работающий проект-прототип. Пока пользователей мало, все оптимизации откладываются. Но, сервис «раскручивается», пользователи уже пришли, и работу нельзя останавливать. А масштабирование невозможно без глубокого рефакторинга схемы данных БД, и простые запросы, работавшие вчера, сегодня «кладут» систему.
Мое основное направление разработки — бэкенд на C#, поэтому в этом ключе и будут примеры.
Итак, попробуем описать этапы развития проекта с точки зрения использования MongoDB, от одиночного сервера до шард-кластера и причин, которые вынуждают нас переходить к следующей, более сложной конфигурации серверов БД.
Standalone
Конечно, для 24/7 проекта мы не рассматриваем одиночный сервер. Но если это внутренний проект или решение для офиса, то вполне сойдет. Надо лишь планировать возможность остановки сервиса для снятия бэкапа или предусмотреть программный экспорт/импорт всех нужных данных. Такая конфигурация особых вопросов в администрировании не вызывает, но и надежностью не блещет.
В основном, одиночные сервера используются для разработки. Это может быть общий сервер — в этом случае удобно пригласить коллегу к разработке приложения на одной БД. Это может быть локально запущенный сервис.
Самое главное — мы ожидаем, что программные решения в клиентском коде будут идентично работать в условиях репликасет. А также, хоть это и ощущается как далекая перспектива, в условиях шард-кластера.
Репликация
Репликация — это необходимое условие непрерывной работы боевого проекта. Мы совершенно точно рассчитываем на:
- Автоматическое или ручное восстановление после отказа отдельных серверов-копий.
- Возможность «заморозки» состояния БД для исследований или снятия бэкапа.
- Переезд из одного датацентра в другой без выключения сайта.
При работе репликасет мы должны постоянно заботиться о том, чтобы:
- Не потерять связность сети и ведущий сервер.
- Ведомым не сильно отстать.
- Не потерять синхронизацию из-за исчерпания opLog.
Может быть потом мы сумеем что-нибудь почитать с ведомых серверов, чтобы разгрузить наш единственный пишущий сервер.
Состав серверов репликасет и ожидание большинства при записи
В схемах развертывания обозначим сервера как: P — primary, S — slave, H — hidden, A — arbiter.
Ожидание записи большинством реплик далее по тексту будет — w:majority.
На некритичных проектах (это если мы можем себе позволить ручное восстановление) можно использовать следующие конфигурации:
- PS — блокируя ведомого, можно снять бэкап, но при этом повиснут все ожидания w:majority.
- PH — блокировка скрытой реплики не мешает приложению, но ее нельзя использовать для чтения.
- PSH — бэкапы есть, w:majority тоже, но мы чувствительны к отключению сервера.
Если требуется автовосстановление кластера, то минимальное число серверов — 4 штуки в конфигурации PSSH. Это оптимальная конфигурация для нас и сейчас. Со скрытой реплики регулярно снимаются бэкапы, а остановка сервиса на любой машине не угрожает приложению. Иногда требуется надолго вывести из работы один из серверов. Можно на это время остановить бэкапы и включить в балансировку скрытую реплику, либо присоединить к репликасету еще один сервер.
Кстати, конфигурации с арбитром следует использовать очень осторожно. Арбитр увеличивает общее число серверов, но не пишет данных. Получается, что для ожидания w:majority, требуется бОльшая доля серверов. Так, на конфигурации PSA, в случае выхода из строя P или S, возможность записи будет, но без w:majority.
В целом, когда планируете конфигурацию, следует перечислить все комбинации отказов серверов, чтобы понимать какие из них вы можете компенсировать в автоматическом режиме. А для ручного восстановления следует сразу заготовить инструкции.
Исчерпание opLog
Это лог операций записи в БД. Каждый ведомый сервер реплицирует его себе с ведущего, а операции записи проигрываются над локальной копией данных.
Первая проблема — opLog имеет ограниченный размер и перезаписывается циклически. Основная опасность в том, что при интенсивной записи история opLog будет покрывать малое время. При любой задержке проигрывания, например в результате перегрузки по CPU или задержки сети или записи диска, ведущий сервер может потерять последнюю запись. В этом случае он остановит синхронизацию и перейдет в режим RECOVERING. При интенсивной записи очень велик риск ухода в этот режим нескольких ведомых серверов. Как следствие, можно потерять кворум для выбора ведущего сервера, и кластер перестанет принимать данные на запись. Восстановление кластера — ручная процедура.
Мы называем это — «развалили» репликасет.
Вторая проблема — объем трафика репликации может стать препятствием для разделения серверов в разные датацентры.
Мы ведем мониторинг размера opLog в секундах, разность между временем первой и последней записями. Например, минимальный размер opLog — 10Ks — позволяет снять бэкап и не потерять синхронизацию сервера. Если средний размер opLog — 100Ks, то этого достаточно, чтобы успеть среагировать при его резком падении.
В каких случаях это происходит:
- Одновременный набег пользователей с добавлением контента или редактированием.
- Ошибка в бизнес-логике, когда мы начинаем обновлять очень много документов.
Самое печальное, что такие ошибки тяжело выявить при профилировании запросов. Надо анализировать opLog непосредственно.
Примеры ошибок в коде бэкенда:
Flush потока записи в GridFS
При экспорте результатов перевода мы в потоковом режиме записываем файлы в zip-архив, а он также в потоковом режиме направляется в GridFS. Размер чанка файла был увеличен до 2 Mb, а записываемые файлы обычно имели размер 200–900 Kb.
По окончании записи каждого файла в архив вызывался метод Stream.Flush() в используемой нами библиотеке. А в MongoCSharpDriver v1.11 есть интересная особенность работы с потоком, которую мы не сразу обнаружили: при вызове метода Stream.Flush() в БД обновлялся текущий записываемый чанк файла.
Например, при записи файла в 6 Mb мы ожидаем, что в opLog попадут 3 операции вставки чанков по 2 Mb, т.е. мы предполагали, что в opLog будет записано данных чуть больше объема файла.
Беда пришла с архивом, в котором было множество файлов по 3–5 Kb.
Для упрощения расчетов будем считать, что мы записываем один файл размером 2 Mb (т.е. ожидаем записи в один 2-мегабайтный чанк). Метод Flush() будет вызываться каждые 4 Kb, и за все время потоковой записи будет вызван 512 раз. Причем размер обновления с каждым разом будет увеличиваться от 4 Kb до 2 Mb линейно. Это арифметическая прогрессия с дельтой в 4 Kb.
Сумма первых 512 элементов
Вместо 2 Mb теряем в opLog 513 Mb — неожиданно...
Первое решение — кэширование файла в памяти перед записью в БД, но сейчас мы используем последнюю версию драйвера v2.4.4. Там эта проблема решена — метод Flush() не обновляет данные в БД.
Обновление вложенного массива
Вложенные массивы в MongoDB могут сильно упростить разработку. Но использовать их стоит крайне осторожно. Вот еще один пример из практики.
В нашей схеме данных документ описывает задание по экспорту списка файлов. Список файлов описывается вложенным массивом, в элементе которого содержится ссылка на файл и статус готовности. Обработка задания выглядит так:
- читаем документ,
- ищем во вложенном массиве очередной файл и обрабатываем его,
- обновляем статус готовности документа.
На этапе разработки количество документов ограничивалось ручным проставлением галочек в UI. Обычно речь шла о 5, максимум 10 файлах.
Особо упертые пользователи могли «прокликать» страницу в 50 элементов. Поэтому разработчик упростил алгоритм — обновлялся весь массив сразу. Размер записи — примерно 300 байт.
10 обновлений —
Кажется, немного. Но, как вы могли заметить, расход объема opLog стал квадратично зависеть от числа файлов. А чуть позднее, благодаря развитию удобства UI, стало возможным выбирать экспорт всех файлов, вложенных в проект.
Последовательный экспорт нескольких проектов по 1000 файлов — и падающий opLog ловили принудительным выключением обработчика задания.
Исправление тривиально — обновление элемента массива по порядковому номеру.
Массированные обновления несущественны, если сервер работает в режиме standalone. Если хранимые данные — временные, то это даже может быть штатный режим работы БД.
Если же ведется разработка для использования в условиях репликации, то в тестировании обязательно надо использовать кластер с включенной репликацией. А сами тесты должны содержать примеры с экстремальными значениями вложенных данных, размерами и числом обрабатываемых файлов.
Rollback при смене мастера
Rollback — это отмена части операций записи на реплике, если они не подтверждаются большинством. Это может произойти после того, как она, будучи мастером, была выключена, а потом опять включилась в работу.
Смена мастера может быть непреднамеренной: в случае рестарта сервера, аварийной остановки сервиса или сбоя сети. А может быть и плановой: при обновлении сервиса или ОС, смене размера opLog, переезде, resync’е, тестировании новой конфигурации сервера или версии БД.
В результате rollback’а можно потерять часть записанных данных, это особенность репликации MongoDB. Для тех документов (изменений), которые не могут быть потеряны (например, отметки об оплате услуг) следует включать w:majority. В этом случае, после успешного ожидания, отмена такой операции не может произойти.
Настройки по умолчанию ожидают записи только на ведущий сервер. Типичное время выполнения такой операции мы полагаем в 0.01 с. Если мы ожидаем w:majority, то типичное время увеличивается до 0.06 с, и только если большинство реплик находятся в одном датацентре.
К счастью, для большинства операций нет необходимости ожидать w:majority. Некоторые данные можно получить при повторном расчете (например экспорт файла), некоторые при сбое несущественны (например, время последней работы пользователя).
Как не устроить rollback в рамках обслуживания серверов:
- Если требуется сместить мастера с конкретного сервера, то подойдет rs.stepDown().
- Если нужно сделать мастером конкретный сервер, то ему надо поднять priority.
Если есть подозрения, что данные были отменены через rollback, то можно поискать их в директории rollback на серверах.
Создание индекса
Если мы не хотим блокировать БД при построении нового индекса, мы строим его в фоновом режиме (опция background: true). Но в условиях репликации построение любого индекса будет запущено на каждом ведомом c блокированием проигрывания opLog — произойдет скачок отставания, а если возрастет интенсивность записи, то и до развала недалеко. Также если индекс строится долго, то может прерваться ожидание w:majority.
Перед построением индекса замеряйте время его создания на похожих данных, чтобы оценить время простоя на боевом кластере.
Есть способ построения большого индекса без блокирования ведомого.
- На каждом ведомом сервере выполняем последовательность действий:
- Переключаем сервер в одиночный режим.
- Запускаем построение нужного индекса и ждем окончания.
- Возвращаем сервер в репликасет и ждем его синхронизации.
- Запускаем построение нужного индекса на ведущем сервере.
Проверка прохождения resync
Resync — это копирование всех данных реплики заново и перепостроение индексов. Это может происходить при вводе в репликасет нового сервера баз данных или при желании устроить дефрагментацию свободного пространства.
Размер БД популярного, нагруженного сервиса может со временем увеличиваться. Соответственно, увеличивается и время копирования данных и построения индексов, и объем памяти для построения индексов.
Легко пропустить момент, когда вы не сможете простым способом присоединить в работающий кластер еще одну реплику. А это важная процедура в случаях утраты сервера, апгрейда или переезда в другой датацентр. Если это произошло можно попытаться скопировать директорию данных сервиса или снять образ диска сервера.
Обычно я считаю, что resync должен занимать не более размера opLog.
Кстати, есть способ увеличения размера opLog’а без потери данных — Сhange oplog size
Бэкап
Процесс копирования данных занимает время, и чтобы получить их моментальный снимок (snapshot), мы используем команду db.fsyncLock() на скрытой реплике, чтобы остановить запись во все коллекции. После этого утилитой mongodump копируем все данные в хранилище бэкапов.
В процессе снятия бэкапа состояние данных все больше отстает от ведущего. Конечно, надо следить, чтобы время снятия бэкапа было не больше длительности opLog.
Есть еще много способов снять бэкап: копия образа диска, копия директории БД, snapshot виртуалки.
Mongodump — самый медленный способ, но у него есть преимущества:
- Не зависит от системы виртуализации.
- Не зависит от версии БД (восстановление старых бэкапов может потребовать развертывания предыдущей версии БД).
- Можно восстановить выбранные коллекции или даже некоторые документы, а как показала практика, фатальные ошибки в коде или при администрировании редко затрагивают более одной коллекции.
Чтение с ведомых серверов
Копия данных, хоть и немного устаревшая, сама просит чтобы ее почитали. Это помогает разгрузить процессор и диск на первичном сервере, но, к сожалению, не все данные можно читать с ведомых серверов.
Среднее время отставания реплик в локальной сети — полторы секунды. Если установлен мониторинг за отставанием, то можно выполнять, например:
- Расчет статистики за прошлый день.
- Архивирование неактуальных данных.
- Чтение «давно» записанных файлов из GridFS.
Но единственный записывающий сервер все еще ждет нашего следующего шага.
Шардинг
Шардинг — это стандартный способ горизонтального масштабирования MongoDB. На мой взгляд, достаточно трудно определить в какой момент можно обосновать переход от одного репликасет к шард-кластеру.
Накладные расходы на администрирование и стоимость дополнительных серверов или виртуалок непросто представить менеджменту проекта. Вместо четырех серверов, мы сразу должны перейти к восьми, плюс потребуется минимум три сервера для конфигурационного репликасета.
Как альтернатива шард-кластеру — долгое время будет возможность понемногу увеличивать число ядер CPU, размер оперативной памяти и размер дисков, а также перейти на RAID или заменить HDD на SSD.
К сожалению, есть предел увеличения размера одного сервера. Например, мы уперлись в скорость чтения с дисков. В это время наша БД была расположена в Azure. На серверах был использован SSD-диск объемом 512 Gb с максимальным iops 2300. Конечно, более простой шаг — это апгрейд подписки до 1Tb с максимальным iops 5000, но дальнейший рост БД приводил к невозможности снятия и восстановления бэкапа в разумное время.
Дело в том, что время снятия бэкапа размером 450 Gb приблизилось к 6 часам. Дальнейший рост БД приводил к тому, что приходилось бы увеличивать и размер opLog. Он на тот момент и так был в 32 Gb. Кроме того, длительность opLog в среднем была 12 часов (да, это скромный поток данных на запись всего в 750 Kb/s), и любая неожиданная нагрузка вроде импорта очень большого файла приводила к тому, что сервер, с которого снимался бэкап, терял синхронизацию, и его приходилось уводить в resync на 12 часов с неясным результатом.
С другой стороны, внедрение шард-кластера уменьшало время бэкапа, следовательно размер opLog тоже мог был сокращен. Проект переставал превышать iops. Ну, и, с точки зрения разработки и администрирования, миграция на шард-кластер идет один раз, а добавлять новые шарды можно многократно и по мере необходимости.
Добавление шардов — простой способ компенсировать ограничения скорости дисковой подсистемы, кратно сократить время бэкапа и восстановления БД. В дальнейшем можно будет сокращать рaзмер opLog, количество оперативной памяти и ядер CPU на каждой реплике.
Было бы очень хорошо добиться равномерного разделения нагрузки на шарды. При росте проекта, это дает возможность без дополнительной разработки держать на требуемом уровне отзывчивость БД и администрирования. К сожалению, равномерность нагрузки будет сильно зависеть от схемы данных.
При включении шардинга у нас есть 2 основные группы проблем.
- Шардинг коллекции не возможен, если:
- требуется обновление полей, входящих в ключ шардирования;
- на коллекции есть несколько уникальных ключей;
- под результат запроса findAndModify попадают данные на разных шардах.
- Несбалансированная нагрузка по шардам, если:
- используются частично упорядоченные идентификаторы;
- слабая селективность ключа шардирования;
- запросы без значений ключа шардинга.
ObjectId
Это глобально уникальный идентификатор (по аналогии с GUID), и он частично упорядоченный. На практике это означает, что, если в коллекцию идет интенсивное добавление записей, то они будут попадать преимущественно на один шард. Что еще хуже, свежие документы обычно наиболее востребованы — получаем еще и серьезную разбалансировку по CPU и opLog.
Есть способ шардинга с использованием хеша, но он применим только к единственному полю.
В этом случае поисковые запросы и обновления без указания идентификатора будут нагружать сразу все шарды.
Т.е. польза от шардинга по хешу будет только в том случае, если вы работаете с документом только по его идентификатору.
GridFS
Схема данных для хранения неизменяемых файлов. Кажется, что это первый кандидат на шардинг, но, увы, не все так просто.
В GridFS чанки («куски» загруженного файла) имеют следующую схему:
{
"_id" : <ObjectId>,
"files_id" : <TFileId>,
"n" : <Int32>,
"data" : <binary data>
}
Драйвер MongoDB обычно пытается создать уникальный индекс по { files_id: 1, n: 1 }
Есть 2 способа разделения чанков:
- Только по идентификатору файла — это пример слабой селективности ключа шардирования.
Это гарантирует, что файл не будет разделен между шардами, а значит восстановить его из бэкапа будет проще. Если файл превысит максимальный размер чанка, то он станет неперемещаемым. Возрастает риск разбалансировки по размеру данных на шарде. - По идентификатору файла и порядковому номеру чанка.
Максимальная селективность ключа шардирования — до одного документа (чанк файла) на чанк шардирования.
Если идентификатор файла — стандартный (TFileId — ObjectId), то перед нами трудный выбор:
- Добавление индекса по хешу и шардинг по хешу от files_id ведет к риску получить неперемещаемые файлы и разбалансировку по месту.
- Шардинг по { files_id: 1, n: 1 } грозит разбалансировкой по opLog и iops диска, а если имеет место интенсивное добавление файлов, то шардинг лучше вообще не включать.
Так что имеет смысл предварительно сменить идентификатор на GUID — это избавит вас от многих проблем в будущем.
Уникальность ключа
Нельзя включить шардинг по коллекции, у которой есть несколько индексов с уникальностью.
Если все же требуется несколько полей с проверкой на уникальность — например email и номер телефона, привязанные к одному пользователю, — то этом случае один из признаков придется отселить в отдельную коллекцию.
Схемы работы могут быть разные:
Последовательные вставки.
Пример:
коллекция users (индекс с уникальностью по email)
{ _id: <ObjectId>, //идентификатор пользователя email: <string>, phone: <string> }
коллекция phones (индекс с уникальностью по phone)
{ _id: <ObjectId>, //идентификатор пользователя phone: <string> }
Сначала делается вставка в phones, в случае успеха — вставка в users.
- Отложенная настройка.
В схеме из предыдущего случая — телефон всегда храним отдельно, а пользователю позволяем добавить его уже после регистрации.
Выбор ключа шардирования на существующей коллекции
При проектировании схемы данных коллекции мы можем не знать (не учесть), как будем ее шардить. Но штатных способов сменить ключ шардинга нет, поэтому особенно важно не промахнуться с выбором ключа.
Алгоритм выбора может быть следующим:
- Еще раз вспоминаем ограничения.
- К существующим индексам добавляем другие со всеми возможными (и осмысленными) комбинациями полей.
- Перечисляем всех кандидатов на ключи шардинга. По каждому допустимому индексу можно выбрать ключ по его любому префиксу. Например для индекса { a: 1, b: 1, c: 1 } можно выбрать префиксы { a: 1} и { a: 1, b: 1 } или полный ключ { a: 1, b: 1, c: 1 }.
Важно! Если поле в индексе, но ключ шарда построен по префиксу без этого поля, то такое поле остается изменяемым. - Для каждого кандидата оцениваем:
- Селективность, есть ли ограничения на рост минимального чанка.
- Возможность добавить в каждый поисковый запрос конкретные значения ключа чанка.
- Возможность исключить обновление поля, попавшего в ключ шардинга.
- Долю запросов, которые не изолируются на одном шарде.
- Выбираем ключ с максимальной долей изолированных запросов.
Шардинг по префиксу индекса с уникальностью
Пример выбора ключа, когда пришлось остановить выбор на менее селективном ключе шардинга.
Сущности в нашем проекте Smartcat:
- Аккаунт (Account) — владелец документов.
- Документ (Document) — документ для перевода (идентификатор глобально уникальный).
- Сегмент (Segment) — предложение в документе.
Поля сегмента:
- accountId — идентификатор аккаунта.
- idInAccount — уникальный идентификатор в рамках аккаунта.
- documentId — идентификатор документа.
- order — порядковый номер сегмента в документе.
- и другие...
Индексы:
- { documentId: 1, idInAccount: 1 }
- { documentId: 1, order: 1 }
- { accountId: 1, idInAccount: 1 }, { unique: true }
Большинство запросов включают в себя documentId. Значит, выбирать будем из первых двух индексов, и это исключает шардинг по accountId. Тогда будем убирать уникальность с третьего индекса. Каждый документ имеет глобально уникальный идентификатор и не может принадлежать двум аккаунтам. Следовательно, первый индекс можно сделать уникальным, а со второго убрать уникальность.
idInAccount в нашем коде известен только в одном поисковом запросе, т.е. при выборе полного ключа шардинга сегменты документа могут быть разделены по разным чанкам, и поисковые запросы могут обращаться к нескольким шардам.
Рассматриваем вариант шардинга по { documentId: 1} — это допустимо, т.к. он является префиксом с уникальностью. Мы оцениваем максимальный объем сегментов одного документа — это примерно 50 Mb. Значит, документ отлично входит в размер одного чанка шардинга (он у нас 64 Mb).
Результат:
- Строим индекс с уникальностью по { documentId: 1, idInAccount: 1 }
- Убираем уникальность с { accountId: 1, idInAccount: 1 }
- Включаем шардинг по { documentId: 1}
Удаление БД или коллекций
На боевом сервисе такого обычно не происходит. БД или коллекция создается, добавляются индексы, включается шардинг. А дальше — долгая эксплуатация.
С тестовыми стендами интереснее. Как правило, удаление БД — регулярная процедура. С появлением шард-кластера в составе тестового стенда мы стали обнаруживать разнородный состав коллекций в зависимости от используемого роутера (mongos). Оказалось, что это известная проблема.
Если вкратце, то удаление БД или коллекции требует сброса кэша каждого роутера и дополнительной чистки в конфигурации шард-кластера.
Но для тестовых стендов можно поступить проще:
- Удаляем все коллекции в БД. Ее ведущий шард не меняется.
- Запускаем скрипты добавления индексов.
- Запускаем скрипты включения шардинга — это распределенная транзакция по всем роутерам. Она гарантирует зачистку старых данных во всех кэшах.
Резюме
Многие решения при выборе схемы данных для обеспечения шардинга или репликации могут выглядеть переусложнением на этапе первоначального развития проекта или прототипирования.
Но если идешь на упрощение в ущерб масштабирования, следует явно резервировать время на рефакторинг схемы данных.
Чек-лист при планировании схемы данных и индексов коллекции:
- Экономим трафик репликации — минимизируем размер обновления документа.
- Помним о rollback — бизнес-логика должна поддерживать обрыв операций записи, так же как и внезапное выключение сервера.
- Не препятствуем шардингу — на больших коллекциях не заводим больше одного индекса с уникальностью, но лучше вообще не включать уникальность, если это не критично для бизнес-логики.
- Изолируем поисковые запросы одним шардом — большинство поисковых запросов должно включать в себя значения ключа шардинга.
- Балансировка шардов — тщательно выбираем тип идентификатора, самый лучший из них — GUID.
На этом пока всё!
Не потеряйте кластер, бойцы невидимого бэкенда!
И отдельная благодарность коллеге nameless_one за редакцию и советы!
onyxmaster
Вы большой молодец. Статья отличная, почти ничего лишнего, спасибо.
Про каталог rollback — у нас для каждого data directory есть триггер на количество файлов в нём и его подкаталогах. Советую, полезная вещь.
Ещё из вроде как сугубо технических вещей могу посоветовать не игнорировать совет из документации хранить данные WT на XFS. На ext4 в момент WT checkpoint даже на относительно свежих ядрах всё плохо, latency даже на SSD взлетает в сотни раз.
exmachine Автор
Подготовка сервера к нагрузке — это целый комплекс мероприятий.
Никакие советы из документации не надо игнорировать.
Кроме WT на XFS, надо еще:
* выключить THP,
* настроить синхронизацию времени всех серверов проекта с одного источника,
* проверить реальную производительность дисковой подсистемы по скорости и параллелизму операций с помощью mongoperf,
* увеличить ограничения сервиса в systemd (пример)
Это что всем полезно будет.
У нас же регламент еще касается проверки сети, настройки мониторинга и бекапов.
exmachine Автор
Мониторить каталог rollback нет необходимости.
Если требуется получить не откатываемые данные, то явно ожидайте w:majority.
Если ожидание ограничено мастером, то бизнес-логика должна быть тертима к некоторому откату данных.
В процессе эксплуатации в rollback всегда что либо попадает.
Мы там ищем данные в случае нарушения структуры БД по вине бизнес-логики.
onyxmaster
Про rollback не соглашусь, у нас там бывает что-нибудь крайне редко (потому что w:majority стоит по умолчанию). Кроме того, я ещё помню как в 2015 году мы из-за бага в MongoDB доставали из rollback несколько гигабайт данных =)
exmachine Автор
Rollback — это всегда следствие смены ведущего сервера.
У нас тоже данные туда попадают не часто, т.к. сеть стабильная, а техобслуживание серверов мы стараемся минимизировать.
Но, совершенно точно rollback не зависит от режима ожидания записи.
Вам на постоянной основе требуется мониторить rollback с учетом ожидания w:majority?
А какие проблемы вы таким образом решаете?