[Maria-developers] Obsolete GTID domain delete on master (MDEV-12012, MDEV-11969)
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Hello. Let me propose methods to clean master off unused gtid domains. The issue is quite practical, as a couple of references on the subject line tell. Either of them narrates a scenario of two default domain-id masters serving to one slave initially by "legacy" non-gtid protocol. When later the masters have changed their common domain to different private ones *and* the slave turns gtid replication ON, it can't connect to a master due to the gtid protocol. And the reason is the prior-to-gtid-connect gained slave's gtid position consisting of gtids generated by the other master obviously (the two masters never replicated each other) does not fit to the current master binlog state. The past default domain-id is actually permanent past from the user perspective in these cases. Its events have been already replicated and none new will be generated and replicated. Therefore such domain conceptually may be cleaned away from either the masters and slave states. Once it's done, the gtid-enabled slave will successfully connect to any master. The slave state purge is simple SET @@global.gtid_slave_pos to a sequence free of the purged domain. The master side binlog state one requires a new "command" unless the user is happy with RESET MASTER. While setting the new gtid binlog state to be old-domain-free we would like for the new "command" to preserve the existing binlog files. This could be accomplished as Kristian suggests in MDEV-12012 (I could not find any earlier references to the idea) as a new option to FLUSH BINARY LOGS DELETE DOMAIN d1, d2 KN> This command would check that the current binlog state is equal to KN> the GTID_LIST_LOG_EVENT at the start of the first binary log file, KN> within the specified domains. If not, it is an error. But if so, KN> the new binary log file created by FLUSH will be written with the KN> specified domains omitted from GTID_LIST_LOG_EVENT (and the current KN> binlog state updated accordingly). The idea looks quite sane, I only could not grasp why presence of being deleted domains in the very first binlog's GTID_LIST_LOG_EVENT list is warrant for throwing error. Maybe we should leave it out to the user, Kristian? That is to decide what domain is garbage regardless of the binlog state history. While the FLUSH way looks sufficient and robust I could not help to think over an alternative. Consider a scenario when a domain's sequence number got run out of range. While deems unrealistic in practice we can simulate it with SET @@SESSION.gtid_domain_id=11; SET @@SESSION.server_id=1; SET @@SESSION.gtid_seq_no=18446744073709551615; /* Exec some dummy loggable query, e.g */ CREATE TABLE IF NOT EXISTS `table_dummy`; SHOW LOCAL VARIABLES LIKE '%gtid_binlog_pos%'; 11-1-18446744073709551615 SET @@SESSION.gtid_seq_no=0 DROP TABLE `table_dummy`; SHOW LOCAL VARIABLES LIKE '%gtid_binlog_pos%'; 11-1-0 I've used two gtids to show domain overflow because I also liked to read the zero of the last gtid as ... there's *nothing* in this domain. So it's actually a new "namesake" one, replacing the old that is wrapped around. The 1st group of events created in the new domain - 11-1-1 - could shadow the old domain's 11-1-1 as well as all the rest of the old domain from gtid replication protocol. And that means the old domain is actually deleted. So if my reading of zero is correct the binlog status would be empty instead. That's how we also can approach the master side old gtid domain purging: 1. Leave wrapping around an old domain to the user via running the queries like above; 2. The binary logger would be made to react on the fact of wrap-around with binary log rotation ("internal" FLUSH BINARY LOG). And the new binlog file won't contain the wrapped "away" domain (because there are no new event group in it of yet). I would be glad to hear your opinions, dear colleagues. Cheers, Andrei
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
andrei.elkin@pp.inet.fi writes:
Let me propose methods to clean master off unused gtid domains. I would be glad to hear your opinions, dear colleagues.
So a bit of background: The central idea in MariaDB GTID is the sequence of events that created the current master state. This is an abstract concept. Conceptually, the current state of this server is defined as executing a specific sequence of events (in practice it might have been restored from a backup or something). Abstractly, the server's binlog is exactly this sequence of events (in practice the early part probably no longer exists or possibly never did). The sequence is multi-streamed (one stream per domain). Everything (in GTID, but also in parallel replication and group commit) is based on the assumption that each stream in the binlog sequence is strictly ordered, at least on a single given server. It is important to understand that it is the actual sequence of events that matters, conceptually. The actual GTID format of D-S-N is only an implementation detail that allows the code to work correctly. The sequence is defined by the binlog, not by the particular sequence numbers in GTID or other details. When a slave connects to our master server, it presents its current position as a single event within each stream. By the above, this is sufficient to reliably find the correct position in the binlog to restart the slave from. Because MariaDB replication is async, we cannot in general prevent different servers from errorneously ending up with different binlog sequence. However, we can ensure a consistent view of the sequence on a single server, and we can try to detect and flag any inconsistencies between servers as they are noticed. This is why it is necessary to give an error if a slave presents a position containing an event that is not in the master's binlog. The master cannot know if this is because the slave is ahead (the event in question will arrive later on the master), or because replication has diverged (the event will never arrive on the master, and the replication position is not well defined). It is a central goal in GTID to avoid, as much as possible, silent incorrect operation in replication. With that explained, now onto some concrete comments/answers:
The past default domain-id is actually permanent past from the user perspective in these cases. Its events have been already replicated and none new will be generated and replicated.
But from the point of view of GTID semantics, the binlog sequence is still defined by this past, and in an inconsistent (and hence incorrect) way.
Therefore such domain conceptually may be cleaned away from either the masters and slave states.
So as you say, the errorneous state must be fixed for GTID to work correctly. One way is to discard the entire incorrect binlog with RESET MASTER. But this discussion is about fixing the binlog in-place, by (conceptually) replacing it with a variant which does not contain the problematic past.
The idea looks quite sane, I only could not grasp why presence of being deleted domains in the very first binlog's GTID_LIST_LOG_EVENT list is warrant for throwing error. Maybe we should leave it out to the user, Kristian? That is to decide what domain is garbage regardless of the binlog state history.
DELETE DOMAIN d1 replaces the conceptual binlog sequence with one in which domain d1 never existed. If there would be actual binlog files containing events in d1, this would be a grave inconsistency. For example, if an existing slave was still replicating events in d1, if a temporary network error caused it to reconnect to the master, it would fail to reconnect. A slave without knowledge of d1 replicating might start re-applying any events encountered. Basically, after DELETE DOMAIN d1, any binlog file containing d1 is invalid and useless, so it seems appropriate to require the user to PURGE BINARY LOG them first.
SET @@SESSION.gtid_seq_no=18446744073709551615; CREATE TABLE IF NOT EXISTS `table_dummy`; SHOW LOCAL VARIABLES LIKE '%gtid_binlog_pos%';
11-1-18446744073709551615
SET @@SESSION.gtid_seq_no=0 DROP TABLE `table_dummy`; SHOW LOCAL VARIABLES LIKE '%gtid_binlog_pos%';
11-1-0
Ouch. That's a bug. This should give an error, I think that could lead to all kinds of extremely nasty problems :-(
1. Leave wrapping around an old domain to the user via running the queries like above; 2. The binary logger would be made to react on the fact of wrap-around with binary log rotation ("internal" FLUSH BINARY LOG). And the new binlog file won't contain the wrapped "away" domain (because there are no new event group in it of yet).
I am not sure I understand you here. Are you suggesting that the GTID sequence wrap-around bug be instead declared a feature, and be documented as the way to delete a domain in the binlog? I do not think that is appropriate. As I see it, there are two sides to this. (1). We want the master to "forget about the past" with respect to a given domain. This is easy. All that is needed is to rotate the binlog and omit the domain from the GTID_LIST event at the start of the new binlog. Because when the master searches back for a given GTID in the binlog, it stops when it sees a GTID_LIST event without that domain. (2). We want to prevent a user accidentally putting the server into an inconsistent state with an incorrect DELETE DOMAIN command. This is ensured by the requirement that all existing binlog files are free of that domain. Should a slave later, incorrectly, try to access that domain, it will receive the wrong error (that it is diverged rather than that the necessary binlog file has been purged), but at least it _will_ get an error as it should, not silently corrupt replication. I think the requirement is a reasonable one. The domain was configured incorrectly, the binlog files containing it cannot be used safely with GTID. The procedure to fix it will then be: 1. FLUSH BINARY LOGS, note the new GTID position. 2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed. 3. PURGE BINARY LOGS to remove the errorneous logs. 4. FLUSH BINARY LOG DELETE DOMAIN d It is of course an option to not do (2). Just be aware that this goes against the whole philosophy that GTID was designed around - to prioritise consistency and "no silent corruption". Hope this helps. Of course feel free to ask for more details on any point that is not clear. - Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian, salute. Let me jump at once to the high-level specification, afterwards I am remarking on or dwelling into specific parts of the text. Your last reply made it explicit that you mean totally strict setup on master (p.(2) of the following list): KN> (1). We want the master to "forget about the past" with respect to a given domain. This is easy. All that is needed is to rotate the binlog and omit the domain from the GTID_LIST event at the start of the new binlog. Because when the master searches back for a given GTID in the binlog, it stops when it sees a GTID_LIST event without that domain. KN> (2). We want to prevent a user accidentally putting the server into an inconsistent state with an incorrect DELETE DOMAIN command. This is ensured by the requirement that all existing binlog files are free of that domain. Should a slave later, incorrectly, try to access that domain, it will receive the wrong error (that it is diverged rather than that the necessary binlog file has been purged), but at least it _will_ get an error as it should, not silently corrupt replication. In contrast, I thought of a "liberal" setup provisioned by "the user must know what he is doing". And I did so seeing no other way to help out MDEV-12012 use case. Indeed, when the undesired domain events reside in the very last binlog file and history behind the last file is still important for the user your 4 step strict protocol of
1. FLUSH BINARY LOGS, note the new GTID position.
2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed.
3. PURGE BINARY LOGS to remove the errorneous logs.
4. FLUSH BINARY LOG DELETE DOMAIN domain
might be equivalent to RESET MASTER as the 'erroneous' log file is last. That's why I was content without p.3 and with p.4 that does not necessary error out. Naturally I am fine with the strictness of 1-4. But I can't say for the user whether the new unyielding (always erroring out that is) delete domain FLUSH LOGS would always satisfy. To dramatize mdev-12012 case with a complication, what if p.2 can't be not ensured, say, due to another temporarily stopped slave who (for simplicity) does not care for the being deleted domain? On one hand we can't purge the master's binlogs (the stopped slave constraint), on the other the p.4 alone suffices to either slave (though the stopped one may need reconfiguration to filter out the deleted domain's events). If my concern is practical we may consider *optionally* strict delete domain FLUSH LOGs. The errored out version would maintain a strict gtid semantics on Master. The liberal one would cover the above case as well. And the user would be to choose.
andrei.elkin@pp.inet.fi writes:
Let me propose methods to clean master off unused gtid domains. I would be glad to hear your opinions, dear colleagues.
So a bit of background: The central idea in MariaDB GTID is the sequence of events that created the current master state. This is an abstract concept. Conceptually, the current state of this server is defined as executing a specific sequence of events (in practice it might have been restored from a backup or something). Abstractly, the server's binlog is exactly this sequence of events (in practice the early part probably no longer exists or possibly never did). The sequence is multi-streamed (one stream per domain). Everything (in GTID, but also in parallel replication and group commit) is based on the assumption that each stream in the binlog sequence is strictly ordered, at least on a single given server.
It is important to understand that it is the actual sequence of events that matters, conceptually. The actual GTID format of D-S-N is only an implementation detail that allows the code to work correctly. The sequence is defined by the binlog, not by the particular sequence numbers in GTID or other details.
When a slave connects to our master server, it presents its current position as a single event within each stream. By the above, this is sufficient to reliably find the correct position in the binlog to restart the slave from.
Because MariaDB replication is async, we cannot in general prevent different servers from errorneously ending up with different binlog sequence. However, we can ensure a consistent view of the sequence on a single server, and we can try to detect and flag any inconsistencies between servers as they are noticed.
This is why it is necessary to give an error if a slave presents a position containing an event that is not in the master's binlog. The master cannot know if this is because the slave is ahead (the event in question will arrive later on the master), or because replication has diverged (the event will never arrive on the master, and the replication position is not well defined). It is a central goal in GTID to avoid, as much as possible, silent incorrect operation in replication.
With that explained, now onto some concrete comments/answers:
The past default domain-id is actually permanent past from the user perspective in these cases. Its events have been already replicated and none new will be generated and replicated.
But from the point of view of GTID semantics, the binlog sequence is still defined by this past, and in an inconsistent (and hence incorrect) way.
Therefore such domain conceptually may be cleaned away from either the masters and slave states.
So as you say, the errorneous state must be fixed for GTID to work correctly. One way is to discard the entire incorrect binlog with RESET MASTER. But this discussion is about fixing the binlog in-place, by (conceptually) replacing it with a variant which does not contain the problematic past.
The idea looks quite sane, I only could not grasp why presence of being deleted domains in the very first binlog's GTID_LIST_LOG_EVENT list is warrant for throwing error. Maybe we should leave it out to the user, Kristian? That is to decide what domain is garbage regardless of the binlog state history.
DELETE DOMAIN d1 replaces the conceptual binlog sequence with one in which domain d1 never existed. If there would be actual binlog files containing events in d1, this would be a grave inconsistency.
For example, if an existing slave was still replicating events in d1, if a temporary network error caused it to reconnect to the master, it would fail to reconnect. A slave without knowledge of d1 replicating might start re-applying any events encountered. Basically, after DELETE DOMAIN d1, any binlog file containing d1 is invalid and useless, so it seems appropriate to require the user to PURGE BINARY LOG them first.
'Invalid and useless' is fair as long as the user opts for the strict semantics. But his actual practice may demand flexibility, I hope my example above is relevant.
SET @@SESSION.gtid_seq_no=18446744073709551615; CREATE TABLE IF NOT EXISTS `table_dummy`; SHOW LOCAL VARIABLES LIKE '%gtid_binlog_pos%';
11-1-18446744073709551615
SET @@SESSION.gtid_seq_no=0 DROP TABLE `table_dummy`; SHOW LOCAL VARIABLES LIKE '%gtid_binlog_pos%';
11-1-0
Ouch. That's a bug. This should give an error, I think that could lead to all kinds of extremely nasty problems :-(
I agree. And you don't just mean the zero sequence number is bogus, do you? There must be some reaction on wrap-around itself I believe.
1. Leave wrapping around an old domain to the user via running the queries like above; 2. The binary logger would be made to react on the fact of wrap-around with binary log rotation ("internal" FLUSH BINARY LOG). And the new binlog file won't contain the wrapped "away" domain (because there are no new event group in it of yet).
I am not sure I understand you here. Are you suggesting that the GTID sequence wrap-around bug be instead declared a feature, and be documented as the way to delete a domain in the binlog? I do not think that is appropriate.
Let me highlight it a bit more. When the domain range gets filled up on Master, it can't just wrap it around and log on, even correctly starting with the sequence number 1. In presence of slaves something like your p.2 synchronization would be required before the domain range could be reset and the number 1 reused. But the synchronization (with all slaves) makes the domain obsolete. And your strict semantics would require p.3 purge at time the range becomes reused (otherwise we would have two binlog files with the same gtid). Therefore I think the domain wrap-around relates to the old domain deletion.
As I see it, there are two sides to this.
(1). We want the master to "forget about the past" with respect to a given domain. This is easy. All that is needed is to rotate the binlog and omit the domain from the GTID_LIST event at the start of the new binlog. Because when the master searches back for a given GTID in the binlog, it stops when it sees a GTID_LIST event without that domain.
(2). We want to prevent a user accidentally putting the server into an inconsistent state with an incorrect DELETE DOMAIN command. This is ensured by the requirement that all existing binlog files are free of that domain. Should a slave later, incorrectly, try to access that domain, it will receive the wrong error (that it is diverged rather than that the necessary binlog file has been purged), but at least it _will_ get an error as it should, not silently corrupt replication.
I think the requirement is a reasonable one. The domain was configured incorrectly, the binlog files containing it cannot be used safely with GTID. The procedure to fix it will then be:
1. FLUSH BINARY LOGS, note the new GTID position.
2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed.
3. PURGE BINARY LOGS to remove the errorneous logs.
4. FLUSH BINARY LOG DELETE DOMAIN d
It is of course an option to not do (2). Just be aware that this goes against the whole philosophy that GTID was designed around - to prioritise consistency and "no silent corruption".
Hope this helps. Of course feel free to ask for more details on any point that is not clear.
- Kristian.
Thank you for discussing it with me! Andrei
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
andrei.elkin@pp.inet.fi writes:
If my concern is practical we may consider *optionally* strict delete domain FLUSH LOGs. The errored out version would maintain a
In that case, I would compare to SET GLOBAL gtid_binlog_state. Currently, this is even more restricted, it is only allowed when the binlog is completely empty. If the philosophy is "the user knows what she is doing", then an unsafe SET GLOBAL gtid_binlog_state allows to do everything an unsafe DELETE DOMAIN would, and more. (Then SET GLOBAL gtid_binlog_state should be changed to not do implicit RESET MASTER, only implicit FLUSH BINARY LOGS). Then there is no need to introduce new syntax.
4. FLUSH BINARY LOG DELETE DOMAIN domain
might be equivalent to RESET MASTER as the 'erroneous' log file is last. That's why I was content without p.3 and with p.4 that does not necessary error out.
I do not think it is equivant. RESET MASTER requires to stop _all_ servers and reset the gtid position on all slaves. That is very disruptive. In contrast, the safe version of DELETE DOMAIN only requires a FLUSH BINARY LOG and waiting until slaves are up-to-date.
To dramatize mdev-12012 case with a complication, what if p.2 can't be not ensured, say, due to another temporarily stopped slave who (for simplicity) does not care for the being deleted domain?
I do not think this is a concern. I think it is reasonable in this case to require that all slaves be up to date with a FLUSH LOGS before switching the system to GTID. One criteria I use for choosing between a strict and a relaxed behaviour is to consider if the relaxed behaviour can be reasonably documented. In fact, it is a good idea to sketch a full documentation for a new feature early during design. If the behaviour in the relaxed case cannot be fully described in documentation, then it will be hard for the user to "know what she is doing". And easy to accidentally end up with an incorrect result. In this case, I find it hard to see how to fully document what happens with any left-over GTIDs in domains that were deleted. Or even fully understand myself how they will behave (or misbehave). Hence my recommendation to make it an error. - Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
If my concern is practical we may consider *optionally* strict delete domain FLUSH LOGs. The errored out version would maintain a
In that case, I would compare to SET GLOBAL gtid_binlog_state.
Fair enough.
Currently, this is even more restricted, it is only allowed when the binlog is completely empty.
If the philosophy is "the user knows what she is doing", then an unsafe SET GLOBAL gtid_binlog_state allows to do everything an unsafe DELETE DOMAIN would, and more. (Then SET GLOBAL gtid_binlog_state should be changed to not do implicit RESET MASTER, only implicit FLUSH BINARY LOGS). Then there is no need to introduce new syntax.
For the unsafe method yes. But I've never given up the strict one! :-)
4. FLUSH BINARY LOG DELETE DOMAIN domain
might be equivalent to RESET MASTER as the 'erroneous' log file is last. That's why I was content without p.3 and with p.4 that does not necessary error out.
I do not think it is equivant.
Sorry, I put that rather harsh. I only needed to point to the total purge of binlog as inferred by RESET MASTER.
RESET MASTER requires to stop _all_ servers and reset the gtid position on all slaves. That is very disruptive. In contrast, the safe version of DELETE DOMAIN only requires a FLUSH BINARY LOG and waiting until slaves are up-to-date.
Right, and then the binlog can be totally and painless purged.
To dramatize mdev-12012 case with a complication, what if p.2 can't be not ensured, say, due to another temporarily stopped slave who (for simplicity) does not care for the being deleted domain?
I do not think this is a concern. I think it is reasonable in this case to require that all slaves be up to date with a FLUSH LOGS before switching the system to GTID.
I don't have any strong objection. After all, we are all ears for demands from practical field and could relax the feature's strictness.
One criteria I use for choosing between a strict and a relaxed behaviour is to consider if the relaxed behaviour can be reasonably documented. In fact, it is a good idea to sketch a full documentation for a new feature early during design. If the behaviour in the relaxed case cannot be fully described in documentation, then it will be hard for the user to "know what she is doing". And easy to accidentally end up with an incorrect result.
To open more about my way of thinking, and why I liked the unsafe method is that we already have the strict and non-strict modes. Your safe purge method perfectly fits to the former. The relaxed method I was going to merge to the non-strict mode whose guarantees I am yet to learn.. Having said this, I am aware of a non-strict master must be much more dangerous than a similar slave .. to further dump the idea.
In this case, I find it hard to see how to fully document what happens with any left-over GTIDs in domains that were deleted. Or even fully understand myself how they will behave (or misbehave). Hence my recommendation to make it an error.
Which is taken. Thanks for your time, pieces of creativity and patience!
- Kristian.
Andrei
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian, hello. Now to the implementation matter,
The procedure to fix it will then be:
1. FLUSH BINARY LOGS, note the new GTID position.
2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed.
3. PURGE BINARY LOGS to remove the errorneous logs.
4. FLUSH BINARY LOG DELETE DOMAIN d
I think we could optimize the list. How about 1. Take note of @@global.gtid_binlog_state 2. Ensure that all slaves are past the last event of being deleted domain 'd' 3. PURGE BINARY LOGS DELETE DELETE 'd' The effect of the last step would include purging all the binary log files plus a planned implicit FLUSH LOGS discarding 'd' from the new emerged binlog. It's one command less and besides PURGE as the command name reflects better the action. Perhaps you shared that feeling when spoke 'PURGE' in your initial response to 12012. Please tell me how this looks from your perspective. Thanks! Andrei
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
andrei.elkin@pp.inet.fi writes:
1. Take note of @@global.gtid_binlog_state 2. Ensure that all slaves are past the last event of being deleted domain 'd' 3. PURGE BINARY LOGS DELETE DELETE 'd'
The effect of the last step would include purging all the binary log files plus a planned implicit FLUSH LOGS discarding 'd' from the new emerged binlog.
Which binary log files would be purged, exactly? All those containing the domain d?
It's one command less and besides PURGE as the command name reflects better the action. Perhaps you shared that feeling when spoke 'PURGE' in your initial response to 12012.
I did mean FLUSH initially. FLUSH seemed more natural to me. The MDEV-12012 is a very special use case. DELETE DOMAIN will commonly be used just to clean up old domains that are no longer used, not because they are a problem but just to clean out the GTID position. Regular automatic binlog file purge might already have purged all traces of the old domain. So in this case only a single binlog rotation is needed, rewriting the GTID_LIST event of the new binlog file. This is natural for FLUSH, which is just a forced binlog rotation. As a user, I would personally prefer first manually specifying which binlog files to purge, and then after getting an error if I purged too little. To avoid accidentally purging too much. But on the other hand, as you say, having the set of binlog files to purge determined automatically can also be seen as convenient. So I think either way could work. It should be possible to delete an old domain without actually purging any binlog files, in the case where the domain is already absent from all remaining binlog files. Hope this helps, - Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
1. Take note of @@global.gtid_binlog_state 2. Ensure that all slaves are past the last event of being deleted domain 'd' 3. PURGE BINARY LOGS DELETE DELETE 'd'
The effect of the last step would include purging all the binary log files plus a planned implicit FLUSH LOGS discarding 'd' from the new emerged binlog.
Which binary log files would be purged, exactly? All those containing the domain d?
Right. So when the master has at PURGE invocation time two binlog files: b.1 (contains a 'd' group), b.2 (does not contain any 'd' group, but contains 'd' in its GTID_List) PURGE delete 'd' would result in b.2, b.3 where b.3 does not even have 'd' in its GTID_List header. I gather remained presence 'd' in non-active b.2's GTID_List is harmless.
It's one command less and besides PURGE as the command name reflects better the action. Perhaps you shared that feeling when spoke 'PURGE' in your initial response to 12012.
I did mean FLUSH initially.
FLUSH seemed more natural to me. The MDEV-12012 is a very special use case. DELETE DOMAIN will commonly be used just to clean up old domains that are no longer used, not because they are a problem but just to clean out the GTID position. Regular automatic binlog file purge might already have purged all traces of the old domain. So in this case only a single binlog rotation is needed, rewriting the GTID_LIST event of the new binlog file. This is natural for FLUSH, which is just a forced binlog rotation.
Indeed, this would be a degenerative case for my new option PURGE.
As a user, I would personally prefer first manually specifying which binlog files to purge, and then after getting an error if I purged too little. To avoid accidentally purging too much.
But on the other hand, as you say, having the set of binlog files to purge determined automatically can also be seen as convenient. So I think either way could work.
Automatic check is as simple as iteratively search for the first positive gt-check between GTID sequence numbers from two GTID_List:s for a specified domain. The first compares @@global.gtid_binlog_state.d.seqno > the 1st binlog file's GTID_List.d.seqno The following ones compare GTID_List.d.seqno of the previous file vs the current one's. The procedure stops at first positive which indicates the file to end purging on inclusive.
It should be possible to delete an old domain without actually purging any binlog files, in the case where the domain is already absent from all remaining binlog files.
Yes, the degenerative use case as above.
Hope this helps,
- Kristian.
It certainly does. Have a nice weekend, Kristian! Andrei
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Hello, everybody, Kristian.
1. Take note of @@global.gtid_binlog_state 2. Ensure that all slaves are past the last event of being deleted domain 'd'
3. PURGE BINARY LOGS DELETE DELETE 'd'
While I was selecting between two candidates where to settle the new option to discard the obsolete domain from Gtid_list of the most recent binlog file, another candidate popped out. And really why not 3. SET @@GLOBAL.gtid_binlog_state=list-without-d; where `list-without-d' sublists the p.1 SELECT @@global.gtid_binlog_state with the obsolete domain discarded? The docs currently say The main usage of @@gtid_binlog_state is to restore the state of the binlog after RESET MASTER (or equivalently if the binlog files are lost). Now when we've defined what the domain-id purge should be, I find RESET-MASTER requirement can be refined/relaxed to the following one: the new state value must cover (include as a math set) all gtids in the current list of binlog files. (Naturally I consider [presume] Gtid_list header of a binlog file assumes there are no gaps or out-of-order in the preceding files. That is it provides the right-hand-side of gtid seqno range for each domain-server pair). Specifically to the obsolete domain case, `list-without-d' new value would reflect gtids from files remained after a user PURGE files containing 'd'. Naturally we would leave the backward compatible behavior in case of the list of binlog files is empty (as after RESET MASTER). As an example: Let binlog consists of two files b.1 /* contains one event from domain 0 */ b.2 /* does not contain domain 0 */ with their header Gtid_lists as b.1.Gtid_list=\empty b.2.Gtid_list={0-1-1} and we would like to forget the domain 0 ('d'). So we conduct the 1-3 procedure. Pseudo-sql would go like mysql> /* 1. */ SELECT @@global.gtid_binlog_state into @pre_purge_state; mysql> /* 2. */ PURGE LOGS to 'b.2' mysql> /* 3. */ SET @@global.gtid_binlog_state="REPLACE"(@pre_purge_state, '0-1-1', ''); At the 3rd step the SET handler would implicitly execute FLUSH LOGS to rotate binlog to b.3 where the new value gets adopted only when the refined check succeeds. Otherwise the SET errors out. The check technically is a comparison between Gtid_list.d term in the oldest file and @pre_purge_state.d one. If they are equal the current binlog files are proven not to contain events from 'd'. I find the SET method as fitting to the matter. Backward compatibility is in place. No new options is required. The burden of PURGE may not necessarily be left to the user though. The proposed new 'delete domain_id' option can be implemented (now, later?). Your feedback is welcome! Cheers, Andrei
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
Andrei Elkin
And really why not
3. SET @@GLOBAL.gtid_binlog_state=list-without-d;
The main issue I see with this if the master is actively adding new transactions (in other domains than d). It will be hard for the user to know the right value to set without doing something like FLUSH TABLES WITH READ LOCK during the operation to stop transactions between selecting @@global.gtid_binlog_state and SETting the new value. Whereas FLUSH BINARY LOGS DELETE DOMAIN d only needs a shorter-lived lock on the binlog while doing the rotation and check. Other than that, I agree SET gtid_binlog_state could be a good fit.
the new state value must cover (include as a math set) all gtids in the current list of binlog files. (Naturally I consider [presume] Gtid_list header of a binlog file assumes there are no gaps or out-of-order in the preceding files. That is it provides the right-hand-side of gtid seqno range for each domain-server pair).
Yes. (There is no strict requirement of no gaps in MariaDB GTID, but it should not matter here).
So we conduct the 1-3 procedure. Pseudo-sql would go like
mysql> /* 1. */ SELECT @@global.gtid_binlog_state into @pre_purge_state; mysql> /* 2. */ PURGE LOGS to 'b.2' mysql> /* 3. */ SET @@global.gtid_binlog_state="REPLACE"(@pre_purge_state, '0-1-1', '');
So this requires that there are no new transactions written to binlog between (1) and (3).
The check technically is a comparison between Gtid_list.d term in the oldest file and @pre_purge_state.d one. If they are equal the current binlog files are proven not to contain events from 'd'.
Agree. - Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Andrei Elkin
writes: And really why not
3. SET @@GLOBAL.gtid_binlog_state=list-without-d;
The main issue I see with this if the master is actively adding new transactions (in other domains than d). It will be hard for the user to know the right value to set without doing something like FLUSH TABLES WITH READ LOCK during the operation to stop transactions between selecting @@global.gtid_binlog_state and SETting the new value.
Whereas FLUSH BINARY LOGS DELETE DOMAIN d only needs a shorter-lived lock on the binlog while doing the rotation and check.
Right.
Other than that, I agree SET gtid_binlog_state could be a good fit.
Then a function to discard a domain term would do: SET @@gtid_binlog_state=gtid_discard_domain(@@gtid_binlog_state,'d') While this time it would be new object introduced still it's of var setting semantics and might be generally useful too. How about it? Andrei
the new state value must cover (include as a math set) all gtids in the current list of binlog files. (Naturally I consider [presume] Gtid_list header of a binlog file assumes there are no gaps or out-of-order in the preceding files. That is it provides the right-hand-side of gtid seqno range for each domain-server pair).
Yes. (There is no strict requirement of no gaps in MariaDB GTID, but it should not matter here).
So we conduct the 1-3 procedure. Pseudo-sql would go like
mysql> /* 1. */ SELECT @@global.gtid_binlog_state into mysql> @pre_purge_state; mysql> /* 2. */ PURGE LOGS to 'b.2' mysql> /* 3. */ SET mysql> @@global.gtid_binlog_state="REPLACE"(@pre_purge_state, mysql> '0-1-1', '');
So this requires that there are no new transactions written to binlog between (1) and (3).
The check technically is a comparison between Gtid_list.d term in the oldest file and @pre_purge_state.d one. If they are equal the current binlog files are proven not to contain events from 'd'.
Agree.
- Kristian.
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
andrei.elkin@pp.inet.fi writes:
Then a function to discard a domain term would do:
SET @@gtid_binlog_state=gtid_discard_domain(@@gtid_binlog_state,'d')
While this time it would be new object introduced still it's of var setting semantics and might be generally useful too.
I'm not sure I understand. Would this function magically lock the binlog until the SET gtid_binlog_state completes, to solve the race? Or just a convenience for the user to not have to do the string replace operation manually? - Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian Nielsen
andrei.elkin@pp.inet.fi writes:
Then a function to discard a domain term would do:
SET @@gtid_binlog_state=gtid_discard_domain(@@gtid_binlog_state,'d')
While this time it would be new object introduced still it's of var setting semantics and might be generally useful too.
I'm not sure I understand. Would this function magically lock the binlog until the SET gtid_binlog_state completes, to solve the race?
Or just a convenience for the user to not have to do the string replace operation manually?
The 2nd one. While it replaces p.3 SET the first p.1 SELECT into @pre_purge_state in
mysql> /* 1. */ SELECT @@global.gtid_binlog_state into mysql> @pre_purge_state; mysql> /* 2. */ PURGE LOGS to 'b.2' mysql> /* 3. */ SET mysql> @@global.gtid_binlog_state="REPLACE"(@pre_purge_state, mysql> '0-1-1', '');
is unnecessary. So the function would subtract 'd' term from the current @@gtid_binlog_state right at the point of new value SET invocation. This solves concurrent updates to other than 'd' terms which will be reflected in the new binlog file (SET handler therefore gains your version of FLUSH logics). Andrei
![](https://secure.gravatar.com/avatar/549a8d764f7b52743868af4448e1bb3f.jpg?s=120&d=mm&r=g)
Hi all,
On 8 Sep 2017, at 16:48, andrei.elkin@pp.inet.fi wrote:
Kristian, hello.
Now to the implementation matter,
The procedure to fix it will then be:
1. FLUSH BINARY LOGS, note the new GTID position.
2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed.
3. PURGE BINARY LOGS to remove the errorneous logs.
4. FLUSH BINARY LOG DELETE DOMAIN d
I think we could optimize the list. How about
1. Take note of @@global.gtid_binlog_state 2. Ensure that all slaves are past the last event of being deleted domain 'd' 3. PURGE BINARY LOGS DELETE DELETE 'd'
The effect of the last step would include purging all the binary log files plus a planned implicit FLUSH LOGS discarding 'd' from the new emerged binlog.
removing old binary logs should _not_ IMO be done as a way of forgetting the past obsolete domains. BINLOGS are important so throwing them away is an issue. I think that somehow the code needs to be aware of the cut-off point and when the “stale domain ids” are removed.) DBAs do not like to remove bin logs “early" as unless you keep a copy somewhere you may lose valuable information, for recovery, for backups etc. Not everyone will be making automatic copies (as MySQL does not provide an automatic way to do this) so in theory you have just one copy. Throwing these away is a really bad idea if it’s part of the solution of forgetting about “some of the past”. Please consider the operational point of view and make MariaDB aware of the past and aware that it can ignore/forget these domain ids. Obviously once all appropriate bin logs have been purged (naturally by other means) then no special processing will be needed. The other comment I see mentioned here was “make sure all slaves are up to date”. That’s going to be hard. The master can only be aware of “connected slaves” and if you have intermediate masters, or a stopped slave then it won’t be aware of these servers. That may be obvious but there’s always the situation that “stopped slaves” or “downstream slaves (of an intermediate master)” are still lagging. Of course catching and checking that is going to be hard to please make the comments explicit if really all you are going to do is to check “connected slaves” as MariaDB is never going to be aware of servers not connected directly to the master. If the required pre-conditions to trigger the “obsolete old domains” is that a DBA needs to be “aware” then make this requirement clear so that people reading the documentation understand what’s needed and what MariaDB expects to see etc. FWIW expiring old domains is good to do. There’s a similar FR for Oracle’s MySQL and while the GTID implementations are completely different the problem space is the same. Coming up with a solution which is simple to use and understand and also avoids where that’s possible making mistakes which may break replication is good. So thanks for looking at this. Just a thought. Simon
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
Simon Mudd
ids. Obviously once all appropriate bin logs have been purged (naturally by other means) then no special processing will be needed.
Right. Hence my original idea (which was unfortunately never implemented so far). If at some point a domain has been unused for so long that all GTIDs in that domain are gone, it is relatively safe to pretend that the domain never existed. I would like to understand if you can think of significant use cases where the DBA needs to have active binlog files in the master containing some domain, while simultaneously pretending that this domain never existed. Or if it is more of a general concern, and the inconvenience for users to have to save old binlogs somewhere else than the master's data directory and binlog index (SHOW BINARY LOGS).
removing old binary logs should _not_ IMO be done as a way of forgetting the past obsolete domains. BINLOGS are important so throwing them away is an issue. I think that somehow the code needs to be aware of the cut-off point and when the “stale domain ids” are removed.)
I understand the desire to not delete binlog files. The problem is: If you want to have GTIDs with some domain in your active binlog files, _and_ you also want to pretend that this domain never existed, what does it mean? What is the semantics? It creates a lot of complexities for defining the semantics, for documenting it, for the users to understand it, and for the code to implement it correctly. So basically, I do not understand what is the intended meaning of FLUSH BINARY LOGS DELETE DOMAIN d _and_ at the same time keeping GTIDs with domain d around in active binlog files? In what respects is the domain deleted, and in what respects not? For the master, the binlog files are mainly used to stream to connecting slaves. Deleting a domain means replacing the conceptual binlog history with one in which that domain never existed. So that domain will be ignored in a connecting slaves position, assuming it is served by another multi-source master. If a new GTID in that domain appears later, it will be considered the very first GTID ever in that domain. So consider what happens if there is anyway GTIDs in that domain deeper in the binlog: 1. An already connected slave may be happily replicating those GTIDs. If that slave reconnects (temporary network error for example), it will instead fail with unknown GTID, or perhaps just start silently ignoring all further GTIDs in that domain. This kind of unpredictable behaviour seems bad. 2. Suppose a slave connects with a position without the deleted domain. The master starts reading the binlog from some point. What happens if a GTID is encountered that contains the deleted domain? The slave will start replicating that domain from some arbitrary point that depends on where it happened to be in other domains at the last disconnect. This also seems undesirable. There may be other scenarios that I did not think about.
DBAs do not like to remove bin logs “early" as unless you keep a copy somewhere you may lose valuable information, for recovery, for backups etc. Not everyone will be making automatic copies (as MySQL does not provide an automatic way to do this)
Understood. Maybe what is needed is a PURGE BINARY LOGS that removes the entries from the binlog index (SHOW BINARY LOGS), but leaves the files in the file system for the convenience of the sysadmin? (Well, you can just hand-edit binlog.index, but that requires master restart I think).
The other comment I see mentioned here was “make sure all slaves are up to date”. That’s going to be hard. The master can only be aware of “connected slaves” and if you have intermediate masters, or a
Indeed, the master cannot ensure this. The idea is that the DBA, who decides to delete a domain, must understand that this should not be done if any slave still needs GTIDs from that domain. This is similar to configuring normal binlog purge, where the DBA needs to ensure that binlogs are kept long enough for the needs of the slowest slave.
FWIW expiring old domains is good to do. There’s a similar FR for
completely different the problem space is the same. Coming up with a solution which is simple to use and understand and also avoids where that’s possible making mistakes which may break replication is good. So thanks for looking at this.
Indeed. And the input from people like you with strong operational experience is very valuable to end up with a good solution, hence my request for additional input. - Kristian.
![](https://secure.gravatar.com/avatar/549a8d764f7b52743868af4448e1bb3f.jpg?s=120&d=mm&r=g)
Hi Kristian, Sorry for the late response.
On 12 Sep 2017, at 10:21, Kristian Nielsen
wrote: Simon Mudd
writes: ids. Obviously once all appropriate bin logs have been purged (naturally by other means) then no special processing will be needed.
Right. Hence my original idea (which was unfortunately never implemented so far). If at some point a domain has been unused for so long that all GTIDs in that domain are gone, it is relatively safe to pretend that the domain never existed.
I would like to understand if you can think of significant use cases where the DBA needs to have active binlog files in the master containing some domain, while simultaneously pretending that this domain never existed.
The only case I can think of would be this: Imagine a replication chain of M[aster] —> S[lave]1, S[lave]2, A[ggregate]1 and A[ggregate]1 —> A[ggregate]2 , A[ggregate]3, …. A1 one has all the data of M but also extra databases where aggregate information is made of the original data in M/S1/… Additionally it might make sense that writes to A1 use a different domain id. If M dies and say A1 happens to be more up to date than S1, S2 then we may want to promote A1 to be the new master, and move S1, S2 under A1, move A2 under A1 (but promote as the aggregate writeable master), and move A3 under A2. This would not be the “desired” setup as probably we’d end up thowing away all the aggregate data on A1. The topology would end up as: A1 [ignore any aggregate database/data] -> S1, S2, A2 and A2 —> A3 In this specific case it may be you really do want to hide the 2 sets of domains and only show one to the S1, S2 boxes, but maintain 2 domains on A2, A3. I have such a setup but have never had to handle such a failure scenario and it may be that there are better ways to handle this but one thing I’m sure about: I wouldn’t want to wipe out the binlogs especially on A1 as they hold valuable information which may not be stored anywhere else. Does that answer your question?
Or if it is more of a general concern, and the inconvenience for users to have to save old binlogs somewhere else than the master's data directory and binlog index (SHOW BINARY LOGS).
If you have to "save binlogs somewhere” you’re doing stuff manually. If you manage a lot of servers that’s really undesirable. ...
I understand the desire to not delete binlog files.
The problem is: If you want to have GTIDs with some domain in your active binlog files, _and_ you also want to pretend that this domain never existed, what does it mean? What is the semantics? It creates a lot of complexities for defining the semantics, for documenting it, for the users to understand it, and for the code to implement it correctly.
Yes. However, it also reflects realities of a slave connecting and trying to get back in sync to a master which may have “more information” (in terms of GTID info) than the slave but which is unable to “serve that information” to the slave. I’ve forgotten now exactly how this is handled by MariaDB but know that in a situation like that with MySQL GTID the sync just won’t work as you can’t afaik tell the slave to “not worry about “uuid:X” or “domain:Y” but just try to sync the rest. And in MySQL GTID at least injecting millions of empty events to get the slave’s GTID state inline with the missing UUID would be well ... rather stupid. In the perfect world this situation never happens. In the real world it does and the DBA often has to just live with inconsistencies between data stored on a master and a slave (which he can fix later) but *let replication flow*. It depends but in my opinion in most cases letting replication flow is more important than having 100% master and slave consistency. The longer the slave is stopped the more differences there are. And when you get in a situation like this you’re very tempted to go back to binlog file plus position, to scan the bin logs with tools like mysqlbinlog and do it the old way like we used to do years ago. This is tedious and error prone but if you’re careful it works fine. The whole idea of GTID is to avoid the DBA ever having to do this…
So basically, I do not understand what is the intended meaning of FLUSH BINARY LOGS DELETE DOMAIN d _and_ at the same time keeping GTIDs with domain d around in active binlog files? In what respects is the domain deleted, and in what respects not?
expire_logs_days allows you to keep bin logs for a time you’re comfortable with. So you can restore from an old system and then roll forward and do point in time recovery or simply catch up with the master. Normally slaves are _well_ ahead of the oldest bin logs. You have a copy of the bin logs on the master and if log_slave_updates is enabled an "equivalent backup” on a number of slaves. You normally don’t expect to use these files, but they’re there if you need them. If you wipe binlogs earlier than the configure expiration then you may need to keep those bin logs somewhere else. That’s just more to manage as the expiry is handled “automatically" for you by MariaDB. So I see the DELETE DOMAIN (MariaDB) or “remove old UUID” (MySQL) type request to be one that means the master will only pretend that it can serve or knows about the remaining domains or UUIDs and if the slaves are sufficiently up to date they really don’t care as their vision will be similar. Such a command would be replicated, right? It has to be for the slaves to change “their view” at the same moment in replication (not necessarily time) as the master. You need to be aware that slave’s won’t be able to request back before this point but that’s our job. This is after all a clean up / housekeeping type problem, and not something you’ll do much. My guess once every few months or once a year and that’s if you have a large number of domains or uuids. So with MariaDB replication I’d not expect this to happen much. For Oracle GTID since there’s a UUID for each master then master switchovers will add a new UUID to the list and if you do a few of these a year it seems quite possible that you may have a gtid_executed containing 20 or 30 UUIDs. That really is messy and in need of a clean up. MariaDB’s situation is likely I think to require a much smaller number of such clean ups but the idea of why this might be needed I think is the same.
For the master, the binlog files are mainly used to stream to connecting slaves. Deleting a domain means replacing the conceptual binlog history with one in which that domain never existed. So that domain will be ignored in a connecting slaves position, assuming it is served by another multi-source master. If a new GTID in that domain appears later, it will be considered the very first GTID ever in that domain.
So consider what happens if there is anyway GTIDs in that domain deeper in the binlog:
1. An already connected slave may be happily replicating those GTIDs. If that slave reconnects (temporary network error for example), it will instead fail with unknown GTID, or perhaps just start silently ignoring all further GTIDs in that domain. This kind of unpredictable behaviour seems bad.
2. Suppose a slave connects with a position without the deleted domain. The master starts reading the binlog from some point. What happens if a GTID is encountered that contains the deleted domain? The slave will start replicating that domain from some arbitrary point that depends on where it happened to be in other domains at the last disconnect. This also seems undesirable.
There may be other scenarios that I did not think about.
The maintenance we’re talking about should happen rather infrequently. So the situations of it going wrong and “causing confusion” are because you don’t control your replication well enough or are too eager to remove domains while adding new ones. In practice I think this is unlikely to happen but if it does it will be user error because they did not understand what they were doing. Appropriate warnings are definitely in order in the documentation of such commands and their usage.
DBAs do not like to remove bin logs “early" as unless you keep a copy somewhere you may lose valuable information, for recovery, for backups etc. Not everyone will be making automatic copies (as MySQL does not provide an automatic way to do this)
Understood. Maybe what is needed is a PURGE BINARY LOGS that removes the entries from the binlog index (SHOW BINARY LOGS), but leaves the files in the file system for the convenience of the sysadmin? (Well, you can just hand-edit binlog.index, but that requires master restart I think).
You’re back to doing stuff manually. Please: no. It doesn’t scale.
The other comment I see mentioned here was “make sure all slaves are up to date”. That’s going to be hard. The master can only be aware of “connected slaves” and if you have intermediate masters, or a
Indeed, the master cannot ensure this. The idea is that the DBA, who decides to delete a domain, must understand that this should not be done if any slave still needs GTIDs from that domain. This is similar to configuring normal binlog purge, where the DBA needs to ensure that binlogs are kept long enough for the needs of the slowest slave.
Yep. The DBA needs to be smart enough to “not worry about the problem” or to understand what he’s doing. So documentation needs warnings, though of course no-one ever reads the docs...
FWIW expiring old domains is good to do. There’s a similar FR for
completely different the problem space is the same. Coming up with a solution which is simple to use and understand and also avoids where that’s possible making mistakes which may break replication is good. So thanks for looking at this.
Indeed. And the input from people like you with strong operational experience is very valuable to end up with a good solution, hence my request for additional input.
Thanks for sharing your idea of things. Most of these things seem unimportant if you manage one or two servers and can shut them down and reconfigure them at your own convenience. However, when you manage more systems and can not shutdown masters the software (MariaDB/MySQL) needs to help you and make this sort of task an easy no-brainer which we can forget about, allowing us to get on with other things. Any help you can provide in solving these problems is certainly appreciated. Simon
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
Simon, thanks for your detailed answer. I see your point on having access to powerful tools when they are needed, even when such tools can be dangerous when used incorrectly. It reminds me of the old "goto considered harmful" which I never agreed with. It occurs to me that there are actually implicitly two distict features being discussed here. One is: Forget this domain_id, it has been unused since forever, but do check that it actually _is_ unused to avoid mistakes (the check would be that the domain is already absent from all available binlog files). This is the one I originally had in mind. Another is: There is this domain_id in the old binlog files, it is causing problems, we need to recover and we know what we are doing. I think this is the one you have in mind in what you write, and it seems very valid as well. It helped me to think of them explicitly as two distinct features. Also, Andrei's suggestion to fix IGNORE_DOMAIN_IDS to be able to connect to a master with that domain and completely ignore it seems useful for some of the scenarious you mention.
Imagine a replication chain of M[aster] —> S[lave]1, S[lave]2, A[ggregate]1 and A[ggregate]1 —> A[ggregate]2 , A[ggregate]3, ….
If M dies and say A1 happens to be more up to date than S1, S2 then we may want to promote A1 to be the new master, and move S1, S2 under A1, move A2 under A1 (but promote as the aggregate writeable master), and move A3 under A2. This would not be the “desired” setup as probably we’d end up thowing away all the aggregate data on A1.
Right, I see. Throwing away table data needs matching editing of the binlog history to give a consistent replication state. And indeed in a failover scenario, waiting for logs to be purged/purgeable does not seem appropriate.
In this specific case it may be you really do want to hide the 2 sets of domains and only show one to the S1, S2 boxes, but maintain 2 domains on A2, A3.
Agree. So a fixed IGNORE_DOMAIN_IDS would seem helpful here.
It depends but in my opinion in most cases letting replication flow is more important than having 100% master and slave consistency. The longer the slave is stopped the more differences there are.
And when you get in a situation like this you’re very tempted to go back to binlog file plus position, to scan the bin logs with tools like mysqlbinlog and do it the old way like we used to do years ago. This is tedious and error prone but if you’re careful it works fine. The whole idea of GTID is to avoid the DBA ever having to do this…
Right. Though once multiple domains are involved, the binlog is effectively multiple streams, and using the old-style single file/offset position may be tricky. But if IGNORE_DOMAIN_IDS works for master connection as well, then the slave has the ability to say exactly which domains it wants to see, and exactly where in each of those domains it wants to start (gtid_slave_pos), so that should be quite flexible. When I designed GTID I actually had this very much in mind, to allow GTID to be a full replacement for the old style of replication and to allow to do what is needed to solve the problem at hand. For example, this is why the code tries so hard to deal with out-of-order GTID sequence numbers (as opposed to just refusing to ever operate with those). On the other hand, it was also a goal to be much more consistent and strict and try to prevent silent failures and inconsistencies. These two goals tend to get in conflicts in some areas. Hence for example the gtid_strict_mode. There are still a few features that were never implemented but should have been (like DELETE DOMAIN and binlog indexes for example), and it is surely not perfect.
So I see the DELETE DOMAIN (MariaDB) or “remove old UUID” (MySQL) type request to be one that means the master will only pretend that it can serve or knows about the remaining domains or UUIDs and if the slaves are sufficiently up to date they really don’t care as their vision will be similar. Such a command would be replicated, right? It has to be for the slaves to change “their view” at the same moment in replication (not necessarily time) as the master.
Hm, good point about whether it will be replicated. FLUSH LOGS is replicated by default with an option not to, so a DELETE DOMAIN would be also, I suppose. This makes it seem even more dangerous, frankly. Imagine an active domain being deleted by mistake, now the mistake immediately propagates to all servers in the replication topology, ouch. Maybe there should be an option, for example FLUSH BINARY LOGS DELETE DOMAIN 10 NOCHECK or FLUSH BINARY LOGS DELETE DOMAIN 10 ALLOW ACTIVE or something. Note that the effect of deleting a domain is basically to add at the head of the binlog a mark that says the domain never existed. All of the old binlog is unchanged. So the command does not really immediately affect running replication, only new slave re-connections. Hope this helps, - Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Simon, Kristian, salute.
Simon, thanks for your detailed answer.
I see your point on having access to powerful tools when they are needed, even when such tools can be dangerous when used incorrectly. It reminds me of the old "goto considered harmful" which I never agreed with.
It occurs to me that there are actually implicitly two distict features being discussed here.
One is: Forget this domain_id, it has been unused since forever, but do check that it actually _is_ unused to avoid mistakes (the check would be that the domain is already absent from all available binlog files). This is the one I originally had in mind.
Another is: There is this domain_id in the old binlog files, it is causing problems, we need to recover and we know what we are doing. I think this is the one you have in mind in what you write, and it seems very valid as well.
It helped me to think of them explicitly as two distinct features.
Also, Andrei's suggestion to fix IGNORE_DOMAIN_IDS to be able to connect to a master with that domain and completely ignore it seems useful for some of the scenarious you mention.
And this would be for a Master-Slave where Master knows more domains than Slave. In a reverse scenario (mdev-12012 is a sort of that) when Slave knows more, we've also learned (thanks to Kristian, see mdev-12012 latest comments) another method to ignore *discrepancy* via faking the "problematic" master's binlog state with a dummy gtid group. After it's done Slave should be able to connect to such Master. This observation must be relieving as don't have to consider keeping the old logs with discarded domain's events. The new FLUSH-delete-domain is to run at the user convenience.
Imagine a replication chain of M[aster] ―> S[lave]1, S[lave]2, A[ggregate]1 and A[ggregate]1 ―> A[ggregate]2 , A[ggregate]3, ….
If M dies and say A1 happens to be more up to date than S1, S2 then we may want to promote A1 to be the new master, and move S1, S2 under A1, move A2 under A1 (but promote as the aggregate writeable master), and move A3 under A2. This would not be the “desired” setup as probably we’d end up thowing away all the aggregate data on A1.
Right, I see. Throwing away table data needs matching editing of the binlog history to give a consistent replication state. And indeed in a failover scenario, waiting for logs to be purged/purgeable does not seem appropriate.
In this specific case it may be you really do want to hide the 2 sets of domains and only show one to the S1, S2 boxes, but maintain 2 domains on A2, A3.
Agree. So a fixed IGNORE_DOMAIN_IDS would seem helpful here.
True.
It depends but in my opinion in most cases letting replication flow is more important than having 100% master and slave consistency. The longer the slave is stopped the more differences there are.
And when you get in a situation like this you’re very tempted to go back to binlog file plus position, to scan the bin logs with tools like mysqlbinlog and do it the old way like we used to do years ago. This is tedious and error prone but if you’re careful it works fine. The whole idea of GTID is to avoid the DBA ever having to do this…
Right. Though once multiple domains are involved, the binlog is effectively multiple streams, and using the old-style single file/offset position may be tricky.
But if IGNORE_DOMAIN_IDS works for master connection as well, then the slave has the ability to say exactly which domains it wants to see, and exactly where in each of those domains it wants to start (gtid_slave_pos), so that should be quite flexible.
When I designed GTID I actually had this very much in mind, to allow GTID to be a full replacement for the old style of replication and to allow to do what is needed to solve the problem at hand. For example, this is why the code tries so hard to deal with out-of-order GTID sequence numbers (as opposed to just refusing to ever operate with those).
On the other hand, it was also a goal to be much more consistent and strict and try to prevent silent failures and inconsistencies. These two goals tend to get in conflicts in some areas. Hence for example the gtid_strict_mode.
I can only add up, the master side (think of fan related metaphor) is always better be strict.
There are still a few features that were never implemented but should have been (like DELETE DOMAIN and binlog indexes for example), and it is surely not perfect.
So I see the DELETE DOMAIN (MariaDB) or “remove old UUID” (MySQL) type request to be one that means the master will only pretend that it can serve or knows about the remaining domains or UUIDs and if the slaves are sufficiently up to date they really don’t care as their vision will be similar. Such a command would be replicated, right? It has to be for the slaves to change “their view” at the same moment in replication (not necessarily time) as the master.
Hm, good point about whether it will be replicated.
FLUSH LOGS is replicated by default with an option not to, so a DELETE DOMAIN would be also, I suppose. This makes it seem even more dangerous, frankly. Imagine an active domain being deleted by mistake
So the point is to have a slave that is not affected and can rectify (e.g with fail-over to it as promoted Master)?
, now the mistake immediately propagates to all servers in the replication topology, ouch.
Maybe there should be an option, for example
FLUSH BINARY LOGS DELETE DOMAIN 10 NOCHECK
or
FLUSH BINARY LOGS DELETE DOMAIN 10 ALLOW ACTIVE
Something like this and also the choice between 'NOCHECK' and 'ALLOW ACTIVE' would be mandatory, that is no replication default for 'DELETE DOMAIN'. So the user first weighs how much risky it would be replicate.
or something. Note that the effect of deleting a domain is basically to add at the head of the binlog a mark that says the domain never existed. All of the old binlog is unchanged. So the command does not really immediately affect running replication, only new slave re-connections.
Hope this helps,
- Kristian.
Thanks for a good piece of analysis, colleagues! Cheers, Andrei
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Hello. I've completed with the patch which passes few tests. However I had to make one concession with regard to replication. Actually..
So I see the DELETE DOMAIN (MariaDB) or “remove old UUID” (MySQL) type request to be one that means the master will only pretend that it can serve or knows about the remaining domains or UUIDs and if the slaves are sufficiently up to date they really don’t care as their vision will be similar. Such a command would be replicated, right? It has to be for the slaves to change “their view” at the same moment in replication (not necessarily time) as the master.
Hm, good point about whether it will be replicated.
FLUSH LOGS is replicated by default with an option not to, so a DELETE DOMAIN would be also, I suppose. This makes it seem even more
the "vanilla" FLUSH-LOGS is not binlogged by decision commented in reload_acl_and_cache(): if (options & REFRESH_BINARY_LOG) { /* Writing this command to the binlog may result in infinite loops when doing mysqlbinlog|mysql, and anyway it does not really make sense to log it automatically (would cause more trouble to users than it would help them) */ tmp_write_to_binlog= 0; ... I read them in a way how
dangerous,
Kristian estimates.
frankly. Imagine an active domain being deleted by mistake
So the point is to have a slave that is not affected and can rectify (e.g with fail-over to it as promoted Master)?
, now the mistake immediately propagates to all servers in the replication topology, ouch.
Maybe there should be an option, for example
FLUSH BINARY LOGS DELETE DOMAIN 10 NOCHECK
or
FLUSH BINARY LOGS DELETE DOMAIN 10 ALLOW ACTIVE
Something like this and also the choice between 'NOCHECK' and 'ALLOW ACTIVE' would be mandatory, that is no replication default for 'DELETE DOMAIN'. So the user first weighs how much risky it would be replicate.
I would to step back from this. The new option does not change the nature of FLUSH BINARY LOGS so the threat comments remain. Also considering that all this measure makes sense only to master, its rush accomplishment on a slave until its promotion to master does not seem necessary. So I would take a simpler originally considered no-replication route. In case the replication requirement will receive more support, we might consider to turn the feature's syntax into something different \footnote{% Consider an "exotic" form of it SET @@global.gtid_binlog_state = "-domain_1,domain_2,..." where '-' hints for decrements}. Yet I think we'll stay with FLUSH BINARY LOGS which I despite some trying could not find any better (The SET I liked though :-)). Cheers, Andrei
![](https://secure.gravatar.com/avatar/549a8d764f7b52743868af4448e1bb3f.jpg?s=120&d=mm&r=g)
Hi Andrei,
On 25 Sep 2017, at 20:17, andrei.elkin@pp.inet.fi wrote:
...
the "vanilla" FLUSH-LOGS is not binlogged by decision commented in reload_acl_and_cache():
In the normal case I think it makes sense to not trigger another flush and to not binlog the command.
if (options & REFRESH_BINARY_LOG) { /* Writing this command to the binlog may result in infinite loops when doing mysqlbinlog|mysql, and anyway it does not really make sense to log it automatically (would cause more trouble to users than it would help them) */ tmp_write_to_binlog= 0; ...
I read them in a way how ??
However for the FLUSH LOGS DELETE DOMAIN …. I’m not so sure. If you “forget" the domain on the upstream server what happens if there are downstream slaves? I think you’ll break replication if they disconnect from this box and try to reconnect. Their GTID information will no longer match. IMO and if I’ve understood correctly this is broken. Please do not expect the DBA to fix this manually. I have lots of places of multi-tier hierarchies and I do not want to touch anything downstream of a master I push changes into. “It should just work”. If FLUSH LOGS should not be binlogged for the reasons stated do not overload this command with something which behaves differently. Use a different command, which you can BINLOG and which won’t trigger confusion. The signal in the binlogs of the fact you’re removing “old domains” is _needed_ by downstream slaves to ensure that they “lose” these domains at the same point in time binlog-wise and thus keep in sync. That’s important. Simon
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Simon, hello.
Hi Andrei,
On 25 Sep 2017, at 20:17, andrei.elkin@pp.inet.fi wrote:
...
the "vanilla" FLUSH-LOGS is not binlogged by decision commented in reload_acl_and_cache():
In the normal case I think it makes sense to not trigger another flush and to not binlog the command.
if (options & REFRESH_BINARY_LOG) { /* Writing this command to the binlog may result in infinite loops when doing mysqlbinlog|mysql, and anyway it does not really make sense to log it automatically (would cause more trouble to users than it would help them) */ tmp_write_to_binlog= 0; ...
I read them in a way how ??
Well, I should've spent one 1 (really) to understand them properly. Apparently mysqlbinlog --read-from-remote-server is meant and it's clear how the pipe is nirvana-like endless, indeed :-).
However for the FLUSH LOGS DELETE DOMAIN …. I’m not so sure.
You are directing to the point. Unlike the plain FLUSH BINARY LOGS the DELETE-DOMAIN one does not have to rotate the logs in case the domains have been already deleted or missed altogether. Therefore the above pipe is not dangerous.
If you “forget" the domain on the upstream server what happens if there are downstream slaves? I think you’ll break replication if they disconnect from this box and try to reconnect. Their GTID information will no longer match. IMO and if I’ve understood correctly this is broken.
Proper usage of FLUSH-DELETE-DOMAIN was thought to require slaves replication position be past the deleted domain. This implies a sort of client> SELECT MASTER_GTID_WAIT() on each slave before FLUSH-DELETE-DOMAIN can be run on the master.
Please do not expect the DBA to fix this manually. I have lots of places of multi-tier hierarchies and I do not want to touch anything downstream of a master I push changes into.
“It should just work”.
This is understood. Now that the new FLUSH-DELETE-DOMAIN is loop-free I don't have any doubt on its replication anymore.
If FLUSH LOGS should not be binlogged for the reasons stated do not overload this command with something which behaves differently. Use a different command, which you can BINLOG and which won’t trigger confusion.
Well, even a new command would rotate binlog so being loop-prone. (But we don't have to make any 2nd FLUSH-DELETE-DOMAIN to rotate, as said above).
The signal in the binlogs of the fact you’re removing “old domains” is _needed_ by downstream slaves to ensure that they “lose” these domains at the same point in time binlog-wise and thus keep in sync. That’s important.
Simon
Thanks for your response. I am going to exempt FLUSH-BINARY-LOGS from replication ban when it's run with the new DELETE_DOMAIN_ID=(list-of-domain-ids) option^\footnote{akin to {DO,IGNORE}_DOMAIN_IDS of CHANGE-MASTER}. Ineffective DELETE_DOMAIN_ID (e.g for a domain that is not in the gtid binlog state) won't cause rotation (the plain FLUSH-BINARY-LOGS part is not run). Existing NO_WRITE_TO_BINLOG|LOCAL options will *actually* control FLUSH-DELETE-DOMAIN replication. I hope this is satisfactory to everybody now. Cheers, Andrei
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian, Simon, hello. Replication side of FLUSH BINARY LOGS DELETE_DOMAIN_ID is actually bound to another requirement specified in early mails. The command is successful only *after* the user has run PURGE BINARY LOGS to 'the-first-log-free-of-old-domains' which is not replicated. Therefore in order to propagate the delete domain instruction we would have to involve into the feature a search for a first satisfactory log and purge. Earlier I fancied it would be syntactically PURGE [NO_WRITE_TO_BINLOG|LOCAL] BINARY LOGS DELETE_DOMAIN_ID=(list-of-domain-ids) with the FLUSH-like final piece of logics. Unlike the plain PURGE this one can be replicated. If we go this way, I also feel replicating behaviour would not be made by default. Apparently an opposite to NO_WRITE_TO_BINLOG|LOCAL would need introduction into parser; say DO_WRITE_TO_BINLOG|REPLICATE ? Now I am wondering what colleguas could say.. Cheers, Andrei
Hi Andrei,
On 25 Sep 2017, at 20:17, andrei.elkin@pp.inet.fi wrote:
...
the "vanilla" FLUSH-LOGS is not binlogged by decision commented in reload_acl_and_cache():
In the normal case I think it makes sense to not trigger another flush and to not binlog the command.
if (options & REFRESH_BINARY_LOG) { /* Writing this command to the binlog may result in infinite loops when doing mysqlbinlog|mysql, and anyway it does not really make sense to log it automatically (would cause more trouble to users than it would help them) */ tmp_write_to_binlog= 0; ...
I read them in a way how ??
Well, I should've spent one 1 (really) to understand them properly. Apparently mysqlbinlog --read-from-remote-server is meant and it's clear how the pipe is nirvana-like endless, indeed :-).
However for the FLUSH LOGS DELETE DOMAIN …. I’m not so sure.
You are directing to the point. Unlike the plain FLUSH BINARY LOGS the DELETE-DOMAIN one does not have to rotate the logs in case the domains have been already deleted or missed altogether. Therefore the above pipe is not dangerous.
If you “forget" the domain on the upstream server what happens if there are downstream slaves? I think you’ll break replication if they disconnect from this box and try to reconnect. Their GTID information will no longer match. IMO and if I’ve understood correctly this is broken.
Proper usage of FLUSH-DELETE-DOMAIN was thought to require slaves replication position be past the deleted domain. This implies a sort of client> SELECT MASTER_GTID_WAIT() on each slave before FLUSH-DELETE-DOMAIN can be run on the master.
Please do not expect the DBA to fix this manually. I have lots of places of multi-tier hierarchies and I do not want to touch anything downstream of a master I push changes into.
“It should just work”.
This is understood. Now that the new FLUSH-DELETE-DOMAIN is loop-free I don't have any doubt on its replication anymore.
If FLUSH LOGS should not be binlogged for the reasons stated do not overload this command with something which behaves differently. Use a different command, which you can BINLOG and which won’t trigger confusion.
Well, even a new command would rotate binlog so being loop-prone. (But we don't have to make any 2nd FLUSH-DELETE-DOMAIN to rotate, as said above).
The signal in the binlogs of the fact you’re removing “old domains” is _needed_ by downstream slaves to ensure that they “lose” these domains at the same point in time binlog-wise and thus keep in sync. That’s important.
Simon
Thanks for your response. I am going to exempt FLUSH-BINARY-LOGS from replication ban when it's run with the new DELETE_DOMAIN_ID=(list-of-domain-ids) option^\footnote{akin to {DO,IGNORE}_DOMAIN_IDS of CHANGE-MASTER}. Ineffective DELETE_DOMAIN_ID (e.g for a domain that is not in the gtid binlog state) won't cause rotation (the plain FLUSH-BINARY-LOGS part is not run).
Existing NO_WRITE_TO_BINLOG|LOCAL options will *actually* control FLUSH-DELETE-DOMAIN replication.
I hope this is satisfactory to everybody now.
Cheers,
Andrei
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
If you “forget" the domain on the upstream server what happens if there are downstream slaves? I think you’ll break replication if they disconnect from this box and try to reconnect. Their GTID information will no longer match. IMO and if I’ve understood correctly this is broken.
It should not break replication. It is allowed for a slave with GTID position 0-1-100,10-2-200 to connect to a master that has nothing in domain 10, this is normal. I am not sure what the use-case of replicating DELETE DOMAIN to a slave would be. Domain deletion does not have a point-in-time property like normal transactions, so it does not help to have it replicated inline in the event stream. If it has an effect on a slave, this effect occurs only when the slave is restarted/reconnected.
I really think there’s a need to indicate what domains should be forgotten/ignored
If CHANGE MASTER ... IGNORE_DOMAIN_IDS is fixed to also ignore the extra domains on master upon connect, it is probably a better way to ignore domains in many cases. It is persisted (in the slave's master.info), and it can be set individually for each slave, which is more flexible (what if one slave needs to ignore a domain but another slave needs to replicate it?).
KN> The procedure to fix it will then be:
1. FLUSH BINARY LOGS, note the new GTID position.
2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed. 3. PURGE BINARY LOGS to remove the errorneous logs.
4. FLUSH BINARY LOG DELETE DOMAIN d
So this was what I suggested at some point related to MDEV-12012. But probably this is not the best suggestion, as I realised later. 1. In MDEV-12012, two independent masters were originally using the same domain id, so their history looks diverged in terms of GTID. This can be fixed by injecting a dummy transaction to make them up-to-date with one another in that domain. Deleting (possibly valuable) part of the history is not needed. 2. Another case, a slave needs to ignore the part of the history on a master connected with some domain. IGNORE_DOMAIN_IDS, once fixed, can do this, again there is no need to delete possibly valuable history on the master. 3. At some point, a domain that was unused for long may no longer appear anywhere, _except_ in gtid_binlog_state and gtid_slave_pos. This may eventually clutter the output and be an annoyance. The original idea with FLUSH BINARY LOGS DELETE DOMAIN was to allow to fix this annoyance by removing such domains from gtid_binlog_state once they are no longer needed anywhere. I am not sure my original suggestion of using PURGE LOGS was ever a good idea, or is ever needed. - Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian, thanks for more remarks!
If you “forget" the domain on the upstream server what happens if there are downstream slaves? I think you’ll break replication if they disconnect from this box and try to reconnect. Their GTID information will no longer match. IMO and if I’ve understood correctly this is broken.
It should not break replication. It is allowed for a slave with GTID position 0-1-100,10-2-200 to connect to a master that has nothing in domain 10, this is normal.
To me in a sense this is "implicit" IGNORE_DOMAIN_IDS on domains that master does not have.
I am not sure what the use-case of replicating DELETE DOMAIN to a slave would be. Domain deletion does not have a point-in-time property like normal transactions, so it does not help to have it replicated inline in the event stream. If it has an effect on a slave, this effect occurs only when the slave is restarted/reconnected.
The use-case must've been the suspected loss of connectivity by slaves.
I really think there’s a need to indicate what domains should be forgotten/ignored
If CHANGE MASTER ... IGNORE_DOMAIN_IDS is fixed to also ignore the extra domains on master upon connect, it is probably a better way to ignore domains in many cases. It is persisted (in the slave's master.info), and it can be set individually for each slave, which is more flexible (what if one slave needs to ignore a domain but another slave needs to replicate it?).
KN> The procedure to fix it will then be:
1. FLUSH BINARY LOGS, note the new GTID position.
2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed. 3. PURGE BINARY LOGS to remove the errorneous logs.
4. FLUSH BINARY LOG DELETE DOMAIN d
So this was what I suggested at some point related to MDEV-12012. But probably this is not the best suggestion, as I realised later.
1. In MDEV-12012, two independent masters were originally using the same domain id, so their history looks diverged in terms of GTID. This can be fixed by injecting a dummy transaction to make them up-to-date with one another in that domain. Deleting (possibly valuable) part of the history is not needed.
2. Another case, a slave needs to ignore the part of the history on a master connected with some domain. IGNORE_DOMAIN_IDS, once fixed, can do this, again there is no need to delete possibly valuable history on the master.
Right. The feature we've been discussing solely deals with p.3.
3. At some point, a domain that was unused for long may no longer appear anywhere, _except_ in gtid_binlog_state and gtid_slave_pos. This may eventually clutter the output and be an annoyance. The original idea with FLUSH BINARY LOGS DELETE DOMAIN was to allow to fix this annoyance by removing such domains from gtid_binlog_state once they are no longer needed anywhere.
I am not sure my original suggestion of using PURGE LOGS was ever a good idea, or is ever needed.
I think it remains as optional which I wrote in my reply last night. Cheers, Andrei
- Kristian.
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian, hello. The patch is ready for review and can be located on bb-10.1-andrei, https://github.com/MariaDB/server/pull/460 (Ignore https://github.com/MariaDB/server/pull/459 which specified 10.2 base by mistake) In case you won't be able to, I'll find replacement, no worries. Have a good time. Andrei
Kristian, thanks for more remarks!
If you “forget" the domain on the upstream server what happens if there are downstream slaves? I think you’ll break replication if they disconnect from this box and try to reconnect. Their GTID information will no longer match. IMO and if I’ve understood correctly this is broken.
It should not break replication. It is allowed for a slave with GTID position 0-1-100,10-2-200 to connect to a master that has nothing in domain 10, this is normal.
To me in a sense this is "implicit" IGNORE_DOMAIN_IDS on domains that master does not have.
I am not sure what the use-case of replicating DELETE DOMAIN to a slave would be. Domain deletion does not have a point-in-time property like normal transactions, so it does not help to have it replicated inline in the event stream. If it has an effect on a slave, this effect occurs only when the slave is restarted/reconnected.
The use-case must've been the suspected loss of connectivity by slaves.
I really think there’s a need to indicate what domains should be forgotten/ignored
If CHANGE MASTER ... IGNORE_DOMAIN_IDS is fixed to also ignore the extra domains on master upon connect, it is probably a better way to ignore domains in many cases. It is persisted (in the slave's master.info), and it can be set individually for each slave, which is more flexible (what if one slave needs to ignore a domain but another slave needs to replicate it?).
KN> The procedure to fix it will then be:
1. FLUSH BINARY LOGS, note the new GTID position.
2. Ensure that all slaves are past the problematic point with MASTER_GTID_WAIT(<pos>). After this, the old errorneous binlog files are no longer needed. 3. PURGE BINARY LOGS to remove the errorneous logs.
4. FLUSH BINARY LOG DELETE DOMAIN d
So this was what I suggested at some point related to MDEV-12012. But probably this is not the best suggestion, as I realised later.
1. In MDEV-12012, two independent masters were originally using the same domain id, so their history looks diverged in terms of GTID. This can be fixed by injecting a dummy transaction to make them up-to-date with one another in that domain. Deleting (possibly valuable) part of the history is not needed.
2. Another case, a slave needs to ignore the part of the history on a master connected with some domain. IGNORE_DOMAIN_IDS, once fixed, can do this, again there is no need to delete possibly valuable history on the master.
Right. The feature we've been discussing solely deals with p.3.
3. At some point, a domain that was unused for long may no longer appear anywhere, _except_ in gtid_binlog_state and gtid_slave_pos. This may eventually clutter the output and be an annoyance. The original idea with FLUSH BINARY LOGS DELETE DOMAIN was to allow to fix this annoyance by removing such domains from gtid_binlog_state once they are no longer needed anywhere.
I am not sure my original suggestion of using PURGE LOGS was ever a good idea, or is ever needed.
I think it remains as optional which I wrote in my reply last night.
Cheers,
Andrei
- Kristian.
_______________________________________________ Mailing list: https://launchpad.net/~maria-developers Post to : maria-developers@lists.launchpad.net Unsubscribe : https://launchpad.net/~maria-developers More help : https://help.launchpad.net/ListHelp
![](https://secure.gravatar.com/avatar/99fde0c1dfd216326aae0aff30d493f1.jpg?s=120&d=mm&r=g)
andrei.elkin@pp.inet.fi writes:
The patch is ready for review and can be located on bb-10.1-andrei, https://github.com/MariaDB/server/pull/460
I think the patch is ok, it looks of good quality and well thought out. A few comments/suggestions: 1. In drop_domain(), the use of the condition if (strlen(errbuf)) would be clearer if it was a separate flag. As the code is now, it implicitly requires errbuf to be initialised to the empty string by caller, which is needlessly errorprone. (In general, I think using the errmsg also as a flag is best avoided, but I realise this is partly inherited also from existing code, as a work-around for C not allowing to return multiple values easily. But inside drop_domain() it is easy to use a separate flag). 2. I would re-consider if this warning is useful: sprintf(errbuf, "missing gtids from '%u-%u' domain-server pair " "which is referred in Gtid list describing earlier binlog state; " "ignore it if the domain was already explicitly deleted", glev->list[l].domain_id, glev->list[l].server_id); Since, as you write in the patch, with this feature it will be a normal state of affairs that domains can be removed from the binlog state. 3. I am not sure I understand the purpose of this warning: sprintf(errbuf, "having gtid '%u-%u-%llu' which is lesser than " "'%u-%u-%llu' of Gtid list describing earlier binlog state; " "possibly the binlog state was affected by smaller sequence number " "gtid injection (manually 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); push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, ER_BINLOG_CANT_DELETE_GTID_DOMAIN, "the current gtid binlog state is incompatible to " "a former one %s", errbuf); The ER_BINLOG_CANT_DELETE_GTID_DOMAIN says "Could not delete gtid domain". But if I understand correctly, this warning is actually unrelated to the domains being listed in DELETE DOMAIN_ID (and in fact such domains can be deleted successfully despite this message). Having a warning here might be ok (this condition would be considered a corruption of the binary log, out-of-order within the same server-id is not valid). But it might be confusing to users the way it is done here? 4. Also consider asking Ian Gilfillan (who did a lot of documentation on MariaDB) for help in clarifying the different error and warning messages in the patch. Eg. "is lesser than" is not correct English (like you, I also am not a native English speaker). Thanks for the patch, this is something that has been requested a number of times. You might consider taking over this task from Jira, which (if I understand the description correctly) you have basically solved (if with a different/better syntax): https://jira.mariadb.org/browse/MDEV-9241 - Kristian.
From 3e9d06db84ab8cd761717fcb5ca4a05dfed70da0 Mon Sep 17 00:00:00 2001 From: Andrei Elkin
Date: Fri, 29 Sep 2017 21:56:59 +0300 Subject: [PATCH] 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. --- mysql-test/include/show_gtid_list.inc | 15 ++ .../r/binlog_flush_binlogs_delete_domain.result | 78 +++++++++ .../r/binlog_gtid_delete_domain_debug.result | 6 + .../t/binlog_flush_binlogs_delete_domain.test | 136 +++++++++++++++ .../binlog/t/binlog_gtid_delete_domain_debug.test | 11 ++ .../perfschema/r/start_server_low_digest.result | 4 +- .../suite/rpl/r/rpl_gtid_delete_domain.result | 30 ++++ mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test | 95 ++++++++++ sql/lex.h | 1 + sql/log.cc | 194 ++++++++++++++++++++- sql/log.h | 7 +- sql/rpl_gtid.cc | 150 +++++++++++++++- sql/rpl_gtid.h | 9 + sql/share/errmsg-utf8.txt | 2 + sql/sql_lex.cc | 5 + sql/sql_lex.h | 7 + sql/sql_reload.cc | 5 +- sql/sql_repl.cc | 68 +------- sql/sql_repl.h | 1 - sql/sql_yacc.yy | 22 ++- 20 files changed, 769 insertions(+), 77 deletions(-) create mode 100644 mysql-test/include/show_gtid_list.inc create mode 100644 mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result create mode 100644 mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result create mode 100644 mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test create mode 100644 mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test create mode 100644 mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result create mode 100644 mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test
diff --git a/mysql-test/include/show_gtid_list.inc b/mysql-test/include/show_gtid_list.inc new file mode 100644 index 000000000000..96f813f180cb --- /dev/null +++ b/mysql-test/include/show_gtid_list.inc @@ -0,0 +1,15 @@ +# ==== 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 diff --git a/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result new file mode 100644 index 000000000000..584515aff354 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result @@ -0,0 +1,78 @@ +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 being deleted gtid domain '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 being deleted domain '1'; make sure first to 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 being deleted domain '1'; make sure first to 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 to a former one missing gtids from '1-1' domain-server pair which is referred in Gtid list describing earlier binlog state; ignore it if the domain was already explicitly deleted +Warning 1982 being deleted gtid domain '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 being deleted domain '1'; make sure first to 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 being deleted gtid domain '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 to a former one having gtid '11-11-1' which is lesser than '11-11-11' of Gtid list describing earlier binlog state; possibly the binlog state was affected by smaller sequence number gtid injection (manually or via replication) +DROP TABLE t; +RESET MASTER; diff --git a/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result b/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result new file mode 100644 index 000000000000..5193267e2f21 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result @@ -0,0 +1,6 @@ +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. diff --git a/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test new file mode 100644 index 000000000000..e7b11e598371 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test @@ -0,0 +1,136 @@ +# 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 $domain_list= $domain_list 0 + +--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; diff --git a/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test b/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test new file mode 100644 index 000000000000..5de549c45bb0 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test @@ -0,0 +1,11 @@ +# 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; diff --git a/mysql-test/suite/perfschema/r/start_server_low_digest.result b/mysql-test/suite/perfschema/r/start_server_low_digest.result index 8cc92f21964f..6fc41fbb7155 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_digest.result +++ b/mysql-test/suite/perfschema/r/start_server_low_digest.result @@ -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 diff --git a/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result new file mode 100644 index 000000000000..3558a6764d1d --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result @@ -0,0 +1,30 @@ +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 diff --git a/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test new file mode 100644 index 000000000000..c2b7338be2d7 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test @@ -0,0 +1,95 @@ +# 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 a divergent domain is "old" that is there +# won't be no more event groups from it generated, the states can be +# made compatible with the domain wiping 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 heal it by 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 diff --git a/sql/lex.h b/sql/lex.h index 85bd20a5f377..6a1cb6653e9d 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -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)}, diff --git a/sql/log.cc b/sql/log.cc index a9f486d88c15..2fcb6f6dbf62 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -6622,6 +6622,119 @@ 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 +6744,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 +6755,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 +10340,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
(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 (ev); + return errormsg; +} + struct st_mysql_storage_engine binlog_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; diff --git a/sql/log.h b/sql/log.h index bf076fae31db..3026ca11e310 100644 --- a/sql/log.h +++ b/sql/log.h @@ -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 */ diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc index 51df8f1a789b..c7761f008bf1 100644 --- a/sql/rpl_gtid.cc +++ b/sql/rpl_gtid.cc @@ -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,154 @@ 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; + 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 greater than that of the list. + Exceptions of this inclusion rule are: + A. the list may still refer to gtids whose domains domains were + already deleted but the files remain (unpurged yet) *only* + referring to the domains through their Gtid lists. + B. the user injected out of order groups + C. manually build list of binlog files to violate the inclusion + constraint. + + It is diagnozed and merely *warned* assuming this might caused by + one of thew two reasons. + */ + for (ulong l= 0; l < glev->count; l++) + { + 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 '%u-%u' domain-server pair " + "which is referred in Gtid list describing earlier binlog state; " + "ignore it if the domain was already explicitly deleted", + glev->list[l].domain_id, glev->list[l].server_id); + else if (rb_state_gtid->seq_no < glev->list[l].seq_no) + sprintf(errbuf, + "having gtid '%u-%u-%llu' which is lesser than " + "'%u-%u-%llu' of Gtid list describing earlier binlog state; " + "possibly the binlog state was affected by smaller sequence number " + "gtid injection (manually 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)) + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_BINLOG_CANT_DELETE_GTID_DOMAIN, + "the current gtid binlog state is incompatible to " + "a former one %s", errbuf); + errbuf[0]= 0; // having been used up so reset + } + + /* + For each domain_id from ids + when no such domain in binlog state + warning && continue + For each domain.server's last gtid + when not locate the last gtid in glev.list + error 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, + "being deleted gtid domain '%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 being deleted domain" + " '%lu'; make sure first to 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() { diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h index ece6effbef6f..79d566bddbfd 100644 --- a/sql/rpl_gtid.h +++ b/sql/rpl_gtid.h @@ -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*); };
diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index d335b0b420b8..15ec98045ea2 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -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." diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index b3a30c69a039..018fa9f3af1d 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -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); }
diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 240eb2373ebd..718a33f68e47 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2725,6 +2725,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) diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index d68ce96dc85a..0488bf7261bc 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -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->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) diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 764047e47206..b5cca334891b 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -30,7 +30,7 @@ #include
#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
(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 (ev); - return errormsg; -} - /* Check if every GTID requested by the slave is contained in this (or a later) diff --git a/sql/sql_repl.h b/sql/sql_repl.h index e2000bbca73a..37acff3141f2 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -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 { }; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index b4c0c4d45c33..0aed06750a8c 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -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 @@ -12768,7 +12770,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 { @@ -12825,6 +12827,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; }
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian, salve. Thanks for checking the patch so promptly!
andrei.elkin@pp.inet.fi writes:
The patch is ready for review and can be located on bb-10.1-andrei, https://github.com/MariaDB/server/pull/460
I think the patch is ok, it looks of good quality and well thought out.
A few comments/suggestions:
1. In drop_domain(), the use of the condition if (strlen(errbuf)) would be clearer if it was a separate flag. As the code is now, it implicitly requires errbuf to be initialised to the empty string by caller, which is needlessly errorprone.
You have a point. No need to work the caller in this case.
(In general, I think using the errmsg also as a flag is best avoided, but I realise this is partly inherited also from existing code, as a work-around for C not allowing to return multiple values easily. But inside drop_domain() it is easy to use a separate flag).
2. I would re-consider if this warning is useful:
sprintf(errbuf, "missing gtids from '%u-%u' domain-server pair " "which is referred in Gtid list describing earlier binlog state; " "ignore it if the domain was already explicitly deleted", glev->list[l].domain_id, glev->list[l].server_id);
Since, as you write in the patch, with this feature it will be a normal state of affairs that domains can be removed from the binlog state.
There still exists a possibility (C) of "manual" and "malign" composition of binlog files which I am trying to take control over. If you think its paranoid too much I won't object :-).
3. I am not sure I understand the purpose of this warning:
sprintf(errbuf, "having gtid '%u-%u-%llu' which is lesser than " "'%u-%u-%llu' of Gtid list describing earlier binlog state; " "possibly the binlog state was affected by smaller sequence number " "gtid injection (manually 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); push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, ER_BINLOG_CANT_DELETE_GTID_DOMAIN, "the current gtid binlog state is incompatible to " "a former one %s", errbuf);
The ER_BINLOG_CANT_DELETE_GTID_DOMAIN says "Could not delete gtid domain". But if I understand correctly, this warning is actually unrelated to the domains being listed in DELETE DOMAIN_ID (and in fact such domains can be deleted successfully despite this message).
A glitch, right.
Having a warning here might be ok (this condition would be considered a corruption of the binary log, out-of-order within the same server-id is not valid). But it might be confusing to users the way it is done here?
I'm correcting the head part of the warning message (naturally the same applies to the first one of "missing gtids from '%u-%u' domain-server pair " should you agree to keep it.
4. Also consider asking Ian Gilfillan (who did a lot of documentation on MariaDB) for help in clarifying the different error and warning messages in the patch. Eg. "is lesser than" is not correct English (like you, I also am not a native English speaker).
I will try to get Ian's input into the new patch.
Thanks for the patch, this is something that has been requested a number of times. You might consider taking over this task from Jira, which (if I understand the description correctly) you have basically solved (if with a different/better syntax):
https://jira.mariadb.org/browse/MDEV-9241
- Kristian.
Above all it's a good piece of collective work which I enjoy! Cheers, Andrei
From 3e9d06db84ab8cd761717fcb5ca4a05dfed70da0 Mon Sep 17 00:00:00 2001 From: Andrei Elkin
Date: Fri, 29 Sep 2017 21:56:59 +0300 Subject: [PATCH] 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. --- mysql-test/include/show_gtid_list.inc | 15 ++ .../r/binlog_flush_binlogs_delete_domain.result | 78 +++++++++ .../r/binlog_gtid_delete_domain_debug.result | 6 + .../t/binlog_flush_binlogs_delete_domain.test | 136 +++++++++++++++ .../binlog/t/binlog_gtid_delete_domain_debug.test | 11 ++ .../perfschema/r/start_server_low_digest.result | 4 +- .../suite/rpl/r/rpl_gtid_delete_domain.result | 30 ++++ mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test | 95 ++++++++++ sql/lex.h | 1 + sql/log.cc | 194 ++++++++++++++++++++- sql/log.h | 7 +- sql/rpl_gtid.cc | 150 +++++++++++++++- sql/rpl_gtid.h | 9 + sql/share/errmsg-utf8.txt | 2 + sql/sql_lex.cc | 5 + sql/sql_lex.h | 7 + sql/sql_reload.cc | 5 +- sql/sql_repl.cc | 68 +------- sql/sql_repl.h | 1 - sql/sql_yacc.yy | 22 ++- 20 files changed, 769 insertions(+), 77 deletions(-) create mode 100644 mysql-test/include/show_gtid_list.inc create mode 100644 mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result create mode 100644 mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result create mode 100644 mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test create mode 100644 mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test create mode 100644 mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result create mode 100644 mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test
diff --git a/mysql-test/include/show_gtid_list.inc b/mysql-test/include/show_gtid_list.inc new file mode 100644 index 000000000000..96f813f180cb --- /dev/null +++ b/mysql-test/include/show_gtid_list.inc @@ -0,0 +1,15 @@ +# ==== 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 diff --git a/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result new file mode 100644 index 000000000000..584515aff354 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result @@ -0,0 +1,78 @@ +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 being deleted gtid domain '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 being deleted domain '1'; make sure first to 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 being deleted domain '1'; make sure first to 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 to a former one missing gtids from '1-1' domain-server pair which is referred in Gtid list describing earlier binlog state; ignore it if the domain was already explicitly deleted +Warning 1982 being deleted gtid domain '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 being deleted domain '1'; make sure first to 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 being deleted gtid domain '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 to a former one having gtid '11-11-1' which is lesser than '11-11-11' of Gtid list describing earlier binlog state; possibly the binlog state was affected by smaller sequence number gtid injection (manually or via replication) +DROP TABLE t; +RESET MASTER; diff --git a/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result b/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result new file mode 100644 index 000000000000..5193267e2f21 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result @@ -0,0 +1,6 @@ +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. diff --git a/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test new file mode 100644 index 000000000000..e7b11e598371 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test @@ -0,0 +1,136 @@ +# 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 $domain_list= $domain_list 0 + +--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; diff --git a/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test b/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test new file mode 100644 index 000000000000..5de549c45bb0 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test @@ -0,0 +1,11 @@ +# 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; diff --git a/mysql-test/suite/perfschema/r/start_server_low_digest.result b/mysql-test/suite/perfschema/r/start_server_low_digest.result index 8cc92f21964f..6fc41fbb7155 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_digest.result +++ b/mysql-test/suite/perfschema/r/start_server_low_digest.result @@ -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 diff --git a/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result new file mode 100644 index 000000000000..3558a6764d1d --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result @@ -0,0 +1,30 @@ +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 diff --git a/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test new file mode 100644 index 000000000000..c2b7338be2d7 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test @@ -0,0 +1,95 @@ +# 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 a divergent domain is "old" that is there +# won't be no more event groups from it generated, the states can be +# made compatible with the domain wiping 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 heal it by 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 diff --git a/sql/lex.h b/sql/lex.h index 85bd20a5f377..6a1cb6653e9d 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -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)}, diff --git a/sql/log.cc b/sql/log.cc index a9f486d88c15..2fcb6f6dbf62 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -6622,6 +6622,119 @@ 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 +6744,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 +6755,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 +10340,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
(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 (ev); + return errormsg; +} + struct st_mysql_storage_engine binlog_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; diff --git a/sql/log.h b/sql/log.h index bf076fae31db..3026ca11e310 100644 --- a/sql/log.h +++ b/sql/log.h @@ -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 */ diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc index 51df8f1a789b..c7761f008bf1 100644 --- a/sql/rpl_gtid.cc +++ b/sql/rpl_gtid.cc @@ -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,154 @@ 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; + 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 greater than that of the list. + Exceptions of this inclusion rule are: + A. the list may still refer to gtids whose domains domains were + already deleted but the files remain (unpurged yet) *only* + referring to the domains through their Gtid lists. + B. the user injected out of order groups + C. manually build list of binlog files to violate the inclusion + constraint. + + It is diagnozed and merely *warned* assuming this might caused by + one of thew two reasons. + */ + for (ulong l= 0; l < glev->count; l++) + { + 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 '%u-%u' domain-server pair " + "which is referred in Gtid list describing earlier binlog state; " + "ignore it if the domain was already explicitly deleted", + glev->list[l].domain_id, glev->list[l].server_id); + else if (rb_state_gtid->seq_no < glev->list[l].seq_no) + sprintf(errbuf, + "having gtid '%u-%u-%llu' which is lesser than " + "'%u-%u-%llu' of Gtid list describing earlier binlog state; " + "possibly the binlog state was affected by smaller sequence number " + "gtid injection (manually 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)) + push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, + ER_BINLOG_CANT_DELETE_GTID_DOMAIN, + "the current gtid binlog state is incompatible to " + "a former one %s", errbuf); + errbuf[0]= 0; // having been used up so reset + } + + /* + For each domain_id from ids + when no such domain in binlog state + warning && continue + For each domain.server's last gtid + when not locate the last gtid in glev.list + error 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, + "being deleted gtid domain '%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 being deleted domain" + " '%lu'; make sure first to 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() { diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h index ece6effbef6f..79d566bddbfd 100644 --- a/sql/rpl_gtid.h +++ b/sql/rpl_gtid.h @@ -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*); };
diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index d335b0b420b8..15ec98045ea2 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -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." diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index b3a30c69a039..018fa9f3af1d 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -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); }
diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 240eb2373ebd..718a33f68e47 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2725,6 +2725,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) diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc index d68ce96dc85a..0488bf7261bc 100644 --- a/sql/sql_reload.cc +++ b/sql/sql_reload.cc @@ -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->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) diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 764047e47206..b5cca334891b 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -30,7 +30,7 @@ #include
#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
(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 (ev); - return errormsg; -} - /* Check if every GTID requested by the slave is contained in this (or a later) diff --git a/sql/sql_repl.h b/sql/sql_repl.h index e2000bbca73a..37acff3141f2 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -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 { }; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index b4c0c4d45c33..0aed06750a8c 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -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 @@ -12768,7 +12770,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 { @@ -12825,6 +12827,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; }
![](https://secure.gravatar.com/avatar/6c892e400b7a448e0b6d23ea9019bc0b.jpg?s=120&d=mm&r=g)
Kristian, hello. The patch is polished to address your comments and Ian's editorial work. I apologize for a possible invinient delay with the final version. It's here: https://github.com/MariaDB/server/pull/460/commits/56b000b2e7d9c4dec61429f0f... as well as appended to the end of this mail. Cheers, Andrei andrei.elkin@pp.inet.fi writes:
Kristian, salve.
Thanks for checking the patch so promptly!
andrei.elkin@pp.inet.fi writes:
The patch is ready for review and can be located on bb-10.1-andrei, https://github.com/MariaDB/server/pull/460
I think the patch is ok, it looks of good quality and well thought out.
A few comments/suggestions:
1. In drop_domain(), the use of the condition if (strlen(errbuf)) would be clearer if it was a separate flag. As the code is now, it implicitly requires errbuf to be initialised to the empty string by caller, which is needlessly errorprone.
You have a point. No need to work the caller in this case.
(In general, I think using the errmsg also as a flag is best avoided, but I realise this is partly inherited also from existing code, as a work-around for C not allowing to return multiple values easily. But inside drop_domain() it is easy to use a separate flag).
2. I would re-consider if this warning is useful:
sprintf(errbuf, "missing gtids from '%u-%u' domain-server pair " "which is referred in Gtid list describing earlier binlog state; " "ignore it if the domain was already explicitly deleted", glev->list[l].domain_id, glev->list[l].server_id);
Since, as you write in the patch, with this feature it will be a normal state of affairs that domains can be removed from the binlog state.
There still exists a possibility (C) of "manual" and "malign" composition of binlog files which I am trying to take control over. If you think its paranoid too much I won't object :-).
3. I am not sure I understand the purpose of this warning:
sprintf(errbuf, "having gtid '%u-%u-%llu' which is lesser than " "'%u-%u-%llu' of Gtid list describing earlier binlog state; " "possibly the binlog state was affected by smaller sequence number " "gtid injection (manually 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); push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, ER_BINLOG_CANT_DELETE_GTID_DOMAIN, "the current gtid binlog state is incompatible to " "a former one %s", errbuf);
The ER_BINLOG_CANT_DELETE_GTID_DOMAIN says "Could not delete gtid domain". But if I understand correctly, this warning is actually unrelated to the domains being listed in DELETE DOMAIN_ID (and in fact such domains can be deleted successfully despite this message).
A glitch, right.
Having a warning here might be ok (this condition would be considered a corruption of the binary log, out-of-order within the same server-id is not valid). But it might be confusing to users the way it is done here?
I'm correcting the head part of the warning message (naturally the same applies to the first one of "missing gtids from '%u-%u' domain-server pair " should you agree to keep it.
4. Also consider asking Ian Gilfillan (who did a lot of documentation on MariaDB) for help in clarifying the different error and warning messages in the patch. Eg. "is lesser than" is not correct English (like you, I also am not a native English speaker).
I will try to get Ian's input into the new patch.
Thanks for the patch, this is something that has been requested a number of times. You might consider taking over this task from Jira, which (if I understand the description correctly) you have basically solved (if with a different/better syntax):
https://jira.mariadb.org/browse/MDEV-9241
- Kristian.
Above all it's a good piece of collective work which I enjoy!
Cheers,
Andrei
diff --git a/mysql-test/include/show_gtid_list.inc b/mysql-test/include/show_gtid_list.inc
new file mode 100644
index 00000000000..96f813f180c
--- /dev/null
+++ b/mysql-test/include/show_gtid_list.inc
@@ -0,0 +1,15 @@
+# ==== 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
diff --git a/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result
new file mode 100644
index 00000000000..daf2f4d6490
--- /dev/null
+++ b/mysql-test/suite/binlog/r/binlog_flush_binlogs_delete_domain.result
@@ -0,0 +1,78 @@
+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;
diff --git a/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result b/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result
new file mode 100644
index 00000000000..5193267e2f2
--- /dev/null
+++ b/mysql-test/suite/binlog/r/binlog_gtid_delete_domain_debug.result
@@ -0,0 +1,6 @@
+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.
diff --git a/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test
new file mode 100644
index 00000000000..0faafa35a1b
--- /dev/null
+++ b/mysql-test/suite/binlog/t/binlog_flush_binlogs_delete_domain.test
@@ -0,0 +1,137 @@
+# 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;
diff --git a/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test b/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test
new file mode 100644
index 00000000000..5de549c45bb
--- /dev/null
+++ b/mysql-test/suite/binlog/t/binlog_gtid_delete_domain_debug.test
@@ -0,0 +1,11 @@
+# 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;
diff --git a/mysql-test/suite/perfschema/r/start_server_low_digest.result b/mysql-test/suite/perfschema/r/start_server_low_digest.result
index 8cc92f21964..6fc41fbb715 100644
--- a/mysql-test/suite/perfschema/r/start_server_low_digest.result
+++ b/mysql-test/suite/perfschema/r/start_server_low_digest.result
@@ -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
diff --git a/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result
new file mode 100644
index 00000000000..3558a6764d1
--- /dev/null
+++ b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result
@@ -0,0 +1,30 @@
+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
diff --git a/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test
new file mode 100644
index 00000000000..5abedd7eb37
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test
@@ -0,0 +1,95 @@
+# 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
diff --git a/sql/lex.h b/sql/lex.h
index 85bd20a5f37..6a1cb6653e9 100644
--- a/sql/lex.h
+++ b/sql/lex.h
@@ -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)},
diff --git a/sql/log.cc b/sql/log.cc
index a9f486d88c1..a0e5e908ca7 100644
--- a/sql/log.cc
+++ b/sql/log.cc
@@ -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
participants (4)
-
Andrei Elkin
-
andrei.elkin@pp.inet.fi
-
Kristian Nielsen
-
Simon Mudd