介绍
Percolate 查询也被称为持久查询、前瞻性搜索、文档路由、反向搜索或逆向搜索。
进行搜索的正常方式是存储我们想要搜索的文档,并对此执行查询。然而,在某些情况下,我们希望将查询应用于传入的新文档,以发出匹配信号。有一些场景是需要这种功能的。例如,监控系统不仅收集数据,还希望在发生不同事件时通知用户。这可以是某个指标达到某个阈值或在监控数据中出现某个特定值。另一个类似的情况是新闻聚合。您可以通知用户关于任何新鲜新闻,但用户可能只想收到某些类别或主题的通知。更进一步,他们可能只对某些“关键词”感兴趣。
进行 percolate 查询的应用程序通常面临高负载:它们必须每秒处理数千个文档,并且可能有数十万条查询来测试每个文档。
Elasticsearch 和 Manticore Search 都提供 Percolate 查询。
尽管 Elasticsearch 的 Percolate 查询已经存在一段时间,但在 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"
"完成税收支持文档但还有很多工作没完成 \ud83d\ude2d"
"RT @RenfuuP: \ud64d\uc900\ud45c \uc9c0\uc9c0\uc728 20%\uc778 \uac1d\uad00\uc801 \uc774\uc720 https:\/\/t.co\/rLC9JCZ9XO"
"RT @iniemailku83: 不需要强壮的男人。需要的男人是能让内裤湿的人。哈"
"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: 我们的孩子理应得到真相。他们这一代人理应能享有清洁的空气和清洁的水。\nhttps:\/\/t.co\/FgVHIx4FzY"
"#105 大学东通道上楼梯现在已停止服务"
因此,这些将是我们要测试的文档。第二件我们急需的事情实际上是 percolate 查询。我决定从推文中前 1000 个最流行的单词生成 100K 随机查询,每个查询包含 2 个 AND 关键词和 2 个 NOT 关键词。以下是我得到的(查询语法在 Manticore Search 和 Elasticsearch 之间略有不同,所以我不得不构建两组相似的查询):
$ 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”,以平衡机会
- 测试之一将是充分利用服务器来测量最大吞吐量。在此测试中,如果我们看到任一引擎内置的多线程没有提供最大吞吐量,我们将通过将多进程添加到多线程来加以帮助。
- 足够的 Java 堆大小对 Elasticsearch 很重要。该索引在磁盘上只占用 23MB,但我们将允许 Elasticsearch 使用 32GB 的 RAM,以防万一
- 对于测试,我们将使用这个开源压力测试工具 - https://github.com/Ivinco/stress-tester
- 两个测试插件仅在每个子进程中打开一次连接,并获取所有找到的文档,这些文档被包含在延迟中
运行文档
Elasticsearch
所以让我们开始吧,首先了解 Elasticsearch 能提供的最大吞吐量。我们知道通常当你增加批量大小时,吞吐量会增加,让我们在并发 = 1 (-c=1
) 的基础上测量前 10000 个文档 (--limit=10000
),这些文档是从 archive.org 下载的 600K 文档中获得的,用于填充查询库。
$ 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。
与 Elasticsearch 相比,吞吐量差异更加明显。我们可以看到,对于批量大小 < 20 时,吞吐量要低得多,但之后当 Elasticsearch 开始下降时,Manticore Search 的吞吐量增长直到每秒 10K 文档。
与最初的 Elastic 测试一样,我们看到服务器并未完全利用(这次是 80%,相比 Elastic 的 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 文档/秒,不过延迟也相应增加。
让我们保持这个批量大小和并发数,运行一个包含 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 vs Elastic 的 510K),这意味着我们比较的对象是相当的,微小的差异可能是由文本分词的细微差异造成的
- 吞吐量为每秒 12949 文档
- 平均延迟约为 29ms
下面两个图表展示了 Elasticsearch 和 Manticore Search 在最大吞吐量下的对比。
中位数(50%分位)延迟在 Elasticsearch 上稍好几毫秒,平均值在 Manticore Search 上稍好。95%和99%分位延迟差异很大:Elasticsearch 在这方面胜出。
但在吞吐量方面,Manticore Search 的性能比 Elasticsearch 高出 8 倍多。
结论
- 如果您需要每秒处理数千文档,并且可以接受几十毫秒的延迟(具体为 32ms),Manticore Search 可以提供更高的吞吐量:约 13K rps 对比 Elastic 的 1463 rps
- 如果您的目标是极低且稳定的延迟(几毫秒),并且没有大量文档,或者可以在多台服务器之间分散负载(例如使用 8 台 Elasticsearch 服务器替代 1 台运行 Manticore Search 的服务器)以提供所需的延迟,那么 Elasticsearch 可以在 288 文档/秒的吞吐量下提供低至 3.27ms 的延迟。而 Manticore Search 只能在 188 rps 时提供 5.2ms,或在 13K rps 时提供 29ms。