Memory Monitoring and Pressure Management in Production Systems


Memory is the resource most engineers underestimate until a production system collapses at 2 AM. A server reporting 80% memory usage can be perfectly healthy, while one at 40% can be minutes away from an OOM kill cascade. The raw numbers are almost always misleading without understanding the Linux memory model, what pressure signals actually indicate, and how containers complicate the picture further. This guide covers the metrics that matter, the failure modes that cause real incidents, and how to build an alert strategy that reflects actual memory risk rather than surface-level utilization.

The Linux Memory Monitoring Model: What Each Field Actually Means

When you run `free -m` or read `/proc/meminfo`, you get a set of fields that look straightforward but carry significant nuance. Understanding each one is the prerequisite for building any useful monitoring strategy.

  • Total is the physical RAM installed in the system. This number is fixed and changes only if the kernel reserves memory for hardware devices or huge pages.
  • Used is total minus free minus buffers minus cache. This represents memory actively holding process data, stack, heap, and mapped files that are not filesystem cache. It is the number closest to “memory your workloads are consuming,” but it is still not the whole story.
  • Free is unallocated memory that has never been touched. On a healthy production system, this number is often very low, because the kernel aggressively uses spare RAM for caching. A low free number is not a problem by itself. See more detail on this in our memory monitoring explained deep dive.
  • Buffers represent kernel memory used for raw block device I/O, primarily metadata for filesystem operations. This is typically small, in the range of tens to hundreds of megabytes.
  • Cached is the page cache: filesystem data the kernel has read from disk and kept in RAM to accelerate future reads. This memory is reclaimable. When a new process needs RAM and free memory is exhausted, the kernel evicts cold cache pages to satisfy the allocation.
  • Available is the kernel’s estimate of how much memory can be allocated to new processes without swapping. It accounts for reclaimable cache and reclaimable slab memory. This is the single most important field in `/proc/meminfo` for operational monitoring.

Slab Memory: The Hidden Allocation Pool

Slab memory is kernel-managed memory used for internal data structures: dentries, inodes, network buffers, and similar objects. It appears under `SReclaimable` and `SUnreclaim` in `/proc/meminfo`. A reclaimable slab can be freed under pressure, but an unreclaimable slab cannot. Workloads that open millions of files or maintain large numbers of network connections can grow slab allocations to several gigabytes. This growth often goes unnoticed until it pushes available memory below safe thresholds.

Why Free Memory Is a Misleading Metric

Alerting on low free memory is one of the most common monitoring mistakes in production environments. A system with 200 MB free out of 64 GB is not in distress if available memory remains at 20 GB and there is no swap activity. The kernel is simply using the rest for cache, which is exactly what it should do.

The metric you want to track is available memory, expressed as a percentage of total. A threshold of 10% available on a 64 GB host translates to roughly 6.4 GB of headroom. For most production workloads, that is enough buffer to absorb burst allocations without triggering swap. For memory-intensive databases or JVM-based services with large heap configurations, you may need a higher threshold, closer to 15-20%.

The broader context on why single-metric monitoring fails across all resource types is worth reading in our CPU, memory monitoring, disk, and network monitoring overview. The same principle applies everywhere: the simple utilization percentage almost never tells the full story.

What Available Memory Does Not Capture

Available memory estimates reclaimable cache but does not account for allocation fragmentation. On systems that have been running for weeks without restart, physical memory can fragment to the point where the kernel cannot satisfy large contiguous allocation requests, even with significant available memory reported. This is more common with huge page workloads and NUMA-sensitive applications. Monitoring `MemAvailable` is necessary but not sufficient for high-memory-pressure workloads.

Memory Pressure Signals: Swapping, OOM Events, and Page Faults

Available memory tells you how much headroom exists. Pressure signals tell you whether the system is actively struggling to manage memory demand.

Swap Activity

Swap usage itself is not the critical metric. The critical metrics are `si` (swap-in rate) and `so` (swap-out rate) from `vmstat`. A system with 2 GB of swap used but zero swap-in and swap-out activity is stable. It swapped some cold pages out overnight and has not touched them since. A system with even 50 MB/s of sustained swap I/O is under active memory pressure, and application latency will show it.

Swap I/O rates above 10 MB/s sustained for more than 60 seconds indicate a system that cannot satisfy memory demand from RAM alone. At rates above 50 MB/s, application response times typically degrade by 200-500ms or more, depending on the storage backend. SSDs reduce the severity, but they do not eliminate it. Track `vmstat 1` output or expose `node_vmstat_pswpin` and `node_vmstat_pswpout` through Node Exporter.

OOM Kill Events

The OOM killer fires when the kernel cannot reclaim enough memory to satisfy an allocation request. It scores all processes using a heuristic that weights memory consumption, run time, and whether the process is a child of init. The highest-scored process gets killed. This is frequently not the process causing the problem.

OOM events appear in `dmesg` and in `/var/log/kern.log` or `/var/log/messages` depending on the distribution. Monitor for the string `Out of memory: Kill process` with a tool that can parse kernel logs in near real time. A single OOM event warrants investigation. Repeated OOM events within a short window indicate the system is in active crisis.

Page Fault Rates and PSI Metrics

Minor page faults are normal: they occur when a process accesses a memory-mapped region that has not yet been faulted in. Major page faults require disk I/O to satisfy and are the direct indicator of memory thrashing. Sustained major fault rates above 100 per second on a production application server are worth investigating immediately.

Kernel 4.20 introduced Pressure Stall Information (PSI), which provides a direct measure of time lost to memory pressure. The metric `memory.some` reports the percentage of time at least one task was stalled waiting for memory. The metric `memory.full` reports the percentage of time all tasks were stalled. At BlueGrid.io, we treat `memory.full` above 5% as a critical alert threshold for production hosts, because it indicates the kernel is spending meaningful time on memory reclaim rather than useful work. PSI metrics are available under `/proc/pressure/memory`. The interaction with disk I/O monitoring is worth understanding here, because memory pressure that forces swap activity directly translates to elevated disk I/O metrics.

cgroups and Container Memory: Kubernetes Limits and Requests

Containers add a second layer of memory accounting that operates independently of host-level metrics. This creates monitoring blind spots that cause genuine production incidents.

Kubernetes uses two memory controls per container: `requests` and `limits`. Requests influence scheduling: the scheduler places pods on nodes with enough unallocated capacity to satisfy the sum of all requested resources. Limits set a hard ceiling enforced by the cgroup memory controller. When a container exceeds its memory limit, the cgroup OOM killer terminates the process, typically the container’s PID 1, which restarts the pod.

The critical monitoring gap here is that host-level memory monitoring does not see individual container pressure. A node reporting 60% host memory utilization can have three pods simultaneously hitting their cgroup limits and cycling through OOM kills. Those kills appear in `kubectl describe pod` and container runtime logs, not in host-level `/proc/meminfo`. You need both layers of visibility to diagnose container memory problems accurately. Our microservices monitoring architecture guide covers how to instrument both layers together.

Memory Requests vs. Actual Usage

Requests that are set too low cause scheduling instability: pods land on nodes that cannot actually support their working set. Requests set too high cause waste and can leave nodes with unschedulable capacity. The right starting point is to measure P95 memory consumption over at least two weeks of representative production traffic, then set requests to P95 and limits to 1.3-1.5x P95 to allow for burst without constant OOM kills. The `memory.usage_in_bytes` and `memory.stat` files under the cgroup hierarchy give you raw data. In cgroups v2, `memory.current` and `memory.pressure` provide the equivalent information.

Working Set vs. RSS

Kubernetes reports `container_memory_working_set_bytes` as the primary memory metric in metrics-server and many Prometheus-based stacks. Working set is RSS plus file-backed memory that cannot be reclaimed under pressure. It is a more conservative and more accurate representation of memory a container will not voluntarily give back. Always prefer working set over RSS when sizing requests and evaluating memory headroom.

Memory Monitoring Leaks in Production: Detection Patterns and Early Indicators

Memory leaks rarely announce themselves. The typical pattern is a gradual, monotonic increase in RSS or working set over hours or days, eventually terminating in an OOM kill or a manual restart.

The earliest indicator is a process RSS that grows consistently across restarts without a corresponding increase in workload. Plot RSS over 24-48 hour windows. A healthy process has a stable RSS with small variations tied to request rate. A leaking process shows a slope with no plateau. Even a slow leak of 50 MB per hour will consume 1.2 GB per day, which is significant on a container with a 2 GB limit.

JVM and Managed Runtime Leaks

JVM-based services complicate leak detection because the heap is pre-allocated at startup and managed by GC. The metric to watch is heap used after full GC, not raw heap consumption. If heap used after major GC grows across successive GC cycles, the application is retaining objects it cannot collect. This shows up in JVM metrics like `jvm_memory_used_bytes` with the `heap` label in Prometheus.

For Go services, watch `go_memstats_heap_inuse_bytes` versus `go_memstats_heap_released_bytes`. A growing gap between these values indicates heap growth that the runtime has not returned to the OS. Node.js leaks typically appear in V8 heap statistics accessible through the `–inspect` flag or via process-level RSS growth visible in `/proc/[pid]/status`.

Native Memory Monitoring Leaks Outside the Heap

Native memory leaks in JVM applications are particularly deceptive. The JVM heap can appear healthy while native memory consumed by direct byte buffers, JNI code, or Metaspace grows unbounded. Monitor total process RSS, not just heap metrics, for JVM workloads. When RSS significantly exceeds heap max plus a reasonable overhead buffer (typically 512 MB to 1 GB), investigate native memory with `jcmd <pid> VM.native_memory`.

What Goes Wrong: Memory Monitoring Failure Modes That Cause Production Incidents

The failure modes below are patterns we see repeatedly in production environments. Each one has caused real outages that would have been preventable with the right monitoring in place.

Alerting on Used Memory Instead of Available Memory

This is the most common misconfiguration. An alert fires at 85% used, but used includes cache. The system has 20 GB of available memory and is completely healthy. The on-call engineer spends 30 minutes investigating a non-problem, then starts ignoring the alert. When actual pressure develops and the same alert fires, it gets dismissed. The fix is to base memory alerts on available memory and PSI metrics.

Missing Container-Level OOM Kills

Host metrics look normal. The application team reports intermittent errors and slow response times. The root cause is a pod cycling through OOM kills every 20 minutes because its memory limit is set below its working set. Host-level monitoring never captures this. You need container memory metrics from cAdvisor or the kubelet metrics endpoint. The pod restart counter in Kubernetes, `kube_pod_container_status_restarts_total`, is a useful proxy alert for this failure mode. Monitoring distributed systems at the right layer is a recurring theme in our how to monitor distributed systems guide.

Slab Memory Growth Consuming Available Memory

A workload that processes large numbers of small files or connections can grow slab memory to 10-15 GB on a 64 GB host without any single process showing high RSS. Available memory decreases, swap activity begins, and no process appears responsible. The fix is to monitor `SReclaimable` and `SUnreclaim` from `/proc/meminfo` as separate metrics and alert when the combined slab total exceeds 15-20% of total RAM.

Swap Configured but Never Monitored

Many teams configure swap and then never instrument swap I/O rates. The system silently degrades for hours before application latency becomes obvious enough to trigger a user-facing alert. By that point, the system may be in a deep thrash cycle. Monitor `pswpin` and `pswpout` from the start, even if swap usage stays near zero most of the time.

Memory Pressure After Deployment

A new deployment increases memory footprint by 30% due to a dependency upgrade or a configuration change. The change passes staging because staging has more headroom. In production, available memory drops to near zero within an hour of rollout. Without trend-based alerting on available memory slope, this is invisible until OOM kills begin. A rate-of-change alert on available memory, something like “available memory decreasing faster than 500 MB per 10 minutes,” catches this before it becomes critical.

Configuring Useful Memory Alerts Beyond Simple Free-Memory Thresholds

A practical production memory alert stack has four layers, each covering a different part of the failure surface.

The first layer is available memory threshold. Alert at warning when available memory drops below 15% of total and at critical when it drops below 8%. These percentages work for most general-purpose hosts. Adjust downward for hosts that run single large workloads with predictable memory behavior.

The second layer is PSI memory pressure. Alert at warning when `memory.some` exceeds 10% over a 5-minute window and at critical when `memory.full` exceeds 5% over the same window. PSI alerts fire based on actual kernel stall time, which correlates directly with application latency degradation.

The third layer is swap I/O rate. Alert when the 5-minute average of `pswpin + pswpout` exceeds 5 MB/s. This threshold is conservative; adjust it based on your storage backend. NVMe swap can sustain higher rates without severe application impact. Spinning disk swap at 5 MB/s is already a serious problem.

The fourth layer is OOM event detection. Parse kernel logs for OOM kill strings and alert on any occurrence. A single OOM kill in production deserves a post-mortem, not just an acknowledgment.

For implementation, how monitoring agents work on Linux covers how agents collect these metrics at the host level, and our Linux monitoring stack with Prometheus and Grafana guide walks through the full configuration for Prometheus-based alert rules covering these thresholds.

How BlueGrid.io Handles Memory Pressure Events in Client Environments

BlueGrid.io’s NOC processes over 50 million monitoring events per month across CDN, SaaS, and cybersecurity infrastructure clients. Memory pressure events follow a specific escalation workflow that avoids both alert fatigue and delayed response.

When available memory drops below 15% on a monitored host, the NOC creates an informational ticket and begins a 10-minute observation window. During that window, analysts watch swap I/O rates, PSI metrics, and per-process RSS trends to determine whether the condition is stable or deteriorating. Stable conditions with no swap activity and low PSI are logged and closed. Deteriorating conditions escalate to the client within the 1-hour SLA, with a summary of the pressure source and recommended actions.

For container environments, 24/7 monitoring covers both the host and the Kubernetes metric layers simultaneously. OOM kill events at either layer trigger immediate escalation with the pod name, node, restart count, and memory limit versus working set comparison included in the notification. This gives client engineers enough context to act without requiring them to reproduce the investigation from scratch.

For memory leak patterns, the NOC maintains per-host RSS trend baselines updated on a weekly schedule. When a process RSS growth rate deviates from baseline by more than 2 standard deviations over a 4-hour window, it generates a proactive alert. This catches slow leaks before they reach OOM territory. Clients using our managed infrastructure monitoring typically catch leak-driven incidents 4-6 hours earlier than teams relying on threshold-only alerting.

Isidora Nikolić

A woman with long dark hair and glasses smiles, wearing a yellow blazer over a white T-shirt illustrated with four fashionable women. She stands against a plain white background with her hands on her hips.

Isidora Nikolić

A dedicated communication and brand enthusiast whose mission is to invigorate the culture and teamwork dynamics at BlueGrid.io through in-depth interviews.

Isidora's emphasis extends to showcasing client success stories, fostering interactions with esteemed industry professionals, and uncovering their valuable insights.

Share this post

Share this link via

Or copy link