OverlayFS là gì và vì sao nó làm chậm Database khi chạy trong Docker

Trà đá nghe được anh bạn ngồi kế bên kể cho một bạn (chắc là mới vào công ty) về việc chạy database bằng docker luôn cho nó nhàn đầu. Thật sự thì đây luôn một chủ đề vẫn mang lại nhiều tranh luận từ trước tới nay giữa Dev và Ops. Về cơ bản thì tôi thấy chẳng có ai đúng mà cũng không có ai sai, quan trọng là trong bài toán nào với nguồn lực ra sao.

Bài này xin phép đi sâu vào phân tích vấn đề trên hệ thống mà tôi biết, mong rằng giúp những ai chưa biết thêm góc nhìn, thêm lựa chọn không bị đóng đinh suy nghĩ Phải thế này hay Phải thế kia nhé.

d7fed702-d4c8-4647-99cf-1632da91144c

Chúng ta nghĩ rằng khi một container chạy trên một node Linux thì nó sẽ đọc ghi trực tiếp vào ext4 hoặc xfs của host. Điều này nghe hợp lý nhưng lại không đúng bản chất kỹ thuật bên dưới.

Thực tế, container chạy trên một lớp filesystem copy-on-write tên là overlayfs. Và chỉ riêng cơ chế overlayfs đã đủ làm các hệ thống database như PostgreSQL, Redis, Kafka giảm hiệu năng theo cấp số nhân, không phải theo vài phần trăm như mọi người tưởng.

OverlayFS là gì và tại sao nó phá hiệu năng

OverlayFS là một filesystem hợp nhất nhiều tầng lại với nhau. Khi Docker chạy một container, nó tạo ra:

  • lowerdir là những layer image read-only
  • upperdir là lớp ghi copy-on-write
  • workdir là nơi kernel xử lý thao tác copy

Kernel hợp nhất lowerdir và upperdir thành một mount ảo mà container thấy như một root filesystem.

Điểm quan trọng nhất là: bất kỳ thao tác ghi nào vào một file thuộc lowerdir sẽ khiến kernel phải copy toàn bộ file đó lên upperdir trước khi ghi.

Việc này gây ra penalty đặc biệt lớn với hệ thống database vốn ghi rất nhiều.

Copy-up penalty: chỉ thay đổi 4KB nhưng kernel phải sao chép 4MB

Giả sử PostgreSQL có một file WAL segment kích thước 16MB được write tuần tự. Lần đầu PostgreSQL ghi vào file đó, overlayfs sẽ thực hiện copy-up toàn bộ file từ lowerdir sang upperdir. Một container mới start PostgreSQL và bắt đầu WAL write gần như luôn phải chịu copy-up tại byte đầu tiên.

Chắc bạn nào có để ý các bài tôi chia sẻ có thể thấy. Tôi đã benchmark khá nhiều các công cụ như điều này trên ext4 và xfs, và mức giảm hiệu năng luôn nằm giữa 5 lần đến 20 lần tùy workload.

Điều đáng nói là PostgreSQL không hề biết nó đang chạy trên overlayfs. Với PostgreSQL, đây đơn giản là một filesystem chậm (rất đáng để nói… nếu hệ thống lớn).

Metadata penalty: Kafka và Redis càng nhỏ càng chậm

Redis tạo ra một lượng metadata rất lớn khi chạy RDB snapshot hoặc AOF rewrite. Mỗi lần ghi file tạm, overlayfs phải quản lý metadata copy-up và whiteout để đánh dấu xóa file. Đặc biệt khi Kafka tạo hàng nghìn segment nhỏ, mỗi thao tác ghi nhỏ sẽ gây ra metadata churn rất nặng.

Trên bare-metal, Kafka tạo và ghi segment lên xfs rất nhanh vì xfs tối ưu allocation group. Nhưng trên overlayfs, mọi thao tác ghi và xóa đều đi qua một lớp translate khiến hiệu năng giảm đáng kể.

Chúng ta thường thấy Kafka trong container dễ bị disk throughput bão hòa hoặc high iowait bất thường. Đây chính là nguyên nhân gốc.

Whiteout và lý do tại sao xóa file trong container không làm nhẹ filesystem

Khi bạn xóa một file trong container, kernel không xóa file gốc trong lowerdir. Nó chỉ tạo một whiteout entry trong upperdir để che file đó đi.

Nghĩa là:

  • Xóa 10GB WAL cũ trong PostgreSQL không giải phóng 10GB thật
  • Kafka xóa log segment cũ nhưng kích thước image vẫn giữ nguyên
  • Redis rewrite AOF liên tục tạo ra rác trong lowerdir mà bạn không bao giờ dọn được

Điều này khiến database chạy trong Docker có cảm giác phình dần dù bạn đã cleanup.

Tại sao PostgreSQL trong Docker không đạt hiệu năng tối đa?

PostgreSQL phụ thuộc nặng vào fsync, fdatasync, và latency ổn định của block device. OverlayFS không cung cấp đặc tính đó.

Có ba lý do chính.

Copy-up phá vỡ mô hình ghi tuần tự của WAL

WAL write vốn tuần tự và linear, nhưng overlayfs buộc kernel phải thực hiện copy lên upperdir mỗi khi file thay đổi lần đầu. Điều này tạo ra latency spike hoàn toàn không đồng nhất.

