Lifecycle: flush, close, shutdown
Three calls manage durability and shutdown. All three accept a timeout bounding how long they wait
(timeout_secs for flush; close_timeout for close / shutdown_logger); pass None to wait without
bound. The defaults are finite so an unreachable backend can never hang interpreter shutdown forever.
| Call | What it does | Run state after |
|---|---|---|
flush(timeout_secs=900) |
Durability barrier — returns only once everything logged before it is durable. | Run stays open, logger usable. |
close(close_timeout=900) |
Flush, mark the run closed, stop the engine. | Run closed. |
shutdown_logger(close_timeout=900) |
Flush, stop the engine, but leave the run open. | Run open (resumable). |
flush — a durability barrier
Section titled “flush — a durability barrier”metrana.flush(timeout_secs=900)flush returns only once everything logged before it is durable — acknowledged by the backend, or (with a
disk spool) fsync’d to it. It rides out a transient outage (a reconnect does not fail it) and raises only
if that data is actually dropped (MetranaSenderError) or the timeout elapses (MetranaTimeoutError —
not loss; the data is still in flight or on the spool). The logger stays usable afterwards.
Call flush at checkpoints. A flush() next to your checkpoint write makes the metrics leading up to the
checkpoint durable together, so a resumed run lines up with its metrics:
if step % checkpoint_every == 0: save_checkpoint(model) metrana.flush() # metrics up to this checkpoint are now durableclose — close the run
Section titled “close — close the run”info = metrana.close(close_timeout=900)close flushes queued points, marks the run closed, and shuts the background engine down, returning a
CloseInfo. Always call it — the engine runs on a daemon thread, so if the interpreter
exits without close(), queued-but-unsent points are lost (a warning is emitted at exit). Use it directly or
via try/finally.
shutdown_logger — stop without closing
Section titled “shutdown_logger — stop without closing”info = metrana.shutdown_logger(close_timeout=900)Like close, but leaves the run open so a later process can resume it. Use it for a graceful, resumable
stop — e.g. on a spot-instance preemption signal, call it to flush in-flight data and stop cleanly
instead of being killed mid-send, then init() the same run when the job restarts. See
Resuming a run.
failover_to_spool — bounded latency on demand
Section titled “failover_to_spool — bounded latency on demand”For callers that cannot tolerate long tail latencies anywhere — not just at close — the spool can
be engaged manually. metrana.failover_to_spool() switches delivery to the disk spool immediately:
everything buffered lands on disk, logging and flushes then complete at local-disk speed, and the
recovery channel drains the spool back to the server in the background — the switch is temporary
and self-healing, returning to gRPC once the backend catches up.
The bounded-latency pattern: a non-blocking backpressure strategy, short flush timeouts, and the failover in the timeout handler — logging never blocks past your budget, and nothing is lost:
from metrana.utils.exceptions import MetranaTimeoutError
metrana.init(..., backpressure_strategy="raise", disk=metrana.DiskConfig("/var/spool/metrana", max_size_bytes=8 << 30))try: metrana.flush(timeout_secs=2)except MetranaTimeoutError: metrana.failover_to_spool() # durable on local disk within moments metrana.flush(timeout_secs=5)It returns without waiting for the switch — follow with flush() as the durability barrier — and
raises MetranaValidationError if no spool is configured.
Prefer the declarative form when the policy is uniform: init(backpressure_strategy="spool_then_raise", disk=...) makes every timed-out log_*/flush wait trigger this failover by itself and wait
disk_spool_retry_timeout_secs (default 5) longer for the spool to unblock it — the same bounded
latency without the try/except wiring. The manual call remains for precise control, e.g. failing
over ahead of a known network cutover.
CloseInfo
Section titled “CloseInfo”close(), shutdown_logger(), and replay_spool()
return a CloseInfo:
info = metrana.close()print(info.metrics) # final self-metrics snapshotif info.has_undelivered_spool: # data still on the disk spool, not yet seen by the backend print(f"{info.leftover_segments} spool segments ({info.leftover_bytes} bytes) left to replay")metrics— final self-metrics snapshot (see Tuning & observability).has_undelivered_spool—Truewhen the disk spool still holds data the recovery channel could not deliver within the close window; a future resume orreplay_spoolwill deliver it. AlwaysFalsewhen no disk spool is configured.leftover_segments/leftover_bytes— how much remains on the spool.
Sizing timeouts for strong guarantees
Section titled “Sizing timeouts for strong guarantees”During a backend outage, data is not spooled instantly — it first exhausts the gRPC retry budget (up to
~2 minutes at the defaults), then fails over to the disk spool. So a flush / close /
shutdown_logger timeout shorter than that window can elapse before the data ever reaches disk.
The two surface differently:
flushraisesMetranaTimeoutError— benign; the data is still in flight, just re-flush or close.close/shutdown_loggerraiseMetranaClosingErroronly if their deadline elapses with data still not durable (neither acknowledged nor written to the spool) — which means likely loss. If the data reached the spool before the deadline they do not raise: they return aCloseInfoand log that the data is safe on disk, awaiting delivery.
For strong guarantees, configure a disk spool and give these
calls a timeout at least as long as the failover window (≥ ~2–3 minutes). An outage at close-time then lands
the data on disk — recoverable on the next resume or via replay_spool — instead of cutting the failover
short.