在數據分析的實際場景中,冷熱數據往往面臨著不同的查詢頻次及響應速度要求。例如在電商訂單場景中,用戶經常訪問近 6 個月的訂單,時間較久遠的訂單訪問次數非常少;在行為分析場景中,需支持近期流量數據的高頻查詢且時效性要求高,但為了保證歷史數據隨時可查,往往要求數據保存周期更為久遠;在日志分析場景中,歷史數據的訪問頻次很低,但需長時間備份以保證后續的審計和回溯的工作...往往歷史數據的應用價值會隨著時間推移而降低,且需要應對的查詢需求也會隨之銳減。而隨著歷史數據的不斷增多,如果我們將所有數據存儲在本地,將造成大量的資源浪費。
為了解決滿足以上問題,冷熱數據分層技術應運而生,以更好滿足企業降本增效的趨勢。顧名思義,冷熱分層是將冷熱數據分別存儲在成本不同的存儲介質上,例如熱數據存儲在成本更高的 SSD 盤上、以提高時效數據的查詢速度和響應能力,而冷數據則存儲在相對低成本的 HDD 盤甚至更為廉價的對象存儲上,以降低存儲成本。我們還可以根據實際業務需求進行靈活的配置和調整,以滿足不同場景的要求。 冷熱分層一般適用于以下需求場景:
數據存儲周期長:面對歷史數據的不斷增加,存儲成本也隨之增加;
冷熱數據訪問頻率及性能要求不同:熱數據訪問頻率高且需要快速響應,而冷數據訪問頻率低且響應速度要求不高;
數據備份和恢復成本高:備份和恢復大量數據需要消耗大量的時間和資源。
.....
更高效率的冷熱分層技術
自 Apache Doris 0.12 版本引入動態分區功能,開始支持對表分區進行生命周期管理,可以設置熱數據轉冷時間以及存儲介質標識,通過后臺任務將熱數據從 SSD 自動冷卻到 HDD,以幫助用戶較大程度地降低存儲成本。用戶可以在建表屬性中配置參數storage_cooldown_time或者dynamic_partition.hot_partition_num來控制數據從 SSD 冷卻到 HDD,當分區滿足冷卻條件時,Doris 會自動執行任務。而 HDD 上的數據是以多副本的方式存儲的,并沒有做到最大程度的成本節約,因此對于冷數據存儲成本仍然有較大的優化空間。 為了幫助用戶進一步降低存儲成本,社區在已有功能上進行了優化,并在 Apache Doris 2.0 版本中推出了冷熱數據分層的功能。冷熱數據分層功能使 Apache Doris 可以將冷數據下沉到存儲成本更加低廉的對象存儲中,同時冷數據在對象存儲上的保存方式也從多副本變為單副本,存儲成本進一步降至原先的三分之一,同時也減少了因存儲附加的計算資源成本和網絡開銷成本。 如下圖所示,在 Apache Doris 2.0 版本中支持三級存儲,分別是 SSD、HDD 和對象存儲。用戶可以配置使數據從 SSD 下沉到 HDD,并使用冷熱分層功能將數據從 SSD 或者 HDD 下沉到對象存儲中。
以公有云價格為例,云磁盤的價格通常是對象存儲的 5-10 倍,如果可以將 80% 的冷數據保存到對象存儲中,存儲成本至少可降低 70%。 我們使用以下公式計算節約的成本,設冷數據比率為 rate,對象存儲價格為 OSS,云磁盤價格為 CloudDisk
這里我們假設用戶有 100TB 的數據,我們按照不同比例將冷數據遷移到對象存儲,來計算一下如果使用冷熱分層之后,相較于全量使用普通云盤、SSD 云盤可節約多少成本。
阿里云 OSS 標準存儲成本是 120 元/ T /月
阿里云普通云盤的價格是 300 元/ T /月
阿里云 SSD 云盤的價格是 1000 元/ T /月
例如在 80% 冷數據占比的情況下,剩余 20% 使用普通云盤每月僅花費 80T*120 + 20T * 300 = 15600元,而全量使用普通云盤則需要花費 30000 元,通過冷熱數據分層節省了48%的存儲成本。如果用戶使用的是 SSD 云盤,那么花費則會從全量使用需花費的 100000 元降低到 80T*120 + 20T * 1000 = 29600元,存儲成本最高降低超過 70%!
冷熱分層使用指南
若要使用 Doris 的冷熱分層功能,首先需要準備一個對象存儲的 Bucket 并獲取對應的 AK/SK。當準備就緒之后,下面為具體的使用步驟:
1. 創建 Resource
可以使用對象存儲的 Bucket 以及 AK/SK 創建 Resource,目前支持 AWS、Azure、阿里云、華為云、騰訊云、百度云等多個云的對象存儲。
CREATE?RESOURCE?IF?NOT?EXISTS?"${resource_name}" ???
?????PROPERTIES( ???????
?????"type"="s3", ????????
????"s3.endpoint"?=?"${S3Endpoint}", ??????
??????"s3.region"?=?"${S3Region}", ??????????
??"s3.root.path"?=?"path/to/root", ???????
?????"s3.access_key"?=?"${S3AK}", ??????
??????"s3.secret_key"?=?"${S3SK}", ???????
?????"s3.connection.maximum"?=?"50", ????????
????"s3.connection.request.timeout"?=?"3000", ??????
??????"s3.connection.timeout"?=?"1000", ????????
????"s3.bucket"?=?"${S3BucketName}" ?????
???);
CREATE?STORAGE?POLICY?testPolicy PROPERTIES( ??"storage_resource"?=?"remote_s3", ??"cooldown_ttl"?=?"1d" ); 例如上方代碼中名為testPolicy的storage policy設置了新導入的數據將在一天后開始冷卻,并且冷卻后的冷數據會存放到remote_s3所表示的對象存儲的?root path下。除了設置 TTL 以外,在 Policy 中也支持設置冷卻的時間點,可以直接設置為:
CREATE?STORAGE?POLICY?testPolicyForTTlDatatime PROPERTIES( ??"storage_resource"?=?"remote_s3", ??"cooldown_datetime"?=?"2023-06-07?2100" );3. 給表或者分區設置 Storage Policy 在創建出對應的 Resource 和 Storage Policy 之后,我們可以在建表的時候對整張表設置 Cooldown Policy,也可以針對某個 Partition 設置 Cooldown Policy。這里以 TPCH 測試數據集中的 Lineitem 表舉例。如果需要將整張表都設置冷卻的策略,則可以直接在整張表的 Properties 中設置:
CREATE?TABLE?IF?NOT?EXISTS?lineitem1?( ????????????L_ORDERKEY????INTEGER?NOT?NULL, ????????????L_PARTKEY?????INTEGER?NOT?NULL, ????????????L_SUPPKEY?????INTEGER?NOT?NULL, ????????????L_LINENUMBER??INTEGER?NOT?NULL, ????????????L_QUANTITY????DECIMAL(15,2)?NOT?NULL, ????????????L_EXTENDEDPRICE??DECIMAL(15,2)?NOT?NULL, ????????????L_DISCOUNT????DECIMAL(15,2)?NOT?NULL, ????????????L_TAX?????????DECIMAL(15,2)?NOT?NULL, ????????????L_RETURNFLAG??CHAR(1)?NOT?NULL, ????????????L_LINESTATUS??CHAR(1)?NOT?NULL, ????????????L_SHIPDATE????DATEV2?NOT?NULL, ????????????L_COMMITDATE??DATEV2?NOT?NULL, ????????????L_RECEIPTDATE?DATEV2?NOT?NULL, ????????????L_SHIPINSTRUCT?CHAR(25)?NOT?NULL, ????????????L_SHIPMODE?????CHAR(10)?NOT?NULL, ????????????L_COMMENT??????VARCHAR(44)?NOT?NULL ????????????) ????????????DUPLICATE?KEY(L_ORDERKEY,?L_PARTKEY,?L_SUPPKEY,?L_LINENUMBER) ????????????PARTITION?BY?RANGE(`L_SHIPDATE`) ????????????( ????????????????PARTITION?`p202301`?VALUES?LESS?THAN?("2017-02-01"), ????????????????PARTITION?`p202302`?VALUES?LESS?THAN?("2017-03-01") ????????????) ????????????DISTRIBUTED?BY?HASH(L_ORDERKEY)?BUCKETS?3 ????????????PROPERTIES?( ????????????"replication_num"?=?"3", ????????????"storage_policy"?=?"${policy_name}" ????????????)用戶可以通過show tablets獲得每個 Tablet 的信息,其中 CooldownReplicaId 不為 -1 并且 CooldownMetaId 不為空的 Tablet 說明使用了 Storage Policy。如下方代碼,通過show tablets可以看到上面的 Table 的所有 Tablet 都設置了 ?CooldownReplicaId 和 CooldownMetaId,這說明整張表都是使用了 Storage Policy。
???????????????TabletId:?3674797 ??????????????ReplicaId:?3674799 ??????????????BackendId:?10162 ?????????????SchemaHash:?513232100 ????????????????Version:?1 ??????LstSuccessVersion:?1 ???????LstFailedVersion:?-1 ??????????LstFailedTime:?NULL ??????????LocalDataSize:?0 ?????????RemoteDataSize:?0 ???????????????RowCount:?0 ??????????????????State:?NORMAL LstConsistencyCheckTime:?NULL ???????????CheckVersion:?-1 ???????????VersionCount:?1 ??????????????QueryHits:?0 ???????????????PathHash:?8030511811695924097 ????????????????MetaUrl:?http://172.16.0.16:6781/api/meta/header/3674797 ???????CompactionStatus:?http://172.16.0.16:6781/api/compaction/show?tablet_id=3674797 ??????CooldownReplicaId:?3674799 ?????????CooldownMetaId:?TUniqueId(hi:-8987737979209762207,?lo:-2847426088899160152)我們也可以對某個具體的 Partition 設置 Storage Policy,只需要在 Partition 的 Properties 中加上具體的 Policy Name 即可:
CREATE?TABLE?IF?NOT?EXISTS?lineitem1?( ????????????L_ORDERKEY????INTEGER?NOT?NULL, ????????????L_PARTKEY?????INTEGER?NOT?NULL, ????????????L_SUPPKEY?????INTEGER?NOT?NULL, ????????????L_LINENUMBER??INTEGER?NOT?NULL, ????????????L_QUANTITY????DECIMAL(15,2)?NOT?NULL, ????????????L_EXTENDEDPRICE??DECIMAL(15,2)?NOT?NULL, ????????????L_DISCOUNT????DECIMAL(15,2)?NOT?NULL, ????????????L_TAX?????????DECIMAL(15,2)?NOT?NULL, ????????????L_RETURNFLAG??CHAR(1)?NOT?NULL, ????????????L_LINESTATUS??CHAR(1)?NOT?NULL, ????????????L_SHIPDATE????DATEV2?NOT?NULL, ????????????L_COMMITDATE??DATEV2?NOT?NULL, ????????????L_RECEIPTDATE?DATEV2?NOT?NULL, ????????????L_SHIPINSTRUCT?CHAR(25)?NOT?NULL, ????????????L_SHIPMODE?????CHAR(10)?NOT?NULL, ????????????L_COMMENT??????VARCHAR(44)?NOT?NULL ????????????) ????????????DUPLICATE?KEY(L_ORDERKEY,?L_PARTKEY,?L_SUPPKEY,?L_LINENUMBER) ????????????PARTITION?BY?RANGE(`L_SHIPDATE`) ????????????( ????????????????PARTITION?`p202301`?VALUES?LESS?THAN?("2017-02-01")?("storage_policy"?=?"${policy_name}"), ????????????????PARTITION?`p202302`?VALUES?LESS?THAN?("2017-03-01") ????????????) ????????????DISTRIBUTED?BY?HASH(L_ORDERKEY)?BUCKETS?3 ????????????PROPERTIES?( ????????????"replication_num"?=?"3" ????????????)這張 Lineitem1 設置了兩個分區,每個分區 3 個 Bucket,另外副本數設置為 3,可以計算出一共有 2*3 = 6 個 Tablet,那么副本數一共是 6*3 = 18 個 Replica,通過show tablets命令可以查看到所有的 Tablet 以及 Replica 的信息,可以看到只有部分 Tablet 的 Replica 是設置了CooldownReplicaId 和 CooldownMetaId 。 用戶可以通過ADMIN SHOW REPLICA STATUS FROM TABLE PARTITION(PARTITION)查看 Partition 下的 Tablet 以及Replica,通過對比可以發現其中只有屬于 p202301 這個 Partition 的 Tablet 的 Replica 設置了CooldownReplicaId 和 CooldownMetaId,而屬于 p202302 這個 Partition 下的數據沒有設置,所以依舊會全部存放到本地磁盤。以上表的 Tablet 3691990 為例,該 Tablet 屬于 p202301,截取 show tablets 拿到的部分關鍵信息如下:
***************************************************************** ???????????????TabletId:?3691990 ??????????????ReplicaId:?3691991 ??????CooldownReplicaId:?3691993 ?????????CooldownMetaId:?TUniqueId(hi:-7401335798601697108,?lo:3253711199097733258) ***************************************************************** ???????????????TabletId:?3691990 ??????????????ReplicaId:?3691992 ??????CooldownReplicaId:?3691993 ?????????CooldownMetaId:?TUniqueId(hi:-7401335798601697108,?lo:3253711199097733258) ***************************************************************** ???????????????TabletId:?3691990 ??????????????ReplicaId:?3691993 ??????CooldownReplicaId:?3691993 ?????????CooldownMetaId:?TUniqueId(hi:-7401335798601697108,?lo:3253711199097733258)? 可以觀察到 3691990 的 3 個副本都選擇了 3691993 副本作為 CooldownReplica,在用戶指定的 Resource 上也只會保存這個副本的數據。
4. 查看數據信息
我們可以按照上述 3 中的 Linetem1 來演示如何查看是使用冷熱數據分層策略的 Table 的數據信息,一般可以通過show tablets from lineitem1?直接查看這張表的 Tablet 信息。Tablet 信息中區分了 LocalDataSize 和 RemoteDataSize,前者表示存儲在本地的數據,后者表示已經冷卻并移動到對象存儲上的數據。具體信息可見下方代碼: 下方為數據剛導入到 BE 時的數據信息,可以看到數據還全部存儲在本地。
***************************?1.?row?***************************
???????????????TabletId:?2749703 ??????????????ReplicaId:?2749704 ??????????????BackendId:?10090 ?????????????SchemaHash:?1159194262 ????????????????Version:?3 ??????LstSuccessVersion:?3 ???????LstFailedVersion:?-1 ??????????LstFailedTime:?NULL ??????????LocalDataSize:?73001235 ?????????RemoteDataSize:?0 ???????????????RowCount:?1996567 ??????????????????State:?NORMAL LstConsistencyCheckTime:?NULL ???????????CheckVersion:?-1 ???????????VersionCount:?3 ??????????????QueryHits:?0 ???????????????PathHash:?-8567514893400420464 ????????????????MetaUrl:?http://172.16.0.8:6781/api/meta/header/2749703 ???????CompactionStatus:?http://172.16.0.8:6781/api/compaction/show?tablet_id=2749703 ??????CooldownReplicaId:?2749704 ?????????CooldownMetaId:? 當數據到達冷卻時間后,再次進行?show tablets from table可以看到對應的數據變化。
***************************?1.?row?*************************** ???????????????TabletId:?2749703 ??????????????ReplicaId:?2749704 ??????????????BackendId:?10090 ?????????????SchemaHash:?1159194262 ????????????????Version:?3 ??????LstSuccessVersion:?3 ???????LstFailedVersion:?-1 ??????????LstFailedTime:?NULL ??????????LocalDataSize:?0 ?????????RemoteDataSize:?73001235 ???????????????RowCount:?1996567 ??????????????????State:?NORMAL LstConsistencyCheckTime:?NULL ???????????CheckVersion:?-1 ???????????VersionCount:?3 ??????????????QueryHits:?0 ???????????????PathHash:?-8567514893400420464 ????????????????MetaUrl:?http://172.16.0.8:6781/api/meta/header/2749703 ???????CompactionStatus:?http://172.16.0.8:6781/api/compaction/show?tablet_id=2749703 ??????CooldownReplicaId:?2749704 ?????????CooldownMetaId:?TUniqueId(hi:-8697097432131255833,?lo:9213158865768502666)? 除了通過上述命令查看數據信息之外,我們也可以在對象存儲上查看冷數據的信息。以騰訊云為例,可以在 Policy 指定的 Bucket 的 Path 下可以查看冷卻過后的數據的信息:
?
?
進入對應文件后可以看到數據和元數據文件
我們可以看到在對象存儲上數據是單副本
5. 查詢
假設 Table Lineitem1 中的所有數據都已經冷卻并且上傳到對象存儲中,如果用戶在 Lineitem1 上進行對應的查詢,Doris 會根據對應 Partition 使用的 Policy 信息找到對應的 Bucket 的 Root Path,并根據不同 Tablet 下的 Rowset 信息下載查詢所需的數據到本地進行運算。 Doris 2.0 在查詢上進行了優化,冷數據第一次查詢會進行完整的 S3 網絡 IO,并將 Remote Rowset 的數據下載到本地后,存放到對應的 Cache 之中,后續的查詢將自動命中 Cache,以此來保證查詢效率。(性能對比可見后文評測部分)。
6. 冷卻后繼續導入數據
在某些場景下,用戶需要對歷史數據進行數據的修正或補充數據,而新數據會按照分區列信息導入到對應的 Partition中。在 Doris 中,每次數據導入都會產生一個新的 Rowset,以保證冷數據的 Rowset 在不會影響新導入數據的 Rowset 的前提下,滿足冷熱數據同時存儲的需求。Doris 2.0 的冷熱分層粒度是基于 Rowset 的,當到達冷卻時間時會將當前滿足條件的 Rowset 全部上傳到 S3 上并刪除本地數據,之后新導入的數據生成的新 Rowset 會在到達冷卻時間后也上傳到 S3。
查詢性能測試
為了測試使用冷熱分層功能之后,查詢對象存儲中的數據是否占用會較大網絡 I/O,從而影響查詢性能,因此我們以 SSB SF100 標準集為例,對冷熱分層表和非冷熱分層表進行了查詢耗時的對比測試。 配置:均在 3 臺 16C 64G 的機器上部署 1FE、3BE 的集群
如上圖所示,在充分預熱之后(數據已經緩存在 Cache 中),冷熱分層表共耗時 5.799s,非冷熱分層表共耗時 5.822s,由此可知,使用冷熱分層查詢表和非冷熱分層表的查詢性能幾乎相同。這表明,使用 Doris 2.0 提供的冷熱分層功能,不會對查詢性能造成的影響。
冷熱分層技術的具體實現
存儲方式的優化
在 Doris 之前的版本中,數據從 SSD 冷卻到 HDD 后,為了保證數據的高可用和可靠性,通常會將一個 Tablet 存儲多份副本在不同 BE 上,為了進一步降低成本,我們在 Apache Doris 2.0 版本引入了對象存儲,推出了冷熱分層功能。由于對象存儲本身具有高可靠高可用性,冷數據在對象存儲上只需要一份即可,元數據以及熱數據仍然保存在 BE,我們稱之為本地副本,本地副本同步冷數據的元數據,這樣就可以實現多個本地副本共用一份冷卻數據的目的,有效避免冷數據占用過多的存儲空間,從而降低數據存儲成本。 具體而言,Doris 的 FE 會從 Tablet 的所有可用本地副本中選擇一個本地副本作為上傳數據的 Leader,并通過 Doris 的周期匯報機制同步 Leader 的信息給其它本地副本。在 Leader 上傳冷卻數據時,也會將冷卻數據的元數據上傳到對象存儲,以便其他副本同步元數據。因此,任何本地副本都可以提供查詢所需的數據,同時也保證了數據的高可用性和可靠性。
冷數據 Compaction
在一些場景下會有大量修補數據的需求,在大量補數據的場景下往往需要刪除歷史數據,刪除可以通過delete where實現,Doris 在 Compaction 時會對符合刪除條件的數據做物理刪除。基于這些場景,冷熱分層也必須實現對冷數據進行 Compaction,因此在 Doris 2.0 版本中我們支持了對冷卻到對象存儲的冷數據進行 Compaction(ColdDataCompaction)的能力,用戶可以通過冷數據 Compaction,將分散的冷數據重新組織并壓縮成更緊湊的格式,從而減少存儲空間的占用,提高存儲效率。
Doris 對于本地副本是各自進行 Compaction,在后續版本中會優化為單副本進行 Compaction。由于冷數據只有一份,因此天然的單副本做 Compaction 是最優秀方案,同時也會簡化處理數據沖突的操作。BE 后臺線程會定期從冷卻的 Tablet 按照一定規則選出 N 個 Tablet 發起 ColdDataCompaction。與數據冷卻流程類似,只有 CooldownReplica 能執行該 Tablet 的 ColdDataCompaction。Compaction下刷數據時每積累一定大小(默認5MB)的數據,就會上傳一個 Part 到對象,而不會占用大量本地存儲空間。Compaction 完成后,CooldownReplica 將冷卻數據的元數據更新到對象存儲,其他 Replica 只需從對象存儲同步元數據,從而大量減少對象存儲的 IO 和節點自身的 CPU 開銷。
冷數據 Cache
冷數據 Cache 在數據查詢中具有重要的作用。冷數據通常是數據量較大、使用頻率較低的數據,如果每次查詢都需要從對象中讀取,會導致查詢效率低下。通過冷數據 Cache 技術,可以將冷數據緩存在本地磁盤中,提高數據讀取速度,從而提高查詢效率。而 Cache 的粒度大小直接影響 Cache 的效率,比較大的粒度會導致 Cache 空間以及帶寬的浪費,過小粒度的 Cache 會導致對象存儲 IO 效率低下,Apache Doris 采用了以 Block 為粒度的 Cache 實現。 如前文所述,Apache Doris 的冷熱分層會將冷數據上傳到對象存儲上,上傳成功后本地的數據將會被刪除。因此,后續涉及到冷數據的查詢均需要對對象存儲發起 IO 。
為了優化性能,Apache Doris 實現了基于了 Block 粒度的 Cache 功能,當遠程數據被訪問時會先將數據按照 Block 的粒度下載到本地的 Block Cache 中存儲,且 Block Cache 中的數據訪問性能和非冷熱分層表的數據性能一致(可見前文查詢性能測試)。 具體來講,前文提到 Doris 的冷熱分層是在 Rowset 級別進行的,當某個 Rowset 在冷卻后其所有的數據都會上傳到對象存儲上。而 Doris 在進行查詢的時候會讀取涉及到的 Tablet 的 Rowset 進行數據聚合和運算,當讀取到冷卻的 Rowset 時,會把查詢需要的冷數據下載到本地 Block Cache 之中。基于性能考量,Doris 的 Cache 按照 Block 對數據進行劃分。Block Cache 本身采用的是簡單的 LRU 策略,可以保證越是使用程度較高數據越能在 Block Cache 中存放的久。
寫在最后
Apache Doris 2.0 版本實現了基于對象存儲的冷熱數據分層,該功能可以幫助我們有效降低存儲成本、提高存儲效率,并提高數據查詢和處理效率。未來,Apache Doris 將會基于冷熱數據分層以及彈性計算節點,為用戶提供更好的資源彈性、更低的使用成本以及更靈活的負載隔離服務。 在前段時間推出的 Apache Doris 2.0 Alpha 版本中,已經實現了單節點數萬 QPS 的高并發點查詢能力、高性能的倒排索引、高效穩定的內存管理、基于代價模型的全新查詢優化器以及 Pipeline 執行引擎等,與此同時, Apache Doris 2.0 Beta 版本也將于近兩周上線,除了已知功能外,還會進一步支持 Unique 模型上的部分列更新,并將 Pipeline 執行引擎、查詢優化器、主鍵模型 Merge-on-Write 等最新特性作為穩定功能默認開啟,并包含了社區近期對性能方面的諸多優化,詳細性能測試結果敬請期待后續社區動態。
作者介紹:
楊勇強,SelectDB?聯合創始人、技術副總裁 岳靖、程宇軒,SelectDB 存儲層研發工程師
編輯:黃飛
評論