Введение
Percolate Queries также известны как Постоянные Запросы, Перспективный Поиск, маршрутизация документов, поиск в обратном направлении или обратный поиск.
Обычный способ выполнения поисков заключается в том, чтобы хранить документы, которые мы хотим искать, и выполнять запросы к ним. Однако есть случаи, когда мы хотим применить запрос к входящему новому документу, чтобы сигнализировать о совпадении. Есть несколько сценариев, где это необходимо. Например, система мониторинга не просто собирает данные, но также желательно уведомлять пользователя о различных событиях. Это может быть достижение некоторого порога для метрики или определенного значения, которое появляется в отслеживаемых данных. Другой аналогичный случай - агрегирование новостей. Вы можете уведомлять пользователя о любых свежих новостях, но пользователь может захотеть получать уведомления только о определенных категориях или темах. Двигаясь дальше, они могут быть заинтересованы только в определенных "ключевых словах".
Приложения, выполняющие перколяционные запросы, часто работают с высокой нагрузкой: им необходимо обрабатывать тысячи документов в секунду и могут иметь сотни тысяч запросов для проверки каждого документа.
Как Elasticsearch, так и Manticore Search предоставляют Percolate Queries.
Хотя Percolate Queries присутствуют в Elasticsearch уже некоторое время, в Manticore они были добавлены в версии 2.6.0, все еще отсутствуют в Sphinx. Сегодня я хотел бы провести некоторые измерения производительности, чтобы выяснить, какие цифры могут предоставить каждая из технологий.
Тестовый случай
Многие компании, работающие в индустрии маркетинга в социальных сетях, часто интересуются анализом Twitter firehose или decahose, другими словами, они хотят искать среди всех или части входящих сообщений, которые люди отправляют на twitter.com по всему миру, чтобы понять, насколько аудитория заинтересована в брендах, продуктах или чем-то еще их клиентов. Чтобы сделать наш тест ближе к реальности, мы также будем использовать образцы твитов. Я нашел хороший архив на archive.org здесь https://archive.org/details/tweets-20170508080715-crawl448 , который дал мне около 600K случайных твитов:
$ wc -l ~/twitter/text
607388 /home/snikolaev/twitter/text
Документы декодированы в json, но на всякий случай, если вы хотите получить представление о том, как они выглядят (да, давайте представим, что это все еще 2005 год, и никто не знает, как обычно выглядит сообщение в твиттере):
$ 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"
Итак, это будут документы, которые мы будем тестировать. Вторая вещь, которая нам очень нужна, это на самом деле перколяционные запросы. Я решил сгенерировать 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. Индекс занимает всего 23MB на диске, но мы позволим Elasticsearch использовать 32GB ОЗУ на всякий случай
- Для тестов мы будем использовать этот инструмент для стресс-тестирования с открытым исходным кодом - 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-20ms для размеров пакетов 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, похоже, это наиболее оптимальное значение для этого теста. Давайте проведем более длительный тест и обработаем 100K документов (--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 документа в секунду со средней задержкой ~32ms. Первые 100K документов против 100K запросов в индексе дали 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. Она на 2ms выше, чем у 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 документов в секунду.
- Средняя задержка составляет ~29 мс.
Следующие 2 графика показывают сравнение между Elasticsearch и Manticore Search при максимальной пропускной способности.
Медианная (50-й процентиль) задержка на несколько миллисекунд лучше в Elasticsearch, средняя немного лучше в Manticore Search. Задержка 95p и 99p сильно различается: здесь выигрывает Elasticsearch.
Но когда дело доходит до пропускной способности, Manticore Search превосходит Elasticsearch более чем в 8 раз.
Выводы
- Если вам нужно обрабатывать тысячи документов в секунду и вы можете позволить себе задержку в десятки миллисекунд (а именно 32 мс), Manticore Search может обеспечить гораздо более высокую пропускную способность: ~13K rps против 1463 rps с Elastic.
- Если ваша цель - крайне низкая и стабильная задержка (несколько мс), и у вас нет большого количества документов или вы можете распределить свою нагрузку между многими серверами (например, 8 серверов Elasticsearch вместо 1, работающего на Manticore Search), чтобы обеспечить желаемую задержку, Elasticsearch может обеспечить задержку до 3.27 мс при пропускной способности 288 документов в секунду. Manticore Search может дать только 5.2 мс при 188 rps или 29 мс при 13K rps.