Bắt bệnh "cục máu đông" dữ liệu: Quy trình ETL (Extract, Transform, Load) cứu rỗi hệ thống như thế nào?
Chào anh em Viblo
Khi lướt các tin tuyển dụng Backend hay Data Engineer dạo gần đây, chắc hẳn anh em rất hay bắt gặp từ khóa ETL. Nghe qua thì có vẻ cao siêu, mang ráng dấp của "Big Data", nhưng thực chất, 90% anh em Backend chúng ta đều đã và đang làm ETL mỗi ngày mà không hề nhận ra.
Nhớ lại cái đợt sấp mặt xử lý các lỗi đồng bộ dữ liệu cho hệ thống vé tự động AFC của tuyến Metro. Mỗi ngày, có hàng triệu transaction log từ các thiết bị cửa trạm do nhà thầu Hitachi đẩy về. Khổ nỗi, dữ liệu thô (raw data) gửi sang thì lộn xộn, lúc thì sai định dạng ngày tháng, lúc thì dính data rác, thậm chí là lỗi thiếu trường. Nhiệm vụ của mình là phải lôi đống hỗn độn đó về, "tắm rửa" sạch sẽ, chuẩn hóa lại rồi mới được nhét vào Elasticsearch và PostgreSQL để phục vụ cho các sếp xem báo cáo doanh thu.
Toàn bộ cái quy trình đau khổ nhưng đầy tính nghệ thuật đó, giới công nghệ gọi gọn trong 3 chữ: ETL (Extract - Transform - Load). Hôm nay, mình sẽ rã đông khái niệm này dưới góc nhìn của một gã Backend "đã từng trải" nhé.
1. Extract (Trích xuất) - Nghệ thuật "đi mót" dữ liệu
Bước đầu tiên của quy trình là Extract. Nôm na là bạn phải đi chui vào các nguồn dữ liệu (Source) để lôi data về. Nguồn ở đây có thể là bất cứ thứ gì: một con Database MySQL cũ rích của hệ thống legacy, một file CSV từ đối tác gửi qua FTP, một đống log từ Kafka, hoặc gọi API từ hệ thống bên thứ ba.
Cạm bẫy lớn nhất ở khâu này: Trích xuất làm sao để không đánh sập hệ thống gốc?
Nếu bạn chỉ có vài ngàn dòng data, một câu SELECT * là xong. Nhưng khi bạn có 50 triệu dòng log giao dịch, việc lôi tất cả về cùng lúc sẽ làm server Source lăn đùng ra chết vì quá tải (OOM - Out of Memory). Kinh nghiệm xương máu của mình là:
- Không bao giờ Full Load nếu không cần thiết: Lần đầu tiên chạy thì có thể lấy hết (Full Load), nhưng những lần sau, hãy chỉ lấy những dữ liệu mới sinh ra hoặc bị thay đổi (Incremental Load).
- Dùng chốt chặn thời gian (Watermark): Luôn lưu lại cái updated_at hoặc id lớn nhất của bản ghi cuối cùng bạn vừa lấy. Lần sau chạy, chỉ query những thằng lớn hơn cái mốc đó.
- Sử dụng CDC (Change Data Capture): Nếu dùng Database xịn, hãy ngâm cứu các công cụ đọc thẳng vào binlog của DB (như Debezium). Có sự thay đổi nào ở Source là nó tự động đẩy về hệ thống của bạn, không cần phải chọc query định kỳ nữa.
2. Transform (Biến đổi) - Khâu "hành xác" nhất nhưng giá trị nhất
Lấy được data về rồi thì khoan hãy mừng. Dữ liệu thô thường rất... bẩn. Transform là cái lò luyện đan, nơi bạn nhào nặn cục đất sét đó thành một bức tượng hoàn chỉnh. Quá trình này ngốn nhiều chất xám và logic code (thường viết bằng Node.js, Go hoặc Python) nhất.
Các "đòn tra tấn" dữ liệu thường gặp ở bước này:
- Data Cleaning (Làm sạch): Vứt bỏ những bản ghi rác (ví dụ ID bị null, email sai định dạng, timestamp bị âm).
- Format/Type Conversion (Ép kiểu): Chỗ thì lưu ngày tháng kiểu DD/MM/YYYY, chỗ thì lưu timestamp Epoch, chỗ thì dùng múi giờ Nhật Bản. Bạn phải quy hoạch tất cả về một chuẩn duy nhất (như Y-m-d H:i:s theo múi giờ UTC hoặc GMT+7).
- Enrichment (Làm giàu dữ liệu): Log giao dịch gửi về chỉ có station_id (mã trạm). Bạn phải dùng cái mã này đi tra (join) với một bảng danh mục khác để đắp thêm station_name (tên trạm) vào data trước khi lưu.
- Aggregation (Tổng hợp): Thay vì lưu 1000 giao dịch quẹt thẻ lẻ tẻ, bạn cộng dồn lại thành 1 bản ghi: "Tổng doanh thu trạm Bến Thành từ 8h-9h là bao nhiêu".
3. Load (Tải) - Đưa "cô dâu" về dinh
Sau khi dữ liệu đã sạch sẽ, thơm tho, bước cuối cùng là Load - nhét nó vào kho chứa cuối cùng (Target / Data Warehouse). Thường thì đó sẽ là một con Database chuyên dụng để phân tích, hoặc đổ vào Elasticsearch để search và lên Dashboard cho lẹ.
Những lưu ý sống còn khi Load:
- Phải dùng Bulk Insert: Đừng bao giờ lặp vòng for rồi gọi hàm Insert 10.000 lần. Database sẽ bóp cổ bạn vì ngập lụt connection. Hãy gom chúng lại thành từng mẻ (Batch/Chunk), ví dụ mỗi lần Insert một cục 1.000 bản ghi. Tốc độ sẽ tăng lên cả trăm lần.
- Upsert (Update or Insert): Trong quá trình chạy ETL, việc một job bị fail giữa chừng rồi chạy lại (retry) là chuyện cơm bữa. Nếu bạn chỉ dùng Insert thông thường, dữ liệu sẽ bị nhân đôi (Duplicate). Hãy luôn dùng cơ chế Upsert (như ON DUPLICATE KEY UPDATE trong MySQL). Nếu bản ghi đã tồn tại, cập nhật nó; nếu chưa có, tạo mới. Tính chất lũy đẳng (Idempotency) này sẽ cứu bạn khỏi những pha report số liệu sai lệch.
Lời kết
Quy trình ETL không phải là một công nghệ cụ thể, nó là một tư duy giải quyết vấn đề về luân chuyển dữ liệu. Dù bạn tự viết script bằng code chay (Laravel, Golang), hay dùng các tool xịn xò (như Apache Airflow, Talend, AWS Glue), thì bản chất vẫn là đi qua 3 bước: Trích xuất cẩn thận - Biến đổi sạch sẽ - Tải vào tối ưu.
Hiểu sâu về ETL không chỉ giúp anh em xử lý mượt mà các hệ thống tích hợp API, đồng bộ dữ liệu giữa các microservices, mà còn là bước đệm hoàn hảo nếu sau này anh em muốn lấn sân sang mảng Data Engineering đấy. Happy coding!
All rights reserved