Làm thế nào để phân tích hành trình mua hàng của khách hàng hiệu quả nhất?

Mục lục

Giới thiệu

Theo báo cáo của Cục Thương mại điện tử và Kinh tế số (Cục TMĐT VN) năm 2024, 73% doanh nghiệp Việt Nam gặp khó khăn trong việc xác định kênh marketing nào thực sự mang lại chuyển đổi. Mô hình last‑click truyền thống đang làm lệch lạc bức tranh phân bổ ngân sách, dẫn đến lãng phí và hiệu quả thấp. Google Tempo 2025 chỉ ra rằng các doanh nghiệp áp dụng data‑driven attribution đạt mức tăng 15% ROAS so với cách làm cũ.

Trong bài viết này, chúng ta sẽ đi sâu vào một phương pháp attribution mạnh mẽ: sử dụng Markov Chains – mô hình stochastic để phân tích chuỗi tương tác (path‑to‑purchase) của khách hàng. Bằng cách coi mỗi kênh là một trạng thái, chúng ta có thể tính toán xác suất chuyển đổi tổng thể và đánh giá đóng góp thực sự của từng kênh thông qua hiệu ứng loại bỏ (removal effect). Bài viết không chỉ trình bày lý thuyết mà còn cung cấp đầy đủ hướng dẫn triển khai thực tế: từ kiến trúc hệ thống, chi phí, timeline, tài liệu bàn giao, cho đến code mẫu chạy được ngay.

🛡️ Lưu ý: Việc thu thập dữ liệu người dùng phải tuân thủ GDPR và Luật bảo vệ dữ liệu cá nhân của Việt Nam. Cần có cơ chế ẩn danh và xin phép khi cần.


Tổng quan về Path‑to‑Purchase và Markov Chains

Path‑to‑Purchase là gì?

Path‑to‑Purchase mô tả hành trình của khách hàng từ lúc nhận biết sản phẩm/dịch vụ cho đến khi thực hiện chuyển đổi (mua hàng, điền form, v.v.). Trong thế giới đa kênh hiện nay, một khách hàng có thể tiếp xúc với nhiều điểm chạm (touchpoint) như:

  • Organic Search (tìm kiếm tự nhiên)
  • Paid Search (quảng cáo tìm kiếm)
  • Social Media (Facebook, Instagram, TikTok)
  • Email Marketing
  • Direct (truy cập trực tiếp)
  • Referral (giới thiệu từ website khác)
  • Display Ads (quảng cáo hiển thị)

Các mô hình attribution truyền thống (last‑click, first‑click, linear, time‑decay) đều có những hạn chế vì chúng gán toàn bộ hoặc phần trăm cố định cho một số kênh, không phản ánh đúng tương tác thực tế.

Markov Chains trong Attribution

Markov Chain là một quá trình ngẫu nhiên mà trạng thái tiếp theo chỉ phụ thuộc vào trạng thái hiện tại. Ứng dụng vào attribution, chúng ta định nghĩa:

  • Trạng thái: mỗi kênh marketing, cộng thêm hai trạng thái hấp thụ (absorbing states): conversion (chuyển đổi) và dropout (rời bỏ).
  • Xác suất chuyển tiếp: xác suất để người dùng di chuyển từ kênh A sang kênh B, hoặc từ kênh cuối cùng sang conversion/dropout.

Từ dữ liệu lịch sử, ta đếm số lần chuyển tiếp giữa các trạng thái và xây dựng ma trận chuyển tiếp (P):

[
P = \begin{pmatrix}
p_{11} & p_{12} & \dots & p_{1n} \
p_{21} & p_{22} & \dots & p_{2n} \
\vdots & \vdots & \ddots & \vdots \
p_{n1} & p_{n2} & \dots & p_{nn}
\end{pmatrix}
]

Trong đó (p_{ij}) là xác suất chuyển từ trạng thái (i) sang trạng thái (j).

Chia ma trận thành các phần:

  • (Q): ma trận con cho các trạng thái không hấp thụ (transient) → transient.
  • (R): ma trận từ transient sang absorbing.

Tính ma trận cơ bản (fundamental matrix):

[
N = (I – Q)^{-1}
]

Xác suất hấp thụ (absorption probabilities):

[
B = N R
]

Xác suất chuyển đổi tổng thể từ trạng thái bắt đầu (start) là phần tử tương ứng trong (B).

Removal Effect và đóng góp của kênh

Để đánh giá tầm quan trọng của một kênh, ta loại bỏ nó khỏi đồ thị (xóa mọi cạnh liên quan) và tính lại xác suất chuyển đổi. Hiệu ứng loại bỏ của kênh (k) là:

[
\text{Removal Effect}k = \frac{\text{Conv}{\text{all}} – \text{Conv}{\text{without }k}}{\text{Conv}{\text{all}}}
]

Sau đó chuẩn hóa các giá trị này để tổng bằng 100% → phần trăm đóng góp của từng kênh. Phương pháp này tương đương với việc tính Shapley value cho các kênh.

Ưu điểm: Mô hình Markov tự động học từ dữ liệu, không cần giả định trước, phù hợp với hành trình phức tạp nhiều kênh.


Dữ liệu và công cụ cần thiết

Dữ liệu cần thu thập

Để xây dựng mô hình, bạn cần dữ liệu về chuỗi tương tác của người dùng trước khi chuyển đổi (hoặc không). Cụ thể:

  • user_id (hoặc client_id) để theo dõi xuyên suốt các phiên.
  • session_id để phân nhóm các sự kiện trong một phiên.
  • timestamp thời gian xảy ra.
  • channel – kênh marketing tương ứng (phân loại dựa trên UTM, nguồn referral, v.v.).
  • event_type (ví dụ: page_view, purchase).
  • conversion_value (giá trị đơn hàng, nếu có).

Nguồn dữ liệu phổ biến:

  • Google Analytics 4 (GA4) với tính năng export sang BigQuery.
  • Adobe Analytics, Mixpanel, hoặc tự thiết kế event tracking.
  • Dữ liệu từ CRM, email marketing platforms (cần tích hợp qua API).

Công cụ đề xuất

Dưới đây là workflow tổng quan từ thu thập đến báo cáo:

[Khách hàng] 
     |
     v
[Website/App] --(sự kiện)--> [Google Tag Manager] 
     |
     v
[Google Analytics 4] --(export)--> [BigQuery]
     |
     v
[Airflow DAG] --(trigger)--> [Python Model] 
     |
     v
[BigQuery Results] --(kết nối)--> [Looker Studio]
     |
     v
[Dashboard cho Marketing]

Các thành phần chính:

  • Data Warehouse: BigQuery (hoặc Redshift, Snowflake) – lưu trữ raw events.
  • Orchestration: Apache Airflow (Cloud Composer) – điều phối ETL hàng ngày.
  • Xử lý mô hình: Python với thư viện pandas, numpy, scipy.
  • Visualization: Looker Studio, Tableau, hoặc Metabase.

Xây dựng mô hình Markov Chains từ dữ liệu thực tế

Bước 1: Chuẩn bị dữ liệu – Sessionization

Từ bảng raw events, bạn cần nhóm các sự kiện thành các user journey. Mỗi journey là một chuỗi các kênh theo thứ tự thời gian, kết thúc bằng conversion (nếu có mua) hoặc dropout.

Ví dụ SQL (BigQuery) để tạo bảng journeys:

-- Xem full code ở phần Code mẫu
WITH sessions AS (...),
journeys AS (
  SELECT
    user_id,
    session_id,
    ARRAY_AGG(channel ORDER BY ts) as path,
    MAX(IF(event_type = 'purchase', 1, 0)) as converted
  FROM ...
)
SELECT * FROM journeys;

Bước 2: Đếm các chuyển tiếp

Với mỗi journey, ta đếm:

  • start → kênh đầu tiên
  • kênh i → kênh i+1
  • kênh cuối → conversion (nếu converted) hoặc dropout

Kết quả là một dictionary (from_state, to_state) -> count.

Bước 3: Xây dựng ma trận chuyển tiếp và tính xác suất chuyển đổi tổng thể

Chuyển đổi counts thành ma trận xác suất (P). Sau đó tính (Q, R) và dùng công thức đã nêu để có xác suất chuyển đổi từ start.

Bước 4: Tính Removal Effect

Lần lượt loại bỏ từng kênh (xóa mọi transition có liên quan đến kênh đó), tính lại xác suất chuyển đổi. Phần trăm đóng góp = (original – new) / original, sau đó chuẩn hóa.

Bước 5: Lưu kết quả và cập nhật dashboard

Kết quả đóng góp của các kênh được lưu vào bảng BigQuery, từ đó kết nối với Looker Studio để hiển thị trực quan.

🐛 Lưu ý: Mô hình cần được chạy định kỳ (hàng ngày/tuần) để cập nhật theo dữ liệu mới. Cần xử lý incremental để tiết kiệm chi phí.


Kiến trúc hệ thống và chi phí

So sánh các tech stack

Dưới đây là 4 lựa chọn công nghệ phổ biến để triển khai hệ thống phân tích Markov attribution:

Tiêu chí Google Cloud Platform (GCP) AWS Azure Open‑source (self‑hosted)
Lưu trữ BigQuery Redshift Azure Synapse PostgreSQL / MariaDB
Xử lý / ETL Dataflow, Python trên Compute Engine Glue, EMR Azure Data Factory, Databricks Python scripts, Spark tự quản lý
Orchestration Cloud Composer (Airflow) MWAA (Managed Workflows for Airflow) ADF hoặc Airflow trên AKS Airflow tự cài
BI / Visualization Looker Studio (free) hoặc Looker (trả phí) QuickSight Power BI Metabase
Chi phí ước tính / tháng $500 – $1.000 (tùy volume) $600 – $1.200 $550 – $1.100 $300 – $500 (server + nhân lực)
Độ phức tạp triển khai 3 (trung bình) 4 4 5 (cao)
Khả năng mở rộng Cao Cao Cao Trung bình
Hỗ trợ tại Việt Nam Community
Phù hợp Doanh nghiệp vừa và lớn Doanh nghiệp lớn Doanh nghiệp dùng Microsoft ecosystem Doanh nghiệp nhỏ, đội kỹ thuật mạnh

💡 Khuyến nghị: Đối với đa số doanh nghiệp tại Việt Nam, stack GCP + BigQuery + Airflow Composer + Looker Studio là tối ưu về chi phí, dễ tích hợp và scale.

Chi phí triển khai và vận hành 30 tháng

Dưới đây là bảng chi tiết chi phí cho stack GCP (bao gồm hạ tầng, nhân sự vận hành) trong 30 tháng (2,5 năm). Giả định doanh nghiệp có quy mô trung bình, dữ liệu tăng 20% mỗi năm, nhân sự tăng lương 5% mỗi năm.

Hạng mục Chi tiết Năm 1 (tháng) Năm 1 (cả năm) Năm 2 (tháng) Năm 2 (cả năm) Năm 3 (tháng) Năm 3 (6 tháng) Tổng 30 tháng
Hạ tầng Cloud
BigQuery (storage + query) $90 $1,080 $108 $1,296 $130 $780 $3,156
Cloud Composer (Airflow) $300 $3,600 $300 $3,600 $300 $1,800 $9,000
Compute Engine (VM nhỏ) $30 $360 $30 $360 $30 $180 $900
Network egress $10 $120 $10 $120 $10 $60 $300
Phần mềm & Licenses Looker Studio (free) $0 $0 $0 $0 $0 $0 $0
Nhân sự vận hành
Data Engineer (0.5 FTE) $2,000 $24,000 $2,100 $25,200 $2,205 $13,230 $62,430
Data Scientist (0.25 FTE) $1,500 $18,000 $1,575 $18,900 $1,654 $9,924 $46,824
DevOps (0.2 FTE) $1,000 $12,000 $1,050 $12,600 $1,103 $6,618 $31,218
Training & Support Khóa huấn luyện, hỗ trợ $42 $500 $17 $200 $33 $200 $900
Tổng cộng hàng tháng $4,972 $5,190 $5,465
Tổng cộng theo năm $59,660 $62,276 $32,792 $154,728

Lưu ý: Chi phí nhân sự có thể thay đổi tùy theo công ty và vị trí địa lý. Bảng trên dựa trên mức lương trung bình tại TP.HCM cho vị trí tương ứng.


Kế hoạch triển khai chi tiết

Dự án được chia thành 8 phase lớn, tổng thời lượng 17 tuần. Mỗi phase có mục tiêu, danh sách công việc con, người phụ trách và phụ thuộc rõ ràng.

Phase 1: Phân tích yêu cầu và thu thập dữ liệu (2 tuần)

Mục tiêu: Xác định business goals, KPI, các kênh cần theo dõi, đánh giá dữ liệu hiện có.

Công việc:
1. Workshop với marketing team để hiểu hành trình khách hàng và các kênh hiện tại.
2. Đánh giá dữ liệu hiện có từ GA4, CRM, quảng cáo.
3. Xác định các metrics cần đo: conversion rate, giá trị đơn hàng, v.v.
4. Thiết kế schema dữ liệu cho events.
5. Lập kế hoạch thu thập dữ liệu bổ sung nếu cần.
6. Tài liệu hóa yêu cầu (Requirements Specification).

Người phụ trách: Product Owner, Data Analyst
Thời gian: Tuần 1 – Tuần 2
Phụ thuộc: Không

Phase 2: Thiết kế kiến trúc và lựa chọn công nghệ (2 tuần)

Mục tiêu: Chọn tech stack, thiết kế pipeline, ước lượng chi phí.

Công việc:
1. So sánh các tech stack (dựa trên bảng so sánh).
2. Lựa chọn stack phù hợp với ngân sách và kỹ năng team.
3. Thiết kế chi tiết kiến trúc hệ thống (data flow, components).
4. Xác định các dịch vụ cloud cần dùng, cấu hình.
5. Lập kế hoạch bảo mật và tuân thủ (GDPR, PDPA).
6. Tài liệu thiết kế kiến trúc (Architecture Design Document).

Người phụ trách: Solution Architect, DevOps
Thời gian: Tuần 3 – Tuần 4
Phụ thuộc: Phase 1 hoàn thành

Phase 3: Xây dựng pipeline thu thập sự kiện (3 tuần)

Mục tiêu: Triển khai tracking code và data ingestion vào data warehouse.

Công việc:
1. Cấu hình Google Tag Manager / GA4 để gửi events đến BigQuery (hoặc tương đương).
2. Thiết lập stream dữ liệu từ các nguồn khác (CRM, email marketing) qua API.
3. Tạo các bảng raw events trong BigQuery.
4. Viết script đảm bảo data quality (validation).
5. Thiết lập Airflow DAG để đồng bộ dữ liệu hàng ngày.
6. Kiểm thử end‑to‑end với dữ liệu mẫu.

Người phụ trách: Data Engineer, Frontend Dev (cho tracking code)
Thời gian: Tuần 5 – Tuần 7
Phụ thuộc: Phase 2 hoàn thành

Phase 4: Xử lý dữ liệu và xây dựng mô hình (4 tuần)

Mục tiêu: Xây dựng ETL để tổng hợp journeys và tính toán Markov model.

Công việc:
1. Viết SQL query để nhóm events thành user journeys, tạo chuỗi touchpoints.
2. Xây dựng Python script để tính transition counts và ma trận xác suất.
3. Triển khai removal effect và tính toán đóng góp kênh.
4. Tối ưu hiệu năng (caching, incremental processing).
5. Tạo bảng kết quả (channel contributions) trong data warehouse.
6. Viết unit test cho model.
7. So sánh kết quả với mô hình heuristic để validate.
8. Tài liệu hóa logic và công thức.

Người phụ trách: Data Scientist, Data Engineer
Thời gian: Tuần 8 – Tuần 11
Phụ thuộc: Phase 3 hoàn thành (dữ liệu sẵn)

Phase 5: Tích hợp báo cáo và dashboard (2 tuần)

Mục tiêu: Tạo dashboard hiển thị kết quả attribution cho stakeholders.

Công việc:
1. Thiết kế dashboard (Looker Studio, Tableau) với các biểu đồ: contribution per channel, conversion paths, trends.
2. Kết nối với bảng kết quả.
3. Tạo cảnh báo nếu có thay đổi lớn.
4. Viết tài liệu hướng dẫn sử dụng.
5. Training cho marketing team.
6. Nhận phản hồi và điều chỉnh.

Người phụ trách: BI Analyst, Data Engineer
Thời gian: Tuần 12 – Tuần 13
Phụ thuộc: Phase 4 hoàn thành

Phase 6: Kiểm thử và tối ưu (2 tuần)

Mục tiêu: Kiểm thử toàn hệ thống, tối ưu hiệu năng và độ chính xác.

Công việc:
1. Kiểm thử tích hợp toàn bộ pipeline.
2. Kiểm tra data quality: completeness, accuracy, consistency.
3. Tối ưu query và script để giảm chi phí và thời gian xử lý.
4. Thực hiện load testing nếu cần.
5. Đánh giá mô hình với dữ liệu mới, điều chỉnh nếu cần.
6. Cập nhật tài liệu.

Người phụ trách: QA Engineer, Data Engineer
Thời gian: Tuần 14 – Tuần 15
Phụ thuộc: Phase 5 hoàn thành

Phase 7: Triển khai production và giám sát (1 tuần)

Mục tiêu: Đưa hệ thống vào vận hành chính thức, thiết lập monitoring.

Công việc:
1. Deploy code lên môi trường production (Airflow, Cloud Functions, etc.).
2. Cấu hình monitoring (logs, alerts) với Stackdriver, Cloud Monitoring.
3. Thiết lập SLA và thông báo sự cố.
4. Kiểm tra backup và recovery.
5. Thực hiện dry‑run với dữ liệu thật.
6. Kích hoạt schedule cho pipeline.

Người phụ trách: DevOps, Data Engineer
Thời gian: Tuần 16
Phụ thuộc: Phase 6 hoàn thành

Phase 8: Bàn giao và đào tạo (1 tuần)

Mục tiêu: Bàn giao hệ thống và tài liệu cho đội vận hành.

Công việc:
1. Hoàn thiện tất cả tài liệu (theo danh sách bàn giao).
2. Tổ chức buổi training cho end‑users (marketing, analysts).
3. Bàn giao source code, credentials, và quyền truy cập.
4. Ký kết biên bản nghiệm thu.
5. Lập kế hoạch hỗ trợ sau triển khai.

Người phụ trách: Project Manager, Solution Architect
Thời gian: Tuần 17
Phụ thuộc: Phase 7 hoàn thành

Timeline tổng quan (Gantt chart)

Tuần       1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17
Phase 1    [========]
Phase 2            [========]
Phase 3                    [===========]
Phase 4                            [================]
Phase 5                                            [======]
Phase 6                                                [======]
Phase 7                                                    [===]
Phase 8                                                      [=]

Mũi tên thể hiện dependency: Phase sau bắt đầu ngay sau khi Phase trước kết thúc.


Tài liệu bàn giao cuối dự án

Dự án phải bàn giao 15 tài liệu chính sau:

STT Tên tài liệu Người viết (vai trò) Mô tả nội dung
1 Project Charter Project Manager Phạm vi, mục tiêu, stakeholders, ràng buộc, rủi ro ban đầu.
2 Requirement Specification Product Owner, BA Yêu cầu chi tiết về business, functional, non‑functional.
3 Data Dictionary Data Engineer Mô tả tất cả bảng, trường, ý nghĩa, nguồn dữ liệu.
4 Architecture Design Document Solution Architect Kiến trúc hệ thống, sơ đồ luồng dữ liệu, công nghệ sử dụng.
5 API Documentation Backend Developer (nếu có) Tài liệu API cho các endpoint tích hợp (nếu có).
6 Source Code Repository Development Team Code đầy đủ với README, hướng dẫn build.
7 Deployment Guide DevOps Các bước triển khai lên môi trường production, cấu hình.
8 User Manual BI Analyst Hướng dẫn sử dụng dashboard, ý nghĩa các chỉ số.
9 Test Plan QA Engineer Kế hoạch kiểm thử: phạm vi, phương pháp, môi trường.
10 Test Cases QA Engineer Các test case chi tiết cho từng thành phần.
11 Test Report QA Engineer Kết quả kiểm thử, đánh giá chất lượng.
12 Maintenance Plan DevOps, Data Engineer Quy trình bảo trì, nâng cấp, xử lý sự cố.
13 Training Materials Trainer / PM Slide, video đào tạo người dùng cuối.
14 Handover Document Project Manager Tóm tắt dự án, trạng thái bàn giao, danh sách công việc còn tồn đọng.
15 Final Project Report Project Manager Báo cáo tổng kết dự án: thành tựu, bài học, khuyến nghị.

Quản lý rủi ro và phương án dự phòng

Rủi ro Mức độ Phương án B (ngay lập tức) Phương án C (dài hạn)
Dữ liệu không đầy đủ, thiếu kênh Trung bình Sử dụng dữ liệu mẫu từ các nguồn khác, bổ sung tracking code. Đầu tư vào hệ thống CDP (Customer Data Platform) để tổng hợp dữ liệu.
Mô hình cho kết quả không ổn định Cao Fallback về mô hình heuristic (time‑decay) trong thời gian hiệu chỉnh. Thuê chuyên gia data science tinh chỉnh mô hình, thử nghiệm Deep Learning.
Chi phí cloud vượt dự toán Trung bình Áp dụng query optimization, chuyển sang reserved instances, giảm tần suất chạy. Chuyển sang kiến trúc hybrid (phần lưu trữ rẻ hơn như AWS S3 + Athena).
Khó khăn tích hợp với nguồn dữ liệu Cao Sử dụng công cụ trung gian như Segment hoặc Fivetran để đơn giản hóa integration. Xây dựng custom connector dựa trên API của từng nguồn.
Thay đổi yêu cầu từ business Trung bình Ưu tiên các tính năng core, lập change request và điều chỉnh timeline. Áp dụng Agile, chia nhỏ thành các sprint để dễ thích nghi.
Vấn đề bảo mật dữ liệu cá nhân Cao Mã hóa dữ liệu, ẩn danh hóa, tuân thủ ngay từ đầu. Thực hiện audit định kỳ, đào tạo nhân sự về GDPR/PDPA.

KPI và đo lường hiệu quả

KPI Công cụ đo Tần suất đo Mục tiêu
Độ chính xác của attribution So sánh với A/B test Hàng tháng Sai số < 5% so với test thực tế
Thời gian xử lý dữ liệu (data freshness) Airflow logs, Monitoring Hàng ngày Dữ liệu cập nhật trong vòng 2h
Tỷ lệ sử dụng dashboard Google Analytics cho BI Hàng tuần > 70% người dùng target truy cập
Cải thiện ROAS Báo cáo tài chính Hàng quý Tăng ít nhất 10% so với baseline
Chi phí vận hành hàng tháng Cloud Billing, nhân sự Hàng tháng Nằm trong ngân sách (≤ $5,000)
Số lỗi trong pipeline Airflow alerts, Logging Hàng ngày 0 lỗi nghiêm trọng

Checklist Go‑live

Trước khi chính thức đưa hệ thống vào vận hành, cần kiểm tra kỹ các hạng mục sau. Checklist được chia thành 5 nhóm, tổng cộng 45 mục.

Security & Compliance

  1. [ ] Mã hóa dữ liệu ở trạng thái nghỉ (at‑rest encryption) đã bật trên BigQuery và Cloud Storage.
  2. [ ] Mã hóa dữ liệu trên đường truyền (TLS 1.2+).
  3. [ ] Access control (IAM) được cấu hình theo nguyên tắc least privilege.
  4. [ ] Đã loại bỏ các secret khỏi code, sử dụng Secret Manager.
  5. [ ] Có cơ chế ẩn danh hóa PII (nếu cần).
  6. [ ] Đã thực hiện đánh giá tác động bảo mật (DPIA) nếu xử lý dữ liệu nhạy cảm.
  7. [ ] Tuân thủ GDPR/PDPA: có Privacy Policy, cơ chế consent.
  8. [ ] Đã cấu hình VPC Service Controls (nếu cần).
  9. [ ] Audit logging được bật và lưu trữ ít nhất 90 ngày.

Performance & Scalability

  1. [ ] Query BigQuery đã được tối ưu (partitioning, clustering).
  2. [ ] Airflow DAG chạy trong thời gian cho phép (< 30 phút).
  3. [ ] Có cơ chế auto‑scaling cho Compute Engine (nếu dùng).
  4. [ ] Đã thực hiện load test với lượng dữ liệu gấp 3 lần dự kiến.
  5. [ ] Sử dụng incremental processing để tránh quét toàn bộ bảng.
  6. [ ] Đã thiết lập caching cho dashboard (nếu cần).
  7. [ ] Có kế hoạch mở rộng khi dữ liệu tăng (sharding, BigQuery reservation).
  8. [ ] Đã đo latency từ khi dữ liệu đến đến khi có báo cáo (< 2h).
  9. [ ] Đã tối ưu Python script (sử dụng vectorization, tránh vòng lặp).

Business & Data Accuracy

  1. [ ] Dữ liệu raw events đã được validate (không null channel, định dạng timestamp).
  2. [ ] Số lượng journeys tạo ra khớp với số session trong GA4 (sai số < 2%).
  3. [ ] Kết quả attribution đã được so sánh với mô hình last‑click để phát hiện bất thường.
  4. [ ] Đã kiểm tra cross‑device tracking (nếu có user‑id).
  5. [ ] Có cơ chế xử lý duplicate events (deduplication).
  6. [ ] Đã thực hiện reconciliation giữa tổng conversion từ model và hệ thống order.
  7. [ ] Dashboard hiển thị đúng dữ liệu mới nhất.
  8. [ ] Có unit test cho các hàm quan trọng (transition counting, matrix inversion).
  9. [ ] Đã chạy mô hình trên dữ liệu lịch sử và so sánh với kết quả đã biết.

Payment & Finance

  1. [ ] Đã thiết lập budget alert trên GCP (80%, 100%).
  2. [ ] Các tài nguyên cloud được gán label để theo dõi chi phí theo dự án.
  3. [ ] Có quy trình đối soát chi phí hàng tháng giữa kế toán và team kỹ thuật.
  4. [ ] Đã tính toán ROI dự kiến sau triển khai.
  5. [ ] Có kế hoạch dự phòng ngân sách cho năm tiếp theo.
  6. [ ] Đã xác định các mục có thể cắt giảm chi phí nếu cần (ví dụ: downgrade VM).
  7. [ ] Đã tích hợp với hệ thống invoice (nếu cần).
  8. [ ] Đã đánh giá chi phí nhân sự vận hành và đưa vào ngân sách.

Monitoring & Rollback

  1. [ ] Đã cấu hình Cloud Monitoring cho BigQuery slot usage, query times.
  2. [ ] Airflow có cảnh báo qua email/Slack khi DAG fail.
  3. [ ] Có dashboard giám sát tổng thể (health check).
  4. [ ] Đã viết runbook xử lý sự cố thường gặp.
  5. [ ] Có cơ chế rollback về phiên bản code trước (Git tags, Docker image versioning).
  6. [ ] Đã test rollback: khôi phục bảng dữ liệu từ snapshot.
  7. [ ] Có lịch trình backup định kỳ cho cơ sở dữ liệu (nếu dùng PostgreSQL).
  8. [ ] Đã thiết lập SLI/SLO cho hệ thống (ví dụ: availability 99.9%).
  9. [ ] Đã phân công người on‑call ứng phó sự cố.

Code và Config mẫu

Dưới đây là 12 đoạn code/config thực tế bạn có thể sử dụng ngay trong dự án.

1. GA4 Tracking Code với channel tự động

<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXX');

  // Hàm phân loại channel từ URL
  function getChannel() {
    const urlParams = new URLSearchParams(window.location.search);
    const utmSource = urlParams.get('utm_source');
    const utmMedium = urlParams.get('utm_medium');
    if (utmSource && utmMedium) return `${utmSource}_${utmMedium}`;
    if (document.referrer.includes('google')) return 'organic_search';
    if (document.referrer.includes('facebook')) return 'social_facebook';
    // ... thêm logic khác
    return 'direct';
  }

  // Gửi page_view với channel
  gtag('event', 'page_view', {
    'channel': getChannel()
  });
</script>

2. BigQuery Schema cho raw_events

CREATE TABLE `project.dataset.raw_events` (
  event_id STRING,
  user_id STRING,
  session_id STRING,
  timestamp TIMESTAMP,
  channel STRING,
  event_type STRING,
  conversion_value FLOAT64,
  page_url STRING,
  referrer STRING,
  utm_source STRING,
  utm_medium STRING,
  utm_campaign STRING
)
PARTITION BY DATE(timestamp)
CLUSTER BY channel, event_type;

3. SQL Tổng hợp user journeys

WITH events_with_session AS (
  SELECT
    user_id,
    TIMESTAMP_MICROS(event_timestamp) as ts,
    channel,
    event_type,
    LAG(TIMESTAMP_MICROS(event_timestamp)) OVER (PARTITION BY user_id ORDER BY event_timestamp) as prev_ts,
    TIMESTAMP_DIFF(TIMESTAMP_MICROS(event_timestamp), LAG(TIMESTAMP_MICROS(event_timestamp)) OVER (PARTITION BY user_id ORDER BY event_timestamp), MINUTE) as mins_since_prev
  FROM `project.dataset.raw_events`
  WHERE channel IS NOT NULL
),
session_groups AS (
  SELECT
    *,
    SUM(IF(mins_since_prev IS NULL OR mins_since_prev > 30, 1, 0)) OVER (PARTITION BY user_id ORDER BY ts) as session_number
  FROM events_with_session
),
user_sessions AS (
  SELECT
    user_id,
    CONCAT(user_id, '_', session_number) as session_id,
    ts,
    channel,
    event_type
  FROM session_groups
),
journeys AS (
  SELECT
    user_id,
    session_id,
    ARRAY_AGG(channel ORDER BY ts) as path,
    MAX(IF(event_type = 'purchase', 1, 0)) as converted
  FROM user_sessions
  GROUP BY user_id, session_id
)
SELECT * FROM journeys

