timing.database

Attributes

logger

Classes

StepTimingDatabase

Async SQLite store for raw step execution history and anomaly flags.

Module Contents

timing.database.logger
class timing.database.StepTimingDatabase(db_path: str)

Async SQLite store for raw step execution history and anomaly flags.

The per-step hot path does zero disk I/O. On the Pi’s SD card the disk was the sole source of the “standstill between steps”: a per-step record_execution SELECT that could stall behind the background writer’s WAL-checkpoint fsync (measured up to ~1.4 s on a single step). So the design is now “all in RAM during the run, persist once after it”:

  1. Reads never touch disk during the run. The whole history is bulk-seeded into per-signature in-memory windows once at initialize() (before the run starts). After that the in-memory window is authoritative — fetch_recent_durations() and the anomaly baseline are pure RAM.

  2. Writes never touch disk during the run. insert_execution() appends to an in-RAM buffer and updates the in-memory baseline synchronously (so read-after-write stays consistent). Nothing is committed on the step transition.

  3. The durable write happens once, after the run, when flush() (or aclose()) is called from the run-completion path. All buffered rows go out in a single executemany + commit while the robot is already idle, so the WAL-checkpoint fsync never lands mid-run.

A hard power-loss (process killed before flush) drops that run’s timing rows, which is fine for diagnostic data — the trade the caller explicitly accepts to keep the run’s motion smooth.

db_path
async initialize() None

Open the persistent connection and create the schema if needed.

If the existing database file is corrupted, it is deleted and recreated automatically. The whole history is bulk-loaded into the in-memory windows here (once, before the run) so no per-signature SELECT ever runs on the hot path, and durable writes are deferred to flush().

async fetch_recent_durations(signature: str, limit: int) list[float]

Return the most recent non-anomalous durations for a signature.

Served from the in-memory window (newest first), so this never touches disk on the hot path after the one-time seed.

async insert_execution(signature: str, duration: float, anomaly: timing.models.AnomalyDetection | None) None

Record a completed execution.

Updates the in-memory baseline synchronously (so a subsequent read sees it) and buffers the durable write in RAM. Returns without touching disk — the step never waits on a commit; persistence happens in flush() after the run.

async flush() None

Persist all buffered executions to disk in one batched commit.

Called from the run-completion path (and by aclose()/tests). This is the only place the step-timing history touches the disk on the hot connection, so the WAL-checkpoint fsync it may trigger lands while the robot is already idle, never between two steps.

async aclose() None

Flush pending writes and close the DB (explicit shutdown/tests).