Preforking Servers and Reforking

Prefork-type HTTP servers, represented by Unicorn, fork(2) child processes immediately after startup. The goal is to improve performance by having each process handle client requests. Apache (mod_prefork) and Gunicorn (Python) are also examples of prefork-type servers.

Pitchfork fork(2)

At this time, a mechanism called Copy on Write (CoW) is introduced to minimize the amount of memory actually required. CoW keeps memory shared until a write occurs and it becomes necessary. This avoids unnecessary memory copying. Theoretically, if no memory writes occur, the amount of memory required to start two processes is almost the same as the amount required to start one process.

Pitchfork CoW

However, when preforking Ruby processes, it is important to note that much of the code is lazily initialized. Even if you fork the process immediately after startup, the lazily initialized code is not yet loaded into memory, so you cannot benefit from the efficiency of CoW.

  • Memoization
    • Objects are loaded into memory only when the code path is executed. Conversely, they are not yet loaded into memory immediately after startup.
  • Inline Caching
    • The method call cache, an optimization technique of the Virtual Machine, also does not exist immediately after startup.
  • JIT Code (Just-in-time compiled)
    • JIT, which compiles code paths as they are executed, generates bytecode after lazy initialization, so it cannot benefit from preforking.

Pitchfork Lazy Initialized

To overcome this Ruby-specific issue, Shopify developed Pitchfork, which introduces Reforking. After starting the application, child processes are "reforked" at an arbitrary timing (actually, after processing N requests). The memory copied at this time should already be loaded with the memory after processing several requests, allowing it to benefit more from CoW than preforked processes.

Pitchfork Reforking

When using Pitchfork, you need to be mindful of the application's Refork Safety. When Linux forks a process, file descriptor tables are copied, but the actual open connections to V-Nodes are shared. Therefore, to avoid unintended resource conflicts (simultaneous access or resource leaks), it is necessary to close connections before and after the fork. This depends on the library.

Pitchfork Fork Safety

2024-03-05