Generate Timebased UUID in elixir

Hey there,

I am using Cassandra and it takes id as of type timebased uuid so I was curious if there is a way or library in Elixir that can generate Timebased UUID for me.

Thanks.

Which relational-mapping library are you using, if any?

I am using Xandra for cassandra in Elixir if you mean that.

1 Like

A cursory look through Xandra’s documentation and source code shows that Xandra does not (yet?) expose any way to generate timeuuid’s in Elixir (but only encoding/decoding values genereated from within Cassandra).

I did find another library called ‘Cassandra’ which has not seen very much usage and has not been updated in a couple of years, but it does contain an internal (undocumented) module called Cassandra.UUID which seems to generate UUIDs in the desired format.

Looking at that module in a bit more detail, it seems like the actual work is offloaded to elixir_uuid (which is stable and widely used), and that Cassandra’s timeuuids are nothing else than ‘v1’ UUIDs.

So using elixir_uuid is probably the best approach :slight_smile:.

2 Likes

Thank you for the details much appreciated.

I do tried V1 UUID but the rows where not inserted as they come it was randomized.

I can think of two possible reasons:

  1. Maybe Cassandra is overwriting the values by values it generates itself?
  2. Maybe you’re trying to insert string-encoded UUIDs whereas Xandra uses a raw binary encoding format as default?

Actually if the type is timebased uuid then I can insert now() which it converts to uuid internally.

ksuid is sortable uuid

4 Likes

KSUID is great, and monotonically increasing. Couldn’t push for it more.

1 Like

For lexicographically sortable UUIDs I prefer UUIDv6.

1 Like

I’ll throw ecto_ulid out there too. ULID is a pretty nice ID type (and while I realize this post is about IDs in Cassandra) but its lack of support in Postgres turned into a PITA. I was storing it as a :binary_id since it’s a 128bit integer but querying by the human-readable format was impossible; I had to convert it manually and that got old.

Anyway, here’s a good write-up of the differences between ULID & KSUID. Both are likely great options for your use case.

3 Likes

My problem with ULID is that it is only length-compatible with UUID, not format-compatible, so it cannot be added to the UUID specs as a new version to the IETF RFC. That is why I prefer UUIDv6 proposal, which could be handled in PostgreSQL and any other implementation with almost no changes to the code.

Exactly. That was the most annoying part for me: having to convert from one format to another. I didn’t really need ULID anyway, but it was nice to get some experience with it.

By format I do not mean hexadecimal representation, but binary representation. Just to make it clear. String 46529a99-5727-4655-b1e9-58ebb8d90c6f is NOT UUID.

Snowflake IDs, GitHub - pggalaviz/exnowflake: A decentralized, unique, time based ID generator. .

Docs:

https://hexdocs.pm/exnowflake/readme.html

2 Likes

Can you explain how to do this? I just get…

** (ArgumentError) Invalid argument; Expected: <<clock_seq::14>>, <<node::48>>
    (uuid 1.1.8) lib/uuid.ex:250: UUID.uuid1/3

Thanks.

I just ported the Cassandra::Uuid::Generator code from the official Cassandra Ruby gem:

defmodule TimeUuid do
  use Bitwise, only_operators: true

  @gregorian_offset 122192928000000000

  def new(date_time, jitter \\ nil) do
    jitter = jitter || :rand.uniform(65536)
    clock_id = :rand.uniform(65536)
    node_id = generate_node_id()
    usecs = DateTime.to_unix(date_time, :microsecond) + jitter

    t = @gregorian_offset + usecs * 10
    time_hi  = t &&& 0x0fff000000000000
    time_mid = t &&& 0x0000ffff00000000
    time_low = t &&& 0x00000000ffffffff
    version = 1
    clock_id = clock_id &&& 0x3fff
    node_id = node_id &&& 0xffffffffffff
    variant = 0x8000

    n = (time_low <<< 96) ||| (time_mid <<< 48) ||| (time_hi <<< 16)
    n = n ||| (version <<< 76)
    n = n ||| ((clock_id ||| variant) <<< 48)
    n = n ||| node_id

    <<n::integer-size(128)>>
    |> UUID.binary_to_string!()
  end

  def generate_node_id do
    :math.pow(2, 47)
    |> trunc()
    |> :rand.uniform()
    |> Bitwise.bor(0x010000000000)
  end

end
1 Like