Skip to content

Import-mode: Avoid expiration and eviction during data syncing#1185

Merged
zuiderkwast merged 14 commits into
valkey-io:unstablefrom
lyq2333:pseudo-master
Nov 19, 2024
Merged

Import-mode: Avoid expiration and eviction during data syncing#1185
zuiderkwast merged 14 commits into
valkey-io:unstablefrom
lyq2333:pseudo-master

Conversation

@lyq2333

@lyq2333 lyq2333 commented Oct 17, 2024

Copy link
Copy Markdown
Contributor

New config: import-mode (yes|no)

New command: CLIENT IMPORT-SOURCE (ON|OFF)

The config, when set to yes, disables eviction and deletion of expired keys, except for commands coming from a client which has marked itself as an import-source, the data source when importing data from another node, using the CLIENT IMPORT-SOURCE command.

When we sync data from the source Valkey to the destination Valkey using some sync tools like redis-shake, the destination Valkey can perform expiration and eviction, which may cause data corruption. This problem has been discussed in redis/redis#9760 (reply in thread) and Redis already have a solution. But in Valkey we haven't fixed it by now.

E.g. we call set key 1 ex 1 on the source server and transfer this command to the destination server. Then we call incr key on the source server before the key expired, we will have a key on the source server with a value of 2. But when the command arrived at the destination server, the key may be expired and has deleted. So we will have a key on the destination server with a value of 1, which is inconsistent with the source server.

In standalone mode, we can use writable replica to simplify the sync process. However, in cluster mode, we still need a sync tool to help us transfer the source data to the destination. The sync tool usually work as a normal client and the destination works as a primary which keep expiration and eviction.

In this PR, we add a new mode named 'import-mode'. In this mode, server stop expiration and eviction just like a replica. Notice that this mode exists only in sync state to avoid data inconsistency caused by expiration and eviction. Import mode only takes effect on the primary. Sync tools can mark their clients as an import source by CLIENT IMPORT-SOURCE, which work like a client from primary and can visit expired keys in lookupkey.

Notice: during the migration, other clients, apart from the import source, should not access the data imported by import source.

@codecov

codecov Bot commented Oct 17, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 89.47368% with 2 lines in your changes missing coverage. Please review.

Project coverage is 70.57%. Comparing base (a62d1f1) to head (7f04964).
Report is 62 commits behind head on unstable.

Files with missing lines Patch % Lines
src/networking.c 81.81% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #1185      +/-   ##
============================================
- Coverage     70.65%   70.57%   -0.09%     
============================================
  Files           114      115       +1     
  Lines         61799    63171    +1372     
============================================
+ Hits          43664    44582     +918     
- Misses        18135    18589     +454     
Files with missing lines Coverage Δ
src/commands.def 100.00% <ø> (ø)
src/config.c 78.83% <ø> (+0.13%) ⬆️
src/db.c 89.27% <100.00%> (+0.75%) ⬆️
src/evict.c 97.75% <100.00%> (+0.02%) ⬆️
src/expire.c 96.57% <100.00%> (+0.14%) ⬆️
src/server.c 87.63% <100.00%> (-1.01%) ⬇️
src/server.h 100.00% <ø> (ø)
src/networking.c 88.50% <81.81%> (+<0.01%) ⬆️

... and 87 files with indirect coverage changes

---- 🚨 Try these New Features:

@soloestoy

Copy link
Copy Markdown
Member

good work! @valkey-io/core-team please take a look

@lyq2333 lyq2333 requested a review from a team October 17, 2024 11:34

@zuiderkwast zuiderkwast left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it. I think it's a little bit strange, but with some better documentation it's not too strange.

We need to have good support for migration tools, especially for users who want to migrate from proprietary software to open source software. 😄

Redis already have a solution.

I remember this discussion from the Redis times. Do you know what Redis solution is (the same REPLCONF PSEUDO-MASTER?) or is it secret?

Any better idea?

Have you considered the idea to let RediShake act as a primary and let the target database replicate from RediShake? It can act as a replication proxy?

+-----------+   PSYNC  +-----------+   PSYNC  +--------+
| Source DB |<---------| RediShake |<---------| Valkey |
+-----------+          +-----------+          +--------+

