mysqlnd query cache plugin: user-defined storage handler
TRANSCRIPT
MySQL native driver for PHP:Customizing the query cache
mysqlnd_qc:
Customization by user defined storage handler
Ulf Wendel, Andrey HristovMySQL Connectors TeamSun Microsystems
Table of Contents
OverviewHow to customize: template pattern
Flavours of user handler
Handler APIStorage
Registration
ExamplesQuick start: identify cache candidates
Master class: slam defense
Breaking the limits
Cache decisionShall this query be cached: is_select()
Filter by content, run or store time, result set size
Cache storageStorage location
Scope
Replacement strategyLimiting cache storage size
Extending monitoring capabilities
Template Method pattern, kind of
Core: invariant part of the caching, handler: variant part
ext/*mysql*ext/*mysql*mysqlndmysqlnd
Query Cache Plugin (mysqlnd_qc core)Storage handler (mysqlnd_qc)
Variant and invariant parts (Miss/Put)
mysqlndCache Plugin handlerCache Plugin corequery()
Should cache?
Is cached?
Activate data recorder
Call original query()
Deactivate recorder
Cache wire data
is_select()Want to cache statement?
find()Available? TTL? Slam defense? Statistics?
add()Run time? Data size?Replacement strategy?
Redefine everything or selected parts
Choose: quick change or complex algorithm?
Full control through new user handlerProcedural: mysqlnd_qc_set_user_handlers()
OOP: interface mysqlnd_qc_handler
Redefining selected invariantsOOP: extend class mysqlnd_qc_handler_default
No other build-in handler exported as user class
All approaches use almost the same handler API
Table of Contents
OverviewHow to customize: template pattern
Flavours of user handler
Handler APIStorage
Registration
ExamplesQuick start: identify cache candidates
Master class: slam defense
Handler API overview
Almost the same for all kinds of user handlerget_hash_key($host, $port, $user, $db, $query)*
find_in_cache($key)
return_to_cache($key)
add_to_cache($key, $data, $ttl, $run_t, ...)
is_select($query)
update_stats($key, $run_t, $store_t)
get_stats()
clear_cache()
init()*, shutdown()*
Handler API Cache Put (I)
ApplicationCache Plugin handlerCache Plugin core*query()
Should cache?Yes!
Cache entry 'key'?
is_select(...) a) Don't cache - return: falseb) Cache it return: int TTL
get_hash_key(...) Return key of cache entry.
find_in_cache(...)Returns NULL because not found.
Do we have 'key'?
Handler API Cache Put (II)
ApplicationCache Plugin handlerCache Plugin core*query()
add_to_cache(...)Add to cache if not exists.Return true if added, falseif already in cache.
Activate data recorder
Call original query()
Deactivate recorder
Cache wire data
*store _result()(often implicit)
Handler API Cache Hit (I)
ApplicationCache Plugin handlerCache Plugin core*query()
Should cache?Yes!
Cache entry 'key'?
is_select(...) a) Don't cache - return: falseb) Cache it return: int TTL
get_hash_key(...) Return key of cache entry.
find_in_cache(...)Search cache entry, check ifstill valid, return cache entry.
Do we have 'key'?
Handler API Cache Hit (II)
ApplicationCache Plugin handlerCache Plugin core*store _result()(often implicit)
Client served!
Record timings!
return_to_cache(...)Cache entry no longer in useby core. (Default needs this)
update_stats(...)Run and store time recordedand reported by the core,useful for per-entry stats.
Handler API Cache Miss (I)
ApplicationCache Plugin handlerCache Plugin core*query()
Should cache?No!
is_select(...) a) Don't cache return: falseb) Cache - see Cache Put (I)
API details - get_hash_key(...)
get_hash_key($host, $port, $user, $db, $query)string $host MySQL Server host info
string $port MySQL Server port
string $user MySQL user
string $db MySQL data base
string $query SQL statement
Returns a string that serves as a 'key' for a cache entry
identified by the given connection
parameters and query string.
API details get_hash_key(...)* pitfall
mysqlnd_qc_handler_default::get_hash_key($host, $port, $user, $db, $query, $persistent)string $host MySQL Server host info
string $port MySQL Server port
string $user MySQL user
string $db MySQL data base
string $query SQL statement
bool $persistent Flag persistent connection
You must not change $persistent when calling build-in method! PHP will crash if you do.
API details - find_in_cache(...)
find_in_cache($key)string $key Key of the requested cache entry
Returns the cache entry associated with the 'key' or NULL.
This is an ideal place to check not only if a cache entry is available but also to implement tife-to-life (TTL) checks or slam defense logic.
For example, if you can find the cache entry but it
has expired, you should return NULL to trigger a
cache miss.
API details - find_in_cache(...)
The build-in slam defense logic of the default and APC handler is implemented through find_in_cache() and add_to_cache(). If a cache entry can be found but is expired the cache entry is not returned to the core but not removed from cache either. The caller will experience a cache miss and attempt to update the cache entry through add_to_cache() later on. Meanwhile, if another client tries to access the cache entry, the stale cache entry will be served to the other client (slam hit). At some point the original caller will cause add_to_cache() to be called and the cache entry can be refreshed.
API details - return_to_cache(...)
return_to_cache($key)string $key Key of the cache entry
Message from the core to the storage handler that the core no longer uses the cache entry returned by find() because a cache hit has been completed.
Hardly any pratical meaning to userland handler.
Relevant for C based handler that work with references, such as the default handler does. See also mysqlnd_qc.std_data_copy for default handler configuration details.
API details - add_to_cache(...)
add_to_cache($key, $data, $ttl, $run_t, $store_t, $row_c)string $key Key of the cache entry
string $data Binary wire data to cache
int $ttl TTL of the cache entry (s)
int $run_t Run time of uncached query (ms)
int $store_t Store time of uncached query (ms)
Message from the core to the handler to create a new cache entry with the given data. Returns true, if the cache entry has been created and false, if the cache entry already exists.
API details - add_to_cache(...)
TTL is forwarded from is_select() call
Timings can be used to build per-entry performance figures such as run time comparisons of the cached and uncached query
If the user storage handler makes use of a cache medium that
persists over multiple web requests it can happen that two web
requests add the same key to the cache almost simultanously one
will be faster. To get the core statistics for cache hits and cache
misses right, you can return true or false.
See also statistics presentation!
API details - is_select(...)
is_select($query)string $query SQL statement
Returns false if the given query shall not be cached. Returns 0 if it shall be cached and TTL shall be equal to mysqlnd_qc.ttl. Returns an integer representing a TTL, if the query shall be cached but a custom TTL is to be used.
Note that you may have to parse the SQL to catch SQL hints that specify the TTL
Note the core logic TTL(0) != endless
API details - is_select(...)
For build-in storage handler the TTL is always measured in seconds. TTL interpretation is subject to storage handler, e.g. in find() to check if a cache entry is still valid. A user handler may interpret the TTL value, for example, as milliseconds. This is perfectly valid as long as you are aware of the potential differences between your own handler and the build-in storage handler and you stay within the limits of integers and you respect the special meaning of the value 0. The core will transparently forward your TTL setting to add().
API details - update_stats(...)
update_stats($key, $run_t, $store_t)string $key Key of the cache entry
double $run_t Run time of the cached query
double $store_t Store time of the cached query
Run and store time recorded by the core.
Can be used to maintain per-entry cache statistics.
API details - clear_cache()
clear_cache()
Flush all cache entries. Called by the core if the user calls mysqlnd_qc_clear_cache().
API details - get_stats()
get_stats()
Returns an array of cache statistics and arbitrary other data which will become part of return value of mysqlnd_qc_get_cache_info().
mysqlnd_qc_get_cache_info() returns a hash. The return value of get_stats() will be added to the hash using the key data. It is recommended to align the return value of get_stats() with the data hash provided by the build-in handlers, in particular Default and APC.
Procedural user storage handler
void mysqlnd_qc_set_user_handlers(
string get_hash_key,
string find_query_in_cache,
string return_to_cache,
string add_query_to_cache_if_not_exists,
string query_is_select,
string update_cache_stats,
string get_stats,
string clear_cache
)
There is also an OO API to please you see below.The OO API has
additional function callbacks!The OO API is likely to become the
future standard.
mysqlnd_qc_set_user_handlers()
Registering OO user storage handler
bool mysqlnd_qc_change_handler (mysqlnd_qc_default_handler handler)bool mysqlnd_qc_change_handler(string handler)
Changes the storage handler. Returns false if thecurrent handler cannot be shutdown or the requestedhandler cannot be initialized. Failing to changethe handler should be considered as a fatal errorunless the change fails because the requested handleris unknown.You can either change the storage handler to one ofbuild-in handlers or register a user-defined storagehandler object derived from mysqlnd_qc_handler_default.
Handler API Handler registration (I)
Active handlerApp / QC Coremysqlnd_qc_change_handler()shutdown active handler: OK!
shutdown()return true
New handlerinit()return true
init new handler: OK!
install to new handler
return true
Handler API Handler registration (II)
Active handlerApp / QC Coremysqlnd_qc_change_handler()shutdown active handler: OK!
shutdown()return false
New handlerinit()return true
init new handler: OK!
install to new handler
Warning: Shutdown of previous handler '%s' failed
return true
Handler API Handler registration(III)
Active handlerApp / QC Coremysqlnd_qc_change_handler()shutdown active handler: OK!
shutdown()return false
New handlerinit()return false
Warning: Error during changing
handler. Init of '%s' failed
use build-in nop handler
Warning: Shutdown of
previous handler '%s' failed
cache disabled: return false
API details - init()
init()
Returns true if the handler is ready to be used. Called upon handler registration triggered by a call to mysqlnd_qc_change_handler().
Not available with mysqlnd_set_user_handlers()!
Part of the class mysqlnd_qc_handler_default
Part of the interface mysqlnd_qc_handler
API details - shutdown()
shutdown()
Returns true if the handler has succeeded to clean up resources and is ready to be shutdown. Called upon handler registration triggered by a call to mysqlnd_qc_change_handler().
Not available with mysqlnd_set_user_handlers()!
Part of the class mysqlnd_qc_handler_default
Part of the interface mysqlnd_qc_handler
Table of Contents
OverviewHow to customize: template pattern
Flavours of user handler
Handler APIStorage
Registration
ExamplesQuick start: identify cache candidates
Master class: slam defense
Quick Start: subclassing
Subclass mysqlnd_qc_handler_default
AdvantagesFast: replace selected invariants only
Easy: no need to know all API calls
Convenient: reuse internal C implementation
DisadvantagesOnly build-in default handler can be subclassed
Class mysqlnd_qc_handler_default
class mysqlnd_qc_handler_default { public function init() {} public function is_select(...) {} public function get_hash_key(...) {} public function return_to_cache(...) {} public function add_to_cache(...) {} public function find_in_cache(...) {} public function update_cache_stats(...) {} public function get_stats(...) {} public function clear_cache() {} public function shutdown() {}}
Quick start: search cache candidates
Which queries does the app run? Which ones to cache?
class qc_monitor extends mysqlnd_qc_handler_default { public function is_select($query) { printf("qc_monitor: '%s'\n", $query); return parent::is_select($query); }}$monitor = new qc_monitor();mysqlnd_qc_change_handler($monitor);
$mysqli = new mysqli("host", "user", "passwd",
"db");$mysqli->query("SELECT 1");
Quick start: query monitoring (I)
class qc_monitor extends mysqlnd_qc_handler_default { private
$key_to_query = array(); public function get_hash_key($h, $p, $u,
$d, $q, $p) { $key = parent::get_hash_key($h, $p, $u, $d, $q, $p);
$this->key_to_query[$key] = $query; return $key; }
public function is_select($query) { return true; }
/* continued */
Quick start: query monitoring (II)
/* continued */ public function add_to_cache
($key, $data, $ttl, $run_t, $store_t, $row_c) {
printf("Query = '%s'\n", $this->key_to_query[$key]); printf("\t run time = %d ms\n", $run_t); printf("\t store time = %d ms\n", $store_t); printf("\t size = %d rows\n", $row_c);
/* do not add to cache! */ return false; }
}
Quick Start: Don't do this
Don't modify wire data!Packets out of order. Expected n received m...
MySQL server has gone away
Error reading result set's header
Don't change $persistent of get_hash_key()!Segmentation fault
Internal Server Error 500
Develop everything from ground up
interface mysqlnd_qc_handler { public function is_select(...) {}
public function get_hash_key(...) {} public function
return_to_cache(...) {} public function add_to_cache(...) {} public
function find_in_cache(...) {} public function
update_cache_stats(...) {} public function get_stats(...) {} public
function clear_cache() {}}
No limits basic usage pattern like before
Cache MissClient 2...100Client 2...100Client 2...100Master class: Slam defense
Serve stale data to avoid MySQL overloading
Client 1Client 2...nMySQL
Client 2...100Client 2...100Client 2...100Client 1Client 2...n Slam Stale Hit
MySQL
Refresh
Cache Hit
Master class: slam defense
Short code exampleswritten to illustrate the algorithm
but pointless because, ...
Slam defense is build-in to APC and Default!mysqlnd_qc.slam_defense_ttl
Storage must survive multiple requestsNo slamming, if only one process...
$this->cache is not shared among PHP instances
Default + CGI
Master class: slam defense (I)
class qc_monitor extends mysqlnd_qc_handler_default { private
$cache = array(); public function add_to_cache(
$key, $data, $ttl, $run_t, $store_t, $row_c) { printf("add :
put(ttl = %d)\n", $ttl); $this->cache[$key] = array( "data"
=> $data, "valid_until" => microtime(true) + $ttl,
"slam_until" => NULL ); return true; }
/* continued */
Master class: slam defense (II)
public function find_in_cache($key) { if (!isset($this->cache[$key])) { printf("find: miss\n"); return NULL; } $now = microtime(true); if ($this->cache[$key]["valid_until"] > $now) { printf("find: hit\n"); return $this->cache[$key]["data"]; }
/* continued */
Master class: slam defense (III)
if ($this->cache[$key]["slam_until"]) { if
($this->cache[$key]["slam_until"] > $now) { printf("find:
hit, slam defense active\n"); return $this->cache[$key]["data"];
} else { printf("find: miss, slam defense expired\n");
unset($this->cache[$key]); return NULL; } } printf("find:
expired, slam defense starts\n");
$this->cache[$key]["slam_until"] = $now + 2; return
$this->cache[$key]["data"]; }}
PresentationsA query cache plugin
Query cache plugin benchmark impressions
Dig deeper with QC statistics
Developing user storage handler
Further reading
The End
Feedback: [email protected]
The End
Feedback:
[email protected],
[email protected]
Sun Microsystems, Inc.
Page
Click to edit the title text format
Click to edit the outline text formatSecond Outline Level
Click to edit the notes format
Page
Click to edit the title text format
Presenters NamePresenters TitlePresenters Company
Click to edit the notes format
Page