Overview
ValKeyper implements automatic key expiration using Go’s time-based channels and goroutines. When a key is set with a TTL (Time To Live), a background goroutine monitors the expiration and automatically removes the key from the store when the TTL expires.Expiry Data Structures
The expiry system uses two key components:Fields
store: Main in-memory map holding key-value pairsexpiryMap: Maps each key with TTL to a control channel- Key: The store key (string)
- Value: Channel to signal expiry cancellation
Setting Keys with Expiry
TheSet method handles key creation with optional TTL:
Flow
- Check Expiry: If
expiry != -1, TTL is enabled - Create Timeout: Convert milliseconds to
time.Duration - Launch Goroutine: Spawn
handleExpiryin background - Store Value: Write key-value to store (synchronous)
Usage via SET Command
processCommand:
Example
The handleExpiry Goroutine
The core expiry mechanism is implemented in thehandleExpiry method:
Goroutine Lifecycle
-
Channel Creation
- Creates unbuffered channel for cancellation signals
- Stores channel in
expiryMapfor external access
-
Event Loop
closeChreceives: Goroutine exits (early cancellation)timeoutreceives: Delete key and continue (TTL expired)
-
Goroutine Termination
- Expires naturally when timeout fires (one-time delete)
- Exits early if cancellation signal received
After the timeout fires and the key is deleted, the goroutine continues looping. However, since
timeout is a one-time channel created by time.After(), it will never fire again. The goroutine effectively waits indefinitely on closeCh unless signaled.Expiry Cancellation
Keys can be deleted manually before expiry using the DEL command:Cancellation Flow
- Check Existence: Verify key exists in store
- Delete from Store: Remove key-value pair
- Lookup Channel: Check if key has expiry goroutine
- Signal Goroutine: Send value to
closeCh - Goroutine Exits: Receives signal and returns
Example
Loading Expiry from RDB
When loading persisted data, ValKeyper recalculates remaining TTL:RDB Expiry Restoration
- Load Regular Keys: Copy
DbStoreto in-memory store - Process Expiry Keys: Iterate
ExpiryStore - Calculate Remaining TTL:
- Launch Goroutines: Start
handleExpirywith remaining duration
Time-Based Expiry
RDB stores absolute expiry timestamps (Unix milliseconds). ValKeyper:- Subtracts current time to get remaining duration
- Handles already-expired keys (negative duration)
Expiry Precision
ValKeyper uses millisecond precision for all expiry operations:Timing Characteristics
- Resolution: Milliseconds
- Mechanism: Go’s
time.After()timer - Accuracy: Subject to Go scheduler and system timer precision
- Guarantee: Key expires at or shortly after the specified time
Memory Management
The expiry system has memory implications:Per-Key Overhead
Goroutine Cleanup
Mitigation strategies:- Always use DEL to explicitly remove keys
- Let natural expiry happen for short-lived keys
- Monitor goroutine count in production
Edge Cases and Behaviors
Overwriting Keys with TTL
Setting a new value for an existing key with TTL:- New
handleExpirygoroutine is launched - Old goroutine continues running (not terminated)
- Result: Goroutine leak accumulates
Setting Key Without TTL
Removing TTL from an existing key:- Key is updated in store
- Old expiry goroutine still runs
- When old TTL expires, key is deleted unexpectedly
Zero or Negative Expiry
time.After(0)fires immediately- Key is deleted almost instantly after being set
Best Practices
Use DEL for Early Removal
Always use DEL to remove keys before expiry to properly clean up goroutines.
Avoid Frequent Overwrites
Minimize repeated SET operations on the same key with TTL to prevent goroutine leaks.
Monitor Memory
Track memory usage and goroutine count when using many expiring keys.
Millisecond Precision
Use millisecond values (PX) for precise expiry control.
Common Patterns
Session Management
Cache with TTL
Rate Limiting
Implementation Reference
Key source locations instore.go:
- KVStore struct: Lines 43-51
- Set method: Lines 53-62
- handleExpiry goroutine: Lines 94-105
- DEL cancellation: Lines 193-208
- RDB loading: Lines 81-92
- SET command parsing: Lines 209-220