Grove
1.x
Docs/Grove/Badger Driver
Open

Reading5 min
Updated31 Jul 2026
Sourcev1/kv/drivers/badger.mdx

The Badger driver connects Grove KV to Badger, a fast embeddable LSM-tree key-value store written in Go. Badger supports native TTL, prefix iteration, range scans, and ACID transactions. It is designed for high-performance workloads with both SSD-optimized and in-memory modes.

Installation01

go get github.com/xraph/grove/kv
go get github.com/xraph/grove/kv/drivers/badgerdriver

Connection02

import (
    "github.com/xraph/grove/kv"
    "github.com/xraph/grove/kv/drivers/badgerdriver"
)

// On-disk database
store, err := kv.Open(badgerdriver.New(), "/path/to/badger")
if err != nil {
    log.Fatal(err)
}
defer store.Close()

For an in-memory database (no disk persistence), use the memory:// DSN:

store, err := kv.Open(badgerdriver.New(), "memory://")
if err != nil {
    log.Fatal(err)
}
defer store.Close()

The DSN is the directory path where Badger stores its data files. The directory is created automatically if it does not exist. Use "memory://" or ":memory:" for an in-memory-only store.

Capabilities03

CapabilitySupported
TTLYes (native)
CAS (SetNX/SetXX)Yes
ScanYes (prefix + glob matching)
BatchYes (MGet/MSet via WriteBatch)
PubSubNo
TransactionsYes (native ACID)
StreamsNo

High-Performance LSM-Tree Storage04

Badger uses a Log-Structured Merge Tree (LSM-tree) architecture optimized for write-heavy workloads:

  • Key-value separation -- keys are stored in the LSM tree while large values are stored in a separate value log, reducing write amplification

  • Concurrent reads -- multiple goroutines can read simultaneously without blocking

  • Concurrent writes -- WriteBatch allows concurrent writes with automatic batching

  • SSD-optimized -- designed to minimize I/O for solid-state drives

  • In-memory mode -- run entirely in RAM for maximum throughput (no persistence)

Native TTL05

Badger supports TTL natively at the storage engine level. Expired entries are automatically garbage-collected during compaction:

// Set with a TTL -- Badger handles expiration automatically
err := store.Set(ctx, "cache:result", data, 5*time.Minute)

// Check remaining TTL
remaining, err := store.TTL(ctx, "cache:result")
if remaining == -1 {
    // Key exists but has no expiry
}

// Update TTL on an existing key
err = store.Expire(ctx, "cache:result", 10*time.Minute)

Unlike emulated TTL in other embedded drivers, Badger's TTL is enforced at the storage level. Expired keys are not returned by reads and are physically removed during garbage collection.

Scan06

Badger supports prefix-based key scanning with glob pattern matching:

// Scan all keys with a prefix
err := store.Scan(ctx, "user:*", func(key string) error {
    fmt.Println(key)
    return nil
})

// Scan all keys
err = store.Scan(ctx, "*", func(key string) error {
    fmt.Println(key)
    return nil
})

Native Transactions07

For fine-grained control, use the driver's transaction methods directly:

Read-Only Transaction

bdb := badgerdriver.Unwrap(store)

err := bdb.ViewTxn(func(txn *badger.Txn) error {
    item, err := txn.Get([]byte("my-key"))
    if err != nil {
        return err
    }
    return item.Value(func(val []byte) error {
        fmt.Println(string(val))
        return nil
    })
})

Read-Write Transaction

bdb := badgerdriver.Unwrap(store)

err := bdb.UpdateTxn(func(txn *badger.Txn) error {
    // Read
    item, err := txn.Get([]byte("counter"))
    if err != nil {
        return err
    }
    val, err := item.ValueCopy(nil)
    if err != nil {
        return err
    }

    // Modify and write back
    count, _ := strconv.Atoi(string(val))
    entry := badger.NewEntry([]byte("counter"), []byte(strconv.Itoa(count+1)))
    return txn.SetEntry(entry)
})

Prefix Scan and Range Scan08

The driver provides dedicated methods for prefix and range iteration that return both keys and values:

Prefix Scan

bdb := badgerdriver.Unwrap(store)

err := bdb.PrefixScan(ctx, "user:", func(key string, value []byte) error {
    fmt.Printf("key=%s value=%s\n", key, value)
    return nil
})

Range Scan

Iterate over a lexicographic range [start, end):

bdb := badgerdriver.Unwrap(store)

err := bdb.RangeScan(ctx, "event:2024-01-01", "event:2024-02-01",
    func(key string, value []byte) error {
        fmt.Printf("key=%s\n", key)
        return nil
    },
)

Options09

The Badger driver detects in-memory mode from the DSN:

DSNMode
/path/to/dirOn-disk persistent storage
memory://In-memory only (no persistence)
:memory:In-memory only (no persistence)

Unwrap for Direct Access10

Access the underlying BadgerDB driver or badger.DB for advanced operations:

// Get the BadgerDB driver
bdb := badgerdriver.Unwrap(store)

// Get the raw badger.DB handle
db := badgerdriver.UnwrapDB(store)

// Access badger directly (e.g., run garbage collection)
err := db.RunValueLogGC(0.5)

// Check database size
lsm, vlog := db.Size()
fmt.Printf("LSM: %d bytes, VLog: %d bytes\n", lsm, vlog)

// Flatten the LSM tree
err = db.Flatten(4) // 4 concurrent compactions