Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
Ruby Proxies for Scale, Performance
    and Monitoring

                                                                  ...
postrank.com/topic/ruby




                              The slides…                         Twitter                     ...
Code + Examples                                           EventMachine




                     Misc                      ...
Proxy Love



Ruby Proxies + EventMachine   http://bit.ly/railsconf-proxy   @igrigorik #railsconf
“Rails, Django, Seaside, Grails…” cant scale.




                                              Myth: Slow Frameworks



R...
The Proxy Solution



Ruby Proxies + EventMachine   http://bit.ly/railsconf-proxy      @igrigorik #railsconf
The “More” Proxy Solution



Ruby Proxies + EventMachine   http://bit.ly/railsconf-proxy   @igrigorik #railsconf
Transparent Scalability



Ruby Proxies + EventMachine   http://bit.ly/railsconf-proxy     @igrigorik #railsconf
Load Balancer

                                                       Reverse Proxy                      App Server




  ...
90% use case


                        %w*Transparent Intercepting Caching …+
                                            ...
Transparent
                                                              HAProxy




                              App se...
Transparent Proxy = Scalability Power Tool



Ruby Proxies + EventMachine   http://bit.ly/railsconf-proxy   @igrigorik #ra...
Proxy                                                               Proxy




        App server A            App server B...
Simulating traffic?

                                                              Proxy

        Duplication




        ...
github.com/igrigorik/autoperf



                                                              Replay log data, rinse, rep...
Profile of queries has changed                                              Fail
          Load on production has changed ...
Duplex Ruby Proxy, FTW!

                                                                      Production




     Real (p...
github.com/igrigorik/em-proxy
         Proxy DSL FTW!




Ruby Proxies + EventMachine   http://bit.ly/railsconf-proxy   @i...
EventMachine: Speed + Convenience
                                  building high performance network apps in Ruby




Rub...
p quot;Startingquot;
                                 while true do
                                                      ...
p quot;Startingquot;
                                 while true do
                                                      ...
C++ core


                                         Easy concurrency
                                         without thre...
http = EM::HttpRequest.new('http://site.com/').get

                               http.callback {
                       ...
http = EM::HttpRequest.new('http://site.com/').get

                              http.callback {
                        ...
EM.run do
         EM.add_timer(1) { p quot;1 second laterquot; }
         EM.add_periodic_timer(5) { p quot;every 5 secon...
EM.run do
         EM.add_timer(1) { p quot;1 second laterquot; }
         EM.add_periodic_timer(5) { p quot;every 5 secon...
http://bit.ly/aiderss-eventmachine
                              by Dan Sinclair (Twitter: @dj2sincl)



Ruby Proxies + Ev...
Proxies for Monitoring, Performance and Scale
                                                    welcome to the wonderful...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
      conn.server :name, :host => quot;127.0.0.1quot;, :por...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
      conn.server :name, :host => quot;127.0.0.1quot;, :por...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
      conn.server :name, :host => quot;127.0.0.1quot;, :por...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
      conn.server :name, :host => quot;127.0.0.1quot;, :por...
%w[ <Transparent> Intercepting Caching … +
                                                              solution for ever...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
      conn.server :srv, :host => quot;127.0.0.1quot;, :port...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
      conn.server :srv, :host => quot;127.0.0.1quot;, :port...
%w[ Transparent <Intercepting> Caching … +
                                                              solution for ever...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
  @start = Time.now
  @data = Hash.new(quot;quot;)

       ...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
  @start = Time.now
  @data = Hash.new(quot;quot;)

  conn....
Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn|
  @start = Time.now
  @data = Hash.new(quot;quot;)

  conn....
[ilya@igvita]      >   ruby examples/appserver.rb 81
 [ilya@igvita]      >   ruby examples/appserver.rb 82
 [ilya@igvita] ...
[ilya@igvita]      >   ruby examples/appserver.rb 81
 [ilya@igvita]      >   ruby examples/appserver.rb 82
 [ilya@igvita] ...
Same response, different
                                                                    turnaround time




         ...
Woops!




                                                              Validating Proxy
                                ...
Hacking SMTP
                                                                          for fun and profit




Ruby Proxies...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn|
  conn.server :srv, :host => quot;127.0.0.1quot;, :port =...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn|
  conn.server :srv, :host => quot;127.0.0.1quot;, :port =...
[ilya@igvita] > mailtrap run –p 2525 –f /tmp/mailtrap.log
 [ilya@igvita] > ruby examples/smtp_whitelist.rb



  > require ...
[ilya@igvita] > mailtrap run –p 2525 –f /tmp/mailtrap.log
 [ilya@igvita] > ruby examples/smtp_whitelist.rb


             ...
“Hacking SMTP”.gsub(/Hacking/, ’Kung-fu’)
                                                              DIY spam filtering...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn|
  conn.server :srv, :host => quot;127.0.0.1quot;, :port =...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn|
  conn.server :srv, :host => quot;127.0.0.1quot;, :port =...
Save data

 conn.on_data do |data|
   @from = data.match(FROM_CMD)[1] if data.match(FROM_CMD)
   @rcpt = data.match(RCPT_C...
conn.on_data do |data|
   @from = data.match(FROM_CMD)[1] if data.match(FROM_CMD)
   @rcpt = data.match(RCPT_CMD)[1] if da...
Ruby Proxies + EventMachine   http://bit.ly/railsconf-proxy   @igrigorik #railsconf
@buffer = false
 uri = URI.parse('http://api.defensio.com/app/1.2/audit/key.yaml')
 res = Net::HTTP.post_form(uri, {
     ...
@buffer = false
 uri = URI.parse('http://api.defensio.com/app/1.2/audit/key.yaml')
 res = Net::HTTP.post_form(uri, {
     ...
Protocol Trace


   [:relay_from_backend, :srv, quot;354 Start your messagequot;]
   [:resp, quot;354 Start your messagequ...
@PostRank: Beanstalkd + Ruby Proxy
                                                              because RAM is still expe...
~ 93 Bytes of overhead per job
        ~300 Bytes of data / job

          x 80,000,000 jobs in memory

          ~ 30 GB ...
Observations:
         1. Each job is rescheduled several times
         2. > 95% are scheduled for > 3 hours into the fut...
1 “Medium” EC2 Instance
                       Intercepting Proxy



                                                     ...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 11300) do |conn|
  conn.server :srv, :host => quot;127.0.0.1quot;, :port ...
Proxy.start(:host => quot;0.0.0.0quot;, :port => 11300) do |conn|
  conn.server :srv, :host => quot;127.0.0.1quot;, :port ...
Overload the protocol



                                                                       MySQL
                    ...
400% cheaper + extensible!                  ~79,000,000 jobs, 4GB RAM



                                                 ...
%w[ Transparent <Intercepting> Caching … +
                                                              solution for ever...
Slides: http://bit.ly/railsconf-proxy
    Code: http://github.com/igrigorik/em-proxy

    Twitter: @igrigorik




        ...
Nächste SlideShare
Wird geladen in …5
×

Ruby Proxies for Scale, Performance, and Monitoring

10.681 Aufrufe

Veröffentlicht am

http://github.com/igrigorik/em-proxy

Veröffentlicht in: Technologie

Ruby Proxies for Scale, Performance, and Monitoring

  1. 1. Ruby Proxies for Scale, Performance and Monitoring Ilya Grigorik @igrigorik Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  2. 2. postrank.com/topic/ruby The slides… Twitter My blog Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  3. 3. Code + Examples EventMachine Misc Proxies Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  4. 4. Proxy Love Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  5. 5. “Rails, Django, Seaside, Grails…” cant scale. Myth: Slow Frameworks Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  6. 6. The Proxy Solution Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  7. 7. The “More” Proxy Solution Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  8. 8. Transparent Scalability Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  9. 9. Load Balancer Reverse Proxy App Server MySQL Proxy Proxy as Middleware middleware ftw! Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  10. 10. 90% use case %w*Transparent Intercepting Caching …+ There are many different types! Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  11. 11. Transparent HAProxy App server A App server B Transparent, Cut-Through Proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  12. 12. Transparent Proxy = Scalability Power Tool Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  13. 13. Proxy Proxy App server A App server B App server C Problem: Staging Environment Production Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  14. 14. Simulating traffic? Proxy Duplication App server C “Representative Load / Staging” Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  15. 15. github.com/igrigorik/autoperf Replay log data, rinse, repeat Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  16. 16. Profile of queries has changed Fail Load on production has changed Fail Parallel environment Fail Slower release cycle Fail Staging fail. Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  17. 17. Duplex Ruby Proxy, FTW! Production Real (production) traffic Benchmark Benchmarking Proxy flash of the obvious Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  18. 18. github.com/igrigorik/em-proxy Proxy DSL FTW! Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  19. 19. EventMachine: Speed + Convenience building high performance network apps in Ruby Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  20. 20. p quot;Startingquot; while true do EM.run do timers p quot;Running in EM reactorquot; network_io end other_io end puts quot;Almost donequot; EventMachine Reactor concurrency without threads Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  21. 21. p quot;Startingquot; while true do EM.run do timers p quot;Running in EM reactorquot; network_io end other_io end puts quot;Almost donequot; EventMachine Reactor concurrency without threads Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  22. 22. C++ core Easy concurrency without threading EventMachine Reactor concurrency without threads Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  23. 23. http = EM::HttpRequest.new('http://site.com/').get http.callback { p http.response } # ... do other work, until callback fires. Event = IO event + block or lambda call EventMachine Reactor concurrency without threads Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  24. 24. http = EM::HttpRequest.new('http://site.com/').get http.callback { p http.response } # ... do other work, until callback fires. Event = IO event + block or lambda call EventMachine Reactor concurrency without threads Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  25. 25. EM.run do EM.add_timer(1) { p quot;1 second laterquot; } EM.add_periodic_timer(5) { p quot;every 5 secondsquot;} EM.defer { long_running_task() } end class Server < EM::Connection def receive_data(data) send_data(quot;Pong; #{data}quot;) end def unbind p [:connection_completed] end end EM.run do EM.start_server quot;0.0.0.0quot;, 3000, Server end Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  26. 26. EM.run do EM.add_timer(1) { p quot;1 second laterquot; } EM.add_periodic_timer(5) { p quot;every 5 secondsquot;} EM.defer { long_running_task() } end class Server < EM::Connection def receive_data(data) send_data(quot;Pong; #{data}quot;) Connection Handler end def unbind p [:connection_completed] end end EM.run do Start Reactor EM.start_server quot;0.0.0.0quot;, 3000, Server end Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  27. 27. http://bit.ly/aiderss-eventmachine by Dan Sinclair (Twitter: @dj2sincl) Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  28. 28. Proxies for Monitoring, Performance and Scale welcome to the wonderful world of… Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  29. 29. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| conn.server :name, :host => quot;127.0.0.1quot;, :port => 81 conn.on_data do |data| # ... end Relay Server conn.on_response do |server, resp| # ... end conn.on_finish do # ... end end EM-Proxy www.github.com/igrigorik/em-proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  30. 30. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| conn.server :name, :host => quot;127.0.0.1quot;, :port => 81 conn.on_data do |data| # ... end Process incoming data conn.on_response do |server, resp| # ... end conn.on_finish do # ... end end EM-Proxy www.github.com/igrigorik/em-proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  31. 31. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| conn.server :name, :host => quot;127.0.0.1quot;, :port => 81 conn.on_data do |data| # ... end Process response data conn.on_response do |server, resp| # ... end conn.on_finish do # ... end end EM-Proxy www.github.com/igrigorik/em-proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  32. 32. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| conn.server :name, :host => quot;127.0.0.1quot;, :port => 81 conn.on_data do |data| # ... end conn.on_response do |server, resp| # ... end Post-processing step conn.on_finish do # ... end end EM-Proxy www.github.com/igrigorik/em-proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  33. 33. %w[ <Transparent> Intercepting Caching … + solution for every problem Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  34. 34. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 81 # modify / process request stream conn.on_data do |data| p [:on_data, data] data end No data modifications # modify / process response stream conn.on_response do |server, resp| p [:on_response, server, resp] resp end end Port-Forwarding transparent proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  35. 35. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 81 conn.on_data do |data| data end conn.on_response do |backend, resp| Alter response resp.gsub(/hello/, 'good bye') end end Port-Forwarding + Alter transparent proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  36. 36. %w[ Transparent <Intercepting> Caching … + solution for every problem Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  37. 37. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| @start = Time.now @data = Hash.new(quot;quot;) Prod + Test conn.server :prod, :host => quot;127.0.0.1quot;, :port => 81 conn.server :test, :host => quot;127.0.0.1quot;, :port => 82 conn.on_data do |data| data.gsub(/User-Agent: .*?rn/, 'User-Agent: em-proxyrn') end conn.on_response do |server, resp| @data[server] += resp resp if server == :prod end conn.on_finish do p [:on_finish, Time.now - @start] p @data end end Duplex HTTP: Benchmarking Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  38. 38. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| @start = Time.now @data = Hash.new(quot;quot;) conn.server :prod, :host => quot;127.0.0.1quot;, :port => 81 conn.server :test, :host => quot;127.0.0.1quot;, :port => 82 conn.on_data do |data| data.gsub(/User-Agent: .*?rn/, 'User-Agent: em-proxyrn') end conn.on_response do |server, resp| Respond from production @data[server] += resp resp if server == :prod end conn.on_finish do p [:on_finish, Time.now - @start] p @data end end Duplex HTTP: Benchmarking Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  39. 39. Proxy.start(:host => quot;0.0.0.0quot;, :port => 80) do |conn| @start = Time.now @data = Hash.new(quot;quot;) conn.server :prod, :host => quot;127.0.0.1quot;, :port => 81 conn.server :test, :host => quot;127.0.0.1quot;, :port => 82 conn.on_data do |data| data.gsub(/User-Agent: .*?rn/, 'User-Agent: em-proxyrn') end conn.on_response do |server, resp| @data[server] += resp resp if server == :prod end Run post-processing conn.on_finish do p [:on_finish, Time.now - @start] p @data end end Duplex HTTP: Benchmarking Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  40. 40. [ilya@igvita] > ruby examples/appserver.rb 81 [ilya@igvita] > ruby examples/appserver.rb 82 [ilya@igvita] > ruby examples/line_interceptor.rb [ilya@igvita] > curl localhost >> [:on_finish, 1.008561] >> {:prod=>quot;HTTP/1.1 200 OKrnConnection: closernDate: Fri, 01 May 2009 04:20:00 GMTrnContent-Type: text/plainrnrnhello world: 0quot;, :test=>quot;HTTP/1.1 200 OKrnConnection: closernDate: Fri, 01 May 2009 04:20:00 GMTrnContent-Type: text/plainrnrnhello world: 1quot;} Duplex HTTP: Benchmarking Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  41. 41. [ilya@igvita] > ruby examples/appserver.rb 81 [ilya@igvita] > ruby examples/appserver.rb 82 [ilya@igvita] > ruby examples/line_interceptor.rb [ilya@igvita] > curl localhost STDOUT [:on_finish, 1.008561] {:prod=>quot;HTTP/1.1 200 OKrnConnection: closernDate: Fri, 01 May 2009 04:20:00 GMTrnContent-Type: text/plainrnrnhello world: 0quot;, :test=>quot;HTTP/1.1 200 OKrnConnection: closernDate: Fri, 01 May 2009 04:20:00 GMTrnContent-Type: text/plainrnrnhello world: 1quot;} Duplex HTTP: Benchmarking Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  42. 42. Same response, different turnaround time Different response body! Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  43. 43. Woops! Validating Proxy easy, real-time diagnostics Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  44. 44. Hacking SMTP for fun and profit Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  45. 45. Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 2525 # RCPT TO:<name@address.com>rn RCPT_CMD = /RCPT TO:<(.*)?>rn/ Intercept Addressee conn.on_data do |data| if rcpt = data.match(RCPT_CMD) if rcpt[1] != quot;ilya@igvita.comquot; conn.send_data quot;550 No such user herenquot; data = nil end end data end conn.on_response do |backend, resp| resp end Defeating SMTP Wildcards end Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  46. 46. Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 2525 # RCPT TO:<name@address.com>rn RCPT_CMD = /RCPT TO:<(.*)?>rn/ conn.on_data do |data| Allow: ilya@igvita.com if rcpt = data.match(RCPT_CMD) if rcpt[1] != quot;ilya@igvita.comquot; conn.send_data quot;550 No such user herenquot; data = nil 550 Error otherwise end end data end conn.on_response do |backend, resp| resp end Defeating SMTP Wildcards end Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  47. 47. [ilya@igvita] > mailtrap run –p 2525 –f /tmp/mailtrap.log [ilya@igvita] > ruby examples/smtp_whitelist.rb > require 'net/smtp‘ > smtp = Net::SMTP.start(quot;localhostquot;, 2524) > smtp.send_message quot;Hello World!quot;, quot;ilya@aiderss.comquot;, quot;ilya@igvita.comquot; => #<Net::SMTP::Response:0xb7dcff5c @status=quot;250quot;, @string=quot;250 OKnquot;> > smtp.finish => #<Net::SMTP::Response:0xb7dcc8d4 @status=quot;221quot;, @string=quot;221 Seeyanquot;> > smtp.send_message quot;Hello World!quot;, quot;ilya@aiderss.comquot;, “missing_user@igvita.comquot; => Net::SMTPFatalError: 550 No such user here Duplex HTTP: Benchmarking Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  48. 48. [ilya@igvita] > mailtrap run –p 2525 –f /tmp/mailtrap.log [ilya@igvita] > ruby examples/smtp_whitelist.rb To: ilya@igvita.com > require 'net/smtp‘ > smtp = Net::SMTP.start(quot;localhostquot;, 2524) > smtp.send_message quot;Hello World!quot;, quot;ilya@aiderss.comquot;, quot;ilya@igvita.comquot; => #<Net::SMTP::Response:0xb7dcff5c @status=quot;250quot;, @string=quot;250 OKnquot;> > smtp.finish => #<Net::SMTP::Response:0xb7dcc8d4 @status=quot;221quot;, @string=quot;221 Seeyanquot;> > smtp.send_message quot;Hello World!quot;, quot;ilya@aiderss.comquot;, “missing_user@igvita.comquot; => Net::SMTPFatalError: 550 No such user here Denied! Duplex HTTP: Benchmarking Intercepting proxy Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  49. 49. “Hacking SMTP”.gsub(/Hacking/, ’Kung-fu’) DIY spam filtering with Defensio Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  50. 50. Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 2525 RCPT_CMD = /RCPT TO:<(.*)?>rn/ FROM_CMD = /MAIL FROM:<(.*)?>rn/ MSG_CMD = /354 Start your message/ MSGEND_CMD = /^.rn/ Intercept commands conn.on_data do |data| #… end conn.on_response do |server, resp| p [:resp, resp] if resp.match(MSG_CMD) @buffer = true @msg = quot;quot; end resp SMTP + SPAM Filtering end end building a state-machine Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  51. 51. Proxy.start(:host => quot;0.0.0.0quot;, :port => 2524) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 2525 RCPT_CMD = /RCPT TO:<(.*)?>rn/ FROM_CMD = /MAIL FROM:<(.*)?>rn/ MSG_CMD = /354 Start your message/ MSGEND_CMD = /^.rn/ conn.on_data do |data| #… end conn.on_response do |server, resp| p [:resp, resp] if resp.match(MSG_CMD) Flag & Buffer message @buffer = true @msg = quot;quot; end resp SMTP + SPAM Filtering end end building a state-machine Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  52. 52. Save data conn.on_data do |data| @from = data.match(FROM_CMD)[1] if data.match(FROM_CMD) @rcpt = data.match(RCPT_CMD)[1] if data.match(RCPT_CMD) @done = true if data.match(MSGEND_CMD) if @buffer @msg += data Buffer data = nil end if @done #… end data SMTP + SPAM Filtering end building a state-machine Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  53. 53. conn.on_data do |data| @from = data.match(FROM_CMD)[1] if data.match(FROM_CMD) @rcpt = data.match(RCPT_CMD)[1] if data.match(RCPT_CMD) @done = true if data.match(MSGEND_CMD) if @buffer @msg += data Flag end of message data = nil end if @done #… Process message end data SMTP + SPAM Filtering end building a state-machine Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  54. 54. Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  55. 55. @buffer = false uri = URI.parse('http://api.defensio.com/app/1.2/audit/key.yaml') res = Net::HTTP.post_form(uri, { quot;owner-urlquot; => quot;http://www.github.com/igrigorik/em-proxyquot;, quot;user-ipquot; => quot;216.16.254.254quot;, quot;article-datequot; => quot;2009/05/01quot;, quot;comment-authorquot; => @from, Defensio API quot;comment-typequot; => quot;commentquot;, quot;comment-contentquot; => @msg}) defensio = YAML.load(res.body)['defensio-result'] p [:defensio, quot;SPAM: #{defensio['spam']}quot;] if defensio['spam'] conn.send_data quot;550 No such user herenquot; else data = @msg end SMTP + SPAM Filtering building a state-machine Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  56. 56. @buffer = false uri = URI.parse('http://api.defensio.com/app/1.2/audit/key.yaml') res = Net::HTTP.post_form(uri, { quot;owner-urlquot; => quot;http://www.github.com/igrigorik/em-proxyquot;, quot;user-ipquot; => quot;216.16.254.254quot;, quot;article-datequot; => quot;2009/05/01quot;, quot;comment-authorquot; => @from, quot;comment-typequot; => quot;commentquot;, quot;comment-contentquot; => @msg}) defensio = YAML.load(res.body)['defensio-result'] p [:defensio, quot;SPAM: #{defensio['spam']}quot;] if defensio['spam'] Pass / Deny Message conn.send_data quot;550 No such user herenquot; else data = @msg end SMTP + SPAM Filtering building a state-machine Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  57. 57. Protocol Trace [:relay_from_backend, :srv, quot;354 Start your messagequot;] [:resp, quot;354 Start your messagequot;] [:srv, quot;nquot;] [:relay_from_backend, :srv, quot;nquot;] [:resp, quot;nquot;] [:connection, quot;Hello Worldrnquot;] [:connection, quot;.rnquot;] [:defensio, quot;SPAM: false, Spaminess: 0.4quot;] [:srv, quot;250 OKnquot;] [:relay_from_backend, :srv, quot;250 OKnquot;] [:resp, quot;250 OKnquot;] SMTP + SPAM Filtering building a state-machine Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  58. 58. @PostRank: Beanstalkd + Ruby Proxy because RAM is still expensive Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  59. 59. ~ 93 Bytes of overhead per job ~300 Bytes of data / job x 80,000,000 jobs in memory ~ 30 GB of RAM = 2 X-Large EC2 instances Oi, expensive! Beanstalkd Math Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  60. 60. Observations: 1. Each job is rescheduled several times 2. > 95% are scheduled for > 3 hours into the future Memory is wasted… 3. Beanstalkd does not have overflow page-to-disk We’ll add it ourselves! Extending Beanstalkd Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  61. 61. 1 “Medium” EC2 Instance Intercepting Proxy MySQL EM-Proxy Beanstalkd @PostRank: “Chronos Scheduler” Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  62. 62. Proxy.start(:host => quot;0.0.0.0quot;, :port => 11300) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 11301 PUT_CMD = /put (d+) (d+) (d+) (d+)rn/ conn.on_data do |data| if put = data.match(PUT_CMD) Intercept PUT command if put[2].to_i > 600 p [:put, :archive] # INSERT INTO .... conn.send_data quot;INSERTED 9999rnquot; data = nil end end data end conn.on_response do |backend, resp| resp end end Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  63. 63. Proxy.start(:host => quot;0.0.0.0quot;, :port => 11300) do |conn| conn.server :srv, :host => quot;127.0.0.1quot;, :port => 11301 PUT_CMD = /put (d+) (d+) (d+) (d+)rn/ conn.on_data do |data| if put = data.match(PUT_CMD) if put[2].to_i > 600 If over 10 minutes… p [:put, :archive] # INSERT INTO .... conn.send_data quot;INSERTED 9999rnquot; data = nil end end Archive & Reply data end conn.on_response do |backend, resp| resp end end Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  64. 64. Overload the protocol MySQL EM-Proxy PUT RESERVE, PUT, … put job, 900 Beanstalkd @PostRank: “Chronos Scheduler” Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  65. 65. 400% cheaper + extensible! ~79,000,000 jobs, 4GB RAM MySQL EM-Proxy PUT Upcoming jobs: ~ 1M RESERVE, PUT, … Beanstalkd @PostRank: “Chronos Scheduler” Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  66. 66. %w[ Transparent <Intercepting> Caching … + solution for every problem Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf
  67. 67. Slides: http://bit.ly/railsconf-proxy Code: http://github.com/igrigorik/em-proxy Twitter: @igrigorik Thanks. Questions? The slides… Twitter My blog Ruby Proxies + EventMachine http://bit.ly/railsconf-proxy @igrigorik #railsconf

×