Tarantool development patches archive
 help / color / mirror / Atom feed
* [PATCH 00/13] Incorporate tuple comparison hints into Vinyl
@ 2019-04-02 17:33 Vladimir Davydov
  2019-04-02 17:33 ` [PATCH 01/13] vinyl: store tuple comparison hints in memory tree Vladimir Davydov
                   ` (12 more replies)
  0 siblings, 13 replies; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

The goal of this patch set is to build comparison hints into every
subsystem of the Vinyl engine. This is a prerequisite for multikey index
support as we are going to reuse comparison hints to store offsets of
indexed array entries. For conventional indexes, this should speed up
all tuple comparisons, similarly to how it was done in case of the memtx
tree index.

The patch set starts from base subsystems such as the memory level or
page index, and then gradually pushes hints to upper levels. The final
patch of the series isn't just about Vinyl - it removes un-hinted
comparators altogether leaving only hinted versions (the caller is
supposed to pass HINT_NONE if there's no hint). This seems reasonable,
because by the time it is applied, all hot paths have been patched to
use hinted comparators.

Although the patches are quite bulky, they are pretty straightforward
and unsophisticated - they just add hints to tuples where appropriate.

https://github.com/tarantool/tarantool/commits/dv/vy-hints

Vladimir Davydov (13):
  vinyl: store tuple comparison hints in memory tree
  vinyl: store tuple comparison hints in cache tree
  vinyl: store tuple comparison hints in tx write set
  vinyl: store tuple comparison hints in tx read set
  vinyl: store tuple comparison hints in range tree
  vinyl: store tuple comparison hints in page index
  vinyl: propagate tuple comparison hints to read iterator
  vinyl: propagate tuple comparison hints to write iterator
  vinyl: forward tuple comparison hints to memory tree
  vinyl: forward tuple comparison hints to cache tree
  vinyl: forward tuple comparison hints to read iterator
  vinyl: forward tuple comparison hints to tx read set
  Make tuple comparison hints mandatory

 src/box/key_def.c               |   5 +-
 src/box/key_def.h               |  80 +++----------
 src/box/memtx_hash.c            |   6 +-
 src/box/memtx_space.c           |   4 +-
 src/box/memtx_tree.c            |  27 ++---
 src/box/space.c                 |   3 +-
 src/box/sql/analyze.c           |   5 +-
 src/box/tuple_compare.cc        | 179 +++++++----------------------
 src/box/vinyl.c                 | 140 ++++++++++++++---------
 src/box/vy_cache.c              | 131 ++++++++++++---------
 src/box/vy_cache.h              |  52 ++++++---
 src/box/vy_history.c            |   9 +-
 src/box/vy_history.h            |  15 ++-
 src/box/vy_lsm.c                |  88 ++++++++++-----
 src/box/vy_lsm.h                |  13 ++-
 src/box/vy_mem.c                | 235 +++++++++++++++++++++-----------------
 src/box/vy_mem.h                |  77 +++++++++----
 src/box/vy_point_lookup.c       |  71 ++++++------
 src/box/vy_point_lookup.h       |   6 +-
 src/box/vy_range.c              |  42 ++++---
 src/box/vy_range.h              |  31 +++--
 src/box/vy_read_iterator.c      | 209 +++++++++++++++++++++-------------
 src/box/vy_read_iterator.h      |  21 +++-
 src/box/vy_read_set.c           |  19 ++--
 src/box/vy_read_set.h           |  10 +-
 src/box/vy_run.c                | 244 ++++++++++++++++++++++++++--------------
 src/box/vy_run.h                |  28 +++--
 src/box/vy_scheduler.c          |   9 +-
 src/box/vy_stmt.h               |  40 +++++--
 src/box/vy_stmt_stream.h        |   4 +-
 src/box/vy_tx.c                 | 122 ++++++++++++--------
 src/box/vy_tx.h                 |  25 ++--
 src/box/vy_upsert.c             |   3 +-
 src/box/vy_write_iterator.c     |  82 ++++++++++----
 test/unit/vy_cache.c            |  12 +-
 test/unit/vy_iterators_helper.c |  16 ++-
 test/unit/vy_mem.c              |  35 +++---
 test/unit/vy_point_lookup.c     |  16 ++-
 test/unit/vy_write_iterator.c   |   3 +-
 test/vinyl/cache.result         |   6 +-
 test/vinyl/stat.result          |  26 ++---
 41 files changed, 1263 insertions(+), 886 deletions(-)

-- 
2.11.0

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

* [PATCH 01/13] vinyl: store tuple comparison hints in memory tree
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04  8:53   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 02/13] vinyl: store tuple comparison hints in cache tree Vladimir Davydov
                   ` (11 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch incorporates tuple comparison hints into vy_mem_tree,
similarly to how it was done in case of memtx_tree.

Apart from speeding up lookups, this is also needed for multikey index
support, because multikey indexes will reuse hints to store offsets of
indexed array entries.

While we are at it, rename tree_mem_key to vy_mem_tree_key.
---
 src/box/key_def.h        |  19 +++-
 src/box/tuple_compare.cc |  11 +++
 src/box/vinyl.c          |  27 +++---
 src/box/vy_mem.c         | 224 +++++++++++++++++++++++++++--------------------
 src/box/vy_mem.h         |  59 ++++++++++---
 src/box/vy_stmt.h        |  45 ++++++++++
 6 files changed, 263 insertions(+), 122 deletions(-)

diff --git a/src/box/key_def.h b/src/box/key_def.h
index 288cf727..7bb2986b 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -577,9 +577,7 @@ tuple_extract_key_raw(const char *data, const char *data_end,
 /**
  * Compare keys using the key definition.
  * @param key_a key parts with MessagePack array header
- * @param part_count_a the number of parts in the key_a
  * @param key_b key_parts with MessagePack array header
- * @param part_count_b the number of parts in the key_b
  * @param key_def key definition
  *
  * @retval 0  if key_a == key_b
@@ -590,6 +588,23 @@ int
 key_compare(const char *key_a, const char *key_b, struct key_def *key_def);
 
 /**
+ * Compare keys using the key definition and comparison hints.
+ * @param key_a key parts with MessagePack array header
+ * @param key_a_hint comparison hint of @a key_a
+ * @param key_b key_parts with MessagePack array header
+ * @param key_b_hint comparison hint of @a key_b
+ * @param key_def key definition
+ *
+ * @retval 0  if key_a == key_b
+ * @retval <0 if key_a < key_b
+ * @retval >0 if key_a > key_b
+ */
+int
+key_compare_hinted(const char *key_a, hint_t key_a_hint,
+		   const char *key_b, hint_t key_b_hint,
+		   struct key_def *key_def);
+
+/**
  * Compare tuples using the key definition.
  * @param tuple_a first tuple
  * @param tuple_b second tuple
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index 93756365..055f3181 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -817,6 +817,17 @@ key_compare(const char *key_a, const char *key_b, struct key_def *key_def)
 	}
 }
 
+int
+key_compare_hinted(const char *key_a, hint_t key_a_hint,
+		   const char *key_b, hint_t key_b_hint,
+		   struct key_def *key_def)
+{
+	int rc = hint_cmp(key_a_hint, key_b_hint);
+	if (rc != 0)
+		return rc;
+	return key_compare(key_a, key_b, key_def);
+}
+
 template <bool is_nullable, bool has_optional_parts>
 static int
 tuple_compare_sequential_hinted(const struct tuple *tuple_a, hint_t tuple_a_hint,
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index da891025..77bacd77 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -3532,6 +3532,8 @@ vy_squash_process(struct vy_squash *squash)
 	if (result == NULL)
 		return 0;
 
+	hint_t hint = vy_stmt_hint(result, lsm->cmp_def);
+
 	/*
 	 * While we were reading on-disk runs, new statements could
 	 * have been prepared for the squashed key. We mustn't apply
@@ -3540,10 +3542,8 @@ vy_squash_process(struct vy_squash *squash)
 	 * vy_lsm_commit_upsert().
 	 */
 	struct vy_mem *mem = lsm->mem;
-	struct tree_mem_key tree_key = {
-		.stmt = result,
-		.lsn = vy_stmt_lsn(result),
-	};
+	struct vy_mem_tree_key tree_key = vy_mem_tree_key(result, hint,
+							  vy_stmt_lsn(result));
 	struct vy_mem_tree_iterator mem_itr =
 		vy_mem_tree_lower_bound(&mem->tree, &tree_key, NULL);
 	if (vy_mem_tree_iterator_is_invalid(&mem_itr)) {
@@ -3557,13 +3557,14 @@ vy_squash_process(struct vy_squash *squash)
 	vy_mem_tree_iterator_prev(&mem->tree, &mem_itr);
 	uint8_t n_upserts = 0;
 	while (!vy_mem_tree_iterator_is_invalid(&mem_itr)) {
-		const struct tuple *mem_stmt;
-		mem_stmt = *vy_mem_tree_iterator_get_elem(&mem->tree, &mem_itr);
-		if (vy_stmt_compare(result, mem_stmt, lsm->cmp_def) != 0 ||
-		    vy_stmt_type(mem_stmt) != IPROTO_UPSERT)
+		struct vy_mem_tree_elem elem;
+		elem = *vy_mem_tree_iterator_get_elem(&mem->tree, &mem_itr);
+		if (vy_stmt_compare_hinted(result, hint, elem.stmt, elem.hint,
+					   lsm->cmp_def) != 0 ||
+		    vy_stmt_type(elem.stmt) != IPROTO_UPSERT)
 			break;
-		assert(vy_stmt_lsn(mem_stmt) >= MAX_LSN);
-		vy_stmt_set_n_upserts((struct tuple *)mem_stmt, n_upserts);
+		assert(vy_stmt_lsn(elem.stmt) >= MAX_LSN);
+		vy_stmt_set_n_upserts((struct tuple *)elem.stmt, n_upserts);
 		if (n_upserts <= VY_UPSERT_THRESHOLD)
 			++n_upserts;
 		vy_mem_tree_iterator_prev(&mem->tree, &mem_itr);
@@ -4166,9 +4167,9 @@ vy_build_recover_mem(struct vy_lsm *lsm, struct vy_lsm *pk, struct vy_mem *mem)
 	struct vy_mem_tree_iterator itr;
 	itr = vy_mem_tree_iterator_last(&mem->tree);
 	while (!vy_mem_tree_iterator_is_invalid(&itr)) {
-		const struct tuple *mem_stmt;
-		mem_stmt = *vy_mem_tree_iterator_get_elem(&mem->tree, &itr);
-		if (vy_build_recover_stmt(lsm, pk, mem_stmt) != 0)
+		struct vy_mem_tree_elem elem;
+		elem = *vy_mem_tree_iterator_get_elem(&mem->tree, &itr);
+		if (vy_build_recover_stmt(lsm, pk, elem.stmt) != 0)
 			return -1;
 		vy_mem_tree_iterator_prev(&mem->tree, &itr);
 	}
diff --git a/src/box/vy_mem.c b/src/box/vy_mem.c
index e3b40083..220751d4 100644
--- a/src/box/vy_mem.c
+++ b/src/box/vy_mem.c
@@ -70,6 +70,23 @@ vy_mem_env_destroy(struct vy_mem_env *env)
 
 /** {{{ vy_mem */
 
+/**
+ * Assert that a memory tree iterator points to a given statement.
+ */
+static inline void
+vy_mem_tree_iterator_assert(struct vy_mem_tree *tree,
+			    struct vy_mem_tree_iterator *pos,
+			    struct vy_mem_tree_elem expected)
+{
+	assert(!vy_mem_tree_iterator_is_invalid(pos));
+	struct vy_mem_tree_elem elem;
+	elem = *vy_mem_tree_iterator_get_elem(tree, pos);
+	assert(elem.stmt == expected.stmt);
+	assert(elem.hint == expected.hint);
+	(void)elem;
+	(void)expected;
+}
+
 static void *
 vy_mem_tree_extent_alloc(void *ctx)
 {
@@ -134,9 +151,9 @@ vy_mem_delete(struct vy_mem *index)
 const struct tuple *
 vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt)
 {
-	struct tree_mem_key tree_key;
-	tree_key.stmt = stmt;
-	tree_key.lsn = vy_stmt_lsn(stmt) - 1;
+	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
+	struct vy_mem_tree_key tree_key = vy_mem_tree_key(stmt, hint,
+						vy_stmt_lsn(stmt) - 1);
 	bool exact = false;
 	struct vy_mem_tree_iterator itr =
 		vy_mem_tree_lower_bound(&mem->tree, &tree_key, &exact);
@@ -144,11 +161,12 @@ vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt)
 	if (vy_mem_tree_iterator_is_invalid(&itr))
 		return NULL;
 
-	const struct tuple *result;
+	struct vy_mem_tree_elem result;
 	result = *vy_mem_tree_iterator_get_elem(&mem->tree, &itr);
-	if (vy_stmt_compare(result, stmt, mem->cmp_def) != 0)
+	if (vy_stmt_compare_hinted(result.stmt, result.hint,
+				   stmt, hint, mem->cmp_def) != 0)
 		return NULL;
-	return result;
+	return result.stmt;
 }
 
 int
@@ -159,15 +177,16 @@ vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt)
 	assert(stmt->format_id == tuple_format_id(mem->format));
 	/* The statement must be from a lsregion. */
 	assert(!vy_stmt_is_refable(stmt));
+	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
 	size_t size = tuple_size(stmt);
-	const struct tuple *replaced_stmt = NULL;
+	struct vy_mem_tree_elem new = vy_mem_tree_elem(stmt, hint);
+	struct vy_mem_tree_elem replaced = vy_mem_tree_elem_invalid();
 	struct vy_mem_tree_iterator inserted;
-	if (vy_mem_tree_insert_get_iterator(&mem->tree, stmt, &replaced_stmt,
+	if (vy_mem_tree_insert_get_iterator(&mem->tree, new, &replaced,
 					    &inserted) != 0)
 		return -1;
-	assert(! vy_mem_tree_iterator_is_invalid(&inserted));
-	assert(*vy_mem_tree_iterator_get_elem(&mem->tree, &inserted) == stmt);
-	if (replaced_stmt == NULL)
+	vy_mem_tree_iterator_assert(&mem->tree, &inserted, new);
+	if (replaced.stmt == NULL)
 		mem->count.rows++;
 	mem->count.bytes += size;
 	/*
@@ -192,12 +211,15 @@ vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt)
 	 * UPSERTs subsequence.
 	 */
 	vy_mem_tree_iterator_next(&mem->tree, &inserted);
-	const struct tuple **older = vy_mem_tree_iterator_get_elem(&mem->tree,
-								   &inserted);
-	if (older == NULL || vy_stmt_type(*older) != IPROTO_UPSERT ||
-	    vy_stmt_compare(stmt, *older, mem->cmp_def) != 0)
+	if (vy_mem_tree_iterator_is_invalid(&inserted))
 		return 0;
-	uint8_t n_upserts = vy_stmt_n_upserts(*older);
+	struct vy_mem_tree_elem older;
+	older = *vy_mem_tree_iterator_get_elem(&mem->tree, &inserted);
+	if (vy_stmt_type(older.stmt) != IPROTO_UPSERT ||
+	    vy_stmt_compare_hinted(stmt, hint, older.stmt, older.hint,
+				   mem->cmp_def) != 0)
+		return 0;
+	uint8_t n_upserts = vy_stmt_n_upserts(older.stmt);
 	/*
 	 * Stop increment if the threshold is reached to avoid
 	 * creation of multiple squashing tasks.
@@ -219,11 +241,13 @@ vy_mem_insert(struct vy_mem *mem, const struct tuple *stmt)
 	       stmt->format_id == tuple_format_id(mem->format));
 	/* The statement must be from a lsregion. */
 	assert(!vy_stmt_is_refable(stmt));
+	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
 	size_t size = tuple_size(stmt);
-	const struct tuple *replaced_stmt = NULL;
-	if (vy_mem_tree_insert(&mem->tree, stmt, &replaced_stmt))
+	struct vy_mem_tree_elem new = vy_mem_tree_elem(stmt, hint);
+	struct vy_mem_tree_elem replaced = vy_mem_tree_elem_invalid();
+	if (vy_mem_tree_insert(&mem->tree, new, &replaced))
 		return -1;
-	if (replaced_stmt == NULL)
+	if (replaced.stmt == NULL)
 		mem->count.rows++;
 	mem->count.bytes += size;
 	/*
@@ -261,7 +285,9 @@ vy_mem_rollback_stmt(struct vy_mem *mem, const struct tuple *stmt)
 {
 	/* This is the statement we've inserted before. */
 	assert(!vy_stmt_is_refable(stmt));
-	int rc = vy_mem_tree_delete(&mem->tree, stmt);
+	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
+	struct vy_mem_tree_elem elem = vy_mem_tree_elem(stmt, hint);
+	int rc = vy_mem_tree_delete(&mem->tree, elem);
 	assert(rc == 0);
 	(void) rc;
 	/* We can't free memory in case of rollback. */
@@ -274,15 +300,6 @@ vy_mem_rollback_stmt(struct vy_mem *mem, const struct tuple *stmt)
 /* {{{ vy_mem_iterator support functions */
 
 /**
- * Get a stmt by current position
- */
-static const struct tuple *
-vy_mem_iterator_curr_stmt(struct vy_mem_iterator *itr)
-{
-	return *vy_mem_tree_iterator_get_elem(&itr->mem->tree, &itr->curr_pos);
-}
-
-/**
  * Make a step in the iterator direction.
  * @retval 0 success
  * @retval 1 EOF
@@ -296,7 +313,8 @@ vy_mem_iterator_step(struct vy_mem_iterator *itr)
 		vy_mem_tree_iterator_next(&itr->mem->tree, &itr->curr_pos);
 	if (vy_mem_tree_iterator_is_invalid(&itr->curr_pos))
 		return 1;
-	itr->curr_stmt = vy_mem_iterator_curr_stmt(itr);
+	itr->curr = *vy_mem_tree_iterator_get_elem(&itr->mem->tree,
+						   &itr->curr_pos);
 	return 0;
 }
 
@@ -311,16 +329,18 @@ vy_mem_iterator_step(struct vy_mem_iterator *itr)
 static int
 vy_mem_iterator_find_lsn(struct vy_mem_iterator *itr)
 {
+	vy_mem_tree_iterator_assert(&itr->mem->tree, &itr->curr_pos, itr->curr);
+
 	/* Skip to the first statement visible in the read view. */
-	assert(!vy_mem_tree_iterator_is_invalid(&itr->curr_pos));
-	assert(itr->curr_stmt == vy_mem_iterator_curr_stmt(itr));
 	struct key_def *cmp_def = itr->mem->cmp_def;
-	while (vy_stmt_lsn(itr->curr_stmt) > (**itr->read_view).vlsn ||
-	       vy_stmt_flags(itr->curr_stmt) & VY_STMT_SKIP_READ) {
+	while (vy_stmt_lsn(itr->curr.stmt) > (**itr->read_view).vlsn ||
+	       vy_stmt_flags(itr->curr.stmt) & VY_STMT_SKIP_READ) {
 		if (vy_mem_iterator_step(itr) != 0 ||
 		    (itr->iterator_type == ITER_EQ &&
-		     vy_stmt_compare(itr->key, itr->curr_stmt, cmp_def))) {
-			itr->curr_stmt = NULL;
+		     vy_stmt_compare_hinted(itr->key, itr->hint,
+					    itr->curr.stmt, itr->curr.hint,
+					    cmp_def) != 0)) {
+			itr->curr = vy_mem_tree_elem_invalid();
 			return 1;
 		}
 	}
@@ -337,10 +357,11 @@ vy_mem_iterator_find_lsn(struct vy_mem_iterator *itr)
 		/* No more statements. */
 		return 0;
 	}
-	const struct tuple *prev_stmt;
-	prev_stmt = *vy_mem_tree_iterator_get_elem(&itr->mem->tree, &prev_pos);
-	if (vy_stmt_lsn(prev_stmt) > (**itr->read_view).vlsn ||
-	    vy_stmt_compare(itr->curr_stmt, prev_stmt, cmp_def) != 0) {
+	struct vy_mem_tree_elem prev;
+	prev = *vy_mem_tree_iterator_get_elem(&itr->mem->tree, &prev_pos);
+	if (vy_stmt_lsn(prev.stmt) > (**itr->read_view).vlsn ||
+	    vy_stmt_compare_hinted(itr->curr.stmt, itr->curr.hint,
+				   prev.stmt, prev.hint, cmp_def) != 0) {
 		/*
 		 * The next statement is either invisible in
 		 * the read view or for another key.
@@ -354,21 +375,20 @@ vy_mem_iterator_find_lsn(struct vy_mem_iterator *itr)
 	 * is going to take long. So instead we look it up - it's
 	 * pretty cheap anyway.
 	 */
-	struct tree_mem_key tree_key;
-	tree_key.stmt = itr->curr_stmt;
-	tree_key.lsn = (**itr->read_view).vlsn;
+	struct vy_mem_tree_key tree_key = vy_mem_tree_key(itr->curr.stmt,
+				itr->curr.hint, (**itr->read_view).vlsn);
 	itr->curr_pos = vy_mem_tree_lower_bound(&itr->mem->tree,
 						&tree_key, NULL);
 	assert(!vy_mem_tree_iterator_is_invalid(&itr->curr_pos));
-	itr->curr_stmt = *vy_mem_tree_iterator_get_elem(&itr->mem->tree,
-							&itr->curr_pos);
+	itr->curr = *vy_mem_tree_iterator_get_elem(&itr->mem->tree,
+						   &itr->curr_pos);
 
 	/* Skip VY_STMT_SKIP_READ statements, if any. */
-	while (vy_stmt_flags(itr->curr_stmt) & VY_STMT_SKIP_READ) {
+	while (vy_stmt_flags(itr->curr.stmt) & VY_STMT_SKIP_READ) {
 		vy_mem_tree_iterator_next(&itr->mem->tree, &itr->curr_pos);
 		assert(!vy_mem_tree_iterator_is_invalid(&itr->curr_pos));
-		itr->curr_stmt = *vy_mem_tree_iterator_get_elem(&itr->mem->tree,
-								&itr->curr_pos);
+		itr->curr = *vy_mem_tree_iterator_get_elem(&itr->mem->tree,
+							   &itr->curr_pos);
 	}
 	return 0;
 }
@@ -382,27 +402,27 @@ vy_mem_iterator_find_lsn(struct vy_mem_iterator *itr)
  * @retval 1 Not found
  */
 static int
-vy_mem_iterator_seek(struct vy_mem_iterator *itr, const struct tuple *last_key)
+vy_mem_iterator_seek(struct vy_mem_iterator *itr, const struct tuple *last_key,
+		     hint_t last_hint)
 {
 	itr->stat->lookup++;
 	itr->search_started = true;
 	itr->version = itr->mem->version;
-	itr->curr_stmt = NULL;
+	itr->curr = vy_mem_tree_elem_invalid();
 
-	const struct tuple *key = itr->key;
+	struct vy_mem_tree_key tree_key;
+	/* (lsn == INT64_MAX - 1) means that lsn is ignored in comparison */
+	tree_key = vy_mem_tree_key(itr->key, itr->hint, INT64_MAX - 1);
 	enum iterator_type iterator_type = itr->iterator_type;
 	if (last_key != NULL) {
-		key = last_key;
+		tree_key.stmt = last_key;
+		tree_key.hint = last_hint;
 		iterator_type = iterator_direction(itr->iterator_type) > 0 ?
 				ITER_GT : ITER_LT;
 	}
 
 	bool exact;
-	struct tree_mem_key tree_key;
-	tree_key.stmt = key;
-	/* (lsn == INT64_MAX - 1) means that lsn is ignored in comparison */
-	tree_key.lsn = INT64_MAX - 1;
-	if (!vy_stmt_is_empty_key(key)) {
+	if (!vy_stmt_is_empty_key(tree_key.stmt)) {
 		if (iterator_type == ITER_LE || iterator_type == ITER_GT) {
 			itr->curr_pos =
 				vy_mem_tree_upper_bound(&itr->mem->tree,
@@ -426,12 +446,15 @@ vy_mem_iterator_seek(struct vy_mem_iterator *itr, const struct tuple *last_key)
 		vy_mem_tree_iterator_prev(&itr->mem->tree, &itr->curr_pos);
 	if (vy_mem_tree_iterator_is_invalid(&itr->curr_pos))
 		return 1;
-	itr->curr_stmt = vy_mem_iterator_curr_stmt(itr);
+	itr->curr = *vy_mem_tree_iterator_get_elem(&itr->mem->tree,
+						   &itr->curr_pos);
 	if (itr->iterator_type == ITER_EQ &&
 	    ((last_key == NULL && !exact) ||
-	     (last_key != NULL && vy_stmt_compare(itr->key, itr->curr_stmt,
-						  itr->mem->cmp_def) != 0))) {
-		itr->curr_stmt = NULL;
+	     (last_key != NULL &&
+	      vy_stmt_compare_hinted(itr->key, itr->hint,
+				     itr->curr.stmt, itr->curr.hint,
+				     itr->mem->cmp_def) != 0))) {
+		itr->curr = vy_mem_tree_elem_invalid();
 		return 1;
 	}
 	return vy_mem_iterator_find_lsn(itr);
@@ -453,10 +476,11 @@ vy_mem_iterator_open(struct vy_mem_iterator *itr, struct vy_mem_iterator_stat *s
 
 	itr->iterator_type = iterator_type;
 	itr->key = key;
+	itr->hint = vy_stmt_hint(key, mem->cmp_def);
 	itr->read_view = rv;
 
 	itr->curr_pos = vy_mem_tree_invalid_iterator();
-	itr->curr_stmt = NULL;
+	itr->curr = vy_mem_tree_elem_invalid();
 
 	itr->search_started = false;
 }
@@ -470,17 +494,18 @@ static NODISCARD int
 vy_mem_iterator_next_key(struct vy_mem_iterator *itr)
 {
 	if (!itr->search_started)
-		return vy_mem_iterator_seek(itr, NULL);
-	if (!itr->curr_stmt) /* End of search. */
+		return vy_mem_iterator_seek(itr, NULL, HINT_NONE);
+	if (itr->curr.stmt == NULL)
 		return 1;
+
 	assert(itr->mem->version == itr->version);
-	assert(!vy_mem_tree_iterator_is_invalid(&itr->curr_pos));
-	assert(itr->curr_stmt == vy_mem_iterator_curr_stmt(itr));
+	vy_mem_tree_iterator_assert(&itr->mem->tree, &itr->curr_pos, itr->curr);
+
 	struct key_def *cmp_def = itr->mem->cmp_def;
+	struct vy_mem_tree_elem prev = itr->curr;
 
-	const struct tuple *prev_stmt = itr->curr_stmt;
 	if (vy_mem_iterator_step(itr) != 0) {
-		itr->curr_stmt = NULL;
+		itr->curr = vy_mem_tree_elem_invalid();
 		return 1;
 	}
 	/*
@@ -489,12 +514,14 @@ vy_mem_iterator_next_key(struct vy_mem_iterator *itr)
 	 * for this key so instead of iterating further we simply
 	 * look up the next key - it's pretty cheap anyway.
 	 */
-	if (vy_stmt_compare(prev_stmt, itr->curr_stmt, cmp_def) == 0)
-		return vy_mem_iterator_seek(itr, itr->curr_stmt);
+	if (vy_stmt_compare_hinted(prev.stmt, prev.hint, itr->curr.stmt,
+				   itr->curr.hint, cmp_def) == 0)
+		return vy_mem_iterator_seek(itr, itr->curr.stmt, itr->curr.hint);
 
 	if (itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare(itr->key, itr->curr_stmt, cmp_def) != 0) {
-		itr->curr_stmt = NULL;
+	    vy_stmt_compare_hinted(itr->key, itr->hint, itr->curr.stmt,
+				   itr->curr.hint, cmp_def) != 0) {
+		itr->curr = vy_mem_tree_elem_invalid();
 		return 1;
 	}
 	return vy_mem_iterator_find_lsn(itr);
@@ -509,27 +536,28 @@ static NODISCARD int
 vy_mem_iterator_next_lsn(struct vy_mem_iterator *itr)
 {
 	assert(itr->search_started);
-	if (!itr->curr_stmt) /* End of search. */
+	if (itr->curr.stmt == NULL)
 		return 1;
+
 	assert(itr->mem->version == itr->version);
-	assert(!vy_mem_tree_iterator_is_invalid(&itr->curr_pos));
-	assert(itr->curr_stmt == vy_mem_iterator_curr_stmt(itr));
+	vy_mem_tree_iterator_assert(&itr->mem->tree, &itr->curr_pos, itr->curr);
+
 	struct key_def *cmp_def = itr->mem->cmp_def;
-
 	struct vy_mem_tree_iterator next_pos = itr->curr_pos;
 next:
 	vy_mem_tree_iterator_next(&itr->mem->tree, &next_pos);
 	if (vy_mem_tree_iterator_is_invalid(&next_pos))
 		return 1; /* EOF */
 
-	const struct tuple *next_stmt;
-	next_stmt = *vy_mem_tree_iterator_get_elem(&itr->mem->tree, &next_pos);
-	if (vy_stmt_compare(itr->curr_stmt, next_stmt, cmp_def) != 0)
+	struct vy_mem_tree_elem next;
+	next = *vy_mem_tree_iterator_get_elem(&itr->mem->tree, &next_pos);
+	if (vy_stmt_compare_hinted(itr->curr.stmt, itr->curr.hint,
+				   next.stmt, next.hint, cmp_def) != 0)
 		return 1;
 
 	itr->curr_pos = next_pos;
-	itr->curr_stmt = next_stmt;
-	if (vy_stmt_flags(itr->curr_stmt) & VY_STMT_SKIP_READ)
+	itr->curr = next;
+	if (vy_stmt_flags(itr->curr.stmt) & VY_STMT_SKIP_READ)
 		goto next;
 	return 0;
 }
@@ -544,7 +572,7 @@ vy_mem_iterator_get_history(struct vy_mem_iterator *itr,
 			    struct vy_history *history)
 {
 	do {
-		struct tuple *stmt = (struct tuple *)itr->curr_stmt;
+		struct tuple *stmt = (struct tuple *)itr->curr.stmt;
 		vy_stmt_counter_acct_tuple(&itr->stat->get, stmt);
 		if (vy_history_append_stmt(history, stmt) != 0)
 			return -1;
@@ -571,18 +599,22 @@ vy_mem_iterator_skip(struct vy_mem_iterator *itr,
 {
 	assert(!itr->search_started || itr->version == itr->mem->version);
 
+	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(last_stmt, itr->mem->cmp_def);
+
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
 	 */
 	if (itr->search_started &&
-	    (itr->curr_stmt == NULL || last_stmt == NULL ||
+	    (itr->curr.stmt == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare(itr->curr_stmt, last_stmt, itr->mem->cmp_def) > 0))
+	     vy_stmt_compare_hinted(itr->curr.stmt, itr->curr.hint, last_stmt,
+				    last_hint, itr->mem->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
-	if (vy_mem_iterator_seek(itr, last_stmt) == 0)
+	if (vy_mem_iterator_seek(itr, last_stmt, last_hint) == 0)
 		return vy_mem_iterator_get_history(itr, history);
 	return 0;
 }
@@ -595,10 +627,12 @@ vy_mem_iterator_restore(struct vy_mem_iterator *itr,
 	if (!itr->search_started || itr->version == itr->mem->version)
 		return 0;
 
-	vy_mem_iterator_seek(itr, last_stmt);
+	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(last_stmt, itr->mem->cmp_def);
+	vy_mem_iterator_seek(itr, last_stmt, last_hint);
 
 	vy_history_cleanup(history);
-	if (itr->curr_stmt != NULL &&
+	if (itr->curr.stmt != NULL &&
 	    vy_mem_iterator_get_history(itr, history) != 0)
 		return -1;
 	return 1;
@@ -616,16 +650,16 @@ vy_mem_stream_next(struct vy_stmt_stream *virt_stream, struct tuple **ret)
 	assert(virt_stream->iface->next == vy_mem_stream_next);
 	struct vy_mem_stream *stream = (struct vy_mem_stream *)virt_stream;
 
-	struct tuple **res = (struct tuple **)
-		vy_mem_tree_iterator_get_elem(&stream->mem->tree,
-					      &stream->curr_pos);
-	if (res == NULL) {
+	if (vy_mem_tree_iterator_is_invalid(&stream->curr_pos)) {
 		*ret = NULL;
-	} else {
-		*ret = *res;
-		vy_mem_tree_iterator_next(&stream->mem->tree,
-					  &stream->curr_pos);
+		return 0;
 	}
+
+	struct vy_mem_tree_elem elem;
+	elem = *vy_mem_tree_iterator_get_elem(&stream->mem->tree,
+					      &stream->curr_pos);
+	*ret = (struct tuple *)elem.stmt;
+	vy_mem_tree_iterator_next(&stream->mem->tree, &stream->curr_pos);
 	return 0;
 }
 
diff --git a/src/box/vy_mem.h b/src/box/vy_mem.h
index 39f238b3..160bf5e1 100644
--- a/src/box/vy_mem.h
+++ b/src/box/vy_mem.h
@@ -78,22 +78,54 @@ vy_mem_env_destroy(struct vy_mem_env *env);
 
 /** @cond false */
 
-struct tree_mem_key {
+struct vy_mem_tree_elem {
 	const struct tuple *stmt;
+	hint_t hint;
+};
+
+struct vy_mem_tree_key {
+	const struct tuple *stmt;
+	hint_t hint;
 	int64_t lsn;
 };
 
+static inline struct vy_mem_tree_key
+vy_mem_tree_key(const struct tuple *stmt, hint_t hint, int64_t lsn)
+{
+	struct vy_mem_tree_key key;
+	key.stmt = stmt;
+	key.hint = hint;
+	key.lsn = lsn;
+	return key;
+}
+
+static inline struct vy_mem_tree_elem
+vy_mem_tree_elem(const struct tuple *stmt, hint_t hint)
+{
+	struct vy_mem_tree_elem elem;
+	elem.stmt = stmt;
+	elem.hint = hint;
+	return elem;
+}
+
+static inline struct vy_mem_tree_elem
+vy_mem_tree_elem_invalid(void)
+{
+	return vy_mem_tree_elem(NULL, HINT_NONE);
+}
+
 /**
  * Internal. Extracted to speed up BPS tree.
  */
 static int
-vy_mem_tree_cmp(const struct tuple *a, const struct tuple *b,
+vy_mem_tree_cmp(struct vy_mem_tree_elem a, struct vy_mem_tree_elem b,
 		struct key_def *cmp_def)
 {
-	int res = vy_stmt_compare(a, b, cmp_def);
+	int res = vy_stmt_compare_hinted(a.stmt, a.hint,
+					 b.stmt, b.hint, cmp_def);
 	if (res)
 		return res;
-	int64_t a_lsn = vy_stmt_lsn(a), b_lsn = vy_stmt_lsn(b);
+	int64_t a_lsn = vy_stmt_lsn(a.stmt), b_lsn = vy_stmt_lsn(b.stmt);
 	return a_lsn > b_lsn ? -1 : a_lsn < b_lsn;
 }
 
@@ -101,14 +133,15 @@ vy_mem_tree_cmp(const struct tuple *a, const struct tuple *b,
  * Internal. Extracted to speed up BPS tree.
  */
 static int
-vy_mem_tree_cmp_key(const struct tuple *a, struct tree_mem_key *key,
+vy_mem_tree_cmp_key(struct vy_mem_tree_elem elem, struct vy_mem_tree_key *key,
 		    struct key_def *cmp_def)
 {
-	int res = vy_stmt_compare(a, key->stmt, cmp_def);
+	int res = vy_stmt_compare_hinted(elem.stmt, elem.hint,
+					 key->stmt, key->hint, cmp_def);
 	if (res == 0) {
 		if (key->lsn == INT64_MAX - 1)
 			return 0;
-		int64_t a_lsn = vy_stmt_lsn(a);
+		int64_t a_lsn = vy_stmt_lsn(elem.stmt);
 		res = a_lsn > key->lsn ? -1 : a_lsn < key->lsn;
 	}
 	return res;
@@ -121,8 +154,8 @@ vy_mem_tree_cmp_key(const struct tuple *a, struct tree_mem_key *key,
 #define BPS_TREE_EXTENT_SIZE VY_MEM_TREE_EXTENT_SIZE
 #define BPS_TREE_COMPARE(a, b, cmp_def) vy_mem_tree_cmp(a, b, cmp_def)
 #define BPS_TREE_COMPARE_KEY(a, b, cmp_def) vy_mem_tree_cmp_key(a, b, cmp_def)
-#define bps_tree_elem_t const struct tuple *
-#define bps_tree_key_t struct tree_mem_key *
+#define bps_tree_elem_t struct vy_mem_tree_elem
+#define bps_tree_key_t struct vy_mem_tree_key *
 #define bps_tree_arg_t struct key_def *
 #define BPS_TREE_NO_DEBUG
 
@@ -346,6 +379,8 @@ struct vy_mem_iterator {
 	enum iterator_type iterator_type;
 	/** Key to search. */
 	const struct tuple *key;
+	/** Comparison hint of the search key. */
+	hint_t hint;
 	/* LSN visibility, iterator shows values with lsn <= than that */
 	const struct vy_read_view **read_view;
 
@@ -354,11 +389,11 @@ struct vy_mem_iterator {
 	struct vy_mem_tree_iterator curr_pos;
 	/*
 	 * The pointer on a region allocated statement from vy_mem BPS tree.
-	 * There is no guarantee that curr_pos points on curr_stmt in the tree.
-	 * For example, cur_pos can be invalid but curr_stmt can point on a
+	 * There is no guarantee that curr_pos points on curr in the tree.
+	 * For example, cur_pos can be invalid but curr can point on a
 	 * valid statement.
 	 */
-	const struct tuple *curr_stmt;
+	struct vy_mem_tree_elem curr;
 	/* data version from vy_mem */
 	uint32_t version;
 
diff --git a/src/box/vy_stmt.h b/src/box/vy_stmt.h
index 2da2593f..928ae348 100644
--- a/src/box/vy_stmt.h
+++ b/src/box/vy_stmt.h
@@ -363,6 +363,21 @@ vy_stmt_unref_if_possible(struct tuple *stmt)
 }
 
 /**
+ * Return a comparison hint of a vinyl statement.
+ */
+static inline hint_t
+vy_stmt_hint(const struct tuple *stmt, struct key_def *key_def)
+{
+	if (vy_stmt_is_key(stmt)) {
+		const char *key = tuple_data(stmt);
+		uint32_t part_count = mp_decode_array(&key);
+		return key_hint(key, part_count, key_def);
+	} else {
+		return tuple_hint(stmt, key_def);
+	}
+}
+
+/**
  * Compare two vinyl statements taking into account their
  * formats (key or tuple).
  */
@@ -389,6 +404,36 @@ vy_stmt_compare(const struct tuple *a, const struct tuple *b,
 }
 
 /**
+ * Compare two vinyl statements taking into account their
+ * formats (key or tuple) and using comparison hints.
+ */
+static inline int
+vy_stmt_compare_hinted(const struct tuple *a, hint_t a_hint,
+		       const struct tuple *b, hint_t b_hint,
+		       struct key_def *key_def)
+{
+	bool a_is_tuple = !vy_stmt_is_key(a);
+	bool b_is_tuple = !vy_stmt_is_key(b);
+	if (a_is_tuple && b_is_tuple) {
+		return tuple_compare_hinted(a, a_hint, b, b_hint, key_def);
+	} else if (a_is_tuple && !b_is_tuple) {
+		const char *key = tuple_data(b);
+		uint32_t part_count = mp_decode_array(&key);
+		return tuple_compare_with_key_hinted(a, a_hint, key, part_count,
+						     b_hint, key_def);
+	} else if (!a_is_tuple && b_is_tuple) {
+		const char *key = tuple_data(a);
+		uint32_t part_count = mp_decode_array(&key);
+		return -tuple_compare_with_key_hinted(b, b_hint, key, part_count,
+						      a_hint, key_def);
+	} else {
+		assert(!a_is_tuple && !b_is_tuple);
+		return key_compare_hinted(tuple_data(a), a_hint,
+					  tuple_data(b), b_hint, key_def);
+	}
+}
+
+/**
  * Compare a vinyl statement (key or tuple) with a raw key
  * (msgpack array).
  */
-- 
2.11.0

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

* [PATCH 02/13] vinyl: store tuple comparison hints in cache tree
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
  2019-04-02 17:33 ` [PATCH 01/13] vinyl: store tuple comparison hints in memory tree Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 11:39   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 03/13] vinyl: store tuple comparison hints in tx write set Vladimir Davydov
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch incorporates tuple comparison hints into vy_cache_tree,
similarly to how it was done in case of memtx_tree.

Apart from speeding up lookups, this is also needed for multikey index
support, because multikey indexes will reuse hints to store offsets of
indexed array entries.
---
 src/box/vy_cache.c      | 119 +++++++++++++++++++++++++++++++-----------------
 src/box/vy_cache.h      |  32 +++++++++++--
 test/vinyl/cache.result |   6 +--
 test/vinyl/stat.result  |   4 +-
 4 files changed, 109 insertions(+), 52 deletions(-)

diff --git a/src/box/vy_cache.c b/src/box/vy_cache.c
index e526ab1e..8fafd648 100644
--- a/src/box/vy_cache.c
+++ b/src/box/vy_cache.c
@@ -84,7 +84,7 @@ vy_cache_entry_size(const struct vy_cache_entry *entry)
 
 static struct vy_cache_entry *
 vy_cache_entry_new(struct vy_cache_env *env, struct vy_cache *cache,
-		   struct tuple *stmt)
+		   struct tuple *stmt, hint_t hint)
 {
 	struct vy_cache_entry *entry = (struct vy_cache_entry *)
 		mempool_alloc(&env->cache_entry_mempool);
@@ -93,6 +93,7 @@ vy_cache_entry_new(struct vy_cache_env *env, struct vy_cache *cache,
 	tuple_ref(stmt);
 	entry->cache = cache;
 	entry->stmt = stmt;
+	entry->hint = hint;
 	entry->flags = 0;
 	entry->left_boundary_level = cache->cmp_def->part_count;
 	entry->right_boundary_level = cache->cmp_def->part_count;
@@ -173,10 +174,11 @@ vy_cache_gc_step(struct vy_cache_env *env)
 	struct vy_cache_tree *tree = &cache->cache_tree;
 	if (entry->flags & (VY_CACHE_LEFT_LINKED |
 			    VY_CACHE_RIGHT_LINKED)) {
+		struct vy_cache_tree_key tree_key;
+		tree_key = vy_cache_tree_key(entry->stmt, entry->hint);
 		bool exact;
 		struct vy_cache_tree_iterator itr =
-			vy_cache_tree_lower_bound(tree, entry->stmt,
-						  &exact);
+			vy_cache_tree_lower_bound(tree, &tree_key, &exact);
 		assert(exact);
 		if (entry->flags & VY_CACHE_LEFT_LINKED) {
 			struct vy_cache_tree_iterator prev = itr;
@@ -257,6 +259,11 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 		return;
 	}
 
+	hint_t hint = stmt == NULL ? HINT_NONE :
+		      vy_stmt_hint(stmt, cache->cmp_def);
+	hint_t prev_hint = prev_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(prev_stmt, cache->cmp_def);
+
 	int direction = iterator_direction(order);
 	/**
 	 * Let's determine boundary_level (left/right) of the new record
@@ -291,6 +298,7 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 		 */
 		direction = -direction;
 		stmt = prev_stmt;
+		hint = prev_hint;
 		prev_stmt = NULL;
 	}
 	TRASH(&order);
@@ -304,7 +312,7 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 
 	/* Insert/replace new entry to the tree */
 	struct vy_cache_entry *entry =
-		vy_cache_entry_new(cache->env, cache, stmt);
+		vy_cache_entry_new(cache->env, cache, stmt, hint);
 	if (entry == NULL) {
 		/* memory error, let's live without a cache */
 		return;
@@ -370,8 +378,11 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 							&inserted);
 		assert(*prev_check_entry != NULL);
 		struct tuple *prev_check_stmt = (*prev_check_entry)->stmt;
-		int cmp = vy_stmt_compare(prev_stmt, prev_check_stmt,
-					  cache->cmp_def);
+		hint_t prev_check_hint = (*prev_check_entry)->hint;
+		int cmp = vy_stmt_compare_hinted(prev_stmt, prev_hint,
+						 prev_check_stmt,
+						 prev_check_hint,
+						 cache->cmp_def);
 
 		if (entry->flags & flag) {
 			/* The found entry must be exactly prev_stmt. (2) */
@@ -394,7 +405,7 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 
 	/* Insert/replace entry with previous statement */
 	struct vy_cache_entry *prev_entry =
-		vy_cache_entry_new(cache->env, cache, prev_stmt);
+		vy_cache_entry_new(cache->env, cache, prev_stmt, prev_hint);
 	if (prev_entry == NULL) {
 		/* memory error, let's live without a chain */
 		return;
@@ -422,8 +433,11 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 struct tuple *
 vy_cache_get(struct vy_cache *cache, const struct tuple *key)
 {
+	hint_t hint = vy_stmt_hint(key, cache->cmp_def);
+	struct vy_cache_tree_key tree_key;
+	tree_key = vy_cache_tree_key(key, hint);
 	struct vy_cache_entry **entry =
-		vy_cache_tree_find(&cache->cache_tree, key);
+		vy_cache_tree_find(&cache->cache_tree, &tree_key);
 	if (entry == NULL)
 		return NULL;
 	return (*entry)->stmt;
@@ -435,8 +449,11 @@ vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
 {
 	vy_cache_gc(cache->env);
 	bool exact = false;
+	hint_t hint = vy_stmt_hint(stmt, cache->cmp_def);
+	struct vy_cache_tree_key tree_key;
+	tree_key = vy_cache_tree_key(stmt, hint);
 	struct vy_cache_tree_iterator itr;
-	itr = vy_cache_tree_lower_bound(&cache->cache_tree, stmt, &exact);
+	itr = vy_cache_tree_lower_bound(&cache->cache_tree, &tree_key, &exact);
 	struct vy_cache_entry **entry =
 		vy_cache_tree_iterator_get_elem(&cache->cache_tree, &itr);
 	assert(!exact || entry != NULL);
@@ -507,18 +524,6 @@ vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
 }
 
 /**
- * Get a stmt by current position
- */
-static struct tuple *
-vy_cache_iterator_curr_stmt(struct vy_cache_iterator *itr)
-{
-	struct vy_cache_tree *tree = &itr->cache->cache_tree;
-	struct vy_cache_entry **entry =
-		vy_cache_tree_iterator_get_elem(tree, &itr->curr_pos);
-	return entry ? (*entry)->stmt : NULL;
-}
-
-/**
  * Determine whether the merge iterator must be stopped or not.
  * That is made by examining flags of a cache record.
  *
@@ -596,6 +601,7 @@ vy_cache_iterator_step(struct vy_cache_iterator *itr)
 	if (itr->curr_stmt != NULL) {
 		tuple_unref(itr->curr_stmt);
 		itr->curr_stmt = NULL;
+		itr->curr_hint = HINT_NONE;
 	}
 	struct vy_cache_tree *tree = &itr->cache->cache_tree;
 	struct vy_cache_entry *prev_entry =
@@ -610,10 +616,12 @@ vy_cache_iterator_step(struct vy_cache_iterator *itr)
 		*vy_cache_tree_iterator_get_elem(tree, &itr->curr_pos);
 
 	if (itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare(itr->key, entry->stmt, itr->cache->cmp_def)) {
+	    vy_stmt_compare_hinted(itr->key, itr->hint, entry->stmt,
+				   entry->hint, itr->cache->cmp_def)) {
 		return vy_cache_iterator_is_end_stop(itr, prev_entry);
 	}
 	itr->curr_stmt = entry->stmt;
+	itr->curr_hint = entry->hint;
 	tuple_ref(itr->curr_stmt);
 	return vy_cache_iterator_is_stop(itr, entry);
 }
@@ -647,31 +655,33 @@ vy_cache_iterator_skip_to_read_view(struct vy_cache_iterator *itr, bool *stop)
  */
 static bool
 vy_cache_iterator_seek(struct vy_cache_iterator *itr,
-		       const struct tuple *last_key)
+		       const struct tuple *last_key, hint_t last_hint)
 {
 	struct vy_cache_tree *tree = &itr->cache->cache_tree;
 
 	if (itr->curr_stmt != NULL) {
 		tuple_unref(itr->curr_stmt);
 		itr->curr_stmt = NULL;
+		itr->curr_hint = HINT_NONE;
 	}
 	itr->cache->stat.lookup++;
 
-	const struct tuple *key = itr->key;
+	struct vy_cache_tree_key tree_key;
+	tree_key = vy_cache_tree_key(itr->key, itr->hint);
 	enum iterator_type iterator_type = itr->iterator_type;
 	if (last_key != NULL) {
-		key = last_key;
+		tree_key = vy_cache_tree_key(last_key, last_hint);
 		iterator_type = iterator_direction(itr->iterator_type) > 0 ?
 				ITER_GT : ITER_LT;
 	}
 
 	bool exact;
-	if (!vy_stmt_is_empty_key(key)) {
+	if (!vy_stmt_is_empty_key(tree_key.stmt)) {
 		itr->curr_pos = iterator_type == ITER_EQ ||
 				iterator_type == ITER_GE ||
 				iterator_type == ITER_LT ?
-				vy_cache_tree_lower_bound(tree, key, &exact) :
-				vy_cache_tree_upper_bound(tree, key, &exact);
+				vy_cache_tree_lower_bound(tree, &tree_key, &exact) :
+				vy_cache_tree_upper_bound(tree, &tree_key, &exact);
 	} else if (iterator_type == ITER_LE) {
 		itr->curr_pos = vy_cache_tree_invalid_iterator();
 	} else {
@@ -689,11 +699,13 @@ vy_cache_iterator_seek(struct vy_cache_iterator *itr,
 
 	if (itr->iterator_type == ITER_EQ &&
 	    ((last_key == NULL && !exact) ||
-	     (last_key != NULL && vy_stmt_compare(itr->key, entry->stmt,
-						  itr->cache->cmp_def) != 0)))
+	     (last_key != NULL &&
+	      vy_stmt_compare_hinted(itr->key, itr->hint, entry->stmt,
+				     entry->hint, itr->cache->cmp_def) != 0)))
 		return false;
 
 	itr->curr_stmt = entry->stmt;
+	itr->curr_hint = entry->hint;
 	tuple_ref(itr->curr_stmt);
 	return vy_cache_iterator_is_stop(itr, entry);
 }
@@ -708,7 +720,7 @@ vy_cache_iterator_next(struct vy_cache_iterator *itr,
 		assert(itr->curr_stmt == NULL);
 		itr->search_started = true;
 		itr->version = itr->cache->version;
-		*stop = vy_cache_iterator_seek(itr, NULL);
+		*stop = vy_cache_iterator_seek(itr, NULL, HINT_NONE);
 	} else {
 		assert(itr->version == itr->cache->version);
 		if (itr->curr_stmt == NULL)
@@ -732,6 +744,8 @@ vy_cache_iterator_skip(struct vy_cache_iterator *itr,
 {
 	assert(!itr->search_started || itr->version == itr->cache->version);
 
+	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(last_stmt, itr->cache->cmp_def);
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
@@ -739,15 +753,16 @@ vy_cache_iterator_skip(struct vy_cache_iterator *itr,
 	if (itr->search_started &&
 	    (itr->curr_stmt == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare(itr->curr_stmt, last_stmt,
-			     itr->cache->cmp_def) > 0))
+	     vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+				    last_stmt, last_hint,
+				    itr->cache->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
 
 	itr->search_started = true;
 	itr->version = itr->cache->version;
-	*stop = vy_cache_iterator_seek(itr, last_stmt);
+	*stop = vy_cache_iterator_seek(itr, last_stmt, last_hint);
 	vy_cache_iterator_skip_to_read_view(itr, stop);
 
 	if (itr->curr_stmt != NULL) {
@@ -766,17 +781,33 @@ vy_cache_iterator_restore(struct vy_cache_iterator *itr,
 	if (!itr->search_started || itr->version == itr->cache->version)
 		return 0;
 
+	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(last_stmt, itr->cache->cmp_def);
+
+	/* Check if the iterator position is still valid. */
+	bool pos_invalid = false;
+	struct vy_cache_tree *tree = &itr->cache->cache_tree;
+	if (itr->curr_stmt != NULL) {
+		struct vy_cache_entry **entry;
+		entry = vy_cache_tree_iterator_get_elem(tree, &itr->curr_pos);
+		if (entry == NULL || (*entry)->stmt != itr->curr_stmt ||
+				     (*entry)->hint != itr->curr_hint)
+			pos_invalid = true;
+	} else {
+		if (itr->iterator_type != ITER_EQ)
+			itr->curr_pos = vy_cache_tree_invalid_iterator();
+		else
+			pos_invalid = true;
+	}
 	bool pos_changed = false;
 	itr->version = itr->cache->version;
-	if ((itr->curr_stmt == NULL && itr->iterator_type == ITER_EQ) ||
-	    (itr->curr_stmt != NULL &&
-	     itr->curr_stmt != vy_cache_iterator_curr_stmt(itr))) {
+	if (pos_invalid) {
 		/*
 		 * EQ search ended or the iterator was invalidated.
 		 * In either case the best we can do is restart the
 		 * search.
 		 */
-		*stop = vy_cache_iterator_seek(itr, last_stmt);
+		*stop = vy_cache_iterator_seek(itr, last_stmt, last_hint);
 		vy_cache_iterator_skip_to_read_view(itr, stop);
 		pos_changed = true;
 	} else {
@@ -788,18 +819,17 @@ vy_cache_iterator_restore(struct vy_cache_iterator *itr,
 		 */
 		bool key_belongs = false;
 		const struct tuple *key = last_stmt;
+		hint_t hint = last_hint;
 		if (key == NULL) {
 			key = itr->key;
+			hint = itr->hint;
 			key_belongs = (itr->iterator_type == ITER_EQ ||
 				       itr->iterator_type == ITER_GE ||
 				       itr->iterator_type == ITER_LE);
 		}
 		int dir = iterator_direction(itr->iterator_type);
 		struct key_def *def = itr->cache->cmp_def;
-		struct vy_cache_tree *tree = &itr->cache->cache_tree;
 		struct vy_cache_tree_iterator pos = itr->curr_pos;
-		if (itr->curr_stmt == NULL)
-			pos = vy_cache_tree_invalid_iterator();
 		while (true) {
 			if (dir > 0)
 				vy_cache_tree_iterator_prev(tree, &pos);
@@ -809,7 +839,9 @@ vy_cache_iterator_restore(struct vy_cache_iterator *itr,
 				break;
 			struct vy_cache_entry *entry =
 				*vy_cache_tree_iterator_get_elem(tree, &pos);
-			int cmp = dir * vy_stmt_compare(entry->stmt, key, def);
+			int cmp = dir * vy_stmt_compare_hinted(entry->stmt,
+							       entry->hint,
+							       key, hint, def);
 			if (cmp < 0 || (cmp == 0 && !key_belongs))
 				break;
 			if (vy_stmt_lsn(entry->stmt) <= (**itr->read_view).vlsn) {
@@ -817,6 +849,7 @@ vy_cache_iterator_restore(struct vy_cache_iterator *itr,
 				if (itr->curr_stmt != NULL)
 					tuple_unref(itr->curr_stmt);
 				itr->curr_stmt = entry->stmt;
+				itr->curr_hint = entry->hint;
 				tuple_ref(itr->curr_stmt);
 				*stop = vy_cache_iterator_is_stop(itr, entry);
 				pos_changed = true;
@@ -856,9 +889,11 @@ vy_cache_iterator_open(struct vy_cache_iterator *itr, struct vy_cache *cache,
 	itr->cache = cache;
 	itr->iterator_type = iterator_type;
 	itr->key = key;
+	itr->hint = vy_stmt_hint(key, cache->cmp_def);
 	itr->read_view = rv;
 
 	itr->curr_stmt = NULL;
+	itr->curr_hint = HINT_NONE;
 	itr->curr_pos = vy_cache_tree_invalid_iterator();
 
 	itr->version = 0;
diff --git a/src/box/vy_cache.h b/src/box/vy_cache.h
index a846622e..4fcdb747 100644
--- a/src/box/vy_cache.h
+++ b/src/box/vy_cache.h
@@ -56,6 +56,8 @@ struct vy_cache_entry {
 	struct vy_cache *cache;
 	/* Statement in cache */
 	struct tuple *stmt;
+	/* Comparison hint of the cached statement. */
+	hint_t hint;
 	/* Link in LRU list */
 	struct rlist in_lru;
 	/* VY_CACHE_LEFT_LINKED and/or VY_CACHE_RIGHT_LINKED, see
@@ -67,6 +69,20 @@ struct vy_cache_entry {
 	uint8_t right_boundary_level;
 };
 
+struct vy_cache_tree_key {
+	const struct tuple *stmt;
+	hint_t hint;
+};
+
+static inline struct vy_cache_tree_key
+vy_cache_tree_key(const struct tuple *stmt, hint_t hint)
+{
+	struct vy_cache_tree_key key;
+	key.stmt = stmt;
+	key.hint = hint;
+	return key;
+}
+
 /**
  * Internal comparator (1) for BPS tree.
  */
@@ -74,17 +90,19 @@ static inline int
 vy_cache_tree_cmp(struct vy_cache_entry *a,
 		  struct vy_cache_entry *b, struct key_def *cmp_def)
 {
-	return vy_stmt_compare(a->stmt, b->stmt, cmp_def);
+	return vy_stmt_compare_hinted(a->stmt, a->hint,
+				      b->stmt, b->hint, cmp_def);
 }
 
 /**
  * Internal comparator (2) for BPS tree.
  */
 static inline int
-vy_cache_tree_key_cmp(struct vy_cache_entry *a,
-		      const struct tuple *b, struct key_def *cmp_def)
+vy_cache_tree_key_cmp(struct vy_cache_entry *a, struct vy_cache_tree_key *b,
+		      struct key_def *cmp_def)
 {
-	return vy_stmt_compare(a->stmt, b, cmp_def);
+	return vy_stmt_compare_hinted(a->stmt, a->hint,
+				      b->stmt, b->hint, cmp_def);
 }
 
 #define VY_CACHE_TREE_EXTENT_SIZE (16 * 1024)
@@ -95,7 +113,7 @@ vy_cache_tree_key_cmp(struct vy_cache_entry *a,
 #define BPS_TREE_COMPARE(a, b, cmp_def) vy_cache_tree_cmp(a, b, cmp_def)
 #define BPS_TREE_COMPARE_KEY(a, b, cmp_def) vy_cache_tree_key_cmp(a, b, cmp_def)
 #define bps_tree_elem_t struct vy_cache_entry *
-#define bps_tree_key_t const struct tuple *
+#define bps_tree_key_t struct vy_cache_tree_key *
 #define bps_tree_arg_t struct key_def *
 #define BPS_TREE_NO_DEBUG
 
@@ -239,6 +257,8 @@ struct vy_cache_iterator {
 	enum iterator_type iterator_type;
 	/* Search key data in terms of vinyl, vy_stmt_compare argument */
 	const struct tuple *key;
+	/** Comparison hint of the search key. */
+	hint_t hint;
 	/* LSN visibility, iterator shows values with lsn <= vlsn */
 	const struct vy_read_view **read_view;
 
@@ -247,6 +267,8 @@ struct vy_cache_iterator {
 	struct vy_cache_tree_iterator curr_pos;
 	/* stmt in current position in tree */
 	struct tuple *curr_stmt;
+	/* Comparison hint of the current statement. */
+	hint_t curr_hint;
 
 	/* Last version of cache */
 	uint32_t version;
diff --git a/test/vinyl/cache.result b/test/vinyl/cache.result
index 85741604..49d2bcc7 100644
--- a/test/vinyl/cache.result
+++ b/test/vinyl/cache.result
@@ -1033,14 +1033,14 @@ for i = 1, 100 do s:get{i} end
 ...
 box.stat.vinyl().memory.tuple_cache
 ---
-- 107700
+- 108500
 ...
 box.cfg{vinyl_cache = 50 * 1000}
 ---
 ...
 box.stat.vinyl().memory.tuple_cache
 ---
-- 49542
+- 49910
 ...
 box.cfg{vinyl_cache = 0}
 ---
@@ -1116,7 +1116,7 @@ s.index.i2:count()
 ...
 box.stat.vinyl().memory.tuple_cache -- should be about 200 KB
 ---
-- 216800
+- 219200
 ...
 s:drop()
 ---
diff --git a/test/vinyl/stat.result b/test/vinyl/stat.result
index ff73d42a..934e0807 100644
--- a/test/vinyl/stat.result
+++ b/test/vinyl/stat.result
@@ -769,7 +769,7 @@ _ = s:get(1)
 ...
 stat_diff(gstat(), st, 'memory.tuple_cache')
 ---
-- 1101
+- 1109
 ...
 s:delete(1)
 ---
@@ -1122,7 +1122,7 @@ gstat()
     gap_locks: 0
     read_views: 0
   memory:
-    tuple_cache: 14313
+    tuple_cache: 14417
     tx: 0
     level0: 262583
     page_index: 1050
-- 
2.11.0

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

* [PATCH 03/13] vinyl: store tuple comparison hints in tx write set
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
  2019-04-02 17:33 ` [PATCH 01/13] vinyl: store tuple comparison hints in memory tree Vladimir Davydov
  2019-04-02 17:33 ` [PATCH 02/13] vinyl: store tuple comparison hints in cache tree Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 11:41   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 04/13] vinyl: store tuple comparison hints in tx read set Vladimir Davydov
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch incorporates tuple comparison hints into the transaction
write set. Now, beside a statement, each txv also stores its hint,
which is used in all comparison operations.

Apart from speeding up lookups, this is also needed for multikey index
support, because multikey indexes will reuse hints to store offsets of
indexed array entries.
---
 src/box/vy_point_lookup.c | 10 ++++---
 src/box/vy_tx.c           | 70 ++++++++++++++++++++++++++++++-----------------
 src/box/vy_tx.h           |  9 ++++--
 3 files changed, 58 insertions(+), 31 deletions(-)

diff --git a/src/box/vy_point_lookup.c b/src/box/vy_point_lookup.c
index 9e1f3ca7..5f1be274 100644
--- a/src/box/vy_point_lookup.c
+++ b/src/box/vy_point_lookup.c
@@ -53,13 +53,13 @@
  */
 static int
 vy_point_lookup_scan_txw(struct vy_lsm *lsm, struct vy_tx *tx,
-			 struct tuple *key, struct vy_history *history)
+			 struct tuple *key, hint_t hint,
+			 struct vy_history *history)
 {
 	if (tx == NULL)
 		return 0;
 	lsm->stat.txw.iterator.lookup++;
-	struct txv *txv =
-		write_set_search_key(&tx->write_set, lsm, key);
+	struct txv *txv = write_set_search_key(&tx->write_set, lsm, key, hint);
 	assert(txv == NULL || txv->lsm == lsm);
 	if (txv == NULL)
 		return 0;
@@ -212,7 +212,9 @@ vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
 	vy_history_create(&mem_history, &lsm->env->history_node_pool);
 	vy_history_create(&disk_history, &lsm->env->history_node_pool);
 
-	rc = vy_point_lookup_scan_txw(lsm, tx, key, &history);
+	hint_t hint = vy_stmt_hint(key, lsm->cmp_def);
+
+	rc = vy_point_lookup_scan_txw(lsm, tx, key, hint, &history);
 	if (rc != 0 || vy_history_is_terminal(&history))
 		goto done;
 
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index 05f757d8..6a464a1e 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -68,7 +68,9 @@ write_set_cmp(struct txv *a, struct txv *b)
 {
 	int rc = a->lsm < b->lsm ? -1 : a->lsm > b->lsm;
 	if (rc == 0)
-		return vy_stmt_compare(a->stmt, b->stmt, a->lsm->cmp_def);
+		return vy_stmt_compare_hinted(a->stmt, a->hint,
+					      b->stmt, b->hint,
+					      a->lsm->cmp_def);
 	return rc;
 }
 
@@ -77,7 +79,9 @@ write_set_key_cmp(struct write_set_key *a, struct txv *b)
 {
 	int rc = a->lsm < b->lsm ? -1 : a->lsm > b->lsm;
 	if (rc == 0)
-		return vy_stmt_compare(a->stmt, b->stmt, a->lsm->cmp_def);
+		return vy_stmt_compare_hinted(a->stmt, a->hint,
+					      b->stmt, b->hint,
+					      a->lsm->cmp_def);
 	return rc;
 }
 
@@ -213,7 +217,7 @@ tx_manager_destroy_read_view(struct tx_manager *xm,
 
 static struct txv *
 txv_new(struct vy_tx *tx, struct vy_lsm *lsm,
-	struct tuple *stmt, uint64_t column_mask)
+	struct tuple *stmt, hint_t hint, uint64_t column_mask)
 {
 	struct tx_manager *xm = tx->xm;
 	struct txv *v = mempool_alloc(&xm->txv_mempool);
@@ -225,6 +229,7 @@ txv_new(struct vy_tx *tx, struct vy_lsm *lsm,
 	vy_lsm_ref(v->lsm);
 	v->mem = NULL;
 	v->stmt = stmt;
+	v->hint = hint;
 	tuple_ref(stmt);
 	v->region_stmt = NULL;
 	v->column_mask = column_mask;
@@ -626,8 +631,9 @@ vy_tx_handle_deferred_delete(struct vy_tx *tx, struct txv *v)
 	int rc = 0;
 	for (uint32_t i = 1; i < space->index_count; i++) {
 		struct vy_lsm *lsm = vy_lsm(space->index[i]);
+		hint_t hint = vy_stmt_hint(delete_stmt, lsm->cmp_def);
 		struct txv *delete_txv = txv_new(tx, lsm, delete_stmt,
-						 UINT64_MAX);
+						 hint, UINT64_MAX);
 		if (delete_txv == NULL) {
 			rc = -1;
 			break;
@@ -1007,7 +1013,8 @@ vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm, struct tuple *stmt)
 		return 0;
 	}
 
-	struct txv *v = write_set_search_key(&tx->write_set, lsm, stmt);
+	hint_t hint = vy_stmt_hint(stmt, lsm->cmp_def);
+	struct txv *v = write_set_search_key(&tx->write_set, lsm, stmt, hint);
 	if (v != NULL && vy_stmt_type(v->stmt) != IPROTO_UPSERT) {
 		/* Reading from own write set is serializable. */
 		return 0;
@@ -1028,7 +1035,8 @@ vy_tx_set_with_colmask(struct vy_tx *tx, struct vy_lsm *lsm,
 	vy_stmt_set_lsn(stmt, INT64_MAX);
 	struct tuple *applied = NULL;
 
-	struct txv *old = write_set_search_key(&tx->write_set, lsm, stmt);
+	hint_t hint = vy_stmt_hint(stmt, lsm->cmp_def);
+	struct txv *old = write_set_search_key(&tx->write_set, lsm, stmt, hint);
 	/* Found a match of the previous action of this transaction */
 	if (old != NULL && vy_stmt_type(stmt) == IPROTO_UPSERT) {
 		assert(lsm->index_id == 0);
@@ -1049,7 +1057,7 @@ vy_tx_set_with_colmask(struct vy_tx *tx, struct vy_lsm *lsm,
 	}
 
 	/* Allocate a MVCC container. */
-	struct txv *v = txv_new(tx, lsm, stmt, column_mask);
+	struct txv *v = txv_new(tx, lsm, stmt, hint, column_mask);
 	if (applied != NULL)
 		tuple_unref(applied);
 	if (v == NULL)
@@ -1122,7 +1130,7 @@ tx_manager_abort_writers_for_ddl(struct tx_manager *xm, struct space *space)
 			continue;
 		if (tx->last_stmt_space == space ||
 		    write_set_search_key(&tx->write_set, lsm,
-					 lsm->env->empty_key) != NULL)
+					 lsm->env->empty_key, HINT_NONE) != NULL)
 			vy_tx_abort(tx);
 	}
 }
@@ -1150,6 +1158,7 @@ vy_txw_iterator_open(struct vy_txw_iterator *itr,
 	itr->lsm = lsm;
 	itr->iterator_type = iterator_type;
 	itr->key = key;
+	itr->hint = vy_stmt_hint(key, lsm->cmp_def);
 	itr->version = UINT32_MAX;
 	itr->curr_txv = NULL;
 	itr->search_started = false;
@@ -1161,24 +1170,25 @@ vy_txw_iterator_open(struct vy_txw_iterator *itr,
  * given key (pass NULL to start iteration).
  */
 static void
-vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key)
+vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key,
+		     hint_t last_hint)
 {
 	itr->stat->lookup++;
 	itr->version = itr->tx->write_set_version;
 	itr->curr_txv = NULL;
 
-	const struct tuple *key = itr->key;
+	struct vy_lsm *lsm = itr->lsm;
+	struct write_set_key k = { lsm, itr->key, itr->hint };
 	enum iterator_type iterator_type = itr->iterator_type;
 	if (last_key != NULL) {
-		key = last_key;
+		k.stmt = last_key;
+		k.hint = last_hint;
 		iterator_type = iterator_direction(iterator_type) > 0 ?
 				ITER_GT : ITER_LT;
 	}
 
-	struct vy_lsm *lsm = itr->lsm;
-	struct write_set_key k = { lsm, key };
 	struct txv *txv;
-	if (!vy_stmt_is_empty_key(key)) {
+	if (!vy_stmt_is_empty_key(k.stmt)) {
 		if (iterator_type == ITER_EQ)
 			txv = write_set_search(&itr->tx->write_set, &k);
 		else if (iterator_type == ITER_GE || iterator_type == ITER_GT)
@@ -1187,7 +1197,8 @@ vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key)
 			txv = write_set_psearch(&itr->tx->write_set, &k);
 		if (txv == NULL || txv->lsm != lsm)
 			return;
-		if (vy_stmt_compare(key, txv->stmt, lsm->cmp_def) == 0) {
+		if (vy_stmt_compare_hinted(k.stmt, k.hint, txv->stmt, txv->hint,
+					   lsm->cmp_def) == 0) {
 			while (true) {
 				struct txv *next;
 				if (iterator_type == ITER_LE ||
@@ -1197,8 +1208,9 @@ vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key)
 					next = write_set_prev(&itr->tx->write_set, txv);
 				if (next == NULL || next->lsm != lsm)
 					break;
-				if (vy_stmt_compare(key, next->stmt,
-						    lsm->cmp_def) != 0)
+				if (vy_stmt_compare_hinted(k.stmt, k.hint,
+							   next->stmt, next->hint,
+							   lsm->cmp_def) != 0)
 					break;
 				txv = next;
 			}
@@ -1216,7 +1228,8 @@ vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key)
 	if (txv == NULL || txv->lsm != lsm)
 		return;
 	if (itr->iterator_type == ITER_EQ && last_key != NULL &&
-	    vy_stmt_compare(itr->key, txv->stmt, lsm->cmp_def) != 0)
+	    vy_stmt_compare_hinted(itr->key, itr->hint, txv->stmt, txv->hint,
+				   lsm->cmp_def) != 0)
 		return;
 	itr->curr_txv = txv;
 }
@@ -1228,7 +1241,7 @@ vy_txw_iterator_next(struct vy_txw_iterator *itr,
 	vy_history_cleanup(history);
 	if (!itr->search_started) {
 		itr->search_started = true;
-		vy_txw_iterator_seek(itr, NULL);
+		vy_txw_iterator_seek(itr, NULL, HINT_NONE);
 		goto out;
 	}
 	assert(itr->version == itr->tx->write_set_version);
@@ -1241,8 +1254,8 @@ vy_txw_iterator_next(struct vy_txw_iterator *itr,
 	if (itr->curr_txv != NULL && itr->curr_txv->lsm != itr->lsm)
 		itr->curr_txv = NULL;
 	if (itr->curr_txv != NULL && itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare(itr->key, itr->curr_txv->stmt,
-			    itr->lsm->cmp_def) != 0)
+	    vy_stmt_compare_hinted(itr->key, itr->hint, itr->curr_txv->stmt,
+				   itr->curr_txv->hint, itr->lsm->cmp_def) != 0)
 		itr->curr_txv = NULL;
 out:
 	if (itr->curr_txv != NULL) {
@@ -1261,6 +1274,8 @@ vy_txw_iterator_skip(struct vy_txw_iterator *itr,
 	assert(!itr->search_started ||
 	       itr->version == itr->tx->write_set_version);
 
+	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(last_stmt, itr->lsm->cmp_def);
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
@@ -1268,14 +1283,16 @@ vy_txw_iterator_skip(struct vy_txw_iterator *itr,
 	if (itr->search_started &&
 	    (itr->curr_txv == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare(itr->curr_txv->stmt, last_stmt,
-			     itr->lsm->cmp_def) > 0))
+	     vy_stmt_compare_hinted(itr->curr_txv->stmt,
+				    itr->curr_txv->hint,
+				    last_stmt, last_hint,
+				    itr->lsm->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
 
 	itr->search_started = true;
-	vy_txw_iterator_seek(itr, last_stmt);
+	vy_txw_iterator_seek(itr, last_stmt, last_hint);
 
 	if (itr->curr_txv != NULL) {
 		vy_stmt_counter_acct_tuple(&itr->stat->get,
@@ -1293,7 +1310,10 @@ vy_txw_iterator_restore(struct vy_txw_iterator *itr,
 	if (!itr->search_started || itr->version == itr->tx->write_set_version)
 		return 0;
 
-	vy_txw_iterator_seek(itr, last_stmt);
+	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(last_stmt, itr->lsm->cmp_def);
+
+	vy_txw_iterator_seek(itr, last_stmt, last_hint);
 
 	vy_history_cleanup(history);
 	if (itr->curr_txv != NULL) {
diff --git a/src/box/vy_tx.h b/src/box/vy_tx.h
index 93e3a8cd..907957ae 100644
--- a/src/box/vy_tx.h
+++ b/src/box/vy_tx.h
@@ -84,6 +84,8 @@ struct txv {
 	struct vy_mem *mem;
 	/** Statement of this operation. */
 	struct tuple *stmt;
+	/** Statement comparison hint. */
+	hint_t hint;
 	/** Statement allocated on vy_mem->allocator. */
 	const struct tuple *region_stmt;
 	/** Mask of columns modified by this operation. */
@@ -115,6 +117,7 @@ struct txv {
 struct write_set_key {
 	struct vy_lsm *lsm;
 	const struct tuple *stmt;
+	hint_t hint;
 };
 
 int
@@ -128,9 +131,9 @@ rb_gen_ext_key(MAYBE_UNUSED static inline, write_set_, write_set_t, struct txv,
 
 static inline struct txv *
 write_set_search_key(write_set_t *tree, struct vy_lsm *lsm,
-		     const struct tuple *stmt)
+		     const struct tuple *stmt, hint_t hint)
 {
-	struct write_set_key key = { .lsm = lsm, .stmt = stmt };
+	struct write_set_key key = { .lsm = lsm, .stmt = stmt, .hint = hint };
 	return write_set_search(tree, &key);
 }
 
@@ -421,6 +424,8 @@ struct vy_txw_iterator {
 	enum iterator_type iterator_type;
 	/** Search key. */
 	const struct tuple *key;
+	/** Comparison hint of the search key. */
+	hint_t hint;
 	/* Last seen value of the write set version. */
 	uint32_t version;
 	/* Current position in the write set. */
-- 
2.11.0

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

* [PATCH 04/13] vinyl: store tuple comparison hints in tx read set
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (2 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 03/13] vinyl: store tuple comparison hints in tx write set Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 11:42   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 05/13] vinyl: store tuple comparison hints in range tree Vladimir Davydov
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch incorporates tuple comparison hints into the transaction
read set. Now, beside a statement, each read interval also stores hints
of boundary statements, which are used in all comparison operations.

Apart from speeding up lookups, this is also needed for multikey index
support, because multikey indexes will reuse hints to store offsets of
indexed array entries.
---
 src/box/vy_read_set.c | 19 ++++++++++++-------
 src/box/vy_read_set.h | 10 +++++++++-
 src/box/vy_tx.c       | 20 ++++++++++++++------
 3 files changed, 35 insertions(+), 14 deletions(-)

diff --git a/src/box/vy_read_set.c b/src/box/vy_read_set.c
index b95d2e4e..464ff060 100644
--- a/src/box/vy_read_set.c
+++ b/src/box/vy_read_set.c
@@ -46,7 +46,8 @@ vy_read_interval_cmpl(const struct vy_read_interval *a,
 {
 	assert(a->lsm == b->lsm);
 	struct key_def *cmp_def = a->lsm->cmp_def;
-	int cmp = vy_stmt_compare(a->left, b->left, cmp_def);
+	int cmp = vy_stmt_compare_hinted(a->left, a->left_hint,
+					 b->left, b->left_hint, cmp_def);
 	if (cmp != 0)
 		return cmp;
 	if (a->left_belongs && !b->left_belongs)
@@ -67,7 +68,8 @@ vy_read_interval_cmpr(const struct vy_read_interval *a,
 {
 	assert(a->lsm == b->lsm);
 	struct key_def *cmp_def = a->lsm->cmp_def;
-	int cmp = vy_stmt_compare(a->right, b->right, cmp_def);
+	int cmp = vy_stmt_compare_hinted(a->right, a->right_hint,
+					 b->right, b->right_hint, cmp_def);
 	if (cmp != 0)
 		return cmp;
 	if (a->right_belongs && !b->right_belongs)
@@ -89,7 +91,8 @@ vy_read_interval_should_merge(const struct vy_read_interval *l,
 	assert(l->lsm == r->lsm);
 	assert(vy_read_interval_cmpl(l, r) <= 0);
 	struct key_def *cmp_def = l->lsm->cmp_def;
-	int cmp = vy_stmt_compare(l->right, r->left, cmp_def);
+	int cmp = vy_stmt_compare_hinted(l->right, l->right_hint,
+					 r->left, r->left_hint, cmp_def);
 	if (cmp > 0)
 		return true;
 	if (cmp < 0)
@@ -118,7 +121,8 @@ vy_tx_conflict_iterator_next(struct vy_tx_conflict_iterator *it)
 		assert(left == NULL || left->lsm == curr->lsm);
 		assert(right == NULL || right->lsm == curr->lsm);
 
-		int cmp_right = vy_stmt_compare(it->stmt, last->right, cmp_def);
+		int cmp_right = vy_stmt_compare_hinted(it->stmt, it->hint,
+					last->right, last->right_hint, cmp_def);
 		if (cmp_right == 0 && !last->right_belongs)
 			cmp_right = 1;
 
@@ -137,7 +141,8 @@ vy_tx_conflict_iterator_next(struct vy_tx_conflict_iterator *it)
 			/* Optimize comparison out. */
 			cmp_left = cmp_right;
 		} else {
-			cmp_left = vy_stmt_compare(it->stmt, curr->left, cmp_def);
+			cmp_left = vy_stmt_compare_hinted(it->stmt, it->hint,
+					curr->left, curr->left_hint, cmp_def);
 			if (cmp_left == 0 && !curr->left_belongs)
 				cmp_left = -1;
 		}
@@ -164,8 +169,8 @@ vy_tx_conflict_iterator_next(struct vy_tx_conflict_iterator *it)
 			/* Optimize comparison out. */
 			cmp_right = cmp_left;
 		} else if (curr != last) {
-			cmp_right = vy_stmt_compare(it->stmt, curr->right,
-						    cmp_def);
+			cmp_right = vy_stmt_compare_hinted(it->stmt, it->hint,
+					curr->right, curr->right_hint, cmp_def);
 			if (cmp_right == 0 && !curr->right_belongs)
 				cmp_right = 1;
 		}
diff --git a/src/box/vy_read_set.h b/src/box/vy_read_set.h
index 1b139ccf..73d5dd4a 100644
--- a/src/box/vy_read_set.h
+++ b/src/box/vy_read_set.h
@@ -39,6 +39,7 @@
 #define RB_COMPACT 1
 #include <small/rb.h>
 
+#include "key_def.h"
 #include "salad/stailq.h"
 #include "trivia/util.h"
 
@@ -60,8 +61,12 @@ struct vy_read_interval {
 	struct vy_lsm *lsm;
 	/** Left boundary of the interval. */
 	struct tuple *left;
+	/** Left boundary statement hint. */
+	hint_t left_hint;
 	/** Right boundary of the interval. */
 	struct tuple *right;
+	/** Right boundary statement hint. */
+	hint_t right_hint;
 	/** Set if the left boundary belongs to the interval. */
 	bool left_belongs;
 	/** Set if the right boundary belongs to the interval. */
@@ -188,6 +193,8 @@ rb_gen_aug(MAYBE_UNUSED static inline, vy_lsm_read_set_, vy_lsm_read_set_t,
 struct vy_tx_conflict_iterator {
 	/** The statement. */
 	const struct tuple *stmt;
+	/** Statement comparison hint. */
+	hint_t hint;
 	/**
 	 * Iterator over the interval tree checked
 	 * for intersections with the statement.
@@ -203,11 +210,12 @@ struct vy_tx_conflict_iterator {
 static inline void
 vy_tx_conflict_iterator_init(struct vy_tx_conflict_iterator *it,
 			     vy_lsm_read_set_t *read_set,
-			     const struct tuple *stmt)
+			     const struct tuple *stmt, hint_t hint)
 {
 	vy_lsm_read_set_walk_init(&it->tree_walk, read_set);
 	it->tree_dir = 0;
 	it->stmt = stmt;
+	it->hint = hint;
 }
 
 /**
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index 6a464a1e..596c7ed0 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -277,8 +277,8 @@ vy_read_interval_unacct(struct vy_read_interval *interval)
 
 static struct vy_read_interval *
 vy_read_interval_new(struct vy_tx *tx, struct vy_lsm *lsm,
-		     struct tuple *left, bool left_belongs,
-		     struct tuple *right, bool right_belongs)
+		     struct tuple *left, hint_t left_hint, bool left_belongs,
+		     struct tuple *right, hint_t right_hint, bool right_belongs)
 {
 	struct tx_manager *xm = tx->xm;
 	struct vy_read_interval *interval;
@@ -293,9 +293,11 @@ vy_read_interval_new(struct vy_tx *tx, struct vy_lsm *lsm,
 	interval->lsm = lsm;
 	tuple_ref(left);
 	interval->left = left;
+	interval->left_hint = left_hint;
 	interval->left_belongs = left_belongs;
 	tuple_ref(right);
 	interval->right = right;
+	interval->right_hint = right_hint;
 	interval->right_belongs = right_belongs;
 	interval->subtree_last = NULL;
 	vy_read_interval_acct(interval);
@@ -392,7 +394,7 @@ static int
 vy_tx_send_to_read_view(struct vy_tx *tx, struct txv *v)
 {
 	struct vy_tx_conflict_iterator it;
-	vy_tx_conflict_iterator_init(&it, &v->lsm->read_set, v->stmt);
+	vy_tx_conflict_iterator_init(&it, &v->lsm->read_set, v->stmt, v->hint);
 	struct vy_tx *abort;
 	while ((abort = vy_tx_conflict_iterator_next(&it)) != NULL) {
 		/* Don't abort self. */
@@ -420,7 +422,7 @@ static void
 vy_tx_abort_readers(struct vy_tx *tx, struct txv *v)
 {
 	struct vy_tx_conflict_iterator it;
-	vy_tx_conflict_iterator_init(&it, &v->lsm->read_set, v->stmt);
+	vy_tx_conflict_iterator_init(&it, &v->lsm->read_set, v->stmt, v->hint);
 	struct vy_tx *abort;
 	while ((abort = vy_tx_conflict_iterator_next(&it)) != NULL) {
 		/* Don't abort self. */
@@ -928,9 +930,13 @@ vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
 		return 0;
 	}
 
+	hint_t left_hint = vy_stmt_hint(left, lsm->cmp_def);
+	hint_t right_hint = vy_stmt_hint(right, lsm->cmp_def);
+
 	struct vy_read_interval *new_interval;
-	new_interval = vy_read_interval_new(tx, lsm, left, left_belongs,
-					    right, right_belongs);
+	new_interval = vy_read_interval_new(tx, lsm, left, left_hint,
+					    left_belongs, right, right_hint,
+					    right_belongs);
 	if (new_interval == NULL)
 		return -1;
 
@@ -978,6 +984,7 @@ vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
 			tuple_ref(interval->left);
 			tuple_unref(new_interval->left);
 			new_interval->left = interval->left;
+			new_interval->left_hint = interval->left_hint;
 			new_interval->left_belongs = interval->left_belongs;
 		}
 		interval = stailq_last_entry(&merge, struct vy_read_interval,
@@ -986,6 +993,7 @@ vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
 			tuple_ref(interval->right);
 			tuple_unref(new_interval->right);
 			new_interval->right = interval->right;
+			new_interval->right_hint = interval->right_hint;
 			new_interval->right_belongs = interval->right_belongs;
 		}
 		struct vy_read_interval *next_interval;
-- 
2.11.0

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

* [PATCH 05/13] vinyl: store tuple comparison hints in range tree
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (3 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 04/13] vinyl: store tuple comparison hints in tx read set Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-02 17:33 ` [PATCH 06/13] vinyl: store tuple comparison hints in page index Vladimir Davydov
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch incorporates tuple comparison hints into the vy_range_tree.
Now, vy_range->begin and ->end are augmented with hints which are used
for searching a range in an LSM tree.

Although this isn't strictly mandatory for multikey support, since we
only use key statements for range boundaries, which can't store multikey
fields, we still need to be able to look up a hinted tuple in a tree,
so augmenting range boundaries with hints makes the code look more
consistent. Besides, it should speed up LSM tree lookups a little.
---
 src/box/vy_lsm.c            | 44 +++++++++++++++++++++++++++++++-------------
 src/box/vy_point_lookup.c   |  7 ++++---
 src/box/vy_range.c          | 32 ++++++++++++++++++++------------
 src/box/vy_range.h          | 31 +++++++++++++++++++++----------
 src/box/vy_read_iterator.c  |  6 +++++-
 test/unit/vy_point_lookup.c |  3 ++-
 6 files changed, 83 insertions(+), 40 deletions(-)

diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index 1e08c0e1..44973a3a 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -325,8 +325,8 @@ vy_lsm_create(struct vy_lsm *lsm)
 	int64_t id = vy_log_next_id();
 
 	/* Create the initial range. */
-	struct vy_range *range = vy_range_new(vy_log_next_id(), NULL, NULL,
-					      lsm->cmp_def);
+	struct vy_range *range = vy_range_new(vy_log_next_id(), NULL, HINT_NONE,
+					      NULL, HINT_NONE, lsm->cmp_def);
 	if (range == NULL)
 		return -1;
 	assert(lsm->range_count == 0);
@@ -443,6 +443,7 @@ vy_lsm_recover_range(struct vy_lsm *lsm,
 		     struct vy_run_env *run_env, bool force_recovery)
 {
 	struct tuple *begin = NULL, *end = NULL;
+	hint_t begin_hint = HINT_NONE, end_hint = HINT_NONE;
 	struct vy_range *range = NULL;
 
 	if (range_info->begin != NULL) {
@@ -450,22 +451,26 @@ vy_lsm_recover_range(struct vy_lsm *lsm,
 					    range_info->begin);
 		if (begin == NULL)
 			goto out;
+		begin_hint = vy_stmt_hint(begin, lsm->cmp_def);
 	}
 	if (range_info->end != NULL) {
 		end = vy_key_from_msgpack(lsm->env->key_format,
 					  range_info->end);
 		if (end == NULL)
 			goto out;
+		end_hint = vy_stmt_hint(end, lsm->cmp_def);
 	}
 	if (begin != NULL && end != NULL &&
-	    vy_stmt_compare(begin, end, lsm->cmp_def) >= 0) {
+	    vy_stmt_compare_hinted(begin, begin_hint, end, end_hint,
+				   lsm->cmp_def) >= 0) {
 		diag_set(ClientError, ER_INVALID_VYLOG_FILE,
 			 tt_sprintf("begin >= end for range %lld",
 				    (long long)range_info->id));
 		goto out;
 	}
 
-	range = vy_range_new(range_info->id, begin, end, lsm->cmp_def);
+	range = vy_range_new(range_info->id, begin, begin_hint,
+			     end, end_hint, lsm->cmp_def);
 	if (range == NULL)
 		goto out;
 
@@ -584,8 +589,9 @@ vy_lsm_recover(struct vy_lsm *lsm, struct vy_recovery *recovery,
 		 * We need range tree initialized for all LSM trees,
 		 * even for dropped ones.
 		 */
-		struct vy_range *range = vy_range_new(vy_log_next_id(),
-						      NULL, NULL, lsm->cmp_def);
+		struct vy_range *range;
+		range = vy_range_new(vy_log_next_id(), NULL, HINT_NONE,
+				     NULL, HINT_NONE, lsm->cmp_def);
 		if (range == NULL)
 			return -1;
 		vy_lsm_add_range(lsm, range);
@@ -639,8 +645,9 @@ vy_lsm_recover(struct vy_lsm *lsm, struct vy_recovery *recovery,
 		int cmp = 0;
 		if (prev != NULL &&
 		    (prev->end == NULL || range->begin == NULL ||
-		     (cmp = vy_stmt_compare(prev->end, range->begin,
-					    lsm->cmp_def)) != 0)) {
+		     (cmp = vy_stmt_compare_hinted(prev->end, prev->end_hint,
+						   range->begin, range->begin_hint,
+						   lsm->cmp_def)) != 0)) {
 			const char *errmsg = cmp > 0 ?
 				"Nearby ranges %lld and %lld overlap" :
 				"Keys between ranges %lld and %lld not spanned";
@@ -1071,18 +1078,23 @@ vy_lsm_find_range_intersection(struct vy_lsm *lsm,
 		struct vy_range **begin, struct vy_range **end)
 {
 	struct tuple_format *key_format = lsm->env->key_format;
+	struct vy_range_tree_key tree_key;
 	struct tuple *stmt;
 
 	stmt = vy_key_from_msgpack(key_format, min_key);
 	if (stmt == NULL)
 		return -1;
-	*begin = vy_range_tree_psearch(&lsm->range_tree, stmt);
+	tree_key.stmt = stmt;
+	tree_key.hint = vy_stmt_hint(stmt, lsm->cmp_def);
+	*begin = vy_range_tree_psearch(&lsm->range_tree, &tree_key);
 	tuple_unref(stmt);
 
 	stmt = vy_key_from_msgpack(key_format, max_key);
 	if (stmt == NULL)
 		return -1;
-	*end = vy_range_tree_psearch(&lsm->range_tree, stmt);
+	tree_key.stmt = stmt;
+	tree_key.hint = vy_stmt_hint(stmt, lsm->cmp_def);
+	*end = vy_range_tree_psearch(&lsm->range_tree, &tree_key);
 	*end = vy_range_tree_next(&lsm->range_tree, *end);
 	tuple_unref(stmt);
 
@@ -1115,6 +1127,11 @@ vy_lsm_split_range(struct vy_lsm *lsm, struct vy_range *range)
 	keys[1] = split_key;
 	keys[2] = range->end;
 
+	hint_t hints[3];
+	hints[0] = range->begin_hint;
+	hints[1] = vy_stmt_hint(split_key, lsm->cmp_def);
+	hints[2] = range->end_hint;
+
 	/*
 	 * Allocate new ranges and create slices of
 	 * the old range's runs for them.
@@ -1122,8 +1139,8 @@ vy_lsm_split_range(struct vy_lsm *lsm, struct vy_range *range)
 	struct vy_slice *slice, *new_slice;
 	struct vy_range *part, *parts[2] = {NULL, };
 	for (int i = 0; i < n_parts; i++) {
-		part = vy_range_new(vy_log_next_id(), keys[i], keys[i + 1],
-				    lsm->cmp_def);
+		part = vy_range_new(vy_log_next_id(), keys[i], hints[i],
+				    keys[i + 1], hints[i + 1], lsm->cmp_def);
 		if (part == NULL)
 			goto fail;
 		parts[i] = part;
@@ -1209,7 +1226,8 @@ vy_lsm_coalesce_range(struct vy_lsm *lsm, struct vy_range *range)
 		return false;
 
 	struct vy_range *result = vy_range_new(vy_log_next_id(),
-			first->begin, last->end, lsm->cmp_def);
+				first->begin, first->begin_hint,
+				last->end, last->end_hint, lsm->cmp_def);
 	if (result == NULL)
 		goto fail_range;
 
diff --git a/src/box/vy_point_lookup.c b/src/box/vy_point_lookup.c
index 5f1be274..aa0625fb 100644
--- a/src/box/vy_point_lookup.c
+++ b/src/box/vy_point_lookup.c
@@ -161,10 +161,11 @@ vy_point_lookup_scan_slice(struct vy_lsm *lsm, struct vy_slice *slice,
  */
 static int
 vy_point_lookup_scan_slices(struct vy_lsm *lsm, const struct vy_read_view **rv,
-			    struct tuple *key, struct vy_history *history)
+			    struct tuple *key, hint_t hint,
+			    struct vy_history *history)
 {
 	struct vy_range *range = vy_range_tree_find_by_key(&lsm->range_tree,
-							   ITER_EQ, key);
+							   ITER_EQ, key, hint);
 	assert(range != NULL);
 	int slice_count = range->slice_count;
 	struct vy_slice **slices = (struct vy_slice **)
@@ -231,7 +232,7 @@ restart:
 	uint32_t mem_version = lsm->mem->version;
 	uint32_t mem_list_version = lsm->mem_list_version;
 
-	rc = vy_point_lookup_scan_slices(lsm, rv, key, &disk_history);
+	rc = vy_point_lookup_scan_slices(lsm, rv, key, hint, &disk_history);
 	if (rc != 0)
 		goto done;
 
diff --git a/src/box/vy_range.c b/src/box/vy_range.c
index f76615c4..edeb8a5f 100644
--- a/src/box/vy_range.c
+++ b/src/box/vy_range.c
@@ -63,23 +63,25 @@ vy_range_tree_cmp(struct vy_range *range_a, struct vy_range *range_b)
 		return 1;
 
 	assert(range_a->cmp_def == range_b->cmp_def);
-	return vy_stmt_compare(range_a->begin, range_b->begin,
-			       range_a->cmp_def);
+	return vy_stmt_compare_hinted(range_a->begin, range_a->begin_hint,
+				      range_b->begin, range_b->begin_hint,
+				      range_a->cmp_def);
 }
 
 int
-vy_range_tree_key_cmp(const struct tuple *stmt, struct vy_range *range)
+vy_range_tree_key_cmp(struct vy_range_tree_key *key, struct vy_range *range)
 {
 	/* Any key > -inf. */
 	if (range->begin == NULL)
 		return 1;
-	return vy_stmt_compare(stmt, range->begin, range->cmp_def);
+	return vy_stmt_compare_hinted(key->stmt, key->hint, range->begin,
+				      range->begin_hint, range->cmp_def);
 }
 
 struct vy_range *
 vy_range_tree_find_by_key(vy_range_tree_t *tree,
 			  enum iterator_type iterator_type,
-			  const struct tuple *key)
+			  const struct tuple *key, hint_t hint)
 {
 	if (vy_stmt_is_empty_key(key)) {
 		switch (iterator_type) {
@@ -97,6 +99,7 @@ vy_range_tree_find_by_key(vy_range_tree_t *tree,
 		}
 	}
 	struct vy_range *range;
+	struct vy_range_tree_key tree_key = { key, hint };
 	if (iterator_type == ITER_GE || iterator_type == ITER_GT ||
 	    iterator_type == ITER_EQ) {
 		/**
@@ -121,11 +124,13 @@ vy_range_tree_find_by_key(vy_range_tree_t *tree,
 		 * vy_range_tree_psearch finds least range with begin == key
 		 * or previous if equal was not found
 		 */
-		range = vy_range_tree_psearch(tree, key);
+		range = vy_range_tree_psearch(tree, &tree_key);
 		/* switch to previous for case (4) */
 		if (range != NULL && range->begin != NULL &&
 		    !vy_stmt_is_full_key(key, range->cmp_def) &&
-		    vy_stmt_compare(key, range->begin, range->cmp_def) == 0)
+		    vy_stmt_compare_hinted(key, hint, range->begin,
+					   range->begin_hint,
+					   range->cmp_def) == 0)
 			range = vy_range_tree_prev(tree, range);
 		/* for case 5 or subcase of case 4 */
 		if (range == NULL)
@@ -155,12 +160,13 @@ vy_range_tree_find_by_key(vy_range_tree_t *tree,
 		 * vy_range_tree_nsearch finds most range with begin == key
 		 * or next if equal was not found
 		 */
-		range = vy_range_tree_nsearch(tree, key);
+		range = vy_range_tree_nsearch(tree, &tree_key);
 		if (range != NULL) {
 			/* fix curr_range for cases 2 and 3 */
 			if (range->begin != NULL &&
-			    vy_stmt_compare(key, range->begin,
-					    range->cmp_def) != 0) {
+			    vy_stmt_compare_hinted(key, hint, range->begin,
+						   range->begin_hint,
+						   range->cmp_def) != 0) {
 				struct vy_range *prev;
 				prev = vy_range_tree_prev(tree, range);
 				if (prev != NULL)
@@ -175,8 +181,8 @@ vy_range_tree_find_by_key(vy_range_tree_t *tree,
 }
 
 struct vy_range *
-vy_range_new(int64_t id, struct tuple *begin, struct tuple *end,
-	     struct key_def *cmp_def)
+vy_range_new(int64_t id, struct tuple *begin, hint_t begin_hint,
+	     struct tuple *end, hint_t end_hint, struct key_def *cmp_def)
 {
 	struct vy_range *range = calloc(1, sizeof(*range));
 	if (range == NULL) {
@@ -193,6 +199,8 @@ vy_range_new(int64_t id, struct tuple *begin, struct tuple *end,
 		tuple_ref(end);
 		range->end = end;
 	}
+	range->begin_hint = begin_hint;
+	range->end_hint = end_hint;
 	range->cmp_def = cmp_def;
 	rlist_create(&range->slices);
 	heap_node_create(&range->heap_node);
diff --git a/src/box/vy_range.h b/src/box/vy_range.h
index 91f2682c..7b5988bf 100644
--- a/src/box/vy_range.h
+++ b/src/box/vy_range.h
@@ -39,6 +39,7 @@
 #include <small/rlist.h>
 
 #include "iterator_type.h"
+#include "key_def.h"
 #define HEAP_FORWARD_DECLARATION
 #include "salad/heap.h"
 #include "trivia/util.h"
@@ -49,7 +50,6 @@ extern "C" {
 #endif /* defined(__cplusplus) */
 
 struct index_opts;
-struct key_def;
 struct tuple;
 struct vy_slice;
 
@@ -65,8 +65,12 @@ struct vy_range {
 	 * the full idexed key.
 	 */
 	struct tuple *begin;
+	/** Comparison hint of the range lower bound. */
+	hint_t begin_hint;
 	/** Range upper bound. NULL if range is rightmost. */
 	struct tuple *end;
+	/** Comparison hint of the range upper bound. */
+	hint_t end_hint;
 	/** Key definition for comparing range boundaries.
 	 * Contains secondary and primary key parts for secondary
 	 * keys, to ensure an always distinct result for
@@ -164,15 +168,19 @@ vy_range_is_scheduled(struct vy_range *range)
  * vy_range->begin. Ranges in a tree are supposed to span
  * all possible keys without overlaps.
  */
+struct vy_range_tree_key {
+	const struct tuple *stmt;
+	hint_t hint;
+};
 int
 vy_range_tree_cmp(struct vy_range *range_a, struct vy_range *range_b);
 int
-vy_range_tree_key_cmp(const struct tuple *stmt, struct vy_range *range);
+vy_range_tree_key_cmp(struct vy_range_tree_key *key, struct vy_range *range);
 
 typedef rb_tree(struct vy_range) vy_range_tree_t;
 rb_gen_ext_key(MAYBE_UNUSED static inline, vy_range_tree_, vy_range_tree_t,
 	       struct vy_range, tree_node, vy_range_tree_cmp,
-	       const struct tuple *, vy_range_tree_key_cmp);
+	       struct vy_range_tree_key *, vy_range_tree_key_cmp);
 
 /**
  * Find the first range in which a given key should be looked up.
@@ -180,29 +188,32 @@ rb_gen_ext_key(MAYBE_UNUSED static inline, vy_range_tree_, vy_range_tree_t,
  * @param tree          Range tree to search.
  * @param iterator_type Iterator type.
  * @param key           Key to look up.
+ * @param hint          Comparison hint of @key.
  *
  * @retval              The first range to look up the key in.
  */
 struct vy_range *
 vy_range_tree_find_by_key(vy_range_tree_t *tree,
 			  enum iterator_type iterator_type,
-			  const struct tuple *key);
+			  const struct tuple *key, hint_t hint);
 
 /**
  * Allocate and initialize a range (either a new one or for
  * restore from disk).
  *
- * @param id        Range id.
- * @param begin     Range begin (inclusive) or NULL for -inf.
- * @param end       Range end (exclusive) or NULL for +inf.
- * @param cmp_def   Key definition for comparing range boundaries.
+ * @param id          Range id.
+ * @param begin       Range begin (inclusive) or NULL for -inf.
+ * @param begin_hint  Comparison hint of @begin.
+ * @param end         Range end (exclusive) or NULL for +inf.
+ * @param end_hint    Comparison hint of @end.
+ * @param cmp_def     Key definition for comparing range boundaries.
  *
  * @retval not NULL The new range.
  * @retval NULL     Out of memory.
  */
 struct vy_range *
-vy_range_new(int64_t id, struct tuple *begin, struct tuple *end,
-	     struct key_def *cmp_def);
+vy_range_new(int64_t id, struct tuple *begin, hint_t begin_hint,
+	     struct tuple *end, hint_t end_hint, struct key_def *cmp_def);
 
 /**
  * Free a range and all its slices.
diff --git a/src/box/vy_read_iterator.c b/src/box/vy_read_iterator.c
index b5012748..f1d8c8e4 100644
--- a/src/box/vy_read_iterator.c
+++ b/src/box/vy_read_iterator.c
@@ -730,10 +730,14 @@ vy_read_iterator_restore(struct vy_read_iterator *itr)
 {
 	vy_read_iterator_cleanup(itr);
 
+	const struct tuple *key = itr->last_stmt != NULL ?
+				  itr->last_stmt : itr->key;
+	hint_t hint = vy_stmt_hint(key, itr->lsm->cmp_def);
+
 	itr->mem_list_version = itr->lsm->mem_list_version;
 	itr->range_tree_version = itr->lsm->range_tree_version;
 	itr->curr_range = vy_range_tree_find_by_key(&itr->lsm->range_tree,
-			itr->iterator_type, itr->last_stmt ?: itr->key);
+				itr->iterator_type, key, hint);
 	itr->range_version = itr->curr_range->version;
 
 	if (itr->tx != NULL) {
diff --git a/test/unit/vy_point_lookup.c b/test/unit/vy_point_lookup.c
index f3cd84d4..8fb1a8d1 100644
--- a/test/unit/vy_point_lookup.c
+++ b/test/unit/vy_point_lookup.c
@@ -98,7 +98,8 @@ test_basic()
 				       index_def, format, NULL, 0);
 	isnt(pk, NULL, "lsm is not NULL")
 
-	struct vy_range *range = vy_range_new(1, NULL, NULL, pk->cmp_def);
+	struct vy_range *range = vy_range_new(1, NULL, HINT_NONE,
+					      NULL, HINT_NONE, pk->cmp_def);
 
 	isnt(pk, NULL, "range is not NULL")
 	vy_lsm_add_range(pk, range);
-- 
2.11.0

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

* [PATCH 06/13] vinyl: store tuple comparison hints in page index
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (4 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 05/13] vinyl: store tuple comparison hints in range tree Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-02 17:33 ` [PATCH 07/13] vinyl: propagate tuple comparison hints to read iterator Vladimir Davydov
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch incorporates tuple comparison hints into the run iterator
implementation. Now, every page in a page index stores not only the min
key, but also a comparison hint corresponding to the key. We also store
comparison hints for vy_slice->begin and ->end. This should speed up
page index lookups and hence the overall run iterator performance.

It isn't strictly necessary to incorporate hints deep down the run
iterator implementation, since runs can only store keys. However,
we will need to look up a statement in a run by a hinted tuple to
support multikey indexes. Storing hints in runs should make the code
look more consistent then.
---
 src/box/vinyl.c             |   8 +-
 src/box/vy_lsm.c            |  16 ++--
 src/box/vy_run.c            | 207 +++++++++++++++++++++++++++++---------------
 src/box/vy_run.h            |  23 +++--
 src/box/vy_scheduler.c      |   6 +-
 src/box/vy_stmt.h           |  11 ++-
 test/unit/vy_point_lookup.c |   6 +-
 test/vinyl/stat.result      |  22 ++---
 8 files changed, 197 insertions(+), 102 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 77bacd77..80b6aecd 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -2955,11 +2955,13 @@ vy_prepare_send_slice(struct vy_join_ctx *ctx,
 	int rc = -1;
 	struct vy_run *run = NULL;
 	struct tuple *begin = NULL, *end = NULL;
+	hint_t begin_hint = HINT_NONE, end_hint = HINT_NONE;
 
 	run = vy_run_new(&ctx->env->run_env, slice_info->run->id);
 	if (run == NULL)
 		goto out;
-	if (vy_run_recover(run, ctx->env->path, ctx->space_id, 0) != 0)
+	if (vy_run_recover(run, ctx->env->path, ctx->space_id, 0,
+			   ctx->key_def) != 0)
 		goto out;
 
 	if (slice_info->begin != NULL) {
@@ -2967,16 +2969,18 @@ vy_prepare_send_slice(struct vy_join_ctx *ctx,
 					    slice_info->begin);
 		if (begin == NULL)
 			goto out;
+		begin_hint = vy_stmt_hint(begin, ctx->key_def);
 	}
 	if (slice_info->end != NULL) {
 		end = vy_key_from_msgpack(ctx->env->lsm_env.key_format,
 					  slice_info->end);
 		if (end == NULL)
 			goto out;
+		end_hint = vy_stmt_hint(end, ctx->key_def);
 	}
 
 	struct vy_slice *slice = vy_slice_new(slice_info->id, run,
-					      begin, end, ctx->key_def);
+			begin, begin_hint, end, end_hint, ctx->key_def);
 	if (slice == NULL)
 		goto out;
 
diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index 44973a3a..52fd429d 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -365,8 +365,8 @@ vy_lsm_recover_run(struct vy_lsm *lsm, struct vy_run_recovery_info *run_info,
 
 	run->dump_lsn = run_info->dump_lsn;
 	run->dump_count = run_info->dump_count;
-	if (vy_run_recover(run, lsm->env->path,
-			   lsm->space_id, lsm->index_id) != 0 &&
+	if (vy_run_recover(run, lsm->env->path, lsm->space_id,
+			   lsm->index_id, lsm->cmp_def) != 0 &&
 	    (!force_recovery ||
 	     vy_run_rebuild_index(run, lsm->env->path,
 				  lsm->space_id, lsm->index_id,
@@ -396,6 +396,7 @@ vy_lsm_recover_slice(struct vy_lsm *lsm, struct vy_range *range,
 		     struct vy_run_env *run_env, bool force_recovery)
 {
 	struct tuple *begin = NULL, *end = NULL;
+	hint_t begin_hint = HINT_NONE, end_hint = HINT_NONE;
 	struct vy_slice *slice = NULL;
 	struct vy_run *run;
 
@@ -404,15 +405,18 @@ vy_lsm_recover_slice(struct vy_lsm *lsm, struct vy_range *range,
 					    slice_info->begin);
 		if (begin == NULL)
 			goto out;
+		begin_hint = vy_stmt_hint(begin, lsm->cmp_def);
 	}
 	if (slice_info->end != NULL) {
 		end = vy_key_from_msgpack(lsm->env->key_format,
 					  slice_info->end);
 		if (end == NULL)
 			goto out;
+		end_hint = vy_stmt_hint(end, lsm->cmp_def);
 	}
 	if (begin != NULL && end != NULL &&
-	    vy_stmt_compare(begin, end, lsm->cmp_def) >= 0) {
+	    vy_stmt_compare_hinted(begin, begin_hint, end, end_hint,
+				   lsm->cmp_def) >= 0) {
 		diag_set(ClientError, ER_INVALID_VYLOG_FILE,
 			 tt_sprintf("begin >= end for slice %lld",
 				    (long long)slice_info->id));
@@ -424,7 +428,8 @@ vy_lsm_recover_slice(struct vy_lsm *lsm, struct vy_range *range,
 	if (run == NULL)
 		goto out;
 
-	slice = vy_slice_new(slice_info->id, run, begin, end, lsm->cmp_def);
+	slice = vy_slice_new(slice_info->id, run, begin, begin_hint,
+			     end, end_hint, lsm->cmp_def);
 	if (slice == NULL)
 		goto out;
 
@@ -1151,7 +1156,8 @@ vy_lsm_split_range(struct vy_lsm *lsm, struct vy_range *range)
 		 */
 		rlist_foreach_entry_reverse(slice, &range->slices, in_range) {
 			if (vy_slice_cut(slice, vy_log_next_id(), part->begin,
-					 part->end, lsm->cmp_def,
+					 part->begin_hint, part->end,
+					 part->end_hint, lsm->cmp_def,
 					 &new_slice) != 0)
 				goto fail;
 			if (new_slice != NULL)
diff --git a/src/box/vy_run.c b/src/box/vy_run.c
index 818d0cf3..9608e207 100644
--- a/src/box/vy_run.c
+++ b/src/box/vy_run.c
@@ -201,13 +201,17 @@ vy_run_env_enable_coio(struct vy_run_env *env)
  */
 static int
 vy_page_info_create(struct vy_page_info *page_info, uint64_t offset,
-		    const char *min_key)
+		    const char *min_key, struct key_def *cmp_def)
 {
 	memset(page_info, 0, sizeof(*page_info));
 	page_info->offset = offset;
 	page_info->unpacked_size = 0;
 	page_info->min_key = vy_key_dup(min_key);
-	return page_info->min_key == NULL ? -1 : 0;
+	if (page_info->min_key == NULL)
+		return -1;
+	uint32_t part_count = mp_decode_array(&min_key);
+	page_info->hint = key_hint(min_key, part_count, cmp_def);
+	return 0;
 }
 
 /**
@@ -288,18 +292,19 @@ vy_run_bloom_size(struct vy_run *run)
  *  must be started from the beginning of the next page.
  *
  * @param run - run
- * @param key - key to find
- * @param key_def - key_def for comparison
  * @param itype - iterator type (see above)
+ * @param key - key to find
+ * @param hint - comparison hint of the key
+ * @param cmp_def - key_def for comparison
  * @param equal_key: *equal_key is set to true if there is a page
  *  with min_key equal to the given key.
  * @return offset of the page in page index OR run->info.page_count if
  *  there no pages fulfilling the conditions.
  */
 static uint32_t
-vy_page_index_find_page(struct vy_run *run, const struct tuple *key,
-			struct key_def *cmp_def, enum iterator_type itype,
-			bool *equal_key)
+vy_page_index_find_page(struct vy_run *run, enum iterator_type itype,
+			const struct tuple *key, hint_t hint,
+			struct key_def *cmp_def, bool *equal_key)
 {
 	if (itype == ITER_EQ)
 		itype = ITER_GE; /* One day it'll become obsolete */
@@ -338,8 +343,8 @@ vy_page_index_find_page(struct vy_run *run, const struct tuple *key,
 	do {
 		int32_t mid = range[0] + (range[1] - range[0]) / 2;
 		struct vy_page_info *info = vy_run_page_info(run, mid);
-		int cmp = vy_stmt_compare_with_raw_key(key, info->min_key,
-						       cmp_def);
+		int cmp = vy_stmt_compare_with_raw_key_hinted(key, hint,
+					info->min_key, info->hint, cmp_def);
 		if (is_lower_bound)
 			range[cmp <= 0] = mid;
 		else
@@ -362,7 +367,8 @@ vy_page_index_find_page(struct vy_run *run, const struct tuple *key,
 
 struct vy_slice *
 vy_slice_new(int64_t id, struct vy_run *run, struct tuple *begin,
-	     struct tuple *end, struct key_def *cmp_def)
+	     hint_t begin_hint, struct tuple *end, hint_t end_hint,
+	     struct key_def *cmp_def)
 {
 	struct vy_slice *slice = malloc(sizeof(*slice));
 	if (slice == NULL) {
@@ -379,9 +385,11 @@ vy_slice_new(int64_t id, struct vy_run *run, struct tuple *begin,
 	if (begin != NULL)
 		tuple_ref(begin);
 	slice->begin = begin;
+	slice->begin_hint = begin_hint;
 	if (end != NULL)
 		tuple_ref(end);
 	slice->end = end;
+	slice->end_hint = end_hint;
 	rlist_create(&slice->in_range);
 	fiber_cond_create(&slice->pin_cond);
 	if (run->info.page_count == 0) {
@@ -394,16 +402,18 @@ vy_slice_new(int64_t id, struct vy_run *run, struct tuple *begin,
 		slice->first_page_no = 0;
 	} else {
 		slice->first_page_no =
-			vy_page_index_find_page(run, slice->begin, cmp_def,
-						ITER_GE, &unused);
+			vy_page_index_find_page(run, ITER_GE, slice->begin,
+						slice->begin_hint, cmp_def,
+						&unused);
 		assert(slice->first_page_no < run->info.page_count);
 	}
 	if (slice->end == NULL) {
 		slice->last_page_no = run->info.page_count - 1;
 	} else {
 		slice->last_page_no =
-			vy_page_index_find_page(run, slice->end, cmp_def,
-						ITER_LT, &unused);
+			vy_page_index_find_page(run, ITER_LT, slice->end,
+						slice->end_hint, cmp_def,
+						&unused);
 		if (slice->last_page_no == run->info.page_count) {
 			/* It's an empty slice */
 			slice->first_page_no = 0;
@@ -443,30 +453,35 @@ vy_slice_delete(struct vy_slice *slice)
 
 int
 vy_slice_cut(struct vy_slice *slice, int64_t id, struct tuple *begin,
-	     struct tuple *end, struct key_def *cmp_def,
-	     struct vy_slice **result)
+	     hint_t begin_hint, struct tuple *end, hint_t end_hint,
+	     struct key_def *cmp_def, struct vy_slice **result)
 {
 	*result = NULL;
 
 	if (begin != NULL && slice->end != NULL &&
-	    vy_stmt_compare(begin, slice->end, cmp_def) >= 0)
+	    vy_stmt_compare_hinted(begin, begin_hint, slice->end,
+				   slice->end_hint, cmp_def) >= 0)
 		return 0; /* no intersection: begin >= slice->end */
 
 	if (end != NULL && slice->begin != NULL &&
-	    vy_stmt_compare(end, slice->begin, cmp_def) <= 0)
+	    vy_stmt_compare_hinted(end, end_hint, slice->begin,
+				   slice->begin_hint, cmp_def) <= 0)
 		return 0; /* no intersection: end <= slice->end */
 
 	/* begin = MAX(begin, slice->begin) */
 	if (slice->begin != NULL &&
-	    (begin == NULL || vy_stmt_compare(begin, slice->begin, cmp_def) < 0))
+	    (begin == NULL || vy_stmt_compare_hinted(begin, begin_hint,
+			slice->begin, slice->begin_hint, cmp_def) < 0))
 		begin = slice->begin;
 
 	/* end = MIN(end, slice->end) */
 	if (slice->end != NULL &&
-	    (end == NULL || vy_stmt_compare(end, slice->end, cmp_def) > 0))
+	    (end == NULL || vy_stmt_compare_hinted(end, end_hint,
+			slice->end, slice->end_hint, cmp_def) > 0))
 		end = slice->end;
 
-	*result = vy_slice_new(id, slice->run, begin, end, cmp_def);
+	*result = vy_slice_new(id, slice->run, begin, begin_hint,
+			       end, end_hint, cmp_def);
 	if (*result == NULL)
 		return -1; /* OOM */
 
@@ -478,6 +493,7 @@ vy_slice_cut(struct vy_slice *slice, int64_t id, struct tuple *begin,
  *
  * @param[out] page Page information.
  * @param xrow      Xrow to decode.
+ * @param cmp_def   Definition of keys stored in the page.
  * @param filename  Filename for error reporting.
  *
  * @retval  0 Success.
@@ -485,7 +501,7 @@ vy_slice_cut(struct vy_slice *slice, int64_t id, struct tuple *begin,
  */
 static int
 vy_page_info_decode(struct vy_page_info *page, const struct xrow_header *xrow,
-		    const char *filename)
+		    struct key_def *cmp_def, const char *filename)
 {
 	assert(xrow->type == VY_INDEX_PAGE_INFO);
 	const char *pos = xrow->body->iov_base;
@@ -494,6 +510,7 @@ vy_page_info_decode(struct vy_page_info *page, const struct xrow_header *xrow,
 	uint32_t map_size = mp_decode_map(&pos);
 	uint32_t map_item;
 	const char *key_beg;
+	uint32_t part_count;
 	for (map_item = 0; map_item < map_size; ++map_item) {
 		uint32_t key = mp_decode_uint(&pos);
 		key_map &= ~(1ULL << key);
@@ -513,6 +530,8 @@ vy_page_info_decode(struct vy_page_info *page, const struct xrow_header *xrow,
 			page->min_key = vy_key_dup(key_beg);
 			if (page->min_key == NULL)
 				return -1;
+			part_count = mp_decode_array(&key_beg);
+			page->hint = key_hint(key_beg, part_count, cmp_def);
 			break;
 		case VY_PAGE_INFO_UNPACKED_SIZE:
 			page->unpacked_size = mp_decode_uint(&pos);
@@ -707,19 +726,24 @@ vy_page_xrow(struct vy_page *page, uint32_t stmt_no,
  * Read raw stmt data from the page
  * @param page          Page.
  * @param stmt_no       Statement position in the page.
+ * @param cmp_def       Definition of keys stored in the page.
  * @param format        Format for REPLACE/DELETE tuples.
+ * @param[out] hint     Comparison hint of the loaded statement.
  *
  * @retval not NULL Statement read from page.
  * @retval     NULL Memory error.
  */
 static struct tuple *
-vy_page_stmt(struct vy_page *page, uint32_t stmt_no,
-	     struct tuple_format *format)
+vy_page_stmt(struct vy_page *page, uint32_t stmt_no, struct key_def *cmp_def,
+	     struct tuple_format *format, hint_t *hint)
 {
 	struct xrow_header xrow;
 	if (vy_page_xrow(page, stmt_no, &xrow) != 0)
 		return NULL;
-	return vy_stmt_decode(&xrow, format);
+	struct tuple *stmt = vy_stmt_decode(&xrow, format);
+	if (stmt != NULL)
+		*hint = vy_stmt_hint(stmt, cmp_def);
+	return stmt;
 }
 
 /**
@@ -731,6 +755,7 @@ vy_run_iterator_stop(struct vy_run_iterator *itr)
 	if (itr->curr_stmt != NULL) {
 		tuple_unref(itr->curr_stmt);
 		itr->curr_stmt = NULL;
+		itr->curr_hint = HINT_NONE;
 	}
 	if (itr->curr_page != NULL) {
 		vy_page_delete(itr->curr_page);
@@ -1015,13 +1040,14 @@ vy_run_iterator_load_page(struct vy_run_iterator *itr, uint32_t page_no,
 static NODISCARD int
 vy_run_iterator_read(struct vy_run_iterator *itr,
 		     struct vy_run_iterator_pos pos,
-		     struct tuple **stmt)
+		     struct tuple **stmt, hint_t *hint)
 {
 	struct vy_page *page;
 	int rc = vy_run_iterator_load_page(itr, pos.page_no, &page);
 	if (rc != 0)
 		return rc;
-	*stmt = vy_page_stmt(page, pos.pos_in_page, itr->format);
+	*stmt = vy_page_stmt(page, pos.pos_in_page, itr->cmp_def,
+			     itr->format, hint);
 	if (*stmt == NULL)
 		return -1;
 	return 0;
@@ -1037,7 +1063,7 @@ vy_run_iterator_read(struct vy_run_iterator *itr,
 static uint32_t
 vy_run_iterator_search_in_page(struct vy_run_iterator *itr,
 			       enum iterator_type iterator_type,
-			       const struct tuple *key,
+			       const struct tuple *key, hint_t hint,
 			       struct vy_page *page, bool *equal_key)
 {
 	uint32_t beg = 0;
@@ -1047,10 +1073,13 @@ vy_run_iterator_search_in_page(struct vy_run_iterator *itr,
 			iterator_type == ITER_LE ? -1 : 0);
 	while (beg != end) {
 		uint32_t mid = beg + (end - beg) / 2;
-		struct tuple *fnd_key = vy_page_stmt(page, mid, itr->format);
+		hint_t fnd_hint;
+		struct tuple *fnd_key = vy_page_stmt(page, mid, itr->cmp_def,
+						     itr->format, &fnd_hint);
 		if (fnd_key == NULL)
 			return end;
-		int cmp = vy_stmt_compare(fnd_key, key, itr->cmp_def);
+		int cmp = vy_stmt_compare_hinted(fnd_key, fnd_hint,
+						 key, hint, itr->cmp_def);
 		cmp = cmp ? cmp : zero_cmp;
 		*equal_key = *equal_key || cmp == 0;
 		if (cmp < 0)
@@ -1076,12 +1105,11 @@ vy_run_iterator_search_in_page(struct vy_run_iterator *itr,
 static NODISCARD int
 vy_run_iterator_search(struct vy_run_iterator *itr,
 		       enum iterator_type iterator_type,
-		       const struct tuple *key,
+		       const struct tuple *key, hint_t hint,
 		       struct vy_run_iterator_pos *pos, bool *equal_key)
 {
-	pos->page_no = vy_page_index_find_page(itr->slice->run, key,
-					       itr->cmp_def, iterator_type,
-					       equal_key);
+	pos->page_no = vy_page_index_find_page(itr->slice->run, iterator_type,
+					       key, hint, itr->cmp_def, equal_key);
 	if (pos->page_no == itr->slice->run->info.page_count)
 		return 1;
 	struct vy_page *page;
@@ -1090,7 +1118,7 @@ vy_run_iterator_search(struct vy_run_iterator *itr,
 		return rc;
 	bool equal_in_page = false;
 	pos->pos_in_page = vy_run_iterator_search_in_page(itr, iterator_type,
-							  key, page,
+							  key, hint, page,
 							  &equal_in_page);
 	if (pos->pos_in_page == page->row_count) {
 		pos->page_no++;
@@ -1176,11 +1204,12 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr, struct tuple **ret)
 		}
 		tuple_unref(itr->curr_stmt);
 		itr->curr_stmt = NULL;
-		if (vy_run_iterator_read(itr, itr->curr_pos,
-					 &itr->curr_stmt) != 0)
+		if (vy_run_iterator_read(itr, itr->curr_pos, &itr->curr_stmt,
+					 &itr->curr_hint) != 0)
 			return -1;
 		if (itr->iterator_type == ITER_EQ &&
-		    vy_stmt_compare(itr->curr_stmt, itr->key, cmp_def) != 0) {
+		    vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+					   itr->key, itr->hint, cmp_def) != 0) {
 			vy_run_iterator_stop(itr);
 			return 0;
 		}
@@ -1189,26 +1218,32 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr, struct tuple **ret)
 		struct vy_run_iterator_pos test_pos;
 		while (vy_run_iterator_next_pos(itr, itr->iterator_type,
 						&test_pos) == 0) {
+			hint_t test_hint;
 			struct tuple *test_stmt;
-			if (vy_run_iterator_read(itr, test_pos,
-						 &test_stmt) != 0)
+			if (vy_run_iterator_read(itr, test_pos, &test_stmt,
+						 &test_hint) != 0)
 				return -1;
 			if (vy_stmt_lsn(test_stmt) > (**itr->read_view).vlsn ||
 			    vy_stmt_flags(test_stmt) & VY_STMT_SKIP_READ ||
-			    vy_stmt_compare(itr->curr_stmt, test_stmt,
-					    cmp_def) != 0) {
+			    vy_stmt_compare_hinted(itr->curr_stmt,
+						   itr->curr_hint,
+						   test_stmt, test_hint,
+						   cmp_def) != 0) {
 				tuple_unref(test_stmt);
 				break;
 			}
 			tuple_unref(itr->curr_stmt);
 			itr->curr_stmt = test_stmt;
+			itr->curr_hint = test_hint;
 			itr->curr_pos = test_pos;
 		}
 	}
 	/* Check if the result is within the slice boundaries. */
 	if (itr->iterator_type == ITER_LE || itr->iterator_type == ITER_LT) {
 		if (slice->begin != NULL &&
-		    vy_stmt_compare(itr->curr_stmt, slice->begin, cmp_def) < 0) {
+		    vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+					   slice->begin, slice->begin_hint,
+					   cmp_def) < 0) {
 			vy_run_iterator_stop(itr);
 			return 0;
 		}
@@ -1217,7 +1252,9 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr, struct tuple **ret)
 		       itr->iterator_type == ITER_GT ||
 		       itr->iterator_type == ITER_EQ);
 		if (slice->end != NULL &&
-		    vy_stmt_compare(itr->curr_stmt, slice->end, cmp_def) >= 0) {
+		    vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+					   slice->end, slice->end_hint,
+					   cmp_def) >= 0) {
 			vy_run_iterator_stop(itr);
 			return 0;
 		}
@@ -1243,13 +1280,13 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr, struct tuple **ret)
 static NODISCARD int
 vy_run_iterator_do_seek(struct vy_run_iterator *itr,
 			enum iterator_type iterator_type,
-			const struct tuple *key)
+			const struct tuple *key, hint_t hint)
 {
 	struct vy_run *run = itr->slice->run;
 	struct vy_run_iterator_pos end_pos = {run->info.page_count, 0};
 	bool equal_found = false;
 	if (!vy_stmt_is_empty_key(key)) {
-		int rc = vy_run_iterator_search(itr, iterator_type, key,
+		int rc = vy_run_iterator_search(itr, iterator_type, key, hint,
 						&itr->curr_pos, &equal_found);
 		if (rc != 0)
 			return rc;
@@ -1296,12 +1333,13 @@ vy_run_iterator_do_seek(struct vy_run_iterator *itr,
  */
 static NODISCARD int
 vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
-		     struct tuple **ret)
+		     hint_t last_hint, struct tuple **ret)
 {
 	struct key_def *cmp_def = itr->cmp_def;
 	struct vy_slice *slice = itr->slice;
 	struct tuple_bloom *bloom = slice->run->info.bloom;
 	const struct tuple *key = itr->key;
+	hint_t hint = itr->hint;
 	enum iterator_type iterator_type = itr->iterator_type;
 
 	*ret = NULL;
@@ -1334,6 +1372,7 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 		iterator_type = iterator_direction(iterator_type) > 0 ?
 				ITER_GT : ITER_LT;
 		key = last_key;
+		hint = last_hint;
 	}
 
 	/* Take slice boundaries into account. */
@@ -1353,7 +1392,8 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 		 *         | ge  | begin | ge  |
 		 *         | eq  |    stop     |
 		 */
-		int cmp = vy_stmt_compare(key, slice->begin, cmp_def);
+		int cmp = vy_stmt_compare_hinted(key, hint, slice->begin,
+						 slice->begin_hint, cmp_def);
 		if (cmp < 0 && iterator_type == ITER_EQ) {
 			vy_run_iterator_stop(itr);
 			return 0;
@@ -1363,6 +1403,7 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 				check_eq = true;
 			iterator_type = ITER_GE;
 			key = slice->begin;
+			hint = slice->begin_hint;
 		}
 	}
 	if (slice->end != NULL &&
@@ -1378,16 +1419,18 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 		 * > end   | lt  | end   | lt  |
 		 *         | le  | end   | lt  |
 		 */
-		int cmp = vy_stmt_compare(key, slice->end, cmp_def);
+		int cmp = vy_stmt_compare_hinted(key, hint, slice->end,
+						 slice->end_hint, cmp_def);
 		if (cmp > 0 || (cmp == 0 && iterator_type != ITER_LT)) {
 			iterator_type = ITER_LT;
 			key = slice->end;
+			hint = slice->end_hint;
 		}
 	}
 
 	/* Perform a lookup in the run. */
 	itr->stat->lookup++;
-	int rc = vy_run_iterator_do_seek(itr, iterator_type, key);
+	int rc = vy_run_iterator_do_seek(itr, iterator_type, key, hint);
 	if (rc < 0)
 		return -1;
 	if (rc > 0)
@@ -1397,13 +1440,15 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 	if (itr->curr_stmt != NULL) {
 		tuple_unref(itr->curr_stmt);
 		itr->curr_stmt = NULL;
+		itr->curr_hint = HINT_NONE;
 	}
-	if (vy_run_iterator_read(itr, itr->curr_pos, &itr->curr_stmt) != 0)
+	if (vy_run_iterator_read(itr, itr->curr_pos, &itr->curr_stmt,
+				 &itr->curr_hint) != 0)
 		return -1;
 
 	/* Check EQ constraint if necessary. */
-	if (check_eq && vy_stmt_compare(itr->curr_stmt, itr->key,
-					itr->cmp_def) != 0)
+	if (check_eq && vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+					itr->key, itr->hint, itr->cmp_def) != 0)
 		goto not_found;
 
 	/* Skip statements invisible from the iterator read view. */
@@ -1436,9 +1481,11 @@ vy_run_iterator_open(struct vy_run_iterator *itr,
 
 	itr->iterator_type = iterator_type;
 	itr->key = key;
+	itr->hint = vy_stmt_hint(key, cmp_def);
 	itr->read_view = rv;
 
 	itr->curr_stmt = NULL;
+	itr->curr_hint = HINT_NONE;
 	itr->curr_pos.page_no = slice->run->info.page_count;
 	itr->curr_page = NULL;
 	itr->prev_page = NULL;
@@ -1467,13 +1514,14 @@ vy_run_iterator_next_key(struct vy_run_iterator *itr, struct tuple **ret)
 
 	if (!itr->search_started) {
 		itr->search_started = true;
-		return vy_run_iterator_seek(itr, NULL, ret);
+		return vy_run_iterator_seek(itr, NULL, HINT_NONE, ret);
 	}
 	if (itr->curr_stmt == NULL)
 		return 0;
 
 	assert(itr->curr_pos.page_no < itr->slice->run->info.page_count);
 
+	hint_t next_hint = HINT_NONE;
 	struct tuple *next_key = NULL;
 	do {
 		if (next_key != NULL)
@@ -1484,15 +1532,19 @@ vy_run_iterator_next_key(struct vy_run_iterator *itr, struct tuple **ret)
 			return 0;
 		}
 
-		if (vy_run_iterator_read(itr, itr->curr_pos, &next_key) != 0)
+		if (vy_run_iterator_read(itr, itr->curr_pos,
+					 &next_key, &next_hint) != 0)
 			return -1;
-	} while (vy_stmt_compare(itr->curr_stmt, next_key, itr->cmp_def) == 0);
+	} while (vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+					next_key, next_hint, itr->cmp_def) == 0);
 
 	tuple_unref(itr->curr_stmt);
 	itr->curr_stmt = next_key;
+	itr->curr_hint = next_hint;
 
 	if (itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare(next_key, itr->key, itr->cmp_def) != 0) {
+	    vy_stmt_compare_hinted(next_key, next_hint, itr->key, itr->hint,
+				   itr->cmp_def) != 0) {
 		vy_run_iterator_stop(itr);
 		return 0;
 	}
@@ -1520,17 +1572,20 @@ next:
 		return 0;
 	}
 
+	hint_t next_hint;
 	struct tuple *next_key;
-	if (vy_run_iterator_read(itr, next_pos, &next_key) != 0)
+	if (vy_run_iterator_read(itr, next_pos, &next_key, &next_hint) != 0)
 		return -1;
 
-	if (vy_stmt_compare(itr->curr_stmt, next_key, itr->cmp_def) != 0) {
+	if (vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+				   next_key, next_hint, itr->cmp_def) != 0) {
 		tuple_unref(next_key);
 		return 0;
 	}
 
 	tuple_unref(itr->curr_stmt);
 	itr->curr_stmt = next_key;
+	itr->curr_hint = next_hint;
 	itr->curr_pos = next_pos;
 	if (vy_stmt_flags(itr->curr_stmt) & VY_STMT_SKIP_READ)
 		goto next;
@@ -1564,6 +1619,8 @@ vy_run_iterator_skip(struct vy_run_iterator *itr,
 		     const struct tuple *last_stmt,
 		     struct vy_history *history)
 {
+	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
+			   vy_stmt_hint(last_stmt, itr->cmp_def);
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
@@ -1571,14 +1628,15 @@ vy_run_iterator_skip(struct vy_run_iterator *itr,
 	if (itr->search_started &&
 	    (itr->curr_stmt == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare(itr->curr_stmt, last_stmt, itr->cmp_def) > 0))
+	     vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+				    last_stmt, last_hint, itr->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
 
 	itr->search_started = true;
 	struct tuple *stmt;
-	if (vy_run_iterator_seek(itr, last_stmt, &stmt) != 0)
+	if (vy_run_iterator_seek(itr, last_stmt, last_hint, &stmt) != 0)
 		return -1;
 
 	while (stmt != NULL) {
@@ -1617,8 +1675,8 @@ vy_run_acct_page(struct vy_run *run, struct vy_page_info *page)
 }
 
 int
-vy_run_recover(struct vy_run *run, const char *dir,
-	       uint32_t space_id, uint32_t iid)
+vy_run_recover(struct vy_run *run, const char *dir, uint32_t space_id,
+	       uint32_t iid, struct key_def *cmp_def)
 {
 	char path[PATH_MAX];
 	vy_run_snprint_path(path, sizeof(path), dir,
@@ -1704,7 +1762,7 @@ vy_run_recover(struct vy_run *run, const char *dir,
 			goto fail_close;
 		}
 		struct vy_page_info *page = run->page_info + page_no;
-		if (vy_page_info_decode(page, &xrow, path) < 0) {
+		if (vy_page_info_decode(page, &xrow, cmp_def, path) < 0) {
 			/**
 			 * Limit the count of pages to successfully
 			 * created pages
@@ -2148,7 +2206,8 @@ vy_run_writer_start_page(struct vy_run_writer *writer,
 			return -1;
 	}
 	struct vy_page_info *page = run->page_info + run->info.page_count;
-	if (vy_page_info_create(page, writer->data_xlog.offset, key) != 0)
+	if (vy_page_info_create(page, writer->data_xlog.offset,
+				key, writer->cmp_def) != 0)
 		return -1;
 	xlog_tx_begin(&writer->data_xlog);
 	return 0;
@@ -2425,7 +2484,8 @@ vy_run_rebuild_index(struct vy_run *run, const char *dir,
 		}
 		struct vy_page_info *info;
 		info = run->page_info + run->info.page_count;
-		if (vy_page_info_create(info, page_offset, page_min_key) != 0)
+		if (vy_page_info_create(info, page_offset,
+					page_min_key, cmp_def) != 0)
 			goto close_err;
 		info->row_count = page_row_count;
 		info->size = next_page_offset - page_offset;
@@ -2570,12 +2630,15 @@ vy_slice_stream_search(struct vy_stmt_stream *virt_stream)
 	uint32_t end = stream->page->row_count;
 	while (beg != end) {
 		uint32_t mid = beg + (end - beg) / 2;
+		hint_t fnd_hint;
 		struct tuple *fnd_key = vy_page_stmt(stream->page, mid,
-						     stream->format);
+				stream->cmp_def, stream->format, &fnd_hint);
 		if (fnd_key == NULL)
 			return -1;
-		int cmp = vy_stmt_compare(fnd_key, stream->slice->begin,
-					  stream->cmp_def);
+		int cmp = vy_stmt_compare_hinted(fnd_key, fnd_hint,
+						 stream->slice->begin,
+						 stream->slice->begin_hint,
+						 stream->cmp_def);
 		if (cmp < 0)
 			beg = mid + 1;
 		else
@@ -2617,15 +2680,17 @@ vy_slice_stream_next(struct vy_stmt_stream *virt_stream, struct tuple **ret)
 		return -1;
 
 	/* Read current tuple from the page */
+	hint_t hint;
 	struct tuple *tuple = vy_page_stmt(stream->page, stream->pos_in_page,
-					   stream->format);
+					stream->cmp_def, stream->format, &hint);
 	if (tuple == NULL) /* Read or memory error */
 		return -1;
 
 	/* Check that the tuple is not out of slice bounds = */
 	if (stream->slice->end != NULL &&
 	    stream->page_no >= stream->slice->last_page_no &&
-	    vy_stmt_compare(tuple, stream->slice->end, stream->cmp_def) >= 0) {
+	    vy_stmt_compare_hinted(tuple, hint, stream->slice->end,
+			stream->slice->end_hint, stream->cmp_def) >= 0) {
 		tuple_unref(tuple);
 		return 0;
 	}
diff --git a/src/box/vy_run.h b/src/box/vy_run.h
index cbc51c50..369221ff 100644
--- a/src/box/vy_run.h
+++ b/src/box/vy_run.h
@@ -105,6 +105,8 @@ struct vy_page_info {
 	uint32_t row_count;
 	/** Minimal key stored in the page. */
 	char *min_key;
+	/** Comparison hint of @min_key. */
+	hint_t hint;
 	/** Offset of the row index in the page. */
 	uint32_t row_index_offset;
 };
@@ -185,6 +187,11 @@ struct vy_slice {
 	struct tuple *begin;
 	struct tuple *end;
 	/**
+	 * Comparison hints of @begin and @end tuples.
+	 */
+	hint_t begin_hint;
+	hint_t end_hint;
+	/**
 	 * Random seed used for compaction randomization.
 	 * Lays in range [0, RAND_MAX].
 	 */
@@ -260,6 +267,8 @@ struct vy_run_iterator {
 	enum iterator_type iterator_type;
 	/** Key to search. */
 	const struct tuple *key;
+	/** Comparison hint of the search key. */
+	hint_t hint;
 	/* LSN visibility, iterator shows values with lsn <= vlsn */
 	const struct vy_read_view **read_view;
 
@@ -268,6 +277,8 @@ struct vy_run_iterator {
 	struct vy_run_iterator_pos curr_pos;
 	/** Statement at curr_pos. */
 	struct tuple *curr_stmt;
+	/** Comparison hint of the current statement. */
+	hint_t curr_hint;
 	/**
 	 * Last two pages read by the iterator. We keep two pages
 	 * rather than just one, because we often probe a page for
@@ -374,11 +385,12 @@ vy_run_unref(struct vy_run *run)
  * @param dir - path to the vinyl directory
  * @param space_id - space id
  * @param iid - index id
+ * @param cmp_def - definition of keys stored in the run
  * @return - 0 on sucess, -1 on fail
  */
 int
-vy_run_recover(struct vy_run *run, const char *dir,
-	       uint32_t space_id, uint32_t iid);
+vy_run_recover(struct vy_run *run, const char *dir, uint32_t space_id,
+	       uint32_t iid, struct key_def *cmp_def);
 
 /**
  * Rebuild run index
@@ -453,7 +465,8 @@ vy_run_remove_files(const char *dir, uint32_t space_id,
  */
 struct vy_slice *
 vy_slice_new(int64_t id, struct vy_run *run, struct tuple *begin,
-	     struct tuple *end, struct key_def *cmp_def);
+	     hint_t begin_hint, struct tuple *end, hint_t end_hint,
+	     struct key_def *cmp_def);
 
 /**
  * Free a run slice.
@@ -504,8 +517,8 @@ vy_slice_wait_pinned(struct vy_slice *slice)
  */
 int
 vy_slice_cut(struct vy_slice *slice, int64_t id, struct tuple *begin,
-	     struct tuple *end, struct key_def *cmp_def,
-	     struct vy_slice **result);
+	     hint_t begin_hint, struct tuple *end, hint_t end_hint,
+	     struct key_def *cmp_def, struct vy_slice **result);
 
 /**
  * Open an iterator over on-disk run.
diff --git a/src/box/vy_scheduler.c b/src/box/vy_scheduler.c
index aaae1a89..723bc9ee 100644
--- a/src/box/vy_scheduler.c
+++ b/src/box/vy_scheduler.c
@@ -1147,7 +1147,8 @@ vy_task_dump_complete(struct vy_task *task)
 	for (range = begin_range, i = 0; range != end_range;
 	     range = vy_range_tree_next(&lsm->range_tree, range), i++) {
 		slice = vy_slice_new(vy_log_next_id(), new_run,
-				     range->begin, range->end, lsm->cmp_def);
+				     range->begin, range->begin_hint,
+				     range->end, range->end_hint, lsm->cmp_def);
 		if (slice == NULL)
 			goto fail_free_slices;
 
@@ -1458,7 +1459,8 @@ vy_task_compaction_complete(struct vy_task *task)
 	 */
 	if (!vy_run_is_empty(new_run)) {
 		new_slice = vy_slice_new(vy_log_next_id(), new_run,
-					 NULL, NULL, lsm->cmp_def);
+					 NULL, HINT_NONE, NULL, HINT_NONE,
+					 lsm->cmp_def);
 		if (new_slice == NULL)
 			return -1;
 	}
diff --git a/src/box/vy_stmt.h b/src/box/vy_stmt.h
index 928ae348..8467c2d7 100644
--- a/src/box/vy_stmt.h
+++ b/src/box/vy_stmt.h
@@ -438,14 +438,17 @@ vy_stmt_compare_hinted(const struct tuple *a, hint_t a_hint,
  * (msgpack array).
  */
 static inline int
-vy_stmt_compare_with_raw_key(const struct tuple *stmt, const char *key,
-			     struct key_def *key_def)
+vy_stmt_compare_with_raw_key_hinted(const struct tuple *stmt, hint_t stmt_hint,
+				    const char *key, hint_t key_hint,
+				    struct key_def *key_def)
 {
 	if (!vy_stmt_is_key(stmt)) {
 		uint32_t part_count = mp_decode_array(&key);
-		return tuple_compare_with_key(stmt, key, part_count, key_def);
+		return tuple_compare_with_key_hinted(stmt, stmt_hint, key,
+						part_count, key_hint, key_def);
 	}
-	return key_compare(tuple_data(stmt), key, key_def);
+	return key_compare_hinted(tuple_data(stmt), stmt_hint,
+				  key, key_hint, key_def);
 }
 
 /**
diff --git a/test/unit/vy_point_lookup.c b/test/unit/vy_point_lookup.c
index 8fb1a8d1..bb9798a9 100644
--- a/test/unit/vy_point_lookup.c
+++ b/test/unit/vy_point_lookup.c
@@ -206,7 +206,8 @@ test_basic()
 	vy_mem_delete(run_mem);
 
 	vy_lsm_add_run(pk, run);
-	struct vy_slice *slice = vy_slice_new(1, run, NULL, NULL, pk->cmp_def);
+	struct vy_slice *slice = vy_slice_new(1, run, NULL, HINT_NONE,
+					      NULL, HINT_NONE, pk->cmp_def);
 	vy_range_add_slice(range, slice);
 	vy_run_unref(run);
 
@@ -236,7 +237,8 @@ test_basic()
 	vy_mem_delete(run_mem);
 
 	vy_lsm_add_run(pk, run);
-	slice = vy_slice_new(1, run, NULL, NULL, pk->cmp_def);
+	slice = vy_slice_new(1, run, NULL, HINT_NONE,
+			     NULL, HINT_NONE, pk->cmp_def);
 	vy_range_add_slice(range, slice);
 	vy_run_unref(run);
 
diff --git a/test/vinyl/stat.result b/test/vinyl/stat.result
index 934e0807..f49394f7 100644
--- a/test/vinyl/stat.result
+++ b/test/vinyl/stat.result
@@ -323,7 +323,7 @@ stat_diff(istat(), st)
         bytes_compressed: <bytes_compressed>
         rows: 25
     bytes: 26049
-    index_size: 294
+    index_size: 350
     pages: 7
     bytes_compressed: <bytes_compressed>
     bloom_size: 70
@@ -370,7 +370,7 @@ stat_diff(istat(), st)
         bytes_compressed: <bytes_compressed>
         rows: 50
     bytes: 26042
-    index_size: 252
+    index_size: 300
     pages: 6
     bytes_compressed: <bytes_compressed>
     compaction:
@@ -1046,7 +1046,7 @@ istat()
         bytes: 0
       count: 0
     bloom_size: 140
-    index_size: 1050
+    index_size: 1250
     iterator:
       read:
         bytes_compressed: <bytes_compressed>
@@ -1125,12 +1125,12 @@ gstat()
     tuple_cache: 14417
     tx: 0
     level0: 262583
-    page_index: 1050
+    page_index: 1250
     bloom_filter: 140
   disk:
     data_compacted: 104300
     data: 104300
-    index: 1190
+    index: 1390
   scheduler:
     tasks_inprogress: 0
     dump_output: 0
@@ -1338,8 +1338,8 @@ i1:len(), i2:len()
 ...
 i1:bsize(), i2:bsize()
 ---
-- 364
-- 920
+- 420
+- 928
 ...
 s:bsize() == st1.disk.bytes
 ---
@@ -1400,8 +1400,8 @@ i1:len(), i2:len()
 ...
 i1:bsize(), i2:bsize()
 ---
-- 49516
-- 50072
+- 49572
+- 50080
 ...
 s:bsize() == st1.memory.bytes + st1.disk.bytes
 ---
@@ -1465,8 +1465,8 @@ i1:len(), i2:len()
 ...
 i1:bsize(), i2:bsize()
 ---
-- 364
-- 920
+- 420
+- 928
 ...
 s:bsize() == st1.disk.bytes
 ---
-- 
2.11.0

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

* [PATCH 07/13] vinyl: propagate tuple comparison hints to read iterator
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (5 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 06/13] vinyl: store tuple comparison hints in page index Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 11:43   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 08/13] vinyl: propagate tuple comparison hints to write iterator Vladimir Davydov
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch propagates hints from source iterators (mem, run, txw, cache)
to the read iterator code. Now, source iterators take and return
comparison hints along with statements. The read iterator uses the hints
to speed up tuple comparisons.

Apart from speeding up lookups, this is also needed for multikey index
support, because multikey indexes will reuse hints to store offsets of
indexed array entries.
---
 src/box/vy_cache.c         |  28 ++++----
 src/box/vy_cache.h         |  11 +--
 src/box/vy_history.c       |   9 ++-
 src/box/vy_history.h       |  15 ++--
 src/box/vy_mem.c           |  16 ++---
 src/box/vy_mem.h           |   7 +-
 src/box/vy_point_lookup.c  |  53 ++++++++------
 src/box/vy_read_iterator.c | 170 +++++++++++++++++++++++++++------------------
 src/box/vy_read_iterator.h |   5 ++
 src/box/vy_run.c           |  47 ++++++++-----
 src/box/vy_run.h           |   5 +-
 src/box/vy_tx.c            |  22 +++---
 src/box/vy_tx.h            |   6 +-
 test/unit/vy_cache.c       |  12 ++--
 test/unit/vy_mem.c         |  22 +++---
 15 files changed, 255 insertions(+), 173 deletions(-)

diff --git a/src/box/vy_cache.c b/src/box/vy_cache.c
index 8fafd648..a4a767bc 100644
--- a/src/box/vy_cache.c
+++ b/src/box/vy_cache.c
@@ -431,15 +431,16 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 }
 
 struct tuple *
-vy_cache_get(struct vy_cache *cache, const struct tuple *key)
+vy_cache_get(struct vy_cache *cache, const struct tuple *key,
+	     hint_t hint, hint_t *ret_hint)
 {
-	hint_t hint = vy_stmt_hint(key, cache->cmp_def);
 	struct vy_cache_tree_key tree_key;
 	tree_key = vy_cache_tree_key(key, hint);
 	struct vy_cache_entry **entry =
 		vy_cache_tree_find(&cache->cache_tree, &tree_key);
 	if (entry == NULL)
 		return NULL;
+	*ret_hint = (*entry)->hint;
 	return (*entry)->stmt;
 }
 
@@ -732,20 +733,19 @@ vy_cache_iterator_next(struct vy_cache_iterator *itr,
 	if (itr->curr_stmt != NULL) {
 		vy_stmt_counter_acct_tuple(&itr->cache->stat.get,
 					   itr->curr_stmt);
-		return vy_history_append_stmt(history, itr->curr_stmt);
+		return vy_history_append_stmt(history, itr->curr_stmt,
+					      itr->curr_hint);
 	}
 	return 0;
 }
 
 NODISCARD int
 vy_cache_iterator_skip(struct vy_cache_iterator *itr,
-		       const struct tuple *last_stmt,
+		       const struct tuple *last_stmt, hint_t last_hint,
 		       struct vy_history *history, bool *stop)
 {
 	assert(!itr->search_started || itr->version == itr->cache->version);
 
-	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(last_stmt, itr->cache->cmp_def);
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
@@ -768,22 +768,20 @@ vy_cache_iterator_skip(struct vy_cache_iterator *itr,
 	if (itr->curr_stmt != NULL) {
 		vy_stmt_counter_acct_tuple(&itr->cache->stat.get,
 					   itr->curr_stmt);
-		return vy_history_append_stmt(history, itr->curr_stmt);
+		return vy_history_append_stmt(history, itr->curr_stmt,
+					      itr->curr_hint);
 	}
 	return 0;
 }
 
 NODISCARD int
 vy_cache_iterator_restore(struct vy_cache_iterator *itr,
-			  const struct tuple *last_stmt,
+			  const struct tuple *last_stmt, hint_t last_hint,
 			  struct vy_history *history, bool *stop)
 {
 	if (!itr->search_started || itr->version == itr->cache->version)
 		return 0;
 
-	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(last_stmt, itr->cache->cmp_def);
-
 	/* Check if the iterator position is still valid. */
 	bool pos_invalid = false;
 	struct vy_cache_tree *tree = &itr->cache->cache_tree;
@@ -865,7 +863,8 @@ vy_cache_iterator_restore(struct vy_cache_iterator *itr,
 	if (itr->curr_stmt != NULL) {
 		vy_stmt_counter_acct_tuple(&itr->cache->stat.get,
 					   itr->curr_stmt);
-		if (vy_history_append_stmt(history, itr->curr_stmt) != 0)
+		if (vy_history_append_stmt(history, itr->curr_stmt,
+					   itr->curr_hint) != 0)
 			return -1;
 	}
 	return 1;
@@ -884,12 +883,13 @@ vy_cache_iterator_close(struct vy_cache_iterator *itr)
 void
 vy_cache_iterator_open(struct vy_cache_iterator *itr, struct vy_cache *cache,
 		       enum iterator_type iterator_type,
-		       const struct tuple *key, const struct vy_read_view **rv)
+		       const struct tuple *key, hint_t hint,
+		       const struct vy_read_view **rv)
 {
 	itr->cache = cache;
 	itr->iterator_type = iterator_type;
 	itr->key = key;
-	itr->hint = vy_stmt_hint(key, cache->cmp_def);
+	itr->hint = hint;
 	itr->read_view = rv;
 
 	itr->curr_stmt = NULL;
diff --git a/src/box/vy_cache.h b/src/box/vy_cache.h
index 4fcdb747..d256f32c 100644
--- a/src/box/vy_cache.h
+++ b/src/box/vy_cache.h
@@ -228,7 +228,8 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
  * @return A tuple equal to key or NULL if not found.
  */
 struct tuple *
-vy_cache_get(struct vy_cache *cache, const struct tuple *key);
+vy_cache_get(struct vy_cache *cache, const struct tuple *key,
+	     hint_t hint, hint_t *ret_hint);
 
 /**
  * Invalidate possibly cached value due to its overwriting
@@ -282,12 +283,14 @@ struct vy_cache_iterator {
  * @param cache - the cache.
  * @param iterator_type - iterator type (EQ, GT, GE, LT, LE or ALL)
  * @param key - search key data in terms of vinyl, vy_stmt_compare argument
+ * @param hint - comparison hint of the search key
  * @param vlsn - LSN visibility, iterator shows values with lsn <= vlsn
  */
 void
 vy_cache_iterator_open(struct vy_cache_iterator *itr, struct vy_cache *cache,
 		       enum iterator_type iterator_type,
-		       const struct tuple *key, const struct vy_read_view **rv);
+		       const struct tuple *key, hint_t hint,
+		       const struct vy_read_view **rv);
 
 /**
  * Advance a cache iterator to the next key.
@@ -308,7 +311,7 @@ vy_cache_iterator_next(struct vy_cache_iterator *itr,
  */
 NODISCARD int
 vy_cache_iterator_skip(struct vy_cache_iterator *itr,
-		       const struct tuple *last_stmt,
+		       const struct tuple *last_stmt, hint_t last_hint,
 		       struct vy_history *history, bool *stop);
 
 /**
@@ -319,7 +322,7 @@ vy_cache_iterator_skip(struct vy_cache_iterator *itr,
  */
 NODISCARD int
 vy_cache_iterator_restore(struct vy_cache_iterator *itr,
-			  const struct tuple *last_stmt,
+			  const struct tuple *last_stmt, hint_t last_hint,
 			  struct vy_history *history, bool *stop);
 
 /**
diff --git a/src/box/vy_history.c b/src/box/vy_history.c
index b45c6d51..2cbc1695 100644
--- a/src/box/vy_history.c
+++ b/src/box/vy_history.c
@@ -43,7 +43,8 @@
 #include "vy_upsert.h"
 
 int
-vy_history_append_stmt(struct vy_history *history, struct tuple *stmt)
+vy_history_append_stmt(struct vy_history *history,
+		       struct tuple *stmt, hint_t hint)
 {
 	assert(history->pool->objsize == sizeof(struct vy_history_node));
 	struct vy_history_node *node = mempool_alloc(history->pool);
@@ -56,6 +57,7 @@ vy_history_append_stmt(struct vy_history *history, struct tuple *stmt)
 	if (node->is_refable)
 		tuple_ref(stmt);
 	node->stmt = stmt;
+	node->hint = hint;
 	rlist_add_tail_entry(&history->stmts, node, link);
 	return 0;
 }
@@ -74,9 +76,11 @@ vy_history_cleanup(struct vy_history *history)
 
 int
 vy_history_apply(struct vy_history *history, struct key_def *cmp_def,
-		 bool keep_delete, int *upserts_applied, struct tuple **ret)
+		 bool keep_delete, int *upserts_applied,
+		 struct tuple **ret, hint_t *ret_hint)
 {
 	*ret = NULL;
+	*ret_hint = HINT_NONE;
 	*upserts_applied = 0;
 	if (rlist_empty(&history->stmts))
 		return 0;
@@ -84,6 +88,7 @@ vy_history_apply(struct vy_history *history, struct key_def *cmp_def,
 	struct tuple *curr_stmt = NULL;
 	struct vy_history_node *node = rlist_last_entry(&history->stmts,
 					struct vy_history_node, link);
+	*ret_hint = node->hint;
 	if (vy_history_is_terminal(history)) {
 		if (!keep_delete && vy_stmt_type(node->stmt) == IPROTO_DELETE) {
 			/*
diff --git a/src/box/vy_history.h b/src/box/vy_history.h
index 458ea749..94070415 100644
--- a/src/box/vy_history.h
+++ b/src/box/vy_history.h
@@ -64,6 +64,8 @@ struct vy_history_node {
 	struct rlist link;
 	/** History statement. Referenced if @is_refable is set. */
 	struct tuple *stmt;
+	/** Comparison hint of the history statement. */
+	hint_t hint;
 	/**
 	 * Set if the statement stored in this node is refable,
 	 * i.e. has a reference counter that can be incremented
@@ -114,13 +116,16 @@ vy_history_is_terminal(struct vy_history *history)
  * key history or NULL if the history is empty.
  */
 static inline struct tuple *
-vy_history_last_stmt(struct vy_history *history)
+vy_history_last_stmt(struct vy_history *history, hint_t *hint)
 {
-	if (rlist_empty(&history->stmts))
+	if (rlist_empty(&history->stmts)) {
+		*hint = HINT_NONE;
 		return NULL;
+	}
 	/* Newest statement is at the head of the list. */
 	struct vy_history_node *node = rlist_first_entry(&history->stmts,
 					struct vy_history_node, link);
+	*hint = node->hint;
 	return node->stmt;
 }
 
@@ -139,7 +144,8 @@ vy_history_splice(struct vy_history *dst, struct vy_history *src)
  * Returns 0 on success, -1 on memory allocation error.
  */
 int
-vy_history_append_stmt(struct vy_history *history, struct tuple *stmt);
+vy_history_append_stmt(struct vy_history *history,
+		       struct tuple *stmt, hint_t hint);
 
 /**
  * Release all statements stored in the given history and
@@ -155,7 +161,8 @@ vy_history_cleanup(struct vy_history *history);
  */
 int
 vy_history_apply(struct vy_history *history, struct key_def *cmp_def,
-		 bool keep_delete, int *upserts_applied, struct tuple **ret);
+		 bool keep_delete, int *upserts_applied,
+		 struct tuple **ret, hint_t *ret_hint);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/box/vy_mem.c b/src/box/vy_mem.c
index 220751d4..aea0ecfb 100644
--- a/src/box/vy_mem.c
+++ b/src/box/vy_mem.c
@@ -467,7 +467,8 @@ vy_mem_iterator_seek(struct vy_mem_iterator *itr, const struct tuple *last_key,
 void
 vy_mem_iterator_open(struct vy_mem_iterator *itr, struct vy_mem_iterator_stat *stat,
 		     struct vy_mem *mem, enum iterator_type iterator_type,
-		     const struct tuple *key, const struct vy_read_view **rv)
+		     const struct tuple *key, hint_t hint,
+		     const struct vy_read_view **rv)
 {
 	itr->stat = stat;
 
@@ -476,7 +477,7 @@ vy_mem_iterator_open(struct vy_mem_iterator *itr, struct vy_mem_iterator_stat *s
 
 	itr->iterator_type = iterator_type;
 	itr->key = key;
-	itr->hint = vy_stmt_hint(key, mem->cmp_def);
+	itr->hint = hint;
 	itr->read_view = rv;
 
 	itr->curr_pos = vy_mem_tree_invalid_iterator();
@@ -574,7 +575,7 @@ vy_mem_iterator_get_history(struct vy_mem_iterator *itr,
 	do {
 		struct tuple *stmt = (struct tuple *)itr->curr.stmt;
 		vy_stmt_counter_acct_tuple(&itr->stat->get, stmt);
-		if (vy_history_append_stmt(history, stmt) != 0)
+		if (vy_history_append_stmt(history, stmt, itr->curr.hint) != 0)
 			return -1;
 		if (vy_history_is_terminal(history))
 			break;
@@ -594,14 +595,11 @@ vy_mem_iterator_next(struct vy_mem_iterator *itr,
 
 NODISCARD int
 vy_mem_iterator_skip(struct vy_mem_iterator *itr,
-		     const struct tuple *last_stmt,
+		     const struct tuple *last_stmt, hint_t last_hint,
 		     struct vy_history *history)
 {
 	assert(!itr->search_started || itr->version == itr->mem->version);
 
-	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(last_stmt, itr->mem->cmp_def);
-
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
@@ -621,14 +619,12 @@ vy_mem_iterator_skip(struct vy_mem_iterator *itr,
 
 NODISCARD int
 vy_mem_iterator_restore(struct vy_mem_iterator *itr,
-			const struct tuple *last_stmt,
+			const struct tuple *last_stmt, hint_t last_hint,
 			struct vy_history *history)
 {
 	if (!itr->search_started || itr->version == itr->mem->version)
 		return 0;
 
-	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(last_stmt, itr->mem->cmp_def);
 	vy_mem_iterator_seek(itr, last_stmt, last_hint);
 
 	vy_history_cleanup(history);
diff --git a/src/box/vy_mem.h b/src/box/vy_mem.h
index 160bf5e1..f31deaed 100644
--- a/src/box/vy_mem.h
+++ b/src/box/vy_mem.h
@@ -407,7 +407,8 @@ struct vy_mem_iterator {
 void
 vy_mem_iterator_open(struct vy_mem_iterator *itr, struct vy_mem_iterator_stat *stat,
 		     struct vy_mem *mem, enum iterator_type iterator_type,
-		     const struct tuple *key, const struct vy_read_view **rv);
+		     const struct tuple *key, hint_t hint,
+		     const struct vy_read_view **rv);
 
 /**
  * Advance a mem iterator to the next key.
@@ -425,7 +426,7 @@ vy_mem_iterator_next(struct vy_mem_iterator *itr,
  */
 NODISCARD int
 vy_mem_iterator_skip(struct vy_mem_iterator *itr,
-		     const struct tuple *last_stmt,
+		     const struct tuple *last_stmt, hint_t last_hint,
 		     struct vy_history *history);
 
 /**
@@ -436,7 +437,7 @@ vy_mem_iterator_skip(struct vy_mem_iterator *itr,
  */
 NODISCARD int
 vy_mem_iterator_restore(struct vy_mem_iterator *itr,
-			const struct tuple *last_stmt,
+			const struct tuple *last_stmt, hint_t last_hint,
 			struct vy_history *history);
 
 /**
diff --git a/src/box/vy_point_lookup.c b/src/box/vy_point_lookup.c
index aa0625fb..25125662 100644
--- a/src/box/vy_point_lookup.c
+++ b/src/box/vy_point_lookup.c
@@ -65,7 +65,7 @@ vy_point_lookup_scan_txw(struct vy_lsm *lsm, struct vy_tx *tx,
 		return 0;
 	vy_stmt_counter_acct_tuple(&lsm->stat.txw.iterator.get,
 				   txv->stmt);
-	return vy_history_append_stmt(history, txv->stmt);
+	return vy_history_append_stmt(history, txv->stmt, txv->hint);
 }
 
 /**
@@ -74,16 +74,18 @@ vy_point_lookup_scan_txw(struct vy_lsm *lsm, struct vy_tx *tx,
  */
 static int
 vy_point_lookup_scan_cache(struct vy_lsm *lsm, const struct vy_read_view **rv,
-			   struct tuple *key, struct vy_history *history)
+			   struct tuple *key, hint_t hint,
+			   struct vy_history *history)
 {
 	lsm->cache.stat.lookup++;
-	struct tuple *stmt = vy_cache_get(&lsm->cache, key);
+	hint_t stmt_hint;
+	struct tuple *stmt = vy_cache_get(&lsm->cache, key, hint, &stmt_hint);
 
 	if (stmt == NULL || vy_stmt_lsn(stmt) > (*rv)->vlsn)
 		return 0;
 
 	vy_stmt_counter_acct_tuple(&lsm->cache.stat.get, stmt);
-	return vy_history_append_stmt(history, stmt);
+	return vy_history_append_stmt(history, stmt, stmt_hint);
 }
 
 /**
@@ -92,12 +94,12 @@ vy_point_lookup_scan_cache(struct vy_lsm *lsm, const struct vy_read_view **rv,
  */
 static int
 vy_point_lookup_scan_mem(struct vy_lsm *lsm, struct vy_mem *mem,
-			 const struct vy_read_view **rv,
-			 struct tuple *key, struct vy_history *history)
+			 const struct vy_read_view **rv, struct tuple *key,
+			 hint_t hint, struct vy_history *history)
 {
 	struct vy_mem_iterator mem_itr;
 	vy_mem_iterator_open(&mem_itr, &lsm->stat.memory.iterator,
-			     mem, ITER_EQ, key, rv);
+			     mem, ITER_EQ, key, hint, rv);
 	struct vy_history mem_history;
 	vy_history_create(&mem_history, &lsm->env->history_node_pool);
 	int rc = vy_mem_iterator_next(&mem_itr, &mem_history);
@@ -113,16 +115,17 @@ vy_point_lookup_scan_mem(struct vy_lsm *lsm, struct vy_mem *mem,
  */
 static int
 vy_point_lookup_scan_mems(struct vy_lsm *lsm, const struct vy_read_view **rv,
-			  struct tuple *key, struct vy_history *history)
+			  struct tuple *key, hint_t hint,
+			  struct vy_history *history)
 {
 	assert(lsm->mem != NULL);
-	int rc = vy_point_lookup_scan_mem(lsm, lsm->mem, rv, key, history);
+	int rc = vy_point_lookup_scan_mem(lsm, lsm->mem, rv, key, hint, history);
 	struct vy_mem *mem;
 	rlist_foreach_entry(mem, &lsm->sealed, in_sealed) {
 		if (rc != 0 || vy_history_is_terminal(history))
 			return rc;
 
-		rc = vy_point_lookup_scan_mem(lsm, mem, rv, key, history);
+		rc = vy_point_lookup_scan_mem(lsm, mem, rv, key, hint, history);
 	}
 	return 0;
 }
@@ -134,7 +137,7 @@ vy_point_lookup_scan_mems(struct vy_lsm *lsm, const struct vy_read_view **rv,
 static int
 vy_point_lookup_scan_slice(struct vy_lsm *lsm, struct vy_slice *slice,
 			   const struct vy_read_view **rv, struct tuple *key,
-			   struct vy_history *history)
+			   hint_t hint, struct vy_history *history)
 {
 	/*
 	 * The format of the statement must be exactly the space
@@ -143,7 +146,7 @@ vy_point_lookup_scan_slice(struct vy_lsm *lsm, struct vy_slice *slice,
 	 */
 	struct vy_run_iterator run_itr;
 	vy_run_iterator_open(&run_itr, &lsm->stat.disk.iterator, slice,
-			     ITER_EQ, key, rv, lsm->cmp_def, lsm->key_def,
+			     ITER_EQ, key, hint, rv, lsm->cmp_def, lsm->key_def,
 			     lsm->disk_format);
 	struct vy_history slice_history;
 	vy_history_create(&slice_history, &lsm->env->history_node_pool);
@@ -185,8 +188,8 @@ vy_point_lookup_scan_slices(struct vy_lsm *lsm, const struct vy_read_view **rv,
 	int rc = 0;
 	for (i = 0; i < slice_count; i++) {
 		if (rc == 0 && !vy_history_is_terminal(history))
-			rc = vy_point_lookup_scan_slice(lsm, slices[i],
-							rv, key, history);
+			rc = vy_point_lookup_scan_slice(lsm, slices[i], rv,
+							key, hint, history);
 		vy_slice_unpin(slices[i]);
 	}
 	return rc;
@@ -219,12 +222,12 @@ vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
 	if (rc != 0 || vy_history_is_terminal(&history))
 		goto done;
 
-	rc = vy_point_lookup_scan_cache(lsm, rv, key, &history);
+	rc = vy_point_lookup_scan_cache(lsm, rv, key, hint, &history);
 	if (rc != 0 || vy_history_is_terminal(&history))
 		goto done;
 
 restart:
-	rc = vy_point_lookup_scan_mems(lsm, rv, key, &mem_history);
+	rc = vy_point_lookup_scan_mems(lsm, rv, key, hint, &mem_history);
 	if (rc != 0 || vy_history_is_terminal(&mem_history))
 		goto done;
 
@@ -276,7 +279,7 @@ restart:
 		 * matching the search key.
 		 */
 		vy_history_cleanup(&mem_history);
-		rc = vy_point_lookup_scan_mems(lsm, rv, key, &mem_history);
+		rc = vy_point_lookup_scan_mems(lsm, rv, key, hint, &mem_history);
 		if (rc != 0)
 			goto done;
 		if (vy_history_is_terminal(&mem_history))
@@ -288,9 +291,10 @@ done:
 	vy_history_splice(&history, &disk_history);
 
 	if (rc == 0) {
+		hint_t unused;
 		int upserts_applied;
-		rc = vy_history_apply(&history, lsm->cmp_def,
-				      false, &upserts_applied, ret);
+		rc = vy_history_apply(&history, lsm->cmp_def, false,
+				      &upserts_applied, ret, &unused);
 		lsm->stat.upsert.applied += upserts_applied;
 	}
 	vy_history_cleanup(&history);
@@ -323,11 +327,13 @@ vy_point_lookup_mem(struct vy_lsm *lsm, const struct vy_read_view **rv,
 	struct vy_history history;
 	vy_history_create(&history, &lsm->env->history_node_pool);
 
-	rc = vy_point_lookup_scan_cache(lsm, rv, key, &history);
+	hint_t hint = vy_stmt_hint(key, lsm->cmp_def);
+
+	rc = vy_point_lookup_scan_cache(lsm, rv, key, hint, &history);
 	if (rc != 0 || vy_history_is_terminal(&history))
 		goto done;
 
-	rc = vy_point_lookup_scan_mems(lsm, rv, key, &history);
+	rc = vy_point_lookup_scan_mems(lsm, rv, key, hint, &history);
 	if (rc != 0 || vy_history_is_terminal(&history))
 		goto done;
 
@@ -335,9 +341,10 @@ vy_point_lookup_mem(struct vy_lsm *lsm, const struct vy_read_view **rv,
 	goto out;
 done:
 	if (rc == 0) {
+		hint_t unused;
 		int upserts_applied;
-		rc = vy_history_apply(&history, lsm->cmp_def,
-				      true, &upserts_applied, ret);
+		rc = vy_history_apply(&history, lsm->cmp_def, true,
+				      &upserts_applied, ret, &unused);
 		lsm->stat.upsert.applied += upserts_applied;
 	}
 out:
diff --git a/src/box/vy_read_iterator.c b/src/box/vy_read_iterator.c
index f1d8c8e4..b96a5d19 100644
--- a/src/box/vy_read_iterator.c
+++ b/src/box/vy_read_iterator.c
@@ -141,24 +141,28 @@ vy_read_iterator_unpin_slices(struct vy_read_iterator *itr)
  */
 static bool
 vy_read_iterator_range_is_done(struct vy_read_iterator *itr,
-			       struct tuple *next_key)
+			       struct tuple *next_key, hint_t next_hint)
 {
 	struct vy_range *range = itr->curr_range;
 	struct key_def *cmp_def = itr->lsm->cmp_def;
 	int dir = iterator_direction(itr->iterator_type);
 
 	if (dir > 0 && range->end != NULL &&
-	    (next_key == NULL || vy_stmt_compare(next_key, range->end,
-						 cmp_def) >= 0) &&
+	    (next_key == NULL ||
+	     vy_stmt_compare_hinted(next_key, next_hint, range->end,
+				    range->end_hint, cmp_def) >= 0) &&
 	    (itr->iterator_type != ITER_EQ ||
-	     vy_stmt_compare(itr->key, range->end, cmp_def) >= 0))
+	     vy_stmt_compare_hinted(itr->key, itr->hint, range->end,
+				    range->end_hint, cmp_def) >= 0))
 		return true;
 
 	if (dir < 0 && range->begin != NULL &&
-	    (next_key == NULL || vy_stmt_compare(next_key, range->begin,
-						 cmp_def) < 0) &&
+	    (next_key == NULL ||
+	     vy_stmt_compare_hinted(next_key, next_hint, range->begin,
+				    range->begin_hint, cmp_def) < 0) &&
 	    (itr->iterator_type != ITER_REQ ||
-	     vy_stmt_compare(itr->key, range->begin, cmp_def) <= 0))
+	     vy_stmt_compare_hinted(itr->key, itr->hint, range->begin,
+				    range->begin_hint, cmp_def) <= 0))
 		return true;
 
 	return false;
@@ -176,7 +180,8 @@ vy_read_iterator_range_is_done(struct vy_read_iterator *itr,
  */
 static inline int
 vy_read_iterator_cmp_stmt(struct vy_read_iterator *itr,
-			  const struct tuple *a, const struct tuple *b)
+			  const struct tuple *a, hint_t a_hint,
+			  const struct tuple *b, hint_t b_hint)
 {
 	if (a == NULL && b != NULL)
 		return 1;
@@ -185,7 +190,7 @@ vy_read_iterator_cmp_stmt(struct vy_read_iterator *itr,
 	if (a == NULL && b == NULL)
 		return 0;
 	return iterator_direction(itr->iterator_type) *
-		vy_stmt_compare(a, b, itr->lsm->cmp_def);
+		vy_stmt_compare_hinted(a, a_hint, b, b_hint, itr->lsm->cmp_def);
 }
 
 /**
@@ -194,9 +199,8 @@ vy_read_iterator_cmp_stmt(struct vy_read_iterator *itr,
  */
 static bool
 vy_read_iterator_is_exact_match(struct vy_read_iterator *itr,
-				struct tuple *stmt)
+				struct tuple *stmt, hint_t hint)
 {
-	struct tuple *key = itr->key;
 	enum iterator_type type = itr->iterator_type;
 	struct key_def *cmp_def = itr->lsm->cmp_def;
 
@@ -208,8 +212,9 @@ vy_read_iterator_is_exact_match(struct vy_read_iterator *itr,
 	return itr->last_stmt == NULL && stmt != NULL &&
 		(type == ITER_EQ || type == ITER_REQ ||
 		 type == ITER_GE || type == ITER_LE) &&
-		vy_stmt_is_full_key(key, cmp_def) &&
-		vy_stmt_compare(stmt, key, cmp_def) == 0;
+		vy_stmt_is_full_key(itr->key, cmp_def) &&
+		vy_stmt_compare_hinted(stmt, hint, itr->key,
+				       itr->hint, cmp_def) == 0;
 }
 
 /**
@@ -222,14 +227,18 @@ vy_read_iterator_is_exact_match(struct vy_read_iterator *itr,
 static void
 vy_read_iterator_evaluate_src(struct vy_read_iterator *itr,
 			      struct vy_read_src *src,
-			      struct tuple **next_key, bool *stop)
+			      struct tuple **next_key, hint_t *next_hint,
+			      bool *stop)
 {
 	uint32_t src_id = src - itr->src;
-	struct tuple *stmt = vy_history_last_stmt(&src->history);
-	int cmp = vy_read_iterator_cmp_stmt(itr, stmt, *next_key);
+	hint_t hint;
+	struct tuple *stmt = vy_history_last_stmt(&src->history, &hint);
+	int cmp = vy_read_iterator_cmp_stmt(itr, stmt, hint,
+					    *next_key, *next_hint);
 	if (cmp < 0) {
 		assert(stmt != NULL);
 		*next_key = stmt;
+		*next_hint = hint;
 		itr->front_id++;
 	}
 	if (cmp <= 0)
@@ -238,7 +247,7 @@ vy_read_iterator_evaluate_src(struct vy_read_iterator *itr,
 	itr->skipped_src = MAX(itr->skipped_src, src_id + 1);
 
 	if (cmp < 0 && vy_history_is_terminal(&src->history) &&
-	    vy_read_iterator_is_exact_match(itr, stmt)) {
+	    vy_read_iterator_is_exact_match(itr, stmt, hint)) {
 		itr->skipped_src = src_id + 1;
 		*stop = true;
 	}
@@ -270,7 +279,8 @@ vy_read_iterator_evaluate_src(struct vy_read_iterator *itr,
 
 static NODISCARD int
 vy_read_iterator_scan_txw(struct vy_read_iterator *itr,
-			  struct tuple **next_key, bool *stop)
+			  struct tuple **next_key, hint_t *next_hint,
+			  bool *stop)
 {
 	struct vy_read_src *src = &itr->src[itr->txw_src];
 	struct vy_txw_iterator *src_itr = &src->txw_iterator;
@@ -281,11 +291,11 @@ vy_read_iterator_scan_txw(struct vy_read_iterator *itr,
 	assert(itr->txw_src < itr->skipped_src);
 
 	int rc = vy_txw_iterator_restore(src_itr, itr->last_stmt,
-					 &src->history);
+					 itr->last_hint, &src->history);
 	if (rc == 0) {
 		if (!src->is_started) {
 			rc = vy_txw_iterator_skip(src_itr, itr->last_stmt,
-						  &src->history);
+						  itr->last_hint, &src->history);
 		} else if (src->front_id == itr->prev_front_id) {
 			rc = vy_txw_iterator_next(src_itr, &src->history);
 		}
@@ -294,24 +304,27 @@ vy_read_iterator_scan_txw(struct vy_read_iterator *itr,
 	if (rc < 0)
 		return -1;
 
-	vy_read_iterator_evaluate_src(itr, src, next_key, stop);
+	vy_read_iterator_evaluate_src(itr, src, next_key, next_hint, stop);
 	return 0;
 }
 
 static NODISCARD int
 vy_read_iterator_scan_cache(struct vy_read_iterator *itr,
-			    struct tuple **next_key, bool *stop)
+			    struct tuple **next_key, hint_t *next_hint,
+			    bool *stop)
 {
 	bool is_interval = false;
 	struct vy_read_src *src = &itr->src[itr->cache_src];
 	struct vy_cache_iterator *src_itr = &src->cache_iterator;
 
 	int rc = vy_cache_iterator_restore(src_itr, itr->last_stmt,
-					   &src->history, &is_interval);
+					   itr->last_hint, &src->history,
+					   &is_interval);
 	if (rc == 0) {
 		if (!src->is_started || itr->cache_src >= itr->skipped_src) {
 			rc = vy_cache_iterator_skip(src_itr, itr->last_stmt,
-						&src->history, &is_interval);
+						    itr->last_hint, &src->history,
+						    &is_interval);
 		} else if (src->front_id == itr->prev_front_id) {
 			rc = vy_cache_iterator_next(src_itr, &src->history,
 						    &is_interval);
@@ -321,7 +334,7 @@ vy_read_iterator_scan_cache(struct vy_read_iterator *itr,
 	if (rc < 0)
 		return -1;
 
-	vy_read_iterator_evaluate_src(itr, src, next_key, stop);
+	vy_read_iterator_evaluate_src(itr, src, next_key, next_hint, stop);
 	if (is_interval) {
 		itr->skipped_src = itr->cache_src + 1;
 		*stop = true;
@@ -331,7 +344,8 @@ vy_read_iterator_scan_cache(struct vy_read_iterator *itr,
 
 static NODISCARD int
 vy_read_iterator_scan_mem(struct vy_read_iterator *itr, uint32_t mem_src,
-			  struct tuple **next_key, bool *stop)
+			  struct tuple **next_key, hint_t *next_hint,
+			  bool *stop)
 {
 	int rc;
 	struct vy_read_src *src = &itr->src[mem_src];
@@ -339,11 +353,12 @@ vy_read_iterator_scan_mem(struct vy_read_iterator *itr, uint32_t mem_src,
 
 	assert(mem_src >= itr->mem_src && mem_src < itr->disk_src);
 
-	rc = vy_mem_iterator_restore(src_itr, itr->last_stmt, &src->history);
+	rc = vy_mem_iterator_restore(src_itr, itr->last_stmt, itr->last_hint,
+				     &src->history);
 	if (rc == 0) {
 		if (!src->is_started || mem_src >= itr->skipped_src) {
 			rc = vy_mem_iterator_skip(src_itr, itr->last_stmt,
-						  &src->history);
+						  itr->last_hint, &src->history);
 		} else if (src->front_id == itr->prev_front_id) {
 			rc = vy_mem_iterator_next(src_itr, &src->history);
 		}
@@ -352,13 +367,14 @@ vy_read_iterator_scan_mem(struct vy_read_iterator *itr, uint32_t mem_src,
 	if (rc < 0)
 		return -1;
 
-	vy_read_iterator_evaluate_src(itr, src, next_key, stop);
+	vy_read_iterator_evaluate_src(itr, src, next_key, next_hint, stop);
 	return 0;
 }
 
 static NODISCARD int
 vy_read_iterator_scan_disk(struct vy_read_iterator *itr, uint32_t disk_src,
-			   struct tuple **next_key, bool *stop)
+			   struct tuple **next_key, hint_t *next_hint,
+			   bool *stop)
 {
 	int rc = 0;
 	struct vy_read_src *src = &itr->src[disk_src];
@@ -368,7 +384,7 @@ vy_read_iterator_scan_disk(struct vy_read_iterator *itr, uint32_t disk_src,
 
 	if (!src->is_started || disk_src >= itr->skipped_src)
 		rc = vy_run_iterator_skip(src_itr, itr->last_stmt,
-					  &src->history);
+					  itr->last_hint, &src->history);
 	else if (src->front_id == itr->prev_front_id)
 		rc = vy_run_iterator_next(src_itr, &src->history);
 	src->is_started = true;
@@ -376,7 +392,7 @@ vy_read_iterator_scan_disk(struct vy_read_iterator *itr, uint32_t disk_src,
 	if (rc < 0)
 		return -1;
 
-	vy_read_iterator_evaluate_src(itr, src, next_key, stop);
+	vy_read_iterator_evaluate_src(itr, src, next_key, next_hint, stop);
 	return 0;
 }
 
@@ -387,21 +403,22 @@ vy_read_iterator_scan_disk(struct vy_read_iterator *itr, uint32_t disk_src,
  */
 static NODISCARD int
 vy_read_iterator_restore_mem(struct vy_read_iterator *itr,
-			     struct tuple **next_key)
+			     struct tuple **next_key, hint_t *next_hint)
 {
 	int rc;
 	int cmp;
 	struct vy_read_src *src = &itr->src[itr->mem_src];
 
-	rc = vy_mem_iterator_restore(&src->mem_iterator,
-				     itr->last_stmt, &src->history);
+	rc = vy_mem_iterator_restore(&src->mem_iterator, itr->last_stmt,
+				     itr->last_hint, &src->history);
 	if (rc < 0)
 		return -1; /* memory allocation error */
 	if (rc == 0)
 		return 0; /* nothing changed */
 
-	struct tuple *stmt = vy_history_last_stmt(&src->history);
-	cmp = vy_read_iterator_cmp_stmt(itr, stmt, *next_key);
+	hint_t hint;
+	struct tuple *stmt = vy_history_last_stmt(&src->history, &hint);
+	cmp = vy_read_iterator_cmp_stmt(itr, stmt, hint, *next_key, *next_hint);
 	if (cmp > 0) {
 		/*
 		 * Memory trees are append-only so if the
@@ -417,6 +434,7 @@ vy_read_iterator_restore_mem(struct vy_read_iterator *itr,
 		 * candidate for the next key.
 		 */
 		*next_key = stmt;
+		*next_hint = hint;
 		itr->front_id++;
 	} else {
 		/*
@@ -475,17 +493,19 @@ restart:
 	 */
 	bool stop = false;
 	struct tuple *next_key = NULL;
-	if (vy_read_iterator_scan_txw(itr, &next_key, &stop) != 0)
+	hint_t next_hint = HINT_NONE;
+	if (vy_read_iterator_scan_txw(itr, &next_key, &next_hint, &stop) != 0)
 		return -1;
 	if (stop)
 		goto done;
-	if (vy_read_iterator_scan_cache(itr, &next_key, &stop) != 0)
+	if (vy_read_iterator_scan_cache(itr, &next_key, &next_hint, &stop) != 0)
 		return -1;
 	if (stop)
 		goto done;
 
 	for (uint32_t i = itr->mem_src; i < itr->disk_src; i++) {
-		if (vy_read_iterator_scan_mem(itr, i, &next_key, &stop) != 0)
+		if (vy_read_iterator_scan_mem(itr, i, &next_key, &next_hint,
+					      &stop) != 0)
 			return -1;
 		if (stop)
 			goto done;
@@ -494,7 +514,8 @@ rescan_disk:
 	/* The following code may yield as it needs to access disk. */
 	vy_read_iterator_pin_slices(itr);
 	for (uint32_t i = itr->disk_src; i < itr->src_count; i++) {
-		if (vy_read_iterator_scan_disk(itr, i, &next_key, &stop) != 0) {
+		if (vy_read_iterator_scan_disk(itr, i, &next_key, &next_hint,
+					       &stop) != 0) {
 			vy_read_iterator_unpin_slices(itr);
 			return -1;
 		}
@@ -531,13 +552,13 @@ rescan_disk:
 	 * as it is owned exclusively by the current fiber so the only
 	 * source to check is the active in-memory tree.
 	 */
-	if (vy_read_iterator_restore_mem(itr, &next_key) != 0)
+	if (vy_read_iterator_restore_mem(itr, &next_key, &next_hint) != 0)
 		return -1;
 	/*
 	 * Scan the next range in case we transgressed the current
 	 * range's boundaries.
 	 */
-	if (vy_read_iterator_range_is_done(itr, next_key)) {
+	if (vy_read_iterator_range_is_done(itr, next_key, next_hint)) {
 		vy_read_iterator_next_range(itr);
 		goto rescan_disk;
 	}
@@ -545,8 +566,9 @@ done:
 #ifndef NDEBUG
 	/* Check that the statement meets search criteria. */
 	if (next_key != NULL) {
-		int cmp = vy_stmt_compare(next_key, itr->key,
-					  itr->lsm->cmp_def);
+		int cmp = vy_stmt_compare_hinted(next_key, next_hint,
+						 itr->key, itr->hint,
+						 itr->lsm->cmp_def);
 		cmp *= iterator_direction(itr->iterator_type);
 		if (itr->iterator_type == ITER_GT ||
 		    itr->iterator_type == ITER_LT)
@@ -559,12 +581,13 @@ done:
 	 * and respects statement order.
 	 */
 	if (itr->last_stmt != NULL && next_key != NULL) {
-	       assert(vy_read_iterator_cmp_stmt(itr, next_key,
-						itr->last_stmt) > 0);
+	       assert(vy_read_iterator_cmp_stmt(itr, next_key, next_hint,
+					itr->last_stmt, itr->last_hint) > 0);
 	}
 #endif
 	if (itr->need_check_eq && next_key != NULL &&
-	    vy_stmt_compare(next_key, itr->key, itr->lsm->cmp_def) != 0)
+	    vy_stmt_compare_hinted(next_key, next_hint, itr->key, itr->hint,
+				   itr->lsm->cmp_def) != 0)
 		itr->front_id++;
 	return 0;
 }
@@ -578,7 +601,7 @@ vy_read_iterator_add_tx(struct vy_read_iterator *itr)
 	struct vy_txw_iterator_stat *stat = &itr->lsm->stat.txw.iterator;
 	struct vy_read_src *sub_src = vy_read_iterator_add_src(itr);
 	vy_txw_iterator_open(&sub_src->txw_iterator, stat, itr->tx, itr->lsm,
-			     iterator_type, itr->key);
+			     iterator_type, itr->key, itr->hint);
 }
 
 static void
@@ -589,7 +612,7 @@ vy_read_iterator_add_cache(struct vy_read_iterator *itr)
 	struct vy_read_src *sub_src = vy_read_iterator_add_src(itr);
 	vy_cache_iterator_open(&sub_src->cache_iterator,
 			       &itr->lsm->cache, iterator_type,
-			       itr->key, itr->read_view);
+			       itr->key, itr->hint, itr->read_view);
 }
 
 static void
@@ -604,14 +627,15 @@ vy_read_iterator_add_mem(struct vy_read_iterator *itr)
 	assert(lsm->mem != NULL);
 	sub_src = vy_read_iterator_add_src(itr);
 	vy_mem_iterator_open(&sub_src->mem_iterator, &lsm->stat.memory.iterator,
-			     lsm->mem, iterator_type, itr->key, itr->read_view);
+			     lsm->mem, iterator_type, itr->key, itr->hint,
+			     itr->read_view);
 	/* Add sealed in-memory indexes. */
 	struct vy_mem *mem;
 	rlist_foreach_entry(mem, &lsm->sealed, in_sealed) {
 		sub_src = vy_read_iterator_add_src(itr);
 		vy_mem_iterator_open(&sub_src->mem_iterator,
-				     &lsm->stat.memory.iterator,
-				     mem, iterator_type, itr->key,
+				     &lsm->stat.memory.iterator, mem,
+				     iterator_type, itr->key, itr->hint,
 				     itr->read_view);
 	}
 }
@@ -633,7 +657,7 @@ vy_read_iterator_add_disk(struct vy_read_iterator *itr)
 		struct vy_read_src *sub_src = vy_read_iterator_add_src(itr);
 		vy_run_iterator_open(&sub_src->run_iterator,
 				     &lsm->stat.disk.iterator, slice,
-				     iterator_type, itr->key,
+				     iterator_type, itr->key, itr->hint,
 				     itr->read_view, lsm->cmp_def,
 				     lsm->key_def, lsm->disk_format);
 	}
@@ -688,7 +712,9 @@ vy_read_iterator_open(struct vy_read_iterator *itr, struct vy_lsm *lsm,
 	itr->tx = tx;
 	itr->iterator_type = iterator_type;
 	itr->key = key;
+	itr->hint = vy_stmt_hint(key, lsm->cmp_def);
 	itr->read_view = rv;
+	itr->last_hint = HINT_NONE;
 
 	if (vy_stmt_is_empty_key(key)) {
 		/*
@@ -730,9 +756,15 @@ vy_read_iterator_restore(struct vy_read_iterator *itr)
 {
 	vy_read_iterator_cleanup(itr);
 
-	const struct tuple *key = itr->last_stmt != NULL ?
-				  itr->last_stmt : itr->key;
-	hint_t hint = vy_stmt_hint(key, itr->lsm->cmp_def);
+	hint_t hint;
+	const struct tuple *key;
+	if (itr->last_stmt != NULL) {
+		key = itr->last_stmt;
+		hint = itr->last_hint;
+	} else {
+		key = itr->key;
+		hint = itr->hint;
+	}
 
 	itr->mem_list_version = itr->lsm->mem_list_version;
 	itr->range_tree_version = itr->lsm->range_tree_version;
@@ -778,13 +810,17 @@ vy_read_iterator_next_range(struct vy_read_iterator *itr)
 		 * We could skip an entire range due to the cache.
 		 * Make sure the next statement falls in the range.
 		 */
-		if (dir > 0 && (range->end == NULL ||
-				vy_stmt_compare(itr->last_stmt, range->end,
-						cmp_def) < 0))
+		if (dir > 0 &&
+		    (range->end == NULL ||
+		     vy_stmt_compare_hinted(itr->last_stmt, itr->last_hint,
+					    range->end, range->end_hint,
+					    cmp_def) < 0))
 			break;
-		if (dir < 0 && (range->begin == NULL ||
-				vy_stmt_compare(itr->last_stmt, range->begin,
-						cmp_def) > 0))
+		if (dir < 0 &&
+		    (range->begin == NULL ||
+		     vy_stmt_compare_hinted(itr->last_stmt, itr->last_hint,
+					    range->begin, range->begin_hint,
+					    cmp_def) > 0))
 			break;
 	}
 	itr->curr_range = range;
@@ -805,7 +841,7 @@ vy_read_iterator_next_range(struct vy_read_iterator *itr)
  */
 static NODISCARD int
 vy_read_iterator_apply_history(struct vy_read_iterator *itr,
-			       struct tuple **ret)
+			       struct tuple **ret, hint_t *ret_hint)
 {
 	struct vy_lsm *lsm = itr->lsm;
 	struct vy_history history;
@@ -822,7 +858,7 @@ vy_read_iterator_apply_history(struct vy_read_iterator *itr,
 
 	int upserts_applied = 0;
 	int rc = vy_history_apply(&history, lsm->cmp_def,
-				  true, &upserts_applied, ret);
+				  true, &upserts_applied, ret, ret_hint);
 
 	lsm->stat.upsert.applied += upserts_applied;
 	vy_history_cleanup(&history);
@@ -865,13 +901,14 @@ vy_read_iterator_next(struct vy_read_iterator *itr, struct tuple **result)
 
 	struct vy_lsm *lsm = itr->lsm;
 	struct tuple *stmt;
+	hint_t hint;
 
 	if (itr->last_stmt == NULL)
 		lsm->stat.lookup++; /* first iteration */
 next_key:
 	if (vy_read_iterator_advance(itr) != 0)
 		return -1;
-	if (vy_read_iterator_apply_history(itr, &stmt) != 0)
+	if (vy_read_iterator_apply_history(itr, &stmt, &hint) != 0)
 		return -1;
 	if (vy_read_iterator_track_read(itr, stmt) != 0)
 		return -1;
@@ -879,6 +916,7 @@ next_key:
 	if (itr->last_stmt != NULL)
 		tuple_unref(itr->last_stmt);
 	itr->last_stmt = stmt;
+	itr->last_hint = hint;
 
 	if (stmt != NULL && vy_stmt_type(stmt) == IPROTO_DELETE) {
 		/*
diff --git a/src/box/vy_read_iterator.h b/src/box/vy_read_iterator.h
index baab8859..d8251372 100644
--- a/src/box/vy_read_iterator.h
+++ b/src/box/vy_read_iterator.h
@@ -34,6 +34,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+#include "key_def.h"
 #include "iterator_type.h"
 #include "trivia/util.h"
 
@@ -55,6 +56,8 @@ struct vy_read_iterator {
 	enum iterator_type iterator_type;
 	/** Search key. */
 	struct tuple *key;
+	/** Comparison hint of the search key. */
+	hint_t hint;
 	/** Read view the iterator lives in. */
 	const struct vy_read_view **read_view;
 	/**
@@ -64,6 +67,8 @@ struct vy_read_iterator {
 	bool need_check_eq;
 	/** Last statement returned by vy_read_iterator_next(). */
 	struct tuple *last_stmt;
+	/** Comparison hint of the last statement. */
+	hint_t last_hint;
 	/**
 	 * Last statement added to the tuple cache by
 	 * vy_read_iterator_cache_add().
diff --git a/src/box/vy_run.c b/src/box/vy_run.c
index 9608e207..897ece2a 100644
--- a/src/box/vy_run.c
+++ b/src/box/vy_run.c
@@ -1184,12 +1184,14 @@ vy_run_iterator_next_pos(struct vy_run_iterator *itr,
  * Affects: curr_loaded_page, curr_pos
  */
 static NODISCARD int
-vy_run_iterator_find_lsn(struct vy_run_iterator *itr, struct tuple **ret)
+vy_run_iterator_find_lsn(struct vy_run_iterator *itr,
+			 struct tuple **ret, hint_t *ret_hint)
 {
 	struct vy_slice *slice = itr->slice;
 	struct key_def *cmp_def = itr->cmp_def;
 
 	*ret = NULL;
+	*ret_hint = HINT_NONE;
 
 	assert(itr->search_started);
 	assert(itr->curr_stmt != NULL);
@@ -1261,6 +1263,7 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr, struct tuple **ret)
 	}
 	vy_stmt_counter_acct_tuple(&itr->stat->get, itr->curr_stmt);
 	*ret = itr->curr_stmt;
+	*ret_hint = itr->curr_hint;
 	return 0;
 }
 
@@ -1333,7 +1336,7 @@ vy_run_iterator_do_seek(struct vy_run_iterator *itr,
  */
 static NODISCARD int
 vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
-		     hint_t last_hint, struct tuple **ret)
+		     hint_t last_hint, struct tuple **ret, hint_t *ret_hint)
 {
 	struct key_def *cmp_def = itr->cmp_def;
 	struct vy_slice *slice = itr->slice;
@@ -1343,6 +1346,7 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 	enum iterator_type iterator_type = itr->iterator_type;
 
 	*ret = NULL;
+	*ret_hint = HINT_NONE;
 	assert(itr->search_started);
 
 	/* Check the bloom filter on the first iteration. */
@@ -1452,7 +1456,7 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 		goto not_found;
 
 	/* Skip statements invisible from the iterator read view. */
-	return vy_run_iterator_find_lsn(itr, ret);
+	return vy_run_iterator_find_lsn(itr, ret, ret_hint);
 
 not_found:
 	if (check_bloom)
@@ -1469,7 +1473,8 @@ void
 vy_run_iterator_open(struct vy_run_iterator *itr,
 		     struct vy_run_iterator_stat *stat,
 		     struct vy_slice *slice, enum iterator_type iterator_type,
-		     const struct tuple *key, const struct vy_read_view **rv,
+		     const struct tuple *key, hint_t hint,
+		     const struct vy_read_view **rv,
 		     struct key_def *cmp_def, struct key_def *key_def,
 		     struct tuple_format *format)
 {
@@ -1481,7 +1486,7 @@ vy_run_iterator_open(struct vy_run_iterator *itr,
 
 	itr->iterator_type = iterator_type;
 	itr->key = key;
-	itr->hint = vy_stmt_hint(key, cmp_def);
+	itr->hint = hint;
 	itr->read_view = rv;
 
 	itr->curr_stmt = NULL;
@@ -1508,13 +1513,16 @@ vy_run_iterator_open(struct vy_run_iterator *itr,
  * Returns 0 on success, -1 on memory allocation or IO error.
  */
 static NODISCARD int
-vy_run_iterator_next_key(struct vy_run_iterator *itr, struct tuple **ret)
+vy_run_iterator_next_key(struct vy_run_iterator *itr, struct tuple **ret,
+			 hint_t *ret_hint)
 {
 	*ret = NULL;
+	*ret_hint = HINT_NONE;
 
 	if (!itr->search_started) {
 		itr->search_started = true;
-		return vy_run_iterator_seek(itr, NULL, HINT_NONE, ret);
+		return vy_run_iterator_seek(itr, NULL, HINT_NONE,
+					    ret, ret_hint);
 	}
 	if (itr->curr_stmt == NULL)
 		return 0;
@@ -1548,7 +1556,7 @@ vy_run_iterator_next_key(struct vy_run_iterator *itr, struct tuple **ret)
 		vy_run_iterator_stop(itr);
 		return 0;
 	}
-	return vy_run_iterator_find_lsn(itr, ret);
+	return vy_run_iterator_find_lsn(itr, ret, ret_hint);
 }
 
 /**
@@ -1557,9 +1565,11 @@ vy_run_iterator_next_key(struct vy_run_iterator *itr, struct tuple **ret)
  * Returns 0 on success, -1 on memory allocation or IO error.
  */
 static NODISCARD int
-vy_run_iterator_next_lsn(struct vy_run_iterator *itr, struct tuple **ret)
+vy_run_iterator_next_lsn(struct vy_run_iterator *itr, struct tuple **ret,
+			 hint_t *ret_hint)
 {
 	*ret = NULL;
+	*ret_hint = HINT_NONE;
 
 	assert(itr->search_started);
 	assert(itr->curr_stmt != NULL);
@@ -1592,6 +1602,7 @@ next:
 
 	vy_stmt_counter_acct_tuple(&itr->stat->get, itr->curr_stmt);
 	*ret = itr->curr_stmt;
+	*ret_hint = itr->curr_hint;
 	return 0;
 }
 
@@ -1600,15 +1611,16 @@ vy_run_iterator_next(struct vy_run_iterator *itr,
 		     struct vy_history *history)
 {
 	vy_history_cleanup(history);
+	hint_t hint;
 	struct tuple *stmt;
-	if (vy_run_iterator_next_key(itr, &stmt) != 0)
+	if (vy_run_iterator_next_key(itr, &stmt, &hint) != 0)
 		return -1;
 	while (stmt != NULL) {
-		if (vy_history_append_stmt(history, stmt) != 0)
+		if (vy_history_append_stmt(history, stmt, hint) != 0)
 			return -1;
 		if (vy_history_is_terminal(history))
 			break;
-		if (vy_run_iterator_next_lsn(itr, &stmt) != 0)
+		if (vy_run_iterator_next_lsn(itr, &stmt, &hint) != 0)
 			return -1;
 	}
 	return 0;
@@ -1616,11 +1628,9 @@ vy_run_iterator_next(struct vy_run_iterator *itr,
 
 NODISCARD int
 vy_run_iterator_skip(struct vy_run_iterator *itr,
-		     const struct tuple *last_stmt,
+		     const struct tuple *last_stmt, hint_t last_hint,
 		     struct vy_history *history)
 {
-	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(last_stmt, itr->cmp_def);
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
@@ -1635,16 +1645,17 @@ vy_run_iterator_skip(struct vy_run_iterator *itr,
 	vy_history_cleanup(history);
 
 	itr->search_started = true;
+	hint_t hint;
 	struct tuple *stmt;
-	if (vy_run_iterator_seek(itr, last_stmt, last_hint, &stmt) != 0)
+	if (vy_run_iterator_seek(itr, last_stmt, last_hint, &stmt, &hint) != 0)
 		return -1;
 
 	while (stmt != NULL) {
-		if (vy_history_append_stmt(history, stmt) != 0)
+		if (vy_history_append_stmt(history, stmt, hint) != 0)
 			return -1;
 		if (vy_history_is_terminal(history))
 			break;
-		if (vy_run_iterator_next_lsn(itr, &stmt) != 0)
+		if (vy_run_iterator_next_lsn(itr, &stmt, &hint) != 0)
 			return -1;
 	}
 	return 0;
diff --git a/src/box/vy_run.h b/src/box/vy_run.h
index 369221ff..9f517978 100644
--- a/src/box/vy_run.h
+++ b/src/box/vy_run.h
@@ -530,7 +530,8 @@ void
 vy_run_iterator_open(struct vy_run_iterator *itr,
 		     struct vy_run_iterator_stat *stat,
 		     struct vy_slice *slice, enum iterator_type iterator_type,
-		     const struct tuple *key, const struct vy_read_view **rv,
+		     const struct tuple *key, hint_t hint,
+		     const struct vy_read_view **rv,
 		     struct key_def *cmp_def, struct key_def *key_def,
 		     struct tuple_format *format);
 
@@ -550,7 +551,7 @@ vy_run_iterator_next(struct vy_run_iterator *itr,
  */
 NODISCARD int
 vy_run_iterator_skip(struct vy_run_iterator *itr,
-		     const struct tuple *last_stmt,
+		     const struct tuple *last_stmt, hint_t last_hint,
 		     struct vy_history *history);
 
 /**
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index 596c7ed0..d8d7c905 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -1159,14 +1159,14 @@ vy_txw_iterator_open(struct vy_txw_iterator *itr,
 		     struct vy_txw_iterator_stat *stat,
 		     struct vy_tx *tx, struct vy_lsm *lsm,
 		     enum iterator_type iterator_type,
-		     const struct tuple *key)
+		     const struct tuple *key, hint_t hint)
 {
 	itr->stat = stat;
 	itr->tx = tx;
 	itr->lsm = lsm;
 	itr->iterator_type = iterator_type;
 	itr->key = key;
-	itr->hint = vy_stmt_hint(key, lsm->cmp_def);
+	itr->hint = hint;
 	itr->version = UINT32_MAX;
 	itr->curr_txv = NULL;
 	itr->search_started = false;
@@ -1269,21 +1269,20 @@ out:
 	if (itr->curr_txv != NULL) {
 		vy_stmt_counter_acct_tuple(&itr->stat->get,
 					   itr->curr_txv->stmt);
-		return vy_history_append_stmt(history, itr->curr_txv->stmt);
+		return vy_history_append_stmt(history, itr->curr_txv->stmt,
+					      itr->curr_txv->hint);
 	}
 	return 0;
 }
 
 NODISCARD int
 vy_txw_iterator_skip(struct vy_txw_iterator *itr,
-		     const struct tuple *last_stmt,
+		     const struct tuple *last_stmt, hint_t last_hint,
 		     struct vy_history *history)
 {
 	assert(!itr->search_started ||
 	       itr->version == itr->tx->write_set_version);
 
-	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(last_stmt, itr->lsm->cmp_def);
 	/*
 	 * Check if the iterator is already positioned
 	 * at the statement following last_stmt.
@@ -1305,29 +1304,28 @@ vy_txw_iterator_skip(struct vy_txw_iterator *itr,
 	if (itr->curr_txv != NULL) {
 		vy_stmt_counter_acct_tuple(&itr->stat->get,
 					   itr->curr_txv->stmt);
-		return vy_history_append_stmt(history, itr->curr_txv->stmt);
+		return vy_history_append_stmt(history, itr->curr_txv->stmt,
+					      itr->curr_txv->hint);
 	}
 	return 0;
 }
 
 NODISCARD int
 vy_txw_iterator_restore(struct vy_txw_iterator *itr,
-			const struct tuple *last_stmt,
+			const struct tuple *last_stmt, hint_t last_hint,
 			struct vy_history *history)
 {
 	if (!itr->search_started || itr->version == itr->tx->write_set_version)
 		return 0;
 
-	hint_t last_hint = last_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(last_stmt, itr->lsm->cmp_def);
-
 	vy_txw_iterator_seek(itr, last_stmt, last_hint);
 
 	vy_history_cleanup(history);
 	if (itr->curr_txv != NULL) {
 		vy_stmt_counter_acct_tuple(&itr->stat->get,
 					   itr->curr_txv->stmt);
-		if (vy_history_append_stmt(history, itr->curr_txv->stmt) != 0)
+		if (vy_history_append_stmt(history, itr->curr_txv->stmt,
+					   itr->curr_txv->hint) != 0)
 			return -1;
 	}
 	return 1;
diff --git a/src/box/vy_tx.h b/src/box/vy_tx.h
index 907957ae..f3b9eacc 100644
--- a/src/box/vy_tx.h
+++ b/src/box/vy_tx.h
@@ -442,7 +442,7 @@ vy_txw_iterator_open(struct vy_txw_iterator *itr,
 		     struct vy_txw_iterator_stat *stat,
 		     struct vy_tx *tx, struct vy_lsm *lsm,
 		     enum iterator_type iterator_type,
-		     const struct tuple *key);
+		     const struct tuple *key, hint_t hint);
 
 /**
  * Advance a txw iterator to the next key.
@@ -460,7 +460,7 @@ vy_txw_iterator_next(struct vy_txw_iterator *itr,
  */
 NODISCARD int
 vy_txw_iterator_skip(struct vy_txw_iterator *itr,
-		     const struct tuple *last_stmt,
+		     const struct tuple *last_stmt, hint_t last_hint,
 		     struct vy_history *history);
 
 /**
@@ -471,7 +471,7 @@ vy_txw_iterator_skip(struct vy_txw_iterator *itr,
  */
 int
 vy_txw_iterator_restore(struct vy_txw_iterator *itr,
-			const struct tuple *last_stmt,
+			const struct tuple *last_stmt, hint_t last_hint,
 			struct vy_history *history);
 
 /**
diff --git a/test/unit/vy_cache.c b/test/unit/vy_cache.c
index d46d6c3f..d88ffc1a 100644
--- a/test/unit/vy_cache.c
+++ b/test/unit/vy_cache.c
@@ -85,16 +85,18 @@ test_basic()
 	struct vy_read_view rv;
 	rv.vlsn = INT64_MAX;
 	const struct vy_read_view *rv_p = &rv;
-	vy_cache_iterator_open(&itr, &cache, ITER_GE, select_all, &rv_p);
+	vy_cache_iterator_open(&itr, &cache, ITER_GE,
+			       select_all, HINT_NONE, &rv_p);
 
 	/* Start iterator and make several steps. */
+	hint_t hint;
 	struct tuple *ret;
 	bool unused;
 	struct vy_history history;
 	vy_history_create(&history, &history_node_pool);
 	for (int i = 0; i < 4; ++i)
 		vy_cache_iterator_next(&itr, &history, &unused);
-	ret = vy_history_last_stmt(&history);
+	ret = vy_history_last_stmt(&history, &hint);
 	ok(vy_stmt_are_same(ret, &chain1[3], format),
 	   "next_key * 4");
 
@@ -115,9 +117,11 @@ test_basic()
 	 * must be chain1[1].
 	 */
 	struct tuple *last_stmt = vy_new_simple_stmt(format, &chain1[0]);
-	ok(vy_cache_iterator_restore(&itr, last_stmt, &history, &unused) >= 0,
+	hint_t last_hint = vy_stmt_hint(last_stmt, key_def);
+	ok(vy_cache_iterator_restore(&itr, last_stmt, last_hint,
+				     &history, &unused) >= 0,
 	   "restore");
-	ret = vy_history_last_stmt(&history);
+	ret = vy_history_last_stmt(&history, &hint);
 	ok(vy_stmt_are_same(ret, &chain1[1], format),
 	   "restore on position after last");
 	tuple_unref(last_stmt);
diff --git a/test/unit/vy_mem.c b/test/unit/vy_mem.c
index 798c7a99..4453e076 100644
--- a/test/unit/vy_mem.c
+++ b/test/unit/vy_mem.c
@@ -100,11 +100,13 @@ test_iterator_restore_after_insertion()
 	end = mp_encode_uint(end, restore_on_value);
 	struct tuple *restore_on_key = vy_stmt_new_replace(format, data, end);
 	vy_stmt_set_lsn(restore_on_key, 100);
+	hint_t restore_on_hint = vy_stmt_hint(restore_on_key, key_def);
 	end = data;
 	end = mp_encode_array(end, 1);
 	end = mp_encode_uint(end, restore_on_value_reverse);
 	struct tuple *restore_on_key_reverse = vy_stmt_new_replace(format, data, end);
 	vy_stmt_set_lsn(restore_on_key_reverse, 100);
+	hint_t restore_on_hint_reverse = vy_stmt_hint(restore_on_key_reverse, key_def);
 
 	bool wrong_output = false;
 	int i_fail = 0;
@@ -187,13 +189,14 @@ test_iterator_restore_after_insertion()
 		rv.vlsn = 100;
 		const struct vy_read_view *prv = &rv;
 		vy_mem_iterator_open(&itr, &stats, mem,
-				     direct ? ITER_GE : ITER_LE, select_key,
-				     &prv);
+				     direct ? ITER_GE : ITER_LE,
+				     select_key, HINT_NONE, &prv);
+		hint_t hint;
 		struct tuple *t;
 		struct vy_history history;
 		vy_history_create(&history, &history_node_pool);
 		int rc = vy_mem_iterator_next(&itr, &history);
-		t = vy_history_last_stmt(&history);
+		t = vy_history_last_stmt(&history, &hint);
 		assert(rc == 0);
 		size_t j = 0;
 		while (t != NULL) {
@@ -214,7 +217,7 @@ test_iterator_restore_after_insertion()
 			else if(!direct && val <= middle_value)
 				break;
 			int rc = vy_mem_iterator_next(&itr, &history);
-			t = vy_history_last_stmt(&history);
+			t = vy_history_last_stmt(&history, &hint);
 			assert(rc == 0);
 		}
 		if (t == NULL && j != expected_count)
@@ -266,10 +269,13 @@ test_iterator_restore_after_insertion()
 		}
 
 		if (direct)
-			rc = vy_mem_iterator_restore(&itr, restore_on_key, &history);
+			rc = vy_mem_iterator_restore(&itr, restore_on_key,
+						     restore_on_hint, &history);
 		else
-			rc = vy_mem_iterator_restore(&itr, restore_on_key_reverse, &history);
-		t = vy_history_last_stmt(&history);
+			rc = vy_mem_iterator_restore(&itr, restore_on_key_reverse,
+						     restore_on_hint_reverse,
+						     &history);
+		t = vy_history_last_stmt(&history, &hint);
 
 		j = 0;
 		while (t != NULL) {
@@ -286,7 +292,7 @@ test_iterator_restore_after_insertion()
 			}
 			j++;
 			int rc = vy_mem_iterator_next(&itr, &history);
-			t = vy_history_last_stmt(&history);
+			t = vy_history_last_stmt(&history, &hint);
 			assert(rc == 0);
 		}
 		if (j != expected_count)
-- 
2.11.0

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

* [PATCH 08/13] vinyl: propagate tuple comparison hints to write iterator
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (6 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 07/13] vinyl: propagate tuple comparison hints to read iterator Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 11:47   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 09/13] vinyl: forward tuple comparison hints to memory tree Vladimir Davydov
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

This patch propagates hints from source streams (mem, run) to the write
iterator, which now uses the hints to efficiently compare tuples and
returns a hint along with a resulting statement.

Apart from speeding up lookups, this is also needed for multikey index
support, because multikey indexes will reuse hints to store offsets of
indexed array entries.

Note, presently the write iterator doesn't really need to return hints,
because a hint isn't needed to write a statement to a run. The only
reason to do this now is for the write iterator to conform to the
vy_stmt_stream interface. However, once multikey indexes are introduced,
we will need hints, because the write iterator will be allowed to return
a multikey tuple coming directly from the memory level, from which it's
impossible to extract a key without a hint.
---
 src/box/vinyl.c               |  3 +-
 src/box/vy_mem.c              |  5 ++-
 src/box/vy_run.c              |  6 +++-
 src/box/vy_scheduler.c        |  3 +-
 src/box/vy_stmt_stream.h      |  4 ++-
 src/box/vy_write_iterator.c   | 83 +++++++++++++++++++++++++++++++------------
 test/unit/vy_point_lookup.c   |  3 +-
 test/unit/vy_write_iterator.c |  3 +-
 8 files changed, 81 insertions(+), 29 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 80b6aecd..d8e22eb8 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -3001,11 +3001,12 @@ vy_send_range_f(struct cbus_call_msg *cmsg)
 {
 	struct vy_join_ctx *ctx = container_of(cmsg, struct vy_join_ctx, cmsg);
 
+	hint_t hint;
 	struct tuple *stmt;
 	int rc = ctx->wi->iface->start(ctx->wi);
 	if (rc != 0)
 		goto err;
-	while ((rc = ctx->wi->iface->next(ctx->wi, &stmt)) == 0 &&
+	while ((rc = ctx->wi->iface->next(ctx->wi, &stmt, &hint)) == 0 &&
 	       stmt != NULL) {
 		struct xrow_header xrow;
 		rc = vy_stmt_encode_primary(stmt, ctx->key_def,
diff --git a/src/box/vy_mem.c b/src/box/vy_mem.c
index aea0ecfb..87ad0661 100644
--- a/src/box/vy_mem.c
+++ b/src/box/vy_mem.c
@@ -641,13 +641,15 @@ vy_mem_iterator_close(struct vy_mem_iterator *itr)
 }
 
 static NODISCARD int
-vy_mem_stream_next(struct vy_stmt_stream *virt_stream, struct tuple **ret)
+vy_mem_stream_next(struct vy_stmt_stream *virt_stream,
+		   struct tuple **ret, hint_t *ret_hint)
 {
 	assert(virt_stream->iface->next == vy_mem_stream_next);
 	struct vy_mem_stream *stream = (struct vy_mem_stream *)virt_stream;
 
 	if (vy_mem_tree_iterator_is_invalid(&stream->curr_pos)) {
 		*ret = NULL;
+		*ret_hint = HINT_NONE;
 		return 0;
 	}
 
@@ -655,6 +657,7 @@ vy_mem_stream_next(struct vy_stmt_stream *virt_stream, struct tuple **ret)
 	elem = *vy_mem_tree_iterator_get_elem(&stream->mem->tree,
 					      &stream->curr_pos);
 	*ret = (struct tuple *)elem.stmt;
+	*ret_hint = elem.hint;
 	vy_mem_tree_iterator_next(&stream->mem->tree, &stream->curr_pos);
 	return 0;
 }
diff --git a/src/box/vy_run.c b/src/box/vy_run.c
index 897ece2a..2aac2b42 100644
--- a/src/box/vy_run.c
+++ b/src/box/vy_run.c
@@ -2676,11 +2676,14 @@ vy_slice_stream_search(struct vy_stmt_stream *virt_stream)
  * @return 0 on success, -1 on memory or read error.
  */
 static NODISCARD int
-vy_slice_stream_next(struct vy_stmt_stream *virt_stream, struct tuple **ret)
+vy_slice_stream_next(struct vy_stmt_stream *virt_stream,
+		     struct tuple **ret, hint_t *ret_hint)
 {
 	assert(virt_stream->iface->next == vy_slice_stream_next);
 	struct vy_slice_stream *stream = (struct vy_slice_stream *)virt_stream;
+
 	*ret = NULL;
+	*ret_hint = HINT_NONE;
 
 	/* If the slice is ended, return EOF */
 	if (stream->page_no > stream->slice->last_page_no)
@@ -2711,6 +2714,7 @@ vy_slice_stream_next(struct vy_stmt_stream *virt_stream, struct tuple **ret)
 		tuple_unref(stream->tuple);
 	stream->tuple = tuple;
 	*ret = tuple;
+	*ret_hint = hint;
 
 	/* Increment position */
 	stream->pos_in_page++;
diff --git a/src/box/vy_scheduler.c b/src/box/vy_scheduler.c
index 723bc9ee..78c4a826 100644
--- a/src/box/vy_scheduler.c
+++ b/src/box/vy_scheduler.c
@@ -1054,8 +1054,9 @@ vy_task_write_run(struct vy_task *task)
 		goto fail_abort_writer;
 	int rc;
 	int loops = 0;
+	hint_t hint;
 	struct tuple *stmt = NULL;
-	while ((rc = wi->iface->next(wi, &stmt)) == 0 && stmt != NULL) {
+	while ((rc = wi->iface->next(wi, &stmt, &hint)) == 0 && stmt != NULL) {
 		inj = errinj(ERRINJ_VY_RUN_WRITE_STMT_TIMEOUT, ERRINJ_DOUBLE);
 		if (inj != NULL && inj->dparam > 0)
 			usleep(inj->dparam * 1000000);
diff --git a/src/box/vy_stmt_stream.h b/src/box/vy_stmt_stream.h
index 098cc8eb..dd93211c 100644
--- a/src/box/vy_stmt_stream.h
+++ b/src/box/vy_stmt_stream.h
@@ -31,6 +31,7 @@
  * SUCH DAMAGE.
  */
 
+#include "key_def.h"
 #include <trivia/util.h>
 
 #if defined(__cplusplus)
@@ -55,7 +56,8 @@ typedef NODISCARD int
  * Get next tuple from a stream.
  */
 typedef NODISCARD int
-(*vy_stream_next_f)(struct vy_stmt_stream *virt_stream, struct tuple **ret);
+(*vy_stream_next_f)(struct vy_stmt_stream *virt_stream,
+		    struct tuple **ret, hint_t *ret_hint);
 
 /**
  * Close the stream.
diff --git a/src/box/vy_write_iterator.c b/src/box/vy_write_iterator.c
index 6818a31c..a2a782fb 100644
--- a/src/box/vy_write_iterator.c
+++ b/src/box/vy_write_iterator.c
@@ -47,6 +47,8 @@ struct vy_write_src {
 	struct heap_node heap_node;
 	/* Current tuple in the source (with minimal key and maximal LSN) */
 	struct tuple *tuple;
+	/** Comparison hint of the current tuple. */
+	hint_t hint;
 	/**
 	 * If this flag is set, this is a so called "virtual"
 	 * source. A virtual source does not stand for any mem or
@@ -84,6 +86,8 @@ struct vy_write_history {
 	struct vy_write_history *next;
 	/** Key. */
 	struct tuple *tuple;
+	/** Tuple comparison hint. */
+	hint_t hint;
 };
 
 /**
@@ -93,19 +97,22 @@ struct vy_write_history {
  * orders statements on the same key chronologically.
  *
  * @param tuple Key version.
+ * @param hint Tuple comparison hint.
  * @param next Next version of the key.
  *
  * @retval not NULL Created object.
  * @retval NULL     Memory error.
  */
 static inline struct vy_write_history *
-vy_write_history_new(struct tuple *tuple, struct vy_write_history *next)
+vy_write_history_new(struct tuple *tuple, hint_t hint,
+		     struct vy_write_history *next)
 {
 	struct vy_write_history *h;
 	h = region_alloc_object(&fiber()->gc, struct vy_write_history);
 	if (h == NULL)
 		return NULL;
 	h->tuple = tuple;
+	h->hint = hint;
 	assert(next == NULL || (next->tuple != NULL &&
 	       vy_stmt_lsn(next->tuple) > vy_stmt_lsn(tuple)));
 	h->next = next;
@@ -134,6 +141,8 @@ struct vy_read_view_stmt {
 	int64_t vlsn;
 	/** Result key version, visible to this @vlsn. */
 	struct tuple *tuple;
+	/** Tuple comparison hint. */
+	hint_t hint;
 	/**
 	 * A history of changes building up to this read
 	 * view. Once built, it is merged into a single
@@ -153,6 +162,7 @@ vy_read_view_stmt_destroy(struct vy_read_view_stmt *rv)
 	if (rv->tuple != NULL)
 		vy_stmt_unref_if_possible(rv->tuple);
 	rv->tuple = NULL;
+	rv->hint = HINT_NONE;
 	if (rv->history != NULL)
 		vy_write_history_destroy(rv->history);
 	rv->history = NULL;
@@ -184,6 +194,8 @@ struct vy_write_iterator {
 	 * of the old tuple from secondary indexes.
 	 */
 	struct tuple *deferred_delete_stmt;
+	/** Comparison hint of the deferred DELETE statement. */
+	hint_t deferred_delete_hint;
 	/** Length of the @read_views. */
 	int rv_count;
 	/**
@@ -223,7 +235,9 @@ heap_less(heap_t *heap, struct vy_write_src *src1, struct vy_write_src *src2)
 	struct vy_write_iterator *stream =
 		container_of(heap, struct vy_write_iterator, src_heap);
 
-	int cmp = vy_stmt_compare(src1->tuple, src2->tuple, stream->cmp_def);
+	int cmp = vy_stmt_compare_hinted(src1->tuple, src1->hint,
+					 src2->tuple, src2->hint,
+					 stream->cmp_def);
 	if (cmp != 0)
 		return cmp < 0;
 
@@ -298,7 +312,7 @@ vy_write_iterator_add_src(struct vy_write_iterator *stream,
 		if (rc != 0)
 			return rc;
 	}
-	int rc = src->stream.iface->next(&src->stream, &src->tuple);
+	int rc = src->stream.iface->next(&src->stream, &src->tuple, &src->hint);
 	if (rc != 0 || src->tuple == NULL)
 		goto stop;
 
@@ -363,12 +377,17 @@ vy_write_iterator_new(struct key_def *cmp_def, bool is_primary,
 	}
 	stream->stmt_i = -1;
 	stream->rv_count = count;
+	stream->read_views[0].hint = HINT_NONE;
 	stream->read_views[0].vlsn = INT64_MAX;
 	count--;
 	struct vy_read_view *rv;
 	/* Descending order. */
-	rlist_foreach_entry(rv, read_views, in_read_views)
-		stream->read_views[count--].vlsn = rv->vlsn;
+	rlist_foreach_entry(rv, read_views, in_read_views) {
+		struct vy_read_view_stmt *rv_stmt;
+		rv_stmt = &stream->read_views[count--];
+		rv_stmt->hint = HINT_NONE;
+		rv_stmt->vlsn = rv->vlsn;
+	}
 	assert(count == 0);
 
 	stream->base.iface = &vy_slice_stream_iface;
@@ -378,6 +397,7 @@ vy_write_iterator_new(struct key_def *cmp_def, bool is_primary,
 	stream->is_primary = is_primary;
 	stream->is_last_level = is_last_level;
 	stream->deferred_delete_handler = handler;
+	stream->deferred_delete_hint = HINT_NONE;
 	return &stream->base;
 }
 
@@ -423,6 +443,7 @@ vy_write_iterator_stop(struct vy_stmt_stream *vstream)
 	if (stream->deferred_delete_stmt != NULL) {
 		vy_stmt_unref_if_possible(stream->deferred_delete_stmt);
 		stream->deferred_delete_stmt = NULL;
+		stream->deferred_delete_hint = HINT_NONE;
 	}
 	struct vy_deferred_delete_handler *handler =
 			stream->deferred_delete_handler;
@@ -489,7 +510,7 @@ vy_write_iterator_merge_step(struct vy_write_iterator *stream)
 {
 	struct vy_write_src *src = vy_source_heap_top(&stream->src_heap);
 	assert(src != NULL);
-	int rc = src->stream.iface->next(&src->stream, &src->tuple);
+	int rc = src->stream.iface->next(&src->stream, &src->tuple, &src->hint);
 	if (rc != 0)
 		return rc;
 	if (src->tuple != NULL)
@@ -519,11 +540,10 @@ vy_write_iterator_get_vlsn(struct vy_write_iterator *stream, int rv_i)
 }
 
 /**
- * Remember the current tuple of the @src as a part of the
- * current read view.
- * @param History objects allocator.
+ * Remember a tuple as a part of the current read view.
  * @param stream Write iterator.
- * @param src Source of the wanted tuple.
+ * @param tuple Tuple to push.
+ * @param hint Tuple comparison hint.
  * @param current_rv_i Index of the current read view.
  *
  * @retval  0 Success.
@@ -531,13 +551,13 @@ vy_write_iterator_get_vlsn(struct vy_write_iterator *stream, int rv_i)
  */
 static inline int
 vy_write_iterator_push_rv(struct vy_write_iterator *stream,
-			  struct tuple *tuple, int current_rv_i)
+			  struct tuple *tuple, hint_t hint, int current_rv_i)
 {
 	assert(current_rv_i < stream->rv_count);
 	struct vy_read_view_stmt *rv = &stream->read_views[current_rv_i];
 	assert(rv->vlsn >= vy_stmt_lsn(tuple));
 	struct vy_write_history *h =
-		vy_write_history_new(tuple, rv->history);
+		vy_write_history_new(tuple, hint, rv->history);
 	if (h == NULL)
 		return -1;
 	rv->history = h;
@@ -557,11 +577,14 @@ vy_write_iterator_push_rv(struct vy_write_iterator *stream,
  * @retval     NULL End of the key (not the end of the sources).
  */
 static inline struct tuple *
-vy_write_iterator_pop_read_view_stmt(struct vy_write_iterator *stream)
+vy_write_iterator_pop_read_view_stmt(struct vy_write_iterator *stream,
+				     hint_t *hint)
 {
 	struct vy_read_view_stmt *rv;
-	if (stream->rv_used_count == 0)
+	if (stream->rv_used_count == 0) {
+		*hint = HINT_NONE;
 		return NULL;
+	}
 	/* Find a next non-empty history element. */
 	do {
 		assert(stream->stmt_i + 1 < stream->rv_count);
@@ -574,7 +597,9 @@ vy_write_iterator_pop_read_view_stmt(struct vy_write_iterator *stream)
 	if (stream->last_stmt != NULL)
 		vy_stmt_unref_if_possible(stream->last_stmt);
 	stream->last_stmt = rv->tuple;
+	*hint = rv->hint;
 	rv->tuple = NULL;
+	rv->hint = HINT_NONE;
 	return stream->last_stmt;
 }
 
@@ -590,7 +615,7 @@ vy_write_iterator_pop_read_view_stmt(struct vy_write_iterator *stream)
  */
 static int
 vy_write_iterator_deferred_delete(struct vy_write_iterator *stream,
-				  struct tuple *stmt)
+				  struct tuple *stmt, hint_t hint)
 {
 	/*
 	 * UPSERTs cannot change secondary index parts neither
@@ -613,6 +638,7 @@ vy_write_iterator_deferred_delete(struct vy_write_iterator *stream,
 			return -1;
 		vy_stmt_unref_if_possible(stream->deferred_delete_stmt);
 		stream->deferred_delete_stmt = NULL;
+		stream->deferred_delete_hint = HINT_NONE;
 	}
 	/*
 	 * Remember the current statement if it is marked with
@@ -625,6 +651,7 @@ vy_write_iterator_deferred_delete(struct vy_write_iterator *stream,
 		       vy_stmt_type(stmt) == IPROTO_REPLACE);
 		vy_stmt_ref_if_possible(stmt);
 		stream->deferred_delete_stmt = stmt;
+		stream->deferred_delete_hint = hint;
 	}
 	return 0;
 }
@@ -672,6 +699,7 @@ vy_write_iterator_build_history(struct vy_write_iterator *stream,
 	struct vy_write_src end_of_key_src;
 	end_of_key_src.is_end_of_key = true;
 	end_of_key_src.tuple = src->tuple;
+	end_of_key_src.hint = src->hint;
 	int rc = vy_source_heap_insert(&stream->src_heap, &end_of_key_src);
 	if (rc) {
 		diag_set(OutOfMemory, sizeof(void *),
@@ -713,7 +741,7 @@ vy_write_iterator_build_history(struct vy_write_iterator *stream,
 		 */
 		if (stream->is_primary) {
 			rc = vy_write_iterator_deferred_delete(stream,
-							       src->tuple);
+						src->tuple, src->hint);
 			if (rc != 0)
 				break;
 		}
@@ -751,7 +779,7 @@ vy_write_iterator_build_history(struct vy_write_iterator *stream,
 			goto next_lsn;
 		}
 
-		rc = vy_write_iterator_push_rv(stream, src->tuple,
+		rc = vy_write_iterator_push_rv(stream, src->tuple, src->hint,
 					       current_rv_i);
 		if (rc != 0)
 			break;
@@ -804,6 +832,7 @@ next_lsn:
  *
  * @param stream Write iterator.
  * @param prev_tuple Tuple from the previous read view (can be NULL).
+ * @param prev_hint Comparison hint of the previous tuple.
  * @param rv Read view to merge.
  * @param is_first_insert Set if the oldest statement for the
  * current key among all sources is an INSERT.
@@ -812,11 +841,13 @@ next_lsn:
  * @retval -1 Memory error.
  */
 static NODISCARD int
-vy_read_view_merge(struct vy_write_iterator *stream, struct tuple *prev_tuple,
+vy_read_view_merge(struct vy_write_iterator *stream,
+		   struct tuple *prev_tuple, hint_t prev_hint,
 		   struct vy_read_view_stmt *rv, bool is_first_insert)
 {
 	assert(rv != NULL);
 	assert(rv->tuple == NULL);
+	assert(rv->hint == HINT_NONE);
 	assert(rv->history != NULL);
 	struct vy_write_history *h = rv->history;
 	/*
@@ -851,6 +882,7 @@ vy_read_view_merge(struct vy_write_iterator *stream, struct tuple *prev_tuple,
 			return -1;
 		vy_stmt_unref_if_possible(h->tuple);
 		h->tuple = applied;
+		h->hint = prev_hint;
 	}
 	/* Squash the rest of UPSERTs. */
 	struct vy_write_history *result = h;
@@ -874,8 +906,10 @@ vy_read_view_merge(struct vy_write_iterator *stream, struct tuple *prev_tuple,
 		result->next = h;
 	}
 	rv->tuple = result->tuple;
+	rv->hint = result->hint;
 	rv->history = NULL;
 	result->tuple = NULL;
+	result->hint = HINT_NONE;
 	assert(result->next == NULL);
 	/*
 	 * The write iterator generates deferred DELETEs for all
@@ -908,6 +942,7 @@ vy_read_view_merge(struct vy_write_iterator *stream, struct tuple *prev_tuple,
 		 */
 		vy_stmt_unref_if_possible(rv->tuple);
 		rv->tuple = NULL;
+		rv->hint = HINT_NONE;
 	} else if ((is_first_insert &&
 		    vy_stmt_type(rv->tuple) == IPROTO_REPLACE) ||
 		   (!is_first_insert &&
@@ -975,11 +1010,12 @@ vy_write_iterator_build_read_views(struct vy_write_iterator *stream, int *count)
 	 * here > 0.
 	 */
 	assert(rv >= &stream->read_views[0] && rv->history != NULL);
+	hint_t prev_hint = HINT_NONE;
 	struct tuple *prev_tuple = NULL;
 	for (; rv >= &stream->read_views[0]; --rv) {
 		if (rv->history == NULL)
 			continue;
-		if (vy_read_view_merge(stream, prev_tuple, rv,
+		if (vy_read_view_merge(stream, prev_tuple, prev_hint, rv,
 				       is_first_insert) != 0)
 			goto error;
 		assert(rv->history == NULL);
@@ -988,6 +1024,7 @@ vy_write_iterator_build_read_views(struct vy_write_iterator *stream, int *count)
 		stream->rv_used_count++;
 		++*count;
 		prev_tuple = rv->tuple;
+		prev_hint = rv->hint;
 	}
 	region_truncate(region, used);
 	return 0;
@@ -1007,7 +1044,7 @@ error:
  */
 static NODISCARD int
 vy_write_iterator_next(struct vy_stmt_stream *vstream,
-		       struct tuple **ret)
+		       struct tuple **ret, hint_t *ret_hint)
 {
 	assert(vstream->iface->next == vy_write_iterator_next);
 	struct vy_write_iterator *stream = (struct vy_write_iterator *)vstream;
@@ -1015,7 +1052,7 @@ vy_write_iterator_next(struct vy_stmt_stream *vstream,
 	 * Try to get the next statement from the current key
 	 * read view statements sequence.
 	 */
-	*ret = vy_write_iterator_pop_read_view_stmt(stream);
+	*ret = vy_write_iterator_pop_read_view_stmt(stream, ret_hint);
 	if (*ret != NULL)
 		return 0;
 	/*
@@ -1036,7 +1073,9 @@ vy_write_iterator_next(struct vy_stmt_stream *vstream,
 			if (stream->last_stmt != NULL)
 				vy_stmt_unref_if_possible(stream->last_stmt);
 			*ret = stream->last_stmt = stream->deferred_delete_stmt;
+			*ret_hint = stream->deferred_delete_hint;
 			stream->deferred_delete_stmt = NULL;
+			stream->deferred_delete_hint = HINT_NONE;
 			return 0;
 		}
 	}
@@ -1059,7 +1098,7 @@ vy_write_iterator_next(struct vy_stmt_stream *vstream,
 			break;
 	}
 	/* Again try to get the statement, after calling next_key(). */
-	*ret = vy_write_iterator_pop_read_view_stmt(stream);
+	*ret = vy_write_iterator_pop_read_view_stmt(stream, ret_hint);
 	return 0;
 }
 
diff --git a/test/unit/vy_point_lookup.c b/test/unit/vy_point_lookup.c
index bb9798a9..eef50c30 100644
--- a/test/unit/vy_point_lookup.c
+++ b/test/unit/vy_point_lookup.c
@@ -30,8 +30,9 @@ write_run(struct vy_run *run, const char *dir_name,
 	if (wi->iface->start(wi) != 0)
 		goto fail_abort_writer;
 	int rc;
+	hint_t hint;
 	struct tuple *stmt = NULL;
-	while ((rc = wi->iface->next(wi, &stmt)) == 0 && stmt != NULL) {
+	while ((rc = wi->iface->next(wi, &stmt, &hint)) == 0 && stmt != NULL) {
 		rc = vy_run_writer_append_stmt(&writer, stmt);
 		if (rc != 0)
 			break;
diff --git a/test/unit/vy_write_iterator.c b/test/unit/vy_write_iterator.c
index ecbc6281..4d55f743 100644
--- a/test/unit/vy_write_iterator.c
+++ b/test/unit/vy_write_iterator.c
@@ -110,11 +110,12 @@ compare_write_iterator_results(const struct vy_stmt_template *content,
 	fail_if(wi == NULL);
 	fail_if(vy_write_iterator_new_mem(wi, mem) != 0);
 
+	hint_t hint;
 	struct tuple *ret;
 	fail_if(wi->iface->start(wi) != 0);
 	int i = 0;
 	do {
-		fail_if(wi->iface->next(wi, &ret) != 0);
+		fail_if(wi->iface->next(wi, &ret, &hint) != 0);
 		if (ret == NULL)
 			break;
 		fail_if(i >= expected_count);
-- 
2.11.0

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

* [PATCH 09/13] vinyl: forward tuple comparison hints to memory tree
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (7 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 08/13] vinyl: propagate tuple comparison hints to write iterator Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 12:10   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 10/13] vinyl: forward tuple comparison hints to cache tree Vladimir Davydov
                   ` (3 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

Instead of computing a statement comparison hint in a vy_mem method,
forward it from the upper level, which already has it computed.

Apart from eliminating extra calls to vy_stmt_hint, this is also a
prerequisite for multikey indexes, which will reuse hints to store
offsets of indexed array entries and thus make hints impossible to be
computed in an arbitrary place in code.
---
 src/box/vinyl.c                 | 10 ++++++----
 src/box/vy_lsm.c                | 24 +++++++++++++-----------
 src/box/vy_lsm.h                | 10 +++++++---
 src/box/vy_mem.c                | 12 ++++--------
 src/box/vy_mem.h                | 11 +++++++----
 src/box/vy_tx.c                 | 16 ++++++++++------
 test/unit/vy_iterators_helper.c |  5 +++--
 test/unit/vy_mem.c              | 13 ++++++++-----
 8 files changed, 58 insertions(+), 43 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index d8e22eb8..3e96f5f5 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -3583,7 +3583,7 @@ vy_squash_process(struct vy_squash *squash)
 	 */
 	size_t mem_used_before = lsregion_used(&env->mem_env.allocator);
 	const struct tuple *region_stmt = NULL;
-	int rc = vy_lsm_set(lsm, mem, result, &region_stmt);
+	int rc = vy_lsm_set(lsm, mem, result, hint, &region_stmt);
 	tuple_unref(result);
 	size_t mem_used_after = lsregion_used(&env->mem_env.allocator);
 	assert(mem_used_after >= mem_used_before);
@@ -4006,12 +4006,13 @@ static int
 vy_build_insert_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
 		     const struct tuple *stmt, int64_t lsn)
 {
+	hint_t hint = vy_stmt_hint(stmt, lsm->cmp_def);
 	const struct tuple *region_stmt = vy_stmt_dup_lsregion(stmt,
 				&mem->env->allocator, mem->generation);
 	if (region_stmt == NULL)
 		return -1;
 	vy_stmt_set_lsn((struct tuple *)region_stmt, lsn);
-	if (vy_mem_insert(mem, region_stmt) != 0)
+	if (vy_mem_insert(mem, region_stmt, hint) != 0)
 		return -1;
 	vy_mem_commit_stmt(mem, region_stmt);
 	vy_stmt_counter_acct_tuple(&lsm->stat.memory.count, region_stmt);
@@ -4470,10 +4471,11 @@ vy_deferred_delete_on_replace(struct trigger *trigger, void *event)
 				break;
 			mem = lsm->mem;
 		}
-		rc = vy_lsm_set(lsm, mem, delete, &region_stmt);
+		hint_t hint = vy_stmt_hint(delete, lsm->cmp_def);
+		rc = vy_lsm_set(lsm, mem, delete, hint, &region_stmt);
 		if (rc != 0)
 			break;
-		vy_lsm_commit_stmt(lsm, mem, region_stmt);
+		vy_lsm_commit_stmt(lsm, mem, region_stmt, hint);
 
 		if (!is_first_statement)
 			continue;
diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index 52fd429d..4065f711 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -885,7 +885,8 @@ vy_lsm_delete_mem(struct vy_lsm *lsm, struct vy_mem *mem)
 
 int
 vy_lsm_set(struct vy_lsm *lsm, struct vy_mem *mem,
-	   const struct tuple *stmt, const struct tuple **region_stmt)
+	   const struct tuple *stmt, hint_t hint,
+	   const struct tuple **region_stmt)
 {
 	uint32_t format_id = stmt->format_id;
 
@@ -917,9 +918,9 @@ vy_lsm_set(struct vy_lsm *lsm, struct vy_mem *mem,
 		return -1;
 	}
 	if (vy_stmt_type(*region_stmt) != IPROTO_UPSERT)
-		return vy_mem_insert(mem, *region_stmt);
+		return vy_mem_insert(mem, *region_stmt, hint);
 	else
-		return vy_mem_insert_upsert(mem, *region_stmt);
+		return vy_mem_insert_upsert(mem, *region_stmt, hint);
 }
 
 /**
@@ -931,10 +932,11 @@ vy_lsm_set(struct vy_lsm *lsm, struct vy_mem *mem,
  * @param lsm   LSM tree the statement was committed to.
  * @param mem   In-memory tree where the statement was saved.
  * @param stmt  UPSERT statement to squash.
+ * @param hint  Statement comparison hint.
  */
 static void
 vy_lsm_commit_upsert(struct vy_lsm *lsm, struct vy_mem *mem,
-		       const struct tuple *stmt)
+		     const struct tuple *stmt, hint_t hint)
 {
 	assert(vy_stmt_type(stmt) == IPROTO_UPSERT);
 	assert(vy_stmt_lsn(stmt) < MAX_LSN);
@@ -971,7 +973,7 @@ vy_lsm_commit_upsert(struct vy_lsm *lsm, struct vy_mem *mem,
 		 * one-key continous UPSERTs sequence.
 		 */
 #ifndef NDEBUG
-		older = vy_mem_older_lsn(mem, stmt);
+		older = vy_mem_older_lsn(mem, stmt, hint);
 		assert(older != NULL && vy_stmt_type(older) == IPROTO_UPSERT &&
 		       vy_stmt_n_upserts(older) == VY_UPSERT_THRESHOLD - 1);
 #endif
@@ -1000,7 +1002,7 @@ vy_lsm_commit_upsert(struct vy_lsm *lsm, struct vy_mem *mem,
 	if (n_upserts == 0 &&
 	    lsm->stat.memory.count.rows == lsm->mem->count.rows &&
 	    lsm->run_count == 0) {
-		older = vy_mem_older_lsn(mem, stmt);
+		older = vy_mem_older_lsn(mem, stmt, hint);
 		assert(older == NULL || vy_stmt_type(older) != IPROTO_UPSERT);
 		struct tuple *upserted =
 			vy_apply_upsert(stmt, older, lsm->cmp_def, false);
@@ -1037,7 +1039,7 @@ vy_lsm_commit_upsert(struct vy_lsm *lsm, struct vy_mem *mem,
 			return;
 		}
 
-		int rc = vy_lsm_set(lsm, mem, upserted, &region_stmt);
+		int rc = vy_lsm_set(lsm, mem, upserted, hint, &region_stmt);
 		/**
 		 * Since we have already allocated mem statement and
 		 * now we replacing one statement with another, the
@@ -1052,14 +1054,14 @@ vy_lsm_commit_upsert(struct vy_lsm *lsm, struct vy_mem *mem,
 
 void
 vy_lsm_commit_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
-		     const struct tuple *stmt)
+		     const struct tuple *stmt, hint_t hint)
 {
 	vy_mem_commit_stmt(mem, stmt);
 
 	lsm->stat.memory.count.rows++;
 
 	if (vy_stmt_type(stmt) == IPROTO_UPSERT)
-		vy_lsm_commit_upsert(lsm, mem, stmt);
+		vy_lsm_commit_upsert(lsm, mem, stmt, hint);
 
 	vy_stmt_counter_acct_tuple(&lsm->stat.put, stmt);
 
@@ -1069,9 +1071,9 @@ vy_lsm_commit_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
 
 void
 vy_lsm_rollback_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
-		       const struct tuple *stmt)
+		       const struct tuple *stmt, hint_t hint)
 {
-	vy_mem_rollback_stmt(mem, stmt);
+	vy_mem_rollback_stmt(mem, stmt, hint);
 
 	/* Invalidate cache element. */
 	vy_cache_on_write(&lsm->cache, stmt, NULL);
diff --git a/src/box/vy_lsm.h b/src/box/vy_lsm.h
index c85bd2b3..541c49f8 100644
--- a/src/box/vy_lsm.h
+++ b/src/box/vy_lsm.h
@@ -590,6 +590,7 @@ vy_lsm_force_compaction(struct vy_lsm *lsm);
  * @param lsm         LSM tree the statement is for.
  * @param mem         In-memory tree to insert the statement into.
  * @param stmt        Statement, allocated on malloc().
+ * @param hint        Statement comparison hint.
  * @param region_stmt NULL or the same statement, allocated on
  *                    lsregion.
  *
@@ -598,7 +599,8 @@ vy_lsm_force_compaction(struct vy_lsm *lsm);
  */
 int
 vy_lsm_set(struct vy_lsm *lsm, struct vy_mem *mem,
-	   const struct tuple *stmt, const struct tuple **region_stmt);
+	   const struct tuple *stmt, hint_t hint,
+	   const struct tuple **region_stmt);
 
 /**
  * Confirm that the statement stays in the in-memory index of
@@ -607,10 +609,11 @@ vy_lsm_set(struct vy_lsm *lsm, struct vy_mem *mem,
  * @param lsm   LSM tree the statement is for.
  * @param mem   In-memory tree where the statement was saved.
  * @param stmt  Statement allocated from lsregion.
+ * @param hint  Statement comparison hint.
  */
 void
 vy_lsm_commit_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
-		   const struct tuple *stmt);
+		   const struct tuple *stmt, hint_t hint);
 
 /**
  * Erase a statement from the in-memory index of an LSM tree.
@@ -618,10 +621,11 @@ vy_lsm_commit_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
  * @param lsm   LSM tree to erase from.
  * @param mem   In-memory tree where the statement was saved.
  * @param stmt  Statement allocated from lsregion.
+ * @param hint  Statement comparison hint.
  */
 void
 vy_lsm_rollback_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
-		     const struct tuple *stmt);
+		     const struct tuple *stmt, hint_t hint);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/box/vy_mem.c b/src/box/vy_mem.c
index 87ad0661..4947df2a 100644
--- a/src/box/vy_mem.c
+++ b/src/box/vy_mem.c
@@ -149,9 +149,8 @@ vy_mem_delete(struct vy_mem *index)
 }
 
 const struct tuple *
-vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt)
+vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt, hint_t hint)
 {
-	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
 	struct vy_mem_tree_key tree_key = vy_mem_tree_key(stmt, hint,
 						vy_stmt_lsn(stmt) - 1);
 	bool exact = false;
@@ -170,14 +169,13 @@ vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt)
 }
 
 int
-vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt)
+vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt, hint_t hint)
 {
 	assert(vy_stmt_type(stmt) == IPROTO_UPSERT);
 	/* Check if the statement can be inserted in the vy_mem. */
 	assert(stmt->format_id == tuple_format_id(mem->format));
 	/* The statement must be from a lsregion. */
 	assert(!vy_stmt_is_refable(stmt));
-	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
 	size_t size = tuple_size(stmt);
 	struct vy_mem_tree_elem new = vy_mem_tree_elem(stmt, hint);
 	struct vy_mem_tree_elem replaced = vy_mem_tree_elem_invalid();
@@ -233,7 +231,7 @@ vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt)
 }
 
 int
-vy_mem_insert(struct vy_mem *mem, const struct tuple *stmt)
+vy_mem_insert(struct vy_mem *mem, const struct tuple *stmt, hint_t hint)
 {
 	assert(vy_stmt_type(stmt) != IPROTO_UPSERT);
 	/* Check if the statement can be inserted in the vy_mem. */
@@ -241,7 +239,6 @@ vy_mem_insert(struct vy_mem *mem, const struct tuple *stmt)
 	       stmt->format_id == tuple_format_id(mem->format));
 	/* The statement must be from a lsregion. */
 	assert(!vy_stmt_is_refable(stmt));
-	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
 	size_t size = tuple_size(stmt);
 	struct vy_mem_tree_elem new = vy_mem_tree_elem(stmt, hint);
 	struct vy_mem_tree_elem replaced = vy_mem_tree_elem_invalid();
@@ -281,11 +278,10 @@ vy_mem_commit_stmt(struct vy_mem *mem, const struct tuple *stmt)
 }
 
 void
-vy_mem_rollback_stmt(struct vy_mem *mem, const struct tuple *stmt)
+vy_mem_rollback_stmt(struct vy_mem *mem, const struct tuple *stmt, hint_t hint)
 {
 	/* This is the statement we've inserted before. */
 	assert(!vy_stmt_is_refable(stmt));
-	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
 	struct vy_mem_tree_elem elem = vy_mem_tree_elem(stmt, hint);
 	int rc = vy_mem_tree_delete(&mem->tree, elem);
 	assert(rc == 0);
diff --git a/src/box/vy_mem.h b/src/box/vy_mem.h
index f31deaed..360e1d9c 100644
--- a/src/box/vy_mem.h
+++ b/src/box/vy_mem.h
@@ -309,30 +309,32 @@ vy_mem_delete(struct vy_mem *index);
  * Return the older statement for the given one.
  */
 const struct tuple *
-vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt);
+vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt, hint_t hint);
 
 /**
  * Insert a statement into the in-memory level.
  * @param mem        vy_mem.
  * @param stmt       Vinyl statement.
+ * @param hint       Statement comparison hint.
  *
  * @retval  0 Success.
  * @retval -1 Memory error.
  */
 int
-vy_mem_insert(struct vy_mem *mem, const struct tuple *stmt);
+vy_mem_insert(struct vy_mem *mem, const struct tuple *stmt, hint_t hint);
 
 /**
  * Insert an upsert statement into the mem.
  *
  * @param mem Mem to insert to.
  * @param stmt Upsert statement to insert.
+ * @param hint Statement comparison hint.
  *
  * @retval  0 Success.
  * @retval -1 Memory error.
  */
 int
-vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt);
+vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt, hint_t hint);
 
 /**
  * Confirm insertion of a statement into the in-memory level.
@@ -346,9 +348,10 @@ vy_mem_commit_stmt(struct vy_mem *mem, const struct tuple *stmt);
  * Remove a statement from the in-memory level.
  * @param mem        vy_mem.
  * @param stmt       Vinyl statement.
+ * @param hint       Statement comparison hint.
  */
 void
-vy_mem_rollback_stmt(struct vy_mem *mem, const struct tuple *stmt);
+vy_mem_rollback_stmt(struct vy_mem *mem, const struct tuple *stmt, hint_t hint);
 
 /**
  * Iterator for in-memory level.
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index d8d7c905..fd8822a1 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -490,6 +490,7 @@ vy_tx_write_prepare(struct txv *v)
  * @param lsm         LSM tree to write to.
  * @param mem         In-memory tree to write to.
  * @param stmt        Statement allocated with malloc().
+ * @param hint        Statement comparison hint.
  * @param region_stmt NULL or the same statement as stmt,
  *                    but allocated on lsregion.
  *
@@ -498,7 +499,8 @@ vy_tx_write_prepare(struct txv *v)
  */
 static int
 vy_tx_write(struct vy_lsm *lsm, struct vy_mem *mem,
-	    struct tuple *stmt, const struct tuple **region_stmt)
+	    struct tuple *stmt, hint_t hint,
+	    const struct tuple **region_stmt)
 {
 	assert(vy_stmt_is_refable(stmt));
 	assert(*region_stmt == NULL || !vy_stmt_is_refable(*region_stmt));
@@ -520,7 +522,7 @@ vy_tx_write(struct vy_lsm *lsm, struct vy_mem *mem,
 			tuple_unref(deleted);
 			if (applied != NULL) {
 				assert(vy_stmt_type(applied) == IPROTO_REPLACE);
-				int rc = vy_lsm_set(lsm, mem, applied,
+				int rc = vy_lsm_set(lsm, mem, applied, hint,
 						    region_stmt);
 				tuple_unref(applied);
 				return rc;
@@ -534,7 +536,7 @@ vy_tx_write(struct vy_lsm *lsm, struct vy_mem *mem,
 		/* Invalidate cache element. */
 		vy_cache_on_write(&lsm->cache, stmt, NULL);
 	}
-	return vy_lsm_set(lsm, mem, stmt, region_stmt);
+	return vy_lsm_set(lsm, mem, stmt, hint, region_stmt);
 }
 
 /**
@@ -775,7 +777,8 @@ vy_tx_prepare(struct vy_tx *tx)
 		vy_stmt_set_lsn(v->stmt, MAX_LSN + tx->psn);
 		const struct tuple **region_stmt =
 			(type == IPROTO_DELETE) ? &delete : &repsert;
-		if (vy_tx_write(lsm, v->mem, v->stmt, region_stmt) != 0)
+		if (vy_tx_write(lsm, v->mem, v->stmt, v->hint,
+				region_stmt) != 0)
 			return -1;
 		v->region_stmt = *region_stmt;
 	}
@@ -805,7 +808,8 @@ vy_tx_commit(struct vy_tx *tx, int64_t lsn)
 	stailq_foreach_entry(v, &tx->log, next_in_log) {
 		if (v->region_stmt != NULL) {
 			vy_stmt_set_lsn((struct tuple *)v->region_stmt, lsn);
-			vy_lsm_commit_stmt(v->lsm, v->mem, v->region_stmt);
+			vy_lsm_commit_stmt(v->lsm, v->mem,
+					   v->region_stmt, v->hint);
 		}
 		if (v->mem != NULL)
 			vy_mem_unpin(v->mem);
@@ -851,7 +855,7 @@ vy_tx_rollback_after_prepare(struct vy_tx *tx)
 	stailq_foreach_entry(v, &tx->log, next_in_log) {
 		if (v->region_stmt != NULL)
 			vy_lsm_rollback_stmt(v->lsm, v->mem,
-					     v->region_stmt);
+					     v->region_stmt, v->hint);
 		if (v->mem != NULL)
 			vy_mem_unpin(v->mem);
 	}
diff --git a/test/unit/vy_iterators_helper.c b/test/unit/vy_iterators_helper.c
index 173d58db..d2ac21a0 100644
--- a/test/unit/vy_iterators_helper.c
+++ b/test/unit/vy_iterators_helper.c
@@ -136,12 +136,13 @@ vy_mem_insert_template(struct vy_mem *mem, const struct vy_stmt_template *templ)
 	struct tuple *stmt = vy_new_simple_stmt(mem->format, templ);
 	struct tuple *region_stmt = vy_stmt_dup_lsregion(stmt,
 			&mem->env->allocator, mem->generation);
+	hint_t hint = vy_stmt_hint(stmt, mem->cmp_def);
 	assert(region_stmt != NULL);
 	tuple_unref(stmt);
 	if (templ->type == IPROTO_UPSERT)
-		vy_mem_insert_upsert(mem, region_stmt);
+		vy_mem_insert_upsert(mem, region_stmt, hint);
 	else
-		vy_mem_insert(mem, region_stmt);
+		vy_mem_insert(mem, region_stmt, hint);
 	return region_stmt;
 }
 
diff --git a/test/unit/vy_mem.c b/test/unit/vy_mem.c
index 4453e076..0355653a 100644
--- a/test/unit/vy_mem.c
+++ b/test/unit/vy_mem.c
@@ -27,6 +27,7 @@ test_basic(void)
 
 	/* Check dump lsn */
 	const struct tuple *stmt = vy_mem_insert_template(mem, &stmts[0]);
+	hint_t hint = vy_stmt_hint(stmt, key_def);
 	is(mem->dump_lsn, -1, "mem->dump_lsn after prepare");
 	vy_mem_commit_stmt(mem, stmt);
 	is(mem->dump_lsn, 100, "mem->dump_lsn after commit");
@@ -34,17 +35,19 @@ test_basic(void)
 	/* Check vy_mem_older_lsn */
 	const struct tuple *older = stmt;
 	stmt = vy_mem_insert_template(mem, &stmts[1]);
-	is(vy_mem_older_lsn(mem, stmt), older, "vy_mem_older_lsn 1");
-	is(vy_mem_older_lsn(mem, older), NULL, "vy_mem_older_lsn 2");
+	hint = vy_stmt_hint(stmt, key_def);
+	is(vy_mem_older_lsn(mem, stmt, hint), older, "vy_mem_older_lsn 1");
+	is(vy_mem_older_lsn(mem, older, hint), NULL, "vy_mem_older_lsn 2");
 	vy_mem_commit_stmt(mem, stmt);
 
 	/* Check rollback  */
 	const struct tuple *olderolder = stmt;
 	older = vy_mem_insert_template(mem, &stmts[2]);
 	stmt = vy_mem_insert_template(mem, &stmts[3]);
-	is(vy_mem_older_lsn(mem, stmt), older, "vy_mem_rollback 1");
-	vy_mem_rollback_stmt(mem, older);
-	is(vy_mem_older_lsn(mem, stmt), olderolder, "vy_mem_rollback 2");
+	hint = vy_stmt_hint(stmt, key_def);
+	is(vy_mem_older_lsn(mem, stmt, hint), older, "vy_mem_rollback 1");
+	vy_mem_rollback_stmt(mem, older, hint);
+	is(vy_mem_older_lsn(mem, stmt, hint), olderolder, "vy_mem_rollback 2");
 
 	/* Check version  */
 	stmt = vy_mem_insert_template(mem, &stmts[4]);
-- 
2.11.0

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

* [PATCH 10/13] vinyl: forward tuple comparison hints to cache tree
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (8 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 09/13] vinyl: forward tuple comparison hints to memory tree Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 12:11   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 11/13] vinyl: forward tuple comparison hints to read iterator Vladimir Davydov
                   ` (2 subsequent siblings)
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

Instead of computing a statement comparison hint in a vy_cache method,
forward it from the read iterator, which already has it computed.

Apart from eliminating extra calls to vy_stmt_hint, this is also a
prerequisite for multikey indexes, which will reuse hints to store
offsets of indexed array entries and thus make hints impossible to be
computed in an arbitrary place in code.
---
 src/box/vinyl.c                 | 56 +++++++++++++++++++++++++----------------
 src/box/vy_cache.c              | 14 +++--------
 src/box/vy_cache.h              | 11 +++++---
 src/box/vy_lsm.c                |  4 +--
 src/box/vy_point_lookup.c       |  8 +++---
 src/box/vy_point_lookup.h       |  5 ++--
 src/box/vy_read_iterator.c      | 14 ++++++++---
 src/box/vy_read_iterator.h      | 12 +++++++--
 src/box/vy_tx.c                 |  4 +--
 test/unit/vy_iterators_helper.c | 11 ++++++--
 test/unit/vy_point_lookup.c     |  3 ++-
 11 files changed, 87 insertions(+), 55 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 3e96f5f5..8832db2c 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1142,8 +1142,9 @@ vinyl_space_check_format(struct space *space, struct tuple_format *format)
 			      &env->xm->p_committed_read_view);
 	int rc;
 	int loops = 0;
+	hint_t hint;
 	struct tuple *tuple;
-	while ((rc = vy_read_iterator_next(&itr, &tuple)) == 0) {
+	while ((rc = vy_read_iterator_next(&itr, &tuple, &hint)) == 0) {
 		/*
 		 * Read iterator yields only when it reads runs.
 		 * Yield periodically in order not to stall the
@@ -1309,6 +1310,7 @@ vy_is_committed(struct vy_env *env, struct space *space)
  * @param tx          Current transaction.
  * @param rv          Read view.
  * @param tuple       Tuple read from a secondary index.
+ * @param hint        Comparison hint of the secondary tuple.
  * @param[out] result The found tuple is stored here. Must be
  *                    unreferenced after usage.
  *
@@ -1318,7 +1320,8 @@ vy_is_committed(struct vy_env *env, struct space *space)
 static int
 vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 			  const struct vy_read_view **rv,
-			  struct tuple *tuple, struct tuple **result)
+			  struct tuple *tuple, hint_t hint,
+			  struct tuple **result)
 {
 	int rc = 0;
 	assert(lsm->index_id > 0);
@@ -1343,7 +1346,9 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 		tuple_ref(key);
 	}
 
-	if (vy_point_lookup(lsm->pk, tx, rv, key, result) != 0) {
+	hint_t primary_hint;
+	if (vy_point_lookup(lsm->pk, tx, rv, key, result,
+			    &primary_hint) != 0) {
 		rc = -1;
 		goto out;
 	}
@@ -1368,7 +1373,7 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 		 * chain intersections, which are not tolerated by
 		 * the tuple cache implementation.
 		 */
-		vy_cache_on_write(&lsm->cache, tuple, NULL);
+		vy_cache_on_write(&lsm->cache, tuple, hint, NULL);
 		goto out;
 	}
 
@@ -1387,7 +1392,8 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 	}
 
 	if ((*rv)->vlsn == INT64_MAX)
-		vy_cache_add(&lsm->pk->cache, *result, NULL, key, ITER_EQ);
+		vy_cache_add(&lsm->pk->cache, *result, primary_hint,
+			     NULL, HINT_NONE, key, ITER_EQ);
 out:
 	tuple_unref(key);
 	return rc;
@@ -1417,6 +1423,7 @@ vy_get(struct vy_lsm *lsm, struct vy_tx *tx,
 	assert(tx == NULL || tx->state == VINYL_TX_READY);
 
 	int rc;
+	hint_t hint;
 	struct tuple *tuple;
 
 	if (vy_stmt_is_full_key(key, lsm->cmp_def)) {
@@ -1425,11 +1432,11 @@ vy_get(struct vy_lsm *lsm, struct vy_tx *tx,
 		 */
 		if (tx != NULL && vy_tx_track_point(tx, lsm, key) != 0)
 			return -1;
-		if (vy_point_lookup(lsm, tx, rv, key, &tuple) != 0)
+		if (vy_point_lookup(lsm, tx, rv, key, &tuple, &hint) != 0)
 			return -1;
 		if (lsm->index_id > 0 && tuple != NULL) {
 			rc = vy_get_by_secondary_tuple(lsm, tx, rv,
-						       tuple, result);
+						       tuple, hint, result);
 			tuple_unref(tuple);
 			if (rc != 0)
 				return -1;
@@ -1437,25 +1444,27 @@ vy_get(struct vy_lsm *lsm, struct vy_tx *tx,
 			*result = tuple;
 		}
 		if ((*rv)->vlsn == INT64_MAX)
-			vy_cache_add(&lsm->cache, *result, NULL, key, ITER_EQ);
+			vy_cache_add(&lsm->cache, *result, hint,
+				     NULL, HINT_NONE, key, ITER_EQ);
 		return 0;
 	}
 
 	struct vy_read_iterator itr;
 	vy_read_iterator_open(&itr, lsm, tx, ITER_EQ, key, rv);
-	while ((rc = vy_read_iterator_next(&itr, &tuple)) == 0) {
+	while ((rc = vy_read_iterator_next(&itr, &tuple, &hint)) == 0) {
 		if (lsm->index_id == 0 || tuple == NULL) {
 			*result = tuple;
 			if (tuple != NULL)
 				tuple_ref(tuple);
 			break;
 		}
-		rc = vy_get_by_secondary_tuple(lsm, tx, rv, tuple, result);
+		rc = vy_get_by_secondary_tuple(lsm, tx, rv, tuple, hint,
+					       result);
 		if (rc != 0 || *result != NULL)
 			break;
 	}
 	if (rc == 0)
-		vy_read_iterator_cache_add(&itr, *result);
+		vy_read_iterator_cache_add(&itr, *result, hint);
 	vy_read_iterator_close(&itr);
 	return rc;
 }
@@ -3530,15 +3539,14 @@ vy_squash_process(struct vy_squash *squash)
 	 * Use the committed read view to avoid squashing
 	 * prepared, but not committed statements.
 	 */
+	hint_t hint;
 	struct tuple *result;
 	if (vy_point_lookup(lsm, NULL, &env->xm->p_committed_read_view,
-			    squash->stmt, &result) != 0)
+			    squash->stmt, &result, &hint) != 0)
 		return -1;
 	if (result == NULL)
 		return 0;
 
-	hint_t hint = vy_stmt_hint(result, lsm->cmp_def);
-
 	/*
 	 * While we were reading on-disk runs, new statements could
 	 * have been prepared for the squashed key. We mustn't apply
@@ -3758,9 +3766,10 @@ vinyl_iterator_primary_next(struct iterator *base, struct tuple **ret)
 
 	if (vinyl_iterator_check_tx(it) != 0)
 		goto fail;
-	if (vy_read_iterator_next(&it->iterator, ret) != 0)
+	hint_t hint;
+	if (vy_read_iterator_next(&it->iterator, ret, &hint) != 0)
 		goto fail;
-	vy_read_iterator_cache_add(&it->iterator, *ret);
+	vy_read_iterator_cache_add(&it->iterator, *ret, hint);
 	if (*ret == NULL) {
 		/* EOF. Close the iterator immediately. */
 		vinyl_iterator_close(it);
@@ -3780,17 +3789,18 @@ vinyl_iterator_secondary_next(struct iterator *base, struct tuple **ret)
 	struct vinyl_iterator *it = (struct vinyl_iterator *)base;
 	assert(it->lsm->index_id > 0);
 	struct tuple *tuple;
+	hint_t hint;
 
 next:
 	if (vinyl_iterator_check_tx(it) != 0)
 		goto fail;
 
-	if (vy_read_iterator_next(&it->iterator, &tuple) != 0)
+	if (vy_read_iterator_next(&it->iterator, &tuple, &hint) != 0)
 		goto fail;
 
 	if (tuple == NULL) {
 		/* EOF. Close the iterator immediately. */
-		vy_read_iterator_cache_add(&it->iterator, NULL);
+		vy_read_iterator_cache_add(&it->iterator, NULL, HINT_NONE);
 		vinyl_iterator_close(it);
 		*ret = NULL;
 		return 0;
@@ -3806,11 +3816,11 @@ next:
 	/* Get the full tuple from the primary index. */
 	if (vy_get_by_secondary_tuple(it->lsm, it->tx,
 				      vy_tx_read_view(it->tx),
-				      tuple, ret) != 0)
+				      tuple, hint, ret) != 0)
 		goto fail;
 	if (*ret == NULL)
 		goto next;
-	vy_read_iterator_cache_add(&it->iterator, *ret);
+	vy_read_iterator_cache_add(&it->iterator, *ret, hint);
 	tuple_bless(*ret);
 	tuple_unref(*ret);
 	return 0;
@@ -4102,9 +4112,10 @@ vy_build_recover_stmt(struct vy_lsm *lsm, struct vy_lsm *pk,
 	/* Lookup the tuple that was affected by this statement. */
 	const struct vy_read_view rv = { .vlsn = lsn - 1 };
 	const struct vy_read_view *p_rv = &rv;
+	hint_t hint;
 	struct tuple *old_tuple;
 	if (vy_point_lookup(pk, NULL, &p_rv, (struct tuple *)mem_stmt,
-			    &old_tuple) != 0)
+			    &old_tuple, &hint) != 0)
 		return -1;
 	/*
 	 * Create DELETE + INSERT statements corresponding to
@@ -4263,9 +4274,10 @@ vinyl_space_build_index(struct space *src_space, struct index *new_index,
 			      &env->xm->p_committed_read_view);
 	int rc;
 	int loops = 0;
+	hint_t hint;
 	struct tuple *tuple;
 	int64_t build_lsn = env->xm->lsn;
-	while ((rc = vy_read_iterator_next(&itr, &tuple)) == 0) {
+	while ((rc = vy_read_iterator_next(&itr, &tuple, &hint)) == 0) {
 		if (tuple == NULL)
 			break;
 		/*
diff --git a/src/box/vy_cache.c b/src/box/vy_cache.c
index a4a767bc..ee3581f3 100644
--- a/src/box/vy_cache.c
+++ b/src/box/vy_cache.c
@@ -229,9 +229,9 @@ vy_cache_env_set_quota(struct vy_cache_env *env, size_t quota)
 }
 
 void
-vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
-	     struct tuple *prev_stmt, const struct tuple *key,
-	     enum iterator_type order)
+vy_cache_add(struct vy_cache *cache, struct tuple *stmt, hint_t hint,
+	     struct tuple *prev_stmt, hint_t prev_hint,
+	     const struct tuple *key, enum iterator_type order)
 {
 	if (cache->env->mem_quota == 0) {
 		/* Cache is disabled. */
@@ -259,11 +259,6 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 		return;
 	}
 
-	hint_t hint = stmt == NULL ? HINT_NONE :
-		      vy_stmt_hint(stmt, cache->cmp_def);
-	hint_t prev_hint = prev_stmt == NULL ? HINT_NONE :
-			   vy_stmt_hint(prev_stmt, cache->cmp_def);
-
 	int direction = iterator_direction(order);
 	/**
 	 * Let's determine boundary_level (left/right) of the new record
@@ -446,11 +441,10 @@ vy_cache_get(struct vy_cache *cache, const struct tuple *key,
 
 void
 vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
-		  struct tuple **deleted)
+		  hint_t hint, struct tuple **deleted)
 {
 	vy_cache_gc(cache->env);
 	bool exact = false;
-	hint_t hint = vy_stmt_hint(stmt, cache->cmp_def);
 	struct vy_cache_tree_key tree_key;
 	tree_key = vy_cache_tree_key(stmt, hint);
 	struct vy_cache_tree_iterator itr;
diff --git a/src/box/vy_cache.h b/src/box/vy_cache.h
index d256f32c..9be2fe68 100644
--- a/src/box/vy_cache.h
+++ b/src/box/vy_cache.h
@@ -213,15 +213,17 @@ vy_cache_destroy(struct vy_cache *cache);
  * @param cache - pointer to tuple cache.
  * @param stmt - statement that was recently read and should be added to the
  * cache.
+ * @param hint - statement comparison hint.
  * @param prev_stmt - previous statement that was read by the reader in one
  * sequence (by one iterator).
+ * @param prev_hint - previous statement comparison hint.
  * @param direction - direction in which the reader (iterator) observes data,
  *  +1 - forward, -1 - backward.
  */
 void
-vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
-	     struct tuple *prev_stmt, const struct tuple *key,
-	     enum iterator_type order);
+vy_cache_add(struct vy_cache *cache, struct tuple *stmt, hint_t hint,
+	     struct tuple *prev_stmt, hint_t prev_hint,
+	     const struct tuple *key, enum iterator_type order);
 
 /**
  * Find value in cache.
@@ -235,12 +237,13 @@ vy_cache_get(struct vy_cache *cache, const struct tuple *key,
  * Invalidate possibly cached value due to its overwriting
  * @param cache - pointer to tuple cache.
  * @param stmt - overwritten statement.
+ * @param hint - statement comparison hint.
  * @param[out] deleted - If not NULL, then is set to deleted
  *             statement.
  */
 void
 vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
-		  struct tuple **deleted);
+		  hint_t hint, struct tuple **deleted);
 
 
 /**
diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index 4065f711..8230410d 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -1066,7 +1066,7 @@ vy_lsm_commit_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
 	vy_stmt_counter_acct_tuple(&lsm->stat.put, stmt);
 
 	/* Invalidate cache element. */
-	vy_cache_on_write(&lsm->cache, stmt, NULL);
+	vy_cache_on_write(&lsm->cache, stmt, hint, NULL);
 }
 
 void
@@ -1076,7 +1076,7 @@ vy_lsm_rollback_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
 	vy_mem_rollback_stmt(mem, stmt, hint);
 
 	/* Invalidate cache element. */
-	vy_cache_on_write(&lsm->cache, stmt, NULL);
+	vy_cache_on_write(&lsm->cache, stmt, hint, NULL);
 }
 
 int
diff --git a/src/box/vy_point_lookup.c b/src/box/vy_point_lookup.c
index 25125662..bac6db2b 100644
--- a/src/box/vy_point_lookup.c
+++ b/src/box/vy_point_lookup.c
@@ -197,14 +197,15 @@ vy_point_lookup_scan_slices(struct vy_lsm *lsm, const struct vy_read_view **rv,
 
 int
 vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
-		const struct vy_read_view **rv,
-		struct tuple *key, struct tuple **ret)
+		const struct vy_read_view **rv, struct tuple *key,
+		struct tuple **ret, hint_t *ret_hint)
 {
 	/* All key parts must be set for a point lookup. */
 	assert(vy_stmt_is_full_key(key, lsm->cmp_def));
 	assert(tx == NULL || tx->state == VINYL_TX_READY);
 
 	*ret = NULL;
+	*ret_hint = HINT_NONE;
 	double start_time = ev_monotonic_now(loop());
 	int rc = 0;
 
@@ -291,10 +292,9 @@ done:
 	vy_history_splice(&history, &disk_history);
 
 	if (rc == 0) {
-		hint_t unused;
 		int upserts_applied;
 		rc = vy_history_apply(&history, lsm->cmp_def, false,
-				      &upserts_applied, ret, &unused);
+				      &upserts_applied, ret, ret_hint);
 		lsm->stat.upsert.applied += upserts_applied;
 	}
 	vy_history_cleanup(&history);
diff --git a/src/box/vy_point_lookup.h b/src/box/vy_point_lookup.h
index 6d77ce9c..a0e23ecb 100644
--- a/src/box/vy_point_lookup.h
+++ b/src/box/vy_point_lookup.h
@@ -46,6 +46,7 @@
  */
 
 #include <stdbool.h>
+#include "key_def.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -68,8 +69,8 @@ struct tuple;
  */
 int
 vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
-		const struct vy_read_view **rv,
-		struct tuple *key, struct tuple **ret);
+		const struct vy_read_view **rv, struct tuple *key,
+		struct tuple **ret, hint_t *ret_hint);
 
 /**
  * Look up a tuple by key in memory.
diff --git a/src/box/vy_read_iterator.c b/src/box/vy_read_iterator.c
index b96a5d19..d0b25610 100644
--- a/src/box/vy_read_iterator.c
+++ b/src/box/vy_read_iterator.c
@@ -715,6 +715,7 @@ vy_read_iterator_open(struct vy_read_iterator *itr, struct vy_lsm *lsm,
 	itr->hint = vy_stmt_hint(key, lsm->cmp_def);
 	itr->read_view = rv;
 	itr->last_hint = HINT_NONE;
+	itr->last_cached_hint = HINT_NONE;
 
 	if (vy_stmt_is_empty_key(key)) {
 		/*
@@ -893,7 +894,8 @@ vy_read_iterator_track_read(struct vy_read_iterator *itr, struct tuple *stmt)
 }
 
 NODISCARD int
-vy_read_iterator_next(struct vy_read_iterator *itr, struct tuple **result)
+vy_read_iterator_next(struct vy_read_iterator *itr,
+		      struct tuple **result, hint_t *result_hint)
 {
 	assert(itr->tx == NULL || itr->tx->state == VINYL_TX_READY);
 
@@ -930,6 +932,7 @@ next_key:
 			if (itr->last_cached_stmt != NULL)
 				tuple_unref(itr->last_cached_stmt);
 			itr->last_cached_stmt = NULL;
+			itr->last_cached_hint = HINT_NONE;
 		}
 		goto next_key;
 	}
@@ -952,11 +955,13 @@ next_key:
 	}
 
 	*result = stmt;
+	*result_hint = hint;
 	return 0;
 }
 
 void
-vy_read_iterator_cache_add(struct vy_read_iterator *itr, struct tuple *stmt)
+vy_read_iterator_cache_add(struct vy_read_iterator *itr,
+			   struct tuple *stmt, hint_t hint)
 {
 	if ((**itr->read_view).vlsn != INT64_MAX) {
 		if (itr->last_cached_stmt != NULL)
@@ -964,13 +969,14 @@ vy_read_iterator_cache_add(struct vy_read_iterator *itr, struct tuple *stmt)
 		itr->last_cached_stmt = NULL;
 		return;
 	}
-	vy_cache_add(&itr->lsm->cache, stmt, itr->last_cached_stmt,
-		     itr->key, itr->iterator_type);
+	vy_cache_add(&itr->lsm->cache, stmt, hint, itr->last_cached_stmt,
+		     itr->last_cached_hint, itr->key, itr->iterator_type);
 	if (stmt != NULL)
 		tuple_ref(stmt);
 	if (itr->last_cached_stmt != NULL)
 		tuple_unref(itr->last_cached_stmt);
 	itr->last_cached_stmt = stmt;
+	itr->last_cached_hint = hint;
 }
 
 /**
diff --git a/src/box/vy_read_iterator.h b/src/box/vy_read_iterator.h
index d8251372..fd876fcc 100644
--- a/src/box/vy_read_iterator.h
+++ b/src/box/vy_read_iterator.h
@@ -75,6 +75,10 @@ struct vy_read_iterator {
 	 */
 	struct tuple *last_cached_stmt;
 	/**
+	 * Comparison hint of the last cached statement.
+	 */
+	hint_t last_cached_hint;
+	/**
 	 * Copy of lsm->range_tree_version.
 	 * Used for detecting range tree changes.
 	 */
@@ -144,17 +148,20 @@ vy_read_iterator_open(struct vy_read_iterator *itr, struct vy_lsm *lsm,
  * if it wasn't started.
  * @param itr         Read iterator.
  * @param[out] result Found statement is stored here.
+ * @param[out] hint   Comparison hint of the found statement.
  *
  * @retval  0 Success.
  * @retval -1 Read error.
  */
 NODISCARD int
-vy_read_iterator_next(struct vy_read_iterator *itr, struct tuple **result);
+vy_read_iterator_next(struct vy_read_iterator *itr,
+		      struct tuple **result, hint_t *hint);
 
 /**
  * Add the last tuple returned by the read iterator to the cache.
  * @param itr  Read iterator
  * @param stmt Last tuple returned by the iterator.
+ * @param hint Comparison hint of the last statement.
  *
  * We use a separate function for populating the cache rather than
  * doing that right in vy_read_iterator_next() so that we can store
@@ -168,7 +175,8 @@ vy_read_iterator_next(struct vy_read_iterator *itr, struct tuple **result);
  *   the result to the cache.
  */
 void
-vy_read_iterator_cache_add(struct vy_read_iterator *itr, struct tuple *stmt);
+vy_read_iterator_cache_add(struct vy_read_iterator *itr,
+			   struct tuple *stmt, hint_t hint);
 
 /**
  * Close the iterator and free resources.
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index fd8822a1..5ded9637 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -515,7 +515,7 @@ vy_tx_write(struct vy_lsm *lsm, struct vy_mem *mem,
 	if (vy_stmt_type(stmt) == IPROTO_UPSERT) {
 		struct tuple *deleted = NULL;
 		/* Invalidate cache element. */
-		vy_cache_on_write(&lsm->cache, stmt, &deleted);
+		vy_cache_on_write(&lsm->cache, stmt, hint, &deleted);
 		if (deleted != NULL) {
 			struct tuple *applied = vy_apply_upsert(stmt, deleted,
 							mem->cmp_def, false);
@@ -534,7 +534,7 @@ vy_tx_write(struct vy_lsm *lsm, struct vy_mem *mem,
 		}
 	} else {
 		/* Invalidate cache element. */
-		vy_cache_on_write(&lsm->cache, stmt, NULL);
+		vy_cache_on_write(&lsm->cache, stmt, hint, NULL);
 	}
 	return vy_lsm_set(lsm, mem, stmt, hint, region_stmt);
 }
diff --git a/test/unit/vy_iterators_helper.c b/test/unit/vy_iterators_helper.c
index d2ac21a0..1f895840 100644
--- a/test/unit/vy_iterators_helper.c
+++ b/test/unit/vy_iterators_helper.c
@@ -156,15 +156,21 @@ vy_cache_insert_templates_chain(struct vy_cache *cache,
 {
 	struct tuple *key = vy_new_simple_stmt(format, key_templ);
 	struct tuple *prev_stmt = NULL;
+	hint_t prev_hint = HINT_NONE;
 	struct tuple *stmt = NULL;
+	hint_t hint = HINT_NONE;
 
 	for (uint i = 0; i < length; ++i) {
 		stmt = vy_new_simple_stmt(format, &chain[i]);
-		vy_cache_add(cache, stmt, prev_stmt, key, order);
+		hint = vy_stmt_hint(stmt, cache->cmp_def);
+		vy_cache_add(cache, stmt, hint, prev_stmt, prev_hint,
+			     key, order);
 		if (i != 0)
 			tuple_unref(prev_stmt);
 		prev_stmt = stmt;
+		prev_hint = hint;
 		stmt = NULL;
+		hint = HINT_NONE;
 	}
 	tuple_unref(key);
 	if (prev_stmt != NULL)
@@ -176,7 +182,8 @@ vy_cache_on_write_template(struct vy_cache *cache, struct tuple_format *format,
 			   const struct vy_stmt_template *templ)
 {
 	struct tuple *written = vy_new_simple_stmt(format, templ);
-	vy_cache_on_write(cache, written, NULL);
+	hint_t hint = vy_stmt_hint(written, cache->cmp_def);
+	vy_cache_on_write(cache, written, hint, NULL);
 	tuple_unref(written);
 }
 
diff --git a/test/unit/vy_point_lookup.c b/test/unit/vy_point_lookup.c
index eef50c30..93841c0a 100644
--- a/test/unit/vy_point_lookup.c
+++ b/test/unit/vy_point_lookup.c
@@ -275,8 +275,9 @@ test_basic()
 				STMT_TEMPLATE(0, SELECT, i);
 			struct tuple *key = vy_new_simple_stmt(format,
 							       &tmpl_key);
+			hint_t hint;
 			struct tuple *res;
-			rc = vy_point_lookup(pk, NULL, &prv, key, &res);
+			rc = vy_point_lookup(pk, NULL, &prv, key, &res, &hint);
 			tuple_unref(key);
 			if (rc != 0) {
 				has_errors = true;
-- 
2.11.0

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

* [PATCH 11/13] vinyl: forward tuple comparison hints to read iterator
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (9 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 10/13] vinyl: forward tuple comparison hints to cache tree Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 12:12   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 12/13] vinyl: forward tuple comparison hints to tx read set Vladimir Davydov
  2019-04-02 17:33 ` [PATCH 13/13] Make tuple comparison hints mandatory Vladimir Davydov
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

Pass a comparison hint along with a search key in the arguments instead
of computing it in the read iterator. This will allow to avoid computing
a hint for the second time in case the calling code already has it.

Besides, it is needed for multikey indexes, which will reuse hints to
store offsets of indexed array entries and thus make hints impossible to
be computed in an arbitrary place in code.
---
 src/box/vinyl.c             | 40 +++++++++++++++++++++++-----------------
 src/box/vy_lsm.c            |  2 +-
 src/box/vy_lsm.h            |  3 ++-
 src/box/vy_point_lookup.c   |  9 +++------
 src/box/vy_point_lookup.h   |  5 +++--
 src/box/vy_read_iterator.c  |  5 +++--
 src/box/vy_read_iterator.h  |  4 +++-
 src/box/vy_tx.c             |  3 ++-
 test/unit/vy_point_lookup.c |  5 +++--
 9 files changed, 43 insertions(+), 33 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 8832db2c..edf8b8b3 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1139,7 +1139,7 @@ vinyl_space_check_format(struct space *space, struct tuple_format *format)
 
 	struct vy_read_iterator itr;
 	vy_read_iterator_open(&itr, pk, NULL, ITER_ALL, pk->env->empty_key,
-			      &env->xm->p_committed_read_view);
+			      HINT_NONE, &env->xm->p_committed_read_view);
 	int rc;
 	int loops = 0;
 	hint_t hint;
@@ -1346,9 +1346,9 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 		tuple_ref(key);
 	}
 
-	hint_t primary_hint;
-	if (vy_point_lookup(lsm->pk, tx, rv, key, result,
-			    &primary_hint) != 0) {
+	hint_t primary_hint = vy_stmt_hint(key, lsm->pk->cmp_def);
+	if (vy_point_lookup(lsm->pk, tx, rv, key, primary_hint,
+			    result, &primary_hint) != 0) {
 		rc = -1;
 		goto out;
 	}
@@ -1426,13 +1426,14 @@ vy_get(struct vy_lsm *lsm, struct vy_tx *tx,
 	hint_t hint;
 	struct tuple *tuple;
 
+	hint = vy_stmt_hint(key, lsm->cmp_def);
 	if (vy_stmt_is_full_key(key, lsm->cmp_def)) {
 		/*
 		 * Use point lookup for a full key.
 		 */
 		if (tx != NULL && vy_tx_track_point(tx, lsm, key) != 0)
 			return -1;
-		if (vy_point_lookup(lsm, tx, rv, key, &tuple, &hint) != 0)
+		if (vy_point_lookup(lsm, tx, rv, key, hint, &tuple, &hint) != 0)
 			return -1;
 		if (lsm->index_id > 0 && tuple != NULL) {
 			rc = vy_get_by_secondary_tuple(lsm, tx, rv,
@@ -1450,7 +1451,7 @@ vy_get(struct vy_lsm *lsm, struct vy_tx *tx,
 	}
 
 	struct vy_read_iterator itr;
-	vy_read_iterator_open(&itr, lsm, tx, ITER_EQ, key, rv);
+	vy_read_iterator_open(&itr, lsm, tx, ITER_EQ, key, hint, rv);
 	while ((rc = vy_read_iterator_next(&itr, &tuple, &hint)) == 0) {
 		if (lsm->index_id == 0 || tuple == NULL) {
 			*result = tuple;
@@ -2545,7 +2546,7 @@ vy_squash_queue_new(void);
 static void
 vy_squash_queue_delete(struct vy_squash_queue *q);
 static void
-vy_squash_schedule(struct vy_lsm *lsm, struct tuple *stmt,
+vy_squash_schedule(struct vy_lsm *lsm, struct tuple *stmt, hint_t hint,
 		   void /* struct vy_env */ *arg);
 
 static struct vy_env *
@@ -3485,6 +3486,8 @@ struct vy_squash {
 	struct vy_lsm *lsm;
 	/** Key to squash upserts for. */
 	struct tuple *stmt;
+	/** Statement comparison hint. */
+	hint_t hint;
 };
 
 struct vy_squash_queue {
@@ -3500,7 +3503,7 @@ struct vy_squash_queue {
 
 static struct vy_squash *
 vy_squash_new(struct mempool *pool, struct vy_env *env,
-	      struct vy_lsm *lsm, struct tuple *stmt)
+	      struct vy_lsm *lsm, struct tuple *stmt, hint_t hint)
 {
 	struct vy_squash *squash;
 	squash = mempool_alloc(pool);
@@ -3511,6 +3514,7 @@ vy_squash_new(struct mempool *pool, struct vy_env *env,
 	squash->lsm = lsm;
 	tuple_ref(stmt);
 	squash->stmt = stmt;
+	squash->hint = hint;
 	return squash;
 }
 
@@ -3542,7 +3546,7 @@ vy_squash_process(struct vy_squash *squash)
 	hint_t hint;
 	struct tuple *result;
 	if (vy_point_lookup(lsm, NULL, &env->xm->p_committed_read_view,
-			    squash->stmt, &result, &hint) != 0)
+			    squash->stmt, squash->hint, &result, &hint) != 0)
 		return -1;
 	if (result == NULL)
 		return 0;
@@ -3661,7 +3665,8 @@ vy_squash_queue_f(va_list va)
  * statement after it. Done in a background fiber.
  */
 static void
-vy_squash_schedule(struct vy_lsm *lsm, struct tuple *stmt, void *arg)
+vy_squash_schedule(struct vy_lsm *lsm, struct tuple *stmt, hint_t hint,
+		   void *arg)
 {
 	struct vy_env *env = arg;
 	struct vy_squash_queue *sq = env->squash_queue;
@@ -3677,7 +3682,8 @@ vy_squash_schedule(struct vy_lsm *lsm, struct tuple *stmt, void *arg)
 		fiber_start(sq->fiber, sq);
 	}
 
-	struct vy_squash *squash = vy_squash_new(&sq->pool, env, lsm, stmt);
+	struct vy_squash *squash = vy_squash_new(&sq->pool, env, lsm,
+						 stmt, hint);
 	if (squash == NULL)
 		goto fail;
 
@@ -3895,7 +3901,8 @@ vinyl_index_create_iterator(struct index *base, enum iterator_type type,
 	}
 	it->tx = tx;
 
-	vy_read_iterator_open(&it->iterator, lsm, tx, type, it->key,
+	hint_t hint = vy_stmt_hint(it->key, lsm->cmp_def);
+	vy_read_iterator_open(&it->iterator, lsm, tx, type, it->key, hint,
 			      (const struct vy_read_view **)&tx->read_view);
 	return (struct iterator *)it;
 }
@@ -4103,7 +4110,7 @@ vy_build_insert_tuple(struct vy_env *env, struct vy_lsm *lsm,
  */
 static int
 vy_build_recover_stmt(struct vy_lsm *lsm, struct vy_lsm *pk,
-		      const struct tuple *mem_stmt)
+		      const struct tuple *mem_stmt, hint_t hint)
 {
 	int64_t lsn = vy_stmt_lsn(mem_stmt);
 	if (lsn <= lsm->dump_lsn)
@@ -4112,9 +4119,8 @@ vy_build_recover_stmt(struct vy_lsm *lsm, struct vy_lsm *pk,
 	/* Lookup the tuple that was affected by this statement. */
 	const struct vy_read_view rv = { .vlsn = lsn - 1 };
 	const struct vy_read_view *p_rv = &rv;
-	hint_t hint;
 	struct tuple *old_tuple;
-	if (vy_point_lookup(pk, NULL, &p_rv, (struct tuple *)mem_stmt,
+	if (vy_point_lookup(pk, NULL, &p_rv, (struct tuple *)mem_stmt, hint,
 			    &old_tuple, &hint) != 0)
 		return -1;
 	/*
@@ -4186,7 +4192,7 @@ vy_build_recover_mem(struct vy_lsm *lsm, struct vy_lsm *pk, struct vy_mem *mem)
 	while (!vy_mem_tree_iterator_is_invalid(&itr)) {
 		struct vy_mem_tree_elem elem;
 		elem = *vy_mem_tree_iterator_get_elem(&mem->tree, &itr);
-		if (vy_build_recover_stmt(lsm, pk, elem.stmt) != 0)
+		if (vy_build_recover_stmt(lsm, pk, elem.stmt, elem.hint) != 0)
 			return -1;
 		vy_mem_tree_iterator_prev(&mem->tree, &itr);
 	}
@@ -4271,7 +4277,7 @@ vinyl_space_build_index(struct space *src_space, struct index *new_index,
 
 	struct vy_read_iterator itr;
 	vy_read_iterator_open(&itr, pk, NULL, ITER_ALL, pk->env->empty_key,
-			      &env->xm->p_committed_read_view);
+			      HINT_NONE, &env->xm->p_committed_read_view);
 	int rc;
 	int loops = 0;
 	hint_t hint;
diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index 8230410d..f2f9fdaf 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -984,7 +984,7 @@ vy_lsm_commit_upsert(struct vy_lsm *lsm, struct vy_mem *mem,
 
 		struct tuple *dup = vy_stmt_dup(stmt);
 		if (dup != NULL) {
-			lsm->env->upsert_thresh_cb(lsm, dup,
+			lsm->env->upsert_thresh_cb(lsm, dup, hint,
 					lsm->env->upsert_thresh_arg);
 			tuple_unref(dup);
 		}
diff --git a/src/box/vy_lsm.h b/src/box/vy_lsm.h
index 541c49f8..fdc93dc5 100644
--- a/src/box/vy_lsm.h
+++ b/src/box/vy_lsm.h
@@ -61,7 +61,8 @@ struct vy_run;
 struct vy_run_env;
 
 typedef void
-(*vy_upsert_thresh_cb)(struct vy_lsm *lsm, struct tuple *stmt, void *arg);
+(*vy_upsert_thresh_cb)(struct vy_lsm *lsm, struct tuple *stmt, hint_t hint,
+		       void *arg);
 
 /** Common LSM tree environment. */
 struct vy_lsm_env {
diff --git a/src/box/vy_point_lookup.c b/src/box/vy_point_lookup.c
index bac6db2b..8e44cbce 100644
--- a/src/box/vy_point_lookup.c
+++ b/src/box/vy_point_lookup.c
@@ -197,7 +197,8 @@ vy_point_lookup_scan_slices(struct vy_lsm *lsm, const struct vy_read_view **rv,
 
 int
 vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
-		const struct vy_read_view **rv, struct tuple *key,
+		const struct vy_read_view **rv,
+		struct tuple *key, hint_t hint,
 		struct tuple **ret, hint_t *ret_hint)
 {
 	/* All key parts must be set for a point lookup. */
@@ -217,8 +218,6 @@ vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
 	vy_history_create(&mem_history, &lsm->env->history_node_pool);
 	vy_history_create(&disk_history, &lsm->env->history_node_pool);
 
-	hint_t hint = vy_stmt_hint(key, lsm->cmp_def);
-
 	rc = vy_point_lookup_scan_txw(lsm, tx, key, hint, &history);
 	if (rc != 0 || vy_history_is_terminal(&history))
 		goto done;
@@ -319,7 +318,7 @@ done:
 
 int
 vy_point_lookup_mem(struct vy_lsm *lsm, const struct vy_read_view **rv,
-		    struct tuple *key, struct tuple **ret)
+		    struct tuple *key, hint_t hint, struct tuple **ret)
 {
 	assert(vy_stmt_is_full_key(key, lsm->cmp_def));
 
@@ -327,8 +326,6 @@ vy_point_lookup_mem(struct vy_lsm *lsm, const struct vy_read_view **rv,
 	struct vy_history history;
 	vy_history_create(&history, &lsm->env->history_node_pool);
 
-	hint_t hint = vy_stmt_hint(key, lsm->cmp_def);
-
 	rc = vy_point_lookup_scan_cache(lsm, rv, key, hint, &history);
 	if (rc != 0 || vy_history_is_terminal(&history))
 		goto done;
diff --git a/src/box/vy_point_lookup.h b/src/box/vy_point_lookup.h
index a0e23ecb..2046744a 100644
--- a/src/box/vy_point_lookup.h
+++ b/src/box/vy_point_lookup.h
@@ -69,7 +69,8 @@ struct tuple;
  */
 int
 vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
-		const struct vy_read_view **rv, struct tuple *key,
+		const struct vy_read_view **rv,
+		struct tuple *key, hint_t hint,
 		struct tuple **ret, hint_t *ret_hint);
 
 /**
@@ -88,7 +89,7 @@ vy_point_lookup(struct vy_lsm *lsm, struct vy_tx *tx,
  */
 int
 vy_point_lookup_mem(struct vy_lsm *lsm, const struct vy_read_view **rv,
-		    struct tuple *key, struct tuple **ret);
+		    struct tuple *key, hint_t hint, struct tuple **ret);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/box/vy_read_iterator.c b/src/box/vy_read_iterator.c
index d0b25610..69fea0cf 100644
--- a/src/box/vy_read_iterator.c
+++ b/src/box/vy_read_iterator.c
@@ -704,7 +704,8 @@ vy_read_iterator_cleanup(struct vy_read_iterator *itr)
 void
 vy_read_iterator_open(struct vy_read_iterator *itr, struct vy_lsm *lsm,
 		      struct vy_tx *tx, enum iterator_type iterator_type,
-		      struct tuple *key, const struct vy_read_view **rv)
+		      struct tuple *key, hint_t hint,
+		      const struct vy_read_view **rv)
 {
 	memset(itr, 0, sizeof(*itr));
 
@@ -712,7 +713,7 @@ vy_read_iterator_open(struct vy_read_iterator *itr, struct vy_lsm *lsm,
 	itr->tx = tx;
 	itr->iterator_type = iterator_type;
 	itr->key = key;
-	itr->hint = vy_stmt_hint(key, lsm->cmp_def);
+	itr->hint = hint;
 	itr->read_view = rv;
 	itr->last_hint = HINT_NONE;
 	itr->last_cached_hint = HINT_NONE;
diff --git a/src/box/vy_read_iterator.h b/src/box/vy_read_iterator.h
index fd876fcc..10a91ca7 100644
--- a/src/box/vy_read_iterator.h
+++ b/src/box/vy_read_iterator.h
@@ -136,12 +136,14 @@ struct vy_read_iterator {
  * @param iterator_type Type of the iterator that determines order
  *                      of the iteration.
  * @param key           Key for the iteration.
+ * @param hint          Comparison hint of the search key.
  * @param rv            Read view.
  */
 void
 vy_read_iterator_open(struct vy_read_iterator *itr, struct vy_lsm *lsm,
 		      struct vy_tx *tx, enum iterator_type iterator_type,
-		      struct tuple *key, const struct vy_read_view **rv);
+		      struct tuple *key, hint_t hint,
+		      const struct vy_read_view **rv);
 
 /**
  * Get the next statement with another key, or start the iterator,
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index 5ded9637..621898c9 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -563,6 +563,7 @@ vy_tx_handle_deferred_delete(struct vy_tx *tx, struct txv *v)
 {
 	struct vy_lsm *pk = v->lsm;
 	struct tuple *stmt = v->stmt;
+	hint_t hint = v->hint;
 	uint8_t flags = vy_stmt_flags(stmt);
 
 	assert(pk->index_id == 0);
@@ -580,7 +581,7 @@ vy_tx_handle_deferred_delete(struct vy_tx *tx, struct txv *v)
 	/* Look up the tuple overwritten by this statement. */
 	struct tuple *tuple;
 	if (vy_point_lookup_mem(pk, &tx->xm->p_global_read_view,
-				stmt, &tuple) != 0)
+				stmt, hint, &tuple) != 0)
 		return -1;
 
 	if (tuple == NULL) {
diff --git a/test/unit/vy_point_lookup.c b/test/unit/vy_point_lookup.c
index 93841c0a..52c2affd 100644
--- a/test/unit/vy_point_lookup.c
+++ b/test/unit/vy_point_lookup.c
@@ -275,9 +275,10 @@ test_basic()
 				STMT_TEMPLATE(0, SELECT, i);
 			struct tuple *key = vy_new_simple_stmt(format,
 							       &tmpl_key);
-			hint_t hint;
+			hint_t hint = vy_stmt_hint(key, key_def);
 			struct tuple *res;
-			rc = vy_point_lookup(pk, NULL, &prv, key, &res, &hint);
+			rc = vy_point_lookup(pk, NULL, &prv, key, hint,
+					     &res, &hint);
 			tuple_unref(key);
 			if (rc != 0) {
 				has_errors = true;
-- 
2.11.0

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

* [PATCH 12/13] vinyl: forward tuple comparison hints to tx read set
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (10 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 11/13] vinyl: forward tuple comparison hints to read iterator Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 12:12   ` Konstantin Osipov
  2019-04-02 17:33 ` [PATCH 13/13] Make tuple comparison hints mandatory Vladimir Davydov
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

Instead of computing a statement comparison hint in vy_tx_track,
forward it from the upper level, which already has it computed.

Apart from eliminating extra calls to vy_stmt_hint, this is also a
prerequisite for multikey indexes, which will reuse hints to store
offsets of indexed array entries and thus make hints impossible to be
computed in an arbitrary place in code.
---
 src/box/vinyl.c            |  5 +++--
 src/box/vy_read_iterator.c | 25 ++++++++++++++++---------
 src/box/vy_tx.c            | 13 +++++--------
 src/box/vy_tx.h            | 10 +++++++---
 4 files changed, 31 insertions(+), 22 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index edf8b8b3..fc90b372 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1385,7 +1385,8 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 	 * the DELETE statement is not written to secondary indexes
 	 * immediately.
 	 */
-	if (tx != NULL && vy_tx_track_point(tx, lsm->pk, *result) != 0) {
+	if (tx != NULL && vy_tx_track_point(tx, lsm->pk, *result,
+					    primary_hint) != 0) {
 		tuple_unref(*result);
 		rc = -1;
 		goto out;
@@ -1431,7 +1432,7 @@ vy_get(struct vy_lsm *lsm, struct vy_tx *tx,
 		/*
 		 * Use point lookup for a full key.
 		 */
-		if (tx != NULL && vy_tx_track_point(tx, lsm, key) != 0)
+		if (tx != NULL && vy_tx_track_point(tx, lsm, key, hint) != 0)
 			return -1;
 		if (vy_point_lookup(lsm, tx, rv, key, hint, &tuple, &hint) != 0)
 			return -1;
diff --git a/src/box/vy_read_iterator.c b/src/box/vy_read_iterator.c
index 69fea0cf..ba280f25 100644
--- a/src/box/vy_read_iterator.c
+++ b/src/box/vy_read_iterator.c
@@ -871,25 +871,32 @@ vy_read_iterator_apply_history(struct vy_read_iterator *itr,
  * Track a read in the conflict manager.
  */
 static int
-vy_read_iterator_track_read(struct vy_read_iterator *itr, struct tuple *stmt)
+vy_read_iterator_track_read(struct vy_read_iterator *itr,
+			    struct tuple *stmt, hint_t hint)
 {
 	if (itr->tx == NULL)
 		return 0;
 
 	if (stmt == NULL) {
-		stmt = (itr->iterator_type == ITER_EQ ||
-			itr->iterator_type == ITER_REQ ?
-			itr->key : itr->lsm->env->empty_key);
+		if (itr->iterator_type == ITER_EQ ||
+		    itr->iterator_type == ITER_REQ) {
+			stmt = itr->key;
+			hint = itr->hint;
+		} else {
+			stmt = itr->lsm->env->empty_key;
+			hint = HINT_NONE;
+		}
 	}
 
 	int rc;
 	if (iterator_direction(itr->iterator_type) >= 0) {
-		rc = vy_tx_track(itr->tx, itr->lsm, itr->key,
+		rc = vy_tx_track(itr->tx, itr->lsm, itr->key, itr->hint,
 				 itr->iterator_type != ITER_GT,
-				 stmt, true);
+				 stmt, hint, true);
 	} else {
-		rc = vy_tx_track(itr->tx, itr->lsm, stmt, true,
-				 itr->key, itr->iterator_type != ITER_LT);
+		rc = vy_tx_track(itr->tx, itr->lsm, stmt, hint, true,
+				 itr->key, itr->hint,
+				 itr->iterator_type != ITER_LT);
 	}
 	return rc;
 }
@@ -913,7 +920,7 @@ next_key:
 		return -1;
 	if (vy_read_iterator_apply_history(itr, &stmt, &hint) != 0)
 		return -1;
-	if (vy_read_iterator_track_read(itr, stmt) != 0)
+	if (vy_read_iterator_track_read(itr, stmt, hint) != 0)
 		return -1;
 
 	if (itr->last_stmt != NULL)
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index 621898c9..6a3f410e 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -927,17 +927,14 @@ vy_tx_rollback_statement(struct vy_tx *tx, void *svp)
 
 int
 vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
-	    struct tuple *left, bool left_belongs,
-	    struct tuple *right, bool right_belongs)
+	    struct tuple *left, hint_t left_hint, bool left_belongs,
+	    struct tuple *right, hint_t right_hint, bool right_belongs)
 {
 	if (vy_tx_is_in_read_view(tx)) {
 		/* No point in tracking reads. */
 		return 0;
 	}
 
-	hint_t left_hint = vy_stmt_hint(left, lsm->cmp_def);
-	hint_t right_hint = vy_stmt_hint(right, lsm->cmp_def);
-
 	struct vy_read_interval *new_interval;
 	new_interval = vy_read_interval_new(tx, lsm, left, left_hint,
 					    left_belongs, right, right_hint,
@@ -1017,7 +1014,8 @@ vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
 }
 
 int
-vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm, struct tuple *stmt)
+vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm,
+		  struct tuple *stmt, hint_t hint)
 {
 	assert(vy_stmt_is_full_key(stmt, lsm->cmp_def));
 
@@ -1026,14 +1024,13 @@ vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm, struct tuple *stmt)
 		return 0;
 	}
 
-	hint_t hint = vy_stmt_hint(stmt, lsm->cmp_def);
 	struct txv *v = write_set_search_key(&tx->write_set, lsm, stmt, hint);
 	if (v != NULL && vy_stmt_type(v->stmt) != IPROTO_UPSERT) {
 		/* Reading from own write set is serializable. */
 		return 0;
 	}
 
-	return vy_tx_track(tx, lsm, stmt, true, stmt, true);
+	return vy_tx_track(tx, lsm, stmt, hint, true, stmt, hint, true);
 }
 
 int
diff --git a/src/box/vy_tx.h b/src/box/vy_tx.h
index f3b9eacc..7e3305b1 100644
--- a/src/box/vy_tx.h
+++ b/src/box/vy_tx.h
@@ -361,9 +361,11 @@ vy_tx_rollback_statement(struct vy_tx *tx, void *svp);
  * @param tx            Transaction that invoked the read.
  * @param lsm           LSM tree that was read from.
  * @param left          Left boundary of the read interval.
+ * @param left_hint     Comparison hint of the left boundary statement.
  * @param left_belongs  Set if the left boundary belongs to
  *                      the interval.
  * @param right         Right boundary of the read interval.
+ * @param right_hint    Comparison hint of the right boundary statement.
  * @param right_belongs Set if the right boundary belongs to
  *                      the interval.
  *
@@ -372,8 +374,8 @@ vy_tx_rollback_statement(struct vy_tx *tx, void *svp);
  */
 int
 vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
-	    struct tuple *left, bool left_belongs,
-	    struct tuple *right, bool right_belongs);
+	    struct tuple *left, hint_t left_hint, bool left_belongs,
+	    struct tuple *right, hint_t right_hint, bool right_belongs);
 
 /**
  * Remember a point read in the conflict manager index.
@@ -381,6 +383,7 @@ vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
  * @param tx    Transaction that invoked the read.
  * @param lsm   LSM tree that was read from.
  * @param stmt  Key that was read.
+ * @param hint  Statement comparison hint.
  *
  * @retval  0 Success.
  * @retval -1 Memory error.
@@ -392,7 +395,8 @@ vy_tx_track(struct vy_tx *tx, struct vy_lsm *lsm,
  * transaction read it from its own write set.
  */
 int
-vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm, struct tuple *stmt);
+vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm,
+		  struct tuple *stmt, hint_t hint);
 
 /** Add a statement to a transaction. */
 int
-- 
2.11.0

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

* [PATCH 13/13] Make tuple comparison hints mandatory
  2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
                   ` (11 preceding siblings ...)
  2019-04-02 17:33 ` [PATCH 12/13] vinyl: forward tuple comparison hints to tx read set Vladimir Davydov
@ 2019-04-02 17:33 ` Vladimir Davydov
  2019-04-04 12:21   ` Konstantin Osipov
  12 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-02 17:33 UTC (permalink / raw)
  To: kostja.osipov; +Cc: tarantool-patches

There isn't much point in having separate versions of tuple comparators
that don't take tuple comparison hints anymore, because all hot paths
have been patched to use the hinted versions. Let's strip the _hinted
suffix from tuple_compare_hinted and others and zap un-hinted versions.
In a few remaining places in the code that still use un-hinted versions,
let's simply pass HINT_NONE.
---
 src/box/key_def.c           |   5 +-
 src/box/key_def.h           |  89 ++++-----------------
 src/box/memtx_hash.c        |   6 +-
 src/box/memtx_space.c       |   4 +-
 src/box/memtx_tree.c        |  27 +++----
 src/box/space.c             |   3 +-
 src/box/sql/analyze.c       |   5 +-
 src/box/tuple_compare.cc    | 190 +++++++++-----------------------------------
 src/box/vinyl.c             |  13 +--
 src/box/vy_cache.c          |  24 +++---
 src/box/vy_cache.h          |   6 +-
 src/box/vy_lsm.c            |  14 ++--
 src/box/vy_mem.c            |  38 +++++----
 src/box/vy_mem.h            |   8 +-
 src/box/vy_range.c          |  36 +++++----
 src/box/vy_read_iterator.c  |  43 +++++-----
 src/box/vy_read_set.c       |  18 ++---
 src/box/vy_run.c            |  80 +++++++++----------
 src/box/vy_stmt.h           |  56 ++++---------
 src/box/vy_tx.c             |  34 ++++----
 src/box/vy_upsert.c         |   3 +-
 src/box/vy_write_iterator.c |   5 +-
 22 files changed, 250 insertions(+), 457 deletions(-)

diff --git a/src/box/key_def.c b/src/box/key_def.c
index 55dcf1eb..f4a6c93c 100644
--- a/src/box/key_def.c
+++ b/src/box/key_def.c
@@ -276,7 +276,7 @@ int
 box_tuple_compare(const box_tuple_t *tuple_a, const box_tuple_t *tuple_b,
 		  box_key_def_t *key_def)
 {
-	return tuple_compare(tuple_a, tuple_b, key_def);
+	return tuple_compare(tuple_a, HINT_NONE, tuple_b, HINT_NONE, key_def);
 }
 
 int
@@ -284,7 +284,8 @@ box_tuple_compare_with_key(const box_tuple_t *tuple_a, const char *key_b,
 			   box_key_def_t *key_def)
 {
 	uint32_t part_count = mp_decode_array(&key_b);
-	return tuple_compare_with_key(tuple_a, key_b, part_count, key_def);
+	return tuple_compare_with_key(tuple_a, HINT_NONE, key_b, part_count,
+				      HINT_NONE, key_def);
 
 }
 
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 7bb2986b..97ca0348 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -150,27 +150,18 @@ key_part_is_nullable(const struct key_part *part)
 }
 
 /** @copydoc tuple_compare_with_key() */
-typedef int (*tuple_compare_with_key_t)(const struct tuple *tuple_a,
+typedef int (*tuple_compare_with_key_t)(const struct tuple *tuple,
+					hint_t tuple_hint,
 					const char *key,
 					uint32_t part_count,
+					hint_t key_hint,
 					struct key_def *key_def);
-/** @copydoc tuple_compare_with_key_hinted() */
-typedef int (*tuple_compare_with_key_hinted_t)(const struct tuple *tuple,
-					       hint_t tuple_hint,
-					       const char *key,
-					       uint32_t part_count,
-					       hint_t key_hint,
-					       struct key_def *key_def);
 /** @copydoc tuple_compare() */
 typedef int (*tuple_compare_t)(const struct tuple *tuple_a,
+			       hint_t tuple_a_hint,
 			       const struct tuple *tuple_b,
+			       hint_t tuple_b_hint,
 			       struct key_def *key_def);
-/** @copydoc tuple_compare_hinted() */
-typedef int (*tuple_compare_hinted_t)(const struct tuple *tuple_a,
-				      hint_t tuple_a_hint,
-				      const struct tuple *tuple_b,
-				      hint_t tuple_b_hint,
-				      struct key_def *key_def);
 /** @copydoc tuple_extract_key() */
 typedef char *(*tuple_extract_key_t)(const struct tuple *tuple,
 				     struct key_def *key_def,
@@ -197,12 +188,8 @@ typedef hint_t (*key_hint_t)(const char *key, uint32_t part_count,
 struct key_def {
 	/** @see tuple_compare() */
 	tuple_compare_t tuple_compare;
-	/** @see tuple_compare_hinted() */
-	tuple_compare_hinted_t tuple_compare_hinted;
 	/** @see tuple_compare_with_key() */
 	tuple_compare_with_key_t tuple_compare_with_key;
-	/** @see tuple_compare_with_key_hinted() */
-	tuple_compare_with_key_hinted_t tuple_compare_with_key_hinted;
 	/** @see tuple_extract_key() */
 	tuple_extract_key_t tuple_extract_key;
 	/** @see tuple_extract_key_raw() */
@@ -575,19 +562,6 @@ tuple_extract_key_raw(const char *data, const char *data_end,
 }
 
 /**
- * Compare keys using the key definition.
- * @param key_a key parts with MessagePack array header
- * @param key_b key_parts with MessagePack array header
- * @param key_def key definition
- *
- * @retval 0  if key_a == key_b
- * @retval <0 if key_a < key_b
- * @retval >0 if key_a > key_b
- */
-int
-key_compare(const char *key_a, const char *key_b, struct key_def *key_def);
-
-/**
  * Compare keys using the key definition and comparison hints.
  * @param key_a key parts with MessagePack array header
  * @param key_a_hint comparison hint of @a key_a
@@ -600,25 +574,9 @@ key_compare(const char *key_a, const char *key_b, struct key_def *key_def);
  * @retval >0 if key_a > key_b
  */
 int
-key_compare_hinted(const char *key_a, hint_t key_a_hint,
-		   const char *key_b, hint_t key_b_hint,
-		   struct key_def *key_def);
-
-/**
- * Compare tuples using the key definition.
- * @param tuple_a first tuple
- * @param tuple_b second tuple
- * @param key_def key definition
- * @retval 0  if key_fields(tuple_a) == key_fields(tuple_b)
- * @retval <0 if key_fields(tuple_a) < key_fields(tuple_b)
- * @retval >0 if key_fields(tuple_a) > key_fields(tuple_b)
- */
-static inline int
-tuple_compare(const struct tuple *tuple_a, const struct tuple *tuple_b,
-	      struct key_def *key_def)
-{
-	return key_def->tuple_compare(tuple_a, tuple_b, key_def);
-}
+key_compare(const char *key_a, hint_t key_a_hint,
+	    const char *key_b, hint_t key_b_hint,
+	    struct key_def *key_def);
 
 /**
  * Compare tuples using the key definition and comparison hints.
@@ -632,30 +590,12 @@ tuple_compare(const struct tuple *tuple_a, const struct tuple *tuple_b,
  * @retval >0 if key_fields(tuple_a) > key_fields(tuple_b)
  */
 static inline int
-tuple_compare_hinted(const struct tuple *tuple_a, hint_t tuple_a_hint,
+tuple_compare(const struct tuple *tuple_a, hint_t tuple_a_hint,
 		     const struct tuple *tuple_b, hint_t tuple_b_hint,
 		     struct key_def *key_def)
 {
-	return key_def->tuple_compare_hinted(tuple_a, tuple_a_hint, tuple_b,
-					     tuple_b_hint, key_def);
-}
-
-/**
- * @brief Compare tuple with key using the key definition.
- * @param tuple tuple
- * @param key key parts without MessagePack array header
- * @param part_count the number of parts in @a key
- * @param key_def key definition
- *
- * @retval 0  if key_fields(tuple) == parts(key)
- * @retval <0 if key_fields(tuple) < parts(key)
- * @retval >0 if key_fields(tuple) > parts(key)
- */
-static inline int
-tuple_compare_with_key(const struct tuple *tuple, const char *key,
-		       uint32_t part_count, struct key_def *key_def)
-{
-	return key_def->tuple_compare_with_key(tuple, key, part_count, key_def);
+	return key_def->tuple_compare(tuple_a, tuple_a_hint, tuple_b,
+				      tuple_b_hint, key_def);
 }
 
 /**
@@ -672,13 +612,12 @@ tuple_compare_with_key(const struct tuple *tuple, const char *key,
  * @retval >0 if key_fields(tuple) > parts(key)
  */
 static inline int
-tuple_compare_with_key_hinted(const struct tuple *tuple, hint_t tuple_hint,
+tuple_compare_with_key(const struct tuple *tuple, hint_t tuple_hint,
 			      const char *key, uint32_t part_count,
 			      hint_t key_hint, struct key_def *key_def)
 {
-	return key_def->tuple_compare_with_key_hinted(tuple, tuple_hint, key,
-						      part_count, key_hint,
-						      key_def);
+	return key_def->tuple_compare_with_key(tuple, tuple_hint, key,
+					       part_count, key_hint, key_def);
 }
 
 /**
diff --git a/src/box/memtx_hash.c b/src/box/memtx_hash.c
index ff478bdc..585cf3e0 100644
--- a/src/box/memtx_hash.c
+++ b/src/box/memtx_hash.c
@@ -44,14 +44,16 @@ static inline bool
 memtx_hash_equal(struct tuple *tuple_a, struct tuple *tuple_b,
 		 struct key_def *key_def)
 {
-	return tuple_compare(tuple_a, tuple_b, key_def) == 0;
+	return tuple_compare(tuple_a, HINT_NONE,
+			     tuple_b, HINT_NONE, key_def) == 0;
 }
 
 static inline bool
 memtx_hash_equal_key(struct tuple *tuple, const char *key,
 		     struct key_def *key_def)
 {
-	return tuple_compare_with_key(tuple, key, key_def->part_count,
+	return tuple_compare_with_key(tuple, HINT_NONE, key,
+				      key_def->part_count, HINT_NONE,
 				      key_def) == 0;
 }
 
diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index d8529fe0..73df4395 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -514,8 +514,8 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 		struct index *pk = space->index[0];
 		if (!key_update_can_be_skipped(pk->def->key_def->column_mask,
 					       column_mask) &&
-		    tuple_compare(old_tuple, stmt->new_tuple,
-				  pk->def->key_def) != 0) {
+		    tuple_compare(old_tuple, HINT_NONE, stmt->new_tuple,
+				  HINT_NONE, pk->def->key_def) != 0) {
 			/* Primary key is changed: log error and do nothing. */
 			diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
 				 pk->def->name, space_name(space));
diff --git a/src/box/memtx_tree.c b/src/box/memtx_tree.c
index fe037c54..ddace2e3 100644
--- a/src/box/memtx_tree.c
+++ b/src/box/memtx_tree.c
@@ -80,11 +80,10 @@ memtx_tree_data_identical(const struct memtx_tree_data *a,
 #define BPS_TREE_BLOCK_SIZE (512)
 #define BPS_TREE_EXTENT_SIZE MEMTX_EXTENT_SIZE
 #define BPS_TREE_COMPARE(a, b, arg)\
-	tuple_compare_hinted((&a)->tuple, (&a)->hint, (&b)->tuple,\
-			     (&b)->hint, arg)
+	tuple_compare((&a)->tuple, (&a)->hint, (&b)->tuple, (&b)->hint, arg)
 #define BPS_TREE_COMPARE_KEY(a, b, arg)\
-	tuple_compare_with_key_hinted((&a)->tuple, (&a)->hint, (b)->key,\
-				      (b)->part_count, (b)->hint, arg)
+	tuple_compare_with_key((&a)->tuple, (&a)->hint, (b)->key,\
+			       (b)->part_count, (b)->hint, arg)
 #define BPS_TREE_IDENTICAL(a, b) memtx_tree_data_identical(&a, &b)
 #define bps_tree_elem_t struct memtx_tree_data
 #define bps_tree_key_t struct memtx_tree_key_data *
@@ -125,8 +124,8 @@ 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_hinted(data_a->tuple, data_a->hint, data_b->tuple,
-				    data_b->hint, key_def);
+	return tuple_compare(data_a->tuple, data_a->hint, data_b->tuple,
+			     data_b->hint, key_def);
 }
 
 /* {{{ MemtxTree Iterators ****************************************/
@@ -248,11 +247,9 @@ tree_iterator_next_equal(struct iterator *iterator, struct tuple **ret)
 		memtx_tree_iterator_get_elem(it->tree, &it->tree_iterator);
 	/* Use user key def to save a few loops. */
 	if (res == NULL ||
-	    tuple_compare_with_key_hinted(res->tuple, res->hint,
-					  it->key_data.key,
-					  it->key_data.part_count,
-					  it->key_data.hint,
-					  it->index_def->key_def) != 0) {
+	    tuple_compare_with_key(res->tuple, res->hint, it->key_data.key,
+				   it->key_data.part_count, it->key_data.hint,
+				   it->index_def->key_def) != 0) {
 		iterator->next = tree_iterator_dummie;
 		it->current.tuple = NULL;
 		*ret = NULL;
@@ -281,11 +278,9 @@ tree_iterator_prev_equal(struct iterator *iterator, struct tuple **ret)
 		memtx_tree_iterator_get_elem(it->tree, &it->tree_iterator);
 	/* Use user key def to save a few loops. */
 	if (res == NULL ||
-	    tuple_compare_with_key_hinted(res->tuple, res->hint,
-					  it->key_data.key,
-					  it->key_data.part_count,
-					  it->key_data.hint,
-					  it->index_def->key_def) != 0) {
+	    tuple_compare_with_key(res->tuple, res->hint, it->key_data.key,
+				   it->key_data.part_count, it->key_data.hint,
+				   it->index_def->key_def) != 0) {
 		iterator->next = tree_iterator_dummie;
 		it->current.tuple = NULL;
 		*ret = NULL;
diff --git a/src/box/space.c b/src/box/space.c
index 1379fa34..e201a63c 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -445,7 +445,8 @@ space_before_replace(struct space *space, struct txn *txn,
 	 */
 	if (pk != NULL && request_changed &&
 	    old_tuple != NULL && new_tuple != NULL &&
-	    tuple_compare(old_tuple, new_tuple, pk->def->key_def) != 0) {
+	    tuple_compare(old_tuple, HINT_NONE, new_tuple, HINT_NONE,
+			  pk->def->key_def) != 0) {
 		diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
 			 pk->def->name, space->def->name);
 		rc = -1;
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 0663c66b..a2177f08 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -1345,8 +1345,9 @@ static int
 sample_compare(const void *a, const void *b, void *arg)
 {
 	struct key_def *def = (struct key_def *)arg;
-	return key_compare(((struct index_sample *) a)->sample_key,
-			   ((struct index_sample *) b)->sample_key, def);
+	return key_compare(((struct index_sample *) a)->sample_key, HINT_NONE,
+			   ((struct index_sample *) b)->sample_key, HINT_NONE,
+			   def);
 }
 
 /**
diff --git a/src/box/tuple_compare.cc b/src/box/tuple_compare.cc
index 055f3181..95c4ab83 100644
--- a/src/box/tuple_compare.cc
+++ b/src/box/tuple_compare.cc
@@ -447,9 +447,9 @@ tuple_compare_field_with_type(const char *field_a, enum mp_type a_type,
 
 template<bool is_nullable, bool has_optional_parts, bool has_json_paths>
 static inline int
-tuple_compare_slowpath_hinted(const struct tuple *tuple_a, hint_t tuple_a_hint,
-			      const struct tuple *tuple_b, hint_t tuple_b_hint,
-			      struct key_def *key_def)
+tuple_compare_slowpath(const struct tuple *tuple_a, hint_t tuple_a_hint,
+		       const struct tuple *tuple_b, hint_t tuple_b_hint,
+		       struct key_def *key_def)
 {
 	assert(has_json_paths == key_def->has_json_paths);
 	assert(!has_optional_parts || is_nullable);
@@ -582,17 +582,7 @@ tuple_compare_slowpath_hinted(const struct tuple *tuple_a, hint_t tuple_a_hint,
 
 template<bool is_nullable, bool has_optional_parts, bool has_json_paths>
 static inline int
-tuple_compare_slowpath(const struct tuple *tuple_a, const struct tuple *tuple_b,
-		       struct key_def *key_def)
-{
-	return tuple_compare_slowpath_hinted
-		<is_nullable, has_optional_parts, has_json_paths>
-		(tuple_a, HINT_NONE, tuple_b, HINT_NONE, key_def);
-}
-
-template<bool is_nullable, bool has_optional_parts, bool has_json_paths>
-static inline int
-tuple_compare_with_key_slowpath_hinted(const struct tuple *tuple,
+tuple_compare_with_key_slowpath(const struct tuple *tuple,
 		hint_t tuple_hint, const char *key, uint32_t part_count,
 		hint_t key_hint, struct key_def *key_def)
 {
@@ -678,16 +668,6 @@ tuple_compare_with_key_slowpath_hinted(const struct tuple *tuple,
 	return 0;
 }
 
-template<bool is_nullable, bool has_optional_parts, bool has_json_paths>
-static inline int
-tuple_compare_with_key_slowpath(const struct tuple *tuple, const char *key,
-				uint32_t part_count, struct key_def *key_def)
-{
-	return tuple_compare_with_key_slowpath_hinted
-		<is_nullable, has_optional_parts, has_json_paths>
-		(tuple, HINT_NONE, key, part_count, HINT_NONE, key_def);
-}
-
 template<bool is_nullable>
 static inline int
 key_compare_parts(const char *key_a, const char *key_b, uint32_t part_count,
@@ -746,7 +726,7 @@ key_compare_parts(const char *key_a, const char *key_b, uint32_t part_count,
 
 template<bool is_nullable, bool has_optional_parts>
 static inline int
-tuple_compare_with_key_sequential_hinted(const struct tuple *tuple,
+tuple_compare_with_key_sequential(const struct tuple *tuple,
 		hint_t tuple_hint, const char *key, uint32_t part_count,
 		hint_t key_hint, struct key_def *key_def)
 {
@@ -789,19 +769,14 @@ tuple_compare_with_key_sequential_hinted(const struct tuple *tuple,
 	return 0;
 }
 
-template<bool is_nullable, bool has_optional_parts>
-static inline int
-tuple_compare_with_key_sequential(const struct tuple *tuple, const char *key,
-				  uint32_t part_count, struct key_def *key_def)
-{
-	return tuple_compare_with_key_sequential_hinted
-		<is_nullable, has_optional_parts>
-		(tuple, HINT_NONE, key, part_count, HINT_NONE, key_def);
-}
-
 int
-key_compare(const char *key_a, const char *key_b, struct key_def *key_def)
+key_compare(const char *key_a, hint_t key_a_hint,
+	    const char *key_b, hint_t key_b_hint,
+	    struct key_def *key_def)
 {
+	int rc = hint_cmp(key_a_hint, key_b_hint);
+	if (rc != 0)
+		return rc;
 	uint32_t part_count_a = mp_decode_array(&key_a);
 	uint32_t part_count_b = mp_decode_array(&key_b);
 	assert(part_count_a <= key_def->part_count);
@@ -817,22 +792,11 @@ key_compare(const char *key_a, const char *key_b, struct key_def *key_def)
 	}
 }
 
-int
-key_compare_hinted(const char *key_a, hint_t key_a_hint,
-		   const char *key_b, hint_t key_b_hint,
-		   struct key_def *key_def)
-{
-	int rc = hint_cmp(key_a_hint, key_b_hint);
-	if (rc != 0)
-		return rc;
-	return key_compare(key_a, key_b, key_def);
-}
-
 template <bool is_nullable, bool has_optional_parts>
 static int
-tuple_compare_sequential_hinted(const struct tuple *tuple_a, hint_t tuple_a_hint,
-				const struct tuple *tuple_b, hint_t tuple_b_hint,
-				struct key_def *key_def)
+tuple_compare_sequential(const struct tuple *tuple_a, hint_t tuple_a_hint,
+			 const struct tuple *tuple_b, hint_t tuple_b_hint,
+			 struct key_def *key_def)
 {
 	assert(!has_optional_parts || is_nullable);
 	assert(has_optional_parts == key_def->has_optional_parts);
@@ -900,16 +864,6 @@ tuple_compare_sequential_hinted(const struct tuple *tuple_a, hint_t tuple_a_hint
 	return 0;
 }
 
-template <bool is_nullable, bool has_optional_parts>
-static int
-tuple_compare_sequential(const struct tuple *tuple_a,
-			 const struct tuple *tuple_b, struct key_def *key_def)
-{
-	return tuple_compare_sequential_hinted
-		<is_nullable, has_optional_parts>
-		(tuple_a, HINT_NONE, tuple_b, HINT_NONE, key_def);
-}
-
 template <int TYPE>
 static inline int
 field_compare(const char **field_a, const char **field_b);
@@ -1025,10 +979,13 @@ struct FieldCompare<IDX, TYPE>
 template <int IDX, int TYPE, int ...MORE_TYPES>
 struct TupleCompare
 {
-	static int compare(const struct tuple *tuple_a,
-			   const struct tuple *tuple_b,
+	static int compare(const struct tuple *tuple_a, hint_t tuple_a_hint,
+			   const struct tuple *tuple_b, hint_t tuple_b_hint,
 			   struct key_def *)
 	{
+		int rc = hint_cmp(tuple_a_hint, tuple_b_hint);
+		if (rc != 0)
+			return rc;
 		struct tuple_format *format_a = tuple_format(tuple_a);
 		struct tuple_format *format_b = tuple_format(tuple_b);
 		const char *field_a, *field_b;
@@ -1040,26 +997,17 @@ struct TupleCompare
 			compare(tuple_a, tuple_b, format_a,
 				format_b, field_a, field_b);
 	}
-
-	static int compare_hinted(const struct tuple *tuple_a,
-				  hint_t tuple_a_hint,
-				  const struct tuple *tuple_b,
-				  hint_t tuple_b_hint,
-				  struct key_def *key_def)
-	{
-		int rc = hint_cmp(tuple_a_hint, tuple_b_hint);
-		if (rc != 0)
-			return rc;
-		return compare(tuple_a, tuple_b, key_def);
-	}
 };
 
 template <int TYPE, int ...MORE_TYPES>
 struct TupleCompare<0, TYPE, MORE_TYPES...> {
-	static int compare(const struct tuple *tuple_a,
-			   const struct tuple *tuple_b,
+	static int compare(const struct tuple *tuple_a, hint_t tuple_a_hint,
+			   const struct tuple *tuple_b, hint_t tuple_b_hint,
 			   struct key_def *)
 	{
+		int rc = hint_cmp(tuple_a_hint, tuple_b_hint);
+		if (rc != 0)
+			return rc;
 		struct tuple_format *format_a = tuple_format(tuple_a);
 		struct tuple_format *format_b = tuple_format(tuple_b);
 		const char *field_a = tuple_data(tuple_a);
@@ -1069,30 +1017,15 @@ struct TupleCompare<0, TYPE, MORE_TYPES...> {
 		return FieldCompare<0, TYPE, MORE_TYPES...>::compare(tuple_a, tuple_b,
 					format_a, format_b, field_a, field_b);
 	}
-
-	static int compare_hinted(const struct tuple *tuple_a,
-				  hint_t tuple_a_hint,
-				  const struct tuple *tuple_b,
-				  hint_t tuple_b_hint,
-				  struct key_def *key_def)
-	{
-		int rc = hint_cmp(tuple_a_hint, tuple_b_hint);
-		if (rc != 0)
-			return rc;
-		return compare(tuple_a, tuple_b, key_def);
-	}
 };
 } /* end of anonymous namespace */
 
 struct comparator_signature {
 	tuple_compare_t f;
-	tuple_compare_hinted_t f_hinted;
 	uint32_t p[64];
 };
 #define COMPARATOR(...) \
-	{ TupleCompare<__VA_ARGS__>::compare, \
-	  TupleCompare<__VA_ARGS__>::compare_hinted, \
-	  { __VA_ARGS__, UINT32_MAX } },
+	{ TupleCompare<__VA_ARGS__>::compare, { __VA_ARGS__, UINT32_MAX } },
 
 /**
  * field1 no, field1 type, field2 no, field2 type, ...
@@ -1230,12 +1163,16 @@ template <int FLD_ID, int IDX, int TYPE, int ...MORE_TYPES>
 struct TupleCompareWithKey
 {
 	static int
-	compare(const struct tuple *tuple, const char *key,
-		uint32_t part_count, struct key_def *key_def)
+	compare(const struct tuple *tuple, hint_t tuple_hint,
+		const char *key, uint32_t part_count, hint_t key_hint,
+		struct key_def *key_def)
 	{
 		/* Part count can be 0 in wildcard searches. */
 		if (part_count == 0)
 			return 0;
+		int rc = hint_cmp(tuple_hint, key_hint);
+		if (rc != 0)
+			return rc;
 		struct tuple_format *format = tuple_format(tuple);
 		const char *field = tuple_field_raw(format, tuple_data(tuple),
 						    tuple_field_map(tuple),
@@ -1244,30 +1181,22 @@ struct TupleCompareWithKey
 				compare(tuple, key, part_count,
 					key_def, format, field);
 	}
-
-	static int
-	compare_hinted(const struct tuple *tuple, hint_t tuple_hint,
-		       const char *key, uint32_t part_count, hint_t key_hint,
-		       struct key_def *key_def)
-	{
-		int rc = hint_cmp(tuple_hint, key_hint);
-		if (rc != 0)
-			return rc;
-		return compare(tuple, key, part_count, key_def);
-	}
 };
 
 template <int TYPE, int ...MORE_TYPES>
 struct TupleCompareWithKey<0, 0, TYPE, MORE_TYPES...>
 {
-	static int compare(const struct tuple *tuple,
-				  const char *key,
-				  uint32_t part_count,
-				  struct key_def *key_def)
+	static int
+	compare(const struct tuple *tuple, hint_t tuple_hint,
+		const char *key, uint32_t part_count, hint_t key_hint,
+		struct key_def *key_def)
 	{
 		/* Part count can be 0 in wildcard searches. */
 		if (part_count == 0)
 			return 0;
+		int rc = hint_cmp(tuple_hint, key_hint);
+		if (rc != 0)
+			return rc;
 		struct tuple_format *format = tuple_format(tuple);
 		const char *field = tuple_data(tuple);
 		mp_decode_array(&field);
@@ -1275,17 +1204,6 @@ struct TupleCompareWithKey<0, 0, TYPE, MORE_TYPES...>
 			compare(tuple, key, part_count,
 				key_def, format, field);
 	}
-
-	static int
-	compare_hinted(const struct tuple *tuple, hint_t tuple_hint,
-		       const char *key, uint32_t part_count, hint_t key_hint,
-		       struct key_def *key_def)
-	{
-		int rc = hint_cmp(tuple_hint, key_hint);
-		if (rc != 0)
-			return rc;
-		return compare(tuple, key, part_count, key_def);
-	}
 };
 
 } /* end of anonymous namespace */
@@ -1293,14 +1211,11 @@ struct TupleCompareWithKey<0, 0, TYPE, MORE_TYPES...>
 struct comparator_with_key_signature
 {
 	tuple_compare_with_key_t f;
-	tuple_compare_with_key_hinted_t f_hinted;
 	uint32_t p[64];
 };
 
 #define KEY_COMPARATOR(...) \
-	{ TupleCompareWithKey<0, __VA_ARGS__>::compare, \
-	  TupleCompareWithKey<0, __VA_ARGS__>::compare_hinted, \
-	  { __VA_ARGS__ } },
+	{ TupleCompareWithKey<0, __VA_ARGS__>::compare, { __VA_ARGS__ } },
 
 static const comparator_with_key_signature cmp_wk_arr[] = {
 	KEY_COMPARATOR(0, FIELD_TYPE_UNSIGNED, 1, FIELD_TYPE_UNSIGNED, 2, FIELD_TYPE_UNSIGNED)
@@ -1681,9 +1596,7 @@ key_def_set_compare_func_fast(struct key_def *def)
 	assert(!key_def_has_collation(def));
 
 	tuple_compare_t cmp = NULL;
-	tuple_compare_hinted_t cmp_hinted = NULL;
 	tuple_compare_with_key_t cmp_wk = NULL;
-	tuple_compare_with_key_hinted_t cmp_wk_hinted = NULL;
 	bool is_sequential = key_def_is_sequential(def);
 
 	/*
@@ -1698,7 +1611,6 @@ key_def_set_compare_func_fast(struct key_def *def)
 				break;
 		if (i == def->part_count && cmp_arr[k].p[i * 2] == UINT32_MAX) {
 			cmp = cmp_arr[k].f;
-			cmp_hinted = cmp_arr[k].f_hinted;
 			break;
 		}
 	}
@@ -1711,7 +1623,6 @@ key_def_set_compare_func_fast(struct key_def *def)
 		}
 		if (i == def->part_count) {
 			cmp_wk = cmp_wk_arr[k].f;
-			cmp_wk_hinted = cmp_wk_arr[k].f_hinted;
 			break;
 		}
 	}
@@ -1719,23 +1630,15 @@ key_def_set_compare_func_fast(struct key_def *def)
 		cmp = is_sequential ?
 			tuple_compare_sequential<false, false> :
 			tuple_compare_slowpath<false, false, false>;
-		cmp_hinted = is_sequential ?
-			tuple_compare_sequential_hinted<false, false> :
-			tuple_compare_slowpath_hinted<false, false, false>;
 	}
 	if (cmp_wk == NULL) {
 		cmp_wk = is_sequential ?
 			tuple_compare_with_key_sequential<false, false> :
 			tuple_compare_with_key_slowpath<false, false, false>;
-		cmp_wk_hinted = is_sequential ?
-			tuple_compare_with_key_sequential_hinted<false, false> :
-			tuple_compare_with_key_slowpath_hinted<false, false, false>;
 	}
 
 	def->tuple_compare = cmp;
-	def->tuple_compare_hinted = cmp_hinted;
 	def->tuple_compare_with_key = cmp_wk;
-	def->tuple_compare_with_key_hinted = cmp_wk_hinted;
 }
 
 template<bool is_nullable, bool has_optional_parts>
@@ -1746,23 +1649,13 @@ key_def_set_compare_func_plain(struct key_def *def)
 	if (key_def_is_sequential(def)) {
 		def->tuple_compare = tuple_compare_sequential
 					<is_nullable, has_optional_parts>;
-		def->tuple_compare_hinted = tuple_compare_sequential_hinted
-					<is_nullable, has_optional_parts>;
 		def->tuple_compare_with_key = tuple_compare_with_key_sequential
 					<is_nullable, has_optional_parts>;
-		def->tuple_compare_with_key_hinted =
-					tuple_compare_with_key_sequential_hinted
-					<is_nullable, has_optional_parts>;
 	} else {
 		def->tuple_compare = tuple_compare_slowpath
 				<is_nullable, has_optional_parts, false>;
-		def->tuple_compare_hinted = tuple_compare_slowpath_hinted
-				<is_nullable, has_optional_parts, false>;
 		def->tuple_compare_with_key = tuple_compare_with_key_slowpath
 				<is_nullable, has_optional_parts, false>;
-		def->tuple_compare_with_key_hinted =
-					tuple_compare_with_key_slowpath_hinted
-					<is_nullable, has_optional_parts, false>;
 	}
 }
 
@@ -1773,13 +1666,8 @@ key_def_set_compare_func_json(struct key_def *def)
 	assert(def->has_json_paths);
 	def->tuple_compare = tuple_compare_slowpath
 			<is_nullable, has_optional_parts, true>;
-	def->tuple_compare_hinted = tuple_compare_slowpath_hinted
-			<is_nullable, has_optional_parts, true>;
 	def->tuple_compare_with_key = tuple_compare_with_key_slowpath
 			<is_nullable, has_optional_parts, true>;
-	def->tuple_compare_with_key_hinted =
-			tuple_compare_with_key_slowpath_hinted
-			<is_nullable, has_optional_parts, true>;
 }
 
 void
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index fc90b372..cb6bedb0 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1354,7 +1354,8 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 	}
 
 	if (*result == NULL ||
-	    vy_stmt_compare(*result, tuple, lsm->cmp_def) != 0) {
+	    vy_stmt_compare(*result, HINT_NONE, tuple, HINT_NONE,
+			    lsm->cmp_def) != 0) {
 		/*
 		 * If a tuple read from a secondary index doesn't
 		 * match the tuple corresponding to it in the
@@ -1581,7 +1582,8 @@ vy_check_is_unique_secondary(struct vy_tx *tx, const struct vy_read_view **rv,
 	 * fail here.
 	 */
 	if (found != NULL && vy_stmt_type(stmt) == IPROTO_REPLACE &&
-	    vy_stmt_compare(stmt, found, lsm->pk->key_def) == 0) {
+	    vy_stmt_compare(stmt, HINT_NONE, found, HINT_NONE,
+			    lsm->pk->key_def) == 0) {
 		tuple_unref(found);
 		return 0;
 	}
@@ -1786,7 +1788,8 @@ vy_check_update(struct space *space, const struct vy_lsm *pk,
 		uint64_t column_mask)
 {
 	if (!key_update_can_be_skipped(pk->key_def->column_mask, column_mask) &&
-	    vy_stmt_compare(old_tuple, new_tuple, pk->key_def) != 0) {
+	    vy_stmt_compare(old_tuple, HINT_NONE, new_tuple, HINT_NONE,
+			    pk->key_def) != 0) {
 		diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
 			 index_name_by_id(space, pk->index_id),
 			 space_name(space));
@@ -3577,8 +3580,8 @@ vy_squash_process(struct vy_squash *squash)
 	while (!vy_mem_tree_iterator_is_invalid(&mem_itr)) {
 		struct vy_mem_tree_elem elem;
 		elem = *vy_mem_tree_iterator_get_elem(&mem->tree, &mem_itr);
-		if (vy_stmt_compare_hinted(result, hint, elem.stmt, elem.hint,
-					   lsm->cmp_def) != 0 ||
+		if (vy_stmt_compare(result, hint, elem.stmt, elem.hint,
+				    lsm->cmp_def) != 0 ||
 		    vy_stmt_type(elem.stmt) != IPROTO_UPSERT)
 			break;
 		assert(vy_stmt_lsn(elem.stmt) >= MAX_LSN);
diff --git a/src/box/vy_cache.c b/src/box/vy_cache.c
index ee3581f3..6415047c 100644
--- a/src/box/vy_cache.c
+++ b/src/box/vy_cache.c
@@ -374,10 +374,8 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt, hint_t hint,
 		assert(*prev_check_entry != NULL);
 		struct tuple *prev_check_stmt = (*prev_check_entry)->stmt;
 		hint_t prev_check_hint = (*prev_check_entry)->hint;
-		int cmp = vy_stmt_compare_hinted(prev_stmt, prev_hint,
-						 prev_check_stmt,
-						 prev_check_hint,
-						 cache->cmp_def);
+		int cmp = vy_stmt_compare(prev_stmt, prev_hint, prev_check_stmt,
+					  prev_check_hint, cache->cmp_def);
 
 		if (entry->flags & flag) {
 			/* The found entry must be exactly prev_stmt. (2) */
@@ -611,8 +609,8 @@ vy_cache_iterator_step(struct vy_cache_iterator *itr)
 		*vy_cache_tree_iterator_get_elem(tree, &itr->curr_pos);
 
 	if (itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare_hinted(itr->key, itr->hint, entry->stmt,
-				   entry->hint, itr->cache->cmp_def)) {
+	    vy_stmt_compare(itr->key, itr->hint, entry->stmt, entry->hint,
+			    itr->cache->cmp_def)) {
 		return vy_cache_iterator_is_end_stop(itr, prev_entry);
 	}
 	itr->curr_stmt = entry->stmt;
@@ -695,8 +693,8 @@ vy_cache_iterator_seek(struct vy_cache_iterator *itr,
 	if (itr->iterator_type == ITER_EQ &&
 	    ((last_key == NULL && !exact) ||
 	     (last_key != NULL &&
-	      vy_stmt_compare_hinted(itr->key, itr->hint, entry->stmt,
-				     entry->hint, itr->cache->cmp_def) != 0)))
+	      vy_stmt_compare(itr->key, itr->hint, entry->stmt, entry->hint,
+			      itr->cache->cmp_def) != 0)))
 		return false;
 
 	itr->curr_stmt = entry->stmt;
@@ -747,9 +745,8 @@ vy_cache_iterator_skip(struct vy_cache_iterator *itr,
 	if (itr->search_started &&
 	    (itr->curr_stmt == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
-				    last_stmt, last_hint,
-				    itr->cache->cmp_def) > 0))
+	     vy_stmt_compare(itr->curr_stmt, itr->curr_hint, last_stmt,
+			     last_hint, itr->cache->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
@@ -831,9 +828,8 @@ vy_cache_iterator_restore(struct vy_cache_iterator *itr,
 				break;
 			struct vy_cache_entry *entry =
 				*vy_cache_tree_iterator_get_elem(tree, &pos);
-			int cmp = dir * vy_stmt_compare_hinted(entry->stmt,
-							       entry->hint,
-							       key, hint, def);
+			int cmp = dir * vy_stmt_compare(entry->stmt, entry->hint,
+							key, hint, def);
 			if (cmp < 0 || (cmp == 0 && !key_belongs))
 				break;
 			if (vy_stmt_lsn(entry->stmt) <= (**itr->read_view).vlsn) {
diff --git a/src/box/vy_cache.h b/src/box/vy_cache.h
index 9be2fe68..5c89dc7e 100644
--- a/src/box/vy_cache.h
+++ b/src/box/vy_cache.h
@@ -90,8 +90,7 @@ static inline int
 vy_cache_tree_cmp(struct vy_cache_entry *a,
 		  struct vy_cache_entry *b, struct key_def *cmp_def)
 {
-	return vy_stmt_compare_hinted(a->stmt, a->hint,
-				      b->stmt, b->hint, cmp_def);
+	return vy_stmt_compare(a->stmt, a->hint, b->stmt, b->hint, cmp_def);
 }
 
 /**
@@ -101,8 +100,7 @@ static inline int
 vy_cache_tree_key_cmp(struct vy_cache_entry *a, struct vy_cache_tree_key *b,
 		      struct key_def *cmp_def)
 {
-	return vy_stmt_compare_hinted(a->stmt, a->hint,
-				      b->stmt, b->hint, cmp_def);
+	return vy_stmt_compare(a->stmt, a->hint, b->stmt, b->hint, cmp_def);
 }
 
 #define VY_CACHE_TREE_EXTENT_SIZE (16 * 1024)
diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index f2f9fdaf..d109aa3c 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -415,8 +415,8 @@ vy_lsm_recover_slice(struct vy_lsm *lsm, struct vy_range *range,
 		end_hint = vy_stmt_hint(end, lsm->cmp_def);
 	}
 	if (begin != NULL && end != NULL &&
-	    vy_stmt_compare_hinted(begin, begin_hint, end, end_hint,
-				   lsm->cmp_def) >= 0) {
+	    vy_stmt_compare(begin, begin_hint, end, end_hint,
+			    lsm->cmp_def) >= 0) {
 		diag_set(ClientError, ER_INVALID_VYLOG_FILE,
 			 tt_sprintf("begin >= end for slice %lld",
 				    (long long)slice_info->id));
@@ -466,8 +466,8 @@ vy_lsm_recover_range(struct vy_lsm *lsm,
 		end_hint = vy_stmt_hint(end, lsm->cmp_def);
 	}
 	if (begin != NULL && end != NULL &&
-	    vy_stmt_compare_hinted(begin, begin_hint, end, end_hint,
-				   lsm->cmp_def) >= 0) {
+	    vy_stmt_compare(begin, begin_hint, end, end_hint,
+			    lsm->cmp_def) >= 0) {
 		diag_set(ClientError, ER_INVALID_VYLOG_FILE,
 			 tt_sprintf("begin >= end for range %lld",
 				    (long long)range_info->id));
@@ -650,9 +650,9 @@ vy_lsm_recover(struct vy_lsm *lsm, struct vy_recovery *recovery,
 		int cmp = 0;
 		if (prev != NULL &&
 		    (prev->end == NULL || range->begin == NULL ||
-		     (cmp = vy_stmt_compare_hinted(prev->end, prev->end_hint,
-						   range->begin, range->begin_hint,
-						   lsm->cmp_def)) != 0)) {
+		     (cmp = vy_stmt_compare(prev->end, prev->end_hint,
+					    range->begin, range->begin_hint,
+					    lsm->cmp_def)) != 0)) {
 			const char *errmsg = cmp > 0 ?
 				"Nearby ranges %lld and %lld overlap" :
 				"Keys between ranges %lld and %lld not spanned";
diff --git a/src/box/vy_mem.c b/src/box/vy_mem.c
index 4947df2a..96bf60fa 100644
--- a/src/box/vy_mem.c
+++ b/src/box/vy_mem.c
@@ -162,8 +162,8 @@ vy_mem_older_lsn(struct vy_mem *mem, const struct tuple *stmt, hint_t hint)
 
 	struct vy_mem_tree_elem result;
 	result = *vy_mem_tree_iterator_get_elem(&mem->tree, &itr);
-	if (vy_stmt_compare_hinted(result.stmt, result.hint,
-				   stmt, hint, mem->cmp_def) != 0)
+	if (vy_stmt_compare(result.stmt, result.hint, stmt, hint,
+			    mem->cmp_def) != 0)
 		return NULL;
 	return result.stmt;
 }
@@ -214,8 +214,8 @@ vy_mem_insert_upsert(struct vy_mem *mem, const struct tuple *stmt, hint_t hint)
 	struct vy_mem_tree_elem older;
 	older = *vy_mem_tree_iterator_get_elem(&mem->tree, &inserted);
 	if (vy_stmt_type(older.stmt) != IPROTO_UPSERT ||
-	    vy_stmt_compare_hinted(stmt, hint, older.stmt, older.hint,
-				   mem->cmp_def) != 0)
+	    vy_stmt_compare(stmt, hint, older.stmt, older.hint,
+			    mem->cmp_def) != 0)
 		return 0;
 	uint8_t n_upserts = vy_stmt_n_upserts(older.stmt);
 	/*
@@ -333,9 +333,8 @@ vy_mem_iterator_find_lsn(struct vy_mem_iterator *itr)
 	       vy_stmt_flags(itr->curr.stmt) & VY_STMT_SKIP_READ) {
 		if (vy_mem_iterator_step(itr) != 0 ||
 		    (itr->iterator_type == ITER_EQ &&
-		     vy_stmt_compare_hinted(itr->key, itr->hint,
-					    itr->curr.stmt, itr->curr.hint,
-					    cmp_def) != 0)) {
+		     vy_stmt_compare(itr->key, itr->hint, itr->curr.stmt,
+				     itr->curr.hint, cmp_def) != 0)) {
 			itr->curr = vy_mem_tree_elem_invalid();
 			return 1;
 		}
@@ -356,8 +355,8 @@ vy_mem_iterator_find_lsn(struct vy_mem_iterator *itr)
 	struct vy_mem_tree_elem prev;
 	prev = *vy_mem_tree_iterator_get_elem(&itr->mem->tree, &prev_pos);
 	if (vy_stmt_lsn(prev.stmt) > (**itr->read_view).vlsn ||
-	    vy_stmt_compare_hinted(itr->curr.stmt, itr->curr.hint,
-				   prev.stmt, prev.hint, cmp_def) != 0) {
+	    vy_stmt_compare(itr->curr.stmt, itr->curr.hint,
+			    prev.stmt, prev.hint, cmp_def) != 0) {
 		/*
 		 * The next statement is either invisible in
 		 * the read view or for another key.
@@ -447,9 +446,8 @@ vy_mem_iterator_seek(struct vy_mem_iterator *itr, const struct tuple *last_key,
 	if (itr->iterator_type == ITER_EQ &&
 	    ((last_key == NULL && !exact) ||
 	     (last_key != NULL &&
-	      vy_stmt_compare_hinted(itr->key, itr->hint,
-				     itr->curr.stmt, itr->curr.hint,
-				     itr->mem->cmp_def) != 0))) {
+	      vy_stmt_compare(itr->key, itr->hint, itr->curr.stmt,
+			      itr->curr.hint, itr->mem->cmp_def) != 0))) {
 		itr->curr = vy_mem_tree_elem_invalid();
 		return 1;
 	}
@@ -511,13 +509,13 @@ vy_mem_iterator_next_key(struct vy_mem_iterator *itr)
 	 * for this key so instead of iterating further we simply
 	 * look up the next key - it's pretty cheap anyway.
 	 */
-	if (vy_stmt_compare_hinted(prev.stmt, prev.hint, itr->curr.stmt,
-				   itr->curr.hint, cmp_def) == 0)
+	if (vy_stmt_compare(prev.stmt, prev.hint, itr->curr.stmt,
+			    itr->curr.hint, cmp_def) == 0)
 		return vy_mem_iterator_seek(itr, itr->curr.stmt, itr->curr.hint);
 
 	if (itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare_hinted(itr->key, itr->hint, itr->curr.stmt,
-				   itr->curr.hint, cmp_def) != 0) {
+	    vy_stmt_compare(itr->key, itr->hint, itr->curr.stmt,
+			    itr->curr.hint, cmp_def) != 0) {
 		itr->curr = vy_mem_tree_elem_invalid();
 		return 1;
 	}
@@ -548,8 +546,8 @@ next:
 
 	struct vy_mem_tree_elem next;
 	next = *vy_mem_tree_iterator_get_elem(&itr->mem->tree, &next_pos);
-	if (vy_stmt_compare_hinted(itr->curr.stmt, itr->curr.hint,
-				   next.stmt, next.hint, cmp_def) != 0)
+	if (vy_stmt_compare(itr->curr.stmt, itr->curr.hint,
+			    next.stmt, next.hint, cmp_def) != 0)
 		return 1;
 
 	itr->curr_pos = next_pos;
@@ -603,8 +601,8 @@ vy_mem_iterator_skip(struct vy_mem_iterator *itr,
 	if (itr->search_started &&
 	    (itr->curr.stmt == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare_hinted(itr->curr.stmt, itr->curr.hint, last_stmt,
-				    last_hint, itr->mem->cmp_def) > 0))
+	     vy_stmt_compare(itr->curr.stmt, itr->curr.hint, last_stmt,
+			     last_hint, itr->mem->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
diff --git a/src/box/vy_mem.h b/src/box/vy_mem.h
index 360e1d9c..5c6b055d 100644
--- a/src/box/vy_mem.h
+++ b/src/box/vy_mem.h
@@ -121,8 +121,8 @@ static int
 vy_mem_tree_cmp(struct vy_mem_tree_elem a, struct vy_mem_tree_elem b,
 		struct key_def *cmp_def)
 {
-	int res = vy_stmt_compare_hinted(a.stmt, a.hint,
-					 b.stmt, b.hint, cmp_def);
+	int res = vy_stmt_compare(a.stmt, a.hint,
+				  b.stmt, b.hint, cmp_def);
 	if (res)
 		return res;
 	int64_t a_lsn = vy_stmt_lsn(a.stmt), b_lsn = vy_stmt_lsn(b.stmt);
@@ -136,8 +136,8 @@ static int
 vy_mem_tree_cmp_key(struct vy_mem_tree_elem elem, struct vy_mem_tree_key *key,
 		    struct key_def *cmp_def)
 {
-	int res = vy_stmt_compare_hinted(elem.stmt, elem.hint,
-					 key->stmt, key->hint, cmp_def);
+	int res = vy_stmt_compare(elem.stmt, elem.hint,
+				  key->stmt, key->hint, cmp_def);
 	if (res == 0) {
 		if (key->lsn == INT64_MAX - 1)
 			return 0;
diff --git a/src/box/vy_range.c b/src/box/vy_range.c
index edeb8a5f..f8e66655 100644
--- a/src/box/vy_range.c
+++ b/src/box/vy_range.c
@@ -63,9 +63,9 @@ vy_range_tree_cmp(struct vy_range *range_a, struct vy_range *range_b)
 		return 1;
 
 	assert(range_a->cmp_def == range_b->cmp_def);
-	return vy_stmt_compare_hinted(range_a->begin, range_a->begin_hint,
-				      range_b->begin, range_b->begin_hint,
-				      range_a->cmp_def);
+	return vy_stmt_compare(range_a->begin, range_a->begin_hint,
+			       range_b->begin, range_b->begin_hint,
+			       range_a->cmp_def);
 }
 
 int
@@ -74,8 +74,8 @@ vy_range_tree_key_cmp(struct vy_range_tree_key *key, struct vy_range *range)
 	/* Any key > -inf. */
 	if (range->begin == NULL)
 		return 1;
-	return vy_stmt_compare_hinted(key->stmt, key->hint, range->begin,
-				      range->begin_hint, range->cmp_def);
+	return vy_stmt_compare(key->stmt, key->hint, range->begin,
+			       range->begin_hint, range->cmp_def);
 }
 
 struct vy_range *
@@ -128,9 +128,8 @@ vy_range_tree_find_by_key(vy_range_tree_t *tree,
 		/* switch to previous for case (4) */
 		if (range != NULL && range->begin != NULL &&
 		    !vy_stmt_is_full_key(key, range->cmp_def) &&
-		    vy_stmt_compare_hinted(key, hint, range->begin,
-					   range->begin_hint,
-					   range->cmp_def) == 0)
+		    vy_stmt_compare(key, hint, range->begin, range->begin_hint,
+				    range->cmp_def) == 0)
 			range = vy_range_tree_prev(tree, range);
 		/* for case 5 or subcase of case 4 */
 		if (range == NULL)
@@ -164,9 +163,9 @@ vy_range_tree_find_by_key(vy_range_tree_t *tree,
 		if (range != NULL) {
 			/* fix curr_range for cases 2 and 3 */
 			if (range->begin != NULL &&
-			    vy_stmt_compare_hinted(key, hint, range->begin,
-						   range->begin_hint,
-						   range->cmp_def) != 0) {
+			    vy_stmt_compare(key, hint, range->begin,
+					    range->begin_hint,
+					    range->cmp_def) != 0) {
 				struct vy_range *prev;
 				prev = vy_range_tree_prev(tree, range);
 				if (prev != NULL)
@@ -488,7 +487,8 @@ vy_range_needs_split(struct vy_range *range, int64_t range_size,
 						slice->first_page_no);
 
 	/* No point in splitting if a new range is going to be empty. */
-	if (key_compare(first_page->min_key, mid_page->min_key,
+	if (key_compare(first_page->min_key, first_page->hint,
+			mid_page->min_key, mid_page->hint,
 			range->cmp_def) == 0)
 		return false;
 	/*
@@ -507,15 +507,19 @@ vy_range_needs_split(struct vy_range *range, int64_t range_size,
 	 *
 	 * In such cases there's no point in splitting the range.
 	 */
-	if (slice->begin != NULL && key_compare(mid_page->min_key,
-			tuple_data(slice->begin), range->cmp_def) <= 0)
+	if (slice->begin != NULL &&
+	    key_compare(mid_page->min_key, mid_page->hint,
+			tuple_data(slice->begin), slice->begin_hint,
+			range->cmp_def) <= 0)
 		return false;
 	/*
 	 * The median key can't be >= the end of the slice as we
 	 * take the min key of a page for the median key.
 	 */
-	assert(slice->end == NULL || key_compare(mid_page->min_key,
-			tuple_data(slice->end), range->cmp_def) < 0);
+	assert(slice->end == NULL ||
+	       key_compare(mid_page->min_key, mid_page->hint,
+			   tuple_data(slice->end), slice->end_hint,
+			   range->cmp_def) < 0);
 
 	*p_split_key = mid_page->min_key;
 	return true;
diff --git a/src/box/vy_read_iterator.c b/src/box/vy_read_iterator.c
index ba280f25..f56b08a4 100644
--- a/src/box/vy_read_iterator.c
+++ b/src/box/vy_read_iterator.c
@@ -149,20 +149,20 @@ vy_read_iterator_range_is_done(struct vy_read_iterator *itr,
 
 	if (dir > 0 && range->end != NULL &&
 	    (next_key == NULL ||
-	     vy_stmt_compare_hinted(next_key, next_hint, range->end,
-				    range->end_hint, cmp_def) >= 0) &&
+	     vy_stmt_compare(next_key, next_hint, range->end,
+			     range->end_hint, cmp_def) >= 0) &&
 	    (itr->iterator_type != ITER_EQ ||
-	     vy_stmt_compare_hinted(itr->key, itr->hint, range->end,
-				    range->end_hint, cmp_def) >= 0))
+	     vy_stmt_compare(itr->key, itr->hint, range->end,
+			     range->end_hint, cmp_def) >= 0))
 		return true;
 
 	if (dir < 0 && range->begin != NULL &&
 	    (next_key == NULL ||
-	     vy_stmt_compare_hinted(next_key, next_hint, range->begin,
-				    range->begin_hint, cmp_def) < 0) &&
+	     vy_stmt_compare(next_key, next_hint, range->begin,
+			     range->begin_hint, cmp_def) < 0) &&
 	    (itr->iterator_type != ITER_REQ ||
-	     vy_stmt_compare_hinted(itr->key, itr->hint, range->begin,
-				    range->begin_hint, cmp_def) <= 0))
+	     vy_stmt_compare(itr->key, itr->hint, range->begin,
+			     range->begin_hint, cmp_def) <= 0))
 		return true;
 
 	return false;
@@ -190,7 +190,7 @@ vy_read_iterator_cmp_stmt(struct vy_read_iterator *itr,
 	if (a == NULL && b == NULL)
 		return 0;
 	return iterator_direction(itr->iterator_type) *
-		vy_stmt_compare_hinted(a, a_hint, b, b_hint, itr->lsm->cmp_def);
+		vy_stmt_compare(a, a_hint, b, b_hint, itr->lsm->cmp_def);
 }
 
 /**
@@ -213,8 +213,7 @@ vy_read_iterator_is_exact_match(struct vy_read_iterator *itr,
 		(type == ITER_EQ || type == ITER_REQ ||
 		 type == ITER_GE || type == ITER_LE) &&
 		vy_stmt_is_full_key(itr->key, cmp_def) &&
-		vy_stmt_compare_hinted(stmt, hint, itr->key,
-				       itr->hint, cmp_def) == 0;
+		vy_stmt_compare(stmt, hint, itr->key, itr->hint, cmp_def) == 0;
 }
 
 /**
@@ -566,9 +565,9 @@ done:
 #ifndef NDEBUG
 	/* Check that the statement meets search criteria. */
 	if (next_key != NULL) {
-		int cmp = vy_stmt_compare_hinted(next_key, next_hint,
-						 itr->key, itr->hint,
-						 itr->lsm->cmp_def);
+		int cmp = vy_stmt_compare(next_key, next_hint,
+					  itr->key, itr->hint,
+					  itr->lsm->cmp_def);
 		cmp *= iterator_direction(itr->iterator_type);
 		if (itr->iterator_type == ITER_GT ||
 		    itr->iterator_type == ITER_LT)
@@ -586,8 +585,8 @@ done:
 	}
 #endif
 	if (itr->need_check_eq && next_key != NULL &&
-	    vy_stmt_compare_hinted(next_key, next_hint, itr->key, itr->hint,
-				   itr->lsm->cmp_def) != 0)
+	    vy_stmt_compare(next_key, next_hint, itr->key, itr->hint,
+			    itr->lsm->cmp_def) != 0)
 		itr->front_id++;
 	return 0;
 }
@@ -814,15 +813,15 @@ vy_read_iterator_next_range(struct vy_read_iterator *itr)
 		 */
 		if (dir > 0 &&
 		    (range->end == NULL ||
-		     vy_stmt_compare_hinted(itr->last_stmt, itr->last_hint,
-					    range->end, range->end_hint,
-					    cmp_def) < 0))
+		     vy_stmt_compare(itr->last_stmt, itr->last_hint,
+				     range->end, range->end_hint,
+				     cmp_def) < 0))
 			break;
 		if (dir < 0 &&
 		    (range->begin == NULL ||
-		     vy_stmt_compare_hinted(itr->last_stmt, itr->last_hint,
-					    range->begin, range->begin_hint,
-					    cmp_def) > 0))
+		     vy_stmt_compare(itr->last_stmt, itr->last_hint,
+				     range->begin, range->begin_hint,
+				     cmp_def) > 0))
 			break;
 	}
 	itr->curr_range = range;
diff --git a/src/box/vy_read_set.c b/src/box/vy_read_set.c
index 464ff060..11d33404 100644
--- a/src/box/vy_read_set.c
+++ b/src/box/vy_read_set.c
@@ -46,8 +46,8 @@ vy_read_interval_cmpl(const struct vy_read_interval *a,
 {
 	assert(a->lsm == b->lsm);
 	struct key_def *cmp_def = a->lsm->cmp_def;
-	int cmp = vy_stmt_compare_hinted(a->left, a->left_hint,
-					 b->left, b->left_hint, cmp_def);
+	int cmp = vy_stmt_compare(a->left, a->left_hint,
+				  b->left, b->left_hint, cmp_def);
 	if (cmp != 0)
 		return cmp;
 	if (a->left_belongs && !b->left_belongs)
@@ -68,8 +68,8 @@ vy_read_interval_cmpr(const struct vy_read_interval *a,
 {
 	assert(a->lsm == b->lsm);
 	struct key_def *cmp_def = a->lsm->cmp_def;
-	int cmp = vy_stmt_compare_hinted(a->right, a->right_hint,
-					 b->right, b->right_hint, cmp_def);
+	int cmp = vy_stmt_compare(a->right, a->right_hint,
+				  b->right, b->right_hint, cmp_def);
 	if (cmp != 0)
 		return cmp;
 	if (a->right_belongs && !b->right_belongs)
@@ -91,8 +91,8 @@ vy_read_interval_should_merge(const struct vy_read_interval *l,
 	assert(l->lsm == r->lsm);
 	assert(vy_read_interval_cmpl(l, r) <= 0);
 	struct key_def *cmp_def = l->lsm->cmp_def;
-	int cmp = vy_stmt_compare_hinted(l->right, l->right_hint,
-					 r->left, r->left_hint, cmp_def);
+	int cmp = vy_stmt_compare(l->right, l->right_hint,
+				  r->left, r->left_hint, cmp_def);
 	if (cmp > 0)
 		return true;
 	if (cmp < 0)
@@ -121,7 +121,7 @@ vy_tx_conflict_iterator_next(struct vy_tx_conflict_iterator *it)
 		assert(left == NULL || left->lsm == curr->lsm);
 		assert(right == NULL || right->lsm == curr->lsm);
 
-		int cmp_right = vy_stmt_compare_hinted(it->stmt, it->hint,
+		int cmp_right = vy_stmt_compare(it->stmt, it->hint,
 					last->right, last->right_hint, cmp_def);
 		if (cmp_right == 0 && !last->right_belongs)
 			cmp_right = 1;
@@ -141,7 +141,7 @@ vy_tx_conflict_iterator_next(struct vy_tx_conflict_iterator *it)
 			/* Optimize comparison out. */
 			cmp_left = cmp_right;
 		} else {
-			cmp_left = vy_stmt_compare_hinted(it->stmt, it->hint,
+			cmp_left = vy_stmt_compare(it->stmt, it->hint,
 					curr->left, curr->left_hint, cmp_def);
 			if (cmp_left == 0 && !curr->left_belongs)
 				cmp_left = -1;
@@ -169,7 +169,7 @@ vy_tx_conflict_iterator_next(struct vy_tx_conflict_iterator *it)
 			/* Optimize comparison out. */
 			cmp_right = cmp_left;
 		} else if (curr != last) {
-			cmp_right = vy_stmt_compare_hinted(it->stmt, it->hint,
+			cmp_right = vy_stmt_compare(it->stmt, it->hint,
 					curr->right, curr->right_hint, cmp_def);
 			if (cmp_right == 0 && !curr->right_belongs)
 				cmp_right = 1;
diff --git a/src/box/vy_run.c b/src/box/vy_run.c
index 2aac2b42..7c063f0a 100644
--- a/src/box/vy_run.c
+++ b/src/box/vy_run.c
@@ -343,8 +343,8 @@ vy_page_index_find_page(struct vy_run *run, enum iterator_type itype,
 	do {
 		int32_t mid = range[0] + (range[1] - range[0]) / 2;
 		struct vy_page_info *info = vy_run_page_info(run, mid);
-		int cmp = vy_stmt_compare_with_raw_key_hinted(key, hint,
-					info->min_key, info->hint, cmp_def);
+		int cmp = vy_stmt_compare_with_raw_key(key, hint, info->min_key,
+						       info->hint, cmp_def);
 		if (is_lower_bound)
 			range[cmp <= 0] = mid;
 		else
@@ -459,24 +459,24 @@ vy_slice_cut(struct vy_slice *slice, int64_t id, struct tuple *begin,
 	*result = NULL;
 
 	if (begin != NULL && slice->end != NULL &&
-	    vy_stmt_compare_hinted(begin, begin_hint, slice->end,
-				   slice->end_hint, cmp_def) >= 0)
+	    vy_stmt_compare(begin, begin_hint, slice->end,
+			    slice->end_hint, cmp_def) >= 0)
 		return 0; /* no intersection: begin >= slice->end */
 
 	if (end != NULL && slice->begin != NULL &&
-	    vy_stmt_compare_hinted(end, end_hint, slice->begin,
-				   slice->begin_hint, cmp_def) <= 0)
+	    vy_stmt_compare(end, end_hint, slice->begin,
+			    slice->begin_hint, cmp_def) <= 0)
 		return 0; /* no intersection: end <= slice->end */
 
 	/* begin = MAX(begin, slice->begin) */
 	if (slice->begin != NULL &&
-	    (begin == NULL || vy_stmt_compare_hinted(begin, begin_hint,
+	    (begin == NULL || vy_stmt_compare(begin, begin_hint,
 			slice->begin, slice->begin_hint, cmp_def) < 0))
 		begin = slice->begin;
 
 	/* end = MIN(end, slice->end) */
 	if (slice->end != NULL &&
-	    (end == NULL || vy_stmt_compare_hinted(end, end_hint,
+	    (end == NULL || vy_stmt_compare(end, end_hint,
 			slice->end, slice->end_hint, cmp_def) > 0))
 		end = slice->end;
 
@@ -1078,8 +1078,8 @@ vy_run_iterator_search_in_page(struct vy_run_iterator *itr,
 						     itr->format, &fnd_hint);
 		if (fnd_key == NULL)
 			return end;
-		int cmp = vy_stmt_compare_hinted(fnd_key, fnd_hint,
-						 key, hint, itr->cmp_def);
+		int cmp = vy_stmt_compare(fnd_key, fnd_hint, key, hint,
+					  itr->cmp_def);
 		cmp = cmp ? cmp : zero_cmp;
 		*equal_key = *equal_key || cmp == 0;
 		if (cmp < 0)
@@ -1210,8 +1210,8 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr,
 					 &itr->curr_hint) != 0)
 			return -1;
 		if (itr->iterator_type == ITER_EQ &&
-		    vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
-					   itr->key, itr->hint, cmp_def) != 0) {
+		    vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
+				    itr->key, itr->hint, cmp_def) != 0) {
 			vy_run_iterator_stop(itr);
 			return 0;
 		}
@@ -1227,10 +1227,8 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr,
 				return -1;
 			if (vy_stmt_lsn(test_stmt) > (**itr->read_view).vlsn ||
 			    vy_stmt_flags(test_stmt) & VY_STMT_SKIP_READ ||
-			    vy_stmt_compare_hinted(itr->curr_stmt,
-						   itr->curr_hint,
-						   test_stmt, test_hint,
-						   cmp_def) != 0) {
+			    vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
+					    test_stmt, test_hint, cmp_def) != 0) {
 				tuple_unref(test_stmt);
 				break;
 			}
@@ -1243,9 +1241,9 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr,
 	/* Check if the result is within the slice boundaries. */
 	if (itr->iterator_type == ITER_LE || itr->iterator_type == ITER_LT) {
 		if (slice->begin != NULL &&
-		    vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
-					   slice->begin, slice->begin_hint,
-					   cmp_def) < 0) {
+		    vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
+				    slice->begin, slice->begin_hint,
+				    cmp_def) < 0) {
 			vy_run_iterator_stop(itr);
 			return 0;
 		}
@@ -1254,9 +1252,9 @@ vy_run_iterator_find_lsn(struct vy_run_iterator *itr,
 		       itr->iterator_type == ITER_GT ||
 		       itr->iterator_type == ITER_EQ);
 		if (slice->end != NULL &&
-		    vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
-					   slice->end, slice->end_hint,
-					   cmp_def) >= 0) {
+		    vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
+				    slice->end, slice->end_hint,
+				    cmp_def) >= 0) {
 			vy_run_iterator_stop(itr);
 			return 0;
 		}
@@ -1396,8 +1394,8 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 		 *         | ge  | begin | ge  |
 		 *         | eq  |    stop     |
 		 */
-		int cmp = vy_stmt_compare_hinted(key, hint, slice->begin,
-						 slice->begin_hint, cmp_def);
+		int cmp = vy_stmt_compare(key, hint, slice->begin,
+					  slice->begin_hint, cmp_def);
 		if (cmp < 0 && iterator_type == ITER_EQ) {
 			vy_run_iterator_stop(itr);
 			return 0;
@@ -1423,8 +1421,8 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 		 * > end   | lt  | end   | lt  |
 		 *         | le  | end   | lt  |
 		 */
-		int cmp = vy_stmt_compare_hinted(key, hint, slice->end,
-						 slice->end_hint, cmp_def);
+		int cmp = vy_stmt_compare(key, hint, slice->end,
+					  slice->end_hint, cmp_def);
 		if (cmp > 0 || (cmp == 0 && iterator_type != ITER_LT)) {
 			iterator_type = ITER_LT;
 			key = slice->end;
@@ -1451,7 +1449,7 @@ vy_run_iterator_seek(struct vy_run_iterator *itr, const struct tuple *last_key,
 		return -1;
 
 	/* Check EQ constraint if necessary. */
-	if (check_eq && vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
+	if (check_eq && vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
 					itr->key, itr->hint, itr->cmp_def) != 0)
 		goto not_found;
 
@@ -1543,16 +1541,16 @@ vy_run_iterator_next_key(struct vy_run_iterator *itr, struct tuple **ret,
 		if (vy_run_iterator_read(itr, itr->curr_pos,
 					 &next_key, &next_hint) != 0)
 			return -1;
-	} while (vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
-					next_key, next_hint, itr->cmp_def) == 0);
+	} while (vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
+				 next_key, next_hint, itr->cmp_def) == 0);
 
 	tuple_unref(itr->curr_stmt);
 	itr->curr_stmt = next_key;
 	itr->curr_hint = next_hint;
 
 	if (itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare_hinted(next_key, next_hint, itr->key, itr->hint,
-				   itr->cmp_def) != 0) {
+	    vy_stmt_compare(next_key, next_hint, itr->key, itr->hint,
+			    itr->cmp_def) != 0) {
 		vy_run_iterator_stop(itr);
 		return 0;
 	}
@@ -1587,8 +1585,8 @@ next:
 	if (vy_run_iterator_read(itr, next_pos, &next_key, &next_hint) != 0)
 		return -1;
 
-	if (vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
-				   next_key, next_hint, itr->cmp_def) != 0) {
+	if (vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
+			    next_key, next_hint, itr->cmp_def) != 0) {
 		tuple_unref(next_key);
 		return 0;
 	}
@@ -1638,8 +1636,8 @@ vy_run_iterator_skip(struct vy_run_iterator *itr,
 	if (itr->search_started &&
 	    (itr->curr_stmt == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare_hinted(itr->curr_stmt, itr->curr_hint,
-				    last_stmt, last_hint, itr->cmp_def) > 0))
+	     vy_stmt_compare(itr->curr_stmt, itr->curr_hint,
+			     last_stmt, last_hint, itr->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
@@ -2646,10 +2644,10 @@ vy_slice_stream_search(struct vy_stmt_stream *virt_stream)
 				stream->cmp_def, stream->format, &fnd_hint);
 		if (fnd_key == NULL)
 			return -1;
-		int cmp = vy_stmt_compare_hinted(fnd_key, fnd_hint,
-						 stream->slice->begin,
-						 stream->slice->begin_hint,
-						 stream->cmp_def);
+		int cmp = vy_stmt_compare(fnd_key, fnd_hint,
+					  stream->slice->begin,
+					  stream->slice->begin_hint,
+					  stream->cmp_def);
 		if (cmp < 0)
 			beg = mid + 1;
 		else
@@ -2703,8 +2701,8 @@ vy_slice_stream_next(struct vy_stmt_stream *virt_stream,
 	/* Check that the tuple is not out of slice bounds = */
 	if (stream->slice->end != NULL &&
 	    stream->page_no >= stream->slice->last_page_no &&
-	    vy_stmt_compare_hinted(tuple, hint, stream->slice->end,
-			stream->slice->end_hint, stream->cmp_def) >= 0) {
+	    vy_stmt_compare(tuple, hint, stream->slice->end,
+			    stream->slice->end_hint, stream->cmp_def) >= 0) {
 		tuple_unref(tuple);
 		return 0;
 	}
diff --git a/src/box/vy_stmt.h b/src/box/vy_stmt.h
index 8467c2d7..ef1b25a2 100644
--- a/src/box/vy_stmt.h
+++ b/src/box/vy_stmt.h
@@ -379,57 +379,31 @@ vy_stmt_hint(const struct tuple *stmt, struct key_def *key_def)
 
 /**
  * Compare two vinyl statements taking into account their
- * formats (key or tuple).
- */
-static inline int
-vy_stmt_compare(const struct tuple *a, const struct tuple *b,
-		struct key_def *key_def)
-{
-	bool a_is_tuple = !vy_stmt_is_key(a);
-	bool b_is_tuple = !vy_stmt_is_key(b);
-	if (a_is_tuple && b_is_tuple) {
-		return tuple_compare(a, b, key_def);
-	} else if (a_is_tuple && !b_is_tuple) {
-		const char *key = tuple_data(b);
-		uint32_t part_count = mp_decode_array(&key);
-		return tuple_compare_with_key(a, key, part_count, key_def);
-	} else if (!a_is_tuple && b_is_tuple) {
-		const char *key = tuple_data(a);
-		uint32_t part_count = mp_decode_array(&key);
-		return -tuple_compare_with_key(b, key, part_count, key_def);
-	} else {
-		assert(!a_is_tuple && !b_is_tuple);
-		return key_compare(tuple_data(a), tuple_data(b), key_def);
-	}
-}
-
-/**
- * Compare two vinyl statements taking into account their
  * formats (key or tuple) and using comparison hints.
  */
 static inline int
-vy_stmt_compare_hinted(const struct tuple *a, hint_t a_hint,
-		       const struct tuple *b, hint_t b_hint,
-		       struct key_def *key_def)
+vy_stmt_compare(const struct tuple *a, hint_t a_hint,
+		const struct tuple *b, hint_t b_hint,
+		struct key_def *key_def)
 {
 	bool a_is_tuple = !vy_stmt_is_key(a);
 	bool b_is_tuple = !vy_stmt_is_key(b);
 	if (a_is_tuple && b_is_tuple) {
-		return tuple_compare_hinted(a, a_hint, b, b_hint, key_def);
+		return tuple_compare(a, a_hint, b, b_hint, key_def);
 	} else if (a_is_tuple && !b_is_tuple) {
 		const char *key = tuple_data(b);
 		uint32_t part_count = mp_decode_array(&key);
-		return tuple_compare_with_key_hinted(a, a_hint, key, part_count,
+		return tuple_compare_with_key(a, a_hint, key, part_count,
 						     b_hint, key_def);
 	} else if (!a_is_tuple && b_is_tuple) {
 		const char *key = tuple_data(a);
 		uint32_t part_count = mp_decode_array(&key);
-		return -tuple_compare_with_key_hinted(b, b_hint, key, part_count,
+		return -tuple_compare_with_key(b, b_hint, key, part_count,
 						      a_hint, key_def);
 	} else {
 		assert(!a_is_tuple && !b_is_tuple);
-		return key_compare_hinted(tuple_data(a), a_hint,
-					  tuple_data(b), b_hint, key_def);
+		return key_compare(tuple_data(a), a_hint,
+				   tuple_data(b), b_hint, key_def);
 	}
 }
 
@@ -438,17 +412,17 @@ vy_stmt_compare_hinted(const struct tuple *a, hint_t a_hint,
  * (msgpack array).
  */
 static inline int
-vy_stmt_compare_with_raw_key_hinted(const struct tuple *stmt, hint_t stmt_hint,
-				    const char *key, hint_t key_hint,
-				    struct key_def *key_def)
+vy_stmt_compare_with_raw_key(const struct tuple *stmt, hint_t stmt_hint,
+			     const char *key, hint_t key_hint,
+			     struct key_def *key_def)
 {
 	if (!vy_stmt_is_key(stmt)) {
 		uint32_t part_count = mp_decode_array(&key);
-		return tuple_compare_with_key_hinted(stmt, stmt_hint, key,
-						part_count, key_hint, key_def);
+		return tuple_compare_with_key(stmt, stmt_hint, key,
+					      part_count, key_hint, key_def);
 	}
-	return key_compare_hinted(tuple_data(stmt), stmt_hint,
-				  key, key_hint, key_def);
+	return key_compare(tuple_data(stmt), stmt_hint,
+			   key, key_hint, key_def);
 }
 
 /**
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index 6a3f410e..bd2c141f 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -68,9 +68,8 @@ write_set_cmp(struct txv *a, struct txv *b)
 {
 	int rc = a->lsm < b->lsm ? -1 : a->lsm > b->lsm;
 	if (rc == 0)
-		return vy_stmt_compare_hinted(a->stmt, a->hint,
-					      b->stmt, b->hint,
-					      a->lsm->cmp_def);
+		return vy_stmt_compare(a->stmt, a->hint, b->stmt, b->hint,
+				       a->lsm->cmp_def);
 	return rc;
 }
 
@@ -79,9 +78,8 @@ write_set_key_cmp(struct write_set_key *a, struct txv *b)
 {
 	int rc = a->lsm < b->lsm ? -1 : a->lsm > b->lsm;
 	if (rc == 0)
-		return vy_stmt_compare_hinted(a->stmt, a->hint,
-					      b->stmt, b->hint,
-					      a->lsm->cmp_def);
+		return vy_stmt_compare(a->stmt, a->hint, b->stmt, b->hint,
+				       a->lsm->cmp_def);
 	return rc;
 }
 
@@ -1207,8 +1205,8 @@ vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key,
 			txv = write_set_psearch(&itr->tx->write_set, &k);
 		if (txv == NULL || txv->lsm != lsm)
 			return;
-		if (vy_stmt_compare_hinted(k.stmt, k.hint, txv->stmt, txv->hint,
-					   lsm->cmp_def) == 0) {
+		if (vy_stmt_compare(k.stmt, k.hint, txv->stmt, txv->hint,
+				    lsm->cmp_def) == 0) {
 			while (true) {
 				struct txv *next;
 				if (iterator_type == ITER_LE ||
@@ -1218,9 +1216,9 @@ vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key,
 					next = write_set_prev(&itr->tx->write_set, txv);
 				if (next == NULL || next->lsm != lsm)
 					break;
-				if (vy_stmt_compare_hinted(k.stmt, k.hint,
-							   next->stmt, next->hint,
-							   lsm->cmp_def) != 0)
+				if (vy_stmt_compare(k.stmt, k.hint,
+						    next->stmt, next->hint,
+						    lsm->cmp_def) != 0)
 					break;
 				txv = next;
 			}
@@ -1238,8 +1236,8 @@ vy_txw_iterator_seek(struct vy_txw_iterator *itr, const struct tuple *last_key,
 	if (txv == NULL || txv->lsm != lsm)
 		return;
 	if (itr->iterator_type == ITER_EQ && last_key != NULL &&
-	    vy_stmt_compare_hinted(itr->key, itr->hint, txv->stmt, txv->hint,
-				   lsm->cmp_def) != 0)
+	    vy_stmt_compare(itr->key, itr->hint, txv->stmt, txv->hint,
+			    lsm->cmp_def) != 0)
 		return;
 	itr->curr_txv = txv;
 }
@@ -1264,8 +1262,8 @@ vy_txw_iterator_next(struct vy_txw_iterator *itr,
 	if (itr->curr_txv != NULL && itr->curr_txv->lsm != itr->lsm)
 		itr->curr_txv = NULL;
 	if (itr->curr_txv != NULL && itr->iterator_type == ITER_EQ &&
-	    vy_stmt_compare_hinted(itr->key, itr->hint, itr->curr_txv->stmt,
-				   itr->curr_txv->hint, itr->lsm->cmp_def) != 0)
+	    vy_stmt_compare(itr->key, itr->hint, itr->curr_txv->stmt,
+			    itr->curr_txv->hint, itr->lsm->cmp_def) != 0)
 		itr->curr_txv = NULL;
 out:
 	if (itr->curr_txv != NULL) {
@@ -1292,10 +1290,8 @@ vy_txw_iterator_skip(struct vy_txw_iterator *itr,
 	if (itr->search_started &&
 	    (itr->curr_txv == NULL || last_stmt == NULL ||
 	     iterator_direction(itr->iterator_type) *
-	     vy_stmt_compare_hinted(itr->curr_txv->stmt,
-				    itr->curr_txv->hint,
-				    last_stmt, last_hint,
-				    itr->lsm->cmp_def) > 0))
+	     vy_stmt_compare(itr->curr_txv->stmt, itr->curr_txv->hint,
+			     last_stmt, last_hint, itr->lsm->cmp_def) > 0))
 		return 0;
 
 	vy_history_cleanup(history);
diff --git a/src/box/vy_upsert.c b/src/box/vy_upsert.c
index 8cf0cc19..40eef8da 100644
--- a/src/box/vy_upsert.c
+++ b/src/box/vy_upsert.c
@@ -206,7 +206,8 @@ check_key:
 	 * Check that key hasn't been changed after applying operations.
 	 */
 	if (!key_update_can_be_skipped(cmp_def->column_mask, column_mask) &&
-	    vy_stmt_compare(old_stmt, result_stmt, cmp_def) != 0) {
+	    vy_stmt_compare(old_stmt, HINT_NONE, result_stmt, HINT_NONE,
+			    cmp_def) != 0) {
 		/*
 		 * Key has been changed: ignore this UPSERT and
 		 * @retval the old stmt.
diff --git a/src/box/vy_write_iterator.c b/src/box/vy_write_iterator.c
index a2a782fb..e02e93ac 100644
--- a/src/box/vy_write_iterator.c
+++ b/src/box/vy_write_iterator.c
@@ -235,9 +235,8 @@ heap_less(heap_t *heap, struct vy_write_src *src1, struct vy_write_src *src2)
 	struct vy_write_iterator *stream =
 		container_of(heap, struct vy_write_iterator, src_heap);
 
-	int cmp = vy_stmt_compare_hinted(src1->tuple, src1->hint,
-					 src2->tuple, src2->hint,
-					 stream->cmp_def);
+	int cmp = vy_stmt_compare(src1->tuple, src1->hint,
+				  src2->tuple, src2->hint, stream->cmp_def);
 	if (cmp != 0)
 		return cmp < 0;
 
-- 
2.11.0

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

* Re: [PATCH 01/13] vinyl: store tuple comparison hints in memory tree
  2019-04-02 17:33 ` [PATCH 01/13] vinyl: store tuple comparison hints in memory tree Vladimir Davydov
@ 2019-04-04  8:53   ` Konstantin Osipov
  2019-04-04  9:09     ` Vladimir Davydov
  0 siblings, 1 reply; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04  8:53 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> This patch incorporates tuple comparison hints into vy_mem_tree,
> similarly to how it was done in case of memtx_tree.
> 
> Apart from speeding up lookups, this is also needed for multikey index
> support, because multikey indexes will reuse hints to store offsets of
> indexed array entries.

The patch is OK to push. See one comment below.

>  	while (!vy_mem_tree_iterator_is_invalid(&itr)) {
> +	struct vy_mem_tree_elem replaced = vy_mem_tree_elem_invalid();

I would use eof or null rather than invalid, e.g.
vy_mem_tree_iterator_is_eof()
vy_mem_tree_eof

or:

vy_mem_tree_iterator_is_eof()
vy_mem_tree_null

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 01/13] vinyl: store tuple comparison hints in memory tree
  2019-04-04  8:53   ` Konstantin Osipov
@ 2019-04-04  9:09     ` Vladimir Davydov
  2019-04-04  9:48       ` Konstantin Osipov
  0 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-04  9:09 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Thu, Apr 04, 2019 at 11:53:27AM +0300, Konstantin Osipov wrote:
> * Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> > This patch incorporates tuple comparison hints into vy_mem_tree,
> > similarly to how it was done in case of memtx_tree.
> > 
> > Apart from speeding up lookups, this is also needed for multikey index
> > support, because multikey indexes will reuse hints to store offsets of
> > indexed array entries.
> 
> The patch is OK to push. See one comment below.
> 
> >  	while (!vy_mem_tree_iterator_is_invalid(&itr)) {
> > +	struct vy_mem_tree_elem replaced = vy_mem_tree_elem_invalid();
> 
> I would use eof or null rather than invalid, e.g.
> vy_mem_tree_iterator_is_eof()
> vy_mem_tree_eof
> 
> or:
> 
> vy_mem_tree_iterator_is_eof()
> vy_mem_tree_null

I called it 'invalid' to be consistent with the BPS tree API, see
bps_tree_invalid_iterator.

Also, it's used not only for EOF - we set the iterator position to
this value before iteration is started.

That said, I'd rather leave the name as is.

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

* Re: [PATCH 01/13] vinyl: store tuple comparison hints in memory tree
  2019-04-04  9:09     ` Vladimir Davydov
@ 2019-04-04  9:48       ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04  9:48 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/04 12:12]:
> > vy_mem_tree_iterator_is_eof()
> > vy_mem_tree_eof
> > 
> > or:
> > 
> > vy_mem_tree_iterator_is_eof()
> > vy_mem_tree_null
> 
> I called it 'invalid' to be consistent with the BPS tree API, see
> bps_tree_invalid_iterator.
> 
> Also, it's used not only for EOF - we set the iterator position to
> this value before iteration is started.
> 
> That said, I'd rather leave the name as is.

I'd rename it across the board.

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 02/13] vinyl: store tuple comparison hints in cache tree
  2019-04-02 17:33 ` [PATCH 02/13] vinyl: store tuple comparison hints in cache tree Vladimir Davydov
@ 2019-04-04 11:39   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 11:39 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> This patch incorporates tuple comparison hints into vy_cache_tree,
> similarly to how it was done in case of memtx_tree.
> 
> Apart from speeding up lookups, this is also needed for multikey index
> support, because multikey indexes will reuse hints to store offsets of
> indexed array entries.

OK to push.

 

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 03/13] vinyl: store tuple comparison hints in tx write set
  2019-04-02 17:33 ` [PATCH 03/13] vinyl: store tuple comparison hints in tx write set Vladimir Davydov
@ 2019-04-04 11:41   ` Konstantin Osipov
  2019-04-04 12:21     ` Vladimir Davydov
  0 siblings, 1 reply; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 11:41 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> This patch incorporates tuple comparison hints into the transaction
> write set. Now, beside a statement, each txv also stores its hint,
> which is used in all comparison operations.

Actually, the more I look at this patch set the more an object
representing a pair <tuple, hint> (vy_key?) is called for.
Please consider adding it, it could be passed around by value.

 

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 04/13] vinyl: store tuple comparison hints in tx read set
  2019-04-02 17:33 ` [PATCH 04/13] vinyl: store tuple comparison hints in tx read set Vladimir Davydov
@ 2019-04-04 11:42   ` Konstantin Osipov
  2019-04-04 12:08     ` Vladimir Davydov
  0 siblings, 1 reply; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 11:42 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> This patch incorporates tuple comparison hints into the transaction
> read set. Now, beside a statement, each read interval also stores hints
> of boundary statements, which are used in all comparison operations.
> 
> Apart from speeding up lookups, this is also needed for multikey index
> support, because multikey indexes will reuse hints to store offsets of
> indexed array entries.

OK to push.

> --- a/src/box/vy_read_set.h
> +++ b/src/box/vy_read_set.h
> @@ -39,6 +39,7 @@
>  #define RB_COMPACT 1
>  #include <small/rb.h>
>  
> +#include "key_def.h"

Isn't it better to define hint_t in tuple_compare.h to avoid extra
includes?

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 07/13] vinyl: propagate tuple comparison hints to read iterator
  2019-04-02 17:33 ` [PATCH 07/13] vinyl: propagate tuple comparison hints to read iterator Vladimir Davydov
@ 2019-04-04 11:43   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 11:43 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> This patch propagates hints from source iterators (mem, run, txw, cache)
> to the read iterator code. Now, source iterators take and return
> comparison hints along with statements. The read iterator uses the hints
> to speed up tuple comparisons.
> 
> Apart from speeding up lookups, this is also needed for multikey index
> support, because multikey indexes will reuse hints to store offsets of
> indexed array entries.

This is a yet another technical patch, I have no comments about,
OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 08/13] vinyl: propagate tuple comparison hints to write iterator
  2019-04-02 17:33 ` [PATCH 08/13] vinyl: propagate tuple comparison hints to write iterator Vladimir Davydov
@ 2019-04-04 11:47   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 11:47 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> This patch propagates hints from source streams (mem, run) to the write
> iterator, which now uses the hints to efficiently compare tuples and
> returns a hint along with a resulting statement.

Same as before, OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 04/13] vinyl: store tuple comparison hints in tx read set
  2019-04-04 11:42   ` Konstantin Osipov
@ 2019-04-04 12:08     ` Vladimir Davydov
  0 siblings, 0 replies; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-04 12:08 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Thu, Apr 04, 2019 at 02:42:32PM +0300, Konstantin Osipov wrote:
> * Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> > This patch incorporates tuple comparison hints into the transaction
> > read set. Now, beside a statement, each read interval also stores hints
> > of boundary statements, which are used in all comparison operations.
> > 
> > Apart from speeding up lookups, this is also needed for multikey index
> > support, because multikey indexes will reuse hints to store offsets of
> > indexed array entries.
> 
> OK to push.
> 
> > --- a/src/box/vy_read_set.h
> > +++ b/src/box/vy_read_set.h
> > @@ -39,6 +39,7 @@
> >  #define RB_COMPACT 1
> >  #include <small/rb.h>
> >  
> > +#include "key_def.h"
> 
> Isn't it better to define hint_t in tuple_compare.h to avoid extra
> includes?

Makes sense. I'll move it in v2.

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

* Re: [PATCH 09/13] vinyl: forward tuple comparison hints to memory tree
  2019-04-02 17:33 ` [PATCH 09/13] vinyl: forward tuple comparison hints to memory tree Vladimir Davydov
@ 2019-04-04 12:10   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 12:10 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> Instead of computing a statement comparison hint in a vy_mem method,
> forward it from the upper level, which already has it computed.
> 
> Apart from eliminating extra calls to vy_stmt_hint, this is also a
> prerequisite for multikey indexes, which will reuse hints to store
> offsets of indexed array entries and thus make hints impossible to be
> computed in an arbitrary place in code.

OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 10/13] vinyl: forward tuple comparison hints to cache tree
  2019-04-02 17:33 ` [PATCH 10/13] vinyl: forward tuple comparison hints to cache tree Vladimir Davydov
@ 2019-04-04 12:11   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 12:11 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> Instead of computing a statement comparison hint in a vy_cache method,
> forward it from the read iterator, which already has it computed.
> 
> Apart from eliminating extra calls to vy_stmt_hint, this is also a
> prerequisite for multikey indexes, which will reuse hints to store
> offsets of indexed array entries and thus make hints impossible to be
> computed in an arbitrary place in code.

OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 11/13] vinyl: forward tuple comparison hints to read iterator
  2019-04-02 17:33 ` [PATCH 11/13] vinyl: forward tuple comparison hints to read iterator Vladimir Davydov
@ 2019-04-04 12:12   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 12:12 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> Pass a comparison hint along with a search key in the arguments instead
> of computing it in the read iterator. This will allow to avoid computing
> a hint for the second time in case the calling code already has it.
> 
> Besides, it is needed for multikey indexes, which will reuse hints to
> store offsets of indexed array entries and thus make hints impossible to
> be computed in an arbitrary place in code.

OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 12/13] vinyl: forward tuple comparison hints to tx read set
  2019-04-02 17:33 ` [PATCH 12/13] vinyl: forward tuple comparison hints to tx read set Vladimir Davydov
@ 2019-04-04 12:12   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 12:12 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> Instead of computing a statement comparison hint in vy_tx_track,
> forward it from the upper level, which already has it computed.
> 
> Apart from eliminating extra calls to vy_stmt_hint, this is also a
> prerequisite for multikey indexes, which will reuse hints to store
> offsets of indexed array entries and thus make hints impossible to be
> computed in an arbitrary place in code.

OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 13/13] Make tuple comparison hints mandatory
  2019-04-02 17:33 ` [PATCH 13/13] Make tuple comparison hints mandatory Vladimir Davydov
@ 2019-04-04 12:21   ` Konstantin Osipov
  0 siblings, 0 replies; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 12:21 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> There isn't much point in having separate versions of tuple comparators
> that don't take tuple comparison hints anymore, because all hot paths
> have been patched to use the hinted versions. Let's strip the _hinted
> suffix from tuple_compare_hinted and others and zap un-hinted versions.
> In a few remaining places in the code that still use un-hinted versions,
> let's simply pass HINT_NONE.

OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 03/13] vinyl: store tuple comparison hints in tx write set
  2019-04-04 11:41   ` Konstantin Osipov
@ 2019-04-04 12:21     ` Vladimir Davydov
  2019-04-04 12:40       ` Konstantin Osipov
  0 siblings, 1 reply; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-04 12:21 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Thu, Apr 04, 2019 at 02:41:17PM +0300, Konstantin Osipov wrote:
> * Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> > This patch incorporates tuple comparison hints into the transaction
> > write set. Now, beside a statement, each txv also stores its hint,
> > which is used in all comparison operations.
> 
> Actually, the more I look at this patch set the more an object
> representing a pair <tuple, hint> (vy_key?) is called for.
> Please consider adding it, it could be passed around by value.

This could be reasonable, but I don't quite like the name vy_key,
because in vinyl we have a different concept of keys.

A few names that pop into mind:

	vy_kv.{stmt,val}
	vy_hinted_stmt.{tuple,val}
	vy_indexed_stmt.{tuple,val}

Any other suggestions?

Will have to rework this patch set, I guess :(

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

* Re: [PATCH 03/13] vinyl: store tuple comparison hints in tx write set
  2019-04-04 12:21     ` Vladimir Davydov
@ 2019-04-04 12:40       ` Konstantin Osipov
  2019-04-04 17:28         ` Vladimir Davydov
  0 siblings, 1 reply; 31+ messages in thread
From: Konstantin Osipov @ 2019-04-04 12:40 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/04 15:24]:
> On Thu, Apr 04, 2019 at 02:41:17PM +0300, Konstantin Osipov wrote:
> > * Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> > > This patch incorporates tuple comparison hints into the transaction
> > > write set. Now, beside a statement, each txv also stores its hint,
> > > which is used in all comparison operations.
> > 
> > Actually, the more I look at this patch set the more an object
> > representing a pair <tuple, hint> (vy_key?) is called for.
> > Please consider adding it, it could be passed around by value.
> 
> This could be reasonable, but I don't quite like the name vy_key,
> because in vinyl we have a different concept of keys.
> 
> A few names that pop into mind:
> 
> 	vy_kv.{stmt,val}

vy_mem_key?

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* Re: [PATCH 03/13] vinyl: store tuple comparison hints in tx write set
  2019-04-04 12:40       ` Konstantin Osipov
@ 2019-04-04 17:28         ` Vladimir Davydov
  0 siblings, 0 replies; 31+ messages in thread
From: Vladimir Davydov @ 2019-04-04 17:28 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Thu, Apr 04, 2019 at 03:40:01PM +0300, Konstantin Osipov wrote:
> * Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/04 15:24]:
> > On Thu, Apr 04, 2019 at 02:41:17PM +0300, Konstantin Osipov wrote:
> > > * Vladimir Davydov <vdavydov.dev@gmail.com> [19/04/02 20:35]:
> > > > This patch incorporates tuple comparison hints into the transaction
> > > > write set. Now, beside a statement, each txv also stores its hint,
> > > > which is used in all comparison operations.
> > > 
> > > Actually, the more I look at this patch set the more an object
> > > representing a pair <tuple, hint> (vy_key?) is called for.
> > > Please consider adding it, it could be passed around by value.
> > 
> > This could be reasonable, but I don't quite like the name vy_key,
> > because in vinyl we have a different concept of keys.
> > 
> > A few names that pop into mind:
> > 
> > 	vy_kv.{stmt,val}
> 
> vy_mem_key?

Nah, it's not exactly a key. It may be a tuple, actually.
Would still be confusing since we have vy_stmt_is_key() helper,
which checks whether a statement is in key format.

Besides, we already have vy_mem_tree_key, which is used for lookups in
vy_mem_tree. Having both vy_mem_key and vy_mem_tree_key would definitely
be confusing.

What about vy_item or vy_entry? That would emphasize that this thing
comes from some kind of a container (e.g. vy_mem_tree). We already have
vy_cache_entry so I'd rather call it vy_item - it's short and unique.

Other variants:

	vy_record
	vy_element (or even vy_elem)
	vy_entity
	vy_unit
	vy_thing :)
	vy_object
	vy_article

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

end of thread, other threads:[~2019-04-04 17:28 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-02 17:33 [PATCH 00/13] Incorporate tuple comparison hints into Vinyl Vladimir Davydov
2019-04-02 17:33 ` [PATCH 01/13] vinyl: store tuple comparison hints in memory tree Vladimir Davydov
2019-04-04  8:53   ` Konstantin Osipov
2019-04-04  9:09     ` Vladimir Davydov
2019-04-04  9:48       ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 02/13] vinyl: store tuple comparison hints in cache tree Vladimir Davydov
2019-04-04 11:39   ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 03/13] vinyl: store tuple comparison hints in tx write set Vladimir Davydov
2019-04-04 11:41   ` Konstantin Osipov
2019-04-04 12:21     ` Vladimir Davydov
2019-04-04 12:40       ` Konstantin Osipov
2019-04-04 17:28         ` Vladimir Davydov
2019-04-02 17:33 ` [PATCH 04/13] vinyl: store tuple comparison hints in tx read set Vladimir Davydov
2019-04-04 11:42   ` Konstantin Osipov
2019-04-04 12:08     ` Vladimir Davydov
2019-04-02 17:33 ` [PATCH 05/13] vinyl: store tuple comparison hints in range tree Vladimir Davydov
2019-04-02 17:33 ` [PATCH 06/13] vinyl: store tuple comparison hints in page index Vladimir Davydov
2019-04-02 17:33 ` [PATCH 07/13] vinyl: propagate tuple comparison hints to read iterator Vladimir Davydov
2019-04-04 11:43   ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 08/13] vinyl: propagate tuple comparison hints to write iterator Vladimir Davydov
2019-04-04 11:47   ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 09/13] vinyl: forward tuple comparison hints to memory tree Vladimir Davydov
2019-04-04 12:10   ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 10/13] vinyl: forward tuple comparison hints to cache tree Vladimir Davydov
2019-04-04 12:11   ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 11/13] vinyl: forward tuple comparison hints to read iterator Vladimir Davydov
2019-04-04 12:12   ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 12/13] vinyl: forward tuple comparison hints to tx read set Vladimir Davydov
2019-04-04 12:12   ` Konstantin Osipov
2019-04-02 17:33 ` [PATCH 13/13] Make tuple comparison hints mandatory Vladimir Davydov
2019-04-04 12:21   ` Konstantin Osipov

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