diff --git a/src/aof.c b/src/aof.c index 2d18aae8210..46d9b6d0c7e 100644 --- a/src/aof.c +++ b/src/aof.c @@ -1524,7 +1524,7 @@ struct client *createAOFClient(void) { * AOF_NOT_EXIST: AOF file doesn't exist. * AOF_EMPTY: The AOF file is empty (nothing to load). * AOF_FAILED: Failed to load the AOF file. */ -int loadSingleAppendOnlyFile(char *filename) { +int loadSingleAppendOnlyFile(char *filename, rdbSaveInfo *rsi) { struct client *fakeClient; struct valkey_stat sb; int old_aof_state = server.aof_state; @@ -1583,7 +1583,7 @@ int loadSingleAppendOnlyFile(char *filename) { if (fseek(fp, 0, SEEK_SET) == -1) goto readerr; rioInitWithFile(&rdb, fp); - if (rdbLoadRio(&rdb, RDBFLAGS_AOF_PREAMBLE, NULL) != RDB_OK) { + if (rdbLoadRio(&rdb, RDBFLAGS_AOF_PREAMBLE, rsi) != RDB_OK) { if (old_style) serverLog(LL_WARNING, "Error reading the RDB preamble of the AOF file %s, AOF loading aborted", filename); @@ -1777,7 +1777,7 @@ int loadSingleAppendOnlyFile(char *filename) { } /* Load the AOF files according the aofManifest pointed by am. */ -int loadAppendOnlyFiles(aofManifest *am) { +int loadAppendOnlyFiles(aofManifest *am, rdbSaveInfo *rsi) { serverAssert(am != NULL); int status, ret = AOF_OK; long long start; @@ -1831,7 +1831,7 @@ int loadAppendOnlyFiles(aofManifest *am) { base_size = getAppendOnlyFileSize(aof_name, NULL); last_file = ++aof_num == total_num; start = ustime(); - ret = loadSingleAppendOnlyFile(aof_name); + ret = loadSingleAppendOnlyFile(aof_name, rsi); if (ret == AOF_OK || (ret == AOF_TRUNCATED && last_file)) { serverLog(LL_NOTICE, "DB loaded from base file %s: %.3f seconds", aof_name, (float)(ustime() - start) / 1000000); @@ -1861,7 +1861,7 @@ int loadAppendOnlyFiles(aofManifest *am) { updateLoadingFileName(aof_name); last_file = ++aof_num == total_num; start = ustime(); - ret = loadSingleAppendOnlyFile(aof_name); + ret = loadSingleAppendOnlyFile(aof_name, rsi); if (ret == AOF_OK || (ret == AOF_TRUNCATED && last_file)) { serverLog(LL_NOTICE, "DB loaded from incr file %s: %.3f seconds", aof_name, (float)(ustime() - start) / 1000000); diff --git a/src/rdb.c b/src/rdb.c index 951321db4ca..0bfdc981a21 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -4289,5 +4289,15 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) { rsi->repl_stream_db = server.cached_primary->db->id; return rsi; } + + /* If the instance is a replica but has no active primary connection + * and no cached primary (e.g., during a clean shutdown where the + * primary client was freed before the RDB save), we can still save + * the replication info using the server's current state. This allows + * a restarted replica to attempt partial resynchronization. */ + if (server.primary_host) { + rsi->repl_stream_db = 0; /* Safe default, SELECT will be sent */ + return rsi; + } return NULL; } diff --git a/src/server.c b/src/server.c index 3f68250339e..e206fc41979 100644 --- a/src/server.c +++ b/src/server.c @@ -7333,10 +7333,45 @@ int checkForSentinelMode(int argc, char **argv, char *exec_name) { void loadDataFromDisk(void) { ustime_t start = ustime(); if (server.aof_state == AOF_ON) { - int ret = loadAppendOnlyFiles(server.aof_manifest); + rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; + int rsi_is_valid = 0; + int ret = loadAppendOnlyFiles(server.aof_manifest, &rsi); if (ret == AOF_FAILED || ret == AOF_OPEN_ERR) exit(1); - if (ret != AOF_NOT_EXIST) + if (ret != AOF_NOT_EXIST) { serverLog(LL_NOTICE, "DB loaded from append only file: %.3f seconds", (float)(ustime() - start) / 1000000); + /* Restore the replication ID / offset from the AOF file's RDB preamble. */ + if (rsi.repl_id_is_set && rsi.repl_offset != -1 && + rsi.repl_stream_db != -1) { + rsi_is_valid = 1; + if (!iAmPrimary()) { + memcpy(server.replid, rsi.repl_id, sizeof(server.replid)); + server.primary_repl_offset = rsi.repl_offset; + /* If this is a replica, create a cached primary from this + * information, in order to allow partial resynchronizations + * with primaries. */ + replicationCachePrimaryUsingMyself(); + selectDb(server.cached_primary, rsi.repl_stream_db); + } else { + /* If this is a primary, we can save the replication info + * as secondary ID and offset, in order to allow replicas + * to partial resynchronizations with primaries. */ + memcpy(server.replid2, rsi.repl_id, sizeof(server.replid)); + server.second_replid_offset = rsi.repl_offset + 1; + /* Rebase primary_repl_offset from rsi.repl_offset. */ + server.primary_repl_offset += rsi.repl_offset; + serverAssert(server.repl_backlog); + server.repl_backlog->offset = server.primary_repl_offset - server.repl_backlog->histlen + 1; + rebaseReplicationBuffer(rsi.repl_offset); + server.repl_no_replicas_since = time(NULL); + } + } + } + /* We always create replication backlog if server is a primary, we need + * it because we put DELs in it when loading expired keys in RDB, but + * if AOF doesn't have replication info or there is no AOF, it is not + * possible to support partial resynchronization, to avoid extra memory + * of replication backlog, we drop it. */ + if (!rsi_is_valid && server.repl_backlog) freeReplicationBacklog(); } else { rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; int rsi_is_valid = 0; diff --git a/src/server.h b/src/server.h index d34fed6851b..ca412c6a5f6 100644 --- a/src/server.h +++ b/src/server.h @@ -3316,7 +3316,7 @@ void flushAppendOnlyFile(int force); void feedAppendOnlyFile(int dictid, robj **argv, int argc); void aofRemoveTempFile(pid_t childpid, int from_signal); int rewriteAppendOnlyFileBackground(void); -int loadAppendOnlyFiles(aofManifest *am); +int loadAppendOnlyFiles(aofManifest *am, rdbSaveInfo *rsi); void stopAppendOnly(void); int startAppendOnly(void); int restartAOFWithSyncRdb(void);