StarRocks 物化視圖刷新流程和原理
前段時間給 StarRocks 的物化視圖新增了一個特性,那也是我第一次接觸 StarRocks,因為完全不熟悉這個數(shù)據(jù)庫,所以很多東西都是從頭開始了解概念。
為了能順利的新增這個特性(具體內容可以見后文),我需要把整個物化視圖的流程串聯(lián)一遍,于是便有了這篇文章。
在開始之前簡單了解下物化視圖的基本概念:
圖片
簡單來說,視圖和 MySQL 這類傳統(tǒng)數(shù)據(jù)庫的概念類似,也是用于解決大量消耗性能的 SQL 的,可以提前將這些數(shù)據(jù)查詢好然后放在一張單獨的表中,這樣再查詢的時候性能消耗就比較低了。
刷新條件
為了保證視圖數(shù)據(jù)的實時性,還需要在數(shù)據(jù)發(fā)生變化的時候能夠及時刷新視圖里的數(shù)據(jù),目前有這幾個地方會觸發(fā)視圖刷新:
圖片
- 手動刷新視圖,使用 REFRESH MATERIALIZED VIEW order_mv; 語句
- 將視圖設置為 active 狀態(tài):ALTER MATERIALIZED VIEW order_mv ACTIVE;
- 基表數(shù)據(jù)發(fā)生變化時觸發(fā)刷新。
- truncate 基表時觸發(fā)刷新:truncate table trunc_db.t1;
- drop partition 時觸發(fā):ALTER TABLE <tbl_name> DROP PARTITION(S) p0, p1 [, ...];
這里的 truncate table 和 drop partition 目前的版本還存在 bug:當基表和物化視圖不在一個數(shù)據(jù)庫時不會觸發(fā)自動刷新,目前已經(jīng)修復了。
圖片
- https://github.com/StarRocks/starrocks/pull/52618
- https://github.com/StarRocks/starrocks/pull/52295
刷新流程
圖片
如圖所示,當觸發(fā)一次刷新之后主要就是需要計算出需要刷新的分區(qū)。
第一次觸發(fā)刷新的時候是不會帶上周期(比如時間范圍),然后根據(jù)過濾計算出來的周期,默認情況下只會使用第一個周期(我們可以通過 partition_refresh_number 參數(shù)來調整單次刷新的分區(qū)數(shù)量)。
然后如果還有其余的周期,會將這些周期重新觸發(fā)一次刷新任務(會帶上剛才剩余的周期數(shù)據(jù)),這樣進行遞歸執(zhí)行。
通過日志會看到返回的分區(qū)數(shù)據(jù)。
新增優(yōu)化參數(shù)
我們在使用物化視圖的時候,碰到一個場景:
CREATE TABLE IF NOT EXISTS test.par_tbl1
(
datekey DATETIME,
k1 INT,
item_id STRING,
v2 INT
)PRIMARY KEY (`datekey`,`k1`)
PARTITION BY date_trunc('day', `datekey`);
CREATE TABLE IF NOT EXISTS test.par_tbl2
(
datekey DATETIME,
k1 INT,
item_id STRING,
v2 INT
)PRIMARY KEY (`datekey`,`k1`)
PARTITION BY date_trunc('day', `datekey`);
CREATE TABLE IF NOT EXISTS test.par_tbl3
(
datekey DATETIME,
k1 INT,
item_id STRING,
v2 INT
)
PRIMARY KEY (`datekey`,`k1`);
但我們有三張基表,其中 1 和 2 都是分區(qū)表,但是 3 是非分區(qū)表。
此時基于他們新建了一個物化視圖:
CREATE
MATERIALIZED VIEW test.mv_test
REFRESH ASYNC
PARTITION BY a_time
PROPERTIES (
"excluded_trigger_tables" = "par_tbl3"
)
AS
select date_trunc("day", a.datekey) as a_time, date_trunc("day", b.datekey) as b_time,date_trunc("day", c.datekey) as c_time
from test.par_tbl1 a
left join test.par_tbl2 b on a.datekey = b.datekey and a.k1 = b.k1
left join test.par_tbl3 c on a.k1 = c.k1;
當我同時更新了分區(qū)表和非分區(qū)表的數(shù)據(jù)時:
UPDATE `par_tbl1` SET `v2` = 2 WHERE `datekey` = '2024-08-05 01:00:00' AND `k1` = 3;
UPDATE `par_tbl3` SET `item_id` = '3' WHERE `datekey` = '2024-10-01 01:00:00' AND `k1` = 3;
預期的結果是只有 par_tbl1 表里修改的數(shù)據(jù)會被同步到視圖("excluded_trigger_tables" = "par_tbl3"已經(jīng)被設置為不會觸發(fā)視圖刷新),但實際情況是 par_tbl1 和 par_tbl2 表里所有的數(shù)據(jù)都會被刷新到物化視圖中。
我們可以使用這個 SQL 查詢無刷視圖任務的運行狀態(tài):
SELECT * FROM information_schema.task_runs order by create_time desc;
這樣就會造成資源損耗,如果這兩張基表的數(shù)據(jù)非常大,本次刷新會非常耗時。
所以我們的需求是在這樣的場景下也只刷新修改的數(shù)據(jù)。
因此我們在新建物化視圖的時候新增了一個參數(shù):
CREATE
MATERIALIZED VIEW test.mv_test
REFRESH ASYNC
PARTITION BY a_time
PROPERTIES (
"excluded_trigger_tables" = "par_tbl3",
"excluded_refresh_tables"="par_tbl3"
)
AS
select date_trunc("day", a.datekey) as a_time, date_trunc("day", b.datekey) as b_time,date_trunc("day", c.datekey) as c_time
from test.par_tbl1 a
left join test.par_tbl2 b on a.datekey = b.datekey and a.k1 = b.k1
left join test.par_tbl3 c on a.k1 = c.k1;
這樣當在刷新數(shù)據(jù)的時候,會判斷 excluded_refresh_tables 配置的表是否有發(fā)生數(shù)據(jù)變化,如果有的話則不能將當前計算出來的分區(qū)(1,2 兩張表的全量數(shù)據(jù))全部刷新,而是繼續(xù)求一個交集,只計算基表發(fā)生變化的數(shù)據(jù)。
這樣就可以避免 par_tbl1、par_tbl2 的數(shù)據(jù)全量刷新,而只刷新修改的數(shù)據(jù)。
這樣的場景通常是在關聯(lián)的基表中有一張字典表,通常數(shù)據(jù)量不大,所以也不需要分區(qū)的場景。
這樣在創(chuàng)建物化視圖的時候就可以使用這兩個參數(shù) excluded_trigger_tables,excluded_refresh_tables 將它排除掉了。
整體的刷新邏輯并不復雜,主要就是幾個不同的刷新入口以及刷新過程中計算分區(qū)的邏輯。
參考鏈接:
- https://docs.starrocks.io/zh/docs/using_starrocks/async_mv/Materialized_view/#%E7%90%86%E8%A7%A3-starrocks-%E7%89%A9%E5%8C%96%E8%A7%86%E5%9B%BE
- https://docs.starrocks.io/zh/docs/using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views/#%E5%88%86%E5%8C%BA%E5%BB%BA%E6%A8%A1
- https://github.com/StarRocks/starrocks/pull/52295
- https://github.com/StarRocks/starrocks/pull/52618