# Pipelines and transactions

```json metadata
{
  "title": "Pipelines and transactions",
  "description": "Learn how to use Redis pipelines and transactions",
  "categories": ["docs","develop","stack","oss","rs","rc","oss","kubernetes","clients"],
  "tableOfContents": {"sections":[{"id":"execute-a-pipeline","title":"Execute a pipeline"},{"id":"execute-a-transaction","title":"Execute a transaction"},{"id":"watch-keys-for-changes","title":"Watch keys for changes"}]}

,
  "codeExamples": [{"codetabsId":"pipe_trans_tutorial-stepbasic_pipe","description":"Foundational: Batch multiple Lettuce commands and flush them to the server together","difficulty":"beginner","id":"basic_pipe","languages":[{"clientId":"lettuce","clientName":"Lettuce","id":"Lettuce-Sync","langId":"java","panelId":"panel_Lettuce-Sync_pipe_trans_tutorial-stepbasic_pipe"},{"clientId":"lettuce","clientName":"Lettuce","id":"Java-Async","langId":"java","panelId":"panel_Java-Async_pipe_trans_tutorial-stepbasic_pipe"},{"clientId":"lettuce","clientName":"Lettuce","id":"Java-Reactive","langId":"java","panelId":"panel_Java-Reactive_pipe_trans_tutorial-stepbasic_pipe"}]},{"codetabsId":"pipe_trans_tutorial-stepbasic_trans","description":"Foundational: Use MULTI and EXEC with Lettuce to execute multiple commands atomically","difficulty":"beginner","id":"basic_trans","languages":[{"clientId":"lettuce","clientName":"Lettuce","id":"Lettuce-Sync","langId":"java","panelId":"panel_Lettuce-Sync_pipe_trans_tutorial-stepbasic_trans"},{"clientId":"lettuce","clientName":"Lettuce","id":"Java-Async","langId":"java","panelId":"panel_Java-Async_pipe_trans_tutorial-stepbasic_trans"},{"clientId":"lettuce","clientName":"Lettuce","id":"Java-Reactive","langId":"java","panelId":"panel_Java-Reactive_pipe_trans_tutorial-stepbasic_trans"}]},{"codetabsId":"pipe_trans_tutorial-steptrans_watch","description":"Optimistic locking: Use WATCH with EXEC and check whether Lettuce discarded the transaction","difficulty":"intermediate","id":"trans_watch","languages":[{"clientId":"lettuce","clientName":"Lettuce","id":"Lettuce-Sync","langId":"java","panelId":"panel_Lettuce-Sync_pipe_trans_tutorial-steptrans_watch"},{"clientId":"lettuce","clientName":"Lettuce","id":"Java-Async","langId":"java","panelId":"panel_Java-Async_pipe_trans_tutorial-steptrans_watch"},{"clientId":"lettuce","clientName":"Lettuce","id":"Java-Reactive","langId":"java","panelId":"panel_Java-Reactive_pipe_trans_tutorial-steptrans_watch"}]}]
}
```## Code Examples Legend

The code examples below show how to perform the same operations in different programming languages and client libraries:

- **Redis CLI**: Command-line interface for Redis
- **C# (Synchronous)**: StackExchange.Redis synchronous client
- **C# (Asynchronous)**: StackExchange.Redis asynchronous client
- **Go**: go-redis client
- **Java (Synchronous - Jedis)**: Jedis synchronous client
- **Java (Asynchronous - Lettuce)**: Lettuce asynchronous client
- **Java (Reactive - Lettuce)**: Lettuce reactive/streaming client
- **JavaScript (Node.js)**: node-redis client
- **PHP**: Predis client
- **Python**: redis-py client
- **Rust (Synchronous)**: redis-rs synchronous client
- **Rust (Asynchronous)**: redis-rs asynchronous client

Each code example demonstrates the same basic operation across different languages. The specific syntax and patterns vary based on the language and client library, but the underlying Redis commands and behavior remain consistent.

---


Redis lets you send a sequence of commands to the server together in a batch.
There are two types of batch that you can use:

-   **Pipelines** avoid network and processing overhead by sending several commands
    to the server together in a single communication. The server then sends back
    a single communication with all the responses. See the
    [Pipelining](https://redis.io/docs/latest/develop/using-commands/pipelining) page for more
    information.
-   **Transactions** guarantee that all the included commands will execute
    to completion without being interrupted by commands from other clients.
    See the [Transactions](https://redis.io/docs/latest/develop/using-commands/transactions)
    page for more information.

## Execute a pipeline

With Lettuce, you pipeline commands by buffering them on the client and then
flushing them to the server together. The synchronous and asynchronous APIs do
this by temporarily disabling auto-flushing with `setAutoFlushCommands()` and
then calling `flushCommands()`; each buffered command returns a `RedisFuture`
that you can inspect after the batch is flushed. The reactive API pipelines
commands by composing the corresponding `Mono` and `Flux` publishers:

Foundational: Batch multiple Lettuce commands and flush them to the server together

**Difficulty:** Beginner

**Available in:** C#, C#, Go, Java, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), PHP, Python, Ruby, Rust (Synchronous)

##### C#

```csharp
        var setTasks = new[]
        {
            db.StringSetAsync("seat:0", "#0"),
            db.StringSetAsync("seat:1", "#1"),
            db.StringSetAsync("seat:2", "#2"),
            db.StringSetAsync("seat:3", "#3"),
            db.StringSetAsync("seat:4", "#4")
        };
        foreach (var setTask in setTasks)
        {
            db.Wait(setTask);
        }

        var resp1Task = db.StringGetAsync("seat:0");
        var resp2Task = db.StringGetAsync("seat:3");
        var resp3Task = db.StringGetAsync("seat:4");

        var resp1 = db.Wait(resp1Task);
        Console.WriteLine(resp1); // >>> #0

        var resp2 = db.Wait(resp2Task);
        Console.WriteLine(resp2); // >>> #3

        var resp3 = db.Wait(resp3Task);
        Console.WriteLine(resp3); // >>> #4
```

##### C#

```csharp
        var pipeline = new Pipeline(db);

        for (int i = 0; i < 5; i++)
        {
            _ = pipeline.Db.StringSetAsync($"seat:{i}", $"#{i}");
        }
        pipeline.Execute();

        var resp1 = db.StringGet("seat:0");
        Console.WriteLine(resp1); // >>> #0

        var resp2 = db.StringGet("seat:3");
        Console.WriteLine(resp2); // >>> #3

        var resp3 = db.StringGet("seat:4");
        Console.WriteLine(resp2); // >>> #4
```

##### C#

```csharp
        var setTasks = new[]
        {
            db.StringSetAsync("seat:0", "#0"),
            db.StringSetAsync("seat:1", "#1"),
            db.StringSetAsync("seat:2", "#2"),
            db.StringSetAsync("seat:3", "#3"),
            db.StringSetAsync("seat:4", "#4")
        };
        foreach (var setTask in setTasks)
        {
            db.Wait(setTask);
        }

        var resp1Task = db.StringGetAsync("seat:0");
        var resp2Task = db.StringGetAsync("seat:3");
        var resp3Task = db.StringGetAsync("seat:4");

        var resp1 = db.Wait(resp1Task);
        Console.WriteLine(resp1); // >>> #0

        var resp2 = db.Wait(resp2Task);
        Console.WriteLine(resp2); // >>> #3

        var resp3 = db.Wait(resp3Task);
        Console.WriteLine(resp3); // >>> #4
```

##### C#

```csharp
        var pipeline = new Pipeline(db);

        for (int i = 0; i < 5; i++)
        {
            _ = pipeline.Db.StringSetAsync($"seat:{i}", $"#{i}");
        }
        pipeline.Execute();

        var resp1 = db.StringGet("seat:0");
        Console.WriteLine(resp1); // >>> #0

        var resp2 = db.StringGet("seat:3");
        Console.WriteLine(resp2); // >>> #3

        var resp3 = db.StringGet("seat:4");
        Console.WriteLine(resp2); // >>> #4
```

##### Go

```go
	pipe := rdb.Pipeline()

	for i := 0; i < 5; i++ {
		pipe.Set(ctx, fmt.Sprintf("seat:%v", i), fmt.Sprintf("#%v", i), 0)
	}

	cmds, err := pipe.Exec(ctx)

	if err != nil {
		panic(err)
	}

	for _, c := range cmds {
		fmt.Printf("%v;", c.(*redis.StatusCmd).Val())
	}

	fmt.Println("")
	// >>> OK;OK;OK;OK;OK;

	pipe = rdb.Pipeline()

	get0Result := pipe.Get(ctx, "seat:0")
	get3Result := pipe.Get(ctx, "seat:3")
	get4Result := pipe.Get(ctx, "seat:4")

	cmds, err = pipe.Exec(ctx)

	// The results are available only after the pipeline
	// has finished executing.
	fmt.Println(get0Result.Val()) // >>> #0
	fmt.Println(get3Result.Val()) // >>> #3
	fmt.Println(get4Result.Val()) // >>> #4
```

##### Java

```java
            // Lettuce pipelines commands by buffering them on the client while
            // auto-flushing is disabled. Use the asynchronous API to obtain a
            // `RedisFuture` for each buffered command, then read the results
            // after the batch has been flushed to the server.
            RedisAsyncCommands<String, String> async = connection.async();
            connection.setAutoFlushCommands(false);

            for (int i = 0; i < 5; i++) {
                async.set("seat:" + i, "#" + i);
            }

            RedisFuture<String> seat0 = async.get("seat:0");
            RedisFuture<String> seat3 = async.get("seat:3");
            RedisFuture<String> seat4 = async.get("seat:4");

            connection.flushCommands();
            connection.setAutoFlushCommands(true);

            System.out.println(seat0.get()); // >>> #0
            System.out.println(seat3.get()); // >>> #3
            System.out.println(seat4.get()); // >>> #4
```

##### Java (Asynchronous - Lettuce)

```java
            // Disable auto-flushing to buffer the commands on the client, then
            // flush them to the server as a single batch. Each command returns
            // a `RedisFuture` that completes when its response arrives.
            connection.setAutoFlushCommands(false);

            for (int i = 0; i < 5; i++) {
                async.set("seat:" + i, "#" + i);
            }

            RedisFuture<String> seat0 = async.get("seat:0");
            RedisFuture<String> seat3 = async.get("seat:3");
            RedisFuture<String> seat4 = async.get("seat:4");

            connection.flushCommands();
            connection.setAutoFlushCommands(true);

            System.out.println(seat0.get()); // >>> #0
            System.out.println(seat3.get()); // >>> #3
            System.out.println(seat4.get()); // >>> #4
```

##### Java (Reactive - Lettuce)

```java
            // Reactive commands are pipelined automatically: the writes below are
            // sent to the server back to back, then the reads are sent as a batch
            // once you subscribe (here, by calling `block()`).
            List<String> seats = Flux.range(0, 5)
                    .flatMap(i -> reactive.set("seat:" + i, "#" + i))
                    .thenMany(Flux.just("seat:0", "seat:3", "seat:4")
                            .flatMapSequential(reactive::get))
                    .collectList()
                    .block();

            System.out.println(seats.get(0)); // >>> #0
            System.out.println(seats.get(1)); // >>> #3
            System.out.println(seats.get(2)); // >>> #4
```

##### Java (Synchronous - Jedis)

```java
        // Make sure you close the pipeline after use to release resources
        // and return the connection to the pool.
        try (AbstractPipeline pipe = jedis.pipelined()) {

            for (int i = 0; i < 5; i++) {
                pipe.set(String.format("seat:%d", i), String.format("#%d", i));
            }

            pipe.sync();
        }

        try (AbstractPipeline pipe = jedis.pipelined()) {

            Response<String> resp0 = pipe.get("seat:0");
            Response<String> resp3 = pipe.get("seat:3");
            Response<String> resp4 = pipe.get("seat:4");

            pipe.sync();

            // Responses are available after the pipeline has executed.
            System.out.println(resp0.get()); // >>> #0
            System.out.println(resp3.get()); // >>> #3
            System.out.println(resp4.get()); // >>> #4


        }
```

##### PHP

```php
$pipe = $r->pipeline();
$pipe->set('seat:0', '#0')
    ->set('seat:1', '#1')
    ->set('seat:2', '#2')
    ->set('seat:3', '#3')
    ->set('seat:4', '#4');
$pipe->execute();

$pipe = $r->pipeline();
$pipe->get('seat:0')
    ->get('seat:1')
    ->get('seat:2')
    ->get('seat:3')
    ->get('seat:4');
$seats = $pipe->execute();

echo implode(', ', $seats), PHP_EOL;
// >>> #0, #1, #2, #3, #4

$responses = $r->pipeline(function ($pipe) {
    $pipe->set('seat:5', '#5');
    $pipe->set('seat:6', '#6');
    $pipe->set('seat:7', '#7');
    $pipe->get('seat:5');
    $pipe->get('seat:6');
    $pipe->get('seat:7');
});

echo implode(', ', array_slice($responses, 3)), PHP_EOL;
// >>> #5, #6, #7
```

##### Python

```python
r = redis.Redis(decode_responses=True)

pipe = r.pipeline()

for i in range(5):
    pipe.set(f"seat:{i}", f"#{i}")

set_5_result = pipe.execute()
print(set_5_result)  # >>> [True, True, True, True, True]

pipe = r.pipeline()

# "Chain" pipeline commands together.
get_3_result = pipe.get("seat:0").get("seat:3").get("seat:4").execute()
print(get_3_result)  # >>> ['#0', '#3', '#4']
```

##### Ruby

```ruby
r.pipelined do |pipe|
  (0..4).each { |i| pipe.set("seat:#{i}", "##{i}") }
end

seats = r.pipelined do |pipe|
  pipe.get('seat:0')
  pipe.get('seat:3')
  pipe.get('seat:4')
end

puts seats[0] # >>> #0
puts seats[1] # >>> #3
puts seats[2] # >>> #4
```

##### Rust (Synchronous)

```rust
        // Check the success of the pipeline without checking the results
        // individually.
        match redis::pipe()
            .set("seat:0", "#0")
            .set("seat:1", "#1")
            .set("seat:2", "#2")
            .set("seat:3", "#3")
            .set("seat:4", "#4")
            .exec(&mut r)
        {
            Ok(_) => {
                println!("Pipe executed successfully");
            },
            Err(e) => {
                println!("Error executing pipe: {e}");
                return;
            }
        };
        
        // Check the success of the pipeline and the results individually.
        let (seat_0, seat_1, seat_2, seat_3, seat_4) :
        (String, String, String, String, String) = match redis::pipe()
            .get("seat:0")
            .get("seat:1")
            .get("seat:2")
            .get("seat:3")
            .get("seat:4")
            .query(&mut r) {
                Ok(res) => res,
                Err(e) => {
                    println!("Error executing pipe: {e}");
                    return;
                }
            };

        println!("{seat_0}, {seat_1}, {seat_2}, {seat_3}, {seat_4}");
        // >>> #0, #1, #2, #3, #4

        // Use `ignore()` to ignore the result of specific commands.
        let (seat_5, seat_6, seat_7) :
            (String, String, String) = match redis::pipe()
            .set("seat:5", "#5").ignore()
            .set("seat:6", "#6").ignore()
            .set("seat:7", "#7").ignore()
            .get("seat:5")
            .get("seat:6")
            .get("seat:7")
            .query(&mut r) {
                Ok(res) => res,
                Err(e) => {
                    println!("Error executing pipe: {e}");
                    return;
                }
            };

        println!("{seat_5}, {seat_6}, {seat_7}");
        // >>> #5, #6, #7
```



## Execute a transaction

Lettuce transactions use the Redis `MULTI` and `EXEC` commands. Commands issued
between `multi()` and `exec()` are queued on the server and only run when
`exec()` completes. The return value from `exec()` is a `TransactionResult`
that contains the results in order:

Foundational: Use MULTI and EXEC with Lettuce to execute multiple commands atomically

**Difficulty:** Beginner

**Available in:** C#, C#, Go, Java, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), PHP, Python, Ruby, Rust (Synchronous)

##### C#

```csharp
        var trans = db.CreateTransaction();

        var incr1 = trans.StringIncrementAsync("counter:1", 1);
        var incr2 = trans.StringIncrementAsync("counter:2", 2);
        var incr3 = trans.StringIncrementAsync("counter:3", 3);

        bool committed = db.Wait(trans.ExecuteAsync());

        var resp4 = db.Wait(incr1);
        Console.WriteLine(resp4); // >>> 1

        var resp5 = db.Wait(incr2);
        Console.WriteLine(resp5); // >>> 2

        var resp6 = db.Wait(incr3);
        Console.WriteLine(resp6);  // >>> 3
```

##### C#

```csharp
        var trans = new Transaction(db);

        _ = trans.Db.StringIncrementAsync("counter:1", 1);
        _ = trans.Db.StringIncrementAsync("counter:2", 2);
        _ = trans.Db.StringIncrementAsync("counter:3", 3);

        trans.Execute();

        var resp4 = db.StringGet("counter:1");
        Console.WriteLine(resp4); // >>> 1

        var resp5 = db.StringGet("counter:2");
        Console.WriteLine(resp5); // >>> 2

        var resp6 = db.StringGet("counter:3");
        Console.WriteLine(resp6);  // >>> 3
```

##### C#

```csharp
        var trans = db.CreateTransaction();

        var incr1 = trans.StringIncrementAsync("counter:1", 1);
        var incr2 = trans.StringIncrementAsync("counter:2", 2);
        var incr3 = trans.StringIncrementAsync("counter:3", 3);

        bool committed = db.Wait(trans.ExecuteAsync());

        var resp4 = db.Wait(incr1);
        Console.WriteLine(resp4); // >>> 1

        var resp5 = db.Wait(incr2);
        Console.WriteLine(resp5); // >>> 2

        var resp6 = db.Wait(incr3);
        Console.WriteLine(resp6);  // >>> 3
```

##### C#

```csharp
        var trans = new Transaction(db);

        _ = trans.Db.StringIncrementAsync("counter:1", 1);
        _ = trans.Db.StringIncrementAsync("counter:2", 2);
        _ = trans.Db.StringIncrementAsync("counter:3", 3);

        trans.Execute();

        var resp4 = db.StringGet("counter:1");
        Console.WriteLine(resp4); // >>> 1

        var resp5 = db.StringGet("counter:2");
        Console.WriteLine(resp5); // >>> 2

        var resp6 = db.StringGet("counter:3");
        Console.WriteLine(resp6);  // >>> 3
```

##### Go

```go
	trans := rdb.TxPipeline()

	trans.IncrBy(ctx, "counter:1", 1)
	trans.IncrBy(ctx, "counter:2", 2)
	trans.IncrBy(ctx, "counter:3", 3)

	cmds, err = trans.Exec(ctx)

	for _, c := range cmds {
		fmt.Println(c.(*redis.IntCmd).Val())
	}
	// >>> 1
	// >>> 2
	// >>> 3
```

##### Java

```java
            commands.multi();

            commands.incrby("counter:1", 1);
            commands.incrby("counter:2", 2);
            commands.incrby("counter:3", 3);

            TransactionResult transResult = commands.exec();

            System.out.println((Long) transResult.get(0)); // >>> 1
            System.out.println((Long) transResult.get(1)); // >>> 2
            System.out.println((Long) transResult.get(2)); // >>> 3
```

##### Java (Asynchronous - Lettuce)

```java
            // Commands issued between `multi()` and `exec()` are queued on the
            // server. Their futures only complete when `exec()` runs.
            async.multi();

            RedisFuture<Long> counter1 = async.incrby("counter:1", 1);
            RedisFuture<Long> counter2 = async.incrby("counter:2", 2);
            RedisFuture<Long> counter3 = async.incrby("counter:3", 3);

            RedisFuture<TransactionResult> execResult = async.exec();
            execResult.get();

            System.out.println(counter1.get()); // >>> 1
            System.out.println(counter2.get()); // >>> 2
            System.out.println(counter3.get()); // >>> 3
```

##### Java (Reactive - Lettuce)

```java
            // Queue the commands between `multi()` and `exec()`. Subscribing to
            // each one inside the `multi()` callback sends it to the server, where
            // it is held until `exec()` runs the transaction.
            TransactionResult transResult = reactive.multi()
                    .flatMap(ok -> {
                        reactive.incrby("counter:1", 1).subscribe();
                        reactive.incrby("counter:2", 2).subscribe();
                        reactive.incrby("counter:3", 3).subscribe();
                        return reactive.exec();
                    })
                    .block();

            System.out.println((Long) transResult.get(0)); // >>> 1
            System.out.println((Long) transResult.get(1)); // >>> 2
            System.out.println((Long) transResult.get(2)); // >>> 3
```

##### Java (Synchronous - Jedis)

```java
        try ( AbstractTransaction trans = jedis.multi()) {

           trans.incrBy("counter:1", 1);
           trans.incrBy("counter:2", 2);
           trans.incrBy("counter:3", 3);

           trans.exec();
        }
        System.out.println(jedis.get("counter:1")); // >>> 1
        System.out.println(jedis.get("counter:2")); // >>> 2
        System.out.println(jedis.get("counter:3")); // >>> 3
```

##### PHP

```php
$r->transaction(function (MultiExec $tx) {
    $tx->incr('counter:1');
    $tx->incrby('counter:2', 2);
    $tx->incrby('counter:3', 3);
});

echo implode(', ', $r->mget('counter:1', 'counter:2', 'counter:3')), PHP_EOL;
// >>> 1, 2, 3
```

##### Python

```python
"""
Code samples for vector database quickstart pages:
    https://redis.io/docs/latest/develop/get-started/vector-database/
"""
import redis

r = redis.Redis(decode_responses=True)

pipe = r.pipeline()

for i in range(5):
    pipe.set(f"seat:{i}", f"#{i}")

set_5_result = pipe.execute()
print(set_5_result)  # >>> [True, True, True, True, True]

pipe = r.pipeline()

# "Chain" pipeline commands together.
get_3_result = pipe.get("seat:0").get("seat:3").get("seat:4").execute()
print(get_3_result)  # >>> ['#0', '#3', '#4']

r.set("shellpath", "/usr/syscmds/")

with r.pipeline() as pipe:
    # Repeat until successful.
    while True:
        try:
            # Watch the key we are about to change.
            pipe.watch("shellpath")

            # The pipeline executes commands directly (instead of
            # buffering them) from immediately after the `watch()`
            # call until we begin the transaction.
            current_path = pipe.get("shellpath")
            new_path = current_path + ":/usr/mycmds/"

            # Start the transaction, which will enable buffering
            # again for the remaining commands.
            pipe.multi()

            pipe.set("shellpath", new_path)

            pipe.execute()

            # The transaction succeeded, so break out of the loop.
            break
        except redis.WatchError:
            # The transaction failed, so continue with the next attempt.
            continue

get_path_result = r.get("shellpath")
print(get_path_result)  # >>> '/usr/syscmds/:/usr/mycmds/'

r.set("shellpath", "/usr/syscmds/")


def watched_sequence(pipe):
    current_path = pipe.get("shellpath")
    new_path = current_path + ":/usr/mycmds/"

    pipe.multi()

    pipe.set("shellpath", new_path)


trans_result = r.transaction(watched_sequence, "shellpath")
print(trans_result)  # True

get_path_result = r.get("shellpath")
print(get_path_result)  # >>> '/usr/syscmds/:/usr/mycmds/'

```

##### Ruby

```ruby
trans_results = r.multi do |tx|
  tx.incrby('counter:1', 1)
  tx.incrby('counter:2', 2)
  tx.incrby('counter:3', 3)
end

puts trans_results[0] # >>> 1
puts trans_results[1] # >>> 2
puts trans_results[2] # >>> 3
```

##### Rust (Synchronous)

```rust
        match redis::pipe()
            .atomic()
            .incr("counter:1", 1)
            .incr("counter:2", 2)
            .incr("counter:3", 3)
            .exec(&mut r)
        {
            Ok(_) => {
                println!("Transaction executed successfully");
            },
            Err(e) => {
                println!("Error executing transaction: {e}");
                return;
            }
        };
```



## Watch keys for changes

Redis supports *optimistic locking* to avoid inconsistent updates to keys that
several clients may modify at the same time. The basic idea is to watch for
changes to any keys that you use in a transaction while you are preparing the
update. If the watched keys do change, Redis discards the transaction and you
must retry using the latest value from Redis. See
[Transactions](https://redis.io/docs/latest/develop/using-commands/transactions)
for more information about optimistic locking.

The example below watches a key, reads its current value, queues an update
inside `MULTI`, and then checks `TransactionResult.wasDiscarded()` after
`EXEC`:

Optimistic locking: Use WATCH with EXEC and check whether Lettuce discarded the transaction

**Difficulty:** Intermediate

**Available in:** C#, C#, Go, Java, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), PHP, Python, Ruby, Rust (Synchronous)

##### C#

```csharp
        var watchedTrans = db.CreateTransaction();

        watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

        var hashSetTask = watchedTrans.HashSetAsync(
            "customer:39182",
            [
                new("name", "David"),
                new("age", "27")
            ]
        );

        bool succeeded = db.Wait(watchedTrans.ExecuteAsync());
        db.Wait(hashSetTask);
        Console.WriteLine(succeeded); // >>> true
```

##### C#

```csharp
        var watchedTrans = new Transaction(db);

        watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

        _ = watchedTrans.Db.HashSetAsync(
            "customer:39182",
            [
                new("name", "David"),
                new("age", "27")
            ]
        );

        bool succeeded = watchedTrans.Execute();
        Console.WriteLine(succeeded); // >>> true
```

##### C#

```csharp
        var watchedTrans = db.CreateTransaction();

        watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

        var hashSetTask = watchedTrans.HashSetAsync(
            "customer:39182",
            [
                new("name", "David"),
                new("age", "27")
            ]
        );

        bool succeeded = db.Wait(watchedTrans.ExecuteAsync());
        db.Wait(hashSetTask);
        Console.WriteLine(succeeded); // >>> true
```

##### C#

```csharp
        var watchedTrans = new Transaction(db);

        watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

        _ = watchedTrans.Db.HashSetAsync(
            "customer:39182",
            [
                new("name", "David"),
                new("age", "27")
            ]
        );

        bool succeeded = watchedTrans.Execute();
        Console.WriteLine(succeeded); // >>> true
```

##### Go

```go
	// Set initial value of `shellpath`.
	rdb.Set(ctx, "shellpath", "/usr/syscmds/", 0)

	const maxRetries = 1000

	// Retry if the key has been changed.
	for i := 0; i < maxRetries; i++ {
		err := rdb.Watch(ctx,
			func(tx *redis.Tx) error {
				currentPath, err := rdb.Get(ctx, "shellpath").Result()
				newPath := currentPath + ":/usr/mycmds/"

				_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
					pipe.Set(ctx, "shellpath", newPath, 0)
					return nil
				})

				return err
			},
			"shellpath",
		)

		if err == nil {
			// Success.
			break
		} else if err == redis.TxFailedErr {
			// Optimistic lock lost. Retry the transaction.
			continue
		} else {
			// Panic for any other error.
			panic(err)
		}
	}

	fmt.Println(rdb.Get(ctx, "shellpath").Val())
	// >>> /usr/syscmds/:/usr/mycmds/
```

##### Java

```java
            commands.set("shellpath", "/usr/syscmds/");

            // Watch the key for changes while we prepare the update.
            commands.watch("shellpath");

            String currentPath = commands.get("shellpath");
            String newPath = currentPath + ":/usr/mycmds/";

            commands.multi();
            commands.set("shellpath", newPath);

            TransactionResult watchedResult = commands.exec();

            // `exec()` returns an empty result that reports `wasDiscarded()` if
            // the watched key changed before the transaction ran.
            if (!watchedResult.wasDiscarded()) {
                System.out.println(commands.get("shellpath"));
                // >>> /usr/syscmds/:/usr/mycmds/
            }
```

##### Java (Asynchronous - Lettuce)

```java
            async.set("shellpath", "/usr/syscmds/").get();

            // Watch the key for changes while we prepare the update.
            async.watch("shellpath").get();

            String currentPath = async.get("shellpath").get();
            String newPath = currentPath + ":/usr/mycmds/";

            async.multi().get();
            async.set("shellpath", newPath);

            TransactionResult watchedResult = async.exec().get();

            // `exec()` reports `wasDiscarded()` if the watched key changed
            // before the transaction ran.
            if (!watchedResult.wasDiscarded()) {
                System.out.println(async.get("shellpath").get());
                // >>> /usr/syscmds/:/usr/mycmds/
            }
```

##### Java (Reactive - Lettuce)

```java
            reactive.set("shellpath", "/usr/syscmds/").block();

            // Watch the key, read its current value, then queue the update in a
            // transaction. `exec()` reports `wasDiscarded()` if the watched key
            // changed before the transaction ran.
            TransactionResult watchedResult = reactive.watch("shellpath")
                    .then(reactive.get("shellpath"))
                    .flatMap(currentPath -> reactive.multi()
                            .flatMap(ok -> {
                                reactive.set("shellpath", currentPath + ":/usr/mycmds/").subscribe();
                                return reactive.exec();
                            }))
                    .block();

            if (!watchedResult.wasDiscarded()) {
                System.out.println(reactive.get("shellpath").block());
                // >>> /usr/syscmds/:/usr/mycmds/
            }
```

##### Java (Synchronous - Jedis)

```java
        // Set initial value of `shellpath`.
        jedis.set("shellpath", "/usr/syscmds/");

        // Start the transaction and watch the key we are about to update.
        try (AbstractTransaction trans = jedis.transaction(false)) { // create a Transaction object without sending MULTI command
            trans.watch("shellpath"); // send WATCH command(s)
            trans.multi(); // send MULTI command

            String currentPath = jedis.get("shellpath");
            String newPath = currentPath + ":/usr/mycmds/";

            // Commands added to the `trans` object
            // will be buffered until `trans.exec()` is called.
            Response<String> setResult = trans.set("shellpath", newPath);
            List<Object> transResults = trans.exec();

            // The `exec()` call returns null if the transaction failed.
            if (transResults != null) {
                // Responses are available if the transaction succeeded.
                System.out.println(setResult.get()); // >>> OK

                // You can also get the results from the list returned by
                // `trans.exec()`.
                for (Object item: transResults) {
                    System.out.println(item);
                }
                // >>> OK

                System.out.println(jedis.get("shellpath"));
                // >>> /usr/syscmds/:/usr/mycmds/
            }
        }
```

##### PHP

```php
$r->set('shellpath', '/usr/syscmds/');

$r->transaction(
    ['cas' => true, 'watch' => 'shellpath', 'retry' => 3],
    function (MultiExec $tx) {
        $path = $tx->get('shellpath');
        $tx->multi();
        $tx->set('shellpath', $path . ':/usr/mycmds/');
    }
);

echo $r->get('shellpath'), PHP_EOL;
// >>> /usr/syscmds/:/usr/mycmds/
```

##### Python

```python
r.set("shellpath", "/usr/syscmds/")

with r.pipeline() as pipe:
    # Repeat until successful.
    while True:
        try:
            # Watch the key we are about to change.
            pipe.watch("shellpath")

            # The pipeline executes commands directly (instead of
            # buffering them) from immediately after the `watch()`
            # call until we begin the transaction.
            current_path = pipe.get("shellpath")
            new_path = current_path + ":/usr/mycmds/"

            # Start the transaction, which will enable buffering
            # again for the remaining commands.
            pipe.multi()

            pipe.set("shellpath", new_path)

            pipe.execute()

            # The transaction succeeded, so break out of the loop.
            break
        except redis.WatchError:
            # The transaction failed, so continue with the next attempt.
            continue

get_path_result = r.get("shellpath")
print(get_path_result)  # >>> '/usr/syscmds/:/usr/mycmds/'
```

##### Ruby

```ruby
r.set('shellpath', '/usr/syscmds/')

# Watch the key, read its current value, then queue the update in a
# transaction. `multi` returns `nil` if the watched key changed before
# `EXEC` ran, in which case you would retry with the latest value.
result = r.watch('shellpath') do |client|
  current_path = client.get('shellpath')
  new_path = current_path + ':/usr/mycmds/'

  client.multi do |tx|
    tx.set('shellpath', new_path)
  end
end

if result.nil?
  puts 'Transaction aborted; retry with the latest value'
else
  puts r.get('shellpath')
  # >>> /usr/syscmds/:/usr/mycmds/
end
```

##### Rust (Synchronous)

```rust
        let key = "shellpath";
        let _: () = r.set(key, "/usr/syscmds/").unwrap();
        
        let Ok(_,): Result<((),), _> = redis::transaction(&mut r, &[key], |r, pipe| {
            let mut path: String = r.get(key).unwrap();
            path.push_str(":/usr/mycmds/");
            pipe.set(key, path).query(r)
        }) else {
            println!("Error executing transaction");
            return;
        };

        match r.get("shellpath") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");
                // >>> /usr/syscmds/:/usr/mycmds/
            },
            Err(e) => {
                println!("Error getting shellpath: {e}");
                return;
            }
        };
```