Comment thread src/replication.c Outdated
Comment thread valkey.conf Outdated
@hwware hwware added the major-decision-pending Major decision pending by TSC team label Oct 17, 2024
@soloestoy

Copy link
Copy Markdown
Member
+-----------+   PSYNC  +-----------+   PSYNC  +--------+
| Source DB |<---------| RediShake |<---------| Valkey |
+-----------+          +-----------+          +--------+

interesting, and we have also considered such a method, but there are several issues.

First, as a cloud provider, we do not allow an instance to become a replica for external instances. This is a very risky operation, especially since the primary connection uses a super user, which has excessive permissions. Additionally, the replica needs to establish an outbound connection, this is also not allowed. These restrictions, I believe, are not unique to cloud providers, many users' security control policies also prohibit such actions.

Another point is that, in a cluster mode, the source and target instances for migration typically have different slot distributions, and redisShake can help with correctly routing the data.

@soloestoy soloestoy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover, I think we should only allow write commands for pseudo-master client when server is in pseudo-replica mode.

Comment thread src/evict.c Outdated
Comment thread src/replication.c Outdated
@lyq2333

lyq2333 commented Oct 18, 2024

Copy link
Copy Markdown
Contributor Author

Redis already have a solution.

I remember this discussion from the Redis times. Do you know what Redis solution is (the same REPLCONF PSEUDO-MASTER?) or is it secret?

It's secret. I used to push this PR to Redis Community(#13077) and they say they will PR their implementation. But no news after that.

Any better idea?

Have you considered the idea to let RediShake act as a primary and let the target database replicate from RediShake? It can act as a replication proxy?

+-----------+   PSYNC  +-----------+   PSYNC  +--------+
| Source DB |<---------| RediShake |<---------| Valkey |
+-----------+          +-----------+          +--------+

What @soloestoy said is a major limitation for us. BTW, sometimes the destination already has some data, PSYNC will delete them.

@enjoy-binbin

Copy link
Copy Markdown
Member

i did not read it carefully, internally we simply pause the expiration on both side i guess.
Redis has an issue that seems to be discussing this: redis/redis#13478

@lyq2333

lyq2333 commented Oct 18, 2024

Copy link
Copy Markdown
Contributor Author

i did not read it carefully, internally we simply pause the expiration on both side i guess. Redis has an issue that seems to be discussing this: redis/redis#13478

Interesting. I think this PR can solve that problem if the server has no other read. If the server has other read, one possible solution is to disable all expiration in lookupKey for all clients. But I don't want this PR to affect read/write from normal client.

Comment thread src/networking.c Outdated
Comment thread src/server.h Outdated
Comment thread valkey.conf Outdated
Comment thread valkey.conf Outdated
@zuiderkwast

Copy link
Copy Markdown
Contributor

@lyq2333 Can you commit the changes to commands.def?

When you run make locally and you have python3 installed, make updates commands.def if there are any new or changed commands.

Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
…mand

Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
@lyq2333

lyq2333 commented Oct 21, 2024

Copy link
Copy Markdown
Contributor Author

@lyq2333 Can you commit the changes to commands.def?

When you run make locally and you have python3 installed, make updates commands.def if there are any new or changed commands.

@zuiderkwast Thanks. Sorry forgot it before. I also forgot to sign off and have to force push. No code is modified.

@madolson

Copy link
Copy Markdown
Member

Weekly core meeting. No specific comments other than we should review this offline and make progress. Directionally seems like a good idea.

@lyq2333

lyq2333 commented Oct 22, 2024

Copy link
Copy Markdown
Contributor Author

The scenario we encountered is that some users want to migrate data from Server A to Server B. Both A and B work as primary and B may have some data before the migration. We find expiration and eviction may lead to data inconsistency. So we came up with a simple method, but it still has a few places to discuss.

We plan to introduce a new config(import-mode in this PR) to mark that this server is importing data. As for expiration, in import mode, active expiration in cron is prohibited and passive expiration in commands is limited depend on the client state. Clients marked as import source work like server.primary, which will not trigger passive expiration for read and write. But there are many ways to handle other normal clients. We think of some ways as follow.

  1. Normal clients work as usual, which means they can perform passive expiration whether for read or write. The drawback is that normal clients accessing the migrating data will also trigger passive expiration and may affect the migration.
  2. Normal clients are prohibited from write commands, meanwhile don't trigger passive expiration when read and don't read expired keys. The server works like a read-only replica. The drawback is that clients need to stop writing during the data migration and may affect users business.
  3. Normal clients don't trigger passive expiration when read and don't read expired keys, but still trigger passive expiration when write. We can't prohibit passive expiration in write commands, because server will crash when we call incr/decr commands on expired keys. The reason is that these commands must delete expired keys first, otherwise it will hit the assert in dbAdd. The drawback is that normal clients calling write commands on the migrating data will trigger passive expiration. This is a trade-off between the above two ways and I think it's better because it won't affect the normal clients and have less impact on the migration process.

As for eviction, should server disable eviction automatically to ensure data consistency in import mode?Or let users choose the maxmeory-policy, I guess no one will choose an option other than noeviction.

@valkey-io/core-team WDYT? Any suggestions would be greatly appreciated.

@soloestoy

Copy link
Copy Markdown
Member

The scenario we encountered is that some users want to migrate data from Server A to Server B. Both A and B work as primary and B may have some data before the migration. We find expiration and eviction may lead to data inconsistency. So we came up with a simple method, but it still has a few places to discuss.

We plan to introduce a new config(import-mode in this PR) to mark that this server is importing data. As for expiration, in import mode, active expiration in cron is prohibited and passive expiration in commands is limited depend on the client state. Clients marked as import source work like server.primary, which will not trigger passive expiration for read and write. But there are many ways to handle other normal clients. We think of some ways as follow.

  1. Normal clients work as usual, which means they can perform passive expiration whether for read or write. The drawback is that normal clients accessing the migrating data will also trigger passive expiration and may affect the migration.
  2. Normal clients are prohibited from write commands, meanwhile don't trigger passive expiration when read and don't read expired keys. The server works like a read-only replica. The drawback is that clients need to stop writing during the data migration and may affect users business.
  3. Normal clients don't trigger passive expiration when read and don't read expired keys, but still trigger passive expiration when write. We can't prohibit passive expiration in write commands, because server will crash when we call incr/decr commands on expired keys. The reason is that these commands must delete expired keys first, otherwise it will hit the assert in dbAdd. The drawback is that normal clients calling write commands on the migrating data will trigger passive expiration. This is a trade-off between the above two ways and I think it's better because it won't affect the normal clients and have less impact on the migration process.

As for eviction, should server disable eviction automatically to ensure data consistency in import mode?Or let users choose the maxmeory-policy, I guess no one will choose an option other than noeviction.

@valkey-io/core-team WDYT? Any suggestions would be greatly appreciated.

The core issue is that for the target node of data migration that is active, we need to provide normal read and write services. However, we don't want to affect the data being migrated and can't identify which data is being migrated.

Method 3 seems to be a relatively suitable option, but of course, it requires users to ensure they do not perform write operations on the data being migrated.

@zuiderkwast

Copy link
Copy Markdown
Contributor

So just to circle back and clarify this: the client which migrates the data will NOT automatically granted with superuser permissions but wil have to count on it's authenticated use to support all needed permissions?

That's right. It would be good to mention this in the docs.

It wouldn't be very secure if a client could get superuser permissions just by sending the command CLIENT IMPORT-SOURCE ON.

@ranshid ranshid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zuiderkwast zuiderkwast changed the title Avoid expiration and eviction during data syncing Import-mode: Avoid expiration and eviction during data syncing Nov 19, 2024
@zuiderkwast zuiderkwast merged commit 4986310 into valkey-io:unstable Nov 19, 2024
@zuiderkwast

Copy link
Copy Markdown
Contributor

Looking forward to a doc PR for

  • commands/client-import-source.md
  • topics/migration.md

In the migration guide, I hope we can have some guide about how to migrate data with import-mode, for example using redishake.

@lyq2333

lyq2333 commented Nov 20, 2024

Copy link
Copy Markdown
Contributor Author

Looking forward to a doc PR for

  • commands/client-import-source.md
  • topics/migration.md

In the migration guide, I hope we can have some guide about how to migrate data with import-mode, for example using redishake.

@suxb201 hi, I think this pr is helpful for RedisShake. PTAL.

@hpatro hpatro left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my 2 cents: easy to introduce bug in the future where a dev can forget to check the import mode flag. It would be nice to introduce a macro for serverInImportMode / serverInDataServingMode and use it everywhere instead of checking if it's primary and in/not-in import mode independently.

Comment thread src/networking.c
Comment thread tests/unit/expire.tcl
Comment thread src/db.c
Comment thread src/server.c
enjoy-binbin added a commit to enjoy-binbin/valkey that referenced this pull request Nov 20, 2024
After valkey-io#1185, a client in import-source state can visit expired
key both in read commands and write commands, this commit handle
keyIsExpired function to handle import-source state as well, so
KEYS can visit the expired key.

This is not particularly important, but it ensures the definition,
also doing some cleanup around the test, verified that the client
can indeed visit the expired key.

Signed-off-by: Binbin <binloveplay1314@qq.com>
@zuiderkwast

Copy link
Copy Markdown
Contributor

@hpatro @enjoy-binbin Sorry for merging too fast. I didn't know you were still reviewing and testing this.

@enjoy-binbin

Copy link
Copy Markdown
Member

Sorry for merging too fast. I didn't know you were still reviewing and testing this.

no worry. i mute the notification at the very first beginning, and only review the code today (review and play with it while dealing the conflicts), the code LGTM overall.

enjoy-binbin added a commit that referenced this pull request Nov 27, 2024
After #1185, a client in import-source state can visit expired key
both in read commands and write commands, this commit handle
keyIsExpired function to handle import-source state as well, so
KEYS can visit the expired key.

This is not particularly important, but it ensures the definition,
also doing some cleanup around the test, verified that the client
can indeed visit the expired key.

Signed-off-by: Binbin <binloveplay1314@qq.com>
zuiderkwast added a commit that referenced this pull request Dec 10, 2024
- Add new flag "I" in `CLIENT LIST` for import-source client
- Add `DEBUG_CONFIG` for import-mode
- Allow import-source status to be turned off when import-mode is off

Fixes #1350 and
#1185 (comment).

---------

Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: Yanqi Lv <lvyanqi.lyq@alibaba-inc.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Binbin <binloveplay1314@qq.com>
vudiep411 pushed a commit to Autxmaton/valkey that referenced this pull request Dec 15, 2024
- Add new flag "I" in `CLIENT LIST` for import-source client
- Add `DEBUG_CONFIG` for import-mode
- Allow import-source status to be turned off when import-mode is off

Fixes valkey-io#1350 and
valkey-io#1185 (comment).

---------

Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: Yanqi Lv <lvyanqi.lyq@alibaba-inc.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Binbin <binloveplay1314@qq.com>
zuiderkwast added a commit to valkey-io/valkey-doc that referenced this pull request Jan 2, 2025
This PR is for valkey-io/valkey#1185 and
valkey-io/valkey#1398.

---------

