diff --git a/src/box/alter.cc b/src/box/alter.cc
index 49f315dde7226710b5c48b4b1b5f8d56818379e1..a1785d78e3f46cda41b996e24328c347d585a108 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -290,13 +290,6 @@ static void
 alter_space_commit(struct trigger *trigger, void * /* event */)
 {
 	struct alter_space *alter = (struct alter_space *) trigger->data;
-#if 0
-	/*
-	 * Clear the lock first - should there be an
-	 * exception/bug, the lock must not be left around.
-	 */
-	space_unset_on_replace(space, alter);
-#endif
 	/*
 	 * If an index is unchanged, all its properties, including
 	 * ID are intact. Move this index here. If an index is
@@ -444,18 +437,18 @@ alter_space_do(struct txn *txn, struct alter_space *alter,
 		op->alter_def(alter);
 	/*
 	 * Create a new (empty) space for the new definition.
-	 * Sic: the space engine is not the same yet, the
-	 * triggers are not set.
+	 * Sic: the triggers are not moved over yet.
 	 */
 	alter->new_space = space_new(&alter->space_def, &alter->key_list);
 	/*
-	 * Copy the engine, the new space is at the same recovery
-	 * phase as the old one. Do it before performing the alter,
-	 * since engine.recover does different things depending on
-	 * the recovery phase.
+	 * Copy the replace function, the new space is at the same recovery
+	 * phase as the old one. This hack is especially necessary for
+	 * system spaces, which may be altered in some row in the
+	 * snapshot/xlog, but needs to continue staying "fully
+	 * built".
 	 */
-	alter->new_space->handler->recovery =
-		alter->old_space->handler->recovery;
+	alter->new_space->handler->replace =
+		alter->old_space->handler->replace;
 
 	memcpy(alter->new_space->access, alter->old_space->access,
 	       sizeof(alter->old_space->access));
@@ -506,12 +499,9 @@ ModifySpace::prepare(struct alter_space *alter)
 			  space_name(alter->old_space),
 			  "can not change space engine");
 
-	engine_recovery *recovery =
-		&alter->old_space->handler->recovery;
-
 	if (def.field_count != 0 &&
 	    def.field_count != alter->old_space->def.field_count &&
-	    recovery->state != READY_NO_KEYS &&
+	    space_index(alter->old_space, 0) != NULL &&
 	    space_size(alter->old_space) > 0) {
 
 		tnt_raise(ClientError, ER_ALTER_SPACE,
@@ -526,7 +516,7 @@ ModifySpace::prepare(struct alter_space *alter)
 			  "space does not support temporary flag");
 	}
 	if (def.temporary != alter->old_space->def.temporary &&
-	    recovery->state != READY_NO_KEYS &&
+	    space_index(alter->old_space, 0) != NULL &&
 	    space_size(alter->old_space) > 0) {
 		tnt_raise(ClientError, ER_ALTER_SPACE,
 			  space_name(alter->old_space),
@@ -592,15 +582,15 @@ DropIndex::alter(struct alter_space *alter)
 			  space_name(alter->new_space));
 	}
 	/*
-	 * OK to drop the primary key. Put the space back to
-	 * 'READY_NO_KEYS' state, so that:
+	 * OK to drop the primary key. Inform the engine about it,
+	 * since it may have to reset handler->replace function,
+	 * so that:
 	 * - DML returns proper errors rather than crashes the
-	 *   server (thanks to engine_no_keys.replace),
-	 * - When a new primary key is finally added, the space
-	 *   can be put back online properly with
-	 *   engine_no_keys.recover.
+	 *   server
+	 * - when a new primary key is finally added, the space
+	 *   can be put back online properly.
 	 */
-	alter->new_space->handler->initRecovery();
+	alter->new_space->handler->engine->dropPrimaryKey(alter->new_space);
 }
 
 void
@@ -779,20 +769,14 @@ on_replace_in_old_space(struct trigger *trigger, void *event)
  * anyway, so there is no need to fully populate index with data,
  * it is done at the end of recovery.
  *
- * Note, that system  spaces are exception to this, since
+ * Note, that system spaces are exception to this, since
  * they are fully enabled at all times.
  */
 void
 AddIndex::alter(struct alter_space *alter)
 {
-	/*
-	 * READY_NO_KEYS is when a space has no functional keys.
-	 * Possible both during and after recovery.
-	 */
-	engine_recovery *recovery =
-		&alter->new_space->handler->recovery;
-
-	if (recovery->state == READY_NO_KEYS) {
+	Engine *engine = alter->new_space->handler->engine;
+	if (space_index(alter->old_space, 0) == NULL) {
 		if (new_key_def->iid == 0) {
 			/*
 			 * Adding a primary key: bring the space
@@ -804,46 +788,27 @@ AddIndex::alter(struct alter_space *alter)
 			 * key. After recovery, it means building
 			 * all keys.
 			 */
-			recovery->recover(alter->new_space);
+			engine->addPrimaryKey(alter->new_space);
 		} else {
 			/*
-			 * Adding a secondary key: nothing to do.
-			 * Before the end of recovery, nothing to do
-			 * because secondary keys are built in bulk later.
-			 * During normal operation, nothing to do
-			 * because without a primary key there is
-			 * no data in the space, and secondary
-			 * keys are built once the primary is
-			 * added.
-			 * TODO Consider prohibiting this branch
-			 * altogether.
+			 * Adding a secondary key.
 			 */
+			tnt_raise(ClientError, ER_ALTER_SPACE,
+				  space_name(alter->new_space),
+				  "can not add a secondary key before primary");
 		}
 		return;
 	}
+	/**
+	 * If it's a secondary key, and we're not building them
+	 * yet (i.e. it's snapshot recovery for memtx), do nothing.
+	 */
+	if (new_key_def->iid != 0 && !engine->needToBuildSecondaryKey(alter->new_space))
+		return;
+
 	Index *pk = index_find(alter->old_space, 0);
 	Index *new_index = index_find(alter->new_space, new_key_def->iid);
 
-	/* READY_PRIMARY_KEY is a state that only occurs during WAL recovery. */
-	if (recovery->state == READY_PRIMARY_KEY) {
-		if (new_key_def->iid == 0) {
-			/*
-			 * Bulk rebuild of the new primary key
-			 * from old primary key - it is safe to do
-			 * in bulk and without tuple-by-tuple
-			 * verification, since all tuples have
-			 * been verified when inserted, before
-			 * shutdown.
-			 */
-			index_build(new_index, pk);
-		} else {
-			/*
-			 * No need to build a secondary key during
-			 * WAL recovery.
-			 */
-		}
-		return;
-	}
 	/* Now deal with any kind of add index during normal operation. */
 	struct iterator *it = pk->position();
 	pk->initIterator(it, ITER_ALL, NULL, 0);
diff --git a/src/box/box.cc b/src/box/box.cc
index df588cb508938764d3b115a7f97f5f877b28d2b3..ae8ce453c647b5935d7a39ecf9d95d4d5f9dfcda 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -444,6 +444,7 @@ box_init(void)
 		engine_end_recover_snapshot();
 	} else if (recovery_has_remote(recovery)) {
 		/* Initialize a new replica */
+		engine_begin_join();
 		replica_bootstrap(recovery);
 		engine_end_recover_snapshot();
 		box_snapshot();
diff --git a/src/box/engine.cc b/src/box/engine.cc
index cd3d53fcbf5dafd9f08b7767bf82c9b77b2ce1c1..1930b2e8305c5d10b9de7199624a376f312c4d5a 100644
--- a/src/box/engine.cc
+++ b/src/box/engine.cc
@@ -56,11 +56,29 @@ void Engine::commit(struct txn*)
 void Engine::rollback(struct txn*)
 {}
 
+void Engine::initSystemSpace(struct space * /* space */)
+{
+	panic("not implemented");
+}
+
+void
+Engine::addPrimaryKey(struct space * /* space */)
+{
+}
+
+void
+Engine::dropPrimaryKey(struct space * /* space */)
+{
+}
+
+bool Engine::needToBuildSecondaryKey(struct space * /* space */)
+{
+	return true;
+}
+
 Handler::Handler(Engine *f)
 	:engine(f)
 {
-	/* derive recovery state from engine */
-	initRecovery();
 }
 
 /** Register engine instance. */
@@ -102,16 +120,13 @@ engine_begin_recover_snapshot(int64_t snapshot_lsn)
 	}
 }
 
-static void
-do_one_recover_step(struct space *space, void * /* param */)
+void
+engine_begin_join()
 {
-	if (space_index(space, 0)) {
-		space->handler->recover(space);
-	} else {
-		/* in case of space has no primary index,
-		 * derive it's engine handler recovery state from
-		 * the global one. */
-		space->handler->initRecovery();
+	/* recover engine snapshot */
+	Engine *engine;
+	engine_foreach(engine) {
+		engine->beginJoin();
 	}
 }
 
@@ -126,7 +141,6 @@ engine_end_recover_snapshot()
 	engine_foreach(engine) {
 		engine->end_recover_snapshot();
 	}
-	space_foreach(do_one_recover_step, NULL);
 }
 
 void
@@ -139,8 +153,6 @@ engine_end_recovery()
 	Engine *engine;
 	engine_foreach(engine)
 		engine->end_recovery();
-
-	space_foreach(do_one_recover_step, NULL);
 }
 
 int
@@ -179,3 +191,12 @@ engine_checkpoint(int64_t checkpoint_id)
 	snapshot_is_in_progress = false;
 	return save_errno;
 }
+
+void
+engine_join(Relay *relay)
+{
+	Engine *engine;
+	engine_foreach(engine) {
+		engine->join(relay);
+	}
+}
diff --git a/src/box/engine.h b/src/box/engine.h
index 0498e40e276f268267857e59d0e8cf26455c5a60..26f6a691e1375f1bfd2fa69f544d37dfb65dfbe8 100644
--- a/src/box/engine.h
+++ b/src/box/engine.h
@@ -32,6 +32,7 @@
 
 struct space;
 struct tuple;
+class Relay;
 
 enum engine_flags {
 	ENGINE_NO_YIELD = 1,
@@ -53,46 +54,10 @@ enum engine_flags {
 extern uint32_t engine_flags[BOX_ENGINE_MAX];
 extern struct rlist engines;
 
-/** Reflects what space_replace() is supposed to do. */
-enum engine_recovery_state {
-	/**
-	 * The space is created, but has no data
-	 * and no primary key, or, if there is a primary
-	 * key, it's not ready for use (being built with
-	 * buildNext()).
-	 * Replace is always an error, since there are no
-	 * indexes to add data to.
-	 */
-	READY_NO_KEYS,
-	/**
-	 * The space has a functional primary key.
-	 * Replace adds the tuple to this key.
-	 */
-	READY_PRIMARY_KEY,
-	/**
-	 * The space is fully functional, all keys
-	 * are fully built, replace adds its tuple
-	 * to all keys.
-	 */
-	READY_ALL_KEYS
-};
-
-typedef void (*engine_recover_f)(struct space*);
-
 typedef struct tuple *
 (*engine_replace_f)(struct space *, struct tuple *, struct tuple *,
                     enum dup_replace_mode);
 
-struct engine_recovery {
-	enum engine_recovery_state state;
-	/* Recover is called after each recover step to enable
-	 * keys. When recovery is complete, it enables all keys
-	 * at once and resets itself to a no-op.
-	 */
-	engine_recover_f recover;
-	engine_replace_f replace;
-};
-
 struct Handler;
 
 /** Engine instance */
@@ -104,6 +69,7 @@ class Engine: public Object {
 	virtual void init();
 	/** Create a new engine instance for a space. */
 	virtual Handler *open() = 0;
+	virtual void initSystemSpace(struct space *space);
 	/**
 	 * Check a key definition for violation of
 	 * various limits.
@@ -114,10 +80,31 @@ class Engine: public Object {
 	 * space.
 	 */
 	virtual Index *createIndex(struct key_def*) = 0;
+	/**
+	 * Called by alter when a primary key added,
+	 * after createIndex is invoked for the new
+	 * key.
+	 */
+	virtual void addPrimaryKey(struct space *space);
 	/**
 	 * Delete all tuples in the index on drop.
 	 */
-	virtual void dropIndex(Index*) = 0;
+	virtual void dropIndex(Index *) = 0;
+	/**
+	 * Called by alter when the primary key is dropped.
+	 * Do whatever is necessary with space/handler object,
+	 * e.g. disable handler replace function, if
+	 * necessary.
+	 */
+	virtual void dropPrimaryKey(struct space *space);
+	/**
+	 * An optimization for MemTX engine, which
+	 * builds all secondary keys after recovery from
+	 * a snapshot.
+	 */
+	virtual bool needToBuildSecondaryKey(struct space *space);
+
+	virtual void join(Relay *) = 0;
 	/**
 	 * Engine specific transaction life-cycle routines.
 	 */
@@ -133,6 +120,10 @@ class Engine: public Object {
 	 * state change (end of recovery from the binary log).
 	 */
 	virtual void end_recovery() = 0;
+	/**
+	 * Notify engine about a JOIN start (slave-side)
+	 */
+	virtual void beginJoin() = 0;
 	/**
 	 * Begin a two-phase snapshot creation in this
 	 * engine (snapshot is a memtx idea of a checkpoint).
@@ -159,7 +150,6 @@ class Engine: public Object {
 	uint32_t id;
 	/** Engine flags */
 	uint32_t flags;
-	struct engine_recovery recovery;
 	/** Used for search for engine by name. */
 	struct rlist link;
 };
@@ -171,23 +161,8 @@ struct Handler: public Object {
 	Handler(Engine *f);
 	virtual ~Handler() {}
 
-	inline struct tuple *
-	replace(struct space *space,
-	        struct tuple *old_tuple,
-	        struct tuple *new_tuple, enum dup_replace_mode mode)
-	{
-		return recovery.replace(space, old_tuple, new_tuple, mode);
-	}
-
-	inline void recover(struct space *space) {
-		recovery.recover(space);
-	}
-
-	inline void initRecovery() {
-		recovery = engine->recovery;
-	}
+	engine_replace_f replace;
 
-	engine_recovery recovery;
 	Engine *engine;
 };
 
@@ -234,6 +209,12 @@ engine_id(Handler *space)
 void
 engine_begin_recover_snapshot(int64_t snapshot_lsn);
 
+/**
+ * Called at the start of JOIN routine.
+ */
+void
+engine_begin_join();
+
 /**
  * Called at the end of recovery from snapshot.
  * Build primary keys in all spaces.
@@ -254,4 +235,10 @@ engine_end_recovery();
 int
 engine_checkpoint(int64_t checkpoint_id);
 
+/**
+ * Send a snapshot.
+ */
+void
+engine_join(Relay *);
+
 #endif /* TARANTOOL_BOX_ENGINE_H_INCLUDED */
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 8c27328020cdb89423aa144ec6bee1be5a757473..cc92cf7b75fbfe8de9d24636bd4a1fdb4ef12c24 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -360,10 +360,15 @@ lbox_commit(lua_State * /* L */)
 	 * Do nothing if transaction is not started,
 	 * it's the same as BEGIN + COMMIT.
 	*/
-	if (txn) {
+	if (! txn)
+		return 0;
+	try {
 		txn_commit(txn);
-		txn_finish(txn);
+	} catch (...) {
+		txn_rollback();
+		throw;
 	}
+	txn_finish(txn);
 	return 0;
 }
 
@@ -615,6 +620,13 @@ execute_call(lua_State *L, struct request *request, struct obuf *out)
 void
 box_lua_call(struct request *request, struct obuf *out)
 {
+	auto txn_guard = make_scoped_guard([=] {
+		struct txn *txn = in_txn();
+		if (txn) {
+			say_warn("transaction is active at CALL return");
+			txn_rollback();
+		}
+	});
 	lua_State *L = NULL;
 	try {
 		L = lua_newthread(tarantool_L);
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index be134f513847b37597850c49776af67349e09f96..55ee8e14fdada0b749ba62297ed5873f90e3231a 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -21,7 +21,8 @@ local default_sophia_cfg = {
     memory_limit = 0,
     threads      = 5,
     node_size    = 134217728,
-    page_size    = 131072
+    page_size    = 131072,
+    compression  = "none"
 }
 
 -- all available options
@@ -65,7 +66,8 @@ local sophia_template_cfg = {
     memory_limit = 'number',
     threads      = 'number',
     node_size    = 'number',
-    page_size    = 'number'
+    page_size    = 'number',
+    compression  = 'string'
 }
 
 -- types of available options
diff --git a/src/box/memtx_engine.cc b/src/box/memtx_engine.cc
index b64baafd6575abf3795d911028968bd82a5ee853..d0bdd0ec8425993b59b377d5338a82190d114254 100644
--- a/src/box/memtx_engine.cc
+++ b/src/box/memtx_engine.cc
@@ -27,6 +27,7 @@
  * SUCH DAMAGE.
  */
 #include "memtx_engine.h"
+#include "replication.h"
 #include "tuple.h"
 #include "txn.h"
 #include "index.h"
@@ -36,6 +37,7 @@
 #include "memtx_bitset.h"
 #include "space.h"
 #include "salad/rlist.h"
+#include "request.h"
 #include <stdlib.h>
 #include <string.h>
 #include "box.h"
@@ -55,11 +57,27 @@ static struct slab_arena memtx_index_arena;
 static struct slab_cache memtx_index_arena_slab_cache;
 static struct mempool memtx_index_extent_pool;
 
+/**
+ * A version of space_replace for a space which has
+ * no indexes (is not yet fully built).
+ */
+struct tuple *
+memtx_replace_no_keys(struct space *space, struct tuple * /* old_tuple */,
+		      struct tuple * /* new_tuple */,
+		      enum dup_replace_mode /* mode */)
+{
+       Index *index = index_find(space, 0);
+       assert(index == NULL); /* not reached. */
+       (void) index;
+       return NULL; /* replace found no old tuple */
+}
 
 struct MemtxSpace: public Handler {
 	MemtxSpace(Engine *e)
 		: Handler(e)
-	{ }
+	{
+		replace = memtx_replace_no_keys;
+	}
 	virtual ~MemtxSpace()
 	{
 		/* do nothing */
@@ -67,48 +85,151 @@ struct MemtxSpace: public Handler {
 	}
 };
 
+
 /**
- * This is a vtab with which a newly created space which has no
- * keys is primed.
- * At first it is set to correctly work for spaces created during
- * recovery from snapshot. In process of recovery it is updated as
- * below:
- *
- * 1) after SNAP is loaded:
- *    recover = space_build_primary_key
- * 2) when all XLOGs are loaded:
- *    recover = space_build_all_keys
-*/
+ * A short-cut version of space_replace() used during bulk load
+ * from snapshot.
+ */
+struct tuple *
+memtx_replace_build_next(struct space *space, struct tuple *old_tuple,
+			 struct tuple *new_tuple, enum dup_replace_mode mode)
+{
+	assert(old_tuple == NULL && mode == DUP_INSERT);
+	(void) mode;
+	if (old_tuple) {
+		/*
+		 * Called from txn_rollback() In practice
+		 * is impossible: all possible checks for tuple
+		 * validity are done before the space is changed,
+		 * and WAL is off, so this part can't fail.
+		 */
+		panic("Failed to commit transaction when loading "
+		      "from snapshot");
+	}
+	space->index[0]->buildNext(new_tuple);
+	return NULL; /* replace found no old tuple */
+}
 
-static inline void
-memtx_recovery_prepare(struct engine_recovery *r)
+/**
+ * A short-cut version of space_replace() used when loading
+ * data from XLOG files.
+ */
+struct tuple *
+memtx_replace_primary_key(struct space *space, struct tuple *old_tuple,
+			  struct tuple *new_tuple, enum dup_replace_mode mode)
 {
-	r->state   = READY_NO_KEYS;
-	r->recover = space_begin_build_primary_key;
-	r->replace = space_replace_no_keys;
+	return space->index[0]->replace(old_tuple, new_tuple, mode);
+}
+
+static struct tuple *
+memtx_replace_all_keys(struct space *space, struct tuple *old_tuple,
+		       struct tuple *new_tuple, enum dup_replace_mode mode)
+{
+	uint32_t i = 0;
+	try {
+		/* Update the primary key */
+		Index *pk = index_find(space, 0);
+		assert(pk->key_def->is_unique);
+		/*
+		 * If old_tuple is not NULL, the index
+		 * has to find and delete it, or raise an
+		 * error.
+		 */
+		old_tuple = pk->replace(old_tuple, new_tuple, mode);
+
+		assert(old_tuple || new_tuple);
+		/* Update secondary keys. */
+		for (i++; i < space->index_count; i++) {
+			Index *index = space->index[i];
+			index->replace(old_tuple, new_tuple, DUP_INSERT);
+		}
+		return old_tuple;
+	} catch (Exception *e) {
+		/* Rollback all changes */
+		for (; i > 0; i--) {
+			Index *index = space->index[i-1];
+			index->replace(new_tuple, old_tuple, DUP_INSERT);
+		}
+		throw;
+	}
+
+	assert(false);
+	return NULL;
+}
+
+static void
+memtx_end_build_primary_key(struct space *space, void *param)
+{
+	if (space->handler->engine != param || space_index(space, 0) == NULL ||
+	    space->handler->replace == memtx_replace_all_keys)
+		return;
+
+	space->index[0]->endBuild();
+	space->handler->replace = memtx_replace_primary_key;
+}
+
+/**
+ * Secondary indexes are built in bulk after all data is
+ * recovered. This function enables secondary keys on a space.
+ * Data dictionary spaces are an exception, they are fully
+ * built right from the start.
+ */
+void
+memtx_build_secondary_keys(struct space *space, void *param)
+{
+	if (space->handler->engine != param || space_index(space, 0) == NULL ||
+	    space->handler->replace == memtx_replace_all_keys)
+		return;
+
+	if (space->index_id_max > 0) {
+		Index *pk = space->index[0];
+		uint32_t n_tuples = pk->size();
+
+		if (n_tuples > 0) {
+			say_info("Building secondary indexes in space '%s'...",
+				 space_name(space));
+		}
+
+		for (uint32_t j = 1; j < space->index_count; j++)
+			index_build(space->index[j], pk);
+
+		if (n_tuples > 0) {
+			say_info("Space '%s': done", space_name(space));
+		}
+	}
+	space->handler->replace = memtx_replace_all_keys;
 }
 
 MemtxEngine::MemtxEngine()
 	:Engine("memtx"),
 	m_snapshot_lsn(-1),
-	m_snapshot_pid(0)
+	m_snapshot_pid(0),
+	m_state(MEMTX_INITIALIZED)
 {
 	flags = ENGINE_NO_YIELD |
 	        ENGINE_CAN_BE_TEMPORARY |
 		ENGINE_AUTO_CHECK_UPDATE;
-	memtx_recovery_prepare(&recovery);
+}
+
+/** Called at start to tell memtx to recover to a given LSN. */
+void
+MemtxEngine::begin_recover_snapshot(int64_t /* lsn */)
+{
+	m_state = MEMTX_READING_SNAPSHOT;
 }
 
 void
 MemtxEngine::end_recover_snapshot()
 {
-	recovery.recover = space_build_primary_key;
+	m_state = MEMTX_READING_WAL;
+	space_foreach(memtx_end_build_primary_key, this);
 }
 
 void
 MemtxEngine::end_recovery()
 {
-	recovery.recover = space_build_all_keys;
+	m_state = MEMTX_OK;
+	space_foreach(memtx_build_secondary_keys, this);
 }
 
 Handler *MemtxEngine::open()
@@ -116,6 +237,55 @@ Handler *MemtxEngine::open()
 	return new MemtxSpace(this);
 }
 
+
+static void
+memtx_add_primary_key(struct space *space, enum memtx_recovery_state state)
+{
+	switch (state) {
+	case MEMTX_INITIALIZED:
+		panic("can't create space");
+		break;
+	case MEMTX_READING_SNAPSHOT:
+		space->index[0]->beginBuild();
+		space->handler->replace = memtx_replace_build_next;
+		break;
+	case MEMTX_READING_WAL:
+		space->index[0]->beginBuild();
+		space->index[0]->endBuild();
+		space->handler->replace = memtx_replace_primary_key;
+		break;
+	case MEMTX_OK:
+		space->index[0]->beginBuild();
+		space->index[0]->endBuild();
+		space->handler->replace = memtx_replace_all_keys;
+		break;
+	}
+}
+
+void
+MemtxEngine::addPrimaryKey(struct space *space)
+{
+	memtx_add_primary_key(space, m_state);
+}
+
+void
+MemtxEngine::dropPrimaryKey(struct space *space)
+{
+	space->handler->replace = memtx_replace_no_keys;
+}
+
+void
+MemtxEngine::initSystemSpace(struct space *space)
+{
+	memtx_add_primary_key(space, MEMTX_OK);
+}
+
+bool
+MemtxEngine::needToBuildSecondaryKey(struct space *space)
+{
+	return space->handler->replace == memtx_replace_all_keys;
+}
+
 Index *
 MemtxEngine::createIndex(struct key_def *key_def)
 {
@@ -231,25 +401,12 @@ MemtxEngine::rollback(struct txn *txn)
 	}
 }
 
-/** Called at start to tell memtx to recover to a given LSN. */
 void
-MemtxEngine::begin_recover_snapshot(int64_t /* lsn */)
+MemtxEngine::beginJoin()
 {
-	/*
-	 * memtx snapshotting supported directly by box.
-	 * do nothing here.
-	 */
+	m_state = MEMTX_OK;
 }
 
-/** The snapshot row metadata repeats the structure of REPLACE request. */
-struct request_replace_body {
-	uint8_t m_body;
-	uint8_t k_space_id;
-	uint8_t m_space_id;
-	uint32_t v_space_id;
-	uint8_t k_tuple;
-} __attribute__((packed));
-
 static void
 snapshot_write_row(struct recovery_state *r, struct xlog *l,
 		   struct xrow_header *row)
@@ -494,6 +651,12 @@ MemtxEngine::abort_checkpoint()
 	m_snapshot_lsn = -1;
 }
 
+void
+MemtxEngine::join(Relay *relay)
+{
+	recover_snap(relay->r);
+}
+
 /**
  * Initialize arena for indexes.
  * The arena is used for memtx_index_extent_alloc
diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h
index 127633f52604b7b63edb6b7c16798211f2a876c6..ba2540eee7e5462b01587004f1f92a5c2117878a 100644
--- a/src/box/memtx_engine.h
+++ b/src/box/memtx_engine.h
@@ -30,26 +30,40 @@
  */
 #include "engine.h"
 
+enum memtx_recovery_state {
+	MEMTX_INITIALIZED,
+	MEMTX_READING_SNAPSHOT,
+	MEMTX_READING_WAL,
+	MEMTX_OK,
+};
+
 struct MemtxEngine: public Engine {
 	MemtxEngine();
 	virtual Handler *open();
 	virtual Index *createIndex(struct key_def *key_def);
+	virtual void addPrimaryKey(struct space *space);
 	virtual void dropIndex(Index *index);
+	virtual void dropPrimaryKey(struct space *space);
+	virtual bool needToBuildSecondaryKey(struct space *space);
 	virtual void keydefCheck(struct space *space, struct key_def *key_def);
 	virtual void rollback(struct txn*);
+	virtual void beginJoin();
 	virtual void begin_recover_snapshot(int64_t lsn);
 	virtual void end_recover_snapshot();
 	virtual void end_recovery();
+	virtual void join(Relay*);
 	virtual int begin_checkpoint(int64_t);
 	virtual int wait_checkpoint();
 	virtual void commit_checkpoint();
 	virtual void abort_checkpoint();
+	virtual void initSystemSpace(struct space *space);
 private:
 	/**
 	 * LSN of the snapshot which is in progress.
 	 */
 	int64_t m_snapshot_lsn;
 	pid_t m_snapshot_pid;
+	enum memtx_recovery_state m_state;
 };
 
 enum {
diff --git a/src/box/recovery.cc b/src/box/recovery.cc
index 2694ce5d156eb94e54cf2bb56ef1dd4e431db89c..a4b8d662ac29bbf2f192a37dc238ceb93666b695 100644
--- a/src/box/recovery.cc
+++ b/src/box/recovery.cc
@@ -181,6 +181,7 @@ recovery_new(const char *snap_dirname, const char *wal_dirname,
 	xdir_create(&r->wal_dir, wal_dirname, XLOG, &r->server_uuid);
 
 	vclock_create(&r->vclock);
+	vclock_create(&r->vclock_join);
 
 	xdir_scan(&r->snap_dir);
 	/**
@@ -375,7 +376,9 @@ recover_snap(struct recovery_state *r)
 
 	say_info("recovering from `%s'", snap->filename);
 	recover_xlog(r, snap);
+
 	/* Replace server vclock using the data from snapshot */
+	vclock_copy(&r->vclock_join, &r->vclock);
 	vclock_copy(&r->vclock, &snap->vclock);
 }
 
diff --git a/src/box/recovery.h b/src/box/recovery.h
index 40ee5ae9a91c6a6ff9d766cdf852369dea7aada0..ad4eabf3070cfef8a82ca0fe7d602462715cc34b 100644
--- a/src/box/recovery.h
+++ b/src/box/recovery.h
@@ -79,6 +79,7 @@ struct remote {
 };
 
 struct recovery_state {
+	struct vclock vclock_join;
 	struct vclock vclock;
 	/** The WAL we're currently reading/writing from/to. */
 	struct xlog *current_wal;
diff --git a/src/box/replication.cc b/src/box/replication.cc
index 0306e8fa42e03a7a4d3031e2713f8bec41edd6d3..aec788cb7d166af96b32014cca054ea11fa4d885 100644
--- a/src/box/replication.cc
+++ b/src/box/replication.cc
@@ -32,8 +32,8 @@
 
 #include "recovery.h"
 #include "xlog.h"
-#include "evio.h"
 #include "iproto_constants.h"
+#include "box/engine.h"
 #include "box/cluster.h"
 #include "box/schema.h"
 #include "box/vclock.h"
@@ -44,34 +44,24 @@
 #include "cfg.h"
 #include "trigger.h"
 
-static void
+void
 replication_send_row(struct recovery_state *r, void *param,
-		     struct xrow_header *packet);
-
-/** State of a replication relay. */
-class Relay {
-public:
-	/** Replica connection */
-	struct ev_io io;
-	/* Request sync */
-	uint64_t sync;
-	ev_tstamp wal_dir_rescan_delay;
-	struct recovery_state *r;
-
-	Relay(int fd_arg, uint64_t sync_arg)
-	{
-		r = recovery_new(cfg_gets("snap_dir"), cfg_gets("wal_dir"),
-				 replication_send_row, this);
-		coio_init(&io);
-		io.fd = fd_arg;
-		sync = sync_arg;
-		wal_dir_rescan_delay = cfg_getd("wal_dir_rescan_delay");
-	}
-	~Relay()
-	{
-		recovery_delete(r);
-	}
-};
+                     struct xrow_header *packet);
+
+Relay::Relay(int fd_arg, uint64_t sync_arg)
+{
+	r = recovery_new(cfg_gets("snap_dir"), cfg_gets("wal_dir"),
+			 replication_send_row, this);
+	coio_init(&io);
+	io.fd = fd_arg;
+	sync = sync_arg;
+	wal_dir_rescan_delay = cfg_getd("wal_dir_rescan_delay");
+}
+
+Relay::~Relay()
+{
+	recovery_delete(r);
+}
 
 static inline void
 relay_set_cord_name(int fd)
@@ -92,8 +82,9 @@ replication_join_f(va_list ap)
 	struct recovery_state *r = relay->r;
 
 	relay_set_cord_name(relay->io.fd);
+
 	/* Send snapshot */
-	recover_snap(r);
+	engine_join(relay);
 
 	/* Send response to JOIN command = end of stream */
 	struct xrow_header row;
@@ -195,10 +186,19 @@ replication_subscribe(int fd, struct xrow_header *packet)
 	cord_cojoin(&cord);
 }
 
+void
+relay_send(Relay *relay, struct xrow_header *packet)
+{
+	packet->sync = relay->sync;
+	struct iovec iov[XROW_IOVMAX];
+	int iovcnt = xrow_to_iovec(packet, iov);
+	coio_writev(&relay->io, iov, iovcnt, 0);
+}
+
 /** Send a single row to the client. */
-static void
+void
 replication_send_row(struct recovery_state *r, void *param,
-		     struct xrow_header *packet)
+                     struct xrow_header *packet)
 {
 	Relay *relay = (Relay *) param;
 	assert(iproto_type_is_dml(packet->type));
@@ -211,12 +211,8 @@ replication_send_row(struct recovery_state *r, void *param,
 	 * it not from the same server (i.e. don't send
 	 * replica's own rows back).
 	 */
-	if (packet->server_id == 0 || packet->server_id != r->server_id)  {
-		packet->sync = relay->sync;
-		struct iovec iov[XROW_IOVMAX];
-		int iovcnt = xrow_to_iovec(packet, iov);
-		coio_writev(&relay->io, iov, iovcnt, 0);
-	}
+	if (packet->server_id == 0 || packet->server_id != r->server_id)
+		relay_send(relay, packet);
 	/*
 	 * Update local vclock. During normal operation wal_write()
 	 * updates local vclock. In relay mode we have to update
diff --git a/src/box/replication.h b/src/box/replication.h
index d46fa04117b629e342eaaa978aad8b2f118bcf5f..d84c70c5443eccc3163cd50b89b97eea62b7875e 100644
--- a/src/box/replication.h
+++ b/src/box/replication.h
@@ -28,8 +28,23 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "evio.h"
+
 struct xrow_header;
 
+/** State of a replication relay. */
+class Relay {
+public:
+	/** Replica connection */
+	struct ev_io io;
+	/* Request sync */
+	uint64_t sync;
+	struct recovery_state *r;
+	ev_tstamp wal_dir_rescan_delay;
+	Relay(int fd_arg, uint64_t sync_arg);
+	~Relay();
+};
+
 void
 replication_join(int fd, struct xrow_header *packet);
 
@@ -41,5 +56,8 @@ replication_join(int fd, struct xrow_header *packet);
 void
 replication_subscribe(int fd, struct xrow_header *packet);
 
+void
+relay_send(Relay *relay, struct xrow_header *packet);
+
 #endif // TARANTOOL_REPLICATION_H_INCLUDED
 
diff --git a/src/box/request.h b/src/box/request.h
index b63f3dd74da04c797e85f2153dd55d09c1fcaab3..745f1e2ed8c03b958b6578eb646e35b766e2f633 100644
--- a/src/box/request.h
+++ b/src/box/request.h
@@ -62,6 +62,15 @@ struct request
 	int field_base;
 };
 
+/** The snapshot row metadata repeats the structure of REPLACE request. */
+struct request_replace_body {
+	uint8_t m_body;
+	uint8_t k_space_id;
+	uint8_t m_space_id;
+	uint32_t v_space_id;
+	uint8_t k_tuple;
+} __attribute__((packed));
+
 void
 request_create(struct request *request, uint32_t code);
 
diff --git a/src/box/schema.cc b/src/box/schema.cc
index e2db3c979fa131187dfa03c35358a25751142376..628093820515166a7728982db26c5d4c3bcc59e7 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -186,9 +186,7 @@ sc_space_new(struct space_def *space_def,
 	 *   ensures validation of tuples when starting from
 	 *   a snapshot of older version.
 	 */
-	space->handler->recover(space); /* load snapshot - begin */
-	space->handler->recover(space); /* load snapshot - end */
-	space->handler->recover(space); /* build secondary keys */
+	space->handler->engine->initSystemSpace(space);
 	return space;
 }
 
diff --git a/src/box/sophia_engine.cc b/src/box/sophia_engine.cc
index 4be24222f0d4cb950cd43cc6246af0b3b919914a..5f387d0918c52079f26ca5bccc13700158b3f983 100644
--- a/src/box/sophia_engine.cc
+++ b/src/box/sophia_engine.cc
@@ -26,6 +26,7 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "replication.h"
 #include "sophia_engine.h"
 #include "cfg.h"
 #include "xrow.h"
@@ -34,7 +35,11 @@
 #include "txn.h"
 #include "index.h"
 #include "sophia_index.h"
+#include "recovery.h"
 #include "space.h"
+#include "request.h"
+#include "iproto_constants.h"
+#include "replication.h"
 #include "salad/rlist.h"
 #include <sophia.h>
 #include <stdlib.h>
@@ -71,51 +76,33 @@ void sophia_info(void (*callback)(const char*, const char*, void*), void *arg)
 	sp_destroy(cur);
 }
 
+static struct tuple *
+sophia_replace(struct space *space, struct tuple *old_tuple,
+               struct tuple *new_tuple,
+               enum dup_replace_mode mode)
+{
+	Index *index = index_find(space, 0);
+	return index->replace(old_tuple, new_tuple, mode);
+}
+
 struct SophiaSpace: public Handler {
 	SophiaSpace(Engine*);
 };
 
 SophiaSpace::SophiaSpace(Engine *e)
 	:Handler(e)
-{ }
-
-static void
-sophia_recovery_end(struct space *space)
-{
-	engine_recovery *r = &space->handler->recovery;
-	r->state   = READY_ALL_KEYS;
-	r->replace = sophia_replace;
-	r->recover = space_noop;
-	/*
-	sophia_complete_recovery(space);
-	*/
-}
-
-static void
-sophia_recovery_end_snapshot(struct space *space)
-{
-	engine_recovery *r = &space->handler->recovery;
-	r->state   = READY_PRIMARY_KEY;
-	r->recover = sophia_recovery_end;
-}
-
-static void
-sophia_recovery_begin_snapshot(struct space *space)
 {
-	engine_recovery *r = &space->handler->recovery;
-	r->recover = sophia_recovery_end_snapshot;
+	replace = sophia_replace;
 }
 
 SophiaEngine::SophiaEngine()
 	:Engine("sophia")
 	 ,m_prev_checkpoint_lsn(-1)
 	 ,m_checkpoint_lsn(-1)
+	 ,recovery_complete(0)
 {
 	flags = 0;
 	env = NULL;
-	recovery.state   = READY_NO_KEYS;
-	recovery.recover = sophia_recovery_begin_snapshot;
-	recovery.replace = sophia_replace_recover;
 }
 
 void
@@ -145,24 +132,114 @@ SophiaEngine::open()
 	return new SophiaSpace(this);
 }
 
+static inline void
+sophia_snapshot_recover(void *env, int64_t lsn);
+
 void
 SophiaEngine::end_recover_snapshot()
 {
-	recovery.replace = sophia_replace_recover;
-	recovery.recover = sophia_recovery_end_snapshot;
+	/* create snapshot reference after tarantool
+	 * recovery, to ensure correct ref counting
+	 * with spaces involved in snapshot. */
+	if (m_checkpoint_lsn >= 0) {
+		sophia_snapshot_recover(env, m_checkpoint_lsn);
+		m_prev_checkpoint_lsn = m_checkpoint_lsn;
+		m_checkpoint_lsn = -1;
+	}
+}
+
+static inline void
+sophia_send_row(Relay *relay, uint32_t space_id, char *tuple,
+                uint32_t tuple_size)
+{
+	struct recovery_state *r = relay->r;
+	struct request_replace_body body;
+	body.m_body = 0x82; /* map of two elements. */
+	body.k_space_id = IPROTO_SPACE_ID;
+	body.m_space_id = 0xce; /* uint32 */
+	body.v_space_id = mp_bswap_u32(space_id);
+	body.k_tuple = IPROTO_TUPLE;
+	struct xrow_header row;
+	row.type = IPROTO_INSERT;
+	row.lsn = vclock_inc(&r->vclock_join, r->server_id);
+	row.server_id = 0;
+	row.bodycnt = 2;
+	row.body[0].iov_base = &body;
+	row.body[0].iov_len = sizeof(body);
+	row.body[1].iov_base = tuple;
+	row.body[1].iov_len = tuple_size;
+	relay_send(relay, &row);
 }
 
+/**
+ * Relay all data that should be present in the snapshot
+ * to the replica.
+ */
+void
+SophiaEngine::join(Relay *relay)
+{
+	struct vclock *res = vclockset_last(&relay->r->snap_dir.index);
+	if (res == NULL)
+		tnt_raise(ClientError, ER_MISSING_SNAPSHOT);
+	int64_t signt = vclock_signature(res);
+
+	/* get snapshot object */
+	char id[128];
+	snprintf(id, sizeof(id), "snapshot.%" PRIu64, signt);
+	void *c = sp_ctl(env);
+	void *snapshot = sp_get(c, id);
+	assert(snapshot != NULL);
+
+	/* iterate through a list of databases which took a
+	 * part in the snapshot */
+	void *db_cursor = sp_ctl(snapshot, "db_list");
+	if (db_cursor == NULL)
+		sophia_raise(env);
+	while (sp_get(db_cursor)) {
+		void *db = sp_object(db_cursor);
+
+		/* get space id */
+		void *dbctl = sp_ctl(db);
+		void *oid = sp_get(dbctl, "name");
+		char *name = (char*)sp_get(oid, "value", NULL);
+		char *pe = NULL;
+		uint32_t space_id = strtoul(name, &pe, 10);
+		sp_destroy(oid);
+
+		/* send database */
+		void *o = sp_object(db);
+		void *cursor = sp_cursor(snapshot, o);
+		if (cursor == NULL) {
+			sp_destroy(db_cursor);
+			sophia_raise(env);
+		}
+		while (sp_get(cursor)) {
+			o = sp_object(cursor);
+			uint32_t tuple_size = 0;
+			char *tuple = (char *)sp_get(o, "value", &tuple_size);
+			try {
+				sophia_send_row(relay, space_id, tuple, tuple_size);
+			} catch (...) {
+				sp_destroy(cursor);
+				sp_destroy(db_cursor);
+				throw;
+			}
+		}
+		sp_destroy(cursor);
+	}
+	sp_destroy(db_cursor);
+}
 
 void
 SophiaEngine::end_recovery()
 {
+	if (recovery_complete)
+		return;
 	/* complete two-phase recovery */
 	int rc = sp_open(env);
 	if (rc == -1)
 		sophia_raise(env);
-	recovery.state   = READY_NO_KEYS;
-	recovery.replace = sophia_replace;
-	recovery.recover = space_noop;
+	recovery_complete = 1;
 }
 
 Index*
@@ -279,8 +356,9 @@ SophiaEngine::commit(struct txn *txn)
 	if (rc == -1)
 		sophia_raise(env);
 	rc = sp_commit(txn->engine_tx);
-	if (rc == -1)
+	if (rc == -1) {
 		sophia_raise(env);
+	}
 	assert(rc == 0);
 }
 
@@ -293,6 +371,14 @@ SophiaEngine::rollback(struct txn *txn)
 	txn->engine_tx = NULL;
 }
 
+void
+SophiaEngine::beginJoin()
+{
+	/* put engine to recovery-complete state to
+	 * correctly support join */
+	end_recovery();
+}
+
 static inline void
 sophia_snapshot(void *env, int64_t lsn)
 {
@@ -301,7 +387,7 @@ sophia_snapshot(void *env, int64_t lsn)
 	int rc = sp_set(c, "scheduler.checkpoint");
 	if (rc == -1)
 		sophia_raise(env);
-	char snapshot[32];
+	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64, lsn);
 	/* ensure snapshot is not already exists */
 	void *o = sp_get(c, snapshot);
@@ -325,7 +411,7 @@ sophia_snapshot_recover(void *env, int64_t lsn)
 	int rc = sp_set(c, "snapshot", snapshot_lsn);
 	if (rc == -1)
 		sophia_raise(env);
-	char snapshot[32];
+	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64 ".lsn", lsn);
 	rc = sp_set(c, snapshot, snapshot_lsn);
 	if (rc == -1)
@@ -336,7 +422,7 @@ static inline int
 sophia_snapshot_ready(void *env, int64_t lsn)
 {
 	/* get sophia lsn associated with snapshot */
-	char snapshot[32];
+	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64 ".lsn", lsn);
 	void *c = sp_ctl(env);
 	void *o = sp_get(c, snapshot);
@@ -363,7 +449,7 @@ sophia_snapshot_ready(void *env, int64_t lsn)
 static inline void
 sophia_delete_checkpoint(void *env, int64_t lsn)
 {
-	char snapshot[32];
+	char snapshot[128];
 	snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64, lsn);
 	void *c = sp_ctl(env);
 	void *s = sp_get(c, snapshot);
@@ -380,7 +466,7 @@ sophia_delete_checkpoint(void *env, int64_t lsn)
 void
 SophiaEngine::begin_recover_snapshot(int64_t lsn)
 {
-	sophia_snapshot_recover(env, lsn);
+	m_checkpoint_lsn = lsn;
 }
 
 int
diff --git a/src/box/sophia_engine.h b/src/box/sophia_engine.h
index ca037d4e7a94c0389fbd51367343baaa63d97e94..f48abf228a1b03fa3a778af20ead6c53636fab6f 100644
--- a/src/box/sophia_engine.h
+++ b/src/box/sophia_engine.h
@@ -40,9 +40,11 @@ struct SophiaEngine: public Engine {
 	virtual void begin(struct txn*, struct space*);
 	virtual void commit(struct txn*);
 	virtual void rollback(struct txn*);
+	virtual void beginJoin();
 	virtual void begin_recover_snapshot(int64_t);
 	virtual void end_recover_snapshot();
 	virtual void end_recovery();
+	virtual void join(Relay*);
 	virtual int begin_checkpoint(int64_t);
 	virtual int wait_checkpoint();
 	virtual void commit_checkpoint();
@@ -51,6 +53,7 @@ struct SophiaEngine: public Engine {
 private:
 	int64_t m_prev_checkpoint_lsn;
 	int64_t m_checkpoint_lsn;
+	int recovery_complete;
 };
 
 void sophia_info(void (*)(const char*, const char*, void*), void*);
diff --git a/src/box/sophia_index.cc b/src/box/sophia_index.cc
index aec713247ae935e6956ea62bbb163174142f061d..e7b47577c10c105d0e5f24447167cdd7996f5ca3 100644
--- a/src/box/sophia_index.cc
+++ b/src/box/sophia_index.cc
@@ -78,38 +78,6 @@ sophia_index_get(void *env, void *db, void *tx, const char *key, size_t keysize,
 	return tuple_new(format, (char*)value, (char*)value + valuesize);
 }
 
-struct tuple*
-sophia_replace_recover(struct space *space,
-                       struct tuple *old_tuple, struct tuple *new_tuple,
-                       enum dup_replace_mode)
-{
-	SophiaIndex *index = (SophiaIndex*)index_find(space, 0);
-	struct txn *txn = in_txn();
-	assert(txn != NULL && txn->engine_tx != NULL);
-	int rc;
-	if (old_tuple) {
-		rc = sophia_index_stmt(txn->engine_tx, index->db, 1,
-		                       index->key_def,
-		                       old_tuple);
-	} else {
-		rc = sophia_index_stmt(txn->engine_tx, index->db, 0,
-		                       index->key_def,
-		                       new_tuple);
-	}
-	if (rc == -1)
-		sophia_raise(index->env);
-	return NULL;
-}
-
-struct tuple *
-sophia_replace(struct space *space,
-               struct tuple *old_tuple, struct tuple *new_tuple,
-               enum dup_replace_mode mode)
-{
-	Index *index = index_find(space, 0);
-	return index->replace(old_tuple, new_tuple, mode);
-}
-
 static inline int
 sophia_index_compare(char *a, size_t asz __attribute__((unused)),
                      char *b, size_t bsz __attribute__((unused)),
@@ -139,6 +107,8 @@ sophia_configure(struct space *space, struct key_def *key_def)
 	snprintf(pointer, sizeof(pointer), "pointer: %p", (void*)sophia_index_compare);
 	snprintf(pointer_arg, sizeof(pointer_arg), "pointer: %p", (void*)key_def);
 	sp_set(c, name, pointer, pointer_arg);
+	snprintf(name, sizeof(name), "db.%" PRIu32 ".compression", key_def->space_id);
+	sp_set(c, name, cfg_gets("sophia.compression"));
 	snprintf(name, sizeof(name), "db.%" PRIu32, key_def->space_id);
 	void *db = sp_get(c, name);
 	if (db == NULL)
@@ -166,16 +136,6 @@ SophiaIndex::SophiaIndex(struct key_def *key_def_arg __attribute__((unused)))
 	tuple_format_ref(space->format, 1);
 }
 
-void
-sophia_complete_recovery(struct space *space)
-{
-	SophiaIndex *index = (SophiaIndex*)index_find(space, 0);
-	assert(space->handler->recovery.recover == space_noop);
-	int rc = sp_open(index->db);
-	if (rc == -1)
-		sophia_raise(index->env);
-}
-
 SophiaIndex::~SophiaIndex()
 {
 	if (m_position != NULL) {
@@ -258,7 +218,8 @@ SophiaIndex::findByKey(const char *key, uint32_t part_count) const
 	mp_next(&keyptr);
 	size_t keysize = keyptr - key;
 	struct space *space = space_cache_find(key_def->space_id);
-	return sophia_index_get(env, db, NULL, key, keysize, space->format);
+	void *tx = in_txn() ? in_txn()->engine_tx : NULL;
+	return sophia_index_get(env, db, tx, key, keysize, space->format);
 }
 
 struct tuple *
diff --git a/src/box/sophia_index.h b/src/box/sophia_index.h
index f1d2752c6372d25ffcc831bd8f721a219f404573..8a9bacc0c08bc09bc958a47302e05bf003a1c5bd 100644
--- a/src/box/sophia_index.h
+++ b/src/box/sophia_index.h
@@ -52,17 +52,4 @@ class SophiaIndex: public Index {
 	void *db;
 };
 
-struct tuple *
-sophia_replace_recover(struct space*,
-                       struct tuple*, struct tuple*,
-                       enum dup_replace_mode);
-
-struct tuple *
-sophia_replace(struct space*,
-               struct tuple*, struct tuple*,
-               enum dup_replace_mode);
-
-void
-sophia_complete_recovery(struct space*);
-
 #endif /* TARANTOOL_BOX_SOPHIA_INDEX_H_INCLUDED */
diff --git a/src/box/space.cc b/src/box/space.cc
index ae197445b42d165a3b8f233d0fc430f03e21e4fd..85777d1eb394860e670bc675c04382f96dd52391 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -89,6 +89,7 @@ space_new(struct space_def *def, struct rlist *key_list)
 	rlist_create(&space->on_replace);
 	auto scoped_guard = make_scoped_guard([=]
 	{
+		/** Ensure space_delete deletes all indexes. */
 		space_fill_index_map(space);
 		space_delete(space);
 	});
@@ -127,182 +128,17 @@ space_delete(struct space *space)
 	free(space);
 }
 
-/**
- * A version of space_replace for a space which has
- * no indexes (is not yet fully built).
- */
-struct tuple *
-space_replace_no_keys(struct space *space, struct tuple * /* old_tuple */,
-			 struct tuple * /* new_tuple */,
-			 enum dup_replace_mode /* mode */)
-{
-	Index *index = index_find(space, 0);
-	assert(index == NULL); /* not reached. */
-	(void) index;
-	return NULL; /* replace found no old tuple */
-}
-
 /** Do nothing if the space is already recovered. */
 void
 space_noop(struct space * /* space */)
 {}
 
-/**
- * A short-cut version of space_replace() used during bulk load
- * from snapshot.
- */
-struct tuple *
-space_replace_build_next(struct space *space, struct tuple *old_tuple,
-			 struct tuple *new_tuple, enum dup_replace_mode mode)
-{
-	assert(old_tuple == NULL && mode == DUP_INSERT);
-	(void) mode;
-	if (old_tuple) {
-		/*
-		 * Called from txn_rollback() In practice
-		 * is impossible: all possible checks for tuple
-		 * validity are done before the space is changed,
-		 * and WAL is off, so this part can't fail.
-		 */
-		panic("Failed to commit transaction when loading "
-		      "from snapshot");
-	}
-	space->index[0]->buildNext(new_tuple);
-	return NULL; /* replace found no old tuple */
-}
-
-/**
- * A short-cut version of space_replace() used when loading
- * data from XLOG files.
- */
-struct tuple *
-space_replace_primary_key(struct space *space, struct tuple *old_tuple,
-			  struct tuple *new_tuple, enum dup_replace_mode mode)
-{
-	return space->index[0]->replace(old_tuple, new_tuple, mode);
-}
-
-static struct tuple *
-space_replace_all_keys(struct space *space, struct tuple *old_tuple,
-		       struct tuple *new_tuple, enum dup_replace_mode mode)
-{
-	uint32_t i = 0;
-	try {
-		/* Update the primary key */
-		Index *pk = space->index[0];
-		assert(pk->key_def->is_unique);
-		/*
-		 * If old_tuple is not NULL, the index
-		 * has to find and delete it, or raise an
-		 * error.
-		 */
-		old_tuple = pk->replace(old_tuple, new_tuple, mode);
-
-		assert(old_tuple || new_tuple);
-		/* Update secondary keys. */
-		for (i++; i < space->index_count; i++) {
-			Index *index = space->index[i];
-			index->replace(old_tuple, new_tuple, DUP_INSERT);
-		}
-		return old_tuple;
-	} catch (Exception *e) {
-		/* Rollback all changes */
-		for (; i > 0; i--) {
-			Index *index = space->index[i-1];
-			index->replace(new_tuple, old_tuple, DUP_INSERT);
-		}
-		throw;
-	}
-
-	assert(false);
-	return NULL;
-}
-
 uint32_t
 space_size(struct space *space)
 {
 	return space_index(space, 0)->size();
 }
 
-/**
- * Secondary indexes are built in bulk after all data is
- * recovered. This function enables secondary keys on a space.
- * Data dictionary spaces are an exception, they are fully
- * built right from the start.
- */
-void
-space_build_secondary_keys(struct space *space)
-{
-	if (space->index_id_max > 0) {
-		Index *pk = space->index[0];
-		uint32_t n_tuples = pk->size();
-
-		if (n_tuples > 0) {
-			say_info("Building secondary indexes in space '%s'...",
-				 space_name(space));
-		}
-
-		for (uint32_t j = 1; j < space->index_count; j++)
-			index_build(space->index[j], pk);
-
-		if (n_tuples > 0) {
-			say_info("Space '%s': done", space_name(space));
-		}
-	}
-	engine_recovery *r = &space->handler->recovery;
-	r->state   = READY_ALL_KEYS;
-	r->recover = space_noop; /* mark the end of recover */
-	r->replace = space_replace_all_keys;
-}
-
-/** Build the primary key after loading data from a snapshot. */
-void
-space_end_build_primary_key(struct space *space)
-{
-	space->index[0]->endBuild();
-	engine_recovery *r = &space->handler->recovery;
-	r->state   = READY_PRIMARY_KEY;
-	r->replace = space_replace_primary_key;
-	r->recover = space_build_secondary_keys;
-}
-
-/** Prepare the primary key for bulk load (loading from
- * a snapshot).
- */
-void
-space_begin_build_primary_key(struct space *space)
-{
-	space->index[0]->beginBuild();
-	engine_recovery *r = &space->handler->recovery;
-	r->replace = space_replace_build_next;
-	r->recover = space_end_build_primary_key;
-}
-
-/**
- * Bring a space up to speed if its primary key is added during
- * XLOG recovery. This is a recovery function called on
- * spaces which had no primary key at the end of snapshot
- * recovery, and got one only when reading an XLOG.
- */
-void
-space_build_primary_key(struct space *space)
-{
-	space_begin_build_primary_key(space);
-	space_end_build_primary_key(space);
-}
-
-/** Bring a space up to speed once it's got a primary key.
- *
- * This is a recovery function used for all spaces added after the
- * end of SNAP/XLOG recovery.
- */
-void
-space_build_all_keys(struct space *space)
-{
-	space_build_primary_key(space);
-	space_build_secondary_keys(space);
-}
-
 void
 space_validate_tuple(struct space *sp, struct tuple *new_tuple)
 {
diff --git a/src/box/space.h b/src/box/space.h
index 3d5190495c48ae22f939e5af1067badd5b51f16b..dd513be6259cacaaaf0d6f2b00f92a1b8be20fc4 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -195,16 +195,6 @@ space_replace(struct space *space, struct tuple *old_tuple,
 	return space->handler->replace(space, old_tuple, new_tuple, mode);
 }
 
-struct tuple *
-space_replace_no_keys(struct space*, struct tuple*, struct tuple*,
-                      enum dup_replace_mode);
-struct tuple *
-space_replace_primary_key(struct space*, struct tuple*, struct tuple*,
-                          enum dup_replace_mode);
-
-void space_begin_build_primary_key(struct space *space);
-void space_build_primary_key(struct space *space);
-void space_build_all_keys(struct space *space);
 void space_noop(struct space *space);
 
 uint32_t
diff --git a/src/box/txn.cc b/src/box/txn.cc
index 6d23a89b08c7c58f9264a65a6050b070dc5f4248..3d4ae07f2d6d4cf15cf84c081c172e5f9ebb9cb7 100644
--- a/src/box/txn.cc
+++ b/src/box/txn.cc
@@ -144,6 +144,7 @@ txn_begin(bool autocommit)
 		rlist_nil, txn_on_yield_or_stop, NULL, NULL
 	};
 	txn->autocommit = autocommit;
+	txn->engine_tx = NULL;
 	fiber_set_txn(fiber(), txn);
 	return txn;
 }
