Asia Business Builders — Merchant Banking Kiến trúc dbt đang chạy Phụ lục 4 · đo 26/09 · cập nhật 08/10/2026

Phụ lục 4 của Kiến trúc SIXDO Data Hub · lớp biến đổi · hiện trạng

dbt trong hub chạy thế nào mỗi đêm

Một lượt 01:00: đọc FAST → dựng lại 46 model từ đầu → kiểm → báo sức khoẻ. Và mỗi ngày chứng từ chỉ được đọc lại đúng hai lần.

Trang này tả cái đang chạy trên máy hub, không phải cái dự định. Mọi con số đo từ log logs/sync_*.log 18–26/09 và từ cơ sở dữ liệu ngay sau lượt đêm 26/09/2026 (xong 01:09:09). Chỗ nào không đo được thì ghi là không đo được.

← Về tài liệu chính: Kiến trúc SIXDO Data Hub
9'07"lượt đêm 26/09, từ 01:00:02 tới 01:09:09
46model dbt: 27 bảng + 19 view, dựng lại hết mỗi đêm
74test dbt; 1 đỏ có chủ đích (cổng M5.7)
2lần một ngày chứng từ được đọc lại, rồi thôi

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.

Luồng một lượt đồng bộ đêm Task Scheduler gọi sync_nightly.ps1 lúc 01:00. Bước 1 sync.py đọc FAST vào schema raw. Bước 2 dbt run dựng staging, mart, dq. Bước 3 dbt test. Bước 4 verify và bước 5 health. Power BI, bản tin Teams, health và DBeaver đọc mart và dq. Task Scheduler 01:00 mỗi ngày …-NightlySync ① ingest/sync.py FAST → schema raw 5'32" đêm 26/09 ② dbt run 46 model, dựng lại hết 2'56" ③ dbt test 74 test · 1 đỏ có chủ đích 0'20" ④ verify ⑤ health cổng thật của đêm 0'17" FAST ERP pos.sixdo.vn:8080 NGOÀI máy chỉ gọi GET 13 endpoint đọc chứng chỉ TLS hết hạn 10/10/2026 PostgreSQL 16 · container datahub-pg · chỉ nghe loopback raw 16 bảng JSONB orders 2,00 tr order_payments items 108.830 customers 386 k + 9 danh mục nhẹ sổ sách hub: load_state doc_status sync_run upsert, không xoá, không giữ bản cũ staging 14 view tách JSON trim mọi mã HDA / HDF ép kiểu an toàn không chứa dữ liệu riêng mart 12 bảng dim/fact 5 view cho BI 4 bảng bantin fact_sales = hợp đồng với BI xoá và dựng lại mỗi đêm dq 11 bảng đối chiếu tiền khoá ngoại cổng M5.7 Power BI Desktop bi_reader · bi_full Bản tin Teams 07:00 · 09:30 Health 09:00 · 15:00 · 21:00 DBeaver · chatbot đọc tay · P2.10

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ướcLàm gìHỏng thì sao
① ingest/sync.pyKhoá 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 runDự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 test74 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.617 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.pyNhị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ộ.


