Logo

Personal Ops Runbook

Personal runbook covering infrastructure operations for Cloud, Kubernetes, OpenStack, and Ceph environments. Includes deployment and teardown procedures, node management, cluster monitoring setup, and incident response workflows compiled from day-to-day operational work. Intended strictly for personal reference — configurations and scripts are environment-specific and not guaranteed to work as-is elsewhere.

Tiêu đề: Mô phỏng quá trình ghi dữ liệu qua Page Cache và lý do vẫn cần Redis/Memcached


1. Tổng quan

Hệ điều hành hiện đại (như Linux) không ghi dữ liệu trực tiếp từ ứng dụng xuống đĩa mà đi qua cơ chế Page Cache trong RAM để tăng hiệu năng. Tuy nhiên, trong thực tế, ta vẫn thấy nhiều hệ thống sử dụng các công cụ cache ở tầng ứng dụng như Redis hay Memcached. Bài viết này sẽ giải thích cơ chế Ứng dụng → VFS → Page Cache (RAM) → Disk, lý do vẫn cần cache ở tầng ứng dụng, cách mô phỏng việc ghi dữ liệu chưa kịp xuống đĩa khi máy bị tắt đột ngột, và hiện tượng “thấy dữ liệu trong tail -f nhưng vẫn mất sau crash”.


2. Cơ chế ghi dữ liệu trong hệ điều hành

2.1 Chuỗi xử lý dữ liệu

Khi một ứng dụng ghi file, dữ liệu đi theo luồng:

[Ứng dụng] 
     │
     ▼
[VFS - Virtual File System]
     │
     ▼
[Page Cache (RAM)]  ← dữ liệu tạm nằm ở đây
     │
 (sau vài giây hoặc fsync)
     ▼
[Disk - Ghi thật]
  • write() chỉ ghi dữ liệu vào Page Cache.
  • fsync() ép kernel ghi xuống đĩa ngay.
  • Nếu không có fsync(), kernel sẽ ghi xuống đĩa tự động sau vài giây hoặc khi RAM gần đầy.

Cơ chế này giúp tốc độ ghi cực nhanh vì phần lớn dữ liệu nằm tạm trong RAM trước khi được flush xuống ổ cứng.


3. Vì sao vẫn cần Redis/Memcached?

Dù hệ điều hành đã có Page Cache, ta vẫn cần cache ở tầng ứng dụng vì hai loại cache này phục vụ mục đích khác nhau.

Tiêu chí Page Cache Redis / Memcached
Vị trí hoạt động Kernel (hệ điều hành) Ứng dụng / dịch vụ riêng
Dữ liệu cache File, block dữ liệu Dữ liệu logic (key-value, JSON, kết quả truy vấn, session, v.v.)
Hiểu ngữ cảnh ứng dụng Không
Khả năng chia sẻ giữa máy Không Có (cluster/distributed)
Quyền kiểm soát Kernel tự quyết Lập trình viên chủ động điều khiển TTL, invalidation
Tính bền vững Cuối cùng được ghi ra đĩa Tuỳ chọn (thường là tạm thời)

Ví dụ:

  • Kernel không biết rằng chuỗi "SELECT * FROM products WHERE category='phone'" sẽ được dùng lại nhiều lần.
  • Redis có thể lưu kết quả đó trực tiếp và trả về trong mili-giây, bỏ qua database và disk hoàn toàn.

4. Vì sao tail -f thấy dữ liệu ngay cả khi chưa ghi ra đĩa?

Lệnh tail -f đọc dữ liệu từ Page Cache. Khi ứng dụng ghi log, dữ liệu mới đã nằm trong RAM và có thể được đọc lại tức thì, dù chưa được flush xuống disk. Vì thế, nếu hệ thống bị tắt trước khi dữ liệu đó được ghi thực sự, phần log đó có thể biến mất sau khi khởi động lại.


5. Cách mô phỏng việc “ghi nhưng chưa kịp lưu” (mất dữ liệu sau crash)

5.1 Chuẩn bị môi trường thử nghiệm

Chỉ thực hiện trong máy ảo (VM) hoặc hệ thống tách biệt. Tuyệt đối không thử trên máy thật.

5.2 Các bước thực hiện

  1. Tạo “đĩa” thử nghiệm không có journaling

bash dd if=/dev/zero of=/tmp/disk.img bs=1M count=200 sudo losetup -fP /tmp/disk.img sudo mkfs.ext4 -O ^has_journal /dev/loop10 sudo mkdir -p /mnt/testdisk sudo mount /dev/loop10 /mnt/testdisk

  1. Kéo dài thời gian flush xuống đĩa

bash sudo sysctl -w vm.dirty_writeback_centisecs=60000 sudo sysctl -w vm.dirty_expire_centisecs=60000

  1. Ghi dữ liệu không dùng fsync()

bash bash -c 'while true; do date +"%F %T" >> /mnt/testdisk/test.txt; sleep 0.5; done'

Ở cửa sổ khác:

bash tail -f /mnt/testdisk/test.txt

  1. Tắt máy đột ngột (power off VM) Sau đó khởi động lại và kiểm tra file. Kết quả: các dòng gần cuối có thể biến mất — chúng chỉ tồn tại trong RAM.

6. So sánh khi có fsync()

Nếu thêm fsync() sau mỗi lần ghi:

import os, time
fd = os.open("/mnt/testdisk/safe.txt", os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0o644)
for i in range(1000000):
    os.write(fd, f"{time.strftime('%F %T')} {i}\n".encode())
    os.fsync(fd)
    time.sleep(0.2)

Lúc này, dù crash đột ngột, dữ liệu được lưu đầy đủ tới lần fsync() cuối cùng.


7. Lời khuyên

  1. Khi xử lý dữ liệu quan trọng (CSDL, cấu hình, giao dịch), luôn đảm bảo gọi fsync() hoặc sử dụng công cụ đảm bảo ACID.
  2. Không nên dựa vào close() vì nó không đảm bảo dữ liệu đã xuống đĩa.
  3. Khi thử nghiệm, hãy làm trên VM hoặc container tách biệt để tránh rủi ro.
  4. Nếu cần demo “mất dữ liệu”, phải kéo dài thời gian writeback, ghi nhanh nhiều dữ liệu, và tắt máy trước khi flush.
  5. Khi thiết kế hệ thống thực tế, hiểu rằng Page Cache chỉ là tối ưu hiệu năng, không phải giải pháp đảm bảo dữ liệu.

8. Kết luận

Cơ chế Ứng dụng → VFS → Page Cache → Disk giúp tăng tốc truy xuất I/O nhờ RAM, nhưng nó không đảm bảo dữ liệu đã bền vững. Việc thấy dữ liệu trong tail -f chỉ có nghĩa là dữ liệu đã vào Page Cache, không đồng nghĩa với việc đã ghi thật xuống ổ cứng. Vì thế, các hệ thống cần độ tin cậy cao phải gọi fsync() đúng lúc hoặc dùng công cụ có cơ chế đảm bảo nhất quán (như database, journaling filesystem).

Redis hay Memcached vẫn cần thiết vì chúng hoạt động ở tầng ứng dụng — nơi có thể cache dữ liệu logic, phân tán, có TTL, và kiểm soát rõ ràng hơn so với Page Cache tự động của kernel.