Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Taming the Unicorn

Taming the Unicorn

The Unicorn web server is a workhorse in the Ruby world. It's used by major Rails sites like Shopify, Github, and 37Signals.

Learn about some of the core features of Unicorn and what makes it special. This'll start at the start and take you through system calls, (pre)forking, signals, kgio, and tricks to conserve memory.

jstorimer

May 02, 2012
Tweet

More Decks by jstorimer

Other Decks in Programming

Transcript

  1. 2

  2. 4

  3. “Thirty years from now, there will still be [system calls]

    and smart people will still be using them to solve hard problems reliably and predictably, just like they were thirty years ago.” ~ Ryan Tomayko 7
  4. Documentation •intro(2) -> $ man 2 intro •malloc(3) -> $

    man 3 malloc •crontab(5) -> $ man 5 crontab Code 9
  5. Documentation •intro(2) -> $ man 2 intro •malloc(3) -> $

    man 3 malloc •crontab(5) -> $ man 5 crontab •[Linux only] socket(7) -> $ man 7 socket Code 9
  6. Documentation •intro(2) -> $ man 2 intro •malloc(3) -> $

    man 3 malloc •crontab(5) -> $ man 5 crontab •[Linux only] socket(7) -> $ man 7 socket •$ apropos thing; Searches the manpages Code 9
  7. 11

  8. 12

  9. 17

  10. 18

  11. 19

  12. 20

  13. require 'socket' concurrency = 3 child_pids = [] signal_queue =

    [] socket = TCPServer.open('127.0.0.1', 4481) concurrency.times do child_pids << fork do trap(:INT) { exit } loop do connection = socket.accept connection.write(connection.read) connection.close end end end [:INT, :CHLD].each do |sig| trap(sig) do signal_queue << sig end end 23
  14. require 'socket' concurrency = 3 child_pids = [] signal_queue =

    [] socket = TCPServer.open('127.0.0.1', 4481) concurrency.times do child_pids << fork do trap(:INT) { exit } loop do connection = socket.accept connection.write(connection.read) connection.close end end end [:INT, :CHLD].each do |sig| trap(sig) do signal_queue << sig end end Parent Process Child Processes 24
  15. Sending a signal •kill(1) or Process.kill •Each signal can be

    represented with a name or number •Anyone ever use? $kill -9 <pid> Code 27
  16. Caveats of signal handling •Handlers may run at *any* time

    •Your handlers must be re-entrant Code 28
  17. Why? •Unicorn uses nonblocking IO methods •They raise Errno::EAGAIN when

    they would block •Socket#accept will block until it gets a connection Code 31
  18. Why? •Unicorn uses nonblocking IO methods •They raise Errno::EAGAIN when

    they would block •Socket#accept will block until it gets a connection •Socket#accept_nonblock will get a connection or raise Errno::EAGAIN Code 31
  19. It’s about tradeoffs •Would you rather get another 1 ms

    of performance or get Feature X implemented? •Applications set their own standards 36
  20. System software has to micro-optimize •It’s at the mercy of

    the standards of the application •What if an application wants really fast responses and the server holds them back? 37
  21. # make the following bet: if we accepted clients this

    round, # we're probably reasonably busy, so avoid calling select() # and do a speculative non-blocking accept() on ready listeners # before we sleep again in select(). unless nr == 0 # (nr < 0) => reopen logs (unlikely) ready = l.dup redo end 39
  22. ... # A frozen format for this is about 15%

    faster REMOTE_ADDR = 'REMOTE_ADDR'.freeze ... e[REMOTE_ADDR] = socket.kgio_addr ... # This is the typical way to accomplish the above: e['REMOTE_ADDR'] = socket.kgio_addr 40
  23. # Does the majority of the IO processing. It has

    been written in # Ruby using about 8 different IO processing strategies. # # It is currently carefully constructed to make sure that it gets # the best possible performance for the common case: GET requests # that are fully complete after a single read(2) # # Anyone who thinks they can make it faster is more than welcome to # take a crack at it. # # returns an environment hash suitable for Rack if successful # This does minimal exception trapping and it is up to the caller # to handle any socket errors (e.g. user aborted upload). def read(socket) 41
  24. 43

  25. Resources •Unicorn source code (http:// unicorn.bogomips.org/) •usp.ruby list (http://librelist.com/browser/ usp.ruby/)

    •http://tomayko.com/writings/unicorn-is-unix •https://github.com/blog/517-unicorn2010/ everything-you-need-to-know-about-unicorn/ •http://www.engineyard.com/blog/2010/ everything-you-need-to-know-about-unicorn/ 44
  26. Credits • Unicorn 502 page: http://chuckjhardy.com/post/3329075019/github-502-unicorn-server- error • The thinker:

    http://www.flickr.com/photos/22087304@N07/5074344673/ • Ritchie and Thompson: http://catb.org/~esr/writings/taoup/html/ch02s01.html • Blocks: http://www.flickr.com/photos/kmtucker/3355551036/ • Signal: http://www.flickr.com/photos/philipstorry/4924064517/ • Color scheme: http://www.colourlovers.com/palette/1111659/Ninja_Rainbow 45