Externí Object Cache ve WordPressu

V případě, že bychom ze své instalace WordPressu na serveru, kde máme k dispozici trochu víc, než jen Apache s PHP a MySQL, chtěli vymáčknou trochu víc, je možné dosadit vlastní backend pro Object Cache a změnit její podstatu z Run-Time Cache na persistentní.

Pojďme se společně podívat na to, jak je možnost vlastního backendu pro Object Cache řešena v samotném WordPressu a jak si, trochu si zapřeháním, třeba napsat vlastní backend.

K čemu je dobrá persistentní cache

V předchozím článku jsem se celkem podrobně rozepsal o tom, jaké výhody přináší jednoduchá Run-Time Cache při zpracování načítání jedné stránky. Persistentní backend pro Object Cache posouvá tyto výhody ještě na vyšší úroveň.

Tím, že máme možnost cachované hodnoty uchovat i poté, co je stránka již vygenerována, nám tyto hodnoty zpřístupní bez dotazu do databáze, nebo bez provedení složité transformace hodnot z databáze, prakticky okamžitě i příště, při načtení dané stránky znovu, nebo na jiných stránkách, kde je opět třeba. A to je právě důvod, proč  implementovat persistentní Object Cache. Jde nám o to, mít již jednou složitě získané hodnoty ihned k dalšímu použití napříč jednotlivými dotazy a v některých případech i napříč stránkami (třeba v případě widgetů, záhlaví, menu, zápatí a tak dále).

Drop-in soubor object-cache.php

Žádný soubor object-cache.php v adresáři wp-content na svém serveru, pokud jste již neinstalovali některý z Object Cache pluginů, nenaleznete. Jedná se totiž o tzv. drop-in soubor.

Drop-in jsou soubory, s jejichž existenci WordPress předpokládá, ovšem nevyžaduje ji. V případě, že některý z předpokládáných drop-in souborů na serveru WordPress při svém nahrávání nalezne, zahrne je do množství souborů, které v rámci jednotlivých requestů nahrává pomocí volání include či require.

Drop-in souborů existuje několik a v budoucích článcích plánuji zmínku snad o všech. Drop-in soubory jsou totiž způsob, jak výkon WordPressu posunout o něco dál (ovšem ne jediný možný, jak se pokusím v budoucnu ukázat také).

Podmíněné nahrání souboru object-cache.php a fallback

Na toto téma jsem narazil již ve svém prvním článku o Object Cache. O podmíněné nahrání se stará funkce wp_start_object_cache, která je volána ze souboru wp_settings.php a to až poté, co možná došlo k include dalšího drop-in souboru advance-cache.php. Tato nyní možná nedůležitá informace hraje roli chápání funkce samotné, která vypadá následovně:

function wp_start_object_cache() {
	global $blog_id;
	$first_init = false;
 	if ( ! function_exists( 'wp_cache_init' ) ) {
		if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
			require_once ( WP_CONTENT_DIR . '/object-cache.php' );
			if ( function_exists( 'wp_cache_init' ) )
				wp_using_ext_object_cache( true );
		}
		$first_init = true;
	} else if ( ! wp_using_ext_object_cache() && file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
		/*
		 * Sometimes advanced-cache.php can load object-cache.php before
		 * it is loaded here. This breaks the function_exists check above
		 * and can result in `$_wp_using_ext_object_cache` being set
		 * incorrectly. Double check if an external cache exists.
		 */
		wp_using_ext_object_cache( true );
	}
	if ( ! wp_using_ext_object_cache() )
		require_once ( ABSPATH . WPINC . '/cache.php' );
	/*
	 * If cache supports reset, reset instead of init if already
	 * initialized. Reset signals to the cache that global IDs
	 * have changed and it may need to update keys and cleanup caches.
	 */
	if ( ! $first_init && function_exists( 'wp_cache_switch_to_blog' ) )
		wp_cache_switch_to_blog( $blog_id );
	elseif ( function_exists( 'wp_cache_init' ) )
		wp_cache_init();
	if ( function_exists( 'wp_cache_add_global_groups' ) ) {
		wp_cache_add_global_groups( array( 'users', 'userlogins', 'usermeta', 'user_meta', 'site-transient', 'site-options', 'site-lookup', 'blog-lookup', 'blog-details', 'rss', 'global-posts', 'blog-id-cache' ) );
		wp_cache_add_non_persistent_groups( array( 'comment', 'counts', 'plugins' ) );
	}
}

Na rozdíl od jednoduchého vysvětlení v předchozím článku se na funkci dnes podíváme trochu podrobněji a vysvětlíme si jednotlivé podmínky – jsou totiž důležité pro pochopení alternativních backendů Object Cache, potažmo pro psaní vlastního.

První podmínka slouží ke standardnímu nahrání drop-in souboru object-cache.php. A to proto, že funkce wp_cache_init je ve WordPressu obsažena pouze v souboru wp-includes/cache.php a jinak její definici musí obsahovat drop-in soubor object-cache.php. Neexistence této funkce je tedy standardní situace za které se podíváme, jestli na serveru náhodou neexistuje soubor object-cache.php. Pokud existuje, includujeme jej a řekneme WordPressu, že budeme používat externí Object Cache – volání funkce wp_using_ext_object_cache.

O funkci wp_using_ext_object_cache píši v samostatném článku.

Existuje ale také možná výjimka z této situace. A sice to, že vlastní backend pro Object Cache v souboru object-cache.php includuje již další z drop-in souborů. Konkrétně pak advance-cache.php třeba proto, že využívá stejného Memcache serveru jako Object Cache a využívá některé funkce v něm definované třídy WP_Object_Cache.

A konečně, pokud soubor object-cache.php není k nalezení, vloží WordPress svůj defaultní fallback, o kterém jsem se detailně rozepisoval v předchozím článku a je obsažen v souboru wp-includes/cache.php.

Nyní se dostáváme k další podmínce, který využívá hodnotu proměnné $first_init. Tato proměnná je spíše pomocná a má pomoci rozlišit právě předchozí situace. Tedy jedná se o první inicializaci objektu třídy WP_Object _Cache. A sice v tom smyslu, jestli je soubor object-cache.php (či wp-includes/cache.php) poprvé includován v rámci této funkce, nebo někdy dříve (v drop-in souboru advance-cache.php).

Soubor advance-cache.php je nahráván relativně velmi bry a proto v něm není možné spoléhat se na standardní funkce vracející aktuální blog_id, user_id a podobně. Proto by soubor object-cache.php měl obsahovat funkci wp_cache_switch_to_blog, která správně nastaví prefix klíčů v závislosti na tom, na jakém blogu z multisite instalace se nacházíme.

V případě standardního postupu, kdy je object-cache.php includován až v rámci této funkce, musí dojít k první inicializaci objektu $wp_object_cache voláním funkce wp_cache_init, která nedělá nic jiného, že vytvoří v rámci globálních proměnných nový objekt třídy WP_Object_Cache.

A nakonec, pokud soubor object-cache.php či wp-includes/cache.php obsahuje funkce wp_cache_add_global_groups a wp_cache_add_non_persistent_groups, jsou tyto zavolány.

Všimněte si, že jedna funkce bez druhé nemůže existovat. Pokud by byla definována pouze funkce wp_cache_add_non_persistent_groups, nebyla by splněna podmínka uvozující tento blok kódu. A naopak, pokud by nebyla definována funkce wp_cache_add_non_persistent_groups, ale funkce wp_chace_add_global_groups ano, došlo by k fatální chybě volání neexistující funkce.

Jaké všechny funkce musí soubor object-cache.php obsahovat

Pouhým rozborem podmíněného nahrávání souboru object-cache.php v předchozí kapitole jsme tedy zjistili, že takový soubor bude obsahovat minimálně 4 funkce:

  • wp_cache_ini
  • wp_cache_switch_to_blog
  • wp_cache_add_global_groups
  • wp_cache_add_non_persistent_groups

To ovšem není všechno. Je zde jeden důležitý fakt. A sice to, že pokud soubor object-cache.php existuje, není nahrán soubor wp-includes/cache.php a ten obsahuje víc, než jen tyto 4 funkce. A všechny funkce v něm obsažené je nutné nahradit, jelikož jsou volány na mnoha dalších místech jádra WordPressu (a to bez kontroly toho, že vůbec existují!).

Podívejme se na to, o jaké funkce se jedná a co je jejich smyslem – tedy co WordPress předpokládá, že budou dělat a v jakých případech je volá.

wp_cache_add( $key, $data, $group = , $expire = 0 )

Tato funkce kontroluje, zda-li je hodnota již zacachovaná a pokud ne, přidá ji.

wp_cache_close()

Funkce, která vyčistí cache poté, co ji WordPress již nepotřebuje. Ve fallbacku implemntovaném v jádře nedělá vůbec nic – jedná se totiž o Run Time Cache a ta nepřežije déle, než jeden page request.

wp_cache_decr( $key, $offset = 1, $group = )

Sníží číselnou hodnotu proměnné uložené v cache o udanou hodnotu. Pozor by se měl dát na záporné hodnoty, pokud nejsou žádnoucí.

wp_cache_delete()

Odstraní uložený klíč a hodnotu v rámci skupiny.

wp_cache_delete$key, $group = ‘ )

Odstraní všechny hodnoty uložené v cache.

wp_cache_get( $key, $group = , $force = false, &$found = null )

Získá hodnotu daného klíče v rámci zadné skupiny. Pokud se jedná o implementaci persistentní cache, která používá také run-time cache, pak parametr $force určuje, zda-li se má hodnota získat z persistentní cache i v případě, že již existuje v rámci Run-Time cache.

Parametr found určuje, co že se má vrátit v případě, že hodnota v cache nebyla nalezena, protože například hodnota false je něco, co můžeme chtít v některých případech cachovat a jde tedy o správnou hodnotu a v jiných naopak nikoli.

wp_cache_incr( $key, $offset = 1, $group = )

Zvýší numerickou hodnotu proměnné uložené v cache o udaný offset.

wp_cache_init()

Inicializuje celou cache – je volána jako první funkce v rámci celé cache jádrem WordPressu.

wp_cache_replace( $key, $data, $group = , $expire = 0 )

Přepíše hodnotu daného klíče novou hodnotu. Typicky by tato funkce měla nejdříve kontrolovat existenci klíče v rámci skupiny a přepsat jej jen v případě, že existuje.

wp_cache_set( $key, $data, $group = , $expire = 0 )

Uloží hodnotu do cache pod daným klíčem bez kontroly toho, zda-li již existuje.

wp_cache_switch_to_blog( $blog_id )

Slouží k případnému nastavení prefixu pro daný blog v rámci multisite. Pokud cache mezi jednotlivými blogy rozlišuje, což by ovšem měla, chce-li podporovat také multisite instalace, které jsou již nějakou dobu součástí jádra WordPressu.

wp_cache_add_global_groups( $groups )

Globální skupiny jsou skupiny, které nejsou závislé na konkrétním blogu v rámci multisite instalace (typicky třeba data uživatelů).

wp_cache_add_non_persistent_groups( $groups )

Některé skupiny není dobré permanentě cachovat a chceme je ponechat jen v rámci run-time cache.

Parametr expire

Paramert expire určuje jak dlouho by hodnota pod daným klíčem měla zůstat zacachovaná. Ovšem zde velmi záleží na tom, jaký backend používáme. V rámci Run-Time Cache například postrádá veškerý smysl.

V rámci persistentní cache je nutné vědět, jakým způsobem s tímto parametrem cache zachází. Většinou se bude jedna o garantovanou maximální životnost hodnoty. To znamená, že se nelze spolehnout na to, že hodnota v cache vydrží dalších 10 minut, protože může zmizet dřív (a to i bez toho, aby někdo provedl smazání celé cache). Na druhou stranu se ale můžeme spolehnout na to, že po uplynutí 10 minut se hodnota v cache již nacházet nebude.

Ve světě Cache obecně platí, že spoléhat na to, že hodnota v cache vydrží celou stanovenou dobu životnosti, je nesmysl a musíme vždy počítat s tím, že jednou zacachovanou hodnotu může být nutné. Proto onen parametr $found u funkce wp_cache_get.

Skupiny

Skupiny slouží k možnost znovu použít stejný klíč v jiném kontextu. To nám dovoluje používat kratší klíče a šetřit prostředky v rámci cache pro hodnoty samotné. Rozdělení hodnot do skupin také může, záleží na implementovaném backendu, zrychlit hledání dané hodnoty dle klíče.

Jak taková implementace skupin v rámci jaderné implementace vypadá jsem opět zmiňoval v předchozím článku, ale neuškodí ukázku zopakovat také zde:

<?php
...
$wp_object_cache->cache = array(
  'post_meta' => array(
    1 => array(
      '_thumbnail_id' => 31,
      ...
    ),
    ...
  ),
  'posts' => array(
    1 => WP_Post_Object(...),
    ...
  ),
  ...
);
...

Hotové backendy: Memcache a Redis

Není hned třeba psát vlastní backend pro Object Cache. Také na tomto poli je z čeho si vybírat už v samotném Plugin Directory. Namátkou zmiňme pluginy pro Memcache či Redis. Instalace je poměrně jednoduchá a až mezi soubory zmíněných pluginů narazíte na soubor object-cache.php, již budete tušit, proč je nutné jej nakopírovat do adresáře wp-content.

Třída WP_Object_Cache

Určitě jste si všimli, že uvnitř souboru wp-includes/cache.php je definována třída WP_Object_Cache. Co jednomu může přijít divné je, že neexistuje žádné interface pro tuto třídu, které by umožňilo snadnou implementaci v rámci vlastního souboru object-cache.php. To proto, že vlastní backend vůbec žádnou třídu WP_Object_Cache obsahovat nemusí.

A zde vyvstává spíše filozofická otázka. Nebylo by lepší definovat v rámci jádra WordPressu interface pro třídu WP_Object_Cache a všechny výše zmíněné funkce odkazující na tuto třídu a nechat na implementátorech vlastního backendu pouze implementaci třídy samé? Existují situace, kdy by tento přístup znemožnil implementci určité technologie? Jsem zvědavý i na Vaše názory!

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s