Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 3 - cache web service call

In my application, in the homepage action, I call a specific web service that returns JSON.

parsed = JSON.parse(open("http://myservice").read)
@history = parsed['DATA']

This data will not change more than once per 60 seconds and does not change on a per-visitor basis, so i would like to, ideally, cache the @history variable itself (since the parsing will not result in a new result) and auto invalidate it if it is more than a minute old.

I'm unsure of the best way to do this. The default Rails caching methods all seem to be more oriented towards content that needs to be manually expired. I'm sure there is a quick and easy method to do this, I just don't know what it is!

like image 500
tkrajcar Avatar asked Aug 30 '11 04:08

tkrajcar


2 Answers

You can use the built in Rails cache for this:

@history = Rails.cache.fetch('parsed_myservice_data', :expires_in => 1.minute) do
  JSON.parse connector.get_response("http://myservice")
end

One problem with this approach is when the rebuilding of the data to be cached takes quite a long time. If you get many client requests during this time, each of them will get a cache miss and call your block, resulting in lots of duplicated effort, not to mention slow response times.

EDIT: In Rails 3.x you can pass the option :race_condition_ttl to the fetch method to avoid this problem. Read more about it here.

A good solution to this in previous versions of Rails is to setup a background/cron job to be run at regular intervals that will fetch and parse the data and update the cache.

In your controller or model:

@history = Rails.cache.fetch('parsed_myservice_data') do
  JSON.parse connector.get_response("http://myservice")
end

In your background/cron job:

Rails.cache.write('parsed_myservice_data',
  JSON.parse connector.get_response("http://myservice"))

This way, your client requests will always get fresh cached data (except for the first request if the background/cron job hasn't been run yet.)

like image 154
Lars Haugseth Avatar answered Nov 15 '22 09:11

Lars Haugseth


I don't know of an easy railsy way of doing this. You might want to look into using redis. Redis lets you set expiration times on the data you store in it. Depending on which redis gem you use it'd look something like this:

@history = $redis.get('history')

if not @history
  @history = JSON.parse(open("http://myservice").read)['DATA']
  $redis.set('history', @history)
  $redis.expire('history', 60)
end

Because there's only one redis service this will work for all your rails processes.

like image 35
chrismealy Avatar answered Nov 15 '22 10:11

chrismealy