Snowflaked
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 thesnowflake_idhelper 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) # Defaults to DEFAULT_EPOCH (2024-01-01) if not configured
end
Configuration is locked after Snowflaked.configure or the first generated/parsed ID. Set it during application boot, before request threads start.
Machine ID
[!TIP] For multi-process servers like Puma, it is recommended to not configure
machine_idexplicitly. The gem automatically calculates a process-local machine ID using(hostname.hash ^ pid) % 1024, so forked workers do not reuse the parent's cached machine ID.
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:
SNOWFLAKED_MACHINE_IDenvironment variableMACHINE_IDenvironment variable- Auto-detected using the following formula:
(hostname.hash ^ pid) % 1024
For Kubernetes deployments, set the machine ID to a numeric value between 0 and 1023:
env:
- name: SNOWFLAKED_MACHINE_ID
value: "42"
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.(id)
# => 2024-12-25 12:34:56 +0000
Snowflaked.(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.3
- rustc / cargo >= 1.81.0 (development uses the toolchain pinned in
mise.toml) - Mise
Development
mise install
bundle install
bundle exec rake
Acknowledgments
- snowflaked-rs - the Rust implementation of Snowflake IDs
License
MIT