Repository: redis/redis

Issue:
Redis PSYNC replication
I want to process all the data received by master for which I am using PSYNC command to receive all the commands received by master.

```
<serverIP>:6379> psync 76c9130f7880f33c8323d894dc2491bf8e57f29a 113690954
Entering replica output mode...  (press Ctrl-C to quit)
SYNC with master, discarding bytes of bulk transfer until EOF marker...
Error reading RDB payload while SYNCing
```

I am getting this error when running PSYNC in redis cli. How can I read the bytes transferred by PSYNC command and will it also keep receiving more updates or every time I will have to pull the updates with new offset using PSYNC command?
How can I read and process data received by this command?
Can anyone please help me with this?

Log from Master

```
24387:M 07 Sep 2022 08:29:09.685 * Partial resynchronization request from 127.0.0.1:<unknown-replica-port> accepted. Sending 4556 bytes of backlog starting from offset 113690954.
24387:M 07 Sep 2022 08:30:04.023 # Connection with replica 127.0.0.1:<unknown-replica-port> lost.
24387:M 07 Sep 2022 08:30:54.212 * Replica 127.0.0.1:<unknown-replica-port> asks for synchronization
24387:M 07 Sep 2022 08:30:54.212 * Partial resynchronization request from 127.0.0.1:<unknown-replica-port> accepted. Sending 4710 bytes of backlog starting from offset 113690954.
24387:M 07 Sep 2022 08:31:55.870 # Disconnecting timedout replica (streaming sync): 127.0.0.1:<unknown-replica-port>
24387:M 07 Sep 2022 08:31:55.870 # Connection with replica 127.0.0.1:<unknown-replica-port> lost.
```
Version - Redis server v=7.0.4


Discussion:
please state which version of redis you're using, and also check the server log file (post the relevant lines from that time)
@oranagra I have updated the description.
i can confirm there's seem to be a problem with latest redis-cli (older one works, even with latest redis).
i don't currently have time to debug it. maybe someone else can step in.

i did also notice two other things:
* doing `redis-cli --slave` does work, but for some reason it takes longer than it used to take with older redis-cli
* using this `psync <id> <offset>` does result in full-sync anyway (i think you intended to ask for partial sync and skip generating an rdb file)
i tested it in 5.0.14 (both cli and server), it work
and then it looks like it's broken since 6.0 (redis-cli)
i can take a look when i have times
@NirmalP29 for now, you can try to use `sync` instead of psync if needed, i suppose it will work.

something goes wrong with psync, related to psync2 and the FULLRESYNC reply, needed some times to figure it out, i think redis-cli now dosen't support psync
@enjoy-binbin 

```
psync 76c9130f7880f33c8323d894dc2491bf8e57f29a 113793594
Entering replica output mode...  (press Ctrl-C to quit)
SYNC with master, discarding 1197 bytes of bulk transfer...
SYNC done. Logging commands from master.
"ping"
"SELECT","0"
"set","c","key"
```

For redis-cli v 5.0.14 I am able to get the incremental load but how do I get the existing data?
Why this is discarding bulk transfer?
Is there any client which I can use to implement redis replication protocol to process data?
the existing data is a RDB file, you need to find a tool that can parse this. and this is the discarded data (RDB payload). as far as i can tell, now redis-cli psync or redis-cli sync only support the incremental data, so it will discard the RDB data

> Is there any client which I can use to implement redis replication protocol to process 
data?

sorry, i don't have much experience and practice here. 
@chenyang8094 maybe you can chime in? and maybe know the tool (RedisShake?) that can help it

@NirmalP29 I don't know the actual scenario you use, but if you want to monitor the data changes of `redis` for a long time (the monitor and redis do not run on the same machine, otherwise you can directly monitor the update of the AOF file). Then I think the `redis-cli` tool is not suitable for doing this, compared to what you may need is a `CDC (Change Data Capture)` tool for sending AOF updates to the remote monitors.
`RedisShake` is essentially a data synchronization tool. It simulates a complete `redis` replica to replicate data from the source `redis` to the target `redis` you specify. This is not the same as `CDC`. `CDC` may not care about the data processing on the target, But `RedisShake` will force quit when the target `redis` execution fails. So, if you want to use `RedisShake` to monitor redis changes, you need to make correct response to `RedisShake` in your monitor program.
I have implemented socket to capture any change using PSYNC command. Will it impact any performance? I will also try reading from AOF but it might be slow as the CDC service is running on different server. The sequence of commands in PSYNC will be same as master?
> I have implemented socket to capture any change using PSYNC command. Will it impact any performance? 

