๐ช The Linux Memory Circus: The Dual Life of File Caching
Author: A Linux veteran once chased by the OOM killerSlogan: Memory management is not magic, but sometimes it’s more magical than magic!
๐ฏ Introduction: Why Should You Care?
Imagine this scenario: you carefully configured a container with 32GB of memory, started a database that is “just right” at 32GB, and then…Bang! The container was taken down by the OOM killer! ๐ฑ
At this moment, you might think: “I calculated everything correctly!” Don’t worry, today I will take you into the circus of Linux memory management to see what those “inactive files that are not released” are really up to!
๐ช Act One: The Great Classification of Memory – The Feud Between Two Families
๐ก The two great families of the memory world:
๐ค Anonymous Memory Family - "I am a temp worker, throw me away when done"
๐ File Memory Family - "I have a background, the disk is my father"
๐ค Anonymous Memory – The “Temp Worker” of the Memory World
# This is the memory dynamically allocated by your application
malloc(1024); # "Boss, give me a piece of land to put something"
Characteristics:
- ๐ซ No disk backup (like your draft paper in memory)
- ๐ธ Relies on swap for survival when memory is tight
- ๐ Includes heap, stack, and dynamically allocated memory
๐ File Memory – The “Insider” of the Memory World
# When reading a file, the kernel secretly helps you cache it
cat large_file.txt > /dev/null # "This file is nice, I will cache it for later"
Characteristics:
- ๐พ Has disk files as a backup
- ๐ A tool to enhance I/O performance
- ๐๏ธ Can be discarded directly when memory is tight (clean pages) or written back and then discarded (dirty pages)
๐ช Act Two: The Dual Life of File Caching – Active vs Inactive
๐ญ The two actors of the file caching family:
๐ฅ Active File - "The Hot Star"
โ๏ธ Inactive File - "The Has-Been"
The LRU Linked List Stage of the Memory Circus
// The "audition scene" in the kernel source - mm/vmscan.c
// Let's see how the pages compete:
static void shrink_active_list(...) {
// Judge kswapd appears: "Let me see which pages are still active!"
if (page_referenced(page, ...)) {
// "Wow, this page has been accessed recently, promoted to Active queue!"
list_add(&page->lru, &l_active);
} else {
// "Sorry, you haven't been accessed for too long, demoted to Inactive queue"
list_add(&page->lru, &l_inactive);
}
}
The rules of this audition show are simple:
- ๐ฏ Recently accessed โ Enter Active queue (debut in the C position)
- ๐ด Not accessed for a long time โ Demoted to Inactive queue (pending area)
- ๐๏ธ When memory is tight โ Prioritize eliminating pages from the Inactive queue
The Real-Life Performance of the Memory Circus
# Let's see the current situation of your memory circus
$ cat /proc/meminfo | grep -E "(Active|Inactive).*file"
Active(file): 1245000 kB # ๐ฅ The Hot Stars
Inactive(file): 560000 kB # โ๏ธ The Has-Beens
๐ช Act Three: The Tragedy Replayed – The OOM Disaster of the 32GB Container
Now let’s return to the tragedy at the beginning: 32GB container + 32GB database = OOM ๐ฅ
The “Death Calculation” of Memory Allocation
๐ Let's calculate this bill:
Total memory of the container: 32GB
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Database working set: 28GB (Anonymous Memory)
File cache: 3GB (at the start)
Other overhead: 1GB
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Used: 32GB (Perfect! Wait...)
But the file cache will grow!
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
File cache grows to: 4GB
Database working set: 28GB
Other overhead: 1GB
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Total: 33GB (Exceeds 1GB โ OOM!)
Why does the inactive file not come to the rescue?
// mm/vmscan.c - The "rescue team" working scene of the kernel
static void shrink_node_memcgs(...) {
// Rescue team kswapd: "Report! Memory is tight!"
// But..."under cgroup limits, the rescue operation is delayed!"
// Inactive File: "I also want to be reclaimed, but it takes time to reclaim!"
// Database: "I can't wait! I need memory! Right now!"
}
Three Major Reasons for Rescue Failure:
- โฐ cgroup statistics delay: “I didn’t react in time, and the memory exploded!”
- ๐ Slow reclamation speed: “Cleaning up inactive files takes time, especially dirty pages need to be written back to disk”
- ๐ช Database is too demanding: Prefetching, logging, temporary tables… constantly creating new file caches
๐ช Act Four: Rescue Solutions – Setting New Rules for the Memory Circus
๐ฏ Solution 1: “Slimming Down” the Container – Reserve Buffer Space
# Wrong approach: Stuffing the container like a train during Spring Festival
docker run --memory=32g database:latest
# Correct approach: Leave some breathing space for memory
docker run \
--memory=32g \
--memory-reservation=28g \ # Soft limit, early warning
database:latest
Metaphor: Don’t stuff the elevator to the brim, leave some space for emergencies!
๐ฏ Solution 2: Adjust Kernel Parameters – Change the Rules of the Game
# Make the kernel more proactive in cleaning file caches
echo 100 > /proc/sys/vm/vfs_cache_pressure # "Cleaner, work more actively!"
# Reduce swap tendency, prioritize reclaiming file caches
echo 50 > /proc/sys/vm/swappiness # "Don't always think about swap, clean file caches first!"
# Prevent overcommitment
echo 1 > /proc/sys/vm/overcommit_memory # "Act within your means, don't make promises recklessly!"
๐ฏ Solution 3: Database Optimization – Make the Actors Follow the Rules
-- MySQL: Give file caches some respect
SET GLOBAL innodb_buffer_pool_size = 26G; -- "Leave 2GB buffer space for yourself"
SET GLOBAL innodb_flush_method = O_DIRECT; -- "Avoid double caching, save some memory"
-- PostgreSQL: Same principle
ALTER SYSTEM SET shared_buffers = '26GB'; -- "Don't be too greedy!"
๐ฏ Solution 4: Proactive Cleanup – Regular Big Cleanups
#!/bin/bash
# "Big cleanup script" before starting the database
echo "๐งน Starting to clean memory cache..."
sync # First, write dirty data back to disk
echo 3 > /proc/sys/vm/drop_caches # Clean pagecache, dentries, inodes
echo "โจ Cleanup completed! Starting database..."
/usr/bin/mysqld
๐ฏ Solution 5: Intelligent Monitoring – Hire a Memory Security Guard
#!/usr/bin/env python3
# Daily work of the memory security guard
def check_memory_usage():
"""Security patrol: Check memory usage"""
if memory_usage_rate > 85%:
print("๐จ Alert! Memory usage too high!")
os.system('sync && echo 1 > /proc/sys/vm/drop_caches')
print("โ
Emergency cleanup executed")
๐ช Act Five: Best Practices – Wisdom from the Veteran
๐ก Golden Rule: Never Overeat
# Wrong: Database working set = Container memory
Container memory = 32GB
Database memory = 32GB # ๐ Death configuration!
# Correct: Leave 20% buffer space
Container memory = 32GB
Database memory = 26GB # โ
Safe configuration!
Buffer space = 6GB # For file caches and other overhead
๐ Monitoring Metrics – Health Check of the Circus
# Daily inspection command
cat /sys/fs/cgroup/memory/memory.usage_in_bytes # Total usage
cat /sys/fs/cgroup/memory/memory.stat | grep file # File cache status
# Set alert threshold
ALERT_THRESHOLD=0.9 # Alert at 90% usage
๐ญ Kubernetes Configuration – Professional Circus Management
apiVersion: v1
kind: Pod
spec:
containers:
- name: database
resources:
requests:
memory: "30Gi" # I usually need this much
limits:
memory: "34Gi" # But give me this much at most, leave some buffer
๐ช Conclusion: Summary of Experiences
Through this performance of the memory circus, we learned:
- ๐ญ File caching has a dual life: Active is the hot star, Inactive is the has-been
- ๐ฅ The root cause of OOM tragedy: Under cgroup limits, file cache reclamation is not timely enough
- ๐ ๏ธ Solutions: Reserve buffer, adjust parameters, optimize configurations, proactive monitoring
- ๐ฏ Core Idea: Don’t calculate memory too rigidly, leave some leeway for system management
Remember, **a good operator does not use memory to 100%, but knows when to use 80%!**
Now, go tame your memory circus! ๐ชโจ
Appendix: The Debugging Command Toolbox of the Veteran ๐งฐ
# View detailed memory information
cat /proc/meminfo
# View cgroup memory statistics
cat /sys/fs/cgroup/memory/memory.stat
# Real-time monitoring of memory reclamation
watch -n 1 'cat /proc/vmstat | grep scan'
# View process memory details
cat /proc/$(pgrep mysqld)/smaps | grep -i anon
Wish you happy debugging, stay away from the OOM killer! ๐ซ๐โ๐