The document discusses various techniques for optimizing a multisite WordPress architecture for a high-traffic music website including: using caching (APC, Memcached, Batcache), output buffering, CDNs, regionalized content/caching, shared content across sites, custom post types, web services, and configuration files. It also covers database scaling, PHP optimizations, and using Amazon services like EC2 and S3.
2. Scott Taylor
Lead PHP Developer, eMusic
@wonderboymusic
www.scotty-t.com
#projectmanagementsoftware
Saturday, June 9, 12
3. eMusic
Multisite
Regionalized Content
Regionalized Caching
Shared Content across Sites
Tons of Custom Post Types
Post Formats
Tons of Web Services
Saturday, June 9, 12
5. APC = duh
⢠Essential for PHP
⢠Opcode cache
⢠apc.shm_size = 64M or higher
Saturday, June 9, 12
6. Logs
⢠Never allow any PHP-related notices,
errors, warnings, exceptions, etc
⢠Check access logs regularly for 404s, 500s
etc
⢠make a constant for toggling debug logging
Saturday, June 9, 12
7. Production Data
⢠Always pull down, never push up
⢠We only push imported legacy content up
⢠output buffering
⢠code ďŹltering
⢠more to come on this....
Saturday, June 9, 12
11. Machine ConďŹg
⢠DB credentials local to that machine
⢠Amazon S3 bucket
⢠Web Service Endpoints
⢠Memcached Servers
if ( ďŹle_exists( â/wp-conďŹg/conďŹg.phpâ ) ) {
require_once( â/wp-conďŹg/conďŹg.phpâ );
} else {
die( âYou must have a local conďŹg!â );
}
Saturday, June 9, 12
12. Memcached
⢠Memcached PHP Extension (not Memcache)
⢠Can be used locally (127.0.0.1)
⢠Memcached Redux supports
wp_cache_get_/set_multi( )
⢠Johnny Cache
Saturday, June 9, 12
13. Batcache
⢠Full-page caching
⢠Can be conďŹgured
⢠You can partition cache by unique values
⢠Loads before plugins - any code you need
has to be duped or added early
(sunrise.php)
Saturday, June 9, 12
14. class batcache {
// This is the base configuration. You can edit these variables or move
them into your wp-config.php file.
var $max_age = 300;
// Expire batcache items aged this many seconds (zero to disable batcache)
var $remote = 0;
// Zero disables sending buffers to remote datacenters (req/sec is never sent)
var $times = 5;
// Only batcache a page after it is accessed this many times... (two or more)
var $seconds = 120;
// ...in this many seconds (zero to ignore this and use batcache immediately)
var $group = 'batcache';
// Name of memcached group. You can simulate a cache flush by changing this.
var $unique = array( BATCACHE_REGION, BATCACHE_COUNTRY );
// If you conditionally serve different content, put the variable values here.
var $headers = array(
'X-nananana' => 'Batcache'
);
. . . .
}
Saturday, June 9, 12
15. Sunrise
⢠Used to alter multisite context
⢠sets $current_blog and $current_site
⢠ďŹlters all URL functions to resolve all URLs
to your current domain
⢠registers custom locations for media
⢠ďŹlters Admin URLs
Saturday, June 9, 12
16. switch_to_blog( $blog_id )
⢠All dynamic functions need to account for
this
⢠Shared content needs to resolve proper
URLs
⢠Different sites have different media
locations
Saturday, June 9, 12
19. Plugins
⢠Filter active network plugins (donât rely on
database being correct)
⢠Filter each siteâs plugins (if you have a
manageable number)
⢠Use classes, not a bunch of functions
⢠Extend before you copy / paste
Saturday, June 9, 12
22. Site Plugins
add_filter( 'pre_option_active_plugins', function () {
return array(
'artist-images/artist-images.php',
'catalog-comments/catalog-comments.php',
'emusic-post-types/emusic-post-types.php',
'discography.php',
'emusic-radio/emusic-radio.php',
'gravityforms/gravityforms.php',
'super-ghetto/super-ghetto.php'
//,'theme-check/theme-check.php'
);
} );
Saturday, June 9, 12
23. class MyPlugin {
function init() {
add_action( âinitâ, array( $this, âregisterâ ) );
}
function register() {}
}
$my_plugin = new MyPlugin();
$my_plugin->init();
class MyPlugin {
function init() {
add_action( âinitâ, array( âMyPluginâ, âregisterâ ) );
}
function register() {}
}
MyPlugin::init();
Saturday, June 9, 12
24. Share code when possible
class FeaturePack extends eMusicPostTypes implements PostType {
..........
}
Saturday, June 9, 12
25. MySQL
⢠Use 5.5, better handlng of weird multibyte
strings
⢠Use InnoDB, not MyISAM, pretty much in
all cases
⢠HyperDB handles scaling for you
⢠Benchmark queries, donât be afraid to roll
your own SQL, tables, use $wpdb
Saturday, June 9, 12
26. HyperDB
⢠Can be empty locally
⢠Inherits wp-conďŹg DB defaults
⢠contains functions for replication lag
detection (when used with mk-heartbeat)
Saturday, June 9, 12
27. Themes
⢠Theme setup is a class
⢠Extend before you repeat
⢠Classes are better than preďŹxing function
names
Saturday, June 9, 12
31. Assets
⢠Use remote storage
⢠Use a CDN
⢠Replace hosts using output buffer
⢠Cloud - pieces of W3 Total Cache
Saturday, June 9, 12
32. Minify
⢠JS / CSS concatenation speed up your
front-end loading / perceived loading
⢠Minify is automagic
⢠HTML5 CSS properties are automatically
inďŹated
⢠Admin tool to cache-bust URLs
⢠Auto-locking while ďŹles are generated
Saturday, June 9, 12
33. Web Services
⢠cURL PHP extension
⢠curl and curl_multi()
⢠Memcached is essential
⢠hooks into parse_request to load data
along with WordPress, allows us to cause
WP to 404 and bail early when required
data response fails
⢠Parallelization with curl_multi()
Saturday, June 9, 12
34. class eMusicRequest {
var $request;
var $sub_request;
var $path;
var $page;
function load( $page = '' ) {
if ( !empty( $page ) )
$this->page = $page;
$ďŹle = $this->path . $this->page . '.php';
if ( ďŹle_exists( $ďŹle ) )
require_once( $ďŹle );
}
function parse() {
$requests = array( $this->request, $this->sub_request );
foreach ( $requests as $request ) {
if ( !empty( $request ) ) {
$keys = array_keys( get_class_vars( get_class( $request ) ) );
foreach ( $keys as $var ) {
if ( !empty( $request->$var ) ) {
$GLOBALS[$var] = $request->$var;
}
}
}
}
}
}
Saturday, June 9, 12
35. class DarkRequest extends eMusicRequest {
var $genre;
var $post_type;
function init( $request ) {
global $_GENRES;
$vars =& $request->query_vars;
.........
}
}
Saturday, June 9, 12
36. switch ( get_current_blog_id() ) {
case 1:
$_dark_request = new DarkRequest();
add_action( 'parse_request', array( $_dark_request, 'init' ) );
break;
case 4:
$_my_emusic_request = new MyEMusicRequest();
add_action( 'parse_request', array( $_my_emusic_request,
'init' ) );
break;
}
Saturday, June 9, 12
41. Because we used
classes, everything is
abstracted
Saturday, June 9, 12
42. Custom Authentication
⢠WP stores slashed passwords
⢠wp_authentication arguments are slashed
⢠Inherit your base systemâs rules
⢠DO NOT sanitize email / password
(WordPress does by default)
⢠User tables in this case are really a
transient cache
Saturday, June 9, 12