It’s really important to make sure that your Rails controllers complete their actions quickly. If you have some code in their that takes a while to execute (say, a remote network call), you need to have that stuff done in a different process or thread.
For Daily Caption, we use Starling to queue up messages that need to get sent to Facebook. Another program, creatively called “facebook_daemon” watches the Starling queue.
So, you’ll want to install starling and memcached in the usual fashion.
Then, in config/initializers/starling.rb, put:
ports = { 'production' => "memcache-server:22122", "development" => "localhost:22122", "test" => "localhost:22122" }
STARLING = MemCache.new(ports[RAILS_ENV])
To add stuff to the Starling queue, you can do stuff like:
STARLING.set "key-of-some-sort", "value"
In our FacebookPublisher model, we have the following method:
class FacebookPublisher > Facebooker::Rails::Publisher
def self.queue action, args
STARLING.set "facebook_actions", [action, *args]
end
# and more
If we need to update Facebook, say, with a updated comment notification, the following code does the trick:
FacebookPublisher.queue(:deliver_notify_caption_comment, self.caption, self)
This adds, to the key “facebook_actions”, an array that looks like this: [:deliver_notify_caption_comment, [self.caption, self]]
You following? At this point, we have that array stored in Starling. Now, it’s up to another program to monitor Starling and pop messages off its queue.
#!/usr/bin/env ruby
require 'rubygems'
require 'daemons'
require File.dirname(__FILE__) + "/../config/environment"
class FacebookDaemon
TIMEOUT = 5
def self.do_publisher_things args
puts "Running FB publisher"
puts args.inspect
action = args.first
args = args[1..-1]
Timeout.timeout(TIMEOUT) { FacebookPublisher.send action, *args}
end
def self.do_profile_updates args
puts "Doing profile update"
puts args.inspect
user, fb_user, content = args
Timeout.timeout(TIMEOUT) { fb_user.profile_fbml = content }
Timeout.timeout(TIMEOUT) { user.update_friends! }
end
def self.update
begin
while fetch = STARLING.get("facebook_actions")
do_publisher_things(fetch)
end
while fetch = STARLING.get("facebook_profile_update")
do_profile_updates(fetch)
end
rescue StandardError => e
puts "**** EXCEPTION!!! fetch = #{ fetch.inspect } *** "
puts e
puts e.backtrace.inspect
end
end
end
ActiveRecord::Base.logger = Logger.new STDOUT
options = {
:app_name => "facebook_daemon",
:ARGV => ARGV,
:dir_mode => :normal,
:dir => File.expand_path(File.dirname(__FILE__) + '/../log'),
:multiple => false,
:backtrace => true,
:monitor => false
}
Daemons.run_proc("facebook_daemon", options) do
loop do
FacebookDaemon.update
sleep 1
end
end
It’s pretty straightforward stuff, FacebookDaemon.update is called once a second. The update method pops stuff off the Starling queue, and then calls do_publisher_things with whatever array was stored. do_publisher_things knows that the method is the first thing in the array, so calls that method. Hence, FacebookPublisher.deliver_notify_caption_comment (or whatever is on the queue to be called) is called, and Facebook gets sent the call.
If you keep the slow code out of the Rails request cycle, everyone will be happy. I’ve outlined one strategy, but there are more. We also use BackgroundJob to move slow stuff out of the cycle.
No comments yet.