diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h
index 2c4f339d58d6e17da6af52126ca0ee9ff28e104d..24c0eabc813a3b74d8e9e60be4700aae5fc9da73 100644
--- a/src/box/memtx_engine.h
+++ b/src/box/memtx_engine.h
@@ -66,7 +66,7 @@ struct MemtxEngine: public Engine {
 };
 
 enum {
-	MEMTX_EXTENT_SIZE = 16 * 1024,
+	MEMTX_EXTENT_SIZE = 32 * 1024,
 	MEMTX_SLAB_SIZE = 4 * 1024 * 1024
 };
 
diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
index 887de62bd13928c37e8a3ccfebb49c262c8f04be..762750785cf2206eaa883a29166814c537860b37 100644
--- a/src/lib/salad/bps_tree.h
+++ b/src/lib/salad/bps_tree.h
@@ -980,7 +980,7 @@ bps_tree_build(struct bps_tree *tree, bps_tree_elem_t *sorted_array,
 	assert(tree->size == 0);
 	assert(tree->root == 0);
 	assert(tree->garbage_head == 0);
-	assert(tree->matras.block_count == 0);
+	assert(tree->matras.block_counts[0] == 0);
 	if (array_size == 0)
 		return true;
 	bps_tree_block_id_t leaf_count = (array_size +
diff --git a/src/lib/salad/light.h b/src/lib/salad/light.h
index 074f15cafe333e45587b9ab1ecbe20ca6eb00ce3..ca3c22a01e338cafa0773b2a9a8f61466e19c2a7 100644
--- a/src/lib/salad/light.h
+++ b/src/lib/salad/light.h
@@ -439,7 +439,7 @@ LIGHT(prepare_first_insert)(struct LIGHT(core) *ht)
 {
 	assert(ht->count == 0);
 	assert(ht->table_size == 0);
-	assert(ht->mtable.block_count == 0);
+	assert(ht->mtable.block_counts[0] == 0);
 
 	uint32_t slot;
 	struct LIGHT(record) *record = (struct LIGHT(record) *)
@@ -695,7 +695,7 @@ inline int
 LIGHT(selfcheck)(const struct LIGHT(core) *ht)
 {
 	int res = 0;
-	if (ht->table_size != ht->mtable.block_count)
+	if (ht->table_size != ht->mtable.block_counts[0])
 		res |= 64;
 	uint32_t empty_slot = ht->empty_slot;
 	if (empty_slot == LIGHT(end)) {
diff --git a/src/lib/small/matras.c b/src/lib/small/matras.c
index 09929583788745bbff44b1819878ab25958de91b..ad814205b0c2b05ab37159db941a5973d4009ddc 100644
--- a/src/lib/small/matras.c
+++ b/src/lib/small/matras.c
@@ -4,6 +4,7 @@
 
 #include "matras.h"
 #include <limits.h>
+#include <string.h>
 #ifdef WIN32
 #include <intrin.h>
 #pragma intrinsic (_BitScanReverse)
@@ -17,12 +18,12 @@
  * approximate (floored) otherwise)
  */
 static matras_id_t
-pt_log2(matras_id_t val)
+matras_log2(matras_id_t val)
 {
 	assert(val > 0);
 #ifdef WIN32
-    unsigned long res = 0;
-    unsigned char nonzero = _BitScanReverse(&res, val);
+	unsigned long res = 0;
+	unsigned char nonzero = _BitScanReverse(&res, val);
 	assert(nonzero); (void)nonzero;
 	return (matras_id_t)res;
 #else
@@ -37,21 +38,28 @@ pt_log2(matras_id_t val)
  */
 void
 matras_create(struct matras *m, matras_id_t extent_size, matras_id_t block_size,
-	      prov_alloc_func alloc_func, prov_free_func free_func)
+	      matras_alloc_func alloc_func, matras_free_func free_func)
 {
 	/*extent_size must be power of 2 */
 	assert((extent_size & (extent_size - 1)) == 0);
 	/*block_size must be power of 2 */
 	assert((block_size & (block_size - 1)) == 0);
+	/*block must be not greater than the extent*/
+	assert(block_size <= extent_size);
+	/*extent must be able to store at least two records*/
+	assert(extent_size > sizeof(struct matras_record));
 
-	m->extent = 0;
-	m->block_count = 0;
+	m->block_counts[0] = 0;
 	m->extent_size = extent_size;
 	m->block_size = block_size;
 
-	matras_id_t log1 = pt_log2(extent_size);
-	matras_id_t log2 = pt_log2(block_size);
-	matras_id_t log3 = pt_log2(sizeof(void *));
+	m->ver_occ_mask = 1;
+
+	matras_id_t log1 = matras_log2(extent_size);
+	matras_id_t log2 = matras_log2(block_size);
+	assert((sizeof(struct matras_record) &
+	       (sizeof(struct matras_record) - 1)) == 0);
+	matras_id_t log3 = matras_log2(sizeof(struct matras_record));
 	m->log2_capacity = log1 * 3 - log2 - log3 * 2;
 	m->shift1 = log1 * 2 - log2 - log3;
 	m->shift2 = log1 - log2;
@@ -69,9 +77,14 @@ matras_create(struct matras *m, matras_id_t extent_size, matras_id_t block_size,
 void
 matras_destroy(struct matras *m)
 {
-	if (m->block_count) {
-		void* extent1 = m->extent;
-		matras_id_t id = m->block_count;
+	while(m->ver_occ_mask != 1) {
+		matras_id_t ver = __builtin_ctzl(m->ver_occ_mask ^ 1);
+		matras_delete_version(m, ver);
+	}
+	if (m->block_counts[0]) {
+		struct matras_record *extent1 = m->roots[0].ptr;
+		matras_id_t id = m->block_counts[0];
+		matras_id_t i, j;
 
 		matras_id_t n1 = id >> m->shift1;
 		id &= m->mask1;
@@ -83,30 +96,30 @@ matras_destroy(struct matras *m)
 			if (id)
 				n2++;
 
-			void *extent2 = ((void **)extent1)[n1];
-			for (matras_id_t j = 0; j < n2; j++) {
-				void *extent3 = ((void **)extent2)[j];
+			struct matras_record *extent2 = extent1[n1].ptr;
+			for (j = 0; j < n2; j++) {
+				struct matras_record *extent3 = extent2[j].ptr;
 				m->free_func(extent3);
 			}
 			m->free_func(extent2);
 		}
 
 		/* free fully loaded extents */
-		matras_id_t n2 = m->extent_size / sizeof(void *);
-		for (matras_id_t i = 0; i < n1; i++) {
-			void *extent2 = ((void **)extent1)[i];
-			for (matras_id_t j = 0; j < n2; j++) {
-				void *extent3 = ((void **)extent2)[j];
+		matras_id_t n2 = m->extent_size / sizeof(struct matras_record);
+		for ( i = 0; i < n1; i++) {
+			struct matras_record *extent2 = extent1[i].ptr;
+			for (j = 0; j < n2; j++) {
+				struct matras_record *extent3 = extent2[j].ptr;
 				m->free_func(extent3);
 			}
 			m->free_func(extent2);
 		}
 
 		m->free_func(extent1);
-		m->block_count = 0;
+		m->block_counts[0] = 0;
 	}
 #ifndef __OPTIMIZE__
-	m->extent = (void *)(long long)0xDEADBEEF;
+	m->roots[0].ptr = (struct matras_record *)(void *)(long long)0xDEADBEEF;
 #endif
 }
 
@@ -119,8 +132,7 @@ void
 matras_reset(struct matras *m)
 {
 	matras_destroy(m);
-	m->extent = 0;
-	m->block_count = 0;
+	m->block_counts[0] = 0;
 }
 
 
@@ -133,8 +145,8 @@ matras_reset(struct matras *m)
 void *
 matras_alloc(struct matras *m, matras_id_t *result_id)
 {
-	if (m->block_count)
-		assert(pt_log2(m->block_count) < m->log2_capacity);
+	if (m->block_counts[0])
+		assert(matras_log2(m->block_counts[0]) < m->log2_capacity);
 	/* Current block_count is the ID of new block */
 
 	/* See "Shifts and masks explanation" for details */
@@ -147,7 +159,7 @@ matras_alloc(struct matras *m, matras_id_t *result_id)
 	 * (n1 == 0 && n2 == 0 && n3 == 0) is identical to (id == 0)
 	 * (n2 == 0 && n3 == 0) is identical to (id ^ mask1 == 0)
 	 */
-	matras_id_t id = m->block_count;
+	matras_id_t id = m->block_counts[0];
 	matras_id_t extent1_not_empty = id;
 	matras_id_t n1 = id >> m->shift1;
 	id &= m->mask1;
@@ -157,33 +169,35 @@ matras_alloc(struct matras *m, matras_id_t *result_id)
 	matras_id_t extent3_not_empty = id;
 	matras_id_t n3 = id;
 
-	void *extent1, *extent2, *extent3;
+	struct matras_record *extent1, *extent2, *extent3;
 
 	if (extent1_not_empty) {
-		extent1 = m->extent;
+		extent1 = m->roots[0].ptr;
 	} else {
-		extent1 = m->alloc_func();
+		extent1 = (struct matras_record *)m->alloc_func();
 		if (!extent1)
 			return 0;
-		m->extent = extent1;
+		m->roots[0].ptr = extent1;
+		m->roots[0].tag = ~(m->ver_occ_mask ^ 1);
 	}
 
 	if (extent2_not_empty) {
-		extent2 = ((void **)extent1)[n1];
+		extent2 = extent1[n1].ptr;
 	} else {
-		extent2 = m->alloc_func();
+		extent2 = (struct matras_record *)m->alloc_func();
 		if (!extent2) {
 			if (!extent1_not_empty) /* means - was empty */
 				m->free_func(extent1);
 			return 0;
 		}
-		((void **)extent1)[n1] = extent2;
+		extent1[n1].ptr = extent2;
+		extent1[n1].tag = ~(m->ver_occ_mask ^ 1);
 	}
 
 	if (extent3_not_empty) {
-		extent3 = ((void **)extent2)[n2];
+		extent3 = extent2[n2].ptr;
 	} else {
-		extent3 = m->alloc_func();
+		extent3 = (struct matras_record *)m->alloc_func();
 		if (!extent3) {
 			if (!extent1_not_empty) /* means - was empty */
 				m->free_func(extent1);
@@ -191,11 +205,12 @@ matras_alloc(struct matras *m, matras_id_t *result_id)
 				m->free_func(extent2);
 			return 0;
 		}
-		((void **)extent2)[n2] = extent3;
+		extent2[n2].ptr = extent3;
+		extent2[n2].tag = ~(m->ver_occ_mask ^ 1);
 	}
 
-	*result_id = m->block_count++;
-	return (void*)((char*)extent3 + n3 * m->block_size);
+	*result_id = m->block_counts[0]++;
+	return (void *)((char*)extent3 + n3 * m->block_size);
 }
 
 /*
@@ -204,14 +219,16 @@ matras_alloc(struct matras *m, matras_id_t *result_id)
 void
 matras_dealloc(struct matras *m)
 {
-	assert(m->block_count);
-	m->block_count--;
+	assert(m->block_counts[0]);
+	matras_id_t last = m->block_counts[0] - 1;
+	matras_before_change(m, last);
+	m->block_counts[0] = last;
 	/* Current block_count is the ID of deleting block */
 
 	/* See "Shifts and masks explanation" for details */
 	/* Deleting extents in same way (but reverse order) like in matras_alloc
 	 * See matras_alloc for details. */
-	matras_id_t id = m->block_count;
+	matras_id_t id = m->block_counts[0];
 	matras_id_t extent1_free = !id;
 	matras_id_t n1 = id >> m->shift1;
 	id &= m->mask1;
@@ -221,10 +238,10 @@ matras_dealloc(struct matras *m)
 	matras_id_t extent3_free = !id;
 
 	if (extent1_free || extent2_free || extent3_free) {
-		void *extent1, *extent2, *extent3;
-		extent1 = m->extent;
-		extent2 = ((void **)extent1)[n1];
-		extent3 = ((void **)extent2)[n2];
+		struct matras_record *extent1, *extent2, *extent3;
+		extent1 = m->roots[0].ptr;
+		extent2 = extent1[n1].ptr;
+		extent3 = extent2[n2].ptr;
 		if (extent3_free)
 			m->free_func(extent3);
 		if (extent2_free)
@@ -246,11 +263,11 @@ matras_dealloc(struct matras *m)
 void *
 matras_alloc_range(struct matras *m, matras_id_t *id, matras_id_t range_count)
 {
-	assert(m->block_count % range_count == 0);
+	assert(m->block_counts[0] % range_count == 0);
 	assert(m->extent_size / m->block_size % range_count == 0);
 	void *res = matras_alloc(m, id);
 	if (res)
-		m->block_count += (range_count - 1);
+		m->block_counts[0] += (range_count - 1);
 	return res;
 }
 
@@ -263,9 +280,9 @@ matras_alloc_range(struct matras *m, matras_id_t *id, matras_id_t range_count)
 void
 matras_dealloc_range(struct matras *m, matras_id_t range_count)
 {
-	assert(m->block_count % range_count == 0);
+	assert(m->block_counts[0] % range_count == 0);
 	assert(m->extent_size / m->block_size % range_count == 0);
-	m->block_count -= (range_count - 1);
+	m->block_counts[0] -= (range_count - 1);
 	matras_dealloc(m);
 }
 
@@ -279,17 +296,369 @@ matras_extents_count(const struct matras *m)
 	 * Let's calculate extents count level by level, starting from leafs
 	 * Last level of the tree consists of extents that stores blocks,
 	 * so we can calculate number of extents by block count: */
-	matras_id_t c = (m->block_count + (m->extent_size / m->block_size - 1))
+	matras_id_t c = (m->block_counts[0] + (m->extent_size / m->block_size - 1))
 		/ (m->extent_size / m->block_size);
 	matras_id_t res = c;
 
 	/* two upper levels consist of extents that stores pointers to extents,
 	 * so we can calculate number of extents by lower level extent count:*/
-	for (matras_id_t i = 0; i < 2; i++) {
-		c = (c + (m->extent_size / sizeof(void *) - 1))
-			/ (m->extent_size / sizeof(void *));
+	matras_id_t i;
+	for (i = 0; i < 2; i++) {
+		c = (c + (m->extent_size / sizeof(struct matras_record) - 1))
+			/ (m->extent_size / sizeof(struct matras_record));
 		res += c;
 	}
 
 	return res;
 }
+
+/*
+ * Create new version of matras memory.
+ * Return 0 if all version IDs are occupied.
+ */
+matras_id_t
+matras_new_version(struct matras *m)
+{
+	matras_id_t ver_id;
+#ifdef WIN32
+	unsigned long res = 0;
+	unsigned char nonzero = _BitScanForward(&res, m->ver_occ_mask);
+	assert(nonzero); (void)nonzero;
+	ver_id = (matras_id_t) res;
+#else
+	ver_id = (matras_id_t)
+		__builtin_ctzl(~m->ver_occ_mask);
+#endif
+	assert(ver_id > 0);
+	if (ver_id >= MATRAS_VERSION_COUNT)
+		return 0;
+	m->ver_occ_mask |= ((matras_version_tag_t)1) << ver_id;
+	m->roots[ver_id] = m->roots[0];
+	m->block_counts[ver_id] = m->block_counts[0];
+	return ver_id;
+}
+
+/*
+ * Delete memory version by specified ID.
+ */
+void
+matras_delete_version(struct matras *m, matras_id_t ver_id)
+{
+	matras_version_tag_t me = ((matras_version_tag_t)1) << ver_id;
+	if (m->block_counts[ver_id]) {
+		matras_id_t step = m->mask2 + 1, j;
+		for (j = 0; j < m->block_counts[ver_id]; j += step) {
+			matras_id_t n1 = j >> m->shift1;
+			matras_id_t n2 = (j & m->mask1) >> m->shift2;
+			matras_version_tag_t owners =
+				m->roots[ver_id].ptr[n1].ptr[n2].tag &
+				m->ver_occ_mask;
+			assert(owners & me);
+			if (owners == me) {
+				m->free_func(m->roots[ver_id].ptr[n1].ptr[n2].ptr);
+				m->roots[ver_id].ptr[n1].ptr[n2].tag ^= me;
+#ifndef __OPTIMIZE__
+				m->roots[ver_id].ptr[n1].ptr[n2].ptr =
+					(struct matras_record *)(void *)
+					(long long)0xDEADBEEF;
+#endif
+			} else {
+				matras_version_tag_t run = owners;
+				do {
+					uint32_t oth_ver = __builtin_ctzl(run);
+					run ^= ((matras_version_tag_t)1) << oth_ver;
+					assert((m->roots[oth_ver].ptr[n1].ptr[n2].tag &
+						m->ver_occ_mask & ~me) ==
+					       (owners & ~me));
+					assert(m->roots[oth_ver].ptr[n1].ptr[n2].ptr ==
+					       m->roots[ver_id].ptr[n1].ptr[n2].ptr);
+					m->roots[oth_ver].ptr[n1].ptr[n2].tag &= ~me;
+				} while (run);
+			}
+		}
+		step = m->mask1 + 1;
+		for (j = 0; j < m->block_counts[ver_id]; j += step) {
+			matras_id_t n1 = j >> m->shift1;
+			matras_version_tag_t owners =
+				m->roots[ver_id].ptr[n1].tag & m->ver_occ_mask;
+			assert(owners & me);
+			if (owners == me) {
+				m->free_func(m->roots[ver_id].ptr[n1].ptr);
+				m->roots[ver_id].ptr[n1].tag ^= me;
+#ifndef __OPTIMIZE__
+				m->roots[ver_id].ptr[n1].ptr =
+					(struct matras_record *)(void *)
+					(long long)0xDEADBEEF;
+#endif
+			} else {
+				matras_version_tag_t run = owners;
+				do {
+					uint32_t oth_ver = __builtin_ctzl(run);
+					run ^= ((matras_version_tag_t)1) << oth_ver;
+					assert((m->roots[oth_ver].ptr[n1].tag &
+						m->ver_occ_mask & ~me) ==
+					       (owners & ~me));
+					assert(m->roots[oth_ver].ptr[n1].ptr ==
+					       m->roots[ver_id].ptr[n1].ptr);
+					m->roots[oth_ver].ptr[n1].tag &= ~me;
+				} while (run);
+			}
+		}
+		matras_version_tag_t owners
+			= m->roots[ver_id].tag & m->ver_occ_mask;
+		assert(owners & me);
+		if (owners == me) {
+			m->free_func(m->roots[ver_id].ptr);
+			m->roots[ver_id].tag ^= me;
+#ifndef __OPTIMIZE__
+			m->roots[ver_id].ptr =
+				(struct matras_record *)(void *)
+				(long long)0xDEADBEEF;
+#endif
+		} else {
+			matras_version_tag_t run = owners;
+			do {
+				uint32_t oth_ver = __builtin_ctzl(run);
+				run ^= ((matras_version_tag_t)1) << oth_ver;
+				assert((m->roots[oth_ver].tag &
+					m->ver_occ_mask & ~me) ==
+				       (owners & ~me));
+				assert(m->roots[oth_ver].ptr ==
+				       m->roots[ver_id].ptr);
+				m->roots[oth_ver].tag &= ~me;
+			} while (run);
+		}
+		m->block_counts[ver_id] = 0;
+	}
+	if (m->block_counts[0]) {
+		matras_id_t step = m->mask2 + 1, j;
+		for (j = 0; j < m->block_counts[0]; j += step) {
+			matras_id_t n1 = j >> m->shift1;
+			matras_id_t n2 = (j & m->mask1) >> m->shift2;
+			matras_version_tag_t run =
+				m->roots[0].ptr[n1].ptr[n2].tag &
+				m->ver_occ_mask;
+			do {
+				uint32_t oth_ver = __builtin_ctzl(run);
+				run ^= ((matras_version_tag_t)1) << oth_ver;
+				m->roots[oth_ver].ptr[n1].ptr[n2].tag |= me;
+			} while (run);
+		}
+		step = m->mask1 + 1;
+		for (j = 0; j < m->block_counts[0]; j += step) {
+			matras_id_t n1 = j >> m->shift1;
+			matras_version_tag_t run =
+				m->roots[0].ptr[n1].tag & m->ver_occ_mask;
+			do {
+				uint32_t oth_ver = __builtin_ctzl(run);
+				run ^= ((matras_version_tag_t)1) << oth_ver;
+				m->roots[oth_ver].ptr[n1].tag |= me;
+			} while (run);
+		}
+		matras_version_tag_t run =
+			m->roots[0].tag & m->ver_occ_mask;
+		do {
+			uint32_t oth_ver = __builtin_ctzl(run);
+			run ^= ((matras_version_tag_t)1) << oth_ver;
+			m->roots[oth_ver].tag |= me;
+		} while (run);
+	}
+	m->ver_occ_mask ^= me;
+}
+
+/*
+ * Notify matras that memory at given ID will be changed.
+ * Returns true if ok, and false if failed to allocate memory.
+ */
+void *
+matras_before_change(struct matras *m, matras_id_t id)
+{
+	assert(id < m->block_counts[0]);
+
+	/* see "Shifts and masks explanation" for details */
+	matras_id_t n1 = id >> m->shift1;
+	matras_id_t n2 = (id & m->mask1) >> m->shift2;
+	matras_id_t n3 = id & m->mask2;
+
+	struct matras_record *l1 = &m->roots[0];
+	struct matras_record *l2 = &l1->ptr[n1];
+	struct matras_record *l3 = &l2->ptr[n2];
+	matras_version_tag_t owner_mask3 = l3->tag & m->ver_occ_mask;
+	assert(owner_mask3 & 1);
+	if (owner_mask3 == 1)
+		return &((char *)(l3->ptr))[n3 * m->block_size]; /* private page */
+
+	struct matras_record *new_extent3 =
+		(struct matras_record *)m->alloc_func();
+	if (!new_extent3)
+		return 0;
+
+	matras_version_tag_t owner_mask1 = l1->tag & m->ver_occ_mask;
+	assert(owner_mask1 & 1);
+	matras_version_tag_t owner_mask2 = l2->tag & m->ver_occ_mask;
+	assert(owner_mask2 & 1);
+	struct matras_record *new_extent1 = 0, *new_extent2 = 0;
+	if (owner_mask1 != 1) {
+		new_extent1 = (struct matras_record *)m->alloc_func();
+		if (!new_extent1) {
+			m->free_func(new_extent3);
+			return 0;
+		}
+	}
+	if (owner_mask2 != 1) {
+		new_extent2 = (struct matras_record *)m->alloc_func();
+		if (!new_extent2) {
+			m->free_func(new_extent3);
+			if (owner_mask1 != 1)
+				m->free_func(new_extent1);
+			return 0;
+		}
+	}
+	matras_version_tag_t new_tag = ~(m->ver_occ_mask ^ 1);
+
+	if (owner_mask1 != 1) {
+		memcpy(new_extent1, l1->ptr, m->extent_size);
+		l1->tag = new_tag;
+		l1->ptr = new_extent1;
+		matras_version_tag_t run = owner_mask1 ^ 1;
+		matras_version_tag_t oth_tag = run & m->ver_occ_mask;
+		do {
+			uint32_t ver = __builtin_ctzl(run);
+			run ^= ((matras_version_tag_t)1) << ver;
+			m->roots[ver].tag = oth_tag;
+		} while (run);
+		l2 = &l1->ptr[n1];
+	}
+
+	if (owner_mask2 != 1) {
+		memcpy(new_extent2, l2->ptr, m->extent_size);
+		l2->tag = new_tag;
+		l2->ptr = new_extent2;
+		matras_version_tag_t run = owner_mask2 ^ 1;
+		matras_version_tag_t oth_tag = run & m->ver_occ_mask;
+		do {
+			uint32_t ver = __builtin_ctzl(run);
+			run ^= ((matras_version_tag_t)1) << ver;
+			m->roots[ver].ptr[n1].tag = oth_tag;
+		} while (run);
+		l3 = &l2->ptr[n2];
+	}
+
+	memcpy(new_extent3, l3->ptr, m->extent_size);
+	l3->tag = new_tag;
+	l3->ptr = new_extent3;
+	matras_version_tag_t run = owner_mask3 ^ 1;
+	matras_version_tag_t oth_tag = run & m->ver_occ_mask;
+	do {
+		uint32_t ver = __builtin_ctzl(run);
+		run ^= ((matras_version_tag_t)1) << ver;
+		m->roots[ver].ptr[n1].ptr[n2].tag = oth_tag;
+	} while (run);
+
+	return &((char *)new_extent3)[n3 * m->block_size]; ;
+}
+
+/*
+ * Debug check that ensures internal consistency.
+ * Must return 0. If i returns not 0, smth is terribly wrong.
+ */
+matras_version_tag_t
+matras_debug_selfcheck(const struct matras *m)
+{
+	matras_version_tag_t res = 0, i;
+	for (i = 0; i < MATRAS_VERSION_COUNT; i++) {
+		matras_version_tag_t me = ((matras_version_tag_t) 1) << i;
+		if (!(m->ver_occ_mask & me))
+			continue;
+		matras_id_t step = m->mask2 + 1, j;
+		for (j = 0; j < m->block_counts[i]; j += step) {
+			matras_id_t n1 = j >> m->shift1;
+			matras_id_t n2 = (j & m->mask1) >> m->shift2;
+			if (!(m->roots[i].tag & me))
+				res |= (1 | (me << 12));
+			if ((m->roots[i].tag & m->ver_occ_mask) != me) {
+				matras_version_tag_t run = m->roots[i].tag
+					& m->ver_occ_mask;
+				do {
+					uint32_t oth_ver = __builtin_ctzl(run);
+					run ^= ((matras_version_tag_t) 1)
+						<< oth_ver;
+					if (m->roots[i].tag
+						!= m->roots[oth_ver].tag)
+						res |= (8 | (me << 12));
+				} while (run);
+			}
+			if (!(m->roots[i].ptr[n1].tag & me))
+				res |= (2 | (me << 12));
+			if ((m->roots[i].ptr[n1].tag & m->ver_occ_mask) != me) {
+				matras_version_tag_t run =
+					m->roots[i].ptr[n1].tag
+						& m->ver_occ_mask;
+				do {
+					uint32_t oth_ver = __builtin_ctzl(run);
+					run ^= ((matras_version_tag_t) 1)
+						<< oth_ver;
+					if (m->roots[i].ptr[n1].tag
+						!= m->roots[oth_ver].ptr[n1].tag)
+						res |= (0x80 | (me << 12));
+				} while (run);
+			}
+			if (!(m->roots[i].ptr[n1].ptr[n2].tag & me))
+				res |= (4 | (me << 12));
+			if ((m->roots[i].ptr[n1].ptr[n2].tag & m->ver_occ_mask)
+				!= me) {
+				matras_version_tag_t run =
+					m->roots[i].ptr[n1].ptr[n2].tag
+						& m->ver_occ_mask;
+				do {
+					uint32_t oth_ver = __builtin_ctzl(run);
+					run ^= ((matras_version_tag_t) 1)
+						<< oth_ver;
+					if (m->roots[i].ptr[n1].ptr[n2].tag
+						!= m->roots[oth_ver].ptr[n1].ptr[n2].tag)
+						res |= (0x800 | (me << 12));
+				} while (run);
+			}
+		}
+	}
+	{
+		i = 0;
+		matras_version_tag_t me = ((matras_version_tag_t) 1) << i;
+		matras_id_t step = m->mask2 + 1, j;
+		for (j = 0; j < m->block_counts[i]; j += step) {
+			matras_id_t n1 = j >> m->shift1;
+			matras_id_t n2 = (j & m->mask1) >> m->shift2;
+			if ((m->roots[i].tag & ~m->ver_occ_mask)
+				!= ~m->ver_occ_mask)
+				res |= (0x10 | (me << 12));
+			if ((m->roots[i].ptr[n1].tag & ~m->ver_occ_mask)
+				!= ~m->ver_occ_mask)
+				res |= (0x20 | (me << 12));
+			if ((m->roots[i].ptr[n1].ptr[n2].tag & ~m->ver_occ_mask)
+				!= ~m->ver_occ_mask)
+				res |= (0x40 | (me << 12));
+		}
+	}
+	for (i = 1; i < MATRAS_VERSION_COUNT; i++) {
+		matras_version_tag_t me = ((matras_version_tag_t) 1) << i;
+		if (!(m->ver_occ_mask & me))
+			continue;
+		matras_id_t step = m->mask2 + 1, j;
+		for (j = 0; j < m->block_counts[i]; j += step) {
+			matras_id_t n1 = j >> m->shift1;
+			matras_id_t n2 = (j & m->mask1) >> m->shift2;
+			if (!(m->roots[i].tag & 1))
+				if ((m->roots[i].tag & ~m->ver_occ_mask) != 0)
+					res |= (0x100 | (me << 12));
+			if (!(m->roots[i].ptr[n1].tag & 1))
+				if ((m->roots[i].ptr[n1].tag & ~m->ver_occ_mask)
+					!= 0)
+					res |= (0x200 | (me << 12));
+			if (!(m->roots[i].ptr[n1].ptr[n2].tag & 1))
+				if ((m->roots[i].ptr[n1].ptr[n2].tag
+					& ~m->ver_occ_mask) != 0)
+					res |= (0x400 | (me << 12));
+		}
+	}
+	return res;
+}
diff --git a/src/lib/small/matras.h b/src/lib/small/matras.h
index 24595995a65ded6134ead750b8c537477337ee74..40c24f01aeda2623faba816acab22203549844b7 100644
--- a/src/lib/small/matras.h
+++ b/src/lib/small/matras.h
@@ -59,7 +59,7 @@
  *  level 1 extent
  *  - second N2 bits - level 1 extent id - stores the address of
  *  the extent which contains actual blocks
- *  - remaining N3 bits - block number in level 1 extent
+ *  - remaining N3 bits - block number in level 2 extent
  * (Actual values of N1 and N2 are a function of block size B).
  * Calculation of N1, N2 and N3 depends on sizes of blocks,
  * extents and sizeof(void *).
@@ -70,7 +70,7 @@
  * instance:
  *
  * 1) can provide not more than
- *    pow(M / sizeof(void*), L - 1) * (M / N)
+ *    pow(M / sizeof(void*) / 2, L - 1) * (M / N)
  *    blocks
  * 2) costs (L - 1) random memory accesses to provide a new block
  *    or restore a block pointer from block id
@@ -79,6 +79,19 @@
  * Of course, the integer type used for block id (matras_id_t,
  * usually is a typedef to uint32) also limits the maximum number
  * of objects that can be block_count by an instance of matras.
+ *
+ * Additionally matras provides freezing all allocated data. At
+ * any moment user can call matras_new_version method for achieving
+ * unique ID of current data state. Then the user could change,
+ * allocate and dealocate main matras data, meanwhile getting data
+ * from previusly freezed version. This works through copy-on-write
+ * mechanism, thus is cheap enough, and of course this works only
+ * if user correctly notifies mastras about chainging data.
+ * An impotant property of implemented copy-on-write mechnism - is
+ * that main data does not moved, i.e. before data modification the
+ * extent is copied to another location to become the extent
+ * for freezed version. Thus main block address with some particular
+ * block ID is unchanged.
  */
 /* }}} */
 
@@ -90,6 +103,7 @@ typedef uint32_t matras_id_t;
 #endif
 
 #include <assert.h>
+#include <stdbool.h>
 
 #if defined(__cplusplus)
 extern "C" {
@@ -104,8 +118,27 @@ extern "C" {
  * of size M). Is allowed to return NULL, but is not allowed
  * to throw an exception
  */
-typedef void *(*prov_alloc_func)();
-typedef void (*prov_free_func)(void *);
+typedef void *(*matras_alloc_func)();
+typedef void (*matras_free_func)(void *);
+
+typedef uint32_t matras_version_tag_t;
+
+struct matras_record {
+	/* pointer to next level of a tree */
+	union {
+		struct matras_record *ptr;
+		matras_version_tag_t ptr_padded;
+	};
+	/* version tag - bitmask of all version referencing ptr above */
+	union {
+		matras_version_tag_t tag;
+		void *tag_padded;
+	};
+};
+
+enum {
+	MATRAS_VERSION_COUNT = 8
+};
 
 /**
  * matras - memory allocator of blocks of equal
@@ -113,13 +146,15 @@ typedef void (*prov_free_func)(void *);
  */
 struct matras {
 	/* Pointer to the root extent of matras */
-	void *extent;
+	struct matras_record roots[MATRAS_VERSION_COUNT];
+	/* A number of already allocated blocks */
+	matras_id_t block_counts[MATRAS_VERSION_COUNT];
+	/* Bit mask of used versions */
+	matras_version_tag_t ver_occ_mask;
 	/* Block size (N) */
 	matras_id_t block_size;
 	/* Extent size (M) */
 	matras_id_t extent_size;
-	/* A number of already allocated blocks */
-	matras_id_t block_count;
 	/* binary logarithm  of maximum possible created blocks count */
 	matras_id_t log2_capacity;
 	/* See "Shifts and masks explanation" below  */
@@ -127,9 +162,9 @@ struct matras {
 	/* See "Shifts and masks explanation" below  */
 	matras_id_t mask1, mask2;
 	/* External extent allocator */
-	prov_alloc_func alloc_func;
+	matras_alloc_func alloc_func;
 	/* External extent deallocator */
-	prov_free_func free_func;
+	matras_free_func free_func;
 };
 
 /*
@@ -160,7 +195,7 @@ struct matras {
  */
 void
 matras_create(struct matras *m, matras_id_t extent_size, matras_id_t block_size,
-	      prov_alloc_func alloc_func, prov_free_func free_func);
+	      matras_alloc_func alloc_func, matras_free_func free_func);
 
 /**
  * Free all memory used by an instance of matras and
@@ -217,12 +252,45 @@ matras_dealloc_range(struct matras *m, matras_id_t range_count);
 static void *
 matras_get(const struct matras *m, matras_id_t id);
 
+/**
+ * Convert block id of a specified version into block address.
+ */
+static void *
+matras_getv(const struct matras *m, matras_id_t id, matras_id_t version);
+
 /*
  * Getting number of allocated extents (of size extent_size each)
 */
 matras_id_t
 matras_extents_count(const struct matras *m);
 
+/*
+ * Create new version of matras memory.
+ * Return 0 if all version IDs are occupied.
+ */
+matras_id_t
+matras_new_version(struct matras *m);
+
+/*
+ * Delete memory version by specified ID.
+ */
+void
+matras_delete_version(struct matras *m, matras_id_t ver_id);
+
+/*
+ * Notify matras that memory at given ID will be changed.
+ * Returns true if ok, and false if failed to allocate memory.
+ * Only needed (and does any work) if some versions are used.
+ */
+void *
+matras_before_change(struct matras *m, matras_id_t id);
+
+/*
+ * Debug check that ensures internal consistency.
+ * Must return 0. If i returns not 0, smth is terribly wrong.
+ */
+matras_version_tag_t
+matras_debug_selfcheck(const struct matras *m);
 
 /**
  * matras_get implementation
@@ -230,14 +298,32 @@ matras_extents_count(const struct matras *m);
 static inline void *
 matras_get(const struct matras *m, matras_id_t id)
 {
-	assert(id < m->block_count);
+	assert(id < m->block_counts[0]);
+
+	/* see "Shifts and masks explanation" for details */
+	matras_id_t n1 = id >> m->shift1;
+	matras_id_t n2 = (id & m->mask1) >> m->shift2;
+	matras_id_t n3 = (id & m->mask2);
+
+	char *extent = (char *)m->roots[0].ptr[n1].ptr[n2].ptr;
+	return &extent[n3 * m->block_size];
+}
+
+/**
+ * matras_getv implementation
+ */
+static inline void *
+matras_getv(const struct matras *m, matras_id_t id, matras_id_t version)
+{
+	assert(id < m->block_counts[version]);
 
 	/* see "Shifts and masks explanation" for details */
 	matras_id_t n1 = id >> m->shift1;
 	matras_id_t n2 = (id & m->mask1) >> m->shift2;
 	matras_id_t n3 = (id & m->mask2);
 
-	return (((char***)m->extent)[n1][n2] + n3 * m->block_size);
+	char *extent = (char *)m->roots[version].ptr[n1].ptr[n2].ptr;
+	return &extent[n3 * m->block_size];
 }
 
 #if defined(__cplusplus)
diff --git a/test/box/errinj_index.result b/test/box/errinj_index.result
index a7536ba883370eb02af855c927d52f2beb417491..028d76a46973214bcdcf0078c0e9fdbd0afa3f35 100644
--- a/test/box/errinj_index.result
+++ b/test/box/errinj_index.result
@@ -91,9 +91,9 @@ res
   - [9, 9, 'test9']
   - [10, 10, 'test10']
 ...
-for i = 501,2500 do s:insert{i, i} end
+for i = 501,5000 do s:insert{i, i} end
 ---
-- error: Failed to allocate 16384 bytes in MemtxTree for replace
+- error: Failed to allocate 32768 bytes in MemtxTree for replace
 ...
 s:delete{1}
 ---
@@ -139,24 +139,24 @@ res
 res = {}
 ---
 ...
-for i = 2001,2010 do table.insert(res, (s:get{i})) end
+for i = 4001,4010 do table.insert(res, (s:get{i})) end
 ---
 ...
 res
 ---
 - []
 ...
---count must be greater that 1000 but less than 2000
-function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count <= 1000 and "fail 1" or count >= 2000 and "fail 2" or "ok" end
+--count must be greater that 2000 but less than 4000
+function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count <= 2000 and "fail 1" or count >= 4000 and "fail 2" or "ok" end
 ---
 ...
 check_iter_and_size()
 ---
 - ok
 ...
-for i = 2501,3500 do s:insert{i, i} end
+for i = 5001,6000 do s:insert{i, i} end
 ---
-- error: Failed to allocate 16384 bytes in MemtxTree for replace
+- error: Failed to allocate 32768 bytes in MemtxTree for replace
 ...
 s:delete{2}
 ---
@@ -183,9 +183,9 @@ res
   - [9, 9, 'test9']
   - [10, 10, 'test10']
 ...
-for i = 3501,4500 do s:insert{i, i} end
+for i = 6001,7000 do s:insert{i, i} end
 ---
-- error: Failed to allocate 16384 bytes in MemtxTree for replace
+- error: Failed to allocate 32768 bytes in MemtxTree for replace
 ...
 s:delete{3}
 ---
@@ -199,7 +199,7 @@ errinj.set("ERRINJ_INDEX_ALLOC", false)
 ---
 - ok
 ...
-for i = 4501,5500 do s:insert{i, i} end
+for i = 7001,8000 do s:insert{i, i} end
 ---
 ...
 res = {}
@@ -240,21 +240,21 @@ res
 res = {}
 ---
 ...
-for i = 5001,5010 do table.insert(res, (s:get{i})) end
+for i = 7501,7510 do table.insert(res, (s:get{i})) end
 ---
 ...
 res
 ---
-- - [5001, 5001]
-  - [5002, 5002]
-  - [5003, 5003]
-  - [5004, 5004]
-  - [5005, 5005]
-  - [5006, 5006]
-  - [5007, 5007]
-  - [5008, 5008]
-  - [5009, 5009]
-  - [5010, 5010]
+- - [7501, 7501]
+  - [7502, 7502]
+  - [7503, 7503]
+  - [7504, 7504]
+  - [7505, 7505]
+  - [7506, 7506]
+  - [7507, 7507]
+  - [7508, 7508]
+  - [7509, 7509]
+  - [7510, 7510]
 ...
 s:drop()
 ---
diff --git a/test/box/errinj_index.test.lua b/test/box/errinj_index.test.lua
index 05155f261e6c16f7831c0e53ba20f9270f89c5f9..845cc2ff6dc9b3d485b95e9fcd665a065c10dcc8 100644
--- a/test/box/errinj_index.test.lua
+++ b/test/box/errinj_index.test.lua
@@ -22,7 +22,7 @@ res = {}
 for _, t in s.index[0]:pairs() do table.insert(res, t) end
 res
 
-for i = 501,2500 do s:insert{i, i} end
+for i = 501,5000 do s:insert{i, i} end
 s:delete{1}
 
 res = {}
@@ -32,27 +32,27 @@ res = {}
 for i = 501,510 do table.insert(res, (s:get{i})) end
 res
 res = {}
-for i = 2001,2010 do table.insert(res, (s:get{i})) end
+for i = 4001,4010 do table.insert(res, (s:get{i})) end
 res
 
---count must be greater that 1000 but less than 2000
-function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count <= 1000 and "fail 1" or count >= 2000 and "fail 2" or "ok" end
+--count must be greater that 2000 but less than 4000
+function check_iter_and_size() local count = 0 for _, t in s.index[0]:pairs() do count = count + 1 end return count <= 2000 and "fail 1" or count >= 4000 and "fail 2" or "ok" end
 check_iter_and_size()
 
-for i = 2501,3500 do s:insert{i, i} end
+for i = 5001,6000 do s:insert{i, i} end
 s:delete{2}
 check_iter_and_size()
 res = {}
 for i = 1,10 do table.insert(res, (s:get{i})) end
 res
 
-for i = 3501,4500 do s:insert{i, i} end
+for i = 6001,7000 do s:insert{i, i} end
 s:delete{3}
 check_iter_and_size()
 
 errinj.set("ERRINJ_INDEX_ALLOC", false)
 
-for i = 4501,5500 do s:insert{i, i} end
+for i = 7001,8000 do s:insert{i, i} end
 res = {}
 for i = 1,10 do table.insert(res, (s:get{i})) end
 res
@@ -61,7 +61,7 @@ res = {}
 for i = 1,10 do table.insert(res, (s:get{i})) end
 res
 res = {}
-for i = 5001,5010 do table.insert(res, (s:get{i})) end
+for i = 7501,7510 do table.insert(res, (s:get{i})) end
 res
 s:drop()
 
diff --git a/test/unit/matras.cc b/test/unit/matras.cc
index 4970cb7a1de98f39dbbb62efeebca09b08157048..04f26b17f64a3be183be59a0abc39e503c428897 100644
--- a/test/unit/matras.cc
+++ b/test/unit/matras.cc
@@ -64,8 +64,8 @@ void matras_alloc_test()
 {
 	std::cout << "Testing matras_alloc..." << std::endl;
 	unsigned int maxCapacity =  PROV_EXTENT_SIZE / PROV_BLOCK_SIZE;
-	maxCapacity *= PROV_EXTENT_SIZE / sizeof(void *);
-	maxCapacity *= PROV_EXTENT_SIZE / sizeof(void *);
+	maxCapacity *= PROV_EXTENT_SIZE / sizeof(struct matras_record);
+	maxCapacity *= PROV_EXTENT_SIZE / sizeof(struct matras_record);
 
 	struct matras pta;
 
@@ -136,10 +136,10 @@ void matras_alloc_test()
 
 		for (unsigned int j = 0; j < maxCapacity; j++) {
 			unsigned int res = 0;
-			unsigned int prev_block_count = pta.block_count;
+			unsigned int prev_block_count = pta.block_counts[0];
 			void *data = matras_alloc(&pta, &res);
 			if (!data) {
-				check(prev_block_count == pta.block_count, "Created count changed during memory fail!");
+				check(prev_block_count == pta.block_counts[0], "Created count changed during memory fail!");
 				break;
 			}
 		}
@@ -150,9 +150,102 @@ void matras_alloc_test()
 	std::cout << "Testing matras_alloc successfully finished" << std::endl;
 }
 
+typedef uint64_t type_t;
+const size_t VER_EXTENT_SIZE = 512;
+int extents_in_use = 0;
+
+void *all()
+{
+	extents_in_use++;
+	return malloc(VER_EXTENT_SIZE);
+}
+
+void dea(void *p)
+{
+	extents_in_use--;
+	free(p);
+}
+void
+matras_vers_test()
+{
+	std::cout << "Testing matras versions..." << std::endl;
+
+	std::vector<type_t> comps[MATRAS_VERSION_COUNT];
+	int use_mask = 1;
+	int cur_num_or_ver = 1;
+	struct matras local;
+	matras_create(&local, VER_EXTENT_SIZE, sizeof(type_t), all, dea);
+	type_t val = 0;
+	for (int s = 10; s < 8000; s = int(s * 1.5)) {
+		for (int k = 0; k < 800; k++) {
+			bool check_me = false;
+			if (rand() % 16 == 0) {
+				bool add_ver;
+				if (cur_num_or_ver == 1)
+					add_ver = true;
+				else if (cur_num_or_ver == MATRAS_VERSION_COUNT)
+					add_ver = false;
+				else
+					add_ver = rand() % 2 == 0;
+				if (add_ver) {
+					cur_num_or_ver++;
+					int new_ver = matras_new_version(&local);
+					use_mask |= (1 << new_ver);
+					comps[new_ver] = comps[0];
+				} else {
+					cur_num_or_ver--;
+					int del_ver;
+					do {
+						del_ver = 1 + rand() % (MATRAS_VERSION_COUNT - 1);
+					} while ((use_mask & (1 << del_ver)) == 0);
+					matras_delete_version(&local, del_ver);
+					comps[del_ver].clear();
+					use_mask &= ~(1 << del_ver);
+				}
+				check_me = true;
+			} else {
+				check_me = rand() % 16 == 0;
+				if (rand() % 8 == 0 && comps[0].size() > 0) {
+					matras_dealloc(&local);
+					comps[0].pop_back();
+				}
+				int p = rand() % s;
+				int mod = 0;
+				while (p >= comps[0].size()) {
+					comps[0].push_back(val * 10000 + mod);
+					matras_id_t tmp;
+					type_t *ptrval = (type_t *)matras_alloc(&local, &tmp);
+					*ptrval = val * 10000 + mod;
+					mod++;
+				}
+				val++;
+				comps[0][p] = val;
+				matras_before_change(&local, p);
+				*(type_t *)matras_get(&local, p) = val;
+			}
+			int checkres = matras_debug_selfcheck(&local);
+			check(checkres == 0, "internal check failed");
+			for (int i = 0; i < MATRAS_VERSION_COUNT; i++) {
+				if ((use_mask & (1 << i)) == 0)
+					continue;
+				check(comps[i].size() == local.block_counts[i], "size mismatch");
+				for (int j = 0; j < comps[i].size(); j++) {
+					type_t val1 = comps[i][j];
+					type_t val2 = *(type_t *)matras_getv(&local, j, i);
+					check(val1 == val2, "data mismatch");
+				}
+			}
+		}
+	}
+	matras_destroy(&local);
+	check(extents_in_use == 0, "memory leak");
+
+	std::cout << "Testing matras_version successfully finished" << std::endl;
+}
 
 int
 main(int, const char **)
 {
 	matras_alloc_test();
+	matras_vers_test();
 }
diff --git a/test/unit/matras.result b/test/unit/matras.result
index 862dd816368ed437298f25c2c42179a8a8e4d806..cf05e6434aa0ffc246291803110d2c01cf5886b3 100644
--- a/test/unit/matras.result
+++ b/test/unit/matras.result
@@ -1,2 +1,4 @@
 Testing matras_alloc...
 Testing matras_alloc successfully finished
+Testing matras versions...
+Testing matras_version successfully finished