Skip to content

Update to match changes in Postgres implementation #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 27, 2025

Conversation

ryanwinchester
Copy link
Owner

@ryanwinchester ryanwinchester commented Feb 26, 2025

Postgres has changed their implementation from CommitFest to the code that is actually committed to their repo.

This library is intended to always match Postgres's implementation.

See: https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/uuid.c#L587

@ryanwinchester ryanwinchester marked this pull request as ready for review February 27, 2025 01:29
Copy link

@hauleth hauleth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it would be better to simply use 1 as a @minimal_step_ns.

lib/clock.ex Outdated
{:unix, :darwin} -> 10
{:win32, _} -> 10
{_, _} -> 12
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be problematic when cross-compiling.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

people successfully cross-compile?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess I could check at runtime :(

lib/clock.ex Outdated

{current_ts, <<clock::big-unsigned-18>>}
end
@minimal_step_ns div(ns_per_ms, Bitwise.bsl(1, step_bits)) + 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just set it to always be 1? I think it would be cleaner and still technically correct.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to match the Postgres implementation as close as possible, but maybe I will have to compromise here

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, but I do not see a point. If you do not have nanosecond precision there, then adding 1 is IMHO more secure than jumping somewhere wrt. precision of the OS. It is also secure to the same degree IMHO. This would also remove the cross-compilation problem.

I think it also reduces the potential problem of leaking information about runtime OS (if that is an issue). Theoretically it can leaks how many incoming requests tere are, but IMHO that is no different from "old" implementation, the difference is just with the returned value.

Additionally it gives more potential values to be generated within the same timeframe. I honestly do not see the point in making that value other than 1.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the previous implementation was a randomly-seeded counter (that resets every millisecond), but this version uses the increased clock precision method (method 3) of section 6.2 of the RFC:

https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-5.5

I think they are trying to stick within the RFC guidelines for Method 3 and stepping by the same fraction of a ms

Copy link
Owner Author

@ryanwinchester ryanwinchester Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I should just default to always using 12 bits for the increased clock precision because nobody that would be concerned about getting 2 extra bits of randomness is going to run elixir in production on windows/mac ;)

plus it's a tradeoff/micro-optimization of not having to check the OS at runtime and do some extra calcs for only 2 bits

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanwinchester I have no horse in this race, but that option sounds most reasonable. Not having to care about the OS the BEAM is running on is a feature, as are 2 more bits of randomness.

Copy link

@grzuy grzuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for building this library!

Leaving a comment about something I was curious about 🙏

opts = [strategy: :one_for_one, name: UUIDv7.Supervisor]
Supervisor.start_link(children, opts)
Supervisor.start_link([], opts)
Copy link

@grzuy grzuy Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the sole purpose of the application start here is left to just initialize the persistent term?

Wouldn't you be able instead to have a default in UUIDv7.Clock on first call to Clock.next_ascending and be able to remove completely the need for a supervisor and an application?

Something like

diff --git a/lib/application.ex b/lib/application.ex
deleted file mode 100644
index 59a603f..0000000
--- a/lib/application.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-defmodule UUIDv7.Application do
-  @moduledoc false
-  use Application
-
-  def start(_type, _args) do
-    # Put the timestamp ref into `:persistent_term` store.
-    # This is used in the `UUIDv7.Clock` module to determine the most
-    # recently-used timestamp.
-    clock_atomics_ref = :atomics.new(1, signed: false)
-    :persistent_term.put(UUIDv7.Clock, clock_atomics_ref)
-
-    opts = [strategy: :one_for_one, name: UUIDv7.Supervisor]
-    Supervisor.start_link([], opts)
-  end
-end
diff --git a/lib/clock.ex b/lib/clock.ex
index dbc23d7..fcfca54 100644
--- a/lib/clock.ex
+++ b/lib/clock.ex
@@ -24,8 +24,7 @@ defmodule UUIDv7.Clock do
   conditions.
   """
   def next_ascending do
-    # Get the atomics ref that we set in `UUIDv7.Application.start/2`.
-    timestamp_ref = :persistent_term.get(__MODULE__)
+    timestamp_ref = :persistent_term.get(__MODULE__, :atomics.new(1, signed: false))
 
     previous_ts = :atomics.get(timestamp_ref, 1)
     min_step_ts = previous_ts + @minimal_step_ns
diff --git a/mix.exs b/mix.exs
index d48ed19..652def1 100644
--- a/mix.exs
+++ b/mix.exs
@@ -24,8 +24,7 @@ defmodule UUIDv7.MixProject do
   # Run "mix help compile.app" to learn about applications.
   def application do
     [
-      extra_applications: [:logger],
-      mod: {UUIDv7.Application, []}
+      extra_applications: [:logger]
     ]
   end

Maybe I am missing something here.. he

Copy link
Owner Author

@ryanwinchester ryanwinchester Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, totally correct and great catch.

since I removed the genserver, UUIDv7.Application is now unnecessary

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:persistent_term.get/2 default doesn't set it, so here's the new code

    timestamp_ref =
      with nil <- :persistent_term.get(__MODULE__, nil) do
        timestamp_ref = :atomics.new(1, signed: false)
        :ok = :persistent_term.put(__MODULE__, timestamp_ref)
        timestamp_ref
      end

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right!
Sounds great.

@ryanwinchester ryanwinchester merged commit 5e4c7e6 into main Feb 27, 2025
5 checks passed
@ryanwinchester ryanwinchester deleted the feature/no-counter branch February 27, 2025 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants