segmentio/ksuid: K-Sortable Unique IDs in Go, and When They Are the Wrong Choice
K-Sortable Globally Unique IDs
At a glance
- What is it?
- The Go reference implementation of KSUIDs packs a 32-bit timestamp and 128 bits of randomness into 20 bytes whose text form sorts by creation time. It is a small library with a clear trade-off: 27-character identifiers instead of 36.
- Who is it for?
- Adopt segmentio/ksuid when your identifiers are generated in Go, you want them to sort by creation time in any text store, and you are comfortable with 27-character strings. Do not adopt it when your stack is not Go and you want a maintained client for your language, when you need the identifier to carry no timestamp, or when you need strict monotonicity inside a single second.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 96 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What a KSUID is, and the problem it removes
A KSUID is a 20-byte identifier: a 32-bit unsigned integer UTC timestamp in big-endian encoding, followed by a 128-bit randomly generated payload. The README describes the timestamp epoch as May 13th, 2014, chosen to give the format over 100 years of life. The text representation is always 27 alphanumeric base62 characters.
The problem it solves is the one that shows up when you store UUIDv4 values as primary keys and then want them in insertion order. A UUIDv4 carries no time component, so ordering by it is meaningless. You either add a separate created_at column and index it, or you accept that range scans over recent rows touch pages scattered across the table. A KSUID embeds the timestamp in the leading bytes, so sorting the identifier itself sorts by generation time. The README states this plainly: running a set of KSUIDs through the UNIX sort command produces a list ordered by generation time.
The audience is Go services that generate identifiers on the write path. The library is the reference implementation of the format, and the README says it has been used in production at Segment across multiple projects. That is a statement about one company's usage, not an independent audit, and the repository carries no release history, so treat version stability as something you verify yourself before pinning.
One property worth separating from the rest: the README claims collision-free, coordination-free, dependency-free generation. The coordination-free part is the real differentiator against Snowflake-style IDs, which fit into a 64-bit space and therefore need machine or worker identifiers to avoid collisions. KSUID spends 20 bytes instead and needs no coordination at all.
The 20-byte layout and why the text form still sorts
The layout is the whole design. Four bytes of big-endian timestamp, sixteen bytes of payload. Big-endian matters because it makes byte-wise comparison equivalent to numeric comparison, which is what lets a plain lexicographic sort on the raw form produce time order.
The text form is base62 over the same 20 bytes, fixed at 27 characters. Because base62 is order-preserving when applied to a fixed-width big-endian value, the string sorts the same way the bytes do. The README notes that no delimiters are used, so stringified KSUIDs will not be truncated or tokenized by software expecting human-readable text, which is the failure mode UUID strings hit when a tokenizer treats the hyphens as separators.
The README is careful about the strength of the ordering claim: it says KSUIDs are loosely sorted by generation time and that this is not a strong guarantee because it depends on wall clocks. That caveat deserves attention. Two KSUIDs generated in the same second will not necessarily come out in the order the calls were made, because the payload is random and the timestamp has one-second resolution. If your application needs strict per-process monotonicity, this format does not provide it, and the README does not claim otherwise.
The entropy comparison in the README is specific: 128 bits of pseudorandom data, described as 64 times larger than the 122 bits used by RFC 4122 UUIDv4, with the timestamp treated as bonus entropy. The README also argues that UUIDv1 has too little randomness to resist an adversary guessing identifiers. That is a reasonable argument for treating UUIDv1 as unsuitable where identifiers are guessable tokens, but it does not follow that a KSUID is a secret. A KSUID timestamp is readable by anyone holding the string.
Installing the Go library and generating your first KSUID
The README gives a single install command. It fetches the module and its dependencies into your Go module cache.
go get -u github.com/segmentio/ksuidGenerating a value is one call to ksuid.New(). The README states that all public package level pure functions are concurrency-safe, protected by a global mutex. The README does not print a full Go program for this, so the call is the whole example: assign the result of ksuid.New() and print it with String().
You should see a 27-character base62 string. The README's example output is 0ujsswThIGTUYm2K8FjOOfXtY1K. Because the KSUID type implements Stringer, printing the value directly goes through the same conversion.
The README states that the KSUID type implements database/sql.Scanner and database/sql/driver.Valuer, plus the binary and text marshal interfaces, and that it is encoding/json friendly. That means you can put a KSUID field directly in a struct you scan from a row or marshal to JSON without writing conversion code. Which representation the driver picks up depends on your column type, so check the round trip against your actual schema rather than assuming.
There is also a command-line tool for generating and inspecting values. The README gives this install command, which requires a Go build environment.
go install github.com/segmentio/ksuid/cmd/ksuidAfter that, ksuid prints one identifier, and ksuid -n 4 prints four, as the README's examples show. The inspect subcommand is the useful one when something looks wrong: the README's example runs ksuid -f inspect 0ujtsYcgvSTl8PAuAdqWYSMnLOv and prints the String and Raw representations followed by a breakdown of the components.
Sequences, append, and the allocation-sensitive API
The README is explicit that the library targets performance-critical code paths and that the KSUID type is derived from a fixed-size array, which removes the reference chasing and allocation that a variable-width type would introduce. Two API details follow from that goal.
The Append method parses a text representation and replaces the contents of an existing KSUID value without additional heap allocation. If you are decoding identifiers in a hot loop into a reused variable, that is the method to reach for. The README does not show a worked example of Append, so read the GoDoc before adopting it.
The Sequence type exists to reduce contention. Package-level generation is protected by a global mutex, which is fine at moderate rates but becomes a serialization point when one goroutine generates a large number of identifiers. Sequence is the documented answer for that case. The README does not state what Sequence guarantees about ordering, so do not infer monotonicity from the name.
The third knob is the random source. By default the library uses a cryptographically secure PRNG. FastRander swaps in the standard PRNG seeded from the secure one. The README's own note is the important part: it says there is no evidence FastRander increases collision probability, but it should not be used where uniqueness matters to security, because generated IDs become more predictable. Treat that as a boundary, not a tuning option. If identifiers appear in URLs, password reset links, or anything a user could enumerate, leave the default in place.
Where KSUIDs are the wrong tool
The ordering guarantee is weaker than the marketing shorthand suggests. It depends on wall clocks, and the timestamp has one-second resolution. On a machine whose clock steps backwards, or across two machines whose clocks disagree, identifiers generated later can sort earlier. The README acknowledges the wall-clock dependency directly. If you need a total order that matches causal order, this format does not give you one, and no amount of sorting fixes it.
The second limitation is the text length. At 27 characters a KSUID is shorter than a 36-character hyphenated UUID, but it is still long enough to be awkward in a URL path segment or a user-facing reference number. If your identifiers are read aloud or typed by people, a shorter scheme is easier, and the README offers no short form.
The third is language reach. This repository is a Go library and a Go command-line tool. The README presents it as the reference implementation of the format, which implies other implementations exist elsewhere, but this project does not ship them. If your identifier generation happens in a Python service, a JVM service, or a browser, this repository is not the thing you install, and the format's cross-language value depends on those other implementations agreeing byte for byte.
Finally, a KSUID is not a secret. The timestamp is in the clear and the payload is random, so the identifier leaks roughly when the row was created. If creation time is sensitive, that leak is a design cost you are choosing.
KSUID against UUIDv4 and UUIDv7
The honest comparison is with UUIDv4, which the README treats as the baseline. UUIDv4 is 128 bits of randomness with no timestamp, so it sorts randomly. A KSUID adds a 32-bit timestamp in front of 128 random bits, which is why the README can claim more entropy than UUIDv4 while also being sortable. The cost is four extra bytes and a non-standard text format: 20 bytes and 27 characters instead of 16 and 36. If every consumer of your identifiers assumes the 8-4-4-4-12 hyphenated shape, a KSUID will break them, and the README does not offer a UUID-compatible rendering.
UUIDv7 is the closer comparison, because it also places a timestamp in the leading bits and is therefore also sortable. The README does not describe UUIDv7's bit layout, so the only defensible statement is directional: UUIDv7 keeps the 128-bit UUID envelope and its hyphenated text form, while a KSUID uses 20 bytes and a 27-character base62 string with no delimiters. Which matters more depends on whether your ecosystem already speaks UUID. If it does, staying inside the UUID envelope avoids a conversion layer; if it does not, the base62 form is friendlier to copy and paste.
Against Snowflake-style IDs, the difference is coordination. Snowflake-derived schemes fit into 64 bits and therefore need worker or machine identifiers to stay unique, which the README describes as significantly increasing deployment complexity. A KSUID avoids that entirely by spending 20 bytes. If you are already running a coordination service for other reasons, the trade looks different than if you would be adding one just for ID generation.
Maintenance, licence, and what upgrading costs
The repository is not archived, and the last push was on 2026-06-25. That is roughly three months before the date of this writing, which is recent enough that the project has not gone quiet, but there is no release history, so there is no version number to pin against and no changelog to read for breaking changes. Anyone adopting this should record the commit they build against.
The go.mod declares go 1.12. That is a low floor, which is good news for older codebases and a signal about how much the module has moved since. It also means the module does not use newer Go features in its own build configuration.
The licence is MIT, per the repository metadata and the LICENSE.md file at the top level. MIT is permissive: it allows use, modification, and redistribution with the licence text retained, and it disclaims warranty. That is the general shape of the licence, not legal advice, and the actual obligations depend on how you distribute the software. The practical implication for a library like this is that embedding it in a closed-source service is normally unproblematic, but the copyright notice has to travel with any redistribution of source or binaries.
Upgrade cost is low by construction. The public surface is small: generation, parsing, a sequence type, a set type, and the standard library interfaces. There is no runtime, no daemon, no schema. The main upgrade risk is not the library but the identifiers themselves. Once KSUIDs are in a database, changing the identifier format is a migration, not a dependency bump, so the decision to adopt is closer to a schema decision than a package decision.
Editorial conclusion
Adopt segmentio/ksuid when your identifiers are generated in Go, you want them to sort by creation time in any text store, and you are comfortable with 27-character strings. Do not adopt it when your stack is not Go and you want a maintained client for your language, when you need the identifier to carry no timestamp, or when you need strict monotonicity inside a single second. Before committing, verify three things against your own code: that the 20-byte raw form fits your column type, that nothing downstream assumes a UUID-shaped string, and that you are not relying on the FastRander shortcut in a path where identifiers must be unguessable. The ksuid -f inspect command is the fastest way to see what the library actually encoded.
Frequently asked questions
What is the difference between a KSUID and a UUID?
A KSUID is 20 bytes: a 32-bit big-endian UTC timestamp plus a 128-bit random payload, rendered as 27 base62 characters. A UUIDv4 is 128 bits of randomness with no timestamp, rendered as 36 hyphenated characters, so it does not sort by creation time.
Are UUIDs sortable?
UUIDv4 is not, because it has no timestamp component. A KSUID is loosely sortable by generation time, though the README says this is not a strong guarantee because it depends on wall clocks.
How can I generate a unique ID?
In Go, install the module with go get -u github.com/segmentio/ksuid and call ksuid.New(). The README states that the public package level pure functions are concurrency-safe, protected by a global mutex.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/segmentio-ksuid)