Skip to content

OrderlyID — Typed, time-sortable 160-bit identifiers with optional checksum, tenant, and shard fields. Safer than UUIDv4, more ergonomic than ULID/TypeID.

License

Notifications You must be signed in to change notification settings

orderlykit/orderlyid

Repository files navigation

OrderlyID

OrderlyID is a typed, time-sortable, globally unique identifier format with optional checksums and built-in fields for tenancy and sharding. It is designed for distributed systems, developer ergonomics, and safer use in public APIs.

Status: Draft v0.1 — stable enough for experimentation. Spec + reference implementation in Go. Feedback and contributions welcome.


TL;DR

OrderlyID = <type>_<payload>[-<checksum>]

  • Typed: human-readable type prefix (order_..., user_...)
  • K-sortable: lexicographic order ≈ creation time (UUIDv7-style)
  • Globally unique: safe to mint in any service/region
  • DX-friendly: great in logs, APIs, and distributed systems
  • Optional extras: checksum to catch copy/paste errors; fields for tenant/shard/sequence

If you currently use AUTO_INCREMENT or UUIDv4, OrderlyID improves safety, scale, and developer ergonomics with minimal fuss.


Why not just INT IDs or UUIDv4?

vs AUTO_INCREMENT

  • Needs DB round-trips / central sequences
  • Predictable & leaks volume (bad for public APIs)
  • Harder to shard or generate offline

vs UUIDv4

  • Not time-sortable (needs extra timestamp/index)
  • No type context in logs
  • Chunky hex; weaker DX

OrderlyID

  • App-side generation, K-sortable, self-describing (order_...)
  • Works across services/regions/edge; fits SQL & NoSQL (DynamoDB) alike
  • Optional checksum for fat-finger protection

Developer use cases

  • Greppable logs: shipment_... vs random hex → faster triage
  • Cursor pagination: WHERE id < :cursor ORDER BY id DESC LIMIT N
  • Idempotency keys: stable across HTTP, queues, DB writes
  • Cross-service correlation: put the same typed ID in headers/metrics
  • Offline generation: mint IDs on mobile/edge; sync later
  • Multi-tenant routing (optional field): cheap partitioning/archival
  • Global feeds: shard by hash(id) and merge per-shard tails
  • Fixtures & tests: readable seed data (user_*, order_*)
  • Operational safety: checksum catches copy/paste/transcription errors
UUIDv4: 550e8400-e29b-41d4-a716-446655440000
OrderlyID: order_00myngy59c0003000dfk59mg3e36j3rr-9xgg

Format

Canonical string

<prefix>_<payload>[-<checksum>]
  • prefix: [a-z][a-z0-9]{1,30} (entity type; lowercase)
  • _: required separator
  • payload: Base32 (Crockford) of a 160-bit body (exactly 32 chars)
  • checksum: optional 4 chars (Bech32-style polymod over <prefix>_<payload>)
Example:
order_00myngy59c0003000dfk59mg3e36j3rr-9xgg
      |                                   |
      +-- prefix ("order")                +-- checksum (optional)
            |--- payload (32 Base32 chars; encodes 160-bit body)

Binary layout (160 bits total)

| 48b time | 8b flags | 16b tenant | 12b seq | 16b shard | 60b random |
  • time (48b): Unix ms since 2020-01-01 UTC (epoch shift trims bits)
  • flags (8b): version + privacy marker
  • tenant (16b): org/region id (0 if unused)
  • seq (12b): per-node monotonic counter for same-ms bursts
  • shard (16b): storage/routing hint
  • random (60b): CSPRNG entropy

Properties

  • Lexicographic order ≈ time → flags → tenant → seq → shard → random
  • Canonical output is lowercase; parsers MAY accept uppercase
  • URL/file safe; stable length; checksum optional

Quick start

Go

import "github.com/orderlykit/orderlyid"

id := orderlyid.New("order",
  orderlyid.WithTenant(12),
  orderlyid.WithShardFromBytes([]byte("customer")),
  orderlyid.WithChecksum(true),
)
// => "order_00myngy59c0003000dfk59mg3e36j3rr-9xgg"

CLI

$ go run ./cmd/orderlyid -prefix order -tenant 12 -n 1
order_00mzmwep4w00030000006tcw2f0g2a5n  2025-09-17T18:03:43.527Z  tenant=12    shard=0     seq=0

$ go run ./cmd/orderlyid -parse order_00mzmwep4w00030000006tcw2f0g2a5n
ID:         order_00mzmwep4w00030000006tcw2f0g2a5n
prefix:     order
time (ms):  1758132223527
time (iso): 2025-09-17T18:03:43.527Z
flags:      0x00
tenant:     12
seq:        0
shard:      0
random60:   0x03699c13c10128b5

DynamoDB & SQL patterns

DynamoDB timeline PK = ITEM#{itemId}, SK = EVT#{orderlyid}Query gives "latest first"

Global lookup by public ID GSI: GSI1PK = PUBLICID#{orderlyid}, GSI1SK = CONST

Postgres / MySQL

  • Store both id_text VARCHAR(64) UNIQUE and id_bin BINARY(20)
  • Index id_bin for range scans
  • Use id_text for APIs/logs

Migration strategy

  • Dual-write a public_id column (keep your existing PK)
  • Accept both old and new IDs at read edges
  • Prefer new IDs in logs/metrics immediately
  • Backfill in batches; flip API docs once ready

Specification

  • spec/0001-spec.md — normative spec
  • spec/test-vectors.json — conformance vectors
  • spec/README.md — overview and guidance

Prior Art & Acknowledgements

OrderlyID builds on a long line of identifier formats and open-source work:

  • UUID (RFC 4122) — the original 128-bit unique identifier
  • ULID — introduced time-sortable IDs with a Base32 encoding
  • UUIDv7 (IETF draft) — formalizes a time-ordered variant
  • TypeID — combined typed prefixes with UUIDv7-style bodies

OrderlyID is inspired by TypeID but is not a fork. It shares the motivation of human-friendly, typed identifiers while extending the design with:

  • A 160-bit body (vs 128-bit) with tenant/shard/seq fields
  • Optional 4-char checksum for safer use in public/admin surfaces
  • A privacy flag for time bucketing in user-facing contexts

We thank the authors of UUID, ULID, UUIDv7, and TypeID for the foundational ideas.


Roadmap

  • Reference implementation: Go (production-ready, conformance-tested).
  • Planned bindings: Rust and Python (contributors welcome).
  • Once the spec stabilizes, we aim to add official libraries for more languages and databases.

Contributing

  • See spec/0001-spec.md and spec/test-vectors.json
  • All implementations must pass the conformance tool before merge. We welcome feedback on the spec, new language bindings, and real-world use cases.

License

Apache License 2.0 © 2025 Piljoong Kim

About

OrderlyID — Typed, time-sortable 160-bit identifiers with optional checksum, tenant, and shard fields. Safer than UUIDv4, more ergonomic than ULID/TypeID.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages