Computer SystemsPage Cache

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.mdx

Nó 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 miss

Có hai trường hợp chính:

CaseChuyện xảy ra
Cache hitPage dữ liệu đã nằm trong RAM, kernel copy từ page cache sang buffer của process.
Cache missKernel 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 memory

chứ không đơn giản là:

storage -> application memory

Page cache khác gì application cache?

CacheNằm ở đâuCache cái gìAi quản lý
OS page cacheKernel memoryRaw file pages / byte rangesLinux kernel
Spark cache() / persist()Executor memory/diskRDD/DataFrame sau decode/transformSpark
Database buffer poolDB process memoryData/index pages theo logic storage engineDatabase
Databricks disk cache / Delta cacheLocal SSD/node cacheRemote file data đã materialize/cachedRuntime/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 layer

Vì 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út

Mộ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ất

Page 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 runGần production hơn nếu workload có reuse.
Node-pinned runGiả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 sau

Page đã 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àn

Warning

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 send

Vì 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ăng

Consumer 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 pool

Direct 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 storage

Dữ 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ầngVí dụ
Connector/client bufferHadoop S3A stream buffer
Local disk cacheDatabricks disk cache / Delta cache
OS page cacheKhi dữ liệu được materialize thành local files
Object storage internal cacheProvider-side, opaque
Proxy/CDN/cache layerNế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 stacks

Nế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ị evict

Kế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 -h

Nhìn buff/cache, không chỉ used.

vmstat 1

Chú ý:

  • si/so: swap in/out
  • bi/bo: block in/out
  • wa: I/O wait
cat /proc/meminfo

Field đáng nhìn:

FieldÝ nghĩa
CachedFile page cache.
DirtyDirty pages chưa flush.
WritebackDirty 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_caches

Dù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

  1. Đây là cold run hay warm run?
  2. Task có chạy lại cùng node không, hay scheduler/autoscaling đổi node?
  3. Storage path là local disk, network volume, object storage, hay FUSE/mount?
  4. Có local disk cache / Delta cache / connector cache nào không?
  5. File layout có quá nhiều small files không?
  6. Executor memory có bóp nghẹt OS page cache không?
  7. Có workload backfill/compaction nào đang pollute cache không?
  8. 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 nhanh

Nếu không phân biệt được ba case này, rất dễ tune sai chỗ.

On this page