Boosting your Rails API Performance Through Caching

Liz Denhup
Aug 22, 2017 · 4 min read

If you’ve ever developed an application whose scalability was limited by increased user traffic or repeated hits to an external API, you’ve probably looked into caching to give your application a performance boost and decrease latency during data fetching operations. Caching, in essence, is when you store the result of an operation or API call in a database so that future requests for that object or response can be served more quickly. Judiciously using caches in your web applications can lead to huge performance boosts and a better experience for end-users.

There are a bunch of different ways to implement a caching scheme, like low-level caching, caching the results of an SQL query, or HTTP caching. Low-level caching applies to situations where you want to persist a bit of data to a cache store (which is the database where your cached data lives). Low-level caching is a good strategy for persisting data that was computationally expensive to generate but not likely to change rapidly. SQL caching is a good option for situations where you want to save the results of an oft-used and rather involved SQL query to a cache store instead of having the query hit the relational database each time. HTTP caching involves storing the response of a given API endpoint. HTTP caching is great because it helps you to stay under your API call rate limits and reduces load on the server. One downside of HTTP caching, however, is that you might store a bad API response in your cache store if you make requests to an endpoint while an API service is unavailable. This bad result could be served to the end-user for however long you have configured the result to be considered fresh.

As with all development tasks, you’ll have to way the possible advantages of caching against any (potential) drawbacks for your unique needs. Overall, however, caching is a great strategy which all developers could benefit from learning more about. You can read more about low-level caching, SQL query caching, and HTTP caching here, here, and here respectively.

I recently started exploring HTTP caching implementations for Rails while working on a small application for fetching weather data by zip code. I used the gems Faraday-middleware and Faraday-HTTP-Cache to gain a lot of control and customizability over the request-response cycle in my API. I defined a class called OverrideCacheControl to set my own cache-control headers (doing so enabled me to set a time-based limit on how long a cached response would be considered fresh. In my case I wanted to cache the forecast for a given zipcode for one hour before discarding it). Here is my custom class for setting cache-control headers:

class Faraday::OverrideCacheControl < Faraday::Middleware  def initialize(app, options = {})    super(app)    @cache_control = options[:cache_control]  end  def call(env)    response = @app.call(env)    response.headers[‘Cache-Control’] = @cache_control    response  endend

After configuring my cache-control headers, I then created an instance of the Faraday-middleware client to actually perform the requests to my API for me.

class ForecastService  def get_forecast(zipcode)    apikey = ENV[‘WUNDERGROUND_API_KEY’]    @client = Faraday.new do |builder|      builder.use :http_cache, store: Rails.cache, logger:      ActiveSupport::Logger.new(STDOUT)      builder.adapter Faraday.default_adapter      builder.response :json, :content_type => /\bjson$/      builder.use Faraday::OverrideCacheControl, cache_control: ‘public, max-age=3600’  end  started = Time.now  response = @client.get(“http://api.wunderground.com/api/#{apikey}/forecast/q/#{zipcode}.json")  finished = Time.now  puts “ Request took #{(finished — started) * 1000} ms.”  return response.body  endend

Because this was a small personal project I used Rails.cache as my cache store, though in production environment I would want to look into other cache stores like Redis or Memcached. (You can read about an awesome real-world application of caching here, where Mark Shaw of Strava engineering discusses how caching with Redis helps Strava to keep their popular leaderboard feature accurate and efficient).

Through using an instance of Rails’ built-in ActiveSupport::Logger class I was able to quantify how much latency I was shaving off by using HTTP caching.

Here is an example two requests I made to my API endpoint for the forecast in Chicago. As you can see, the first request took 254.035 ms and was a ‘cache miss’ — that is, no cached result for this endpoint existed in the store yet so the HTTP response was saved to the store. The second request, which I made about 12 seconds after the first, took only 4 ms to serve up because it had been persisted to the cache store after the first cache miss. This difference represents a 98.45% boost in response time.

app/services/forecast_service.rb:42:in `get_forecast’app/controllers/api/v1/forecasts_controller.rb:4:in `search’Started GET “/api/v1/search?query=60615” for ::1 at 2017–08–22 15:34:43 -0500Processing by Api::V1::ForecastsController#search as */*Parameters: {“query”=>”60615"}HTTP Cache: [GET /api/{apikey}/forecast/q/60637.json] miss, storeRequest took 254.03500000000003 ms.Completed 200 OK in 259ms (Views: 3.3ms)Started GET “/api/v1/search?query=60615” for ::1 at 2017–08–22 15:34:56 -0500Processing by Api::V1::ForecastsController#search as */*Parameters: {“query”=>”60615"}HTTP Cache: [GET /api/{apikey}/forecast/q/60637.json] freshRequest took 0.882 ms.Completed 200 OK in 4ms (Views: 2.5ms)

As you can see, the use of HTTP caching in my application made a huge difference, even though the app itself was fairly small and uncomplicated. For larger applications that serve more users, the performance increases from caching can add up rapidly.

)

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade