-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjump-hash.js
More file actions
88 lines (81 loc) · 2.23 KB
/
jump-hash.js
File metadata and controls
88 lines (81 loc) · 2.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import crypto from "crypto";
/**
* JumpConsistentHash maps string keys to an integer index in [0, N)
* using the Jump Consistent Hash algorithm by Lamping & Veach.
*
* Usage:
* const jch = new JumpConsistentHash(16)
* const idx = jch.getIndex('some-key') // 0..15
*/
class JumpConsistentHash {
/**
* @param {number} indexes - Total number of indexes (buckets), must be >= 1 integer
*/
constructor(indexes) {
this.setIndexes(indexes);
}
/**
* Update number of indexes (buckets).
* @param {number} indexes
*/
setIndexes(indexes) {
if (!Number.isInteger(indexes) || indexes <= 0) {
throw new Error("Indexes must be a positive integer");
}
this.indexes = indexes;
}
/**
* @returns {number} Current number of indexes (buckets)
*/
size() {
return this.indexes;
}
/**
* Compute stable index in [0, indexes) for the given key.
* @param {string} key
* @returns {number}
*/
getIndex(key) {
if (!key || typeof key !== "string") {
throw new Error("Key must be a non-empty string");
}
const k = this.#hash64(key);
return this.#jumpHash(k, this.indexes);
}
/**
* Hash string to unsigned 64-bit BigInt using SHA-1 (first 8 bytes).
* Endianness choice is arbitrary but consistent (little-endian).
* @param {string} key
* @returns {bigint}
*/
#hash64(key) {
const digest = crypto.createHash("sha1").update(key).digest();
let x = 0n;
const len = Math.min(8, digest.length);
for (let i = 0; i < len; i++) {
x |= BigInt(digest[i]) << BigInt(8 * i);
}
return x;
}
/**
* Jump Consistent Hash (Lamping & Veach) implemented with 64-bit arithmetic.
* @param {bigint} key - 64-bit unsigned key
* @param {number} buckets - number of buckets (indexes)
* @returns {number}
*/
#jumpHash(key, buckets) {
// Constants per paper
const mul = 2862933555777941757n;
let b = -1;
let j = 0;
while (j < buckets) {
b = j;
key = (key * mul + 1n) & ((1n << 64n) - 1n); // mod 2^64
// (key >> 33) fits in 31 bits => safe to convert to Number
const r = Number(key >> 33n) + 1;
j = Math.floor((b + 1) * (2147483648 / r));
}
return b;
}
}
export default JumpConsistentHash;