@@ -207,6 +208,14 @@ txn_commit(struct txn *txn)
 	struct txn_stmt *stmt;
 	/* if (!txn->autocommit && txn->n_stmts && engine_no_yield(txn->engine)) */
 
+	/* xxx: temporary workaround to handle transaction
+	 *      conflicts with sophia.
+	 */
+	if (txn->engine) {
+		txn->engine->commit(txn);
+		txn->engine_tx = NULL;
+	}
+
 	trigger_clear(&txn->fiber_on_yield);
 	trigger_clear(&txn->fiber_on_stop);
 
@@ -228,8 +237,10 @@ txn_commit(struct txn *txn)
 			tnt_raise(LoggedError, ER_WAL_IO);
 		txn->signature = res;
 	}
+	/*
 	if (txn->engine)
 		txn->engine->commit(txn);
+	*/
 	trigger_run(&txn->on_commit, txn); /* must not throw. */
 }
 
diff --git a/src/ffisyms.cc b/src/ffisyms.cc
index e7dafaf0056eef6e83101b94257385dd6a56eb78..b2917b1798336b13b4b11adefc4eef69d4aa14ca 100644
--- a/src/ffisyms.cc
+++ b/src/ffisyms.cc
@@ -1,3 +1,4 @@
+
 #include <bit/bit.h>
 #include <lib/msgpuck/msgpuck.h>
 #include "scramble.h"
diff --git a/test/box/admin.result b/test/box/admin.result
index 807cff8b1fdc2191de54ab3bfe0745a8b11243df..973347aa1bf609cbebd3b827727fad26fa23c4a6 100644
--- a/test/box/admin.result
+++ b/test/box/admin.result
@@ -26,9 +26,10 @@ box.cfg
   slab_alloc_arena: 0.1
   sophia:
     page_size: 131072
+    memory_limit: 0
     threads: 5
     node_size: 134217728
-    memory_limit: 0
+    compression: none
   listen: <uri>
   logger_nonblock: true
   snap_dir: .
diff --git a/test/box/cfg.result b/test/box/cfg.result
index 61e12fb73d441be2a8f7e3918be35412a7201ef0..fc615be3ed65375fb4c314c964cfb8460d8efa40 100644
--- a/test/box/cfg.result
+++ b/test/box/cfg.result
@@ -2,7 +2,7 @@
 --# push filter 'admin: .*' to 'admin: <uri>'
 box.cfg.nosuchoption = 1
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:260: Attempt to modify a read-only
+- error: '[string "-- load_cfg.lua - internal file..."]:262: Attempt to modify a read-only
     table'
 ...
 t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end
@@ -36,7 +36,7 @@ t
 -- must be read-only
 box.cfg()
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:206: bad argument #1 to ''pairs''
+- error: '[string "-- load_cfg.lua - internal file..."]:208: bad argument #1 to ''pairs''
     (table expected, got nil)'
 ...
 t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end
@@ -70,23 +70,23 @@ t
 -- check that cfg with unexpected parameter fails.
 box.cfg{sherlock = 'holmes'}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:162: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:164: Error: cfg parameter
     ''sherlock'' is unexpected'
 ...
 -- check that cfg with unexpected type of parameter failes
 box.cfg{listen = {}}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:182: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:184: Error: cfg parameter
     ''listen'' should be one of types: string, number'
 ...
 box.cfg{wal_dir = 0}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter
     ''wal_dir'' should be of type string'
 ...
 box.cfg{coredump = 'true'}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter
     ''coredump'' should be of type boolean'
 ...
 --------------------------------------------------------------------------------
@@ -94,17 +94,17 @@ box.cfg{coredump = 'true'}
 --------------------------------------------------------------------------------
 box.cfg{slab_alloc_arena = "100500"}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter
     ''slab_alloc_arena'' should be of type number'
 ...
 box.cfg{sophia = "sophia"}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:170: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:172: Error: cfg parameter
     ''sophia'' should be a table'
 ...
 box.cfg{sophia = {threads = "threads"}}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter
     ''sophia.threads'' should be of type number'
 ...
 --------------------------------------------------------------------------------
diff --git a/test/replication/sophia_join.result b/test/replication/sophia_join.result
new file mode 100644
index 0000000000000000000000000000000000000000..ece1d73df3c21f82774c65f606769df2cabd4b6f
--- /dev/null
+++ b/test/replication/sophia_join.result
@@ -0,0 +1,162 @@
+box.schema.user.grant('guest', 'replication')
+---
+...
+space = box.schema.space.create('test', { id = 99999, engine = "sophia" })
+---
+...
+index = space:create_index('primary', { type = 'tree'})
+---
+...
+for k = 1, 123 do space:insert{k, k*k} end
+---
+...
+box.snapshot()
+---
+- ok
+...
+-------------------------------------------------------------
+replica JOIN
+-------------------------------------------------------------
+box.space.test:select()
+---
+- - [1, 1]
+  - [2, 4]
+  - [3, 9]
+  - [4, 16]
+  - [5, 25]
+  - [6, 36]
+  - [7, 49]
+  - [8, 64]
+  - [9, 81]
+  - [10, 100]
+  - [11, 121]
+  - [12, 144]
+  - [13, 169]
+  - [14, 196]
+  - [15, 225]
+  - [16, 256]
+  - [17, 289]
+  - [18, 324]
+  - [19, 361]
+  - [20, 400]
+  - [21, 441]
+  - [22, 484]
+  - [23, 529]
+  - [24, 576]
+  - [25, 625]
+  - [26, 676]
+  - [27, 729]
+  - [28, 784]
+  - [29, 841]
+  - [30, 900]
+  - [31, 961]
+  - [32, 1024]
+  - [33, 1089]
+  - [34, 1156]
+  - [35, 1225]
+  - [36, 1296]
+  - [37, 1369]
+  - [38, 1444]
+  - [39, 1521]
+  - [40, 1600]
+  - [41, 1681]
+  - [42, 1764]
+  - [43, 1849]
+  - [44, 1936]
+  - [45, 2025]
+  - [46, 2116]
+  - [47, 2209]
+  - [48, 2304]
+  - [49, 2401]
+  - [50, 2500]
+  - [51, 2601]
+  - [52, 2704]
+  - [53, 2809]
+  - [54, 2916]
+  - [55, 3025]
+  - [56, 3136]
+  - [57, 3249]
+  - [58, 3364]
+  - [59, 3481]
+  - [60, 3600]
+  - [61, 3721]
+  - [62, 3844]
+  - [63, 3969]
+  - [64, 4096]
+  - [65, 4225]
+  - [66, 4356]
+  - [67, 4489]
+  - [68, 4624]
+  - [69, 4761]
+  - [70, 4900]
+  - [71, 5041]
+  - [72, 5184]
+  - [73, 5329]
+  - [74, 5476]
+  - [75, 5625]
+  - [76, 5776]
+  - [77, 5929]
+  - [78, 6084]
+  - [79, 6241]
+  - [80, 6400]
+  - [81, 6561]
+  - [82, 6724]
+  - [83, 6889]
+  - [84, 7056]
+  - [85, 7225]
+  - [86, 7396]
+  - [87, 7569]
+  - [88, 7744]
+  - [89, 7921]
+  - [90, 8100]
+  - [91, 8281]
+  - [92, 8464]
+  - [93, 8649]
+  - [94, 8836]
+  - [95, 9025]
+  - [96, 9216]
+  - [97, 9409]
+  - [98, 9604]
+  - [99, 9801]
+  - [100, 10000]
+  - [101, 10201]
+  - [102, 10404]
+  - [103, 10609]
+  - [104, 10816]
+  - [105, 11025]
+  - [106, 11236]
+  - [107, 11449]
+  - [108, 11664]
+  - [109, 11881]
+  - [110, 12100]
+  - [111, 12321]
+  - [112, 12544]
+  - [113, 12769]
+  - [114, 12996]
+  - [115, 13225]
+  - [116, 13456]
+  - [117, 13689]
+  - [118, 13924]
+  - [119, 14161]
+  - [120, 14400]
+  - [121, 14641]
+  - [122, 14884]
+  - [123, 15129]
+...
+space:drop()
+---
+...
+box.snapshot()
+---
+- ok
+...
+ffi = require('ffi')
+---
+...
+ffi.cdef("int sophia_schedule(void);")
+---
+...
+ffi.C.sophia_schedule() >= 0
+---
+- true
+...
diff --git a/test/replication/sophia_join.test.py b/test/replication/sophia_join.test.py
new file mode 100644
index 0000000000000000000000000000000000000000..945c8e397ae205305ecddf2ea1d89ebab3499cad
--- /dev/null
+++ b/test/replication/sophia_join.test.py
@@ -0,0 +1,37 @@
+import os
+import glob
+from lib.tarantool_server import TarantoolServer
+
+# master server
+master = server
+master_id = master.get_param('server')['id']
+
+master.admin("box.schema.user.grant('guest', 'replication')")
+master.admin("space = box.schema.space.create('test', { id = 99999, engine = \"sophia\" })")
+master.admin("index = space:create_index('primary', { type = 'tree'})")
+master.admin('for k = 1, 123 do space:insert{k, k*k} end')
+master.admin('box.snapshot()')
+lsn = master.get_lsn(master_id)
+
+print '-------------------------------------------------------------'
+print 'replica JOIN'
+print '-------------------------------------------------------------'
+
+# replica server
+replica = TarantoolServer(server.ini)
+replica.script = 'replication/replica.lua'
+replica.vardir = os.path.join(server.vardir, 'replica')
+replica.rpl_master = master
+replica.deploy()
+replica.wait_lsn(master_id, lsn)
+replica.admin('box.space.test:select()')
+
+replica.stop()
+replica.cleanup(True)
+
+# remove space
+master.admin("space:drop()")
+master.admin('box.snapshot()')
+master.admin("ffi = require('ffi')")
+master.admin("ffi.cdef(\"int sophia_schedule(void);\")")
+master.admin("ffi.C.sophia_schedule() >= 0")
diff --git a/test/sophia/crud.result b/test/sophia/crud.result
index 550a79d18992dd1303493b78461cc48a3741a87a..4d43a559a8922a8fd4ff75d61ba44e67fa0712d6 100644
--- a/test/sophia/crud.result
+++ b/test/sophia/crud.result
@@ -1171,7 +1171,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 sophia_dir()[1]
 ---
diff --git a/test/sophia/dml.result b/test/sophia/ddl.result
similarity index 98%
rename from test/sophia/dml.result
rename to test/sophia/ddl.result
index 345a84d22b2b1c33937a5e391c60911dc0d7d66b..3c1c14425b2b6bb4d429d13d43dc01ce0fc2e721 100644
--- a/test/sophia/dml.result
+++ b/test/sophia/ddl.result
@@ -11,7 +11,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 0
 ...
 sophia_dir()[1]
 ---
@@ -33,7 +32,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 sophia_dir()[1]
 ---
@@ -61,7 +59,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 sophia_dir()[1]
 ---
@@ -83,7 +80,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 sophia_dir()[1]
 ---
@@ -105,7 +101,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 sophia_dir()[1]
 ---
@@ -124,7 +119,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 0
 ...
 sophia_dir()[1]
 ---
@@ -147,7 +141,6 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 sophia_dir()[1]
 ---
@@ -188,5 +181,4 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
diff --git a/test/sophia/dml.test.lua b/test/sophia/ddl.test.lua
similarity index 100%
rename from test/sophia/dml.test.lua
rename to test/sophia/ddl.test.lua
diff --git a/test/sophia/gh.result b/test/sophia/gh.result
index c1933e0429adef4d27af6c2b7820e35a6c5cf835..cbe6fa5d15900340ae29338bb207f548c769cc54 100644
--- a/test/sophia/gh.result
+++ b/test/sophia/gh.result
@@ -14,7 +14,6 @@ s:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 s = box.schema.space.create('space0', {engine='sophia'})
 ---
@@ -38,7 +37,6 @@ s:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 s = box.schema.space.create('space0', {engine='sophia'})
 ---
@@ -62,7 +60,6 @@ s:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 -- gh-280: Sophia: crash if insert without index
 s = box.schema.space.create('test', {engine='sophia'})
@@ -77,7 +74,6 @@ s:drop()
 ...
 sophia_schedule()
 ---
-- 0
 ...
 -- gh-436: No error when creating temporary sophia space
 s = box.schema.space.create('tester',{engine='sophia', temporary=true})
@@ -114,7 +110,6 @@ s:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 s = box.schema.space.create('tester', {engine='sophia'})
 ---
@@ -144,7 +139,6 @@ s:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 -- gh-680: Sophia: assertion on update
 s = box.schema.space.create('tester', {engine='sophia'})
@@ -186,5 +180,4 @@ s:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
diff --git a/test/sophia/options.result b/test/sophia/options.result
index 75c23ee4a8d5f129793fca2178484888994edce6..4249fd3862eb027730828d2108c946b35d499774 100644
--- a/test/sophia/options.result
+++ b/test/sophia/options.result
@@ -1,9 +1,10 @@
 box.cfg.sophia
 ---
 - page_size: 131072
+  memory_limit: 0
   threads: 0
   node_size: 134217728
-  memory_limit: 0
+  compression: none
 ...
 box.cfg.sophia.threads = 3
 ---
diff --git a/test/sophia/random.result b/test/sophia/random.result
index 4d57fe8919eb99c46cad2032c2f1c1a8071e073f..56b896d871e1528ac386599c353ce860786ee534 100644
--- a/test/sophia/random.result
+++ b/test/sophia/random.result
@@ -17,5 +17,4 @@ space:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
diff --git a/test/sophia/snapshot.result b/test/sophia/snapshot.result
index 6b27014b729998fbf0388c4f4d2dbe53f37ee17b..06454b80527b1a59cf63047cbc009b9a130014ce 100644
--- a/test/sophia/snapshot.result
+++ b/test/sophia/snapshot.result
@@ -1,15 +1,23 @@
 -- snapshot
-space = box.schema.space.create('test', { id = 100, engine = 'sophia' })
+os.execute("rm -f *.snap")
 ---
+- 0
 ...
-index = space:create_index('primary')
+os.execute("rm -f *.xlog")
+---
+- 0
+...
+os.execute("touch mt")
+---
+- 0
+...
+--# stop server default
+--# start server default
+space = box.schema.space.create('test', { engine = 'sophia' })
 ---
 ...
-sophia_printdir()
+index = space:create_index('primary')
 ---
-- '100
-
-'
 ...
 for key = 1, 351 do space:insert({key}) end
 ---
@@ -18,6 +26,10 @@ box.snapshot()
 ---
 - ok
 ...
+os.execute("rm -f mt")
+---
+- 0
+...
 os.execute("touch lock")
 ---
 - 0
diff --git a/test/sophia/snapshot.test.lua b/test/sophia/snapshot.test.lua
index a213e2fb16698b816b05d9bd850b5170eb30c8c8..6b3f0743d72323834fb86b74a836767944468115 100644
--- a/test/sophia/snapshot.test.lua
+++ b/test/sophia/snapshot.test.lua
@@ -1,6 +1,8 @@
 
 -- snapshot
 
+os.execute("rm -f *.snap")
+os.execute("rm -f *.xlog")
 os.execute("touch mt")
 
 --# stop server default
diff --git a/test/sophia/snapshot_gc.result b/test/sophia/snapshot_gc.result
new file mode 100644
index 0000000000000000000000000000000000000000..6bd29f8710f6382d57e4ef881be8c10bda99f76a
--- /dev/null
+++ b/test/sophia/snapshot_gc.result
@@ -0,0 +1,110 @@
+os.execute("rm -f *.snap")
+---
+- 0
+...
+os.execute("rm -f *.xlog")
+---
+- 0
+...
+os.execute("touch mt")
+---
+- 0
+...
+--# stop server default
+--# start server default
+space = box.schema.create_space('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 1, 351 do space:insert({key}) end
+---
+...
+box.snapshot()
+---
+- ok
+...
+space:drop()
+---
+...
+sophia_schedule()
+---
+...
+sophia_dir()[1]
+---
+- 1
+...
+-- ensure that previous space has been garbage collected
+space = box.schema.create_space('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 1, 351 do space:insert({key}) end
+---
+...
+sophia_dir()[1] -- 2
+---
+- 2
+...
+box.snapshot()
+---
+- ok
+...
+space:drop()
+---
+...
+sophia_schedule()
+---
+...
+sophia_dir()[1] -- 1
+---
+- 1
+...
+space = box.schema.create_space('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 1, 351 do space:insert({key}) end
+---
+...
+sophia_dir()[1] -- 2
+---
+- 2
+...
+box.snapshot()
+---
+- ok
+...
+space:drop()
+---
+...
+sophia_schedule()
+---
+...
+sophia_dir()[1] -- 1
+---
+- 1
+...
+os.execute("rm -f *.snap")
+---
+- 0
+...
+os.execute("rm -f *.xlog")
+---
+- 0
+...
+os.execute("rm -f mt")
+---
+- 0
+...
+os.execute("rm -f lock")
+---
+- 0
+...
+--# stop server default
+--# start server default
diff --git a/test/sophia/snapshot_gc.test.lua b/test/sophia/snapshot_gc.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..52b372c32063a7efbe041433df996b9a6243412d
--- /dev/null
+++ b/test/sophia/snapshot_gc.test.lua
@@ -0,0 +1,43 @@
+
+os.execute("rm -f *.snap")
+os.execute("rm -f *.xlog")
+os.execute("touch mt")
+
+--# stop server default
+--# start server default
+
+space = box.schema.create_space('test', { engine = 'sophia' })
+index = space:create_index('primary')
+
+for key = 1, 351 do space:insert({key}) end
+box.snapshot()
+space:drop()
+sophia_schedule()
+sophia_dir()[1]
+
+-- ensure that previous space has been garbage collected
+space = box.schema.create_space('test', { engine = 'sophia' })
+index = space:create_index('primary')
+for key = 1, 351 do space:insert({key}) end
+sophia_dir()[1] -- 2
+box.snapshot()
+space:drop()
+sophia_schedule()
+sophia_dir()[1] -- 1
+
+space = box.schema.create_space('test', { engine = 'sophia' })
+index = space:create_index('primary')
+for key = 1, 351 do space:insert({key}) end
+sophia_dir()[1] -- 2
+box.snapshot()
+space:drop()
+sophia_schedule()
+sophia_dir()[1] -- 1
+
+os.execute("rm -f *.snap")
+os.execute("rm -f *.xlog")
+os.execute("rm -f mt")
+os.execute("rm -f lock")
+
+--# stop server default
+--# start server default
diff --git a/test/sophia/snapshot_view.result b/test/sophia/snapshot_view.result
new file mode 100644
index 0000000000000000000000000000000000000000..e52947f1a5e31d9060d283f59708acd998a51929
--- /dev/null
+++ b/test/sophia/snapshot_view.result
@@ -0,0 +1,83 @@
+os.execute("rm -f *.snap")
+---
+- 0
+...
+os.execute("rm -f *.xlog")
+---
+- 0
+...
+os.execute("touch mt")
+---
+- 0
+...
+--# stop server default
+--# start server default
+space = box.schema.create_space('test', { engine = 'sophia' })
+---
+...
+index = space:create_index('primary')
+---
+...
+for key = 1, 351 do space:insert({key}) end
+---
+...
+box.snapshot()
+---
+- ok
+...
+space:drop()
+---
+...
+sophia_schedule()
+---
+...
+-- remove tarantool xlogs
+os.execute("rm -f *.xlog")
+---
+- 0
+...
+os.execute("rm -f mt")
+---
+- 0
+...
+os.execute("touch lock")
+---
+- 0
+...
+--# stop server default
+--# start server default
+space = box.space['test']
+---
+...
+space:len()
+---
+- 351
+...
+sophia_dir()[1]
+---
+- 1
+...
+space:drop()
+---
+...
+sophia_schedule()
+---
+...
+sophia_dir()[1]
+---
+- 1
+...
+os.execute("rm -f *.snap")
+---
+- 0
+...
+os.execute("rm -f *.xlog")
+---
+- 0
+...
+os.execute("rm -f lock")
+---
+- 0
+...
+--# stop server default
+--# start server default
diff --git a/test/sophia/snapshot_view.test.lua b/test/sophia/snapshot_view.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f0d6ce5f5eec5d782541831f818d1bbd230745e5
--- /dev/null
+++ b/test/sophia/snapshot_view.test.lua
@@ -0,0 +1,38 @@
+
+os.execute("rm -f *.snap")
+os.execute("rm -f *.xlog")
+os.execute("touch mt")
+
+--# stop server default
+--# start server default
+
+space = box.schema.create_space('test', { engine = 'sophia' })
+index = space:create_index('primary')
+
+for key = 1, 351 do space:insert({key}) end
+box.snapshot()
+space:drop()
+sophia_schedule()
+
+-- remove tarantool xlogs
+os.execute("rm -f *.xlog")
+
+os.execute("rm -f mt")
+os.execute("touch lock")
+
+--# stop server default
+--# start server default
+
+space = box.space['test']
+space:len()
+sophia_dir()[1]
+space:drop()
+sophia_schedule()
+sophia_dir()[1]
+
+os.execute("rm -f *.snap")
+os.execute("rm -f *.xlog")
+os.execute("rm -f lock")
+
+--# stop server default
+--# start server default
diff --git a/test/sophia/suite.ini b/test/sophia/suite.ini
index 121dcab9d4bd5155925e5519c3f957da1afb4b17..368cc90bd4702f4394f53b70546c3861d752913e 100644
--- a/test/sophia/suite.ini
+++ b/test/sophia/suite.ini
@@ -2,7 +2,7 @@
 core = tarantool
 description = sophia integration tests
 script = box.lua
-disabled = info.test.lua truncate.test.lua snapshot.test.lua
+disabled = info.test.lua truncate.test.lua
 valgrind_disabled =
 release_disabled =
 lua_libs = suite.lua index_random_test.lua
diff --git a/test/sophia/suite.lua b/test/sophia/suite.lua
index 1138205d07eacfce8909641ef40acacec43eb13c..47bc3af6d1d4af73064fa1a67ba6f4d161a5a9cf 100644
--- a/test/sophia/suite.lua
+++ b/test/sophia/suite.lua
@@ -8,7 +8,7 @@ ffi.cdef[[
 ]]
 
 function sophia_schedule()
-	return ffi.C.sophia_schedule()
+	ffi.C.sophia_schedule()
 end
 
 function sophia_dir()
diff --git a/test/sophia/transaction_multidb.result b/test/sophia/transaction_multidb.result
index 8131471ec10a01cc0907f7d296dcc7a964438503..42cab9b94b551be306a2ed9d02570138dfee5e84 100644
--- a/test/sophia/transaction_multidb.result
+++ b/test/sophia/transaction_multidb.result
@@ -173,12 +173,10 @@ a:drop()
 ...
 sophia_schedule()
 ---
-- 1
 ...
 b:drop()
 ---
 ...
 sophia_schedule()
 ---
-- 1
 ...
diff --git a/third_party/sophia b/third_party/sophia
index ee54a7c08c2a87bf996c7a3f014b0d01dd4bb1e2..68ce375fb75f9194edf967d96d0ff5dc04f3724f 160000
--- a/third_party/sophia
+++ b/third_party/sophia
@@ -1 +1 @@
-Subproject commit ee54a7c08c2a87bf996c7a3f014b0d01dd4bb1e2
+Subproject commit 68ce375fb75f9194edf967d96d0ff5dc04f3724f