diff --git a/src/config.c b/src/config.c index a0313d128..bf56dac38 100644 --- a/src/config.c +++ b/src/config.c @@ -3096,6 +3096,8 @@ standardConfig static_configs[] = { createBoolConfig("cluster-require-full-coverage", NULL, MODIFIABLE_CONFIG, server.cluster_require_full_coverage, 1, NULL, NULL), createBoolConfig("rdb-save-incremental-fsync", NULL, MODIFIABLE_CONFIG, server.rdb_save_incremental_fsync, 1, NULL, NULL), createBoolConfig("aof-load-truncated", NULL, MODIFIABLE_CONFIG, server.aof_load_truncated, 1, NULL, NULL), + /* When AOF is enabled on startup but no AOF files exist, optionally load RDB */ + createBoolConfig("aof-load-rdb-on-startup", NULL, MODIFIABLE_CONFIG, server.aof_load_rdb_on_startup, 0, NULL, NULL), createBoolConfig("aof-use-rdb-preamble", NULL, MODIFIABLE_CONFIG, server.aof_use_rdb_preamble, 1, NULL, NULL), createBoolConfig("aof-timestamp-enabled", NULL, MODIFIABLE_CONFIG, server.aof_timestamp_enabled, 0, NULL, NULL), createBoolConfig("cluster-replica-no-failover", "cluster-slave-no-failover", MODIFIABLE_CONFIG, server.cluster_slave_no_failover, 0, NULL, updateClusterFlags), /* Failover by default. */ diff --git a/src/server.c b/src/server.c index b56c6d3fb..e8d8847ae 100644 --- a/src/server.c +++ b/src/server.c @@ -7176,9 +7176,63 @@ void loadDataFromDisk(void) { int ret = loadAppendOnlyFiles(server.aof_manifest); if (ret == AOF_FAILED || ret == AOF_OPEN_ERR) exit(1); - if (ret != AOF_NOT_EXIST) + + if (ret == AOF_NOT_EXIST || ret == AOF_EMPTY) { + /* Optionally fall back to loading RDB when no usable AOF exists */ + if (server.aof_load_rdb_on_startup) { + rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; + int rsi_is_valid = 0; + errno = 0; + int rdb_flags = RDBFLAGS_NONE; + if (iAmMaster()) { + /* Master may delete expired keys when loading, we should + * propagate expire to replication backlog. */ + createReplicationBacklog(); + rdb_flags |= RDBFLAGS_FEED_REPL; + } + int rdb_load_ret = rdbLoad(server.rdb_filename, &rsi, rdb_flags); + if (rdb_load_ret == RDB_OK) { + serverLog(LL_NOTICE, + "AOF not found/empty, DB loaded from RDB due to aof-load-rdb-on-startup: %.3f seconds", + (float)(ustime()-start)/1000000); + + if (rsi.repl_id_is_set && rsi.repl_offset != -1 && rsi.repl_stream_db != -1) { + rsi_is_valid = 1; + if (!iAmMaster()) { + memcpy(server.replid,rsi.repl_id,sizeof(server.replid)); + server.master_repl_offset = rsi.repl_offset; + replicationCacheMasterUsingMyself(); + selectDb(server.cached_master,rsi.repl_stream_db); + } else { + memcpy(server.replid2,rsi.repl_id,sizeof(server.replid)); + server.second_replid_offset = rsi.repl_offset+1; + server.master_repl_offset += rsi.repl_offset; + serverAssert(server.repl_backlog); + server.repl_backlog->offset = server.master_repl_offset - + server.repl_backlog->histlen + 1; + rebaseReplicationBuffer(rsi.repl_offset); + server.repl_no_slaves_since = time(NULL); + } + } + } else if (rdb_load_ret == RDB_NOT_EXIST) { + serverLog(LL_NOTICE, + "AOF not found/empty and no RDB found, starting with empty dataset"); + } else { + serverLog(LL_WARNING, "Fatal error loading the DB, check server logs. Exiting."); + exit(1); + } + + /* Drop backlog if partial resync is not possible */ + if (!rsi_is_valid && server.repl_backlog) + freeReplicationBacklog(); + } else { + serverLog(LL_NOTICE, + "AOF not found/empty and aof-load-rdb-on-startup is disabled, starting with empty dataset"); + } + } else { serverLog(LL_NOTICE, "DB loaded from append only file: %.3f seconds", (float)(ustime()-start)/1000000); - updateReplOffsetAndResetEndOffset(); + updateReplOffsetAndResetEndOffset(); + } } else { rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; int rsi_is_valid = 0; diff --git a/src/server.h b/src/server.h index 1e537e2a7..cd2506287 100644 --- a/src/server.h +++ b/src/server.h @@ -2088,6 +2088,7 @@ struct redisServer { aofManifest *aof_manifest; /* Used to track AOFs. */ int aof_disable_auto_gc; /* If disable automatically deleting HISTORY type AOFs? default no. (for testings). */ + int aof_load_rdb_on_startup; /* If AOF is enabled but no AOF exists, load RDB on startup. */ /* RDB persistence */ long long dirty; /* Changes to DB from the last save */ diff --git a/tests/integration/aof-rdb-fallback.tcl b/tests/integration/aof-rdb-fallback.tcl new file mode 100644 index 000000000..2094c6322 --- /dev/null +++ b/tests/integration/aof-rdb-fallback.tcl @@ -0,0 +1,30 @@ +set server_path [tmpdir aof.rdb.fallback] + +tags {aof} { + # Stage 1: Create an RDB snapshot without AOF present. + start_server [list overrides [list dir $server_path appendonly {no}] keep_persistence true] { + test {Prepare dataset and force RDB SAVE} { + r set foo bar + r save + } + } + + # Sanity: Starting with AOF enabled, without fallback, should not load RDB. + start_server [list overrides [list dir $server_path appendonly {yes}] keep_persistence true] { + test {Without fallback, AOF enabled but missing: dataset is empty} { + assert_equal 0 [r exists foo] + } + } + + # Remove any AOF artifacts to simulate missing AOF before fallback test + catch { exec rm -rf [file join $server_path appendonlydir] } + + # Stage 2: Enable fallback and verify RDB is loaded and AOF is active. + start_server [list overrides [list dir $server_path appendonly {yes} aof-load-rdb-on-startup {yes}] keep_persistence true] { + test {With fallback, AOF enabled and missing: load RDB on startup} { + assert_equal {bar} [r get foo] + } + } +} + +