diff --git a/mod/box/box.m b/mod/box/box.m
index 48ff2cab30f1a625045788af9c466f7b64637a3b..3acf6ff37d4e216d1db917525b66cbd717cf40b4 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -117,6 +117,42 @@ tuple_txn_ref(struct box_txn *txn, struct box_tuple *tuple)
 	tuple_ref(tuple, +1);
 }
 
+void
+validate_indexes(struct box_txn *txn)
+{
+	if (space[txn->n].index[1] == nil)
+		return;
+
+	/* There is more than one index. */
+	foreach_index(txn->n, index) {
+		/* XXX: skip the first index here! */
+		for (u32 f = 0; f < index->key.part_count; ++f) {
+			if (index->key.parts[f].fieldno >= txn->tuple->cardinality)
+				tnt_raise(IllegalParams, :"tuple must have all indexed fields");
+
+			if (index->key.parts[f].type == STRING)
+				continue;
+
+			void *field = tuple_field(txn->tuple, index->key.parts[f].fieldno);
+			u32 len = load_varint32(&field);
+
+			if (index->key.parts[f].type == NUM && len != sizeof(u32))
+				tnt_raise(IllegalParams, :"field must be NUM");
+
+			if (index->key.parts[f].type == NUM64 && len != sizeof(u64))
+				tnt_raise(IllegalParams, :"field must be NUM64");
+		}
+		if (index->type == TREE && index->unique == false)
+			/* Don't check non unique indexes */
+			continue;
+
+		struct box_tuple *tuple = [index findBy: txn->tuple];
+
+		if (tuple != NULL && tuple != txn->old_tuple)
+			tnt_raise(ClientError, :ER_INDEX_VIOLATION);
+	}
+}
+
 static void __attribute__((noinline))
 prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 {
@@ -132,7 +168,7 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 	txn->tuple->cardinality = cardinality;
 	memcpy(txn->tuple->data, data->data, data->size);
 
-	txn->old_tuple = txn->index->find_by_tuple(txn->index, txn->tuple);
+	txn->old_tuple = [txn->index findBy: txn->tuple];
 
 	if (txn->old_tuple != NULL)
 		tuple_txn_ref(txn, txn->old_tuple);
@@ -175,7 +211,7 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 		 * txn_commit().
 		 */
 		foreach_index(txn->n, index)
-			index->replace(index, NULL, txn->tuple);
+			[index replace: NULL :txn->tuple];
 	}
 
 	txn->out->dup_u32(1); /* Affected tuples */
@@ -189,7 +225,7 @@ commit_replace(struct box_txn *txn)
 {
 	if (txn->old_tuple != NULL) {
 		foreach_index(txn->n, index)
-			index->replace(index, txn->old_tuple, txn->tuple);
+			[index replace: txn->old_tuple :txn->tuple];
 
 		tuple_ref(txn->old_tuple, -1);
 	}
@@ -207,7 +243,7 @@ rollback_replace(struct box_txn *txn)
 
 	if (txn->tuple && txn->tuple->flags & GHOST) {
 		foreach_index(txn->n, index)
-			index->remove(index, txn->tuple);
+			[index remove: txn->tuple];
 	}
 }
 
@@ -341,7 +377,7 @@ prepare_update(struct box_txn *txn, struct tbuf *data)
 	if (key == NULL)
 		tnt_raise(IllegalParams, :"invalid key");
 
-	txn->old_tuple = txn->index->find(txn->index, key);
+	txn->old_tuple = [txn->index find: key];
 	if (txn->old_tuple == NULL) {
 		txn->flags |= BOX_NOT_STORE;
 
@@ -468,9 +504,10 @@ process_select(struct box_txn *txn, u32 limit, u32 offset, struct tbuf *data)
 		for (int i = 1; i < key_cardinality; i++)
 			read_field(data);
 
-		index->iterator_init(index, key_cardinality, key);
+		struct iterator *it = index->position;
+		[index initIterator: it :key :key_cardinality];
 
-		while ((tuple = index->iterator.next_equal(index)) != NULL) {
+		while ((tuple = it->next_equal(it)) != NULL) {
 			if (tuple->flags & GHOST)
 				continue;
 
@@ -494,7 +531,7 @@ prepare_delete(struct box_txn *txn, void *key)
 {
 	u32 tuples_affected = 0;
 
-	txn->old_tuple = txn->index->find(txn->index, key);
+	txn->old_tuple = [txn->index find: key];
 
 	if (txn->old_tuple == NULL)
 		/*
@@ -523,7 +560,7 @@ commit_delete(struct box_txn *txn)
 		return;
 
 	foreach_index(txn->n, index)
-		index->remove(index, txn->old_tuple);
+		[index remove: txn->old_tuple];
 	tuple_ref(txn->old_tuple, -1);
 }
 
@@ -892,7 +929,7 @@ space_free(void)
 		int j;
 		for (j = 0 ; j < BOX_INDEX_MAX ; j++) {
 			Index *index = space[i].index[j];
-			if (index == 0)
+			if (index == nil)
 				break;
 			[index free];
 		}
@@ -972,14 +1009,11 @@ space_config(void)
 		/* fill space indexes */
 		for (int j = 0; cfg_space->index[j] != NULL; ++j) {
 			typeof(cfg_space->index[j]) cfg_index = cfg_space->index[j];
-			Index *index = [[Index alloc] init];
 			struct key key;
 			key_config(&key, cfg_index);
-			index->key = key;
-			index->unique = cfg_index->unique;
-			index->type = STR2ENUM(index_type, cfg_index->type);
-			index->n = j;
-			[index init: &space[i]];
+			enum index_type type = STR2ENUM(index_type, cfg_index->type);
+			Index *index = [Index alloc: type :&key];
+			[index init: &key :&space[i]];
 			space[i].index[j] = index;
 		}
 
@@ -1497,8 +1531,9 @@ mod_snapshot(struct log_io_iter *i)
 
 		Index *pk = space[n].index[0];
 
-		pk->iterator_init(pk, 0, NULL);
-		while ((tuple = pk->iterator.next(pk))) {
+		struct iterator *it = pk->position;
+		[pk initIterator: it];
+		while ((tuple = it->next(it))) {
 			snapshot_write_tuple(i, n, tuple);
 		}
 	}
diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m
index 162ed9ba32d43083e39783ea4f177a1d93dad37d..d68efa40fdf0562f9b663a706988c7de18fe9d7e 100644
--- a/mod/box/box_lua.m
+++ b/mod/box/box_lua.m
@@ -272,7 +272,7 @@ static int
 lbox_index_len(struct lua_State *L)
 {
 	Index *index = lua_checkindex(L, 1);
-	lua_pushinteger(L, index->size(index));
+	lua_pushinteger(L, [index size]);
 	return 1;
 }
 
@@ -280,7 +280,7 @@ static int
 lbox_index_min(struct lua_State *L)
 {
 	Index *index = lua_checkindex(L, 1);
-	lbox_pushtuple(L, index->min(index));
+	lbox_pushtuple(L, [index min]);
 	return 1;
 }
 
@@ -288,7 +288,7 @@ static int
 lbox_index_max(struct lua_State *L)
 {
 	Index *index = lua_checkindex(L, 1);
-	lbox_pushtuple(L, index->max(index));
+	lbox_pushtuple(L, [index max]);
 	return 1;
 }
 
@@ -368,7 +368,7 @@ lbox_index_next(struct lua_State *L)
 		 * If there is nothing or nil on top of the stack,
 		 * start iteration from the beginning.
 		 */
-		index->iterator_init(index, 0, NULL);
+		[index initIterator: index->position];
 	} else if (argc > 1 || !lua_islightuserdata(L, 2)) {
 		/*
 		 * We've got something different from iterator's
@@ -403,11 +403,11 @@ lbox_index_next(struct lua_State *L)
 			luaL_error(L, "index.next(): key part count (%d) "
 				   "does not match index cardinality (%d)",
 				   cardinality, index->key.part_count);
-		index->iterator_init(index, cardinality, key);
+		[index initIterator: index->position :key :cardinality];
 	}
-	struct box_tuple *tuple = index->iterator.next(index);
+	struct box_tuple *tuple = index->position->next(index->position);
 	if (tuple)
-		lua_pushlightuserdata(L, &index->iterator);
+		lua_pushlightuserdata(L, index->position);
 	/* If tuple is NULL, pushes nil as end indicator. */
 	lbox_pushtuple(L, tuple);
 	return tuple ? 2 : 1;
diff --git a/mod/box/index.h b/mod/box/index.h
index 58af46372aa5f6cfec8f73c1bbbbdcc9bdebe38a..b969ebb47ad999e17185139da417308eac8ac308 100644
--- a/mod/box/index.h
+++ b/mod/box/index.h
@@ -26,29 +26,12 @@
  * SUCH DAMAGE.
  */
 #import <objc/Object.h>
-#include <assoc.h>
+#include <stdbool.h>
+#include <util.h>
 
-/**
- * A field reference used for TREE indexes. Either stores a copy
- * of the corresponding field in the tuple or points to that field
- * in the tuple (depending on field length).
- */
-
-struct field {
-	/** Field data length. */
-	u32 len;
-	/** Actual field data. For small fields we store the value
-	 * of the field (u32, u64, strings up to 8 bytes), for
-	 * longer fields, we store a pointer to field data in the
-	 * tuple in the primary index.
-	 */
-	union {
-		u32 u32;
-		u64 u64;
-		u8 data[sizeof(u64)];
-		void *data_ptr;
-	};
-};
+struct box_tuple;
+struct space;
+struct index;
 
 /*
  * Possible field data types. Can't use STRS/ENUM macros for them,
@@ -61,29 +44,13 @@ extern const char *field_data_type_strs[];
 enum index_type { HASH, TREE, index_type_MAX };
 extern const char *index_type_strs[];
 
-struct index_tree_el {
-	struct box_tuple *tuple;
-	struct field key[];
-};
-
-#define INDEX_TREE_EL_SIZE(index) \
-	(sizeof(struct index_tree_el) + sizeof(struct field) * (index)->key.part_count)
-
-#include <third_party/sptree.h>
-SPTREE_DEF(str_t, realloc);
-
-/* Indexes at preallocated search positions.  */
-enum { POS_READ = 0, POS_WRITE = 1, POS_MAX = 2 };
-
 /** Descriptor of a single part in a multipart key. */
-
 struct key_part {
 	u32 fieldno;
 	enum field_data_type type;
 };
 
 /* Descriptor of a multipart key. */
-
 struct key {
 	/* Description of parts of a multipart index. */
 	struct key_part *parts;
@@ -113,32 +80,6 @@ struct key {
 	bool enabled;
 	bool unique;
 
-	size_t (*size)(Index *index);
-	struct box_tuple *(*find)(Index *index, void *key); /* only for unique lookups */
-	struct box_tuple *(*min)(Index *index);
-	struct box_tuple *(*max)(Index *index);
-	struct box_tuple *(*find_by_tuple)(Index * index, struct box_tuple * pattern);
-	void (*remove)(Index *index, struct box_tuple *);
-	void (*replace)(Index *index, struct box_tuple *, struct box_tuple *);
-	void (*iterator_init)(Index *, int cardinality, void *key);
-	union {
-		struct mh_lstrptr_t *str_hash;
-		struct mh_i32ptr_t *int_hash;
-		struct mh_i64ptr_t *int64_hash;
-		struct mh_i32ptr_t *hash;
-		sptree_str_t *tree;
-	} idx;
-	struct iterator {
-		union {
-			struct sptree_str_t_iterator *t_iter;
-			mh_int_t h_iter;
-		};
-		struct box_tuple *(*next)(Index *);
-		struct box_tuple *(*next_equal)(Index *);
-	} iterator;
-	/* Reusable iteration positions, to save on memory allocation. */
-	struct index_tree_el *position[POS_MAX];
-
 	struct space *space;
 
 	/* Description of a possibly multipart key. */
@@ -148,28 +89,56 @@ struct key {
 	u32 n;
 
 	enum index_type type;
+
+	/*
+	 * Pre-allocated iterator to speed up the main case of
+	 * box_process(). Should not be used elsewhere.
+	 */
+	struct iterator *position;
 };
 
++ (Index *) alloc: (enum index_type) type :(struct key *) key;
 /**
  * Initialize index instance.
  *
  * @param space    space the index belongs to
+ * @param key      key part description
  */
-- (void) init: (struct space *) space_arg;
+- (void) init: (struct key *) key :(struct space *) space_arg;
+/** Destroy and free index instance. */
+- (void) free;
 /**
- * Destroy and free index instance.
+ * Finish index construction.
  */
-- (void) free;
+- (void) enable;
+- (size_t) size;
+- (struct box_tuple *) min;
+- (struct box_tuple *) max;
+- (struct box_tuple *) find: (void *) key; /* only for unique lookups */
+- (struct box_tuple *) findBy: (struct box_tuple *) pattern;
+- (void) remove: (struct box_tuple *) tuple;
+- (void) replace: (struct box_tuple *) old :(struct box_tuple *) new;
+/**
+ * Create a structure to represent an iterator. Must be
+ * initialized separately.
+ */
+- (struct iterator *) allocIterator;
+- (void) initIterator: (struct iterator *) iterator;
+- (void) initIterator: (struct iterator *) iterator :(void *) key
+			:(int) part_count;
 @end
 
+struct iterator {
+	struct box_tuple *(*next)(struct iterator *);
+	struct box_tuple *(*next_equal)(struct iterator *);
+};
+
 #define foreach_index(n, index_var)					\
 	Index *index_var;						\
 	for (Index **index_ptr = space[(n)].index;			\
 	     *index_ptr != nil; index_ptr++)				\
 		if ((index_var = *index_ptr)->enabled)
 
-struct box_txn;
-void validate_indexes(struct box_txn *txn);
 void build_indexes(void);
 
 #endif /* TARANTOOL_BOX_INDEX_H_INCLUDED */
diff --git a/mod/box/index.m b/mod/box/index.m
index 0cb5c460d560db5f796f082aa32c5ffbf50195a7..5c4e6d84319f5bc69490fe27450418eb9a6c894f 100644
--- a/mod/box/index.m
+++ b/mod/box/index.m
@@ -23,7 +23,27 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "index.h"
+#include "say.h"
+#include "tuple.h"
+#include "pickle.h"
+#include "exception.h"
+#include "box.h"
 
+const char *field_data_type_strs[] = {"NUM", "NUM64", "STR", "\0"};
+const char *index_type_strs[] = { "HASH", "TREE", "\0" };
+
+#if 0
+
+			index->key = key;
+			index->unique = cfg_index->unique;
+			index->type = STR2ENUM(index_type, cfg_index->type);
+			index->n = j;
+	memc_index->key = key;
+	memc_index->unique = true;
+	memc_index->type = HASH;
+	memc_index->enabled = true;
+	memc_index->n = 0;
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdbool.h>
@@ -44,100 +64,9 @@
 #include <mod/box/index.h>
 #include <mod/box/tuple.h>
 
-const char *field_data_type_strs[] = {"NUM", "NUM64", "STR", "\0"};
-const char *index_type_strs[] = { "HASH", "TREE", "\0" };
-
-const struct field ASTERISK = {
-	.len = UINT32_MAX,
-	{
-		.data_ptr = NULL,
-	}
-};
-
-#define IS_ASTERISK(f) ((f)->len == ASTERISK.len && (f)->data_ptr == ASTERISK.data_ptr)
-
-/** Compare two fields of an index key.
- *
- * @retval 0  two fields are equal
- * @retval -1 f2 is less than f1
- * @retval 1 f2 is greater than f1
- */
-
-static i8
-field_compare(struct field *f1, struct field *f2, enum field_data_type type)
-{
-	if (IS_ASTERISK(f1) || IS_ASTERISK(f2))
-		return 0;
-
-	if (type == NUM) {
-		assert(f1->len == f2->len);
-		assert(f1->len == sizeof(f1->u32));
-
-		return f1->u32 >f2->u32 ? 1 : f1->u32 == f2->u32 ? 0 : -1;
-	} else if (type == NUM64) {
-		assert(f1->len == f2->len);
-		assert(f1->len == sizeof(f1->u64));
-
-		return f1->u64 >f2->u64 ? 1 : f1->u64 == f2->u64 ? 0 : -1;
-	} else if (type == STRING) {
-		i32 cmp;
-		void *f1_data, *f2_data;
-
-		f1_data = f1->len <= sizeof(f1->data) ? f1->data : f1->data_ptr;
-		f2_data = f2->len <= sizeof(f2->data) ? f2->data : f2->data_ptr;
-
-		cmp = memcmp(f1_data, f2_data, MIN(f1->len, f2->len));
-
-		if (cmp > 0)
-			return 1;
-		else if (cmp < 0)
-			return -1;
-		else if (f1->len == f2->len)
-			return 0;
-		else if (f1->len > f2->len)
-			return 1;
-		else
-			return -1;
-	}
-
-	panic("impossible happened");
-}
 
 
-/*
- * Compare index_tree elements only by fields defined in
- * index->field_cmp_order.
- * Return:
- *      Common meaning:
- *              < 0  - a is smaller than b
- *              == 0 - a is equal to b
- *              > 0  - a is greater than b
- */
-static int
-index_tree_el_unique_cmp(struct index_tree_el *elem_a,
-			 struct index_tree_el *elem_b,
-			 Index *index)
-{
-	int r = 0;
-	for (i32 i = 0, end = index->key.part_count; i < end; ++i) {
-		r = field_compare(&elem_a->key[i], &elem_b->key[i],
-				  index->key.parts[i].type);
-		if (r != 0)
-			break;
-	}
-	return r;
-}
 
-static int
-index_tree_el_cmp(struct index_tree_el *elem_a, struct index_tree_el *elem_b,
-		  Index *index)
-{
-	int r = index_tree_el_unique_cmp(elem_a, elem_b, index);
-	if (r == 0 && elem_a->tuple && elem_b->tuple)
-		r = (elem_a->tuple < elem_b->tuple ?
-		     -1 : elem_a->tuple > elem_b->tuple);
-	return r;
-}
 
 static size_t
 index_tree_size(Index *index)
@@ -281,43 +210,6 @@ index_hash_str_find(Index *self, void *key)
 	return ret;
 }
 
-void
-index_tree_el_init(struct index_tree_el *elem,
-		   Index *index, struct box_tuple *tuple)
-{
-	void *tuple_data = tuple->data;
-
-	for (i32 i = 0; i < index->key.max_fieldno; ++i) {
-		struct field f;
-
-		if (i < tuple->cardinality) {
-			f.len = load_varint32(&tuple_data);
-			if (f.len <= sizeof(f.data)) {
-				memset(f.data, 0, sizeof(f.data));
-				memcpy(f.data, tuple_data, f.len);
-			} else
-				f.data_ptr = tuple_data;
-			tuple_data += f.len;
-		} else
-			f = ASTERISK;
-
-		u32 key_field_no = index->key.cmp_order[i];
-
-		if (key_field_no == -1)
-			continue;
-
-		if (index->key.parts[key_field_no].type == NUM) {
-			if (f.len != 4)
-				tnt_raise(IllegalParams, :"key is not u32");
-		} else if (index->key.parts[key_field_no].type == NUM64 && f.len != 8) {
-				tnt_raise(IllegalParams, :"key is not u64");
-		}
-
-		elem->key[key_field_no] = f;
-	}
-	elem->tuple = tuple;
-}
-
 void
 init_search_pattern(Index *index, int key_cardinality, void *key)
 {
@@ -586,108 +478,8 @@ index_tree_iterator_init(Index *index,
 				       index->position[POS_READ]);
 }
 
-void
-validate_indexes(struct box_txn *txn)
-{
-	if (space[txn->n].index[1] != nil) {	/* there is more than one index */
-		foreach_index(txn->n, index) {
-			for (u32 f = 0; f < index->key.part_count; ++f) {
-				if (index->key.parts[f].fieldno >= txn->tuple->cardinality)
-					tnt_raise(IllegalParams, :"tuple must have all indexed fields");
-
-				if (index->key.parts[f].type == STRING)
-					continue;
-
-				void *field = tuple_field(txn->tuple, index->key.parts[f].fieldno);
-				u32 len = load_varint32(&field);
-
-				if (index->key.parts[f].type == NUM && len != sizeof(u32))
-					tnt_raise(IllegalParams, :"field must be NUM");
-
-				if (index->key.parts[f].type == NUM64 && len != sizeof(u64))
-					tnt_raise(IllegalParams, :"field must be NUM64");
-			}
-			if (index->type == TREE && index->unique == false)
-				/* Don't check non unique indexes */
-				continue;
-
-			struct box_tuple *tuple = index->find_by_tuple(index, txn->tuple);
-
-			if (tuple != NULL && tuple != txn->old_tuple)
-				tnt_raise(ClientError, :ER_INDEX_VIOLATION);
-		}
-	}
-}
-
 
-void
-build_index(Index *pk, Index *index)
-{
-	u32 n_tuples = pk->size(pk);
-	u32 estimated_tuples = n_tuples * 1.2;
 
-	struct index_tree_el *elem = NULL;
-	if (n_tuples) {
-		/*
-		 * Allocate a little extra to avoid
-		 * unnecessary realloc() when more data is
-		 * inserted.
-		*/
-		size_t sz = estimated_tuples * INDEX_TREE_EL_SIZE(index);
-		elem = malloc(sz);
-		if (elem == NULL)
-			panic("malloc(): failed to allocate %"PRI_SZ" bytes", sz);
-	}
-	struct index_tree_el *m;
-	u32 i = 0;
-
-	pk->iterator_init(pk, 0, NULL);
-	struct box_tuple *tuple;
-	while ((tuple = pk->iterator.next(pk))) {
-
-		m = (struct index_tree_el *)
-			((char *)elem + i * INDEX_TREE_EL_SIZE(index));
-
-		index_tree_el_init(m, index, tuple);
-		++i;
-	}
-
-	if (n_tuples)
-		say_info("Sorting %"PRIu32 " keys in index %" PRIu32 "...", n_tuples, index->n);
-
-	/* If n_tuples == 0 then estimated_tuples = 0, elem == NULL, tree is empty */
-	sptree_str_t_init(index->idx.tree, INDEX_TREE_EL_SIZE(index),
-			  elem, n_tuples, estimated_tuples,
-			  (void*) (index->unique ? index_tree_el_unique_cmp :
-			  index_tree_el_cmp), index);
-	index->enabled = true;
-}
-
-void
-build_indexes(void)
-{
-	for (u32 n = 0; n < BOX_SPACE_MAX; ++n) {
-		if (space[n].enabled == false)
-			continue;
-		/* A shortcut to avoid unnecessary log messages. */
-		if (space[n].index[1] == nil)
-			continue; /* no secondary keys */
-		say_info("Building secondary keys in space %" PRIu32 "...", n);
-		Index *pk = space[n].index[0];
-		for (u32 idx = 1;; idx++) {
-			Index *index = space[n].index[idx];
-			if (index == nil)
-				break;
-
-			if (index->type != TREE)
-				continue;
-			assert(index->enabled == false);
-
-			build_index(pk, index);
-		}
-		say_info("Space %"PRIu32": done", n);
-	}
-}
 
 static void
 index_hash_num(Index *index, struct space *space)
@@ -857,3 +649,287 @@ index_tree_free(Index *index)
 
 @end
 
+#endif
+
+/* {{{ HashIndex -- base class for all hashes. ********************/
+
+@interface HashIndex: Index
+@end
+
+@implementation HashIndex
+@end
+
+/* }}} */
+
+/* {{{ Hash32Index ************************************************/
+
+@interface Hash32Index: HashIndex
+@end
+
+@implementation Hash32Index
+@end
+
+/* }}} */
+
+/* {{{ Hash64Index ************************************************/
+
+@interface Hash64Index: HashIndex
+@end
+
+@implementation Hash64Index
+@end
+
+/* }}} */
+
+/* {{{ HashStrIndex ***********************************************/
+
+@interface HashStrIndex: HashIndex
+@end
+
+@implementation HashStrIndex
+@end
+
+/* }}} */
+
+/* {{{ TreeIndex and auxiliary structures. ************************/
+/**
+ * A field reference used for TREE indexes. Either stores a copy
+ * of the corresponding field in the tuple or points to that field
+ * in the tuple (depending on field length).
+ */
+struct field {
+	/** Field data length. */
+	u32 len;
+	/** Actual field data. For small fields we store the value
+	 * of the field (u32, u64, strings up to 8 bytes), for
+	 * longer fields, we store a pointer to field data in the
+	 * tuple in the primary index.
+	 */
+	union {
+		u32 u32;
+		u64 u64;
+		u8 data[sizeof(u64)];
+		void *data_ptr;
+	};
+};
+
+const struct field ASTERISK = {
+	.len = UINT32_MAX,
+	{
+		.data_ptr = NULL,
+	}
+};
+
+#define IS_ASTERISK(f) ((f)->len == ASTERISK.len && (f)->data_ptr == ASTERISK.data_ptr)
+
+/** Compare two fields of an index key.
+ *
+ * @retval 0  two fields are equal
+ * @retval -1 f2 is less than f1
+ * @retval 1 f2 is greater than f1
+ */
+static i8
+field_compare(struct field *f1, struct field *f2, enum field_data_type type)
+{
+	if (IS_ASTERISK(f1) || IS_ASTERISK(f2))
+		return 0;
+
+	if (type == NUM) {
+		assert(f1->len == f2->len);
+		assert(f1->len == sizeof(f1->u32));
+
+		return f1->u32 >f2->u32 ? 1 : f1->u32 == f2->u32 ? 0 : -1;
+	} else if (type == NUM64) {
+		assert(f1->len == f2->len);
+		assert(f1->len == sizeof(f1->u64));
+
+		return f1->u64 >f2->u64 ? 1 : f1->u64 == f2->u64 ? 0 : -1;
+	} else if (type == STRING) {
+		i32 cmp;
+		void *f1_data, *f2_data;
+
+		f1_data = f1->len <= sizeof(f1->data) ? f1->data : f1->data_ptr;
+		f2_data = f2->len <= sizeof(f2->data) ? f2->data : f2->data_ptr;
+
+		cmp = memcmp(f1_data, f2_data, MIN(f1->len, f2->len));
+
+		if (cmp > 0)
+			return 1;
+		else if (cmp < 0)
+			return -1;
+		else if (f1->len == f2->len)
+			return 0;
+		else if (f1->len > f2->len)
+			return 1;
+		else
+			return -1;
+	}
+	panic("impossible happened");
+}
+
+struct index_tree_el {
+	struct box_tuple *tuple;
+	struct field key[];
+};
+
+#define INDEX_TREE_EL_SIZE(key) \
+	(sizeof(struct index_tree_el) + sizeof(struct field) * (key)->part_count)
+
+void
+index_tree_el_init(struct index_tree_el *elem,
+		   struct key *key, struct box_tuple *tuple)
+{
+	void *tuple_data = tuple->data;
+
+	for (i32 i = 0; i < key->max_fieldno; ++i) {
+		struct field f;
+
+		if (i < tuple->cardinality) {
+			f.len = load_varint32(&tuple_data);
+			if (f.len <= sizeof(f.data)) {
+				memset(f.data, 0, sizeof(f.data));
+				memcpy(f.data, tuple_data, f.len);
+			} else
+				f.data_ptr = tuple_data;
+			tuple_data += f.len;
+		} else
+			f = ASTERISK;
+
+		u32 key_field_no = key->cmp_order[i];
+
+		if (key_field_no == -1)
+			continue;
+
+		if (key->parts[key_field_no].type == NUM) {
+			if (f.len != 4)
+				tnt_raise(IllegalParams, :"key is not u32");
+		} else if (key->parts[key_field_no].type == NUM64 && f.len != 8) {
+				tnt_raise(IllegalParams, :"key is not u64");
+		}
+
+		elem->key[key_field_no] = f;
+	}
+	elem->tuple = tuple;
+}
+
+/*
+ * Compare index_tree elements only by fields defined in
+ * index->field_cmp_order.
+ * Return:
+ *      Common meaning:
+ *              < 0  - a is smaller than b
+ *              == 0 - a is equal to b
+ *              > 0  - a is greater than b
+ */
+static int
+index_tree_el_unique_cmp(struct index_tree_el *elem_a,
+			 struct index_tree_el *elem_b,
+			 struct key *key)
+{
+	int r = 0;
+	for (i32 i = 0, end = key->part_count; i < end; ++i) {
+		r = field_compare(&elem_a->key[i], &elem_b->key[i],
+				  key->parts[i].type);
+		if (r != 0)
+			break;
+	}
+	return r;
+}
+
+static int
+index_tree_el_cmp(struct index_tree_el *elem_a, struct index_tree_el *elem_b,
+		  struct key *key)
+{
+	int r = index_tree_el_unique_cmp(elem_a, elem_b, key);
+	if (r == 0 && elem_a->tuple && elem_b->tuple)
+		r = (elem_a->tuple < elem_b->tuple ?
+		     -1 : elem_a->tuple > elem_b->tuple);
+	return r;
+}
+
+#include <third_party/sptree.h>
+SPTREE_DEF(str_t, realloc);
+
+@interface TreeIndex: Index {
+	sptree_str_t *tree;
+};
+- (void) build: (Index *) pk;
+@end
+
+@implementation TreeIndex
+
+- (void) build: (Index *) pk
+{
+	u32 n_tuples = [pk size];
+	u32 estimated_tuples = n_tuples * 1.2;
+
+	assert(enabled == false);
+
+	struct index_tree_el *elem = NULL;
+	if (n_tuples) {
+		/*
+		 * Allocate a little extra to avoid
+		 * unnecessary realloc() when more data is
+		 * inserted.
+		*/
+		size_t sz = estimated_tuples * INDEX_TREE_EL_SIZE(&key);
+		elem = malloc(sz);
+		if (elem == NULL)
+			panic("malloc(): failed to allocate %"PRI_SZ" bytes", sz);
+	}
+	struct index_tree_el *m;
+	u32 i = 0;
+
+	struct iterator *it = pk->position;
+	[pk initIterator: it];
+	struct box_tuple *tuple;
+	while ((tuple = it->next(it))) {
+
+		m = (struct index_tree_el *)
+			((char *)elem + i * INDEX_TREE_EL_SIZE(&key));
+
+		index_tree_el_init(m, &key, tuple);
+		++i;
+	}
+
+	if (n_tuples)
+		say_info("Sorting %"PRIu32 " keys in index %" PRIu32 "...", n_tuples, self->n);
+
+	/* If n_tuples == 0 then estimated_tuples = 0, elem == NULL, tree is empty */
+	sptree_str_t_init(tree, INDEX_TREE_EL_SIZE(&key),
+			  elem, n_tuples, estimated_tuples,
+			  (void *) (unique ? index_tree_el_unique_cmp :
+			  index_tree_el_cmp), &key);
+	enabled = true;
+}
+@end
+
+/* }}} */
+
+void
+build_indexes(void)
+{
+	for (u32 n = 0; n < BOX_SPACE_MAX; ++n) {
+		if (space[n].enabled == false)
+			continue;
+		/* A shortcut to avoid unnecessary log messages. */
+		if (space[n].index[1] == nil)
+			continue; /* no secondary keys */
+		say_info("Building secondary keys in space %" PRIu32 "...", n);
+		Index *pk = space[n].index[0];
+		for (u32 idx = 1;; idx++) {
+			Index *index = space[n].index[idx];
+			if (index == nil)
+				break;
+
+			if (index->type != TREE)
+				continue;
+			[(TreeIndex*) index build: pk];
+		}
+		say_info("Space %"PRIu32": done", n);
+	}
+}
+
+/**
+ * vim: foldmethod=marker
+ */
diff --git a/mod/box/memcached.m b/mod/box/memcached.m
index 95af064faa10819227a7fb42f2a9ce74a81a6a71..cdcd2a3033d349eb438cd6c94e1e731cccbfae9d 100644
--- a/mod/box/memcached.m
+++ b/mod/box/memcached.m
@@ -32,6 +32,7 @@
 #include "say.h"
 #include "stat.h"
 #include "salloc.h"
+#include "pickle.h"
 
 #define STAT(_)					\
         _(MEMC_GET, 1)				\
@@ -129,7 +130,7 @@ delete(void *key)
 static struct box_tuple *
 find(void *key)
 {
-	return memcached_index->find(memcached_index, key);
+	return [memcached_index find: key];
 }
 
 static struct meta *
@@ -282,10 +283,12 @@ flush_all(void *data)
 	uintptr_t delay = (uintptr_t)data;
 	fiber_sleep(delay - ev_now());
 	struct box_tuple *tuple;
-	memcached_index->iterator_init(memcached_index, 0, NULL);
-	while ((tuple = memcached_index->iterator.next(memcached_index))) {
+	struct iterator *it = [memcached_index allocIterator];
+	[memcached_index initIterator: it];
+	while ((tuple = it->next(it))) {
 	       meta(tuple)->exptime = 1;
 	}
+	sfree(it);
 }
 
 #define STORE									\
@@ -427,9 +430,6 @@ memcached_space_init()
 	memc_s->cardinality = 4;
 	memc_s->n = cfg.memcached_space;
 
-	/* Configure memcached index. */
-	Index *memc_index = memc_s->index[0] = [[Index alloc] init];
-
 	struct key key;
 	/* Configure memcached index key. */
 	key.part_count = 1;
@@ -446,12 +446,9 @@ memcached_space_init()
 	key.max_fieldno = 1;
 	key.cmp_order[0] = 0;
 
-	memc_index->key = key;
-	memc_index->unique = true;
-	memc_index->type = HASH;
-	memc_index->enabled = true;
-	memc_index->n = 0;
-	[memc_index init: memc_s];
+	/* Configure memcached index. */
+	Index *memc_index = memc_s->index[0] = [Index alloc: HASH :&key];
+	[memc_index init: &key :memc_s];
 }
 
 /** Delete a bunch of expired keys. */
@@ -476,7 +473,7 @@ memcached_delete_expired_keys(struct tbuf *keys_to_delete)
 
 	double delay = ((double) cfg.memcached_expire_per_loop *
 			cfg.memcached_expire_full_sweep /
-			(memcached_index->size(memcached_index) + 1));
+			([memcached_index size] + 1));
 	if (delay > 1)
 		delay = 1;
 	fiber_setcancelstate(true);
@@ -490,15 +487,17 @@ memcached_expire_loop(void *data __attribute__((unused)))
 	struct box_tuple *tuple = NULL;
 
 	say_info("memcached expire fiber started");
-	for (;;) {
+	struct iterator *it = [memcached_index allocIterator];
+	@try {
+restart:
 		if (tuple == NULL)
-			memcached_index->iterator_init(memcached_index, 0, NULL);
+			[memcached_index initIterator: it];
 
 		struct tbuf *keys_to_delete = tbuf_alloc(fiber->gc_pool);
 
 		for (int j = 0; j < cfg.memcached_expire_per_loop; j++) {
 
-			tuple = memcached_index->iterator.next(memcached_index);
+			tuple = it->next(it);
 
 			if (tuple == NULL)
 				break;
@@ -511,6 +510,9 @@ memcached_expire_loop(void *data __attribute__((unused)))
 		}
 		memcached_delete_expired_keys(keys_to_delete);
 		fiber_gc();
+		goto restart;
+	} @finally {
+		sfree(it);
 	}
 }