diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 08a67f4ac997564a981bcab580428a248e77b7b1..764d85c154a1099d74d74ddd10116b891b206649 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -62,3 +62,5 @@ add_library(box
     lua/error.cc
     lua/session.cc
     ${bin_sources})
+
+# target_link_libraries(box salad)
diff --git a/src/ffisyms.cc b/src/ffisyms.cc
index 808d7af999b4c1ff84bdc87ea22c4d683cc4bea2..e0d34d7703811759ade585ed49df800b980dd77f 100644
--- a/src/ffisyms.cc
+++ b/src/ffisyms.cc
@@ -11,6 +11,7 @@
 #include "lua/bsdsocket.h"
 #include "lua/digest.h"
 #include "base64.h"
+#include <lib/salad/guava.h>
 
 /*
  * A special hack to cc/ld to keep symbols in an optimized binary.
@@ -50,5 +51,6 @@ void *ffi_symbols[] = {
 	(void *) base64_decode,
 	(void *) base64_encode,
 	(void *) base64_bufsize,
-	(void *) SHA1internal
+	(void *) SHA1internal,
+	(void *) guava
 };
diff --git a/src/lib/salad/CMakeLists.txt b/src/lib/salad/CMakeLists.txt
index 9db3d800e80b8288bb0ed709e7b0e65865a53c06..26b8a26eacdd53eefbdc7f2b85746e4117a6e1bd 100644
--- a/src/lib/salad/CMakeLists.txt
+++ b/src/lib/salad/CMakeLists.txt
@@ -1,3 +1,3 @@
-set(lib_sources rope.c rlist.c rtree.c)
+set(lib_sources rope.c rlist.c rtree.c guava.c)
 set_source_files_compile_flags(${lib_sources})
 add_library(salad ${lib_sources})
diff --git a/src/lib/salad/guava.c b/src/lib/salad/guava.c
new file mode 100644
index 0000000000000000000000000000000000000000..84fc5cbfa2f91e7a6a1219513ca7af6f5c2e4d59
--- /dev/null
+++ b/src/lib/salad/guava.c
@@ -0,0 +1,55 @@
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "lib/salad/guava.h"
+
+#include <stdint.h>
+
+static const int64_t K = 2862933555777941757;
+static const double  D = 0x1.0p31;
+
+static inline double lcg(int64_t *state)
+{
+	return (double )((int32_t)(((uint64_t )*state >> 33) + 1)) / D;
+}
+
+int32_t
+guava(int64_t state, int32_t buckets)
+{
+	int32_t candidate = 0;
+	int32_t next;
+	while (1) {
+		state = K * state + 1;
+		next = (int32_t)((candidate + 1) / lcg(&state));
+		if (next >= 0 && next < buckets)
+			candidate = next;
+		else
+			return candidate;
+	}
+}
diff --git a/src/lib/salad/guava.h b/src/lib/salad/guava.h
new file mode 100644
index 0000000000000000000000000000000000000000..6a9e877dad8c892c7a59b2c1e381c70132b3c7de
--- /dev/null
+++ b/src/lib/salad/guava.h
@@ -0,0 +1,46 @@
+#ifndef TARANTOOL_LIB_GUAVA_H_INCLUDED
+#define TARANTOOL_LIB_GUAVA_H_INCLUDED
+
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+int32_t
+guava(int64_t state, int32_t buckets);
+
+#if defined(__cplusplus)
+} /* extern C */
+#endif
+
+#endif /* TARANTOOL_LIB_GUAVA_H_INCLUDED */
diff --git a/src/lua/digest.lua b/src/lua/digest.lua
index 1e41483f6622795b1e4d47b870e3fdc2edae0041..724bfb24d7825ae9c86009c879495f59f9c4f75d 100644
--- a/src/lua/digest.lua
+++ b/src/lua/digest.lua
@@ -21,9 +21,9 @@ ffi.cdef[[
     /* from libc */
     int snprintf(char *str, size_t size, const char *format, ...);
 
-
     typedef uint32_t (*crc32_func)(uint32_t crc,
         const unsigned char *buf, unsigned int len);
+    extern int32_t guava(int64_t state, int32_t buckets);
     extern crc32_func crc32_calc;
 
    /* base64 */
@@ -53,13 +53,12 @@ end
 
 local def = {
     sha     = { 'SHA',    20 },
---     sha1    = { 'SHA1',   20 },
     sha224  = { 'SHA224', 28 },
     sha256  = { 'SHA256', 32 },
     sha384  = { 'SHA384', 48 },
     sha512  = { 'SHA512', 64 },
     md5     = { 'MD5',    16 },
-    md4     = { 'MD4',    16 }
+    md4     = { 'MD4',    16 },
 }
 
 local hexres = ffi.new('char[129]')
@@ -131,7 +130,11 @@ local m = {
         end
         local r = ffi.C.SHA1internal(str, #str, nil)
         return tohex(r, 20)
-    end
+    end,
+
+    guava = function(state, buckets)
+       return ffi.C.guava(state, buckets)
+    end,
 }
 
 if ssl ~= nil then
@@ -149,7 +152,7 @@ if ssl ~= nil then
             local r = ssl[hfunction](str, string.len(str), nil)
             return ffi.string(r, hsize)
         end
-        
+
         m[ pname .. '_hex' ] = function(str)
             if str == nil then
                 str = ''
diff --git a/test/box/digest.result b/test/box/digest.result
index a5b1e4b69da769ac69226535e1c0a55b8c54ec1b..f290ccc0823c6ee878a1dc2859630557c0922cc0 100644
--- a/test/box/digest.result
+++ b/test/box/digest.result
@@ -193,19 +193,39 @@ digest.base64_decode(b) == s
 ...
 digest.base64_decode(nil)
 ---
-- error: 'builtin/digest.lua:89: Usage: digest.base64_decode(string)'
+- error: 'builtin/digest.lua:88: Usage: digest.base64_decode(string)'
 ...
 digest.base64_encode(nil)
 ---
-- error: 'builtin/digest.lua:78: Usage: digest.base64_encode(string)'
+- error: 'builtin/digest.lua:77: Usage: digest.base64_encode(string)'
 ...
 digest.base64_encode(123)
 ---
-- error: 'builtin/digest.lua:78: Usage: digest.base64_encode(string)'
+- error: 'builtin/digest.lua:77: Usage: digest.base64_encode(string)'
 ...
 digest.base64_decode(123)
 ---
-- error: 'builtin/digest.lua:89: Usage: digest.base64_decode(string)'
+- error: 'builtin/digest.lua:88: Usage: digest.base64_decode(string)'
+...
+digest.guava('hello', 0)
+---
+- error: 'bad argument #1 to ''?'' (cannot convert ''string'' to ''int64_t'')'
+...
+digest.guava(1, 'nope_')
+---
+- error: 'bad argument #2 to ''?'' (cannot convert ''string'' to ''int'')'
+...
+digest.guava(10863919174838991, 11)
+---
+- 8
+...
+digest.guava(2016238256797177309, 11)
+---
+- 7
+...
+digest.guava(1673758223894951030, 11)
+---
+- 7
 ...
 digest = nil
 ---
diff --git a/test/box/digest.test.lua b/test/box/digest.test.lua
index 6475bf6b650136b9cd62cc81a0d1b134d9989b7d..a4583eece4f2fb356fd3df99330086a35c11c338 100644
--- a/test/box/digest.test.lua
+++ b/test/box/digest.test.lua
@@ -59,4 +59,10 @@ digest.base64_encode(nil)
 digest.base64_encode(123)
 digest.base64_decode(123)
 
+digest.guava('hello', 0)
+digest.guava(1, 'nope_')
+digest.guava(10863919174838991, 11)
+digest.guava(2016238256797177309, 11)
+digest.guava(1673758223894951030, 11)
+
 digest = nil
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 8812c891540a2ca88c68cbc7b02d56a91f69f396..0df204d057626b048ddfba8928dc078377577bfa 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -73,3 +73,6 @@ add_executable(scramble.test scramble.c
     ${CMAKE_SOURCE_DIR}/third_party/sha1.c
     ${CMAKE_SOURCE_DIR}/third_party/base64.c
     ${CMAKE_SOURCE_DIR}/src/random.c)
+
+add_executable(guava.test guava.c)
+target_link_libraries(guava.test salad)
diff --git a/test/unit/guava.c b/test/unit/guava.c
new file mode 100644
index 0000000000000000000000000000000000000000..6b38871db32fa29ab0c2708fb7a5101654c1d250
--- /dev/null
+++ b/test/unit/guava.c
@@ -0,0 +1,68 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "unit.h"
+#include "salad/guava.h"
+
+static void
+check_guava_correctness(uint64_t code)
+{
+	int32_t last = 0;
+	for (int32_t shards = 1; shards <= 100000; shards++) {
+		int32_t b = guava(code, shards);
+		if (b != last) {
+			fail_if(shards - 1 != b);
+			last = b;
+		}
+	}
+}
+
+static void
+correctness_check()
+{
+	header();
+	int64_t i_vals[] = {0, 1, 2};
+	for (size_t i = 0; i < sizeof(i_vals) / sizeof(int64_t); ++i)
+		check_guava_correctness(i_vals[i]);
+	srand(time(NULL));
+	for (size_t i = 0; i < 20; ++i)
+		check_guava_correctness(rand() % 7);
+	footer();
+}
+
+static void
+sameresult_check()
+{
+	header();
+	fail_if(guava(100, 20) != guava(100, 20));
+	footer();
+}
+
+static void
+lcg_compat_check()
+{
+	header();
+	int32_t golden100[] = {
+		0, 55, 62, 8, 45, 59, 86, 97, 82, 59,
+		73, 37, 17, 56, 86, 21, 90, 37, 38, 83
+	};
+	for (size_t i = 0; i < sizeof(golden100) / sizeof(int64_t); ++i)
+		check_guava_correctness(golden100[i]);
+
+	fail_if(6     != guava(10863919174838991ULL, 11));
+	fail_if(3     != guava(2016238256797177309ULL, 11));
+	fail_if(5     != guava(1673758223894951030ULL, 11));
+	fail_if(80343 != guava(2, 100001));
+	fail_if(22152 != guava(2201, 100001));
+	fail_if(15018 != guava(2202, 100001));
+	footer();
+}
+
+int
+main(void)
+{
+	correctness_check();
+	lcg_compat_check();
+	sameresult_check();
+}
diff --git a/test/unit/guava.result b/test/unit/guava.result
new file mode 100644
index 0000000000000000000000000000000000000000..0e996c6ce096f5be6f9d9e2bbf4c8caf1c3ececb
--- /dev/null
+++ b/test/unit/guava.result
@@ -0,0 +1,7 @@
+	*** correctness_check ***
+	*** correctness_check: done ***
+ 	*** lcg_compat_check ***
+	*** lcg_compat_check: done ***
+ 	*** sameresult_check ***
+	*** sameresult_check: done ***
+ 
\ No newline at end of file