目的
通過本篇文章帶大家理解一下 Databend 的存盤結構,Databend 內置的 Table 引擎為 Fuse table engine,也是接下來要花重點篇幅要講的,
另外,Databend 還支持外置的 Hive table 及 Icebreg Table ( 即將到來),Fuse table 是 Databend 直接把資料存盤到 S3 類物件存盤上,從而讓用戶達到一個按需付費,無須關注存盤的高可用及擴容,副本這些問題,
Hive Table 是利用 Databend 替換 Hive 的查詢能力,從而減少 Hive 計算節點,起到降本增效的效果(該功能已經使用),
Iceberg Table 正在規劃中 https://github.com/datafuselabs/databend/issues/8216
Fuse Table Engine 基礎概念
在 Fuse Table 中有一些基礎概念先做一個解釋方便更想 Databend Fuse Table 的存盤結構,
1. 什么是 db_id?
這是 Databend 中的一個 internal 的標識 (u64),不是暴露給用戶使用,Databend 對于 create database 會在對應的 bucket/[root] 下面創建一個整數命名的目錄,
2. 什么是 table_id?
這是 Databend 中的一個 internal 的標識 (u64),不是暴露給用戶使用,Databend 對于 create table 會在 /bucket/[root]/<db_id>/ 創建一個整數命名的目錄,
3. Databend 的存 block 檔案是什么?
Databend 最終存盤 block 是以 Parquet 為格式存盤,在存盤上以表為單位,檔案名為:[UUID].parquet, 存盤路徑為:
/bucket/[root]/<db_id>/<table_id>/_b/<32 位 16 進制字串 >_v0.parquet
如:d5ee665801a64a079a8fd2711a71c780_v0.parquet
4. Databend 中 segment 檔案是什么?
Databend 中用于組織 Block 的檔案,一個 segment 可以多含的 Block 塊,檔案是 json 格式: /bucket/[root]/<db_id>/<table_id>/_sg/<32 位 16 進制字串 >_v1.json ,
如:3b5e1325f68e47b0bd1517ffeb888a36_v1.json
5. Snapshot 是什么?
snapshot 相當于每一個資料的一個版本號(uuid, 32 位 16 進制字串),每個寫入動作都會有一個唯一的版本號, json 格式,內部包含對應的 segment 檔案, /bucket/[root]/<db_id>/<table_id>/_ss/<32 位 16 進制字串 >_v1.json,
如:e7ccbdcff8d54ebe9aee85d9fbb3dbcb_v1.json
6. Databend 支持什么索引?
Databend 目前支持三類索引:min/max index, sparse index, bloom filter index ,其中 min/max, sparse index 在 Block 的 parquet 及對應的:ss, segment 中都有存盤,bloom fliter 是單獨存盤為 parquet 檔案,
Databend 存盤結構 Databend 整體上的存盤結構大概如下:
/bucket/[root]/snapshot 下面有 N 多的 segment , 一個 segment 里包含至少一個 block, 最多 1000 個 block ,
存盤配置Databend
存盤配置
[storage]
# fs | s3 | azblob | obs
type = "s3"
# To use S3-compatible object storage, uncomment this block and set your values.
[storage.s3]
bucket = "testbucket"
root = "20221012"
endpoint_url = "url"
access_key_id = "=user"
secret_access_key = "mypassword"
上面這段配置的作用:以 s3 方式把檔案存到 testbucket 下面的 20221012 目錄, 最侄訓形成如下的結構:
其中配置中 root 可以省略,
例如:/testbucket/20221012/17818/17825 對應的是 /bucket/root/db_id/table_id 這樣一個結構,
table_id 里面每個目錄的意義
| 目錄 | 意義 |
|---|---|
| _b | 用于存盤資料的真正block, 以parquet 格式存盤 |
| _i_b_v2 | 資料本身的 bloom fliter 索引,以 parquet 格式存盤 |
| _sg | 全稱:segment 用于管理 block 組成,json 檔案格式, 一個 sg 檔案最少包含一個 block ,最多包含 1000 個 block |
| _ss | 全稱:snapshot, 用于關聯一個版本對應的 segment |
| last_snapshot_location_hint | 指向最后一個 snapshot 存盤的位置 |
驗證環境
驗證1 ss/sg/_b/_i_b_v2 關系
為了分析他們的關系,這里通過一個 create database/ create table / insert 例子來看看他們是怎么生成的,
create database wubx;
use wubx;
create table tb1(id int, c1 varchar);
insert into tb1 values(1, 'databend');
show create table tb1;
最后通過 show create table 可以看到:
CREATE TABLE `tb1` (
`id` INT,
`c1` VARCHAR
) ENGINE=FUSE SNAPSHOT_LOCATION='17818/17825/_ss/e7ccbdcff8d54ebe9aee85d9fbb3dbcb_v1.json'
這里可以看到:
- wubx 的 db_id 是:17818
- tb1 的 table_id 是:17825
- 對應的第一個 snapshot 檔案是:17818/17825/_ss/e7ccbdcff8d54ebe9aee85d9fbb3dbcb_v1.json
1.查詢對應的 snapshot
MySQL [wubx]> select snapshot_id, snapshot_location from fuse_snapshot('wubx','tb1')\G;
*************************** 1. row ***************************
snapshot_id: e7ccbdcff8d54ebe9aee85d9fbb3dbcb
snapshot_location: 17818/17825/_ss/e7ccbdcff8d54ebe9aee85d9fbb3dbcb_v1.json
1 row in set (0.005 sec)
2.接下來我們看一下,這個 snapshot 中包含那些 segment:
MySQL [wubx]> select * from fuse_segment('wubx','tb1', 'e7ccbdcff8d54ebe9aee85d9fbb3dbcb')\G;
*************************** 1. row ***************************
file_location: 17818/17825/_sg/3b5e1325f68e47b0bd1517ffeb888a36_v1.json
format_version: 1
block_count: 1
row_count: 1
bytes_uncompressed: 28
bytes_compressed: 296
1 row in set (0.006 sec)
從這個查詢中可以看到 snapshot: e7ccbdcff8d54ebe9aee85d9fbb3dbcb 只包含一個 segment: 17818/17825/_sg/3b5e1325f68e47b0bd1517ffeb888a36_v1.json, 而這個 segment 只有一個 1 block,這個 Block 只有 1 行資料,對應的 JSON 檔案:
{
"format_version": 1,
"blocks": [
{
...
"location": [
"17818/17825/_b/d5ee665801a64a079a8fd2711a71c780_v0.parquet",
0
],
"bloom_filter_index_location": [
"17818/17825/_i_b_v2/d5ee665801a64a079a8fd2711a71c780_v2.parquet",
2
],
"bloom_filter_index_size": 470,
"compression": "Lz4Raw"
}
],
"summary": {
...
}
}
原始檔案較長,有興趣的可以詳細閱讀一個原檔案,
3.對應的 block 查詢
MySQL [wubx]> select * from fuse_block('wubx','tb1')\G;
*************************** 1. row ***************************
snapshot_id: e7ccbdcff8d54ebe9aee85d9fbb3dbcb
timestamp: 2022-10-14 06:53:55.147359
block_location: 17818/17825/_b/d5ee665801a64a079a8fd2711a71c780_v0.parquet
block_size: 28
bloom_filter_location: 17818/17825/_i_b_v2/d5ee665801a64a079a8fd2711a71c780_v2.parquet
bloom_filter_size: 470
1 row in set (0.006 sec)
驗證1 總結:
- 任何一次寫入都會生成對應的 snapshot (用于 time travel)
- 生成的 block 會被 Segment 參考,一個寫入產生的 block 數量在小于 1000 個的情況下都會屬于一個 segment 中,如果超過 1000 個 block 會生成多個 segement (這個操作太大了,就不證明了)
- 如果上面情況,一次 insert 也會生成:一個 snapshot , 一個 segment ,一個 block,一個 bloom fliter block
基于上面的原理:
對于 Databend 寫入推薦使用批量寫入,不推薦單條的 insert 做生成中的資料生成,在 Databend 海量資料寫入推薦使用 copy into, streaming_load , clickhouse http handler 這三種方法, 其中前兩種吞吐能力最好,
驗證1 ss/sg/_b/_i_b_v2 關系
多次重復制執行:Insert into tb1 select * from tb1; 共執行 10 次,加上原來 1 次,總共會形成 11 個 snapshot:
接下來看 tb1 的 snapshot 指向:17818/17825/_ss/5a0ba62a222441d3acd2d93549e46d82_v1.json
show create table tb1;
CREATE TABLE `tb1` (
`id` INT,
`c1` VARCHAR
) ENGINE=FUSE SNAPSHOT_LOCATION='17818/17825/_ss/5a0ba62a222441d3acd2d93549e46d82_v1.json'
Q1:snapshot 主要用來做什么?
Databend 基于 snapshot 獲取相應版本的資料,Databend 也是基于 snapshot 實作事務的 RR 隔離級別,
例如:Select count() from tb1;相當于:select count() from tb1 at(snapshot=>'5a0ba62a222441d3acd2d93549e46d82');
這個 at 陳述句是 time travel 的一個特性,對于 time travel 可以參考:https://databend.rs/doc/reference/sql/query-syntax/dml-at#obtaining-snapshot-id-and-timestamp
Q2:snapshot 是否可以被清理?
可以的,
清理 snapshot 命令:optimize table tb1; 或是 optimize table tb1 purge;
MySQL [wubx]> optimize table tb1;
Query OK, 0 rows affected (0.013 sec)
MySQL [wubx]> select snapshot_id, snapshot_location from fuse_snapshot('wubx','tb1');
+----------------------------------+----------------------------------------------------------+
| snapshot_id | snapshot_location |
+----------------------------------+----------------------------------------------------------+
| 5a0ba62a222441d3acd2d93549e46d82 | 17818/17825/_ss/5a0ba62a222441d3acd2d93549e46d82_v1.json |
+----------------------------------+----------------------------------------------------------+
1 row in set (0.005 sec)
但清理后,time travel 功能需要針對后面的資料才能生效,前面的 time travel 資料已經丟掉,
Q3:是否可以創建一個不帶 time travel 的表?
可以的,
Databend 支持:CREATE TRANSIENT TABLE .. 創建的表
參考:https://databend.rs/doc/reference/sql/ddl/table/ddl-create-table#create-transient-table-
該方式創建的表存在一個缺點:在高并發寫入讀取中,容易造成正在讀取的 snapshot 被回收及報錯的問題,
存盤優化Tips
Q1:大量小的 block 檔案,是不是可以進行合并?
可以合并的,
目前需要用戶進行手工觸發,
optimize table tbname compact;
這個命令的作用:
- 把原有的 block 塊 max_threads 進行并發合并,生成一份最佳的 Block size 檔案串列
- 每個 thread 任務對應一個 segment 檔案,超過 1000 個 block 會生成多個 segment
- 最終生成一個 snapshot 檔案
經過 Compact 的最佳的 Block 塊,后續在運行 compact 動作會直接跳過,
Q2: 什么時間決定需要運行 tb 的 compact?
目前 Databend 對于 Block 判定要執行 compact 的條件:
- 單個 block 塊里行數少于 80 萬行且 block 小于 100M 會進行合并
- 單個 block 塊超過 100 萬行,block 會被拆分,
可以用一個簡單的條件來判斷
a. Block 數量大于 max_threads* 4 倍
select count(*) from fuse_block('db','tb');
b.表里 block 資料少于 100M 且行數低于 80 萬的數量超過 100 個
select if(count(*)>100,'你需要運行compact','你的block大小非常合理') from fuse_block('db','tb') where file_size <100*1024*1024 and row_count<800000;
Q3: 當出現大量的 segment 檔案,是不是需要對 segment 檔案合并?
是的,
對于 segment 合并也可以引入一條簡單的規則
select count(*),avg(block_count),if(avg(block_count)<500,'need compact segment','segment file is ok') from fuse_segment('db','tb','snapshot_id');
如果 segment 總數超過 1000 ,而且每個 segment 平均 block 數小于 500 需要運行:
optimize table tb compact segment;
對于頻繁寫入的場景建議定期運行一下 compact segment ,這樣來壓縮一下 ss 及對 segemnt 檔案的大小, 方便 meta 資訊進行快取,
Q4:進行合并操作后檔案占用空間比較大,如何釋放?
Databend 是一個多版本及支持 Time travel 特性的云數倉,隨著歷史增長,會出現挺多的版本資料,對于存在的歷史版本資料可以使用
optimize table table_name purge;
現在 purge 動作會把當前的 snapshot 之外的版本全部清理掉,造成 time travel 失效的問題,后續 purge 會支持傳入 snapshot 或是時間指定清理到什么位置,
Q5:如何進行 compact 和同時清理過舊的資料?
optimize table table_name all;
這個命令相當于:optimize table table_name compact; optimize table table_name purge;
Q6:如何真正洗掉一張表?
Databend 中 Drop table 為了支持 undrop table 不會所表直正洗掉,如果你需要立即 Drop 一張表建議使用:
drop table table_name all;
目前需要洗掉一個 Database 也面臨這樣的問題,需要先做表的洗掉,再刪 Database ,
關于 Databend
Databend 是一款開源、彈性、低成本,基于物件存盤也可以做實時分析的新式數倉,期待您的關注,一起探索云原生數倉解決方案,打造新一代開源 Data Cloud,
- Databend 檔案:https://databend.rs/
- Twitter:https://twitter.com/Datafuse_Labs
- Slack:https://datafusecloud.slack.com/
- Wechat:Databend
- GitHub :https://github.com/datafuselabs/databend
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/518902.html
標籤:大數據
