Incremental model
Bạn sẽ học được
- Khi nào một model nên là
incrementalthay vìtable. - Viết điều kiện
is_incremental()đúng để không trùng dữ liệu. - Chọn
unique_keyvà chiến lược ghi phù hợp. - Xử lý dữ liệu đến muộn bằng cửa sổ lookback.
- Biết khi nào bắt buộc phải
--full-refresh.
Khái niệm cốt lõi
Một bảng fact có 40 triệu dòng và mỗi ngày thêm 180.000 dòng. Nếu materialization là table, mỗi đêm dbt phải quét và ghi lại toàn bộ 40 triệu dòng: đo được 28 phút. Phần lớn thời gian đó lặp lại công việc của hôm qua.
incremental chỉ xử lý dữ liệu mới. Cùng bảng đó, mỗi đêm chỉ đọc 180.000 dòng mới, chạy trong 40 giây.
Cơ chế gồm ba phần:
- Lần chạy đầu tiên: dbt tạo bảng như bình thường (giống
table). - Các lần sau: dbt thêm điều kiện lọc vào câu
SELECTcủa bạn quais_incremental(). - dbt ghi phần kết quả vào bảng có sẵn bằng chiến lược đã cấu hình.
{{ config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='merge'
) }}
select
order_id,
customer_id,
order_date,
amount,
updated_at
from {{ ref('stg_orders') }}
{% if is_incremental() %}
where updated_at > (select coalesce(max(updated_at), '1900-01-01') from {{ this }})
{% endif %}{{ this }} trỏ tới chính bảng đang được build. Đây là cách duy nhất để model biết trạng thái hiện tại của nó.
Không có
is_incremental()thì model làincrementaltrên giấy nhưng mỗi lần chạy lại đọc toàn bộ nguồn — chậm mà vẫn có thể trùng.
Chiến lược ghi
| Chiến lược | Cách làm | Dùng khi |
|---|---|---|
append | Chèn thêm, không kiểm tra | Bảng chỉ thêm, không bao giờ sửa |
merge | Khớp theo unique_key, cập nhật hoặc chèn | Dữ liệu có thể sửa (trạng thái đơn) |
delete+insert | Xoá các dòng cùng khoá rồi chèn lại | Adapter không hỗ trợ merge |
Với dbt-postgres, mặc định là append. Nếu bảng có cập nhật trạng thái — đơn từ pending sang paid — append tạo hai dòng cho cùng một order_id và mọi phép sum đều sai. Phải đặt unique_key và merge (hoặc delete+insert).
Dữ liệu đến muộn
Điều kiện updated_at > max(updated_at) bỏ sót bản ghi đến muộn: một đơn tạo ngày 9/3 nhưng được cập nhật vào kho lúc 10/3 lại mang updated_at cũ. Nó không vượt qua bộ lọc và vĩnh viễn không được nạp.
Cách sửa là mở rộng cửa sổ về quá khứ:
{% if is_incremental() %}
where updated_at >= (select max(updated_at) from {{ this }}) - interval '3 days'
{% endif %}Ba ngày là cửa sổ lookback. Mỗi lần chạy đọc lại 3 ngày gần nhất — với 180.000 dòng/ngày là 540.000 dòng, vẫn nhanh hơn nhiều so với 40 triệu.
Vì cửa sổ đọc lại các dòng cũ, bắt buộc phải có unique_key và chiến lược merge/delete+insert. Nếu để append, mỗi lần chạy lại 3 ngày sẽ nhân bản đúng phần đó.
--full-refresh
Một số thay đổi không thể áp dụng tăng dần:
- Thêm cột mới vào
SELECT. - Đổi kiểu dữ liệu của cột.
- Đổi logic tính toán cho toàn bộ lịch sử.
- Đổi
unique_key.
Khi đó chạy:
dbt run --select fct_orders --full-refreshdbt drop bảng và build lại từ đầu. Mất 28 phút nhưng cho kết quả đúng. Trong production nên thông báo trước vì bảng tạm thời không đọc được trong lúc build.
Đổi cột mà quên
--full-refreshlà lỗi phổ biến: dbt cố chèn cột mới vào bảng cũ và báo lỗi kiểucolumn does not exist.
Kiểm thử model incremental
Incremental sai thường không báo lỗi. Nó chỉ âm thầm nhân đôi dữ liệu. Luôn có:
models:
- name: fct_orders
columns:
- name: order_id
tests:
- unique
- not_nullTest này chỉ có tác dụng khi build lại sau lần chạy incremental thứ hai. Quy trình kiểm tra:
- Chạy
dbt build --select fct_ordershai lần liên tiếp. - So
count(*)trước và sau — phải bằng nhau nếu không có dữ liệu mới. - Nếu tăng, kiểm tra
unique_keyvà chiến lược ghi.
select count(*) as tong, count(distinct order_id) as khac_nhau
from fct_orders;Hai số phải bằng nhau.
Thực hành
- Đổi một model lớn sang
incrementalvớiunique_key. - Chạy hai lần, xác nhận số dòng không đổi.
- Thêm cửa sổ lookback 3 ngày và giải thích chi phí.
- Chèn một bản ghi có
updated_atcũ, xác nhận nó được nạp. - Thêm cột mới, chạy không
--full-refresh, đọc lỗi, rồi chạy lại với cờ đó.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
| Số dòng tăng sau mỗi lần chạy | append không unique_key | Đặt unique_key + merge |
| Báo cáo doanh thu gấp đôi | Trùng ở biên cửa sổ lookback | Dùng merge và unique_key |
| Đơn đến muộn bị mất | Điều kiện > chỉ lấy dòng mới | Mở lookback về vài ngày |
column does not exist sau khi thêm cột | Bảng cũ chưa có cột | --full-refresh một lần |
| Lần chạy đầu rất chậm | Lần đầu build toàn bộ, bình thường | Chấp nhận, hoặc chạy ngoài giờ |
Model incremental chậm như table | Thiếu is_incremental() | Thêm khối {% if %} |
Tóm tắt
incrementalchỉ hữu ích khi có điều kiệnis_incremental().unique_key+mergelà cấu hình an toàn cho dữ liệu có cập nhật.- Dữ liệu đến muộn cần cửa sổ lookback, kèm chiến lược ghi không nhân bản.
- Thêm cột hoặc đổi logic lịch sử phải
--full-refresh. - Chạy hai lần và kiểm tra
count(*)là cách phát hiện lỗi sớm nhất.
Use ← → to move between lessons