Skip to content

Commit 7199595

Browse files
author
Andrei Elkin
committed
commit c2718152b0e6d00dba8d791cf3866c7315418a85
Author: Andrei Elkin <[email protected]> Date: Fri Nov 25 15:17:17 2016 +0200 WL#9175 Correct recovery of DDL statements/transactions by binary log The patch consists of two parts implementing the WL agenda which is is to provide crash-safety for DDL. That is a server (a general one, master or slave) must be able to recover from crash to commit or rollback every DDL command that was in progress on the eve of crash. The Commit decision is done to commands that had reached Engine-prepared status and got successfully logged into binary log. Otherwise they are rolled back. In order to achieve the goal some refinements are done to the binlogging mechanism, minor addition is done to the server recovery module and some changes applied to the slave side. The binary log part includes Query-log-event which is made to contain xid that is a key item at server recovery. The recovery now is concern with it along with its standard location in Xid_log_event. The first part deals with the ACL DDL sub-class and TRIGGER related queries are fully 2pc-capable. It constructs the WL's framework which is proved on these subclasses. It also specifies how to cover the rest of DDLs by the WL's framework. For those not 2pc-ready DDL cases, sometimes "stub" tests are prepared to be refined by responsible worklogs. Take a few notes to the low-level details of implementation. Note #1. Tagging by xid number is done to the exact 2pc-capable DDL subclass. For DDL:s that will be ready for xiding in future, there is a tech specification how to do so. Note #2. By virtue of existing mechanisms, the slave applier augments the DDL transaction incorporating the slave info table update and the Gtid-executed table (either one optionally) at time of the DDL is ready for the final commit. When for filtering reason the DDL skips committing at its regular time, the augmented transaction would still be not empty consisting of only the added statements, and it would have to be committed by top-level slave specific functions through Log_event::do_update_pos(). To aid this process Query_log_event::has_committed is introduced. Note #3 (QA, please read this.) Replication System_table interface that is employed by handler of TABLE type slave info had to be refined in few places. Note #4 (runtime code). While trying to lessen the footprint to the runtime server code few concessions had to be conceded. These include changes to ha_commit_trans() to invoke new pre_commit() and post_commit(), and post_rollback() hooks due to the slave extra statement. ------------------------------------------------------------------- The 2nd part patch extends the basic framework, xidifies the rest of DDL commands that are (at least) committable at recovery. At the moment those include all Data Definition Statements except ones related to VIEWs, STORED Functions and Procedures. DDL Query is recoverable for these subclasses when it has been recorded into the binary log and was discovered there at the server restart, quite compatible with the DML algorithm. However a clean automatic rollback can't be provided for some of the commands and the user would have to complete recovery manually.
1 parent 8570781 commit 7199595

File tree

95 files changed

+4592
-571
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+4592
-571
lines changed

libbinlogevents/include/binlog_event.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@
127127
1U + (MAX_DBS_IN_EVENT_MTS * (1 + NAME_LEN)) + \
128128
3U + /* type, microseconds */ + \
129129
1U + 32*3 + 1 + 60 \
130-
/* type, user_len, user, host_len, host */)
130+
/* type, user_len, user, host_len, host */ + \
131+
1U + 1 /* type, explicit_def..ts*/+ \
132+
1U + 8 /* type, xid of DDL */)
131133

132134

133135
/**

libbinlogevents/include/statement_events.h

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333

3434
namespace binary_log
3535
{
36+
/**
37+
The following constant represents the maximum of MYSQL_XID domain.
38+
The maximum XID value practically is never supposed to grow beyond UINT64 range.
39+
*/
40+
const uint64_t INVALID_XID= -1ULL;
3641

3742
/**
3843
@class Query_event
@@ -391,12 +396,18 @@ namespace binary_log
391396
</td>
392397
</tr>
393398
<tr>
394-
<td>commit_seq_no</td>
395-
<td>Q_COMMIT_TS</td>
399+
<td>explicit_defaults_ts</td>
400+
<td>Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP</td>
401+
<td>1 byte boolean</td>
402+
<td>Stores master connection @@session.explicit_defaults_for_timestamp when
403+
CREATE and ALTER operate on a table with a TIMESTAMP column. </td>
404+
</tr>
405+
<tr>
406+
<td>ddl_xid</td>
407+
<td>Q_DDL_LOGGED_WITH_XID</td>
396408
<td>8 byte integer</td>
397-
<td>Stores the logical timestamp when the transaction
398-
entered the commit phase. This wll be used to apply transactions
399-
in parallel on the slave. </td>
409+
<td>Stores variable carrying xid info of 2pc-aware (recoverable) DDL
410+
queries. </td>
400411
</tr>
401412
</table>
402413
@@ -472,7 +483,8 @@ class Query_event: public Binary_log_event
472483
*/
473484
Q_COMMIT_TS,
474485
/*
475-
A code for Query_log_event status, similar to G_COMMIT_TS2.
486+
An old (unused after migration to Gtid_event) code for
487+
Query_log_event status, similar to G_COMMIT_TS2.
476488
*/
477489
Q_COMMIT_TS2,
478490
/*
@@ -481,7 +493,11 @@ class Query_event: public Binary_log_event
481493
a TIMESTAMP column, that are dependent on that feature.
482494
For pre-WL6292 master's the associated with this code value is zero.
483495
*/
484-
Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP
496+
Q_EXPLICIT_DEFAULTS_FOR_TIMESTAMP,
497+
/*
498+
The variable carries xid info of 2pc-aware (recoverable) DDL queries.
499+
*/
500+
Q_DDL_LOGGED_WITH_XID
485501
};
486502
const char* query;
487503
const char* db;
@@ -602,7 +618,8 @@ class Query_event: public Binary_log_event
602618
*/
603619
unsigned char mts_accessed_dbs;
604620
char mts_accessed_db_names[MAX_DBS_IN_EVENT_MTS][NAME_LEN];
605-
621+
/* XID value when the event is a 2pc-capable DDL */
622+
uint64_t ddl_xid;
606623
/**
607624
The constructor will be used while creating a Query_event, to be
608625
written to the binary log.

libbinlogevents/src/statement_events.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Query_event::Query_event(const char* query_arg, const char* catalog_arg,
6161
charset_database_number(0),
6262
table_map_for_update(table_map_for_update_arg),
6363
master_data_written(0), explicit_defaults_ts(TERNARY_UNSET),
64-
mts_accessed_dbs(0)
64+
mts_accessed_dbs(0), ddl_xid(INVALID_XID)
6565
{
6666
}
6767

@@ -117,7 +117,7 @@ Query_event::Query_event(const char* buf, unsigned int event_len,
117117
time_zone_len(0), catalog_len(0), lc_time_names_number(0),
118118
charset_database_number(0), table_map_for_update(0), master_data_written(0),
119119
explicit_defaults_ts(TERNARY_UNSET),
120-
mts_accessed_dbs(OVER_MAX_DBS_IN_EVENT_MTS)
120+
mts_accessed_dbs(OVER_MAX_DBS_IN_EVENT_MTS), ddl_xid(INVALID_XID)
121121
{
122122
//buf is advanced in Binary_log_event constructor to point to
123123
//beginning of post-header
@@ -363,6 +363,16 @@ break;
363363
explicit_defaults_ts= *pos++ == 0 ? TERNARY_OFF : TERNARY_ON;
364364
break;
365365
}
366+
case Q_DDL_LOGGED_WITH_XID:
367+
CHECK_SPACE(pos, end, 8);
368+
/*
369+
Like in Xid_log_event case, the xid value is not used on the slave
370+
so the number does not really need to respect endiness.
371+
*/
372+
memcpy((char*) &ddl_xid, pos, 8);
373+
ddl_xid= le64toh(ddl_xid);
374+
pos+= 8;
375+
break;
366376
default:
367377
/* That's why you must write status vars in growing order of code */
368378
pos= (const unsigned char*) end; // Break loop
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#
2+
# The macro processes a DDL statement while its handling is
3+
# interrupted by simulated crashes at time of the query
4+
# has not yet been logged and upon that.
5+
# The first, pre-binlog crash happens right after prepare in SEs
6+
# (storage engines), before binary log event for the statement is
7+
# flushed to the disk. In this case statement should be correctly
8+
# rolled back by recovery. The second, post-binlog crash to the
9+
# repeated statement occurs after binary log event is flushed to the
10+
# disk, before commit in the SEs.
11+
# The query results must be found committed upon the server restart.
12+
# The binlog is rotated at the beginning of either crash simulation
13+
# and its resulted file is memorized to optionally print out
14+
# its events which must contain a new XID field.
15+
#
16+
# Another purpose of the macro to run simply as a load
17+
# generator e.g for a slave server without crash simulations
18+
# ($do_only_regular_logging).
19+
#
20+
# The macro increments
21+
# $count_ddl_queries
22+
# per its invocation which serves as a counter for the number of
23+
# processed queries.
24+
#
25+
# Usage, e.g:
26+
#
27+
# --let $do_pre_binlog=1
28+
# --let $do_post_binlog=1
29+
# --let $do_only_regular_logging=1 # run only query no crash simulation
30+
# --let $ddl_query=CREATE USER user1
31+
# --let $pre_binlog_crash_check= [SELECT count(*) = 0 FROM mysql.user WHERE user = 'user1']
32+
# --let $post_binlog_crash_check= [SELECT count(*) = 1 FROM mysql.user WHERE user = 'user1']
33+
#
34+
# --let $do_show_binlog_events=1 # invoke show-binlog-events at the
35+
# # end of query execution
36+
# --let $do_count_queries # When set the processed queries are counted in $count_ddl_queries
37+
# --let $master_log_table # DDL query and post-recovery checks can be logged in a table
38+
# --let $master_log_db # The database of the log table
39+
#
40+
# here a value in [] can be just empty to indicate no check after
41+
# the corresponding crash is done.
42+
#
43+
44+
if ($do_count_queries)
45+
{
46+
--inc $count_ddl_queries
47+
}
48+
49+
--disable_query_log
50+
if ($master_log_table)
51+
{
52+
--let $save_curr_db=`SELECT database()`
53+
if ($master_log_db)
54+
{
55+
--eval USE $master_log_db
56+
}
57+
--eval INSERT INTO $master_log_table SET id= NULL, ddl_query= "$ddl_query", pre_binlog_check= "$pre_binlog_crash_check", post_binlog_check= "$post_binlog_crash_check"
58+
--eval USE $save_curr_db
59+
}
60+
--enable_query_log
61+
62+
# $do_only_regular_logging is incompatible with crash simulating $do:s.
63+
if ($do_only_regular_logging)
64+
{
65+
if ($do_pre_binlog)
66+
{
67+
--echo *** Wrong option combination! ***
68+
--die
69+
}
70+
if ($do_post_binlog)
71+
{
72+
--echo *** Wrong option combination! ***
73+
--die
74+
}
75+
}
76+
77+
--source include/gtid_step_reset.inc
78+
79+
if ($do_pre_binlog)
80+
{
81+
FLUSH LOGS;
82+
--let $binlog_file= query_get_value("SHOW MASTER STATUS", File, 1)
83+
84+
#
85+
# prepare, the first CRASH, /* log, commit */
86+
#
87+
--echo *** Crash right after '$ddl_query' has been prepared in the engine before being logged ***
88+
--source include/expect_crash.inc
89+
90+
SET @@SESSION.debug="+d,crash_commit_before_log";
91+
--disable_query_log
92+
if ($manual_gtid_next)
93+
{
94+
--eval $manual_gtid_next
95+
}
96+
--enable_query_log
97+
--error 2013
98+
--eval $ddl_query
99+
100+
#
101+
# restart the server
102+
#
103+
--source include/start_mysqld.inc
104+
105+
if ($pre_binlog_crash_check)
106+
{
107+
if (!`$pre_binlog_crash_check`)
108+
{
109+
--echo *** State check upon recovery after pre-binlog crash fails ***
110+
--die
111+
}
112+
}
113+
114+
if ($do_show_binlog_events)
115+
{
116+
--let $keep_ddl_xid=1
117+
--let $show_binlog_events_mask_columns=1,2,4,5
118+
--source include/show_binlog_events.inc
119+
}
120+
121+
# The Gtid check, when its mode ON.
122+
# After rollback-recovery of this branch Gtid executed must
123+
# remain as was before the query execution.
124+
--let $gtid_step_count=0
125+
--source include/gtid_step_assert.inc
126+
127+
} # end of $do_pre_binlog
128+
129+
if ($do_post_binlog)
130+
{
131+
FLUSH LOGS;
132+
--let $binlog_file= query_get_value("SHOW MASTER STATUS", File, 1)
133+
134+
#
135+
# prepare, log, the 2nd CRASH, /* commit */
136+
#
137+
--echo *** Crash right after '$ddl_query' has been binary-logged before committed in the engine ***
138+
--source include/expect_crash.inc
139+
140+
SET @@SESSION.debug="+d,crash_commit_after_log";
141+
# End of Debug
142+
--disable_query_log
143+
if ($manual_gtid_next)
144+
{
145+
--eval $manual_gtid_next
146+
}
147+
--enable_query_log
148+
--error 2013
149+
--eval $ddl_query
150+
151+
#
152+
# restart the server
153+
#
154+
--source include/start_mysqld.inc
155+
156+
if ($post_binlog_crash_check)
157+
{
158+
if (!`$post_binlog_crash_check`)
159+
{
160+
--echo *** State check upon recovery after pre-commit crash fails ***
161+
--die
162+
}
163+
}
164+
if ($do_show_binlog_events)
165+
{
166+
--let $keep_ddl_xid=1
167+
--let $show_binlog_events_mask_columns=1,2,4,5
168+
--source include/show_binlog_events.inc
169+
}
170+
171+
# The Gtid check, when its mode ON.
172+
# After commit-recovery of this branch Gtid executed must
173+
# receive a new gtid item of the current DDL transaction.
174+
175+
--let $gtid_step_count=1
176+
--source include/gtid_step_assert.inc
177+
178+
} # end of $do_post_binlog
179+
180+
if ($do_only_regular_logging)
181+
{
182+
FLUSH LOGS;
183+
--eval $ddl_query
184+
if ($do_show_binlog_events)
185+
{
186+
--let $keep_ddl_xid=1
187+
--let $show_binlog_events_mask_columns=1,2,4,5
188+
--source include/show_binlog_events.inc
189+
}
190+
191+
# The Gtid check, when its mode ON.
192+
# After commit-recovery of this branch Gtid executed must
193+
# receive a new gtid item of the current DDL transaction.
194+
195+
--let $gtid_step_count=1
196+
--source include/gtid_step_assert.inc
197+
}
198+
199+
#
200+
# Cleanup
201+
#
202+
--let $keep_ddl_xid=
203+
--let $show_binlog_events_mask_columns=

0 commit comments

Comments
 (0)