ES(Elastic Search)針對日志場景(讀測試)
為了能衡量ES查詢日志的成本與性能,我們團隊北洲同學做的ES相關實驗(ZZ):
目的
對Elastic Search在日志查詢場景下的性能有一個比較直觀的認識,該場景可以描述為單TermQuery、時間區間為一個小時、按照時間降序排列、查詢結果支持翻頁、每頁兩百條。
數據集合:我們構造了兩組索引,分別是perf_simple和acc_20k,兩組索引分別用于模擬日志流量比較小的用戶以及流量比較大的用戶,perf_simple包含189887481個document,索引大小為40.64G,acc_20k包含907786934個doc,索引大小為270.02G,
環境配置:四個es服務部署在同一臺機器上,16個shard,0個replica,每個es服務自動分配四個shard。服務器配置為:
- Intel(R) Xeon(R) CPU E5-2430 0 @ 2.20GH 24Core
- 96GB
- 1W SATA </ul>
- 1.1和1.2對比time range對latency的影響:time range是1 hour的latency更大,此外隨著讀壓力的增大,latency也相應的升高,但是qps隨著讀壓力增大,剛開始是上升,后來便遇到瓶頸,接著便開始下降了。在實際場景中需要權衡好單機吞吐量,準確獲取吞吐量瓶頸以及和請求延遲的關系對用戶體驗至關重要,除此以外,這些信息對于集群規模大小的考量也很重要。
- 1.1和1.2對比還可以發現term query的數量對查詢性能影響也是很大的,雖然lucene對于類似date range的數值range查詢做了一些優化,使得query parser出來的query數量并不會隨著range的變大而呈正比變大,但是在我們測試的例子中,range由10min變大到1hour,time range query parser之后子term query變多了,這就意味著需要合并的倒排表數量變多了。
- 2.1是對acc_20k的測試,索引量放大了將近一個數量級,message中term range也放大了一個數量級,這就意味著term的倒排表長度在同一個數量級,從結果來看2.1和場景1.2中的測試數據基本上處在同一個level,這說明es的詞典大小以及索引量對性能影響不大,性能好壞主要影響因素是term的倒排表長度。2.2進一步驗證了2.1中的推論,使用 message:PutData作為查詢詞,hits count數量級為幾億條,latency明顯升高很多。
- es的查詢性能還是非常好的,主要體現在延時方面, 從測試的結果來看,查詢1億條數據查詢命中幾十萬次(場景1.1),需要100ms左右, 命中幾萬次(場景2),只需要40ms左右。
準備
壓測使用JMeter工具,查詢語句語義上等價于下面的sql:
select doc from perf_simple
where message=“${term}” and timestamp in [t0, t1]
order by timestamp desc limit 200 offset ${offset};
其中message是索引中的一個field,建索引時,該字段中包含類似200.300.500.600的子串,英文句點被作為分詞token,目的主要是為了方便構造TermQuery,term是可變量,offset是命中結果的偏移,”order by timestamp desc;limit 200 offset $offset“含義就是hits按照time降序排列之后取offset之后的200條。
perf_simple和acc_20k對應的索引格式相同,不同的地方在message字段的取值,字段格式舉例如下:
"message":"15852.5917.15852.4971 - - [27/Mar/2015:09:39:49 +0800] \"POST /PutData?APIVersion=0.3.0&AccessKeyId=&Date=MonMarxxxx&yabace=deqa&acdea=xxxeeqaGMT\" 15852 4402 7124 3375 \"-\" \"my-logagent\"",
"@version":"1",
"@timestamp":"2015-03-27T02:13:16.567Z",
"type":"perf",
"host":"rs1i07386.et2sqa",
"path":"/home/admin/test/test.log_1427420275"
perf_simple message字段中類似200.300.500.600的子串部分term的取值范圍是200到2000,acc_20k取值范圍是200到20000。
測試場景:
對perf_simple索引進行測試
在此場景下term為[200,2000]中的隨機值,此場景下hits count 在time range為1 hour時數量級為幾十萬條,所以offset取[0,100000]中的隨機數,在time range為10 min時數量級為幾萬條,此時offset取[0,10000]中的隨機數,這樣做的目的是為了模擬用戶翻頁。按照timestamp的range將場景 1分為兩部分,1hour和10min。
1.1 (t1-t0 == 1 hour) 讀1小時時間段日志
Threads | samples | average latency(ms) | qps | request size per sec(KB/s) |
---|---|---|---|---|
1 | 5201 | 98 | 10.1 | 632 |
7 | 11001 | 225 | 30.5 | 1905.6 |
Threads是用于壓測的線程數量,samples是樣本數量,average latency是請求的平均延遲,單位是毫秒,qps是請求吞吐量,request size per sec是每秒請求的平均大小,單位是KB/s。
1.2 (t1-t0 == 10 min) 讀10分鐘時間段日志
Threads | samples | average latency(ms) | qps | request size per sec(KB/s) |
---|---|---|---|---|
1 | 17501 | 15 | 61.8 | 2172.5 |
5 | 23201 | 39 | 120.4 | 4178.6 |
10 | 17982 | 72 | 130.9 | 4558.2 |
15 | 21001 | 108 | 130.2 | 4574.7 |
表格中最后一組數據都是把es服務所在機器的cpu打滿情況下的數據,jvm heap大小設置為11.85g。
對acc_20k索引進行測試(大用戶)
2.1 大用戶標準場景測試
變量的取值范圍為Offset:[0,10000],Term: [200,20000],Time range: 1 hour。該場景下hits count為幾萬條。
Threads | samples | average latency(ms) | qps | request size per sec(KB/s) |
---|---|---|---|---|
1 | 17088 | 10 | 71.4 | 4503.4 |
5 | 27760 | 21 | 194.4 | 12246.8 |
10 | 104441 | 39 | 229.3 | 14442.3 |
15 | 82601 | 62 | 232.9 | 14679.8 |
2.2 測試極限場景性能(幾乎不復用Cache)
offset從0開始每次遞增200,Term固定為PutData,Time range為1hour,該場景下hits count為幾億條
Threads | samples | average latency(ms) | qps | request size per sec(KB/s) | cpu usage | jvm heap usage(usage / limit) |
---|---|---|---|---|---|---|
1 | 1891 | 4389 | 13.6/min | 14.4 | 50% | 6.14g / 11.85g |
2 | 1414 | 5373 | 22.3/min | 23.6 | 80% | 8g / 11.85g |
3 | 1010 | 6237 | 28.7/min | 30.1 | 96% | 8.44g / 11.85g |
結果分析
排除client瓶頸
壓測過程中沒有發現error的情況,懷疑是瓶頸主要在client端,后來把單機15線程,分配到3臺機器,5threads/machine,發現兩種方式結果一樣,說明瓶頸在服務端。
延遲包含哪些因素
ES讀索引初始cpu iowait很高,此時latency比較大,說明disk io占了latency很大一部分,隨著讀的進行,iowait降低,latency變小,主要是因為索引被cache(es使用了mmap)。上面的測試結果是在cpu iowait較低的情況下獲得的,這樣也比較貼合用戶場景,因此下面的分析將主要考慮計算的latency。
結果分析
綜上所述es的查詢性能主要和兩方面因素有關系,分別是query parser之后term query的數量和倒排表長度,和被索引的文檔數量沒有直接的關系,這就指導我們分析的時候從三方面著手,分別是系統的查詢串復雜度、term規模、索引文檔規模,查詢串的復雜度主要通過parser之后term的數量衡量。
一些推測
結合之前對lucene的調研,對測試結論給出一些依據。假設在詞典中,distinct字母數量為n,使用樹存儲詞典(不考慮優化),樹的孩子遵從字母序,詞的平均長度為k,那么找到一個詞的最壞時間為k*logn(二分查找)。從這里可以看出查找一個詞和詞典的大小幾乎沒有關系,只和詞典中詞的分布有關系(不同的document集合,詞典不一樣,從而樹的結構也不一樣)。上面分析可以得出一個重要的結論:document上一定規模之后,在詞典中找詞的時間基本上不會隨著doc規模變化。
詞找到了,接著就是找倒排表,假設詞典中每個詞都可以攜帶數據(payload),這樣的數據包括倒排表的文件偏移、meta的文件偏移,meta包括詞的倒排表長度等,那么找到詞的同時也就找到了倒排表的位置,倒排表使用SkipList組織,這就意味著假如要在倒排表中找某一個特定的詞復雜度數量級為log(doc count),log的底取決于SkipList的Skip步長,如果是多個term query,and查詢,skip list求交集,可以互相之間加速,并且skiplist可以優化,比如在葉子上記錄parent偏移,skip list求交集將更快。粗略評估下復雜度,假設m個term,doc數量為n,復雜度為m*n*logn,這個結果沒有考慮skip list的優化。對于日期區間查詢,query parser會按照類似前綴樹重寫query,這個之前分享過,這樣就會大大減少m。Order by可以通過doc value perfield做,也就是通過空間換時間做一個doc->field value的map,查詢結果到這里排序。以上討論只針對lucene中一個segment的情況,以上過程在每個segment中都會走一遍,不同 seg的結果需要merge。
來自:http://simplelogservice.com/?p=10554