Skip to content

Commit 594c56a

Browse files
committed
feat(ur)!: use consistent hashing hash ring to determine host for less churn when hosts are modified
1 parent 3010a86 commit 594c56a

File tree

4 files changed

+249
-279
lines changed

4 files changed

+249
-279
lines changed

servers/ur/package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

servers/ur/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
},
1212
"dependencies": {
1313
"arbundles": "0.11.1",
14+
"consistent-hash": "^1.2.2",
1415
"cors": "^2.8.5",
1516
"debug": "^4.3.7",
1617
"express": "^4.21.0",

servers/ur/src/domain.js

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defaultTo, isEmpty, complement, path } from 'ramda'
22
import { LRUCache } from 'lru-cache'
3+
import ConsistentHash from 'consistent-hash'
34

45
const isNotEmpty = complement(isEmpty)
56

@@ -72,18 +73,13 @@ export function bailoutWith ({ fetch, subrouterUrl, surUrl, owners, processToHos
7273
*/
7374
export function determineHostWith ({ hosts = [], bailout }) {
7475
/**
75-
* TODO: should we inject this cache?
76+
* A consistent hashing hashring is used, to minimize the amount of
77+
* processes that must swap hosts when hosts are added or removed
7678
*/
77-
const processToHostCache = new LRUCache({
78-
/**
79-
* 10MB
80-
*/
81-
maxSize: 10_000_000,
82-
/**
83-
* A number is 8 bytes
84-
*/
85-
sizeCalculation: () => 8
86-
})
79+
const hashring = new ConsistentHash({ weight: 400, distribution: 'uniform' })
80+
const processToHostCache = new LRUCache({ maxSize: 10_000_000, sizeCalculation: () => 8 })
81+
82+
hosts.forEach(host => hashring.add(host))
8783

8884
return async ({ processId, failoverAttempt = 0 }) => {
8985
if (failoverAttempt >= hosts.length) return
@@ -93,43 +89,23 @@ export function determineHostWith ({ hosts = [], bailout }) {
9389
if (bail) return bail
9490
}
9591

96-
/**
97-
* Check cache, and hydrate if necessary
98-
*/
99-
let hashSum = processToHostCache.get(processId)
100-
if (!hashSum) {
92+
if (failoverAttempt) {
10193
/**
102-
* Only perform the expensive computation of hash -> idx once and cache
94+
* Passing in a count will return an array of nodes,
95+
* first the one that handles the named resource,
96+
* then the following closest nodes around the hash ring.
10397
*/
104-
hashSum = computeHashSumFromProcessId({ processId, length: hosts.length })
105-
processToHostCache.set(processId, hashSum)
98+
const hosts = hashring.get(processId, failoverAttempt + 1)
99+
return hosts.pop()
106100
}
107101

108-
return hosts[(hashSum + failoverAttempt) % hosts.length]
109-
}
110-
}
102+
if (processToHostCache.has(processId)) return processToHostCache.get(processId)
111103

112-
export function computeHashSumFromProcessId ({ processId, length }) {
113-
// return processId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
114-
return Number(BigInt(cyrb53(processId)) % BigInt(length))
115-
}
116-
117-
/**
118-
cyrb53 (c) 2018 bryc (github.com/bryc)
119-
License: Public domain (or MIT if needed). Attribution appreciated.
120-
A fast and simple 53-bit string hash function with decent collision resistance.
121-
Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
122-
*/
123-
const cyrb53 = (str, seed = 0) => {
124-
let h1 = 0xdeadbeef ^ seed; let h2 = 0x41c6ce57 ^ seed
125-
for (let i = 0, ch; i < str.length; i++) {
126-
ch = str.charCodeAt(i)
127-
h1 = Math.imul(h1 ^ ch, 2654435761)
128-
h2 = Math.imul(h2 ^ ch, 1597334677)
104+
/**
105+
* Only perform the expensive computation of hash once
106+
*/
107+
const host = hashring.get(processId)
108+
processToHostCache.set(processId, host)
109+
return host
129110
}
130-
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)
131-
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909)
132-
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)
133-
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909)
134-
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
135111
}

0 commit comments

Comments
 (0)