mysqlnd query cache plugin: user-defined storage handler

Download mysqlnd query cache plugin: user-defined storage handler

If you can't read please download the document

Upload: ulf-wendel

Post on 16-Apr-2017

2.648 views

Category:

Technology


0 download

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