<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Operating Dwarf on Dwarf</title><link>https://docs.dwarf.run/operating/</link><description>Recent content in Operating Dwarf on Dwarf</description><generator>Hugo</generator><language>en-US</language><atom:link href="https://docs.dwarf.run/operating/index.xml" rel="self" type="application/rss+xml"/><item><title>Production Checklist</title><link>https://docs.dwarf.run/operating/production-checklist/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/production-checklist/</guid><description>&lt;h1 id="production-checklist"&gt;Production Checklist&lt;a class="anchor" href="#production-checklist" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators&lt;/strong&gt; about to run Dwarf for real. Every item links to the guide that explains
it. Most defaults are safe; what follows is the set that is &lt;strong&gt;not&lt;/strong&gt; defaulted, or where the default is
deliberately conservative and wrong for production.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="database"&gt;Database&lt;a class="anchor" href="#database" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;PostgreSQL&lt;/strong&gt;, unless you have a reason. It needs no isolation-level tuning and runs fan-out
deadlock-free at any concurrency. → &lt;a href="https://docs.dwarf.run/operating/deployment/#choosing-a-database"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;SQLite only for one replica at a modest flow rate&lt;/strong&gt;, on a local-disk file with the recommended DSN
(&lt;code&gt;journal_mode(WAL)&lt;/code&gt;, &lt;code&gt;synchronous(NORMAL)&lt;/code&gt;, &lt;code&gt;auto_vacuum(FULL)&lt;/code&gt;) if flows must survive a restart, a retention that
fits the disk, and live backups with &lt;code&gt;VACUUM INTO&lt;/code&gt;; in memory they last only as long as the process.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#sqlite--one-replica-low-throughput"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;MySQL/MariaDB only:&lt;/strong&gt; &lt;code&gt;transaction-isolation = READ-COMMITTED&lt;/code&gt;, &lt;code&gt;innodb_autoinc_lock_mode = 2&lt;/code&gt; with
&lt;code&gt;binlog_format = ROW&lt;/code&gt;. Without the first, concurrent flow creation deadlocks on gap locks.
→ &lt;a href="https://docs.dwarf.run/operating/runbook/#deadlock-errors-on-mysql-or-sql-server"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;MySQL/MariaDB only:&lt;/strong&gt; &lt;code&gt;interpolateParams=true&lt;/code&gt; in the DSN. Without it every statement with arguments
costs two round trips, roughly doubling each step&amp;rsquo;s. → &lt;a href="https://docs.dwarf.run/operating/deployment/#mysql--mariadb--supported-expect-tuning"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;SQL Server only:&lt;/strong&gt; &lt;code&gt;READ_COMMITTED_SNAPSHOT ON&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every replica connects to a shard as one database user&lt;/strong&gt;, allowed to create and drop tables in the
engine&amp;rsquo;s schema. The engine keeps the archive in a table per day, creating and dropping them as it runs.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#sharding"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Disk IOPS sized to the write rate&lt;/strong&gt;, not just to capacity. Cloud providers often provision IOPS by
disk size, and a throttled disk shows up as throughput collapse with everything apparently idle.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#disk-throughput"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;PostgreSQL 16 and older: &lt;code&gt;autovacuum_work_mem&lt;/code&gt; set to 1 GB.&lt;/strong&gt; At a 64 MB default, vacuum on the step table
rescans every index each time its memory fills, and falls behind. → &lt;a href="https://docs.dwarf.run/operating/deployment/#postgresql--recommended-for-production"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;PostgreSQL: checkpoints sized for the write load.&lt;/strong&gt; &lt;code&gt;max_wal_size&lt;/code&gt; about 2 × peak WAL MB/s × 60 s,
&lt;code&gt;checkpoint_timeout&lt;/code&gt; 15 minutes. At the 1.5 GB default a busy shard checkpoints every few
seconds and ran 30% slower with 8 KB payloads. → &lt;a href="https://docs.dwarf.run/operating/deployment/#postgresql--recommended-for-production"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;ANALYZE&lt;/code&gt;/autovacuum tuned for &lt;code&gt;dwarf_steps&lt;/code&gt;.&lt;/strong&gt; It is a high-churn queue table whose row counts swing
by orders of magnitude, and stale statistics can flip the selection query from an index scan to a
sequential one. → &lt;a href="https://docs.dwarf.run/operating/runbook/#backlog-is-growing"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;No statement, transaction or idle-in-transaction timeout&lt;/strong&gt; on the engine&amp;rsquo;s database, its role or its
connection string. That means PostgreSQL&amp;rsquo;s &lt;code&gt;statement_timeout&lt;/code&gt;, &lt;code&gt;transaction_timeout&lt;/code&gt; and
&lt;code&gt;idle_in_transaction_session_timeout&lt;/code&gt;, and MySQL&amp;rsquo;s &lt;code&gt;max_execution_time&lt;/code&gt;. General database guidance often
recommends them; leave them unset here.
&lt;ul&gt;
&lt;li&gt;Some of the engine&amp;rsquo;s background queries grow with the backlog, so no value is safe for every backlog. A
timeout that cuts them off can stop a database&amp;rsquo;s work from being dispatched at all.&lt;/li&gt;
&lt;li&gt;An idle-in-transaction timeout ends transactions while the engine is busiest, and the work is retried.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Long-running queries stay off the shard databases:&lt;/strong&gt; analytics, reporting, ad hoc sessions. While a query
runs for minutes, PostgreSQL cannot clean up the engine&amp;rsquo;s queue table behind it, and dispatch slows until it
ends. A read replica with &lt;code&gt;hot_standby_feedback&lt;/code&gt; on holds cleanup back the same way, and so does a lagging
replication slot. → &lt;a href="https://docs.dwarf.run/operating/runbook/#backlog-is-growing"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="engine-configuration"&gt;Engine Configuration&lt;a class="anchor" href="#engine-configuration" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Connection budgets suit each database.&lt;/strong&gt; The default (250, rising to 500 for a distant database,
never more than half its &lt;code&gt;max_connections&lt;/code&gt;) suits databases of 4-32 vCPU. Set &lt;code&gt;MaxConnections&lt;/code&gt; for a larger
one or one shared with other heavy users, and set &lt;code&gt;Weight&lt;/code&gt; on shards of unequal size so new flows follow
capacity. → &lt;a href="https://docs.dwarf.run/operating/deployment/#connection-pool"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The shard index-to-DSN map is identical on every replica&lt;/strong&gt; and stable across restarts. A flow key
encodes its shard, so a flow created on a shard a peer does not know about is unroutable there.
→ &lt;a href="https://docs.dwarf.run/operating/resharding/"&gt;Resharding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;SetEngineID&lt;/code&gt; pinned&lt;/strong&gt; if your platform restarts replicas in place. Otherwise a crash-looping replica
leaves a stale registry row per restart, and one that ranked as a shard&amp;rsquo;s &lt;a href="https://docs.dwarf.run/operating/deployment/#dispatchers"&gt;dispatcher&lt;/a&gt; holds that slot until it
ages out, leaving the shard fewer replicas running it. Must be unique among
concurrently-live replicas. → &lt;a href="https://docs.dwarf.run/operating/upgrading/#deploying-your-own-application"&gt;Upgrading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;GOMEMLIMIT&lt;/code&gt; set on engine hosts&lt;/strong&gt;, a little below the memory the process may use. The engine keeps a
tenth of it for large state fields, and without it Go&amp;rsquo;s garbage collector can let the heap reach about twice
its live size. → &lt;a href="https://docs.dwarf.run/operating/deployment/"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Logger, meter provider and tracer provider injected.&lt;/strong&gt; All three default to no-ops, so an engine with
nothing injected is silent — including its error logs. → &lt;a href="https://docs.dwarf.run/operating/observability/"&gt;Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Two engine replicas per shard, each at half the database&amp;rsquo;s vCPU&lt;/strong&gt; (two 8-vCPU replicas per 32-vCPU
database), so either can carry the shard alone. → &lt;a href="https://docs.dwarf.run/operating/deployment/#sizing-a-deployment"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="lifecycle"&gt;Lifecycle&lt;a class="anchor" href="#lifecycle" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;Shutdown&lt;/code&gt; is called&lt;/strong&gt; on termination, and the platform&amp;rsquo;s &lt;strong&gt;termination grace period exceeds your
largest &lt;a href="https://docs.dwarf.run/building-workflows/scheduling/#time-budgets"&gt;&lt;code&gt;TimeBudget&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;. Graceful shutdown waits for in-flight tasks; if the platform kills the process
first, those steps are abandoned and &lt;strong&gt;their tasks re-run elsewhere&lt;/strong&gt;.
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#shutting-down"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Deploys replace replicas one at a time&lt;/strong&gt;, letting the fleet settle between steps, so pools re-divide
cleanly rather than transiently over-connecting. → &lt;a href="https://docs.dwarf.run/operating/upgrading/"&gt;Upgrading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Dwarf version bumps are planned as a maintenance window&lt;/strong&gt;, not a rolling deploy — v0.x makes no
compatibility promise. → &lt;a href="https://docs.dwarf.run/operating/upgrading/#dwarf-is-v0x-and-that-is-the-headline"&gt;Upgrading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="observability"&gt;Observability&lt;a class="anchor" href="#observability" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;These five page someone.&lt;/strong&gt; Each is documented as zero in a healthy engine:
&lt;code&gt;dwarf_steps_unwedged_total&lt;/code&gt;, &lt;code&gt;dwarf_flows_orphaned_total&lt;/code&gt;, &lt;code&gt;dwarf_steps_write_failed_total&lt;/code&gt;,
&lt;code&gt;dwarf_flows_reaped_total{reason=&amp;quot;archive_failed&amp;quot;}&lt;/code&gt; and &lt;code&gt;dwarf_flows_expire_failed_total&lt;/code&gt;.
→ &lt;a href="https://docs.dwarf.run/operating/runbook/"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;dwarf_steps_dispatch_lag_seconds&lt;/code&gt; is dashboarded&lt;/strong&gt; — it is the honest backlog signal, better
than a queue count.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Per-shard gauges aggregate with &lt;code&gt;max&lt;/code&gt; per shard, not &lt;code&gt;sum&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;dwarf_steps_dispatch_lag_seconds&lt;/code&gt;,
&lt;code&gt;dwarf_steps_pending&lt;/code&gt; and &lt;code&gt;dwarf_steps_oldest_pending_age_seconds&lt;/code&gt; describe a shard, and more than one
replica can report the same one; summing across replicas overstates the backlog and makes the age readings
meaningless. → &lt;a href="https://docs.dwarf.run/operating/observability/"&gt;Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Someone knows where the runbook is&lt;/strong&gt; before the first alarm, not after.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="data-and-retention"&gt;Data and Retention&lt;a class="anchor" href="#data-and-retention" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The retention is set to what you must keep.&lt;/strong&gt; Closed flows are archived and kept for &lt;code&gt;SetRetention&lt;/code&gt; days
(30 by default), then dropped a day at a time; open flows are never removed by the engine, unless they expire
(below).
→ &lt;a href="https://docs.dwarf.run/operating/data-handling/#retention-and-deletion"&gt;Data Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Abandoned flows are bounded, if they can occur.&lt;/strong&gt; A flow left interrupted, or polling forever, stays in the
live tables until it closes. &lt;code&gt;SetFlowExpiration&lt;/code&gt; terminates any flow older than it.
→ &lt;a href="https://docs.dwarf.run/building-workflows/flows/#expiring-flows-that-never-end"&gt;Expiring Flows That Never End&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The archive&amp;rsquo;s disk is sized for the retention&lt;/strong&gt;: a day&amp;rsquo;s closed flows times the retention, per shard.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Backups cover every shard on the same schedule&lt;/strong&gt;, and a restore has been rehearsed against a staging
fleet — the part worth testing is what your workflows do when they re-run.
→ &lt;a href="https://docs.dwarf.run/operating/backup-and-restore/"&gt;Backup and Restore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;ListOpen&lt;/code&gt; and &lt;code&gt;ListClosed&lt;/code&gt; (and their &lt;code&gt;Query.Search&lt;/code&gt;) are gated by credential&lt;/strong&gt; in your host. The engine has
no notion of a caller, and the lists are the operations that return flow keys wholesale — which are capabilities.
→ &lt;a href="https://docs.dwarf.run/operating/data-handling/#access-control-is-the-hosts-job"&gt;Data Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Quotas enforced before &lt;code&gt;Create&lt;/code&gt;.&lt;/strong&gt; Beyond refusing any single value over 256 MB, the engine bounds
nothing: not initial state size, not baggage, not fan-out width, not subgraph depth.
→ &lt;a href="https://docs.dwarf.run/operating/data-handling/#what-the-engine-does-not-bound"&gt;Data Handling&lt;/a&gt;,
and a worked front door that enforces a quota in &lt;a href="https://docs.dwarf.run/building-workflows/building-a-host/#starting-a-flow-quota-claims-and-tenant"&gt;Building a Host&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;A flow&amp;rsquo;s final state fits in 256 MB.&lt;/strong&gt; State can grow past it while the flow runs, but the final
state holds every field at once. → &lt;a href="https://docs.dwarf.run/operating/data-handling/#the-size-limit-256-mb-per-value"&gt;Data Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;MySQL/MariaDB: &lt;code&gt;max_allowed_packet&lt;/code&gt; is at least 512M.&lt;/strong&gt;
→ &lt;a href="https://docs.dwarf.run/operating/deployment/#mysql--mariadb--supported-expect-tuning"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="workflows-and-tasks"&gt;Workflows and Tasks&lt;a class="anchor" href="#workflows-and-tasks" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Tasks are idempotent&lt;/strong&gt;, keyed on &lt;code&gt;f.StepKey()&lt;/code&gt; where the downstream supports it. Execution is
at-least-once and two attempts can overlap. → &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#idempotency"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Tasks respect their context deadline.&lt;/strong&gt; A task that overruns its &lt;code&gt;TimeBudget&lt;/code&gt; loses its &lt;a href="https://docs.dwarf.run/how-dwarf-works/#crash-recovery"&gt;lease&lt;/a&gt; to a
peer and runs twice concurrently.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;No PII in error text or terminate/cancel reasons.&lt;/strong&gt; Error text is returned by both the lists and
&lt;code&gt;History&lt;/code&gt; — the readers that otherwise expose no state payloads — and the terminate or cancel reason by
the lists. Both are substring-searchable. → &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#keep-payload-data-out-of-error-text"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Large integers read with typed accessors, or carried as strings.&lt;/strong&gt; Beyond ±2^53 storage is exact and
&lt;code&gt;GetInt&lt;/code&gt; and &lt;code&gt;when&lt;/code&gt; expressions are exact, but an untyped read rounds. And &lt;strong&gt;binary data base64-encoded&lt;/strong&gt; —
a &lt;code&gt;NUL&lt;/code&gt; byte is rejected by PostgreSQL and accepted by everything else.
→ &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#large-integers-exact-in-storage-rounded-by-untyped-reads"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every field whose writes should combine has an explicit reducer.&lt;/strong&gt; There is no inference from a field&amp;rsquo;s name —
an unwired &lt;code&gt;sumTotal&lt;/code&gt; silently takes &lt;code&gt;replace&lt;/code&gt;, not &lt;code&gt;add&lt;/code&gt;. → &lt;a href="https://docs.dwarf.run/building-workflows/graphs/"&gt;Building Graphs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Flow deadlines your workflows rely on are in author space.&lt;/strong&gt; An &lt;code&gt;interrupted&lt;/code&gt; flow waits indefinitely unless
&lt;code&gt;SetFlowExpiration&lt;/code&gt; is set, and that terminates without running any of your code. → &lt;a href="https://docs.dwarf.run/building-workflows/tasks/#timestamps"&gt;Writing Tasks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="first-week"&gt;First Week&lt;a class="anchor" href="#first-week" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Watch &lt;code&gt;dwarf_peer_replicas&lt;/code&gt; match your actual replica count.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Watch &lt;code&gt;dwarf_steps_recovered_total&lt;/code&gt; — a steady trickle usually means tasks are overrunning their
budget, or that your termination grace period is cutting drains short. Neither is broken, but both are
worth knowing. → &lt;a href="https://docs.dwarf.run/operating/runbook/#tasks-are-running-more-than-once"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Confirm the live tables stay level under load, by watching table size rather than disk usage —
PostgreSQL holds its high-water mark. → &lt;a href="https://docs.dwarf.run/operating/runbook/#disk-did-not-shrink-after-a-purge"&gt;Runbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Re-read the sizing rules against what you actually observe. The published numbers assume cheap
in-process tasks; a host dispatching over a network transport pays more engine CPU per step.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next: &lt;a href="https://docs.dwarf.run/operating/deployment/"&gt;Deployment&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Deployment</title><link>https://docs.dwarf.run/operating/deployment/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/deployment/</guid><description>&lt;h1 id="deployment"&gt;Deployment&lt;a class="anchor" href="#deployment" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; standing Dwarf up in production, and for developers doing the wiring. It covers choosing
and tuning a database, declaring shards, connection pools, workers, and running multiple replicas. Once it
is running, &lt;a href="https://docs.dwarf.run/operating/"&gt;Operating Dwarf&lt;/a&gt; takes over.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="configuration"&gt;Configuration&lt;a class="anchor" href="#configuration" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All configuration is set with &lt;code&gt;Set*&lt;/code&gt; methods, each returning an &lt;code&gt;error&lt;/code&gt;. They split by whether the knob can
change on a running engine: the &lt;strong&gt;live&lt;/strong&gt; one (&lt;code&gt;SetTimeBudget&lt;/code&gt;) takes effect immediately, even after &lt;code&gt;Startup&lt;/code&gt;; the
&lt;strong&gt;construction-time-only&lt;/strong&gt; ones
(&lt;code&gt;SetShard&lt;/code&gt;, &lt;code&gt;SetWorkers&lt;/code&gt;, &lt;code&gt;SetStateCacheSize&lt;/code&gt;, &lt;code&gt;SetMaxPriority&lt;/code&gt;, &lt;code&gt;SetRetention&lt;/code&gt;, &lt;code&gt;SetFlowExpiration&lt;/code&gt;, and the dependency-injection setters below) are
rejected if called after &lt;code&gt;Startup&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Observability</title><link>https://docs.dwarf.run/operating/observability/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/observability/</guid><description>&lt;h1 id="observability"&gt;Observability&lt;a class="anchor" href="#observability" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators.&lt;/strong&gt; Developers inject the three providers; operators read what comes out.
This is the full instrument catalogue and how to interpret each one. For which of them to alert on, see
&lt;a href="https://docs.dwarf.run/operating/"&gt;Operating Dwarf&lt;/a&gt;; for what to do when one fires, the &lt;a href="https://docs.dwarf.run/operating/runbook/"&gt;Runbook&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Dwarf emits structured logs, OpenTelemetry metrics, and distributed traces. All three go through standard
providers you inject — the engine owns no exporter and adds no network connections. With nothing injected,
everything degrades to a no-op, so unconfigured and test use pays nothing.&lt;/p&gt;</description></item><item><title>Upgrading</title><link>https://docs.dwarf.run/operating/upgrading/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/upgrading/</guid><description>&lt;h1 id="upgrading"&gt;Upgrading&lt;a class="anchor" href="#upgrading" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; deploying an application that embeds Dwarf. It covers two different operations that get
confused with each other — redeploying your own code, and moving to a new version of Dwarf — plus the
compatibility obligation that outlives both: a long-running flow dispatches tasks from a graph frozen
weeks ago.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="dwarf-is-v0x-and-that-is-the-headline"&gt;Dwarf Is v0.x, and That Is the Headline&lt;a class="anchor" href="#dwarf-is-v0x-and-that-is-the-headline" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;There is no backward-compatibility promise yet.&lt;/strong&gt; The API and the database schema are both still evolving,
and a v0.x release may change either — including by editing a migration an earlier release already shipped,
which a database created by that earlier release does not pick up. Version skew between replicas is not
tested and not supported.&lt;/p&gt;</description></item><item><title>Resharding</title><link>https://docs.dwarf.run/operating/resharding/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/resharding/</guid><description>&lt;h1 id="resharding"&gt;Resharding&lt;a class="anchor" href="#resharding" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; changing the set of database shards behind a running fleet. Read
&lt;a href="https://docs.dwarf.run/operating/deployment/#sharding"&gt;Deployment → Sharding&lt;/a&gt; first for what shards are and how many to run; this page is
about changing the set after flows already exist.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="the-constraint"&gt;The Constraint&lt;a class="anchor" href="#the-constraint" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A flow&amp;rsquo;s shard is part of its identity and never changes.&lt;/strong&gt; Every flow key is &lt;code&gt;{shard}-{hour}-{id}-{token}&lt;/code&gt;, so
the shard index is encoded in the handle your callers hold, and it is what routes every subsequent operation.
Everything derived from a flow stays on its shard for life: subgraph children, continuations, and
forks are all shard-pinned.&lt;/p&gt;</description></item><item><title>Backup and Restore</title><link>https://docs.dwarf.run/operating/backup-and-restore/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/backup-and-restore/</guid><description>&lt;h1 id="backup-and-restore"&gt;Backup and Restore&lt;a class="anchor" href="#backup-and-restore" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; planning backups, and for whoever is holding the pager when a restore actually happens.
A restore is a workflow-level event, not just a database one — flows re-run, completed work rewinds, and
side effects fire again. Know that before you need to.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="what-to-back-up"&gt;What to Back Up&lt;a class="anchor" href="#what-to-back-up" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The shard databases, and nothing else.&lt;/strong&gt; Dwarf keeps no state outside SQL: no local disk, no broker, no
external service, no in-memory state that matters across a restart. A replica is disposable — everything
durable is in the tables. So your backup strategy is entirely your database&amp;rsquo;s, and the engine adds no step
to it.&lt;/p&gt;</description></item><item><title>Data Handling &amp; Security</title><link>https://docs.dwarf.run/operating/data-handling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/data-handling/</guid><description>&lt;h1 id="data-handling--security"&gt;Data Handling &amp;amp; Security&lt;a class="anchor" href="#data-handling--security" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For developers and operators&lt;/strong&gt; who have to answer for what the engine stores: a compliance review, a
deletion request, a decision about who may hold a flow key. It covers what lands in the database, which of
it is reachable by free-text search, what telemetry carries, and how deletion actually works.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="what-the-engine-stores"&gt;What the Engine Stores&lt;a class="anchor" href="#what-the-engine-stores" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Everything durable is in the SQL databases you provide. There is no other store, no local disk state, and no
external service. Two tables carry data you supply.&lt;/p&gt;</description></item><item><title>Inspecting and Repairing a Flow</title><link>https://docs.dwarf.run/operating/inspecting-and-repairing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/inspecting-and-repairing/</guid><description>&lt;h1 id="inspecting-and-repairing-a-flow"&gt;Inspecting and Repairing a Flow&lt;a class="anchor" href="#inspecting-and-repairing-a-flow" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators and developers&lt;/strong&gt; looking into a flow that failed, stalled or did something unexpected,
and fixing it. The &lt;a href="https://docs.dwarf.run/operating/runbook/"&gt;Runbook&lt;/a&gt; tells you &lt;em&gt;when&lt;/em&gt; to do this; this page shows &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The example is a payout flow with two tasks, &lt;code&gt;Validate&lt;/code&gt; and &lt;code&gt;Pay&lt;/code&gt;, where &lt;code&gt;Pay&lt;/code&gt; rejects amounts over a limit.
A flow was created with an amount of 5,000, and it failed.&lt;/p&gt;
&lt;h2 id="1-find-the-flow"&gt;1. Find the Flow&lt;a class="anchor" href="#1-find-the-flow" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ListClosed&lt;/code&gt; finds closed flows by status, workflow, task, tenant and free text, within a window on when each
was archived; &lt;code&gt;ListOpen&lt;/code&gt; does the same for flows still running or interrupted:&lt;/p&gt;</description></item><item><title>Runbook</title><link>https://docs.dwarf.run/operating/runbook/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://docs.dwarf.run/operating/runbook/</guid><description>&lt;h1 id="runbook"&gt;Runbook&lt;a class="anchor" href="#runbook" aria-label="Anchor" aria-hidden="true"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;&lt;strong&gt;For operators&lt;/strong&gt; responding to a live incident. Each entry is a symptom you can observe from a dashboard
or an alert, followed by how to confirm it and what to do. For the orientation — what the engine repairs on
its own, and on what schedule — see &lt;a href="https://docs.dwarf.run/operating/"&gt;Operating Dwarf&lt;/a&gt;. Timings quoted here are the engine&amp;rsquo;s
defaults.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Before anything else: check whether it is already being handled.&lt;/strong&gt; Most faults are absorbed. A database
outage shorter than a step&amp;rsquo;s &lt;a href="https://docs.dwarf.run/how-dwarf-works/#crash-recovery"&gt;lease&lt;/a&gt; produces latency and no errors at all; a dead replica&amp;rsquo;s work is
re-dispatched within the flow&amp;rsquo;s time budget plus about a minute and a half, including a step it had finished
but not yet advanced. If the symptom is &amp;ldquo;things are slow&amp;rdquo; rather than &amp;ldquo;a counter
fired,&amp;rdquo; start at &lt;a href="#backlog-is-growing"&gt;Backlog is growing&lt;/a&gt; and resist restarting
replicas — a restart discards in-flight work that was about to complete and adds a phantom replica to the
&lt;a href="https://docs.dwarf.run/operating/deployment/#running-multiple-replicas"&gt;registry&lt;/a&gt;.&lt;/p&gt;</description></item></channel></rss>