Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions content/commands/xadd.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,22 +306,42 @@ For more information about Redis streams, see the

## Examples

{{% redis-cli %}}
XADD mystream * name Sara surname OConnor
XADD mystream * field1 value1 field2 value2 field3 value3
XLEN mystream
XRANGE mystream - +
{{% /redis-cli %}}
{{< clients-example set="cmds_stream" step="xadd1" description="Basic XADD: Add entries to a stream with auto-generated IDs, check stream the stream size, and read entries" difficulty="beginner" >}}
> XADD mystream * name Sara surname OConnor
4378417975-0"
> XADD mystream * field1 value1 field2 value2 field3 value3
4378417976-0"
> XLEN mystream
eger) 2
> XRANGE mystream - +
1) 1) "1774378417975-0"
2) 1) "name"
2) "Sara"
3) "surname"
4) "OConnor"
2) 1) "1774378417976-0"
2) 1) "field1"
2) "value1"
3) "field2"
4) "value2"
5) "field3"
6) "value3"
{{< /clients-example >}}

### Idempotent message processing examples

{{% redis-cli %}}
XADD mystream IDMP producer1 msg1 * field value
XADD mystream IDMP producer1 msg1 * field different_value
XADD mystream IDMPAUTO producer2 * field value
XADD mystream IDMPAUTO producer2 * field value
XCFGSET mystream IDMP-DURATION 300 IDMP-MAXSIZE 1000
{{% /redis-cli %}}
{{< clients-example set="cmds_stream" step="xadd2" description="Idempotent XADD (Redis 8.6+): Use IDMP and IDMPAUTO for at-most-once message delivery, preventing duplicate entries" difficulty="intermediate" >}}
> XADD mystream IDMP producer1 msg1 * field value
"1774378417976-0"
> XADD mystream IDMP producer1 msg1 * field different_value
"1774378417976-0"
> XADD mystream IDMPAUTO producer2 * field value
"1774378417977-0"
> XADD mystream IDMPAUTO producer2 * field value
"1774378417977-0"
> XCFGSET mystream IDMP-DURATION 300 IDMP-MAXSIZE 1000
"OK"
{{< /clients-example >}}

## Redis Software and Redis Cloud compatibility

Expand Down
100 changes: 100 additions & 0 deletions local_examples/cmds_stream/NRedisStack/CmdsStreamExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// EXAMPLE: cmds_stream

using StackExchange.Redis;
// REMOVE_START
using NRedisStack.Tests;
using Xunit;
// REMOVE_END

// REMOVE_START
namespace Doc;

[Collection("DocsTests")]
// REMOVE_END
public class CmdsStreamExample
// REMOVE_START
: AbstractNRedisStackTest, IDisposable
// REMOVE_END
{
// REMOVE_START
public CmdsStreamExample(EndpointsFixture fixture) : base(fixture) { }

[Fact]
// REMOVE_END
public void Run()
{
//REMOVE_START
SkipIfTargetConnectionDoesNotExist(EndpointsFixture.Env.Standalone);
var _ = GetCleanDatabase(EndpointsFixture.Env.Standalone);
//REMOVE_END
var muxer = ConnectionMultiplexer.Connect("localhost:6379");
var db = muxer.GetDatabase();

// REMOVE_START
db.KeyDelete("mystream");
// REMOVE_END

// STEP_START xadd1
var res1 = db.StreamAdd("mystream", new NameValueEntry[] {
new NameValueEntry("name", "Sara"),
new NameValueEntry("surname", "OConnor")
});
Console.WriteLine(res1); // >>> 1726055713866-0

var res2 = db.StreamAdd("mystream", new NameValueEntry[] {
new NameValueEntry("field1", "value1"),
new NameValueEntry("field2", "value2"),
new NameValueEntry("field3", "value3")
});
Console.WriteLine(res2); // >>> 1726055713866-1

var res3 = db.StreamLength("mystream");
Console.WriteLine(res3); // >>> 2

var res4 = db.StreamRange("mystream", "-", "+");
foreach (var entry in res4)
{
Console.WriteLine($"{entry.Id} -> {string.Join(", ", entry.Values.Select(v => $"{v.Name}={v.Value}"))}");
}
// >>> 1726055713866-0 -> name=Sara, surname=OConnor
// >>> 1726055713866-1 -> field1=value1, field2=value2, field3=value3
// STEP_END

// REMOVE_START
Assert.Equal(2, res3);
Assert.Equal(2, res4.Length);
db.KeyDelete("mystream");
// REMOVE_END

// STEP_START xadd2
// Note: IDMP is a Redis 8.6 feature - using Execute for raw command access
var res5 = db.Execute("XADD", "mystream", "IDMP", "producer1", "msg1", "*", "field", "value");
Console.WriteLine(res5); // >>> 1726055713867-0

// Attempting to add the same message again with IDMP returns the original entry ID
var res6 = db.Execute("XADD", "mystream", "IDMP", "producer1", "msg1", "*", "field", "different_value");
Console.WriteLine(res6); // >>> 1726055713867-0 (same ID as res5, message was deduplicated)

var res7 = db.Execute("XADD", "mystream", "IDMPAUTO", "producer2", "*", "field", "value");
Console.WriteLine(res7); // >>> 1726055713867-1

// Auto-generated idempotent ID prevents duplicates for same producer+content
var res8 = db.Execute("XADD", "mystream", "IDMPAUTO", "producer2", "*", "field", "value");
Console.WriteLine(res8); // >>> 1726055713867-1 (same ID as res7, duplicate detected)

// Configure idempotent message processing settings
var res9 = db.Execute("XCFGSET", "mystream", "IDMP-DURATION", "300", "IDMP-MAXSIZE", "1000");
Console.WriteLine(res9); // >>> OK
// STEP_END

// REMOVE_START
Assert.NotNull(res5);
db.KeyDelete("mystream");
// REMOVE_END

// HIDE_START
muxer.Close();
// HIDE_END
}
}

170 changes: 170 additions & 0 deletions local_examples/cmds_stream/go-redis/cmds_stream_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// EXAMPLE: cmds_stream
// HIDE_START
package example_commands_test

import (
"context"
"fmt"

"github.com/redis/go-redis/v9"
)

// HIDE_END

func ExampleClient_xadd1() {
ctx := context.Background()

rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})

// REMOVE_START
rdb.Del(ctx, "mystream")
// REMOVE_END

// STEP_START xadd1
res1, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{
"name": "Sara",
"surname": "OConnor",
},
}).Result()

if err != nil {
panic(err)
}

fmt.Println(res1 != "") // >>> true

res2, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{
"field1": "value1",
"field2": "value2",
"field3": "value3",
},
}).Result()

if err != nil {
panic(err)
}

fmt.Println(res2 != "") // >>> true

res3, err := rdb.XLen(ctx, "mystream").Result()

if err != nil {
panic(err)
}

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

res4, err := rdb.XRange(ctx, "mystream", "-", "+").Result()

if err != nil {
panic(err)
}

fmt.Println(len(res4)) // >>> 2
// STEP_END

// Output:
// true
// true
// 2
// 2
}

func ExampleClient_xadd2() {
ctx := context.Background()

rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})

// REMOVE_START
rdb.Del(ctx, "mystream")
// REMOVE_END

// STEP_START xadd2
res5, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{"field": "value"},
ProducerID: "producer1",
IdempotentID: "msg1",
}).Result()

if err != nil {
panic(err)
}

fmt.Println(res5 != "") // >>> true

// Attempting to add the same message again with IDMP returns the original entry ID
res6, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{"field": "different_value"},
ProducerID: "producer1",
IdempotentID: "msg1",
}).Result()

if err != nil {
panic(err)
}

fmt.Println(res5 == res6) // >>> true (same ID, message was deduplicated)

res7, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{"field": "value"},
ProducerID: "producer2",
IdempotentAuto: true,
}).Result()

if err != nil {
panic(err)
}

fmt.Println(res7 != "") // >>> true

// Auto-generated idempotent ID prevents duplicates for same producer+content
res8, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{"field": "value"},
ProducerID: "producer2",
IdempotentAuto: true,
}).Result()

if err != nil {
panic(err)
}

fmt.Println(res7 == res8) // >>> true (same ID, duplicate detected)

// Configure idempotent message processing settings
res9, err := rdb.XCfgSet(ctx, &redis.XCfgSetArgs{
Stream: "mystream",
Duration: 300, // 300 seconds
MaxSize: 1000, // 1000 idempotent IDs
}).Result()

if err != nil {
panic(err)
}

fmt.Println(res9) // >>> OK
// STEP_END

// Output:
// true
// true
// true
// true
// OK
}

Loading
Loading