kripod / uuidv7

UUIDv7 generator with millisecond precision
MIT License
107 stars 2 forks source link

Provide custom clock source for monotonicity #2

Closed CMCDragonkai closed 3 years ago

CMCDragonkai commented 3 years ago

I'm concerned that the usage of Date is not monotonic. I've tested this myself and Date can give back the same datetime when done very closely.

Furthermore if the OS clock drifts or is set backwards, I may want my application to be monotonic even then. So having the ability to provide a custom clock where I enforce monotonicity would be useful.

Can be helpful for testing and mocking if the clocksource can be dependency injected.

kripod commented 3 years ago

Thank you for raising this concern!

I think the monotonic seq counter helps avoiding collisions as shown – when a timestamp matches the previous one, the clock sequence is increased by 1, otherwise it gets reset to 0: https://github.com/kripod/uuidv7/blob/501d4ef5f97893e445c4160541971741db9f9257/src/_common.ts#L12-L18

OS clock drifts may possibly happen, but is there a sufficient alternative for Date.now() in JS to avoid those? We may use performance.timing.navigationStart + performance.now() or something similar:

unlike Date.now(), the values returned by performance.now() always increase at a constant rate, independent of the system clock (which might be adjusted manually or skewed by software like NTP)

CMCDragonkai commented 3 years ago

There are definitely solutions we could hack into the code itself. But I'm requesting for some flexibility. If as a user I can inject my own clock source, I can work out a solution in my specific environment. If I don't inject a clock source, I'm happy for the library to default it to whatever is convenient.(

So if Date.now() is being used, I suppose an object that has the now method should be sufficient. Then the default implementation is Date. A typescript interface can enforce this.

On 9/28/21 4:34 PM, Kristóf Poduszló wrote:

Thank you for raising this concern!

I think the monotonic |seq| counter helps avoiding collisions as shown – when a timestamp matches the previous one, the clock sequence is increased by 1, otherwise it’s reset to 0: https://github.com/kripod/uuidv7/blob/501d4ef5f97893e445c4160541971741db9f9257/src/_common.ts#L12-L18 https://github.com/kripod/uuidv7/blob/501d4ef5f97893e445c4160541971741db9f9257/src/_common.ts#L12-L18

OS clock drifts may possibly happen, but is there a sufficient alternative for |Date.now()| in JS to avoid those? We may use |performance.timing.navigationStart + performance.now()| https://developer.mozilla.org/en-US/docs/Web/API/Performance/now or something similar as an alternative:

unlike |Date.now()|, the values returned by |performance.now()|
always increase at a constant rate, independent of the system
clock (which might be adjusted manually or skewed by software like
NTP)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/kripod/uuidv7/issues/2#issuecomment-928897286, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAE4OHJYGLLCJCUUHO72UDTUEFOXXANCNFSM5E4S6NXQ.

kripod commented 3 years ago

v0.2.2 has just introduced a fix for system clock drifts: https://github.com/kripod/uuidv7/commit/2161e35a5dec7cc1a45e549126251291858ae383

Your custom clock use-case would be suited better by UUIDv8, which features highly customizable timestamps. I’m closing this for now, as this project aims to offer a lightweight implementation rather than providing low-level custom options. Thanks again for sharing your ideas 😊

CMCDragonkai commented 3 years ago

@kripod thanks for that implementation, however I don't think that solution works when the process context can restart.

My situation involves process restart, and so I intend to persist the clock state on disk so I can ensure that my ids are always monotonic even after process restart.

kripod commented 3 years ago

I see, thanks for sharing that insight. I’m not sure why persisting clock state to disk would be necessary, as time passes in a monotonic manner. There is almost no chance that a process could restart within a single millisecond in the foreseeable future. Even if that happens, it’d take ~17 years in order to have a 1% probability of at least one collision when generating 1000 IDs per hour within the same millisecond.

CMCDragonkai commented 3 years ago

I'm building a decentralized application that will run on user machines. Users who may reset their clock. I'd prefer to be foolproof and ensure that my uuid generation isn't broken inside the app even if the OS clocks have been reset. My application is persistent, so I can save the clock state or last generated ID and use that as a reference point.