Concurrency 2 (Tue Apr 5, lect 21) | previous | next | slides |

Continuing the discussion on this critical aspect of scaling

Logistics

Tuning Threads and Processes

Why

  • In tuning an application for performance you need to configure the web server, app server, database server
  • How many threads, how many processes, etc
  • If done wrong the peformance will actually get worse
  • Or you will have very weird bugs

Review the major players

  1. The web server (thin, puma, other)
  2. Your sinatra app itself
  3. Any gems you use (i.e. ActiveRecord itself)
  4. Database servers

What makes it Thread safe?

  • Simply put: when threads don’t share mutable state
  • So, a class method (i.e. def self.do_this_thing) is fine
  • But a class instance variable (.e. def self do this thing; @a = 100) is asking for trouble

Mini case study



glass CurrentState

  def self.favorite_user_id
     @favorite_user_id
  end

  def self.favorite_user_id= curr
     @favorite_user_id = curr
  end

  def self.favorite_user
     @favorite_user ||= User.find(@favorite_user_id)
  end
end

# And then
def handle_form
  CurrentState.favorite_user_id = some_user_id
#...
  CurrentState.favorite_user.name 
#...
end
  • A convenient way to globally share the current user
  • Without regard to thread or to Sinatra dependency
  • Terrible idea! You are sharing mutable state between threads
Discussion: Would this be a problem if your web server was running just one thread? How about if it was running two processes, one thread each? So how would you safely share the current user when you are multi threaded?

Ruby

  • None of the core data structures of MRI Ruby are thread safe (except for Queue)
  • Any ruby expression that generates more than one “ruby operation” (e.g. +=) is not safe!
  • Only if they are used with thread shared data
  • You can avoid using all of them and use thread safe equivalents
  • For example Ruby Concurrency Library
  • Has thread-safe versions of array, hash, set, map, tuple and others
  • So: Your ruby code is probably not thread-safe!

What about the GIL?

  • GIL (Global Interpreter Lock) ensures that your Ruby code doesn’t run in parallel - at the same time
  • But operating system threads get interrupted (by the clock) at arbitrary times
  • GIL is what makes MRI itself threadsafe but it doesnt make your code threadsafe

What about Rails (and its key dependency, ActiveRecord)

  • It is threadsafe!
  • You need to “just” worry about your own code
  • And any gems or libraries you use!
  • How to make your code threadsafe>

Avoid global state

  • Ruby global variables: $logger or $redis
  • Ruby class variables and class instance variables (see example above)
  • Memoization can accidentally get you in trouble (it happened to me)

Session

  • As a SESSION belongs to one request
  • Thread safe from the perspective of a multi-threaded web server

Puma

  • 1 or more workers - they are processes
    • No thread safety problems
    • Each process uses a complete memory space
  • 1 or more threads per worker
    • Pay attention to thread safety
    • If a “global” value changes out from under you – suspect a race condition

Useful References for this section

Thank you. Questions?  (random Image from picsum.photos)