Documentation ¶
Overview ¶
Package sno provides fast generators of compact, sortable, unique IDs with embedded metadata.
Index ¶
- Constants
- func Sort(s []ID)
- type Generator
- func (g *Generator) Cap() int
- func (g *Generator) Len() int
- func (g *Generator) New(meta byte) (id ID)
- func (g *Generator) NewWithTime(meta byte, t time.Time) (id ID)
- func (g *Generator) Partition() Partition
- func (g *Generator) Sequence() uint32
- func (g *Generator) SequenceMax() uint16
- func (g *Generator) SequenceMin() uint16
- func (g *Generator) Snapshot() GeneratorSnapshot
- type GeneratorSnapshot
- type ID
- func (id ID) Bytes() []byte
- func (id ID) Compare(that ID) int
- func (id ID) IsZero() bool
- func (id ID) MarshalBinary() ([]byte, error)
- func (id ID) MarshalJSON() ([]byte, error)
- func (id ID) MarshalText() ([]byte, error)
- func (id ID) Meta() byte
- func (id ID) Partition() Partition
- func (id *ID) Scan(value interface{}) error
- func (id ID) Sequence() uint16
- func (id ID) String() string
- func (id ID) Time() time.Time
- func (id ID) Timestamp() int64
- func (id *ID) UnmarshalBinary(src []byte) error
- func (id *ID) UnmarshalJSON(src []byte) error
- func (id *ID) UnmarshalText(src []byte) error
- func (id ID) Value() (driver.Value, error)
- type InvalidDataSizeError
- type InvalidSequenceBoundsError
- type InvalidTypeError
- type Partition
- type PartitionPoolExhaustedError
- type SequenceOverflowNotification
Constants ¶
const ( // SizeBinary is the length of an ID in its binary array representation. SizeBinary = 10 // SizeEncoded is the length of an ID in its canonical base-32 encoded representation. SizeEncoded = 16 // Epoch is the offset to the Unix epoch, in seconds, that ID timestamps are embedded with. // Corresponds to 2010-01-01 00:00:00 UTC. Epoch = 1262304000 // TimeUnit is the time unit timestamps are embedded with - 4msec, as expressed in nanoseconds. TimeUnit = 4e6 // MaxTimestamp is the max number of time units that can be embedded in an ID's timestamp. // Corresponds to 2079-09-07 15:47:35.548 UTC in our custom epoch. MaxTimestamp = 1<<39 - 1 // MaxPartition is the max Partition number when represented as a uint16. // It equals max uint16 (65535) and is the equivalent of Partition{255, 255}. MaxPartition = 1<<16 - 1 // MaxSequence is the max sequence number supported by generators. As bounds can be set // individually - this is the upper cap and equals max uint16 (65535). MaxSequence = 1<<16 - 1 )
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Generator ¶
type Generator struct {
// contains filtered or unexported fields
}
Generator is responsible for generating new IDs scoped to a given fixed Partition and managing their sequence.
A Generator must be constructed using NewGenerator - the zero value of a Generator is an unusable state.
A Generator must not be copied after first use.
func NewGenerator ¶
func NewGenerator(snapshot *GeneratorSnapshot, c chan<- *SequenceOverflowNotification) (*Generator, error)
NewGenerator returns a new generator based on the optional Snapshot.
func (*Generator) Cap ¶
Cap returns the total capacity of the Generator.
To get its current capacity (e.g. number of possible additional IDs in the current timeframe), simply:
spare := generator.Cap() - generator.Len()
The result will always be non-negative.
func (*Generator) NewWithTime ¶
NewWithTime generates a new ID using the given time for the timestamp.
IDs generated with user-specified timestamps are exempt from the tick-tock mechanism and use a sequence separate from New() - one that is independent from time, as time provided to this method can be arbitrary. The sequence increases strictly monotonically up to hitting the generator's SequenceMax, after which it rolls over silently back to SequenceMin.
That means bounds are respected, but unlike New(), NewWithTime() will not block the caller when the (separate) sequence rolls over as the Generator would be unable to determine when to resume processing within the constraints of this method.
Managing potential collisions due to the arbitrary time is left to the user.
This utility is primarily meant to enable porting of old IDs to sno and assumed to be ran before an ID scheme goes online.
func (*Generator) Sequence ¶
Sequence returns the current sequence the Generator is at.
This does *not* mean that if one were to call New() right now, the generated ID will necessarily get this sequence, as other things may happen before.
If the next call to New() would result in a reset of the sequence, SequenceMin is returned instead of the current internal sequence.
If the generator is currently overflowing, the sequence returned will be higher than the generator's SequenceMax (thus a uint32 return type), meaning it can be used to determine the current overflow via:
overflow := int(uint32(generator.SequenceMax()) - generator.Sequence())
func (*Generator) SequenceMax ¶
SequenceMax returns the upper bound of the sequence pool of this generator.
func (*Generator) SequenceMin ¶
SequenceMin returns the lower bound of the sequence pool of this generator.
func (*Generator) Snapshot ¶
func (g *Generator) Snapshot() GeneratorSnapshot
Snapshot returns a copy of the Generator's current bookkeeping data.
type GeneratorSnapshot ¶
type GeneratorSnapshot struct { // The Partition the generator is scoped to. A zero value ({0, 0}) is valid and will be used. Partition Partition `json:"partition"` // Sequence pool bounds (inclusive). Can be given in either order - lower value will become lower bound. // When SequenceMax is 0 and SequenceMin != 65535, SequenceMax will be set to 65535. SequenceMin uint16 `json:"sequenceMin"` SequenceMax uint16 `json:"sequenceMax"` // Current sequence number. When 0, it will be set to SequenceMin. May overflow SequenceMax, // but not underflow SequenceMin. Sequence uint32 `json:"sequence"` Now int64 `json:"now"` // Wall time the snapshot was taken at in sno time units and in our epoch. WallHi int64 `json:"wallHi"` // WallSafe int64 `json:"wallSafe"` // Drifts uint32 `json:"drifts"` // Count of wall clock regressions the generator tick-tocked at. }
GeneratorSnapshot represents the bookkeeping data of a Generator at some point in time.
Snapshots serve both as configuration and a means of restoring generators across restarts, to ensure newly generated IDs don't overwrite IDs generated before going offline.
type ID ¶
type ID [SizeBinary]byte
ID is the binary representation of a sno ID.
It is comprised of 10 bytes in 2 blocks of 40 bits, with its components stored in big-endian order.
The timestamp:
39 bits - unsigned milliseconds since epoch with a 4msec resolution 1 bit - the tick-tock toggle
The payload:
8 bits - metabyte 16 bits - partition 16 bits - sequence
func FromBinaryBytes ¶
FromBinaryBytes takes a byte slice and copies its contents into an ID, returning the bytes as an ID.
The slice must have a length of 10. Returns a InvalidDataSizeError if it does not.
func FromEncodedBytes ¶
FromEncodedBytes decodes a canonically base32-encoded byte slice representation of an ID into its binary representation and returns it.
The slice must have a length of 16. Returns a InvalidDataSizeError if it does not.
func FromEncodedString ¶
FromEncodedString decodes a canonically base32-encoded string representation of an ID into its binary representation and returns it.
The string must have a length of 16. Returns a InvalidDataSizeError if it does not.
func New ¶
New uses the package-level generator to generate a new ID using the current system time for its timestamp.
func NewWithTime ¶
NewWithTime uses the package-level generator to generate a new ID using the given time for the timestamp.
IDs generated using this method are subject to several caveats. See generator.NewWithTime() for their documentation.
func Zero ¶
func Zero() ID
Zero returns the zero value of an ID, which is 10 zero bytes and equivalent to:
id := sno.ID{}
... e.g. ...
id := sno.ID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
func (ID) Compare ¶
Compare returns an integer comparing this and that ID lexicographically.
Returns:
0 - if this and that are equal, -1 - if this is smaller than that, 1 - if this is greater than that.
Note that IDs are byte arrays - if all you need is to check for equality, a simple...
if thisID == thatID {...}
... will do the trick.
func (ID) MarshalBinary ¶
MarshalBinary implements encoding.BinaryMarshaler by returning the ID as a byte slice.
func (ID) MarshalJSON ¶
MarshalJSON implements encoding.json.Marshaler by returning the base32-encoded and quoted representation of the ID as a byte slice.
If the ID is a zero value, MarshalJSON will return a byte slice containing 'null' (unquoted) instead.
Note that ID's are byte arrays and Go's std (un)marshaler is unable to distinguish the zero values of custom structs as "empty", so the 'omitempty' tag has the same caveats as, for example, time.Time.
See https://github.com/golang/go/issues/11939 for tracking purposes as changes are being discussed.
func (ID) MarshalText ¶
MarshalText implements encoding.TextMarshaler by returning the base32-encoded representation of the ID as a byte slice.
func (*ID) Scan ¶
Scan implements the sql.Scanner interface by attempting to convert the given value into an ID.
When given a byte slice:
- with a length of SizeBinary (10), its contents will be copied into ID.
- with a length of 0, ID will be set to a zero ID.
- with any other length, sets ID to a zero ID and returns InvalidDataSizeError.
When given a string:
- with a length of SizeEncoded (16), its contents will be decoded into ID.
- with a length of 0, ID will be set to a zero ID.
- with any other length, sets ID to a zero ID and returns InvalidDataSizeError.
When given nil, ID will be set to a zero ID.
When given any other type, returns a InvalidTypeError.
func (ID) String ¶
String implements fmt.Stringer by returning the base32-encoded representation of the ID as a string.
func (ID) Timestamp ¶
Timestamp returns the timestamp of the ID as nanoseconds relative to the Unix epoch.
func (*ID) UnmarshalBinary ¶
UnmarshalBinary implements encoding.BinaryUnmarshaler by copying src into the receiver.
func (*ID) UnmarshalJSON ¶
UnmarshalJSON implements encoding.json.Unmarshaler by decoding a base32-encoded and quoted representation of an ID from src into the receiver.
If the byte slice is an unquoted 'null', the receiving ID will instead be set to a zero ID.
func (*ID) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler by decoding a base32-encoded representation of the ID from src into the receiver.
func (ID) Value ¶
Value implements the sql.driver.Valuer interface by returning the ID as a byte slice. If you'd rather receive a string, wrapping an ID is a possible solution...
// stringedID wraps a sno ID to provide a driver.Valuer implementation which // returns strings. type stringedID sno.ID func (id stringedID) Value() (driver.Value, error) { return sno.ID(id).String(), nil } // ... and use it via: db.Exec(..., stringedID(id))
type InvalidDataSizeError ¶
type InvalidDataSizeError struct {
Size int
}
InvalidDataSizeError gets returned when attempting to unmarshal or decode an ID from data that is not nil and not of a size of: SizeBinary, SizeEncoded nor 0.
func (*InvalidDataSizeError) Error ¶
func (e *InvalidDataSizeError) Error() string
type InvalidSequenceBoundsError ¶
InvalidSequenceBoundsError gets returned when a Generator gets seeded with sequence boundaries which are invalid, e.g. the pool is too small or the current sequence overflows the bounds.
func (*InvalidSequenceBoundsError) Error ¶
func (e *InvalidSequenceBoundsError) Error() string
type InvalidTypeError ¶
type InvalidTypeError struct {
Value interface{}
}
InvalidTypeError gets returned when attempting to scan a value that is neither...
- a string
- a byte slice
- nil
... into an ID via ID.Scan().
func (*InvalidTypeError) Error ¶
func (e *InvalidTypeError) Error() string
type Partition ¶
type Partition [2]byte
Partition represents the fixed identifier of a Generator.
If you'd rather define Partitions as integers instead of as byte arrays, then:
var p sno.Partition p.PutUint16(65535)
type PartitionPoolExhaustedError ¶ added in v1.1.0
type PartitionPoolExhaustedError struct{}
PartitionPoolExhaustedError gets returned when attempting to create more than MaxPartition (65535) Generators using the default configuration (eg. without snapshots).
Should you ever run into this, please consult the docs on the genPartition() internal function.
func (*PartitionPoolExhaustedError) Error ¶ added in v1.1.0
func (e *PartitionPoolExhaustedError) Error() string
type SequenceOverflowNotification ¶
type SequenceOverflowNotification struct { Now time.Time // Time of tick. Count uint32 // Number of currently overflowing generation calls. Ticks uint32 // Total count of ticks while dealing with the *current* overflow. }
SequenceOverflowNotification contains information pertaining to the current state of a Generator while it is overflowing.