Skip to content

Commit

Permalink
Version 1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
radixxko committed Aug 27, 2019
1 parent c9daf42 commit f46fec2
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 95 deletions.
201 changes: 125 additions & 76 deletions lib/unique_id.js
Original file line number Diff line number Diff line change
@@ -1,104 +1,153 @@
'use strict';

let startTimestamp = (new Date()).getTime();
let startHRtime = process.hrtime();
let currentTimestamp = null;
let machineID = null;
const Options = require('liqd-options');

const defaults =
{
min: 0,
max: Number.MAX_SAFE_INTEGER,
unique_interval: 86400,
}
let START_TIMESTAMP = Date.now(), START_HRTIME = process.hrtime(), CURRENT_TIME, MACHINE_ID;

function getBinary( number, length )
{
return ('0'.repeat( length ) + number.toString(2)).substr(-length);
return ('0'.repeat( length ) + number.toString(2)).substr(-length);
}

function hash32( data )
{
const sha1 = require('crypto').createHash('sha1');
sha1.update( data );
const sha1 = require('crypto').createHash('sha1');
sha1.update( data );

return parseInt( sha1.digest('hex').substr(0, 8), 16 );
return parseInt( sha1.digest('hex').substr(0, 8), 16 );
}

module.exports = class UniqueID
{
constructor( options = {} )
{
let min = Math.ceil( options.min || defaults.min ),
max = Math.floor( options.max || defaults.max ),
unique_interval = options.unique_interval || defaults.unique_interval;

this.generator =
{
prefix: options.prefix,
min,
max,
id: { bits: Math.floor( Math.log2( max - min ) ), max: 0 },
timestamp: { bits: Math.ceil( Math.log2( unique_interval * 1000 ) ), max: 0, offset: 0 },
node: { bits: 0, max: 0, offset: 0, value: typeof options.node_id !== 'undefined' ? hash32( options.node_id ) : ( machineID !== null ? machineID : Math.floor( Math.random() * Math.pow( 2, 32 ) ) ) },
pid: { bits: 0, max: 0, offset: 0, value: process.pid },
iterator: { bits: 0, max: 0, value: 0 }
};

if( Math.ceil( ( this.generator.id.bits - this.generator.timestamp.bits ) * 2 / 5 ) < 8 )
constructor( options = {} )
{
throw 'Error: small range for generator uniqueness, increase min/max or lower the unique interval';
this._options = Options( options,
{
prefix : { _required: false, _default: undefined },
min : { _required: false, _type: 'number', _default: 0 },
max : { _required: false, _type: 'number', _default: Number.MAX_SAFE_INTEGER },
unique_interval : { _required: false, _type: 'number', _default: 86400 },
node : { _required: false, _type: [ 'boolean', 'number', 'string' ], _default: true },
pid : { _required: false, _type: [ 'boolean', 'number', 'string' ], _default: true }
});

this._generator =
{
prefix : this._options.prefix,
min : this._options.min,
max : this._options.max,
id : { bits: Math.floor( Math.log2( this._options.max - this._options.min )), max: 0 },
timestamp : { bits: Math.ceil( Math.log2( this._options.unique_interval )), max: 0, offset: 0 },
node : { bits: 0, max: 0, offset: 0, value: this._options.node !== true ? this._options.node : MACHINE_ID },
pid : { bits: 0, max: 0, offset: 0, value: this._options.pid !== true ? this._options.pid : process.pid },
iterator : { bits: 0, max: 0, value: 0 }
};

if( Math.ceil(( this._generator.id.bits - this._generator.timestamp.bits ) * 2 / 5 ) < 8 )
{
throw 'Error: small range for generator uniqueness, increase min/max or lower the unique interval';
}

this._generator.id.max = Math.pow( 2, this._generator.id.bits );
this._generator.timestamp.max = Math.pow( 2, this._generator.timestamp.bits );
this._generator.timestamp.offset = Math.pow( 2, this._generator.id.bits - this._generator.timestamp.bits );

let instance_bits = 0;

if( this._options.node === false && this._options.pid === false )
{
this._generator.iterator.bits = this._generator.id.bits - this._generator.timestamp.bits;
}
else if( this._options.node === false || this._options.pid === false )
{
this._generator.iterator.bits = Math.ceil(( this._generator.id.bits - this._generator.timestamp.bits ) * 3 / 5 );
instance_bits = Math.ceil(( this._generator.id.bits - this._generator.timestamp.bits - this._generator.iterator.bits ) * 3 / 5 );
instance_bits += Math.floor( instance_bits / 3 );
this._generator.iterator.bits = this._generator.id.bits - this._generator.timestamp.bits - instance_bits;
}
else
{
this._generator.iterator.bits = Math.ceil(( this._generator.id.bits - this._generator.timestamp.bits ) * 3 / 5 );
instance_bits = Math.ceil(( this._generator.id.bits - this._generator.timestamp.bits - this._generator.iterator.bits ) * 3 / 5 );
}

if( this._options.node !== false )
{
this._generator.node.bits = instance_bits;
this._generator.node.max = Math.pow( 2, this._generator.node.bits );
this._generator.node.offset = Math.pow( 2, this._generator.id.bits - this._generator.timestamp.bits - this._generator.node.bits );

if( this._options.node === true && MACHINE_ID === undefined )
{
this._generator.node.value = Math.floor( Math.random() * Math.pow( 2, this._generator.node.bits ));
this._generator.node.value *= this._generator.node.offset;

require('./machine_id')().then( id =>
{
this._generator.node.value = ( MACHINE_ID = id ? hash32( id ) : Math.floor( Math.random() * Math.pow( 2, 32 ))) % Math.pow( 2, this._generator.node.bits );
this._generator.node.value *= this._generator.node.offset;
});
}
else
{
this._generator.node.value = ( typeof this._generator.node.value === 'number' ? this._generator.node.value : hash32( this._generator.node.value )) % Math.pow( 2, this._generator.node.bits );
this._generator.node.value *= this._generator.node.offset;
}
}
else
{
this._generator.node.value = 0;
}

if( this._options.pid !== false )
{
this._generator.pid.bits = instance_bits;
this._generator.pid.max = Math.pow( 2, this._generator.pid.bits );
this._generator.pid.offset = Math.pow( 2, this._generator.iterator.bits );
this._generator.pid.value = ( typeof this._generator.pid.value === 'number' ? this._generator.pid.value : hash32( this._generator.pid.value )) % Math.pow( 2, this._generator.pid.bits );
this._generator.pid.value *= this._generator.pid.offset;
}
else
{
this._generator.pid.value = 0;
}

this._generator.iterator.max = Math.pow( 2, this._generator.iterator.bits );
this._generator.iterator.value = Math.floor( Math.random() * this._generator.iterator.max );
}

if( typeof options.node_id === 'undefined' && machineID === null )
info()
{
require('./machine_id')().then( id =>
let info = JSON.parse( JSON.stringify( this._generator ));

info.mask =
{
this.generator.node.value = ( ( machineID = hash32( id ) ) % this.generator.node.max ) * this.generator.node.offset;
});
'id ' : getBinary( this._generator.id.max - 1, this._generator.id.bits ),
'time ' : getBinary(( this._generator.timestamp.max - 1 ) * this._generator.timestamp.offset, this._generator.id.bits ),
'node ' : getBinary(( this._generator.node.max - 1 ) * this._generator.node.offset, this._generator.id.bits ),
'pid ' : getBinary(( this._generator.pid.max - 1 ) * this._generator.pid.offset, this._generator.id.bits ),
'iterator ' : getBinary( this._generator.iterator.max - 1, this._generator.id.bits )
}

return info;
}

this.generator.node.bits = this.generator.pid.bits = this.generator.iterator.bits = Math.ceil( ( this.generator.id.bits - this.generator.timestamp.bits ) * 2 / 5 );

this.generator.id.max = Math.pow( 2, this.generator.id.bits );
this.generator.timestamp.max = Math.pow( 2, this.generator.timestamp.bits );
this.generator.node.max = this.generator.pid.max = this.generator.iterator.max = Math.pow( 2, this.generator.iterator.bits );

this.generator.timestamp.offset = Math.pow( 2, this.generator.id.bits - this.generator.timestamp.bits );
this.generator.node.offset = Math.pow( 2, this.generator.id.bits - this.generator.timestamp.bits - this.generator.node.bits );
this.generator.pid.offset = Math.pow( 2, this.generator.iterator.bits );

this.generator.node.value = ( this.generator.node.value % this.generator.node.max ) * this.generator.node.offset;
this.generator.pid.value = ( this.generator.pid.value % this.generator.pid.max ) * this.generator.pid.offset;
this.generator.iterator.value = Math.floor( Math.random() * this.generator.iterator.max );

/*console.log( this.generator );
console.log('-----');
console.log( 'id : ' + getBinary( this.generator.id.max - 1, this.generator.id.bits ) );
console.log( 'time : ' + getBinary( ( this.generator.timestamp.max - 1 ) * this.generator.timestamp.offset, this.generator.id.bits ) );
console.log( 'node : ' + getBinary( ( this.generator.node.max - 1 ) * this.generator.node.offset, this.generator.id.bits ) );
console.log( 'pid : ' + getBinary( ( this.generator.pid.max - 1 ) * this.generator.pid.offset, this.generator.id.bits ) );
console.log( 'iter : ' + getBinary( this.generator.iterator.max - 1, this.generator.id.bits ) );
console.log('-----');*/
}

get()
{
if( currentTimestamp === null )
get()
{
let elapsedHRTime = process.hrtime( startHRtime );
if( CURRENT_TIME === undefined )
{
let elapsed = process.hrtime( START_HRTIME );

currentTimestamp = startTimestamp + elapsedHRTime[0] * 1000 + Math.round( elapsedHRTime[1] / 1e6 );
CURRENT_TIME = Math.floor( START_TIMESTAMP + elapsed[0] + elapsed[1] / 1e9 );

setImmediate( () => { currentTimestamp = null; } );
}
setImmediate(() => { CURRENT_TIME = undefined });
}

let id = ( ( currentTimestamp % this.generator.timestamp.max ) * this.generator.timestamp.offset ) +
this.generator.node.value + this.generator.pid.value +
( this.generator.iterator.value = ( this.generator.iterator.value + 1 ) % this.generator.iterator.max ) +
this.generator.min;
let id = (( CURRENT_TIME % this._generator.timestamp.max ) * this._generator.timestamp.offset ) +
this._generator.node.value + this._generator.pid.value +
( this._generator.iterator.value = ( this._generator.iterator.value + 1 ) % this._generator.iterator.max ) +
this._generator.min;

return ( this.prefix ? this.prefix + id : id );
}
return ( this._options.prefix ? this._options.prefix + id : id );
}
}
33 changes: 19 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "liqd-unique-id",
"description": "Unique ID generator working across multiple cluster nodes and processes",
"version": "1.0.3",
"version": "1.1.0",
"author": "radixxko",
"license": "MIT",
"main": "lib/unique_id.js",
Expand All @@ -22,9 +22,11 @@
"generator",
"liqd"
],
"dependencies": {},
"dependencies": {
"liqd-options": "^1.0.8"
},
"devDependencies": {
"coveralls": "^3.0.4",
"coveralls": "^3.0.6",
"mocha": "^5.2.0",
"nyc": "^14.1.1"
},
Expand Down
4 changes: 2 additions & 2 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const generatedIDs = new Set();

const UniqueID = require('../lib/unique_id');

let sessionID = new UniqueID({ unique_interval: 365 * 24 * 3600 });
let sessionID = new UniqueID({ /*unique_interval: 365 * 24 * 3600,*/unique_interval: 60, node: true });

let start = process.hrtime();
let ids = 0;
Expand All @@ -22,7 +22,7 @@ function generate()
process.exit();
}

for( let i = 0; i < 256; ++i )
for( let i = 0; i < 2560000; ++i )
{
id = sessionID.get();

Expand Down

0 comments on commit f46fec2

Please sign in to comment.