Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TSID as database primary keys in multi tenant environment #32

Closed
JuanCarlosGonzalez opened this issue Apr 4, 2024 · 2 comments
Closed
Labels
question Further information is requested

Comments

@JuanCarlosGonzalez
Copy link

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

@fabiolimace
Copy link
Member

fabiolimace commented Apr 5, 2024

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.

@JuanCarlosGonzalez
Copy link
Author

Hi Fabio.

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

Best regards,

@fabiolimace fabiolimace added the question Further information is requested label Apr 15, 2024
@fabiolimace fabiolimace pinned this issue May 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants