ORM Examples¶
Real-world ORM usage patterns from production zlmdb applications.
Overview¶
This guide demonstrates zlmdb ORM patterns drawn from production codebases:
Crossbar.io via cfxdb - Session management, event history, authentication
pydefi - Cryptocurrency market data persistence
All examples shown are from real production code, demonstrating battle-tested patterns.
Schema Architecture¶
Pattern from: cfxdb.cookiestore
zlmdb ORM uses a Schema class to organize related tables:
from zlmdb import Schema
class CookieStore(Schema):
"""
Schema for HTTP cookie-based authentication.
Used in Crossbar.io for web transport authentication.
"""
def attach(self, db):
"""Attach tables to database environment"""
# Define main tables
self.cookies_by_authid = ...
self.cookies_by_cookie_id = ...
# Define index tables
self.idx_cookies_by_modified = ...
Key Concepts:
Schema class organizes related tables
``attach()`` method binds tables to LMDB environment
Table attributes use type hints for IDE support
Index tables created alongside main tables
Table Definition with @table Decorator¶
Pattern from: cfxdb.globalschema
The @table decorator defines tables with UUID-based identifiers:
from zlmdb import table, MapUuidCbor, MapUuidFlatBuffers
@table('e56ca7df-4cf2-4f38-a0ac-34d9be1ce9c3')
class User:
"""User entity with CBOR serialization"""
pass
@table('a739ff38-d3ed-43c0-8a06-090ba7198c39',
marshal=lambda obj: obj.build(),
unmarshal=lambda data: User.cast(data))
class Market:
"""Market entity with FlatBuffers serialization"""
pass
In schema class:
class GlobalSchema(Schema):
def attach(self, db):
# Simple table with CBOR serialization
self.users = MapUuidCbor(User)
# Table with FlatBuffers (for performance)
self.markets = MapUuidFlatBuffers(Market)
Key Concepts:
UUID table identifiers enable schema versioning
``@table`` decorator marks persistent classes
Serialization strategies chosen per table (CBOR vs FlatBuffers)
``marshal``/``unmarshal`` customize serialization
Serialization Strategies¶
From production usage analysis:
CBOR Serialization (Simple Data)¶
When to use:
Simple data structures (dicts, lists, primitives)
Rapid development
Schema flexibility
Human-readable debugging
Example from cfxdb:
from zlmdb import MapUuidCbor
# Cookie authentication data (simple dict)
self.cookies = MapUuidCbor(1,
marshal=lambda obj: obj.__dict__,
unmarshal=lambda data: Cookie(**data))
Characteristics:
✅ Easy to use (automatic serialization)
✅ Handles Python types naturally
✅ Good for development/prototyping
⚠️ Slower than FlatBuffers for complex objects
FlatBuffers Serialization (Complex/Performance-Critical)¶
When to use:
Complex nested structures
Performance-critical paths
Large datasets
Type safety required
Example from pydefi:
from zlmdb import MapUuidFlatBuffers
# Market data with complex schema
self.markets = MapUuidFlatBuffers(2,
build=Market.build,
cast=Market.cast)
Characteristics:
✅ Zero-copy deserialization
✅ Strong type safety (schema evolution)
✅ Excellent performance
⚠️ Requires FlatBuffers schema definition
Production guideline: Start with CBOR for development, migrate to FlatBuffers when performance matters.
Index Patterns¶
Pattern from: pydefi.database
Indexes enable efficient lookups beyond primary keys:
Simple Index (One-to-Many)¶
from zlmdb import MapUuidUuidSet
class MarketSchema(Schema):
def attach(self, db):
# Main table: market_id -> Market object
self.markets = MapUuidFlatBuffers(1, ...)
# Index: exchange_id -> set of market_ids
self.markets_by_exchange = MapUuidUuidSet(2)
Usage:
# Write: insert market and update index
with db.begin(write=True) as txn:
# Insert main record
schema.markets[txn, market.oid] = market
# Update index
schema.markets_by_exchange[txn, market.exchange_oid].add(market.oid)
# Read: find all markets for an exchange
with db.begin() as txn:
market_ids = schema.markets_by_exchange[txn, exchange_id]
for market_id in market_ids:
market = schema.markets[txn, market_id]
process(market)
Composite Index (Time-Series)¶
Pattern from pydefi for time-series data:
from zlmdb import MapTimestampUuidCbor
class TickerSchema(Schema):
def attach(self, db):
# Composite key: (timestamp, market_id) -> ticker data
self.tickers = MapTimestampUuidCbor(3)
Usage:
# Write time-series data
with db.begin(write=True) as txn:
key = (ticker.timestamp, ticker.market_oid)
schema.tickers[txn, key] = ticker.data
# Range query: get all tickers for market in time range
with db.begin() as txn:
from_key = (start_time, market_id)
to_key = (end_time, market_id)
for (ts, mid), data in schema.tickers.select(txn,
from_key=from_key,
to_key=to_key):
process(ts, data)
Key Concepts:
Index tables separate from main tables
``MapUuidUuidSet`` for one-to-many relationships
Composite keys for time-series patterns
Manual index maintenance (application responsibility)
Foreign Key Relationships¶
Pattern from: pydefi market hierarchy
zlmdb uses UUID-based foreign keys for relationships:
from uuid import uuid4
# Entity definitions
@table('uuid-1')
class Exchange:
oid: UUID # Primary key
name: str
@table('uuid-2')
class Market:
oid: UUID # Primary key
exchange_oid: UUID # Foreign key to Exchange
pair_oid: UUID # Foreign key to Pair
symbol: str
# Schema with relationships
class TradingSchema(Schema):
def attach(self, db):
self.exchanges = MapUuidCbor(1)
self.markets = MapUuidCbor(2)
self.pairs = MapUuidCbor(3)
# Indexes for foreign key lookups
self.markets_by_exchange = MapUuidUuidSet(10)
self.markets_by_pair = MapUuidUuidSet(11)
Usage:
# Create related entities
with db.begin(write=True) as txn:
# Create exchange
exchange = Exchange(oid=uuid4(), name="Binance")
schema.exchanges[txn, exchange.oid] = exchange
# Create market with foreign keys
market = Market(
oid=uuid4(),
exchange_oid=exchange.oid, # FK to exchange
pair_oid=pair.oid, # FK to pair
symbol="BTC/USDT"
)
schema.markets[txn, market.oid] = market
# Maintain indexes
schema.markets_by_exchange[txn, exchange.oid].add(market.oid)
schema.markets_by_pair[txn, pair.oid].add(market.oid)
# Navigate relationships
with db.begin() as txn:
# Get market
market = schema.markets[txn, market_id]
# Follow FK to exchange
exchange = schema.exchanges[txn, market.exchange_oid]
# Find all markets for exchange (using index)
market_ids = schema.markets_by_exchange[txn, exchange.oid]
Key Concepts:
UUIDs for all primary keys
Foreign keys stored as UUID fields
No automatic cascade (application manages)
Indexes enable reverse lookups (FK → PK)
Multi-Table Schemas¶
Pattern from: cfxdb.globalschema
Large applications organize 20+ tables in a single schema:
class GlobalSchema(Schema):
"""
Crossbar.io management realm schema (20+ tables).
Organizes:
- User/authentication tables
- Node/worker management
- Router realm configuration
- Application deployment
"""
def attach(self, db):
# User management
self.users = MapUuidCbor(1)
self.credentials = MapUuidCbor(2)
self.activations = MapUuidCbor(3)
# Node management
self.nodes = MapUuidCbor(10)
self.workers = MapUuidCbor(11)
self.controllers = MapUuidCbor(12)
# Router configuration
self.router_realms = MapUuidCbor(20)
self.router_realm_roles = MapUuidCbor(21)
# Indexes
self.idx_users_by_email = MapStringUuid(100)
self.idx_nodes_by_pubkey = MapBytesUuid(101)
# ... 20 more tables
Organizational Patterns:
Table ID ranges by functional area (1-9: users, 10-19: nodes, etc.)
Main tables use low IDs (1-99)
Index tables use high IDs (100+)
Type hints on schema class for IDE support
Real-World Example: Market Data (pydefi)¶
From: pydefi project
Cryptocurrency market data persistence patterns:
Schema:
class PyDefiSchema(Schema):
"""Cryptocurrency market data (exchanges, markets, trades, tickers)"""
def attach(self, db):
# Entities (FlatBuffers for performance)
self.exchanges = MapUuidFlatBuffers(1, ...)
self.markets = MapUuidFlatBuffers(2, ...)
self.pairs = MapUuidFlatBuffers(3, ...)
# Time-series data (composite keys)
self.trades = MapTimestampUuidFlatBuffers(10, ...)
self.tickers = MapTimestampUuidFlatBuffers(11, ...)
# Indexes
self.markets_by_exchange = MapUuidUuidSet(100)
self.markets_by_pair = MapUuidUuidSet(101)
Usage:
# Store market tick
def store_ticker(self, market_id, timestamp, data):
with self.db.begin(write=True) as txn:
key = (timestamp, market_id)
self.tickers[txn, key] = data
# Query time-series data
def get_tickers(self, market_id, start_time, end_time):
with self.db.begin() as txn:
from_key = (start_time, market_id)
to_key = (end_time, market_id)
tickers = []
for (ts, mid), data in self.tickers.select(
txn, from_key=from_key, to_key=to_key
):
tickers.append((ts, data))
return tickers
Patterns Demonstrated:
FlatBuffers for high-performance market data
Composite keys (timestamp, market_id) for time-series
Range queries using
select()Entity-relationship model (Exchange → Market → Pair)
Best Practices from Production¶
1. Schema Organization
Group related tables in Schema classes
Use UUID table identifiers (enables versioning)
Reserve ID ranges for functional areas
Document table purposes
2. Serialization Choice
CBOR: Simple data, rapid development, dict-based
FlatBuffers: Complex schemas, performance-critical, type safety
NumPy: Numerical arrays, scientific data
3. Index Strategy
Create indexes for frequent lookups
Maintain indexes manually (no automatic cascades)
Use
MapUuidUuidSetfor one-to-manyUse composite keys for time-series
4. Transaction Patterns
Keep transactions short
Batch related operations
Use read transactions by default
Minimize write transaction duration
5. Foreign Keys
Always use UUIDs for primary keys
Store foreign keys as UUID fields
Create indexes for reverse lookups
Document relationships in schema class
Complete Schema Example¶
Putting it all together:
from zlmdb import Schema, table
from zlmdb import MapUuidCbor, MapUuidUuidSet, MapStringUuid
from uuid import UUID
@table('550e8400-e29b-41d4-a716-446655440001')
class Company:
oid: UUID
name: str
country: str
@table('550e8400-e29b-41d4-a716-446655440002')
class Employee:
oid: UUID
company_oid: UUID # FK to Company
email: str
name: str
class HRSchema(Schema):
"""Human resources database schema"""
def attach(self, db):
# Main tables
self.companies = MapUuidCbor(1,
marshal=lambda obj: obj.__dict__,
unmarshal=lambda data: Company(**data))
self.employees = MapUuidCbor(2,
marshal=lambda obj: obj.__dict__,
unmarshal=lambda data: Employee(**data))
# Indexes
self.employees_by_company = MapUuidUuidSet(10)
self.employees_by_email = MapStringUuid(11)
# Usage
db = zlmdb.Database('/tmp/hr.db')
schema = HRSchema.attach(db)
# Insert company and employees
with db.begin(write=True) as txn:
company = Company(oid=uuid4(), name="Acme", country="US")
schema.companies[txn, company.oid] = company
for email in ["alice@acme.com", "bob@acme.com"]:
employee = Employee(
oid=uuid4(),
company_oid=company.oid,
email=email,
name=email.split("@")[0]
)
schema.employees[txn, employee.oid] = employee
schema.employees_by_company[txn, company.oid].add(employee.oid)
schema.employees_by_email[txn, email] = employee.oid
# Query: find all employees of a company
with db.begin() as txn:
company = schema.companies[txn, company_id]
employee_ids = schema.employees_by_company[txn, company_id]
for emp_id in employee_ids:
employee = schema.employees[txn, emp_id]
print(f"{employee.name} works at {company.name}")
See Also¶
High-Level ORM API - ORM overview
ORM Quick Start - Getting started
Schema Design Patterns - Schema design patterns
Indexes and Lookups - Index management
Serialization Strategies - Serialization strategies
cfxdb repository - Full source code
pydefi repository - Market data patterns