MSO Cloud · Documentation

Kho dữ liệu — kiến trúc medallion đa plane

Source: docs/architecture/reference/data-warehouse.md Updated 2026-09-21
On this page

Kho dữ liệu MSO Cloud là kiến trúc medallion (Bronze → Silver → Gold) phục vụ đồng thời nhiều plane dữ liệu có độ tin cậy khác nhau. Mọi con số trên dashboard truy được về đúng một tầng và đúng một helper.

Ba plane, một kho#

Ba plane dữ liệu không bao giờ trộn lẫn — trộn sai plane là lớp bug lặp lại nhiều nhất của hệ.

Plane Nguồn Đích Kiểm chứng
1st-party marketplace — sự thật (money plane) connector sàn + upload CSV/XLSX Bronze → Silver → Gold agg_* verify:data — reconciliation neo vào oracle NGOÀI code
1st-party marketing ads / web / search / CRM Bronze → Silver → Gold (semantics riêng, không blend với eCom) verify:data (assertion riêng theo grain)
3rd-party thị trường / social — ước tính dữ liệu thị trường 3rd, social listening 3rd store market_intel_* / social_signal_daily (bỏ qua Bronze) KHÔNG vào verify:data — badge "ước tính"

Chỉ hai plane 1st-party đi qua medallion. Plane ước tính hội tụ về một persister riêng (persistMarketIntelSnapshot), supersede theo natural key, và chỉ vào hệ ra quyết định ở vai trò TÍN HIỆU — không bao giờ nuôi KPI 1st-party.

Bronze — thô, append-only, đã khử trùng lặp#

Tầng tiếp nhận. Mọi ingest (connector hoặc upload) đổ vào Bronze trước:

  • raw_events — append-only, mỗi row ký sha256(payload); ON CONFLICT DO NOTHING khử trùng lặp. Chỉ ba stream canonical: orders | creators | inventory.
  • import_staging_rows — mọi stream phi-canonical (kho, payout, hoàn trả, chi phí quảng cáo, …). Bronze giữ đúng ba stream canonical; phần còn lại đợi promoter riêng.
  • connector_watermarkslast_cursor + high_water_mark; chỉ tiến khi cả job thành công (một lần chạy lỗi KHÔNG làm tiến watermark).
  • sync_rejected_rows — row rớt Zod validation.
  • sync_dead_letters — lỗi permanent vào DLQ một lần, và tự lành khi một lần sync sau đó thành công.

Bronze là bất biến: chế độ replay re-derive lại Silver từ Bronze mà không phải trả phí gọi API nguồn lần nữa.

Silver — fact typed, re-derive được#

"Promoter" per-stream trong apps/worker/src/promoters/ chuyển Bronze thành fact typed: orders, order_items, creator_performance_daily, inventory_snapshots, … Promoter idempotent — chạy lại re-derive Silver một cách tất định từ Bronze. Thứ tự chạy có ý nghĩa: nội dung API phải được promote vào creator-daily TRƯỚC khi rollup creator chạy, nếu không rollup đọc thiếu.

Gold — KPI materialized, read-path của dashboard#

refreshOrgMaterializations tính các bảng agg_* — read-path nhanh mà dashboard và report đọc. Một số bảng tiêu biểu:

  • agg_org_day — KPI theo org theo ngày.
  • agg_org_sku_day — theo SKU theo ngày.
  • agg_org_creator_day — theo creator theo ngày.
  • agg_content_dim_day — Audience CI, phân phối theo dimension.

Danh sách đầy đủ mở rộng theo pillar; mỗi bảng agg mới tuân cùng một hợp đồng key và refresh.

Mọi key của một bảng agg đều chứa brand_id. NULL brand hiển thị dưới MỌI brand, nên một write thiếu brand sẽ rò rỉ chéo brand; refresh brand-scoped DELETE theo (org_id, brand_id). Cube và tRPC chỉ đọc Gold (analytics ad-hoc nặng đi qua Cube đặt trên Silver/Gold).

Sơ đồ — luồng dữ liệu medallion#

