tsid-creator icon indicating copy to clipboard operation
tsid-creator copied to clipboard

TSID as database primary keys in multi tenant environment

Open JuanCarlosGonzalez opened this issue 10 months ago • 2 comments

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

JuanCarlosGonzalez avatar Apr 04 '24 07:04 JuanCarlosGonzalez

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.

fabiolimace avatar Apr 05 '24 23:04 fabiolimace

Hi Fabio.

Thanks for your inspiring suggestions. I like the second approach too.

Best regards,

JuanCarlosGonzalez avatar Apr 08 '24 06:04 JuanCarlosGonzalez