Commit aae49327 authored by Andrei Elkin's avatar Andrei Elkin

MDEV-12012/MDEV-11969 Can't remove GTIDs for a stale GTID Domain ID

As reported in MDEV-11969 "there's no way to ditch knowledge" about some
domain that is no longer updated on a server. Besides being of annoyance to
clutter output in DBA console stale domains can prevent the slave
to connect the master as MDEV-12012 witnesses.
What domain is obsolete must be evaluated by the user (DBA) according
to whether the domain info is still relevant and will the domain ever
receive any update.

This patch introduces a method to discard obsolete gtid domains from
the server binlog state. The removal requires no event group from such
domain present in existing binlog files though. If there are any the
containing logs must be first PURGEd in order for

  FLUSH BINARY LOGS DELETE_DOMAIN_ID=(list-of-domains)

succeed. Otherwise the command returns an error.

The list of obsolete domains can be computed through
intersecting two sets - the earliest (first) binlog's Gtid_list
and the current value of @@global.gtid_binlog_state - and extracting
the domain id components from the intersection list items.
The new DELETE_DOMAIN_ID featured FLUSH continues to rotate binlog
omitting the deleted domains from the active binlog file's Gtid_list.
Notice though when the command is ineffective - that none of requested to delete
domain exists in the binlog state - rotation does not occur.

Obsolete domain deletion is not harmful for connected slaves as long
as master side binlog files *purge* is synchronized with FLUSH-DELETE_DOMAIN_ID.
The slaves must have the last event from purged files processed as usual,
in order not to bump later into requesting a gtid from a file which
was already gone.
While the command is not replicated (as ordinary FLUSH BINLOG LOGS is)
slaves, even though having extra domains, won't suffer from reconnection errors
thanks to master-slave gtid connection protocol allowing the master
to be ignorant about a gtid domain.
Should at failover such slave to be promoted into master role it may run
the ex-master's

 FLUSH BINARY LOGS DELETE_DOMAIN_ID=(list-of-domains)

to clean its own binlog state.

NOTES.
  suite/perfschema/r/start_server_low_digest.result
