From d3a2e81566760f06c7b3e01b2b67e30356a4de22 Mon Sep 17 00:00:00 2001
From: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date: Mon, 19 Jun 2017 15:56:37 +0300
Subject: [PATCH] vinyl: make UPSERT prepare use of a cache as a hint

Use a tuple cache to turn an UPSERT in the PREPARE, if the cache
contains a tuple with the exact same key. The UPSERT can be applied
to the cached tuples, because
1. The cache always contains only REPLACE statements;
2. The cache always contains only newest statements.

Closes #2520
---
 src/box/vinyl.c          | 43 ++++++++++++++++++++++++++++++++--------
 src/box/vy_cache.c       |  8 +++++++-
 src/box/vy_cache.h       |  5 ++++-
 test/vinyl/upsert.result |  2 +-
 4 files changed, 47 insertions(+), 11 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index d0a2be9266..e05f6ea688 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -2644,7 +2644,7 @@ vy_index_commit_stmt(struct vy_index *index, struct vy_mem *mem,
 		vy_index_commit_upsert(index, mem, stmt);
 
 	/* Invalidate cache element. */
-	vy_cache_on_write(&index->cache, stmt);
+	vy_cache_on_write(&index->cache, stmt, NULL);
 }
 
 /*
@@ -2660,7 +2660,7 @@ vy_index_rollback_stmt(struct vy_index *index, struct vy_mem *mem,
 	vy_mem_rollback_stmt(mem, stmt);
 
 	/* Invalidate cache element. */
-	vy_cache_on_write(&index->cache, stmt);
+	vy_cache_on_write(&index->cache, stmt, NULL);
 }
 
 /**
@@ -2848,13 +2848,40 @@ vy_tx_write(struct vy_index *index, struct vy_mem *mem,
 	assert(*region_stmt == NULL ||
 	       vy_stmt_is_region_allocated(*region_stmt));
 
-	int rc = vy_index_set(index, mem, stmt, region_stmt);
-
 	/*
-	 * Invalidate cache element.
-	 */
-	vy_cache_on_write(&index->cache, stmt);
-	return rc;
+	 * The UPSERT statement can be applied to the cached
+	 * statement, because the cache always contains only
+	 * newest REPLACE statements. In such a case the UPSERT,
+	 * applied to the cached statement, can be inserted
+	 * instead of the original UPSERT.
+	 */
+	if (vy_stmt_type(stmt) == IPROTO_UPSERT) {
+		struct tuple *deleted = NULL;
+		/* Invalidate cache element. */
+		vy_cache_on_write(&index->cache, stmt, &deleted);
+		if (deleted != NULL) {
+			struct tuple *applied =
+				vy_apply_upsert(stmt, deleted, mem->key_def,
+						mem->format, mem->upsert_format,
+						false);
+			tuple_unref(deleted);
+			if (applied != NULL) {
+				assert(vy_stmt_type(applied) == IPROTO_REPLACE);
+				int rc = vy_index_set(index, mem, applied,
+						      region_stmt);
+				tuple_unref(applied);
+				return rc;
+			}
+			/*
+			 * Ignore a memory error, because it is
+			 * not critical to apply the optimization.
+			 */
+		}
+	} else {
+		/* Invalidate cache element. */
+		vy_cache_on_write(&index->cache, stmt, NULL);
+	}
+	return vy_index_set(index, mem, stmt, region_stmt);
 }
 
 /* {{{ Scheduler Task */
diff --git a/src/box/vy_cache.c b/src/box/vy_cache.c
index e31aa25c70..48f480ced7 100644
--- a/src/box/vy_cache.c
+++ b/src/box/vy_cache.c
@@ -349,7 +349,8 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 }
 
 void
-vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt)
+vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
+		  struct tuple **deleted)
 {
 	vy_cache_gc(cache->env);
 	bool exact = false;
@@ -409,6 +410,11 @@ vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt)
 	if (exact) {
 		cache->version++;
 		struct vy_cache_entry *to_delete = *entry;
+		assert(vy_stmt_type(to_delete->stmt) == IPROTO_REPLACE);
+		if (deleted != NULL) {
+			*deleted = to_delete->stmt;
+			tuple_ref(to_delete->stmt);
+		}
 		vy_cache_tree_delete(&cache->cache_tree, to_delete);
 		vy_cache_entry_delete(cache->env, to_delete);
 	}
diff --git a/src/box/vy_cache.h b/src/box/vy_cache.h
index f472b14e01..0e7792b554 100644
--- a/src/box/vy_cache.h
+++ b/src/box/vy_cache.h
@@ -190,9 +190,12 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
  * Invalidate possibly cached value due to its overwriting
  * @param cache - pointer to tuple cache.
  * @param stmt - overwritten statement.
+ * @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);
+vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
+		  struct tuple **deleted);
 
 
 /**
diff --git a/test/vinyl/upsert.result b/test/vinyl/upsert.result
index 9f48d6136a..0d11b71db4 100644
--- a/test/vinyl/upsert.result
+++ b/test/vinyl/upsert.result
@@ -739,7 +739,7 @@ new_stat = box.info.vinyl().performance["iterator"].run.lookup_count
 ...
 new_stat - old_stat
 ---
-- 2
+- 1
 ...
 old_stat = new_stat
 ---
-- 
GitLab