Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH 0/2] Make tree hint optional
@ 2020-10-19  9:51 Aleksandr Lyapunov
  2020-10-19  9:51 ` [Tarantool-patches] [PATCH 1/2] memtx: move memtx_tree.c to memtx_tree.cc Aleksandr Lyapunov
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Aleksandr Lyapunov @ 2020-10-19  9:51 UTC (permalink / raw)
  To: tarantool-patches, korablev @ tarantool . org,
	i . kosarev @ tarantool . org

https://github.com/tarantool/tarantool/issues/4927
https://github.com/tarantool/tarantool/tree/alyapunov/gh-4927-optional-hints

Aleksandr Lyapunov (1):
  memtx: make tuple compare hints optional

Ilya Kosarev (1):
  memtx: move memtx_tree.c to memtx_tree.cc

 src/box/CMakeLists.txt              |    2 +-
 src/box/index_def.c                 |    2 +
 src/box/index_def.h                 |    6 +
 src/box/lua/schema.lua              |   53 ++
 src/box/lua/space.cc                |    7 +
 src/box/memtx_engine.c              |    2 +
 src/box/memtx_tree.c                | 1523 -------------------------------
 src/box/memtx_tree.cc               | 1723 +++++++++++++++++++++++++++++++++++
 src/lib/salad/bps_tree.h            |   19 +
 test/box/alter.result               |  103 ++-
 test/box/alter.test.lua             |   34 +
 test/box/errinj.result              |    3 +-
 test/box/tree_pk.result             |  314 +++++++
 test/box/tree_pk.test.lua           |  115 +++
 test/box/tree_pk_multipart.result   |  153 ++++
 test/box/tree_pk_multipart.test.lua |   64 ++
 16 files changed, 2595 insertions(+), 1528 deletions(-)
 delete mode 100644 src/box/memtx_tree.c
 create mode 100644 src/box/memtx_tree.cc

-- 
2.7.4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH 1/2] memtx: move memtx_tree.c to memtx_tree.cc
  2020-10-19  9:51 [Tarantool-patches] [PATCH 0/2] Make tree hint optional Aleksandr Lyapunov
@ 2020-10-19  9:51 ` Aleksandr Lyapunov
  2020-10-19  9:51 ` [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional Aleksandr Lyapunov
  2020-10-20  1:01 ` [Tarantool-patches] [PATCH 0/2] Make tree hint optional Nikita Pettik
  2 siblings, 0 replies; 11+ messages in thread
From: Aleksandr Lyapunov @ 2020-10-19  9:51 UTC (permalink / raw)
  To: tarantool-patches, korablev @ tarantool . org,
	i . kosarev @ tarantool . org

From: Ilya Kosarev <i.kosarev@tarantool.org>

It is needed for the further c++ implementation in memtx_tree.cc. To
see the file diff properly it should not be renamed and reworked
in one commit. Some not c++ comparable casts were fixed.

Prerequisites: #4927
---
 src/box/CMakeLists.txt |    2 +-
 src/box/memtx_tree.c   | 1523 -----------------------------------------------
 src/box/memtx_tree.cc  | 1526 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1527 insertions(+), 1524 deletions(-)
 delete mode 100644 src/box/memtx_tree.c
 create mode 100644 src/box/memtx_tree.cc

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 18d678e..ab1ab19 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -126,7 +126,7 @@ add_library(box STATIC
     index_def.c
     iterator_type.c
     memtx_hash.c
-    memtx_tree.c
+    memtx_tree.cc
     memtx_rtree.c
     memtx_bitset.c
     memtx_tx.c
diff --git a/src/box/memtx_tree.c b/src/box/memtx_tree.c
deleted file mode 100644
index 5af482f..0000000
--- a/src/box/memtx_tree.c
+++ /dev/null
@@ -1,1523 +0,0 @@
-/*
- * Copyright 2010-2016, Tarantool AUTHORS, please see AUTHORS file.
- *
- * 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 "memtx_tree.h"
-#include "memtx_engine.h"
-#include "space.h"
-#include "schema.h" /* space_by_id(), space_cache_find() */
-#include "errinj.h"
-#include "memory.h"
-#include "fiber.h"
-#include "key_list.h"
-#include "tuple.h"
-#include "txn.h"
-#include "memtx_tx.h"
-#include <third_party/qsort_arg.h>
-#include <small/mempool.h>
-
-/**
- * Struct that is used as a key in BPS tree definition.
- */
-struct memtx_tree_key_data {
-	/** Sequence of msgpacked search fields. */
-	const char *key;
-	/** Number of msgpacked search fields. */
-	uint32_t part_count;
-	/** Comparison hint, see tuple_hint(). */
-	hint_t hint;
-};
-
-/**
- * Struct that is used as a elem in BPS tree definition.
- */
-struct memtx_tree_data {
-	/* Tuple that this node is represents. */
-	struct tuple *tuple;
-	/** Comparison hint, see key_hint(). */
-	hint_t hint;
-};
-
-/**
- * Test whether BPS tree elements are identical i.e. represent
- * the same tuple at the same position in the tree.
- * @param a - First BPS tree element to compare.
- * @param b - Second BPS tree element to compare.
- * @retval true - When elements a and b are identical.
- * @retval false - Otherwise.
- */
-static bool
-memtx_tree_data_is_equal(const struct memtx_tree_data *a,
-			 const struct memtx_tree_data *b)
-{
-	return a->tuple == b->tuple;
-}
-
-#define BPS_TREE_NAME memtx_tree
-#define BPS_TREE_BLOCK_SIZE (512)
-#define BPS_TREE_EXTENT_SIZE MEMTX_EXTENT_SIZE
-#define BPS_TREE_COMPARE(a, b, arg)\
-	tuple_compare((&a)->tuple, (&a)->hint, (&b)->tuple, (&b)->hint, arg)
-#define BPS_TREE_COMPARE_KEY(a, b, arg)\
-	tuple_compare_with_key((&a)->tuple, (&a)->hint, (b)->key,\
-			       (b)->part_count, (b)->hint, arg)
-#define BPS_TREE_IS_IDENTICAL(a, b) memtx_tree_data_is_equal(&a, &b)
-#define BPS_TREE_NO_DEBUG 1
-#define bps_tree_elem_t struct memtx_tree_data
-#define bps_tree_key_t struct memtx_tree_key_data *
-#define bps_tree_arg_t struct key_def *
-
-#include "salad/bps_tree.h"
-
-#undef BPS_TREE_NAME
-#undef BPS_TREE_BLOCK_SIZE
-#undef BPS_TREE_EXTENT_SIZE
-#undef BPS_TREE_COMPARE
-#undef BPS_TREE_COMPARE_KEY
-#undef BPS_TREE_IS_IDENTICAL
-#undef BPS_TREE_NO_DEBUG
-#undef bps_tree_elem_t
-#undef bps_tree_key_t
-#undef bps_tree_arg_t
-
-struct memtx_tree_index {
-	struct index base;
-	struct memtx_tree tree;
-	struct memtx_tree_data *build_array;
-	size_t build_array_size, build_array_alloc_size;
-	struct memtx_gc_task gc_task;
-	struct memtx_tree_iterator gc_iterator;
-};
-
-/* {{{ Utilities. *************************************************/
-
-static inline struct key_def *
-memtx_tree_cmp_def(struct memtx_tree *tree)
-{
-	return tree->arg;
-}
-
-static int
-memtx_tree_qcompare(const void* a, const void *b, void *c)
-{
-	const struct memtx_tree_data *data_a = a;
-	const struct memtx_tree_data *data_b = b;
-	struct key_def *key_def = c;
-	return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
-			     data_b->hint, key_def);
-}
-
-/* {{{ MemtxTree Iterators ****************************************/
-struct tree_iterator {
-	struct iterator base;
-	struct memtx_tree_iterator tree_iterator;
-	enum iterator_type type;
-	struct memtx_tree_key_data key_data;
-	struct memtx_tree_data current;
-	/** Memory pool the iterator was allocated from. */
-	struct mempool *pool;
-};
-
-static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,
-	      "sizeof(struct tree_iterator) must be less than or equal "
-	      "to MEMTX_ITERATOR_SIZE");
-
-static void
-tree_iterator_free(struct iterator *iterator);
-
-static inline struct tree_iterator *
-tree_iterator(struct iterator *it)
-{
-	assert(it->free == tree_iterator_free);
-	return (struct tree_iterator *) it;
-}
-
-static void
-tree_iterator_free(struct iterator *iterator)
-{
-	struct tree_iterator *it = tree_iterator(iterator);
-	struct tuple *tuple = it->current.tuple;
-	if (tuple != NULL)
-		tuple_unref(tuple);
-	mempool_free(it->pool, it);
-}
-
-static int
-tree_iterator_dummie(struct iterator *iterator, struct tuple **ret)
-{
-	(void)iterator;
-	*ret = NULL;
-	return 0;
-}
-
-static int
-tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
-{
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
-	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
-		it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
-								it->current, NULL);
-	} else {
-		memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
-	}
-	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	if (res == NULL) {
-		iterator->next = tree_iterator_dummie;
-		it->current.tuple = NULL;
-		*ret = NULL;
-	} else {
-		*ret = res->tuple;
-		tuple_ref(*ret);
-		it->current = *res;
-	}
-	return 0;
-}
-
-static int
-tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
-{
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
-	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
-		it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
-								it->current, NULL);
-	}
-	memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
-	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	if (!res) {
-		iterator->next = tree_iterator_dummie;
-		it->current.tuple = NULL;
-		*ret = NULL;
-	} else {
-		*ret = res->tuple;
-		tuple_ref(*ret);
-		it->current = *res;
-	}
-	return 0;
-}
-
-static int
-tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
-{
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
-	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
-		it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
-								it->current, NULL);
-	} else {
-		memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
-	}
-	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	/* Use user key def to save a few loops. */
-	if (res == NULL ||
-	    tuple_compare_with_key(res->tuple, res->hint,
-				   it->key_data.key,
-				   it->key_data.part_count,
-				   it->key_data.hint,
-				   index->base.def->key_def) != 0) {
-		iterator->next = tree_iterator_dummie;
-		it->current.tuple = NULL;
-		*ret = NULL;
-	} else {
-		*ret = res->tuple;
-		tuple_ref(*ret);
-		it->current = *res;
-	}
-	return 0;
-}
-
-static int
-tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
-{
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
-	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
-		it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
-								it->current, NULL);
-	}
-	memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
-	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
-		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
-	/* Use user key def to save a few loops. */
-	if (res == NULL ||
-	    tuple_compare_with_key(res->tuple, res->hint,
-				   it->key_data.key,
-				   it->key_data.part_count,
-				   it->key_data.hint,
-				   index->base.def->key_def) != 0) {
-		iterator->next = tree_iterator_dummie;
-		it->current.tuple = NULL;
-		*ret = NULL;
-	} else {
-		*ret = res->tuple;
-		tuple_ref(*ret);
-		it->current = *res;
-	}
-	return 0;
-}
-
-#define WRAP_ITERATOR_METHOD(name)						\
-static int									\
-name(struct iterator *iterator, struct tuple **ret)				\
-{										\
-	struct memtx_tree *tree =						\
-		&((struct memtx_tree_index *)iterator->index)->tree;		\
-	struct tree_iterator *it = tree_iterator(iterator);			\
-	struct memtx_tree_iterator *ti = &it->tree_iterator;			\
-	uint32_t iid = iterator->index->def->iid;				\
-	bool is_multikey = iterator->index->def->key_def->is_multikey;		\
-	struct txn *txn = in_txn();						\
-	struct space *space = space_by_id(iterator->space_id);			\
-	bool is_rw = txn != NULL;						\
-	do {									\
-		int rc = name##_base(iterator, ret);				\
-		if (rc != 0 || *ret == NULL)					\
-			return rc;						\
-		uint32_t mk_index = 0;						\
-		if (is_multikey) {						\
-			struct memtx_tree_data *check =				\
-				memtx_tree_iterator_get_elem(tree, ti);		\
-			assert(check != NULL);					\
-			mk_index = check->hint;					\
-		}								\
-		*ret = memtx_tx_tuple_clarify(txn, space, *ret,			\
-					      iid, mk_index, is_rw);		\
-	} while (*ret == NULL);							\
-	tuple_unref(it->current.tuple);						\
-	it->current.tuple = *ret;						\
-	tuple_ref(it->current.tuple);						\
-	return 0;								\
-}										\
-struct forgot_to_add_semicolon
-
-WRAP_ITERATOR_METHOD(tree_iterator_next);
-WRAP_ITERATOR_METHOD(tree_iterator_prev);
-WRAP_ITERATOR_METHOD(tree_iterator_next_equal);
-WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);
-
-#undef WRAP_ITERATOR_METHOD
-
-static void
-tree_iterator_set_next_method(struct tree_iterator *it)
-{
-	assert(it->current.tuple != NULL);
-	switch (it->type) {
-	case ITER_EQ:
-		it->base.next = tree_iterator_next_equal;
-		break;
-	case ITER_REQ:
-		it->base.next = tree_iterator_prev_equal;
-		break;
-	case ITER_ALL:
-		it->base.next = tree_iterator_next;
-		break;
-	case ITER_LT:
-	case ITER_LE:
-		it->base.next = tree_iterator_prev;
-		break;
-	case ITER_GE:
-	case ITER_GT:
-		it->base.next = tree_iterator_next;
-		break;
-	default:
-		/* The type was checked in initIterator */
-		assert(false);
-	}
-}
-
-static int
-tree_iterator_start(struct iterator *iterator, struct tuple **ret)
-{
-	*ret = NULL;
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
-	it->base.next = tree_iterator_dummie;
-	struct memtx_tree *tree = &index->tree;
-	enum iterator_type type = it->type;
-	bool exact = false;
-	assert(it->current.tuple == NULL);
-	if (it->key_data.key == 0) {
-		if (iterator_type_is_reverse(it->type))
-			it->tree_iterator = memtx_tree_iterator_last(tree);
-		else
-			it->tree_iterator = memtx_tree_iterator_first(tree);
-	} else {
-		if (type == ITER_ALL || type == ITER_EQ ||
-		    type == ITER_GE || type == ITER_LT) {
-			it->tree_iterator =
-				memtx_tree_lower_bound(tree, &it->key_data,
-						       &exact);
-			if (type == ITER_EQ && !exact)
-				return 0;
-		} else { // ITER_GT, ITER_REQ, ITER_LE
-			it->tree_iterator =
-				memtx_tree_upper_bound(tree, &it->key_data,
-						       &exact);
-			if (type == ITER_REQ && !exact)
-				return 0;
-		}
-		if (iterator_type_is_reverse(type)) {
-			/*
-			 * Because of limitations of tree search API we use use
-			 * lower_bound for LT search and upper_bound for LE
-			 * and REQ searches. Thus we found position to the
-			 * right of the target one. Let's make a step to the
-			 * left to reach target position.
-			 * If we found an invalid iterator all the elements in
-			 * the tree are less (less or equal) to the key, and
-			 * iterator_next call will convert the iterator to the
-			 * last position in the tree, that's what we need.
-			 */
-			memtx_tree_iterator_prev(tree, &it->tree_iterator);
-		}
-	}
-
-	struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,
-							&it->tree_iterator);
-	if (!res)
-		return 0;
-	*ret = res->tuple;
-	tuple_ref(*ret);
-	it->current = *res;
-	tree_iterator_set_next_method(it);
-
-	uint32_t iid = iterator->index->def->iid;
-	bool is_multikey = iterator->index->def->key_def->is_multikey;
-	struct txn *txn = in_txn();
-	struct space *space = space_by_id(iterator->space_id);
-	bool is_rw = txn != NULL;
-	uint32_t mk_index = is_multikey ? res->hint : 0;
-	*ret = memtx_tx_tuple_clarify(txn, space, *ret, iid, mk_index, is_rw);
-	if (*ret == NULL) {
-		return iterator->next(iterator, ret);
-	} else {
-		tuple_unref(it->current.tuple);
-		it->current.tuple = *ret;
-		tuple_ref(it->current.tuple);
-	}
-
-	return 0;
-}
-
-/* }}} */
-
-/* {{{ MemtxTree  **********************************************************/
-
-static void
-memtx_tree_index_free(struct memtx_tree_index *index)
-{
-	memtx_tree_destroy(&index->tree);
-	free(index->build_array);
-	free(index);
-}
-
-static void
-memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
-{
-	/*
-	 * Yield every 1K tuples to keep latency < 0.1 ms.
-	 * Yield more often in debug mode.
-	 */
-#ifdef NDEBUG
-	enum { YIELD_LOOPS = 1000 };
-#else
-	enum { YIELD_LOOPS = 10 };
-#endif
-
-	struct memtx_tree_index *index = container_of(task,
-			struct memtx_tree_index, gc_task);
-	struct memtx_tree *tree = &index->tree;
-	struct memtx_tree_iterator *itr = &index->gc_iterator;
-
-	unsigned int loops = 0;
-	while (!memtx_tree_iterator_is_invalid(itr)) {
-		struct memtx_tree_data *res =
-			memtx_tree_iterator_get_elem(tree, itr);
-		memtx_tree_iterator_next(tree, itr);
-		tuple_unref(res->tuple);
-		if (++loops >= YIELD_LOOPS) {
-			*done = false;
-			return;
-		}
-	}
-	*done = true;
-}
-
-static void
-memtx_tree_index_gc_free(struct memtx_gc_task *task)
-{
-	struct memtx_tree_index *index = container_of(task,
-			struct memtx_tree_index, gc_task);
-	memtx_tree_index_free(index);
-}
-
-static const struct memtx_gc_task_vtab memtx_tree_index_gc_vtab = {
-	.run = memtx_tree_index_gc_run,
-	.free = memtx_tree_index_gc_free,
-};
-
-static void
-memtx_tree_index_destroy(struct index *base)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
-	if (base->def->iid == 0) {
-		/*
-		 * Primary index. We need to free all tuples stored
-		 * in the index, which may take a while. Schedule a
-		 * background task in order not to block tx thread.
-		 */
-		index->gc_task.vtab = &memtx_tree_index_gc_vtab;
-		index->gc_iterator = memtx_tree_iterator_first(&index->tree);
-		memtx_engine_schedule_gc(memtx, &index->gc_task);
-	} else {
-		/*
-		 * Secondary index. Destruction is fast, no need to
-		 * hand over to background fiber.
-		 */
-		memtx_tree_index_free(index);
-	}
-}
-
-static void
-memtx_tree_index_update_def(struct index *base)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct index_def *def = base->def;
-	/*
-	 * We use extended key def for non-unique and nullable
-	 * indexes. Unique but nullable index can store multiple
-	 * NULLs. To correctly compare these NULLs extended key
-	 * def must be used. For details @sa tuple_compare.cc.
-	 */
-	index->tree.arg = def->opts.is_unique && !def->key_def->is_nullable ?
-						def->key_def : def->cmp_def;
-}
-
-static bool
-memtx_tree_index_depends_on_pk(struct index *base)
-{
-	struct index_def *def = base->def;
-	/* See comment to memtx_tree_index_update_def(). */
-	return !def->opts.is_unique || def->key_def->is_nullable;
-}
-
-static ssize_t
-memtx_tree_index_size(struct index *base)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	return memtx_tree_size(&index->tree);
-}
-
-static ssize_t
-memtx_tree_index_bsize(struct index *base)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	return memtx_tree_mem_used(&index->tree);
-}
-
-static int
-memtx_tree_index_random(struct index *base, uint32_t rnd, struct tuple **result)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct memtx_tree_data *res = memtx_tree_random(&index->tree, rnd);
-	*result = res != NULL ? res->tuple : NULL;
-	return 0;
-}
-
-static ssize_t
-memtx_tree_index_count(struct index *base, enum iterator_type type,
-		       const char *key, uint32_t part_count)
-{
-	if (type == ITER_ALL)
-		return memtx_tree_index_size(base); /* optimization */
-	return generic_index_count(base, type, key, part_count);
-}
-
-static int
-memtx_tree_index_get(struct index *base, const char *key,
-		     uint32_t part_count, struct tuple **result)
-{
-	assert(base->def->opts.is_unique &&
-	       part_count == base->def->key_def->part_count);
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	struct memtx_tree_key_data key_data;
-	key_data.key = key;
-	key_data.part_count = part_count;
-	key_data.hint = key_hint(key, part_count, cmp_def);
-	struct memtx_tree_data *res = memtx_tree_find(&index->tree, &key_data);
-	if (res == NULL) {
-		*result = NULL;
-		return 0;
-	}
-	struct txn *txn = in_txn();
-	struct space *space = space_by_id(base->def->space_id);
-	bool is_rw = txn != NULL;
-	uint32_t mk_index = base->def->key_def->is_multikey ? res->hint : 0;
-	*result = memtx_tx_tuple_clarify(txn, space, res->tuple, base->def->iid,
-					 mk_index, is_rw);
-	return 0;
-}
-
-static int
-memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
-			 struct tuple *new_tuple, enum dup_replace_mode mode,
-			 struct tuple **result)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	if (new_tuple) {
-		struct memtx_tree_data new_data;
-		new_data.tuple = new_tuple;
-		new_data.hint = tuple_hint(new_tuple, cmp_def);
-		struct memtx_tree_data dup_data;
-		dup_data.tuple = NULL;
-
-		/* Try to optimistically replace the new_tuple. */
-		int tree_res = memtx_tree_insert(&index->tree, new_data,
-						 &dup_data);
-		if (tree_res) {
-			diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
-				 "memtx_tree_index", "replace");
-			return -1;
-		}
-
-		uint32_t errcode = replace_check_dup(old_tuple,
-						     dup_data.tuple, mode);
-		if (errcode) {
-			memtx_tree_delete(&index->tree, new_data);
-			if (dup_data.tuple != NULL)
-				memtx_tree_insert(&index->tree, dup_data, NULL);
-			struct space *sp = space_cache_find(base->def->space_id);
-			if (sp != NULL)
-				diag_set(ClientError, errcode, base->def->name,
-					 space_name(sp));
-			return -1;
-		}
-		if (dup_data.tuple != NULL) {
-			*result = dup_data.tuple;
-			return 0;
-		}
-	}
-	if (old_tuple) {
-		struct memtx_tree_data old_data;
-		old_data.tuple = old_tuple;
-		old_data.hint = tuple_hint(old_tuple, cmp_def);
-		memtx_tree_delete(&index->tree, old_data);
-	}
-	*result = old_tuple;
-	return 0;
-}
-
-/**
- * Perform tuple insertion by given multikey index.
- * In case of replacement, all old tuple entries are deleted
- * by all it's multikey indexes.
- */
-static int
-memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
-			struct tuple *old_tuple, struct tuple *new_tuple,
-			enum dup_replace_mode mode, hint_t hint,
-			struct memtx_tree_data *replaced_data,
-			bool *is_multikey_conflict)
-{
-	struct memtx_tree_data new_data, dup_data;
-	new_data.tuple = new_tuple;
-	new_data.hint = hint;
-	dup_data.tuple = NULL;
-	*is_multikey_conflict = false;
-	if (memtx_tree_insert(&index->tree, new_data, &dup_data) != 0) {
-		diag_set(OutOfMemory, MEMTX_EXTENT_SIZE, "memtx_tree_index",
-			 "replace");
-		return -1;
-	}
-	int errcode = 0;
-	if (dup_data.tuple == new_tuple) {
-		/*
-		 * When tuple contains the same key multiple
-		 * times, the previous key occurrence is pushed
-		 * out of the index.
-		 */
-		*is_multikey_conflict = true;
-	} else if ((errcode = replace_check_dup(old_tuple, dup_data.tuple,
-					        mode)) != 0) {
-		/* Rollback replace. */
-		memtx_tree_delete(&index->tree, new_data);
-		if (dup_data.tuple != NULL)
-			memtx_tree_insert(&index->tree, dup_data, NULL);
-		struct space *sp = space_cache_find(index->base.def->space_id);
-		if (sp != NULL) {
-			diag_set(ClientError, errcode, index->base.def->name,
-				 space_name(sp));
-		}
-		return -1;
-	}
-	*replaced_data = dup_data;
-	return 0;
-}
-
-/**
- * Rollback the sequence of memtx_tree_index_replace_multikey_one
- * insertions with multikey indexes [0, err_multikey_idx - 1]
- * where the err_multikey_idx is the first multikey index where
- * error has been raised.
- *
- * This routine can't fail because all replaced_tuple (when
- * specified) nodes in tree are already allocated (they might be
- * overridden with new_tuple, but they definitely present) and
- * delete operation is fault-tolerant.
- */
-static void
-memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index *index,
-			struct tuple *new_tuple, struct tuple *replaced_tuple,
-			int err_multikey_idx)
-{
-	struct memtx_tree_data data;
-	if (replaced_tuple != NULL) {
-		/* Restore replaced tuple index occurrences. */
-		struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-		data.tuple = replaced_tuple;
-		uint32_t multikey_count =
-			tuple_multikey_count(replaced_tuple, cmp_def);
-		for (int i = 0; (uint32_t) i < multikey_count; i++) {
-			data.hint = i;
-			memtx_tree_insert(&index->tree, data, NULL);
-		}
-	}
-	/*
-	 * Rollback new_tuple insertion by multikey index
-	 * [0, multikey_idx).
-	 */
-	data.tuple = new_tuple;
-	for (int i = 0; i < err_multikey_idx; i++) {
-		data.hint = i;
-		memtx_tree_delete_value(&index->tree, data, NULL);
-	}
-}
-
-/**
- * :replace() function for a multikey index: replace old tuple
- * index entries with ones from the new tuple.
- *
- * In a multikey index a single tuple is associated with 0..N keys
- * of the b+*tree. Imagine old tuple key set is called "old_keys"
- * and a new tuple set is called "new_keys". This function must
- * 1) delete all removed keys: (new_keys - old_keys)
- * 2) update tuple pointer in all preserved keys: (old_keys ^ new_keys)
- * 3) insert data for all new keys (new_keys - old_keys).
- *
- * Compare with a standard unique or non-unique index, when a key
- * is present only once, so whenever we encounter a duplicate, it
- * is guaranteed to point at the old tuple (in non-unique indexes
- * we augment the secondary key parts with primary key parts, so
- * b+*tree still contains unique entries only).
- *
- * To reduce the number of insert and delete operations on the
- * tree, this function attempts to optimistically add all keys
- * from the new tuple to the tree first.
- *
- * When this step finds a duplicate, it's either of the following:
- * - for a unique multikey index, it may be the old tuple or
- *   some other tuple. Since unique index forbids duplicates,
- *   this branch ends with an error unless we found the old tuple.
- * - for a non-unique multikey index, both secondary and primary
- *   key parts must match, so it's guaranteed to be the old tuple.
- *
- * In other words, when an optimistic insert finds a duplicate,
- * it's either an error, in which case we roll back all the new
- * keys from the tree and abort the procedure, or the old tuple,
- * which we save to get back to, later.
- *
- * When adding new keys finishes, we have completed steps
- * 2) and 3):
- * - added set (new_keys - old_keys) to the index
- * - updated set (new_keys ^ old_keys) with a new tuple pointer.
- *
- * We now must perform 1), which is remove (old_keys - new_keys).
- *
- * This is done by using the old tuple pointer saved from the
- * previous step. To not accidentally delete the common key
- * set of the old and the new tuple, we don't using key parts alone
- * to compare - we also look at b+* tree value that has the tuple
- * pointer, and delete old tuple entries only.
- */
-static int
-memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
-			struct tuple *new_tuple, enum dup_replace_mode mode,
-			struct tuple **result)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	*result = NULL;
-	if (new_tuple != NULL) {
-		int multikey_idx = 0, err = 0;
-		uint32_t multikey_count =
-			tuple_multikey_count(new_tuple, cmp_def);
-		for (; (uint32_t) multikey_idx < multikey_count;
-		     multikey_idx++) {
-			bool is_multikey_conflict;
-			struct memtx_tree_data replaced_data;
-			err = memtx_tree_index_replace_multikey_one(index,
-						old_tuple, new_tuple, mode,
-						multikey_idx, &replaced_data,
-						&is_multikey_conflict);
-			if (err != 0)
-				break;
-			if (replaced_data.tuple != NULL &&
-			    !is_multikey_conflict) {
-				assert(*result == NULL ||
-				       *result == replaced_data.tuple);
-				*result = replaced_data.tuple;
-			}
-		}
-		if (err != 0) {
-			memtx_tree_index_replace_multikey_rollback(index,
-					new_tuple, *result, multikey_idx);
-			return -1;
-		}
-		if (*result != NULL) {
-			assert(old_tuple == NULL || old_tuple == *result);
-			old_tuple = *result;
-		}
-	}
-	if (old_tuple != NULL) {
-		struct memtx_tree_data data;
-		data.tuple = old_tuple;
-		uint32_t multikey_count =
-			tuple_multikey_count(old_tuple, cmp_def);
-		for (int i = 0; (uint32_t) i < multikey_count; i++) {
-			data.hint = i;
-			memtx_tree_delete_value(&index->tree, data, NULL);
-		}
-	}
-	return 0;
-}
-
-/** A dummy key allocator used when removing tuples from an index. */
-static const char *
-func_index_key_dummy_alloc(struct tuple *tuple, const char *key,
-			   uint32_t key_sz)
-{
-	(void) tuple;
-	(void) key_sz;
-	return (void*) key;
-}
-
-/**
- * An undo entry for multikey functional index replace operation.
- * Used to roll back a failed insert/replace and restore the
- * original key_hint(s) and to commit a completed insert/replace
- * and destruct old tuple key_hint(s).
-*/
-struct func_key_undo {
-	/** A link to organize entries in list. */
-	struct rlist link;
-	/** An inserted record copy. */
-	struct memtx_tree_data key;
-};
-
-/** Allocate a new func_key_undo on given region. */
-struct func_key_undo *
-func_key_undo_new(struct region *region)
-{
-	size_t size;
-	struct func_key_undo *undo = region_alloc_object(region, typeof(*undo),
-							 &size);
-	if (undo == NULL) {
-		diag_set(OutOfMemory, size, "region_alloc_object", "undo");
-		return NULL;
-	}
-	return undo;
-}
-
-/**
- * Rollback a sequence of memtx_tree_index_replace_multikey_one
- * insertions for functional index. Routine uses given list to
- * return a given index object in it's original state.
- */
-static void
-memtx_tree_func_index_replace_rollback(struct memtx_tree_index *index,
-				       struct rlist *old_keys,
-				       struct rlist *new_keys)
-{
-	struct func_key_undo *entry;
-	rlist_foreach_entry(entry, new_keys, link) {
-		memtx_tree_delete_value(&index->tree, entry->key, NULL);
-		tuple_chunk_delete(entry->key.tuple,
-				   (const char *)entry->key.hint);
-	}
-	rlist_foreach_entry(entry, old_keys, link)
-		memtx_tree_insert(&index->tree, entry->key, NULL);
-}
-
-/**
- * @sa memtx_tree_index_replace_multikey().
- * Use the functional index function from the key definition
- * to build a key list. Then each returned key is reallocated in
- * engine's memory as key_hint object and is used as comparison
- * hint.
- * To release key_hint memory in case of replace failure
- * we use a list of undo records which are allocated on region.
- * It is used to restore the original b+* entries with their
- * original key_hint(s) pointers in case of failure and release
- * the now useless hints of old items in case of success.
- */
-static int
-memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
-			struct tuple *new_tuple, enum dup_replace_mode mode,
-			struct tuple **result)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct index_def *index_def = index->base.def;
-	assert(index_def->key_def->for_func_index);
-
-	int rc = -1;
-	struct region *region = &fiber()->gc;
-	size_t region_svp = region_used(region);
-
-	*result = NULL;
-	struct key_list_iterator it;
-	if (new_tuple != NULL) {
-		struct rlist old_keys, new_keys;
-		rlist_create(&old_keys);
-		rlist_create(&new_keys);
-		if (key_list_iterator_create(&it, new_tuple, index_def, true,
-					     tuple_chunk_new) != 0)
-			goto end;
-		int err = 0;
-		const char *key;
-		struct func_key_undo *undo;
-		while ((err = key_list_iterator_next(&it, &key)) == 0 &&
-			key != NULL) {
-			/* Perform insertion, log it in list. */
-			undo = func_key_undo_new(region);
-			if (undo == NULL) {
-				tuple_chunk_delete(new_tuple, key);
-				err = -1;
-				break;
-			}
-			undo->key.tuple = new_tuple;
-			undo->key.hint = (hint_t)key;
-			rlist_add(&new_keys, &undo->link);
-			bool is_multikey_conflict;
-			struct memtx_tree_data old_data;
-			old_data.tuple = NULL;
-			err = memtx_tree_index_replace_multikey_one(index,
-						old_tuple, new_tuple,
-						mode, (hint_t)key, &old_data,
-						&is_multikey_conflict);
-			if (err != 0)
-				break;
-			if (old_data.tuple != NULL && !is_multikey_conflict) {
-				undo = func_key_undo_new(region);
-				if (undo == NULL) {
-					/*
-					 * Can't append this
-					 * operation in rollback
-					 * journal. Roll it back
-					 * manually.
-					 */
-					memtx_tree_insert(&index->tree,
-							  old_data, NULL);
-					err = -1;
-					break;
-				}
-				undo->key = old_data;
-				rlist_add(&old_keys, &undo->link);
-				*result = old_data.tuple;
-			} else if (old_data.tuple != NULL &&
-				   is_multikey_conflict) {
-				/*
-				 * Remove the replaced tuple undo
-				 * from undo list.
-				 */
-				tuple_chunk_delete(new_tuple,
-						(const char *)old_data.hint);
-				rlist_foreach_entry(undo, &new_keys, link) {
-					if (undo->key.hint == old_data.hint) {
-						rlist_del(&undo->link);
-						break;
-					}
-				}
-			}
-		}
-		if (key != NULL || err != 0) {
-			memtx_tree_func_index_replace_rollback(index,
-						&old_keys, &new_keys);
-			goto end;
-		}
-		if (*result != NULL) {
-			assert(old_tuple == NULL || old_tuple == *result);
-			old_tuple = *result;
-		}
-		/*
-		 * Commit changes: release hints for
-		 * replaced entries.
-		 */
-		rlist_foreach_entry(undo, &old_keys, link) {
-			tuple_chunk_delete(undo->key.tuple,
-					   (const char *)undo->key.hint);
-		}
-	}
-	if (old_tuple != NULL) {
-		if (key_list_iterator_create(&it, old_tuple, index_def, false,
-					     func_index_key_dummy_alloc) != 0)
-			goto end;
-		struct memtx_tree_data data, deleted_data;
-		data.tuple = old_tuple;
-		const char *key;
-		while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
-			data.hint = (hint_t) key;
-			deleted_data.tuple = NULL;
-			memtx_tree_delete_value(&index->tree, data,
-						&deleted_data);
-			if (deleted_data.tuple != NULL) {
-				/*
-				 * Release related hint on
-				 * successful node deletion.
-				 */
-				tuple_chunk_delete(deleted_data.tuple,
-					(const char *)deleted_data.hint);
-			}
-		}
-		assert(key == NULL);
-	}
-	rc = 0;
-end:
-	region_truncate(region, region_svp);
-	return rc;
-}
-
-static struct iterator *
-memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
-				 const char *key, uint32_t part_count)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-
-	assert(part_count == 0 || key != NULL);
-	if (type > ITER_GT) {
-		diag_set(UnsupportedIndexFeature, base->def,
-			 "requested iterator type");
-		return NULL;
-	}
-
-	if (part_count == 0) {
-		/*
-		 * If no key is specified, downgrade equality
-		 * iterators to a full range.
-		 */
-		type = iterator_type_is_reverse(type) ? ITER_LE : ITER_GE;
-		key = NULL;
-	}
-
-	struct tree_iterator *it = mempool_alloc(&memtx->iterator_pool);
-	if (it == NULL) {
-		diag_set(OutOfMemory, sizeof(struct tree_iterator),
-			 "memtx_tree_index", "iterator");
-		return NULL;
-	}
-	iterator_create(&it->base, base);
-	it->pool = &memtx->iterator_pool;
-	it->base.next = tree_iterator_start;
-	it->base.free = tree_iterator_free;
-	it->type = type;
-	it->key_data.key = key;
-	it->key_data.part_count = part_count;
-	it->key_data.hint = key_hint(key, part_count, cmp_def);
-	it->tree_iterator = memtx_tree_invalid_iterator();
-	it->current.tuple = NULL;
-	return (struct iterator *)it;
-}
-
-static void
-memtx_tree_index_begin_build(struct index *base)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	assert(memtx_tree_size(&index->tree) == 0);
-	(void)index;
-}
-
-static int
-memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	if (size_hint < index->build_array_alloc_size)
-		return 0;
-	struct memtx_tree_data *tmp =
-		realloc(index->build_array, size_hint * sizeof(*tmp));
-	if (tmp == NULL) {
-		diag_set(OutOfMemory, size_hint * sizeof(*tmp),
-			 "memtx_tree_index", "reserve");
-		return -1;
-	}
-	index->build_array = tmp;
-	index->build_array_alloc_size = size_hint;
-	return 0;
-}
-
-/** Initialize the next element of the index build_array. */
-static int
-memtx_tree_index_build_array_append(struct memtx_tree_index *index,
-				    struct tuple *tuple, hint_t hint)
-{
-	if (index->build_array == NULL) {
-		index->build_array = malloc(MEMTX_EXTENT_SIZE);
-		if (index->build_array == NULL) {
-			diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
-				 "memtx_tree_index", "build_next");
-			return -1;
-		}
-		index->build_array_alloc_size =
-			MEMTX_EXTENT_SIZE / sizeof(index->build_array[0]);
-	}
-	assert(index->build_array_size <= index->build_array_alloc_size);
-	if (index->build_array_size == index->build_array_alloc_size) {
-		index->build_array_alloc_size = index->build_array_alloc_size +
-				DIV_ROUND_UP(index->build_array_alloc_size, 2);
-		struct memtx_tree_data *tmp =
-			realloc(index->build_array,
-				index->build_array_alloc_size * sizeof(*tmp));
-		if (tmp == NULL) {
-			diag_set(OutOfMemory, index->build_array_alloc_size *
-				 sizeof(*tmp), "memtx_tree_index", "build_next");
-			return -1;
-		}
-		index->build_array = tmp;
-	}
-	struct memtx_tree_data *elem =
-		&index->build_array[index->build_array_size++];
-	elem->tuple = tuple;
-	elem->hint = hint;
-	return 0;
-}
-
-static int
-memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	return memtx_tree_index_build_array_append(index, tuple,
-						   tuple_hint(tuple, cmp_def));
-}
-
-static int
-memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);
-	for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;
-	     multikey_idx++) {
-		if (memtx_tree_index_build_array_append(index, tuple,
-							multikey_idx) != 0)
-			return -1;
-	}
-	return 0;
-}
-
-static int
-memtx_tree_func_index_build_next(struct index *base, struct tuple *tuple)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct index_def *index_def = index->base.def;
-	assert(index_def->key_def->for_func_index);
-
-	struct region *region = &fiber()->gc;
-	size_t region_svp = region_used(region);
-
-	struct key_list_iterator it;
-	if (key_list_iterator_create(&it, tuple, index_def, false,
-				     tuple_chunk_new) != 0)
-		return -1;
-
-	const char *key;
-	uint32_t insert_idx = index->build_array_size;
-	while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
-		if (memtx_tree_index_build_array_append(index, tuple,
-							(hint_t)key) != 0)
-			goto error;
-	}
-	assert(key == NULL);
-	region_truncate(region, region_svp);
-	return 0;
-error:
-	for (uint32_t i = insert_idx; i < index->build_array_size; i++) {
-		tuple_chunk_delete(index->build_array[i].tuple,
-				   (const char *)index->build_array[i].hint);
-	}
-	region_truncate(region, region_svp);
-	return -1;
-}
-
-/**
- * Process build_array of specified index and remove duplicates
- * of equal tuples (in terms of index's cmp_def and have same
- * tuple pointer). The build_array is expected to be sorted.
- */
-static void
-memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
-			void (*destroy)(struct tuple *tuple, const char *hint))
-{
-	if (index->build_array_size == 0)
-		return;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	size_t w_idx = 0, r_idx = 1;
-	while (r_idx < index->build_array_size) {
-		if (index->build_array[w_idx].tuple !=
-		    index->build_array[r_idx].tuple ||
-		    tuple_compare(index->build_array[w_idx].tuple,
-				  index->build_array[w_idx].hint,
-				  index->build_array[r_idx].tuple,
-				  index->build_array[r_idx].hint,
-				  cmp_def) != 0) {
-			/* Do not override the element itself. */
-			if (++w_idx == r_idx)
-				continue;
-			SWAP(index->build_array[w_idx],
-			     index->build_array[r_idx]);
-		}
-		r_idx++;
-	}
-	if (destroy != NULL) {
-		/* Destroy deduplicated entries. */
-		for (r_idx = w_idx + 1;
-		     r_idx < index->build_array_size; r_idx++) {
-			destroy(index->build_array[r_idx].tuple,
-				(const char *)index->build_array[r_idx].hint);
-		}
-	}
-	index->build_array_size = w_idx + 1;
-}
-
-static void
-memtx_tree_index_end_build(struct index *base)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	qsort_arg(index->build_array, index->build_array_size,
-		  sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);
-	if (cmp_def->is_multikey) {
-		/*
-		 * Multikey index may have equal(in terms of
-		 * cmp_def) keys inserted by different multikey
-		 * offsets. We must deduplicate them because
-		 * the following memtx_tree_build assumes that
-		 * all keys are unique.
-		 */
-		memtx_tree_index_build_array_deduplicate(index, NULL);
-	} else if (cmp_def->for_func_index) {
-		memtx_tree_index_build_array_deduplicate(index,
-							 tuple_chunk_delete);
-	}
-	memtx_tree_build(&index->tree, index->build_array,
-			 index->build_array_size);
-
-	free(index->build_array);
-	index->build_array = NULL;
-	index->build_array_size = 0;
-	index->build_array_alloc_size = 0;
-}
-
-struct tree_snapshot_iterator {
-	struct snapshot_iterator base;
-	struct memtx_tree_index *index;
-	struct memtx_tree_iterator tree_iterator;
-	struct memtx_tx_snapshot_cleaner cleaner;
-};
-
-static void
-tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
-{
-	assert(iterator->free == tree_snapshot_iterator_free);
-	struct tree_snapshot_iterator *it =
-		(struct tree_snapshot_iterator *)iterator;
-	memtx_leave_delayed_free_mode((struct memtx_engine *)
-				      it->index->base.engine);
-	memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);
-	index_unref(&it->index->base);
-	memtx_tx_snapshot_cleaner_destroy(&it->cleaner);
-	free(iterator);
-}
-
-static int
-tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
-			    const char **data, uint32_t *size)
-{
-	assert(iterator->free == tree_snapshot_iterator_free);
-	struct tree_snapshot_iterator *it =
-		(struct tree_snapshot_iterator *)iterator;
-	struct memtx_tree *tree = &it->index->tree;
-
-	while (true) {
-		struct memtx_tree_data *res =
-			memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
-
-		if (res == NULL) {
-			*data = NULL;
-			return 0;
-		}
-
-		memtx_tree_iterator_next(tree, &it->tree_iterator);
-
-		struct tuple *tuple = res->tuple;
-		tuple = memtx_tx_snapshot_clarify(&it->cleaner, tuple);
-
-		if (tuple != NULL) {
-			*data = tuple_data_range(tuple, size);
-			return 0;
-		}
-	}
-
-	return 0;
-}
-
-/**
- * Create an ALL iterator with personal read view so further
- * index modifications will not affect the iteration results.
- * Must be destroyed by iterator->free after usage.
- */
-static struct snapshot_iterator *
-memtx_tree_index_create_snapshot_iterator(struct index *base)
-{
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct tree_snapshot_iterator *it =
-		(struct tree_snapshot_iterator *) calloc(1, sizeof(*it));
-	if (it == NULL) {
-		diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),
-			 "memtx_tree_index", "create_snapshot_iterator");
-		return NULL;
-	}
-
-	struct space *space = space_cache_find(base->def->space_id);
-	if (memtx_tx_snapshot_cleaner_create(&it->cleaner, space,
-					     "memtx_tree_index") != 0) {
-		free(it);
-		return NULL;
-	}
-
-	it->base.free = tree_snapshot_iterator_free;
-	it->base.next = tree_snapshot_iterator_next;
-	it->index = index;
-	index_ref(base);
-	it->tree_iterator = memtx_tree_iterator_first(&index->tree);
-	memtx_tree_iterator_freeze(&index->tree, &it->tree_iterator);
-	memtx_enter_delayed_free_mode((struct memtx_engine *)base->engine);
-	return (struct snapshot_iterator *) it;
-}
-
-static const struct index_vtab memtx_tree_index_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
-	/* .commit_create = */ generic_index_commit_create,
-	/* .abort_create = */ generic_index_abort_create,
-	/* .commit_modify = */ generic_index_commit_modify,
-	/* .commit_drop = */ generic_index_commit_drop,
-	/* .update_def = */ memtx_tree_index_update_def,
-	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
-	/* .def_change_requires_rebuild = */
-		memtx_index_def_change_requires_rebuild,
-	/* .size = */ memtx_tree_index_size,
-	/* .bsize = */ memtx_tree_index_bsize,
-	/* .min = */ generic_index_min,
-	/* .max = */ generic_index_max,
-	/* .random = */ memtx_tree_index_random,
-	/* .count = */ memtx_tree_index_count,
-	/* .get = */ memtx_tree_index_get,
-	/* .replace = */ memtx_tree_index_replace,
-	/* .create_iterator = */ memtx_tree_index_create_iterator,
-	/* .create_snapshot_iterator = */
-		memtx_tree_index_create_snapshot_iterator,
-	/* .stat = */ generic_index_stat,
-	/* .compact = */ generic_index_compact,
-	/* .reset_stat = */ generic_index_reset_stat,
-	/* .begin_build = */ memtx_tree_index_begin_build,
-	/* .reserve = */ memtx_tree_index_reserve,
-	/* .build_next = */ memtx_tree_index_build_next,
-	/* .end_build = */ memtx_tree_index_end_build,
-};
-
-static const struct index_vtab memtx_tree_index_multikey_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
-	/* .commit_create = */ generic_index_commit_create,
-	/* .abort_create = */ generic_index_abort_create,
-	/* .commit_modify = */ generic_index_commit_modify,
-	/* .commit_drop = */ generic_index_commit_drop,
-	/* .update_def = */ memtx_tree_index_update_def,
-	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
-	/* .def_change_requires_rebuild = */
-		memtx_index_def_change_requires_rebuild,
-	/* .size = */ memtx_tree_index_size,
-	/* .bsize = */ memtx_tree_index_bsize,
-	/* .min = */ generic_index_min,
-	/* .max = */ generic_index_max,
-	/* .random = */ memtx_tree_index_random,
-	/* .count = */ memtx_tree_index_count,
-	/* .get = */ memtx_tree_index_get,
-	/* .replace = */ memtx_tree_index_replace_multikey,
-	/* .create_iterator = */ memtx_tree_index_create_iterator,
-	/* .create_snapshot_iterator = */
-		memtx_tree_index_create_snapshot_iterator,
-	/* .stat = */ generic_index_stat,
-	/* .compact = */ generic_index_compact,
-	/* .reset_stat = */ generic_index_reset_stat,
-	/* .begin_build = */ memtx_tree_index_begin_build,
-	/* .reserve = */ memtx_tree_index_reserve,
-	/* .build_next = */ memtx_tree_index_build_next_multikey,
-	/* .end_build = */ memtx_tree_index_end_build,
-};
-
-static const struct index_vtab memtx_tree_func_index_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
-	/* .commit_create = */ generic_index_commit_create,
-	/* .abort_create = */ generic_index_abort_create,
-	/* .commit_modify = */ generic_index_commit_modify,
-	/* .commit_drop = */ generic_index_commit_drop,
-	/* .update_def = */ memtx_tree_index_update_def,
-	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
-	/* .def_change_requires_rebuild = */
-		memtx_index_def_change_requires_rebuild,
-	/* .size = */ memtx_tree_index_size,
-	/* .bsize = */ memtx_tree_index_bsize,
-	/* .min = */ generic_index_min,
-	/* .max = */ generic_index_max,
-	/* .random = */ memtx_tree_index_random,
-	/* .count = */ memtx_tree_index_count,
-	/* .get = */ memtx_tree_index_get,
-	/* .replace = */ memtx_tree_func_index_replace,
-	/* .create_iterator = */ memtx_tree_index_create_iterator,
-	/* .create_snapshot_iterator = */
-		memtx_tree_index_create_snapshot_iterator,
-	/* .stat = */ generic_index_stat,
-	/* .compact = */ generic_index_compact,
-	/* .reset_stat = */ generic_index_reset_stat,
-	/* .begin_build = */ memtx_tree_index_begin_build,
-	/* .reserve = */ memtx_tree_index_reserve,
-	/* .build_next = */ memtx_tree_func_index_build_next,
-	/* .end_build = */ memtx_tree_index_end_build,
-};
-
-/**
- * A disabled index vtab provides safe dummy methods for
- * 'inactive' index. It is required to perform a fault-tolerant
- * recovery from snapshoot in case of func_index (because
- * key defintion is not completely initialized at that moment).
- */
-static const struct index_vtab memtx_tree_disabled_index_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
-	/* .commit_create = */ generic_index_commit_create,
-	/* .abort_create = */ generic_index_abort_create,
-	/* .commit_modify = */ generic_index_commit_modify,
-	/* .commit_drop = */ generic_index_commit_drop,
-	/* .update_def = */ generic_index_update_def,
-	/* .depends_on_pk = */ generic_index_depends_on_pk,
-	/* .def_change_requires_rebuild = */
-		generic_index_def_change_requires_rebuild,
-	/* .size = */ generic_index_size,
-	/* .bsize = */ generic_index_bsize,
-	/* .min = */ generic_index_min,
-	/* .max = */ generic_index_max,
-	/* .random = */ generic_index_random,
-	/* .count = */ generic_index_count,
-	/* .get = */ generic_index_get,
-	/* .replace = */ disabled_index_replace,
-	/* .create_iterator = */ generic_index_create_iterator,
-	/* .create_snapshot_iterator = */
-		generic_index_create_snapshot_iterator,
-	/* .stat = */ generic_index_stat,
-	/* .compact = */ generic_index_compact,
-	/* .reset_stat = */ generic_index_reset_stat,
-	/* .begin_build = */ generic_index_begin_build,
-	/* .reserve = */ generic_index_reserve,
-	/* .build_next = */ disabled_index_build_next,
-	/* .end_build = */ generic_index_end_build,
-};
-
-struct index *
-memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
-{
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)calloc(1, sizeof(*index));
-	if (index == NULL) {
-		diag_set(OutOfMemory, sizeof(*index),
-			 "malloc", "struct memtx_tree_index");
-		return NULL;
-	}
-	const struct index_vtab *vtab;
-	if (def->key_def->for_func_index) {
-		if (def->key_def->func_index_func == NULL)
-			vtab = &memtx_tree_disabled_index_vtab;
-		else
-			vtab = &memtx_tree_func_index_vtab;
-	} else if (def->key_def->is_multikey) {
-		vtab = &memtx_tree_index_multikey_vtab;
-	} else {
-		vtab = &memtx_tree_index_vtab;
-	}
-	if (index_create(&index->base, (struct engine *)memtx,
-			 vtab, def) != 0) {
-		free(index);
-		return NULL;
-	}
-
-	/* See comment to memtx_tree_index_update_def(). */
-	struct key_def *cmp_def;
-	cmp_def = def->opts.is_unique && !def->key_def->is_nullable ?
-			index->base.def->key_def : index->base.def->cmp_def;
-
-	memtx_tree_create(&index->tree, cmp_def, memtx_index_extent_alloc,
-			  memtx_index_extent_free, memtx);
-	return &index->base;
-}
diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
new file mode 100644
index 0000000..d3b993b
--- /dev/null
+++ b/src/box/memtx_tree.cc
@@ -0,0 +1,1526 @@
+/*
+ * Copyright 2010-2016, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * 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 "memtx_tree.h"
+#include "memtx_engine.h"
+#include "space.h"
+#include "schema.h" /* space_by_id(), space_cache_find() */
+#include "errinj.h"
+#include "memory.h"
+#include "fiber.h"
+#include "key_list.h"
+#include "tuple.h"
+#include "txn.h"
+#include "memtx_tx.h"
+#include <third_party/qsort_arg.h>
+#include <small/mempool.h>
+
+/**
+ * Struct that is used as a key in BPS tree definition.
+ */
+struct memtx_tree_key_data {
+	/** Sequence of msgpacked search fields. */
+	const char *key;
+	/** Number of msgpacked search fields. */
+	uint32_t part_count;
+	/** Comparison hint, see tuple_hint(). */
+	hint_t hint;
+};
+
+/**
+ * Struct that is used as a elem in BPS tree definition.
+ */
+struct memtx_tree_data {
+	/* Tuple that this node is represents. */
+	struct tuple *tuple;
+	/** Comparison hint, see key_hint(). */
+	hint_t hint;
+};
+
+/**
+ * Test whether BPS tree elements are identical i.e. represent
+ * the same tuple at the same position in the tree.
+ * @param a - First BPS tree element to compare.
+ * @param b - Second BPS tree element to compare.
+ * @retval true - When elements a and b are identical.
+ * @retval false - Otherwise.
+ */
+static bool
+memtx_tree_data_is_equal(const struct memtx_tree_data *a,
+			 const struct memtx_tree_data *b)
+{
+	return a->tuple == b->tuple;
+}
+
+#define BPS_TREE_NAME memtx_tree
+#define BPS_TREE_BLOCK_SIZE (512)
+#define BPS_TREE_EXTENT_SIZE MEMTX_EXTENT_SIZE
+#define BPS_TREE_COMPARE(a, b, arg)\
+	tuple_compare((&a)->tuple, (&a)->hint, (&b)->tuple, (&b)->hint, arg)
+#define BPS_TREE_COMPARE_KEY(a, b, arg)\
+	tuple_compare_with_key((&a)->tuple, (&a)->hint, (b)->key,\
+			       (b)->part_count, (b)->hint, arg)
+#define BPS_TREE_IS_IDENTICAL(a, b) memtx_tree_data_is_equal(&a, &b)
+#define BPS_TREE_NO_DEBUG 1
+#define bps_tree_elem_t struct memtx_tree_data
+#define bps_tree_key_t struct memtx_tree_key_data *
+#define bps_tree_arg_t struct key_def *
+
+#include "salad/bps_tree.h"
+
+#undef BPS_TREE_NAME
+#undef BPS_TREE_BLOCK_SIZE
+#undef BPS_TREE_EXTENT_SIZE
+#undef BPS_TREE_COMPARE
+#undef BPS_TREE_COMPARE_KEY
+#undef BPS_TREE_IS_IDENTICAL
+#undef BPS_TREE_NO_DEBUG
+#undef bps_tree_elem_t
+#undef bps_tree_key_t
+#undef bps_tree_arg_t
+
+struct memtx_tree_index {
+	struct index base;
+	struct memtx_tree tree;
+	struct memtx_tree_data *build_array;
+	size_t build_array_size, build_array_alloc_size;
+	struct memtx_gc_task gc_task;
+	struct memtx_tree_iterator gc_iterator;
+};
+
+/* {{{ Utilities. *************************************************/
+
+static inline struct key_def *
+memtx_tree_cmp_def(struct memtx_tree *tree)
+{
+	return tree->arg;
+}
+
+static int
+memtx_tree_qcompare(const void* a, const void *b, void *c)
+{
+	const struct memtx_tree_data *data_a = (struct memtx_tree_data *)a;
+	const struct memtx_tree_data *data_b = (struct memtx_tree_data *)b;
+	struct key_def *key_def = (struct key_def *)c;
+	return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
+			     data_b->hint, key_def);
+}
+
+/* {{{ MemtxTree Iterators ****************************************/
+struct tree_iterator {
+	struct iterator base;
+	struct memtx_tree_iterator tree_iterator;
+	enum iterator_type type;
+	struct memtx_tree_key_data key_data;
+	struct memtx_tree_data current;
+	/** Memory pool the iterator was allocated from. */
+	struct mempool *pool;
+};
+
+static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,
+	      "sizeof(struct tree_iterator) must be less than or equal "
+	      "to MEMTX_ITERATOR_SIZE");
+
+static void
+tree_iterator_free(struct iterator *iterator);
+
+static inline struct tree_iterator *
+tree_iterator(struct iterator *it)
+{
+	assert(it->free == tree_iterator_free);
+	return (struct tree_iterator *) it;
+}
+
+static void
+tree_iterator_free(struct iterator *iterator)
+{
+	struct tree_iterator *it = tree_iterator(iterator);
+	struct tuple *tuple = it->current.tuple;
+	if (tuple != NULL)
+		tuple_unref(tuple);
+	mempool_free(it->pool, it);
+}
+
+static int
+tree_iterator_dummie(struct iterator *iterator, struct tuple **ret)
+{
+	(void)iterator;
+	*ret = NULL;
+	return 0;
+}
+
+static int
+tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
+{
+	struct memtx_tree_index *index =
+		(struct memtx_tree_index *)iterator->index;
+	struct tree_iterator *it = tree_iterator(iterator);
+	assert(it->current.tuple != NULL);
+	struct memtx_tree_data *check =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
+		it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
+								it->current, NULL);
+	} else {
+		memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
+	}
+	tuple_unref(it->current.tuple);
+	struct memtx_tree_data *res =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	if (res == NULL) {
+		iterator->next = tree_iterator_dummie;
+		it->current.tuple = NULL;
+		*ret = NULL;
+	} else {
+		*ret = res->tuple;
+		tuple_ref(*ret);
+		it->current = *res;
+	}
+	return 0;
+}
+
+static int
+tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
+{
+	struct memtx_tree_index *index =
+		(struct memtx_tree_index *)iterator->index;
+	struct tree_iterator *it = tree_iterator(iterator);
+	assert(it->current.tuple != NULL);
+	struct memtx_tree_data *check =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
+		it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
+								it->current, NULL);
+	}
+	memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
+	tuple_unref(it->current.tuple);
+	struct memtx_tree_data *res =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	if (!res) {
+		iterator->next = tree_iterator_dummie;
+		it->current.tuple = NULL;
+		*ret = NULL;
+	} else {
+		*ret = res->tuple;
+		tuple_ref(*ret);
+		it->current = *res;
+	}
+	return 0;
+}
+
+static int
+tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
+{
+	struct memtx_tree_index *index =
+		(struct memtx_tree_index *)iterator->index;
+	struct tree_iterator *it = tree_iterator(iterator);
+	assert(it->current.tuple != NULL);
+	struct memtx_tree_data *check =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
+		it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
+								it->current, NULL);
+	} else {
+		memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
+	}
+	tuple_unref(it->current.tuple);
+	struct memtx_tree_data *res =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	/* Use user key def to save a few loops. */
+	if (res == NULL ||
+	    tuple_compare_with_key(res->tuple, res->hint,
+				   it->key_data.key,
+				   it->key_data.part_count,
+				   it->key_data.hint,
+				   index->base.def->key_def) != 0) {
+		iterator->next = tree_iterator_dummie;
+		it->current.tuple = NULL;
+		*ret = NULL;
+	} else {
+		*ret = res->tuple;
+		tuple_ref(*ret);
+		it->current = *res;
+	}
+	return 0;
+}
+
+static int
+tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
+{
+	struct memtx_tree_index *index =
+		(struct memtx_tree_index *)iterator->index;
+	struct tree_iterator *it = tree_iterator(iterator);
+	assert(it->current.tuple != NULL);
+	struct memtx_tree_data *check =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
+		it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
+								it->current, NULL);
+	}
+	memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
+	tuple_unref(it->current.tuple);
+	struct memtx_tree_data *res =
+		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
+	/* Use user key def to save a few loops. */
+	if (res == NULL ||
+	    tuple_compare_with_key(res->tuple, res->hint,
+				   it->key_data.key,
+				   it->key_data.part_count,
+				   it->key_data.hint,
+				   index->base.def->key_def) != 0) {
+		iterator->next = tree_iterator_dummie;
+		it->current.tuple = NULL;
+		*ret = NULL;
+	} else {
+		*ret = res->tuple;
+		tuple_ref(*ret);
+		it->current = *res;
+	}
+	return 0;
+}
+
+#define WRAP_ITERATOR_METHOD(name)						\
+static int									\
+name(struct iterator *iterator, struct tuple **ret)				\
+{										\
+	struct memtx_tree *tree =						\
+		&((struct memtx_tree_index *)iterator->index)->tree;		\
+	struct tree_iterator *it = tree_iterator(iterator);			\
+	struct memtx_tree_iterator *ti = &it->tree_iterator;			\
+	uint32_t iid = iterator->index->def->iid;				\
+	bool is_multikey = iterator->index->def->key_def->is_multikey;		\
+	struct txn *txn = in_txn();						\
+	struct space *space = space_by_id(iterator->space_id);			\
+	bool is_rw = txn != NULL;						\
+	do {									\
+		int rc = name##_base(iterator, ret);				\
+		if (rc != 0 || *ret == NULL)					\
+			return rc;						\
+		uint32_t mk_index = 0;						\
+		if (is_multikey) {						\
+			struct memtx_tree_data *check =				\
+				memtx_tree_iterator_get_elem(tree, ti);		\
+			assert(check != NULL);					\
+			mk_index = check->hint;					\
+		}								\
+		*ret = memtx_tx_tuple_clarify(txn, space, *ret,			\
+					      iid, mk_index, is_rw);		\
+	} while (*ret == NULL);							\
+	tuple_unref(it->current.tuple);						\
+	it->current.tuple = *ret;						\
+	tuple_ref(it->current.tuple);						\
+	return 0;								\
+}										\
+struct forgot_to_add_semicolon
+
+WRAP_ITERATOR_METHOD(tree_iterator_next);
+WRAP_ITERATOR_METHOD(tree_iterator_prev);
+WRAP_ITERATOR_METHOD(tree_iterator_next_equal);
+WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);
+
+#undef WRAP_ITERATOR_METHOD
+
+static void
+tree_iterator_set_next_method(struct tree_iterator *it)
+{
+	assert(it->current.tuple != NULL);
+	switch (it->type) {
+	case ITER_EQ:
+		it->base.next = tree_iterator_next_equal;
+		break;
+	case ITER_REQ:
+		it->base.next = tree_iterator_prev_equal;
+		break;
+	case ITER_ALL:
+		it->base.next = tree_iterator_next;
+		break;
+	case ITER_LT:
+	case ITER_LE:
+		it->base.next = tree_iterator_prev;
+		break;
+	case ITER_GE:
+	case ITER_GT:
+		it->base.next = tree_iterator_next;
+		break;
+	default:
+		/* The type was checked in initIterator */
+		assert(false);
+	}
+}
+
+static int
+tree_iterator_start(struct iterator *iterator, struct tuple **ret)
+{
+	*ret = NULL;
+	struct memtx_tree_index *index =
+		(struct memtx_tree_index *)iterator->index;
+	struct tree_iterator *it = tree_iterator(iterator);
+	it->base.next = tree_iterator_dummie;
+	struct memtx_tree *tree = &index->tree;
+	enum iterator_type type = it->type;
+	bool exact = false;
+	assert(it->current.tuple == NULL);
+	if (it->key_data.key == 0) {
+		if (iterator_type_is_reverse(it->type))
+			it->tree_iterator = memtx_tree_iterator_last(tree);
+		else
+			it->tree_iterator = memtx_tree_iterator_first(tree);
+	} else {
+		if (type == ITER_ALL || type == ITER_EQ ||
+		    type == ITER_GE || type == ITER_LT) {
+			it->tree_iterator =
+				memtx_tree_lower_bound(tree, &it->key_data,
+						       &exact);
+			if (type == ITER_EQ && !exact)
+				return 0;
+		} else { // ITER_GT, ITER_REQ, ITER_LE
+			it->tree_iterator =
+				memtx_tree_upper_bound(tree, &it->key_data,
+						       &exact);
+			if (type == ITER_REQ && !exact)
+				return 0;
+		}
+		if (iterator_type_is_reverse(type)) {
+			/*
+			 * Because of limitations of tree search API we use use
+			 * lower_bound for LT search and upper_bound for LE
+			 * and REQ searches. Thus we found position to the
+			 * right of the target one. Let's make a step to the
+			 * left to reach target position.
+			 * If we found an invalid iterator all the elements in
+			 * the tree are less (less or equal) to the key, and
+			 * iterator_next call will convert the iterator to the
+			 * last position in the tree, that's what we need.
+			 */
+			memtx_tree_iterator_prev(tree, &it->tree_iterator);
+		}
+	}
+
+	struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,
+							&it->tree_iterator);
+	if (!res)
+		return 0;
+	*ret = res->tuple;
+	tuple_ref(*ret);
+	it->current = *res;
+	tree_iterator_set_next_method(it);
+
+	uint32_t iid = iterator->index->def->iid;
+	bool is_multikey = iterator->index->def->key_def->is_multikey;
+	struct txn *txn = in_txn();
+	struct space *space = space_by_id(iterator->space_id);
+	bool is_rw = txn != NULL;
+	uint32_t mk_index = is_multikey ? res->hint : 0;
+	*ret = memtx_tx_tuple_clarify(txn, space, *ret, iid, mk_index, is_rw);
+	if (*ret == NULL) {
+		return iterator->next(iterator, ret);
+	} else {
+		tuple_unref(it->current.tuple);
+		it->current.tuple = *ret;
+		tuple_ref(it->current.tuple);
+	}
+
+	return 0;
+}
+
+/* }}} */
+
+/* {{{ MemtxTree  **********************************************************/
+
+static void
+memtx_tree_index_free(struct memtx_tree_index *index)
+{
+	memtx_tree_destroy(&index->tree);
+	free(index->build_array);
+	free(index);
+}
+
+static void
+memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
+{
+	/*
+	 * Yield every 1K tuples to keep latency < 0.1 ms.
+	 * Yield more often in debug mode.
+	 */
+#ifdef NDEBUG
+	enum { YIELD_LOOPS = 1000 };
+#else
+	enum { YIELD_LOOPS = 10 };
+#endif
+
+	struct memtx_tree_index *index = container_of(task,
+			struct memtx_tree_index, gc_task);
+	struct memtx_tree *tree = &index->tree;
+	struct memtx_tree_iterator *itr = &index->gc_iterator;
+
+	unsigned int loops = 0;
+	while (!memtx_tree_iterator_is_invalid(itr)) {
+		struct memtx_tree_data *res =
+			memtx_tree_iterator_get_elem(tree, itr);
+		memtx_tree_iterator_next(tree, itr);
+		tuple_unref(res->tuple);
+		if (++loops >= YIELD_LOOPS) {
+			*done = false;
+			return;
+		}
+	}
+	*done = true;
+}
+
+static void
+memtx_tree_index_gc_free(struct memtx_gc_task *task)
+{
+	struct memtx_tree_index *index = container_of(task,
+			struct memtx_tree_index, gc_task);
+	memtx_tree_index_free(index);
+}
+
+static const struct memtx_gc_task_vtab memtx_tree_index_gc_vtab = {
+	.run = memtx_tree_index_gc_run,
+	.free = memtx_tree_index_gc_free,
+};
+
+static void
+memtx_tree_index_destroy(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
+	if (base->def->iid == 0) {
+		/*
+		 * Primary index. We need to free all tuples stored
+		 * in the index, which may take a while. Schedule a
+		 * background task in order not to block tx thread.
+		 */
+		index->gc_task.vtab = &memtx_tree_index_gc_vtab;
+		index->gc_iterator = memtx_tree_iterator_first(&index->tree);
+		memtx_engine_schedule_gc(memtx, &index->gc_task);
+	} else {
+		/*
+		 * Secondary index. Destruction is fast, no need to
+		 * hand over to background fiber.
+		 */
+		memtx_tree_index_free(index);
+	}
+}
+
+static void
+memtx_tree_index_update_def(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct index_def *def = base->def;
+	/*
+	 * We use extended key def for non-unique and nullable
+	 * indexes. Unique but nullable index can store multiple
+	 * NULLs. To correctly compare these NULLs extended key
+	 * def must be used. For details @sa tuple_compare.cc.
+	 */
+	index->tree.arg = def->opts.is_unique && !def->key_def->is_nullable ?
+						def->key_def : def->cmp_def;
+}
+
+static bool
+memtx_tree_index_depends_on_pk(struct index *base)
+{
+	struct index_def *def = base->def;
+	/* See comment to memtx_tree_index_update_def(). */
+	return !def->opts.is_unique || def->key_def->is_nullable;
+}
+
+static ssize_t
+memtx_tree_index_size(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	return memtx_tree_size(&index->tree);
+}
+
+static ssize_t
+memtx_tree_index_bsize(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	return memtx_tree_mem_used(&index->tree);
+}
+
+static int
+memtx_tree_index_random(struct index *base, uint32_t rnd, struct tuple **result)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_data *res = memtx_tree_random(&index->tree, rnd);
+	*result = res != NULL ? res->tuple : NULL;
+	return 0;
+}
+
+static ssize_t
+memtx_tree_index_count(struct index *base, enum iterator_type type,
+		       const char *key, uint32_t part_count)
+{
+	if (type == ITER_ALL)
+		return memtx_tree_index_size(base); /* optimization */
+	return generic_index_count(base, type, key, part_count);
+}
+
+static int
+memtx_tree_index_get(struct index *base, const char *key,
+		     uint32_t part_count, struct tuple **result)
+{
+	assert(base->def->opts.is_unique &&
+	       part_count == base->def->key_def->part_count);
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+	struct memtx_tree_key_data key_data;
+	key_data.key = key;
+	key_data.part_count = part_count;
+	key_data.hint = key_hint(key, part_count, cmp_def);
+	struct memtx_tree_data *res = memtx_tree_find(&index->tree, &key_data);
+	if (res == NULL) {
+		*result = NULL;
+		return 0;
+	}
+	struct txn *txn = in_txn();
+	struct space *space = space_by_id(base->def->space_id);
+	bool is_rw = txn != NULL;
+	uint32_t mk_index = base->def->key_def->is_multikey ? res->hint : 0;
+	*result = memtx_tx_tuple_clarify(txn, space, res->tuple, base->def->iid,
+					 mk_index, is_rw);
+	return 0;
+}
+
+static int
+memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
+			 struct tuple *new_tuple, enum dup_replace_mode mode,
+			 struct tuple **result)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+	if (new_tuple) {
+		struct memtx_tree_data new_data;
+		new_data.tuple = new_tuple;
+		new_data.hint = tuple_hint(new_tuple, cmp_def);
+		struct memtx_tree_data dup_data;
+		dup_data.tuple = NULL;
+
+		/* Try to optimistically replace the new_tuple. */
+		int tree_res = memtx_tree_insert(&index->tree, new_data,
+						 &dup_data);
+		if (tree_res) {
+			diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
+				 "memtx_tree_index", "replace");
+			return -1;
+		}
+
+		uint32_t errcode = replace_check_dup(old_tuple,
+						     dup_data.tuple, mode);
+		if (errcode) {
+			memtx_tree_delete(&index->tree, new_data);
+			if (dup_data.tuple != NULL)
+				memtx_tree_insert(&index->tree, dup_data, NULL);
+			struct space *sp = space_cache_find(base->def->space_id);
+			if (sp != NULL)
+				diag_set(ClientError, errcode, base->def->name,
+					 space_name(sp));
+			return -1;
+		}
+		if (dup_data.tuple != NULL) {
+			*result = dup_data.tuple;
+			return 0;
+		}
+	}
+	if (old_tuple) {
+		struct memtx_tree_data old_data;
+		old_data.tuple = old_tuple;
+		old_data.hint = tuple_hint(old_tuple, cmp_def);
+		memtx_tree_delete(&index->tree, old_data);
+	}
+	*result = old_tuple;
+	return 0;
+}
+
+/**
+ * Perform tuple insertion by given multikey index.
+ * In case of replacement, all old tuple entries are deleted
+ * by all it's multikey indexes.
+ */
+static int
+memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
+			struct tuple *old_tuple, struct tuple *new_tuple,
+			enum dup_replace_mode mode, hint_t hint,
+			struct memtx_tree_data *replaced_data,
+			bool *is_multikey_conflict)
+{
+	struct memtx_tree_data new_data, dup_data;
+	new_data.tuple = new_tuple;
+	new_data.hint = hint;
+	dup_data.tuple = NULL;
+	*is_multikey_conflict = false;
+	if (memtx_tree_insert(&index->tree, new_data, &dup_data) != 0) {
+		diag_set(OutOfMemory, MEMTX_EXTENT_SIZE, "memtx_tree_index",
+			 "replace");
+		return -1;
+	}
+	int errcode = 0;
+	if (dup_data.tuple == new_tuple) {
+		/*
+		 * When tuple contains the same key multiple
+		 * times, the previous key occurrence is pushed
+		 * out of the index.
+		 */
+		*is_multikey_conflict = true;
+	} else if ((errcode = replace_check_dup(old_tuple, dup_data.tuple,
+					        mode)) != 0) {
+		/* Rollback replace. */
+		memtx_tree_delete(&index->tree, new_data);
+		if (dup_data.tuple != NULL)
+			memtx_tree_insert(&index->tree, dup_data, NULL);
+		struct space *sp = space_cache_find(index->base.def->space_id);
+		if (sp != NULL) {
+			diag_set(ClientError, errcode, index->base.def->name,
+				 space_name(sp));
+		}
+		return -1;
+	}
+	*replaced_data = dup_data;
+	return 0;
+}
+
+/**
+ * Rollback the sequence of memtx_tree_index_replace_multikey_one
+ * insertions with multikey indexes [0, err_multikey_idx - 1]
+ * where the err_multikey_idx is the first multikey index where
+ * error has been raised.
+ *
+ * This routine can't fail because all replaced_tuple (when
+ * specified) nodes in tree are already allocated (they might be
+ * overridden with new_tuple, but they definitely present) and
+ * delete operation is fault-tolerant.
+ */
+static void
+memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index *index,
+			struct tuple *new_tuple, struct tuple *replaced_tuple,
+			int err_multikey_idx)
+{
+	struct memtx_tree_data data;
+	if (replaced_tuple != NULL) {
+		/* Restore replaced tuple index occurrences. */
+		struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+		data.tuple = replaced_tuple;
+		uint32_t multikey_count =
+			tuple_multikey_count(replaced_tuple, cmp_def);
+		for (int i = 0; (uint32_t) i < multikey_count; i++) {
+			data.hint = i;
+			memtx_tree_insert(&index->tree, data, NULL);
+		}
+	}
+	/*
+	 * Rollback new_tuple insertion by multikey index
+	 * [0, multikey_idx).
+	 */
+	data.tuple = new_tuple;
+	for (int i = 0; i < err_multikey_idx; i++) {
+		data.hint = i;
+		memtx_tree_delete_value(&index->tree, data, NULL);
+	}
+}
+
+/**
+ * :replace() function for a multikey index: replace old tuple
+ * index entries with ones from the new tuple.
+ *
+ * In a multikey index a single tuple is associated with 0..N keys
+ * of the b+*tree. Imagine old tuple key set is called "old_keys"
+ * and a new tuple set is called "new_keys". This function must
+ * 1) delete all removed keys: (new_keys - old_keys)
+ * 2) update tuple pointer in all preserved keys: (old_keys ^ new_keys)
+ * 3) insert data for all new keys (new_keys - old_keys).
+ *
+ * Compare with a standard unique or non-unique index, when a key
+ * is present only once, so whenever we encounter a duplicate, it
+ * is guaranteed to point at the old tuple (in non-unique indexes
+ * we augment the secondary key parts with primary key parts, so
+ * b+*tree still contains unique entries only).
+ *
+ * To reduce the number of insert and delete operations on the
+ * tree, this function attempts to optimistically add all keys
+ * from the new tuple to the tree first.
+ *
+ * When this step finds a duplicate, it's either of the following:
+ * - for a unique multikey index, it may be the old tuple or
+ *   some other tuple. Since unique index forbids duplicates,
+ *   this branch ends with an error unless we found the old tuple.
+ * - for a non-unique multikey index, both secondary and primary
+ *   key parts must match, so it's guaranteed to be the old tuple.
+ *
+ * In other words, when an optimistic insert finds a duplicate,
+ * it's either an error, in which case we roll back all the new
+ * keys from the tree and abort the procedure, or the old tuple,
+ * which we save to get back to, later.
+ *
+ * When adding new keys finishes, we have completed steps
+ * 2) and 3):
+ * - added set (new_keys - old_keys) to the index
+ * - updated set (new_keys ^ old_keys) with a new tuple pointer.
+ *
+ * We now must perform 1), which is remove (old_keys - new_keys).
+ *
+ * This is done by using the old tuple pointer saved from the
+ * previous step. To not accidentally delete the common key
+ * set of the old and the new tuple, we don't using key parts alone
+ * to compare - we also look at b+* tree value that has the tuple
+ * pointer, and delete old tuple entries only.
+ */
+static int
+memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
+			struct tuple *new_tuple, enum dup_replace_mode mode,
+			struct tuple **result)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+	*result = NULL;
+	if (new_tuple != NULL) {
+		int multikey_idx = 0, err = 0;
+		uint32_t multikey_count =
+			tuple_multikey_count(new_tuple, cmp_def);
+		for (; (uint32_t) multikey_idx < multikey_count;
+		     multikey_idx++) {
+			bool is_multikey_conflict;
+			struct memtx_tree_data replaced_data;
+			err = memtx_tree_index_replace_multikey_one(index,
+						old_tuple, new_tuple, mode,
+						multikey_idx, &replaced_data,
+						&is_multikey_conflict);
+			if (err != 0)
+				break;
+			if (replaced_data.tuple != NULL &&
+			    !is_multikey_conflict) {
+				assert(*result == NULL ||
+				       *result == replaced_data.tuple);
+				*result = replaced_data.tuple;
+			}
+		}
+		if (err != 0) {
+			memtx_tree_index_replace_multikey_rollback(index,
+					new_tuple, *result, multikey_idx);
+			return -1;
+		}
+		if (*result != NULL) {
+			assert(old_tuple == NULL || old_tuple == *result);
+			old_tuple = *result;
+		}
+	}
+	if (old_tuple != NULL) {
+		struct memtx_tree_data data;
+		data.tuple = old_tuple;
+		uint32_t multikey_count =
+			tuple_multikey_count(old_tuple, cmp_def);
+		for (int i = 0; (uint32_t) i < multikey_count; i++) {
+			data.hint = i;
+			memtx_tree_delete_value(&index->tree, data, NULL);
+		}
+	}
+	return 0;
+}
+
+/** A dummy key allocator used when removing tuples from an index. */
+static const char *
+func_index_key_dummy_alloc(struct tuple *tuple, const char *key,
+			   uint32_t key_sz)
+{
+	(void) tuple;
+	(void) key_sz;
+	return key;
+}
+
+/**
+ * An undo entry for multikey functional index replace operation.
+ * Used to roll back a failed insert/replace and restore the
+ * original key_hint(s) and to commit a completed insert/replace
+ * and destruct old tuple key_hint(s).
+*/
+struct func_key_undo {
+	/** A link to organize entries in list. */
+	struct rlist link;
+	/** An inserted record copy. */
+	struct memtx_tree_data key;
+};
+
+/** Allocate a new func_key_undo on given region. */
+struct func_key_undo *
+func_key_undo_new(struct region *region)
+{
+	size_t size;
+	struct func_key_undo *undo = region_alloc_object(region, typeof(*undo),
+							 &size);
+	if (undo == NULL) {
+		diag_set(OutOfMemory, size, "region_alloc_object", "undo");
+		return NULL;
+	}
+	return undo;
+}
+
+/**
+ * Rollback a sequence of memtx_tree_index_replace_multikey_one
+ * insertions for functional index. Routine uses given list to
+ * return a given index object in it's original state.
+ */
+static void
+memtx_tree_func_index_replace_rollback(struct memtx_tree_index *index,
+				       struct rlist *old_keys,
+				       struct rlist *new_keys)
+{
+	struct func_key_undo *entry;
+	rlist_foreach_entry(entry, new_keys, link) {
+		memtx_tree_delete_value(&index->tree, entry->key, NULL);
+		tuple_chunk_delete(entry->key.tuple,
+				   (const char *)entry->key.hint);
+	}
+	rlist_foreach_entry(entry, old_keys, link)
+		memtx_tree_insert(&index->tree, entry->key, NULL);
+}
+
+/**
+ * @sa memtx_tree_index_replace_multikey().
+ * Use the functional index function from the key definition
+ * to build a key list. Then each returned key is reallocated in
+ * engine's memory as key_hint object and is used as comparison
+ * hint.
+ * To release key_hint memory in case of replace failure
+ * we use a list of undo records which are allocated on region.
+ * It is used to restore the original b+* entries with their
+ * original key_hint(s) pointers in case of failure and release
+ * the now useless hints of old items in case of success.
+ */
+static int
+memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
+			struct tuple *new_tuple, enum dup_replace_mode mode,
+			struct tuple **result)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct index_def *index_def = index->base.def;
+	assert(index_def->key_def->for_func_index);
+
+	int rc = -1;
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+
+	*result = NULL;
+	struct key_list_iterator it;
+	if (new_tuple != NULL) {
+		struct rlist old_keys, new_keys;
+		rlist_create(&old_keys);
+		rlist_create(&new_keys);
+		if (key_list_iterator_create(&it, new_tuple, index_def, true,
+					     tuple_chunk_new) != 0)
+			goto end;
+		int err = 0;
+		const char *key;
+		struct func_key_undo *undo;
+		while ((err = key_list_iterator_next(&it, &key)) == 0 &&
+			key != NULL) {
+			/* Perform insertion, log it in list. */
+			undo = func_key_undo_new(region);
+			if (undo == NULL) {
+				tuple_chunk_delete(new_tuple, key);
+				err = -1;
+				break;
+			}
+			undo->key.tuple = new_tuple;
+			undo->key.hint = (hint_t)key;
+			rlist_add(&new_keys, &undo->link);
+			bool is_multikey_conflict;
+			struct memtx_tree_data old_data;
+			old_data.tuple = NULL;
+			err = memtx_tree_index_replace_multikey_one(index,
+						old_tuple, new_tuple,
+						mode, (hint_t)key, &old_data,
+						&is_multikey_conflict);
+			if (err != 0)
+				break;
+			if (old_data.tuple != NULL && !is_multikey_conflict) {
+				undo = func_key_undo_new(region);
+				if (undo == NULL) {
+					/*
+					 * Can't append this
+					 * operation in rollback
+					 * journal. Roll it back
+					 * manually.
+					 */
+					memtx_tree_insert(&index->tree,
+							  old_data, NULL);
+					err = -1;
+					break;
+				}
+				undo->key = old_data;
+				rlist_add(&old_keys, &undo->link);
+				*result = old_data.tuple;
+			} else if (old_data.tuple != NULL &&
+				   is_multikey_conflict) {
+				/*
+				 * Remove the replaced tuple undo
+				 * from undo list.
+				 */
+				tuple_chunk_delete(new_tuple,
+						(const char *)old_data.hint);
+				rlist_foreach_entry(undo, &new_keys, link) {
+					if (undo->key.hint == old_data.hint) {
+						rlist_del(&undo->link);
+						break;
+					}
+				}
+			}
+		}
+		if (key != NULL || err != 0) {
+			memtx_tree_func_index_replace_rollback(index,
+						&old_keys, &new_keys);
+			goto end;
+		}
+		if (*result != NULL) {
+			assert(old_tuple == NULL || old_tuple == *result);
+			old_tuple = *result;
+		}
+		/*
+		 * Commit changes: release hints for
+		 * replaced entries.
+		 */
+		rlist_foreach_entry(undo, &old_keys, link) {
+			tuple_chunk_delete(undo->key.tuple,
+					   (const char *)undo->key.hint);
+		}
+	}
+	if (old_tuple != NULL) {
+		if (key_list_iterator_create(&it, old_tuple, index_def, false,
+					     func_index_key_dummy_alloc) != 0)
+			goto end;
+		struct memtx_tree_data data, deleted_data;
+		data.tuple = old_tuple;
+		const char *key;
+		while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
+			data.hint = (hint_t) key;
+			deleted_data.tuple = NULL;
+			memtx_tree_delete_value(&index->tree, data,
+						&deleted_data);
+			if (deleted_data.tuple != NULL) {
+				/*
+				 * Release related hint on
+				 * successful node deletion.
+				 */
+				tuple_chunk_delete(deleted_data.tuple,
+					(const char *)deleted_data.hint);
+			}
+		}
+		assert(key == NULL);
+	}
+	rc = 0;
+end:
+	region_truncate(region, region_svp);
+	return rc;
+}
+
+static struct iterator *
+memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
+				 const char *key, uint32_t part_count)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+
+	assert(part_count == 0 || key != NULL);
+	if (type > ITER_GT) {
+		diag_set(UnsupportedIndexFeature, base->def,
+			 "requested iterator type");
+		return NULL;
+	}
+
+	if (part_count == 0) {
+		/*
+		 * If no key is specified, downgrade equality
+		 * iterators to a full range.
+		 */
+		type = iterator_type_is_reverse(type) ? ITER_LE : ITER_GE;
+		key = NULL;
+	}
+
+	struct tree_iterator *it = (struct tree_iterator *)
+		mempool_alloc(&memtx->iterator_pool);
+	if (it == NULL) {
+		diag_set(OutOfMemory, sizeof(struct tree_iterator),
+			 "memtx_tree_index", "iterator");
+		return NULL;
+	}
+	iterator_create(&it->base, base);
+	it->pool = &memtx->iterator_pool;
+	it->base.next = tree_iterator_start;
+	it->base.free = tree_iterator_free;
+	it->type = type;
+	it->key_data.key = key;
+	it->key_data.part_count = part_count;
+	it->key_data.hint = key_hint(key, part_count, cmp_def);
+	it->tree_iterator = memtx_tree_invalid_iterator();
+	it->current.tuple = NULL;
+	return (struct iterator *)it;
+}
+
+static void
+memtx_tree_index_begin_build(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	assert(memtx_tree_size(&index->tree) == 0);
+	(void)index;
+}
+
+static int
+memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	if (size_hint < index->build_array_alloc_size)
+		return 0;
+	struct memtx_tree_data *tmp =
+		(struct memtx_tree_data *)
+			realloc(index->build_array, size_hint * sizeof(*tmp));
+	if (tmp == NULL) {
+		diag_set(OutOfMemory, size_hint * sizeof(*tmp),
+			 "memtx_tree_index", "reserve");
+		return -1;
+	}
+	index->build_array = tmp;
+	index->build_array_alloc_size = size_hint;
+	return 0;
+}
+
+/** Initialize the next element of the index build_array. */
+static int
+memtx_tree_index_build_array_append(struct memtx_tree_index *index,
+				    struct tuple *tuple, hint_t hint)
+{
+	if (index->build_array == NULL) {
+		index->build_array =
+			(struct memtx_tree_data *)malloc(MEMTX_EXTENT_SIZE);
+		if (index->build_array == NULL) {
+			diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
+				 "memtx_tree_index", "build_next");
+			return -1;
+		}
+		index->build_array_alloc_size =
+			MEMTX_EXTENT_SIZE / sizeof(index->build_array[0]);
+	}
+	assert(index->build_array_size <= index->build_array_alloc_size);
+	if (index->build_array_size == index->build_array_alloc_size) {
+		index->build_array_alloc_size = index->build_array_alloc_size +
+				DIV_ROUND_UP(index->build_array_alloc_size, 2);
+		struct memtx_tree_data *tmp =
+			(struct memtx_tree_data *)realloc(index->build_array,
+				index->build_array_alloc_size * sizeof(*tmp));
+		if (tmp == NULL) {
+			diag_set(OutOfMemory, index->build_array_alloc_size *
+				 sizeof(*tmp), "memtx_tree_index", "build_next");
+			return -1;
+		}
+		index->build_array = tmp;
+	}
+	struct memtx_tree_data *elem =
+		&index->build_array[index->build_array_size++];
+	elem->tuple = tuple;
+	elem->hint = hint;
+	return 0;
+}
+
+static int
+memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+	return memtx_tree_index_build_array_append(index, tuple,
+						   tuple_hint(tuple, cmp_def));
+}
+
+static int
+memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+	uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);
+	for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;
+	     multikey_idx++) {
+		if (memtx_tree_index_build_array_append(index, tuple,
+							multikey_idx) != 0)
+			return -1;
+	}
+	return 0;
+}
+
+static int
+memtx_tree_func_index_build_next(struct index *base, struct tuple *tuple)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct index_def *index_def = index->base.def;
+	assert(index_def->key_def->for_func_index);
+
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+
+	struct key_list_iterator it;
+	if (key_list_iterator_create(&it, tuple, index_def, false,
+				     tuple_chunk_new) != 0)
+		return -1;
+
+	const char *key;
+	uint32_t insert_idx = index->build_array_size;
+	while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
+		if (memtx_tree_index_build_array_append(index, tuple,
+							(hint_t)key) != 0)
+			goto error;
+	}
+	assert(key == NULL);
+	region_truncate(region, region_svp);
+	return 0;
+error:
+	for (uint32_t i = insert_idx; i < index->build_array_size; i++) {
+		tuple_chunk_delete(index->build_array[i].tuple,
+				   (const char *)index->build_array[i].hint);
+	}
+	region_truncate(region, region_svp);
+	return -1;
+}
+
+/**
+ * Process build_array of specified index and remove duplicates
+ * of equal tuples (in terms of index's cmp_def and have same
+ * tuple pointer). The build_array is expected to be sorted.
+ */
+static void
+memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
+			void (*destroy)(struct tuple *tuple, const char *hint))
+{
+	if (index->build_array_size == 0)
+		return;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+	size_t w_idx = 0, r_idx = 1;
+	while (r_idx < index->build_array_size) {
+		if (index->build_array[w_idx].tuple !=
+		    index->build_array[r_idx].tuple ||
+		    tuple_compare(index->build_array[w_idx].tuple,
+				  index->build_array[w_idx].hint,
+				  index->build_array[r_idx].tuple,
+				  index->build_array[r_idx].hint,
+				  cmp_def) != 0) {
+			/* Do not override the element itself. */
+			if (++w_idx == r_idx)
+				continue;
+			SWAP(index->build_array[w_idx],
+			     index->build_array[r_idx]);
+		}
+		r_idx++;
+	}
+	if (destroy != NULL) {
+		/* Destroy deduplicated entries. */
+		for (r_idx = w_idx + 1;
+		     r_idx < index->build_array_size; r_idx++) {
+			destroy(index->build_array[r_idx].tuple,
+				(const char *)index->build_array[r_idx].hint);
+		}
+	}
+	index->build_array_size = w_idx + 1;
+}
+
+static void
+memtx_tree_index_end_build(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
+	qsort_arg(index->build_array, index->build_array_size,
+		  sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);
+	if (cmp_def->is_multikey) {
+		/*
+		 * Multikey index may have equal(in terms of
+		 * cmp_def) keys inserted by different multikey
+		 * offsets. We must deduplicate them because
+		 * the following memtx_tree_build assumes that
+		 * all keys are unique.
+		 */
+		memtx_tree_index_build_array_deduplicate(index, NULL);
+	} else if (cmp_def->for_func_index) {
+		memtx_tree_index_build_array_deduplicate(index,
+							 tuple_chunk_delete);
+	}
+	memtx_tree_build(&index->tree, index->build_array,
+			 index->build_array_size);
+
+	free(index->build_array);
+	index->build_array = NULL;
+	index->build_array_size = 0;
+	index->build_array_alloc_size = 0;
+}
+
+struct tree_snapshot_iterator {
+	struct snapshot_iterator base;
+	struct memtx_tree_index *index;
+	struct memtx_tree_iterator tree_iterator;
+	struct memtx_tx_snapshot_cleaner cleaner;
+};
+
+static void
+tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
+{
+	assert(iterator->free == tree_snapshot_iterator_free);
+	struct tree_snapshot_iterator *it =
+		(struct tree_snapshot_iterator *)iterator;
+	memtx_leave_delayed_free_mode((struct memtx_engine *)
+				      it->index->base.engine);
+	memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);
+	index_unref(&it->index->base);
+	memtx_tx_snapshot_cleaner_destroy(&it->cleaner);
+	free(iterator);
+}
+
+static int
+tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
+			    const char **data, uint32_t *size)
+{
+	assert(iterator->free == tree_snapshot_iterator_free);
+	struct tree_snapshot_iterator *it =
+		(struct tree_snapshot_iterator *)iterator;
+	struct memtx_tree *tree = &it->index->tree;
+
+	while (true) {
+		struct memtx_tree_data *res =
+			memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
+
+		if (res == NULL) {
+			*data = NULL;
+			return 0;
+		}
+
+		memtx_tree_iterator_next(tree, &it->tree_iterator);
+
+		struct tuple *tuple = res->tuple;
+		tuple = memtx_tx_snapshot_clarify(&it->cleaner, tuple);
+
+		if (tuple != NULL) {
+			*data = tuple_data_range(tuple, size);
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * Create an ALL iterator with personal read view so further
+ * index modifications will not affect the iteration results.
+ * Must be destroyed by iterator->free after usage.
+ */
+static struct snapshot_iterator *
+memtx_tree_index_create_snapshot_iterator(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct tree_snapshot_iterator *it =
+		(struct tree_snapshot_iterator *) calloc(1, sizeof(*it));
+	if (it == NULL) {
+		diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),
+			 "memtx_tree_index", "create_snapshot_iterator");
+		return NULL;
+	}
+
+	struct space *space = space_cache_find(base->def->space_id);
+	if (memtx_tx_snapshot_cleaner_create(&it->cleaner, space,
+					     "memtx_tree_index") != 0) {
+		free(it);
+		return NULL;
+	}
+
+	it->base.free = tree_snapshot_iterator_free;
+	it->base.next = tree_snapshot_iterator_next;
+	it->index = index;
+	index_ref(base);
+	it->tree_iterator = memtx_tree_iterator_first(&index->tree);
+	memtx_tree_iterator_freeze(&index->tree, &it->tree_iterator);
+	memtx_enter_delayed_free_mode((struct memtx_engine *)base->engine);
+	return (struct snapshot_iterator *) it;
+}
+
+static const struct index_vtab memtx_tree_index_vtab = {
+	/* .destroy = */ memtx_tree_index_destroy,
+	/* .commit_create = */ generic_index_commit_create,
+	/* .abort_create = */ generic_index_abort_create,
+	/* .commit_modify = */ generic_index_commit_modify,
+	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ memtx_tree_index_update_def,
+	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
+	/* .def_change_requires_rebuild = */
+		memtx_index_def_change_requires_rebuild,
+	/* .size = */ memtx_tree_index_size,
+	/* .bsize = */ memtx_tree_index_bsize,
+	/* .min = */ generic_index_min,
+	/* .max = */ generic_index_max,
+	/* .random = */ memtx_tree_index_random,
+	/* .count = */ memtx_tree_index_count,
+	/* .get = */ memtx_tree_index_get,
+	/* .replace = */ memtx_tree_index_replace,
+	/* .create_iterator = */ memtx_tree_index_create_iterator,
+	/* .create_snapshot_iterator = */
+		memtx_tree_index_create_snapshot_iterator,
+	/* .stat = */ generic_index_stat,
+	/* .compact = */ generic_index_compact,
+	/* .reset_stat = */ generic_index_reset_stat,
+	/* .begin_build = */ memtx_tree_index_begin_build,
+	/* .reserve = */ memtx_tree_index_reserve,
+	/* .build_next = */ memtx_tree_index_build_next,
+	/* .end_build = */ memtx_tree_index_end_build,
+};
+
+static const struct index_vtab memtx_tree_index_multikey_vtab = {
+	/* .destroy = */ memtx_tree_index_destroy,
+	/* .commit_create = */ generic_index_commit_create,
+	/* .abort_create = */ generic_index_abort_create,
+	/* .commit_modify = */ generic_index_commit_modify,
+	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ memtx_tree_index_update_def,
+	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
+	/* .def_change_requires_rebuild = */
+		memtx_index_def_change_requires_rebuild,
+	/* .size = */ memtx_tree_index_size,
+	/* .bsize = */ memtx_tree_index_bsize,
+	/* .min = */ generic_index_min,
+	/* .max = */ generic_index_max,
+	/* .random = */ memtx_tree_index_random,
+	/* .count = */ memtx_tree_index_count,
+	/* .get = */ memtx_tree_index_get,
+	/* .replace = */ memtx_tree_index_replace_multikey,
+	/* .create_iterator = */ memtx_tree_index_create_iterator,
+	/* .create_snapshot_iterator = */
+		memtx_tree_index_create_snapshot_iterator,
+	/* .stat = */ generic_index_stat,
+	/* .compact = */ generic_index_compact,
+	/* .reset_stat = */ generic_index_reset_stat,
+	/* .begin_build = */ memtx_tree_index_begin_build,
+	/* .reserve = */ memtx_tree_index_reserve,
+	/* .build_next = */ memtx_tree_index_build_next_multikey,
+	/* .end_build = */ memtx_tree_index_end_build,
+};
+
+static const struct index_vtab memtx_tree_func_index_vtab = {
+	/* .destroy = */ memtx_tree_index_destroy,
+	/* .commit_create = */ generic_index_commit_create,
+	/* .abort_create = */ generic_index_abort_create,
+	/* .commit_modify = */ generic_index_commit_modify,
+	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ memtx_tree_index_update_def,
+	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
+	/* .def_change_requires_rebuild = */
+		memtx_index_def_change_requires_rebuild,
+	/* .size = */ memtx_tree_index_size,
+	/* .bsize = */ memtx_tree_index_bsize,
+	/* .min = */ generic_index_min,
+	/* .max = */ generic_index_max,
+	/* .random = */ memtx_tree_index_random,
+	/* .count = */ memtx_tree_index_count,
+	/* .get = */ memtx_tree_index_get,
+	/* .replace = */ memtx_tree_func_index_replace,
+	/* .create_iterator = */ memtx_tree_index_create_iterator,
+	/* .create_snapshot_iterator = */
+		memtx_tree_index_create_snapshot_iterator,
+	/* .stat = */ generic_index_stat,
+	/* .compact = */ generic_index_compact,
+	/* .reset_stat = */ generic_index_reset_stat,
+	/* .begin_build = */ memtx_tree_index_begin_build,
+	/* .reserve = */ memtx_tree_index_reserve,
+	/* .build_next = */ memtx_tree_func_index_build_next,
+	/* .end_build = */ memtx_tree_index_end_build,
+};
+
+/**
+ * A disabled index vtab provides safe dummy methods for
+ * 'inactive' index. It is required to perform a fault-tolerant
+ * recovery from snapshoot in case of func_index (because
+ * key defintion is not completely initialized at that moment).
+ */
+static const struct index_vtab memtx_tree_disabled_index_vtab = {
+	/* .destroy = */ memtx_tree_index_destroy,
+	/* .commit_create = */ generic_index_commit_create,
+	/* .abort_create = */ generic_index_abort_create,
+	/* .commit_modify = */ generic_index_commit_modify,
+	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ generic_index_update_def,
+	/* .depends_on_pk = */ generic_index_depends_on_pk,
+	/* .def_change_requires_rebuild = */
+		generic_index_def_change_requires_rebuild,
+	/* .size = */ generic_index_size,
+	/* .bsize = */ generic_index_bsize,
+	/* .min = */ generic_index_min,
+	/* .max = */ generic_index_max,
+	/* .random = */ generic_index_random,
+	/* .count = */ generic_index_count,
+	/* .get = */ generic_index_get,
+	/* .replace = */ disabled_index_replace,
+	/* .create_iterator = */ generic_index_create_iterator,
+	/* .create_snapshot_iterator = */
+		generic_index_create_snapshot_iterator,
+	/* .stat = */ generic_index_stat,
+	/* .compact = */ generic_index_compact,
+	/* .reset_stat = */ generic_index_reset_stat,
+	/* .begin_build = */ generic_index_begin_build,
+	/* .reserve = */ generic_index_reserve,
+	/* .build_next = */ disabled_index_build_next,
+	/* .end_build = */ generic_index_end_build,
+};
+
+struct index *
+memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
+{
+	struct memtx_tree_index *index =
+		(struct memtx_tree_index *)calloc(1, sizeof(*index));
+	if (index == NULL) {
+		diag_set(OutOfMemory, sizeof(*index),
+			 "malloc", "struct memtx_tree_index");
+		return NULL;
+	}
+	const struct index_vtab *vtab;
+	if (def->key_def->for_func_index) {
+		if (def->key_def->func_index_func == NULL)
+			vtab = &memtx_tree_disabled_index_vtab;
+		else
+			vtab = &memtx_tree_func_index_vtab;
+	} else if (def->key_def->is_multikey) {
+		vtab = &memtx_tree_index_multikey_vtab;
+	} else {
+		vtab = &memtx_tree_index_vtab;
+	}
+	if (index_create(&index->base, (struct engine *)memtx,
+			 vtab, def) != 0) {
+		free(index);
+		return NULL;
+	}
+
+	/* See comment to memtx_tree_index_update_def(). */
+	struct key_def *cmp_def;
+	cmp_def = def->opts.is_unique && !def->key_def->is_nullable ?
+			index->base.def->key_def : index->base.def->cmp_def;
+
+	memtx_tree_create(&index->tree, cmp_def, memtx_index_extent_alloc,
+			  memtx_index_extent_free, memtx);
+	return &index->base;
+}
-- 
2.7.4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional
  2020-10-19  9:51 [Tarantool-patches] [PATCH 0/2] Make tree hint optional Aleksandr Lyapunov
  2020-10-19  9:51 ` [Tarantool-patches] [PATCH 1/2] memtx: move memtx_tree.c to memtx_tree.cc Aleksandr Lyapunov
@ 2020-10-19  9:51 ` Aleksandr Lyapunov
  2020-10-20 12:34   ` Ilya Kosarev
  2020-10-20  1:01 ` [Tarantool-patches] [PATCH 0/2] Make tree hint optional Nikita Pettik
  2 siblings, 1 reply; 11+ messages in thread
From: Aleksandr Lyapunov @ 2020-10-19  9:51 UTC (permalink / raw)
  To: tarantool-patches, korablev @ tarantool . org,
	i . kosarev @ tarantool . org

Since 9fba29abb4e05babb9b23b4413bd8083f0fba933 (memtx: introduce tuple
compare hint) memtx tree key data (indexes) started to contain extra 8
bytes as a hint. Now it is optional and can be turned off in an index
options with "hint = false" entry.

Closes #4927

@TarantoolBot document
Title: memtx: optional tuple compare hints
Update the documentation for an index creation to reflect that there is
now an option to turn off hints for the index.
---
 src/box/index_def.c                 |   2 +
 src/box/index_def.h                 |   6 +
 src/box/lua/schema.lua              |  53 ++++
 src/box/lua/space.cc                |   7 +
 src/box/memtx_engine.c              |   2 +
 src/box/memtx_tree.cc               | 599 ++++++++++++++++++++++++------------
 src/lib/salad/bps_tree.h            |  19 ++
 test/box/alter.result               | 103 ++++++-
 test/box/alter.test.lua             |  34 ++
 test/box/errinj.result              |   3 +-
 test/box/tree_pk.result             | 314 +++++++++++++++++++
 test/box/tree_pk.test.lua           | 115 +++++++
 test/box/tree_pk_multipart.result   | 153 +++++++++
 test/box/tree_pk_multipart.test.lua |  64 ++++
 14 files changed, 1269 insertions(+), 205 deletions(-)

diff --git a/src/box/index_def.c b/src/box/index_def.c
index 9802961..79394b8 100644
--- a/src/box/index_def.c
+++ b/src/box/index_def.c
@@ -51,6 +51,7 @@ const struct index_opts index_opts_default = {
 	/* .lsn                 = */ 0,
 	/* .stat                = */ NULL,
 	/* .func                = */ 0,
+	/* .hint                = */ true,
 };
 
 const struct opt_def index_opts_reg[] = {
@@ -66,6 +67,7 @@ const struct opt_def index_opts_reg[] = {
 	OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn),
 	OPT_DEF("func", OPT_UINT32, struct index_opts, func_id),
 	OPT_DEF_LEGACY("sql"),
+	OPT_DEF("hint", OPT_BOOL, struct index_opts, hint),
 	OPT_END,
 };
 
diff --git a/src/box/index_def.h b/src/box/index_def.h
index d928b23..2180a69 100644
--- a/src/box/index_def.h
+++ b/src/box/index_def.h
@@ -165,6 +165,10 @@ struct index_opts {
 	struct index_stat *stat;
 	/** Identifier of the functional index function. */
 	uint32_t func_id;
+	/**
+	 * Use hint optimization for tree index.
+	 */
+	bool hint;
 };
 
 extern const struct index_opts index_opts_default;
@@ -211,6 +215,8 @@ index_opts_cmp(const struct index_opts *o1, const struct index_opts *o2)
 		return o1->bloom_fpr < o2->bloom_fpr ? -1 : 1;
 	if (o1->func_id != o2->func_id)
 		return o1->func_id - o2->func_id;
+	if (o1->hint != o2->hint)
+		return o1->hint - o2->hint;
 	return 0;
 }
 
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 1131af7..9cc1289 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1000,8 +1000,31 @@ local index_options = {
     page_size = 'number',
     bloom_fpr = 'number',
     func = 'number, string',
+    hint = 'boolean',
 }
 
+local function jsonpaths_from_idx_parts(parts)
+    local paths = {}
+
+    for _, part in pairs(parts) do
+        if type(part.path) == 'string' then
+            table.insert(paths, part.path)
+        end
+    end
+
+    return paths
+end
+
+local function is_multikey_index(parts)
+    for _, path in pairs(jsonpaths_from_idx_parts(parts)) do
+        if path:find('[*]', 1, true) then
+            return true
+        end
+    end
+
+    return false
+end
+
 --
 -- check_param_table() template for alter index,
 -- includes all index options.
@@ -1076,6 +1099,15 @@ box.schema.index.create = function(space_id, name, options)
         options_defaults = {}
     end
     options = update_param_table(options, options_defaults)
+    if options.hint and
+            (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
+        box.error(box.error.MODIFY_INDEX, name, space.name,
+                "hint is only reasonable with memtx tree index")
+    end
+    if options.hint and options.func then
+        box.error(box.error.MODIFY_INDEX, name, space.name,
+                "functional index can't use hints")
+    end
 
     local _index = box.space[box.schema.INDEX_ID]
     local _vindex = box.space[box.schema.VINDEX_ID]
@@ -1115,6 +1147,7 @@ box.schema.index.create = function(space_id, name, options)
             run_size_ratio = options.run_size_ratio,
             bloom_fpr = options.bloom_fpr,
             func = options.func,
+            hint = options.hint,
     }
     local field_type_aliases = {
         num = 'unsigned'; -- Deprecated since 1.7.2
@@ -1135,6 +1168,10 @@ box.schema.index.create = function(space_id, name, options)
     if parts_can_be_simplified then
         parts = simplify_index_parts(parts)
     end
+    if options.hint and is_multikey_index(parts) then
+        box.error(box.error.MODIFY_INDEX, name, space.name,
+                "multikey index can't use hints")
+    end
     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
         index_opts.func = func_id_by_name(index_opts.func)
     end
@@ -1253,6 +1290,17 @@ box.schema.index.alter = function(space_id, index_id, options)
             index_opts[k] = options[k]
         end
     end
+    if options.hint and
+       (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
+        box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
+                                          space.name,
+            "hint is only reasonable with memtx tree index")
+    end
+    if options.hint and options.func then
+        box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
+                                          space.name,
+                "functional index can't use hints")
+    end
     if options.parts then
         local parts_can_be_simplified
         parts, parts_can_be_simplified =
@@ -1262,6 +1310,11 @@ box.schema.index.alter = function(space_id, index_id, options)
             parts = simplify_index_parts(parts)
         end
     end
+    if options.hint and is_multikey_index(parts) then
+        box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
+                                          space.name,
+                "multikey index can't use hints")
+    end
     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
         index_opts.func = func_id_by_name(index_opts.func)
     end
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 177c588..1ea993c 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -344,6 +344,13 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 			lua_pushnumber(L, index_opts->dimension);
 			lua_setfield(L, -2, "dimension");
 		}
+		if (space_is_memtx(space) && index_def->type == TREE) {
+			lua_pushboolean(L, index_opts->hint);
+			lua_setfield(L, -2, "hint");
+		} else {
+			lua_pushnil(L);
+			lua_setfield(L, -2, "hint");
+		}
 
 		if (index_opts->func_id > 0) {
 			lua_pushstring(L, "func");
diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
index 8147557..43000ba 100644
--- a/src/box/memtx_engine.c
+++ b/src/box/memtx_engine.c
@@ -1398,6 +1398,8 @@ memtx_index_def_change_requires_rebuild(struct index *index,
 		return true;
 	if (old_def->opts.func_id != new_def->opts.func_id)
 		return true;
+	if (old_def->opts.hint != new_def->opts.hint)
+		return true;
 
 	const struct key_def *old_cmp_def, *new_cmp_def;
 	if (index_depends_on_pk(index)) {
diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
index d3b993b..17d58c5 100644
--- a/src/box/memtx_tree.cc
+++ b/src/box/memtx_tree.cc
@@ -45,23 +45,51 @@
 /**
  * Struct that is used as a key in BPS tree definition.
  */
-struct memtx_tree_key_data {
+struct memtx_tree_key_data_common {
 	/** Sequence of msgpacked search fields. */
 	const char *key;
 	/** Number of msgpacked search fields. */
 	uint32_t part_count;
+};
+
+template <int USE_HINT>
+struct memtx_tree_key_data;
+
+template <>
+struct memtx_tree_key_data<0> : memtx_tree_key_data_common {
+	static constexpr hint_t hint = HINT_NONE;
+	void set_hint(hint_t) { assert(false); }
+};
+
+template <>
+struct memtx_tree_key_data<1> : memtx_tree_key_data_common {
 	/** Comparison hint, see tuple_hint(). */
 	hint_t hint;
+	void set_hint(hint_t h) { hint = h; }
 };
 
 /**
  * Struct that is used as a elem in BPS tree definition.
  */
-struct memtx_tree_data {
+struct memtx_tree_data_common {
 	/* Tuple that this node is represents. */
 	struct tuple *tuple;
+};
+
+template <int USE_HINT>
+struct memtx_tree_data;
+
+template <>
+struct memtx_tree_data<0> : memtx_tree_data_common {
+	static constexpr hint_t hint = HINT_NONE;
+	void set_hint(hint_t) { assert(false); }
+};
+
+template <>
+struct memtx_tree_data<1> :  memtx_tree_data<0> {
 	/** Comparison hint, see key_hint(). */
 	hint_t hint;
+	void set_hint(hint_t h) { hint = h; }
 };
 
 /**
@@ -73,8 +101,8 @@ struct memtx_tree_data {
  * @retval false - Otherwise.
  */
 static bool
-memtx_tree_data_is_equal(const struct memtx_tree_data *a,
-			 const struct memtx_tree_data *b)
+memtx_tree_data_is_equal(const struct memtx_tree_data_common *a,
+			 const struct memtx_tree_data_common *b)
 {
 	return a->tuple == b->tuple;
 }
@@ -89,12 +117,28 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
 			       (b)->part_count, (b)->hint, arg)
 #define BPS_TREE_IS_IDENTICAL(a, b) memtx_tree_data_is_equal(&a, &b)
 #define BPS_TREE_NO_DEBUG 1
-#define bps_tree_elem_t struct memtx_tree_data
-#define bps_tree_key_t struct memtx_tree_key_data *
 #define bps_tree_arg_t struct key_def *
 
+#define BPS_TREE_NAMESPACE NS_NO_HINT
+#define bps_tree_elem_t struct memtx_tree_data<0>
+#define bps_tree_key_t struct memtx_tree_key_data<0> *
+
 #include "salad/bps_tree.h"
 
+#undef BPS_TREE_NAMESPACE
+#undef bps_tree_elem_t
+#undef bps_tree_key_t
+
+#define BPS_TREE_NAMESPACE NS_USE_HINT
+#define bps_tree_elem_t struct memtx_tree_data<1>
+#define bps_tree_key_t struct memtx_tree_key_data<1> *
+
+#include "salad/bps_tree.h"
+
+#undef BPS_TREE_NAMESPACE
+#undef bps_tree_elem_t
+#undef bps_tree_key_t
+
 #undef BPS_TREE_NAME
 #undef BPS_TREE_BLOCK_SIZE
 #undef BPS_TREE_EXTENT_SIZE
@@ -102,66 +146,119 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
 #undef BPS_TREE_COMPARE_KEY
 #undef BPS_TREE_IS_IDENTICAL
 #undef BPS_TREE_NO_DEBUG
-#undef bps_tree_elem_t
-#undef bps_tree_key_t
 #undef bps_tree_arg_t
 
+using namespace NS_NO_HINT;
+using namespace NS_USE_HINT;
+
+template <int USE_HINT>
+struct memtx_tree_selector;
+
+template <>
+struct memtx_tree_selector<0> : NS_NO_HINT::memtx_tree {};
+
+template <>
+struct memtx_tree_selector<1> : NS_USE_HINT::memtx_tree {};
+
+template <int USE_HINT>
+using memtx_tree_t = struct memtx_tree_selector<USE_HINT>;
+
+template <int USE_HINT>
+struct memtx_tree_iterator_selector;
+
+template <>
+struct memtx_tree_iterator_selector<0> {
+	using type = NS_NO_HINT::memtx_tree_iterator;
+};
+
+template <>
+struct memtx_tree_iterator_selector<1> {
+	using type = NS_USE_HINT::memtx_tree_iterator;
+};
+
+template <int USE_HINT>
+using memtx_tree_iterator_t = typename memtx_tree_iterator_selector<USE_HINT>::type;
+
+static void
+invalidate_tree_iterator(NS_NO_HINT::memtx_tree_iterator *itr)
+{
+	*itr = NS_NO_HINT::memtx_tree_invalid_iterator();
+}
+
+static void
+invalidate_tree_iterator(NS_USE_HINT::memtx_tree_iterator *itr)
+{
+	*itr = NS_USE_HINT::memtx_tree_invalid_iterator();
+}
+
+template <int USE_HINT>
 struct memtx_tree_index {
 	struct index base;
-	struct memtx_tree tree;
-	struct memtx_tree_data *build_array;
+	memtx_tree_t<USE_HINT> tree;
+	struct memtx_tree_data<USE_HINT> *build_array;
 	size_t build_array_size, build_array_alloc_size;
 	struct memtx_gc_task gc_task;
-	struct memtx_tree_iterator gc_iterator;
+	memtx_tree_iterator_t<USE_HINT> gc_iterator;
 };
 
 /* {{{ Utilities. *************************************************/
 
+template <class TREE>
 static inline struct key_def *
-memtx_tree_cmp_def(struct memtx_tree *tree)
+memtx_tree_cmp_def(TREE *tree)
 {
 	return tree->arg;
 }
 
+template <int USE_HINT>
 static int
 memtx_tree_qcompare(const void* a, const void *b, void *c)
 {
-	const struct memtx_tree_data *data_a = (struct memtx_tree_data *)a;
-	const struct memtx_tree_data *data_b = (struct memtx_tree_data *)b;
+	const struct memtx_tree_data<USE_HINT> *data_a =
+		(struct memtx_tree_data<USE_HINT> *)a;
+	const struct memtx_tree_data<USE_HINT> *data_b =
+		(struct memtx_tree_data<USE_HINT> *)b;
 	struct key_def *key_def = (struct key_def *)c;
 	return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
 			     data_b->hint, key_def);
 }
 
 /* {{{ MemtxTree Iterators ****************************************/
+template <int USE_HINT>
 struct tree_iterator {
 	struct iterator base;
-	struct memtx_tree_iterator tree_iterator;
+	memtx_tree_iterator_t<USE_HINT> tree_iterator;
 	enum iterator_type type;
-	struct memtx_tree_key_data key_data;
-	struct memtx_tree_data current;
+	struct memtx_tree_key_data<USE_HINT> key_data;
+	struct memtx_tree_data<USE_HINT> current;
 	/** Memory pool the iterator was allocated from. */
 	struct mempool *pool;
 };
 
-static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,
-	      "sizeof(struct tree_iterator) must be less than or equal "
+static_assert(sizeof(struct tree_iterator<0>) <= MEMTX_ITERATOR_SIZE,
+	      "sizeof(struct tree_iterator<0>) must be less than or equal "
+	      "to MEMTX_ITERATOR_SIZE");
+static_assert(sizeof(struct tree_iterator<1>) <= MEMTX_ITERATOR_SIZE,
+	      "sizeof(struct tree_iterator<1>) must be less than or equal "
 	      "to MEMTX_ITERATOR_SIZE");
 
+template <int USE_HINT>
 static void
 tree_iterator_free(struct iterator *iterator);
 
-static inline struct tree_iterator *
-tree_iterator(struct iterator *it)
+template <int USE_HINT>
+static inline struct tree_iterator<USE_HINT> *
+get_tree_iterator(struct iterator *it)
 {
-	assert(it->free == tree_iterator_free);
-	return (struct tree_iterator *) it;
+	assert(it->free == &tree_iterator_free<USE_HINT>);
+	return (struct tree_iterator<USE_HINT> *) it;
 }
 
+template <int USE_HINT>
 static void
 tree_iterator_free(struct iterator *iterator)
 {
-	struct tree_iterator *it = tree_iterator(iterator);
+	struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
 	struct tuple *tuple = it->current.tuple;
 	if (tuple != NULL)
 		tuple_unref(tuple);
@@ -176,14 +273,15 @@ tree_iterator_dummie(struct iterator *iterator, struct tuple **ret)
 	return 0;
 }
 
+template <int USE_HINT>
 static int
 tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
 {
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)iterator->index;
+	struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
 	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
+	struct memtx_tree_data<USE_HINT> *check =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
 		it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
@@ -192,7 +290,7 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
 		memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
 	}
 	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
+	struct memtx_tree_data<USE_HINT> *res =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	if (res == NULL) {
 		iterator->next = tree_iterator_dummie;
@@ -206,14 +304,15 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
 	return 0;
 }
 
+template <int USE_HINT>
 static int
 tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
 {
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)iterator->index;
+	struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
 	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
+	struct memtx_tree_data<USE_HINT> *check =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
 		it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
@@ -221,7 +320,7 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
 	}
 	memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
 	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
+	struct memtx_tree_data<USE_HINT> *res =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	if (!res) {
 		iterator->next = tree_iterator_dummie;
@@ -235,14 +334,15 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
 	return 0;
 }
 
+template <int USE_HINT>
 static int
 tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
 {
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)iterator->index;
+	struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
 	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
+	struct memtx_tree_data<USE_HINT> *check =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
 		it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
@@ -251,7 +351,7 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
 		memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
 	}
 	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
+	struct memtx_tree_data<USE_HINT> *res =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	/* Use user key def to save a few loops. */
 	if (res == NULL ||
@@ -271,14 +371,15 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
 	return 0;
 }
 
+template <int USE_HINT>
 static int
 tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
 {
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)iterator->index;
+	struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
 	assert(it->current.tuple != NULL);
-	struct memtx_tree_data *check =
+	struct memtx_tree_data<USE_HINT> *check =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
 		it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
@@ -286,7 +387,7 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
 	}
 	memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
 	tuple_unref(it->current.tuple);
-	struct memtx_tree_data *res =
+	struct memtx_tree_data<USE_HINT> *res =
 		memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
 	/* Use user key def to save a few loops. */
 	if (res == NULL ||
@@ -307,28 +408,30 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
 }
 
 #define WRAP_ITERATOR_METHOD(name)						\
+template <int USE_HINT>								\
 static int									\
 name(struct iterator *iterator, struct tuple **ret)				\
 {										\
-	struct memtx_tree *tree =						\
-		&((struct memtx_tree_index *)iterator->index)->tree;		\
-	struct tree_iterator *it = tree_iterator(iterator);			\
-	struct memtx_tree_iterator *ti = &it->tree_iterator;			\
+	memtx_tree_t<USE_HINT> *tree =						\
+		&((struct memtx_tree_index<USE_HINT> *)iterator->index)->tree;	\
+	struct tree_iterator<USE_HINT> *it =   					\
+		get_tree_iterator<USE_HINT>(iterator);				\
+	memtx_tree_iterator_t<USE_HINT> *ti = &it->tree_iterator;		\
 	uint32_t iid = iterator->index->def->iid;				\
 	bool is_multikey = iterator->index->def->key_def->is_multikey;		\
 	struct txn *txn = in_txn();						\
 	struct space *space = space_by_id(iterator->space_id);			\
 	bool is_rw = txn != NULL;						\
 	do {									\
-		int rc = name##_base(iterator, ret);				\
+		int rc = name##_base<USE_HINT>(iterator, ret);			\
 		if (rc != 0 || *ret == NULL)					\
 			return rc;						\
 		uint32_t mk_index = 0;						\
 		if (is_multikey) {						\
-			struct memtx_tree_data *check =				\
+			struct memtx_tree_data<USE_HINT> *check =		\
 				memtx_tree_iterator_get_elem(tree, ti);		\
 			assert(check != NULL);					\
-			mk_index = check->hint;					\
+			mk_index = (uint32_t)check->hint;			\
 		}								\
 		*ret = memtx_tx_tuple_clarify(txn, space, *ret,			\
 					      iid, mk_index, is_rw);		\
@@ -347,27 +450,28 @@ WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);
 
 #undef WRAP_ITERATOR_METHOD
 
+template <int USE_HINT>
 static void
-tree_iterator_set_next_method(struct tree_iterator *it)
+tree_iterator_set_next_method(struct tree_iterator<USE_HINT> *it)
 {
 	assert(it->current.tuple != NULL);
 	switch (it->type) {
 	case ITER_EQ:
-		it->base.next = tree_iterator_next_equal;
+		it->base.next = tree_iterator_next_equal<USE_HINT>;
 		break;
 	case ITER_REQ:
-		it->base.next = tree_iterator_prev_equal;
+		it->base.next = tree_iterator_prev_equal<USE_HINT>;
 		break;
 	case ITER_ALL:
-		it->base.next = tree_iterator_next;
+		it->base.next = tree_iterator_next<USE_HINT>;
 		break;
 	case ITER_LT:
 	case ITER_LE:
-		it->base.next = tree_iterator_prev;
+		it->base.next = tree_iterator_prev<USE_HINT>;
 		break;
 	case ITER_GE:
 	case ITER_GT:
-		it->base.next = tree_iterator_next;
+		it->base.next = tree_iterator_next<USE_HINT>;
 		break;
 	default:
 		/* The type was checked in initIterator */
@@ -375,15 +479,16 @@ tree_iterator_set_next_method(struct tree_iterator *it)
 	}
 }
 
+template <int USE_HINT>
 static int
 tree_iterator_start(struct iterator *iterator, struct tuple **ret)
 {
 	*ret = NULL;
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)iterator->index;
-	struct tree_iterator *it = tree_iterator(iterator);
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)iterator->index;
+	struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
 	it->base.next = tree_iterator_dummie;
-	struct memtx_tree *tree = &index->tree;
+	memtx_tree_t<USE_HINT> *tree = &index->tree;
 	enum iterator_type type = it->type;
 	bool exact = false;
 	assert(it->current.tuple == NULL);
@@ -423,8 +528,8 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
 		}
 	}
 
-	struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,
-							&it->tree_iterator);
+	struct memtx_tree_data<USE_HINT> *res =
+		memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
 	if (!res)
 		return 0;
 	*ret = res->tuple;
@@ -454,14 +559,16 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
 
 /* {{{ MemtxTree  **********************************************************/
 
+template <int USE_HINT>
 static void
-memtx_tree_index_free(struct memtx_tree_index *index)
+memtx_tree_index_free(struct memtx_tree_index<USE_HINT> *index)
 {
 	memtx_tree_destroy(&index->tree);
 	free(index->build_array);
 	free(index);
 }
 
+template <int USE_HINT>
 static void
 memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
 {
@@ -475,14 +582,14 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
 	enum { YIELD_LOOPS = 10 };
 #endif
 
-	struct memtx_tree_index *index = container_of(task,
-			struct memtx_tree_index, gc_task);
-	struct memtx_tree *tree = &index->tree;
-	struct memtx_tree_iterator *itr = &index->gc_iterator;
+	struct memtx_tree_index<USE_HINT> *index = container_of(task,
+			struct memtx_tree_index<USE_HINT>, gc_task);
+	memtx_tree_t<USE_HINT> *tree = &index->tree;
+	memtx_tree_iterator_t<USE_HINT> *itr = &index->gc_iterator;
 
 	unsigned int loops = 0;
 	while (!memtx_tree_iterator_is_invalid(itr)) {
-		struct memtx_tree_data *res =
+		struct memtx_tree_data<USE_HINT> *res =
 			memtx_tree_iterator_get_elem(tree, itr);
 		memtx_tree_iterator_next(tree, itr);
 		tuple_unref(res->tuple);
@@ -494,23 +601,32 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
 	*done = true;
 }
 
+template <int USE_HINT>
 static void
 memtx_tree_index_gc_free(struct memtx_gc_task *task)
 {
-	struct memtx_tree_index *index = container_of(task,
-			struct memtx_tree_index, gc_task);
+	struct memtx_tree_index<USE_HINT> *index = container_of(task,
+			struct memtx_tree_index<USE_HINT>, gc_task);
 	memtx_tree_index_free(index);
 }
 
-static const struct memtx_gc_task_vtab memtx_tree_index_gc_vtab = {
-	.run = memtx_tree_index_gc_run,
-	.free = memtx_tree_index_gc_free,
+template <int USE_HINT>
+static struct memtx_gc_task_vtab * get_memtx_tree_index_gc_vtab()
+{
+	static memtx_gc_task_vtab tab =
+	{
+		.run = memtx_tree_index_gc_run<USE_HINT>,
+		.free = memtx_tree_index_gc_free<USE_HINT>,
+	};
+	return &tab;
 };
 
+template <int USE_HINT>
 static void
 memtx_tree_index_destroy(struct index *base)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
 	if (base->def->iid == 0) {
 		/*
@@ -518,7 +634,7 @@ memtx_tree_index_destroy(struct index *base)
 		 * in the index, which may take a while. Schedule a
 		 * background task in order not to block tx thread.
 		 */
-		index->gc_task.vtab = &memtx_tree_index_gc_vtab;
+		index->gc_task.vtab = get_memtx_tree_index_gc_vtab<USE_HINT>();
 		index->gc_iterator = memtx_tree_iterator_first(&index->tree);
 		memtx_engine_schedule_gc(memtx, &index->gc_task);
 	} else {
@@ -530,10 +646,12 @@ memtx_tree_index_destroy(struct index *base)
 	}
 }
 
+template <int USE_HINT>
 static void
 memtx_tree_index_update_def(struct index *base)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	struct index_def *def = base->def;
 	/*
 	 * We use extended key def for non-unique and nullable
@@ -553,51 +671,62 @@ memtx_tree_index_depends_on_pk(struct index *base)
 	return !def->opts.is_unique || def->key_def->is_nullable;
 }
 
+template <int USE_HINT>
 static ssize_t
 memtx_tree_index_size(struct index *base)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	return memtx_tree_size(&index->tree);
 }
 
+template <int USE_HINT>
 static ssize_t
 memtx_tree_index_bsize(struct index *base)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	return memtx_tree_mem_used(&index->tree);
 }
 
+template <int USE_HINT>
 static int
 memtx_tree_index_random(struct index *base, uint32_t rnd, struct tuple **result)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct memtx_tree_data *res = memtx_tree_random(&index->tree, rnd);
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
+	struct memtx_tree_data<USE_HINT> *res = memtx_tree_random(&index->tree, rnd);
 	*result = res != NULL ? res->tuple : NULL;
 	return 0;
 }
 
+template <int USE_HINT>
 static ssize_t
 memtx_tree_index_count(struct index *base, enum iterator_type type,
 		       const char *key, uint32_t part_count)
 {
 	if (type == ITER_ALL)
-		return memtx_tree_index_size(base); /* optimization */
+		return memtx_tree_index_size<USE_HINT>(base); /* optimization */
 	return generic_index_count(base, type, key, part_count);
 }
 
+template <int USE_HINT>
 static int
 memtx_tree_index_get(struct index *base, const char *key,
 		     uint32_t part_count, struct tuple **result)
 {
 	assert(base->def->opts.is_unique &&
 	       part_count == base->def->key_def->part_count);
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
-	struct memtx_tree_key_data key_data;
+	struct memtx_tree_key_data<USE_HINT> key_data;
 	key_data.key = key;
 	key_data.part_count = part_count;
-	key_data.hint = key_hint(key, part_count, cmp_def);
-	struct memtx_tree_data *res = memtx_tree_find(&index->tree, &key_data);
+	if (USE_HINT)
+		key_data.set_hint(key_hint(key, part_count, cmp_def));
+	struct memtx_tree_data<USE_HINT> *res =
+		memtx_tree_find(&index->tree, &key_data);
 	if (res == NULL) {
 		*result = NULL;
 		return 0;
@@ -611,18 +740,21 @@ memtx_tree_index_get(struct index *base, const char *key,
 	return 0;
 }
 
+template <int USE_HINT>
 static int
 memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
 			 struct tuple *new_tuple, enum dup_replace_mode mode,
 			 struct tuple **result)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
 	if (new_tuple) {
-		struct memtx_tree_data new_data;
+		struct memtx_tree_data<USE_HINT> new_data;
 		new_data.tuple = new_tuple;
-		new_data.hint = tuple_hint(new_tuple, cmp_def);
-		struct memtx_tree_data dup_data;
+		if (USE_HINT)
+			new_data.set_hint(tuple_hint(new_tuple, cmp_def));
+		struct memtx_tree_data<USE_HINT> dup_data;
 		dup_data.tuple = NULL;
 
 		/* Try to optimistically replace the new_tuple. */
@@ -652,9 +784,10 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
 		}
 	}
 	if (old_tuple) {
-		struct memtx_tree_data old_data;
+		struct memtx_tree_data<USE_HINT> old_data;
 		old_data.tuple = old_tuple;
-		old_data.hint = tuple_hint(old_tuple, cmp_def);
+		if (USE_HINT)
+			old_data.set_hint(tuple_hint(old_tuple, cmp_def));
 		memtx_tree_delete(&index->tree, old_data);
 	}
 	*result = old_tuple;
@@ -667,13 +800,13 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
  * by all it's multikey indexes.
  */
 static int
-memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
+memtx_tree_index_replace_multikey_one(struct memtx_tree_index<1> *index,
 			struct tuple *old_tuple, struct tuple *new_tuple,
 			enum dup_replace_mode mode, hint_t hint,
-			struct memtx_tree_data *replaced_data,
+			struct memtx_tree_data<1> *replaced_data,
 			bool *is_multikey_conflict)
 {
-	struct memtx_tree_data new_data, dup_data;
+	struct memtx_tree_data<1> new_data, dup_data;
 	new_data.tuple = new_tuple;
 	new_data.hint = hint;
 	dup_data.tuple = NULL;
@@ -720,11 +853,11 @@ memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
  * delete operation is fault-tolerant.
  */
 static void
-memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index *index,
+memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index<1> *index,
 			struct tuple *new_tuple, struct tuple *replaced_tuple,
 			int err_multikey_idx)
 {
-	struct memtx_tree_data data;
+	struct memtx_tree_data<1> data;
 	if (replaced_tuple != NULL) {
 		/* Restore replaced tuple index occurrences. */
 		struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
@@ -798,7 +931,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
 			struct tuple *new_tuple, enum dup_replace_mode mode,
 			struct tuple **result)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
 	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
 	*result = NULL;
 	if (new_tuple != NULL) {
@@ -808,7 +941,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
 		for (; (uint32_t) multikey_idx < multikey_count;
 		     multikey_idx++) {
 			bool is_multikey_conflict;
-			struct memtx_tree_data replaced_data;
+			struct memtx_tree_data<1> replaced_data;
 			err = memtx_tree_index_replace_multikey_one(index,
 						old_tuple, new_tuple, mode,
 						multikey_idx, &replaced_data,
@@ -833,7 +966,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
 		}
 	}
 	if (old_tuple != NULL) {
-		struct memtx_tree_data data;
+		struct memtx_tree_data<1> data;
 		data.tuple = old_tuple;
 		uint32_t multikey_count =
 			tuple_multikey_count(old_tuple, cmp_def);
@@ -865,7 +998,7 @@ struct func_key_undo {
 	/** A link to organize entries in list. */
 	struct rlist link;
 	/** An inserted record copy. */
-	struct memtx_tree_data key;
+	struct memtx_tree_data<1> key;
 };
 
 /** Allocate a new func_key_undo on given region. */
@@ -888,7 +1021,7 @@ func_key_undo_new(struct region *region)
  * return a given index object in it's original state.
  */
 static void
-memtx_tree_func_index_replace_rollback(struct memtx_tree_index *index,
+memtx_tree_func_index_replace_rollback(struct memtx_tree_index<1> *index,
 				       struct rlist *old_keys,
 				       struct rlist *new_keys)
 {
@@ -919,7 +1052,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
 			struct tuple *new_tuple, enum dup_replace_mode mode,
 			struct tuple **result)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
 	struct index_def *index_def = index->base.def;
 	assert(index_def->key_def->for_func_index);
 
@@ -952,7 +1085,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
 			undo->key.hint = (hint_t)key;
 			rlist_add(&new_keys, &undo->link);
 			bool is_multikey_conflict;
-			struct memtx_tree_data old_data;
+			struct memtx_tree_data<1> old_data;
 			old_data.tuple = NULL;
 			err = memtx_tree_index_replace_multikey_one(index,
 						old_tuple, new_tuple,
@@ -1015,7 +1148,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
 		if (key_list_iterator_create(&it, old_tuple, index_def, false,
 					     func_index_key_dummy_alloc) != 0)
 			goto end;
-		struct memtx_tree_data data, deleted_data;
+		struct memtx_tree_data<1> data, deleted_data;
 		data.tuple = old_tuple;
 		const char *key;
 		while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
@@ -1040,11 +1173,13 @@ end:
 	return rc;
 }
 
+template <int USE_HINT>
 static struct iterator *
 memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
 				 const char *key, uint32_t part_count)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
 	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
 
@@ -1064,42 +1199,47 @@ memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
 		key = NULL;
 	}
 
-	struct tree_iterator *it = (struct tree_iterator *)
+	struct tree_iterator<USE_HINT> *it = (struct tree_iterator<USE_HINT> *)
 		mempool_alloc(&memtx->iterator_pool);
 	if (it == NULL) {
-		diag_set(OutOfMemory, sizeof(struct tree_iterator),
+		diag_set(OutOfMemory, sizeof(struct tree_iterator<USE_HINT>),
 			 "memtx_tree_index", "iterator");
 		return NULL;
 	}
 	iterator_create(&it->base, base);
 	it->pool = &memtx->iterator_pool;
-	it->base.next = tree_iterator_start;
-	it->base.free = tree_iterator_free;
+	it->base.next = tree_iterator_start<USE_HINT>;
+	it->base.free = tree_iterator_free<USE_HINT>;
 	it->type = type;
 	it->key_data.key = key;
 	it->key_data.part_count = part_count;
-	it->key_data.hint = key_hint(key, part_count, cmp_def);
-	it->tree_iterator = memtx_tree_invalid_iterator();
+	if (USE_HINT)
+		it->key_data.set_hint(key_hint(key, part_count, cmp_def));
+	invalidate_tree_iterator(&it->tree_iterator);
 	it->current.tuple = NULL;
 	return (struct iterator *)it;
 }
 
+template <int USE_HINT>
 static void
 memtx_tree_index_begin_build(struct index *base)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	assert(memtx_tree_size(&index->tree) == 0);
 	(void)index;
 }
 
+template <int USE_HINT>
 static int
 memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	if (size_hint < index->build_array_alloc_size)
 		return 0;
-	struct memtx_tree_data *tmp =
-		(struct memtx_tree_data *)
+	struct memtx_tree_data<USE_HINT> *tmp =
+		(struct memtx_tree_data<USE_HINT> *)
 			realloc(index->build_array, size_hint * sizeof(*tmp));
 	if (tmp == NULL) {
 		diag_set(OutOfMemory, size_hint * sizeof(*tmp),
@@ -1111,14 +1251,15 @@ memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
 	return 0;
 }
 
+template <int USE_HINT>
 /** Initialize the next element of the index build_array. */
 static int
-memtx_tree_index_build_array_append(struct memtx_tree_index *index,
+memtx_tree_index_build_array_append(struct memtx_tree_index<USE_HINT> *index,
 				    struct tuple *tuple, hint_t hint)
 {
 	if (index->build_array == NULL) {
 		index->build_array =
-			(struct memtx_tree_data *)malloc(MEMTX_EXTENT_SIZE);
+			(struct memtx_tree_data<USE_HINT> *)malloc(MEMTX_EXTENT_SIZE);
 		if (index->build_array == NULL) {
 			diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
 				 "memtx_tree_index", "build_next");
@@ -1131,8 +1272,8 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
 	if (index->build_array_size == index->build_array_alloc_size) {
 		index->build_array_alloc_size = index->build_array_alloc_size +
 				DIV_ROUND_UP(index->build_array_alloc_size, 2);
-		struct memtx_tree_data *tmp =
-			(struct memtx_tree_data *)realloc(index->build_array,
+		struct memtx_tree_data<USE_HINT> *tmp =
+			(struct memtx_tree_data<USE_HINT> *)realloc(index->build_array,
 				index->build_array_alloc_size * sizeof(*tmp));
 		if (tmp == NULL) {
 			diag_set(OutOfMemory, index->build_array_alloc_size *
@@ -1141,17 +1282,20 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
 		}
 		index->build_array = tmp;
 	}
-	struct memtx_tree_data *elem =
+	struct memtx_tree_data<USE_HINT> *elem =
 		&index->build_array[index->build_array_size++];
 	elem->tuple = tuple;
-	elem->hint = hint;
+	if (USE_HINT)
+		elem->set_hint(hint);
 	return 0;
 }
 
+template <int USE_HINT>
 static int
 memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
 	return memtx_tree_index_build_array_append(index, tuple,
 						   tuple_hint(tuple, cmp_def));
@@ -1160,7 +1304,7 @@ memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
 static int
 memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
 	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
 	uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);
 	for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;
@@ -1175,7 +1319,7 @@ memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
 static int
 memtx_tree_func_index_build_next(struct index *base, struct tuple *tuple)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
 	struct index_def *index_def = index->base.def;
 	assert(index_def->key_def->for_func_index);
 
@@ -1211,8 +1355,9 @@ error:
  * of equal tuples (in terms of index's cmp_def and have same
  * tuple pointer). The build_array is expected to be sorted.
  */
+template <int USE_HINT>
 static void
-memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
+memtx_tree_index_build_array_deduplicate(struct memtx_tree_index<USE_HINT> *index,
 			void (*destroy)(struct tuple *tuple, const char *hint))
 {
 	if (index->build_array_size == 0)
@@ -1246,13 +1391,16 @@ memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
 	index->build_array_size = w_idx + 1;
 }
 
+template <int USE_HINT>
 static void
 memtx_tree_index_end_build(struct index *base)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
 	struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
 	qsort_arg(index->build_array, index->build_array_size,
-		  sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);
+		  sizeof(index->build_array[0]),
+		  memtx_tree_qcompare<USE_HINT>, cmp_def);
 	if (cmp_def->is_multikey) {
 		/*
 		 * Multikey index may have equal(in terms of
@@ -1261,9 +1409,9 @@ memtx_tree_index_end_build(struct index *base)
 		 * the following memtx_tree_build assumes that
 		 * all keys are unique.
 		 */
-		memtx_tree_index_build_array_deduplicate(index, NULL);
+		memtx_tree_index_build_array_deduplicate<USE_HINT>(index, NULL);
 	} else if (cmp_def->for_func_index) {
-		memtx_tree_index_build_array_deduplicate(index,
+		memtx_tree_index_build_array_deduplicate<USE_HINT>(index,
 							 tuple_chunk_delete);
 	}
 	memtx_tree_build(&index->tree, index->build_array,
@@ -1275,19 +1423,21 @@ memtx_tree_index_end_build(struct index *base)
 	index->build_array_alloc_size = 0;
 }
 
+template <int USE_HINT>
 struct tree_snapshot_iterator {
 	struct snapshot_iterator base;
-	struct memtx_tree_index *index;
-	struct memtx_tree_iterator tree_iterator;
+	struct memtx_tree_index<USE_HINT> *index;
+	memtx_tree_iterator_t<USE_HINT> tree_iterator;
 	struct memtx_tx_snapshot_cleaner cleaner;
 };
 
+template <int USE_HINT>
 static void
 tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
 {
-	assert(iterator->free == tree_snapshot_iterator_free);
-	struct tree_snapshot_iterator *it =
-		(struct tree_snapshot_iterator *)iterator;
+	assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
+	struct tree_snapshot_iterator<USE_HINT> *it =
+		(struct tree_snapshot_iterator<USE_HINT> *)iterator;
 	memtx_leave_delayed_free_mode((struct memtx_engine *)
 				      it->index->base.engine);
 	memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);
@@ -1296,17 +1446,18 @@ tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
 	free(iterator);
 }
 
+template <int USE_HINT>
 static int
 tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
 			    const char **data, uint32_t *size)
 {
-	assert(iterator->free == tree_snapshot_iterator_free);
-	struct tree_snapshot_iterator *it =
-		(struct tree_snapshot_iterator *)iterator;
-	struct memtx_tree *tree = &it->index->tree;
+	assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
+	struct tree_snapshot_iterator<USE_HINT> *it =
+		(struct tree_snapshot_iterator<USE_HINT> *)iterator;
+	memtx_tree_t<USE_HINT> *tree = &it->index->tree;
 
 	while (true) {
-		struct memtx_tree_data *res =
+		struct memtx_tree_data<USE_HINT> *res =
 			memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
 
 		if (res == NULL) {
@@ -1333,14 +1484,18 @@ tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
  * index modifications will not affect the iteration results.
  * Must be destroyed by iterator->free after usage.
  */
+template <int USE_HINT>
 static struct snapshot_iterator *
 memtx_tree_index_create_snapshot_iterator(struct index *base)
 {
-	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
-	struct tree_snapshot_iterator *it =
-		(struct tree_snapshot_iterator *) calloc(1, sizeof(*it));
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)base;
+	struct tree_snapshot_iterator<USE_HINT> *it =
+		(struct tree_snapshot_iterator<USE_HINT> *)
+		calloc(1, sizeof(*it));
 	if (it == NULL) {
-		diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),
+		diag_set(OutOfMemory,
+			 sizeof(struct tree_snapshot_iterator<USE_HINT>),
 			 "memtx_tree_index", "create_snapshot_iterator");
 		return NULL;
 	}
@@ -1352,8 +1507,8 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
 		return NULL;
 	}
 
-	it->base.free = tree_snapshot_iterator_free;
-	it->base.next = tree_snapshot_iterator_next;
+	it->base.free = tree_snapshot_iterator_free<USE_HINT>;
+	it->base.next = tree_snapshot_iterator_next<USE_HINT>;
 	it->index = index;
 	index_ref(base);
 	it->tree_iterator = memtx_tree_iterator_first(&index->tree);
@@ -1362,94 +1517,124 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
 	return (struct snapshot_iterator *) it;
 }
 
-static const struct index_vtab memtx_tree_index_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
+static const struct index_vtab memtx_tree_no_hint_index_vtab = {
+	/* .destroy = */ memtx_tree_index_destroy<0>,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .abort_create = */ generic_index_abort_create,
 	/* .commit_modify = */ generic_index_commit_modify,
 	/* .commit_drop = */ generic_index_commit_drop,
-	/* .update_def = */ memtx_tree_index_update_def,
+	/* .update_def = */ memtx_tree_index_update_def<0>,
 	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
 	/* .def_change_requires_rebuild = */
 		memtx_index_def_change_requires_rebuild,
-	/* .size = */ memtx_tree_index_size,
-	/* .bsize = */ memtx_tree_index_bsize,
+	/* .size = */ memtx_tree_index_size<0>,
+	/* .bsize = */ memtx_tree_index_bsize<0>,
 	/* .min = */ generic_index_min,
 	/* .max = */ generic_index_max,
-	/* .random = */ memtx_tree_index_random,
-	/* .count = */ memtx_tree_index_count,
-	/* .get = */ memtx_tree_index_get,
-	/* .replace = */ memtx_tree_index_replace,
-	/* .create_iterator = */ memtx_tree_index_create_iterator,
+	/* .random = */ memtx_tree_index_random<0>,
+	/* .count = */ memtx_tree_index_count<0>,
+	/* .get = */ memtx_tree_index_get<0>,
+	/* .replace = */ memtx_tree_index_replace<0>,
+	/* .create_iterator = */ memtx_tree_index_create_iterator<0>,
 	/* .create_snapshot_iterator = */
-		memtx_tree_index_create_snapshot_iterator,
+		memtx_tree_index_create_snapshot_iterator<0>,
 	/* .stat = */ generic_index_stat,
 	/* .compact = */ generic_index_compact,
 	/* .reset_stat = */ generic_index_reset_stat,
-	/* .begin_build = */ memtx_tree_index_begin_build,
-	/* .reserve = */ memtx_tree_index_reserve,
-	/* .build_next = */ memtx_tree_index_build_next,
-	/* .end_build = */ memtx_tree_index_end_build,
+	/* .begin_build = */ memtx_tree_index_begin_build<0>,
+	/* .reserve = */ memtx_tree_index_reserve<0>,
+	/* .build_next = */ memtx_tree_index_build_next<0>,
+	/* .end_build = */ memtx_tree_index_end_build<0>,
+};
+
+static const struct index_vtab memtx_tree_use_hint_index_vtab = {
+	/* .destroy = */ memtx_tree_index_destroy<1>,
+	/* .commit_create = */ generic_index_commit_create,
+	/* .abort_create = */ generic_index_abort_create,
+	/* .commit_modify = */ generic_index_commit_modify,
+	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ memtx_tree_index_update_def<1>,
+	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
+	/* .def_change_requires_rebuild = */
+		memtx_index_def_change_requires_rebuild,
+	/* .size = */ memtx_tree_index_size<1>,
+	/* .bsize = */ memtx_tree_index_bsize<1>,
+	/* .min = */ generic_index_min,
+	/* .max = */ generic_index_max,
+	/* .random = */ memtx_tree_index_random<1>,
+	/* .count = */ memtx_tree_index_count<1>,
+	/* .get = */ memtx_tree_index_get<1>,
+	/* .replace = */ memtx_tree_index_replace<1>,
+	/* .create_iterator = */ memtx_tree_index_create_iterator<1>,
+	/* .create_snapshot_iterator = */
+		memtx_tree_index_create_snapshot_iterator<1>,
+	/* .stat = */ generic_index_stat,
+	/* .compact = */ generic_index_compact,
+	/* .reset_stat = */ generic_index_reset_stat,
+	/* .begin_build = */ memtx_tree_index_begin_build<1>,
+	/* .reserve = */ memtx_tree_index_reserve<1>,
+	/* .build_next = */ memtx_tree_index_build_next<1>,
+	/* .end_build = */ memtx_tree_index_end_build<1>,
 };
 
 static const struct index_vtab memtx_tree_index_multikey_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
+	/* .destroy = */ memtx_tree_index_destroy<1>,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .abort_create = */ generic_index_abort_create,
 	/* .commit_modify = */ generic_index_commit_modify,
 	/* .commit_drop = */ generic_index_commit_drop,
-	/* .update_def = */ memtx_tree_index_update_def,
+	/* .update_def = */ memtx_tree_index_update_def<1>,
 	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
 	/* .def_change_requires_rebuild = */
 		memtx_index_def_change_requires_rebuild,
-	/* .size = */ memtx_tree_index_size,
-	/* .bsize = */ memtx_tree_index_bsize,
+	/* .size = */ memtx_tree_index_size<1>,
+	/* .bsize = */ memtx_tree_index_bsize<1>,
 	/* .min = */ generic_index_min,
 	/* .max = */ generic_index_max,
-	/* .random = */ memtx_tree_index_random,
-	/* .count = */ memtx_tree_index_count,
-	/* .get = */ memtx_tree_index_get,
+	/* .random = */ memtx_tree_index_random<1>,
+	/* .count = */ memtx_tree_index_count<1>,
+	/* .get = */ memtx_tree_index_get<1>,
 	/* .replace = */ memtx_tree_index_replace_multikey,
-	/* .create_iterator = */ memtx_tree_index_create_iterator,
+	/* .create_iterator = */ memtx_tree_index_create_iterator<1>,
 	/* .create_snapshot_iterator = */
-		memtx_tree_index_create_snapshot_iterator,
+		memtx_tree_index_create_snapshot_iterator<1>,
 	/* .stat = */ generic_index_stat,
 	/* .compact = */ generic_index_compact,
 	/* .reset_stat = */ generic_index_reset_stat,
-	/* .begin_build = */ memtx_tree_index_begin_build,
-	/* .reserve = */ memtx_tree_index_reserve,
+	/* .begin_build = */ memtx_tree_index_begin_build<1>,
+	/* .reserve = */ memtx_tree_index_reserve<1>,
 	/* .build_next = */ memtx_tree_index_build_next_multikey,
-	/* .end_build = */ memtx_tree_index_end_build,
+	/* .end_build = */ memtx_tree_index_end_build<1>,
 };
 
 static const struct index_vtab memtx_tree_func_index_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
+	/* .destroy = */ memtx_tree_index_destroy<1>,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .abort_create = */ generic_index_abort_create,
 	/* .commit_modify = */ generic_index_commit_modify,
 	/* .commit_drop = */ generic_index_commit_drop,
-	/* .update_def = */ memtx_tree_index_update_def,
+	/* .update_def = */ memtx_tree_index_update_def<1>,
 	/* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
 	/* .def_change_requires_rebuild = */
 		memtx_index_def_change_requires_rebuild,
-	/* .size = */ memtx_tree_index_size,
-	/* .bsize = */ memtx_tree_index_bsize,
+	/* .size = */ memtx_tree_index_size<1>,
+	/* .bsize = */ memtx_tree_index_bsize<1>,
 	/* .min = */ generic_index_min,
 	/* .max = */ generic_index_max,
-	/* .random = */ memtx_tree_index_random,
-	/* .count = */ memtx_tree_index_count,
-	/* .get = */ memtx_tree_index_get,
+	/* .random = */ memtx_tree_index_random<1>,
+	/* .count = */ memtx_tree_index_count<1>,
+	/* .get = */ memtx_tree_index_get<1>,
 	/* .replace = */ memtx_tree_func_index_replace,
-	/* .create_iterator = */ memtx_tree_index_create_iterator,
+	/* .create_iterator = */ memtx_tree_index_create_iterator<1>,
 	/* .create_snapshot_iterator = */
-		memtx_tree_index_create_snapshot_iterator,
+		memtx_tree_index_create_snapshot_iterator<1>,
 	/* .stat = */ generic_index_stat,
 	/* .compact = */ generic_index_compact,
 	/* .reset_stat = */ generic_index_reset_stat,
-	/* .begin_build = */ memtx_tree_index_begin_build,
-	/* .reserve = */ memtx_tree_index_reserve,
+	/* .begin_build = */ memtx_tree_index_begin_build<1>,
+	/* .reserve = */ memtx_tree_index_reserve<1>,
 	/* .build_next = */ memtx_tree_func_index_build_next,
-	/* .end_build = */ memtx_tree_index_end_build,
+	/* .end_build = */ memtx_tree_index_end_build<1>,
 };
 
 /**
@@ -1459,7 +1644,7 @@ static const struct index_vtab memtx_tree_func_index_vtab = {
  * key defintion is not completely initialized at that moment).
  */
 static const struct index_vtab memtx_tree_disabled_index_vtab = {
-	/* .destroy = */ memtx_tree_index_destroy,
+	/* .destroy = */ memtx_tree_index_destroy<1>,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .abort_create = */ generic_index_abort_create,
 	/* .commit_modify = */ generic_index_commit_modify,
@@ -1488,27 +1673,19 @@ static const struct index_vtab memtx_tree_disabled_index_vtab = {
 	/* .end_build = */ generic_index_end_build,
 };
 
-struct index *
-memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
+template <int USE_HINT>
+static struct index *
+memtx_tree_index_new_tpl(struct memtx_engine *memtx, struct index_def *def,
+			 const struct index_vtab *vtab)
 {
-	struct memtx_tree_index *index =
-		(struct memtx_tree_index *)calloc(1, sizeof(*index));
+	struct memtx_tree_index<USE_HINT> *index =
+		(struct memtx_tree_index<USE_HINT> *)
+		calloc(1, sizeof(*index));
 	if (index == NULL) {
 		diag_set(OutOfMemory, sizeof(*index),
 			 "malloc", "struct memtx_tree_index");
 		return NULL;
 	}
-	const struct index_vtab *vtab;
-	if (def->key_def->for_func_index) {
-		if (def->key_def->func_index_func == NULL)
-			vtab = &memtx_tree_disabled_index_vtab;
-		else
-			vtab = &memtx_tree_func_index_vtab;
-	} else if (def->key_def->is_multikey) {
-		vtab = &memtx_tree_index_multikey_vtab;
-	} else {
-		vtab = &memtx_tree_index_vtab;
-	}
 	if (index_create(&index->base, (struct engine *)memtx,
 			 vtab, def) != 0) {
 		free(index);
@@ -1524,3 +1701,23 @@ memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
 			  memtx_index_extent_free, memtx);
 	return &index->base;
 }
+
+struct index *
+memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
+{
+	const struct index_vtab *vtab;
+	if (def->key_def->for_func_index) {
+		if (def->key_def->func_index_func == NULL)
+			vtab = &memtx_tree_disabled_index_vtab;
+		else
+			vtab = &memtx_tree_func_index_vtab;
+	} else if (def->key_def->is_multikey) {
+		vtab = &memtx_tree_index_multikey_vtab;
+	} else if (def->opts.hint) {
+		vtab = &memtx_tree_use_hint_index_vtab;
+	} else {
+		vtab = &memtx_tree_no_hint_index_vtab;
+		return memtx_tree_index_new_tpl<0>(memtx, def, vtab);
+	}
+	return memtx_tree_index_new_tpl<1>(memtx, def, vtab);
+}
diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
index ef5ae3d..0bb803a 100644
--- a/src/lib/salad/bps_tree.h
+++ b/src/lib/salad/bps_tree.h
@@ -168,6 +168,17 @@
 
 /* {{{ BPS-tree interface settings */
 /**
+ * Optional namespace for structs and functions.
+ * Is set, struct and functions will have BPS_TREE_NAMESPACE::  prefix.
+ * For example one can #define BPS_TREE_NAMESPACE my along with
+ * #define BPS_TREE_NAME _test, and use then
+ *  struct my::bps_tree_test my_tree;
+ *  my::bps_tree_test_create(&my_tree, ...);
+ *
+ * #define BPS_TREE_NAMESPACE
+ */
+
+/**
  * Custom name for structs and functions.
  * Struct and functions will have bps_tree##BPS_TREE_NAME name or prefix.
  * For example one can #define BPS_TREE_NAME _test, and use then
@@ -300,6 +311,10 @@
 
 /* }}} */
 
+#ifdef BPS_TREE_NAMESPACE
+namespace BPS_TREE_NAMESPACE {
+#endif
+
 /* {{{ BPS-tree internal settings */
 typedef int16_t bps_tree_pos_t;
 typedef uint32_t bps_tree_block_id_t;
@@ -6188,3 +6203,7 @@ bps_tree_debug_check_internal_functions(bool assertme)
 #undef bps_tree_debug_check_insert_and_move_to_left_inner
 
 /* }}} */
+
+#ifdef BPS_TREE_NAMESPACE
+} /* namespace BPS_TREE_NAMESPACE { */
+#endif
diff --git a/test/box/alter.result b/test/box/alter.result
index 237c2d8..5b412a7 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -680,8 +680,9 @@ s.index.pk
   - type: unsigned
     is_nullable: false
     fieldno: 2
-  type: TREE
+  hint: true
   id: 0
+  type: TREE
   space_id: 731
   name: pk
 ...
@@ -710,9 +711,10 @@ s.index.pk
   - type: unsigned
     is_nullable: false
     fieldno: 1
-  space_id: 731
+  hint: true
   id: 0
   type: TREE
+  space_id: 731
   name: pk
 ...
 s.index.secondary
@@ -722,8 +724,9 @@ s.index.secondary
   - type: unsigned
     is_nullable: false
     fieldno: 2
-  type: TREE
+  hint: true
   id: 1
+  type: TREE
   space_id: 731
   name: secondary
 ...
@@ -1559,3 +1562,97 @@ assert(err:match('does not exist') ~= nil)
 ---
 - true
 ...
+-- hint
+s = box.schema.create_space('test');
+---
+...
+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
+---
+- true
+...
+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
+---
+- true
+...
+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
+---
+- false
+...
+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
+---
+- false
+...
+s.index.test1.hint
+---
+- true
+...
+s.index.test2.hint
+---
+- true
+...
+s.index.test3.hint
+---
+- false
+...
+s.index.test4.hint
+---
+- false
+...
+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
+---
+...
+s.index.test1:bsize() == s.index.test2:bsize()
+---
+- true
+...
+s.index.test1:bsize() > s.index.test3:bsize()
+---
+- true
+...
+s.index.test1:bsize() > s.index.test4:bsize()
+---
+- true
+...
+s.index.test1:alter{hint=false}
+---
+...
+s.index.test2:alter{hint=true}
+---
+...
+s.index.test3:alter{name='test33', hint=false}
+---
+...
+s.index.test4:alter{hint=true}
+---
+...
+s.index.test1.hint
+---
+- false
+...
+s.index.test2.hint
+---
+- true
+...
+s.index.test33.hint
+---
+- false
+...
+s.index.test4.hint
+---
+- true
+...
+s.index.test1:bsize() < s.index.test2:bsize()
+---
+- true
+...
+s.index.test1:bsize() == s.index.test33:bsize()
+---
+- true
+...
+s.index.test1:bsize() < s.index.test4:bsize()
+---
+- true
+...
+s:drop()
+---
+...
diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
index abd08e2..2114186 100644
--- a/test/box/alter.test.lua
+++ b/test/box/alter.test.lua
@@ -620,3 +620,37 @@ s:drop()
 s:alter({})
 ok, err = pcall(box.schema.space.alter, s.id, {})
 assert(err:match('does not exist') ~= nil)
+
+-- hint
+s = box.schema.create_space('test');
+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
+
+s.index.test1.hint
+s.index.test2.hint
+s.index.test3.hint
+s.index.test4.hint
+
+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
+
+s.index.test1:bsize() == s.index.test2:bsize()
+s.index.test1:bsize() > s.index.test3:bsize()
+s.index.test1:bsize() > s.index.test4:bsize()
+
+s.index.test1:alter{hint=false}
+s.index.test2:alter{hint=true}
+s.index.test3:alter{name='test33', hint=false}
+s.index.test4:alter{hint=true}
+
+s.index.test1.hint
+s.index.test2.hint
+s.index.test33.hint
+s.index.test4.hint
+
+s.index.test1:bsize() < s.index.test2:bsize()
+s.index.test1:bsize() == s.index.test33:bsize()
+s.index.test1:bsize() < s.index.test4:bsize()
+
+s:drop()
\ No newline at end of file
diff --git a/test/box/errinj.result b/test/box/errinj.result
index 613d22c..b8c2476 100644
--- a/test/box/errinj.result
+++ b/test/box/errinj.result
@@ -1774,9 +1774,10 @@ rtreespace:create_index('pk', {if_not_exists = true})
   - type: unsigned
     is_nullable: false
     fieldno: 1
+  hint: true
   id: 0
-  space_id: 512
   type: TREE
+  space_id: 512
   name: pk
 ...
 rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array'},unique = false, if_not_exists = true,})
diff --git a/test/box/tree_pk.result b/test/box/tree_pk.result
index df3c78b..18cb607 100644
--- a/test/box/tree_pk.result
+++ b/test/box/tree_pk.result
@@ -852,3 +852,317 @@ box.internal.collation.drop('test')
 box.internal.collation.drop('test-ci')
 ---
 ...
+-- hints
+s = box.schema.space.create('test')
+---
+...
+s:create_index('test', {type = 'tree', hint = 'true'} )
+---
+- error: Illegal parameters, options parameter 'hint' should be of type boolean
+...
+s:create_index('test', {type = 'hash', hint = true} )
+---
+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
+    with memtx tree index'
+...
+s:create_index('test', {type = 'hash'}):alter({hint = true})
+---
+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
+    with memtx tree index'
+...
+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
+---
+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
+    can''t use hints'
+...
+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
+---
+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
+    can''t use hints'
+...
+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
+---
+...
+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
+---
+...
+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
+---
+- error: 'Can''t create or modify index ''func'' in space ''test'': functional index
+    can''t use hints'
+...
+s:drop()
+---
+...
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+s:create_index('test', {type = 'tree', hint = true} )
+---
+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
+    with memtx tree index'
+...
+s:drop()
+---
+...
+-- numeric hints
+s1 = box.schema.space.create('test1')
+---
+...
+s1:create_index('test', {type = 'tree', hint = false}).hint
+---
+- false
+...
+s2 = box.schema.space.create('test2')
+---
+...
+s2:create_index('test', {type = 'tree'}).hint
+---
+- true
+...
+N = 2000
+---
+...
+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
+---
+...
+s1:count() == s2:count()
+---
+- true
+...
+good = true r1 = s1:select{} r2 = s2:select{}
+---
+...
+if #r1 ~= #r2 then good = false end
+---
+...
+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
+---
+...
+good
+---
+- true
+...
+r1 = nil r2 = nil
+---
+...
+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
+---
+...
+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
+---
+...
+good
+---
+- true
+...
+s1.index[0]:bsize() < s2.index[0]:bsize()
+---
+- true
+...
+s1.index[0]:alter{hint=true}
+---
+...
+s1.index[0]:bsize() == s2.index[0]:bsize()
+---
+- true
+...
+s2.index[0]:alter{hint=false}
+---
+...
+s1.index[0]:bsize() > s2.index[0]:bsize()
+---
+- true
+...
+s1.index[0]:alter{hint=false}
+---
+...
+s1.index[0]:bsize() == s2.index[0]:bsize()
+---
+- true
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
+-- string hints
+s1 = box.schema.space.create('test1')
+---
+...
+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
+---
+- false
+...
+s2 = box.schema.space.create('test2')
+---
+...
+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
+---
+- true
+...
+N = 1000
+---
+...
+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
+---
+...
+s1:count() == s2:count()
+---
+- true
+...
+good = true r1 = s1:select{} r2 = s2:select{}
+---
+...
+if #r1 ~= #r2 then good = false end
+---
+...
+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
+---
+...
+good
+---
+- true
+...
+r1 = nil r2 = nil
+---
+...
+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
+---
+...
+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
+---
+...
+good
+---
+- true
+...
+s1.index[0]:bsize() < s2.index[0]:bsize()
+---
+- true
+...
+s1.index[0]:alter{hint=true}
+---
+...
+s1.index[0]:bsize() == s2.index[0]:bsize()
+---
+- true
+...
+s2.index[0]:alter{hint=false}
+---
+...
+s1.index[0]:bsize() > s2.index[0]:bsize()
+---
+- true
+...
+s1.index[0]:alter{hint=false}
+---
+...
+s1.index[0]:bsize() == s2.index[0]:bsize()
+---
+- true
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
+-- string with collation hints
+s1 = box.schema.space.create('test1')
+---
+...
+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
+---
+- false
+...
+s2 = box.schema.space.create('test2')
+---
+...
+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
+---
+- true
+...
+N = 1000
+---
+...
+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
+---
+...
+syms_size = #syms
+---
+...
+len = 20
+---
+...
+vals = {}
+---
+...
+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
+---
+...
+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
+---
+...
+s1:count() == s2:count()
+---
+- true
+...
+good = true r1 = s1:select{} r2 = s2:select{}
+---
+...
+if #r1 ~= #r2 then good = false end
+---
+...
+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
+---
+...
+good
+---
+- true
+...
+r1 = nil r2 = nil
+---
+...
+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
+---
+...
+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
+---
+...
+good
+---
+- true
+...
+s1.index[0]:bsize() < s2.index[0]:bsize()
+---
+- true
+...
+s1.index[0]:alter{hint=true}
+---
+...
+s1.index[0]:bsize() == s2.index[0]:bsize()
+---
+- true
+...
+s2.index[0]:alter{hint=false}
+---
+...
+s1.index[0]:bsize() > s2.index[0]:bsize()
+---
+- true
+...
+s1.index[0]:alter{hint=false}
+---
+...
+s1.index[0]:bsize() == s2.index[0]:bsize()
+---
+- true
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
diff --git a/test/box/tree_pk.test.lua b/test/box/tree_pk.test.lua
index 1190ab4..2f22ed9 100644
--- a/test/box/tree_pk.test.lua
+++ b/test/box/tree_pk.test.lua
@@ -301,3 +301,118 @@ s:drop()
 
 box.internal.collation.drop('test')
 box.internal.collation.drop('test-ci')
+
+-- hints
+s = box.schema.space.create('test')
+s:create_index('test', {type = 'tree', hint = 'true'} )
+s:create_index('test', {type = 'hash', hint = true} )
+s:create_index('test', {type = 'hash'}):alter({hint = true})
+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
+s:drop()
+
+s = box.schema.space.create('test', {engine = 'vinyl'})
+s:create_index('test', {type = 'tree', hint = true} )
+s:drop()
+
+-- numeric hints
+s1 = box.schema.space.create('test1')
+s1:create_index('test', {type = 'tree', hint = false}).hint
+s2 = box.schema.space.create('test2')
+s2:create_index('test', {type = 'tree'}).hint
+
+N = 2000
+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
+s1:count() == s2:count()
+
+good = true r1 = s1:select{} r2 = s2:select{}
+if #r1 ~= #r2 then good = false end
+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
+good
+r1 = nil r2 = nil
+
+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
+good
+
+s1.index[0]:bsize() < s2.index[0]:bsize()
+s1.index[0]:alter{hint=true}
+s1.index[0]:bsize() == s2.index[0]:bsize()
+s2.index[0]:alter{hint=false}
+s1.index[0]:bsize() > s2.index[0]:bsize()
+s1.index[0]:alter{hint=false}
+s1.index[0]:bsize() == s2.index[0]:bsize()
+
+s1:drop()
+s2:drop()
+
+-- string hints
+s1 = box.schema.space.create('test1')
+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
+s2 = box.schema.space.create('test2')
+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
+
+N = 1000
+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
+s1:count() == s2:count()
+
+good = true r1 = s1:select{} r2 = s2:select{}
+if #r1 ~= #r2 then good = false end
+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
+good
+r1 = nil r2 = nil
+
+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
+good
+
+s1.index[0]:bsize() < s2.index[0]:bsize()
+s1.index[0]:alter{hint=true}
+s1.index[0]:bsize() == s2.index[0]:bsize()
+s2.index[0]:alter{hint=false}
+s1.index[0]:bsize() > s2.index[0]:bsize()
+s1.index[0]:alter{hint=false}
+s1.index[0]:bsize() == s2.index[0]:bsize()
+
+s1:drop()
+s2:drop()
+
+-- string with collation hints
+s1 = box.schema.space.create('test1')
+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
+s2 = box.schema.space.create('test2')
+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
+
+N = 1000
+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
+syms_size = #syms
+len = 20
+vals = {}
+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
+
+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
+s1:count() == s2:count()
+
+good = true r1 = s1:select{} r2 = s2:select{}
+if #r1 ~= #r2 then good = false end
+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
+good
+r1 = nil r2 = nil
+
+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
+good
+
+s1.index[0]:bsize() < s2.index[0]:bsize()
+s1.index[0]:alter{hint=true}
+s1.index[0]:bsize() == s2.index[0]:bsize()
+s2.index[0]:alter{hint=false}
+s1.index[0]:bsize() > s2.index[0]:bsize()
+s1.index[0]:alter{hint=false}
+s1.index[0]:bsize() == s2.index[0]:bsize()
+
+s1:drop()
+s2:drop()
diff --git a/test/box/tree_pk_multipart.result b/test/box/tree_pk_multipart.result
index 93219f6..3f6bb94 100644
--- a/test/box/tree_pk_multipart.result
+++ b/test/box/tree_pk_multipart.result
@@ -542,3 +542,156 @@ space:drop()
 space = nil
 ---
 ...
+-- hints
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function equal(res1, res2)
+    if #res1 ~= #res2 then
+        return false
+    end
+    for k,v in pairs(res1) do
+        if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
+            return false
+        end
+    end
+    return true
+end
+test_run:cmd("setopt delimiter ''");
+---
+...
+-- num num
+N1 = 100
+---
+...
+t1 = {}
+---
+...
+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
+---
+...
+N2 = 5
+---
+...
+t2 = {}
+---
+...
+for i = 1,N2*2 do t2[i] = math.random(1000000) end
+---
+...
+s1 = box.schema.space.create('test1')
+---
+...
+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
+---
+- false
+...
+s2 = box.schema.space.create('test2')
+---
+...
+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
+---
+- true
+...
+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
+---
+...
+s1:count() == s2:count()
+---
+- true
+...
+equal(s1:select{}, s2:select{})
+---
+- true
+...
+good = true
+---
+...
+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
+---
+...
+good
+---
+- true
+...
+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
+---
+...
+good
+---
+- true
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
+-- str num
+N1 = 100
+---
+...
+t1 = {}
+---
+...
+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
+---
+...
+N2 = 5
+---
+...
+t2 = {}
+---
+...
+for i = 1,N2*2 do t2[i] = math.random(1000000) end
+---
+...
+s1 = box.schema.space.create('test1')
+---
+...
+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
+---
+- false
+...
+s2 = box.schema.space.create('test2')
+---
+...
+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
+---
+- true
+...
+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
+---
+...
+s1:count() == s2:count()
+---
+- true
+...
+equal(s1:select{}, s2:select{})
+---
+- true
+...
+good = true
+---
+...
+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
+---
+...
+good
+---
+- true
+...
+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
+---
+...
+good
+---
+- true
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
diff --git a/test/box/tree_pk_multipart.test.lua b/test/box/tree_pk_multipart.test.lua
index 7d3405f..cba3a98 100644
--- a/test/box/tree_pk_multipart.test.lua
+++ b/test/box/tree_pk_multipart.test.lua
@@ -194,3 +194,67 @@ test_run:cmd("setopt delimiter ''");
 space:drop()
 
 space = nil
+
+
+-- hints
+test_run:cmd("setopt delimiter ';'")
+function equal(res1, res2)
+    if #res1 ~= #res2 then
+        return false
+    end
+    for k,v in pairs(res1) do
+        if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
+            return false
+        end
+    end
+    return true
+end
+test_run:cmd("setopt delimiter ''");
+
+-- num num
+N1 = 100
+t1 = {}
+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
+N2 = 5
+t2 = {}
+for i = 1,N2*2 do t2[i] = math.random(1000000) end
+
+s1 = box.schema.space.create('test1')
+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
+s2 = box.schema.space.create('test2')
+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
+s1:count() == s2:count()
+equal(s1:select{}, s2:select{})
+good = true
+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
+good
+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
+good
+
+s1:drop()
+s2:drop()
+
+-- str num
+N1 = 100
+t1 = {}
+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
+N2 = 5
+t2 = {}
+for i = 1,N2*2 do t2[i] = math.random(1000000) end
+
+s1 = box.schema.space.create('test1')
+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
+s2 = box.schema.space.create('test2')
+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
+s1:count() == s2:count()
+equal(s1:select{}, s2:select{})
+good = true
+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
+good
+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
+good
+
+s1:drop()
+s2:drop()
-- 
2.7.4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH 0/2] Make tree hint optional
  2020-10-19  9:51 [Tarantool-patches] [PATCH 0/2] Make tree hint optional Aleksandr Lyapunov
  2020-10-19  9:51 ` [Tarantool-patches] [PATCH 1/2] memtx: move memtx_tree.c to memtx_tree.cc Aleksandr Lyapunov
  2020-10-19  9:51 ` [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional Aleksandr Lyapunov
@ 2020-10-20  1:01 ` Nikita Pettik
  2020-10-21 17:02   ` Alexander V. Tikhonov
  2 siblings, 1 reply; 11+ messages in thread
From: Nikita Pettik @ 2020-10-20  1:01 UTC (permalink / raw)
  To: Aleksandr Lyapunov; +Cc: tarantool-patches

On 19 Oct 12:51, Aleksandr Lyapunov wrote:
> https://github.com/tarantool/tarantool/issues/4927
> https://github.com/tarantool/tarantool/tree/alyapunov/gh-4927-optional-hints

Release fails to build:
https://gitlab.com/tarantool/tarantool/-/jobs/798546360
https://gitlab.com/tarantool/tarantool/-/jobs/798546354
 
> Aleksandr Lyapunov (1):
>   memtx: make tuple compare hints optional
> 
> Ilya Kosarev (1):
>   memtx: move memtx_tree.c to memtx_tree.cc
> 
>  src/box/CMakeLists.txt              |    2 +-
>  src/box/index_def.c                 |    2 +
>  src/box/index_def.h                 |    6 +
>  src/box/lua/schema.lua              |   53 ++
>  src/box/lua/space.cc                |    7 +
>  src/box/memtx_engine.c              |    2 +
>  src/box/memtx_tree.c                | 1523 -------------------------------
>  src/box/memtx_tree.cc               | 1723 +++++++++++++++++++++++++++++++++++
>  src/lib/salad/bps_tree.h            |   19 +
>  test/box/alter.result               |  103 ++-
>  test/box/alter.test.lua             |   34 +
>  test/box/errinj.result              |    3 +-
>  test/box/tree_pk.result             |  314 +++++++
>  test/box/tree_pk.test.lua           |  115 +++
>  test/box/tree_pk_multipart.result   |  153 ++++
>  test/box/tree_pk_multipart.test.lua |   64 ++
>  16 files changed, 2595 insertions(+), 1528 deletions(-)
>  delete mode 100644 src/box/memtx_tree.c
>  create mode 100644 src/box/memtx_tree.cc
> 
> -- 
> 2.7.4
> 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional
  2020-10-19  9:51 ` [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional Aleksandr Lyapunov
@ 2020-10-20 12:34   ` Ilya Kosarev
  2020-10-20 12:37     ` Nikita Pettik
  0 siblings, 1 reply; 11+ messages in thread
From: Ilya Kosarev @ 2020-10-20 12:34 UTC (permalink / raw)
  To: Aleksandr Lyapunov; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 78453 bytes --]


Hi,
 
Thanks for the patch!
 
LGTM, i checked for the number of compilers that if with the templated
agrument is being processed at the compile time so that there are no jumps
in asm code.
 
What i think is needed is the enum like the following:
enum {
        NOT_HINTED =  0 ,
        HINTED =  1 ,
};
 
to avoid using <0> & <1>. If needed i can push such patch to the branch. 
>Понедельник, 19 октября 2020, 12:52 +03:00 от Aleksandr Lyapunov <alyapunov@tarantool.org>:
> 
>Since 9fba29abb4e05babb9b23b4413bd8083f0fba933 (memtx: introduce tuple
>compare hint) memtx tree key data (indexes) started to contain extra 8
>bytes as a hint. Now it is optional and can be turned off in an index
>options with "hint = false" entry.
>
>Closes #4927
>
>@TarantoolBot document
>Title: memtx: optional tuple compare hints
>Update the documentation for an index creation to reflect that there is
>now an option to turn off hints for the index.
>---
> src/box/index_def.c | 2 +
> src/box/index_def.h | 6 +
> src/box/lua/schema.lua | 53 ++++
> src/box/lua/space.cc | 7 +
> src/box/memtx_engine.c | 2 +
> src/box/memtx_tree.cc | 599 ++++++++++++++++++++++++------------
> src/lib/salad/bps_tree.h | 19 ++
> test/box/alter.result | 103 ++++++-
> test/box/alter.test.lua | 34 ++
> test/box/errinj.result | 3 +-
> test/box/tree_pk.result | 314 +++++++++++++++++++
> test/box/tree_pk.test.lua | 115 +++++++
> test/box/tree_pk_multipart.result | 153 +++++++++
> test/box/tree_pk_multipart.test.lua | 64 ++++
> 14 files changed, 1269 insertions(+), 205 deletions(-)
>
>diff --git a/src/box/index_def.c b/src/box/index_def.c
>index 9802961..79394b8 100644
>--- a/src/box/index_def.c
>+++ b/src/box/index_def.c
>@@ -51,6 +51,7 @@ const struct index_opts index_opts_default = {
>  /* .lsn = */ 0,
>  /* .stat = */ NULL,
>  /* .func = */ 0,
>+ /* .hint = */ true,
> };
> 
> const struct opt_def index_opts_reg[] = {
>@@ -66,6 +67,7 @@ const struct opt_def index_opts_reg[] = {
>  OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn),
>  OPT_DEF("func", OPT_UINT32, struct index_opts, func_id),
>  OPT_DEF_LEGACY("sql"),
>+ OPT_DEF("hint", OPT_BOOL, struct index_opts, hint),
>  OPT_END,
> };
> 
>diff --git a/src/box/index_def.h b/src/box/index_def.h
>index d928b23..2180a69 100644
>--- a/src/box/index_def.h
>+++ b/src/box/index_def.h
>@@ -165,6 +165,10 @@ struct index_opts {
>  struct index_stat *stat;
>  /** Identifier of the functional index function. */
>  uint32_t func_id;
>+ /**
>+ * Use hint optimization for tree index.
>+ */
>+ bool hint;
> };
> 
> extern const struct index_opts index_opts_default;
>@@ -211,6 +215,8 @@ index_opts_cmp(const struct index_opts *o1, const struct index_opts *o2)
>  return o1->bloom_fpr < o2->bloom_fpr ? -1 : 1;
>  if (o1->func_id != o2->func_id)
>  return o1->func_id - o2->func_id;
>+ if (o1->hint != o2->hint)
>+ return o1->hint - o2->hint;
>  return 0;
> }
> 
>diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
>index 1131af7..9cc1289 100644
>--- a/src/box/lua/schema.lua
>+++ b/src/box/lua/schema.lua
>@@ -1000,8 +1000,31 @@ local index_options = {
>     page_size = 'number',
>     bloom_fpr = 'number',
>     func = 'number, string',
>+ hint = 'boolean',
> }
> 
>+local function jsonpaths_from_idx_parts(parts)
>+ local paths = {}
>+
>+ for _, part in pairs(parts) do
>+ if type(part.path) == 'string' then
>+ table.insert(paths, part.path)
>+ end
>+ end
>+
>+ return paths
>+end
>+
>+local function is_multikey_index(parts)
>+ for _, path in pairs(jsonpaths_from_idx_parts(parts)) do
>+ if path:find('[*]', 1, true) then
>+ return true
>+ end
>+ end
>+
>+ return false
>+end
>+
> --
> -- check_param_table() template for alter index,
> -- includes all index options.
>@@ -1076,6 +1099,15 @@ box.schema.index.create = function(space_id, name, options)
>         options_defaults = {}
>     end
>     options = update_param_table(options, options_defaults)
>+ if options.hint and
>+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
>+ box.error(box.error.MODIFY_INDEX, name, space.name,
>+ "hint is only reasonable with memtx tree index")
>+ end
>+ if options.hint and options.func then
>+ box.error(box.error.MODIFY_INDEX, name, space.name,
>+ "functional index can't use hints")
>+ end
> 
>     local _index = box.space[box.schema.INDEX_ID]
>     local _vindex = box.space[box.schema.VINDEX_ID]
>@@ -1115,6 +1147,7 @@ box.schema.index.create = function(space_id, name, options)
>             run_size_ratio = options.run_size_ratio,
>             bloom_fpr = options.bloom_fpr,
>             func = options.func,
>+ hint = options.hint,
>     }
>     local field_type_aliases = {
>         num = 'unsigned'; -- Deprecated since 1.7.2
>@@ -1135,6 +1168,10 @@ box.schema.index.create = function(space_id, name, options)
>     if parts_can_be_simplified then
>         parts = simplify_index_parts(parts)
>     end
>+ if options.hint and is_multikey_index(parts) then
>+ box.error(box.error.MODIFY_INDEX, name, space.name,
>+ "multikey index can't use hints")
>+ end
>     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
>         index_opts.func = func_id_by_name(index_opts.func)
>     end
>@@ -1253,6 +1290,17 @@ box.schema.index.alter = function(space_id, index_id, options)
>             index_opts[k] = options[k]
>         end
>     end
>+ if options.hint and
>+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
>+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>+ space.name,
>+ "hint is only reasonable with memtx tree index")
>+ end
>+ if options.hint and options.func then
>+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>+ space.name,
>+ "functional index can't use hints")
>+ end
>     if options.parts then
>         local parts_can_be_simplified
>         parts, parts_can_be_simplified =
>@@ -1262,6 +1310,11 @@ box.schema.index.alter = function(space_id, index_id, options)
>             parts = simplify_index_parts(parts)
>         end
>     end
>+ if options.hint and is_multikey_index(parts) then
>+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>+ space.name,
>+ "multikey index can't use hints")
>+ end
>     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
>         index_opts.func = func_id_by_name(index_opts.func)
>     end
>diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
>index 177c588..1ea993c 100644
>--- a/src/box/lua/space.cc
>+++ b/src/box/lua/space.cc
>@@ -344,6 +344,13 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
>  lua_pushnumber(L, index_opts->dimension);
>  lua_setfield(L, -2, "dimension");
>  }
>+ if (space_is_memtx(space) && index_def->type == TREE) {
>+ lua_pushboolean(L, index_opts->hint);
>+ lua_setfield(L, -2, "hint");
>+ } else {
>+ lua_pushnil(L);
>+ lua_setfield(L, -2, "hint");
>+ }
> 
>  if (index_opts->func_id > 0) {
>  lua_pushstring(L, "func");
>diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
>index  8147557 ..43000ba 100644
>--- a/src/box/memtx_engine.c
>+++ b/src/box/memtx_engine.c
>@@ -1398,6 +1398,8 @@ memtx_index_def_change_requires_rebuild(struct index *index,
>  return true;
>  if (old_def->opts.func_id != new_def->opts.func_id)
>  return true;
>+ if (old_def->opts.hint != new_def->opts.hint)
>+ return true;
> 
>  const struct key_def *old_cmp_def, *new_cmp_def;
>  if (index_depends_on_pk(index)) {
>diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
>index d3b993b..17d58c5 100644
>--- a/src/box/memtx_tree.cc
>+++ b/src/box/memtx_tree.cc
>@@ -45,23 +45,51 @@
> /**
>  * Struct that is used as a key in BPS tree definition.
>  */
>-struct memtx_tree_key_data {
>+struct memtx_tree_key_data_common {
>  /** Sequence of msgpacked search fields. */
>  const char *key;
>  /** Number of msgpacked search fields. */
>  uint32_t part_count;
>+};
>+
>+template <int USE_HINT>
>+struct memtx_tree_key_data;
>+
>+template <>
>+struct memtx_tree_key_data<0> : memtx_tree_key_data_common {
>+ static constexpr hint_t hint = HINT_NONE;
>+ void set_hint(hint_t) { assert(false); }
>+};
>+
>+template <>
>+struct memtx_tree_key_data<1> : memtx_tree_key_data_common {
>  /** Comparison hint, see tuple_hint(). */
>  hint_t hint;
>+ void set_hint(hint_t h) { hint = h; }
> };
> 
> /**
>  * Struct that is used as a elem in BPS tree definition.
>  */
>-struct memtx_tree_data {
>+struct memtx_tree_data_common {
>  /* Tuple that this node is represents. */
>  struct tuple *tuple;
>+};
>+
>+template <int USE_HINT>
>+struct memtx_tree_data;
>+
>+template <>
>+struct memtx_tree_data<0> : memtx_tree_data_common {
>+ static constexpr hint_t hint = HINT_NONE;
>+ void set_hint(hint_t) { assert(false); }
>+};
>+
>+template <>
>+struct memtx_tree_data<1> : memtx_tree_data<0> {
>  /** Comparison hint, see key_hint(). */
>  hint_t hint;
>+ void set_hint(hint_t h) { hint = h; }
> };
> 
> /**
>@@ -73,8 +101,8 @@ struct memtx_tree_data {
>  * @retval false - Otherwise.
>  */
> static bool
>-memtx_tree_data_is_equal(const struct memtx_tree_data *a,
>- const struct memtx_tree_data *b)
>+memtx_tree_data_is_equal(const struct memtx_tree_data_common *a,
>+ const struct memtx_tree_data_common *b)
> {
>  return a->tuple == b->tuple;
> }
>@@ -89,12 +117,28 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
>  (b)->part_count, (b)->hint, arg)
> #define BPS_TREE_IS_IDENTICAL(a, b) memtx_tree_data_is_equal(&a, &b)
> #define BPS_TREE_NO_DEBUG 1
>-#define bps_tree_elem_t struct memtx_tree_data
>-#define bps_tree_key_t struct memtx_tree_key_data *
> #define bps_tree_arg_t struct key_def *
> 
>+#define BPS_TREE_NAMESPACE NS_NO_HINT
>+#define bps_tree_elem_t struct memtx_tree_data<0>
>+#define bps_tree_key_t struct memtx_tree_key_data<0> *
>+
> #include "salad/bps_tree.h"
> 
>+#undef BPS_TREE_NAMESPACE
>+#undef bps_tree_elem_t
>+#undef bps_tree_key_t
>+
>+#define BPS_TREE_NAMESPACE NS_USE_HINT
>+#define bps_tree_elem_t struct memtx_tree_data<1>
>+#define bps_tree_key_t struct memtx_tree_key_data<1> *
>+
>+#include "salad/bps_tree.h"
>+
>+#undef BPS_TREE_NAMESPACE
>+#undef bps_tree_elem_t
>+#undef bps_tree_key_t
>+
> #undef BPS_TREE_NAME
> #undef BPS_TREE_BLOCK_SIZE
> #undef BPS_TREE_EXTENT_SIZE
>@@ -102,66 +146,119 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
> #undef BPS_TREE_COMPARE_KEY
> #undef BPS_TREE_IS_IDENTICAL
> #undef BPS_TREE_NO_DEBUG
>-#undef bps_tree_elem_t
>-#undef bps_tree_key_t
> #undef bps_tree_arg_t
> 
>+using namespace NS_NO_HINT;
>+using namespace NS_USE_HINT;
>+
>+template <int USE_HINT>
>+struct memtx_tree_selector;
>+
>+template <>
>+struct memtx_tree_selector<0> : NS_NO_HINT::memtx_tree {};
>+
>+template <>
>+struct memtx_tree_selector<1> : NS_USE_HINT::memtx_tree {};
>+
>+template <int USE_HINT>
>+using memtx_tree_t = struct memtx_tree_selector<USE_HINT>;
>+
>+template <int USE_HINT>
>+struct memtx_tree_iterator_selector;
>+
>+template <>
>+struct memtx_tree_iterator_selector<0> {
>+ using type = NS_NO_HINT::memtx_tree_iterator;
>+};
>+
>+template <>
>+struct memtx_tree_iterator_selector<1> {
>+ using type = NS_USE_HINT::memtx_tree_iterator;
>+};
>+
>+template <int USE_HINT>
>+using memtx_tree_iterator_t = typename memtx_tree_iterator_selector<USE_HINT>::type;
>+
>+static void
>+invalidate_tree_iterator(NS_NO_HINT::memtx_tree_iterator *itr)
>+{
>+ *itr = NS_NO_HINT::memtx_tree_invalid_iterator();
>+}
>+
>+static void
>+invalidate_tree_iterator(NS_USE_HINT::memtx_tree_iterator *itr)
>+{
>+ *itr = NS_USE_HINT::memtx_tree_invalid_iterator();
>+}
>+
>+template <int USE_HINT>
> struct memtx_tree_index {
>  struct index base;
>- struct memtx_tree tree;
>- struct memtx_tree_data *build_array;
>+ memtx_tree_t<USE_HINT> tree;
>+ struct memtx_tree_data<USE_HINT> *build_array;
>  size_t build_array_size, build_array_alloc_size;
>  struct memtx_gc_task gc_task;
>- struct memtx_tree_iterator gc_iterator;
>+ memtx_tree_iterator_t<USE_HINT> gc_iterator;
> };
> 
> /* {{{ Utilities. *************************************************/
> 
>+template <class TREE>
> static inline struct key_def *
>-memtx_tree_cmp_def(struct memtx_tree *tree)
>+memtx_tree_cmp_def(TREE *tree)
> {
>  return tree->arg;
> }
> 
>+template <int USE_HINT>
> static int
> memtx_tree_qcompare(const void* a, const void *b, void *c)
> {
>- const struct memtx_tree_data *data_a = (struct memtx_tree_data *)a;
>- const struct memtx_tree_data *data_b = (struct memtx_tree_data *)b;
>+ const struct memtx_tree_data<USE_HINT> *data_a =
>+ (struct memtx_tree_data<USE_HINT> *)a;
>+ const struct memtx_tree_data<USE_HINT> *data_b =
>+ (struct memtx_tree_data<USE_HINT> *)b;
>  struct key_def *key_def = (struct key_def *)c;
>  return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
>  data_b->hint, key_def);
> }
> 
> /* {{{ MemtxTree Iterators ****************************************/
>+template <int USE_HINT>
> struct tree_iterator {
>  struct iterator base;
>- struct memtx_tree_iterator tree_iterator;
>+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
>  enum iterator_type type;
>- struct memtx_tree_key_data key_data;
>- struct memtx_tree_data current;
>+ struct memtx_tree_key_data<USE_HINT> key_data;
>+ struct memtx_tree_data<USE_HINT> current;
>  /** Memory pool the iterator was allocated from. */
>  struct mempool *pool;
> };
> 
>-static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,
>- "sizeof(struct tree_iterator) must be less than or equal "
>+static_assert(sizeof(struct tree_iterator<0>) <= MEMTX_ITERATOR_SIZE,
>+ "sizeof(struct tree_iterator<0>) must be less than or equal "
>+ "to MEMTX_ITERATOR_SIZE");
>+static_assert(sizeof(struct tree_iterator<1>) <= MEMTX_ITERATOR_SIZE,
>+ "sizeof(struct tree_iterator<1>) must be less than or equal "
>  "to MEMTX_ITERATOR_SIZE");
> 
>+template <int USE_HINT>
> static void
> tree_iterator_free(struct iterator *iterator);
> 
>-static inline struct tree_iterator *
>-tree_iterator(struct iterator *it)
>+template <int USE_HINT>
>+static inline struct tree_iterator<USE_HINT> *
>+get_tree_iterator(struct iterator *it)
> {
>- assert(it->free == tree_iterator_free);
>- return (struct tree_iterator *) it;
>+ assert(it->free == &tree_iterator_free<USE_HINT>);
>+ return (struct tree_iterator<USE_HINT> *) it;
> }
> 
>+template <int USE_HINT>
> static void
> tree_iterator_free(struct iterator *iterator)
> {
>- struct tree_iterator *it = tree_iterator(iterator);
>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>  struct tuple *tuple = it->current.tuple;
>  if (tuple != NULL)
>  tuple_unref(tuple);
>@@ -176,14 +273,15 @@ tree_iterator_dummie(struct iterator *iterator, struct tuple **ret)
>  return 0;
> }
> 
>+template <int USE_HINT>
> static int
> tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
> {
>- struct memtx_tree_index *index =
>- (struct memtx_tree_index *)iterator->index;
>- struct tree_iterator *it = tree_iterator(iterator);
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>  assert(it->current.tuple != NULL);
>- struct memtx_tree_data *check =
>+ struct memtx_tree_data<USE_HINT> *check =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
>@@ -192,7 +290,7 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
>  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
>  }
>  tuple_unref(it->current.tuple);
>- struct memtx_tree_data *res =
>+ struct memtx_tree_data<USE_HINT> *res =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  if (res == NULL) {
>  iterator->next = tree_iterator_dummie;
>@@ -206,14 +304,15 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
>  return 0;
> }
> 
>+template <int USE_HINT>
> static int
> tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
> {
>- struct memtx_tree_index *index =
>- (struct memtx_tree_index *)iterator->index;
>- struct tree_iterator *it = tree_iterator(iterator);
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>  assert(it->current.tuple != NULL);
>- struct memtx_tree_data *check =
>+ struct memtx_tree_data<USE_HINT> *check =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
>@@ -221,7 +320,7 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
>  }
>  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
>  tuple_unref(it->current.tuple);
>- struct memtx_tree_data *res =
>+ struct memtx_tree_data<USE_HINT> *res =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  if (!res) {
>  iterator->next = tree_iterator_dummie;
>@@ -235,14 +334,15 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
>  return 0;
> }
> 
>+template <int USE_HINT>
> static int
> tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
> {
>- struct memtx_tree_index *index =
>- (struct memtx_tree_index *)iterator->index;
>- struct tree_iterator *it = tree_iterator(iterator);
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>  assert(it->current.tuple != NULL);
>- struct memtx_tree_data *check =
>+ struct memtx_tree_data<USE_HINT> *check =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
>@@ -251,7 +351,7 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
>  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
>  }
>  tuple_unref(it->current.tuple);
>- struct memtx_tree_data *res =
>+ struct memtx_tree_data<USE_HINT> *res =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  /* Use user key def to save a few loops. */
>  if (res == NULL ||
>@@ -271,14 +371,15 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
>  return 0;
> }
> 
>+template <int USE_HINT>
> static int
> tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
> {
>- struct memtx_tree_index *index =
>- (struct memtx_tree_index *)iterator->index;
>- struct tree_iterator *it = tree_iterator(iterator);
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>  assert(it->current.tuple != NULL);
>- struct memtx_tree_data *check =
>+ struct memtx_tree_data<USE_HINT> *check =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
>@@ -286,7 +387,7 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
>  }
>  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
>  tuple_unref(it->current.tuple);
>- struct memtx_tree_data *res =
>+ struct memtx_tree_data<USE_HINT> *res =
>  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>  /* Use user key def to save a few loops. */
>  if (res == NULL ||
>@@ -307,28 +408,30 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
> }
> 
> #define WRAP_ITERATOR_METHOD(name) \
>+template <int USE_HINT> \
> static int \
> name(struct iterator *iterator, struct tuple **ret) \
> { \
>- struct memtx_tree *tree = \
>- &((struct memtx_tree_index *)iterator->index)->tree; \
>- struct tree_iterator *it = tree_iterator(iterator); \
>- struct memtx_tree_iterator *ti = &it->tree_iterator; \
>+ memtx_tree_t<USE_HINT> *tree = \
>+ &((struct memtx_tree_index<USE_HINT> *)iterator->index)->tree; \
>+ struct tree_iterator<USE_HINT> *it = \
>+ get_tree_iterator<USE_HINT>(iterator); \
>+ memtx_tree_iterator_t<USE_HINT> *ti = &it->tree_iterator; \
>  uint32_t iid = iterator->index->def->iid; \
>  bool is_multikey = iterator->index->def->key_def->is_multikey; \
>  struct txn *txn = in_txn(); \
>  struct space *space = space_by_id(iterator->space_id); \
>  bool is_rw = txn != NULL; \
>  do { \
>- int rc = name##_base(iterator, ret); \
>+ int rc = name##_base<USE_HINT>(iterator, ret); \
>  if (rc != 0 || *ret == NULL) \
>  return rc; \
>  uint32_t mk_index = 0; \
>  if (is_multikey) { \
>- struct memtx_tree_data *check = \
>+ struct memtx_tree_data<USE_HINT> *check = \
>  memtx_tree_iterator_get_elem(tree, ti); \
>  assert(check != NULL); \
>- mk_index = check->hint; \
>+ mk_index = (uint32_t)check->hint; \
>  } \
>  *ret = memtx_tx_tuple_clarify(txn, space, *ret, \
>  iid, mk_index, is_rw); \
>@@ -347,27 +450,28 @@ WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);
> 
> #undef WRAP_ITERATOR_METHOD
> 
>+template <int USE_HINT>
> static void
>-tree_iterator_set_next_method(struct tree_iterator *it)
>+tree_iterator_set_next_method(struct tree_iterator<USE_HINT> *it)
> {
>  assert(it->current.tuple != NULL);
>  switch (it->type) {
>  case ITER_EQ:
>- it->base.next = tree_iterator_next_equal;
>+ it->base.next = tree_iterator_next_equal<USE_HINT>;
>  break;
>  case ITER_REQ:
>- it->base.next = tree_iterator_prev_equal;
>+ it->base.next = tree_iterator_prev_equal<USE_HINT>;
>  break;
>  case ITER_ALL:
>- it->base.next = tree_iterator_next;
>+ it->base.next = tree_iterator_next<USE_HINT>;
>  break;
>  case ITER_LT:
>  case ITER_LE:
>- it->base.next = tree_iterator_prev;
>+ it->base.next = tree_iterator_prev<USE_HINT>;
>  break;
>  case ITER_GE:
>  case ITER_GT:
>- it->base.next = tree_iterator_next;
>+ it->base.next = tree_iterator_next<USE_HINT>;
>  break;
>  default:
>  /* The type was checked in initIterator */
>@@ -375,15 +479,16 @@ tree_iterator_set_next_method(struct tree_iterator *it)
>  }
> }
> 
>+template <int USE_HINT>
> static int
> tree_iterator_start(struct iterator *iterator, struct tuple **ret)
> {
>  *ret = NULL;
>- struct memtx_tree_index *index =
>- (struct memtx_tree_index *)iterator->index;
>- struct tree_iterator *it = tree_iterator(iterator);
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>  it->base.next = tree_iterator_dummie;
>- struct memtx_tree *tree = &index->tree;
>+ memtx_tree_t<USE_HINT> *tree = &index->tree;
>  enum iterator_type type = it->type;
>  bool exact = false;
>  assert(it->current.tuple == NULL);
>@@ -423,8 +528,8 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
>  }
>  }
> 
>- struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,
>- &it->tree_iterator);
>+ struct memtx_tree_data<USE_HINT> *res =
>+ memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
>  if (!res)
>  return 0;
>  *ret = res->tuple;
>@@ -454,14 +559,16 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
> 
> /* {{{ MemtxTree **********************************************************/
> 
>+template <int USE_HINT>
> static void
>-memtx_tree_index_free(struct memtx_tree_index *index)
>+memtx_tree_index_free(struct memtx_tree_index<USE_HINT> *index)
> {
>  memtx_tree_destroy(&index->tree);
>  free(index->build_array);
>  free(index);
> }
> 
>+template <int USE_HINT>
> static void
> memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
> {
>@@ -475,14 +582,14 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
>  enum { YIELD_LOOPS = 10 };
> #endif
> 
>- struct memtx_tree_index *index = container_of(task,
>- struct memtx_tree_index, gc_task);
>- struct memtx_tree *tree = &index->tree;
>- struct memtx_tree_iterator *itr = &index->gc_iterator;
>+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
>+ struct memtx_tree_index<USE_HINT>, gc_task);
>+ memtx_tree_t<USE_HINT> *tree = &index->tree;
>+ memtx_tree_iterator_t<USE_HINT> *itr = &index->gc_iterator;
> 
>  unsigned int loops = 0;
>  while (!memtx_tree_iterator_is_invalid(itr)) {
>- struct memtx_tree_data *res =
>+ struct memtx_tree_data<USE_HINT> *res =
>  memtx_tree_iterator_get_elem(tree, itr);
>  memtx_tree_iterator_next(tree, itr);
>  tuple_unref(res->tuple);
>@@ -494,23 +601,32 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
>  *done = true;
> }
> 
>+template <int USE_HINT>
> static void
> memtx_tree_index_gc_free(struct memtx_gc_task *task)
> {
>- struct memtx_tree_index *index = container_of(task,
>- struct memtx_tree_index, gc_task);
>+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
>+ struct memtx_tree_index<USE_HINT>, gc_task);
>  memtx_tree_index_free(index);
> }
> 
>-static const struct memtx_gc_task_vtab memtx_tree_index_gc_vtab = {
>- .run = memtx_tree_index_gc_run,
>- .free = memtx_tree_index_gc_free,
>+template <int USE_HINT>
>+static struct memtx_gc_task_vtab * get_memtx_tree_index_gc_vtab()
>+{
>+ static memtx_gc_task_vtab tab =
>+ {
>+ .run = memtx_tree_index_gc_run<USE_HINT>,
>+ .free = memtx_tree_index_gc_free<USE_HINT>,
>+ };
>+ return &tab;
> };
> 
>+template <int USE_HINT>
> static void
> memtx_tree_index_destroy(struct index *base)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
>  if (base->def->iid == 0) {
>  /*
>@@ -518,7 +634,7 @@ memtx_tree_index_destroy(struct index *base)
>  * in the index, which may take a while. Schedule a
>  * background task in order not to block tx thread.
>  */
>- index->gc_task.vtab = &memtx_tree_index_gc_vtab;
>+ index->gc_task.vtab = get_memtx_tree_index_gc_vtab<USE_HINT>();
>  index->gc_iterator = memtx_tree_iterator_first(&index->tree);
>  memtx_engine_schedule_gc(memtx, &index->gc_task);
>  } else {
>@@ -530,10 +646,12 @@ memtx_tree_index_destroy(struct index *base)
>  }
> }
> 
>+template <int USE_HINT>
> static void
> memtx_tree_index_update_def(struct index *base)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  struct index_def *def = base->def;
>  /*
>  * We use extended key def for non-unique and nullable
>@@ -553,51 +671,62 @@ memtx_tree_index_depends_on_pk(struct index *base)
>  return !def->opts.is_unique || def->key_def->is_nullable;
> }
> 
>+template <int USE_HINT>
> static ssize_t
> memtx_tree_index_size(struct index *base)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  return memtx_tree_size(&index->tree);
> }
> 
>+template <int USE_HINT>
> static ssize_t
> memtx_tree_index_bsize(struct index *base)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  return memtx_tree_mem_used(&index->tree);
> }
> 
>+template <int USE_HINT>
> static int
> memtx_tree_index_random(struct index *base, uint32_t rnd, struct tuple **result)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>- struct memtx_tree_data *res = memtx_tree_random(&index->tree, rnd);
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>+ struct memtx_tree_data<USE_HINT> *res = memtx_tree_random(&index->tree, rnd);
>  *result = res != NULL ? res->tuple : NULL;
>  return 0;
> }
> 
>+template <int USE_HINT>
> static ssize_t
> memtx_tree_index_count(struct index *base, enum iterator_type type,
>  const char *key, uint32_t part_count)
> {
>  if (type == ITER_ALL)
>- return memtx_tree_index_size(base); /* optimization */
>+ return memtx_tree_index_size<USE_HINT>(base); /* optimization */
>  return generic_index_count(base, type, key, part_count);
> }
> 
>+template <int USE_HINT>
> static int
> memtx_tree_index_get(struct index *base, const char *key,
>  uint32_t part_count, struct tuple **result)
> {
>  assert(base->def->opts.is_unique &&
>  part_count == base->def->key_def->part_count);
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>- struct memtx_tree_key_data key_data;
>+ struct memtx_tree_key_data<USE_HINT> key_data;
>  key_data.key = key;
>  key_data.part_count = part_count;
>- key_data.hint = key_hint(key, part_count, cmp_def);
>- struct memtx_tree_data *res = memtx_tree_find(&index->tree, &key_data);
>+ if (USE_HINT)
>+ key_data.set_hint(key_hint(key, part_count, cmp_def));
>+ struct memtx_tree_data<USE_HINT> *res =
>+ memtx_tree_find(&index->tree, &key_data);
>  if (res == NULL) {
>  *result = NULL;
>  return 0;
>@@ -611,18 +740,21 @@ memtx_tree_index_get(struct index *base, const char *key,
>  return 0;
> }
> 
>+template <int USE_HINT>
> static int
> memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
>  struct tuple *new_tuple, enum dup_replace_mode mode,
>  struct tuple **result)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>  if (new_tuple) {
>- struct memtx_tree_data new_data;
>+ struct memtx_tree_data<USE_HINT> new_data;
>  new_data.tuple = new_tuple;
>- new_data.hint = tuple_hint(new_tuple, cmp_def);
>- struct memtx_tree_data dup_data;
>+ if (USE_HINT)
>+ new_data.set_hint(tuple_hint(new_tuple, cmp_def));
>+ struct memtx_tree_data<USE_HINT> dup_data;
>  dup_data.tuple = NULL;
> 
>  /* Try to optimistically replace the new_tuple. */
>@@ -652,9 +784,10 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
>  }
>  }
>  if (old_tuple) {
>- struct memtx_tree_data old_data;
>+ struct memtx_tree_data<USE_HINT> old_data;
>  old_data.tuple = old_tuple;
>- old_data.hint = tuple_hint(old_tuple, cmp_def);
>+ if (USE_HINT)
>+ old_data.set_hint(tuple_hint(old_tuple, cmp_def));
>  memtx_tree_delete(&index->tree, old_data);
>  }
>  *result = old_tuple;
>@@ -667,13 +800,13 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
>  * by all it's multikey indexes.
>  */
> static int
>-memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
>+memtx_tree_index_replace_multikey_one(struct memtx_tree_index<1> *index,
>  struct tuple *old_tuple, struct tuple *new_tuple,
>  enum dup_replace_mode mode, hint_t hint,
>- struct memtx_tree_data *replaced_data,
>+ struct memtx_tree_data<1> *replaced_data,
>  bool *is_multikey_conflict)
> {
>- struct memtx_tree_data new_data, dup_data;
>+ struct memtx_tree_data<1> new_data, dup_data;
>  new_data.tuple = new_tuple;
>  new_data.hint = hint;
>  dup_data.tuple = NULL;
>@@ -720,11 +853,11 @@ memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
>  * delete operation is fault-tolerant.
>  */
> static void
>-memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index *index,
>+memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index<1> *index,
>  struct tuple *new_tuple, struct tuple *replaced_tuple,
>  int err_multikey_idx)
> {
>- struct memtx_tree_data data;
>+ struct memtx_tree_data<1> data;
>  if (replaced_tuple != NULL) {
>  /* Restore replaced tuple index occurrences. */
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>@@ -798,7 +931,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
>  struct tuple *new_tuple, enum dup_replace_mode mode,
>  struct tuple **result)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>  *result = NULL;
>  if (new_tuple != NULL) {
>@@ -808,7 +941,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
>  for (; (uint32_t) multikey_idx < multikey_count;
>  multikey_idx++) {
>  bool is_multikey_conflict;
>- struct memtx_tree_data replaced_data;
>+ struct memtx_tree_data<1> replaced_data;
>  err = memtx_tree_index_replace_multikey_one(index,
>  old_tuple, new_tuple, mode,
>  multikey_idx, &replaced_data,
>@@ -833,7 +966,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
>  }
>  }
>  if (old_tuple != NULL) {
>- struct memtx_tree_data data;
>+ struct memtx_tree_data<1> data;
>  data.tuple = old_tuple;
>  uint32_t multikey_count =
>  tuple_multikey_count(old_tuple, cmp_def);
>@@ -865,7 +998,7 @@ struct func_key_undo {
>  /** A link to organize entries in list. */
>  struct rlist link;
>  /** An inserted record copy. */
>- struct memtx_tree_data key;
>+ struct memtx_tree_data<1> key;
> };
> 
> /** Allocate a new func_key_undo on given region. */
>@@ -888,7 +1021,7 @@ func_key_undo_new(struct region *region)
>  * return a given index object in it's original state.
>  */
> static void
>-memtx_tree_func_index_replace_rollback(struct memtx_tree_index *index,
>+memtx_tree_func_index_replace_rollback(struct memtx_tree_index<1> *index,
>  struct rlist *old_keys,
>  struct rlist *new_keys)
> {
>@@ -919,7 +1052,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
>  struct tuple *new_tuple, enum dup_replace_mode mode,
>  struct tuple **result)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>  struct index_def *index_def = index->base.def;
>  assert(index_def->key_def->for_func_index);
> 
>@@ -952,7 +1085,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
>  undo->key.hint = (hint_t)key;
>  rlist_add(&new_keys, &undo->link);
>  bool is_multikey_conflict;
>- struct memtx_tree_data old_data;
>+ struct memtx_tree_data<1> old_data;
>  old_data.tuple = NULL;
>  err = memtx_tree_index_replace_multikey_one(index,
>  old_tuple, new_tuple,
>@@ -1015,7 +1148,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
>  if (key_list_iterator_create(&it, old_tuple, index_def, false,
>  func_index_key_dummy_alloc) != 0)
>  goto end;
>- struct memtx_tree_data data, deleted_data;
>+ struct memtx_tree_data<1> data, deleted_data;
>  data.tuple = old_tuple;
>  const char *key;
>  while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
>@@ -1040,11 +1173,13 @@ end:
>  return rc;
> }
> 
>+template <int USE_HINT>
> static struct iterator *
> memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
>  const char *key, uint32_t part_count)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> 
>@@ -1064,42 +1199,47 @@ memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
>  key = NULL;
>  }
> 
>- struct tree_iterator *it = (struct tree_iterator *)
>+ struct tree_iterator<USE_HINT> *it = (struct tree_iterator<USE_HINT> *)
>  mempool_alloc(&memtx->iterator_pool);
>  if (it == NULL) {
>- diag_set(OutOfMemory, sizeof(struct tree_iterator),
>+ diag_set(OutOfMemory, sizeof(struct tree_iterator<USE_HINT>),
>  "memtx_tree_index", "iterator");
>  return NULL;
>  }
>  iterator_create(&it->base, base);
>  it->pool = &memtx->iterator_pool;
>- it->base.next = tree_iterator_start;
>- it->base.free = tree_iterator_free;
>+ it->base.next = tree_iterator_start<USE_HINT>;
>+ it->base.free = tree_iterator_free<USE_HINT>;
>  it->type = type;
>  it->key_data.key = key;
>  it->key_data.part_count = part_count;
>- it->key_data.hint = key_hint(key, part_count, cmp_def);
>- it->tree_iterator = memtx_tree_invalid_iterator();
>+ if (USE_HINT)
>+ it->key_data.set_hint(key_hint(key, part_count, cmp_def));
>+ invalidate_tree_iterator(&it->tree_iterator);
>  it->current.tuple = NULL;
>  return (struct iterator *)it;
> }
> 
>+template <int USE_HINT>
> static void
> memtx_tree_index_begin_build(struct index *base)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  assert(memtx_tree_size(&index->tree) == 0);
>  (void)index;
> }
> 
>+template <int USE_HINT>
> static int
> memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  if (size_hint < index->build_array_alloc_size)
>  return 0;
>- struct memtx_tree_data *tmp =
>- (struct memtx_tree_data *)
>+ struct memtx_tree_data<USE_HINT> *tmp =
>+ (struct memtx_tree_data<USE_HINT> *)
>  realloc(index->build_array, size_hint * sizeof(*tmp));
>  if (tmp == NULL) {
>  diag_set(OutOfMemory, size_hint * sizeof(*tmp),
>@@ -1111,14 +1251,15 @@ memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
>  return 0;
> }
> 
>+template <int USE_HINT>
> /** Initialize the next element of the index build_array. */
> static int
>-memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>+memtx_tree_index_build_array_append(struct memtx_tree_index<USE_HINT> *index,
>  struct tuple *tuple, hint_t hint)
> {
>  if (index->build_array == NULL) {
>  index->build_array =
>- (struct memtx_tree_data *)malloc(MEMTX_EXTENT_SIZE);
>+ (struct memtx_tree_data<USE_HINT> *)malloc(MEMTX_EXTENT_SIZE);
>  if (index->build_array == NULL) {
>  diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
>  "memtx_tree_index", "build_next");
>@@ -1131,8 +1272,8 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>  if (index->build_array_size == index->build_array_alloc_size) {
>  index->build_array_alloc_size = index->build_array_alloc_size +
>  DIV_ROUND_UP(index->build_array_alloc_size, 2);
>- struct memtx_tree_data *tmp =
>- (struct memtx_tree_data *)realloc(index->build_array,
>+ struct memtx_tree_data<USE_HINT> *tmp =
>+ (struct memtx_tree_data<USE_HINT> *)realloc(index->build_array,
>  index->build_array_alloc_size * sizeof(*tmp));
>  if (tmp == NULL) {
>  diag_set(OutOfMemory, index->build_array_alloc_size *
>@@ -1141,17 +1282,20 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>  }
>  index->build_array = tmp;
>  }
>- struct memtx_tree_data *elem =
>+ struct memtx_tree_data<USE_HINT> *elem =
>  &index->build_array[index->build_array_size++];
>  elem->tuple = tuple;
>- elem->hint = hint;
>+ if (USE_HINT)
>+ elem->set_hint(hint);
>  return 0;
> }
> 
>+template <int USE_HINT>
> static int
> memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>  return memtx_tree_index_build_array_append(index, tuple,
>  tuple_hint(tuple, cmp_def));
>@@ -1160,7 +1304,7 @@ memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
> static int
> memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>  uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);
>  for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;
>@@ -1175,7 +1319,7 @@ memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
> static int
> memtx_tree_func_index_build_next(struct index *base, struct tuple *tuple)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>  struct index_def *index_def = index->base.def;
>  assert(index_def->key_def->for_func_index);
> 
>@@ -1211,8 +1355,9 @@ error:
>  * of equal tuples (in terms of index's cmp_def and have same
>  * tuple pointer). The build_array is expected to be sorted.
>  */
>+template <int USE_HINT>
> static void
>-memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
>+memtx_tree_index_build_array_deduplicate(struct memtx_tree_index<USE_HINT> *index,
>  void (*destroy)(struct tuple *tuple, const char *hint))
> {
>  if (index->build_array_size == 0)
>@@ -1246,13 +1391,16 @@ memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
>  index->build_array_size = w_idx + 1;
> }
> 
>+template <int USE_HINT>
> static void
> memtx_tree_index_end_build(struct index *base)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>  qsort_arg(index->build_array, index->build_array_size,
>- sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);
>+ sizeof(index->build_array[0]),
>+ memtx_tree_qcompare<USE_HINT>, cmp_def);
>  if (cmp_def->is_multikey) {
>  /*
>  * Multikey index may have equal(in terms of
>@@ -1261,9 +1409,9 @@ memtx_tree_index_end_build(struct index *base)
>  * the following memtx_tree_build assumes that
>  * all keys are unique.
>  */
>- memtx_tree_index_build_array_deduplicate(index, NULL);
>+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index, NULL);
>  } else if (cmp_def->for_func_index) {
>- memtx_tree_index_build_array_deduplicate(index,
>+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index,
>  tuple_chunk_delete);
>  }
>  memtx_tree_build(&index->tree, index->build_array,
>@@ -1275,19 +1423,21 @@ memtx_tree_index_end_build(struct index *base)
>  index->build_array_alloc_size = 0;
> }
> 
>+template <int USE_HINT>
> struct tree_snapshot_iterator {
>  struct snapshot_iterator base;
>- struct memtx_tree_index *index;
>- struct memtx_tree_iterator tree_iterator;
>+ struct memtx_tree_index<USE_HINT> *index;
>+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
>  struct memtx_tx_snapshot_cleaner cleaner;
> };
> 
>+template <int USE_HINT>
> static void
> tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
> {
>- assert(iterator->free == tree_snapshot_iterator_free);
>- struct tree_snapshot_iterator *it =
>- (struct tree_snapshot_iterator *)iterator;
>+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
>+ struct tree_snapshot_iterator<USE_HINT> *it =
>+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
>  memtx_leave_delayed_free_mode((struct memtx_engine *)
>  it->index->base.engine);
>  memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);
>@@ -1296,17 +1446,18 @@ tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
>  free(iterator);
> }
> 
>+template <int USE_HINT>
> static int
> tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
>  const char **data, uint32_t *size)
> {
>- assert(iterator->free == tree_snapshot_iterator_free);
>- struct tree_snapshot_iterator *it =
>- (struct tree_snapshot_iterator *)iterator;
>- struct memtx_tree *tree = &it->index->tree;
>+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
>+ struct tree_snapshot_iterator<USE_HINT> *it =
>+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
>+ memtx_tree_t<USE_HINT> *tree = &it->index->tree;
> 
>  while (true) {
>- struct memtx_tree_data *res =
>+ struct memtx_tree_data<USE_HINT> *res =
>  memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
> 
>  if (res == NULL) {
>@@ -1333,14 +1484,18 @@ tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
>  * index modifications will not affect the iteration results.
>  * Must be destroyed by iterator->free after usage.
>  */
>+template <int USE_HINT>
> static struct snapshot_iterator *
> memtx_tree_index_create_snapshot_iterator(struct index *base)
> {
>- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>- struct tree_snapshot_iterator *it =
>- (struct tree_snapshot_iterator *) calloc(1, sizeof(*it));
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)base;
>+ struct tree_snapshot_iterator<USE_HINT> *it =
>+ (struct tree_snapshot_iterator<USE_HINT> *)
>+ calloc(1, sizeof(*it));
>  if (it == NULL) {
>- diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),
>+ diag_set(OutOfMemory,
>+ sizeof(struct tree_snapshot_iterator<USE_HINT>),
>  "memtx_tree_index", "create_snapshot_iterator");
>  return NULL;
>  }
>@@ -1352,8 +1507,8 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
>  return NULL;
>  }
> 
>- it->base.free = tree_snapshot_iterator_free;
>- it->base.next = tree_snapshot_iterator_next;
>+ it->base.free = tree_snapshot_iterator_free<USE_HINT>;
>+ it->base.next = tree_snapshot_iterator_next<USE_HINT>;
>  it->index = index;
>  index_ref(base);
>  it->tree_iterator = memtx_tree_iterator_first(&index->tree);
>@@ -1362,94 +1517,124 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
>  return (struct snapshot_iterator *) it;
> }
> 
>-static const struct index_vtab memtx_tree_index_vtab = {
>- /* .destroy = */ memtx_tree_index_destroy,
>+static const struct index_vtab memtx_tree_no_hint_index_vtab = {
>+ /* .destroy = */ memtx_tree_index_destroy<0>,
>  /* .commit_create = */ generic_index_commit_create,
>  /* .abort_create = */ generic_index_abort_create,
>  /* .commit_modify = */ generic_index_commit_modify,
>  /* .commit_drop = */ generic_index_commit_drop,
>- /* .update_def = */ memtx_tree_index_update_def,
>+ /* .update_def = */ memtx_tree_index_update_def<0>,
>  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>  /* .def_change_requires_rebuild = */
>  memtx_index_def_change_requires_rebuild,
>- /* .size = */ memtx_tree_index_size,
>- /* .bsize = */ memtx_tree_index_bsize,
>+ /* .size = */ memtx_tree_index_size<0>,
>+ /* .bsize = */ memtx_tree_index_bsize<0>,
>  /* .min = */ generic_index_min,
>  /* .max = */ generic_index_max,
>- /* .random = */ memtx_tree_index_random,
>- /* .count = */ memtx_tree_index_count,
>- /* .get = */ memtx_tree_index_get,
>- /* .replace = */ memtx_tree_index_replace,
>- /* .create_iterator = */ memtx_tree_index_create_iterator,
>+ /* .random = */ memtx_tree_index_random<0>,
>+ /* .count = */ memtx_tree_index_count<0>,
>+ /* .get = */ memtx_tree_index_get<0>,
>+ /* .replace = */ memtx_tree_index_replace<0>,
>+ /* .create_iterator = */ memtx_tree_index_create_iterator<0>,
>  /* .create_snapshot_iterator = */
>- memtx_tree_index_create_snapshot_iterator,
>+ memtx_tree_index_create_snapshot_iterator<0>,
>  /* .stat = */ generic_index_stat,
>  /* .compact = */ generic_index_compact,
>  /* .reset_stat = */ generic_index_reset_stat,
>- /* .begin_build = */ memtx_tree_index_begin_build,
>- /* .reserve = */ memtx_tree_index_reserve,
>- /* .build_next = */ memtx_tree_index_build_next,
>- /* .end_build = */ memtx_tree_index_end_build,
>+ /* .begin_build = */ memtx_tree_index_begin_build<0>,
>+ /* .reserve = */ memtx_tree_index_reserve<0>,
>+ /* .build_next = */ memtx_tree_index_build_next<0>,
>+ /* .end_build = */ memtx_tree_index_end_build<0>,
>+};
>+
>+static const struct index_vtab memtx_tree_use_hint_index_vtab = {
>+ /* .destroy = */ memtx_tree_index_destroy<1>,
>+ /* .commit_create = */ generic_index_commit_create,
>+ /* .abort_create = */ generic_index_abort_create,
>+ /* .commit_modify = */ generic_index_commit_modify,
>+ /* .commit_drop = */ generic_index_commit_drop,
>+ /* .update_def = */ memtx_tree_index_update_def<1>,
>+ /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>+ /* .def_change_requires_rebuild = */
>+ memtx_index_def_change_requires_rebuild,
>+ /* .size = */ memtx_tree_index_size<1>,
>+ /* .bsize = */ memtx_tree_index_bsize<1>,
>+ /* .min = */ generic_index_min,
>+ /* .max = */ generic_index_max,
>+ /* .random = */ memtx_tree_index_random<1>,
>+ /* .count = */ memtx_tree_index_count<1>,
>+ /* .get = */ memtx_tree_index_get<1>,
>+ /* .replace = */ memtx_tree_index_replace<1>,
>+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>+ /* .create_snapshot_iterator = */
>+ memtx_tree_index_create_snapshot_iterator<1>,
>+ /* .stat = */ generic_index_stat,
>+ /* .compact = */ generic_index_compact,
>+ /* .reset_stat = */ generic_index_reset_stat,
>+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>+ /* .reserve = */ memtx_tree_index_reserve<1>,
>+ /* .build_next = */ memtx_tree_index_build_next<1>,
>+ /* .end_build = */ memtx_tree_index_end_build<1>,
> };
> 
> static const struct index_vtab memtx_tree_index_multikey_vtab = {
>- /* .destroy = */ memtx_tree_index_destroy,
>+ /* .destroy = */ memtx_tree_index_destroy<1>,
>  /* .commit_create = */ generic_index_commit_create,
>  /* .abort_create = */ generic_index_abort_create,
>  /* .commit_modify = */ generic_index_commit_modify,
>  /* .commit_drop = */ generic_index_commit_drop,
>- /* .update_def = */ memtx_tree_index_update_def,
>+ /* .update_def = */ memtx_tree_index_update_def<1>,
>  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>  /* .def_change_requires_rebuild = */
>  memtx_index_def_change_requires_rebuild,
>- /* .size = */ memtx_tree_index_size,
>- /* .bsize = */ memtx_tree_index_bsize,
>+ /* .size = */ memtx_tree_index_size<1>,
>+ /* .bsize = */ memtx_tree_index_bsize<1>,
>  /* .min = */ generic_index_min,
>  /* .max = */ generic_index_max,
>- /* .random = */ memtx_tree_index_random,
>- /* .count = */ memtx_tree_index_count,
>- /* .get = */ memtx_tree_index_get,
>+ /* .random = */ memtx_tree_index_random<1>,
>+ /* .count = */ memtx_tree_index_count<1>,
>+ /* .get = */ memtx_tree_index_get<1>,
>  /* .replace = */ memtx_tree_index_replace_multikey,
>- /* .create_iterator = */ memtx_tree_index_create_iterator,
>+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>  /* .create_snapshot_iterator = */
>- memtx_tree_index_create_snapshot_iterator,
>+ memtx_tree_index_create_snapshot_iterator<1>,
>  /* .stat = */ generic_index_stat,
>  /* .compact = */ generic_index_compact,
>  /* .reset_stat = */ generic_index_reset_stat,
>- /* .begin_build = */ memtx_tree_index_begin_build,
>- /* .reserve = */ memtx_tree_index_reserve,
>+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>+ /* .reserve = */ memtx_tree_index_reserve<1>,
>  /* .build_next = */ memtx_tree_index_build_next_multikey,
>- /* .end_build = */ memtx_tree_index_end_build,
>+ /* .end_build = */ memtx_tree_index_end_build<1>,
> };
> 
> static const struct index_vtab memtx_tree_func_index_vtab = {
>- /* .destroy = */ memtx_tree_index_destroy,
>+ /* .destroy = */ memtx_tree_index_destroy<1>,
>  /* .commit_create = */ generic_index_commit_create,
>  /* .abort_create = */ generic_index_abort_create,
>  /* .commit_modify = */ generic_index_commit_modify,
>  /* .commit_drop = */ generic_index_commit_drop,
>- /* .update_def = */ memtx_tree_index_update_def,
>+ /* .update_def = */ memtx_tree_index_update_def<1>,
>  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>  /* .def_change_requires_rebuild = */
>  memtx_index_def_change_requires_rebuild,
>- /* .size = */ memtx_tree_index_size,
>- /* .bsize = */ memtx_tree_index_bsize,
>+ /* .size = */ memtx_tree_index_size<1>,
>+ /* .bsize = */ memtx_tree_index_bsize<1>,
>  /* .min = */ generic_index_min,
>  /* .max = */ generic_index_max,
>- /* .random = */ memtx_tree_index_random,
>- /* .count = */ memtx_tree_index_count,
>- /* .get = */ memtx_tree_index_get,
>+ /* .random = */ memtx_tree_index_random<1>,
>+ /* .count = */ memtx_tree_index_count<1>,
>+ /* .get = */ memtx_tree_index_get<1>,
>  /* .replace = */ memtx_tree_func_index_replace,
>- /* .create_iterator = */ memtx_tree_index_create_iterator,
>+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>  /* .create_snapshot_iterator = */
>- memtx_tree_index_create_snapshot_iterator,
>+ memtx_tree_index_create_snapshot_iterator<1>,
>  /* .stat = */ generic_index_stat,
>  /* .compact = */ generic_index_compact,
>  /* .reset_stat = */ generic_index_reset_stat,
>- /* .begin_build = */ memtx_tree_index_begin_build,
>- /* .reserve = */ memtx_tree_index_reserve,
>+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>+ /* .reserve = */ memtx_tree_index_reserve<1>,
>  /* .build_next = */ memtx_tree_func_index_build_next,
>- /* .end_build = */ memtx_tree_index_end_build,
>+ /* .end_build = */ memtx_tree_index_end_build<1>,
> };
> 
> /**
>@@ -1459,7 +1644,7 @@ static const struct index_vtab memtx_tree_func_index_vtab = {
>  * key defintion is not completely initialized at that moment).
>  */
> static const struct index_vtab memtx_tree_disabled_index_vtab = {
>- /* .destroy = */ memtx_tree_index_destroy,
>+ /* .destroy = */ memtx_tree_index_destroy<1>,
>  /* .commit_create = */ generic_index_commit_create,
>  /* .abort_create = */ generic_index_abort_create,
>  /* .commit_modify = */ generic_index_commit_modify,
>@@ -1488,27 +1673,19 @@ static const struct index_vtab memtx_tree_disabled_index_vtab = {
>  /* .end_build = */ generic_index_end_build,
> };
> 
>-struct index *
>-memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
>+template <int USE_HINT>
>+static struct index *
>+memtx_tree_index_new_tpl(struct memtx_engine *memtx, struct index_def *def,
>+ const struct index_vtab *vtab)
> {
>- struct memtx_tree_index *index =
>- (struct memtx_tree_index *)calloc(1, sizeof(*index));
>+ struct memtx_tree_index<USE_HINT> *index =
>+ (struct memtx_tree_index<USE_HINT> *)
>+ calloc(1, sizeof(*index));
>  if (index == NULL) {
>  diag_set(OutOfMemory, sizeof(*index),
>  "malloc", "struct memtx_tree_index");
>  return NULL;
>  }
>- const struct index_vtab *vtab;
>- if (def->key_def->for_func_index) {
>- if (def->key_def->func_index_func == NULL)
>- vtab = &memtx_tree_disabled_index_vtab;
>- else
>- vtab = &memtx_tree_func_index_vtab;
>- } else if (def->key_def->is_multikey) {
>- vtab = &memtx_tree_index_multikey_vtab;
>- } else {
>- vtab = &memtx_tree_index_vtab;
>- }
>  if (index_create(&index->base, (struct engine *)memtx,
>  vtab, def) != 0) {
>  free(index);
>@@ -1524,3 +1701,23 @@ memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
>  memtx_index_extent_free, memtx);
>  return &index->base;
> }
>+
>+struct index *
>+memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
>+{
>+ const struct index_vtab *vtab;
>+ if (def->key_def->for_func_index) {
>+ if (def->key_def->func_index_func == NULL)
>+ vtab = &memtx_tree_disabled_index_vtab;
>+ else
>+ vtab = &memtx_tree_func_index_vtab;
>+ } else if (def->key_def->is_multikey) {
>+ vtab = &memtx_tree_index_multikey_vtab;
>+ } else if (def->opts.hint) {
>+ vtab = &memtx_tree_use_hint_index_vtab;
>+ } else {
>+ vtab = &memtx_tree_no_hint_index_vtab;
>+ return memtx_tree_index_new_tpl<0>(memtx, def, vtab);
>+ }
>+ return memtx_tree_index_new_tpl<1>(memtx, def, vtab);
>+}
>diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
>index ef5ae3d..0bb803a 100644
>--- a/src/lib/salad/bps_tree.h
>+++ b/src/lib/salad/bps_tree.h
>@@ -168,6 +168,17 @@
> 
> /* {{{ BPS-tree interface settings */
> /**
>+ * Optional namespace for structs and functions.
>+ * Is set, struct and functions will have BPS_TREE_NAMESPACE:: prefix.
>+ * For example one can #define BPS_TREE_NAMESPACE my along with
>+ * #define BPS_TREE_NAME _test, and use then
>+ * struct my::bps_tree_test my_tree;
>+ * my::bps_tree_test_create(&my_tree, ...);
>+ *
>+ * #define BPS_TREE_NAMESPACE
>+ */
>+
>+/**
>  * Custom name for structs and functions.
>  * Struct and functions will have bps_tree##BPS_TREE_NAME name or prefix.
>  * For example one can #define BPS_TREE_NAME _test, and use then
>@@ -300,6 +311,10 @@
> 
> /* }}} */
> 
>+#ifdef BPS_TREE_NAMESPACE
>+namespace BPS_TREE_NAMESPACE {
>+#endif
>+
> /* {{{ BPS-tree internal settings */
> typedef int16_t bps_tree_pos_t;
> typedef uint32_t bps_tree_block_id_t;
>@@ -6188,3 +6203,7 @@ bps_tree_debug_check_internal_functions(bool assertme)
> #undef bps_tree_debug_check_insert_and_move_to_left_inner
> 
> /* }}} */
>+
>+#ifdef BPS_TREE_NAMESPACE
>+} /* namespace BPS_TREE_NAMESPACE { */
>+#endif
>diff --git a/test/box/alter.result b/test/box/alter.result
>index 237c2d8..5b412a7 100644
>--- a/test/box/alter.result
>+++ b/test/box/alter.result
>@@ -680,8 +680,9 @@ s.index.pk
>   - type: unsigned
>     is_nullable: false
>     fieldno: 2
>- type: TREE
>+ hint: true
>   id: 0
>+ type: TREE
>   space_id: 731
>   name: pk
> ...
>@@ -710,9 +711,10 @@ s.index.pk
>   - type: unsigned
>     is_nullable: false
>     fieldno: 1
>- space_id: 731
>+ hint: true
>   id: 0
>   type: TREE
>+ space_id: 731
>   name: pk
> ...
> s.index.secondary
>@@ -722,8 +724,9 @@ s.index.secondary
>   - type: unsigned
>     is_nullable: false
>     fieldno: 2
>- type: TREE
>+ hint: true
>   id: 1
>+ type: TREE
>   space_id: 731
>   name: secondary
> ...
>@@ -1559,3 +1562,97 @@ assert(err:match('does not exist') ~= nil)
> ---
> - true
> ...
>+-- hint
>+s = box.schema.create_space('test');
>+---
>+...
>+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
>+---
>+- true
>+...
>+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
>+---
>+- true
>+...
>+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
>+---
>+- false
>+...
>+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
>+---
>+- false
>+...
>+s.index.test1.hint
>+---
>+- true
>+...
>+s.index.test2.hint
>+---
>+- true
>+...
>+s.index.test3.hint
>+---
>+- false
>+...
>+s.index.test4.hint
>+---
>+- false
>+...
>+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
>+---
>+...
>+s.index.test1:bsize() == s.index.test2:bsize()
>+---
>+- true
>+...
>+s.index.test1:bsize() > s.index.test3:bsize()
>+---
>+- true
>+...
>+s.index.test1:bsize() > s.index.test4:bsize()
>+---
>+- true
>+...
>+s.index.test1:alter{hint=false}
>+---
>+...
>+s.index.test2:alter{hint=true}
>+---
>+...
>+s.index.test3:alter{name='test33', hint=false}
>+---
>+...
>+s.index.test4:alter{hint=true}
>+---
>+...
>+s.index.test1.hint
>+---
>+- false
>+...
>+s.index.test2.hint
>+---
>+- true
>+...
>+s.index.test33.hint
>+---
>+- false
>+...
>+s.index.test4.hint
>+---
>+- true
>+...
>+s.index.test1:bsize() < s.index.test2:bsize()
>+---
>+- true
>+...
>+s.index.test1:bsize() == s.index.test33:bsize()
>+---
>+- true
>+...
>+s.index.test1:bsize() < s.index.test4:bsize()
>+---
>+- true
>+...
>+s:drop()
>+---
>+...
>diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
>index abd08e2..2114186 100644
>--- a/test/box/alter.test.lua
>+++ b/test/box/alter.test.lua
>@@ -620,3 +620,37 @@ s:drop()
> s:alter({})
> ok, err = pcall(box.schema.space.alter, s.id, {})
> assert(err:match('does not exist') ~= nil)
>+
>+-- hint
>+s = box.schema.create_space('test');
>+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
>+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
>+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
>+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
>+
>+s.index.test1.hint
>+s.index.test2.hint
>+s.index.test3.hint
>+s.index.test4.hint
>+
>+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
>+
>+s.index.test1:bsize() == s.index.test2:bsize()
>+s.index.test1:bsize() > s.index.test3:bsize()
>+s.index.test1:bsize() > s.index.test4:bsize()
>+
>+s.index.test1:alter{hint=false}
>+s.index.test2:alter{hint=true}
>+s.index.test3:alter{name='test33', hint=false}
>+s.index.test4:alter{hint=true}
>+
>+s.index.test1.hint
>+s.index.test2.hint
>+s.index.test33.hint
>+s.index.test4.hint
>+
>+s.index.test1:bsize() < s.index.test2:bsize()
>+s.index.test1:bsize() == s.index.test33:bsize()
>+s.index.test1:bsize() < s.index.test4:bsize()
>+
>+s:drop()
>\ No newline at end of file
>diff --git a/test/box/errinj.result b/test/box/errinj.result
>index 613d22c..b8c2476 100644
>--- a/test/box/errinj.result
>+++ b/test/box/errinj.result
>@@ -1774,9 +1774,10 @@ rtreespace:create_index('pk', {if_not_exists = true})
>   - type: unsigned
>     is_nullable: false
>     fieldno: 1
>+ hint: true
>   id: 0
>- space_id: 512
>   type: TREE
>+ space_id: 512
>   name: pk
> ...
> rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array'},unique = false, if_not_exists = true,})
>diff --git a/test/box/tree_pk.result b/test/box/tree_pk.result
>index df3c78b..18cb607 100644
>--- a/test/box/tree_pk.result
>+++ b/test/box/tree_pk.result
>@@ -852,3 +852,317 @@ box.internal.collation.drop('test')
> box.internal.collation.drop('test-ci')
> ---
> ...
>+-- hints
>+s = box.schema.space.create('test')
>+---
>+...
>+s:create_index('test', {type = 'tree', hint = 'true'} )
>+---
>+- error: Illegal parameters, options parameter 'hint' should be of type boolean
>+...
>+s:create_index('test', {type = 'hash', hint = true} )
>+---
>+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
>+ with memtx tree index'
>+...
>+s:create_index('test', {type = 'hash'}):alter({hint = true})
>+---
>+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
>+ with memtx tree index'
>+...
>+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
>+---
>+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
>+ can''t use hints'
>+...
>+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
>+---
>+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
>+ can''t use hints'
>+...
>+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
>+---
>+...
>+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>+---
>+...
>+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
>+---
>+- error: 'Can''t create or modify index ''func'' in space ''test'': functional index
>+ can''t use hints'
>+...
>+s:drop()
>+---
>+...
>+s = box.schema.space.create('test', {engine = 'vinyl'})
>+---
>+...
>+s:create_index('test', {type = 'tree', hint = true} )
>+---
>+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
>+ with memtx tree index'
>+...
>+s:drop()
>+---
>+...
>+-- numeric hints
>+s1 = box.schema.space.create('test1')
>+---
>+...
>+s1:create_index('test', {type = 'tree', hint = false}).hint
>+---
>+- false
>+...
>+s2 = box.schema.space.create('test2')
>+---
>+...
>+s2:create_index('test', {type = 'tree'}).hint
>+---
>+- true
>+...
>+N = 2000
>+---
>+...
>+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>+---
>+...
>+s1:count() == s2:count()
>+---
>+- true
>+...
>+good = true r1 = s1:select{} r2 = s2:select{}
>+---
>+...
>+if #r1 ~= #r2 then good = false end
>+---
>+...
>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+r1 = nil r2 = nil
>+---
>+...
>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>+---
>+...
>+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+s1.index[0]:bsize() < s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1.index[0]:alter{hint=true}
>+---
>+...
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+---
>+- true
>+...
>+s2.index[0]:alter{hint=false}
>+---
>+...
>+s1.index[0]:bsize() > s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1.index[0]:alter{hint=false}
>+---
>+...
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1:drop()
>+---
>+...
>+s2:drop()
>+---
>+...
>+-- string hints
>+s1 = box.schema.space.create('test1')
>+---
>+...
>+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
>+---
>+- false
>+...
>+s2 = box.schema.space.create('test2')
>+---
>+...
>+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
>+---
>+- true
>+...
>+N = 1000
>+---
>+...
>+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>+---
>+...
>+s1:count() == s2:count()
>+---
>+- true
>+...
>+good = true r1 = s1:select{} r2 = s2:select{}
>+---
>+...
>+if #r1 ~= #r2 then good = false end
>+---
>+...
>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+r1 = nil r2 = nil
>+---
>+...
>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>+---
>+...
>+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+s1.index[0]:bsize() < s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1.index[0]:alter{hint=true}
>+---
>+...
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+---
>+- true
>+...
>+s2.index[0]:alter{hint=false}
>+---
>+...
>+s1.index[0]:bsize() > s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1.index[0]:alter{hint=false}
>+---
>+...
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1:drop()
>+---
>+...
>+s2:drop()
>+---
>+...
>+-- string with collation hints
>+s1 = box.schema.space.create('test1')
>+---
>+...
>+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
>+---
>+- false
>+...
>+s2 = box.schema.space.create('test2')
>+---
>+...
>+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
>+---
>+- true
>+...
>+N = 1000
>+---
>+...
>+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
>+---
>+...
>+syms_size = #syms
>+---
>+...
>+len = 20
>+---
>+...
>+vals = {}
>+---
>+...
>+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
>+---
>+...
>+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
>+---
>+...
>+s1:count() == s2:count()
>+---
>+- true
>+...
>+good = true r1 = s1:select{} r2 = s2:select{}
>+---
>+...
>+if #r1 ~= #r2 then good = false end
>+---
>+...
>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+r1 = nil r2 = nil
>+---
>+...
>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>+---
>+...
>+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+s1.index[0]:bsize() < s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1.index[0]:alter{hint=true}
>+---
>+...
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+---
>+- true
>+...
>+s2.index[0]:alter{hint=false}
>+---
>+...
>+s1.index[0]:bsize() > s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1.index[0]:alter{hint=false}
>+---
>+...
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+---
>+- true
>+...
>+s1:drop()
>+---
>+...
>+s2:drop()
>+---
>+...
>diff --git a/test/box/tree_pk.test.lua b/test/box/tree_pk.test.lua
>index 1190ab4..2f22ed9 100644
>--- a/test/box/tree_pk.test.lua
>+++ b/test/box/tree_pk.test.lua
>@@ -301,3 +301,118 @@ s:drop()
> 
> box.internal.collation.drop('test')
> box.internal.collation.drop('test-ci')
>+
>+-- hints
>+s = box.schema.space.create('test')
>+s:create_index('test', {type = 'tree', hint = 'true'} )
>+s:create_index('test', {type = 'hash', hint = true} )
>+s:create_index('test', {type = 'hash'}):alter({hint = true})
>+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
>+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
>+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
>+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
>+s:drop()
>+
>+s = box.schema.space.create('test', {engine = 'vinyl'})
>+s:create_index('test', {type = 'tree', hint = true} )
>+s:drop()
>+
>+-- numeric hints
>+s1 = box.schema.space.create('test1')
>+s1:create_index('test', {type = 'tree', hint = false}).hint
>+s2 = box.schema.space.create('test2')
>+s2:create_index('test', {type = 'tree'}).hint
>+
>+N = 2000
>+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>+s1:count() == s2:count()
>+
>+good = true r1 = s1:select{} r2 = s2:select{}
>+if #r1 ~= #r2 then good = false end
>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>+good
>+r1 = nil r2 = nil
>+
>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
>+good
>+
>+s1.index[0]:bsize() < s2.index[0]:bsize()
>+s1.index[0]:alter{hint=true}
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+s2.index[0]:alter{hint=false}
>+s1.index[0]:bsize() > s2.index[0]:bsize()
>+s1.index[0]:alter{hint=false}
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+
>+s1:drop()
>+s2:drop()
>+
>+-- string hints
>+s1 = box.schema.space.create('test1')
>+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
>+s2 = box.schema.space.create('test2')
>+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
>+
>+N = 1000
>+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>+s1:count() == s2:count()
>+
>+good = true r1 = s1:select{} r2 = s2:select{}
>+if #r1 ~= #r2 then good = false end
>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>+good
>+r1 = nil r2 = nil
>+
>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
>+good
>+
>+s1.index[0]:bsize() < s2.index[0]:bsize()
>+s1.index[0]:alter{hint=true}
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+s2.index[0]:alter{hint=false}
>+s1.index[0]:bsize() > s2.index[0]:bsize()
>+s1.index[0]:alter{hint=false}
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+
>+s1:drop()
>+s2:drop()
>+
>+-- string with collation hints
>+s1 = box.schema.space.create('test1')
>+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
>+s2 = box.schema.space.create('test2')
>+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
>+
>+N = 1000
>+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
>+syms_size = #syms
>+len = 20
>+vals = {}
>+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
>+
>+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
>+s1:count() == s2:count()
>+
>+good = true r1 = s1:select{} r2 = s2:select{}
>+if #r1 ~= #r2 then good = false end
>+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>+good
>+r1 = nil r2 = nil
>+
>+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
>+good
>+
>+s1.index[0]:bsize() < s2.index[0]:bsize()
>+s1.index[0]:alter{hint=true}
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+s2.index[0]:alter{hint=false}
>+s1.index[0]:bsize() > s2.index[0]:bsize()
>+s1.index[0]:alter{hint=false}
>+s1.index[0]:bsize() == s2.index[0]:bsize()
>+
>+s1:drop()
>+s2:drop()
>diff --git a/test/box/tree_pk_multipart.result b/test/box/tree_pk_multipart.result
>index 93219f6..3f6bb94 100644
>--- a/test/box/tree_pk_multipart.result
>+++ b/test/box/tree_pk_multipart.result
>@@ -542,3 +542,156 @@ space:drop()
> space = nil
> ---
> ...
>+-- hints
>+test_run:cmd("setopt delimiter ';'")
>+---
>+- true
>+...
>+function equal(res1, res2)
>+ if #res1 ~= #res2 then
>+ return false
>+ end
>+ for k,v in pairs(res1) do
>+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
>+ return false
>+ end
>+ end
>+ return true
>+end
>+test_run:cmd("setopt delimiter ''");
>+---
>+...
>+-- num num
>+N1 = 100
>+---
>+...
>+t1 = {}
>+---
>+...
>+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
>+---
>+...
>+N2 = 5
>+---
>+...
>+t2 = {}
>+---
>+...
>+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>+---
>+...
>+s1 = box.schema.space.create('test1')
>+---
>+...
>+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
>+---
>+- false
>+...
>+s2 = box.schema.space.create('test2')
>+---
>+...
>+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
>+---
>+- true
>+...
>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>+---
>+...
>+s1:count() == s2:count()
>+---
>+- true
>+...
>+equal(s1:select{}, s2:select{})
>+---
>+- true
>+...
>+good = true
>+---
>+...
>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>+---
>+...
>+good
>+---
>+- true
>+...
>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+s1:drop()
>+---
>+...
>+s2:drop()
>+---
>+...
>+-- str num
>+N1 = 100
>+---
>+...
>+t1 = {}
>+---
>+...
>+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
>+---
>+...
>+N2 = 5
>+---
>+...
>+t2 = {}
>+---
>+...
>+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>+---
>+...
>+s1 = box.schema.space.create('test1')
>+---
>+...
>+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
>+---
>+- false
>+...
>+s2 = box.schema.space.create('test2')
>+---
>+...
>+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
>+---
>+- true
>+...
>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>+---
>+...
>+s1:count() == s2:count()
>+---
>+- true
>+...
>+equal(s1:select{}, s2:select{})
>+---
>+- true
>+...
>+good = true
>+---
>+...
>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>+---
>+...
>+good
>+---
>+- true
>+...
>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>+---
>+...
>+good
>+---
>+- true
>+...
>+s1:drop()
>+---
>+...
>+s2:drop()
>+---
>+...
>diff --git a/test/box/tree_pk_multipart.test.lua b/test/box/tree_pk_multipart.test.lua
>index 7d3405f..cba3a98 100644
>--- a/test/box/tree_pk_multipart.test.lua
>+++ b/test/box/tree_pk_multipart.test.lua
>@@ -194,3 +194,67 @@ test_run:cmd("setopt delimiter ''");
> space:drop()
> 
> space = nil
>+
>+
>+-- hints
>+test_run:cmd("setopt delimiter ';'")
>+function equal(res1, res2)
>+ if #res1 ~= #res2 then
>+ return false
>+ end
>+ for k,v in pairs(res1) do
>+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
>+ return false
>+ end
>+ end
>+ return true
>+end
>+test_run:cmd("setopt delimiter ''");
>+
>+-- num num
>+N1 = 100
>+t1 = {}
>+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
>+N2 = 5
>+t2 = {}
>+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>+
>+s1 = box.schema.space.create('test1')
>+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
>+s2 = box.schema.space.create('test2')
>+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>+s1:count() == s2:count()
>+equal(s1:select{}, s2:select{})
>+good = true
>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>+good
>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>+good
>+
>+s1:drop()
>+s2:drop()
>+
>+-- str num
>+N1 = 100
>+t1 = {}
>+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
>+N2 = 5
>+t2 = {}
>+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>+
>+s1 = box.schema.space.create('test1')
>+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
>+s2 = box.schema.space.create('test2')
>+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
>+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>+s1:count() == s2:count()
>+equal(s1:select{}, s2:select{})
>+good = true
>+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>+good
>+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>+good
>+
>+s1:drop()
>+s2:drop()
>--
>2.7.4
> 
--
Ilya Kosarev

[-- Attachment #2: Type: text/html, Size: 91906 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional
  2020-10-20 12:34   ` Ilya Kosarev
@ 2020-10-20 12:37     ` Nikita Pettik
  2020-10-20 12:57       ` Ilya Kosarev
  0 siblings, 1 reply; 11+ messages in thread
From: Nikita Pettik @ 2020-10-20 12:37 UTC (permalink / raw)
  To: Ilya Kosarev; +Cc: tarantool-patches

On 20 Oct 15:34, Ilya Kosarev wrote:
> 
> Hi,
>  
> Thanks for the patch!
>  
> LGTM, i checked for the number of compilers that if with the templated
> agrument is being processed at the compile time so that there are no jumps
> in asm code.
>  
> What i think is needed is the enum like the following:
> enum {
>         NOT_HINTED =  0 ,
>         HINTED =  1 ,
> };

I like this way. Btw why not using just boolean type?
  
> to avoid using <0> & <1>. If needed i can push such patch to the branch. 
> >Понедельник, 19 октября 2020, 12:52 +03:00 от Aleksandr Lyapunov <alyapunov@tarantool.org>:
> > 
> >Since 9fba29abb4e05babb9b23b4413bd8083f0fba933 (memtx: introduce tuple
> >compare hint) memtx tree key data (indexes) started to contain extra 8
> >bytes as a hint. Now it is optional and can be turned off in an index
> >options with "hint = false" entry.
> >
> >Closes #4927
> >
> >@TarantoolBot document
> >Title: memtx: optional tuple compare hints
> >Update the documentation for an index creation to reflect that there is
> >now an option to turn off hints for the index.
> >---
> > src/box/index_def.c | 2 +
> > src/box/index_def.h | 6 +
> > src/box/lua/schema.lua | 53 ++++
> > src/box/lua/space.cc | 7 +
> > src/box/memtx_engine.c | 2 +
> > src/box/memtx_tree.cc | 599 ++++++++++++++++++++++++------------
> > src/lib/salad/bps_tree.h | 19 ++
> > test/box/alter.result | 103 ++++++-
> > test/box/alter.test.lua | 34 ++
> > test/box/errinj.result | 3 +-
> > test/box/tree_pk.result | 314 +++++++++++++++++++
> > test/box/tree_pk.test.lua | 115 +++++++
> > test/box/tree_pk_multipart.result | 153 +++++++++
> > test/box/tree_pk_multipart.test.lua | 64 ++++
> > 14 files changed, 1269 insertions(+), 205 deletions(-)
> >
> >diff --git a/src/box/index_def.c b/src/box/index_def.c
> >index 9802961..79394b8 100644
> >--- a/src/box/index_def.c
> >+++ b/src/box/index_def.c
> >@@ -51,6 +51,7 @@ const struct index_opts index_opts_default = {
> >  /* .lsn = */ 0,
> >  /* .stat = */ NULL,
> >  /* .func = */ 0,
> >+ /* .hint = */ true,
> > };
> > 
> > const struct opt_def index_opts_reg[] = {
> >@@ -66,6 +67,7 @@ const struct opt_def index_opts_reg[] = {
> >  OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn),
> >  OPT_DEF("func", OPT_UINT32, struct index_opts, func_id),
> >  OPT_DEF_LEGACY("sql"),
> >+ OPT_DEF("hint", OPT_BOOL, struct index_opts, hint),
> >  OPT_END,
> > };
> > 
> >diff --git a/src/box/index_def.h b/src/box/index_def.h
> >index d928b23..2180a69 100644
> >--- a/src/box/index_def.h
> >+++ b/src/box/index_def.h
> >@@ -165,6 +165,10 @@ struct index_opts {
> >  struct index_stat *stat;
> >  /** Identifier of the functional index function. */
> >  uint32_t func_id;
> >+ /**
> >+ * Use hint optimization for tree index.
> >+ */
> >+ bool hint;
> > };
> > 
> > extern const struct index_opts index_opts_default;
> >@@ -211,6 +215,8 @@ index_opts_cmp(const struct index_opts *o1, const struct index_opts *o2)
> >  return o1->bloom_fpr < o2->bloom_fpr ? -1 : 1;
> >  if (o1->func_id != o2->func_id)
> >  return o1->func_id - o2->func_id;
> >+ if (o1->hint != o2->hint)
> >+ return o1->hint - o2->hint;
> >  return 0;
> > }
> > 
> >diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
> >index 1131af7..9cc1289 100644
> >--- a/src/box/lua/schema.lua
> >+++ b/src/box/lua/schema.lua
> >@@ -1000,8 +1000,31 @@ local index_options = {
> >     page_size = 'number',
> >     bloom_fpr = 'number',
> >     func = 'number, string',
> >+ hint = 'boolean',
> > }
> > 
> >+local function jsonpaths_from_idx_parts(parts)
> >+ local paths = {}
> >+
> >+ for _, part in pairs(parts) do
> >+ if type(part.path) == 'string' then
> >+ table.insert(paths, part.path)
> >+ end
> >+ end
> >+
> >+ return paths
> >+end
> >+
> >+local function is_multikey_index(parts)
> >+ for _, path in pairs(jsonpaths_from_idx_parts(parts)) do
> >+ if path:find('[*]', 1, true) then
> >+ return true
> >+ end
> >+ end
> >+
> >+ return false
> >+end
> >+
> > --
> > -- check_param_table() template for alter index,
> > -- includes all index options.
> >@@ -1076,6 +1099,15 @@ box.schema.index.create = function(space_id, name, options)
> >         options_defaults = {}
> >     end
> >     options = update_param_table(options, options_defaults)
> >+ if options.hint and
> >+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
> >+ box.error(box.error.MODIFY_INDEX, name, space.name,
> >+ "hint is only reasonable with memtx tree index")
> >+ end
> >+ if options.hint and options.func then
> >+ box.error(box.error.MODIFY_INDEX, name, space.name,
> >+ "functional index can't use hints")
> >+ end
> > 
> >     local _index = box.space[box.schema.INDEX_ID]
> >     local _vindex = box.space[box.schema.VINDEX_ID]
> >@@ -1115,6 +1147,7 @@ box.schema.index.create = function(space_id, name, options)
> >             run_size_ratio = options.run_size_ratio,
> >             bloom_fpr = options.bloom_fpr,
> >             func = options.func,
> >+ hint = options.hint,
> >     }
> >     local field_type_aliases = {
> >         num = 'unsigned'; -- Deprecated since 1.7.2
> >@@ -1135,6 +1168,10 @@ box.schema.index.create = function(space_id, name, options)
> >     if parts_can_be_simplified then
> >         parts = simplify_index_parts(parts)
> >     end
> >+ if options.hint and is_multikey_index(parts) then
> >+ box.error(box.error.MODIFY_INDEX, name, space.name,
> >+ "multikey index can't use hints")
> >+ end
> >     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
> >         index_opts.func = func_id_by_name(index_opts.func)
> >     end
> >@@ -1253,6 +1290,17 @@ box.schema.index.alter = function(space_id, index_id, options)
> >             index_opts[k] = options[k]
> >         end
> >     end
> >+ if options.hint and
> >+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
> >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
> >+ space.name,
> >+ "hint is only reasonable with memtx tree index")
> >+ end
> >+ if options.hint and options.func then
> >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
> >+ space.name,
> >+ "functional index can't use hints")
> >+ end
> >     if options.parts then
> >         local parts_can_be_simplified
> >         parts, parts_can_be_simplified =
> >@@ -1262,6 +1310,11 @@ box.schema.index.alter = function(space_id, index_id, options)
> >             parts = simplify_index_parts(parts)
> >         end
> >     end
> >+ if options.hint and is_multikey_index(parts) then
> >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
> >+ space.name,
> >+ "multikey index can't use hints")
> >+ end
> >     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
> >         index_opts.func = func_id_by_name(index_opts.func)
> >     end
> >diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
> >index 177c588..1ea993c 100644
> >--- a/src/box/lua/space.cc
> >+++ b/src/box/lua/space.cc
> >@@ -344,6 +344,13 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
> >  lua_pushnumber(L, index_opts->dimension);
> >  lua_setfield(L, -2, "dimension");
> >  }
> >+ if (space_is_memtx(space) && index_def->type == TREE) {
> >+ lua_pushboolean(L, index_opts->hint);
> >+ lua_setfield(L, -2, "hint");
> >+ } else {
> >+ lua_pushnil(L);
> >+ lua_setfield(L, -2, "hint");
> >+ }
> > 
> >  if (index_opts->func_id > 0) {
> >  lua_pushstring(L, "func");
> >diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
> >index  8147557 ..43000ba 100644
> >--- a/src/box/memtx_engine.c
> >+++ b/src/box/memtx_engine.c
> >@@ -1398,6 +1398,8 @@ memtx_index_def_change_requires_rebuild(struct index *index,
> >  return true;
> >  if (old_def->opts.func_id != new_def->opts.func_id)
> >  return true;
> >+ if (old_def->opts.hint != new_def->opts.hint)
> >+ return true;
> > 
> >  const struct key_def *old_cmp_def, *new_cmp_def;
> >  if (index_depends_on_pk(index)) {
> >diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
> >index d3b993b..17d58c5 100644
> >--- a/src/box/memtx_tree.cc
> >+++ b/src/box/memtx_tree.cc
> >@@ -45,23 +45,51 @@
> > /**
> >  * Struct that is used as a key in BPS tree definition.
> >  */
> >-struct memtx_tree_key_data {
> >+struct memtx_tree_key_data_common {
> >  /** Sequence of msgpacked search fields. */
> >  const char *key;
> >  /** Number of msgpacked search fields. */
> >  uint32_t part_count;
> >+};
> >+
> >+template <int USE_HINT>
> >+struct memtx_tree_key_data;
> >+
> >+template <>
> >+struct memtx_tree_key_data<0> : memtx_tree_key_data_common {
> >+ static constexpr hint_t hint = HINT_NONE;
> >+ void set_hint(hint_t) { assert(false); }
> >+};
> >+
> >+template <>
> >+struct memtx_tree_key_data<1> : memtx_tree_key_data_common {
> >  /** Comparison hint, see tuple_hint(). */
> >  hint_t hint;
> >+ void set_hint(hint_t h) { hint = h; }
> > };
> > 
> > /**
> >  * Struct that is used as a elem in BPS tree definition.
> >  */
> >-struct memtx_tree_data {
> >+struct memtx_tree_data_common {
> >  /* Tuple that this node is represents. */
> >  struct tuple *tuple;
> >+};
> >+
> >+template <int USE_HINT>
> >+struct memtx_tree_data;
> >+
> >+template <>
> >+struct memtx_tree_data<0> : memtx_tree_data_common {
> >+ static constexpr hint_t hint = HINT_NONE;
> >+ void set_hint(hint_t) { assert(false); }
> >+};
> >+
> >+template <>
> >+struct memtx_tree_data<1> : memtx_tree_data<0> {
> >  /** Comparison hint, see key_hint(). */
> >  hint_t hint;
> >+ void set_hint(hint_t h) { hint = h; }
> > };
> > 
> > /**
> >@@ -73,8 +101,8 @@ struct memtx_tree_data {
> >  * @retval false - Otherwise.
> >  */
> > static bool
> >-memtx_tree_data_is_equal(const struct memtx_tree_data *a,
> >- const struct memtx_tree_data *b)
> >+memtx_tree_data_is_equal(const struct memtx_tree_data_common *a,
> >+ const struct memtx_tree_data_common *b)
> > {
> >  return a->tuple == b->tuple;
> > }
> >@@ -89,12 +117,28 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
> >  (b)->part_count, (b)->hint, arg)
> > #define BPS_TREE_IS_IDENTICAL(a, b) memtx_tree_data_is_equal(&a, &b)
> > #define BPS_TREE_NO_DEBUG 1
> >-#define bps_tree_elem_t struct memtx_tree_data
> >-#define bps_tree_key_t struct memtx_tree_key_data *
> > #define bps_tree_arg_t struct key_def *
> > 
> >+#define BPS_TREE_NAMESPACE NS_NO_HINT
> >+#define bps_tree_elem_t struct memtx_tree_data<0>
> >+#define bps_tree_key_t struct memtx_tree_key_data<0> *
> >+
> > #include "salad/bps_tree.h"
> > 
> >+#undef BPS_TREE_NAMESPACE
> >+#undef bps_tree_elem_t
> >+#undef bps_tree_key_t
> >+
> >+#define BPS_TREE_NAMESPACE NS_USE_HINT
> >+#define bps_tree_elem_t struct memtx_tree_data<1>
> >+#define bps_tree_key_t struct memtx_tree_key_data<1> *
> >+
> >+#include "salad/bps_tree.h"
> >+
> >+#undef BPS_TREE_NAMESPACE
> >+#undef bps_tree_elem_t
> >+#undef bps_tree_key_t
> >+
> > #undef BPS_TREE_NAME
> > #undef BPS_TREE_BLOCK_SIZE
> > #undef BPS_TREE_EXTENT_SIZE
> >@@ -102,66 +146,119 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
> > #undef BPS_TREE_COMPARE_KEY
> > #undef BPS_TREE_IS_IDENTICAL
> > #undef BPS_TREE_NO_DEBUG
> >-#undef bps_tree_elem_t
> >-#undef bps_tree_key_t
> > #undef bps_tree_arg_t
> > 
> >+using namespace NS_NO_HINT;
> >+using namespace NS_USE_HINT;
> >+
> >+template <int USE_HINT>
> >+struct memtx_tree_selector;
> >+
> >+template <>
> >+struct memtx_tree_selector<0> : NS_NO_HINT::memtx_tree {};
> >+
> >+template <>
> >+struct memtx_tree_selector<1> : NS_USE_HINT::memtx_tree {};
> >+
> >+template <int USE_HINT>
> >+using memtx_tree_t = struct memtx_tree_selector<USE_HINT>;
> >+
> >+template <int USE_HINT>
> >+struct memtx_tree_iterator_selector;
> >+
> >+template <>
> >+struct memtx_tree_iterator_selector<0> {
> >+ using type = NS_NO_HINT::memtx_tree_iterator;
> >+};
> >+
> >+template <>
> >+struct memtx_tree_iterator_selector<1> {
> >+ using type = NS_USE_HINT::memtx_tree_iterator;
> >+};
> >+
> >+template <int USE_HINT>
> >+using memtx_tree_iterator_t = typename memtx_tree_iterator_selector<USE_HINT>::type;
> >+
> >+static void
> >+invalidate_tree_iterator(NS_NO_HINT::memtx_tree_iterator *itr)
> >+{
> >+ *itr = NS_NO_HINT::memtx_tree_invalid_iterator();
> >+}
> >+
> >+static void
> >+invalidate_tree_iterator(NS_USE_HINT::memtx_tree_iterator *itr)
> >+{
> >+ *itr = NS_USE_HINT::memtx_tree_invalid_iterator();
> >+}
> >+
> >+template <int USE_HINT>
> > struct memtx_tree_index {
> >  struct index base;
> >- struct memtx_tree tree;
> >- struct memtx_tree_data *build_array;
> >+ memtx_tree_t<USE_HINT> tree;
> >+ struct memtx_tree_data<USE_HINT> *build_array;
> >  size_t build_array_size, build_array_alloc_size;
> >  struct memtx_gc_task gc_task;
> >- struct memtx_tree_iterator gc_iterator;
> >+ memtx_tree_iterator_t<USE_HINT> gc_iterator;
> > };
> > 
> > /* {{{ Utilities. *************************************************/
> > 
> >+template <class TREE>
> > static inline struct key_def *
> >-memtx_tree_cmp_def(struct memtx_tree *tree)
> >+memtx_tree_cmp_def(TREE *tree)
> > {
> >  return tree->arg;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > memtx_tree_qcompare(const void* a, const void *b, void *c)
> > {
> >- const struct memtx_tree_data *data_a = (struct memtx_tree_data *)a;
> >- const struct memtx_tree_data *data_b = (struct memtx_tree_data *)b;
> >+ const struct memtx_tree_data<USE_HINT> *data_a =
> >+ (struct memtx_tree_data<USE_HINT> *)a;
> >+ const struct memtx_tree_data<USE_HINT> *data_b =
> >+ (struct memtx_tree_data<USE_HINT> *)b;
> >  struct key_def *key_def = (struct key_def *)c;
> >  return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
> >  data_b->hint, key_def);
> > }
> > 
> > /* {{{ MemtxTree Iterators ****************************************/
> >+template <int USE_HINT>
> > struct tree_iterator {
> >  struct iterator base;
> >- struct memtx_tree_iterator tree_iterator;
> >+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
> >  enum iterator_type type;
> >- struct memtx_tree_key_data key_data;
> >- struct memtx_tree_data current;
> >+ struct memtx_tree_key_data<USE_HINT> key_data;
> >+ struct memtx_tree_data<USE_HINT> current;
> >  /** Memory pool the iterator was allocated from. */
> >  struct mempool *pool;
> > };
> > 
> >-static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,
> >- "sizeof(struct tree_iterator) must be less than or equal "
> >+static_assert(sizeof(struct tree_iterator<0>) <= MEMTX_ITERATOR_SIZE,
> >+ "sizeof(struct tree_iterator<0>) must be less than or equal "
> >+ "to MEMTX_ITERATOR_SIZE");
> >+static_assert(sizeof(struct tree_iterator<1>) <= MEMTX_ITERATOR_SIZE,
> >+ "sizeof(struct tree_iterator<1>) must be less than or equal "
> >  "to MEMTX_ITERATOR_SIZE");
> > 
> >+template <int USE_HINT>
> > static void
> > tree_iterator_free(struct iterator *iterator);
> > 
> >-static inline struct tree_iterator *
> >-tree_iterator(struct iterator *it)
> >+template <int USE_HINT>
> >+static inline struct tree_iterator<USE_HINT> *
> >+get_tree_iterator(struct iterator *it)
> > {
> >- assert(it->free == tree_iterator_free);
> >- return (struct tree_iterator *) it;
> >+ assert(it->free == &tree_iterator_free<USE_HINT>);
> >+ return (struct tree_iterator<USE_HINT> *) it;
> > }
> > 
> >+template <int USE_HINT>
> > static void
> > tree_iterator_free(struct iterator *iterator)
> > {
> >- struct tree_iterator *it = tree_iterator(iterator);
> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
> >  struct tuple *tuple = it->current.tuple;
> >  if (tuple != NULL)
> >  tuple_unref(tuple);
> >@@ -176,14 +273,15 @@ tree_iterator_dummie(struct iterator *iterator, struct tuple **ret)
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
> > {
> >- struct memtx_tree_index *index =
> >- (struct memtx_tree_index *)iterator->index;
> >- struct tree_iterator *it = tree_iterator(iterator);
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
> >  assert(it->current.tuple != NULL);
> >- struct memtx_tree_data *check =
> >+ struct memtx_tree_data<USE_HINT> *check =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
> >  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
> >@@ -192,7 +290,7 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
> >  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
> >  }
> >  tuple_unref(it->current.tuple);
> >- struct memtx_tree_data *res =
> >+ struct memtx_tree_data<USE_HINT> *res =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  if (res == NULL) {
> >  iterator->next = tree_iterator_dummie;
> >@@ -206,14 +304,15 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
> > {
> >- struct memtx_tree_index *index =
> >- (struct memtx_tree_index *)iterator->index;
> >- struct tree_iterator *it = tree_iterator(iterator);
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
> >  assert(it->current.tuple != NULL);
> >- struct memtx_tree_data *check =
> >+ struct memtx_tree_data<USE_HINT> *check =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
> >  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
> >@@ -221,7 +320,7 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
> >  }
> >  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
> >  tuple_unref(it->current.tuple);
> >- struct memtx_tree_data *res =
> >+ struct memtx_tree_data<USE_HINT> *res =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  if (!res) {
> >  iterator->next = tree_iterator_dummie;
> >@@ -235,14 +334,15 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
> > {
> >- struct memtx_tree_index *index =
> >- (struct memtx_tree_index *)iterator->index;
> >- struct tree_iterator *it = tree_iterator(iterator);
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
> >  assert(it->current.tuple != NULL);
> >- struct memtx_tree_data *check =
> >+ struct memtx_tree_data<USE_HINT> *check =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
> >  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
> >@@ -251,7 +351,7 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
> >  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
> >  }
> >  tuple_unref(it->current.tuple);
> >- struct memtx_tree_data *res =
> >+ struct memtx_tree_data<USE_HINT> *res =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  /* Use user key def to save a few loops. */
> >  if (res == NULL ||
> >@@ -271,14 +371,15 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
> > {
> >- struct memtx_tree_index *index =
> >- (struct memtx_tree_index *)iterator->index;
> >- struct tree_iterator *it = tree_iterator(iterator);
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
> >  assert(it->current.tuple != NULL);
> >- struct memtx_tree_data *check =
> >+ struct memtx_tree_data<USE_HINT> *check =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
> >  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
> >@@ -286,7 +387,7 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
> >  }
> >  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
> >  tuple_unref(it->current.tuple);
> >- struct memtx_tree_data *res =
> >+ struct memtx_tree_data<USE_HINT> *res =
> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
> >  /* Use user key def to save a few loops. */
> >  if (res == NULL ||
> >@@ -307,28 +408,30 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
> > }
> > 
> > #define WRAP_ITERATOR_METHOD(name) \
> >+template <int USE_HINT> \
> > static int \
> > name(struct iterator *iterator, struct tuple **ret) \
> > { \
> >- struct memtx_tree *tree = \
> >- &((struct memtx_tree_index *)iterator->index)->tree; \
> >- struct tree_iterator *it = tree_iterator(iterator); \
> >- struct memtx_tree_iterator *ti = &it->tree_iterator; \
> >+ memtx_tree_t<USE_HINT> *tree = \
> >+ &((struct memtx_tree_index<USE_HINT> *)iterator->index)->tree; \
> >+ struct tree_iterator<USE_HINT> *it = \
> >+ get_tree_iterator<USE_HINT>(iterator); \
> >+ memtx_tree_iterator_t<USE_HINT> *ti = &it->tree_iterator; \
> >  uint32_t iid = iterator->index->def->iid; \
> >  bool is_multikey = iterator->index->def->key_def->is_multikey; \
> >  struct txn *txn = in_txn(); \
> >  struct space *space = space_by_id(iterator->space_id); \
> >  bool is_rw = txn != NULL; \
> >  do { \
> >- int rc = name##_base(iterator, ret); \
> >+ int rc = name##_base<USE_HINT>(iterator, ret); \
> >  if (rc != 0 || *ret == NULL) \
> >  return rc; \
> >  uint32_t mk_index = 0; \
> >  if (is_multikey) { \
> >- struct memtx_tree_data *check = \
> >+ struct memtx_tree_data<USE_HINT> *check = \
> >  memtx_tree_iterator_get_elem(tree, ti); \
> >  assert(check != NULL); \
> >- mk_index = check->hint; \
> >+ mk_index = (uint32_t)check->hint; \
> >  } \
> >  *ret = memtx_tx_tuple_clarify(txn, space, *ret, \
> >  iid, mk_index, is_rw); \
> >@@ -347,27 +450,28 @@ WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);
> > 
> > #undef WRAP_ITERATOR_METHOD
> > 
> >+template <int USE_HINT>
> > static void
> >-tree_iterator_set_next_method(struct tree_iterator *it)
> >+tree_iterator_set_next_method(struct tree_iterator<USE_HINT> *it)
> > {
> >  assert(it->current.tuple != NULL);
> >  switch (it->type) {
> >  case ITER_EQ:
> >- it->base.next = tree_iterator_next_equal;
> >+ it->base.next = tree_iterator_next_equal<USE_HINT>;
> >  break;
> >  case ITER_REQ:
> >- it->base.next = tree_iterator_prev_equal;
> >+ it->base.next = tree_iterator_prev_equal<USE_HINT>;
> >  break;
> >  case ITER_ALL:
> >- it->base.next = tree_iterator_next;
> >+ it->base.next = tree_iterator_next<USE_HINT>;
> >  break;
> >  case ITER_LT:
> >  case ITER_LE:
> >- it->base.next = tree_iterator_prev;
> >+ it->base.next = tree_iterator_prev<USE_HINT>;
> >  break;
> >  case ITER_GE:
> >  case ITER_GT:
> >- it->base.next = tree_iterator_next;
> >+ it->base.next = tree_iterator_next<USE_HINT>;
> >  break;
> >  default:
> >  /* The type was checked in initIterator */
> >@@ -375,15 +479,16 @@ tree_iterator_set_next_method(struct tree_iterator *it)
> >  }
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > tree_iterator_start(struct iterator *iterator, struct tuple **ret)
> > {
> >  *ret = NULL;
> >- struct memtx_tree_index *index =
> >- (struct memtx_tree_index *)iterator->index;
> >- struct tree_iterator *it = tree_iterator(iterator);
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
> >  it->base.next = tree_iterator_dummie;
> >- struct memtx_tree *tree = &index->tree;
> >+ memtx_tree_t<USE_HINT> *tree = &index->tree;
> >  enum iterator_type type = it->type;
> >  bool exact = false;
> >  assert(it->current.tuple == NULL);
> >@@ -423,8 +528,8 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
> >  }
> >  }
> > 
> >- struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,
> >- &it->tree_iterator);
> >+ struct memtx_tree_data<USE_HINT> *res =
> >+ memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
> >  if (!res)
> >  return 0;
> >  *ret = res->tuple;
> >@@ -454,14 +559,16 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
> > 
> > /* {{{ MemtxTree **********************************************************/
> > 
> >+template <int USE_HINT>
> > static void
> >-memtx_tree_index_free(struct memtx_tree_index *index)
> >+memtx_tree_index_free(struct memtx_tree_index<USE_HINT> *index)
> > {
> >  memtx_tree_destroy(&index->tree);
> >  free(index->build_array);
> >  free(index);
> > }
> > 
> >+template <int USE_HINT>
> > static void
> > memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
> > {
> >@@ -475,14 +582,14 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
> >  enum { YIELD_LOOPS = 10 };
> > #endif
> > 
> >- struct memtx_tree_index *index = container_of(task,
> >- struct memtx_tree_index, gc_task);
> >- struct memtx_tree *tree = &index->tree;
> >- struct memtx_tree_iterator *itr = &index->gc_iterator;
> >+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
> >+ struct memtx_tree_index<USE_HINT>, gc_task);
> >+ memtx_tree_t<USE_HINT> *tree = &index->tree;
> >+ memtx_tree_iterator_t<USE_HINT> *itr = &index->gc_iterator;
> > 
> >  unsigned int loops = 0;
> >  while (!memtx_tree_iterator_is_invalid(itr)) {
> >- struct memtx_tree_data *res =
> >+ struct memtx_tree_data<USE_HINT> *res =
> >  memtx_tree_iterator_get_elem(tree, itr);
> >  memtx_tree_iterator_next(tree, itr);
> >  tuple_unref(res->tuple);
> >@@ -494,23 +601,32 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
> >  *done = true;
> > }
> > 
> >+template <int USE_HINT>
> > static void
> > memtx_tree_index_gc_free(struct memtx_gc_task *task)
> > {
> >- struct memtx_tree_index *index = container_of(task,
> >- struct memtx_tree_index, gc_task);
> >+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
> >+ struct memtx_tree_index<USE_HINT>, gc_task);
> >  memtx_tree_index_free(index);
> > }
> > 
> >-static const struct memtx_gc_task_vtab memtx_tree_index_gc_vtab = {
> >- .run = memtx_tree_index_gc_run,
> >- .free = memtx_tree_index_gc_free,
> >+template <int USE_HINT>
> >+static struct memtx_gc_task_vtab * get_memtx_tree_index_gc_vtab()
> >+{
> >+ static memtx_gc_task_vtab tab =
> >+ {
> >+ .run = memtx_tree_index_gc_run<USE_HINT>,
> >+ .free = memtx_tree_index_gc_free<USE_HINT>,
> >+ };
> >+ return &tab;
> > };
> > 
> >+template <int USE_HINT>
> > static void
> > memtx_tree_index_destroy(struct index *base)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
> >  if (base->def->iid == 0) {
> >  /*
> >@@ -518,7 +634,7 @@ memtx_tree_index_destroy(struct index *base)
> >  * in the index, which may take a while. Schedule a
> >  * background task in order not to block tx thread.
> >  */
> >- index->gc_task.vtab = &memtx_tree_index_gc_vtab;
> >+ index->gc_task.vtab = get_memtx_tree_index_gc_vtab<USE_HINT>();
> >  index->gc_iterator = memtx_tree_iterator_first(&index->tree);
> >  memtx_engine_schedule_gc(memtx, &index->gc_task);
> >  } else {
> >@@ -530,10 +646,12 @@ memtx_tree_index_destroy(struct index *base)
> >  }
> > }
> > 
> >+template <int USE_HINT>
> > static void
> > memtx_tree_index_update_def(struct index *base)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  struct index_def *def = base->def;
> >  /*
> >  * We use extended key def for non-unique and nullable
> >@@ -553,51 +671,62 @@ memtx_tree_index_depends_on_pk(struct index *base)
> >  return !def->opts.is_unique || def->key_def->is_nullable;
> > }
> > 
> >+template <int USE_HINT>
> > static ssize_t
> > memtx_tree_index_size(struct index *base)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  return memtx_tree_size(&index->tree);
> > }
> > 
> >+template <int USE_HINT>
> > static ssize_t
> > memtx_tree_index_bsize(struct index *base)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  return memtx_tree_mem_used(&index->tree);
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > memtx_tree_index_random(struct index *base, uint32_t rnd, struct tuple **result)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >- struct memtx_tree_data *res = memtx_tree_random(&index->tree, rnd);
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >+ struct memtx_tree_data<USE_HINT> *res = memtx_tree_random(&index->tree, rnd);
> >  *result = res != NULL ? res->tuple : NULL;
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > static ssize_t
> > memtx_tree_index_count(struct index *base, enum iterator_type type,
> >  const char *key, uint32_t part_count)
> > {
> >  if (type == ITER_ALL)
> >- return memtx_tree_index_size(base); /* optimization */
> >+ return memtx_tree_index_size<USE_HINT>(base); /* optimization */
> >  return generic_index_count(base, type, key, part_count);
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > memtx_tree_index_get(struct index *base, const char *key,
> >  uint32_t part_count, struct tuple **result)
> > {
> >  assert(base->def->opts.is_unique &&
> >  part_count == base->def->key_def->part_count);
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> >- struct memtx_tree_key_data key_data;
> >+ struct memtx_tree_key_data<USE_HINT> key_data;
> >  key_data.key = key;
> >  key_data.part_count = part_count;
> >- key_data.hint = key_hint(key, part_count, cmp_def);
> >- struct memtx_tree_data *res = memtx_tree_find(&index->tree, &key_data);
> >+ if (USE_HINT)
> >+ key_data.set_hint(key_hint(key, part_count, cmp_def));
> >+ struct memtx_tree_data<USE_HINT> *res =
> >+ memtx_tree_find(&index->tree, &key_data);
> >  if (res == NULL) {
> >  *result = NULL;
> >  return 0;
> >@@ -611,18 +740,21 @@ memtx_tree_index_get(struct index *base, const char *key,
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
> >  struct tuple *new_tuple, enum dup_replace_mode mode,
> >  struct tuple **result)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> >  if (new_tuple) {
> >- struct memtx_tree_data new_data;
> >+ struct memtx_tree_data<USE_HINT> new_data;
> >  new_data.tuple = new_tuple;
> >- new_data.hint = tuple_hint(new_tuple, cmp_def);
> >- struct memtx_tree_data dup_data;
> >+ if (USE_HINT)
> >+ new_data.set_hint(tuple_hint(new_tuple, cmp_def));
> >+ struct memtx_tree_data<USE_HINT> dup_data;
> >  dup_data.tuple = NULL;
> > 
> >  /* Try to optimistically replace the new_tuple. */
> >@@ -652,9 +784,10 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
> >  }
> >  }
> >  if (old_tuple) {
> >- struct memtx_tree_data old_data;
> >+ struct memtx_tree_data<USE_HINT> old_data;
> >  old_data.tuple = old_tuple;
> >- old_data.hint = tuple_hint(old_tuple, cmp_def);
> >+ if (USE_HINT)
> >+ old_data.set_hint(tuple_hint(old_tuple, cmp_def));
> >  memtx_tree_delete(&index->tree, old_data);
> >  }
> >  *result = old_tuple;
> >@@ -667,13 +800,13 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
> >  * by all it's multikey indexes.
> >  */
> > static int
> >-memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
> >+memtx_tree_index_replace_multikey_one(struct memtx_tree_index<1> *index,
> >  struct tuple *old_tuple, struct tuple *new_tuple,
> >  enum dup_replace_mode mode, hint_t hint,
> >- struct memtx_tree_data *replaced_data,
> >+ struct memtx_tree_data<1> *replaced_data,
> >  bool *is_multikey_conflict)
> > {
> >- struct memtx_tree_data new_data, dup_data;
> >+ struct memtx_tree_data<1> new_data, dup_data;
> >  new_data.tuple = new_tuple;
> >  new_data.hint = hint;
> >  dup_data.tuple = NULL;
> >@@ -720,11 +853,11 @@ memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
> >  * delete operation is fault-tolerant.
> >  */
> > static void
> >-memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index *index,
> >+memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index<1> *index,
> >  struct tuple *new_tuple, struct tuple *replaced_tuple,
> >  int err_multikey_idx)
> > {
> >- struct memtx_tree_data data;
> >+ struct memtx_tree_data<1> data;
> >  if (replaced_tuple != NULL) {
> >  /* Restore replaced tuple index occurrences. */
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> >@@ -798,7 +931,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
> >  struct tuple *new_tuple, enum dup_replace_mode mode,
> >  struct tuple **result)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> >  *result = NULL;
> >  if (new_tuple != NULL) {
> >@@ -808,7 +941,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
> >  for (; (uint32_t) multikey_idx < multikey_count;
> >  multikey_idx++) {
> >  bool is_multikey_conflict;
> >- struct memtx_tree_data replaced_data;
> >+ struct memtx_tree_data<1> replaced_data;
> >  err = memtx_tree_index_replace_multikey_one(index,
> >  old_tuple, new_tuple, mode,
> >  multikey_idx, &replaced_data,
> >@@ -833,7 +966,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
> >  }
> >  }
> >  if (old_tuple != NULL) {
> >- struct memtx_tree_data data;
> >+ struct memtx_tree_data<1> data;
> >  data.tuple = old_tuple;
> >  uint32_t multikey_count =
> >  tuple_multikey_count(old_tuple, cmp_def);
> >@@ -865,7 +998,7 @@ struct func_key_undo {
> >  /** A link to organize entries in list. */
> >  struct rlist link;
> >  /** An inserted record copy. */
> >- struct memtx_tree_data key;
> >+ struct memtx_tree_data<1> key;
> > };
> > 
> > /** Allocate a new func_key_undo on given region. */
> >@@ -888,7 +1021,7 @@ func_key_undo_new(struct region *region)
> >  * return a given index object in it's original state.
> >  */
> > static void
> >-memtx_tree_func_index_replace_rollback(struct memtx_tree_index *index,
> >+memtx_tree_func_index_replace_rollback(struct memtx_tree_index<1> *index,
> >  struct rlist *old_keys,
> >  struct rlist *new_keys)
> > {
> >@@ -919,7 +1052,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
> >  struct tuple *new_tuple, enum dup_replace_mode mode,
> >  struct tuple **result)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
> >  struct index_def *index_def = index->base.def;
> >  assert(index_def->key_def->for_func_index);
> > 
> >@@ -952,7 +1085,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
> >  undo->key.hint = (hint_t)key;
> >  rlist_add(&new_keys, &undo->link);
> >  bool is_multikey_conflict;
> >- struct memtx_tree_data old_data;
> >+ struct memtx_tree_data<1> old_data;
> >  old_data.tuple = NULL;
> >  err = memtx_tree_index_replace_multikey_one(index,
> >  old_tuple, new_tuple,
> >@@ -1015,7 +1148,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
> >  if (key_list_iterator_create(&it, old_tuple, index_def, false,
> >  func_index_key_dummy_alloc) != 0)
> >  goto end;
> >- struct memtx_tree_data data, deleted_data;
> >+ struct memtx_tree_data<1> data, deleted_data;
> >  data.tuple = old_tuple;
> >  const char *key;
> >  while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
> >@@ -1040,11 +1173,13 @@ end:
> >  return rc;
> > }
> > 
> >+template <int USE_HINT>
> > static struct iterator *
> > memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
> >  const char *key, uint32_t part_count)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> > 
> >@@ -1064,42 +1199,47 @@ memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
> >  key = NULL;
> >  }
> > 
> >- struct tree_iterator *it = (struct tree_iterator *)
> >+ struct tree_iterator<USE_HINT> *it = (struct tree_iterator<USE_HINT> *)
> >  mempool_alloc(&memtx->iterator_pool);
> >  if (it == NULL) {
> >- diag_set(OutOfMemory, sizeof(struct tree_iterator),
> >+ diag_set(OutOfMemory, sizeof(struct tree_iterator<USE_HINT>),
> >  "memtx_tree_index", "iterator");
> >  return NULL;
> >  }
> >  iterator_create(&it->base, base);
> >  it->pool = &memtx->iterator_pool;
> >- it->base.next = tree_iterator_start;
> >- it->base.free = tree_iterator_free;
> >+ it->base.next = tree_iterator_start<USE_HINT>;
> >+ it->base.free = tree_iterator_free<USE_HINT>;
> >  it->type = type;
> >  it->key_data.key = key;
> >  it->key_data.part_count = part_count;
> >- it->key_data.hint = key_hint(key, part_count, cmp_def);
> >- it->tree_iterator = memtx_tree_invalid_iterator();
> >+ if (USE_HINT)
> >+ it->key_data.set_hint(key_hint(key, part_count, cmp_def));
> >+ invalidate_tree_iterator(&it->tree_iterator);
> >  it->current.tuple = NULL;
> >  return (struct iterator *)it;
> > }
> > 
> >+template <int USE_HINT>
> > static void
> > memtx_tree_index_begin_build(struct index *base)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  assert(memtx_tree_size(&index->tree) == 0);
> >  (void)index;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  if (size_hint < index->build_array_alloc_size)
> >  return 0;
> >- struct memtx_tree_data *tmp =
> >- (struct memtx_tree_data *)
> >+ struct memtx_tree_data<USE_HINT> *tmp =
> >+ (struct memtx_tree_data<USE_HINT> *)
> >  realloc(index->build_array, size_hint * sizeof(*tmp));
> >  if (tmp == NULL) {
> >  diag_set(OutOfMemory, size_hint * sizeof(*tmp),
> >@@ -1111,14 +1251,15 @@ memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > /** Initialize the next element of the index build_array. */
> > static int
> >-memtx_tree_index_build_array_append(struct memtx_tree_index *index,
> >+memtx_tree_index_build_array_append(struct memtx_tree_index<USE_HINT> *index,
> >  struct tuple *tuple, hint_t hint)
> > {
> >  if (index->build_array == NULL) {
> >  index->build_array =
> >- (struct memtx_tree_data *)malloc(MEMTX_EXTENT_SIZE);
> >+ (struct memtx_tree_data<USE_HINT> *)malloc(MEMTX_EXTENT_SIZE);
> >  if (index->build_array == NULL) {
> >  diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
> >  "memtx_tree_index", "build_next");
> >@@ -1131,8 +1272,8 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
> >  if (index->build_array_size == index->build_array_alloc_size) {
> >  index->build_array_alloc_size = index->build_array_alloc_size +
> >  DIV_ROUND_UP(index->build_array_alloc_size, 2);
> >- struct memtx_tree_data *tmp =
> >- (struct memtx_tree_data *)realloc(index->build_array,
> >+ struct memtx_tree_data<USE_HINT> *tmp =
> >+ (struct memtx_tree_data<USE_HINT> *)realloc(index->build_array,
> >  index->build_array_alloc_size * sizeof(*tmp));
> >  if (tmp == NULL) {
> >  diag_set(OutOfMemory, index->build_array_alloc_size *
> >@@ -1141,17 +1282,20 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
> >  }
> >  index->build_array = tmp;
> >  }
> >- struct memtx_tree_data *elem =
> >+ struct memtx_tree_data<USE_HINT> *elem =
> >  &index->build_array[index->build_array_size++];
> >  elem->tuple = tuple;
> >- elem->hint = hint;
> >+ if (USE_HINT)
> >+ elem->set_hint(hint);
> >  return 0;
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> >  return memtx_tree_index_build_array_append(index, tuple,
> >  tuple_hint(tuple, cmp_def));
> >@@ -1160,7 +1304,7 @@ memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
> > static int
> > memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> >  uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);
> >  for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;
> >@@ -1175,7 +1319,7 @@ memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
> > static int
> > memtx_tree_func_index_build_next(struct index *base, struct tuple *tuple)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
> >  struct index_def *index_def = index->base.def;
> >  assert(index_def->key_def->for_func_index);
> > 
> >@@ -1211,8 +1355,9 @@ error:
> >  * of equal tuples (in terms of index's cmp_def and have same
> >  * tuple pointer). The build_array is expected to be sorted.
> >  */
> >+template <int USE_HINT>
> > static void
> >-memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
> >+memtx_tree_index_build_array_deduplicate(struct memtx_tree_index<USE_HINT> *index,
> >  void (*destroy)(struct tuple *tuple, const char *hint))
> > {
> >  if (index->build_array_size == 0)
> >@@ -1246,13 +1391,16 @@ memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
> >  index->build_array_size = w_idx + 1;
> > }
> > 
> >+template <int USE_HINT>
> > static void
> > memtx_tree_index_end_build(struct index *base)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
> >  qsort_arg(index->build_array, index->build_array_size,
> >- sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);
> >+ sizeof(index->build_array[0]),
> >+ memtx_tree_qcompare<USE_HINT>, cmp_def);
> >  if (cmp_def->is_multikey) {
> >  /*
> >  * Multikey index may have equal(in terms of
> >@@ -1261,9 +1409,9 @@ memtx_tree_index_end_build(struct index *base)
> >  * the following memtx_tree_build assumes that
> >  * all keys are unique.
> >  */
> >- memtx_tree_index_build_array_deduplicate(index, NULL);
> >+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index, NULL);
> >  } else if (cmp_def->for_func_index) {
> >- memtx_tree_index_build_array_deduplicate(index,
> >+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index,
> >  tuple_chunk_delete);
> >  }
> >  memtx_tree_build(&index->tree, index->build_array,
> >@@ -1275,19 +1423,21 @@ memtx_tree_index_end_build(struct index *base)
> >  index->build_array_alloc_size = 0;
> > }
> > 
> >+template <int USE_HINT>
> > struct tree_snapshot_iterator {
> >  struct snapshot_iterator base;
> >- struct memtx_tree_index *index;
> >- struct memtx_tree_iterator tree_iterator;
> >+ struct memtx_tree_index<USE_HINT> *index;
> >+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
> >  struct memtx_tx_snapshot_cleaner cleaner;
> > };
> > 
> >+template <int USE_HINT>
> > static void
> > tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
> > {
> >- assert(iterator->free == tree_snapshot_iterator_free);
> >- struct tree_snapshot_iterator *it =
> >- (struct tree_snapshot_iterator *)iterator;
> >+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
> >+ struct tree_snapshot_iterator<USE_HINT> *it =
> >+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
> >  memtx_leave_delayed_free_mode((struct memtx_engine *)
> >  it->index->base.engine);
> >  memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);
> >@@ -1296,17 +1446,18 @@ tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
> >  free(iterator);
> > }
> > 
> >+template <int USE_HINT>
> > static int
> > tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
> >  const char **data, uint32_t *size)
> > {
> >- assert(iterator->free == tree_snapshot_iterator_free);
> >- struct tree_snapshot_iterator *it =
> >- (struct tree_snapshot_iterator *)iterator;
> >- struct memtx_tree *tree = &it->index->tree;
> >+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
> >+ struct tree_snapshot_iterator<USE_HINT> *it =
> >+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
> >+ memtx_tree_t<USE_HINT> *tree = &it->index->tree;
> > 
> >  while (true) {
> >- struct memtx_tree_data *res =
> >+ struct memtx_tree_data<USE_HINT> *res =
> >  memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
> > 
> >  if (res == NULL) {
> >@@ -1333,14 +1484,18 @@ tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
> >  * index modifications will not affect the iteration results.
> >  * Must be destroyed by iterator->free after usage.
> >  */
> >+template <int USE_HINT>
> > static struct snapshot_iterator *
> > memtx_tree_index_create_snapshot_iterator(struct index *base)
> > {
> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
> >- struct tree_snapshot_iterator *it =
> >- (struct tree_snapshot_iterator *) calloc(1, sizeof(*it));
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)base;
> >+ struct tree_snapshot_iterator<USE_HINT> *it =
> >+ (struct tree_snapshot_iterator<USE_HINT> *)
> >+ calloc(1, sizeof(*it));
> >  if (it == NULL) {
> >- diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),
> >+ diag_set(OutOfMemory,
> >+ sizeof(struct tree_snapshot_iterator<USE_HINT>),
> >  "memtx_tree_index", "create_snapshot_iterator");
> >  return NULL;
> >  }
> >@@ -1352,8 +1507,8 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
> >  return NULL;
> >  }
> > 
> >- it->base.free = tree_snapshot_iterator_free;
> >- it->base.next = tree_snapshot_iterator_next;
> >+ it->base.free = tree_snapshot_iterator_free<USE_HINT>;
> >+ it->base.next = tree_snapshot_iterator_next<USE_HINT>;
> >  it->index = index;
> >  index_ref(base);
> >  it->tree_iterator = memtx_tree_iterator_first(&index->tree);
> >@@ -1362,94 +1517,124 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
> >  return (struct snapshot_iterator *) it;
> > }
> > 
> >-static const struct index_vtab memtx_tree_index_vtab = {
> >- /* .destroy = */ memtx_tree_index_destroy,
> >+static const struct index_vtab memtx_tree_no_hint_index_vtab = {
> >+ /* .destroy = */ memtx_tree_index_destroy<0>,
> >  /* .commit_create = */ generic_index_commit_create,
> >  /* .abort_create = */ generic_index_abort_create,
> >  /* .commit_modify = */ generic_index_commit_modify,
> >  /* .commit_drop = */ generic_index_commit_drop,
> >- /* .update_def = */ memtx_tree_index_update_def,
> >+ /* .update_def = */ memtx_tree_index_update_def<0>,
> >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
> >  /* .def_change_requires_rebuild = */
> >  memtx_index_def_change_requires_rebuild,
> >- /* .size = */ memtx_tree_index_size,
> >- /* .bsize = */ memtx_tree_index_bsize,
> >+ /* .size = */ memtx_tree_index_size<0>,
> >+ /* .bsize = */ memtx_tree_index_bsize<0>,
> >  /* .min = */ generic_index_min,
> >  /* .max = */ generic_index_max,
> >- /* .random = */ memtx_tree_index_random,
> >- /* .count = */ memtx_tree_index_count,
> >- /* .get = */ memtx_tree_index_get,
> >- /* .replace = */ memtx_tree_index_replace,
> >- /* .create_iterator = */ memtx_tree_index_create_iterator,
> >+ /* .random = */ memtx_tree_index_random<0>,
> >+ /* .count = */ memtx_tree_index_count<0>,
> >+ /* .get = */ memtx_tree_index_get<0>,
> >+ /* .replace = */ memtx_tree_index_replace<0>,
> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<0>,
> >  /* .create_snapshot_iterator = */
> >- memtx_tree_index_create_snapshot_iterator,
> >+ memtx_tree_index_create_snapshot_iterator<0>,
> >  /* .stat = */ generic_index_stat,
> >  /* .compact = */ generic_index_compact,
> >  /* .reset_stat = */ generic_index_reset_stat,
> >- /* .begin_build = */ memtx_tree_index_begin_build,
> >- /* .reserve = */ memtx_tree_index_reserve,
> >- /* .build_next = */ memtx_tree_index_build_next,
> >- /* .end_build = */ memtx_tree_index_end_build,
> >+ /* .begin_build = */ memtx_tree_index_begin_build<0>,
> >+ /* .reserve = */ memtx_tree_index_reserve<0>,
> >+ /* .build_next = */ memtx_tree_index_build_next<0>,
> >+ /* .end_build = */ memtx_tree_index_end_build<0>,
> >+};
> >+
> >+static const struct index_vtab memtx_tree_use_hint_index_vtab = {
> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
> >+ /* .commit_create = */ generic_index_commit_create,
> >+ /* .abort_create = */ generic_index_abort_create,
> >+ /* .commit_modify = */ generic_index_commit_modify,
> >+ /* .commit_drop = */ generic_index_commit_drop,
> >+ /* .update_def = */ memtx_tree_index_update_def<1>,
> >+ /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
> >+ /* .def_change_requires_rebuild = */
> >+ memtx_index_def_change_requires_rebuild,
> >+ /* .size = */ memtx_tree_index_size<1>,
> >+ /* .bsize = */ memtx_tree_index_bsize<1>,
> >+ /* .min = */ generic_index_min,
> >+ /* .max = */ generic_index_max,
> >+ /* .random = */ memtx_tree_index_random<1>,
> >+ /* .count = */ memtx_tree_index_count<1>,
> >+ /* .get = */ memtx_tree_index_get<1>,
> >+ /* .replace = */ memtx_tree_index_replace<1>,
> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
> >+ /* .create_snapshot_iterator = */
> >+ memtx_tree_index_create_snapshot_iterator<1>,
> >+ /* .stat = */ generic_index_stat,
> >+ /* .compact = */ generic_index_compact,
> >+ /* .reset_stat = */ generic_index_reset_stat,
> >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
> >+ /* .reserve = */ memtx_tree_index_reserve<1>,
> >+ /* .build_next = */ memtx_tree_index_build_next<1>,
> >+ /* .end_build = */ memtx_tree_index_end_build<1>,
> > };
> > 
> > static const struct index_vtab memtx_tree_index_multikey_vtab = {
> >- /* .destroy = */ memtx_tree_index_destroy,
> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
> >  /* .commit_create = */ generic_index_commit_create,
> >  /* .abort_create = */ generic_index_abort_create,
> >  /* .commit_modify = */ generic_index_commit_modify,
> >  /* .commit_drop = */ generic_index_commit_drop,
> >- /* .update_def = */ memtx_tree_index_update_def,
> >+ /* .update_def = */ memtx_tree_index_update_def<1>,
> >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
> >  /* .def_change_requires_rebuild = */
> >  memtx_index_def_change_requires_rebuild,
> >- /* .size = */ memtx_tree_index_size,
> >- /* .bsize = */ memtx_tree_index_bsize,
> >+ /* .size = */ memtx_tree_index_size<1>,
> >+ /* .bsize = */ memtx_tree_index_bsize<1>,
> >  /* .min = */ generic_index_min,
> >  /* .max = */ generic_index_max,
> >- /* .random = */ memtx_tree_index_random,
> >- /* .count = */ memtx_tree_index_count,
> >- /* .get = */ memtx_tree_index_get,
> >+ /* .random = */ memtx_tree_index_random<1>,
> >+ /* .count = */ memtx_tree_index_count<1>,
> >+ /* .get = */ memtx_tree_index_get<1>,
> >  /* .replace = */ memtx_tree_index_replace_multikey,
> >- /* .create_iterator = */ memtx_tree_index_create_iterator,
> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
> >  /* .create_snapshot_iterator = */
> >- memtx_tree_index_create_snapshot_iterator,
> >+ memtx_tree_index_create_snapshot_iterator<1>,
> >  /* .stat = */ generic_index_stat,
> >  /* .compact = */ generic_index_compact,
> >  /* .reset_stat = */ generic_index_reset_stat,
> >- /* .begin_build = */ memtx_tree_index_begin_build,
> >- /* .reserve = */ memtx_tree_index_reserve,
> >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
> >+ /* .reserve = */ memtx_tree_index_reserve<1>,
> >  /* .build_next = */ memtx_tree_index_build_next_multikey,
> >- /* .end_build = */ memtx_tree_index_end_build,
> >+ /* .end_build = */ memtx_tree_index_end_build<1>,
> > };
> > 
> > static const struct index_vtab memtx_tree_func_index_vtab = {
> >- /* .destroy = */ memtx_tree_index_destroy,
> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
> >  /* .commit_create = */ generic_index_commit_create,
> >  /* .abort_create = */ generic_index_abort_create,
> >  /* .commit_modify = */ generic_index_commit_modify,
> >  /* .commit_drop = */ generic_index_commit_drop,
> >- /* .update_def = */ memtx_tree_index_update_def,
> >+ /* .update_def = */ memtx_tree_index_update_def<1>,
> >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
> >  /* .def_change_requires_rebuild = */
> >  memtx_index_def_change_requires_rebuild,
> >- /* .size = */ memtx_tree_index_size,
> >- /* .bsize = */ memtx_tree_index_bsize,
> >+ /* .size = */ memtx_tree_index_size<1>,
> >+ /* .bsize = */ memtx_tree_index_bsize<1>,
> >  /* .min = */ generic_index_min,
> >  /* .max = */ generic_index_max,
> >- /* .random = */ memtx_tree_index_random,
> >- /* .count = */ memtx_tree_index_count,
> >- /* .get = */ memtx_tree_index_get,
> >+ /* .random = */ memtx_tree_index_random<1>,
> >+ /* .count = */ memtx_tree_index_count<1>,
> >+ /* .get = */ memtx_tree_index_get<1>,
> >  /* .replace = */ memtx_tree_func_index_replace,
> >- /* .create_iterator = */ memtx_tree_index_create_iterator,
> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
> >  /* .create_snapshot_iterator = */
> >- memtx_tree_index_create_snapshot_iterator,
> >+ memtx_tree_index_create_snapshot_iterator<1>,
> >  /* .stat = */ generic_index_stat,
> >  /* .compact = */ generic_index_compact,
> >  /* .reset_stat = */ generic_index_reset_stat,
> >- /* .begin_build = */ memtx_tree_index_begin_build,
> >- /* .reserve = */ memtx_tree_index_reserve,
> >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
> >+ /* .reserve = */ memtx_tree_index_reserve<1>,
> >  /* .build_next = */ memtx_tree_func_index_build_next,
> >- /* .end_build = */ memtx_tree_index_end_build,
> >+ /* .end_build = */ memtx_tree_index_end_build<1>,
> > };
> > 
> > /**
> >@@ -1459,7 +1644,7 @@ static const struct index_vtab memtx_tree_func_index_vtab = {
> >  * key defintion is not completely initialized at that moment).
> >  */
> > static const struct index_vtab memtx_tree_disabled_index_vtab = {
> >- /* .destroy = */ memtx_tree_index_destroy,
> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
> >  /* .commit_create = */ generic_index_commit_create,
> >  /* .abort_create = */ generic_index_abort_create,
> >  /* .commit_modify = */ generic_index_commit_modify,
> >@@ -1488,27 +1673,19 @@ static const struct index_vtab memtx_tree_disabled_index_vtab = {
> >  /* .end_build = */ generic_index_end_build,
> > };
> > 
> >-struct index *
> >-memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
> >+template <int USE_HINT>
> >+static struct index *
> >+memtx_tree_index_new_tpl(struct memtx_engine *memtx, struct index_def *def,
> >+ const struct index_vtab *vtab)
> > {
> >- struct memtx_tree_index *index =
> >- (struct memtx_tree_index *)calloc(1, sizeof(*index));
> >+ struct memtx_tree_index<USE_HINT> *index =
> >+ (struct memtx_tree_index<USE_HINT> *)
> >+ calloc(1, sizeof(*index));
> >  if (index == NULL) {
> >  diag_set(OutOfMemory, sizeof(*index),
> >  "malloc", "struct memtx_tree_index");
> >  return NULL;
> >  }
> >- const struct index_vtab *vtab;
> >- if (def->key_def->for_func_index) {
> >- if (def->key_def->func_index_func == NULL)
> >- vtab = &memtx_tree_disabled_index_vtab;
> >- else
> >- vtab = &memtx_tree_func_index_vtab;
> >- } else if (def->key_def->is_multikey) {
> >- vtab = &memtx_tree_index_multikey_vtab;
> >- } else {
> >- vtab = &memtx_tree_index_vtab;
> >- }
> >  if (index_create(&index->base, (struct engine *)memtx,
> >  vtab, def) != 0) {
> >  free(index);
> >@@ -1524,3 +1701,23 @@ memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
> >  memtx_index_extent_free, memtx);
> >  return &index->base;
> > }
> >+
> >+struct index *
> >+memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
> >+{
> >+ const struct index_vtab *vtab;
> >+ if (def->key_def->for_func_index) {
> >+ if (def->key_def->func_index_func == NULL)
> >+ vtab = &memtx_tree_disabled_index_vtab;
> >+ else
> >+ vtab = &memtx_tree_func_index_vtab;
> >+ } else if (def->key_def->is_multikey) {
> >+ vtab = &memtx_tree_index_multikey_vtab;
> >+ } else if (def->opts.hint) {
> >+ vtab = &memtx_tree_use_hint_index_vtab;
> >+ } else {
> >+ vtab = &memtx_tree_no_hint_index_vtab;
> >+ return memtx_tree_index_new_tpl<0>(memtx, def, vtab);
> >+ }
> >+ return memtx_tree_index_new_tpl<1>(memtx, def, vtab);
> >+}
> >diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
> >index ef5ae3d..0bb803a 100644
> >--- a/src/lib/salad/bps_tree.h
> >+++ b/src/lib/salad/bps_tree.h
> >@@ -168,6 +168,17 @@
> > 
> > /* {{{ BPS-tree interface settings */
> > /**
> >+ * Optional namespace for structs and functions.
> >+ * Is set, struct and functions will have BPS_TREE_NAMESPACE:: prefix.
> >+ * For example one can #define BPS_TREE_NAMESPACE my along with
> >+ * #define BPS_TREE_NAME _test, and use then
> >+ * struct my::bps_tree_test my_tree;
> >+ * my::bps_tree_test_create(&my_tree, ...);
> >+ *
> >+ * #define BPS_TREE_NAMESPACE
> >+ */
> >+
> >+/**
> >  * Custom name for structs and functions.
> >  * Struct and functions will have bps_tree##BPS_TREE_NAME name or prefix.
> >  * For example one can #define BPS_TREE_NAME _test, and use then
> >@@ -300,6 +311,10 @@
> > 
> > /* }}} */
> > 
> >+#ifdef BPS_TREE_NAMESPACE
> >+namespace BPS_TREE_NAMESPACE {
> >+#endif
> >+
> > /* {{{ BPS-tree internal settings */
> > typedef int16_t bps_tree_pos_t;
> > typedef uint32_t bps_tree_block_id_t;
> >@@ -6188,3 +6203,7 @@ bps_tree_debug_check_internal_functions(bool assertme)
> > #undef bps_tree_debug_check_insert_and_move_to_left_inner
> > 
> > /* }}} */
> >+
> >+#ifdef BPS_TREE_NAMESPACE
> >+} /* namespace BPS_TREE_NAMESPACE { */
> >+#endif
> >diff --git a/test/box/alter.result b/test/box/alter.result
> >index 237c2d8..5b412a7 100644
> >--- a/test/box/alter.result
> >+++ b/test/box/alter.result
> >@@ -680,8 +680,9 @@ s.index.pk
> >   - type: unsigned
> >     is_nullable: false
> >     fieldno: 2
> >- type: TREE
> >+ hint: true
> >   id: 0
> >+ type: TREE
> >   space_id: 731
> >   name: pk
> > ...
> >@@ -710,9 +711,10 @@ s.index.pk
> >   - type: unsigned
> >     is_nullable: false
> >     fieldno: 1
> >- space_id: 731
> >+ hint: true
> >   id: 0
> >   type: TREE
> >+ space_id: 731
> >   name: pk
> > ...
> > s.index.secondary
> >@@ -722,8 +724,9 @@ s.index.secondary
> >   - type: unsigned
> >     is_nullable: false
> >     fieldno: 2
> >- type: TREE
> >+ hint: true
> >   id: 1
> >+ type: TREE
> >   space_id: 731
> >   name: secondary
> > ...
> >@@ -1559,3 +1562,97 @@ assert(err:match('does not exist') ~= nil)
> > ---
> > - true
> > ...
> >+-- hint
> >+s = box.schema.create_space('test');
> >+---
> >+...
> >+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
> >+---
> >+- true
> >+...
> >+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
> >+---
> >+- true
> >+...
> >+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
> >+---
> >+- false
> >+...
> >+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
> >+---
> >+- false
> >+...
> >+s.index.test1.hint
> >+---
> >+- true
> >+...
> >+s.index.test2.hint
> >+---
> >+- true
> >+...
> >+s.index.test3.hint
> >+---
> >+- false
> >+...
> >+s.index.test4.hint
> >+---
> >+- false
> >+...
> >+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
> >+---
> >+...
> >+s.index.test1:bsize() == s.index.test2:bsize()
> >+---
> >+- true
> >+...
> >+s.index.test1:bsize() > s.index.test3:bsize()
> >+---
> >+- true
> >+...
> >+s.index.test1:bsize() > s.index.test4:bsize()
> >+---
> >+- true
> >+...
> >+s.index.test1:alter{hint=false}
> >+---
> >+...
> >+s.index.test2:alter{hint=true}
> >+---
> >+...
> >+s.index.test3:alter{name='test33', hint=false}
> >+---
> >+...
> >+s.index.test4:alter{hint=true}
> >+---
> >+...
> >+s.index.test1.hint
> >+---
> >+- false
> >+...
> >+s.index.test2.hint
> >+---
> >+- true
> >+...
> >+s.index.test33.hint
> >+---
> >+- false
> >+...
> >+s.index.test4.hint
> >+---
> >+- true
> >+...
> >+s.index.test1:bsize() < s.index.test2:bsize()
> >+---
> >+- true
> >+...
> >+s.index.test1:bsize() == s.index.test33:bsize()
> >+---
> >+- true
> >+...
> >+s.index.test1:bsize() < s.index.test4:bsize()
> >+---
> >+- true
> >+...
> >+s:drop()
> >+---
> >+...
> >diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
> >index abd08e2..2114186 100644
> >--- a/test/box/alter.test.lua
> >+++ b/test/box/alter.test.lua
> >@@ -620,3 +620,37 @@ s:drop()
> > s:alter({})
> > ok, err = pcall(box.schema.space.alter, s.id, {})
> > assert(err:match('does not exist') ~= nil)
> >+
> >+-- hint
> >+s = box.schema.create_space('test');
> >+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
> >+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
> >+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
> >+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
> >+
> >+s.index.test1.hint
> >+s.index.test2.hint
> >+s.index.test3.hint
> >+s.index.test4.hint
> >+
> >+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
> >+
> >+s.index.test1:bsize() == s.index.test2:bsize()
> >+s.index.test1:bsize() > s.index.test3:bsize()
> >+s.index.test1:bsize() > s.index.test4:bsize()
> >+
> >+s.index.test1:alter{hint=false}
> >+s.index.test2:alter{hint=true}
> >+s.index.test3:alter{name='test33', hint=false}
> >+s.index.test4:alter{hint=true}
> >+
> >+s.index.test1.hint
> >+s.index.test2.hint
> >+s.index.test33.hint
> >+s.index.test4.hint
> >+
> >+s.index.test1:bsize() < s.index.test2:bsize()
> >+s.index.test1:bsize() == s.index.test33:bsize()
> >+s.index.test1:bsize() < s.index.test4:bsize()
> >+
> >+s:drop()
> >\ No newline at end of file
> >diff --git a/test/box/errinj.result b/test/box/errinj.result
> >index 613d22c..b8c2476 100644
> >--- a/test/box/errinj.result
> >+++ b/test/box/errinj.result
> >@@ -1774,9 +1774,10 @@ rtreespace:create_index('pk', {if_not_exists = true})
> >   - type: unsigned
> >     is_nullable: false
> >     fieldno: 1
> >+ hint: true
> >   id: 0
> >- space_id: 512
> >   type: TREE
> >+ space_id: 512
> >   name: pk
> > ...
> > rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array'},unique = false, if_not_exists = true,})
> >diff --git a/test/box/tree_pk.result b/test/box/tree_pk.result
> >index df3c78b..18cb607 100644
> >--- a/test/box/tree_pk.result
> >+++ b/test/box/tree_pk.result
> >@@ -852,3 +852,317 @@ box.internal.collation.drop('test')
> > box.internal.collation.drop('test-ci')
> > ---
> > ...
> >+-- hints
> >+s = box.schema.space.create('test')
> >+---
> >+...
> >+s:create_index('test', {type = 'tree', hint = 'true'} )
> >+---
> >+- error: Illegal parameters, options parameter 'hint' should be of type boolean
> >+...
> >+s:create_index('test', {type = 'hash', hint = true} )
> >+---
> >+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
> >+ with memtx tree index'
> >+...
> >+s:create_index('test', {type = 'hash'}):alter({hint = true})
> >+---
> >+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
> >+ with memtx tree index'
> >+...
> >+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
> >+---
> >+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
> >+ can''t use hints'
> >+...
> >+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
> >+---
> >+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
> >+ can''t use hints'
> >+...
> >+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
> >+---
> >+...
> >+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
> >+---
> >+...
> >+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
> >+---
> >+- error: 'Can''t create or modify index ''func'' in space ''test'': functional index
> >+ can''t use hints'
> >+...
> >+s:drop()
> >+---
> >+...
> >+s = box.schema.space.create('test', {engine = 'vinyl'})
> >+---
> >+...
> >+s:create_index('test', {type = 'tree', hint = true} )
> >+---
> >+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
> >+ with memtx tree index'
> >+...
> >+s:drop()
> >+---
> >+...
> >+-- numeric hints
> >+s1 = box.schema.space.create('test1')
> >+---
> >+...
> >+s1:create_index('test', {type = 'tree', hint = false}).hint
> >+---
> >+- false
> >+...
> >+s2 = box.schema.space.create('test2')
> >+---
> >+...
> >+s2:create_index('test', {type = 'tree'}).hint
> >+---
> >+- true
> >+...
> >+N = 2000
> >+---
> >+...
> >+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
> >+---
> >+...
> >+s1:count() == s2:count()
> >+---
> >+- true
> >+...
> >+good = true r1 = s1:select{} r2 = s2:select{}
> >+---
> >+...
> >+if #r1 ~= #r2 then good = false end
> >+---
> >+...
> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+r1 = nil r2 = nil
> >+---
> >+...
> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
> >+---
> >+...
> >+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+s1.index[0]:bsize() < s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1.index[0]:alter{hint=true}
> >+---
> >+...
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s2.index[0]:alter{hint=false}
> >+---
> >+...
> >+s1.index[0]:bsize() > s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1.index[0]:alter{hint=false}
> >+---
> >+...
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1:drop()
> >+---
> >+...
> >+s2:drop()
> >+---
> >+...
> >+-- string hints
> >+s1 = box.schema.space.create('test1')
> >+---
> >+...
> >+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
> >+---
> >+- false
> >+...
> >+s2 = box.schema.space.create('test2')
> >+---
> >+...
> >+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
> >+---
> >+- true
> >+...
> >+N = 1000
> >+---
> >+...
> >+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
> >+---
> >+...
> >+s1:count() == s2:count()
> >+---
> >+- true
> >+...
> >+good = true r1 = s1:select{} r2 = s2:select{}
> >+---
> >+...
> >+if #r1 ~= #r2 then good = false end
> >+---
> >+...
> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+r1 = nil r2 = nil
> >+---
> >+...
> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
> >+---
> >+...
> >+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+s1.index[0]:bsize() < s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1.index[0]:alter{hint=true}
> >+---
> >+...
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s2.index[0]:alter{hint=false}
> >+---
> >+...
> >+s1.index[0]:bsize() > s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1.index[0]:alter{hint=false}
> >+---
> >+...
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1:drop()
> >+---
> >+...
> >+s2:drop()
> >+---
> >+...
> >+-- string with collation hints
> >+s1 = box.schema.space.create('test1')
> >+---
> >+...
> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
> >+---
> >+- false
> >+...
> >+s2 = box.schema.space.create('test2')
> >+---
> >+...
> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
> >+---
> >+- true
> >+...
> >+N = 1000
> >+---
> >+...
> >+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
> >+---
> >+...
> >+syms_size = #syms
> >+---
> >+...
> >+len = 20
> >+---
> >+...
> >+vals = {}
> >+---
> >+...
> >+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
> >+---
> >+...
> >+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
> >+---
> >+...
> >+s1:count() == s2:count()
> >+---
> >+- true
> >+...
> >+good = true r1 = s1:select{} r2 = s2:select{}
> >+---
> >+...
> >+if #r1 ~= #r2 then good = false end
> >+---
> >+...
> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+r1 = nil r2 = nil
> >+---
> >+...
> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
> >+---
> >+...
> >+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+s1.index[0]:bsize() < s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1.index[0]:alter{hint=true}
> >+---
> >+...
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s2.index[0]:alter{hint=false}
> >+---
> >+...
> >+s1.index[0]:bsize() > s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1.index[0]:alter{hint=false}
> >+---
> >+...
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+---
> >+- true
> >+...
> >+s1:drop()
> >+---
> >+...
> >+s2:drop()
> >+---
> >+...
> >diff --git a/test/box/tree_pk.test.lua b/test/box/tree_pk.test.lua
> >index 1190ab4..2f22ed9 100644
> >--- a/test/box/tree_pk.test.lua
> >+++ b/test/box/tree_pk.test.lua
> >@@ -301,3 +301,118 @@ s:drop()
> > 
> > box.internal.collation.drop('test')
> > box.internal.collation.drop('test-ci')
> >+
> >+-- hints
> >+s = box.schema.space.create('test')
> >+s:create_index('test', {type = 'tree', hint = 'true'} )
> >+s:create_index('test', {type = 'hash', hint = true} )
> >+s:create_index('test', {type = 'hash'}):alter({hint = true})
> >+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
> >+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
> >+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
> >+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
> >+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
> >+s:drop()
> >+
> >+s = box.schema.space.create('test', {engine = 'vinyl'})
> >+s:create_index('test', {type = 'tree', hint = true} )
> >+s:drop()
> >+
> >+-- numeric hints
> >+s1 = box.schema.space.create('test1')
> >+s1:create_index('test', {type = 'tree', hint = false}).hint
> >+s2 = box.schema.space.create('test2')
> >+s2:create_index('test', {type = 'tree'}).hint
> >+
> >+N = 2000
> >+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
> >+s1:count() == s2:count()
> >+
> >+good = true r1 = s1:select{} r2 = s2:select{}
> >+if #r1 ~= #r2 then good = false end
> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
> >+good
> >+r1 = nil r2 = nil
> >+
> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
> >+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
> >+good
> >+
> >+s1.index[0]:bsize() < s2.index[0]:bsize()
> >+s1.index[0]:alter{hint=true}
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+s2.index[0]:alter{hint=false}
> >+s1.index[0]:bsize() > s2.index[0]:bsize()
> >+s1.index[0]:alter{hint=false}
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+
> >+s1:drop()
> >+s2:drop()
> >+
> >+-- string hints
> >+s1 = box.schema.space.create('test1')
> >+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
> >+s2 = box.schema.space.create('test2')
> >+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
> >+
> >+N = 1000
> >+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
> >+s1:count() == s2:count()
> >+
> >+good = true r1 = s1:select{} r2 = s2:select{}
> >+if #r1 ~= #r2 then good = false end
> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
> >+good
> >+r1 = nil r2 = nil
> >+
> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
> >+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
> >+good
> >+
> >+s1.index[0]:bsize() < s2.index[0]:bsize()
> >+s1.index[0]:alter{hint=true}
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+s2.index[0]:alter{hint=false}
> >+s1.index[0]:bsize() > s2.index[0]:bsize()
> >+s1.index[0]:alter{hint=false}
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+
> >+s1:drop()
> >+s2:drop()
> >+
> >+-- string with collation hints
> >+s1 = box.schema.space.create('test1')
> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
> >+s2 = box.schema.space.create('test2')
> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
> >+
> >+N = 1000
> >+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
> >+syms_size = #syms
> >+len = 20
> >+vals = {}
> >+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
> >+
> >+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
> >+s1:count() == s2:count()
> >+
> >+good = true r1 = s1:select{} r2 = s2:select{}
> >+if #r1 ~= #r2 then good = false end
> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
> >+good
> >+r1 = nil r2 = nil
> >+
> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
> >+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
> >+good
> >+
> >+s1.index[0]:bsize() < s2.index[0]:bsize()
> >+s1.index[0]:alter{hint=true}
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+s2.index[0]:alter{hint=false}
> >+s1.index[0]:bsize() > s2.index[0]:bsize()
> >+s1.index[0]:alter{hint=false}
> >+s1.index[0]:bsize() == s2.index[0]:bsize()
> >+
> >+s1:drop()
> >+s2:drop()
> >diff --git a/test/box/tree_pk_multipart.result b/test/box/tree_pk_multipart.result
> >index 93219f6..3f6bb94 100644
> >--- a/test/box/tree_pk_multipart.result
> >+++ b/test/box/tree_pk_multipart.result
> >@@ -542,3 +542,156 @@ space:drop()
> > space = nil
> > ---
> > ...
> >+-- hints
> >+test_run:cmd("setopt delimiter ';'")
> >+---
> >+- true
> >+...
> >+function equal(res1, res2)
> >+ if #res1 ~= #res2 then
> >+ return false
> >+ end
> >+ for k,v in pairs(res1) do
> >+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
> >+ return false
> >+ end
> >+ end
> >+ return true
> >+end
> >+test_run:cmd("setopt delimiter ''");
> >+---
> >+...
> >+-- num num
> >+N1 = 100
> >+---
> >+...
> >+t1 = {}
> >+---
> >+...
> >+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
> >+---
> >+...
> >+N2 = 5
> >+---
> >+...
> >+t2 = {}
> >+---
> >+...
> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
> >+---
> >+...
> >+s1 = box.schema.space.create('test1')
> >+---
> >+...
> >+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
> >+---
> >+- false
> >+...
> >+s2 = box.schema.space.create('test2')
> >+---
> >+...
> >+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
> >+---
> >+- true
> >+...
> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
> >+---
> >+...
> >+s1:count() == s2:count()
> >+---
> >+- true
> >+...
> >+equal(s1:select{}, s2:select{})
> >+---
> >+- true
> >+...
> >+good = true
> >+---
> >+...
> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+s1:drop()
> >+---
> >+...
> >+s2:drop()
> >+---
> >+...
> >+-- str num
> >+N1 = 100
> >+---
> >+...
> >+t1 = {}
> >+---
> >+...
> >+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
> >+---
> >+...
> >+N2 = 5
> >+---
> >+...
> >+t2 = {}
> >+---
> >+...
> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
> >+---
> >+...
> >+s1 = box.schema.space.create('test1')
> >+---
> >+...
> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
> >+---
> >+- false
> >+...
> >+s2 = box.schema.space.create('test2')
> >+---
> >+...
> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
> >+---
> >+- true
> >+...
> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
> >+---
> >+...
> >+s1:count() == s2:count()
> >+---
> >+- true
> >+...
> >+equal(s1:select{}, s2:select{})
> >+---
> >+- true
> >+...
> >+good = true
> >+---
> >+...
> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
> >+---
> >+...
> >+good
> >+---
> >+- true
> >+...
> >+s1:drop()
> >+---
> >+...
> >+s2:drop()
> >+---
> >+...
> >diff --git a/test/box/tree_pk_multipart.test.lua b/test/box/tree_pk_multipart.test.lua
> >index 7d3405f..cba3a98 100644
> >--- a/test/box/tree_pk_multipart.test.lua
> >+++ b/test/box/tree_pk_multipart.test.lua
> >@@ -194,3 +194,67 @@ test_run:cmd("setopt delimiter ''");
> > space:drop()
> > 
> > space = nil
> >+
> >+
> >+-- hints
> >+test_run:cmd("setopt delimiter ';'")
> >+function equal(res1, res2)
> >+ if #res1 ~= #res2 then
> >+ return false
> >+ end
> >+ for k,v in pairs(res1) do
> >+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
> >+ return false
> >+ end
> >+ end
> >+ return true
> >+end
> >+test_run:cmd("setopt delimiter ''");
> >+
> >+-- num num
> >+N1 = 100
> >+t1 = {}
> >+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
> >+N2 = 5
> >+t2 = {}
> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
> >+
> >+s1 = box.schema.space.create('test1')
> >+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
> >+s2 = box.schema.space.create('test2')
> >+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
> >+s1:count() == s2:count()
> >+equal(s1:select{}, s2:select{})
> >+good = true
> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
> >+good
> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
> >+good
> >+
> >+s1:drop()
> >+s2:drop()
> >+
> >+-- str num
> >+N1 = 100
> >+t1 = {}
> >+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
> >+N2 = 5
> >+t2 = {}
> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
> >+
> >+s1 = box.schema.space.create('test1')
> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
> >+s2 = box.schema.space.create('test2')
> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
> >+s1:count() == s2:count()
> >+equal(s1:select{}, s2:select{})
> >+good = true
> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
> >+good
> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
> >+good
> >+
> >+s1:drop()
> >+s2:drop()
> >--
> >2.7.4
> > 
> --
> Ilya Kosarev

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional
  2020-10-20 12:37     ` Nikita Pettik
@ 2020-10-20 12:57       ` Ilya Kosarev
  2020-10-21  9:54         ` Aleksandr Lyapunov
  0 siblings, 1 reply; 11+ messages in thread
From: Ilya Kosarev @ 2020-10-20 12:57 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 86063 bytes --]




  
>Вторник, 20 октября 2020, 15:38 +03:00 от Nikita Pettik <korablev@tarantool.org>:
> 
>On 20 Oct 15:34, Ilya Kosarev wrote:
>>
>> Hi,
>>  
>> Thanks for the patch!
>>  
>> LGTM, i checked for the number of compilers that if with the templated
>> agrument is being processed at the compile time so that there are no jumps
>> in asm code.
>>  
>> What i think is needed is the enum like the following:
>> enum {
>>         NOT_HINTED = 0 ,
>>         HINTED = 1 ,
>> };
>
>I like this way. Btw why not using just boolean type?
I think there was an idea to implement more than one type of hints.
Also it doesn’t really matter as far as we have the proper constants.
>  
>> to avoid using <0> & <1>. If needed i can push such patch to the branch.
>> >Понедельник, 19 октября 2020, 12:52 +03:00 от Aleksandr Lyapunov < alyapunov@tarantool.org >:
>> > 
>> >Since 9fba29abb4e05babb9b23b4413bd8083f0fba933 (memtx: introduce tuple
>> >compare hint) memtx tree key data (indexes) started to contain extra 8
>> >bytes as a hint. Now it is optional and can be turned off in an index
>> >options with "hint = false" entry.
>> >
>> >Closes #4927
>> >
>> >@TarantoolBot document
>> >Title: memtx: optional tuple compare hints
>> >Update the documentation for an index creation to reflect that there is
>> >now an option to turn off hints for the index.
>> >---
>> > src/box/index_def.c | 2 +
>> > src/box/index_def.h | 6 +
>> > src/box/lua/schema.lua | 53 ++++
>> > src/box/lua/space.cc | 7 +
>> > src/box/memtx_engine.c | 2 +
>> > src/box/memtx_tree.cc | 599 ++++++++++++++++++++++++------------
>> > src/lib/salad/bps_tree.h | 19 ++
>> > test/box/alter.result | 103 ++++++-
>> > test/box/alter.test.lua | 34 ++
>> > test/box/errinj.result | 3 +-
>> > test/box/tree_pk.result | 314 +++++++++++++++++++
>> > test/box/tree_pk.test.lua | 115 +++++++
>> > test/box/tree_pk_multipart.result | 153 +++++++++
>> > test/box/tree_pk_multipart.test.lua | 64 ++++
>> > 14 files changed, 1269 insertions(+), 205 deletions(-)
>> >
>> >diff --git a/src/box/index_def.c b/src/box/index_def.c
>> >index 9802961..79394b8 100644
>> >--- a/src/box/index_def.c
>> >+++ b/src/box/index_def.c
>> >@@ -51,6 +51,7 @@ const struct index_opts index_opts_default = {
>> >  /* .lsn = */ 0,
>> >  /* .stat = */ NULL,
>> >  /* .func = */ 0,
>> >+ /* .hint = */ true,
>> > };
>> > 
>> > const struct opt_def index_opts_reg[] = {
>> >@@ -66,6 +67,7 @@ const struct opt_def index_opts_reg[] = {
>> >  OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn),
>> >  OPT_DEF("func", OPT_UINT32, struct index_opts, func_id),
>> >  OPT_DEF_LEGACY("sql"),
>> >+ OPT_DEF("hint", OPT_BOOL, struct index_opts, hint),
>> >  OPT_END,
>> > };
>> > 
>> >diff --git a/src/box/index_def.h b/src/box/index_def.h
>> >index d928b23..2180a69 100644
>> >--- a/src/box/index_def.h
>> >+++ b/src/box/index_def.h
>> >@@ -165,6 +165,10 @@ struct index_opts {
>> >  struct index_stat *stat;
>> >  /** Identifier of the functional index function. */
>> >  uint32_t func_id;
>> >+ /**
>> >+ * Use hint optimization for tree index.
>> >+ */
>> >+ bool hint;
>> > };
>> > 
>> > extern const struct index_opts index_opts_default;
>> >@@ -211,6 +215,8 @@ index_opts_cmp(const struct index_opts *o1, const struct index_opts *o2)
>> >  return o1->bloom_fpr < o2->bloom_fpr ? -1 : 1;
>> >  if (o1->func_id != o2->func_id)
>> >  return o1->func_id - o2->func_id;
>> >+ if (o1->hint != o2->hint)
>> >+ return o1->hint - o2->hint;
>> >  return 0;
>> > }
>> > 
>> >diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
>> >index 1131af7..9cc1289 100644
>> >--- a/src/box/lua/schema.lua
>> >+++ b/src/box/lua/schema.lua
>> >@@ -1000,8 +1000,31 @@ local index_options = {
>> >     page_size = 'number',
>> >     bloom_fpr = 'number',
>> >     func = 'number, string',
>> >+ hint = 'boolean',
>> > }
>> > 
>> >+local function jsonpaths_from_idx_parts(parts)
>> >+ local paths = {}
>> >+
>> >+ for _, part in pairs(parts) do
>> >+ if type(part.path) == 'string' then
>> >+ table.insert(paths, part.path)
>> >+ end
>> >+ end
>> >+
>> >+ return paths
>> >+end
>> >+
>> >+local function is_multikey_index(parts)
>> >+ for _, path in pairs(jsonpaths_from_idx_parts(parts)) do
>> >+ if path:find('[*]', 1, true) then
>> >+ return true
>> >+ end
>> >+ end
>> >+
>> >+ return false
>> >+end
>> >+
>> > --
>> > -- check_param_table() template for alter index,
>> > -- includes all index options.
>> >@@ -1076,6 +1099,15 @@ box.schema.index.create = function(space_id, name, options)
>> >         options_defaults = {}
>> >     end
>> >     options = update_param_table(options, options_defaults)
>> >+ if options.hint and
>> >+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
>> >+ box.error(box.error.MODIFY_INDEX, name, space.name,
>> >+ "hint is only reasonable with memtx tree index")
>> >+ end
>> >+ if options.hint and options.func then
>> >+ box.error(box.error.MODIFY_INDEX, name, space.name,
>> >+ "functional index can't use hints")
>> >+ end
>> > 
>> >     local _index = box.space[box.schema.INDEX_ID]
>> >     local _vindex = box.space[box.schema.VINDEX_ID]
>> >@@ -1115,6 +1147,7 @@ box.schema.index.create = function(space_id, name, options)
>> >             run_size_ratio = options.run_size_ratio,
>> >             bloom_fpr = options.bloom_fpr,
>> >             func = options.func,
>> >+ hint = options.hint,
>> >     }
>> >     local field_type_aliases = {
>> >         num = 'unsigned'; -- Deprecated since 1.7.2
>> >@@ -1135,6 +1168,10 @@ box.schema.index.create = function(space_id, name, options)
>> >     if parts_can_be_simplified then
>> >         parts = simplify_index_parts(parts)
>> >     end
>> >+ if options.hint and is_multikey_index(parts) then
>> >+ box.error(box.error.MODIFY_INDEX, name, space.name,
>> >+ "multikey index can't use hints")
>> >+ end
>> >     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
>> >         index_opts.func = func_id_by_name(index_opts.func)
>> >     end
>> >@@ -1253,6 +1290,17 @@ box.schema.index.alter = function(space_id, index_id, options)
>> >             index_opts[k] = options[k]
>> >         end
>> >     end
>> >+ if options.hint and
>> >+ (options.type ~= 'tree' or box.space[space_id].engine ~= 'memtx') then
>> >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>> >+ space.name,
>> >+ "hint is only reasonable with memtx tree index")
>> >+ end
>> >+ if options.hint and options.func then
>> >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>> >+ space.name,
>> >+ "functional index can't use hints")
>> >+ end
>> >     if options.parts then
>> >         local parts_can_be_simplified
>> >         parts, parts_can_be_simplified =
>> >@@ -1262,6 +1310,11 @@ box.schema.index.alter = function(space_id, index_id, options)
>> >             parts = simplify_index_parts(parts)
>> >         end
>> >     end
>> >+ if options.hint and is_multikey_index(parts) then
>> >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>> >+ space.name,
>> >+ "multikey index can't use hints")
>> >+ end
>> >     if index_opts.func ~= nil and type(index_opts.func) == 'string' then
>> >         index_opts.func = func_id_by_name(index_opts.func)
>> >     end
>> >diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
>> >index 177c588..1ea993c 100644
>> >--- a/src/box/lua/space.cc
>> >+++ b/src/box/lua/space.cc
>> >@@ -344,6 +344,13 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
>> >  lua_pushnumber(L, index_opts->dimension);
>> >  lua_setfield(L, -2, "dimension");
>> >  }
>> >+ if (space_is_memtx(space) && index_def->type == TREE) {
>> >+ lua_pushboolean(L, index_opts->hint);
>> >+ lua_setfield(L, -2, "hint");
>> >+ } else {
>> >+ lua_pushnil(L);
>> >+ lua_setfield(L, -2, "hint");
>> >+ }
>> > 
>> >  if (index_opts->func_id > 0) {
>> >  lua_pushstring(L, "func");
>> >diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
>> >index  8147557 ..43000ba 100644
>> >--- a/src/box/memtx_engine.c
>> >+++ b/src/box/memtx_engine.c
>> >@@ -1398,6 +1398,8 @@ memtx_index_def_change_requires_rebuild(struct index *index,
>> >  return true;
>> >  if (old_def->opts.func_id != new_def->opts.func_id)
>> >  return true;
>> >+ if (old_def->opts.hint != new_def->opts.hint)
>> >+ return true;
>> > 
>> >  const struct key_def *old_cmp_def, *new_cmp_def;
>> >  if (index_depends_on_pk(index)) {
>> >diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
>> >index d3b993b..17d58c5 100644
>> >--- a/src/box/memtx_tree.cc
>> >+++ b/src/box/memtx_tree.cc
>> >@@ -45,23 +45,51 @@
>> > /**
>> >  * Struct that is used as a key in BPS tree definition.
>> >  */
>> >-struct memtx_tree_key_data {
>> >+struct memtx_tree_key_data_common {
>> >  /** Sequence of msgpacked search fields. */
>> >  const char *key;
>> >  /** Number of msgpacked search fields. */
>> >  uint32_t part_count;
>> >+};
>> >+
>> >+template <int USE_HINT>
>> >+struct memtx_tree_key_data;
>> >+
>> >+template <>
>> >+struct memtx_tree_key_data<0> : memtx_tree_key_data_common {
>> >+ static constexpr hint_t hint = HINT_NONE;
>> >+ void set_hint(hint_t) { assert(false); }
>> >+};
>> >+
>> >+template <>
>> >+struct memtx_tree_key_data<1> : memtx_tree_key_data_common {
>> >  /** Comparison hint, see tuple_hint(). */
>> >  hint_t hint;
>> >+ void set_hint(hint_t h) { hint = h; }
>> > };
>> > 
>> > /**
>> >  * Struct that is used as a elem in BPS tree definition.
>> >  */
>> >-struct memtx_tree_data {
>> >+struct memtx_tree_data_common {
>> >  /* Tuple that this node is represents. */
>> >  struct tuple *tuple;
>> >+};
>> >+
>> >+template <int USE_HINT>
>> >+struct memtx_tree_data;
>> >+
>> >+template <>
>> >+struct memtx_tree_data<0> : memtx_tree_data_common {
>> >+ static constexpr hint_t hint = HINT_NONE;
>> >+ void set_hint(hint_t) { assert(false); }
>> >+};
>> >+
>> >+template <>
>> >+struct memtx_tree_data<1> : memtx_tree_data<0> {
>> >  /** Comparison hint, see key_hint(). */
>> >  hint_t hint;
>> >+ void set_hint(hint_t h) { hint = h; }
>> > };
>> > 
>> > /**
>> >@@ -73,8 +101,8 @@ struct memtx_tree_data {
>> >  * @retval false - Otherwise.
>> >  */
>> > static bool
>> >-memtx_tree_data_is_equal(const struct memtx_tree_data *a,
>> >- const struct memtx_tree_data *b)
>> >+memtx_tree_data_is_equal(const struct memtx_tree_data_common *a,
>> >+ const struct memtx_tree_data_common *b)
>> > {
>> >  return a->tuple == b->tuple;
>> > }
>> >@@ -89,12 +117,28 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
>> >  (b)->part_count, (b)->hint, arg)
>> > #define BPS_TREE_IS_IDENTICAL(a, b) memtx_tree_data_is_equal(&a, &b)
>> > #define BPS_TREE_NO_DEBUG 1
>> >-#define bps_tree_elem_t struct memtx_tree_data
>> >-#define bps_tree_key_t struct memtx_tree_key_data *
>> > #define bps_tree_arg_t struct key_def *
>> > 
>> >+#define BPS_TREE_NAMESPACE NS_NO_HINT
>> >+#define bps_tree_elem_t struct memtx_tree_data<0>
>> >+#define bps_tree_key_t struct memtx_tree_key_data<0> *
>> >+
>> > #include "salad/bps_tree.h"
>> > 
>> >+#undef BPS_TREE_NAMESPACE
>> >+#undef bps_tree_elem_t
>> >+#undef bps_tree_key_t
>> >+
>> >+#define BPS_TREE_NAMESPACE NS_USE_HINT
>> >+#define bps_tree_elem_t struct memtx_tree_data<1>
>> >+#define bps_tree_key_t struct memtx_tree_key_data<1> *
>> >+
>> >+#include "salad/bps_tree.h"
>> >+
>> >+#undef BPS_TREE_NAMESPACE
>> >+#undef bps_tree_elem_t
>> >+#undef bps_tree_key_t
>> >+
>> > #undef BPS_TREE_NAME
>> > #undef BPS_TREE_BLOCK_SIZE
>> > #undef BPS_TREE_EXTENT_SIZE
>> >@@ -102,66 +146,119 @@ memtx_tree_data_is_equal(const struct memtx_tree_data *a,
>> > #undef BPS_TREE_COMPARE_KEY
>> > #undef BPS_TREE_IS_IDENTICAL
>> > #undef BPS_TREE_NO_DEBUG
>> >-#undef bps_tree_elem_t
>> >-#undef bps_tree_key_t
>> > #undef bps_tree_arg_t
>> > 
>> >+using namespace NS_NO_HINT;
>> >+using namespace NS_USE_HINT;
>> >+
>> >+template <int USE_HINT>
>> >+struct memtx_tree_selector;
>> >+
>> >+template <>
>> >+struct memtx_tree_selector<0> : NS_NO_HINT::memtx_tree {};
>> >+
>> >+template <>
>> >+struct memtx_tree_selector<1> : NS_USE_HINT::memtx_tree {};
>> >+
>> >+template <int USE_HINT>
>> >+using memtx_tree_t = struct memtx_tree_selector<USE_HINT>;
>> >+
>> >+template <int USE_HINT>
>> >+struct memtx_tree_iterator_selector;
>> >+
>> >+template <>
>> >+struct memtx_tree_iterator_selector<0> {
>> >+ using type = NS_NO_HINT::memtx_tree_iterator;
>> >+};
>> >+
>> >+template <>
>> >+struct memtx_tree_iterator_selector<1> {
>> >+ using type = NS_USE_HINT::memtx_tree_iterator;
>> >+};
>> >+
>> >+template <int USE_HINT>
>> >+using memtx_tree_iterator_t = typename memtx_tree_iterator_selector<USE_HINT>::type;
>> >+
>> >+static void
>> >+invalidate_tree_iterator(NS_NO_HINT::memtx_tree_iterator *itr)
>> >+{
>> >+ *itr = NS_NO_HINT::memtx_tree_invalid_iterator();
>> >+}
>> >+
>> >+static void
>> >+invalidate_tree_iterator(NS_USE_HINT::memtx_tree_iterator *itr)
>> >+{
>> >+ *itr = NS_USE_HINT::memtx_tree_invalid_iterator();
>> >+}
>> >+
>> >+template <int USE_HINT>
>> > struct memtx_tree_index {
>> >  struct index base;
>> >- struct memtx_tree tree;
>> >- struct memtx_tree_data *build_array;
>> >+ memtx_tree_t<USE_HINT> tree;
>> >+ struct memtx_tree_data<USE_HINT> *build_array;
>> >  size_t build_array_size, build_array_alloc_size;
>> >  struct memtx_gc_task gc_task;
>> >- struct memtx_tree_iterator gc_iterator;
>> >+ memtx_tree_iterator_t<USE_HINT> gc_iterator;
>> > };
>> > 
>> > /* {{{ Utilities. *************************************************/
>> > 
>> >+template <class TREE>
>> > static inline struct key_def *
>> >-memtx_tree_cmp_def(struct memtx_tree *tree)
>> >+memtx_tree_cmp_def(TREE *tree)
>> > {
>> >  return tree->arg;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > memtx_tree_qcompare(const void* a, const void *b, void *c)
>> > {
>> >- const struct memtx_tree_data *data_a = (struct memtx_tree_data *)a;
>> >- const struct memtx_tree_data *data_b = (struct memtx_tree_data *)b;
>> >+ const struct memtx_tree_data<USE_HINT> *data_a =
>> >+ (struct memtx_tree_data<USE_HINT> *)a;
>> >+ const struct memtx_tree_data<USE_HINT> *data_b =
>> >+ (struct memtx_tree_data<USE_HINT> *)b;
>> >  struct key_def *key_def = (struct key_def *)c;
>> >  return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
>> >  data_b->hint, key_def);
>> > }
>> > 
>> > /* {{{ MemtxTree Iterators ****************************************/
>> >+template <int USE_HINT>
>> > struct tree_iterator {
>> >  struct iterator base;
>> >- struct memtx_tree_iterator tree_iterator;
>> >+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
>> >  enum iterator_type type;
>> >- struct memtx_tree_key_data key_data;
>> >- struct memtx_tree_data current;
>> >+ struct memtx_tree_key_data<USE_HINT> key_data;
>> >+ struct memtx_tree_data<USE_HINT> current;
>> >  /** Memory pool the iterator was allocated from. */
>> >  struct mempool *pool;
>> > };
>> > 
>> >-static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,
>> >- "sizeof(struct tree_iterator) must be less than or equal "
>> >+static_assert(sizeof(struct tree_iterator<0>) <= MEMTX_ITERATOR_SIZE,
>> >+ "sizeof(struct tree_iterator<0>) must be less than or equal "
>> >+ "to MEMTX_ITERATOR_SIZE");
>> >+static_assert(sizeof(struct tree_iterator<1>) <= MEMTX_ITERATOR_SIZE,
>> >+ "sizeof(struct tree_iterator<1>) must be less than or equal "
>> >  "to MEMTX_ITERATOR_SIZE");
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > tree_iterator_free(struct iterator *iterator);
>> > 
>> >-static inline struct tree_iterator *
>> >-tree_iterator(struct iterator *it)
>> >+template <int USE_HINT>
>> >+static inline struct tree_iterator<USE_HINT> *
>> >+get_tree_iterator(struct iterator *it)
>> > {
>> >- assert(it->free == tree_iterator_free);
>> >- return (struct tree_iterator *) it;
>> >+ assert(it->free == &tree_iterator_free<USE_HINT>);
>> >+ return (struct tree_iterator<USE_HINT> *) it;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > tree_iterator_free(struct iterator *iterator)
>> > {
>> >- struct tree_iterator *it = tree_iterator(iterator);
>> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>> >  struct tuple *tuple = it->current.tuple;
>> >  if (tuple != NULL)
>> >  tuple_unref(tuple);
>> >@@ -176,14 +273,15 @@ tree_iterator_dummie(struct iterator *iterator, struct tuple **ret)
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
>> > {
>> >- struct memtx_tree_index *index =
>> >- (struct memtx_tree_index *)iterator->index;
>> >- struct tree_iterator *it = tree_iterator(iterator);
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>> >  assert(it->current.tuple != NULL);
>> >- struct memtx_tree_data *check =
>> >+ struct memtx_tree_data<USE_HINT> *check =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>> >  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
>> >@@ -192,7 +290,7 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
>> >  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
>> >  }
>> >  tuple_unref(it->current.tuple);
>> >- struct memtx_tree_data *res =
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  if (res == NULL) {
>> >  iterator->next = tree_iterator_dummie;
>> >@@ -206,14 +304,15 @@ tree_iterator_next_base(struct iterator *iterator, struct tuple **ret)
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
>> > {
>> >- struct memtx_tree_index *index =
>> >- (struct memtx_tree_index *)iterator->index;
>> >- struct tree_iterator *it = tree_iterator(iterator);
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>> >  assert(it->current.tuple != NULL);
>> >- struct memtx_tree_data *check =
>> >+ struct memtx_tree_data<USE_HINT> *check =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>> >  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
>> >@@ -221,7 +320,7 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
>> >  }
>> >  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
>> >  tuple_unref(it->current.tuple);
>> >- struct memtx_tree_data *res =
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  if (!res) {
>> >  iterator->next = tree_iterator_dummie;
>> >@@ -235,14 +334,15 @@ tree_iterator_prev_base(struct iterator *iterator, struct tuple **ret)
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
>> > {
>> >- struct memtx_tree_index *index =
>> >- (struct memtx_tree_index *)iterator->index;
>> >- struct tree_iterator *it = tree_iterator(iterator);
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>> >  assert(it->current.tuple != NULL);
>> >- struct memtx_tree_data *check =
>> >+ struct memtx_tree_data<USE_HINT> *check =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>> >  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
>> >@@ -251,7 +351,7 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
>> >  memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
>> >  }
>> >  tuple_unref(it->current.tuple);
>> >- struct memtx_tree_data *res =
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  /* Use user key def to save a few loops. */
>> >  if (res == NULL ||
>> >@@ -271,14 +371,15 @@ tree_iterator_next_equal_base(struct iterator *iterator, struct tuple **ret)
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
>> > {
>> >- struct memtx_tree_index *index =
>> >- (struct memtx_tree_index *)iterator->index;
>> >- struct tree_iterator *it = tree_iterator(iterator);
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>> >  assert(it->current.tuple != NULL);
>> >- struct memtx_tree_data *check =
>> >+ struct memtx_tree_data<USE_HINT> *check =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  if (check == NULL || !memtx_tree_data_is_equal(check, &it->current)) {
>> >  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
>> >@@ -286,7 +387,7 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
>> >  }
>> >  memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
>> >  tuple_unref(it->current.tuple);
>> >- struct memtx_tree_data *res =
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >  memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>> >  /* Use user key def to save a few loops. */
>> >  if (res == NULL ||
>> >@@ -307,28 +408,30 @@ tree_iterator_prev_equal_base(struct iterator *iterator, struct tuple **ret)
>> > }
>> > 
>> > #define WRAP_ITERATOR_METHOD(name) \
>> >+template <int USE_HINT> \
>> > static int \
>> > name(struct iterator *iterator, struct tuple **ret) \
>> > { \
>> >- struct memtx_tree *tree = \
>> >- &((struct memtx_tree_index *)iterator->index)->tree; \
>> >- struct tree_iterator *it = tree_iterator(iterator); \
>> >- struct memtx_tree_iterator *ti = &it->tree_iterator; \
>> >+ memtx_tree_t<USE_HINT> *tree = \
>> >+ &((struct memtx_tree_index<USE_HINT> *)iterator->index)->tree; \
>> >+ struct tree_iterator<USE_HINT> *it = \
>> >+ get_tree_iterator<USE_HINT>(iterator); \
>> >+ memtx_tree_iterator_t<USE_HINT> *ti = &it->tree_iterator; \
>> >  uint32_t iid = iterator->index->def->iid; \
>> >  bool is_multikey = iterator->index->def->key_def->is_multikey; \
>> >  struct txn *txn = in_txn(); \
>> >  struct space *space = space_by_id(iterator->space_id); \
>> >  bool is_rw = txn != NULL; \
>> >  do { \
>> >- int rc = name##_base(iterator, ret); \
>> >+ int rc = name##_base<USE_HINT>(iterator, ret); \
>> >  if (rc != 0 || *ret == NULL) \
>> >  return rc; \
>> >  uint32_t mk_index = 0; \
>> >  if (is_multikey) { \
>> >- struct memtx_tree_data *check = \
>> >+ struct memtx_tree_data<USE_HINT> *check = \
>> >  memtx_tree_iterator_get_elem(tree, ti); \
>> >  assert(check != NULL); \
>> >- mk_index = check->hint; \
>> >+ mk_index = (uint32_t)check->hint; \
>> >  } \
>> >  *ret = memtx_tx_tuple_clarify(txn, space, *ret, \
>> >  iid, mk_index, is_rw); \
>> >@@ -347,27 +450,28 @@ WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);
>> > 
>> > #undef WRAP_ITERATOR_METHOD
>> > 
>> >+template <int USE_HINT>
>> > static void
>> >-tree_iterator_set_next_method(struct tree_iterator *it)
>> >+tree_iterator_set_next_method(struct tree_iterator<USE_HINT> *it)
>> > {
>> >  assert(it->current.tuple != NULL);
>> >  switch (it->type) {
>> >  case ITER_EQ:
>> >- it->base.next = tree_iterator_next_equal;
>> >+ it->base.next = tree_iterator_next_equal<USE_HINT>;
>> >  break;
>> >  case ITER_REQ:
>> >- it->base.next = tree_iterator_prev_equal;
>> >+ it->base.next = tree_iterator_prev_equal<USE_HINT>;
>> >  break;
>> >  case ITER_ALL:
>> >- it->base.next = tree_iterator_next;
>> >+ it->base.next = tree_iterator_next<USE_HINT>;
>> >  break;
>> >  case ITER_LT:
>> >  case ITER_LE:
>> >- it->base.next = tree_iterator_prev;
>> >+ it->base.next = tree_iterator_prev<USE_HINT>;
>> >  break;
>> >  case ITER_GE:
>> >  case ITER_GT:
>> >- it->base.next = tree_iterator_next;
>> >+ it->base.next = tree_iterator_next<USE_HINT>;
>> >  break;
>> >  default:
>> >  /* The type was checked in initIterator */
>> >@@ -375,15 +479,16 @@ tree_iterator_set_next_method(struct tree_iterator *it)
>> >  }
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > tree_iterator_start(struct iterator *iterator, struct tuple **ret)
>> > {
>> >  *ret = NULL;
>> >- struct memtx_tree_index *index =
>> >- (struct memtx_tree_index *)iterator->index;
>> >- struct tree_iterator *it = tree_iterator(iterator);
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>> >+ struct tree_iterator<USE_HINT> *it = get_tree_iterator<USE_HINT>(iterator);
>> >  it->base.next = tree_iterator_dummie;
>> >- struct memtx_tree *tree = &index->tree;
>> >+ memtx_tree_t<USE_HINT> *tree = &index->tree;
>> >  enum iterator_type type = it->type;
>> >  bool exact = false;
>> >  assert(it->current.tuple == NULL);
>> >@@ -423,8 +528,8 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
>> >  }
>> >  }
>> > 
>> >- struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,
>> >- &it->tree_iterator);
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >+ memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
>> >  if (!res)
>> >  return 0;
>> >  *ret = res->tuple;
>> >@@ -454,14 +559,16 @@ tree_iterator_start(struct iterator *iterator, struct tuple **ret)
>> > 
>> > /* {{{ MemtxTree **********************************************************/
>> > 
>> >+template <int USE_HINT>
>> > static void
>> >-memtx_tree_index_free(struct memtx_tree_index *index)
>> >+memtx_tree_index_free(struct memtx_tree_index<USE_HINT> *index)
>> > {
>> >  memtx_tree_destroy(&index->tree);
>> >  free(index->build_array);
>> >  free(index);
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
>> > {
>> >@@ -475,14 +582,14 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
>> >  enum { YIELD_LOOPS = 10 };
>> > #endif
>> > 
>> >- struct memtx_tree_index *index = container_of(task,
>> >- struct memtx_tree_index, gc_task);
>> >- struct memtx_tree *tree = &index->tree;
>> >- struct memtx_tree_iterator *itr = &index->gc_iterator;
>> >+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
>> >+ struct memtx_tree_index<USE_HINT>, gc_task);
>> >+ memtx_tree_t<USE_HINT> *tree = &index->tree;
>> >+ memtx_tree_iterator_t<USE_HINT> *itr = &index->gc_iterator;
>> > 
>> >  unsigned int loops = 0;
>> >  while (!memtx_tree_iterator_is_invalid(itr)) {
>> >- struct memtx_tree_data *res =
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >  memtx_tree_iterator_get_elem(tree, itr);
>> >  memtx_tree_iterator_next(tree, itr);
>> >  tuple_unref(res->tuple);
>> >@@ -494,23 +601,32 @@ memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
>> >  *done = true;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > memtx_tree_index_gc_free(struct memtx_gc_task *task)
>> > {
>> >- struct memtx_tree_index *index = container_of(task,
>> >- struct memtx_tree_index, gc_task);
>> >+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
>> >+ struct memtx_tree_index<USE_HINT>, gc_task);
>> >  memtx_tree_index_free(index);
>> > }
>> > 
>> >-static const struct memtx_gc_task_vtab memtx_tree_index_gc_vtab = {
>> >- .run = memtx_tree_index_gc_run,
>> >- .free = memtx_tree_index_gc_free,
>> >+template <int USE_HINT>
>> >+static struct memtx_gc_task_vtab * get_memtx_tree_index_gc_vtab()
>> >+{
>> >+ static memtx_gc_task_vtab tab =
>> >+ {
>> >+ .run = memtx_tree_index_gc_run<USE_HINT>,
>> >+ .free = memtx_tree_index_gc_free<USE_HINT>,
>> >+ };
>> >+ return &tab;
>> > };
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > memtx_tree_index_destroy(struct index *base)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
>> >  if (base->def->iid == 0) {
>> >  /*
>> >@@ -518,7 +634,7 @@ memtx_tree_index_destroy(struct index *base)
>> >  * in the index, which may take a while. Schedule a
>> >  * background task in order not to block tx thread.
>> >  */
>> >- index->gc_task.vtab = &memtx_tree_index_gc_vtab;
>> >+ index->gc_task.vtab = get_memtx_tree_index_gc_vtab<USE_HINT>();
>> >  index->gc_iterator = memtx_tree_iterator_first(&index->tree);
>> >  memtx_engine_schedule_gc(memtx, &index->gc_task);
>> >  } else {
>> >@@ -530,10 +646,12 @@ memtx_tree_index_destroy(struct index *base)
>> >  }
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > memtx_tree_index_update_def(struct index *base)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  struct index_def *def = base->def;
>> >  /*
>> >  * We use extended key def for non-unique and nullable
>> >@@ -553,51 +671,62 @@ memtx_tree_index_depends_on_pk(struct index *base)
>> >  return !def->opts.is_unique || def->key_def->is_nullable;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static ssize_t
>> > memtx_tree_index_size(struct index *base)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  return memtx_tree_size(&index->tree);
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static ssize_t
>> > memtx_tree_index_bsize(struct index *base)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  return memtx_tree_mem_used(&index->tree);
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > memtx_tree_index_random(struct index *base, uint32_t rnd, struct tuple **result)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >- struct memtx_tree_data *res = memtx_tree_random(&index->tree, rnd);
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >+ struct memtx_tree_data<USE_HINT> *res = memtx_tree_random(&index->tree, rnd);
>> >  *result = res != NULL ? res->tuple : NULL;
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static ssize_t
>> > memtx_tree_index_count(struct index *base, enum iterator_type type,
>> >  const char *key, uint32_t part_count)
>> > {
>> >  if (type == ITER_ALL)
>> >- return memtx_tree_index_size(base); /* optimization */
>> >+ return memtx_tree_index_size<USE_HINT>(base); /* optimization */
>> >  return generic_index_count(base, type, key, part_count);
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > memtx_tree_index_get(struct index *base, const char *key,
>> >  uint32_t part_count, struct tuple **result)
>> > {
>> >  assert(base->def->opts.is_unique &&
>> >  part_count == base->def->key_def->part_count);
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> >- struct memtx_tree_key_data key_data;
>> >+ struct memtx_tree_key_data<USE_HINT> key_data;
>> >  key_data.key = key;
>> >  key_data.part_count = part_count;
>> >- key_data.hint = key_hint(key, part_count, cmp_def);
>> >- struct memtx_tree_data *res = memtx_tree_find(&index->tree, &key_data);
>> >+ if (USE_HINT)
>> >+ key_data.set_hint(key_hint(key, part_count, cmp_def));
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >+ memtx_tree_find(&index->tree, &key_data);
>> >  if (res == NULL) {
>> >  *result = NULL;
>> >  return 0;
>> >@@ -611,18 +740,21 @@ memtx_tree_index_get(struct index *base, const char *key,
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
>> >  struct tuple *new_tuple, enum dup_replace_mode mode,
>> >  struct tuple **result)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> >  if (new_tuple) {
>> >- struct memtx_tree_data new_data;
>> >+ struct memtx_tree_data<USE_HINT> new_data;
>> >  new_data.tuple = new_tuple;
>> >- new_data.hint = tuple_hint(new_tuple, cmp_def);
>> >- struct memtx_tree_data dup_data;
>> >+ if (USE_HINT)
>> >+ new_data.set_hint(tuple_hint(new_tuple, cmp_def));
>> >+ struct memtx_tree_data<USE_HINT> dup_data;
>> >  dup_data.tuple = NULL;
>> > 
>> >  /* Try to optimistically replace the new_tuple. */
>> >@@ -652,9 +784,10 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
>> >  }
>> >  }
>> >  if (old_tuple) {
>> >- struct memtx_tree_data old_data;
>> >+ struct memtx_tree_data<USE_HINT> old_data;
>> >  old_data.tuple = old_tuple;
>> >- old_data.hint = tuple_hint(old_tuple, cmp_def);
>> >+ if (USE_HINT)
>> >+ old_data.set_hint(tuple_hint(old_tuple, cmp_def));
>> >  memtx_tree_delete(&index->tree, old_data);
>> >  }
>> >  *result = old_tuple;
>> >@@ -667,13 +800,13 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
>> >  * by all it's multikey indexes.
>> >  */
>> > static int
>> >-memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
>> >+memtx_tree_index_replace_multikey_one(struct memtx_tree_index<1> *index,
>> >  struct tuple *old_tuple, struct tuple *new_tuple,
>> >  enum dup_replace_mode mode, hint_t hint,
>> >- struct memtx_tree_data *replaced_data,
>> >+ struct memtx_tree_data<1> *replaced_data,
>> >  bool *is_multikey_conflict)
>> > {
>> >- struct memtx_tree_data new_data, dup_data;
>> >+ struct memtx_tree_data<1> new_data, dup_data;
>> >  new_data.tuple = new_tuple;
>> >  new_data.hint = hint;
>> >  dup_data.tuple = NULL;
>> >@@ -720,11 +853,11 @@ memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
>> >  * delete operation is fault-tolerant.
>> >  */
>> > static void
>> >-memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index *index,
>> >+memtx_tree_index_replace_multikey_rollback(struct memtx_tree_index<1> *index,
>> >  struct tuple *new_tuple, struct tuple *replaced_tuple,
>> >  int err_multikey_idx)
>> > {
>> >- struct memtx_tree_data data;
>> >+ struct memtx_tree_data<1> data;
>> >  if (replaced_tuple != NULL) {
>> >  /* Restore replaced tuple index occurrences. */
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> >@@ -798,7 +931,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
>> >  struct tuple *new_tuple, enum dup_replace_mode mode,
>> >  struct tuple **result)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> >  *result = NULL;
>> >  if (new_tuple != NULL) {
>> >@@ -808,7 +941,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
>> >  for (; (uint32_t) multikey_idx < multikey_count;
>> >  multikey_idx++) {
>> >  bool is_multikey_conflict;
>> >- struct memtx_tree_data replaced_data;
>> >+ struct memtx_tree_data<1> replaced_data;
>> >  err = memtx_tree_index_replace_multikey_one(index,
>> >  old_tuple, new_tuple, mode,
>> >  multikey_idx, &replaced_data,
>> >@@ -833,7 +966,7 @@ memtx_tree_index_replace_multikey(struct index *base, struct tuple *old_tuple,
>> >  }
>> >  }
>> >  if (old_tuple != NULL) {
>> >- struct memtx_tree_data data;
>> >+ struct memtx_tree_data<1> data;
>> >  data.tuple = old_tuple;
>> >  uint32_t multikey_count =
>> >  tuple_multikey_count(old_tuple, cmp_def);
>> >@@ -865,7 +998,7 @@ struct func_key_undo {
>> >  /** A link to organize entries in list. */
>> >  struct rlist link;
>> >  /** An inserted record copy. */
>> >- struct memtx_tree_data key;
>> >+ struct memtx_tree_data<1> key;
>> > };
>> > 
>> > /** Allocate a new func_key_undo on given region. */
>> >@@ -888,7 +1021,7 @@ func_key_undo_new(struct region *region)
>> >  * return a given index object in it's original state.
>> >  */
>> > static void
>> >-memtx_tree_func_index_replace_rollback(struct memtx_tree_index *index,
>> >+memtx_tree_func_index_replace_rollback(struct memtx_tree_index<1> *index,
>> >  struct rlist *old_keys,
>> >  struct rlist *new_keys)
>> > {
>> >@@ -919,7 +1052,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
>> >  struct tuple *new_tuple, enum dup_replace_mode mode,
>> >  struct tuple **result)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>> >  struct index_def *index_def = index->base.def;
>> >  assert(index_def->key_def->for_func_index);
>> > 
>> >@@ -952,7 +1085,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
>> >  undo->key.hint = (hint_t)key;
>> >  rlist_add(&new_keys, &undo->link);
>> >  bool is_multikey_conflict;
>> >- struct memtx_tree_data old_data;
>> >+ struct memtx_tree_data<1> old_data;
>> >  old_data.tuple = NULL;
>> >  err = memtx_tree_index_replace_multikey_one(index,
>> >  old_tuple, new_tuple,
>> >@@ -1015,7 +1148,7 @@ memtx_tree_func_index_replace(struct index *base, struct tuple *old_tuple,
>> >  if (key_list_iterator_create(&it, old_tuple, index_def, false,
>> >  func_index_key_dummy_alloc) != 0)
>> >  goto end;
>> >- struct memtx_tree_data data, deleted_data;
>> >+ struct memtx_tree_data<1> data, deleted_data;
>> >  data.tuple = old_tuple;
>> >  const char *key;
>> >  while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
>> >@@ -1040,11 +1173,13 @@ end:
>> >  return rc;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static struct iterator *
>> > memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
>> >  const char *key, uint32_t part_count)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> > 
>> >@@ -1064,42 +1199,47 @@ memtx_tree_index_create_iterator(struct index *base, enum iterator_type type,
>> >  key = NULL;
>> >  }
>> > 
>> >- struct tree_iterator *it = (struct tree_iterator *)
>> >+ struct tree_iterator<USE_HINT> *it = (struct tree_iterator<USE_HINT> *)
>> >  mempool_alloc(&memtx->iterator_pool);
>> >  if (it == NULL) {
>> >- diag_set(OutOfMemory, sizeof(struct tree_iterator),
>> >+ diag_set(OutOfMemory, sizeof(struct tree_iterator<USE_HINT>),
>> >  "memtx_tree_index", "iterator");
>> >  return NULL;
>> >  }
>> >  iterator_create(&it->base, base);
>> >  it->pool = &memtx->iterator_pool;
>> >- it->base.next = tree_iterator_start;
>> >- it->base.free = tree_iterator_free;
>> >+ it->base.next = tree_iterator_start<USE_HINT>;
>> >+ it->base.free = tree_iterator_free<USE_HINT>;
>> >  it->type = type;
>> >  it->key_data.key = key;
>> >  it->key_data.part_count = part_count;
>> >- it->key_data.hint = key_hint(key, part_count, cmp_def);
>> >- it->tree_iterator = memtx_tree_invalid_iterator();
>> >+ if (USE_HINT)
>> >+ it->key_data.set_hint(key_hint(key, part_count, cmp_def));
>> >+ invalidate_tree_iterator(&it->tree_iterator);
>> >  it->current.tuple = NULL;
>> >  return (struct iterator *)it;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > memtx_tree_index_begin_build(struct index *base)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  assert(memtx_tree_size(&index->tree) == 0);
>> >  (void)index;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  if (size_hint < index->build_array_alloc_size)
>> >  return 0;
>> >- struct memtx_tree_data *tmp =
>> >- (struct memtx_tree_data *)
>> >+ struct memtx_tree_data<USE_HINT> *tmp =
>> >+ (struct memtx_tree_data<USE_HINT> *)
>> >  realloc(index->build_array, size_hint * sizeof(*tmp));
>> >  if (tmp == NULL) {
>> >  diag_set(OutOfMemory, size_hint * sizeof(*tmp),
>> >@@ -1111,14 +1251,15 @@ memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > /** Initialize the next element of the index build_array. */
>> > static int
>> >-memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>> >+memtx_tree_index_build_array_append(struct memtx_tree_index<USE_HINT> *index,
>> >  struct tuple *tuple, hint_t hint)
>> > {
>> >  if (index->build_array == NULL) {
>> >  index->build_array =
>> >- (struct memtx_tree_data *)malloc(MEMTX_EXTENT_SIZE);
>> >+ (struct memtx_tree_data<USE_HINT> *)malloc(MEMTX_EXTENT_SIZE);
>> >  if (index->build_array == NULL) {
>> >  diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
>> >  "memtx_tree_index", "build_next");
>> >@@ -1131,8 +1272,8 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>> >  if (index->build_array_size == index->build_array_alloc_size) {
>> >  index->build_array_alloc_size = index->build_array_alloc_size +
>> >  DIV_ROUND_UP(index->build_array_alloc_size, 2);
>> >- struct memtx_tree_data *tmp =
>> >- (struct memtx_tree_data *)realloc(index->build_array,
>> >+ struct memtx_tree_data<USE_HINT> *tmp =
>> >+ (struct memtx_tree_data<USE_HINT> *)realloc(index->build_array,
>> >  index->build_array_alloc_size * sizeof(*tmp));
>> >  if (tmp == NULL) {
>> >  diag_set(OutOfMemory, index->build_array_alloc_size *
>> >@@ -1141,17 +1282,20 @@ memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>> >  }
>> >  index->build_array = tmp;
>> >  }
>> >- struct memtx_tree_data *elem =
>> >+ struct memtx_tree_data<USE_HINT> *elem =
>> >  &index->build_array[index->build_array_size++];
>> >  elem->tuple = tuple;
>> >- elem->hint = hint;
>> >+ if (USE_HINT)
>> >+ elem->set_hint(hint);
>> >  return 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> >  return memtx_tree_index_build_array_append(index, tuple,
>> >  tuple_hint(tuple, cmp_def));
>> >@@ -1160,7 +1304,7 @@ memtx_tree_index_build_next(struct index *base, struct tuple *tuple)
>> > static int
>> > memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> >  uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);
>> >  for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;
>> >@@ -1175,7 +1319,7 @@ memtx_tree_index_build_next_multikey(struct index *base, struct tuple *tuple)
>> > static int
>> > memtx_tree_func_index_build_next(struct index *base, struct tuple *tuple)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<1> *index = (struct memtx_tree_index<1> *)base;
>> >  struct index_def *index_def = index->base.def;
>> >  assert(index_def->key_def->for_func_index);
>> > 
>> >@@ -1211,8 +1355,9 @@ error:
>> >  * of equal tuples (in terms of index's cmp_def and have same
>> >  * tuple pointer). The build_array is expected to be sorted.
>> >  */
>> >+template <int USE_HINT>
>> > static void
>> >-memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
>> >+memtx_tree_index_build_array_deduplicate(struct memtx_tree_index<USE_HINT> *index,
>> >  void (*destroy)(struct tuple *tuple, const char *hint))
>> > {
>> >  if (index->build_array_size == 0)
>> >@@ -1246,13 +1391,16 @@ memtx_tree_index_build_array_deduplicate(struct memtx_tree_index *index,
>> >  index->build_array_size = w_idx + 1;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > memtx_tree_index_end_build(struct index *base)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>> >  qsort_arg(index->build_array, index->build_array_size,
>> >- sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);
>> >+ sizeof(index->build_array[0]),
>> >+ memtx_tree_qcompare<USE_HINT>, cmp_def);
>> >  if (cmp_def->is_multikey) {
>> >  /*
>> >  * Multikey index may have equal(in terms of
>> >@@ -1261,9 +1409,9 @@ memtx_tree_index_end_build(struct index *base)
>> >  * the following memtx_tree_build assumes that
>> >  * all keys are unique.
>> >  */
>> >- memtx_tree_index_build_array_deduplicate(index, NULL);
>> >+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index, NULL);
>> >  } else if (cmp_def->for_func_index) {
>> >- memtx_tree_index_build_array_deduplicate(index,
>> >+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index,
>> >  tuple_chunk_delete);
>> >  }
>> >  memtx_tree_build(&index->tree, index->build_array,
>> >@@ -1275,19 +1423,21 @@ memtx_tree_index_end_build(struct index *base)
>> >  index->build_array_alloc_size = 0;
>> > }
>> > 
>> >+template <int USE_HINT>
>> > struct tree_snapshot_iterator {
>> >  struct snapshot_iterator base;
>> >- struct memtx_tree_index *index;
>> >- struct memtx_tree_iterator tree_iterator;
>> >+ struct memtx_tree_index<USE_HINT> *index;
>> >+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
>> >  struct memtx_tx_snapshot_cleaner cleaner;
>> > };
>> > 
>> >+template <int USE_HINT>
>> > static void
>> > tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
>> > {
>> >- assert(iterator->free == tree_snapshot_iterator_free);
>> >- struct tree_snapshot_iterator *it =
>> >- (struct tree_snapshot_iterator *)iterator;
>> >+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
>> >+ struct tree_snapshot_iterator<USE_HINT> *it =
>> >+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
>> >  memtx_leave_delayed_free_mode((struct memtx_engine *)
>> >  it->index->base.engine);
>> >  memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);
>> >@@ -1296,17 +1446,18 @@ tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
>> >  free(iterator);
>> > }
>> > 
>> >+template <int USE_HINT>
>> > static int
>> > tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
>> >  const char **data, uint32_t *size)
>> > {
>> >- assert(iterator->free == tree_snapshot_iterator_free);
>> >- struct tree_snapshot_iterator *it =
>> >- (struct tree_snapshot_iterator *)iterator;
>> >- struct memtx_tree *tree = &it->index->tree;
>> >+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
>> >+ struct tree_snapshot_iterator<USE_HINT> *it =
>> >+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
>> >+ memtx_tree_t<USE_HINT> *tree = &it->index->tree;
>> > 
>> >  while (true) {
>> >- struct memtx_tree_data *res =
>> >+ struct memtx_tree_data<USE_HINT> *res =
>> >  memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
>> > 
>> >  if (res == NULL) {
>> >@@ -1333,14 +1484,18 @@ tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
>> >  * index modifications will not affect the iteration results.
>> >  * Must be destroyed by iterator->free after usage.
>> >  */
>> >+template <int USE_HINT>
>> > static struct snapshot_iterator *
>> > memtx_tree_index_create_snapshot_iterator(struct index *base)
>> > {
>> >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>> >- struct tree_snapshot_iterator *it =
>> >- (struct tree_snapshot_iterator *) calloc(1, sizeof(*it));
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)base;
>> >+ struct tree_snapshot_iterator<USE_HINT> *it =
>> >+ (struct tree_snapshot_iterator<USE_HINT> *)
>> >+ calloc(1, sizeof(*it));
>> >  if (it == NULL) {
>> >- diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),
>> >+ diag_set(OutOfMemory,
>> >+ sizeof(struct tree_snapshot_iterator<USE_HINT>),
>> >  "memtx_tree_index", "create_snapshot_iterator");
>> >  return NULL;
>> >  }
>> >@@ -1352,8 +1507,8 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
>> >  return NULL;
>> >  }
>> > 
>> >- it->base.free = tree_snapshot_iterator_free;
>> >- it->base.next = tree_snapshot_iterator_next;
>> >+ it->base.free = tree_snapshot_iterator_free<USE_HINT>;
>> >+ it->base.next = tree_snapshot_iterator_next<USE_HINT>;
>> >  it->index = index;
>> >  index_ref(base);
>> >  it->tree_iterator = memtx_tree_iterator_first(&index->tree);
>> >@@ -1362,94 +1517,124 @@ memtx_tree_index_create_snapshot_iterator(struct index *base)
>> >  return (struct snapshot_iterator *) it;
>> > }
>> > 
>> >-static const struct index_vtab memtx_tree_index_vtab = {
>> >- /* .destroy = */ memtx_tree_index_destroy,
>> >+static const struct index_vtab memtx_tree_no_hint_index_vtab = {
>> >+ /* .destroy = */ memtx_tree_index_destroy<0>,
>> >  /* .commit_create = */ generic_index_commit_create,
>> >  /* .abort_create = */ generic_index_abort_create,
>> >  /* .commit_modify = */ generic_index_commit_modify,
>> >  /* .commit_drop = */ generic_index_commit_drop,
>> >- /* .update_def = */ memtx_tree_index_update_def,
>> >+ /* .update_def = */ memtx_tree_index_update_def<0>,
>> >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>> >  /* .def_change_requires_rebuild = */
>> >  memtx_index_def_change_requires_rebuild,
>> >- /* .size = */ memtx_tree_index_size,
>> >- /* .bsize = */ memtx_tree_index_bsize,
>> >+ /* .size = */ memtx_tree_index_size<0>,
>> >+ /* .bsize = */ memtx_tree_index_bsize<0>,
>> >  /* .min = */ generic_index_min,
>> >  /* .max = */ generic_index_max,
>> >- /* .random = */ memtx_tree_index_random,
>> >- /* .count = */ memtx_tree_index_count,
>> >- /* .get = */ memtx_tree_index_get,
>> >- /* .replace = */ memtx_tree_index_replace,
>> >- /* .create_iterator = */ memtx_tree_index_create_iterator,
>> >+ /* .random = */ memtx_tree_index_random<0>,
>> >+ /* .count = */ memtx_tree_index_count<0>,
>> >+ /* .get = */ memtx_tree_index_get<0>,
>> >+ /* .replace = */ memtx_tree_index_replace<0>,
>> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<0>,
>> >  /* .create_snapshot_iterator = */
>> >- memtx_tree_index_create_snapshot_iterator,
>> >+ memtx_tree_index_create_snapshot_iterator<0>,
>> >  /* .stat = */ generic_index_stat,
>> >  /* .compact = */ generic_index_compact,
>> >  /* .reset_stat = */ generic_index_reset_stat,
>> >- /* .begin_build = */ memtx_tree_index_begin_build,
>> >- /* .reserve = */ memtx_tree_index_reserve,
>> >- /* .build_next = */ memtx_tree_index_build_next,
>> >- /* .end_build = */ memtx_tree_index_end_build,
>> >+ /* .begin_build = */ memtx_tree_index_begin_build<0>,
>> >+ /* .reserve = */ memtx_tree_index_reserve<0>,
>> >+ /* .build_next = */ memtx_tree_index_build_next<0>,
>> >+ /* .end_build = */ memtx_tree_index_end_build<0>,
>> >+};
>> >+
>> >+static const struct index_vtab memtx_tree_use_hint_index_vtab = {
>> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>> >+ /* .commit_create = */ generic_index_commit_create,
>> >+ /* .abort_create = */ generic_index_abort_create,
>> >+ /* .commit_modify = */ generic_index_commit_modify,
>> >+ /* .commit_drop = */ generic_index_commit_drop,
>> >+ /* .update_def = */ memtx_tree_index_update_def<1>,
>> >+ /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>> >+ /* .def_change_requires_rebuild = */
>> >+ memtx_index_def_change_requires_rebuild,
>> >+ /* .size = */ memtx_tree_index_size<1>,
>> >+ /* .bsize = */ memtx_tree_index_bsize<1>,
>> >+ /* .min = */ generic_index_min,
>> >+ /* .max = */ generic_index_max,
>> >+ /* .random = */ memtx_tree_index_random<1>,
>> >+ /* .count = */ memtx_tree_index_count<1>,
>> >+ /* .get = */ memtx_tree_index_get<1>,
>> >+ /* .replace = */ memtx_tree_index_replace<1>,
>> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>> >+ /* .create_snapshot_iterator = */
>> >+ memtx_tree_index_create_snapshot_iterator<1>,
>> >+ /* .stat = */ generic_index_stat,
>> >+ /* .compact = */ generic_index_compact,
>> >+ /* .reset_stat = */ generic_index_reset_stat,
>> >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>> >+ /* .reserve = */ memtx_tree_index_reserve<1>,
>> >+ /* .build_next = */ memtx_tree_index_build_next<1>,
>> >+ /* .end_build = */ memtx_tree_index_end_build<1>,
>> > };
>> > 
>> > static const struct index_vtab memtx_tree_index_multikey_vtab = {
>> >- /* .destroy = */ memtx_tree_index_destroy,
>> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>> >  /* .commit_create = */ generic_index_commit_create,
>> >  /* .abort_create = */ generic_index_abort_create,
>> >  /* .commit_modify = */ generic_index_commit_modify,
>> >  /* .commit_drop = */ generic_index_commit_drop,
>> >- /* .update_def = */ memtx_tree_index_update_def,
>> >+ /* .update_def = */ memtx_tree_index_update_def<1>,
>> >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>> >  /* .def_change_requires_rebuild = */
>> >  memtx_index_def_change_requires_rebuild,
>> >- /* .size = */ memtx_tree_index_size,
>> >- /* .bsize = */ memtx_tree_index_bsize,
>> >+ /* .size = */ memtx_tree_index_size<1>,
>> >+ /* .bsize = */ memtx_tree_index_bsize<1>,
>> >  /* .min = */ generic_index_min,
>> >  /* .max = */ generic_index_max,
>> >- /* .random = */ memtx_tree_index_random,
>> >- /* .count = */ memtx_tree_index_count,
>> >- /* .get = */ memtx_tree_index_get,
>> >+ /* .random = */ memtx_tree_index_random<1>,
>> >+ /* .count = */ memtx_tree_index_count<1>,
>> >+ /* .get = */ memtx_tree_index_get<1>,
>> >  /* .replace = */ memtx_tree_index_replace_multikey,
>> >- /* .create_iterator = */ memtx_tree_index_create_iterator,
>> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>> >  /* .create_snapshot_iterator = */
>> >- memtx_tree_index_create_snapshot_iterator,
>> >+ memtx_tree_index_create_snapshot_iterator<1>,
>> >  /* .stat = */ generic_index_stat,
>> >  /* .compact = */ generic_index_compact,
>> >  /* .reset_stat = */ generic_index_reset_stat,
>> >- /* .begin_build = */ memtx_tree_index_begin_build,
>> >- /* .reserve = */ memtx_tree_index_reserve,
>> >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>> >+ /* .reserve = */ memtx_tree_index_reserve<1>,
>> >  /* .build_next = */ memtx_tree_index_build_next_multikey,
>> >- /* .end_build = */ memtx_tree_index_end_build,
>> >+ /* .end_build = */ memtx_tree_index_end_build<1>,
>> > };
>> > 
>> > static const struct index_vtab memtx_tree_func_index_vtab = {
>> >- /* .destroy = */ memtx_tree_index_destroy,
>> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>> >  /* .commit_create = */ generic_index_commit_create,
>> >  /* .abort_create = */ generic_index_abort_create,
>> >  /* .commit_modify = */ generic_index_commit_modify,
>> >  /* .commit_drop = */ generic_index_commit_drop,
>> >- /* .update_def = */ memtx_tree_index_update_def,
>> >+ /* .update_def = */ memtx_tree_index_update_def<1>,
>> >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>> >  /* .def_change_requires_rebuild = */
>> >  memtx_index_def_change_requires_rebuild,
>> >- /* .size = */ memtx_tree_index_size,
>> >- /* .bsize = */ memtx_tree_index_bsize,
>> >+ /* .size = */ memtx_tree_index_size<1>,
>> >+ /* .bsize = */ memtx_tree_index_bsize<1>,
>> >  /* .min = */ generic_index_min,
>> >  /* .max = */ generic_index_max,
>> >- /* .random = */ memtx_tree_index_random,
>> >- /* .count = */ memtx_tree_index_count,
>> >- /* .get = */ memtx_tree_index_get,
>> >+ /* .random = */ memtx_tree_index_random<1>,
>> >+ /* .count = */ memtx_tree_index_count<1>,
>> >+ /* .get = */ memtx_tree_index_get<1>,
>> >  /* .replace = */ memtx_tree_func_index_replace,
>> >- /* .create_iterator = */ memtx_tree_index_create_iterator,
>> >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>> >  /* .create_snapshot_iterator = */
>> >- memtx_tree_index_create_snapshot_iterator,
>> >+ memtx_tree_index_create_snapshot_iterator<1>,
>> >  /* .stat = */ generic_index_stat,
>> >  /* .compact = */ generic_index_compact,
>> >  /* .reset_stat = */ generic_index_reset_stat,
>> >- /* .begin_build = */ memtx_tree_index_begin_build,
>> >- /* .reserve = */ memtx_tree_index_reserve,
>> >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>> >+ /* .reserve = */ memtx_tree_index_reserve<1>,
>> >  /* .build_next = */ memtx_tree_func_index_build_next,
>> >- /* .end_build = */ memtx_tree_index_end_build,
>> >+ /* .end_build = */ memtx_tree_index_end_build<1>,
>> > };
>> > 
>> > /**
>> >@@ -1459,7 +1644,7 @@ static const struct index_vtab memtx_tree_func_index_vtab = {
>> >  * key defintion is not completely initialized at that moment).
>> >  */
>> > static const struct index_vtab memtx_tree_disabled_index_vtab = {
>> >- /* .destroy = */ memtx_tree_index_destroy,
>> >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>> >  /* .commit_create = */ generic_index_commit_create,
>> >  /* .abort_create = */ generic_index_abort_create,
>> >  /* .commit_modify = */ generic_index_commit_modify,
>> >@@ -1488,27 +1673,19 @@ static const struct index_vtab memtx_tree_disabled_index_vtab = {
>> >  /* .end_build = */ generic_index_end_build,
>> > };
>> > 
>> >-struct index *
>> >-memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
>> >+template <int USE_HINT>
>> >+static struct index *
>> >+memtx_tree_index_new_tpl(struct memtx_engine *memtx, struct index_def *def,
>> >+ const struct index_vtab *vtab)
>> > {
>> >- struct memtx_tree_index *index =
>> >- (struct memtx_tree_index *)calloc(1, sizeof(*index));
>> >+ struct memtx_tree_index<USE_HINT> *index =
>> >+ (struct memtx_tree_index<USE_HINT> *)
>> >+ calloc(1, sizeof(*index));
>> >  if (index == NULL) {
>> >  diag_set(OutOfMemory, sizeof(*index),
>> >  "malloc", "struct memtx_tree_index");
>> >  return NULL;
>> >  }
>> >- const struct index_vtab *vtab;
>> >- if (def->key_def->for_func_index) {
>> >- if (def->key_def->func_index_func == NULL)
>> >- vtab = &memtx_tree_disabled_index_vtab;
>> >- else
>> >- vtab = &memtx_tree_func_index_vtab;
>> >- } else if (def->key_def->is_multikey) {
>> >- vtab = &memtx_tree_index_multikey_vtab;
>> >- } else {
>> >- vtab = &memtx_tree_index_vtab;
>> >- }
>> >  if (index_create(&index->base, (struct engine *)memtx,
>> >  vtab, def) != 0) {
>> >  free(index);
>> >@@ -1524,3 +1701,23 @@ memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
>> >  memtx_index_extent_free, memtx);
>> >  return &index->base;
>> > }
>> >+
>> >+struct index *
>> >+memtx_tree_index_new(struct memtx_engine *memtx, struct index_def *def)
>> >+{
>> >+ const struct index_vtab *vtab;
>> >+ if (def->key_def->for_func_index) {
>> >+ if (def->key_def->func_index_func == NULL)
>> >+ vtab = &memtx_tree_disabled_index_vtab;
>> >+ else
>> >+ vtab = &memtx_tree_func_index_vtab;
>> >+ } else if (def->key_def->is_multikey) {
>> >+ vtab = &memtx_tree_index_multikey_vtab;
>> >+ } else if (def->opts.hint) {
>> >+ vtab = &memtx_tree_use_hint_index_vtab;
>> >+ } else {
>> >+ vtab = &memtx_tree_no_hint_index_vtab;
>> >+ return memtx_tree_index_new_tpl<0>(memtx, def, vtab);
>> >+ }
>> >+ return memtx_tree_index_new_tpl<1>(memtx, def, vtab);
>> >+}
>> >diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
>> >index ef5ae3d..0bb803a 100644
>> >--- a/src/lib/salad/bps_tree.h
>> >+++ b/src/lib/salad/bps_tree.h
>> >@@ -168,6 +168,17 @@
>> > 
>> > /* {{{ BPS-tree interface settings */
>> > /**
>> >+ * Optional namespace for structs and functions.
>> >+ * Is set, struct and functions will have BPS_TREE_NAMESPACE:: prefix.
>> >+ * For example one can #define BPS_TREE_NAMESPACE my along with
>> >+ * #define BPS_TREE_NAME _test, and use then
>> >+ * struct my::bps_tree_test my_tree;
>> >+ * my::bps_tree_test_create(&my_tree, ...);
>> >+ *
>> >+ * #define BPS_TREE_NAMESPACE
>> >+ */
>> >+
>> >+/**
>> >  * Custom name for structs and functions.
>> >  * Struct and functions will have bps_tree##BPS_TREE_NAME name or prefix.
>> >  * For example one can #define BPS_TREE_NAME _test, and use then
>> >@@ -300,6 +311,10 @@
>> > 
>> > /* }}} */
>> > 
>> >+#ifdef BPS_TREE_NAMESPACE
>> >+namespace BPS_TREE_NAMESPACE {
>> >+#endif
>> >+
>> > /* {{{ BPS-tree internal settings */
>> > typedef int16_t bps_tree_pos_t;
>> > typedef uint32_t bps_tree_block_id_t;
>> >@@ -6188,3 +6203,7 @@ bps_tree_debug_check_internal_functions(bool assertme)
>> > #undef bps_tree_debug_check_insert_and_move_to_left_inner
>> > 
>> > /* }}} */
>> >+
>> >+#ifdef BPS_TREE_NAMESPACE
>> >+} /* namespace BPS_TREE_NAMESPACE { */
>> >+#endif
>> >diff --git a/test/box/alter.result b/test/box/alter.result
>> >index 237c2d8..5b412a7 100644
>> >--- a/test/box/alter.result
>> >+++ b/test/box/alter.result
>> >@@ -680,8 +680,9 @@ s.index.pk
>> >   - type: unsigned
>> >     is_nullable: false
>> >     fieldno: 2
>> >- type: TREE
>> >+ hint: true
>> >   id: 0
>> >+ type: TREE
>> >   space_id: 731
>> >   name: pk
>> > ...
>> >@@ -710,9 +711,10 @@ s.index.pk
>> >   - type: unsigned
>> >     is_nullable: false
>> >     fieldno: 1
>> >- space_id: 731
>> >+ hint: true
>> >   id: 0
>> >   type: TREE
>> >+ space_id: 731
>> >   name: pk
>> > ...
>> > s.index.secondary
>> >@@ -722,8 +724,9 @@ s.index.secondary
>> >   - type: unsigned
>> >     is_nullable: false
>> >     fieldno: 2
>> >- type: TREE
>> >+ hint: true
>> >   id: 1
>> >+ type: TREE
>> >   space_id: 731
>> >   name: secondary
>> > ...
>> >@@ -1559,3 +1562,97 @@ assert(err:match('does not exist') ~= nil)
>> > ---
>> > - true
>> > ...
>> >+-- hint
>> >+s = box.schema.create_space('test');
>> >+---
>> >+...
>> >+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
>> >+---
>> >+- true
>> >+...
>> >+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
>> >+---
>> >+- true
>> >+...
>> >+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
>> >+---
>> >+- false
>> >+...
>> >+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
>> >+---
>> >+- false
>> >+...
>> >+s.index.test1.hint
>> >+---
>> >+- true
>> >+...
>> >+s.index.test2.hint
>> >+---
>> >+- true
>> >+...
>> >+s.index.test3.hint
>> >+---
>> >+- false
>> >+...
>> >+s.index.test4.hint
>> >+---
>> >+- false
>> >+...
>> >+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
>> >+---
>> >+...
>> >+s.index.test1:bsize() == s.index.test2:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s.index.test1:bsize() > s.index.test3:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s.index.test1:bsize() > s.index.test4:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s.index.test1:alter{hint=false}
>> >+---
>> >+...
>> >+s.index.test2:alter{hint=true}
>> >+---
>> >+...
>> >+s.index.test3:alter{name='test33', hint=false}
>> >+---
>> >+...
>> >+s.index.test4:alter{hint=true}
>> >+---
>> >+...
>> >+s.index.test1.hint
>> >+---
>> >+- false
>> >+...
>> >+s.index.test2.hint
>> >+---
>> >+- true
>> >+...
>> >+s.index.test33.hint
>> >+---
>> >+- false
>> >+...
>> >+s.index.test4.hint
>> >+---
>> >+- true
>> >+...
>> >+s.index.test1:bsize() < s.index.test2:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s.index.test1:bsize() == s.index.test33:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s.index.test1:bsize() < s.index.test4:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s:drop()
>> >+---
>> >+...
>> >diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
>> >index abd08e2..2114186 100644
>> >--- a/test/box/alter.test.lua
>> >+++ b/test/box/alter.test.lua
>> >@@ -620,3 +620,37 @@ s:drop()
>> > s:alter({})
>> > ok, err = pcall(box.schema.space.alter, s.id, {})
>> > assert(err:match('does not exist') ~= nil)
>> >+
>> >+-- hint
>> >+s = box.schema.create_space('test');
>> >+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
>> >+s:create_index('test2', {type='tree', parts={{2, 'uint'}}, hint = true}).hint
>> >+s:create_index('test3', {type='tree', parts={{3, 'uint'}}, hint = false}).hint
>> >+s:create_index('test4', {type='tree', parts={{4, 'string'}}, hint = false}).hint
>> >+
>> >+s.index.test1.hint
>> >+s.index.test2.hint
>> >+s.index.test3.hint
>> >+s.index.test4.hint
>> >+
>> >+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' .. i} end box.commit()
>> >+
>> >+s.index.test1:bsize() == s.index.test2:bsize()
>> >+s.index.test1:bsize() > s.index.test3:bsize()
>> >+s.index.test1:bsize() > s.index.test4:bsize()
>> >+
>> >+s.index.test1:alter{hint=false}
>> >+s.index.test2:alter{hint=true}
>> >+s.index.test3:alter{name='test33', hint=false}
>> >+s.index.test4:alter{hint=true}
>> >+
>> >+s.index.test1.hint
>> >+s.index.test2.hint
>> >+s.index.test33.hint
>> >+s.index.test4.hint
>> >+
>> >+s.index.test1:bsize() < s.index.test2:bsize()
>> >+s.index.test1:bsize() == s.index.test33:bsize()
>> >+s.index.test1:bsize() < s.index.test4:bsize()
>> >+
>> >+s:drop()
>> >\ No newline at end of file
>> >diff --git a/test/box/errinj.result b/test/box/errinj.result
>> >index 613d22c..b8c2476 100644
>> >--- a/test/box/errinj.result
>> >+++ b/test/box/errinj.result
>> >@@ -1774,9 +1774,10 @@ rtreespace:create_index('pk', {if_not_exists = true})
>> >   - type: unsigned
>> >     is_nullable: false
>> >     fieldno: 1
>> >+ hint: true
>> >   id: 0
>> >- space_id: 512
>> >   type: TREE
>> >+ space_id: 512
>> >   name: pk
>> > ...
>> > rtreespace:create_index('target', {type='rtree', dimension = 3, parts={2, 'array'},unique = false, if_not_exists = true,})
>> >diff --git a/test/box/tree_pk.result b/test/box/tree_pk.result
>> >index df3c78b..18cb607 100644
>> >--- a/test/box/tree_pk.result
>> >+++ b/test/box/tree_pk.result
>> >@@ -852,3 +852,317 @@ box.internal.collation.drop('test')
>> > box.internal.collation.drop('test-ci')
>> > ---
>> > ...
>> >+-- hints
>> >+s = box.schema.space.create('test')
>> >+---
>> >+...
>> >+s:create_index('test', {type = 'tree', hint = 'true'} )
>> >+---
>> >+- error: Illegal parameters, options parameter 'hint' should be of type boolean
>> >+...
>> >+s:create_index('test', {type = 'hash', hint = true} )
>> >+---
>> >+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
>> >+ with memtx tree index'
>> >+...
>> >+s:create_index('test', {type = 'hash'}):alter({hint = true})
>> >+---
>> >+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
>> >+ with memtx tree index'
>> >+...
>> >+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
>> >+---
>> >+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
>> >+ can''t use hints'
>> >+...
>> >+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
>> >+---
>> >+- error: 'Can''t create or modify index ''multikey'' in space ''test'': multikey index
>> >+ can''t use hints'
>> >+...
>> >+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
>> >+---
>> >+...
>> >+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>> >+---
>> >+...
>> >+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
>> >+---
>> >+- error: 'Can''t create or modify index ''func'' in space ''test'': functional index
>> >+ can''t use hints'
>> >+...
>> >+s:drop()
>> >+---
>> >+...
>> >+s = box.schema.space.create('test', {engine = 'vinyl'})
>> >+---
>> >+...
>> >+s:create_index('test', {type = 'tree', hint = true} )
>> >+---
>> >+- error: 'Can''t create or modify index ''test'' in space ''test'': hint is only reasonable
>> >+ with memtx tree index'
>> >+...
>> >+s:drop()
>> >+---
>> >+...
>> >+-- numeric hints
>> >+s1 = box.schema.space.create('test1')
>> >+---
>> >+...
>> >+s1:create_index('test', {type = 'tree', hint = false}).hint
>> >+---
>> >+- false
>> >+...
>> >+s2 = box.schema.space.create('test2')
>> >+---
>> >+...
>> >+s2:create_index('test', {type = 'tree'}).hint
>> >+---
>> >+- true
>> >+...
>> >+N = 2000
>> >+---
>> >+...
>> >+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>> >+---
>> >+...
>> >+s1:count() == s2:count()
>> >+---
>> >+- true
>> >+...
>> >+good = true r1 = s1:select{} r2 = s2:select{}
>> >+---
>> >+...
>> >+if #r1 ~= #r2 then good = false end
>> >+---
>> >+...
>> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+r1 = nil r2 = nil
>> >+---
>> >+...
>> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>> >+---
>> >+...
>> >+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:bsize() < s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:alter{hint=true}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s2.index[0]:alter{hint=false}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() > s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:alter{hint=false}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1:drop()
>> >+---
>> >+...
>> >+s2:drop()
>> >+---
>> >+...
>> >+-- string hints
>> >+s1 = box.schema.space.create('test1')
>> >+---
>> >+...
>> >+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
>> >+---
>> >+- false
>> >+...
>> >+s2 = box.schema.space.create('test2')
>> >+---
>> >+...
>> >+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
>> >+---
>> >+- true
>> >+...
>> >+N = 1000
>> >+---
>> >+...
>> >+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>> >+---
>> >+...
>> >+s1:count() == s2:count()
>> >+---
>> >+- true
>> >+...
>> >+good = true r1 = s1:select{} r2 = s2:select{}
>> >+---
>> >+...
>> >+if #r1 ~= #r2 then good = false end
>> >+---
>> >+...
>> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+r1 = nil r2 = nil
>> >+---
>> >+...
>> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>> >+---
>> >+...
>> >+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:bsize() < s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:alter{hint=true}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s2.index[0]:alter{hint=false}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() > s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:alter{hint=false}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1:drop()
>> >+---
>> >+...
>> >+s2:drop()
>> >+---
>> >+...
>> >+-- string with collation hints
>> >+s1 = box.schema.space.create('test1')
>> >+---
>> >+...
>> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
>> >+---
>> >+- false
>> >+...
>> >+s2 = box.schema.space.create('test2')
>> >+---
>> >+...
>> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
>> >+---
>> >+- true
>> >+...
>> >+N = 1000
>> >+---
>> >+...
>> >+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
>> >+---
>> >+...
>> >+syms_size = #syms
>> >+---
>> >+...
>> >+len = 20
>> >+---
>> >+...
>> >+vals = {}
>> >+---
>> >+...
>> >+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
>> >+---
>> >+...
>> >+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
>> >+---
>> >+...
>> >+s1:count() == s2:count()
>> >+---
>> >+- true
>> >+...
>> >+good = true r1 = s1:select{} r2 = s2:select{}
>> >+---
>> >+...
>> >+if #r1 ~= #r2 then good = false end
>> >+---
>> >+...
>> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+r1 = nil r2 = nil
>> >+---
>> >+...
>> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>> >+---
>> >+...
>> >+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:bsize() < s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:alter{hint=true}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s2.index[0]:alter{hint=false}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() > s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1.index[0]:alter{hint=false}
>> >+---
>> >+...
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+---
>> >+- true
>> >+...
>> >+s1:drop()
>> >+---
>> >+...
>> >+s2:drop()
>> >+---
>> >+...
>> >diff --git a/test/box/tree_pk.test.lua b/test/box/tree_pk.test.lua
>> >index 1190ab4..2f22ed9 100644
>> >--- a/test/box/tree_pk.test.lua
>> >+++ b/test/box/tree_pk.test.lua
>> >@@ -301,3 +301,118 @@ s:drop()
>> > 
>> > box.internal.collation.drop('test')
>> > box.internal.collation.drop('test-ci')
>> >+
>> >+-- hints
>> >+s = box.schema.space.create('test')
>> >+s:create_index('test', {type = 'tree', hint = 'true'} )
>> >+s:create_index('test', {type = 'hash', hint = true} )
>> >+s:create_index('test', {type = 'hash'}):alter({hint = true})
>> >+s:create_index('multikey', {hint = true, parts = {{2, 'int', path = '[*]'}}})
>> >+s:create_index('multikey', {parts = {{2, 'int', path = '[*]'}}}):alter({hint = true})
>> >+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
>> >+box.schema.func.create('s', {body = lua_code, is_deterministic = true, is_sandboxed = true})
>> >+s:create_index('func', {hint = true, func = box.func.s.id, parts = {{1, 'unsigned'}}})
>> >+s:drop()
>> >+
>> >+s = box.schema.space.create('test', {engine = 'vinyl'})
>> >+s:create_index('test', {type = 'tree', hint = true} )
>> >+s:drop()
>> >+
>> >+-- numeric hints
>> >+s1 = box.schema.space.create('test1')
>> >+s1:create_index('test', {type = 'tree', hint = false}).hint
>> >+s2 = box.schema.space.create('test2')
>> >+s2:create_index('test', {type = 'tree'}).hint
>> >+
>> >+N = 2000
>> >+box.begin() for i = 1,N do local v = math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>> >+s1:count() == s2:count()
>> >+
>> >+good = true r1 = s1:select{} r2 = s2:select{}
>> >+if #r1 ~= #r2 then good = false end
>> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>> >+good
>> >+r1 = nil r2 = nil
>> >+
>> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>> >+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good = false end end
>> >+good
>> >+
>> >+s1.index[0]:bsize() < s2.index[0]:bsize()
>> >+s1.index[0]:alter{hint=true}
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+s2.index[0]:alter{hint=false}
>> >+s1.index[0]:bsize() > s2.index[0]:bsize()
>> >+s1.index[0]:alter{hint=false}
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+
>> >+s1:drop()
>> >+s2:drop()
>> >+
>> >+-- string hints
>> >+s1 = box.schema.space.create('test1')
>> >+s1:create_index('test', {type = 'tree', parts = {1, 'str'}, hint = false}).hint
>> >+s2 = box.schema.space.create('test2')
>> >+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
>> >+
>> >+N = 1000
>> >+box.begin() for i = 1,N do local v = ''..math.random(10 * N) s1:replace{v} s2:replace{v} end box.commit()
>> >+s1:count() == s2:count()
>> >+
>> >+good = true r1 = s1:select{} r2 = s2:select{}
>> >+if #r1 ~= #r2 then good = false end
>> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>> >+good
>> >+r1 = nil r2 = nil
>> >+
>> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>> >+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v}) then good = false end end
>> >+good
>> >+
>> >+s1.index[0]:bsize() < s2.index[0]:bsize()
>> >+s1.index[0]:alter{hint=true}
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+s2.index[0]:alter{hint=false}
>> >+s1.index[0]:bsize() > s2.index[0]:bsize()
>> >+s1.index[0]:alter{hint=false}
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+
>> >+s1:drop()
>> >+s2:drop()
>> >+
>> >+-- string with collation hints
>> >+s1 = box.schema.space.create('test1')
>> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}, hint = false}).hint
>> >+s2 = box.schema.space.create('test2')
>> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str', collation = 'Unicode'}}}).hint
>> >+
>> >+N = 1000
>> >+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
>> >+syms_size = #syms
>> >+len = 20
>> >+vals = {}
>> >+for i = 1,2*N do str = '' for j=1,len do str = str .. syms[math.random(syms_size)] end vals[i] = str end
>> >+
>> >+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
>> >+s1:count() == s2:count()
>> >+
>> >+good = true r1 = s1:select{} r2 = s2:select{}
>> >+if #r1 ~= #r2 then good = false end
>> >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false end end
>> >+good
>> >+r1 = nil r2 = nil
>> >+
>> >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else return t2 ~= nil end end
>> >+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v}) then good = false end end
>> >+good
>> >+
>> >+s1.index[0]:bsize() < s2.index[0]:bsize()
>> >+s1.index[0]:alter{hint=true}
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+s2.index[0]:alter{hint=false}
>> >+s1.index[0]:bsize() > s2.index[0]:bsize()
>> >+s1.index[0]:alter{hint=false}
>> >+s1.index[0]:bsize() == s2.index[0]:bsize()
>> >+
>> >+s1:drop()
>> >+s2:drop()
>> >diff --git a/test/box/tree_pk_multipart.result b/test/box/tree_pk_multipart.result
>> >index 93219f6..3f6bb94 100644
>> >--- a/test/box/tree_pk_multipart.result
>> >+++ b/test/box/tree_pk_multipart.result
>> >@@ -542,3 +542,156 @@ space:drop()
>> > space = nil
>> > ---
>> > ...
>> >+-- hints
>> >+test_run:cmd("setopt delimiter ';'")
>> >+---
>> >+- true
>> >+...
>> >+function equal(res1, res2)
>> >+ if #res1 ~= #res2 then
>> >+ return false
>> >+ end
>> >+ for k,v in pairs(res1) do
>> >+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
>> >+ return false
>> >+ end
>> >+ end
>> >+ return true
>> >+end
>> >+test_run:cmd("setopt delimiter ''");
>> >+---
>> >+...
>> >+-- num num
>> >+N1 = 100
>> >+---
>> >+...
>> >+t1 = {}
>> >+---
>> >+...
>> >+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
>> >+---
>> >+...
>> >+N2 = 5
>> >+---
>> >+...
>> >+t2 = {}
>> >+---
>> >+...
>> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>> >+---
>> >+...
>> >+s1 = box.schema.space.create('test1')
>> >+---
>> >+...
>> >+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
>> >+---
>> >+- false
>> >+...
>> >+s2 = box.schema.space.create('test2')
>> >+---
>> >+...
>> >+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
>> >+---
>> >+- true
>> >+...
>> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>> >+---
>> >+...
>> >+s1:count() == s2:count()
>> >+---
>> >+- true
>> >+...
>> >+equal(s1:select{}, s2:select{})
>> >+---
>> >+- true
>> >+...
>> >+good = true
>> >+---
>> >+...
>> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+s1:drop()
>> >+---
>> >+...
>> >+s2:drop()
>> >+---
>> >+...
>> >+-- str num
>> >+N1 = 100
>> >+---
>> >+...
>> >+t1 = {}
>> >+---
>> >+...
>> >+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
>> >+---
>> >+...
>> >+N2 = 5
>> >+---
>> >+...
>> >+t2 = {}
>> >+---
>> >+...
>> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>> >+---
>> >+...
>> >+s1 = box.schema.space.create('test1')
>> >+---
>> >+...
>> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
>> >+---
>> >+- false
>> >+...
>> >+s2 = box.schema.space.create('test2')
>> >+---
>> >+...
>> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
>> >+---
>> >+- true
>> >+...
>> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>> >+---
>> >+...
>> >+s1:count() == s2:count()
>> >+---
>> >+- true
>> >+...
>> >+equal(s1:select{}, s2:select{})
>> >+---
>> >+- true
>> >+...
>> >+good = true
>> >+---
>> >+...
>> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>> >+---
>> >+...
>> >+good
>> >+---
>> >+- true
>> >+...
>> >+s1:drop()
>> >+---
>> >+...
>> >+s2:drop()
>> >+---
>> >+...
>> >diff --git a/test/box/tree_pk_multipart.test.lua b/test/box/tree_pk_multipart.test.lua
>> >index 7d3405f..cba3a98 100644
>> >--- a/test/box/tree_pk_multipart.test.lua
>> >+++ b/test/box/tree_pk_multipart.test.lua
>> >@@ -194,3 +194,67 @@ test_run:cmd("setopt delimiter ''");
>> > space:drop()
>> > 
>> > space = nil
>> >+
>> >+
>> >+-- hints
>> >+test_run:cmd("setopt delimiter ';'")
>> >+function equal(res1, res2)
>> >+ if #res1 ~= #res2 then
>> >+ return false
>> >+ end
>> >+ for k,v in pairs(res1) do
>> >+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
>> >+ return false
>> >+ end
>> >+ end
>> >+ return true
>> >+end
>> >+test_run:cmd("setopt delimiter ''");
>> >+
>> >+-- num num
>> >+N1 = 100
>> >+t1 = {}
>> >+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
>> >+N2 = 5
>> >+t2 = {}
>> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>> >+
>> >+s1 = box.schema.space.create('test1')
>> >+s1:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = false}).hint
>> >+s2 = box.schema.space.create('test2')
>> >+s2:create_index('test', {type = 'tree', parts = {{1, 'num'}, {2, 'num'}}, hint = true}).hint
>> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>> >+s1:count() == s2:count()
>> >+equal(s1:select{}, s2:select{})
>> >+good = true
>> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>> >+good
>> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>> >+good
>> >+
>> >+s1:drop()
>> >+s2:drop()
>> >+
>> >+-- str num
>> >+N1 = 100
>> >+t1 = {}
>> >+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
>> >+N2 = 5
>> >+t2 = {}
>> >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>> >+
>> >+s1 = box.schema.space.create('test1')
>> >+s1:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = false}).hint
>> >+s2 = box.schema.space.create('test2')
>> >+s2:create_index('test', {type = 'tree', parts = {{1, 'str'}, {2, 'num'}}, hint = true}).hint
>> >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]} s2:replace{t1[i], t2[j]} end end
>> >+s1:count() == s2:count()
>> >+equal(s1:select{}, s2:select{})
>> >+good = true
>> >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]}, s2:select{t1[i]}) end
>> >+good
>> >+for i = 1,N1*2 do for j=1,N2*2 do good = good and equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>> >+good
>> >+
>> >+s1:drop()
>> >+s2:drop()
>> >--
>> >2.7.4
>> > 
>> --
>> Ilya Kosarev
--
Ilya Kosarev

[-- Attachment #2: Type: text/html, Size: 113350 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional
  2020-10-20 12:57       ` Ilya Kosarev
@ 2020-10-21  9:54         ` Aleksandr Lyapunov
  0 siblings, 0 replies; 11+ messages in thread
From: Aleksandr Lyapunov @ 2020-10-21  9:54 UTC (permalink / raw)
  To: Ilya Kosarev, Nikita Pettik; +Cc: tarantool-patches

Actually I remember some problems with bool template parameters..
Now I didn't find them. Changed to bool and everything works fine. thanks!

On 10/20/20 3:57 PM, Ilya Kosarev wrote:

>
>
>     Вторник, 20 октября 2020, 15:38 +03:00 от Nikita Pettik
>     <korablev@tarantool.org>:
>     On 20 Oct 15:34, Ilya Kosarev wrote:
>     >
>     > Hi,
>     >
>     > Thanks for the patch!
>     >
>     > LGTM, i checked for the number of compilers that if with
>     the templated
>     > agrument is being processed at the compile time so that there
>     are no jumps
>     > in asm code.
>     >
>     > What i think is needed is the enum like the following:
>     > enum {
>     >         NOT_HINTED = 0 ,
>     >         HINTED = 1 ,
>     > };
>
>     I like this way. Btw why not using just boolean type?
>
> I think there was an idea to implement more than one type of hints.
> Also it doesn’t really matter as far as we have the proper constants.
>
>
>     > to avoid using <0> & <1>. If needed i can push such patch to the
>     branch.
>     > >Понедельник, 19 октября 2020, 12:52 +03:00 от Aleksandr
>     Lyapunov <alyapunov@tarantool.org
>     </compose?To=alyapunov@tarantool.org>>:
>     > >
>     > >Since 9fba29abb4e05babb9b23b4413bd8083f0fba933 (memtx:
>     introduce tuple
>     > >compare hint) memtx tree key data (indexes) started to contain
>     extra 8
>     > >bytes as a hint. Now it is optional and can be turned off in an
>     index
>     > >options with "hint = false" entry.
>     > >
>     > >Closes #4927
>     > >
>     > >@TarantoolBot document
>     > >Title: memtx: optional tuple compare hints
>     > >Update the documentation for an index creation to reflect that
>     there is
>     > >now an option to turn off hints for the index.
>     > >---
>     > > src/box/index_def.c | 2 +
>     > > src/box/index_def.h | 6 +
>     > > src/box/lua/schema.lua | 53 ++++
>     > > src/box/lua/space.cc | 7 +
>     > > src/box/memtx_engine.c | 2 +
>     > > src/box/memtx_tree.cc | 599 ++++++++++++++++++++++++------------
>     > > src/lib/salad/bps_tree.h | 19 ++
>     > > test/box/alter.result | 103 ++++++-
>     > > test/box/alter.test.lua | 34 ++
>     > > test/box/errinj.result | 3 +-
>     > > test/box/tree_pk.result | 314 +++++++++++++++++++
>     > > test/box/tree_pk.test.lua | 115 +++++++
>     > > test/box/tree_pk_multipart.result | 153 +++++++++
>     > > test/box/tree_pk_multipart.test.lua | 64 ++++
>     > > 14 files changed, 1269 insertions(+), 205 deletions(-)
>     > >
>     > >diff --git a/src/box/index_def.c b/src/box/index_def.c
>     > >index 9802961..79394b8 100644
>     > >--- a/src/box/index_def.c
>     > >+++ b/src/box/index_def.c
>     > >@@ -51,6 +51,7 @@ const struct index_opts index_opts_default = {
>     > >  /* .lsn = */ 0,
>     > >  /* .stat = */ NULL,
>     > >  /* .func = */ 0,
>     > >+ /* .hint = */ true,
>     > > };
>     > >
>     > > const struct opt_def index_opts_reg[] = {
>     > >@@ -66,6 +67,7 @@ const struct opt_def index_opts_reg[] = {
>     > >  OPT_DEF("lsn", OPT_INT64, struct index_opts, lsn),
>     > >  OPT_DEF("func", OPT_UINT32, struct index_opts, func_id),
>     > >  OPT_DEF_LEGACY("sql"),
>     > >+ OPT_DEF("hint", OPT_BOOL, struct index_opts, hint),
>     > >  OPT_END,
>     > > };
>     > >
>     > >diff --git a/src/box/index_def.h b/src/box/index_def.h
>     > >index d928b23..2180a69 100644
>     > >--- a/src/box/index_def.h
>     > >+++ b/src/box/index_def.h
>     > >@@ -165,6 +165,10 @@ struct index_opts {
>     > >  struct index_stat *stat;
>     > >  /** Identifier of the functional index function. */
>     > >  uint32_t func_id;
>     > >+ /**
>     > >+ * Use hint optimization for tree index.
>     > >+ */
>     > >+ bool hint;
>     > > };
>     > >
>     > > extern const struct index_opts index_opts_default;
>     > >@@ -211,6 +215,8 @@ index_opts_cmp(const struct index_opts *o1,
>     const struct index_opts *o2)
>     > >  return o1->bloom_fpr < o2->bloom_fpr ? -1 : 1;
>     > >  if (o1->func_id != o2->func_id)
>     > >  return o1->func_id - o2->func_id;
>     > >+ if (o1->hint != o2->hint)
>     > >+ return o1->hint - o2->hint;
>     > >  return 0;
>     > > }
>     > >
>     > >diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
>     > >index 1131af7..9cc1289 100644
>     > >--- a/src/box/lua/schema.lua
>     > >+++ b/src/box/lua/schema.lua
>     > >@@ -1000,8 +1000,31 @@ local index_options = {
>     > >     page_size = 'number',
>     > >     bloom_fpr = 'number',
>     > >     func = 'number, string',
>     > >+ hint = 'boolean',
>     > > }
>     > >
>     > >+local function jsonpaths_from_idx_parts(parts)
>     > >+ local paths = {}
>     > >+
>     > >+ for _, part in pairs(parts) do
>     > >+ if type(part.path) == 'string' then
>     > >+ table.insert(paths, part.path)
>     > >+ end
>     > >+ end
>     > >+
>     > >+ return paths
>     > >+end
>     > >+
>     > >+local function is_multikey_index(parts)
>     > >+ for _, path in pairs(jsonpaths_from_idx_parts(parts)) do
>     > >+ if path:find('[*]', 1, true) then
>     > >+ return true
>     > >+ end
>     > >+ end
>     > >+
>     > >+ return false
>     > >+end
>     > >+
>     > > --
>     > > -- check_param_table() template for alter index,
>     > > -- includes all index options.
>     > >@@ -1076,6 +1099,15 @@ box.schema.index.create =
>     function(space_id, name, options)
>     > >         options_defaults = {}
>     > >     end
>     > >     options = update_param_table(options, options_defaults)
>     > >+ if options.hint and
>     > >+ (options.type ~= 'tree' or box.space[space_id].engine ~=
>     'memtx') then
>     > >+ box.error(box.error.MODIFY_INDEX, name, space.name,
>     > >+ "hint is only reasonable with memtx tree index")
>     > >+ end
>     > >+ if options.hint and options.func then
>     > >+ box.error(box.error.MODIFY_INDEX, name, space.name,
>     > >+ "functional index can't use hints")
>     > >+ end
>     > >
>     > >     local _index = box.space[box.schema.INDEX_ID]
>     > >     local _vindex = box.space[box.schema.VINDEX_ID]
>     > >@@ -1115,6 +1147,7 @@ box.schema.index.create =
>     function(space_id, name, options)
>     > >             run_size_ratio = options.run_size_ratio,
>     > >             bloom_fpr = options.bloom_fpr,
>     > >             func = options.func,
>     > >+ hint = options.hint,
>     > >     }
>     > >     local field_type_aliases = {
>     > >         num = 'unsigned'; -- Deprecated since 1.7.2
>     > >@@ -1135,6 +1168,10 @@ box.schema.index.create =
>     function(space_id, name, options)
>     > >     if parts_can_be_simplified then
>     > >         parts = simplify_index_parts(parts)
>     > >     end
>     > >+ if options.hint and is_multikey_index(parts) then
>     > >+ box.error(box.error.MODIFY_INDEX, name, space.name,
>     > >+ "multikey index can't use hints")
>     > >+ end
>     > >     if index_opts.func ~= nil and type(index_opts.func) ==
>     'string' then
>     > >         index_opts.func = func_id_by_name(index_opts.func)
>     > >     end
>     > >@@ -1253,6 +1290,17 @@ box.schema.index.alter =
>     function(space_id, index_id, options)
>     > >             index_opts[k] = options[k]
>     > >         end
>     > >     end
>     > >+ if options.hint and
>     > >+ (options.type ~= 'tree' or box.space[space_id].engine ~=
>     'memtx') then
>     > >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>     > >+ space.name,
>     > >+ "hint is only reasonable with memtx tree index")
>     > >+ end
>     > >+ if options.hint and options.func then
>     > >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>     > >+ space.name,
>     > >+ "functional index can't use hints")
>     > >+ end
>     > >     if options.parts then
>     > >         local parts_can_be_simplified
>     > >         parts, parts_can_be_simplified =
>     > >@@ -1262,6 +1310,11 @@ box.schema.index.alter =
>     function(space_id, index_id, options)
>     > >             parts = simplify_index_parts(parts)
>     > >         end
>     > >     end
>     > >+ if options.hint and is_multikey_index(parts) then
>     > >+ box.error(box.error.MODIFY_INDEX, space.index[index_id].name,
>     > >+ space.name,
>     > >+ "multikey index can't use hints")
>     > >+ end
>     > >     if index_opts.func ~= nil and type(index_opts.func) ==
>     'string' then
>     > >         index_opts.func = func_id_by_name(index_opts.func)
>     > >     end
>     > >diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
>     > >index 177c588..1ea993c 100644
>     > >--- a/src/box/lua/space.cc
>     > >+++ b/src/box/lua/space.cc
>     > >@@ -344,6 +344,13 @@ lbox_fillspace(struct lua_State *L, struct
>     space *space, int i)
>     > >  lua_pushnumber(L, index_opts->dimension);
>     > >  lua_setfield(L, -2, "dimension");
>     > >  }
>     > >+ if (space_is_memtx(space) && index_def->type == TREE) {
>     > >+ lua_pushboolean(L, index_opts->hint);
>     > >+ lua_setfield(L, -2, "hint");
>     > >+ } else {
>     > >+ lua_pushnil(L);
>     > >+ lua_setfield(L, -2, "hint");
>     > >+ }
>     > >
>     > >  if (index_opts->func_id > 0) {
>     > >  lua_pushstring(L, "func");
>     > >diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
>     > >index 8147557 ..43000ba 100644
>     > >--- a/src/box/memtx_engine.c
>     > >+++ b/src/box/memtx_engine.c
>     > >@@ -1398,6 +1398,8 @@
>     memtx_index_def_change_requires_rebuild(struct index *index,
>     > >  return true;
>     > >  if (old_def->opts.func_id != new_def->opts.func_id)
>     > >  return true;
>     > >+ if (old_def->opts.hint != new_def->opts.hint)
>     > >+ return true;
>     > >
>     > >  const struct key_def *old_cmp_def, *new_cmp_def;
>     > >  if (index_depends_on_pk(index)) {
>     > >diff --git a/src/box/memtx_tree.cc b/src/box/memtx_tree.cc
>     > >index d3b993b..17d58c5 100644
>     > >--- a/src/box/memtx_tree.cc
>     > >+++ b/src/box/memtx_tree.cc
>     > >@@ -45,23 +45,51 @@
>     > > /**
>     > >  * Struct that is used as a key in BPS tree definition.
>     > >  */
>     > >-struct memtx_tree_key_data {
>     > >+struct memtx_tree_key_data_common {
>     > >  /** Sequence of msgpacked search fields. */
>     > >  const char *key;
>     > >  /** Number of msgpacked search fields. */
>     > >  uint32_t part_count;
>     > >+};
>     > >+
>     > >+template <int USE_HINT>
>     > >+struct memtx_tree_key_data;
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_key_data<0> : memtx_tree_key_data_common {
>     > >+ static constexpr hint_t hint = HINT_NONE;
>     > >+ void set_hint(hint_t) { assert(false); }
>     > >+};
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_key_data<1> : memtx_tree_key_data_common {
>     > >  /** Comparison hint, see tuple_hint(). */
>     > >  hint_t hint;
>     > >+ void set_hint(hint_t h) { hint = h; }
>     > > };
>     > >
>     > > /**
>     > >  * Struct that is used as a elem in BPS tree definition.
>     > >  */
>     > >-struct memtx_tree_data {
>     > >+struct memtx_tree_data_common {
>     > >  /* Tuple that this node is represents. */
>     > >  struct tuple *tuple;
>     > >+};
>     > >+
>     > >+template <int USE_HINT>
>     > >+struct memtx_tree_data;
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_data<0> : memtx_tree_data_common {
>     > >+ static constexpr hint_t hint = HINT_NONE;
>     > >+ void set_hint(hint_t) { assert(false); }
>     > >+};
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_data<1> : memtx_tree_data<0> {
>     > >  /** Comparison hint, see key_hint(). */
>     > >  hint_t hint;
>     > >+ void set_hint(hint_t h) { hint = h; }
>     > > };
>     > >
>     > > /**
>     > >@@ -73,8 +101,8 @@ struct memtx_tree_data {
>     > >  * @retval false - Otherwise.
>     > >  */
>     > > static bool
>     > >-memtx_tree_data_is_equal(const struct memtx_tree_data *a,
>     > >- const struct memtx_tree_data *b)
>     > >+memtx_tree_data_is_equal(const struct memtx_tree_data_common *a,
>     > >+ const struct memtx_tree_data_common *b)
>     > > {
>     > >  return a->tuple == b->tuple;
>     > > }
>     > >@@ -89,12 +117,28 @@ memtx_tree_data_is_equal(const struct
>     memtx_tree_data *a,
>     > >  (b)->part_count, (b)->hint, arg)
>     > > #define BPS_TREE_IS_IDENTICAL(a, b)
>     memtx_tree_data_is_equal(&a, &b)
>     > > #define BPS_TREE_NO_DEBUG 1
>     > >-#define bps_tree_elem_t struct memtx_tree_data
>     > >-#define bps_tree_key_t struct memtx_tree_key_data *
>     > > #define bps_tree_arg_t struct key_def *
>     > >
>     > >+#define BPS_TREE_NAMESPACE NS_NO_HINT
>     > >+#define bps_tree_elem_t struct memtx_tree_data<0>
>     > >+#define bps_tree_key_t struct memtx_tree_key_data<0> *
>     > >+
>     > > #include "salad/bps_tree.h"
>     > >
>     > >+#undef BPS_TREE_NAMESPACE
>     > >+#undef bps_tree_elem_t
>     > >+#undef bps_tree_key_t
>     > >+
>     > >+#define BPS_TREE_NAMESPACE NS_USE_HINT
>     > >+#define bps_tree_elem_t struct memtx_tree_data<1>
>     > >+#define bps_tree_key_t struct memtx_tree_key_data<1> *
>     > >+
>     > >+#include "salad/bps_tree.h"
>     > >+
>     > >+#undef BPS_TREE_NAMESPACE
>     > >+#undef bps_tree_elem_t
>     > >+#undef bps_tree_key_t
>     > >+
>     > > #undef BPS_TREE_NAME
>     > > #undef BPS_TREE_BLOCK_SIZE
>     > > #undef BPS_TREE_EXTENT_SIZE
>     > >@@ -102,66 +146,119 @@ memtx_tree_data_is_equal(const struct
>     memtx_tree_data *a,
>     > > #undef BPS_TREE_COMPARE_KEY
>     > > #undef BPS_TREE_IS_IDENTICAL
>     > > #undef BPS_TREE_NO_DEBUG
>     > >-#undef bps_tree_elem_t
>     > >-#undef bps_tree_key_t
>     > > #undef bps_tree_arg_t
>     > >
>     > >+using namespace NS_NO_HINT;
>     > >+using namespace NS_USE_HINT;
>     > >+
>     > >+template <int USE_HINT>
>     > >+struct memtx_tree_selector;
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_selector<0> : NS_NO_HINT::memtx_tree {};
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_selector<1> : NS_USE_HINT::memtx_tree {};
>     > >+
>     > >+template <int USE_HINT>
>     > >+using memtx_tree_t = struct memtx_tree_selector<USE_HINT>;
>     > >+
>     > >+template <int USE_HINT>
>     > >+struct memtx_tree_iterator_selector;
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_iterator_selector<0> {
>     > >+ using type = NS_NO_HINT::memtx_tree_iterator;
>     > >+};
>     > >+
>     > >+template <>
>     > >+struct memtx_tree_iterator_selector<1> {
>     > >+ using type = NS_USE_HINT::memtx_tree_iterator;
>     > >+};
>     > >+
>     > >+template <int USE_HINT>
>     > >+using memtx_tree_iterator_t = typename
>     memtx_tree_iterator_selector<USE_HINT>::type;
>     > >+
>     > >+static void
>     > >+invalidate_tree_iterator(NS_NO_HINT::memtx_tree_iterator *itr)
>     > >+{
>     > >+ *itr = NS_NO_HINT::memtx_tree_invalid_iterator();
>     > >+}
>     > >+
>     > >+static void
>     > >+invalidate_tree_iterator(NS_USE_HINT::memtx_tree_iterator *itr)
>     > >+{
>     > >+ *itr = NS_USE_HINT::memtx_tree_invalid_iterator();
>     > >+}
>     > >+
>     > >+template <int USE_HINT>
>     > > struct memtx_tree_index {
>     > >  struct index base;
>     > >- struct memtx_tree tree;
>     > >- struct memtx_tree_data *build_array;
>     > >+ memtx_tree_t<USE_HINT> tree;
>     > >+ struct memtx_tree_data<USE_HINT> *build_array;
>     > >  size_t build_array_size, build_array_alloc_size;
>     > >  struct memtx_gc_task gc_task;
>     > >- struct memtx_tree_iterator gc_iterator;
>     > >+ memtx_tree_iterator_t<USE_HINT> gc_iterator;
>     > > };
>     > >
>     > > /* {{{ Utilities.
>     *************************************************/
>     > >
>     > >+template <class TREE>
>     > > static inline struct key_def *
>     > >-memtx_tree_cmp_def(struct memtx_tree *tree)
>     > >+memtx_tree_cmp_def(TREE *tree)
>     > > {
>     > >  return tree->arg;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > memtx_tree_qcompare(const void* a, const void *b, void *c)
>     > > {
>     > >- const struct memtx_tree_data *data_a = (struct
>     memtx_tree_data *)a;
>     > >- const struct memtx_tree_data *data_b = (struct
>     memtx_tree_data *)b;
>     > >+ const struct memtx_tree_data<USE_HINT> *data_a =
>     > >+ (struct memtx_tree_data<USE_HINT> *)a;
>     > >+ const struct memtx_tree_data<USE_HINT> *data_b =
>     > >+ (struct memtx_tree_data<USE_HINT> *)b;
>     > >  struct key_def *key_def = (struct key_def *)c;
>     > >  return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
>     > >  data_b->hint, key_def);
>     > > }
>     > >
>     > > /* {{{ MemtxTree Iterators
>     ****************************************/
>     > >+template <int USE_HINT>
>     > > struct tree_iterator {
>     > >  struct iterator base;
>     > >- struct memtx_tree_iterator tree_iterator;
>     > >+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
>     > >  enum iterator_type type;
>     > >- struct memtx_tree_key_data key_data;
>     > >- struct memtx_tree_data current;
>     > >+ struct memtx_tree_key_data<USE_HINT> key_data;
>     > >+ struct memtx_tree_data<USE_HINT> current;
>     > >  /** Memory pool the iterator was allocated from. */
>     > >  struct mempool *pool;
>     > > };
>     > >
>     > >-static_assert(sizeof(struct tree_iterator) <= MEMTX_ITERATOR_SIZE,
>     > >- "sizeof(struct tree_iterator) must be less than or equal "
>     > >+static_assert(sizeof(struct tree_iterator<0>) <=
>     MEMTX_ITERATOR_SIZE,
>     > >+ "sizeof(struct tree_iterator<0>) must be less than or equal "
>     > >+ "to MEMTX_ITERATOR_SIZE");
>     > >+static_assert(sizeof(struct tree_iterator<1>) <=
>     MEMTX_ITERATOR_SIZE,
>     > >+ "sizeof(struct tree_iterator<1>) must be less than or equal "
>     > >  "to MEMTX_ITERATOR_SIZE");
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > tree_iterator_free(struct iterator *iterator);
>     > >
>     > >-static inline struct tree_iterator *
>     > >-tree_iterator(struct iterator *it)
>     > >+template <int USE_HINT>
>     > >+static inline struct tree_iterator<USE_HINT> *
>     > >+get_tree_iterator(struct iterator *it)
>     > > {
>     > >- assert(it->free == tree_iterator_free);
>     > >- return (struct tree_iterator *) it;
>     > >+ assert(it->free == &tree_iterator_free<USE_HINT>);
>     > >+ return (struct tree_iterator<USE_HINT> *) it;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > tree_iterator_free(struct iterator *iterator)
>     > > {
>     > >- struct tree_iterator *it = tree_iterator(iterator);
>     > >+ struct tree_iterator<USE_HINT> *it =
>     get_tree_iterator<USE_HINT>(iterator);
>     > >  struct tuple *tuple = it->current.tuple;
>     > >  if (tuple != NULL)
>     > >  tuple_unref(tuple);
>     > >@@ -176,14 +273,15 @@ tree_iterator_dummie(struct iterator
>     *iterator, struct tuple **ret)
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > tree_iterator_next_base(struct iterator *iterator, struct
>     tuple **ret)
>     > > {
>     > >- struct memtx_tree_index *index =
>     > >- (struct memtx_tree_index *)iterator->index;
>     > >- struct tree_iterator *it = tree_iterator(iterator);
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>     > >+ struct tree_iterator<USE_HINT> *it =
>     get_tree_iterator<USE_HINT>(iterator);
>     > >  assert(it->current.tuple != NULL);
>     > >- struct memtx_tree_data *check =
>     > >+ struct memtx_tree_data<USE_HINT> *check =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  if (check == NULL || !memtx_tree_data_is_equal(check,
>     &it->current)) {
>     > >  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
>     > >@@ -192,7 +290,7 @@ tree_iterator_next_base(struct iterator
>     *iterator, struct tuple **ret)
>     > > memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
>     > >  }
>     > >  tuple_unref(it->current.tuple);
>     > >- struct memtx_tree_data *res =
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  if (res == NULL) {
>     > >  iterator->next = tree_iterator_dummie;
>     > >@@ -206,14 +304,15 @@ tree_iterator_next_base(struct iterator
>     *iterator, struct tuple **ret)
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > tree_iterator_prev_base(struct iterator *iterator, struct
>     tuple **ret)
>     > > {
>     > >- struct memtx_tree_index *index =
>     > >- (struct memtx_tree_index *)iterator->index;
>     > >- struct tree_iterator *it = tree_iterator(iterator);
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>     > >+ struct tree_iterator<USE_HINT> *it =
>     get_tree_iterator<USE_HINT>(iterator);
>     > >  assert(it->current.tuple != NULL);
>     > >- struct memtx_tree_data *check =
>     > >+ struct memtx_tree_data<USE_HINT> *check =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  if (check == NULL || !memtx_tree_data_is_equal(check,
>     &it->current)) {
>     > >  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
>     > >@@ -221,7 +320,7 @@ tree_iterator_prev_base(struct iterator
>     *iterator, struct tuple **ret)
>     > >  }
>     > > memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
>     > >  tuple_unref(it->current.tuple);
>     > >- struct memtx_tree_data *res =
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  if (!res) {
>     > >  iterator->next = tree_iterator_dummie;
>     > >@@ -235,14 +334,15 @@ tree_iterator_prev_base(struct iterator
>     *iterator, struct tuple **ret)
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > tree_iterator_next_equal_base(struct iterator *iterator,
>     struct tuple **ret)
>     > > {
>     > >- struct memtx_tree_index *index =
>     > >- (struct memtx_tree_index *)iterator->index;
>     > >- struct tree_iterator *it = tree_iterator(iterator);
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>     > >+ struct tree_iterator<USE_HINT> *it =
>     get_tree_iterator<USE_HINT>(iterator);
>     > >  assert(it->current.tuple != NULL);
>     > >- struct memtx_tree_data *check =
>     > >+ struct memtx_tree_data<USE_HINT> *check =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  if (check == NULL || !memtx_tree_data_is_equal(check,
>     &it->current)) {
>     > >  it->tree_iterator = memtx_tree_upper_bound_elem(&index->tree,
>     > >@@ -251,7 +351,7 @@ tree_iterator_next_equal_base(struct
>     iterator *iterator, struct tuple **ret)
>     > > memtx_tree_iterator_next(&index->tree, &it->tree_iterator);
>     > >  }
>     > >  tuple_unref(it->current.tuple);
>     > >- struct memtx_tree_data *res =
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  /* Use user key def to save a few loops. */
>     > >  if (res == NULL ||
>     > >@@ -271,14 +371,15 @@ tree_iterator_next_equal_base(struct
>     iterator *iterator, struct tuple **ret)
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > tree_iterator_prev_equal_base(struct iterator *iterator,
>     struct tuple **ret)
>     > > {
>     > >- struct memtx_tree_index *index =
>     > >- (struct memtx_tree_index *)iterator->index;
>     > >- struct tree_iterator *it = tree_iterator(iterator);
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>     > >+ struct tree_iterator<USE_HINT> *it =
>     get_tree_iterator<USE_HINT>(iterator);
>     > >  assert(it->current.tuple != NULL);
>     > >- struct memtx_tree_data *check =
>     > >+ struct memtx_tree_data<USE_HINT> *check =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  if (check == NULL || !memtx_tree_data_is_equal(check,
>     &it->current)) {
>     > >  it->tree_iterator = memtx_tree_lower_bound_elem(&index->tree,
>     > >@@ -286,7 +387,7 @@ tree_iterator_prev_equal_base(struct
>     iterator *iterator, struct tuple **ret)
>     > >  }
>     > > memtx_tree_iterator_prev(&index->tree, &it->tree_iterator);
>     > >  tuple_unref(it->current.tuple);
>     > >- struct memtx_tree_data *res =
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > > memtx_tree_iterator_get_elem(&index->tree, &it->tree_iterator);
>     > >  /* Use user key def to save a few loops. */
>     > >  if (res == NULL ||
>     > >@@ -307,28 +408,30 @@ tree_iterator_prev_equal_base(struct
>     iterator *iterator, struct tuple **ret)
>     > > }
>     > >
>     > > #define WRAP_ITERATOR_METHOD(name) \
>     > >+template <int USE_HINT> \
>     > > static int \
>     > > name(struct iterator *iterator, struct tuple **ret) \
>     > > { \
>     > >- struct memtx_tree *tree = \
>     > >- &((struct memtx_tree_index *)iterator->index)->tree; \
>     > >- struct tree_iterator *it = tree_iterator(iterator); \
>     > >- struct memtx_tree_iterator *ti = &it->tree_iterator; \
>     > >+ memtx_tree_t<USE_HINT> *tree = \
>     > >+ &((struct memtx_tree_index<USE_HINT> *)iterator->index)->tree; \
>     > >+ struct tree_iterator<USE_HINT> *it = \
>     > >+ get_tree_iterator<USE_HINT>(iterator); \
>     > >+ memtx_tree_iterator_t<USE_HINT> *ti = &it->tree_iterator; \
>     > >  uint32_t iid = iterator->index->def->iid; \
>     > >  bool is_multikey = iterator->index->def->key_def->is_multikey; \
>     > >  struct txn *txn = in_txn(); \
>     > >  struct space *space = space_by_id(iterator->space_id); \
>     > >  bool is_rw = txn != NULL; \
>     > >  do { \
>     > >- int rc = name##_base(iterator, ret); \
>     > >+ int rc = name##_base<USE_HINT>(iterator, ret); \
>     > >  if (rc != 0 || *ret == NULL) \
>     > >  return rc; \
>     > >  uint32_t mk_index = 0; \
>     > >  if (is_multikey) { \
>     > >- struct memtx_tree_data *check = \
>     > >+ struct memtx_tree_data<USE_HINT> *check = \
>     > >  memtx_tree_iterator_get_elem(tree, ti); \
>     > >  assert(check != NULL); \
>     > >- mk_index = check->hint; \
>     > >+ mk_index = (uint32_t)check->hint; \
>     > >  } \
>     > >  *ret = memtx_tx_tuple_clarify(txn, space, *ret, \
>     > >  iid, mk_index, is_rw); \
>     > >@@ -347,27 +450,28 @@
>     WRAP_ITERATOR_METHOD(tree_iterator_prev_equal);
>     > >
>     > > #undef WRAP_ITERATOR_METHOD
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > >-tree_iterator_set_next_method(struct tree_iterator *it)
>     > >+tree_iterator_set_next_method(struct tree_iterator<USE_HINT> *it)
>     > > {
>     > >  assert(it->current.tuple != NULL);
>     > >  switch (it->type) {
>     > >  case ITER_EQ:
>     > >- it->base.next = tree_iterator_next_equal;
>     > >+ it->base.next = tree_iterator_next_equal<USE_HINT>;
>     > >  break;
>     > >  case ITER_REQ:
>     > >- it->base.next = tree_iterator_prev_equal;
>     > >+ it->base.next = tree_iterator_prev_equal<USE_HINT>;
>     > >  break;
>     > >  case ITER_ALL:
>     > >- it->base.next = tree_iterator_next;
>     > >+ it->base.next = tree_iterator_next<USE_HINT>;
>     > >  break;
>     > >  case ITER_LT:
>     > >  case ITER_LE:
>     > >- it->base.next = tree_iterator_prev;
>     > >+ it->base.next = tree_iterator_prev<USE_HINT>;
>     > >  break;
>     > >  case ITER_GE:
>     > >  case ITER_GT:
>     > >- it->base.next = tree_iterator_next;
>     > >+ it->base.next = tree_iterator_next<USE_HINT>;
>     > >  break;
>     > >  default:
>     > >  /* The type was checked in initIterator */
>     > >@@ -375,15 +479,16 @@ tree_iterator_set_next_method(struct
>     tree_iterator *it)
>     > >  }
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > tree_iterator_start(struct iterator *iterator, struct tuple **ret)
>     > > {
>     > >  *ret = NULL;
>     > >- struct memtx_tree_index *index =
>     > >- (struct memtx_tree_index *)iterator->index;
>     > >- struct tree_iterator *it = tree_iterator(iterator);
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)iterator->index;
>     > >+ struct tree_iterator<USE_HINT> *it =
>     get_tree_iterator<USE_HINT>(iterator);
>     > >  it->base.next = tree_iterator_dummie;
>     > >- struct memtx_tree *tree = &index->tree;
>     > >+ memtx_tree_t<USE_HINT> *tree = &index->tree;
>     > >�� enum iterator_type type = it->type;
>     > >  bool exact = false;
>     > >  assert(it->current.tuple == NULL);
>     > >@@ -423,8 +528,8 @@ tree_iterator_start(struct iterator
>     *iterator, struct tuple **ret)
>     > >  }
>     > >  }
>     > >
>     > >- struct memtx_tree_data *res = memtx_tree_iterator_get_elem(tree,
>     > >- &it->tree_iterator);
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > >+ memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
>     > >  if (!res)
>     > >  return 0;
>     > >  *ret = res->tuple;
>     > >@@ -454,14 +559,16 @@ tree_iterator_start(struct iterator
>     *iterator, struct tuple **ret)
>     > >
>     > > /* {{{ MemtxTree
>     **********************************************************/
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > >-memtx_tree_index_free(struct memtx_tree_index *index)
>     > >+memtx_tree_index_free(struct memtx_tree_index<USE_HINT> *index)
>     > > {
>     > >  memtx_tree_destroy(&index->tree);
>     > >  free(index->build_array);
>     > >  free(index);
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > memtx_tree_index_gc_run(struct memtx_gc_task *task, bool *done)
>     > > {
>     > >@@ -475,14 +582,14 @@ memtx_tree_index_gc_run(struct
>     memtx_gc_task *task, bool *done)
>     > >  enum { YIELD_LOOPS = 10 };
>     > > #endif
>     > >
>     > >- struct memtx_tree_index *index = container_of(task,
>     > >- struct memtx_tree_index, gc_task);
>     > >- struct memtx_tree *tree = &index->tree;
>     > >- struct memtx_tree_iterator *itr = &index->gc_iterator;
>     > >+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
>     > >+ struct memtx_tree_index<USE_HINT>, gc_task);
>     > >+ memtx_tree_t<USE_HINT> *tree = &index->tree;
>     > >+ memtx_tree_iterator_t<USE_HINT> *itr = &index->gc_iterator;
>     > >
>     > >  unsigned int loops = 0;
>     > >  while (!memtx_tree_iterator_is_invalid(itr)) {
>     > >- struct memtx_tree_data *res =
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > >  memtx_tree_iterator_get_elem(tree, itr);
>     > >  memtx_tree_iterator_next(tree, itr);
>     > >  tuple_unref(res->tuple);
>     > >@@ -494,23 +601,32 @@ memtx_tree_index_gc_run(struct
>     memtx_gc_task *task, bool *done)
>     > >  *done = true;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > memtx_tree_index_gc_free(struct memtx_gc_task *task)
>     > > {
>     > >- struct memtx_tree_index *index = container_of(task,
>     > >- struct memtx_tree_index, gc_task);
>     > >+ struct memtx_tree_index<USE_HINT> *index = container_of(task,
>     > >+ struct memtx_tree_index<USE_HINT>, gc_task);
>     > >  memtx_tree_index_free(index);
>     > > }
>     > >
>     > >-static const struct memtx_gc_task_vtab
>     memtx_tree_index_gc_vtab = {
>     > >- .run = memtx_tree_index_gc_run,
>     > >- .free = memtx_tree_index_gc_free,
>     > >+template <int USE_HINT>
>     > >+static struct memtx_gc_task_vtab * get_memtx_tree_index_gc_vtab()
>     > >+{
>     > >+ static memtx_gc_task_vtab tab =
>     > >+ {
>     > >+ .run = memtx_tree_index_gc_run<USE_HINT>,
>     > >+ .free = memtx_tree_index_gc_free<USE_HINT>,
>     > >+ };
>     > >+ return &tab;
>     > > };
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > memtx_tree_index_destroy(struct index *base)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
>     > >  if (base->def->iid == 0) {
>     > >  /*
>     > >@@ -518,7 +634,7 @@ memtx_tree_index_destroy(struct index *base)
>     > >  * in the index, which may take a while. Schedule a
>     > >  * background task in order not to block tx thread.
>     > >  */
>     > >- index->gc_task.vtab = &memtx_tree_index_gc_vtab;
>     > >+ index->gc_task.vtab = get_memtx_tree_index_gc_vtab<USE_HINT>();
>     > >  index->gc_iterator = memtx_tree_iterator_first(&index->tree);
>     > >  memtx_engine_schedule_gc(memtx, &index->gc_task);
>     > >  } else {
>     > >@@ -530,10 +646,12 @@ memtx_tree_index_destroy(struct index *base)
>     > >  }
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > memtx_tree_index_update_def(struct index *base)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  struct index_def *def = base->def;
>     > >  /*
>     > >  * We use extended key def for non-unique and nullable
>     > >@@ -553,51 +671,62 @@ memtx_tree_index_depends_on_pk(struct
>     index *base)
>     > >  return !def->opts.is_unique || def->key_def->is_nullable;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static ssize_t
>     > > memtx_tree_index_size(struct index *base)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  return memtx_tree_size(&index->tree);
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static ssize_t
>     > > memtx_tree_index_bsize(struct index *base)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  return memtx_tree_mem_used(&index->tree);
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > memtx_tree_index_random(struct index *base, uint32_t rnd,
>     struct tuple **result)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >- struct memtx_tree_data *res = memtx_tree_random(&index->tree,
>     rnd);
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     memtx_tree_random(&index->tree, rnd);
>     > >  *result = res != NULL ? res->tuple : NULL;
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static ssize_t
>     > > memtx_tree_index_count(struct index *base, enum iterator_type
>     type,
>     > >  const char *key, uint32_t part_count)
>     > > {
>     > >  if (type == ITER_ALL)
>     > >- return memtx_tree_index_size(base); /* optimization */
>     > >+ return memtx_tree_index_size<USE_HINT>(base); /* optimization */
>     > >  return generic_index_count(base, type, key, part_count);
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > memtx_tree_index_get(struct index *base, const char *key,
>     > >  uint32_t part_count, struct tuple **result)
>     > > {
>     > >  assert(base->def->opts.is_unique &&
>     > >  part_count == base->def->key_def->part_count);
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >- struct memtx_tree_key_data key_data;
>     > >+ struct memtx_tree_key_data<USE_HINT> key_data;
>     > >  key_data.key = key;
>     > >  key_data.part_count = part_count;
>     > >- key_data.hint = key_hint(key, part_count, cmp_def);
>     > >- struct memtx_tree_data *res = memtx_tree_find(&index->tree,
>     &key_data);
>     > >+ if (USE_HINT)
>     > >+ key_data.set_hint(key_hint(key, part_count, cmp_def));
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > >+ memtx_tree_find(&index->tree, &key_data);
>     > >  if (res == NULL) {
>     > >  *result = NULL;
>     > >  return 0;
>     > >@@ -611,18 +740,21 @@ memtx_tree_index_get(struct index *base,
>     const char *key,
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > memtx_tree_index_replace(struct index *base, struct tuple
>     *old_tuple,
>     > >  struct tuple *new_tuple, enum dup_replace_mode mode,
>     > >  struct tuple **result)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >  if (new_tuple) {
>     > >- struct memtx_tree_data new_data;
>     > >+ struct memtx_tree_data<USE_HINT> new_data;
>     > >  new_data.tuple = new_tuple;
>     > >- new_data.hint = tuple_hint(new_tuple, cmp_def);
>     > >- struct memtx_tree_data dup_data;
>     > >+ if (USE_HINT)
>     > >+ new_data.set_hint(tuple_hint(new_tuple, cmp_def));
>     > >+ struct memtx_tree_data<USE_HINT> dup_data;
>     > >  dup_data.tuple = NULL;
>     > >
>     > >  /* Try to optimistically replace the new_tuple. */
>     > >@@ -652,9 +784,10 @@ memtx_tree_index_replace(struct index
>     *base, struct tuple *old_tuple,
>     > >  }
>     > >  }
>     > >  if (old_tuple) {
>     > >- struct memtx_tree_data old_data;
>     > >+ struct memtx_tree_data<USE_HINT> old_data;
>     > >  old_data.tuple = old_tuple;
>     > >- old_data.hint = tuple_hint(old_tuple, cmp_def);
>     > >+ if (USE_HINT)
>     > >+ old_data.set_hint(tuple_hint(old_tuple, cmp_def));
>     > >  memtx_tree_delete(&index->tree, old_data);
>     > >  }
>     > >  *result = old_tuple;
>     > >@@ -667,13 +800,13 @@ memtx_tree_index_replace(struct index
>     *base, struct tuple *old_tuple,
>     > >  * by all it's multikey indexes.
>     > >  */
>     > > static int
>     > >-memtx_tree_index_replace_multikey_one(struct memtx_tree_index
>     *index,
>     > >+memtx_tree_index_replace_multikey_one(struct
>     memtx_tree_index<1> *index,
>     > >  struct tuple *old_tuple, struct tuple *new_tuple,
>     > >  enum dup_replace_mode mode, hint_t hint,
>     > >- struct memtx_tree_data *replaced_data,
>     > >+ struct memtx_tree_data<1> *replaced_data,
>     > >  bool *is_multikey_conflict)
>     > > {
>     > >- struct memtx_tree_data new_data, dup_data;
>     > >+ struct memtx_tree_data<1> new_data, dup_data;
>     > >  new_data.tuple = new_tuple;
>     > >  new_data.hint = hint;
>     > >  dup_data.tuple = NULL;
>     > >@@ -720,11 +853,11 @@
>     memtx_tree_index_replace_multikey_one(struct memtx_tree_index *index,
>     > >  * delete operation is fault-tolerant.
>     > >  */
>     > > static void
>     > >-memtx_tree_index_replace_multikey_rollback(struct
>     memtx_tree_index *index,
>     > >+memtx_tree_index_replace_multikey_rollback(struct
>     memtx_tree_index<1> *index,
>     > >  struct tuple *new_tuple, struct tuple *replaced_tuple,
>     > >  int err_multikey_idx)
>     > > {
>     > >- struct memtx_tree_data data;
>     > >+ struct memtx_tree_data<1> data;
>     > >  if (replaced_tuple != NULL) {
>     > >  /* Restore replaced tuple index occurrences. */
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >@@ -798,7 +931,7 @@ memtx_tree_index_replace_multikey(struct
>     index *base, struct tuple *old_tuple,
>     > >  struct tuple *new_tuple, enum dup_replace_mode mode,
>     > >  struct tuple **result)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<1> *index = (struct
>     memtx_tree_index<1> *)base;
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >  *result = NULL;
>     > >  if (new_tuple != NULL) {
>     > >@@ -808,7 +941,7 @@ memtx_tree_index_replace_multikey(struct
>     index *base, struct tuple *old_tuple,
>     > >  for (; (uint32_t) multikey_idx < multikey_count;
>     > >  multikey_idx++) {
>     > >  bool is_multikey_conflict;
>     > >- struct memtx_tree_data replaced_data;
>     > >+ struct memtx_tree_data<1> replaced_data;
>     > >  err = memtx_tree_index_replace_multikey_one(index,
>     > >  old_tuple, new_tuple, mode,
>     > >  multikey_idx, &replaced_data,
>     > >@@ -833,7 +966,7 @@ memtx_tree_index_replace_multikey(struct
>     index *base, struct tuple *old_tuple,
>     > >  }
>     > >  }
>     > >  if (old_tuple != NULL) {
>     > >- struct memtx_tree_data data;
>     > >+ struct memtx_tree_data<1> data;
>     > >  data.tuple = old_tuple;
>     > >  uint32_t multikey_count =
>     > >  tuple_multikey_count(old_tuple, cmp_def);
>     > >@@ -865,7 +998,7 @@ struct func_key_undo {
>     > >  /** A link to organize entries in list. */
>     > >  struct rlist link;
>     > >  /** An inserted record copy. */
>     > >- struct memtx_tree_data key;
>     > >+ struct memtx_tree_data<1> key;
>     > > };
>     > >
>     > > /** Allocate a new func_key_undo on given region. */
>     > >@@ -888,7 +1021,7 @@ func_key_undo_new(struct region *region)
>     > >  * return a given index object in it's original state.
>     > >  */
>     > > static void
>     > >-memtx_tree_func_index_replace_rollback(struct memtx_tree_index
>     *index,
>     > >+memtx_tree_func_index_replace_rollback(struct
>     memtx_tree_index<1> *index,
>     > >  struct rlist *old_keys,
>     > >  struct rlist *new_keys)
>     > > {
>     > >@@ -919,7 +1052,7 @@ memtx_tree_func_index_replace(struct index
>     *base, struct tuple *old_tuple,
>     > >  struct tuple *new_tuple, enum dup_replace_mode mode,
>     > >  struct tuple **result)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<1> *index = (struct
>     memtx_tree_index<1> *)base;
>     > >  struct index_def *index_def = index->base.def;
>     > > assert(index_def->key_def->for_func_index);
>     > >
>     > >@@ -952,7 +1085,7 @@ memtx_tree_func_index_replace(struct index
>     *base, struct tuple *old_tuple,
>     > >  undo->key.hint = (hint_t)key;
>     > >  rlist_add(&new_keys, &undo->link);
>     > >  bool is_multikey_conflict;
>     > >- struct memtx_tree_data old_data;
>     > >+ struct memtx_tree_data<1> old_data;
>     > >  old_data.tuple = NULL;
>     > >  err = memtx_tree_index_replace_multikey_one(index,
>     > >  old_tuple, new_tuple,
>     > >@@ -1015,7 +1148,7 @@ memtx_tree_func_index_replace(struct
>     index *base, struct tuple *old_tuple,
>     > >  if (key_list_iterator_create(&it, old_tuple, index_def, false,
>     > >  func_index_key_dummy_alloc) != 0)
>     > >  goto end;
>     > >- struct memtx_tree_data data, deleted_data;
>     > >+ struct memtx_tree_data<1> data, deleted_data;
>     > >  data.tuple = old_tuple;
>     > >  const char *key;
>     > >  while (key_list_iterator_next(&it, &key) == 0 && key != NULL) {
>     > >@@ -1040,11 +1173,13 @@ end:
>     > >  return rc;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static struct iterator *
>     > > memtx_tree_index_create_iterator(struct index *base, enum
>     iterator_type type,
>     > >  const char *key, uint32_t part_count)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  struct memtx_engine *memtx = (struct memtx_engine *)base->engine;
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >
>     > >@@ -1064,42 +1199,47 @@ memtx_tree_index_create_iterator(struct
>     index *base, enum iterator_type type,
>     > >  key = NULL;
>     > >  }
>     > >
>     > >- struct tree_iterator *it = (struct tree_iterator *)
>     > >+ struct tree_iterator<USE_HINT> *it = (struct
>     tree_iterator<USE_HINT> *)
>     > > mempool_alloc(&memtx->iterator_pool);
>     > >  if (it == NULL) {
>     > >- diag_set(OutOfMemory, sizeof(struct tree_iterator),
>     > >+ diag_set(OutOfMemory, sizeof(struct tree_iterator<USE_HINT>),
>     > >  "memtx_tree_index", "iterator");
>     > >  return NULL;
>     > >  }
>     > >  iterator_create(&it->base, base);
>     > >  it->pool = &memtx->iterator_pool;
>     > >- it->base.next = tree_iterator_start;
>     > >- it->base.free = tree_iterator_free;
>     > >+ it->base.next = tree_iterator_start<USE_HINT>;
>     > >+ it->base.free = tree_iterator_free<USE_HINT>;
>     > >  it->type = type;
>     > >  it->key_data.key = key;
>     > >  it->key_data.part_count = part_count;
>     > >- it->key_data.hint = key_hint(key, part_count, cmp_def);
>     > >- it->tree_iterator = memtx_tree_invalid_iterator();
>     > >+ if (USE_HINT)
>     > >+ it->key_data.set_hint(key_hint(key, part_count, cmp_def));
>     > >+ invalidate_tree_iterator(&it->tree_iterator);
>     > >  it->current.tuple = NULL;
>     > >  return (struct iterator *)it;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > memtx_tree_index_begin_build(struct index *base)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  assert(memtx_tree_size(&index->tree) == 0);
>     > >  (void)index;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > memtx_tree_index_reserve(struct index *base, uint32_t size_hint)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  if (size_hint < index->build_array_alloc_size)
>     > >  return 0;
>     > >- struct memtx_tree_data *tmp =
>     > >- (struct memtx_tree_data *)
>     > >+ struct memtx_tree_data<USE_HINT> *tmp =
>     > >+ (struct memtx_tree_data<USE_HINT> *)
>     > >  realloc(index->build_array, size_hint * sizeof(*tmp));
>     > >  if (tmp == NULL) {
>     > >  diag_set(OutOfMemory, size_hint * sizeof(*tmp),
>     > >@@ -1111,14 +1251,15 @@ memtx_tree_index_reserve(struct index
>     *base, uint32_t size_hint)
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > /** Initialize the next element of the index build_array. */
>     > > static int
>     > >-memtx_tree_index_build_array_append(struct memtx_tree_index
>     *index,
>     > >+memtx_tree_index_build_array_append(struct
>     memtx_tree_index<USE_HINT> *index,
>     > >  struct tuple *tuple, hint_t hint)
>     > > {
>     > >  if (index->build_array == NULL) {
>     > >  index->build_array =
>     > >- (struct memtx_tree_data *)malloc(MEMTX_EXTENT_SIZE);
>     > >+ (struct memtx_tree_data<USE_HINT> *)malloc(MEMTX_EXTENT_SIZE);
>     > >  if (index->build_array == NULL) {
>     > >  diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
>     > >  "memtx_tree_index", "build_next");
>     > >@@ -1131,8 +1272,8 @@
>     memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>     > >  if (index->build_array_size == index->build_array_alloc_size) {
>     > >  index->build_array_alloc_size = index->build_array_alloc_size +
>     > > DIV_ROUND_UP(index->build_array_alloc_size, 2);
>     > >- struct memtx_tree_data *tmp =
>     > >- (struct memtx_tree_data *)realloc(index->build_array,
>     > >+ struct memtx_tree_data<USE_HINT> *tmp =
>     > >+ (struct memtx_tree_data<USE_HINT> *)realloc(index->build_array,
>     > >  index->build_array_alloc_size * sizeof(*tmp));
>     > >  if (tmp == NULL) {
>     > >  diag_set(OutOfMemory, index->build_array_alloc_size *
>     > >@@ -1141,17 +1282,20 @@
>     memtx_tree_index_build_array_append(struct memtx_tree_index *index,
>     > >  }
>     > >  index->build_array = tmp;
>     > >  }
>     > >- struct memtx_tree_data *elem =
>     > >+ struct memtx_tree_data<USE_HINT> *elem =
>     > > &index->build_array[index->build_array_size++];
>     > >  elem->tuple = tuple;
>     > >- elem->hint = hint;
>     > >+ if (USE_HINT)
>     > >+ elem->set_hint(hint);
>     > >  return 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > memtx_tree_index_build_next(struct index *base, struct tuple
>     *tuple)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >  return memtx_tree_index_build_array_append(index, tuple,
>     > >  tuple_hint(tuple, cmp_def));
>     > >@@ -1160,7 +1304,7 @@ memtx_tree_index_build_next(struct index
>     *base, struct tuple *tuple)
>     > > static int
>     > > memtx_tree_index_build_next_multikey(struct index *base,
>     struct tuple *tuple)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<1> *index = (struct
>     memtx_tree_index<1> *)base;
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >  uint32_t multikey_count = tuple_multikey_count(tuple, cmp_def);
>     > >  for (uint32_t multikey_idx = 0; multikey_idx < multikey_count;
>     > >@@ -1175,7 +1319,7 @@
>     memtx_tree_index_build_next_multikey(struct index *base, struct
>     tuple *tuple)
>     > > static int
>     > > memtx_tree_func_index_build_next(struct index *base, struct
>     tuple *tuple)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<1> *index = (struct
>     memtx_tree_index<1> *)base;
>     > >  struct index_def *index_def = index->base.def;
>     > > assert(index_def->key_def->for_func_index);
>     > >
>     > >@@ -1211,8 +1355,9 @@ error:
>     > >  * of equal tuples (in terms of index's cmp_def and have same
>     > >  * tuple pointer). The build_array is expected to be sorted.
>     > >  */
>     > >+template <int USE_HINT>
>     > > static void
>     > >-memtx_tree_index_build_array_deduplicate(struct
>     memtx_tree_index *index,
>     > >+memtx_tree_index_build_array_deduplicate(struct
>     memtx_tree_index<USE_HINT> *index,
>     > >  void (*destroy)(struct tuple *tuple, const char *hint))
>     > > {
>     > >  if (index->build_array_size == 0)
>     > >@@ -1246,13 +1391,16 @@
>     memtx_tree_index_build_array_deduplicate(struct memtx_tree_index
>     *index,
>     > >  index->build_array_size = w_idx + 1;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > memtx_tree_index_end_build(struct index *base)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >  struct key_def *cmp_def = memtx_tree_cmp_def(&index->tree);
>     > >  qsort_arg(index->build_array, index->build_array_size,
>     > >- sizeof(index->build_array[0]), memtx_tree_qcompare, cmp_def);
>     > >+ sizeof(index->build_array[0]),
>     > >+ memtx_tree_qcompare<USE_HINT>, cmp_def);
>     > >  if (cmp_def->is_multikey) {
>     > >  /*
>     > >  * Multikey index may have equal(in terms of
>     > >@@ -1261,9 +1409,9 @@ memtx_tree_index_end_build(struct index
>     *base)
>     > >  * the following memtx_tree_build assumes that
>     > >  * all keys are unique.
>     > >  */
>     > >- memtx_tree_index_build_array_deduplicate(index, NULL);
>     > >+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index, NULL);
>     > >  } else if (cmp_def->for_func_index) {
>     > >- memtx_tree_index_build_array_deduplicate(index,
>     > >+ memtx_tree_index_build_array_deduplicate<USE_HINT>(index,
>     > >  tuple_chunk_delete);
>     > >  }
>     > >  memtx_tree_build(&index->tree, index->build_array,
>     > >@@ -1275,19 +1423,21 @@ memtx_tree_index_end_build(struct index
>     *base)
>     > >  index->build_array_alloc_size = 0;
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > struct tree_snapshot_iterator {
>     > >  struct snapshot_iterator base;
>     > >- struct memtx_tree_index *index;
>     > >- struct memtx_tree_iterator tree_iterator;
>     > >+ struct memtx_tree_index<USE_HINT> *index;
>     > >+ memtx_tree_iterator_t<USE_HINT> tree_iterator;
>     > >  struct memtx_tx_snapshot_cleaner cleaner;
>     > > };
>     > >
>     > >+template <int USE_HINT>
>     > > static void
>     > > tree_snapshot_iterator_free(struct snapshot_iterator *iterator)
>     > > {
>     > >- assert(iterator->free == tree_snapshot_iterator_free);
>     > >- struct tree_snapshot_iterator *it =
>     > >- (struct tree_snapshot_iterator *)iterator;
>     > >+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
>     > >+ struct tree_snapshot_iterator<USE_HINT> *it =
>     > >+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
>     > >  memtx_leave_delayed_free_mode((struct memtx_engine *)
>     > >  it->index->base.engine);
>     > > memtx_tree_iterator_destroy(&it->index->tree, &it->tree_iterator);
>     > >@@ -1296,17 +1446,18 @@ tree_snapshot_iterator_free(struct
>     snapshot_iterator *iterator)
>     > >  free(iterator);
>     > > }
>     > >
>     > >+template <int USE_HINT>
>     > > static int
>     > > tree_snapshot_iterator_next(struct snapshot_iterator *iterator,
>     > >  const char **data, uint32_t *size)
>     > > {
>     > >- assert(iterator->free == tree_snapshot_iterator_free);
>     > >- struct tree_snapshot_iterator *it =
>     > >- (struct tree_snapshot_iterator *)iterator;
>     > >- struct memtx_tree *tree = &it->index->tree;
>     > >+ assert(iterator->free == &tree_snapshot_iterator_free<USE_HINT>);
>     > >+ struct tree_snapshot_iterator<USE_HINT> *it =
>     > >+ (struct tree_snapshot_iterator<USE_HINT> *)iterator;
>     > >+ memtx_tree_t<USE_HINT> *tree = &it->index->tree;
>     > >
>     > >  while (true) {
>     > >- struct memtx_tree_data *res =
>     > >+ struct memtx_tree_data<USE_HINT> *res =
>     > >  memtx_tree_iterator_get_elem(tree, &it->tree_iterator);
>     > >
>     > >  if (res == NULL) {
>     > >@@ -1333,14 +1484,18 @@ tree_snapshot_iterator_next(struct
>     snapshot_iterator *iterator,
>     > >  * index modifications will not affect the iteration results.
>     > >  * Must be destroyed by iterator->free after usage.
>     > >  */
>     > >+template <int USE_HINT>
>     > > static struct snapshot_iterator *
>     > > memtx_tree_index_create_snapshot_iterator(struct index *base)
>     > > {
>     > >- struct memtx_tree_index *index = (struct memtx_tree_index *)base;
>     > >- struct tree_snapshot_iterator *it =
>     > >- (struct tree_snapshot_iterator *) calloc(1, sizeof(*it));
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)base;
>     > >+ struct tree_snapshot_iterator<USE_HINT> *it =
>     > >+ (struct tree_snapshot_iterator<USE_HINT> *)
>     > >+ calloc(1, sizeof(*it));
>     > >  if (it == NULL) {
>     > >- diag_set(OutOfMemory, sizeof(struct tree_snapshot_iterator),
>     > >+ diag_set(OutOfMemory,
>     > >+ sizeof(struct tree_snapshot_iterator<USE_HINT>),
>     > >  "memtx_tree_index", "create_snapshot_iterator");
>     > >  return NULL;
>     > >  }
>     > >@@ -1352,8 +1507,8 @@
>     memtx_tree_index_create_snapshot_iterator(struct index *base)
>     > >  return NULL;
>     > >  }
>     > >
>     > >- it->base.free = tree_snapshot_iterator_free;
>     > >- it->base.next = tree_snapshot_iterator_next;
>     > >+ it->base.free = tree_snapshot_iterator_free<USE_HINT>;
>     > >+ it->base.next = tree_snapshot_iterator_next<USE_HINT>;
>     > >  it->index = index;
>     > >  index_ref(base);
>     > >  it->tree_iterator = memtx_tree_iterator_first(&index->tree);
>     > >@@ -1362,94 +1517,124 @@
>     memtx_tree_index_create_snapshot_iterator(struct index *base)
>     > >  return (struct snapshot_iterator *) it;
>     > > }
>     > >
>     > >-static const struct index_vtab memtx_tree_index_vtab = {
>     > >- /* .destroy = */ memtx_tree_index_destroy,
>     > >+static const struct index_vtab memtx_tree_no_hint_index_vtab = {
>     > >+ /* .destroy = */ memtx_tree_index_destroy<0>,
>     > >  /* .commit_create = */ generic_index_commit_create,
>     > >  /* .abort_create = */ generic_index_abort_create,
>     > >  /* .commit_modify = */ generic_index_commit_modify,
>     > >  /* .commit_drop = */ generic_index_commit_drop,
>     > >- /* .update_def = */ memtx_tree_index_update_def,
>     > >+ /* .update_def = */ memtx_tree_index_update_def<0>,
>     > >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>     > >  /* .def_change_requires_rebuild = */
>     > >  memtx_index_def_change_requires_rebuild,
>     > >- /* .size = */ memtx_tree_index_size,
>     > >- /* .bsize = */ memtx_tree_index_bsize,
>     > >+ /* .size = */ memtx_tree_index_size<0>,
>     > >+ /* .bsize = */ memtx_tree_index_bsize<0>,
>     > >  /* .min = */ generic_index_min,
>     > >  /* .max = */ generic_index_max,
>     > >- /* .random = */ memtx_tree_index_random,
>     > >- /* .count = */ memtx_tree_index_count,
>     > >- /* .get = */ memtx_tree_index_get,
>     > >- /* .replace = */ memtx_tree_index_replace,
>     > >- /* .create_iterator = */ memtx_tree_index_create_iterator,
>     > >+ /* .random = */ memtx_tree_index_random<0>,
>     > >+ /* .count = */ memtx_tree_index_count<0>,
>     > >+ /* .get = */ memtx_tree_index_get<0>,
>     > >+ /* .replace = */ memtx_tree_index_replace<0>,
>     > >+ /* .create_iterator = */ memtx_tree_index_create_iterator<0>,
>     > >  /* .create_snapshot_iterator = */
>     > >- memtx_tree_index_create_snapshot_iterator,
>     > >+ memtx_tree_index_create_snapshot_iterator<0>,
>     > >  /* .stat = */ generic_index_stat,
>     > >  /* .compact = */ generic_index_compact,
>     > >  /* .reset_stat = */ generic_index_reset_stat,
>     > >- /* .begin_build = */ memtx_tree_index_begin_build,
>     > >- /* .reserve = */ memtx_tree_index_reserve,
>     > >- /* .build_next = */ memtx_tree_index_build_next,
>     > >- /* .end_build = */ memtx_tree_index_end_build,
>     > >+ /* .begin_build = */ memtx_tree_index_begin_build<0>,
>     > >+ /* .reserve = */ memtx_tree_index_reserve<0>,
>     > >+ /* .build_next = */ memtx_tree_index_build_next<0>,
>     > >+ /* .end_build = */ memtx_tree_index_end_build<0>,
>     > >+};
>     > >+
>     > >+static const struct index_vtab memtx_tree_use_hint_index_vtab = {
>     > >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>     > >+ /* .commit_create = */ generic_index_commit_create,
>     > >+ /* .abort_create = */ generic_index_abort_create,
>     > >+ /* .commit_modify = */ generic_index_commit_modify,
>     > >+ /* .commit_drop = */ generic_index_commit_drop,
>     > >+ /* .update_def = */ memtx_tree_index_update_def<1>,
>     > >+ /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>     > >+ /* .def_change_requires_rebuild = */
>     > >+ memtx_index_def_change_requires_rebuild,
>     > >+ /* .size = */ memtx_tree_index_size<1>,
>     > >+ /* .bsize = */ memtx_tree_index_bsize<1>,
>     > >+ /* .min = */ generic_index_min,
>     > >+ /* .max = */ generic_index_max,
>     > >+ /* .random = */ memtx_tree_index_random<1>,
>     > >+ /* .count = */ memtx_tree_index_count<1>,
>     > >+ /* .get = */ memtx_tree_index_get<1>,
>     > >+ /* .replace = */ memtx_tree_index_replace<1>,
>     > >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>     > >+ /* .create_snapshot_iterator = */
>     > >+ memtx_tree_index_create_snapshot_iterator<1>,
>     > >+ /* .stat = */ generic_index_stat,
>     > >+ /* .compact = */ generic_index_compact,
>     > >+ /* .reset_stat = */ generic_index_reset_stat,
>     > >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>     > >+ /* .reserve = */ memtx_tree_index_reserve<1>,
>     > >+ /* .build_next = */ memtx_tree_index_build_next<1>,
>     > >+ /* .end_build = */ memtx_tree_index_end_build<1>,
>     > > };
>     > >
>     > > static const struct index_vtab memtx_tree_index_multikey_vtab = {
>     > >- /* .destroy = */ memtx_tree_index_destroy,
>     > >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>     > >  /* .commit_create = */ generic_index_commit_create,
>     > >  /* .abort_create = */ generic_index_abort_create,
>     > >  /* .commit_modify = */ generic_index_commit_modify,
>     > >  /* .commit_drop = */ generic_index_commit_drop,
>     > >- /* .update_def = */ memtx_tree_index_update_def,
>     > >+ /* .update_def = */ memtx_tree_index_update_def<1>,
>     > >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>     > >  /* .def_change_requires_rebuild = */
>     > >  memtx_index_def_change_requires_rebuild,
>     > >- /* .size = */ memtx_tree_index_size,
>     > >- /* .bsize = */ memtx_tree_index_bsize,
>     > >+ /* .size = */ memtx_tree_index_size<1>,
>     > >+ /* .bsize = */ memtx_tree_index_bsize<1>,
>     > >  /* .min = */ generic_index_min,
>     > >  /* .max = */ generic_index_max,
>     > >- /* .random = */ memtx_tree_index_random,
>     > >- /* .count = */ memtx_tree_index_count,
>     > >- /* .get = */ memtx_tree_index_get,
>     > >+ /* .random = */ memtx_tree_index_random<1>,
>     > >+ /* .count = */ memtx_tree_index_count<1>,
>     > >+ /* .get = */ memtx_tree_index_get<1>,
>     > >  /* .replace = */ memtx_tree_index_replace_multikey,
>     > >- /* .create_iterator = */ memtx_tree_index_create_iterator,
>     > >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>     > >  /* .create_snapshot_iterator = */
>     > >- memtx_tree_index_create_snapshot_iterator,
>     > >+ memtx_tree_index_create_snapshot_iterator<1>,
>     > >  /* .stat = */ generic_index_stat,
>     > >  /* .compact = */ generic_index_compact,
>     > >  /* .reset_stat = */ generic_index_reset_stat,
>     > >- /* .begin_build = */ memtx_tree_index_begin_build,
>     > >- /* .reserve = */ memtx_tree_index_reserve,
>     > >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>     > >+ /* .reserve = */ memtx_tree_index_reserve<1>,
>     > >  /* .build_next = */ memtx_tree_index_build_next_multikey,
>     > >- /* .end_build = */ memtx_tree_index_end_build,
>     > >+ /* .end_build = */ memtx_tree_index_end_build<1>,
>     > > };
>     > >
>     > > static const struct index_vtab memtx_tree_func_index_vtab = {
>     > >- /* .destroy = */ memtx_tree_index_destroy,
>     > >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>     > >  /* .commit_create = */ generic_index_commit_create,
>     > >  /* .abort_create = */ generic_index_abort_create,
>     > >  /* .commit_modify = */ generic_index_commit_modify,
>     > >  /* .commit_drop = */ generic_index_commit_drop,
>     > >- /* .update_def = */ memtx_tree_index_update_def,
>     > >+ /* .update_def = */ memtx_tree_index_update_def<1>,
>     > >  /* .depends_on_pk = */ memtx_tree_index_depends_on_pk,
>     > >  /* .def_change_requires_rebuild = */
>     > >  memtx_index_def_change_requires_rebuild,
>     > >- /* .size = */ memtx_tree_index_size,
>     > >- /* .bsize = */ memtx_tree_index_bsize,
>     > >+ /* .size = */ memtx_tree_index_size<1>,
>     > >+ /* .bsize = */ memtx_tree_index_bsize<1>,
>     > >  /* .min = */ generic_index_min,
>     > >  /* .max = */ generic_index_max,
>     > >- /* .random = */ memtx_tree_index_random,
>     > >- /* .count = */ memtx_tree_index_count,
>     > >- /* .get = */ memtx_tree_index_get,
>     > >+ /* .random = */ memtx_tree_index_random<1>,
>     > >+ /* .count = */ memtx_tree_index_count<1>,
>     > >+ /* .get = */ memtx_tree_index_get<1>,
>     > >  /* .replace = */ memtx_tree_func_index_replace,
>     > >- /* .create_iterator = */ memtx_tree_index_create_iterator,
>     > >+ /* .create_iterator = */ memtx_tree_index_create_iterator<1>,
>     > >  /* .create_snapshot_iterator = */
>     > >- memtx_tree_index_create_snapshot_iterator,
>     > >+ memtx_tree_index_create_snapshot_iterator<1>,
>     > >  /* .stat = */ generic_index_stat,
>     > >  /* .compact = */ generic_index_compact,
>     > >  /* .reset_stat = */ generic_index_reset_stat,
>     > >- /* .begin_build = */ memtx_tree_index_begin_build,
>     > >- /* .reserve = */ memtx_tree_index_reserve,
>     > >+ /* .begin_build = */ memtx_tree_index_begin_build<1>,
>     > >+ /* .reserve = */ memtx_tree_index_reserve<1>,
>     > >  /* .build_next = */ memtx_tree_func_index_build_next,
>     > >- /* .end_build = */ memtx_tree_index_end_build,
>     > >+ /* .end_build = */ memtx_tree_index_end_build<1>,
>     > > };
>     > >
>     > > /**
>     > >@@ -1459,7 +1644,7 @@ static const struct index_vtab
>     memtx_tree_func_index_vtab = {
>     > >  * key defintion is not completely initialized at that moment).
>     > >  */
>     > > static const struct index_vtab memtx_tree_disabled_index_vtab = {
>     > >- /* .destroy = */ memtx_tree_index_destroy,
>     > >+ /* .destroy = */ memtx_tree_index_destroy<1>,
>     > >  /* .commit_create = */ generic_index_commit_create,
>     > >  /* .abort_create = */ generic_index_abort_create,
>     > >  /* .commit_modify = */ generic_index_commit_modify,
>     > >@@ -1488,27 +1673,19 @@ static const struct index_vtab
>     memtx_tree_disabled_index_vtab = {
>     > >  /* .end_build = */ generic_index_end_build,
>     > > };
>     > >
>     > >-struct index *
>     > >-memtx_tree_index_new(struct memtx_engine *memtx, struct
>     index_def *def)
>     > >+template <int USE_HINT>
>     > >+static struct index *
>     > >+memtx_tree_index_new_tpl(struct memtx_engine *memtx, struct
>     index_def *def,
>     > >+ const struct index_vtab *vtab)
>     > > {
>     > >- struct memtx_tree_index *index =
>     > >- (struct memtx_tree_index *)calloc(1, sizeof(*index));
>     > >+ struct memtx_tree_index<USE_HINT> *index =
>     > >+ (struct memtx_tree_index<USE_HINT> *)
>     > >+ calloc(1, sizeof(*index));
>     > >  if (index == NULL) {
>     > >  diag_set(OutOfMemory, sizeof(*index),
>     > >  "malloc", "struct memtx_tree_index");
>     > >  return NULL;
>     > >  }
>     > >- const struct index_vtab *vtab;
>     > >- if (def->key_def->for_func_index) {
>     > >- if (def->key_def->func_index_func == NULL)
>     > >- vtab = &memtx_tree_disabled_index_vtab;
>     > >- else
>     > >- vtab = &memtx_tree_func_index_vtab;
>     > >- } else if (def->key_def->is_multikey) {
>     > >- vtab = &memtx_tree_index_multikey_vtab;
>     > >- } else {
>     > >- vtab = &memtx_tree_index_vtab;
>     > >- }
>     > >  if (index_create(&index->base, (struct engine *)memtx,
>     > >  vtab, def) != 0) {
>     > >  free(index);
>     > >@@ -1524,3 +1701,23 @@ memtx_tree_index_new(struct memtx_engine
>     *memtx, struct index_def *def)
>     > >  memtx_index_extent_free, memtx);
>     > >  return &index->base;
>     > > }
>     > >+
>     > >+struct index *
>     > >+memtx_tree_index_new(struct memtx_engine *memtx, struct
>     index_def *def)
>     > >+{
>     > >+ const struct index_vtab *vtab;
>     > >+ if (def->key_def->for_func_index) {
>     > >+ if (def->key_def->func_index_func == NULL)
>     > >+ vtab = &memtx_tree_disabled_index_vtab;
>     > >+ else
>     > >+ vtab = &memtx_tree_func_index_vtab;
>     > >+ } else if (def->key_def->is_multikey) {
>     > >+ vtab = &memtx_tree_index_multikey_vtab;
>     > >+ } else if (def->opts.hint) {
>     > >+ vtab = &memtx_tree_use_hint_index_vtab;
>     > >+ } else {
>     > >+ vtab = &memtx_tree_no_hint_index_vtab;
>     > >+ return memtx_tree_index_new_tpl<0>(memtx, def, vtab);
>     > >+ }
>     > >+ return memtx_tree_index_new_tpl<1>(memtx, def, vtab);
>     > >+}
>     > >diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
>     > >index ef5ae3d..0bb803a 100644
>     > >--- a/src/lib/salad/bps_tree.h
>     > >+++ b/src/lib/salad/bps_tree.h
>     > >@@ -168,6 +168,17 @@
>     > >
>     > > /* {{{ BPS-tree interface settings */
>     > > /**
>     > >+ * Optional namespace for structs and functions.
>     > >+ * Is set, struct and functions will have BPS_TREE_NAMESPACE::
>     prefix.
>     > >+ * For example one can #define BPS_TREE_NAMESPACE my along with
>     > >+ * #define BPS_TREE_NAME _test, and use then
>     > >+ * struct my::bps_tree_test my_tree;
>     > >+ * my::bps_tree_test_create(&my_tree, ...);
>     > >+ *
>     > >+ * #define BPS_TREE_NAMESPACE
>     > >+ */
>     > >+
>     > >+/**
>     > >  * Custom name for structs and functions.
>     > >  * Struct and functions will have bps_tree##BPS_TREE_NAME name
>     or prefix.
>     > >  * For example one can #define BPS_TREE_NAME _test, and use then
>     > >@@ -300,6 +311,10 @@
>     > >
>     > > /* }}} */
>     > >
>     > >+#ifdef BPS_TREE_NAMESPACE
>     > >+namespace BPS_TREE_NAMESPACE {
>     > >+#endif
>     > >+
>     > > /* {{{ BPS-tree internal settings */
>     > > typedef int16_t bps_tree_pos_t;
>     > > typedef uint32_t bps_tree_block_id_t;
>     > >@@ -6188,3 +6203,7 @@
>     bps_tree_debug_check_internal_functions(bool assertme)
>     > > #undef bps_tree_debug_check_insert_and_move_to_left_inner
>     > >
>     > > /* }}} */
>     > >+
>     > >+#ifdef BPS_TREE_NAMESPACE
>     > >+} /* namespace BPS_TREE_NAMESPACE { */
>     > >+#endif
>     > >diff --git a/test/box/alter.result b/test/box/alter.result
>     > >index 237c2d8..5b412a7 100644
>     > >--- a/test/box/alter.result
>     > >+++ b/test/box/alter.result
>     > >@@ -680,8 +680,9 @@ s.index.pk
>     > >   - type: unsigned
>     > >     is_nullable: false
>     > >     fieldno: 2
>     > >- type: TREE
>     > >+ hint: true
>     > >   id: 0
>     > >+ type: TREE
>     > >   space_id: 731
>     > >   name: pk
>     > > ...
>     > >@@ -710,9 +711,10 @@ s.index.pk
>     > >   - type: unsigned
>     > >     is_nullable: false
>     > >     fieldno: 1
>     > >- space_id: 731
>     > >+ hint: true
>     > >   id: 0
>     > >   type: TREE
>     > >+ space_id: 731
>     > >   name: pk
>     > > ...
>     > > s.index.secondary
>     > >@@ -722,8 +724,9 @@ s.index.secondary
>     > >   - type: unsigned
>     > >     is_nullable: false
>     > >     fieldno: 2
>     > >- type: TREE
>     > >+ hint: true
>     > >   id: 1
>     > >+ type: TREE
>     > >   space_id: 731
>     > >   name: secondary
>     > > ...
>     > >@@ -1559,3 +1562,97 @@ assert(err:match('does not exist') ~= nil)
>     > > ---
>     > > - true
>     > > ...
>     > >+-- hint
>     > >+s = box.schema.create_space('test');
>     > >+---
>     > >+...
>     > >+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+s:create_index('test2', {type='tree', parts={{2, 'uint'}},
>     hint = true}).hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+s:create_index('test3', {type='tree', parts={{3, 'uint'}},
>     hint = false}).hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s:create_index('test4', {type='tree', parts={{4, 'string'}},
>     hint = false}).hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s.index.test1.hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test2.hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test3.hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s.index.test4.hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' ..
>     i} end box.commit()
>     > >+---
>     > >+...
>     > >+s.index.test1:bsize() == s.index.test2:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test1:bsize() > s.index.test3:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test1:bsize() > s.index.test4:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test1:alter{hint=false}
>     > >+---
>     > >+...
>     > >+s.index.test2:alter{hint=true}
>     > >+---
>     > >+...
>     > >+s.index.test3:alter{name='test33', hint=false}
>     > >+---
>     > >+...
>     > >+s.index.test4:alter{hint=true}
>     > >+---
>     > >+...
>     > >+s.index.test1.hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s.index.test2.hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test33.hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s.index.test4.hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test1:bsize() < s.index.test2:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test1:bsize() == s.index.test33:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s.index.test1:bsize() < s.index.test4:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s:drop()
>     > >+---
>     > >+...
>     > >diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
>     > >index abd08e2..2114186 100644
>     > >--- a/test/box/alter.test.lua
>     > >+++ b/test/box/alter.test.lua
>     > >@@ -620,3 +620,37 @@ s:drop()
>     > > s:alter({})
>     > > ok, err = pcall(box.schema.space.alter, s.id, {})
>     > > assert(err:match('does not exist') ~= nil)
>     > >+
>     > >+-- hint
>     > >+s = box.schema.create_space('test');
>     > >+s:create_index('test1', {type='tree', parts={{1, 'uint'}}}).hint
>     > >+s:create_index('test2', {type='tree', parts={{2, 'uint'}},
>     hint = true}).hint
>     > >+s:create_index('test3', {type='tree', parts={{3, 'uint'}},
>     hint = false}).hint
>     > >+s:create_index('test4', {type='tree', parts={{4, 'string'}},
>     hint = false}).hint
>     > >+
>     > >+s.index.test1.hint
>     > >+s.index.test2.hint
>     > >+s.index.test3.hint
>     > >+s.index.test4.hint
>     > >+
>     > >+N = 1000 box.begin() for i = 1,N do s:replace{i, i, i, '' ..
>     i} end box.commit()
>     > >+
>     > >+s.index.test1:bsize() == s.index.test2:bsize()
>     > >+s.index.test1:bsize() > s.index.test3:bsize()
>     > >+s.index.test1:bsize() > s.index.test4:bsize()
>     > >+
>     > >+s.index.test1:alter{hint=false}
>     > >+s.index.test2:alter{hint=true}
>     > >+s.index.test3:alter{name='test33', hint=false}
>     > >+s.index.test4:alter{hint=true}
>     > >+
>     > >+s.index.test1.hint
>     > >+s.index.test2.hint
>     > >+s.index.test33.hint
>     > >+s.index.test4.hint
>     > >+
>     > >+s.index.test1:bsize() < s.index.test2:bsize()
>     > >+s.index.test1:bsize() == s.index.test33:bsize()
>     > >+s.index.test1:bsize() < s.index.test4:bsize()
>     > >+
>     > >+s:drop()
>     > >\ No newline at end of file
>     > >diff --git a/test/box/errinj.result b/test/box/errinj.result
>     > >index 613d22c..b8c2476 100644
>     > >--- a/test/box/errinj.result
>     > >+++ b/test/box/errinj.result
>     > >@@ -1774,9 +1774,10 @@ rtreespace:create_index('pk',
>     {if_not_exists = true})
>     > >   - type: unsigned
>     > >     is_nullable: false
>     > >     fieldno: 1
>     > >+ hint: true
>     > >   id: 0
>     > >- space_id: 512
>     > >   type: TREE
>     > >+ space_id: 512
>     > >   name: pk
>     > > ...
>     > > rtreespace:create_index('target', {type='rtree', dimension =
>     3, parts={2, 'array'},unique = false, if_not_exists = true,})
>     > >diff --git a/test/box/tree_pk.result b/test/box/tree_pk.result
>     > >index df3c78b..18cb607 100644
>     > >--- a/test/box/tree_pk.result
>     > >+++ b/test/box/tree_pk.result
>     > >@@ -852,3 +852,317 @@ box.internal.collation.drop('test')
>     > > box.internal.collation.drop('test-ci')
>     > > ---
>     > > ...
>     > >+-- hints
>     > >+s = box.schema.space.create('test')
>     > >+---
>     > >+...
>     > >+s:create_index('test', {type = 'tree', hint = 'true'} )
>     > >+---
>     > >+- error: Illegal parameters, options parameter 'hint' should
>     be of type boolean
>     > >+...
>     > >+s:create_index('test', {type = 'hash', hint = true} )
>     > >+---
>     > >+- error: 'Can''t create or modify index ''test'' in space
>     ''test'': hint is only reasonable
>     > >+ with memtx tree index'
>     > >+...
>     > >+s:create_index('test', {type = 'hash'}):alter({hint = true})
>     > >+---
>     > >+- error: 'Can''t create or modify index ''test'' in space
>     ''test'': hint is only reasonable
>     > >+ with memtx tree index'
>     > >+...
>     > >+s:create_index('multikey', {hint = true, parts = {{2, 'int',
>     path = '[*]'}}})
>     > >+---
>     > >+- error: 'Can''t create or modify index ''multikey'' in space
>     ''test'': multikey index
>     > >+ can''t use hints'
>     > >+...
>     > >+s:create_index('multikey', {parts = {{2, 'int', path =
>     '[*]'}}}):alter({hint = true})
>     > >+---
>     > >+- error: 'Can''t create or modify index ''multikey'' in space
>     ''test'': multikey index
>     > >+ can''t use hints'
>     > >+...
>     > >+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
>     > >+---
>     > >+...
>     > >+box.schema.func.create('s', {body = lua_code, is_deterministic
>     = true, is_sandboxed = true})
>     > >+---
>     > >+...
>     > >+s:create_index('func', {hint = true, func = box.func.s.id,
>     parts = {{1, 'unsigned'}}})
>     > >+---
>     > >+- error: 'Can''t create or modify index ''func'' in space
>     ''test'': functional index
>     > >+ can''t use hints'
>     > >+...
>     > >+s:drop()
>     > >+---
>     > >+...
>     > >+s = box.schema.space.create('test', {engine = 'vinyl'})
>     > >+---
>     > >+...
>     > >+s:create_index('test', {type = 'tree', hint = true} )
>     > >+---
>     > >+- error: 'Can''t create or modify index ''test'' in space
>     ''test'': hint is only reasonable
>     > >+ with memtx tree index'
>     > >+...
>     > >+s:drop()
>     > >+---
>     > >+...
>     > >+-- numeric hints
>     > >+s1 = box.schema.space.create('test1')
>     > >+---
>     > >+...
>     > >+s1:create_index('test', {type = 'tree', hint = false}).hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s2 = box.schema.space.create('test2')
>     > >+---
>     > >+...
>     > >+s2:create_index('test', {type = 'tree'}).hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+N = 2000
>     > >+---
>     > >+...
>     > >+box.begin() for i = 1,N do local v = math.random(10 * N)
>     s1:replace{v} s2:replace{v} end box.commit()
>     > >+---
>     > >+...
>     > >+s1:count() == s2:count()
>     > >+---
>     > >+- true
>     > >+...
>     > >+good = true r1 = s1:select{} r2 = s2:select{}
>     > >+---
>     > >+...
>     > >+if #r1 ~= #r2 then good = false end
>     > >+---
>     > >+...
>     > >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false
>     end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+r1 = nil r2 = nil
>     > >+---
>     > >+...
>     > >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else
>     return t2 ~= nil end end
>     > >+---
>     > >+...
>     > >+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good =
>     false end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:bsize() < s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:alter{hint=true}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s2.index[0]:alter{hint=false}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() > s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:alter{hint=false}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1:drop()
>     > >+---
>     > >+...
>     > >+s2:drop()
>     > >+---
>     > >+...
>     > >+-- string hints
>     > >+s1 = box.schema.space.create('test1')
>     > >+---
>     > >+...
>     > >+s1:create_index('test', {type = 'tree', parts = {1, 'str'},
>     hint = false}).hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s2 = box.schema.space.create('test2')
>     > >+---
>     > >+...
>     > >+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+N = 1000
>     > >+---
>     > >+...
>     > >+box.begin() for i = 1,N do local v = ''..math.random(10 * N)
>     s1:replace{v} s2:replace{v} end box.commit()
>     > >+---
>     > >+...
>     > >+s1:count() == s2:count()
>     > >+---
>     > >+- true
>     > >+...
>     > >+good = true r1 = s1:select{} r2 = s2:select{}
>     > >+---
>     > >+...
>     > >+if #r1 ~= #r2 then good = false end
>     > >+---
>     > >+...
>     > >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false
>     end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+r1 = nil r2 = nil
>     > >+---
>     > >+...
>     > >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else
>     return t2 ~= nil end end
>     > >+---
>     > >+...
>     > >+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v})
>     then good = false end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:bsize() < s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:alter{hint=true}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s2.index[0]:alter{hint=false}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() > s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:alter{hint=false}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1:drop()
>     > >+---
>     > >+...
>     > >+s2:drop()
>     > >+---
>     > >+...
>     > >+-- string with collation hints
>     > >+s1 = box.schema.space.create('test1')
>     > >+---
>     > >+...
>     > >+s1:create_index('test', {type = 'tree', parts = {{1, 'str',
>     collation = 'Unicode'}}, hint = false}).hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s2 = box.schema.space.create('test2')
>     > >+---
>     > >+...
>     > >+s2:create_index('test', {type = 'tree', parts = {{1, 'str',
>     collation = 'Unicode'}}}).hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+N = 1000
>     > >+---
>     > >+...
>     > >+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
>     > >+---
>     > >+...
>     > >+syms_size = #syms
>     > >+---
>     > >+...
>     > >+len = 20
>     > >+---
>     > >+...
>     > >+vals = {}
>     > >+---
>     > >+...
>     > >+for i = 1,2*N do str = '' for j=1,len do str = str ..
>     syms[math.random(syms_size)] end vals[i] = str end
>     > >+---
>     > >+...
>     > >+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
>     > >+---
>     > >+...
>     > >+s1:count() == s2:count()
>     > >+---
>     > >+- true
>     > >+...
>     > >+good = true r1 = s1:select{} r2 = s2:select{}
>     > >+---
>     > >+...
>     > >+if #r1 ~= #r2 then good = false end
>     > >+---
>     > >+...
>     > >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false
>     end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+r1 = nil r2 = nil
>     > >+---
>     > >+...
>     > >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else
>     return t2 ~= nil end end
>     > >+---
>     > >+...
>     > >+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v})
>     then good = false end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:bsize() < s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:alter{hint=true}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s2.index[0]:alter{hint=false}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() > s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1.index[0]:alter{hint=false}
>     > >+---
>     > >+...
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1:drop()
>     > >+---
>     > >+...
>     > >+s2:drop()
>     > >+---
>     > >+...
>     > >diff --git a/test/box/tree_pk.test.lua b/test/box/tree_pk.test.lua
>     > >index 1190ab4..2f22ed9 100644
>     > >--- a/test/box/tree_pk.test.lua
>     > >+++ b/test/box/tree_pk.test.lua
>     > >@@ -301,3 +301,118 @@ s:drop()
>     > >
>     > > box.internal.collation.drop('test')
>     > > box.internal.collation.drop('test-ci')
>     > >+
>     > >+-- hints
>     > >+s = box.schema.space.create('test')
>     > >+s:create_index('test', {type = 'tree', hint = 'true'} )
>     > >+s:create_index('test', {type = 'hash', hint = true} )
>     > >+s:create_index('test', {type = 'hash'}):alter({hint = true})
>     > >+s:create_index('multikey', {hint = true, parts = {{2, 'int',
>     path = '[*]'}}})
>     > >+s:create_index('multikey', {parts = {{2, 'int', path =
>     '[*]'}}}):alter({hint = true})
>     > >+lua_code = [[function(tuple) return {tuple[1] + tuple[2]} end]]
>     > >+box.schema.func.create('s', {body = lua_code, is_deterministic
>     = true, is_sandboxed = true})
>     > >+s:create_index('func', {hint = true, func = box.func.s.id,
>     parts = {{1, 'unsigned'}}})
>     > >+s:drop()
>     > >+
>     > >+s = box.schema.space.create('test', {engine = 'vinyl'})
>     > >+s:create_index('test', {type = 'tree', hint = true} )
>     > >+s:drop()
>     > >+
>     > >+-- numeric hints
>     > >+s1 = box.schema.space.create('test1')
>     > >+s1:create_index('test', {type = 'tree', hint = false}).hint
>     > >+s2 = box.schema.space.create('test2')
>     > >+s2:create_index('test', {type = 'tree'}).hint
>     > >+
>     > >+N = 2000
>     > >+box.begin() for i = 1,N do local v = math.random(10 * N)
>     s1:replace{v} s2:replace{v} end box.commit()
>     > >+s1:count() == s2:count()
>     > >+
>     > >+good = true r1 = s1:select{} r2 = s2:select{}
>     > >+if #r1 ~= #r2 then good = false end
>     > >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false
>     end end
>     > >+good
>     > >+r1 = nil r2 = nil
>     > >+
>     > >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else
>     return t2 ~= nil end end
>     > >+for i = 1, N * 10 do if diff(s1:get{i}, s2:get{i}) then good =
>     false end end
>     > >+good
>     > >+
>     > >+s1.index[0]:bsize() < s2.index[0]:bsize()
>     > >+s1.index[0]:alter{hint=true}
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+s2.index[0]:alter{hint=false}
>     > >+s1.index[0]:bsize() > s2.index[0]:bsize()
>     > >+s1.index[0]:alter{hint=false}
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+
>     > >+s1:drop()
>     > >+s2:drop()
>     > >+
>     > >+-- string hints
>     > >+s1 = box.schema.space.create('test1')
>     > >+s1:create_index('test', {type = 'tree', parts = {1, 'str'},
>     hint = false}).hint
>     > >+s2 = box.schema.space.create('test2')
>     > >+s2:create_index('test', {type = 'tree', parts = {1, 'str'}}).hint
>     > >+
>     > >+N = 1000
>     > >+box.begin() for i = 1,N do local v = ''..math.random(10 * N)
>     s1:replace{v} s2:replace{v} end box.commit()
>     > >+s1:count() == s2:count()
>     > >+
>     > >+good = true r1 = s1:select{} r2 = s2:select{}
>     > >+if #r1 ~= #r2 then good = false end
>     > >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false
>     end end
>     > >+good
>     > >+r1 = nil r2 = nil
>     > >+
>     > >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else
>     return t2 ~= nil end end
>     > >+for i = 1, N * 10 do v = ''..i if diff(s1:get{v}, s2:get{v})
>     then good = false end end
>     > >+good
>     > >+
>     > >+s1.index[0]:bsize() < s2.index[0]:bsize()
>     > >+s1.index[0]:alter{hint=true}
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+s2.index[0]:alter{hint=false}
>     > >+s1.index[0]:bsize() > s2.index[0]:bsize()
>     > >+s1.index[0]:alter{hint=false}
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+
>     > >+s1:drop()
>     > >+s2:drop()
>     > >+
>     > >+-- string with collation hints
>     > >+s1 = box.schema.space.create('test1')
>     > >+s1:create_index('test', {type = 'tree', parts = {{1, 'str',
>     collation = 'Unicode'}}, hint = false}).hint
>     > >+s2 = box.schema.space.create('test2')
>     > >+s2:create_index('test', {type = 'tree', parts = {{1, 'str',
>     collation = 'Unicode'}}}).hint
>     > >+
>     > >+N = 1000
>     > >+syms = {'a', 'B', 'c', 'D', 'ж', 'Ё', '~', '1', '%', '2'}
>     > >+syms_size = #syms
>     > >+len = 20
>     > >+vals = {}
>     > >+for i = 1,2*N do str = '' for j=1,len do str = str ..
>     syms[math.random(syms_size)] end vals[i] = str end
>     > >+
>     > >+for i = 1,N do local v = vals[i] s1:replace{v} s2:replace{v} end
>     > >+s1:count() == s2:count()
>     > >+
>     > >+good = true r1 = s1:select{} r2 = s2:select{}
>     > >+if #r1 ~= #r2 then good = false end
>     > >+for k,v in pairs(r1) do if r2[k][1] ~= v[1] then good = false
>     end end
>     > >+good
>     > >+r1 = nil r2 = nil
>     > >+
>     > >+function diff(t1, t2) if t1 then return t1[1] ~= t2[1] else
>     return t2 ~= nil end end
>     > >+for i = 1, N * 2 do v = vals[i] if diff(s1:get{v}, s2:get{v})
>     then good = false end end
>     > >+good
>     > >+
>     > >+s1.index[0]:bsize() < s2.index[0]:bsize()
>     > >+s1.index[0]:alter{hint=true}
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+s2.index[0]:alter{hint=false}
>     > >+s1.index[0]:bsize() > s2.index[0]:bsize()
>     > >+s1.index[0]:alter{hint=false}
>     > >+s1.index[0]:bsize() == s2.index[0]:bsize()
>     > >+
>     > >+s1:drop()
>     > >+s2:drop()
>     > >diff --git a/test/box/tree_pk_multipart.result
>     b/test/box/tree_pk_multipart.result
>     > >index 93219f6..3f6bb94 100644
>     > >--- a/test/box/tree_pk_multipart.result
>     > >+++ b/test/box/tree_pk_multipart.result
>     > >@@ -542,3 +542,156 @@ space:drop()
>     > > space = nil
>     > > ---
>     > > ...
>     > >+-- hints
>     > >+test_run:cmd("setopt delimiter ';'")
>     > >+---
>     > >+- true
>     > >+...
>     > >+function equal(res1, res2)
>     > >+ if #res1 ~= #res2 then
>     > >+ return false
>     > >+ end
>     > >+ for k,v in pairs(res1) do
>     > >+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
>     > >+ return false
>     > >+ end
>     > >+ end
>     > >+ return true
>     > >+end
>     > >+test_run:cmd("setopt delimiter ''");
>     > >+---
>     > >+...
>     > >+-- num num
>     > >+N1 = 100
>     > >+---
>     > >+...
>     > >+t1 = {}
>     > >+---
>     > >+...
>     > >+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
>     > >+---
>     > >+...
>     > >+N2 = 5
>     > >+---
>     > >+...
>     > >+t2 = {}
>     > >+---
>     > >+...
>     > >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>     > >+---
>     > >+...
>     > >+s1 = box.schema.space.create('test1')
>     > >+---
>     > >+...
>     > >+s1:create_index('test', {type = 'tree', parts = {{1, 'num'},
>     {2, 'num'}}, hint = false}).hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s2 = box.schema.space.create('test2')
>     > >+---
>     > >+...
>     > >+s2:create_index('test', {type = 'tree', parts = {{1, 'num'},
>     {2, 'num'}}, hint = true}).hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]}
>     s2:replace{t1[i], t2[j]} end end
>     > >+---
>     > >+...
>     > >+s1:count() == s2:count()
>     > >+---
>     > >+- true
>     > >+...
>     > >+equal(s1:select{}, s2:select{})
>     > >+---
>     > >+- true
>     > >+...
>     > >+good = true
>     > >+---
>     > >+...
>     > >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]},
>     s2:select{t1[i]}) end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+for i = 1,N1*2 do for j=1,N2*2 do good = good and
>     equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1:drop()
>     > >+---
>     > >+...
>     > >+s2:drop()
>     > >+---
>     > >+...
>     > >+-- str num
>     > >+N1 = 100
>     > >+---
>     > >+...
>     > >+t1 = {}
>     > >+---
>     > >+...
>     > >+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
>     > >+---
>     > >+...
>     > >+N2 = 5
>     > >+---
>     > >+...
>     > >+t2 = {}
>     > >+---
>     > >+...
>     > >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>     > >+---
>     > >+...
>     > >+s1 = box.schema.space.create('test1')
>     > >+---
>     > >+...
>     > >+s1:create_index('test', {type = 'tree', parts = {{1, 'str'},
>     {2, 'num'}}, hint = false}).hint
>     > >+---
>     > >+- false
>     > >+...
>     > >+s2 = box.schema.space.create('test2')
>     > >+---
>     > >+...
>     > >+s2:create_index('test', {type = 'tree', parts = {{1, 'str'},
>     {2, 'num'}}, hint = true}).hint
>     > >+---
>     > >+- true
>     > >+...
>     > >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]}
>     s2:replace{t1[i], t2[j]} end end
>     > >+---
>     > >+...
>     > >+s1:count() == s2:count()
>     > >+---
>     > >+- true
>     > >+...
>     > >+equal(s1:select{}, s2:select{})
>     > >+---
>     > >+- true
>     > >+...
>     > >+good = true
>     > >+---
>     > >+...
>     > >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]},
>     s2:select{t1[i]}) end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+for i = 1,N1*2 do for j=1,N2*2 do good = good and
>     equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>     > >+---
>     > >+...
>     > >+good
>     > >+---
>     > >+- true
>     > >+...
>     > >+s1:drop()
>     > >+---
>     > >+...
>     > >+s2:drop()
>     > >+---
>     > >+...
>     > >diff --git a/test/box/tree_pk_multipart.test.lua
>     b/test/box/tree_pk_multipart.test.lua
>     > >index 7d3405f..cba3a98 100644
>     > >--- a/test/box/tree_pk_multipart.test.lua
>     > >+++ b/test/box/tree_pk_multipart.test.lua
>     > >@@ -194,3 +194,67 @@ test_run:cmd("setopt delimiter ''");
>     > > space:drop()
>     > >
>     > > space = nil
>     > >+
>     > >+
>     > >+-- hints
>     > >+test_run:cmd("setopt delimiter ';'")
>     > >+function equal(res1, res2)
>     > >+ if #res1 ~= #res2 then
>     > >+ return false
>     > >+ end
>     > >+ for k,v in pairs(res1) do
>     > >+ if res2[k][1] ~= v[1] or res2[k][2] ~= v[2] then
>     > >+ return false
>     > >+ end
>     > >+ end
>     > >+ return true
>     > >+end
>     > >+test_run:cmd("setopt delimiter ''");
>     > >+
>     > >+-- num num
>     > >+N1 = 100
>     > >+t1 = {}
>     > >+for i = 1,N1*2 do t1[i] = math.random(10000) * 10000 + i end
>     > >+N2 = 5
>     > >+t2 = {}
>     > >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>     > >+
>     > >+s1 = box.schema.space.create('test1')
>     > >+s1:create_index('test', {type = 'tree', parts = {{1, 'num'},
>     {2, 'num'}}, hint = false}).hint
>     > >+s2 = box.schema.space.create('test2')
>     > >+s2:create_index('test', {type = 'tree', parts = {{1, 'num'},
>     {2, 'num'}}, hint = true}).hint
>     > >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]}
>     s2:replace{t1[i], t2[j]} end end
>     > >+s1:count() == s2:count()
>     > >+equal(s1:select{}, s2:select{})
>     > >+good = true
>     > >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]},
>     s2:select{t1[i]}) end
>     > >+good
>     > >+for i = 1,N1*2 do for j=1,N2*2 do good = good and
>     equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>     > >+good
>     > >+
>     > >+s1:drop()
>     > >+s2:drop()
>     > >+
>     > >+-- str num
>     > >+N1 = 100
>     > >+t1 = {}
>     > >+for i = 1,N1*2 do t1[i] = ''..(math.random(10000) * 10000 + i) end
>     > >+N2 = 5
>     > >+t2 = {}
>     > >+for i = 1,N2*2 do t2[i] = math.random(1000000) end
>     > >+
>     > >+s1 = box.schema.space.create('test1')
>     > >+s1:create_index('test', {type = 'tree', parts = {{1, 'str'},
>     {2, 'num'}}, hint = false}).hint
>     > >+s2 = box.schema.space.create('test2')
>     > >+s2:create_index('test', {type = 'tree', parts = {{1, 'str'},
>     {2, 'num'}}, hint = true}).hint
>     > >+for j = 1,N2 do for i = 1,N1 do s1:replace{t1[i], t2[j]}
>     s2:replace{t1[i], t2[j]} end end
>     > >+s1:count() == s2:count()
>     > >+equal(s1:select{}, s2:select{})
>     > >+good = true
>     > >+for i = 1,N1*2 do good = good and equal(s1:select{t1[i]},
>     s2:select{t1[i]}) end
>     > >+good
>     > >+for i = 1,N1*2 do for j=1,N2*2 do good = good and
>     equal(s1:select{t1[i], t2[j]}, s2:select{t1[i], t2[j]}) end end
>     > >+good
>     > >+
>     > >+s1:drop()
>     > >+s2:drop()
>     > >--
>     > >2.7.4
>     > >
>     > --
>     > Ilya Kosarev
>
>
> --
> Ilya Kosarev

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH 0/2] Make tree hint optional
  2020-10-20  1:01 ` [Tarantool-patches] [PATCH 0/2] Make tree hint optional Nikita Pettik
@ 2020-10-21 17:02   ` Alexander V. Tikhonov
  2020-10-21 17:06     ` Nikita Pettik
  0 siblings, 1 reply; 11+ messages in thread
From: Alexander V. Tikhonov @ 2020-10-21 17:02 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

Hi Aleksandr, thanks for the patch. As I see in results it doesn't have
any new degradations[1]. Patchset LGTM.

https://gitlab.com/tarantool/tarantool/-/pipelines/205874308

On Tue, Oct 20, 2020 at 01:01:57AM +0000, Nikita Pettik wrote:
> On 19 Oct 12:51, Aleksandr Lyapunov wrote:
> > https://github.com/tarantool/tarantool/issues/4927
> > https://github.com/tarantool/tarantool/tree/alyapunov/gh-4927-optional-hints
> 
> Release fails to build:
> https://gitlab.com/tarantool/tarantool/-/jobs/798546360
> https://gitlab.com/tarantool/tarantool/-/jobs/798546354
>  
> > Aleksandr Lyapunov (1):
> >   memtx: make tuple compare hints optional
> > 
> > Ilya Kosarev (1):
> >   memtx: move memtx_tree.c to memtx_tree.cc
> > 
> >  src/box/CMakeLists.txt              |    2 +-
> >  src/box/index_def.c                 |    2 +
> >  src/box/index_def.h                 |    6 +
> >  src/box/lua/schema.lua              |   53 ++
> >  src/box/lua/space.cc                |    7 +
> >  src/box/memtx_engine.c              |    2 +
> >  src/box/memtx_tree.c                | 1523 -------------------------------
> >  src/box/memtx_tree.cc               | 1723 +++++++++++++++++++++++++++++++++++
> >  src/lib/salad/bps_tree.h            |   19 +
> >  test/box/alter.result               |  103 ++-
> >  test/box/alter.test.lua             |   34 +
> >  test/box/errinj.result              |    3 +-
> >  test/box/tree_pk.result             |  314 +++++++
> >  test/box/tree_pk.test.lua           |  115 +++
> >  test/box/tree_pk_multipart.result   |  153 ++++
> >  test/box/tree_pk_multipart.test.lua |   64 ++
> >  16 files changed, 2595 insertions(+), 1528 deletions(-)
> >  delete mode 100644 src/box/memtx_tree.c
> >  create mode 100644 src/box/memtx_tree.cc
> > 
> > -- 
> > 2.7.4
> > 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH 0/2] Make tree hint optional
  2020-10-21 17:02   ` Alexander V. Tikhonov
@ 2020-10-21 17:06     ` Nikita Pettik
  0 siblings, 0 replies; 11+ messages in thread
From: Nikita Pettik @ 2020-10-21 17:06 UTC (permalink / raw)
  To: Alexander V. Tikhonov; +Cc: tarantool-patches

On 21 Oct 20:02, Alexander V. Tikhonov wrote:
> Hi Aleksandr, thanks for the patch. As I see in results it doesn't have
> any new degradations[1]. Patchset LGTM.
> 
> https://gitlab.com/tarantool/tarantool/-/pipelines/205874308

Pushed to master, changelog is updated, original branch is dropped.
 
> On Tue, Oct 20, 2020 at 01:01:57AM +0000, Nikita Pettik wrote:
> > On 19 Oct 12:51, Aleksandr Lyapunov wrote:
> > > https://github.com/tarantool/tarantool/issues/4927
> > > https://github.com/tarantool/tarantool/tree/alyapunov/gh-4927-optional-hints
> > 
> > Release fails to build:
> > https://gitlab.com/tarantool/tarantool/-/jobs/798546360
> > https://gitlab.com/tarantool/tarantool/-/jobs/798546354
> >  
> > > Aleksandr Lyapunov (1):
> > >   memtx: make tuple compare hints optional
> > > 
> > > Ilya Kosarev (1):
> > >   memtx: move memtx_tree.c to memtx_tree.cc
> > > 
> > >  src/box/CMakeLists.txt              |    2 +-
> > >  src/box/index_def.c                 |    2 +
> > >  src/box/index_def.h                 |    6 +
> > >  src/box/lua/schema.lua              |   53 ++
> > >  src/box/lua/space.cc                |    7 +
> > >  src/box/memtx_engine.c              |    2 +
> > >  src/box/memtx_tree.c                | 1523 -------------------------------
> > >  src/box/memtx_tree.cc               | 1723 +++++++++++++++++++++++++++++++++++
> > >  src/lib/salad/bps_tree.h            |   19 +
> > >  test/box/alter.result               |  103 ++-
> > >  test/box/alter.test.lua             |   34 +
> > >  test/box/errinj.result              |    3 +-
> > >  test/box/tree_pk.result             |  314 +++++++
> > >  test/box/tree_pk.test.lua           |  115 +++
> > >  test/box/tree_pk_multipart.result   |  153 ++++
> > >  test/box/tree_pk_multipart.test.lua |   64 ++
> > >  16 files changed, 2595 insertions(+), 1528 deletions(-)
> > >  delete mode 100644 src/box/memtx_tree.c
> > >  create mode 100644 src/box/memtx_tree.cc
> > > 
> > > -- 
> > > 2.7.4
> > > 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH 0/2] Make tree hint optional
@ 2020-10-19  9:26 Aleksandr Lyapunov
  0 siblings, 0 replies; 11+ messages in thread
From: Aleksandr Lyapunov @ 2020-10-19  9:26 UTC (permalink / raw)
  To: tarantool-patches

Add an option that disables hints in tree indexes.

https://github.com/tarantool/tarantool/issues/4927
https://github.com/tarantool/tarantool/tree/alyapunov/gh-4927-optional-hints

Aleksandr Lyapunov (1):
  memtx: make tuple compare hints optional

Ilya Kosarev (1):
  memtx: move memtx_tree.c to memtx_tree.cc

 src/box/CMakeLists.txt              |    2 +-
 src/box/index_def.c                 |    2 +
 src/box/index_def.h                 |    6 +
 src/box/lua/schema.lua              |   53 ++
 src/box/lua/space.cc                |    7 +
 src/box/memtx_engine.c              |    2 +
 src/box/memtx_tree.c                | 1523 -------------------------------
 src/box/memtx_tree.cc               | 1723 +++++++++++++++++++++++++++++++++++
 src/lib/salad/bps_tree.h            |   19 +
 test/box/alter.result               |  103 ++-
 test/box/alter.test.lua             |   34 +
 test/box/errinj.result              |    3 +-
 test/box/tree_pk.result             |  314 +++++++
 test/box/tree_pk.test.lua           |  115 +++
 test/box/tree_pk_multipart.result   |  153 ++++
 test/box/tree_pk_multipart.test.lua |   64 ++
 16 files changed, 2595 insertions(+), 1528 deletions(-)
 delete mode 100644 src/box/memtx_tree.c
 create mode 100644 src/box/memtx_tree.cc

-- 
2.7.4

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2020-10-21 17:06 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-19  9:51 [Tarantool-patches] [PATCH 0/2] Make tree hint optional Aleksandr Lyapunov
2020-10-19  9:51 ` [Tarantool-patches] [PATCH 1/2] memtx: move memtx_tree.c to memtx_tree.cc Aleksandr Lyapunov
2020-10-19  9:51 ` [Tarantool-patches] [PATCH 2/2] memtx: make tuple compare hints optional Aleksandr Lyapunov
2020-10-20 12:34   ` Ilya Kosarev
2020-10-20 12:37     ` Nikita Pettik
2020-10-20 12:57       ` Ilya Kosarev
2020-10-21  9:54         ` Aleksandr Lyapunov
2020-10-20  1:01 ` [Tarantool-patches] [PATCH 0/2] Make tree hint optional Nikita Pettik
2020-10-21 17:02   ` Alexander V. Tikhonov
2020-10-21 17:06     ` Nikita Pettik
  -- strict thread matches above, loose matches on Subject: below --
2020-10-19  9:26 Aleksandr Lyapunov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox