Nguyên tắc là không đọc cả file vào bộ nhớ: đọc từng phần, xử lý, ghi ra hoặc cộng dồn kết quả rồi bỏ phần đó đi. Khi đó bộ nhớ dùng chỉ phụ thuộc kích thước một phần, không phụ thuộc kích thước file.
Cách 1 — generator, đọc từng dòng. Hợp khi mỗi dòng xử lý độc lập (lọc, chuyển đổi, đẩy lên API).
import csv
def read_rows(path):
with open(path, newline="", encoding="utf-8") as f:
yield from csv.DictReader(f)
def paid_orders(rows):
for row in rows:
if row["status"] == "paid":
yield row
total = sum(float(r["amount"]) for r in paid_orders(read_rows("orders.csv")))Mỗi bước là generator nên tại một thời điểm chỉ có một dòng nằm trong RAM.
Cách 2 — pandas theo chunk. Hợp khi cần thao tác kiểu bảng.
import pandas as pd
totals = pd.Series(dtype="float64")
for chunk in pd.read_csv("orders.csv", usecols=["country", "amount"], chunksize=1_000_000):
totals = totals.add(chunk.groupby("country")["amount"].sum(), fill_value=0)Chỉ đọc cột cần (usecols) và dùng kiểu gọn (category cho cột ít giá trị khác nhau) giảm thêm bộ nhớ; trong ví dụ của tài liệu pandas, chỉ đọc vài cột dùng khoảng 1/10 bộ nhớ.
Cách 3 — đổi công cụ. DuckDB hoặc Polars (chế độ lazy/streaming) query thẳng file lớn hơn RAM bằng SQL. Nếu đây là job chạy hằng ngày thì chuyển file sang Parquet và chạy bằng Spark.
Lưu ý: phép tính cần nhìn toàn bộ dữ liệu cùng lúc như sort toàn cục, dedup theo khoá hay join hai file lớn không làm đơn giản theo chunk được. Khi đó chia file theo hash của khoá ra nhiều file nhỏ rồi xử lý từng file, hoặc dùng công cụ biết ghi tạm ra đĩa (spill) như DuckDB, Spark.