Commit 86767bcc authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-29593 Purge misses a chance to free not-yet-reused undo pages

trx_purge_truncate_rseg_history(): If all other conditions for
invoking trx_purge_remove_log_hdr() hold, but the state is
TRX_UNDO_CACHED instead of TRX_UNDO_TO_PURGE, detach and free it.

Tested by: Matthias Leich
parent 40eff3f8
...@@ -343,7 +343,7 @@ struct mtr_t { ...@@ -343,7 +343,7 @@ struct mtr_t {
{ {
mtr_memo_slot_t &slot= m_memo[savepoint]; mtr_memo_slot_t &slot= m_memo[savepoint];
ut_ad(slot.type <= MTR_MEMO_BUF_FIX); ut_ad(slot.type <= MTR_MEMO_BUF_FIX);
ut_ad(type <= MTR_MEMO_BUF_FIX); ut_ad(type < MTR_MEMO_S_LOCK);
slot.type= type; slot.type= type;
} }
......
...@@ -275,7 +275,7 @@ trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr) ...@@ -275,7 +275,7 @@ trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr)
if (undo->state != TRX_UNDO_CACHED) { if (undo->state != TRX_UNDO_CACHED) {
/* The undo log segment will not be reused */ /* The undo log segment will not be reused */
ut_a(undo->id < TRX_RSEG_N_SLOTS); ut_a(undo->id < TRX_RSEG_N_SLOTS);
compile_time_assert(FIL_NULL == 0xffffffff); static_assert(FIL_NULL == 0xffffffff, "");
mtr->memset(rseg_header, mtr->memset(rseg_header,
TRX_RSEG + TRX_RSEG_UNDO_SLOTS TRX_RSEG + TRX_RSEG_UNDO_SLOTS
+ undo->id * TRX_RSEG_SLOT_SIZE, 4, 0xff); + undo->id * TRX_RSEG_SLOT_SIZE, 4, 0xff);
...@@ -385,45 +385,11 @@ static dberr_t trx_purge_remove_log_hdr(buf_block_t *rseg, buf_block_t* log, ...@@ -385,45 +385,11 @@ static dberr_t trx_purge_remove_log_hdr(buf_block_t *rseg, buf_block_t* log,
uint16_t(offset + TRX_UNDO_HISTORY_NODE), mtr); uint16_t(offset + TRX_UNDO_HISTORY_NODE), mtr);
} }
MY_ATTRIBUTE((nonnull, warn_unused_result)) /** Free an undo log segment.
/** Free an undo log segment, and remove the header from the history list. @param block rollback segment header page
@param[in,out] mtr mini-transaction @param mtr mini-transaction */
@param[in,out] rseg rollback segment static void trx_purge_free_segment(buf_block_t *block, mtr_t &mtr)
@param[in] hdr_addr file address of log_hdr
@return error code */
static dberr_t
trx_purge_free_segment(mtr_t &mtr, trx_rseg_t* rseg, fil_addr_t hdr_addr)
{ {
mtr.commit();
log_free_check();
mtr.start();
const page_id_t hdr_page_id{rseg->space->id, hdr_addr.page};
dberr_t err;
buf_block_t *rseg_hdr= rseg->get(&mtr, &err);
if (!rseg_hdr)
return err;
buf_block_t *block= buf_page_get_gen(hdr_page_id, 0, RW_X_LATCH,
nullptr, BUF_GET_POSSIBLY_FREED,
&mtr, &err);
if (!block)
return err;
const uint32_t seg_size=
flst_get_len(TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST + block->page.frame);
err= trx_purge_remove_log_hdr(rseg_hdr, block, hdr_addr.boffset, &mtr);
if (UNIV_UNLIKELY(err != DB_SUCCESS))
return err;
ut_ad(rseg->curr_size >= seg_size);
rseg->curr_size-= seg_size;
rseg->history_size--;
byte *hist= TRX_RSEG + TRX_RSEG_HISTORY_SIZE + rseg_hdr->page.frame;
ut_ad(mach_read_from_4(hist) >= seg_size);
mtr.write<4>(*rseg_hdr, hist, mach_read_from_4(hist) - seg_size);
while (!fseg_free_step_not_header(TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER + while (!fseg_free_step_not_header(TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER +
block->page.frame, &mtr)) block->page.frame, &mtr))
{ {
...@@ -444,9 +410,9 @@ trx_purge_free_segment(mtr_t &mtr, trx_rseg_t* rseg, fil_addr_t hdr_addr) ...@@ -444,9 +410,9 @@ trx_purge_free_segment(mtr_t &mtr, trx_rseg_t* rseg, fil_addr_t hdr_addr)
{ {
block->unfix(); block->unfix();
block->page.lock.x_unlock(); block->page.lock.x_unlock();
block= buf_page_get_gen(id, 0, RW_X_LATCH, nullptr, BUF_GET, &mtr, &err); block= buf_page_get_gen(id, 0, RW_X_LATCH, nullptr, BUF_GET, &mtr);
if (!block) if (!block)
return err; return;
} }
else else
mtr.memo_push(block, MTR_MEMO_PAGE_X_MODIFY); mtr.memo_push(block, MTR_MEMO_PAGE_X_MODIFY);
...@@ -454,100 +420,133 @@ trx_purge_free_segment(mtr_t &mtr, trx_rseg_t* rseg, fil_addr_t hdr_addr) ...@@ -454,100 +420,133 @@ trx_purge_free_segment(mtr_t &mtr, trx_rseg_t* rseg, fil_addr_t hdr_addr)
while (!fseg_free_step(TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER + while (!fseg_free_step(TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER +
block->page.frame, &mtr)); block->page.frame, &mtr));
return DB_SUCCESS;
} }
/** Remove unnecessary history data from a rollback segment. /** Remove unnecessary history data from a rollback segment.
@param[in,out] rseg rollback segment @param[in,out] rseg rollback segment
@param[in] limit truncate anything before this @param[in] limit truncate anything before this
@return error code */ @return error code */
static static dberr_t
dberr_t trx_purge_truncate_rseg_history(trx_rseg_t& rseg,
trx_purge_truncate_rseg_history(
trx_rseg_t& rseg,
const purge_sys_t::iterator& limit) const purge_sys_t::iterator& limit)
{ {
fil_addr_t hdr_addr; fil_addr_t hdr_addr;
mtr_t mtr; mtr_t mtr;
log_free_check();
mtr.start(); mtr.start();
dberr_t err; dberr_t err;
buf_block_t* rseg_hdr = rseg.get(&mtr, &err); reget:
if (!rseg_hdr) { buf_block_t *rseg_hdr= rseg.get(&mtr, &err);
goto func_exit; if (!rseg_hdr)
} {
hdr_addr = flst_get_last(TRX_RSEG + TRX_RSEG_HISTORY
+ rseg_hdr->page.frame);
hdr_addr.boffset = static_cast<uint16_t>(hdr_addr.boffset
- TRX_UNDO_HISTORY_NODE);
loop:
if (hdr_addr.page == FIL_NULL) {
func_exit: func_exit:
mtr.commit(); mtr.commit();
return err; return err;
} }
buf_block_t* block = buf_page_get_gen(page_id_t(rseg.space->id, hdr_addr= flst_get_last(TRX_RSEG + TRX_RSEG_HISTORY + rseg_hdr->page.frame);
hdr_addr.page), hdr_addr.boffset= static_cast<uint16_t>(hdr_addr.boffset -
0, RW_X_LATCH, nullptr, TRX_UNDO_HISTORY_NODE);
BUF_GET_POSSIBLY_FREED,
loop:
if (hdr_addr.page == FIL_NULL)
goto func_exit;
buf_block_t *b=
buf_page_get_gen(page_id_t(rseg.space->id, hdr_addr.page),
0, RW_X_LATCH, nullptr, BUF_GET_POSSIBLY_FREED,
&mtr, &err); &mtr, &err);
if (!block) { if (!b)
goto func_exit; goto func_exit;
}
const trx_id_t undo_trx_no = mach_read_from_8( const trx_id_t undo_trx_no=
block->page.frame + hdr_addr.boffset + TRX_UNDO_TRX_NO); mach_read_from_8(b->page.frame + hdr_addr.boffset + TRX_UNDO_TRX_NO);
if (undo_trx_no >= limit.trx_no) { if (undo_trx_no >= limit.trx_no)
if (undo_trx_no == limit.trx_no) { {
err = trx_undo_truncate_start( if (undo_trx_no == limit.trx_no)
&rseg, hdr_addr.page, err = trx_undo_truncate_start(&rseg, hdr_addr.page,
hdr_addr.boffset, limit.undo_no); hdr_addr.boffset, limit.undo_no);
goto func_exit;
} }
fil_addr_t prev_hdr_addr=
flst_get_prev_addr(b->page.frame + hdr_addr.boffset +
TRX_UNDO_HISTORY_NODE);
prev_hdr_addr.boffset= static_cast<uint16_t>(prev_hdr_addr.boffset -
TRX_UNDO_HISTORY_NODE);
err= trx_purge_remove_log_hdr(rseg_hdr, b, hdr_addr.boffset, &mtr);
if (UNIV_UNLIKELY(err != DB_SUCCESS))
goto func_exit; goto func_exit;
}
fil_addr_t prev_hdr_addr = flst_get_prev_addr( rseg_hdr->fix();
block->page.frame + hdr_addr.boffset + TRX_UNDO_HISTORY_NODE);
prev_hdr_addr.boffset = static_cast<uint16_t>(prev_hdr_addr.boffset
- TRX_UNDO_HISTORY_NODE);
if (!rseg.is_referenced() if (mach_read_from_2(b->page.frame + hdr_addr.boffset + TRX_UNDO_NEXT_LOG) ||
&& rseg.needs_purge <= (purge_sys.head.trx_no rseg.is_referenced() ||
rseg.needs_purge > (purge_sys.head.trx_no
? purge_sys.head.trx_no ? purge_sys.head.trx_no
: purge_sys.tail.trx_no) : purge_sys.tail.trx_no))
&& mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE /* We cannot free the entire undo page. */;
+ block->page.frame) else
== TRX_UNDO_TO_PURGE {
&& !mach_read_from_2(block->page.frame + hdr_addr.boffset const uint32_t seg_size=
+ TRX_UNDO_NEXT_LOG)) { flst_get_len(TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST + b->page.frame);
/* We can free the whole log segment. switch (mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE +
This will call trx_purge_remove_log_hdr(). */ b->page.frame)) {
err = trx_purge_free_segment(mtr, &rseg, hdr_addr); case TRX_UNDO_TO_PURGE:
} else { {
/* Remove the log hdr from the rseg history. */ byte *hist= TRX_RSEG + TRX_RSEG_HISTORY_SIZE + rseg_hdr->page.frame;
rseg.history_size--; ut_ad(mach_read_from_4(hist) >= seg_size);
err = trx_purge_remove_log_hdr(rseg_hdr, block, mtr.write<4>(*rseg_hdr, hist, mach_read_from_4(hist) - seg_size);
hdr_addr.boffset, &mtr); }
free_segment:
ut_ad(rseg.curr_size >= seg_size);
rseg.curr_size-= seg_size;
trx_purge_free_segment(b, mtr);
break;
case TRX_UNDO_CACHED:
/* rseg.undo_cached must point to this page */
trx_undo_t *undo= UT_LIST_GET_FIRST(rseg.undo_cached);
for (; undo; undo= UT_LIST_GET_NEXT(undo_list, undo))
if (undo->hdr_page_no == hdr_addr.page)
goto found_cached;
ut_ad("inconsistent undo logs" == 0);
break;
found_cached:
UT_LIST_REMOVE(rseg.undo_cached, undo);
static_assert(FIL_NULL == 0xffffffff, "");
if (UNIV_UNLIKELY(mach_read_from_4(TRX_RSEG + TRX_RSEG_FORMAT +
rseg_hdr->page.frame)))
trx_rseg_format_upgrade(rseg_hdr, &mtr);
mtr.memset(rseg_hdr, TRX_RSEG + TRX_RSEG_UNDO_SLOTS +
undo->id * TRX_RSEG_SLOT_SIZE, 4, 0xff);
ut_free(undo);
mtr.write<8,mtr_t::MAYBE_NOP>(*rseg_hdr, TRX_RSEG + TRX_RSEG_MAX_TRX_ID +
rseg_hdr->page.frame,
trx_sys.get_max_trx_id() - 1);
MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_USED);
goto free_segment;
} }
mtr.commit();
if (err != DB_SUCCESS) {
return err;
} }
mtr.start();
hdr_addr = prev_hdr_addr; hdr_addr= prev_hdr_addr;
rseg_hdr = rseg.get(&mtr, &err); mtr.commit();
if (!rseg_hdr) { ut_ad(rseg.history_size > 0);
goto func_exit; rseg.history_size--;
log_free_check();
mtr.start();
rseg_hdr->page.lock.x_lock();
if (UNIV_UNLIKELY(rseg_hdr->page.id() != rseg.page_id()))
{
rseg_hdr->unfix();
rseg_hdr->page.lock.x_unlock();
goto reget;
} }
mtr.memo_push(rseg_hdr, MTR_MEMO_PAGE_X_MODIFY);
goto loop; goto loop;
} }
......
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