Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Distributed Sequence ID (Long) generator in Java - Could someone validate if this design is correct?

Requirement: A sequence ID (Long) generator which would work in a distributed environment (multiple JVMs). There will be multiple threads working on each JVM.

Solution: We have a centralized persistent key-value store. But we do not want to make a remote call for every incoming request, so we thought of fetching batch of sequence IDs from this centralized key-value store and keep in local JVM and then use it.

Key Value Store: This is our centralized key-value store in which we keep an object SEQUENCE_ID with a Long value. This key-value store of ours has a feature of controlling concurrent updates through a version number.

BatchRetriever: This performs following operations:

  1. Get the current value for SEQUENCE_ID from key-value store
  2. Add the batch size to the retrieved value
  3. Update the new value for SEQUENCE_ID

Multiple threads could be trying to do this, so all these 3 steps will be performed as a single atomic work. We use version-number feature of this key-value store to control this concurrent updates.

SequenceHolder: A queue based data structure which would hold the batch of sequence IDs.

SequenceObserver: An observer (implemented through Observer design pattern) which can check if the size of SequenceHolder has gone down to a threshold value, it will use BatchRetriever to retrieve next batch.

Appreciate if someone can validate this design and suggest a better one.

~ NN

like image 305
Niranjan Avatar asked Oct 27 '25 05:10

Niranjan


1 Answers

These are good approaches.

A simpler solution might be to use shared memory. This has the performance of AtomicLong, while being shared across processes on the same machine.

import net.openhft.lang.io.DirectBytes;
import net.openhft.lang.io.MappedStore;

import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class CounterExampleMain {
    static volatile long id;

    public static void main(String... ignored) throws IOException {
        int counters = 128;
        int repeats = 100000;

        File file = new File(System.getProperty("java.io.tmpdir") + "/counters");
        MappedStore ms = new MappedStore(file, FileChannel.MapMode.READ_WRITE, counters * 8);
        DirectBytes slice = ms.bytes();

        long start = System.nanoTime();
        for (int j = 0; j < repeats; j++) {
            for (int i = 0; i < counters; i++) {
                id = slice.addAtomicLong(i * 8, 1);
            }
        }
        long time = System.nanoTime() - start;
        System.out.printf("Took %.3f second to increment %,d counters, %,d times, last id=%,d%n",
                time / 1e9, counters, repeats, id);
        ms.free();
    }
}

Each time I run it on my laptop, I get

Took 0.252 second to increment 128 counters, 100,000 times, last id=100,000
Took 0.267 second to increment 128 counters, 100,000 times, last id=200,000
Took 0.255 second to increment 128 counters, 100,000 times, last id=300,000

As you can see this is really cheap, averaging ~25 ns per increment and persisted between runs of the program. It is also thread safe and can be shared between JVMs.

BTW in a contented example where multiple threads are updating the same counters, I would expect closer to 50 ns.

The library I used was

<dependency>
    <groupId>net.openhft</groupId>
    <artifactId>lang</artifactId>
    <version>6.4.8</version>
</dependency>
like image 182
Peter Lawrey Avatar answered Oct 28 '25 17:10

Peter Lawrey



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!