Redis uses asynchronous replication by default, so I think this has a minimal impact on performance.

> The sequence of commands in PSYNC will be same as master?

The master will rewrite some commands, so the command received by the replica is not necessarily the original command.


Hi @enjoy-binbin , I created a PR https://github.com/redis/redis/pull/11644 to fix this issue. Could you help review it? 

This PR only makes `PSYNC` fall back to `SYNC` logic. I am not sure if we are willing to support `PSYNC` in redis-cli. I will update the PR to support it if so. 

Thanks. 

Patch:
diff --git a/src/redis-cli.c b/src/redis-cli.c
index 1472315401c..c8576702cf0 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -284,7 +284,7 @@ static struct pref {
 
 static volatile sig_atomic_t force_cancel_loop = 0;
 static void usage(int err);
-static void slaveMode(void);
+static void slaveMode(int send_sync);
 char *redisGitSHA1(void);
 char *redisGitDirty(void);
 static int cliConnect(int flags);
@@ -1848,7 +1848,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
 
         if (config.slave_mode) {
             printf("Entering replica output mode...  (press Ctrl-C to quit)\n");
-            slaveMode();
+            slaveMode(0);
             config.slave_mode = 0;
             zfree(argvlen);
             return REDIS_ERR;  /* Error = slaveMode lost connection to master */
@@ -7695,8 +7695,15 @@ static ssize_t readConn(redisContext *c, char *buf, size_t len)
 
 /* Sends SYNC and reads the number of bytes in the payload. Used both by
  * slaveMode() and getRDB().
- * returns 0 in case an EOF marker is used. */
-unsigned long long sendSync(redisContext *c, char *out_eof) {
+ *
+ * send_sync if 1 means we will explicitly send SYNC command. If 0 means
+ * we will not send SYNC command, will send the command that in c->obuf.
+ *
+ * Returns the size of the RDB payload to read, or 0 in case an EOF marker is used and the size
+ * is unknown, also returns 0 in case a PSYNC +CONTINUE was found (no RDB payload).
+ *
+ * The out_full_mode parameter if 1 means this is a full sync, if 0 means this is partial mode. */
+unsigned long long sendSync(redisContext *c, int send_sync, char *out_eof, int *out_full_mode) {
     /* To start we need to send the SYNC command and return the payload.
      * The hiredis client lib does not understand this part of the protocol
      * and we don't want to mess with its buffers, so everything is performed
@@ -7704,10 +7711,20 @@ unsigned long long sendSync(redisContext *c, char *out_eof) {
     char buf[4096], *p;
     ssize_t nread;
 
-    /* Send the SYNC command. */
-    if (cliWriteConn(c, "SYNC\r\n", 6) != 6) {
-        fprintf(stderr,"Error writing to master\n");
-        exit(1);
+    if (out_full_mode) *out_full_mode = 1;
+
+    if (send_sync) {
+        /* Send the SYNC command. */
+        if (cliWriteConn(c, "SYNC\r\n", 6) != 6) {
+            fprintf(stderr,"Error writing to master\n");
+            exit(1);
+        }
+    } else {
+        /* We have written the command into c->obuf before. */
+        if (cliWriteConn(c, "", 0) != 0) {
+            fprintf(stderr,"Error writing to master\n");
+            exit(1);
+        }
     }
 
     /* Read $<payload>\r\n, making sure to read just up to "\n" */
@@ -7720,12 +7737,41 @@ unsigned long long sendSync(redisContext *c, char *out_eof) {
         }
         if (*p == '\n' && p != buf) break;
         if (*p != '\n') p++;
+        if (p >= buf + sizeof(buf) - 1) break; /* Go back one more char for null-term. */
     }
     *p = '\0';
     if (buf[0] == '-') {
         fprintf(stderr, "SYNC with master failed: %s\n", buf);
         exit(1);
     }
+
+    /* Handling PSYNC responses.
+     * Read +FULLRESYNC <replid> <offset>\r\n, after that is the $<payload> or the $EOF:<40 bytes delimiter>
+     * Read +CONTINUE <replid>\r\n or +CONTINUE\r\n, after that is the command stream */
+    if (!strncmp(buf, "+FULLRESYNC", 11) ||
+        !strncmp(buf, "+CONTINUE", 9))
+    {
+        int sync_partial = !strncmp(buf, "+CONTINUE", 9);
+        fprintf(stderr, "PSYNC replied %s\n", buf);
+        p = buf;
+        while(1) {
+            nread = readConn(c,p,1);
+            if (nread <= 0) {
+                fprintf(stderr,"Error reading bulk length while PSYNCing\n");
+                exit(1);
+            }
+            if (*p == '\n' && p != buf) break;
+            if (*p != '\n') p++;
+            if (p >= buf + sizeof(buf) - 1) break; /* Go back one more char for null-term. */
+        }
+        *p = '\0';
+
+        if (sync_partial) {
+            if (out_full_mode) *out_full_mode = 0;
+            return 0;
+        }
+    }
+
     if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= RDB_EOF_MARK_SIZE) {
         memcpy(out_eof, buf+5, RDB_EOF_MARK_SIZE);
         return 0;
@@ -7733,33 +7779,39 @@ unsigned long long sendSync(redisContext *c, char *out_eof) {
     return strtoull(buf+1,NULL,10);
 }
 
-static void slaveMode(void) {
+static void slaveMode(int send_sync) {
     static char eofmark[RDB_EOF_MARK_SIZE];
     static char lastbytes[RDB_EOF_MARK_SIZE];
     static int usemark = 0;
-    unsigned long long payload = sendSync(context,eofmark);
+    static int out_full_mode;
+    unsigned long long payload = sendSync(context, send_sync, eofmark, &out_full_mode);
     char buf[1024];
     int original_output = config.output;
+    char *info = out_full_mode ? "Full resync" : "Partial resync";
 
-    if (payload == 0) {
+    if (out_full_mode == 1 && payload == 0) {
+        /* SYNC with EOF marker or PSYNC +FULLRESYNC with EOF marker. */
         payload = ULLONG_MAX;
         memset(lastbytes,0,RDB_EOF_MARK_SIZE);
         usemark = 1;
-        fprintf(stderr,"SYNC with master, discarding "
-                       "bytes of bulk transfer until EOF marker...\n");
-    } else {
-        fprintf(stderr,"SYNC with master, discarding %llu "
-                       "bytes of bulk transfer...\n", payload);
+        fprintf(stderr, "%s with master, discarding "
+                        "bytes of bulk transfer until EOF marker...\n", info);
+    } else if (out_full_mode == 1 && payload != 0) {
+        /* SYNC without EOF marker or PSYNC +FULLRESYNC. */
+        fprintf(stderr, "%s with master, discarding %llu "
+                        "bytes of bulk transfer...\n", info, payload);
+    } else if (out_full_mode == 0 && payload == 0) {
+        /* PSYNC +CONTINUE (no RDB payload). */
+        fprintf(stderr, "%s with master...\n", info);
     }
 
-
     /* Discard the payload. */
     while(payload) {
         ssize_t nread;
 
         nread = readConn(context,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
         if (nread <= 0) {
-            fprintf(stderr,"Error reading RDB payload while SYNCing\n");
+            fprintf(stderr,"Error reading RDB payload while %sing\n", info);
             exit(1);
         }
         payload -= nread;
@@ -7780,12 +7832,12 @@ static void slaveMode(void) {
 
     if (usemark) {
         unsigned long long offset = ULLONG_MAX - payload;
-        fprintf(stderr,"SYNC done after %llu bytes. Logging commands from master.\n", offset);
+        fprintf(stderr,"%s done after %llu bytes. Logging commands from master.\n", info, offset);
         /* put the slave online */
         sleep(1);
         sendReplconf("ACK", "0");
     } else
-        fprintf(stderr,"SYNC done. Logging commands from master.\n");
+        fprintf(stderr,"%s done. Logging commands from master.\n", info);
 
     /* Now we can use hiredis to read the incoming protocol. */
     config.output = OUTPUT_CSV;
@@ -7814,7 +7866,7 @@ static void getRDB(clusterManagerNode *node) {
     static char eofmark[RDB_EOF_MARK_SIZE];
     static char lastbytes[RDB_EOF_MARK_SIZE];
     static int usemark = 0;
-    unsigned long long payload = sendSync(s, eofmark);
+    unsigned long long payload = sendSync(s, 1, eofmark, NULL);
     char buf[4096];
 
     if (payload == 0) {
@@ -9027,7 +9079,7 @@ int main(int argc, char **argv) {
         if (cliConnect(0) == REDIS_ERR) exit(1);
         sendCapa();
         sendReplconf("rdb-filter-only", "");
-        slaveMode();
+        slaveMode(1);
     }
 
     /* Get RDB/functions mode. */