tsid-creator
tsid-creator copied to clipboard
TSID as database primary keys in multi tenant environment
Hi,
First of all, thanks for this library. I haven't used it yet, but it looks promising. I'm working on a project with Spring/Hibernate/MySql classical stack on a multitenant environment, where there is a database catalog per tenant. Additionally, some tenants data could be consolidated in a different catalog as they belong to the same organization. Anyway, my requirement is to generate primary keys in a way where future clashes can be avoided. This is where this library fits. My initial thought was to assign a different node id for each tenant, but the library is designed assuming that node id is immutable. So, my questions are:
- Does my approach make sense?.
- If so, do you think that this requirement could be fit in your library?.
- If not, my idea is to clone the repo and make an implementation where the create method receives an overriding node id. What do you think about this approach?.
Thanks for your time. Best regards,
Juan Carlos
Hi @JuanCarlosGonzalez !
I think you could build a TsidFactory
every time you need to generate a new TSID with a different node ID. This is the approach I use in UuidCreator
to override the node ID. The only problem is that the counter (sequence) will be always random. See the example below:
package com.example;
import com.github.f4b6a3.tsid.Tsid;
import com.github.f4b6a3.tsid.TsidFactory;
public class TsidGenerator {
private static final int nodeBits = 10;
public static Tsid next(int node) {
// setup a factory to be used in this call
TsidFactory factory = TsidFactory.builder() //
.withNodeBits(nodeBits) // max: 20
.withNode(node) // max: 2^nodeBits
.build();
// use the factory once
return factory.create();
}
// TODO: remove me
public static void main(String[] args) {
System.out.println(TsidGenerator.next(1000));
}
}
The next example is closer to your approach. It intercepts the TSID and overrides its node ID before returning the TSID to the caller. You could use TsidCreator
instead of TsidFactory
. I chose the latter because it is adjustable, although more complex. The inspect()
and main()
methods only exist to check the output. They can be removed.
package com.example;
import com.github.f4b6a3.tsid.Tsid;
import com.github.f4b6a3.tsid.TsidFactory;
public class TsidGenerator2 {
// NOTE: change me if you want (max:20)
private static final int nodeBits = 10;
// NOTE: don't need to change from here
private static final int randBits = 22;
private static final int counterBits = randBits - nodeBits;
private static final long nodeMask = (1L << nodeBits) - 1L;
private static final long tsidMask = ~(nodeMask << counterBits);
private static final TsidFactory factory = TsidFactory.builder().withNodeBits(nodeBits).withNode(0L).build();
public static Tsid next(int node) {
// generate a TSID using the custom factory
final long tsid = factory.create().toLong();
// override the node ID, preserving the time stamp and counter/sequence
return Tsid.from((tsid & tsidMask) | ((node & nodeMask) << counterBits));
}
// TODO: remove me
private static void inspect() {
Tsid[] list = new Tsid[10000];
for (int node = 0; node < list.length; node++) {
list[node] = TsidGenerator2.next(node % (1 << nodeBits));
}
for (int node = 0; node < list.length; node++) {
Tsid tsid = list[node];
System.out.println(String.format(
"TSID hex: %s, TSID binary: %s, TSID time: %s, TSID counter/sequence: %d , input node: %d; output node: %d, equals: %s",
/* TSID hex */ Long.toUnsignedString(tsid.toLong(), 16), //
/* TSID binary */ tsid.encode(2), //
/* TSID time */ tsid.getInstant(), //
/* TSID counter/sequence */ tsid.getRandom() & tsidMask, //
/* input node */ node % (1 << nodeBits), //
/* output node */ tsid.getRandom() >>> counterBits, //
/* equals */ node % (1 << nodeBits) == tsid.getRandom() >>> counterBits));
}
System.out.println();
System.out.println("SETTINGS");
System.out.println("--------");
System.out.println("nodeBits: " + nodeBits);
System.out.println("counterBits: " + counterBits);
System.out.println("nodeMask: " + Long.toUnsignedString(nodeMask, 16));
System.out.println("tsidMask: " + Long.toUnsignedString(tsidMask, 16));
}
// TODO: remove me
public static void main(String[] args) {
inspect();
}
}
Feel free to clone the repo and implement your own solution if you don't like the examples.
Best regards.
Hi Fabio.
Thanks for your inspiring suggestions. I like the second approach too.
Best regards,