Low-Level LMDB API¶
Overview¶
The zlmdb low-level LMDB API (zlmdb.lmdb) provides direct access to LMDB’s core functionality with full control over transactions, cursors, and database operations.
This API is:
py-lmdb compatible: Drop-in replacement for the popular py-lmdb package
Full-featured: Complete access to LMDB capabilities
High-performance: Minimal overhead, maximum speed
Expert-friendly: For users who know LMDB and want fine-grained control
import zlmdb.lmdb as lmdb
env = lmdb.open('/tmp/mydb', max_dbs=10)
db = env.open_db(b'users')
with env.begin(write=True) as txn:
txn.put(b'alice', b'alice@example.com', db=db)
When to Use the Low-Level API¶
Choose the low-level LMDB API when you:
- Need Maximum Performance
Every nanosecond counts
Want zero abstraction overhead
Direct control over memory layout
- Have Existing py-lmdb Code
Migration from py-lmdb
Existing scripts and tools
Familiarity with LMDB concepts
- Require Fine-Grained Control
Custom key/value encodings
Specialized cursor operations
Advanced LMDB features (duplicate keys, etc.)
- Want Simplicity
No schema required
No serialization layer
Pure key-value operations
When NOT to Use the Low-Level API¶
Consider the High-Level ORM API instead if you:
Want automatic serialization (JSON, CBOR, FlatBuffers)
Need schema management and type safety
Prefer object-oriented data access
Want automatic indexes and relationships
Are building a Python application (not a system tool)
The ORM provides these conveniences while maintaining good performance.
Key Differences from py-lmdb¶
zlmdb’s zlmdb.lmdb is designed as a drop-in replacement for py-lmdb with the following differences:
- Implementation
zlmdb uses CFFI (not CPyExt)
Better PyPy performance (JIT-friendly)
Same API surface
- Dependencies
zlmdb bundles LMDB (vendored)
py-lmdb requires system LMDB installation
zlmdb has no external dependencies
- Compatibility
Both libraries are compatible at the database file level - databases created with py-lmdb can be opened with zlmdb and vice versa.
Quick Start¶
Basic operations with the low-level API:
Opening a database:
import zlmdb.lmdb as lmdb
# Open environment (directory, not file)
env = lmdb.open('/tmp/mydb',
map_size=10*1024*1024, # 10MB
max_dbs=10) # Number of named databases
# Open (or create) a named database
db = env.open_db(b'users')
Writing data:
# Write transaction
with env.begin(write=True) as txn:
txn.put(b'alice', b'alice@example.com', db=db)
txn.put(b'bob', b'bob@example.com', db=db)
# Automatic commit on context exit
Reading data:
# Read transaction
with env.begin() as txn:
email = txn.get(b'alice', db=db)
print(email) # b'alice@example.com'
Iterating with cursors:
with env.begin() as txn:
with txn.cursor(db=db) as cursor:
for key, value in cursor:
print(f"{key.decode()}: {value.decode()}")
For complete examples, see LMDB API Quick Start.
API Documentation¶
Core Concepts¶
Environment¶
An LMDB environment is a directory containing one or more databases:
env = lmdb.open('/path/to/dir',
map_size=1024*1024*1024, # 1GB
max_dbs=10, # Up to 10 named databases
readonly=False, # Read-write mode
metasync=True, # Sync metadata
sync=True, # Sync data
map_async=False) # Sync immediately
Key parameters:
map_size: Maximum database size (can be very large, only uses what’s needed)max_dbs: Number of named databases (sub-databases) allowedreadonly: Open in read-only mode (allows multiple reader processes)sync: Flush data to disk on commit (durable but slower)
Database¶
A database is a key-value store within an environment:
# Unnamed database (default)
db = None
# Named database
db = env.open_db(b'users', dupsort=False)
Parameters:
dupsort: Allow duplicate keys (default: False)create: Create if doesn’t exist (default: True)
Transactions¶
Transactions provide ACID guarantees:
# Read transaction (shared lock)
with env.begin() as txn:
value = txn.get(b'key', db=db)
# Write transaction (exclusive lock)
with env.begin(write=True) as txn:
txn.put(b'key', b'value', db=db)
txn.delete(b'old_key', db=db)
Properties:
Read transactions are lock-free (MVCC)
Write transactions serialize automatically
Nested transactions not supported
Use context managers for automatic commit/abort
See LMDB Transactions for advanced patterns.
Cursors¶
Cursors enable efficient iteration and positioning:
with env.begin() as txn:
with txn.cursor(db=db) as cursor:
# Iterate all records
for key, value in cursor:
process(key, value)
# Seek to position
if cursor.set_key(b'start_key'):
# Found, now iterate from here
for key, value in cursor:
process(key, value)
See LMDB Cursors for cursor operations.
Performance Characteristics¶
The low-level API delivers exceptional performance:
- Reads
Lock-free: MVCC allows concurrent readers
Zero-copy: Memory-mapped I/O, no buffer copying
Millions of reads/sec on modern hardware
- Writes
Serialized: One writer at a time
Batch-friendly: Commit overhead amortized over transaction
Hundreds of thousands of writes/sec
- Memory
Shared: Memory-mapped pages shared across processes
Efficient: Only active data in RAM
No cache: OS manages page cache
See LMDB API Performance for detailed benchmarks.
Best Practices¶
Use context managers:
# Good: Auto-commit/abort
with env.begin(write=True) as txn:
txn.put(b'key', b'value')
# Bad: Manual management
txn = env.begin(write=True)
txn.put(b'key', b'value')
txn.commit()
Batch writes:
# Good: One transaction for many writes
with env.begin(write=True) as txn:
for key, value in data:
txn.put(key, value, db=db)
# Bad: Many transactions
for key, value in data:
with env.begin(write=True) as txn:
txn.put(key, value, db=db)
Use appropriate map_size:
# Good: Generous map_size (only uses what's needed)
env = lmdb.open(path, map_size=10*1024*1024*1024) # 10GB
# Bad: Too small, requires frequent resizing
env = lmdb.open(path, map_size=1024*1024) # 1MB
Close resources:
# Always close when done
env.close()
# Or use context manager (Python 3.10+)
with lmdb.open(path) as env:
# Use env
pass
Migration from py-lmdb¶
If you have existing py-lmdb code, migration is simple:
Change import:
# Old
import lmdb
# New
import zlmdb.lmdb as lmdb
That’s it! Your code should work unchanged.
Why migrate?
Vendored LMDB: No system dependency
Binary wheels: Easy installation
PyPy performance: CFFI-based for JIT optimization
Consistent versioning: LMDB version bundled with zlmdb
Examples¶
See LMDB Examples for complete working examples:
address-book.py: Simple CRUD operations
dirtybench.py: Write performance benchmarking
nastybench.py: Stress testing
parabench.py: Parallel access patterns
Next Steps¶
LMDB API Quick Start - Learn basic operations
LMDB Transactions - Master transaction patterns
LMDB Cursors - Efficient iteration and seeking
LMDB Examples - Study complete working examples
LMDB API Performance - Understand performance characteristics
See also
High-Level ORM API - High-level ORM alternative