1
1
import { defaultTo , isEmpty , complement , path } from 'ramda'
2
2
import { LRUCache } from 'lru-cache'
3
+ import ConsistentHash from 'consistent-hash'
3
4
4
5
const isNotEmpty = complement ( isEmpty )
5
6
@@ -72,18 +73,13 @@ export function bailoutWith ({ fetch, subrouterUrl, surUrl, owners, processToHos
72
73
*/
73
74
export function determineHostWith ( { hosts = [ ] , bailout } ) {
74
75
/**
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
76
78
*/
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 ) )
87
83
88
84
return async ( { processId, failoverAttempt = 0 } ) => {
89
85
if ( failoverAttempt >= hosts . length ) return
@@ -93,43 +89,23 @@ export function determineHostWith ({ hosts = [], bailout }) {
93
89
if ( bail ) return bail
94
90
}
95
91
96
- /**
97
- * Check cache, and hydrate if necessary
98
- */
99
- let hashSum = processToHostCache . get ( processId )
100
- if ( ! hashSum ) {
92
+ if ( failoverAttempt ) {
101
93
/**
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.
103
97
*/
104
- hashSum = computeHashSumFromProcessId ( { processId, length : hosts . length } )
105
- processToHostCache . set ( processId , hashSum )
98
+ const hosts = hashring . get ( processId , failoverAttempt + 1 )
99
+ return hosts . pop ( )
106
100
}
107
101
108
- return hosts [ ( hashSum + failoverAttempt ) % hosts . length ]
109
- }
110
- }
102
+ if ( processToHostCache . has ( processId ) ) return processToHostCache . get ( processId )
111
103
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
129
110
}
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 )
135
111
}
0 commit comments