Skip to content

Instantly share code, notes, and snippets.

@jordanorelli
Created June 23, 2012 18:32
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jordanorelli/2979347 to your computer and use it in GitHub Desktop.
Save jordanorelli/2979347 to your computer and use it in GitHub Desktop.
fast unique id generation in Go
package main
import (
"crypto/md5"
"encoding/binary"
"fmt"
"os"
"sync/atomic"
"time"
)
// RequestId is used for tagging each incoming http request for logging
// purposes. The actual implementation is just the ObjectId implementation
// found in launchpad.net/mgo/bson. This will most likely change and evolve
// into its own format.
type RequestId string
func (id RequestId) String() string {
return fmt.Sprintf("%x", string(id))
}
// Time returns the timestamp part of the id.
// It's a runtime error to call this method with an invalid id.
func (id RequestId) Time() time.Time {
secs := int64(binary.BigEndian.Uint32(id.byteSlice(0, 4)))
return time.Unix(secs, 0)
}
// byteSlice returns byte slice of id from start to end.
// Calling this function with an invalid id will cause a runtime panic.
func (id RequestId) byteSlice(start, end int) []byte {
if len(id) != 12 {
panic(fmt.Sprintf("Invalid RequestId: %q", string(id)))
}
return []byte(string(id)[start:end])
}
// requestIdCounter is atomically incremented when generating a new ObjectId
// using NewObjectId() function. It's used as a counter part of an id.
var requestIdCounter uint32 = 0
// machineId stores machine id generated once and used in subsequent calls
// to NewObjectId function.
var machineId []byte
// initMachineId generates machine id and puts it into the machineId global
// variable. If this function fails to get the hostname, it will cause
// a runtime error.
func initMachineId() {
var sum [3]byte
hostname, err := os.Hostname()
if err != nil {
panic("Failed to get hostname: " + err.Error())
}
hw := md5.New()
hw.Write([]byte(hostname))
copy(sum[:3], hw.Sum(nil))
machineId = sum[:]
}
// NewObjectId returns a new unique ObjectId.
// This function causes a runtime error if it fails to get the hostname
// of the current machine.
func newRequestId() RequestId {
b := make([]byte, 12)
// Timestamp, 4 bytes, big endian
binary.BigEndian.PutUint32(b, uint32(time.Now().Unix()))
// Machine, first 3 bytes of md5(hostname)
if machineId == nil {
initMachineId()
}
b[4] = machineId[0]
b[5] = machineId[1]
b[6] = machineId[2]
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
pid := os.Getpid()
b[7] = byte(pid >> 8)
b[8] = byte(pid)
// Increment, 3 bytes, big endian
i := atomic.AddUint32(&requestIdCounter, 1)
b[9] = byte(i >> 16)
b[10] = byte(i >> 8)
b[11] = byte(i)
return RequestId(b)
}
func main() {
for {
fmt.Println(newRequestId())
time.Sleep(1e6)
}
}
@jordanorelli
Copy link
Author

the source for this is extracted from the mongo driver mgo; it is not my own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment