diff --git a/mysql-test/r/all_persisted_variables.result b/mysql-test/r/all_persisted_variables.result index dda96f910df3..feca676de66e 100644 --- a/mysql-test/r/all_persisted_variables.result +++ b/mysql-test/r/all_persisted_variables.result @@ -46,7 +46,7 @@ include/assert.inc [Expect 500+ variables in the table. Due to open Bugs, we are # Test SET PERSIST -include/assert.inc [Expect 445 persisted variables in the table.] +include/assert.inc [Expect 446 persisted variables in the table.] ************************************************************ * 3. Restart server, it must preserve the persisted variable @@ -54,9 +54,9 @@ include/assert.inc [Expect 445 persisted variables in the table.] ************************************************************ # restart -include/assert.inc [Expect 445 persisted variables in persisted_variables table.] -include/assert.inc [Expect 445 persisted variables shown as PERSISTED in variables_info table.] -include/assert.inc [Expect 445 persisted variables with matching peristed and global values.] +include/assert.inc [Expect 446 persisted variables in persisted_variables table.] +include/assert.inc [Expect 446 persisted variables shown as PERSISTED in variables_info table.] +include/assert.inc [Expect 446 persisted variables with matching peristed and global values.] ************************************************************ * 4. Test RESET PERSIST IF EXISTS. Verify persisted variable diff --git a/mysql-test/suite/innodb/r/monitor.result b/mysql-test/suite/innodb/r/monitor.result index 83ad8b43640e..cb295e8c80e2 100644 --- a/mysql-test/suite/innodb/r/monitor.result +++ b/mysql-test/suite/innodb/r/monitor.result @@ -178,6 +178,9 @@ undo_truncate_count disabled undo_truncate_start_logging_count disabled undo_truncate_done_logging_count disabled undo_truncate_usec disabled +undo_truncate_snapshot_ticket_grant_count disabled +undo_truncate_snapshot_ticket_try_count disabled +undo_truncate_snapshot_ticket_wait_count disabled log_lsn_last_flush disabled log_lsn_last_checkpoint disabled log_lsn_current disabled diff --git a/mysql-test/suite/innodb_undo/r/undo_tablespace_snapshot.result b/mysql-test/suite/innodb_undo/r/undo_tablespace_snapshot.result new file mode 100644 index 000000000000..45a62ee2013d --- /dev/null +++ b/mysql-test/suite/innodb_undo/r/undo_tablespace_snapshot.result @@ -0,0 +1,47 @@ +# +# undo tablespace counter +# +SHOW VARIABLES LIKE 'innodb_rollback_segments'; +Variable_name Value +innodb_rollback_segments 1 +SELECT NAME, SPACE_TYPE, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES +WHERE SPACE_TYPE = 'Undo' ORDER BY NAME; +NAME SPACE_TYPE STATE +innodb_undo_001 Undo active +innodb_undo_002 Undo active +SET GLOBAL innodb_purge_stop_now=ON; +create table t1( +keyc int, +c1 char(255), +c2 char(255), +c3 char(255), +c4 char(255), +c5 char(255), +c6 char(255), +primary key(keyc)) engine = innodb; +CREATE PROCEDURE populate_t1() +BEGIN +DECLARE i INT DEFAULT 1; +while (i <= 20000) DO +insert into t1 values (i, 'a', 'b', 'c', 'd', 'e', 'f' ); +SET i = i + 1; +END WHILE; +END | +call populate_t1(); +delete from t1 where keyc < 10000; +update t1 set c1 = 'mysql' where keyc > 10000; +update t1 set c2 = 'mysql' where keyc > 10000; +update t1 set c3= 'mysql' where keyc > 10000; +update t1 set c4= 'mysql' where keyc > 10000; +update t1 set c5= 'mysql' where keyc > 10000; +update t1 set c6= 'mysql' where keyc > 10000; +SELECT @@innodb_undo_spaces_snapshot_tickets; +@@innodb_undo_spaces_snapshot_tickets +100 +SET GLOBAL debug="+d,until_undo_spaces_snapshot_change,abort_if_use_undospace_latch"; +SET GLOBAL innodb_purge_run_now=ON; +SET GLOBAL debug="-d,until_undo_spaces_snapshot_change"; +update t1 set c6= 'modified' where keyc = 10000; +SET GLOBAL debug="-d,abort_if_use_undospace_latch"; +drop PROCEDURE populate_t1; +drop table t1; \ No newline at end of file diff --git a/mysql-test/suite/innodb_undo/t/undo_tablespace_snapshot-master.opt b/mysql-test/suite/innodb_undo/t/undo_tablespace_snapshot-master.opt new file mode 100644 index 000000000000..bbbce834dcd9 --- /dev/null +++ b/mysql-test/suite/innodb_undo/t/undo_tablespace_snapshot-master.opt @@ -0,0 +1,6 @@ +--innodb_rollback_segments=1 +--innodb_undo_log_truncate=1 +--innodb_max_undo_log_size=11M +--innodb_purge_rseg_truncate_frequency=1 +--log-error-verbosity=3 +--innodb_undo_spaces_snapshot_tickets=100 \ No newline at end of file diff --git a/mysql-test/suite/innodb_undo/t/undo_tablespace_snapshot.test b/mysql-test/suite/innodb_undo/t/undo_tablespace_snapshot.test new file mode 100644 index 000000000000..91307880e44c --- /dev/null +++ b/mysql-test/suite/innodb_undo/t/undo_tablespace_snapshot.test @@ -0,0 +1,71 @@ +--echo # +--echo # undo tablespace counter +--echo # + +# This test uses debug settings like innodb_purge_stop_now. +--source include/have_debug.inc + +# Valgrind would complain about memory leaks when we crash on purpose. +--source include/not_valgrind.inc + +--source include/big_test.inc +--source include/have_innodb_default_undo_tablespaces.inc + +SHOW VARIABLES LIKE 'innodb_rollback_segments'; +SELECT NAME, SPACE_TYPE, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES + WHERE SPACE_TYPE = 'Undo' ORDER BY NAME; + +SET GLOBAL innodb_purge_stop_now=ON; + +--connect(conn1,localhost,root,,test) +--connection conn1 + +create table t1( + keyc int, + c1 char(255), + c2 char(255), + c3 char(255), + c4 char(255), + c5 char(255), + c6 char(255), + primary key(keyc)) engine = innodb; + +delimiter |; +CREATE PROCEDURE populate_t1() +BEGIN + DECLARE i INT DEFAULT 1; + while (i <= 20000) DO + insert into t1 values (i, 'a', 'b', 'c', 'd', 'e', 'f' ); + SET i = i + 1; + END WHILE; +END | +delimiter ;| + +call populate_t1(); +delete from t1 where keyc < 10000; +update t1 set c1 = 'mysql' where keyc > 10000; +update t1 set c2 = 'mysql' where keyc > 10000; +update t1 set c3= 'mysql' where keyc > 10000; +update t1 set c4= 'mysql' where keyc > 10000; +update t1 set c5= 'mysql' where keyc > 10000; +update t1 set c6= 'mysql' where keyc > 10000; + +SELECT @@innodb_undo_spaces_snapshot_tickets; +SET GLOBAL debug="+d,until_undo_spaces_snapshot_change,abort_if_use_undospace_latch"; +SET GLOBAL innodb_purge_run_now=ON; + +# Wait until until_undo_spaces_snapshot_change to be reached. +--sleep 10 + +connection default; +SET GLOBAL debug="-d,until_undo_spaces_snapshot_change"; +# The command can succeed iff until_undo_spaces_snapshot_change loop has ended. Otherwise abort_if_use_undospace_latch will be triggered. +update t1 set c6= 'modified' where keyc = 10000; + +# Cleanup +SET GLOBAL debug="-d,abort_if_use_undospace_latch"; +drop PROCEDURE populate_t1; +drop table t1; +--disconnect conn1 + +--sleep 5 \ No newline at end of file diff --git a/mysql-test/suite/sys_vars/r/all_vars.result b/mysql-test/suite/sys_vars/r/all_vars.result index e28e3b50e6a1..1f5a937b6914 100644 --- a/mysql-test/suite/sys_vars/r/all_vars.result +++ b/mysql-test/suite/sys_vars/r/all_vars.result @@ -48,6 +48,8 @@ innodb_log_spin_cpu_pct_hwm innodb_log_spin_cpu_pct_hwm innodb_log_wait_for_flush_spin_hwm innodb_log_wait_for_flush_spin_hwm +innodb_undo_spaces_snapshot_tickets +innodb_undo_spaces_snapshot_tickets keyring_operations keyring_operations log_replica_updates diff --git a/mysql-test/suite/sys_vars/r/innodb_monitor_disable_basic.result b/mysql-test/suite/sys_vars/r/innodb_monitor_disable_basic.result index e4561b180e66..d347d4144106 100644 --- a/mysql-test/suite/sys_vars/r/innodb_monitor_disable_basic.result +++ b/mysql-test/suite/sys_vars/r/innodb_monitor_disable_basic.result @@ -178,6 +178,9 @@ undo_truncate_count disabled undo_truncate_start_logging_count disabled undo_truncate_done_logging_count disabled undo_truncate_usec disabled +undo_truncate_snapshot_ticket_grant_count disabled +undo_truncate_snapshot_ticket_try_count disabled +undo_truncate_snapshot_ticket_wait_count disabled log_lsn_last_flush disabled log_lsn_last_checkpoint disabled log_lsn_current disabled diff --git a/mysql-test/suite/sys_vars/r/innodb_monitor_enable_basic.result b/mysql-test/suite/sys_vars/r/innodb_monitor_enable_basic.result index e4561b180e66..d347d4144106 100644 --- a/mysql-test/suite/sys_vars/r/innodb_monitor_enable_basic.result +++ b/mysql-test/suite/sys_vars/r/innodb_monitor_enable_basic.result @@ -178,6 +178,9 @@ undo_truncate_count disabled undo_truncate_start_logging_count disabled undo_truncate_done_logging_count disabled undo_truncate_usec disabled +undo_truncate_snapshot_ticket_grant_count disabled +undo_truncate_snapshot_ticket_try_count disabled +undo_truncate_snapshot_ticket_wait_count disabled log_lsn_last_flush disabled log_lsn_last_checkpoint disabled log_lsn_current disabled diff --git a/mysql-test/suite/sys_vars/r/innodb_monitor_reset_all_basic.result b/mysql-test/suite/sys_vars/r/innodb_monitor_reset_all_basic.result index e4561b180e66..d347d4144106 100644 --- a/mysql-test/suite/sys_vars/r/innodb_monitor_reset_all_basic.result +++ b/mysql-test/suite/sys_vars/r/innodb_monitor_reset_all_basic.result @@ -178,6 +178,9 @@ undo_truncate_count disabled undo_truncate_start_logging_count disabled undo_truncate_done_logging_count disabled undo_truncate_usec disabled +undo_truncate_snapshot_ticket_grant_count disabled +undo_truncate_snapshot_ticket_try_count disabled +undo_truncate_snapshot_ticket_wait_count disabled log_lsn_last_flush disabled log_lsn_last_checkpoint disabled log_lsn_current disabled diff --git a/mysql-test/suite/sys_vars/r/innodb_monitor_reset_basic.result b/mysql-test/suite/sys_vars/r/innodb_monitor_reset_basic.result index 2876f4423427..72c751034795 100644 --- a/mysql-test/suite/sys_vars/r/innodb_monitor_reset_basic.result +++ b/mysql-test/suite/sys_vars/r/innodb_monitor_reset_basic.result @@ -178,6 +178,9 @@ undo_truncate_count disabled undo_truncate_start_logging_count disabled undo_truncate_done_logging_count disabled undo_truncate_usec disabled +undo_truncate_snapshot_ticket_grant_count disabled +undo_truncate_snapshot_ticket_try_count disabled +undo_truncate_snapshot_ticket_wait_count disabled log_lsn_last_flush disabled log_lsn_last_checkpoint disabled log_lsn_current disabled diff --git a/mysql-test/t/all_persisted_variables.test b/mysql-test/t/all_persisted_variables.test index 4e222f45c6c2..43f1f9487de6 100644 --- a/mysql-test/t/all_persisted_variables.test +++ b/mysql-test/t/all_persisted_variables.test @@ -56,7 +56,7 @@ let $total_global_vars=`SELECT COUNT(*) AND variable_name NOT LIKE 'debug_%' AND variable_name NOT LIKE '%telemetry%'`; -let $total_persistent_vars=445; +let $total_persistent_vars=446; --echo *************************************************************** --echo * 0. Verify that variables present in performance_schema.global diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index 7d343bbcc03b..308f3ec9ac21 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -254,6 +254,7 @@ SET(INNOBASE_SOURCES trx/trx0sys.cc trx/trx0trx.cc trx/trx0undo.cc + trx/undo_spaces_snapshot.cc usr/usr0sess.cc ut/ut0dbg.cc ut/ut0list.cc diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index a0c320789746..b8d20412585d 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -611,6 +611,8 @@ static ulonglong innobase_fts_flags() { return (FTS_ORDERED_RESULT | FTS_DOCID_IN_RESULT); } +static constexpr uint innodb_undo_spaces_snapshot_tickets_max = 1048576; + /** Find and Retrieve the FTS doc_id for the current result row @param[in,out] fts_hdl FTS handler @return the document ID */ @@ -22315,6 +22317,13 @@ static MYSQL_SYSVAR_ULONG(sync_array_size, srv_sync_array_size, 1, /* Minimum value */ 1024, 0); /* Maximum value */ +static MYSQL_SYSVAR_UINT(undo_spaces_snapshot_tickets, innodb_undo_spaces_snapshot_tickets, + PLUGIN_VAR_OPCMDARG, + "Ticket number of undo spaces snapshot.", nullptr, + nullptr, 0, /* Default setting */ + 0, /* Minimum value */ + innodb_undo_spaces_snapshot_tickets_max, 0); /* Maximum value */ + static MYSQL_SYSVAR_ULONG( fast_shutdown, srv_fast_shutdown, PLUGIN_VAR_OPCMDARG, "Speeds up the shutdown process of the InnoDB storage engine. Possible" @@ -23605,6 +23614,7 @@ static SYS_VAR *innobase_system_variables[] = { MYSQL_SYSVAR(undo_directory), MYSQL_SYSVAR(temp_tablespaces_dir), MYSQL_SYSVAR(sync_array_size), + MYSQL_SYSVAR(undo_spaces_snapshot_tickets), MYSQL_SYSVAR(compression_failure_threshold_pct), MYSQL_SYSVAR(compression_pad_pct_max), MYSQL_SYSVAR(default_row_format), diff --git a/storage/innobase/include/srv0mon.h b/storage/innobase/include/srv0mon.h index 5a596d8b3a7e..184691ab9439 100644 --- a/storage/innobase/include/srv0mon.h +++ b/storage/innobase/include/srv0mon.h @@ -340,6 +340,9 @@ enum monitor_id_t { MONITOR_UNDO_TRUNCATE_START_LOGGING_COUNT, MONITOR_UNDO_TRUNCATE_DONE_LOGGING_COUNT, MONITOR_UNDO_TRUNCATE_MICROSECOND, + MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_GRANT_COUNT, + MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_TRY_COUNT, + MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_WAIT_COUNT, /* Recovery related counters */ MONITOR_MODULE_REDO_LOG, diff --git a/storage/innobase/include/trx0purge.h b/storage/innobase/include/trx0purge.h index 7c56efc25cbd..332e0e71c69d 100644 --- a/storage/innobase/include/trx0purge.h +++ b/storage/innobase/include/trx0purge.h @@ -34,6 +34,7 @@ this program; if not, write to the Free Software Foundation, Inc., #ifndef trx0purge_h #define trx0purge_h +#include #include "fil0fil.h" #include "mtr0mtr.h" #include "page0page.h" @@ -50,6 +51,8 @@ this program; if not, write to the Free Software Foundation, Inc., /** The global data structure coordinating a purge */ extern trx_purge_t *purge_sys; +extern unsigned int innodb_undo_spaces_snapshot_tickets; + /** Calculates the file address of an undo log header when we have the file address of its history list node. @return file address of the log */ @@ -140,6 +143,9 @@ struct purge_iter_t { to truncate an undo tablespace. */ namespace undo { +// Forward declaration +class Undo_spaces_snapshot; + /** Magic Number to indicate truncate action is complete. */ const uint32_t s_magic = 76845412; @@ -814,6 +820,8 @@ class Inject_failure_once { #endif /* UNIV_DEBUG */ +extern Undo_spaces_snapshot *undo_spaces_snapshot; + /** Create the truncate log file. Needed to track the state of truncate during a crash. An auxiliary redo log file undo__trunc.log will be created while the truncate of the UNDO is in progress. This file is required during diff --git a/storage/innobase/include/undo_spaces_snapshot.h b/storage/innobase/include/undo_spaces_snapshot.h new file mode 100644 index 000000000000..4ca5ed73b14f --- /dev/null +++ b/storage/innobase/include/undo_spaces_snapshot.h @@ -0,0 +1,67 @@ +#ifndef UNDO_SPACES_SNAPSHOT_H +#define UNDO_SPACES_SNAPSHOT_H + +#include +#include +#include "trx0purge.h" + +#ifdef GMOCK_FOUND +#include "gtest/gtest_prod.h" +#endif + +namespace undo { + +class Undo_spaces_snapshot { + public: + Undo_spaces_snapshot(); + Undo_spaces_snapshot(const Undo_spaces_snapshot &) = delete; + Undo_spaces_snapshot &operator=(const Undo_spaces_snapshot &) = delete; + Undo_spaces_snapshot(Undo_spaces_snapshot &&) = delete; + Undo_spaces_snapshot &operator=(Undo_spaces_snapshot &&) = delete; + ~Undo_spaces_snapshot(); + /* Reset the snapshot and tickets. The method should never be called when + * block_until_tickets_returned is being executed. */ + void reset(const unsigned int max_tickets, + const std::vector &tablespaces); + /* Return the undo tablespaces size in the snapshot. */ + std::size_t get_target_undo_tablespaces_size() const; + /* Return the unused tickets number. */ + unsigned int get_unused_tickets_number() const; + /* Request a ticket. It will repeatedly call try_request_ticket until either + it succeeds or unused_tickets_number equals 0. + Return true if it succeeds. + Return false if there is no unused ticket. */ + bool request_ticket(); + /* Return a ticket requested earlier. */ + void return_ticket(); + /* Block the caller until all used tickets have been returned. */ + void block_until_tickets_returned(); + /* Return the max tickets number. */ + unsigned int get_max_tickets_number() const; + /* Wrapper */ + bool is_active_no_latch_for_undo_space(size_t pos) const; + /* Wrapper */ + ulint get_rsegs_size_for_undo_space(size_t pos) const; + /* Wrapper */ + trx_rseg_t *get_active_for_undo_space(size_t pos, ulint rseg_slot) const; + + private: + /* Return the tablespace pointer at pos, or nullptr if pos is out of bound. */ + Tablespace *at(size_t pos) const; + + private: + unsigned int m_max_tickets; + std::atomic m_unused_tickets; + std::atomic m_used_tickets; + std::vector m_tablespaces; + std::atomic m_is_depriving; + +#ifdef FRIEND_TEST + FRIEND_TEST(UndoSpacesSnapshotTest, DefaultConstructor); + FRIEND_TEST(UndoSpacesSnapshotTest, UseAllTickets); + FRIEND_TEST(UndoSpacesSnapshotTest, UseSomeTickets); +#endif +}; /* class Undo_spaces_snapshot */ +} /* namespace undo */ + +#endif // UNDO_SPACES_SNAPSHOT_H \ No newline at end of file diff --git a/storage/innobase/srv/srv0mon.cc b/storage/innobase/srv/srv0mon.cc index 693450daae84..bac6586fe2ca 100644 --- a/storage/innobase/srv/srv0mon.cc +++ b/storage/innobase/srv/srv0mon.cc @@ -864,6 +864,21 @@ static monitor_info_t innodb_counter_info[] = { "Time (in microseconds) spent to process undo truncation", MONITOR_NONE, MONITOR_DEFAULT_START, MONITOR_UNDO_TRUNCATE_MICROSECOND}, + {"undo_truncate_snapshot_ticket_grant_count", "undo", + "Number of times during undo truncation a snapshot ticket was granted", + MONITOR_NONE, MONITOR_DEFAULT_START, + MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_GRANT_COUNT}, + + {"undo_truncate_snapshot_ticket_try_count", "undo", + "Number of times during undo truncation a snapshot ticket was tried", + MONITOR_NONE, MONITOR_DEFAULT_START, + MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_TRY_COUNT}, + + {"undo_truncate_snapshot_ticket_wait_count", "undo", + "Number of times during undo truncation the purge coordinator has waited until all tickets were returned", + MONITOR_NONE, MONITOR_DEFAULT_START, + MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_WAIT_COUNT}, + /* ========== Counters for Redo log Module ========== */ {"module_log", "log", "Redo log Module", MONITOR_MODULE, MONITOR_DEFAULT_START, MONITOR_MODULE_REDO_LOG}, diff --git a/storage/innobase/srv/srv0start.cc b/storage/innobase/srv/srv0start.cc index 4842d10b2fe9..6c8cf205549f 100644 --- a/storage/innobase/srv/srv0start.cc +++ b/storage/innobase/srv/srv0start.cc @@ -125,6 +125,7 @@ this program; if not, write to the Free Software Foundation, Inc., #include "usr0sess.h" #include "ut0crc32.h" #include "ut0new.h" +#include "undo_spaces_snapshot.h" /** fil_space_t::flags for hard-coded tablespaces */ extern uint32_t predefined_flags; @@ -1121,6 +1122,8 @@ void undo_spaces_init() { undo::spaces = ut::new_withkey( ut::make_psi_memory_key(mem_key_undo_spaces)); + undo::undo_spaces_snapshot = UT_NEW_NOKEY(undo::Undo_spaces_snapshot()); + trx_sys_undo_spaces_init(); undo::init_space_id_bank(); @@ -1141,6 +1144,11 @@ void undo_spaces_deinit() { undo::spaces = nullptr; } + if (undo::undo_spaces_snapshot != nullptr) { + UT_DELETE(undo::undo_spaces_snapshot); + undo::undo_spaces_snapshot = nullptr; + } + trx_sys_undo_spaces_deinit(); if (undo::space_id_bank != nullptr) { diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index 5fdbfcdcd262..89092f6cfd27 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -66,6 +66,7 @@ this program; if not, write to the Free Software Foundation, Inc., #include "trx0rseg.h" #include "trx0trx.h" #include "ut0math.h" +#include "undo_spaces_snapshot.h" /** Maximum allowable purge history length. <=0 means 'infinite'. */ ulong srv_max_purge_lag = 0; @@ -94,6 +95,9 @@ const TrxUndoRsegs TrxUndoRsegsIterator::NullElement(UINT64_UNDEFINED); undo log which can be skipped by purge */ static trx_undo_rec_t trx_purge_ignore_rec; +unsigned int innodb_undo_spaces_snapshot_tickets = 0; +extern mysql_mutex_t LOCK_global_system_variables; + /** Constructor */ TrxUndoRsegsIterator::TrxUndoRsegsIterator(trx_purge_t *purge_sys) : m_purge_sys(purge_sys), @@ -606,6 +610,9 @@ ib_mutex_t ddl_mutex; /** A global object that contains a vector of undo::Tablespace structs. */ Tablespaces *spaces; +/** An global auxiliary object to reduce contention on the undo::spaces::m_latch. */ +Undo_spaces_snapshot *undo_spaces_snapshot; + /** List of currently used undo space IDs for each undo space number along with a boolean showing whether the undo space number is in use. */ struct space_id_account *space_id_bank; @@ -1409,10 +1416,33 @@ static bool trx_purge_truncate_marked_undo_low(space_id_t space_num, ut_d(undo::inject_crash("ib_undo_trunc_before_ddl_log_start")); + std::vector unmarked_spaces; + for (auto undo_space : undo::spaces->m_spaces) { + if (undo_space != marked_space) { + unmarked_spaces.push_back(undo_space); + } + } + /* System variable innodb_undo_spaces_snapshot_tickets may be concurrently + updated by other thread. Ensure atomic read with LOCK_global_system_variables.*/ + mysql_mutex_lock(&LOCK_global_system_variables); + auto updated_innodb_undo_spaces_snapshot_tickets = + innodb_undo_spaces_snapshot_tickets; + mysql_mutex_unlock(&LOCK_global_system_variables); + undo::undo_spaces_snapshot->reset(updated_innodb_undo_spaces_snapshot_tickets, unmarked_spaces); + DBUG_EXECUTE_IF("until_undo_spaces_snapshot_change", { + ib::info() << "until_undo_spaces_snapshot_change"; + while (undo::undo_spaces_snapshot->get_unused_tickets_number() == + undo::undo_spaces_snapshot->get_max_tickets_number()) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + ib::info() << "until_undo_spaces_snapshot_change end"; + }); + MONITOR_INC_VALUE(MONITOR_UNDO_TRUNCATE_START_LOGGING_COUNT, 1); dberr_t err = undo::start_logging(marked_space); if (err != DB_SUCCESS) { ib::error(ER_IB_MSG_UNDO_TRUNCATE_DELAY_BY_LOG_CREATE, space_name.c_str()); + undo::undo_spaces_snapshot->block_until_tickets_returned(); return (false); } ut_ad(err == DB_SUCCESS); @@ -1432,6 +1462,7 @@ static bool trx_purge_truncate_marked_undo_low(space_id_t space_num, #endif /* UNIV_DEBUG */ if (in_fast_shutdown) { + undo::undo_spaces_snapshot->block_until_tickets_returned(); return (false); } @@ -1448,6 +1479,8 @@ static bool trx_purge_truncate_marked_undo_low(space_id_t space_num, /* Do the truncate. This will change the space_id of the marked_space. */ bool success = trx_undo_truncate_tablespace(marked_space); + undo::undo_spaces_snapshot->block_until_tickets_returned(); + if (!success) { /* Note: In case of error we don't enable the rsegs nor unmark the tablespace. So the tablespace will continue to remain inactive. */ diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index d40f94ee3cec..164949880551 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -66,6 +66,7 @@ this program; if not, write to the Free Software Foundation, Inc., #include "ut0new.h" #include "ut0pool.h" #include "ut0vec.h" +#include "undo_spaces_snapshot.h" #include "my_dbug.h" #include "mysql/plugin.h" @@ -1137,12 +1138,31 @@ until the transaction is done with it. static trx_rseg_t *get_next_redo_rseg_from_undo_spaces() { undo::Tablespace *undo_space; - /* The number of undo tablespaces cannot be changed while - we have this s_lock. */ - undo::spaces->s_lock(); + bool use_no_latch = undo::undo_spaces_snapshot->request_ticket(); + ulint target_undo_tablespaces = 0; + if (use_no_latch) { + target_undo_tablespaces = + undo::undo_spaces_snapshot->get_target_undo_tablespaces_size(); + // Undospace marked for truancate is not included in target_undo_tablespaces. + if (target_undo_tablespaces == 0) { + undo::undo_spaces_snapshot->return_ticket(); + use_no_latch = false; + } + } - /* Use all known undo tablespaces. Some may be inactive. */ - ulint target_undo_tablespaces = undo::spaces->size(); + if (!use_no_latch) { + DBUG_EXECUTE_IF("abort_if_use_undospace_latch",{ + ib::info() << "Server will crash by intention. This macro is used only in test cases."; + ut_error; + }); + + /* The number of undo tablespaces cannot be changed while + we have this s_lock. */ + undo::spaces->s_lock(); + + /* Use all known undo tablespaces. Some may be inactive. */ + target_undo_tablespaces = undo::spaces->size(); + } ut_ad(target_undo_tablespaces > 0); @@ -1170,26 +1190,40 @@ static trx_rseg_t *get_next_redo_rseg_from_undo_spaces() { current++; - undo_space = undo::spaces->at(spaces_slot); - - /* Avoid any rseg that resides in a tablespace that has been made - inactive either explicitly or by being marked for truncate. We do - not want to wait here on an x_lock for an rseg in an undo tablespace - that is being truncated. So check this first without the latch. - It could be set immediately after this, but that is a very short gap - and the get_active() call below will use an rseg->s_lock. */ - if (!undo_space->is_active_no_latch()) { - continue; + if (use_no_latch) { + if (!undo::undo_spaces_snapshot->is_active_no_latch_for_undo_space(spaces_slot)) { + continue; + } + ut_ad(target_rollback_segments <= undo::undo_spaces_snapshot->get_rsegs_size_for_undo_space(spaces_slot)); + rseg = undo::undo_spaces_snapshot->get_active_for_undo_space(spaces_slot, rseg_slot); } + else { + undo_space = undo::spaces->at(spaces_slot); + + /* Avoid any rseg that resides in a tablespace that has been made + inactive either explicitly or by being marked for truncate. We do + not want to wait here on an x_lock for an rseg in an undo tablespace + that is being truncated. So check this first without the latch. + It could be set immediately after this, but that is a very short gap + and the get_active() call below will use an rseg->s_lock. */ + if (!undo_space->is_active_no_latch()) { + continue; + } - /* This is done here because we know the rsegs() pointer is good. */ - ut_ad(target_rollback_segments <= undo_space->rsegs()->size()); + /* This is done here because we know the rsegs() pointer is good. */ + ut_ad(target_rollback_segments <= undo_space->rsegs()->size()); - /* Check again with a shared lock. */ - rseg = undo_space->get_active(rseg_slot); + /* Check again with a shared lock. */ + rseg = undo_space->get_active(rseg_slot); + } } - undo::spaces->s_unlock(); + if (use_no_latch) { + undo::undo_spaces_snapshot->return_ticket(); + } + else { + undo::spaces->s_unlock(); + } ut_ad(rseg->trx_ref_count > 0); diff --git a/storage/innobase/trx/undo_spaces_snapshot.cc b/storage/innobase/trx/undo_spaces_snapshot.cc new file mode 100644 index 000000000000..c0232d597ef1 --- /dev/null +++ b/storage/innobase/trx/undo_spaces_snapshot.cc @@ -0,0 +1,104 @@ +#include "undo_spaces_snapshot.h" +#include +#include +#include "trx0purge.h" + +namespace undo { +Undo_spaces_snapshot::Undo_spaces_snapshot() + : m_max_tickets(0), + m_unused_tickets(0), + m_used_tickets(0), + m_tablespaces(), + m_is_depriving(false) {} + +Undo_spaces_snapshot::~Undo_spaces_snapshot() {} + +void Undo_spaces_snapshot::reset(const unsigned int max_tickets, + const std::vector &tablespaces) { + m_used_tickets.store(0u, std::memory_order_release); + m_tablespaces = tablespaces; + m_max_tickets = max_tickets; + m_is_depriving.store(false, std::memory_order_release); + // Set m_unused_tickets in the end, which request_ticket depends on + m_unused_tickets.store(m_max_tickets, std::memory_order_release); +} + +std::size_t Undo_spaces_snapshot::get_target_undo_tablespaces_size() const { + return m_tablespaces.size(); +} + +unsigned int Undo_spaces_snapshot::get_unused_tickets_number() const { + return m_unused_tickets.load(std::memory_order_acquire); +} + +bool Undo_spaces_snapshot::request_ticket() { + unsigned int current_tickets_number = 0; + while (m_is_depriving.load(std::memory_order_acquire) == false && + (current_tickets_number = get_unused_tickets_number()) > 0) { + MONITOR_INC_VALUE(MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_TRY_COUNT, 1); + if (m_unused_tickets.compare_exchange_strong(current_tickets_number, + current_tickets_number - 1, + std::memory_order_acq_rel)) { + MONITOR_INC_VALUE(MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_GRANT_COUNT, 1); + return true; + } + } + return false; +} + +void Undo_spaces_snapshot::return_ticket() { m_used_tickets.fetch_add(1); } + +void Undo_spaces_snapshot::block_until_tickets_returned() { + unsigned int deprived_tickets_number = 0; + bool tickets_cleaned = false; + m_is_depriving.store(true, std::memory_order_release); + while (!tickets_cleaned) { + deprived_tickets_number = get_unused_tickets_number(); + tickets_cleaned = m_unused_tickets.compare_exchange_strong( + deprived_tickets_number, 0, std::memory_order_acq_rel); + } + while (m_used_tickets.load(std::memory_order_acquire) + + deprived_tickets_number < + m_max_tickets) { + MONITOR_INC_VALUE(MONITOR_UNDO_TRUNCATE_SNAPSHOT_TICKET_WAIT_COUNT, 1) + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } +} + +unsigned int Undo_spaces_snapshot::get_max_tickets_number() const { + return m_max_tickets; +} + +undo::Tablespace *Undo_spaces_snapshot::at(size_t pos) const { + if (pos >= m_tablespaces.size()) { + return nullptr; + } + return m_tablespaces[pos]; +} + +bool Undo_spaces_snapshot::is_active_no_latch_for_undo_space(size_t pos) const { + auto undo_space = this->at(pos); + if (undo_space == nullptr) { + return false; + } + return undo_space->is_active_no_latch(); +} + +ulint Undo_spaces_snapshot::get_rsegs_size_for_undo_space(size_t pos) const { + auto undo_space = this->at(pos); + if (undo_space == nullptr) { + return 0; + } + return undo_space->rsegs()->size(); +} + +trx_rseg_t *Undo_spaces_snapshot::get_active_for_undo_space( + size_t pos, ulint rseg_slot) const { + auto undo_space = this->at(pos); + if (undo_space == nullptr) { + return nullptr; + } + return undo_space->get_active(rseg_slot); +} + +} /* namespace undo */ \ No newline at end of file diff --git a/unittest/gunit/innodb/CMakeLists.txt b/unittest/gunit/innodb/CMakeLists.txt index a5b232575fea..5e061fc88324 100644 --- a/unittest/gunit/innodb/CMakeLists.txt +++ b/unittest/gunit/innodb/CMakeLists.txt @@ -56,6 +56,7 @@ SET(TESTS ut0object_cache ut0rbt ut0rnd + undo_spaces_snapshot ) # Treat warnings as errors on MSVC for InnoDB's unittests diff --git a/unittest/gunit/innodb/undo_spaces_snapshot-t.cc b/unittest/gunit/innodb/undo_spaces_snapshot-t.cc new file mode 100644 index 000000000000..6a4f7de9bba4 --- /dev/null +++ b/unittest/gunit/innodb/undo_spaces_snapshot-t.cc @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "my_config.h" +#include "undo_spaces_snapshot.h" + + +namespace undo { + +class UndoSpacesSnapshotTest : public ::testing::Test { + protected: + void SetUp() override { + m_undo_spaces_snapshot = new undo::Undo_spaces_snapshot(); + for (auto i = 0u; i < 100; ++i) { + m_tablespaces.push_back(new undo::Tablespace((i + 1) * 10)); + } + } + + void TearDown() override { + for (auto i = 0u; i < m_tablespaces.size(); ++i) { + delete m_tablespaces[i]; + } + delete m_undo_spaces_snapshot; + m_undo_spaces_snapshot = nullptr; + } + + protected: + undo::Undo_spaces_snapshot *m_undo_spaces_snapshot; + std::vector m_tablespaces; +}; + +TEST_F(UndoSpacesSnapshotTest, DefaultConstructor) { + ASSERT_NE(m_undo_spaces_snapshot, nullptr); + ASSERT_EQ(m_undo_spaces_snapshot->get_unused_tickets_number(), 0); + ASSERT_EQ(m_undo_spaces_snapshot->get_target_undo_tablespaces_size(), 0); + ASSERT_EQ(m_undo_spaces_snapshot->at(0), nullptr); +} + +void request_ticket(undo::Undo_spaces_snapshot *undo_spaces_snapshot, + std::atomic &counter, std::mutex &mutex, + std::condition_variable &cv) { + { + std::unique_lock thread_lock(mutex); + cv.wait(thread_lock); + } + bool use_no_latch = undo_spaces_snapshot->request_ticket(); + if (use_no_latch) { + counter++; + std::this_thread::sleep_for(std::chrono::seconds(1)); + undo_spaces_snapshot->return_ticket(); + } +} + +TEST_F(UndoSpacesSnapshotTest, UseAllTickets) { + constexpr unsigned int ticket_number = 100; + m_undo_spaces_snapshot->reset(ticket_number, m_tablespaces); + ASSERT_EQ(m_undo_spaces_snapshot->get_unused_tickets_number(), ticket_number); + ASSERT_EQ(m_undo_spaces_snapshot->get_max_tickets_number(), ticket_number); + ASSERT_EQ(m_undo_spaces_snapshot->get_target_undo_tablespaces_size(), + m_tablespaces.size()); + ASSERT_NE(m_undo_spaces_snapshot->at(0), nullptr); + ASSERT_EQ(m_undo_spaces_snapshot->at(m_tablespaces.size()), nullptr); + + std::vector sessions; + std::atomic success_requested(0); + std::mutex mutex; + std::condition_variable cv; + for (auto i = 0u; i < ticket_number * 2; ++i) { + sessions.push_back(std::thread(&request_ticket, m_undo_spaces_snapshot, + std::ref(success_requested), std::ref(mutex), + std::ref(cv))); + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(success_requested.load(), 0); + cv.notify_all(); + for (auto i = 0u; i < sessions.size(); ++i) { + sessions[i].join(); + } + m_undo_spaces_snapshot->block_until_tickets_returned(); + ASSERT_EQ(success_requested.load(), ticket_number); + ASSERT_EQ(m_undo_spaces_snapshot->get_unused_tickets_number(), 0); +} + +TEST_F(UndoSpacesSnapshotTest, UseSomeTickets) { + constexpr unsigned int ticket_number = 100; + m_undo_spaces_snapshot->reset(ticket_number, m_tablespaces); + ASSERT_EQ(m_undo_spaces_snapshot->get_unused_tickets_number(), ticket_number); + ASSERT_EQ(m_undo_spaces_snapshot->get_max_tickets_number(), ticket_number); + ASSERT_EQ(m_undo_spaces_snapshot->get_target_undo_tablespaces_size(), + m_tablespaces.size()); + ASSERT_NE(m_undo_spaces_snapshot->at(0), nullptr); + ASSERT_EQ(m_undo_spaces_snapshot->at(m_tablespaces.size()), nullptr); + + std::vector sessions; + std::atomic success_requested(0); + std::mutex mutex; + std::condition_variable cv; + for (auto i = 0u; i < ticket_number / 2; ++i) { + sessions.push_back(std::thread(&request_ticket, m_undo_spaces_snapshot, + std::ref(success_requested), std::ref(mutex), + std::ref(cv))); + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(success_requested.load(), 0); + cv.notify_all(); + for (auto i = 0u; i < sessions.size(); ++i) { + sessions[i].join(); + } + m_undo_spaces_snapshot->block_until_tickets_returned(); + ASSERT_EQ(success_requested.load(), ticket_number / 2); + ASSERT_EQ(m_undo_spaces_snapshot->get_unused_tickets_number(), 0); +} + +} \ No newline at end of file