Сравнение Hazelcast и Ignite

Я использую сетки данных в качестве своей основной «базы данных». Я заметил резкую разницу между производительностью запросов Hazelcast и Ignite. Я оптимизировал использование сетки данных с помощью правильной пользовательской сериализации и индексов, но разница все еще заметна, IMO.

Поскольку никто не спрашивал об этом здесь, я собираюсь ответить на свой вопрос для всех будущих ссылок. Это не абстрактное (учебное) упражнение, а реальный тест, который моделирует использование моей сетки данных в больших системах SaaS — в первую очередь для отображения отсортированных и отфильтрованных списков с разбивкой на страницы. В первую очередь я хотел знать, сколько накладных расходов добавляет мой универсальный уровень доступа к сетке данных в стиле JDBC по сравнению с необработанным использованием Hazelcast и Ignite без фреймворков. Но так как я сравниваю яблоки с яблоками, вот эталон.


person Alex Rogachevsky    schedule 11.08.2015    source источник


Ответы (2)


Я просмотрел предоставленный код на GitHub и получил много комментариев:

Индексирование и объединение

  1. Вероятно, самым важным моментом является то, что индексация Apache Ignite намного сложнее, чем Hazelcast. В отличие от Hazelcast, Ignite поддерживает ANSI 99 SQL, поэтому вы можете писать свои запросы по своему желанию.
  2. Самое главное, что в отличие от Hazelcast, Ignite поддерживает групповые индексы и SQL JOIN для разных кэшей или типов данных. Представьте, что у вас есть таблицы «Люди» и «Организация», и вам нужно выбрать всех «Людей», работающих в одной и той же организации. Это было бы невозможно сделать за 1 шаг в Hazelcast (поправьте меня, если я ошибаюсь), но в Ignite это простой запрос SQL JOIN.

Учитывая вышесказанное, создание индексов Ignite займет немного больше времени, особенно в вашем тесте, где у вас их 7.

Исправления в классе TestEntity

В вашем коде сущность, которую вы сохраняете в кэше, TestEntity, пересчитывает значение для idSort, createdAtSort и modifiedAtSort каждый раз, когда вызывается геттер. Ignite вызывает эти геттеры несколько раз, пока сущность сохраняется в дереве индекса. Простое исправление класса TestEntity обеспечивает 4-кратное повышение производительности:

Измерение кучи неточно

То, как вы измеряете кучу, неверно. Вы должны по крайней мере вызвать System.gc() перед измерением кучи, и даже это не будет точным. Например, в приведенных ниже результатах я получаю отрицательный размер кучи, используя ваш метод.

Разогреть

Каждый тест требует разминки. Например, когда я применяю исправление TestEntity, как было предложено выше, и дважды выполняю заполнение кэша и запросы, я получаю лучшие результаты.

Сравнение MySQL

Я не думаю, что сравнение теста Data Grid с одним узлом с MySQL справедливо ни для Ignite, ни для Hazelcast. Базы данных имеют собственное кэширование, и всякий раз, когда вы работаете с такими небольшими размерами памяти, вы обычно тестируете кэш базы данных в памяти и кэш в памяти Data Grid.

Преимущество в производительности обычно проявляется при выполнении распределенного теста над секционированным кешем. Таким образом, Data Grid будет выполнять запрос на каждом узле кластера параллельно, и результаты должны возвращаться намного быстрее.

Полученные результаты

Вот результаты, которые я получил для Apache Ignite. Они выглядят намного лучше после того, как я сделал вышеупомянутые исправления.

Обратите внимание, что во второй раз, когда мы выполняем заполнение кеша и запросы к кешу, мы получаем лучшие результаты, потому что JVM HotSpot прогревается.

Стоит отметить, что Ignite не кэширует результаты запроса. Каждый раз, когда вы запускаете запрос, вы выполняете его с нуля.

[00:45:15] Ignite node started OK (id=0960e091, grid=Benchmark)
[00:45:15] Topology snapshot [ver=1, servers=1, clients=0, CPUs=4, heap=8.0GB]
Starting - used heap: 225847216 bytes
Inserting 100000 records: ....................................................................................................
Inserted all records - used heap: 1001824120 bytes
Cache: 100000 entries, heap size: 775976904 bytes, inserts took 14819 ms
------------------------------------
Starting - used heap: 1139467848 bytes
Inserting 100000 records: ....................................................................................................
Inserted all records - used heap: 978473664 bytes
Cache: 100000 entries, heap size: **-160994184** bytes, inserts took 11082 ms
------------------------------------
Query 1 count: 100, time: 110 ms, heap size: 1037116472 bytes
Query 2 count: 100, time: 285 ms, heap size: 1037116472 bytes
Query 3 count: 100, time: 19 ms, heap size: 1037116472 bytes
Query 4 count: 100, time: 123 ms, heap size: 1037116472 bytes
------------------------------------
Query 1 count: 100, time: 10 ms, heap size: 1037116472 bytes
Query 2 count: 100, time: 116 ms, heap size: 1056692952 bytes
Query 3 count: 100, time: 6 ms, heap size: 1056692952 bytes
Query 4 count: 100, time: 119 ms, heap size: 1056692952 bytes
------------------------------------
[00:45:52] Ignite node stopped OK [uptime=00:00:36:515]

