DataOps nâng cao 16 — Chuyển đổi từ legacy sang DataOps

13 thg 7, 2026 2 lượt xem
#data-engineering
#migration
#dataops
#modernization
#legacy

Bài toán không phải "xây mới" mà là "thay hệ đang chạy"

Mười lăm bài trước của series dựng năng lực DataOps trên nền trắng: kiến trúc lakehouse, dbt medallion, Airflow, CI/CD, quality, observability. Nhưng thực tế ở một ngân hàng đang vận hành thì không có nền trắng. Có một hệ báo cáo cũ đang chạy mỗi đêm, đang nộp số cho NHNN, đang bị hàng chục phòng ban phụ thuộc — và bạn không được phép làm nó ngừng chạy một buổi nào.

Bài này nói về việc khó nhất trong nghề dữ liệu ngân hàng: chuyển đổi (migration/modernization) từ cách làm cũ sang DataOps hiện đại mà không làm vỡ hệ đang phục vụ. Đây không phải bài lý thuyết — đây là bài về cách sống sót qua một dự án thay tim khi bệnh nhân vẫn đang tỉnh.

Thực trạng legacy: nó trông như thế nào

Trước khi bàn cách chuyển, phải nhìn thẳng vào cái đang có. Ở đa số ngân hàng Việt Nam, lớp "biến đổi dữ liệu để ra báo cáo" là một tổ hợp các di sản sau, thường trộn lẫn:

Di sản legacyBiểu hiện điển hìnhVấn đề cốt lõi
Stored procedure / PL-SQL khổng lồMột sp_bao_cao_npl dài 3000 dòng, gọi 12 proc conLogic ẩn trong DB, không đọc nổi, không test
SSIS / Informatica / ETL kéo-thảPackage nhiều tab, biến đổi trong hộp đen GUIKhông diff được, không review qua Git
SQL script chạy tay / cron rời rạcAnalyst mở SSMS chạy 20 query theo thứ tự trong đầuKhông tái lập, sai thứ tự là sai số
Excel / Access làm bước cuốiGhép, pivot, tô màu tay rồi gửi emailKhông audit, mỗi người một bản
"Tri thức trong đầu vài người""Hỏi anh Tuấn, chỉ anh ấy biết công thức trích lập"Rủi ro nghỉ việc = mất pipeline

Điểm chung của tất cả: không version, không test, không tài liệu, không lineage. Hệ vẫn ra số đúng — cho tới ngày nó không đúng, và không ai biết vì sao.

Rủi ro khi giữ nguyên legacy

Nhiều lãnh đạo hỏi "nó chạy tốt bao năm, sao phải đổi?". Chi phí của việc không đổi là chi phí ẩn, thường chỉ lộ ra vào lúc tệ nhất:

  • Dễ vỡ (brittle). Một thay đổi nhỏ ở nguồn (thêm loại giao dịch, đổi mã tiền tệ) làm proc chạy sai âm thầm, không ai phát hiện tới khi thanh tra hỏi.
  • Khó thay đổi. Muốn thêm một chỉ tiêu mới vào báo cáo NHNN phải sửa proc 3000 dòng mà không dám, vì không có test bảo vệ.
  • Phụ thuộc người. Anh Tuấn nghỉ, cả pipeline trích lập dự phòng thành hộp đen không ai dám đụng.
  • Không audit được. Thanh tra NHNN hỏi "số NPL này ra từ đâu, công thức nào, ngày nào?" — legacy không trả lời được vì không có lineage, không có version của logic.
  • Không tái lập. Chạy lại báo cáo kỳ trước ra số khác kỳ này, vì query tay đã bị sửa mà không lưu vết.

Chiến lược chuyển đổi: đánh giá trước, chọn cách sau

Sai lầm số một là nhảy vào viết lại ngay. Chuyển đổi đúng bắt đầu bằng đánh giá và lập bản đồ (assess & map).

Bước 1 — Inventory: lập bản đồ tài sản legacy

Bạn không thể chuyển thứ bạn không biết mình có. Lập một danh mục (inventory) mọi pipeline/báo cáo, với ba trục:

  • Phụ thuộc (dependency): proc/script này đọc bảng nào, ai đọc output của nó.
  • Độ quan trọng (criticality): báo cáo nộp NHNN (cao nhất) hay báo cáo nội bộ tham khảo (thấp).
  • Độ đau (pain): hay lỗi không, chạy bao lâu, bao nhiêu người phải can thiệp tay.

Kết quả là một ma trận giá trị/rủi ro để quyết định chuyển cái gì trước.

Bước 2 — Chọn cách chuyển (6R rút gọn)

Không phải mọi thứ đều đáng viết lại. Ánh xạ từng pipeline vào một trong các lựa chọn (mượn khung "6R" của cloud migration, rút gọn):

CáchNghĩaKhi nào dùng
RehostBê nguyên chạy chỗ khácProc còn tốt, chỉ cần rời server cũ — mua thời gian
ReplatformĐổi nền, giữ logic gần nguyênSSIS → Airflow gọi lại chính SQL cũ, chưa refactor
RefactorViết lại logic theo chuẩn mớiProc quan trọng, hay đổi → chuyển thành dbt model
Re-architectureThiết kế lại từ đầuChỉ khi logic cũ sai/đáng bỏ, và giá trị bù được rủi ro
RetireBỏ hẳnBáo cáo không ai đọc nữa (thường 20-30% danh mục!)

Nguyên tắc vàng: re-architecture là ngoại lệ, không phải mặc định. Đa số pipeline ngân hàng nên đi đường replatform rồi refactor dần, không phải đập đi xây lại.

Strangler Fig: xương sống của chuyển đổi an toàn

Với báo cáo quan trọng (nộp NHNN), không bao giờ được "tắt cũ, bật mới" trong một đêm. Pattern chuẩn là Strangler Fig (cây bóp cổ) — tên lấy từ loài cây leo bao quanh cây chủ, dần thay thế nó cho tới khi cây chủ tiêu biến mà tán lá vẫn liền mạch.

Áp dụng cho pipeline: xây dần hệ mới bao quanh hệ cũ, thay thế từng phần, chạy song song cả haiđối soát kết quả cho tới khi hệ mới đủ tin cậy, rồi mới cắt hệ cũ.

Ba giai đoạn: (1) chỉ hệ cũ chạy như thường; (2) hệ mới chạy song song, mỗi kỳ đối soát output hai bên, lệch thì điều tra — thường là do một mẩu logic ẩn trong proc cũ mà không ai nhớ; (3) khi khớp N kỳ liên tiếp (thường 1-3 tháng chốt sổ), cắt cũ và cho proc cũ "về hưu" (retire).

Đối soát song song: trái tim của độ an toàn

Đối soát (reconciliation) là việc so cùng một chỉ tiêu tổng hợp giữa hệ cũ và hệ mới, phát hiện mọi chênh lệch trước khi tin hệ mới. Trong thực tế, ta không so từng dòng (quá nặng) mà so các chỉ tiêu tổng hợp then chốt: tổng số dư theo loại tiền, tổng dư nợ theo nhóm, số bút toán theo tháng.

Dưới đây là một truy vấn minh hoạ hình dạng phía "hệ mới" — tính tổng số dư và số tài khoản theo loại tiền, đúng dạng chỉ tiêu ta sẽ đem đối soát với con số proc cũ xuất ra. Chạy được trên sandbox demo:

-- ▶ Chạy được
-- Phía "hệ mới": tổng số dư & số tài khoản theo loại tiền (chỉ tiêu đối soát)
SELECT
    a.currency                                   AS loai_tien,
    COUNT(*)                                      AS so_tai_khoan,
    ROUND(SUM(a.balance)::numeric, 2)            AS tong_so_du
FROM accounts a
GROUP BY a.currency
ORDER BY a.currency;

Trong project thật, con số bên trên đến từ mart dbt; con số hệ cũ đến từ output của stored proc. Bạn đưa cả hai vào một bảng đối soát và tính chênh lệch. Truy vấn dưới minh hoạ chính khung đối soát đó — ghép chỉ tiêu tổng hợp theo loại tiền và theo tháng (từ giao dịch), rồi tính hiệu số. Ở đây ta giả lập "hệ cũ" và "hệ mới" bằng cùng nguồn để minh hoạ khung so khớp; thực tế hai nhánh lấy từ hai nguồn khác nhau:

-- ▶ Chạy được
-- Khung đối soát cũ-mới: cùng một chỉ tiêu (tổng số tiền giao dịch) theo loại tiền + tháng
-- Ở đây hai nhánh dùng cùng nguồn (minh hoạ khung); thật: he_moi từ dbt, he_cu từ proc
WITH giao_dich AS (
    SELECT
        ac.currency                                     AS loai_tien,
        date_trunc('month', t.created_at)::date         AS thang,
        t.amount
    FROM transactions t
    JOIN accounts ac  ON t.account_id  = ac.id
    JOIN customers cu ON ac.customer_id = cu.id
),
he_moi AS (
    SELECT loai_tien, thang, SUM(amount) AS tong_moi
    FROM giao_dich
    GROUP BY loai_tien, thang
),
he_cu AS (
    SELECT loai_tien, thang, SUM(amount) AS tong_cu
    FROM giao_dich
    GROUP BY loai_tien, thang
)
SELECT
    m.loai_tien,
    m.thang,
    ROUND(m.tong_moi::numeric, 2)                       AS tong_he_moi,
    ROUND(c.tong_cu::numeric, 2)                        AS tong_he_cu,
    ROUND((m.tong_moi - c.tong_cu)::numeric, 2)         AS chenh_lech
FROM he_moi m
JOIN he_cu c ON m.loai_tien = c.loai_tien AND m.thang = c.thang
ORDER BY m.loai_tien, m.thang;

Truy vấn dùng 3 bảng qua đúng khoá join (transactions.account_id → accounts.id, accounts.customer_id → customers.id); cột chenh_lech trong project thật phải bằng 0 (hoặc trong ngưỡng làm tròn cho phép) ở mọi hàng thì mới được coi là khớp. Bất kỳ hàng nào lệch là dấu hiệu có logic hệ cũ chưa được tái hiện đúng ở hệ mới — chính là việc cần điều tra trong giai đoạn song song.

Lưu ý: cả hai bảng demo (customers/accounts/transactions) chỉ để minh hoạ hình dạng của một chỉ tiêu đối soát. Bộ 5 bảng sandbox không có khái niệm "nhóm nợ" hay "trích lập"; project thật thay các bảng này bằng mart bạc/vàng của dbt.

Refactor stored proc thành dbt model

Đây là phần "refactor" đắt giá nhất và có giá trị nhất. Ý tưởng: bóc logic khỏi hộp đen stored proc, tách thành các model dbt nhỏ, mỗi model một trách nhiệm, kèm test + tài liệu + lineage.

Before — stored proc (giả lập, phương ngữ T-SQL, KHÔNG chạy trên sandbox):

-- KHÔNG chạy được trên sandbox (T-SQL, DDL/DML nhiều câu) — chỉ minh hoạ legacy
CREATE PROCEDURE sp_tong_du_no_theo_tien AS
BEGIN
    -- bảng tạm, con trỏ, biến, IF lồng nhau... logic ẩn khắp nơi
    CREATE TABLE #tmp (loai_tien VARCHAR(3), tong DECIMAL(18,2));
    INSERT INTO #tmp
    SELECT currency, SUM(balance)
    FROM dbo.accounts
    WHERE is_active = 1            -- điều kiện ẩn dễ bị bỏ sót khi migrate!
    GROUP BY currency;
    -- ... 200 dòng ghép Excel/pivot phía sau ...
    SELECT * FROM #tmp;
END

After — dbt model (SQL thuần, kèm test & tài liệu): file models/marts/mart_du_no_theo_tien.sql

-- dbt model: 1 SELECT sạch, ref() tạo lineage, test khai báo ở YAML
-- (minh hoạ; trong dbt thật dùng {{ ref('stg_accounts') }} thay tên bảng)
SELECT
    a.currency                          AS loai_tien,
    COUNT(*)                            AS so_tai_khoan,
    ROUND(SUM(a.balance)::numeric, 2)   AS tong_du_no
FROM accounts a
GROUP BY a.currency

Điều kiện ẩn WHERE is_active = 1 là ví dụ kinh điển của logic ẩn — nó có thể là đúng (chỉ tính tài khoản active) hoặc là bug lịch sử. Khi refactor, bạn phải chủ động phát hiện nó qua đối soát: nếu hệ mới bỏ điều kiện này, con số sẽ lệch, và đó chính là lúc bạn tra ra và quyết định giữ hay bỏ. Sau khi thành dbt model, logic này được viết tường minh, review qua PR, và mọi thay đổi có test bảo vệ — xem Bài 2 — dbt & ELTdbt 1 — Tổng quan cho cách tổ chức model + test + lineage.

Đưa vào version, CI/CD, observability

Chuyển sang dbt/Airflow mới là một nửa việc. Nửa còn lại là bọc quy trình DataOps quanh nó — thứ legacy không bao giờ có:

  • Version control: mọi logic vào Git, mỗi thay đổi là một commit có thể diff và revert. Xem Bài 4 — CI/CD.
  • CI/CD có phê duyệt: PR → chạy test tự động → maker-checker approve → deploy. Không còn "sửa thẳng trên prod".
  • Data quality & contract: test số dư không âm, tổng khớp, khoá không trùng — chặn số sai trước khi phục vụ. Xem Bài 5 — Quality & contract.
  • Observability: freshness, cảnh báo trễ, lineage đầu-cuối để trả lời thanh tra "số này từ đâu ra".

Di chuyển dữ liệu & lịch sử

Ngoài logic, còn phải chuyển dữ liệu lịch sử. Với báo cáo giám sát, không thể chỉ có số từ ngày cắt hệ mới — cần backfill để so sánh chuỗi thời gian. Hai lựa chọn:

  • Backfill lại từ nguồn qua hệ mới (nếu nguồn thô còn giữ) — sạch nhất, và chính backfill là một bài kiểm tra đối soát khổng lồ.
  • Nạp snapshot số cũ làm bảng lịch sử "đóng băng" nếu nguồn thô đã mất — chấp nhận không tái lập được, đánh dấu rõ.

Dù cách nào, phải niêm phong (freeze) và ghi vết ranh giới ngày chuyển đổi, để sau này ai đọc cũng biết trước ngày X số đến từ hệ cũ.

Con người & quản lý thay đổi

Phần bị xem nhẹ nhất nhưng làm chết nhiều dự án nhất: con người. Chuyển đổi kỹ thuật thất bại thường không vì kỹ thuật.

  • Kỹ năng mới: analyst quen SSMS/Excel giờ phải học Git, dbt, review PR. Cần đào tạo thật, không phải một buổi.
  • Buy-in lãnh đạo: phải cho thấy giá trị bằng con số (giảm ngày chốt sổ, giảm lỗi) chứ không phải bằng "công nghệ mới hay".
  • Chống kháng cự: người nắm "tri thức trong đầu" có thể sợ mất giá trị. Biến họ thành người dẫn dắt việc bóc logic — họ là nguồn tri thức quý nhất, không phải rào cản.
  • Đào tạo & cặp kèm: pair một data engineer với một analyst nghiệp vụ trong suốt giai đoạn song song.

Ưu tiên theo giá trị/rủi ro

Bắt đầu ở đâu? Hai trường phái, chọn theo bối cảnh:

  • Bắt đầu từ pipeline đau nhất: báo cáo hay lỗi, tốn nhiều công tay — giá trị chuyển đổi rõ ràng, nhưng rủi ro cao. Hợp khi cần chứng minh ROI mạnh.
  • Bắt đầu từ quick win: một báo cáo nhỏ, rủi ro thấp — để đội quen quy trình mới, xây niềm tin, tạo template trước khi đụng báo cáo NHNN.

Kinh nghiệm ngân hàng: quick win trước để rèn quy trình + đối soát, rồi mới đụng báo cáo trọng yếu với Strangler Fig đầy đủ.

Cạm bẫy thường gặp

  • Big-bang rewrite: viết lại toàn bộ rồi "một đêm chuyển hết" — gần như luôn thất bại, vì logic ẩn không bao giờ được phát hiện hết cho tới khi đối soát thật. Luôn dùng Strangler Fig.
  • Bỏ sót logic ẩn: những WHERE is_active = 1, những điều chỉnh tay cuối tháng, những ngoại lệ trong đầu người. Chỉ đối soát mới lôi chúng ra.
  • Không đối soát, hoặc đối soát hời hợt: so một kỳ rồi tin ngay. Phải khớp nhiều kỳ liên tiếp qua các tình huống khác nhau (cuối tháng, cuối quý, có bút toán điều chỉnh) mới cắt cũ.
  • Cắt cũ quá sớm vì áp lực tiến độ — rồi phát hiện lệch khi đã không còn hệ cũ để so.

Use case thực tế

Bối cảnh (NCB minh hoạ). Báo cáo phân loại nợ & tỷ lệ NPL nộp NHNN đang chạy trên một stored proc T-SQL ~2800 dòng cộng một package SSIS, chốt sổ mỗi đêm, một analyst can thiệp tay ~1 giờ/ngày, và "chỉ hai người hiểu công thức trích lập".

Cách làm:

  1. Inventory (2 tuần): liệt kê 14 báo cáo dùng proc này; phân loại 3 báo cáo trọng yếu (nộp NHNN), 4 nội bộ, và retire 2 báo cáo không ai đọc từ 2024.
  2. Quick win trước: chuyển 1 báo cáo nội bộ nhỏ sang dbt+Airflow để đội quen quy trình + dựng khung đối soát.
  3. Strangler Fig cho báo cáo NPL: dựng mart dbt song song, chạy cùng proc cũ mỗi đêm, đối soát 6 chỉ tiêu tổng hợp (tổng dư nợ theo nhóm 1-5, số trích lập, tỷ lệ NPL...) theo khung như truy vấn ở trên.
  4. Điều tra chênh lệch: kỳ đầu lệch 0,3% ở nhóm 2 — truy ra một điều kiện lọc tài khoản đóng trong proc cũ không được ghi tài liệu. Đưa vào dbt model tường minh, thêm test.
  5. Cắt cũ: sau 3 kỳ chốt sổ khớp tuyệt đối (gồm một kỳ cuối quý có điều chỉnh), retire proc cũ.

Kết quả (minh hoạ): thời gian chốt báo cáo NPL giảm từ ~5 giờ (gồm can thiệp tay) xuống ~40 phút tự động; mỗi con số có lineage tới nguồn; thanh tra hỏi "công thức nhóm 3 tháng 6" trả lời được bằng một PR + commit; và tri thức "trong đầu hai người" giờ nằm trong code có test. Xem Bài 8 — Case study end-to-endBài 1 — Kiến trúc cho bức tranh hệ đích.

Ghi nhớ

  • Legacy ngân hàng = stored proc khổng lồ + SSIS/Informatica + SQL chạy tay + Excel + "tri thức trong đầu vài người", không version/test/tài liệu/lineage.
  • Giữ nguyên legacy có chi phí ẩn: dễ vỡ, khó đổi, phụ thuộc người, không audit & không tái lập được — lộ ra đúng lúc thanh tra hỏi.
  • Chuyển đổi bắt đầu bằng đánh giá & lập bản đồ (inventory: phụ thuộc, độ quan trọng, độ đau), rồi chọn rehost/replatform/refactor/retire; re-architecture là ngoại lệ, không phải mặc định. 20-30% báo cáo thường nên retire.
  • Strangler Fig là xương sống an toàn: thay dần từng phần, chạy song song cũ-mới, đối soát chỉ tiêu tổng hợp, chỉ cắt cũ khi khớp nhiều kỳ liên tiếp.
  • Đối soát = so cùng chỉ tiêu tổng hợp (theo loại tiền/tháng/nhóm) giữa hai hệ; mọi chênh lệch là dấu hiệu logic ẩn cần điều tra.
  • Refactor proc → dbt model: 1 SELECT sạch mỗi model, kèm test + tài liệu + lineage; logic ẩn (WHERE is_active=1) phải được lôi ra tường minh.
  • Con người quyết định thành bại: kỹ năng mới, buy-in bằng số, chống kháng cự bằng cách biến người giữ tri thức thành người dẫn dắt.
  • Ưu tiên: quick win trước để rèn quy trình, rồi mới đụng báo cáo trọng yếu với Strangler Fig đầy đủ.
  • Tránh: big-bang rewrite, bỏ sót logic ẩn, không/hời hợt đối soát, cắt cũ quá sớm.

Bài viết liên quan

CSV/JSON/Parquet/Avro, lưu trữ theo hàng vs cột, nén, và OLTP vs OLAP.

13 thg 7, 2026 8

Data Engineering là gì, vai trò trong vòng đời dữ liệu, và bức tranh hệ sinh thái công cụ.

13 thg 7, 2026 5

Vì sao xử lý phân tán, mô hình Spark (RDD/DataFrame), lazy evaluation, shuffle và tối ưu.

13 thg 7, 2026 5

Đảm bảo chất lượng & minh bạch dữ liệu bằng dbt: data tests dựng sẵn (unique, not_null, relationships, accepted_values), singular test, unit test, và tài liệu tự sinh kèm lineage graph.

13 thg 7, 2026 5