Our site has counters for views, comments and likes that are displayed on article thumbnails. Those thumbnails are cached and then used in various places, often cached themselves. The issue is that as the counters increment, all these different cache fragments quickly go stale. The result is that different pages displaying the same articles will show counters with different values.
The site is made in RoR 4.2.1 and caching is done through Memcached.
What is the best way to go about maximizing caching for best performance while still keeping updated content?
There are multiple approaches to do this, and it all boils down to how much optimization you need:
Fetch the count for page views in a separate AJAX call once the cached page has loaded. This solution should be highly compatible with your current design.
Cache partials instead of complete page. This means that you'll have cached copies of all partials except that for page views, and so you might need to make major changes in your code.
If the requests are too high, you can also write an approximation function (in js), which given past page views tells you the estimated page views, which can be displayed to end user. The base data can be refreshed every minute (or custom frequency).
Related
I use rails to present automated hardware testing results; our tests are run mainly via TCL. Recently, we have implemented a "log4TCL" which is basically a translated version of log4J. The log files have upwards of 40000 lines, each of which is written to the database as a logline record, and load time for the view is too long to be considered usable. I have tried to use ajax requests to speed things up, but the initial query/page load accounts for ~75% of the full page load.
My solution is page caching. I cannot use the rails included page caching because each log report is a different instance of "log_viewer". The report is generated using a test_run_id parameter. Rails-included page caching only caches one instance of "log_viewer.html". What I need is "log_viewer_#{test_run_id}.html". I have implemented a way of doing this. The reports age out after one week and are purged from the test_runs/log_viewer_cache directory to save disk space. If an older report is needed, loading the page re-generates the report with a fresh age-out timer.
I have come to the conclusion that this is the way to go. My concern is that I have not found any other implementations such as this anywhere which leads me to believe that I have missed an inherent flaw in my design. Any input would be much appreciated.
EDIT: For clarification, the "Dynamic" content of this report is what takes too long to load. I need to cache multiple instances of what action/fragment caching is not concerned with.
Is there and easy way to do this ? I have the search working ... I have paging working but not together
So the easiest way to me seems to just cache the search results and then refer to that collection in the controller as the collection to page through.. Does that make sense ?
Or how should I approach this from a high level ? (I come from 10 years of ASP.NET webforms)
Caching search results improves performance of paging through the results data, but consumes memory. If searches are done by multiple users you need to cache search results for each user. Depending on the potential amount of data to be held in memory this may or may not be feasible. (Such caching should be done for a short time with sliding/absolute expiration.)
Doing search every time you need to provide data for the current result page creates higher CPU load, but does not consume memory between requests. Of course, request for page data must carry search parameters, so that the search can be redone.
This is the usual compromise between placing more stress on CPU or memory, and it is up to you to decide what to choose. I would cache the results with a short sliding expiration time (1 minute) and still make sure that the search can be performed again if the cached result is not present.
The point of concatenation is to improve performance by having just one file to download, but that means that every time you change a bit of your own javascript, the whole package is recompiled and fingerprinted - including large libraries like jQuery that haven't changed, and would have been cached if they were downloadable separately, but now jQuery is going to be redownloaded each time as part of your unified application.js.
What am I missing here? Wouldn't the best approach be to create two manifests - one for your own files (which are small and change frequently), and one for libraries (which are large and change infrequently)?
I will give it a try, with some speculation inside it ...
First, JQuery is provided by Rails itself, and depending on your layout, it will come from a CDN. So lets look at the libraries that may change over time. What are the scenarios here?
A user is visiting the web site for the first time. His browser (depending on the type) has to load all Javascript files before he can show something that comes below that (therefore, move it to the end). Depending on the browser, it may load 2, 4, 6 or 8 resources at one time, if your site consists of dozens or even hundreds of them, this will slow the presentation then.
A user is visiting the web site (this page) the second time. Normally on the same day, hour or even minute. The whole thing will be cached, there is only one request, that the cached thing can be used, pretty fast then. If all resources (hundreds) would be loaded one after another, there will be hundreds of requests if the cache is valid.
A user is visiting the web site the second time, and there was some time in between (lets say 15 days). Only 1 resource was changed, all other could be cached and reused. How probable is that?
A user (the developer) is visiting his work during development. No asset pipeline is used, no caching, because every change should be noticed immediately.
So I think, from a web site view, only the scenario 3 may be (a little bit) slower, and it is the most improbable one. Normally, the overhead of many, many requests is much more relevant than the size of the requests.
If you have the time, just try with a tool that displays it the loading time of all resources. There may be edge cases that one resource will change often, and should therefore not included in the asset pipeline, but normally, every change includes numerous resources, and caching them as one bit blob helps to avoid a lot of requests.
Here are some references to literature that discusses this:
Steve Souders: High performance web sites, short and a good summary
Steve Souders: High performance web sites the same in a book
Steve Souders: Even faster web sites more advanced, same topic
Cary Millsap: Thinking clearly about performance (first part) more on the server side, but excellent and especially the start very clear.
Caching is something that I kind of ignored for a long time, as projects that I worked on were on local intranets with very little activity. I'm working on a much larger Rails 3 personal project now, and I'm trying to work out what and when I should cache things.
How do people generally determine this?
If I know a site is going to be relatively low-activity, should I just cache every single page?
If I have a page that calls several partials, is it better to do fragment caching in those partials, or page caching on those partials?
The Ruby on Rails guides did a fine job of explaining how caching in Rails 3 works, but I'm having trouble understanding the decision-making process associated with it.
Don't ever cache for the sake of it, cache because there's a need (with the exception of something like the homepage, which you know is going to be super popular.) Launch the site, and either parse your logs or use something like NewRelic to see what's slow. From there, you can work out what's worth caching.
Generally though, if something takes 500ms to complete, you should cache, and if it's over 1 second, you're probably doing too much in the request, and you should farm whatever you're doing to a background process…for example, fetching a Twitter feed, or manipulating images.
EDIT: See apneadiving's answer too, he links to some great screencasts (albeit based on Rails 2, but the theory is the same.)
You'll want to think about caching several kinds of things:
Requests that are hit a lot, and seldom change
Requests that are "expensive" to draw, lots of database calls, etc. Also hopefully these seldom change.
The other side of caching that shouldn't go without mention, is expiration. Its also often the harder part. You have to know when a cache is no longer good, and clear it out so fresh content will be generated. Sweepers, or Observers, depending on how you implement your cache can help you with this. You could also do it just based on a time value, allow caches to have a max-age and clear them after that no matter what.
As for fragment vs full page caching, think of it in terms of how often those parts are updated. If 3 partials of a page are never updated, and one is, maybe you want to cache those 3, and allow that 1 to be fetched live for so you can have up to the second accuracy. Or if the different partials of a page should have different caching rules: maybe a "timeline" section is cached, but has a cache-age of 1 minute. While the "friends" partial is cached for 12 hours.
Hope this helps!
If the site is relatively low activity you shouldn't cache any page. You cache because of performance problems, and performance problems come about because you have too much data to query, too many users, or worse, both of those situations at the same time.
Before you even think about caching, the first thing you do is look through your application for the requests that are taking up the most time. Not the slowest requests, but the requests your application spends the most aggregate time performing. That is if you have a request A that runs 10 times at 1500ms and request B that runs 5000 times at 250ms you work on optimizing B first.
It's actually pretty easy to grep through your production.log and extract rendering times and URLs to combine them into a simple report. You can even do that in real-time if you want.
Once you've identified a problematic request, you go about picking apart what it's doing to service the request. The first thing is to look for any queries that can be combined by using eager loading or by looking ahead a bit more to anticipate what you'll need. The next thing is to ensure you're not loading data that isn't used.
So many times you'll see code to list users and it's loading 50KB per person of biographical data, their Facebook and Twitter handles, literally everything about them, and all you use is their name.
Fetch as little as you need, and fetch it in the most efficient way you can. Use connection.select_rows when you don't need models.
The next step is to look at what kind of queries you're running, and how they're under-performing. Ensure your indexes are all set properly and are being used. Check that you're not doing complicated JOIN operations that could be resolved by a bit of tactical de-normalization.
Have a look at what data you are storing in your application, and try and find things that can be removed from your production database and warehoused somewhere else. Cycle your data out regularly when it's no longer relevant, preserve it in a separate database if you need to.
Then go over and have a look at how your database server is tuned. Does it have sufficiently large buffers? Is it on hardware that could be upgraded with more memory at a nominal cost? Too many people are running a completely un-tuned database server and with a few simple settings they can get ten-fold performance increases.
If, and only if, you still have a performance problem at this point then you might want to consider caching.
You know why you don't cache first? It's because once you cache something, that cached data is immediately stale. If parts of your application use this data under the assumption it's always up to date, you will have problems. If you don't expire this cache when the data does change, you will have problems. If you cache the data and never use it again, you're just clogging up your cache and you will have problems. Basically you'll have lots of problems when you use caching, so it's often a last resort.
Suppose we are building an E-commerce site that allows consumers to search for products by typing in keywords. Say there are at most 200,000 products, and there are millions of consumers using the system. Let’s say the product table is updated fairly frequently. Since the number of products is not that high and we can probably store the entire product table in memory and search against it instead of hitting the database. We are hoping to create distributed caches that store the same data but reside in different servers (for high availability and performance reason) and we need to be able to synchronize data among these caches and invalidate caches when product table is modified.
Our application is built using ASP.NET MVC and NHibernate. I am trying to understand whether NHibernate’s level-2 caching would help with my situation. I would really appreciate if you guys can shed some light on this.
I understand that level-2 caching will help cache query result so if two different users are searching using the same keyword, the L2 Cache will serve the result from the cache instead of from the database. But it doesn’t help us much since the product table is updated frequently and the cached result will be stale.
My question is am I understanding L2 caching correctly and is there exists anything that help manage cache the way I would like to do (multiple caches, the same data, synchronize between cache and invalidate cache). Any thoughts is highly appreciated.
Having used both the second-level cache (using the memcached provider) and the NHibernate.Search add-on it seems to me you could benefit from both.
The NHibernate.Search component depends on Lucene.Net and keyword search is decoupled from the Database it self. A different index file is created per class mapped and optimizations can be set on the property level using attributes, giving you an extra level of granularity. Additionally, you can implement best match and propositions (check Lucene in Action and/or Hibernate Search in action). As a note, you don't have to maintain the index (unless you explicitly request an index rebuild); the implementation manages everything behind the scenes although you can manipulate the index if you wish to do so. So, adding/deleting/updating a product will automatically update the according index.
For the second-level cache you get instant performance boost. On a test environment with a data set of approx 2 mil rows i had more than 20% improvement even on an extremely low request count. The performance boost is gradually larger as the request count increases - the application first hits the 2nd level cache and if it does not find it then hits the DB to fetch the required rows and inserts them on the cache for future queries. Again you can manage stuff like cache duration and other configuration settings, as well as explicitly clear the cache (all of it, a part of it, or particular entries) if you wish to do so. Note that cache state is managed by the application during save/update/delete.
For scallability
* the 2nd level cache depends on the provider (ie memcached is highly performant and scalable and supports distributed instances).
* for the Lucene.Net/NHibernate.Search you will need to set up a specific place that the indexes will reside and that place must be accessible for read/write by all web-application instances. Note here that the sensitive link is I/O and file contention, so setting up a machine with a faster than light file system will prevent that from happening (i am speaking for your scenario with many thousands of search requests per second)
As a side note i would highly recommend NHibernate.Search since it is extremely faster than LIKE queries and is easier to use than implementing SQL-Server's FullText search inside the application (which i have done).
Whether a second level cache will help depends on exactly how frequently your product table is updated in relation to cache hits. If you add 100 new products an hour but receive 10,000 queries an hour, even a 10% cache hit rate will make a big difference. If the rates are reversed, a second level cache will be of almost no value.
I suggest you set up a stress test environment that closely approximates your production environment and perform benchmarking on the various second level cache providers.
Also check that your DB is configured properly for an update-heavy scenario.
I recommend using NHibernate.Search w/ Lucene. It works together with the 2nd level cache. Lucene can do sophisticated text searching ripping fast and then return back the entity keys to NHibernate which pulls the full entity out of its 2nd level cache. The NHibernate.Search extension does the work of keeping your Lucene index in sync.
TekPub did a recent episode on your exact scenario of searching product descriptions. The episode compares NHibernate queries, SQL Full-text indexing and Lucene w/ NHibernate.Search.