Signed-off-by: lvyanqi.lyq <lvyanqi.lyq@alibaba-inc.com>
Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
soloestoy pushed a commit that referenced this pull request Dec 26, 2025
When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) { 
     long long t = objectGetExpire(val); 
     serverAssert(t >= 0); 
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in #1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
jdheyburn pushed a commit to jdheyburn/valkey that referenced this pull request Jan 8, 2026
…ey-io#2944)

When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) { 
     long long t = objectGetExpire(val); 
     serverAssert(t >= 0); 
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in valkey-io#1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
zuiderkwast pushed a commit to zuiderkwast/valkey that referenced this pull request Jan 29, 2026
…ey-io#2944)

When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) { 
     long long t = objectGetExpire(val); 
     serverAssert(t >= 0); 
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in valkey-io#1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
roshkhatri pushed a commit to roshkhatri/valkey that referenced this pull request Jan 29, 2026
…ey-io#2944)

When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) { 
     long long t = objectGetExpire(val); 
     serverAssert(t >= 0); 
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in valkey-io#1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
roshkhatri pushed a commit to roshkhatri/valkey that referenced this pull request Jan 29, 2026
…ey-io#2944)

When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) {
     long long t = objectGetExpire(val);
     serverAssert(t >= 0);
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in valkey-io#1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
Signed-off-by: Roshan Khatri <rvkhatri@amazon.com>
roshkhatri pushed a commit to roshkhatri/valkey that referenced this pull request Jan 29, 2026
…ey-io#2944)

When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) {
     long long t = objectGetExpire(val);
     serverAssert(t >= 0);
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in valkey-io#1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
Signed-off-by: Roshan Khatri <rvkhatri@amazon.com>
zuiderkwast pushed a commit to zuiderkwast/valkey that referenced this pull request Jan 30, 2026
…ey-io#2944)

When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) { 
     long long t = objectGetExpire(val); 
     serverAssert(t >= 0); 
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in valkey-io#1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
zuiderkwast pushed a commit that referenced this pull request Feb 3, 2026
When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) { 
     long long t = objectGetExpire(val); 
     serverAssert(t >= 0); 
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in #1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
madolson pushed a commit that referenced this pull request Feb 24, 2026
When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) {
     long long t = objectGetExpire(val);
     serverAssert(t >= 0);
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in #1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
Signed-off-by: Roshan Khatri <rvkhatri@amazon.com>
hpatro pushed a commit to hpatro/valkey that referenced this pull request Mar 5, 2026
…ey-io#2944)

When import-mode is yes, we might be able to set an expired TTL. At the
same time,
commands like EXPIREAT/EXPIRE do not restrict TTL from being negative.
After we
set import-mode to no, server will crash at:
```
 int activeExpireCycleTryExpire(serverDb *db, robj *val, long long now, int didx) {
     long long t = objectGetExpire(val);
     serverAssert(t >= 0);
```

In this case, we restrict ttl from being negative in
expireGenericCommand, we simply
change the expiration time to 0 to mark the key as expired since in
import-mode, the
import-source client can always read the expired keys anyway.

import-mode was introduced in valkey-io#1185

---------

Signed-off-by: cjx-zar <jxchenczar@foxmail.com>
Signed-off-by: Harkrishn Patro <bunty.hari@gmail.com>
enjoy-binbin added a commit to enjoy-binbin/redis that referenced this pull request May 25, 2026
…y#1185)

See valkey-io/valkey#1185 and related PRs for
more details. Cherry-pick from Valkey.

New config: `import-mode (yes|no)`

New command: `CLIENT IMPORT-SOURCE (ON|OFF)`

The config, when set to `yes`, disables eviction and deletion of expired
keys, except for commands coming from a client which has marked itself
as an import-source, the data source when importing data from another
node, using the CLIENT IMPORT-SOURCE command.

When we sync data from the source Valkey to the destination Valkey using
some sync tools like
[redis-shake](https://github.com/tair-opensource/RedisShake), the
destination Valkey can perform expiration and eviction, which may cause
data corruption. This problem has been discussed in
redis#9760 (reply in thread)
and Redis already have a solution. But in Valkey we haven't fixed it by
now.

E.g. we call `set key 1 ex 1` on the source server and transfer this
command to the destination server. Then we call `incr key` on the source
server before the key expired, we will have a key on the source server
with a value of 2. But when the command arrived at the destination
server, the key may be expired and has deleted. So we will have a key on
the destination server with a value of 1, which is inconsistent with the
source server.

In standalone mode, we can use writable replica to simplify the sync
process. However, in cluster mode, we still need a sync tool to help us
transfer the source data to the destination. The sync tool usually work
as a normal client and the destination works as a primary which keep
expiration and eviction.

In this PR, we add a new mode named 'import-mode'. In this mode, server
stop expiration and eviction just like a replica. Notice that this mode
exists only in sync state to avoid data inconsistency caused by
expiration and eviction. Import mode only takes effect on the primary.
Sync tools can mark their clients as an import source by `CLIENT
IMPORT-SOURCE`, which work like a client from primary and can visit
expired keys in `lookupkey`.

**Notice: during the migration, other clients, apart from the import
source, should not access the data imported by import source.**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major-decision-approved Major decision approved by TSC team needs-doc-pr This change needs to update a documentation page. Remove label once doc PR is open. release-notes This issue should get a line item in the release notes

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

9 participants