+2

"Under the Hood": Database thực sự lưu dữ liệu xuống ổ đĩa như thế nào?

Chào các bạn,

Khi làm việc với SQL hay NoSQL, chúng ta thường thao tác với các khái niệm "sang chảnh" như Table, Document, Row, hay Column. Nhưng đã bao giờ bạn tự hỏi: Phía sau lớp vỏ bọc đó, trên ổ đĩa (HDD/SSD) vật lý, dữ liệu thực sự nằm ở đâu và trông như thế nào chưa?

Tại sao Insert 1 triệu bản ghi lại chậm? Tại sao Index lại giúp tìm kiếm nhanh? Câu trả lời nằm ở cách Database "xếp hình" dữ liệu xuống đĩa cứng.

1. Đơn vị cơ bản: Không phải Row, mà là "Page"

Đĩa cứng không đọc từng byte dữ liệu. Thay vào đó, nó đọc theo từng khối (block). Database cũng vậy, nó chia dữ liệu thành các đơn vị gọi là Pages (thường là 8KB hoặc 16KB tùy hệ quản trị).

Mọi thao tác đọc/ghi đều diễn ra trên Page. Khi bạn muốn đọc 1 Row, Database sẽ bốc nguyên cả cái Page chứa Row đó lên RAM.

  • Header: Lưu thông tin về Page (ID, loại dữ liệu, dung lượng còn trống).
  • Data/Slots: Nơi chứa nội dung thực sự của các bản ghi.
  • Offset Table: Một bảng mục lục nhỏ ở cuối Page để chỉ ra chính xác vị trí bắt đầu của mỗi Row.

2. Hai "trường phái" lưu trữ: Row-oriented vs. Column-oriented

Cách sắp xếp dữ liệu trong Page sẽ quyết định hiệu năng của hệ thống.

  • Row-oriented (Lưu theo dòng): Dữ liệu của cùng một Row sẽ nằm cạnh nhau. Đây là cách các "ông lớn" OLTP như MySQL, PostgreSQL hoạt động.
  • Ưu điểm: Cực nhanh khi bạn cần lấy toàn bộ thông tin của 1 User (Select * where id = 1).
  • Nhược điểm: Chậm khi bạn muốn tính trung bình cộng tuổi của 1 triệu User (phải load toàn bộ dữ liệu thừa lên RAM).
  • Column-oriented (Lưu theo cột): Dữ liệu của cùng một cột sẽ nằm cạnh nhau. Đại diện tiêu biểu là BigQuery, ClickHouse hoặc Apache Parquet.
  • Ưu điểm: Thần tốc cho các tác vụ phân tích (Analytics). Nếu chỉ cần tính lương trung bình, nó chỉ đọc đúng cột "Lương".
  • Nhược điểm: "Ác mộng" khi cần Insert 1 bản ghi mới vì phải nhảy đến nhiều vị trí khác nhau trên đĩa để ghi cho từng cột.

3. Cấu trúc dữ liệu: Tại sao lại là B-Tree?

Làm sao Database biết Row mình cần nằm ở Page nào? Nó cần một "bản đồ", đó chính là Index.

Đa số các Database quan hệ sử dụng cấu trúc B+Tree. Hãy tưởng tượng nó như một cây thư mục phân cấp. Thay vì quét từ đầu đến cuối 1 tỷ bản ghi (O(n)), B+Tree cho phép bạn tìm thấy dữ liệu chỉ sau vài bước nhảy (O(log n)).

Mỗi "nút" trên cây thực chất là một Page. Các nút lá (leaf nodes) ở dưới cùng sẽ chứa dữ liệu thật hoặc con trỏ trỏ tới dữ liệu thật. Điểm hay của B+Tree là nó giữ cho các Page luôn được sắp xếp, giúp việc tìm kiếm theo khoảng (ví dụ: lấy user có tuổi từ 20-30) cực kỳ hiệu quả.

4. Bí mật của Write-Ahead Logging (WAL)

Ghi dữ liệu xuống đĩa là một thao tác đắt đỏ và chậm chạp. Nếu mỗi khi có lệnh INSERT, Database lại phải đi tìm đúng cái Page đó trên đĩa để sửa, thì hệ thống sẽ nghẽn cổ chai ngay lập tức.

Để tối ưu, Database sử dụng cơ chế WAL:

  1. Khi có thay đổi, Database ghi ngay một dòng log cực ngắn gọn vào file gọi là Redo Log (đây là ghi tuần tự, nên rất nhanh).
  2. Sau đó, nó mới cập nhật Page tương ứng trên RAM.
  3. Vào một thời điểm "rảnh rỗi" (Checkpoint), nó mới lẳng lặng đồng bộ các Page đã sửa trên RAM xuống đĩa cứng.

Nếu chẳng may mất điện? Đừng lo, khi khởi động lại, Database sẽ đọc file WAL để "diễn lại" (replay) các thao tác và khôi phục dữ liệu.

Lời kết

Database không phải là một "hộp đen" ma thuật. Nó là một kiệt tác về kỹ thuật tối ưu hóa phần cứng. Việc hiểu rõ Page, B-Tree và cơ chế ghi log sẽ giúp bạn thiết kế Database schema thông minh hơn, viết câu Query chuẩn hơn và đặc biệt là không còn "bàng hoàng" khi hệ thống gặp vấn đề về hiệu năng.

Anh em có muốn mình đi sâu hơn vào cách LSM-Tree (thứ giúp NoSQL ghi dữ liệu nhanh như chớp) hoạt động không? Hãy để lại comment nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí