介绍
Percolate 查询也被称为持久查询、前瞻性搜索、文档路由、反向搜索或逆向搜索。
进行搜索的正常方式是存储我们想要搜索的文档,并对其执行查询。然而,有些情况下我们希望将查询应用于即将到来的新文档,以发出匹配信号。有一些场景是需要这样的功能。例如,监控系统不仅收集数据,还希望在不同事件上通知用户。这可能是达到某个指标的阈值或监控数据中出现的某个特定值。另一个类似的案例是新闻聚合。您可以通知用户任何新鲜新闻,但用户可能只希望被通知某些类别或主题。进一步说,他们可能只对某些“关键词”感兴趣。
进行 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 ,给了我大约 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"
所以这些将是我们将要测试的文档。我们迫切需要的第二件事实际上是 percolate 查询。我决定从推文中最流行的 1000 个单词中生成 100K 随机查询,使每个查询包含 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 的时间是 Manticore Search 的 17 倍。
让我们看看搜索性能如何。为了不误解结论,让我们首先达成几个假设:
- Manticore Search 和 Elasticsearch 的配置都是按原样使用的,或者根据基本文档使用,即没有对任一配置进行优化
- 但由于 Elasticsearch 默认是多线程的,而 Manticore Search 不是,我在 Manticore Search 的配置中添加了 "dist_threads = 8" 以平衡机会
- 测试之一将是充分利用服务器以测量最大吞吐量。在此测试中,如果我们看到任一引擎的内置多线程没有提供最大吞吐量,我们将通过将多处理添加到多线程来帮助它。
- 对于 Elasticsearch,足够的 Java 堆大小是重要的。索引在磁盘上只占 23MB,但我们将让 Elasticsearch 使用 32GB 的 RAM 以防万一
- 对于测试,我们将使用这个开源压力测试工具 - https://github.com/Ivinco/stress-tester
- 这两个测试插件每个子进程只打开一次连接,并获取所有找到的文档,这也包括在延迟中
运行文档
Elasticsearch
那么让我们开始,首先了解 Elasticsearch 可以提供的最大吞吐量。我们知道,通常当您增加批量大小时,吞吐量会增加,让我们在并发 = 1 (-c=1) 的情况下测量这一点,基于从 archive.org 下载的 600K 文档中的前 10000 个文档 (--limit=10000),用于填充查询语料库。
$ 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 时增长,但随后下降。总体吞吐量看起来非常弱。另一方面,延迟看起来非常好,尤其是在最低批量大小数字上 - 批量大小为 1 - 10 时为 3-20ms。与此同时,我可以看到服务器的利用率仅为 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 那样增长(从毫秒到秒)。不过,对于批量大小为 1,它比 Elasticsearch 高出 2ms。
The throughput is even more different comparing to Elasticsearch. 我们可以看到,对于批量大小 < 20,它的吞吐量要低得多,但在此之后,当 Elasticsearch 开始下降时,Manticore Search 的吞吐量增长到每秒 10K 文档。
As well as with the initial Elastic's test we see that the server is not fully utilized (80% in this case comparing to Elastic's 25%). 让我们将并发从 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 文档,尽管延迟也增加了。
Let's stick with this batch size and concurrency and run an extended test with 100K documents:
$ ./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
We can see that:
- Total matches doesn't differ much (519K vs 510K in Elastic) which means we compare things properly, the slight difference is probably caused by minor difference in text tokenization
- The throughput is 12949 documents per second
- The average latency is ~29ms
The following 2 charts show comparison between Elasticsearch and Manticore Search at max throughput.
Median (50th percentile) latency is few milliseconds better in Elasticsearch, avg is a little bit better in Manticore Search. 95p and 99p latency differ a lot: Elasticsearch wins here.
But when it comes to the throughput Manticore Search outperforms Elasticsearch by more than 8 times.
Conclusions
- If you need to process thousands of documents per second and can afford tens of milliseconds latency (namely 32ms) Manticore Search can provide much higher throughput: ~13K rps vs 1463 rps with Elastic
- If your goal is extremely low and stable latency (few ms) and you don't have lots of documents or can afford distributing your load among many servers (e.g. 8 Elasticsearch servers instead of 1 running Manticore Search) to provide the desired latency Elasticsearch can provide as low as 3.27ms latency at throughput of 288 docs per second. Manticore Search can only give 5.2ms at 188 rps or 29ms at 13K rps.