I explain how cgroup v2 uses its memory controller to properly enforce memory limits, protect services, and contain local OOM events. This allows administrators to set clear Resources-Establish rules, control peak loads, and protect critical processes against power shortages.
Key points
The following list summarizes the key points I discuss in detail in the article.
- Uniform Architecture: cgroup v2 simplifies control and monitoring.
- Hard Limit: memory.max prevents uncontrolled allocations.
- Gentle Brake: memory.high reduces pressure without causing immediate kills.
- More targeted Protection: `memory.low` and `memory.min` prioritize services.
- Transparent Check: `memory.current` provides measurement values for tuning.
How cgroup v2 Handles Memory Differently
I summarize processes in Control I group them together and manage their memory requirements as a single unit. With version v2, the kernel standardizes interfaces, allowing me to apply limits, protection thresholds, and telemetry consistently. The memory logic separates hard isolation from soft throttling, which doesn’t immediately choke off allocations but slows them down in an orderly manner. This allows me to respond to outliers without affecting the entire system, because terminations occur locally within the affected group. For hosting and containers, this provides predictable Resources-Allocation and predictable responses to load spikes.
I use these features to bundle services with similar profiles and establish clear rules. I keep containers, PHP workers, and database processes neatly separated so that each set of workloads has its own boundaries. This helps me avoid cross-contamination, such as global memory pressure that affects harmless jobs. This isolation can be refined step by step until the load distribution responds predictably. This allows me to gain Predictability during operations and keep service quality on track during peak demand.
An Overview of Tax Files
Memory management revolves around just a few Parameters, which I set in the cgroup filesystem. Each cgroup is assigned its own values for hard limits, soft throttling points, and safety thresholds. This allows me to scale from mild reclaim to uncompromising isolation, depending on the importance of the service. Monitoring simultaneously reads the current usage and issues an alert when protection thresholds are triggered. This creates a closed-loop control system consisting of specifications and measured values, which Resources-makes consumption manageable.
| Parameters | Kind | Effect | Typical use |
|---|---|---|---|
| memory.max | Hard Border | Blocks new allocations above the limit; local OOM kill | Databases, JVMs, PHP-FPM pools with clear caps |
| memory.high | Soft brake | Increases Reclaim and latency during allocations; no instant kills | Gentle De-escalation Before Things Escalate |
| memory.low | SoftProtection | Best possible protection against reclaim below the threshold | Key middleware, caches, central services |
| memory.min | Harder Protection | No "Reclaim" below the threshold; OOM tends to affect other groups more | Critical Core Components |
| memory.current | Live-Value | Shows current usage; basis for alerts and tuning | Dashboards, Trend Analyses |
| memory.oom.group | Kill-Scope | Aggregates OOM kills at the group level | Consistent termination of related processes |
Understanding Hierarchies and Inheritance
I'm setting up cgroups hierarchical: Parent groups define the framework; children inherit limits and share the available memory. This structure makes constraints predictable but requires clear rules. The parent's `memory.max` limits the total amount of memory for the children; `memory.low` and `memory.min` act as Priorities: A group with a higher protection level is more likely to retain its base memory, while less important groups are reclaimed more aggressively. This helps me secure critical paths without compromising global limits.
I note that the protection values additive The idea is this: If the memory.min values are set too high across all child nodes, it blocks reclamation in the hierarchy and shifts the pressure upward, all the way to the host. That’s why I calibrate protection budgets per level and always leave a buffer free. In tiered models, I define classes (critical, important, best effort) and apply consistent bandwidths and protection thresholds for each class. This keeps load distribution fair and transparent—even when teams manage subgroups independently.
Hard Limit: Setting `memory.max` Correctly
I set memory.max so that the process has enough headroom for spikes but does not dominate the server. To do this, I measure realistic peaks, add a buffer, and then consistently cap the usage. If a service hits this upper limit, no further allocations are made, and the kernel terminates local processes within the group. This encapsulation prevents domino effects on other workloads. For memory-hungry services, this provides clear Security without cross-damage.
For large heaps or caches, I deliberately build in buffers, because garbage collection and background tasks cause fluctuations. I validate the limit with load tests to ensure that OOM events do not occur during normal operation. If usage remains consistently close to the limit, I first increase the reserve or reduce the actual workload. This way, I keep the margin for error small and efficiency high. This discipline pays off in Availability from.
Gentle Braking: memory.high in Everyday Life
With memory.high I set a warning and throttling threshold before the hard limit. If the group exceeds that threshold, the kernel activates Reclaim and slows down allocations without immediately cleaning up. I use this time to flush caches, stagger batch loads, or lower request limits. This allows me to smooth out peaks before kills become necessary. This improves the Service quality during sudden load spikes.
I make sure to set a noticeable gap between `memory.high` and `memory.max` so the system has some real breathing room. If the gap is too small, I run out of memory (OOM) too quickly. If it’s too large, I lose control over latencies. I test both under production profiles and calibrate the sweet spot. This allows me to create a reliable Throttle, which takes effect in a timely manner.
Swap Policy: Choose `memory.swap.max` Carefully
I determine whether and to what extent a group Swap may use. With `memory.swap.max`, I can limit swapping separately from the RAM limit. If I set the value to 0, I disable swapping for the group—which is useful for latency-sensitive services that must not block. If I allow moderate swapping, I gain flexibility for caches and rarely accessed pages. It’s important that I urgency Understand the different types of workloads: Databases and JVMs often benefit from strict or very tight swap policies, while batch or reporting jobs are more flexible when it comes to swapping.
I coordinate the swap strategy with the host configuration (e.g., swappiness, zram/zswap) to ensure that the measures do not conflict with one another. Excessive swap only masks memory shortages in the short term and shifts the load to I/O—I use it specifically as Buffer, not as a permanent condition. Metrics such as major page faults and latencies quickly reveal whether swapping helps or hinders performance. This is how I keep delays and tail latencies under control.
Protection thresholds: memory.low and memory.min
I use memory.low, to reserve base memory for important services. As long as usage remains below this threshold, the kernel spares this portion and prefers to reclaim memory elsewhere. For highly prioritized components, I also use `memory.min`. This hard protection threshold makes it clear to the kernel that I do not allow reclamation here. This ensures that the core of an application remains functional even under extreme load and responsive.
I set the weighting deliberately: central databases get `memory.min`, critical middleware gets `memory.low`, and non-critical batch jobs get no extra protection. This prioritization makes it easier to make decisions during bottlenecks. If an OOM occurs, this classification protects my key paths. I retain control over which processes give up memory first. This gives me clear Priorities in the event of bottlenecks.
Transparency: memory.current in Monitoring
I read memory.current I continuously monitor it and correlate it with application metrics. This allows me to identify trends, backlog buildup, and spikes. If the system detects an increase in instances where `memory.high` is exceeded or in OOM events, I adjust the limits or the workload. Dashboards and alerts give me a head start in addressing issues. From this data, I derive Tuning-decisions that prevent absences in the long term.
In addition to the value itself, I monitor page fault rates, cache hit rates, and latencies. This view shows whether reclaim is causing excessive slowdowns or whether protection mechanisms are kicking in. I adjust intervals and thresholds until the alerts are useful rather than annoying. Then I automate countermeasures such as cache trim or queue limiting. This ensures that the response remains fast and targeted.
Telemetry in Depth: memory.stat, memory.events, and PSI
I'm adding the following to `memory.current`: memory.stat and memory.events, to identify causes rather than just symptoms. `memory.stat` breaks down usage by Anon, File Cache, Slab, and other categories. From these percentages, I can tell whether an application’s allocations or the page cache are growing—and adjust accordingly (e.g., cache sizes vs. number of workers). memory.events and memory.events.local count triggers such as violations of low/high/max limits as well as oom and oom_kill. This provides reliable triggers for alerts and automated remediation.
I also use PSI (Pressure Stall Information) to quantify pressure rather than guess at it. If Memory-PSI values rise continuously, threads experience delays; I throttle the workload, increase `memory.high`, or free up bandwidth in the pipeline. Overall, this generates telemetry that gives me a gradual Early warnings provides—before strict limits are enforced.
Containers and orchestration
If I set memory limits in Kubernetes, they end up as cgroup-Values such as `memory.max` and, optionally, `memory.high` in the runtime. The orchestration applies policies on a per-pod basis, while I define the specifics for each namespace or deployment. To ensure reliable SLOs, I link limits to HPA strategies and pod budgets. This holistic approach prevents individual pods from dominating memory usage. A good introduction to Resource Isolation with cgroups makes it easier to plan containers with clear boundaries and access routes.
I also check whether sidecars and init containers are assigned their own limits, so that auxiliary processes don't restrict core workloads. For stateful workloads, I set `memory.low` or `memory.min` so that caches and buffers don't shrink immediately. I document these decisions in the deployment so the team can easily understand them. This way, I ensure Consistency between infrastructure and applications. The result is predictable workload profiles.
Systemd Integration and Automation
I use systemd to set cgroup v2 parameters declaratively: MemoryMax is equal to memory.max, MemoryHigh the memory.high, MemoryLow and MemoryMin set protection lines, MemorySwapMax is managed by Swap. This mapping makes policies transparent in the code repository and simplifies rollbacks. In larger environments, I use it to orchestrate consistent Standards per service class and decouple operations from manual interventions.
For automated interventions, I combine events from memory.events/PSI with policy engines. If a group repeatedly exceeds `memory.high`, I simultaneously reduce the number of workers, limit burst rates, or trigger targeted Cache trim. If these steps don't work, I let the system's built-in OOM mechanisms take effect in a controlled manner—the `memory.oom.group` setting ensures that the effect remains local and predictable. This results in a gradual, self-healing behavior without any surprises.
Multi-Tenant Hosting with CloudLinux
I encapsulate customer environments in separate cgroups and set clear limits for each tenant. CloudLinux complements this with tools that allocate RAM, CPU, and I/O on a per-account basis. This keeps neighboring-account effects manageable, and prevents individual outliers from dragging down all accounts. For those who want to dive deeper, here’s a practical overview of CloudLinux and cgroup v2 in the context of shared hosting. This allows me to maintain fair Resources-Distribution across many customers.
I set `memory.max` per customer based on the measured daily profile, assign a `memory.low` value to caches, and protect core processes with `memory.min`. When limits are exceeded, throttling mechanisms kick in first, rather than abruptly halting accounts. If an OOM occurs, it affects only the affected group locally. This ensures that the platform remains available to other tenants. This approach strengthens Plannability compared to traffic spikes.
Special Cases: Page Cache, THP, and Large Pages
I differentiate between Anon-Memory (heaps, stacks) and File Cache (Page Cache). Under heavy load, it’s easier to free up file cache, while anonymous pages require swapping or lead to OOM. `memory.high` and protection limits help me reclaim file cache without affecting critical heaps. For Transparent Huge Pages (THP), I check whether they benefit the application or increase fragmentation and latency—depending on the profile, I adjust the THP policy to ensure that it continues to work well with the memory controller.
Uses an application Hugepages Specifically, I isolate their memory requirements from RAM management using the corresponding controllers. This prevents large pages from displacing regular RAM. I keep these special reserves tightly managed and coordinate them with the other limits to avoid unexpected bottlenecks. Overall, this establishes clear guidelines for regular and special memory usage.
Best Practices for Limits
I start with real-world usage profiles and set memory.max I leave some headroom so that peaks don't immediately trigger an OOM. I set `memory.high` noticeably lower to smooth out load spikes and slow down allocations. Prioritization is key: the database gets `memory.min`, middleware gets `memory.low`, and batch loads are not given special treatment. Monitoring accompanies operations and shows whether thresholds are effective or have been set too strictly. Based on these signals, I adjust limits and simultaneously increase the Efficiency the application.
I document the metrics for each service, explain the rationale behind them, and record changes in a way that’s easy to trace. This helps me ensure that decisions are firmly established within the team and prevents guesswork weeks down the line. Before making updates or architectural changes, I review trend charts to avoid tightening or loosening settings blindly. A small test environment saves a lot of trouble later in production. This rhythm ensures Constance in day-to-day business.
Practical Exercise: Organizing Web Hosting Servers
I create a separate one for each customer cgroup and move PHP-FPM, the database, and the cache into it. I allocate `memory.max` plus a buffer to each set, while `memory.high` takes precedence and smooths out fluctuations. Critical customer services are given protection limits to ensure their core memory doesn’t drop. Logs and dashboards show which processes are slowing things down, which are causing spikes, and where OOM is a threat. Additionally, tips on Namespaces and Isolation Concepts, so that clients remain clearly separated and Security increases.
I also adjust the number of PHP workers, OPcache sizes, and query caches to reduce memory footprints. Often, simply reducing memory spikes using `memory.high` is enough to cut down on time. For testing, I use real-world load patterns, not synthetic ideal values. Afterward, I document new limits and link them to SLAs. This way, the Transparency toward customers and internal support.
Troubleshooting Memory Printing
Rises memory.current When things are moving fast, I first check for changes in traffic, deployments, or configurations. I compare the trends in high-threshold violations, page faults, and latencies. If OOMs occur in succession, I identify the affected processes via the kernel log and adjust limits or the workload. If the cause lies in faulty caches, I perform targeted trimming rather than applying a global solution. This diagnostic chain quickly leads me to the Cause, not just a symptom.
If the load remains high, I scale back operations: lower burst limits for Ingress, reduce queue lengths, and reschedule batch jobs. At the same time, I temporarily increase `memory.high` to buy some breathing room without raising `memory.max`. If I find memory leaks, I tighten the guardrails until a fix is in place. In stubborn cases, I reduce the service scope or replicate the instance. That’s how I keep the Operation Reliable operation, even under pressure.
Automation: Event-Driven Countermeasures
I'm tying Actions For events: `memory.events` provides counters that I process using a watcher or metrics pipeline. In the event of repeated high-hit counts, I selectively flush caches, reduce concurrency, or initiate reclaim attempts before users notice anything. If mild interventions fail, I switch to hard measures: request throttling, queue draining, and prioritization changes. It’s important that decisions deterministic are—same triggers, same reactions—so that teams can understand and replicate the behavior.
I'm also keeping the Scope Keeping an eye on OOM. With `memory.oom.group`, I avoid partial kills that put applications into inconsistent states. If something needs to be terminated, it should be done in a coordinated and swift manner so that remaining capacity is quickly available again. Combined with telemetry and documented playbooks, this creates a robust feedback loop that holds up under real production conditions.
Outlook and summary
The memory controller from cgroup v2 provides me with a tiered set of tools: hard caps, soft brakes, and guard lines with clear priorities. By deliberately using `memory.max`, `memory.high`, `memory.low`, and `memory.min`, I can respond systematically to sudden spikes in load and keep services running. Monitoring via `memory.current` provides early warning of where limits are being reached or reserves are running low. In container and multi-tenant setups, these mechanisms ensure fair resource allocation without causing collateral damage. With discipline, metrics, and small corrective steps, I achieve reliable Performance – from a single virtual machine to a heavily loaded host.


