Skip to content

Instantly share code, notes, and snippets.

@tarekziade
Last active June 4, 2016 05:39
Show Gist options
  • Save tarekziade/efa320ee463d9675db6f55f2ffaa7f86 to your computer and use it in GitHub Desktop.
Save tarekziade/efa320ee463d9675db6f55f2ffaa7f86 to your computer and use it in GitHub Desktop.
Consistent Distribution of users across servers
""" Consistent load-balancing.
We have a few servers and we want a load-balancer to
distribute incoming requests across them in a deterministic
and consistent way.
Removing a backend server should not impact users on other
servers.
Adding a backend will generate the redistribution of
users on the nearest 2 servers.
inspired by:
http://techspot.zzzeek.org/2012/07/07/the-absolutely-simplest-consistent-hashing-example
"""
import hashlib
import bisect
from collections import defaultdict
def _hash(key):
return long(hashlib.md5(key).hexdigest(), 16)
def _repl(name, index):
return '%s:%d' % (name, index)
class Servers(object):
def __init__(self, ips=[], replicas=100):
self._ips = {}
self._hashed_ips = []
self.replicas = replicas
for ip in ips:
self.add(ip)
def add(self, ip):
for i in range(self.replicas):
sip = _repl(ip, i)
hashed = _hash(sip)
self._ips[hashed] = sip
bisect.insort(self._hashed_ips, hashed)
def remove(self, ip):
for i in range(self.replicas):
sip = _repl(ip, i)
hashed = _hash(sip)
del self._ips[hashed]
index = bisect.bisect_left(self._hashed_ips, hashed)
del self._hashed_ips[index]
def select(self, username):
hashed = _hash(username)
start = bisect.bisect(self._hashed_ips, hashed,
hi=len(self._hashed_ips)-1)
return self._ips[self._hashed_ips[start]].split(':')[0]
NUM_USERS = 1000000
if __name__ == '__main__':
selection = defaultdict(list)
servers = Servers(['postgres5', 'postgres2', 'postgres3', 'postgres4',
'postgres1'])
users = ['%06d' % i for i in range(NUM_USERS)]
users = users
for user in users:
user_db = servers.select(user)
selection[user_db].append(user)
print '===='
print('Distribution')
for db in selection:
print('%d users in %s' % (len(selection[db]), db))
# removing server 2 and 4
servers.remove('postgres2')
print '===='
selection = defaultdict(list)
for user in users:
user_db = servers.select(user)
selection[user_db].append(user)
for db in selection:
print('%d users in %s' % (len(selection[db]), db))
@tarekziade
Copy link
Author

tarekziade commented May 14, 2016

<ConsistentHashing with <function md5 at 0x1011b3140> hash>
====
Distribution
218247 users in postgres4
197079 users in postgres5
196812 users in postgres2
192469 users in postgres3
195393 users in postgres1
span: 25778
====
259155 users in postgres4
247254 users in postgres5
248181 users in postgres3
245410 users in postgres1
span: 13745
Took 8 seconds


<ConsistentHashing with <function sha256 at 0x1011aeed8> hash>
====
Distribution
217384 users in postgres4
192092 users in postgres5
190393 users in postgres2
191161 users in postgres3
208970 users in postgres1
span: 26991
====
247301 users in postgres4
238765 users in postgres5
259807 users in postgres3
254127 users in postgres1
span: 21042
Took 8 seconds


<ConsistentHashing with <built-in function crc32> hash>
====
Distribution
171316 users in postgres4
231019 users in postgres5
230567 users in postgres2
162721 users in postgres3
204377 users in postgres1
span: 68298
====
223819 users in postgres4
317162 users in postgres5
210481 users in postgres3
248538 users in postgres1
span: 106681
Took 4 seconds


<ConsistentHashing with <function fnv32a at 0x1011aef50> hash>
====
Distribution
123996 users in postgres4
235235 users in postgres5
324004 users in postgres2
191223 users in postgres3
125542 users in postgres1
span: 200008
====
124818 users in postgres4
360886 users in postgres5
351909 users in postgres3
162387 users in postgres1
span: 236068
Took 6 seconds


<ConsistentHashing with <function sha512 at 0x1011aee60> hash>
====
Distribution
218262 users in postgres4
224372 users in postgres5
187891 users in postgres2
180580 users in postgres3
188895 users in postgres1
span: 43792
====
268997 users in postgres4
278203 users in postgres5
222944 users in postgres3
229856 users in postgres1
span: 55259
Took 10 seconds


<RendezVous with <function md5 at 0x1011b3140> hash>
====
Distribution
199971 users in postgres4
199994 users in postgres5
199315 users in postgres2
201333 users in postgres3
199387 users in postgres1
span: 2018
====
249819 users in postgres4
249765 users in postgres5
251381 users in postgres3
249035 users in postgres1
span: 2346
Took 21 seconds


<RendezVous with <function sha256 at 0x1011aeed8> hash>
====
Distribution
199955 users in postgres4
200206 users in postgres5
200333 users in postgres2
199431 users in postgres3
200075 users in postgres1
span: 902
====
249874 users in postgres4
250312 users in postgres5
249492 users in postgres3
250322 users in postgres1
span: 830
Took 26 seconds


<RendezVous with <built-in function crc32> hash>
====
Distribution
125000 users in postgres4
250000 users in postgres5
250000 users in postgres2
125000 users in postgres3
250000 users in postgres1
span: 125000
====
125000 users in postgres4
500000 users in postgres5
125000 users in postgres3
250000 users in postgres1
span: 375000
Took 8 seconds


<RendezVous with <function fnv32a at 0x1011aef50> hash>
====
Distribution
199691 users in postgres4
200721 users in postgres5
201455 users in postgres2
199780 users in postgres3
198353 users in postgres1
span: 3102
====
249691 users in postgres4
252724 users in postgres5
249953 users in postgres3
247632 users in postgres1
span: 5092
Took 32 seconds


<RendezVous with <function sha512 at 0x1011aee60> hash>
====
Distribution
200326 users in postgres4
200590 users in postgres5
200024 users in postgres2
199341 users in postgres3
199719 users in postgres1
span: 1249
====
250431 users in postgres4
250731 users in postgres5
249384 users in postgres3
249454 users in postgres1
span: 1347
Took 32 seconds

@tarekziade
Copy link
Author

And the winner is... RendezVous + sha256

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment