Skip to content

The most complete Snowflake ID generator in TypeScript

License

Notifications You must be signed in to change notification settings

CatWithAWand/snowflakify

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

snowflakify - npm GitHub-pages deployment Commitizen friendly TypeScript
A complete Snowflake ID generator in TypeScript.
Generation, destructuring, custom snowflake structure, and more...



About

Snowflakify is a complete Node.js module for distributed systems to generate snowflake IDs, written in TypeScript.

  • IDs based on worker threads/cluster,
    machine IPv4/MAC addresses
  • Circular/Ring Buffer for increased output
  • Snowflake destructuring
  • BigInt based
  • Custom epoch
  • Custom snowflake fragments
    with customizable bit length:
    • Timestamp
    • Random
    • Worker ID
    • Process ID
    • IPv4 Address
    • MAC Address
    • Sequence

Circular/Ring Buffer performance

useBuffer workerCount Time Period (s) Generated/Main Generated/Workers IDs/ms
false 0 10 3453950 0 345.4
true 1 10 1190497 5700275 689.1
true 2 10 0 8677260 867.7
true 3 10 0 9726306 972.6



Installation

Requires Node.js 14.5.0 or newer.

npm:

$ npm install snowflakify

yarn:

$ yarn add snowflakify

pnpm:

$ pnpm add snowflakify



Usage

Creating a new Snowflakify ID generator

import Snowflakify from 'snowflakify';

// with default options
const snowflakify = new Snowflakify();

// or with a custom epoch and one of the three presets
const CUSTOM_EPOCH = 1262304001000;
const snowflakify = new Snowflakify({ epoch: CUSTOM_EPOCH, preset: 'ipv4' });

Generating a snowflake ID

snowflakify.nextId();
// 1000920566716264449n

Destructuring a snowflake ID

const snowflakeId = snowflakify.nextId();
// 1000920566716264449n

snowflakify.destructure(snowflakeId);
// [
//   { identifier: 'TimestampFragment', value: 1658708459310n },
//   { identifier: 'WorkerFragment', value: 0 },
//   { identifier: 'ProcessFragment', value: 23 },
//   { identifier: 'SequenceFragment', value: 1 }
// ]

Hexadecimal snowflake ID

const id = snowflakify.nextHexId();
snowflakify.destructureHex(id);

Creating a custom snowflake structure

import Snowflakify, {
  TimestampFragment,
  NetworkFragment,
  SequenceFragment,
} from 'snowflakify';

const CUSTOM_EPOCH = 1262304001000;

const snowflakify = new Snowflakify({
  fragmentArray: [
    new TimestampFragment(42, CUSTOM_EPOCH),
    new NetworkFragment(10, 'ipv4'),
    new SequenceFragment(12),
  ],
});

const snowflakeId = snowflakify.nextId();
// 1662643509670989825n

snowflakify.destructure(snowflakeId);
// [
//   { identifier: 'TimestampFragment', value: 1658709104128n },
//   { identifier: 'NetworkFragment:ipv4', value: 197 },
//   { identifier: 'SequenceFragment', value: 1 }
// ]



Note: Snowflakify must be instantiated inside the worker when working with worker_threads or cluster, else it won't be able to see the worker.


Example with worker_threads

index.js

import { Worker } from 'worker_threads';

const numWorkers = 3;

for (let i = 0; i < numWorkers; i += 1) {
  const worker = new Worker('./worker.js');

  worker.on('message', (msg) => {
    console.log(`Worker ${worker.threadId} generated snowflake:`);
    console.log(msg);
  });
}

worker.js

import { parentPort } from 'worker_threads';
import Snowflakify from 'snowflakify';

const snowflakify = new Snowflakify();

const snowflakeId = () => {
  const snowflake = snowflakify.nextId();
  const destructuredSnowflake = snowflakify.destructure(snowflake);

  return {
    snowflake,
    destructuredSnowflake,
  };
};

parentPort.postMessage(snowflakeId());

Console output

Worker 1 generated snowflake:
{
  snowflake: 1001124197361188865n,
  destructuredSnowflake: [
    { identifier: 'TimestampFragment', value: 1658757008639n },
    { identifier: 'WorkerFragment', value: 1 },
    { identifier: 'ProcessFragment', value: 16 },
    { identifier: 'SequenceFragment', value: 1 }
  ]
}
Worker 2 generated snowflake:
{
  snowflake: 1001124197382291457n,
  destructuredSnowflake: [
    { identifier: 'TimestampFragment', value: 1658757008644n },
    { identifier: 'WorkerFragment', value: 2 },
    { identifier: 'ProcessFragment', value: 16 },
    { identifier: 'SequenceFragment', value: 1 }
  ]
}
Worker 3 generated snowflake:
{
  snowflake: 1001124197378228225n,
  destructuredSnowflake: [
    { identifier: 'TimestampFragment', value: 1658757008643n },
    { identifier: 'WorkerFragment', value: 3 },
    { identifier: 'ProcessFragment', value: 16 },
    { identifier: 'SequenceFragment', value: 1 }
  ]
}

Example with cluster

index.js

import cluster from 'node:cluster';
import Snowflakify from 'snowflakify';

const numWorkers = 3;

if (cluster.isPrimary) {
  for (let i = 0; i < numWorkers; i += 1) {
    cluster.fork();
  }
} else {
  const snowflakify = new Snowflakify();

  const snowflake = snowflakify.nextId();
  const destructuredSnowflake = snowflakify.destructure(snowflake);

  console.log(`Worker ${cluster.worker.id} generated snowflake:`);
  console.log({ snowflake, destructuredSnowflake });
}

Console output

Worker 1 generated snowflake:
{
  snowflake: 1001124291087147009n,
  destructuredSnowflake: [
    { identifier: 'TimestampFragment', value: 1658757030985n },
    { identifier: 'WorkerFragment', value: 1 },
    { identifier: 'ProcessFragment', value: 26 },
    { identifier: 'SequenceFragment', value: 1 }
  ]
}
Worker 2 generated snowflake:
{
  snowflake: 1001124291099865089n,
  destructuredSnowflake: [
    { identifier: 'TimestampFragment', value: 1658757030988n },
    { identifier: 'WorkerFragment', value: 2 },
    { identifier: 'ProcessFragment', value: 27 },
    { identifier: 'SequenceFragment', value: 1 }
  ]
}
Worker 3 generated snowflake:
{
  snowflake: 1001124291150331905n,
  destructuredSnowflake: [
    { identifier: 'TimestampFragment', value: 1658757031000n },
    { identifier: 'WorkerFragment', value: 3 },
    { identifier: 'ProcessFragment', value: 28 },
    { identifier: 'SequenceFragment', value: 1 }
  ]
}

Using the Circular/Ring Buffer

import Snowflakify, {
  TimestampFragment,
  WorkerFragment,
  ProcessFragment,
  SequenceFragment,
} from 'snowflakify';

const CUSTOM_EPOCH = 1262304001000;

const snowflakify = new Snowflakify({
  useBuffer: true,
  bufferSize: 2 ** 21,
  workerCount: 2,
  fragmentArray: [
    new TimestampFragment(42, CUSTOM_EPOCH),
    new WorkerFragment(5),
    new ProcessFragment(5),
    new SequenceFragment(12),
  ],
});

// ...

Console output (next 3 snowflake IDs generated and destructured)

1662657179066654721n
[
  { identifier: 'TimestampFragment', value: 1658712363166n },
  { identifier: 'WorkerFragment', value: 2 },
  { identifier: 'ProcessFragment', value: 22 },
  { identifier: 'SequenceFragment', value: 1 }
]
1662657179066654722n
[
  { identifier: 'TimestampFragment', value: 1658712363166n },
  { identifier: 'WorkerFragment', value: 2 },
  { identifier: 'ProcessFragment', value: 22 },
  { identifier: 'SequenceFragment', value: 2 }
]
1662657179066654723n
[
  { identifier: 'TimestampFragment', value: 1658712363166n },
  { identifier: 'WorkerFragment', value: 2 },
  { identifier: 'ProcessFragment', value: 22 },
  { identifier: 'SequenceFragment', value: 3 }
]