WL#6591: Restore backup on existing GTID-aware server - make GTID_PURGED settable always
Affects: Server-8.0
—
Status: Complete
The goal of this worklog is to extend the possible use cases where it is allowed to restore a backup on a server that has GTIDs enabled. In particular, we make it possible to restore backups on an existing server without losing existing GTID information and binary logs. In general, the server is always in a particular state. The state was created by executing a sequence of transactions. The variable GTID_EXECUTED contains the GTIDs of the transactions that created the current state. Some of these transactions may exist in a binary log, whereas other transactions may not exist - e.g. because the binary log was purged, or because the server state was created by restoring a backup. The variable GTID_PURGED contains the subset of GTID_EXECUTED that does *not* exist in the binary log. The latter is subset of the former: GTID_PURGED \subset= GTID_EXECUTED (*) To give some example, say the executed and the purged sets are as the following GTID_EXECUTED : = \union A:1-10,A:21-30, B:9-100, C:2-1000 GTID_PURGED := A:21-30,B:9-100,C:2-2 where A,B,C are uuid:s of the source servers; notice (*) is satisfied. This combination translates in that the server's binlog does not contain the 2nd range from A, misses out the whole B, and contains all but the very first known transaction from C which is C:2. When a backup is restored on a server, the state changes and thus we need to add some GTIDs to GTID_EXECUTED. The GTIDs that we need to add are precisely those that existed in GTID_EXECUTED when the backup was taken\footnote{ notice, the backup is here the full backup, not a partial one}. Unless the binary logs are restored too, the same GTIDs need to be added to GTID_PURGED. Currently, it is possible to add GTIDs to GTID_PURGED only when GTID_EXECUTED is empty, i.e., when restoring a backup on an empty server. In this worklog, we will make it possible to add GTIDs to GTID_PURGED at any time, even when GTID_EXECUTED is nonempty. Notice that GTID_PURGED initialization by restarted server does not have to require any refinement. Purging gtids "in the middle", through backup restore, has been already possible. When the server restarts the purged set is initialized through provisioned mysql.gtid_executed.
== Functional Requirements == F1. It shall be possible to assign to GTID_PURGED in run time, regardless of whether GTID_EXECUTED is empty or not, through the following statement: SET GTID_PURGED = "" F2. It shall be possible to append a certain gtid set via a "flavored" syntax of the SET assignment operator: SET GTID_PURGED = "+ " where the '+' character must be first character of the rhs string. The increment-like syntax is akin to that of DEBUG system variable. F3. The string " " shall be a string of the form described at http://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html If it does not have this form, an error shall be generated. F4. " " the rhs of the assignment operator is a set of GTIDs. In case of the append assignment this set must be disjoint with the current values of @@GLOBAL.GTID_EXECUTED and @@GLOBAL.GTID_OWNED. In case of the plain assignment the being assigned set must be a superset of the current GTID_PURGED's value, disjoint with GTID_EXECUTED that are not purged, and disjoint with @@GLOBAL.GTID_OWNED. When the conditions do not hold an error shall be generated. F5. The result of the assignment operator and the function work shall be: - GTID_PURGED becomes the union of GTID_PURGED and " ". - GTID_EXECUTED becomes the union of GTID_EXECUTED and " ". F6. Prerequisites of the WL must remain: - The binary log is *not* rotated. The GTIDs of " " are stored in 'mysql.gtid_executed' table which is the stable memory for the purge set initialization. == Non Functional Requirements == N1. The old dump, described by the old plainly assinged GTID_PURGED should be restorable on the new server in conditions of the old server that is when the current value GTID_EXECUTED is empty. N2. The new dump should be restorable on the old server in conditions of the old server. Footnotes: $. Previous_gtids_log_event might've been considered as the purged info placeholder which would little bit complicate the purged set initialization requiring two logs to process rather than one, but on the other hand require only the binlog.
IS-1: The new increment-like assignment SET GTID_PURGED = "+" normally should be generated by a backup program. It's semantics is to append the disjoint rhs set to the current values of GTID_PURGED and GTID_EXECUTED. See F4 for constraints. IS-2: The former syntax of SET GTID_PURGED = " " is made also to cover a new use case when GTID_PURGED is not empty, that is to override the current value. See F4 for constraints. The core change in code is that the condition to allow setting GTID_PURGED is changed. Before it was: GTID_EXECUTED \not= \empty and GTID_OWNED \not= \empty We need to change it to GTID_OWNED \cap gtid_set => \empty combined with one of the following two: GTID_EXECUTED \cap gtid_set => empty, gtid_set \superset GTID_PURGED => true and (GTID_EXECUTED - GTID_PURGED) \cap gtid_set => empty where the first deals with IS-1 syntax, and the 2nd covers the overriding semantics of IS-2 that is applies when GTID_EXECUTED \not= \empty. In addition we need to change the error message generated when the condition is violated accordingly, and turn mysqldump to output the append semantics assignment in response to --set-gtid-purged = AUTO.
diff --git a/sql/rpl_gtid_state.cc b/sql/rpl_gtid_state.cc index 7829df2..17a965d 100644 --- a/sql/rpl_gtid_state.cc +++ b/sql/rpl_gtid_state.cc @@ -686,21 +686,22 @@ enum_return_status Gtid_state::add_lost_gtids(const Gtid_set *gtid_set) gtid_set->dbug_print("add_lost_gtids"); - if (!executed_gtids.is_empty()) + if (executed_gtids.is_intersection_nonempty(gtid_set)) { - BINLOG_ERROR((ER(ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY)), - (ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY, - MYF(0))); + my_error(ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY, MYF(0)); RETURN_REPORTED_ERROR; } - if (!owned_gtids.is_empty()) + Gtid_set owned_gtid_set(global_sid_map); + // Reduce risk for memory allocation in owned_gtids.get_gtids + Gtid_set::Interval interval_list[64]; + owned_gtid_set.add_interval_memory(64, interval_list); + owned_gtids.get_gtids(owned_gtid_set); + if (owned_gtid_set.is_intersection_nonempty(gtid_set)) { - BINLOG_ERROR((ER(ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY)), - (ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY, - MYF(0))); + my_error(ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY, MYF(0)); RETURN_REPORTED_ERROR; } - DBUG_ASSERT(lost_gtids.is_empty()); + DBUG_ASSERT(!lost_gtids.is_intersection_nonempty(gtid_set)); if (save(gtid_set)) RETURN_REPORTED_ERROR; diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 1964a5b..d08cad4 100755 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -6901,10 +6901,10 @@ ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF eng "@@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_MODE = ON." ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY - eng "@@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty." + eng "@@GLOBAL.GTID_PURGED can only be set to a value that does not intersect with @@GLOBAL.GTID_EXECUTED. Suggest to use GTID_SUBTRACT(gtid_set, @@GLOBAL.GTID_EXECUTED)." ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY - eng "@@GLOBAL.GTID_PURGED can only be set when there are no ongoing transactions (not even in other clients)." + eng "@@GLOBAL.GTID_PURGED can only be set to a value that does not intersect with @@GLOBAL.GTID_PURGED." ER_GTID_PURGED_WAS_CHANGED eng "@@GLOBAL.GTID_PURGED was changed from '%s' to '%s'."
Copyright (c) 2000, 2024, Oracle Corporation and/or its affiliates. All rights reserved.