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

MDEV-26445 innodb_undo_log_truncate is unnecessarily slow

trx_purge_truncate_history(): Do not force a write of the undo tablespace
that is being truncated. Instead, prevent page writes by acquiring
an exclusive latch on all dirty pages of the tablespace.

fseg_create(): Relax an assertion that could fail if a dirty undo page
is being initialized during undo tablespace truncation (and
trx_purge_truncate_history() already acquired an exclusive latch on it).

fsp_page_create(): If we are truncating a tablespace, try to reuse
a page that we may have already latched exclusively (because it was
in buf_pool.flush_list). To some extent, this helps the test
innodb.undo_truncate,16k to avoid running out of buffer pool.

mtr_t::commit_shrink(): Mark as clean all pages that are outside the
new bounds of the tablespace, and only add the newly reinitialized pages
to the buf_pool.flush_list.

buf_page_create(): Do not unnecessarily invoke change buffer merge on
undo tablespaces.

buf_page_t::clear_oldest_modification(bool temporary): Move some
assertions to the caller buf_page_write_complete().

innodb.undo_truncate: Use a bigger innodb_buffer_pool_size=24M.
On my system, it would otherwise hang 1 out of 1547 attempts
(on the 40th repeat of innodb.undo_truncate,16k).
Other page sizes were not affected.
parent f5fddae3
......@@ -3,6 +3,10 @@
--source include/have_undo_tablespaces.inc
--source include/have_sequence.inc
--disable_query_log
call mtr.add_suppression("InnoDB: Difficult to find free blocks in the buffer pool");
--enable_query_log
SET @save_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET @save_truncate = @@GLOBAL.innodb_undo_log_truncate;
SET GLOBAL innodb_undo_log_truncate = 0;
......@@ -46,17 +50,5 @@ drop table t1, t2;
--source include/wait_all_purged.inc
# Truncation will normally not occur with innodb_page_size=64k,
# and occasionally not with innodb_page_size=32k,
# because the undo log will not grow enough.
# TODO: For some reason this does not occur on 4k either!
if (`select @@innodb_page_size IN (8192,16384)`)
{
let $wait_condition = (SELECT variable_value!=@trunc_start
FROM information_schema.global_status
WHERE variable_name = 'innodb_undo_truncations');
source include/wait_condition.inc;
}
SET GLOBAL innodb_purge_rseg_truncate_frequency = @save_frequency;
SET GLOBAL innodb_undo_log_truncate = @save_truncate;
......@@ -3796,6 +3796,7 @@ buf_page_create(fil_space_t *space, uint32_t offset,
/* Delete possible entries for the page from the insert buffer:
such can exist if the page belonged to an index which was dropped */
if (page_id < page_id_t{SRV_SPACE_ID_UPPER_BOUND, 0} &&
!srv_is_undo_tablespace(page_id.space()) &&
!recv_recovery_is_on())
ibuf_merge_or_delete_for_page(nullptr, page_id, zip_size);
......
......@@ -363,10 +363,12 @@ void buf_page_write_complete(const IORequest &request)
const bool temp= fsp_is_system_temporary(bpage->id().space());
mysql_mutex_lock(&buf_pool.mutex);
mysql_mutex_assert_not_owner(&buf_pool.flush_list_mutex);
buf_pool.stat.n_pages_written++;
/* While we do not need any mutex for clearing oldest_modification
here, we hope that it will be in the same cache line with io_fix,
whose changes must be protected by buf_pool.mutex. */
ut_ad(temp || bpage->oldest_modification() > 2);
bpage->clear_oldest_modification(temp);
ut_ad(bpage->io_fix() == BUF_IO_WRITE);
bpage->set_io_fix(BUF_IO_NONE);
......
......@@ -1054,11 +1054,36 @@ static
buf_block_t*
fsp_page_create(fil_space_t *space, page_no_t offset, mtr_t *mtr)
{
buf_block_t *free_block= buf_LRU_get_free_block(false);
buf_block_t *block= buf_page_create(space, static_cast<uint32_t>(offset),
buf_block_t *block, *free_block;
if (UNIV_UNLIKELY(space->is_being_truncated))
{
const page_id_t page_id{space->id, offset};
const ulint fold= page_id.fold();
mysql_mutex_lock(&buf_pool.mutex);
block= reinterpret_cast<buf_block_t*>
(buf_pool.page_hash_get_low(page_id, fold));
if (block && block->page.oldest_modification() <= 1)
block= nullptr;
mysql_mutex_unlock(&buf_pool.mutex);
if (block)
{
ut_ad(block->page.buf_fix_count() >= 1);
ut_ad(rw_lock_get_x_lock_count(&block->lock) == 1);
ut_ad(mtr->have_x_latch(*block));
free_block= block;
goto got_free_block;
}
}
free_block= buf_LRU_get_free_block(false);
got_free_block:
block= buf_page_create(space, static_cast<uint32_t>(offset),
space->zip_size(), mtr, free_block);
if (UNIV_UNLIKELY(block != free_block))
buf_pool.free_block(free_block);
fsp_init_file_page(space, block, mtr);
return block;
}
......@@ -1728,7 +1753,10 @@ fseg_create(fil_space_t *space, ulint byte_offset, mtr_t *mtr,
goto funct_exit;
}
ut_ad(rw_lock_get_x_lock_count(&block->lock) == 1);
ut_d(const auto x = rw_lock_get_x_lock_count(&block->lock));
ut_ad(x > 0);
ut_ad(x == 1 || space->is_being_truncated);
ut_ad(x <= 2);
ut_ad(!fil_page_get_type(block->frame));
mtr->write<1>(*block, FIL_PAGE_TYPE + 1 + block->frame,
FIL_PAGE_TYPE_SYS);
......
......@@ -2232,9 +2232,7 @@ inline void buf_page_t::clear_oldest_modification()
it from buf_pool.flush_list */
inline void buf_page_t::clear_oldest_modification(bool temporary)
{
mysql_mutex_assert_not_owner(&buf_pool.flush_list_mutex);
ut_ad(temporary == fsp_is_system_temporary(id().space()));
ut_ad(io_fix_ == BUF_IO_WRITE);
if (temporary)
{
ut_ad(oldest_modification() == 2);
......
......@@ -459,15 +459,15 @@ void mtr_t::commit()
release_resources();
}
#ifdef UNIV_DEBUG
/** Check that all pages belong to a shrunk tablespace. */
/** Shrink a tablespace. */
struct Shrink
{
const page_id_t low, high;
Shrink(const fil_space_t &space) :
low({space.id, 0}), high({space.id, space.size}) {}
/** the first non-existing page in the tablespace */
const page_id_t high;
bool operator()(const mtr_memo_slot_t *slot) const
Shrink(const fil_space_t &space) : high({space.id, space.size}) {}
bool operator()(mtr_memo_slot_t *slot) const
{
if (!slot->object)
return true;
......@@ -476,29 +476,31 @@ struct Shrink
ut_ad("invalid type" == 0);
return false;
case MTR_MEMO_SPACE_X_LOCK:
ut_ad(low.space() == static_cast<fil_space_t*>(slot->object)->id);
ut_ad(high.space() == static_cast<fil_space_t*>(slot->object)->id);
return true;
case MTR_MEMO_PAGE_X_MODIFY:
case MTR_MEMO_PAGE_SX_MODIFY:
case MTR_MEMO_PAGE_X_FIX:
case MTR_MEMO_PAGE_SX_FIX:
const auto &bpage= static_cast<buf_block_t*>(slot->object)->page;
auto &bpage= static_cast<buf_block_t*>(slot->object)->page;
ut_ad(bpage.io_fix() == BUF_IO_NONE);
const auto id= bpage.id();
if (id == page_id_t{0, TRX_SYS_PAGE_NO})
if (id < high)
{
ut_ad(srv_is_undo_tablespace(low.space()));
ut_ad(id.space() == high.space() ||
(id == page_id_t{0, TRX_SYS_PAGE_NO} &&
srv_is_undo_tablespace(high.space())));
break;
}
ut_ad(id >= low);
ut_ad(id < high);
ut_ad(id.space() == high.space());
ut_ad(bpage.state() == BUF_BLOCK_FILE_PAGE);
ut_ad(bpage.oldest_modification() <= 1);
break;
if (bpage.oldest_modification() > 1)
bpage.clear_oldest_modification(false);
slot->type= static_cast<mtr_memo_type_t>(slot->type & ~MTR_MEMO_MODIFY);
}
return true;
}
};
#endif
/** Commit a mini-transaction that is shrinking a tablespace.
@param space tablespace that is being shrunk */
......@@ -542,7 +544,7 @@ void mtr_t::commit_shrink(fil_space_t &space)
else
ut_ad(!m_freed_space);
ut_d(m_memo.for_each_block_in_reverse(CIterate<Shrink>{space}));
m_memo.for_each_block_in_reverse(CIterate<Shrink>{space});
m_memo.for_each_block_in_reverse(CIterate<const ReleaseBlocks>
(ReleaseBlocks(start_lsn, m_commit_lsn,
......
This diff is collapsed.
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