lmdb

Minimal LMDB bindings for Haskell.
git clone git://git.ppad.tech/lmdb.git
Log | Files | Refs | README | LICENSE

commit c8d31da1834ea29bc4e00a6cdfc748fc3ebd01b1
parent 08059713a94e7dffca071f84a936ee7124a77c27
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 24 May 2026 18:10:38 -0230

docs: flesh out README sample, add Performance section

The previous GHCi sample only covered put/get. Expand it to:

- highlight envMapSize as the first thing users should think
  about (the 10 MiB default is the most common footgun)
- demonstrate a cursor range scan via cursorSeek + cursorNext

Diffstat:
MCHANGELOG | 8++++----
MREADME.md | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
2 files changed, 73 insertions(+), 20 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,6 +1,6 @@ # Changelog -- 0.1.0 (unreleased) - * Initial release: minimal bindings to OpenLDAP LMDB. Supports - environments, transactions (phantom-typed read-only vs read-write), - databases, get / put / del, and cursor-based range scans. +- 0.1.0 (2026-05-24) + * Initial release, consisting of support for LMDB environments, + transactions, basic row operations (get, put, del), and range scans. + diff --git a/README.md b/README.md @@ -4,14 +4,12 @@ ![](https://img.shields.io/badge/license-MIT-brightgreen) [![](https://img.shields.io/badge/haddock-lmdb-lightblue)](https://docs.ppad.tech/lmdb) -Minimal Haskell bindings to [LMDB][lmdb] (Lightning Memory-Mapped -Database), an embedded ACID key-value store. LMDB is single-writer, -zero-copy on reads, MMAP-backed, and has no server process; this -library exposes just enough of its C API to use it as a persistent -key-value blob store. +Bindings to [LMDB][lmdb] (Lightning Memory-Mapped Database), which is an +embedded ACID key-value store. -The upstream LMDB C source is vendored under `cbits/` at release -`LMDB_0.9.33`; no external `liblmdb` is required. +This library exposes a minimal subset of the LMDB API, mainly supporting +LMDB environments, transactions, basic row operations (get, put, del), +and range scans. ## Usage @@ -21,21 +19,76 @@ A sample GHCi session: > :set -XOverloadedStrings > import qualified Database.LMDB as L > - > -- open an environment + dbi, write a value, read it back - > let flags = L.defaultEnvFlags { L.envNoSubdir = True } - > L.withEnv "/tmp/mydb" flags $ \env -> do + > -- envMapSize caps the on-disk size; the default (10 MiB) is + > -- intentionally small. writes past it fail with LMDBMapFull, + > -- so set it to something appropriate for your workload + > let flags = L.defaultEnvFlags + > { L.envNoSubdir = True + > , L.envMapSize = 256 * 1024 * 1024 -- 256 MiB + > } + > + > -- populate the env in a write transaction + > L.withEnv "/tmp/mydb" flags $ \env -> > L.withWriteTxn env $ \txn -> do > dbi <- L.openDbi txn Nothing True - > L.put txn dbi "hello" "world" + > L.put txn dbi "apple" "red" + > L.put txn dbi "banana" "yellow" + > L.put txn dbi "cherry" "red" + > + > -- point lookup + > L.withEnv "/tmp/mydb" flags $ \env -> + > L.withReadTxn env $ \txn -> do + > dbi <- L.openDbi txn Nothing False + > L.get txn dbi "banana" + Just "yellow" + > + > -- range scan; every entry with key >= "b" + > L.withEnv "/tmp/mydb" flags $ \env -> > L.withReadTxn env $ \txn -> do > dbi <- L.openDbi txn Nothing False - > L.get txn dbi "hello" - Just "world" + > L.withCursor txn dbi $ \cur -> do + > let go acc Nothing = pure (reverse acc) + > go acc (Just kv) = L.cursorNext cur >>= go (kv : acc) + > L.cursorSeek cur "b" >>= go [] + [("banana","yellow"),("cherry","red")] ``` -Read-only and read-write transactions are distinguished at the type -level via a phantom parameter, so writing from a read-only transaction -is a compile-time error. +## Performance + +Current benchmark figures on an M4 Silicon MacBook Air look like (use +`cabal bench` to run the benchmark suite): + +``` + benchmarking put/1k inserts (one txn) + time 559.5 μs (557.5 μs .. 562.0 μs) + 1.000 R² (0.999 R² .. 1.000 R²) + mean 561.8 μs (559.6 μs .. 567.0 μs) + std dev 11.34 μs (5.445 μs .. 21.32 μs) + + benchmarking put/10k inserts (one txn) + time 4.026 ms (4.016 ms .. 4.037 ms) + 1.000 R² (1.000 R² .. 1.000 R²) + mean 4.013 ms (4.005 ms .. 4.022 ms) + std dev 26.80 μs (22.08 μs .. 36.75 μs) + + benchmarking get/1k random hits + time 307.1 μs (306.3 μs .. 308.1 μs) + 1.000 R² (1.000 R² .. 1.000 R²) + mean 308.0 μs (307.3 μs .. 309.4 μs) + std dev 3.368 μs (1.893 μs .. 5.357 μs) + + benchmarking get/10k random hits + time 2.882 ms (2.872 ms .. 2.893 ms) + 1.000 R² (1.000 R² .. 1.000 R²) + mean 2.884 ms (2.879 ms .. 2.889 ms) + std dev 16.25 μs (13.40 μs .. 21.54 μs) + + benchmarking cursor/scan 10k entries + time 1.249 ms (1.245 ms .. 1.258 ms) + 0.999 R² (0.996 R² .. 1.000 R²) + mean 1.251 ms (1.245 ms .. 1.281 ms) + std dev 35.00 μs (5.595 μs .. 82.99 μs) +``` ## Documentation