diff --git a/src/box/alter.cc b/src/box/alter.cc
index 774d5a9a25b5971dea84019be4e666e6e9ba4f2a..bca20b3000bd6301047a19b5a2709bcfc78b13bc 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2380,6 +2380,17 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 				  "the space has check constraints");
 			return -1;
 		}
+		/* Check whether old_space is used somewhere. */
+		enum space_cache_holder_type pinned_type;
+		if (space_cache_is_pinned(old_space, &pinned_type)) {
+			const char *type_str =
+				space_cache_holder_type_strs[pinned_type];
+			diag_set(ClientError, ER_DROP_SPACE,
+				 space_name(old_space),
+				 tt_sprintf("space is referenced by %s",
+					    type_str));
+			return -1;
+		}
 		/**
 		 * The space must be deleted from the space
 		 * cache right away to achieve linearisable
@@ -2653,6 +2664,20 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 				  "space sequence exists");
 			return -1;
 		}
+
+		/*
+		 * Must not truncate pinned space.
+		 */
+		enum space_cache_holder_type pinned_type;
+		if (space_cache_is_pinned(old_space, &pinned_type)) {
+			const char *type_str =
+				space_cache_holder_type_strs[pinned_type];
+			diag_set(ClientError, ER_ALTER_SPACE,
+				 space_name(old_space),
+				 tt_sprintf("space is referenced by %s",
+					    type_str));
+			return -1;
+		}
 	}
 
 	if (iid != 0 && space_index(old_space, 0) == NULL) {
diff --git a/src/box/space.c b/src/box/space.c
index 4d2c704a10306fb7fe6c99ae1150d5ecae18fcb7..04e0f03f5b45612d55cf3cade5ea5b172789c424 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -271,6 +271,7 @@ space_create(struct space *space, struct engine *engine,
 		}
 	}
 	space->constraint_ids = mh_strnptr_new();
+	rlist_create(&space->space_cache_pin_list);
 	rlist_create(&space->memtx_stories);
 	return 0;
 
@@ -350,6 +351,7 @@ space_delete(struct space *space)
 	assert(rlist_empty(&space->parent_fk_constraint));
 	assert(rlist_empty(&space->child_fk_constraint));
 	assert(rlist_empty(&space->ck_constraint));
+	assert(rlist_empty(&space->space_cache_pin_list));
 	space->vtab->destroy(space);
 }
 
diff --git a/src/box/space.h b/src/box/space.h
index 5d476d4ac5b343ce2beb819b87cbae285ff1d014..e25f5a0b386aa437ce09a4085d38fca12c316298 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -239,6 +239,8 @@ struct space {
 	 * Hash table with constraint identifiers hashed by name.
 	 */
 	struct mh_strnptr_t *constraint_ids;
+	/** List of space holders. This member is a property of space cache. */
+	struct rlist space_cache_pin_list;
 	/**
 	 * List of all tx stories in the space.
 	 */
diff --git a/src/box/space_cache.c b/src/box/space_cache.c
index ffa02051bb3fbdb324b0374249836353fb13bfe5..a4cd55c16abce4e58758380d96c93b781416bd62 100644
--- a/src/box/space_cache.c
+++ b/src/box/space_cache.c
@@ -21,6 +21,10 @@ static struct mh_strnptr_t *spaces_by_name;
  */
 uint32_t space_cache_version;
 
+const char *space_cache_holder_type_strs[SPACE_HOLDER_MAX] = {
+	"foreign key",
+};
+
 void
 space_cache_init(void)
 {
@@ -123,6 +127,29 @@ space_foreach(int (*func)(struct space *sp, void *udata), void *udata)
 	return 0;
 }
 
+/**
+ * If the @a old_space space is pinned, relink holders of that space to
+ * the @a new_space.
+ */
+static void
+space_cache_repin_pinned(struct space *old_space, struct space *new_space)
+{
+	assert(new_space != NULL);
+	assert(rlist_empty(&new_space->space_cache_pin_list));
+	if (old_space == NULL)
+		return;
+
+	rlist_swap(&new_space->space_cache_pin_list,
+		   &old_space->space_cache_pin_list);
+
+	struct space_cache_holder *h;
+	rlist_foreach_entry(h, &new_space->space_cache_pin_list, link) {
+		assert(h->space == old_space);
+		h->space = new_space;
+		h->on_replace(h, old_space);
+	}
+}
+
 void
 space_cache_replace(struct space *old_space, struct space *new_space)
 {
@@ -172,6 +199,9 @@ space_cache_replace(struct space *old_space, struct space *new_space)
 			old_space_by_name = (struct space *)p_old_s->val;
 		assert(old_space_by_name == old_space);
 		(void)old_space_by_name;
+
+		/* If old space is pinned, we have to pin the new space. */
+		space_cache_repin_pinned(old_space, new_space);
 	} else {
 		/*
 		 * Delete @old_space from @spaces cache.
@@ -194,6 +224,7 @@ space_cache_replace(struct space *old_space, struct space *new_space)
 		assert(old_space_by_name == old_space);
 		(void)old_space_by_name;
 		mh_strnptr_del(spaces_by_name, k, NULL);
+		assert(rlist_empty(&old_space->space_cache_pin_list));
 	}
 	space_cache_version++;
 
@@ -206,3 +237,53 @@ space_cache_replace(struct space *old_space, struct space *new_space)
 	if (old_space != NULL)
 		space_invalidate(old_space);
 }
+
+void
+space_cache_on_replace_noop(struct space_cache_holder *holder,
+			    struct space *old_space)
+{
+	(void)holder;
+	(void)old_space;
+}
+
+void
+space_cache_pin(struct space *space, struct space_cache_holder *holder,
+		space_cache_on_replace on_replace,
+		enum space_cache_holder_type type)
+{
+	assert(mh_i32ptr_find(spaces, space->def->id, NULL) != mh_end(spaces));
+	holder->on_replace = on_replace;
+	holder->type = type;
+	rlist_add_tail(&space->space_cache_pin_list, &holder->link);
+	holder->space = space;
+}
+
+void
+space_cache_unpin(struct space_cache_holder *holder)
+{
+	struct space *space = holder->space; (void)space;
+	assert(mh_i32ptr_find(spaces, space->def->id, NULL) != mh_end(spaces));
+#ifndef NDEBUG
+	/* Paranoid check that the holder in space's pin list. */
+	bool is_in_list = false;
+	struct rlist *tmp;
+	rlist_foreach(tmp, &space->space_cache_pin_list)
+		is_in_list = is_in_list || tmp == &holder->link;
+	assert(is_in_list);
+#endif
+	rlist_del(&holder->link);
+	holder->space = NULL;
+}
+
+bool
+space_cache_is_pinned(struct space *space, enum space_cache_holder_type *type)
+{
+	assert(mh_i32ptr_find(spaces, space->def->id, NULL) != mh_end(spaces));
+	if (rlist_empty(&space->space_cache_pin_list))
+		return false;
+	struct space_cache_holder *h =
+		rlist_first_entry(&space->space_cache_pin_list,
+				  struct space_cache_holder, link);
+	*type = h->type;
+	return true;
+}
diff --git a/src/box/space_cache.h b/src/box/space_cache.h
index 5d73b0ba7e0fa2bb1b84a7be94020c7235fa9577..5808fbb0519df0df3f52468c73e5d16450c9241c 100644
--- a/src/box/space_cache.h
+++ b/src/box/space_cache.h
@@ -27,6 +27,45 @@ extern uint32_t space_cache_version;
  */
 extern struct rlist on_alter_space;
 
+/**
+ * Type of a holder that can pin a space. @sa struct space_cache_holder.
+ */
+enum space_cache_holder_type {
+	SPACE_HOLDER_FOREIGN_KEY,
+	SPACE_HOLDER_MAX,
+};
+
+/**
+ * Lowercase name of each type.
+ */
+extern const char *space_cache_holder_type_strs[SPACE_HOLDER_MAX];
+
+struct space_cache_holder;
+typedef void
+(*space_cache_on_replace)(struct space_cache_holder *holder,
+			  struct space *old_space);
+
+/**
+ * Definition of a holder that pinned some space. Pinning of a space is
+ * a mechanism that is designed for preventing of deletion of some space from
+ * space cache by storing links to holders that prevented that. On the other
+ * hand it is allowed to replace a space with another - the new space becomes
+ * pinned after this point.
+ */
+struct space_cache_holder {
+	/** Holders of the same func are linked into ring list by this link. */
+	struct rlist link;
+	/** Actual pointer to space. */
+	struct space *space;
+	/** Callback that is called when the space is replaced in cache. */
+	space_cache_on_replace on_replace;
+	/**
+	 * Type of holder, mostly for better error generation, but also can be
+	 * used for proper container_of application.
+	 */
+	enum space_cache_holder_type type;
+};
+
 /**
  * Initialize space cache storage.
  */
@@ -108,6 +147,41 @@ space_foreach(int (*func)(struct space *sp, void *udata), void *udata);
 void
 space_cache_replace(struct space *old_space, struct space *new_space);
 
+/** No-op callback for space_cache_pin */
+void
+space_cache_on_replace_noop(struct space_cache_holder *holder,
+			    struct space *old_space);
+
+/**
+ * Register that there is a @a holder of type @a type that is dependent
+ * on @a space.
+ * The space must be in cache (asserted).
+ * If a space has holders, it must not be deleted (asserted). It can be
+ * replaced though, the holder will hold the new space in that case and
+ * @a on_replace callback is called.
+ */
+void
+space_cache_pin(struct space *space, struct space_cache_holder *holder,
+		space_cache_on_replace on_replace,
+		enum space_cache_holder_type type);
+
+/**
+ * Notify that a @a holder does not depend anymore on @a space.
+ * The space must be in cache (asserted).
+ * If a space has no holders, it can be deleted.
+ */
+void
+space_cache_unpin(struct space_cache_holder *holder);
+
+/**
+ * Check whether the @a space has holders or not.
+ * If it has, @a type argument is set to the first holder's type.
+ * The function must be in cache (asserted).
+ * If a space has holders, it must not be deleted (asserted).
+ */
+bool
+space_cache_is_pinned(struct space *space, enum space_cache_holder_type *type);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */