Skip to content

Commit

Permalink
MDEV-22929 MariaBackup option to report and/or continue when corrupti…
Browse files Browse the repository at this point in the history
…on is encountered

The new option --log-innodb-page-corruption is introduced.

When this option is set, backup is not interrupted if innodb corrupted
page is detected. Instead it logs all found corrupted pages in
innodb_corrupted_pages file in backup directory and finishes with error.

For incremental backup corrupted pages are also copied to .delta file,
because we can't do LSN check for such pages during backup,
innodb_corrupted_pages will also be created in incremental backup
directory.

During --prepare, corrupted pages list is read from the file just after
redo log is applied, and each page from the list is checked if it is allocated
in it's tablespace or not. If it is not allocated, then it is zeroed out,
flushed to the tablespace and removed from the list. If all pages are removed
from the list, then --prepare is finished successfully and
innodb_corrupted_pages file is removed from backup directory. Otherwise
--prepare is finished with error message and innodb_corrupted_pages contains
the list of the pages, which are detected as corrupted during backup, and are
allocated in their tablespaces, what means backup directory contains corrupted
innodb pages, and backup can not be considered as consistent.

For incremental --prepare corrupted pages from .delta files are applied
to the base backup, innodb_corrupted_pages is read from both base in
incremental directories, and the same action is proceded for corrupted
pages list as for full --prepare. innodb_corrupted_pages file is
modified or removed only in base directory.

If DDL happens during backup, it is also processed at the end of backup
to have correct tablespace names in innodb_corrupted_pages.
  • Loading branch information
vlad-lesin committed Dec 1, 2020
1 parent 828471c commit e6b3e38
Show file tree
Hide file tree
Showing 15 changed files with 1,204 additions and 128 deletions.
32 changes: 18 additions & 14 deletions extra/mariabackup/backup_copy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -867,21 +867,14 @@ datafile_rsync_backup(const char *filepath, bool save_to_list, FILE *f)
return(true);
}


static
bool
backup_file_vprintf(const char *filename, const char *fmt, va_list ap)
bool backup_file_print_buf(const char *filename, const char *buf, int buf_len)
{
ds_file_t *dstfile = NULL;
MY_STAT stat; /* unused for now */
char *buf = 0;
int buf_len;
const char *action;

memset(&stat, 0, sizeof(stat));

buf_len = vasprintf(&buf, fmt, ap);

stat.st_size = buf_len;
stat.st_mtime = my_time(0);

Expand All @@ -905,7 +898,6 @@ backup_file_vprintf(const char *filename, const char *fmt, va_list ap)

/* close */
msg(" ...done");
free(buf);

if (ds_close(dstfile)) {
goto error_close;
Expand All @@ -914,16 +906,28 @@ backup_file_vprintf(const char *filename, const char *fmt, va_list ap)
return(true);

error:
free(buf);
if (dstfile != NULL) {
ds_close(dstfile);
}

error_close:
msg("Error: backup file failed.");
return(false); /*ERROR*/
}

return true;
};

static
bool
backup_file_vprintf(const char *filename, const char *fmt, va_list ap)
{
char *buf = 0;
int buf_len;
buf_len = vasprintf(&buf, fmt, ap);
bool result = backup_file_print_buf(filename, buf, buf_len);
free(buf);
return result;
}

bool
backup_file_printf(const char *filename, const char *fmt, ...)
Expand Down Expand Up @@ -1446,7 +1450,7 @@ backup_files(const char *from, bool prep_mode)
return(ret);
}

void backup_fix_ddl(void);
void backup_fix_ddl(CorruptedPages &);

lsn_t get_current_lsn(MYSQL *connection)
{
Expand All @@ -1471,7 +1475,7 @@ lsn_t get_current_lsn(MYSQL *connection)
lsn_t server_lsn_after_lock;
extern void backup_wait_for_lsn(lsn_t lsn);
/** Start --backup */
bool backup_start()
bool backup_start(CorruptedPages &corrupted_pages)
{
if (!opt_no_lock) {
if (opt_safe_slave_backup) {
Expand Down Expand Up @@ -1506,7 +1510,7 @@ bool backup_start()

msg("Waiting for log copy thread to read lsn %llu", (ulonglong)server_lsn_after_lock);
backup_wait_for_lsn(server_lsn_after_lock);
backup_fix_ddl();
backup_fix_ddl(corrupted_pages);

// There is no need to stop slave thread before coping non-Innodb data when
// --no-lock option is used because --no-lock option requires that no DDL or
Expand Down
3 changes: 2 additions & 1 deletion extra/mariabackup/backup_copy.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ copy_file(ds_ctxt_t *datasink,
uint thread_n);

/** Start --backup */
bool backup_start();
bool backup_start(CorruptedPages &corrupted_pages);
/** Release resources after backup_start() */
void backup_release();
/** Finish after backup_start() and backup_release() */
Expand All @@ -51,5 +51,6 @@ directory_exists(const char *dir, bool create);

lsn_t
get_current_lsn(MYSQL *connection);
bool backup_file_print_buf(const char *filename, const char *buf, int buf_len);

#endif
32 changes: 32 additions & 0 deletions extra/mariabackup/backup_debug.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once
#include "my_dbug.h"
#ifndef DBUG_OFF
extern char *dbug_mariabackup_get_val(const char *event, const char *key);
/*
In debug mode, execute SQL statement that was passed via environment.
To use this facility, you need to
1. Add code DBUG_EXECUTE_MARIABACKUP_EVENT("my_event_name", key););
to the code. key is usually a table name
2. Set environment variable my_event_name_$key SQL statement you want to execute
when event occurs, in DBUG_EXECUTE_IF from above.
In mtr , you can set environment via 'let' statement (do not use $ as the first char
for the variable)
3. start mariabackup with --dbug=+d,debug_mariabackup_events
*/
extern void dbug_mariabackup_event(
const char *event,const char *key);
#define DBUG_MARIABACKUP_EVENT(A, B) \
DBUG_EXECUTE_IF("mariabackup_events", \
dbug_mariabackup_event(A,B););
#define DBUG_EXECUTE_FOR_KEY(EVENT, KEY, CODE) \
DBUG_EXECUTE_IF("mariabackup_inject_code", {\
char *dbug_val = dbug_mariabackup_get_val(EVENT, KEY); \
if (dbug_val && *dbug_val) CODE \
})
#else
#define DBUG_MARIABACKUP_EVENT(A,B)
#define DBUG_MARIABACKUP_EVENT_LOCK(A,B)
#define DBUG_EXECUTE_FOR_KEY(EVENT, KEY, CODE)
#endif

1 change: 0 additions & 1 deletion extra/mariabackup/encryption_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include <mysql.h>
#include <xtrabackup.h>
#include <encryption_plugin.h>
#include <backup_copy.h>
#include <sql_plugin.h>
#include <sstream>
#include <vector>
Expand Down
50 changes: 35 additions & 15 deletions extra/mariabackup/fil_cur.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
#include "read_filt.h"
#include "xtrabackup.h"
#include "xb0xb.h"
#include "backup_debug.h"

/* Size of read buffer in pages (640 pages = 10M for 16K sized pages) */
#define XB_FIL_CUR_PAGES 640
Expand Down Expand Up @@ -372,16 +373,15 @@ static bool page_is_corrupted(const byte *page, ulint page_no,
return buf_page_is_corrupted(true, page, cursor->page_size, space);
}

/************************************************************************
Reads and verifies the next block of pages from the source
/** Reads and verifies the next block of pages from the source
file. Positions the cursor after the last read non-corrupted page.
@param[in,out] cursor source file cursor
@param[out] corrupted_pages adds corrupted pages if
opt_log_innodb_page_corruption is set
@return XB_FIL_CUR_SUCCESS if some have been read successfully, XB_FIL_CUR_EOF
if there are no more pages to read and XB_FIL_CUR_ERROR on error. */
xb_fil_cur_result_t
xb_fil_cur_read(
/*============*/
xb_fil_cur_t* cursor) /*!< in/out: source file cursor */
xb_fil_cur_result_t xb_fil_cur_read(xb_fil_cur_t* cursor,
CorruptedPages &corrupted_pages)
{
byte* page;
ulint i;
Expand Down Expand Up @@ -455,20 +455,40 @@ xb_fil_cur_read(
retry_count--;

if (retry_count == 0) {
const char *ignore_corruption_warn = opt_log_innodb_page_corruption ?
" WARNING!!! The corruption is ignored due to"
" log-innodb-page-corruption option, the backup can contain"
" corrupted data." : "";
msg(cursor->thread_n,
"Error: failed to read page after "
"10 retries. File %s seems to be "
"corrupted.", cursor->abs_path);
ret = XB_FIL_CUR_ERROR;
"corrupted.%s", cursor->abs_path, ignore_corruption_warn);
buf_page_print(page, cursor->page_size);
break;
if (opt_log_innodb_page_corruption) {
corrupted_pages.add_page(cursor->node->name, cursor->node->space->id,
page_no);
retry_count = 1;
}
else {
ret = XB_FIL_CUR_ERROR;
break;
}
}
else {
msg(cursor->thread_n, "Database page corruption detected at page "
ULINTPF ", retrying...",
page_no);
os_thread_sleep(100000);
goto read_retry;
}
msg(cursor->thread_n, "Database page corruption detected at page "
ULINTPF ", retrying...",
page_no);
os_thread_sleep(100000);
goto read_retry;
}
DBUG_EXECUTE_FOR_KEY("add_corrupted_page_for", cursor->node->space->name,
{
ulint corrupted_page_no = strtoul(dbug_val, NULL, 10);
if (page_no == corrupted_page_no)
corrupted_pages.add_page(cursor->node->name, cursor->node->space->id,
corrupted_page_no);
});
cursor->buf_read += page_size;
cursor->buf_npages++;
}
Expand Down
15 changes: 7 additions & 8 deletions extra/mariabackup/fil_cur.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
#include <my_dir.h>
#include "read_filt.h"
#include "srv0start.h"
#include "xtrabackup.h"

struct xb_fil_cur_t {
pfs_os_file_t file; /*!< source file handle */
Expand Down Expand Up @@ -89,17 +90,15 @@ xb_fil_cur_open(
uint thread_n, /*!< thread number for diagnostics */
ulonglong max_file_size = ULLONG_MAX);

/************************************************************************
Reads and verifies the next block of pages from the source
/** Reads and verifies the next block of pages from the source
file. Positions the cursor after the last read non-corrupted page.
@param[in,out] cursor source file cursor
@param[out] corrupted_pages adds corrupted pages if
opt_log_innodb_page_corruption is set
@return XB_FIL_CUR_SUCCESS if some have been read successfully, XB_FIL_CUR_EOF
if there are no more pages to read and XB_FIL_CUR_ERROR on error. */
xb_fil_cur_result_t
xb_fil_cur_read(
/*============*/
xb_fil_cur_t* cursor); /*!< in/out: source file cursor */

xb_fil_cur_result_t xb_fil_cur_read(xb_fil_cur_t *cursor,
CorruptedPages &corrupted_pages);
/************************************************************************
Close the source file cursor opened with xb_fil_cur_open() and its
associated read filter. */
Expand Down
26 changes: 14 additions & 12 deletions extra/mariabackup/write_filt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
#include "common.h"
#include "write_filt.h"
#include "fil_cur.h"
#include "xtrabackup.h"
#include <os0proc.h>

/************************************************************************
Write-through page write filter. */
static my_bool wf_wt_init(xb_write_filt_ctxt_t *ctxt, char *dst_name,
xb_fil_cur_t *cursor);
xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages);
static my_bool wf_wt_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile);

xb_write_filt_t wf_write_through = {
Expand All @@ -45,7 +44,7 @@ xb_write_filt_t wf_write_through = {
/************************************************************************
Incremental page write filter. */
static my_bool wf_incremental_init(xb_write_filt_ctxt_t *ctxt, char *dst_name,
xb_fil_cur_t *cursor);
xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages);
static my_bool wf_incremental_process(xb_write_filt_ctxt_t *ctxt,
ds_file_t *dstfile);
static my_bool wf_incremental_finalize(xb_write_filt_ctxt_t *ctxt,
Expand All @@ -65,11 +64,11 @@ Initialize incremental page write filter.
@return TRUE on success, FALSE on error. */
static my_bool
wf_incremental_init(xb_write_filt_ctxt_t *ctxt, char *dst_name,
xb_fil_cur_t *cursor)
xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages)
{
char meta_name[FN_REFLEN];
xb_wf_incremental_ctxt_t *cp =
&(ctxt->u.wf_incremental_ctxt);
&(ctxt->wf_incremental_ctxt);

ctxt->cursor = cursor;

Expand Down Expand Up @@ -100,7 +99,9 @@ wf_incremental_init(xb_write_filt_ctxt_t *ctxt, char *dst_name,
strcat(dst_name, ".delta");

mach_write_to_4(cp->delta_buf, 0x78747261UL); /*"xtra"*/

cp->npages = 1;
cp->corrupted_pages = corrupted_pages;

return(TRUE);
}
Expand All @@ -117,15 +118,16 @@ wf_incremental_process(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile)
byte *page;
const ulint page_size
= cursor->page_size.physical();
xb_wf_incremental_ctxt_t *cp = &(ctxt->u.wf_incremental_ctxt);
xb_wf_incremental_ctxt_t *cp = &(ctxt->wf_incremental_ctxt);

for (i = 0, page = cursor->buf; i < cursor->buf_npages;
i++, page += page_size) {

if (incremental_lsn >= mach_read_from_8(page + FIL_PAGE_LSN)) {

if ((!cp->corrupted_pages ||
!cp->corrupted_pages->contains(cursor->node->space->id,
cursor->buf_page_no + i)) &&
incremental_lsn >= mach_read_from_8(page + FIL_PAGE_LSN))
continue;
}

/* updated page */
if (cp->npages == page_size / 4) {
Expand Down Expand Up @@ -163,7 +165,7 @@ wf_incremental_finalize(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile)
xb_fil_cur_t *cursor = ctxt->cursor;
const ulint page_size
= cursor->page_size.physical();
xb_wf_incremental_ctxt_t *cp = &(ctxt->u.wf_incremental_ctxt);
xb_wf_incremental_ctxt_t *cp = &(ctxt->wf_incremental_ctxt);

if (cp->npages != page_size / 4) {
mach_write_to_4(cp->delta_buf + cp->npages * 4, 0xFFFFFFFFUL);
Expand All @@ -185,7 +187,7 @@ Free the incremental page write filter's buffer. */
static void
wf_incremental_deinit(xb_write_filt_ctxt_t *ctxt)
{
xb_wf_incremental_ctxt_t *cp = &(ctxt->u.wf_incremental_ctxt);
xb_wf_incremental_ctxt_t *cp = &(ctxt->wf_incremental_ctxt);
os_mem_free_large(cp->delta_buf, cp->delta_buf_size);
}

Expand All @@ -195,7 +197,7 @@ Initialize the write-through page write filter.
@return TRUE on success, FALSE on error. */
static my_bool
wf_wt_init(xb_write_filt_ctxt_t *ctxt, char *dst_name __attribute__((unused)),
xb_fil_cur_t *cursor)
xb_fil_cur_t *cursor, CorruptedPages *)
{
ctxt->cursor = cursor;

Expand Down
8 changes: 4 additions & 4 deletions extra/mariabackup/write_filt.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA

#include "fil_cur.h"
#include "datasink.h"
#include "xtrabackup.h"

/* Incremental page filter context */
typedef struct {
ulint delta_buf_size;
byte *delta_buf;
ulint npages;
CorruptedPages *corrupted_pages;
} xb_wf_incremental_ctxt_t;

/* Page filter context used as an opaque structure by callers */
typedef struct {
xb_fil_cur_t *cursor;
union {
xb_wf_incremental_ctxt_t wf_incremental_ctxt;
} u;
xb_wf_incremental_ctxt_t wf_incremental_ctxt;
} xb_write_filt_ctxt_t;


typedef struct {
my_bool (*init)(xb_write_filt_ctxt_t *ctxt, char *dst_name,
xb_fil_cur_t *cursor);
xb_fil_cur_t *cursor, CorruptedPages *corrupted_pages);
my_bool (*process)(xb_write_filt_ctxt_t *ctxt, ds_file_t *dstfile);
my_bool (*finalize)(xb_write_filt_ctxt_t *, ds_file_t *dstfile);
void (*deinit)(xb_write_filt_ctxt_t *);
Expand Down
Loading

0 comments on commit e6b3e38

Please sign in to comment.