Link Search Menu Expand Document
English

Введение

MongoDB — документо-ориентированная система управления базами данных, не требующая описания схемы таблиц. Пионер документно-ориентированной модели данных, возможности горизонтального масштабирования MongoDB добавила позже других NoSQL систем. Для хранения используется собственный формат — Binary JavaScript Object Notation (BSON), который построен на основе языка JavaScript. Репликация данных осуществляется путем использования набора реплик (replicaset), где один из узлов является ведущим. В кластере из одного репликасета, для повышения доступности дополнительно может быть использован арбитр — легковесный процесс, выполняющий только функцию голосования, и не хранящий данных. Поддерживается агрегация данных, вторичные индексы и запросы по диапазону. Горизонтальное масштабирование использует стратегию шардирования наборов реплик, маршрутизация запросов происходит путем использования балансировщика нагрузки с паре с конфигурационным сервером, хранящим информацию о маппинге данных кластера на шарды.

В процессе тестирования MongoDB мы преследовали три основные цели:

  • проверить, соответствует ли MongoDB критериям ACID или нет;

  • проверить, не будет ли она слишком требовательна к аппаратным средствам и будет ли работать по крайней мере так же хорошо, как FoundationDB и PostgreSQL на аналогичном оборудовании;

  • проверить, можно ли её масштабировать, например, увеличив в два или четыре раза количество ядер или репликасетов (шардов).

Как и в предыдущих тестах PostgreSQL и FoundationDB, мы начали тестирование с типовых конфигураций, в ожидании получить сравнимые результаты. Первоначальные тесты проводились на репликасете из 3-х нод с 1-м и 2-мя логическими ядрами. Последующие тесты оценивали как вертикальное, так и горизонтальное масштабирование без явной привязки к конфигурациям кластеров FDB или PostgreSQL по причине большой разницы в производительности, полученной при первых тестах. Так, например, “большой тест” проводился в конфигурации с увеличенным в несколько раз количеством CPU и RAM по сравнению с аналогичным тестом FDB, как в формате одного репликасета, так и разделенным на несколько шардов.

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

Следует отметить, что на текущий момент официальный MongoDB Community Operator не поддерживает использование шардирования, ограничивая возможности развертывания наборами реплик, а использование Enterprise-оператора для наших целей ограничено нюансами лицензирования. По этой причине для целей тестирования был выбран open-source оператор от компании Percona, который поддерживает все необходимые возможности, и образ Percona MongoDB Server, который обеспечивает 100% совместимость с версией MongoDB Community.

Общие результаты

Перед описанием полученных результатов и выводов отметим:

  • Первые два теста MongoDB не использовали шардирование и проводились на MongoDB Community Operator, все последующие — с использованием оператора от Percona.
  • Все тесты, отраженные в таб. 1, были выполнены без каких-либо изменений внутренних параметров MongoDB, как со стороны оператора, так и через конфигурационные файлы. Указывались только лимиты CPU, RAM и объем storage. Мы провели целый ряд экспериментов по тюнингу, но существенного влияния на результаты они не оказали, поэтому в данном отчете не приведены.
  • Также в итоговой таблице отражены только успешно пройденные тесты с nemesis, общие результаты тестирования отказоустойчивости будут описаны отдельно
  • Все указанные в таблице тесты произведены на версии 4.4. Замена 4.4 на 5.0 на результаты существенно не повлияла.
  • Тесты с шардированием используют распределение данных на основе хеша, что даёт более равномерное распределение, тем более что в в stroppy нет запросов по диапазону, которые существенно выиграли бы от распределения на основе диапазона.

Таблица 1: результаты ключевых тестов MongoDB

№№ VCPU/узел RAM/узел, GB HDD/узел, GB Кол-во шардов Кол-во реплик Кол-во клиентов Кол-во узлов Число счетов, млн Число переводов, млн Транзакций в секунду
1 1 8 100 1 3 16 3 10 10 340
2 2 8 100 1 3 16 3 10 10 720
3 4 8 100 1 3 64 3 10 10 1843
4 4 8 100 1 3 128 3 10 10 2661
5 2 8 100 2 3 32 6 10 10 427
6 6 16 100 1 2+1 арбитр 128 3 100 100 2725
7 6 16 100 1 3 128 3 100 100 2761
8 6 12 100 1 2+1 арбитр 128 3 100 100 2592
9 6 12 100 1 3 128 3 100 100 2551
10 2 8 100 2 3 128 6 100 100 575
11 2 8 100 2 3 128 6 100 100 445
12 4 8 100 8 3 128 12 100 100 1171
13 8 16 100 2 3 128 6 1000 10 443
14 8 16 100 4 3 128 12 1000 10 718
15 8 16 100 4 3 128 12 1000 10 670
16 4 8 100 8 3 128 12 1000 10 947
17 12 40 100 1 3 128 3 1000 10 3272
18 3 8 100 2 3 128 6 10 10 653
19 3 8 100 2 3 128 6 10 10 534

Число транзакций в виде диаграммы: Число транзакций

Таблица 2: Задержки для денежных переводов (*)

№№ задержка, мс (средняя) задержка, мс (макс) задержка, мс, 99 перцентиль
1 22 401 79
2 47 793 178
3 35 1037 104
4 45 1807 172
5 75 800 217
6 47 2763 173
7 46 2424 171
8 49 26515 169
9 50 2066 221
10 222 4052 801
11 287 6372 1434
12 109 4148 500
13 288 7293 1099
14 178 4137 867
15 190 5917 631
16 135 5578 898
17 39 1824 111
18 195 4990 662
19 239 2972 657

Задержки в виде диаграммы: Задержки

Таблица 3: Размер набора данных (*)

Счетов, млн Трансферов, млн Размер данных на диске, GB Размер данных на диске, FoundationDB
10 10 3 ГБ 3 ГБ
100 100 24 ГБ 32 ГБ
1000 10 136 ГБ 127 ГБ

(*) В таблице указан суммарный размер двух коллекций и их индексов, полученный через интерфейс администрирования mongo.

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

Наибольшей производительности удалось достичь на кластере из 1 репликасета из трёх реплик с 12 ядрами и 40 ГБ RAM на одну реплику, см. тест №9. Одной из аномалий данной конфигурации было высокое потребление CPU master-репликой. На этом тесте рабочий объём (136ГБ) превышал объём доступной оперативной памяти (40ГБ) фиксировалась достаточно высокая утилизация диска (>85%) на большинстве реплик.

Сравнение тестов №1 (один репликасет, 1CPU/8GB RAM на реплику) и №2 (2CPU на реплику) показывает, что на объеме данных, который полностью помещается в RAM мастер-реплики, увеличение количества ядер дает более чем двукратное увеличение производительности. Сочетание двукратного увеличения количества ядер и увеличения количества воркеров, как видно в тестах №3 и №4, в конечном итоге дает ускорение в 8 раз от минимальной конфигурации (тест №1). Разделение тех же ресурсов между несколькими репликасетами приводит к снижению производительности в несколько раз (тест №5).

Переход к среднему тесту сопряжён с “пограничным” соотношением память/диск. Данные уже перестают помещаться в RAM целиком, но ещё не превышают доступный объём кратно. При проведении тестирования мы также проверили гипотезу о влиянии арбитра или количества реплик на производительность. Впридачу к по-прежнему высокой утилизации CPU мы наблюдали высокую интенсивность чтений с диска. Производительность вертикально масштабированных конфигураций оставалась несколько ниже лучших результатов малого теста, несмотря на 50% увеличение количества ядер на реплику и двукратное увеличение памяти.

Тест №6-№9 демонстрируют разницу в производительности при разных вариантах конфигурирования репликасета — с арбитром и без. Разница между тестами №8 и №9 составляет около 1,5% в пользу варианта с арбитром, в тоже время тесты №6 и №7 показывают разницу в 1% в пользу варианта с тремя репликами в кластере. На основании данных мониторинга этих прогонов можно сделать вывод, вариант использования репликасета с арбитром не дает явного преимущества. При этом конфигурация с арбитром не может быть использована для последующего горизонтального масштабирования с распределенными транзакциями.

Пары тестов №6 и №8, №7 и №9 показывают разницу в производительности при снижении RAM на 25%. На примере тестов №7 и №9 с репликасетами из 3-х реплик мы видим, что снижение производительности составило всего 8,2%. При этом, как указано выше, во всей группе тестов данные по счетам умещались в объем RAM, лог же транзакций выходил за этот объём.

Сравнение результатов большого теста №17 с средним тестом №7 показывает, что несмотря на двукратное увеличение количества ядер и больше, чем трехкратное увеличение RAM на реплику, в ситуации, когда объем данных превышает объем RAM на мастер-реплике, прирост производительности составил всего 30%.

Тесты №10-16 показывают результаты тестирования mongodb при горизонтальном масштабировании. Результаты данных тестов выходят за границы оговоренного лимита latency на транзакцию в 100 ms, в 3-6 раз ниже лучших результатов для вертикального масштабирования и в 4-8 раз ниже результатов горизонтального масштабирования FDB, использовавшего меньшее число ядер.

В сравнении с FoundationDB в конфигурации шардированных кластеров MongoDB также требует установки отдельного репликасета балансировщика (до 2 cpu на реплику, 3 реплики) и конфиг-сервера (1 сpu, 0.5 RAM на реплику). Затраты на эти узлы не учтены в сводной таблице, но могут значимо влиять на стоимость владения для небольших кластеров.

Еще в малом тесте №5 было установлено, что одна из реплик на каждом репликасете утилизирует до 100% CPU, поэтому мы провели тесты №13 и №14 с 4-х кратным увеличением CPU и двукратным увеличением RAM с увеличением объема данных в 100 раз (большие тесты). Их результаты демонстрируют, что при переходе от двух шардов к четырем производительность выросла на 62%, т.е возросла нелинейно количеству выделенных ресурсов. Потребление CPU при этом находилось на уровне ~2,5 ядер на реплику.

Тесты №10 и №15 отражают результаты использования файловой системы xfs в сравнении с ext4, заданной по умолчанию. Тест №10 сравнивался с тестом №11, тест №15 — с тестом №14. Как видно из результатов, на конфигурации с объемом счетов in-memory, xfs быстрее на ~22% (575 против 445), на объеме счетов disk-bound, в свою очередь, xfs показала результат на 7% хуже, чем ext4(670 против 718). При этом на тестах с xfs наблюдалось примерно такое же потребление ресурсов, как и на аналогичных тестах с ext4, за исключением иной динамики работы с диском — на обоих тестах наблюдалось меньшая по записи, и большая по чтению активность, сравнительно с ext4.

Тест №16 демонстрирует, что производительность системы переходит на иной, более низкий уровень, как только объем данных перестает помещаться в память и остаётся на нём при дальнейшем увеличении объёма данных. Так, при десятикратном увеличении объема данных в этом тесте относительно №12, производительность кластера упала лишь на 23%.

Наибольшей производительности как большого теста так и в целом удалось достигнуть на кластере из одного репликасета с 12 cpu и 40 GB RAM на реплику (тест №17), что почти в 3 раза выше чем при распределении тех же ресурсов между 8-мью репликасетами (тест № 12). Похожие результаты мы получили на двух репликах PostgreSQL (4 ядра на реплику) и кластере из пяти узлов FoundationDB. Нагрузочный тест при этом использовал 128 соединений, в то время как оптимальным количеством соединений для PostgreSQL было 256, для FoundationDB — 512.

Результаты тестирования устойчивости к отказам

Тесты №18 и №19 с разрывами сети и отказами оборудования проводились на кластере, состоящим из двух шардов. В процессе тестирования устойчивости к отказу одного из mongo-процессов проверялись следующие виды отказов:

  • рестарт одной любой из запущенных реплик на любом из шардов каждые две минуты — тест завершился аварийно на этапе загрузки счетов
  • рестарт одной из 2-х заранее заданных реплик на одном шарде каждые две минуты — тест завершился аварийно на этапе загрузки счетов
  • отключение одной из 2-х заранее заданных реплик на одном шарде каждые две минуты с добавленным переповторов ряда сетевых ошибок и ошибок, связанных с процессом выбора лидера — тест загрузки счетов завершился успешно, тест переводов завершился не полностью, часть воркеров была остановлена в процессе выполнения теста из-за ошибки TransactionExceededLifetimeLimitSeconds (более 10 секунд, лимита по умолчанию)
  • рестарт одной из двух заранее заданных реплик, расположенных на разных шардах каждые две минуты — тест завершился успешно, результаты отражены в таблице (тест №18)
  • 100% деградация сети между двумя выбранными репликами на одном шарде каждые две минуты — тест завершился успешно, результаты отражены в таблице (тест №19)

Уточним, что отключение одной реплики, которое представляет собой остановку и удаление запущенного контейнера с процессом mongod (сценарий container-kill) не приводило к полному исключению реплики из репликасета до конца теста, т.к. данные, связанные с контейнером, сохранялись и требовалась только докатка данных, измененных в период пересоздания контейнера. Т.е. для рассматриваемого случая сценарий container-kill может быть приравнен к рестарту процесса.

По результатам указанных выше тестов можно сделать вывод, что кратковременные сетевые разрывы между двумя репликами внутри репликасета не приводят к явной потере производительности. Также MongoDB не продемонстрировала явной деградации при попеременном выходе из строя двух определенных реплик из репликасета на любом из шардов, несмотря на частоту “инъекций” раз в две минуты. ` `В то же время попеременная остановка как двух реплик внутри репликасета в шарде, так и случайно выбранных реплик кластера приводила к аварийной остановке теста на этапе загрузки счетов при выполнении среднего теста. Сетевые ошибки и ошибки репликации, которые приводили к остановке среднего теста, были добавлены в механизм переповтора внутри stroppy и при выполнении малого теста на загрузке счетов наблюдалась серия ошибок Could not find host matching read preference { mode: “primary” } (без остановки воркеров), при тесте переводов тест доходил до конца, но в процессе часть воркеров останавливалась с ошибкой TransactionExceededLifetimeLimitSeconds, что нельзя признать успешным результатом в контексте устойчивости MongoDB к подобному сценарию. Безусловно, результаты исследования нельзя назвать абсолютно полными, но, дабы не посвятить все ресурсы исследования только этой теме, было принято решение остановиться на текущем этапе, т.к. данный ответ можно отнести скорее к нестабильному поведению тестируемой СУБД под нагрузкой, чем к “штатному” ответу при выполнении каких-либо внутренних процессов, например восстановление после рестарта или выборам нового лидера, который наблюдался при тестах отказов до добавления подобных ответов в механизм переповтора.

Особенности MongoDB

В процессе тестирования мы столкнулись с целым рядом особенностей и ограничений MongoDB:

  1. Гарантии majority имеют опцию wtimeout. Неиспользование опции может привести к длительным блокировкам, использование без расчета времени выполнения операций — к расхождению данных. Расхождение данных, а именно итогового баланса, было получено опытным путем на большом прогоне 4-х шардового кластера с указанием wTimeout = 0. Одна из транзакций была успешно сохранена на secondary-репликах, но отсутствовала на master-реплике.

  2. Для транзакций с несколькими документами mongodb требует настройку предпочтительного чтения только с primary реплики и включение опции writeConcernMajorityJournalDefault — MongoDB подтверждает операцию записи только после того, как большинство участников с правом голоса записали данные в журнал на диске.

  3. WiredTiger, как и FoundationDB, использует оптимистический контроль транзакций и MVCC, но сама MongoDB использует также механизм блокировок разного уровня для обеспечения консистентности данных. Foundationdb, в свою очередь, не использует блокировки, полагаясь на указанные стратегии и внутренние механизмы, например отмену транзакции длительностью более 5 секунд. К сожалению, мы не смогли установить причины использования блокировок поверх других методов управления транзакциями у MongoDB, вероятно, это наследие предыдущей подсистемы хранения данных, MMAPv1, которая использовала блокировки разного уровня. На основании результатов тестирования можно предположить, что использование текущего механизма блокировок, в том числе на шардированном кластере, влияет на суммарную производительность негативно.

  4. В процессе тестирования мы столкнулись с проблемой постепенного роста количества потоков внутри контейнеров mongod из-за большого количества запросов от оператора. Для консультации были привлечены инженеры Percona, проблема локализована и решается в рамках https://jira.percona.com/browse/K8SPSMDB-602. На результаты тестирования этот дефект оператора не повлиял.

Выводы

В итоговой таблице отражены только наиболее значимые, по нашему мнению, результаты тестирования. Суммарно было проведено не менее 100 прогонов с различными вариантами конфигурации кластера и серверных параметров для достижения наиболее оптимальных значений производительности. Мы считаем, что равнозначное сравнение конфигураций MongoDB с конфигурациями FoundationDB и PostgreSQL не представляется возможным по причине более высоких требований к ресурсам у MongoDB. Также результаты пройденных тестов показывают, что производительность MongoDB уступает производительности FDB и PostgreSQL.

Например, малый тест №1 (один репликасет, 3 узла по 1-му cpu, 8 GB RAM на реплик, идентично минимальной конфигурации FDB) показывает производительность почти в 7 раз ниже (340 на тесте №1 против 2263 на малом тесте FDB). Малый тест №5 с горизонтальным масштабированием (2 репликасета по 2 cpu и 8 ГБ на реплику) — ниже в 5 раз (427 на тесте №5 против 2263). Производительность, превышающая результат FDB на 17%, была получена на конфигурации малого теста №4(один репликасет, 4 ядра cpu, 8 ГБ на реплику) — 2661 против 2263. Малый тест для Postgres не проводился.

Лучший результат среднего теста на одном репликасете на тесте №7(6 cpu, 16 ГБ на реплику) уступает среднему тесту FDB(5 узлов, по 1 cpu и 16 ГБ на узел) в 2 раза (2761 против 5782) и среднему тесту PostgreSQL (2 узла, 4 cpu и 30 ГБ на узел) на 40% (2761 против 4663). Результат лучшего среднего теста на горизонтально масштабированном кластере, №12, медленее результатов FDB почти в 5 раз (1171 против 5782) и PostgreSQL в 4 раза.

Большой тест №17 (лучший результат в целом для MongoDB) медленнее большого теста FDB в конфигурации 5 узлов по 1-му cpu и 16 GB на узел на 3% (3272 у mongo против 3369) и требует значительно больших ресурсов — репликасет состоит из 3-х узлов по 12 CPU и 40 GB RAM. Большой тест для PostgreSQL не проводился.

Нами был произведен ряд экспериментов с целью найти возможные причины такой заметной разницы в производительности — от доработок самого Stroppy, до изменения настроек подсистемы хранения WiredTiger и смены файловой системы. Ни одна из гипотез не показала значительного прироста производительности.

Масштабирование как подаваемой нагрузки, так и самой СУБД, оптимально при использовании 128 воркеров, что также меньше, чем у FDB с 512 воркерами и PostgreSQL с 256 воркерами, на меньшем объеме ресурсов. Увеличение количества воркеров, несмотря на увеличение параметров wiredTigerConcurrentReadTransactions и wiredTigerConcurrentWriteTransactions приводит к снижению производительности.

Максимальный результат производительности был достигнут путем вертикального масштабирования репликасета и даже на сравнительно большом кластере наблюдались утилизация CPU и диска, близкие к 100%. Горизонтальное масштабирование продемонстрировало достаточно низкую производительность распределенных транзакций. С увеличением количества шардов мы, безусловно, фиксируем рост производительности, но она остается также относительно невысокой.

Также стоит отметить разницу архитектур MongoDB и FDB — в то время как в FDB участники кластера практически равнозначны, автоматически распределяют роли и обрабатывают нагрузку совместно, в MongoDB основным приёмником нагрузки, как в репликасете, так и в шарде, является master-реплика, которая, в свою очередь, должна реплицировать данные на вторичные реплики. Чтение же со вторичных реплик может отставать от мастера и не обеспечивает необходимых нам гарантий целостности данных для чтения и записи.