4. Python: Đếm transitions từ journeys

from collections import defaultdict

def count_transitions(journeys):
    """
    journeys: list of tuples (path_list, converted)
    Returns: dict (from_state, to_state) -> count
    """
    trans = defaultdict(int)
    for path, converted in journeys:
        if not path:
            continue
        # start -> first
        trans[('start', path[0])] += 1
        # intermediate
        for i in range(len(path)-1):
            trans[(path[i], path[i+1])] += 1
        # last -> conversion or dropout
        last = path[-1]
        if converted:
            trans[(last, 'conversion')] += 1
        else:
            trans[(last, 'dropout')] += 1
    return trans

5. Python: Xây dựng ma trận và tính Removal Effect

import numpy as np
from collections import OrderedDict

def build_states(trans_counts):
    states_set = set()
    for fr, to in trans_counts:
        states_set.add(fr)
        states_set.add(to)
    states_set.update(['start', 'conversion', 'dropout'])
    channels = sorted([s for s in states_set if s not in ('start','conversion','dropout')])
    states = ['start'] + channels + ['conversion','dropout']
    idx = {s:i for i,s in enumerate(states)}
    n = len(states)
    P = np.zeros((n,n))
    for (fr, to), cnt in trans_counts.items():
        i = idx[fr]
        j = idx[to]
        P[i,j] = cnt
    row_sums = P.sum(axis=1, keepdims=True)
    P = np.divide(P, row_sums, out=np.zeros_like(P), where=row_sums!=0)
    return states, idx, P

def conversion_prob(P, idx):
    trans_idx = [i for i,s in enumerate(states) if s not in ('conversion','dropout')]
    abs_idx = [idx['conversion'], idx['dropout']]
    Q = P[np.ix_(trans_idx, trans_idx)]
    R = P[np.ix_(trans_idx, abs_idx)]
    I = np.eye(len(trans_idx))
    N = np.linalg.inv(I - Q)
    B = N @ R
    start_pos = trans_idx.index(idx['start'])
    conv_pos = list(abs_idx).index(idx['conversion'])
    return B[start_pos, conv_pos]

def removal_effect(trans_counts):
    states, idx, P = build_states(trans_counts)
    overall = conversion_prob(P, idx)
    channels = [s for s in states if s not in ('start','conversion','dropout')]
    contributions = {}
    for ch in channels:
        # Loại bỏ mọi transition liên quan đến kênh này
        new_counts = {k:v for k,v in trans_counts.items() if ch not in k}
        try:
            new_states, new_idx, new_P = build_states(new_counts)
            new_conv = conversion_prob(new_P, new_idx)
        except np.linalg.LinAlgError:
            new_conv = 0.0
        contributions[ch] = (overall - new_conv) / overall
    # Chuẩn hóa
    total = sum(contributions.values())
    if total > 0:
        contributions = {k: v/total for k,v in contributions.items()}
    return contributions

6. Dockerfile cho môi trường Python

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "run_model.py"]

7. docker-compose.yml cho Airflow local

version: '3'
services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_USER: airflow
      POSTGRES_PASSWORD: airflow
      POSTGRES_DB: airflow
    volumes:
      - postgres-db-volume:/var/lib/postgresql/data

  redis:
    image: redis:6.2

  airflow-webserver:
    image: apache/airflow:2.5.1
    command: webserver
    depends_on:
      - postgres
      - redis
    environment:
      AIRFLOW__CORE__EXECUTOR: CeleryExecutor
      AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
      AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
      AIRFLOW__CELERY__BROKER_URL: redis://redis:6379/0
    volumes:
      - ./dags:/opt/airflow/dags
      - ./logs:/opt/airflow/logs
      - ./plugins:/opt/airflow/plugins
    ports:
      - "8080:8080"

  airflow-scheduler:
    image: apache/airflow:2.5.1
    command: scheduler
    depends_on:
      - postgres
      - redis
    environment:
      AIRFLOW__CORE__EXECUTOR: CeleryExecutor
      AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
      AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
      AIRFLOW__CELERY__BROKER_URL: redis://redis:6379/0
    volumes:
      - ./dags:/opt/airflow/dags
      - ./logs:/opt/airflow/logs
      - ./plugins:/opt/airflow/plugins

  airflow-worker:
    image: apache/airflow:2.5.1
    command: celery worker
    depends_on:
      - postgres
      - redis
    environment:
      AIRFLOW__CORE__EXECUTOR: CeleryExecutor
      AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
      AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
      AIRFLOW__CELERY__BROKER_URL: redis://redis:6379/0
    volumes:
      - ./dags:/opt/airflow/dags
      - ./logs:/opt/airflow/logs
      - ./plugins:/opt/airflow/plugins

volumes:
  postgres-db-volume:

8. Airflow DAG chạy model hàng ngày

from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.providers.google.cloud.operators.bigquery import BigQueryExecuteQueryOperator
from datetime import datetime, timedelta

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2025, 1, 1),
    'email_on_failure': True,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}

dag = DAG(
    'markov_attribution',
    default_args=default_args,
    schedule_interval='0 2 * * *',
    catchup=False,
)

