Commit f8c88d90 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-33213 History list is not shrunk unless there is a pause in the workload

The parameter innodb_undo_log_truncate=ON enables a multi-phased logic:
1. Any "producers" (new starting transactions) are prohibited
from using the rollback segments that reside in the undo tablespace.
2. Any transactions that use any of the rollback segments must be
committed or aborted.
3. The purge of committed transaction history must process all the
rollback segments.
4. The undo tablespace is truncated and rebuilt.
5. The rollback segments are re-enabled for new transactions.

There was one flaw in this logic: The first step was not being invoked
as often as it could be, and therefore innodb_undo_log_truncate=ON
would have no chance to work during a heavy write workload.

Independent of innodb_undo_log_truncate, even after
commit 86767bcc
we are missing some chances to free processed undo log pages.
If we prohibited the creation of new transactions in one busy
rollback segment at a time, we would be eventually guaranteed
to be able to free such pages.

purge_sys_t::skipped_rseg: The current candidate rollback segment
for shrinking the history independent of innodb_undo_log_truncate.

purge_sys_t::iterator::free_history_rseg(): Renamed from
trx_purge_truncate_rseg_history(). Implement the logic
around purge_sys.m_skipped_rseg.

purge_sys_t::truncate_undo_space: Renamed from truncate.

purge_sys.truncate_undo_space.last: Changed the type to integer
to get rid of some pointer dereferencing and conditional branches.

purge_sys_t::truncating_tablespace(), purge_sys_t::undo_truncate_try():
Refactored from trx_purge_truncate_history().
Set purge_sys.truncate_undo_space.current if applicable,
or return an already set purge_sys.truncate_undo_space.current.

purge_coordinator_state::do_purge(): Invoke
purge_sys_t::truncating_tablespace() as part of the normal work loop,
to implement innodb_undo_log_truncate=ON as often as possible.

trx_purge_truncate_rseg_history(): Remove a redundant parameter.

trx_undo_truncate_start(): Replace dead code with a debug assertion.

Correctness tested by: Matthias Leich
Performance tested by: Axel Schwenke
Reviewed by: Debarun Banerjee
parent 931df937
......@@ -140,6 +140,15 @@ class purge_sys_t
bool m_initialized{false};
/** whether purge is enabled; protected by latch and std::atomic */
std::atomic<bool> m_enabled{false};
/** The primary candidate for iterator::free_history() is
rseg=trx_sys.rseg_array[skipped_rseg]. This field may be changed
after invoking rseg.set_skip_allocation() and rseg.clear_skip_allocation()
and while holding the exclusive rseg.latch.
This may only be 0 if innodb_undo_tablespaces=0, because rollback segment
0 always resides in the system tablespace and would never be used when
dedicated undo tablespaces are in use. */
Atomic_relaxed<uint8_t> skipped_rseg;
public:
/** whether purge is active (may hold table handles) */
std::atomic<bool> m_active{false};
......@@ -197,6 +206,11 @@ class purge_sys_t
return undo_no <= other.undo_no;
}
/** Remove unnecessary history data from a rollback segment.
@param rseg rollback segment
@return error code */
inline dberr_t free_history_rseg(trx_rseg_t &rseg) const;
/** Free the undo pages up to this. */
dberr_t free_history() const;
......@@ -240,14 +254,15 @@ class purge_sys_t
by the pq_mutex */
mysql_mutex_t pq_mutex; /*!< Mutex protecting purge_queue */
/** Undo tablespace file truncation (only accessed by the
srv_purge_coordinator_thread) */
struct {
/** The undo tablespace that is currently being truncated */
fil_space_t* current;
/** The undo tablespace that was last truncated */
fil_space_t* last;
} truncate;
/** innodb_undo_log_truncate=ON state;
only modified by purge_coordinator_callback() */
struct {
/** The undo tablespace that is currently being truncated */
Atomic_relaxed<fil_space_t*> current;
/** The number of the undo tablespace that was last truncated,
relative from srv_undo_space_id_start */
ulint last;
} truncate_undo_space;
/** Create the instance */
void create();
......@@ -357,6 +372,26 @@ class purge_sys_t
typically via purge_sys_t::view_guard. */
return view.sees(id);
}
private:
/** Enable the use of a rollback segment and advance skipped_rseg,
after iterator::free_history_rseg() had invoked
rseg.set_skip_allocation(). */
inline void rseg_enable(trx_rseg_t &rseg);
/** Try to start truncating a tablespace.
@param id undo tablespace identifier
@param size the maximum desired undo tablespace size, in pages
@return undo tablespace whose truncation was started
@retval nullptr if truncation is not currently possible */
inline fil_space_t *undo_truncate_try(ulint id, ulint size);
public:
/** Check if innodb_undo_log_truncate=ON needs to be handled.
This is only to be called by purge_coordinator_callback().
@return undo tablespace chosen by innodb_undo_log_truncate=ON
@retval nullptr if truncation is not currently possible */
fil_space_t *truncating_tablespace();
/** A wrapper around trx_sys_t::clone_oldest_view(). */
template<bool also_end_view= false>
void clone_oldest_view()
......
......@@ -73,14 +73,15 @@ struct alignas(CPU_LEVEL1_DCACHE_LINESIZE) trx_rseg_t
/** Reference counter to track is_persistent() transactions,
with SKIP flag. */
std::atomic<uint32_t> ref;
public:
/** Whether undo tablespace truncation is pending */
static constexpr uint32_t SKIP= 1;
/** Transaction reference count multiplier */
static constexpr uint32_t REF= 2;
/** @return the reference count and flags */
uint32_t ref_load() const { return ref.load(std::memory_order_relaxed); }
private:
/** Set the SKIP bit */
void ref_set_skip()
{
......
......@@ -1189,6 +1189,11 @@ class trx_sys_t
return count;
}
/** Disable further allocation of transactions in a rollback segment
that are subject to innodb_undo_log_truncate=ON
@param space undo tablespace that will be truncated */
inline void undo_truncate_start(fil_space_t &space);
private:
static my_bool find_same_or_older_callback(rw_trx_hash_element_t *element,
trx_id_t *id)
......
......@@ -1638,7 +1638,8 @@ inline void purge_coordinator_state::do_purge()
ulint n_pages_handled= trx_purge(n_threads, history_size);
if (!trx_sys.history_exists())
goto no_history;
if (purge_sys.truncate.current || srv_shutdown_state != SRV_SHUTDOWN_NONE)
if (purge_sys.truncating_tablespace() ||
srv_shutdown_state != SRV_SHUTDOWN_NONE)
{
purge_truncation_task.wait();
trx_purge_truncate_history();
......
This diff is collapsed.
......@@ -907,15 +907,13 @@ trx_undo_truncate_start(
trx_undo_rec_t* last_rec;
mtr_t mtr;
ut_ad(rseg->is_persistent());
if (!limit) {
return DB_SUCCESS;
}
loop:
mtr_start(&mtr);
if (!rseg->is_persistent()) {
mtr.set_log_mode(MTR_LOG_NO_REDO);
}
mtr.start();
dberr_t err;
const buf_block_t* undo_page;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment