title: Redis Strings

summary: Introduction to Redis strings

title: String command summary

text:
**25 commands in this group:**

[View all string commands](https://redis.io/commands/?group=string)

| Command | Summary | Complexity | Since |
|---------|---------|------------|-------|
| [APPEND](https://redis.io/commands/append/) | Appends a string to the value of a key. Creates the key if it doesn't exist. | O(1). The amortized time complexity is O(1) ass... | 2.0.0 |
| [DECR](https://redis.io/commands/decr/) | Decrements the integer value of a key by one. Uses 0 as initial value if the key doesn't exist. | O(1) | 1.0.0 |
| [DECRBY](https://redis.io/commands/decrby/) | Decrements a number from the integer value of a key. Uses 0 as initial value if the key doesn't exist. | O(1) | 1.0.0 |
| [DELEX](https://redis.io/commands/delex/) | Conditionally removes the specified key based on value or digest comparison. | O(1) for IFEQ/IFNE, O(N) for IFDEQ/IFDNE where ... | 8.4.0 |
| [DIGEST](https://redis.io/commands/digest/) | Returns the XXH3 hash of a string value. | O(N) where N is the length of the string value. | 8.4.0 |
| [GET](https://redis.io/commands/get/) | Returns the string value of a key. | O(1) | 1.0.0 |
| [GETDEL](https://redis.io/commands/getdel/) | Returns the string value of a key after deleting the key. | O(1) | 6.2.0 |
| [GETEX](https://redis.io/commands/getex/) | Returns the string value of a key after setting its expiration time. | O(1) | 6.2.0 |
| [GETRANGE](https://redis.io/commands/getrange/) | Returns a substring of the string stored at a key. | O(N) where N is the length of the returned stri... | 2.4.0 |
| [GETSET](https://redis.io/commands/getset/) | Returns the previous string value of a key after setting it to a new value. | O(1) | 1.0.0 |
| [INCR](https://redis.io/commands/incr/) | Increments the integer value of a key by one. Uses 0 as initial value if the key doesn't exist. | O(1) | 1.0.0 |
| [INCRBY](https://redis.io/commands/incrby/) | Increments the integer value of a key by a number. Uses 0 as initial value if the key doesn't exist. | O(1) | 1.0.0 |
| [INCRBYFLOAT](https://redis.io/commands/incrbyfloat/) | Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist. | O(1) | 2.6.0 |
| [LCS](https://redis.io/commands/lcs/) | Finds the longest common substring. | O(N*M) where N and M are the lengths of s1 and ... | 7.0.0 |
| [MGET](https://redis.io/commands/mget/) | Atomically returns the string values of one or more keys. | O(N) where N is the number of keys to retrieve. | 1.0.0 |
| [MSET](https://redis.io/commands/mset/) | Atomically creates or modifies the string values of one or more keys. | O(N) where N is the number of keys to set. | 1.0.1 |
| [MSETEX](https://redis.io/commands/msetex/) | Atomically sets multiple string keys with a shared expiration in a single operation. Supports flexible argument parsing where condition and expiration flags can appear in any order. | O(N) where N is the number of keys to set. | 8.4.0 |
| [MSETNX](https://redis.io/commands/msetnx/) | Atomically modifies the string values of one or more keys only when all keys don't exist. | O(N) where N is the number of keys to set. | 1.0.1 |
| [PSETEX](https://redis.io/commands/psetex/) | Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist. | O(1) | 2.6.0 |
| [SET](https://redis.io/commands/set/) | Sets the string value of a key, ignoring its type. The key is created if it doesn't exist. | O(1) | 1.0.0 |
| [SETEX](https://redis.io/commands/setex/) | Sets the string value and expiration time of a key. Creates the key if it doesn't exist. | O(1) | 2.0.0 |
| [SETNX](https://redis.io/commands/setnx/) | Set the string value of a key only when the key doesn't exist. | O(1) | 1.0.0 |
| [SETRANGE](https://redis.io/commands/setrange/) | Overwrites a part of a string value with another by an offset. Creates the key if it doesn't exist. | O(1), not counting the time taken to copy the n... | 2.2.0 |
| [STRLEN](https://redis.io/commands/strlen/) | Returns the length of a string value. | O(1) | 2.2.0 |
| [SUBSTR](https://redis.io/commands/substr/) | Returns a substring from a string value. | O(N) where N is the length of the returned stri... | 1.0.0 |

Redis strings store sequences of bytes, including text, serialized objects, and binary arrays.
As such, strings are the simplest type of value you can associate with a Redis key.
They're often used for caching, but they support additional functionality that lets you implement counters and perform bitwise operations, too.

Since Redis keys are strings, when we use the string type as a value too,
we are mapping a string to another string. The string data type is useful
for a number of use cases, like caching HTML fragments or pages.

#### Code Examples

Foundational: Set and retrieve string values using SET and GET (overwrites existing values)

**Commands:** SET, GET

**Complexity:**
- SET: O(1)
- GET: O(1)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), PHP, Python, Rust (Asynchronous), Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

As you can see using the [`SET`]() and the [`GET`]() commands are the way we set
and retrieve a string value. Note that [`SET`]() will replace any existing value
already stored into the key, in the case that the key already exists, even if
the key is associated with a non-string value. So [`SET`]() performs an assignment.

Values can be strings (including binary data) of every kind, for instance you
can store a jpeg image inside a value. A value can't be bigger than 512 MB.

The [`SET`]() command has interesting options that are provided as additional
arguments. For example, I may ask [`SET`]() to fail if the key already exists,
or the opposite, that it only succeed if the key already exists:

#### Code Examples

Conditional SET operations: Use NX and XX options to control key existence when you need atomic compare-and-set behavior

**Difficulty:** Intermediate

**Builds upon:** set_get

**Commands:** SET

**Complexity:**
- SET: O(1)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), PHP, Python, Rust (Asynchronous), Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

There are a number of other commands for operating on strings. For example
the [`GETSET`]() command sets a key to a new value, returning the old value as the
result. You can use this command, for example, if you have a
system that increments a Redis key using [`INCR`]()
every time your web site receives a new visitor. You may want to collect this
information once every hour, without losing a single increment.
You can [`GETSET`]() the key, assigning it the new value of "0" and reading the
old value back.

The ability to set or retrieve the value of multiple keys in a single
command is also useful for reduced latency. For this reason there are
the [`MSET`]() and [`MGET`]() commands:

#### Code Examples

Set and retrieve multiple values using MSET and MGET when you need to reduce round trips to the server

**Builds upon:** set_get

**Commands:** MSET, MGET

**Complexity:**
- MSET: O(N)
- MGET: O(N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), PHP, Python, Rust (Asynchronous), Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

When [`MGET`]() is used, Redis returns an array of values.

title: Strings as counters

text:
Even if strings are the basic values of Redis, there are interesting operations
you can perform with them. For instance, one is atomic increment:

#### Code Examples

Atomic counters: Increment string values using INCR and INCRBY when you need thread-safe operations (initializes to 0 if key doesn't exist)

**Builds upon:** set_get

**Commands:** SET, INCR, INCRBY

**Complexity:**
- SET: O(1)
- INCR: O(1)
- INCRBY: O(1)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), PHP, Python, Rust (Asynchronous), Rust (Synchronous)

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**PHP:**

[code example]

**Python:**

[code example]

**Rust (Asynchronous):**

[code example]

**Rust (Synchronous):**

[code example]

The [`INCR`]() command parses the string value as an integer,
increments it by one, and finally sets the obtained value as the new value.
There are other similar commands like [`INCRBY`](),
[`DECR`]() and [`DECRBY`](). Internally it's
always the same command, acting in a slightly different way.

What does it mean that INCR is atomic?
That even multiple clients issuing INCR against
the same key will never enter into a race condition. For instance, it will never
happen that client 1 reads "10", client 2 reads "10" at the same time, both
increment to 11, and set the new value to 11. The final value will always be
12 and the read-increment-set operation is performed while all the other
clients are not executing a command at the same time.

title: Limits

role: limits

text: By default, a single Redis string can be a maximum of 512 MB.

title: Bitwise and bitfield operations

text: To perform bitwise operations on a string, see the [bitmaps data type]() docs. To store and manipulate integer values within a string, see the [bitfields data type]() docs.

title: Performance

text:
Most string operations are O(1), which means they're highly efficient.
However, be careful with the [`SUBSTR`](), [`GETRANGE`](), and [`SETRANGE`]() commands, which can be O(n).
These random-access string commands may cause performance issues when dealing with large strings.

title: Alternatives

text: If you're storing structured data as a serialized string, you may also want to consider Redis [hashes]() or [JSON]().

title: Learn more

text:
* [Redis Strings Explained](https://www.youtube.com/watch?v=7CUt4yWeRQE) is a short, comprehensive video explainer on Redis strings.
* [Redis University's RU101](https://university.redis.com/courses/ru101/) covers Redis strings in detail.

code:
> SET bike:1 Deimos
OK
> GET bike:1
"Deimos"

section_id: string-command-summary

code:
var res1 = db.StringSet("bike:1", "Deimos");
        Console.WriteLine(res1); // true
        var res2 = db.StringGet("bike:1");
        Console.WriteLine(res2); // Deimos

code:
res1, err := rdb.Set(ctx, "bike:1", "Deimos", 0).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1) // >>> OK

	res2, err := rdb.Get(ctx, "bike:1").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2) // >>> Deimos

code:
CompletableFuture<Void> setAndGet = asyncCommands.set("bike:1", "Deimos").thenCompose(v -> {
                System.out.println(v); // >>> OK
                return asyncCommands.get("bike:1");
            })
                    .thenAccept(System.out::println) // >>> Deimos
                    .toCompletableFuture();

code:
Mono<Void> setAndGet = reactiveCommands.set("bike:1", "Deimos").doOnNext(v -> {
                System.out.println(v); // OK
            }).flatMap(v -> reactiveCommands.get("bike:1")).doOnNext(res -> {
                System.out.println(res); // Deimos
            }).then();

code:
String res1 = jedis.set("bike:1", "Deimos");
      System.out.println(res1); // OK
      String res2 = jedis.get("bike:1");
      System.out.println(res2); // Deimos

code:
const res1 = await client.set("bike:1", "Deimos");
console.log(res1);  // OK
const res2 = await client.get("bike:1");
console.log(res2);  // Deimos

code:
$res1 = $r->set('bike:1', 'Deimos');
        echo "$res1" . PHP_EOL;
        // >>> OK

        $res2 = $r->get('bike:1');
        echo "$res2" . PHP_EOL;
        // >>> Deimos

code:
res1 = r.set("bike:1", "Deimos")
print(res1)  # True
res2 = r.get("bike:1")
print(res2)  # Deimos

code:
if let Ok(res) = r.set("bike:1", "Deimos").await {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.get("bike:1").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> Deimos
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

code:
if let Ok(res) = r.set("bike:1", "Deimos") {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.get("bike:1") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        };

code:
> SET bike:1 bike NX
(nil)
> SET bike:1 bike XX
OK

code:
var res3 = db.StringSet("bike:1", "bike", when: When.NotExists);
        Console.WriteLine(res3); // false
        Console.WriteLine(db.StringGet("bike:1"));
        var res4 = db.StringSet("bike:1", "bike", when: When.Exists);
        Console.WriteLine(res4); // true

code:
res3, err := rdb.SetNX(ctx, "bike:1", "bike", 0).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3) // >>> false

	res4, err := rdb.Get(ctx, "bike:1").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res4) // >>> Deimos

	res5, err := rdb.SetXX(ctx, "bike:1", "bike", 0).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res5) // >>> OK

code:
CompletableFuture<Void> setnx = asyncCommands.setnx("bike:1", "bike").thenCompose(v -> {
                System.out.println(v); // >>> false (because key already exists)
                return asyncCommands.get("bike:1");
            })
                    .thenAccept(System.out::println) // >>> Deimos (value is unchanged)
                    .toCompletableFuture();
            setnx.join();

            // set the value to "bike" if it already exists
            CompletableFuture<Void> setxx = asyncCommands.set("bike:1", "bike", SetArgs.Builder.xx())
                    .thenAccept(System.out::println) // >>> OK
                    .toCompletableFuture();
            setxx.join();

code:
Mono<Void> setnx = reactiveCommands.setnx("bike:1", "bike").doOnNext(v -> {
                System.out.println(v); // false (because key already exists)
            }).flatMap(v -> reactiveCommands.get("bike:1")).doOnNext(res -> {
                System.out.println(res); // Deimos (value is unchanged)
            }).then();

            Mono<Void> setxx = reactiveCommands.set("bike:1", "bike", SetArgs.Builder.xx()).doOnNext(res -> {
                System.out.println(res); // OK
            }).then();

code:
Long res3 = jedis.setnx("bike:1", "bike");
      System.out.println(res3); // 0 (because key already exists)
      System.out.println(jedis.get("bike:1")); // Deimos (value is unchanged)
      String res4 = jedis.set("bike:1", "bike", SetParams.setParams().xx()); // set the value to "bike" if it
      // already
      // exists
      System.out.println(res4); // OK

code:
const res3 = await client.set("bike:1", "bike", {'NX': true});
console.log(res3);  // null
console.log(await client.get("bike:1"));  // Deimos
const res4 = await client.set("bike:1", "bike", {'XX': true});
console.log(res4);  // OK

code:
$res3 = $r->set('bike:1', 'bike', 'nx');
        echo "$res3" . PHP_EOL;
        // >>> (null)

        echo $r->get('bike:1') . PHP_EOL;
        // >>> Deimos

        $res4 = $r->set('bike:1', 'bike', 'xx');
        echo "$res4" . PHP_EOL;
        // >>> OK

code:
res3 = r.set("bike:1", "bike", nx=True)
print(res3)  # None
print(r.get("bike:1"))  # Deimos
res4 = r.set("bike:1", "bike", xx=True)
print(res4)  # True

code:
if let Ok(res) = r.set_options("bike:1", "bike", redis::SetOptions::default().conditional_set(ExistenceCheck::NX)).await {
            let res: bool = res;
            println!("{res}");    // >>> false
        }

        match r.get("bike:1").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> Deimos
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

        if let Ok(res) = r.set_options("bike:1", "bike", redis::SetOptions::default().conditional_set(ExistenceCheck::XX)).await {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.get("bike:1").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> bike
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

code:
if let Ok(res) = r.set_options("bike:1", "bike", redis::SetOptions::default().conditional_set(ExistenceCheck::NX)) {
            let res: bool = res;
            println!("{res}");    // >>> false
        }

        match r.get("bike:1") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        };

        if let Ok(res) = r.set_options("bike:1", "bike", redis::SetOptions::default().conditional_set(ExistenceCheck::XX)) {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.get("bike:1") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> bike
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        };

code:
> MSET bike:1 "Deimos" bike:2 "Ares" bike:3 "Vanth"
OK
> MGET bike:1 bike:2 bike:3
1) "Deimos"
2) "Ares"
3) "Vanth"

code:
var res5 = db.StringSet([
            new ("bike:1", "Deimos"), new("bike:2", "Ares"), new("bike:3", "Vanth")
        ]);
        Console.WriteLine(res5);
        var res6 = db.StringGet(["bike:1", "bike:2", "bike:3"]);
        Console.WriteLine(string.Join(", ", res6));

code:
res6, err := rdb.MSet(ctx, "bike:1", "Deimos", "bike:2", "Ares", "bike:3", "Vanth").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res6) // >>> OK

	res7, err := rdb.MGet(ctx, "bike:1", "bike:2", "bike:3").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res7) // >>> [Deimos Ares Vanth]

code:
Map<String, String> bikeMap = new HashMap<>();
            bikeMap.put("bike:1", "Deimos");
            bikeMap.put("bike:2", "Ares");
            bikeMap.put("bike:3", "Vanth");

            CompletableFuture<Void> mset = asyncCommands.mset(bikeMap).thenCompose(v -> {
                System.out.println(v); // >>> OK
                return asyncCommands.mget("bike:1", "bike:2", "bike:3");
            })
                    .thenAccept(System.out::println)
                    // >>> [KeyValue[bike:1, Deimos], KeyValue[bike:2, Ares], KeyValue[bike:3,
                    // Vanth]]
                    .toCompletableFuture();

code:
Map<String, String> bikeMap = new HashMap<>();
            bikeMap.put("bike:1", "Deimos");
            bikeMap.put("bike:2", "Ares");
            bikeMap.put("bike:3", "Vanth");

            Mono<Void> mset = reactiveCommands.mset(bikeMap).doOnNext(System.out::println) // OK
                    .flatMap(v -> reactiveCommands.mget("bike:1", "bike:2", "bike:3").collectList()).doOnNext(res -> {
                        System.out.println(res); // [KeyValue[bike:1, Deimos], KeyValue[bike:2, Ares], KeyValue[bike:3, Vanth]]
                    }).then();

code:
String res5 = jedis.mset("bike:1", "Deimos", "bike:2", "Ares", "bike:3", "Vanth");
      System.out.println(res5); // OK
      List<String> res6 = jedis.mget("bike:1", "bike:2", "bike:3");
      System.out.println(res6); // [Deimos, Ares, Vanth]

code:
const res5 = await client.mSet([
  ["bike:1", "Deimos"],
  ["bike:2", "Ares"],
  ["bike:3", "Vanth"]
]);

console.log(res5);  // OK
const res6 = await client.mGet(["bike:1", "bike:2", "bike:3"]);
console.log(res6);  // ['Deimos', 'Ares', 'Vanth']

code:
$res5 = $r->mset([
            'bike:1' => 'Deimos', 'bike:2' => 'Ares', 'bike:3' => 'Vanth'
        ]);
        echo "$res5" . PHP_EOL;
        // >>> OK

        $res6 = $r->mget(['bike:1', 'bike:2', 'bike:3']);
        echo json_encode($res6) . PHP_EOL;
        // >>> ["Deimos","Ares","Vanth"]

code:
res5 = r.mset({"bike:1": "Deimos", "bike:2": "Ares", "bike:3": "Vanth"})
print(res5)  # True
res6 = r.mget(["bike:1", "bike:2", "bike:3"])
print(res6)  # ['Deimos', 'Ares', 'Vanth']

code:
if let Ok(res) = r.mset(&[("bike:1", "Deimos"), ("bike:2", "Ares"), ("bike:3", "Vanth")]).await {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.mget(&["bike:1", "bike:2", "bike:3"]).await {
            Ok(res) => {
                let res: Vec<String> = res;
                println!("{res:?}");   // >>> ["Deimos", "Ares", "Vanth"]
            },
            Err(e) => {
                println!("Error getting foo: {e}");
                return;
            }
        };

code:
if let Ok(res) = r.mset(&[("bike:1", "Deimos"), ("bike:2", "Ares"), ("bike:3", "Vanth")]) {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.mget(&["bike:1", "bike:2", "bike:3"]) {
            Ok(res) => {
                let res: Vec<String> = res;
                println!("{res:?}");   // >>> ["Deimos", "Ares", "Vanth"]
            },
            Err(e) => {
                println!("Error getting values: {e}");
                return;
            }
        };

code:
> SET total_crashes 0
OK
> INCR total_crashes
(integer) 1
> INCRBY total_crashes 10
(integer) 11

section_id: strings-as-counters

code:
db.StringSet("total_crashes", 0);
        var res7 = db.StringIncrement("total_crashes");
        Console.WriteLine(res7); // 1
        var res8 = db.StringIncrement("total_crashes", 10);
        Console.WriteLine(res8);

code:
res8, err := rdb.Set(ctx, "total_crashes", "0", 0).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res8) // >>> OK

	res9, err := rdb.Incr(ctx, "total_crashes").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res9) // >>> 1

	res10, err := rdb.IncrBy(ctx, "total_crashes", 10).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res10) // >>> 11

code:
CompletableFuture<Void> incrby = asyncCommands.set("total_crashes", "0")
                    .thenCompose(v -> asyncCommands.incr("total_crashes")).thenCompose(v -> {
                        System.out.println(v); // >>> 1
                        return asyncCommands.incrby("total_crashes", 10);
                    })
                    .thenAccept(System.out::println) // >>> 11
                    .toCompletableFuture();

code:
Mono<Void> incrby = reactiveCommands.set("total_crashes", "0").flatMap(v -> reactiveCommands.incr("total_crashes"))
                    .doOnNext(v -> {
                        System.out.println(v); // 1
                    }).flatMap(v -> reactiveCommands.incrby("total_crashes", 10)).doOnNext(res -> {
                        System.out.println(res); // 11
                    }).then();

code:
jedis.set("total_crashes", "0");
      Long res7 = jedis.incr("total_crashes");
      System.out.println(res7); // 1
      Long res8 = jedis.incrBy("total_crashes", 10);
      System.out.println(res8); // 11

code:
await client.set("total_crashes", 0);
const res7 = await client.incr("total_crashes");
console.log(res7); // 1
const res8 = await client.incrBy("total_crashes", 10);
console.log(res8); // 11

code:
$r->set('total_crashes', 0);
        $res7 = $r->incr('total_crashes');
        echo "$res7" . PHP_EOL;
        // >>> 1

        $res8 = $r->incrby('total_crashes', 10);
        echo "$res8" . PHP_EOL;
        // >>> 11

code:
r.set("total_crashes", 0)
res7 = r.incr("total_crashes")
print(res7)  # 1
res8 = r.incrby("total_crashes", 10)
print(res8)  # 11

code:
if let Ok(res) = r.set("total_crashes", 0).await {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        if let Ok(res) = r.incr("total_crashes", 1).await {
            let res: i32 = res;
            println!("{res}");    // >>> 1
        }

        if let Ok(res) = r.incr("total_crashes", 10).await {
            let res: i32 = res;
            println!("{res}");    // >>> 11
        }

code:
if let Ok(res) = r.set("total_crashes", 0) {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        if let Ok(res) = r.incr("total_crashes", 1) {
            let res: i32 = res;
            println!("{res}");    // >>> 1
        }

        if let Ok(res) = r.incr("total_crashes", 10) {
            let res: i32 = res;
            println!("{res}");    // >>> 11
        }


title: Configuration Parameters

summary: Redis time series support multiple configuration parameters.

page_type: content

title: Redis Open Source - set configuration parameters

role: overview

text:
Before Redis 8 in Redis Open Source (version 8.0), all time series configuration parameters are load-time parameters.
Use one of the following methods to set the values of load-time configuration parameters:

- Pass them as command-line arguments following the `loadmodule` argument when starting `redis-server`:

  `redis-server --loadmodule ./{modulename}.so [OPT VAL]...`

- Add them as arguments to the `loadmodule` directive in your configuration file (for example, `redis.conf`):

  `loadmodule ./{modulename}.so [OPT VAL]...`

- Use the `MODULE LOAD path [arg [arg ...]]` command.

- Use the `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ....]]` command.

Starting with Redis 8.0, most time series configuration parameters are runtime parameters.
While you can set runtime parameters at load time, using the Redis `CONFIG` command is easier and works the same way as with Redis runtime configuration parameters.

This means:

- `CONFIG SET parameter value [parameter value ...] `

  Set one or more configuration parameters.

- `CONFIG GET parameter [parameter ...]`

  Read the current value of one of more parameters.

- `CONFIG REWRITE`

  Rewrite your Redis configuration file (for example, the `redis.conf` file) to reflect the configuration changes.

Starting with Redis 8.0, you can specify time series configuration parameters directly in your Redis configuration file the same way you would for Redis configuration parameters.

Once a value is set with `CONFIG SET` or added manually to your configuration file, it will overwrite values set with `--loadmodule`, `loadmodule`, `MODULE LOAD`, or `MODULE LOADEX`.

In a cluster, you must run `CONFIG SET` and `CONFIG REWRITE` on each node separately.

In Redis 8.0, new names for the time series configuration parameters were introduced to align the naming with the Redis configuration parameters.
You must use the new names when using the `CONFIG` command.

See also [Redis configuration]().

title: Time series configuration parameters

role: content

text:
| Parameter name
(version < 8.0) | Parameter name
(version ≥ 8.0) | Run-time | Redis
Software | Redis
Cloud |
| :------- | :------- | :------- | :------- | :------- |
| CHUNK_SIZE_BYTES     | [ts-chunk-size-bytes](#chunk_size_bytes--ts-chunk-size-bytes)    | :white_check_mark:   | ✅ Supported

 | ✅ Flexible & Annual
<nobr>❌ Free & Fixed</nobr> |
| COMPACTION_POLICY    | [ts-compaction-policy](#compaction_policy--ts-compaction-policy) | :white_check_mark:   | ✅ Supported

 | ✅ Flexible & Annual
<nobr>❌ Free & Fixed</nobr> |
| DUPLICATE_POLICY     | [ts-duplicate-policy](#duplicate_policy--ts-duplicate-policy)    | :white_check_mark:   | ✅ Supported

 | ✅ Flexible & Annual
<nobr>❌ Free & Fixed</nobr> |
| RETENTION_POLICY     | [ts-retention-policy](#retention_policy--ts-retention-policy)    | :white_check_mark:   | ✅ Supported

 | ✅ Flexible & Annual
<nobr>❌ Free & Fixed</nobr> |
| ENCODING             | [ts-encoding](#encoding--ts-encoding)                            | :white_check_mark:   | ✅ Supported

 | ✅ Flexible & Annual
<nobr>❌ Free & Fixed</nobr> |
| IGNORE_MAX_TIME_DIFF | [ts-ignore-max-time-diff](#ignore_max_time_diff--ts-ignore-max-time-diff-and-ignore_max_val_diff--ts-ignore-max-val-diff) | :white_check_mark:   |||
| IGNORE_MAX_VAL_DIFF  | [ts-ignore-max-val-diff](#ignore_max_time_diff--ts-ignore-max-time-diff-and-ignore_max_val_diff--ts-ignore-max-val-diff)  | :white_check_mark:   |||
| NUM_THREADS          | [ts-num-threads](#num_threads--ts-num-threads)                   | :white_large_square: | ✅ Supported

 | ❌ Flexible & Annual
<nobr>❌ Free & Fixed</nobr> |
| [OSS_GLOBAL_PASSWORD](#oss_global_password)  | Deprecated in v8.0                       | :white_check_mark:   |||

---

title: CHUNK_SIZE_BYTES / ts-chunk-size-bytes

text:
The initial allocation size, in bytes, for the data part of each new chunk. Actual chunks may consume more memory.
Changing this value does not affect existing chunks.

Type: integer

Valid range: `[48 .. 1048576]`; must be a multiple of 8

#### Precedence order

Because the chunk size can be provided at different levels, the actual precedence of the chunk size is:

1. Key-level policy, as set with [`TS.CREATE`]()'s and [`TS.ALTER`]()'s `CHUNK_SIZE` optional argument.
1. The `ts-chunk-size-bytes` configuration parameter.
1. The hard-coded default: `4096`

#### Example

Set the default chunk size to 1024 bytes:

Version < 8.0:

[code example]

Version >= 8.0:

[code example]

title: COMPACTION_POLICY / ts-compaction-policy

text:
Default compaction rules for newly created keys with [`TS.ADD`](), [`TS.INCRBY`](), and  [`TS.DECRBY`]().

Type: string

Note that this configuration parameter does not affect keys you create with [`TS.CREATE`](). To understand why, consider the following scenario: Suppose you define a default compaction policy but then want to manually create an additional compaction rule (using [`TS.CREATERULE`]()), which requires you to first create an empty destination key (using `TS.CREATE`). This approach creates a problem: the default compaction policy would cause Redis to automatically create undesired compactions for the destination key.

Each rule is separated by a semicolon (`;`), the rule consists of multiple fields that are separated by a colon (`:`):

* Aggregation type: One of the following:

  | Aggregator | Description                                                      |
  | ---------- | ---------------------------------------------------------------- |
  | `avg`      | Arithmetic mean of all values                                    |
  | `sum`      | Sum of all values                                                |
  | `min`      | Minimum value                                                    |
  | `max`      | Maximum value                                                    |
  | `range`    | Difference between the highest and the lowest value              |
  | `count`    | Number of values                                                 |
  | `first`    | The value with the lowest timestamp in the bucket                |
  | `last`     | The value with the highest timestamp in the bucket               |
  | `std.p`    | Population standard deviation of the values                      |
  | `std.s`    | Sample standard deviation of the values                          |
  | `var.p`    | Population variance of the values                                |
  | `var.s`    | Sample variance of the values                                    |
  | `twa`      | Time-weighted average of all values (since v1.8)                 |

* Duration of each time bucket - number and the time representation (Example for one minute: `1M`, `60s`, or `60000m`)

    * m - millisecond
    * s - seconds
    * M - minute
    * h - hour
    * d - day

* Retention time - number and the time representation (Example for one minute: `1M`, `60s`, or `60000m`)

    * m - millisecond
    * s - seconds
    * M - minute
    * h - hour
    * d - day

  `0m`, `0s`, `0M`, `0h`, or `0d` means no expiration.

* (Since v1.8):

  Optional: Time bucket alignment - number and the time representation (Example for one minute: `1M`, `60s`, or `60000m`)

    * m - millisecond
    * s - seconds
    * M - minute
    * h - hour
    * d - day

  Ensure that there is a bucket that starts at exactly _alignTimestamp_ after the Epoch and align all other buckets accordingly. Default value: 0 (aligned with the Epoch). Example: if _bucketDuration_ is 24 hours, setting _alignTimestamp_ to `6h` (6 hours after the Epoch) will ensure that each bucket’s timeframe is [06:00 .. 06:00).

In a clustered environment, if you set this configuration parameter, you must use [hash tags](#hash-tags) for all time series key names. This ensures that Redis will create each compaction in the same hash slot as its source key. If you don't, the system may fail to compact the data without displaying any error messages.

When a compaction policy is defined, compaction rules are created automatically for newly created time series, and the compaction key name would be:

* If the time bucket alignment is 0:

   _key_agg_dur_ where _key_ is the key of the source time series, _agg_ is the aggregator (in uppercase), and _dur_ is the bucket duration in milliseconds. Example: `key_SUM_60000`.

* If the time bucket alignment is not 0:

   _key_agg_dur_aln_ where _key_ is the key of the source time series, _agg_ is the aggregator (in uppercase), _dur_ is the bucket duration in milliseconds, and _aln_ is the time bucket alignment in milliseconds. Example: `key_SUM_60000_1000`.

#### Precedence order

1. The `ts-compaction-policy` configuration parameter.
1. No compaction rules.

#### Example rules

- `max:1M:1h` - Aggregate using `max` over one-minute windows and retain the last hour
- `twa:1d:0m:360M` - Aggregate daily [06:00 .. 06:00) using `twa`; no expiration

#### Example

Set a compaction policy composed of 5 compaction rules:

Version < 8.0:

[code example]

Version >= 8.0:

[code example]

title: DUPLICATE_POLICY / ts-duplicate-policy

text:
The default policy for handling insertion ([`TS.ADD`]() and [`TS.MADD`]()) of multiple samples with identical timestamps, with one of the following values:

  | policy     | description                                                      |
  | ---------- | ---------------------------------------------------------------- |
  | `BLOCK`    | Ignore any newly reported value and reply with an error          |
  | `FIRST`    | Ignore any newly reported value                                  |
  | `LAST`     | Override with the newly reported value                           |
  | `MIN`      | Only override if the value is lower than the existing value      |
  | `MAX`      | Only override if the value is higher than the existing value     |
  | `SUM`      | If a previous sample exists, add the new sample to it so that the updated value is equal to (previous + new). If no previous sample exists, set the updated value equal to the new value. |

The default value is applied to each new time series upon its creation.

Type: string

#### Precedence order

Because the duplication policy can be provided at different levels, the actual precedence of the duplication policy is:

1. [`TS.ADD`]()'s `ON_DUPLICATE_POLICY` optional argument.
1. Key-level policy, as set with [`TS.CREATE`]()'s and [`TS.ALTER`]()'s `DUPLICATE_POLICY` optional argument.
1. The `ts-duplicate-policy` configuration parameter.
1. The hard-coded default: `BLOCK`

title: RETENTION_POLICY / ts-retention-policy

text:
The default retention period, in milliseconds, for newly created keys.

The retention period is the maximum age of samples compared to the highest reported timestamp, per key. Samples are expired based solely on the difference between their timestamps and the timestamps passed to subsequent [`TS.ADD`](), [`TS.MADD`](), [`TS.INCRBY`](), and [`TS.DECRBY`]() calls.

Type: integer

Valid range: `[0 .. 9,223,372,036,854,775,807]`

The value `0` means no expiration.

When both `COMPACTION_POLICY` / `ts-compaction-policy` and `RETENTION_POLICY` / `ts-retention-policy` are specified, the retention of newly created compactions is according to the retention time specified in `COMPACTION_POLICY` / `ts-compaction-policy`.

#### Precedence order

Because the retention can be provided at different levels, the actual precedence of the retention is:

1. Key-level retention, as set with [`TS.CREATE`]()'s and [`TS.ALTER`]()'s `RETENTION` optional argument.
1. The `ts-retention-policy` configuration parameter.
1. No retention.

#### Example

Set the default retention to 300 days:

Version < 8.0:

[code example]

Version >= 8.0:

[code example]

title: ENCODING / ts-encoding

text:
Note: Before v1.6 this configuration parameter was named `CHUNK_TYPE`.

Default chunk encoding for automatically created compactions when [ts-compaction-policy](#ts-compaction-policy) is configured.

Type: string

Valid values: `COMPRESSED`, `UNCOMPRESSED`

#### Precedence order

1. The `ts-encoding` configuration parameter.
1. The hard-coded default: `COMPRESSED`

#### Example

Set the default encoding to `UNCOMPRESSED`:

Version < 8.0:

[code example]

Version >= 8.0:

[code example]

title: IGNORE_MAX_TIME_DIFF / ts-ignore-max-time-diff and IGNORE_MAX_VAL_DIFF / ts-ignore-max-val-diff

text:
Default values for newly created keys.

Types:
- `ts-ignore-max-time-diff`: integer
- `ts-ignore-max-val-diff`: double

Valid ranges:
- `ts-ignore-max-time-diff`: `[0 .. 9,223,372,036,854,775,807]`
- `ts-ignore-max-val-diff`: `[0 .. 1.7976931348623157e+308]`

Many sensors report data periodically. Often, the difference between the measured value and the previous measured value is negligible and related to random noise or to measurement accuracy limitations. In such situations it may be preferable not to add the new measurement to the time series.

A new sample is considered a duplicate and is ignored if the following conditions are met:

1. The time series is not a compaction.
1. The time series' `ts-duplicate-policy` is `LAST`.
1. The sample is added in-order (`timestamp ≥ max_timestamp`).
1. The difference of the current timestamp from the previous timestamp (`timestamp - max_timestamp`) is less than or equal to `ts-ignore-max-time-diff`.
1. The absolute value difference of the current value from the value at the previous maximum timestamp (`abs(value - value_at_max_timestamp`) is less than or equal to `ts-ignore-max-val-diff`.

where `max_timestamp` is the timestamp of the sample with the largest timestamp in the time series, and `value_at_max_timestamp` is the value at `max_timestamp`.

#### Precedence order

1. The `ts-ignore-max-time-diff` and `ts-ignore-max-val-diff` configuration parameters.
1. The hard-coded defaults: `0` and `0.0`.

#### Example

Version < 8.0:

[code example]

Version >= 8.0:

[code example]

title: NUM_THREADS / ts-num-threads

text:
The maximum number of per-shard threads for cross-key queries when using cluster mode ([`TS.MRANGE`](), [`TS.MREVRANGE`](), [`TS.MGET`](), and [`TS.QUERYINDEX`]()). The value must be equal to or greater than `1`. Note that increasing this value may either increase or decrease the performance!

Type: integer

Valid range: `[1..16]`

Redis Open Source default: `3`

Redis Software default: Set by plan, and automatically updates when you change your plan.

Redis Cloud defaults:
- Flexible & Annual: Set by plan
- Free & Fixed: `1`

#### Example

Version < 8.0:

[code example]

Version >= 8.0:

[code example]

title: OSS_GLOBAL_PASSWORD

text: Prior to version 8.0, when using time series in a cluster, you had to set the `OSS_GLOBAL_PASSWORD` configuration parameter on all cluster nodes. As of version 8.0, Redis no longer uses this parameter and ignores it if present. Redis now uses a new shared secret mechanism to send internal commands between cluster nodes.

code: $ redis-server --loadmodule ./redistimeseries.so CHUNK_SIZE_BYTES 1024

section_id: chunk-size-bytes-ts-chunk-size-bytes

code: redis> CONFIG SET ts-chunk-size-bytes 1024

code: $ redis-server --loadmodule ./redistimeseries.so COMPACTION_POLICY max:1m:1h;min:10s:5d:10d;last:5M:10m;avg:2h:10d;avg:3d:100d

section_id: compaction-policy-ts-compaction-policy

code: redis> CONFIG SET ts-compaction-policy max:1m:1h;min:10s:5d:10d;last:5M:10m;avg:2h:10d;avg:3d:100d

code: $ redis-server --loadmodule ./redistimeseries.so RETENTION_POLICY 25920000000

section_id: retention-policy-ts-retention-policy

code: redis> CONFIG SET ts-retention-policy 25920000000

code: $ redis-server --loadmodule ./redistimeseries.so ENCODING UNCOMPRESSED

section_id: encoding-ts-encoding

code: redis> CONFIG SET ts-encoding UNCOMPRESSED

code: $ redis-server --loadmodule ./redistimeseries.so IGNORE_MAX_TIME_DIFF 10 IGNORE_MAX_VAL_DIFF 0.1

code: redis> CONFIG SET ts-ignore-max-time-diff 10 ts-ignore-max-val-diff 0.1

code: $ redis-server --loadmodule ./redistimeseries.so NUM_THREADS 3

section_id: num-threads-ts-num-threads

code: redis> redis-server --loadmodule ./redistimeseries.so ts-num-threads 3