動態表的概念是社群很早就提出的但並沒有全部實現,下文中所有介紹都是基於已有規劃和proposal給出的,可能與之後實現存在出入僅供參考複製程式碼
概念
動態表直觀上看是一個類似於資料庫中的Materialized View
概念。動態表隨著時間改變;類似靜態的batch table一樣可以用標準SQL進行查詢然後一個新的動態表;可以和流無損地互相轉換(對偶的)。對現有的API最大的改進關鍵在表的內容隨著時間改變,而現在的狀態只是append。當前的streaming table可以認為是一種動態表,append模式的動態表。
流到 Dynamic Table
流被轉換成Table時決定選擇哪種模式是依據表的schema是否定義primary key。
Append模式:
如果表的schema沒有包括key的定義那轉換成表時採用append模式。把流中每條新來的record當做新的row append到表中。一旦資料加到表中就不能再被更新和刪除(指當前表中,不考慮轉換成新表)。
Replace模式:
相對應,如果定義了key,那麼對於流中的每條記錄如果key不在表中就insert否則就update。
Dynamic Table 到 流
表到流的操作是把表的所有change以changelog stream的方式傳送到下游。這一步也有兩種模式。
Retraction模式:
traction模式中對於Dynamic Table的insert和delete的change分別產生insert或delete event。如果是update的change會產生兩種change event,對於之前傳送出去的同樣key的record會產生delete event,對於當前的record是產生insert event。如下圖所示:
Update模式:
update模式依賴Dynamic Table定義了key。所有的change event是一個kv對。key對應表的key在當前record中的值;對於insert和change value對應新的record。對於delete value是空表示該可以已經被刪除。如下圖所示:
example
表的內容隨著時間改變意味著對錶的query結果也是隨著時間改變的。我們定義:
- A[t]: 時間t時的表A
- q(A[t]):時間t時對錶A執行query q
舉個例子來理解動態表的概念:
query的限制
由於流是無限的,相對應 Dynamic Table 也是無界的。當查詢無限的表的時候我們需要保證query的定時是良好的,有意義可行的。
1.在實踐中Flink將查詢轉換成持續的流式應用,執行的query僅針對當前的邏輯時間,所以不支援對於任意時間點的查詢(A[t])。
2.最直觀的原則是query可能的狀態和計算必須是有界的,所以可以支援可增量計算的查詢:
- 不斷更新當前結果的查詢:查詢可以產生insert,update和delete更改。查詢可以表示為
Q(t+1) = q'(Q(t), c(T, t, t+1))
,其中Q(t)是query q的前一次查詢結果,c(T, t, t_+1) 是表T從t+1到t的變化, q'是q的增量版本。 - 產生append-only的表,可以從輸入表的尾端直接計算出新資料。查詢可以表示為
Q(t+1) = q''(c(T, t-x, t+1)) ∪ Q(t)
,q''是不需要時間t時q的結果增量版本query q。c(T, t-x, t+1)是表T尾部的x+1個資料,x取決於語義。例如最後一小時的window aggregation至少需要最後一小時的資料作為狀態。其他能支援的查詢型別還有:單獨在每一行上操作的SELECT WHERE;rowtime上的GROUP BY子句(比如基於時間的window aggregate);ORDER BY rowtime的OVER windows(row-windows);ORDER BY rowtime。
3.當輸入表足夠小時,對錶的每條資料進行訪問。比如對兩個大小固定的流表(比如key的個數固定)進行join。
中間狀態有界
如上文所說的,某些增量查詢需要保留一些資料(部分輸入資料或者中間結果)作為狀態。為了保證query不會失敗,保證查詢所需要的空間是有界的不隨著時間無限增長很重要。主要有兩個原因使得狀態增長:
- 不受時間謂詞約束的中間計算狀態的增長(比如 聚合key的膨脹)
- 時間有界但是需要遲到的資料(比如 window 的聚合)
雖然第二種情況可有通過下文提到的"Last Result Offset"引數解決,但是第一種情況需要優化器檢測。我們應該拒絕不受時間限制的中間狀態增長的查詢。優化器應該提供如何修復查詢且要求有適當的時間謂詞。比如下面這個查詢:
SELECT user, page, COUNT(page) AS pCnt
FROM pageviews
GROUP BY user, page複製程式碼
隨著使用者數和頁面數的增長,中間狀態會資料隨著時間推移而增長。對於儲存空間的要求可以通過新增時間謂詞來限制:
SELECT user, page, COUNT(page) AS pCnt
FROM pageviews
WHERE rowtime BETWEEN now() - INTERVAL '1' HOUR AND now() // only last hour
GROUP BY user, page複製程式碼
因為不是所有屬性都是不斷增長的, 因此可以告訴優化器domain的size, 就可以推斷中間狀態不會隨著時間推移而增長,然後接受沒有時間謂詞的查詢。
val sensorT: Table = sensors
.toTable('id, 'loc, 'stime, 'temp)
.attributeDomain('loc, Domain.constant) // domain of 'loc is not growing
env.registerTable("sensors", sensorT)
SELECT loc, AVG(temp) AS avgTemp
FROM sensors
GROUP BY loc複製程式碼
結果的計算和細化時序
一些關係運算子必須等資料到達才能計算最終結果。例如:在10:30關閉的視窗至少要等到10:30才能計算出最終的結果。Flink的logical clock(即 決定何時才是10:30)取決於使用event time 還是 processing time。在processing time的情況下,logical time是每個機器的wallclock;在event time的情況下,logical clock time是由源頭提供的watermark決定的。由於資料的亂序和延遲當在event time模式下時等待一段時間來減小計算結果不完整性。另一方面某些情況下希望得到不斷改進的早期結果。因此對於結果被計算、改進或者做出最終結果時有不同的要求、
下圖描繪了不同的配置引數如何用於控制早期結果和細化計算結果的。
- "First Result Offset" 指第一個早期結果被計算的結果的時間。時間是相對於第一次可以計算完整結果的時間(比如相對於window的結束時間10:30)。如果設定的是-10分鐘,對於結束時間是10:30的window那麼第一個被髮出去的結果是在邏輯時間10:20計算的。這個引數的預設值是0,即在window結束的時候才計算結果。
- "Complete Result Offset" 指完整的結果被計算的時間。時間是相對於第一次可以計算完整的時間。如果設定的是+5分鐘,對於結束時間是10:30的window那麼產生完整結果的時間是10:35。這個引數可以減輕延遲資料造成的影響。預設是0,即在window結束的時候計算的結果就是完整結果。
- "Update Rate" 指計算完整結果之前一次次更新結果的時間間隔(可以是時間和次數)。如果設為5分鐘,視窗大小是30分鐘的tumbling window,開始時間是10:300,"First Result Offset"是-15分鐘, "Complete Result Offset"是2分鐘,那麼將在10:20, 10:25, 10:30更新結果,10:15禪城寄一個結果,10:32產生完整結果。
- "Last Updates Switch" 指完整結果發出後對於延遲的資料是否計算延遲更新,直到計算狀態被清除。
- "Last Result Offset" 指可計算的最後一個結果的時間。這是內部狀態被清除的時間,清除狀態後再到達的資料將被丟棄。Last Result Offset 意味著計算的結果是近似值,不能保證精確。