Unverified Commit a50aa29b authored by chenyang8094's avatar chenyang8094 Committed by GitHub
Browse files

Adapt redis-check-aof tool for Multi Part Aof (#10061)

Modifications of this PR:
1. Support the verification of `Multi Part AOF`, while still maintaining support for the
  old-style `AOF/RDB-preamble`. `redis-check-aof` will automatically choose which
  mode to use according to the incoming file format.
   
`Usage: redis-check-aof [--fix|--truncate-to-timestamp $timestamp] <AOF/manifest>`
 
2. Refactor part of the code to make it easier to understand
3. Currently only supports truncate  (`--fix` or `--truncate-to-timestamp`) the last AOF
  file (may be `BASE` or `INCR`)

The reasons for 3 above:
- for `--fix`: Only the last AOF may be truncated, this is guaranteed by redis
- for `--truncate-to-timestamp`:  Normally, we only have `BASE` + `INCR` files
  at most, and `BASE` cannot be truncated(It only contains a timestamp annotation
  at the beginning of the file), so only `INCR` can be truncated. If we have a
  `BASE+INCR1+INCR2` file (meaning we have an interrupted AOFRW), Only `INCR2`
  files can be truncated at this time. If we still insist on truncate `INCR1`, we need to
  manually delete `INCR2` and update the manifest file, then re-run `redis-check-aof`
- If we want to support truncate any file, we need to add very complicated code to support
  the atomic modification of multiple file deletion and update manifest, I think this is unnecessary
parent f7f68c65
......@@ -47,6 +47,8 @@ off_t getBaseAndIncrAppendOnlyFilesSize(aofManifest *am);
int getBaseAndIncrAppendOnlyFilesNum(aofManifest *am);
int aofFileExist(char *filename);
int rewriteAppendOnlyFile(char *filename);
aofManifest *aofLoadManifestFromFile(sds am_filepath);
void aofManifestFreeAndUpdate(aofManifest *am);
/* ----------------------------------------------------------------------------
* AOF Manifest file implementation.
......@@ -226,13 +228,8 @@ sds getAofManifestAsString(aofManifest *am) {
* in order to support seamless upgrades from previous versions which did not
* use them.
*/
#define MANIFEST_MAX_LINE 1024
void aofLoadManifestFromDisk(void) {
const char *err = NULL;
long long maxseq = 0;
server.aof_manifest = aofManifestCreate();
if (!dirExists(server.aof_dirname)) {
serverLog(LL_NOTICE, "The AOF directory %s doesn't exist", server.aof_dirname);
return;
......@@ -247,16 +244,26 @@ void aofLoadManifestFromDisk(void) {
return;
}
aofManifest *am = aofLoadManifestFromFile(am_filepath);
if (am) aofManifestFreeAndUpdate(am);
sdsfree(am_name);
sdsfree(am_filepath);
}
/* Generic manifest loading function, used in `aofLoadManifestFromDisk` and redis-check-aof tool. */
#define MANIFEST_MAX_LINE 1024
aofManifest *aofLoadManifestFromFile(sds am_filepath) {
const char *err = NULL;
long long maxseq = 0;
aofManifest *am = aofManifestCreate();
FILE *fp = fopen(am_filepath, "r");
if (fp == NULL) {
serverLog(LL_WARNING, "Fatal error: can't open the AOF manifest "
"file %s for reading: %s", am_name, strerror(errno));
"file %s for reading: %s", am_filepath, strerror(errno));
exit(1);
}
sdsfree(am_name);
sdsfree(am_filepath);
char buf[MANIFEST_MAX_LINE+1];
sds *argv = NULL;
int argc;
......@@ -292,14 +299,14 @@ void aofLoadManifestFromDisk(void) {
line = sdstrim(sdsnew(buf), " \t\r\n");
if (!sdslen(line)) {
err = "The AOF manifest file is invalid format";
err = "Invalid AOF manifest file format";
goto loaderr;
}
argv = sdssplitargs(line, &argc);
/* 'argc < 6' was done for forward compatibility. */
if (argv == NULL || argc < 6 || (argc % 2)) {
err = "The AOF manifest file is invalid format";
err = "Invalid AOF manifest file format";
goto loaderr;
}
......@@ -321,7 +328,7 @@ void aofLoadManifestFromDisk(void) {
/* We have to make sure we load all the information. */
if (!ai->file_name || !ai->file_seq || !ai->file_type) {
err = "The AOF manifest file is invalid format";
err = "Invalid AOF manifest file format";
goto loaderr;
}
......@@ -329,21 +336,21 @@ void aofLoadManifestFromDisk(void) {
argv = NULL;
if (ai->file_type == AOF_FILE_TYPE_BASE) {
if (server.aof_manifest->base_aof_info) {
if (am->base_aof_info) {
err = "Found duplicate base file information";
goto loaderr;
}
server.aof_manifest->base_aof_info = ai;
server.aof_manifest->curr_base_file_seq = ai->file_seq;
am->base_aof_info = ai;
am->curr_base_file_seq = ai->file_seq;
} else if (ai->file_type == AOF_FILE_TYPE_HIST) {
listAddNodeTail(server.aof_manifest->history_aof_list, ai);
listAddNodeTail(am->history_aof_list, ai);
} else if (ai->file_type == AOF_FILE_TYPE_INCR) {
if (ai->file_seq <= maxseq) {
err = "Found a non-monotonic sequence number";
goto loaderr;
}
listAddNodeTail(server.aof_manifest->incr_aof_list, ai);
server.aof_manifest->curr_incr_file_seq = ai->file_seq;
listAddNodeTail(am->incr_aof_list, ai);
am->curr_incr_file_seq = ai->file_seq;
maxseq = ai->file_seq;
} else {
err = "Unknown AOF file type";
......@@ -356,7 +363,7 @@ void aofLoadManifestFromDisk(void) {
}
fclose(fp);
return;
return am;
loaderr:
/* Sanitizer suppression: may report a false positive if we goto loaderr
......@@ -1526,15 +1533,15 @@ uxeof: /* Unexpected AOF end of file. */
}
}
}
serverLog(LL_WARNING,"Unexpected end of file reading the append only file %s. You can: \
1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. \
2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.", filename);
serverLog(LL_WARNING, "Unexpected end of file reading the append only file %s. You can: "
"1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename.manifest>. "
"2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.", filename);
ret = AOF_FAILED;
goto cleanup;
fmterr: /* Format error. */
serverLog(LL_WARNING,"Bad file format reading the append only file %s: \
make a backup of your AOF file, then use ./redis-check-aof --fix <filename>", filename);
serverLog(LL_WARNING, "Bad file format reading the append only file %s: "
"make a backup of your AOF file, then use ./redis-check-aof --fix <filename.manifest>", filename);
ret = AOF_FAILED;
/* fall through to cleanup. */
......
This diff is collapsed.
......@@ -100,7 +100,7 @@ tags {"external:skip"} {
fail "AOF loading didn't fail"
}
assert_equal 1 [count_message_lines $server_path/stdout "The AOF manifest file is invalid format"]
assert_equal 1 [count_message_lines $server_path/stdout "Invalid AOF manifest file format"]
}
clean_aof_persistence $aof_dirpath
......@@ -186,7 +186,7 @@ tags {"external:skip"} {
fail "AOF loading didn't fail"
}
assert_equal 2 [count_message_lines $server_path/stdout "The AOF manifest file is invalid format"]
assert_equal 2 [count_message_lines $server_path/stdout "Invalid AOF manifest file format"]
}
clean_aof_persistence $aof_dirpath
......@@ -213,7 +213,7 @@ tags {"external:skip"} {
fail "AOF loading didn't fail"
}
assert_equal 3 [count_message_lines $server_path/stdout "The AOF manifest file is invalid format"]
assert_equal 3 [count_message_lines $server_path/stdout "Invalid AOF manifest file format"]
}
clean_aof_persistence $aof_dirpath
......@@ -267,7 +267,7 @@ tags {"external:skip"} {
fail "AOF loading didn't fail"
}
assert_equal 4 [count_message_lines $server_path/stdout "The AOF manifest file is invalid format"]
assert_equal 4 [count_message_lines $server_path/stdout "Invalid AOF manifest file format"]
}
clean_aof_persistence $aof_dirpath
......
......@@ -4,6 +4,7 @@ set server_path [tmpdir server.aof]
set aof_dirname "appendonlydir"
set aof_basename "appendonly.aof"
set aof_dirpath "$server_path/$aof_dirname"
set aof_base_file "$server_path/$aof_dirname/${aof_basename}.1$::base_aof_sufix$::aof_format_suffix"
set aof_file "$server_path/$aof_dirname/${aof_basename}.1$::incr_aof_sufix$::aof_format_suffix"
set aof_manifest_file "$server_path/$aof_dirname/$aof_basename$::manifest_suffix"
......@@ -142,7 +143,7 @@ tags {"aof external:skip"} {
## Test that redis-check-aof indeed sees this AOF is not valid
test "Short read: Utility should confirm the AOF is not valid" {
catch {
exec src/redis-check-aof $aof_file
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*not valid*" $result
}
......@@ -154,13 +155,13 @@ tags {"aof external:skip"} {
}
catch {
exec src/redis-check-aof $aof_file
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*ok_up_to_line=8*" $result
}
test "Short read: Utility should be able to fix the AOF" {
set result [exec src/redis-check-aof --fix $aof_file << "y\n"]
set result [exec src/redis-check-aof --fix $aof_manifest_file << "y\n"]
assert_match "*Successfully truncated AOF*" $result
}
......@@ -444,7 +445,7 @@ tags {"aof external:skip"} {
test {Truncate AOF to specific timestamp} {
# truncate to timestamp 1628217473
exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_file
exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file
start_server_aof [list dir $server_path] {
set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $c
......@@ -454,7 +455,7 @@ tags {"aof external:skip"} {
}
# truncate to timestamp 1628217471
exec src/redis-check-aof --truncate-to-timestamp 1628217471 $aof_file
exec src/redis-check-aof --truncate-to-timestamp 1628217471 $aof_manifest_file
start_server_aof [list dir $server_path] {
set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $c
......@@ -464,7 +465,7 @@ tags {"aof external:skip"} {
}
# truncate to timestamp 1628217470
exec src/redis-check-aof --truncate-to-timestamp 1628217470 $aof_file
exec src/redis-check-aof --truncate-to-timestamp 1628217470 $aof_manifest_file
start_server_aof [list dir $server_path] {
set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_done_loading $c
......@@ -473,7 +474,7 @@ tags {"aof external:skip"} {
}
# truncate to timestamp 1628217469
catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_file} e
catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_manifest_file} e
assert_match {*aborting*} $e
}
......@@ -515,4 +516,120 @@ tags {"aof external:skip"} {
assert_equal [r get foo] 102
}
}
test {Test redis-check-aof for old style resp AOF} {
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
catch {
exec src/redis-check-aof $aof_file
} result
assert_match "*Start checking Old-Style AOF*is valid*" $result
}
test {Test redis-check-aof for old style rdb-preamble AOF} {
catch {
exec src/redis-check-aof tests/assets/rdb-preamble.aof
} result
assert_match "*Start checking Old-Style AOF*RDB preamble is OK, proceeding with AOF tail*is valid*" $result
}
test {Test redis-check-aof for Multi Part AOF with resp AOF base} {
create_aof $aof_dirpath $aof_base_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RESP format)*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result
}
test {Test redis-check-aof for Multi Part AOF with rdb-preamble AOF base} {
exec cp tests/assets/rdb-preamble.aof $aof_base_file
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RDB format)*DB preamble is OK, proceeding with AOF tail*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result
}
test {Test redis-check-aof only truncates the last file for Multi Part AOF in fix mode} {
create_aof $aof_dirpath $aof_base_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand multi]
append_to_aof [formatCommand set bar world]
}
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof $aof_manifest_file
} result
assert_match "*not valid*" $result
catch {
exec src/redis-check-aof --fix $aof_manifest_file
} result
assert_match "*Failed to truncate AOF*because it is not the last file*" $result
}
test {Test redis-check-aof only truncates the last file for Multi Part AOF in truncate-to-timestamp mode} {
create_aof $aof_dirpath $aof_base_file {
append_to_aof "#TS:1628217470\r\n"
append_to_aof [formatCommand set foo1 bar1]
append_to_aof "#TS:1628217471\r\n"
append_to_aof [formatCommand set foo2 bar2]
append_to_aof "#TS:1628217472\r\n"
append_to_aof "#TS:1628217473\r\n"
append_to_aof [formatCommand set foo3 bar3]
append_to_aof "#TS:1628217474\r\n"
}
create_aof $aof_dirpath $aof_file {
append_to_aof [formatCommand set foo hello]
append_to_aof [formatCommand set bar world]
}
create_aof_manifest $aof_dirpath $aof_manifest_file {
append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n"
append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n"
}
catch {
exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file
} result
assert_match "*Failed to truncate AOF*to timestamp*because it is not the last file*" $result
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment