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