redb

repository·master·Indexed 26 days ago

https://github.com/cberner/redb

A simple, portable, high-performance, ACID-compliant embedded key-value store written in pure Rust. Inspired by LMDB, redb uses copy-on-write B+trees and supports MVCC for concurrent readers and writers without blocking. It features a zero-copy, thread-safe BTreeMap-based API, crash-safety by default, and support for savepoints and rollbacks. The project includes procedural macros for deriving Key and Value traits, as well as Python bindings for managing databases and transactions.

Tokens
5.9K
Snippets
5
Records
33
Agent score
89%

What's inside redb

  1. Overview of redb architecture and features

    master

    redb is a portable, high-performance, ACID-compliant, embedded key-value store. It is designed for simplicity and uses MVCC (Multi-Version Concurrency Control) to provide isolation.

    Key Characteristics:

    • Isolation Level: Provides a single isolation level: serializable, where all writes are applied sequentially.
    • Concurrency: Supports a single writer and multiple concurrent readers.
    • Data Model: Each database contains multiple tables, where each table acts as a key-value mapping similar to a BTreeMap.
    • Persistence: Uses a copy-on-write mechanism for all data structures except for the database metadata.
    • File Structure: The database file consists of metadata followed by one or more regions. Each region contains a header and a data section split into pages.
  2. Understand redb's MVCC and Savepoint behavior

    master

    redb uses Multi-Version Concurrency Control (MVCC) built on a copy-on-write B+tree to isolate transactions.

    Transaction Isolation

    • Read Transactions: Create a private copy of the B+tree root. Pages referenced by this root are protected from being freed until the transaction completes.
    • Write Transactions: Use copy-on-write for committed pages. Modifications result in new page allocations rather than in-place updates. Dirty pages (allocated within the current transaction) can be modified in-place.

    Savepoints

    Savepoints allow you to capture a snapshot of the database and rollback to it. They are implemented using the same MVCC structures as transactions.

    There are two types of savepoints:

    1. Ephemeral: Automatically deallocated when dropped. They do not persist across restarts.
    2. Persistent: Stored in a table within the system table tree and persist across restarts. These must be explicitly deallocated to avoid resource leaks.
  3. redb core features

    master

    redb provides the following capabilities:

    • Zero-copy, thread-safe, BTreeMap based API
    • Fully ACID-compliant transactions
    • MVCC support for concurrent readers & writer, without blocking
    • Crash-safe by default
    • Savepoints and rollbacks
  4. Use redb as an embedded key-value store

    master

    redb is a high-performance, ACID-compliant, embedded key-value store written in pure Rust. It uses copy-on-write B+trees and supports MVCC for concurrent readers and writers without blocking.

    To use redb, you typically:

    1. Define a TableDefinition with specific key and value types.
    2. Create a Database instance pointing to a file.
    3. Start a write transaction using db.begin_write() to modify data.
    4. Start a read transaction using db.begin_read() to access data.
    5. Commit write transactions using .commit() to persist changes.
    use redb::{Database, Error, ReadableDatabase, TableDefinition};
    
    const TABLE: TableDefinition<&str, u64> = TableDefinition::new("my_data");
    
    fn main() -> Result<(), Error> {
        let db = Database::create("my_db.redb")?;
        let write_txn = db.begin_write()?;
        {
            let mut table = write_txn.open_table(TABLE)?;
            table.insert("my_key", &123)?;
        }
        write_txn.commit()?;
    
        let read_txn = db.begin_read()?;
        let table = read_txn.open_table(TABLE)?;
        assert_eq!(table.get("my_key")?.unwrap().value(), 123);
    
        Ok()
    }
  5. Repair a redb database after an unclean shutdown

    master

    If a database experiences an unclean shutdown, it may require repair to ensure consistency between the super-header and the allocator state.

    Quick Repair

    If the last commit had quick-repair enabled, the primary commit slot is guaranteed to be valid via a 2-phase commit, and the allocator state is stored in the allocator state table. This makes repair trivial.

    Full Repair

    If quick-repair is not available, a full repair is required:

    1. Update Super-header: If the primary commit slot is invalid, switch to the secondary slot to reference the last fully committed transaction.
    2. Rebuild Allocator State: Rebuild the state by walking the following trees and marking all referenced pages as allocated:
      • data tree
      • system tree
      • freed tree (including all pending free pages contained within)

    Note: All pages referenced by a savepoint must be accounted for in this process, as they are either committed pages or pages in a pending free state within the freed tree.

  6. Choose a redb commit strategy

    master

    redb provides different commit strategies depending on your requirements for durability and security:

    Non-durable commits

    • Behavior: No guarantee of durability in the event of a crash.
    • Consistency: The database is still guaranteed to be consistent and will roll back to either the last non-durable commit or the last full commit.
    • Use Case: High-performance scenarios where losing the most recent transaction is acceptable.

    1-phase + checksum durable commits (1PC+C)

    • Behavior: The default strategy. Uses a single fsync.
    • Mechanism: Writes data, checksums, and a transaction ID, then flips the primary page and calls fsync.
    • Recovery: If a crash occurs, redb verifies the transaction ID and checksums. If invalid, it rolls back to the secondary page.
    • Security Note: Uses non-cryptographic XXH3 checksums. Theoretically vulnerable to attackers who can control disk flush order and crash timing to forge valid checksums for partial writes.

    2-phase durable commits (2PC)

    • Behavior: Mitigates theoretical attacks involving malicious data and high control over the process.
    • Mechanism:
      1. Write data to a new B+tree copy.
      2. Perform fsync.
      3. Flip the primary byte.
      4. Perform a second fsync.
    • Use Case: Required when accepting potentially malicious input where checksum collision resistance is a concern.
  7. Configure a redb Database using the Builder

    master

    Use Database::builder() to configure and open or create a database. This allows you to customize the cache size, page size, and repair behavior.

    Key configuration methods:

    • set_cache_size(bytes): Sets the amount of memory in bytes used for caching data. Default is 1GiB.
    • set_page_size(size): Sets the internal page size. Must be a power of two and $\ge 512$. Note: This is part of the file format.
    • set_region_size(size): Sets the region size (must be a power of two).
    • set_repair_callback(callback): Sets a callback invoked if the database file needs repair. The callback receives a RepairSession which can be used to monitor progress or abort the repair.
    let db = Database::builder()
        .set_cache_size(1024 * 1024)
        .set_page_size(8 * 1024)
        .set_region_size(32 * 4096)
        .create("path/to/db")
        .unwrap();
  8. Reference the redb Transaction Slot layout

    master

    The database uses two 128-byte 'commit slots' (slot 0 and slot 1) for atomic commits. The god byte toggles between them. Each slot follows this layout:

    FieldSizeDescription
    version1 byteFile format version number.
    user root non-null1 byteBoolean: is user root page non-null?
    sys root non-null1 byteBoolean: is system root page non-null?
    freed table root non-null1 byteBoolean: is freed table root page non-null?
    padding4 bytesAlignment padding.
    user root page8 bytesPage number of the user table tree root.
    user root checksum16 bytesXXH3_128bit checksum of the user root page.
    user root length8 bytesNumber of tables in the user table tree (v2+).
    system root page8 bytesPage number of the system table tree root.
    system root checksum16 bytesXXH3_128bit checksum of the system root page.
    system root length8 bytesNumber of tables in the system table tree (v2+).
    last committed transaction id8 bytesThe ID of the last committed transaction.
    slot checksum16 bytesXXH3_128bit checksum of all preceding fields in the slot.
  9. Understand the Region Tracker and BtreeBitmap

    master

    The Region Tracker is an array of BtreeBitmaps used to track free page orders in each region.

    • Storage: On clean shutdown, it is written to a page in a region's data section. For crash recovery (quick-repair), it is written to an entry in the allocator state table.
    • BtreeBitmap Structure: A 64-way tree where each node is a single bit (packed into u64) indicating if any descendant is free.

    BtreeBitmap Layout:

    • height (4 bytes): The tree height.
    • end offset... (4 bytes, repeating): The ending offsets of layers (excluding the root layer).
    • Tree data: The actual bit-packed tree data.
  10. Filesystem requirements for redb safety

    master

    redb is designed to be safe during power failures or on poorly behaved media, provided the underlying filesystem meets these three assumptions:

    1. Atomic Single-Byte Writes: Each byte is written completely or not at all, even during power failure.
    2. Durable fsync: Following an fsync operation, writes are guaranteed to be durable.
    3. Powersafe Overwrite: When writing a range of bytes, no bytes outside that specific range will change (even if a crash occurs during the write).
  11. Understand the redb file format and logical structure

    master

    A redb database file is organized into several logical components:

    • Pending Free Tree: Maps transaction IDs to the list of pages they have freed.
    • Table Tree: A mapping of table names to their respective definitions.
    • Data Trees: One per table, providing the actual key-to-value mapping.
    • Regions: The file is divided into regions to allow for efficient, dynamic growth. Each region contains a header and a data section divided into pages.

    Technical Note: All multi-byte integers are stored in little-endian order.

  12. Reference the redb Database Header fields

    master

    The database header (64 bytes) contains immutable configuration and state information. It is part of the 512-byte super-header.

    FieldSizeDescription
    magic number9 bytesMust be ASCII 'redb' followed by 0x1A, 0x0A, 0xA9, 0x0D, 0x0A.
    god byte1 byteA bitfield controlling database state (see God Byte details).
    padding2 bytesAlignment padding.
    page size4 bytesThe size of a redb page in bytes.
    region header pages4 bytesNumber of pages in each region's header.
    region max data pages4 bytesMaximum number of data pages in each region.
    number of full regions4 bytesNumber of full regions (valid if no recovery is required).
    data pages in trailing region4 bytesNumber of pages in the last (partial) region (valid if no recovery is required).
    padding32 bytesAlignment padding to reach 64 bytes.