Cache strategy for rails where new objects appearing invalidates the cache - ruby-on-rails

I'm not seeing something obvious re:caching strategies in rails.
I have a prices table that logs prices at particular times. So the current price would be the last row added to the prices table. Very frequently, the table is queried to display the entries, so caching that fetching would be great to stop (very) frequent queries hitting the database.
So as far as I can see it would be fine for my entire app to cache that data completely until a new row gets added.
Does rails caching handle that well? I see examples for on update of an active record object you expire the cache and force the updated object to be retrieved again - but I want the collection of objects (e.g. Price.find(:all) to be cached until Price.find(:all) contains a new object. So adding a new row to the db would have to expire the cache and force a new retrieval - the new price might be the latest for a few days, or it might only last a few minutes.)
If not self-evident, this is the first time I've ever looked at caching. I'll be attempting to deploy memcache on heroku.
Thanks a lot!
Edit: Just thought it might be useful to point out that the rails controllers only render JSON requests - rich single page app - so the main things to cache are the database query. This is why it is confusing me, I see partial caching, page caching, but I'm struggling to understand the type of caching I'm hopefully describing above.
Dave

To cache your prices, you can use the following. It would be helpful to place this somewhere it could be reused (such as your model).
cached_prices = Rails.cache.fetch("cached_prices", :expires_in => 300) do
Price.find(:all)
end
The above code caches the query for 5 minutes (300 seconds). In order to manually delete the cache entry (say when a new Price entry is created), call the following:
Rails.cache.delete("cached_prices")
To make this easier, you can place the cache delete code in an Observer on the Price model.
You can find much more information on all of the types of caching you have available to you here: http://guides.rubyonrails.org/caching_with_rails.html

Related

Ruby on Rails 4 - Cache Partials from Model

I am working on an app where certain pages take longer to load because of the views are a list of X to 1000 table rows. Each row has Y columns that take time to construct.
Right now, the first time the user hits the page with the table rows, it takes say N seconds. However, on refreshes, the page loads very quickly b/c of the cache.
I would like preload these partials and store it in my cache from a Rails model when the user logs in so if the user visits the page with the table rows, it will already be cached.
I have attempted to do this by calling a Sidekiq job when the user logins and cache the partial via a call below however even though I used the same cache key, it doesn't seem to be pulling from the same cache?
view = ActionView::Base.new(ActionController::Base.view_paths, {})
view.render(...)
Any suggestions? I do know it is because when I call from the above, the fragment cache logic does not generate the digest. I ended up solving the issue by using Rails.cache fetch calls within a helper.

"Archive" a table on rails

long time reader first time poster.
I recently started using ruby on rails so I am still very new to the environment (even though I have completed a few guides) so be gentle please.
What I want to do is create a sort of archive table of another table that the user can access at any time(via a different link on the website).
So for example, if I have the "users" table, I want to be able to archive old users but still give the option for someone to go and view them.
Basically, it will sort of have to delete the user from the initial table, and save his/her info in to the archived_users table.
Thank you for your time.
I figured my comment was more of an answer, so posting it here and adding more info
In this situation you're better off adding some sort if "active" flag to the users table, which you can flip on or off as needed. That way you don't need to worry about dealing with yet another model class, and you can reuse all the same view and controller structures. In your views, you can then simply "hide" any inactive users (and maybe only show inactive folks if the logged in user is an admin...etc).
You also have the freedom to include other meta data such as "deactivated on" (time stamp) for example.
Long story short, if you're concerned about performance, with proper indexing (and partitioning if necessary), you shouldn't really need to create a separate archive table.
The only reason I can think of to do this is if you're dealing with billions upon billions of records, and/or growing by an insane amount (which is probably not your case).
The best way to do this is probably to add a column called deleted on the original Users table. You can then filter out the old users in normal circumstances (preferably using a default scope) but allow them to be seen/queried when needed.
Papertrail might work for you.
It creates a "versions" table and logs create/update/destroy events for any class which includes has_paper_trail. For example:
class User < ActiveRecord::Base
has_paper_trail
end
deleted_users = Papertrail::Version.where(item_type: User, event: "destroy")
deleted_users.last.reify.name # assuming the users table has a 'name' column

How to design a database in rails where all tables can be filtered by a global setting? Default global scope?

