Python hiện đại 1 — Bộ công cụ data 2025: tổng quan

13 thg 7, 2026 3 lượt xem
#data-engineering
#python
#polars
#duckdb
#modern-python

Vì sao "Python cho data" của 2025 khác hẳn 2020

Nếu bạn học phân tích dữ liệu bằng Python cách đây năm, sáu năm, gần như chắc chắn bạn học import pandas as pd, df = pd.read_csv(...), rồi xoay xở với groupby, merge, apply. Đó là thế giới cũ — và nó vẫn còn giá trị. Nhưng giai đoạn 2023–2025 chứng kiến một sự dịch chuyển thật sự trong cách một data engineer hay data analyst người Việt làm việc hằng ngày. Không phải trào lưu nhất thời, mà là sự trưởng thành của một thế hệ công cụ mới, phần lớn viết bằng Rust, giải quyết đúng những điểm đau mà ai từng xử lý dữ liệu ngân hàng đều thấm: file quá lớn so với RAM, pandas ngốn bộ nhớ và chạy một lõi, môi trường cài đặt rối rắm, và code không có kiểm tra kiểu (type check) nên lỗi chỉ lộ ra lúc chạy.

Bài này là bài mở màn của series Python hiện đại cho Data. Mục tiêu không phải dạy chi tiết từng công cụ (mỗi công cụ có bài riêng), mà vẽ bản đồ để bạn hiểu bức tranh tổng thể: có những mảnh ghép nào, mỗi mảnh giải quyết vấn đề gì, chúng nối với nhau ra sao, và quan trọng nhất — khi nào dùng gì. Sau bài này bạn sẽ biết mình cần đọc bài nào tiếp theo cho công việc trước mắt.

Bốn sức ép tạo ra làn sóng mới

1. Dữ liệu lớn hơn RAM một máy — nhưng chưa cần Spark. Đây là "vùng lửng" phổ biến nhất và cũng khó chịu nhất. Một file sao kê tổng hợp vài chục triệu dòng có thể nặng 5–20 GB. Nó không vừa RAM 16 GB của laptop khi mở bằng pandas (pandas thường cần bộ nhớ gấp 2–5 lần kích thước file), nhưng dựng cả một cụm Spark để xử lý thì quá nặng nề, tốn tiền và chậm khởi động. Thế hệ công cụ mới sinh ra chính cho vùng này: xử lý được dữ liệu lớn hơn RAM trên một máy.

2. pandas chậm và ngốn RAM. pandas ra đời 2008, kiến trúc dựa trên NumPy, chạy một lõi (single-thread) cho hầu hết thao tác và giữ toàn bộ dữ liệu trong bộ nhớ theo mô hình eager (làm tới đâu tính tới đó). Với dữ liệu vừa và nhỏ nó tuyệt vời; với dữ liệu lớn nó hụt hơi thấy rõ.

3. Rust khiến mọi thứ nhanh hơn nhiều. Polars, phần lõi của DuckDB, uv, ruff — đều viết bằng Rust hoặc C++, tận dụng đa lõi, SIMD, và quản lý bộ nhớ chặt chẽ. Người dùng cuối vẫn viết Python, nhưng phần nặng chạy bằng code biên dịch tốc độ cao.

4. Type safety và tooling đã trưởng thành. Type hints (gợi ý kiểu) của Python nay đã đủ chín; mypy/pyright bắt lỗi trước khi chạy; Pydantic v2 validate dữ liệu ở biên hệ thống; uv và ruff biến việc dựng môi trường và giữ code sạch thành chuyện vài giây. Python "cho data" năm 2025 không chỉ nhanh hơn mà còn đáng tin hơn.


Bản đồ bộ công cụ hiện đại

Hãy nhìn toàn cảnh trước, rồi đi vào từng mảnh. Sơ đồ dưới đây là "địa hình" mà cả series sẽ đi qua: từ khâu nạp dữ liệu, tới xử lý bằng Polars/DuckDB (kết nối qua Arrow), tới kiểm định và I/O bất đồng bộ, rồi kiểm thử.

Polars — DataFrame nhanh, lazy, đa lõi

Polars là thư viện DataFrame viết bằng Rust, đóng vai trò thay thế hoặc bổ sung cho pandas. Ba đặc điểm định hình nó:

  • Đa lõi (multi-threaded): mặc định dùng hết các nhân CPU, không cần bạn cấu hình gì.
  • Lazy execution: bạn mô tả chuỗi phép biến đổi (đọc, lọc, gộp nhóm...) rồi Polars xây một kế hoạch truy vấn, tối ưu nó (đẩy bộ lọc xuống sát nguồn — predicate pushdown, chỉ đọc cột cần — projection pushdown), và chỉ thực thi khi bạn gọi .collect(). Nhờ vậy nó có thể xử lý dữ liệu lớn hơn RAM bằng cách streaming.
  • Dựa trên Arrow: bố cục bộ nhớ theo chuẩn Apache Arrow, nền tảng cho zero-copy (chia sẻ dữ liệu không sao chép).

Chi tiết cú pháp, lazy vs eager, so sánh trực tiếp với pandas — xem Python hiện đại 2 — Polars.

DuckDB — CSDL OLAP nhúng, chạy SQL ngay trên file

DuckDB thường được ví là "SQLite cho phân tích". Nó là một cơ sở dữ liệu OLAP (phân tích, cột hoá) chạy nhúng ngay trong tiến trình Python của bạn — không server, không cài đặt, chỉ pip install duckdb. Điều khiến nó đặc biệt hữu ích: bạn viết SQL thẳng trên file Parquet/CSV/Arrow mà không cần nạp trước vào database.

import duckdb
# Query SQL ngay trên file Parquet, không cần import trước (minh hoạ)
duckdb.sql("""
    SELECT chi_nhanh, COUNT(*) AS so_gd, SUM(so_tien) AS tong_tien
    FROM 'sao_ke_2025.parquet'
    WHERE loai_gd = 'debit'
    GROUP BY chi_nhanh
    ORDER BY tong_tien DESC
""").show()

Với ai đã quen SQL — phần lớn cán bộ nghiệp vụ và analyst ngân hàng — DuckDB là cây cầu ngắn nhất để phân tích cục bộ mạnh mẽ. Chi tiết ở Python hiện đại 3 — DuckDB.

Apache Arrow — chuẩn cột trong bộ nhớ, nền của mọi thứ

Apache Arrow không phải công cụ bạn dùng trực tiếp hằng ngày, mà là chuẩn nền mà các công cụ khác đứng trên. Arrow định nghĩa một cách sắp xếp dữ liệu dạng bảng trong bộ nhớ theo cột (columnar), thống nhất giữa các thư viện. Lợi ích cốt lõi là zero-copy: Polars, DuckDB, và pandas (nhờ backend Arrow từ pandas 2.0) có thể chuyển dữ liệu qua lại mà không sao chép hay chuyển đổi định dạng. Một DataFrame Polars có thể đưa sang DuckDB để chạy SQL, rồi nhận kết quả về, gần như tức thì.

Chính Arrow là lý do bản đồ ở trên có thể "ghép" liền mạch: nó là ngôn ngữ chung của bộ nhớ. Series này giả định Arrow là nền và sẽ nhắc lại khi liên quan (đọc thêm nền tảng ở Python DE 3 — Polars & Arrow).

uv & ruff — nền tảng dự án nhanh gọn

Hai công cụ của Astral (cũng viết bằng Rust) thay đổi trải nghiệm dựng và giữ dự án:

  • uv — quản lý gói và môi trường ảo (venv) siêu nhanh, thay cho pip + virtualenv + pip-tools, nhanh hơn hàng chục lần khi giải phụ thuộc và cài đặt. Quản lý pyproject.toml, khoá phiên bản (lockfile) để môi trường tái lập được.
  • ruff — linter kiêm formatter, gộp vai trò của flake8, isort, black và nhiều plugin, chạy gần như tức thì trên cả codebase lớn.

Chi tiết thiết lập và quy trình chuẩn ở Python hiện đại 4 — uv & ruff.

Pydantic v2, async, pytest — phần "kỹ sư" của bộ công cụ

  • Pydantic v2 + type hints — validate và ép kiểu dữ liệu ở biên hệ thống (dữ liệu từ API, file cấu hình, message queue), với lõi Rust nên nhanh hơn nhiều v1. Là cách chuẩn để "chốt hợp đồng dữ liệu". Xem Python hiện đại 5 — Pydantic & typing.
  • async / await — mô hình bất đồng bộ cho tác vụ I/O (gọi hàng trăm API, đọc/ghi mạng đồng thời). Không làm CPU nhanh hơn, nhưng loại bỏ thời gian chờ mạng. Xem Python hiện đại 6 — async & concurrency.
  • pytest & chất lượng — kiểm thử, fixtures, kiểm thử pipeline dữ liệu, cùng mypy/ruff tạo "lưới an toàn". Xem Python hiện đại 7 — testing & quality.

Khi nào dùng gì: cây quyết định

Câu hỏi thực dụng nhất là: đứng trước một khối dữ liệu, tôi nên chọn công cụ nào? Cây quyết định dưới đây gói gọn nguyên tắc.

Diễn giải thành bảng:

Tình huốngCông cụ nên chọnVì sao
Dữ liệu vừa RAM, cần hệ sinh thái rộng (viz, ML)pandasTrưởng thành, tương thích mọi thư viện
Dữ liệu vừa/lớn, cần tốc độ & đa lõiPolarsNhanh, lazy, tiết kiệm RAM
Lớn hơn RAM, một máy, quen SQLDuckDBSQL thẳng trên Parquet, streaming
Lớn hơn RAM, một máy, quen DataFramePolars lazyStreaming, tối ưu kế hoạch
TB+, nhiều máy, đã có hạ tầng cụmSparkPhân tán thật sự

Ranh giới Polars/DuckDB ↔ Spark là điểm hay bị nhầm. Nhiều đội theo phản xạ "dữ liệu lớn thì Spark", nhưng thực tế phần lớn công việc phân tích hằng ngày nằm dưới ngưỡng vài trăm GB và chạy nhanh hơn, rẻ hơn trên một máy với Polars/DuckDB. Chỉ khi dữ liệu thật sự tới hàng terabyte, cần tính toán phân tán trên nhiều máy, hoặc bạn đã có sẵn lakehouse trên cụm, thì Spark mới xứng đáng với độ phức tạp của nó — xem Spark 1 — Tổng quan. Và pandas vẫn hợp khi dữ liệu nhỏ, khi bạn cần hệ sinh thái quanh nó (matplotlib, scikit-learn), hoặc khi cả đội đã quen tay và bài toán không đòi hỏi tốc độ.


Nguyên tắc Python "sạch" cho data

Bộ công cụ chỉ phát huy khi đi kèm vài nguyên tắc. Cả series được dệt quanh bốn nguyên tắc này:

  1. Type hints ở mọi biên. Hàm nhận/trả gì, cấu hình có những trường nào — khai báo kiểu rõ ràng để mypy/pyright bắt lỗi sớm và người đọc hiểu ngay ý định.
  2. Môi trường tái lập. Một pyproject.toml + lockfile qua uv, để "chạy được trên máy tôi" cũng là "chạy được trên máy bạn" và trên production. Không còn cảnh môi trường lệch phiên bản.
  3. Lazy & vectorize. Ưu tiên phép toán vector hoá trên cả cột thay vì vòng lặp Python từng dòng; ưu tiên lazy để engine tối ưu toàn bộ chuỗi biến đổi trước khi chạy. Đây là nguồn tăng tốc lớn nhất, thường gấp nhiều lần.
  4. Arrow để nối công cụ. Chọn định dạng và bố cục bộ nhớ tương thích Arrow (Parquet trên đĩa, Arrow trong bộ nhớ) để chuyển dữ liệu giữa Polars, DuckDB, pandas mà không tốn chi phí sao chép.

Ví dụ ngắn: Polars lazy + DuckDB trên cùng dữ liệu

Đây là minh hoạ tinh thần "một máy, hai công cụ, một dữ liệu, nối qua Arrow". Ta đọc Parquet bằng Polars ở chế độ lazy, rồi chạy một truy vấn SQL bằng DuckDB ngay trên chính DataFrame Polars đó — không xuất ra file trung gian, không sao chép.

import polars as pl
import duckdb

# 1) Polars lazy: mô tả chuỗi biến đổi, chưa thực thi (minh hoạ)
lazy = (
    pl.scan_parquet("sao_ke_2025.parquet")   # scan = lazy, chưa nạp
      .filter(pl.col("loai_gd") == "debit")   # predicate pushdown
      .select(["chi_nhanh", "so_tien"])       # projection pushdown
)
df = lazy.collect()   # tới đây engine mới chạy, tối ưu toàn kế hoạch

# 2) DuckDB query thẳng trên DataFrame Polars (zero-copy qua Arrow)
ket_qua = duckdb.sql("""
    SELECT chi_nhanh, COUNT(*) AS so_gd, SUM(so_tien) AS tong_tien
    FROM df
    GROUP BY chi_nhanh
    ORDER BY tong_tien DESC
    LIMIT 10
""").pl()   # trả về lại Polars DataFrame

Lưu ý: khối SQL trên là DuckDB, chạy trong Python trên DataFrame trong bộ nhớ — không phải PostgreSQL, nên đây chỉ là minh hoạ, không đánh dấu chạy được. Điều đáng chú ý về mặt kiến trúc: DuckDB đọc trực tiếp biến df của Polars như một bảng, nhờ cả hai cùng nói "tiếng Arrow". Bạn tự do chọn công cụ hợp với từng bước — DataFrame hay SQL — trên cùng một khối dữ liệu.


Use case thực tế

Bối cảnh (NCB). Một analyst rủi ro cần phân tích file sao kê tổng hợp quý — khoảng 40 triệu dòng, xuất từ core banking ra Parquet, nặng chừng 8–10 GB. Yêu cầu: với mỗi chi nhánh, tính tổng số tiền giao dịch debit, số giao dịch, và giá trị trung vị, để lọc ra các chi nhánh có biến động bất thường trong quý.

Cách cũ. Mở bằng pandas trên laptop RAM 16 GB thường bất khả thi: pandas cần bộ nhớ gấp nhiều lần kích thước file, tiến trình dễ bị "Killed" vì hết RAM (out-of-memory). Phương án còn lại là gửi job lên cụm Spark dùng chung — nhưng phải xin quyền, xếp hàng chờ tài nguyên, chờ cụm khởi động, và một truy vấn tổng hợp đơn giản cũng mất hàng chục phút tới cả tiếng tính cả thời gian chờ. Với một câu hỏi thăm dò, vòng lặp thử–sai như vậy quá chậm.

Cách mới (Polars + DuckDB, một laptop). Analyst dùng pl.scan_parquet (lazy) để chỉ đọc hai cột cần thiết và đẩy bộ lọc debit xuống sát nguồn — Polars streaming xử lý được khối lớn hơn RAM vì không nạp toàn bộ cùng lúc. Phần tổng hợp theo nhóm và trung vị viết bằng SQL DuckDB cho gần với tư duy nghiệp vụ. Toàn bộ chạy cục bộ, không cần cluster.

Ước lượng (không phải số đo chuẩn — tuỳ phần cứng, cần đo lại tại chỗ):

Chỉ sốpandasSpark cụm dùng chungPolars + DuckDB (laptop)
Thời gian tới kết quảthường OOM~30–60 phút (gồm chờ)ước lượng vài chục giây tới ~2 phút
RAM đỉnhvượt 16 GB(trên cụm)ước lượng dưới ngưỡng RAM nhờ streaming
Chi phí hạ tầng0tài nguyên cụm0
Vòng lặp thử–saichậmrất chậmnhanh, làm tại chỗ

Kết quả nghiệp vụ. Analyst chạy đi chạy lại nhiều biến thể câu hỏi ngay trên laptop trong một buổi sáng, thay vì mỗi lần chờ cluster nửa tiếng. Cụm Spark được dành cho những khối dữ liệu thật sự cỡ terabyte và các pipeline sản xuất định kỳ — đúng chỗ nó phát huy. Xin nhấn mạnh: mọi con số ở trên là ước lượng minh hoạ để thấy độ lớn tương đối, không phải benchmark; hãy đo trên chính dữ liệu và máy của bạn.


Lộ trình series

Các bài tiếp theo đi sâu từng mảnh của bản đồ:


Ghi nhớ

  • Giai đoạn 2023–2025, bộ công cụ Python cho data đổi thật sự — chủ yếu nhờ Rust: nhanh hơn, tiết kiệm RAM hơn, tooling và type safety trưởng thành.
  • Có một "vùng lửng" quan trọng: dữ liệu lớn hơn RAM một máy nhưng chưa cần Spark. Đây chính là sân nhà của Polars và DuckDB.
  • Polars = DataFrame nhanh, lazy, đa lõi (Rust) — thay/bổ sung pandas. DuckDB = CSDL OLAP nhúng, chạy SQL thẳng trên Parquet/CSV/Arrow. Arrow = chuẩn cột trong bộ nhớ, nền zero-copy nối chúng lại.
  • uv (gói/venv) và ruff (lint+format) làm nền dự án nhanh gọn; Pydantic v2 validate ở biên; async cho I/O; pytest cho chất lượng.
  • Cây quyết định: vừa RAM → pandas/Polars; lớn hơn RAM, một máy → Polars (DataFrame) hoặc DuckDB (SQL); TB+, phân tán → Spark. pandas vẫn hợp khi dữ liệu nhỏ hoặc cần hệ sinh thái quanh nó.
  • Bốn nguyên tắc Python "sạch" cho data: type hints, môi trường tái lập (uv/pyproject), lazy & vectorize, Arrow để nối công cụ.
  • Polars và DuckDB dùng chung được một khối dữ liệu trong bộ nhớ nhờ Arrow (zero-copy) — chọn DataFrame hay SQL tuỳ từng bước.
  • Với NCB, xử lý file sao kê vài chục triệu dòng trên laptop bằng Polars+DuckDB rút vòng lặp thử–sai từ hàng chục phút (chờ cluster) xuống ước lượng vài chục giây — số liệu chỉ minh hoạ, cần tự đo.

Nguồn tham khảo

Bài viết liên quan

Vì sao Python là ngôn ngữ số một của data engineer: vai trò trong pipeline (ingest/transform/orchestrate), hệ sinh thái thư viện (pandas/polars/pyarrow/sqlalchemy), quản lý môi trường (venv/uv/poetry), và khi nào dùng Python vs SQL/Spark.

13 thg 7, 2026 6

Học cách tổ chức code Python: định nghĩa hàm với tham số vị trí/từ khoá/mặc định, *args/**kwargs, lambda và hàm bậc cao, closure, decorator, generator với yield. Đóng gói code thành module và package, cô lập thư viện bằng môi trường ảo venv, quản lý phụ thuộc với pip và requirements.txt để dự án tái lập được trên mọi máy.

13 thg 7, 2026 5

Biến script thành pipeline đáng tin cậy: cấu trúc project & packaging (uv/poetry), type hints & pydantic, kiểm thử với pytest, logging & cấu hình, đóng gói Docker, và tích hợp CI cho code dữ liệu.

13 thg 7, 2026 5

Hướng dẫn OOP trong Python từ class/instance, kế thừa và super(), đa hình & duck typing, encapsulation tới dunder methods, @property, classmethod/staticmethod, dataclass và type hints (mypy). Kèm nguyên tắc clean code: đặt tên rõ nghĩa, hàm nhỏ, DRY, SOLID cùng chuẩn PEP8 với công cụ ruff/black.

13 thg 7, 2026 5

Cảm nhận của bạn

Bình luận

Bạn cần để viết bình luận.

Chưa có bình luận. Hãy là người đầu tiên chia sẻ!