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).