Limitations¶
The store is deliberately small, and its edges are worth knowing before you rely on it.
Copying a store requires a quiescent source¶
The database uses a rollback journal, which keeps a store portable as a single file, but that is not a snapshot guarantee on its own. Copying while a transaction is open can capture a half-applied state, and copying while a writer still holds unflushed metrics or conditions captures less than the whole store. Copy only from a store that no process is mid-writing and that has been flushed. See sync.
Sync is not fully atomic¶
Sync validates every merged table before copying a single file, and installs all five tables in one transaction — a rejected merge never reaches the install step. That is not a full rollback, though:
- Sync first flushes the destination's own buffered metrics and conditions under the store lock. Those writes are already committed by the time the merge is validated, so a later failure does not undo them.
- Files copied before a later step fails are not removed.
A failed sync leaves the local store's previously buffered records written and any already-copied files in place. Only the table install is all-or-nothing.
The post-commit unlink is not crash-atomic¶
Deleting an evaluation commits its metadata deletion first and unlinks orphaned artifact files afterwards. A crash between the two leaves the file on disk with no metadata pointing at it. This ordering is intentional — a stray file is harmless, while deleting a file whose metadata still exists is destructive — but it does mean the store can accumulate files nothing references.
atexit flush is best-effort¶
Buffered metrics and conditions are flushed at the end of an evaluation block,
at their batch threshold, on flush(), and on normal process exit via atexit.
atexit does not run on SIGKILL, an abort, a segfault, or a power loss, and it
does not run when a worker process is hard-killed by a scheduler. Anything
written but not yet flushed in those cases is lost. Call flush() explicitly
before copying a store, before handing it to another process, or at any point
where the process might not exit cleanly.
Evaluation rollback is exception cleanup, not a crash-proof transaction: a process killed mid-evaluation keeps whatever was already committed.
update_table is an unconditional whole-table replace¶
update_table(kind, df) deletes every row in the table and inserts the frame you
give it, in one transaction. It validates the canonical columns, rejects
duplicate primary keys, and rejects absolute or escaping stored paths — but it
will not help you merge. Passing a frame that is missing rows silently drops
them. It exists for bulk loads such as the
migration, not for incremental edits.
Loads read whole tables¶
There is no query pushdown. load_table and its per-table wrappers read the
entire table into a pandas frame with explicit dtypes, and the CLI's query
runs DataFrame.query over that frame in memory. Memory use grows with the
table, and a query's cost is a full scan. This is fine for the sizes the store
targets; it is not built for millions of sample-level metrics in one store.
Buffers are per instance¶
The metric and condition buffers live on the ExperimentStore object. Two
ExperimentStore objects over the same directory have independent buffers, and
neither's flush() drains the other's. Create one store per process (the
Lightning callback does) and explicitly flush before sharing a directory.
condition.value has a fixed type set¶
add_condition accepts str, int, float, or bool; the value is stored as
JSON and keeps its exact Python type on read, with 1, 1.0, and True staying
distinct. Other types (a datetime, a list, a dict) are not part of the
supported set.
Paths must stay inside the store¶
Config, checkpoint, and artifact paths are stored relative to the store root and
must remain contained. An absolute path, or one that escapes the root with ..,
raises ValueError on every public write path. A file that must live outside
the store has to be copied in first, or referenced by your own convention in a
non-path column.