Skip to content

Commit a390aaa

Browse files
Thirunarayananvuvova
authored andcommitted
MDEV-36180 Doublewrite recovery of innodb_checksum_algorithm=full_crc32 page_compressed pages does not work
- InnoDB fails to recover the full crc32 page_compressed page from doublewrite buffer. The reason is that buf_dblwr_t::recover() fails to identify the space id from the page because the page has compressed from FIL_PAGE_FILE_FLUSH_LSN_OR_KEY_VERSION bytes. Fix: === recv_dblwr_t::find_deferred_page(): Find the page which has the same page number and try to decompress/decrypt the page based on the tablespace metadata. After the decompression/decryption, compare the space id and write the recovered page back to the file. buf_page_t::read_complete(): Page read from disk is corrupted then try to read the page from deferred pages in doublewrite buffer.
1 parent 19c4e1a commit a390aaa

File tree

7 files changed

+85
-34
lines changed

7 files changed

+85
-34
lines changed

mysql-test/suite/encryption/r/doublewrite_debug.result

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,47 @@ call mtr.add_suppression("InnoDB: Unable to apply log to corrupted page ");
33
call mtr.add_suppression("InnoDB: Plugin initialization aborted");
44
call mtr.add_suppression("Plugin 'InnoDB' init function returned error");
55
call mtr.add_suppression("Plugin 'InnoDB' registration as a STORAGE ENGINE failed");
6-
create table t1 (f1 int primary key, f2 blob)page_compressed = 1 engine=innodb stats_persistent=0;
7-
create table t2(f1 int primary key, f2 blob)engine=innodb stats_persistent=0;
6+
create table t1 (f1 int primary key, f2 blob)page_compressed=1 engine=innodb encrypted=yes stats_persistent=0;
7+
create table t2(f1 int primary key, f2 blob)engine=innodb encrypted=yes stats_persistent=0;
8+
create table t3(f1 int primary key, f2 blob)page_compressed=1 engine=innodb encrypted=no stats_persistent=0;
89
start transaction;
910
insert into t1 values(1, repeat('#',12));
1011
insert into t1 values(2, repeat('+',12));
1112
insert into t1 values(3, repeat('/',12));
1213
insert into t1 values(4, repeat('-',12));
1314
insert into t1 values(5, repeat('.',12));
1415
insert into t2 select * from t1;
16+
insert into t3 select * from t1;
1517
commit work;
1618
SET GLOBAL innodb_fast_shutdown = 0;
1719
# restart: --debug_dbug=+d,ib_log_checkpoint_avoid_hard --innodb_flush_sync=0
1820
select space into @t1_space_id from information_schema.innodb_sys_tablespaces where name='test/t1';
1921
select space into @t2_space_id from information_schema.innodb_sys_tablespaces where name='test/t2';
22+
select space into @t3_space_id from information_schema.innodb_sys_tablespaces where name='test/t3';
2023
begin;
2124
insert into t1 values (6, repeat('%', 400));
2225
insert into t2 values (6, repeat('%', 400));
26+
insert into t3 values (6, repeat('%', 400));
2327
# xtrabackup prepare
2428
set global innodb_saved_page_number_debug = 3;
2529
set global innodb_fil_make_page_dirty_debug = @t1_space_id;
2630
set global innodb_saved_page_number_debug = 3;
2731
set global innodb_fil_make_page_dirty_debug = @t2_space_id;
32+
set global innodb_saved_page_number_debug = 3;
33+
set global innodb_fil_make_page_dirty_debug = @t3_space_id;
2834
set global innodb_buf_flush_list_now = 1;
2935
# Kill the server
3036
# restart
31-
FOUND 2 /InnoDB: Recovered page \[page id: space=[1-9]*, page number=3\]/ in mysqld.1.err
37+
FOUND 3 /InnoDB: Recovered page \[page id: space=[1-9]*, page number=3\]/ in mysqld.1.err
3238
check table t1;
3339
Table Op Msg_type Msg_text
3440
test.t1 check status OK
3541
check table t2;
3642
Table Op Msg_type Msg_text
3743
test.t2 check status OK
44+
check table t3;
45+
Table Op Msg_type Msg_text
46+
test.t3 check status OK
3847
select f1, f2 from t1;
3948
f1 f2
4049
1 ############
@@ -49,6 +58,13 @@ f1 f2
4958
3 ////////////
5059
4 ------------
5160
5 ............
61+
select f1, f2 from t3;
62+
f1 f2
63+
1 ############
64+
2 ++++++++++++
65+
3 ////////////
66+
4 ------------
67+
5 ............
5268
SET GLOBAL innodb_fast_shutdown = 0;
5369
# shutdown server
5470
# remove datadir
@@ -78,4 +94,4 @@ f1 f2
7894
3 ////////////
7995
4 ------------
8096
5 ............
81-
drop table t2, t1;
97+
drop table t3, t2, t1;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
--innodb-use-atomic-writes=0
2-
--innodb-encrypt-tables=FORCE
2+
--innodb-encrypt-tables=on
33
--innodb_sys_tablespaces

mysql-test/suite/encryption/t/doublewrite_debug.test

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ let INNODB_PAGE_SIZE=`select @@innodb_page_size`;
1212
let MYSQLD_DATADIR=`select @@datadir`;
1313
let ALGO=`select @@innodb_checksum_algorithm`;
1414

15-
create table t1 (f1 int primary key, f2 blob)page_compressed = 1 engine=innodb stats_persistent=0;
16-
create table t2(f1 int primary key, f2 blob)engine=innodb stats_persistent=0;
15+
create table t1 (f1 int primary key, f2 blob)page_compressed=1 engine=innodb encrypted=yes stats_persistent=0;
16+
create table t2(f1 int primary key, f2 blob)engine=innodb encrypted=yes stats_persistent=0;
17+
create table t3(f1 int primary key, f2 blob)page_compressed=1 engine=innodb encrypted=no stats_persistent=0;
1718

1819
start transaction;
1920
insert into t1 values(1, repeat('#',12));
@@ -22,6 +23,7 @@ insert into t1 values(3, repeat('/',12));
2223
insert into t1 values(4, repeat('-',12));
2324
insert into t1 values(5, repeat('.',12));
2425
insert into t2 select * from t1;
26+
insert into t3 select * from t1;
2527
commit work;
2628

2729
# Slow shutdown and restart to make sure ibuf merge is finished
@@ -33,12 +35,14 @@ let $restart_parameters=--debug_dbug=+d,ib_log_checkpoint_avoid_hard --innodb_fl
3335

3436
select space into @t1_space_id from information_schema.innodb_sys_tablespaces where name='test/t1';
3537
select space into @t2_space_id from information_schema.innodb_sys_tablespaces where name='test/t2';
38+
select space into @t3_space_id from information_schema.innodb_sys_tablespaces where name='test/t3';
3639

3740
begin;
3841
insert into t1 values (6, repeat('%', 400));
3942
insert into t2 values (6, repeat('%', 400));
43+
insert into t3 values (6, repeat('%', 400));
4044

41-
# Copy the t1.ibd, t2.ibd file
45+
# Copy the t1.ibd, t2.ibd, t3.ibd file
4246
let $targetdir=$MYSQLTEST_VARDIR/tmp/backup_1;
4347
--disable_result_log
4448
exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$targetdir;
@@ -54,8 +58,11 @@ set global innodb_fil_make_page_dirty_debug = @t1_space_id;
5458
set global innodb_saved_page_number_debug = 3;
5559
set global innodb_fil_make_page_dirty_debug = @t2_space_id;
5660

61+
set global innodb_saved_page_number_debug = 3;
62+
set global innodb_fil_make_page_dirty_debug = @t3_space_id;
63+
5764
set global innodb_buf_flush_list_now = 1;
58-
--let CLEANUP_IF_CHECKPOINT=drop table t1, t2, unexpected_checkpoint;
65+
--let CLEANUP_IF_CHECKPOINT=drop table t1, t2, t3, unexpected_checkpoint;
5966
--source ../../suite/innodb/include/no_checkpoint_end.inc
6067
# Corrupt the page 3 in t1.ibd, t2.ibd file
6168
perl;
@@ -103,6 +110,15 @@ binmode FILE;
103110
sysseek(FILE, 3*$page_size, 0);
104111
print FILE chr(0) x ($ENV{'INNODB_PAGE_SIZE'});
105112
close FILE;
113+
114+
# Zero the complete page
115+
my $fname= "$ENV{'MYSQLD_DATADIR'}test/t3.ibd";
116+
open(FILE, "+<", $fname) or die;
117+
FILE->autoflush(1);
118+
binmode FILE;
119+
sysseek(FILE, 3*$page_size, 0);
120+
print FILE chr(0) x ($ENV{'INNODB_PAGE_SIZE'});
121+
close FILE;
106122
EOF
107123

108124
# Successful recover from doublewrite buffer
@@ -114,8 +130,10 @@ let SEARCH_PATTERN=InnoDB: Recovered page \\[page id: space=[1-9]*, page number=
114130

115131
check table t1;
116132
check table t2;
133+
check table t3;
117134
select f1, f2 from t1;
118135
select f1, f2 from t2;
136+
select f1, f2 from t3;
119137

120138
SET GLOBAL innodb_fast_shutdown = 0;
121139
let $shutdown_timeout=;
@@ -220,4 +238,4 @@ select * from t1;
220238

221239
--source ../../mariabackup/include/restart_and_restore.inc
222240
select * from t1;
223-
drop table t2, t1;
241+
drop table t3, t2, t1;

storage/innobase/buf/buf0buf.cc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3766,10 +3766,9 @@ dberr_t buf_page_t::read_complete(const fil_node_t &node) noexcept
37663766
if (err == DB_PAGE_CORRUPTED || err == DB_DECRYPTION_FAILED)
37673767
{
37683768
release_page:
3769-
if (node.space->full_crc32() && node.space->crypt_data &&
3770-
recv_recovery_is_on() &&
3771-
recv_sys.dblwr.find_encrypted_page(node, id().page_no(),
3772-
const_cast<byte*>(read_frame)))
3769+
if (node.space->full_crc32() && recv_recovery_is_on() &&
3770+
recv_sys.dblwr.find_deferred_page(node, id().page_no(),
3771+
const_cast<byte*>(read_frame)))
37733772
{
37743773
/* Recover from doublewrite buffer */
37753774
err= DB_SUCCESS;

storage/innobase/buf/buf0dblwr.cc

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ void buf_dblwr_t::recover() noexcept
377377
srv_page_size));
378378
byte *const buf= read_buf + srv_page_size;
379379

380-
std::deque<byte*> encrypted_pages;
380+
std::deque<byte*> deferred_pages;
381381
for (recv_dblwr_t::list::iterator i= recv_sys.dblwr.pages.begin();
382382
i != recv_sys.dblwr.pages.end(); ++i, ++page_no_dblwr)
383383
{
@@ -396,11 +396,12 @@ void buf_dblwr_t::recover() noexcept
396396
{
397397
/* These pages does not appear to belong to any tablespace.
398398
There is a possibility that this page could be
399-
encrypted using full_crc32 format. If innodb encounters
400-
any corrupted encrypted page during recovery then
401-
InnoDB should use this page to find the valid page.
402-
See find_encrypted_page() */
403-
encrypted_pages.push_back(*i);
399+
encrypted/compressed using full_crc32 format.
400+
If innodb encounters any corrupted encrypted/compressed
401+
page during recovery then InnoDB should use this page to
402+
find the valid page.
403+
See find_encrypted_page()/find_page_compressed() */
404+
deferred_pages.push_back(*i);
404405
continue;
405406
}
406407

@@ -481,7 +482,7 @@ void buf_dblwr_t::recover() noexcept
481482
}
482483

483484
recv_sys.dblwr.pages.clear();
484-
for (byte *page : encrypted_pages)
485+
for (byte *page : deferred_pages)
485486
recv_sys.dblwr.pages.push_back(page);
486487
fil_flush_file_spaces();
487488
aligned_free(read_buf);

storage/innobase/include/log0recv.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,17 @@ struct recv_dblwr_t
150150
const fil_space_t *space= nullptr,
151151
byte *tmp_buf= nullptr) const noexcept;
152152

153-
/** Find the doublewrite copy of an encrypted page with the
154-
smallest FIL_PAGE_LSN that is large enough for recovery.
153+
/** Find the doublewrite copy of an encrypted/page_compressed
154+
page with the smallest FIL_PAGE_LSN that is large enough for
155+
recovery.
155156
@param space tablespace object
156157
@param page_no page number to find
157-
@param buf buffer for unencrypted page
158+
@param buf buffer for unencrypted/uncompressed page
158159
@return buf
159160
@retval nullptr if the page was not found in doublewrite buffer */
160-
byte *find_encrypted_page(const fil_node_t &space, uint32_t page_no,
161-
byte *buf) noexcept;
161+
ATTRIBUTE_COLD byte *find_deferred_page(const fil_node_t &space,
162+
uint32_t page_no,
163+
byte *buf) noexcept;
162164

163165
/** Restore the first page of the given tablespace from
164166
doublewrite buffer.

storage/innobase/log/log0recv.cc

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4842,28 +4842,43 @@ bool recv_dblwr_t::validate_page(const page_id_t page_id, lsn_t max_lsn,
48424842
goto check_if_corrupted;
48434843
}
48444844

4845-
byte *recv_dblwr_t::find_encrypted_page(const fil_node_t &node,
4846-
uint32_t page_no,
4847-
byte *buf) noexcept
4845+
ATTRIBUTE_COLD
4846+
byte *recv_dblwr_t::find_deferred_page(const fil_node_t &node,
4847+
uint32_t page_no,
4848+
byte *buf) noexcept
48484849
{
4849-
ut_ad(node.space->crypt_data);
48504850
ut_ad(node.space->full_crc32());
48514851
mysql_mutex_lock(&recv_sys.mutex);
48524852
byte *result_page= nullptr;
4853+
bool is_encrypted= node.space->crypt_data &&
4854+
node.space->crypt_data->is_encrypted();
48534855
for (list::iterator page_it= pages.begin(); page_it != pages.end();
48544856
page_it++)
48554857
{
48564858
if (page_get_page_no(*page_it) != page_no ||
48574859
buf_page_is_corrupted(true, *page_it, node.space->flags))
48584860
continue;
4861+
4862+
if (is_encrypted &&
4863+
!mach_read_from_4(*page_it + FIL_PAGE_FCRC32_KEY_VERSION))
4864+
continue;
4865+
48594866
memcpy(buf, *page_it, node.space->physical_size());
48604867
buf_tmp_buffer_t *slot= buf_pool.io_buf_reserve(false);
48614868
ut_a(slot);
48624869
slot->allocate();
4863-
bool invalidate=
4864-
!fil_space_decrypt(node.space, slot->crypt_buf, buf) ||
4865-
(node.space->is_compressed() &&
4866-
!fil_page_decompress(slot->crypt_buf, buf, node.space->flags));
4870+
4871+
bool invalidate= false;
4872+
if (is_encrypted)
4873+
{
4874+
invalidate= !fil_space_decrypt(node.space, slot->crypt_buf, buf);
4875+
if (!invalidate && node.space->is_compressed())
4876+
goto decompress;
4877+
}
4878+
else
4879+
decompress:
4880+
invalidate= !fil_page_decompress(slot->crypt_buf, buf,
4881+
node.space->flags);
48674882
slot->release();
48684883

48694884
if (invalidate ||

0 commit comments

Comments
 (0)