monotime

package module
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Nov 27, 2025 License: Apache-2.0 Imports: 8 Imported by: 0

README

monotime

GoDoc License

Strictly monotonic, lock-free generators for time-based identifiers.

Generators guarantee strict monotonicity even when:

  • System clock is misconfigured
  • Time is adjusted manually
  • NTP steps the clock backward
  • Process restarts (with a previously stored value)

This is accomplished by switching between real timestamps and logical clock whenever the system clock falls behind.


1. Gen

Produces raw int64 nanosecond timestamps. Fast and simple way to get unique and ordered timestamps.

// Initialize with the last known timestamp from your DB (or 0 for a fresh start)
mono := monotime.NewGen(lastKnownTimestamp)

// Get the next timestamp
ts1 := mono.Next() // e.g., 1731627315000000001
ts2 := mono.Next() // e.g., 1731627315000000002
Behavior
  • If the system time is greater than the last emitted ID, generator uses the real timestamp.
  • If the system clock ever moves backwards or stops, generator switches into logical mode, incrementing the nanosecond counter until real time catches up.
  • When real time becomes greater again, generator resumes using real timestamps.

2. GenUUID

Produces K-sortable, time-ordered UUID v7 identifiers.
It embeds the monotonic nanosecond timestamp directly into the UUID structure and requires a node ID (24-bit) to ensure uniqueness across different services or cluster nodes.

This implementation also includes a custom magic prefix (0xE355) and zeroes out the rand_a portion, allowing to parse and verify that a UUID was generated by this package.

// Initialize with a unique node ID and the last known UUID from your DB
// (or monotime.ZeroUUID for a fresh start)
mono, err := monotime.NewGenUUID(nodeID, lastKnownUUID)

// Get the next UUID
id1 := mono.Next() // e.g., 0124e053-3580-7000-8e35-500000100001
id2 := mono.Next() // e.g., 0124e053-3580-7000-8e35-500000100002
Behavior

UUID v7 is time-ordered by design, but it normally does not guarantee strict monotonicity.
This implementation enforces it:

  • For each generated UUID, the timestamp component is checked against the last emitted UUID.
  • If the clock goes backward or stops, generator switches to logical timestamp increments.
  • The nanosecond counter is incremented for each ID, ensuring uniqueness within the same millisecond.
  • The generator never emits a UUID with a timestamp earlier than the previous one.

Running ahead of the clock

The generators must produce strictly increasing IDs.
This priority is more important than matching the system clock.

The core logic is: next = max(now, old + 1)

This means the generator's internal timestamp (last) can "run ahead" of the real system clock (now) in two specific scenarios:

  1. Clock rollback: If the system clock moves backward (e.g., NTP update), now will be less than old.
    The generator must choose old + 1 to maintain monotonicity.

  2. High throughput: If Next() is called in a very tight loop (e.g., millions of times per second), now might be equal to old (or time.Now() hasn't ticked forward).
    The generator must choose old + 1 to guarantee uniqueness.

In both cases, the generator starts issuing "logical" timestamps that are ahead of real time.
This is the correct and intended behavior.

Once the system clock eventually catches up (i.e., now becomes greater than the generator's last value), the generator will automatically switch back to using now as the base.

Impact on Generators
  • Gen: the returned timestamp might be "in the future" compared to time.Now().UnixNano().

  • GenUUID: the 48-bit millisecond part of the UUID will simply roll forward as the internal nanosecond counter increments. For example, if the counter is at ...999_999 (last nanosecond of a millisecond) and Next() is called, the new value will be ...000_000 and the millisecond portion of the UUID will be incremented by one.


Benchmarks
goos: linux
goarch: amd64
pkg: github.com/vapstack/monotime
cpu: AMD Ryzen 9 5900HX with Radeon Graphics
BenchmarkGenNext-16                     26904223       44.18 ns/op       0 B/op     0 allocs/op
BenchmarkGenNextParallel-16              5724068      208.6 ns/op        0 B/op     0 allocs/op
BenchmarkGenUUIDNext-16                 21900927       53.21 ns/op       0 B/op     0 allocs/op
BenchmarkGenUUIDNextParallel-16          5640699      214.6 ns/op        0 B/op     0 allocs/op
BenchmarkUUIDString-16                  33152881       31.97 ns/op      48 B/op     1 allocs/op
BenchmarkUUIDMarshalText-16             34586162       31.90 ns/op      48 B/op     1 allocs/op
BenchmarkUUIDParse-16                   130833162       9.132 ns/op      0 B/op     0 allocs/op
BenchmarkUUIDUnmarshalText-16           43163187       26.48 ns/op       0 B/op     0 allocs/op
BenchmarkUUIDUnmarshalBinary-16         147303606       8.138 ns/op      0 B/op     0 allocs/op
BenchmarkUUIDMarshalBinary-16           60735979       17.62 ns/op      16 B/op     1 allocs/op
BenchmarkTimeNow-16                     29152137       40.37 ns/op

Documentation

Overview

Package monotime provides fast, lock-free, and strictly monotonic time and UUID v7 generators.

These generators are guaranteed to never go backward, even in the event of system clock adjustments or NTP jumps, ensuring strict monotonicity and uniqueness.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Gen added in v0.1.1

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

Gen provides a simple, lock-free, and strictly monotonic time source that generates nanosecond timestamps.

func NewGen added in v0.1.1

func NewGen(lastKnown int64) *Gen

NewGen creates a new monotonic timestamp generator. The generator guarantees strictly monotonic, ever-increasing values, even if the system clock moves backwards or stops. If lastKnown is greater than the current system time, the generator continues from it.

func (*Gen) Next added in v0.1.1

func (g *Gen) Next() int64

Next returns the next strictly monotonic nanosecond timestamp. The returned value will always be greater than any previously returned value from this generator. Next never blocks, even if the system clock moves backwards or stops; it atomically increments the last timestamp by 1. Next is lock-free and safe to use from multiple goroutines.

type GenUUID added in v0.1.1

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

GenUUID generates strictly monotonic, K-sortable UUID v7 identifiers.

The generator is lock-free and guarantees that each generated UUID is strictly greater than the previous one generated by the same node ID. It is resilient to system clock rollbacks or standstills (e.g., NTP adjustments), in which case it will increment the nanosecond counter logically to ensure monotonicity and uniqueness.

The generated UUIDs contain a custom magic prefix and zeroed rand_a field to distinguish them from standard UUID v7 implementations.

func NewGenUUID added in v0.1.1

func NewGenUUID(nodeID int, lastKnown UUID) (*GenUUID, error)

NewGenUUID creates a new monotime UUID generator bound to the given node ID.

If lastKnown is ZeroUUID, the generator starts from the current system time. Otherwise, lastKnown must be a valid monotime UUID previously produced by the same node; its timestamp is used to ensure monotonicity.

Returns an error if:

  • nodeID is zero or outside the 24-bit range,
  • lastKnown is not a valid monotime UUID,
  • lastKnown was generated by a different node.

func (*GenUUID) Next added in v0.1.1

func (g *GenUUID) Next() UUID

Next generates the next strictly monotonic monotime UUID.

The returned UUID is always greater than any previously generated UUID, even if the system clock jumps backwards or stops.

Next is lock-free and safe to use from multiple goroutines.

type UUID

type UUID [16]byte

UUID represents a 16-byte UUID v7 value following RFC 9562 and supporting the custom "monotime" extension format.

A monotime UUID embeds:

  • a 48-bit timestamp in milliseconds,
  • a 12-bit zeroed rand_a field (used as signature),
  • a 62-bit custom rand_b field containing a fixed prefix, node ID (24 bits) and a 20-bit nanosecond remainder.

This layout allows round-tripping back to the original time with full nanosecond precision and provides detection of monotime-encoded UUIDs.

var ZeroUUID UUID

ZeroUUID represents an empty (zero) monotime UUID value which indicates that no prior UUID is known when initializing a generator.

func MinUUID added in v0.3.2

func MinUUID(nodeID int, t time.Time) (id UUID)

MinUUID returns a bare UUID containing only the millisecond portion. Primary use is to provide ranges for database scans.

func (UUID) MarshalBinary

func (u UUID) MarshalBinary() ([]byte, error)

MarshalBinary implements encoding.BinaryMarshaler and returns a copy of the raw 16-byte UUID value.

func (UUID) MarshalText

func (u UUID) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler. It returns the canonical 36-byte string representation of the UUID.

func (UUID) NodeID

func (u UUID) NodeID() int

NodeID returns the node ID embedded in the UUID. It returns 0 if the UUID is not a valid monotime UUID.

func (UUID) Parse

func (u UUID) Parse() (time.Time, int, bool)

Parse extracts time, node ID, and a flag indicating whether the UUID conforms to the monotime UUID format.

If the UUID is not a valid monotime UUID (incorrect version, variant, signature bits, or prefix), flag will be false. Otherwise, it returns the full timestamp reconstructed with nanosecond precision and the encoded node ID.

func (*UUID) Scan

func (u *UUID) Scan(src any) error

Scan implements sql.Scanner and loads a UUID from either binary or text SQL column types. It accepts:

  • string (text form),
  • []byte (binary form or text form),

and returns an error for unsupported types.

func (UUID) String

func (u UUID) String() string

String implements fmt.Stringer and returns the canonical string representation of the UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).

func (UUID) Time

func (u UUID) Time() time.Time

Time returns the timestamp embedded in the UUID. If the UUID is not a valid monotime UUID, it returns zero time. This method preserves full nanosecond precision reconstructed from the custom rand_b field.

func (*UUID) UnmarshalBinary

func (u *UUID) UnmarshalBinary(data []byte) error

UnmarshalBinary implements encoding.BinaryUnmarshaler and loads the UUID from a 16-byte binary slice.

func (*UUID) UnmarshalText

func (u *UUID) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler and parses a UUID from its canonical 36-character string form.

func (UUID) Valid added in v0.1.1

func (u UUID) Valid() bool

Valid returns true if the UUID contains a valid time and node ID.

func (UUID) Value

func (u UUID) Value() (driver.Value, error)

Value implements sql.Valuer and returns the UUID as a string suitable for storing in SQL databases.

Jump to

Keyboard shortcuts

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