Project

snowflaked

0.0
A long-lived project that still receives updates
A Ruby gem for generating Twitter Snowflake IDs using a high-performance Rust backend. Thread-safe with configurable machine ID and custom epoch support.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 0.9
 Project Readme

Snowflaked

CI Gem Version Downloads License

A database-agnostic, high-performance, thread-safe Snowflake ID generator for Ruby, powered by Rust.

Snowflake IDs are 64-bit unique identifiers that encode a timestamp, machine ID, and sequence number. They're time-sortable (IDs created later are always larger), making them ideal for distributed systems where you need unique IDs without coordination between machines. Unlike UUIDs, Snowflake IDs are smaller, sortable, and index-friendly for databases.

Installation

Add to your Gemfile:

gem "snowflaked"

Quick Start

id = Snowflaked.id
# => 7193489234823847936

Rails Integration

All models automatically generate a Snowflake ID for the :id attribute:

class User < ApplicationRecord
end

User.create!
# => #<User id: 7193489234823847936>

You can also define additional Snowflake columns in migrations:

class CreateUsers < ActiveRecord::Migration[8.1]
  def change
    create_table :users do |t|
      t.snowflake :external_id
      t.bigint    :uid
    end
  end
end

Columns created with t.snowflake are automatically detected and will have Snowflake IDs generated for them.

Warning

SQLite does not support column comments, which Snowflaked uses to auto-detect snowflake columns other than :id. When using SQLite, you must explicitly declare snowflake columns using the snowflake_id helper in your model.

If you want to generate Snowflake IDs for additional columns, you can do so by using the snowflake_id method, without having to migrate the table:

class User < ApplicationRecord
  snowflake_id :uid
end

It is also possible to disable automatic :id generation by passing id: false to the snowflake_id method:

class Post < ApplicationRecord
  snowflake_id id: false
end

Or generate Snowflake IDs for other columns but not :id:

class Post < ApplicationRecord
  snowflake_id :external_id, id: false
end

Configuration

Snowflaked.configure do |config|
  config.machine_id = 42
  config.epoch = Time.utc(1989, 1, 3) # When not configured, the epoch is set to the Unix epoch (January 1, 1970)
end

Machine ID

Tip

For multi-process servers like Puma, it is recommended to not configure machine_id explicitly. The gem automatically calculates a unique machine ID using (hostname.hash ^ pid) % 1024, which ensures each forked worker process gets a different ID and avoids duplicate Snowflake IDs.

If you must set machine_id explicitly, use environment variables that differ per worker process.

Machine ID Resolution

If machine_id is not explicitly configured, it resolves in this order:

  1. SNOWFLAKED_MACHINE_ID environment variable
  2. MACHINE_ID environment variable
  3. Auto-detected using the following formula: (hostname.hash ^ pid) % 1024

For Kubernetes deployments, you can set the machine ID using an environment variable:

env:
  - name: SNOWFLAKED_MACHINE_ID
    valueFrom:
      fieldRef:
        fieldPath: metadata.name

Or use a StatefulSet ordinal for guaranteed unique values:

env:
  - name: SNOWFLAKED_MACHINE_ID
    valueFrom:
      fieldRef:
        fieldPath: metadata.annotations['apps.kubernetes.io/pod-index']

API Reference

id = Snowflaked.id

Snowflaked.parse(id)
# => {timestamp_ms: 1735123456789, machine_id: 42, sequence: 0}

Snowflaked.timestamp(id)
# => 2024-12-25 12:34:56 +0000

Snowflaked.timestamp_ms(id)
# => 1735123456789

Snowflaked.sequence(id)
# => 0

Snowflaked.machine_id(id)
# => 42

Benchmarks

See BENCHMARKS.md for more details.

tl;dr: Snowflake IDs have a negligible performance impact compared to database-backed IDs.

Requirements

  • Ruby >= 3.2
  • rustc >= 1.81.0
  • cargo >= 1.81.0
  • Mise

Development

mise install
bundle install
bundle exec rake

Acknowledgments

License

MIT