VI Platform Services

Shared Decisions

A component usually runs as several replicas. They are identical and independent: each serves calls, none knows about the others, and state is shared only through storage. This is what makes scaling a matter of adding processes.

Some decisions cannot be made independently. If each replica cancels expired orders every hour, the work is done several times. If each replica enforces a rate limit of ten per minute, the component as a whole allows thirty. The replicas have to decide together.

The platform provides a small set of such shared decisions. The runtime uses them for its own machinery, and components can use them through the context.

§Partitioning

Partitioning divides a fixed number of slots among the replicas currently running, so that each slot belongs to exactly one replica at a time.

Work assigned by slot is done once, however many replicas there are. When a replica starts or stops, slots are redistributed among those present.

This is how the runtime decides which replica makes a pulse call and which replica recovers events that failed to publish. A component can use it the same way, for instance to split a queue of external work.

A replica may briefly hold no slots: at startup, during a rollout, or when the arbiter is unreachable. Work based on partitioning pauses in that replica and resumes when slots are assigned. It does not proceed on a guess.

§Locking

A lock lets one replica at a time run a piece of work:

await context.atom.lock('settlement', async () => {
  await settle()
})

While one replica holds the lock, others asking for the same key wait. The lock is held for as long as the work runs and is released when it ends, including when the replica fails.

A lock is the right tool for work that reaches outside the application and must not overlap with itself: a batch submitted to a bank, a file regenerated in place.

§Metering

Metering counts usage against a key across all replicas, so a limit applies to the component as a whole. It answers how much has been used recently by this customer, or this address, by anyone in the group.

The gateway uses it to throttle requests and to limit failed authentication attempts, consistently across its replicas.

§What does not need them

These tools are for coordination around state. Entity state itself does not depend on them.

A transition is protected by the entity’s version, which the storage checks at commit. Two replicas changing the same order are already safe without a lock, and adding one would only make them slower. A repeated call is already absorbed by once.

A useful rule: if the thing being protected is an entity, the model already protects it. Shared decisions are for what lies outside the model, and for work that should happen once per component.

§Their limits

Shared decisions are arbitrated by a service the replicas all reach, and they are as dependable as that arrangement.

They are designed to fail toward doing less. A replica that cannot reach the arbiter holds no slots and makes no pulse calls. It does not act alone. A short interruption costs a delay.

Under unusual conditions, such as clocks disagreeing at the moment work changes hands, a piece of work can be done twice. This is the same at-least-once property found throughout the runtime, and work coordinated this way is written with the same care as an operation that events can reach.

Search
Search the documentation