is re-recorded as consequence of internal parser codes changes.
parent 7e1326cf
# ==== Purpose ====
#
# Extract Gtid_list info from SHOW BINLOG EVENTS output masking
# non-deterministic fields.
#
# ==== Usage ====
#
# [--let $binlog_file=filename
#
if ($binlog_file)
{
--let $_in_binlog_file=in '$binlog_file'
}
--replace_column 2 # 5 #
--eval show binlog events $_in_binlog_file limit 1,1
RESET MASTER;
FLUSH BINARY LOGS DELETE_DOMAIN_ID = ();
and the command execution is effective thence rotates binlog as usual
show binary logs;
Log_name File_size
master-bin.000001 #
master-bin.000002 #
Non-existed domain is warned, the command completes without rotation
but with a warning
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (99);
Warnings:
Warning 1982 The gtid domain being deleted ('99') is not in the current binlog state
show binary logs;
Log_name File_size
master-bin.000001 #
master-bin.000002 #
SET @@SESSION.gtid_domain_id=1;
SET @@SESSION.server_id=1;
CREATE TABLE t (a int);
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
ERROR HY000: Could not delete gtid domain. Reason: binlog files may contain gtids from the domain ('1') being deleted. Make sure to first purge those files.
FLUSH BINARY LOGS;
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
ERROR HY000: Could not delete gtid domain. Reason: binlog files may contain gtids from the domain ('1') being deleted. Make sure to first purge those files.
PURGE BINARY LOGS TO 'master-bin.000003';;
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
Gtid_list of the current binlog does not contain '1':
show binlog events in 'master-bin.000004' limit 1,1;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000004 # Gtid_list 1 # []
But the previous log's Gtid_list may have it which explains a warning from the following command
show binlog events in 'master-bin.000003' limit 1,1;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000003 # Gtid_list 1 # [1-1-1]
Already deleted domain in Gtid_list of the earliest log is benign
but may cause a warning
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
Warnings:
Warning 1982 The current gtid binlog state is incompatible with a former one missing gtids from the '1-1' domain-server pair which is referred to in the gtid list describing an earlier state. Ignore if the domain ('1') was already explicitly deleted.
Warning 1982 The gtid domain being deleted ('1') is not in the current binlog state
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 0);
ERROR HY000: Could not delete gtid domain. Reason: binlog files may contain gtids from the domain ('1') being deleted. Make sure to first purge those files.
FLUSH BINARY LOGS;
PURGE BINARY LOGS TO 'master-bin.000005';
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 0);
Warnings:
Warning 1982 The gtid domain being deleted ('0') is not in the current binlog state
Gtid_list of the current binlog does not contain 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 0:
show binlog events in 'master-bin.000006' limit 1,1;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000006 # Gtid_list 1 # []
SET @@SESSION.gtid_domain_id=1;;
SET @@SESSION.server_id=1;
SET @@SESSION.gtid_seq_no=1;
INSERT INTO t SET a=1;
SET @@SESSION.server_id=2;
SET @@SESSION.gtid_seq_no=2;
INSERT INTO t SET a=2;
SET @@SESSION.gtid_domain_id=11;
SET @@SESSION.server_id=11;
SET @@SESSION.gtid_seq_no=11;
INSERT INTO t SET a=11;
SET @gtid_binlog_state_saved=@@GLOBAL.gtid_binlog_state;
FLUSH BINARY LOGS;
SET @@SESSION.gtid_domain_id=11;
SET @@SESSION.server_id=11;
SET @@SESSION.gtid_seq_no=1;
INSERT INTO t SET a=1;
SELECT @gtid_binlog_state_saved "as original state", @@GLOBAL.gtid_binlog_state as "out of order for 11 domain state";
as original state out of order for 11 domain state
1-1-1,1-2-2,11-11-11 1-1-1,1-2-2,11-11-1
PURGE BINARY LOGS TO 'master-bin.000007';
the following command succeeds with warnings
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
Warnings:
Warning 1982 The current gtid binlog state is incompatible with a former one having a gtid '11-11-1' which is less than the '11-11-11' of the gtid list describing an earlier state. The state may have been affected by manually injecting a lower sequence number gtid or via replication.
DROP TABLE t;
RESET MASTER;
SET @@SESSION.debug_dbug='+d,inject_binlog_delete_domain_init_error';
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (99);
ERROR HY000: Could not delete gtid domain. Reason: injected error.
SHOW WARNINGS;
Level Code Message
Error 1982 Could not delete gtid domain. Reason: injected error.
# Prove basic properties of
#
# FLUSH BINARY LOGS DELETE_DOMAIN_ID = (...)
#
# The command removes the supplied list of domains from the current
# @@global.gtid_binlog_state provided the binlog files do not contain
# events from such domains.
# The test is not format specific. One format is chosen to run it.
--source include/have_binlog_format_mixed.inc
# Reset binlog state
RESET MASTER;
# Empty list is accepted
FLUSH BINARY LOGS DELETE_DOMAIN_ID = ();
--echo and the command execution is effective thence rotates binlog as usual
--source include/show_binary_logs.inc
--echo Non-existed domain is warned, the command completes without rotation
--echo but with a warning
--let $binlog_pre_flush=query_get_value(SHOW MASTER STATUS, Position, 1)
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (99);
--let $binlog_start=$binlog_pre_flush
--source include/show_binary_logs.inc
# Log one event in a specified domain and try to delete the domain
SET @@SESSION.gtid_domain_id=1;
SET @@SESSION.server_id=1;
CREATE TABLE t (a int);
--error ER_BINLOG_CANT_DELETE_GTID_DOMAIN
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
# the same error after log rotation
FLUSH BINARY LOGS;
--error ER_BINLOG_CANT_DELETE_GTID_DOMAIN
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
# the latest binlog does not really contain any events incl ones from 1-domain
--let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1)
--eval PURGE BINARY LOGS TO '$purge_to_binlog';
# So now it's safe to delete
--error 0
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
--echo Gtid_list of the current binlog does not contain '1':
--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1)
--source include/show_gtid_list.inc
--echo But the previous log's Gtid_list may have it which explains a warning from the following command
--let $binlog_file=$purge_to_binlog
--source include/show_gtid_list.inc
--echo Already deleted domain in Gtid_list of the earliest log is benign
--echo but may cause a warning
--error 0
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (1);
# Few domains delete. The chosen number verifies among others how
# expected overrun of the static buffers of underlying dynamic arrays is doing.
--let $domain_cnt=17
--let $server_in_domain_cnt=3
--let $domain_list=
--disable_query_log
while ($domain_cnt)
{
--let servers=$server_in_domain_cnt
--eval SET @@SESSION.gtid_domain_id=$domain_cnt
while ($servers)
{
--eval SET @@SESSION.server_id=10*$domain_cnt + $servers
--eval INSERT INTO t SET a=@@SESSION.server_id
--dec $servers
}
--let $domain_list= $domain_cnt, $domain_list
--dec $domain_cnt
}
--enable_query_log
--let $zero=0
--let $domain_list= $domain_list$zero
--error ER_BINLOG_CANT_DELETE_GTID_DOMAIN
--eval FLUSH BINARY LOGS DELETE_DOMAIN_ID = ($domain_list)
# Now satisfy the safety condtion to purge log files containing $domain list
FLUSH BINARY LOGS;
--let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1)
--eval PURGE BINARY LOGS TO '$purge_to_binlog'
--error 0
--eval FLUSH BINARY LOGS DELETE_DOMAIN_ID = ($domain_list)
--echo Gtid_list of the current binlog does not contain $domain_list:
--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1)
--source include/show_gtid_list.inc
# Show reaction on @@global.gtid_binlog_state not succeeding
# earlier state as described by the 1st binlog' Gtid_list.
# Now let it be out-order gtid logged to a domain unrelated to deletion.
--let $del_d_id=1
--eval SET @@SESSION.gtid_domain_id=$del_d_id;
SET @@SESSION.server_id=1;
SET @@SESSION.gtid_seq_no=1;
INSERT INTO t SET a=1;
SET @@SESSION.server_id=2;
SET @@SESSION.gtid_seq_no=2;
INSERT INTO t SET a=2;
SET @@SESSION.gtid_domain_id=11;
SET @@SESSION.server_id=11;
SET @@SESSION.gtid_seq_no=11;
INSERT INTO t SET a=11;
SET @gtid_binlog_state_saved=@@GLOBAL.gtid_binlog_state;
FLUSH BINARY LOGS;
# Inject out of order for domain '11' before
SET @@SESSION.gtid_domain_id=11;
SET @@SESSION.server_id=11;
SET @@SESSION.gtid_seq_no=1;
INSERT INTO t SET a=1;
SELECT @gtid_binlog_state_saved "as original state", @@GLOBAL.gtid_binlog_state as "out of order for 11 domain state";
# to delete '1', first to purge logs containing its events
--let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1)
--eval PURGE BINARY LOGS TO '$purge_to_binlog'
--echo the following command succeeds with warnings
--eval FLUSH BINARY LOGS DELETE_DOMAIN_ID = ($del_d_id)
#
# Cleanup
#
DROP TABLE t;
RESET MASTER;
# Check "internal" error branches of
# FLUSH BINARY LOGS DELETE_DOMAIN_ID = (...)
# handler.
--source include/have_debug.inc
--source include/have_binlog_format_mixed.inc
SET @@SESSION.debug_dbug='+d,inject_binlog_delete_domain_init_error';
--error ER_BINLOG_CANT_DELETE_GTID_DOMAIN
FLUSH BINARY LOGS DELETE_DOMAIN_ID = (99);
SHOW WARNINGS;
......@@ -8,5 +8,5 @@ SELECT 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
####################################
SELECT event_name, digest, digest_text, sql_text FROM events_statements_history_long;
event_name digest digest_text sql_text
statement/sql/truncate e1c917a43f978456fab15240f89372ca TRUNCATE TABLE truncate table events_statements_history_long
statement/sql/select 3f7ca34376814d0e985337bd588b5ffd SELECT ? + ? + SELECT 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
statement/sql/truncate 6206ac02a54d832f55015e480e6f2213 TRUNCATE TABLE truncate table events_statements_history_long
statement/sql/select 4cc1c447d79877c4e8df0423fd0cde9a SELECT ? + ? + SELECT 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
include/master-slave.inc
[connection master]
SET @@SESSION.gtid_domain_id=0;
CREATE TABLE t (a INT);
call mtr.add_suppression("connecting slave requested to start from.*which is not in the master's binlog");
include/stop_slave.inc
CHANGE MASTER TO master_use_gtid=slave_pos;
SET @@SESSION.gtid_domain_id=11;
SET @@SESSION.server_id=111;
SET @@SESSION.gtid_seq_no=1;
INSERT INTO t SET a=1;
SET @save.gtid_slave_pos=@@global.gtid_slave_pos;
SET @@global.gtid_slave_pos=concat(@@global.gtid_slave_pos, ",", 11, "-", 111, "-", 1 + 1);
Warnings:
Warning 1947 Specified GTID 0-1-1 conflicts with the binary log which contains a more recent GTID 0-2-2. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos.
START SLAVE IO_THREAD;
include/wait_for_slave_io_error.inc [errno=1236]
FLUSH BINARY LOGS;
PURGE BINARY LOGS TO 'master-bin.000002';;
FLUSH BINARY LOGS DELETE_DOMAIN_ID=(11);
include/start_slave.inc
INSERT INTO t SET a=1;
include/wait_for_slave_io_error.inc [errno=1236]
FLUSH BINARY LOGS;
PURGE BINARY LOGS TO 'master-bin.000004';;
FLUSH BINARY LOGS DELETE_DOMAIN_ID=(11);
include/start_slave.inc
SET @@SESSION.gtid_domain_id=0;
DROP TABLE t;
include/rpl_end.inc
# In case master's gtid binlog state is divergent from the slave's gtid_slave_pos
# slave may not be able to connect.
# For instance when slave is more updated in some of domains, see
# MDEV-12012 as example, the master's state may require adjustment.
# In a specific case of an "old" divergent domain, that is there
# won't be no more event groups from it generated, the states can be
# made compatible with wiping the problematic domain away. After that slave
# becomes connectable.
#
# Notice that the slave applied gtid state is not really required to
# be similarly cleaned in order for replication to flow.
# However this could lead to an expected error when the master
# resumes binlogging of such domain which the test demonstrate.
--source include/master-slave.inc
--connection master
# enforce the default domain_id binlogging explicitly
SET @@SESSION.gtid_domain_id=0;
CREATE TABLE t (a INT);
--sync_slave_with_master
--connection slave
call mtr.add_suppression("connecting slave requested to start from.*which is not in the master's binlog");
--source include/stop_slave.inc
CHANGE MASTER TO master_use_gtid=slave_pos;
--connection master
# create extra gtid domains for binlog state
--let $extra_domain_id=11
--let $extra_domain_server_id=111
--let $extra_gtid_seq_no=1
--eval SET @@SESSION.gtid_domain_id=$extra_domain_id
--eval SET @@SESSION.server_id=$extra_domain_server_id
--eval SET @@SESSION.gtid_seq_no=$extra_gtid_seq_no
INSERT INTO t SET a=1;
#
# Set up the slave replication state as if slave knows more events from the extra
# domain.
#
--connection slave
SET @save.gtid_slave_pos=@@global.gtid_slave_pos;
--eval SET @@global.gtid_slave_pos=concat(@@global.gtid_slave_pos, ",", $extra_domain_id, "-", $extra_domain_server_id, "-", $extra_gtid_seq_no + 1)
# unsuccessful attempt to start slave
START SLAVE IO_THREAD;
--let $slave_io_errno=1236
--source include/wait_for_slave_io_error.inc
--connection master
# adjust the master binlog state
FLUSH BINARY LOGS;
--let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1)
--eval PURGE BINARY LOGS TO '$purge_to_binlog';
# with final removal of the extra domain
--eval FLUSH BINARY LOGS DELETE_DOMAIN_ID=($extra_domain_id)
--connection slave
# start the slave sucessfully
--source include/start_slave.inc
--connection master
# but the following gtid from the *extra* domain will break replication
INSERT INTO t SET a=1;
# take note of the slave io thread error due to being dismissed
# extra domain at connection to master which tried becoming active;
# slave is to stop.
--connection slave
--let $errno=1236
--source include/wait_for_slave_io_error.inc
# let's apply the very same medicine
--connection master
FLUSH BINARY LOGS;
--let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1)
--eval PURGE BINARY LOGS TO '$purge_to_binlog';
# with final removal of the extra domain
--eval FLUSH BINARY LOGS DELETE_DOMAIN_ID=($extra_domain_id)
--connection slave
--source include/start_slave.inc
#
# cleanup
#
--connection master
SET @@SESSION.gtid_domain_id=0;
DROP TABLE t;
sync_slave_with_master;
--source include/rpl_end.inc
......@@ -179,6 +179,7 @@ static SYMBOL symbols[] = {
{ "DELAYED", SYM(DELAYED_SYM)},
{ "DELAY_KEY_WRITE", SYM(DELAY_KEY_WRITE_SYM)},
{ "DELETE", SYM(DELETE_SYM)},
{ "DELETE_DOMAIN_ID", SYM(DELETE_DOMAIN_ID_SYM)},
{ "DESC", SYM(DESC)},
{ "DESCRIBE", SYM(DESCRIBE)},
{ "DES_KEY_FILE", SYM(DES_KEY_FILE)},
......
......@@ -6622,6 +6622,120 @@ void MYSQL_BIN_LOG::checkpoint_and_purge(ulong binlog_id)
purge();
}
/**
Searches for the first (oldest) binlog file name in in the binlog index.
@param[in,out] buf_arg pointer to a buffer to hold found
the first binary log file name
@return NULL on success, otherwise error message
*/
static const char* get_first_binlog(char* buf_arg)
{
IO_CACHE *index_file;
size_t length;
char fname[FN_REFLEN];
const char* errmsg= NULL;
DBUG_ENTER("get_first_binlog");
DBUG_ASSERT(mysql_bin_log.is_open());
mysql_bin_log.lock_index();
index_file=mysql_bin_log.get_index_file();
if (reinit_io_cache(index_file, READ_CACHE, (my_off_t) 0, 0, 0))
{
errmsg= "failed to create a cache on binlog index";
goto end;
}
/* The file ends with EOF or empty line */
if ((length=my_b_gets(index_file, fname, sizeof(fname))) <= 1)
{
errmsg= "empty binlog index";
goto end;
}
else
{
fname[length-1]= 0; // Remove end \n
}
if (normalize_binlog_name(buf_arg, fname, false))
{
errmsg= "cound not normalize the first file name in the binlog index";
goto end;
}
end:
mysql_bin_log.unlock_index();
DBUG_RETURN(errmsg);
}
/**
Check weather the gtid binlog state can safely remove gtid
domains passed as the argument. A safety condition is satisfied when
there are no events from the being deleted domains in the currently existing
binlog files. Upon successful check the supplied domains are removed
from @@gtid_binlog_state. The caller is supposed to rotate binlog so that
the active latest file won't have the deleted domains in its Gtid_list header.
@param domain_drop_lex gtid domain id sequence from lex.
Passed as a pointer to dynamic array must be not empty
unless pointer value NULL.
@retval zero on success
@retval > 0 ineffective call none from the *non* empty
gtid domain sequence is deleted
@retval < 0 on error
*/
static int do_delete_gtid_domain(DYNAMIC_ARRAY *domain_drop_lex)
{
int rc= 0;
Gtid_list_log_event *glev= NULL;
char buf[FN_REFLEN];
File file;
IO_CACHE cache;
const char* errmsg= NULL;
char errbuf[MYSQL_ERRMSG_SIZE]= {0};
if (!domain_drop_lex)
return 0; // still "effective" having empty domain sequence to delete
DBUG_ASSERT(domain_drop_lex->elements > 0);
mysql_mutex_assert_owner(mysql_bin_log.get_log_lock());
if ((errmsg= get_first_binlog(buf)) != NULL)
goto end;
bzero((char*) &cache, sizeof(cache));
if ((file= open_binlog(&cache, buf, &errmsg)) == (File) -1)
goto end;
errmsg= get_gtid_list_event(&cache, &glev);
end_io_cache(&cache);
mysql_file_close(file, MYF(MY_WME));
DBUG_EXECUTE_IF("inject_binlog_delete_domain_init_error",
errmsg= "injected error";);
if (errmsg)
goto end;
errmsg= rpl_global_gtid_binlog_state.drop_domain(domain_drop_lex,
glev, errbuf);
end:
if (errmsg)
{
if (strlen(errmsg) > 0)
{
my_error(ER_BINLOG_CANT_DELETE_GTID_DOMAIN, MYF(0), errmsg);
rc= -1;
}
else
{
rc= 1;
}
}
delete glev;
return rc;
}
/**
The method is a shortcut of @c rotate() and @c purge().
LOCK_log is acquired prior to rotate and is released after it.
......@@ -6631,9 +6745,10 @@ void MYSQL_BIN_LOG::checkpoint_and_purge(ulong binlog_id)
@retval
nonzero - error in rotating routine.
*/
int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate)
int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate,
DYNAMIC_ARRAY *domain_drop_lex)
{
int error= 0;
int err_gtid=0, error= 0;
ulong prev_binlog_id;
DBUG_ENTER("MYSQL_BIN_LOG::rotate_and_purge");
bool check_purge= false;
......@@ -6641,7 +6756,14 @@ int MYSQL_BIN_LOG::rotate_and_purge(bool force_rotate)
//todo: fix the macro def and restore safe_mutex_assert_not_owner(&LOCK_log);
mysql_mutex_lock(&LOCK_log);
prev_binlog_id= current_binlog_id;
if ((error= rotate(force_rotate, &check_purge)))
if ((err_gtid= do_delete_gtid_domain(domain_drop_lex)))
{
// inffective attempt to delete merely skips rotate and purge
if (err_gtid < 0)
error= 1; // otherwise error is propagated the user
}
else if ((error= rotate(force_rotate, &check_purge)))
check_purge= false;
/*
NOTE: Run purge_logs wo/ holding LOCK_log because it does not need
......@@ -10219,6 +10341,73 @@ TC_LOG_BINLOG::set_status_variables(THD *thd)
}
}
/*
Find the Gtid_list_log_event at the start of a binlog.
NULL for ok, non-NULL error message for error.
If ok, then the event is returned in *out_gtid_list. This can be NULL if we
get back to binlogs written by old server version without GTID support. If
so, it means we have reached the point to start from, as no GTID events can
exist in earlier binlogs.
*/
const char *
get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list)
{
Format_description_log_event init_fdle(BINLOG_VERSION);
Format_description_log_event *fdle;
Log_event *ev;
const char *errormsg = NULL;
*out_gtid_list= NULL;
if (!(ev= Log_event::read_log_event(cache, 0, &init_fdle,
opt_master_verify_checksum)) ||
ev->get_type_code() != FORMAT_DESCRIPTION_EVENT)
{
if (ev)
delete ev;
return "Could not read format description log event while looking for "
"GTID position in binlog";
}
fdle= static_cast<Format_description_log_event *>(ev);
for (;;)
{
Log_event_type typ;
ev= Log_event::read_log_event(cache, 0, fdle, opt_master_verify_checksum);
if (!ev)
{
errormsg= "Could not read GTID list event while looking for GTID "
"position in binlog";
break;
}
typ= ev->get_type_code();
if (typ == GTID_LIST_EVENT)
break; /* Done, found it */
if (typ == START_ENCRYPTION_EVENT)
{
if (fdle->start_decryption((Start_encryption_log_event*) ev))
errormsg= "Could not set up decryption for binlog.";
}
delete ev;
if (typ == ROTATE_EVENT || typ == STOP_EVENT ||
typ == FORMAT_DESCRIPTION_EVENT || typ == START_ENCRYPTION_EVENT)
continue; /* Continue looking */
/* We did not find any Gtid_list_log_event, must be old binlog. */
ev= NULL;
break;
}
delete fdle;
*out_gtid_list= static_cast<Gtid_list_log_event *>(ev);
return errormsg;
}
struct st_mysql_storage_engine binlog_storage_engine=
{ MYSQL_HANDLERTON_INTERFACE_VERSION };
......
......@@ -755,7 +755,7 @@ class MYSQL_BIN_LOG: public TC_LOG, private MYSQL_LOG
int update_log_index(LOG_INFO* linfo, bool need_update_threads);
int rotate(bool force_rotate, bool* check_purge);
void checkpoint_and_purge(ulong binlog_id);
int rotate_and_purge(bool force_rotate);
int rotate_and_purge(bool force_rotate, DYNAMIC_ARRAY* drop_gtid_domain= NULL);
/**
Flush binlog cache and synchronize to disk.
......@@ -1165,4 +1165,9 @@ static inline TC_LOG *get_tc_log_implementation()
return &tc_log_mmap;
}
class Gtid_list_log_event;
const char *
get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list);
#endif /* LOG_H */
......@@ -26,7 +26,7 @@
#include "key.h"
#include "rpl_gtid.h"
#include "rpl_rli.h"
#include "log_event.h"
const LEX_STRING rpl_gtid_slave_state_table_name=
{ C_STRING_WITH_LEN("gtid_slave_pos") };
......@@ -1728,6 +1728,155 @@ rpl_binlog_state::append_state(String *str)
return res;
}
/**
Remove domains supplied by the first argument from binlog state.
Removal is done for any domain whose last gtids (from all its servers) match
ones in Gtid list event of the 2nd argument.
@param ids gtid domain id sequence, may contain dups
@param glev pointer to Gtid list event describing
the match condition
@param errbuf [out] pointer to possible error message array
@retval NULL as success when at least one domain is removed
@retval "" empty string to indicate ineffective call
when no domains removed
@retval NOT EMPTY string otherwise an error message
*/
const char*
rpl_binlog_state::drop_domain(DYNAMIC_ARRAY *ids,
Gtid_list_log_event *glev,
char* errbuf)
{
DYNAMIC_ARRAY domain_unique; // sequece (unsorted) of unique element*:s
rpl_binlog_state::element* domain_unique_buffer[16];
ulong k, l;
const char* errmsg= NULL;
DBUG_ENTER("rpl_binlog_state::drop_domain");
my_init_dynamic_array2(&domain_unique,
sizeof(element*), domain_unique_buffer,
sizeof(domain_unique_buffer) / sizeof(element*), 4, 0);
mysql_mutex_lock(&LOCK_binlog_state);
/*
Gtid list is supposed to come from a binlog's Gtid_list event and
therefore should be a subset of the current binlog state. That is
for every domain in the list the binlog state contains a gtid with
sequence number not less than that of the list.
Exceptions of this inclusion rule are:
A. the list may still refer to gtids from already deleted domains.
Files containing them must have been purged whereas the file
with the list is not yet.
B. out of order groups were injected
C. manually build list of binlog files violating the inclusion
constraint.
While A is a normal case (not necessarily distinguishable from C though),
B and C may require the user's attention so any (incl the A's suspected)
inconsistency is diagnosed and *warned*.
*/
for (l= 0, errbuf[0]= 0; l < glev->count; l++, errbuf[0]= 0)
{
rpl_gtid* rb_state_gtid= find_nolock(glev->list[l].domain_id,
glev->list[l].server_id);
if (!rb_state_gtid)
sprintf(errbuf,
"missing gtids from the '%u-%u' domain-server pair which is "
"referred to in the gtid list describing an earlier state. Ignore "
"if the domain ('%u') was already explicitly deleted",
glev->list[l].domain_id, glev->list[l].server_id,
glev->list[l].domain_id);
else if (rb_state_gtid->seq_no < glev->list[l].seq_no)
sprintf(errbuf,
"having a gtid '%u-%u-%llu' which is less than "
"the '%u-%u-%llu' of the gtid list describing an earlier state. "
"The state may have been affected by manually injecting "
"a lower sequence number gtid or via replication",
rb_state_gtid->domain_id, rb_state_gtid->server_id,
rb_state_gtid->seq_no, glev->list[l].domain_id,
glev->list[l].server_id, glev->list[l].seq_no);
if (strlen(errbuf)) // use strlen() as cheap flag
push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
ER_BINLOG_CANT_DELETE_GTID_DOMAIN,
"The current gtid binlog state is incompatible with "
"a former one %s.", errbuf);
}
/*
For each domain_id from ids
when no such domain in binlog state
warn && continue
For each domain.server's last gtid
when not locate the last gtid in glev.list
error out binlog state can't change
otherwise continue
*/
for (ulong i= 0; i < ids->elements; i++)
{
rpl_binlog_state::element *elem= NULL;
ulong *ptr_domain_id;
bool not_match;
ptr_domain_id= (ulong*) dynamic_array_ptr(ids, i);
elem= (rpl_binlog_state::element *)
my_hash_search(&hash, (const uchar *) ptr_domain_id, 0);
if (!elem)
{
push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
ER_BINLOG_CANT_DELETE_GTID_DOMAIN,
"The gtid domain being deleted ('%lu') is not in "
"the current binlog state", *ptr_domain_id);
continue;
}
for (not_match= true, k= 0; k < elem->hash.records; k++)
{
rpl_gtid *d_gtid= (rpl_gtid *)my_hash_element(&elem->hash, k);
for (ulong l= 0; l < glev->count && not_match; l++)
not_match= !(*d_gtid == glev->list[l]);
}
if (not_match)
{
sprintf(errbuf, "binlog files may contain gtids from the domain ('%lu') "
"being deleted. Make sure to first purge those files",
*ptr_domain_id);
errmsg= errbuf;
goto end;
}
// compose a sequence of unique pointers to domain object
for (k= 0; k < domain_unique.elements; k++)
{
if ((rpl_binlog_state::element*) dynamic_array_ptr(&domain_unique, k)
== elem)
break; // domain_id's elem has been already in
}
if (k == domain_unique.elements) // proven not to have duplicates
insert_dynamic(&domain_unique, (uchar*) &elem);
}
// Domain removal from binlog state
for (k= 0; k < domain_unique.elements; k++)
{
rpl_binlog_state::element *elem= *(rpl_binlog_state::element**)
dynamic_array_ptr(&domain_unique, k);
my_hash_free(&elem->hash);
my_hash_delete(&hash, (uchar*) elem);
}
DBUG_ASSERT(strlen(errbuf) == 0);
if (domain_unique.elements == 0)
errmsg= "";
end:
mysql_mutex_unlock(&LOCK_binlog_state);
delete_dynamic(&domain_unique);
DBUG_RETURN(errmsg);
}
slave_connection_state::slave_connection_state()
{
......
......@@ -34,6 +34,13 @@ struct rpl_gtid
uint64 seq_no;
};
inline bool operator==(const rpl_gtid& lhs, const rpl_gtid& rhs)
{
return
lhs.domain_id == rhs.domain_id &&
lhs.server_id == rhs.server_id &&
lhs.seq_no == rhs.seq_no;
};
enum enum_gtid_skip_type {
GTID_SKIP_NOT, GTID_SKIP_STANDALONE, GTID_SKIP_TRANSACTION
......@@ -93,6 +100,7 @@ struct gtid_waiting {
class Relay_log_info;
struct rpl_group_info;
class Gtid_list_log_event;
/*
Replication slave state.
......@@ -256,6 +264,7 @@ struct rpl_binlog_state
rpl_gtid *find_nolock(uint32 domain_id, uint32 server_id);
rpl_gtid *find(uint32 domain_id, uint32 server_id);
rpl_gtid *find_most_recent(uint32 domain_id);
const char* drop_domain(DYNAMIC_ARRAY *ids, Gtid_list_log_event *glev, char*);
};
......
......@@ -7142,3 +7142,5 @@ ER_NO_EIS_FOR_FIELD
ER_WARN_AGGFUNC_DEPENDENCE
eng "Aggregate function '%-.192s)' of SELECT #%d belongs to SELECT #%d"
ukr "Агрегатна функція '%-.192s)' з SELECTу #%d належить до SELECTу #%d"
ER_BINLOG_CANT_DELETE_GTID_DOMAIN
eng "Could not delete gtid domain. Reason: %s."
......@@ -800,6 +800,7 @@ void lex_end_stage2(LEX *lex)
/* Reset LEX_MASTER_INFO */
lex->mi.reset(lex->sql_command == SQLCOM_CHANGE_MASTER);
delete_dynamic(&lex->delete_gtid_domain);
DBUG_VOID_RETURN;
}
......@@ -2878,6 +2879,10 @@ LEX::LEX()
INITIAL_LEX_PLUGIN_LIST_SIZE, 0);
reset_query_tables_list(TRUE);
mi.init();
init_dynamic_array2(&delete_gtid_domain, sizeof(ulong*),
gtid_domain_static_buffer,
initial_gtid_domain_buffer_size,
initial_gtid_domain_buffer_size, 0);
}
......
......@@ -2726,6 +2726,13 @@ struct LEX: public Query_tables_list
*/
Item *limit_rows_examined;
ulonglong limit_rows_examined_cnt;
/**
Holds a set of domain_ids for deletion at FLUSH..DELETE_DOMAIN_ID
*/
DYNAMIC_ARRAY delete_gtid_domain;
static const ulong initial_gtid_domain_buffer_size= 16;
ulong gtid_domain_static_buffer[initial_gtid_domain_buffer_size];
inline void set_limit_rows_examined()
{
if (limit_rows_examined)
......
......@@ -153,7 +153,10 @@ bool reload_acl_and_cache(THD *thd, unsigned long long options,
tmp_write_to_binlog= 0;
if (mysql_bin_log.is_open())
{
if (mysql_bin_log.rotate_and_purge(true))
DYNAMIC_ARRAY *drop_gtid_domain=
(thd && (thd->lex->delete_gtid_domain.elements > 0)) ?
&thd->lex->delete_gtid_domain : NULL;
if (mysql_bin_log.rotate_and_purge(true, drop_gtid_domain))
*write_to_binlog= -1;
if (WSREP_ON)
......
......@@ -30,7 +30,7 @@
#include <my_dir.h>
#include "rpl_handler.h"
#include "debug_sync.h"
#include "log.h" // get_gtid_list_event
enum enum_gtid_until_state {
GTID_UNTIL_NOT_DONE,
......@@ -875,72 +875,6 @@ get_binlog_list(MEM_ROOT *memroot)
DBUG_RETURN(current_list);
}
/*
Find the Gtid_list_log_event at the start of a binlog.
NULL for ok, non-NULL error message for error.
If ok, then the event is returned in *out_gtid_list. This can be NULL if we
get back to binlogs written by old server version without GTID support. If
so, it means we have reached the point to start from, as no GTID events can
exist in earlier binlogs.
*/
static const char *
get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list)
{
Format_description_log_event init_fdle(BINLOG_VERSION);
Format_description_log_event *fdle;
Log_event *ev;
const char *errormsg = NULL;
*out_gtid_list= NULL;
if (!(ev= Log_event::read_log_event(cache, 0, &init_fdle,
opt_master_verify_checksum)) ||
ev->get_type_code() != FORMAT_DESCRIPTION_EVENT)
{
if (ev)
delete ev;
return "Could not read format description log event while looking for "
"GTID position in binlog";
}
fdle= static_cast<Format_description_log_event *>(ev);
for (;;)
{
Log_event_type typ;
ev= Log_event::read_log_event(cache, 0, fdle, opt_master_verify_checksum);
if (!ev)
{
errormsg= "Could not read GTID list event while looking for GTID "
"position in binlog";
break;
}
typ= ev->get_type_code();
if (typ == GTID_LIST_EVENT)
break; /* Done, found it */
if (typ == START_ENCRYPTION_EVENT)
{
if (fdle->start_decryption((Start_encryption_log_event*) ev))
errormsg= "Could not set up decryption for binlog.";
}
delete ev;
if (typ == ROTATE_EVENT || typ == STOP_EVENT ||
typ == FORMAT_DESCRIPTION_EVENT || typ == START_ENCRYPTION_EVENT)
continue; /* Continue looking */
/* We did not find any Gtid_list_log_event, must be old binlog. */
ev= NULL;
break;
}
delete fdle;
*out_gtid_list= static_cast<Gtid_list_log_event *>(ev);
return errormsg;
}
/*
Check if every GTID requested by the slave is contained in this (or a later)
......
......@@ -82,7 +82,6 @@ int rpl_append_gtid_state(String *dest, bool use_binlog);
int rpl_load_gtid_state(slave_connection_state *state, bool use_binlog);
bool rpl_gtid_pos_check(THD *thd, char *str, size_t len);
bool rpl_gtid_pos_update(THD *thd, char *str, size_t len);
#else
struct LOAD_FILE_IO_CACHE : public IO_CACHE { };
......
......@@ -1182,6 +1182,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token DELAYED_SYM
%token DELAY_KEY_WRITE_SYM
%token DELETE_SYM /* SQL-2003-R */
%token DELETE_DOMAIN_ID_SYM
%token DESC /* SQL-2003-N */
%token DESCRIBE /* SQL-2003-R */
%token DES_KEY_FILE
......@@ -1951,6 +1952,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
parse_vcol_expr vcol_opt_specifier vcol_opt_attribute
vcol_opt_attribute_list vcol_attribute
explainable_command
opt_delete_gtid_domain
END_OF_INPUT
%type <NONE> call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt
......@@ -12762,7 +12764,7 @@ flush_option:
{ Lex->type|= REFRESH_GENERAL_LOG; }
| SLOW LOGS_SYM
{ Lex->type|= REFRESH_SLOW_LOG; }
| BINARY LOGS_SYM
| BINARY LOGS_SYM opt_delete_gtid_domain
{ Lex->type|= REFRESH_BINARY_LOG; }
| RELAY LOGS_SYM optional_connection_name
{
......@@ -12819,6 +12821,24 @@ opt_table_list:
| table_list {}
;
opt_delete_gtid_domain:
/* empty */ {}
| DELETE_DOMAIN_ID_SYM '=' '(' delete_domain_id_list ')'
{}
;
delete_domain_id_list:
/* Empty */
| delete_domain_id
| delete_domain_id_list ',' delete_domain_id
;
delete_domain_id:
ulong_num
{
insert_dynamic(&Lex->delete_gtid_domain, (uchar*) &($1));
}
;
optional_flush_tables_arguments:
/* empty */ {$$= 0;}
| AND_SYM DISABLE_SYM CHECKPOINT_SYM {$$= REFRESH_CHECKPOINT; }
......
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