Distributed Coordination

Database as source of truth

Mechanoid prefers a load-on-demand model over in-memory cluster gossip:

  • State lives in the EventStore

  • Application nodes stay stateless: any node can handle any instance

  • Fresh loads see the latest events; no pub/sub membership protocol

load / appendload / append

Optimistic sequence numbers always detect write conflicts. Distributed locking prevents them.

LockingStrategy

StrategyBehavior
LockingStrategy.optimistic[Id]Detect SequenceConflictError at append
LockingStrategy.distributed[Id]Acquire FSMInstanceLock before each transition
{
  enum OrderState derives Finite:
    case Pending, Paid, Shipped

  enum OrderEvent derives Finite:
    case Pay, Ship

  import OrderState.*, OrderEvent.*

  val machine = Machine(
    assembly[OrderState, OrderEvent](
      Pending via Pay to Paid,
      Paid via Ship to Shipped,
    )
  )

  val orderId: OrderId = "order-lock-1"

  ZIO
    .scoped {
      for
        fsm   <- FSMRuntime(orderId, machine, Pending)
        _     <- fsm.send(Pay)
        state <- fsm.currentState
      yield state
    }
    .provide(
      InMemoryEventStore.layer[OrderId, OrderState, OrderEvent],
      ZLayer.fromZIO(InMemoryFSMInstanceLock.make[OrderId]),
      TimeoutStrategy.fiber[OrderId],
      LockingStrategy.distributed[OrderId],
    )
    .asDoc
}
Paid

Lock heartbeat and atomic transitions

renewal fails

withLockAndHeartbeat renews the lock while long work runs (LockHeartbeatConfig: renewalInterval, renewalDuration, jitterFactor, onLockLost).

LockedFSMRuntime.withAtomicTransitions holds one lock across a multi-step sequence so validation → approval style flows stay exclusive. Contention still surfaces as SequenceConflictError under optimistic locking; distributed locking reduces that race by serializing writers per instance id.

Combine durable timeouts + distributed locking for production multi-node setups:

.provide(
  eventStoreLayer,
  timeoutStoreLayer,
  lockServiceLayer,
  TimeoutStrategy.durable[OrderId],
  LockingStrategy.distributed[OrderId],
)

Next: Visualization.