独家深度 | 一文看懂 ClickHouse vs Elasticsearch:谁更胜一筹?
簡介: 本文的主旨在于通過徹底剖析ClickHouse和Elasticsearch的內核架構,從原理上講明白兩者的優劣之處,同時會附上一份覆蓋多場景的測試報告給讀者作為參考。
作者:阿里云數據庫OLAP產品部 仁劼
Clickhouse是俄羅斯搜索巨頭Yandex開發的完全列式存儲計算的分析型數據庫。ClickHouse在這兩年的OLAP領域中一直非常熱門,國內互聯網大廠都有大規模使用。Elasticsearch是一個近實時的分布式搜索分析引擎,它的底層存儲完全構建在Lucene之上。簡單來說是通過擴展Lucene的單機搜索能力,使其具有分布式的搜索和分析能力。Elasticsearch通常會和其它兩個開源組件Logstash(日志采集)和Kibana(儀表盤)一起提供端到端的日志/搜索分析的功能,常常被簡稱為ELK。
今天很多用戶在實際的業務場景中,常常面對ClickHouse和Elasticsearch技術選型的難題。用戶對ClickHouse和Elasticsearch的內核知識了解不足,往往只能通過性能測試的手段來進行選型。本文的主旨在于通過徹底剖析ClickHouse和Elasticsearch的內核架構,從原理上講明白兩者的優劣之處,同時會附上一份覆蓋多場景的測試報告給讀者作為參考。
分布式架構
Elasticsearch和ClickHouse都是支持分布式多機的數據產品,這里作者首先要比對的就是兩者的分布式架構差異,分布式結構設計對產品的易用性和可擴展性具有非常重要的影響。在分布式架構上,核心要解決的幾個問題包括:節點發現、Meta同步、副本數據同步。Elasticsearch作為一個老牌的開源產品,在這塊上做的相對比較成熟。原生的節點發現、Meta同步協議,給用戶非常好的易用性體驗。Elasticsearch的Meta同步協議需要解決的問題其實和開源的Raft協議非常相似,只不過在Elasticsearch誕生的時候還沒有Raft出現,所以就只能自己動手搞一個了。經過這么多年的打磨,Elasticsearch的Meta同步協議也是相當成熟了。依托于此,Elasticsearch具有非常易用的多角色劃分,auto schema inference等功能。值得一提的是Elasticsearch的多副本數據同步,并沒有復用Meta同步協議,而是采用傳統的主備同步機制,由主節點負責同步到備節點,這種方式會更加簡單高效。
ClickHouse的分布式架構能力相對會簡單一些,這也是因為ClickHouse還是一個比較年輕的開源產品,還處在分布式易用性不斷迭代上升的階段。ClickHouse引入了外置的ZooKeeper集群,來進行分布式DDL任務(節點Meta變更)、主備同步任務等操作的下發。多副本之間的數據同步(data shipping)任務下發也是依賴于ZooKeeper集群,但最終多副本之間的數據傳輸還是通過Http協議來進行點對點的數據拷貝,同時多副本都可寫,數據同步是完全多向的。至于節點發現,ClickHouse目前都沒有這方面的能力,都是需要通過手動配置集群節點地址來解決。ClickHouse目前這種腳手架式的分布式架構,導致它具有極強的靈活部署能力和運維介入能力,對用戶的易用性略差,用戶門檻相對較高,但是在能力上限方面,ClickHouse的分布式部署擴展性并沒有短板,集群規模上限對比Elasticsearch沒有差異。ClickHouse架構扁平,沒有前端節點和后端節點之分,可部署任意規模集群。同時ClickHouse在多副本功能上有更細粒度的控制能力,可以做到表級別副本數配置,同一物理集群可劃分多個邏輯集群,每個邏輯機器可任意配置分片數和副本數。
存儲架構
寫入鏈路設計
寫入吞吐能力是大數據場景下的一項核心指標,用戶對大數據產品的要求不光是要存的下,還要寫得快。這里首先介紹Elasticsearch的實時寫入鏈路設計:在Elasticsearch的每一個Shard中,寫入流程分為兩部分,先寫入Lucene,再寫入TransLog。寫入請求到達Shard后,先寫Lucene內存索引,此時數據還在內存里面,接著去寫TransLog,寫完TransLog后,刷新TransLog數據到磁盤上,寫磁盤成功后,請求返回給用戶。這里有幾個關鍵點,一是把寫Lucene放在了最前面,主要是防止用戶的寫入請求包含“非法”的數據。二是寫Lucene索引后,并不是可被搜索的,需要通過refresh把內存的對象轉成完整的Segment后,然后再次reopen后才能被搜索,這個refresh時間間隔是用戶可設定的。可以看出Lucene索引并沒有寫入實時可見的能力,所以Elasticsearch是一個近實時(Near Real Time)的系統。最后是每隔一段比較長的時間,比如30分鐘后,Lucene會把內存中生成的新Segment刷新到磁盤上,刷新后索引文件已經持久化了,歷史的TransLog就沒用了,才會清空掉舊的TransLog。
△Elasticsearch單Shard寫入鏈路
△ClickHouse單Shard寫入鏈路
對比Elasticsearch的寫入鏈路,ClickHouse的寫入方式更加“簡單直接”、極致,上面已經講過Elasticsearch是一個近實時系統,內存存儲引擎中新寫入的數據需要定時flush才可見。而ClickHouse則是干脆徹底放棄了內存存儲引擎這一功能,所有的數據寫入時直接落盤,同時也就省略了傳統的寫redo日志階段。在極高寫入吞吐要求的場景下,Elasticsearch和ClickHouse都需要為了提升吞吐而放棄部分寫入實時可見性。只不過ClickHouse主推的做法是把數據延遲攢批寫入交給客戶端來實現。另外在多副本同步上,Elasticsearch要求的是實時同步,也就是寫入請求必須寫穿多個副本才會返回,而ClickHouse是依賴于ZooKeeper做異步的磁盤文件同步(data shipping)。在實戰中ClickHouse的寫入吞吐能力可以遠遠超過同規格的Elasticsearch。
Segment vs DataPart
Elasticsearch和ClickHouse的存儲設計外表上看起來非常相似,但能力卻又截然不同。Elasticsearch的磁盤文件由一個個Segment組成,Segment實際上是一份最小單位的Lucene索引,關于Segment內部的存儲格式這里不展開討論。而Segment又會在后臺異步合并,這里合并主要解決兩個問題:1)讓二級索引更加有序;2)完成主鍵數據變更。二級索引是一種“全局”有序的索引,全部數據構建到一個索引里面比構建到多個索引里對查詢的加速更明顯。Elasticsearch是支持主鍵刪除更新的,這都是依托于Lucene索引的刪除功能來實現的,更新操作會被轉換成刪除操作加寫入操作。當Lucene索引的Segment里存在多條刪除記錄時,系統就需要通過Segment合并來剔除這些記錄。在多個Segment進行合并的時候,Lucene索引中的存儲數據表現出的是append-only的合并,這種方式下二級索引的合并就不需要進行“重排序”。
對比Elasticsearch中的Segment,ClickHouse存儲中的最小單位是DataPart,一次批量寫入的數據會落盤成一個DataPart。DataPart內部的數據存儲是完全有序的狀態(按照表定義的order by排序),這種有序存儲就是一種默認聚簇索引可以用來加速數據掃描。ClickHouse也會對DataPart進行異步合并,其合并也是用來解決兩個問題:1)讓數據存儲更加有序;2)完成主鍵數據變更。DataPart在合并存儲數據時表現出的是merge-sorted的方式,合并后產生的DataPart仍然處于完全有序狀態。依賴于DataPart存儲完全有序的設定,ClickHouse實現主鍵數據更新的方式和Elasticsearch截然不同。Elasticsearch在變更主鍵時,采用的是“先查原紀錄-生成新記錄-刪除原紀錄-寫入新紀錄”的方式,這種方式完全限制住了主鍵更新的效率,主鍵更新寫入和append-only寫入的效率差異非常大。而ClickHouse的主鍵更新是完全異步進行的,主鍵相同的多條記錄在異步合并的時候會產生最新的記錄結果。這種異步批量的主鍵更新方式比Elasticsearch更加高效。
最后總結一下Segment和DataPart內部文件存儲的能力差異,Segment完全就是Lucene索引的存儲格式,Lucene索引在倒排文件上的存儲毋庸置疑是做到極致的,Lucene索引同時也提供了行存、列存等不同格式的原數據存儲。Elasticsearch默認都會把原數據存兩份,一份在行存里,一份在列存里。Elasticsearch會根據查詢的pattern,選擇掃描的合適的存儲文件。原生ClickHouse的DataPart中并沒有任何二級索引文件,數據完全按列存儲,ClickHouse實現的列存在壓縮率、掃描吞吐上都做到了極致。相對而言Elasticsearch中的存儲比較中庸,并且成本至少翻倍。
再談Schemaless
講到Elasticsearch的特性,大家都會提到Schemaless這個詞,Elasticsearch可以自動推斷寫入數據的json-shema,根據寫入數據的json-schema調整存儲表的Meta結構,這可以幫助用戶節省很多建表、加列的麻煩。但是在作者看來,Elasticsearch的這種能力其實叫auto schema inference更為恰當,這都得益于Elasticsearch的分布式Meta同步能力。而Elasticsearch的存儲其實是需要schema的,甚至是強綁定schema的,因為它是以二級索引為核心的存儲,沒有類型的字段又如何能構建索引呢?真正的Schemaless應該是可以靈活高效變更字段類型,同時保證查詢性能不會大幅下降的能力。今天用戶想變更Elasticsearch index中的某個字段類型,那只有一種方法:就是把整份數據數據reindex。相對比,ClickHouse的存儲反而不是強綁定schema的,因為ClickHouse的分析能力是以存儲掃描為核心的,它是可以在數據掃描進行動態類型轉換,也可以在DataPart合并的時候慢慢異步調整字段的類型,在查詢的時候字段類型變更引起的代價也就是運行時增加cast算子的開銷,用戶不會感受到急劇的性能下降。作者認為Schemeless絕對不是Elasticsearch的護城河能力,相對反而是它的弱項。至于auto schema inference,這是對小規模用戶非常友好的能力,但它永遠不可能能幫用戶創建出性能最佳的Schema,在大數據量場景下大家還是需要根據具體的查詢需求來創建Schema,所有的便利最后都是有成本代價的。
查詢架構
計算引擎
作者在這里把ClickHouse和Elasticsearch擺在一起講計算引擎其實有些荒謬的味道,因為Elasticsearch實現的只是一個通用化搜索引擎。而搜索引擎能處理的查詢復雜度是確定的、有上限的,所有的搜索查詢經過確定的若干個階段就可以得出結果,但是計算引擎則不然。Elasticsearch雖然也有SQL支持的插件,但是這種插件的實現邏輯就是把簡單的SQL查詢翻譯到確定的搜索模式上面。對于搜索引擎原來就不支持的數據分析行為,Elasticsearch-SQL也無濟于事。另外Elasticsearch-SQL當前的翻譯能力看起來并不是非常完備和智能,為了獲得最高的搜索性能用戶還是需要嘗試Elasticsearch原生的查詢API。對于習慣使用SQL的用戶而言,Elasticsearch的查詢API是完全陌生的一套體系,復雜查詢非常難寫。
Elasticsearch的搜索引擎支持三種不同模式的搜索方式:query_and_fetch,query_then_fetch,dfs_query_then_fetch。第一種模式很簡單,每個分布式節點獨立搜索然后把得到的結果返回給客戶端,第二種模式是每個分布式存儲節點先搜索到各自TopN的記錄Id和對應的score,匯聚到查詢請求節點后做重排得到最終的TopN結果,最后再請求存儲節點去拉取明細數據。這里設計成兩輪請求的目的就是盡量減少拉取明細的數量,也就是磁盤掃描的次數。最后一種方式是為了均衡各個存儲節點打分的標準,先統計全局的TF(Term Frequency)和DF(Document Frequency),再進行query_then_fetch。Elasticsearch的搜索引擎完全不具備數據庫計算引擎的流式處理能力,它是完全回合制的request-response數據處理。當用戶需要返回的數據量很大時,就很容易出現查詢失敗,或者觸發GC。一般來說Elasticsearch的搜索引擎能力上限就是兩階段的查詢,像多表關聯這種查詢是完全超出其能力上限的。
ClickHouse的計算引擎特點則是極致的向量化,完全用c++模板手寫的向量化函數和aggregator算子使得它在聚合查詢上的處理性能達到了極致。配合上存儲極致的并行掃描能力,輕松就可以把機器資源跑滿。ClickHouse的計算引擎能力在分析查詢支持上可以完全覆蓋住Elasticsearch的搜索引擎,有完備SQL能力的計算引擎可以讓用戶在處理數據分析時更加靈活、自由。
數據掃描
ClickHouse是完全列式的存儲計算引擎,而且是以有序存儲為核心,在查詢掃描數據的過程中,首先會根據存儲的有序性、列存塊統計信息、分區鍵等信息推斷出需要掃描的列存塊,然后進行并行的數據掃描,像表達式計算、聚合算子都是在正規的計算引擎中處理。從計算引擎到數據掃描,數據流轉都是以列存塊為單位,高度向量化的。而Elasticsearch的數據掃描如上一節所述,主要發生在query和fetch階段。其中query階段主要是掃描Lucene的索引文件獲取查詢命中的DocId,也包括掃描列存文件進行聚合計算。而fetch階段主要是點查Lucene索引中的行存文件讀取明細結果。表達式計算和聚合計算在兩個階段都有可能發生,其計算邏輯都是以行為單位進行運算。總的來說Elasticsearch的數據掃描和計算都沒有向量化的能力,而且是以二級索引結果為基礎,當二級索引返回的命中行數特別大時(涉及大量數據的分析查詢),其搜索引擎就會暴露出數據處理能力不足的短板。
再談高并發
很多用戶談到ClickHouse,都會有一個錯誤的映像,ClickHouse查詢跑得快,但是并發不行。但這背后的原因其實是ClickHouse的并行太牛逼了,這是ClickHouse的一大強項,一個查詢就可以把磁盤吞吐都打滿,查詢并行完全不依賴于shard,可以任意調整。不可否認處理并發請求的吞吐能力是衡量一個數據系統效率的最終指標,ClickHouse的架構上并沒有什么天然的并發缺陷,只不過它是個耿直boy,查詢需要掃描的數據量和計算復雜度擺在那,ClickHouse只是每次都老老實實計算而已,機器的硬件能力就決定了它的并發上限。ClickHouse的并發能力事實上是不錯的,認為它并發不行是個誤區。只是默認情況下ClickHouse的目標是保證單個query的latency足夠低;部分場景下用戶可以通過設置合適的系統參數來提升并發能力,比如max_threads等。反過來,在這里介紹一下為什么有些場景下Elasticsearch的并發能力會很好。首先從Cache設計層面來看,Elasticsearch的Cache包括Query Cache, Request Cache,Data Cache,Index Cache,從查詢結果到索引掃描結果層層的Cache加速,就是因為Elasticsearch認為它的場景下存在熱點數據,可能被反復查詢。反觀ClickHouse,只有一個面向IO的UnCompressedBlockCache和系統的PageCache,為什么呢?因為ClickHouse立足于分析查詢場景,分析場景下的數據和查詢都是多變的,查詢結果等Cache都不容易命中,所以ClickHouse的做法是始終圍繞磁盤數據,具備良好的IO Cache能力。其次回到數據掃描粒度,Elasticsearch具備全列的二級索引能力,這些索引一般都是預熱好提前加載到內存中的,即使在多變的查詢條件下索引查詢得到結果的代價也很低,拿到索引結果就可以按行讀取數據進行計算。而原生ClickHouse并沒有二級索引的能力,在多變的查詢條件下只能大批量地去掃描數據過濾出結果(阿里云ClickHouse已經具備二級索引能力,解決了這一問題,性能水平和Elasticsearch相當,后續性能測評部分會進行詳細介紹)。但是Elasticsearch具備二級索引,并發能力就一定會好么?也不盡然,當二級索引搜索得到的結果集很大時,查詢還是會伴隨大量的IO掃描,高并發就無從談起,除非Elasticsearch的Data Cache足夠大,把所有原數據都加載到內存里來。
總結來說,Elasticsearch只有在完全搜索場景下面(where過濾后的記錄數較少),并且內存足夠的運行環境下,才能展現出并發上的優勢。而在分析場景下(where過濾后的記錄數較多),ClickHouse憑借極致的列存和向量化計算會有更加出色的并發表現。兩者的側重不同而已,同時ClickHouse并發處理能力立足于磁盤吞吐,而Elasticsearch的并發處理能力立足于內存Cache。ClickHouse更加適合低成本、大數據量的分析場景,它能夠充分利用磁盤的帶寬能力。
?
性能測試
在本章中,作者選取了用戶業務中多個具有代表性的數據場景,以此對Elasticsearch和ClickHouse做了一個全方面多角度的性能測試報告。具體的測試集群環境如下:
?
| Clickhouse | Elasticsearch | 節點數 |
| CPU:8core Memory:32GB 存儲:ESSD PL1 1500GB | CPU:8core Memory:32GB 存儲:ESSD PL1 1500GB | 4 |
?
日志分析場景
作者在日志分析場景中選取了兩個具有代表性的查詢場景進行對比測試,結果如下所示。從結果分析來看ClickHouse和Elasicsearch在兩個場景中的性能差距隨著where條件過濾后的記錄數增大而擴大,在數據量更大的trace_log場景中,兩者的分析查詢性能差距一目了然。Elasticsearch和ClickHouse完整版建表語句和查詢下載:日志分析場景
access_log(數據量197921836)
ClickHouse中的建表語句如下:
CREATE TABLE access_log_local on cluster default (`sql` String, `schema` String, `type` String, `access_ip` String, `conn_id` UInt32, `process_id` String, `logic_ins_id` UInt32, `accept_time` UInt64, `_date` DateTime, `total_time` UInt32, `succeed` String, `inst_name` String ) ENGINE = MergeTree() PARTITION BY toYYYYMMDD(_date) ORDER BY (logic_ins_id, accept_time);CREATE TABLE access_log on cluster default as access_log_local engine = Distributed(default, default, access_log_local, rand());?
ClickHouse中的查詢語句如下:
--Q1 select _date, accept_time, access_ip, type, total_time, concat(toString(total_time),'ms') as total_time_ms, sql,schema,succeed,process_id,inst_name from access_log where _date >= '2020-12-27 00:38:31' and _date <= '2020-12-28 00:38:31' and logic_ins_id = 502680264 and accept_time <= 1609087111000 and accept_time >= 1609000711000 and positionCaseInsensitive(sql, 'select') > 0 order by accept_time desc limit 50,50; --Q2 select case when total_time <=100 then 1 when total_time > 100 and total_time <= 500 then 2 when total_time > 500 and total_time <= 1000 then 3 when total_time > 1000 and total_time <= 3000 then 4 when total_time > 3000 and total_time <= 10000 then 5 when total_time > 10000 and total_time <= 30000 then 6 else 7 end as reorder, case when total_time <=100 then '0~100ms' when total_time > 100 and total_time <= 500 then '100ms~500ms' when total_time > 500 and total_time <= 1000 then '500ms~1s' when total_time > 1000 and total_time <= 3000 then '1s~3s' when total_time > 3000 and total_time <= 10000 then '3s~10s' when total_time > 10000 and total_time <= 30000 then '10s~30s' else '30s以上' end as label, case when total_time <= 100 then '0~100' when total_time > 100 and total_time <= 500 then '100~500' when total_time > 500 and total_time <= 1000 then '500~1000' when total_time > 1000 and total_time <= 3000 then '1000~3000' when total_time > 3000 and total_time <= 10000 then '3000~10000' when total_time > 10000 and total_time <= 30000 then '10000~30000' else '30000~10000000000' end as vlabel, count() as value from access_log where logic_ins_id = 502867976 and _date >= '2020-12-27 00:38:31' and _date <= '2020-12-28 00:38:31' and accept_time <= 1609087111000 and accept_time >= 1609000711000 group by label,vlabel,reorder order by reorder; --Q3 select toStartOfMinute(_date) as time, count() as value from access_log where logic_ins_id = 500152868 and accept_time <= 1609087111000 and accept_time >= 1609000711000 group by time order by time; --Q4 select count(*) as c from (select _date, accept_time, access_ip, type, total_time, concat(toString(total_time),'ms') as total_time_ms, sql, schema, succeed, process_id, inst_name from access_log where logic_ins_id = 501422856 and _date >= '2020-12-27 00:38:31' and _date <= '2020-12-28 00:38:31' and accept_time <= 1609087111000 and accept_time >= 1609000711000 );?
性能對比如下:
?
?
trace_log(數據量569816761)
?
ClickHouse中的建表語句如下:
CREATE TABLE trace_local on cluster default (`serviceName` LowCardinality(String), `host` LowCardinality(String), `ip` String, `spanName` String, `spanId` String, `pid` LowCardinality(String), `parentSpanId` String, `ppid` String, `duration` Int64, `rpcType` Int32, `startTime` Int64, `traceId` String, `tags.k` Array(String), `tags.v` Array(String), `events` String,KEY trace_idx traceId TYPE range ) ENGINE = MergeTree() PARTITION BY intDiv(startTime, toInt64(7200000000)) PRIMARY KEY (serviceName, host, ip, pid, spanName) ORDER BY (serviceName, host, ip, pid, spanName, tags.k);CREATE TABLE trace on cluster default as trace_local engine = Distributed(default, default, trace_local, rand());?
ClickHouse中的查詢語句如下:
--Q1 select * from trace prewhere traceId ='ccc6084420b76183' where startTime > 1597968000300000 and startTime < 1598054399099000 settings max_threads = 1; --Q2 select count(*) count, spanName as name from trace where serviceName ='conan-dean-user-period' and startTime > 1597968000300000 and startTime < 1598054399099000 group by spanName order by count desc limit 1000; --Q3 select host as name, count(*) count from trace where serviceName ='conan-dean-user-period' and startTime > 1597968000300000 and startTime < 1598054399099000 group by host; --Q4 select count(*) count, tags.k as name from trace array join tags.k where serviceName ='conan-dean-user-period' and startTime > 1597968000300000 and startTime < 1598054399099000 group by tags.k; --Q5 select count(*) spancount, sum(duration) as sumDuration, intDiv(startTime, 1440000000) as timeSel from trace where serviceName ='conan-dean-user-period' and startTime > 1597968000300000 and startTime < 1598054399099000 group by timeSel; --Q6 select count(*) spanCount, countIf(duration <=1000000), countIf(duration > 1000000), countIf(duration > 3000000) from trace where serviceName ='conan-dean-user-period' and startTime > 1597968000300000 and startTime < 1598054399099000; --Q7 select host, startTime,traceId,spanName,duration,tags.k,tags.v from trace where serviceName ='conan-dean-user-period' and startTime > 1597968000300000 and startTime < 1598054399099000 limit 1000000;?
性能對比如下:
官方Ontime測試集
?
Ontime測試集是ClickHouse官網上推薦的一個分析型查詢benchmark,為了更加公證公開地對比ClickHouse和Elasticsearch在分析查詢上的性能差異。作者也引入了這個數據集進行測試比對,結果如下所示,ClickHouse在純分析型查詢場景下具有巨大性能優勢。Elasticsearch和ClickHouse完整版建表語句和查詢下載:聚合分析場景
?
用戶畫像場景(數據量262933269)
?
用戶畫像場景也是用戶比較難選擇使用Elasticsearch還是ClickHouse的一個典型場景,該場景的具體特點是超大寬表,大批量更新寫入,查詢返回的數據量大,篩選條件復雜多變。用戶在使用Elasticsearch時遇到的難點問題主要有兩個:數據寫不進去,導入慢;數據拉不出來,返回大規模明細數據非常慢。針對這個場景,作者根據真實用戶的業務場景,mock了一張接近150列的大寬表進行相關的查詢測試,具體的查詢如下所示,每條查詢返回的結果集在10萬到100萬行級別。Elasticsearch和ClickHouse完整版建表語句和查詢下載:用戶畫像場景
?
ClickHouse中的查詢語句如下:
--Q1 select user_id from person_tag where mock3d_like > 8 and mock3d_consume_content_cnt > 8 and mock_10_day_product_avg_amt < 1 settings append_squashing_after_filter = 1; --Q2 select user_id from person_tag where mock_7_day_receive_cnt > 8 and like_fitness = 1 and mock14d_share_cnt > 8 settings append_squashing_after_filter = 1; --Q3 select user_id from person_tag where home_perfer_mock_score > 8 and mock7d_access_homepage_cnt > 8 settings append_squashing_after_filter = 1; --Q4 select user_id from person_tag where is_send_register_coupon > 8 and mock1d_like > 8 settings append_squashing_after_filter = 1; --Q5 select user_id from person_tag where like_sports = 1 and like_3c = 1 and sex = 1 and like_dance = 1 and mock1d_share_cnt > 6 settings append_squashing_after_filter = 1; --Q6 select user_id from person_tag where mock14d_access_homepage_cnt > 8 and like_anime = 1 settings append_squashing_after_filter = 1; --Q7 select user_id,offline_ver,is_visitor,mock1d_comment_like,reg_days,mock14d_share_cnt,mock_30_order_avg_delivery_time_cnt,mock7d_comment_cnt,performance_rate,mock3d_valid_user_follow_cnt,mock30d_consume_content_cnt,like_cnt,like_photo,ls90_day_access_days,mock3d_release_trend_cnt,mock14d_access_homepage_range,qutdoor_perfer_mock_score,mock3d_access_homepage_cnt,mock_15_order_avg_delivery_time_cnt,mock7d_release_trend_cnt,like_food,mock30d_follow_topic_cnt,mock7d_is_access_topic,like_music,mock3d_interactive_cnt,mock14d_valid_user_follow_cnt,reg_platform,mock_7_day_lottery_participate_cnt,pre_churn_users,etl_time,like_anime,mock14d_access_homepage_cnt,mock14d_consume_content_cnt,like_travel,like_watches,mock14d_comment_like,ls30_day_access_days,mock14d_release_trend_cnt,ftooeawr_perfer_mock_score,mock7d_valid_user_follow_cnt,beauty_perfer_mock_score from person_tag where mock3d_like > 8 and mock3d_consume_content_cnt > 8 and mock_10_day_product_avg_amt < 1 settings append_squashing_after_filter = 1;?
查詢性能結果對比如下,可以看出Elasticsearch在掃描導出大量結果數據的場景下,性能非常大,返回的結果集越大越慢,其中Q5是查詢命中結果集很小的對比case。
二級索引點查場景(數據量1000000000)
?
在分析查詢業務場景中,用戶難免會有幾個明細點查case,例如根據日志traceId查詢明細信息。開源ClickHouse因為沒有二級索引能力,在遇到這種情況時,查詢性能對比Elasticsearch完全落后。阿里云ClickHouse自研了二級索引能力,補齊了這方面的短板,作者在這里專門加了一個二級索引點查的場景來進行性能對比測試。Elasticsearch和ClickHouse完整版建表語句和查詢下載:二級索引點查場景
?
ClickHouse中的建表語句如下:
CREATE TABLE point_search_test_local on cluster default (`PRI_KEY` String, `SED_KEY` String, `INT_0` UInt32, `INT_1` UInt32, `INT_2` UInt32, `INT_3` UInt32, `INT_4` UInt32, `LONG_0` UInt64, `LONG_1` UInt64, `LONG_2` UInt64, `LONG_3` UInt64, `LONG_4` UInt64, `STR_0` String, `STR_1` String, `STR_2` String, `STR_3` String, `STR_4` String, `FIXSTR_0` FixedString(16), `FIXSTR_1` FixedString(16), `FIXSTR_2` FixedString(16), `FIXSTR_3` FixedString(16), `FIXSTR_4` FixedString(16), KEY SED_KEY_IDX SED_KEY Type range ) ENGINE = MergeTree ORDER BY PRI_KEY SETTINGS index_granularity_bytes = 4096, secondary_key_segment_min_rows = 1000000000, min_rows_for_wide_part = 2000000000;CREATE TABLE point_search_test on cluster default as point_search_test_local engine = Distributed(default, default, point_search_test_local, rand());?
ClickHouse中的查詢模板語句如下:
select * from point_search_test where SED_KEY = 'XXX' settings max_threads = 1;?
最終的查詢性能對比如下,阿里云ClickHouse具備二級索引能力后,其點查能力完全不弱于Elasticsearch,存儲原生支持的二級索引能力,具有極致性能。(阿里云ClickHouse二級索引文檔)
數據導入性能對比
?
前面列舉的所有數據集數據,作者都使用了ESSD本地文件導入的方式測試對比了Elasticsearch和ClickHouse的導入性能。ClickHouse可以直接使用ClickHouse-Client讀取各種格式的本地文件進行導入,而Elasticsearch則是通過配置Logstash任務。具體耗時結果如下:
結語
Elasticsearch最擅長的主要是完全搜索場景(where過濾后的記錄數較少),在內存富裕運行環境下可以展現出非常出色的并發查詢能力。但是在大規模數據的分析場景下(where過濾后的記錄數較多),ClickHouse憑借極致的列存和向量化計算會有更加出色的并發表現,并且查詢支持完備度也更好。ClickHouse的并發處理能力立足于磁盤吞吐,而Elasticsearch的并發處理能力立足于內存Cache,這使得兩者的成本區間有很大差異,ClickHouse更加適合低成本、大數據量的分析場景,它能夠充分利用磁盤的帶寬能力。數據導入和存儲成本上,ClickHouse更加具有絕對的優勢。
原文鏈接
本文為阿里云原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的独家深度 | 一文看懂 ClickHouse vs Elasticsearch:谁更胜一筹?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云原生数据仓库TPC-H第一背后的Las
- 下一篇: 针对数据库连接池到DRDS连接探活的优化