LMDB

Language: CPP

Database

LMDB was created by Howard Chu of the OpenLDAP project to provide a lightweight, transactional database for LDAP storage. Unlike traditional databases, LMDB uses memory-mapped files, allowing it to achieve excellent performance with minimal overhead. It has since been adopted widely in blockchain systems, distributed storage, and embedded applications.

LMDB (Lightning Memory-Mapped Database) is a high-performance, memory-mapped key-value store developed by the OpenLDAP project. It is compact, transactional, and highly efficient, designed for read-heavy workloads with low latency.

Installation

linux: sudo apt install liblmdb-dev
mac: brew install lmdb
windows: vcpkg install lmdb or build from source

Usage

LMDB provides a transactional key-value store API, supporting ACID properties, multiple named databases, and concurrent readers. It is optimized for read-heavy workloads with very fast lookup performance.

Creating and writing to a database

#include <lmdb.h>
#include <iostream>

int main() {
    MDB_env* env;
    MDB_dbi dbi;
    MDB_val key, data;
    MDB_txn* txn;
    MDB_cursor* cursor;

    // Create environment
    mdb_env_create(&env);
    mdb_env_set_maxdbs(env, 1);
    mdb_env_open(env, "./testdb", 0, 0664);

    // Start transaction
    mdb_txn_begin(env, NULL, 0, &txn);
    mdb_open(txn, NULL, 0, &dbi);

    key.mv_size = 5; key.mv_data = (void*)"hello";
    data.mv_size = 5; data.mv_data = (void*)"world";

    mdb_put(txn, dbi, &key, &data, 0);
    mdb_txn_commit(txn);

    mdb_close(env, dbi);
    mdb_env_close(env);
    return 0;
}

Creates an LMDB environment, inserts a key-value pair (`hello -> world`), and commits the transaction.

Reading values

mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
mdb_cursor_open(txn, dbi, &cursor);
while (mdb_cursor_get(cursor, &key, &data, MDB_NEXT) == 0) {
    std::cout << (char*)key.mv_data << " => " << (char*)data.mv_data << std::endl;
}
mdb_cursor_close(cursor);
mdb_txn_abort(txn);

Iterates over all key-value pairs in the LMDB database.

Using multiple databases

mdb_open(txn, "users", MDB_CREATE, &dbi);

Opens a named sub-database inside the LMDB environment.

Transactions

mdb_txn_begin(env, NULL, 0, &txn);
// Perform operations
mdb_txn_commit(txn);

Ensures ACID compliance by explicitly wrapping operations in transactions.

Concurrent readers

mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);

LMDB allows multiple concurrent read-only transactions without blocking writers.

Error Handling

MDB_MAP_FULL: Occurs when the database map size is exceeded. Increase map size with `mdb_env_set_mapsize`.
MDB_BAD_TXN: Happens when using a transaction after it has been committed or aborted.
MDB_CORRUPTED: Indicates possible data corruption. Restore from backup if repair is not possible.

Best Practices

Use LMDB for workloads with frequent reads and fewer writes.

Avoid long-running read transactions as they may block database growth.

Store small to medium-sized values; LMDB is not optimized for huge blobs.

Use named databases when organizing multiple datasets in the same environment.

Carefully size the LMDB map to avoid resizing overhead.