Is it possible, when K different hosts are generating IDs? Yes.
And we can do it with readily available libraries.
ULIDs do a pretty good job already,
but they're not collision free.
It looks like the format may soon be known as
UUIDv7.
We allocate 48 bits for a millisecond timestamp that won't
overflow for millennia, plus 80 bits of entropy, for a 128-bit GUID.
So by the
birthday paradox,
those K servers would (collectively) need to generate on the
order of 2 ** 40
random IDs within a single millisecond, to have a
decent chance of producing a collision.
Suppose K is "large", and servers are fast, and
your appetite for risk says the odds aren't looking good enough to you.
Fair enough.
We could allocate 208 bits of entropy, but the collision risk is
still non-zero. There must be another way.
Use a Distributed Consensus protocol, perhaps based on
Raft,
to enroll all servers in your scheme and assign each
a distinct host_id
in the range 1 .. K.
I don't know how many servers you have.
So let us allocate 32 bits to store such IDs.
That's enough to accommodate all publicly accessible IPv4 destinations
in the global internet.
The GUID prefix will be {timestamp, host_id}.
Now we have a mere 48 bits of entropy to work with.
So if any single host rolled on the order of 2 ** 24
random IDs within a millisecond, it would have
a fair chance of self colliding.
That's a mere 16 million IDs,
achievable if we spend about a tenth of a nanosecond
generating each ID.
Perhaps plausible for a core router that puts IDs
on each forwarded packet.
But wait!
By hypothesis these IDs are all being
generated locally on a single host.
So we can just use a counter for uniqueness.
Or several counters, one per functional sub-unit,
if for example our router has several line cards
or our GPU has several cores.
If all sub-units consume IDs at roughly the same rate,
then we can essentially stretch the 32-bit host_id
into something longer which encompasses the sub-units.
If that's still not enough, we just request additional host_ids.
For sub-units that process IDs at diverse rates,
it's easy to make some central thread hand out big counter blocks
of a thousand or a million.
Oracle database servers have been allocating
PK IDs
in this way for decades.
So there you have it.
Your hosts, and their sub-units, can only generate
so many IDs per second, and 2 ** 80
is a large number.
Allocate prefix bits to hosts and optionally to sub-units,
and then you just need counter(s) for the remaining bits.
This makes the (reasonably weak) assumptions that a host
can learn approximately what the current time is,
that it can persist such timestamps so upon reboot it
only lets the clock march monotonically forward,
and that it can keep track of what happened in the
last millisecond or so, to coordinate ID uniqueness
across its sub-units.
If you require even greater rates of ID generation,
or if some assumption can't feasibly be engineered
into your use case, then just allocate more than 128 bits total
for each distinct ID.