User-defined storage handler are the way to lift most limitations of the query cache plugin for mysqlnd. For example, you can break out TTL invalidation and put any other more complex invalidation in place. You may go as far as preventing stale results from being saved. Learn how!
10. Template Method pattern, kind of Core: invariant part of the caching, handler: variant part <?php /* Any PHP MySQL application */ ?> ext/*mysql* ext/*mysql* mysqlnd mysqlnd Query Cache Plugin (mysqlnd_qc core) Storage handler (mysqlnd_qc)
11. Variant and invariant parts (Miss/Put) mysqlnd Cache Plugin handler Cache Plugin core query() 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?
29. Handler API – Cache Put (I) Application Cache Plugin handler Cache Plugin core *query() Should cache? Yes! Cache entry 'key'? is_select(...) a) Don't cache - return: false b) 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'?
30. Handler API – Cache Put (II) Application Cache Plugin handler Cache Plugin core *query() add_to_cache(...) Add to cache if not exists. Return true if added, false if already in cache. Activate data recorder Call original query() Deactivate recorder Cache wire data *store _result() (often implicit)
31. Handler API – Cache Hit (I) Application Cache Plugin handler Cache Plugin core *query() Should cache? Yes! Cache entry 'key'? is_select(...) a) Don't cache - return: false b) Cache it – return: int TTL get_hash_key(...) Return key of cache entry. find_in_cache(...) Search cache entry, check if still valid, return cache entry. Do we have 'key'?
32. Handler API – Cache Hit (II) Application Cache Plugin handler Cache Plugin core *store _result() (often implicit) Client served! Record timings! return_to_cache(...) Cache entry no longer in use by core. (Default needs this) update_stats(...) Run and store time recorded and reported by the core, useful for per-entry stats.
33. Handler API – Cache Miss (I) Application Cache Plugin handler Cache Plugin core *query() Should cache? No! is_select(...) a) Don't cache – return: false b) Cache - see Cache Put (I)
51. 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.
58. Timings can be used to build per-entry performance figures such as run time comparisons of the cached and uncached query
59. 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!
60.
61. Note that you may have to parse the SQL to catch SQL hints that specify the TTL
67. Can be used to maintain per-entry cache statistics.
68.
69. Flush all cache entries. Called by the core if the user calls mysqlnd_qc_clear_cache().
70.
71. Returns an array of cache statistics and arbitrary other data which will become part of return value of mysqlnd_qc_get_cache_info().
72. 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.
73. 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()
74. 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 the current handler cannot be shutdown or the requested handler cannot be initialized. Failing to change the handler should be considered as a fatal error unless the change fails because the requested handler is unknown. You can either change the storage handler to one of build-in handlers or register a user-defined storage handler object derived from mysqlnd_qc_handler_default.
75. Handler API – Handler registration (I) Active handler App / QC Core mysqlnd_qc_change_handler() shutdown active handler: OK! shutdown() return true New handler init() return true init new handler: OK! install to new handler return true
76. Handler API – Handler registration (II) Active handler App / QC Core mysqlnd_qc_change_handler() shutdown active handler: OK! shutdown() return false New handler init() return true init new handler: OK! install to new handler Warning: Shutdown of previous handler '%s' failed return true
77. Handler API – Handler registration(III) Active handler App / QC Core mysqlnd_qc_change_handler() shutdown active handler: OK! shutdown() return false New handler init() 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
78.
79. Returns true if the handler is ready to be used. Called upon handler registration triggered by a call to mysqlnd_qc_change_handler().
84. 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().
96. 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() {} }
97. 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'", $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");
98. 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 */
99. Quick start: query monitoring (II) /* continued */ public function add_to_cache ($key, $data, $ttl, $run_t, $store_t, $row_c) { printf("Query = '%s'", $this->key_to_query[$key]); printf(" run time = %d ms", $run_t); printf(" store time = %d ms", $store_t); printf(" size = %d rows", $row_c); /* do not add to cache! */ return false; } }
104. 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
105. Cache Miss Client 2...100 Client 2...100 Client 2...100 Master class: Slam defense Serve stale data to avoid MySQL overloading Client 1 Client 2...n MySQL Client 2...100 Client 2...100 Client 2...100 Client 1 Client 2...n Slam Stale Hit MySQL Cache Hit
111. Master class: slam defense (II) public function find_in_cache($key) { if (!isset($this->cache[$key])) { printf("find: miss"); return NULL; } $now = microtime(true); if ($this->cache[$key]["valid_until"] > $now) { printf("find: hit"); return $this->cache[$key]["data"]; } /* continued */
112. Master class: slam defense (III) if ($this->cache[$key]["slam_until"]) { if ($this->cache[$key]["slam_until"] > $now) { printf("find: hit, slam defense active"); return $this->cache[$key]["data"]; } else { printf("find: miss, slam defense expired"); unset($this->cache[$key]); return NULL; } } printf("find: expired, slam defense starts"); $this->cache[$key]["slam_until"] = $now + 2; return $this->cache[$key]["data"]; } }