def run_model(**context):
    from model import compute_contributions
    compute_contributions()
    return 'Done'

extract_journeys = BigQueryExecuteQueryOperator(
    task_id='extract_journeys',
    sql='''
    CREATE OR REPLACE TABLE `project.dataset.journeys` AS
    -- SQL từ snippet 3
    ''',
    use_legacy_sql=False,
    gcp_conn_id='google_cloud_default',
    dag=dag,
)

compute = PythonOperator(
    task_id='compute_contributions',
    python_callable=run_model,
    dag=dag,
)

extract_journeys >> compute

9. Nginx config cho API (nếu cần)

server {
    listen 80;
    server_name api.yourdomain.com;

    location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # SSL config (recommended)
    # listen 443 ssl;
    # ssl_certificate /path/to/cert;
    # ssl_certificate_key /path/to/key;
}

10. Cloudflare Worker thu thập event (edge tracking)

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const url = new URL(request.url)
  if (url.pathname === '/track') {
    const params = url.searchParams
    const eventName = params.get('event') || 'page_view'
    const channel = params.get('channel')
    const cid = params.get('cid') || generateUUID()
    const payload = {
      client_id: cid,
      events: [{
        name: eventName,
        params: {
          channel: channel,
          page_location: params.get('url'),
          page_referrer: params.get('ref'),
        }
      }]
    }
    // Gửi đến GA4 Measurement Protocol
    await fetch('https://www.google-analytics.com/mp/collect?measurement_id=G-XXXX&api_secret=YYYY', {
      method: 'POST',
      body: JSON.stringify(payload)
    })
    // Có thể gửi đồng thời đến endpoint riêng
    return new Response('OK', { status: 200 })
  }
  return new Response('Not found', { status: 404 })
}

function generateUUID() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8)
    return v.toString(16)
  })
}

11. GitHub Actions CI/CD

name: CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  release:
    types: [created]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run unit tests
        run: pytest tests/

  deploy:
    needs: test
    if: github.event_name == 'release' && github.event.action == 'created'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to Cloud Run
        uses: google-github-actions/deploy-cloudrun@v0
        with:
          service: markov-attribution
          image: gcr.io/your-project/markov-attribution:${{ github.sha }}
          credentials: ${{ secrets.GCP_SA_KEY }}

12. Script validation dữ liệu

from google.cloud import bigquery

client = bigquery.Client()

def validate():
    # Số lượng raw events
    raw_q = client.query('SELECT COUNT(*) as cnt FROM `project.dataset.raw_events`')
    raw_cnt = list(raw_q.result())[0].cnt
    print(f"Raw events: {raw_cnt}")

    # Số lượng journeys
    journeys_q = client.query('SELECT COUNT(*) as cnt FROM `project.dataset.journeys`')
    journeys_cnt = list(journeys_q.result())[0].cnt
    print(f"Journeys: {journeys_cnt}")

    # Kiểm tra null channel
    null_channel_q = client.query('''
        SELECT COUNT(*) as cnt
        FROM `project.dataset.raw_events`
        WHERE channel IS NULL
    ''')
    null_channel = list(null_channel_q.result())[0].cnt
    if null_channel > 0:
        print(f"WARNING: {null_channel} rows have null channel")

    # Kiểm tra conversion count khớp với orders
    orders_q = client.query('SELECT COUNT(*) as cnt FROM `project.dataset.orders`')
    orders_cnt = list(orders_q.result())[0].cnt
    conv_q = client.query('SELECT COUNT(*) as cnt FROM `project.dataset.journeys` WHERE converted = 1')
    conv_cnt = list(conv_q.result())[0].cnt
    if abs(orders_cnt - conv_cnt) > 10:
        print(f"DISCREPANCY: orders={orders_cnt}, journeys converted={conv_cnt}")

Kết luận và thảo luận

Phân tích path‑to‑purchase bằng Markov Chains cung cấp một cách tiếp cận khách quan, dựa trên dữ liệu để đánh giá đóng góp thực sự của các kênh marketing. Triển khai mô hình này không quá phức tạp nếu bạn có pipeline dữ liệu vững chắc và tuân theo các bước đã nêu.

Key takeaways:

  • Mô hình Markov giúp loại bỏ những giả định chủ quan, tự động học từ hành vi người dùng.
  • Cần đầu tư vào việc thu thập dữ liệu đầy đủ, nhất là xuyên suốt các phiên.
  • Kiến trúc GCP + BigQuery + Airflow + Python là lựa chọn cân bằng giữa chi phí và hiệu năng.
  • Checklist go‑live chi tiết giúp đảm bảo hệ thống sẵn sàng cho production.

Câu hỏi thảo luận: Anh em đã từng triển khai attribution model nào chưa? Có gặp khó khăn gì trong việc xác định đóng góp của các kênh? Hãy chia sẻ ở phần bình luận!

Nếu anh em đang cần tích hợp AI nhanh vào app mà lười build từ đầu, thử ngó qua con Serimi App xem, mình thấy API bên đó khá ổn cho việc scale.


Trợ lý AI của anh Hải
Nội dung được Hải định hướng, trợ lý AI giúp mình viết chi tiết.
Chia sẻ tới bạn bè và gia đình