diff --git a/connector/php/tarantool.c b/connector/php/tarantool.c
index b783ed37344582eb42daf86e0e2558cfdea99038..6fd10b9af349c77ddc7b02112e16dec9603f5779 100644
--- a/connector/php/tarantool.c
+++ b/connector/php/tarantool.c
@@ -23,11 +23,13 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <netinet/in.h>
+#include <netinet/tcp.h>
 #include <netdb.h>
 #include <inttypes.h>
 
 #include <php.h>
 #include <php_ini.h>
+#include <php_network.h>
 #include <ext/standard/info.h>
 #include <zend_exceptions.h>
 
@@ -1874,6 +1876,9 @@ io_buf_send_yaml(php_stream *stream, struct io_buf *buf)
 		return false;
 	}
 
+	/* flush request */
+	php_stream_flush(stream);
+
 	return true;
 }
 
@@ -1917,6 +1922,9 @@ io_buf_send_iproto(php_stream *stream, int32_t type, int32_t request_id, struct
 		return false;
 	}
 
+	/* flush request */
+	php_stream_flush(stream);
+
 	return true;
 }
 
@@ -2014,11 +2022,31 @@ establish_connection(char *host, int port)
 	if (error_code && error_msg) {
 		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
 								"establist connection fail: %s", error_msg);
-		efree(error_msg);
-		return NULL;
+		goto process_error;
+	}
+
+	/* set socket flag 'TCP_NODELAY' */
+	int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
+	flags = 1;
+	int result = setsockopt(socketd, IPPROTO_TCP, TCP_NODELAY, (char *) &flags, sizeof(int));
+	if (result != 0) {
+		char error_buf[64];
+		zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_DC,
+								"establist connection fail: setsockopt %s", strerror_r(errno, error_buf, sizeof(error_buf)));
+		goto process_error;
 	}
 
 	return stream;
+
+process_error:
+
+	if (error_msg)
+		efree(error_msg);
+
+	if (stream)
+		php_stream_close(stream);
+
+	return NULL;
 }
 
 static bool
diff --git a/core/cpu_feature.m b/core/cpu_feature.m
index 01eb71b490f804e1c5eaec1c3c3337a3b3edf748..3dbf7f47bb2a68208291e4f97cd6dd4b1a608183 100644
--- a/core/cpu_feature.m
+++ b/core/cpu_feature.m
@@ -28,18 +28,20 @@
 
 #include "cpu_feature.h"
 
+#if defined (__i386__) || defined (__x86_64__)
+
 enum { eAX=0, eBX, eCX, eDX };
 
 static const struct cpuid_feature {
 	unsigned int 	ri;
-	u_int32_t 		mask;
-} cpu_ftr[] = {
+	u_int32_t	bitmask;
+} cpu_mask[] = {
 	{eDX, (1 << 28)},	/* HT 		*/
 	{eCX, (1 << 19)},	/* SSE 4.1 	*/
 	{eCX, (1 << 20)},	/* SSE 4.2 	*/
 	{eCX, (1 << 31)}	/* HYPERV	*/
 };
-static const size_t LEN_cpu_ftr = sizeof(cpu_ftr) / sizeof (cpu_ftr[0]);
+static const size_t LEN_cpu_mask = sizeof(cpu_mask) / sizeof (cpu_mask[0]);
 
 #define SCALE_F		sizeof(unsigned long)
 
@@ -47,13 +49,12 @@ static const size_t LEN_cpu_ftr = sizeof(cpu_ftr) / sizeof (cpu_ftr[0]);
 	#define REX_PRE "0x48, "
 #elif defined (__i386__)
 	#define REX_PRE
-#else
-	#error "Only x86 and x86_64 architectures supported"
 #endif
 
 
-/* hw-calculate CRC32 per byte (for the unaligned portion of data buffer)
- */
+/* Hw-calculate CRC32 per byte (for the unaligned portion of data buffer). */
+/* NOTE: the function below was adopted from Linux 2.6 kernel source tree,
+   licensed under GPL. */
 static u_int32_t
 crc32c_hw_byte(u_int32_t crc, unsigned char const *data, size_t length)
 {
@@ -70,14 +71,15 @@ crc32c_hw_byte(u_int32_t crc, unsigned char const *data, size_t length)
 }
 
 
-/* hw-calculate CRC32 for the given data buffer
- */
-u_int32_t
-crc32c_hw(u_int32_t crc, unsigned char const *p, size_t len)
+/* Hw-calculate CRC32 for the given data buffer.  */
+/* NOTE: the function below was adopted from Linux 2.6 kernel source tree,
+   licensed under GPL. */
+static u_int32_t
+crc32c_hw_intel(u_int32_t crc, unsigned char const *buf, size_t len)
 {
 	unsigned int iquotient = len / SCALE_F;
 	unsigned int iremainder = len % SCALE_F;
-	unsigned long *ptmp = (unsigned long *)p;
+	unsigned long *ptmp = (unsigned long *)buf;
 
 	while (iquotient--) {
 		__asm__ __volatile__(
@@ -97,9 +99,7 @@ crc32c_hw(u_int32_t crc, unsigned char const *p, size_t len)
 }
 
 
-/* toggle x86 flag-register bits, as per mask
-   return flags both in original and toggled state
- */
+/* Toggle x86 flag-register bits, as per mask. */
 static void
 toggle_x86_flags (long mask, long* orig, long* toggled)
 {
@@ -127,7 +127,7 @@ toggle_x86_flags (long mask, long* orig, long* toggled)
 }
 
 
-/* is CPUID instruction available ? */
+/* Is CPUID instruction available ? */
 static int
 can_cpuid ()
 {
@@ -140,15 +140,15 @@ can_cpuid ()
 	};
 
 
-	/* check if AC (alignment) flag could be toggled:
-		if not - it's i386, thus no CPUID
+	/* Check if AC (alignment) flag could be toggled:
+		if not - it's i386, thus no CPUID.
 	*/
 	toggle_x86_flags (cpuf_AC, &of, &tf);
 	if ((of & cpuf_AC) == (tf & cpuf_AC)) {
 		return 0;
 	}
 
-	/* next try toggling CPUID (ID) flag */
+	/* Next try toggling CPUID (ID) flag. */
 	toggle_x86_flags (cpuf_ID, &of, &tf);
 	if ((of & cpuf_ID) == (tf & cpuf_ID)) {
 		return 0;
@@ -158,7 +158,7 @@ can_cpuid ()
 }
 
 
-/* retrieve CPUID data using info as the EAX key */
+/* Retrieve CPUID data using info as the EAX key. */
 static void
 get_cpuid (long info, long* eax, long* ebx, long* ecx, long *edx)
 {
@@ -183,7 +183,7 @@ get_cpuid (long info, long* eax, long* ebx, long* ecx, long *edx)
 }
 
 
-/* return 1=feature is available, 0=unavailable, 0>(errno) = error */
+/* Check whether CPU has a certain feature. */
 int
 cpu_has (unsigned int feature)
 {
@@ -192,14 +192,42 @@ cpu_has (unsigned int feature)
 	if (!can_cpuid ())
 		return -EINVAL;
 
-	if (feature > LEN_cpu_ftr)
+	if (feature > LEN_cpu_mask)
 		return -ERANGE;
 
 	get_cpuid (info, &reg[eAX], &reg[eBX], &reg[eCX], &reg[eDX]);
 
-	return (reg[cpu_ftr[feature].ri] & cpu_ftr[feature].mask) ? 1 : 0;
+	return (reg[cpu_mask[feature].ri] & cpu_mask[feature].bitmask) ? 1 : 0;
+}
+
+
+u_int32_t
+crc32c_hw(u_int32_t crc, const unsigned char *buf, unsigned int len)
+{
+	return crc32c_hw_intel (crc, (unsigned char const*)buf, len);
+}
+
+#else /* other (yet unsupported architectures) */
+
+int
+cpu_has (unsigned int feature)
+{
+	(void)feature;
+	return EINVAL;
+}
+
+u_int32_t
+crc32c_hw(u_int32_t crc, const unsigned char *buf, unsigned int len)
+{
+	(void)crc; (void)buf, (void)len;
+	assert (false);
+	return 0;
 }
 
 
+#endif /* defined (__i386__) || defined (__x86_64__) */
+
+
+
 /* __EOF__ */
 
diff --git a/core/log_io.m b/core/log_io.m
index f8d681aa797cc5b237c62cf34b08d8738d66140e..6d9d89d0cde51df3d603a286250153fb826b31c0 100644
--- a/core/log_io.m
+++ b/core/log_io.m
@@ -43,6 +43,7 @@
 #include <say.h>
 #include <third_party/crc32.h>
 #include <pickle.h>
+#include <cpu_feature.h>
 
 const u16 snap_tag = -1;
 const u16 wal_tag = -2;
@@ -71,6 +72,19 @@ struct log_io_iter {
 	int io_rate_limit;
 };
 
+static u_int32_t (*calc_crc32c)(u_int32_t crc, const unsigned char *buf,
+		unsigned int len) = NULL;
+
+void
+mach_setup_crc32 ()
+{
+#if defined (__i386__) || defined (__x86_64__)
+	calc_crc32c = cpu_has (cpuf_sse4_2) ? &crc32c_hw : &crc32c;
+#else
+	calc_crc32c = &crc32c;
+#endif
+}
+
 
 void
 wait_lsn_set(struct wait_lsn *wait_lsn, i64 lsn)
@@ -458,7 +472,7 @@ row_reader_v11(FILE *f, struct palloc_pool *pool)
 	m->size = offsetof(struct row_v11, data);
 
 	/* header crc32c calculated on <lsn, tm, len, data_crc32c> */
-	header_crc = crc32c(0, m->data + offsetof(struct row_v11, lsn),
+	header_crc = calc_crc32c(0, m->data + offsetof(struct row_v11, lsn),
 			    sizeof(struct row_v11) - offsetof(struct row_v11, lsn));
 
 	if (row_v11(m)->header_crc32c != header_crc) {
@@ -472,7 +486,7 @@ row_reader_v11(FILE *f, struct palloc_pool *pool)
 
 	m->size += row_v11(m)->len;
 
-	data_crc = crc32c(0, row_v11(m)->data, row_v11(m)->len);
+	data_crc = calc_crc32c(0, row_v11(m)->data, row_v11(m)->len);
 	if (row_v11(m)->data_crc32c != data_crc) {
 		say_error("data crc32c mismatch");
 		return NULL;
@@ -1235,9 +1249,9 @@ write_to_disk(void *_state, struct tbuf *t)
 	row_v11(header)->tm = ev_now();
 	row_v11(header)->len = wal_write_request(t)->len;
 	row_v11(header)->data_crc32c =
-		crc32c(0, wal_write_request(t)->data, wal_write_request(t)->len);
+		calc_crc32c(0, wal_write_request(t)->data, wal_write_request(t)->len);
 	row_v11(header)->header_crc32c =
-		crc32c(0, header->data + field_sizeof(struct row_v11, header_crc32c),
+		calc_crc32c(0, header->data + field_sizeof(struct row_v11, header_crc32c),
 		       sizeof(struct row_v11) - field_sizeof(struct row_v11, header_crc32c));
 
 	if (fwrite(header->data, header->size, 1, wal->f) != 1) {
@@ -1381,9 +1395,9 @@ write_rows(struct log_io_iter *i)
 		row_v11(row)->lsn = 0;	/* unused */
 		row_v11(row)->tm = ev_now();
 		row_v11(row)->len = data->size;
-		row_v11(row)->data_crc32c = crc32c(0, data->data, data->size);
+		row_v11(row)->data_crc32c = calc_crc32c(0, data->data, data->size);
 		row_v11(row)->header_crc32c =
-			crc32c(0, row->data + field_sizeof(struct row_v11, header_crc32c),
+			calc_crc32c(0, row->data + field_sizeof(struct row_v11, header_crc32c),
 			       sizeof(struct row_v11) - field_sizeof(struct row_v11,
 								     header_crc32c));
 
diff --git a/core/tarantool.m b/core/tarantool.m
index 2b018a80286eecdc1cc81ec7d86cc8faf29dfd18..12b2ce6f119799772b2ea9ad3ef37c0e8172a181 100644
--- a/core/tarantool.m
+++ b/core/tarantool.m
@@ -411,6 +411,12 @@ initialize_minimal()
 	initialize(0.1, 4, 2);
 }
 
+inline static void
+mach_init ()
+{
+	mach_setup_crc32 ();
+}
+
 int
 main(int argc, char **argv)
 {
@@ -431,6 +437,7 @@ main(int argc, char **argv)
 	master_pid = getpid();
 	stat_init();
 	palloc_init();
+	mach_init ();
 
 #ifdef HAVE_BFD
 	symbols_load(argv[0]);
diff --git a/doc/user/stored-programs.xml b/doc/user/stored-programs.xml
index 080fc11e954c664ffb4a33b1ccf780705a9925c2..a5f3e4a1a2aa8eb36a4c74d128c5b297aded72da 100644
--- a/doc/user/stored-programs.xml
+++ b/doc/user/stored-programs.xml
@@ -328,8 +328,8 @@ localhost> lua box.select(5, 1, 'firstname', 'lastname')
                 Update a tuple identified by a primary
                 <code>key</code>. Update arguments follow,
                 described by <code>format</code>.
-                The Format and arguments are passed to
-                <code>box.pack()</code>, and the result is then sent
+                The format and arguments are passed to
+                <code>box.pack()</code> and the result is sent
                 to <code>box.process()</code>.
                 A correct <code>format</code> is a sequence of
                 pairs: update operation, operation arguments. A
@@ -578,8 +578,8 @@ Note: the administrative console output must be YAML-compatible.
     <title>Package <code xml:id="box.tuple" xreflabel="box.tuple">box.tuple</code></title>
     <para>The package contains no functions, but stands for
     <code>box.tuple</code> userdata type. It is possible to access individual
-    tuple fields using an index, or iterate over all fields in a
-    tuple. Tuples are immutable.</para>
+    tuple fields using an index, iterate over all fields in a
+    tuple or convert a tuple to a Lua table. Tuples are immutable.</para>
     <varlistentry>
         <term><emphasis role="lua"> </emphasis></term>
         <listitem><para>
@@ -610,6 +610,15 @@ efg
 ghq
 qkl
 ...
+localhost> lua t:unpack()
+---
+ - 
+ - abc
+ - cde
+ - efg
+ - ghq
+ - qkl
+...
 </programlisting>
 
         </para></listitem>
@@ -617,8 +626,8 @@ qkl
 </variablelist>
 
 <variablelist>
-    <title>Package <code>box.space</code></title>
-    <para>This package is a container of all
+    <title>Package <code xml:id="box.space" xreflabel="box.space">box.space</code></title>
+    <para>This package is a container for all
     configured spaces. A space object provides access to space
     attributes, such as id, whether or not a space is
     enabled, space cardinality, estimated number of rows. It also
@@ -668,6 +677,13 @@ qkl
         <listitem><simpara></simpara></listitem>
     </varlistentry>
 
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:select_range(index_no, limit, key, ...)</emphasis>
+        </term>
+        <listitem><simpara></simpara></listitem>
+    </varlistentry>
+
     <varlistentry>
         <term>
             <emphasis role="lua">space:insert(...)</emphasis>
@@ -749,16 +765,17 @@ localhost> lua for k,v in box.space[0]:pairs() do print(v) end
 <variablelist>
     <title>Package <code xml:id="box.index"
 xreflabel="box.index">box.index</code></title>
-    <para>This package implements methods of type <code>box.index</code>.</para>
-    <varlistentry>
-        <term><emphasis role="lua">index.n</emphasis></term>
-        <listitem><simpara>Ordinal index number in the space, <code>space.index[i].n == i</code>.</simpara></listitem>
-    </varlistentry>
+    <para>
+        This package implements methods of type <code>box.index</code>.
+        Indexes are contained in <code
+        xlink:href="#box.space">box.space[i].index[]</code> array
+        within each space object. They provide an API for
+        ordered iteration over tuples.
+    </para>
     <varlistentry>
-        <term><emphasis role="lua">index.enabled</emphasis></term>
+        <term><emphasis role="lua">index.unique</emphasis></term>
         <listitem><simpara>
-            Whether or not this index is enabled in the
-            configuration file.
+            Boolean, true if the index is unique.
         </simpara></listitem>
     </varlistentry>
 
@@ -771,6 +788,15 @@ xreflabel="box.index">box.index</code></title>
         </simpara></listitem>
     </varlistentry>
 
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index.key_field[]</emphasis>
+        </term>
+        <listitem><simpara>
+            An array describing index key fields.
+        </simpara></listitem>
+    </varlistentry>
+
     <varlistentry>
         <term>
             <emphasis role="lua">index.idx</emphasis>
@@ -780,29 +806,137 @@ xreflabel="box.index">box.index</code></title>
         </simpara></listitem>
     </varlistentry>
 
+    <varlistentry>
+        <term>
+            <emphasis role="lua">space:select_range(limit, key)</emphasis>
+        </term>
+        <listitem><simpara>Select a range of tuples, limited by
+        <code>limit</code>, starting from <code>key</code>.
+        </simpara></listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:min()</emphasis>
+        </term>
+        <listitem><simpara>
+            The smallest value in the index. Available only for
+            indexes of type 'TREE'.
+        </simpara>
+        </listitem>
+    </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:max()</emphasis>
+        </term>
+        <listitem><simpara>
+            The biggest value in the index. Available only for
+            indexes of type 'TREE'.
+        </simpara>
+        </listitem>
+    </varlistentry>
+
     <varlistentry>
         <term>
             <emphasis role="lua">index:pairs()</emphasis>
         </term>
         <listitem><simpara>
-            A helper function to iterate over all tuples in index order.
+            A helper function to iterate over all tuples in an
+            index.
         </simpara>
         </listitem>
     </varlistentry>
+
+    <varlistentry>
+        <term>
+            <emphasis role="lua">index:next(iteration_state, key)</emphasis>
+        </term>
+        <listitem><simpara>
+            This function can be used for positioned iteration, or
+            resuming iteration from a given key. It follows the
+            <link xlink:href='http://pgl.yoyo.org/luai/i/next'>Lua
+            iteration pattern</link> and returns a pair
+            <code>&lt;iteration_state, tuple&gt;</code>.
+            When called with no arguments, it starts iteration
+            from the beginning. If called with userdata
+            <code>iteration_state</code>, it returns a tuple
+            corresponding to iterator position, plus a new
+            <code>iteration_state</code>. When called with a key,
+            it positions the iterator on the key, and returns the
+            respective tuple and <code>iteration_state</code>.
+        </simpara>
+        <bridgehead renderas="sect4">Example</bridgehead>
+<programlisting>
+localhost> insert into t0 values (1, 'Russia')
+Insert OK, 1 rows affected
+localhost> insert into t0 values (2, 'Serbia')
+Insert OK, 1 rows affected
+localhost> insert into t0 values (3, 'Bulgaria')
+Insert OK, 1 rows affected
+localhost> lua i = box.space[0].index[0]
+---
+...
+localhost> lua k,v=i:next()
+---
+...
+localhost> lua print(v)
+--
+1: {'Russia'}
+...
+localhost> lua k,v=i:next(k)
+---
+...
+localhost> lua print(v)
+---
+2: {'Serbia'}
+...
+localhost> lua k,v=i:next(k)
+---
+...
+localhost> lua print(v)
+---
+3: {'Bulgaria'}
+...
+localhost> lua k,v=i:next(k)
+---
+...
+localhost> lua print(v)
+---
+nil
+...
+localhost> lua k,v=i:next(2)
+---
+...
+localhost> lua print(v)
+---
+2: {'Serbia'}
+...
+localhost> lua for k,v in i.next, i, nil do print(v) end
+---
+1: {'Russia'}
+2: {'Serbia'}
+3: {'Bulgaria'}
+...
+</programlisting>
+        </listitem>
+    </varlistentry>
+
 </variablelist>
 
 <variablelist>
     <title>Package <code>box.fiber</code></title>
-    <para>Create, run and manage existing <emphasis>fibers</emphasis>.
+    <para>Functions in this package allow to create, run and
+    manage existing <emphasis>fibers</emphasis>.
     </para>
     <para>
-A fiber is an independent execution thread, implemented
-using the mechanism of cooperative multitasking.
+A fiber is an independent execution thread implemented
+using a mechanism of cooperative multitasking.
 Each fiber can be running, suspended or dead.
 A fiber is created (<code>box.fiber.create()</code>) suspended.
 It can be started with <code>box.fiber.resume()</code>, yield
 control back to the caller with <code>box.fiber.yield()</code>
-end with <code>return</code> or just by reaching the end of the
+end with <code>return</code> or just by reaching the end of
 fiber function.
     </para>
     <para>
@@ -810,7 +944,7 @@ A fiber can also be attached or detached.
 An attached fiber is a child of the creator,
 and is running only if the creator has called
 <code>box.fiber.resume()</code>. A detached fiber is a child of
-Tarntool internal <quote>sched</quote> fiber, and gets
+Tarantool internal <quote>sched</quote> fiber, and gets
 scheduled only if there is a libev event associated
 with it.
     </para>
@@ -840,7 +974,7 @@ or <code>box.update()</code>, are calling <code>box.fiber.testcancel()</code>.
 <code>box.select()</code> doesn't.
     </para>
     <para>
-A runaway fiber can really only become cuckoo
+In practice, a runaway fiber can only become unresponsive
 if it does a lot of computations and doesn't check
 whether it's been canceled. In addition
 to the advisory cancellation, configuration parameter <code>lua_timeout</code>
@@ -848,13 +982,12 @@ can be used to cancel runaway Lua procedures.
     </para>
     <para>
 The other potential problem comes from detached
-fibers which never get scheduled, because are subscribed
-or get no events. Such morphing fibers can be killed
-with <code>box.fiber.cancel()</code> at any time,
+fibers which never get scheduled, because are not subscribed
+to any events, or no relevant events occur. Such morphing fibers
+can be killed with <code>box.fiber.cancel()</code> at any time,
 since <code>box.fiber.cancel()</code>
 sends an asynchronous wakeup event to the fiber,
-and when returning from <code>box.fiber.yield()</code>
-<code>box.fiber.testcancel()</code> is invoked.
+and <code>box.fiber.testcancel()</code> is checked whenever such an event occurs.
     </para>
     <para>Like all Lua objects, dead fibers are
     garbage collected.</para>
@@ -862,14 +995,14 @@ and when returning from <code>box.fiber.yield()</code>
         <term>
             <emphasis role="lua">box.fiber.id(fiber) </emphasis>
         </term>
-        <listitem><simpara>Returns a numeric id of the fiber.</simpara></listitem>
+        <listitem><simpara>Return a numeric id of the fiber.</simpara></listitem>
     </varlistentry>
 
     <varlistentry>
         <term>
             <emphasis role="lua">box.fiber.self() </emphasis>
         </term>
-        <listitem><simpara>Returns <code>box.fiber</code> userdata
+        <listitem><simpara>Return <code>box.fiber</code> userdata
         object for the currently scheduled fiber.</simpara></listitem>
     </varlistentry>
 
@@ -877,7 +1010,7 @@ and when returning from <code>box.fiber.yield()</code>
         <term>
             <emphasis role="lua">box.fiber.find(id) </emphasis>
         </term>
-        <listitem><simpara></simpara></listitem>
+        <listitem><simpara>Locate a fiber userdata object by id.</simpara></listitem>
     </varlistentry>
 
     <varlistentry>
@@ -885,10 +1018,11 @@ and when returning from <code>box.fiber.yield()</code>
             <emphasis role="lua">box.fiber.create(function) </emphasis>
         </term>
         <listitem><simpara>
-		Create a fiber for the passed change.
-        Can hit a recursion limit. Is a cancellation
-        point.
-        </simpara></listitem>
+		Create a fiber for <code>function</code>.
+        </simpara>
+        <bridgehead renderas="sect4">Errors</bridgehead>
+        <simpara>Can hit a recursion limit.</simpara>
+        </listitem>
     </varlistentry>
 
     <varlistentry>
@@ -904,7 +1038,7 @@ and when returning from <code>box.fiber.yield()</code>
             <emphasis role="lua">box.fiber.yield(...) </emphasis>
         </term>
         <listitem><para>
-        Yield control to the calling fiber &mdash; if the fiber
+        Yield control to the calling fiber, if the fiber
         is attached, or to sched otherwise.
         </para>
         <para>
@@ -929,8 +1063,7 @@ and when returning from <code>box.fiber.yield()</code>
             <emphasis role="lua">box.fiber.sleep(time)</emphasis>
         </term>
         <listitem><simpara>
-    Yield to the sched fiber and sleep.
-    @param[in]  amount of time to sleep (double)
+    Yield to the sched fiber and sleep <code>time</code> seconds.
     Only the current fiber can be made to sleep.
         </simpara></listitem>
     </varlistentry>
@@ -940,9 +1073,9 @@ and when returning from <code>box.fiber.yield()</code>
             <emphasis role="lua">box.fiber.cancel(fiber)</emphasis>
         </term>
         <listitem><simpara>
-Running and suspended fibers can be canceled.
-Zombie fibers can't. Returns an error if
-subject fiber does not permit cancel.
+        Cancel a <code>fiber</code>.
+        Running and suspended fibers can be canceled.
+        Returns an error if the subject fiber does not permit cancel.
         </simpara></listitem>
     </varlistentry>
 
@@ -951,8 +1084,8 @@ subject fiber does not permit cancel.
             <emphasis role="lua">box.fiber.testcancel()</emphasis>
         </term>
         <listitem><simpara>
-Check if this current fiber has been canceled and
-throw an exception if this is the case.
+            Check if the current fiber has been canceled and
+            throw an exception if this is the case.
         </simpara></listitem>
     </varlistentry>
 
@@ -980,8 +1113,9 @@ logger = cat - >> tarantool.log
 </variablelist>
 
 <para>
-    Additional examples can be found in the open source Lua stored
-    procedures repository and in the server test suite.
+    Additional examples can be found in the open source <link
+    xlink:href="https://github.com/mailru/tntlua">Lua stored
+    procedures repository</link> and in the server test suite.
 </para>
 
 <section>
@@ -997,9 +1131,9 @@ logger = cat - >> tarantool.log
     paradigm: unless a running fiber deliberately yields control
     to some other fiber, it is not preempted.
     <quote>Yield points</quote> are built into all
-    calls from Tarantool core into the operating system.
+    calls from Tarantool core to the operating system.
     Any system call which can block is performed in
-    asynchronous manner, and the fiber waiting
+    asynchronous manner and the fiber waiting
     on the system call is preempted with a fiber ready to
     run. This model makes all programmatic locks unnecessary:
     cooperative multitasking ensures that there is no concurrency
@@ -1029,7 +1163,7 @@ logger = cat - >> tarantool.log
     any yield in a stored procedure is a potential change in the
     database state. Effectively, it's only possible
     to have CAS (compare-and-swap) -like atomic stored
-    procedures: select and then modify a record.
+    procedures: i.e. procedures which select and then modify a record.
 
     Multiple data change requests always run through a built-in
     yield point.
diff --git a/doc/user/target.db b/doc/user/target.db
index ab16a3831b2a471fd1a9ffabaf8a8eebe518c4ba..ee73c37c4bb101c8d69b5bed3f1858d03bd65d96 100644
--- a/doc/user/target.db
+++ b/doc/user/target.db
@@ -1 +1 @@
-<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.4-129-g4703934</ttl><xreftext>Tarantool/Box User Guide, version 1.4.4-129-g4703934</xreftext><div element="chapter" href="#id417098" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overview”</xreftext></div><div element="section" href="#id415928" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventions”</xreftext></div><div element="section" href="#id416002" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugs”</xreftext></div></div><div element="chapter" href="#id418152" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#data-and-persistence" number="3" targetptr="data-and-persistence"><ttl>Data model and data persitence</ttl><xreftext>Chapter 3, <i>Data model and data persitence</i></xreftext><div element="section" href="#id417928" number=""><ttl>Dynamic data model</ttl><xreftext>the section called “Dynamic data model”</xreftext></div><div element="section" href="#id419599" number=""><ttl>Data persistence</ttl><xreftext>the section called “Data persistence”</xreftext></div></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#id421335" number=""><ttl>Data manipulation</ttl><xreftext>the section called “Data manipulation”</xreftext><div element="section" href="#id421296" number=""><ttl>Memcached protocol</ttl><xreftext>the section called “Memcached protocol”</xreftext></div></div><div element="section" href="#id421275" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative console”</xreftext><obj element="term" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj><obj element="term" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="term" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="term" href="#show-info" number="" targetptr="show-info"><ttl>???TITLE???</ttl><xreftext>SHOW INFO</xreftext></obj><obj element="term" href="#show-stat" number="" targetptr="show-stat"><ttl>???TITLE???</ttl><xreftext>SHOW STAT</xreftext></obj><obj element="term" href="#show-slab" number="" targetptr="show-slab"><ttl>???TITLE???</ttl><xreftext>SHOW SLAB</xreftext></obj><obj element="term" href="#show-palloc" number="" targetptr="show-palloc"><ttl>???TITLE???</ttl><xreftext>SHOW PALLOC</xreftext></obj><obj element="term" href="#save-coredump" number="" targetptr="save-coredump"><ttl>???TITLE???</ttl><xreftext>SAVE COREDUMP</xreftext></obj><obj element="term" href="#show-fiber" number="" targetptr="show-fiber"><ttl>???TITLE???</ttl><xreftext>SHOW FIBER</xreftext></obj><obj element="term" href="#lua-command" number="" targetptr="lua-command"><ttl>???TITLE???</ttl><xreftext>LUA ...</xreftext></obj></div><div element="section" href="#stored-programs" number="" targetptr="stored-programs"><ttl>Writing stored procedures in Lua</ttl><xreftext>the section called “Writing stored procedures in Lua”</xreftext><obj element="code" href="#box" number="" targetptr="box"><ttl>???TITLE???</ttl><xreftext>box</xreftext></obj><obj element="code" href="#box.tuple" number="" targetptr="box.tuple"><ttl>???TITLE???</ttl><xreftext>box.tuple</xreftext></obj><div element="section" href="#id419145" number=""><ttl>Limitation of stored programs</ttl><xreftext>the section called “Limitation of stored programs”</xreftext></div></div></div><div element="chapter" href="#replication" number="5" targetptr="replication"><ttl>Replication</ttl><xreftext>Chapter 5, <i>Replication</i></xreftext><div element="section" href="#id418807" number=""><ttl>Replication architecture</ttl><xreftext>the section called “Replication architecture”</xreftext></div><div element="section" href="#id416068" number=""><ttl>Setting up the master</ttl><xreftext>the section called “Setting up the master”</xreftext></div><div element="section" href="#id416092" number=""><ttl>Setting up a replica</ttl><xreftext>the section called “Setting up a replica”</xreftext></div><div element="section" href="#id418356" number=""><ttl>Recovering from a degraded state</ttl><xreftext>the section called “Recovering from a degraded state”</xreftext></div></div><div element="chapter" href="#configuration-reference" number="6" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 6, <i>Configuration reference</i></xreftext><div element="section" href="#id423367" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line options”</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="option" href="#init-storage-option" number="" targetptr="init-storage-option"><ttl>???TITLE???</ttl><xreftext>--init-storage</xreftext></obj><obj element="option" href="#cat-option" number="" targetptr="cat-option"><ttl>???TITLE???</ttl><xreftext>--cat</xreftext></obj></div><div element="section" href="#option-file" number="" targetptr="option-file"><ttl>The option file</ttl><xreftext>option file</xreftext><obj element="table" href="#id423452" number="6.1"><ttl>Basic parameters</ttl><xreftext>Table 6.1, “Basic parameters”</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="entry" href="#secondary_port" number="" targetptr="secondary_port"><ttl>???TITLE???</ttl><xreftext>secondary_port</xreftext></obj><obj element="entry" href="#admin_port" number="" targetptr="admin_port"><ttl>???TITLE???</ttl><xreftext>admin_port</xreftext></obj><obj element="entry" href="#custom_proc_title" number="" targetptr="custom_proc_title"><ttl>???TITLE???</ttl><xreftext>custom_proc_title</xreftext></obj><obj element="table" href="#id423858" number="6.2"><ttl>Configuring the storage</ttl><xreftext>Table 6.2, “Configuring the storage”</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#space" number="" targetptr="space"><ttl>???TITLE???</ttl><xreftext>the section called “The option file”</xreftext></obj><obj element="table" href="#id424158" number="6.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 6.3, “Binary logging and snapshots”</xreftext></obj><obj element="entry" href="#rows_per_wal" number="" targetptr="rows_per_wal"><ttl>???TITLE???</ttl><xreftext>rows_per_wal</xreftext></obj><obj element="entry" href="#wal_writer_inbox_size" number="" targetptr="wal_writer_inbox_size"><ttl>???TITLE???</ttl><xreftext>wal_writer_inbox_size</xreftext></obj><obj element="table" href="#id424430" number="6.4"><ttl>Replication</ttl><xreftext>Table 6.4, “Replication”</xreftext></obj><obj element="entry" href="#replication_port" number="" targetptr="replication_port"><ttl>???TITLE???</ttl><xreftext>replication_port</xreftext></obj><obj element="entry" href="#replication_source" number="" targetptr="replication_source"><ttl>???TITLE???</ttl><xreftext>replication_source</xreftext></obj><obj element="table" href="#id424588" number="6.5"><ttl>Networking</ttl><xreftext>Table 6.5, “Networking”</xreftext></obj><obj element="table" href="#id424745" number="6.6"><ttl>Logging</ttl><xreftext>Table 6.6, “Logging”</xreftext></obj><obj element="table" href="#id424952" number="6.7"><ttl>Memcached protocol support</ttl><xreftext>Table 6.7, “Memcached protocol support”</xreftext></obj><obj element="anchor" href="#memcached_port" number="" targetptr="memcached_port"><ttl>???TITLE???</ttl><xreftext>memcached_port</xreftext></obj><obj element="anchor" href="#memcached_space" number="" targetptr="memcached_space"><ttl>???TITLE???</ttl><xreftext>memcached_space</xreftext></obj><obj element="anchor" href="#memcached_expire" number="" targetptr="memcached_expire"><ttl>???TITLE???</ttl><xreftext>memcached_expire</xreftext></obj></div></div><div element="chapter" href="#connectors" number="7" targetptr="connectors"><ttl>Connectors</ttl><xreftext>Chapter 7, <i>Connectors</i></xreftext><div element="section" href="#id419391" number=""><ttl>C</ttl><xreftext>the section called “C”</xreftext></div><div element="section" href="#id418165" number=""><ttl>Perl</ttl><xreftext>the section called “Perl”</xreftext></div><div element="section" href="#id418182" number=""><ttl>PHP</ttl><xreftext>the section called “PHP”</xreftext></div><div element="section" href="#id421638" number=""><ttl>Python</ttl><xreftext>the section called “Python”</xreftext></div><div element="section" href="#id417807" number=""><ttl>Ruby</ttl><xreftext>the section called “Ruby”</xreftext></div></div><div element="appendix" href="#proctitle" number="A" targetptr="proctitle"><ttl>Server process titles</ttl><xreftext>Appendix A, <i>Server process titles</i></xreftext></div><div element="appendix" href="#errcode" number="B" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix B, <i>List of error codes</i></xreftext><obj element="term" href="#ER_NONMASTER" number="" targetptr="ER_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ER_NONMASTER</xreftext></obj><obj element="term" href="#ER_ILLEGAL_PARAMS" number="" targetptr="ER_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ER_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ER_TUPLE_IS_RO" number="" targetptr="ER_TUPLE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ER_TUPLE_IS_RO</xreftext></obj><obj element="term" href="#ER_MEMORY_ISSUE" number="" targetptr="ER_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ER_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ER_WAL_IO" number="" targetptr="ER_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ER_WAL_IO</xreftext></obj><obj element="term" href="#ER_INDEX_VIOLATION" number="" targetptr="ER_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ER_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ER_NO_SUCH_SPACE" number="" targetptr="ER_NO_SUCH_SPACE"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_SPACE</xreftext></obj><obj element="term" href="#ER_NO_SUCH_INDEX" number="" targetptr="ER_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_INDEX</xreftext></obj><obj element="term" href="#ER_PROC_LUA" number="" targetptr="ER_PROC_LUA"><ttl>???TITLE???</ttl><xreftext>ER_PROC_LUA</xreftext></obj></div></div>
+<div element="book" href="#tarantool-user-guide" number="" targetptr="tarantool-user-guide"><ttl>Tarantool/Box User Guide, version 1.4.4-127-g8c61695</ttl><xreftext>Tarantool/Box User Guide, version 1.4.4-127-g8c61695</xreftext><div element="chapter" href="#idm34816" number="1"><ttl>Preface</ttl><xreftext>Chapter 1, <i>Preface</i></xreftext><div element="section" href="#preface" number="" targetptr="preface"><ttl>Tarantool/Box: an overview</ttl><xreftext>the section called “Tarantool/Box: an overview”</xreftext></div><div element="section" href="#idp200384" number=""><ttl>Conventions</ttl><xreftext>the section called “Conventions”</xreftext></div><div element="section" href="#idp209280" number=""><ttl>Reporting bugs</ttl><xreftext>the section called “Reporting bugs”</xreftext></div></div><div element="chapter" href="#idp229968" number="2"><ttl>Getting started</ttl><xreftext>Chapter 2, <i>Getting started</i></xreftext></div><div element="chapter" href="#data-and-persistence" number="3" targetptr="data-and-persistence"><ttl>Data model and data persitence</ttl><xreftext>Chapter 3, <i>Data model and data persitence</i></xreftext><div element="section" href="#idp312384" number=""><ttl>Dynamic data model</ttl><xreftext>the section called “Dynamic data model”</xreftext></div><div element="section" href="#idp403344" number=""><ttl>Data persistence</ttl><xreftext>the section called “Data persistence”</xreftext></div></div><div element="chapter" href="#language-reference" number="4" targetptr="language-reference"><ttl>Language reference</ttl><xreftext>Chapter 4, <i>Language reference</i></xreftext><div element="section" href="#idp643840" number=""><ttl>Data manipulation</ttl><xreftext>the section called “Data manipulation”</xreftext><div element="section" href="#idp637888" number=""><ttl>Memcached protocol</ttl><xreftext>the section called “Memcached protocol”</xreftext></div></div><div element="section" href="#idp636960" number=""><ttl>Administrative console</ttl><xreftext>the section called “Administrative console”</xreftext><obj element="term" href="#save-snapshot" number="" targetptr="save-snapshot"><ttl>???TITLE???</ttl><xreftext>SAVE SNAPSHOT</xreftext></obj><obj element="term" href="#reload-configuration" number="" targetptr="reload-configuration"><ttl>???TITLE???</ttl><xreftext>RELOAD CONFIGURATION</xreftext></obj><obj element="term" href="#show-configuration" number="" targetptr="show-configuration"><ttl>???TITLE???</ttl><xreftext>SHOW CONFIGURATION</xreftext></obj><obj element="term" href="#show-info" number="" targetptr="show-info"><ttl>???TITLE???</ttl><xreftext>SHOW INFO</xreftext></obj><obj element="term" href="#show-stat" number="" targetptr="show-stat"><ttl>???TITLE???</ttl><xreftext>SHOW STAT</xreftext></obj><obj element="term" href="#show-slab" number="" targetptr="show-slab"><ttl>???TITLE???</ttl><xreftext>SHOW SLAB</xreftext></obj><obj element="term" href="#show-palloc" number="" targetptr="show-palloc"><ttl>???TITLE???</ttl><xreftext>SHOW PALLOC</xreftext></obj><obj element="term" href="#save-coredump" number="" targetptr="save-coredump"><ttl>???TITLE???</ttl><xreftext>SAVE COREDUMP</xreftext></obj><obj element="term" href="#show-fiber" number="" targetptr="show-fiber"><ttl>???TITLE???</ttl><xreftext>SHOW FIBER</xreftext></obj><obj element="term" href="#lua-command" number="" targetptr="lua-command"><ttl>???TITLE???</ttl><xreftext>LUA ...</xreftext></obj></div><div element="section" href="#stored-programs" number="" targetptr="stored-programs"><ttl>Writing stored procedures in Lua</ttl><xreftext>the section called “Writing stored procedures in Lua”</xreftext><obj element="code" href="#box" number="" targetptr="box"><ttl>???TITLE???</ttl><xreftext>box</xreftext></obj><obj element="code" href="#box.tuple" number="" targetptr="box.tuple"><ttl>???TITLE???</ttl><xreftext>box.tuple</xreftext></obj><obj element="code" href="#box.space" number="" targetptr="box.space"><ttl>???TITLE???</ttl><xreftext>box.space</xreftext></obj><obj element="code" href="#box.index" number="" targetptr="box.index"><ttl>???TITLE???</ttl><xreftext>box.index</xreftext></obj><div element="section" href="#idp844528" number=""><ttl>Limitation of stored programs</ttl><xreftext>the section called “Limitation of stored programs”</xreftext></div></div></div><div element="chapter" href="#replication" number="5" targetptr="replication"><ttl>Replication</ttl><xreftext>Chapter 5, <i>Replication</i></xreftext><div element="section" href="#idp31184" number=""><ttl>Replication architecture</ttl><xreftext>the section called “Replication architecture”</xreftext></div><div element="section" href="#idp304320" number=""><ttl>Setting up the master</ttl><xreftext>the section called “Setting up the master”</xreftext></div><div element="section" href="#idp307200" number=""><ttl>Setting up a replica</ttl><xreftext>the section called “Setting up a replica”</xreftext></div><div element="section" href="#idp393936" number=""><ttl>Recovering from a degraded state</ttl><xreftext>the section called “Recovering from a degraded state”</xreftext></div></div><div element="chapter" href="#configuration-reference" number="6" targetptr="configuration-reference"><ttl>Configuration reference</ttl><xreftext>Chapter 6, <i>Configuration reference</i></xreftext><div element="section" href="#idp890800" number=""><ttl>Command line options</ttl><xreftext>the section called “Command line options”</xreftext><obj element="listitem" href="#help-option" number="" targetptr="help-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#version-option" number="" targetptr="version-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="listitem" href="#config-option" number="" targetptr="config-option"><ttl>???TITLE???</ttl><xreftext/></obj><obj element="option" href="#init-storage-option" number="" targetptr="init-storage-option"><ttl>???TITLE???</ttl><xreftext>--init-storage</xreftext></obj><obj element="option" href="#cat-option" number="" targetptr="cat-option"><ttl>???TITLE???</ttl><xreftext>--cat</xreftext></obj></div><div element="section" href="#option-file" number="" targetptr="option-file"><ttl>The option file</ttl><xreftext>option file</xreftext><obj element="table" href="#idp903568" number="6.1"><ttl>Basic parameters</ttl><xreftext>Table 6.1, “Basic parameters”</xreftext></obj><obj element="entry" href="#wal_dir" number="" targetptr="wal_dir"><ttl>???TITLE???</ttl><xreftext>wal_dir</xreftext></obj><obj element="entry" href="#snap_dir" number="" targetptr="snap_dir"><ttl>???TITLE???</ttl><xreftext>snap_dir</xreftext></obj><obj element="entry" href="#primary_port" number="" targetptr="primary_port"><ttl>???TITLE???</ttl><xreftext>primary_port</xreftext></obj><obj element="entry" href="#secondary_port" number="" targetptr="secondary_port"><ttl>???TITLE???</ttl><xreftext>secondary_port</xreftext></obj><obj element="entry" href="#admin_port" number="" targetptr="admin_port"><ttl>???TITLE???</ttl><xreftext>admin_port</xreftext></obj><obj element="entry" href="#custom_proc_title" number="" targetptr="custom_proc_title"><ttl>???TITLE???</ttl><xreftext>custom_proc_title</xreftext></obj><obj element="table" href="#idp952208" number="6.2"><ttl>Configuring the storage</ttl><xreftext>Table 6.2, “Configuring the storage”</xreftext></obj><obj element="anchor" href="#slab_alloc_arena" number="" targetptr="slab_alloc_arena"><ttl>???TITLE???</ttl><xreftext>slab_alloc_arena</xreftext></obj><obj element="para" href="#space" number="" targetptr="space"><ttl>???TITLE???</ttl><xreftext>the section called “The option file”</xreftext></obj><obj element="table" href="#idp988224" number="6.3"><ttl>Binary logging and snapshots</ttl><xreftext>Table 6.3, “Binary logging and snapshots”</xreftext></obj><obj element="entry" href="#rows_per_wal" number="" targetptr="rows_per_wal"><ttl>???TITLE???</ttl><xreftext>rows_per_wal</xreftext></obj><obj element="entry" href="#wal_writer_inbox_size" number="" targetptr="wal_writer_inbox_size"><ttl>???TITLE???</ttl><xreftext>wal_writer_inbox_size</xreftext></obj><obj element="table" href="#idp1020912" number="6.4"><ttl>Replication</ttl><xreftext>Table 6.4, “Replication”</xreftext></obj><obj element="entry" href="#replication_port" number="" targetptr="replication_port"><ttl>???TITLE???</ttl><xreftext>replication_port</xreftext></obj><obj element="entry" href="#replication_source" number="" targetptr="replication_source"><ttl>???TITLE???</ttl><xreftext>replication_source</xreftext></obj><obj element="table" href="#idp1039888" number="6.5"><ttl>Networking</ttl><xreftext>Table 6.5, “Networking”</xreftext></obj><obj element="table" href="#idp1058656" number="6.6"><ttl>Logging</ttl><xreftext>Table 6.6, “Logging”</xreftext></obj><obj element="table" href="#idp1083552" number="6.7"><ttl>Memcached protocol support</ttl><xreftext>Table 6.7, “Memcached protocol support”</xreftext></obj><obj element="anchor" href="#memcached_port" number="" targetptr="memcached_port"><ttl>???TITLE???</ttl><xreftext>memcached_port</xreftext></obj><obj element="anchor" href="#memcached_space" number="" targetptr="memcached_space"><ttl>???TITLE???</ttl><xreftext>memcached_space</xreftext></obj><obj element="anchor" href="#memcached_expire" number="" targetptr="memcached_expire"><ttl>???TITLE???</ttl><xreftext>memcached_expire</xreftext></obj></div></div><div element="chapter" href="#connectors" number="7" targetptr="connectors"><ttl>Connectors</ttl><xreftext>Chapter 7, <i>Connectors</i></xreftext><div element="section" href="#idp191920" number=""><ttl>C</ttl><xreftext>the section called “C”</xreftext></div><div element="section" href="#idp678512" number=""><ttl>Perl</ttl><xreftext>the section called “Perl”</xreftext></div><div element="section" href="#idp680512" number=""><ttl>PHP</ttl><xreftext>the section called “PHP”</xreftext></div><div element="section" href="#idp734112" number=""><ttl>Python</ttl><xreftext>the section called “Python”</xreftext></div><div element="section" href="#idp757312" number=""><ttl>Ruby</ttl><xreftext>the section called “Ruby”</xreftext></div></div><div element="appendix" href="#proctitle" number="A" targetptr="proctitle"><ttl>Server process titles</ttl><xreftext>Appendix A, <i>Server process titles</i></xreftext></div><div element="appendix" href="#errcode" number="B" targetptr="errcode"><ttl>List of error codes</ttl><xreftext>Appendix B, <i>List of error codes</i></xreftext><obj element="term" href="#ER_NONMASTER" number="" targetptr="ER_NONMASTER"><ttl>???TITLE???</ttl><xreftext>ER_NONMASTER</xreftext></obj><obj element="term" href="#ER_ILLEGAL_PARAMS" number="" targetptr="ER_ILLEGAL_PARAMS"><ttl>???TITLE???</ttl><xreftext>ER_ILLEGAL_PARAMS</xreftext></obj><obj element="term" href="#ER_TUPLE_IS_RO" number="" targetptr="ER_TUPLE_IS_RO"><ttl>???TITLE???</ttl><xreftext>ER_TUPLE_IS_RO</xreftext></obj><obj element="term" href="#ER_MEMORY_ISSUE" number="" targetptr="ER_MEMORY_ISSUE"><ttl>???TITLE???</ttl><xreftext>ER_MEMORY_ISSUE</xreftext></obj><obj element="term" href="#ER_WAL_IO" number="" targetptr="ER_WAL_IO"><ttl>???TITLE???</ttl><xreftext>ER_WAL_IO</xreftext></obj><obj element="term" href="#ER_INDEX_VIOLATION" number="" targetptr="ER_INDEX_VIOLATION"><ttl>???TITLE???</ttl><xreftext>ER_INDEX_VIOLATION</xreftext></obj><obj element="term" href="#ER_NO_SUCH_SPACE" number="" targetptr="ER_NO_SUCH_SPACE"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_SPACE</xreftext></obj><obj element="term" href="#ER_NO_SUCH_INDEX" number="" targetptr="ER_NO_SUCH_INDEX"><ttl>???TITLE???</ttl><xreftext>ER_NO_SUCH_INDEX</xreftext></obj><obj element="term" href="#ER_PROC_LUA" number="" targetptr="ER_PROC_LUA"><ttl>???TITLE???</ttl><xreftext>ER_PROC_LUA</xreftext></obj></div></div>
diff --git a/extra/tarantool b/extra/tarantool
index 33077270e6e662d44b546d4b26f0688cab884906..192e4a7f8fd9a3404716dc1483cb6da2e48248ed 100755
--- a/extra/tarantool
+++ b/extra/tarantool
@@ -14,7 +14,13 @@ export PIDFILE="/var/${INST}/pid"
 export WRAP_PIDFILE="/var/${INST}/wrapper.pid"
 export OPTIONS=""
 
-# We must not run immediate!
+# This script is normally invoked via a symlink.
+# An own symlink is created for each instance.
+# E.g., in case of 4 instances, there are symlinks
+# tarantool0, tarantool1, tarantool2, tarantool3.
+# If, for some reason, this script is invoked not via
+# a symlink, do nothing.
+#
 if [ "${INST}" == "tarantool" ]
 then
         echo_failure
diff --git a/extra/tarantool_expand.sh b/extra/tarantool_expand.sh
index a1ab42aae29212acc02aeb28c4aa8e36a577b00f..45adb282ce8e1fc86b1b3cc4cdbdca4be848760f 100755
--- a/extra/tarantool_expand.sh
+++ b/extra/tarantool_expand.sh
@@ -1,17 +1,33 @@
 #!/bin/sh
 
 #
-# Tarantool DB expand script
+# Copyright (C) 2012 Mail.RU
+#
+# 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 AUTHOR AND CONTRIBUTORS ``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 AUTHOR 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.
 #
 
-prefix="/usr/local"
-prefix_var="/var"
-prefix_etc="/etc"
-
-deploy_cfg="${prefix}/etc/tarantool_deploy.cfg"
-deploy_exists=0
-deploy_current=0
-deploy_count=0
+#
+# Tarantool instance expansion script
+#
 
 prompt_name=`basename $0`
 
@@ -19,6 +35,7 @@ act_prompt=1
 act_status=0
 act_debug=0
 act_dry=0
+instance_current=0
 
 error() {
 	echo "$prompt_name error: $*" 1>&2
@@ -26,16 +43,16 @@ error() {
 }
 
 log() {
-	echo "$prompt_name > $*"
+	echo "$prompt_name: $*"
 }
 
 usage() {
-	echo "Tarantool DB expand script"
+	echo "Tarantool expansion script: add more Tarantool instances."
 	echo "usage: tarantool_expand.sh [options] <instances>"
 	echo
-	echo "  --prefix <path>       installation path ($prefix)"
-	echo "  --prefix_etc <path>   installation etc path ($prefix_etc)"
-	echo "  --prefix_var <path>   installation var path ($prefix_var)"
+	echo "  --prefix <path>       installation path (/usr/local)"
+	echo "  --prefix_etc <path>   installation etc path (/etc)"
+	echo "  --prefix_var <path>   installation var path (/var)"
 	echo
 	echo "  --status              display deployment status"
 	echo "  --dry                 don't create anything, show commands"
@@ -44,7 +61,13 @@ usage() {
 	echo "  --yes                 don't prompt"
 	echo "  --help                display this usage"
 	echo
-	exit 0
+	exit $1
+}
+
+is_num_positive() {
+	num=$1
+	[ $num -eq $num ] 2>/dev/null && [ $num -ge 0 ] 2>/dev/null || \
+		return 1
 }
 
 rollback_instance() {
@@ -58,9 +81,9 @@ rollback_instance() {
 }
 
 rollback() {
-	log ">>>> rollbacking changes"
+	log ">>>> rolling back changes"
 	start=`expr $deploy_current + 1`
-	for instance in `seq $start $deploy_count`; do
+	for instance in `seq $start $instance_current`; do
 		rollback_instance $instance
 	done
 	exit 1
@@ -69,7 +92,7 @@ rollback() {
 try() {
 	cmd="$*"
 	[ $act_debug -gt 0 ] && log $cmd
-	if [ $act_dry -eq 0 ]; then 
+	if [ $act_dry -eq 0 ]; then
 		eval $cmd
 		if [ $? -gt 0 ]; then
 			rollback
@@ -86,6 +109,7 @@ deploy_instance() {
 
 	# setting up work environment
 	try "mkdir -p $workdir/logs"
+
 	# setting up startup snapshot
 	try "cp \"${prefix}/share/tarantool/00000000000000000001.snap\" $workdir"
 	try "chown tarantool:tarantool -R $workdir"
@@ -98,7 +122,7 @@ deploy_instance() {
 
 	# setting up wrapper
 	try "ln -s \"${prefix}/bin/tarantool_multi.sh\" \"${prefix}/bin/tarantool$id.sh\""
-	
+
 	# setting up startup script
 	try "ln -s \"${prefix_etc}/init.d/tarantool\" \"${prefix_etc}/init.d/tarantool$id\""
 }
@@ -106,6 +130,7 @@ deploy_instance() {
 deploy() {
 	start=`expr $deploy_current + 1`
 	for instance in `seq $start $deploy_count`; do
+		instance_current=$instance
 		deploy_instance $instance
 	done
 }
@@ -117,26 +142,40 @@ commit() {
 
 # processing command line arguments
 if [ $# -eq 0 ]; then
-	usage
+	usage 1
 fi
+
+deploy_count=0
 while [ $# -ge 1 ]; do
 	case $1 in
 		--yes) act_prompt=0 ; shift 1 ;;
 		--prefix) prefix=$2 ; shift 2 ;;
 		--prefix_var) prefix_var=$2 ; shift 2 ;;
 		--prefix_etc) prefix_etc=$2 ; shift 2 ;;
-		--dry) act_dry=1 ; act_debug=1 ; shift 1;;
-		--debug) act_debug=1; shift 1;;
+		--dry) act_dry=1 ; act_debug=1 ; shift 1 ;;
+		--debug) act_debug=1; shift 1 ;;
 		--status) act_status=1 ; shift 1 ;;
-		--help) usage ; shift 1 ;;
+		--help) usage 0 ; shift 1 ;;
 		*) deploy_count=$1 ; shift 1 ; break ;;
 	esac
 done
 
+set ${prefix:="/usr/local"}
+set ${prefix_var:="/var"}
+set ${prefix_etc:="/etc"}
+
+deploy_cfg="${prefix}/etc/tarantool_deploy.cfg"
+deploy_exists=0
+deploy_current=0
+
 # checking deployment configuration file
 if [ -f $deploy_cfg ]; then
 	deploy_exists=1
 	deploy_current=`cat $deploy_cfg`
+	[ $? -eq 0 ] || error "failed to read deploy config"
+	# validating instance number
+	is_num_positive $deploy_current
+	[ $? -eq 0 ] || error "bad deploy config instance number"
 	# dont' change deploy if it said so in configuration file
 	if [ $deploy_current -eq 0 ]; then
 		log "skipping deploy setup (cancel by config)"
@@ -145,7 +184,7 @@ if [ -f $deploy_cfg ]; then
 fi
 
 # displaying status
-if [ $act_status -ne 0 ]; then 
+if [ $act_status -ne 0 ]; then
 	if [ $deploy_exists -eq 0 ]; then
 		log "no tarantool instances found."
 	else
@@ -155,19 +194,19 @@ if [ $act_status -ne 0 ]; then
 fi
 
 # validating instance number
-[ $deploy_count -eq $deploy_count ] 2>/dev/null && [ $deploy_count -gt 0 ] || \
-	error "bad instance number"
+is_num_positive $deploy_count
+[ $? -eq 0 ] && [ $deploy_count -gt 0 ] || error "bad instance number"
 
 if [ $deploy_count -le $deploy_current ]; then
 	error "expand only is supported (required instances number $deploy_count" \
-	      "is lower/equal than deployed $deploy_current)" 
+	      "is lower/equal than deployed $deploy_current)"
 fi
 
 # asking permission to continue
 if [ $act_prompt -eq 1 ]; then
 	[ $act_dry -ne 0 ] && log "(dry mode)"
 	log "About to extend tarantool instances from $deploy_current to $deploy_count."
-	log "Run? [n/y]"
+	log "Continue? [n/y]"
 	read answer
 	case "$answer" in
 		[Yy]) ;;
@@ -182,3 +221,6 @@ deploy
 commit
 
 log "done"
+
+# __EOF__
+
diff --git a/include/cpu_feature.h b/include/cpu_feature.h
index 7a97590861a2fe6c63d619940bd3138b97d7cb7f..e3b74f70db3c56cbe71c484ebdf7fb2a2d4666d5 100644
--- a/include/cpu_feature.h
+++ b/include/cpu_feature.h
@@ -27,22 +27,34 @@
 
 #include <sys/types.h>
 
-/* CPU feature capabilities to use with cpu_has (feature) */
+/* CPU feature capabilities to use with cpu_has (feature). */
+
+#if defined (__i386__) || defined (__x86_64__)
 enum {
 	cpuf_ht = 0, cpuf_sse4_1, cpuf_sse4_2, cpuf_hypervisor
 };
+#endif
 
-/*	return 1=feature is available, 0=unavailable, -EINVAL = unsupported CPU,
-	-ERANGE = invalid feature
-*/
+/* Check whether CPU has a certain feature.
+ *
+ * @param	feature		indetifier (see above) of the target feature
+ *
+ * @return	1 if feature is available, 0 if unavailable,
+ *		-EINVAL if unsupported CPU, -ERANGE if invalid feature
+ */
 int cpu_has (unsigned int feature);
 
 
-/* hardware-calculate CRC32 for the given data buffer
- * NB: 	requires 1 == cpu_has (cpuf_sse4_2),
- * 		CALLING IT W/O CHECKING for sse4_2 CAN CAUSE SIGABRT
+/* Hardware-calculate CRC32 for the given data buffer.
+ *
+ * @param	crc 		initial CRC
+ * @param	buf			data buffer
+ * @param	len			buffer length
+ *
+ * @pre 	1 == cpu_has (cpuf_sse4_2)
+ * @return	CRC32 value
  */
-u_int32_t crc32c_hw(u_int32_t crc, unsigned char const *p, size_t len);
+u_int32_t crc32c_hw(u_int32_t crc, const unsigned char *buf, unsigned int len);
 
 
 #endif /* TARANTOOL_CPU_FEATURES_H */
diff --git a/include/log_io.h b/include/log_io.h
index d7e8f4f77cc623ce2cb7c8a60fa16508e43a7197..e157a2bb0e0666e74015f0fd8fc156fe1595539a 100644
--- a/include/log_io.h
+++ b/include/log_io.h
@@ -146,6 +146,8 @@ static inline struct row_v11 *row_v11(const struct tbuf *t)
 	return (struct row_v11 *)t->data;
 }
 
+void mach_setup_crc32 ();
+
 struct tbuf *convert_to_v11(struct tbuf *orig, u16 tag, u64 cookie, i64 lsn);
 
 struct recovery_state *recover_init(const char *snap_dirname, const char *xlog_dirname,
diff --git a/mod/box/CMakeLists.txt b/mod/box/CMakeLists.txt
index e0ad40a566d9027293863dba6d4f2f9ee598bf44..dbd0a324aab61fa75b9571a56c8662dd0d883ea8 100644
--- a/mod/box/CMakeLists.txt
+++ b/mod/box/CMakeLists.txt
@@ -28,5 +28,6 @@ set_source_files_properties(memcached-grammar.m
 set_source_files_properties(memcached.m
     PROPERTIES COMPILE_FLAGS "-Wno-uninitialized")
 
-tarantool_module("box" tuple.m index.m box.m box_lua.m memcached.m memcached-grammar.m
+tarantool_module("box" tuple.m index.m tree.m box.m box_lua.m
+    memcached.m memcached-grammar.m
     box.lua.o)
diff --git a/mod/box/box.h b/mod/box/box.h
index 1dd2adac28306ec7e90e134ea97a4b0776efdb73..fcb031a079fa269178c0d05dd45b46a7819473d4 100644
--- a/mod/box/box.h
+++ b/mod/box/box.h
@@ -42,10 +42,40 @@ enum
 };
 
 struct space {
-	int n;
-	bool enabled;
-	int cardinality;
 	Index *index[BOX_INDEX_MAX];
+	int cardinality;
+
+	/**
+	 * The number of indexes in the space.
+	 *
+	 * It is equal to the number of non-nil members of the index
+	 * array and defines the key_defs array size as well.
+	 */
+	int key_count;
+
+	/**
+	 * The descriptors for all indexes that belong to the space.
+	 */
+	struct key_def *key_defs;
+
+	/**
+	 * Field types of indexed fields. This is an array of size
+	 * field_count. If there are gaps, i.e. fields that do not
+	 * participate in any index and thus we cannot infer their
+	 * type, then respective array members have value UNKNOWN.
+	 * XXX: right now UNKNOWN is also set for fields which types
+	 * in two indexes contradict each other.
+	 */
+	enum field_data_type *field_types;
+
+	/**
+	 * Max field no which participates in any of the space indexes.
+	 * Each tuple in this space must have, therefore, at least
+	 * field_count fields.
+	 */
+	int field_count;
+
+	bool enabled;
 };
 
 extern struct space *space;
@@ -119,6 +149,41 @@ ENUM(messages, MESSAGES);
 
 extern iproto_callback rw_callback;
 
+/**
+ * Get space ordinal number.
+ */
+static inline int
+space_n(struct space *sp)
+{
+	assert(sp >= space && sp < (space + BOX_SPACE_MAX));
+	return sp - space;
+}
+
+/**
+ * Get key_def ordinal number.
+ */
+static inline int
+key_def_n(struct space *sp, struct key_def *kp)
+{
+	assert(kp >= sp->key_defs && kp < (sp->key_defs + sp->key_count));
+	return kp - sp->key_defs;
+}
+
+/**
+ * Get index ordinal number.
+ */
+static inline int
+index_n(Index *index)
+{
+	return key_def_n(index->space, index->key_def);
+}
+
+static inline bool
+index_is_primary(Index *index)
+{
+	return index_n(index) == 0;
+}
+
 /* These are used to implement memcached 'GET' */
 static inline struct box_txn *in_txn() { return fiber->mod_data.txn; }
 struct box_txn *txn_begin();
diff --git a/mod/box/box.lua b/mod/box/box.lua
index d38aa96e0241818c0055e861d9488028331718a2..1556becb922fc018ae3118a5c439e29b95687607 100644
--- a/mod/box/box.lua
+++ b/mod/box/box.lua
@@ -20,7 +20,7 @@ end
 -- starts from the key.
 --
 function box.select_range(sno, ino, limit, ...)
-    return box.space[tonumber(sno)].index[tonumber(ino)]:range(tonumber(limit), ...)
+    return box.space[tonumber(sno)].index[tonumber(ino)]:select_range(tonumber(limit), ...)
 end
 
 --
@@ -57,7 +57,7 @@ function box.insert(space, ...)
                                 unpack(tuple)))
 end
 
--- 
+--
 function box.update(space, key, format, ...)
     local ops = {...}
     return box.process(19,
@@ -84,7 +84,10 @@ function box.on_reload_configuration()
     index_mt.pairs = function(index)
         return index.idx.next, index.idx, nil end
     --
-    index_mt.range = function(index, limit, ...)
+    index_mt.next = function(index, ...)
+        return index.idx:next(...) end
+    --
+    index_mt.select_range = function(index, limit, ...)
         local range = {}
         for k, v in index.idx.next, index.idx, ... do
             if #range >= limit then
@@ -99,6 +102,9 @@ function box.on_reload_configuration()
     space_mt.len = function(space) return space.index[0]:len() end
     space_mt.__newindex = index_mt.__newindex
     space_mt.select = function(space, ...) return box.select(space.n, ...) end
+    space_mt.select_range = function(space, ino, limit, ...)
+        return space.index[ino]:select_range(limit, ...)
+    end
     space_mt.insert = function(space, ...) return box.insert(space.n, ...) end
     space_mt.update = function(space, ...) return box.update(space.n, ...) end
     space_mt.replace = function(space, ...) return box.replace(space.n, ...) end
diff --git a/mod/box/box.m b/mod/box/box.m
index b3489b9010638b74054cf8bb38a5730376e0776a..708830bd6886ac53cdaad423f8756cd0cbe43a30 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -47,6 +47,7 @@
 #include <mod/box/tuple.h>
 #include "memcached.h"
 #include "box_lua.h"
+#include "tree.h"
 
 static void box_process_ro(u32 op, struct tbuf *request_data);
 static void box_process_rw(u32 op, struct tbuf *request_data);
@@ -75,6 +76,11 @@ const int BOX_REF_THRESHOLD = 8196;
 
 struct space *space = NULL;
 
+/* Secondary indexes are built in bulk after all data is
+   recovered. This flag indicates that the indexes are
+   already built and ready for use. */
+static bool secondary_indexes_enabled = false;
+
 struct box_snap_row {
 	u32 space;
 	u32 tuple_size;
@@ -83,6 +89,21 @@ struct box_snap_row {
 } __attribute__((packed));
 
 
+static inline int
+index_count(struct space *sp)
+{
+	if (!secondary_indexes_enabled) {
+		/* If the secondary indexes are not enabled yet
+		   then we can use only the primary index. So
+		   return 1 if there is at least one index (which
+		   must be primary) and return 0 otherwise. */
+		return sp->key_count > 0;
+	} else {
+		/* Return the actual number of indexes. */
+		return sp->key_count;
+	}
+}
+
 static inline struct box_snap_row *
 box_snap_row(const struct tbuf *t)
 {
@@ -117,39 +138,48 @@ tuple_txn_ref(struct box_txn *txn, struct box_tuple *tuple)
 	tuple_ref(tuple, +1);
 }
 
-void
+static void
 validate_indexes(struct box_txn *txn)
 {
-	if (space[txn->n].index[1] == nil)
-		return;
-
-	/* There is more than one index. */
-	foreach_index(txn->n, index) {
-		/* XXX: skip the first index here! */
-		for (u32 f = 0; f < index->key_def.part_count; ++f) {
-			if (index->key_def.parts[f].fieldno >= txn->tuple->cardinality)
-				tnt_raise(IllegalParams, :"tuple must have all indexed fields");
-
-			if (index->key_def.parts[f].type == STRING)
-				continue;
+	struct space *sp = &space[txn->n];
+	int n = index_count(sp);
 
-			void *field = tuple_field(txn->tuple, index->key_def.parts[f].fieldno);
-			u32 len = load_varint32(&field);
+	/* Only secondary indexes are validated here. So check to see
+	   if there are any.*/
+	if (n <= 1) {
+		return;
+	}
 
-			if (index->key_def.parts[f].type == NUM && len != sizeof(u32))
+	/* Check to see if the tuple has a sufficient number of fields. */
+	if (txn->tuple->cardinality < sp->field_count)
+		tnt_raise(IllegalParams, :"tuple must have all indexed fields");
+
+	/* Sweep through the tuple and check the field sizes. */
+	u8 *data = txn->tuple->data;
+	for (int f = 0; f < sp->field_count; ++f) {
+		/* Get the size of the current field and advance. */
+		u32 len = load_varint32((void **) &data);
+		data += len;
+
+		/* Check fixed size fields (NUM and NUM64) and skip undefined
+		   size fields (STRING and UNKNOWN). */
+		if (sp->field_types[f] == NUM) {
+			if (len != sizeof(u32))
 				tnt_raise(IllegalParams, :"field must be NUM");
-
-			if (index->key_def.parts[f].type == NUM64 && len != sizeof(u64))
+		} else if (sp->field_types[f] == NUM64) {
+			if (len != sizeof(u64))
 				tnt_raise(IllegalParams, :"field must be NUM64");
 		}
-		if (index->type == TREE && index->key_def.is_unique == false)
-			/* Don't check non unique indexes */
-			continue;
-
-		struct box_tuple *tuple = [index findByTuple: txn->tuple];
+	}
 
-		if (tuple != NULL && tuple != txn->old_tuple)
-			tnt_raise(ClientError, :ER_INDEX_VIOLATION);
+	/* Check key uniqueness */
+	for (int i = 1; i < n; ++i) {
+		Index *index = space[txn->n].index[i];
+		if (index->key_def->is_unique) {
+			struct box_tuple *tuple = [index findByTuple: txn->tuple];
+			if (tuple != NULL && tuple != txn->old_tuple)
+				tnt_raise(ClientError, :ER_INDEX_VIOLATION);
+		}
 	}
 }
 
@@ -184,8 +214,8 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 	if (txn->old_tuple != NULL) {
 #ifndef NDEBUG
 		void *ka, *kb;
-		ka = tuple_field(txn->tuple, txn->index->key_def.parts[0].fieldno);
-		kb = tuple_field(txn->old_tuple, txn->index->key_def.parts[0].fieldno);
+		ka = tuple_field(txn->tuple, txn->index->key_def->parts[0].fieldno);
+		kb = tuple_field(txn->old_tuple, txn->index->key_def->parts[0].fieldno);
 		int kal, kab;
 		kal = load_varint32(&ka);
 		kab = load_varint32(&kb);
@@ -210,8 +240,11 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 		 * Tuple reference counter will be incremented in
 		 * txn_commit().
 		 */
-		foreach_index(txn->n, index)
+		int n = index_count(&space[txn->n]);
+		for (int i = 0; i < n; i++) {
+			Index *index = space[txn->n].index[i];
 			[index replace: NULL :txn->tuple];
+		}
 	}
 
 	txn->out->dup_u32(1); /* Affected tuples */
@@ -224,8 +257,11 @@ static void
 commit_replace(struct box_txn *txn)
 {
 	if (txn->old_tuple != NULL) {
-		foreach_index(txn->n, index)
+		int n = index_count(&space[txn->n]);
+		for (int i = 0; i < n; i++) {
+			Index *index = space[txn->n].index[i];
 			[index replace: txn->old_tuple :txn->tuple];
+		}
 
 		tuple_ref(txn->old_tuple, -1);
 	}
@@ -242,8 +278,11 @@ rollback_replace(struct box_txn *txn)
 	say_debug("rollback_replace: txn->tuple:%p", txn->tuple);
 
 	if (txn->tuple && txn->tuple->flags & GHOST) {
-		foreach_index(txn->n, index)
+		int n = index_count(&space[txn->n]);
+		for (int i = 0; i < n; i++) {
+			Index *index = space[txn->n].index[i];
 			[index remove: txn->tuple];
+		}
 	}
 }
 
@@ -487,22 +526,15 @@ process_select(struct box_txn *txn, u32 limit, u32 offset, struct tbuf *data)
 			return;
 
 		u32 key_cardinality = read_u32(data);
-		void *key = NULL;
 
-		if (key_cardinality != 0)
+		void *key = NULL;
+		if (key_cardinality) {
 			key = read_field(data);
 
-		/*
-		 * For TREE indexes, we allow partially specified
-		 * keys. HASH indexes are always unique and can
-		 * not have multiple parts.
-		 */
-		if (index->type == HASH && key_cardinality != 1)
-			tnt_raise(IllegalParams, :"key must be single valued");
-
-		/* advance remaining fields of a key */
-		for (int i = 1; i < key_cardinality; i++)
-			read_field(data);
+			/* advance remaining fields of a key */
+			for (int i = 1; i < key_cardinality; i++)
+				read_field(data);
+		}
 
 		struct iterator *it = index->position;
 		[index initIterator: it :key :key_cardinality];
@@ -559,8 +591,12 @@ commit_delete(struct box_txn *txn)
 	if (txn->old_tuple == NULL)
 		return;
 
-	foreach_index(txn->n, index)
+	int n = index_count(&space[txn->n]);
+	for (int i = 0; i < n; i++) {
+		Index *index = space[txn->n].index[i];
 		[index remove: txn->old_tuple];
+	}
+
 	tuple_ref(txn->old_tuple, -1);
 }
 
@@ -771,7 +807,7 @@ box_dispatch(struct box_txn *txn, struct tbuf *data)
 		u32 offset = read_u32(data);
 		u32 limit = read_u32(data);
 
-		if (i >= BOX_INDEX_MAX || space[txn->n].index[i] == nil)
+		if (i >= space[txn->n].key_count)
 			tnt_raise(LoggedError, :ER_NO_SUCH_INDEX, i, txn->n);
 		txn->index = space[txn->n].index[i];
 
@@ -919,20 +955,31 @@ xlog_print(struct recovery_state *r __attribute__((unused)), struct tbuf *t)
 	return res;
 }
 
-void
+/** Free a key definition. */
+static void
+key_free(struct key_def *key_def)
+{
+	free(key_def->parts);
+	free(key_def->cmp_order);
+}
+
+static void
 space_free(void)
 {
 	int i;
 	for (i = 0 ; i < BOX_SPACE_MAX ; i++) {
 		if (!space[i].enabled)
 			continue;
+
 		int j;
-		for (j = 0 ; j < BOX_INDEX_MAX ; j++) {
+		for (j = 0 ; j < space[i].key_count; j++) {
 			Index *index = space[i].index[j];
-			if (index == nil)
-				break;
 			[index free];
+			key_free(&space[i].key_defs[j]);
 		}
+
+		free(space[i].key_defs);
+		free(space[i].field_types);
 	}
 }
 
@@ -956,14 +1003,14 @@ key_init(struct key_def *def, struct tarantool_cfg_space_index *cfg_index)
 	}
 
 	/* init def array */
-	def->parts = salloc(sizeof(struct key_part) * def->part_count);
+	def->parts = malloc(sizeof(struct key_part) * def->part_count);
 	if (def->parts == NULL) {
 		panic("can't allocate def parts array for index");
 	}
 
 	/* init compare order array */
 	def->max_fieldno++;
-	def->cmp_order = salloc(def->max_fieldno * sizeof(u32));
+	def->cmp_order = malloc(def->max_fieldno * sizeof(u32));
 	if (def->cmp_order == NULL) {
 		panic("can't allocate def cmp_order array for index");
 	}
@@ -987,6 +1034,55 @@ key_init(struct key_def *def, struct tarantool_cfg_space_index *cfg_index)
 	def->is_unique = cfg_index->unique;
 }
 
+/**
+ * Extract all available field info from keys
+ *
+ * @param space		space to extract field info for
+ * @param key_count	the number of keys
+ * @param key_defs	key description array
+ */
+static void
+space_init_field_types(struct space *space)
+{
+	int i, field_count;
+	int key_count = space->key_count;
+	struct key_def *key_defs = space->key_defs;
+
+	/* find max max field no */
+	field_count = 0;
+	for (i = 0; i < key_count; i++) {
+		field_count = MAX(field_count, key_defs[i].max_fieldno);
+	}
+
+	/* alloc & init field type info */
+	space->field_count = field_count;
+	space->field_types = malloc(field_count * sizeof(enum field_data_type));
+	for (i = 0; i < field_count; i++) {
+		space->field_types[i] = UNKNOWN;
+	}
+
+	/* extract field type info */
+	for (i = 0; i < key_count; i++) {
+		struct key_def *def = &key_defs[i];
+		for (int pi = 0; pi < def->part_count; pi++) {
+			struct key_part *part = &def->parts[pi];
+			assert(part->fieldno < field_count);
+			space->field_types[part->fieldno] = part->type;
+		}
+	}
+
+#ifndef NDEBUG
+	/* validate field type info */
+	for (i = 0; i < key_count; i++) {
+		struct key_def *def = &key_defs[i];
+		for (int pi = 0; pi < def->part_count; pi++) {
+			struct key_part *part = &def->parts[pi];
+			assert(space->field_types[part->fieldno] == part->type);
+		}
+	}
+#endif
+}
+
 static void
 space_config(void)
 {
@@ -1005,33 +1101,49 @@ space_config(void)
 		assert(cfg.memcached_port == 0 || i != cfg.memcached_space);
 
 		space[i].enabled = true;
-
 		space[i].cardinality = cfg_space->cardinality;
+
+		/*
+		 * Collect key/field info. We need aggregate
+		 * information on all keys before we can create
+		 * indexes.
+		 */
+		space[i].key_count = 0;
+		for (int j = 0; cfg_space->index[j] != NULL; ++j) {
+			++space[i].key_count;
+		}
+
+		space[i].key_defs = malloc(space[i].key_count *
+					    sizeof(struct key_def));
+		if (space[i].key_defs == NULL) {
+			panic("can't allocate key def array");
+		}
+		for (int j = 0; cfg_space->index[j] != NULL; ++j) {
+			typeof(cfg_space->index[j]) cfg_index = cfg_space->index[j];
+			key_init(&space[i].key_defs[j], cfg_index);
+		}
+		space_init_field_types(&space[i]);
+
 		/* fill space indexes */
 		for (int j = 0; cfg_space->index[j] != NULL; ++j) {
 			typeof(cfg_space->index[j]) cfg_index = cfg_space->index[j];
-			struct key_def key_def;
-			key_init(&key_def, cfg_index);
 			enum index_type type = STR2ENUM(index_type, cfg_index->type);
-			Index *index = [Index alloc: type :&key_def];
-			[index init: type :&key_def:space + i :j];
+			struct key_def *key_def = &space[i].key_defs[j];
+			Index *index = [Index alloc: type :key_def :&space[i]];
+			[index init: key_def :&space[i]];
 			space[i].index[j] = index;
 		}
-
-		space[i].enabled = true;
-		space[i].n = i;
 		say_info("space %i successfully configured", i);
 	}
 }
 
-void
+static void
 space_init(void)
 {
 	/* Allocate and initialize space memory. */
 	space = palloc(eter_pool, sizeof(struct space) * BOX_SPACE_MAX);
 	memset(space, 0, sizeof(struct space) * BOX_SPACE_MAX);
 
-
 	/* configure regular spaces */
 	space_config();
 
@@ -1039,6 +1151,29 @@ space_init(void)
 	memcached_space_init();
 }
 
+static void
+build_indexes(void)
+{
+	for (u32 n = 0; n < BOX_SPACE_MAX; ++n) {
+		if (space[n].enabled == false)
+			continue;
+		if (space[n].key_count <= 1)
+			continue; /* no secondary keys */
+
+		say_info("Building secondary keys in space %" PRIu32 "...", n);
+
+		Index *pk = space[n].index[0];
+		for (int i = 1; i < space[n].key_count; i++) {
+			Index *index = space[n].index[i];
+			if ([index isKindOf: [TreeIndex class]]) {
+				[(TreeIndex *) index build: pk];
+			}
+		}
+
+		say_info("Space %"PRIu32": done", n);
+	}
+}
+
 static void
 box_process_rw(u32 op, struct tbuf *request_data)
 {
@@ -1232,6 +1367,8 @@ check_spaces(struct tarantool_cfg *conf)
 			return -1;
 		}
 
+		int max_key_fieldno = -1;
+
 		/* check spaces indexes */
 		for (size_t j = 0; space->index[j] != NULL; ++j) {
 			typeof(space->index[j]) index = space->index[j];
@@ -1275,6 +1412,10 @@ check_spaces(struct tarantool_cfg *conf)
 					return -1;
 				}
 
+				if (max_key_fieldno < key->fieldno) {
+					max_key_fieldno = key->fieldno;
+				}
+
 				++index_cardinality;
 			}
 
@@ -1329,6 +1470,34 @@ check_spaces(struct tarantool_cfg *conf)
 				assert(false);
 			}
 		}
+
+		/* Check for index field type conflicts */
+		if (max_key_fieldno >= 0) {
+			char *types = alloca(max_key_fieldno + 1);
+			memset(types, UNKNOWN, max_key_fieldno + 1);
+			for (size_t j = 0; space->index[j] != NULL; ++j) {
+				typeof(space->index[j]) index = space->index[j];
+				for (size_t k = 0; index->key_field[k] != NULL; ++k) {
+					typeof(index->key_field[k]) key = index->key_field[k];
+					int f = key->fieldno;
+					if (f == -1) {
+						break;
+					}
+					enum field_data_type t = STR2ENUM(field_data_type, key->type);
+					assert(t != field_data_type_MAX);
+					if (types[f] != t) {
+						if (types[f] == UNKNOWN) {
+							types[f] = t;
+						} else {
+							out_warning(0, "(space = %zu fieldno = %zu) "
+								    "index field type mismatch", i, f);
+							return -1;
+						}
+					}
+				}
+
+			}
+		}
 	}
 
 	return 0;
@@ -1428,6 +1597,7 @@ void
 mod_free(void)
 {
 	space_free();
+	memcached_free();
 }
 
 void
@@ -1438,6 +1608,9 @@ mod_init(void)
 	title("loading");
 	atexit(mod_free);
 
+	/* disable secondary indexes while loading */
+	secondary_indexes_enabled = false;
+
 	box_lua_init();
 
 	/* initialization spaces */
@@ -1466,8 +1639,12 @@ mod_init(void)
 
 	title("building indexes");
 
+	/* build secondary indexes */
 	build_indexes();
 
+	/* enable secondary indexes now */
+	secondary_indexes_enabled = true;
+
 	title("orphan");
 
 	if (cfg.local_hot_standby) {
diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m
index b8a1364f502880b97fcbd7d7782271e8f2846a77..0a5087d16e6107db622e5454969766f0c3f5ff2c 100644
--- a/mod/box/box_lua.m
+++ b/mod/box/box_lua.m
@@ -292,7 +292,7 @@ lbox_index_tostring(struct lua_State *L)
 {
 	Index *index = lua_checkindex(L, 1);
 	lua_pushfstring(L, "index %d in space %d",
-			index->n, index->space->n);
+			index_n(index), space_n(index->space));
 	return 1;
 }
 
@@ -420,7 +420,7 @@ lbox_index_next(struct lua_State *L)
 			struct tbuf *data = tbuf_alloc(fiber->gc_pool);
 			for (int i = 0; i < argc; ++i)
 				append_key_part(L, i + 2, data,
-						index->key_def.parts[i].type);
+						index->key_def->parts[i].type);
 			key = data->data;
 		}
 		/*
@@ -429,10 +429,10 @@ lbox_index_next(struct lua_State *L)
 		 * keys.
 		*/
 		assert(cardinality != 0);
-		if (cardinality > index->key_def.part_count)
+		if (cardinality > index->key_def->part_count)
 			luaL_error(L, "index.next(): key part count (%d) "
 				   "does not match index cardinality (%d)",
-				   cardinality, index->key_def.part_count);
+				   cardinality, index->key_def->part_count);
 		it = [index allocIterator];
 		[index initIterator: it :key :cardinality];
 		lbox_pushiterator(L, it);
diff --git a/mod/box/index.h b/mod/box/index.h
index 0cc6e85297219e7e4058c073699bad43b06de74b..156d2920ca10c693ecfdedd50b5ce43f66458c97 100644
--- a/mod/box/index.h
+++ b/mod/box/index.h
@@ -38,7 +38,7 @@ struct index;
  * since there is a mismatch between enum name (STRING) and type
  * name literal ("STR"). STR is already used as Objective C type.
  */
-enum field_data_type { NUM, NUM64, STRING, field_data_type_MAX };
+enum field_data_type { UNKNOWN = -1, NUM = 0, NUM64, STRING, field_data_type_MAX };
 extern const char *field_data_type_strs[];
 
 enum index_type { HASH, TREE, index_type_MAX };
@@ -78,29 +78,33 @@ struct key_def {
 
 @interface Index: Object {
  @public
+	/* Index owner space */
 	struct space *space;
+	/* Description of a possibly multipart key. */
+	struct key_def *key_def;
 	/*
 	 * Pre-allocated iterator to speed up the main case of
 	 * box_process(). Should not be used elsewhere.
 	 */
 	struct iterator *position;
-	/* Description of a possibly multipart key. */
-	struct key_def key_def;
-	enum index_type type;
-	bool enabled;
-	/* Relative offset of the index in its namespace. */
-	u32 n;
 };
 
-+ (Index *) alloc: (enum index_type) type_arg :(struct key_def *) key_def_arg;
+/**
+ * Allocate index instance.
+ *
+ * @param type     index type
+ * @param key_def  key part description
+ * @param space    space the index belongs to
+ */
++ (Index *) alloc: (enum index_type) type :(struct key_def *) key_def
+	:(struct space *) space;
 /**
  * Initialize index instance.
  *
+ * @param key_def  key part description
  * @param space    space the index belongs to
- * @param key      key part description
  */
-- (id) init: (enum index_type) type_arg :(struct key_def *) key_def_arg
-	:(struct space *) space_arg :(u32) n_arg;
+- (id) init: (struct key_def *) key_def_arg :(struct space *) space_arg;
 /** Destroy and free index instance. */
 - (void) free;
 /**
@@ -130,12 +134,6 @@ struct iterator {
 	void (*free)(struct iterator *);
 };
 
-#define foreach_index(n, index_var)					\
-	Index *index_var;						\
-	for (Index **index_ptr = space[(n)].index;			\
-	     *index_ptr != nil; index_ptr++)				\
-		if ((index_var = *index_ptr)->enabled)
-
-void build_indexes(void);
+struct box_tuple *iterator_first_equal(struct iterator *it);
 
 #endif /* TARANTOOL_BOX_INDEX_H_INCLUDED */
diff --git a/mod/box/index.m b/mod/box/index.m
index 7786efb7f70f0d356034526005b473267bc75828..e1314b27aeaea9c25fe4c316aa433659cd559579 100644
--- a/mod/box/index.m
+++ b/mod/box/index.m
@@ -24,6 +24,7 @@
  * SUCH DAMAGE.
  */
 #include "index.h"
+#include "tree.h"
 #include "say.h"
 #include "tuple.h"
 #include "pickle.h"
@@ -41,7 +42,7 @@ iterator_next_equal(struct iterator *it __attribute__((unused)))
 	return NULL;
 }
 
-static struct box_tuple *
+struct box_tuple *
 iterator_first_equal(struct iterator *it)
 {
 	it->next_equal = iterator_next_equal;
@@ -57,7 +58,9 @@ iterator_first_equal(struct iterator *it)
 @class HashStrIndex;
 @class TreeIndex;
 
-+ (Index *) alloc: (enum index_type) type :(struct key_def *) key_def
++ (Index *) alloc: (enum index_type) type
+	 :(struct key_def *) key_def
+	 :(struct space *) space
 {
 	switch (type) {
 	case HASH:
@@ -76,20 +79,17 @@ iterator_first_equal(struct iterator *it)
 		}
 		break;
 	case TREE:
-		return [TreeIndex alloc];
+		return [TreeIndex alloc: key_def :space];
 	default:
 		break;
 	}
 	panic("unsupported index type");
 }
 
-- (id) init: (enum index_type) type_arg :(struct key_def *) key_def_arg
-	:(struct space *) space_arg :(u32) n_arg;
+- (id) init: (struct key_def *) key_def_arg :(struct space *) space_arg
 {
 	self = [super init];
-	key_def = *key_def_arg;
-	type = type_arg;
-	n = n_arg;
+	key_def = key_def_arg;
 	space = space_arg;
 	position = [self allocIterator];
 	[self enable];
@@ -98,8 +98,6 @@ iterator_first_equal(struct iterator *it)
 
 - (void) free
 {
-	sfree(key_def.parts);
-	sfree(key_def.cmp_order);
 	position->free(position);
 	[super free];
 }
@@ -240,9 +238,10 @@ hash_iterator_free(struct iterator *iterator)
 - (struct box_tuple *) findByTuple: (struct box_tuple *) tuple
 {
 	/* Hash index currently is always single-part. */
-	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def->parts[0].fieldno);
 	if (field == NULL)
-		tnt_raise(ClientError, :ER_NO_SUCH_FIELD, key_def.parts[0].fieldno);
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD,
+			  key_def->parts[0].fieldno);
 	return [self find: field];
 }
 
@@ -276,7 +275,6 @@ hash_iterator_free(struct iterator *iterator)
 
 - (void) enable
 {
-	enabled = true;
 	int_hash = mh_i32ptr_init();
 }
 
@@ -305,7 +303,7 @@ hash_iterator_free(struct iterator *iterator)
 
 - (void) remove: (struct box_tuple *) tuple
 {
-	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def->parts[0].fieldno);
 	unsigned int field_size = load_varint32(&field);
 	u32 num = *(u32 *)field;
 
@@ -323,7 +321,7 @@ hash_iterator_free(struct iterator *iterator)
 - (void) replace: (struct box_tuple *) old_tuple
 	:(struct box_tuple *) new_tuple
 {
-	void *field = tuple_field(new_tuple, key_def.parts[0].fieldno);
+	void *field = tuple_field(new_tuple, key_def->parts[0].fieldno);
 	u32 field_size = load_varint32(&field);
 	u32 num = *(u32 *)field;
 
@@ -331,7 +329,8 @@ hash_iterator_free(struct iterator *iterator)
 		tnt_raise(IllegalParams, :"key is not u32");
 
 	if (old_tuple != NULL) {
-		void *old_field = tuple_field(old_tuple, key_def.parts[0].fieldno);
+		void *old_field = tuple_field(old_tuple,
+					      key_def->parts[0].fieldno);
 		load_varint32(&old_field);
 		u32 old_num = *(u32 *)old_field;
 		mh_int_t k = mh_i32ptr_get(int_hash, old_num);
@@ -361,18 +360,18 @@ hash_iterator_free(struct iterator *iterator)
 - (void) initIterator: (struct iterator *) iterator :(void *) key
 			:(int) part_count
 {
+	assert(iterator->next = hash_iterator_next);
 	struct hash_iterator *it = hash_iterator(iterator);
 
-	(void) part_count;
-	assert(part_count == 1);
-	assert(iterator->next = hash_iterator_next);
+	if (part_count != 1)
+		tnt_raise(IllegalParams, :"key must be single valued");
 
 	u32 field_size = load_varint32(&key);
-	u32 num = *(u32 *)key;
-
 	if (field_size != 4)
 		tnt_raise(IllegalParams, :"key is not u32");
 
+	u32 num = *(u32 *)key;
+
 	it->base.next_equal = iterator_first_equal;
 	it->h_pos = mh_i32ptr_get(int_hash, num);
 	it->hash = int_hash;
@@ -397,7 +396,6 @@ hash_iterator_free(struct iterator *iterator)
 
 - (void) enable
 {
-	enabled = true;
 	int64_hash = mh_i64ptr_init();
 }
 
@@ -426,7 +424,7 @@ hash_iterator_free(struct iterator *iterator)
 
 - (void) remove: (struct box_tuple *) tuple
 {
-	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def->parts[0].fieldno);
 	unsigned int field_size = load_varint32(&field);
 	u64 num = *(u64 *)field;
 
@@ -444,7 +442,7 @@ hash_iterator_free(struct iterator *iterator)
 - (void) replace: (struct box_tuple *) old_tuple
 	:(struct box_tuple *) new_tuple
 {
-	void *field = tuple_field(new_tuple, key_def.parts[0].fieldno);
+	void *field = tuple_field(new_tuple, key_def->parts[0].fieldno);
 	u32 field_size = load_varint32(&field);
 	u64 num = *(u64 *)field;
 
@@ -453,7 +451,7 @@ hash_iterator_free(struct iterator *iterator)
 
 	if (old_tuple != NULL) {
 		void *old_field = tuple_field(old_tuple,
-					      key_def.parts[0].fieldno);
+					      key_def->parts[0].fieldno);
 		load_varint32(&old_field);
 		u64 old_num = *(u64 *)old_field;
 		mh_int_t k = mh_i64ptr_get(int64_hash, old_num);
@@ -484,17 +482,17 @@ hash_iterator_free(struct iterator *iterator)
 			:(int) part_count
 {
 	assert(iterator->next = hash_iterator_next);
-	assert(part_count == 1);
-	(void) part_count;
-
 	struct hash_iterator *it = hash_iterator(iterator);
 
-	u32 field_size = load_varint32(&field);
-	u64 num = *(u64 *)field;
+	if (part_count != 1)
+		tnt_raise(IllegalParams, :"key must be single valued");
 
+	u32 field_size = load_varint32(&field);
 	if (field_size != 8)
 		tnt_raise(IllegalParams, :"key is not u64");
 
+	u64 num = *(u64 *)field;
+
 	it->base.next_equal = iterator_first_equal;
 	it->h_pos = mh_i64ptr_get(int64_hash, num);
 	it->hash = (struct mh_i32ptr_t *) int64_hash;
@@ -519,7 +517,6 @@ hash_iterator_free(struct iterator *iterator)
 
 - (void) enable
 {
-	enabled = true;
 	str_hash = mh_lstrptr_init();
 }
 
@@ -545,7 +542,7 @@ hash_iterator_free(struct iterator *iterator)
 
 - (void) remove: (struct box_tuple *) tuple
 {
-	void *field = tuple_field(tuple, key_def.parts[0].fieldno);
+	void *field = tuple_field(tuple, key_def->parts[0].fieldno);
 
 	mh_int_t k = mh_lstrptr_get(str_hash, field);
 	if (k != mh_end(str_hash))
@@ -560,15 +557,15 @@ hash_iterator_free(struct iterator *iterator)
 - (void) replace: (struct box_tuple *) old_tuple
 	:(struct box_tuple *) new_tuple
 {
-	void *field = tuple_field(new_tuple, key_def.parts[0].fieldno);
+	void *field = tuple_field(new_tuple, key_def->parts[0].fieldno);
 
 	if (field == NULL)
 		tnt_raise(ClientError, :ER_NO_SUCH_FIELD,
-			  key_def.parts[0].fieldno);
+			  key_def->parts[0].fieldno);
 
 	if (old_tuple != NULL) {
 		void *old_field = tuple_field(old_tuple,
-					      key_def.parts[0].fieldno);
+					      key_def->parts[0].fieldno);
 		mh_int_t k = mh_lstrptr_get(str_hash, old_field);
 		if (k != mh_end(str_hash))
 			mh_lstrptr_del(str_hash, k);
@@ -597,11 +594,11 @@ hash_iterator_free(struct iterator *iterator)
 			:(int) part_count
 {
 	assert(iterator->next = hash_iterator_next);
-	assert(part_count== 1);
-	(void) part_count;
-
 	struct hash_iterator *it = hash_iterator(iterator);
 
+	if (part_count != 1)
+		tnt_raise(IllegalParams, :"key must be single valued");
+
 	it->base.next_equal = iterator_first_equal;
 	it->h_pos = mh_lstrptr_get(str_hash, key);
 	it->hash = (struct mh_i32ptr_t *) str_hash;
@@ -610,444 +607,6 @@ hash_iterator_free(struct iterator *iterator)
 
 /* }}} */
 
-/* {{{ TreeIndex and auxiliary structures. ************************/
-/**
- * A field reference used for TREE indexes. Either stores a copy
- * of the corresponding field in the tuple or points to that field
- * in the tuple (depending on field length).
- */
-struct field {
-	/** Field data length. */
-	u32 len;
-	/** Actual field data. For small fields we store the value
-	 * of the field (u32, u64, strings up to 8 bytes), for
-	 * longer fields, we store a pointer to field data in the
-	 * tuple in the primary index.
-	 */
-	union {
-		u32 u32;
-		u64 u64;
-		u8 data[sizeof(u64)];
-		void *data_ptr;
-	};
-};
-
-const struct field ASTERISK = {
-	.len = UINT32_MAX,
-	{
-		.data_ptr = NULL,
-	}
-};
-
-#define IS_ASTERISK(f) ((f)->len == ASTERISK.len && (f)->data_ptr == ASTERISK.data_ptr)
-
-/** Compare two fields of an index key.
- *
- * @retval 0  two fields are equal
- * @retval -1 f2 is less than f1
- * @retval 1 f2 is greater than f1
- */
-static i8
-field_compare(struct field *f1, struct field *f2, enum field_data_type type)
-{
-	if (IS_ASTERISK(f1) || IS_ASTERISK(f2))
-		return 0;
-
-	if (type == NUM) {
-		assert(f1->len == f2->len);
-		assert(f1->len == sizeof(f1->u32));
-
-		return f1->u32 >f2->u32 ? 1 : f1->u32 == f2->u32 ? 0 : -1;
-	} else if (type == NUM64) {
-		assert(f1->len == f2->len);
-		assert(f1->len == sizeof(f1->u64));
-
-		return f1->u64 >f2->u64 ? 1 : f1->u64 == f2->u64 ? 0 : -1;
-	} else if (type == STRING) {
-		i32 cmp;
-		void *f1_data, *f2_data;
-
-		f1_data = f1->len <= sizeof(f1->data) ? f1->data : f1->data_ptr;
-		f2_data = f2->len <= sizeof(f2->data) ? f2->data : f2->data_ptr;
-
-		cmp = memcmp(f1_data, f2_data, MIN(f1->len, f2->len));
-
-		if (cmp > 0)
-			return 1;
-		else if (cmp < 0)
-			return -1;
-		else if (f1->len == f2->len)
-			return 0;
-		else if (f1->len > f2->len)
-			return 1;
-		else
-			return -1;
-	}
-	panic("impossible happened");
-}
-
-struct tree_el {
-	struct box_tuple *tuple;
-	struct field key[];
-};
-
-#define TREE_EL_SIZE(key) \
-	(sizeof(struct tree_el) + sizeof(struct field) * (key)->part_count)
-
-void
-tree_el_init(struct tree_el *elem,
-		   struct key_def *key_def, struct box_tuple *tuple)
-{
-	void *tuple_data = tuple->data;
-
-	for (i32 i = 0; i < key_def->max_fieldno; ++i) {
-		struct field f;
-
-		if (i < tuple->cardinality) {
-			f.len = load_varint32(&tuple_data);
-			if (f.len <= sizeof(f.data)) {
-				memset(f.data, 0, sizeof(f.data));
-				memcpy(f.data, tuple_data, f.len);
-			} else
-				f.data_ptr = tuple_data;
-			tuple_data += f.len;
-		} else
-			f = ASTERISK;
-
-		u32 fieldno = key_def->cmp_order[i];
-
-		if (fieldno == -1)
-			continue;
-
-		if (key_def->parts[fieldno].type == NUM) {
-			if (f.len != 4)
-				tnt_raise(IllegalParams, :"key is not u32");
-		} else if (key_def->parts[fieldno].type == NUM64 && f.len != 8) {
-				tnt_raise(IllegalParams, :"key is not u64");
-		}
-
-		elem->key[fieldno] = f;
-	}
-	elem->tuple = tuple;
-}
-
-void
-init_search_pattern(struct tree_el *pattern,
-		    struct key_def *key_def, int part_count, void *key)
-{
-	assert(part_count <= key_def->part_count);
-
-	for (i32 i = 0; i < key_def->part_count; ++i)
-		pattern->key[i] = ASTERISK;
-	for (int i = 0; i < part_count; i++) {
-		u32 len;
-
-		len = pattern->key[i].len = load_varint32(&key);
-		if (key_def->parts[i].type == NUM) {
-			if (len != 4)
-				tnt_raise(IllegalParams, :"key is not u32");
-		} else if (key_def->parts[i].type == NUM64 && len != 8) {
-				tnt_raise(IllegalParams, :"key is not u64");
-		}
-		if (len <= sizeof(pattern->key[i].data)) {
-			memset(pattern->key[i].data, 0, sizeof(pattern->key[i].data));
-			memcpy(pattern->key[i].data, key, len);
-		} else
-			pattern->key[i].data_ptr = key;
-
-		key += len;
-	}
-
-	pattern->tuple = NULL;
-}
-/*
- * Compare index_tree elements only by fields defined in
- * index->field_cmp_order.
- * Return:
- *      Common meaning:
- *              < 0  - a is smaller than b
- *              == 0 - a is equal to b
- *              > 0  - a is greater than b
- */
-static int
-tree_el_unique_cmp(struct tree_el *elem_a,
-			 struct tree_el *elem_b,
-			 struct key_def *key_def)
-{
-	int r = 0;
-	for (i32 i = 0, end = key_def->part_count; i < end; ++i) {
-		r = field_compare(&elem_a->key[i], &elem_b->key[i],
-				  key_def->parts[i].type);
-		if (r != 0)
-			break;
-	}
-	return r;
-}
-
-static int
-tree_el_cmp(struct tree_el *elem_a, struct tree_el *elem_b,
-		  struct key_def *key_def)
-{
-	int r = tree_el_unique_cmp(elem_a, elem_b, key_def);
-	if (r == 0 && elem_a->tuple && elem_b->tuple)
-		r = (elem_a->tuple < elem_b->tuple ?
-		     -1 : elem_a->tuple > elem_b->tuple);
-	return r;
-}
-
-#include <third_party/sptree.h>
-SPTREE_DEF(str_t, realloc);
-
-@interface TreeIndex: Index {
-	sptree_str_t *tree;
-	struct tree_el *pattern;
-};
-- (void) build: (Index *) pk;
-@end
-
-struct tree_iterator {
-	struct iterator base;
-	struct sptree_str_t_iterator *t_iter;
-	struct tree_el *pattern;
-	struct key_def *key_def;
-};
-
-static inline struct tree_iterator *
-tree_iterator(struct iterator *it)
-{
-	return (struct tree_iterator *) it;
-}
-
-static struct box_tuple *
-tree_iterator_next(struct iterator *iterator)
-{
-	assert(iterator->next == tree_iterator_next);
-
-	struct tree_iterator *it = tree_iterator(iterator);
-
-	struct tree_el *elem = sptree_str_t_iterator_next(it->t_iter);
-
-	return elem ? elem->tuple : NULL;
-}
-
-void
-tree_iterator_free(struct iterator *iterator)
-{
-	assert(iterator->next == tree_iterator_next);
-	struct tree_iterator *it = tree_iterator(iterator);
-
-	if (it->t_iter)
-		sptree_str_t_iterator_free(it->t_iter);
-	sfree(it);
-}
-
-static struct box_tuple *
-tree_iterator_next_equal(struct iterator *iterator)
-{
-	assert(iterator->next == tree_iterator_next);
-
-	struct tree_iterator *it = tree_iterator(iterator);
-
-	struct tree_el *elem =
-		sptree_str_t_iterator_next(it->t_iter);
-
-	if (elem != NULL &&
-	    tree_el_unique_cmp(it->pattern, elem, it->key_def) == 0) {
-		return elem->tuple;
-	}
-
-	return NULL;
-}
-
-@implementation TreeIndex
-
-- (void) free
-{
-	sfree(pattern);
-	sptree_str_t_destroy(tree);
-	sfree(tree);
-	[super free];
-}
-
-- (void) enable
-{
-	enabled = false;
-	pattern = salloc(TREE_EL_SIZE(&key_def));
-	tree = salloc(sizeof(*tree));
-	memset(tree, 0, sizeof(*tree));
-	if (n == 0) {/* pk */
-		sptree_str_t_init(tree,
-				  TREE_EL_SIZE(&key_def),
-				  NULL, 0, 0,
-				  (void *)tree_el_unique_cmp, &key_def);
-		enabled = true;
-	}
-}
-
-- (size_t) size
-{
-	return tree->size;
-}
-
-- (struct box_tuple *) min
-{
-	struct tree_el *elem = sptree_str_t_first(tree);
-
-	return elem ? elem->tuple : NULL;
-}
-
-- (struct box_tuple *) max
-{
-	struct tree_el *elem = sptree_str_t_last(tree);
-
-	return elem ? elem->tuple : NULL;
-}
-
-- (struct box_tuple *) find: (void *) key
-{
-	init_search_pattern(pattern, &key_def, 1, key);
-	struct tree_el *elem = sptree_str_t_find(tree, pattern);
-
-	return elem ? elem->tuple : NULL;
-}
-
-- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple
-{
-	tree_el_init(pattern, &key_def, tuple);
-
-	struct tree_el *elem = sptree_str_t_find(tree, pattern);
-
-	return elem ? elem->tuple : NULL;
-}
-
-- (void) remove: (struct box_tuple *) tuple
-{
-	tree_el_init(pattern, &key_def, tuple);
-	sptree_str_t_delete(tree, pattern);
-}
-
-- (void) replace: (struct box_tuple *) old_tuple
-	:(struct box_tuple *) new_tuple
-{
-	if (new_tuple->cardinality < key_def.max_fieldno)
-		tnt_raise(ClientError, :ER_NO_SUCH_FIELD,
-			  key_def.max_fieldno);
-
-	if (old_tuple) {
-		tree_el_init(pattern, &key_def, old_tuple);
-		sptree_str_t_delete(tree, pattern);
-	}
-	tree_el_init(pattern, &key_def, new_tuple);
-	sptree_str_t_insert(tree, pattern);
-}
-
-- (struct iterator *) allocIterator
-{
-	struct tree_iterator *it = salloc(sizeof(struct tree_iterator) +
-					  TREE_EL_SIZE(&key_def));
-	if (it) {
-		memset(it, 0, sizeof(struct tree_iterator));
-		it->base.next = tree_iterator_next;
-		it->base.free = tree_iterator_free;
-		it->pattern = (struct tree_el *) (it + 1);
-		it->key_def = &key_def;
-	}
-	return (struct iterator *) it;
-}
-
-- (void) initIterator: (struct iterator *) it
-{
-	[self initIterator: it :NULL :0];
-}
-
-- (void) initIterator: (struct iterator *) iterator :(void *) key
-			:(int) part_count
-{
-	assert(iterator->next == tree_iterator_next);
-
-	struct tree_iterator *it = tree_iterator(iterator);
-
-	if (key_def.is_unique && part_count == key_def.part_count)
-		it->base.next_equal = iterator_first_equal;
-	else
-		it->base.next_equal = tree_iterator_next_equal;
-
-	init_search_pattern(it->pattern, &key_def, part_count, key);
-	sptree_str_t_iterator_init_set(tree, &it->t_iter, it->pattern);
-}
-
-- (void) build: (Index *) pk
-{
-	u32 n_tuples = [pk size];
-	u32 estimated_tuples = n_tuples * 1.2;
-
-	assert(enabled == false);
-
-	struct tree_el *elem = NULL;
-	if (n_tuples) {
-		/*
-		 * Allocate a little extra to avoid
-		 * unnecessary realloc() when more data is
-		 * inserted.
-		*/
-		size_t sz = estimated_tuples * TREE_EL_SIZE(&key_def);
-		elem = malloc(sz);
-		if (elem == NULL)
-			panic("malloc(): failed to allocate %"PRI_SZ" bytes", sz);
-	}
-	struct tree_el *m;
-	u32 i = 0;
-
-	struct iterator *it = pk->position;
-	[pk initIterator: it];
-	struct box_tuple *tuple;
-	while ((tuple = it->next(it))) {
-
-		m = (struct tree_el *)
-			((char *)elem + i * TREE_EL_SIZE(&key_def));
-
-		tree_el_init(m, &key_def, tuple);
-		++i;
-	}
-
-	if (n_tuples)
-		say_info("Sorting %"PRIu32 " keys in index %" PRIu32 "...", n_tuples, self->n);
-
-	/* If n_tuples == 0 then estimated_tuples = 0, elem == NULL, tree is empty */
-	sptree_str_t_init(tree, TREE_EL_SIZE(&key_def),
-			  elem, n_tuples, estimated_tuples,
-			  (void *) (key_def.is_unique ?  tree_el_unique_cmp
-				    : tree_el_cmp), &key_def);
-	enabled = true;
-}
-@end
-
-/* }}} */
-
-void
-build_indexes(void)
-{
-	for (u32 n = 0; n < BOX_SPACE_MAX; ++n) {
-		if (space[n].enabled == false)
-			continue;
-		/* A shortcut to avoid unnecessary log messages. */
-		if (space[n].index[1] == nil)
-			continue; /* no secondary keys */
-		say_info("Building secondary keys in space %" PRIu32 "...", n);
-		Index *pk = space[n].index[0];
-		for (u32 idx = 1;; idx++) {
-			Index *index = space[n].index[idx];
-			if (index == nil)
-				break;
-
-			if (index->type != TREE)
-				continue;
-			[(TreeIndex*) index build: pk];
-		}
-		say_info("Space %"PRIu32": done", n);
-	}
-}
-
 /**
  * vim: foldmethod=marker
  */
diff --git a/mod/box/memcached.h b/mod/box/memcached.h
index b5ec2c64bba420b162e36a4cee1204817aecff59..10d4df108c2bd1a8c1fd9e6a581c018195dccc59 100644
--- a/mod/box/memcached.h
+++ b/mod/box/memcached.h
@@ -33,6 +33,9 @@ struct tarantool_cfg;
 void
 memcached_init();
 
+void
+memcached_free();
+
 void
 memcached_space_init();
 
diff --git a/mod/box/memcached.m b/mod/box/memcached.m
index 903b58ca826295a63317e2daaf6c26008bc6d317..3717294c505f4fc718ef84361c8625c2ce6d1cec 100644
--- a/mod/box/memcached.m
+++ b/mod/box/memcached.m
@@ -47,6 +47,7 @@ static int stat_base;
 static struct fiber *memcached_expire = NULL;
 
 static Index *memcached_index;
+static struct iterator *memcached_it;
 
 /* memcached tuple format:
    <key, meta, data> */
@@ -418,6 +419,13 @@ memcached_init(void)
 	memcached_index = space[cfg.memcached_space].index[0];
 }
 
+void
+memcached_free()
+{
+	if (memcached_it)
+		memcached_it->free(memcached_it);
+}
+
 void
 memcached_space_init()
 {
@@ -428,28 +436,33 @@ memcached_space_init()
 	struct space *memc_s = &space[cfg.memcached_space];
 	memc_s->enabled = true;
 	memc_s->cardinality = 4;
-	memc_s->n = cfg.memcached_space;
 
-	struct key_def key_def;
+	memc_s->key_count = 1;
+	memc_s->key_defs = malloc(sizeof(struct key_def));
+
+	if (memc_s->key_defs == NULL)
+		panic("out of memory when configuring memcached_space");
+
+	struct key_def *key_def = memc_s->key_defs;
 	/* Configure memcached index key. */
-	key_def.part_count = 1;
-	key_def.is_unique = true;
+	key_def->part_count = 1;
+	key_def->is_unique = true;
 
-	key_def.parts = salloc(sizeof(struct key_part));
-	key_def.cmp_order = salloc(sizeof(u32));
+	key_def->parts = malloc(sizeof(struct key_part));
+	key_def->cmp_order = malloc(sizeof(u32));
 
-	if (key_def.parts == NULL || key_def.cmp_order == NULL)
+	if (key_def->parts == NULL || key_def->cmp_order == NULL)
 		panic("out of memory when configuring memcached_space");
 
-	key_def.parts[0].fieldno = 0;
-	key_def.parts[0].type = STRING;
+	key_def->parts[0].fieldno = 0;
+	key_def->parts[0].type = STRING;
 
-	key_def.max_fieldno = 1;
-	key_def.cmp_order[0] = 0;
+	key_def->max_fieldno = 1;
+	key_def->cmp_order[0] = 0;
 
 	/* Configure memcached index. */
-	Index *memc_index = memc_s->index[0] = [Index alloc: HASH :&key_def];
-	[memc_index init: HASH :&key_def :memc_s :0];
+	Index *memc_index = memc_s->index[0] = [Index alloc: HASH :key_def :memc_s];
+	[memc_index init: key_def :memc_s];
 }
 
 /** Delete a bunch of expired keys. */
@@ -488,17 +501,17 @@ memcached_expire_loop(void *data __attribute__((unused)))
 	struct box_tuple *tuple = NULL;
 
 	say_info("memcached expire fiber started");
-	struct iterator *it = [memcached_index allocIterator];
+	memcached_it = [memcached_index allocIterator];
 	@try {
 restart:
 		if (tuple == NULL)
-			[memcached_index initIterator: it];
+			[memcached_index initIterator: memcached_it];
 
 		struct tbuf *keys_to_delete = tbuf_alloc(fiber->gc_pool);
 
 		for (int j = 0; j < cfg.memcached_expire_per_loop; j++) {
 
-			tuple = it->next(it);
+			tuple = memcached_it->next(memcached_it);
 
 			if (tuple == NULL)
 				break;
@@ -513,7 +526,8 @@ restart:
 		fiber_gc();
 		goto restart;
 	} @finally {
-		it->free(it);
+		memcached_it->free(memcached_it);
+		memcached_it = NULL;
 	}
 }
 
@@ -523,7 +537,7 @@ void memcached_start_expire()
 		return;
 
 	assert(memcached_expire == NULL);
-	memcached_expire = fiber_create("memecached_expire", -1,
+	memcached_expire = fiber_create("memcached_expire", -1,
 					-1, memcached_expire_loop, NULL);
 	if (memcached_expire == NULL)
 		say_error("can't start the expire fiber");
diff --git a/mod/box/tree.h b/mod/box/tree.h
new file mode 100644
index 0000000000000000000000000000000000000000..e51f167861a65f60c6d7211344abc94860874b01
--- /dev/null
+++ b/mod/box/tree.h
@@ -0,0 +1,59 @@
+#ifndef TARANTOOL_BOX_TREE_H_INCLUDED
+#define TARANTOOL_BOX_TREE_H_INCLUDED
+/*
+ * Copyright (C) 2011 Mail.RU
+ *
+ * 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 AUTHOR AND CONTRIBUTORS ``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 AUTHOR 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 <third_party/sptree.h>
+
+/**
+ * Instantiate sptree definitions
+ */
+SPTREE_DEF(index, realloc);
+
+typedef int (*tree_cmp_t)(const void *, const void *, void *);
+
+@interface TreeIndex: Index {
+	@public
+	sptree_index tree;
+};
+
++ (Index *) alloc: (struct key_def *) key_def :(struct space *) space;
+- (void) build: (Index *) pk;
+
+/** To be defined in subclasses. */
+- (size_t) node_size;
+- (tree_cmp_t) node_cmp;
+- (tree_cmp_t) dup_node_cmp;
+- (tree_cmp_t) key_node_cmp;
+- (void) fold: (void *) node :(struct box_tuple *) tuple;
+- (struct box_tuple *) unfold: (const void *) node;
+- (int) compare: (const void *) node_a :(const void *) node_b;
+- (int) key_compare: (const void *) key :(const void *) node;
+
+@end
+
+#endif /* TARANTOOL_BOX_TREE_H_INCLUDED */
diff --git a/mod/box/tree.m b/mod/box/tree.m
new file mode 100644
index 0000000000000000000000000000000000000000..d44e13efa8b7f79ea7c21ff33ce179dafe3c9216
--- /dev/null
+++ b/mod/box/tree.m
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2011 Mail.RU
+ *
+ * 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 AUTHOR AND CONTRIBUTORS ``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 AUTHOR 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 "tree.h"
+#include "box.h"
+#include "tuple.h"
+#include <salloc.h>
+#include <pickle.h>
+
+/* {{{ Utilities. *************************************************/
+
+/**
+ * Unsigned 32-bit int comparison.
+ */
+static inline int
+u32_cmp(u32 a, u32 b)
+{
+	return a < b ? -1 : (a > b);
+}
+
+/**
+ * Unsigned 64-bit int comparison.
+ */
+static inline int
+u64_cmp(u64 a, u64 b)
+{
+	return a < b ? -1 : (a > b);
+}
+
+/**
+ * Tuple addrress comparison.
+ */
+static inline int
+ta_cmp(struct box_tuple *tuple_a, struct box_tuple *tuple_b)
+{
+	if (!tuple_a)
+		return 0;
+	if (!tuple_b)
+		return 0;
+	return tuple_a < tuple_b ? -1 : (tuple_a > tuple_b);
+}
+
+/* }}} */
+
+/* {{{ Tree internal data types. **********************************/
+
+/**
+ * Tree types
+ *
+ * There are four specialized kinds of tree indexes optimized for different
+ * combinations of index fields.
+ *
+ * In the most general case tuples consist of variable length fields and the
+ * index uses a sparsely distributed subset of these fields. So to determine
+ * the field location in the tuple it is required to scan all the preceding
+ * fields. To avoid such scans on each access to a tree node we use the SPARSE
+ * tree index structure. It pre-computes on the per-tuple basis the required
+ * field offsets (or immediate field values for NUMs) and stores them in the
+ * corresponding tree node.
+ *
+ * In case the index fields form a dense sequence it is possible to find
+ * each successive field location based on the previous field location. So it
+ * is only required to find the offset of the first index field in the tuple
+ * and store it in the tree node. In this case we use the DENSE tree index
+ * structure.
+ *
+ * In case the index consists of only one small field it is cheaper to
+ * store the field value immediately in the node rather than store the field
+ * offset. In this case we use the NUM32 tree index structure.
+ *
+ * In case the first field offset in a dense sequence is constant there is no
+ * need to store any extra data in the node. For instance, the first index
+ * field may be the first field in the tuple so the offset is always zero or
+ * all the preceding fields may be fixed-size NUMs so the offset is a non-zero
+ * constant. In this case we use the FIXED tree structure.
+ *
+ * Note that there may be fields with unknown types. In particular, if a field
+ * is not used by any index then it doesn't have to be typed. So in many cases
+ * we cannot actually determine if the fields preceding to the index are fixed
+ * size or not. Therefore we may miss the opportunity to use this optimization
+ * in such cases.
+ */
+enum tree_type { TREE_SPARSE, TREE_DENSE, TREE_NUM32, TREE_FIXED };
+
+/**
+ * Representation of a STR field within a sparse tree index.
+
+ * Depending on the STR length we keep either the offset of the field within
+ * the tuple or a copy of the field. Specifically, if the STR length is less
+ * than or equal to 7 then the length is stored in the "length" field while
+ * the copy of the STR data in the "data" field. Otherwise the STR offset in
+ * the tuple is stored in the "offset" field. The "length" field in this case
+ * is set to 0xFF. The actual length has to be read from the tuple.
+ */
+struct sparse_str
+{
+	union
+	{
+		u8 data[7];
+		u32 offset;
+	};
+	u8 length;
+} __attribute__((packed));
+
+#define BIG_LENGTH 0xff
+
+/**
+ * Reprsentation of a tuple field within a sparse tree index.
+ *
+ * For all NUMs and short STRs it keeps a copy of the field, for long STRs
+ * it keeps the offset of the field in the tuple.
+ */
+union sparse_part {
+	u32 num32;
+	u64 num64;
+	struct sparse_str str;
+};
+
+#define _SIZEOF_SPARSE_PARTS(part_count) \
+	(sizeof(union sparse_part) * (part_count))
+
+#define SIZEOF_SPARSE_PARTS(def) _SIZEOF_SPARSE_PARTS((def)->part_count)
+
+/**
+ * Tree nodes for different tree types
+ */
+
+struct sparse_node {
+	struct box_tuple *tuple;
+	union sparse_part parts[];
+} __attribute__((packed));
+
+struct dense_node {
+	struct box_tuple *tuple;
+	u32 offset;
+} __attribute__((packed));
+
+struct num32_node {
+	struct box_tuple *tuple;
+	u32 value;
+} __attribute__((packed));
+
+struct fixed_node {
+	struct box_tuple *tuple;
+};
+
+/**
+ * Representation of data for key search. The data corresponds to some
+ * struct key_def. The part_count field from struct key_data may be less
+ * than or equal to the part_count field from the struct key_def. Thus
+ * the search data may be partially specified.
+ *
+ * For simplicity sake the key search data uses sparse_part internally
+ * regardless of the target kind of tree because there is little benefit
+ * of having the most compact representation of transient search data.
+ */
+struct key_data
+{
+	u8 *data;
+	int part_count;
+	union sparse_part parts[];
+};
+
+/* }}} */
+
+/* {{{ Tree auxiliary functions. **********************************/
+
+/**
+ * Find if the field has fixed offset.
+ */
+static int
+find_fixed_offset(struct space *space, int fieldno, int skip)
+{
+	int i = skip;
+	int offset = 0;
+
+	while (i < fieldno) {
+		/* if the field is unknown give up on it */
+		if (i >= space->field_count || space->field_types[i] == UNKNOWN) {
+			return -1;
+		}
+
+		/* On a fixed length field account for the appropiate
+		   varint length code and for the actual data length */
+		if (space->field_types[i] == NUM) {
+			offset += 1 + 4;
+		} else if (space->field_types[i] == NUM64) {
+			offset += 1 + 8;
+		}
+		/* On a variable length field give up */
+		else {
+			return -1;
+		}
+
+		++i;
+	}
+
+	return offset;
+}
+
+/**
+ * Find the first index field.
+ */
+static u32
+find_first_field(struct key_def *key_def)
+{
+	for (int field = 0; field < key_def->max_fieldno; ++field) {
+		int part = key_def->cmp_order[field];
+		if (part != -1) {
+			return field;
+		}
+	}
+	panic("index field not found");
+}
+
+/**
+ * Find the appropriate tree type for a given key.
+ */
+static enum tree_type
+find_tree_type(struct space *space, struct key_def *key_def)
+{
+	int dense = 1;
+	int fixed = 1;
+
+	/* Scan for the first tuple field used by the index */
+	int field = find_first_field(key_def);
+	if (find_fixed_offset(space, field, 0) < 0) {
+		fixed = 0;
+	}
+
+	/* Check that there are no gaps after the first field */
+	for (; field < key_def->max_fieldno; ++field) {
+		int part = key_def->cmp_order[field];
+		if (part == -1) {
+			dense = 0;
+			break;
+		}
+	}
+
+	/* Return the appropriate type */
+	if (!dense) {
+		return TREE_SPARSE;
+	} else if (fixed) {
+		return TREE_FIXED;
+	} else if (key_def->part_count == 1 && key_def->parts[0].type == NUM) {
+		return TREE_NUM32;
+	} else {
+		return TREE_DENSE;
+	}
+}
+
+/**
+ * Find field offsets/values for a sparse node.
+ */
+static void
+fold_with_sparse_parts(struct key_def *key_def, struct box_tuple *tuple, union sparse_part* parts)
+{
+	u8 *part_data = tuple->data;
+
+	memset(parts, 0, sizeof(parts[0]) * key_def->part_count);
+
+	for (int field = 0; field < key_def->max_fieldno; ++field) {
+		assert(field < tuple->cardinality);
+
+		u8 *data = part_data;
+		u32 len = load_varint32((void**) &data);
+
+		int part = key_def->cmp_order[field];
+		if (part != -1) {
+			if (key_def->parts[part].type == NUM) {
+				if (len != sizeof parts[part].num32) {
+					tnt_raise(IllegalParams, :"key is not u32");
+				}
+				memcpy(&parts[part].num32, data, len);
+			} else if (key_def->parts[part].type == NUM64) {
+				if (len != sizeof parts[part].num64) {
+					tnt_raise(IllegalParams, :"key is not u64");
+				}
+				memcpy(&parts[part].num64, data, len);
+			} else if (len <= sizeof(parts[part].str.data)) {
+				parts[part].str.length = len;
+				memcpy(parts[part].str.data, data, len);
+			} else {
+				parts[part].str.length = BIG_LENGTH;
+				parts[part].str.offset = (u32) (part_data - tuple->data);
+			}
+		}
+
+		part_data = data + len;
+	}
+}
+
+/**
+ * Find field offsets/values for a key.
+ */
+static void
+fold_with_key_parts(struct key_def *key_def, struct key_data *key_data)
+{
+	u8 *part_data = key_data->data;
+	union sparse_part* parts = key_data->parts;
+
+	memset(parts, 0, sizeof(parts[0]) * key_data->part_count);
+
+	for (int part = 0; part < key_data->part_count; ++part) {
+		u8 *data = part_data;
+		u32 len = load_varint32((void**) &data);
+
+		if (key_def->parts[part].type == NUM) {
+			if (len != sizeof parts[part].num32)
+				tnt_raise(IllegalParams, :"key is not u32");
+			memcpy(&parts[part].num32, data, len);
+		} else if (key_def->parts[part].type == NUM64) {
+			if (len != sizeof parts[part].num64)
+				tnt_raise(IllegalParams, :"key is not u64");
+			memcpy(&parts[part].num64, data, len);
+		} else if (len <= sizeof(parts[part].str.data)) {
+			parts[part].str.length = len;
+			memcpy(parts[part].str.data, data, len);
+		} else {
+			parts[part].str.length = BIG_LENGTH;
+			parts[part].str.offset = (u32) (part_data - key_data->data);
+		}
+
+		part_data = data + len;
+	}
+}
+
+/**
+ * Find the offset for a dense node.
+ */
+static u32
+fold_with_dense_offset(struct key_def *key_def, struct box_tuple *tuple)
+{
+	u8 *tuple_data = tuple->data;
+
+	for (int field = 0; field < key_def->max_fieldno; ++field) {
+		assert(field < tuple->cardinality);
+
+		u8 *data = tuple_data;
+		u32 len = load_varint32((void**) &data);
+
+		int part = key_def->cmp_order[field];
+		if (part != -1) {
+			return (u32) (tuple_data - tuple->data);
+		}
+
+		tuple_data = data + len;
+	}
+
+	panic("index field not found");
+}
+
+/**
+ * Find the value for a num32 node.
+ */
+static u32
+fold_with_num32_value(struct key_def *key_def, struct box_tuple *tuple)
+{
+	u8 *tuple_data = tuple->data;
+
+	for (int field = 0; field < key_def->max_fieldno; ++field) {
+		assert(field < tuple->cardinality);
+
+		u8 *data = tuple_data;
+		u32 len = load_varint32((void**) &data);
+
+		int part = key_def->cmp_order[field];
+		if (part != -1) {
+			u32 value;
+			assert(len == sizeof value);
+			memcpy(&value, data, sizeof value);
+			return value;
+		}
+
+		tuple_data = data + len;
+	}
+
+	panic("index field not found");
+}
+
+/**
+ * Compare a part for two keys.
+ */
+static int
+sparse_part_compare(enum field_data_type type,
+		    const u8 *data_a, union sparse_part part_a,
+		    const u8 *data_b, union sparse_part part_b)
+{
+	if (type == NUM) {
+		return u32_cmp(part_a.num32, part_b.num32);
+	} else if (type == NUM64) {
+		return u64_cmp(part_a.num64, part_b.num64);
+	} else {
+		int cmp;
+		const u8 *ad, *bd;
+		u32 al = part_a.str.length;
+		u32 bl = part_b.str.length;
+		if (al == BIG_LENGTH) {
+			ad = data_a + part_a.str.offset;
+			al = load_varint32((void **) &ad);
+		} else {
+			assert(al <= sizeof(part_a.str.data));
+			ad = part_a.str.data;
+		}
+		if (bl == BIG_LENGTH) {
+			bd = data_b + part_b.str.offset;
+			bl = load_varint32((void **) &bd);
+		} else {
+			assert(bl <= sizeof(part_b.str.data));
+			bd = part_b.str.data;
+		}
+
+		cmp = memcmp(ad, bd, MIN(al, bl));
+		if (cmp == 0) {
+			cmp = (int) al - (int) bl;
+		}
+
+		return cmp;
+	}
+}
+
+/**
+ * Compare a key for two sparse nodes.
+ */
+static int
+sparse_node_compare(struct key_def *key_def,
+		    struct box_tuple *tuple_a,
+		    const union sparse_part* parts_a,
+		    struct box_tuple *tuple_b,
+		    const union sparse_part* parts_b)
+{
+	for (int part = 0; part < key_def->part_count; ++part) {
+		int r = sparse_part_compare(key_def->parts[part].type,
+					    tuple_a->data, parts_a[part],
+					    tuple_b->data, parts_b[part]);
+		if (r) {
+			return r;
+		}
+	}
+	return 0;
+}
+
+/**
+ * Compare a key for a key search data and a sparse node.
+ */
+static int
+sparse_key_node_compare(struct key_def *key_def,
+			const struct key_data *key_data,
+			struct box_tuple *tuple,
+			const union sparse_part* parts)
+{
+	for (int part = 0; part < key_data->part_count; ++part) {
+		int r = sparse_part_compare(key_def->parts[part].type,
+					    key_data->data,
+					    key_data->parts[part],
+					    tuple->data, parts[part]);
+		if (r) {
+			return r;
+		}
+	}
+	return 0;
+}
+
+/**
+ * Compare a part for two dense keys.
+ */
+static int
+dense_part_compare(enum field_data_type type, const u8 *data_a,
+		   u32 offset_a, const u8 *data_b, u32 offset_b)
+{
+	const u8 *ad = data_a + offset_a;
+	const u8 *bd = data_b + offset_b;
+	u32 al = load_varint32((void *) &ad);
+	u32 bl = load_varint32((void *) &bd);
+	if (type == NUM) {
+		u32 an, bn;
+		assert(al == sizeof an && bl == sizeof bn);
+		memcpy(&an, ad, sizeof an);
+		memcpy(&bn, bd, sizeof bn);
+		return u32_cmp(an, bn);
+	} else if (type == NUM64) {
+		u64 an, bn;
+		assert(al == sizeof an && bl == sizeof bn);
+		memcpy(&an, ad, sizeof an);
+		memcpy(&bn, bd, sizeof bn);
+		return u64_cmp(an, bn);
+	} else {
+		int cmp = memcmp(ad, bd, MIN(al, bl));
+		if (cmp == 0) {
+			cmp = (int) al - (int) bl;
+		}
+		return cmp;
+	}
+}
+
+/**
+ * Compare a key for two dense nodes.
+ */
+static int
+dense_node_compare(struct key_def *key_def, u32 first_field,
+		   struct box_tuple *tuple_a, u32 offset_a,
+		   struct box_tuple *tuple_b, u32 offset_b)
+{
+	/* find field offsets */
+	u32 off_a[key_def->part_count];
+	u32 off_b[key_def->part_count];
+	u8 *ad = tuple_a->data + offset_a;
+	u8 *bd = tuple_b->data + offset_b;
+	for (int i = 0; i < key_def->part_count; ++i) {
+		assert(first_field + i < tuple_a->cardinality);
+		assert(first_field + i < tuple_b->cardinality);
+		off_a[i] = ad - tuple_a->data;
+		off_b[i] = bd - tuple_b->data;
+		u32 al = load_varint32((void**) &ad);
+		u32 bl = load_varint32((void**) &bd);
+		ad += al;
+		bd += bl;
+	}
+
+	/* compare key parts */
+	for (int part = 0; part < key_def->part_count; ++part) {
+		int field = key_def->parts[part].fieldno;
+		int r = dense_part_compare(key_def->parts[part].type,
+					   tuple_a->data,
+					   off_a[field - first_field],
+					   tuple_b->data,
+					   off_b[field - first_field]);
+		if (r) {
+			return r;
+		}
+	}
+	return 0;
+}
+
+/**
+ * Compare a part for a key search data and a dense key.
+ */
+static int
+dense_key_part_compare(enum field_data_type type,
+		       const u8 *data_a, union sparse_part part_a,
+		       const u8 *data_b, u32 offset_b)
+{
+	const u8 *bd = data_b + offset_b;
+	u32 bl = load_varint32((void *) &bd);
+	if (type == NUM) {
+		u32 an, bn;
+		an = part_a.num32;
+		assert(bl == sizeof bn);
+		memcpy(&bn, bd, sizeof bn);
+		return u32_cmp(an, bn);
+	} else if (type == NUM64) {
+		u64 an, bn;
+		an = part_a.num64;
+		assert(bl == sizeof bn);
+		memcpy(&bn, bd, sizeof bn);
+		return u64_cmp(an, bn);
+	} else {
+		int cmp;
+		const u8 *ad;
+		u32 al = part_a.str.length;
+		if (al == BIG_LENGTH) {
+			ad = data_a + part_a.str.offset;
+			al = load_varint32((void **) &ad);
+		} else {
+			assert(al <= sizeof(part_a.str.data));
+			ad = part_a.str.data;
+		}
+
+		cmp = memcmp(ad, bd, MIN(al, bl));
+		if (cmp == 0) {
+			cmp = (int) al - (int) bl;
+		}
+
+		return cmp;
+	}
+}
+
+/**
+ * Compare a key for a key search data and a dense node.
+ */
+static int
+dense_key_node_compare(struct key_def *key_def,
+		       const struct key_data *key_data,
+		       u32 first_field, struct box_tuple *tuple, u32 offset)
+{
+	/* find field offsets */
+	u32 off[key_def->part_count];
+	u8 *data = tuple->data + offset;
+	for (int i = 0; i < key_def->part_count; ++i) {
+		assert(first_field + i < tuple->cardinality);
+		off[i] = data - tuple->data;
+		u32 len = load_varint32((void**) &data);
+		data += len;
+	}
+
+	/* compare key parts */
+	for (int part = 0; part < key_data->part_count; ++part) {
+		int field = key_def->parts[part].fieldno;
+		int r = dense_key_part_compare(key_def->parts[part].type,
+					       key_data->data,
+					       key_data->parts[part],
+					       tuple->data,
+					       off[field - first_field]);
+		if (r) {
+			return r;
+		}
+	}
+	return 0;
+}
+
+/* }}} */
+
+/* {{{ Tree iterator **********************************************/
+
+struct tree_iterator {
+	struct iterator base;
+	TreeIndex *index;
+	struct sptree_index_iterator *iter;
+	struct key_data key_data;
+};
+
+static inline struct tree_iterator *
+tree_iterator(struct iterator *it)
+{
+	return (struct tree_iterator *) it;
+}
+
+static struct box_tuple *
+tree_iterator_next(struct iterator *iterator)
+{
+	assert(iterator->next == tree_iterator_next);
+	struct tree_iterator *it = tree_iterator(iterator);
+
+	void *node = sptree_index_iterator_next(it->iter);
+	return [it->index unfold: node];
+}
+
+static struct box_tuple *
+tree_iterator_next_equal(struct iterator *iterator)
+{
+	assert(iterator->next == tree_iterator_next);
+	struct tree_iterator *it = tree_iterator(iterator);
+
+	void *node = sptree_index_iterator_next(it->iter);
+	if (node != NULL
+	    && it->index->tree.compare(&it->key_data, node, it->index) == 0) {
+		return [it->index unfold: node];
+	}
+
+	return NULL;
+}
+
+static void
+tree_iterator_free(struct iterator *iterator)
+{
+	assert(iterator->next == tree_iterator_next);
+	struct tree_iterator *it = tree_iterator(iterator);
+
+	if (it->iter)
+		sptree_index_iterator_free(it->iter);
+
+	sfree(it);
+}
+
+/* }}} */
+
+/* {{{ TreeIndex -- base tree index class *************************/
+
+@implementation TreeIndex
+
+@class SparseTreeIndex;
+@class DenseTreeIndex;
+@class Num32TreeIndex;
+@class FixedTreeIndex;
+
++ (Index *) alloc: (struct key_def *) key_def :(struct space *) space
+{
+	enum tree_type type = find_tree_type(space, key_def);
+	switch (type) {
+	case TREE_SPARSE:
+		return [SparseTreeIndex alloc];
+	case TREE_DENSE:
+		return [DenseTreeIndex alloc];
+	case TREE_NUM32:
+		return [Num32TreeIndex alloc];
+	case TREE_FIXED:
+		return [FixedTreeIndex alloc];
+	}
+	panic("tree index type not implemented");
+}
+
+- (void) free
+{
+	sptree_index_destroy(&tree);
+	[super free];
+}
+
+- (void) enable
+{
+	memset(&tree, 0, sizeof tree);
+	if (index_is_primary(self)) {
+		sptree_index_init(&tree,
+				  [self node_size], NULL, 0, 0,
+				  [self key_node_cmp], [self node_cmp],
+				  self);
+	}
+}
+
+- (size_t) size
+{
+	return tree.size;
+}
+
+- (struct box_tuple *) min
+{
+	void *node = sptree_index_first(&tree);
+	return [self unfold: node];
+}
+
+- (struct box_tuple *) max
+{
+	void *node = sptree_index_last(&tree);
+	return [self unfold: node];
+}
+
+- (struct box_tuple *) find: (void *) key
+{
+	struct key_data *key_data
+		= alloca(sizeof(struct key_data) + _SIZEOF_SPARSE_PARTS(1));
+
+	key_data->data = key;
+	key_data->part_count = 1;
+	fold_with_key_parts(key_def, key_data);
+
+	void *node = sptree_index_find(&tree, key_data);
+	return [self unfold: node];
+}
+
+- (struct box_tuple *) findByTuple: (struct box_tuple *) tuple
+{
+	struct key_data *key_data
+		= alloca(sizeof(struct key_data) + _SIZEOF_SPARSE_PARTS(tuple->cardinality));
+
+	key_data->data = tuple->data;
+	key_data->part_count = tuple->cardinality;
+	fold_with_sparse_parts(key_def, tuple, key_data->parts);
+
+	void *node = sptree_index_find(&tree, key_data);
+	return [self unfold: node];
+}
+
+- (void) remove: (struct box_tuple *) tuple
+{
+	void *node = alloca([self node_size]);
+	[self fold: node :tuple];
+	sptree_index_delete(&tree, node);
+}
+
+- (void) replace: (struct box_tuple *) old_tuple
+		: (struct box_tuple *) new_tuple
+{
+	if (new_tuple->cardinality < key_def->max_fieldno)
+		tnt_raise(ClientError, :ER_NO_SUCH_FIELD,
+			  key_def->max_fieldno);
+
+	void *node = alloca([self node_size]);
+	if (old_tuple) {
+		[self fold: node :old_tuple];
+		sptree_index_delete(&tree, node);
+	}
+	[self fold: node :new_tuple];
+	sptree_index_insert(&tree, node);
+}
+
+- (struct iterator *) allocIterator
+{
+	struct tree_iterator *it
+		= salloc(sizeof(struct tree_iterator) + SIZEOF_SPARSE_PARTS(key_def));
+
+	if (it) {
+		memset(it, 0, sizeof(struct tree_iterator));
+		it->index = self;
+		it->base.next = tree_iterator_next;
+		it->base.free = tree_iterator_free;
+	}
+	return (struct iterator *) it;
+}
+
+- (void) initIterator: (struct iterator *) iterator
+{
+	[self initIterator: iterator :NULL :0];
+}
+
+- (void) initIterator: (struct iterator *) iterator
+		     : (void *) key
+		     : (int) part_count
+{
+	assert(iterator->next == tree_iterator_next);
+	struct tree_iterator *it = tree_iterator(iterator);
+
+	if (key_def->is_unique && part_count == key_def->part_count)
+		it->base.next_equal = iterator_first_equal;
+	else
+		it->base.next_equal = tree_iterator_next_equal;
+
+	it->key_data.data = key;
+	it->key_data.part_count = part_count;
+	fold_with_key_parts(key_def, &it->key_data);
+	sptree_index_iterator_init_set(&tree, &it->iter, &it->key_data);
+}
+
+- (void) build: (Index *) pk
+{
+	u32 n_tuples = [pk size];
+	u32 estimated_tuples = n_tuples * 1.2;
+	int node_size = [self node_size];
+
+	void *nodes = NULL;
+	if (n_tuples) {
+		/*
+		 * Allocate a little extra to avoid
+		 * unnecessary realloc() when more data is
+		 * inserted.
+		*/
+		size_t sz = estimated_tuples * node_size;
+		nodes = malloc(sz);
+		if (nodes == NULL) {
+			panic("malloc(): failed to allocate %"PRI_SZ" bytes", sz);
+		}
+	}
+
+	struct iterator *it = pk->position;
+	[pk initIterator: it];
+
+	struct box_tuple *tuple;
+	for (u32 i = 0; (tuple = it->next(it)) != NULL; ++i) {
+		void *node = ((u8 *) nodes + i * node_size);
+		[self fold: node :tuple];
+	}
+
+	if (n_tuples) {
+		say_info("Sorting %"PRIu32 " keys in index %" PRIu32 "...", n_tuples,
+			 index_n(self));
+	}
+
+	/* If n_tuples == 0 then estimated_tuples = 0, elem == NULL, tree is empty */
+	sptree_index_init(&tree,
+			  node_size, nodes, n_tuples, estimated_tuples,
+			  [self key_node_cmp],
+			  key_def->is_unique ? [self node_cmp] : [self dup_node_cmp],
+			  self);
+}
+
+- (size_t) node_size
+{
+	[self subclassResponsibility: _cmd];
+	return 0;
+}
+
+- (tree_cmp_t) node_cmp
+{
+	[self subclassResponsibility: _cmd];
+	return 0;
+}
+
+- (tree_cmp_t) dup_node_cmp
+{
+	[self subclassResponsibility: _cmd];
+	return 0;
+}
+
+- (tree_cmp_t) key_node_cmp
+{
+	[self subclassResponsibility: _cmd];
+	return 0;
+}
+
+- (void) fold: (void *) node :(struct box_tuple *) tuple
+{
+	(void) node;
+	(void) tuple;
+	[self subclassResponsibility: _cmd];
+}
+
+- (struct box_tuple *) unfold: (const void *) node
+{
+	(void) node;
+	[self subclassResponsibility: _cmd];
+	return NULL;
+}
+
+- (int) compare: (const void *) node_a :(const void *) node_b
+{
+	(void) node_a;
+	(void) node_b;
+	[self subclassResponsibility: _cmd];
+	return 0;
+}
+
+- (int) key_compare: (const void *) key :(const void *) node
+{
+	(void) key;
+	(void) node;
+	[self subclassResponsibility: _cmd];
+	return 0;
+}
+
+@end
+
+/* }}} */
+
+/* {{{ SparseTreeIndex ********************************************/
+
+@interface SparseTreeIndex: TreeIndex
+@end
+
+static int
+sparse_node_cmp(const void *node_a, const void *node_b, void *arg)
+{
+	SparseTreeIndex *index = (SparseTreeIndex *) arg;
+	const struct sparse_node *node_xa = node_a;
+	const struct sparse_node *node_xb = node_b;
+	return sparse_node_compare(index->key_def,
+				   node_xa->tuple, node_xa->parts,
+				   node_xb->tuple, node_xb->parts);
+}
+
+static int
+sparse_dup_node_cmp(const void *node_a, const void *node_b, void *arg)
+{
+	int r = sparse_node_cmp(node_a, node_b, arg);
+	if (r == 0) {
+		const struct sparse_node *node_xa = node_a;
+		const struct sparse_node *node_xb = node_b;
+		r = ta_cmp(node_xa->tuple, node_xb->tuple);
+	}
+	return r;
+}
+
+static int
+sparse_key_node_cmp(const void *key, const void *node, void *arg)
+{
+	SparseTreeIndex *index = (SparseTreeIndex *) arg;
+	const struct key_data *key_data = key;
+	const struct sparse_node *node_x = node;
+	return sparse_key_node_compare(index->key_def, key_data,
+				       node_x->tuple, node_x->parts);
+}
+
+@implementation SparseTreeIndex
+
+- (size_t) node_size
+{
+	return sizeof(struct sparse_node) + SIZEOF_SPARSE_PARTS(key_def);
+}
+
+- (tree_cmp_t) node_cmp
+{
+	return sparse_node_cmp;
+}
+
+- (tree_cmp_t) dup_node_cmp
+{
+	return sparse_dup_node_cmp;
+}
+
+- (tree_cmp_t) key_node_cmp
+{
+	return sparse_key_node_cmp;
+}
+
+- (void) fold: (void *) node :(struct box_tuple *) tuple
+{
+	struct sparse_node *node_x = node;
+	node_x->tuple = tuple;
+	fold_with_sparse_parts(key_def, tuple, node_x->parts);
+}
+
+- (struct box_tuple *) unfold: (const void *) node
+{
+	const struct sparse_node *node_x = node;
+	return node_x ? node_x->tuple : NULL;
+}
+
+@end
+
+/* }}} */
+
+/* {{{ DenseTreeIndex *********************************************/
+
+@interface DenseTreeIndex: TreeIndex {
+	@public
+	u32 first_field;
+}
+@end
+
+static int
+dense_node_cmp(const void *node_a, const void *node_b, void *arg)
+{
+	DenseTreeIndex *index = (DenseTreeIndex *) arg;
+	const struct dense_node *node_xa = node_a;
+	const struct dense_node *node_xb = node_b;
+	return dense_node_compare(index->key_def, index->first_field,
+				  node_xa->tuple, node_xa->offset,
+				  node_xb->tuple, node_xb->offset);
+}
+
+static int
+dense_dup_node_cmp(const void *node_a, const void *node_b, void *arg)
+{
+	int r = dense_node_cmp(node_a, node_b, arg);
+	if (r == 0) {
+		const struct dense_node *node_xa = node_a;
+		const struct dense_node *node_xb = node_b;
+		r = ta_cmp(node_xa->tuple, node_xb->tuple);
+	}
+	return r;
+}
+
+static int
+dense_key_node_cmp(const void *key, const void * node, void *arg)
+{
+	DenseTreeIndex *index = (DenseTreeIndex *) arg;
+	const struct key_data *key_data = key;
+	const struct dense_node *node_x = node;
+	return dense_key_node_compare(index->key_def, key_data,
+				      index->first_field,
+				      node_x->tuple, node_x->offset);
+}
+
+@implementation DenseTreeIndex
+
+- (void) enable
+{
+	[super enable];
+	first_field = find_first_field(key_def);
+}
+
+- (size_t) node_size
+{
+	return sizeof(struct dense_node);
+}
+
+- (tree_cmp_t) node_cmp
+{
+	return dense_node_cmp;
+}
+
+- (tree_cmp_t) dup_node_cmp
+{
+	return dense_dup_node_cmp;
+}
+
+- (tree_cmp_t) key_node_cmp
+{
+	return dense_key_node_cmp;
+}
+
+- (void) fold: (void *) node :(struct box_tuple *) tuple
+{
+	struct dense_node *node_x = node;
+	node_x->tuple = tuple;
+	node_x->offset = fold_with_dense_offset(key_def, tuple);
+}
+
+- (struct box_tuple *) unfold: (const void *) node
+{
+	const struct dense_node *node_x = node;
+	return node_x ? node_x->tuple : NULL;
+}
+
+@end
+
+/* }}} */
+
+/* {{{ Num32TreeIndex *********************************************/
+
+@interface Num32TreeIndex: TreeIndex
+@end
+
+static int
+num32_node_cmp(const void * node_a, const void * node_b, void *arg)
+{
+	(void) arg;
+	const struct num32_node *node_xa = node_a;
+	const struct num32_node *node_xb = node_b;
+	return u32_cmp(node_xa->value, node_xb->value);
+}
+
+static int
+num32_dup_node_cmp(const void * node_a, const void * node_b, void *arg)
+{
+	int r = num32_node_cmp(node_a, node_b, arg);
+	if (r == 0) {
+		const struct num32_node *node_xa = node_a;
+		const struct num32_node *node_xb = node_b;
+		r = ta_cmp(node_xa->tuple, node_xb->tuple);
+	}
+	return r;
+}
+
+static int
+num32_key_node_cmp(const void * key, const void * node, void *arg)
+{
+	(void) arg;
+	const struct key_data *key_data = key;
+	const struct num32_node *node_x = node;
+	return u32_cmp(key_data->parts[0].num32, node_x->value);
+}
+
+@implementation Num32TreeIndex
+
+- (size_t) node_size
+{
+	return sizeof(struct num32_node);
+}
+
+- (tree_cmp_t) node_cmp
+{
+	return num32_node_cmp;
+}
+
+- (tree_cmp_t) dup_node_cmp
+{
+	return num32_dup_node_cmp;
+}
+
+- (tree_cmp_t) key_node_cmp
+{
+	return num32_key_node_cmp;
+}
+
+- (void) fold: (void *) node :(struct box_tuple *) tuple
+{
+	struct num32_node *node_x = (struct num32_node *) node;
+	node_x->tuple = tuple;
+	node_x->value = fold_with_num32_value(key_def, tuple);
+}
+
+- (struct box_tuple *) unfold: (const void *) node
+{
+	const struct num32_node *node_x = node;
+	return node_x ? node_x->tuple : NULL;
+}
+
+@end
+
+/* }}} */
+
+/* {{{ FixedTreeIndex *********************************************/
+
+@interface FixedTreeIndex: TreeIndex {
+	@public
+	u32 first_field;
+	u32 first_offset;
+}
+@end
+
+static int
+fixed_node_cmp(const void *node_a, const void *node_b, void *arg)
+{
+	FixedTreeIndex *index = (FixedTreeIndex *) arg;
+	const struct fixed_node *node_xa = node_a;
+	const struct fixed_node *node_xb = node_b;
+	return dense_node_compare(index->key_def, index->first_field,
+				  node_xa->tuple, index->first_offset,
+				  node_xb->tuple, index->first_offset);
+}
+
+static int
+fixed_dup_node_cmp(const void *node_a, const void *node_b, void *arg)
+{
+	int r = fixed_node_cmp(node_a, node_b, arg);
+	if (r == 0) {
+		const struct fixed_node *node_xa = node_a;
+		const struct fixed_node *node_xb = node_b;
+		r = ta_cmp(node_xa->tuple, node_xb->tuple);
+	}
+	return r;
+}
+
+static int
+fixed_key_node_cmp(const void *key, const void * node, void *arg)
+{
+	FixedTreeIndex *index = (FixedTreeIndex *) arg;
+	const struct key_data *key_data = key;
+	const struct fixed_node *node_x = node;
+	return dense_key_node_compare(index->key_def, key_data,
+				      index->first_field,
+				      node_x->tuple, index->first_offset);
+}
+
+@implementation FixedTreeIndex
+
+- (void) enable
+{
+	[super enable];
+	first_field = find_first_field(key_def);
+	first_offset = find_fixed_offset(space, first_field, 0);
+}
+
+- (size_t) node_size
+{
+	return sizeof(struct fixed_node);
+}
+
+- (tree_cmp_t) node_cmp
+{
+	return fixed_node_cmp;
+}
+
+- (tree_cmp_t) dup_node_cmp
+{
+	return fixed_dup_node_cmp;
+}
+
+- (tree_cmp_t) key_node_cmp
+{
+	return fixed_key_node_cmp;
+}
+
+- (void) fold: (void *) node :(struct box_tuple *) tuple
+{
+	struct fixed_node *node_x = (struct fixed_node *) node;
+	node_x->tuple = tuple;
+}
+
+- (struct box_tuple *) unfold: (const void *) node
+{
+	const struct fixed_node *node_x = node;
+	return node_x ? node_x->tuple : NULL;
+}
+
+@end
+
+/* }}} */
+
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 5298f02c1cc321ffed0c424db52db1c5e75f7348..b77e58c7141ad3293f525a578cd9247376f5daa5 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -14,7 +14,7 @@ if ("${CPACK_GENERATOR}" STREQUAL "RPM")
 		 DESTINATION share/tarantool)
 else()
 	install (FILES ${CMAKE_SOURCE_DIR}/test/box/tarantool.cfg
-		DESTINATION bin)
+		DESTINATION "${CMAKE_SYSCONF_DIR}")
 	install (FILES ${CMAKE_SOURCE_DIR}/test/box/00000000000000000001.snap
-		DESTINATION bin)
+		DESTINATION "${CMAKE_LOCALSTATE_DIR}/lib/tarantool")
 endif()
diff --git a/test/box/args.result b/test/box/args.result
index 7ff5654dbafa63c00b282ab42e851b07cbb2a9a0..92cb78ea0733c3806abc2d55ac50d06e48b9c986 100644
--- a/test/box/args.result
+++ b/test/box/args.result
@@ -98,3 +98,10 @@ tarantool_box: creating `snapshots/00000000000000000001.snap.inprogress'
 tarantool_box: saving snapshot `snapshots/00000000000000000001.snap'
 tarantool_box: done
 
+#
+# A test case for Bug#897162, cat command should
+# not require a configuration file.
+
+tarantool_box --config=nonexists.cfg --cat=nonexists.xlog
+tarantool_box: access("nonexists.xlog"): No such file or directory
+
diff --git a/test/box/args.test b/test/box/args.test
index f51766a25af318a735dc407b3dbb6b6a1eb7d9db..2d014d162aafd4ee67245817bf08200b0a61b069 100644
--- a/test/box/args.test
+++ b/test/box/args.test
@@ -56,5 +56,13 @@ sys.stdout.pop_filter()
 os.unlink(cfg)
 shutil.rmtree(os.path.join(vardir, "bug726778"))
 
+print """#
+# A test case for Bug#897162, cat command should
+# not require a configuration file.
+"""
+sys.stdout.push_filter("(/\S+)+/tarantool", "tarantool")
+server.test_option("--config=nonexists.cfg --cat=nonexists.xlog")
+sys.stdout.pop_filter()
+
 # Args filter cleanup
 # vim: syntax=python
diff --git a/test/box/cat.result b/test/box/cat.result
new file mode 100644
index 0000000000000000000000000000000000000000..6d6d507f2f530bec7f7bc92fe9fbf4dfa06917a6
--- /dev/null
+++ b/test/box/cat.result
@@ -0,0 +1,506 @@
+
+# Verify that the server starts from a pre-recorded snapshot.
+# This way we check that the server can read old snapshots (v11)
+# going forward.
+
+tarantool_box --cat=00000000000000000500.snap
+n:0 1: {'I am a tuple 1'}
+n:0 2: {'I am a tuple 2'}
+n:0 3: {'I am a tuple 3'}
+n:0 4: {'I am a tuple 4'}
+n:0 5: {'I am a tuple 5'}
+n:0 6: {'I am a tuple 6'}
+n:0 7: {'I am a tuple 7'}
+n:0 8: {'I am a tuple 8'}
+n:0 9: {'I am a tuple 9'}
+n:0 10: {'I am a tuple 10'}
+n:0 11: {'I am a tuple 11'}
+n:0 12: {'I am a tuple 12'}
+n:0 13: {'I am a tuple 13'}
+n:0 14: {'I am a tuple 14'}
+n:0 15: {'I am a tuple 15'}
+n:0 16: {'I am a tuple 16'}
+n:0 17: {'I am a tuple 17'}
+n:0 18: {'I am a tuple 18'}
+n:0 19: {'I am a tuple 19'}
+n:0 20: {'I am a tuple 20'}
+n:0 21: {'I am a tuple 21'}
+n:0 22: {'I am a tuple 22'}
+n:0 23: {'I am a tuple 23'}
+n:0 24: {'I am a tuple 24'}
+n:0 25: {'I am a tuple 25'}
+n:0 26: {'I am a tuple 26'}
+n:0 27: {'I am a tuple 27'}
+n:0 28: {'I am a tuple 28'}
+n:0 29: {'I am a tuple 29'}
+n:0 30: {'I am a tuple 30'}
+n:0 31: {'I am a tuple 31'}
+n:0 32: {'I am a tuple 32'}
+n:0 33: {'I am a tuple 33'}
+n:0 34: {'I am a tuple 34'}
+n:0 35: {'I am a tuple 35'}
+n:0 36: {'I am a tuple 36'}
+n:0 37: {'I am a tuple 37'}
+n:0 38: {'I am a tuple 38'}
+n:0 39: {'I am a tuple 39'}
+n:0 40: {'I am a tuple 40'}
+n:0 41: {'I am a tuple 41'}
+n:0 42: {'I am a tuple 42'}
+n:0 43: {'I am a tuple 43'}
+n:0 44: {'I am a tuple 44'}
+n:0 45: {'I am a tuple 45'}
+n:0 46: {'I am a tuple 46'}
+n:0 47: {'I am a tuple 47'}
+n:0 48: {'I am a tuple 48'}
+n:0 49: {'I am a tuple 49'}
+n:0 50: {'I am a tuple 50'}
+n:0 51: {'I am a tuple 51'}
+n:0 52: {'I am a tuple 52'}
+n:0 53: {'I am a tuple 53'}
+n:0 54: {'I am a tuple 54'}
+n:0 55: {'I am a tuple 55'}
+n:0 56: {'I am a tuple 56'}
+n:0 57: {'I am a tuple 57'}
+n:0 58: {'I am a tuple 58'}
+n:0 59: {'I am a tuple 59'}
+n:0 60: {'I am a tuple 60'}
+n:0 61: {'I am a tuple 61'}
+n:0 62: {'I am a tuple 62'}
+n:0 63: {'I am a tuple 63'}
+n:0 64: {'I am a tuple 64'}
+n:0 65: {'I am a tuple 65'}
+n:0 66: {'I am a tuple 66'}
+n:0 67: {'I am a tuple 67'}
+n:0 68: {'I am a tuple 68'}
+n:0 69: {'I am a tuple 69'}
+n:0 70: {'I am a tuple 70'}
+n:0 71: {'I am a tuple 71'}
+n:0 72: {'I am a tuple 72'}
+n:0 73: {'I am a tuple 73'}
+n:0 74: {'I am a tuple 74'}
+n:0 75: {'I am a tuple 75'}
+n:0 76: {'I am a tuple 76'}
+n:0 77: {'I am a tuple 77'}
+n:0 78: {'I am a tuple 78'}
+n:0 79: {'I am a tuple 79'}
+n:0 80: {'I am a tuple 80'}
+n:0 81: {'I am a tuple 81'}
+n:0 82: {'I am a tuple 82'}
+n:0 83: {'I am a tuple 83'}
+n:0 84: {'I am a tuple 84'}
+n:0 85: {'I am a tuple 85'}
+n:0 86: {'I am a tuple 86'}
+n:0 87: {'I am a tuple 87'}
+n:0 88: {'I am a tuple 88'}
+n:0 89: {'I am a tuple 89'}
+n:0 90: {'I am a tuple 90'}
+n:0 91: {'I am a tuple 91'}
+n:0 92: {'I am a tuple 92'}
+n:0 93: {'I am a tuple 93'}
+n:0 94: {'I am a tuple 94'}
+n:0 95: {'I am a tuple 95'}
+n:0 96: {'I am a tuple 96'}
+n:0 97: {'I am a tuple 97'}
+n:0 98: {'I am a tuple 98'}
+n:0 99: {'I am a tuple 99'}
+n:0 100: {'I am a tuple 100'}
+n:0 101: {'I am a tuple 101'}
+n:0 102: {'I am a tuple 102'}
+n:0 103: {'I am a tuple 103'}
+n:0 104: {'I am a tuple 104'}
+n:0 105: {'I am a tuple 105'}
+n:0 106: {'I am a tuple 106'}
+n:0 107: {'I am a tuple 107'}
+n:0 108: {'I am a tuple 108'}
+n:0 109: {'I am a tuple 109'}
+n:0 110: {'I am a tuple 110'}
+n:0 111: {'I am a tuple 111'}
+n:0 112: {'I am a tuple 112'}
+n:0 113: {'I am a tuple 113'}
+n:0 114: {'I am a tuple 114'}
+n:0 115: {'I am a tuple 115'}
+n:0 116: {'I am a tuple 116'}
+n:0 117: {'I am a tuple 117'}
+n:0 118: {'I am a tuple 118'}
+n:0 119: {'I am a tuple 119'}
+n:0 120: {'I am a tuple 120'}
+n:0 121: {'I am a tuple 121'}
+n:0 122: {'I am a tuple 122'}
+n:0 123: {'I am a tuple 123'}
+n:0 124: {'I am a tuple 124'}
+n:0 125: {'I am a tuple 125'}
+n:0 126: {'I am a tuple 126'}
+n:0 127: {'I am a tuple 127'}
+n:0 128: {'I am a tuple 128'}
+n:0 129: {'I am a tuple 129'}
+n:0 130: {'I am a tuple 130'}
+n:0 131: {'I am a tuple 131'}
+n:0 132: {'I am a tuple 132'}
+n:0 133: {'I am a tuple 133'}
+n:0 134: {'I am a tuple 134'}
+n:0 135: {'I am a tuple 135'}
+n:0 136: {'I am a tuple 136'}
+n:0 137: {'I am a tuple 137'}
+n:0 138: {'I am a tuple 138'}
+n:0 139: {'I am a tuple 139'}
+n:0 140: {'I am a tuple 140'}
+n:0 141: {'I am a tuple 141'}
+n:0 142: {'I am a tuple 142'}
+n:0 143: {'I am a tuple 143'}
+n:0 144: {'I am a tuple 144'}
+n:0 145: {'I am a tuple 145'}
+n:0 146: {'I am a tuple 146'}
+n:0 147: {'I am a tuple 147'}
+n:0 148: {'I am a tuple 148'}
+n:0 149: {'I am a tuple 149'}
+n:0 150: {'I am a tuple 150'}
+n:0 151: {'I am a tuple 151'}
+n:0 152: {'I am a tuple 152'}
+n:0 153: {'I am a tuple 153'}
+n:0 154: {'I am a tuple 154'}
+n:0 155: {'I am a tuple 155'}
+n:0 156: {'I am a tuple 156'}
+n:0 157: {'I am a tuple 157'}
+n:0 158: {'I am a tuple 158'}
+n:0 159: {'I am a tuple 159'}
+n:0 160: {'I am a tuple 160'}
+n:0 161: {'I am a tuple 161'}
+n:0 162: {'I am a tuple 162'}
+n:0 163: {'I am a tuple 163'}
+n:0 164: {'I am a tuple 164'}
+n:0 165: {'I am a tuple 165'}
+n:0 166: {'I am a tuple 166'}
+n:0 167: {'I am a tuple 167'}
+n:0 168: {'I am a tuple 168'}
+n:0 169: {'I am a tuple 169'}
+n:0 170: {'I am a tuple 170'}
+n:0 171: {'I am a tuple 171'}
+n:0 172: {'I am a tuple 172'}
+n:0 173: {'I am a tuple 173'}
+n:0 174: {'I am a tuple 174'}
+n:0 175: {'I am a tuple 175'}
+n:0 176: {'I am a tuple 176'}
+n:0 177: {'I am a tuple 177'}
+n:0 178: {'I am a tuple 178'}
+n:0 179: {'I am a tuple 179'}
+n:0 180: {'I am a tuple 180'}
+n:0 181: {'I am a tuple 181'}
+n:0 182: {'I am a tuple 182'}
+n:0 183: {'I am a tuple 183'}
+n:0 184: {'I am a tuple 184'}
+n:0 185: {'I am a tuple 185'}
+n:0 186: {'I am a tuple 186'}
+n:0 187: {'I am a tuple 187'}
+n:0 188: {'I am a tuple 188'}
+n:0 189: {'I am a tuple 189'}
+n:0 190: {'I am a tuple 190'}
+n:0 191: {'I am a tuple 191'}
+n:0 192: {'I am a tuple 192'}
+n:0 193: {'I am a tuple 193'}
+n:0 194: {'I am a tuple 194'}
+n:0 195: {'I am a tuple 195'}
+n:0 196: {'I am a tuple 196'}
+n:0 197: {'I am a tuple 197'}
+n:0 198: {'I am a tuple 198'}
+n:0 199: {'I am a tuple 199'}
+n:0 200: {'I am a tuple 200'}
+n:0 201: {'I am a tuple 201'}
+n:0 202: {'I am a tuple 202'}
+n:0 203: {'I am a tuple 203'}
+n:0 204: {'I am a tuple 204'}
+n:0 205: {'I am a tuple 205'}
+n:0 206: {'I am a tuple 206'}
+n:0 207: {'I am a tuple 207'}
+n:0 208: {'I am a tuple 208'}
+n:0 209: {'I am a tuple 209'}
+n:0 210: {'I am a tuple 210'}
+n:0 211: {'I am a tuple 211'}
+n:0 212: {'I am a tuple 212'}
+n:0 213: {'I am a tuple 213'}
+n:0 214: {'I am a tuple 214'}
+n:0 215: {'I am a tuple 215'}
+n:0 216: {'I am a tuple 216'}
+n:0 217: {'I am a tuple 217'}
+n:0 218: {'I am a tuple 218'}
+n:0 219: {'I am a tuple 219'}
+n:0 220: {'I am a tuple 220'}
+n:0 221: {'I am a tuple 221'}
+n:0 222: {'I am a tuple 222'}
+n:0 223: {'I am a tuple 223'}
+n:0 224: {'I am a tuple 224'}
+n:0 225: {'I am a tuple 225'}
+n:0 226: {'I am a tuple 226'}
+n:0 227: {'I am a tuple 227'}
+n:0 228: {'I am a tuple 228'}
+n:0 229: {'I am a tuple 229'}
+n:0 230: {'I am a tuple 230'}
+n:0 231: {'I am a tuple 231'}
+n:0 232: {'I am a tuple 232'}
+n:0 233: {'I am a tuple 233'}
+n:0 234: {'I am a tuple 234'}
+n:0 235: {'I am a tuple 235'}
+n:0 236: {'I am a tuple 236'}
+n:0 237: {'I am a tuple 237'}
+n:0 238: {'I am a tuple 238'}
+n:0 239: {'I am a tuple 239'}
+n:0 240: {'I am a tuple 240'}
+n:0 241: {'I am a tuple 241'}
+n:0 242: {'I am a tuple 242'}
+n:0 243: {'I am a tuple 243'}
+n:0 244: {'I am a tuple 244'}
+n:0 245: {'I am a tuple 245'}
+n:0 246: {'I am a tuple 246'}
+n:0 247: {'I am a tuple 247'}
+n:0 248: {'I am a tuple 248'}
+n:0 249: {'I am a tuple 249'}
+n:0 250: {'I am a tuple 250'}
+n:0 251: {'I am a tuple 251'}
+n:0 252: {'I am a tuple 252'}
+n:0 253: {'I am a tuple 253'}
+n:0 254: {'I am a tuple 254'}
+n:0 255: {'I am a tuple 255'}
+n:0 256: {'I am a tuple 256'}
+n:0 257: {'I am a tuple 257'}
+n:0 258: {'I am a tuple 258'}
+n:0 259: {'I am a tuple 259'}
+n:0 260: {'I am a tuple 260'}
+n:0 261: {'I am a tuple 261'}
+n:0 262: {'I am a tuple 262'}
+n:0 263: {'I am a tuple 263'}
+n:0 264: {'I am a tuple 264'}
+n:0 265: {'I am a tuple 265'}
+n:0 266: {'I am a tuple 266'}
+n:0 267: {'I am a tuple 267'}
+n:0 268: {'I am a tuple 268'}
+n:0 269: {'I am a tuple 269'}
+n:0 270: {'I am a tuple 270'}
+n:0 271: {'I am a tuple 271'}
+n:0 272: {'I am a tuple 272'}
+n:0 273: {'I am a tuple 273'}
+n:0 274: {'I am a tuple 274'}
+n:0 275: {'I am a tuple 275'}
+n:0 276: {'I am a tuple 276'}
+n:0 277: {'I am a tuple 277'}
+n:0 278: {'I am a tuple 278'}
+n:0 279: {'I am a tuple 279'}
+n:0 280: {'I am a tuple 280'}
+n:0 281: {'I am a tuple 281'}
+n:0 282: {'I am a tuple 282'}
+n:0 283: {'I am a tuple 283'}
+n:0 284: {'I am a tuple 284'}
+n:0 285: {'I am a tuple 285'}
+n:0 286: {'I am a tuple 286'}
+n:0 287: {'I am a tuple 287'}
+n:0 288: {'I am a tuple 288'}
+n:0 289: {'I am a tuple 289'}
+n:0 290: {'I am a tuple 290'}
+n:0 291: {'I am a tuple 291'}
+n:0 292: {'I am a tuple 292'}
+n:0 293: {'I am a tuple 293'}
+n:0 294: {'I am a tuple 294'}
+n:0 295: {'I am a tuple 295'}
+n:0 296: {'I am a tuple 296'}
+n:0 297: {'I am a tuple 297'}
+n:0 298: {'I am a tuple 298'}
+n:0 299: {'I am a tuple 299'}
+n:0 300: {'I am a tuple 300'}
+n:0 301: {'I am a tuple 301'}
+n:0 302: {'I am a tuple 302'}
+n:0 303: {'I am a tuple 303'}
+n:0 304: {'I am a tuple 304'}
+n:0 305: {'I am a tuple 305'}
+n:0 306: {'I am a tuple 306'}
+n:0 307: {'I am a tuple 307'}
+n:0 308: {'I am a tuple 308'}
+n:0 309: {'I am a tuple 309'}
+n:0 310: {'I am a tuple 310'}
+n:0 311: {'I am a tuple 311'}
+n:0 312: {'I am a tuple 312'}
+n:0 313: {'I am a tuple 313'}
+n:0 314: {'I am a tuple 314'}
+n:0 315: {'I am a tuple 315'}
+n:0 316: {'I am a tuple 316'}
+n:0 317: {'I am a tuple 317'}
+n:0 318: {'I am a tuple 318'}
+n:0 319: {'I am a tuple 319'}
+n:0 320: {'I am a tuple 320'}
+n:0 321: {'I am a tuple 321'}
+n:0 322: {'I am a tuple 322'}
+n:0 323: {'I am a tuple 323'}
+n:0 324: {'I am a tuple 324'}
+n:0 325: {'I am a tuple 325'}
+n:0 326: {'I am a tuple 326'}
+n:0 327: {'I am a tuple 327'}
+n:0 328: {'I am a tuple 328'}
+n:0 329: {'I am a tuple 329'}
+n:0 330: {'I am a tuple 330'}
+n:0 331: {'I am a tuple 331'}
+n:0 332: {'I am a tuple 332'}
+n:0 333: {'I am a tuple 333'}
+n:0 334: {'I am a tuple 334'}
+n:0 335: {'I am a tuple 335'}
+n:0 336: {'I am a tuple 336'}
+n:0 337: {'I am a tuple 337'}
+n:0 338: {'I am a tuple 338'}
+n:0 339: {'I am a tuple 339'}
+n:0 340: {'I am a tuple 340'}
+n:0 341: {'I am a tuple 341'}
+n:0 342: {'I am a tuple 342'}
+n:0 343: {'I am a tuple 343'}
+n:0 344: {'I am a tuple 344'}
+n:0 345: {'I am a tuple 345'}
+n:0 346: {'I am a tuple 346'}
+n:0 347: {'I am a tuple 347'}
+n:0 348: {'I am a tuple 348'}
+n:0 349: {'I am a tuple 349'}
+n:0 350: {'I am a tuple 350'}
+n:0 351: {'I am a tuple 351'}
+n:0 352: {'I am a tuple 352'}
+n:0 353: {'I am a tuple 353'}
+n:0 354: {'I am a tuple 354'}
+n:0 355: {'I am a tuple 355'}
+n:0 356: {'I am a tuple 356'}
+n:0 357: {'I am a tuple 357'}
+n:0 358: {'I am a tuple 358'}
+n:0 359: {'I am a tuple 359'}
+n:0 360: {'I am a tuple 360'}
+n:0 361: {'I am a tuple 361'}
+n:0 362: {'I am a tuple 362'}
+n:0 363: {'I am a tuple 363'}
+n:0 364: {'I am a tuple 364'}
+n:0 365: {'I am a tuple 365'}
+n:0 366: {'I am a tuple 366'}
+n:0 367: {'I am a tuple 367'}
+n:0 368: {'I am a tuple 368'}
+n:0 369: {'I am a tuple 369'}
+n:0 370: {'I am a tuple 370'}
+n:0 371: {'I am a tuple 371'}
+n:0 372: {'I am a tuple 372'}
+n:0 373: {'I am a tuple 373'}
+n:0 374: {'I am a tuple 374'}
+n:0 375: {'I am a tuple 375'}
+n:0 376: {'I am a tuple 376'}
+n:0 377: {'I am a tuple 377'}
+n:0 378: {'I am a tuple 378'}
+n:0 379: {'I am a tuple 379'}
+n:0 380: {'I am a tuple 380'}
+n:0 381: {'I am a tuple 381'}
+n:0 382: {'I am a tuple 382'}
+n:0 383: {'I am a tuple 383'}
+n:0 384: {'I am a tuple 384'}
+n:0 385: {'I am a tuple 385'}
+n:0 386: {'I am a tuple 386'}
+n:0 387: {'I am a tuple 387'}
+n:0 388: {'I am a tuple 388'}
+n:0 389: {'I am a tuple 389'}
+n:0 390: {'I am a tuple 390'}
+n:0 391: {'I am a tuple 391'}
+n:0 392: {'I am a tuple 392'}
+n:0 393: {'I am a tuple 393'}
+n:0 394: {'I am a tuple 394'}
+n:0 395: {'I am a tuple 395'}
+n:0 396: {'I am a tuple 396'}
+n:0 397: {'I am a tuple 397'}
+n:0 398: {'I am a tuple 398'}
+n:0 399: {'I am a tuple 399'}
+n:0 400: {'I am a tuple 400'}
+n:0 401: {'I am a tuple 401'}
+n:0 402: {'I am a tuple 402'}
+n:0 403: {'I am a tuple 403'}
+n:0 404: {'I am a tuple 404'}
+n:0 405: {'I am a tuple 405'}
+n:0 406: {'I am a tuple 406'}
+n:0 407: {'I am a tuple 407'}
+n:0 408: {'I am a tuple 408'}
+n:0 409: {'I am a tuple 409'}
+n:0 410: {'I am a tuple 410'}
+n:0 411: {'I am a tuple 411'}
+n:0 412: {'I am a tuple 412'}
+n:0 413: {'I am a tuple 413'}
+n:0 414: {'I am a tuple 414'}
+n:0 415: {'I am a tuple 415'}
+n:0 416: {'I am a tuple 416'}
+n:0 417: {'I am a tuple 417'}
+n:0 418: {'I am a tuple 418'}
+n:0 419: {'I am a tuple 419'}
+n:0 420: {'I am a tuple 420'}
+n:0 421: {'I am a tuple 421'}
+n:0 422: {'I am a tuple 422'}
+n:0 423: {'I am a tuple 423'}
+n:0 424: {'I am a tuple 424'}
+n:0 425: {'I am a tuple 425'}
+n:0 426: {'I am a tuple 426'}
+n:0 427: {'I am a tuple 427'}
+n:0 428: {'I am a tuple 428'}
+n:0 429: {'I am a tuple 429'}
+n:0 430: {'I am a tuple 430'}
+n:0 431: {'I am a tuple 431'}
+n:0 432: {'I am a tuple 432'}
+n:0 433: {'I am a tuple 433'}
+n:0 434: {'I am a tuple 434'}
+n:0 435: {'I am a tuple 435'}
+n:0 436: {'I am a tuple 436'}
+n:0 437: {'I am a tuple 437'}
+n:0 438: {'I am a tuple 438'}
+n:0 439: {'I am a tuple 439'}
+n:0 440: {'I am a tuple 440'}
+n:0 441: {'I am a tuple 441'}
+n:0 442: {'I am a tuple 442'}
+n:0 443: {'I am a tuple 443'}
+n:0 444: {'I am a tuple 444'}
+n:0 445: {'I am a tuple 445'}
+n:0 446: {'I am a tuple 446'}
+n:0 447: {'I am a tuple 447'}
+n:0 448: {'I am a tuple 448'}
+n:0 449: {'I am a tuple 449'}
+n:0 450: {'I am a tuple 450'}
+n:0 451: {'I am a tuple 451'}
+n:0 452: {'I am a tuple 452'}
+n:0 453: {'I am a tuple 453'}
+n:0 454: {'I am a tuple 454'}
+n:0 455: {'I am a tuple 455'}
+n:0 456: {'I am a tuple 456'}
+n:0 457: {'I am a tuple 457'}
+n:0 458: {'I am a tuple 458'}
+n:0 459: {'I am a tuple 459'}
+n:0 460: {'I am a tuple 460'}
+n:0 461: {'I am a tuple 461'}
+n:0 462: {'I am a tuple 462'}
+n:0 463: {'I am a tuple 463'}
+n:0 464: {'I am a tuple 464'}
+n:0 465: {'I am a tuple 465'}
+n:0 466: {'I am a tuple 466'}
+n:0 467: {'I am a tuple 467'}
+n:0 468: {'I am a tuple 468'}
+n:0 469: {'I am a tuple 469'}
+n:0 470: {'I am a tuple 470'}
+n:0 471: {'I am a tuple 471'}
+n:0 472: {'I am a tuple 472'}
+n:0 473: {'I am a tuple 473'}
+n:0 474: {'I am a tuple 474'}
+n:0 475: {'I am a tuple 475'}
+n:0 476: {'I am a tuple 476'}
+n:0 477: {'I am a tuple 477'}
+n:0 478: {'I am a tuple 478'}
+n:0 479: {'I am a tuple 479'}
+n:0 480: {'I am a tuple 480'}
+n:0 481: {'I am a tuple 481'}
+n:0 482: {'I am a tuple 482'}
+n:0 483: {'I am a tuple 483'}
+n:0 484: {'I am a tuple 484'}
+n:0 485: {'I am a tuple 485'}
+n:0 486: {'I am a tuple 486'}
+n:0 487: {'I am a tuple 487'}
+n:0 488: {'I am a tuple 488'}
+n:0 489: {'I am a tuple 489'}
+n:0 490: {'I am a tuple 490'}
+n:0 491: {'I am a tuple 491'}
+n:0 492: {'I am a tuple 492'}
+n:0 493: {'I am a tuple 493'}
+n:0 494: {'I am a tuple 494'}
+n:0 495: {'I am a tuple 495'}
+n:0 496: {'I am a tuple 496'}
+n:0 497: {'I am a tuple 497'}
+n:0 498: {'I am a tuple 498'}
+n:0 499: {'I am a tuple 499'}
+
diff --git a/test/box/cat.test b/test/box/cat.test
new file mode 100644
index 0000000000000000000000000000000000000000..789827230ddfdd492731e03196584ef1046c9076
--- /dev/null
+++ b/test/box/cat.test
@@ -0,0 +1,24 @@
+# encoding: tarantool
+#
+# Created to test for [Bug 920951]: server crashes on cat command
+#
+import os
+import time
+import yaml
+from signal import SIGUSR1
+
+print """
+# Verify that the server starts from a pre-recorded snapshot.
+# This way we check that the server can read old snapshots (v11)
+# going forward.
+"""
+server.stop()
+snapshot = os.path.join(vardir, "00000000000000000500.snap")
+os.symlink(os.path.abspath("box/00000000000000000500.snap"), snapshot)
+server.test_option("--cat=00000000000000000500.snap")
+# server.start()
+
+# print "# Restore the default server..."
+# server.stop()
+os.unlink(snapshot)
+
diff --git a/test/box/configuration.result b/test/box/configuration.result
index f4c527e1c6d978c5ab8adda89bc2853fcd41689a..ded392434a19c82b584904728c4139217c2533ab 100644
--- a/test/box/configuration.result
+++ b/test/box/configuration.result
@@ -185,3 +185,10 @@ lua box.cfg.wal_fsync_delay
 ---
  - 0.01
 ...
+
+#  Test field type conflict in keys
+
+tarantool_box -c tarantool_bad_type.cfg
+tarantool_box: can't load config:
+ - (space = 0 fieldno = 0) index field type mismatch
+
diff --git a/test/box/configuration.test b/test/box/configuration.test
index e58585a34a5781b38b5184994da019897d7b8289..7eb7cb3bd6b08ee65704cfa5d3b4711753a6181f 100644
--- a/test/box/configuration.test
+++ b/test/box/configuration.test
@@ -1,5 +1,9 @@
 # encoding: tarantool
 #
+
+import os
+import sys
+
 print """
 # Bug #708685:
 #  Addition of required configuration file options broke backward
@@ -37,6 +41,16 @@ server.deploy("box/tarantool_bug876541.cfg")
 # check values
 exec admin "lua box.cfg.wal_fsync_delay"
 
+print """
+#  Test field type conflict in keys
+"""
+# stop current server
+server.stop()
+# start server with memcached space conflict
+sys.stdout.push_filter("(/\S+)+/tarantool", "tarantool")
+server.test_option("-c " + os.path.join(os.getcwd(), "box/tarantool_bad_type.cfg"))
+sys.stdout.pop_filter()
+
 # restore default server
 server.stop()
 server.deploy(self.suite_ini["config"])
diff --git a/test/box/suite.ini b/test/box/suite.ini
index ebc1e935aa1536eaeee1d3b2978609188bb0719c..ae4bde328a080541bcb84acc35210c39573e5727 100644
--- a/test/box/suite.ini
+++ b/test/box/suite.ini
@@ -2,6 +2,6 @@
 description = tarantool/box, minimal configuration
 config = tarantool.cfg
 # put disabled tests here
-#disabled = sql.test
+#disabled = xlog.test
 # put disabled in valgrind test here
 valgrind_disabled = admin_coredump.test
diff --git a/test/box/tarantool_bad_type.cfg b/test/box/tarantool_bad_type.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..3fdfd3044ae6dec77c28865d03329dd0a6e68ba6
--- /dev/null
+++ b/test/box/tarantool_bad_type.cfg
@@ -0,0 +1,24 @@
+slab_alloc_arena = 0.1
+
+pid_file = "box.pid"
+
+logger="cat - >> tarantool.log"
+
+primary_port = 33013
+secondary_port = 33014
+admin_port = 33015
+
+rows_per_wal = 50
+# This is one of the few modifiable settings, change it
+too_long_threshold=2
+
+space[0].enabled = 1
+space[0].index[0].type = "HASH"
+space[0].index[0].unique = 1
+space[0].index[0].key_field[0].fieldno = 0
+space[0].index[0].key_field[0].type = "NUM"
+space[0].index[1].type = "TREE"
+space[0].index[1].unique = 1
+space[0].index[1].key_field[0].fieldno = 0
+space[0].index[1].key_field[0].type = "NUM64"
+
diff --git a/test/box_big/tarantool.cfg b/test/box_big/tarantool.cfg
index a7f934b946ba169d066ca319b5335cc2c2f99e58..f298f2bdd0d0ffd5f7f010bab50d0673d5709171 100644
--- a/test/box_big/tarantool.cfg
+++ b/test/box_big/tarantool.cfg
@@ -65,3 +65,62 @@ space[5].index[1].key_field[0].fieldno = 1
 space[5].index[1].key_field[0].type = "STR"
 space[5].index[1].key_field[1].fieldno = 2
 space[5].index[1].key_field[1].type = "STR"
+
+
+#
+# Tree index variants
+#
+#  Tuple fields:
+#
+#  0: NUM, 1: NUM64, 2: NUM64, 3: STR, 4: STR, 5: STR, 6: STR, 7: *, 8: NUM
+#
+
+space[6].enabled = 1
+
+space[6].index[0].type = "TREE"
+space[6].index[0].unique = 1
+space[6].index[0].key_field[0].fieldno = 0
+space[6].index[0].key_field[0].type = "NUM"
+
+space[6].index[1].type = "TREE"
+space[6].index[1].unique = 1
+space[6].index[1].key_field[0].fieldno = 1
+space[6].index[1].key_field[0].type = "NUM64"
+
+space[6].index[2].type = "TREE"
+space[6].index[2].unique = 0
+space[6].index[2].key_field[0].fieldno = 2
+space[6].index[2].key_field[0].type = "NUM64"
+
+space[6].index[3].type = "TREE"
+space[6].index[3].unique = 0
+space[6].index[3].key_field[0].fieldno = 3
+space[6].index[3].key_field[0].type = "STR"
+space[6].index[3].key_field[1].fieldno = 4
+space[6].index[3].key_field[1].type = "STR"
+
+space[6].index[4].type = "TREE"
+space[6].index[4].unique = 0
+space[6].index[4].key_field[0].fieldno = 6
+space[6].index[4].key_field[0].type = "STR"
+space[6].index[4].key_field[1].fieldno = 5
+space[6].index[4].key_field[1].type = "STR"
+
+space[6].index[5].type = "TREE"
+space[6].index[5].unique = 0
+space[6].index[5].key_field[0].fieldno = 8
+space[6].index[5].key_field[0].type = "NUM"
+
+space[6].index[6].type = "TREE"
+space[6].index[6].unique = 1
+space[6].index[6].key_field[0].fieldno = 6
+space[6].index[6].key_field[0].type = "STR"
+space[6].index[6].key_field[1].fieldno = 5
+space[6].index[6].key_field[1].type = "STR"
+space[6].index[6].key_field[2].fieldno = 3
+space[6].index[6].key_field[2].type = "STR"
+space[6].index[6].key_field[3].fieldno = 4
+space[6].index[6].key_field[3].type = "STR"
+space[6].index[6].key_field[4].fieldno = 8
+space[6].index[6].key_field[4].type = "NUM"
+
diff --git a/test/box_big/tree_variants.result b/test/box_big/tree_variants.result
new file mode 100644
index 0000000000000000000000000000000000000000..dcc277aced44fce47032a086a234ceac92e21254
--- /dev/null
+++ b/test/box_big/tree_variants.result
@@ -0,0 +1,79 @@
+insert into t6 values (0, '00000000', '00000100', 'Joe', 'Sixpack', 'Drinks', 'Amstel', 'bar', 2000 )
+Insert OK, 1 row affected
+insert into t6 values (1, '00000001', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Heineken', 'bar', 2001 )
+Insert OK, 1 row affected
+insert into t6 values (2, '00000002', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Carlsberg', 'bar', 2002 )
+Insert OK, 1 row affected
+insert into t6 values (3, '00000003', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Corona Extra', 'bar', 2003 )
+Insert OK, 1 row affected
+insert into t6 values (4, '00000004', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Stella Artois', 'bar', 2004 )
+Insert OK, 1 row affected
+insert into t6 values (5, '00000005', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Miller Genuine Draft', 'bar', 2005 )
+Insert OK, 1 row affected
+insert into t6 values (6, '00000006', '00000400', 'John', 'Smoker', 'Hits', 'A Pipe', 'foo', 2006 )
+Insert OK, 1 row affected
+insert into t6 values (7, '00000007', '00000400', 'John', 'Smoker', 'Hits', 'A Bong', 'foo', 2007 )
+Insert OK, 1 row affected
+insert into t6 values (8, '00000008', '00000400', 'John', 'Smoker', 'Rolls', 'A Joint', 'foo', 2008 )
+Insert OK, 1 row affected
+insert into t6 values (9, '00000009', '00000400', 'John', 'Smoker', 'Rolls', 'A Blunt', 'foo', 2009 )
+Insert OK, 1 row affected
+select * from t6 where k0 = 1
+Found 1 tuple:
+[1, '00000001', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Heineken', 'bar', 2001]
+select * from t6 where k1 = '00000002'
+Found 1 tuple:
+[2, '00000002', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Carlsberg', 'bar', 2002]
+select * from t6 where k2 = '00000300'
+Found 3 tuples:
+[3, '00000003', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Corona Extra', 'bar', 2003]
+[4, '00000004', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Stella Artois', 'bar', 2004]
+[5, '00000005', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Miller Genuine Draft', 'bar', 2005]
+lua box.space[6]:select(3, 'Joe', 'Sixpack')
+---
+ - 0: {'00000000', '00000100', 'Joe', 'Sixpack', 'Drinks', 'Amstel', 'bar', 2000}
+ - 1: {'00000001', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Heineken', 'bar', 2001}
+ - 2: {'00000002', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Carlsberg', 'bar', 2002}
+ - 3: {'00000003', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Corona Extra', 'bar', 2003}
+ - 4: {'00000004', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Stella Artois', 'bar', 2004}
+ - 5: {'00000005', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Miller Genuine Draft', 'bar', 2005}
+...
+lua box.space[6]:select(3, 'John')
+---
+ - 6: {'00000006', '00000400', 1852337994, 'Smoker', 1937008968, 'A Pipe', 'foo', 2006}
+ - 7: {'00000007', '00000400', 1852337994, 'Smoker', 1937008968, 'A Bong', 'foo', 2007}
+ - 8: {'00000008', '00000400', 1852337994, 'Smoker', 'Rolls', 'A Joint', 'foo', 2008}
+ - 9: {'00000009', '00000400', 1852337994, 'Smoker', 'Rolls', 'A Blunt', 'foo', 2009}
+...
+lua box.space[6]:select(4, 'A Pipe')
+---
+ - 6: {'00000006', '00000400', 1852337994, 'Smoker', 1937008968, 'A Pipe', 'foo', 2006}
+...
+lua box.space[6]:select(4, 'Miller Genuine Draft', 'Drinks')
+---
+ - 5: {'00000005', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Miller Genuine Draft', 'bar', 2005}
+...
+select * from t6 where k5 = 2007
+Found 1 tuple:
+[7, '00000007', '00000400', 1852337994, 'Smoker', 1937008968, 'A Bong', 'foo', 2007]
+lua box.space[6]:select(6, 'Miller Genuine Draft', 'Drinks')
+---
+ - 5: {'00000005', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Miller Genuine Draft', 'bar', 2005}
+...
+delete from t6 where k0 = 6
+Delete OK, 1 row affected
+delete from t6 where k0 = 7
+Delete OK, 1 row affected
+delete from t6 where k0 = 8
+Delete OK, 1 row affected
+delete from t6 where k0 = 9
+Delete OK, 1 row affected
+lua for k,v in box.space[6]:pairs() do print(v) end
+---
+0: {'00000000', '00000100', 'Joe', 'Sixpack', 'Drinks', 'Amstel', 'bar', 2000}
+1: {'00000001', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Heineken', 'bar', 2001}
+2: {'00000002', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Carlsberg', 'bar', 2002}
+3: {'00000003', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Corona Extra', 'bar', 2003}
+4: {'00000004', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Stella Artois', 'bar', 2004}
+5: {'00000005', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Miller Genuine Draft', 'bar', 2005}
+...
diff --git a/test/box_big/tree_variants.test b/test/box_big/tree_variants.test
new file mode 100644
index 0000000000000000000000000000000000000000..9c26aa3ac1aecb26e1195e401f188d4fa9327115
--- /dev/null
+++ b/test/box_big/tree_variants.test
@@ -0,0 +1,31 @@
+# encoding: tarantool
+#
+
+exec sql "insert into t6 values (0, '00000000', '00000100', 'Joe', 'Sixpack', 'Drinks', 'Amstel', 'bar', 2000 )"
+exec sql "insert into t6 values (1, '00000001', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Heineken', 'bar', 2001 )"
+exec sql "insert into t6 values (2, '00000002', '00000200', 'Joe', 'Sixpack', 'Drinks', 'Carlsberg', 'bar', 2002 )"
+exec sql "insert into t6 values (3, '00000003', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Corona Extra', 'bar', 2003 )"
+exec sql "insert into t6 values (4, '00000004', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Stella Artois', 'bar', 2004 )"
+exec sql "insert into t6 values (5, '00000005', '00000300', 'Joe', 'Sixpack', 'Drinks', 'Miller Genuine Draft', 'bar', 2005 )"
+exec sql "insert into t6 values (6, '00000006', '00000400', 'John', 'Smoker', 'Hits', 'A Pipe', 'foo', 2006 )"
+exec sql "insert into t6 values (7, '00000007', '00000400', 'John', 'Smoker', 'Hits', 'A Bong', 'foo', 2007 )"
+exec sql "insert into t6 values (8, '00000008', '00000400', 'John', 'Smoker', 'Rolls', 'A Joint', 'foo', 2008 )"
+exec sql "insert into t6 values (9, '00000009', '00000400', 'John', 'Smoker', 'Rolls', 'A Blunt', 'foo', 2009 )"
+
+exec sql "select * from t6 where k0 = 1"
+exec sql "select * from t6 where k1 = '00000002'"
+exec sql "select * from t6 where k2 = '00000300'"
+exec admin "lua box.space[6]:select(3, 'Joe', 'Sixpack')"
+exec admin "lua box.space[6]:select(3, 'John')"
+exec admin "lua box.space[6]:select(4, 'A Pipe')"
+exec admin "lua box.space[6]:select(4, 'Miller Genuine Draft', 'Drinks')"
+exec sql "select * from t6 where k5 = 2007"
+exec admin "lua box.space[6]:select(6, 'Miller Genuine Draft', 'Drinks')"
+
+exec sql "delete from t6 where k0 = 6"
+exec sql "delete from t6 where k0 = 7"
+exec sql "delete from t6 where k0 = 8"
+exec sql "delete from t6 where k0 = 9"
+
+exec admin "lua for k,v in box.space[6]:pairs() do print(v) end"
+
diff --git a/test/share/tarantool_box.sup b/test/share/tarantool_box.sup
index e8cef3c8bf6b9ec8e978c94a5cc0f6bf7910331d..b7fbfebafa622462f916b7d16f4267604947e3de 100644
--- a/test/share/tarantool_box.sup
+++ b/test/share/tarantool_box.sup
@@ -139,6 +139,17 @@
    ...
 }
 
+{
+   <box upadte command>
+   Memcheck:Leak
+   fun:salloc
+   fun:tuple_alloc
+   fun:prepare_update
+   fun:box_dispatch
+   fun:box_process*
+   ...
+}
+
 ##
 ## tarantool/lua suppressions
 ##
diff --git a/third_party/sptree.h b/third_party/sptree.h
index cb5551ba10def7d753eb907f62092cc923599f2e..0bae478873bcf39e611abe98111d696ba3d30d66 100644
--- a/third_party/sptree.h
+++ b/third_party/sptree.h
@@ -64,6 +64,7 @@ typedef struct sptree_node_pointers {
 #define    _SET_SPNODE_RIGHT(n, v)     SET_SPNODE_RIGHT( t->lrpointers + (n), (v) )
 
 #define    ITHELEM(t, i)               ( (t)->members + (t)->elemsize * (i) )
+#define    ELEMIDX(t, e)               ( ((e) - (t)->members) / (t)->elemsize )
 
 /*
  * makes definition of tree with methods, name should
@@ -72,7 +73,9 @@ typedef struct sptree_node_pointers {
  * Methods:
  *   void sptree_NAME_init(sptree_NAME *tree, size_t elemsize, void *array, 
  *                         spnode_t array_len, spnode_t array_size, 
- *                         int (*compar)(const void *, const void *, void *), void *arg)
+ *                         int (*compare)(const void *key, const void *elem, void *arg),
+ *                         int (*elemcompare)(const void *e1, const void *e2, void *arg),
+ *                         void *arg)
  *   void* sptree_NAME_find(sptree_NAME *tree, void *key)
  *   void sptree_NAME_insert(sptree_NAME *tree, void *value)
  *   void sptree_NAME_delete(sptree_NAME *tree, void *value)
@@ -92,7 +95,8 @@ typedef struct sptree_##name {
     spnode_t                nmember;                                                      \
     spnode_t                ntotal;                                                       \
                                                                                           \
-    int                     (*compare)(const void *, const void *, void *);               \
+    int                     (*compare)(const void *key, const void *elem, void *);        \
+    int                     (*elemcompare)(const void *e1, const void *e2, void *);       \
     void*                   arg;                                                          \
     size_t                  elemsize;                                                     \
                                                                                           \
@@ -127,12 +131,15 @@ sptree_##name##_mktree(sptree_##name *t, spnode_t depth,
 static inline void                                                                        \
 sptree_##name##_init(sptree_##name *t, size_t elemsize, void *m,                          \
                     spnode_t nm, spnode_t nt,                                             \
-                    int (*compare)(const void *, const void *, void *), void *arg) {      \
+                    int (*compare)(const void *, const void *, void *),                   \
+                    int (*elemcompare)(const void *, const void *, void *),               \
+                    void *arg) {                                                          \
     memset(t, 0, sizeof(*t));                                                             \
     t->members = m;                                                                       \
     t->max_size = t->size = t->nmember = nm;                                              \
     t->ntotal = (nt==0) ? nm : nt;                                                        \
-    t->compare = compare;                                                                 \
+    t->compare = compare != NULL ? compare : elemcompare;                                 \
+    t->elemcompare = elemcompare != NULL ? elemcompare : compare;			  \
     t->arg = arg;                                                                         \
     t->elemsize = elemsize;                                                               \
     t->garbage_head = t->root = SPNIL;                                                    \
@@ -153,7 +160,7 @@ sptree_##name##_init(sptree_##name *t, size_t elemsize, void *m,
         _SET_SPNODE_RIGHT(0, SPNIL);                                                      \
         _SET_SPNODE_LEFT(0, SPNIL);                                                       \
     } else if (t->nmember > 1)    {                                                       \
-        qsort_arg(t->members, t->nmember, elemsize, t->compare, t->arg);                  \
+        qsort_arg(t->members, t->nmember, elemsize, t->elemcompare, t->arg);              \
         /* create tree */                                                                 \
         t->root = sptree_##name##_mktree(t, 1, 0, t->nmember);                            \
     }                                                                                     \
@@ -299,7 +306,7 @@ sptree_##name##_insert(sptree_##name *t, void *v) {
         spnode_t    parent = t->root;                                                     \
                                                                                           \
         for(;;)    {                                                                      \
-            int r = t->compare(v, ITHELEM(t, parent), t->arg);                            \
+            int r = t->elemcompare(v, ITHELEM(t, parent), t->arg);                        \
             if (r==0) {                                                                   \
                 memcpy(ITHELEM(t, parent), v, t->elemsize);                               \
                 return;                                                                   \
@@ -370,7 +377,7 @@ sptree_##name##_delete(sptree_##name *t, void *k) {
     spnode_t    parent = SPNIL;                                                           \
     int            lr = 0;                                                                \
     while(node != SPNIL) {                                                                \
-        int r = t->compare(k, ITHELEM(t, node), t->arg);                                  \
+        int r = t->elemcompare(k, ITHELEM(t, node), t->arg);                              \
         if (r > 0) {                                                                      \
             parent = node;                                                                \
             node = _GET_SPNODE_RIGHT(node);                                               \