diff --git a/src/box/memtx_hash.cc b/src/box/memtx_hash.cc
index 61ac03c7c2a21ad1ea8e7a69f1d7574bfbedc78a..edc46d8ef922955ee96bde3eaf4f60e443ce71dd 100644
--- a/src/box/memtx_hash.cc
+++ b/src/box/memtx_hash.cc
@@ -30,6 +30,15 @@
 #include "say.h"
 #include "tuple.h"
 #include "errinj.h"
+#include "memory.h"
+
+/** For all memory used by all tree indexes. */
+extern struct quota memtx_quota;
+static struct slab_arena index_arena;
+static struct slab_cache index_arena_slab_cache;
+static struct mempool hash_extent_pool;
+/** Number of allocated extents. */
+static bool index_arena_initialized = false;
 
 #include "third_party/PMurHash.h"
 
@@ -38,17 +47,17 @@ enum {
 };
 
 static inline bool
-mh_index_eq(struct tuple *const *tuple_a, struct tuple *const *tuple_b,
+equal(struct tuple *tuple_a, struct tuple *tuple_b,
 	    const struct key_def *key_def)
 {
-	return tuple_compare(*tuple_a, *tuple_b, key_def) == 0;
+	return tuple_compare(tuple_a, tuple_b, key_def) == 0;
 }
 
 static inline bool
-mh_index_eq_key(const char *key, struct tuple *const *tuple,
+equal_key(struct tuple *tuple, const char *key,
 		const struct key_def *key_def)
 {
-	return tuple_compare_with_key(*tuple, key, key_def->part_count,
+	return tuple_compare_with_key(tuple, key, key_def->part_count,
 				      key_def) == 0;
 }
 
@@ -88,15 +97,15 @@ mh_hash_field(uint32_t *ph1, uint32_t *pcarry, const char **field,
 }
 
 static inline uint32_t
-mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def)
+tuple_hash(struct tuple *tuple, const struct key_def *key_def)
 {
 	const struct key_part *part = key_def->parts;
 	/*
 	 * Speed up the simplest case when we have a
-	 * single-part hash over an integer field.
+	 * single-part hash_table over an integer field.
 	 */
 	if (key_def->part_count == 1 && part->type == NUM) {
-		const char *field = tuple_field(*tuple, part->fieldno);
+		const char *field = tuple_field(tuple, part->fieldno);
 		uint64_t val = mp_decode_uint(&field);
 		if (likely(val <= UINT32_MAX))
 			return val;
@@ -108,7 +117,7 @@ mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def)
 	uint32_t total_size = 0;
 
 	for ( ; part < key_def->parts + key_def->part_count; part++) {
-		const char *field = tuple_field(*tuple, part->fieldno);
+		const char *field = tuple_field(tuple, part->fieldno);
 		total_size += mh_hash_field(&h, &carry, &field, part->type);
 	}
 
@@ -116,7 +125,7 @@ mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def)
 }
 
 static inline uint32_t
-mh_index_hash_key(const char *key, const struct key_def *key_def)
+key_hash(const char *key, const struct key_def *key_def)
 {
 	const struct key_part *part = key_def->parts;
 
@@ -138,26 +147,21 @@ mh_index_hash_key(const char *key, const struct key_def *key_def)
 	return PMurHash32_Result(h, carry, total_size);
 }
 
-#define mh_int_t uint32_t
-#define mh_arg_t const struct key_def *
-
-#define mh_hash(a, arg) mh_index_hash(a, arg)
-#define mh_hash_key(a, arg) mh_index_hash_key(a, arg)
-#define mh_eq(a, b, arg) mh_index_eq(a, b, arg)
-#define mh_eq_key(a, b, arg) mh_index_eq_key(a, b, arg)
-
-#define mh_key_t const char *
-typedef struct tuple * mh_node_t;
-#define mh_name _index
-#define MH_SOURCE 1
-#define mh_bytemap 1
-#include "salad/mhash.h"
+#define LIGHT_NAME _index
+#define LIGHT_DATA_TYPE struct tuple *
+#define LIGHT_KEY_TYPE const char *
+#define LIGHT_CMP_ARG_TYPE struct key_def *
+#define LIGHT_EQUAL(a, b, c) equal(a, b, c)
+#define LIGHT_EQUAL_KEY(a, b, c) equal_key(a, b, c)
+#define HASH_INDEX_EXTENT_SIZE (16 * 1024)
+typedef uint32_t hash_t;
+#include "salad/light.h"
 
 /* {{{ MemtxHash Iterators ****************************************/
 
 struct hash_iterator {
 	struct iterator base; /* Must be the first member. */
-	struct mh_index_t *hash;
+	struct light_index_core *hash_table;
 	uint32_t h_pos;
 };
 
@@ -174,10 +178,13 @@ hash_iterator_ge(struct iterator *ptr)
 	assert(ptr->free == hash_iterator_free);
 	struct hash_iterator *it = (struct hash_iterator *) ptr;
 
-	while (it->h_pos < mh_end(it->hash)) {
-		if (mh_exist(it->hash, it->h_pos))
-			return *mh_index_node(it->hash, it->h_pos++);
+	if (it->h_pos < it->hash_table->table_size) {
+		struct tuple *res = light_index_get(it->hash_table, it->h_pos);
 		it->h_pos++;
+		while (it->h_pos < it->hash_table->table_size
+		       && !light_index_pos_valid(it->hash_table, it->h_pos))
+			it->h_pos++;
+		return res;
 	}
 	return NULL;
 }
@@ -197,48 +204,80 @@ hash_iterator_eq(struct iterator *it)
 
 /* }}} */
 
+static void *
+extent_alloc()
+{
+	ERROR_INJECT(ERRINJ_INDEX_ALLOC, return 0);
+	return mempool_alloc(&hash_extent_pool);
+}
+
+static void
+extent_free(void *extent)
+{
+	return mempool_free(&hash_extent_pool, extent);
+}
+
 /* {{{ MemtxHash -- implementation of all hashes. **********************/
 
 MemtxHash::MemtxHash(struct key_def *key_def)
 	: Index(key_def)
 {
-	hash = mh_index_new();
-	if (hash == NULL) {
-		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(hash),
-			  "MemtxHash", "hash");
+	if (index_arena_initialized == false) {
+		const uint32_t SLAB_SIZE = 4 * 1024 * 1024;
+		if (slab_arena_create(&index_arena, &memtx_quota,
+				      0, SLAB_SIZE, MAP_PRIVATE)) {
+			panic_syserror("failed to initialize index arena");
+		}
+		slab_cache_create(&index_arena_slab_cache, &index_arena,
+				  SLAB_SIZE);
+		mempool_create(&hash_extent_pool, &index_arena_slab_cache,
+			HASH_INDEX_EXTENT_SIZE);
+		index_arena_initialized = true;
 	}
+	hash_table = (struct light_index_core *) malloc(sizeof(*hash_table));
+	if (hash_table == NULL) {
+		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(hash_table),
+			  "MemtxHash", "hash_table");
+	}
+	light_index_create(hash_table, HASH_INDEX_EXTENT_SIZE,
+			   extent_alloc, extent_free, this->key_def);
 }
 
 MemtxHash::~MemtxHash()
 {
-	mh_index_delete(hash);
+	light_index_destroy(hash_table);
+	free(hash_table);
 }
 
 void
 MemtxHash::reserve(uint32_t size_hint)
 {
-	mh_index_reserve(hash, size_hint, key_def);
+	(void)size_hint;
 }
 
 size_t
 MemtxHash::size() const
 {
-	return mh_size(hash);
+	return hash_table->count;
 }
 
 size_t
 MemtxHash::memsize() const
 {
-        return mh_index_memsize(hash);
+        return matras_extents_count(&hash_table->mtable) * HASH_INDEX_EXTENT_SIZE;
 }
 
 struct tuple *
 MemtxHash::random(uint32_t rnd) const
 {
-	uint32_t k = mh_index_random(hash, rnd);
-	if (k != mh_end(hash))
-		return *mh_index_node(hash, k);
-	return NULL;
+	if (hash_table->count == 0)
+		return NULL;
+	rnd %= (hash_table->table_size);
+	while (!light_index_pos_valid(hash_table, rnd)) {
+		rnd++;
+		rnd %= (hash_table->table_size);
+	}
+	return light_index_get(hash_table, rnd);
 }
 
 struct tuple *
@@ -248,9 +287,10 @@ MemtxHash::findByKey(const char *key, uint32_t part_count) const
 	(void) part_count;
 
 	struct tuple *ret = NULL;
-	uint32_t k = mh_index_find(hash, key, key_def);
-	if (k != mh_end(hash))
-		ret = *mh_index_node(hash, k);
+	uint32_t h = key_hash(key, key_def);
+	uint32_t k = light_index_find_key(hash_table, h, key);
+	if (k != light_index_end)
+		ret = light_index_get(hash_table, k);
 	return ret;
 }
 
@@ -261,31 +301,31 @@ MemtxHash::replace(struct tuple *old_tuple, struct tuple *new_tuple,
 	uint32_t errcode;
 
 	if (new_tuple) {
+		uint32_t h = tuple_hash(new_tuple, key_def);
 		struct tuple *dup_tuple = NULL;
-		struct tuple **dup_node = &dup_tuple;
-		uint32_t pos = mh_index_put(hash, &new_tuple,
-					    &dup_node, key_def);
+		hash_t pos = light_index_replace(hash_table, h, new_tuple, &dup_tuple);
+		if (pos == light_index_end)
+			pos = light_index_insert(hash_table, h, new_tuple);
 
 		ERROR_INJECT(ERRINJ_INDEX_ALLOC,
 		{
-			mh_index_del(hash, pos, key_def);
-			pos = mh_end(hash);
+			light_index_delete(hash_table, pos);
+			pos = light_index_end;
 		});
 
-		if (pos == mh_end(hash)) {
-			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos,
-				  "hash", "key");
+		if (pos == light_index_end) {
+			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) hash_table->count,
+				  "hash_table", "key");
 		}
 		errcode = replace_check_dup(old_tuple, dup_tuple, mode);
 
 		if (errcode) {
-			mh_index_remove(hash, &new_tuple, key_def);
+			light_index_delete(hash_table, pos);
 			if (dup_tuple) {
-				pos = mh_index_put(hash, &dup_tuple, NULL,
-						   key_def);
-				if (pos == mh_end(hash)) {
+				uint32_t pos = light_index_insert(hash_table, h, dup_tuple);
+				if (pos == light_index_end) {
 					panic("Failed to allocate memory in "
-					      "recover of int hash");
+					      "recover of int hash_table");
 				}
 			}
 			tnt_raise(ClientError, errcode, index_id(this));
@@ -296,7 +336,10 @@ MemtxHash::replace(struct tuple *old_tuple, struct tuple *new_tuple,
 	}
 
 	if (old_tuple) {
-		mh_index_remove(hash, &old_tuple, key_def);
+		uint32_t h = tuple_hash(old_tuple, key_def);
+		hash_t slot = light_index_find(hash_table, h, old_tuple);
+		assert(slot != light_index_end);
+		light_index_delete(hash_table, slot);
 	}
 	return old_tuple;
 }
@@ -326,21 +369,24 @@ MemtxHash::initIterator(struct iterator *ptr, enum iterator_type type,
 	assert(ptr->free == hash_iterator_free);
 
 	struct hash_iterator *it = (struct hash_iterator *) ptr;
+	it->hash_table = hash_table;
 
 	switch (type) {
 	case ITER_ALL:
-		it->h_pos = mh_begin(hash);
+		it->h_pos = 0;
+		while (it->h_pos < it->hash_table->table_size
+		       && !light_index_pos_valid(it->hash_table, it->h_pos))
+			it->h_pos++;
 		it->base.next = hash_iterator_ge;
 		break;
 	case ITER_EQ:
 		assert(part_count > 0);
-		it->h_pos = mh_index_find(hash, key, key_def);
+		it->h_pos = light_index_find_key(hash_table, key_hash(key, key_def), key);
 		it->base.next = hash_iterator_eq;
 		break;
 	default:
 		tnt_raise(ClientError, ER_UNSUPPORTED,
 			  "Hash index", "requested iterator type");
 	}
-	it->hash = hash;
 }
 /* }}} */
diff --git a/src/box/memtx_hash.h b/src/box/memtx_hash.h
index 51848d3f40aacbdf36e07f10ea53fa0f75417cea..711256981258c5506112543971f3f3255582250d 100644
--- a/src/box/memtx_hash.h
+++ b/src/box/memtx_hash.h
@@ -31,7 +31,7 @@
 
 #include "index.h"
 
-struct mh_index_t;
+struct light_index_core;
 
 class MemtxHash: public Index {
 public:
@@ -53,7 +53,7 @@ class MemtxHash: public Index {
 	virtual size_t memsize() const;
 
 protected:
-	struct mh_index_t *hash;
+	struct light_index_core *hash_table;
 };
 
 #endif /* TARANTOOL_BOX_MEMTX_HASH_H_INCLUDED */
diff --git a/src/lib/salad/light.h b/src/lib/salad/light.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb418ea47d18fdd4397ee8ebb2f0a60c577b338d
--- /dev/null
+++ b/src/lib/salad/light.h
@@ -0,0 +1,771 @@
+/*
+ * *No header guard*: the header is allowed to be included twice
+ * with different sets of defines.
+ */
+/*
+ * 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 "small/matras.h"
+
+/**
+ * Additional user defined name that appended to prefix 'light'
+ *  for all names of structs and functions in this header file.
+ * All names use pattern: light<LIGHT_NAME>_<name of func/struct>
+ * May be empty, but still have to be defined (just #define LIGHT_NAME)
+ * Example:
+ * #define LIGHT_NAME _test
+ * ...
+ * struct light_test_core hash_table;
+ * light_test_create(&hash_table, ...);
+ */
+#ifndef LIGHT_NAME
+#error "LIGHT_NAME must be defined"
+#endif
+
+/**
+ * Data type that hash table holds. Must be less tant 8 bytes.
+ */
+#ifndef LIGHT_DATA_TYPE
+#error "LIGHT_DATA_TYPE must be defined"
+#endif
+
+/**
+ * Data type that used to for finding values.
+ */
+#ifndef LIGHT_KEY_TYPE
+#error "LIGHT_KEY_TYPE must be defined"
+#endif
+
+/**
+ * Type of optional third parameter of comparing function.
+ * If not needed, simply use #define LIGHT_CMP_ARG_TYPE int
+ */
+#ifndef LIGHT_CMP_ARG_TYPE
+#error "LIGHT_CMP_ARG_TYPE must be defined"
+#endif
+
+/**
+ * Data comparing function. Takes 3 parameters - value1, value2 and
+ * optional value that stored in hash table struct.
+ * Third parameter may be simply ignored like that:
+ * #define LIGHT_EQUAL(a, b, garb) a == b
+ */
+#ifndef LIGHT_EQUAL
+#error "LIGHT_EQUAL must be defined"
+#endif
+
+/**
+ * Data comparing function. Takes 3 parameters - value, key and
+ * optional value that stored in hash table struct.
+ * Third parameter may be simply ignored like that:
+ * #define LIGHT_EQUAL_KEY(a, b, garb) a == b
+ */
+#ifndef LIGHT_EQUAL_KEY
+#error "LIGHT_EQUAL_KEY must be defined"
+#endif
+
+/**
+ * Tools for name substitution:
+ */
+#ifndef CONCAT4
+#define CONCAT4_R(a, b, c, d) a##b##c##d
+#define CONCAT4(a, b, c, d) CONCAT4_R(a, b, c, d)
+#endif
+
+#ifdef _
+#error '_' must be undefinded!
+#endif
+#define LIGHT(name) CONCAT4(light, LIGHT_NAME, _, name)
+
+/**
+ * Struct for one record of the hash table
+ */
+struct LIGHT(record) {
+	/* hash of a value */
+	uint32_t hash;
+	/* slot of the next record in chain */
+	uint32_t next;
+	/* the value */
+	LIGHT_DATA_TYPE value;
+};
+
+/**
+ * Main struct for holding hash table
+ */
+struct LIGHT(core) {
+	/* Number of records added while grow iteration */
+	enum { GROW_INCREMENT = 8 };
+	/* count of values in hash table */
+	uint32_t count;
+	/* size of hash table ( equal to mtable.size ) */
+	uint32_t table_size;
+	/*
+	 * cover is power of two;
+	 * if count is positive, then cover/2 < count <= cover
+	 * cover_mask is cover - 1
+	 */
+	uint32_t cover_mask;
+
+	/*
+	 * Start of chain of empty slots
+	 */
+	uint32_t empty_slot;
+	struct LIGHT(record) *empty_record;
+
+	/* additional parameter for data comparison */
+	LIGHT_CMP_ARG_TYPE arg;
+
+	/* dynamic storage for records */
+	struct matras mtable;
+};
+
+/**
+ * Type of functions for memory allocation and deallocation
+ */
+typedef void *(*LIGHT(extent_alloc_t))();
+typedef void (*LIGHT(extent_free_t))(void *);
+
+/**
+ * Special result of light_find that means that nothing was found
+ */
+static const uint32_t LIGHT(end) = 0xFFFFFFFF;
+
+/**
+ * @brief Hash table construction. Fills struct light members.
+ * @param ht - pointer to a hash table struct
+ * @param extent_size - size of allocating memory blocks
+ * @param extent_alloc_func - memory blocks allocation function
+ * @param extent_free_func - memory blocks allocation function
+ * @param arg - optional parameter to save for comparing function
+ */
+void
+LIGHT(create)(struct LIGHT(core) *ht, size_t extent_size,
+	      LIGHT(extent_alloc_t) extent_alloc_func,
+	      LIGHT(extent_free_t) extent_free_func,
+	      LIGHT_CMP_ARG_TYPE arg);
+
+/**
+ * @brief Hash table destruction. Frees all allocated memory
+ * @param ht - pointer to a hash table struct
+ */
+void
+LIGHT(destroy)(struct LIGHT(core) *ht);
+
+/**
+ * @brief Find a record with given hash and value
+ * @param ht - pointer to a hash table struct
+ * @param hash - hash to find
+ * @param data - value to find
+ * @return integer ID of found record or light_end if nothing found
+ */
+uint32_t
+LIGHT(find)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE data);
+
+/**
+ * @brief Find a record with given hash and key
+ * @param ht - pointer to a hash table struct
+ * @param hash - hash to find
+ * @param data - key to find
+ * @return integer ID of found record or light_end if nothing found
+ */
+uint32_t
+LIGHT(find_key)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_KEY_TYPE data);
+
+/**
+ * @brief Insert a record with given hash and value
+ * @param ht - pointer to a hash table struct
+ * @param hash - hash to insert
+ * @param data - value to insert
+ * @return integer ID of inserted record or light_end if failed
+ */
+uint32_t
+LIGHT(insert)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE data);
+
+/**
+ * @brief Replace a record with given hash and value
+ * @param ht - pointer to a hash table struct
+ * @param hash - hash to find
+ * @param data - value to find and replace
+ * @param replaced - pointer to a value that was stored in table before replace
+ * @return integer ID of found record or light_end if nothing found
+ */
+uint32_t
+LIGHT(replace)(struct LIGHT(core) *ht, uint32_t hash,
+	       LIGHT_DATA_TYPE data, LIGHT_DATA_TYPE *replaced);
+
+/**
+ * @brief Delete a record from a hash table by given record ID
+ * @param ht - pointer to a hash table struct
+ * @param slotpos - ID of an record. See LIGHT(find) for details.
+ */
+void
+LIGHT(delete)(struct LIGHT(core) *ht, uint32_t slotpos);
+
+/**
+ * @brief Get a value from a desired position
+ * @param ht - pointer to a hash table struct
+ * @param slotpos - ID of an record
+ */
+LIGHT_DATA_TYPE
+LIGHT(get)(struct LIGHT(core) *ht, uint32_t slotpos);
+
+/**
+ * @brief Determine if posision holds a value
+ * @param ht - pointer to a hash table struct
+ * @param slotpos - ID of an record
+ */
+bool
+LIGHT(pos_valid)(struct LIGHT(core) *ht, uint32_t slotpos);
+
+
+
+inline void
+LIGHT(create)(struct LIGHT(core) *ht, size_t extent_size,
+	      LIGHT(extent_alloc_t) extent_alloc_func,
+	      LIGHT(extent_free_t) extent_free_func,
+	      LIGHT_CMP_ARG_TYPE arg)
+{
+	assert((ht->GROW_INCREMENT & (ht->GROW_INCREMENT - 1)) == 0);
+	assert(sizeof(LIGHT_DATA_TYPE) >= sizeof(uint32_t));
+	ht->count = 0;
+	ht->table_size = 0;
+	ht->empty_slot = LIGHT(end);
+	ht->empty_record = 0;
+	ht->arg = arg;
+	matras_create(&ht->mtable,
+		      extent_size, sizeof(struct LIGHT(record)),
+		      extent_alloc_func, extent_free_func);
+}
+
+inline void
+LIGHT(destroy)(struct LIGHT(core) *ht)
+{
+	matras_destroy(&ht->mtable);
+}
+
+inline uint32_t
+LIGHT(slot)(const struct LIGHT(core) *ht, uint32_t hash)
+{
+	uint32_t cover_mask = ht->cover_mask;
+	uint32_t res = hash & cover_mask;
+	uint32_t probe = (ht->table_size - res - 1) >> 31;
+	uint32_t shift = __builtin_ctz(~(cover_mask >> 1));
+	res ^= (probe << shift);
+	return res;
+
+}
+
+inline uint32_t
+LIGHT(find)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value)
+{
+	if (ht->count == 0)
+		return LIGHT(end);
+	uint32_t slot = LIGHT(slot)(ht, hash);
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slot);
+	if (record->next == slot)
+		return LIGHT(end);
+	while (1) {
+		if (record->hash == hash && LIGHT_EQUAL((record->value), (value), (ht->arg)))
+			return slot;
+		slot = record->next;
+		if (slot == LIGHT(end))
+			return LIGHT(end);
+		record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, slot);
+	}
+	/* unreachable */
+	return LIGHT(end);
+}
+
+inline uint32_t
+LIGHT(replace)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value, LIGHT_DATA_TYPE *replaced)
+{
+	if (ht->count == 0)
+		return LIGHT(end);
+	uint32_t slot = LIGHT(slot)(ht, hash);
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slot);
+	if (record->next == slot)
+		return LIGHT(end);
+	while (1) {
+		if (record->hash == hash && LIGHT_EQUAL((record->value), (value), (ht->arg))) {
+			*replaced = record->value;
+			record->value = value;
+			return slot;
+		}
+		slot = record->next;
+		if (slot == LIGHT(end))
+			return LIGHT(end);
+		record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, slot);
+	}
+	/* unreachable */
+	return LIGHT(end);
+}
+
+
+
+inline uint32_t
+LIGHT(find_key)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_KEY_TYPE key)
+{
+	if (ht->count == 0)
+		return LIGHT(end);
+	uint32_t slot = LIGHT(slot)(ht, hash);
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slot);
+	if (record->next == slot)
+		return LIGHT(end);
+	while (1) {
+		if (record->hash == hash && LIGHT_EQUAL_KEY((record->value), (key), (ht->arg)))
+			return slot;
+		slot = record->next;
+		if (slot == LIGHT(end))
+			return LIGHT(end);
+		record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, slot);
+	}
+	/* unreachable */
+	return LIGHT(end);
+}
+
+inline uint32_t
+LIGHT(get_empty_prev)(struct LIGHT(record) *record)
+{
+	return record->hash;
+}
+
+inline void
+LIGHT(set_empty_prev)(struct LIGHT(record) *record, uint32_t pos)
+{
+	record->hash = pos;
+}
+
+inline uint32_t
+LIGHT(get_empty_next)(struct LIGHT(record) *record)
+{
+	return (uint32_t)(uint64_t)record->value;
+}
+
+inline void
+LIGHT(set_empty_next)(struct LIGHT(record) *record, uint32_t pos)
+{
+	record->value = (LIGHT_DATA_TYPE)(int64_t)pos;
+}
+
+inline void
+LIGHT(enqueue_empty)(struct LIGHT(core) *ht, uint32_t slot, struct LIGHT(record) *record)
+{
+	record->next = slot;
+	if (ht->empty_record)
+		LIGHT(set_empty_prev)(ht->empty_record, slot);
+	LIGHT(set_empty_prev)(record, LIGHT(end));
+	LIGHT(set_empty_next)(record, ht->empty_slot);
+	ht->empty_record = record;
+	ht->empty_slot = slot;
+}
+
+inline void
+LIGHT(detach_first_empty)(struct LIGHT(core) *ht)
+{
+	assert(ht->empty_record);
+	ht->empty_slot = LIGHT(get_empty_next)(ht->empty_record);
+	if (ht->empty_slot == LIGHT(end)) {
+		ht->empty_record = 0;
+	} else {
+		ht->empty_record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, ht->empty_slot);
+		LIGHT(set_empty_prev)(ht->empty_record, LIGHT(end));
+	}
+}
+
+inline void
+LIGHT(detach_empty)(struct LIGHT(core) *ht, struct LIGHT(record) *record)
+{
+	uint32_t prev_slot = LIGHT(get_empty_prev)(record);
+	uint32_t next_slot = LIGHT(get_empty_next)(record);
+	if (prev_slot == LIGHT(end)) {
+		ht->empty_slot = next_slot;
+		if (next_slot == LIGHT(end))
+			ht->empty_record = 0;
+		else
+			ht->empty_record = (struct LIGHT(record) *)
+				matras_get(&ht->mtable, next_slot);
+	} else {
+		struct LIGHT(record) *prev_record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, prev_slot);
+		LIGHT(set_empty_next)(prev_record, next_slot);
+	}
+	if (next_slot != LIGHT(end)) {
+		struct LIGHT(record) *next_record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, next_slot);
+		LIGHT(set_empty_prev)(next_record, prev_slot);
+	}
+}
+
+inline bool
+LIGHT(prepare_first_insert)(struct LIGHT(core) *ht)
+{
+	assert(ht->count == 0);
+	assert(ht->table_size == 0);
+	assert(ht->mtable.block_count == 0);
+
+	uint32_t slot;
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_alloc_range(&ht->mtable, &slot, ht->GROW_INCREMENT);
+	if (!record)
+		return false;
+	assert(slot == 0);
+	ht->table_size = ht->GROW_INCREMENT;
+	ht->cover_mask = ht->GROW_INCREMENT - 1;
+	ht->empty_slot = 0;
+	ht->empty_record = record;
+	for (int i = 0; i < ht->GROW_INCREMENT; i++) {
+		record[i].next = i;
+		LIGHT(set_empty_prev)(record + i, i - 1);
+		LIGHT(set_empty_next)(record + i, i + 1);
+	}
+	LIGHT(set_empty_prev)(record, LIGHT(end));
+	LIGHT(set_empty_next)(record + ht->GROW_INCREMENT - 1, LIGHT(end));
+	return true;
+}
+
+inline bool
+LIGHT(grow)(struct LIGHT(core) *ht)
+{
+	uint32_t new_slot;
+	struct LIGHT(record) *new_record = (struct LIGHT(record) *)
+		matras_alloc_range(&ht->mtable, &new_slot, ht->GROW_INCREMENT);
+	if (!new_record) /* memory failure */
+		return false;
+	ht->table_size += ht->GROW_INCREMENT;
+
+	if (ht->cover_mask < ht->table_size - 1)
+		ht->cover_mask = (ht->cover_mask << 1) | (uint32_t)1;
+
+	uint32_t split_comm_mask = (ht->cover_mask >> 1);
+	uint32_t split_diff_mask = ht->cover_mask ^ split_comm_mask;
+
+	uint32_t susp_slot = new_slot & split_comm_mask;
+	struct LIGHT(record) *susp_record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, susp_slot);
+
+	for (int i = 0; i < ht->GROW_INCREMENT; i++, susp_slot++, susp_record++, new_slot++, new_record++) {
+		if (susp_record->next == susp_slot) {
+			/* Suspicious slot is empty, nothing to split */
+			LIGHT(enqueue_empty)(ht, new_slot, new_record);
+			continue;
+		}
+		if ((susp_record->hash & split_comm_mask) != susp_slot) {
+			/* Another chain in suspicious slot, nothing to split */
+			LIGHT(enqueue_empty)(ht, new_slot, new_record);
+			continue;
+		}
+
+		uint32_t chain_head_slot[2] = {susp_slot, new_slot};
+		struct LIGHT(record) *chain_head[2] = {susp_record, new_record};
+		struct LIGHT(record) *chain_tail[2] = {0, 0};
+		uint32_t shift = __builtin_ctz(split_diff_mask);
+		assert(split_diff_mask == (((uint32_t)1) << shift));
+
+		uint32_t last_empty_slot = new_slot;
+		struct LIGHT(record) *last_empty_record = new_record;
+		uint32_t prev_flag = 0;
+		struct LIGHT(record) *test_record = susp_record;
+		uint32_t test_slot = susp_slot;
+		struct LIGHT(record) *prev_record = 0;
+		while (1) {
+			uint32_t test_flag = (test_record->hash >> shift) & ((uint32_t)1);
+			if (test_flag  != prev_flag) {
+				chain_tail[prev_flag] = prev_record;
+				if (chain_tail[test_flag]) {
+					chain_tail[test_flag]->next = test_slot;
+				} else {
+					*chain_head[test_flag] = *test_record;
+					last_empty_slot = test_slot;
+					last_empty_record = test_record;
+					test_record = chain_head[test_flag];
+					test_slot = chain_head_slot[test_flag];
+				}
+				prev_flag = test_flag;
+			}
+			test_slot = test_record->next;
+			if (test_slot == LIGHT(end))
+				break;
+			prev_record = test_record;
+			test_record = (struct LIGHT(record) *)
+				matras_get(&ht->mtable, test_slot);
+		}
+		prev_flag = prev_flag ^ ((uint32_t)1);
+		if (chain_tail[prev_flag])
+			chain_tail[prev_flag]->next = LIGHT(end);
+
+		LIGHT(enqueue_empty)(ht, last_empty_slot, last_empty_record);
+	}
+	return true;
+}
+
+inline uint32_t
+LIGHT(insert)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value)
+{
+	if (ht->table_size == 0)
+		if (!LIGHT(prepare_first_insert)(ht))
+			return LIGHT(end);
+	if (!ht->empty_record)
+		if (!LIGHT(grow)(ht))
+			return LIGHT(end);
+
+	ht->count++;
+	uint32_t slot = LIGHT(slot)(ht, hash);
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slot);
+
+	if (record->next == slot) {
+		/* Inserting to an empty slot */
+		LIGHT(detach_empty)(ht, record);
+		record->value = value;
+		record->hash = hash;
+		record->next = LIGHT(end);
+		return slot;
+	}
+
+	uint32_t empty_slot = ht->empty_slot;
+	struct LIGHT(record) *empty_record = ht->empty_record;
+	LIGHT(detach_first_empty)(ht);
+
+	uint32_t chain_slot = LIGHT(slot)(ht, record->hash);
+	if (chain_slot == slot) {
+		/* add to existing chain */
+		empty_record->value = value;
+		empty_record->hash = hash;
+		empty_record->next = record->next;
+		record->next = empty_slot;
+		return empty_slot;
+	} else {
+		/* create new chain */
+		struct LIGHT(record) *chain = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, chain_slot);
+		while (chain->next != slot) {
+			chain_slot = chain->next;
+			chain = (struct LIGHT(record) *)
+				matras_get(&ht->mtable, chain_slot);
+		}
+		*empty_record = *record;
+		chain->next = empty_slot;
+		record->value = value;
+		record->hash = hash;
+		record->next = LIGHT(end);
+		return slot;
+	}
+}
+
+inline void
+LIGHT(delete)(struct LIGHT(core) *ht, uint32_t slot)
+{
+	assert(slot < ht->table_size);
+	uint32_t empty_slot;
+	struct LIGHT(record) *empty_record;
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slot);
+	assert(record->next != slot);
+	if (record->next != LIGHT(end)) {
+		empty_slot = record->next;
+		empty_record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, empty_slot);
+		*record = *empty_record;
+	} else {
+		empty_slot = slot;
+		empty_record = record;
+		uint32_t chain_slot = LIGHT(slot)(ht, record->hash);
+		if (chain_slot != slot) {
+			/* deleting a last record of chain */
+			struct LIGHT(record) *chain = (struct LIGHT(record) *)
+				matras_get(&ht->mtable, chain_slot);
+			uint32_t chain_next_slot = chain->next;
+			assert(chain_next_slot != LIGHT(end));
+			while (chain_next_slot != slot) {
+				chain_slot = chain_next_slot;
+				chain = (struct LIGHT(record) *)
+					matras_get(&ht->mtable, chain_next_slot);
+				chain_next_slot = chain->next;
+				assert(chain_next_slot != LIGHT(end));
+			}
+			chain->next = LIGHT(end);
+		}
+	}
+	LIGHT(enqueue_empty)(ht, empty_slot, empty_record);
+	ht->count--;
+}
+
+inline void
+LIGHT(delete_value)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value)
+{
+	if (ht->count == 0)
+		return;
+	uint32_t slot = LIGHT(slot)(ht, hash);
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slot);
+	if (record->next == slot)
+		return;
+	struct LIGHT(record) *prev_record = 0;
+	while (1) {
+		if (record->hash == hash && LIGHT_EQUAL((record->value), (value), (ht->arg)))
+			break;
+		slot = record->next;
+		if (slot == LIGHT(end))
+			return;
+		prev_record = record;
+		record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, slot);
+	}
+	ht->count--;
+	if (prev_record) {
+		prev_record->next = record->next;
+		LIGHT(enqueue_empty)(ht, slot, record);
+		return;
+	}
+	if (record->next == LIGHT(end)) {
+		LIGHT(enqueue_empty)(ht, slot, record);
+		return;
+	}
+	uint32_t next_slot = record->next;
+	struct LIGHT(record) *next_record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, next_slot);
+	*record = *next_record;
+	LIGHT(enqueue_empty)(ht, next_slot, next_record);
+}
+
+inline LIGHT_DATA_TYPE
+LIGHT(get)(struct LIGHT(core) *ht, uint32_t slotpos)
+{
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slotpos);
+	return record->value;
+}
+
+/**
+ * @brief Determine if posision holds a value
+ * @param ht - pointer to a hash table struct
+ * @param slotpos - ID of an record
+ */
+inline bool
+LIGHT(pos_valid)(LIGHT(core) *ht, uint32_t slotpos)
+{
+	struct LIGHT(record) *record = (struct LIGHT(record) *)
+		matras_get(&ht->mtable, slotpos);
+	return record->next != slotpos;
+}
+
+
+inline int
+LIGHT(selfcheck)(const struct LIGHT(core) *ht)
+{
+	int res = 0;
+	if (ht->table_size != ht->mtable.block_count)
+		res |= 64;
+	uint32_t empty_slot = ht->empty_slot;
+	if (empty_slot == LIGHT(end)) {
+		if (ht->empty_record)
+			res |= 512;
+	} else {
+		struct LIGHT(record) *should_be = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, empty_slot);
+		if (ht->empty_record != should_be)
+			res |= 1024;
+	}
+	uint32_t prev_empty_slot = LIGHT(end);
+	while (empty_slot != LIGHT(end)) {
+		struct LIGHT(record) *empty_record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, empty_slot);
+		if (empty_record->next != empty_slot)
+			res |= 2048;
+		if (LIGHT(get_empty_prev)(empty_record) != prev_empty_slot)
+			res |= 4096;
+		prev_empty_slot = empty_slot;
+		empty_slot = LIGHT(get_empty_next)(empty_record);
+	}
+	for (uint32_t i = 0; i < ht->table_size; i++) {
+		struct LIGHT(record) *record = (struct LIGHT(record) *)
+			matras_get(&ht->mtable, i);
+		if (record->next == i) {
+			uint32_t empty_slot = ht->empty_slot;
+			while (empty_slot != LIGHT(end) && empty_slot != i) {
+				struct LIGHT(record) *empty_record = (struct LIGHT(record) *)
+					matras_get(&ht->mtable, empty_slot);
+				empty_slot = LIGHT(get_empty_next)(empty_record);
+			}
+			if (empty_slot != i)
+				res |= 256;
+			continue;
+		}
+		uint32_t slot = LIGHT(slot)(ht, record->hash);
+		if (slot != i) {
+			bool found = false;
+			uint32_t chain_slot = slot;
+			uint32_t chain_start_slot = slot;
+			do {
+				struct LIGHT(record) *chain_record = (struct LIGHT(record) *)
+					matras_get(&ht->mtable, chain_slot);
+				chain_slot = chain_record->next;
+				if (chain_slot >= ht->table_size) {
+					res |= 16; /* out of bounds (1) */
+					break;
+				}
+				if (chain_slot == i) {
+					found = true;
+					break;
+				}
+				if (chain_slot == chain_start_slot) {
+					res |= 4; /* cycles in chain (1) */
+					break;
+				}
+			} while (chain_slot != LIGHT(end));
+			if (!found)
+				res |= 1; /* slot is out of chain */
+		} else {
+			do {
+				struct LIGHT(record) *record = (struct LIGHT(record) *)
+					matras_get(&ht->mtable, slot);
+				if (LIGHT(slot)(ht, record->hash) != i)
+					res |= 2; /* wrong value in chain */
+				slot = record->next;
+				if (slot != LIGHT(end) && slot >= ht->table_size) {
+					res |= 32; /* out of bounds (2) */
+					break;
+				}
+				if (slot == i) {
+					res |= 8; /* cycles in chain (2) */
+					break;
+				}
+			} while (slot != LIGHT(end));
+		}
+	}
+	return res;
+}
+
diff --git a/src/lib/small/matras.c b/src/lib/small/matras.c
index 95cdd196c973d4d3c27f6206b1f4974a5ad7e6a0..09929583788745bbff44b1819878ab25958de91b 100644
--- a/src/lib/small/matras.c
+++ b/src/lib/small/matras.c
@@ -4,6 +4,13 @@
 
 #include "matras.h"
 #include <limits.h>
+#ifdef WIN32
+#include <intrin.h>
+#pragma intrinsic (_BitScanReverse)
+#ifndef _DEBUG
+#define __OPTIMIZE__ 1
+#endif
+#endif
 
 /*
  * Binary logarithm of value (exact if the value is a power of 2,
@@ -13,8 +20,15 @@ static matras_id_t
 pt_log2(matras_id_t val)
 {
 	assert(val > 0);
-	return sizeof(unsigned long) * CHAR_BIT -
-		__builtin_clzl((unsigned long) val) - 1;
+#ifdef WIN32
+    unsigned long res = 0;
+    unsigned char nonzero = _BitScanReverse(&res, val);
+	assert(nonzero); (void)nonzero;
+	return (matras_id_t)res;
+#else
+	return sizeof(unsigned int) * CHAR_BIT -
+		__builtin_clz((unsigned int) val) - 1;
+#endif
 }
 
 /**
@@ -92,7 +106,7 @@ matras_destroy(struct matras *m)
 		m->block_count = 0;
 	}
 #ifndef __OPTIMIZE__
-	m->extent = (void *)0xDEADBEEF;
+	m->extent = (void *)(long long)0xDEADBEEF;
 #endif
 }
 
@@ -221,6 +235,40 @@ matras_dealloc(struct matras *m)
 	}
 }
 
+/**
+ * Allocate a range_count of blocks. Return both, first block pointer
+ * and first block id. This method only works if current number of blocks and
+ * number of blocks in one extent are divisible by range_count.
+ * range_count must also be less or equal to number of blocks in one extent.
+ *
+ * @retval NULL failed to allocate memory
+ */
+void *
+matras_alloc_range(struct matras *m, matras_id_t *id, matras_id_t range_count)
+{
+	assert(m->block_count % range_count == 0);
+	assert(m->extent_size / m->block_size % range_count == 0);
+	void *res = matras_alloc(m, id);
+	if (res)
+		m->block_count += (range_count - 1);
+	return res;
+}
+
+/*
+ * Deallocate last range_count of blocks (blocks with maximum ID)
+ * This method only works if current number of blocks and
+ * number of blocks in one extent are divisible by range_count.
+ * range_count must also be less or equal to number of blocks in one extent.
+ */
+void
+matras_dealloc_range(struct matras *m, matras_id_t range_count)
+{
+	assert(m->block_count % range_count == 0);
+	assert(m->extent_size / m->block_size % range_count == 0);
+	m->block_count -= (range_count - 1);
+	matras_dealloc(m);
+}
+
 /**
  * Return the number of allocated extents (of size m->extent_size each)
  */
diff --git a/src/lib/small/matras.h b/src/lib/small/matras.h
index 06bd34f7400328a36026a7ff47eef02323f9441d..24595995a65ded6134ead750b8c537477337ee74 100644
--- a/src/lib/small/matras.h
+++ b/src/lib/small/matras.h
@@ -82,7 +82,13 @@
  */
 /* }}} */
 
+#ifdef WIN32
+typedef unsigned __int32 matras_id_t;
+#else
 #include <stdint.h>
+typedef uint32_t matras_id_t;
+#endif
+
 #include <assert.h>
 
 #if defined(__cplusplus)
@@ -92,7 +98,6 @@ extern "C" {
 /**
  * Type of a block ID.
  */
-typedef uint32_t matras_id_t;
 
 /**
  * Type of the extent allocator (the allocator for regions
@@ -109,7 +114,7 @@ typedef void (*prov_free_func)(void *);
 struct matras {
 	/* Pointer to the root extent of matras */
 	void *extent;
-	/* Block size (M) */
+	/* Block size (N) */
 	matras_id_t block_size;
 	/* Extent size (M) */
 	matras_id_t extent_size;
@@ -186,6 +191,26 @@ matras_alloc(struct matras *m, matras_id_t *id);
 void
 matras_dealloc(struct matras *m);
 
+/**
+ * Allocate a range_count of blocks. Return both, first block pointer
+ * and first block id. This method only works if current number of blocks and
+ * number of blocks in one extent are divisible by range_count.
+ * range_count must also be less or equal to number of blocks in one extent.
+ *
+ * @retval NULL failed to allocate memory
+ */
+void *
+matras_alloc_range(struct matras *m, matras_id_t *id, matras_id_t range_count);
+
+/*
+ * Deallocate last range_count of blocks (blocks with maximum ID)
+ * This method only works if current number of blocks and
+ * number of blocks in one extent are divisible by range_count.
+ * range_count must also be less or equal to number of blocks in one extent.
+ */
+void
+matras_dealloc_range(struct matras *m, matras_id_t range_count);
+
 /**
  * Convert block id into block address.
  */
@@ -215,8 +240,6 @@ matras_get(const struct matras *m, matras_id_t id)
 	return (((char***)m->extent)[n1][n2] + n3 * m->block_size);
 }
 
-
-
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 52e788ab82c63a65cb52272990db158e47bc9738..35c5509b940fb21e6c6ee9d66f7be87381e72e47 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -178,8 +178,8 @@ session.su('admin')
 ...
 s:select()
 ---
-- - [3]
-  - [2]
+- - [2]
+  - [3]
 ...
 --
 -- Create user with universe read&write grants
@@ -244,8 +244,8 @@ box.schema.user.drop('uniuser_testus')
 --
 box.space.admin_space:select()
 ---
-- - [3]
-  - [2]
+- - [2]
+  - [3]
 ...
 box.space._user:select(1)
 ---
diff --git a/test/box/errinj_index.result b/test/box/errinj_index.result
index 47b26fc5a53ebfb09d39b6a2384875d4142ecd59..c232a89b22d7c00cd9e8d52702c5764a8d02aebf 100644
--- a/test/box/errinj_index.result
+++ b/test/box/errinj_index.result
@@ -352,7 +352,7 @@ res
 ...
 for i = 501,2500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 23 bytes in hash for key
+- error: Failed to allocate 10 bytes in hash_table for key
 ...
 s:delete{1}
 ---
@@ -406,7 +406,7 @@ check_iter_and_size(9)
 ...
 for i = 2501,3500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 23 bytes in hash for key
+- error: Failed to allocate 9 bytes in hash_table for key
 ...
 s:delete{2}
 ---
@@ -435,7 +435,7 @@ res
 ...
 for i = 3501,4500 do s:insert{i, i} end
 ---
-- error: Failed to allocate 23 bytes in hash for key
+- error: Failed to allocate 8 bytes in hash_table for key
 ...
 s:delete{3}
 ---
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index c15d0861ab701aebcd96d20a6437834b30463572..143288c05cb049b6ebd13749225947f9a93a0e29 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -48,6 +48,8 @@ add_executable(rtree_itr.test rtree_itr.cc ${CMAKE_SOURCE_DIR}/src/lib/salad/rtr
 target_link_libraries(rtree_itr.test)
 add_executable(matras.test matras.cc)
 target_link_libraries(matras.test small)
+add_executable(light.test light.cc)
+target_link_libraries(light.test small)
 add_executable(vclock.test vclock.cc unit.c
     ${CMAKE_SOURCE_DIR}/src/box/vclock.c
     ${CMAKE_SOURCE_DIR}/src/box/errcode.c
diff --git a/test/unit/light.cc b/test/unit/light.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2dee5dcba3040cb2260857dfaea5e4ac08d5ce19
--- /dev/null
+++ b/test/unit/light.cc
@@ -0,0 +1,193 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <vector>
+
+#include "unit.h"
+
+typedef uint64_t hash_value_t;
+typedef uint32_t hash_t;
+
+static const size_t light_extent_size = 16 * 1024;
+static size_t extents_count = 0;
+
+hash_t
+hash(hash_value_t value)
+{
+	return (hash_t) value;
+}
+
+bool
+equal(hash_value_t v1, hash_value_t v2)
+{
+	return v1 == v2;
+}
+
+bool
+equal_key(hash_value_t v1, hash_value_t v2)
+{
+	return v1 == v2;
+}
+
+inline void *
+my_light_alloc()
+{
+	extents_count++;
+	char *draft = (char *)malloc(light_extent_size + 64 + 8);
+	void *result = draft + 8 + (63 - ((uint64_t)(draft + 8) % 64));
+	((void **)result)[-1] = draft;
+	return result;
+}
+
+inline void
+my_light_free(void *p)
+{
+	extents_count--;
+	free(((void **)p)[-1]);
+}
+
+#define LIGHT_NAME
+#define LIGHT_DATA_TYPE uint64_t
+#define LIGHT_KEY_TYPE uint64_t
+#define LIGHT_CMP_ARG_TYPE int
+#define LIGHT_EQUAL(a, b, arg) equal(a, b)
+#define LIGHT_EQUAL_KEY(a, b, arg) equal_key(a, b)
+#include "salad/light.h"
+
+
+static void
+simple_test()
+{
+	header();
+
+	struct light_core ht;
+	light_create(&ht, light_extent_size, my_light_alloc, my_light_free, 0);
+	std::vector<bool> vect;
+	size_t count = 0;
+	const size_t rounds = 1000;
+	const size_t start_limits = 20;
+	for(size_t limits = start_limits; limits <= 2 * rounds; limits *= 10) {
+		while (vect.size() < limits)
+			vect.push_back(false);
+		for (size_t i = 0; i < rounds; i++) {
+
+			hash_value_t val = rand() % limits;
+			hash_t h = hash(val);
+			hash_t fnd = light_find(&ht, h, val);
+			bool has1 = fnd != light_end;
+			bool has2 = vect[val];
+			assert(has1 == has2);
+			if (has1 != has2) {
+				fail("find key failed!", "true");
+				return;
+			}
+
+			if (!has1) {
+				count++;
+				vect[val] = true;
+				light_insert(&ht, h, val);
+			} else {
+				count--;
+				vect[val] = false;
+				light_delete(&ht, fnd);
+			}
+
+			if (count != ht.count)
+				fail("count check failed!", "true");
+
+			bool identical = true;
+			for (hash_value_t test = 0; test < limits; test++) {
+				if (vect[test]) {
+					if (light_find(&ht, hash(test), test) == light_end)
+						identical = false;
+				} else {
+					if (light_find(&ht, hash(test), test) != light_end)
+						identical = false;
+				}
+			}
+			if (!identical)
+				fail("internal test failed!", "true");
+
+			int check = light_selfcheck(&ht);
+			if (check)
+				fail("internal test failed!", "true");
+		}
+	}
+	light_destroy(&ht);
+
+	footer();
+}
+
+static void
+collision_test()
+{
+	header();
+
+	struct light_core ht;
+	light_create(&ht, light_extent_size, my_light_alloc, my_light_free, 0);
+	std::vector<bool> vect;
+	size_t count = 0;
+	const size_t rounds = 100;
+	const size_t start_limits = 20;
+	for(size_t limits = start_limits; limits <= 2 * rounds; limits *= 10) {
+		while (vect.size() < limits)
+			vect.push_back(false);
+		for (size_t i = 0; i < rounds; i++) {
+
+			hash_value_t val = rand() % limits;
+			hash_t h = hash(val);
+			hash_t fnd = light_find(&ht, h * 1024, val);
+			bool has1 = fnd != light_end;
+			bool has2 = vect[val];
+			assert(has1 == has2);
+			if (has1 != has2) {
+				fail("find key failed!", "true");
+				return;
+			}
+
+			if (!has1) {
+				count++;
+				vect[val] = true;
+				light_insert(&ht, h * 1024, val);
+			} else {
+				count--;
+				vect[val] = false;
+				light_delete(&ht, fnd);
+			}
+
+			if (count != ht.count)
+				fail("count check failed!", "true");
+
+			bool identical = true;
+			for (hash_value_t test = 0; test < limits; test++) {
+				if (vect[test]) {
+					if (light_find(&ht, hash(test) * 1024, test) == light_end)
+						identical = false;
+				} else {
+					if (light_find(&ht, hash(test) * 1024, test) != light_end)
+						identical = false;
+				}
+			}
+			if (!identical)
+				fail("internal test failed!", "true");
+
+			int check = light_selfcheck(&ht);
+			if (check)
+				fail("internal test failed!", "true");
+		}
+	}
+	light_destroy(&ht);
+
+	footer();
+}
+
+int
+main(int, const char**)
+{
+	simple_test();
+	collision_test();
+	if (extents_count != 0)
+		fail("memory leak!", "true");
+}
diff --git a/test/unit/light.result b/test/unit/light.result
new file mode 100644
index 0000000000000000000000000000000000000000..b37b0ed568394b2cebd65e2e951c4a5c5c7528f9
--- /dev/null
+++ b/test/unit/light.result
@@ -0,0 +1,5 @@
+	*** simple_test ***
+	*** simple_test: done ***
+ 	*** collision_test ***
+	*** collision_test: done ***
+ 
\ No newline at end of file
diff --git a/test/wal/oom.result b/test/wal/oom.result
index 1256e4946451bae12e270fa922f59beffc3d6022..7487cd4252efadeca13d0263545e7c3de65ad122 100644
--- a/test/wal/oom.result
+++ b/test/wal/oom.result
@@ -15,11 +15,11 @@ while true do
     i = i + 1
 end;
 ---
-- error: Failed to allocate 252 bytes in slab allocator for tuple
+- error: Failed to allocate 244 bytes in slab allocator for tuple
 ...
 space:len();
 ---
-- 58
+- 56
 ...
 i = 1;
 ---
@@ -29,11 +29,11 @@ while true do
     i = i + 1
 end;
 ---
-- error: Failed to allocate 252 bytes in slab allocator for tuple
+- error: Failed to allocate 244 bytes in slab allocator for tuple
 ...
 space:len();
 ---
-- 116
+- 112
 ...
 i = 1;
 ---
@@ -43,12 +43,12 @@ while true do
     i = i + 1
 end;
 ---
-- error: Failed to allocate 249 bytes in slab allocator for tuple
+- error: Failed to allocate 241 bytes in slab allocator for tuple
 ...
 --# setopt delimiter ''
 space:len()
 ---
-- 173
+- 167
 ...
 space.index['primary']:get{0}
 ---