Memcache for pagination - ruby-on-rails

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 :)

Related

Rails Heroku app spending lots of time in Ruby running app code

I have a production Rails app running on Heroku, and some API endpoints are taking a long period of time to resolve (~1-2s).
It's a normal Rails RESTful GET action cases#index. The method looks like so:
#cases = cases_query
meta = {
total: #cases.total_count,
count: params[:count],
page: params[:page],
sort: params[:order],
filter: pagination[:filter],
params: params
}
render json: #cases,
root: 'cases',
each_serializer: CaseSerializer,
meta: meta
The method runs an ActiveRecord query to select data, serializes each record and renders JSON. Skylight, a Rails profiler/monitoring/performance tool, is telling me that this endpoint amongst others is spending 70% in the controller method (in Ruby), and 30% in the database.
What in this code or in my app's setup is causing this to spend so much time in the app code? Could it be a gem?
Picture of Skylight analytics on this endpoint (you can see the bulk of the time is spent in Ruby in the controller action):
ActiveRecord can generate a ton of Ruby objects from queries. So you track the time it takes for the database to return results, and that may be ~20% of your request, but the rest could still be in ActiveRecord converting those results into Ruby objects.
Does your query for this request return a lot of rows? Are those rows very wide (like when you do a join of table1., table2., table3.*)?
I've had some experience in the past with serializers really really crushing performance. That usually ends up being a bit of a line by line hunt for what's responsible.
To troubleshoot this issue I recommend finding a way to get realtime or near realtime feedback on your performance. The newrelic_rpm gem has a /newrelic page you can view in development mode, which should provide feedback similar to Skylight. Skylight may have a similar development mode page you should look into.
There's also a gem called Peek that adds a little performance meter to each page view, that you can add gems to in order to show specific slices of the request, like DB, views, and even Garbage collection. https://github.com/peek/peek Check it out, especially the GC plugin.
Once you have that realtime feedback setup, and you can see something that maps roughly to your skylight output, you can start using a binary search in your code to isolate the performance problem.
In your controller, eliminate the rendering step by something like:
render json: {}
and look at the results of that request. If the performance improves dramatically then your issue is probably in the serialization phase.
If not, then maybe it is ActiveRecord blowing up the Ruby objectspace. Do a google search for Ruby Object Space profiling and you'll find ways to troubleshoot this.
If that's your problem, then try to narrow down the results returned by your query. select only the columns you need to render in this response. Try to eliminate joins if possible (by returning a foreign key instead of an object, if that is possible).
If serialization is your problem... Good luck. This one is particularly hard to troubleshoot in my experience. You may try using a more efficient JSON gem like OJ, or hardcoding your serializers rather than using ActiveRecord::Serializer (last resort!).
Good luck!
Normally database queries can cause this kind of issue revisit you database queries and try to optimize them apply joins where you can.
Also try to use Puma gem with heroku to improve your server performance.

How could I enforce model data in memory to accelerate the query

For some models, the data is small but frequently used and also changing several times a week.
Currently, I'm using the cache to accelerate the response (by putting them into memory)
However, it will be a sort of buggy,
Once I forget to change the cache name like "model_#{self.to_s}_#{__callee__}_#{city}-2016-01-28"
How could I enable the access models operations in memory (like put them into Redis, or some memory based DB, only for some special models)
I'm using mongoDB currently.
Thanks
class AA
include Mongoid::Document
def self.get_country(city_or_airport_name)
any_of({airport: /.*#{city_or_airport_name}.*/i},
{city: /.*#{city_or_airport_name}.*/i}).to_a.first["country"]
end
def self.get_airports(city)
Rails.cache.fetch("model_#{self.to_s}_#{__callee__}_#{city}-2016-01-28") do
where(city: city).to_a.collect{|i| i.airport}
end
end
I use memcached in front of some SQL tables that don't change too often, but get hit a few times per second. Redis will do too. I also have tables which change rarely (once a week or so), when there is under 100-500k of data i dump it to a JSON file via crontab or upon change, then preload at application boot, but this slows down booting and may cause trouble later if the table/set grows.

How to improve performance of single-page application?

Introduction
I have a (mostly) single-page application built with BackboneJS and a Rails backend.
Because most of the interaction happens on one page of the webapp, when the user first visits the page I basically have to pull a ton of information out of the database in one large deeply joined query.
This is causing me some rather extreme load times on this one page.
NewRelic appears to be telling me that most of my problems are because of 457 individual fast method calls.
Now I've done all the eager loading I can do (I checked with the Bullet gem) and I still have a problem.
These method calls are most likely ocurring in my Rabl serializer which I use to serialize a bunch of JSON to embed into the page for initializing Backbone. You don't need to understand all this but suffice to say it could add up to 457 method calls.
object #search
attributes :id, :name, :subscription_limit
# NOTE: Include a list of the members of this search.
child :searchers => :searchers do
attributes :id, :name, :gravatar_icon
end
# Each search has many concepts (there could be over 100 of them).
child :concepts do |search|
attributes :id, :title, :search_id, :created_at
# The person who suggested each concept.
child :suggester => :suggester do
attributes :id, :name, :gravatar_icon
end
# Each concept has many suggestions (approx. 4 each).
node :suggestions do |concept|
# Here I'm scoping suggestions to only ones which meet certain conditions.
partial "suggestions/show", object: concept.active_suggestions
end
# Add a boolean flag to tell if the concept is a favourite or not.
node :favourite_id do |concept|
# Another method call which occurs for each concept.
concept.favourite_id_for(current_user)
end
end
# Each search has subscriptions to certain services (approx. 4).
child :service_subscriptions do
# This contains a few attributes and 2 fairly innocuous method calls.
extends "service_subscriptions/show"
end
So it seems that I need to do something about this but I'm not sure what approach to take. Here is a list of potential ideas I have:
Performance Improvement Ideas
Dumb-Down the Interface
Maybe I can come up with ways to present information to the user which don't require the actual data to be present. I don't see why I should absolutely need to do this though, other single-page apps such as Trello have incredibly complicated interfaces.
Concept Pagination
If I paginate concepts it will reduct the amount of data being extracted from the database each time. Would product an inferior user interface though.
Caching
At the moment, refreshing the page just extracts the entire search out of the DB again. Perhaps I can cache parts of the app to reduce on DB hits. This seems messy though because not much of the data I'm dealing with is static.
Multiple Requests
It is technically bad to serve the page without embedding the JSON into the page but perhaps the user will feel like things are happening faster if I load the page unpopulated and then fetch the data.
Indexes
I should make sure that I have indexes on all my foreign keys. I should also try to think about places where it would help to have indexes (such as favourites?) and add them.
Move Method Calls into DB
Perhaps I can cache some of the results of the iteration I do in my view layer into the DB and just pull them out instead of computing them. Or I could sync things on write rather than on read.
Question
Does anyone have any suggestions as to what I should be spending my time on?
This is a hard question to answer without being able to see the actual user interface, but I would focus on loading exactly only as much data as is required to display the initial interface. For example, if the user has to drill down to see some of the data you're presenting, then you can load that data on demand, rather than loading it as part of the initial payload. You mention that a search can have as many as 100 "concepts," maybe you don't need to fetch all of those concepts initially?
Bottom line, it doesn't sound like your issue is really on the client side -- it sounds like your server-side code is slowing things down, so I'd explore what you can do to fetch less data, or to defer the complex queries until they are definitely required.
I'd recommend separating your JS code-base into modules that are dynamically loaded using an asset loader like RequireJS. This way you won't have so many XHRs firing at load time.
When a specific module is needed it can load and initialize at an appropriate time instead of every page load.
If you complicate your code a little, each module should be able to start and stop. So, if you have any polling occurring or complex code executing you can stop the module to increase performance and decrease the network load.

Rails 2 call from one model to another is slow

In Rails 2, I'm trying to optimize the performance of a web page the loads slowly.
I'm timing the execution time of statements in a model and finding that a surprising amount of the time is in a call from inside one model to another model, even though it appears there is no database access at all.
To be specific, let's say the model that is slow is department, and I'm calculating Department.expenditures. The expenditures method needs to know whether the quarter has been closed, and that information is in a different model, Quarter
The first time that Department.expenditures calls Quarter.closed? there is a database access, and I can accept that. But I've done something so to keep that in memory inside the model method, so that future calls to Quarter.closed? have no database access. The code inside Quarter.closed? now runs in around 4 microseconds, but simply invoking Quarter.closed? from inside Department.expenditures takes 400 microseconds, and with hundreds of departments, that adds up.
I could cache the Quarter.closed value inside a global variable, but that seems hairy. Does anyone know what is going on or have a suggestion about a better practice?
Not 100% sure if this applies to your problem. But with similar loading time problems in many cases eager loading solves the problem. You would do it like this:
Department.all(:include => :expenditures)
I'm a bit out of Rails 2 syntax. In Rails 3 you can specify includes quite detailed like this:
Category.includes(:posts => [{:comments => :guest}, :tags]).find(1)
I think (but not sure) the :include option in Rails 2 allowed for similar syntax
So maybe this would work:
Department.all(:include => [:expenditures => [:quarters]])
(Maybe need some experiments with combination of arra/hash syntax here)

Cache strategy for rails where new objects appearing invalidates the cache

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

Resources