Page Cache
Page cache as the kernel-managed file cache between applications and storage, and why it matters for Spark, Kafka, databases, and lakehouse workloads.
Page Cache: RAM của kernel nằm giữa application và storage
Vì sao note này nằm trong Computer Systems?
Page cache là một cơ chế của operating system, không phải của riêng Spark, database hay Kafka. Nhưng nó ảnh hưởng trực tiếp tới cách data systems đọc/ghi dữ liệu:
- Spark/Trino đọc Parquet có thể nhanh bất thường khi dữ liệu đang warm trong cache.
- Kafka broker tận dụng page cache để append/read log segment rất hiệu quả.
- Database phải cân bằng giữa buffer pool riêng và OS page cache.
- Benchmark pipeline rất dễ sai nếu so một cold run với một warm run.
Vì vậy note này đặt ở:
content/notes/computer-systems/operating-systems/page-cache.mdxNó là phần nền tảng OS, rồi từ đó link ngược lên Spark, Kafka, database, lakehouse.
Mental model
Linux quản lý RAM theo đơn vị page, thường là 4 KiB. Khi application gọi read() để đọc file, dữ liệu thường đi qua page cache:
Application process
-> read(fd, buffer, nbytes)
-> Linux kernel
-> page cache
-> storage nếu cache missCó hai trường hợp chính:
| Case | Chuyện xảy ra |
|---|---|
| Cache hit | Page dữ liệu đã nằm trong RAM, kernel copy từ page cache sang buffer của process. |
| Cache miss | Kernel phải đọc từ storage vào page cache, rồi mới trả dữ liệu cho process. |
Nên đọc file thường là:
storage -> kernel page cache -> application memorychứ không đơn giản là:
storage -> application memoryPage cache khác gì application cache?
| Cache | Nằm ở đâu | Cache cái gì | Ai quản lý |
|---|---|---|---|
| OS page cache | Kernel memory | Raw file pages / byte ranges | Linux kernel |
Spark cache() / persist() | Executor memory/disk | RDD/DataFrame sau decode/transform | Spark |
| Database buffer pool | DB process memory | Data/index pages theo logic storage engine | Database |
| Databricks disk cache / Delta cache | Local SSD/node cache | Remote file data đã materialize/cached | Runtime/platform |
OS page cache chỉ biết byte range của file. Nó không hiểu:
- Parquet schema
- column pruning
- Spark partition
- SQL predicate
- Delta transaction log semantics
Ví dụ Spark đọc Parquet:
Object storage / disk
-> OS page cache hoặc connector/local cache
-> Parquet reader
-> decoded columnar batches
-> Spark execution/cache layerVì sao rerun Spark job có thể nhanh hơn?
Một Spark job đọc cùng một tập Parquet có thể có runtime kiểu:
Run 1: 42 phút
Run 2: 18 phút
Run 3: 17 phút
Hôm sau: 45 phútMột giải thích rất thường gặp:
Run 1: cold cache, đọc storage nhiều
Run 2/3: warm cache, nhiều byte range đã nằm ở RAM/local cache
Hôm sau: cluster/node khác, cache mấtPage cache là local to machine. Nếu scheduler đưa task sang node khác, hoặc autoscaling terminate worker cũ, cache locality biến mất.
Benchmark vì vậy phải phân biệt:
| Benchmark | Ý nghĩa |
|---|---|
| Cold run | Đo storage + compute khi chưa có cache. |
| Warm run | Đo lợi ích repeated access/cache locality. |
| Mixed run | Gần production hơn nếu workload có reuse. |
| Node-pinned run | Giảm nhiễu do scheduler đổi node. |
Clean page, dirty page, writeback
Page cache không chỉ dùng cho read mà còn cho write.
Khi application gọi write():
application write()
-> data được copy vào page cache
-> syscall có thể return trước khi data thật sự durable trên disk
-> kernel flush xuống storage sauPage đã sửa nhưng chưa flush gọi là dirty page.
Page cache
├── clean pages: có thể drop/reclaim nhanh
└── dirty pages: phải writeback xuống storage trước khi reclaim an toànWarning
write() thành công không đồng nghĩa data đã durable trên disk.
Muốn ép durability thường cần fsync(), hoặc hệ thống bên trên phải có protocol riêng: WAL, replication, commit log, checkpoint, object-store commit protocol, etc.
Kafka là ví dụ kinh điển
Kafka log là append-only files. Broker append message vào log segment, consumer đọc từ log segment. OS page cache giúp recent data nằm trong RAM.
Producer
-> Kafka broker
-> append log segment
-> page cache
-> disk writeback later
Consumer
-> read log segment
-> page cache hit nếu data còn nóng
-> socket sendVì vậy Kafka có thể tận dụng RAM của OS thay vì tự giữ toàn bộ message cache trong JVM heap.
Nhưng nếu consumer lag quá xa:
consumer đọc log segment cũ
-> page cache miss nhiều hơn
-> hit disk nhiều hơn
-> throughput giảm / latency tăngConsumer lag không chỉ là “data đến chậm”, mà còn có thể biến workload từ RAM-speed thành disk-speed.
Database buffer pool vs OS page cache
Database thường có buffer pool riêng:
Disk page
-> OS page cache
-> DB buffer pool
-> query executorĐiều này có thể gây double caching: cùng một page tồn tại trong kernel page cache và DB buffer pool.
Một số database/storage engine dùng direct I/O để bypass page cache:
normal I/O:
storage -> OS page cache -> DB buffer pool
direct I/O:
storage -> DB buffer poolDirect I/O hợp lý khi database tự quản lý caching tốt hơn OS và muốn tránh page cache pollution. Nhưng nó không miễn phí: app phải tự xử lý alignment, readahead, batching, random I/O behavior.
Readahead và small files
Khi kernel thấy application đọc tuần tự, nó có thể readahead: đọc trước các page kế tiếp vào page cache.
Hợp với:
- scan large Parquet/ORC files
- Kafka sequential log read
- table scan
- ML training đọc shard tuần tự
Kém hợp với:
- nhiều random reads
- access pattern nhảy lung tung
- quá nhiều small files
Small files tệ không chỉ vì metadata/listing overhead. Nó còn làm I/O pattern vụn hơn, giảm hiệu quả readahead và làm page cache kém hữu ích hơn.
Page cache trong object storage world
Với S3/ADLS/GCS, Spark thường đọc qua connector bằng HTTP range request:
Spark executor
-> Hadoop/S3A/ABFS/GCS connector
-> HTTP range GET
-> object storageDữ liệu remote object storage không tự động trở thành POSIX file-backed page cache giống local disk. Nhưng vẫn có nhiều tầng cache khác:
| Tầng | Ví dụ |
|---|---|
| Connector/client buffer | Hadoop S3A stream buffer |
| Local disk cache | Databricks disk cache / Delta cache |
| OS page cache | Khi dữ liệu được materialize thành local files |
| Object storage internal cache | Provider-side, opaque |
| Proxy/CDN/cache layer | Nếu platform có |
Với lakehouse, cần cẩn thận khi nói “được cache rồi”: cache ở tầng nào mới là quan trọng.
Memory pressure và cache pollution
Page cache dùng RAM rảnh. Đây là lý do free -h có thể nhìn như RAM gần đầy nhưng thực ra nhiều phần là buff/cache.
Nhưng page cache vẫn cạnh tranh với application memory:
Node RAM
├── OS/kernel
├── page cache
├── JVM heap
├── Spark off-heap / Netty / Arrow
├── Python workers
└── native libraries / thread stacksNếu executor memory cấu hình quá sát RAM node, OS còn ít chỗ cho page cache. Với workload file-heavy, điều này có thể làm đọc file, spill, shuffle hoặc local cache tệ hơn.
Một one-off scan lớn cũng có thể pollute cache:
before: hot pages của workload A nằm trong cache
large backfill scan: đọc rất nhiều cold data
after: hot pages bị evictKết quả là workload khác bị latency spike dù code không đổi.
Quan sát nhanh trên Linux
Một vài lệnh hữu ích:
free -hNhìn buff/cache, không chỉ used.
vmstat 1Chú ý:
si/so: swap in/outbi/bo: block in/outwa: I/O wait
cat /proc/meminfoField đáng nhìn:
| Field | Ý nghĩa |
|---|---|
Cached | File page cache. |
Dirty | Dirty pages chưa flush. |
Writeback | Dirty pages đang được flush. |
Active(file) | File-backed pages đang hot. |
Inactive(file) | File-backed pages dễ reclaim hơn. |
Active(anon) | Heap/stack/anonymous memory hot. |
Inactive(anon) | Anonymous memory ít active hơn. |
Có thể dùng vmtouch để xem file nào đang nằm trong page cache, nhưng chỉ nên dùng trong môi trường controlled benchmark/dev.
Drop cache để benchmark
Trên Linux có thể drop clean caches:
sync
echo 3 | sudo tee /proc/sys/vm/drop_cachesDùng cho benchmark controlled/local VM. Không làm bừa trên production database, Kafka broker, shared Spark worker, hay bất kỳ node đang phục vụ workload thật nào.
Checklist khi pipeline đọc file lúc nhanh lúc chậm
- Đây là cold run hay warm run?
- Task có chạy lại cùng node không, hay scheduler/autoscaling đổi node?
- Storage path là local disk, network volume, object storage, hay FUSE/mount?
- Có local disk cache / Delta cache / connector cache nào không?
- File layout có quá nhiều small files không?
- Executor memory có bóp nghẹt OS page cache không?
- Có workload backfill/compaction nào đang pollute cache không?
- Benchmark có đang so cold run với warm run không?
Câu nhớ nhanh
Cùng một dòng:
spark.read.parquet(path)có thể thực tế là một trong ba thứ rất khác nhau:
1. đọc remote storage chậm
2. đọc local disk/cache trung bình
3. đọc RAM/page cache rất nhanhNếu không phân biệt được ba case này, rất dễ tune sai chỗ.