Benchmark thực tế:

  • Bare-metal xfs: fsync cho WAL trung bình 0.3ms
  • Overlayfs trên cùng ổ đĩa: 3 đến 6ms

Đây không phải vấn đề PostgreSQL, đây là penalty COW.

Fsync trên overlayfs không flush đúng block device

Fsync trên overlayfs phải flush cả upperdir và đôi khi cả workdir. Điều này làm tăng syscall time và khiến checkpoint mất nhiều thời gian hơn.

Checkpoint của PostgreSQL khi chạy trong container chậm hơn 2 đến 5 lần.

VACUUM bị ảnh hưởng nặng

VACUUM tạo và xóa rất nhiều file nhỏ. Overlayfs xử lý delete bằng whiteout khiến VACUUM tốn I/O gấp nhiều lần và không reclaim được dung lượng thật.

Kết quả là hệ thống có cảm giác nặng dần theo thời gian.

Tại sao Redis trong Docker không đạt hiệu năng tối đa?

Redis có ba đặc tính gây xung đột với overlayfs.

Rewrite AOF tạo hàng triệu thao tác ghi nhỏ

AOF rewrite tạo chuỗi ghi nhỏ liên tục. Overlayfs phải translate mỗi ghi thành copy-on-write logic khiến latency tăng.

RDB snapshot là thao tác ghi file lớn

Lần đầu snapshot viết xuống một file lớn, overlayfs phải create file ở upperdir theo cơ chế COW, không phải write thẳng vào block device. Điều này làm giảm throughput.

Whiteout khiến ổ đĩa phình ra dù đã rewrite

Bạn rewrite AOF năm lần nhưng lowerdir vẫn chứa rác nên dung lượng thật của host không giảm.

Trên bare-metal, Redis AOF rewrite giữ hiệu năng cực ổn định. Trong container, độ trễ tăng theo thời gian.

Tại sao Kafka trong Docker không đạt hiệu năng tối đa?

Kafka có pattern write heavy và metadata heavy, hoàn toàn không tương thích với cơ chế copy-on-write của OverlayFS.

Ghi segment log liên tục

Mỗi segment mới là một file mới. Overlayfs phải quản lý metadata cho từng file, không được hưởng lợi từ allocation group của xfs như bare-metal.

Delete segment cũ là delete giả

Whiteout khiến Kafka không reclaim được dung lượng thật, làm node storage phình to.

Page cache và overlayfs không cộng hưởng tốt

Kafka rely vào page cache để tăng throughput nhưng overlayfs có tầng upperdir riêng, làm pattern caching không còn hiệu quả như trên block device trực tiếp.

Kết quả: Kafka trong Docker thường có throughput thấp hơn 30 đến 70 phần trăm so với bare-metal xfs (tỷ lệ này tùy hệ thống nhưng cơ bản tôi kiểm chứng thấy trong khoảng này).

Vậy có giải pháp nào để chạy database trong Docker mà vẫn nhanh?

Vẫn có. Và đây là phần có thể nhiều bạn không lưu ý.

1. Database không nên chạy trên overlayfs

Hãy chạy PostgreSQL, Redis, Kafka bằng volume type bind mount trỏ thẳng vào host filesystem.

2. Trong Kubernetes, dùng StatefulSet với volume block mode

Block mode tạo raw device và bỏ qua overlayfs hoàn toàn.

3. Dùng containerd và snapshotter loại block

btrfs snapshotter hoặc devmapper snapshotter cho hiệu năng tốt hơn overlayfs.

4. Luôn đặt log, WAL, AOF, RDB, segment log ra volume riêng

Không dùng rootfs layer của container.

Kết luận

Chắc chắn, như tôi cũng nói ngay ban đầu chạy Database hay bất kể công cụ nào bằng Docker có được không? Vẫn được. Thế giới vẫn dùng. Tuy nhiên, cũng phải hiểu đúng để vừa đảm bảo an toàn dữ liệu, tốc độ tối ưu và khả năng vận hành thực tế.

Với tôi thì những bài toán nhỏ, test nhanh hoặc thiếu người hiểu sâu vận hành hạ tầng hệ thống thì dùng Docker vẫn được và thật sự độ chênh lệch khi hạ tầng nhỏ rất khó nhận ra. Nhưng chắc chắn để làm những hệ thống lớn đảm bảo tối đa sự ổn định của hệ thống thì câu trả lời của tôi luôn luôn là chạy hạ tầng công cụ trực tiếp trên server.

Nếu mọi người quan tâm tôi sâu hơn để tiết kiệm thời gian nghiên cứu tôi có thể chi tiết thêm phần benchmark, fdatasync trace, iowait timeline và so sánh ext4 vs xfs vs overlayfs. Bạn nào làm tối ưu sâu cho các hệ thống lớn thì điều này cũng rất cần thiết và cũng là việc nên làm.

Thông tin nổi bật

Sự kiện phát trực tiếp​

Event Thumbnail

Báo cáo quan trọng

Article Thumbnail
Article Thumbnail
Chia sẻ bài viết:
Theo dõi
Thông báo của
4 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận

Tiêu điểm chuyên gia