Skip to main content

Puma Overview

Puma is the web server that receives http requests, and forwards it to the right controller.

Puma is set to run on cluster mode.

  • A Master puma process will execute when we run rails
    • Tn production, we would start Thurster, which is the default http/2 proxy that ships with rails.

puma.rb​

on_worker_boot​

Analogy and mermaid diagrams generated by Gemini Pro 2.5 Reviewed and edited by Ali

Imagine you're running a restaurant.

  • The Puma Master Process is the Head Chef.
  • The Worker Processes are the Line Cooks that the Head Chef hires and trains.
  • The Threads in each worker are the number of pans each Line Cook can have on the stove at once.
  • The Database & Redis are the Pantry and Fridge.
  • A Network Connection is a Key to the Pantry/Fridge.

The Core Problem: Photocopying the Key​

Puma's cluster mode is designed for efficiency. The Head Chef (Master Process) first sets up the kitchen by loading the entire Rails application (preload_app!).

  • This is like preparing all the recipe books and ingredients.

Now, to handle more customers, the Head Chef decides to hire two Line Cooks (Worker Processes).

  • workers(ENV.fetch('WEB_CONCURRENCY') { 1 })
  • Instead of re-training each one from scratch, the Chef uses a magical photocopier (fork()) to create perfect clones of themself.

Here's the critical part: When the Head Chef (Master) opens the connection to the Pantry (Database/Redis), they get a single, specific key.

  • When they photocopy themself to create the Line Cooks (Workers), each cook gets a photocopy of that same key.

Now you have two cooks (Worker 1, Worker 2) thinking they have their own private key, but they're both using a copy of the original.

What happens?

  • Cook 1 goes to the pantry, unlocks it, and starts getting an ingredient.
  • Cook 2, using their copied key, barges in at the same time.
  • They bump into each other, drop ingredients, get the wrong orders, and make a mess. The data they get is garbled and corrupted.

This is exactly what happens with your Redis and ActiveRecord connections.

  • The forked worker processes all end up with a shared, copied network connection (known as a file descriptor or socket), leading to chaos and data corruption.

The Solution: Give Each Cook Their Own Key​

The on_worker_boot function is the solution. It's a special instruction from the Head Chef (Puma Master) that says:

"To each new Line Cook I just hired: The key you have is a useless photocopy. Throw it away. Now, go to the manager and get your own, brand new, personal key to the pantry."

This is what Rails.cache.reconnect and ActiveRecord::Base.establish_connection do. They tell each worker process to discard the inherited, copied connection and establish its own fresh, clean, and private connection to Redis and the database.

Visualizing the Process​

Here is a diagram illustrating the wrong way vs. the right way.

The WRONG Way: Connecting Before the Fork

This is what happens if you don't have an on_worker_boot hook.

In this scenario, both workers are trying to talk over the same line. It's a mess.


The RIGHT Way: Using on_worker_boot​

  1. Rails app loads, Puma's Master Process starts
  2. Puma sees config that they need to start in cluster mode. Master Process then executes the function fork() to make a copy of itself
  3. Puma sees the config on_worker_boot, so it will create new connection pools for External Services (i.e. ActiveRecord and Redis)
  4. Now each worker thread has a new connection pool for the External Services

Here, each worker process establishes its own set of connections after it has been created.

  • Worker 1 has its own private connection pool
    • Worker 2 has its own.

They are completely isolated and can't interfere with each other.

Summary​

By doing this, we ensure process isolation and resource management.

1. Stability

By forcing each worker to create its own connections, you eliminate the risk of unpredictable concurrency-related bugs that are a nightmare to debug. Your application becomes stable and reliable under load.

2. Scalability

This model scales horizontally. Whether you run 2 workers or 50 workers, the principle remains the same. Each new process is a self-contained unit with its own resources, preventing bottlenecks and interference.

3. Performance

preload_app! in config/puma.rb gives you a fast startup time for new workers because the Rails code is already loaded (copy-on-write memory). The on_worker_boot step adds a tiny one-time cost to worker startup, but it's a necessary investment for the massive stability gains.