Choosing Unique Identifiers: Snowflake ID vs UUIDv4 vs UUIDv7 vs ULIDs
When designing database schemas, you often need to choose a unique identifier strategy. There are several options available, each with…
Choosing Unique Identifiers: Snowflake ID vs UUIDv4 vs UUIDv7 vs ULIDs
When designing database schemas, you often need to choose a unique identifier strategy. There are several options available, each with different trade-offs in terms of performance, storage efficiency, and sortability. Depending on your requirements, some choices may cause unexpected issues with indexing performance or storage overhead. In this article, we’ll explore four popular approaches to generating unique identifiers and examine their strengths and weaknesses.

Rationale
In a recent project, I needed to stream event-driven data to Kinesis and process it with a custom Flink-like solution. My use case was straightforward: read data from a source and write it to Kinesis without requiring any specific ordering (essentially a ‘select * from table’ operation). I chose UUIDv4-based random strings as partition keys for sharding across Kinesis partitions, since I only needed unique identifiers for distribution, not database indexing.
While my use case was simple and didn’t require ordered identifiers, many developers face the dilemma of choosing the right identifier type that provides both uniqueness and sortability. Let’s explore the available options and help you determine which might be best for your requirements.
Snowflake IDs
Snowflake IDs, originally developed by Twitter, create unique identifiers based on timestamps with additional bits reserved to avoid collisions in distributed systems. The standard implementation uses a 64-bit structure.
Design Considerations
Total size selection: 64 bits strikes the optimal balance between uniqueness, storage efficiency, and performance. While larger sizes like 128 bits offer more flexibility, they increase storage overhead and may impact indexing performance. The 64-bit format fits comfortably in most programming languages’ native integer types.
Bit Allocation Breakdown
1 bit for sign compatibility — This bit is always set to 0 to ensure the ID remains positive when interpreted by signed integer types in languages like Java. While this bit is essentially wasted (since timestamps are always positive), it prevents negative number interpretation issues in systems expecting signed 64-bit integers.
41 bits for timestamp — With ²⁴¹ possible values, this provides approximately 69.7 years of coverage when using millisecond precision. Starting from a custom epoch (rather than Unix epoch) maximizes the useful range. Each additional bit would nearly double the timespan but reduces bits available for collision avoidance.
10 bits for machine identifier — Supporting up to 1,024 unique machines (²¹⁰). Each machine in your distributed system gets assigned a unique value from this range, ensuring IDs generated simultaneously on different machines remain unique. Increase this allocation if you need to support more machines.
12 bits for sequence counter — Handles up to 4,096 unique IDs (²¹²) generated within the same millisecond on a single machine. When this counter reaches its maximum, the system typically waits until the next millisecond before generating more IDs. Adjust this value based on your expected throughput per machine.
Key Properties
Global sortability: IDs are sortable by timestamp first, then by machine ID, then by sequence number. This means newer IDs are always numerically larger than older ones, regardless of which machine generated them.
Collision avoidance: The combination of timestamp precision, machine identification, and sequence numbering ensures uniqueness across your entire distributed system.
Partial randomness: While not cryptographically random, the distribution across machines and timing variations provide sufficient unpredictability for most use cases.
Practical Considerations
- Requires loose clock synchronization across machines
- Handle sequence counter overflow by waiting for next millisecond
- Consider your system’s expected lifespan when choosing timestamp bit allocation
- Machine ID assignment strategy needs to prevent duplicates across your infrastructure
UUIDv4
UUIDv4 offers a simple and widely adopted approach to generating unique identifiers through randomness. This 128-bit data type provides excellent collision resistance, making conflicts extremely rare even across large distributed systems.
Key Characteristics
Strong uniqueness guarantee: With 122 bits of randomness (6 bits are reserved for version and variant), the probability of collision is negligible for practical purposes. You’d need to generate trillions of UUIDs before encountering a duplicate.
Zero coordination required: Unlike timestamp-based approaches, UUIDv4 generation requires no coordination between machines, no clock synchronization, and no centralized ID assignment.
Universal compatibility: Supported natively across virtually all programming languages, databases, and systems.
Performance Considerations
Generation overhead: The performance impact varies by implementation. Java’s default UUID generation uses SecureRandom, which provides cryptographic randomness but can be slower than pseudorandom alternatives. However, for most applications, this overhead is negligible compared to other operations like database writes or network calls.
Storage requirements: Each UUID consumes 16 bytes compared to 8 bytes for 64-bit alternatives like Snowflake IDs. This 2x storage overhead becomes significant for tables with hundreds of millions of records.
Indexing Challenges
B-tree performance: The random nature of UUIDv4s creates challenges for database indexing. Unlike sequential IDs, random UUIDs cause:
- Index fragmentation: New records are inserted randomly throughout the B-tree rather than at the end
- Page splits: More frequent splitting of index pages as random values fill gaps
- Reduced cache efficiency: Hot index pages are scattered rather than concentrated
This doesn’t prevent indexing UUIDs, but makes index maintenance more expensive, particularly for write-heavy workloads.
When to Choose UUIDv4
Ideal scenarios:
- Distributed systems without coordination infrastructure
- Applications prioritizing simplicity over performance optimization
- Systems where preventing ID enumeration is important (e.g., public-facing APIs)
- Merging data from multiple independent sources
Consider alternatives when:
- Write performance is critical and you have high insert volumes
- Storage costs are a primary concern
- You need naturally sorted identifiers for range queries or chronological ordering
While UUIDv4’s simplicity makes it an attractive default choice, understanding these tradeoffs helps determine whether it’s optimal for your specific use case.
UUIDv7
UUIDv7 draws inspiration from Snowflake IDs by incorporating timestamp-based ordering while maintaining UUID format compatibility. It addresses UUIDv4’s indexing performance issues by ensuring generated values are naturally sortable.
Structure
UUIDv7 maintains the standard 128-bit UUID format with the following allocation:
Bit breakdown:
- 48 bits: Unix timestamp in milliseconds
- 12 bits: Optional sub-millisecond precision or randomness
- 62 bits: Random data for uniqueness
- 6 bits: Version (4 bits) and variant (2 bits) identifiers
Key Advantages
Improved database performance: The timestamp prefix ensures new UUIDs are inserted sequentially in B-tree indexes, reducing fragmentation and page splits compared to UUIDv4’s random insertion pattern.
Global temporal ordering: UUIDs generated at different times will sort chronologically, regardless of which machine created them. This provides approximate creation-time ordering across distributed systems.
UUID ecosystem compatibility: Maintains standard UUID format, so existing tooling, libraries, and database UUID types work without modification.
Collision resistance: Despite having less randomness than UUIDv4 (62 vs 122 bits), collision probability remains negligible for practical applications.
Ordering Characteristics
Millisecond-level global ordering: UUIDs are globally sortable by their creation timestamp down to millisecond precision. Two UUIDs generated in different milliseconds will always sort in chronological order.
Within-millisecond randomness: UUIDs generated within the same millisecond on different machines may not maintain strict chronological order, as the remaining bits are random. However, this limitation rarely impacts real-world applications since microsecond-level ordering across distributed systems is generally not required.
When to Choose UUIDv7
Ideal for:
- Applications needing both UUID compatibility and better indexing performance
- Systems requiring approximate chronological ordering without coordination
- Migration from UUIDv4 where you want to retain UUID format but improve performance
Consider alternatives when:
- You need strict ordering guarantees within sub-millisecond timeframes
- Microsecond precision timing is critical for your use case
ULIDs
ULIDs (Universally Unique Lexicographically Sortable Identifiers) are 128-bit identifiers that share UUIDv7’s timestamp-based approach but with several distinctive characteristics that make them appealing for specific use cases.
Structure
Bit allocation:
- 48 bits: Unix timestamp in milliseconds (same as UUIDv7)
- 80 bits: Random data for uniqueness
Key Differences from UUIDv7
String representation: ULIDs use Base32 encoding (Crockford’s variant) instead of hexadecimal, resulting in more compact, URL-safe strings:
- ULID:
01ARZ3NDEKTSV4RRFFQ69G5FAV(26 characters) - UUID:
550e8400-e29b-41d4-a716-446655440000(36 characters)
Lexicographic sorting: ULIDs are designed to sort correctly as strings, meaning alphabetical sorting produces chronological order. UUIDs require special handling to achieve the same result.
Case-insensitive: Base32 encoding avoids visual confusion between similar characters (0/O, 1/I/l), making them more human-readable.
Monotonic ordering: When multiple ULIDs are generated within the same millisecond, implementations typically increment the random portion to maintain strict ordering.
Advantages
Developer experience: More readable and compact than traditional UUIDs, easier to work with in logs, APIs, and debugging.
String-based sorting: Natural alphabetical sorting gives chronological order without special logic.
Database performance: Like UUIDv7, provides sequential insertion patterns that improve B-tree index performance over UUIDv4.
URL-friendly: Base32 encoding makes them safe for URLs without escaping.
Trade-offs
Ecosystem maturity: Less widespread adoption compared to UUIDs — fewer native database types and library support.
Encoding overhead: Base32 is less space-efficient than binary representation, though still more compact than hex-encoded UUIDs in string form.
Non-standard format: Unlike UUIDs, not an official standard, which may complicate interoperability with systems expecting UUID format.
When to Choose ULIDs
Ideal for:
- Applications where identifiers are frequently displayed or manipulated as strings
- Systems prioritizing human readability and URL safety
- New projects not constrained by existing UUID infrastructure
Consider UUIDv7 instead when:
- You need compatibility with existing UUID-based systems
- Database or framework has strong native UUID support
- Interoperability with UUID-expecting APIs is required
Summary

메타데이터
- post_id
- a67cd03f4934
- slug
- choosing-unique-identifiers-snowflake-id-vs-uuidv4-vs-uuidv7-vs-ulids-a67cd03f4934
- url
- https://medium.com/@sinha.k/choosing-unique-identifiers-snowflake-id-vs-uuidv4-vs-uuidv7-vs-ulids-a67cd03f4934
- canonical_url
- https://medium.com/@sinha.k/choosing-unique-identifiers-snowflake-id-vs-uuidv4-vs-uuidv7-vs-ulids-a67cd03f4934
- author_url
- https://medium.com/@sinha.k
- status
- ok
- fetched_at
- 2026-06-25 07:00:49