Puma Worker Killer

repository·main·Indexed 20 days ago

https://github.com/zombocom/puma_worker_killer

A Ruby gem designed to manage memory bloat in Puma web servers. It automatically kills and restarts workers that exceed specified RAM thresholds or follows a rolling restart schedule, which is recommended for containerized platforms like Heroku. It includes a Reaper class for monitoring memory usage and provides configuration options for RAM limits, check frequency, and lifecycle hooks.

Tokens
2.6K
Snippets
11
Records
12
Agent score
72%

What's inside puma_worker_killer

  1. Enable Memory-Based Worker Killing

    main

    For non-containerized environments, you can enable worker killing based on RAM thresholds. Puma Worker Killer will periodically evaluate the memory usage of all Puma processes and their forks, killing workers that exceed the configured limit. Puma will automatically spawn fresh copies.

    # config/puma.rb
    
    before_fork do
      require 'puma_worker_killer'
    
      PumaWorkerKiller.start
    end
  2. Enable Rolling Restarts (Heroku Mode)

    main

    Rolling restarts kill workers on a periodic basis to keep memory usage down. This is recommended for containerized platforms like Heroku where measuring RAM from inside the container is difficult.

    Note: If you use enable_rolling_restart, do not also call PumaWorkerKiller.start.

    # config/puma.rb
    
    before_fork do
      require 'puma_worker_killer'
    
      # Default is every 6 hours
      PumaWorkerKiller.enable_rolling_restart 
    end
  3. Configure PumaWorkerKiller

    main

    You can configure the gem using a PumaWorkerKiller.config block or by calling methods directly on the module. Configuration must be done before calling PumaWorkerKiller.start or PumaWorkerKiller.enable_rolling_restart.

    PumaWorkerKiller.config do |config|
      config.ram           = 1024 # mb
      config.frequency     = 5    # seconds
      config.percent_usage = 0.98
      config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds
      config.reaper_status_logs = true
    
      config.pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed" }
      config.rolling_pre_term = -> (worker) { puts "Worker #{worker.inspect} being killed by rolling restart" }
    end
    PumaWorkerKiller.start
  4. Troubleshoot Puma Worker Killer

    main

    If you do not see PumaWorkerKiller debug output during boot, check the following:

    1. Cluster Mode: Ensure Puma is running in cluster mode or hybrid mode. PWK only functions if you have multiple workers enabled. Look for * Process workers: X in the boot logs.
    2. Frequency: If you have configured a frequency, try reducing it to a very low value to see if it triggers more often.
    3. Daemon Mode: If running Puma as a daemon, ensure your configuration is placed in config/puma.rb inside a before_fork block rather than in a standard Rails initializer.
  5. Reference: PumaWorkerKiller Configuration Options

    main

    The following configuration keys are available via the PumaWorkerKiller.config block or direct method calls:

    • ram: The maximum RAM threshold in MB (Default: 512).
    • frequency: How often (in seconds) to check memory usage (Default: 5).
    • percent_usage: The utilization percentage threshold (e.g., 0.98 for 98%) before workers are killed (Default: 0.99).
    • rolling_restart_frequency: Frequency for rolling restarts in seconds (Default: 12 * 3600 / 6 hours).
    • reaper_status_logs: Boolean to enable/disable status logs like PumaWorkerKiller: Consuming ... mb (Default: true).
    • pre_term: A lambda called just before a worker is terminated due to exceeding RAM limits. Receives the worker object.
    • rolling_pre_term: A lambda called just before a worker is terminated due to a rolling restart. Receives the worker object.
    • on_calculation: A lambda called every time memory usage is calculated. Receives the total memory used as a single value.
    # Example of direct method configuration
    PumaWorkerKiller.ram = 1024
    PumaWorkerKiller.frequency = 20
  6. Configure PumaWorkerKiller settings

    main

    Use the PumaWorkerKiller.config method to set the configuration parameters for the worker killer. This is typically done in your config/puma.rb file. The configuration block yields the PumaWorkerKiller module itself, allowing you to set attributes like RAM limits, check frequency, and lifecycle hooks.

    PumaWorkerKiller.config do |config|
      config.ram = 512
      config.frequency = 10
      config.percent_usage = 0.99
      config.pre_term = -> { puts 'Cleaning up...' }
    end
  7. Enable Rolling Restarts

    main

    To prevent all workers from restarting simultaneously (which can cause downtime), you can enable rolling restarts using PumaWorkerKiller.enable_rolling_restart. This uses the rolling_restart_frequency and rolling_restart_splay_seconds settings to randomize the restart timing across workers.

    # Uses configured rolling_restart_frequency and rolling_restart_splay_seconds
    PumaWorkerKiller.enable_rolling_restart
    
    # Or provide custom frequency and splay duration
    PumaWorkerKiller.enable_rolling_restart(3600, 60.0)
  8. Execute the memory reaping process with reap()

    main

    The reap method performs the actual memory check.

    1. It checks if workers have already stopped to avoid redundant operations.
    2. It calculates the total memory usage of the cluster.
    3. If the total memory exceeds @max_ram:
      • It logs an 'Out of memory' message.
      • It identifies the largest_worker (the worker consuming the most memory).
      • It executes the pre_term callback (if provided) passing the largest_worker.
      • It sends a TERM signal to the largest_worker's PID.
    4. If memory is within limits and @reaper_status_logs is enabled, it logs the current consumption.

    Returns false if workers are already stopped, otherwise returns the result of the logic (implicitly nil or the result of the log/term operations).

    # Typically called within a periodic timer or a Puma lifecycle hook
    reaper.reap
  9. Start PumaWorkerKiller

    main

    To begin monitoring and killing workers that exceed memory limits, call PumaWorkerKiller.start. By default, it uses the configured frequency and a reaper instance created from your current configuration. You can optionally pass a custom frequency or a custom reaper instance to the start method.

    # Uses default configuration
    PumaWorkerKiller.start
    
    # Or provide custom arguments
    PumaWorkerKiller.start(5, my_custom_reaper)
  10. Initialize the PumaWorkerKiller::Reaper class

    main

    The PumaWorkerKiller::Reaper class is responsible for monitoring memory usage and terminating workers that exceed the defined threshold. You can initialize it with custom hooks for pre-termination logic and memory calculation logging.

    Parameters

    • max_ram: The maximum allowed RAM (in MB) for the entire cluster.
    • master: The Puma master process (used for logging and cluster management).
    • reaper_status_logs (Boolean): If true, logs the current memory consumption of the cluster.
    • pre_term (Proc/Callable): A callback executed with the largest_worker object before it is terminated. This is useful for performing cleanup or graceful shutdowns.
    • on_calculation (Proc/Callable): A callback executed with the total memory usage value during each reap cycle.
    reaper = PumaWorkerKiller::Reaper.new(
      512,                                # max_ram in MB
      puma_master,                        # master process
      true,                               # reaper_status_logs
      ->(worker) { puts "Cleaning up #{worker.pid}" }, # pre_term callback
      ->(total) { puts "Current usage: #{total}MB" }  # on_calculation callback
    )
  11. Reference: PumaWorkerKiller configuration attributes

    main

    The following attributes can be configured on the PumaWorkerKiller module to control its behavior:

    attr_accessor :ram,                         # RAM limit in MB (default: 512)
    attr_accessor :frequency,                   # Check frequency in seconds (default: 10)
    attr_accessor :percent_usage,                # Percent of RAM to use (default: 0.99)
    attr_accessor :rolling_restart_frequency,   # Frequency for rolling restarts in seconds (default: 6 * 3600)
    attr_accessor :rolling_restart_splay_seconds, # Randomization range for rolling restarts (default: 0.0..300.0)
    attr_accessor :reaper_status_logs,          # Boolean to enable/disable status logs (default: true)
    attr_accessor :pre_term,                   # Proc/callback executed before worker termination
    attr_accessor :rolling_pre_term,           # Proc/callback executed before rolling restart termination
    attr_accessor :on_calculation              # Proc/callback executed during memory calculation