README.md

# Snow - High-Performance Lock-Free Erlang Snowflake ID Generator

A high-performance, lock-free Snowflake ID generation library for Erlang, built with `persistent_term` and `atomics` for truly concurrent operation.

## Features

- 🚀 **Lock-Free Design**: Uses Erlang atomics and CAS operations for true lock-free concurrency
- ⚡ **High Performance**: 
  - Single-thread: 2M+ IDs/sec
  - Concurrent (20 processes): 3.7M+ IDs/sec with 1.8x speedup
- 🌍 **Distributed-Friendly**: Supports region and worker ID configuration for multi-node deployments
- ⏰ **Clock Protection**: Automatic detection and handling of clock drift/backwards
- 🔧 **Configurable**: Custom epoch, region, worker ID, and compile-time bit allocation
- 🎯 **Aggressive CAS**: Direct retry with CAS return value optimization for maximum throughput
- 📦 **Zero Dependencies**: Pure Erlang implementation

## ID Structure (64-bit, can be changed via rebar.config)

```
| 1 bit | 41 bits | 4 bits | 6 bits | 12 bits |
|-------|---------|--------|--------|---------|
|   0   |timestamp| region | worker |sequence |
```

- **Reserved** (1 bit): Always 0
- **Timestamp** (41 bits): Millisecond timestamp, ~69 years from epoch
- **Region** (4 bits): Region ID (0-15, configurable)
- **Worker** (6 bits): Worker node ID (0-63, configurable)  
- **Sequence** (12 bits): Per-millisecond sequence (0-4095, auto-calculated)

## Quick Start

### Add Dependency

```erlang
{deps, [
    {snow, {git, "https://github.com/longlene/snow.git", {branch, "main"}}}
]}.
```

### Configuration

Configure in `sys.config`:

```erlang
[
  {snow, [
    {epoch, 1640995200000}, % 2022-01-01 00:00:00 UTC
    {region, 0},            % 0-15
    {worker, 0}             % 0-63
  ]}
].
```

### Usage Examples

```erlang
%% Start application
application:ensure_all_started(snow).

%% Generate single ID
Id = snow:next_id().
% 7128834371969425408

%% Batch generate IDs (optimized for bulk operations)
Ids = snow:next_ids(1000).

%% Decode ID
#{timestamp := Ts, region := R, worker := W, sequence := Seq} = snow:decode_id(Id).

%% Custom initialization (global worker)
snow:init(1640995200000, 5, 10).

%% Multi-worker API - create independent workers
Worker1 = snow:start_worker(1640995200000, 1, 2),
Worker2 = snow:start_worker(1640995200000, 1, 3),

%% Generate IDs using specific workers
Id1 = snow:next_id(Worker1),
Id2 = snow:next_id(Worker2),
Ids = snow:next_ids(Worker1, 1000),

%% Get configuration info
snow:info(),                    % Global worker info
snow:worker_info(Worker1).      % Specific worker info
```

## API Reference

### snow:init/3
Initialize the global generator with custom configuration.

**Parameters:**
- `Epoch :: non_neg_integer()` - Start timestamp in milliseconds
- `Region :: 0..15` - Region ID
- `Worker :: 0..63` - Worker node ID

### snow:start_worker/3
Create a new independent worker instance (recommended for multi-worker scenarios).

**Parameters:**
- `Epoch :: non_neg_integer()` - Start timestamp in milliseconds
- `Region :: 0..15` - Region ID
- `Worker :: 0..63` - Worker node ID

**Returns:** `worker_handle()` - Opaque worker handle for ID generation

### snow:next_id/0
Generate a single Snowflake ID using the global worker.

**Returns:** `non_neg_integer()`

### snow:next_id/1
Generate a single Snowflake ID using a specific worker.

**Parameters:**
- `WorkerHandle :: worker_handle()` - Worker handle from `start_worker/3`

**Returns:** `non_neg_integer()`

### snow:next_ids/1
Efficiently generate multiple IDs in batch using the global worker.

**Parameters:**
- `Count :: pos_integer()` - Number of IDs to generate

**Returns:** `[non_neg_integer()]`

### snow:next_ids/2
Efficiently generate multiple IDs in batch using a specific worker.

**Parameters:**
- `WorkerHandle :: worker_handle()` - Worker handle from `start_worker/3`
- `Count :: pos_integer()` - Number of IDs to generate

**Returns:** `[non_neg_integer()]`

### snow:decode_id/1
Decode a Snowflake ID into its components.

**Parameters:**
- `Id :: non_neg_integer()` - Snowflake ID to decode

**Returns:**
```erlang
#{
    timestamp := non_neg_integer(),
    region := non_neg_integer(),
    worker := non_neg_integer(),
    sequence := non_neg_integer()
}
```

### snow:info/0
Get global worker configuration and bit allocation.

**Returns:**
```erlang
#{
    epoch := non_neg_integer(),
    region := non_neg_integer(),
    worker := non_neg_integer(),
    bits := #{
        timestamp := pos_integer(),
        region := pos_integer(),
        worker := pos_integer(),
        sequence := pos_integer()
    }
}
```

### snow:worker_info/1
Get specific worker configuration and bit allocation.

**Parameters:**
- `WorkerHandle :: worker_handle()` - Worker handle from `start_worker/3`

**Returns:**
```erlang
#{
    epoch := non_neg_integer(),
    region := non_neg_integer(),
    worker := non_neg_integer(),
    bits := #{
        timestamp := pos_integer(),
        region := pos_integer(),
        worker := pos_integer(),
        sequence := pos_integer()
    }
}
```

## Performance

Benchmarks on modern hardware (Erlang/OTP 26):

| Scenario | Performance | Notes |
|----------|------------|--------|
| Single-thread | 2.07M IDs/sec | Maximum single-threaded performance |
| 10 processes | 2.26M IDs/sec | Good concurrent performance |
| 20 processes | 3.24M IDs/sec | Excellent concurrent scalability |
| 50 processes | 2.75M IDs/sec | Strong performance under high load |

### Performance Optimizations

1. **Pre-computed Base IDs**: Region and worker bits calculated once at initialization
2. **Compile-time Constants**: Bit shifts resolved at compile time
3. **CAS Return Value Optimization**: Uses actual values from failed CAS operations for immediate retry
4. **Batch Sequence Reservation**: Single CAS operation reserves multiple sequence numbers
5. **Lock-Free Design**: Pure atomic operations without any locking mechanisms

## Compile-time Configuration

Customize bit allocation by modifying `rebar.config`:

```erlang
{erl_opts, [
    debug_info,
    {d, timestamp_bits, 41},  % Timestamp bits (supports ~69 years)
    {d, region_bits, 4},      % Region bits (16 regions)
    {d, worker_bits, 6}       % Worker bits (64 workers)
    % sequence_bits automatically calculated: 64 - 1 - 41 - 4 - 6 = 12
]}.
```

**Example configurations:**
- High regions: `{d, region_bits, 5}` + `{d, worker_bits, 5}` = 32 regions, 32 workers, 2048/ms
- High workers: `{d, region_bits, 3}` + `{d, worker_bits, 7}` = 8 regions, 128 workers, 2048/ms

## Architecture

### Lock-Free Design
- Uses `atomics:compare_exchange/4` for atomic sequence updates
- No GenServer or process-based state management
- Configuration stored in `persistent_term` for fast access

### CAS Optimization Strategy
Maximizes throughput through immediate CAS retry:

```erlang
%% Uses return value from failed CAS operations
case atomics:compare_exchange(AtomicRef, 1, OldVal, NewVal) of
    ok ->
        %% Success - return generated ID
        construct_final_id(Timestamp, BaseId, Sequence);
    CurrentVal ->
        %% Failed - retry immediately with current value
        generate_id_loop(Epoch, BaseId, AtomicRef, CurrentVal)
end
```

### Multi-Worker Architecture

**Single Worker per Node (Traditional):**
```erlang
%% Node 1
snow:init(1640995200000, 0, 1).

%% Node 2  
snow:init(1640995200000, 0, 2).

%% Node 3
snow:init(1640995200000, 0, 3).
```

**Multiple Workers per Node (Recommended):**
```erlang
%% Create multiple independent workers on same node
Worker1 = snow:start_worker(1640995200000, 1, 1),  % Region 1, Worker 1
Worker2 = snow:start_worker(1640995200000, 1, 2),  % Region 1, Worker 2  
Worker3 = snow:start_worker(1640995200000, 2, 1),  % Region 2, Worker 1

%% Use workers for different purposes
OrderId = snow:next_id(Worker1),      % Order service
UserId = snow:next_id(Worker2),       % User service  
PaymentId = snow:next_id(Worker3),    % Payment service
```

**Key Benefits of Multi-Worker:**
- **Zero contention**: Each worker has independent atomic state
- **Better performance**: Avoid global persistent_term lookups  
- **Service isolation**: Different services can use different workers
- **Flexible deployment**: Support any number of workers per node

## Testing

```bash
# Run unit tests
rebar3 ct

# Run performance benchmarks
make bench

# Run with custom compiler optimizations
rebar3 as bench compile
```

## Error Handling

- **Clock backwards**: Throws `{clock_backwards, OldTime, NewTime}` 
- **Invalid configuration**: Validates region/worker bounds at initialization
- **Sequence exhaustion**: Automatically waits for next millisecond

## License

MIT License - see LICENSE file for details.

## Contributing

Contributions welcome! Please ensure:
- All tests pass (`rebar3 ct`)
- Performance benchmarks show no regression
- Code follows existing style conventions