Mini Unique Lexicographically-Sortable ID

Mitch

Mitch

Background

When working with IDs in software, I wanted a way to improve both database performance and storage efficiency without falling back to integer IDs. There are existing solutions for both of these problems independently, but not together.

  • For improved indexed database insert performance, you can look to UUIDv7 or ULID
  • For shorter IDs, you have things like NanoID.

But nothing combined both. For this reason, I decided to develop the Mini Unique Lexicographically-Sortable ID, or MULSID.

What is MULSID?

A MULSID is a short, 10-character, Base62 ID that encodes both a timestamp and a small amount of randomness. It is lexicographically sortable, URL-safe, and can optionally be generated monotonically (in strictly increasing order).

Here is how it compares to other common IDs:

FormatExampleLengthSortable
UUIDv44f761213-4402-463c-aaee-8dc84bcac57336🚫
UUIDv7019a1c10-3920-76a5-b8d5-b2ad94e7b72636
ULID01K8E0QWJ0CK8ZPDYZPM90TSKQ26
NanoIDdY-_WxAy8tVariable (10)🚫
MULSID3RcxISAq7f10

How it Works

A MULSID is composed of two parts:

  • Timestamp (7 chars) – Encodes the Unix time in 9 ms ticks, covering roughly 1,000 years.
  • Randomness (3 chars) – 3 Base62 characters of entropy (62^3 = 238,328 possible values per tick).

These two components create compact, sortable IDs while still maintaining a relatively high degree of uniqueness.

Timestamp Encoding

The timestamp portion of a MULSID is contained within the first 7 characters of the ID. The caveat here is that in order to be condensed to only 7 Base62 characters, it cannot use millisecond-precision, nor can it extend to the maximum length that a Unix timestamp can reach. Instead, MULSID timestamps are reduced to “ticks” that are 9ms in length, and extend to about 1000 years past the Unix epoch. This means that MULSID will work without issue until approximately 2970; way past the time where anyone currently alive will need to worry about it!

Randomness

The randomness portion is generated using a cryptographically secure random number generator (CSPRNG). It is designed such that all three characters are fully filled so you get 238,328 possible values per tick. That’s plenty for workloads with relatively low throughput (hundreds of IDs per second) and still small enough to keep the overall ID length at 10 chars.

Monotonicity

MULSID can optionally be generated in a monotonic mode, meaning IDs are strictly increasing even when multiple are created within the same 9 ms tick. This guarantees both uniqueness and sort order stability which is ideal for high-frequency ID generation. When the counter overflows, the timestamp simply increments by 1 tick.

Design Choices

To fit MULSID neatly into 10 characters, some tradeoffs were necessary between timestamp precision and randomness entropy. Let’s look at how those choices were evaluated.

Timestamp Length

The first consideration made when determining the anatomy of MULSID was timestamp length. We want the maximum tick length to fit within roughly one second of granularity while covering our 1,000 year time limit. We can calculate this by taking the number of milliseconds in 1,000 years (≈ 31.56 trillion ms) and dividing by 62 raised to the timestamp length until the tick size < 1000 ms.

LengthTick Size
6556ms
79ms
81ms

Randomness and Collision Probability

Now that we have determined our candidates for randomness length, let’s look at how each would perform on its own. The key metric we need to look at when it comes to randomness in an ID is its probability of collision. This is a classic case of the birthday problem. For each option, we can calculate the number of possible random values per tick (V = 62^n) and estimate how many IDs can be generated per tick before a 1% collision chance occurs.

Random CharactersPossible ValuesIDs For 1% Collision Chance
23844~9.3
3238328~69.7
414776336~545.6

Clearly, more random characters greatly increase safety, but we must balance that with timestamp precision.

Timestamp vs Randomness Tradeoff

Now we can look at how the tick window interacts with generation rate. While a larger random value may be good, we also have a larger window of time in which that collision can happen. With this in mind, we now have to determine what the sweet spot is between tick length and randomness. We’ll need two equations this time around:

Probability per-tick of a collision

Probability of collision within a window of time

Where:

  • R = generation rate (per second)
  • d = time per-tick
  • V = possible random values
  • T = ticks per-window

Using a 556ms window of time, based off of our shortest timestamp length, and solving for a 1% collision probability we get:

TimestampRandomnessTick DurationTicks in windowIDs/s for 1% Collision
6 chars4 chars556ms1~981
7 chars3 chars9ms~61.8~1036
8 chars2 chars1ms556~1124

Seems like with such a short tick length our 8 character ID seems to be the best, but it is not that simple. What if we have a higher rate of IDs generated per second? Let’s look at some probabilities at some higher rates and see how the shorter randomness quickly increases risk of collision:

ID per Second6 chars (4 rand)7 chars (3 rand)8 chars (2 rand)
1500~2.32%~2.16%~5.28%
2000~4.09%~3.89%~13.47%
2500~6.32%~6.07%~23.75%

As we can see, our 7 character timestamp seems to hold up pretty well as our rate increases compared to our 6 character timestamp while offering much tighter precision. With this in mind, I think our sweet spot can be determined as the 7 character timestamp with 3 characters of randomness.

Pros and Cons

MULSID is not for every use case. You can tell by the design decisions and calculations behind them that we are making some pretty significant tradeoffs here. Let’s take a deeper dive into what these pros and cons are.

ProsCons
Very short (10 chars), URL-safe, sortableCollisions possible at higher generation rates
Great for indexed DB insertsTimestamp gives away metadata
Optional monotonic generationNot suitable for secrets or API keys

Addressing the Tradeoffs

While MULSID does have some cons, here is why I feel like the pros out-weigh them, and why they are not necessarily detrimental to it.

Collisions

The monotonic variant of MULSID eliminates collisions entirely. The only caveat to this is that when the monotonic counter overflows, the timestamp needs to be incremented instead. If generation time really matters for your use-case, this could be an issue.

Predictability

While MULSID timestamps reveal rough creation time, this isn’t a real vulnerability in most systems. “Security through obscurity” isn’t robust anyway and MULSIDs should simply not be used where secrecy is required (like API keys or password resets). For identifiers such as user IDs, posts, or similar, this predictability is harmless.

Use Cases

MULSID is ideal for:

  • Databases that benefit from lexicographically sortable keys
  • Caches, queues, or job systems requiring compact chronological IDs
  • Public URLs or short IDs where UUIDs are too long

Avoid using MULSID for:

  • Secrets, tokens, or invitation links
  • Environments generating millions of IDs per second

Conclusion

MULSID offers a balanced approach to ID generation; short, sortable, and practical. It’s not designed to replace UUIDs in every scenario, but for database-centric or public-facing applications where brevity and order matter, it is a great alternative.

You can try it online at mulsid.mpact.llc or install it via NPM or JSR to use in your own JS/TS projects.

If you enjoyed this, check out my other blog posts or the MPACT LLC catalog. Thanks for reading!