Databend query result cache 設計與實現(xiàn)
Databend 在 1.0 中支持了對查詢結果集的緩存,大大提高了多次相同查詢返回結果的效率。
Query result cache 主要用于處理數(shù)據(jù)更新頻率不高的查詢,它通過緩存第一次查詢返回的結果集,以便在之后對相同數(shù)據(jù)執(zhí)行相同查詢時能夠立即返回結果,從而提高查詢效率。
比如我們有個需求是每隔十秒獲取一次銷量前 5 的產品,通過以下 sql 執(zhí)行查詢:
在沒有 cache 的情況下,每次都需要執(zhí)行完整的 sql 查詢流程,而整個流程可能耗時比較久,但結果僅僅返回5條數(shù)據(jù)。如果 sales_count 表中的數(shù)據(jù)更新頻率不高,那么通過 cache 可以立即返回之后查詢的結果,大大降低了等待時間和 Server 的負載。
整體設計
Query Result Cache 的生命周期
每個被緩存的結果集都會設置一個緩存失效時間(TTL),每次對相同緩存結果集的訪問都會刷新失效時間,緩存的默認失效時間為 300 秒,可以通過設置 query_result_cache_ttl_secs
來修改。當失效時間到達后,緩存的結果集將不再可用。
除了 TTL 之外,如果底層數(shù)據(jù)(如 snapshot id、segment id、partition location)發(fā)生變化,緩存就會變得不準確。但是,這種底層數(shù)據(jù)的修改不會影響緩存的效果。如果仍然希望快速返回結果集,可以通過設置 SET query_result_cache_allow_inconsistent=1
來允許返回不一致的結果。如果您對 Databend 底層存儲結構感興趣,可以參考 Databend 存儲概覽[1]。
緩存結果存儲
Databend 使用鍵值對來存儲查詢結果集,對于每一次查詢, Databend 根據(jù) query 信息構造一個對應的 key,然后將查詢結果集的一些元信息構造成 value 存入到 meta service 中。
其中 Key 的生成規(guī)則為:
Value 的結構如下(注意:value 中只存儲對應結果集的元信息,真正的結果集會寫到當前使用的 storage 中,比如 local fs, s3...):
讀取 cache
讀 cache 流程比較簡單,通過以下偽代碼說明:
寫入 cache
寫 cache 的主要流程如上圖所示,當一個查詢執(zhí)行沒有命中 cache 時,就會觸發(fā)寫 cache 流程。
Databend 使用 pipeline 方式調度和處理讀寫任務,通常的 pipeline 流程是 source -> transform -> transform .. -> sink
, 寫 cache 會增加一個 sink 出口,因此需要首先并行的加一條管道來復制上游數(shù)據(jù) (圖中 duplicate 部分)
而由于 pipeline 中前置節(jié)點的 output port
和后置節(jié)點的 input port
是一一對應的,所以這里我們通過 shuffle 來重排序,以此來銜接前后處理節(jié)點。
注意事項
如果 query 中使用了不確定性的函數(shù),比如 now()
, rand()
, uuid()
,那么結果集將不會被 cache,另外 system 下的表也不會被 cache。
另外目前結果集最大緩存 1MiB 的數(shù)據(jù),可以通過設置 query_result_cache_max_bytes
來調整允許 cache 的大小。
使用方式
相關設置
測試 cache 是否生效
可以看到,相同的查詢,第二次的結果是立即返回的。
RESULT_SCAN
Query result cache 同時提供了 RESULT_SCAN
的 table function,在同一個 session 中,可以快速根據(jù) query_id 來拿到之前查詢的結果,使用方式可以參考文檔[2]。
另外用戶可以通過 SELECT * from system.query_cache
來獲取當前租戶下被 cache 所有結果集的元信息,包括
sql結果集對應的原始 sqlquery_id結果集對應的 query idresult_size緩存結果集的大小num_rows緩存結果集的行數(shù)partitions_sha查詢對應 partitions 的 hash 值location緩存結果集在存儲中的地址active_result_scan為 true 表示可以被 result_scan 使用
未來規(guī)劃
緩存數(shù)據(jù)清理:當前緩存的結果集在 TTL 到期后不可用,但是底層數(shù)據(jù)并未被清理,未來可以有個定時任務去清理過期數(shù)據(jù)
對緩存結果進行壓縮,進一步節(jié)省空間
對復合 SQL 進行結果集緩存,比如:(
INSERT INTO xxx SELECT ...
,COPY FROM SELECT
)
對以上改進感興趣的同學歡迎為 Databend 添磚加瓦。
致謝
Databend 結果集緩存的設計參考了 ClickHouse 和 Snowflake,如果想進一步跟進 query result cache 的細節(jié),請參考以下鏈接:
Databend Query Result Cache RFC[3]
Query Cache in ClickHouse[4]
ClickHouse query cache blog[5]
Snowflake RESULT_SCAN function[6]
Tuning the Result Cache in Oracle[7]
引用鏈接
[1]
?Databend 存儲覽:?https://www.cnblogs.com/databend/p/16814420.html
[2]
?參考文檔:?https://databend.rs/doc/sql-functions/table-functions/result_scan[3]
?Databend Query Result Cache RFC:?https://databend.rs/doc/contributing/rfcs/query-result-cache[4]
?Query Cache in ClickHouse:?https://clickhouse.com/docs/en/operations/query-cache/[5]
?ClickHouse query cache blog:?https://clickhouse.com/blog/introduction-to-the-clickhouse-query-cache-and-design[6]
?Snowflake RESULT_SCAN function:?https://docs.snowflake.com/en/sql-reference/functions/result_scan[7]
?Tuning the Result Cache in Oracle:?https://docs.oracle.com/en/database/oracle/oracle-database/19/tgdba/tuning-result-cache.html