Election Guide
Why election matters
In a distributed backend, multiple worker processes may all be capable of running the same logic, but some responsibilities should be owned only by one worker or by a small fixed number of workers.
Vona provides election for exactly that scenario.
This matters when backend code needs to:
- start a standalone service
- own a leader-like runtime responsibility
- recover ownership automatically when a worker exits
- allow limited parallel ownership instead of full fan-out execution
Core election model
The election model works like this:
- workers compete to obtain ownership of a named resource
- one worker, or a configured number of workers, acquires that ownership
- only the worker that obtains ownership starts the protected logic
- if an owning worker exits, other workers compete again and ownership can move automatically
That makes election a coordination primitive, not just a boolean lock.
Create meta.election
Example: create meta.election in module demo-student.
npm run vona :create:bean meta election -- --module=demo-studentRepresentative definition:
export type TypeElectionObtainResource = 'echo';
@Meta()
export class MetaElection extends BeanElectionBase<TypeElectionObtainResource> {}The resource type expresses which logical resources can be competed for inside the module.
Obtain ownership from a module monkey
Election usually participates in backend lifecycle hooks rather than ordinary request handlers.
Representative pattern:
export class Monkey extends BeanSimple implements IMonkeyAppStarted {
async appStarted() {
const scope = this.app.scope(__ThisModule__);
scope.election.obtain(
'echo',
() => {
// custom logic
},
async () => {
// cleanup
},
);
}
}A useful mental model is:
appStarted()is where the backend decides to compete for ownershipobtain(...)starts the owned logic only on the worker that wins- the cleanup callback releases or cleans up local resources when ownership ends
Tickets: allow more than one owner
Sometimes one owner is too restrictive, but full broadcast fan-out is too broad.
In that case, election supports tickets:
scope.election.obtain(
'echo',
() => {
// custom logic
},
async () => {
// cleanup
},
{ tickets: 2 },
);A practical interpretation is:
tickets: 1means one ownertickets: 2means two workers may own the same resource simultaneously
This makes election useful for limited parallel ownership patterns.
When to use election vs queue vs broadcast
Read this guide together with:
A practical boundary is:
- use election when only one worker, or a fixed small number of workers, should own the responsibility
- use queue when work should be pushed asynchronously to background execution
- use broadcast when many workers should all receive the message
This distinction is important because these abstractions solve different distributed coordination problems.
Relationship to startup lifecycle
Election often starts from backend startup hooks.
That means a common pattern is:
- backend startup reaches
appStarted - the module competes for an election resource
- the winning worker starts the protected service or loop
- ownership can fail over to another worker if the current owner exits
So startup answers when lifecycle hooks fire, while election answers which worker should own a singleton-like responsibility.
Implementation checks for distributed-election changes
When editing distributed backend coordination, ask:
- is this really a singleton-like ownership problem?
- should one worker own the responsibility, or should there be multiple tickets?
- does the ownership logic belong in backend startup hooks rather than a request path?
- would queue or broadcast be the wrong abstraction for this job?
That helps AI choose election only when the underlying coordination problem truly matches it.