diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 98020848997baf23b5339a0755f013ca9ce2084c..f0028d08b1bb00e15c09ce7cbec2b18955d4b725 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -67,7 +67,6 @@ set (common_sources
      crc32.c
      random.c
      scramble.c
-     tbuf.c
      opts.c
      cfg.cc
      cpu_feature.c
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 8cb6295982548b00add6647eaaf34ff606589f1e..72ae654e3ceeeacfed51e9cbe7d8a37116e2a120 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -28,6 +28,7 @@ add_library(box
     index.cc
     hash_index.cc
     tree_index.cc
+    rtree_index.cc
     bitset_index.cc
     engine.cc
     engine_memtx.cc
diff --git a/src/box/engine_memtx.cc b/src/box/engine_memtx.cc
index 24f30946565638c2b25bda4935a5ef1a06314337..fa9c459ee482373e4e20f868d3888c1b945dabf2 100644
--- a/src/box/engine_memtx.cc
+++ b/src/box/engine_memtx.cc
@@ -32,6 +32,7 @@
 #include "index.h"
 #include "hash_index.h"
 #include "tree_index.h"
+#include "rtree_index.h"
 #include "bitset_index.h"
 #include "space.h"
 #include "exception.h"
@@ -105,6 +106,8 @@ MemtxFactory::createIndex(struct key_def *key_def)
 		return new HashIndex(key_def);
 	case TREE:
 		return new TreeIndex(key_def);
+	case RTREE:
+		return new RTreeIndex(key_def);
 	case BITSET:
 		return new BitsetIndex(key_def);
 	default:
@@ -138,6 +141,20 @@ MemtxFactory::keydefCheck(struct key_def *key_def)
 	case TREE:
 		/* TREE index has no limitations. */
 		break;
+	case RTREE:
+		if (key_def->part_count != 1) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
+				  "RTREE index key can not be multipart");
+		}
+		if (key_def->is_unique) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
+				  "RTREE index can not be unique");
+		}
+		break;
 	case BITSET:
 		if (key_def->part_count != 1) {
 			tnt_raise(ClientError, ER_MODIFY_INDEX,
@@ -158,4 +175,28 @@ MemtxFactory::keydefCheck(struct key_def *key_def)
 			  (unsigned) key_def->space_id);
 		break;
 	}
+	for (uint32_t i = 0; i < key_def->part_count; i++) {
+		switch (key_def->parts[i].type) {
+		case NUM:
+		case STRING:
+			if (key_def->type == RTREE) {
+				tnt_raise(ClientError, ER_MODIFY_INDEX,
+					  (unsigned) key_def->iid,
+					  (unsigned) key_def->space_id,
+					  "RTREE index field type must be ARRAY");
+			}
+			break;
+		case ARRAY:
+			if (key_def->type != RTREE) {
+				tnt_raise(ClientError, ER_MODIFY_INDEX,
+					  (unsigned) key_def->iid,
+					  (unsigned) key_def->space_id,
+					  "ARRAY field type is not supported");
+			}
+			break;
+		default:
+			assert(false);
+			break;
+		}
+	}
 }
diff --git a/src/box/engine_sophia.cc b/src/box/engine_sophia.cc
index 20a9555196e7656f493456181535adde9e14d226..e1058e6305fef5b257c9cc8cd85a147f32d893d1 100644
--- a/src/box/engine_sophia.cc
+++ b/src/box/engine_sophia.cc
@@ -202,6 +202,13 @@ SophiaFactory::keydefCheck(struct key_def *key_def)
 				  (unsigned) key_def->space_id,
 				  "Sophia TREE index key can not be multipart");
 		}
+		if (key_def->parts[0].type != NUM &&
+		    key_def->parts[0].type != STRING) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) key_def->iid,
+				  (unsigned) key_def->space_id,
+				  "Sophia TREE index field type must be STR or NUM");
+		}
 		break;
 	default:
 		tnt_raise(ClientError, ER_INDEX_TYPE,
diff --git a/src/box/index.cc b/src/box/index.cc
index d03db083b9be1555b495ed3ffa1e706cc886d751..4d3074d50b07f13d07dc6d3096dc507c2e4a4fe1 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -73,16 +73,29 @@ key_validate(struct key_def *key_def, enum iterator_type type, const char *key,
 		/* Fall through. */
 	}
 
-	if (part_count > key_def->part_count)
-		tnt_raise(ClientError, ER_KEY_PART_COUNT,
-			  key_def->part_count, part_count);
-
-	/* Partial keys are allowed only for TREE index type. */
-	if (key_def->type != TREE && part_count < key_def->part_count) {
-		tnt_raise(ClientError, ER_EXACT_MATCH,
-			  key_def->part_count, part_count);
-	}
-	key_validate_parts(key_def, key, part_count);
+        if (key_def->type == RTREE) {
+                if (part_count != 1 && part_count != 2 && part_count != 4) {
+                        tnt_raise(ClientError, ER_KEY_PART_COUNT, 4, part_count);
+                }
+#if 0
+		for (uint32_t part = 0; part < part_count; part++) {
+			enum mp_type mp_type = mp_typeof(*key);
+			mp_next(&key);
+			key_mp_type_validate(NUM, mp_type, ER_KEY_PART_TYPE, part);
+		}
+#endif
+        } else {
+                if (part_count > key_def->part_count)
+                        tnt_raise(ClientError, ER_KEY_PART_COUNT,
+                                  key_def->part_count, part_count);
+
+                /* Partial keys are allowed only for TREE index type. */
+                if (key_def->type != TREE && part_count < key_def->part_count) {
+                        tnt_raise(ClientError, ER_EXACT_MATCH,
+                                  key_def->part_count, part_count);
+                }
+                key_validate_parts(key_def, key, part_count);
+        }
 }
 
 void
diff --git a/src/box/index.h b/src/box/index.h
index 5ba9fd7ad8a92e33b908f33cddbae0c490504990..0b22c3fb9e4cdbb3511c806c1edf8b1a875c527b 100644
--- a/src/box/index.h
+++ b/src/box/index.h
@@ -70,6 +70,8 @@ struct tuple;
 	_(ITER_BITS_ALL_SET,     7) /* all bits from x are set in key      */ \
 	_(ITER_BITS_ANY_SET,     8) /* at least one x's bit is set         */ \
 	_(ITER_BITS_ALL_NOT_SET, 9) /* all bits are not set                */ \
+	_(ITER_OVERLAPS, 10) /* key overlaps x */ \
+	_(ITER_NEIGHBOR, 11) /* typles in distance ascending order from specified point */ \
 
 ENUM(iterator_type, ITERATOR_TYPE);
 extern const char *iterator_type_strs[];
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index c184871f4b49f884c2ff0f39f6d2bba4362f8441..bce1016e6c2c27ffe470ed24f173ca4dcb6edf5a 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -774,7 +774,6 @@ iproto_process_connect(struct iproto_request *request)
 		return;
 	} catch (Exception *e) {
 		e->log();
-		assert(con->session == NULL);
 		iproto_connection_close(con);
 		return;
 	}
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 42283d80aeb7268d526eb517a7e283ea9d881780..0f670e3396dcac2a7f3d7a663a257392c16166b3 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -33,13 +33,14 @@
 #include <stdio.h>
 #include "exception.h"
 
-const char *field_type_strs[] = {"UNKNOWN", "NUM", "STR", "\0"};
+const char *field_type_strs[] = {"UNKNOWN", "NUM", "STR", "ARRAY", "\0"};
 STRS(index_type, ENUM_INDEX_TYPE);
 
 const uint32_t key_mp_type[] = {
 	/* [UNKNOWN] = */ UINT32_MAX,
-	/* [NUM]     = */ 1U << MP_UINT,
-	/* [_STR]    = */ 1U << MP_STR
+	/* [NUM]     = */  1U << MP_UINT,
+	/* [STR]     =  */  1U << MP_STR,
+	/* [ARRAY]   =  */  1U << MP_ARRAY,
 };
 
 enum schema_object_type
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 91982d07ebcab8b110a859185a36b8a590752189..fb7a3a4b7059256a6f590adebbd42e721b85303e 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -75,21 +75,22 @@ schema_object_type(const char *name);
  * since there is a mismatch between enum name (STRING) and type
  * name literal ("STR"). STR is already used as Objective C type.
  */
-enum field_type { UNKNOWN = 0, NUM, STRING, field_type_MAX };
+enum field_type { UNKNOWN = 0, NUM, STRING, ARRAY, field_type_MAX };
 extern const char *field_type_strs[];
 
 static inline uint32_t
 field_type_maxlen(enum field_type type)
 {
 	static const uint32_t maxlen[] =
-		{ UINT32_MAX, 4, 8, UINT32_MAX, UINT32_MAX };
+		{ UINT32_MAX, 8, UINT32_MAX, UINT32_MAX, UINT32_MAX };
 	return maxlen[type];
 }
 
 #define ENUM_INDEX_TYPE(_) \
 	_(HASH,    0) /* HASH Index */   \
 	_(TREE,    1) /* TREE Index */   \
-	_(BITSET,  2) /* BITSET Index */
+	_(BITSET,  2) /* BITSET Index */ \
+	_(RTREE,   3) /* R-Tree Index */ \
 
 ENUM(index_type, ENUM_INDEX_TYPE);
 extern const char *index_type_strs[];
@@ -265,7 +266,6 @@ key_mp_type_validate(enum field_type key_type, enum mp_type mp_type,
 {
 	assert(key_type < field_type_MAX);
 	assert((int) mp_type < (int) CHAR_BIT * sizeof(*key_mp_type));
-
 	if (unlikely((key_mp_type[key_type] & (1U << mp_type)) == 0))
 		tnt_raise(ClientError, err, field_no,
 			  field_type_strs[key_type]);
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 00a661a72c564257ff57303ceaccb12358b71bc3..9eb6643b7968676dc6ec670a7f9a5ddc3c63bab8 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -40,7 +40,7 @@
 
 #include "lua/utils.h"
 #include "lua/msgpack.h"
-#include "tbuf.h"
+#include "iobuf.h"
 #include "fiber.h"
 #include "scoped_guard.h"
 #include "box/box.h"
@@ -248,18 +248,20 @@ lbox_request_create(struct request *request,
 	request_create(request, type);
 	request->space_id = lua_tointeger(L, 1);
 	if (key > 0) {
-		struct tbuf *key_buf = tbuf_new(&fiber()->gc);
-		luamp_encode(L, luaL_msgpack_default, key_buf, key);
-		request->key = key_buf->data;
-		request->key_end = key_buf->data + key_buf->size;
+		struct obuf key_buf;
+		obuf_create(&key_buf, &fiber()->gc, LUAMP_ALLOC_FACTOR);
+		luamp_encode(L, luaL_msgpack_default, &key_buf, key);
+		request->key = obuf_join(&key_buf);
+		request->key_end = request->key + obuf_size(&key_buf);
 		if (mp_typeof(*request->key) != MP_ARRAY)
 			tnt_raise(ClientError, ER_TUPLE_NOT_ARRAY);
 	}
 	if (tuple > 0) {
-		struct tbuf *tuple_buf = tbuf_new(&fiber()->gc);
-		luamp_encode(L, luaL_msgpack_default, tuple_buf, tuple);
-		request->tuple = tuple_buf->data;
-		request->tuple_end = tuple_buf->data + tuple_buf->size;
+		struct obuf tuple_buf;
+		obuf_create(&tuple_buf, &fiber()->gc, LUAMP_ALLOC_FACTOR);
+		luamp_encode(L, luaL_msgpack_default, &tuple_buf, tuple);
+		request->tuple = obuf_join(&tuple_buf);
+		request->tuple_end = request->tuple + obuf_size(&tuple_buf);
 		if (mp_typeof(*request->tuple) != MP_ARRAY)
 			tnt_raise(ClientError, ER_TUPLE_NOT_ARRAY);
 	}
diff --git a/src/box/lua/index.cc b/src/box/lua/index.cc
index 9293cf8ae52fef4df1539bd28096a7d1b15cdb2b..b2a607c27f9e40aa52a9702dd008210c075cc919 100644
--- a/src/box/lua/index.cc
+++ b/src/box/lua/index.cc
@@ -34,7 +34,6 @@
 #include "box/user_def.h"
 #include "box/lua/tuple.h"
 #include "fiber.h"
-#include "tbuf.h"
 
 /** {{{ box.index Lua library: access to spaces and indexes
  */
diff --git a/src/box/lua/tuple.cc b/src/box/lua/tuple.cc
index f0c204c4bbdec6d62ebdb804a331cb86e46464d3..57968d387f41b57593b08bd0df723a384442b684 100644
--- a/src/box/lua/tuple.cc
+++ b/src/box/lua/tuple.cc
@@ -31,7 +31,7 @@
 #include "box/tuple.h"
 #include "box/tuple_update.h"
 #include "fiber.h"
-#include "tbuf.h"
+#include "iobuf.h"
 #include "lua/utils.h"
 #include "lua/msgpack.h"
 #include "third_party/lua-yaml/lyaml.h"
@@ -169,11 +169,11 @@ lbox_tuple_slice(struct lua_State *L)
 
 /* A MsgPack extensions handler that supports tuples */
 static int
-luamp_encode_extension_box(struct lua_State *L, int idx, struct tbuf *b)
+luamp_encode_extension_box(struct lua_State *L, int idx, struct obuf *b)
 {
 	struct tuple *tuple = lua_istuple(L, idx);
 	if (tuple != NULL) {
-		tuple_to_tbuf(tuple, b);
+		tuple_to_obuf(tuple, b);
 		return 0;
 	}
 
@@ -185,7 +185,7 @@ luamp_encode_extension_box(struct lua_State *L, int idx, struct tbuf *b)
  * Will be removed after API change.
  */
 int
-luamp_encodestack(struct lua_State *L, struct tbuf *b, int first, int last)
+luamp_encodestack(struct lua_State *L, struct obuf *b, int first, int last)
 {
 	if (first == last && (lua_istable(L, first) || lua_istuple(L, first))) {
 		/* New format */
@@ -193,7 +193,7 @@ luamp_encodestack(struct lua_State *L, struct tbuf *b, int first, int last)
 		return 1;
 	} else {
 		/* Backward-compatible format */
-		/* sic: if arg_count is 0, first > last */
+		/* sic: first > last */
 		luamp_encode_array(luaL_msgpack_default, b, last + 1 - first);
 		for (int k = first; k <= last; ++k) {
 			luamp_encode(L, luaL_msgpack_default, b, k);
@@ -269,27 +269,29 @@ lbox_tuple_transform(struct lua_State *L)
 	/*
 	 * Prepare UPDATE expression
 	 */
-	struct tbuf *b = tbuf_new(&fiber()->gc);
-	luamp_encode_array(luaL_msgpack_default,b, op_cnt);
+	struct obuf buf;
+	obuf_create(&buf, &fiber()->gc, LUAMP_ALLOC_FACTOR);
+	luamp_encode_array(luaL_msgpack_default, &buf, op_cnt);
 	if (len > 0) {
-		luamp_encode_array(luaL_msgpack_default, b, 3);
-		luamp_encode_str(luaL_msgpack_default, b, "#", 1);
-		luamp_encode_uint(luaL_msgpack_default, b, offset);
-		luamp_encode_uint(luaL_msgpack_default, b, len);
+		luamp_encode_array(luaL_msgpack_default, &buf, 3);
+		luamp_encode_str(luaL_msgpack_default, &buf, "#", 1);
+		luamp_encode_uint(luaL_msgpack_default, &buf, offset);
+		luamp_encode_uint(luaL_msgpack_default, &buf, len);
 	}
 
 	for (int i = argc ; i > 3; i--) {
-		luamp_encode_array(luaL_msgpack_default, b, 3);
-		luamp_encode_str(luaL_msgpack_default, b, "!", 1);
-		luamp_encode_uint(luaL_msgpack_default, b, offset);
-		luamp_encode(L, luaL_msgpack_default, b, i);
+		luamp_encode_array(luaL_msgpack_default, &buf, 3);
+		luamp_encode_str(luaL_msgpack_default, &buf, "!", 1);
+		luamp_encode_uint(luaL_msgpack_default, &buf, offset);
+		luamp_encode(L, luaL_msgpack_default, &buf, i);
 	}
 
 	/* Execute tuple_update */
+	const char *expr = obuf_join(&buf);
 	struct tuple *new_tuple = tuple_update(tuple_format_ber,
 					       tuple_update_region_alloc,
 					       &fiber()->gc,
-					       tuple, tbuf_str(b), tbuf_end(b),
+					       tuple, expr, expr + obuf_size(&buf),
 					       0);
 	lbox_pushtuple(L, new_tuple);
 	return 1;
@@ -328,22 +330,25 @@ static const struct luaL_reg lbox_tuple_iterator_meta[] = {
 };
 
 
-struct tuple*
+struct tuple *
 lua_totuple(struct lua_State *L, int first, int last)
 {
 	RegionGuard region_guard(&fiber()->gc);
-	struct tbuf *b = tbuf_new(&fiber()->gc);
+	struct obuf buf;
+	obuf_create(&buf, &fiber()->gc, LUAMP_ALLOC_FACTOR);
+
 	try {
-		luamp_encodestack(L, b, first, last);
+		luamp_encodestack(L, &buf, first, last);
 	} catch (Exception *e) {
 		throw;
 	} catch (...) {
 		tnt_raise(ClientError, ER_PROC_LUA, lua_tostring(L, -1));
 	}
-	const char *data = b->data;
+	const char *data = obuf_join(&buf);
 	if (unlikely(mp_typeof(*data) != MP_ARRAY))
 		tnt_raise(ClientError, ER_TUPLE_NOT_ARRAY);
-	struct tuple *tuple = tuple_new(tuple_format_ber, data, tbuf_end(b));
+	struct tuple *tuple = tuple_new(tuple_format_ber, data,
+					data + obuf_size(&buf));
 	return tuple;
 }
 
diff --git a/src/box/rtree_index.cc b/src/box/rtree_index.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f8ddddf7e216f5a60ce0be2fadd95921191002b8
--- /dev/null
+++ b/src/box/rtree_index.cc
@@ -0,0 +1,361 @@
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "rtree_index.h"
+#include "tuple.h"
+#include "space.h"
+#include "exception.h"
+#include "errinj.h"
+#include "fiber.h"
+#include "small/small.h"
+
+/** For all memory used by all rtree indexes. */
+static struct mempool rtree_page_pool;
+/** Number of allocated pages. */
+static int rtree_page_pool_initialized = 0;
+
+/* {{{ Utilities. *************************************************/
+
+inline void extract_rectangle(struct rtree_rect *rect,
+			      const struct tuple *tuple, struct key_def *kd)
+{
+	const char *elems = tuple_field(tuple, kd->parts[0].fieldno);
+	uint32_t size = mp_decode_array(&elems);
+        assert (kd->part_count == 1);
+	switch (size) {
+	case 1: // array
+	{
+		const char* elems = tuple_field(tuple, kd->parts[0].fieldno);
+		uint32_t size = mp_decode_array(&elems);
+		switch (size) {
+		case 2: // point
+			rect->lower_point.coords[0] =
+				rect->upper_point.coords[0] =
+				mp_decode_num(&elems, 0);
+			rect->lower_point.coords[1] =
+				rect->upper_point.coords[1] =
+				mp_decode_num(&elems, 1);
+			break;
+		case 4:
+			rect->lower_point.coords[0] = mp_decode_num(&elems, 0);
+			rect->lower_point.coords[1] = mp_decode_num(&elems, 1);
+			rect->upper_point.coords[0] = mp_decode_num(&elems, 2);
+			rect->upper_point.coords[1] = mp_decode_num(&elems, 3);
+			break;
+		default:
+			tnt_raise(ClientError, ER_UNSUPPORTED,
+				  "R-Tree index", "Field should be array with "
+				  "size 2 (point) or 4 (rectangle)");
+
+		}
+		break;
+	}
+	case 2: // point
+		rect->lower_point.coords[0] =
+			rect->upper_point.coords[0] =
+			mp_decode_num(&elems, 0);
+		rect->lower_point.coords[1] =
+			rect->upper_point.coords[1] =
+			mp_decode_num(&elems, 1);
+		break;
+	case 4:
+		rect->lower_point.coords[0] = mp_decode_num(&elems, 0);
+		rect->lower_point.coords[1] = mp_decode_num(&elems, 1);
+		rect->upper_point.coords[0] = mp_decode_num(&elems, 2);
+		rect->upper_point.coords[1] = mp_decode_num(&elems, 3);
+		break;
+	default:
+		tnt_raise(ClientError, ER_UNSUPPORTED,
+			  "R-Tree index", "Key should contain 2 (point) or 4 (rectangle) coordinates");
+
+	}
+	rtree_rect_normalize(rect);
+}
+/* {{{ TreeIndex Iterators ****************************************/
+
+struct index_rtree_iterator {
+        struct iterator base;
+        struct rtree_iterator impl;
+};
+
+static void
+index_rtree_iterator_free(struct iterator *i)
+{
+	struct index_rtree_iterator *itr = (struct index_rtree_iterator *)i;
+	rtree_iterator_destroy(&itr->impl);
+        delete itr;
+}
+
+static struct tuple *
+index_rtree_iterator_next(struct iterator *i)
+{
+	struct index_rtree_iterator *itr = (struct index_rtree_iterator *)i;
+	return (struct tuple *)rtree_iterator_next(&itr->impl);
+}
+
+/* }}} */
+
+/* {{{ TreeIndex  **********************************************************/
+
+static void *
+rtree_page_alloc()
+{
+	ERROR_INJECT(ERRINJ_INDEX_ALLOC, return 0);
+	return mempool_alloc(&rtree_page_pool);
+}
+
+static void
+rtree_page_free(void *page)
+{
+	return mempool_free(&rtree_page_pool, page);
+}
+RTreeIndex::~RTreeIndex()
+{
+	// Iterator has to be destroye prior to tree
+	if (m_position != NULL) {
+		index_rtree_iterator_free(m_position);
+		m_position = NULL;
+	}
+	rtree_destroy(&tree);
+}
+
+RTreeIndex::RTreeIndex(struct key_def *key_def)
+  : Index(key_def)
+{
+	assert(key_def->part_count == 1);
+	assert(key_def->parts[0].type = ARRAY);
+	assert(key_def->is_unique == false);
+
+	if (rtree_page_pool_initialized == 0) {
+		mempool_create(&rtree_page_pool, &cord()->slabc,
+			       RTREE_PAGE_SIZE);
+		rtree_page_pool_initialized = 1;
+	}
+	rtree_init(&tree, rtree_page_alloc, rtree_page_free);
+}
+
+size_t
+RTreeIndex::size() const
+{
+	return rtree_number_of_records(&tree);
+}
+
+size_t
+RTreeIndex::memsize() const
+{
+        return rtree_used_size(&tree);
+}
+
+struct tuple *
+RTreeIndex::findByKey(const char *key, uint32_t part_count) const
+{
+	rtree_rect rect;
+        struct rtree_iterator iterator;
+        rtree_iterator_init(&iterator);
+        switch (part_count) {
+	case 1:
+	{
+		uint32_t size = mp_decode_array(&key);
+		switch (size) {
+		case 2:
+			rect.lower_point.coords[0] =
+				rect.upper_point.coords[0] =
+				mp_decode_num(&key, 0);
+			rect.lower_point.coords[1] =
+				rect.upper_point.coords[1] =
+				mp_decode_num(&key, 1);
+			break;
+		case 4:
+			rect.lower_point.coords[0] = mp_decode_num(&key, 0);
+			rect.lower_point.coords[1] = mp_decode_num(&key, 1);
+			rect.upper_point.coords[0] = mp_decode_num(&key, 2);
+			rect.upper_point.coords[1] = mp_decode_num(&key, 3);
+			break;
+		default:
+			tnt_raise(ClientError, ER_UNSUPPORTED,
+				  "R-Tree key", "Key should be array of 2 (point) "
+				  "or 4 (rectangle) numeric coordinates");
+		}
+		break;
+	}
+	case 2:
+		rect.lower_point.coords[0] =
+			rect.upper_point.coords[0] =
+			mp_decode_num(&key, 0);
+		rect.lower_point.coords[1] =
+			rect.upper_point.coords[1] =
+			mp_decode_num(&key, 1);
+		break;
+	case 4:
+		rect.lower_point.coords[0] = mp_decode_num(&key, 0);
+		rect.lower_point.coords[1] = mp_decode_num(&key, 1);
+		rect.upper_point.coords[0] = mp_decode_num(&key, 2);
+		rect.upper_point.coords[1] = mp_decode_num(&key, 3);
+		break;
+	default:
+		tnt_raise(ClientError, ER_UNSUPPORTED,
+			  "R-Tree key", "Key should contain 2 (point) "
+			  "or 4 (rectangle) numeric coordinates");
+	}
+        struct tuple *result = NULL;
+        if (rtree_search(&tree, &rect, SOP_OVERLAPS, &iterator))
+        	result = (struct tuple *)rtree_iterator_next(&iterator);
+        rtree_iterator_destroy(&iterator);
+        return result;
+}
+
+struct tuple *
+RTreeIndex::replace(struct tuple *old_tuple, struct tuple *new_tuple,
+                    enum dup_replace_mode)
+{
+        struct rtree_rect rect;
+        if (new_tuple) {
+                extract_rectangle(&rect, new_tuple, key_def);
+                rtree_insert(&tree, &rect, new_tuple);
+        }
+	if (old_tuple) {
+                extract_rectangle(&rect, old_tuple, key_def);
+                if (!rtree_remove(&tree, &rect, old_tuple))
+                        old_tuple = NULL;
+        }
+        return old_tuple;
+}
+
+struct iterator *
+RTreeIndex::allocIterator() const
+{
+	index_rtree_iterator *it = new index_rtree_iterator;
+	memset(it, 0, sizeof(*it));
+	rtree_iterator_init(&it->impl);
+	if (it == NULL) {
+		tnt_raise(ClientError, ER_MEMORY_ISSUE,
+			  sizeof(struct index_rtree_iterator),
+			  "RTreeIndex", "iterator");
+	}
+	it->base.next = index_rtree_iterator_next;
+	it->base.free = index_rtree_iterator_free;
+	return &it->base;
+}
+
+void
+RTreeIndex::initIterator(struct iterator *iterator, enum iterator_type type,
+                         const char *key, uint32_t part_count) const
+{
+        struct rtree_rect rect;
+        index_rtree_iterator *it = (index_rtree_iterator *)iterator;
+        switch (part_count) {
+        case 0:
+                if (type != ITER_ALL) {
+                        tnt_raise(ClientError, ER_UNSUPPORTED,
+				  "R-Tree index", "It is possible to omit key only for ITER_ALL");
+                }
+                break;
+	case 1:
+	{
+		uint32_t size = mp_decode_array(&key);
+		switch (size) {
+		case 2:
+			rect.lower_point.coords[0] =
+				rect.upper_point.coords[0] =
+				mp_decode_num(&key, 0);
+			rect.lower_point.coords[1] =
+				rect.upper_point.coords[1] =
+				mp_decode_num(&key, 1);
+			break;
+		case 4:
+			rect.lower_point.coords[0] = mp_decode_num(&key, 0);
+			rect.lower_point.coords[1] = mp_decode_num(&key, 1);
+			rect.upper_point.coords[0] = mp_decode_num(&key, 2);
+			rect.upper_point.coords[1] = mp_decode_num(&key, 3);
+			break;
+		default:
+			tnt_raise(ClientError, ER_UNSUPPORTED,
+				  "R-Tree index", "Key should be array of 2 (point) "
+				  "or 4 (rectangle) numeric coordinates");
+		}
+		break;
+	}
+	case 2:
+		rect.lower_point.coords[0] =
+			rect.upper_point.coords[0] =
+			mp_decode_num(&key, 0);
+		rect.lower_point.coords[1] =
+			rect.upper_point.coords[1] =
+			mp_decode_num(&key, 1);
+		break;
+	case 4:
+		rect.lower_point.coords[0] = mp_decode_num(&key, 0);
+		rect.lower_point.coords[1] = mp_decode_num(&key, 1);
+		rect.upper_point.coords[0] = mp_decode_num(&key, 2);
+		rect.upper_point.coords[1] = mp_decode_num(&key, 3);
+		break;
+	default:
+		tnt_raise(ClientError, ER_UNSUPPORTED,
+			  "R-Tree index", "Key contain 2 (point) "
+			  "or 4 (rectangle) numeric coordinates");
+	}
+        spatial_search_op op;
+        switch (type) {
+        case ITER_ALL:
+                op = SOP_ALL;
+                break;
+        case ITER_EQ:
+                op = SOP_EQUALS;
+                break;
+        case ITER_GT:
+                op = SOP_STRICT_CONTAINS;
+                break;
+        case ITER_GE:
+                op = SOP_CONTAINS;
+                break;
+        case ITER_LT:
+                op = SOP_STRICT_BELONGS;
+                break;
+        case ITER_LE:
+                op = SOP_BELONGS;
+                break;
+        case ITER_OVERLAPS:
+                op = SOP_OVERLAPS;
+                break;
+        case ITER_NEIGHBOR:
+                op = SOP_NEIGHBOR;
+                break;
+        default:
+                tnt_raise(ClientError, ER_UNSUPPORTED,
+                          "R-Tree index", "Unsupported search operation for R-Tree");
+        }
+        rtree_search(&tree, &rect, op, &it->impl);
+}
+
+void
+RTreeIndex::beginBuild()
+{
+	rtree_purge(&tree);
+}
+
+
diff --git a/src/box/rtree_index.h b/src/box/rtree_index.h
new file mode 100644
index 0000000000000000000000000000000000000000..451e0d77775857b8bcfdc1bd472ea1ff39b5bf3a
--- /dev/null
+++ b/src/box/rtree_index.h
@@ -0,0 +1,59 @@
+#ifndef TARANTOOL_BOX_RTREE_INDEX_H_INCLUDED
+#define TARANTOOL_BOX_RTREE_INDEX_H_INCLUDED
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "index.h"
+
+#include <salad/rtree.h>
+
+class RTreeIndex: public Index
+{
+public:
+	RTreeIndex(struct key_def *key_def);
+	~RTreeIndex();
+
+	virtual void beginBuild();
+	virtual size_t size() const;
+	virtual struct tuple *findByKey(const char *key, uint32_t part_count) const;
+	virtual struct tuple *replace(struct tuple *old_tuple,
+                                      struct tuple *new_tuple,
+                                      enum dup_replace_mode mode);
+
+	virtual size_t memsize() const;
+	virtual struct iterator *allocIterator() const;
+	virtual void initIterator(struct iterator *iterator,
+                                  enum iterator_type type,
+                                  const char *key, uint32_t part_count) const;
+
+protected:
+	struct rtree tree;
+};
+
+#endif /* TARANTOOL_BOX_RTREE_INDEX_H_INCLUDED */
diff --git a/src/box/tuple.cc b/src/box/tuple.cc
index b1b873df4d6df227fdccdc1976a1f9e5d96b2e18..de65e1af7b86c997256824d9bd0fc7e2844ac9ea 100644
--- a/src/box/tuple.cc
+++ b/src/box/tuple.cc
@@ -30,13 +30,21 @@
 
 #include "small/small.h"
 #include "small/quota.h"
-#include "tbuf.h"
 
 #include "key_def.h"
 #include "tuple_update.h"
 #include <exception.h>
 #include <stdio.h>
 
+#ifndef DBL_MIN
+#define DBL_MIN		4.94065645841246544e-324
+#define FLT_MIN		((float)1.40129846432481707e-45)
+#endif
+#ifndef DBL_MAX
+#define DBL_MAX		1.79769313486231470e+308
+#define FLT_MAX		((float)3.40282346638528860e+38)
+#endif
+
 /** Global table of tuple formats */
 struct tuple_format **tuple_formats;
 struct tuple_format *tuple_format_ber;
@@ -536,7 +544,7 @@ tuple_init(float tuple_arena_max_size, uint32_t objsize_min,
 		say_warn("disable shared arena since running under OpenVZ "
 		    "(https://bugzilla.openvz.org/show_bug.cgi?id=2805)");
 		flags = MAP_PRIVATE;
-        } else {
+	} else {
 		say_info("mapping %zu bytes for a shared arena...",
 			max_size);
 		flags = MAP_SHARED;
@@ -589,3 +597,26 @@ tuple_end_snapshot()
 {
 	small_alloc_setopt(&memtx_alloc, SMALL_DELAYED_FREE_MODE, false);
 }
+
+double mp_decode_num(const char **data, uint32_t i)
+{
+	double val;
+	switch (mp_typeof(**data)) {
+	case MP_UINT:
+		val = mp_decode_uint(data);
+		break;
+	case MP_INT:
+		val = mp_decode_int(data);
+		break;
+	case MP_FLOAT:
+		val = mp_decode_float(data);
+		break;
+	case MP_DOUBLE:
+		val = mp_decode_double(data);
+		break;
+	default:
+		tnt_raise(ClientError, ER_FIELD_TYPE, i, field_type_strs[NUM]);
+	}
+	return val;
+}
+
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 5c973d512e063059dc7854c293c4edb8e6a19a2e..4e2b2902ea65bcf7d3763229ea7830dd331ec3fe 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -34,8 +34,6 @@
 enum { FORMAT_ID_MAX = UINT16_MAX - 1, FORMAT_ID_NIL = UINT16_MAX };
 enum { FORMAT_REF_MAX = INT32_MAX, TUPLE_REF_MAX = UINT16_MAX };
 
-struct tbuf;
-
 /** Common quota for tuples and indexes */
 extern struct quota memtx_quota;
 /** Tuple allocator */
@@ -344,6 +342,25 @@ tuple_field_u32(struct tuple *tuple, uint32_t i)
 	return (uint32_t) val;
 }
 
+/**
+ * Decode numeric field and return its value as double
+ */
+double
+mp_decode_num(const char **data, uint32_t i);
+
+/**
+ * A convenience shortcut for data dictionary - get a numeric tuple field as double
+ */
+inline double
+tuple_field_num(const struct tuple* tuple, uint32_t field_no)
+{
+	const char* field = tuple_field(tuple, field_no);
+	if (field == NULL) {
+		tnt_raise(ClientError, ER_NO_SUCH_FIELD, field_no);
+	}
+	return mp_decode_num(&field, field_no);
+}
+
 /**
  * A convenience shortcut for data dictionary - get a tuple field
  * as a NUL-terminated string - returns a string of up to 256 bytes.
@@ -509,10 +526,6 @@ tuple_compare_with_key(const struct tuple *tuple_a, const char *key,
 void
 tuple_to_obuf(struct tuple *tuple, struct obuf *buf);
 
-/* Store tuple fields in the tbuf, BER-length-encoded. */
-void
-tuple_to_tbuf(struct tuple *tuple, struct tbuf *buf);
-
 /**
  * Store tuple fields in the memory buffer. Buffer must have at least
  * tuple->bsize bytes.
diff --git a/src/box/tuple_convert.cc b/src/box/tuple_convert.cc
index 5e1346dd85115dcff78798035294e1278e66159c..5e9b505dc35a38746c0d26f6fb4b864090411a9b 100644
--- a/src/box/tuple_convert.cc
+++ b/src/box/tuple_convert.cc
@@ -28,7 +28,6 @@
  */
 #include "tuple.h"
 #include "iobuf.h"
-#include "tbuf.h"
 
 void
 tuple_to_obuf(struct tuple *tuple, struct obuf *buf)
@@ -36,12 +35,6 @@ tuple_to_obuf(struct tuple *tuple, struct obuf *buf)
 	obuf_dup(buf, tuple->data, tuple->bsize);
 }
 
-void
-tuple_to_tbuf(struct tuple *tuple, struct tbuf *buf)
-{
-	tbuf_append(buf, tuple->data, tuple->bsize);
-}
-
 void
 tuple_to_buf(struct tuple *tuple, char *buf)
 {
diff --git a/src/errinj.cc b/src/errinj.cc
index 1617a26f4f6984490cb31b7d9a8aa85660ecc51d..f4fad088f565bcccbb1454f2bf3019c083d2f02d 100644
--- a/src/errinj.cc
+++ b/src/errinj.cc
@@ -34,7 +34,6 @@
 #include "trivia/config.h"
 #include "trivia/util.h"
 #include "say.h"
-#include "tbuf.h"
 #include "errinj.h"
 
 #define ERRINJ_MEMBER(n, s) { /* .name = */ #n, /* .state = */ s },
diff --git a/src/iobuf.cc b/src/iobuf.cc
index d4837d673a03e3124e69cc5a6df657ff2d354a94..4f565693b4b43f19dd80a4d35e54c55bcc19aa4f 100644
--- a/src/iobuf.cc
+++ b/src/iobuf.cc
@@ -112,7 +112,7 @@ obuf_init_pos(struct obuf *buf, size_t pos)
 static inline void
 obuf_alloc_pos(struct obuf *buf, size_t pos, size_t size)
 {
-	size_t capacity = pos > 0 ?  buf->capacity[pos-1] * 2 : iobuf_readahead;
+	size_t capacity = pos > 0 ?  buf->capacity[pos-1] * 2 : buf->alloc_factor;
 	while (capacity < size) {
 		capacity *=2;
 	}
@@ -126,11 +126,12 @@ obuf_alloc_pos(struct obuf *buf, size_t pos, size_t size)
  * yet -- it may never be needed.
  */
 void
-obuf_create(struct obuf *buf, struct region *pool)
+obuf_create(struct obuf *buf, struct region *pool, size_t alloc_factor)
 {
 	buf->pool = pool;
 	buf->pos = 0;
 	buf->size = 0;
+	buf->alloc_factor = alloc_factor;
 	obuf_init_pos(buf, buf->pos);
 }
 
@@ -155,7 +156,7 @@ obuf_dup(struct obuf *buf, const void *data, size_t size)
 	/**
 	 * @pre buf->pos points at an array of allocated buffers.
 	 * The array ends with a zero-initialized buffer.
-         */
+	 */
 	while (iov->iov_len + size > capacity) {
 		/*
 		 * The data doesn't fit into this buffer.
@@ -206,36 +207,39 @@ obuf_dup(struct obuf *buf, const void *data, size_t size)
 	assert(iov->iov_len <= buf->capacity[buf->pos]);
 }
 
-/** Book a few bytes in the output buffer. */
-struct obuf_svp
-obuf_book(struct obuf *buf, size_t size)
+void
+obuf_ensure_resize(struct obuf *buf, size_t size)
 {
 	struct iovec *iov = &buf->iov[buf->pos];
 	size_t capacity = buf->capacity[buf->pos];
-	if (iov->iov_len + size > capacity) {
-		if (iov->iov_len > 0) {
-			/* Move to the next buffer. */
-			buf->pos++;
-			iov = &buf->iov[buf->pos];
-			capacity = buf->capacity[buf->pos];
-		}
-		/* Make sure the next buffer can store size.  */
-		if (capacity == 0) {
-			obuf_init_pos(buf, buf->pos + 1);
-			obuf_alloc_pos(buf, buf->pos, size);
-		} else if (size > capacity) {
-			/* Simply realloc. */
-			obuf_alloc_pos(buf, buf->pos, size);
-		}
+	if (iov->iov_len > 0) {
+		/* Move to the next buffer. */
+		buf->pos++;
+		iov = &buf->iov[buf->pos];
+		capacity = buf->capacity[buf->pos];
+	}
+	/* Make sure the next buffer can store size.  */
+	if (capacity == 0) {
+		obuf_init_pos(buf, buf->pos + 1);
+		obuf_alloc_pos(buf, buf->pos, size);
+	} else if (size > capacity) {
+		/* Simply realloc. */
+		obuf_alloc_pos(buf, buf->pos, size);
 	}
+}
+
+/** Book a few bytes in the output buffer. */
+struct obuf_svp
+obuf_book(struct obuf *buf, size_t size)
+{
+	obuf_ensure(buf, size);
+
 	struct obuf_svp svp;
 	svp.pos = buf->pos;
-	svp.iov_len = iov->iov_len;
+	svp.iov_len = buf->iov[buf->pos].iov_len;
 	svp.size = buf->size;
 
-	iov->iov_len += size;
-	buf->size += size;
-	assert(iov->iov_len <= buf->capacity[buf->pos]);
+	obuf_advance(buf, size);
 	return svp;
 }
 
@@ -285,7 +289,7 @@ iobuf_new(const char *name)
 		region_create(&iobuf->pool, &cord()->slabc);
 		/* Note: do not allocate memory upfront. */
 		ibuf_create(&iobuf->in, &iobuf->pool);
-		obuf_create(&iobuf->out, &iobuf->pool);
+		obuf_create(&iobuf->out, &iobuf->pool, iobuf_readahead);
 	} else {
 		iobuf = SLIST_FIRST(&iobuf_cache);
 		SLIST_REMOVE_HEAD(&iobuf_cache, next);
@@ -307,7 +311,7 @@ iobuf_delete(struct iobuf *iobuf)
 	} else {
 		region_free(pool);
 		ibuf_create(&iobuf->in, pool);
-		obuf_create(&iobuf->out, pool);
+		obuf_create(&iobuf->out, pool, iobuf_readahead);
 	}
 	region_set_name(pool, "iobuf_cache");
 	SLIST_INSERT_HEAD(&iobuf_cache, iobuf, next);
diff --git a/src/iobuf.h b/src/iobuf.h
index 25d97babc89a8c2e689955175c4da0692d79d03e..2e0e80b710021ffca38a08c6ddebbc3abde51864 100644
--- a/src/iobuf.h
+++ b/src/iobuf.h
@@ -113,6 +113,8 @@ struct obuf
 	size_t size;
 	/** Position of the "current" iovec. */
 	size_t pos;
+	/** Allocation factor (allocations are a multiple of this number) */
+	size_t alloc_factor;
 	/** How many bytes are actually allocated for each iovec. */
 	size_t capacity[IOBUF_IOV_MAX];
 	/**
@@ -124,6 +126,9 @@ struct obuf
 	struct iovec iov[IOBUF_IOV_MAX];
 };
 
+void
+obuf_create(struct obuf *buf, struct region *pool, size_t alloc_factor);
+
 /** How many bytes are in the output buffer. */
 static inline size_t
 obuf_size(struct obuf *obuf)
@@ -151,6 +156,41 @@ struct obuf_svp
 	size_t size;
 };
 
+void
+obuf_ensure_resize(struct obuf *buf, size_t size);
+
+/**
+ * \brief Ensure \a buf to have at least \a size bytes of contiguous memory
+ * for write and return a point to this chunk.
+ * After write please call obuf_advance(wsize) where wsize <= size to advance
+ * a write position.
+ * \param buf
+ * \param size
+ * \return a pointer to contiguous chunk of memory
+ */
+static inline char *
+obuf_ensure(struct obuf *buf, size_t size)
+{
+	if (buf->iov[buf->pos].iov_len + size > buf->capacity[buf->pos])
+		obuf_ensure_resize(buf, size);
+	struct iovec *iov = &buf->iov[buf->pos];
+	return (char *) iov->iov_base + iov->iov_len;
+}
+
+/**
+ * \brief Advance write position after using obuf_ensure()
+ * \param buf
+ * \param size
+ * \sa obuf_ensure
+ */
+static inline void
+obuf_advance(struct obuf *buf, size_t size)
+{
+	buf->iov[buf->pos].iov_len += size;
+	buf->size += size;
+	assert(buf->iov[buf->pos].iov_len <= buf->capacity[buf->pos]);
+}
+
 /**
  * Reserve size bytes in the output buffer
  * and return a pointer to the reserved
@@ -192,6 +232,28 @@ obuf_svp_to_ptr(struct obuf *buf, struct obuf_svp *svp)
 void
 obuf_rollback_to_svp(struct obuf *buf, struct obuf_svp *svp);
 
+/**
+ * \brief Conventional function to join iovec into a solid memory chunk.
+ * For iovec of size 1 returns iov->iov_base without allocation extra memory.
+ * \param[out] size calculated length of \a iov
+ * \return solid memory chunk
+ */
+static inline char *
+obuf_join(struct obuf *obuf)
+{
+	size_t iovcnt = obuf_iovcnt(obuf);
+	if (iovcnt == 1)
+		return (char *) obuf->iov[0].iov_base;
+
+	char *data = (char *) region_alloc(obuf->pool, obuf_size(obuf));
+	char *pos = data;
+	for (int i = 0; i < iovcnt; i++) {
+		memcpy(pos, obuf->iov[i].iov_base, obuf->iov[i].iov_len);
+		pos += obuf->iov[i].iov_len;
+	}
+	return data;
+}
+
 /* }}} */
 
 /** {{{  Input/output pair. */
diff --git a/src/lib/salad/CMakeLists.txt b/src/lib/salad/CMakeLists.txt
index fe1be4c4bc9729f4d198ad6f0ccac9655687890f..9db3d800e80b8288bb0ed709e7b0e65865a53c06 100644
--- a/src/lib/salad/CMakeLists.txt
+++ b/src/lib/salad/CMakeLists.txt
@@ -1,3 +1,3 @@
-set(lib_sources rope.c rlist.c)
+set(lib_sources rope.c rlist.c rtree.c)
 set_source_files_compile_flags(${lib_sources})
 add_library(salad ${lib_sources})
diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
index 02708a15623e1f1c4c8cabbceeb844ff094b3979..887de62bd13928c37e8a3ccfebb49c262c8f04be 100644
--- a/src/lib/salad/bps_tree.h
+++ b/src/lib/salad/bps_tree.h
@@ -1,3 +1,35 @@
+/*
+ * *No header guard*: the header is allowed to be included twice
+ * with different sets of defines.
+ */
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
 #include <string.h> /* memmove, memset */
 #include <stdint.h>
 #include <assert.h>
@@ -426,8 +458,7 @@ typedef uint32_t bps_tree_block_id_t;
 
 /**
  * struct bps_block forward declaration (Used in struct bps_tree)
- */
-struct bps_block;
+ */struct bps_block;
 
 #ifdef BPS_TREE_DEBUG_BRANCH_VISIT
 #define BPS_TREE_BRANCH_TRACE(tree, type, branch_bit) \
diff --git a/src/lib/salad/mhash.h b/src/lib/salad/mhash.h
index 1d8c080811ce0bd637498a578315e2f7b813f224..c04adfafa1cbf2faa3a83639e1482fd3289a44de 100644
--- a/src/lib/salad/mhash.h
+++ b/src/lib/salad/mhash.h
@@ -1,3 +1,7 @@
+/*
+ * *No header guard*: the header is allowed to be included twice
+ * with different sets of defines.
+ */
 /*
  * Redistribution and use in source and binary forms, with or
  * without modification, are permitted provided that the following
diff --git a/src/lib/salad/rtree.c b/src/lib/salad/rtree.c
new file mode 100644
index 0000000000000000000000000000000000000000..1ad73cd886e99468e18ff89aeec660f94fcc124a
--- /dev/null
+++ b/src/lib/salad/rtree.c
@@ -0,0 +1,979 @@
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "rtree.h"
+#include <string.h>
+#include <assert.h>
+
+/*------------------------------------------------------------------------- */
+/* R-tree internal structures definition */
+/*------------------------------------------------------------------------- */
+
+struct rtree_page_branch {
+	struct rtree_rect rect;
+	union {
+		struct rtree_page *page;
+		record_t record;
+	} data;
+};
+
+enum {
+	/* maximal number of branches at page */
+	RTREE_MAX_FILL = (RTREE_PAGE_SIZE - sizeof(int)) /
+		sizeof(struct rtree_page_branch),
+	/* minimal number of branches at non-root page */
+	RTREE_MIN_FILL = RTREE_MAX_FILL / 2
+};
+
+struct rtree_page {
+	/* number of branches at page */
+	int n;
+	/* branches */
+	struct rtree_page_branch b[RTREE_MAX_FILL];
+};
+
+struct rtree_neighbor {
+	void *child;
+	struct rtree_neighbor *next;
+	int level;
+	sq_coord_t distance;
+};
+
+enum {
+	RTREE_NEIGHBORS_IN_PAGE = (RTREE_PAGE_SIZE - sizeof(void*)) /
+		sizeof(struct rtree_neighbor)
+};
+
+struct rtree_neighbor_page {
+	struct rtree_neighbor_page* next;
+	struct rtree_neighbor buf[RTREE_NEIGHBORS_IN_PAGE];
+};
+
+struct rtree_reinsert_list {
+	struct rtree_page *chain;
+	int level;
+};
+
+/*------------------------------------------------------------------------- */
+/* R-tree rectangle methods */
+/*------------------------------------------------------------------------- */
+
+
+void
+rtree_rect_normalize(struct rtree_rect *rect)
+{
+	for (int i = RTREE_DIMENSION; --i >= 0; ) {
+		if (rect->lower_point.coords[i] <= rect->upper_point.coords[i])
+			continue;
+		coord_t tmp = rect->lower_point.coords[i];
+		rect->lower_point.coords[i] = rect->upper_point.coords[i];
+		rect->upper_point.coords[i] = tmp;
+	}
+}
+
+void
+rtree_set2d(struct rtree_rect *rect,
+	    coord_t left, coord_t bottom, coord_t right, coord_t top)
+{
+	assert(RTREE_DIMENSION == 2);
+	rect->lower_point.coords[0] = left;
+	rect->lower_point.coords[1] = bottom;
+	rect->upper_point.coords[0] = right;
+	rect->upper_point.coords[1] = top;
+}
+
+static sq_coord_t
+rtree_rect_point_distance2(const struct rtree_rect *rect,
+			   const struct rtree_point *point)
+{
+	sq_coord_t result = 0;
+	for (int i = RTREE_DIMENSION; --i >= 0; ) {
+		if (point->coords[i] < rect->lower_point.coords[i]) {
+			sq_coord_t diff = (sq_coord_t)(point->coords[i] -
+				rect->lower_point.coords[i]);
+			result += diff * diff;
+		} else if (point->coords[i] > rect->upper_point.coords[i]) {
+			sq_coord_t diff = (sq_coord_t)(point->coords[i] -
+				rect->upper_point.coords[i]);
+			result += diff * diff;
+		}
+	}
+	return result;
+}
+
+static area_t
+rtree_rect_area(const struct rtree_rect *rect)
+{
+	area_t area = 1;
+	for (int i = RTREE_DIMENSION; --i >= 0; ) {
+		area *= rect->upper_point.coords[i] -
+			rect->lower_point.coords[i];
+	}
+	return area;
+}
+
+static void
+rtree_rect_add(struct rtree_rect *to, const struct rtree_rect *item)
+{
+	for (int i = RTREE_DIMENSION; --i >= 0; ) {
+		if (to->lower_point.coords[i] > item->lower_point.coords[i])
+			to->lower_point.coords[i] = item->lower_point.coords[i];
+		if (to->upper_point.coords[i] < item->upper_point.coords[i])
+			to->upper_point.coords[i] = item->upper_point.coords[i];
+	}
+}
+
+static coord_t
+rtree_min(coord_t a, coord_t b)
+{
+	return a < b ? a : b;
+}
+
+static coord_t
+rtree_max(coord_t a, coord_t b)
+{
+	return a > b ? a : b;
+}
+
+static struct rtree_rect
+rtree_rect_cover(const struct rtree_rect *item1,
+		 const struct rtree_rect *item2)
+{
+	struct rtree_rect res;
+	for (int i = RTREE_DIMENSION; --i >= 0; ) {
+		res.lower_point.coords[i] =
+			rtree_min(item1->lower_point.coords[i],
+				  item2->lower_point.coords[i]);
+		res.upper_point.coords[i] =
+			rtree_max(item1->upper_point.coords[i],
+				  item2->upper_point.coords[i]);
+	}
+	return res;
+}
+
+static bool
+rtree_rect_intersects_rect(const struct rtree_rect *rt1,
+			   const struct rtree_rect *rt2)
+{
+	for (int i = RTREE_DIMENSION; --i >= 0; )
+		if (rt1->lower_point.coords[i] > rt2->upper_point.coords[i] ||
+		    rt1->upper_point.coords[i] < rt2->lower_point.coords[i])
+			return false;
+	return true;
+}
+
+static bool
+rtree_rect_in_rect(const struct rtree_rect *rt1,
+		   const struct rtree_rect *rt2)
+{
+	for (int i = RTREE_DIMENSION; --i >= 0; )
+		if (rt1->lower_point.coords[i] < rt2->lower_point.coords[i] ||
+		    rt1->upper_point.coords[i] > rt2->upper_point.coords[i])
+			return false;
+	return true;
+}
+
+static bool
+rtree_rect_strict_in_rect(const struct rtree_rect *rt1,
+			  const struct rtree_rect *rt2)
+{
+	for (int i = RTREE_DIMENSION; --i >= 0; )
+		if (rt1->lower_point.coords[i] <= rt2->lower_point.coords[i] ||
+		    rt1->upper_point.coords[i] >= rt2->upper_point.coords[i])
+			return false;
+	return true;
+}
+
+static bool
+rtree_rect_holds_rect(const struct rtree_rect *rt1,
+		      const struct rtree_rect *rt2)
+{
+	return rtree_rect_in_rect(rt2, rt1);
+}
+
+static bool
+rtree_rect_strict_holds_rect(const struct rtree_rect *rt1,
+			     const struct rtree_rect *rt2)
+{
+	return rtree_rect_strict_in_rect(rt2, rt1);
+}
+
+static bool
+rtree_rect_equal_to_rect(const struct rtree_rect *rt1,
+			 const struct rtree_rect *rt2)
+{
+	for (int i = RTREE_DIMENSION; --i >= 0; )
+		if (rt1->lower_point.coords[i] != rt2->lower_point.coords[i] ||
+		    rt1->upper_point.coords[i] != rt2->upper_point.coords[i])
+			return false;
+	return true;
+}
+
+static bool
+rtree_always_true(const struct rtree_rect *rt1,
+		  const struct rtree_rect *rt2)
+{
+	(void)rt1;
+	(void)rt2;
+	return true;
+}
+
+/*------------------------------------------------------------------------- */
+/* R-tree page methods */
+/*------------------------------------------------------------------------- */
+
+static struct rtree_page *
+rtree_alloc_page(struct rtree *tree)
+{
+	return (struct rtree_page *)tree->page_alloc();
+}
+
+static void
+rtree_free_page(struct rtree *tree, struct rtree_page *page)
+{
+	tree->page_free(page);
+}
+
+static void
+set_next_reinsert_page(struct rtree_page *page, struct rtree_page *next_page)
+{
+	/* The page must be MIN_FILLed, so last branch is unused */
+	page->b[RTREE_MAX_FILL - 1].data.page = next_page;
+}
+
+struct rtree_page *
+get_next_reinsert_page(const struct rtree_page *page)
+{
+	return page->b[RTREE_MAX_FILL - 1].data.page;
+}
+
+/* Calculate cover of all rectangles at page */
+static struct rtree_rect
+rtree_page_cover(const struct rtree_page *page)
+{
+	struct rtree_rect res = page->b[0].rect;
+	for (int i = 1; i < page->n; i++)
+		rtree_rect_add(&res, &page->b[i].rect);
+	return res;
+}
+
+/* Create root page by first inserting record */
+static void
+rtree_page_init_record(struct rtree_page *page,
+		       struct rtree_rect *rect, record_t obj)
+{
+	page->n = 1;
+	page->b[0].rect = *rect;
+	page->b[0].data.record = obj;
+}
+
+/* Create root page by branch */
+static void
+rtree_page_init_branch(struct rtree_page *page,
+		       const struct rtree_page_branch *br)
+{
+	page->n = 1;
+	page->b[0] = *br;
+}
+
+/* Create new root page (root splitting) */
+static void
+rtree_page_init_page(struct rtree_page *page,
+		     struct rtree_page *page1,
+		     struct rtree_page *page2)
+{
+	page->n = 2;
+	page->b[0].rect = rtree_page_cover(page1);
+	page->b[0].data.page = page1;
+	page->b[1].rect = rtree_page_cover(page2);
+	page->b[1].data.page = page2;
+}
+
+static struct rtree_page *
+rtree_split_page(struct rtree *tree, struct rtree_page *page,
+		 const struct rtree_page_branch *br)
+{
+	area_t rect_area[RTREE_MAX_FILL + 1];
+	rect_area[0] = rtree_rect_area(&br->rect);
+	for (int i = 0; i < RTREE_MAX_FILL; i++)
+		rect_area[i + 1] = rtree_rect_area(&page->b[i].rect);
+
+	/*
+	 * As the seeds for the two groups, find two rectangles
+	 * which waste the most area if covered by a single
+	 * rectangle.
+	 */
+	int seed[2] = {-1, -1};
+	coord_t worst_waste = 0;
+	bool worst_waste_set = false;
+
+	const struct rtree_page_branch *bp = br;
+	for (int i = 0; i < RTREE_MAX_FILL; i++) {
+		for (int j = i + 1; j <= RTREE_MAX_FILL; j++) {
+			struct rtree_rect cover =
+				rtree_rect_cover(&bp->rect,
+						 &page->b[j - 1].rect);
+			coord_t waste = rtree_rect_area(&cover) -
+				rect_area[i] - rect_area[j];
+			if (!worst_waste_set) {
+				worst_waste_set = true;
+				worst_waste = waste;
+				seed[0] = i;
+				seed[1] = j;
+			}else if (waste > worst_waste) {
+				worst_waste = waste;
+				seed[0] = i;
+				seed[1] = j;
+			}
+		}
+		bp = page->b + i;
+	}
+	assert(seed[0] >= 0);
+
+	char taken[RTREE_MAX_FILL];
+	memset(taken, 0, sizeof(taken));
+	struct rtree_rect group_rect[2];
+	struct rtree_page *p = rtree_alloc_page(tree);
+	tree->n_pages++;
+
+	taken[seed[1] - 1] = 2;
+	group_rect[1] = page->b[seed[1] - 1].rect;
+
+	if (seed[0] == 0) {
+		group_rect[0] = br->rect;
+		rtree_page_init_branch(p, br);
+	} else {
+		group_rect[0] = page->b[seed[0] - 1].rect;
+		rtree_page_init_branch(p, &page->b[seed[0] - 1]);
+		page->b[seed[0] - 1] = *br;
+	}
+	area_t group_area[2] = {rect_area[seed[0]], rect_area[seed[1]]};
+	int group_card[2] = {1, 1};
+
+	/*
+	 * Split remaining rectangles between two groups.
+	 * The one chosen is the one with the greatest difference in area
+	 * expansion depending on which group - the rect most strongly
+	 * attracted to one group and repelled from the other.
+	 */
+	while (group_card[0] + group_card[1] < RTREE_MAX_FILL + 1
+	       && group_card[0] < RTREE_MAX_FILL + 1 - RTREE_MIN_FILL
+	       && group_card[1] < RTREE_MAX_FILL + 1 - RTREE_MIN_FILL)
+	{
+		int better_group = -1, chosen = -1;
+		area_t biggest_diff = -1;
+		for (int i = 0; i < RTREE_MAX_FILL; i++) {
+			if (taken[i])
+				continue;
+			struct rtree_rect cover0 =
+				rtree_rect_cover(&group_rect[0],
+						 &page->b[i].rect);
+			struct rtree_rect cover1 =
+				rtree_rect_cover(&group_rect[1],
+						 &page->b[i].rect);
+			area_t diff = rtree_rect_area(&cover0) - group_area[0]
+				- (rtree_rect_area(&cover1) - group_area[1]);
+			if (diff > biggest_diff || -diff > biggest_diff) {
+				chosen = i;
+				if (diff < 0) {
+					better_group = 0;
+					biggest_diff = -diff;
+				} else {
+					better_group = 1;
+					biggest_diff = diff;
+				}
+			}
+		}
+		assert(chosen >= 0);
+		group_card[better_group]++;
+		rtree_rect_add(&group_rect[better_group],
+			       &page->b[chosen].rect);
+		group_area[better_group] =
+			rtree_rect_area(&group_rect[better_group]);
+		taken[chosen] = better_group + 1;
+		if (better_group == 0)
+			p->b[group_card[0] - 1] = page->b[chosen];
+	}
+	/*
+	 * If one group gets too full, then remaining rectangle
+	 * are split between two groups in such way to balance
+	 * CARDs of two groups.
+	 */
+	if (group_card[0] + group_card[1] < RTREE_MAX_FILL + 1) {
+		for (int i = 0; i < RTREE_MAX_FILL; i++) {
+			if (taken[i])
+				continue;
+			if (group_card[0] >= group_card[1]) {
+				taken[i] = 2;
+				group_card[1] += 1;
+			} else {
+				taken[i] = 1;
+				p->b[group_card[0]++] = page->b[i];
+			}
+		}
+	}
+	p->n = group_card[0];
+	page->n = group_card[1];
+	for (int i = 0, j = 0; i < page->n; j++) {
+		if (taken[j] == 2)
+			page->b[i++] = page->b[j];
+	}
+	return p;
+}
+
+static struct rtree_page*
+rtree_page_add_branch(struct rtree *tree, struct rtree_page *page,
+		      const struct rtree_page_branch *br)
+{
+	if (page->n < RTREE_MAX_FILL) {
+		page->b[page->n++] = *br;
+		return NULL;
+	} else {
+		return rtree_split_page(tree, page, br);
+	}
+}
+
+static void
+rtree_page_remove_branch(struct rtree_page *page, int i)
+{
+	page->n -= 1;
+	memmove(page->b + i, page->b + i + 1,
+		(page->n - i) * sizeof(struct rtree_page_branch));
+}
+
+static struct rtree_page *
+rtree_page_insert(struct rtree *tree, struct rtree_page *page,
+		  const struct rtree_rect *rect, record_t obj, int level)
+{
+	struct rtree_page_branch br;
+	if (--level != 0) {
+		/* not a leaf page */
+		int mini = -1;
+		area_t min_incr, best_area;
+		for (int i = 0; i < page->n; i++) {
+			area_t r_area = rtree_rect_area(&page->b[i].rect);
+			struct rtree_rect cover =
+				rtree_rect_cover(&page->b[i].rect, rect);
+			area_t incr = rtree_rect_area(&cover) - r_area;
+			assert(incr >= 0);
+			if (i == 0) {
+				best_area = r_area;
+				min_incr = incr;
+				mini = i;
+			} else if (incr < min_incr) {
+				best_area = r_area;
+				min_incr = incr;
+				mini = i;
+			} else if (incr == min_incr && r_area < best_area) {
+				best_area = r_area;
+				mini = i;
+			}
+		}
+		assert(mini >= 0);
+		struct rtree_page *p = page->b[mini].data.page;
+		struct rtree_page *q = rtree_page_insert(tree, p,
+							 rect, obj, level);
+		if (q == NULL) {
+			/* child was not split */
+			rtree_rect_add(&page->b[mini].rect, rect);
+			return NULL;
+		} else {
+			/* child was split */
+			page->b[mini].rect = rtree_page_cover(p);
+			br.data.page = q;
+			br.rect = rtree_page_cover(q);
+			return rtree_page_add_branch(tree, page, &br);
+		}
+	} else {
+		br.data.record = obj;
+		br.rect = *rect;
+		return rtree_page_add_branch(tree, page, &br);
+	}
+}
+
+static bool
+rtree_page_remove(struct rtree *tree, struct rtree_page *page,
+		  const struct rtree_rect *rect, record_t obj,
+		  int level, struct rtree_reinsert_list *rlist)
+{
+	if (--level != 0) {
+		for (int i = 0; i < page->n; i++) {
+			if (!rtree_rect_intersects_rect(&page->b[i].rect, rect))
+				continue;
+			struct rtree_page *next_page = page->b[i].data.page;
+			if (!rtree_page_remove(tree, next_page, rect,
+					       obj, level, rlist))
+				continue;
+			if (next_page->n >= RTREE_MIN_FILL) {
+				page->b[i].rect =
+					rtree_page_cover(next_page);
+			} else {
+				/* not enough entries in child */
+				set_next_reinsert_page(next_page, rlist->chain);
+				rlist->chain = next_page;
+				rlist->level = level - 1;
+				rtree_page_remove_branch(page, i);
+			}
+			return true;
+		}
+	} else {
+		for (int i = 0; i < page->n; i++) {
+			if (page->b[i].data.page == obj) {
+				rtree_page_remove_branch(page, i);
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+static void
+rtree_page_purge(struct rtree *tree, struct rtree_page *page, int level)
+{
+	if (--level != 0) { /* this is an internal node in the tree */
+		for (int i = 0; i < page->n; i++)
+			rtree_page_purge(tree, page->b[i].data.page, level);
+	}
+	tree->page_free(page);
+}
+
+/*------------------------------------------------------------------------- */
+/* R-tree iterator methods */
+/*------------------------------------------------------------------------- */
+
+static bool
+rtree_iterator_goto_first(struct rtree_iterator *itr, int sp, struct rtree_page* pg)
+{
+	if (sp + 1 == itr->tree->height) {
+		for (int i = 0, n = pg->n; i < n; i++) {
+			if (itr->leaf_cmp(&itr->rect, &pg->b[i].rect)) {
+				itr->stack[sp].page = pg;
+				itr->stack[sp].pos = i;
+				return true;
+			}
+		}
+	} else {
+		for (int i = 0, n = pg->n; i < n; i++) {
+			if (itr->intr_cmp(&itr->rect, &pg->b[i].rect)
+			    && rtree_iterator_goto_first(itr, sp + 1,
+							 pg->b[i].data.page))
+			{
+				itr->stack[sp].page = pg;
+				itr->stack[sp].pos = i;
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+
+static bool
+rtree_iterator_goto_next(struct rtree_iterator *itr, int sp)
+{
+	struct rtree_page *pg = itr->stack[sp].page;
+	if (sp + 1 == itr->tree->height) {
+		for (int i = itr->stack[sp].pos, n = pg->n; ++i < n;) {
+			if (itr->leaf_cmp(&itr->rect, &pg->b[i].rect)) {
+				itr->stack[sp].pos = i;
+				return true;
+			}
+		}
+	} else {
+		for (int i = itr->stack[sp].pos, n = pg->n; ++i < n;) {
+			if (itr->intr_cmp(&itr->rect, &pg->b[i].rect)
+			    && rtree_iterator_goto_first(itr, sp + 1,
+							 pg->b[i].data.page))
+			{
+				itr->stack[sp].page = pg;
+				itr->stack[sp].pos = i;
+				return true;
+			}
+		}
+	}
+	return sp > 0 ? rtree_iterator_goto_next(itr, sp - 1) : false;
+}
+
+void
+rtree_iterator_destroy(struct rtree_iterator *itr)
+{
+	struct rtree_neighbor_page *curr, *next;
+	for (curr = itr->page_list; curr != NULL; curr = next) {
+		next = curr->next;
+		itr->tree->page_free(curr);
+	}
+	itr->page_list = NULL;
+	itr->page_pos = RTREE_NEIGHBORS_IN_PAGE;
+}
+
+static void
+rtree_iterator_reset(struct rtree_iterator *itr)
+{
+	if (itr->neigh_list != NULL) {
+		struct rtree_neighbor **npp = &itr->neigh_free_list;
+		while (*npp != NULL) {
+			npp = &(*npp)->next;
+		}
+		*npp = itr->neigh_list;
+		itr->neigh_list = NULL;
+	}
+}
+
+static struct rtree_neighbor *
+rtree_iterator_allocate_neighbour(struct rtree_iterator *itr)
+{
+	if (itr->page_pos >= RTREE_NEIGHBORS_IN_PAGE) {
+		struct rtree_neighbor_page *new_page =
+			(struct rtree_neighbor_page *)itr->tree->page_alloc();
+		new_page->next = itr->page_list;
+		itr->page_list = new_page;
+		itr->page_pos = 0;
+	}
+	return itr->page_list->buf + itr->page_pos++;
+}
+
+static struct rtree_neighbor *
+rtree_iterator_new_neighbor(struct rtree_iterator *itr,
+			    void *child, sq_coord_t distance, int level)
+{
+	struct rtree_neighbor *n = itr->neigh_free_list;
+	if (n == NULL)
+		n = rtree_iterator_allocate_neighbour(itr);
+	else
+		itr->neigh_free_list = n->next;
+	n->child = child;
+	n->distance = distance;
+	n->level = level;
+	n->next = NULL;
+	return n;
+}
+
+static void
+rtree_iterator_free_neighbor(struct rtree_iterator *itr,
+			     struct rtree_neighbor *n)
+{
+	n->next = itr->neigh_free_list;
+	itr->neigh_free_list = n;
+}
+
+void
+rtree_iterator_init(struct rtree_iterator *itr)
+{
+	itr->neigh_list = NULL;
+	itr->neigh_free_list = NULL;
+	itr->page_list = NULL;
+	itr->page_pos = RTREE_NEIGHBORS_IN_PAGE;
+}
+
+static void
+rtree_iterator_insert_neighbor(struct rtree_iterator *itr,
+			       struct rtree_neighbor *node)
+{
+	struct rtree_neighbor *prev = NULL, *next = itr->neigh_list;
+	sq_coord_t distance = node->distance;
+	while (next != NULL && next->distance < distance) {
+		prev = next;
+		next = prev->next;
+	}
+	node->next = next;
+	if (prev == NULL)
+		itr->neigh_list = node;
+	else
+		prev->next = node;
+}
+
+record_t
+rtree_iterator_next(struct rtree_iterator *itr)
+{
+	if (itr->version != itr->tree->version) {
+		/* Index was updated since cursor initialziation */
+		return NULL;
+	}
+	if (itr->op == SOP_NEIGHBOR) {
+		/* To return element in order of increasing distance from
+		 * specified point, we build sorted list of R-Tree items
+		 * (ordered by distance from specified point) starting from
+		 * root page.
+		 * Algorithm is the following:
+		 *
+		 * insert root R-Tree page in the sorted list
+		 * while sorted list is not empty:
+		 *      get top element from the sorted list
+		 *      if it is tree leaf (record) then return it as
+		 *      current element
+		 *      otherwise (R-Tree page)  get siblings of this R-Tree
+		 *      page and insert them in sorted list
+		*/
+		while (true) {
+			struct rtree_neighbor *neighbor = itr->neigh_list;
+			if (neighbor == NULL)
+				return NULL;
+			void *child = neighbor->child;
+			int level = neighbor->level;
+			itr->neigh_list = neighbor->next;
+			rtree_iterator_free_neighbor(itr, neighbor);
+			if (level == 0)
+				return (record_t)child;
+			struct rtree_page *pg = (struct rtree_page *)child;
+			for (int i = 0, n = pg->n; i < n; i++) {
+				struct rtree_page *pg =
+					(struct rtree_page *)child;
+				coord_t distance =
+					rtree_rect_point_distance2(&pg->b[i].rect,
+								   &itr->rect.lower_point);
+				struct rtree_neighbor *neigh =
+					rtree_iterator_new_neighbor(itr, pg->b[i].data.page,
+								    distance, level - 1);
+				rtree_iterator_insert_neighbor(itr, neigh);
+			}
+		}
+	}
+	int sp = itr->tree->height - 1;
+	if (!itr->eof && rtree_iterator_goto_next(itr, sp))
+		return itr->stack[sp].page->b[itr->stack[sp].pos].data.record;
+	itr->eof = true;
+	return NULL;
+}
+
+/*------------------------------------------------------------------------- */
+/* R-tree methods */
+/*------------------------------------------------------------------------- */
+
+void
+rtree_init(struct rtree *tree,
+	   rtree_page_alloc_t page_alloc, rtree_page_free_t page_free)
+{
+	tree->n_records = 0;
+	tree->height = 0;
+	tree->root = NULL;
+	tree->version = 0;
+	tree->n_pages = 0;
+	tree->page_alloc = page_alloc;
+	tree->page_free = page_free;
+}
+
+void
+rtree_destroy(struct rtree *tree)
+{
+	rtree_purge(tree);
+}
+
+void
+rtree_insert(struct rtree *tree, struct rtree_rect *rect, record_t obj)
+{
+	if (tree->root == NULL) {
+		tree->root = rtree_alloc_page(tree);
+		rtree_page_init_record(tree->root, rect, obj);
+		tree->height = 1;
+		tree->n_pages++;
+	} else {
+		struct rtree_page *p =
+			rtree_page_insert(tree, tree->root, rect, obj, tree->height);
+		if (p != NULL) {
+			/* root splitted */
+			struct rtree_page *new_root = rtree_alloc_page(tree);
+			rtree_page_init_page(new_root, tree->root, p);
+			tree->root = new_root;
+			tree->height++;
+			tree->n_pages++;
+		}
+	}
+	tree->version++;
+	tree->n_records++;
+}
+
+
+bool
+rtree_remove(struct rtree *tree, const struct rtree_rect *rect, record_t obj)
+{
+	struct rtree_reinsert_list rlist;
+	rlist.chain = NULL;
+	if (tree->height == 0)
+		return false;
+	if (!rtree_page_remove(tree, tree->root, rect, obj, tree->height, &rlist))
+		return false;
+	struct rtree_page *pg = rlist.chain;
+	int level = rlist.level;
+	while (pg != NULL) {
+		for (int i = 0, n = pg->n; i < n; i++) {
+			struct rtree_page *p =
+				rtree_page_insert(tree, tree->root,
+						  &pg->b[i].rect,
+						  pg->b[i].data.record,
+						  tree->height - level);
+			if (p != NULL) {
+				/* root splitted */
+				struct rtree_page *new_root
+					= rtree_alloc_page(tree);
+				rtree_page_init_page(new_root, tree->root, p);
+				tree->root = new_root;
+				tree->height++;
+				tree->n_pages++;
+			}
+		}
+		level--;
+		struct rtree_page *next = get_next_reinsert_page(pg);
+		rtree_free_page(tree, pg);
+		tree->n_pages--;
+		pg = next;
+	}
+	if (tree->root->n == 1 && tree->height > 1) {
+		struct rtree_page *new_root = tree->root->b[0].data.page;
+		rtree_free_page(tree, tree->root);
+		tree->root = new_root;
+		tree->height--;
+		tree->n_pages--;
+	}
+	tree->n_records--;
+	tree->version++;
+	return true;
+}
+
+bool
+rtree_search(const struct rtree *tree, const struct rtree_rect *rect,
+	     enum spatial_search_op op, struct rtree_iterator *itr)
+{
+	rtree_iterator_reset(itr);
+	itr->tree = tree;
+	itr->version = tree->version;
+	itr->rect = *rect;
+	itr->op = op;
+	assert(tree->height <= RTREE_MAX_HEIGHT);
+	switch (op) {
+	case SOP_ALL:
+		itr->intr_cmp = itr->leaf_cmp = rtree_always_true;
+		break;
+	case SOP_EQUALS:
+		itr->intr_cmp = rtree_rect_in_rect;
+		itr->leaf_cmp = rtree_rect_equal_to_rect;
+		break;
+	case SOP_CONTAINS:
+		itr->intr_cmp = itr->leaf_cmp = rtree_rect_in_rect;
+		break;
+	case SOP_STRICT_CONTAINS:
+		itr->intr_cmp = itr->leaf_cmp = rtree_rect_strict_in_rect;
+		break;
+	case SOP_OVERLAPS:
+		itr->intr_cmp = itr->leaf_cmp = rtree_rect_intersects_rect;
+		break;
+	case SOP_BELONGS:
+		itr->intr_cmp = rtree_rect_intersects_rect;
+		itr->leaf_cmp = rtree_rect_holds_rect;
+		break;
+	case SOP_STRICT_BELONGS:
+		itr->intr_cmp = rtree_rect_intersects_rect;
+		itr->leaf_cmp = rtree_rect_strict_holds_rect;
+		break;
+	case SOP_NEIGHBOR:
+		if (tree->root) {
+			struct rtree_rect cover = rtree_page_cover(tree->root);
+			sq_coord_t distance =
+				rtree_rect_point_distance2(&cover,
+							   &rect->lower_point);
+			itr->neigh_list =
+				rtree_iterator_new_neighbor(itr, tree->root,
+							    distance,
+							    tree->height);
+			return true;
+		} else {
+			itr->neigh_list = NULL;
+			return false;
+		}
+	}
+	if (tree->root && rtree_iterator_goto_first(itr, 0, tree->root)) {
+		itr->stack[tree->height-1].pos -= 1;
+		/* will be incremented by goto_next */
+		itr->eof = false;
+		return true;
+	} else {
+		itr->eof = true;
+		return false;
+	}
+}
+
+void
+rtree_purge(struct rtree *tree)
+{
+	if (tree->root != NULL) {
+		rtree_page_purge(tree, tree->root, tree->height);
+		tree->root = NULL;
+		tree->n_records = 0;
+		tree->n_pages = 0;
+		tree->height = 0;
+	}
+}
+
+size_t
+rtree_used_size(const struct rtree *tree)
+{
+	return tree->n_pages * RTREE_PAGE_SIZE;
+}
+
+unsigned
+rtree_number_of_records(const struct rtree *tree) {
+	return tree->n_records;
+}
+
+#if 0
+void
+rtree_debug_print_page(const struct rtree_page *page, unsigned level, unsigned path)
+{
+	printf("%d:", path);
+	if (--level) {
+		for (int i = 0; i < page->n; i++) {
+			printf(" [");
+			for (int j = 0; j < RTREE_DIMENSION; j++)
+				printf("%lg ", (double)page->b[i].rect.lower_point.coords[j]);
+			for (int j = 0; j < RTREE_DIMENSION; j++)
+				printf("%lg ", (double)page->b[i].rect.upper_point.coords[j]);
+			printf("]");
+		}
+		printf("\n");
+		for (int i = 0; i < page->n; i++)
+			rtree_debug_print_page(page->b[i].data.page, level, path * 100 + i);
+	} else {
+		for (int i = 0; i < page->n; i++) {
+			printf(" [");
+			for (int j = 0; j < RTREE_DIMENSION; j++)
+				printf("%lg ", (double)page->b[i].rect.lower_point.coords[j]);
+			for (int j = 0; j < RTREE_DIMENSION; j++)
+				printf("%lg ", (double)page->b[i].rect.upper_point.coords[j]);
+			printf(": %p]", (void *)page->b[i].data.record);
+		}
+		printf("\n");
+	}
+}
+
+void
+rtree_debug_print(const struct rtree *tree)
+{
+	if (tree->root)
+		rtree_debug_print_page(tree->root, tree->height, 0);
+}
+#endif
+
diff --git a/src/lib/salad/rtree.h b/src/lib/salad/rtree.h
new file mode 100644
index 0000000000000000000000000000000000000000..6eb8a3eb1fc8262afc5cd2d2baa1e35c9fb95649
--- /dev/null
+++ b/src/lib/salad/rtree.h
@@ -0,0 +1,205 @@
+#ifndef INCLUDES_TARANTOOL_SALAD_RTREE_H
+#define INCLUDES_TARANTOOL_SALAD_RTREE_H
+/*
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <stddef.h>
+#include <stdbool.h>
+
+/**
+ * In-memory Guttman's R-tree
+ */
+
+/* Type of payload data */
+typedef void *record_t;
+/* Type of coordinate */
+typedef double coord_t;
+/* Type of square coordinate */
+typedef double sq_coord_t;
+/* Type of area (volume) of rectangle (box) */
+typedef double area_t;
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+enum {
+	/** Number of dimensions of R-tree geometry */
+	RTREE_DIMENSION = 2,
+	/** Maximal possible R-tree height */
+	RTREE_MAX_HEIGHT = 16,
+	/**
+	 * R-Tree uses linear search for elements on a page,
+	 * so a larger page size can hurt performance.
+	 */
+	RTREE_PAGE_SIZE = 1024
+};
+
+enum spatial_search_op
+{
+	SOP_ALL,
+	SOP_EQUALS,
+	SOP_CONTAINS,
+	SOP_STRICT_CONTAINS,
+	SOP_OVERLAPS,
+	SOP_BELONGS,
+	SOP_STRICT_BELONGS,
+	SOP_NEIGHBOR
+};
+
+/* pointers to page allocation and deallocations functions */
+typedef void *(*rtree_page_alloc_t)();
+typedef void (*rtree_page_free_t)(void *);
+
+/* A point in RTREE_DIMENSION space */
+struct rtree_point
+{
+	/* coordinates of the point */
+	coord_t coords[RTREE_DIMENSION];
+};
+
+/* A box in RTREE_DIMENSION space */
+struct rtree_rect
+{
+	/* vertex with minimal coordinates */
+	struct rtree_point lower_point;
+	/* vertex with maximal coordinates (diagonal to lower_point) */
+	struct rtree_point upper_point;
+};
+
+/* Type of function, comparing two rectangles */
+typedef bool (*rtree_comparator_t)(const struct rtree_rect *rt1,
+				   const struct rtree_rect *rt2);
+
+/* Main rtree struct */
+struct rtree
+{
+	/* Root node (page) */
+	struct rtree_page *root;
+	/* Number of records in entire tree */
+	unsigned n_records;
+	/* Height of a tree */
+	unsigned height;
+	/* Unique version that increments on every tree modification */
+	unsigned version;
+	/* Number of allocated (used) pages */
+	unsigned n_pages;
+	/* Function for allocation new pages */
+	rtree_page_alloc_t page_alloc;
+	/* Function for deallocation new pages */
+	rtree_page_free_t page_free;
+};
+
+/* Struct for iteration and retrieving rtree values */
+struct rtree_iterator
+{
+	/* Pointer to rtree */
+	const struct rtree *tree;
+	/* Rectangle of current iteration operation */
+	struct rtree_rect rect;
+	/* Type of current iteration operation */
+	enum spatial_search_op op;
+	/* Flag that means that no more values left */
+	bool eof;
+	/* A verion of a tree when the iterator was created */
+	int version;
+
+	/* Special single-linked list of closest neqighbors
+	 * Used only for iteration with op = SOP_NEIGHBOR
+	 * For allocating list entries, page allocator of tree is used.
+	 * Allocated page is much bigger than list entry and thus
+	 * provides several list entries.
+	 */
+	struct rtree_neighbor *neigh_list;
+	/* List of unused (deleted) list entries */
+	struct rtree_neighbor *neigh_free_list;
+	/* List of tree pages, allocated for list entries */
+	struct rtree_neighbor_page *page_list;
+	/* Position of ready-to-use list entry in allocated page */
+	int page_pos;
+
+	rtree_comparator_t intr_cmp;
+	rtree_comparator_t leaf_cmp;
+
+	struct {
+		struct rtree_page *page;
+		int pos;
+	} stack[RTREE_MAX_HEIGHT];
+};
+
+void
+rtree_rect_normalize(struct rtree_rect *rect);
+
+void
+rtree_set2d(struct rtree_rect *rect,
+	    coord_t left, coord_t bottom, coord_t right, coord_t top);
+
+void
+rtree_init(struct rtree *tree, rtree_page_alloc_t page_alloc, rtree_page_free_t page_free);
+
+void
+rtree_destroy(struct rtree *tree);
+
+void
+rtree_purge(struct rtree *tree);
+
+bool
+rtree_search(const struct rtree *tree, const struct rtree_rect *rect,
+	     enum spatial_search_op op, struct rtree_iterator *itr);
+
+void
+rtree_insert(struct rtree *tree, struct rtree_rect *rect, record_t obj);
+
+bool
+rtree_remove(struct rtree *tree, const struct rtree_rect *rect, record_t obj);
+
+size_t
+rtree_used_size(const struct rtree *tree);
+
+unsigned
+rtree_number_of_records(const struct rtree *tree);
+
+#if 0
+void
+rtree_debug_print(const struct rtree *tree);
+#endif
+
+void
+rtree_iterator_init(struct rtree_iterator *itr);
+
+void
+rtree_iterator_destroy(struct rtree_iterator *itr);
+
+record_t
+rtree_iterator_next(struct rtree_iterator *itr);
+
+#if defined(__cplusplus)
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* #ifndef INCLUDES_TARANTOOL_SALAD_RTREE_H */
diff --git a/src/lua/init.cc b/src/lua/init.cc
index 667cfbc314d010f6ec51eaabe6726bd3876c381a..7a39e454b74f91d309c49e3dc87283f63141078c 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -30,7 +30,6 @@
 #include "lua/utils.h"
 #include "tarantool.h"
 #include "box/box.h"
-#include "tbuf.h"
 #if defined(__FreeBSD__) || defined(__APPLE__)
 #include "libgen.h"
 #endif
diff --git a/src/lua/msgpack.cc b/src/lua/msgpack.cc
index b260e095ecb0e83d3b8dff619f86113dc14c9091..747258c242bade123e7e3a1a92b32fc3b53a1af8 100644
--- a/src/lua/msgpack.cc
+++ b/src/lua/msgpack.cc
@@ -39,14 +39,14 @@ extern "C" {
 } /* extern "C" */
 
 #include <msgpuck/msgpuck.h>
-#include <tbuf.h>
+#include <iobuf.h>
 #include <fiber.h>
 #include "small/region.h"
 
 struct luaL_serializer *luaL_msgpack_default = NULL;
 
 static int
-luamp_encode_extension_default(struct lua_State *L, int idx, struct tbuf *buf);
+luamp_encode_extension_default(struct lua_State *L, int idx, struct obuf *buf);
 
 static void
 luamp_decode_extension_default(struct lua_State *L, const char **data);
@@ -57,115 +57,98 @@ static luamp_decode_extension_f luamp_decode_extension =
 		luamp_decode_extension_default;
 
 void
-luamp_encode_array(struct luaL_serializer *cfg, struct tbuf *buf, uint32_t size)
+luamp_encode_array(struct luaL_serializer *cfg, struct obuf *buf, uint32_t size)
 {
 	(void) cfg;
 	assert(mp_sizeof_array(size) <= 5);
-	tbuf_ensure(buf, 5 + size);
-	char *data = mp_encode_array(buf->data + buf->size, size);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 5);
+	char *pos = mp_encode_array(data, size);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_map(struct luaL_serializer *cfg, struct tbuf *buf, uint32_t size)
+luamp_encode_map(struct luaL_serializer *cfg, struct obuf *buf, uint32_t size)
 {
 	(void) cfg;
 	assert(mp_sizeof_map(size) <= 5);
-	tbuf_ensure(buf, 5 + size);
-
-	char *data = mp_encode_map(buf->data + buf->size, size);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 5);
+	char *pos = mp_encode_map(data, size);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_uint(struct luaL_serializer *cfg, struct tbuf *buf, uint64_t num)
+luamp_encode_uint(struct luaL_serializer *cfg, struct obuf *buf, uint64_t num)
 {
 	(void) cfg;
 	assert(mp_sizeof_uint(num) <= 9);
-	tbuf_ensure(buf, 9);
-
-	char *data = mp_encode_uint(buf->data + buf->size, num);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 9);
+	char *pos = mp_encode_uint(data, num);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_int(struct luaL_serializer *cfg, struct tbuf *buf, int64_t num)
+luamp_encode_int(struct luaL_serializer *cfg, struct obuf *buf, int64_t num)
 {
 	(void) cfg;
 	assert(mp_sizeof_int(num) <= 9);
-	tbuf_ensure(buf, 9);
-
-	char *data = mp_encode_int(buf->data + buf->size, num);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 9);
+	char *pos = mp_encode_int(data, num);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_float(struct luaL_serializer *cfg, struct tbuf *buf, float num)
+luamp_encode_float(struct luaL_serializer *cfg, struct obuf *buf, float num)
 {
 	(void) cfg;
 	assert(mp_sizeof_float(num) <= 5);
-	tbuf_ensure(buf, 5);
-
-	char *data = mp_encode_float(buf->data + buf->size, num);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 5);
+	char *pos = mp_encode_float(data, num);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_double(struct luaL_serializer *cfg, struct tbuf *buf, double num)
+luamp_encode_double(struct luaL_serializer *cfg, struct obuf *buf, double num)
 {
 	(void) cfg;
 	assert(mp_sizeof_double(num) <= 9);
-	tbuf_ensure(buf, 9);
-
-	char *data = mp_encode_double(buf->data + buf->size, num);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 9);
+	char *pos = mp_encode_double(data, num);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_str(struct luaL_serializer *cfg, struct tbuf *buf,
+luamp_encode_str(struct luaL_serializer *cfg, struct obuf *buf,
 		 const char *str, uint32_t len)
 {
 	(void) cfg;
 	assert(mp_sizeof_str(len) <= 5 + len);
-	tbuf_ensure(buf, 5 + len);
-
-	char *data = mp_encode_str(buf->data + buf->size, str, len);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 5 + len);
+	char *pos = mp_encode_str(data, str, len);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_nil(struct luaL_serializer *cfg, struct tbuf *buf)
+luamp_encode_nil(struct luaL_serializer *cfg, struct obuf *buf)
 {
 	(void) cfg;
 	assert(mp_sizeof_nil() <= 1);
-	tbuf_ensure(buf, 1);
-
-	char *data = mp_encode_nil(buf->data + buf->size);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 1);
+	char *pos = mp_encode_nil(data);
+	obuf_advance(buf, pos - data);
 }
 
 void
-luamp_encode_bool(struct luaL_serializer *cfg, struct tbuf *buf, bool val)
+luamp_encode_bool(struct luaL_serializer *cfg, struct obuf *buf, bool val)
 {
 	(void) cfg;
 	assert(mp_sizeof_bool(val) <= 1);
-	tbuf_ensure(buf, 1);
-
-	char *data = mp_encode_bool(buf->data + buf->size, val);
-	assert(data <= buf->data + buf->capacity);
-	buf->size = data - buf->data;
+	char *data = obuf_ensure(buf, 1);
+	char *pos = mp_encode_bool(data, val);
+	obuf_advance(buf, pos - data);
 }
 
 static int
-luamp_encode_extension_default(struct lua_State *L, int idx, struct tbuf *b)
+luamp_encode_extension_default(struct lua_State *L, int idx, struct obuf *b)
 {
 	(void) L;
 	(void) idx;
@@ -202,7 +185,7 @@ luamp_set_decode_extension(luamp_decode_extension_f handler)
 }
 
 static void
-luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, struct tbuf *b,
+luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, struct obuf *b,
 	       int level)
 {
 	int index = lua_gettop(L);
@@ -266,7 +249,7 @@ luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, struct tbuf *b,
 }
 
 void
-luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, struct tbuf *b,
+luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, struct obuf *b,
 	     int index)
 {
 	int top = lua_gettop(L);
@@ -370,9 +353,13 @@ lua_msgpack_encode(lua_State *L)
 	struct luaL_serializer *cfg = luaL_checkserializer(L);
 
 	RegionGuard region_guard(&fiber()->gc);
-	struct tbuf *buf = tbuf_new(&fiber()->gc);
-	luamp_encode_r(L, cfg, buf, 0);
-	lua_pushlstring(L, buf->data, buf->size);
+
+	struct obuf buf;
+	obuf_create(&buf, &fiber()->gc, LUAMP_ALLOC_FACTOR);
+	luamp_encode_r(L, cfg, &buf, 0);
+
+	const char *res = obuf_join(&buf);
+	lua_pushlstring(L, res, obuf_size(&buf));
 	return 1;
 }
 
diff --git a/src/lua/msgpack.h b/src/lua/msgpack.h
index 9ed18abbd19914372b68e3d3cbbffe4595b8827c..c9b6cdd107f2965753a37a78068ba88820f1598f 100644
--- a/src/lua/msgpack.h
+++ b/src/lua/msgpack.h
@@ -48,38 +48,40 @@ extern "C" {
  */
 extern luaL_serializer *luaL_msgpack_default;
 
-struct tbuf;
+struct obuf;
+
+enum { LUAMP_ALLOC_FACTOR = 256 };
 
 void
-luamp_encode_array(struct luaL_serializer *cfg, struct tbuf *buf, uint32_t size);
+luamp_encode_array(struct luaL_serializer *cfg, struct obuf *buf, uint32_t size);
 
 void
-luamp_encode_map(struct luaL_serializer *cfg, struct tbuf *buf, uint32_t size);
+luamp_encode_map(struct luaL_serializer *cfg, struct obuf *buf, uint32_t size);
 
 void
-luamp_encode_uint(struct luaL_serializer *cfg, struct tbuf *buf, uint64_t num);
+luamp_encode_uint(struct luaL_serializer *cfg, struct obuf *buf, uint64_t num);
 
 void
-luamp_encode_int(struct luaL_serializer *cfg, struct tbuf *buf, int64_t num);
+luamp_encode_int(struct luaL_serializer *cfg, struct obuf *buf, int64_t num);
 
 void
-luamp_encode_float(struct luaL_serializer *cfg, struct tbuf *buf, float num);
+luamp_encode_float(struct luaL_serializer *cfg, struct obuf *buf, float num);
 
 void
-luamp_encode_double(struct luaL_serializer *cfg, struct tbuf *buf, double num);
+luamp_encode_double(struct luaL_serializer *cfg, struct obuf *buf, double num);
 
 void
-luamp_encode_str(struct luaL_serializer *cfg, struct tbuf *buf, const char *str,
+luamp_encode_str(struct luaL_serializer *cfg, struct obuf *buf, const char *str,
 		 uint32_t len);
 
 void
 luamp_encode_nil(struct luaL_serializer *cfg);
 
 void
-luamp_encode_bool(struct luaL_serializer *cfg, struct tbuf *buf, bool val);
+luamp_encode_bool(struct luaL_serializer *cfg, struct obuf *buf, bool val);
 
 void
-luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, struct tbuf *buf,
+luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, struct obuf *buf,
 	     int index);
 
 void
@@ -87,7 +89,7 @@ luamp_decode(struct lua_State *L, struct luaL_serializer *cfg,
 	     const char **data);
 
 typedef int
-(*luamp_encode_extension_f)(struct lua_State *, int, struct tbuf *);
+(*luamp_encode_extension_f)(struct lua_State *, int, struct obuf *);
 
 /**
  * @brief Set a callback that executed by encoder on unsupported Lua type
diff --git a/src/lua/pickle.cc b/src/lua/pickle.cc
index 15c7dcc2ecc69b58c34285b1c4a72906e916d944..5b7d5e0af449359a22d9f2a9a5cec786aa96b26c 100644
--- a/src/lua/pickle.cc
+++ b/src/lua/pickle.cc
@@ -39,8 +39,8 @@ extern "C" {
 
 #include "lua/utils.h"
 #include "lua/msgpack.h" /* luaL_msgpack_default */
-#include <tbuf.h>
 #include <fiber.h>
+#include "iobuf.h"
 #include "bit/bit.h"
 
 static int
@@ -54,7 +54,8 @@ lbox_pack(struct lua_State *L)
 	const char *str;
 
 	RegionGuard region_guard(&fiber()->gc);
-	struct tbuf *b = tbuf_new(&fiber()->gc);
+	struct obuf buf;
+	obuf_create(&buf, &fiber()->gc, LUAMP_ALLOC_FACTOR);
 
 	struct luaL_field field;
 	double dbl;
@@ -71,7 +72,7 @@ lbox_pack(struct lua_State *L)
 			if (field.type != MP_UINT && field.type != MP_INT)
 				luaL_error(L, "pickle.pack: expected 8-bit int");
 
-			tbuf_append(b, (char *) &field.ival, sizeof(uint8_t));
+			obuf_dup(&buf, &field.ival, sizeof(uint8_t));
 			break;
 		case 'S':
 		case 's':
@@ -79,7 +80,7 @@ lbox_pack(struct lua_State *L)
 			if (field.type != MP_UINT && field.type != MP_INT)
 				luaL_error(L, "pickle.pack: expected 16-bit int");
 
-			tbuf_append(b, (char *) &field.ival, sizeof(uint16_t));
+			obuf_dup(&buf, &field.ival, sizeof(uint16_t));
 			break;
 		case 'n':
 			/* signed and unsigned 16-bit big endian integers */
@@ -87,7 +88,7 @@ lbox_pack(struct lua_State *L)
 				luaL_error(L, "pickle.pack: expected 16-bit int");
 
 			field.ival = (uint16_t) htons((uint16_t) field.ival);
-			tbuf_append(b, (char *) &field.ival, sizeof(uint16_t));
+			obuf_dup(&buf, &field.ival, sizeof(uint16_t));
 			break;
 		case 'I':
 		case 'i':
@@ -95,7 +96,7 @@ lbox_pack(struct lua_State *L)
 			if (field.type != MP_UINT && field.ival != MP_INT)
 				luaL_error(L, "pickle.pack: expected 32-bit int");
 
-			tbuf_append(b, (char *) &field.ival, sizeof(uint32_t));
+			obuf_dup(&buf, &field.ival, sizeof(uint32_t));
 			break;
 		case 'N':
 			/* signed and unsigned 32-bit big endian integers */
@@ -103,7 +104,7 @@ lbox_pack(struct lua_State *L)
 				luaL_error(L, "pickle.pack: expected 32-bit int");
 
 			field.ival = htonl(field.ival);
-			tbuf_append(b, (char *) &field.ival, sizeof(uint32_t));
+			obuf_dup(&buf, &field.ival, sizeof(uint32_t));
 			break;
 		case 'L':
 		case 'l':
@@ -111,7 +112,7 @@ lbox_pack(struct lua_State *L)
 			if (field.type != MP_UINT && field.type != MP_INT)
 				luaL_error(L, "pickle.pack: expected 64-bit int");
 
-			tbuf_append(b, (char *) &field.ival, sizeof(uint64_t));
+			obuf_dup(&buf, &field.ival, sizeof(uint64_t));
 			break;
 		case 'Q':
 		case 'q':
@@ -120,21 +121,21 @@ lbox_pack(struct lua_State *L)
 				luaL_error(L, "pickle.pack: expected 64-bit int");
 
 			field.ival = bswap_u64(field.ival);
-			tbuf_append(b, (char *) &field.ival, sizeof(uint64_t));
+			obuf_dup(&buf,  &field.ival, sizeof(uint64_t));
 			break;
 		case 'd':
 			dbl = (double) lua_tonumber(L, i);
-			tbuf_append(b, (char *) &dbl, sizeof(double));
+			obuf_dup(&buf, &dbl, sizeof(double));
 			break;
 		case 'f':
 			flt = (float) lua_tonumber(L, i);
-			tbuf_append(b, (char *) &flt, sizeof(float));
+			obuf_dup(&buf, &flt, sizeof(float));
 			break;
 		case 'A':
 		case 'a':
 			/* A sequence of bytes */
 			str = luaL_checklstring(L, i, &size);
-			tbuf_append(b, str, size);
+			obuf_dup(&buf, str, size);
 			break;
 		default:
 			luaL_error(L, "pickle.pack: unsupported pack "
@@ -144,8 +145,8 @@ lbox_pack(struct lua_State *L)
 		format++;
 	}
 
-	lua_pushlstring(L, tbuf_str(b), b->size);
-
+	const char *res = obuf_join(&buf);
+	lua_pushlstring(L, res, obuf_size(&buf));
 	return 1;
 }
 
diff --git a/src/stat.cc b/src/stat.cc
index f0b8d5259540e5a7929b360a244a5a8af873622c..56f9f3a431838707ac9be56c6ebe18a5e5dd87c4 100644
--- a/src/stat.cc
+++ b/src/stat.cc
@@ -30,7 +30,6 @@
 
 #include "trivia/util.h"
 #include "fiber.h"
-#include <tbuf.h>
 #include <say.h>
 
 #include <assoc.h>
diff --git a/src/stat.h b/src/stat.h
index 26b9fdb4e1e82b5422b9eac235754d07dabe0fbc..95f0bc0bc5e3731a7eb3499110b18c99145f171f 100644
--- a/src/stat.h
+++ b/src/stat.h
@@ -28,7 +28,9 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
-#include <tbuf.h>
+
+#include <stddef.h>
+#include <stdint.h>
 
 void stat_init(void);
 void stat_free(void);
diff --git a/src/tbuf.c b/src/tbuf.c
deleted file mode 100644
index d3d792099d57505eb814cd89ea138fee3d0e1627..0000000000000000000000000000000000000000
--- a/src/tbuf.c
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-#include "tbuf.h"
-
-#include <string.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <stdbool.h>
-
-#include "lib/small/region.h"
-
-#ifdef POISON
-#  define TBUF_POISON
-#endif
-
-#ifdef TBUF_POISON
-#  define poison(ptr, len) memset((ptr), 'A', (len))
-#else
-#  define poison(ptr, len)
-#endif
-
-/** Try to make all region allocations a multiple of  this number. */
-enum { TBUF_ALLOC_FACTOR = 128 };
-
-static void
-tbuf_assert(const struct tbuf *b)
-{
-	(void)b;		/* arg used :-) */
-	assert(b->size <= b->capacity);
-}
-
-struct tbuf *
-tbuf_new(struct region *pool)
-{
-	struct tbuf *e = (struct tbuf *) region_alloc_nothrow(pool, TBUF_ALLOC_FACTOR);
-	e->size = 0;
-	e->capacity = TBUF_ALLOC_FACTOR - sizeof(struct tbuf);
-	e->data = (char *)e + sizeof(struct tbuf);
-	e->pool = pool;
-	poison(e->data, e->capacity);
-	tbuf_assert(e);
-	return e;
-}
-
-void
-tbuf_ensure_resize(struct tbuf *e, size_t required)
-{
-	tbuf_assert(e);
-
-	/* Make new capacity a multiple of alloc factor. */
-	size_t new_capacity = MAX(e->capacity, (uint32_t)TBUF_ALLOC_FACTOR) * 2;
-
-	while (new_capacity < e->size + required)
-		new_capacity *= 2;
-
-	char *p = (char *) region_alloc_nothrow(e->pool, new_capacity);
-
-	poison(p, new_capacity);
-	memcpy(p, e->data, e->size);
-	poison(e->data, e->size);
-	e->data = p;
-	e->capacity = new_capacity;
-	tbuf_assert(e);
-}
-
-struct tbuf *
-tbuf_clone(struct region *pool, const struct tbuf *orig)
-{
-	struct tbuf *clone = tbuf_new(pool);
-	tbuf_assert(orig);
-	tbuf_append(clone, orig->data, orig->size);
-	return clone;
-}
-
-struct tbuf *
-tbuf_split(struct tbuf *orig, size_t at)
-{
-	struct tbuf *head = (struct tbuf *) region_alloc_nothrow(orig->pool, sizeof(*orig));
-	assert(at <= orig->size);
-	tbuf_assert(orig);
-	head->pool = orig->pool;
-	head->data = orig->data;
-	head->size = head->capacity = at;
-	orig->data += at;
-	orig->capacity -= at;
-	orig->size -= at;
-	return head;
-}
-
-void *
-tbuf_peek(struct tbuf *b, size_t count)
-{
-	void *p = b->data;
-	tbuf_assert(b);
-	if (count <= b->size) {
-		b->data += count;
-		b->size -= count;
-		b->capacity -= count;
-		return p;
-	}
-	return NULL;
-}
-
-/** Remove first count bytes from the beginning. */
-
-void
-tbuf_ltrim(struct tbuf *b, size_t count)
-{
-	tbuf_assert(b);
-	assert(count <= b->size);
-
-	memmove(b->data, b->data + count, b->size - count);
-	b->size -= count;
-}
-
-void
-tbuf_reset(struct tbuf *b)
-{
-	tbuf_assert(b);
-	poison(b->data, b->size);
-	b->size = 0;
-}
-
-void
-tbuf_vprintf(struct tbuf *b, const char *format, va_list ap)
-{
-	int printed_len;
-	size_t free_len = b->capacity - b->size;
-	va_list ap_copy;
-
-	va_copy(ap_copy, ap);
-
-	tbuf_assert(b);
-	printed_len = vsnprintf(b->data + b->size, free_len, format, ap);
-
-	/*
-	 * if buffer too short, resize buffer and
-	 * print it again
-	 */
-	if (free_len <= printed_len) {
-		tbuf_ensure(b, printed_len + 1);
-		free_len = b->capacity - b->size - 1;
-		printed_len = vsnprintf(b->data + b->size, free_len, format, ap_copy);
-	}
-
-	b->size += printed_len;
-
-	va_end(ap_copy);
-}
-
-void
-tbuf_printf(struct tbuf *b, const char *format, ...)
-{
-	va_list args;
-
-	va_start(args, format);
-	tbuf_vprintf(b, format, args);
-	va_end(args);
-}
-
-/* for debug printing */
-char *
-tbuf_to_hex(const struct tbuf *x)
-{
-	const char *data = x->data;
-	size_t size = x->size;
-	char *out = (char *) region_alloc_nothrow(x->pool, size * 3 + 1);
-	out[size * 3] = 0;
-
-	for (int i = 0; i < size; i++) {
-		int c = *(data + i);
-		sprintf(out + i * 3, "%02x ", c);
-	}
-
-	return out;
-}
diff --git a/src/tbuf.h b/src/tbuf.h
deleted file mode 100644
index e9c972418cc20ceb3c4d417da50746feeb0e0184..0000000000000000000000000000000000000000
--- a/src/tbuf.h
+++ /dev/null
@@ -1,105 +0,0 @@
-#ifndef TARANTOOL_TBUF_H_INCLUDED
-#define TARANTOOL_TBUF_H_INCLUDED
-/*
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-#include <stdarg.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "trivia/util.h"
-
-#if defined(__cplusplus)
-extern "C" {
-#endif /* defined(__cplusplus) */
-
-struct tbuf {
-	/* Used space in the buffer. */
-	uint32_t size;
-	/* Total allocated buffer capacity. */
-	uint32_t capacity;
-	/* Allocated buffer. */
-	char *data;
-	struct region *pool;
-};
-
-struct tbuf *
-tbuf_new(struct region *pool);
-
-void tbuf_ensure_resize(struct tbuf *e, size_t bytes_required);
-static inline void tbuf_ensure(struct tbuf *e, size_t required)
-{
-	assert(e->size <= e->capacity);
-	if (unlikely(e->size + required > e->capacity))
-		tbuf_ensure_resize(e, required);
-}
-
-static inline void tbuf_append(struct tbuf *b, const void *data, size_t len)
-{
-	tbuf_ensure(b, len + 1); /* +1 for trailing '\0' */
-	memcpy(b->data + b->size, data, len);
-	b->size += len;
-	*((b->data) + b->size) = '\0';
-}
-
-static inline char *
-tbuf_str(struct tbuf *tbuf) { return tbuf->data; }
-
-static inline char *
-tbuf_end(struct tbuf *tbuf) { return tbuf->data + tbuf->size; }
-
-static inline size_t
-tbuf_unused(const struct tbuf *tbuf) { return tbuf->capacity - tbuf->size; }
-
-struct tbuf *tbuf_clone(struct region *pool, const struct tbuf *orig);
-struct tbuf *tbuf_split(struct tbuf *e, size_t at);
-void tbuf_reset(struct tbuf *b);
-void *tbuf_peek(struct tbuf *b, size_t count);
-
-/**
- * Remove count bytes from the beginning, and adjust all sizes
- * accordingly.
- *
- * @param    count   the number of bytes to forget about.
- *
- * @pre      0 <= count <= tbuf->len
- */
-void tbuf_ltrim(struct tbuf *b, size_t count);
-
-void tbuf_vprintf(struct tbuf *b, const char *format, va_list ap)
-	__attribute__ ((format(FORMAT_PRINTF, 2, 0)));
-void tbuf_printf(struct tbuf *b, const char *format, ...)
-	__attribute__ ((format(FORMAT_PRINTF, 2, 3)));
-
-char *tbuf_to_hex(const struct tbuf *x);
-
-#if defined(__cplusplus)
-} /* extern "C" */
-#endif /* defined(__cplusplus) */
-
-#endif /* TARANTOOL_TBUF_H_INCLUDED */
diff --git a/test/box/rtree_array.result b/test/box/rtree_array.result
new file mode 100644
index 0000000000000000000000000000000000000000..e58a1b45aeadd41caa6edc757bfb44b233dcd870
--- /dev/null
+++ b/test/box/rtree_array.result
@@ -0,0 +1,103 @@
+s = box.schema.create_space('spatial')
+---
+...
+s:create_index('primary')
+---
+- unique: true
+  parts:
+  - type: NUM
+    fieldno: 1
+  id: 0
+  space_id: 512
+  name: primary
+  type: TREE
+...
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+---
+- unique: false
+  parts:
+  - type: ARRAY
+    fieldno: 2
+  id: 1
+  space_id: 512
+  name: spatial
+  type: RTREE
+...
+s:insert{1,{0.0,0.0}}
+---
+- [1, [0, 0]]
+...
+s:insert{2,{0.0,10.0}}
+---
+- [2, [0, 10]]
+...
+s:insert{3,{0.0,50.0}}
+---
+- [3, [0, 50]]
+...
+s:insert{4,{10.0,0.0}}
+---
+- [4, [10, 0]]
+...
+s:insert{5,{50.0,0.0}}
+---
+- [5, [50, 0]]
+...
+s:insert{6,{10.0,10.0}}
+---
+- [6, [10, 10]]
+...
+s:insert{7,{10.0,50.0}}
+---
+- [7, [10, 50]]
+...
+s:insert{8,{50.0,10.0}}
+---
+- [8, [50, 10]]
+...
+s:insert{9,{50.0,50.0}}
+---
+- [9, [50, 50]]
+...
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+---
+- - [1, [0, 0]]
+  - [2, [0, 10]]
+  - [3, [0, 50]]
+  - [4, [10, 0]]
+  - [5, [50, 0]]
+  - [6, [10, 10]]
+  - [7, [10, 50]]
+  - [8, [50, 10]]
+  - [9, [50, 50]]
+...
+-- select records belonging to rectangle (0,0,10,10)
+s.index.spatial:select({0.0,0.0,10.0,10.0}, {iterator = 'LE'})
+---
+- - [1, [0, 0]]
+  - [2, [0, 10]]
+  - [4, [10, 0]]
+  - [6, [10, 10]]
+...
+-- select records with coordinates (10,10)
+s.index.spatial:select({10.0,10.0}, {iterator = 'EQ'})
+---
+- - [6, [10, 10]]
+...
+-- select neighbors of point (5,5)
+s.index.spatial:select({5.0,5.0}, {iterator = 'NEIGHBOR'})
+---
+- - [6, [10, 10]]
+  - [4, [10, 0]]
+  - [2, [0, 10]]
+  - [1, [0, 0]]
+  - [8, [50, 10]]
+  - [7, [10, 50]]
+  - [5, [50, 0]]
+  - [3, [0, 50]]
+  - [9, [50, 50]]
+...
+s:drop()
+---
+...
diff --git a/test/box/rtree_array.test.lua b/test/box/rtree_array.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1dcecac280e68d8e0d5b02bba4d7e9534b1d9b69
--- /dev/null
+++ b/test/box/rtree_array.test.lua
@@ -0,0 +1,24 @@
+s = box.schema.create_space('spatial')
+s:create_index('primary')
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+
+s:insert{1,{0.0,0.0}}
+s:insert{2,{0.0,10.0}}
+s:insert{3,{0.0,50.0}}
+s:insert{4,{10.0,0.0}}
+s:insert{5,{50.0,0.0}}
+s:insert{6,{10.0,10.0}}
+s:insert{7,{10.0,50.0}}
+s:insert{8,{50.0,10.0}}
+s:insert{9,{50.0,50.0}}
+
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+-- select records belonging to rectangle (0,0,10,10)
+s.index.spatial:select({0.0,0.0,10.0,10.0}, {iterator = 'LE'})
+-- select records with coordinates (10,10)
+s.index.spatial:select({10.0,10.0}, {iterator = 'EQ'})
+-- select neighbors of point (5,5)
+s.index.spatial:select({5.0,5.0}, {iterator = 'NEIGHBOR'})
+
+s:drop()
diff --git a/test/box/rtree_point.result b/test/box/rtree_point.result
new file mode 100644
index 0000000000000000000000000000000000000000..c011d852ff01c241c55cfa55efb9f7a13d578ff2
--- /dev/null
+++ b/test/box/rtree_point.result
@@ -0,0 +1,103 @@
+s = box.schema.create_space('spatial')
+---
+...
+s:create_index('primary')
+---
+- unique: true
+  parts:
+  - type: NUM
+    fieldno: 1
+  id: 0
+  space_id: 512
+  name: primary
+  type: TREE
+...
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+---
+- unique: false
+  parts:
+  - type: ARRAY
+    fieldno: 2
+  id: 1
+  space_id: 512
+  name: spatial
+  type: RTREE
+...
+s:insert{1,{0,0}}
+---
+- [1, [0, 0]]
+...
+s:insert{2,{0,10}}
+---
+- [2, [0, 10]]
+...
+s:insert{3,{0,50}}
+---
+- [3, [0, 50]]
+...
+s:insert{4,{10,0}}
+---
+- [4, [10, 0]]
+...
+s:insert{5,{50,0}}
+---
+- [5, [50, 0]]
+...
+s:insert{6,{10,10}}
+---
+- [6, [10, 10]]
+...
+s:insert{7,{10,50}}
+---
+- [7, [10, 50]]
+...
+s:insert{8,{50,10}}
+---
+- [8, [50, 10]]
+...
+s:insert{9,{50,50}}
+---
+- [9, [50, 50]]
+...
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+---
+- - [1, [0, 0]]
+  - [2, [0, 10]]
+  - [3, [0, 50]]
+  - [4, [10, 0]]
+  - [5, [50, 0]]
+  - [6, [10, 10]]
+  - [7, [10, 50]]
+  - [8, [50, 10]]
+  - [9, [50, 50]]
+...
+-- select records belonging to rectangle (0,0,10,10)
+s.index.spatial:select({0,0,10,10}, {iterator = 'LE'})
+---
+- - [1, [0, 0]]
+  - [2, [0, 10]]
+  - [4, [10, 0]]
+  - [6, [10, 10]]
+...
+-- select records with coordinates (10,10)
+s.index.spatial:select({10,10}, {iterator = 'EQ'})
+---
+- - [6, [10, 10]]
+...
+-- select neighbors of point (5,5)
+s.index.spatial:select({5,5}, {iterator = 'NEIGHBOR'})
+---
+- - [6, [10, 10]]
+  - [4, [10, 0]]
+  - [2, [0, 10]]
+  - [1, [0, 0]]
+  - [8, [50, 10]]
+  - [7, [10, 50]]
+  - [5, [50, 0]]
+  - [3, [0, 50]]
+  - [9, [50, 50]]
+...
+s:drop()
+---
+...
diff --git a/test/box/rtree_point.test.lua b/test/box/rtree_point.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..394a23a5d48d76b99accd906ba229004b93c85e8
--- /dev/null
+++ b/test/box/rtree_point.test.lua
@@ -0,0 +1,24 @@
+s = box.schema.create_space('spatial')
+s:create_index('primary')
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+
+s:insert{1,{0,0}}
+s:insert{2,{0,10}}
+s:insert{3,{0,50}}
+s:insert{4,{10,0}}
+s:insert{5,{50,0}}
+s:insert{6,{10,10}}
+s:insert{7,{10,50}}
+s:insert{8,{50,10}}
+s:insert{9,{50,50}}
+
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+-- select records belonging to rectangle (0,0,10,10)
+s.index.spatial:select({0,0,10,10}, {iterator = 'LE'})
+-- select records with coordinates (10,10)
+s.index.spatial:select({10,10}, {iterator = 'EQ'})
+-- select neighbors of point (5,5)
+s.index.spatial:select({5,5}, {iterator = 'NEIGHBOR'})
+
+s:drop()
diff --git a/test/box/rtree_point_r2.result b/test/box/rtree_point_r2.result
new file mode 100644
index 0000000000000000000000000000000000000000..e58a1b45aeadd41caa6edc757bfb44b233dcd870
--- /dev/null
+++ b/test/box/rtree_point_r2.result
@@ -0,0 +1,103 @@
+s = box.schema.create_space('spatial')
+---
+...
+s:create_index('primary')
+---
+- unique: true
+  parts:
+  - type: NUM
+    fieldno: 1
+  id: 0
+  space_id: 512
+  name: primary
+  type: TREE
+...
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+---
+- unique: false
+  parts:
+  - type: ARRAY
+    fieldno: 2
+  id: 1
+  space_id: 512
+  name: spatial
+  type: RTREE
+...
+s:insert{1,{0.0,0.0}}
+---
+- [1, [0, 0]]
+...
+s:insert{2,{0.0,10.0}}
+---
+- [2, [0, 10]]
+...
+s:insert{3,{0.0,50.0}}
+---
+- [3, [0, 50]]
+...
+s:insert{4,{10.0,0.0}}
+---
+- [4, [10, 0]]
+...
+s:insert{5,{50.0,0.0}}
+---
+- [5, [50, 0]]
+...
+s:insert{6,{10.0,10.0}}
+---
+- [6, [10, 10]]
+...
+s:insert{7,{10.0,50.0}}
+---
+- [7, [10, 50]]
+...
+s:insert{8,{50.0,10.0}}
+---
+- [8, [50, 10]]
+...
+s:insert{9,{50.0,50.0}}
+---
+- [9, [50, 50]]
+...
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+---
+- - [1, [0, 0]]
+  - [2, [0, 10]]
+  - [3, [0, 50]]
+  - [4, [10, 0]]
+  - [5, [50, 0]]
+  - [6, [10, 10]]
+  - [7, [10, 50]]
+  - [8, [50, 10]]
+  - [9, [50, 50]]
+...
+-- select records belonging to rectangle (0,0,10,10)
+s.index.spatial:select({0.0,0.0,10.0,10.0}, {iterator = 'LE'})
+---
+- - [1, [0, 0]]
+  - [2, [0, 10]]
+  - [4, [10, 0]]
+  - [6, [10, 10]]
+...
+-- select records with coordinates (10,10)
+s.index.spatial:select({10.0,10.0}, {iterator = 'EQ'})
+---
+- - [6, [10, 10]]
+...
+-- select neighbors of point (5,5)
+s.index.spatial:select({5.0,5.0}, {iterator = 'NEIGHBOR'})
+---
+- - [6, [10, 10]]
+  - [4, [10, 0]]
+  - [2, [0, 10]]
+  - [1, [0, 0]]
+  - [8, [50, 10]]
+  - [7, [10, 50]]
+  - [5, [50, 0]]
+  - [3, [0, 50]]
+  - [9, [50, 50]]
+...
+s:drop()
+---
+...
diff --git a/test/box/rtree_point_r2.test.lua b/test/box/rtree_point_r2.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..1dcecac280e68d8e0d5b02bba4d7e9534b1d9b69
--- /dev/null
+++ b/test/box/rtree_point_r2.test.lua
@@ -0,0 +1,24 @@
+s = box.schema.create_space('spatial')
+s:create_index('primary')
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+
+s:insert{1,{0.0,0.0}}
+s:insert{2,{0.0,10.0}}
+s:insert{3,{0.0,50.0}}
+s:insert{4,{10.0,0.0}}
+s:insert{5,{50.0,0.0}}
+s:insert{6,{10.0,10.0}}
+s:insert{7,{10.0,50.0}}
+s:insert{8,{50.0,10.0}}
+s:insert{9,{50.0,50.0}}
+
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+-- select records belonging to rectangle (0,0,10,10)
+s.index.spatial:select({0.0,0.0,10.0,10.0}, {iterator = 'LE'})
+-- select records with coordinates (10,10)
+s.index.spatial:select({10.0,10.0}, {iterator = 'EQ'})
+-- select neighbors of point (5,5)
+s.index.spatial:select({5.0,5.0}, {iterator = 'NEIGHBOR'})
+
+s:drop()
diff --git a/test/box/rtree_rect.result b/test/box/rtree_rect.result
new file mode 100644
index 0000000000000000000000000000000000000000..c4ee5637cb9b62f4cb3413537e57e38fe1cf8132
--- /dev/null
+++ b/test/box/rtree_rect.result
@@ -0,0 +1,93 @@
+s = box.schema.create_space('spatial')
+---
+...
+s:create_index('primary')
+---
+- unique: true
+  parts:
+  - type: NUM
+    fieldno: 1
+  id: 0
+  space_id: 512
+  name: primary
+  type: TREE
+...
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+---
+- unique: false
+  parts:
+  - type: ARRAY
+    fieldno: 2
+  id: 1
+  space_id: 512
+  name: spatial
+  type: RTREE
+...
+s:insert{1,{0,0,10,10}{
+---
+- error: '[string "s:insert{1,{0,0,10,10}{ "]:1: ''}'' expected near ''{'''
+...
+s:insert{2,{5,5,10,10}}
+---
+- [2, [5, 5, 10, 10]]
+...
+s:insert{3,{0,0,5,5}}
+---
+- [3, [0, 0, 5, 5]]
+...
+-- select all records
+s.index.spatial:select({}, {iterator = 'ALL'})
+---
+- - [2, [5, 5, 10, 10]]
+  - [3, [0, 0, 5, 5]]
+...
+-- select records belonging to rectangle (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'LE'})
+---
+- - [3, [0, 0, 5, 5]]
+...
+-- select records strict belonging to rectangle (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'LT'})
+---
+- []
+...
+-- select records strict belonging to rectangle (4,4,11,11)
+s.index.spatial:select({4,4,11,11}, {iterator = 'LT'})
+---
+- - [2, [5, 5, 10, 10]]
+...
+-- select records containing point (5,5)
+s.index.spatial:select({5,5}, {iterator = 'GE'})
+---
+- - [2, [5, 5, 10, 10]]
+  - [3, [0, 0, 5, 5]]
+...
+-- select records containing rectangle (1,1,2,2)
+s.index.spatial:select({1,1,2,2}, {iterator = 'GE'})
+---
+- - [3, [0, 0, 5, 5]]
+...
+-- select records strict containing rectangle (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'GT'})
+---
+- []
+...
+-- select records overlapping rectangle (9,4,11,6)
+s.index.spatial:select({9,4,11,6}, {iterator = 'OVERLAPS'})
+---
+- - [2, [5, 5, 10, 10]]
+...
+-- select records with coordinates (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'EQ'})
+---
+- - [3, [0, 0, 5, 5]]
+...
+-- select neighbors of point (1,1)
+s.index.spatial:select({1,1}, {iterator = 'NEIGHBOR'})
+---
+- - [3, [0, 0, 5, 5]]
+  - [2, [5, 5, 10, 10]]
+...
+s:drop()
+---
+...
diff --git a/test/box/rtree_rect.test.lua b/test/box/rtree_rect.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..3580985e397786e485250532b46c0f4f45ef5083
--- /dev/null
+++ b/test/box/rtree_rect.test.lua
@@ -0,0 +1,30 @@
+s = box.schema.create_space('spatial')
+s:create_index('primary')
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+
+s:insert{1,{0,0,10,10}{
+s:insert{2,{5,5,10,10}}
+s:insert{3,{0,0,5,5}}
+
+-- select all records
+s.index.spatial:select({}, {iterator = 'ALL'})
+-- select records belonging to rectangle (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'LE'})
+-- select records strict belonging to rectangle (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'LT'})
+-- select records strict belonging to rectangle (4,4,11,11)
+s.index.spatial:select({4,4,11,11}, {iterator = 'LT'})
+-- select records containing point (5,5)
+s.index.spatial:select({5,5}, {iterator = 'GE'})
+-- select records containing rectangle (1,1,2,2)
+s.index.spatial:select({1,1,2,2}, {iterator = 'GE'})
+-- select records strict containing rectangle (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'GT'})
+-- select records overlapping rectangle (9,4,11,6)
+s.index.spatial:select({9,4,11,6}, {iterator = 'OVERLAPS'})
+-- select records with coordinates (0,0,5,5)
+s.index.spatial:select({0,0,5,5}, {iterator = 'EQ'})
+-- select neighbors of point (1,1)
+s.index.spatial:select({1,1}, {iterator = 'NEIGHBOR'})
+
+s:drop()
diff --git a/test/box/suite.ini b/test/box/suite.ini
index adc25b973ed65ff82f0e9d8a061d64bc6197240f..807422123594320de65c110e9f2e8bebdbc5291e 100644
--- a/test/box/suite.ini
+++ b/test/box/suite.ini
@@ -4,6 +4,6 @@ description = tarantool/box, minimal configuration
 script = box.lua
 disabled =
 valgrind_disabled = admin_coredump.test.lua
-release_disabled = errinj.test.lua errinj_index.test.lua
+release_disabled = errinj.test.lua errinj_index.test.lua cmdline.test.lua
 lua_libs = lua/fiber.lua lua/fifo.lua
 use_unix_sockets = True
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index dc113ed4955596b58e92f319083d3bfde8e54aae..7565250669a8a1f58bf77bab417997034719fe32 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -41,6 +41,10 @@ add_executable(bps_tree.test bps_tree.cc ${CMAKE_SOURCE_DIR}/third_party/qsort_a
 target_link_libraries(bps_tree.test small)
 add_executable(bps_tree_itr.test bps_tree_itr.cc ${CMAKE_SOURCE_DIR}/third_party/qsort_arg.c)
 target_link_libraries(bps_tree_itr.test small)
+add_executable(rtree.test rtree.cc ${CMAKE_SOURCE_DIR}/src/lib/salad/rtree.c)
+target_link_libraries(rtree.test)
+add_executable(rtree_itr.test rtree_itr.cc ${CMAKE_SOURCE_DIR}/src/lib/salad/rtree.c)
+target_link_libraries(rtree_itr.test)
 add_executable(matras.test matras.cc)
 target_link_libraries(matras.test small)
 add_executable(vclock.test vclock.cc test.c
diff --git a/test/unit/rtree.cc b/test/unit/rtree.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9e4960c862b0a49f901150f994eb98212e11396e
--- /dev/null
+++ b/test/unit/rtree.cc
@@ -0,0 +1,289 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include "unit.h"
+#include "salad/rtree.h"
+
+static int page_count = 0;
+
+static void *
+page_alloc()
+{
+	page_count++;
+	return malloc(RTREE_PAGE_SIZE);
+}
+
+static void
+page_free(void *page)
+{
+	page_count--;
+	free(page);
+}
+
+static void
+simple_check()
+{
+	struct rtree_rect rect;
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+	const size_t rounds = 2000;
+
+	header();
+
+	struct rtree tree;
+	rtree_init(&tree, page_alloc, page_free);
+
+	printf("Insert 1..X, remove 1..X\n");
+	for (size_t i = 1; i <= rounds; i++) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (rtree_search(&tree, &rect, SOP_EQUALS, &iterator)) {
+			fail("element already in tree (1)", "true");
+		}
+		rtree_insert(&tree, &rect, rec);
+	}
+	if (rtree_number_of_records(&tree) != rounds) {
+		fail("Tree count mismatch (1)", "true");
+	}
+	for (size_t i = 1; i <= rounds; i++) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (!rtree_search(&tree, &rect, SOP_EQUALS, &iterator)) {
+			fail("element in tree (1)", "false");
+		}
+		if (rtree_iterator_next(&iterator) != rec) {
+			fail("right search result (1)", "true");
+		}
+		if (rtree_iterator_next(&iterator)) {
+			fail("single search result (1)", "true");
+		}
+		if (!rtree_remove(&tree, &rect, rec)) {
+			fail("delete element in tree (1)", "false");
+		}
+		if (rtree_search(&tree, &rect, SOP_EQUALS, &iterator)) {
+			fail("element still in tree (1)", "true");
+		}
+	}
+	if (rtree_number_of_records(&tree) != 0) {
+		fail("Tree count mismatch (1)", "true");
+	}
+
+	printf("Insert 1..X, remove X..1\n");
+	for (size_t i = 1; i <= rounds; i++) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (rtree_search(&tree, &rect, SOP_EQUALS, &iterator)) {
+			fail("element already in tree (2)", "true");
+		}
+		rtree_insert(&tree, &rect, rec);
+	}
+	if (rtree_number_of_records(&tree) != rounds) {
+		fail("Tree count mismatch (2)", "true");
+	}
+	for (size_t i = rounds; i != 0; i--) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (!rtree_search(&tree, &rect, SOP_OVERLAPS, &iterator)) {
+			fail("element in tree (2)", "false");
+		}
+		if (rtree_iterator_next(&iterator) != rec) {
+			fail("right search result (2)", "true");
+		}
+		if (rtree_iterator_next(&iterator)) {
+			fail("single search result (2)", "true");
+		}
+		if (!rtree_remove(&tree, &rect, rec)) {
+			fail("delete element in tree (2)", "false");
+		}
+		if (rtree_search(&tree, &rect, SOP_OVERLAPS, &iterator)) {
+			fail("element still in tree (2)", "true");
+		}
+	}
+	if (rtree_number_of_records(&tree) != 0) {
+		fail("Tree count mismatch (2)", "true");
+	}
+
+
+	printf("Insert X..1, remove 1..X\n");
+	for (size_t i = rounds; i != 0; i--) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (rtree_search(&tree, &rect, SOP_BELONGS, &iterator)) {
+			fail("element already in tree (3)", "true");
+		}
+		rtree_insert(&tree, &rect, rec);
+	}
+	if (rtree_number_of_records(&tree) != rounds) {
+		fail("Tree count mismatch (3)", "true");
+	}
+	for (size_t i = 1; i <= rounds; i++) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (!rtree_search(&tree, &rect, SOP_BELONGS, &iterator)) {
+			fail("element in tree (3)", "false");
+		}
+		if (rtree_iterator_next(&iterator) != rec) {
+			fail("right search result (3)", "true");
+		}
+		if (rtree_iterator_next(&iterator)) {
+			fail("single search result (3)", "true");
+		}
+		if (!rtree_remove(&tree, &rect, rec)) {
+			fail("delete element in tree (3)", "false");
+		}
+		if (rtree_search(&tree, &rect, SOP_BELONGS, &iterator)) {
+			fail("element still in tree (3)", "true");
+		}
+	}
+	if (rtree_number_of_records(&tree) != 0) {
+		fail("Tree count mismatch (3)", "true");
+	}
+
+
+	printf("Insert X..1, remove X..1\n");
+	for (size_t i = rounds; i != 0; i--) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (rtree_search(&tree, &rect, SOP_CONTAINS, &iterator)) {
+			fail("element already in tree (4)", "true");
+		}
+		rtree_insert(&tree, &rect, rec);
+	}
+	if (rtree_number_of_records(&tree) != rounds) {
+		fail("Tree count mismatch (4)", "true");
+	}
+	for (size_t i = rounds; i != 0; i--) {
+		record_t rec = (record_t)i;
+
+		rect.lower_point.coords[0] = i;
+		rect.lower_point.coords[1] = i;
+		rect.upper_point.coords[0] = i + 0.5;
+		rect.upper_point.coords[1] = i + 0.5;
+
+		if (!rtree_search(&tree, &rect, SOP_CONTAINS, &iterator)) {
+			fail("element in tree (4)", "false");
+		}
+		if (rtree_iterator_next(&iterator) != rec) {
+			fail("right search result (4)", "true");
+		}
+		if (rtree_iterator_next(&iterator)) {
+			fail("single search result (4)", "true");
+		}
+		if (!rtree_remove(&tree, &rect, rec)) {
+			fail("delete element in tree (4)", "false");
+		}
+		if (rtree_search(&tree, &rect, SOP_CONTAINS, &iterator)) {
+			fail("element still in tree (4)", "true");
+		}
+	}
+	if (rtree_number_of_records(&tree) != 0) {
+		fail("Tree count mismatch (4)", "true");
+	}
+
+	rtree_purge(&tree);
+	rtree_destroy(&tree);
+
+	rtree_iterator_destroy(&iterator);
+
+	footer();
+}
+
+static void
+rtree_test_build(struct rtree *tree, struct rtree_rect *arr, int count)
+{
+	for (size_t i = 0; i < count; i++) {
+		record_t rec = (record_t)(i + 1);
+		rtree_insert(tree, &arr[i], rec);
+	}
+}
+
+static void
+neighbor_test()
+{
+	header();
+
+	const int test_count = 1000;
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+	struct rtree_rect arr[test_count];
+	static struct rtree_rect basis;
+
+	for (size_t i = 0; i < test_count; i++) {
+		arr[i].lower_point.coords[0] = i;
+		arr[i].lower_point.coords[1] = i;
+		arr[i].upper_point.coords[0] = i + 1;
+		arr[i].upper_point.coords[1] = i + 1;
+	}
+
+	for (size_t i = 0; i <= test_count; i++) {
+		struct rtree tree;
+		rtree_init(&tree, page_alloc, page_free);
+
+		rtree_test_build(&tree, arr, i);
+
+		if (!rtree_search(&tree, &basis, SOP_NEIGHBOR, &iterator) && i != 0) {
+			fail("search is successful", "true");
+		}
+
+		for (size_t j = 0; j < i; j++) {
+			record_t rec = rtree_iterator_next(&iterator);
+			if (rec != record_t(j+1)) {
+				fail("wrong search result", "true");
+			}
+		}
+		rtree_destroy(&tree);
+	}
+
+	rtree_iterator_destroy(&iterator);
+
+	footer();
+}
+
+
+int
+main(void)
+{
+	simple_check();
+	neighbor_test();
+	if (page_count != 0) {
+		fail("memory leak!", "true");
+	}
+}
diff --git a/test/unit/rtree.result b/test/unit/rtree.result
new file mode 100644
index 0000000000000000000000000000000000000000..4f56e6513d07b4f03d4c095d0a186e405411329f
--- /dev/null
+++ b/test/unit/rtree.result
@@ -0,0 +1,9 @@
+	*** simple_check ***
+Insert 1..X, remove 1..X
+Insert 1..X, remove X..1
+Insert X..1, remove 1..X
+Insert X..1, remove X..1
+	*** simple_check: done ***
+ 	*** neighbor_test ***
+	*** neighbor_test: done ***
+ 
\ No newline at end of file
diff --git a/test/unit/rtree_itr.cc b/test/unit/rtree_itr.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fae7c431108f5346f2cdcdcd1126d6337794bf1e
--- /dev/null
+++ b/test/unit/rtree_itr.cc
@@ -0,0 +1,296 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include "unit.h"
+#include "salad/rtree.h"
+
+static int page_count = 0;
+
+static void *
+page_alloc()
+{
+	page_count++;
+	return malloc(RTREE_PAGE_SIZE);
+}
+
+static void
+page_free(void *page)
+{
+	page_count--;
+	free(page);
+}
+
+static void
+itr_check()
+{
+	header();
+
+	struct rtree tree;
+	rtree_init(&tree, page_alloc, page_free);
+
+	/* Filling tree */
+	const size_t count1 = 10000;
+	const size_t count2 = 5;
+	struct rtree_rect rect;
+	size_t count = 0;
+	record_t rec;
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+
+	for (size_t i = 0; i < count1; i++) {
+		coord_t coord = i * 2 * count2; /* note that filled with even numbers */
+		for (size_t j = 0; j < count2; j++) {
+			rtree_set2d(&rect, coord, coord, coord + j, coord + j);
+			rtree_insert(&tree, &rect, record_t(++count));
+		}
+	}
+	printf("Test tree size: %d\n", (int)rtree_number_of_records(&tree));
+
+	/* Test that tree filled ok */
+	for (size_t i = 0; i < count1; i++) {
+		for (size_t j = 0; j < count2; j++) {
+			coord_t coord = i * 2 * count2;
+			rtree_set2d(&rect, coord, coord, coord + j, coord + j);
+			if (!rtree_search(&tree, &rect, SOP_BELONGS, &iterator)) {
+				fail("Integrity check failed (1)", "false");
+			}
+			for (size_t k = 0; k <= j; k++) {
+				if (!rtree_iterator_next(&iterator)) {
+					fail("Integrity check failed (2)", "false");
+				}
+			}
+			if (rtree_iterator_next(&iterator)) {
+				fail("Integrity check failed (3)", "true");
+			}
+			coord = (i * 2  + 1) * count2;;
+			rtree_set2d(&rect, coord, coord, coord + j, coord + j);
+			if (rtree_search(&tree, &rect, SOP_BELONGS, &iterator)) {
+				fail("Integrity check failed (4)", "true");
+			}
+		}
+	}
+
+	/* Print 7 elems closest to coordinate basis */
+	{
+		static struct rtree_rect basis;
+		printf("--> ");
+		if (!rtree_search(&tree, &basis, SOP_NEIGHBOR, &iterator)) {
+			fail("Integrity check failed (5)", "false");
+		}
+		for (int i = 0; i < 7; i++) {
+			rec = rtree_iterator_next(&iterator);
+			if (rec == 0) {
+				fail("Integrity check failed (6)", "false");
+			}
+			printf("%p ", rec);
+		}
+		printf("\n");
+	}
+	/* Print 7 elems closest to the point [(count1-1)*count2*2, (count1-1)*count2*2] */
+	{
+		printf("<-- ");
+		coord_t coord = (count1 - 1) * count2 * 2;
+		rtree_set2d(&rect, coord, coord, coord, coord);
+		if (!rtree_search(&tree, &rect, SOP_NEIGHBOR, &iterator)) {
+			fail("Integrity check failed (5)", "false");
+		}
+		for (int i = 0; i < 7; i++) {
+		        rec = rtree_iterator_next(&iterator);
+			if (rec == 0) {
+				fail("Integrity check failed (6)", "false");
+			}
+			printf("%p ", rec);
+		}
+		printf("\n");
+	}
+
+	/* Test strict belongs */
+	for (size_t i = 0; i < count1; i++) {
+		for (size_t j = 0; j < count2; j++) {
+			coord_t coord = i * 2 * count2;
+			rtree_set2d(&rect, coord - 0.1, coord - 0.1, coord + j, coord + j);
+			if (!rtree_search(&tree, &rect, SOP_STRICT_BELONGS, &iterator) && j != 0) {
+				fail("Integrity check failed (7)", "false");
+			}
+			for (size_t k = 0; k < j; k++) {
+				if (!rtree_iterator_next(&iterator)) {
+					fail("Integrity check failed (8)", "false");
+				}
+			}
+			if (rtree_iterator_next(&iterator)) {
+				fail("Integrity check failed (9)", "true");
+			}
+			coord = (i * 2 + 1) * count2;
+			rtree_set2d(&rect, coord, coord, coord + j, coord + j);
+			if (rtree_search(&tree, &rect, SOP_STRICT_BELONGS, &iterator)) {
+				fail("Integrity check failed (10)", "true");
+			}
+		}
+	}
+
+	/* Test contains */
+	for (size_t i = 0; i < count1; i++) {
+		for (size_t j = 0; j < count2; j++) {
+			coord_t coord = i * 2 * count2;
+			rtree_set2d(&rect, coord, coord, coord + j, coord + j);
+			if (!rtree_search(&tree, &rect, SOP_CONTAINS, &iterator)) {
+				fail("Integrity check failed (11)", "false");
+			}
+			for (size_t k = j; k < count2; k++) {
+				if (!rtree_iterator_next(&iterator)) {
+					fail("Integrity check failed (12)", "false");
+				}
+			}
+			if (rtree_iterator_next(&iterator)) {
+				fail("Integrity check failed (13)", "true");
+			}
+			coord = (i * 2 + 1) * count2;
+			rtree_set2d(&rect, coord, coord, coord + j, coord + j);
+			if (rtree_search(&tree, &rect, SOP_CONTAINS, &iterator)) {
+				fail("Integrity check failed (14)", "true");
+			}
+		}
+	}
+
+	/* Test strict contains */
+	for (size_t i = 0; i < count1; i++) {
+		for (size_t j = 0; j < count2; j++) {
+			coord_t coord = i * 2 * count2;
+			rtree_set2d(&rect, coord + 0.1, coord + 0.1, coord + j, coord + j);
+			rtree_rect_normalize(&rect);
+			if (!rtree_search(&tree, &rect, SOP_STRICT_CONTAINS, &iterator) && j != 0 && j != count2 - 1) {
+				fail("Integrity check failed (11)", "false");
+			}
+			if (j) {
+				for (size_t k = j; k < count2 - 1; k++) {
+					if (!rtree_iterator_next(&iterator)) {
+						fail("Integrity check failed (12)", "false");
+					}
+				}
+			}
+			if (rtree_iterator_next(&iterator)) {
+				fail("Integrity check failed (13)", "true");
+			}
+			coord = (i * 2 + 1) * count2;
+			rtree_set2d(&rect, coord, coord, coord + j, coord + j);
+			if (rtree_search(&tree, &rect, SOP_STRICT_CONTAINS, &iterator)) {
+				fail("Integrity check failed (14)", "true");
+			}
+		}
+	}
+
+	rtree_purge(&tree);
+	rtree_destroy(&tree);
+	rtree_iterator_destroy(&iterator);
+
+	footer();
+}
+
+static void
+itr_invalidate_check()
+{
+	header();
+
+	const size_t test_size = 300;
+	const size_t max_delete_count = 100;
+	const size_t max_insert_count = 200;
+	const size_t attempt_count = 100;
+	struct rtree_iterator iterators[test_size];
+	for (size_t i = 0; i < test_size; i++)
+		rtree_iterator_init(iterators + i);
+
+	struct rtree_rect rect;
+
+	/* invalidation during deletion */
+	srand(0);
+	for (size_t attempt = 0; attempt < attempt_count; attempt++) {
+		size_t del_pos = rand() % test_size;
+		size_t del_cnt = rand() % max_delete_count + 1;
+		if (del_pos + del_cnt > test_size) {
+			del_cnt = test_size - del_pos;
+		}
+		struct rtree tree;
+		rtree_init(&tree, page_alloc, page_free);
+
+		for (size_t i = 0; i < test_size; i++) {
+			rtree_set2d(&rect, i, i, i, i);
+			rtree_insert(&tree, &rect, record_t(i+1));
+		}
+		rtree_set2d(&rect, 0, 0, test_size, test_size);
+		rtree_search(&tree, &rect, SOP_BELONGS, &iterators[0]);
+		if (!rtree_iterator_next(&iterators[0])) {
+			fail("Integrity check failed (15)", "false");
+		}
+		for (size_t i = 1; i < test_size; i++) {
+			iterators[i] = iterators[i - 1];
+			if (!rtree_iterator_next(&iterators[i])) {
+				fail("Integrity check failed (16)", "false");
+			}
+		}
+		for (size_t i = del_pos; i < del_pos + del_cnt; i++) {
+			rtree_set2d(&rect, i, i, i, i);
+			if (!rtree_remove(&tree, &rect, record_t(i+1))) {
+				fail("Integrity check failed (17)", "false");
+			}
+		}
+		for (size_t i = 0; i < test_size; i++) {
+			if (rtree_iterator_next(&iterators[i])) {
+				fail("Iterator was not invalidated (18)", "true");
+			}
+		}
+		rtree_destroy(&tree);
+	}
+
+	/* invalidation during insertion */
+	srand(0);
+	for (size_t attempt = 0; attempt < attempt_count; attempt++) {
+		size_t ins_pos = rand() % test_size;
+		size_t ins_cnt = rand() % max_insert_count + 1;
+
+		struct rtree tree;
+		rtree_init(&tree, page_alloc, page_free);
+
+		for (size_t i = 0; i < test_size; i++) {
+			rtree_set2d(&rect, i, i, i, i);
+			rtree_insert(&tree, &rect, record_t(i+1));
+		}
+		rtree_set2d(&rect, 0, 0, test_size, test_size);
+		rtree_search(&tree, &rect, SOP_BELONGS, &iterators[0]);
+		if (!rtree_iterator_next(&iterators[0])) {
+			fail("Integrity check failed (19)", "false");
+		}
+		for (size_t i = 1; i < test_size; i++) {
+			iterators[i] = iterators[i - 1];
+			if (!rtree_iterator_next(&iterators[0])) {
+				fail("Integrity check failed (20)", "false");
+			}
+		}
+		for (size_t i = ins_pos; i < ins_pos + ins_cnt; i++) {
+			rtree_set2d(&rect, i, i, i, i);
+			rtree_insert(&tree, &rect, record_t(test_size + i - ins_pos + 1));
+		}
+		for (size_t i = 0; i < test_size; i++) {
+			if (rtree_iterator_next(&iterators[i])) {
+				fail("Iterator was not invalidated (22)", "true");
+			}
+		}
+		rtree_destroy(&tree);
+	}
+
+	for (size_t i = 0; i < test_size; i++)
+		rtree_iterator_destroy(iterators + i);
+
+	footer();
+}
+
+int
+main(void)
+{
+	itr_check();
+	itr_invalidate_check();
+	if (page_count != 0) {
+		fail("memory leak!", "false");
+	}
+}
diff --git a/test/unit/rtree_itr.result b/test/unit/rtree_itr.result
new file mode 100644
index 0000000000000000000000000000000000000000..4a3762cf11520ff94c45e7e94d41795a43ae52aa
--- /dev/null
+++ b/test/unit/rtree_itr.result
@@ -0,0 +1,8 @@
+	*** itr_check ***
+Test tree size: 50000
+--> 0x5 0x4 0x3 0x2 0x1 0xa 0x9 
+<-- 0xc34c 0xc34d 0xc34e 0xc34f 0xc350 0xc34b 0xc34a 
+	*** itr_check: done ***
+ 	*** itr_invalidate_check ***
+	*** itr_invalidate_check: done ***
+ 
\ No newline at end of file
diff --git a/test/unit/slab_cache.c b/test/unit/slab_cache.c
index 3e3c3ea90d90e652708607b5d739b186eae698bf..81839a0ff621d4d252b9ac60193c5a14beae3de5 100644
--- a/test/unit/slab_cache.c
+++ b/test/unit/slab_cache.c
@@ -1,4 +1,5 @@
 #include "small/slab_cache.h"
+#include "small/quota.h"
 #include <stdio.h>
 #include <limits.h>
 #include <stdlib.h>
@@ -13,10 +14,13 @@ int main()
 {
 	srand(time(0));
 
+	struct quota quota;
 	struct slab_arena arena;
 	struct slab_cache cache;
 
-	slab_arena_create(&arena, 0, UINT_MAX, 4000000, MAP_PRIVATE);
+	quota_init(&quota, UINT_MAX);
+
+	slab_arena_create(&arena, &quota, 0, 4000000, MAP_PRIVATE);
 	slab_cache_create(&cache, &arena, 0);
 
 	int i = 0;
diff --git a/test/wal/rtree_benchmark.result b/test/wal/rtree_benchmark.result
new file mode 100644
index 0000000000000000000000000000000000000000..3e9256577421aa52c5e878d61153a154d9dd8c9d
--- /dev/null
+++ b/test/wal/rtree_benchmark.result
@@ -0,0 +1,105 @@
+s = box.schema.create_space('rtreebench')
+---
+...
+s:create_index('primary')
+---
+- unique: true
+  parts:
+  - type: NUM
+    fieldno: 1
+  id: 0
+  space_id: 512
+  name: primary
+  type: TREE
+...
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+---
+- unique: false
+  parts:
+  - type: ARRAY
+    fieldno: 2
+  id: 1
+  space_id: 512
+  name: spatial
+  type: RTREE
+...
+n_records = 20000
+---
+...
+n_iterations = 10000
+---
+...
+n_neighbors = 10
+---
+...
+file = io.open("rtree_benchmark.res", "w")
+---
+...
+start = os.clock()
+---
+...
+--# setopt delimiter ';'
+for i = 1, n_records do
+   s:insert{i,{180*math.random(),180*math.random()}}
+end;
+---
+...
+file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.clock() - start));
+---
+- true
+...
+start = os.clock();
+---
+...
+n = 0;
+---
+...
+for i = 1, n_iterations do
+   x = 180*math.random()
+   y = 180*math.random()
+   for k,v in s.index.spatial:pairs({x,y,x+1,y+1}, {iterator = 'LE'}) do
+       n = n + 1
+   end
+end;
+---
+...
+file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.clock() - start));
+---
+- true
+...
+start = os.clock();
+---
+...
+n = 0
+for i = 1, n_iterations do
+   x = 180*math.random()
+   y = 180*math.random()
+   for k,v in pairs(s.index.spatial:select({x,y }, {limit = n_neighbors, iterator = 'NEIGHBOR'})) do
+      n = n + 1
+   end
+end;
+---
+...
+file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.clock() - start));
+---
+- true
+...
+start = os.clock();
+---
+...
+for i = 1, n_records do
+    s:delete{i}
+end;
+---
+...
+file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.clock() - start));
+---
+- true
+...
+file:close();
+---
+- true
+...
+s:drop();
+---
+...
diff --git a/test/wal/rtree_benchmark.test.lua b/test/wal/rtree_benchmark.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..07db874218197ec656a267372b526abae0784a5e
--- /dev/null
+++ b/test/wal/rtree_benchmark.test.lua
@@ -0,0 +1,49 @@
+s = box.schema.create_space('rtreebench')
+s:create_index('primary')
+s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+
+n_records = 20000
+n_iterations = 10000
+n_neighbors = 10
+
+file = io.open("rtree_benchmark.res", "w")
+start = os.clock()
+
+--# setopt delimiter ';'
+for i = 1, n_records do
+   s:insert{i,{180*math.random(),180*math.random()}}
+end;
+
+file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.clock() - start));
+
+start = os.clock();
+n = 0;
+for i = 1, n_iterations do
+   x = 180*math.random()
+   y = 180*math.random()
+   for k,v in s.index.spatial:pairs({x,y,x+1,y+1}, {iterator = 'LE'}) do
+       n = n + 1
+   end
+end;
+file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.clock() - start));
+
+start = os.clock();
+n = 0
+for i = 1, n_iterations do
+   x = 180*math.random()
+   y = 180*math.random()
+   for k,v in pairs(s.index.spatial:select({x,y }, {limit = n_neighbors, iterator = 'NEIGHBOR'})) do
+      n = n + 1
+   end
+end;
+file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.clock() - start));
+
+start = os.clock();
+for i = 1, n_records do
+    s:delete{i}
+end;
+file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.clock() - start));
+
+file:close();
+s:drop();
+