summaryrefslogtreecommitdiff
path: root/storage/innobase/dict/dict0stats_bg.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/dict/dict0stats_bg.cc')
-rw-r--r--storage/innobase/dict/dict0stats_bg.cc402
1 files changed, 402 insertions, 0 deletions
diff --git a/storage/innobase/dict/dict0stats_bg.cc b/storage/innobase/dict/dict0stats_bg.cc
new file mode 100644
index 00000000000..9a10d995360
--- /dev/null
+++ b/storage/innobase/dict/dict0stats_bg.cc
@@ -0,0 +1,402 @@
+/*****************************************************************************
+
+Copyright (c) 2012, Oracle and/or its affiliates. All Rights Reserved.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; version 2 of the License.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+
+*****************************************************************************/
+
+/**************************************************//**
+@file dict/dict0stats_bg.cc
+Code used for background table and index stats gathering.
+
+Created Apr 25, 2012 Vasil Dimov
+*******************************************************/
+
+#include "row0mysql.h"
+#include "srv0start.h"
+#include "dict0stats.h"
+#include "dict0stats_bg.h"
+
+#include <vector>
+
+/** Minimum time interval between stats recalc for a given table */
+#define MIN_RECALC_INTERVAL 10 /* seconds */
+
+#define SHUTTING_DOWN() (srv_shutdown_state != SRV_SHUTDOWN_NONE)
+
+/** Event to wake up the stats thread */
+UNIV_INTERN os_event_t dict_stats_event = NULL;
+
+/** This mutex protects the "recalc_pool" variable. */
+static ib_mutex_t recalc_pool_mutex;
+#ifdef HAVE_PSI_INTERFACE
+static mysql_pfs_key_t recalc_pool_mutex_key;
+#endif /* HAVE_PSI_INTERFACE */
+
+/** The number of tables that can be added to "recalc_pool" before
+it is enlarged */
+static const ulint RECALC_POOL_INITIAL_SLOTS = 128;
+
+/** The multitude of tables whose stats are to be automatically
+recalculated - an STL vector */
+typedef std::vector<table_id_t> recalc_pool_t;
+static recalc_pool_t recalc_pool;
+
+typedef recalc_pool_t::iterator recalc_pool_iterator_t;
+
+/*****************************************************************//**
+Initialize the recalc pool, called once during thread initialization. */
+static
+void
+dict_stats_recalc_pool_init()
+/*=========================*/
+{
+ ut_ad(!srv_read_only_mode);
+
+ recalc_pool.reserve(RECALC_POOL_INITIAL_SLOTS);
+}
+
+/*****************************************************************//**
+Free the resources occupied by the recalc pool, called once during
+thread de-initialization. */
+static
+void
+dict_stats_recalc_pool_deinit()
+/*===========================*/
+{
+ ut_ad(!srv_read_only_mode);
+
+ recalc_pool.clear();
+ /*
+ recalc_pool may still have its buffer allocated. It will free it when
+ its destructor is called.
+ The problem is, memory leak detector is run before the recalc_pool's
+ destructor is invoked, and will report recalc_pool's buffer as leaked
+ memory. To avoid that, we force recalc_pool to surrender its buffer
+ to empty_pool object, which will free it when leaving this function:
+ */
+ recalc_pool_t empty_pool;
+ recalc_pool.swap(empty_pool);
+}
+
+/*****************************************************************//**
+Add a table to the recalc pool, which is processed by the
+background stats gathering thread. Only the table id is added to the
+list, so the table can be closed after being enqueued and it will be
+opened when needed. If the table does not exist later (has been DROPped),
+then it will be removed from the pool and skipped.
+dict_stats_recalc_pool_add() @{ */
+UNIV_INTERN
+void
+dict_stats_recalc_pool_add(
+/*=======================*/
+ const dict_table_t* table) /*!< in: table to add */
+{
+ ut_ad(!srv_read_only_mode);
+
+ mutex_enter(&recalc_pool_mutex);
+
+ /* quit if already in the list */
+ for (recalc_pool_iterator_t iter = recalc_pool.begin();
+ iter != recalc_pool.end();
+ ++iter) {
+
+ if (*iter == table->id) {
+ mutex_exit(&recalc_pool_mutex);
+ return;
+ }
+ }
+
+ recalc_pool.push_back(table->id);
+
+ mutex_exit(&recalc_pool_mutex);
+
+ os_event_set(dict_stats_event);
+}
+/* @} */
+
+/*****************************************************************//**
+Get a table from the auto recalc pool. The returned table id is removed
+from the pool.
+dict_stats_recalc_pool_get() @{
+@return true if the pool was non-empty and "id" was set, false otherwise */
+static
+bool
+dict_stats_recalc_pool_get(
+/*=======================*/
+ table_id_t* id) /*!< out: table id, or unmodified if list is
+ empty */
+{
+ ut_ad(!srv_read_only_mode);
+
+ mutex_enter(&recalc_pool_mutex);
+
+ if (recalc_pool.empty()) {
+ mutex_exit(&recalc_pool_mutex);
+ return(false);
+ }
+
+ *id = recalc_pool[0];
+
+ recalc_pool.erase(recalc_pool.begin());
+
+ mutex_exit(&recalc_pool_mutex);
+
+ return(true);
+}
+/* @} */
+
+/*****************************************************************//**
+Delete a given table from the auto recalc pool.
+dict_stats_recalc_pool_del() */
+UNIV_INTERN
+void
+dict_stats_recalc_pool_del(
+/*=======================*/
+ const dict_table_t* table) /*!< in: table to remove */
+{
+ ut_ad(!srv_read_only_mode);
+ ut_ad(mutex_own(&dict_sys->mutex));
+
+ mutex_enter(&recalc_pool_mutex);
+
+ ut_ad(table->id > 0);
+
+ for (recalc_pool_iterator_t iter = recalc_pool.begin();
+ iter != recalc_pool.end();
+ ++iter) {
+
+ if (*iter == table->id) {
+ /* erase() invalidates the iterator */
+ recalc_pool.erase(iter);
+ break;
+ }
+ }
+
+ mutex_exit(&recalc_pool_mutex);
+}
+
+/*****************************************************************//**
+Wait until background stats thread has stopped using the specified table(s).
+The caller must have locked the data dictionary using
+row_mysql_lock_data_dictionary() and this function may unlock it temporarily
+and restore the lock before it exits.
+The background stats thead is guaranteed not to start using the specified
+tables after this function returns and before the caller unlocks the data
+dictionary because it sets the BG_STAT_IN_PROGRESS bit in table->stats_bg_flag
+under dict_sys->mutex.
+dict_stats_wait_bg_to_stop_using_table() @{ */
+UNIV_INTERN
+void
+dict_stats_wait_bg_to_stop_using_tables(
+/*====================================*/
+ dict_table_t* table1, /*!< in/out: table1 */
+ dict_table_t* table2, /*!< in/out: table2, could be NULL */
+ trx_t* trx) /*!< in/out: transaction to use for
+ unlocking/locking the data dict */
+{
+ ut_ad(!srv_read_only_mode);
+
+ while ((table1->stats_bg_flag & BG_STAT_IN_PROGRESS)
+ || (table2 != NULL
+ && (table2->stats_bg_flag & BG_STAT_IN_PROGRESS))) {
+
+ table1->stats_bg_flag |= BG_STAT_SHOULD_QUIT;
+ if (table2 != NULL) {
+ table2->stats_bg_flag |= BG_STAT_SHOULD_QUIT;
+ }
+
+ row_mysql_unlock_data_dictionary(trx);
+ os_thread_sleep(250000);
+ row_mysql_lock_data_dictionary(trx);
+ }
+}
+/* @} */
+
+/*****************************************************************//**
+Initialize global variables needed for the operation of dict_stats_thread()
+Must be called before dict_stats_thread() is started.
+dict_stats_thread_init() @{ */
+UNIV_INTERN
+void
+dict_stats_thread_init()
+/*====================*/
+{
+ ut_a(!srv_read_only_mode);
+
+ dict_stats_event = os_event_create();
+
+ /* The recalc_pool_mutex is acquired from:
+ 1) the background stats gathering thread before any other latch
+ and released without latching anything else in between (thus
+ any level would do here)
+ 2) from row_update_statistics_if_needed()
+ and released without latching anything else in between. We know
+ that dict_sys->mutex (SYNC_DICT) is not acquired when
+ row_update_statistics_if_needed() is called and it may be acquired
+ inside that function (thus a level <=SYNC_DICT would do).
+ 3) from row_drop_table_for_mysql() after dict_sys->mutex (SYNC_DICT)
+ and dict_operation_lock (SYNC_DICT_OPERATION) have been locked
+ (thus a level <SYNC_DICT && <SYNC_DICT_OPERATION would do)
+ So we choose SYNC_STATS_AUTO_RECALC to be about below SYNC_DICT. */
+ mutex_create(recalc_pool_mutex_key, &recalc_pool_mutex,
+ SYNC_STATS_AUTO_RECALC);
+
+ dict_stats_recalc_pool_init();
+}
+/* @} */
+
+/*****************************************************************//**
+Free resources allocated by dict_stats_thread_init(), must be called
+after dict_stats_thread() has exited.
+dict_stats_thread_deinit() @{ */
+UNIV_INTERN
+void
+dict_stats_thread_deinit()
+/*======================*/
+{
+ ut_a(!srv_read_only_mode);
+ ut_ad(!srv_dict_stats_thread_active);
+
+ dict_stats_recalc_pool_deinit();
+
+ mutex_free(&recalc_pool_mutex);
+ memset(&recalc_pool_mutex, 0x0, sizeof(recalc_pool_mutex));
+
+ os_event_free(dict_stats_event);
+ dict_stats_event = NULL;
+}
+/* @} */
+
+/*****************************************************************//**
+Get the first table that has been added for auto recalc and eventually
+update its stats.
+dict_stats_process_entry_from_recalc_pool() @{ */
+static
+void
+dict_stats_process_entry_from_recalc_pool()
+/*=======================================*/
+{
+ table_id_t table_id;
+
+ ut_ad(!srv_read_only_mode);
+
+ /* pop the first table from the auto recalc pool */
+ if (!dict_stats_recalc_pool_get(&table_id)) {
+ /* no tables for auto recalc */
+ return;
+ }
+
+ dict_table_t* table;
+
+ mutex_enter(&dict_sys->mutex);
+
+ table = dict_table_open_on_id(table_id, TRUE, FALSE);
+
+ if (table == NULL) {
+ /* table does not exist, must have been DROPped
+ after its id was enqueued */
+ mutex_exit(&dict_sys->mutex);
+ return;
+ }
+
+ /* Check whether table is corrupted */
+ if (table->corrupted) {
+ dict_table_close(table, TRUE, FALSE);
+ mutex_exit(&dict_sys->mutex);
+ return;
+ }
+
+ table->stats_bg_flag = BG_STAT_IN_PROGRESS;
+
+ mutex_exit(&dict_sys->mutex);
+
+ /* ut_time() could be expensive, the current function
+ is called once every time a table has been changed more than 10% and
+ on a system with lots of small tables, this could become hot. If we
+ find out that this is a problem, then the check below could eventually
+ be replaced with something else, though a time interval is the natural
+ approach. */
+
+ if (ut_difftime(ut_time(), table->stats_last_recalc)
+ < MIN_RECALC_INTERVAL) {
+
+ /* Stats were (re)calculated not long ago. To avoid
+ too frequent stats updates we put back the table on
+ the auto recalc list and do nothing. */
+
+ dict_stats_recalc_pool_add(table);
+
+ } else {
+
+ dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT);
+ }
+
+ mutex_enter(&dict_sys->mutex);
+
+ table->stats_bg_flag = BG_STAT_NONE;
+
+ dict_table_close(table, TRUE, FALSE);
+
+ mutex_exit(&dict_sys->mutex);
+}
+/* @} */
+
+/*****************************************************************//**
+This is the thread for background stats gathering. It pops tables, from
+the auto recalc list and proceeds them, eventually recalculating their
+statistics.
+dict_stats_thread() @{
+@return this function does not return, it calls os_thread_exit() */
+extern "C" UNIV_INTERN
+os_thread_ret_t
+DECLARE_THREAD(dict_stats_thread)(
+/*==============================*/
+ void* arg __attribute__((unused))) /*!< in: a dummy parameter
+ required by os_thread_create */
+{
+ ut_a(!srv_read_only_mode);
+
+ srv_dict_stats_thread_active = TRUE;
+
+ while (!SHUTTING_DOWN()) {
+
+ /* Wake up periodically even if not signaled. This is
+ because we may lose an event - if the below call to
+ dict_stats_process_entry_from_recalc_pool() puts the entry back
+ in the list, the os_event_set() will be lost by the subsequent
+ os_event_reset(). */
+ os_event_wait_time(
+ dict_stats_event, MIN_RECALC_INTERVAL * 1000000);
+
+ if (SHUTTING_DOWN()) {
+ break;
+ }
+
+ dict_stats_process_entry_from_recalc_pool();
+
+ os_event_reset(dict_stats_event);
+ }
+
+ srv_dict_stats_thread_active = FALSE;
+
+ /* We count the number of threads in os_thread_exit(). A created
+ thread should always use that to exit instead of return(). */
+ os_thread_exit(NULL);
+
+ OS_THREAD_DUMMY_RETURN;
+}
+/* @} */
+
+/* vim: set foldmethod=marker foldmarker=@{,@}: */