I am building a rails app and the data should be reset every "season" but still kept. In other words, the only data retrieved from any table should be for the current season but if you want to access previous seasons, you can.
We basically need to have multiple instances of the entire database, one for each season.
The clients idea was to export the database at the end of the season and save it, then start fresh. The problem with this is that we can't look at all of the data at once.
The only idea I have is to add a season_id column to every model. But in this scenario, every query would need to have where(season_id: CURRENT_SEASON). Should I just make this a default scope for every model?
Is there a good way to do this?
If you want all the data in a single database, then you'll have to filter it, so you're on the right track. This is totally fine, as data is filtered all the time anyway so it's not a big deal. Also, what you're describing sounds very similar to marking data as archived (where anything not in the current season is essentially archived), something that is very commonly done and usually accomplished (I believe) via setting a boolean flag on every record to true or false in order to hide it, or some equivalent method.
You'll probably want a scope or default_scope, where the main downside of a default_scope is that you must use .unscoped in all places where you want to access data outside of the current season, whereas not using a default scope means you must specify the scope on every call. Default scopes can also seem to get applied in funny places from time to time, and in my experience I prefer to always be explicit about the scopes I'm using (i.e. I therefore never use default_scope), but this is more of a personal preference.
In terms of how to design the database you can either add the boolean flag for every record that tells whether or not that data is in the current season, or as you noted you can include a season_id that will be checked against the current season ID and filter it that way. Either way, a scope of some sort would be a good way to do it.
If using a simple boolean, then either at the end of the current season or the start of the new season, you would have to go and mark any current season records as no longer current. This may require a rake task or something similar to make this convenient, but adds a small amount of maintenance.
If using a season_id plus a constant in the code to indicate which season is current (perhaps via a config file) it would be easier to mark things as the current season since no DB updates will be required from season to season.
[Disclaimer: I'm not familiar with Ruby so I'll just comment from the database perspective.]
The problem with this is that we can't look at all of the data at once.
If you need to keep the old versions accessible, then you should keep them in the same database.
Designing "versioned" (or "temporal" or "historized") data model is something of a black art - let me know how your model looks like now and I might have some suggestions how to "version" it. Things can get especially complicated when handling connections between versioned objects.
In the meantime, take a look at this post, for an example of one such model (unrelated to your domain, but hopefully providing some ideas).
Alternatively, you could try using a DBMS-specific mechanism such as Oracle's flashback query, but this is obviously not available to everybody and may not be suitable for keeping the permanent history...

How would you expire fragment caches in the model or in a Resque worker?

I've taken the quote below, which I can see some sense in:
"Cached pages and fragments usually depend on model states. The cache doesn't care about which actions create, change or destroy the relevant model(s). So using a normal observer seems to be the best choice to me for expiring caches."
For example. I've got a resque worker that updates a model. I need a fragment cache to expire when a model is updated / created. This can't be done with a sweeper.
However, using an observer will mean I would need something like, either in the model or in the Resque job:
ActionController::Base.new.expire_fragment('foobar')
The model itself should not know about caching. Which will also break MVC principles that will lead to ugly ugly results down the road.
Use an ActiveRecord::Observer to watch for model changes. It can expire the cache.
You can auto-expire the cache by passing the model as an argument in your view template:
<% cache #model do %>
# your code here
<% end %>
What's happening behind the scenes is a cache named [model]/[id]-[updated_at] is created. Models have a method cache_key, which returns a string containing the model id and updated_at timestamp. When a model changes, the fragment's updated_at timestamp won't match and the cache will re-generate.
This is a much nicer approach and you don't have to worry about background workers or expiring the cache in your controllers/observers.
Ryan Bates also has a paid Railscast on the topic: Fragment Caching
A good and simple solution would be not to expire but to cache it with a key that will be different if the content is different. Here is an example
<% cache "post-#{#post.id}", #post.updated_at.to_i do %>
When that post gets updated or deleted and you fetch it again, it will miss the cache since the hash is different, so it will kind of expire and cache the new value. I think you can have some problems by doing this, for example if you are using the Rails default cache wich creates html files as cache, so you would end up with a lot of files in your public dir after some time, so you better set your application to use something like memcached, wich manages memory deleting old cached records/pages/parcials if needed to cache others or something like that.
I'd recommend reviewing this section on sweepers in the Rails Guide - Caching with Rails: An overview
http://guides.rubyonrails.org/caching_with_rails.html#sweepers
It looks like this can be done without specifically creating lots of cache expiration observers.

Memcache for pagination

This feels like a really bad idea on implementation and I'm not sure of the speed savings given the potential cost in memory.
Using Dalli and Memecache to store a set of data that gets pulled on every page request that needs to be paginated. Its a lot of data so I'd really rather not keep hitting the database if possible, but I also don't know how expensive this operation is for memory, both in memcache as well as just in system memory. To paginate, I do basically the following:
Call to memcache to see if given data exists
If found, get data and go to 4.
If not found, get from database and cache
Dup object because object is frozen. ?? <-- seems like a really bad idea...
Return paginated data for display.
WillPaginate has to perform actions on the dataset coming out of the DB but it throws a "can't modify frozen object" error.
How much bad is this? dup-ing a large object (which is cached to save time in calls to the db) seems like it will end up chewing up a lot of extra memory and run the GC a lot more than needs be. Anyone suggest some ways around this?
Instead of working with the objects at a Model / List view, you can also cache the rendered html. You can even pre-cache the contents for an extra boost :)

Resources