Machine ID
A machine id is the worker-id field of a Snowflake ID (and of a CosIdGenerator ID). Two instances that generate IDs with the same machine id at the same time can generate the same IDs, so each running instance must hold a distinct one.
Despite the name, a machine id has nothing to do with physical machines. It is held by an instance, one running process, identified by an InstanceId: cosid.machine.instance-id when set; otherwise the host address and the process id (192.168.1.10:12345) for a stable instance, and the host address, the process id and a random suffix chosen at startup (192.168.1.10:12345:3f9a1c2e) for a non-stable one. Several processes on one host each hold their own machine id. CosId keeps the name "machine id" for compatibility with its configuration and APIs.
Containers
Containers that share a host address can all run as process 1. Since 4.0.2 a non-stable instance adds a random suffix to its default id, so two such containers never share a machine id. The flip side: a non-stable instance that is killed and restarted takes a fresh machine id each time, and the old one is only freed once its safe guard duration has passed, so a crash loop can use up machine ids. To keep one machine id across restarts, set a stable instance with an explicit id, such as cosid.machine.stable: true with cosid.machine.instance-id: ${HOSTNAME}. Startup logs a warning when the default id is used in a container.
Machine ids are scoped by a namespace (cosid.namespace, usually the application name): instances of different applications can hold the same machine id, because their IDs never meet.
Getting a machine id
cosid.machine.distributor.type chooses how an instance gets its machine id:
| Type | How | Recycling | Use it when |
|---|---|---|---|
redis, jdbc, mongo, zookeeper, proxy | Leased from a store (LeaseStoreMachineIdDistributor on the backend's MachineLeaseStore) | Yes, after the lease expires | Almost always: pick the store you already run, see Choosing a backend |
stateful_set | The ordinal of a Kubernetes StatefulSet pod, parsed from HOSTNAME (order-service-3 gives 3) | Not needed | Pods of a StatefulSet; the ordinal must fit in the machine id bits |
manual (default) | cosid.machine.distributor.manual.machine-id | No | A single instance, or tests. Nothing stops two instances from using the same value |
| any other value | The CosIdStoreProvider bean whose getType() matches | Depends on the provider | A store CosId does not ship |
The lease
With a lease store, a machine id is a lease that the instance must keep renewing.
- Claim. At startup the instance takes, in order: the lease it already holds (after a restart with the same instance id), a machine id another instance released, a never-used one, and only then one whose lease has not been renewed for
cosid.machine.guarder.safe-guard-duration(5 minutes). If none is left, startup fails withMachineIdOverflowException. Claims are atomic: when instances race, each machine id goes to one of them. - Renew. The guarder renews the lease every
cosid.machine.guarder.delay(1 minute), starting afterinitial-delay(1 minute). A failed renewal is logged, and themachineIdhealth indicator reportsDOWNuntil the next one succeeds. - Fail fast. With
cosid.machine.guarder.fail-fast: true(the default), a generator throwsMachineIdLostExceptioninstead of generating once its lease is lost (another instance owns the machine id now) or has not been renewed for the safe guard duration, minus a margin for clock skew ofmin(10s, safe-guard-duration / 10). A failed request is better than a duplicate ID. - Release. On shutdown, after the web server has stopped taking requests, the instance stops its generators and releases the machine id, so the next instance can take it at once. A stable instance keeps its machine id: release only raises the watermark.
A lease that is never renewed, because the process was killed, becomes available to other instances after the safe guard duration.
Keep the rules the same across a namespace
All instances of a namespace must use the same safe-guard-duration. An instance with a shorter one can take over a lease its holder still considers valid, and both then generate the same IDs. Keep host clocks in sync (NTP) as well: lease times come from the store's clock where it has one (Redis, databases, MongoDB, and for the proxy, the clock of the proxy's store) and from the host clock otherwise (ZooKeeper).
Stable instances
cosid.machine.stable: true marks an instance whose identity survives restarts, such as a fixed instance-id on a fixed host. Other instances never take over its machine id, even after it has been offline longer than the safe guard duration, and it never takes over another instance's expired lease. A stable instance also keeps its machine state in a local file (cosid.machine.state-storage.local.state-location, by default ~/.cosid-machine-state). Mark only instances that will come back: a stable machine id that is never released stays taken.
Clock backwards after a restart
A generator detects a clock that moved backwards while it runs (see SnowflakeId). After a restart, the last timestamp is no longer in memory, so CosId keeps it with the machine id:
- Every lease carries a watermark, the last timestamp issued under that machine id, raised on every claim and on release. A new owner whose clock is behind the watermark waits until its clock passes it, with the
cosid.machine.clock-backwardsthresholds: when it is more thanbroken-threshold(500 ms) behind, startup fails withClockTooManyBackwardsExceptionand the machine id is released again. - A stable instance also writes its last timestamp to the local state file every
cosid.machine.guarder.local-state-interval(1 second). After a crash, a clock jump shorter than about this interval may go undetected.
This is why host clocks must be in sync: a host whose clock runs ahead of the others leaves a watermark the next owner of its machine id has to wait for.
Machine id bits
cosid.machine.machine-bit (10) sets how many machine ids a namespace has, and must match the generator's layout. Each generator can override it with its own machine-bit; give such a generator its own namespace too, since an instance holds one machine id per namespace.
Configuration
cosid:
namespace: ${spring.application.name}
machine:
enabled: true
distributor:
type: redis
guarder:
safe-guard-duration: 5mSee the machine ID configuration for every property.
