1,1 миллиона комментариев от Hacker News: полнотекстовый тест малых данных / аналитика

Intro

В этом тесте мы используем коллекцию данных из 1,1 млн. курируемых комментариев Hacker News с числовыми полями с сайта https://zenodo.org/record/45901. В современном мире 1 миллион документов можно считать очень маленьким набором данных, который, однако, может быть типичным для многих приложений: блогов и новостных сайтов, интернет-магазинов, сайтов о работе, автомобилях и недвижимости и так далее. Для таких приложений типично иметь:

  • не очень длинные текстовые данные в одном или нескольких полях
  • и несколько атрибутов

Сбор данных

Источником сбора данных является 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"}
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

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

  • MySQL и Percona Server for MySQL: никаких настроек, только CREATE TABLE ..., FULLTEXT(story_text,story_author,comment_text,comment_author)) и стандартный образ mysql docker.
  • Clickhouse: никакой настройки, просто CREATE TABLE ... ENGINE = MergeTree() ORDER BY id SETTINGS index_granularity = 8192 и стандартный docker-образ clickhouse-server.
  • Elasticsearch: также никаких настроек, кроме bootstrap.memory_lock=true, поскольку, как сказано на https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_disable_swapping, это необходимо для производительности. Образ докера также от производителя.
  • Manticore Search:

    • , а для остальных мы используем их собственный образ docker + их библиотеку columnar (но она не используется в этом тесте).

О кэшах

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

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

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

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

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

  • Clickhouse:
  • Elasticsearch:
    • «index.queries.cache.enabled»: false в своей конфигурации
    • /_cache/clear?request=true&query=true&fielddata=true перед тестированием каждого нового запроса (не каждой попытки одного и того же запроса).
  • Manticore Search (в конфигурационном файле):
    • qcache_max_bytes = 0
    • docstore_cache_size = 0
  • Операционная система:
    • мы делаем echo 3 > /proc/sys/vm/drop_caches; sync перед каждым НОВЫМ запросом (НЕ перед каждой попыткой). Т.е. для каждого нового запроса мы:
    • останавливаем базу данных
    • сбрасываем кэш ОС
    • запустить его снова
    • выполнить самый первый холодный запуск и измерить его время
    • и сделать еще десятки попыток (до 100 или пока коэффициент вариации не станет достаточно низким, чтобы считать результаты тестирования качественными).

Запросы

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

[
"select count(*) from hn_small",
"select count(*) from hn_small where comment_ranking=100",
"select count(*) from hn_small where comment_ranking=500",
"select count(*) from hn_small where comment_ranking > 300 and comment_ranking < 500",
"select story_author, count(*) from hn_small group by story_author order by count(*) desc limit 20",
"select story_author, avg(comment_ranking) avg from hn_small group by story_author order by avg desc limit 20",
"select comment_ranking, count(*) from hn_small group by comment_ranking order by count(*) desc limit 20",
"select comment_ranking, avg(author_comment_count) avg from hn_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small 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_small where match('abc') limit 20",
    "clickhouse": "select * from hn_small 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_small where query('abc') limit 20",
    "mysql": "select * from hn_small where match(story_text,story_author,comment_text,comment_author) against ('google') limit 20"
},
{
    "manticoresearch": "select * from hn_small where match('abc -google') limit 20",
    "clickhouse": "select * from hn_small 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_small where query('abc !google') limit 20",
    "mysql": "select * from hn_small where match(story_text,story_author,comment_text,comment_author) against ('abc -google') limit 20"
},
{
    "manticoresearch": "select * from hn_small where match('"elon musk"') limit 20",
    "clickhouse": "select * from hn_small 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_small where query('\"elon musk\"') limit 20",
    "mysql": "select * from hn_small where match(story_text,story_author,comment_text,comment_author) against ('"elon musk"') limit 20"
},
{
    "manticoresearch": "select * from hn_small where match('abc') order by comment_ranking asc limit 20",
    "clickhouse": "select * from hn_small 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_small where query('abc') order by comment_ranking asc limit 20",
    "mysql": "select * from hn_small where match(story_text,story_author,comment_text,comment_author) against ('abc') order by comment_ranking asc limit 20"
},
{
    "manticoresearch": "select * from hn_small where match('abc') order by comment_ranking asc, story_id desc limit 20",
    "clickhouse": "select * from hn_small 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_small where query('abc') order by comment_ranking asc, story_id desc limit 20",
    "mysql": "select * from hn_small 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_small where match('google') and comment_ranking > 200",
    "clickhouse": "select count(*) from hn_small 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_small where query('google') and comment_ranking > 200",
    "mysql": "select count(*) from hn_small where match(story_text,story_author,comment_text,comment_author) against ('google') and comment_ranking > 200"
},
{
    "manticoresearch": "select story_id from hn_small where match('me') order by comment_ranking asc limit 20",
    "clickhouse": "select story_id from hn_small 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_small where query('me') order by comment_ranking asc limit 20",
    "mysql": "select story_id from hn_small 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_small 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_small 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_small 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_small where match(story_text,story_author,comment_text,comment_author) against ('abc') limit 20"
},
"select * from hn_small order by comment_ranking asc limit 20",
"select * from hn_small order by comment_ranking desc limit 20",
"select * from hn_small order by comment_ranking asc, story_id asc limit 20",
"select comment_ranking from hn_small order by comment_ranking asc limit 20",
"select comment_ranking, story_text from hn_small order by comment_ranking asc limit 20",
"select count(*) from hn_small where comment_ranking in (100,200)",
"select story_id from hn_small order by comment_ranking asc, author_comment_count asc, story_comment_count asc, comment_id asc limit 20"
]
Войти в полноэкранный режим Выход из полноэкранного режима

Результаты

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

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

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

  • вы можете использовать структуру тестов, чтобы узнать, как они были сделаны

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

Все конкуренты сразу

MySQL vs Clickhouse

MySQL vs Elasitcsearch

MySQL vs Manticore Search

MySQL vs Percona Server

Clickhouse vs Elasticsearch

Manticore Search vs Elasticsearch

Manticore Search vs Clickhouse

Отказ от ответственности

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

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