mirror of
https://github.com/redis/go-redis.git
synced 2025-11-14 10:22:26 +03:00
* add cas/cad commands
* feat(command): Add SetIFDEQ, SetIFDNE and *Get cmds
Decided to move the *Get argument as a separate methods, since the
response will be always the previous value, but in the case where
the previous value is `OK` there result may be ambiguous.
* fix tests
* matchValue to be interface{}
* Only Args approach for DelEx
* use uint64 for digest, add example
* test only for 8.4
201 lines
5.5 KiB
Markdown
201 lines
5.5 KiB
Markdown
# Redis Digest & Optimistic Locking Example
|
|
|
|
This example demonstrates how to use Redis DIGEST command and digest-based optimistic locking with go-redis.
|
|
|
|
## What is Redis DIGEST?
|
|
|
|
The DIGEST command (Redis 8.4+) returns a 64-bit xxh3 hash of a key's value. This hash can be used for:
|
|
|
|
- **Optimistic locking**: Update values only if they haven't changed
|
|
- **Change detection**: Detect if a value was modified
|
|
- **Conditional operations**: Delete or update based on expected content
|
|
|
|
## Features Demonstrated
|
|
|
|
1. **Basic Digest Usage**: Get digest from Redis and verify with client-side calculation
|
|
2. **Optimistic Locking with SetIFDEQ**: Update only if digest matches (value unchanged)
|
|
3. **Change Detection with SetIFDNE**: Update only if digest differs (value changed)
|
|
4. **Conditional Delete**: Delete only if digest matches expected value
|
|
5. **Client-Side Digest Generation**: Calculate digests without fetching from Redis
|
|
|
|
## Requirements
|
|
|
|
- Redis 8.4+ (for DIGEST command support)
|
|
- Go 1.18+
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
cd example/digest-optimistic-locking
|
|
go mod tidy
|
|
```
|
|
|
|
## Running the Example
|
|
|
|
```bash
|
|
# Make sure Redis 8.4+ is running on localhost:6379
|
|
redis-server
|
|
|
|
# In another terminal, run the example
|
|
go run .
|
|
```
|
|
|
|
## Expected Output
|
|
|
|
```
|
|
=== 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! ===
|
|
```
|
|
|
|
## How It Works
|
|
|
|
### Digest Calculation
|
|
|
|
Redis uses the **xxh3** hashing algorithm. To calculate digests client-side, use `github.com/zeebo/xxh3`:
|
|
|
|
```go
|
|
import "github.com/zeebo/xxh3"
|
|
|
|
// For strings
|
|
digest := xxh3.HashString("myvalue")
|
|
|
|
// For binary data
|
|
digest := xxh3.Hash([]byte{0x01, 0x02, 0x03})
|
|
```
|
|
|
|
### Optimistic Locking Pattern
|
|
|
|
```go
|
|
// 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
|
|
}
|
|
```
|
|
|
|
### Client-Side Digest (No Extra Round Trip)
|
|
|
|
```go
|
|
// If you know the expected current value, calculate digest client-side
|
|
expectedValue := "100"
|
|
expectedDigest := xxh3.HashString(expectedValue)
|
|
|
|
// Update without fetching digest from Redis first
|
|
result := rdb.SetIFDEQ(ctx, "counter", "150", expectedDigest, 0)
|
|
```
|
|
|
|
## Use Cases
|
|
|
|
### 1. Distributed Counter with Conflict Detection
|
|
|
|
```go
|
|
// 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
|
|
}
|
|
```
|
|
|
|
### 2. Session Management
|
|
|
|
```go
|
|
// Delete session only if it contains expected data
|
|
sessionData := "user:1234:active"
|
|
expectedDigest := xxh3.HashString(sessionData)
|
|
|
|
deleted := rdb.DelExArgs(ctx, "session:xyz", redis.DelExArgs{
|
|
Mode: "IFDEQ",
|
|
MatchDigest: expectedDigest,
|
|
}).Val()
|
|
```
|
|
|
|
### 3. Configuration Updates
|
|
|
|
```go
|
|
// Update config only if it changed
|
|
oldConfig := loadOldConfig()
|
|
oldDigest := xxh3.HashString(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!")
|
|
}
|
|
```
|
|
|
|
## Advantages Over WATCH/MULTI/EXEC
|
|
|
|
- **Simpler**: Single command instead of transaction
|
|
- **Faster**: No transaction overhead
|
|
- **Client-side digest**: Can calculate expected digest without fetching from Redis
|
|
- **Works with any command**: Not limited to transactions
|
|
|
|
## Learn More
|
|
|
|
- [Redis DIGEST command](https://redis.io/commands/digest/)
|
|
- [Redis SET command with IFDEQ/IFDNE](https://redis.io/commands/set/)
|
|
- [xxh3 hashing algorithm](https://github.com/Cyan4973/xxHash)
|
|
- [github.com/zeebo/xxh3](https://github.com/zeebo/xxh3)
|
|
|
|
## Comparison: XXH3 vs XXH64
|
|
|
|
**Note**: Redis uses **XXH3**, not XXH64. If you have `github.com/cespare/xxhash/v2` in your project, it implements XXH64 which produces **different hash values**. You must use `github.com/zeebo/xxh3` for Redis DIGEST operations.
|
|
|
|
See [XXHASH_LIBRARY_COMPARISON.md](../../XXHASH_LIBRARY_COMPARISON.md) for detailed comparison.
|
|
|