110 миллионов комментариев от эталона Hacker News


Интро

В этой статье мы рассмотрим еще один тест, добавленный к https://db-benchmarks.com/ — 1.1M курируемых комментариев Hacker News с числовыми полями из https://zenodo.org/record/45901, умноженными на 100.

Сбор данных

Источником сбора данных является https://zenodo.org/record/45901.

Структура записи такова:

"properties": {
   "story_id": {"type": "integer"},
   "story_text": {"type": "text"},
   "story_author": {"type": "text", "fields": {"raw": {"type":"keyword"}}},
   "comment_id": {"type": "integer"},
   "comment_text": {"type": "text"},
   "comment_author": {"type": "text", "fields": {"raw": {"type":"keyword"}}},
   "comment_ranking": {"type": "integer"},
   "author_comment_count": {"type": "integer"},
   "story_comment_count": {"type": "integer"}
}
Вход в полноэкранный режим Выход из полноэкранного режима

Базы данных

На данный момент мы сделали этот тест доступным для 3 баз данных:

  • Clickhouse — мощная OLAP база данных,
  • Elasticsearch — «поисково-аналитический движок» общего назначения,
  • Manticore Search — «база данных для поиска», альтернатива Elasticsearch.

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

  • Clickhouse: никаких настроек, просто CREATE TABLE ... ENGINE = MergeTree() ORDER BY id и стандартный докер-образ clickhouse-server.
  • Elasticsearch: как мы видели в другом тесте, шардинг может существенно помочь Elasticsearch, поэтому, учитывая, что 100+ M документов — не самый маленький набор данных, мы решили, что будет справедливее:

    • позволить Elasticsearch сделать 32 шарда: ("number_of_shards": 32), иначе он не смог бы использовать CPU, который имеет 32 ядра на сервере, поскольку, как сказано в официальном руководстве Elasticsearch, «каждый шард выполняет поиск на одном потоке CPU».
    • Мы также настроили его, установив bootstrap.memory_lock=true, поскольку, как сказано на https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_disable_swapping, это необходимо для производительности.
    • образ докера является стандартным
  • Manticore Search также используется в виде их официального образа docker + предоставляемая ими библиотека columnar. Следующие обновления были сделаны в их настройках по умолчанию:

    • мы тестировали Manticore в двух режимах:
  1. row-wise storage, который используется по умолчанию, поэтому его стоит протестировать
  2. columnar storage: коллекция данных имеет средний размер, поэтому при условии, что Elasticsearch и Clickhouse внутренне используют структуры, ориентированные на столбцы, кажется справедливым сравнить их с columnar storage Manticore.

О кэшах

Мы также настроили базы данных так, чтобы они не использовали никаких внутренних кэшей. Почему это важно:

  1. В этом бенчмарке мы проводим точное измерение задержки, чтобы выяснить, какое время отклика может ожидать пользователь, если он выполнит один из тестируемых запросов в случайный момент, а не после многократного выполнения одного и того же запроса.
  2. Любой кэш — это короткий путь к низкой задержке. Как написано в Википедии, «кэш хранит данные, чтобы будущие запросы на эти данные обслуживались быстрее». Но кэши бывают разные, их можно разделить на 2 основные группы:

    • 👌 те, которые просто кэшируют необработанные данные, хранящиеся на диске. Например, многие базы данных используют mmap() для отображения данных, хранящихся на диске, в память, легкого доступа к ним и предоставления операционной системе заботиться об остальном (чтение с диска, когда есть свободная память, удаление из памяти, когда она нужна для чего-то более важного и т.д.). Это нормально с точки зрения тестирования производительности, потому что мы позволяем каждой базе данных использовать преимущества использования страничного кэша ОС (или ее внутреннего аналогичного кэша, который просто считывает данные с диска) Именно это мы и делаем в этом бенчмарке.
    • ❗ те, которые используются для сохранения результатов предыдущих вычислений. И это хорошо во многих случаях, но с точки зрения этого бенчмарка позволить базе данных включить такой кэш — плохая идея, потому что:
      • это нарушает правильное измерение: вместо измерения времени вычисления вы начинаете измерять, сколько времени требуется, чтобы найти значение по ключу в памяти. Это не то, что мы хотим сделать в данном тесте (но это интересно в целом, и мы, возможно, сделаем это в будущем и опубликуем статью «Бенчмарк кэшей»).
      • Даже если они сохраняют не полный результат конкретного запроса, а результаты его подвычислений, это нехорошо, потому что нарушает идею теста — «какое время отклика может ожидать пользователь, если он выполнит один из тестируемых запросов в случайный момент времени».
      • Некоторые базы данных имеют такой кэш (он обычно называется «кэш запросов»), другие нет, поэтому если мы не отключим внутренний кэш базы данных, мы дадим несправедливое преимущество тем, у кого он есть.

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

Что именно мы делаем для достижения этой цели:

  • Clickhouse:

  • Elasticsearch:

  • Manticore Search (в конфигурационном файле):

    • qcache_max_bytes = 0
    • docstore_cache_size = 0
  • Операционная система:

    • мы делаем echo 3 > /proc/sys/vm/drop_caches; sync перед каждым НОВЫМ запросом (НЕ перед каждой попыткой). Т.е. для каждого нового запроса мы:
    • останавливаем базу данных
    • сбрасываем кэш ОС
    • запускаем её снова
    • делаем самый первый холодный запрос и измеряем его время
    • и сделать еще десятки попыток (до 100 или пока коэффициент вариации не станет достаточно низким, чтобы считать результаты теста высококачественными).

Запросы

Набор запросов состоит как из полнотекстовых, так и аналитических (фильтрация, сортировка, группировка, агрегирование) запросов:

[
"select count(*) from hn",
"select count(*) from hn where comment_ranking=100",
"select count(*) from hn where comment_ranking=500",
"select count(*) from hn where comment_ranking > 300 and comment_ranking < 500",
"select story_author, count(*) from hn group by story_author order by count(*) desc limit 20",
"select story_author, avg(comment_ranking) avg from hn group by story_author order by avg desc limit 20",
"select comment_ranking, count(*) from hn group by comment_ranking order by count(*) desc limit 20",
"select comment_ranking, avg(author_comment_count) avg from hn group by comment_ranking order by avg desc, comment_ranking desc limit 20",
"select comment_ranking, avg(author_comment_count+story_comment_count) avg from hn group by comment_ranking order by avg desc, comment_ranking desc limit 20",
"select comment_ranking, avg(author_comment_count+story_comment_count) avg from hn where comment_ranking < 10 group by comment_ranking order by avg desc, comment_ranking desc limit 20",
{
    "manticoresearch": "select comment_ranking, avg(author_comment_count) avg from hn where match('google') group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "clickhouse": "select comment_ranking, avg(author_comment_count) avg from hn where (match(story_text, '(?i)\Wgoogle\W') or match(story_author,'(?i)\Wgoogle\W') or match(comment_text, '(?i)\Wgoogle\W') or match(comment_author, '(?i)\Wgoogle\W')) group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "elasticsearch": "select comment_ranking, avg(author_comment_count) avg from hn where query('google') group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "mysql": "select comment_ranking, avg(author_comment_count) avg from hn where match(story_text,story_author,comment_text,comment_author) against ('google') group by comment_ranking order by avg desc, comment_ranking desc limit 20"
},
{
    "manticoresearch": "select comment_ranking, avg(author_comment_count) avg from hn where match('google') and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "clickhouse":"select comment_ranking, avg(author_comment_count) avg from hn where (match(story_text, '(?i)\Wgoogle\W') or match(story_author,'(?i)\Wgoogle\W') or match(comment_text, '(?i)\Wgoogle\W') or match(comment_author, '(?i)\Wgoogle\W')) and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "elasticsearch":"select comment_ranking, avg(author_comment_count) avg from hn where query('google') and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "mysql":"select comment_ranking, avg(author_comment_count) avg from hn where match(story_text,story_author,comment_text,comment_author) against ('google') and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20"
},
{
    "manticoresearch": "select comment_ranking, avg(author_comment_count+story_comment_count) avg from hn where match('google') and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "clickhouse": "select comment_ranking, avg(author_comment_count+story_comment_count) avg from hn where (match(story_text, '(?i)\Wgoogle\W') or match(story_author,'(?i)\Wgoogle\W') or match(comment_text, '(?i)\Wgoogle\W') or match(comment_author, '(?i)\Wgoogle\W')) and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "elasticsearch": "select comment_ranking, avg(author_comment_count+story_comment_count) avg from hn where query('google') and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20",
    "mysql": "select comment_ranking, avg(author_comment_count+story_comment_count) avg from hn where match(story_text,story_author,comment_text,comment_author) against ('google') and comment_ranking > 200 group by comment_ranking order by avg desc, comment_ranking desc limit 20"
},
{
    "manticoresearch": "select * from hn where match('abc') limit 20",
    "clickhouse": "select * from hn where (match(story_text, '(?i)\Wabc\W') or match(story_author,'(?i)\Wabc\W') or match(comment_text, '(?i)\Wabc\W') or match(comment_author, '(?i)\Wabc\W')) limit 20",
    "elasticsearch": "select * from hn where query('abc') limit 20",
    "mysql": "select * from hn where match(story_text,story_author,comment_text,comment_author) against ('google') limit 20"
},
{
    "manticoresearch": "select * from hn where match('abc -google') limit 20",
    "clickhouse": "select * from hn where (match(story_text, '(?i)\Wabc\W') or match(story_author,'(?i)\Wabc\W') or match(comment_text, '(?i)\Wabc\W') or match(comment_author, '(?i)\Wabc\W')) and not (match(story_text, '(?i)\Wgoogle\W') or match(story_author,'(?i)\Wgoogle\W') or match(comment_text, '(?i)\Wgoogle\W') or match(comment_author, '(?i)\Wgoogle\W')) limit 20",
    "elasticsearch": "select * from hn where query('abc !google') limit 20",
    "mysql": "select * from hn where match(story_text,story_author,comment_text,comment_author) against ('abc -google') limit 20"
},
{
    "manticoresearch": "select * from hn where match('"elon musk"') limit 20",
    "clickhouse": "select * from hn where (match(story_text, '(?i)\Welon\Wmusk\W') or match(story_author,'(?i)\Welon\Wmusk\W') or match(comment_text, '(?i)\Welon\Wmusk\W') or match(comment_author, '(?i)\Welon\Wmusk\W')) limit 20",
    "elasticsearch": "select * from hn where query('\"elon musk\"') limit 20",
    "mysql": "select * from hn where match(story_text,story_author,comment_text,comment_author) against ('"elon musk"') limit 20"
},
{
    "manticoresearch": "select * from hn where match('abc') order by comment_ranking asc limit 20",
    "clickhouse": "select * from hn where (match(story_text, '(?i)\Wabc\W') or match(story_author,'(?i)\Wabc\W') or match(comment_text, '(?i)\Wabc\W') or match(comment_author, '(?i)\Wabc\W')) order by comment_ranking asc limit 20",
    "elasticsearch": "select * from hn where query('abc') order by comment_ranking asc limit 20",
    "mysql": "select * from hn where match(story_text,story_author,comment_text,comment_author) against ('abc') order by comment_ranking asc limit 20"
},
{
    "manticoresearch": "select * from hn where match('abc') order by comment_ranking asc, story_id desc limit 20",
    "clickhouse": "select * from hn where (match(story_text, '(?i)\Wabc\W') or match(story_author,'(?i)\Wabc\W') or match(comment_text, '(?i)\Wabc\W') or match(comment_author, '(?i)\Wabc\W')) order by comment_ranking asc, story_id desc limit 20",
    "elasticsearch": "select * from hn where query('abc') order by comment_ranking asc, story_id desc limit 20",
    "mysql": "select * from hn where match(story_text,story_author,comment_text,comment_author) against ('abc') order by comment_ranking asc, story_id desc limit 20"
},
{
    "manticoresearch": "select count(*) from hn where match('google') and comment_ranking > 200",
    "clickhouse": "select count(*) from hn where (match(story_text, '(?i)\Wgoogle\W') or match(story_author,'(?i)\Wgoogle\W') or match(comment_text, '(?i)\Wgoogle\W') or match(comment_author, '(?i)\Wgoogle\W')) and comment_ranking > 200",
    "elasticsearch": "select count(*) from hn where query('google') and comment_ranking > 200",
    "mysql": "select count(*) from hn where match(story_text,story_author,comment_text,comment_author) against ('google') and comment_ranking > 200"
},
{
    "manticoresearch": "select story_id from hn where match('me') order by comment_ranking asc limit 20",
    "clickhouse": "select story_id from hn where (match(story_text, '(?i)\Wme\W') or match(story_author,'(?i)\Wme\W') or match(comment_text, '(?i)\Wme\W') or match(comment_author, '(?i)\Wme\W')) order by comment_ranking asc limit 20",
    "elasticsearch": "select story_id from hn where query('me') order by comment_ranking asc limit 20",
    "mysql": "select story_id from hn where match(story_text,story_author,comment_text,comment_author) against ('me') order by comment_ranking asc limit 20"
},
{
    "manticoresearch": "select story_id, comment_id, comment_ranking, author_comment_count, story_comment_count, story_author, comment_author from hn where match('abc') limit 20",
    "clickhouse": "select story_id, comment_id, comment_ranking, author_comment_count, story_comment_count, story_author, comment_author from hn where (match(story_text, '(?i)\Wabc\W') or match(story_author,'(?i)\Wabc\W') or match(comment_text, '(?i)\Wabc\W') or match(comment_author, '(?i)\Wabc\W')) limit 20",
    "elasticsearch": "select story_id, comment_id, comment_ranking, author_comment_count, story_comment_count, story_author, comment_author from hn where query('abc') limit 20",
    "mysql": "select story_id, comment_id, comment_ranking, author_comment_count, story_comment_count, story_author, comment_author from hn where match(story_text,story_author,comment_text,comment_author) against ('abc') limit 20"
},
"select * from hn order by comment_ranking asc limit 20",
"select * from hn order by comment_ranking desc limit 20",
"select * from hn order by comment_ranking asc, story_id asc limit 20",
"select comment_ranking from hn order by comment_ranking asc limit 20",
"select comment_ranking, story_text from hn order by comment_ranking asc limit 20",
"select count(*) from hn where comment_ranking in (100,200)",
"select story_id from hn order by comment_ranking asc, author_comment_count asc, story_comment_count asc, comment_id asc limit 20"
]
Войти в полноэкранный режим Выход из полноэкранного режима

