complex made simple: sleep better with torquebox
TRANSCRIPT
Complex Made SimpleSleep Better With TorqueBox
Lance BallSenior Software EngineerRed Hat
Creative Commons BY-SA 3.0
Friday, April 27, 12
Complex Made SimpleSleep Better With TorqueBox
Lance BallSenior Software EngineerRed Hat
Creative Commons BY-SA 3.0
Friday, April 27, 12
Who am I?
๏ Senior Software Engineer @ Red Hat๏ TorqueBox core developer๏ Part of the project:odd team๏ Open source contributor
Lance Ball
Friday, April 27, 12
Scenario
You've got a Ruby application to deploy, for production.
Friday, April 27, 12
Ruby AppRails
Rack
Sinatra
Apache/Nginx
Passenger/Thin
Friday, April 27, 12
But...
The app you deploy today will grow in the future, if you’re successful.
Friday, April 27, 12
Eventual Complexity
Friday, April 27, 12
Ruby AppRails
Rack
Sinatra
Apache/Nginx
Passenger/ThinResque/
DelayedJob
Tasks
Friday, April 27, 12
Ruby AppRails
Rack
Sinatra
Apache/Nginx
Passenger/Thin
crond
JobsResque/
DelayedJob
Tasks
Friday, April 27, 12
Ruby AppRails
Rack
Sinatra
Apache/Nginx
Passenger/Thin
crond
JobsResque/
DelayedJob
Tasks
god/monit
Daemons
Friday, April 27, 12
Deploy Continuum
Friday, April 27, 12
Deploy Continuum
Friday, April 27, 12
Deploy Continuum
Friday, April 27, 12
What is TorqueBox?
TorqueBox is a Ruby Application Server.
Based on JRuby and JBoss AS7.
Friday, April 27, 12
Rails
Rack
Sinatra
ProcsTasks DaemonsJobs
HAClustering Load Balancing
JBoss ASWeb Messaging Scheduling Services
TorqueBox AS
Friday, April 27, 12
Roll your own
๏ Install OS๏ Install packages๏ Configure everything๏ Manage everything
Friday, April 27, 12
Roll your own๏ Apache httpd๏ Load balancer๏ Unicorn, pack of Mongrels๏ crontab๏ SMTP๏ memcached๏ Database๏ deployment๏ monitoring
Friday, April 27, 12
Or outsource some...
Friday, April 27, 12
Roll your own >>๏ Apache httpd๏ Load balancer๏ Unicorn, pack of Mongrels๏ crontab๏ SMTP Amazon SES, SendGrid๏ memcached๏ Database๏ deployment Capistrano๏ monitoring New Relic
Friday, April 27, 12
Use a Paas
Outsource everything but your app.
Friday, April 27, 12
Heroku๏ Apache httpd Heroku๏ Load balancer Heroku๏ Unicorn, pack of Mongrels Heroku๏ crontab Heroku๏ SMTP SendGrid๏ memcached Heroku๏ Database Heroku๏ deployment Heroku๏ monitoring New Relic
Friday, April 27, 12
TorqueBox๏ Apache httpd๏ Load balancer JBoss mod_cluster๏ Unicorn, pack of Mongrels TorqueBox๏ crontab TorqueBox๏ SMTP Amazon SES, SendGrid๏ memcached TorqueBox๏ Database๏ deployment Capistrano๏ monitoring NewRelic
Friday, April 27, 12
Case Study!
Friday, April 27, 12
Appalachian Sustainable Agriculture Project (ASAP)
Friday, April 27, 12
Application Needs๏ Rails 2.x web app๏ Caching๏ Background tasks๏ Cron jobs๏ Deployment from SCM๏ Sending email๏ Database queries๏ Monitoring & metrics
Friday, April 27, 12
As deployed on Heroku
Friday, April 27, 12
Migration Motivation
In this particular case, client feels it could save money, and have a better system by moving to an affordable VPS, letting TorqueBox absorb some of the roll-your-own complexity.
Friday, April 27, 12
Install
$ rvm install jruby-1.6.7$ rvm use jruby-1.6.7$ gem install torquebox-server
Friday, April 27, 12
Templatize
$ torquebox rails myapp
Friday, April 27, 12
torquebox.rb template
๏ torquebox-* and activerecord-jdbc-adapter gems
๏ torquebox-rake-support๏ Directories for services, jobs, etc๏ Web session store๏ Rails.cache with TorqueBox๏ Background jobs
Friday, April 27, 12
“Porting” the application
Friday, April 27, 12
Background Jobs
Friday, April 27, 12
Delayed::Job
App uses Delayed::Job
Replace with TorqueBox Backgroundable
Friday, April 27, 12
controller.rb
Delayed Job
def create @export = ExcelExport.create(...) job = ExcelExportJob.new job.excel_export_id = @export.id Delayed::Job.enqueue job @export.set_status "Queued for Export" @export.save!end
Friday, April 27, 12
controller.rb
Delayed Job
def create @export = ExcelExport.create(...) job = ExcelExportJob.new job.excel_export_id = @export.id Delayed::Job.enqueue job @export.set_status "Queued for Export" @export.save!end
job = ExcelExportJob.new job.excel_export_id = @export.id Delayed::Job.enqueue job
Friday, April 27, 12
excel_export_job.rb
class ExcelExportJob < Struct.new(:excel_export_id) def perform ExcelExport.find(self.excel_export_id).generate_report endend
Delayed Job
Friday, April 27, 12
excel_export.rb
Delayed Job
class ExcelExport < ActiveRecord::Base def generate_report begin set_status 'Processing' spreadsheet = Spreadsheet.new(self.businesses) spreadsheet.write_workbook self.workbook_file set_status 'Storing on S3' File.open(self.workbook_file) {|out|self.output = out} File.delete(self.workbook_file) set_status 'Complete' rescue set_status 'Export Error' end endend
Friday, April 27, 12
Delayed Job
Mayhap you need to run a worker, also...
Friday, April 27, 12
Delayed::Job
Exporter < AR::Base
ExportJob < Struct
Database
Worker
Friday, April 27, 12
That's pretty explicit
TorqueBox allows you to very easily run methods asynchronously.
Friday, April 27, 12
excel_export.rb
Backgroundable
class ExcelExport < ActiveRecord::Base always_background :generate_report
def generate_report begin set_status 'Processing' ... rescue set_status 'Export Error' end end
end
Friday, April 27, 12
excel_export.rb
Backgroundable
class ExcelExport < ActiveRecord::Base always_background :generate_report
def generate_report begin set_status 'Processing' ... rescue set_status 'Export Error' end end
end
always_background :generate_report
Friday, April 27, 12
export_controller.rb
Backgroundable
def create @export = ExcelExport.create(...) @export.set_status "Queued for Export" @export.save @export.generate_reportend
Friday, April 27, 12
export_controller.rb
Backgroundable
def create @export = ExcelExport.create(...) @export.set_status "Queued for Export" @export.save @export.generate_reportend
@export.generate_report
Friday, April 27, 12
$ git rm excel_export_job.rb
excel_export_job.rb
Backgroundable
Kill it!
Friday, April 27, 12
Backgroundable
Exporter < AR::Base
ExportJob < Struct
Database
Worker
Queue Processor
Implicit!magic!
Friday, April 27, 12
Caching
Friday, April 27, 12
Caching
๏ template.rb does the work๏Web sessions๏Fragment and other caching
Friday, April 27, 12
No more memcached!
Friday, April 27, 12
cache = TorqueBox::Infinispan::Cache.newcache.put(“something”, anything)cache.get(“something”)
Cache
Friday, April 27, 12
cron0 0 2 * * ?/WTF??
Friday, April 27, 12
Heroku Cron
๏Free version runs 1x daily๏ Implemented as a rake task๏ Includes app environment
Friday, April 27, 12
Rakefile
Heroku Cron
desc "This task is called by the heroku cron add-on"task :cron => [:environment, :heroku_backup, :refresh_trip_planner]
desc "Refresh the trip planner business data cache"task :refresh_trip_planner => :environment do # # update GIS cache #end
Friday, April 27, 12
Scheduled Jobs
In TorqueBox, a scheduled job is a component of your application.
Friday, April 27, 12
refresh_trip_planner.rb
Straight-forward
class RefreshTripPlanner def run() # # update the GIS cache # endend
Friday, April 27, 12
torquebox.yml
Configuration
jobs: cache.updater: job: RefreshTripPlanner cron: '0 0 2 * * ?' description: Refresh trip cache
Friday, April 27, 12
But...
Doing a huge query once-a-day isn't ideal, it was just "free".
Friday, April 27, 12
Services
Friday, April 27, 12
TorqueBox Services
๏Avoid the huge query๏Keep data more fresher
Friday, April 27, 12
trip_planner_service.rb
Keep it fresher class TripPlannerService def initialize( options={} ) @queue = TorqueBox::Messaging::Queue.new('/queues/trip_planner') end def start Thread.new do initialize_cache while should_run update_cache(@queue.receive) end end end
def update_cache( business_id ) TripPlanner.insert_or_update business_id end # initialize_cache(), stop(), etcend
Friday, April 27, 12
trip_planner_service.rb
Keep it fresher TripPlannerService def initialize( options={} ) @queue = TorqueBox::Messaging::Queue.new( '/queues/trip_planner' ) end def start Thread.new do initialize_cache while should_run update_cache @queue.receive end end end
def update_cache( business_id ) TripPlanner.insert_or_update business_id end # initialize_cache(), stop(), etcend
def initialize( options={} ) @queue = TorqueBox::Messaging::Queue.new( ... )end
Friday, April 27, 12
trip_planner_service.rb
Keep it fresher TripPlannerService def initialize( options={} ) @queue = TorqueBox::Messaging::Queue.new( '/queues/trip_planner' ) end def start Thread.new do initialize_cache while should_run update_cache @queue.receive end end end
def update_cache( business_id ) TripPlanner.insert_or_update business_id end # initialize_cache(), stop(), etcend
def start Thread.new do initialize_cache while should_run update_cache(@queue.receive) end endend
Friday, April 27, 12
require 'torquebox-messaging'
class Business < ActiveRecord::Base
after_save :update_trip_planner
def update_trip_planner queue = TorqueBox::Messaging::Queue.new('/queues/trip_planner') queue.publish( self.id ) end
end
app/models/business.rb
Keep it fresher
Friday, April 27, 12
require 'torquebox-messaging'
class Business < ActiveRecord::Base
after_save :update_trip_planner
def update_trip_planner queue = TorqueBox::Messaging::Queue.new('/queues/trip_planner') queue.publish( self.id ) end
end
app/models/business.rb
Keep it fresher
queue = TorqueBox::Messaging::Queue.new(...)queue.publish( self.id )
Friday, April 27, 12
Queue, Service, Cache
Non-blocking
Service
Cache
ActiveRecord
Business
Queue
Friday, April 27, 12
Production!
Friday, April 27, 12
(Remember, I work for Red Hat)
Friday, April 27, 12
Set up the server
$ yum install \ java-1.6.0-openjdk \ httpd \ mod_cluster \ git \ postgresql-server
Friday, April 27, 12
Launch on boot
Friday, April 27, 12
init.d
Install /etc/init.d/jboss-asfrom the stock distribution
Friday, April 27, 12
/etc/jboss-as/jboss-as.conf
JBoss configuration
# General configuration for the init.d script# Place this file in /etc/jboss-as/jboss-as.conf# and copy $JBOSS_HOME/bin/init.d/jboss-as-standalone.sh # to /etc/init.d/jboss-as-standalone
JBOSS_USER=torqueboxJBOSS_HOME=/opt/torquebox/current/jbossJBOSS_PIDFILE=/var/run/torquebox/torquebox.pidJBOSS_CONSOLE_LOG=/var/log/torquebox/console.logJBOSS_CONFIG=standalone-ha.xml
Friday, April 27, 12
Load Balance with mod_cluster
Friday, April 27, 12
httpd.conf
mod_cluster
LoadModule slotmem_module modules/mod_slotmem.soLoadModule proxy_cluster_module modules/mod_proxy_cluster.soLoadModule advertise_module modules/mod_advertise.soLoadModule manager_module modules/mod_manager.so
<Location /mod_cluster_manager> SetHandler mod_cluster-manager AllowDisplay On</Location>
Listen torquebox-balancer:6666
Friday, April 27, 12
httpd.conf (continued)
mod_cluster<VirtualHost torquebox-balancer:6666> <Directory /> Order deny,allow Deny from all Allow from all </Directory> KeepAliveTimeout 60 MaxKeepAliveRequests 0
EnableMCPMReceive ManagerBalancerName torquebox-balancer AllowDisplay On AdvertiseFrequency 5 AdvertiseSecurityKey secret </VirtualHost>
Friday, April 27, 12
Deploy with Capistrano
Friday, April 27, 12
Capistrano
Regular capistrano deployments are supported given the recipes provided by torquebox-capistrano-support.gem
Friday, April 27, 12
Deployed
[root@torquebox opt]# ls -l apps/buyappalachian.org/total 8lrwxrwxrwx 1 23 Mar 16 15:10 current -> releases/20120315230553drwxrwxr-x 5 4096 Mar 15 19:05 releasesdrwxrwxr-x 6 4096 Mar 15 17:29 shared
Friday, April 27, 12
And then it grows...
As your app evolves, you might add stuff like WebSockets/STOMP, or HA or other facilities already in the tin.
Friday, April 27, 12
Can we go further?
Friday, April 27, 12
What's a PaaS?
Application
Queues
DB Email
Cache Jobs
Friday, April 27, 12
OPENSHIFT
http://openshift.redhat.comhttps://github.com/torquebox/torquebox-openshift-express
Friday, April 27, 12
Roadmap & Status
๏Current 2.0.1๏Continued Red Hat investment๏ Interop with other languages, such as Clojure via Immutant
Friday, April 27, 12
Resources
http://torquebox.org/
@torquebox on Twitter
#torquebox on IRC
Friday, April 27, 12
Question everything.
Friday, April 27, 12
Creative Commons BY-SA 3.0
Friday, April 27, 12