Rewrite and tidy up of performance section

This commit is contained in:
Will Rossiter 2014-10-17 21:16:50 +13:00 committed by Cam Findlay
parent 576f34a539
commit 627cc520d9
8 changed files with 179 additions and 143 deletions

View File

@ -1,64 +1,49 @@
title: Partial Caching
summary: Cache SilverStripe templates to reduce database queries.
# Partial Caching
## Introduction
Partial caching is a feature that allows the caching of just a portion of a page.
As opposed to static publishing, which avoids the SilverStripe controller layer on cached pages, partial caching allows
caching for pages that contain a mix of moderately static & user specific data, and still provide full access control
and permission enforcement.
The trade-off is that it does not provide as much performance improvement as static publishing, although for data heavy
pages the speed increases can be significant.
## Basics
The way you mark a section of the template as being cached is to wrap that section in a cached tag, like so:
Partial caching is a feature that allows the caching of just a portion of a page.
:::ss
<% cached %>
<% cached 'CacheKey' %>
$DataTable
...
<% end_cached %>
Each cache block has a cache key - an unlimited number of comma separated variables (in the same form as `if` and
`loop`/`with` tag variables) and quoted strings.
Every time the cache key returns a different result, the contents of the block are recalculated. If the cache key is the
same as a previous render, the cached value stored last time is used.
Each cache block has a cache key. A cache key is an unlimited number of comma separated variables and quoted strings.
Every time the cache key returns a different result, the contents of the block are recalculated. If the cache key is
the same as a previous render, the cached value stored last time is used.
Since the above example contains just one argument as the cache key, a string (which will be the same every render) it
will invalidate the cache after the TTL has expired (default 10 minutes)
will invalidate the cache after a given amount of time has expired (default 10 minutes).
Here are some more complex examples:
From a block that updates every time the Page subclass it's the template for updates
:::ss
<% cached 'database', LastEdited %>
From a block that shows a login block if not logged in, or a homepage link if logged in, depending on the current member
:::ss
<% cached 'database', LastEdited %>
<!-- that updates every time the record changes. -->
<% end_cached %>
<% cached 'loginblock', CurrentMember.ID %>
<!-- cached unique to the user. i.e for user 2, they will see a different cache to user 1 -->
<% end_cached %>
From a block that shows a summary of the page edits if administrator, nothing if not
:::ss
<% cached 'loginblock', LastEdited, CurrentMember.isAdmin %>
<!-- recached when block object changes, and if the user is admin -->
<% end_cached %>
An additional global key is incorporated in the cache lookup. The default value for this is
`$CurrentReadingMode, $CurrentUser.ID`, which ensures that the current `[api:Versioned]` state and user ID are
used. This may be configured by changing the config value of `SSViewer.global_key`. It is also necessary
to flush the template caching when modifying this config, as this key is cached within the template itself.
An additional global key is incorporated in the cache lookup. The default value for this is
`$CurrentReadingMode, $CurrentUser.ID`. This ensures that the current `[api:Versioned]` state and user ID are used.
This may be configured by changing the config value of `SSViewer.global_key`. It is also necessary to flush the
template caching when modifying this config, as this key is cached within the template itself.
For example, to ensure that the cache is configured to respect another variable, and if the current logged in
user does not influence your template content, you can update this key as below;
**mysite/_config/app.yml**
:::yaml
SSViewer:
global_key: '$CurrentReadingMode, $Locale'
@ -66,39 +51,45 @@ user does not influence your template content, you can update this key as below;
## Aggregates
Often you want to invalidate a cache when any in a set of objects change, or when the objects in a relationship change.
To help do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates
on sets of `[api:DataObject]`s - the most useful for us being the Max aggregate.
Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
change. To do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates
on sets of [api:DataObject]s - the most useful for us being the `Max` aggregate.
For example, if we have a menu, we want that menu to update whenever _any_ page is edited, but would like to cache it
otherwise. By using aggregates, we can do that like this:
otherwise. By using aggregates, we do that like this:
:::ss
<% cached 'navigation', List(SiteTree).max(LastEdited), List(SiteTree).count() %>
If we have a block that shows a list of categories, we can make sure the cache updates every time a category is added or
edited
The cache for this will update whenever a page is added, removed or edited.
If we have a block that shows a list of categories, we can make sure the cache updates every time a category is added
or edited
:::ss
<% cached 'categorylist', List(Category).max(LastEdited), List(Category).count() %>
Note the use of both .max(LastEdited) and .count() - this takes care of both the case where an object has been edited
since the cache was last built, and also when an object has been deleted/un-linked since the cache was last built.
<div class="notice" markdown="1">
Note the use of both `.max(LastEdited)` and `.count()` - this takes care of both the case where an object has been
edited since the cache was last built, and also when an object has been deleted since the cache was last built.
</div>
We can also calculate aggregates on relationships. A block that shows the current member's favourites needs to update
whenever the relationship Member::$has_many = array('Favourites' => Favourite') changes.
We can also calculate aggregates on relationships. A block that shows the current member's favorites needs to update
whenever the relationship `Member::$has_many = array('Favourites' => Favourite')` changes.
:::ss
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %>
## Cache key calculated in controller
That last example is a bit large, and is complicating our template up with icky logic. Better would be to extract that
logic into the controller
In the previous example the cache key is getting a bit large, and is complicating our template up. Better would be to
extract that logic into the controller.
:::php
public function FavouriteCacheKey() {
$member = Member::currentUser();
return implode('_', array(
'favourites',
$member->ID,
@ -106,8 +97,7 @@ logic into the controller
));
}
and then using that function in the cache key
Then using that function in the cache key:
:::ss
<% cached FavouriteCacheKey %>
@ -159,29 +149,28 @@ heavy load:
<% cached 'blogstatistics', Blog.ID if HighLoad %>
By adding a HighLoad function to your page controller, you could enable or disable caching dynamically.
By adding a `HighLoad` function to your `Page_Controller`, you could enable or disable caching dynamically.
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
you could use something like:
use something like:
:::ss
<% cached unless CurrentUser %>
## Uncached
As a shortcut, the template tag 'uncached' can be used - it is the exact equivilent of a cached block with an if
condition that always returns false. The key and conditionals in an uncached tag are ignored, so you can easily
temporarily disable a particular cache block by changing just the tag, leaving the key and conditional intact.
Yhe template tag 'uncached' can be used - it is the exact equivalent of a cached block with an if condition that always
returns false. The key and conditionals in an uncached tag are ignored, so you can easily temporarily disable a
particular cache block by changing just the tag, leaving the key and conditional intact.
:::ss
<% uncached %>
## Nested cacheblocks
## Nested cache blocks
You can also nest independent cache blocks (with one important rule, discussed later).
Any nested cache blocks are calculated independently from their containing block, regardless of the cached state of that
container.
You can also nest independent cache blocks Any nested cache blocks are calculated independently from their containing
block, regardless of the cached state of that container.
This allows you to wrap an entire page in a cache block on the page's LastEdited value, but still keep a member-specific
portion dynamic, without having to include any member info in the page's cache key.
@ -217,11 +206,10 @@ could also write the last example as:
$ASlowCalculation
<% end_cached %>
## The important rule
<div class="warning" markdown="1">
Currently cached blocks can not be contained within if or loop blocks. The template engine will throw an error
letting you know if you've done this. You can often get around this using aggregates.
</div>
Failing example:
@ -236,8 +224,6 @@ Failing example:
<% end_cached %>
Can be re-written as:
:::ss
@ -249,4 +235,4 @@ Can be re-written as:
<% end_loop %>
<% end_cached %>
<% end_cached %>
<% end_cached %>

View File

@ -1,25 +0,0 @@
# Cache control
By default, PHP add caching headers that make the page appear "purely dynamic".
This isn't usually appropriate for most sites, even ones that are updated reasonably frequently.
In particular, the default PHP cache-control settings prevent sites from appearing in the internet archive.
SilverStripe overrides the default settings with the following:
Default setting:
* The `Last-Modified` date is set to be most recent modification date of any database record queried in the generation of the page.
* The `Expiry` date is set by taking the age of the page and adding that to the current time.
* `Cache-Control` is set to `max-age=86400, must-revalidate`
* Since a visitor cookie is set, the site won't be cached by proxies
* Ajax requests are never cached.
Overriding these defaults
* `[api:HTTP::$cache_age]` can be used to set the max-age component of the cache-control line, in seconds.
Set it to 0 to disable caching; the "no-cache" clause in `Cache-Control` and `Pragma` will be included. It works only for live sites, if `SS_ENVIRONMENT_TYPE` is set to "dev" `[api:HTTP::$cache_age]` will be always overridden with 0.
* `[api:HTTP::register_modification_date()]` can be used to set the modification date to something more recent than the default.
How it works:
* `[api:DataObject::__construct()]` calls `[api:HTTP::register_modification_date()]` whenever a record comes from the database
* `Controller::run()` calls `[api:HTTP::add_cache_headers()]` before outputting the page

View File

@ -0,0 +1,33 @@
title: HTTP Cache Headers
summary: Set the correct HTTP cache headers for your responses.
# Caching Headers
By default, PHP adds caching headers that make the page appear purely dynamic. This isn't usually appropriate for most
sites, even ones that are updated reasonably frequently. SilverStripe overrides the default settings with the following
headers:
* The `Last-Modified` date is set to be most recent modification date of any database record queried in the generation
of the page.
* The `Expiry` date is set by taking the age of the page and adding that to the current time.
* `Cache-Control` is set to `max-age=86400, must-revalidate`
* Since a visitor cookie is set, the site won't be cached by proxies.
* Ajax requests are never cached.
## Customizing Cache Headers
### HTTP::set_cache_age
:::php
HTTP::set_cache_age(0);
Used to set the max-age component of the cache-control line, in seconds. Set it to 0 to disable caching; the "no-cache"
clause in `Cache-Control` and `Pragma` will be included.
### HTTP::register_modification_date
:::php
HTTP::register_modification_date('2014-10-10');
Used to set the modification date to something more recent than the default. [api:DataObject::__construct] calls
[api:HTTP::register_modification_date(] whenever a record comes from the database ensuring the newest date is present.

View File

@ -0,0 +1,13 @@
title: Profiling
summary: Identify bottlenecks within your application.
# Profiling
Profiling is the best way to identify bottle necks and other slow moving parts of your application prime for
optimization.
SilverStripe does not include any profiling tools out of the box, but we recommend the use of existing tools such as
[XHProf](https://github.com/facebook/xhprof/) and [XDebug](http://xdebug.org/).
* [Profiling with XHProf](http://techportal.inviqa.com/2009/12/01/profiling-with-xhprof/)
* [Profiling PHP Applications With xdebug](http://devzone.zend.com/1139/profiling-php-applications-with-xdebug/)

View File

@ -0,0 +1,20 @@
title: Static Publishing
summary: Export your web pages as static HTML and serve the web like it's 1999.
# Static Publishing
One of the best ways to get the top performance out of SilverStripe is to bypass it completely. This saves on any loading
time, connecting to the database and formatting your templates. This is only appropriate approach on web pages that
have completely static content.
<div class="info" markdown="1">
If you want to cache part of a page, or your site has interactive elements such as forms, then
[Partial Caching](partial_caching) is more suitable.
</div>
By publishing the page as HTML it's possible to run SilverStripe from behind a corporate firewall, on a low performance
server or serve millions of hits an hour without expensive hardware.
This functionality is available through the [StaticPublisher](https://github.com/silverstripe-labs/silverstripe-staticpublisher)
module. The module provides hooks for developers to generate static HTML files for the whole application or publish key
pages (e.g a web applications home page) as HTML to reduce load on the server.

View File

@ -1,8 +0,0 @@
#### Profiling
Profiling is the best way to identify bottle necks and other slow moving parts of your application prime for optimization. SilverStripe
does not include any profiling tools out of the box, but we recommend the use of existing tools such as [XHProf](https://github.com/facebook/xhprof/)
and [XDebug](http://xdebug.org/).
* [Profiling with XHProf](http://techportal.inviqa.com/2009/12/01/profiling-with-xhprof/)
* [Profiling PHP Applications With xdebug](http://devzone.zend.com/1139/profiling-php-applications-with-xdebug/)

View File

@ -1,8 +1,14 @@
title: Performance
summary: Make your applications faster by learning how to write more scalable code and ways to cache your important information.
introduction: Make your applications faster by learning how to write more scalable code and ways to cache your important information.
[CHILDREN]
The following guide describes the common ways to speed your SilverStripe website up. The general rules for getting
the best performance out of SilverStripe include running the latest versions of PHP alongside a
[opcode](http://en.wikipedia.org/wiki/Opcode) cache such as [XCache](http://xcache.lighttpd.net/) or
[APC](http://nz2.php.net/manual/en/intro.apc.php).
## How-to
If you're running shared hosting, make sure your host meets the minimum system requirements and has activated one of the
PHP opcode caches to achieve the best results for your application. Once your hardware is performing it's best, dig
into the guides below to see what you can do.
[CHILDREN How_To]
[CHILDREN Exclude=How_Tos]

View File

@ -1,13 +1,13 @@
title: Command Line Interface
summary: Automate SilverStripe, run Cron Jobs or sync with other platforms through the Command Line Interface.
introduction: Automate SilverStripe, run Cron Jobs or sync with other platforms through the Command Line Interface.
# Command Line
SilverStripe can call [Controllers](../controllers) through a command line interface (CLI) just as easily as through a
web browser. This functionality can be used to automate tasks with cron jobs, run unit tests, or anything else that
needs to interface over the command line.
SilverStripe can call [controllers](../controllers) through a command line interface (CLI) just as easily as through a
web browser. This can be used to automate tasks with cron jobs, run unit tests, or anything else that needs to interface
over the command line.
The main entry point for any command line execution is `cli-script.php`. For example, to run a database rebuild
from the command line, use this command:
The main entry point for any command line execution is `framework/cli-script.php`. For example, to run a database
rebuild from the command line, use this command:
:::bash
cd your-webroot/
@ -19,76 +19,86 @@ more). This can be a good thing, your CLI can be configured to use higher memory
to have.
</div>
## Sake: SilverStripe Make
## Sake - SilverStripe Make
Sake is a simple wrapper around `cli-script.php`. It also tries to detect which `php` executable to use if more than one
are available.
<div class="info" markdown='1'>
If you are using a debian server: Check you have the php-cli package installed for sake to work. If you get an error
If you are using a Debian server: Check you have the php-cli package installed for sake to work. If you get an error
when running the command php -v, then you may not have php-cli installed so sake won't work.
</div>
### Installation
You can copy the `sake` file into `/usr/bin/sake` for easier access (this is optional):
`sake` can be invoked using `./framework/sake`. For easier access, copy the `sake` file into `/usr/bin/sake`.
cd your-webroot/
sudo ./framework/sake installsake
<div class="warning">
This currently only works on UNIX-like systems, not on Windows.
This currently only works on UNIX like systems, not on Windows.
</div>
### Configuration
Sometimes SilverStripe needs to know the URL of your site, for example, when sending an email or generating static
files. When you're visiting your site in a web browser this is easy to work out, but if you're executing scripts on the
command line, it has no way of knowing.
To work this out, you should add lines of this form to your [_ss_environment.php](/getting_started/environment_management)
file.
Sometimes SilverStripe needs to know the URL of your site. For example, when sending an email or generating static
files. When you're visiting the site in a web browser this is easy to work out, but when executing scripts on the
command line, it has no way of knowing. To work this out, add lines to your
[_ss_environment.php](/getting_started/environment_management) file.
:::php
global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
The above statement tells SilverStripe that anything executed under the `/Users/sminnee/Sites` directly will have the
base URL `http://localhost`.
The above statement tells SilverStripe that anything executed under the `/Users/sminnee/Sites` directory will have the
base URL `http://localhost`. The site `/Users/sminnee/Sites/my_silverstripe_project` will translate to the URL
`http://localhost/my_silverstripe_project`.
You can add multiple file to url mapping definitions. The most specific mapping will be used. For example:
You can add multiple file to url mapping definitions. The most specific mapping will be used.
:::php
global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites/mysite'] = 'http://mysite.localhost';
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites/my_silverstripe_project'] = 'http://project.localhost';
### Usage
Sake is particularly useful for running build tasks
Sake can run any controller by passing the relative URL to that controller.
:::bash
sake /
# returns the homepage
sake dev/
# shows a list of development operations
Sake is particularly useful for running build tasks.
:::bash
cd /your/site/folder
sake dev/build "flush=1"
Or running unit tests..
:::bash
sake dev/tests/all
It can also be handy if you have a long running script.
It can also be handy if you have a long running script..
:::bash
cd /your/site/folder
sake dev/tasks/MyReallyLongTask
### Running processes
You can use sake to make daemon processes for your application.
`sake` can be used to make daemon processes for your application.
Step 1: Make a task or controller class that runs a loop. To avoid memory leaks, you should make the PHP process exit
when it hits some reasonable memory limit. Sake will automatically restart your process whenever it exits.
Make a task or controller class that runs a loop. To avoid memory leaks, you should make the PHP process exit when it
hits some reasonable memory limit. Sake will automatically restart your process whenever it exits.
Step 2: Include some appropriate sleep()s so that your process doesn't hog the system. The best thing to do is to have
a short sleep when the process is in the middle of doing things, and a long sleep when doesn't have anything to do.
Include some appropriate sleep()s so that your process doesn't hog the system. The best thing to do is to have a short
sleep when the process is in the middle of doing things, and a long sleep when doesn't have anything to do.
This code provides a good template:
@ -115,22 +125,21 @@ This code provides a good template:
}
}
Step 3: Install the "daemon" command-line tool on your server.
Step 4: Use sake to start and stop your process
Then the process can be managed through `sake`
:::bash
sake -start MyProcess
sake -stop MyProcess
<div class="notice">
Sake stores Pid and log files in the site root directory.
`sake` stores `pid` and log files in the site root directory.
</div>
## GET parameters as arguments
## Arguments
You can add parameters to the command by using normal form encoding. All parameters will be available in `$_GET` within
SilverStripe. Using the `cli-script.php` directly:
Parameters can be added to the command. All parameters will be available in `$_GET` array on the server.
:::bash
cd your-webroot/
@ -143,8 +152,10 @@ Or if you're using `sake`
## Running Regular Tasks With Cron
On a UNIX machine, you can typically run a scheduled task with a [cron job](http://en.wikipedia.org/wiki/Cron). You can
execute any `BuildTask` in SilverStripe as a cron job using `Sake`.
On a UNIX machine, you can typically run a scheduled task with a [cron job](http://en.wikipedia.org/wiki/Cron). Run
`BuildTask` in SilverStripe as a cron job using `sake`.
The following will run `MyTask` every minute.
:::bash
* * * * * /your/site/folder/sake dev/tasks/MyTask