ĐêmBắt đầu① FAST + sync.py② dbt rundbt tự báo③ dbt test④+⑤TổngKết quả
18/0901:00:023'23"2'34"145,5 s0'10"0'18"6'34"Đạt · lượt delta thật đầu tiên
19/0903:43:26——————Chết giữa chừng (máy ngủ), sync_run treo tới lượt sau dọn
20/0901:00:025'08"—————dbt kẹt khoá rồi bị đóng cửa sổ 01:05
20/0902:20:443'43"2'26"142,2 s0'19"0'18"6'50"Đạt (chạy tay lại)
21/0901:00:022'12"—————sync.py lỗi (container tắt) → không chạy dbt
22/0900:24:433'35"3'09"183,4 s0'11"0'19"7'17"Đạt (chạy tay)
22/0901:00:013'32"1'52"109,6 s0'09"0'16"5'52"Đạt
23/0901:00:033'17"1'44"97,0 s0'11"0'18"5'32"Đạt
24/0905:55:034'28"2'27"140,8 s0'15"0'24"7'47"Đạt · chạy bù sau khi máy dậy
25/0902:04:204'09"2'33"147,6 s0'18"0'21"7'25"Đạt · chạy bù (máy ngủ tới 02:03)
26/0901:00:025'32"2'56"167,4 s0'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ọiSố lần đoTrung vịNhanh nhấtChậm nhấtBản ghi trả về (trung vị)
GetItemList (toàn bộ sản phẩm)8103,4 s74,0 s170,6 s108.014
GetCustomersList (cửa sổ 90 ngày)827,0 s23,2 s56,8 s11.910
GetListVoucherInfo (đơn, 1 ngày)1617,2 s12,0 s30,8 s1.246
GetListVoucherPaymentInfo (TT, 1 ngày)1610,6 s6,2 s14,6 s1.246
Phát hiện rút (SQL trên hub, 1 ngày)165,2 s3,2 s7,9 s—
9 danh mục nhẹ cộng lại (kể cả nghỉ giữa các lời gọi)1 đêm33,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

ModelTrung vịChậm nhấtSố đêm đoGhi chú
mart.bt_khach_mua_dau55,1 s57,7 s2lớp bản tin, mới từ 25/09
mart.fact_order_line49,5 s82,8 s85,1 triệu dòng, tách từ JSON
mart.bt_kpi_ngay48,2 s50,5 s2lớp bản tin
dq.dq_order_recon40,2 s57,0 s8đối chiếu tiền từng đơn
dq.dq_fk_check37,9 s54,0 s8kiểm khoá ngoại
mart.fact_order26,6 s53,9 s82,0 triệu đơn
dq.dq_summary24,9 s31,3 s8bảng nghiệm thu
dq.dq_sync_summary21,1 s27,3 s8
mart.bt_khach_ngay20,9 s22,8 s2lớp bản tin
dq.dq_exception20,1 s32,2 s8

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 rawEndpoint FASTMỗi đêm đọc gìKhoáDòng hiện có
ordersGetListVoucherInfo2 ngày chứng từ gần nhất (hôm kia, hôm qua), từng ngày một lời gọistt_rec2.001.727
order_paymentsGetListVoucherPaymentInfocùng 2 ngày, ngay sau đơnstt_rec2.001.723
itemsGetItemListtoàn bộ danh mục (ngày tạo 2015-01-01 → hôm nay)ma_vt108.830
FAST trả 108.557
customersGetCustomersListchỉ khách tạo trong 90 ngày gần nhấtma_kh386.290
đêm nay đọc 11.920
item_propertiesGetPropertiesItemList × 9 loạitoàn bộType:code865
salespeopleGetSalesEmployeeListtoàn bộma_nvbh2.956
stores · sitesGetStoreList · GetSiteListtoàn bộma_bp · ma_kho94 · 329
provinces · districts · wards3 endpoint địa lýtoàn bộkhoá ghép63 · 704 · 10.467
payment_types · nationalities2 endpointtoàn bộma_tt · ma_qt13 · 13
load_stateSổ 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_statusCờ 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_runMộ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_orderraw.orders1 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_lineraw.ordersTá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_paymentraw.order_payments · raw.payment_typesTách Paymentdetails[]; danh mục hình thức thanh toán.
stg_product · stg_product_propertyraw.items · raw.item_propertiesGiữ mã thuộc tính API trả sẵn (màu, size, dòng, mùa…), không cắt chuỗi mã.
stg_customerraw.customersCó PII (tên, SĐT, email) — chỉ vai bi_pii đọc được ở tầng mart.
7 view danh mụcraw tương ứngstg_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ómModelHạt · điểm đáng nhớ
Fact (bảng)fact_order1 dòng / đơn. Thêm is_withdrawn, is_replaced, is_excluded từ raw.doc_status.
fact_order_line1 dòng / dòng hàng (~5,1 triệu). Mang is_excluded ở hạt dòng.
fact_payment1 dòng / dòng thanh toán.
Dim (bảng)dim_customerKhá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_datedim_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 BIfact_salesHợ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ềndim_customer_bi, dim_customer_pii, fact_order_bi, dim_salesperson_biBả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_ngaySố 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_reconMỗ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_summaryBả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_tomtatKhoá 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_tomtatCổ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_doichieuLớ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.


Phụ thuộc giữa các model trên đường doanh thu raw.orders sinh stg_order và stg_order_line, thành fact_order và fact_order_line. raw.order_payments thành stg_payment_line và fact_payment. raw.doc_status gắn cờ is_excluded vào ba fact. fact_order, fact_order_line và dim_product tạo view fact_sales, từ đó dựng lớp bantin. Ba fact tạo dq_order_recon rồi dq_summary. RAW STAGING (view) MART (bảng) VIEW · BANTIN · DQ raw.orders raw.order_payments raw.doc_status raw.items + item_properties raw.customers stg_order stg_order_line stg_payment_line stg_product + _property stg_customer fact_order fact_order_line fact_payment dim_store · dim_salesperson dim_payment · dim_geo dim_date · dim_channel dim_nationality dim_product dim_customer fact_sales (view) hợp đồng với Power BI bantin: bt_kpi_ngay, bt_khach_* bản tin Teams v2 · chatbot dq_order_recon → dq_summary · dq_m57_* · dq_q22 → dq_exception · dq_doc_status dim_customer_bi (view) · _pii (view) cờ rút / tái phát hành → is_excluded

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.


Dòng thời gian đọc lại một ngày chứng từ D Ngày D bán hàng. 01:00 ngày D+1 đọc lần 1, số tạm. Khoảng trưa D+1 kế toán sửa, hoàn, đổi. 01:00 ngày D+2 đọc lần 2 và là lần cuối. Sau D+2 hub không đọc lại ngày D. Phiếu trả HDF ghi ngày trả, trung vị 8 ngày sau ngày bán, được đọc như chứng từ mới. FAST vẫn cập nhật trạng thái giao vận trên đơn cũ nhiều tuần sau. Sau D+2: hub KHÔNG đọc lại ngày D nữa Ngày D bán hàng Lần đọc 1 01:00 D+1 số tạm Trưa D+1 sửa · hoàn · đổi Lần đọc 2 01:00 D+2 = lần cuối D+8 phiếu trả HDF (ngày mới, vẫn đọc được) D+3 … D+30 FAST đổi trạng thái giao, rút đơn — hub không thấy Đo được (đọc lần 2 so với lần 1, các ngày 21–24/09): • FAST trả thêm +21 đến +55 đơn mới cho cùng một ngày (21/09: 1.158 → 1.192 · 22/09: 1.468 → 1.523 · 24/09: 1.534 → 1.555) • 7–16 đơn/ngày bị FAST rút (0,46–1,33% số đơn, 0,44–1,20% tiền) — toàn bộ phát hiện ở lần đọc 2, đa số là đơn sàn/online • Các ngày 16–20/09 chỉ được đọc trong MỘT đêm (đêm lỗi / máy ngủ) → 0 đơn rút được phát hiện cho 5 ngày đó

Lần đọc cuối của từng ngày chứng từ tháng 9

Ngày chứng từĐược đọcLần đọc cuốiNghĩa là
01–14/091 lần15/09 22:40 (nạp lần đầu)Sửa của FAST sau 15/09 không có trong hub
15/092 lần16/09 21:00
16–17/091 lần18/09 01:02Lượt 19/09 chết → không có lần đọc 2
18–19/091 đêm20/09 02:23Hai lượt cùng một đêm (01:00 và chạy lại 02:20)
20/091 đêm22/09 01:02Lượt 21/09 lỗi
21/09 → 24/092 đêmD+2 (23/09 … 26/09)Đúng thiết kế
25/091 lần tới giờ26/09 01:04Lầ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ánHub đọc lần cuối sauDeliveredĐang đi đường*% đang đi đường
03/086 tuần3.7082386,0%
10/085 tuần2.6781816,3%
17/084 tuần6.2363425,2%
24/083 tuần5.3734427,6%
31/089–15 ngày1.91826011,9%
07/091–8 ngày2.72288624,6%
14/091–2 ngày4633.25287,5%
21/091–2 ngày9573.14776,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 đượcAi dùng
bi_reader23 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_fullbi_reader + bi_piiPower 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ứcBẫyKịch bản hỏng
CaoChứng chỉ TLS pos.sixdo.vn hết hạn 10/10/2026Từ 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).
CaoMá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ề".
CaoTruy 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.
Caodbt run --select không kèm +Xem Phần 10 — view hợp đồng BI biến mất, dbt vẫn PASS.
TBCửa sổ vắng bảng lúc dbt chạyKhoả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ờ đó.
TBLog 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".
TBTruy vấn song song trên dq/bantinBộ 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ấpKhông incremental, 0 index trên mart/dqHô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 đúngNgà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ụcGetItemList đã 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ạiThê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
ALượ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.
BThê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àyBắ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).
DGhi 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_runMỗ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.
EBá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.
FThê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.
GKhá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ạiKhá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ôngKhô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:00Có số lúc 07:00Chạ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ạyKhô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ừ FASTMới / đổi ngày vàoBiến mấtĐổi tiềnChỉ đổi trạng thái giaoFAST − hub (đồng)% tiền
21–25/09D+1 / D+27.367172041.177−12.181.576−0,17%
16–20/09D+1 / D+2, chỉ một đêm5.7647360282.838−24.684.169−0,34%
01–15/0915–16/09 (1–14 ngày)13.67710537111.202+83.482.115+0,50%
Tháng 815/09 (15–45 ngày)42.6596414504−81.981.400−0,19%
28/06–31/0715/09 (46–79 ngày)49.2140010−25.0000,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ángHub đang tínhFAST hôm nayChênhNghĩa là
07/202661.455.903.05561.455.878.055−25.000Coi như đã chốt
08/202644.054.364.64743.972.383.247−81.981.400Hub thừa 82 triệu (0,19%)
09/2026 (01–25)31.095.927.26531.142.543.635+46.616.370Hub thiếu 47 triệu (0,15%)

FAST đã đổi những gì

Kiểu thay đổiVí dụ đo đượcẢnh hưởng
Xoá rồi tạo lại số chứng từ mới12/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ạt26/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ày03/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àng48 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ận5.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

T-2Cũng chưa phải số chốt: nhóm hub đọc ở D+2 vẫn lệch −0,17% chỉ vài ngày sau.
~2 tuầnTrạng thái giao vận ngừng đổi.
~8 tuầnTiền còn đổi tới chứng từ 45–56 ngày tuổi (ngày 01/08: 10 chứng từ). Chứng từ từ 54 ngày tuổi trở lên: 0 thay đổi; ngày 24/07 (53 ngày) có 1 chứng từ −25.000.
Hiện tạiHub lệch FAST −82 triệu tháng 8 và +47 triệu tháng 9 — sửa được bằng cơ chế sẵn có.

Đề xuất sau khi đo (chưa làm, cần người duyệt)

#ViệcChi phí đo đượcPhản biện
1Sử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.
2Lượt đêm đọc lại 14 ngày (-Lookback 14)+~6,6 phút/đêmBắt trạng thái giao + đổi sớm; không bắt được xoá/thêm muộn kiểu tháng 8.
3Thê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ầnTiề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.
4Chạy lại phép so sánh chỉ đọc này mỗi tháng~40 phút, không ghi DBLà 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ángTrướcSauĐổi
07/202661.455.903.05561.455.878.055−25.000
08/202644.054.364.64743.957.951.247−96.413.400
09/2026 (01–25)31.095.927.26531.160.223.139+64.295.874
1Sao lưu trước: infra\backups\datahub_2026-09-26_082152.dump (446,8 MB, pg_restore đọc lại được).
2Lượt -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.
3Hoàn tất không gọi FAST thêm: -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.
4Kiểm: 100% thay đổi phương án C tìm thấy đã vào hub. Phần khác thêm so với 02:11 là FAST đổi tiếp trong buổi sáng (25/09 +27,2 triệu; tháng 8 rút thêm −14,4 triệu).
5Còn thiếu: thanh toán 22/08–23/09 chưa đọc lại (doanh thu không ảnh hưởng — 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.