• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Why the Linux OOM Killer Terminates Your Ruby Processes on DigitalOcean (And How to Prevent It)

Why the Linux OOM Killer Terminates Your Ruby Processes on DigitalOcean (And How to Prevent It)

Understanding the Linux OOM Killer

The Out-Of-Memory (OOM) Killer is a crucial component of the Linux kernel designed to prevent a system from crashing entirely when it runs out of available memory. When the kernel detects that memory is critically low and cannot satisfy new memory allocation requests, it invokes the OOM Killer. This process selects one or more running processes to terminate, thereby freeing up memory and allowing the system to continue operating.

The selection of which process to kill is based on a heuristic scoring system. Each process is assigned an “oom_score” which is calculated based on factors like its memory usage, its “niceness” value (a measure of its priority), and whether it’s a privileged process. Processes with higher oom_scores are more likely candidates for termination. This mechanism, while vital for system stability, can be disruptive, especially when it targets critical application processes like your Ruby applications.

Why Ruby Processes are Prime Targets

Ruby applications, particularly those built with frameworks like Ruby on Rails, can be memory-intensive. This is often due to:

  • Object Allocation: Ruby’s dynamic nature and object-oriented design can lead to the creation of many small objects, which can consume significant memory.
  • Gems and Libraries: The extensive use of gems and external libraries, while powerful, can add to the memory footprint of an application.
  • Caching Mechanisms: In-memory caching strategies (e.g., using Redis or Memcached, but also internal application caches) can consume substantial RAM.
  • Long-Running Processes: Application servers like Puma or Unicorn, which manage multiple worker processes or threads, can accumulate memory over time, especially if there are memory leaks or inefficient memory management within the application code.
  • Database Connections: A large number of open database connections, each with its own memory overhead, can also contribute to high memory consumption.

On a DigitalOcean droplet (or any Linux server), when these memory demands exceed the available RAM, the OOM Killer might identify your Ruby process as the “least valuable” or “most problematic” in terms of memory consumption and terminate it abruptly. This often manifests as your application becoming unresponsive or disappearing without a clear error message within the application logs.

Diagnosing OOM Killer Activity

The first step in preventing OOM kills is to confirm that the OOM Killer is indeed the culprit. The most reliable place to find evidence is the system logs.

Checking System Logs

On most modern Linux distributions, including those used by DigitalOcean (like Ubuntu or Debian), system logs are managed by `systemd-journald`. You can query these logs using the `journalctl` command.

To view recent kernel messages, including OOM Killer events, you can use:

sudo journalctl -k | grep -i "killed process"

If the OOM Killer has recently terminated a process, you’ll see output similar to this:

[...timestamp...] Out of memory: Kill process [PID] ([process_name]) score [oom_score] or sacrifice child
[...timestamp...] Killed process [PID] ([process_name]) , not the least, not the biggest: [oom_score]kB

Look for lines containing “Out of memory” and “Killed process”. The `[process_name]` will often be your Ruby application server (e.g., `puma`, `unicorn`, `ruby`). The `[oom_score]` indicates how the kernel prioritized this process for termination.

Strategies to Prevent OOM Kills

Preventing OOM kills involves a multi-pronged approach: reducing memory consumption, increasing available memory, and fine-tuning the OOM Killer’s behavior.

1. Optimize Ruby Application Memory Usage

This is often the most impactful long-term solution. Focus on:

  • Profiling: Use memory profiling tools to identify memory leaks or excessive object allocation in your Ruby code. Tools like memory_profiler gem can be invaluable.
  • Efficient Data Structures: Be mindful of how you store and process data. Avoid loading entire datasets into memory if possible; use pagination or streaming.
  • Garbage Collection Tuning: While Ruby’s GC is generally good, understanding its behavior and potential tuning options (though often complex) can help.
  • Connection Pooling: Ensure your database connection pools are appropriately sized and managed.
  • Background Jobs: Offload long-running or memory-intensive tasks to background job processors (e.g., Sidekiq, Resque) that can run on separate, potentially more powerful, machines or processes.

2. Configure Application Server Workers/Threads

Application servers like Puma and Unicorn manage worker processes or threads. The number of these directly impacts memory usage. A common mistake is to over-provision them.

Puma Example (config/puma.rb):

# Example: Limit to 2 workers and 4 threads per worker
workers 2
threads 0, 4 # Min threads 0, Max threads 4

# Adjust based on your server's RAM and CPU cores.
# A common starting point is (CPU Cores / Number of Workers) * Threads per Worker.
# For a 2-core CPU, 2 workers, and 4 threads: (2 / 2) * 4 = 4 concurrent requests per worker.
# Total concurrent requests = 2 workers * 4 threads = 8.
# This is a simplification; actual usage depends heavily on request complexity.

Unicorn Example (unicorn.rb):

# Example: Limit to 2 workers
worker_processes 2

# Adjust based on your server's RAM.
# Each worker will consume memory. Monitor your server's memory usage under load.
# A good rule of thumb is to leave ample buffer for the OS and other services.

Start with conservative numbers and gradually increase them while monitoring memory usage. Tools like htop or top are essential for this.

3. Increase Server Resources (Vertical Scaling)

If your application’s memory usage is legitimate and optimized, the simplest solution might be to upgrade your DigitalOcean droplet to one with more RAM. This is often the quickest fix for immediate relief.

DigitalOcean offers various droplet sizes. Consider moving from a shared CPU to a dedicated CPU droplet if you suspect noisy neighbors are also contributing to memory pressure, though OOM is typically a direct result of your own process’s consumption.

4. Adjust OOM Killer Behavior (Use with Caution)

While generally not recommended for production systems without careful consideration, you can influence the OOM Killer’s behavior. This involves adjusting the oom_score_adj value for specific processes.

The oom_score_adj is a value added to the base oom_score. A higher value makes a process more likely to be killed, while a lower (or negative) value makes it less likely.

Setting oom_score_adj for a Process

You can set this value dynamically for a running process or configure it to be set on startup.

Dynamically for a running process:

# Find the PID of your Ruby process (e.g., Puma worker)
pgrep -f "puma: cluster"`

# Example: Set oom_score_adj to -500 (making it less likely to be killed)
# Replace [PID] with the actual process ID
echo -500 | sudo tee /proc/[PID]/oom_score_adj

Making it persistent via systemd:

If your Ruby application is managed by `systemd` (which is common for services deployed via tools like Capistrano or systemd unit files), you can add the OOMScoreAdjust directive to your service unit file.

Locate your service file (e.g., /etc/systemd/system/my_ruby_app.service) and add or modify the [Service] section:

[Unit]
Description=My Ruby Application

[Service]
User=deploy
WorkingDirectory=/home/deploy/my_app
ExecStart=/usr/local/bin/bundle exec puma -C config/puma.rb
Restart=always
# Add this line to make the process less likely to be killed by OOM Killer
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target

After modifying the unit file, reload the systemd daemon and restart your service:

sudo systemctl daemon-reload
sudo systemctl restart my_ruby_app.service

Important Considerations for oom_score_adj:

  • Setting a very low (highly negative) oom_score_adj for your application means that if memory pressure becomes extreme, the OOM Killer might instead target other critical system processes, potentially leading to a system hang or crash rather than just your application restarting.
  • This is a workaround, not a solution to underlying memory issues. Prioritize application-level optimization and resource scaling first.
  • Ensure that essential system processes (like SSHd, systemd itself) have default or even lower oom_score_adj values to protect them.

5. Configure Swappiness

Swappiness is a kernel parameter that controls how aggressively the system swaps memory pages to disk. A higher value means more aggressive swapping. While swapping can prevent OOM kills by providing more “virtual” memory, it comes at a significant performance cost due to disk I/O latency.

You can check the current swappiness value:

cat /proc/sys/vm/swappiness

A typical default is 60. For servers where performance is critical and you want to avoid swapping as much as possible until absolutely necessary, you might lower this value. For memory-constrained droplets where you want to avoid OOM kills at all costs, you might increase it.

To temporarily change it:

sudo sysctl vm.swappiness=10 # Lower swappiness
# or
sudo sysctl vm.swappiness=90 # Higher swappiness

To make it persistent across reboots, edit /etc/sysctl.conf or a file in /etc/sysctl.d/:

# Add or modify this line in /etc/sysctl.conf
vm.swappiness = 10

Then apply the changes:

sudo sysctl -p

For memory-intensive applications like Ruby on Rails, a lower swappiness (e.g., 10-30) is often preferred to keep the application in RAM for better performance, accepting the risk of OOM kills if memory truly runs out. A higher swappiness might be acceptable on less performance-sensitive systems or if you have ample swap space configured.

Conclusion

The Linux OOM Killer is a safety net, but its intervention in your Ruby application’s lifecycle indicates a memory pressure issue. Proactive monitoring, application-level optimization, and appropriate server resource allocation are the primary defenses. While tuning OOM Killer behavior or swappiness can offer temporary relief or specific control, they should be employed judiciously as part of a broader strategy to ensure infrastructure resilience and application stability.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala