diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 44992b050e9d73d69608fb92782d1e965f6cdc51..641929b9ee883517c447f58a59201197a3320982 100644
Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 580e0ea2c0cedb2f12e7e8c666643305c91eb9cd..25b7e36da952d9717a93f1fd5b766a6d2df16b42 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -518,6 +518,8 @@ box_lua_space_init(struct lua_State *L)
 	lua_newtable(L);
 	lua_setfield(L, -2, "schema");
 	lua_getfield(L, -1, "schema");
+	lua_pushnumber(L, BOX_VINYL_DEFERRED_DELETE_ID);
+	lua_setfield(L, -2, "VINYL_DEFERRED_DELETE_ID");
 	lua_pushnumber(L, BOX_SCHEMA_ID);
 	lua_setfield(L, -2, "SCHEMA_ID");
 	lua_pushnumber(L, BOX_SPACE_ID);
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index ec3826399ab264bd94eb0d9046a773bf57dcc0d2..66de41fd35232277931b796ab1a522c8d27c1019 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -983,8 +983,23 @@ local function upgrade_priv_to_1_10_2()
     _vpriv.index.object:alter{parts={3, 'string', 4, 'scalar'}}
 end
 
+local function create_vinyl_deferred_delete_space()
+    local _space = box.space[box.schema.SPACE_ID]
+    local _vinyl_deferred_delete = box.space[box.schema.VINYL_DEFERRED_DELETE_ID]
+
+    local format = {}
+    format[1] = {name = 'space_id', type = 'unsigned'}
+    format[2] = {name = 'lsn', type = 'unsigned'}
+    format[3] = {name = 'tuple', type = 'array'}
+
+    log.info("create space _vinyl_deferred_delete")
+    _space:insert{_vinyl_deferred_delete.id, ADMIN, '_vinyl_deferred_delete',
+                  'blackhole', 0, {group_id = 1}, format}
+end
+
 local function upgrade_to_1_10_2()
     upgrade_priv_to_1_10_2()
+    create_vinyl_deferred_delete_space()
 end
 
 local function get_version()
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 4502ca6dcd4164099d1e4542aa933c7e7e38941a..7f20f36567dfc5247d5c4a022a86cb7bc85e96e6 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -37,7 +37,8 @@
 #include "scoped_guard.h"
 #include "version.h"
 #include "user.h"
-#include <stdio.h>
+#include "vclock.h"
+
 /**
  * @module Data Dictionary
  *
@@ -351,6 +352,39 @@ schema_init()
 			 FIELD_TYPE_UNSIGNED, false, NULL, COLL_NONE);
 	sc_space_new(BOX_INDEX_ID, "_index", key_def,
 		     &alter_space_on_replace_index, &on_stmt_begin_index);
+
+	/*
+	 * _vinyl_deferred_delete - blackhole that is needed
+	 * for writing deferred DELETE statements generated by
+	 * vinyl compaction tasks to WAL.
+	 *
+	 * There is an intricate ordering dependency between
+	 * recovery of this system space and initialization of
+	 * the vinyl engine, when we set an on_replace trigger
+	 * on the space. To resolve this dependency, we create
+	 * a space stub in schema_init(), then set a trigger in
+	 * engine_begin_initial_recovery(), which is called next,
+	 * then recover WAL rows, executing the trigger for each
+	 * of them.
+	 */
+	{
+		const char *engine = "blackhole";
+		const char *name = "_vinyl_deferred_delete";
+		struct space_opts opts = space_opts_default;
+		opts.group_id = GROUP_LOCAL;
+		struct space_def *def;
+		def = space_def_new_xc(BOX_VINYL_DEFERRED_DELETE_ID, ADMIN, 0,
+				       name, strlen(name), engine,
+				       strlen(engine), &opts, NULL, 0);
+		auto def_guard = make_scoped_guard([=] {
+			space_def_delete(def);
+		});
+		RLIST_HEAD(key_list);
+		struct space *space = space_new_xc(def, &key_list);
+		space_cache_replace(space);
+		init_system_space(space);
+		trigger_run_xc(&on_alter_space, space);
+	}
 }
 
 void
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index c0444cd11095135b05e03c57fa9eda15864589a2..df83d48d61af9a1280d98e7a8fe01da35fe24de3 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -66,6 +66,8 @@ static_assert(BOX_INVALID_NAME_MAX <= BOX_NAME_MAX,
 enum {
 	/** Start of the reserved range of system spaces. */
 	BOX_SYSTEM_ID_MIN = 256,
+	/** Space if of _vinyl_deferred_delete. */
+	BOX_VINYL_DEFERRED_DELETE_ID = 257,
 	/** Space id of _schema. */
 	BOX_SCHEMA_ID = 272,
 	/** Space id of _collation. */
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index fd14d1e744aeb8d3f3a0628e77b3dcce0c11098b..18aa1ba5b6188805e4bcea1b2d79aad0b977f485 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -65,6 +65,7 @@
 #include "engine.h"
 #include "space.h"
 #include "index.h"
+#include "schema.h"
 #include "xstream.h"
 #include "info.h"
 #include "column_mask.h"
@@ -256,6 +257,8 @@ static const struct engine_vtab vinyl_engine_vtab;
 static const struct space_vtab vinyl_space_vtab;
 static const struct index_vtab vinyl_index_vtab;
 
+static struct trigger on_replace_vinyl_deferred_delete;
+
 /**
  * A quick intro into Vinyl cosmology and file format
  * --------------------------------------------------
@@ -2771,6 +2774,20 @@ vinyl_engine_abort_checkpoint(struct engine *engine)
 
 /** {{{ Recovery */
 
+/**
+ * Install trigger on the _vinyl_deferred_delete system space.
+ * Called on bootstrap and recovery. Note, this function can't
+ * be called from engine constructor, because the latter is
+ * invoked before the schema is initialized.
+ */
+static void
+vy_set_deferred_delete_trigger(void)
+{
+	struct space *space = space_by_id(BOX_VINYL_DEFERRED_DELETE_ID);
+	assert(space != NULL);
+	trigger_add(&space->on_replace, &on_replace_vinyl_deferred_delete);
+}
+
 static int
 vinyl_engine_bootstrap(struct engine *engine)
 {
@@ -2780,6 +2797,7 @@ vinyl_engine_bootstrap(struct engine *engine)
 		return -1;
 	vy_quota_set_limit(&e->quota, e->memory);
 	e->status = VINYL_ONLINE;
+	vy_set_deferred_delete_trigger();
 	return 0;
 }
 
@@ -2802,6 +2820,7 @@ vinyl_engine_begin_initial_recovery(struct engine *engine,
 		vy_quota_set_limit(&e->quota, e->memory);
 		e->status = VINYL_INITIAL_RECOVERY_REMOTE;
 	}
+	vy_set_deferred_delete_trigger();
 	return 0;
 }
 
@@ -4265,6 +4284,21 @@ vinyl_space_build_index(struct space *src_space, struct index *new_index,
 
 /* }}} Index build */
 
+/* {{{ Deferred DELETE handling */
+
+static void
+vy_deferred_delete_on_replace(struct trigger *trigger, void *event)
+{
+	(void)trigger;
+	(void)event;
+}
+
+static struct trigger on_replace_vinyl_deferred_delete = {
+	RLIST_LINK_INITIALIZER, vy_deferred_delete_on_replace, NULL, NULL
+};
+
+/* }}} Deferred DELETE handling */
+
 static const struct engine_vtab vinyl_engine_vtab = {
 	/* .shutdown = */ vinyl_engine_shutdown,
 	/* .create_space = */ vinyl_engine_create_space,
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index 64f2aa7e717a09f9fc53f2cbe3dca293b7c3d53c..340232ace119f2ddf418abd47d91c68ffb2cee15 100755
--- a/test/app-tap/tarantoolctl.test.lua
+++ b/test/app-tap/tarantoolctl.test.lua
@@ -384,7 +384,7 @@ do
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0)
-            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 18)
+            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 19)
             check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 43)
         end)
     end)
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index cf8242de51751266189a74fc1cfeb393dff694f3..11d67acbb498418ccb9939e5e30b14086ddda0b7 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -13,7 +13,10 @@ box.space._cluster:select{}
 ...
 box.space._space:select{}
 ---
-- - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
+- - [257, 1, '_vinyl_deferred_delete', 'blackhole', 0, {'group_id': 1}, [{'name': 'space_id',
+        'type': 'unsigned'}, {'name': 'lsn', 'type': 'unsigned'}, {'name': 'tuple',
+        'type': 'array'}]]
+  - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
   - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {
         'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'},
       {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, {
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index c1809d69aa9cdcf1641544268062653031c99fdb..7c8abeccd20c80fec3ee83862199ae80437738cf 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -752,7 +752,10 @@ box.space._user:select()
 ...
 box.space._space:select()
 ---
-- - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
+- - [257, 1, '_vinyl_deferred_delete', 'blackhole', 0, {'group_id': 1}, [{'name': 'space_id',
+        'type': 'unsigned'}, {'name': 'lsn', 'type': 'unsigned'}, {'name': 'tuple',
+        'type': 'array'}]]
+  - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
   - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {
         'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'},
       {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, {
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index 20efd2bbce4aaff8c216296e11bc78093f7f03a1..bc5e069fea54991aa85faf607dfc29022d4af2f5 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -230,7 +230,7 @@ box.session.su('guest')
 ...
 #box.space._vspace:select{}
 ---
-- 19
+- 20
 ...
 #box.space._vindex:select{}
 ---
diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result
index afac1e55d41da8eccb7927492ca3c948c5af1ffb..f4703395c136be747b981d146d0a17a5976bceca 100644
--- a/test/wal_off/alter.result
+++ b/test/wal_off/alter.result
@@ -28,7 +28,7 @@ end;
 ...
 #spaces;
 ---
-- 65515
+- 65513
 ...
 -- cleanup
 for k, v in pairs(spaces) do
diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
index 76467baf15c45a03b533580321f008edc3d22cdc..073bab3f8afc3ec3c6a9b880cda12522a7736dfb 100644
--- a/test/xlog/upgrade.result
+++ b/test/xlog/upgrade.result
@@ -40,7 +40,10 @@ box.space._schema:select()
 ...
 box.space._space:select()
 ---
-- - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
+- - [257, 1, '_vinyl_deferred_delete', 'blackhole', 0, {'group_id': 1}, [{'name': 'space_id',
+        'type': 'unsigned'}, {'name': 'lsn', 'type': 'unsigned'}, {'name': 'tuple',
+        'type': 'array'}]]
+  - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
   - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {
         'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'},
       {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, {