Awesome
TSID (Time-Sorted ID)
Implementation of TSID based on Snowflake ID.
Important notice
This project/repository will be archived and migrated to another project.
What is TSID?
TSID (stands for Time-Sorted ID) is a special type of ID that looks like UUID but is actually sortable and incremental. This ensures the usage of indexing in the database while preserving the uniqueness of UUID (or mostly the look of it).
TSID is basically a signed 64-bit integer (long in Java). The implementation is heavily inspired by the following sources:
- Vlad Mihalcea's posts on TSID and why using it at all.
- Snowflake ID from X (formerly Twitter).
Implementation Details
The current implementation of TSID consists of three parts:
- The first 42 bits represent the timestamp of the creation of the TSID. Specifically, it comprises a 1-bit signed value and a 41-bit timestamp. The timestamp is calculated by getting the current UTC in milliseconds since Unix epoch and subtracting the custom epoch value (if defined).
- The next 10 bits represent the node ID for the current machine/node/worker. This allocation is particularly useful in systems where multiple instances of applications are deployed, and each utilizes the TsidFactory for generating the Tsid.
- Finally, the last 12 bits represent an incremental sequence number for multiple TSIDs generated within the same millisecond (timestamp), if such occurrences arise. The starting sequence for each timestamp is securely randomized to minimize the predictability of the TSID.
The generation of Tsid per node is guaranteed to be thread-safe.
Usage
To generate an instance of Tsid
, we use TsidFactory
:
import com.vincentdao.tsid.Tsid;
import com.vincentdao.tsid.TsidFactory;
public static void main(String[] args) {
// Quickly generate a new one (NOT THREAD-SAFE!)
Tsid quickGeneratedTsid = TsidFactory.instance().quickGenerate();
// Generate new Tsid (thread-safe)
Tsid tsid = TsidFactory.instance().generate();
// Reset the factory
TsidFactory.reset();
// Configure the factory using its Builder
TsidFactory.builder()
.withNode()
.customizedAs(69L)
.withEpoch()
.customizedAs(1000000L)
.build();
// Now, the new Tsid will have its node set to 69 and timestamp calculated from epoch 1000000
Tsid configuredTsid = TsidFactory.instance().generate();
}
If we already have a Tsid
value (long
or String
), we can use Tsid
static methods:
import com.vincentdao.tsid.Tsid;
public static void main(String[] args) {
// The value must be 64-bit integer!
Tsid fromLongTsid = Tsid.fromLong(1541815603606036480L);
// String must be in Crockford's encoding
Tsid fromStringTsid = Tsid.fromString("2NJT27V22YG00");
}
Using Tsid
in database
Since Tsid
values are essentially 64-bit integers (i.e., long
), we store them as such in the database. However, each
database vendor has its own syntax for representing 64-bit integers. Below are the syntaxes for the long
type used by
some well-known database vendors:
- MySQL, MariaDB, SQL Server, IBM DB2:
BIGINT
- SQLite:
INTEGER
(Note: SQLite uses dynamic typing, butINTEGER
can store up to 64-bit values.) - Oracle:
NUMBER
(It's worth noting that Oracle'sNUMBER
type is capable of representing a wide range of values, so specifying precision and scale might be necessary depending on the use case.)
On the application level, we can retrieve the Tsid
from the stored long
value and present it back to the users in
whatever format is most suitable.
Configure the factory
There are currently two configurations for the factory:
- Node ID: Indicates the current machine/node/worker. As TSIDs can be generated by multiple instances in the system, it is crucial to clearly define each node with its ID to ensure safe generation (i.e., no collision). The default value is the current thread's ID where the call to obtain the factory instance occurs.
- Custom epoch: Specifies the epoch for calculating the timestamp. The default is the Unix epoch.
These values can be defined at 3 places: system's environment, system's properties (for current running Java app), and
on code-level using TsidFactory.Builder
.
Configuring for the properties is used through -D
argument:
java -D
Here are the name of these variables:
- Node:
TSID_NODE
forenv
tsid.node
forproperty
- Epoch:
TSID_EPOCH
forenv
tsid.epoch
forproperty
String Representation
The String representation of TSID is based on Crockford's Base32:
import com.vincentdao.tsid.Tsid;
import com.vincentdao.tsid.TsidFactory;
public static void main(String[] args) {
// Example for Tsid with value "1541815603606036480"
Tsid id = TsidFactory.instance().generate();
System.out.println(id.asLong()); // 1541815603606036480
System.out.println(id.asString()); // "2NJT27V22YG00"
System.out.println(id); // "2NJT27V22YG00"
System.out.println(id.asLowercaseString()); // "2njt27v22yg00"
}
License
This project is licensed under MIT License.