Когда одного Postgres'a мало: сравнение производительности PostgreSQL и распределенных СУБД

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Важно: данный пост написан разработчиком YDB и основан на совместном исследовании с Евгением Ефимкиным, экспертом в области PostgreSQL, не работающим в YDB.

Общеизвестно, что PostgreSQL - крайне эффективная СУБД с богатой функциональностью. Энергичное сообщество PostgreSQL значительно повлияло на то, какие возможности современные СУБД должны предоставлять разработчикам и пользователям. При этом не секрет, что PostgreSQL масштабируется только вертикально и её производительность ограничена возможностями одного сервера.

Написано много хороших постов, в которых сравнивают архитектуру монолитных и распределенных СУБД. Например, рекомендуем этот. К сожалению, обычно авторы ограничиваются теоретическим сравнением и не приводят конкретные цифры. Данный пост же наоборот основан на эмпирическом исследовании с использованием бенчмарка TPC-C, который является промышленным стандартом для оценки производительности транзакционных СУБД (On-Line Transaction Processing, OLTP).

Наш подход крайне прост. Есть три сервера, в каждом из которых 128 ядер CPU, 512 GiB RAM и четыре NVMe-диска. Настраиваем СУБД так, чтобы переживать выход из строя одного из серверов. Размер датасета должен быть не менее 1 ТиБ. Запускаем бенчмарк TPC-C на 12 часов (мы покажем, что даже это не так уж и много, чтобы тщательно проверить работу СУБД).

Учитывая, что не существует универсальной общепринятой конфигурации PostgreSQL, нам пришлось попробовать разные варианты. Мы начали с менее надежного, но наиболее производительного без лога записи (WAL) и репликации. Постепенно мы пришли к варианту конфигурации с двумя синхронными репликами, менее производительному, но соответствующему нашим требованиям в плане отказоустойчивости. Затем мы сравнили полученные результаты TPC-C на PostgreSQL с результатами двух хорошо известных распределенных СУБД с открытым исходным кодом: CockroachDB и YDB.

Пост получился достаточно большой, поэтому сначала мы расскажем о результатах, а потом погрузимся в технические детали. Но сперва вспомним, что такое TPC-C.

TPC-C

TPC-C - это «Единственная объективная методика оценки производительности OLTP», — CockroachDB.

«Наиболее реалистичное и объективное измерение производительности OLTP-систем», — PingCAP.

«На протяжении нескольких десятилетий TPC-C является одним из наиболее популярных бенчмарков OLTP-систем и используется для определения стоимости и эффективности системы. Он полезен в оценке общей производительности OLTP СУБД», - YugabyteDB.

Мы уже писали о TPC-C и его реализации для YDB. Кроме того, мы рассказали об особенностях TPC-C для PostgreSQL. С тех пор единственное значимое изменение в TPC-C для PostgreSQL - замена c3p0 на HikariCP. Мы не стали повторять уже написанное, ниже лишь кратко опишем метрики TPC-C, необходимые для понимания результатов наших тестов.

Цель TPC-C - выполнять как можно большее число транзакций "новый заказ" в минуту (метрика называется tpmC) при заданных ограничениях на время выполнения (latency) этих транзакций. Например, на 90-м персентиле время выполнения транзакций "новый заказ" не должно превышать 5 секунд. Важной особенностью бенчмарка является то, что максимальный tpmC на один склад ограничен: чтобы получить больше tpmC, надо добавить больше складов, а каждый склад это дополнительно 100 МиБ данных. В данном посте в случаях, когда число tpmC близко к максимально возможному, для простоты вместо tpmC мы указываем число складов и эффективность - сколько набрано tpmC от максимального для данного числа складов.

Важно не забывать, что TPC-C - это только стандарт, описывающий бенчмарк, у которого нет официальной реализации. Ни одна из используемых нами реализаций TPC-C, включая реализацию для CockroachDB, официально не сертифицирована организацией TPC. Но все реализации очень точно следуют спецификации TPC-C версии v5.11.0. Это означает, что результаты, опубликованные нами, не являются официально принятыми TPC результатами и несопоставимы с другими результатами теста TPC-C, опубликованными на сайте TPC.

Тестовый стенд

Наш стенд для тестирования производительности — это кластер из трех физических машин со следующей конфигурацией каждой из них:

  • 128 логических ядер: два 32-ядерных процессора Intel Xeon Gold 6338 с частотой 2,00 ГГц с включенным гипертредингом;

  • 512 ГБ ОЗУ;

  • 4xNVMe диска Intel-SSDPE2KE032T80;

  • скорость передачи данных в сети 50 Гбит/с;

  • в случае Postgres включены huge pages, при тестировании распределенных СУБД включены transparent huge pages;

  • Ubuntu 20.04.3 LTS;

  • Ядро Linux версии 5.4.161-26.3.

Настройки PostgreSQL

Мы используем PostgreSQL версии 16.2 (Ubuntu 16.2-1.pgdg20.04+1). В итоговой версии стенда мы используем два RAID-0, каждый из двух NVMe-дисков. Один RAID 0 используется для WAL, второй - под данные. Ниже приведены настройки Linux:

# we have a write intensive workload: # background ratio is decreased, dirty ratio is increased sudo sysctl -w vm.dirty_background_ratio=5 sudo sysctl -w vm.dirty_ratio=40  # Based on our config, see https://www.postgresql.org/docs/current/kernel-resources.html#LINUX-HUGE-PAGES # sudo -u postgres /usr/lib/postgresql/16/bin/postgres  #                  -D /opt/postgres/postgres/16/main  #                  -c config_file=/etc/postgresql/16/main/postgresql.conf  #                  -C shared_memory_size_in_huge_pages sudo sysctl -w vm.nr_hugepages=67129  # disable overcommit sudo sysctl -w vm.overcommit_memory=2  # max GHz sudo tuned-adm profile throughput-performance 

Создание RAID 0:

sudo mdadm --create /dev/md/md_db  --assume-clean -l0 --raid-devices=2 /dev/nvme1n1p2 /dev/nvme0n1p2 sudo mdadm --create /dev/md/md_wal  --assume-clean -l0 --raid-devices=2 /dev/nvme2n1p2 /dev/nvme3n1p2 

Форматирование RAID-дисков:

mkfs.ext4 -F -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard 

При монтировании дисков мы используем noatime.

Здесь можно посмотреть на полный конфиг PostgreSQL. TPC-C запускается на 5 клиентах, каждый по 60 сессий.

Настройки YDB

YDB версии 24.1.10. На одной машине работает 1 узел хранения данных (с привязкой к 32 ядрам CPU) и 6 вычислительных узлов (каждый привязан к своим 16 ядрам). Полную конфигурацию (близка к конфигу по умолчанию) можно найти здесь. Мы запускаем 3 клиента TPC-C, в сумме используется 3000 соединений с базой.

Настройки CockroachDB

Мы используем CockroachDB 23.1.10. К сожалению, в более свежей версия 23.2.2 мы нашли [проблему]((https://github.com/cockroachdb/cockroach/issues/119924), из-за которой у нас не получилось провести тесты TPC-C. Похожая проблема присутствует и в 23.1.10, но согласно мнению команды CockroachDB, в 23.1.10 она вызвана перегрузкой кластера.

Для каждого диска мы запускаем один экземпляр CockroachDB в привязке к 32 ядрам. Каждый инстанс использует 37,5 ГБ оперативной памяти для кэша и еще 37,5 для SQL. Мы применяем рекомендуемые настройки базы. Здесь представлена конфигурация, используемая нашими установочными скриптами. Отметим, что мы не используем partitioning, который является коммерческой фичей CockroachDB. Мы избегаем этого, так как существуют сценарии, когда partitioning данных, как в TPC-C, невозможен.

Результаты

Напомним, что цель TPC-C - достичь максимального tpmC, оставаясь в пределах ограничений на время выполнения транзакций. На диаграмме видно, что PostgreSQL победил, набрав в 1.05 раз больше tpmC при обслуживании 18K складов с эффективностью 92.37%, чем YDB, обслуживающий 16K складов с эффективностью 98.57%. При этом YDB набрал в 1.29 раз больше tpmC, чем CockroachDB (12K складов, эффективность 97.8%). Однако, важно отметить, что время выполнения транзакций в PostgreSQL хотя и в пределах ограничений, но намного хуже, чем у обеих распределенных СУБД:

Обычно существует размен (trade-off) между пропускной способностью (tpmC) и временем выполнения транзакций: чем больше значение tpmC, тем выше время выполнения транзакций. Так, при обслуживании 16K складов YDB достигает своего самого высокого значения tpmC, сохраняя время выполнения транзакций в пределах ограничений, заданных TPC-C. Но для некоторых продакшен сценариев такие задержки могут быть слишком большими. Поэтому мы также привели результаты для YDB и 13K складов (эффективность 99.24%): при уменьшении числа складов (и соответственно tpmC) время выполнения транзакций значительно улучшилось и стало сопоставимым с CockroachDB, при этом YDB все равно набрала немного больше tpmC, чем CockroachDB.

Ниже приведено время выполнения транзакций "Новый заказ", измеренное клиентом PostgreSQL TPC-C:

Хорошо видно, что задержки не смогли выйти на плато и график по форме напоминает синусоиду. Каждый пик совпадает с началом записи чекпоинта, и мы видим, что сессии "висят" в ожидании IPC: SyncRep. Несмотря на все наши старания, у нас не получилось решить эту проблему. В случае PostgreSQL мы смогли получить более адекватное время выполнения транзакций, только уменьшив число складов до значений, ниже 10K. В следующем разделе мы подробно опишем, как мы решали различные проблемы с производительностью Postgres'a и занимались тюнингом.

Если сравнить эффективность, то PostgreSQL на машине с лидером потребляет всего 15% CPU. Мы пришли к выводу, что репликация является единственным узким местом - мы наглядно покажем это в следующем разделе. Это означает, что скорее всего PostgreSQL показал бы точно такой результат на серверах, где меньше ядер CPU, чем у нас. Распределенные СУБД наоборот менее эффективны в плане CPU и загружают почти все ядра на 100%, но при этом показывают гораздо лучшее время выполнения транзакций, что отчасти можно считать разменом CPU на лучшие задержки.

Таким образом, с одной стороны наши результаты показывают исключительную эффективность PostgreSQL. Но с другой стороны они сильно ниже наших ожиданий, основанных на общепринятом мнении, что распределенные СУБД показывают себя во всей красе только при большом количестве машин в кластере. В какой-то степени мы развенчали этот миф.

Стоит отметить, что все рассматриваемые в данном посте распределенные СУБД достигают больше одного миллиона tpmC на обычном железе путем добавления дополнительных машин в кластер. Ниже приведен пример предварительных тестов масштабируемости YDB. Мы постепенно увеличивали размер кластера до 9, 18 и 36 машин, не меняя конфигурацию YDB. Результаты получены на версии, которая в разработке, но мы ожидаем аналогичные цифры у YDB версии 23. Хотя эти результаты неокончательные и могут быть улучшены, они хорошо подтверждают наше утверждение о масштабируемости распределенных СУБД:

В начале поста мы обозначили, что СУБД должна переживать отказ одной из машин, что в зависимости от расположения машин подразумевает возможность пережить выход из строя датацентра. Кроме того, мы подразумеваем, что при отказе одной из машин, клиентская нагрузка должна сразу переключиться на другую машину, чтобы избежать даунтайма. В некоторых случаях это слишком жесткие требования. Поэтому далее мы рассмотрим отказонеустойчивые ("fault intolerant") конфигурации PostgreSQL, которые при этом показывают беспрецедентную производительность.

Отказонеуcтойчивые, но фантастически высокопроизводительные конфигурации PostgreSQL

Настройка PostgreSQL уже давно стала отдельной инженерной специальностью. В случае бенчмаркинга основная проблема заключается в том, что если ты не уперся в железо, то трудно понять, достаточно ли хорош твой конфиг или надо продолжать тюнить PostgreSQL и Linux. А настроек бесчисленное количество, начиная от ввода-вывода и параллельного выполнения (I/O and concurrency) и заканчивая планами выполнения SQL запросов.

Чтобы с чего-то начать, мы решили сначала попробовать "unlogged"-таблицы без репликации (конфигурация "NoWAL"). При таком подходе нет накладных расходов на WAL и устраняется потенциальный ботлнек с репликацией, что максимально снижает время коммитов и позволяет достичь наибольшей производительности. Такая конфигурация помогает оценить эффективность операций чтения-записи данных базы с диска и реализацию выполнения SQL.

Затем мы добавили WAL, но настроили его так, чтобы максимально распределить ввод-вывод во времени. Для этого выставлен большой размер WAL и чекпоинты пишутся редко. Мы назвали эту конфигурацию "HugeWAL": из-за огромного времени восстановления (десятки минут, исходя из нашего опыта) после падения базы/ОС данная конфигурация не подходит для использования в проде, но дает ценную информацию для дальнейшей оценки производительности различных частей PostgreSQL.

В самом конце наших экспериментов мы, наконец, добавили две синхронные реплики ("R1"), на которые можно переключиться, если сломается лидер. Во всех трех конфигурациях мы использовали три NVMe диска в RAID 0 для хранения данных базы и один NVMe диск для хранения WAL. Пора посмотреть на результаты:

Согласно нашим наблюдениям во время всех прогонов было достаточно много свободного CPU и у диска оставалась пропускная способность на запись. Эксперименты показали следующее:

  1. Благодаря конфигурации "NoWAL" мы узнали потенциальные ограничения производительности, вызванные либо выполнением SQL, либо задержками диска (RAID 0) при записи данных базы. Потребление CPU составляет 40 ядер, PostgreSQL пишет 1,5-2 ГБ/с (200-250K операций записи в секунду) и читает 2,3 ГБ/c (275K чтений/c).

  2. Сравнение "HugeWAL" и "NoWAL" указывает, что снижение производительности вызвано накладными расходами на WAL. PostgreSQL пишет 800 МБ/c данных WAL (примерно 6.7K операций записи в секунду, т.е. размер записи 128 КиБ, что является лимитом нашей модели диска).

  3. Относительно плохой результат "R1" вызван исключительно репликацией и никак не связан ни с ограничениями на выполнение SQL, ни WAL.

35K складов (450K tpmC) - это выдающийся результат, но только у "NoWAL" совсем нет отказоустойчивости. 29K складов, полученные "HugeWAL", тоже очень хорошо, и это именно тот результат, который мы ожидали получить в "R1". Зная, что Postgres точно может больше, мы занялись поиском проблемы с репликацией.

Первое, что бросилось нам в глаза, это то, что реплики пишут в 1.5-2 раза больше WAL, чем лидер. Например, когда лидер пишет 250-450 МБ/с (в среднем 4K записей/c), реплики пишут 500-800 МБ/с. К сожалению, точного объяснения этому у нас пока нет.

Но зато это подсказало нам сосредоточиться на WAL. Мы воспользовались procstat, чтобы получать в реальном времени данные о работе дисков. Очень рекомендуем procstat, потому что этот инструмент крайне полезен и при этом прост в использовании. Посмотрим внимательно на скриншоты procstat, показывающие работу диска с WAL.

Лидер:

Реплика:

Хорошо видно, когда Postgres пишет чекпоинты. Но самое интересное, что реплики не только пишут больше, но еще и с задержками выше в 10-30 раз: единицы миллисекунд против сотен микросекунд на лидере. Это натолкнуло нас на мысль, что у реплик узким местом является именно WAL.

Чтобы как-то решить эту проблемы, мы попробовали следующие настройки:

bgwriter_delay = 20 bgwriter_lru_maxpages = 4192  wal_buffers = 256MB wal_writer_delay = 400  commit_delay = 200  checkpoint_completion_target = 0.99 checkpoint_flush_after = 2MB 

Это не помогло и мы зашли с другой стороны: стали использовать два диска в RAID 0 (вместо трех) для данных базы и другие два диска в RAID 0 для WAL. Более того, чтобы ускорить коммиты, мы переключили synchronous_commit с on на remote_write. При такой настройке коммиты на репликах могут потеряться в случае падения машины (например, из-за проблем ОС). Но при этом уменьшение времени коммита должно ощутимо улучшить производительность - в некоторых случаях допустимый компромисс.

При такой конфигурации у нас получилось выполнить двухчасовой прогон TPC-C с 20K складами. Однако, когда мы увеличили время до 12 часов, мы обнаружили ряд проблем. СУБД работала 5 часов, после чего на репликах заканчивалось место под WAL. В следующие 3 часа дайнтайма, они усердно применяли и чистили свой WAL. Если уменьшить число складов до 17K, то можно добиться успешного выполнения бенчмарка в течение 12 часов. Любопытно, что после окончания выполнения бенчмарка, реплики активно нагоняют лаг еще 3 часа:

tpcc=# SELECT pid,application_name,client_addr,client_hostname,state,sync_state,replay_lag FROM pg_stat_replication;    pid   | application_name |           client_addr            | client_hostname |   state   | sync_state |   replay_lag ---------+------------------+----------------------------------+-----------------+-----------+------------+-----------------  1250777 | s1               | 2a02:6b8:c34:14:0:5a59:eb1f:2ca6 |                 | streaming | sync       | 02:56:10.114747  1250824 | s2               | 2a02:6b8:c34:14:0:5a59:eb1f:2956 |                 | streaming | sync       | 02:52:11.340846 

Из-за этого реплики не могут использоваться для горячей замены лидера: в случае его падения, им потребуется значительное время, чтобы нагнать лаг, а это потенциально часы даунтайма. Кроме того, если WAL постоянно растет, то рано или поздно место под него закончится. А когда заканчивается WAL, лидер перестает обрабатывать запросы пользователей, что согласно нашим экспериментам приводит к многочасовому даунтайму.

В итоге, для решения проблемы с лагом мы поставили synchronous_commit в значение remote_apply. Это финальный конфиг PostgreSQL, при котором он смог обслуживать 18K складов. replay_lag при данной настройке пренебрежительно мал и можно быстро переключиться на реплики при возникновении проблем с лидером. В данной конфигурации лидер потребляет достаточно мало ресурсов:

  • 20 ядер CPU;

  • 400 МБ/с (6K записей/с) пишется WAL и 600 МБ/с данных базы (80K записей/с);

  • чтение данных базы составляет 700 МБ/с;

  • пиковое потребление сети составляет 9 Гбит/с.

Каждая из реплик:

  • пишет 800 МБ/с WAL и 500 МБ/с данных;

  • пиковое потребление сети 4 Гбит/с.

Заключение

Число вариантов оборудования, настроек PostgreSQL и видов нагрузки бесконечно. В этом посте мы изучили ограничения PostgreSQL на конкретном железе под нагрузкой TPC-C. Читатели нашего блога, возможно, помнят, что это наши стандартные серверы для исследования производительности. И, конечно, как и все разработчики СУБД, мы очень любим TPC-C.

Мы признаем, что PostgreSQL действительно очень эффективная СУБД в плане CPU, даже сильно выше наших ожиданий. Отказонеустойчивые конфигурации PostgreSQL показали фантастические результаты, что делает Postgres отличным выбором, когда отказ сервера (одного датацентра) и даунтайм не проблема. Это так же очевидно, как то, что Земля вертится.

Но если нужны надежность и отказоустойчивость, а даунтайм неприемлем для бизнеса, то для работы любой СУБД требуется несколько машин. Нас очень удивило, что репликация в PostgreSQL является настолько узким местом, что так сильно влияет на результаты TPC-C. Кроме того, в некоторых продакшен сценариях значительные пики задержек могут быть серьезной проблемой. Возможно, есть какое-то решение этой проблемы, о котором мы не знаем. Напишите, пожалуйста, в комментариях к посту, если знаете о каком-то волшебном переключателе, который мы не попробовали.

К счастью, когда одного Postgres'a становится мало, выход есть. Несмотря на распространенное заблуждение, распределенные СУБД показывают хорошие результаты даже на небольших трехнодных кластерах. Более того, на примере YDB мы показали, что распределенные СУБД предоставляют отличную возможность легко масштабироваться, позволяя сконцентрироваться на разработке приложения, а не нюансах настройки СУБД.

Благодарности

Это исследование выполнено при содействии и тесном сотрудничестве с Евгением Ефимкиным, экспертом в PostgreSQL.

Мы выражаем особую благодарность Андрею Бородину, активному контрибьютеру в PostgreSQL и разработчику Одиссея, масштабируемого пула сессий. На протяжении нашей работы Андрей оказывал нам всяческую поддержку.

Общение с Frits Hoogland было крайне продуктивным, и он поделился с нами очень полезным инструментом procstat.

Приложение: latency транзакций

PostgreSQL (18K)

Transaction

50%, мс

95%, мс

99%, мс

NewOrder

32

3000

3500

Payment

32

3000

3500

Order-Status

9

3000

3500

Delivery

32

3000

3500

Stock-level

16

3000

3500

YDB (16K)

Transaction

50%, мс

95%, мс

99%, мс

NewOrder

128

512

1000

Payment

64

256

512

Order-Status

64

128

256

Delivery

512

2000

3500

Stock-level

32

256

512

YDB (13K)

Transaction

50%, мс

95%, мс

99%, мс

NewOrder

64

256

256

Payment

32

64

128

Order-Status

32

128

128

Delivery

256

512

1000

Stock-level

16

128

256

CockroachDB (12K)

Transaction

50%, мс

95%, мс

99%, мс

NewOrder

36

105

193

Payment

19

65

117

Order-Status

9

26

50

Delivery

65

184

302

Stock-level

19

44

75


Источник: habr.com

Комментарии: