emoragaf

emoragaf

Hello, we have this situation right now:

Every time we make a sale we need to emit a document with a unique and sequential document number.

These numbers belong to a sequence based on the product sold (each document is for one product), we have a reserved range of numbers for each product, the ranges are big enough that we do not need to worry about running out of numbers right now.

We are looking at some options to generate these numbers (postgres sequences, Kafka events, Zookeeper sequence nodes), and I thought that maybe this is a good fit for Elixir.

I’m thinking of spawning a GenServer for each product configured with the sequence range and current value, so I can use it to generate the subsequent numbers.

I would persist the updated values to a DB to restore the processes in case of failure, and in case of needing some redundancy I could use something like libcluster + swarm to spread my GenServers across a cluster

Does this look right? Maybe I’m missing an obvious alternative, any pitfalls I might not be taking into account?

Showing Posts 1 to 10

kokolegorille

kokolegorille

ksuid generate unique, sortable id.

You could (post/)prepend with your product id if You want.

Now your solution with a Genserver per product could work… it depends how much do You sell, but if You sell too much You might have a bottleneck. It will not work that well in a distributed mode.

It depends also if You just want an increasing counter, or uuid style id.

emoragaf

emoragaf OP

I saw something like ksuid before, the problem is that we need the ids to be inside a particular range, so yeah an increasing counter. For example:

For product A we have the range 202043200000000 to 202043210000000, so the generated numbers would be:

  • 202043200000000
  • 202043200000001
  • 202043200000002

For product B we could have a range of 13200500 to 20500000 so

  • 13200500
  • 13200501
  • 13200502
kokolegorille

kokolegorille

I would probably have a look at erlang counters if I had to do the same task.

emoragaf

emoragaf OP

ty for the link, i’ll take a look

shanesveller

shanesveller

Do the counters / sequences need to be durable across BEAM restarts, or internally consistent across multiple BEAM nodes? Both of those are challenging with any of the ideas above. Some other questions that come to mind:

Do they need to be monotonic across all machines?
Are gaps permissible?
Are collisions disastrous?
Can you overflow a given product’s range?

Most of these point me towards solutions on the persistent storage rather than application code, i.e. a specialized use of Postgres sequences or similar.

I’d also be curious what real-world or business constraints are at play here since the described technical limitations are unusual.

lalo2302

lalo2302

In my company we had the exact problem you have and we solved it using Postgres advisory locks:
PostgreSQL: Documentation: 9.4: Explicit Locking Point 13.3.5

So you don’t deal with the concurrency on your elixir application, but on the database level.

So let’s say you want to have a sequential number for product “A”, and 2 concurrent requests come

Request 1 and 2 concurrently:

    1. Comes in
    1. Comes in
    1. Sets advisory lock with id :erlang.crc32("A")
    1. Sets advisory lock with id :erlang.crc32("A")
    1. Lock is being used, I’ll wait until it is free
    1. Query count “A” => returns 2
    1. Inserts new record with 3
    1. Finishes transaction and releases lock
    1. Lock is available, I can proceed
    1. Query count “A” => returns 3
    1. Inserts new record with 4

I hope I was explicit enough. Using postgres takes away you a lot of headaches. You don’t need to worry about if your new GenServer is a bottle neck, or if you deploy on a cluster how should you manage your processes.

emoragaf

emoragaf OP

The counters should be durable and consistent.

Having gaps is permissible but not ideal

Collisions are a big problem

Overflowing a range should not be an issue (range >>> sale volume)

My first thought was using something like Postgres sequences, but then I thought that having it on application could be a good fit and also help bring some devs in the team into the elixir world

Business constraints, in short we sell insurance policies, we have ranges of reserved policy numbers for different products from different companies, so we are limited to those IDs for public facing documents

kreiling.io

kreiling.io

@emoragaf I just had similar requirements pop up in my project and I’m wondering if you chose an application-oriented solution?

I currently cluster my Elixir project using libcluster+swarm so I think I can pull this off in pure Elixir/Erlang using globally-named processes. But I’m not looking to reinvent the wheel :grinning_face_with_smiling_eyes:

edit: fixed a typo

derek-zhou

derek-zhou

If you use a database like postgreSQL, you can use an auto-incremented sequence.
The problem of using a OTP process is you will still need to serialize it in storage or you will start from 1 again once the cluster reboot. With serialization come with all the ACID problems, so you will be looking precisely at reinventing the wheel.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

This will not provide the properties that the OP wants. auto-incrementing sequences do not guarantee sequential ids in records. Sequences are non transactional, so if you open a transaction, insert a record (which increments the sequence), and then rollback the transaction, the sequence is still incremented.

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
psy-q
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews