Internal storage configuration
CocoIndex persists target states and memo results in LMDB. The database grows automatically when a write runs out of space; tune the initial map_size and max_dbs via environment variables or programmatic settings for large-scale deployments.
CocoIndex uses an LMDB database to persist its internal state. This database tracks target states and memoization results from previous runs, enabling CocoIndex to detect what changed and apply only the necessary updates.
Database path
CocoIndex needs a database path (db_path) to know where to store this internal state. The simplest way to set it is via the COCOINDEX_DB environment variable:
export COCOINDEX_DB=./cocoindex.db
You can also set it programmatically in a lifespan function:
@coco.lifespan
def coco_lifespan(builder: coco.EnvironmentBuilder) -> Iterator[None]:
builder.settings.db_path = pathlib.Path("./cocoindex.db")
yield
Or pass it directly when creating a Settings object:
settings = coco.Settings(db_path=pathlib.Path("./cocoindex.db"))
Setting db_path in the lifespan or Settings takes precedence over the COCOINDEX_DB environment variable. If neither is provided, CocoIndex will raise an error.
LMDB tuning
The LMDB database has two tunable settings, grouped under coco.LmdbSettings and attached to Settings as db_settings. The defaults work well for most use cases — you only need to adjust them for large-scale deployments.
| Setting | Default | Env Variable | Description |
|---|---|---|---|
max_dbs | 1024 | COCOINDEX_LMDB_MAX_DBS | Maximum number of apps (named LMDB sub-databases) sharing one db_path. Unlike map_size, this is a hard cap — creating one app beyond it fails. Applied at open time, so raising it takes effect on the next start. Must be ≥ 1. |
map_size | 4294967296 (4 GiB) | COCOINDEX_LMDB_MAP_SIZE | Initial size of the LMDB memory map in bytes. CocoIndex enlarges the map automatically as the state grows, so this is a starting point, not a cap. Must be > 0; rounded up to the nearest multiple of the system page size. |
When to adjust
map_size— most deployments never need to touch it. The map grows on its own and each step is cheap, so it is worth setting only when that growth actually costs you something: a large state re-climbing from 4 GiB on every restart, or resize pauses you can measure. If you do size it up, every enlargement logsLMDB map full, auto-resizing to N bytes and retrying— the largestNyou see is the number to use. On 64-bit systems,map_sizeis a virtual address space reservation, so setting it larger than needed is safe and consumes neither physical memory nor disk.max_dbs— raise it if you have an unusually large number of apps sharing a single database directory.
When a write runs out of map space, CocoIndex doubles the map and retries, repeating until the write fits. The remap itself is cheap — no data copy, no file rewrite — but it needs exclusivity: it waits for every LMDB transaction open in the process to finish, and queues new ones behind it. That’s imperceptible for ordinary reads and writes, but a long streaming read (cocoindex show over a large app) holds its transaction for the whole stream, and the resize waits that long.
The enlarged size doesn’t carry over. The next process opens at the configured map_size, raised to at least the space the data file already occupies — the file’s high-water mark, not the live data, since LMDB reuses freed pages but never shrinks the file. So a database that outgrew its map_size still opens and reads fine, just with no headroom: the next write needing new space enlarges it again. A map that grew to 16 GiB over a 10 GiB file reopens at 10 GiB, not 16, then doubles to 20 GiB on the next write that needs space.
- The memoization cache — usually the bulk. Each entry stores its call’s return value, so memoized functions that return large values (e.g., embedding vectors) dominate quickly.
- Target-state bookkeeping — grows with the number of target states your app declares.
If internal state grows faster than your targets, oversized memoized return values are the first thing to check — see the cost note and placement rule in When to memoize.
Configuration
Via environment variables:
export COCOINDEX_LMDB_MAP_SIZE=8589934592 # 8 GiB
export COCOINDEX_LMDB_MAX_DBS=2048
Or programmatically in a lifespan function:
@coco.lifespan
def coco_lifespan(builder: coco.EnvironmentBuilder) -> Iterator[None]:
builder.settings.db_path = pathlib.Path("./cocoindex.db")
builder.settings.db_settings.map_size = 8 * 1024 * 1024 * 1024 # 8 GiB
builder.settings.db_settings.max_dbs = 2048
yield
Or when creating a Settings object directly:
settings = coco.Settings(
db_path=pathlib.Path("./cocoindex.db"),
db_settings=coco.LmdbSettings(
map_size=8 * 1024 * 1024 * 1024, # 8 GiB
max_dbs=2048,
),
)
When using Settings.from_env(), the LMDB settings are automatically loaded from their environment variables if set; otherwise, the defaults apply.
For backward compatibility, Settings still accepts lmdb_max_dbs and lmdb_map_size as keyword arguments, and exposes them as attributes (e.g., settings.lmdb_map_size = ...). These read and write the same underlying values as settings.db_settings.max_dbs / settings.db_settings.map_size. Passing both db_settings= and the legacy keywords in the same Settings(...) call raises ValueError.