ruby-on-railscachingserializationrails-apifastjsonapi

How to cache a rails api controller


I'm seeing some performance issues in my application and was wondering whether my cache was working properly or if I misunderstood / misconfigured anything. I'm using fast-jsonapi for serialization which comes with a built-in caching option.

Let's say:

class BooksController < ApplicationController
...
  def index
    @books = Books.includes(:author, :publisher).with_attached_cover.all
    BookSerializer.new(@book, { include: [:author, :publisher]}).serializable_hash.to_json
  end
...
end

and

class BookSerializer
  include FastJsonapi::ObjectSerializer
  cache_options store: Rails.cache, namespace: 'fast-jsonapi', expires_in: 24.hours

  belongs_to :author
  belongs_to :publisher

  attributes :all_the_book_attributes_im_too_lazy_to_list
end

also let's assume I have around 5000 books of 2000 authors and 100 publishers, so I would expect caching to make a big difference in performance.

However, in fact I'm seeing my database hit the same way with or without caching enabled and really slow response times. Further, when checking my cache it only seems to be caching each individual book but not the serialized hash as a whole.

Now I'm wondering whether I completely missed the purpose of caching in a serializer or if I need to add some additional layer in my controller? If yes, is there a solution to do so in a DRY way? Would it conflict with the cache of the serializer? What is the purpose of a cache in a serializer then anyway?

I know that there are multiple ways/layers of cache I can use. I'm just not sure which ones to combine and whether to prevent any conflicts between those layers.


Solution

  • Like I can see you want to cache this JSON response.

    Add a cache key for this query. You need this to invalidate the response, when books change over the time.

    # model book.rb
    class Book < ApplicationRecord
    ...
      def self.cache_key
        {
          serializer: 'books',
          stat_record: Book.maximum(:updated_at)
        }
      end
    ...
    end
    

    In your controller use that key to get the data from the cache OR make a new query:

    class BooksController < ApplicationController
    ...
      def index
        @books = Rails.cache.fetch(Book.cache_key) do
          BookSerializer.new(
            Books.includes(:author, :publisher).with_attached_cover.all, 
            { 
              include: [:author, :publisher]
            }
          ).serializable_hash.to_json
        end
    
        render json: @books 
      end
    ...
    end
    

    You can also have a look at page caching.

    And btw if you have 5000 entries you should think of pagination.