Hi Bhaskar,

OK I'm finally done with this. Having reviewed the existing code
allowed me to change my mind on a few points.

1) "hash algorithm" => I realized that this naming is confusing
   because it's used in conjunction with the balance algorithm.
   In practice, both the terms "hash algorithm" or "hash function"
   are used, with the latter being much more common. So I changed
   again the HALG_* flags to HFCN_*.

2) These HFCN_* flags are not contained anymore in the BE_LB_ALGO
   mask, and this significantly simplifies the patches (no more mess
   trying to exclude some masks)

3) In order to make the code more homogenous, there a mask to detect
   the modifier as well, which right now may only be "avalanche".

4) I finally thought that despite "hash-type avalanche" not being used
   anywhere, it was a bit rude to remove it without any information.
   This can happen if someone uses an obsolete doc like those found on
   code.google.com/p/haproxy-docs. So now if "hash-type avalanche" is
   found, the error message explains how to replace it.

5) while running the tests at the end, I found that DJB2 was awful with
   numbers on the input (my test tool simply put a visitor number in a
   URL parameter which I used to balance on). I had 64 servers in the
   farm, and some of them took more than twice the load of the others.
   Worse, when applying consistent hashing on that, only two servers
   got the load for several seconds, then two other ones, then two
   other ones, etc... So in practice the load was spread over all the
   servers over the long term, but the instant load was running only
   on two among 64. Running with 33 servers with map-based resulted
   in only 10 servers taking traffic, as expected. Using sdbm with 64
   servers resulted in only half the servers getting traffic. Adding
   avalanche fixed the issue in both cases, but at the expense of a
   less smooth distribution. So in the end, I changed my mind regarding
   the wt6 function and accepted to reintroduce it because in my tests
   it performed better for such workloads in my tests since I can't
   produce these nasty patterns with it.

Now I have tested all combinations. Everything looks OK and I really
like this new ability to pick the best hashing function for the job
depending on what is being hashed (eg: neither sdbm nor djb2 are good
on IP addresses unless avalanche is used).

I'm attaching the 3 resulting patches here. Please tell me if that's
OK for you, in which case I'm going to merge them.

Thanks!
Willy

>From 07b0c569d23a57b0e5e4c261206fceaf61bf0d4a Mon Sep 17 00:00:00 2001
From: Bhaskar <[email protected]>
Date: Tue, 29 Oct 2013 23:30:51 -0400
Subject: MEDIUM: backend: Enhance hash-type directive with an algorithm
 options

Summary:
In testing at tumblr, we found that using djb2 hashing instead of the
default sdbm hashing resulted is better workload distribution to our backends.

This commit implements a change, that allows the user to specify the hash
function they want to use. It does not limit itself to consistent hashing
scenarios.

The supported hash functions are sdbm (default), and djb2.

For a discussion of the feature and analysis, see mailing list thread
"Consistent hashing alternative to sdbm" :

      http://marc.info/?l=haproxy&m=138213693909219

Note: This change does NOT make changes to new features, for instance,
applying an avalance hashing always being performed before applying
consistent hashing.
---
 Makefile                |  2 +-
 doc/configuration.txt   | 90 ++++++++++++++++++++++++++++++-------------------
 include/common/hash.h   | 28 +++++++++++++++
 include/types/backend.h |  6 ++++
 src/backend.c           | 76 +++++++++++++++++++++++++++--------------
 src/cfgparse.c          | 18 ++++++++--
 src/hash.c              | 61 +++++++++++++++++++++++++++++++++
 7 files changed, 217 insertions(+), 64 deletions(-)
 create mode 100644 include/common/hash.h
 create mode 100644 src/hash.c

diff --git a/Makefile b/Makefile
index 2acaee4..4c30cc0 100644
--- a/Makefile
+++ b/Makefile
@@ -637,7 +637,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o 
src/protocol.o \
        src/stream_interface.o src/dumpstats.o src/proto_tcp.o \
        src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \
        src/acl.o src/sample.o src/memory.o src/freq_ctr.o src/auth.o \
