The rdb package provides functionality for parsing Redis RDB (Redis Database) snapshot files, enabling persistence and data recovery in ValKeyper.
RDB
The main RDB file parser structure.
Buffered reader for the RDB file
Auxiliary metadata fields (redis-ver, redis-bits, etc.)
Array of parsed database sections
NewRDB
Creates a new RDB parser from a file path.
func NewRDB(file string) (*RDB, error)
A new RDB parser instance
Error if the file cannot be opened
Example:
rdbFile, err := rdb.NewRDB("/var/lib/redis/dump.rdb")
if err != nil {
log.Fatal(err)
}
NewFromBytes
Creates an RDB parser from a byte slice (typically received during replication).
func NewFromBytes(content []byte) (*RDB, error)
A new RDB parser instance
Error if the content cannot be processed
Example:
// During replication handshake
rdbContent, _ := parser.ParseBulkString()
rdbFile, err := rdb.NewFromBytes(rdbContent)
Parse
Parses the entire RDB file structure.
func (rdb *RDB) Parse() error
Error if the RDB format is invalid or parsing fails
Parsing Steps:
- Validates the
REDIS magic string
- Reads the version number
- Parses auxiliary metadata fields
- Parses database sections
- Validates the EOF marker (
0xFF)
Example:
rdbFile, _ := rdb.NewRDB("dump.rdb")
err := rdbFile.Parse()
if err != nil {
log.Fatal("Invalid RDB file:", err)
}
// Access parsed data
for _, db := range rdbFile.Dbs {
fmt.Printf("Database %d has %d keys\n", db.Index, db.Size)
}
ParseLength
Parses a length-encoded value from the RDB file.
func (rdb *RDB) ParseLength() (int, bool, error)
True if the value represents an encoded integer, false for a length
Length Encoding:
00xxxxxx - 6-bit length (0-63)
01xxxxxx - 14-bit length (next byte contains lower 8 bits)
11000000 - Next 1 byte is an integer
11000001 - Next 2 bytes are an integer
11000010 - Next 4 bytes are an integer
Example:
length, isInt, err := rdb.ParseLength()
if isInt {
fmt.Printf("Encoded integer of size: %d bytes\n", length)
} else {
fmt.Printf("Length: %d\n", length)
}
ParseString
Parses a string value from the RDB file.
func (rdb *RDB) ParseString() (string, error)
Handles:
- Length-prefixed strings
- Encoded integers
- Compressed strings
Example:
key, err := rdb.ParseString()
if err != nil {
log.Fatal(err)
}
value, err := rdb.ParseString()
ParseSelectDB
Parses a database section from the RDB file.
func (rdb *RDB) ParseSelectDB() error
Error if the database section is invalid
Database Section Structure:
0xFE - Database selector opcode
- Database index (length-encoded)
0xFB - Hash table size info opcode
- Database size (length-encoded)
- Expiry size (length-encoded)
- Key-value pairs (with optional expiry)
Example:
err := rdb.ParseSelectDB()
if err != nil {
if err.Error() == "not FE" {
// Not a database section, move on
}
}
ParseKeyValue
Parses a single key-value pair from the RDB file.
func (rdb *RDB) ParseKeyValue(dbIdx int) ([]string, error)
Index of the database being parsed
Two-element slice containing [key, value]
Error if parsing fails or value type is unsupported
Example:
keyValue, err := rdb.ParseKeyValue(0)
if err == nil {
key := keyValue[0]
value := keyValue[1]
fmt.Printf("%s = %s\n", key, value)
}
ParseAux
Parses auxiliary metadata fields from the RDB file.
func (rdb *RDB) ParseAux() error
Error if not an auxiliary field or parsing fails
Common Auxiliary Fields:
redis-ver - Redis version
redis-bits - Architecture (32 or 64 bit)
ctime - Creation timestamp
used-mem - Memory usage
Example:
for {
err := rdb.ParseAux()
if err != nil && err.Error() == "not aux" {
break // No more auxiliary fields
}
}
version := rdb.aux["redis-ver"]
fmt.Printf("RDB created by Redis %s\n", version)
Database
Represents a parsed database section from the RDB file.
Database index (0-15 by default)
Number of keys in the database
Number of keys with expiration set
Key-value pairs without expiration
Key-value pairs with expiration timestamps
Example:
for _, db := range rdbFile.Dbs {
fmt.Printf("Database %d:\n", db.Index)
fmt.Printf(" Total keys: %d\n", db.Size)
fmt.Printf(" Keys with expiry: %d\n", db.Expiry)
// Regular keys
for key, value := range db.DbStore {
fmt.Printf(" %s = %s\n", key, value)
}
// Keys with expiration
for _, entry := range db.ExpiryStore {
fmt.Printf(" %s = %s (expires: %d)\n",
entry.Key, entry.Value, entry.Expiry)
}
}
expiryEntry
Internal structure for keys with expiration.
Unix timestamp in milliseconds when the key expires
Complete Example
Here’s how to parse an RDB file and load it into the store:
package main
import (
"fmt"
"log"
"time"
"github.com/codecrafters-io/redis-starter-go/app/rdb"
"github.com/codecrafters-io/redis-starter-go/app/store"
)
func main() {
// Create a new store
kvStore := store.New()
// Parse the RDB file
rdbFile, err := rdb.NewRDB("/var/lib/redis/dump.rdb")
if err != nil {
log.Fatal("Failed to open RDB file:", err)
}
err = rdbFile.Parse()
if err != nil {
log.Fatal("Failed to parse RDB file:", err)
}
// Load data into store
kvStore.LoadFromRDB(rdbFile)
// Display loaded data
fmt.Println("Loaded RDB file successfully")
fmt.Printf("RDB Version: %d\n", rdbFile.version)
fmt.Printf("Redis Version: %s\n", rdbFile.aux["redis-ver"])
for _, db := range rdbFile.Dbs {
fmt.Printf("\nDatabase %d:\n", db.Index)
fmt.Printf(" Total keys: %d\n", db.Size)
// Show regular keys
for key, value := range db.DbStore {
fmt.Printf(" [PERSIST] %s = %s\n", key, value)
}
// Show keys with expiration
now := time.Now().UnixMilli()
for _, entry := range db.ExpiryStore {
remaining := int64(entry.Expiry) - now
if remaining > 0 {
fmt.Printf(" [EXPIRE] %s = %s (in %dms)\n",
entry.Key, entry.Value, remaining)
} else {
fmt.Printf(" [EXPIRED] %s = %s\n",
entry.Key, entry.Value)
}
}
}
}
The parser handles the standard RDB file format:
52 45 44 49 53 # Magic string "REDIS"
30 30 30 33 # Version "0003"
FA # Auxiliary field opcode
... # Key-value metadata
FE 00 # Select DB 0
FB # Hash table sizes
... # Size info
FC # Expiry in milliseconds (optional)
... # 8-byte timestamp
00 # String type
... # Key-value pair
FF # EOF opcode
... # 8-byte checksum
Opcodes Reference
| Opcode | Hex | Description |
|---|
| EOF | 0xFF | End of file marker |
| SELECTDB | 0xFE | Database selector |
| EXPIRETIME | 0xFD | Expiry in seconds |
| EXPIRETIMEMS | 0xFC | Expiry in milliseconds |
| RESIZEDB | 0xFB | Hash table size info |
| AUX | 0xFA | Auxiliary metadata |