Unix Philosophy & MapReduce — Xử Lý Batch Trong Hệ Phân Tán (DDIA)
Mở đầu
Ảnh: ThisisEngineering — Pexels
Cuốn Designing Data-Intensive Applications của Martin Kleppmann chia luồng dữ liệu trong hệ thống thành ba kiểu chính: services (request/response), event streams (message-passing), và batch processing. Hai chương trước đã nói về services và event streams — chương này tập trung vào batch processing, một trong những cách xử lý dữ liệu lâu đời nhất nhưng vẫn cực kỳ hiệu quả cho khối lượng lớn.
Điều thú vị là batch processing không phải là phát minh mới. Nó bắt nguồn từ triết lý Unix của những năm 1970 — ý tưởng viết những chương trình nhỏ, mỗi chương trình làm đúng một việc, rồi kết nối chúng lại với nhau. Google đã mang triết lý đó lên một tầm cao mới với MapReduce, cho phép xử lý petabyte dữ liệu trên hàng ngàn máy.
Triết lý Unix — Do One Thing Well
Ảnh: Pixabay — Pexels
Doug McIlroy, cha đẻ của Unix pipes, từng nói: "Hãy viết những chương trình chỉ làm một việc nhưng làm thật tốt. Hãy viết những chương trình có thể phối hợp với nhau." Đây là triết lý nền tảng của Unix — và cũng là cảm hứng cho MapReduce sau này.
Trong Unix, bạn có thể ghép các lệnh lại bằng pipe (|):
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
Mỗi lệnh trong pipeline này là một bộ lọc (filter) — nó đọc dữ liệu từ stdin, xử lý, và ghi ra stdout. Không có side-effect nào khác. Điều này cho phép bạn ghép chúng theo bất kỳ thứ tự nào, miễn là output của lệnh trước khớp với input của lệnh sau.
Ba đặc điểm chính của Unix pipeline:
- Composability: Các chương trình nhỏ ghép lại thành pipeline phức tạp
- Stateless: Mỗi chương trình không có state — input → output thuần tuý
- Lazy evaluation: Pipe dùng buffer, không ghi file tạm — tiết kiệm I/O
Sort — Vũ Khí Bí Mật Của Unix
Một trong những lệnh Unix thú vị nhất cho xử lý dữ liệu là sort. Nó sắp xếp toàn bộ dữ liệu đầu vào theo một hoặc nhiều key. Sắp xếp tưởng chừng đơn giản nhưng lại là nền tảng cho rất nhiều thao tác phức tạp:
- Grouping: Các dòng giống nhau sẽ nằm cạnh nhau sau sort → dễ dàng đếm, gộp
- Merging: Hai file đã sort có thể merge bằng
sort -m— rất nhanh - Joining:
joinlệnh kết nối hai file trên một key chung (giống SQL JOIN) - Unique:
uniqlọc dòng trùng — chỉ hoạt động sau sort
Chính nhờ sort mà các lệnh Unix có thể làm được nhiều việc mà không cần quản lý vị trí dữ liệu — sort là điểm tập trung thông tin.
MapReduce — Unix Cho Distributed Systems
Năm 2004, Google công bố MapReduce — một framework batch processing cho phép xử lý dữ liệu trên hàng ngàn máy. Cái tên MapReduce phản ánh hai bước chính:
- Map: Áp dụng một hàm xử lý lên từng record — output là các cặp key-value (giống
awktrong Unix) - Reduce: Gom các cặp key-value theo key, rồi áp dụng hàm aggregate (giống
sort | uniq -c)
Giữa Map và Reduce là bước shuffle — hệ thống tự động sắp xếp output theo key, chuyển các cặp có cùng key về cùng một reducer. Bước này tương đương với sort trong Unix.
MapReduce vs Unix Pipes
Ảnh: Negative Space — Pexels
MapReduce có thể coi là "Unix pipes cho distributed systems". Cả hai cùng dùng chung một triết lý: ghép các toán tử nhỏ thành pipeline xử lý dữ liệu. Nhưng có những khác biệt quan trọng:
| Đặc điểm | Unix Pipes | MapReduce |
|---|---|---|
| Phạm vi | Một máy | Hàng ngàn máy |
| Fault tolerance | Không (crash = mất tất cả) | Có (tự động restart task failed) |
| I/O | Streaming (pipe buffer) | Ghi file (HDFS/GFS) |
| Độ trễ | < 1 giây | Vài phút đến giờ |
| Lazy | Có (streaming) | Không (materialize kết quả trung gian) |
MapReduce ghi kết quả trung gian ra distributed filesystem thay vì pipe trong memory. Điều này làm tăng độ trễ nhưng mang lại fault tolerance — nếu một task chết, task khác có thể đọc lại file và chạy tiếp.
MapReduce Workflow Cụ Thể
Một job MapReduce điển hình trải qua các bước:
- Input split: Dữ liệu đầu vào được chia thành các split (thường 64-256MB mỗi split)
- Map: Mỗi split được xử lý bởi một mapper — mapper đọc dữ liệu và emit các cặp (key, value)
- Shuffle + Sort: Các cặp key-value được sort theo key và phân phối đến reducer. Tất cả cặp có cùng key sẽ về cùng một reducer
- Reduce: Reducer nhận danh sách (key, list[value]) và aggregate — có thể là count, sum, filter, hoặc bất kỳ logic nào
- Output: Mỗi reducer ghi kết quả ra một file (thường là trên HDFS)
Quan trọng: MapReduce KHÔNG có index. Nó quét toàn bộ dữ liệu mỗi lần chạy. Đây là lý do nó phù hợp cho batch processing (quét TB-PB dữ liệu) nhưng không phù hợp cho OLTP.
MapReduce Trong Thực Tế
Mặc dù MapReduce đã được thay thế bởi các engine hiện đại hơn (Spark, Flink, Hive, Pig), triết lý của nó vẫn sống mãi. Các hệ thống batch processing ngày nay vẫn dùng mô hình "map-filter-reduce" với shuffle làm bước tập trung dữ liệu.
Spark khác MapReduce ở chỗ nó giữ dữ liệu trong memory giữa các bước (thay vì ghi disk), cho phép xử lý nhanh hơn gấp 10-100 lần cho các workflow cần nhiều map-reduce nối tiếp. Nhưng Spark vẫn kế thừa chính triết lý: chia nhỏ công việc, shuffle để group, reduce để aggregate.
Key Takeaways
- Triết lý Unix (do one thing well, compose with pipes) là nền tảng tư tưởng cho batch processing hiện đại
- Sort là building block quan trọng — nó biến dữ liệu không có thứ tự thành có thứ tự, cho phép grouping và joining
- MapReduce mở rộng triết lý Unix ra distributed systems — map = awk, shuffle = sort, reduce = uniq/aggregate
- MapReduce ghi intermediate state ra disk (HDFS) để đạt fault tolerance, đánh đổi lấy độ trễ cao hơn
- Các engine hiện đại (Spark, Flink) cải thiện performance nhưng vẫn giữ nguyên mô hình map-shuffle-reduce
- Batch processing phù hợp cho workloads quét toàn bộ dữ liệu (ETL, analytics, indexing) — không cần index
Kết
Điều làm mình thấy thú vị nhất ở chương này là cách một triết lý từ những năm 1970 (Unix pipes) lại trở thành cảm hứng cho một trong những hệ thống xử lý dữ liệu lớn nhất của thế kỷ 21. MapReduce không phải là công nghệ mới — nó là cách nhìn cũ được áp dụng ở quy mô lớn hơn.
Chương tiếp theo sẽ đi sâu vào chi tiết — cách MapReduce thực sự hoạt động với join, grouping, và handle skew.