Generate Time‑Ordered IDs with TSID in Spring Boot 3
This guide explains why random UUIDs are inefficient as primary keys, introduces the TSID library that creates 64‑bit time‑ordered identifiers, and provides step‑by‑step Maven setup, API usage, JPA integration, and testing examples for Spring Boot 3.5.0.
1. Introduction
Standard 128‑bit random UUIDs are widely used but are poorly suited for database primary keys because they are large and cause low B‑tree index fill factors, frequent page splits, and excessive memory consumption.
Each UUID occupies 16 bytes, inflating foreign‑key columns.
B‑tree indexes store data in sorted order; random values break this order.
Random keys lead to low page fill factors and many page splits, wasting disk and buffer‑pool space.
2. TSID Overview
TSID (Time‑Sorted Identifier) is an open‑source Java library that generates unique IDs ordered by creation time. It combines concepts from Twitter's Snowflake and the ULID specification.
Time‑ordered generation
Fits in a 64‑bit long Can be represented as a 13‑character URL‑safe string (Crockford Base‑32)
Shorter than UUID, ULID, and KSUID
3. Maven Dependency
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-tsid</artifactId>
<version>2.1.4</version>
</dependency>Check the latest version on Maven Repository.
4. TSID Structure
TSID consists of two parts:
Time part (42 bits) : milliseconds elapsed since 2020‑01‑01 00:00:00 UTC.
Random part (22 bits) : composed of a node identifier (0‑20 bits) and a counter (2‑22 bits). The number of node bits determines the counter bits.
Example with default 10‑bit node and 12‑bit counter:
adjustable →
|------------------------------------------|----------|------------|
time (42 bits) node (10 bits) counter (12 bits)Ranges:
Time: 2^42 ≈ 69 years (signed 64‑bit) or ≈139 years (unsigned).
Node: up to 2^20‑1 = 1,048,575 (configurable 0‑20 bits).
Counter: up to 2^12‑1 = 4,095 per millisecond with the default node size.
5. Basic API Usage
Creating a TSID and converting it:
TSID tsid = TSID.Factory.getTsid();
long id = TSID.Factory.getTsid().toLong(); // 797263809025044592
String strId = TSID.Factory.getTsid().toString(); // 0P43KE5EXXM3ZBatch generation:
for (int i = 0; i < 20; i++) {
long id = TSID.Factory.getTsid().toLong();
System.err.println(id);
}Fast generation (up to 2^22 IDs per millisecond) using TSID.fast() which relies on AtomicInteger. Use the regular factory for cryptographically‑secure scenarios.
TSID tsid = TSID.fast();Other utilities:
Parse from a 13‑character string: TSID tsid = TSID.from("0123456789ABC"); Retrieve creation timestamp: Instant instant = TSID.Factory.getTsid().getInstant(); Encode to base‑62: String base62 = TSID.Factory.getTsid().encode(62); Custom formatting:
String formatted = tsid.format("pack%S"); // pack0P43SMJ0FH80H6. Custom JPA Primary‑Key Generation
Component that builds a configurable TSID.Factory:
@Component
public class TsidComponent {
public static final String TSID_NODE_COUNT_PROPERTY = "tsid.node.count";
public static final String TSID_NODE_COUNT_ENV = "TSID_NODE_COUNT";
public static TSID.Factory TSID_FACTORY;
static {
String nodeCountSetting = System.getProperty(TSID_NODE_COUNT_PROPERTY);
if (nodeCountSetting == null) {
nodeCountSetting = System.getenv(TSID_NODE_COUNT_ENV);
}
int nodeCount = nodeCountSetting != null ? Integer.parseInt(nodeCountSetting) : 256;
TSID_FACTORY = getTsidFactory(nodeCount, 0);
}
public static TSID.Factory getTsidFactory(int nodeCount, int nodeId) {
int nodeBits = ((int) (Math.log(nodeCount) / Math.log(2))) + 1;
return TSID.Factory.builder()
.withRandomFunction(TSID.Factory.THREAD_LOCAL_RANDOM_FUNCTION)
.withNodeBits(nodeBits)
.withNode(nodeId)
.build();
}
}Custom annotation for JPA:
@IdGeneratorType(PackIdGenerator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface PackSequence {}Entity example:
@Entity
@Table(name = "o_student")
public class Student {
@Id
@PackSequence
private Long id;
private String name;
private String sno;
}7. Test Example
@Resource
private StudentService studentService;
@Test
public void testSave() {
for (int i = 0; i < 10; i++) {
Student student = new Student();
student.setName("pack_xg_%s".formatted(i));
student.setSno("X00002-%s".formatted(i));
this.studentService.save(student);
}
}The TSID generator is thread‑safe, making it suitable for high‑concurrency scenarios such as logging or distributed systems.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