Я создам еще один репозиторий GitHub с исправленным кодом и опубликую его здесь, когда проснусь (кофе больше не помогает).

person Dmitriy    schedule 11.08.2015
comment
Спасибо, Дмитрий. Просто краткая история моего проекта. Напишу вам, ребята, отдельное письмо в приват. У меня большие планы на Ignite. - person Alex Rogachevsky; 11.08.2015
comment
Спасибо, Дмитрий. Просто краткая история моего проекта. Я запустил Px100 с Hazelcast (в предыдущей версии использовался стандартный SQL/JPA, если вам интересно). Я не был наивен, думая, что он превзойдет MySQL, просто находясь в памяти, но я ожидал, по крайней мере, сопоставимой производительности. Как и все остальные, я использовал Serializable и не заботился об индексах. Я оптимизировал его по официальной рекомендации Hazelcast. Это немного улучшило ситуацию, но проблемы снова появились на больших наборах данных. Напишу вам, ребята, отдельное письмо про Px100 в приват. У меня большие планы на Ignite. - person Alex Rogachevsky; 11.08.2015
comment
@AlexRogacheevsky Алекс, звучит хорошо. С нетерпением жду ваших планов относительно Apache Ignite. - person Dmitriy; 12.08.2015
comment
Дмитрий, я последовал вашему предложению относительно вычисляемых геттеров, однако вы также должны убедиться, что вы вызываете их один раз, а затем передаете это значение. Эти геттеры довольно распространены для всего, кроме глупых сущностей в стиле DTO. Второй вопрос, который у меня есть, касается составных индексов. Я хотел бы, чтобы IgniteBiTuple объяснялся в javadoc + подробные примеры в документации. И что именно означает логическое значение: вероятно, по возрастанию. Такие вещи не всегда очевидны для лошади. Наконец, у вас есть серьезная ошибка производительности - при указании ведения журнала log4j без конфигурации log4j. - person Alex Rogachevsky; 19.08.2015
comment
@AlexRogachevsky Вот документация по функциям Ignite SQL, включая составные индексы: apacheignite. gridgain.org/v1.3/docs/sql-запросы - person Dmitriy; 19.08.2015
comment
@AlexRogacheevsky Что касается геттеров, я не уверен, что согласен, но мы это рассмотрим. То, что вы делали, это пересчитывать одно и то же значение снова и снова каждый раз, когда вызывается геттер, что не является обычным явлением. - person Dmitriy; 19.08.2015
comment
@AlexRogachevsky Спасибо, что заметили ошибку log4j с нулевым добавлением (по какой-то причине она включала режим отладки) - уже исправлено в мастере и будет частью следующего выпуска. - person Dmitriy; 19.08.2015
comment
Дмитрий, индексы я настраиваю программно. Кстати, это не работает. У меня не может быть аннотаций для конкретного поставщика в универсальном фреймворке/API. Пересмотрите javadoc для класса CacheTypeMetadata, особенно метод setGroups(). В настоящее время он читает Задает поля с групповым индексом. и сложный параметр: карта карт IgniteBiTuple мне мало что говорит. Вы также можете упростить эту подпись. Вы, вероятно, не рассчитывали на то, что кто-то будет использовать его вместо аннотаций. Я делаю. Что касается геттеров, я рефакторил те, что вы видели, но у меня все еще есть много вычисляемых значений из измененных полей. - person Alex Rogachevsky; 19.08.2015
comment
Геттеры и сеттеры существуют не просто так. В них может быть и логика, и полностью преходящие вычисляемые поля. Например. сущность, приложение InsuranceApplication имеет List‹Person›, который, в свою очередь, имеет List‹String› телефонов. Я хочу найти все приложения, в которых 3-й телефон 5-го по возрасту человека начинается с 310. Вы можете написать сложный (и медленный) запрос, чтобы сделать это во время выполнения, или вы можете получить геттер, вычисляющий его, и кэшировать/сохранить это значение с помощью поля запроса. По сути, эти геттеры являются пользовательскими функциями SQL. Не думайте в терминах SQL. В NoSQL вам не нужно иметь объявленное физическое поле. - person Alex Rogachevsky; 19.08.2015
comment
Объясняет ли это документацию? На производительность это тоже никак не влияет. Тип CacheTypeMetadata = новый CacheTypeMetadata(); LinkedHashMap‹String, IgniteBiTuple‹Class‹?›, логическое значение››companyIndex = new LinkedHashMap‹›(); СоединениеIndex.put(textField, новый IgniteBiTuple‹›(String.class, true)); compoundIndex.put(id, new IgniteBiTuple‹›(Long.class, false)); type.getGroups().put(compIdx1, compIdIndex); - person Alex Rogachevsky; 20.08.2015

Вот исходный код теста: https://github.com/a-rog/px100data/tree/master/examples/HazelcastVsIgnite

Это часть платформы NoSQL в стиле JDBC, о которой я упоминал ранее: Px100 Data

Создание и запуск:

cd <project-dir>
mvn clean package
cd target
java -cp "grid-benchmark.jar:lib/*" -Xms512m -Xmx3000m -Xss4m com.px100systems.platform.benchmark.HazelcastTest 100000
java -cp "grid-benchmark.jar:lib/*" -Xms512m -Xmx3000m -Xss4m com.px100systems.platform.benchmark.IgniteTest 100000

Как видите, я установил высокие лимиты памяти, чтобы избежать сборки мусора. Вы также можете запустить мой собственный фреймворк-тест (см. Px100DataTest.java) и сравнить с двумя вышеприведенными, но давайте сосредоточимся на чистой производительности. Ни один из тестов не использует Spring или что-то еще, кроме Hazelcast 3.5.1 и Ignite 1.3.3 — последних на данный момент.

Эталонный тест транзакционно вставляет указанное количество прибл. Записи размером 1K (из них 100000 — можно увеличить, но остерегайтесь памяти) в пакетах (транзакциях) по 1000. Затем он выполняет два запроса с сортировкой по возрастанию и убыванию: всего четыре. Все поля запроса и ORDER BY индексируются.

Я не буду публиковать весь класс (скачайте его с GitHub). Запрос Hazelcast выглядит следующим образом:

PagingPredicate predicate = new PagingPredicate(
        new Predicates.AndPredicate(new Predicates.LikePredicate("textField", "%Jane%"),
            new Predicates.GreaterLessPredicate("id", first.getId(), false, false)),
        (o1, o2) -> ((TestEntity)o1.getValue()).getId().compareTo(((TestEntity)o2.getValue()).getId()),
        100);

Соответствующий запрос Ignite:

SqlQuery<Object, TestEntity> query = new SqlQuery<>(TestEntity.class,
        "FROM TestEntity WHERE textField LIKE '%Jane%' AND id > '" + first.getId() + "' ORDER BY id LIMIT 100");
    query.setPageSize(100);

Вот результаты, выполненные на моем 8-ядерном MBP 2012 года с 8 ГБ памяти:

Хейзелкаст

Starting - used heap: 49791048 bytes
Inserting 100000 records: ....................................................................................................
Inserted all records - used heap: 580885264 bytes
Map: 100000 entries, used heap: 531094216 bytes, inserts took 5458 ms
Query 1 count: 100, time: 344 ms, heap size: 298844824 bytes
Query 2 count: 100, time: 115 ms, heap size: 454902648 bytes
Query 3 count: 100, time: 165 ms, heap size: 657153784 bytes
Query 4 count: 100, time: 106 ms, heap size: 811155544 bytes

Зажечь

Starting - used heap: 100261632 bytes
Inserting 100000 records: ....................................................................................................
Inserted all records - used heap: 1241999968 bytes
Cache: 100000 entries, heap size: 1141738336 bytes, inserts took 14387 ms
Query 1 count: 100, time: 222 ms, heap size: 917907456 bytes
Query 2 count: 100, time: 128 ms, heap size: 926325264 bytes
Query 3 count: 100, time: 7 ms, heap size: 926325264 bytes
Query 4 count: 100, time: 103 ms, heap size: 934743064 bytes 

Одним из очевидных отличий является производительность вставки, заметная в реальной жизни. Однако очень редко вставляется 1000 записей. Обычно это одна вставка или обновление (сохранение введенных данных пользователя и т. д.), поэтому меня это не беспокоит. Однако производительность запроса делает. Большинство программ для бизнеса, ориентированных на данные, требуют большого количества операций чтения.

Обратите внимание на потребление памяти. Ignite потребляет больше оперативной памяти, чем Hazelcast. Что может объяснить лучшую производительность запросов. Хорошо, если я решил использовать сетку в памяти, должен ли я беспокоиться о памяти?

Вы можете четко сказать, когда сетки данных попадают в индексы, а когда нет, как они кэшируют скомпилированные запросы (7 мс) и т. д. Я не хочу спекулировать и позволю вам поиграть с этим, как Hazelcast и Разработчики Ignite дают некоторое представление.

Что касается общей производительности, то она сравнима, если не ниже MySQL. Технология IMO in-memory должна работать лучше. Я уверен, что обе компании примут к сведению.

Приведенные выше результаты довольно близки. Однако при использовании в Px100 Data и более высоком уровне Px100 (который в значительной степени зависит от индексированных «полей сортировки» для разбивки на страницы) Ignite вырывается вперед и лучше подходит для моей структуры. Я забочусь в первую очередь о производительности запросов.

person Alex Rogachevsky    schedule 11.08.2015
comment
Если я решу использовать сетку в памяти, должен ли я беспокоиться о памяти? Ну, да, вы должны, если только вы не хотите, чтобы запрос отключил ваш кластер с помощью OOME. Конечно, производительность действительно очень важна, но это не значит, что вы не должны беспокоиться об использовании памяти. - person nilskp; 02.09.2015