Skip to content

Commit e908c71

Browse files
author
Karolina Szczepankiewicz
committed
WL#7353 Recover relay log by truncating half written transactions [1/3]
Step 1. Log sanitizer - common class for relay/binary log recovery This step refactors Binlog_recovery class and methods to be re-usable during the relay log recovery process. Introduced changes: New Log_sanitizer class, containing utilities for: - iteration over events in existing log file/files and extracting: - the last valid log position - the last valid source log file and position - removal of log files containing only partially-written transactions from disk and the index file - truncation of the last log file to contain only fully written transactions Some of the functionalities are used only for relay log recovery. Binary log recovery will never remove binlog file, since binary log file always start after the last finished transaction. When relay log recovery is considered, we need to find a start place, which will be either: - a Query Event containing the end of the transaction: - COMMIT / ROLLBACK / XA COMMIT / XA ROLLBACK - a Query Event containing an atomic DDL - a source Rotate Event - a Xid Event Afterwards, searching for the last valid position begins. If no valid position can be found in relay log files (e.g. we cannot decrypt relay log files), relay log recovery won't perform any action. Change-Id: If76694d2dfa0c62ff7b7243cff9bea451117b376
1 parent 889a66a commit e908c71

File tree

12 files changed

+935
-452
lines changed

12 files changed

+935
-452
lines changed

share/messages_to_error_log.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12686,6 +12686,9 @@ ER_IB_LONG_ROLLBACK
1268612686
ER_INVALID_FILE_FORMAT
1268712687
eng "Invalid format of file '%s'."
1268812688

12689+
ER_LOG_SANITIZATION
12690+
eng "Relay log sanitization: %s"
12691+
1268912692
# DO NOT add server-to-client messages here;
1269012693
# they go in messages_to_clients.txt
1269112694
# in the same directory as this file.

