Phần 01
Một đêm trong một hình
dbt không có lịch riêng. Nó là bước ② của một kịch bản PowerShell do Windows Task Scheduler gọi lúc 01:00. Bước ① hỏng thì dbt không chạy; dbt run hỏng thì các bước sau không chạy.
Cập nhật 08/10/2026 — trang này tả lượt đêm trên laptop, đo 18–26/09. Đã đổi từ đó: (1) số ngày đọc lại: 14 ngày từ đêm 28/09, rồi 90 ngày từ đêm 02/10 (người chốt 01/10 sau đối soát tháng 8) — câu "mỗi ngày chứng từ chỉ được đọc lại hai lần" ở bìa và Phần 09, cùng các phương án ở Phần 12, nay là lịch sử; (2) lượt đêm chạy trên PC từ 05→06/10: đo 06/10 mất 51 phút (01:00:01 → 01:51:17), dbt run 47/47, dbt test 94 đạt / 1 cảnh báo / 1 lỗi có chủ đích; (3) 47 model (28 bảng + 19 view): thêm dim_thue_gtgt, và fact_sales có thêm các cột doanh_thu, sl_ban (GATE B), kenh_hien_thi; (4) 96 test dbt. Bản đồ các lớp đo trên PC ngày 08/10: Phụ lục 7.
Thời gian ghi trên từng bước là của lượt đêm 26/09/2026. Mũi tên đỏ là chỗ duy nhất dữ liệu đi vào máy từ bên ngoài.
| Bước | Làm gì | Hỏng thì sao |
|---|---|---|
① ingest/sync.py | Khoá advisory 91626 → 9 danh mục nhẹ (đọc lại toàn bộ) → GetItemList toàn bộ danh mục → GetCustomersList khách tạo trong 90 ngày → đơn của 2 ngày chứng từ gần nhất (D-1, D) → thanh toán cùng 2 ngày → phát hiện chứng từ bị rút / tái phát hành → ghi raw.sync_run. | Ghi logs/ALERT_sync.txt, thoát 1, không chạy dbt → mart giữ số hôm trước (đêm 21/09 đã xảy ra). |
② dbt run | Dựng lại cả 46 model, 4 luồng song song. Không có model incremental nào: mỗi bảng mart bị xoá và tạo lại từ đầu. | ALERT "dbt RUN that bai", dừng. Mỗi bảng được thay nguyên khối, nhưng dừng giữa chừng thì mart nửa mới nửa cũ (đêm 20/09). |
③ dbt test | 74 test. Đêm 26/09: 72 đạt · 1 FAIL có chủ đích (dq_summary.passed, cổng C05b/C06b — Q22, luật R12) · 1 WARN (fact_payment.order_id → fact_order, 1 dòng). | Chỉ ghi log, không báo động — tách riêng để cái đỏ có chủ đích không che lỗi model thật. |
④ verify.py P1.6 | 17 phép kiểm nghiệm thu đồng bộ — đây mới là cổng thật của đêm. | ALERT, thoát 1. |
⑤ checks/sync_health.py | Nhịp tim 26 giờ, ghi logs/p16_health.json. Task …-Health chạy lại độc lập lúc 09/15/21 giờ. | Tự ghi ALERT. |
Phần 02
Thời gian đo được 18–26/09
Phần lớn một đêm là chờ FAST trả lời (3–5,5 phút). dbt run ổn định 1'44"–3'09" và không phụ thuộc số ngày đọc lại, vì nó luôn dựng lại toàn bộ.
| Đêm | Bắt đầu | ① FAST + sync.py | ② dbt run | dbt tự báo | ③ dbt test | ④+⑤ | Tổng | Kết quả |
|---|---|---|---|---|---|---|---|---|
| 18/09 | 01:00:02 | 3'23" | 2'34" | 145,5 s | 0'10" | 0'18" | 6'34" | Đạt · lượt delta thật đầu tiên |
| 19/09 | 03:43:26 | — | — | — | — | — | — | Chết giữa chừng (máy ngủ), sync_run treo tới lượt sau dọn |
| 20/09 | 01:00:02 | 5'08" | — | — | — | — | — | dbt kẹt khoá rồi bị đóng cửa sổ 01:05 |
| 20/09 | 02:20:44 | 3'43" | 2'26" | 142,2 s | 0'19" | 0'18" | 6'50" | Đạt (chạy tay lại) |
| 21/09 | 01:00:02 | 2'12" | — | — | — | — | — | sync.py lỗi (container tắt) → không chạy dbt |
| 22/09 | 00:24:43 | 3'35" | 3'09" | 183,4 s | 0'11" | 0'19" | 7'17" | Đạt (chạy tay) |
| 22/09 | 01:00:01 | 3'32" | 1'52" | 109,6 s | 0'09" | 0'16" | 5'52" | Đạt |
| 23/09 | 01:00:03 | 3'17" | 1'44" | 97,0 s | 0'11" | 0'18" | 5'32" | Đạt |
| 24/09 | 05:55:03 | 4'28" | 2'27" | 140,8 s | 0'15" | 0'24" | 7'47" | Đạt · chạy bù sau khi máy dậy |
| 25/09 | 02:04:20 | 4'09" | 2'33" | 147,6 s | 0'18" | 0'21" | 7'25" | Đạt · chạy bù (máy ngủ tới 02:03) |
| 26/09 | 01:00:02 | 5'32" | 2'56" | 167,4 s | 0'20" | 0'17" | 9'07" | Đạt · 46/46 model |
"② dbt run" = từ dòng Running with dbt= tới dòng dbt run PASS của wrapper; "dbt tự báo" = con số trong dòng Finished running … (…s), không tính ~4 s dbt đọc dự án. Số model tăng dần: 39 (20/09) → 40 (22/09) → 41 (23–24/09) → 46 (từ 25/09, thêm lớp bantin).
Mỗi lần gọi FAST mất bao lâu
| Lời gọi | Số lần đo | Trung vị | Nhanh nhất | Chậm nhất | Bản ghi trả về (trung vị) |
|---|---|---|---|---|---|
GetItemList (toàn bộ sản phẩm) | 8 | 103,4 s | 74,0 s | 170,6 s | 108.014 |
GetCustomersList (cửa sổ 90 ngày) | 8 | 27,0 s | 23,2 s | 56,8 s | 11.910 |
GetListVoucherInfo (đơn, 1 ngày) | 16 | 17,2 s | 12,0 s | 30,8 s | 1.246 |
GetListVoucherPaymentInfo (TT, 1 ngày) | 16 | 10,6 s | 6,2 s | 14,6 s | 1.246 |
| Phát hiện rút (SQL trên hub, 1 ngày) | 16 | 5,2 s | 3,2 s | 7,9 s | — |
| 9 danh mục nhẹ cộng lại (kể cả nghỉ giữa các lời gọi) | 1 đêm | 33,7 s | — | — | phường/xã 10.467 là nặng nhất |
Đơn vị chi phí cần nhớ: đọc lại một ngày chứng từ = đơn + thanh toán + phát hiện rút ≈ 33 s (trung vị), tệ nhất đo được ≈ 53 s. Phần 12 dùng con số này để tính giá của việc quét lại 7 / 45 / 90 ngày. Tất cả số đo lúc 01:00–06:00; gọi FAST giữa giờ bán hàng mất bao lâu thì chưa đo.
Model dbt chậm nhất
| Model | Trung vị | Chậm nhất | Số đêm đo | Ghi chú |
|---|---|---|---|---|
mart.bt_khach_mua_dau | 55,1 s | 57,7 s | 2 | lớp bản tin, mới từ 25/09 |
mart.fact_order_line | 49,5 s | 82,8 s | 8 | 5,1 triệu dòng, tách từ JSON |
mart.bt_kpi_ngay | 48,2 s | 50,5 s | 2 | lớp bản tin |
dq.dq_order_recon | 40,2 s | 57,0 s | 8 | đối chiếu tiền từng đơn |
dq.dq_fk_check | 37,9 s | 54,0 s | 8 | kiểm khoá ngoại |
mart.fact_order | 26,6 s | 53,9 s | 8 | 2,0 triệu đơn |
dq.dq_summary | 24,9 s | 31,3 s | 8 | bảng nghiệm thu |
dq.dq_sync_summary | 21,1 s | 27,3 s | 8 | |
mart.bt_khach_ngay | 20,9 s | 22,8 s | 2 | lớp bản tin |
dq.dq_exception | 20,1 s | 32,2 s | 8 |
4 luồng chạy song song nên tổng thời gian model (~7 phút) lớn hơn thời gian dbt run (~2,5–3 phút). 14 view staging dựng trong 0,1–0,3 s mỗi cái.
Phần 03
Cách tự đo lại
Ba cách, từ đầy đủ tới nhanh. Cả ba chỉ đọc — không chạm dữ liệu, không gọi FAST.
Cách 1 — script đọc log (đầy đủ nhất)
File do_thoi_gian_dem.py gửi kèm trang này. Nó tách từng lượt trong logs/sync_*.log, tính thời gian từng bước, từng lần gọi FAST, số đơn FAST trả cho từng ngày qua các lần đọc, và model dbt chậm nhất.
sixdo-datahub\.venv\Scripts\python.exe do_thoi_gian_dem.py --ngay 10 --model 15
Cách 2 — một dòng PowerShell
Select-String -Path sixdo-datahub\logs\sync_*.log -Pattern 'sync bat dau','Finished running','dbt run PASS','sync XONG','CANH BAO' | ForEach-Object { $_.Filename + ' ' + $_.Line }
Cách 3 — hỏi cơ sở dữ liệu (chỉ phần sync.py)
select run_id, status, started_at, finished_at,
finished_at - started_at as thoi_gian_sync_py,
rows_orders, n_withdrawn
from raw.sync_run order by started_at desc limit 10;
Ba cái bẫy khi đọc số: (1) dòng log của dbt in giờ UTC — 18:08:24 trong log là 01:08:24 giờ máy; dòng của wrapper [2026-09-26 01:08:30] mới là giờ máy. (2) raw.sync_run chỉ đo bước ①, đóng dòng trước khi dbt chạy — nó không biết dbt mất bao lâu, cũng không biết dbt có hỏng không. (3) transform/target/run_results.json bị ghi đè mỗi lệnh dbt — sau lượt đêm nó chứa kết quả của dbt test, không phải dbt run.
Log ghi theo lô: wrapper chỉ đổ ra file khi một bước xong, nên nhìn log lúc 01:03 không thấy gì của bước đang chạy. Muốn biết lượt đêm đã xong chưa thì chờ dòng sync XONG hoặc CANH BAO.
Phần 04
Lớp raw — FAST đổ vào đâu
Đây là lớp duy nhất dbt không tạo ra: ingest/sync.py ghi vào, dbt khai nó là source('raw', …). Mỗi bản ghi FAST là một dòng JSONB nguyên bản, kể cả mảng con dòng hàng / dòng thanh toán.
| Bảng raw | Endpoint FAST | Mỗi đêm đọc gì | Khoá | Dòng hiện có |
|---|---|---|---|---|
orders | GetListVoucherInfo | 2 ngày chứng từ gần nhất (hôm kia, hôm qua), từng ngày một lời gọi | stt_rec | 2.001.727 |
order_payments | GetListVoucherPaymentInfo | cùng 2 ngày, ngay sau đơn | stt_rec | 2.001.723 |
items | GetItemList | toàn bộ danh mục (ngày tạo 2015-01-01 → hôm nay) | ma_vt | 108.830 FAST trả 108.557 |
customers | GetCustomersList | chỉ khách tạo trong 90 ngày gần nhất | ma_kh | 386.290 đêm nay đọc 11.920 |
item_properties | GetPropertiesItemList × 9 loại | toàn bộ | Type:code | 865 |
salespeople | GetSalesEmployeeList | toàn bộ | ma_nvbh | 2.956 |
stores · sites | GetStoreList · GetSiteList | toàn bộ | ma_bp · ma_kho | 94 · 329 |
provinces · districts · wards | 3 endpoint địa lý | toàn bộ | khoá ghép | 63 · 704 · 10.467 |
payment_types · nationalities | 2 endpoint | toàn bộ | ma_tt · ma_qt | 13 · 13 |
load_state | Sổ checkpoint: mỗi (endpoint, cửa sổ) một dòng done/error. sync.py dùng nó để tự nạp bù khi lỡ đêm (tối đa 45 ngày). | |||
doc_status | Cờ chứng từ bị FAST rút (withdrawn) hoặc tái phát hành (replaced). Không xoá nguồn; mart đọc cờ này thành is_excluded. | |||
sync_run | Một dòng mỗi lượt đồng bộ: giờ bắt đầu/kết thúc bước ①, số đơn, số rút. Nguồn của nhịp tim 26 giờ. | |||
Ghi thế nào upsert, không xoá, không giữ bản cũ
INSERT … ON CONFLICT (natural_key) DO UPDATE SET payload, batch_id, loaded_at. Hệ quả: (1) chạy lại không nhân đôi; (2) chứng từ FAST xoá thì hub vẫn giữ — chỉ bị gắn cờ nếu ngày đó được đọc lại; (3) FAST sửa một chứng từ thì bản mới đè bản cũ, không còn dấu vết đã sửa gì — nên hiện không đo được FAST sửa bao nhiêu chứng từ cũ.
Phát hiện rút nhờ batch_id chung
Mọi bản ghi của một lượt mang cùng batch_id. Sau khi đọc lại một ngày, đơn nào của ngày đó không mang batch mới = FAST không còn trả → gắn withdrawn. Có chốt an toàn: nếu >10% và >20 đơn của một ngày "biến mất" thì coi là FAST trả thiếu, không gắn cờ, chỉ cảnh báo. Đơn đã gắn cờ mà FAST trả lại thì tự gỡ cờ.
Phần 05
Lớp staging — 14 view
Mỗi bảng raw một view (đơn thì hai: đầu đơn và dòng đơn). View không chứa dữ liệu; dựng lại mất 0,1–0,3 s. Việc nặng xảy ra khi mart đọc qua nó.
| View | Đọc từ | Việc chính |
|---|---|---|
stg_order | raw.orders | 1 dòng / stt_rec. Loại chứng từ theo hậu tố stt_rec: HDA = bán, HDF = trả. Kênh = nguon_bill. Có địa chỉ giao (PII) và trạng thái giao vận. |
stg_order_line | raw.orders | Tách mảng details[]. trim(ma_vt) bắt buộc (mã ở dòng đơn dài đúng 32 ký tự). Phiếu HDF có số lượng âm. |
stg_payment_line · stg_payment | raw.order_payments · raw.payment_types | Tách Paymentdetails[]; danh mục hình thức thanh toán. |
stg_product · stg_product_property | raw.items · raw.item_properties | Giữ mã thuộc tính API trả sẵn (màu, size, dòng, mùa…), không cắt chuỗi mã. |
stg_customer | raw.customers | Có PII (tên, SĐT, email) — chỉ vai bi_pii đọc được ở tầng mart. |
| 7 view danh mục | raw tương ứng | stg_store, stg_site, stg_salesperson, stg_nationality, stg_province, stg_district, stg_ward. |
Phần 06
Lớp mart — 21 model người dùng thật đọc
Mặc định là bảng (+materialized: table): mỗi đêm dbt tạo bảng mới rồi đổi tên thay bảng cũ. Năm model là view để không nhân bản 5 triệu dòng. Bốn bảng bantin là lớp tổng hợp cho bản tin Teams.
| Nhóm | Model | Hạt · điểm đáng nhớ |
|---|---|---|
| Fact (bảng) | fact_order | 1 dòng / đơn. Thêm is_withdrawn, is_replaced, is_excluded từ raw.doc_status. |
fact_order_line | 1 dòng / dòng hàng (~5,1 triệu). Mang is_excluded ở hạt dòng. | |
fact_payment | 1 dòng / dòng thanh toán. | |
| Dim (bảng) | dim_customer | Khách FAST + dòng "unknown member" cho mã khách có trong đơn mà không có trong danh sách khách (is_synthetic). |
dim_product, dim_store, dim_salesperson, dim_payment, dim_geo, dim_nationality, dim_channel, dim_date | dim_channel suy từ fact_order (kênh mới tự hiện); dim_date do dbt tự sinh 2020–2027 (2.922 ngày). | |
| View cho BI | fact_sales | Hợp đồng giao diện với Power BI: nối dòng + đầu đơn, phân loại dòng (SALE/PACKAGING/GIFT/DPNV), gộp is_excluded. Doanh thu chỉ dùng cột amount. |
| View theo quyền | dim_customer_bi, dim_customer_pii, fact_order_bi, dim_salesperson_bi | Bản không PII cho bi_reader; bản PII chỉ cho bi_pii. Từ 26/09 dim_salesperson_bi chỉ cho bi_pii (có tên nhân viên). |
Bản tin (tag bantin) | bt_kenh_nhom, bt_kpi_ngay, bt_khach_mua_dau, bt_khach_ngay | Số cộng sẵn theo ngày × kênh × cửa hàng cho bản tin Teams v2 (và chatbot P2.10). Tắt truy vấn song song bằng pre-hook. |
Không có model incremental nào. Đêm nào cũng tính lại toàn bộ 6 năm dữ liệu. Với khối lượng hiện nay việc này mất 1'44"–3'09" nên chưa đáng tối ưu, và nó có một cái lợi lớn: mọi thay đổi ở raw — kể cả chứng từ cũ — đều lên mart ngay đêm đó. Nút thắt về độ tươi nằm ở bước ① (đọc FAST ngày nào), không nằm ở dbt.
Phần 07
Lớp dq và 74 test
11 bảng dq đo chất lượng dữ liệu ngay trong dbt, dựng lại mỗi đêm như mart. Test dbt đọc các bảng này; một test đỏ có chủ đích và phải giữ đỏ.
| Bảng dq | Đo gì |
|---|---|
dq_order_recon | Mỗi đơn: tiền dòng hàng so với tiền thanh toán; lệch dưới 1 đồng là khớp. Gốc của mọi phép đối chiếu tiền. |
dq_summary | Bảng nghiệm thu: 1 dòng / phép kiểm. is_gate = true là cổng chặn với ngưỡng chốt trước (M5.7: lệch < 0,5% mỗi tháng, mỗi cửa hàng); false là báo cáo. |
dq_fk_check · dq_exception · dq_exception_tomtat | Khoá ngoại mồ côi; danh sách ngoại lệ từng đơn và bản tóm tắt. |
dq_m57_thang · dq_m57_cuahang · dq_q22_tomtat | Cổng M5.7 theo tháng / cửa hàng; tóm tắt Q22 (đơn thẻ trả trước) — cho trang Vận hành của Power BI. |
dq_doc_status · dq_sync_summary | Đơn bị rút / tái phát hành; tóm tắt các lượt đồng bộ. |
dq_bantin_doichieu | Lớp bantin cộng lại có khớp fact_sales không (B01–B06) — cổng sẵn sàng của bản tin Teams v2. |
Đỏ có chủ đích (luật R12): test accepted_values_dq_summary_passed__True FAIL vì cổng C05b/C06b bắt được một vấn đề dữ liệu thật (Q22). Không được "sửa" bằng cách nới ngưỡng hay lọc bớt. Vì vậy wrapper chạy dbt test tách khỏi dbt run và không báo động khi test thoát 1. Kịch bản hỏng nếu ai đó gộp lại thành dbt build: test đỏ sẽ chặn dựng các model phía sau → mart đứng im, bản tin gửi số cũ.
Phần 08
Sơ đồ phụ thuộc — xương sống doanh thu
46 model vẽ hết thì không đọc được. Hình dưới chỉ vẽ đường đi của doanh thu; các danh mục còn lại đều theo mẫu một-raw → một-staging → một-dim.
Nét đứt xám = view (không lưu dữ liệu). Nét đỏ = cờ từ raw.doc_status, chỉ có khi ngày chứng từ được đọc lại (Phần 09). Danh sách đầy đủ phụ thuộc: dbt ls hoặc dbt docs generate trong transform/.
Phần 09
Mỗi ngày chứng từ được đọc lại mấy lần
Câu hỏi quyết định số trên báo cáo đúng tới đâu. Trả lời: hai lần — 01:00 ngày D+1 và 01:00 ngày D+2 — rồi không bao giờ nữa. dbt thì dựng lại toàn bộ mỗi đêm, nhưng nó chỉ dựng lại được cái đã có trong raw.
Lần đọc cuối của từng ngày chứng từ tháng 9
| Ngày chứng từ | Được đọc | Lần đọc cuối | Nghĩa là |
|---|---|---|---|
| 01–14/09 | 1 lần | 15/09 22:40 (nạp lần đầu) | Sửa của FAST sau 15/09 không có trong hub |
| 15/09 | 2 lần | 16/09 21:00 | |
| 16–17/09 | 1 lần | 18/09 01:02 | Lượt 19/09 chết → không có lần đọc 2 |
| 18–19/09 | 1 đêm | 20/09 02:23 | Hai lượt cùng một đêm (01:00 và chạy lại 02:20) |
| 20/09 | 1 đêm | 22/09 01:02 | Lượt 21/09 lỗi |
| 21/09 → 24/09 | 2 đêm | D+2 (23/09 … 26/09) | Đúng thiết kế |
| 25/09 | 1 lần tới giờ | 26/09 01:04 | Lần 2 là 01:00 ngày 27/09 |
Đo từ raw.orders.loaded_at ngay sau lượt 26/09.
Hàng trả không sửa ngày bán — nó là chứng từ mới
Phiếu trả HDF dùng lại gốc số stt_rec của hoá đơn bán, nên ghép được. Từ 01/01/2025: 78.557 phiếu HDF, ghép được 70.347; trong đó 70.315 ghi ngày sau ngày bán, 32 cùng ngày, 0 phiếu lùi về trước. Độ trễ trung vị 8 ngày, 90% trong vòng 15 ngày. Vì phiếu trả mang ngày mới, đồng bộ đêm đọc nó như mọi chứng từ khác — tháng có hàng trả thì doanh thu tháng giảm đúng vào ngày trả, giống cách báo cáo FAST tính theo ngày chứng từ.
Nhưng FAST có sửa đơn cũ — bằng chứng từ trạng thái giao vận
Cột delivery_state nằm trên chính hoá đơn bán. Nếu FAST không bao giờ sửa đơn cũ thì tỉ lệ đơn "đang đi đường" phải như nhau bất kể hub đọc lúc nào. Đo trên đơn HDA ngoài POS:
| Tuần bán | Hub đọc lần cuối sau | Delivered | Đang đi đường* | % đang đi đường |
|---|---|---|---|---|
| 03/08 | 6 tuần | 3.708 | 238 | 6,0% |
| 10/08 | 5 tuần | 2.678 | 181 | 6,3% |
| 17/08 | 4 tuần | 6.236 | 342 | 5,2% |
| 24/08 | 3 tuần | 5.373 | 442 | 7,6% |
| 31/08 | 9–15 ngày | 1.918 | 260 | 11,9% |
| 07/09 | 1–8 ngày | 2.722 | 886 | 24,6% |
| 14/09 | 1–2 ngày | 463 | 3.252 | 87,5% |
| 21/09 | 1–2 ngày | 957 | 3.147 | 76,7% |
* TPLConfirmed + Shipped + Delivering + ReadyToShip + Delay. Không tính các trạng thái tiếng Việt, "(trống)", hoàn/huỷ. Với thiết kế hiện nay, đơn tuần 14/09 và 21/09 sẽ giữ nguyên trạng thái chụp lúc D+2 vì không được đọc lại.
Đọc bảng này thế nào: càng được đọc muộn, trạng thái càng "xong" — tức FAST cập nhật chứng từ gốc trong khoảng 2–3 tuần sau ngày bán, rồi chững ở ~5–7%. Đây là suy luận từ số đo, không phải đọc thẳng từ FAST. Cái không đo được: FAST có sửa tiền hay dòng hàng trên đơn cũ không — upsert đè bản cũ nên hub không còn gì để so.
Phần 10
Quyền và post-hook
Vì bảng mart bị xoá và tạo lại mỗi đêm, mọi GRANT cấp tay sẽ mất sau một đêm. Nên quyền được cấp bằng chính dbt, trong post_hook của từng model.
| Vai | Đọc được | Ai dùng |
|---|---|---|
bi_reader | 23 quan hệ: 15 ở mart (gồm fact_sales, dim_customer_bi, fact_order_bi, 4 bảng bantin) + 8 ở dq. Không có bảng/cột PII. (24 trước 26/09 08:11 — A2 của P2.10 chuyển dim_salesperson_bi sang bi_pii vì có tên nhân viên.) | Bản tin Teams; chatbot P2.10 (vai claude_ro kế thừa) |
bi_pii (không đăng nhập) | dim_customer, fact_order, fact_order_line, dim_salesperson + view dim_customer_pii, dim_salesperson_bi (6). Hook bọc if exists để chạy được cả khi vai chưa có. | Chỉ qua bi_full |
bi_full | bi_reader + bi_pii | Power BI Desktop (từ P2.8) |
Luật chạy tay: với mart, không bao giờ dbt run --select <một bảng mart> mà không kèm + phía sau. Bảng bị xoá kiểu CASCADE kéo theo các view phụ thuộc (fact_sales, dim_customer_bi, …) và dbt vẫn báo PASS. Đã xảy ra 22/09 (DBT-CASCADE-VIEW). Lượt đêm không dính vì nó chạy toàn bộ.
Phần 11
Bẫy vận hành đã biết
Mỗi dòng đều đã xảy ra thật hoặc đã đo được, kèm một kịch bản hỏng cụ thể.
| Mức | Bẫy | Kịch bản hỏng |
|---|---|---|
| Cao | Chứng chỉ TLS pos.sixdo.vn hết hạn 10/10/2026 | Từ 11/10 bước ① lỗi mỗi đêm → dbt không chạy → mọi báo cáo đứng ở ngày 09/10. Không được tắt kiểm TLS để lách (R10). |
| Cao | Máy hub ngủ qua 01:00 | Đêm 23→24/09 và 24→25/09 lượt đêm chạy bù lúc 05:55 và 02:04. Máy ngủ tới quá 07:00 thì bản tin gửi thẻ "chưa về". |
| Cao | Truy vấn tay còn treo trên mart | Đêm 20/09: một psql bỏ dở giữ khoá đọc 3 giờ → dbt run kẹt vì đổi tên bảng cần khoá độc quyền → người đóng cửa sổ → dbt chết giữa chừng. |
| Cao | dbt run --select không kèm + | Xem Phần 10 — view hợp đồng BI biến mất, dbt vẫn PASS. |
| TB | Cửa sổ vắng bảng lúc dbt chạy | Khoảng 01:05–01:09, refresh Power BI hay hỏi chatbot có thể gặp lỗi "bảng không tồn tại". Vì vậy lịch 01:00 — không ai dùng giờ đó. |
| TB | Log dbt in giờ UTC; wrapper ghi log theo lô | Đọc nhầm 18:08 là 6 giờ tối; nhìn log giữa chừng tưởng lượt đêm "đứng im". |
| TB | Truy vấn song song trên dq/bantin | Bộ nhớ chia sẻ container từng chỉ 64 MB → lỗi "could not resize shared memory segment". Pre-hook max_parallel_workers_per_gather = 0 vẫn giữ cho hai lớp này. |
| Thấp | Không incremental, 0 index trên mart/dq | Hôm nay chưa sao (dbt run ≤ 3'09"). Khi dữ liệu gấp đôi, thời gian ước tăng theo — chưa đo. |
Phần 12
Khoảng trống và phương án — chưa làm, chờ người quyết
Ba vấn đề nêu ngày 26/09, đặt cạnh số đo. Không có dòng code nào bị đổi để viết trang này.
| Vấn đề | Số đo nói gì | Kết luận |
|---|---|---|
| 1. Số T-1 sai, T-2 mới đúng | Ngày D đã được đọc lại lúc 01:00 D+2, tức sau buổi trưa D+1. Lần đọc 2 thêm 21–55 đơn và rút 7–16 đơn mỗi ngày. | Đúng — T-1 lúc 07:00 là số tạm. T-2 là số đã qua một lần sửa, chưa chắc là số cuối. |
| 2. Trong tháng số vẫn đổi do hoàn trả | Phiếu trả HDF mang ngày trả (0/70.347 lùi ngày) → được đọc bình thường. Cái bị bỏ sót là FAST sửa/xoá đơn cũ sau D+2 (trạng thái giao đổi trong 2–3 tuần; đơn rút). | Cơ chế quét lại đã có trong code (sync.py --lookback N: đọc lại, ghi đè, gắn/gỡ cờ rút) nhưng chỉ đang chạy với N = 2. |
| 3. Mã sản phẩm đổi liên tục | GetItemList đã kéo toàn bộ danh mục mỗi đêm (108.557 mã, 74–171 s). Mỗi đêm 15–42 mã biến khỏi FAST; 274 mã như vậy vẫn nằm trong dim_product, 0 dòng đơn dùng chúng. | Đã hơn "7 ngày nạp lại một lần". Thiếu duy nhất: cờ đánh dấu mã không còn trong FAST. |
Giá của việc đọc lại N ngày mỗi lượt
| N ngày đọc lại | Thêm so với hiện nay (trung vị) | Thêm (tệ nhất đã đo) | Tổng một lượt (ước) | Lời gọi FAST đơn + TT |
|---|---|---|---|---|
| 2 (hiện nay) | — | — | 9'07" (đo 26/09) | 4 |
| 3 | +0'33" | +0'53" | ~9'40" | 6 |
| 7 | +2'45" | +4'27" | ~12' | 14 |
| 14 | +6'36" | +10'40" | ~16' | 28 |
| 45 | +23'39" | +38'12" | ~33' | 90 |
| 90 | +48'24" | +78'10" | ~58' | 180 |
Tính = (N − 2) × đơn giá một ngày đo được (33 s trung vị, 53 s tệ nhất). Chưa chạy thử N > 2; ngày cũ nhiều đơn hơn thì chậm hơn. dbt run không đổi theo N.
Các phương án
| # | Phương án | Được gì | Phản biện |
|---|---|---|---|
| A | Lượt đêm đọc lại 7 ngày thay vì 2 (một tham số -Lookback 7 của task) | Sửa buổi trưa D+1, đơn rút và trạng thái giao trong tuần đều về hub. +~3 phút/đêm. | Số của 6 ngày gần nhất trên báo cáo sẽ còn nhích mỗi sáng — người đọc cần được báo trước. |
| B | Thêm một lượt quét sâu hằng tuần (vd. Chủ nhật 03:00) đọc lại 45 hoặc 90 ngày | Bắt thay đổi muộn trong tháng và tháng trước. 35–60 phút một tuần. | Chưa biết FAST chịu 90–180 lời gọi liền nhau thế nào. Hai task không được chồng giờ: khoá chỉ chặn bước ①, bước dbt của lượt bị bỏ qua vẫn chạy. |
| C | Đo trước khi chọn 90: một lượt so-sánh-chỉ-đọc 90 ngày gần nhất, không ghi DB, đếm mỗi ngày: đơn mới / đơn biến mất / đơn đổi nội dung | ĐÃ LÀM 26/09 01:30–02:11 — kết quả ở Phần 13. | Chỉ là ảnh chụp 10 ngày (15/09 → 26/09). |
| D | Ghi dấu vân tay nội dung (hash) mỗi chứng từ khi nạp, và số chứng từ "đổi nội dung" vào raw.sync_run | Mỗi lượt quét lại tự báo tìm được bao nhiêu thay đổi. | Đổi sync.py và bảng raw — phải qua nghiệm thu P1.6 lại. |
| E | Báo cáo coi T-2 là số chốt, T-1 ghi "tạm tính" (bản tin Teams + thẻ ngày dữ liệu Power BI) | Hết cảnh người đọc tin số T-1. Không đổi dbt. | Đổi hợp đồng số của bản tin (P2.9b) — phải đo lại và báo người nhận. |
| F | Thêm cột "mã còn trong FAST" vào dim_product (model dbt, R11) | Lọc được 274 mã đã biến mất. | 0 dòng đơn dùng các mã đó → giá trị thấp. |
| G | Khách hàng cũ: 373.507 khách lần đọc cuối 15/09 vì chỉ khách tạo trong 90 ngày được đọc lại | Khách đổi SĐT/tên sẽ về hub. API có tham số lọc theo ngày sửa (dFrom2/dTo2). | Tham số đó chưa thử với FAST — cần một lời gọi thử (chỉ đọc). |
Dời lượt đêm sang 07:00?
| 01:00 (hiện nay) | 07:00 | |
|---|---|---|
| Bắt được sửa buổi trưa D+1 cho T-1? | Không | Không — 07:00 vẫn trước buổi trưa. Muốn T-1 đúng trong ngày phải chạy thêm một lượt sau ~13:00. |
| Bản tin Teams 07:00 | Có số lúc 07:00 | Chạy cùng lúc với lượt đồng bộ → gửi thẻ "chưa về", 09:30 mới có số → phải dời bản tin sang ~07:30. |
| Cửa sổ vắng bảng khi dbt chạy | Không ai dùng | ~07:05–07:10, sát giờ làm. |
| Máy ngủ | Đã rơi 2 đêm (23→24, 24→25/09) | Chưa đo máy có thức lúc 07:00 không. |
| Tải lên FAST | Đo được (Phần 02) | Chưa đo ở giờ đó. |
Đổi giờ chỉ là đổi trigger của task (không đổi code). Lệnh dành cho người chạy, chưa chạy:
Set-ScheduledTask -TaskName "SIXDO-DataHub-NightlySync" -Trigger (New-ScheduledTaskTrigger -Daily -At 07:00) Set-ScheduledTask -TaskName "SIXDO-DataHub-TeamsDigest" -Trigger @((New-ScheduledTaskTrigger -Daily -At 07:30),(New-ScheduledTaskTrigger -Daily -At 09:30))
Phần 13
Kết quả phương án C — FAST còn sửa tiền tới ~8 tuần
26/09/2026 01:30–02:11: đọc lại từ FAST 90 ngày chứng từ (28/06 → 25/09), so với hub, không ghi gì vào DB (kết nối chế độ chỉ đọc, không đụng load_state, không gắn cờ). 180 lời gọi GET, 40,5 phút, 0 lỗi, 0 ngày nghi FAST trả thiếu.
Bộ so sánh đã tự kiểm trước: ngày 24/09 — hub vừa đọc lúc 01:04 — cho 0 khác biệt trên 1.555 chứng từ. Nghĩa là mọi khác biệt dưới đây là FAST đổi thật, không phải do cách so.
Theo nhóm ngày chứng từ
| Ngày chứng từ | Hub đọc lần cuối (tuổi chứng từ lúc đó) | Chứng từ FAST | Mới / đổi ngày vào | Biến mất | Đổi tiền | Chỉ đổi trạng thái giao | FAST − hub (đồng) | % tiền |
|---|---|---|---|---|---|---|---|---|
| 21–25/09 | D+1 / D+2 | 7.367 | 17 | 20 | 4 | 1.177 | −12.181.576 | −0,17% |
| 16–20/09 | D+1 / D+2, chỉ một đêm | 5.764 | 73 | 60 | 28 | 2.838 | −24.684.169 | −0,34% |
| 01–15/09 | 15–16/09 (1–14 ngày) | 13.677 | 105 | 37 | 11 | 1.202 | +83.482.115 | +0,50% |
| Tháng 8 | 15/09 (15–45 ngày) | 42.659 | 64 | 145 | 0 | 4 | −81.981.400 | −0,19% |
| 28/06–31/07 | 15/09 (46–79 ngày) | 49.214 | 0 | 0 | 1 | 0 | −25.000 | 0,00% |
"FAST − hub" = tổng tiền dòng (tt_nt, cùng cột amount mà [Doanh thu] dùng) của chứng từ FAST trả hôm nay, trừ phần hub đang tính (đã loại đơn bị cờ rút). Âm = hub đang tính thừa; dương = hub thiếu. Chưa đối chiếu trực tiếp với con số trên Power BI.
Theo tháng
| Tháng | Hub đang tính | FAST hôm nay | Chênh | Nghĩa là |
|---|---|---|---|---|
| 07/2026 | 61.455.903.055 | 61.455.878.055 | −25.000 | Coi như đã chốt |
| 08/2026 | 44.054.364.647 | 43.972.383.247 | −81.981.400 | Hub thừa 82 triệu (0,19%) |
| 09/2026 (01–25) | 31.095.927.265 | 31.142.543.635 | +46.616.370 | Hub thiếu 47 triệu (0,15%) |
FAST đã đổi những gì
| Kiểu thay đổi | Ví dụ đo được | Ảnh hưởng |
|---|---|---|
| Xoá rồi tạo lại số chứng từ mới | 12/08: 15 đơn TikTok số cũ biến mất, 15 đơn TikTok số mới xuất hiện; 22/08 tương tự (8 mất, 20 mới). | Hub đếm cả hai nếu không đọc lại. |
| Xoá hàng loạt | 26/08: 42 đơn website (DSCWEB) + 5 đơn TikTok biến mất = −57,4 triệu trong một ngày. | Hub đang tính thừa. |
| Thêm chứng từ lùi ngày | 03/09: FAST có thêm 50 đơn DSCWEB ghi ngày 03/09 = +68,3 triệu (sau lần hub đọc 15/09). | Hub đang thiếu. |
| Đổi ngày chứng từ | 13 đơn DSCWEB/DSCAPP chuyển từ ngày 17–22/08 sang 01/09 (lệch 10–15 ngày). | Doanh thu nhảy từ tháng 8 sang tháng 9. |
| Sửa dòng hàng | 48 chứng từ đổi details: 37 là phiếu HDF (đổi/trả) ở POS, đều đổi tiền (vd 1.547.000 → 49.987); còn lại 7 HDA POS, 3 DSCWEB, 1 O2O. | Tiền đổi, cùng số chứng từ. |
| Trạng thái giao vận | 5.226 chứng từ đổi trang_thai_vd, 469 đổi so_van_don; gần như hết khi chứng từ quá ~2 tuần tuổi. | Không đổi tiền; đổi báo cáo giao hàng/hoàn. |
Kênh của các chứng từ có thay đổi ngoài trạng thái giao (mới, mất, đổi ngày, đổi nội dung): TikTok 247, website DSCWEB 158, Shopee 99, POS 56, DSCAPP 11, O2O 1.
Kết luận từ số đo
Đề xuất sau khi đo (chưa làm, cần người duyệt)
| # | Việc | Chi phí đo được | Phản biện |
|---|---|---|---|
| 1 | Sửa dữ liệu hiện có: một lượt đồng bộ ghi thật sync_nightly.ps1 -Lookback 90 | ĐÃ LÀM 26/09 08:23–09:21 (xem khung dưới) | Số tháng 8 trên Power BI/bản tin giảm, tháng 9 tăng — người đọc cần được báo trước. |
| 2 | Lượt đêm đọc lại 14 ngày (-Lookback 14) | +~6,6 phút/đêm | Bắt trạng thái giao + đổi sớm; không bắt được xoá/thêm muộn kiểu tháng 8. |
| 3 | Thêm lượt hằng tuần đọc lại 90 ngày, lệch giờ với 01:00 (vd Chủ nhật 03:00) | ~50 phút/tuần | Tiền đổi tới ~8 tuần nên 60 ngày đã đủ theo số đo; 90 là biên an toàn cho một ảnh chụp mới 10 ngày. |
| 4 | Chạy lại phép so sánh chỉ đọc này mỗi tháng | ~40 phút, không ghi DB | Là cách duy nhất biết cửa sổ đọc lại còn đủ hay không. |
Đã sửa hub — 26/09/2026 08:23–09:21
| Tháng | Trước | Sau | Đổi |
|---|---|---|---|
| 07/2026 | 61.455.903.055 | 61.455.878.055 | −25.000 |
| 08/2026 | 44.054.364.647 | 43.957.951.247 | −96.413.400 |
| 09/2026 (01–25) | 31.095.927.265 | 31.160.223.139 | +64.295.874 |
infra\backups\datahub_2026-09-26_082152.dump (446,8 MB, pg_restore đọc lại được).-Lookback 90 đọc lại đủ 90 ngày đơn, nhưng phần thanh toán dừng ở ngày 55/90: FAST trả HTTP 500 ba lần cho thanh toán 22/08 lúc 09:06 (giờ bán hàng) → bộ ngắt mạch dừng, dbt không chạy.-NoFetch -Lookback 90 — phát hiện rút trên lô đơn vừa đọc (334 cờ rút gồm 55 cũ, 18 tái phát hành, 0 ngày nghi đọc thiếu) → dbt 46/46 → test chỉ đỏ cổng M5.7 → verify P1.6 ĐẠT.fact_sales tính từ dòng đơn; ảnh hưởng fact_payment và đối chiếu dq). Cần đọc lại ngoài giờ bán. Đã hẹn một task Windows chạy một lần lúc 02:30 ngày 27/09 (-Asof 2026-09-26 -Lookback 36: đơn + thanh toán 22/08–26/09 cùng phiên).Giới hạn của phép đo: đây là một ảnh chụp 10 ngày (hub đọc 15/09, FAST đọc 26/09) — có thể trùng đợt chốt sổ tháng 8. "Tháng 7 bằng 0" chỉ có nghĩa là không đổi trong 10 ngày này. Mọi việc ở bảng trên dừng lại nếu chứng chỉ TLS của FAST hết hạn ngày 10/10 mà không được gia hạn.