example/digest-optimistic-locking/README.md
This example demonstrates how to use Redis DIGEST command and digest-based optimistic locking with go-redis.
The DIGEST command (Redis 8.4+) returns a 64-bit xxh3 hash of a key's value. This hash can be used for:
cd example/digest-optimistic-locking
go mod tidy
# Make sure Redis 8.4+ is running on localhost:6379
redis-server
# In another terminal, run the example
go run .
=== Redis Digest & Optimistic Locking Example ===
1. Basic Digest Usage
---------------------
Key: user:1000:name
Value: Alice
Digest: 7234567890123456789 (0x6478a1b2c3d4e5f6)
Client-calculated digest: 7234567890123456789 (0x6478a1b2c3d4e5f6)
✓ Digests match!
2. Optimistic Locking with SetIFDEQ
------------------------------------
Initial value: 100
Current digest: 0x1234567890abcdef
✓ Update successful! New value: 150
✓ Correctly rejected update with wrong digest
3. Detecting Changes with SetIFDNE
-----------------------------------
Initial value: v1.0.0
Old digest: 0xabcdef1234567890
✓ Value changed! Updated to: v2.0.0
✓ Correctly rejected: current value matches the digest
4. Conditional Delete with DelExArgs
-------------------------------------
Created session: session:abc123
Expected digest: 0x9876543210fedcba
✓ Correctly refused to delete (wrong digest)
✓ Successfully deleted with correct digest
✓ Session deleted
5. Client-Side Digest Generation
---------------------------------
Current price: $29.99
Expected digest (calculated client-side): 0xfedcba0987654321
✓ Price updated successfully to $24.99
Binary data example:
Binary data digest: 0x1122334455667788
✓ Binary digest matches!
=== All examples completed successfully! ===
Redis uses the xxh3 hashing algorithm. The go-redis library provides built-in helper functions to calculate digests client-side:
import "github.com/redis/go-redis/v9/helper"
// For strings
digest := helper.DigestString("myvalue")
// For binary data
digest := helper.DigestBytes([]byte{0x01, 0x02, 0x03})
// 1. Read current value and get its digest
currentValue := rdb.Get(ctx, "key").Val()
currentDigest := rdb.Digest(ctx, "key").Val()
// 2. Perform business logic
newValue := processValue(currentValue)
// 3. Update only if value hasn't changed
result := rdb.SetIFDEQ(ctx, "key", newValue, currentDigest, 0)
if result.Err() == redis.Nil {
// Value was modified by another client - retry or handle conflict
}
import "github.com/redis/go-redis/v9/helper"
// If you know the expected current value, calculate digest client-side
expectedValue := "100"
expectedDigest := helper.DigestString(expectedValue)
// Update without fetching digest from Redis first
result := rdb.SetIFDEQ(ctx, "counter", "150", expectedDigest, 0)
// Multiple clients can safely update a counter
currentValue := rdb.Get(ctx, "counter").Val()
currentDigest := rdb.Digest(ctx, "counter").Val()
newValue := incrementCounter(currentValue)
// Only succeeds if no other client modified it
if rdb.SetIFDEQ(ctx, "counter", newValue, currentDigest, 0).Err() == redis.Nil {
// Retry with new value
}
import "github.com/redis/go-redis/v9/helper"
// Delete session only if it contains expected data
sessionData := "user:1234:active"
expectedDigest := helper.DigestString(sessionData)
deleted := rdb.DelExArgs(ctx, "session:xyz", redis.DelExArgs{
Mode: "IFDEQ",
MatchDigest: expectedDigest,
}).Val()
import "github.com/redis/go-redis/v9/helper"
// Update config only if it changed
oldConfig := loadOldConfig()
oldDigest := helper.DigestString(oldConfig)
newConfig := loadNewConfig()
// Only update if config actually changed
result := rdb.SetIFDNE(ctx, "config", newConfig, oldDigest, 0)
if result.Err() != redis.Nil {
fmt.Println("Config updated!")
}
The github.com/redis/go-redis/v9/helper package provides:
| Function | Description |
|---|---|
DigestString(s string) uint64 | Computes xxh3 hash of a string |
DigestBytes(data []byte) uint64 | Computes xxh3 hash of a byte slice |
Both functions produce the exact same hash as the Redis DIGEST command.