Skip to content

Commit

Permalink
Merge branch 'gtid_table_garbage_rows' into gtid_table_garbage_rows_10.3
Browse files Browse the repository at this point in the history
  • Loading branch information
knielsen committed Oct 7, 2018
2 parents 15c7225 + 2f4a0c5 commit 3eb2c46
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 21 deletions.
8 changes: 8 additions & 0 deletions mysql-test/suite/rpl/r/rpl_parallel_optimistic.result
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,12 @@ SET GLOBAL slave_parallel_threads=@old_parallel_threads;
include/start_slave.inc
connection server_1;
DROP TABLE t1, t2, t3;
include/save_master_gtid.inc
connection server_2;
include/sync_with_master_gtid.inc
Check that no more than the expected last four GTIDs are in mysql.gtid_slave_pos
select count(4) <= 4 from mysql.gtid_slave_pos order by domain_id, sub_id;
count(4) <= 4
1
connection server_1;
include/rpl_end.inc
24 changes: 24 additions & 0 deletions mysql-test/suite/rpl/t/rpl_parallel_optimistic.test
Original file line number Diff line number Diff line change
Expand Up @@ -553,5 +553,29 @@ SET GLOBAL slave_parallel_threads=@old_parallel_threads;

--connection server_1
DROP TABLE t1, t2, t3;
--source include/save_master_gtid.inc

--connection server_2
--source include/sync_with_master_gtid.inc
# Check for left-over rows in table mysql.gtid_slave_pos (MDEV-12147).
#
# There was a bug when a transaction got a conflict and was rolled back. It
# might have also handled deletion of some old rows, and these deletions would
# then also be rolled back. And since the deletes were never re-tried, old no
# longer needed rows would accumulate in the table without limit.
#
# The earlier part of this test file have plenty of transactions being rolled
# back. But the last DROP TABLE statement runs on its own and should never
# conflict, thus at this point the mysql.gtid_slave_pos table should be clean.
#
# To support @@gtid_pos_auto_engines, when a row is inserted in the table, it
# is associated with the engine of the table at insertion time, and it will
# only be deleted during record_gtid from a table of the same engine. Since we
# alter the table from MyISAM to InnoDB at the start of this test, we should
# end up with 4 rows: two left-over from when the table was MyISAM, and two
# left-over from the InnoDB part.
--echo Check that no more than the expected last four GTIDs are in mysql.gtid_slave_pos
select count(4) <= 4 from mysql.gtid_slave_pos order by domain_id, sub_id;

--connection server_1
--source include/rpl_end.inc
6 changes: 3 additions & 3 deletions sql/log_event.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5565,7 +5565,7 @@ int Query_log_event::do_apply_event(rpl_group_info *rgi,
gtid= rgi->current_gtid;
if (unlikely(rpl_global_gtid_slave_state->record_gtid(thd, &gtid,
sub_id,
true, false,
rgi, false,
&hton)))
{
int errcode= thd->get_stmt_da()->sql_errno();
Expand Down Expand Up @@ -8362,7 +8362,7 @@ Gtid_list_log_event::do_apply_event(rpl_group_info *rgi)
{
if ((ret= rpl_global_gtid_slave_state->record_gtid(thd, &list[i],
sub_id_list[i],
false, false, &hton)))
NULL, false, &hton)))
return ret;
rpl_global_gtid_slave_state->update_state_hash(sub_id_list[i], &list[i],
hton, NULL);
Expand Down Expand Up @@ -8899,7 +8899,7 @@ int Xid_log_event::do_apply_event(rpl_group_info *rgi)
rgi->gtid_pending= false;

gtid= rgi->current_gtid;
err= rpl_global_gtid_slave_state->record_gtid(thd, &gtid, sub_id, true,
err= rpl_global_gtid_slave_state->record_gtid(thd, &gtid, sub_id, rgi,
false, &hton);
if (unlikely(err))
{
Expand Down
58 changes: 41 additions & 17 deletions sql/rpl_gtid.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ rpl_slave_state::record_and_update_gtid(THD *thd, rpl_group_info *rgi)
rgi->gtid_pending= false;
if (rgi->gtid_ignore_duplicate_state!=rpl_group_info::GTID_DUPLICATE_IGNORE)
{
if (record_gtid(thd, &rgi->current_gtid, sub_id, false, false, &hton))
if (record_gtid(thd, &rgi->current_gtid, sub_id, NULL, false, &hton))
DBUG_RETURN(1);
update_state_hash(sub_id, &rgi->current_gtid, hton, rgi);
}
Expand Down Expand Up @@ -331,6 +331,8 @@ rpl_slave_state::update(uint32 domain_id, uint32 server_id, uint64 sub_id,
}
}
rgi->gtid_ignore_duplicate_state= rpl_group_info::GTID_DUPLICATE_NULL;

rgi->pending_gtid_deletes_clear();
}

if (!(list_elem= (list_element *)my_malloc(sizeof(*list_elem), MYF(MY_WME))))
Expand Down Expand Up @@ -381,15 +383,24 @@ int
rpl_slave_state::put_back_list(uint32 domain_id, list_element *list)
{
element *e;
int err= 0;

mysql_mutex_lock(&LOCK_slave_state);
if (!(e= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0)))
return 1;
{
err= 1;
goto end;
}
while (list)
{
list_element *next= list->next;
e->add(list);
list= next;
}
return 0;

end:
mysql_mutex_unlock(&LOCK_slave_state);
return err;
}


Expand Down Expand Up @@ -559,20 +570,20 @@ rpl_slave_state::select_gtid_pos_table(THD *thd, LEX_CSTRING *out_tablename)
/*
Write a gtid to the replication slave state table.
Do it as part of the transaction, to get slave crash safety, or as a separate
transaction if !in_transaction (eg. MyISAM or DDL).
gtid The global transaction id for this event group.
sub_id Value allocated within the sub_id when the event group was
read (sub_id must be consistent with commit order in master binlog).
rgi rpl_group_info context, if we are recording the gtid transactionally
as part of replicating a transactional event. NULL if called from
outside of a replicated transaction.
Note that caller must later ensure that the new gtid and sub_id is inserted
into the appropriate HASH element with rpl_slave_state.add(), so that it can
be deleted later. But this must only be done after COMMIT if in transaction.
*/
int
rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
bool in_transaction, bool in_statement,
rpl_group_info *rgi, bool in_statement,
void **out_hton)
{
TABLE_LIST tlist;
Expand Down Expand Up @@ -671,7 +682,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
thd->wsrep_ignore_table= true;
#endif

if (!in_transaction)
if (!rgi)
{
DBUG_PRINT("info", ("resetting OPTION_BEGIN"));
thd->variables.option_bits&=
Expand Down Expand Up @@ -776,7 +787,8 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
table->file->print_error(err, MYF(0));
goto end;
}
while (delete_list)
cur = delete_list;
while (cur)
{
uchar key_buffer[4+8];

Expand All @@ -786,9 +798,9 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
/* `break' does not work inside DBUG_EXECUTE_IF */
goto dbug_break; });

next= delete_list->next;
next= cur->next;

table->field[1]->store(delete_list->sub_id, true);
table->field[1]->store(cur->sub_id, true);
/* domain_id is already set in table->record[0] from write_row() above. */
key_copy(key_buffer, table->record[0], &table->key_info[0], 0, false);
if (table->file->ha_index_read_map(table->record[1], key_buffer,
Expand All @@ -802,8 +814,7 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
not want to endlessly error on the same element in case of table
corruption or such.
*/
my_free(delete_list);
delete_list= next;
cur= next;
if (err)
break;
}
Expand All @@ -826,18 +837,31 @@ IF_DBUG(dbug_break:, )
*/
if (delete_list)
{
mysql_mutex_lock(&LOCK_slave_state);
put_back_list(gtid->domain_id, delete_list);
mysql_mutex_unlock(&LOCK_slave_state);
delete_list = 0;
}

ha_rollback_trans(thd, FALSE);
}
close_thread_tables(thd);
if (in_transaction)
if (rgi)
{
thd->mdl_context.release_statement_locks();
/*
Save the list of old gtid entries we deleted. If this transaction
fails later for some reason and is rolled back, the deletion of those
entries will be rolled back as well, and we will need to put them back
on the to-be-deleted list so we can re-do the deletion. Otherwise
redundant rows in mysql.gtid_slave_pos may accumulate if transactions
are rolled back and retried after record_gtid().
*/
rgi->pending_gtid_deletes_save(gtid->domain_id, delete_list);
}
else
{
thd->mdl_context.release_transactional_locks();
rpl_group_info::pending_gtid_deletes_free(delete_list);
}
}
thd->lex->restore_backup_query_tables_list(&lex_backup);
thd->variables.option_bits= thd_saved_option;
Expand Down Expand Up @@ -1221,7 +1245,7 @@ rpl_slave_state::load(THD *thd, const char *state_from_master, size_t len,

if (gtid_parser_helper(&state_from_master, end, &gtid) ||
!(sub_id= next_sub_id(gtid.domain_id)) ||
record_gtid(thd, &gtid, sub_id, false, in_statement, &hton) ||
record_gtid(thd, &gtid, sub_id, NULL, in_statement, &hton) ||
update(gtid.domain_id, gtid.server_id, sub_id, gtid.seq_no, hton, NULL))
return 1;
if (state_from_master == end)
Expand Down
2 changes: 1 addition & 1 deletion sql/rpl_gtid.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ struct rpl_slave_state
int truncate_state_table(THD *thd);
void select_gtid_pos_table(THD *thd, LEX_CSTRING *out_tablename);
int record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
bool in_transaction, bool in_statement, void **out_hton);
rpl_group_info *rgi, bool in_statement, void **out_hton);
uint64 next_sub_id(uint32 domain_id);
int iterate(int (*cb)(rpl_gtid *, void *), void *data,
rpl_gtid *extra_gtids, uint32 num_extra,
Expand Down
79 changes: 79 additions & 0 deletions sql/rpl_rli.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,7 @@ rpl_group_info::reinit(Relay_log_info *rli)
long_find_row_note_printed= false;
did_mark_start_commit= false;
gtid_ev_flags2= 0;
pending_gtid_delete_list= NULL;
last_master_timestamp = 0;
gtid_ignore_duplicate_state= GTID_DUPLICATE_NULL;
speculation= SPECULATE_NO;
Expand Down Expand Up @@ -2216,6 +2217,12 @@ void rpl_group_info::cleanup_context(THD *thd, bool error)
erroneously update the GTID position.
*/
gtid_pending= false;

/*
Rollback will have undone any deletions of old rows we might have made
in mysql.gtid_slave_pos. Put those rows back on the list to be deleted.
*/
pending_gtid_deletes_put_back();
}
m_table_map.clear_tables();
slave_close_thread_tables(thd);
Expand Down Expand Up @@ -2441,6 +2448,78 @@ rpl_group_info::unmark_start_commit()
}


/*
When record_gtid() has deleted any old rows from the table
mysql.gtid_slave_pos as part of a replicated transaction, save the list of
rows deleted here.
If later the transaction fails (eg. optimistic parallel replication), the
deletes will be undone when the transaction is rolled back. Then we can
put back the list of rows into the rpl_global_gtid_slave_state, so that
we can re-do the deletes and avoid accumulating old rows in the table.
*/
void
rpl_group_info::pending_gtid_deletes_save(uint32 domain_id,
rpl_slave_state::list_element *list)
{
/*
We should never get to a state where we try to save a new pending list of
gtid deletes while we still have an old one. But make sure we handle it
anyway just in case, so we avoid leaving stray entries in the
mysql.gtid_slave_pos table.
*/
DBUG_ASSERT(!pending_gtid_delete_list);
if (unlikely(pending_gtid_delete_list))
pending_gtid_deletes_put_back();

pending_gtid_delete_list= list;
pending_gtid_delete_list_domain= domain_id;
}


/*
Take the list recorded by pending_gtid_deletes_save() and put it back into
rpl_global_gtid_slave_state. This is needed if deletion of the rows was
rolled back due to transaction failure.
*/
void
rpl_group_info::pending_gtid_deletes_put_back()
{
if (pending_gtid_delete_list)
{
rpl_global_gtid_slave_state->put_back_list(pending_gtid_delete_list_domain,
pending_gtid_delete_list);
pending_gtid_delete_list= NULL;
}
}


/*
Free the list recorded by pending_gtid_deletes_save(). Done when the deletes
in the list have been permanently committed.
*/
void
rpl_group_info::pending_gtid_deletes_clear()
{
pending_gtid_deletes_free(pending_gtid_delete_list);
pending_gtid_delete_list= NULL;
}


void
rpl_group_info::pending_gtid_deletes_free(rpl_slave_state::list_element *list)
{
rpl_slave_state::list_element *next;

while (list)
{
next= list->next;
my_free(list);
list= next;
}
}


rpl_sql_thread_info::rpl_sql_thread_info(Rpl_filter *filter)
: rpl_filter(filter)
{
Expand Down
11 changes: 11 additions & 0 deletions sql/rpl_rli.h
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,11 @@ struct rpl_group_info
/* Needs room for "Gtid D-S-N\x00". */
char gtid_info_buf[5+10+1+10+1+20+1];

/* List of not yet committed deletions in mysql.gtid_slave_pos. */
rpl_slave_state::list_element *pending_gtid_delete_list;
/* Domain associated with pending_gtid_delete_list. */
uint32 pending_gtid_delete_list_domain;

/*
The timestamp, from the master, of the commit event.
Used to do delayed update of rli->last_master_timestamp, for getting
Expand Down Expand Up @@ -898,6 +903,12 @@ struct rpl_group_info
char *gtid_info();
void unmark_start_commit();

static void pending_gtid_deletes_free(rpl_slave_state::list_element *list);
void pending_gtid_deletes_save(uint32 domain_id,
rpl_slave_state::list_element *list);
void pending_gtid_deletes_put_back();
void pending_gtid_deletes_clear();

longlong get_row_stmt_start_timestamp()
{
return row_stmt_start_timestamp;
Expand Down

0 comments on commit 3eb2c46

Please sign in to comment.