XID: The GUID Alternative

2 min read Tweet this post

XID is a globally unique identifier (GUID) alternative written in Go. It is based on the Mongo Object ID algorithm and is optimized for high performance and low storage overhead. In this article, we will explore XID and its benefits over traditional GUIDs.

XID is a 12-byte binary data type that consists of:

  • a 4-byte timestamp (seconds since epoch),
  • a 3-byte machine identifier,
  • a 2-byte process identifier, and
  • a 3-byte counter.

XID is designed to be globally unique, sortable, and usable as a shard key. It is also optimized for low storage overhead and high performance.

  • Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
  • Base32 hex encoded by default (20 chars when transported as printable string, still sortable)
  • Non configured, you don’t need set a unique machine and/or data center id
  • K-ordered
  • Embedded time with 1 second precision
  • Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process
  • Lock-free (i.e.: unlike UUIDv1 and v2)

GUIDs are widely used for unique identification in distributed systems. However, they have some drawbacks:

  1. GUIDs are usually 16 bytes in length, which can take up a lot of storage space.
  2. Generating GUIDs requires a lot of computation, which can slow down the system.
  3. GUIDs are not sortable, which can make indexing difficult.

XID addresses these issues by using a compact binary format and by optimizing the algorithm for performance. It is also sortable, which makes indexing and querying more efficient.

Using XID in Go is very simple. First, you need to install the XID package:

go get github.com/rs/xid

Next, you can use XID to generate a new ID:

package main

import (
	"fmt"

	"github.com/rs/xid"
)

func main() {
	guid := xid.New()

	println(guid.String())
  // 9bsv0s37pdv002seao8g

	fmt.Printf("machine:%v\npid:%v\ntime:%v\ncounter:%v",
    guid.Machine(),
		guid.Pid(),
		guid.Time(),
		guid.Counter(),
	)

  // machine:[103 203 126]
  // pid:11
  // time:2009-11-10 23:00:00 +0000 UTC
  // counter:9328145
}

Try it yourself https://go.dev/play/p/AvPz0TRyMe5

The String() method returns the ID as a string. You can also get the ID as a byte slice using the Bytes() method:

id := xid.New()
fmt.Println(id.Bytes())

Benchmark against Go Maxim Bublis’s UUID.

BenchmarkXID        20000000        91.1 ns/op      32 B/op       1 allocs/op
BenchmarkXID-2      20000000        55.9 ns/op      32 B/op       1 allocs/op
BenchmarkXID-4      50000000        32.3 ns/op      32 B/op       1 allocs/op
BenchmarkUUIDv1     10000000       204 ns/op      48 B/op       1 allocs/op
BenchmarkUUIDv1-2   10000000       160 ns/op      48 B/op       1 allocs/op
BenchmarkUUIDv1-4   10000000       195 ns/op      48 B/op       1 allocs/op
BenchmarkUUIDv4      1000000      1503 ns/op      64 B/op       2 allocs/op
BenchmarkUUIDv4-2    1000000      1427 ns/op      64 B/op       2 allocs/op
BenchmarkUUIDv4-4    1000000      1452 ns/op      64 B/op       2 allocs/op

Note: UUIDv1 requires a global lock, hence the performance degradation as we add more CPUs.

XID is a great alternative to traditional GUIDs. It is designed to be globally unique, sortable, and optimized for performance and low storage overhead. If you are building a distributed system, XID is definitely worth considering as an ID generator.

practical go