Apache Cassandra 4.0 приближается к бете (прим. переводчика: на текущий момент уже доступна бета 4, выпущенная в конце декабря 2020), и это первая версия, которая будет поддерживать JDK 11 и более поздних версий. Пользователей Apache Cassandra, очевидно, волнует задержка, так что мы возлагаем большие надежды на ZGC — новый сборщик мусора с низкой задержкой, представленный в JDK 11.


В JDK 14 он был выпущен уже в GA-версии, и нам было очень интересно оценить, насколько он подходит для кластеров Apache Cassandra. Мы хотели сравнить производительность Apache Cassandra 3.11.6 и 4.0 и проверить, подходит ли Shenandoah, сборщик мусора от Red Hat, для продакшена. Спойлер: Cassandra 4.0 значительно лучше по производительности сама по себе, а с новыми сборщиками мусора (ZGC и особенно Shenandoah) будет совсем хорошо.


Методология бенчмарков


Мы провели следующие бенчмарки, используя утилиту tlp-cluster для развертывания и настройки кластеров Apache Cassandra в AWS и tlp-stress — для создания нагрузок и сбора метрик. Все эти инструменты доступны в опенсорсе, все бенчмарки легко воспроизвести при наличии учетки AWS.


Кластеры состояли из трех нод на инстансах r3.2xlarge и одной стресс-ноды на c3.2xlarge.


Мы использовали дефолтные параметры Apache Cassandra, кроме настроек кучи и сборщика мусора.


Для развертывания и настройки кластера мы взяли последний релиз tlp-cluster. Недавно мы добавили скрипты, чтобы автоматизировать создание кластера и установку Reaper и Medusa.


Установите и настройте tlp-cluster по инструкциям в документации, чтобы создать такие же кластеры, которые мы использовали для бенчмарков:


# 3.11.6 CMS JDK8
build_cluster.sh -n CMS_3-11-6_jdk8 -v 3.11.6 --heap=16 --gc=CMS -s 1 -i r3.2xlarge --jdk=8 --cores=8

# 3.11.6 G1 JDK8
build_cluster.sh -n G1_3-11-6_jdk8 -v 3.11.6 --heap=31 --gc=G1 -s 1 -i r3.2xlarge --jdk=8 --cores=8

# 4.0 CMS JDK11
build_cluster.sh -n CMS_4-0_jdk11 -v 4.0~alpha4 --heap=16 --gc=CMS -s 1 -i r3.2xlarge --jdk=11 --cores=8

# 4.0 G1 JDK14
build_cluster.sh -n G1_4-0_jdk14 -v 4.0~alpha4 --heap=31 --gc=G1 -s 1 -i r3.2xlarge --jdk=14 --cores=8

# 4.0 ZGC JDK11
build_cluster.sh -n ZGC_4-0_jdk11 -v 4.0~alpha4 --heap=31 --gc=ZGC -s 1 -i r3.2xlarge --jdk=11 --cores=8

# 4.0 ZGC JDK14
build_cluster.sh -n ZGC_4-0_jdk14 -v 4.0~alpha4 --heap=31 --gc=ZGC -s 1 -i r3.2xlarge --jdk=14 --cores=8

# 4.0 Shenandoah JDK11
build_cluster.sh -n Shenandoah_4-0_jdk11 -v 4.0~alpha4 --heap=31 --gc=Shenandoah -s 1 -i r3.2xlarge --jdk=11 --cores=8

Примечание. Чтобы воспроизвести схожие условия, мы использовали для всех тестов один набор инстансов EC2.


Мы сделали апгрейд с Cassandra 3.11.6 на Cassandra 4.0~alpha4 и меняли JDK следующим кодом:


#!/usr/bin/env bash

OLD=$1
NEW=$2
curl -sL https://github.com/shyiko/jabba/raw/master/install.sh | bash
. ~/.jabba/jabba.sh
jabba uninstall $OLD
jabba install $NEW
jabba alias default $NEW
sudo update-alternatives --install /usr/bin/java java ${JAVA_HOME%*/}/bin/java 20000
sudo update-alternatives --install /usr/bin/javac javac ${JAVA_HOME%*/}/bin/javac 20000

Мы использовали следующие значения JDK при вызове jabba:


  • openjdk@1.11.0-2
  • openjdk@1.14.0
  • openjdk-shenandoah@1.8.0
  • openjdk-shenandoah@1.11.0

OpenJDK 8 был установлен с помощью Ubuntu apt.


Вывод команды java -version для разных JDK, которые мы использовали для бенчмарков:


jdk8


openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1~18.04-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

jdk8 с Shenandoah


openjdk version "1.8.0-builds.shipilev.net-openjdk-shenandoah-jdk8-b712-20200629"
OpenJDK Runtime Environment (build 1.8.0-builds.shipilev.net-openjdk-shenandoah-jdk8-b712-20200629-b712)
OpenJDK 64-Bit Server VM (build 25.71-b712, mixed mode)

jdk11


openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)

jdk11 с Shenandoah


openjdk version "11.0.8-testing" 2020-07-14
OpenJDK Runtime Environment (build 11.0.8-testing+0-builds.shipilev.net-openjdk-shenandoah-jdk11-b277-20200624)
OpenJDK 64-Bit Server VM (build 11.0.8-testing+0-builds.shipilev.net-openjdk-shenandoah-jdk11-b277-20200624, mixed mode)

jdk14


openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment (build 14.0.1+7)
OpenJDK 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)

CMS


CMS (Concurrent Mark Sweep) — это текущий дефолтный сборщик мусора в Apache Cassandra. Он был удален из JDK 14, так что все тесты проводились с JDK 8 или 11.


Для CMS параметры были такие:


-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=1
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSWaitDuration=10000
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSEdenChunksRecordAlways
-XX:+CMSClassUnloadingEnabled
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=8
-Xms16G
-Xmx16G
-Xmn8G

Флаг -XX:+UseParNewGC удален из JDK 11 и является неявным. С этим флагом JVM не запустится.


Для CMS мы задали максимальный размер кучи 16 ГБ, иначе между сборками будут слишком длинные паузы.


G1


G1GC (сборщик мусора по принципу Garbage-First) настроить легче, чем CMS, потому что он динамически изменяет размер младшего поколения, но лучше работает с большими кучами (от 24 ГБ). Это объясняет, почему он не стал дефолтным сборщиком мусора. Задержки у него выше, чем у настроенного CMS, зато пропускная способность лучше.


Для G1 параметры были такие:


-XX:+UseG1GC
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:MaxGCPauseMillis=300
-XX:InitiatingHeapOccupancyPercent=70
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=8
-Xms31G
-Xmx31G

Для версии 4.0 мы использовали JDK 14 в тестах с G1.


Мы взяли размер кучи 31 ГБ, чтобы использовать преимущества compressed oops и получить больше адресуемых объектов для кучи минимального размера.


ZGC


ZGC (Z Garbage Collector) — это новейший сборщик мусора в JDK, который минимизирует задержку благодаря паузам stop-the-world меньше 10 мс. Предполагается, что благодаря этому размер кучи не будет влиять на продолжительность пауз, что позволит увеличить его до 16 ТБ. Если так и будет, хранилище вне кучи не понадобится, и некоторые аспекты разработки в Apache Cassandra станут проще.


Для ZGC параметры были такие:


-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
-XX:ConcGCThreads=8
-XX:ParallelGCThreads=8
-XX:+UseTransparentHugePages
-verbose:gc
-Xms31G
-Xmx31G

-XX:+UseTransparentHugePages здесь нужен как обходной путь, чтобы избежать включения больших страниц в Linux. В официальной документации по ZGC сказано, что потенциально могут возникать пики задержки, но на практике мы такого не увидели. Может быть, следует протестировать пропускную способность на больших страницах и посмотреть, как это скажется на результате.


ZGC не может использовать compressed oops и не соблюдает порог 32 ГБ. Мы взяли кучу размером 31 ГБ, как и для G1, чтобы у системы был тот же объем свободной оперативки.


Shenandoah


Shenandoah — это сборщик мусора с низкой задержкой от Red Hat. Он доступен как бэкпорт в JDK 8 и 11 и в mainline-сборках OpenJDK начиная с Java 13.


Как и ZGC, Shenandoah стремится к параллелизму, чтобы паузы не зависели от размера кучи.
Для Shenandoah параметры были такие:


-XX:+UnlockExperimentalVMOptions
-XX:+UseShenandoahGC
-XX:ConcGCThreads=8
-XX:ParallelGCThreads=8
-XX:+UseTransparentHugePages
-Xms31G
-Xmx31G

Shenandoah может использовать compressed oops, а значит лучше работает с кучами размером чуть меньше 32 ГБ.


Конфигурация Cassandra 4.0 JVM


Cassandra 4.0 поставляется с отдельными файлами jvm.options для Java 8 и Java 11, а именно:


  • conf/jvm-server.options
  • conf/jvm8-server.options
  • conf/jvm11-server.options

Апгрейд до 4.0 будет работать с имеющимся файлом jvm.options версии 3.11, если его переименовать в jvm-server.options, а файлы jvm8-server.options и jvm11-server.options удалить. Но мы такой подход не рекомендуем.


Лучше повторно применить параметры из предыдущего файла jvm.options к новым файлам jvm-server.options и jvm8-server.options. Конкретные файлы опций Java, в основном, связаны с флагами для сборки мусора. Если обновить эти два файла, будет проще настроить jvm11-server.options и перейти с JDK 8 на JDK 11.


Рабочие нагрузки


В бенчмарках мы использовали восемь потоков с ограничением скорости и соотношением записи и чтения 80/20%. tlp-stress широко использует асинхронные запросы, которые легко могут перегрузить ноды Cassandra с ограниченным числом стресс-потоков. В нагрузочных тестах каждый поток отправлял 50 параллельных запросов за раз. Мы создали keyspace с коэффициентом репликации 3, и все запросы выполнялись на уровне согласованности LOCAL_ONE.


Все сборщики мусора и версии Cassandra тестировались по нарастанию — 25к, 40к, 45к и 50к операций в секунду, — чтобы оценить производительность при разных уровнях нагрузки.


Команды tlp-stress:


tlp-stress run BasicTimeSeries -d 30m -p 100M -c 50 --pg sequence -t 8 -r 0.2 --rate <desired rate> --populate 200000

Все рабочие нагрузки выполнялись 30 минут, загружая от 5 до 16 ГБ данных на ноду и допуская нагрузку от compaction в разумной степени.


Примечание. Мы не пытались оценить максимальную производительность Cassandra, которую можно по-разному настроить в зависимости от рабочей нагрузки. И настраивать сборщики мусора мы тоже не планировали, потому что у них множество параметров для разных нагрузок. Мы просто хотели честно сравнить разные сборщики мусора, в основном на дефолтных параметрах, при одинаковой нагрузке в Cassandra.


Результаты бенчмарков


3.11.6, 25-40к операций/с:



4.0, 25-40к операций/с:



4.0, 45-50к операций/с:




В плане пропускной способности Cassandra 3.11.6 осилила 41к операций в секунду, а Cassandra 4.0 — 51к, то есть на 25% больше. В обоих случаях использовался CMS. В версии 4.0 много улучшений, которые объясняют эти результаты, особенно в вопросах нагрузки на кучу, вызванной compaction (см. CASSANDRA-14654, например).


Shenandoah в jdk8 на Cassandra 3.11.6 не достигает максимальной пропускной способности в тесте на 40к операций в секунду — появляются невыполненные запросы. С jdk11 и Cassandra 4.0 результат получше — 49,6к, почти как у CMS. У G1 и Shenandoah с jdk 8 получилось 36к/с на Cassandra 3.11.6.


G1, видимо, усовершенствован в jdk14 и показал себя немного лучше, чем с jdk11, — 47 против 50к/с.


ZGC не дотянул до конкурентов ни с jdk11, ни с jdk14, показав 41к/с максимум.








При умеренной нагрузке Shenandoah в jdk8 показывает хороший результат на Cassandra 3.11.6, но под высокой нагрузкой серьезно увеличиваются задержки.


С CMS Cassandra 4.0 показывает среднюю задержку для p99 (99-го перцентиля) от 11 до 31 мс при 50к операций в секунду. Средняя задержка операций чтения для p99 при умеренной нагрузке снизилась с 17 мс в Cassandra 3.11.6 до 11,5 мс в Cassandra 4.0, то есть на целых 30%.


В целом Cassandra 4.0 лучше Cassandra 3.11.6 с теми же сборщиками мусора на 25–30% по пропускной способности и задержке.


Shenandoah дает очень низкие задержки при умеренной нагрузке в Cassandra 3.11.6, но с высокими нагрузками справляется не очень.


По задержкам ZGC хорошо себя проявляет при умеренной нагрузке, особенно с jdk14, но при более высокой скорости отстает от Shenandoah. Почти во всех нагрузочных тестах Shenandoah показал минимальные средние задержки для p99 для чтения и записи. Если сложить эти задержки с пропускной способностью в Cassandra 4.0, получится что этот сборщик мусора — неплохой выбор, если вы обновляете версию. Средняя задержка 2,64 мс при чтении для p99 при умеренной нагрузке — это впечатляет. Тем более на клиенте.


G1, в основном, соответствует настроенному максимальному времени паузы 300 мс для максимального значения p99, но с меньшей паузой при высоких нагрузках могут возникать нежелательные эффекты, и пауза может быть больше.


При умеренной нагрузке Shenandoah дает среднюю задержку для p99 на 77% ниже. 2,64 мс — это самый низкий результат. Полезно для сценариев, где важна задержка. По сравнению с CMS в Cassandra 3.11.6 это на целых 85% ниже для операций чтения на p99!


Отдадим должное ZGC в jdk14, который хорошо себя показал при умеренных нагрузках, но с увеличением скорости, увы, не справился. Мы надеемся, что через несколько месяцев он станет лучше и сможет конкурировать с Shenandoah.


Итоги


G1 улучшает юзабилити Cassandra — не нужно настраивать размеры поколений в ущерб производительности. В версии Apache Cassandra 4.0, которая и без того работает гораздо лучше, можно будет использовать сборщики мусора нового поколения, например Shenandoah и ZGC, которые не требуют тщательной настройки и сокращают задержки.


Мы не рекомендуем Shenandoah для Cassandra 3.11.6, поскольку он не справляется с высокими нагрузками, но начиная с jdk11 и Cassandra 4.0 этот сборщик мусора показывает низкие задержки и обеспечивает почти максимальную пропускную способность для базы данных.


Ваши цифры могут отличаться в зависимости от рабочей нагрузки, но результаты позволяют надеяться, что в будущем Apache Cassandra станет неплохим инструментом для сценариев, где особо важны задержки. Во всяком случае, новая версия показала себя гораздо лучше, чем Cassandra 3.11.6.


Загрузите последнюю сборку Apache 4 и попробуйте сами. Будем рады обратной связи тут или в Slack компании ASF.


От редакции: приглашаем на открытую онлайн-конференцию Cassandra Day Russia 2021 в субботу 27 марта. В программе доклады и воркшопы от опытных NoSQL-специалистов.