2022年5月26日 安德烈·佩奇庫(kù)羅夫 QuestDB工程師
QuestDB 6.2是我們之前的次要版本,它為SQL過(guò)濾器引入了JIT(實(shí)時(shí))編譯器。正如我們上次提到的,下一步將是在適當(dāng)?shù)臅r(shí)候并行化查詢(xún)執(zhí)行,以進(jìn)一步提高執(zhí)行時(shí)間,這就是我們今天要討論和測(cè)試的內(nèi)容。QuestDB 6.3默認(rèn)啟用JIT編譯的過(guò)濾器,更值得注意的是,它還包括并行SQL過(guò)濾器執(zhí)行優(yōu)化,允許我們大大減少冷查詢(xún)和熱查詢(xún)執(zhí)行時(shí)間。
在深入研究實(shí)現(xiàn)細(xì)節(jié)并為QuestDB運(yùn)行一些前后基準(zhǔn)測(cè)試之前,我們將與兩個(gè)流行的時(shí)間序列和分析數(shù)據(jù)庫(kù)TimescaleDB和ClickHouse進(jìn)行友好的競(jìng)爭(zhēng)。本次競(jìng)賽的目的僅僅是試圖了解我們的并行過(guò)濾器執(zhí)行是否值真的有效。
安德烈·佩奇庫(kù)羅夫 QuestDB工程師
一、與其他數(shù)據(jù)庫(kù)比較
我們的測(cè)試箱是c5a.12xlarge AWS VM運(yùn)行Ubuntu服務(wù)器20.04 64位。實(shí)際上,這意味著48個(gè)vCPU和96 GB RAM。連接的存儲(chǔ)是一個(gè)1 TB gp3卷,配置為1000 MB/s吞吐量和16000 IOPS。除此之外,我們將使用QuestDB 6.3.1和默認(rèn)設(shè)置,這意味著同時(shí)啟用并行過(guò)濾器執(zhí)行和JIT編譯。
為了使基準(zhǔn)測(cè)試易于再現(xiàn),我們將使用TSBS基準(zhǔn)測(cè)試實(shí)用程序來(lái)生成數(shù)據(jù)。我們將使用所謂的物聯(lián)網(wǎng)用例:
/tsbs_generate_data --use-case="iot"
--seed=123
--scale=5000
--timestamp-start="2020-01-01T00:00:00Z"
--timestamp-end="2020-07-01T00:00:00Z"
--log-interval="60s"
--format="influx" > /tmp/data
/
上述命令為5000輛卡車(chē)物聯(lián)網(wǎng)設(shè)備生成六個(gè)月的每分鐘測(cè)量值。這將產(chǎn)生近12億條記錄,存儲(chǔ)在名為Reads的表中。
加載數(shù)據(jù)非常簡(jiǎn)單:
./tsbs_load_questdb --file /tmp/data
現(xiàn)在,當(dāng)數(shù)據(jù)庫(kù)中有數(shù)據(jù)時(shí),我們將對(duì)Readers表執(zhí)行以下查詢(xún):
Query 1
SELECT *
FROM readings WHERE velocity > 90.0
AND latitude >= 7.75 AND latitude <= 7.80
AND longitude >= 14.90 AND longitude <= 14.95;
這種(類(lèi)似于合成的)查詢(xún)旨在查找給定位置快速移動(dòng)卡車(chē)發(fā)送的所有測(cè)量值。除此之外,它在三個(gè)雙欄上有一個(gè)過(guò)濾器,不包括分析子句,如GROUP BY或SAMPLE BY,這正是我們所需要的。
我們的第一個(gè)競(jìng)爭(zhēng)對(duì)手是運(yùn)行在PostgreSQL 14.2之上的TimescaleDB 2.6.0。正如官方安裝指南所建議的那樣,我們確保運(yùn)行timescaledb tune來(lái)微調(diào)timescaledb以獲得更好的性能。
我們使用以下命令生成測(cè)試數(shù)據(jù):
./tsbs_generate_data --use-case="iot"
--seed=123
--scale=5000
--timestamp-start="2020-01-01T00:00:00Z"
--timestamp-end="2020-07-01T00:00:00Z"
--log-interval="60s"
--format="timescaledb" > /tmp/data
/
這與之前的命令相同,但format參數(shù)設(shè)置為timescaledb。接下來(lái),我們加載數(shù)據(jù):
./tsbs_load_timescaledb --pass your_pwd --file /tmp/data
QuestDB和此特定環(huán)境中的其他兩個(gè)數(shù)據(jù)庫(kù)。然而,對(duì)于任何想重復(fù)基準(zhǔn)的人來(lái)說(shuō),這只是一個(gè)注釋。如果您想了解有關(guān)攝入性能主題的更多信息,請(qǐng)查看此博客文章。(https://questdb.io/time-series-benchmark-suite/)
最后,我們可以運(yùn)行第一個(gè)查詢(xún)并測(cè)量熱執(zhí)行時(shí)間。然而,如果我們這樣做,TimescaleDB執(zhí)行此查詢(xún)將需要15分鐘以上。此時(shí),有經(jīng)驗(yàn)的TimescaleDB和PostgreSQL用戶(hù)可能會(huì)建議我們添加一個(gè)索引來(lái)加速這個(gè)具體的查詢(xún)。那么,讓我們這樣做:
CREATE INDEX ON readings (velocity, latitude, longitude);
有了索引,TimescaleDB可以在大約4.4秒內(nèi)更快地執(zhí)行查詢(xún)。為了全面了解情況,讓我們?cè)偌尤胍幻x手。
我們比賽的第三名成員是ClickHouse 22.4.1.752。與TimescaleDB一樣,生成數(shù)據(jù)的命令保持不變,只有format參數(shù)設(shè)置為clickhouse。生成數(shù)據(jù)后,可以將其加載到數(shù)據(jù)庫(kù)中:
./tsbs_load_clickhouse --file /tmp/data
我們已經(jīng)準(zhǔn)備好進(jìn)行基準(zhǔn)測(cè)試運(yùn)行。
上圖顯示,在這個(gè)特定查詢(xún)中,QuestDB比TimescaleDB和ClickHouse都快一個(gè)數(shù)量級(jí)。
有趣的是,基于索引的掃描并不能幫助TimescaleDB贏得競(jìng)爭(zhēng)。這很好地說(shuō)明了這樣一個(gè)事實(shí),即專(zhuān)用的并行友好存儲(chǔ)模型可以使您不必處理索引并在數(shù)據(jù)攝取期間支付額外的開(kāi)銷(xiāo)。
下一步,讓我們嘗試另一種流行的查詢(xún)類(lèi)型。在時(shí)間序列數(shù)據(jù)的世界中,通常只根據(jù)某個(gè)過(guò)濾器查詢(xún)最新的行。QuestDB通過(guò)負(fù)限制子句值優(yōu)雅地支持這一點(diǎn)。如果我們要查詢(xún)從快速移動(dòng)但省油的卡車(chē)發(fā)送的十個(gè)最新測(cè)量值,它將如下所示:
Query 2 (QuestDB)
SELECT *
FROM readings
WHERE velocity > 75.0 AND fuel_consumption < 10.0
LIMIT -10;
請(qǐng)注意查詢(xún)中的LIMIT-10子句。它基本上要求數(shù)據(jù)庫(kù)返回與過(guò)濾器相對(duì)應(yīng)的最后10行。由于基于指定的時(shí)間戳列的隱式升序,我們也不必指定order BY子句。
在TimescaleDB中,此查詢(xún)看起來(lái)更詳細(xì):
Query 2 (ClickHouse and TimescaleDB)
SELECT *
FROM readings
WHERE velocity > 75.0 AND fuel_consumption < 10.0
ORDER BY time DESC
LIMIT 10;
這里,我們必須指定降序BY和限制子句。當(dāng)談到ClickHouse時(shí),除了另一列用于存儲(chǔ)時(shí)間戳(創(chuàng)建時(shí)間而不是時(shí)間)之外,查詢(xún)看起來(lái)就像TimescaleDB。
我們列表中的數(shù)據(jù)庫(kù)如何處理此類(lèi)查詢(xún)?
讓我們測(cè)量并找出答案!
QuestDB、ClickHouse和TimescaleDB的熱限制查詢(xún)執(zhí)行次數(shù)-查詢(xún)2
這一次,不管是否令人驚訝,TimescaleDB比ClickHouse做得更好。這是因?yàn)?,就像QuestDB一樣,TimescaleDB過(guò)濾從最新的基于時(shí)間的分區(qū)開(kāi)始的數(shù)據(jù),并在找到足夠多的行后停止過(guò)濾。我們還可以在velocity和fuel_consumption列上添加一個(gè)索引,但這不會(huì)改變結(jié)果。這是因?yàn)門(mén)imescaleDB不使用索引,而是對(duì)此查詢(xún)進(jìn)行完全掃描。由于這種行為,QuestDB和TimescaleDB在練習(xí)中都比ClickHouse快得多。
不用說(shuō),TimescaleDB和ClickHouse都是偉大的工程。您的觀察可能會(huì)有所不同,而您的特定應(yīng)用程序的性能取決于許多因素。因此,與任何基準(zhǔn)一樣,您可以帶著懷疑來(lái)看待我們的測(cè)試結(jié)果,并且自己進(jìn)行測(cè)試。
這是我們的比較,現(xiàn)在是討論并行SQL過(guò)濾器執(zhí)行背后的設(shè)計(jì)決策的時(shí)候了。
二、它是如何工作的?
首先,讓我們快速回顧一下QuestDB的存儲(chǔ)模型,以了解它為什么支持高效的多核執(zhí)行。數(shù)據(jù)庫(kù)具有基于列的僅附加存儲(chǔ)模型。數(shù)據(jù)存儲(chǔ)在表中,每列存儲(chǔ)在其自己的文件或多個(gè)文件中,以防表按指定的時(shí)間戳進(jìn)行分區(qū)。
列文件布局示例
執(zhí)行SQL篩選器(think,WHERE子句)時(shí),數(shù)據(jù)庫(kù)需要掃描文件以查找相應(yīng)的篩選列。正如您可能已經(jīng)猜到的,當(dāng)列文件足夠大,或者查詢(xún)涉及多個(gè)分區(qū)時(shí),在單個(gè)線程上過(guò)濾記錄的效率很低。相反,可以將文件拆分為連續(xù)的塊(我們稱(chēng)之為“頁(yè)面幀”)。然后,多個(gè)線程可以以一種更為優(yōu)化的方式,利用CPU和磁盤(pán)資源在每個(gè)頁(yè)面幀上執(zhí)行過(guò)濾器。
并行頁(yè)幀掃描示例
我們已經(jīng)對(duì)某些分析類(lèi)型的查詢(xún)進(jìn)行了此優(yōu)化,但對(duì)于使用過(guò)濾器的完整或部分表掃描,我們沒(méi)有進(jìn)行此優(yōu)化。這就是我們?cè)?.3版中添加的內(nèi)容。
像往常一樣,存在邊緣案例和暗礁,因此實(shí)現(xiàn)并不像聽(tīng)起來(lái)那么簡(jiǎn)單。比方說(shuō),如果您的查詢(xún)有一個(gè)過(guò)濾器和一個(gè)LIMIT-10子句,就像我們最近的基準(zhǔn)測(cè)試中一樣,該怎么辦?然后,數(shù)據(jù)庫(kù)應(yīng)該并行執(zhí)行查詢(xún),獲取最后10條記錄并取消剩余的頁(yè)面框架過(guò)濾任務(wù),這樣就不會(huì)有其他工作線程進(jìn)行無(wú)用的過(guò)濾。當(dāng)PG連接或HTTP連接關(guān)閉或查詢(xún)執(zhí)行超時(shí)時(shí),應(yīng)進(jìn)行類(lèi)似的取消。因此,正如您在上面的比較中所看到的,我們確保處理所有這些邊緣情況。如果您對(duì)實(shí)現(xiàn)細(xì)節(jié)感興趣,請(qǐng)檢查這個(gè)冗長(zhǎng)的pull請(qǐng)求。
從最終用戶(hù)的角度來(lái)看,此優(yōu)化始終處于啟用狀態(tài),并應(yīng)用于非JIT和JIT編譯的過(guò)濾器。但是它如何提高QuestDB的性能呢?讓我們看看吧!
三、加速測(cè)量
我們將使用與上述相同的基準(zhǔn)測(cè)試環(huán)境,同時(shí)使用略有不同的查詢(xún)來(lái)保持簡(jiǎn)單:
Query 3
SELECT count(*)
FROM readings
WHERE velocity > 75.0 AND fuel_consumption < 10.0;
此查詢(xún)統(tǒng)計(jì)快速移動(dòng)但省油的卡車(chē)發(fā)送的測(cè)量總數(shù)。
首先,我們關(guān)注冷執(zhí)行時(shí)間,即列文件數(shù)據(jù)不在操作系統(tǒng)頁(yè)面緩存中的情況。多線程運(yùn)行使用QuestDB 6.3.1,而單線程運(yùn)行使用6.2.0版本的數(shù)據(jù)庫(kù)。這是因?yàn)镴IT編譯僅在從6.3開(kāi)始執(zhí)行并行過(guò)濾器時(shí)才可用。數(shù)據(jù)庫(kù)配置保持默認(rèn),但在相應(yīng)測(cè)量中禁用或啟用JIT除外。還請(qǐng)注意,雖然此給定查詢(xún)支持JIT編譯,但JIT編譯器支持的查詢(xún)類(lèi)型有許多限制。
下表顯示了冷執(zhí)行時(shí)間。
QuestDB 6.3-query 3中冷查詢(xún)執(zhí)行時(shí)間的改進(jìn)
那是什么?并行過(guò)濾器的執(zhí)行速度只有原來(lái)的兩倍。此外,啟用JIT編譯的過(guò)濾器對(duì)最終結(jié)果幾乎沒(méi)有影響。問(wèn)題是磁盤(pán)是這里的瓶頸。
讓我們?cè)囍鴱倪@些結(jié)果中了解一些道理。當(dāng)數(shù)據(jù)僅在磁盤(pán)上時(shí),QuestDB 6.3執(zhí)行查詢(xún)大約需要30.7秒。查詢(xún)引擎必須掃描兩組列文件,182個(gè)分區(qū),每個(gè)分區(qū)有兩個(gè)50 MB的文件。這為我們提供了大約18.2 GB的磁盤(pán)數(shù)據(jù)和大約592 MB/s的磁盤(pán)讀取速率。這低于EBS卷中配置的最大值,但我們應(yīng)該記住,最大吞吐量允許有10%的波動(dòng),更重要的是,EBS優(yōu)化實(shí)例的個(gè)別限制。我們的實(shí)例類(lèi)型是c5a。12xlarge,而且根據(jù)AWS文檔,它在128千兆位I/O上的速度限制為594 MB/s,這非常接近我們的封底計(jì)算。
長(zhǎng)話(huà)短說(shuō),我們使用多線程查詢(xún)執(zhí)行來(lái)最大化磁盤(pán),而版本6.2中的單線程執(zhí)行時(shí)間保持不變??紤]到這一點(diǎn),進(jìn)一步改進(jìn)實(shí)例類(lèi)型和卷將帶來(lái)更好的性能。
當(dāng)涉及到熱執(zhí)行場(chǎng)景時(shí),事情應(yīng)該會(huì)變得更加令人興奮,所以我們開(kāi)始吧。在接下來(lái)的以及所有后續(xù)的基準(zhǔn)測(cè)試運(yùn)行中,我們測(cè)量同一查詢(xún)的平均熱執(zhí)行時(shí)間。
QuestDB 6.3-query 3中的熱查詢(xún)執(zhí)行時(shí)間改進(jìn)
在這個(gè)特定的框中,默認(rèn)QuestDB配置導(dǎo)致共享工作線程池使用16個(gè)線程。因此,與6.2運(yùn)行相比,這兩個(gè)6.3運(yùn)行都在多個(gè)線程上執(zhí)行過(guò)濾器,從而加快了查詢(xún)速度。另一個(gè)觀察結(jié)果是6.3上JIT編譯過(guò)濾器和非JIT過(guò)濾器之間幾乎有1倍的差異。因此,即使有許多內(nèi)核可用于并行查詢(xún)執(zhí)行,保持JIT編譯處于啟用狀態(tài)也是一個(gè)好主意。
你可能已經(jīng)注意到上圖中有一個(gè)奇怪的比例。即禁用JIT編譯時(shí)的執(zhí)行時(shí)間差。QuestDB 6.2用一個(gè)線程完成查詢(xún)需要30秒,而在6.3上只需要大約1.3秒。這是23倍的改進(jìn),僅用并行處理無(wú)法解釋這一點(diǎn)(記住,我們?cè)?6個(gè)線程上運(yùn)行過(guò)濾器)。那么,原因可能是什么呢?
問(wèn)題是并行過(guò)濾器執(zhí)行與JIT編譯的過(guò)濾器函數(shù)使用相同的基于批的模型。這意味著過(guò)濾器在一個(gè)緊湊、CPU友好的循環(huán)中執(zhí)行,而匹配行的結(jié)果標(biāo)識(shí)符存儲(chǔ)在一個(gè)中間數(shù)組中。例如,如果我們限制并行過(guò)濾器引擎在單個(gè)線程上運(yùn)行,這就像添加共享一樣簡(jiǎn)單。工人count=1數(shù)據(jù)庫(kù)設(shè)置,則測(cè)試中的查詢(xún)將在大約13.5秒內(nèi)執(zhí)行。因此,在這個(gè)場(chǎng)景中,在單個(gè)線程上完成的基于批處理的過(guò)濾器處理允許我們減少55%的查詢(xún)執(zhí)行時(shí)間。顯然,引擎可以使用多個(gè)線程,使其運(yùn)行得更快。有關(guān)如何在SQL JIT編譯器中執(zhí)行基于批處理的過(guò)濾器處理的更多信息,請(qǐng)參閱本文。
我們?cè)谶@里使用的查詢(xún)還有一個(gè)優(yōu)化機(jī)會(huì)。也就是說(shuō),如果查詢(xún)只選擇簡(jiǎn)單的聚合函數(shù),如count(*)或max(*),而沒(méi)有列值,我們可以將函數(shù)下推到過(guò)濾器循環(huán)中。例如,過(guò)濾器循環(huán)將增加count(*)函數(shù)的計(jì)數(shù)器,而不是對(duì)過(guò)濾后的行標(biāo)識(shí)符進(jìn)行更通用的累加。您可以說(shuō)這樣的查詢(xún)非常適合,但它們可以在各種儀表板應(yīng)用程序中得到滿(mǎn)足。因此,這是我們將來(lái)肯定會(huì)考慮添加的內(nèi)容。
四、下一步是什么?
當(dāng)然,6.3中引入的并行SQL過(guò)濾器執(zhí)行并不是我們追求的最終目標(biāo)。正如我們已經(jīng)提到的,我們?yōu)榫酆喜樵?xún)(如SAMPLE BY或GROUP BY)準(zhǔn)備了多線程,但只針對(duì)特定形狀的查詢(xún)。聚合函數(shù)下推是另一種潛在的優(yōu)化。因此,請(qǐng)繼續(xù)關(guān)注進(jìn)一步的改進(jìn)!
一如既往,我們鼓勵(lì)用戶(hù)在QuestDB實(shí)例上試用6.3.1版本,并在Slack社區(qū)中提供反饋。您還可以玩我們的實(shí)時(shí)演示,看看它執(zhí)行查詢(xún)的速度有多快。當(dāng)然,我們非常歡迎對(duì)GitHub項(xiàng)目的開(kāi)源貢獻(xiàn)。
(免責(zé)聲明:本網(wǎng)站內(nèi)容主要來(lái)自原創(chuàng)、合作伙伴供稿和第三方自媒體作者投稿,凡在本網(wǎng)站出現(xiàn)的信息,均僅供參考。本網(wǎng)站將盡力確保所提供信息的準(zhǔn)確性及可靠性,但不保證有關(guān)資料的準(zhǔn)確性及可靠性,讀者在使用前請(qǐng)進(jìn)一步核實(shí),并對(duì)任何自主決定的行為負(fù)責(zé)。本網(wǎng)站對(duì)有關(guān)資料所引致的錯(cuò)誤、不確或遺漏,概不負(fù)任何法律責(zé)任。
任何單位或個(gè)人認(rèn)為本網(wǎng)站中的網(wǎng)頁(yè)或鏈接內(nèi)容可能涉嫌侵犯其知識(shí)產(chǎn)權(quán)或存在不實(shí)內(nèi)容時(shí),應(yīng)及時(shí)向本網(wǎng)站提出書(shū)面權(quán)利通知或不實(shí)情況說(shuō)明,并提供身份證明、權(quán)屬證明及詳細(xì)侵權(quán)或不實(shí)情況證明。本網(wǎng)站在收到上述法律文件后,將會(huì)依法盡快聯(lián)系相關(guān)文章源頭核實(shí),溝通刪除相關(guān)內(nèi)容或斷開(kāi)相關(guān)鏈接。 )