Результаты

Вы можете найти все результаты на странице результатов, выбрав «Тест: hn».

Помните, что единственной метрикой высокого качества является «Fast avg», так как она гарантирует низкий коэффициент вариации и высокое количество запросов, проведенных по каждому запросу. Две другие («Fastest» и «Slowest») не дают никаких гарантий, поскольку:

  • Slowest — это результат одной попытки, в большинстве случаев самый первый холодный запрос. Несмотря на то, что мы очищаем кэш ОС перед каждым холодным запросом, его нельзя считать стабильным. Поэтому его можно использовать только в информационных целях (несмотря на то, что многие авторы бенчмарков публикуют такие результаты без каких-либо оговорок).
  • Fastest — просто самый быстрый результат, он должен быть в большинстве случаев похож на метрику «Fast avg», но может быть более изменчив от прогона к прогону.Помните, что тесты, включая результаты, на 100% прозрачны, как и все в этом проекте, поэтому:
    • вы можете использовать структуру тестов, чтобы узнать, как они были сделаны
    • и найти необработанные результаты тестов в каталоге результатов.

В отличие от других менее прозрачных и менее объективных бенчмарков, мы не делаем никаких выводов, мы просто оставляем здесь скриншоты результатов:

4 конкурента одновременно

Clickhouse vs Elasticsearch

Manticore Search (columnar storage) vs Elasticsearch

Manticore Search (колоночное хранение) vs Clickhouse

Manticore Search row-wise storage vs columnar storage

А что насчет MySQL?
Как вы можете видеть на скриншотах, MySQL также был протестирован, но мы не сравниваем его с другими, поскольку он был сильно настроен — ключи были добавлены на основе запросов.

Отказ от ответственности
Автор данного теста и тестового фреймворка является членом команды разработчиков Manticore Search, и тест изначально был сделан для сравнения Manticore Search с Elasticsearch, но, как показано выше и можно убедиться в открытом исходном коде и при самостоятельном выполнении теста, Manticore Search не получил никаких несправедливых преимуществ, поэтому тест можно считать непредвзятым. Тем не менее, если в тесте что-то упущено или неправильно (т.е. не объективно), не стесняйтесь сделать запрос на исправление или проблему на Github. Ваше мнение будет оценено по достоинству! Спасибо, что потратили свое время на прочтение этой статьи!

Оцените статью
Procodings.ru
Добавить комментарий