Files
zeekay 6994cf05aa kv, not redis: rename the package, the extras and the URL scheme
We do not use Redis. The client was a go-redis fork and still said so
everywhere: `package redis`, `redis.NewClient`, `redis://`, `extra/redisotel`.
Consumers wrote `redis.Client` against a Valkey-backed store called KV.

  package redis            -> package kv          (93 decls)
  redis.X                  -> kv.X                (2,794 call sites)
  "redis: …" errors        -> "kv: …"             (206)
  extra/redis{otel,cmd,prometheus,census} -> extra/kv{otel,cmd,prometheus,census}
  redis:// , rediss://     -> kv:// , kvs://      (ParseURL)
  Redis*/‌*Redis* idents    -> KV*/*KV*
  REDIS_VERSION, REDIS_ENDPOINTS_CONFIG_PATH -> KV_*

I previously argued this rename was unsafe because much of it was "wire
contract". That was wrong, and the categorisation shows why: RESP command names
are GET/SET/HSET — they never contain the word. Of 5,771 occurrences only two
kinds were server-visible at all: the ParseURL scheme (we choose what we pass;
our configs already use kv://) and the CLIENT SETINFO lib-name label, which the
server merely records. Nothing here changes a byte of protocol.

Two things deliberately keep the old spelling:
  RediSearch  — names a specific external module the FT.* commands map to.
  StoreDist   — contains the letters by accident (sto-reDis-t), not the word.

Breaking for consumers by design: every import site must move redis.X -> kv.X.
Minor bump, never a major — this module stays v9 forever.
2026-07-26 08:34:19 -07:00
..

KV Digest & Optimistic Locking Example

This example demonstrates how to use KV DIGEST command and digest-based optimistic locking with go-kv.

What is KV DIGEST?

The DIGEST command (KV 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 KV 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 KV

Requirements

  • KV 8.4+ (for DIGEST command support)
  • Go 1.18+

Installation

cd example/digest-optimistic-locking
go mod tidy

Running the Example

# Make sure KV 8.4+ is running on localhost:6379
kv-server

# In another terminal, run the example
go run .

Expected Output

=== KV 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

KV uses the xxh3 hashing algorithm. The go-kv library provides built-in helper functions to calculate digests client-side:

import "github.com/kv/go-kv/v9/helper"

// For strings
digest := helper.DigestString("myvalue")

// For binary data
digest := helper.DigestBytes([]byte{0x01, 0x02, 0x03})

Optimistic Locking Pattern

// 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() == kv.Nil {
    // Value was modified by another client - retry or handle conflict
}

Client-Side Digest (No Extra Round Trip)

import "github.com/kv/go-kv/v9/helper"

// If you know the expected current value, calculate digest client-side
expectedValue := "100"
expectedDigest := helper.DigestString(expectedValue)

// Update without fetching digest from KV first
result := rdb.SetIFDEQ(ctx, "counter", "150", expectedDigest, 0)

Use Cases

1. Distributed Counter with Conflict Detection

// 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() == kv.Nil {
    // Retry with new value
}

2. Session Management

import "github.com/kv/go-kv/v9/helper"

// Delete session only if it contains expected data
sessionData := "user:1234:active"
expectedDigest := helper.DigestString(sessionData)

deleted := rdb.DelExArgs(ctx, "session:xyz", kv.DelExArgs{
    Mode:        "IFDEQ",
    MatchDigest: expectedDigest,
}).Val()

3. Configuration Updates

import "github.com/kv/go-kv/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() != kv.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 KV
  • Works with any command: Not limited to transactions

Learn More

Helper Functions Reference

The github.com/kv/go-kv/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 KV DIGEST command.