-       src/compression.o src/payload.o
+       src/compression.o src/payload.o src/hash.o
 
 EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \
               $(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \
diff --git a/doc/configuration.txt b/doc/configuration.txt
index ba8057f..643afd9 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2496,45 +2496,65 @@ grace <time>
   simplify it.
 
 
-hash-type <method>
+hash-type <method> <function>
   Specify a method to use for mapping hashes to servers
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
   Arguments :
-    map-based   the hash table is a static array containing all alive servers.
-                The hashes will be very smooth, will consider weights, but will
-                be static in that weight changes while a server is up will be
-                ignored. This means that there will be no slow start. Also,
-                since a server is selected by its position in the array, most
-                mappings are changed when the server count changes. This means
-                that when a server goes up or down, or when a server is added
-                to a farm, most connections will be redistributed to different
-                servers. This can be inconvenient with caches for instance.
-
-    avalanche   this mechanism uses the default map-based hashing described
-                above but applies a full avalanche hash before performing the
-                mapping. The result is a slightly less smooth hash for most
-                situations, but the hash becomes better than pure map-based
-                hashes when the number of servers is a multiple of the size of
-                the input set. When using URI hash with a number of servers
-                multiple of 64, it's desirable to change the hash type to
-                this value.
-
-    consistent  the hash table is a tree filled with many occurrences of each
-                server. The hash key is looked up in the tree and the closest
-                server is chosen. This hash is dynamic, it supports changing
-                weights while the servers are up, so it is compatible with the
-                slow start feature. It has the advantage that when a server
-                goes up or down, only its associations are moved. When a server
-                is added to the farm, only a few part of the mappings are
-                redistributed, making it an ideal algorithm for caches.
-                However, due to its principle, the algorithm will never be very
-                smooth and it may sometimes be necessary to adjust a server's
-                weight or its ID to get a more balanced distribution. In order
-                to get the same distribution on multiple load balancers, it is
-                important that all servers have the same IDs.
-
-  The default hash type is "map-based" and is recommended for most usages.
+    <method> is the method used to select a server from the hash computed by
+             the <function> :
+
+      map-based   the hash table is a static array containing all alive 
servers.
+                  The hashes will be very smooth, will consider weights, but
+                  will be static in that weight changes while a server is up
+                  will be ignored. This means that there will be no slow start.
+                  Also, since a server is selected by its position in the 
array,
+                  most mappings are changed when the server count changes. This
+                  means that when a server goes up or down, or when a server is
+                  added to a farm, most connections will be redistributed to
+                  different servers. This can be inconvenient with caches for
+                  instance.
+
+      avalanche   this mechanism uses the default map-based hashing described
+                  above but applies a full avalanche hash before performing the
+                  mapping. The result is a slightly less smooth hash for most
+                  situations, but the hash becomes better than pure map-based
+                  hashes when the number of servers is a multiple of the size 
of
+                  the input set. When using URI hash with a number of servers
+                  multiple of 64, it's desirable to change the hash type to
+                  this value.
+
+      consistent  the hash table is a tree filled with many occurrences of each
+                  server. The hash key is looked up in the tree and the closest
+                  server is chosen. This hash is dynamic, it supports changing
+                  weights while the servers are up, so it is compatible with 
the
+                  slow start feature. It has the advantage that when a server
+                  goes up or down, only its associations are moved. When a
+                  server is added to the farm, only a few part of the mappings
+                  are redistributed, making it an ideal method for caches.
+                  However, due to its principle, the distribution will never be
+                  very smooth and it may sometimes be necessary to adjust a
+                  server's weight or its ID to get a more balanced 
distribution.
+                  In order to get the same distribution on multiple load
+                  balancers, it is important that all servers have the exact
+                  same IDs. Note: by default, a full avalanche hash is always
+                  performed before applying the consistent hash.
+
+    <function> is the hash function to be used :
+
+       sdbm   this function was created intially for sdbm (a public-domain
+              reimplementation of ndbm) database library. It was found to do
+              well in scrambling bits, causing better distribution of the keys
+              and fewer splits. It also happens to be a good general hashing
+              function with good distribution.
+
+       djb2   this function was first proposed by Dan Bernstein many years ago
+              on comp.lang.c. Studies have shown that for certain workload this
+              function provides a better distribution than sdbm.
+
+  The default hash type is "map-based" and is recommended for most usages. The
+  default function is "sdbm", the selection of a function should be based on
+  the range of the values being hashed.
 
   See also : "balance", "server"
 
diff --git a/include/common/hash.h b/include/common/hash.h
new file mode 100644
index 0000000..f6875c9
--- /dev/null
+++ b/include/common/hash.h
@@ -0,0 +1,28 @@
+/*
+ * include/common/hash.h
+ * Macros for different hashing function.
+ *
+ * Copyright (C) 2000-2011 Willy Tarreau - [email protected]
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.1
+ * exclusively.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
+ */
+
+#ifndef _COMMON_HASH_H_
+#define _COMMON_HASH_H_
+
+unsigned long hash_djb2(const char *key, int len);
+unsigned long hash_sdbm(const char *key, int len);
+
+#endif /* _COMMON_HASH_H_ */
diff --git a/include/types/backend.h b/include/types/backend.h
index 1183b36..1a433cf 100644
--- a/include/types/backend.h
+++ b/include/types/backend.h
@@ -108,6 +108,12 @@
 #define BE_LB_HASH_AVAL   0x200000 /* run an avalanche hash before a map */
 #define BE_LB_HASH_TYPE   0x300000 /* get/clear hash types */
 
+/* BE_LB_HFCN_* is the hash function, to be used with BE_LB_HASH_FUNC */
+#define BE_LB_HFCN_SDBM   0x000000 /* sdbm hash */
+#define BE_LB_HFCN_DJB2   0x400000 /* djb2 hash */
+#define BE_LB_HASH_FUNC   0xC00000 /* get/clear hash function */
+
+
 /* various constants */
 
 /* The scale factor between user weight and effective weight allows smooth
diff --git a/src/backend.c b/src/backend.c
index 48c8761..bf4a81d 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -23,6 +23,7 @@
 #include <common/compat.h>
 #include <common/config.h>
 #include <common/debug.h>
+#include <common/hash.h>
 #include <common/ticks.h>
 #include <common/time.h>
 
@@ -51,6 +52,25 @@
 #include <proto/stream_interface.h>
 #include <proto/task.h>
 
+/* helper function to invoke the correct hash method */
+static unsigned long gen_hash(const struct proxy* px, const char* key, 
unsigned long len)
+{
+       unsigned long hash;
+
+       switch (px->lbprm.algo & BE_LB_HASH_FUNC) {
+       case BE_LB_HFCN_DJB2:
+               hash = hash_djb2(key, len);
+               break;
+       case BE_LB_HFCN_SDBM:
+               /* this is the default hash function */
+       default:
+               hash = hash_sdbm(key, len);
+               break;
+       }
+
+       return hash;
+}
+
 /*
  * This function recounts the number of usable active and backup servers for
  * proxy <p>. These numbers are returned into the p->srv_act and p->srv_bck.
@@ -153,6 +173,7 @@ struct server *get_server_uh(struct proxy *px, char *uri, 
int uri_len)
        unsigned long hash = 0;
        int c;
        int slashes = 0;
+       const char *start, *end;
 
        if (px->lbprm.tot_weight == 0)
                return NULL;
@@ -164,8 +185,9 @@ struct server *get_server_uh(struct proxy *px, char *uri, 
int uri_len)
        if (px->uri_len_limit)
                uri_len = MIN(uri_len, px->uri_len_limit);
 
+       start = end = uri;
        while (uri_len--) {
-               c = *uri++;
+               c = *end++;
                if (c == '/') {
                        slashes++;
                        if (slashes == px->uri_dirs_depth1) /* depth+1 */
@@ -173,9 +195,10 @@ struct server *get_server_uh(struct proxy *px, char *uri, 
int uri_len)
                }
                else if (c == '?' && !px->uri_whole)
                        break;
-
-               hash = c + (hash << 6) + (hash << 16) - hash;
        }
+
+       hash = gen_hash(px, start, (end - start));
+
        if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
                hash = full_hash(hash);
  hash_done:
@@ -197,6 +220,7 @@ struct server *get_server_uh(struct proxy *px, char *uri, 
int uri_len)
 struct server *get_server_ph(struct proxy *px, const char *uri, int uri_len)
 {
        unsigned long hash = 0;
+       const char *start, *end;
        const char *p;
        const char *params;
        int plen;
@@ -223,15 +247,18 @@ struct server *get_server_ph(struct proxy *px, const char 
*uri, int uri_len)
                                 * skip the equal symbol
                                 */
                                p += plen + 1;
+                               start = end = p;
                                uri_len -= plen + 1;
 
-                               while (uri_len && *p != '&') {
-                                       hash = *p + (hash << 6) + (hash << 16) 
- hash;
+                               while (uri_len && *end != '&') {
                                        uri_len--;
-                                       p++;
+                                       end++;
                                }
+                               hash = gen_hash(px, start, (end - start));
+
                                if ((px->lbprm.algo & BE_LB_HASH_TYPE) != 
BE_LB_HASH_MAP)
                                        hash = full_hash(hash);
+
                                if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
                                        return chash_get_server_hash(px, hash);
                                else
@@ -263,6 +290,7 @@ struct server *get_server_ph_post(struct session *s)
        unsigned long    len  = msg->body_len;
        const char      *params = b_ptr(req->buf, (int)(msg->sov - 
req->buf->o));
        const char      *p    = params;
+       const char      *start, *end;
 
        if (len > buffer_len(req->buf) - msg->sov)
                len = buffer_len(req->buf) - msg->sov;
@@ -282,12 +310,13 @@ struct server *get_server_ph_post(struct session *s)
                                 * skip the equal symbol
                                 */
                                p += plen + 1;
+                               start = end = p;
                                len -= plen + 1;
 
-                               while (len && *p != '&') {
+                               while (len && *end != '&') {
                                        if (unlikely(!HTTP_IS_TOKEN(*p))) {
                                                /* if in a POST, body must be 
URI encoded or it's not a URI.
-                                                * Do not interprete any 
possible binary data as a parameter.
+                                                * Do not interpret any 
possible binary data as a parameter.
                                                 */
                                                if (likely(HTTP_IS_LWS(*p))) /* 
eol, uncertain uri len */
                                                        break;
@@ -295,13 +324,15 @@ struct server *get_server_ph_post(struct session *s)
                                                                              * 
This body does not contain parameters.
                                                                              */
                                        }
-                                       hash = *p + (hash << 6) + (hash << 16) 
- hash;
                                        len--;
-                                       p++;
+                                       end++;
                                        /* should we break if vlen exceeds 
limit? */
                                }
+                               hash = gen_hash(px, start, (end - start));
+
                                if ((px->lbprm.algo & BE_LB_HASH_TYPE) != 
BE_LB_HASH_MAP)
                                        hash = full_hash(hash);
+
                                if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
                                        return chash_get_server_hash(px, hash);
                                else
@@ -338,6 +369,7 @@ struct server *get_server_hh(struct session *s)
        unsigned long    len;
        struct hdr_ctx   ctx;
        const char      *p;
+       const char *start, *end;
 
        /* tot_weight appears to mean srv_count */
        if (px->lbprm.tot_weight == 0)
@@ -362,14 +394,11 @@ struct server *get_server_hh(struct session *s)
        len = ctx.vlen;
        p = (char *)ctx.line + ctx.val;
        if (!px->hh_match_domain) {
-               while (len) {
-                       hash = *p + (hash << 6) + (hash << 16) - hash;
-                       len--;
-                       p++;
-               }
+               hash = gen_hash(px, p, len);
        } else {
                int dohash = 0;
                p += len - 1;
+               start = end = p;
                /* special computation, use only main domain name, not tld/host
                 * going back from the end of string, start hashing at first
                 * dot stop at next.
@@ -378,17 +407,20 @@ struct server *get_server_hh(struct session *s)
                 */
                while (len) {
                        if (*p == '.') {
-                               if (!dohash)
+                               if (!dohash) {
                                        dohash = 1;
+                                       start = end = p - 1;
+                               }
                                else
                                        break;
                        } else {
                                if (dohash)
-                                       hash = *p + (hash << 6) + (hash << 16) 
- hash;
+                                       start--;
                        }
                        len--;
                        p--;
                }
+               hash = gen_hash(px, start, (end - start));
        }
        if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
                hash = full_hash(hash);
@@ -405,7 +437,6 @@ struct server *get_server_rch(struct session *s)
        unsigned long    hash = 0;
        struct proxy    *px   = s->be;
        unsigned long    len;
-       const char      *p;
        int              ret;
        struct sample    smp;
        int rewind;
@@ -433,12 +464,8 @@ struct server *get_server_rch(struct session *s)
        /* Found a the hh_name in the headers.
         * we will compute the hash based on this value ctx.val.
         */
-       p = smp.data.str.str;
-       while (len) {
-               hash = *p + (hash << 6) + (hash << 16) - hash;
-               len--;
-               p++;
-       }
+       hash = gen_hash(px, smp.data.str.str, len);
+
        if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
                hash = full_hash(hash);
  hash_done:
@@ -1318,7 +1345,6 @@ int backend_parse_balance(const char **args, char **err, 
struct proxy *curproxy)
                        }
                        curproxy->hh_match_domain = 1;
                }
-
        }
        else if (!strncmp(args[0], "rdp-cookie", 10)) {
                curproxy->lbprm.algo &= ~BE_LB_ALGO;
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 41c1949..051f600 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -4085,19 +4085,18 @@ stats_error_parsing:
                }
        }
        else if (!strcmp(args[0], "hash-type")) { /* set hashing method */
+               curproxy->lbprm.algo &= ~(BE_LB_HASH_TYPE | BE_LB_HASH_FUNC);
+
                if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], 
NULL))
                        err_code |= ERR_WARN;
 
                if (strcmp(args[1], "consistent") == 0) {       /* use 
consistent hashing */
-                       curproxy->lbprm.algo &= ~BE_LB_HASH_TYPE;
                        curproxy->lbprm.algo |= BE_LB_HASH_CONS;
                }
                else if (strcmp(args[1], "map-based") == 0) {   /* use 
map-based hashing */
-                       curproxy->lbprm.algo &= ~BE_LB_HASH_TYPE;
                        curproxy->lbprm.algo |= BE_LB_HASH_MAP;
                }
                else if (strcmp(args[1], "avalanche") == 0) {   /* use full 
hash before map-based hashing */
-                       curproxy->lbprm.algo &= ~BE_LB_HASH_TYPE;
                        curproxy->lbprm.algo |= BE_LB_HASH_AVAL;
                }
                else {
@@ -4105,6 +4104,19 @@ stats_error_parsing:
                        err_code |= ERR_ALERT | ERR_FATAL;
                        goto out;
                }
+
+               /* set the hash function to use */
+               if (!*args[2]) {
+                       curproxy->lbprm.algo |= BE_LB_HFCN_SDBM;
+               } else if (!strcmp(args[2], "sdbm")) {
+                       curproxy->lbprm.algo |= BE_LB_HFCN_SDBM;
+               } else if (!strcmp(args[2], "djb2")) {
+                       curproxy->lbprm.algo |= BE_LB_HFCN_DJB2;
+               } else {
+                       Alert("parsing [%s:%d] : '%s' only supports 'sdbm' and 
'djb2' hash functions.\n", file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
        }
        else if (!strcmp(args[0], "server") || !strcmp(args[0], 
"default-server")) {  /* server address */
                int cur_arg;
diff --git a/src/hash.c b/src/hash.c
new file mode 100644
index 0000000..8da3003
--- /dev/null
+++ b/src/hash.c
@@ -0,0 +1,61 @@
+/*
+ * Hash function implementation
+ *
+ * See mailing list thread on "Consistent hashing alternative to sdbm"
+ * http://marc.info/?l=haproxy&m=138213693909219
+ *
+ * Copyright 2000-2010 Willy Tarreau <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+
+#include <common/hash.h>
+
+
+unsigned long hash_djb2(const char *key, int len)
+{
+       unsigned long hash = 5381;
+
+       /* the hash unrolled eight times */
+       for (; len >= 8; len -= 8) {
+               hash = ((hash << 5) + hash) + *key++;
+               hash = ((hash << 5) + hash) + *key++;
+               hash = ((hash << 5) + hash) + *key++;
+               hash = ((hash << 5) + hash) + *key++;
+               hash = ((hash << 5) + hash) + *key++;
+               hash = ((hash << 5) + hash) + *key++;
+               hash = ((hash << 5) + hash) + *key++;
+               hash = ((hash << 5) + hash) + *key++;
+       }
+       switch (len) {
+               case 7: hash = ((hash << 5) + hash) + *key++; /* fallthrough... 
*/
+               case 6: hash = ((hash << 5) + hash) + *key++; /* fallthrough... 
*/
+               case 5: hash = ((hash << 5) + hash) + *key++; /* fallthrough... 
*/
+               case 4: hash = ((hash << 5) + hash) + *key++; /* fallthrough... 
*/
+               case 3: hash = ((hash << 5) + hash) + *key++; /* fallthrough... 
*/
+               case 2: hash = ((hash << 5) + hash) + *key++; /* fallthrough... 
*/
+               case 1: hash = ((hash << 5) + hash) + *key++; break;
+               default: /* case 0: */ break;
+       }
+       return hash;
+}
+
+unsigned long hash_sdbm(const char *key, int len)
+{
+       unsigned long hash = 0;
+       int c;
+
+       while (len--) {
+               c = *key++;
+               hash = c + (hash << 6) + (hash << 16) - hash;
+       }
+
+       return hash;
+}
+
+
-- 
1.7.12.1

>From fee609f600ae4e258e39f1fb0a7f4a7826a56543 Mon Sep 17 00:00:00 2001
From: Bhaskar Maddala <[email protected]>
Date: Tue, 5 Nov 2013 11:54:02 -0500
Subject: MEDIUM: backend: Implement avalanche as a modifier of the hashing
 functions.

Summary:
Avalanche is supported not as a native hashing choice, but a modifier
on the hashing function. Note that this means that possible configs
written after 1.5-dev4 using "hash-type avalanche" will get an informative
error instead. But as discussed on the mailing list it seems nobody ever
used it anyway, so let's fix it before the final 1.5 release.

The default values were selected for backward compatibility with previous
releases, as discussed on the mailing list, which means that the consistent
hashing will still apply the avalanche hash by default when no explicit
algorithm is specified.

Examples
  (default) hash-type map-based
        Map based hashing using sdbm without avalanche

  (default) hash-type consistent
        Consistent hashing using sdbm with avalanche

Additional Examples:

  (a) hash-type map-based sdbm
        Same as default for map-based above
  (b) hash-type map-based sdbm avalanche
        Map based hashing using sdbm with avalanche
  (c) hash-type map-based djb2
        Map based hashing using djb2 without avalanche
  (d) hash-type map-based djb2 avalanche
        Map based hashing using djb2 with avalanche
  (e) hash-type consistent sdbm avalanche
        Same as default for consistent above
  (f) hash-type consistent sdbm
        Consistent hashing using sdbm without avalanche
  (g) hash-type consistent djb2
        Consistent hashing using djb2 without avalanche
  (h) hash-type consistent djb2 avalanche
        Consistent hashing using djb2 with avalanche
---
 doc/configuration.txt   | 39 ++++++++++++++++++++++++--------------
 include/types/backend.h |  7 +++++--
 src/backend.c           | 12 ++++++------
 src/cfgparse.c          | 50 ++++++++++++++++++++++++++++++++++++++-----------
 4 files changed, 75 insertions(+), 33 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 643afd9..c430ec3 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2496,7 +2496,7 @@ grace <time>
   simplify it.
 
 
-hash-type <method> <function>
+hash-type <method> <function> <modifier>
   Specify a method to use for mapping hashes to servers
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
@@ -2515,15 +2515,6 @@ hash-type <method> <function>
                   different servers. This can be inconvenient with caches for
                   instance.
 
-      avalanche   this mechanism uses the default map-based hashing described
-                  above but applies a full avalanche hash before performing the
-                  mapping. The result is a slightly less smooth hash for most
-                  situations, but the hash becomes better than pure map-based
-                  hashes when the number of servers is a multiple of the size 
of
-                  the input set. When using URI hash with a number of servers
-                  multiple of 64, it's desirable to change the hash type to
-                  this value.
-
       consistent  the hash table is a tree filled with many occurrences of each
                   server. The hash key is looked up in the tree and the closest
                   server is chosen. This hash is dynamic, it supports changing
@@ -2537,8 +2528,8 @@ hash-type <method> <function>
                   server's weight or its ID to get a more balanced 
distribution.
                   In order to get the same distribution on multiple load
                   balancers, it is important that all servers have the exact
-                  same IDs. Note: by default, a full avalanche hash is always
-                  performed before applying the consistent hash.
+                  same IDs. Note: consistent hash uses sdbm and avalanche if no
+                  hash function is specified.
 
     <function> is the hash function to be used :
 
@@ -2546,11 +2537,31 @@ hash-type <method> <function>
               reimplementation of ndbm) database library. It was found to do
               well in scrambling bits, causing better distribution of the keys
               and fewer splits. It also happens to be a good general hashing
-              function with good distribution.
+              function with good distribution, unless the total server weight
+              is a multiple of 64, in which case applying the avalanche
+              modifier may help.
 
        djb2   this function was first proposed by Dan Bernstein many years ago
               on comp.lang.c. Studies have shown that for certain workload this
-              function provides a better distribution than sdbm.
+              function provides a better distribution than sdbm. It generally
+              works well with text-based inputs though it can perform extremely
+              poorly with numeric-only input or when the total server weight is
+              a multiple of 33, unless the avalanche modifier is also used.
+
+    <modifier> indicates an optional method applied after hashing the key :
+
+       avalanche   This directive indicates that the result from the hash
+                   function above should not be used in its raw form but that
+                   a 4-byte full avalanche hash must be applied first. The
+                   purpose of this step is to mix the resulting bits from the
+                   previous hash in order to avoid any undesired effect when
+                   the input contains some limited values or when the number of
+                   servers is a multiple of one of the hash's components (64
+                   for SDBM, 33 for DJB2). Enabling avalanche tends to make the
+                   result less predictable, but it's also not as smooth as when
+                   using the original function. Some testing might be needed
+                   with some workloads. This hash is one of the many proposed
+                   by Bob Jenkins.
 
   The default hash type is "map-based" and is recommended for most usages. The
   default function is "sdbm", the selection of a function should be based on
diff --git a/include/types/backend.h b/include/types/backend.h
index 1a433cf..57d17bd 100644
--- a/include/types/backend.h
+++ b/include/types/backend.h
@@ -105,8 +105,11 @@
 /* hash types */
 #define BE_LB_HASH_MAP    0x000000 /* map-based hash (default) */
 #define BE_LB_HASH_CONS   0x100000 /* consistent hashbit to indicate a dynamic 
algorithm */
-#define BE_LB_HASH_AVAL   0x200000 /* run an avalanche hash before a map */
-#define BE_LB_HASH_TYPE   0x300000 /* get/clear hash types */
+#define BE_LB_HASH_TYPE   0x100000 /* get/clear hash types */
+
+/* additional modifier on top of the hash function (only avalanche right now) 
*/
+#define BE_LB_HMOD_AVAL   0x200000  /* avalanche modifier */
+#define BE_LB_HASH_MOD    0x200000  /* get/clear hash modifier */
 
 /* BE_LB_HFCN_* is the hash function, to be used with BE_LB_HASH_FUNC */
 #define BE_LB_HFCN_SDBM   0x000000 /* sdbm hash */
diff --git a/src/backend.c b/src/backend.c
index bf4a81d..e044430 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -147,7 +147,7 @@ struct server *get_server_sh(struct proxy *px, const char 
*addr, int len)
                h ^= ntohl(*(unsigned int *)(&addr[l]));
                l += sizeof (int);
        }
-       if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
+       if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL)
                h = full_hash(h);
  hash_done:
        if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
@@ -199,7 +199,7 @@ struct server *get_server_uh(struct proxy *px, char *uri, 
int uri_len)
 
        hash = gen_hash(px, start, (end - start));
 
-       if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
+       if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL)
                hash = full_hash(hash);
  hash_done:
        if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
@@ -256,7 +256,7 @@ struct server *get_server_ph(struct proxy *px, const char 
*uri, int uri_len)
                                }
                                hash = gen_hash(px, start, (end - start));
 
-                               if ((px->lbprm.algo & BE_LB_HASH_TYPE) != 
BE_LB_HASH_MAP)
+                               if ((px->lbprm.algo & BE_LB_HASH_MOD) == 
BE_LB_HMOD_AVAL)
                                        hash = full_hash(hash);
 
                                if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
@@ -330,7 +330,7 @@ struct server *get_server_ph_post(struct session *s)
                                }
                                hash = gen_hash(px, start, (end - start));
 
-                               if ((px->lbprm.algo & BE_LB_HASH_TYPE) != 
BE_LB_HASH_MAP)
+                               if ((px->lbprm.algo & BE_LB_HASH_MOD) == 
BE_LB_HMOD_AVAL)
                                        hash = full_hash(hash);
 
                                if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
@@ -422,7 +422,7 @@ struct server *get_server_hh(struct session *s)
                }
                hash = gen_hash(px, start, (end - start));
        }
-       if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
+       if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL)
                hash = full_hash(hash);
  hash_done:
        if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
@@ -466,7 +466,7 @@ struct server *get_server_rch(struct session *s)
         */
        hash = gen_hash(px, smp.data.str.str, len);
 
-       if ((px->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP)
+       if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL)
                hash = full_hash(hash);
  hash_done:
        if (px->lbprm.algo & BE_LB_LKUP_CHTREE)
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 051f600..bb79a34 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -4085,7 +4085,13 @@ stats_error_parsing:
                }
        }
        else if (!strcmp(args[0], "hash-type")) { /* set hashing method */
-               curproxy->lbprm.algo &= ~(BE_LB_HASH_TYPE | BE_LB_HASH_FUNC);
+               /**
+                * The syntax for hash-type config element is
+                * hash-type {map-based|consistent} [[<algo>] avalanche]
+                *
+                * The default hash function is sdbm for map-based and 
sdbm+avalanche for consistent.
+                */
+               curproxy->lbprm.algo &= ~(BE_LB_HASH_TYPE | BE_LB_HASH_FUNC | 
BE_LB_HASH_MOD);
 
                if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], 
NULL))
                        err_code |= ERR_WARN;
@@ -4096,26 +4102,48 @@ stats_error_parsing:
                else if (strcmp(args[1], "map-based") == 0) {   /* use 
map-based hashing */
                        curproxy->lbprm.algo |= BE_LB_HASH_MAP;
                }
-               else if (strcmp(args[1], "avalanche") == 0) {   /* use full 
hash before map-based hashing */
-                       curproxy->lbprm.algo |= BE_LB_HASH_AVAL;
+               else if (strcmp(args[1], "avalanche") == 0) {
+                       Alert("parsing [%s:%d] : experimental feature '%s %s' 
is not supported anymore, please use '%s map-based sdbm avalanche' instead.\n", 
file, linenum, args[0], args[1], args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
                }
                else {
-                       Alert("parsing [%s:%d] : '%s' only supports 
'avalanche', 'consistent' and 'map-based'.\n", file, linenum, args[0]);
+                       Alert("parsing [%s:%d] : '%s' only supports 
'consistent' and 'map-based'.\n", file, linenum, args[0]);
                        err_code |= ERR_ALERT | ERR_FATAL;
                        goto out;
                }
 
                /* set the hash function to use */
                if (!*args[2]) {
+                       /* the default algo is sdbm */
                        curproxy->lbprm.algo |= BE_LB_HFCN_SDBM;
-               } else if (!strcmp(args[2], "sdbm")) {
-                       curproxy->lbprm.algo |= BE_LB_HFCN_SDBM;
-               } else if (!strcmp(args[2], "djb2")) {
-                       curproxy->lbprm.algo |= BE_LB_HFCN_DJB2;
+
+                       /* if consistent with no argument, then avalanche 
modifier is also applied */
+                       if ((curproxy->lbprm.algo & BE_LB_HASH_TYPE) == 
BE_LB_HASH_CONS)
+                               curproxy->lbprm.algo |= BE_LB_HMOD_AVAL;
                } else {
-                       Alert("parsing [%s:%d] : '%s' only supports 'sdbm' and 
'djb2' hash functions.\n", file, linenum, args[0]);
-                       err_code |= ERR_ALERT | ERR_FATAL;
-                       goto out;
+                       /* set the hash function */
+                       if (!strcmp(args[2], "sdbm")) {
+                               curproxy->lbprm.algo |= BE_LB_HFCN_SDBM;
+                       }
+                       else if (!strcmp(args[2], "djb2")) {
+                               curproxy->lbprm.algo |= BE_LB_HFCN_DJB2;
+                       }
+                       else {
+                               Alert("parsing [%s:%d] : '%s' only supports 
'sdbm' and 'djb2' hash functions.\n", file, linenum, args[0]);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+
+                       /* set the hash modifier */
+                       if (!strcmp(args[3], "avalanche")) {
+                               curproxy->lbprm.algo |= BE_LB_HMOD_AVAL;
+                       }
+                       else if (*args[3]) {
+                               Alert("parsing [%s:%d] : '%s' only supports 
'avalanche' as a modifier for hash functions.\n", file, linenum, args[0]);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
                }
        }
        else if (!strcmp(args[0], "server") || !strcmp(args[0], 
"default-server")) {  /* server address */
-- 
1.7.12.1

>From 86844eb8526edc6e8895fc2663786ad6488d1a70 Mon Sep 17 00:00:00 2001
From: Willy Tarreau <[email protected]>
Date: Thu, 14 Nov 2013 14:30:35 +0100
Subject: MEDIUM: backend: add support for the wt6 hash

This function was designed for haproxy while testing other functions
in the past. Initially it was not planned to be used given the not
very interesting numbers it showed on real URL data : it is not as
smooth as the other ones. But later tests showed that the other ones
are extremely sensible to the server count and the type of input data,
especially DJB2 which must not be used on numeric input. So in fact
this function is still a generally average performer and it can make
sense to merge it in the end, as it can provide an alternative to
sdbm+avalanche or djb2+avalanche for consistent hashing or when hashing
on numeric data such as a source IP address or a visitor identifier in
a URL parameter.
---
 doc/configuration.txt   |  8 ++++++++
 include/common/hash.h   |  1 +
 include/types/backend.h |  1 +
 src/backend.c           |  3 +++
 src/cfgparse.c          |  4 +++-
 src/hash.c              | 27 +++++++++++++++++++++++++++
 6 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index c430ec3..0b4844b 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2548,6 +2548,14 @@ hash-type <method> <function> <modifier>
               poorly with numeric-only input or when the total server weight is
               a multiple of 33, unless the avalanche modifier is also used.
 
+       wt6    this function was designed for haproxy while testing other
+              functions in the past. It is not as smooth as the other ones, but
+              is much less sensible to the input data set or to the number of
+              servers. It can make sense as an alternative to sdbm+avalanche or
+              djb2+avalanche for consistent hashing or when hashing on numeric
+              data such as a source IP address or a visitor identifier in a URL
+              parameter.
+
     <modifier> indicates an optional method applied after hashing the key :
 
        avalanche   This directive indicates that the result from the hash
diff --git a/include/common/hash.h b/include/common/hash.h
index f6875c9..379bf89 100644
--- a/include/common/hash.h
+++ b/include/common/hash.h
@@ -23,6 +23,7 @@
 #define _COMMON_HASH_H_
 
 unsigned long hash_djb2(const char *key, int len);
+unsigned long hash_wt6(const char *key, int len);
 unsigned long hash_sdbm(const char *key, int len);
 
 #endif /* _COMMON_HASH_H_ */
diff --git a/include/types/backend.h b/include/types/backend.h
index 57d17bd..6325be2 100644
--- a/include/types/backend.h
+++ b/include/types/backend.h
@@ -114,6 +114,7 @@
 /* BE_LB_HFCN_* is the hash function, to be used with BE_LB_HASH_FUNC */
 #define BE_LB_HFCN_SDBM   0x000000 /* sdbm hash */
 #define BE_LB_HFCN_DJB2   0x400000 /* djb2 hash */
+#define BE_LB_HFCN_WT6    0x800000 /* wt6 hash */
 #define BE_LB_HASH_FUNC   0xC00000 /* get/clear hash function */
 
 
diff --git a/src/backend.c b/src/backend.c
index e044430..7689cea 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -61,6 +61,9 @@ static unsigned long gen_hash(const struct proxy* px, const 
char* key, unsigned
        case BE_LB_HFCN_DJB2:
                hash = hash_djb2(key, len);
                break;
+       case BE_LB_HFCN_WT6:
+               hash = hash_wt6(key, len);
+               break;
        case BE_LB_HFCN_SDBM:
                /* this is the default hash function */
        default:
diff --git a/src/cfgparse.c b/src/cfgparse.c
index bb79a34..28507dd 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -4128,9 +4128,11 @@ stats_error_parsing:
                        }
                        else if (!strcmp(args[2], "djb2")) {
                                curproxy->lbprm.algo |= BE_LB_HFCN_DJB2;
+                       } else if (!strcmp(args[2], "wt6")) {
+                               curproxy->lbprm.algo |= BE_LB_HFCN_WT6;
                        }
                        else {
-                               Alert("parsing [%s:%d] : '%s' only supports 
'sdbm' and 'djb2' hash functions.\n", file, linenum, args[0]);
+                               Alert("parsing [%s:%d] : '%s' only supports 
'sdbm', 'djb2' or 'wt6' hash functions.\n", file, linenum, args[0]);
                                err_code |= ERR_ALERT | ERR_FATAL;
                                goto out;
                        }
diff --git a/src/hash.c b/src/hash.c
index 8da3003..034685e 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -17,6 +17,33 @@
 #include <common/hash.h>
 
 
+unsigned long hash_wt6(const char *key, int len)
+{
+       unsigned h0 = 0xa53c965aUL;
+       unsigned h1 = 0x5ca6953aUL;
+       unsigned step0 = 6;
+       unsigned step1 = 18;
+
+       for (; len > 0; len--) {
+               unsigned int t;
+
+               t = ((unsigned int)*key);
+               key++;
+
+               h0 = ~(h0 ^ t);
+               h1 = ~(h1 + t);
+
+               t  = (h1 << step0) | (h1 >> (32-step0));
+               h1 = (h0 << step1) | (h0 >> (32-step1));
+               h0 = t;
+
+               t = ((h0 >> 16) ^ h1) & 0xffff;
+               step0 = t & 0x1F;
+               step1 = t >> 11;
+       }
+       return h0 ^ h1;
+}
+
 unsigned long hash_djb2(const char *key, int len)
 {
        unsigned long hash = 5381;
-- 
1.7.12.1

Reply via email to