hexid

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2025 License: MIT Imports: 15 Imported by: 2

README

hexid

Zero-dependency, zero-allocation, time-sortable, random-looking 63-bit IDs for Go. ~40ns per ID.

hexid is a compact, deterministic ID system that produces 63-bit identifiers safe for PostgreSQL BIGINT.
IDs are chronologically sortable, distributed-safe, and allocation-free (except when converting to hex strings).
All encoding and decoding are fully deterministic between Go and PostgreSQL.


🚀 Key Features

  • 🪶 Zero dependencies: Pure Go — no third-party packages.
  • ⚡ Zero allocations: Except when encoding to a new hex string.
  • 🐘 Compact & efficient: 63-bit IDs fit safely in Postgres BIGINT.
  • ⏱️ Time-sortable: Encodes seconds + milliseconds for chronological order.
  • 🌍 Distribution-safe: 6-bit node field (up to 63 nodes).
  • 💥 High throughput: ~25 million IDs/s per node (~40 ns per ID).
  • 🧠 Deterministic: Identical encoding and decoding in Go and PostgreSQL.
  • 🔒 Hash mode: Deterministic HashedID() for stable, non-time-based IDs.

📦 Installation

go get github.com/webmafia/hexid

Import and use:

import "github.com/webmafia/hexid"

🧩 ID Layout

 63 62                            31 30      21 20  15 14            0
┌──┬────────────────────────────────┬──────────┬──────┬───────────────┐
│X │ 32 bits unix seconds           │ 10 bits  │ 6 b  │ 15 bits       │
│  │                                │ ms       │ node │ sequence      │
└──┴────────────────────────────────┴──────────┴──────┴───────────────┘
X = unused (sign bit of int64)
Field Bits Range Purpose
Seconds 32 0 – 4,294,967,295 Valid until year 2106
Milliseconds 10 0 – 999 Sub-second precision
Node 6 1 – 63 Up to 63 generator nodes (0 is reserved for hashed IDs)
Sequence 15 0 – 32 767 Per-ms per-node counter
Total 63 bits < 2⁶³ Safe in signed BIGINT

🧰 Usage

1. Global generator (thread-safe)
id := hexid.Generate()
fmt.Println(id.String()) // scrambled 16-char hex string

id2 := hexid.IDFromTime(time.Now())
2. Local generator (faster, not thread-safe)
g, _ := hexid.NewGenerator(5) // node ID 5
id := g.ID()
3. Thread-safe generator
g, _ := hexid.NewAtomicGenerator(12)
id := g.ID()
4. Deterministic (non-time) hashed IDs
h1 := hexid.HashedID("user", "42")
h2 := hexid.HashedIDBytes([]byte("my-unique-key"))

Hashed IDs always have Node() == 0 and a zero timestamp.


🧩 ID Accessors

Method Description
id.Unix() Extract unix seconds.
id.Millis() Milliseconds within the second (0–999).
id.Node() Node ID (0–63).
id.Seq() Sequence number (0–32 767).
id.Time() Reconstruct creation time.
id.String() Scrambled 16-character hex encoding.
IDFromString(str) Decode from hex string.
id.Bytes() 8-byte big-endian binary form.

🐘 Encoding/decoding from PostgreSQL

Matching SQL functions for direct database use:

CREATE OR REPLACE FUNCTION hexid_encode(id bigint)
RETURNS text AS $$
  SELECT lpad(
    to_hex(
      ((id::numeric * 7993060983890856527)
       % 9223372036854775808)::bigint
    ), 16, '0');
$$ LANGUAGE sql IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION hexid_decode(hexid text)
RETURNS bigint AS $$
  SELECT (
    (('x' || hexid)::bit(64)::bigint::numeric *
     3418993122468531375) % 9223372036854775808
  )::bigint;
$$ LANGUAGE sql IMMUTABLE STRICT;

These produce and decode exactly the same hex values as Go’s String() / IDFromString().


🧬 Collisions and ID Uniqueness

Generating an ID takes ~40 ns on a modern CPU thread (~25 000 IDs/ms). Each ID includes a millisecond timestamp and a 15-bit sequence counter (max = 32 767). IDs are guaranteed unique as long as:

  • the generator’s node ID is unique, and
  • the generation rate does not exceed ~32 767 IDs/ms (~30 ns per ID), preventing sequence overflow within a single millisecond.

⚡ Benchmark

goos: darwin
goarch: arm64
pkg: github.com/webmafia/hexid
cpu: Apple M1 Pro
BenchmarkGenerator/New-10                   176147698             6.669 ns/op           0 B/op           0 allocs/op
BenchmarkGenerator/ID-10                     28672035            42.550 ns/op           0 B/op           0 allocs/op
BenchmarkGenerator/IDFromTime-10            557195217             2.162 ns/op           0 B/op           0 allocs/op
BenchmarkAtomicGenerator/New-10             180172480             6.674 ns/op           0 B/op           0 allocs/op
BenchmarkAtomicGenerator/ID-10               29088128            44.700 ns/op           0 B/op           0 allocs/op
BenchmarkAtomicGenerator/IDFromTime-10      173841322             6.885 ns/op           0 B/op           0 allocs/op
BenchmarkHashedID-10                        248941737             4.733 ns/op           0 B/op           0 allocs/op

⚖️ License

MIT © 2025 The Web Mafia, Ltd.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetValuerType added in v0.3.0

func SetValuerType(typ valuer.Type) error

Types

type AtomicGenerator

type AtomicGenerator struct {
	// contains filtered or unexported fields
}

Thread-safe ID generator. Can generate up to 2^15 (32,768) locally unique IDs per millisecond per node.

func NewAtomicGenerator

func NewAtomicGenerator(node ...uint8) (g AtomicGenerator, err error)

Create an atomic ID generator. The generator is thread-safe.

func (*AtomicGenerator) ID

func (g *AtomicGenerator) ID() ID

func (*AtomicGenerator) IDFromTime

func (g *AtomicGenerator) IDFromTime(ts time.Time) ID

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Non-thread-safe ID generator. Can generate up to 2^15 (32,768) locally unique IDs per millisecond per node.

Example
g, err := NewGenerator()

if err != nil {
	panic(err)
}

// Ensure a deterministic sequence in this example
g.seq = 1

ts := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)

for range 4 {
	id := g.IDFromTime(ts)
	fmt.Println(id)
}
Output:

58c1fa4a4a00ca4f
c7af08e7eeda149e
369c178593b35eed
a5892623388ca93c

func NewGenerator

func NewGenerator(node ...uint8) (g Generator, err error)

Create an ID generator. The generator is NOT thread-safe.

func (*Generator) ID

func (g *Generator) ID() (id ID)

func (*Generator) IDFromTime

func (g *Generator) IDFromTime(ts time.Time) (id ID)

type ID

type ID uint64

func Generate

func Generate() ID

Atomically generates the next ID based on current time. Thread-safe.

func HashedID

func HashedID(s ...string) ID

HashedID produces a deterministic 63-bit ID from one or more strings. The resulting ID is based on an FNV-1a hash and always has node ID = 0. The timestamp portion is meaningless but guaranteed not to collide with time-based IDs, since those always have node ≥ 1.

Example
a := HashedID("foobar")
b := HashedID("foobaz")

fmt.Println(a.Hashed(), a)
fmt.Println(b.Hashed(), b)
Output:

true 45ecc9eb54b12098
true 951ba2f26ae6feb0

func HashedIDBytes

func HashedIDBytes(b []byte) ID

HashedIDBytes produces a deterministic 63-bit ID from a byte slice. The resulting ID always has node ID = 0.

func IDFromEntropy added in v1.2.0

func IDFromEntropy(unix, entropy uint32) ID

Reassambles an ID from `(ID).Unix()` and `(ID).Entropy()`.

Example
id1, _ := IDFromString("24a3b3372e1a0a50")
id2 := IDFromEntropy(id1.Unix(), id1.Entropy())

fmt.Println(id1)
fmt.Println(id2)
Output:
24a3b3372e1a0a50
24a3b3372e1a0a50

func IDFromString

func IDFromString(str string) (id ID, err error)

func IDFromTime

func IDFromTime(ts time.Time) ID

Atomically generates the next ID based on provided timestamp. Thread-safe.

func (ID) AppendBinary

func (id ID) AppendBinary(b []byte) ([]byte, error)

AppendBinary implements internal.BinaryAppender.

func (ID) AppendText

func (id ID) AppendText(b []byte) ([]byte, error)

AppendBinary implements internal.TextAppender.

func (ID) Bytes added in v0.3.0

func (id ID) Bytes() []byte

Returns raw representation of the ID as 8 big-endian bytes.

func (ID) Entropy added in v1.1.0

func (id ID) Entropy() uint32

Entropy returns everything after the Unix timestamp seconds (milliseconds + node + sequence)

func (ID) Hashed added in v1.0.0

func (id ID) Hashed() bool

func (ID) Int64 added in v1.0.0

func (id ID) Int64() int64

Int64 returns the raw numeric value of the ID.

func (ID) IsNil added in v0.1.0

func (id ID) IsNil() bool

IsNil is equivalent to IsZero.

func (ID) IsZero added in v0.1.0

func (id ID) IsZero() bool

IsZero reports whether the ID is zero.

func (ID) MarshalJSON

func (id ID) MarshalJSON() (b []byte, err error)

MarshalJSON implements json.Marshaler.

func (ID) MarshalText added in v0.2.5

func (id ID) MarshalText() (text []byte, err error)

MarshalText implements encoding.TextMarshaler.

func (ID) Millis added in v1.0.0

func (id ID) Millis() uint16

Millis returns the millisecond part within the second.

func (ID) Node added in v1.0.0

func (id ID) Node() uint8

Node returns the 6-bit node ID.

func (*ID) Scan added in v0.2.0

func (id *ID) Scan(src any) (err error)

Scan implements sql.Scanner.

func (ID) Seq

func (id ID) Seq() uint16

Seq returns the 15-bit sequence number.

func (ID) String

func (id ID) String() string

func (ID) Time

func (id ID) Time() time.Time

Time reconstructs the approximate creation time of the ID.

func (ID) Uint64

func (id ID) Uint64() uint64

Uint64 returns the raw numeric value of the ID.

func (ID) Unix

func (id ID) Unix() uint32

Unix returns the Unix timestamp in seconds.

func (*ID) UnmarshalJSON

func (id *ID) UnmarshalJSON(b []byte) (err error)

UnmarshalJSON implements json.Unmarshaler.

func (*ID) UnmarshalText added in v0.2.5

func (id *ID) UnmarshalText(text []byte) (err error)

UnmarshalText implements encoding.TextUnmarshaler.

func (ID) Value added in v0.2.0

func (id ID) Value() (driver.Value, error)

Value implements driver.Valuer.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL