Введение
Percolate-запросы также известны как постоянные запросы, перспективный поиск, маршрутизация документов, поиск в обратном порядке или обратный поиск.
Обычный способ проведения поисков — это хранение документов, которые мы хотим искать, и выполнение запросов к ним. Однако бывают случаи, когда мы хотим применить запрос к поступающему новому документу, чтобы сигнализировать о совпадении. Существуют некоторые сценарии, когда это необходимо. Например, система мониторинга не только собирает данные, но также желает уведомлять пользователя о различных событиях. Это может быть достижение некоторого порога для метрики или определенного значения, которое появляется в отслеживаемых данных. Другой похожий случай — агрегирование новостей. Вы можете уведомлять пользователя о любых свежих новостях, но пользователь может захотеть получать уведомления только о определенных категориях или темах. Продвигаясь дальше, они могут быть заинтересованы только в определенных «ключевых словах».
Приложения, осуществляющие перколацию запросов, часто имеют высокую нагрузку: им нужно обрабатывать тысячи документов в секунду и могут иметь сотни тысяч запросов, с которыми нужно проверить каждый документ.
И Elasticsearch, и Manticore Search предоставляют Percolate-запросы.
Хотя Percolate-запросы присутствуют в Elasticsearch уже некоторое время, в Manticore они были добавлены в версии 2.6.0 и до сих пор отсутствуют в Sphinx. Сегодня я хотел бы провести некоторые измерения производительности, чтобы выяснить, какие значения могут предоставить каждая из технологий.
Тестовый случай
Многие компании, работающие в индустрии маркетинга в социальных сетях, часто интересуются анализом Twitter firehose или decahose, другими словами, они хотят просматривать все или часть поступающих сообщений, которые пользователи отправляют на twitter.com по всему миру, чтобы понять, насколько аудитория заинтересована в брендах, продуктах своих клиентов или чем-то еще. Чтобы сделать наш тест более приближенным к реальности, мы также будем использовать образцы твитов. Я нашел хороший архив на archive.org здесь https://archive.org/details/tweets-20170508080715-crawl448 , который предоставил мне около 600К случайных твитов:
$ wc -l ~/twitter/text
607388 /home/snikolaev/twitter/text
Документы раскодированы из json, но на всякий случай, если вы хотите получить представление о том, как они выглядят (да, представим, что это все еще 2005 год, и никто не знает, как обычно выглядит сообщение в Twitter):
$ head text
"\u8266\u968a\u304c\u5e30\u6295\u3057\u305f\u3002"
"RT @BTS_jp_official: #BTS \u65e5\u672c7th\u30b7\u30f3\u30b0\u30eb\u300c\u8840\u3001\u6c57\u3001\u6d99 -Japanese ver.-\u300dMV\n\u21d2 https:\/\/t.co\/jQGltkBQhj \niTunes\uff1a https:\/\/t.co\/2OGIYrrkaE\n#\u9632\u5f3e\u5c11\u5e74\u56e3 #\u8840\u6c57\u6d99"
"RT @79rofylno: \uc6b0'3'\nhttps:\/\/t.co\/4nrRh4l8IG\n\n#\uc7ac\uc708 #jaewin https:\/\/t.co\/ZdLk5BiHDn"
"Done with taxation supporting doc but still so mannnyyy work tak siap lagi \ud83d\ude2d"
"RT @RenfuuP: \ud64d\uc900\ud45c \uc9c0\uc9c0\uc728 20%\uc778 \uac1d\uad00\uc801 \uc774\uc720 https:\/\/t.co\/rLC9JCZ9XO"
"RT @iniemailku83: Tidak butuh lelaki yg kuat. Tp butuh lelaki yg bisa membuat celana dalam basah. Haaa"
"5\/10 02\u6642\u66f4\u65b0\uff01BUMP OF CHICKEN(\u30d0\u30f3\u30d7)\u306e\u30c1\u30b1\u30c3\u30c8\u304c27\u679a\u30ea\u30af\u30a8\u30b9\u30c8\u4e2d\u3002\u4eca\u3059\u3050\u30c1\u30a7\u30c3\u30af https:\/\/t.co\/acFozBWrcm #BUMPOFCHICKEN #BUMP #\u30d0\u30f3\u30d7"
"---https:\/\/t.co\/DLwBKzniz6"
"RT @KamalaHarris: Our kids deserve the truth. Their generation deserves access to clean air and clean water.\nhttps:\/\/t.co\/FgVHIx4FzY"
"#105 UNIVERSITY East Concourse Up Escalator is now Out of Service"
Итак, эти документы будут тестироваться. Второе, что нам очень нужно, — это сами percolate-запросы. Я решил сгенерировать 100K случайных запросов из 1000 самых популярных слов из твитов, сделав так, чтобы каждый из запросов имел 2 AND ключевых слова и 2 NOT ключевых слова. Вот что у меня получилось (синтаксис запросов немного отличается между Manticore Search и Elasticsearch, поэтому мне пришлось создать 2 набора схожих запросов):
$ head queries_100K_ms.txt queries_100K_es.txt
== queries_100K_ms.txt ==
ll 0553 -pop -crying
nuevo against -watch -has
strong trop -around -would
most mad -cosas -guess
heart estamos -is -esc2017
money tel -didn -suis
omg then -tel -get
eviniz yates -wait -mtvinstaglcabello
9 sa -ao -album
tout re -oi -trop
== queries_100K_es.txt ==
ll AND 0553 -pop -crying
nuevo AND against -watch -has
strong AND trop -around -would
most AND mad -cosas -guess
heart AND estamos -is -esc2017
money AND tel -didn -suis
omg AND then -tel -get
eviniz AND yates -wait -mtvinstaglcabello
9 AND sa -ao -album
tout AND re -oi -trop
Запросы не имеют смысла, но для целей тестирования производительности они имеют значение.
Вставка запросов
Следующий шаг — вставка запросов в Manticore Search и Elasticsearch. Это можно сделать довольно просто:
Manticore Search
$ IFS=$'\n'; time for n in `cat queries_100K_ms.txt`; do mysql -P9314 -h0 -e "insert into pq values('$n');"; done;
real 6m37.202s
user 0m20.512s
sys 1m38.764s
Elasticsearch
$ IFS=$'\n'; id=0; time for n in `cat queries_100K_es.txt`; do echo $n; curl -s -XPUT http://localhost:9200/pq/docs/$id?refresh -H 'Content-Type: application/json' -d "{\"query\": {\"query_string\": {\"query\": \"$n\" }}}" > /dev/null; id=$((id+1)); done;
...
mayo AND yourself -ils -tan
told AND should -man -go
well AND week -won -perfect
real 112m58.019s
user 11m4.168s
sys 6m45.024s
Удивительно, что вставка 600K запросов в Elasticsearch заняла в 17 раз больше времени, чем в Manticore Search.
Давайте посмотрим, какова производительность поиска. Чтобы не ошибиться с выводом, давайте сначала согласимся на некоторые предположения:
- конфигурации как Manticore Search, так и Elasticsearch используются такими, какими они есть из коробки или согласно базовой документации, т.е. оптимизации не были произведены в конфигурации ни одной из систем
- но поскольку Elasticsearch по умолчанию многопоточен, а Manticore Search нет, я добавил “dist_threads = 8” в конфигурацию Manticore Search, чтобы выровнять шансы
- один из тестов будет заключаться в полной загрузке сервера для измерения максимальной пропускной способности. В этом тесте, если мы увидим, что встроенная многопоточность в любом из движков не обеспечивает максимальную пропускную способность, мы поможем ей, добавив многопроцессорность к многопоточности.
- Достаточный размер кучи Java важен для Elasticsearch. Индекс занимает всего 23 МБ на диске, но мы позволим Elasticsearch использовать 32 ГБ ОЗУ на всякий случай
- Для тестов мы будем использовать этот инструмент нагрузочного тестирования с открытым исходным кодом - https://github.com/Ivinco/stress-tester
- Оба тестовых плагина открывают соединение только один раз на ребенка и извлекают все найденные документы, что включается в латентность
Запуск документов
Elasticsearch
Итак, давайте начнем, и прежде всего давайте поймем, какую максимальную пропускную способность может дать Elasticsearch. Мы знаем, что обычно пропускная способность увеличивается при увеличении размера партии, давайте измерим это с параллелизмом = 1 (-c=1
) на основе первых 10000 документов (--limit=10000
) из тех 600K, загруженных с archive.org и использованных для заполнения корпуса запросов.
$ for batchSize in 1 4 5 6 10 20 50 100 200; do ./test.php --plugin=es_pq_twitter.php --data=/home/snikolaev/twitter/text -b=$batchSize -c=1 --limit=10000 --csv; done;
concurrency;batch size;total time;throughput;elements count;latencies count;avg latency, ms;median latency, ms;95p latency, ms;99p latency, ms
1;1;34.638;288;10000;10000;3.27;2.702;6.457;23.308
1;4;21.413;466;10000;10000;8.381;6.135;19.207;57.288
1;5;20.184;495;10000;10000;9.926;7.285;22.768;60.953
1;6;19.773;505;10000;10000;11.634;8.672;26.118;63.064
1;10;19.984;500;10000;10000;19.687;15.826;57.909;76.686
1;20;23.438;426;10000;10000;46.662;40.104;101.406;119.874
1;50;30.882;323;10000;10000;153.864;157.726;232.276;251.269
1;100;69.842;143;10000;10000;696.237;390.192;2410.755;2684.982
1;200;146.443;68;10000;10000;2927.343;3221.381;3433.848;3661.143
Действительно, пропускная способность растет, но только до размера партии 6-10, затем она ухудшается. А общая пропускная способность выглядит очень слабой. С другой стороны, латентность выглядит очень хорошо, особенно для самых низких значений размера партии - 3-20мс для размеров партий 1 - 10. Тем временем я вижу, что сервер используется всего на 25%, что хорошо для латентности, но мы также хотим увидеть максимальную пропускную способность, так что давайте запустим тест снова с параллелизмом = 8:
$ for batchSize in 1 4 5 6 10 20 50 100 200; do ./test.php --plugin=es_pq_twitter.php --data=/home/snikolaev/twitter/text -b=$batchSize -c=8 --limit=10000 --csv; done;
8;1;12.87;777;10000;10000;8.771;4.864;50.212;63.838
8;4;7.98;1253;10000;10000;24.071;12.5;77.675;103.735
8;5;7.133;1401;10000;10000;27.538;15.42;79.169;99.058
8;6;7.04;1420;10000;10000;32.978;19.097;87.458;111.311
8;10;7.374;1356;10000;10000;57.576;51.933;117.053;172.985
8;20;8.642;1157;10000;10000;136.103;125.133;228.399;288.927
8;50;11.565;864;10000;10000;454.78;448.788;659.542;781.465
8;100;25.57;391;10000;10000;1976.077;1110.372;6744.786;7822.412
8;200;52.251;191;10000;10000;7957.451;8980.085;9773.551;10167.927
1;200;146.443;68;10000;10000;2927.343;3221.381;3433.848;3661.143
Гораздо лучше: сервер полностью загружен, и пропускная способность теперь намного выше, а также латентность. И мы все еще видим, что Elasticsearch ухудшается после размера партии 6-10:
Давайте придерживаться размера партии = 6, похоже, это наиболее оптимальное значение для этого теста. Давайте проведем более продолжительный тест и обработаем 100К документов (--limit=100000
):
$ ./test.php --plugin=es_pq_twitter.php --data=/home/snikolaev/twitter/text -b=6 -c=8 --limit=100000
Time elapsed: 0 sec, throughput (curr / from start): 0 / 0 rps, 0 children running, 100000 elements left
Time elapsed: 1.001 sec, throughput (curr / from start): 1510 / 1510 rps, 8 children running, 98440 elements left
Time elapsed: 2.002 sec, throughput (curr / from start): 1330 / 1420 rps, 8 children running, 97108 elements left
Time elapsed: 3.002 sec, throughput (curr / from start): 1433 / 1424 rps, 8 children running, 95674 elements left
...
Time elapsed: 67.099 sec, throughput (curr / from start): 1336 / 1465 rps, 8 children running, 1618 elements left
Time elapsed: 68.1 sec, throughput (curr / from start): 1431 / 1465 rps, 8 children running, 184 elements left
FINISHED. Total time: 68.345 sec, throughput: 1463 rps
Latency stats:
count: 100000 latencies analyzed
avg: 31.993 ms
median: 19.667 ms
95p: 83.06 ms
99p: 102.186 ms
Plugin's output:
Total matches: 509883
Count: 100000
И у нас есть число - Elasticsearch смог обработать 1463 документа в секунду со средней латентностью ~32мс. Первые 100К документов против 100К запросов в индексе дали 509883 совпадения. Нам нужно это число, чтобы затем сравнить с Manticore Search и убедиться, что они не отличаются слишком сильно, что означает, что мы проводим правильное сравнение.
Manticore Search
Теперь давайте посмотрим на Manticore Search:
$ for batchSize in 1 4 5 6 10 20 50 100 200 300; do ./test.php --plugin=pq_twitter.php --data=/home/snikolaev/twitter/text -b=$batchSize -c=1 --limit=10000 --csv; done;
concurrency;batch size;total time;throughput;elements count;latencies count;avg latency, ms;median latency, ms;95p latency, ms;99p latency, ms
1;1;54.343;184;10000;10000;5.219;5.207;5.964;6.378
1;4;14.197;704;10000;10000;5.441;5.416;6.117;6.596
1;5;11.405;876;10000;10000;5.455;5.444;6.041;6.513
1;6;9.467;1056;10000;10000;5.466;5.436;6.116;6.534
1;10;5.935;1684;10000;10000;5.679;5.636;6.364;6.845
1;20;3.283;3045;10000;10000;6.149;6.042;7.097;8.001
1;50;1.605;6230;10000;10000;7.458;7.468;8.368;8.825
1;100;1.124;8899;10000;10000;10.019;9.972;11.654;12.542
1;200;0.95;10530;10000;10000;17.369;17.154;20.985;23.355
1;300;1.071;9334;10000;10000;29.058;28.667;34.449;42.163
Пропускная способность Manticore Search продолжает расти до размера партии 200, в то время как латентность не растет так сильно (с миллисекунд до секунд), как у Elasticsearch. Это на 2 мс выше, чем у Elasticsearch для размера партии 1.
Пропускная способность еще более различается по сравнению с Elasticsearch. Мы видим, что она намного ниже для размера партии < 20, но после этого, когда Elasticsearch начинает деградировать, пропускная способность Manticore Search возрастает до 10K документов в секунду.
Как и в первоначальном тесте Elastic, мы видим, что сервер не полностью загружен (80% в этом случае по сравнению с 25% Elastic). Давайте увеличим конкурентность с 1 до 2, чтобы лучше загрузить сервер:
$ for batchSize in 1 4 5 6 10 20 50 100 200 300; do ./test.php --plugin=pq_twitter.php --data=/home/snikolaev/twitter/text -b=$batchSize -c=2 --limit=10000 --csv; done;
concurrency;batch size;total time;throughput;elements count;latencies count;avg latency, ms;median latency, ms;95p latency, ms;99p latency, ms
2;1;44.715;223;10000;10000;8.445;8.017;12.682;15.386
2;4;11.852;843;10000;10000;8.906;8.493;13.411;16.085
2;5;9.468;1056;10000;10000;9.001;8.566;13.476;16.284
2;6;8.004;1249;10000;10000;9.073;8.626;13.647;15.848
2;10;4.931;2028;10000;10000;9.202;8.799;13.525;15.622
2;20;2.803;3567;10000;10000;9.924;9.352;15.322;18.252
2;50;1.352;7395;10000;10000;12.28;11.946;18.048;27.884
2;100;0.938;10659;10000;10000;17.098;16.832;22.914;26.719
2;200;0.76;13157;10000;10000;27.474;27.239;35.103;36.61
2;300;0.882;11337;10000;10000;47.747;47.611;63.327;70.952
Это повысило пропускную способность для размера партии 200 с 10K до 13157 документов в секунду, хотя задержка тоже увеличилась.
Давайте остановимся на этом размере партии и конкурентности и проведем расширенный тест с 100K документами:
$ ./test.php --plugin=pq_twitter.php --data=/home/snikolaev/twitter/text -b=200 -c=2 --limit=100000
Time elapsed: 0 sec, throughput (curr / from start): 0 / 0 rps, 0 children running, 100000 elements left
Time elapsed: 1.001 sec, throughput (curr / from start): 12587 / 12586 rps, 2 children running, 87000 elements left
Time elapsed: 2.002 sec, throughput (curr / from start): 12990 / 12787 rps, 2 children running, 73800 elements left
Time elapsed: 3.002 sec, throughput (curr / from start): 13397 / 12991 rps, 2 children running, 60600 elements left
Time elapsed: 4.003 sec, throughput (curr / from start): 12992 / 12991 rps, 2 children running, 47600 elements left
Time elapsed: 5.004 sec, throughput (curr / from start): 12984 / 12989 rps, 2 children running, 34600 elements left
Time elapsed: 6.005 sec, throughput (curr / from start): 12787 / 12956 rps, 2 children running, 21800 elements left
Time elapsed: 7.005 sec, throughput (curr / from start): 12999 / 12962 rps, 2 children running, 8800 elements left
FINISHED. Total time: 7.722 sec, throughput: 12949 rps
Latency stats:
count: 100000 latencies analyzed
avg: 28.631 ms
median: 28.111 ms
95p: 37.679 ms
99p: 44.615 ms
Plugin's output:
Total matches: 518874
Count: 100000
Мы видим, что:
- Общее количество совпадений не сильно различается (519K против 510K в Elastic), что означает, что мы правильно сравниваем вещи, небольшая разница, вероятно, вызвана незначительными различиями в токенизации текста.
- Пропускная способность составляет 12949 документов в секунду.
- Средняя задержка составляет ~29ms.
Следующие 2 графика показывают сравнение между Elasticsearch и Manticore Search при максимальной пропускной способности.
Медианная (50-й процентиль) задержка немного лучше в Elasticsearch, средняя немного лучше в Manticore Search. 95p и 99p задержка значительно различаются: здесь выигрывает Elasticsearch.
Но когда речь идет о пропускной способности, Manticore Search превосходит Elasticsearch более чем в 8 раз.
Выводы
- Если вам нужно обрабатывать тысячи документов в секунду и вы можете позволить себе задержку в десятки миллисекунд (а именно 32ms), Manticore Search может обеспечить гораздо большую пропускную способность: ~13K rps против 1463 rps с Elastic.
- Если ваша цель — исключительно низкая и стабильная задержка (несколько мс), и у вас нет большого количества документов или вы можете распределить свою нагрузку между многими серверами (например, 8 серверов Elasticsearch вместо 1, работающего на Manticore Search), чтобы обеспечить желаемую задержку, Elasticsearch может обеспечить задержку до 3.27ms при пропускной способности 288 документов в секунду. Manticore Search может предоставить лишь 5.2ms при 188 rps или 29ms при 13K rps.