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 549531798f
commit f4dad25af0
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 # Partial Caching
## Introduction Partial caching is a feature that allows the caching of just a portion of a page.
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:
:::ss :::ss
<% cached %> <% cached 'CacheKey' %>
$DataTable $DataTable
... ...
<% end_cached %> <% end_cached %>
Each cache block has a cache key - an unlimited number of comma separated variables (in the same form as `if` and Each cache block has a cache key. A cache key is an unlimited number of comma separated variables and quoted strings.
`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.
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 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: 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 :::ss
<% cached 'database', LastEdited %>
<!-- that updates every time the record changes. -->
<% end_cached %>
<% cached 'loginblock', CurrentMember.ID %> <% 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 %> <% 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
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.
`$CurrentReadingMode, $CurrentUser.ID`, which ensures that the current `[api:Versioned]` state and user ID are This may be configured by changing the config value of `SSViewer.global_key`. It is also necessary to flush the
used. This may be configured by changing the config value of `SSViewer.global_key`. It is also necessary template caching when modifying this config, as this key is cached within the template itself.
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 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; user does not influence your template content, you can update this key as below;
**mysite/_config/app.yml**
:::yaml :::yaml
SSViewer: SSViewer:
global_key: '$CurrentReadingMode, $Locale' global_key: '$CurrentReadingMode, $Locale'
@ -66,39 +51,45 @@ user does not influence your template content, you can update this key as below;
## Aggregates ## Aggregates
Often you want to invalidate a cache when any in a set of objects change, or when the objects in a relationship change. Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
To help do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates 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. 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 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 :::ss
<% cached 'navigation', List(SiteTree).max(LastEdited), List(SiteTree).count() %> <% 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 The cache for this will update whenever a page is added, removed or edited.
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 :::ss
<% cached 'categorylist', List(Category).max(LastEdited), List(Category).count() %> <% 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 <div class="notice" markdown="1">
since the cache was last built, and also when an object has been deleted/un-linked since the cache was last built. 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 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. whenever the relationship `Member::$has_many = array('Favourites' => Favourite')` changes.
:::ss :::ss
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %> <% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %>
## Cache key calculated in controller ## 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 In the previous example the cache key is getting a bit large, and is complicating our template up. Better would be to
logic into the controller extract that logic into the controller.
:::php :::php
public function FavouriteCacheKey() { public function FavouriteCacheKey() {
$member = Member::currentUser(); $member = Member::currentUser();
return implode('_', array( return implode('_', array(
'favourites', 'favourites',
$member->ID, $member->ID,
@ -106,8 +97,7 @@ logic into the controller
)); ));
} }
Then using that function in the cache key:
and then using that function in the cache key
:::ss :::ss
<% cached FavouriteCacheKey %> <% cached FavouriteCacheKey %>
@ -159,29 +149,28 @@ heavy load:
<% cached 'blogstatistics', Blog.ID if HighLoad %> <% 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, 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 :::ss
<% cached unless CurrentUser %> <% 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 Yhe template tag 'uncached' can be used - it is the exact equivalent of a cached block with an if condition that always
condition that always returns false. The key and conditionals in an uncached tag are ignored, so you can easily returns false. The key and conditionals in an uncached tag are ignored, so you can easily temporarily disable a
temporarily disable a particular cache block by changing just the tag, leaving the key and conditional intact. particular cache block by changing just the tag, leaving the key and conditional intact.
:::ss :::ss
<% uncached %> <% uncached %>
## Nested cacheblocks ## Nested cache blocks
You can also nest independent cache blocks (with one important rule, discussed later). 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.
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 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. 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 $ASlowCalculation
<% end_cached %> <% end_cached %>
<div class="warning" markdown="1">
## The important rule
Currently cached blocks can not be contained within if or loop blocks. The template engine will throw an error 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. letting you know if you've done this. You can often get around this using aggregates.
</div>
Failing example: Failing example:
@ -236,8 +224,6 @@ Failing example:
<% end_cached %> <% end_cached %>
Can be re-written as: Can be re-written as:
:::ss :::ss
@ -249,4 +235,4 @@ Can be re-written as:
<% end_loop %> <% end_loop %>
<% end_cached %> <% 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 title: Performance
summary: Make your applications faster by learning how to write more scalable code and ways to cache your important information. 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. 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 The main entry point for any command line execution is `framework/cli-script.php`. For example, to run a database
web browser. This can be used to automate tasks with cron jobs, run unit tests, or anything else that needs to interface rebuild from the command line, use this command:
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:
:::bash :::bash
cd your-webroot/ 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. to have.
</div> </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 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. are available.
<div class="info" markdown='1'> <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. when running the command php -v, then you may not have php-cli installed so sake won't work.
</div> </div>
### Installation ### 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/ cd your-webroot/
sudo ./framework/sake installsake sudo ./framework/sake installsake
<div class="warning"> <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> </div>
### Configuration ### Configuration
Sometimes SilverStripe needs to know the URL of your site, for example, when sending an email or generating static 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 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. command line, it has no way of knowing. To work this out, add lines to your
[_ss_environment.php](/getting_started/environment_management) file.
To work this out, you should add lines of this form to your [_ss_environment.php](/getting_started/environment_management)
file.
:::php :::php
global $_FILE_TO_URL_MAPPING; global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost'; $_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 The above statement tells SilverStripe that anything executed under the `/Users/sminnee/Sites` directory will have the
base URL `http://localhost`. 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 :::php
global $_FILE_TO_URL_MAPPING; global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost'; $_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 ### 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 :::bash
cd /your/site/folder
sake dev/build "flush=1" sake dev/build "flush=1"
Or running unit tests..
:::bash
sake dev/tests/all 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 :::bash
cd /your/site/folder
sake dev/tasks/MyReallyLongTask sake dev/tasks/MyReallyLongTask
### Running processes ### 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 Make a task or controller class that runs a loop. To avoid memory leaks, you should make the PHP process exit when it
when it hits some reasonable memory limit. Sake will automatically restart your process whenever it exits. 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 Include some appropriate sleep()s so that your process doesn't hog the system. The best thing to do is to have a short
a short sleep when the process is in the middle of doing things, and a long sleep when doesn't have anything to do. 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: 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. Then the process can be managed through `sake`
Step 4: Use sake to start and stop your process
:::bash :::bash
sake -start MyProcess sake -start MyProcess
sake -stop MyProcess sake -stop MyProcess
<div class="notice"> <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> </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 Parameters can be added to the command. All parameters will be available in `$_GET` array on the server.
SilverStripe. Using the `cli-script.php` directly:
:::bash :::bash
cd your-webroot/ cd your-webroot/
@ -143,8 +152,10 @@ Or if you're using `sake`
## Running Regular Tasks With Cron ## 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 On a UNIX machine, you can typically run a scheduled task with a [cron job](http://en.wikipedia.org/wiki/Cron). Run
execute any `BuildTask` in SilverStripe as a cron job using `Sake`. `BuildTask` in SilverStripe as a cron job using `sake`.
The following will run `MyTask` every minute.
:::bash :::bash
* * * * * /your/site/folder/sake dev/tasks/MyTask * * * * * /your/site/folder/sake dev/tasks/MyTask