Playing Around with Unique Random Keys

I had a project recently where I was trying to decide the optimal length for a nice human readable key, while making it unlikely to have collisions while keeping it semi-unguessable.

I wrote the code below so I could play around with different character sets, key lengths, and number of keys while seeing how those affected the timing and likelihood of collisions. The project allowed for retrying in the case of a collision, so this code below allows for those with a random delay to lookup whether a key has been taken already.

const assert = require("assert");
const {
toPairs,
toArray,
countBy,
times,
random,
snakeCase
} = require("lodash");
const {
KEY_LENGTH,
ITERATIONS,
MAX_RETRIES,
DELAY_LOOKUP_MIN,
DELAY_LOOKUP_MAX,
CHAR_SET
} = process.argv.slice(2).reduce(
(acc, k, i, list) => {
if (i % 2 > 0) return acc;
const key = snakeCase(k.slice(2)).toUpperCase();
const value = list[i + 1];
acc[key] = /^\d+$/.test(value) ? Number(value) : value;
return acc;
},
{
KEY_LENGTH: 4,
ITERATIONS: 923521,
MAX_RETRIES: Infinity,
DELAY_LOOKUP_MIN: 0,
DELAY_LOOKUP_MAX: 1,
// Less ambiguous character set (no o, 0, 1, l, i, etc)
CHAR_SET: "abcdefghjkmnpqrstuvwxyz23456789"
}
);
assert.ok(
ITERATIONS <= Math.pow(CHAR_SET.length, KEY_LENGTH),
"Iterations is too big for the max of char set and key length"
);
const hrtimeN = hrtime => hrtime[0] * 1e9 + hrtime[1];
const hrtimeM = hrtime => hrtimeN(hrtime) / 1e6;
const hrtimeS = hrtime => hrtimeM(hrtime) / 1e3;
const sleep = time => {
const stop = process.hrtime();
while (hrtimeM(process.hrtime(stop)) < time) {}
return true;
};
const generateKey = (length = KEY_LENGTH) =>
times(length, () => CHAR_SET.charAt(random(0, CHAR_SET.length - 1))).join("");
class DelayMap extends Map {
has(key) {
if (DELAY_LOOKUP_MAX) {
sleep(random(DELAY_LOOKUP_MIN || 0, DELAY_LOOKUP_MAX));
}
return super.has(key);
}
}
const keys = new DelayMap();
const tryGenerateKey = () => {
let key = null;
let tries = 0;
do {
tries++;
key = generateKey();
if (!keys.has(key)) {
return { key, tries };
}
if (tries >= MAX_RETRIES) {
assert.fail(`Exceeded max retries ${MAX_RETRIES}`);
}
} while (true);
};
const start = process.hrtime();
for (let i = 0; i < ITERATIONS; i++) {
const startKey = process.hrtime();
const { key, tries } = tryGenerateKey();
keys.set(key, tries);
const endKey = process.hrtime(startKey);
console.log(key, tries, keys.size, `${hrtimeM(endKey)}ms`);
}
assert.equal(keys.size, ITERATIONS);
const end = process.hrtime(start);
const countByTries = toPairs(countBy(toArray(keys.values())))
.map(v => v.join(" "))
.join("\n");
console.log("-".repeat(40));
console.log(countByTries);
console.log(`${hrtimeS(end)}s`);
view raw index.js hosted with ❤ by GitHub