flowchart TB
  subgraph SRC["Nguồn"]
    direction LR
    MP["Marketplace API<br/>+ upload"]
    MK["Marketing · CRM<br/>· Sheets"]
    CT["Social listening<br/>(content)"]
    TP["Thị trường 3rd"]
  end

  ING["Ingest — apps/worker<br/>rate-limit → Zod validation → dedupe sha256"]

  subgraph BRZ["Bronze — append-only"]
    direction LR
    RAW["raw_events<br/>orders | creators | inventory"]
    STG["import_staging_rows<br/>stream phi-canonical"]
  end

  PROM["Promoters — idempotent, re-derive tất định từ Bronze"]

  SLV["Silver — fact typed<br/>orders · order_items · creator_performance_daily · inventory_snapshots"]

  GLD["Gold — agg_* KPI, mọi key có brand_id<br/>refreshOrgMaterializations"]

  SRV["Cube semantic layer · tRPC v11 · RSC"]

  DASH["Dashboard / báo cáo"]
  EST["market_intel_* · social_signal_daily<br/>badge: ước tính"]

  MP --> ING
  MK --> ING
  CT --> ING
  ING --> BRZ
  BRZ --> PROM
  PROM --> SLV
  SLV --> GLD
  GLD --> SRV
  SRV --> DASH

  TP -.->|"bỏ qua Bronze"| EST
  EST -.->|"chỉ tín hiệu, không nuôi KPI 1st-party"| SRV

Plane ước tính (market_intel_*) cắm thẳng vào store riêng, không đi qua Bronze, và luôn mang badge "ước tính". Nó ở ngoài verify:data theo thiết kế — một ước lượng thị trường không có oracle ngoài để đối chiếu, nên không được đặt cạnh con số 1st-party như thể cùng độ tin cậy.

Sơ đồ — thứ tự promoter#

flowchart TB
  A["raw_events / import_staging_rows"]
  B["promote orders → orders · order_items"]
  C["promote creators → creator_performance_daily"]
  D["promoteApiContentToCreatorDaily"]
  E["refreshAggOrgCreatorDay"]
  F["refreshOrgMaterializations → agg_*"]
  A --> B
  A --> C
  C --> D
  D --> E
  B --> F
  E --> F

Thứ tự là bất biến: nội dung API vào creator-daily TRƯỚC rollup creator; mọi rollup chạy trước khi materialize Gold.

Vì sao các con số đáng tin — các luật độ tin cậy#

Kho dữ liệu này khác một warehouse thông thường ở chỗ mỗi con số đều bị ràng buộc bởi một bất biến được cưỡng chế:

  • VND là số nguyên 64-bit (bigint), không thập phân. Không bao giờ dùng float/numeric cho tiền; format qua một helper duy nhất.
  • Mọi ngày theo Asia/Ho_Chi_Minh (UTC+7), end-to-end. Con số trên màn hình PHẢI bằng con số khách tải về. Không convert UTC trên đường ngày; bucket SQL neo theo ICT — tránh rò biên ngày (nửa đêm UTC = 07:00 ICT kéo nhầm ngày tháng bên cạnh vào khoảng lọc).
  • is_sample ở khắp nơi. Mỗi bảng mang cờ này; seed một brand không được đụng brand khác. Dữ liệu mẫu và dữ liệu thật không bao giờ lẫn.
  • Một metric = một helper. Mỗi metric có đúng một định nghĩa sau đúng một hàm (ví dụ merge creator CSV/API là đúng một impl); mọi surface gọi cùng helper đó — không có đường tính thứ hai để lệch.
  • Mỗi metric surface nợ một reconciliation assertion. Headline PHẢI bằng SUM(dòng bảng render); rolling PHẢI bằng period cho cùng cửa sổ; không double-count khi merge. Giá trị EXPECTED neo vào oracle NGOÀI code (export thật của khách / ground-truth đóng băng), không bao giờ là một phép re-derive trong chính code. Một discrepancy không mặc nhiên là bug — phải xác nhận semantics với oracle trước khi "sửa".

Plane ước tính có bộ luật riêng: không có mẫu số bịa, không multiplier bịa; ở trần đọc thì render "≥ N" thay vì một con số cắt cụt.

Đọc thêm#

  • architecture-detail.md — kiến trúc phân tầng chi tiết (Bronze → Silver → Gold trong ngữ cảnh toàn hệ).
  • data-validation.md — hợp đồng kiểm chứng dữ liệu tự động.