Page Caching for Logged-in Users: Loading Dynamic Content with Ajax (Authcache)

How fast do your dynamic PHP/MySQL pages load? Answer: not fast enough. A truly optimized website should serve pages instantaneously to its visitors. There should be no lag time, even 200 milliseconds of server-side script execution time should be considered a burden worth removing.

The keyword for performance is "caching." By taking the load off the processor and database, a website can easily handle hundreds of thousands of visitors. Many frameworks offers some sort of caching method that stores the final rendered HTML. This is easy to implement for anonymous users, but what about logged-in users?

I recently released an open-source Drupal module called Authenticated User Page Caching (Authcache) (and a demo site) for logged-in users. Drupal is a great PHP/MySQL CMS/framework for building community-based websites, though it suffers from a "one-more-query" syndrome--a single page request can have hundreds of SQL queries! The reason why authenticated caching is difficult is because most of the time, when you're logged into a website, the content changes according to your account (like a "You're logged in as [username]" link), so it's pointless to save the final HTML to the server's cache. Instead the entire page has to be rendered by PHP on each request, requiring precious CPU cycles & database hits. Ajax/JavaScript can be used for customizing the cached HTML, however. Of course, this technique can't be used with every website--some sites are just too dynamic--but it can work if the content of pages is similar across users.


Caching Flowchart

Every millisecond counts when it comes to performing page requests--not only are cached pages served faster to the user, but it frees up services/memory faster on the server, giving more bang for your buck. This is important not only on high-volume websites, but when you're on a hosting plan that doesn't give you gig upon gigs of memory or high-performance CPU processing power.

The modular nature of Drupal and its excellent API makes it rather straightforward to implement this type of caching. Other frameworks may prove more difficult. The Authcache module can use a number of different caching engines, such as APC, memcached, or even static files. I've found that APC offers the fastest retrieval times, allowing pages to be served in under 1 millisecond!

Implementing Page Caching
The tricky part with page caching is knowing when to retrieve from the cache and when to delete outdated cache. If there is a POST request, such as when a comment is made, then Drupal/PHP will execute the page code normally and invalidate the cache. When we are logged in as an admin, we don't display or save cache pages (because we want our admin links and don't want users to see them). We may also expire our cache after a certain amount of time to always display the most up-to-date content, which is useful on a front page and search result page. Yes, these little details can be a hassle, but the end result is well worth it if your goal is a faster site.

What About Dynamic Content?
This is where the beauty of Ajax/JavaScript comes in. We don't need to re-render an entire page, just perform a separate confined action. For example, instead of having PHP render "Hello, [username]" text for each page for logged in users along with everything, store the username in a cookie and have JavaScript render the username. Then create a separate cache for pages for logged in users and one for anonymous users. If certain pages absolutely must be dynamic, then they can be excluded from the cache. Another example: I like to keep a view tally on my articles in this blog. All my pages are cached, however, so to update the count my pages ping a small PHP script after loading, which then quickly connects to the database, sends an update query, then exits, all independently of the what matters the most: serving the page to the user.

Useful Browser Caching Headers
There are also several browser caching techniques that can be used when cached HTML:

  • Sending "HTTP 304 Not Modified" headers – Save a timestamp of when the cached page was last modified and send it with a "Last Modified" header. Then we can examine the user's "If-Modified-Since" request headers and send a 304 response to any pages that the browser has already saved in their cache. (header("HTTP/1.1 304 Not Modified"); A 304 response does not send any HTML.) This saves on bandwidth and allows the browser to display the page directly from memory without having to redownload and parse the HTML again.

  • Using "max-age" headers – The "max-age" response header informs browsers not to download the URL again for "max-age" seconds (header("Cache-Control: max-age=3600, must-revalidate");). We can this header to cache Ajax responses. This is useful for blocks of user-specific content. For example, if a user votes on a poll, we would want to display the poll results and not the poll vote form. But we don't want to retrieve the poll results on each page view, so we can tell the browser to just cache the block for 10 minutes. A cookie with a timestamp can be used for cache invalidation (e.g., if a user changes their vote, a "vote" cookie containing the unix timestamp is saved, then the Ajax request uses this cookie value when generating the request URL).

Comments

Great stuff! I have been

Great stuff! I have been using a .NET content cache called NWebCache for a while and it really does have a positive effect and ehances page load time. In-memory caching in a distributed environment is the need of the hour especially with the advent of cloud, grid and virtual computing.

Cheers.

ACL incompatability

unfortunately authCache has compatability issues with ACL which makes it a no go for me

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: [code], [blockcode], [bash], [css], [html], [ini], [javascript], [mysql], [php], [sql], [xml]. PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

More information about formatting options