diff --git a/reference/mysqlnd_qc/quickstart.xml b/reference/mysqlnd_qc/quickstart.xml index e7479e0c38..55b6b871e9 100644 --- a/reference/mysqlnd_qc/quickstart.xml +++ b/reference/mysqlnd_qc/quickstart.xml @@ -713,6 +713,12 @@ mysqlnd_qc.slam_defense_ttl=1 The extended life time of a cache entry is set with mysqlnd_qc.slam_defense_ttl. + + The function + mysqlnd_qc_get_core_stats returns an array of + statistics. The statistics slam_stale_refresh and + slam_stale_hit are incremented if slam defense takes place. + It is not possible to give a one-fits-all recommendation on the slam defense configuration. Users are advised to monitor and test their setup and derive @@ -720,8 +726,601 @@ mysqlnd_qc.slam_defense_ttl=1 +
+ Finding cache candidates + + A statement should be considered for caching if it is executed often and has + a long run time. Cache candidates are found by creating a list of statements + sorted by the product of the number of executions multiplied by the + statements run time. The function + mysqlnd_qc_get_query_trace_log + returns a query log which help with the task. + + + Collecting a query trace is a slow operation. Thus, it is disabled by default. + The PHP configuration directive + mysqlnd_qc.collect_query_trace + is used to enable it. The functions trace contains one entry for every + query issued before the function is called. + + + + + + + +query("SELECT 1 AS _one FROM DUAL"); + $res->free(); +} + +/* dump trace */ +var_dump(mysqlnd_qc_get_query_trace_log()); +?> +]]> + + &examples.outputs; + + + array(8) { + ["query"]=> + string(26) "SELECT 1 AS _one FROM DUAL" + ["origin"]=> + string(102) "#0 qc.php(7): mysqli->query('SELECT 1 AS _on...') +#1 {main}" + ["run_time"]=> + int(0) + ["store_time"]=> + int(25) + ["eligible_for_caching"]=> + bool(false) + ["no_table"]=> + bool(false) + ["was_added"]=> + bool(false) + ["was_already_in_cache"]=> + bool(false) + } + [1]=> + array(8) { + ["query"]=> + string(26) "SELECT 1 AS _one FROM DUAL" + ["origin"]=> + string(102) "#0 qc.php(7): mysqli->query('SELECT 1 AS _on...') +#1 {main}" + ["run_time"]=> + int(0) + ["store_time"]=> + int(8) + ["eligible_for_caching"]=> + bool(false) + ["no_table"]=> + bool(false) + ["was_added"]=> + bool(false) + ["was_already_in_cache"]=> + bool(false) + } +} +]]> + + + + + Assorted information is given in the trace. Among them + timings and the origin of the query call. The origin property + holds a code backtrace to identify the source of the query. + The depth of the backtrace can be limited with + the PHP configuration directive + mysqlnd_qc.query_trace_bt_depth. + The default depth is 3. + + + + + + + +query("DROP TABLE IF EXISTS test"); +$mysqli->query("CREATE TABLE test(id INT)"); +$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)"); + +/* dummy queries to fill the query trace */ +for ($i = 0; $i < 3; $i++) { + $res = $mysqli->query("SELECT id FROM test WHERE id = " . $mysqli->real_escape_string($i)); + $res->free(); +} + +$trace = mysqlnd_qc_get_query_trace_log(); +$summary = array(); +foreach ($trace as $entry) { + if (!isset($summary[$entry['query']])) { + $summary[$entry['query']] = array( + "executions" => 1, + "time" => $entry['run_time'] + $entry['store_time'], + ); + } else { + $summary[$entry['query']]['executions']++; + $summary[$entry['query']]['time'] += $entry['run_time'] + $entry['store_time']; + } +} +foreach ($summary as $query => $details) { + printf("%45s: %5dms (%dx)\n", + $query, $details['time'], $details['executions']); +} +?> +]]> + + &examples.outputs; + + + + + +
+ +
+ Measuring cache efficiency + + PECL/mysqlnd_qc offers three ways to measure the cache efficiency. + The function + + mysqlnd_qc_get_normalized_query_trace_log + + returns statistics aggregated by the normalized query string, + + mysqlnd_qc_get_cache_info + gives storage handler specific information which includes a list + of all cached items, depending on the storage handler. Additionally, the + core of PECL/mysqlnd_qc collects high-level summary statistics aggregated + per PHP process. The high-level satistics are returned by + + mysqlnd_qc_get_core_stats + . + + + The functions + mysqlnd_qc_get_normalized_query_trace_log + and + + mysqlnd_qc_get_core_stats + will not collect data unless, data collection has been + enabled through their corresponding PHP configuration directives. Please, + find the names of the configuration directives in the examples. Data collection + is disabled by default for performance considerations. It has been made + configurable through + mysqlnd_qc.time_statistics whether to collect timing + information or not. Collection of time statistics is enabled by default + but only performed, if data collection as such has been enabled. + Recording time statistics causes extra system calls. In most cases, + the benefit of the monitoring outweights any potential performance penalty of + the additional system calls. + + + + + + + +query("DROP TABLE IF EXISTS test"); +$mysqli->query("CREATE TABLE test(id INT)"); +$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)"); + +/* dummy queries */ +for ($i = 1; $i <= 4; $i++) { + $query = sprintf("/*%s*/SELECT id FROM test WHERE id = %d", + MYSQLND_QC_ENABLE_SWITCH, + $i % 2); + $res = $mysqli->query($query); + $res->free(); +} + +var_dump(mysqlnd_qc_get_core_stats()); +?> +]]> + + &examples.outputs; + + + string(1) "2" + ["cache_miss"]=> + string(1) "2" + ["cache_put"]=> + string(1) "2" + ["query_should_cache"]=> + string(1) "4" + ["query_should_not_cache"]=> + string(1) "3" + ["query_not_cached"]=> + string(1) "3" + ["query_could_cache"]=> + string(1) "4" + ["query_found_in_cache"]=> + string(1) "2" + ["query_uncached_other"]=> + string(1) "0" + ["query_uncached_no_table"]=> + string(1) "0" + ["query_uncached_no_result"]=> + string(1) "0" + ["query_uncached_use_result"]=> + string(1) "0" + ["query_aggr_run_time_cache_hit"]=> + string(2) "28" + ["query_aggr_run_time_cache_put"]=> + string(3) "900" + ["query_aggr_run_time_total"]=> + string(3) "928" + ["query_aggr_store_time_cache_hit"]=> + string(2) "14" + ["query_aggr_store_time_cache_put"]=> + string(2) "40" + ["query_aggr_store_time_total"]=> + string(2) "54" + ["receive_bytes_recorded"]=> + string(3) "136" + ["receive_bytes_replayed"]=> + string(3) "136" + ["send_bytes_recorded"]=> + string(2) "84" + ["send_bytes_replayed"]=> + string(2) "84" + ["slam_stale_refresh"]=> + string(1) "0" + ["slam_stale_hit"]=> + string(1) "0" + ["request_counter"]=> + int(1) + ["process_hash"]=> + int(1929695233) +} +]]> + + + + + For a quick overview, call + mysqlnd_qc_get_core_stats. It delivers + cache usage, cache timing and traffic related statistics. Values are aggregated + on a per process basis for all queries issued by any PHP MySQL API call. + + + Some storage handler, such as the default handler, can report cache entries, + statistics related to the entries and meta data for the underlying query through the + + mysqlnd_qc_get_cache_info + function. Please note, that the information returned depends + on the storage handler. Values are aggregated on a per process basis. + + + + + + + +query("DROP TABLE IF EXISTS test"); +$mysqli->query("CREATE TABLE test(id INT)"); +$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)"); + +/* dummy queries to fill the query trace */ +for ($i = 1; $i <= 4; $i++) { + $query = sprintf("/*%s*/SELECT id FROM test WHERE id = %d", + MYSQLND_QC_ENABLE_SWITCH, + $i % 2); + $res = $mysqli->query($query); + $res->free(); +} +var_dump(mysqlnd_qc_get_cache_info()); +?> +]]> + + &examples.outputs; + + + int(2) + ["handler"]=> + string(7) "default" + ["handler_version"]=> + string(5) "1.0.0" + ["data"]=> + array(2) { + ["Localhost via UNIX socket +3306 +root +test|/*qc=on*/SELECT id FROM test WHERE id = 1"]=> + array(2) { + ["statistics"]=> + array(11) { + ["rows"]=> + int(1) + ["stored_size"]=> + int(71) + ["cache_hits"]=> + int(1) + ["run_time"]=> + int(391) + ["store_time"]=> + int(27) + ["min_run_time"]=> + int(16) + ["max_run_time"]=> + int(16) + ["min_store_time"]=> + int(8) + ["max_store_time"]=> + int(8) + ["avg_run_time"]=> + int(8) + ["avg_store_time"]=> + int(4) + } + ["metadata"]=> + array(1) { + [0]=> + array(8) { + ["name"]=> + string(2) "id" + ["orig_name"]=> + string(2) "id" + ["table"]=> + string(4) "test" + ["orig_table"]=> + string(4) "test" + ["db"]=> + string(4) "test" + ["max_length"]=> + int(1) + ["length"]=> + int(11) + ["type"]=> + int(3) + } + } + } + ["Localhost via UNIX socket +3306 +root +test|/*qc=on*/SELECT id FROM test WHERE id = 0"]=> + array(2) { + ["statistics"]=> + array(11) { + ["rows"]=> + int(0) + ["stored_size"]=> + int(65) + ["cache_hits"]=> + int(1) + ["run_time"]=> + int(299) + ["store_time"]=> + int(13) + ["min_run_time"]=> + int(11) + ["max_run_time"]=> + int(11) + ["min_store_time"]=> + int(6) + ["max_store_time"]=> + int(6) + ["avg_run_time"]=> + int(5) + ["avg_store_time"]=> + int(3) + } + ["metadata"]=> + array(1) { + [0]=> + array(8) { + ["name"]=> + string(2) "id" + ["orig_name"]=> + string(2) "id" + ["table"]=> + string(4) "test" + ["orig_table"]=> + string(4) "test" + ["db"]=> + string(4) "test" + ["max_length"]=> + int(0) + ["length"]=> + int(11) + ["type"]=> + int(3) + } + } + } + } +} +]]> + + + + + It is possible to further break down the granularity of statistics + to the level of the normalized statement string. + The normalized statement string is the statements string with all parameters + replaced with question marks. For example, the two statements + SELECT id FROM test WHERE id = 0 and + SELECT id FROM test WHERE id = 1 are normalized into + SELECT id FROM test WHERE id = ?. Their both + statistics are aggregated into one entry for + SELECT id FROM test WHERE id = ?. + + + + + + + +query("DROP TABLE IF EXISTS test"); +$mysqli->query("CREATE TABLE test(id INT)"); +$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)"); + +/* dummy queries to fill the query trace */ +for ($i = 1; $i <= 4; $i++) { + $query = sprintf("/*%s*/SELECT id FROM test WHERE id = %d", + MYSQLND_QC_ENABLE_SWITCH, + $i % 2); + $res = $mysqli->query($query); + $res->free(); +} +var_dump(mysqlnd_qc_get_normalized_query_trace_log()); +?> +]]> + + &examples.outputs; + + + array(9) { + ["query"]=> + string(25) "DROP TABLE IF EXISTS test" + ["occurences"]=> + int(0) + ["eligible_for_caching"]=> + bool(false) + ["avg_run_time"]=> + int(0) + ["min_run_time"]=> + int(0) + ["max_run_time"]=> + int(0) + ["avg_store_time"]=> + int(0) + ["min_store_time"]=> + int(0) + ["max_store_time"]=> + int(0) + } + [1]=> + array(9) { + ["query"]=> + string(27) "CREATE TABLE test (id INT )" + ["occurences"]=> + int(0) + ["eligible_for_caching"]=> + bool(false) + ["avg_run_time"]=> + int(0) + ["min_run_time"]=> + int(0) + ["max_run_time"]=> + int(0) + ["avg_store_time"]=> + int(0) + ["min_store_time"]=> + int(0) + ["max_store_time"]=> + int(0) + } + [2]=> + array(9) { + ["query"]=> + string(46) "INSERT INTO test (id ) VALUES (? ), (? ), (? )" + ["occurences"]=> + int(0) + ["eligible_for_caching"]=> + bool(false) + ["avg_run_time"]=> + int(0) + ["min_run_time"]=> + int(0) + ["max_run_time"]=> + int(0) + ["avg_store_time"]=> + int(0) + ["min_store_time"]=> + int(0) + ["max_store_time"]=> + int(0) + } + [3]=> + array(9) { + ["query"]=> + string(31) "SELECT id FROM test WHERE id =?" + ["occurences"]=> + int(4) + ["eligible_for_caching"]=> + bool(true) + ["avg_run_time"]=> + int(179) + ["min_run_time"]=> + int(11) + ["max_run_time"]=> + int(393) + ["avg_store_time"]=> + int(12) + ["min_store_time"]=> + int(7) + ["max_store_time"]=> + int(25) + } +} +]]> + + + + + The source distribution of PECL/mysqlnd_qc contains a directory + web/ in which web based monitoring + scripts can be found which give an example how to write a cache monitor. + Please, follow the instructions given in the source. + +
+
- Procedural user-defined storage handler + Beyond TTL: user-defined storage The query cache plugin supports the use of user-defined storage handler. User-defined storage handler can use arbitrarily complex invalidation