├── requirements.txt ├── .gitignore ├── LICENSE ├── .github └── workflows │ ├── unstable.yaml │ ├── 5.0.yaml │ ├── 6.0.yaml │ ├── 6.2.yaml │ ├── 7.0.yaml │ ├── 7.2.yaml │ ├── 4.0.yaml │ └── tf.yaml ├── config.yaml ├── README-CN.md ├── conn.py ├── README.md ├── Terraform └── Aliyun │ └── main.tf ├── resp_compatibility.py ├── compatibility_report_zh_CN.md ├── compatibility_report_en_US.md └── cts_refer.md /requirements.txt: -------------------------------------------------------------------------------- 1 | redis>=4.3.4 2 | pyyaml>=6.0 3 | dataclasses -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | test.json 3 | venv 4 | html 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 tair-opensource 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/unstable.yaml: -------------------------------------------------------------------------------- 1 | name: unstable 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-with-redis-unstable: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: clone and build redis 15 | run: | 16 | sudo apt-get install git 17 | git clone https://github.com/redis/redis 18 | cd redis 19 | make -j 20 | 21 | - name: start redis 22 | run: | 23 | ./redis/src/redis-server & 24 | 25 | - name: start redis cluster 26 | run: | 27 | cd redis/utils/create-cluster 28 | ./create-cluster start 29 | echo yes|./create-cluster create 30 | 31 | - name: set up python 32 | uses: actions/setup-python@v4 33 | with: 34 | python-version: "3.10" 35 | 36 | - name: install requirements 37 | run: pip install -r requirements.txt 38 | 39 | - name: run test 40 | run: python resp_compatibility.py --testfile cts.json --show-failed > test.result 41 | 42 | - name: check fail tests 43 | run: | 44 | cat test.result 45 | grep -q "This is failed tests for" test.result && exit -1 || exit 0 46 | 47 | - name: run cluster test 48 | run: python resp_compatibility.py --testfile cts.json --port 30001 --cluster --show-failed > cluster.result 49 | 50 | - name: check cluster fail tests 51 | run: | 52 | cat cluster.result 53 | grep -q "This is failed tests for" cluster.result && exit -1 || exit 0 -------------------------------------------------------------------------------- /.github/workflows/5.0.yaml: -------------------------------------------------------------------------------- 1 | name: 5.0 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-with-redis-5-0: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: clone and build redis 15 | run: | 16 | sudo apt-get install git 17 | git clone https://github.com/redis/redis 18 | cd redis 19 | git checkout 5.0 20 | make -j 21 | 22 | - name: start redis 23 | run: | 24 | ./redis/src/redis-server & 25 | 26 | - name: start redis cluster 27 | run: | 28 | cd redis/utils/create-cluster 29 | ./create-cluster start 30 | echo yes|./create-cluster create 31 | 32 | - name: set up python 33 | uses: actions/setup-python@v4 34 | with: 35 | python-version: "3.10" 36 | 37 | - name: install requirements 38 | run: pip install -r requirements.txt 39 | 40 | - name: run test 41 | run: python resp_compatibility.py --testfile cts.json --specific-version 5.0.0 --show-failed > test.result 42 | 43 | - name: check fail tests 44 | run: | 45 | cat test.result 46 | grep -q "This is failed tests for" test.result && exit -1 || exit 0 47 | 48 | - name: run cluster test 49 | run: python resp_compatibility.py --testfile cts.json --port 30001 --specific-version 5.0.0 --cluster --show-failed > cluster.result 50 | 51 | - name: check cluster fail tests 52 | run: | 53 | cat cluster.result 54 | grep -q "This is failed tests for" cluster.result && exit -1 || exit 0 -------------------------------------------------------------------------------- /.github/workflows/6.0.yaml: -------------------------------------------------------------------------------- 1 | name: 6.0 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-with-redis-6-0: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: clone and build redis 15 | run: | 16 | sudo apt-get install git 17 | git clone https://github.com/redis/redis 18 | cd redis 19 | git checkout 6.0 20 | make -j 21 | 22 | - name: start redis 23 | run: | 24 | ./redis/src/redis-server & 25 | 26 | - name: start redis cluster 27 | run: | 28 | cd redis/utils/create-cluster 29 | ./create-cluster start 30 | echo yes|./create-cluster create 31 | 32 | - name: set up python 33 | uses: actions/setup-python@v4 34 | with: 35 | python-version: "3.10" 36 | 37 | - name: install requirements 38 | run: pip install -r requirements.txt 39 | 40 | - name: run test 41 | run: python resp_compatibility.py --testfile cts.json --specific-version 6.0.0 --show-failed > test.result 42 | 43 | - name: check fail tests 44 | run: | 45 | cat test.result 46 | grep -q "This is failed tests for" test.result && exit -1 || exit 0 47 | 48 | - name: run cluster test 49 | run: python resp_compatibility.py --testfile cts.json --port 30001 --specific-version 6.0.0 --cluster --show-failed > cluster.result 50 | 51 | - name: check cluster fail tests 52 | run: | 53 | cat cluster.result 54 | grep -q "This is failed tests for" cluster.result && exit -1 || exit 0 -------------------------------------------------------------------------------- /.github/workflows/6.2.yaml: -------------------------------------------------------------------------------- 1 | name: 6.2 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-with-redis-6-2: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: clone and build redis 15 | run: | 16 | sudo apt-get install git 17 | git clone https://github.com/redis/redis 18 | cd redis 19 | git checkout 6.2 20 | make -j 21 | 22 | - name: start redis 23 | run: | 24 | ./redis/src/redis-server & 25 | 26 | - name: start redis cluster 27 | run: | 28 | cd redis/utils/create-cluster 29 | ./create-cluster start 30 | echo yes|./create-cluster create 31 | 32 | - name: set up python 33 | uses: actions/setup-python@v4 34 | with: 35 | python-version: "3.10" 36 | 37 | - name: install requirements 38 | run: pip install -r requirements.txt 39 | 40 | - name: run test 41 | run: python resp_compatibility.py --testfile cts.json --specific-version 6.2.0 --show-failed > test.result 42 | 43 | - name: check fail tests 44 | run: | 45 | cat test.result 46 | grep -q "This is failed tests for" test.result && exit -1 || exit 0 47 | 48 | - name: run cluster test 49 | run: python resp_compatibility.py --testfile cts.json --port 30001 --specific-version 6.2.0 --cluster --show-failed > cluster.result 50 | 51 | - name: check cluster fail tests 52 | run: | 53 | cat cluster.result 54 | grep -q "This is failed tests for" cluster.result && exit -1 || exit 0 -------------------------------------------------------------------------------- /.github/workflows/7.0.yaml: -------------------------------------------------------------------------------- 1 | name: 7.0 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-with-redis-7-0: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: clone and build redis 15 | run: | 16 | sudo apt-get install git 17 | git clone https://github.com/redis/redis 18 | cd redis 19 | git checkout 7.0 20 | make -j 21 | 22 | - name: start redis 23 | run: | 24 | ./redis/src/redis-server & 25 | 26 | - name: start redis cluster 27 | run: | 28 | cd redis/utils/create-cluster 29 | ./create-cluster start 30 | echo yes|./create-cluster create 31 | 32 | - name: set up python 33 | uses: actions/setup-python@v4 34 | with: 35 | python-version: "3.10" 36 | 37 | - name: install requirements 38 | run: pip install -r requirements.txt 39 | 40 | - name: run test 41 | run: python resp_compatibility.py --testfile cts.json --specific-version 7.0.0 --show-failed > test.result 42 | 43 | - name: check fail tests 44 | run: | 45 | cat test.result 46 | grep -q "This is failed tests for" test.result && exit -1 || exit 0 47 | 48 | - name: run cluster test 49 | run: python resp_compatibility.py --testfile cts.json --port 30001 --specific-version 7.0.0 --cluster --show-failed > cluster.result 50 | 51 | - name: check cluster fail tests 52 | run: | 53 | cat cluster.result 54 | grep -q "This is failed tests for" cluster.result && exit -1 || exit 0 -------------------------------------------------------------------------------- /.github/workflows/7.2.yaml: -------------------------------------------------------------------------------- 1 | name: 7.2 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-with-redis-7-2: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: clone and build redis 15 | run: | 16 | sudo apt-get install git 17 | git clone https://github.com/redis/redis 18 | cd redis 19 | git checkout 7.2 20 | make -j 21 | 22 | - name: start redis 23 | run: | 24 | ./redis/src/redis-server & 25 | 26 | - name: start redis cluster 27 | run: | 28 | cd redis/utils/create-cluster 29 | ./create-cluster start 30 | echo yes|./create-cluster create 31 | 32 | - name: set up python 33 | uses: actions/setup-python@v4 34 | with: 35 | python-version: "3.10" 36 | 37 | - name: install requirements 38 | run: pip install -r requirements.txt 39 | 40 | - name: run test 41 | run: python resp_compatibility.py --testfile cts.json --specific-version 7.2.0 --show-failed > test.result 42 | 43 | - name: check fail tests 44 | run: | 45 | cat test.result 46 | grep -q "This is failed tests for" test.result && exit -1 || exit 0 47 | 48 | - name: run cluster test 49 | run: python resp_compatibility.py --testfile cts.json --port 30001 --specific-version 7.2.0 --cluster --show-failed > cluster.result 50 | 51 | - name: check cluster fail tests 52 | run: | 53 | cat cluster.result 54 | grep -q "This is failed tests for" cluster.result && exit -1 || exit 0 -------------------------------------------------------------------------------- /.github/workflows/4.0.yaml: -------------------------------------------------------------------------------- 1 | name: 4.0 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-with-redis-4-0: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: clone and build redis 15 | run: | 16 | sudo apt-get install git ruby 17 | git clone https://github.com/redis/redis 18 | cd redis 19 | git checkout 4.0 20 | make -j 21 | 22 | - name: start redis 23 | run: | 24 | ./redis/src/redis-server & 25 | 26 | - name: start redis cluster 27 | run: | 28 | sudo gem install redis 29 | cd redis/utils/create-cluster 30 | ./create-cluster start 31 | echo yes|./create-cluster create 32 | 33 | - name: set up python 34 | uses: actions/setup-python@v4 35 | with: 36 | python-version: "3.10" 37 | 38 | - name: install requirements 39 | run: pip install -r requirements.txt 40 | 41 | - name: run test 42 | run: python resp_compatibility.py --testfile cts.json --specific-version 4.0.0 --show-failed > test.result 43 | 44 | - name: check fail tests 45 | run: | 46 | cat test.result 47 | grep -q "This is failed tests for" test.result && exit -1 || exit 0 48 | 49 | - name: run cluster test 50 | run: python resp_compatibility.py --testfile cts.json --port 30001 --specific-version 4.0.0 --cluster --show-failed > cluster.result 51 | 52 | - name: check cluster fail tests 53 | run: | 54 | cat cluster.result 55 | grep -q "This is failed tests for" cluster.result && exit -1 || exit 0 56 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # config.yaml is used when you need to test the compatibility of multiple Redis-Like 2 | # systems and generate html reports (--genhtml) 3 | 4 | # html report directory 5 | Dir: "html" 6 | 7 | # specify test version 8 | SpecificVersion: 9 | - 4.0.0 10 | - 5.0.0 11 | - 6.0.0 12 | - 7.0.0 13 | 14 | # the host, port, password of all the db to be tested 15 | Database: 16 | Redis: 17 | host: 127.0.0.1 18 | port: 6379 19 | password: 20 | ssl: false 21 | cluster: false 22 | version: latest 23 | 24 | DragonflyDB: 25 | host: 127.0.0.1 26 | port: 6380 27 | password: 28 | ssl: false 29 | cluster: false 30 | version: latest 31 | 32 | Kvrocks: 33 | host: 127.0.0.1 34 | port: 6381 35 | password: 36 | ssl: false 37 | cluster: false 38 | version: latest 39 | 40 | KeyDB: 41 | host: 127.0.0.1 42 | port: 6382 43 | password: 44 | ssl: false 45 | cluster: false 46 | version: latest 47 | 48 | Pika: 49 | host: 127.0.0.1 50 | port: 6383 51 | password: 52 | ssl: false 53 | cluster: false 54 | version: latest 55 | 56 | Valkey: 57 | host: 127.0.0.1 58 | port: 6384 59 | password: 60 | ssl: false 61 | cluster: false 62 | version: latest 63 | 64 | RedisCluster: 65 | host: 127.0.0.1 66 | port: 30001 67 | password: 68 | ssl: false 69 | cluster: true 70 | version: latest 71 | 72 | AliyunRedis: 73 | host: 74 | port: 75 | password: 76 | ssl: 77 | cluster: false 78 | version: 79 | 80 | AliyunRedisCluster: 81 | host: 82 | port: 83 | password: 84 | ssl: 85 | cluster: true 86 | version: 87 | 88 | AliyunTair: 89 | host: 90 | port: 91 | password: 92 | ssl: 93 | cluster: false 94 | version: 95 | 96 | AliyunTairCluster: 97 | host: 98 | port: 99 | password: 100 | ssl: 101 | cluster: true 102 | version: 103 | 104 | ElastiCache: 105 | host: 106 | port: 107 | password: 108 | ssl: 109 | cluster: false 110 | version: 111 | 112 | MemoryDB: 113 | host: 114 | port: 115 | password: 116 | ssl: 117 | cluster: false 118 | version: 119 | 120 | AzureRedis: 121 | host: 122 | port: 123 | password: 124 | ssl: 125 | cluster: false 126 | version: 127 | 128 | GoogleMemoryStore: 129 | host: 130 | port: 131 | password: 132 | ssl: 133 | cluster: false 134 | version: 135 | 136 | TencentRedis: 137 | host: 138 | port: 139 | password: 140 | ssl: 141 | cluster: false 142 | version: 143 | 144 | GaussDBRedis: 145 | host: 146 | port: 147 | password: 148 | ssl: 149 | cluster: false 150 | version: 151 | -------------------------------------------------------------------------------- /.github/workflows/tf.yaml: -------------------------------------------------------------------------------- 1 | name: Terraform in Daily Test 2 | 3 | on: 4 | workflow_dispatch: #允许手动运行 5 | schedule: 6 | - cron: '0 18 * * *' # 每天 UTC 时间 18:00,即中国时间凌晨 2 点 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Check GitHub Secrets 14 | run: | 15 | if [ -z "${{ secrets.MYAPP_GITHUB_TOKEN }}" ]; then 16 | echo "GitHub Token is missing" 17 | else 18 | echo "GitHub Token is set" 19 | fi 20 | 21 | if [ -z "${{ secrets.MYAPP_USER_EMAIL }}" ]; then 22 | echo "User Email is missing" 23 | else 24 | echo "User Email is set" 25 | fi 26 | 27 | if [ -z "${{ secrets.MYAPP_USER_NAME }}" ]; then 28 | echo "User Name is missing" 29 | else 30 | echo "User Name is set" 31 | fi 32 | 33 | - name: Checkout repository 34 | uses: actions/checkout@v2 35 | 36 | - name: Setup Terraform 37 | uses: hashicorp/setup-terraform@v2 38 | with: 39 | terraform_version: 1.9.4 40 | 41 | - name: Initialize Terraform 42 | env: 43 | TF_VAR_access_key: ${{ secrets.ALI_ACCESS_KEY }} 44 | TF_VAR_secret_key: ${{ secrets.ALI_SECRET_KEY }} 45 | TF_VAR_github_token: ${{ secrets.MYAPP_GITHUB_TOKEN }} 46 | TF_VAR_user_name: ${{ secrets.MYAPP_USER_NAME }} 47 | TF_VAR_user_email: ${{ secrets.MYAPP_USER_EMAIL }} 48 | run: terraform -chdir=./Terraform/Aliyun init 49 | 50 | - name: Apply Terraform Configuration 51 | env: 52 | TF_VAR_access_key: ${{ secrets.ALI_ACCESS_KEY }} 53 | TF_VAR_secret_key: ${{ secrets.ALI_SECRET_KEY }} 54 | TF_VAR_github_token: ${{ secrets.MYAPP_GITHUB_TOKEN }} 55 | TF_VAR_user_name: ${{ secrets.MYAPP_USER_NAME }} 56 | TF_VAR_user_email: ${{ secrets.MYAPP_USER_EMAIL }} 57 | run: terraform -chdir=./Terraform/Aliyun apply -auto-approve 58 | 59 | - name: Print current directory and file list 60 | run: | 61 | cd ./Terraform/Aliyun 62 | echo "Current directory:" 63 | pwd 64 | echo "Files in the current directory:" 65 | ls 66 | 67 | - name: Wait for 20 minutes before destroying resources 68 | run: sleep 1200 # 等待 20 分钟 69 | 70 | - name: Destroy Terraform Configuration 71 | env: 72 | TF_VAR_access_key: ${{ secrets.ALI_ACCESS_KEY }} 73 | TF_VAR_secret_key: ${{ secrets.ALI_SECRET_KEY }} 74 | TF_VAR_github_token: ${{ secrets.MYAPP_GITHUB_TOKEN }} 75 | TF_VAR_user_name: ${{ secrets.MYAPP_USER_NAME }} 76 | TF_VAR_user_email: ${{ secrets.MYAPP_USER_EMAIL }} 77 | run: terraform -chdir=./Terraform/Aliyun destroy -auto-approve 78 | 79 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # resp-compatibility 2 | 3 | ` resp-compatibility`是一个用来检测你的`Redis-Like`系统兼容到开源`Redis`哪个版本的工具(6.0还是7.0等)。 4 | 5 | # 安装 6 | 7 | 要求`Python 3.7`及以上版本。 8 | ``` 9 | pip3 install -r requirements.txt 10 | ``` 11 | 12 | # 可测试命令 13 | 14 | CTS 可以测试的命令及其对应的版本信息可参考[此表](cts_refer.md) 15 | 16 | # 如何使用 17 | 18 | 一些命令行支持的参数如下所示: 19 | 20 | ``` 21 | optional arguments: 22 | -h, --help show this help message and exit 23 | --host HOST the redis host 24 | --port PORT the redis port 25 | --password PASSWORD the redis password 26 | --testfile TESTFILE the redis compatibility test cases 27 | --specific-version {1.0.0,2.8.0,3.2.0,4.0.0,5.0.0,6.0.0,6.2.0,7.0.0,7.2.0} 28 | the redis version 29 | --show-failed show details of failed tests 30 | --cluster server is a node of the Redis cluster 31 | --ssl open ssl connection 32 | --genhtml generate test report in html format 33 | ``` 34 | 例如,测试 host:port 对应的服务是否兼容 Redis 6.2.0 版本并显示失败的测试。 35 | ``` 36 | $ python3 resp_compatibility.py -h host -p port --testfile cts.json --specific-version 6.2.0 --show-failed 37 | Connecting to 127.0.0.1:6379 use standalone client 38 | test: del command passed 39 | test: unlink command passed 40 | ... 41 | test: xtrim command with MINID/LIMIT passed 42 | -------- The result of tests -------- 43 | Summary: version: 6.2.0, total tests: 285, passed: 285, rate: 100.00% 44 | ``` 45 | 更多的示例请参考 `python3 resp_compatibility.py -h`。 46 | 47 | ## cluster 48 | Redis 在 API 层面有两种模式,一个是`Standalone`(Sentinel 模式的 API 兼容性和 Standalone 是一样的),一个是`Cluster`。命令在`Standalone`模式下没有跨 Slot 的限制,但是在集群模式下,要求多 key 的命令(例如 mset/mget命令)必须在同一 Slot 中。因此,我们支持了`--cluster`这个选项来测试系统对于 `Redis Cluster` 模式的兼容性,用法如下: 49 | ``` 50 | $ python3.9 resp_compatibility.py --testfile cts.json --host 127.0.0.1 --port 30001 --cluster --specific-version 6.2.0 51 | connecting to 127.0.0.1:30001 use cluster client 52 | test: del command passed 53 | test: unlink command passed 54 | ... 55 | test: xtrim command with MINID/LIMIT passed 56 | -------- The result of tests -------- 57 | Summary: version: 6.2.0, total tests: 260, passed: 260, rate: 100.00% 58 | ``` 59 | 60 | ## genhtml 61 | 您可以使用 `--genhtml` 选项来生成和此[网站](https://tair-opensource.github.io/resp-compatibility/)相同的 html 报告。 请注意,当使用此选项时候,将会读取 [config.yaml](config.yaml) 文件中的配置进行测试,此时命令行中的 `specific-version` 将会被文件中的覆盖。 62 | ``` 63 | $ python3.9 resp_compatibility.py --testfile cts.json --genhtml --show-failed 64 | directory html already exists, will be deleted and renewed. 65 | start test Redis for version 4.0.0 66 | connecting to 127.0.0.1:6379 using standalone client 67 | start test Redis for version 5.0.0 68 | connecting to 127.0.0.1:6379 using standalone client 69 | start test Redis for version 6.0.0 70 | connecting to 127.0.0.1:6379 using standalone client 71 | start test Redis for version 7.0.0 72 | connecting to 127.0.0.1:6379 using standalone client 73 | ... 74 | Visit http://localhost:8000 for the report. 75 | ``` 76 | 结束之后,一个 HTTP 服务器将会被启动在 http://localhost:8000 ,您可以访问此网站获得 html 报告。 77 | 78 | ## 更多用法 79 | 80 | ### 在迁移您的业务之前验证数据库的支持 81 | 当您需要将业务系统从 A 数据库迁移到 B 时,为了防止 B 的不兼容,您可以编写自己的 `cts.json` (compatibility-test-suite) 来验证 B 数据库系统兼容性,其格式示例如下: 82 | ``` 83 | [ 84 | { 85 | "name": "del command", 86 | "command": [ 87 | "set k v", 88 | "del k" 89 | ], 90 | "result": [ 91 | "OK", 92 | 1 93 | ], 94 | "since": "1.0.0" 95 | } 96 | ] 97 | ``` 98 | 整体上是一个 JSON数组,包含多条测试 case,每一个都是 JSON Object,`command`和`result`是一一对应的。除过上述示例中的字段,还有一些字段如下: 99 | 100 | | name | value | 含义 | 101 | |----------------|--------------------|-----------------------------| 102 | | tags | standalone,cluster | 只在tags指定的模式下才允许此case | 103 | | skipped | true | 跳过此case | 104 | | command_binary | true | 将命令转为二进制,例如命令中包含非可见的ascii字符 | 105 | | sort_result | true | 对返回结果进行排序 | 106 | 107 | ### 使用别的编程语言使用此工具 108 | 本项目的主要工作是我们在 `cts.json`中增加了超过 7000 行测试,如果您希望用别的编程语言(例如 Java, Go, Rust等)实现相同功能的测试工具,那么您只需要解析 `cts.json` 的格式,并且将测试依次执行即可,玩的愉快。 109 | 110 | ## 版本升级兼容性指南 111 | 如果您正在计划升级 Redis 版本,我们建议您查阅这份 **[兼容性报告](compatibility_report_zh_CN.md)**。该报告梳理了 Redis 大版本之间的破坏性改动以及一些重要 Bug 的修复情况,旨在帮助您快速评估版本升级的风险,并提前规避已知问题。 112 | 113 | ## License 114 | [MIT](LICENSE) -------------------------------------------------------------------------------- /conn.py: -------------------------------------------------------------------------------- 1 | import time 2 | import yaml 3 | import subprocess 4 | import logging 5 | 6 | # 配置日志记录 7 | logging.basicConfig( 8 | filename='/tmp/conn.log', 9 | level=logging.INFO, 10 | format='%(asctime)s - %(levelname)s - %(message)s' 11 | ) 12 | 13 | 14 | def update_config(): 15 | yml_file_path = '/root/db_config.yml' 16 | config_file_path = 'config.yaml' 17 | 18 | try: 19 | logging.info(f"Reading YAML file from {yml_file_path} and config file from {config_file_path}") 20 | with open(yml_file_path, 'r') as yml_file, open(config_file_path, 'r') as config_file: 21 | yml_data = yaml.safe_load(yml_file) 22 | config_data = yaml.safe_load(config_file) 23 | 24 | logging.info("Merging YAML data into config data") 25 | for db_name, db_config in yml_data.items(): 26 | if db_name in config_data['Database']: 27 | for key, value in db_config.items(): 28 | config_data['Database'][db_name][key] = value 29 | else: 30 | config_data['Database'][db_name] = db_config 31 | 32 | logging.info(f"Writing updated config data to {config_file_path}") 33 | with open(config_file_path, 'w') as config_file: 34 | yaml.dump(config_data, config_file, sort_keys=False, default_flow_style=False) 35 | 36 | logging.info("Config file updated successfully.") 37 | 38 | except FileNotFoundError as e: 39 | logging.error(f"Error: {e}. Please check the file paths.") 40 | except yaml.YAMLError as e: 41 | logging.error(f"Error in YAML processing: {e}") 42 | except Exception as e: 43 | logging.error(f"An unexpected error occurred: {e}") 44 | 45 | 46 | def execute_command(commands): 47 | for command in commands: 48 | logging.info(f"Executing command: {command}") 49 | result = subprocess.run(command, shell=True, capture_output=True, text=True) 50 | if result.returncode != 0: 51 | logging.error(f"Error executing command '{command}': {result.stderr}") 52 | return False 53 | else: 54 | logging.info(f"Successfully executed command '{command}': {result.stdout}") 55 | return True 56 | 57 | 58 | def run_tests(): 59 | run_test_command = [ 60 | "pip3 install -r requirements.txt", 61 | "python3 resp_compatibility.py --testfile cts.json --genhtml --show-failed", 62 | ] 63 | 64 | logging.info("Starting test execution") 65 | if not execute_command(run_test_command): 66 | logging.error("Test failed. Exiting...") 67 | exit(1) 68 | else: 69 | logging.info("Test completed successfully.") 70 | 71 | 72 | def commit_and_push_results(): 73 | commit_and_push_commands = [ 74 | "mv html /root", 75 | "git stash -u", 76 | "git checkout gh-pages || git checkout -b gh-pages", 77 | "git pull origin gh-pages", 78 | "cp -r /root/html/* .", 79 | "git add .", 80 | "git commit -m 'Update test results'", 81 | ] 82 | 83 | logging.info("Starting commit and push process") 84 | if not execute_command(commit_and_push_commands): 85 | logging.error("Failed to commit and push changes. Exiting...") 86 | exit(1) 87 | 88 | 89 | def git_push_with_retry(): 90 | logging.info("Starting git push with retry") 91 | while True: 92 | result = subprocess.run("git push -u origin gh-pages", shell=True, capture_output=True, text=True) 93 | if result.returncode == 0: 94 | logging.info("Successfully pushed to GitHub.") 95 | break 96 | else: 97 | logging.error(f"Git push failed: {result.stderr}. Retrying in 5 seconds...") 98 | time.sleep(5) 99 | 100 | 101 | def main(): 102 | logging.info("Starting main function") 103 | 104 | update_config() 105 | 106 | package_update_commands = [ 107 | "apt-get update", 108 | "apt-get install -y python3-pip", 109 | ] 110 | logging.info("Updating packages") 111 | if not execute_command(package_update_commands): 112 | logging.error("Failed to update or install packages. Exiting...") 113 | exit(1) 114 | 115 | logging.info("Running tests") 116 | run_tests() 117 | 118 | commit_and_push_results() 119 | git_push_with_retry() 120 | 121 | logging.info("Main function completed successfully") 122 | 123 | 124 | if __name__ == "__main__": 125 | main() 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # resp-compatibility 2 | 3 | [![4.0](https://github.com/tair-opensource/resp-compatibility/actions/workflows/4.0.yaml/badge.svg)](https://github.com/tair-opensource/resp-compatibility/actions/workflows/4.0.yaml) [![5.0](https://github.com/tair-opensource/resp-compatibility/actions/workflows/5.0.yaml/badge.svg)](https://github.com/tair-opensource/resp-compatibility/actions/workflows/5.0.yaml) [![6.0](https://github.com/tair-opensource/resp-compatibility/actions/workflows/6.0.yaml/badge.svg)](https://github.com/tair-opensource/resp-compatibility/actions/workflows/6.0.yaml) [![6.2](https://github.com/tair-opensource/resp-compatibility/actions/workflows/6.2.yaml/badge.svg)](https://github.com/tair-opensource/resp-compatibility/actions/workflows/6.2.yaml) [![7.0](https://github.com/tair-opensource/resp-compatibility/actions/workflows/7.0.yaml/badge.svg)](https://github.com/tair-opensource/resp-compatibility/actions/workflows/7.0.yaml) [![7.2](https://github.com/tair-opensource/resp-compatibility/actions/workflows/7.2.yaml/badge.svg)](https://github.com/tair-opensource/resp-compatibility/actions/workflows/7.2.yaml) [![unstable](https://github.com/tair-opensource/resp-compatibility/actions/workflows/unstable.yaml/badge.svg)](https://github.com/tair-opensource/resp-compatibility/actions/workflows/unstable.yaml) 4 | 5 | resp-compatibility is used to test whether your redis-like database is compatible with Redis versions (such as 6 | 6.0, 7.0, etc.) [中文文档](README-CN.md) 7 | 8 | # Install 9 | 10 | requires `Python 3.7` or later. 11 | 12 | ``` 13 | pip3 install -r requirements.txt 14 | ``` 15 | 16 | # Testable Commands 17 | 18 | Refer to [this table](cts_refer.md) for the commands that CTS can test and their corresponding version information. 19 | 20 | # How to use 21 | 22 | ``` 23 | optional arguments: 24 | -h, --help show this help message and exit 25 | --host HOST the redis host 26 | --port PORT the redis port 27 | --password PASSWORD the redis password 28 | --testfile TESTFILE the redis compatibility test cases 29 | --specific-version {1.0.0,2.8.0,3.2.0,4.0.0,5.0.0,6.0.0,6.2.0,7.0.0,7.2.0} 30 | the redis version 31 | --show-failed show details of failed tests 32 | --cluster server is a node of the Redis cluster 33 | --ssl open ssl connection 34 | --genhtml generate test report in html format 35 | ``` 36 | e.g. Test whether host:port is compatible with redis 6.2.0 and display failure case: 37 | ``` 38 | $ python3 resp_compatibility.py -h host -p port --testfile cts.json --specific-version 6.2.0 --show-failed 39 | Connecting to 127.0.0.1:6379 use standalone client 40 | test: del command passed 41 | test: unlink command passed 42 | ... 43 | test: xtrim command with MINID/LIMIT passed 44 | -------- The result of tests -------- 45 | Summary: version: 6.2.0, total tests: 285, passed: 285, rate: 100.00% 46 | ``` 47 | More examples are shown `python3 resp_compatibility.py -h`. 48 | 49 | ## cluster 50 | Redis has two modes from the API level, namely `Standalone` (Sentinel has no API restrictions like Standalone) and `Cluster`, where the command support of Standalone does not require Cross slot, but Cluster restricts multi-key commands to be executed in the same slot (e.g. mset/mget ), therefore, we support `--cluster` to test the compatibility of cluster mode, you can test your Redis Cluster cluster compatibility as follows: 51 | ``` 52 | $ python3.9 resp_compatibility.py --testfile cts.json --host 127.0.0.1 --port 30001 --cluster --specific-version 6.2.0 53 | connecting to 127.0.0.1:30001 use cluster client 54 | test: del command passed 55 | test: unlink command passed 56 | ... 57 | test: xtrim command with MINID/LIMIT passed 58 | -------- The result of tests -------- 59 | Summary: version: 6.2.0, total tests: 260, passed: 260, rate: 100.00% 60 | ``` 61 | 62 | ## genhtml 63 | You can use `--genhtml` to generate a test report similar to the html of this [website](https://tair-opensource.github.io/resp-compatibility/). It should be noted that this option will read the configuration in [config.yaml](config.yaml) for testing. Special attention needs to be paid, at this time the `specific-version` specified in your command line will be overwritten by the one in the configuration file. 64 | ``` 65 | $ python3.9 resp_compatibility.py --testfile cts.json --genhtml --show-failed 66 | directory html already exists, will be deleted and renewed. 67 | start test Redis for version 4.0.0 68 | connecting to 127.0.0.1:6379 using standalone client 69 | start test Redis for version 5.0.0 70 | connecting to 127.0.0.1:6379 using standalone client 71 | start test Redis for version 6.0.0 72 | connecting to 127.0.0.1:6379 using standalone client 73 | start test Redis for version 7.0.0 74 | connecting to 127.0.0.1:6379 using standalone client 75 | ... 76 | Visit http://localhost:8000 for the report. 77 | ``` 78 | Then, a Http Server will be started on http://localhost:8000 by default, and you can access it to get reports. 79 | 80 | ## More uses 81 | 82 | ### Verify before migrating your database 83 | When you need to migrate the business system from A database to B, in order to prevent the incompatibility of B, you can write your own `cts.json` (compatibility-test-suite) to verify the compatibility of B database system, the format example is as follows : 84 | ``` 85 | [ 86 | { 87 | "name": "del command", 88 | "command": [ 89 | "set k v", 90 | "del k" 91 | ], 92 | "result": [ 93 | "OK", 94 | 1 95 | ], 96 | "since": "1.0.0" 97 | } 98 | ] 99 | ``` 100 | Overall, it is a JSON array, containing multiple test cases, each of which is a JSON Object, and `command` and `result` are in one-to-one correspondence. In addition to the fields in the example, there are some as follows: 101 | 102 | | name | value | meaning | 103 | |----------------|-----------------------|------------------------------------------------------------------------------------------| 104 | | tags | standalone or cluster | This case is only allowed in the mode specified by tags | 105 | | skipped | true | skip this case | 106 | | command_binary | true | Convert the command to binary, such as the command contains non-visible ascii characters | 107 | | sort_result | true | Sort the returned results | 108 | 109 | ### Test with another programming language 110 | The main work of this project is that we have added more than 7000 lines of tests in `cts.json`, if you want to implement tests in other programming languages (such as Java, Go, Rust, etc.), then you only need to parse `cts.json ` format, and execute the tests sequentially, have fun. 111 | 112 | ## Upgrade Compatibility Guide 113 | 114 | Planning a major version upgrade? To prevent unexpected issues, please check our **[Compatibility Report](compatibility_report_en_US.md)** for a list of breaking changes and important bug fixes. 115 | 116 | ## License 117 | [MIT](LICENSE) -------------------------------------------------------------------------------- /Terraform/Aliyun/main.tf: -------------------------------------------------------------------------------- 1 | variable "access_key" { 2 | description = "Access key for Alicloud provider" 3 | type = string 4 | } 5 | 6 | variable "secret_key" { 7 | description = "Secret key for Alicloud provider" 8 | type = string 9 | } 10 | 11 | variable "github_token" { 12 | description = "GitHub token for accessing GitHub API" 13 | type = string 14 | } 15 | 16 | variable "user_name" { 17 | description = "GitHub user name" 18 | type = string 19 | } 20 | 21 | variable "user_email" { 22 | description = "GitHub user email" 23 | type = string 24 | } 25 | 26 | locals { 27 | selected_zone_id = "cn-hongkong-b" 28 | } 29 | 30 | provider "alicloud" { 31 | access_key = var.access_key 32 | secret_key = var.secret_key 33 | region = "cn-hongkong" 34 | } 35 | 36 | # 创建 VPC 37 | resource "alicloud_vpc" "my_vpc" { 38 | vpc_name = "MyVPC" 39 | cidr_block = "172.16.0.0/24" 40 | } 41 | 42 | # 创建 VSwitch 43 | resource "alicloud_vswitch" "my_vswitch" { 44 | vswitch_name = "glcc_vswitch" 45 | vpc_id = alicloud_vpc.my_vpc.id 46 | cidr_block = "172.16.0.0/24" 47 | zone_id = local.selected_zone_id 48 | } 49 | 50 | resource "alicloud_security_group" "my_sg" { 51 | name = "glcc_test_security_group" 52 | vpc_id = alicloud_vpc.my_vpc.id 53 | description = "Security Group for testing" 54 | } 55 | 56 | resource "alicloud_security_group_rule" "allow_ssh" { 57 | security_group_id = alicloud_security_group.my_sg.id 58 | type = "ingress" 59 | ip_protocol = "tcp" 60 | nic_type = "intranet" 61 | policy = "accept" 62 | port_range = "22/22" 63 | priority = 1 64 | cidr_ip = "0.0.0.0/0" 65 | } 66 | 67 | resource "alicloud_security_group_rule" "allow_http" { 68 | security_group_id = alicloud_security_group.my_sg.id 69 | type = "ingress" 70 | ip_protocol = "tcp" 71 | nic_type = "intranet" 72 | policy = "accept" 73 | port_range = "80/80" 74 | priority = 100 75 | cidr_ip = "0.0.0.0/0" 76 | } 77 | 78 | resource "alicloud_security_group_rule" "allow_https_outbound" { 79 | type = "egress" 80 | security_group_id = alicloud_security_group.my_sg.id 81 | ip_protocol = "tcp" 82 | nic_type = "intranet" 83 | policy = "accept" 84 | port_range = "443/443" 85 | cidr_ip = "0.0.0.0/0" 86 | description = "Allow outbound HTTPS traffic to external services" 87 | } 88 | 89 | resource "alicloud_security_group_rule" "allow_redis" { 90 | type = "ingress" 91 | security_group_id = alicloud_security_group.my_sg.id 92 | ip_protocol = "tcp" 93 | nic_type = "intranet" 94 | policy = "accept" 95 | port_range = "6379/17005" 96 | cidr_ip = "172.16.0.10/16" # 仅允许特定 ECS 实例的私有 IP 访问 97 | description = "Allow inbound Redis traffic" 98 | } 99 | 100 | # 云原生 1GB 标准版 Tair 6.0 101 | resource "alicloud_kvstore_instance" "my_tair_standard" { 102 | db_instance_name = "glcc_tair_standard" 103 | instance_class = "tair.rdb.1g" 104 | instance_type = "Redis" 105 | engine_version = "7.0" 106 | zone_id = local.selected_zone_id 107 | vswitch_id = alicloud_vswitch.my_vswitch.id 108 | payment_type = "PostPaid" 109 | password = "T123456@*" 110 | security_ips = ["172.16.0.10"] 111 | } 112 | 113 | 114 | # 云原生 1GB 集群版 Tair 6.0 115 | resource "alicloud_kvstore_instance" "my_tair_cluster" { 116 | db_instance_name = "glcc_tair_cluster" 117 | instance_class = "tair.rdb.with.proxy.1g" 118 | instance_type = "Redis" 119 | engine_version = "7.0" 120 | shard_count = "2" 121 | zone_id = local.selected_zone_id 122 | vswitch_id = alicloud_vswitch.my_vswitch.id 123 | payment_type = "PostPaid" 124 | password = "T123456@*" 125 | security_ips = ["172.16.0.10"] 126 | } 127 | 128 | # 云原生 1GB 标准版 Redis 6.0 129 | resource "alicloud_kvstore_instance" "my_redis_standard" { 130 | db_instance_name = "glcc_redis_standard" 131 | instance_class = "redis.shard.small.2.ce" 132 | instance_type = "Redis" 133 | engine_version = "7.0" 134 | zone_id = local.selected_zone_id 135 | vswitch_id = alicloud_vswitch.my_vswitch.id 136 | payment_type = "PostPaid" 137 | password = "T123456@*" 138 | security_ips = ["172.16.0.10"] 139 | } 140 | 141 | 142 | # 云原生 1GB 集群版 Redis 6.0 143 | resource "alicloud_kvstore_instance" "my_redis_cluster" { 144 | db_instance_name = "glcc_redis_cluster" 145 | instance_class = "redis.shard.with.proxy.small.ce" 146 | instance_type = "Redis" 147 | engine_version = "7.0" 148 | shard_count = "2" 149 | zone_id = local.selected_zone_id 150 | vswitch_id = alicloud_vswitch.my_vswitch.id 151 | payment_type = "PostPaid" 152 | password = "T123456@*" 153 | security_ips = ["172.16.0.10"] 154 | } 155 | 156 | 157 | # 输出实例信息,目前用于验证 158 | output "tair_standard_instance_address" { 159 | value = alicloud_kvstore_instance.my_tair_standard.connection_domain 160 | } 161 | 162 | output "tair_standard_instance_port" { 163 | value = alicloud_kvstore_instance.my_tair_standard.private_connection_port 164 | } 165 | 166 | output "tair_standard_instance_password" { 167 | value = alicloud_kvstore_instance.my_tair_standard.password 168 | sensitive = true 169 | } 170 | 171 | output "tair_cluster_instance_address" { 172 | value = alicloud_kvstore_instance.my_tair_cluster.connection_domain 173 | } 174 | 175 | output "tair_cluster_instance_port" { 176 | value = alicloud_kvstore_instance.my_tair_cluster.private_connection_port 177 | } 178 | 179 | output "tair_cluster_instance_password" { 180 | value = alicloud_kvstore_instance.my_tair_cluster.password 181 | sensitive = true 182 | } 183 | 184 | output "redis_standard_instance_address" { 185 | value = alicloud_kvstore_instance.my_redis_standard.connection_domain 186 | } 187 | 188 | output "redis_standard_instance_port" { 189 | value = alicloud_kvstore_instance.my_redis_standard.private_connection_port 190 | } 191 | 192 | output "redis_standard_instance_password" { 193 | value = alicloud_kvstore_instance.my_redis_standard.password 194 | sensitive = true 195 | } 196 | 197 | output "redis_cluster_instance_address" { 198 | value = alicloud_kvstore_instance.my_redis_cluster.connection_domain 199 | } 200 | 201 | output "redis_cluster_instance_port" { 202 | value = alicloud_kvstore_instance.my_redis_cluster.private_connection_port 203 | } 204 | 205 | output "redis_cluster_instance_password" { 206 | value = alicloud_kvstore_instance.my_redis_cluster.password 207 | sensitive = true 208 | } 209 | 210 | # 创建 ECS 实例 211 | resource "alicloud_instance" "my_ecs" { 212 | private_ip = "172.16.0.10" 213 | instance_type = "ecs.g6.xlarge" 214 | security_groups = [alicloud_security_group.my_sg.id] 215 | instance_charge_type = "PostPaid" 216 | internet_charge_type = "PayByTraffic" 217 | internet_max_bandwidth_out = 10 218 | image_id = "ubuntu_22_04_x64_20G_alibase_20240130.vhd" 219 | instance_name = "glcc_ecs" 220 | vswitch_id = alicloud_vswitch.my_vswitch.id 221 | system_disk_category = "cloud_efficiency" 222 | password = "T123456@*" 223 | 224 | lifecycle { 225 | create_before_destroy = true 226 | } 227 | 228 | user_data = <=6.0" 235 | git config --global credential.helper 'store' 236 | source /etc/profile 237 | 238 | # 写入数据库配置信息 239 | cat <> /root/db_config.yml 240 | AliyunTair: 241 | host: ${alicloud_kvstore_instance.my_tair_standard.connection_domain} 242 | port: ${alicloud_kvstore_instance.my_tair_standard.private_connection_port} 243 | password: T123456@* 244 | ssl: false 245 | cluster: false 246 | version: 7.0 247 | 248 | AliyunTairCluster: 249 | host: ${alicloud_kvstore_instance.my_tair_cluster.connection_domain} 250 | port: ${alicloud_kvstore_instance.my_tair_cluster.private_connection_port} 251 | password: T123456@* 252 | ssl: false 253 | cluster: true 254 | version: 7.0 255 | 256 | AliyunRedis: 257 | host: ${alicloud_kvstore_instance.my_redis_standard.connection_domain} 258 | port: ${alicloud_kvstore_instance.my_redis_standard.private_connection_port} 259 | password: T123456@* 260 | ssl: false 261 | cluster: false 262 | version: 7.0 263 | 264 | AliyunRedisCluster: 265 | host: ${alicloud_kvstore_instance.my_redis_cluster.connection_domain} 266 | port: ${alicloud_kvstore_instance.my_redis_cluster.private_connection_port} 267 | password: T123456@* 268 | ssl: false 269 | cluster: true 270 | version: 7.0 271 | EOT 272 | 273 | 274 | # 拉取和运行数据库容器 275 | docker pull docker.io/redis:latest 276 | docker run --name redis -d -p 6379:6379 redis 277 | 278 | docker pull docker.dragonflydb.io/dragonflydb/dragonfly:latest 279 | docker run --name dragonflydb -d -p 6380:6379 docker.dragonflydb.io/dragonflydb/dragonfly 280 | 281 | docker pull apache/kvrocks:latest 282 | docker run --name kvrocks -d -p 6381:6666 apache/kvrocks 283 | 284 | docker pull docker.io/eqalpha/keydb:latest 285 | docker run --name keydb -d -p 6382:6379 eqalpha/keydb 286 | 287 | docker pull docker.io/pikadb/pika:latest 288 | docker run --name pika -d -p 6383:9221 pikadb/pika 289 | 290 | docker pull valkey/valkey:latest 291 | docker run --name valkey -d -p 6384:6379 valkey/valkey 292 | 293 | 294 | # 配置Git 295 | echo "https://${var.github_token}:x-oauth-basic@github.com" > ~/.git-credentials 296 | git config --global user.name "${var.user_name}" 297 | git config --global user.email "${var.user_email}" 298 | 299 | # 启动Redis Cluster 300 | cd ~ 301 | git clone https://github.com/redis/redis.git 302 | cd redis 303 | make -j 304 | cd utils/create-cluster 305 | ./create-cluster start 306 | echo yes | ./create-cluster create 307 | 308 | cd ~ 309 | REPO_URL="https://github.com/tair-opensource/resp-compatibility.git" 310 | RETRY_COUNT=0 311 | MAX_RETRIES=10 312 | SLEEP_DURATION=30 313 | 314 | while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do 315 | if git clone $REPO_URL; then 316 | echo "Git clone succeeded" 317 | break 318 | else 319 | RETRY_COUNT=$((RETRY_COUNT + 1)) 320 | echo "Git clone failed, attempt $RETRY_COUNT/$MAX_RETRIES" 321 | sleep $SLEEP_DURATION 322 | fi 323 | done 324 | 325 | if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then 326 | echo "Git clone failed after $MAX_RETRIES attempts" >&2 327 | exit 1 328 | fi 329 | 330 | cd resp-compatibility 331 | python3 conn.py 332 | EOF 333 | } 334 | -------------------------------------------------------------------------------- /resp_compatibility.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | import re 5 | import http.server 6 | 7 | import redis 8 | import json 9 | import yaml 10 | import shutil 11 | import datetime 12 | from dataclasses import dataclass 13 | from typing import List, Dict 14 | 15 | EXAMPLE = """ 16 | Examples: 17 | 18 | Run tests without specifying a version 19 | python3 resp_compatibility.py --testfile cts.json 20 | 21 | Run the test for compatibility with Redis 6.2.0 22 | python3 resp_compatibility.py --testfile cts.json --specific-version 6.2.0 23 | 24 | Run the test whether it is compatible with Redis 6.2.0, and print the failure case 25 | python3 resp_compatibility.py --testfile cts.json --specific-version 6.2.0 --show-failed 26 | 27 | Run the test for redis cluster 28 | python3 resp_compatibility.py --testfile cts.json --host 127.0.0.1 --port 30001 --cluster 29 | """ 30 | 31 | 32 | @dataclass 33 | class FailedTest: 34 | name: str 35 | reason: object 36 | 37 | 38 | @dataclass 39 | class TestResult: 40 | total: int 41 | passed: int 42 | failed: List[FailedTest] 43 | 44 | 45 | r: redis.Redis = None 46 | g_results: Dict[str, TestResult] = {} 47 | logfile = None 48 | 49 | 50 | def report_result(): 51 | print(f"-------- The result of tests --------", file=logfile) 52 | if args.specific_version: 53 | total = passed = 0 54 | failed: List[FailedTest] = [] 55 | for v, t in g_results.items(): 56 | total += t.total 57 | passed += t.passed 58 | failed.extend(t.failed) 59 | rate = passed / total * 100 60 | print(f"Summary: version: {args.specific_version}, total tests: {total}, passed: {passed}, " 61 | f"rate: {rate:.2f}%", file=logfile) 62 | if args.show_failed and len(failed) != 0: 63 | print(f"This is failed tests for {args.specific_version}:", file=logfile) 64 | print('\n'.join(str(fail) for fail in failed), file=logfile) 65 | else: 66 | for v, t in sorted(g_results.items()): 67 | rate = t.passed / t.total * 100 68 | print(f"Summary: version: {v}, total tests: {t.total}, passed: {t.passed}, " 69 | f"rate: {rate:.2f}%", file=logfile) 70 | for v, t in sorted(g_results.items()): 71 | if args.show_failed and len(t.failed) != 0: 72 | print(f"This is failed tests for {v}:", file=logfile) 73 | print('\n'.join(str(fail) for fail in t.failed), file=logfile) 74 | 75 | 76 | def test_passed(result): 77 | print("passed", file=logfile) 78 | result.total += 1 79 | result.passed += 1 80 | 81 | 82 | def test_failed(result, name, e): 83 | print("failed", file=logfile) 84 | result.total += 1 85 | result.failed.append(FailedTest(name=name, reason=e)) 86 | 87 | 88 | def trans_cmd_to_binary(cmd): 89 | array = bytearray() 90 | i = 0 91 | while i < len(cmd): 92 | if cmd[i] == '\\' and cmd[i + 1] == '\\': 93 | array.append(92) 94 | i += 2 95 | elif cmd[i] == '\\' and cmd[i + 1] == '"': 96 | array.append(34) 97 | i += 2 98 | elif cmd[i] == '\\' and cmd[i + 1] == 'n': 99 | array.append(10) 100 | i += 2 101 | elif cmd[i] == '\\' and cmd[i + 1] == 'r': 102 | array.append(13) 103 | i += 2 104 | elif cmd[i] == '\\' and cmd[i + 1] == 't': 105 | array.append(9) 106 | i += 2 107 | elif cmd[i] == '\\' and cmd[i + 1] == 'a': 108 | array.append(7) 109 | i += 2 110 | elif cmd[i] == '\\' and cmd[i + 1] == 'b': 111 | array.append(8) 112 | i += 2 113 | elif cmd[i] == '\\' and cmd[i + 1] == 'x': 114 | array.append(int(cmd[i + 2], 16) * 16 + int(cmd[i + 3], 16)) 115 | i += 4 116 | else: 117 | array.append(ord(cmd[i])) 118 | i += 1 119 | return bytes(array) 120 | 121 | 122 | def split_cmd_as_list(cmd, command_binary): 123 | # split command by "" 124 | # input: 'hello "world of python" example' 125 | # output: ['hello', 'world of python', 'example'] 126 | parts = [] 127 | in_quote = False 128 | current_part = b'' if command_binary else '' 129 | for char in cmd: 130 | byte = char if command_binary else ord(char) 131 | if byte == ord('"'): 132 | in_quote = not in_quote 133 | elif byte == ord(' ') and not in_quote: 134 | parts.append(current_part) 135 | current_part = b'' if command_binary else '' 136 | else: 137 | if command_binary: 138 | current_part += bytes([byte]) 139 | else: 140 | current_part += char 141 | parts.append(current_part) 142 | if command_binary: 143 | parts[0] = parts[0].decode() 144 | return parts 145 | 146 | 147 | def sort_nested_list(result): 148 | has_nested_list = False 149 | for i in range(len(result)): 150 | if isinstance(result[i], list): 151 | has_nested_list = True 152 | result[i] = sort_nested_list(result[i]) 153 | if has_nested_list: 154 | return result 155 | else: 156 | return sorted(result) 157 | 158 | 159 | def compare_nested_lists_with_float_tolerance(list1, list2, tolerance=0.01): 160 | """ 161 | Compare two nested lists with float tolerance. 162 | For each string that can be converted to float, compare as float with tolerance. 163 | 164 | Args: 165 | list1: First list to compare 166 | list2: Second list to compare 167 | tolerance: Float tolerance for comparison (default 0.01) 168 | 169 | Returns: 170 | bool: True if lists are equal within tolerance, False otherwise 171 | """ 172 | if type(list1) != type(list2): 173 | return False 174 | 175 | if isinstance(list1, list): 176 | if len(list1) != len(list2): 177 | return False 178 | for i in range(len(list1)): 179 | if not compare_nested_lists_with_float_tolerance(list1[i], list2[i], tolerance): 180 | return False 181 | return True 182 | elif isinstance(list1, str): 183 | # Try to convert both strings to float for comparison 184 | try: 185 | float1 = float(list1) 186 | float2 = float(list2) 187 | return abs(float1 - float2) < tolerance 188 | except (ValueError, TypeError): 189 | # If conversion fails, compare as strings 190 | return list1 == list2 191 | else: 192 | # For other types (int, bool, etc.), use direct comparison 193 | return list1 == list2 194 | 195 | 196 | def run_test(test): 197 | name = test['name'] 198 | print(f"test: {name}", end=" ", file=logfile) 199 | # if test need skipped 200 | if 'skipped' in test: 201 | print("skipped", file=logfile) 202 | return 203 | 204 | # judge tags not match 205 | if 'tags' in test: 206 | tags = test['tags'] 207 | if (args.cluster and tags == "standalone") or \ 208 | (not args.cluster and tags == "cluster"): 209 | print("tags skipped", file=logfile) 210 | return 211 | 212 | # high version test 213 | since = test['since'] 214 | if args.specific_version and since > args.specific_version: 215 | print("version skipped", file=logfile) 216 | return 217 | if since not in g_results: 218 | g_results[since] = TestResult(total=0, passed=0, failed=[]) 219 | 220 | r.flushall() 221 | command = test['command'] 222 | result = test['result'] 223 | try: 224 | for idx, cmd in enumerate(command): 225 | if 'command_binary' in test: 226 | cmd = trans_cmd_to_binary(cmd) 227 | tcmd = split_cmd_as_list(cmd, True) 228 | else: 229 | tcmd = split_cmd_as_list(cmd, False) 230 | ret = r.execute_command(*tcmd) 231 | if 'sort_result' in test and isinstance(result[idx], list): 232 | ret = sort_nested_list(ret) 233 | result[idx] = sort_nested_list(result[idx]) 234 | if 'float_result' in test and isinstance(result[idx], list): 235 | if not compare_nested_lists_with_float_tolerance(result[idx], ret): 236 | test_failed(g_results[since], name, f"expected: {result[idx]}, result: {ret}") 237 | return 238 | elif result[idx] != ret: 239 | test_failed(g_results[since], name, f"expected: {result[idx]}, result: {ret}") 240 | return 241 | test_passed(g_results[since]) 242 | except Exception as e: 243 | test_failed(g_results[since], name, e) 244 | 245 | 246 | def run_compatibility_tests(filename): 247 | with open(filename, "r") as f: 248 | tests = f.read() 249 | tests_array = json.loads(tests) 250 | for test in tests_array: 251 | try: 252 | run_test(test) 253 | except Exception as e: 254 | print(f"run test error {e}", file=logfile) 255 | continue 256 | 257 | 258 | def generate_html_report(logdir, configs): 259 | filepath = f"{logdir}/index.html" 260 | html = open(filepath, "w") 261 | html.write("This page is automatically generated by resp-compatibility " 263 | "to show the compatibility of the following Redis-Like systems and different versions of Redis.

") 264 | 265 | # Separate databases into cluster and standalone 266 | cluster_databases = [] 267 | standalone_databases = [] 268 | 269 | for config in configs['Database']: 270 | if configs['Database'][config]['cluster']: 271 | cluster_databases.append(config) 272 | else: 273 | standalone_databases.append(config) 274 | 275 | # Function to generate a table 276 | def generate_table(databases, title): 277 | html.write(f"

{title}

") 278 | html.write("") 279 | # generate header 280 | html.write("") 281 | html.write("") 282 | html.write("") 283 | for version in configs['SpecificVersion']: 284 | html.write(f"") 285 | html.write("") 286 | html.write("") 287 | # generate body 288 | html.write("") 289 | for config in databases: 290 | html.write("") 291 | html.write(f"") 292 | for version in configs['SpecificVersion']: 293 | filepath = f"{logdir}/{config}-{version}.html" 294 | if not os.path.exists(filepath): 295 | html.write(f"") 296 | continue 297 | with open(filepath, 'r') as f: 298 | s = f.read() 299 | match = re.search(r"rate: (\d+\.\d+)%", s) 300 | assert match 301 | rate = match.group(1) 302 | color = "#40de5a" 303 | if eval(rate) < 80: 304 | color = "#f05654" 305 | elif eval(rate) < 100: 306 | color = "#ffa400" 307 | html.write(f"") 308 | html.write("") 309 | html.write("") 310 | html.write("
Product / Redis Version{version}
{config}({configs['Database'][config]['version']})-{rate}% detail

") 311 | 312 | # Generate standalone table 313 | generate_table(standalone_databases, "Standalone Databases") 314 | 315 | # Generate cluster table 316 | generate_table(cluster_databases, "Cluster Databases") 317 | 318 | time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 319 | html.write(f"This report was generated on {time}.") 320 | html.write("") 321 | html.close() 322 | 323 | 324 | def start_webserver(logdir): 325 | os.chdir(logdir) 326 | handler = http.server.SimpleHTTPRequestHandler 327 | httpd = http.server.HTTPServer(('', 8000), handler) 328 | httpd.directory = logdir 329 | print(f"Visit http://localhost:8000 for the report.") 330 | httpd.serve_forever() 331 | 332 | 333 | def run_test_by_configfile(): 334 | global logfile 335 | try: 336 | with open('config.yaml', 'r') as f: 337 | configs = yaml.load(f, Loader=yaml.FullLoader) 338 | except FileNotFoundError as e: 339 | print(f"error {e}") 340 | exit(-1) 341 | 342 | logdir = configs['Dir'] 343 | if os.path.exists(logdir): 344 | print(f"directory {logdir} already exists, will be deleted and renew.") 345 | shutil.rmtree(logdir) 346 | os.makedirs(logdir) 347 | for config in configs['Database']: 348 | for version in configs['SpecificVersion']: 349 | print(f"start test {config} for version {version}") 350 | try: 351 | create_client(configs['Database'][config]['host'], 352 | configs['Database'][config]['port'], 353 | configs['Database'][config]['password'], 354 | configs['Database'][config]['ssl'], 355 | configs['Database'][config]['cluster']) 356 | except Exception as e: 357 | print(f"connect to {configs['Database'][config]['host']}:{configs['Database'][config]['port']} " 358 | f"fail, skip this test, error {e}") 359 | break 360 | filepath = f"{logdir}/{config}-{version}.html" 361 | logfile = open(filepath, "w") 362 | args.specific_version = version 363 | args.show_failed = True 364 | args.cluster = configs['Database'][config]['cluster'] 365 | g_results.clear() 366 | print("
", file=logfile)
367 |             run_compatibility_tests(args.testfile)
368 |             report_result()
369 |             print("
", file=logfile) 370 | logfile.close() 371 | logfile = None 372 | # now we generate index.html 373 | generate_html_report(logdir, configs) 374 | if args.webserver: 375 | start_webserver(logdir) 376 | 377 | 378 | def create_client(host, port, password, ssl, cluster): 379 | global r 380 | if cluster: 381 | print(f"connecting to {host}:{port} use cluster client", file=logfile) 382 | r = redis.RedisCluster(host=host, port=port, password=password, ssl=ssl, decode_responses=True) 383 | for _, nodes in dict(r.nodes_manager.slots_cache).items(): 384 | for node in nodes: 385 | node.redis_connection.response_callbacks = {} 386 | assert r.ping() 387 | else: 388 | print(f"connecting to {host}:{port} use standalone client", file=logfile) 389 | r = redis.Redis(host=host, port=port, password=password, ssl=ssl, decode_responses=True) 390 | r.response_callbacks = {} 391 | assert r.ping() 392 | 393 | 394 | def parse_args(): 395 | parser = argparse.ArgumentParser(prog=" resp_compatibility", 396 | description=" resp_compatibility is used to test whether your redis-like " 397 | "database is compatible with Redis versions (such as 6.0, 7.0, etc.)", 398 | formatter_class=argparse.RawDescriptionHelpFormatter, 399 | epilog=EXAMPLE) 400 | parser.add_argument("--host", help="the redis host", default="127.0.0.1") 401 | parser.add_argument("--port", help="the redis port", default=6379, type=int) 402 | parser.add_argument("--password", help="the redis password", default="") 403 | parser.add_argument("--testfile", help="the redis compatibility test cases", required=True) 404 | parser.add_argument("--specific-version", dest="specific_version", help="the redis version", 405 | choices=['1.0.0', '2.8.0', '3.2.0', '4.0.0', '5.0.0', '6.0.0', '6.2.0', '7.0.0', '7.2.0']) 406 | parser.add_argument("--show-failed", dest="show_failed", help="show details of failed tests", default=False, 407 | action="store_true") 408 | parser.add_argument("--cluster", help="server is a node of the Redis cluster", default=False, action="store_true") 409 | parser.add_argument("--ssl", help="open ssl connection", default=False, action="store_true") 410 | parser.add_argument("--genhtml", help="generate test report in html format", default=False, action="store_true") 411 | parser.add_argument("--webserver", help="start a web server to show the test report", default=False, 412 | action="store_true") 413 | return parser.parse_args() 414 | 415 | 416 | if __name__ == '__main__': 417 | args = parse_args() 418 | if args.genhtml: 419 | run_test_by_configfile() 420 | else: 421 | create_client(args.host, args.port, args.password, args.ssl, args.cluster) 422 | run_compatibility_tests(args.testfile) 423 | report_result() 424 | -------------------------------------------------------------------------------- /compatibility_report_zh_CN.md: -------------------------------------------------------------------------------- 1 | # Redis 大版本兼容性报告 2 | 3 | 本文档根据 Redis 社区的 Release Note 以及阿里云 Tair 团队阅读 Redis 代码和日常运营遇到的问题撰写,供 Redis 用户升级大版本前评估和参考。如有遗漏,欢迎提 issue 补充,采纳后会同时添加您到贡献者列表。 4 | 5 | # 用户视角兼容性差异: 6 | 7 | ## 4.0 版本升级到 5.0 版本 8 | 9 | 1. `KEYS` 命令遍历过程中只跳过已过期的 key,不再删除。不能再使用 `KEYS` 命令来主动清理已过期的 key,`SCAN` 命令仍会删除扫描到的已过期数据。 10 | - 社区 PR:[https://github.com/redis/redis/commit/edc47a3a](https://github.com/redis/redis/commit/edc47a3a) 11 | 12 | 2. Lua 脚本默认使用效果复制,而不再复制脚本本身,防止脚本在主备上执行结果不一致带来的数据不一致,可以通过配置项 `lua-replicate-commands` 更改(在 7.0 版本移除),或是 `DEBUG lua-always-replicate-commands` 命令更改。 13 | - 社区 PR:[https://github.com/redis/redis/commit/3e1fb5ff](https://github.com/redis/redis/commit/3e1fb5ff) 14 | 15 | ```plaintext 16 | 1. 使用随机性命令,复制脚本会导致在主备上结果不同 17 | local key = KEYS[1] 18 | local member = redis.call('SPOP', key) 19 | redis.call('SET', 'last_removed', member) 20 | return member 21 | 22 | 2. 由于过期/逐出行为,导致 get 得到的结果在主备上不同,导致脚本在主备执行不同分支 23 | local key = KEYS[1] 24 | local value = redis.call('GET', key) 25 | if value then 26 | redis.call('SET', key, tonumber(value) + 1) 27 | else 28 | redis.call('SET', key, 1) 29 | end 30 | ``` 31 | 32 | 3. 只读脚本会复制为 `SCRIPT LOAD` 命令,不再复制 `EVAL`。 33 | - 社区 PR:[https://github.com/redis/redis/commit/dfbce91a](https://github.com/redis/redis/commit/dfbce91a) 34 | 35 | 4. 备库默认忽略 `maxmemory` 限制,不再尝试逐出数据,可以通过配置项 `slave-ignore-maxmemory` 更改。 36 | - 社区 PR:[https://github.com/redis/redis/commit/447da44d](https://github.com/redis/redis/commit/447da44d) 37 | 38 | 5. Redis 在开启 `volatile-lru` 内存逐出策略的时候,会禁止使用 `shared obj` 来表示诸如 1,2,3 等类似的数字(避免更新 LRU 信息的时候互相影响),副作用是浪费了内存。但是在主备复制时,如果走了全量复制,那么备库在加载 RDB 时没有复用那个策略,导致备库加载起来之后内存中的 key 使用了 `shared obj` 对象。因此这就会导致主备虽然数据一致,但是内存中的数据编码不一样。从而导致备库使用的内存比主库的小。该问题在 5.0 版本修复。升级后可能会遇到内存容量不一样的情况,社区 MR: 39 | - [https://github.com/redis/redis/commit/bd92389c2dc1afcf53218880fc31ba45e66f4ded](https://github.com/redis/redis/commit/bd92389c2dc1afcf53218880fc31ba45e66f4ded) 40 | - [https://github.com/redis/redis/commit/0ed0dc3c02dfffdf6bfb9e32f1335ddc34f37723](https://github.com/redis/redis/commit/0ed0dc3c02dfffdf6bfb9e32f1335ddc34f37723) 41 | 42 | ## 5.0 版本升级到 6.0 版本 43 | 44 | 1. `OBJECT ENCODING` 命令支持识别并返回 stream 类型,原来对 stream 类型的数据使用 `OBJECT ENCODING` 命令会错误返回 "unknown"。 45 | - 社区 PR:[https://github.com/redis/redis/pull/7797](https://github.com/redis/redis/pull/7797) 46 | 47 | 2. 集群模式下,只读事务允许在只读节点上执行,而不是重定向到主节点。 48 | - 社区 PR:[https://github.com/redis/redis/pull/7766](https://github.com/redis/redis/pull/7766) 49 | 50 | ```plaintext 51 | 原来的行为: 52 | connect to a replica in cluster 53 | > readonly 54 | > get k 55 | "v" 56 | > multi 57 | > get k 58 | > exec 59 | (error) MOVED 60 | 61 | 改动后: 62 | connect to a replica in cluster 63 | > readonly 64 | > get k 65 | "v" 66 | > multi 67 | > get k 68 | > exec 69 | "v" 70 | ``` 71 | 72 | 3. `BRPOP`/`BLPOP`/`BRPOPLPUSH` timeout 参数由整数改为浮点数。引入了当 timeout 小于等于 0.001 秒时,会被解析为 0 导致永久阻塞的 bug,在 7.0 版本才修复。 73 | - 命令文档:[https://redis.io/docs/latest/commands/brpop/](https://redis.io/docs/latest/commands/brpop/), [https://redis.io/docs/latest/commands/blpop/](https://redis.io/docs/latest/commands/blpop/), [https://redis.io/docs/latest/commands/brpoplpush/](https://redis.io/docs/latest/commands/brpoplpush/) 74 | 75 | 4. `BZPOPMIN`/`BZPOPMAX` timeout 参数由整数改为浮点数。引入了当 timeout 小于等于 0.001 秒时,会被解析为 0 导致永久阻塞的 bug,在 7.0 版本才修复。 76 | - 命令文档:[https://redis.io/docs/latest/commands/bzpopmin/](https://redis.io/docs/latest/commands/bzpopmin/), [https://redis.io/docs/latest/commands/bzpopmax/](https://redis.io/docs/latest/commands/bzpopmax/) 77 | 78 | 5. `SETRANGE`, `APPEND` 命令所能生成的最大 string 类型长度由确定的 512MB 修改为使用配置 `proto_max_bulk_len` 控制(`proto_max_bulk_len` 默认值仍为 512MB)。 79 | - 社区 PR:[https://github.com/redis/redis/pull/4633](https://github.com/redis/redis/pull/4633) 80 | 81 | ## 6.0 版本升级到 6.2 版本 82 | 83 | 1. 当 AOF fsync 的策略是 'always' 时,fsync 失败会直接让进程退出。 84 | - 社区 PR:[https://github.com/redis/redis/pull/8347](https://github.com/redis/redis/pull/8347) 85 | 86 | 2. 将 `SRANDMEMBER` 命令在 RESP 3 协议下的返回值类型从 Set 改为 Array,因为当使用负数的 `COUNT` 参数时,可以允许执行结果包含重复元素。 87 | - 社区 PR:[https://github.com/redis/redis/pull/8504](https://github.com/redis/redis/pull/8504) 88 | 89 | 3. `PUBSUB NUMPAT` 命令返回值含义改变,6.0 及以前版本返回的是所有 pattern 的订阅总数(客户端数量),6.2 版本开始返回 pattern 总数(pattern 数量)。 90 | - 社区 PR:[https://github.com/redis/redis/pull/8472](https://github.com/redis/redis/pull/8472) 91 | - 命令文档:[https://redis.io/docs/latest/commands/pubsub-numpat/](https://redis.io/docs/latest/commands/pubsub-numpat/) 92 | 93 | 4. `CLIENT TRACKING` 如果订阅了相互包含的前缀会返回错误,用于防止客户端收到重复的消息。 94 | - 社区 PR:[https://github.com/redis/redis/pull/8176](https://github.com/redis/redis/pull/8176) 95 | 96 | 5. `SWAPDB` 命令会 touch 两个 DB 中的所有 watch keys,让事务失败。 97 | - 社区 PR:[https://github.com/redis/redis/pull/8239](https://github.com/redis/redis/pull/8239) 98 | 99 | ```plaintext 100 | 原来的行为: 101 | client A> select 0 102 | client A> set k 0 103 | client A> select 1 104 | client A[1]> set k 1 105 | client A[1]> watch k 106 | client A[1]> multi 107 | client A[1]> get k 108 | 109 | client B> swapdb 0 1 110 | 111 | client A[1]> exec 112 | "0" 113 | 114 | 改动后: 115 | client A> select 0 116 | client A> set k 0 117 | client A> select 1 118 | client A[1]> set k 1 119 | client A[1]> watch k 120 | client A[1]> multi 121 | client A[1]> get k 122 | 123 | client B> swapdb 0 1 124 | 125 | client A[1]> exec 126 | (nil) 127 | ``` 128 | 129 | 6. `FLUSHDB` 会清除 server 上所有客户端的所有 tracking key 记录。 130 | - 社区 PR:[https://github.com/redis/redis/pull/8039](https://github.com/redis/redis/pull/8039) 131 | 132 | 7. BIT 操作最大限制长度由确定的 512MB 修改为使用配置 `proto_max_bulk_len` 控制(`proto_max_bulk_len` 默认值仍为 512MB)。 133 | - 社区 PR:[https://github.com/redis/redis/pull/8096](https://github.com/redis/redis/pull/8096) 134 | 135 | 8. `bind` 配置在启动 server 时需要指明 `-` 前缀,有 `-` 前缀的地址会自动忽略 `EADDRNOTAVAIL` 错误,否则不忽略;之前的版本默认会忽略 `EADDRNOTAVAIL` 错误。同时支持使用 `bind *` 来监听所有地址,之前的版本只能通过省略 `bind` 配置来实现。 136 | - 社区 PR:[https://github.com/redis/redis/pull/7936](https://github.com/redis/redis/pull/7936) 137 | 138 | ```plaintext 139 | 原来的行为: 140 | bind 任何地址,只要有一个地址成功,其它失败不会影响 Redis 启动 141 | 142 | 改动后: 143 | bind 默认地址(127.0.0.1/::1),只要有一个地址成功,其它失败不会影响 Redis 启动 144 | bind 指定'-' + 非默认地址(如 -192.168.1.100),该地址绑定失败不会影响 Redis 启动,不加'-'的非默认地址失败会导致 Redis 启动失败 145 | ``` 146 | 147 | 9. 使用参数启动 `redis-server` 不再重置 `save` 配置,之前的版本只要使用了额外的参数,无论是否包括 `save`,就会导致 `save` 配置先被重置为 `""`,然后再加载其他额外参数。 148 | - 社区 PR:[https://github.com/redis/redis/pull/7092](https://github.com/redis/redis/pull/7092) 149 | 150 | ```plaintext 151 | 原来的行为: 152 | redis-server --other-args xx --> save "" 153 | redis-server [--other-args xx] --save 3600 1 --> save "3600 1" 154 | redis-server --> save "3600 1 300 100 60 10000" 155 | 156 | 改动后: 157 | redis-server --other-args xx --> save "3600 1 300 100 60 10000" 158 | redis-server [--other-args xx] --save 3600 1 --> save "3600 1" 159 | redis-server --> save "3600 1 300 100 60 10000" 160 | ``` 161 | 162 | 10. `SLOWLOG` 中记录原始命令而不是 rewrite 之后的命令。 163 | - 社区 PR:[https://github.com/redis/redis/pull/8006](https://github.com/redis/redis/pull/8006) 164 | 165 | ```plaintext 166 | 原来的行为: 167 | SPOP 等命令导致 key 删除/GEOADD/incrbyfloat 等命令如果被慢日志记录,记录的会是 rewrite 后的命令 DEL/ZADD/SET 168 | 169 | 改动后: 170 | 慢日志中始终展示的是真正执行(未经 rewrite 的)命令 171 | ``` 172 | 173 | ## 6.2 版本升级到 7.0 版本 174 | 175 | 1. Lua 脚本不再进行持久化和复制。`SCRIPT LOAD`,`SCRIPT FLUSH` 也不再复制,`lua-replicate-commands` 配置移除,现在 Lua 确定进行效果复制。 176 | - 社区 PR:[https://github.com/redis/redis/pull/9812](https://github.com/redis/redis/pull/9812) 177 | 178 | 2. `SHUTDOWN`/`SIGTERM`/`SIGINT` 触发的退出现在会等待 replica 同步最多 `shutdown-timeout` 秒。 179 | - 社区 PR:[https://github.com/redis/redis/pull/9872](https://github.com/redis/redis/pull/9872) 180 | - 命令文档:[https://redis.io/docs/latest/commands/shutdown/](https://redis.io/docs/latest/commands/shutdown/) 181 | 182 | 3. ACL 默认的 channel 权限从 `allchannels` 改为 `resetchannels`,默认禁止所有 channel 的 Pub/Sub。 183 | - 社区 PR:[https://github.com/redis/redis/pull/10181](https://github.com/redis/redis/pull/10181) 184 | 185 | 4. ACL load 过程中如果有相同的用户,不再静默地以最后一条为准,现在会抛出错误。 186 | - 社区 PR:[https://github.com/redis/redis/pull/9330](https://github.com/redis/redis/pull/9330) 187 | 188 | 5. 过期时间永远以毫秒级绝对时间戳进行复制。 189 | - 社区 PR:[https://github.com/redis/redis/pull/8474](https://github.com/redis/redis/pull/8474) 190 | 191 | 6. 移除 `STRALGO` 命令,添加 `LCS` 作为一个独立的命令。 192 | - 社区 PR:[https://github.com/redis/redis/pull/9799](https://github.com/redis/redis/pull/9799) 193 | - 命令文档:[https://redis.io/docs/latest/commands/lcs/](https://redis.io/docs/latest/commands/lcs/) 194 | 195 | 7. 增加了三个 immutable 配置 `enable-protected-configs`, `enable-debug-command`, `enable-module-command`, 默认值均为 no,分别默认禁止修改带有 `PROTECTED_CONFIG` 属性的配置,默认禁止使用 `DEBUG` 命令,默认禁止使用 `MODULE` 命令。 196 | - 社区 PR:[https://github.com/redis/redis/pull/9920](https://github.com/redis/redis/pull/9920) 197 | 198 | 8. 禁止 `SAVE`, `PSYNC`, `SYNC`, `SHUTDOWN` 命令在事务中执行;事务中的 `BGSAVE`, `BGREWRITEAOF`, `CONFIG SET appendonly` 命令会延迟到事务结束后进行。 199 | - 社区 PR:[https://github.com/redis/redis/pull/10015](https://github.com/redis/redis/pull/10015) 200 | 201 | 9. `ZPOPMIN`, `ZPOPMAX` 命令当 key 的类型错误或输入的 count 参数为负数时,会返回错误而不是空数组。 202 | - 社区 PR:[https://github.com/redis/redis/pull/9711](https://github.com/redis/redis/pull/9711) 203 | 204 | 10. `CONFIG REWRITE` 命令会将已经加载的 module rewrite 为 `loadmodule` 写入文件中。 205 | - 社区 PR:[https://github.com/redis/redis/pull/4848](https://github.com/redis/redis/pull/4848) 206 | 207 | 11. `X[AUTO]CLAIM` 当消息已经被删除时,不再返回 nil,`XCLAIM` 会自动将已经删除的消息从 PEL 中移除,`XAUTOCLAIM` 的返回值会新增已删除消息 ID 列表。 208 | - 社区 PR:[https://github.com/redis/redis/pull/10227](https://github.com/redis/redis/pull/10227) 209 | - 命令文档:[https://redis.io/docs/latest/commands/xclaim/](https://redis.io/docs/latest/commands/xclaim/), [https://redis.io/docs/latest/commands/xautoclaim/](https://redis.io/docs/latest/commands/xautoclaim/) 210 | 211 | ```plaintext 212 | 原来的行为: 213 | > XADD x 1 f1 v1 214 | "1-0" 215 | > XADD x 2 f1 v1 216 | "2-0" 217 | > XADD x 3 f1 v1 218 | "3-0" 219 | > XGROUP CREATE x grp 0 220 | OK 221 | > XREADGROUP GROUP grp Alice COUNT 2 STREAMS x > 222 | 1) 1) "x" 223 | 2) 1) 1) "1-0" 224 | 2) 1) "f1" 225 | 2) "v1" 226 | 2) 1) "2-0" 227 | 2) 1) "f1" 228 | 2) "v1" 229 | > XDEL x 1 2 230 | (integer) 2 231 | > XCLAIM x grp Bob 0 0-99 1-0 1-99 2-0 232 | 1) (nil) 233 | 2) (nil) 234 | > XPENDING x grp 235 | 1) (integer) 2 236 | 2) "1-0" 237 | 3) "2-0" 238 | 4) 1) 1) "Bob" 239 | 2) "2" 240 | 241 | 改动后: 242 | > 到 XDEL 为止都相同 243 | > XCLAIM x grp Bob 0 0-99 1-0 1-99 2-0 244 | (empty array) 245 | > XPENDING x grp 246 | 1) (integer) 0 247 | 2) (nil) 248 | 3) (nil) 249 | 4) (nil) 250 | ``` 251 | 252 | 12. `XREADGROUP` 命令使用 block 参数,导致客户端因此阻塞时,如果 Stream key 被删除,会唤醒 block 的客户端。 253 | - 社区 PR:[https://github.com/redis/redis/pull/10306](https://github.com/redis/redis/pull/10306) 254 | 255 | 13. `SORT`/`SORT_RO` 命令在用户没有所有 key 的读权限时,使用 `BY`/`GET` 参数会报错。 256 | - 社区 PR:[https://github.com/redis/redis/pull/10340](https://github.com/redis/redis/pull/10340) 257 | 258 | 14. 当 replica 持久化失败时,会 panic 退出而不是继续执行主节点发来的命令。添加了配置项 `replica-ignore-disk-write-errors`,默认值为 0。设置为 1 会忽略持久化失败,和低版本行为一致。 259 | - 社区 PR:[https://github.com/redis/redis/pull/10504](https://github.com/redis/redis/pull/10504) 260 | 261 | 15. 移除了 Lua 中的 `print()` 函数,需要使用 `redis.log` 代替。 262 | - 社区 PR:[https://github.com/redis/redis/pull/10651](https://github.com/redis/redis/pull/10651) 263 | 264 | 16. `PFCOUNT` 和 `PUBLISH` 命令禁止在只读脚本中调用,因为它们可能产生复制流量。 265 | - 社区 PR:[https://github.com/redis/redis/pull/10744](https://github.com/redis/redis/pull/10744) 266 | 267 | 17. 命令的统计以子命令为单位,`INFO commandstats` 命令返回具体每个子命令的信息,比如现在会具体展示 `CLIENT LIST`、`CLIENT TRACKING` 命令的统计信息而不是笼统的展示 `CLIENT` 命令。 268 | - 社区 PR:[https://github.com/redis/redis/pull/9504](https://github.com/redis/redis/pull/9504) 269 | 270 | 18. 引入了 Multi Part AOF 机制,现在 AOF 文件夹中将会有 Meta、BASE AOF 和 INCR AOF 三种文件,文件夹路径由配置 `appenddirname` 决定。 271 | - 社区 PR:[https://github.com/redis/redis/pull/9788](https://github.com/redis/redis/pull/9788) 272 | 273 | ## 7.0 版本升级到 7.2 版本 274 | 275 | 1. 开启了 Client Tracking 的客户端执行 Lua 脚本时,跟踪的 key 由 Lua 脚本声明的 key 变为脚本中的具体命令实际读取的 key。 276 | - 社区 PR:[https://github.com/redis/redis/pull/11770](https://github.com/redis/redis/pull/11770) 277 | 278 | ```plaintext 279 | 原来的行为: 280 | client A> CLIENT TRACKING on 281 | client A> EVAL "redis.call('get', 'key2')" 2 key1 key2 282 | 283 | client B> MSET key1 1 key2 2 284 | 285 | client A> -> invalidate: 'key1' 286 | client A> -> invalidate: 'key2' 287 | 288 | 改动后: 289 | client A> CLIENT TRACKING on 290 | client A> EVAL "redis.call('get', 'key2')" 2 key1 key2 291 | 292 | client B> MSET key1 1 key2 2 293 | 294 | client A> -> invalidate: 'key2' 295 | ``` 296 | 297 | 2. 所有命令(包括一个 Lua 脚本内部)在执行过程中使用快照时间,所看到的时间不再变化,典型的例子是不能在一个 Lua 脚本中等待一个 key 过期。 298 | - 社区 PR:[https://github.com/redis/redis/pull/10300](https://github.com/redis/redis/pull/10300) 299 | 300 | 3. ACL 不再移除冗余的权限变更记录,所有的操作记录都将被记录,影响 `ACL SAVE`, `ACL GETUSER`, `ACL LIST` 命令的输出。 301 | - 社区 PR:[https://github.com/redis/redis/pull/11224](https://github.com/redis/redis/pull/11224) 302 | 303 | 4. `XREADGROUP` 和 `X[AUTO]CLAIM` 命令无论是否读取/声明成功,都会创建消费者。 304 | - 社区 PR:[https://github.com/redis/redis/pull/11099](https://github.com/redis/redis/pull/11099) 305 | 306 | 5. `XREADGROUP` 命令在没有读取到消息时,会 reset 该 consumer 的 idle 字段,同时增加 active-time 字段记录 consumer 上次成功读取到消息的时间。 307 | - 社区 PR:[https://github.com/redis/redis/pull/11099](https://github.com/redis/redis/pull/11099) 308 | 309 | 6. 当阻塞命令解除阻塞时,会重新检查进行 ACL 权限、内存限制等检查,并根据解除阻塞后,重新执行命令时的情况区分错误码。 310 | - 社区 PR:[https://github.com/redis/redis/pull/11012](https://github.com/redis/redis/pull/11012) 311 | 312 | ```plaintext 313 | 原来的行为: 314 | ACL、OOM check --> block command --> unblock --> execute 315 | 316 | 改动后: 317 | ACL、OOM check --> block command --> unblock --> ACL、OOM recheck --> execute 318 | ``` 319 | 320 | # 重要 Bugfix: 321 | 322 | ## 5.0 版本 323 | 324 | 1. 修复了判断 key 过期的逻辑,防止命令执行过程中,key 因为过期被删除导致的 UAF crash 问题(4.0 及以下版本仍有影响) 325 | - 社区 PR:[https://github.com/redis/redis/commit/68d71d83](https://github.com/redis/redis/commit/68d71d83) 326 | 327 | 2. 修复了当 AOF 刷盘策略设置为 everysec 时,有可能出现最后一秒内的数据没有落盘导致数据丢失的 Bug(4.0 及以下版本仍有影响) 328 | - 社区 PR:[https://github.com/redis/redis/commit/c6b1252f](https://github.com/redis/redis/commit/c6b1252f) 329 | 330 | 3. 修复了事务中的命令错误统计到了 exec 命令上的 Bug(4.0 及以下版本仍有影响) 331 | - 社区 PR:[https://github.com/redis/redis/commit/d6aeca86](https://github.com/redis/redis/commit/d6aeca86) 332 | 333 | ## 6.0 版本 334 | 335 | 1. 修复了 `KEYS` 命令使用以 `\*\0` 开头的模式时,会返回所有 key 的 Bug(backport to 5.0,4.0 及以下版本仍有影响) 336 | - 社区 PR:[https://github.com/redis/redis/commit/c7f75266](https://github.com/redis/redis/commit/c7f75266) 337 | 338 | 2. 当字符串中包含 `\0` 时,尝试将该字符串转换为浮点类型的操作应该报错,影响 `HINCRBYFLOAT` 命令(5.0 及以下版本仍有影响) 339 | - 社区 PR:[https://github.com/redis/redis/commit/6fe55c2f](https://github.com/redis/redis/commit/6fe55c2f) 340 | 341 | 3. `-READONLY` 报错应该中断事务(5.0 及以下版本仍有影响) 342 | - 社区 PR:[https://github.com/redis/redis/commit/8783304a](https://github.com/redis/redis/commit/8783304a) 343 | 344 | ## 6.2 版本 345 | 346 | 1. `EXISTS` 命令不再改变 LRU,`OBJECT` 命令不会展示已经过期的 key 的信息。(backport to 6.0,5.0 及以下仍有影响) 347 | - 社区 PR:[https://github.com/redis/redis/pull/8016](https://github.com/redis/redis/pull/8016) 348 | 349 | 2. 修复当从哈希表中随机挑选一个元素的操作只能支持 0 ~ 2^31 - 1 的范围,当哈希表元素超过 2^31 时,影响逐出、`RANDOMKEY`、`SRANDMEMBER` 等行为的公平性。(backport to 6.0,5.0 及以下版本仍有影响) 350 | - 社区 PR:[https://github.com/redis/redis/pull/8133](https://github.com/redis/redis/pull/8133) 351 | 352 | 3. `SMOVE` 命令没有改变目标 key 时,不通知 `WATCH` 和 `CLIENT TRACKING`。(backport to 6.0,5.0 及以下版本仍有影响) 353 | - 社区 PR:[https://github.com/redis/redis/pull/9244](https://github.com/redis/redis/pull/9244) 354 | 355 | 4. 修复了在 pipeline 中使用超时的 Lua 脚本时,有可能导致服务端不再处理 pipeline 中后续的其它命令,直到该连接有新的消息。(6.0 及以下版本仍有影响) 356 | - 社区 PR:[https://github.com/redis/redis/pull/8715](https://github.com/redis/redis/pull/8715) 357 | 358 | 5. `SCRIPT KILL` 能够终止 Lua 中的 pcall。原来如果 Lua 中一直在执行 pcall,那么 `SCRIPT KILL` 产生的 error 就会被 pcall 捕获,导致脚本不会停止,现在通过在 Lua VM 层面产生 error,来终止 Lua 执行。(6.0 及以下版本仍有影响) 359 | - 社区 PR:[https://github.com/redis/redis/pull/8661](https://github.com/redis/redis/pull/8661) 360 | 361 | ## 7.0 版本 362 | 363 | 1. 修复了用户参数过大导致触发 Lua 栈溢出的 assert。(backport to 6.0,5.0 版本仍有影响) 364 | - 社区 PR:[https://github.com/redis/redis/pull/9809](https://github.com/redis/redis/pull/9809) 365 | 366 | 2. 修复了使用 `list-compress-depth` 配置时可能出现的 crash。(6.2 及以下版本仍有影响) 367 | - 社区 PR:[https://github.com/redis/redis/pull/9849](https://github.com/redis/redis/pull/9849) 368 | 369 | 3. 修复了当单个 String/List 超过 4GB 时,开启 `rdbcompression` 配置生产 RDB 时会导致 RDB 损坏的问题。(6.2 及以下版本仍有影响) 370 | - 社区 PR:[https://github.com/redis/redis/pull/9776](https://github.com/redis/redis/pull/9776) 371 | 372 | 4. 修复了当向 Set/Hash 添加超过 2GB 大小的元素时,会导致 crash 的 Bug。(6.2 及以下版本仍有影响) 373 | - 社区 PR:[https://github.com/redis/redis/pull/9916](https://github.com/redis/redis/pull/9916) 374 | 375 | 5. 当设置极大或极小的过期时间,导致过期时间溢出时应该报错。(backport to 6.2,6.0 及以下版本仍有影响) 376 | - 社区 PR:[https://github.com/redis/redis/pull/8287](https://github.com/redis/redis/pull/8287) 377 | 378 | 6. 当 `DECRBY` 命令输入 `LLONG_MIN` 时应该报错而不是导致溢出。(6.2 及以下版本仍有影响) 379 | - 社区 PR:[https://github.com/redis/redis/pull/9577](https://github.com/redis/redis/pull/9577) 380 | 381 | 7. 修复了 ZSet 元素数量大于 `UINT32_MAX` 时,可能出现 rank 计算溢出。(6.2 及以下版本仍有影响) 382 | - 社区 PR:[https://github.com/redis/redis/pull/9249](https://github.com/redis/redis/pull/9249) 383 | 384 | 8. 修复了 Lua 中的写命令会无视 `CLIENT TRACKING NOLOOP` 的 Bug。(6.0 版本仍有影响) 385 | - 社区 PR:[https://github.com/redis/redis/pull/11052](https://github.com/redis/redis/pull/11052) 386 | 387 | 9. 修复了 `CLIENT TRACKING` 开启时,key 失效的 push 包有可能穿插在当前连接执行其它命令的回包之间(backport to 6.2,6.0 版本仍有影响) 388 | - 社区 PR:[https://github.com/redis/redis/pull/11038](https://github.com/redis/redis/pull/11038), [https://github.com/redis/redis/pull/9422](https://github.com/redis/redis/pull/9422) 389 | 390 | 10. 当 `WATCH` 的 key 在 `EXEC` 执行时已经过期,无论是否被删除,事务都应该失败。另外在事务执行期间,事务中 server 用于判断是否过期的时间戳不应该更新。(backport to 6.0,5.0 及以下版本仍有影响) 391 | - 社区 PR:[https://github.com/redis/redis/pull/9194](https://github.com/redis/redis/pull/9194) 392 | 393 | 11. 修复了 `SINTERSTORE` 命令操作的 key 类型不对时没有返回 `WRONGTYPE` 错误,在特殊情况下有可能导致目标 key 被删除的 Bug(backport to 6.0,5.0 及以下版本仍有影响) 394 | - 社区 PR:[https://github.com/redis/redis/pull/9032](https://github.com/redis/redis/pull/9032) 395 | 396 | 12. 修复了客户端内存超过 `client-output-buffer-limit` 的 soft limit 后,如果一直没有流量,超过了 soft limit timeout 后仍然不会被断连接的 Bug。(backport to 6.2,6.0 及以下版本仍有影响) 397 | - 社区 PR:[https://github.com/redis/redis/pull/8833](https://github.com/redis/redis/pull/8833) 398 | 399 | 13. 修复了 `XREADGROUP`, `XCLAIM`, `XAUTOCLAIM` 命令创建消费者时,没有发出 keyspace notification;`XGROUP DELCONSUMER` 命令删除不存在的消费者时,不应该发出 keyspace notification。(6.2 及以下版本仍有影响) 400 | - 社区 PR:[https://github.com/redis/redis/pull/9263](https://github.com/redis/redis/pull/9263) 401 | 402 | 14. 修复了 `GEO` 命令在 search 时,有可能遗漏掉比较靠近搜索边界的点的 Bug。(6.0 及以下版本仍有影响) 403 | - 社区 PR:[https://github.com/redis/redis/pull/10018](https://github.com/redis/redis/pull/10018) 404 | 405 | 15. 修复了在 Lua 的返回值处理过程中,使用 metatable 时如果触发错误会导致 server crash 问题。(6.0 及以下版本仍有影响) 406 | - 社区 PR:[https://github.com/redis/redis/pull/11032](https://github.com/redis/redis/pull/11032) 407 | 408 | 16. 修复了以秒为单位的 Blocking 命令(比如 `BLPOP`/`BRPOP`)在 timeout 小于 0.001 秒时有可能被解析为 0 导致永久阻塞的 bug(6.2 及以下版本仍有影响)。 409 | - 社区 PR:[https://github.com/redis/redis/pull/11688](https://github.com/redis/redis/pull/11688) 410 | 411 | 17. 修复了恶意的 `KEYS`, `HRANDFIELD`, `SRANDMEMBER`, `ZRANDMEMBER` 命令可能导致 server hang 死,(`KEYS` 仍有可能在 6.2 及以下版本造成 server hang 死)。 412 | - 社区 PR:[https://github.com/redis/redis/pull/11676](https://github.com/redis/redis/pull/11676) 413 | 414 | 18. Lua 脚本安全性修复,禁止修改已有的全局变量和标准库函数。(backport to 6.2,6.0 及以下版本仍有风险) 415 | - 社区 PR:https://github.com/redis/redis/pull/10651 416 | 417 | ## 7.2 版本 418 | 419 | 1. 修复了 `SRANDMEMBER`, `ZRANDMEMBER`, `HRANDFIELD` 命令在使用 `count` 参数时,有小概率死循环的 Bug(backport to 7.0,6.2 及以下版本仍有问题) 420 | - 社区 PR:[https://github.com/redis/redis/pull/12276](https://github.com/redis/redis/pull/12276) 421 | 422 | 2. `HINCRBYFLOAT` 命令在解析 increment 参数失败后不会创建 key(backport to 6.0,5.0 及以下版本仍有问题) 423 | - 社区 PR:[https://github.com/redis/redis/pull/11149](https://github.com/redis/redis/pull/11149) 424 | - 命令文档:[https://redis.io/docs/latest/commands/hincrbyfloat/](https://redis.io/docs/latest/commands/hincrbyfloat/) 425 | 426 | 3. `LSET` 命令将 List 中大量的小元素换成大元素时,极端情况下可能导致 crash(7.0 版本仍有问题) 427 | - 社区 PR:[https://github.com/redis/redis/pull/12955](https://github.com/redis/redis/pull/12955) 428 | - 命令文档:[https://redis.io/docs/latest/commands/lset/](https://redis.io/docs/latest/commands/lset/) 429 | -------------------------------------------------------------------------------- /compatibility_report_en_US.md: -------------------------------------------------------------------------------- 1 | # Redis Major Version Compatibility Report 2 | 3 | This document is compiled from the official Release Notes of the Redis community, insights from code reviews, and operational experience from the Alibaba Cloud Tair Team. It is intended to serve as a technical reference for Redis users to evaluate compatibility risks before undertaking major version upgrades. If you find any omissions, please open an issue to supplement this report. Accepted contributions will be acknowledged by adding your name to the list of contributors. 4 | 5 | # User-Facing Compatibility Differences 6 | 7 | ## Upgrading from Version 4.0 to 5.0 8 | 9 | 1. **`KEYS` Command Behavior:** The `KEYS` command no longer actively deletes expired keys it encounters during traversal; it only skips them. It can no longer be used as a mechanism for active expiration. The `SCAN` command, however, retains its behavior of deleting expired keys it finds. 10 | - PR: [https://github.com/redis/redis/commit/edc47a3a](https://github.com/redis/redis/commit/edc47a3a) 11 | 12 | 2. **Lua Script Replication:** Lua scripts now default to effect replication (replicating the resulting commands) instead of script replication. This change prevents data divergence between primary and replica instances. This behavior can be reverted by modifying the `lua-replicate-commands` configuration (removed in 7.0) or using the `DEBUG lua-always-replicate-commands` command. 13 | - PR: [https://github.com/redis/redis/commit/3e1fb5ff](https://github.com/redis/redis/commit/3e1fb5ff) 14 | 15 | ```plaintext 16 | // 1. Non-deterministic commands can yield different results. 17 | local key = KEYS[1] 18 | local member = redis.call('SPOP', key) 19 | redis.call('SET', 'last_removed', member) 20 | return member 21 | 22 | // 2. Different execution paths due to expiration/eviction. 23 | local key = KEYS[1] 24 | local value = redis.call('GET', key) 25 | if value then 26 | redis.call('SET', key, tonumber(value) + 1) 27 | else 28 | redis.call('SET', key, 1) 29 | end 30 | ``` 31 | 32 | 3. **Read-only Script Replication:** Read-only scripts are now replicated as `SCRIPT LOAD` commands, not `EVAL`. 33 | - PR: [https://github.com/redis/redis/commit/dfbce91a](https://github.com/redis/redis/commit/dfbce91a) 34 | 35 | 4. **Replica `maxmemory` Policy:** Replicas now ignore the `maxmemory` setting by default and will not perform data eviction. The previous behavior can be restored by setting `replica-ignore-maxmemory` to `no`. 36 | - PR: [https://github.com/redis/redis/commit/447da44d](https://github.com/redis/redis/commit/447da44d) 37 | 38 | 5. **Shared Object Consistency:** When the `volatile-lru` eviction policy is active, Redis avoids using shared integer objects to ensure accurate LRU metadata. However, replicas loading an RDB file during a full sync did not apply this logic, resulting in the use of shared objects. This caused a memory representation mismatch where replicas consumed less memory than the primary for the same dataset. This has been fixed in Redis 5.0. After upgrading, you may observe that replica memory usage increases to match the primary's. 39 | - MRs: 40 | - [https://github.com/redis/redis/commit/bd92389c2dc1afcf53218880fc31ba45e66f4ded](https://github.com/redis/redis/commit/bd92389c2dc1afcf53218880fc31ba45e66f4ded) 41 | - [https://github.com/redis/redis/commit/0ed0dc3c02dfffdf6bfb9e32f1335ddc34f37723](https://github.com/redis/redis/commit/0ed0dc3c02dfffdf6bfb9e32f1335ddc34f37723) 42 | 43 | ## Upgrading from Version 5.0 to 6.0 44 | 45 | 1. **`OBJECT ENCODING` for Stream:** The `OBJECT ENCODING` command now correctly returns the encoding type for stream, instead of "unknown." 46 | - PR: [https://github.com/redis/redis/pull/7797](https://github.com/redis/redis/pull/7797) 47 | 48 | 2. **Read-only Transactions in Cluster:** In cluster mode, read-only transactions can now be executed on replicas instead of being redirected to the primary with a `MOVED` error. 49 | - PR: [https://github.com/redis/redis/pull/7766](https://github.com/redis/redis/pull/7766) 50 | 51 | ```plaintext 52 | // Previous Behavior: 53 | connect to a replica in cluster 54 | > readonly 55 | > get k 56 | "v" 57 | > multi 58 | > get k 59 | > exec 60 | (error) MOVED 61 | 62 | // New Behavior: 63 | connect to a replica in cluster 64 | > readonly 65 | > get k 66 | "v" 67 | > multi 68 | > get k 69 | > exec 70 | "v" 71 | ``` 72 | 73 | 3. **Blocking Command Timeout Precision:** The `timeout` parameter for `BRPOP`, `BLPOP`, and `BRPOPLPUSH` was changed from an integer (seconds) to a float number (seconds). This introduced a bug where a timeout value less than or equal to 0.001 seconds was parsed as 0, causing an indefinite block. This bug was fixed in Redis 7.0. 74 | - Docs: [https://redis.io/docs/latest/commands/brpop/](https://redis.io/docs/latest/commands/brpop/), [https://redis.io/docs/latest/commands/blpop/](https://redis.io/docs/latest/commands/blpop/), [https://redis.io/docs/latest/commands/brpoplpush/](https://redis.io/docs/latest/commands/brpoplpush/) 75 | 76 | 4. **Blocking Pop Timeout Precision:** The `timeout` parameter for `BZPOPMIN` and `BZPOPMAX` was also changed from an integer to a float, introducing the same indefinite block bug for small timeout values, which was fixed in 7.0. 77 | - Docs: [https://redis.io/docs/latest/commands/bzpopmin/](https://redis.io/docs/latest/commands/bzpopmin/), [https://redis.io/docs/latest/commands/bzpopmax/](https://redis.io/docs/latest/commands/bzpopmax/) 78 | 79 | 5. **String Length Limits:** The maximum string size for `SETRANGE` and `APPEND` is now determined by the `proto_max_bulk_len` configuration (default 512MB), rather than a hardcoded 512MB limit. 80 | - PR: [https://github.com/redis/redis/pull/4633](https://github.com/redis/redis/pull/4633) 81 | 82 | ## Upgrading from Version 6.0 to 6.2 83 | 84 | 1. **AOF `fsync=always` Error Handling:** When `appendfsync` is set to `always`, a failed `fsync` operation will now cause the Redis process to terminate immediately. 85 | - PR: [https://github.com/redis/redis/pull/8347](https://github.com/redis/redis/pull/8347) 86 | 87 | 2. **`SRANDMEMBER` RESP3 Return Type:** The RESP3 return type for `SRANDMEMBER` has been changed from a Set to an Array to accommodate duplicate elements, which can be returned when a negative `count` is provided. 88 | - PR: [https://github.com/redis/redis/pull/8504](https://github.com/redis/redis/pull/8504) 89 | 90 | 3. **`PUBSUB NUMPAT` Return Value:** The meaning of the value returned by `PUBSUB NUMPAT` has changed. It now returns the count of unique subscribed patterns, whereas previously it returned the total number of subscribed clients for all patterns. 91 | - PR: [https://github.com/redis/redis/pull/8472](https://github.com/redis/redis/pull/8472) 92 | - Doc: [https://redis.io/docs/latest/commands/pubsub-numpat/](https://redis.io/docs/latest/commands/pubsub-numpat/) 93 | 94 | 4. **`CLIENT TRACKING` with Overlapping Prefixes:** `CLIENT TRACKING` now returns an error if a client attempts to track overlapping prefixes, which prevents duplicate invalidation messages. 95 | - PR: [https://github.com/redis/redis/pull/8176](https://github.com/redis/redis/pull/8176) 96 | 97 | 5. **`SWAPDB` and `WATCH`:** The `SWAPDB` command now touches all keys being watched in both of the swapped databases, causing any transactions watching those keys to fail. 98 | - PR: [https://github.com/redis/redis/pull/8239](https://github.com/redis/redis/pull/8239) 99 | 100 | ```plaintext 101 | // Previous Behavior: Transaction succeeds 102 | client A> select 0 103 | client A> set k 0 104 | client A> select 1 105 | client A[1]> set k 1 106 | client A[1]> watch k 107 | client A[1]> multi 108 | client A[1]> get k 109 | 110 | client B> swapdb 0 1 111 | 112 | client A[1]> exec 113 | "0" 114 | 115 | // New Behavior: Transaction fails 116 | client A> select 0 117 | client A> set k 0 118 | client A> select 1 119 | client A[1]> set k 1 120 | client A[1]> watch k 121 | client A[1]> multi 122 | client A[1]> get k 123 | 124 | client B> swapdb 0 1 125 | 126 | client A[1]> exec 127 | (nil) 128 | ``` 129 | 130 | 6. **`FLUSHDB` and Client Tracking:** `FLUSHDB` now invalidates all keys for all clients that have tracking enabled. 131 | - PR: [https://github.com/redis/redis/pull/8039](https://github.com/redis/redis/pull/8039) 132 | 133 | 7. **BIT ops Length Limit:** The maximum result size for bit ops is now controlled by `proto_max_bulk_len` (default 512MB), not a hardcoded 512MB limit. 134 | - PR: [https://github.com/redis/redis/pull/8096](https://github.com/redis/redis/pull/8096) 135 | 136 | 8. **`bind` Configuration Syntax:** A `-` prefix on an IP address in the `bind` directive now signifies that an `EADDRNOTAVAIL` error for that address should be ignored. Without the prefix, such an error on a non-default address will cause startup to fail. Additionally, `bind *` is now supported as an explicit way to bind to all available network interfaces. 137 | - PR: [https://github.com/redis/redis/pull/7936](https://github.com/redis/redis/pull/7936) 138 | 139 | ```plaintext 140 | // Previous Behavior: 141 | Any bind failure was ignored as long as at least one succeeded. 142 | 143 | // New Behavior: 144 | Binding to the default address (127.0.0.1/::1) will not affect Redis startup if at least one address succeeds. 145 | Binding to a non-default address (e.g., -192.168.1.100) with a '-' prefix will not affect Redis startup if the address fails to bind. Failure to bind to the non-default address without the '-' prefix will cause Redis startup to fail. 146 | ``` 147 | 148 | 9. **`save` Configuration with Command-Line Arguments:** Launching `redis-server` with parameters no longer resets the `save` configuration. Previously, using additional parameters, regardless of whether `save` was included, would cause the save configuration to be reset to "" before loading other additional parameters. 149 | - PR: [https://github.com/redis/redis/pull/7092](https://github.com/redis/redis/pull/7092) 150 | 151 | ```plaintext 152 | // Previous Behavior: 153 | redis-server --other-args xx --> save "" 154 | redis-server [--other-args xx] --save 3600 1 --> save "3600 1" 155 | redis-server --> save "3600 1 300 100 60 10000" 156 | 157 | // New Behavior: 158 | redis-server --other-args xx --> save "3600 1 300 100 60 10000" 159 | redis-server [--other-args xx] --save 3600 1 --> save "3600 1" 160 | redis-server --> save "3600 1 300 100 60 10000" 161 | ``` 162 | 163 | 10. **`SLOWLOG` Command Representation:** `SLOWLOG` now records the original command as executed by the client, not the rewritten version (e.g., `SPOP` is logged as `SPOP`, not `DEL`). 164 | - PR: [https://github.com/redis/redis/pull/8006](https://github.com/redis/redis/pull/8006) 165 | 166 | ## Upgrading from Version 6.2 to 7.0 167 | 168 | 1. **Lua Script Replication Finalized:** Lua scripts are definitively no longer persisted or replicated. The `SCRIPT LOAD` and `SCRIPT FLUSH` commands are also no longer replicated, and the `lua-replicate-commands` configuration has been removed. Effect replication is now the only mode. 169 | - PR: [https://github.com/redis/redis/pull/9812](https://github.com/redis/redis/pull/9812) 170 | 171 | 2. **Synchronous Shutdown:** A shutdown initiated by `SHUTDOWN`, `SIGTERM`, or `SIGINT` now waits for replicas to catch up on replication, with a timeout defined by `shutdown-timeout` (default 10 seconds). 172 | - PR: [https://github.com/redis/redis/pull/9872](https://github.com/redis/redis/pull/9872) 173 | - Command Doc: [https://redis.io/docs/latest/commands/shutdown/](https://redis.io/docs/latest/commands/shutdown/) 174 | 175 | 3. **ACL Default Channel Permissions:** The default user's channel permissions have been changed from `allchannels` to `resetchannels`, effectively disabling all Pub/Sub commands by default until explicitly permitted. 176 | - PR: [https://github.com/redis/redis/pull/10181](https://github.com/redis/redis/pull/10181) 177 | 178 | 4. **`ACL LOAD` with Duplicate Users:** `ACL LOAD` now returns an error if the loaded configuration contains duplicate user definitions, instead of silently applying the last one. 179 | - PR: [https://github.com/redis/redis/pull/9330](https://github.com/redis/redis/pull/9330) 180 | 181 | 5. **Expiration Replication:** Key expiration times are now always replicated as absolute millisecond-precision timestamps. 182 | - PR: [https://github.com/redis/redis/pull/8474](https://github.com/redis/redis/pull/8474) 183 | 184 | 6. **`STRALGO` Command Removed:** The `STRALGO` command has been removed and its functionality promoted to the top-level `LCS` command. 185 | - PR: [https://github.com/redis/redis/pull/9799](https://github.com/redis/redis/pull/9799) 186 | - Doc: [https://redis.io/docs/latest/commands/lcs/](https://redis.io/docs/latest/commands/lcs/) 187 | 188 | 7. **New Security Configuration:** Three new immutable startup configurations have been added: `enable-protected-configs`, `enable-debug-command`, and `enable-module-command`. All default to `no`, providing a more secure-by-default posture by disabling runtime changes to protected configs and access to `DEBUG` and `MODULE` commands. 189 | - PR: [https://github.com/redis/redis/pull/9920](https://github.com/redis/redis/pull/9920) 190 | 191 | 8. **Transaction Command Restrictions:** `SAVE`, `PSYNC`, `SYNC`, and `SHUTDOWN` are now prohibited inside a `MULTI`/`EXEC` block. `BGSAVE`, `BGREWRITEAOF`, and `CONFIG SET appendonly` are deferred until after the transaction executes. 192 | - PR: [https://github.com/redis/redis/pull/10015](https://github.com/redis/redis/pull/10015) 193 | 194 | 9. **`ZPOPMIN`/`ZPOPMAX` Error Handling:** These commands now return a `WRONGTYPE` error for non-sorted-set keys and an error for a negative `count`, instead of returning an empty array. 195 | - PR: [https://github.com/redis/redis/pull/9711](https://github.com/redis/redis/pull/9711) 196 | 197 | 10. **`CONFIG REWRITE` and Modules:** `CONFIG REWRITE` now persists currently loaded modules to the configuration file using `loadmodule` directives. 198 | - PR: [https://github.com/redis/redis/pull/4848](https://github.com/redis/redis/pull/4848) 199 | 200 | 11. **`X[AUTO]CLAIM` for Deleted Messages:** `XCLAIM` and `XAUTOCLAIM` now handle cases where a pending message has been deleted from the stream. Instead of returning `nil`, they now remove the message from the Pending Entries List (PEL). `XAUTOCLAIM` also returns a list of deleted message IDs. 201 | - PR: [https://github.com/redis/redis/pull/10227](https://github.com/redis/redis/pull/10227) 202 | - Docs: [https://redis.io/docs/latest/commands/xclaim/](https://redis.io/docs/latest/commands/xclaim/), [https://redis.io/docs/latest/commands/xautoclaim/](https://redis.io/docs/latest/commands/xautoclaim/) 203 | 204 | ```plaintext 205 | // Previous Behavior: 206 | > XADD x 1 f1 v1 207 | "1-0" 208 | > XADD x 2 f1 v1 209 | "2-0" 210 | > XADD x 3 f1 v1 211 | "3-0" 212 | > XGROUP CREATE x grp 0 213 | OK 214 | > XREADGROUP GROUP grp Alice COUNT 2 STREAMS x > 215 | 1) 1) "x" 216 | 2) 1) 1) "1-0" 217 | 2) 1) "f1" 218 | 2) "v1" 219 | 2) 1) "2-0" 220 | 2) 1) "f1" 221 | 2) "v1" 222 | > XDEL x 1 2 223 | (integer) 2 224 | > XCLAIM x grp Bob 0 0-99 1-0 1-99 2-0 225 | 1) (nil) 226 | 2) (nil) 227 | > XPENDING x grp 228 | 1) (integer) 2 229 | 2) "1-0" 230 | 3) "2-0" 231 | 4) 1) 1) "Bob" 232 | 2) "2" 233 | 234 | // New Behavior: 235 | > Same until XDEL 236 | > XCLAIM x grp Bob 0 0-99 1-0 1-99 2-0 237 | (empty array) 238 | > XPENDING x grp 239 | 1) (integer) 0 240 | 2) (nil) 241 | 3) (nil) 242 | 4) (nil) 243 | ``` 244 | 245 | 12. **`XREADGROUP` Unblocking:** A client blocked on `XREADGROUP` will now be unblocked if the target stream key is deleted. 246 | - PR: [https://github.com/redis/redis/pull/10306](https://github.com/redis/redis/pull/10306) 247 | 248 | 13. **`SORT` with `BY`/`GET` Permissions:** `SORT` and `SORT_RO` now require read permissions for all external keys referenced via `BY` or `GET` patterns. 249 | - PR: [https://github.com/redis/redis/pull/10340](https://github.com/redis/redis/pull/10340) 250 | 251 | 14. **Replica Persistence Errors:** A replica will now panic and exit if it fails to do persistence operations, preventing it from continuing with a potentially diverged dataset. Set `replica-ignore-disk-write-errors yes` to restore the old behavior (default is no). 252 | - PR: [https://github.com/redis/redis/pull/10504](https://github.com/redis/redis/pull/10504) 253 | 254 | 15. **Lua `print` Function Removed:** The `print()` function in Lua scripts is removed. Use `redis.log(...)` for logging. 255 | - PR: [https://github.com/redis/redis/pull/10651](https://github.com/redis/redis/pull/10651) 256 | 257 | 16. **Read-only Script Command Restrictions:** `PFCOUNT` and `PUBLISH` are now disallowed in read-only scripts, as they can have side effects that must be replicated. 258 | - PR: [https://github.com/redis/redis/pull/10744](https://github.com/redis/redis/pull/10744) 259 | 260 | 17. **Subcommand Statistics:** Command statistics are now tracked at the subcommand level. For example, `INFO commandstats` will show separate entries for `CLIENT LIST` and `CLIENT TRACKING` instead of a single entry for `CLIENT`. 261 | - PR: [https://github.com/redis/redis/pull/9504](https://github.com/redis/redis/pull/9504) 262 | 263 | 18. **Multi-Part AOF:** A new AOF format has been introduced. The AOF is now stored as a directory (path configured by `appenddirname`) containing a manifest, a base AOF file, and incremental AOF files. 264 | - PR: [https://github.com/redis/redis/pull/9788](https://github.com/redis/redis/pull/9788) 265 | 266 | ## Upgrading from Version 7.0 to 7.2 267 | 268 | 1. **Client Tracking in Lua Scripts:** When Client Tracking is enabled, tracking is now based on the keys actually accessed by commands within a Lua script, rather than all keys declared in the `EVAL` call. 269 | - PR: [https://github.com/redis/redis/pull/11770](https://github.com/redis/redis/pull/11770) 270 | 271 | ```plaintext 272 | // Previous Behavior: Tracks all declared keys 273 | client A> CLIENT TRACKING on 274 | client A> EVAL "redis.call('get', 'key2')" 2 key1 key2 275 | 276 | client B> MSET key1 1 key2 2 277 | 278 | client A> -> invalidate: 'key1' 279 | client A> -> invalidate: 'key2' 280 | 281 | // New Behavior: Tracks only accessed keys 282 | client A> CLIENT TRACKING on 283 | client A> EVAL "redis.call('get', 'key2')" 2 key1 key2 284 | 285 | client B> MSET key1 1 key2 2 286 | 287 | client A> -> invalidate: 'key2' 288 | ``` 289 | 290 | 2. **Time Caching within Commands:** Command execution now uses a "time snapshot." The server time is cached at the beginning of a command's execution (or a Lua script) and remains constant throughout. A typical example is that you cannot wait for a key to expire in a Lua script. 291 | - PR: [https://github.com/redis/redis/pull/10300](https://github.com/redis/redis/pull/10300) 292 | 293 | 3. **ACL Verbosity:** ACL now records all permission modifications, even redundant ones. This affects the output of `ACL SAVE`, `ACL GETUSER`, and `ACL LIST`. 294 | - PR: [https://github.com/redis/redis/pull/11224](https://github.com/redis/redis/pull/11224) 295 | 296 | 4. **Stream Consumer Creation:** `XREADGROUP` and `X[AUTO]CLAIM` now create a consumer in the consumer group even if no messages are successfully read or claimed. 297 | - PR: [https://github.com/redis/redis/pull/11099](https://github.com/redis/redis/pull/11099) 298 | 299 | 5. **Stream Consumer Idle Time:** When the XREADGROUP command does not read any message, it resets the idle field of the consumer and adds the active-time field to record the time when the consumer last successfully read a message. 300 | - PR: [https://github.com/redis/redis/pull/11099](https://github.com/redis/redis/pull/11099) 301 | 302 | 6. **Re-checking on Unblocking:** When a client unblocks from a blocking command, security and resource limit checks (e.g., ACL, OOM) are performed again before the command proceeds. 303 | - PR: [https://github.com/redis/redis/pull/11012](https://github.com/redis/redis/pull/11012) 304 | 305 | ```plaintext 306 | // Previous Behavior: 307 | // Check ACL/OOM -> Block -> Unblock -> Execute 308 | 309 | // New Behavior: 310 | // Check ACL/OOM -> Block -> Unblock -> Re-check ACL/OOM -> Execute 311 | ``` 312 | 313 | # Important Bugfixes 314 | 315 | ## Version 5.0 316 | 317 | 1. **UAF Crash on Key Expiration:** Fixed a critical Use-After-Free (UAF) vulnerability where a key could expire and be deleted during command execution, leading to a server crash. (Affects 4.0 and earlier) 318 | - PR: [https://github.com/redis/redis/commit/68d71d83](https://github.com/redis/redis/commit/68d71d83) 319 | 320 | 2. **AOF `everysec` Data Loss:** Fixed a bug when the AOF flushing policy is set to everysec, data within the last second may not be flushed to disk, resulting in data loss. (Affects 4.0 and earlier) 321 | - PR: [https://github.com/redis/redis/commit/c6b1252f](https://github.com/redis/redis/commit/c6b1252f) 322 | 323 | 3. **Transaction Error Stats:** Fixed a bug where errors from commands within a transaction were incorrectly attributed to the `EXEC` command in command statistics. (Affects 4.0 and earlier) 324 | - PR: [https://github.com/redis/redis/commit/d6aeca86](https://github.com/redis/redis/commit/d6aeca86) 325 | 326 | ## Version 6.0 327 | 328 | 1. **`KEYS` Pattern with Null Byte:** Fixed a bug where a `KEYS` pattern starting with `\*\0` would match all keys in the database. (Backported to 5.0; affects 4.0 and earlier) 329 | - PR: [https://github.com/redis/redis/commit/c7f75266](https://github.com/redis/redis/commit/c7f75266) 330 | 331 | 2. **Float Conversion with Null Byte:** String-to-float conversions now correctly fail if the string contains a null byte (`\0`). This affects commands like `HINCRBYFLOAT`. (Affects 5.0 and earlier) 332 | - PR: [https://github.com/redis/redis/commit/6fe55c2f](https://github.com/redis/redis/commit/6fe55c2f) 333 | 334 | 3. **`-READONLY` Error in Transactions:** A `-READONLY` error within a transaction now correctly aborts the transaction. (Affects 5.0 and earlier) 335 | - PR: [https://github.com/redis/redis/commit/8783304a](https://github.com/redis/redis/commit/8783304a) 336 | 337 | ## Version 6.2 338 | 339 | 1. **`EXISTS`/`OBJECT` and LRU:** `EXISTS` no longer modifies a key's LRU value, and `OBJECT` no longer returns information for expired keys. (Backported to 6.0; affects 5.0 and earlier) 340 | - PR: [https://github.com/redis/redis/pull/8016](https://github.com/redis/redis/pull/8016) 341 | 342 | 2. **Hash Table Sampling Fairness:** Fixed a 32-bit limitation in random element sampling from hash tables, which impacted the fairness of eviction and commands like `RANDOMKEY` and `SRANDMEMBER` on large hashes. (Backported to 6.0; affects 5.0 and earlier) 343 | - PR: [https://github.com/redis/redis/pull/8133](https://github.com/redis/redis/pull/8133) 344 | 345 | 3. **`SMOVE` Notifications:** `SMOVE` no longer generates `WATCH` or `CLIENT TRACKING` notifications if the move is a no-op. (Backported to 6.0; affects 5.0 and earlier) 346 | - PR: [https://github.com/redis/redis/pull/9244](https://github.com/redis/redis/pull/9244) 347 | 348 | 4. **Pipeline Stall after Lua Timeout:** Fixed an issue where a timed-out Lua script in a pipeline could cause the server to stop processing subsequent commands from that client until new data arrived. (Affects 6.0 and earlier) 349 | - PR: [https://github.com/redis/redis/pull/8715](https://github.com/redis/redis/pull/8715) 350 | 351 | 5. **`SCRIPT KILL` and `pcall`:** `SCRIPT KILL` can now successfully terminate a script that is inside a `pcall` block. (Affects 6.0 and earlier) 352 | - PR: [https://github.com/redis/redis/pull/8661](https://github.com/redis/redis/pull/8661) 353 | 354 | 355 | ## Version 7.0 356 | 357 | 1. **Lua Stack Overflow:** Fixed an assertion failure caused by a stack overflow when passing an extremely large number of arguments to a Lua script. (Backported to 6.0; affects 5.0) 358 | - PR: [https://github.com/redis/redis/pull/9809](https://github.com/redis/redis/pull/9809) 359 | 360 | 2. **`list-compress-depth` Crash:** Fixed a potential crash when using the `list-compress-depth` configuration. (Affects 6.2 and earlier) 361 | - PR: [https://github.com/redis/redis/pull/9849](https://github.com/redis/redis/pull/9849) 362 | 363 | 3. **RDB Corruption with Large Items:** Fixed RDB file corruption when `rdbcompression` is enabled and the RDB contains a String or List value larger than 4GB. (Affects 6.2 and earlier) 364 | - PR: [https://github.com/redis/redis/pull/9776](https://github.com/redis/redis/pull/9776) 365 | 366 | 4. **Crash on Large Set/Hash Elements:** Fixed a crash when adding an element larger than 2GB to a Set or Hash. (Affects 6.2 and earlier) 367 | - PR: [https://github.com/redis/redis/pull/9916](https://github.com/redis/redis/pull/9916) 368 | 369 | 5. **Expiration Time Overflow:** Setting an extremely large or small expiration time now returns an error instead of causing an integer overflow. (Backported to 6.2; affects 6.0 and earlier) 370 | - PR: [https://github.com/redis/redis/pull/8287](https://github.com/redis/redis/pull/8287) 371 | 372 | 6. **`DECRBY` with `LLONG_MIN`:** `DECRBY` now returns an error when the decrement value is `LLONG_MIN`, preventing an integer overflow. (Affects 6.2 and earlier) 373 | - PR: [https://github.com/redis/redis/pull/9577](https://github.com/redis/redis/pull/9577) 374 | 375 | 7. **ZSet Rank Overflow:** Fixed a potential rank calculation overflow in sorted sets with more than `UINT32_MAX` elements. (Affects 6.2 and earlier) 376 | - PR: [https://github.com/redis/redis/pull/9249](https://github.com/redis/redis/pull/9249) 377 | 378 | 8. **Lua and `CLIENT TRACKING NOLOOP`:** Fixed a bug where write commands executed within a Lua script would ignore the `CLIENT TRACKING NOLOOP` option. (Affects 6.0) 379 | - PR: [https://github.com/redis/redis/pull/11052](https://github.com/redis/redis/pull/11052) 380 | 381 | 9. **`CLIENT TRACKING` Message Interleaving:** Fixed a bug where client tracking invalidation messages could be interleaved with the replies of other commands on the same connection. (Backported to 6.2; affects 6.0) 382 | - PRs: [https://github.com/redis/redis/pull/11038](https://github.com/redis/redis/pull/11038), [https://github.com/redis/redis/pull/9422](https://github.com/redis/redis/pull/9422) 383 | 384 | 10. **`WATCH` and Expired Keys:** A transaction now fails if a `WATCH`ed key has expired by the time of `EXEC`, regardless of whether it has been actively deleted. In addition, during the execution of a transaction, the timestamp used by the server to determine whether it has expired should not be updated. (Backported to 6.0; affects 5.0 and earlier) 385 | - PR: [https://github.com/redis/redis/pull/9194](https://github.com/redis/redis/pull/9194) 386 | 387 | 11. **`SINTERSTORE` Type Errors:** Fixed a bug where `SINTERSTORE` failed to return a `WRONGTYPE` error and could delete the destination key when operating on incorrect types. (Backported to 6.0; affects 5.0 and earlier) 388 | - PR: [https://github.com/redis/redis/pull/9032](https://github.com/redis/redis/pull/9032) 389 | 390 | 12. **Client Buffer Soft Limit Timeout:** Fixed a bug where a client exceeding a `client-output-buffer-limit` soft limit would not be disconnected after the timeout if it remained idle. (Backported to 6.2; affects 6.0 and earlier) 391 | - PR: [https://github.com/redis/redis/pull/8833](https://github.com/redis/redis/pull/8833) 392 | 393 | 13. **Stream Keyspace Notifications:** Fixed bugs where stream commands for consumer management did not emit or incorrectly emitted keyspace notifications. (Affects 6.2 and earlier) 394 | - PR: [https://github.com/redis/redis/pull/9263](https://github.com/redis/redis/pull/9263) 395 | 396 | 14. **`GEO` Search Boundary Bug:** Fixed a bug in `GEO` search commands that could cause them to miss elements located very close to the search radius boundary. (Affects 6.0 and earlier) 397 | - PR: [https://github.com/redis/redis/pull/10018](https://github.com/redis/redis/pull/10018) 398 | 399 | 15. **Lua Metatable Error Crash:** Fixed a server crash that could occur if an error was triggered while processing a Lua return value that involved a metatable. (Affects 6.0 and earlier) 400 | - PR: [https://github.com/redis/redis/pull/11032](https://github.com/redis/redis/pull/11032) 401 | 402 | 16. **Blocking Command Indefinite Block:** Fixed the bug where blocking commands with timeouts could block indefinitely if the timeout was less than 0.001 seconds. (Affects 6.2 and earlier) 403 | - PR: [https://github.com/redis/redis/pull/11688](https://github.com/redis/redis/pull/11688) 404 | 405 | 17. **Denial of Service via Random Commands:** Fixed vulnerabilities where maliciously crafted `KEYS`, `HRANDFIELD`, `SRANDMEMBER`, and `ZRANDMEMBER` commands could cause the server to hang. (`KEYS` may still causes a hang in 6.2 and earlier) 406 | - PR: [https://github.com/redis/redis/pull/11676](https://github.com/redis/redis/pull/11676) 407 | 408 | 18. **Lua Readonly Table:** As a security enhancement, prohibiting modification of existing global variables and standard library functions.(Backported to 6.2; affects 6.0 and earlier) 409 | - PR: [https://github.com/redis/redis/pull/10651](https://github.com/redis/redis/pull/10651) 410 | 411 | ## Version 7.2 412 | 413 | 1. **Random Command Infinite Loop:** Fixed a bug in `SRANDMEMBER`, `ZRANDMEMBER`, and `HRANDFIELD` where using the `count` argument had a small probability of causing an infinite loop. (Backported to 7.0; affects 6.2 and earlier) 414 | - PR: [https://github.com/redis/redis/pull/12276](https://github.com/redis/redis/pull/12276) 415 | 416 | 2. **`HINCRBYFLOAT` Key Creation:** `HINCRBYFLOAT` no longer creates a hash key if the `increment` argument is not a valid float. (Backported to 6.0; affects 5.0 and earlier) 417 | - PR: [https://github.com/redis/redis/pull/11149](https://github.com/redis/redis/pull/11149) 418 | - Doc: [https://redis.io/docs/latest/commands/hincrbyfloat/](https://redis.io/docs/latest/commands/hincrbyfloat/) 419 | 420 | 3. **`LSET` Crash on Large Element Replacement:** Fixed a potential crash with `LSET` in extreme cases when replacing many small list elements with a large one. (Affects 7.0) 421 | - PR: [https://github.com/redis/redis/pull/12955](https://github.com/redis/redis/pull/12955) 422 | - Doc: [https://redis.io/docs/latest/commands/lset/](https://redis.io/docs/latest/commands/lset/) -------------------------------------------------------------------------------- /cts_refer.md: -------------------------------------------------------------------------------- 1 | > 1. The table content consists of the commands supported by CTS (resp-compatibility) and their version information 2 | > 2. This table is generated based on the command JSON format description of Redis 7.2. If you have any doubts, you can refer to the relevant files 3 | > 3. If the table information does not match the actual testing, please submit an Issue :) 4 | - Supported major versions 5 | - 1.0 6 | - 2.8 7 | - 3.2 8 | - 4.0 9 | - 5.0 10 | - 6.0 11 | - 6.2 12 | - 7.0 13 | - 7.2 14 | 15 | 16 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 17 | |-------------|-------------------------------------------------------|--------------------------------------------------------|-----------------|-----------------|-----------------|-----------------|-------------------------------------------------------------------------------------------------|-------------------------------------------|-----------------|--------------------------------------------------------|-----------------| 18 | | **generic** | [del](https://redis.io/commands/del/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 19 | | | [unlink](https://redis.io/commands/unlink/) | | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 20 | | | [rename](https://redis.io/commands/rename/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 21 | | | [renamenx](https://redis.io/commands/renamenx/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 22 | | | [randomkey](https://redis.io/commands/randomkey/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 23 | | | [exists](https://redis.io/commands/exists/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 24 | | | [ttl](https://redis.io/commands/ttl/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 25 | | | [pttl](https://redis.io/commands/pttl/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 26 | | | [expire](https://redis.io/commands/expire/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added options: `NX`, `XX`, `GT` and `LT`.'] | ✓ | 27 | | | [expireat](https://redis.io/commands/expireat/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added options: `NX`, `XX`, `GT` and `LT`.'] | ✓ | 28 | | | [pexpire](https://redis.io/commands/pexpire/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added options: `NX`, `XX`, `GT` and `LT`.'] | ✓ | 29 | | | [pexpireat](https://redis.io/commands/pexpireat/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added options: `NX`, `XX`, `GT` and `LT`.'] | ✓ | 30 | | | [expiretime](https://redis.io/commands/expiretime/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 31 | | | [pexpiretime](https://redis.io/commands/pexpiretime/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 32 | | | [persist](https://redis.io/commands/persist/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 33 | | | [dump](https://redis.io/commands/dump/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 34 | | | [touch](https://redis.io/commands/touch/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 35 | | | [restore](https://redis.io/commands/restore/) | | ✗ | ✓ | ✓ | ✓ | ['5.0.0', 'Added the `ABSTTL` modifier.']/['5.0.0', 'Added the `IDLETIME` and `FREQ` options.'] | ✓ | ✓ | ✓ | ✓ | 36 | | | [scan](https://redis.io/commands/scan/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.0.0', 'Added the `TYPE` subcommand.'] | ✓ | ✓ | ✓ | 37 | | | [keys](https://redis.io/commands/keys/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 38 | | | [move](https://redis.io/commands/move/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 39 | | | [copy](https://redis.io/commands/copy/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 40 | | | [type](https://redis.io/commands/type/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 41 | | | [wait](https://redis.io/commands/wait/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 42 | | | [sort](https://redis.io/commands/sort/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 43 | | | [sort_ro](https://redis.io/commands/sort_ro/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 44 | | | [migrate](https://redis.io/commands/migrate/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.0.0', 'Added the `AUTH2` option.'] | ✓ | ✓ | ✓ | 45 | | | object | [freq](https://redis.io/commands/object-freq/) | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 46 | | | | [refcount](https://redis.io/commands/object-refcount/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 47 | | | | [idletime](https://redis.io/commands/object-idletime/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 48 | | | | [encoding](https://redis.io/commands/object-encoding/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 49 | 50 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 51 | |------------|---------------------------------------------------------|----------------------------------------------------------|-----------------|-----------------|-----------------|--------------------------------------------------------|-----------------|-----------------|-------------------------------------------------------|--------------------------------------------------------------------------------|-----------------------------------------------------------------------------| 52 | | **server** | acl | [whoami](https://redis.io/commands/acl-whoami/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | 53 | | | | [setuser](https://redis.io/commands/acl-setuser/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ['6.2.0', 'Added Pub/Sub channel patterns.'] | ['7.0.0', 'Added selectors and key based permissions.'] | ✓ | 54 | | | | [deluser](https://redis.io/commands/acl-deluser/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | 55 | | | | [users](https://redis.io/commands/acl-users/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | 56 | | | | [log](https://redis.io/commands/acl-log/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ['7.2.0', 'Added entry ID, timestamp created, and timestamp last updated.'] | 57 | | | | [dryrun](https://redis.io/commands/acl-dryrun/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 58 | | | config | [get](https://redis.io/commands/config-get/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the ability to pass multiple pattern parameters in one call'] | ✓ | 59 | | | | [set](https://redis.io/commands/config-set/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the ability to pass multiple pattern parameters in one call'] | ✓ | 60 | | | | [resetstat](https://redis.io/commands/config-resetstat/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 61 | | | module | [list](https://redis.io/commands/module-list/) | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 62 | | | [bgsave](https://redis.io/commands/bgsave/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 63 | | | [bgrewriteaof](https://redis.io/commands/bgrewriteaof/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 64 | | | [dbsize](https://redis.io/commands/dbsize/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 65 | | | [swapdb](https://redis.io/commands/swapdb/) | | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 66 | | | [slaveof](https://redis.io/commands/slaveof/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 67 | | | [flushall](https://redis.io/commands/flushall/) | | ✓ | ✓ | ✓ | ['4.0.0', 'Added the `ASYNC` flushing mode modifier.'] | ✓ | ✓ | ['6.2.0', 'Added the `SYNC` flushing mode modifier.'] | ✓ | ✓ | 68 | | | [flushdb](https://redis.io/commands/flushdb/) | | ✓ | ✓ | ✓ | ['4.0.0', 'Added the `ASYNC` flushing mode modifier.'] | ✓ | ✓ | ['6.2.0', 'Added the `SYNC` flushing mode modifier.'] | ✓ | ✓ | 69 | | | [replicaof](https://redis.io/commands/replicaof/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 70 | 71 | 72 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 73 | |------------|-------------------------------------------------------|-----|-----------------|-----------------|-----------------|-----------------|-----------------|------------------------------------------|---------------------------------------------------------|----------------------------------------------------------------------|-----------------| 74 | | **string** | [set](https://redis.io/commands/set/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.0.0', 'Added the `KEEPTTL` option.'] | ['6.2.0', 'Added the `GET`, `EXAT` and `PXAT` option.'] | ['7.0.0', 'Allowed the `NX` and `GET` options to be used together.'] | ✓ | 75 | | | [setex](https://redis.io/commands/setex/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 76 | | | [setnx](https://redis.io/commands/setnx/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 77 | | | [psetex](https://redis.io/commands/psetex/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 78 | | | [append](https://redis.io/commands/append/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 79 | | | [get](https://redis.io/commands/get/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 80 | | | [getex](https://redis.io/commands/getex/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 81 | | | [getdel](https://redis.io/commands/getdel/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 82 | | | [substr](https://redis.io/commands/substr/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 83 | | | [strlen](https://redis.io/commands/strlen/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 84 | | | [getset](https://redis.io/commands/getset/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 85 | | | [setrange](https://redis.io/commands/setrange/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 86 | | | [getrange](https://redis.io/commands/getrange/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 87 | | | [incr](https://redis.io/commands/incr/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 88 | | | [decr](https://redis.io/commands/decr/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 89 | | | [incrby](https://redis.io/commands/incrby/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 90 | | | [decrby](https://redis.io/commands/decrby/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 91 | | | [incrbyfloat](https://redis.io/commands/incrbyfloat/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 92 | | | [mset](https://redis.io/commands/mset/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 93 | | | [msetnx](https://redis.io/commands/msetnx/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 94 | | | [mget](https://redis.io/commands/mget/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 95 | | | [lcs](https://redis.io/commands/lcs/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 96 | 97 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 98 | |------------|-------------------------------------------------------|---|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|---------------------------------------------|-----------------| 99 | | **bitmap** | [setbit](https://redis.io/commands/setbit/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 100 | | | [getbit](https://redis.io/commands/getbit/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 101 | | | [bitop](https://redis.io/commands/bitop/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 102 | | | [bitpos](https://redis.io/commands/bitpos/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the `BYTE / BIT` option.'] | ✓ | 103 | | | [bitcount](https://redis.io/commands/bitcount/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the `BYTE / BIT` option.'] | ✓ | 104 | | | [bitfield](https://redis.io/commands/bitfield/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 105 | | | [bitfield_ro](https://redis.io/commands/bitfield_ro/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | 106 | 107 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 108 | |-----------------|-----------------------------------------------|---|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------| 109 | | **transaction** | [discard](https://redis.io/commands/discard/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 110 | | | [exec](https://redis.io/commands/exec/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 111 | | | [multi](https://redis.io/commands/multi/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 112 | | | [unwatch](https://redis.io/commands/unwatch/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 113 | | | [watch](https://redis.io/commands/watch/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 114 | 115 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 116 | |-------------|-----------------------------------------------------------|---|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------| 117 | | hyperloglog | [pfadd](https://redis.io/commands/pfadd/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 118 | | | [pfcount](https://redis.io/commands/pfcount/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 119 | | | [pfmerge](https://redis.io/commands/pfmerge/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 120 | 121 | 122 | 123 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 124 | |-----------|-----------------------------------------------------|---|-----------------|-----------------|-----------------|----------------------------------------------------|-----------------|--------------------------------------------------------------------------|------------------------------------------|-----------------|-----------------| 125 | | **list** | [lset](https://redis.io/commands/lset/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 126 | | | [llen](https://redis.io/commands/llen/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 127 | | | [lpush](https://redis.io/commands/lpush/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 128 | | | [lpushx](https://redis.io/commands/lpushx/) | | ✗ | ✓ | ✓ | ['4.0.0', 'Accepts multiple `element` arguments.'] | ✓ | ✓ | ✓ | ✓ | ✓ | 129 | | | [lindex](https://redis.io/commands/lindex/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 130 | | | [lpos](https://redis.io/commands/lpos/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 131 | | | [lrem](https://redis.io/commands/lrem/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 132 | | | [lrange](https://redis.io/commands/lrange/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 133 | | | [rpush](https://redis.io/commands/rpush/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 134 | | | [ltrim](https://redis.io/commands/ltrim/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 135 | | | [linsert](https://redis.io/commands/linsert/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 136 | | | [rpushx](https://redis.io/commands/rpushx/) | | ✗ | ✗ | ✗ | ['4.0.0', 'Accepts multiple `element` arguments.'] | ✓ | ✓ | ✓ | ✓ | ✓ | 137 | | | [rpoplpush](https://redis.io/commands/rpoplpush/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 138 | | | [lmove](https://redis.io/commands/lmove/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 139 | | | [lmpop](https://redis.io/commands/lmpop/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 140 | | | [rpop](https://redis.io/commands/rpop/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `count` argument.'] | ✓ | ✓ | 141 | | | [lpop](https://redis.io/commands/lpop/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `count` argument.'] | ✓ | ✓ | 142 | | | [brpop](https://redis.io/commands/brpop/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.0.0', '`timeout` is interpreted as a double instead of an integer.'] | ✓ | ✓ | ✓ | 143 | | | [blpop](https://redis.io/commands/blpop/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.0.0', '`timeout` is interpreted as a double instead of an integer.'] | ✓ | ✓ | ✓ | 144 | | | [brpoplpush](https://redis.io/commands/brpoplpush/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.0.0', '`timeout` is interpreted as a double instead of an integer.'] | ✓ | ✓ | ✓ | 145 | | | [blmove](https://redis.io/commands/blmove/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 146 | | | [blmpop](https://redis.io/commands/blmpop/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 147 | 148 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 149 | |-----------|---------------------------------------------------------|---|-----------------|-----------------|-----------------|--------------------------------------------------------------|-----------------|-----------------|-----------------|-----------------|-----------------| 150 | | **hash** | [hset](https://redis.io/commands/hset/) | | ✗ | ✓ | ✓ | ['4.0.0', 'Accepts multiple `field` and `value` arguments.'] | ✓ | ✓ | ✓ | ✓ | ✓ | 151 | | | [hsetnx](https://redis.io/commands/hsetnx/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 152 | | | [hget](https://redis.io/commands/hget/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 153 | | | [hstrlen](https://redis.io/commands/hstrlen/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 154 | | | [hexists](https://redis.io/commands/hexists/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 155 | | | [hlen](https://redis.io/commands/hlen/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 156 | | | [hkeys](https://redis.io/commands/hkeys/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 157 | | | [hvals](https://redis.io/commands/hvals/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 158 | | | [hgetall](https://redis.io/commands/hgetall/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 159 | | | [hincrby](https://redis.io/commands/hincrby/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 160 | | | [hincrbyfloat](https://redis.io/commands/hincrbyfloat/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 161 | | | [hscan](https://redis.io/commands/hscan/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 162 | | | [hrandfield](https://redis.io/commands/hrandfield/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 163 | | | [hdel](https://redis.io/commands/hdel/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 164 | | | [hmset](https://redis.io/commands/hmset/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 165 | | | [hmget](https://redis.io/commands/hmget/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 166 | 167 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 168 | |-----------|-------------------------------------------------------|---|-----------------|-----------------|------------------------------------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------| 169 | | **set** | [sadd](https://redis.io/commands/sadd/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 170 | | | [srem](https://redis.io/commands/srem/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 171 | | | [sscan](https://redis.io/commands/sscan/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 172 | | | [scard](https://redis.io/commands/scard/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 173 | | | [spop](https://redis.io/commands/spop/) | | ✓ | ✓ | ['3.2.0', 'Added the `count` argument.'] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 174 | | | [srandmember](https://redis.io/commands/srandmember/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 175 | | | [smembers](https://redis.io/commands/smembers/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 176 | | | [sismember](https://redis.io/commands/sismember/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 177 | | | [smismember](https://redis.io/commands/smismember/) | | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 178 | | | [sdiffstore](https://redis.io/commands/sdiffstore/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 179 | | | [sinterstore](https://redis.io/commands/sinterstore/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 180 | | | [sunionstore](https://redis.io/commands/sunionstore/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 181 | | | [sdiff](https://redis.io/commands/sdiff/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 182 | | | [sinter](https://redis.io/commands/sinter/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 183 | | | [sunion](https://redis.io/commands/sunion/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 184 | | | [smove](https://redis.io/commands/smove/) | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 185 | | | [sintercard](https://redis.io/commands/sintercard/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 186 | 187 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 188 | |----------------|-----------------------------------------------------------------|---|-----------------|-----------------|-----------------|-----------------|-----------------|--------------------------------------------------------------------------|-----------------------------------------------------------------------|-----------------|-------------------------------------------------------| 189 | | **sorted set** | [zadd](https://redis.io/commands/zadd/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `GT` and `LT` options.'] | ✓ | ✓ | 190 | | | [zrem](https://redis.io/commands/zrem/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 191 | | | [zcard](https://redis.io/commands/zcard/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 192 | | | [zrank](https://redis.io/commands/zrank/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.2.0', 'Added the optional `WITHSCORE` argument.'] | 193 | | | [zrevrank](https://redis.io/commands/zrevrank/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.2.0', 'Added the optional `WITHSCORE` argument.'] | 194 | | | [zcount](https://redis.io/commands/zcount/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 195 | | | [zincrby](https://redis.io/commands/zincrby/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 196 | | | [zscan](https://redis.io/commands/zscan/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 197 | | | [zrandmember](https://redis.io/commands/zrandmember/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 198 | | | [zremrangebylex](https://redis.io/commands/zremrangebylex/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 199 | | | [zremrangebyscore](https://redis.io/commands/zremrangebyscore/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 200 | | | [zremrangebyrank](https://redis.io/commands/zremrangebyrank/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 201 | | | [zlexcount](https://redis.io/commands/zlexcount/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 202 | | | [zrangebylex](https://redis.io/commands/zrangebylex/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 203 | | | [zrevrangebylex](https://redis.io/commands/zrevrangebylex/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 204 | | | [zrangebyscore](https://redis.io/commands/zrangebyscore/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 205 | | | [zrevrangebyscore](https://redis.io/commands/zrevrangebyscore/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 206 | | | [zscore](https://redis.io/commands/zscore/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 207 | | | [zmscore](https://redis.io/commands/zmscore/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 208 | | | [zpopmax](https://redis.io/commands/zpopmax/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 209 | | | [zpopmin](https://redis.io/commands/zpopmin/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 210 | | | [zmpop](https://redis.io/commands/zmpop/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 211 | | | [bzmpop](https://redis.io/commands/bzmpop/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 212 | | | [zintercard](https://redis.io/commands/zintercard/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 213 | | | [bzpopmin](https://redis.io/commands/bzpopmin/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ['6.0.0', '`timeout` is interpreted as a double instead of an integer.'] | ✓ | ✓ | ✓ | 214 | | | [bzpopmax](https://redis.io/commands/bzpopmax/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ['6.0.0', '`timeout` is interpreted as a double instead of an integer.'] | ✓ | ✓ | ✓ | 215 | | | [zdiffstore](https://redis.io/commands/zdiffstore/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 216 | | | [zinterstore](https://redis.io/commands/zinterstore/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 217 | | | [zunionstore](https://redis.io/commands/zunionstore/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 218 | | | [zrangestore](https://redis.io/commands/zrangestore/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 219 | | | [zdiff](https://redis.io/commands/zdiff/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 220 | | | [zinter](https://redis.io/commands/zinter/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 221 | | | [zunion](https://redis.io/commands/zunion/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 222 | | | [zrange](https://redis.io/commands/zrange/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `REV`, `BYSCORE`, `BYLEX` and `LIMIT` options.'] | ✓ | ✓ | 223 | | | [zrevrange](https://redis.io/commands/zrevrange/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 224 | 225 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 226 | |-----------|-------------------------------------------------------------------------|---|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------------------------------------------|------------------------------------------------------|-----------------| 227 | | **geo** | [geoadd](https://redis.io/commands/geoadd/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `CH`, `NX` and `XX` options.'] | ✓ | ✓ | 228 | | | [geohash](https://redis.io/commands/geohash/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 229 | | | [geodist](https://redis.io/commands/geodist/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 230 | | | [geopos](https://redis.io/commands/geopos/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 231 | | | [georadius](https://redis.io/commands/georadius/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `ANY` option for `COUNT`.'] | ['7.0.0', 'Added support for uppercase unit names.'] | ✓ | 232 | | | [georadius_ro](https://redis.io/commands/georadius_ro/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `ANY` option for `COUNT`.'] | ✓ | ✓ | 233 | | | [georadiusbymember](https://redis.io/commands/georadiusbymember/) | | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ['7.0.0', 'Added support for uppercase unit names.'] | ✓ | 234 | | | [georadiusbymember_ro](https://redis.io/commands/georadiusbymember_ro/) | | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 235 | | | [geosearch](https://redis.io/commands/geosearch/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ['7.0.0', 'Added support for uppercase unit names.'] | ✓ | 236 | | | [geosearchstore](https://redis.io/commands/geosearchstore/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ['7.0.0', 'Added support for uppercase unit names.'] | ✓ | 237 | 238 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 239 | |------------|-----------------------------------------------------|--------------------------------------------------------------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------------------------------|-----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| 240 | | **stream** | [xadd](https://redis.io/commands/xadd/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ['6.2.0', 'Added the `NOMKSTREAM` option, `MINID` trimming strategy and the `LIMIT` option.'] | ['7.0.0', 'Added support for the `-*` explicit ID form.'] | ✓ | 241 | | | [xdel](https://redis.io/commands/xdel/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 242 | | | [xack](https://redis.io/commands/xack/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 243 | | | [xclaim](https://redis.io/commands/xclaim/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 244 | | | [xlen](https://redis.io/commands/xlen/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 245 | | | [xtrim](https://redis.io/commands/xtrim/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ['6.2.0', 'Added the `MINID` trimming strategy and the `LIMIT` option.'] | ✓ | ✓ | 246 | | | [xread](https://redis.io/commands/xread/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 247 | | | [xreadgroup](https://redis.io/commands/xreadgroup/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 248 | | | [xrange](https://redis.io/commands/xrange/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ['6.2.0', 'Added exclusive ranges.'] | ✓ | ✓ | 249 | | | [xrevrange](https://redis.io/commands/xrevrange/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ['6.2.0', 'Added exclusive ranges.'] | ✓ | ✓ | 250 | | | [xsetid](https://redis.io/commands/xsetid/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the `entries_added` and `max_deleted_entry_id` arguments.'] | ✓ | 251 | | | [xpending](https://redis.io/commands/xpending/) | | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ['6.2.0', 'Added the `IDLE` option and exclusive range intervals.'] | ✓ | ✓ | 252 | | | [xautoclaim](https://redis.io/commands/xautoclaim/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ['7.0.0', 'Added an element to the reply array, containing deleted entries the command cleared from the PEL'] | ✓ | 253 | | | xgroup | [createconsumer](https://redis.io/commands/xgroup-createconsumer/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | 254 | | | | [delconsumer](https://redis.io/commands/xgroup-delconsumer/) | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 255 | | | | [create](https://redis.io/commands/xgroup-create/) | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the `entries_read` named argument.'] | ✓ | 256 | | | | [destroy](https://redis.io/commands/xgroup-destroy/) | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | 257 | | | | [setid](https://redis.io/commands/xgroup-setid/) | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the optional `entries_read` argument.'] | ✓ | 258 | | | xinfo | [groups](https://redis.io/commands/xinfo-groups/) | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ['7.0.0', 'Added the `entries-read` and `lag` fields'] | ✓ | 259 | | | | [consumers](https://redis.io/commands/xinfo-consumers/) | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ['7.2.0', 'Added the `inactive` field.'] | 260 | | | | [stream](https://redis.io/commands/xinfo-stream/) | ✗ | ✗ | ✗ | ✗ | ✓ | ['6.0.0', 'Added the `FULL` modifier.'] | ✓ | ['7.0.0', 'Added the `max-deleted-entry-id`, `entries-added`, `recorded-first-entry-id`, `entries-read` and `lag` fields'] | ['7.2.0', 'Added the `active-time` field, and changed the meaning of `seen-time`.'] | 261 | 262 | 263 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 264 | |-----------|-----------------------------------------------------|--------------------------------------------------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|--------------------------------------------------------------------|-----------------|-----------------| 265 | | scripting | [eval](https://redis.io/commands/eval/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 266 | | | [evalsha](https://redis.io/commands/evalsha/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 267 | | | [eval_ro](https://redis.io/commands/eval_ro/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 268 | | | [evalsha_ro](https://redis.io/commands/evalsha_ro/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 269 | | | script | [load](https://redis.io/commands/script-load/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 270 | | | | [exists](https://redis.io/commands/script-exists/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 271 | | | | [flush](https://redis.io/commands/script-flush/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', 'Added the `ASYNC` and `SYNC` flushing mode modifiers.'] | ✓ | ✓ | 272 | | | | [debug](https://redis.io/commands/script-debug/) | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 273 | | | [fcall](https://redis.io/commands/fcall/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 274 | | | [fcall_ro](https://redis.io/commands/fcall_ro/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 275 | | | function | [stats](https://redis.io/commands/function-stats/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 276 | | | | [list](https://redis.io/commands/function-list/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 277 | | | | [dump](https://redis.io/commands/function-dump/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 278 | | | | [restore](https://redis.io/commands/function-restore/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 279 | | | | [flush](https://redis.io/commands/function-flush/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 280 | | | | [delete](https://redis.io/commands/function-delete/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 281 | 282 | | **group** | **command** | | **Version 1.0** | **Version 2.8** | **Version 3.2** | **Version 4.0** | **Version 5.0** | **Version 6.0** | **Version 6.2** | **Version 7.0** | **Version 7.2** | 283 | |------------|:-------------------------------------------------------:|------------------------------------------------------------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|--------------------------------------------------------------|-----------------|-----------------| 284 | | **pubsub** | [publish](https://redis.io/commands/publish/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 285 | | | [subscribe](https://redis.io/commands/subscribe/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', '`RESET` can be called to exit subscribed state.'] | ✓ | ✓ | 286 | | | [unsubscribe](https://redis.io/commands/unsubscribe/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 287 | | | [punsubscribe](https://redis.io/commands/punsubscribe/) | | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ['6.2.0', '`RESET` can be called to exit subscribed state.'] | ✓ | ✓ | 288 | | | [spublish](https://redis.io/commands/spublish/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 289 | | | [ssubscribe](https://redis.io/commands/ssubscribe/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 290 | | | [sunsubscribe](https://redis.io/commands/sunsubscribe/) | | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 291 | | | pubsub | [numsub](https://redis.io/commands/pubsub-numsub/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 292 | | | | [numpat](https://redis.io/commands/pubsub-numpat/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 293 | | | | [channels](https://redis.io/commands/pubsub-channels/) | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 294 | | | | [shardnumsub](https://redis.io/commands/pubsub-shardnumsub/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | 295 | | | | [shardchannels](https://redis.io/commands/pubsub-shardchannels/) | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | --------------------------------------------------------------------------------