It is easy enough, but require some degree of coordination.
The general scheme is relatively simple: partition the bit-space of the ID in two sections, one with a "generator ID" unique to each generator, and one with a strictly monotonically increasing sequence.
For example: the UUID v1 scheme uses MAC addresses as generator ID, and then timestamp + counter as strictly monotonically increasing sequence.
There are 2 challenges to solve:
- Ensuring unicityuniqueness of the generator ID.
- Ensuring strict monotonicity of the sequence.
The first challenge typically requires some form of coordination across generators. This can be a one-off coordination system -- assign a unique ID when setting up the generator -- or it can be a more active form of coordination -- such as registering with a central entity on start-up.
The second challenge requires either knowledge of the system, railguardsguardrails, or persistence:
- Persistence: each generated sequence number is persisted, on start-up a generator restarts from the last persisted number. In practice, persistence is likely best performed in batch: acquire a batch of N numbers (bumping the persisted item by N), then distribute them, rinse and repeat.
- Knowledge: if the operation for which IDs are provided is known not to exceed a generation rate of N/s, then the sequence number can be seeded on start-up by a timestamp of appropriate resolution, which is then simply incremented.
- RailguardsGuardrails: Same as Knowledge, except that a check is made that the maximum rate of ID generation is not exceeded.
As a practical example, I've regularly implemented such a scheme to generate unique 64-bits ID within a multithreaded application. In such a case, the partition key was a thread index (from 0 to N) and the sequence was seeded with a nanosecond resolution timestamp stored in a thread-local variable, and incremented on each use. The maximum rate of generation being 1/ns/thread, we knew it wouldn't be exceeded -- our application was plenty fast, but still.
For export, the ID was prepended with a 64-bits ID unique to the running process, obtained from a shared Zoo Keeper cluster. The running process would lock the use of this ID for the duration -- via a lease system -- ensuring no other process could possibly use it, and thus we had a 128-bits ID guaranteed unique across all applications.