接著來說說實現 incremental model 省錢的路途上,遇到的問題吧。
首先是 is_incremental 內的寫法,dbt 推薦了一個做法,可以用 {{ this }}
來取得該表中特定欄位的最新時間,然而在後續運行時,我卻發現這樣的作法在 BigQuery 中並沒有省到運算的費用,試了蠻多做法,我推測這可能是由於 BigQuery 在處理條件時的順序問題。當用以下寫法時,where 後面的篩選條件被視為查詢結果,而不是定值,導致此篩選沒有縮減到運算量。
若是將這段 select coalesce(max(event_time),'1900-01-01') from {{ this }}
先運算後,將這段以運算後得到的值取代,就能縮減到運算量了。
{{
config(
materialized='incremental'
)
}}
select
*,
from {{ ref('app_data_events') }}
{% if is_incremental() %}
where event_time >= (select coalesce(max(event_time),'1900-01-01') from {{ this }} )
{% endif %}
怎麼會這樣!!
於是我只好自己寫了一個 macro function,取代這段運算,改成這個做法之後,運算的邏輯就變成會先取得運算後的定值,進而就縮減到運算量了。
{% macro get_latest_date_with_late_arriving(column_name, time_part="day") %}
{% set sql_statement %}
select date(timestamp_trunc(max(timestamp_sub({{ column_name }}, interval 3 day)), {{ time_part }}))
from {{ this }}
{% endset %}
{% set latest_time = dbt_utils.get_single_value(sql_statement, default="2012-01-01") %}
{{ return("'" ~ latest_time ~ "'") }}
{% endmacro %}
我覺得這個部分真的蠻怪的,也許應該要去 dbt 開個 issue 討論嗎?
在搜尋有沒有人遇到類似情況的時候,卻都沒有發現相關的討論,主要的討論通常聚焦在另一點 — 晚到的資料。
大家如果看到剛才的 macro,也許會好奇為何有一個 timestamp_sub 3 day 的處理,這個是因為我發現資料備份時,有部分資料會晚兩天才抵達,三天以上的情況幾乎沒有,因此每次更新時,我們還是多重刷了三天的資料,減少資料遺漏的狀況。
大家比起這點運算成本,好像更在意資料完整度,當然不是說我們把資料完整度放在後面,而是小組織還是挺在意錢錢的,能省則省。
incremental models 在 testing 時其實也有可以節省運算量的部分,在 yaml file 的 tests 部分,多加上篩選日期的 config,畢竟 tests 其實也就是把這些 yaml file 的內容轉換成 sql code 拿去驗證,能省則省。
- name: my_column
tests:
- accepted_values:
values: ["a", "b", "c"]
config:
where: "date_column = current_date"
另外提一個用 is_incremental 的小偏方,我遇到了一個情境,是當 incremental model 在 create 跟 insert 的時候需要做不同的轉換,這時候在 dbt 可以加入這樣 if else 的邏輯,來做出不同的轉換分流,挺方便的!
不過會用到這樣方法來處理的狀況應該是不太多的,剛好想到提及一下。
select
*,
{% if is_incremental() %}
from a
{% else %}
from b
{% endif %}
以上心得,出自於實踐的過程中發現的問題,沒有找到相關的討論及驗證,如果有人有新的發現歡迎告知!