sql/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,7 @@ SET(BINLOG_SOURCE
10201020
basic_istream.cc
10211021
basic_ostream.cc
10221022
binlog/global.cc
1023+
binlog/log_sanitizer.cc
10231024
binlog/recovery.cc
10241025
binlog/group_commit/bgc_ticket_manager.cc
10251026
binlog/group_commit/bgc_ticket.cc
@@ -1078,6 +1079,7 @@ TARGET_LINK_LIBRARIES(rpl_source binlog sql_main)
10781079

10791080
SET (RPL_REPLICA_SRCS
10801081
dynamic_ids.cc
1082+
binlog/log_sanitizer.cc
10811083
changestreams/apply/commit_order_queue.cc
10821084
changestreams/apply/replication_thread_status.cc
10831085
rpl_applier_reader.cc

sql/binlog.cc

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,26 +1903,43 @@ int MYSQL_BIN_LOG::gtid_end_transaction(THD *thd) {
19031903
return 0;
19041904
}
19051905

1906+
std::pair<std::list<std::string>, mysql::utils::Error>
1907+
MYSQL_BIN_LOG::get_filename_list() {
1908+
std::pair<std::list<std::string>, mysql::utils::Error> result;
1909+
auto &[filename_list, internal_error] = result;
1910+
LOG_INFO linfo;
1911+
int error = 0;
1912+
std::string error_message;
1913+
MUTEX_LOCK(g, &LOCK_index);
1914+
try {
1915+
for (error = find_log_pos(&linfo, nullptr, false); !error;
1916+
error = find_next_log(&linfo, false)) {
1917+
filename_list.push_back(string(linfo.log_file_name));
1918+
}
1919+
} catch (std::bad_alloc &) {
1920+
internal_error = mysql::utils::Error("MYSQL_BIN_LOG", __FILE__, __LINE__,
1921+
"Out of memory");
1922+
}
1923+
if (error != LOG_INFO_EOF) {
1924+
internal_error = mysql::utils::Error("MYSQL_BIN_LOG", __FILE__, __LINE__,
1925+
"Error while reading index file");
1926+
}
1927+
return result;
1928+
}
1929+
19061930
bool MYSQL_BIN_LOG::reencrypt_logs() {
19071931
DBUG_TRACE;
19081932

19091933
if (!is_open()) return false;
19101934

19111935
std::string error_message;
1912-
/* Gather the set of files to be accessed. */
1913-
list<string> filename_list;
19141936
LOG_INFO linfo;
19151937
int error = 0;
19161938
list<string>::reverse_iterator rit;
19171939

19181940
/* Read binary/relay log file names from index file. */
1919-
mysql_mutex_lock(&LOCK_index);
1920-
for (error = find_log_pos(&linfo, nullptr, false); !error;
1921-
error = find_next_log(&linfo, false)) {
1922-
filename_list.push_back(string(linfo.log_file_name));
1923-
}
1924-
mysql_mutex_unlock(&LOCK_index);
1925-
if (error != LOG_INFO_EOF ||
1941+
auto [filename_list, internal_error] = get_filename_list();
1942+
if (internal_error.is_error() ||
19261943
DBUG_EVALUATE_IF("fail_to_open_index_file", true, false)) {
19271944
error_message.assign("I/O error reading index file '");
19281945
error_message.append(index_file_name);
@@ -3296,37 +3313,41 @@ int query_error_code(const THD *thd, bool not_killed) {
32963313
buffers would just make things slower and more complicated.
32973314
In most cases the copy loop should only do one read.
32983315
3299-
@param from File to copy.
3300-
@param to File to copy to.
3301-
@param offset Offset in 'from' file.
3316+
@param from File to copy.
3317+
@param to File to copy to.
3318+
@param offset Offset in 'from' file.
3319+
@param end_pos End position in 'from' file up to which content should be
3320+
copied; 0 means copying till the end of file
33023321
33033322
33043323
@retval
33053324
0 ok
33063325
@retval
33073326
-1 error
33083327
*/
3309-
static bool copy_file(IO_CACHE *from, IO_CACHE *to, my_off_t offset) {
3328+
static bool copy_file(IO_CACHE *from, IO_CACHE *to, my_off_t offset,
3329+
my_off_t end_pos = 0) {
33103330
int bytes_read;
33113331
uchar io_buf[IO_SIZE * 2];
33123332
DBUG_TRACE;
3333+
unsigned int bytes_written = 0;
33133334

33143335
mysql_file_seek(from->file, offset, MY_SEEK_SET, MYF(0));
33153336
while (true) {
33163337
if ((bytes_read = (int)mysql_file_read(from->file, io_buf, sizeof(io_buf),
33173338
MYF(MY_WME))) < 0)
3318-
goto err;
3339+
return true;
33193340
if (DBUG_EVALUATE_IF("fault_injection_copy_part_file", 1, 0))
33203341
bytes_read = bytes_read / 2;
33213342
if (!bytes_read) break; // end of file
3343+
if (end_pos != 0 && (bytes_written + bytes_read > end_pos - offset)) {
3344+
bytes_read = end_pos - offset - bytes_written;
3345+
}
3346+
bytes_written += bytes_read;
33223347
if (mysql_file_write(to->file, io_buf, bytes_read, MYF(MY_WME | MY_NABP)))
3323-
goto err;
3348+
return true;
33243349
}
3325-
33263350
return false;
3327-
3328-
err:
3329-
return true;
33303351
}
33313352

33323353
/**
@@ -5868,59 +5889,57 @@ int MYSQL_BIN_LOG::close_crash_safe_index_file() {
58685889
return error;
58695890
}
58705891

5871-
/**
5872-
Remove logs from index file.
5873-
5874-
- To make it crash safe, we copy the content of the index file
5875-
from index_file_start_offset recorded in log_info to a
5876-
crash safe index file first and then move the crash
5877-
safe index file to the index file.
5892+
int MYSQL_BIN_LOG::remove_logs_outside_range_from_index(
5893+
const std::string &first, const std::string &last) {
5894+
LOG_INFO first_linfo;
5895+
LOG_INFO last_linfo;
58785896

5879-
@param log_info Store here the found log file name and
5880-
position to the NEXT log file name in
5881-
the index file.
5882-
5883-
@param need_update_threads If we want to update the log coordinates
5884-
of all threads. False for relay logs,
5885-
true otherwise.
5897+
MUTEX_LOCK(g, &LOCK_index);
5898+
int error = find_log_pos(&first_linfo, first.c_str(), false);
5899+
if (error) return error;
5900+
error = find_log_pos(&last_linfo, last.c_str(), false);
5901+
if (error) return error;
5902+
return remove_logs_outside_range_from_index(&first_linfo, is_relay_log,
5903+
&last_linfo);
5904+
}
58865905

5887-
@retval
5888-
0 ok
5889-
@retval
5890-
LOG_INFO_IO Got IO error while reading/writing file
5891-
*/
5892-
int MYSQL_BIN_LOG::remove_logs_from_index(LOG_INFO *log_info,
5893-
bool need_update_threads) {
5906+
int MYSQL_BIN_LOG::remove_logs_outside_range_from_index(
5907+
LOG_INFO *start_log_info, bool need_update_threads,
5908+
LOG_INFO *last_log_info) {
5909+
my_off_t end_offset = 0;
58945910
if (open_crash_safe_index_file()) {
58955911
LogErr(ERROR_LEVEL, ER_BINLOG_CANT_OPEN_TMP_INDEX,
5896-
"MYSQL_BIN_LOG::remove_logs_from_index");
5912+
"MYSQL_BIN_LOG::remove_logs_outside_range_from_index");
58975913
goto err;
58985914
}
5915+
if (last_log_info != nullptr) {
5916+
end_offset = last_log_info->index_file_offset;
5917+
}
58995918

59005919
if (copy_file(&index_file, &crash_safe_index_file,
5901-
log_info->index_file_start_offset)) {
5920+
start_log_info->index_file_start_offset, end_offset)) {
59025921
LogErr(ERROR_LEVEL, ER_BINLOG_CANT_COPY_INDEX_TO_TMP,
5903-
"MYSQL_BIN_LOG::remove_logs_from_index");
5922+
"MYSQL_BIN_LOG::remove_logs_outside_range_from_index");
59045923
goto err;
59055924
}
59065925

59075926
if (close_crash_safe_index_file()) {
59085927
LogErr(ERROR_LEVEL, ER_BINLOG_CANT_CLOSE_TMP_INDEX,
5909-
"MYSQL_BIN_LOG::remove_logs_from_index");
5928+
"MYSQL_BIN_LOG::remove_logs_outside_range_from_index");
59105929
goto err;
59115930
}
59125931
DBUG_EXECUTE_IF("fault_injection_copy_part_file", DBUG_SUICIDE(););
59135932

59145933
if (move_crash_safe_index_file_to_index_file(
59155934
false /*need_lock_index=false*/)) {
59165935
LogErr(ERROR_LEVEL, ER_BINLOG_CANT_MOVE_TMP_TO_INDEX,
5917-
"MYSQL_BIN_LOG::remove_logs_from_index");
5936+
"MYSQL_BIN_LOG::remove_logs_outside_range_from_index");
59185937
goto err;
59195938
}
59205939

59215940
// now update offsets in index file for running threads
59225941
if (need_update_threads)
5923-
adjust_linfo_offsets(log_info->index_file_start_offset);
5942+
adjust_linfo_offsets(start_log_info->index_file_start_offset);
59245943
return 0;
59255944

59265945
err:
@@ -6028,7 +6047,8 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, bool included,
60286047
}
60296048

60306049
/* We know how many files to delete. Update index file. */
6031-
if ((error = remove_logs_from_index(&log_info, need_update_threads))) {
6050+
if ((error = remove_logs_outside_range_from_index(&log_info,
6051+
need_update_threads))) {
60326052
LogErr(ERROR_LEVEL, ER_BINLOG_PURGE_LOGS_CANT_UPDATE_INDEX_FILE);
60336053
goto err;
60346054
}

sql/binlog.h

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ class MYSQL_BIN_LOG : public TC_LOG {
319319
char *buff);
320320
bool is_open() const { return atomic_log_state != LOG_CLOSED; }
321321

322+
/// @brief Obtains the list of logs from the index file
323+
/// @return List of log filenames
324+
std::pair<std::list<std::string>, mysql::utils::Error> get_filename_list();
325+
322326
/* This is relay log */
323327
bool is_relay_log;
324328

@@ -857,7 +861,36 @@ class MYSQL_BIN_LOG : public TC_LOG {
857861
public:
858862
void make_log_name(char *buf, const char *log_ident);
859863
bool is_active(const char *log_file_name) const;
860-
int remove_logs_from_index(LOG_INFO *linfo, bool need_update_threads);
864+
865+
/// @brief Remove logs from index file, except files between 'start' and
866+
/// 'last'
867+
/// @details To make it crash safe, we copy the content of the index file
868+
/// from index_file_start_offset recorded in log_info to a
869+
/// crash safe index file first and then move the crash
870+
/// safe index file to the index file.
871+
/// @param start_log_info Metadata of the first log to be kept
872+
/// in the index file
873+
/// @param need_update_threads If we want to update the log coordinates
874+
/// of all threads. False for relay logs,
875+
/// true otherwise.
876+
/// @param last_log_info Metadata of the last log to be kept in the index
877+
/// file; nullptr means that all logs after start_log_info will be kept
878+
/// @retval
879+
/// 0 ok
880+
/// @retval
881+
/// LOG_INFO_IO Got IO error while reading/writing file
882+
int remove_logs_outside_range_from_index(LOG_INFO *start_log_info,
883+
bool need_update_threads,
884+
LOG_INFO *last_log_info = nullptr);
885+
/// @brief Remove logs from index file except logs between first and last
886+
/// @param first Filename of the first relay log to be kept in index file
887+
/// @param last Filename of the last relay log to be kept in index file
888+
889+
/// @retval 0 OK
890+
/// @retval LOG_INFO_IO Got IO error while reading/writing file
891+
/// @retval LOG_INFO_EOF Could not find requested log file (first or last)
892+
int remove_logs_outside_range_from_index(const std::string &first,
893+
const std::string &last);
861894
int rotate(bool force_rotate, bool *check_purge);
862895

863896
/**

0 commit comments

Comments
 (0)