diff --git a/src/box/alter.cc b/src/box/alter.cc
index 9055eda77cc67496d4963d93fc24f76ac0d1450a..1364f5ea82b751fb70828935bd19f2044dc15266 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -79,6 +79,21 @@ access_check_ddl(uint32_t owner_uid, enum schema_object_type type)
 	}
 }
 
+/**
+ * Throw an exception if the given index definition
+ * is incompatible with a sequence.
+ */
+static void
+index_def_check_sequence(struct index_def *index_def, const char *space_name)
+{
+	enum field_type type = index_def->key_def->parts[0].type;
+	if (type != FIELD_TYPE_UNSIGNED && type != FIELD_TYPE_INTEGER) {
+		tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name,
+			  space_name, "sequence cannot be used with "
+			  "a non-integer key");
+	}
+}
+
 /**
  * Support function for index_def_new_from_tuple(..)
  * Checks tuple (of _index space) and throws a nice error if it is invalid
@@ -407,6 +422,8 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *old_space)
 	auto index_def_guard = make_scoped_guard([=] { index_def_delete(index_def); });
 	index_def_check_xc(index_def, space_name(old_space));
 	old_space->handler->checkIndexDef(old_space, index_def);
+	if (old_space->sequence != NULL)
+		index_def_check_sequence(index_def, space_name(old_space));
 	index_def_guard.is_active = false;
 	return index_def;
 }
@@ -910,9 +927,11 @@ alter_space_do(struct txn *txn, struct alter_space *alter)
 	alter->new_space->handler->prepareAlterSpace(alter->old_space,
 						     alter->new_space);
 
+	alter->new_space->sequence = alter->old_space->sequence;
 	alter->new_space->truncate_count = alter->old_space->truncate_count;
 	memcpy(alter->new_space->access, alter->old_space->access,
 	       sizeof(alter->old_space->access));
+
 	/*
 	 * Change the new space: build the new index, rename,
 	 * change the fixed field count.
@@ -1633,6 +1652,15 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			tnt_raise(ClientError, ER_DROP_PRIMARY_KEY,
 				  space_name(old_space));
 		}
+		/*
+		 * Can't drop primary key before space sequence.
+		 */
+		if (old_space->sequence != NULL) {
+			tnt_raise(ClientError, ER_ALTER_SPACE,
+				  space_name(old_space),
+				  "can not drop primary key while "
+				  "space sequence exists");
+		}
 	}
 
 	if (iid != 0 && space_index(old_space, 0) == NULL) {
@@ -1839,6 +1867,9 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
 	/* Preserve the access control lists during truncate. */
 	memcpy(new_space->access, old_space->access, sizeof(old_space->access));
 
+	/* Truncate does not affect space sequence. */
+	new_space->sequence = old_space->sequence;
+
 	/*
 	 * Replace the old space with the new one in the space
 	 * cache. Requests processed after this point will see
@@ -2690,6 +2721,9 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 		if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id))
 			tnt_raise(ClientError, ER_DROP_SEQUENCE,
 				  seq->def->name, "the sequence has data");
+		if (space_has_data(BOX_SPACE_SEQUENCE_ID, 1, id))
+			tnt_raise(ClientError, ER_DROP_SEQUENCE,
+				  seq->def->name, "the sequence is in use");
 		alter->old_def = seq->def;
 	} else {						/* UPDATE */
 		new_def = sequence_def_new_from_tuple(new_tuple,
@@ -2737,6 +2771,50 @@ on_replace_dd_sequence_data(struct trigger * /* trigger */, void *event)
 	}
 }
 
+/**
+ * Run the triggers registered on commit of a change in _space.
+ */
+static void
+on_commit_dd_space_sequence(struct trigger *trigger, void * /* event */)
+{
+	struct space *space = (struct space *) trigger->data;
+	trigger_run(&on_alter_space, space);
+}
+
+/**
+ * A trigger invoked on replace in space _space_sequence.
+ * Used to update space <-> sequence mapping.
+ */
+static void
+on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	txn_check_singlestatement(txn, "Space _space_sequence");
+	struct txn_stmt *stmt = txn_current_stmt(txn);
+	struct tuple *tuple = stmt->new_tuple ?: stmt->old_tuple;
+
+	uint32_t space_id = tuple_field_u32_xc(tuple,
+				BOX_SPACE_SEQUENCE_FIELD_ID);
+	uint32_t sequence_id = tuple_field_u32_xc(tuple,
+				BOX_SPACE_SEQUENCE_FIELD_SEQUENCE_ID);
+
+	struct space *space = space_cache_find(space_id);
+	struct sequence *seq = sequence_cache_find(sequence_id);
+
+	struct trigger *on_commit =
+		txn_alter_trigger_new(on_commit_dd_space_sequence, space);
+	txn_on_commit(txn, on_commit);
+
+	if (stmt->new_tuple != NULL) {			/* INSERT, UPDATE */
+		struct Index *pk = index_find(space, 0);
+		index_def_check_sequence(pk->index_def, space_name(space));
+		space->sequence = seq;
+	} else {					/* DELETE */
+		assert(space->sequence == seq);
+		space->sequence = NULL;
+	}
+}
+
 /* }}} sequence */
 
 static void
@@ -2803,6 +2881,10 @@ struct trigger on_replace_sequence_data = {
 	RLIST_LINK_INITIALIZER, on_replace_dd_sequence_data, NULL, NULL
 };
 
+struct trigger on_replace_space_sequence = {
+	RLIST_LINK_INITIALIZER, on_replace_dd_space_sequence, NULL, NULL
+};
+
 struct trigger on_stmt_begin_space = {
 	RLIST_LINK_INITIALIZER, lock_before_dd, NULL, NULL
 };
diff --git a/src/box/alter.h b/src/box/alter.h
index 4835a0c7c90ae50f3e03513c06a266d0df8f8eb1..423648793603e006bf98ce3a97f508f184893245 100644
--- a/src/box/alter.h
+++ b/src/box/alter.h
@@ -42,6 +42,7 @@ extern struct trigger on_replace_priv;
 extern struct trigger on_replace_cluster;
 extern struct trigger on_replace_sequence;
 extern struct trigger on_replace_sequence_data;
+extern struct trigger on_replace_space_sequence;
 extern struct trigger on_stmt_begin_space;
 extern struct trigger on_stmt_begin_index;
 extern struct trigger on_stmt_begin_truncate;
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 1977f0c8a3cda0165b0e67ae23f4e520907eefaf..a8098ed560e3aace1126cf15c73c36f80d4287ad 100644
Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ
diff --git a/src/box/box.cc b/src/box/box.cc
index e834d15ed3b9db1723450a5791a7cccc96ae15a3..cac0cea086b2138e322ba7205f0ecd4e8e0bf75d 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -170,6 +170,91 @@ request_rebind_to_primary_key(struct request *request, struct space *space,
 	request->header = NULL;
 }
 
+/**
+ * Handle INSERT/REPLACE in a space with a sequence attached.
+ */
+static void
+request_handle_sequence(struct request *request, struct space *space)
+{
+	struct sequence *seq = space->sequence;
+
+	assert(seq != NULL);
+	assert(request->type == IPROTO_INSERT ||
+	       request->type == IPROTO_REPLACE);
+
+	struct Index *pk = space_index(space, 0);
+	if (unlikely(pk == NULL))
+		return;
+
+	/*
+	 * Look up the first field of the primary key.
+	 */
+	const char *data = request->tuple;
+	const char *data_end = request->tuple_end;
+	int len = mp_decode_array(&data);
+	int fieldno = pk->index_def->key_def->parts[0].fieldno;
+	if (unlikely(len < fieldno + 1))
+		return;
+
+	const char *key = data;
+	if (unlikely(fieldno > 0)) {
+		do {
+			mp_next(&key);
+		} while (--fieldno > 0);
+	}
+
+	int64_t value;
+	if (mp_typeof(*key) == MP_NIL) {
+		/*
+		 * If the first field of the primary key is nil,
+		 * this is an auto increment request and we need
+		 * to replace the nil with the next value generated
+		 * by the space sequence.
+		 */
+		if (unlikely(sequence_next(seq, &value) != 0))
+			diag_raise();
+
+		const char *key_end = key;
+		mp_decode_nil(&key_end);
+
+		size_t buf_size = (request->tuple_end - request->tuple) +
+						mp_sizeof_uint(UINT64_MAX);
+		char *tuple = (char *) region_alloc_xc(&fiber()->gc, buf_size);
+		char *tuple_end = mp_encode_array(tuple, len);
+
+		if (unlikely(key != data)) {
+			memcpy(tuple_end, data, key - data);
+			tuple_end += key - data;
+		}
+
+		if (value >= 0)
+			tuple_end = mp_encode_uint(tuple_end, value);
+		else
+			tuple_end = mp_encode_int(tuple_end, value);
+
+		memcpy(tuple_end, key_end, data_end - key_end);
+		tuple_end += data_end - key_end;
+
+		assert(tuple_end <= tuple + buf_size);
+
+		request->tuple = tuple;
+		request->tuple_end = tuple_end;
+	} else {
+		/*
+		 * If the first field is not nil, update the space
+		 * sequence with its value, to make sure that an
+		 * auto increment request never tries to insert a
+		 * value that is already in the space. Note, this
+		 * code is also invoked on final recovery to restore
+		 * the sequence value from WAL.
+		 */
+		if (likely(mp_read_int64(&key, &value) == 0)) {
+			if (sequence_update(seq, value) != 0)
+				diag_raise();
+		}
+	}
+}
+
 static void
 process_rw(struct request *request, struct space *space, struct tuple **result)
 {
@@ -182,6 +267,8 @@ process_rw(struct request *request, struct space *space, struct tuple **result)
 		switch (request->type) {
 		case IPROTO_INSERT:
 		case IPROTO_REPLACE:
+			if (space->sequence != NULL)
+				request_handle_sequence(request, space);
 			tuple = space->handler->executeReplace(txn, space,
 							       request);
 
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 3bc9bb6ac3802a01ac328092cb449dc16e17d925..79f87449094cce41b5bc1eea3c7d681040f90929 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -397,6 +397,12 @@ box.schema.space.drop = function(space_id, space_name, opts)
     local _index = box.space[box.schema.INDEX_ID]
     local _priv = box.space[box.schema.PRIV_ID]
     local _truncate = box.space[box.schema.TRUNCATE_ID]
+    local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
+    local sequence_tuple = _space_sequence:delete{space_id}
+    if sequence_tuple ~= nil and sequence_tuple[3] == true then
+        -- Delete automatically generated sequence.
+        box.schema.sequence.drop(sequence_tuple[2])
+    end
     local keys = _index:select(space_id)
     for i = #keys, 1, -1 do
         local v = keys[i]
@@ -491,6 +497,7 @@ local alter_index_template = {
     name = 'string',
     type = 'string',
     parts = 'table',
+    sequence = 'boolean, number, string',
 }
 for k, v in pairs(index_options) do
     alter_index_template[k] = v
@@ -591,14 +598,49 @@ box.schema.index.create = function(space_id, name, options)
                      "please use '%s' instead", field_type, part[2])
         end
     end
+    local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
+    local sequence_is_generated = false
+    local sequence = options.sequence or nil -- ignore sequence = false
+    if sequence ~= nil then
+        if iid ~= 0 then
+            box.error(box.error.MODIFY_INDEX, name, box.space[space_id].name,
+                      "sequence cannot be used with a secondary key")
+        end
+        if #parts >= 1 and parts[1][2] ~= 'integer' and
+                           parts[1][2] ~= 'unsigned' then
+            box.error(box.error.MODIFY_INDEX, name, box.space[space_id].name,
+                      "sequence cannot be used with a non-integer key")
+        end
+        if sequence == true then
+            sequence = box.schema.sequence.create(
+                box.space[space_id].name .. '_seq')
+            sequence = sequence.id
+            sequence_is_generated = true
+        else
+            sequence = sequence_resolve(sequence)
+            if sequence == nil then
+                box.error(box.error.NO_SUCH_SEQUENCE, options.sequence)
+            end
+        end
+    end
     _index:insert{space_id, iid, name, options.type, index_opts, parts}
+    if sequence ~= nil then
+        _space_sequence:insert{space_id, sequence, sequence_is_generated}
+    end
     return box.space[space_id].index[name]
 end
 
 box.schema.index.drop = function(space_id, index_id)
     check_param(space_id, 'space_id', 'number')
     check_param(index_id, 'index_id', 'number')
-
+    if index_id == 0 then
+        local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
+        local sequence_tuple = _space_sequence:delete{space_id}
+        if sequence_tuple ~= nil and sequence_tuple[3] == true then
+            -- Delete automatically generated sequence.
+            box.schema.sequence.drop(sequence_tuple[2])
+        end
+    end
     local _index = box.space[box.schema.INDEX_ID]
     _index:delete{space_id, index_id}
 end
@@ -695,8 +737,52 @@ box.schema.index.alter = function(space_id, index_id, options)
             table.insert(parts, {options.parts[i], options.parts[i + 1]})
         end
     end
+    local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
+    local sequence_is_generated = false
+    local sequence = options.sequence
+    local sequence_tuple = _space_sequence:get(space_id)
+    if sequence or (sequence ~= false and sequence_tuple ~= nil) then
+        if index_id ~= 0 then
+            box.error(box.error.MODIFY_INDEX,
+                      options.name, box.space[space_id].name,
+                      "sequence cannot be used with a secondary key")
+        end
+        if #parts >= 1 and parts[1][2] ~= 'integer' and
+                           parts[1][2] ~= 'unsigned' then
+            box.error(box.error.MODIFY_INDEX,
+                      options.name, box.space[space_id].name,
+                      "sequence cannot be used with a non-integer key")
+        end
+    end
+    if sequence == true then
+        if sequence_tuple == nil or sequence_tuple[3] == false then
+            sequence = box.schema.sequence.create(
+                box.space[space_id].name .. '_seq')
+            sequence = sequence.id
+            sequence_is_generated = true
+        else
+            -- Space already has an automatically generated sequence.
+            sequence = nil
+        end
+    elseif sequence then
+        sequence = sequence_resolve(sequence)
+        if sequence == nil then
+            box.error(box.error.NO_SUCH_SEQUENCE, options.sequence)
+        end
+    end
+    if sequence == false then
+        _space_sequence:delete(space_id)
+    end
     _index:replace{space_id, index_id, options.name, options.type,
                    index_opts, parts}
+    if sequence then
+        _space_sequence:replace{space_id, sequence, sequence_is_generated}
+    end
+    if sequence_tuple ~= nil and sequence_tuple[3] == true and
+       sequence_tuple[2] ~= sequence then
+        -- Delete automatically generated sequence.
+        box.schema.sequence.drop(sequence_tuple[2])
+    end
 end
 
 -- a static box_tuple_t ** instance for calling box_index_* API
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index ecaa6bccb6dca010c3e2aac4aec85dea5e8db6f2..94a88c7e58e6a87d6fe1d91949a12593d9c155dd 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -45,6 +45,7 @@ extern "C" {
 #include "box/tuple.h"
 #include "box/txn.h"
 #include "box/vclock.h" /* VCLOCK_MAX */
+#include "box/sequence.h"
 
 /**
  * Trigger function for all spaces
@@ -205,6 +206,11 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 
 		lua_settable(L, -3); /* space.index[k].parts */
 
+		if (k == 0 && space->sequence != NULL) {
+			lua_pushnumber(L, space->sequence->def->id);
+			lua_setfield(L, -2, "sequence_id");
+		}
+
 		if (space_is_vinyl(space)) {
 			lua_pushstring(L, "options");
 			lua_newtable(L);
@@ -359,6 +365,8 @@ box_lua_space_init(struct lua_State *L)
 	lua_setfield(L, -2, "SEQUENCE_ID");
 	lua_pushnumber(L, BOX_SEQUENCE_DATA_ID);
 	lua_setfield(L, -2, "SEQUENCE_DATA_ID");
+	lua_pushnumber(L, BOX_SPACE_SEQUENCE_ID);
+	lua_setfield(L, -2, "SPACE_SEQUENCE_ID");
 	lua_pushnumber(L, BOX_SYSTEM_ID_MIN);
 	lua_setfield(L, -2, "SYSTEM_ID_MIN");
 	lua_pushnumber(L, BOX_SYSTEM_ID_MAX);
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index fb5d1dd06c0e0c704d9d4817a22797fec6333267..537e282c84291acbfe20a12356f49a2182425053 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -844,6 +844,7 @@ local function create_sequence_space()
     local _index = box.space[box.schema.INDEX_ID]
     local _sequence = box.space[box.schema.SEQUENCE_ID]
     local _sequence_data = box.space[box.schema.SEQUENCE_DATA_ID]
+    local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
     local MAP = setmap({})
 
     log.info("create space _sequence")
@@ -869,6 +870,16 @@ local function create_sequence_space()
                   {{name = 'id', type = 'unsigned'}, {name = 'value', type = 'integer'}}}
     log.info("create index primary on _sequence_data")
     _index:insert{_sequence_data.id, 0, 'primary', 'hash', {unique = true}, {{0, 'unsigned'}}}
+
+    log.info("create space _space_sequence")
+    _space:insert{_space_sequence.id, ADMIN, '_space_sequence', 'memtx', 0, MAP,
+                  {{name = 'id', type = 'unsigned'},
+                   {name = 'sequence_id', type = 'unsigned'},
+                   {name = 'is_generated', type = 'boolean'}}}
+    log.info("create index _space_sequence:primary")
+    _index:insert{_space_sequence.id, 0, 'primary', 'tree', {unique = true}, {{0, 'unsigned'}}}
+    log.info("create index _space_sequence:sequence")
+    _index:insert{_space_sequence.id, 1, 'sequence', 'tree', {unique = false}, {{1, 'unsigned'}}}
 end
 
 local function upgrade_to_1_7_6()
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 794a2cba066ee2facaae3daba9061ab0e3ce788c..225b7212002ffe0c11818f61979fdca72b0999f4 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -294,6 +294,10 @@ schema_init()
 	sc_space_new(BOX_SEQUENCE_DATA_ID, "_sequence_data", key_def,
 		     &on_replace_sequence_data, NULL);
 
+	/* _space_seq - association space <-> sequence. */
+	sc_space_new(BOX_SPACE_SEQUENCE_ID, "_space_sequence", key_def,
+		     &on_replace_space_sequence, NULL);
+
 	/* _user - all existing users */
 	sc_space_new(BOX_USER_ID, "_user", key_def, &on_replace_user, NULL);
 
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index 046dd5306fcb3e04b395ae7165c45ef9d7c3cf93..47a717328c9bb31d28e8355b4e1a7b441179fa61 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -96,6 +96,8 @@ enum {
 	BOX_CLUSTER_ID = 320,
 	/** Space id of _truncate. */
 	BOX_TRUNCATE_ID = 330,
+	/** Space id of _space_sequence. */
+	BOX_SPACE_SEQUENCE_ID = 340,
 	/** End of the reserved range of system spaces. */
 	BOX_SYSTEM_ID_MAX = 511,
 	BOX_ID_NIL = 2147483647
@@ -189,6 +191,13 @@ enum {
 	BOX_SEQUENCE_DATA_FIELD_VALUE = 1,
 };
 
+/** _space_seq fields. */
+enum {
+	BOX_SPACE_SEQUENCE_FIELD_ID = 0,
+	BOX_SPACE_SEQUENCE_FIELD_SEQUENCE_ID = 1,
+	BOX_SPACE_SEQUENCE_FIELD_IS_GENERATED = 2,
+};
+
 /*
  * Different objects which can be subject to access
  * control.
diff --git a/src/box/sequence.c b/src/box/sequence.c
index 93a938369409a187ebc4f1f4b9c174864218fd80..8da31823c96fea853f9a89eaaac629abeb5916da 100644
--- a/src/box/sequence.c
+++ b/src/box/sequence.c
@@ -121,6 +121,31 @@ sequence_set(struct sequence *seq, int64_t value)
 	return -1;
 }
 
+int
+sequence_update(struct sequence *seq, int64_t value)
+{
+	uint32_t key = seq->def->id;
+	uint32_t hash = sequence_hash(key);
+	uint32_t pos = light_sequence_find_key(&sequence_data_index, hash, key);
+	struct sequence_data new_data, data;
+	new_data.id = key;
+	new_data.value = value;
+	if (pos != light_sequence_end) {
+		data = light_sequence_get(&sequence_data_index, pos);
+		if ((seq->def->step > 0 && value > data.value) ||
+		    (seq->def->step < 0 && value < data.value)) {
+			if (light_sequence_replace(&sequence_data_index, hash,
+					new_data, &data) == light_sequence_end)
+				unreachable();
+		}
+	} else {
+		if (light_sequence_insert(&sequence_data_index, hash,
+					  new_data) == light_sequence_end)
+			return -1;
+	}
+	return 0;
+}
+
 int
 sequence_next(struct sequence *seq, int64_t *result)
 {
diff --git a/src/box/sequence.h b/src/box/sequence.h
index 08d452f010aa28161e0c0897c0c757b02b0a74e1..dcfcf528f7bce68809d211b83ad4f850df856493 100644
--- a/src/box/sequence.h
+++ b/src/box/sequence.h
@@ -141,6 +141,15 @@ sequence_reset(struct sequence *seq);
 int
 sequence_set(struct sequence *seq, int64_t value);
 
+/**
+ * Update the sequence if the given value is newer than
+ * the last generated value.
+ *
+ * Return 0 on success, -1 on memory allocation failure.
+ */
+int
+sequence_update(struct sequence *seq, int64_t value);
+
 /**
  * Advance a sequence.
  *
diff --git a/src/box/space.h b/src/box/space.h
index ca0a48727f9f9bfdae6b4a8a5cde663165266077..b09644787dc0352deed0e75c323bcb9cfe0721b5 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -41,6 +41,7 @@ extern "C" {
 
 struct Index;
 struct Handler;
+struct sequence;
 
 struct space {
 	struct access access[BOX_USER_MAX];
@@ -72,6 +73,8 @@ struct space {
 	uint32_t index_id_max;
 	/** Space meta. */
 	struct space_def *def;
+	/** Sequence attached to this space or NULL. */
+	struct sequence *sequence;
 	/**
 	 * Number of times the space has been truncated.
 	 * Updating this counter via _truncate space triggers
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index c1982bed663bf24f01575d5b636f5d707f89a4ea..38945ec5ba89cd678a243c164e4073ed1162786d 100755
--- a/test/app-tap/tarantoolctl.test.lua
+++ b/test/app-tap/tarantoolctl.test.lua
@@ -338,8 +338,8 @@ 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", 15)
-            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 36)
+            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 16)
+            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 38)
         end)
     end)
 
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index d6ef7d692b8d707060d06e254670c48d43779790..0994b084dbf5e31d4d3dea76479fb0c39eb159f1 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -57,6 +57,8 @@ box.space._space:select{}
         'type': 'string'}]]
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
         'type': 'unsigned'}]]
+  - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
+      {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]]
 ...
 box.space._index:select{}
 ---
@@ -98,6 +100,8 @@ box.space._index:select{}
   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [340, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]]
 ...
 box.space._user:select{}
 ---
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index a1080c5dae243499d8059cd70b11fbfdc871ada1..2c585b06558ae0469f491b208f7907ffbeddf80e 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -78,7 +78,7 @@ s:delete(1)
 ...
 s:drop()
 ---
-- error: Read access is denied for user 'testus' to space '_index'
+- error: Write access is denied for user 'testus' to space '_space_sequence'
 ...
 --
 -- Check double revoke
@@ -126,7 +126,7 @@ s:insert({3})
 ...
 s:drop()
 ---
-- error: Read access is denied for user 'testus' to space '_index'
+- error: Write access is denied for user 'testus' to space '_space_sequence'
 ...
 session.su('admin')
 ---
@@ -169,7 +169,7 @@ s:delete({3})
 ...
 s:drop()
 ---
-- error: Read access is denied for user 'guest' to space '_index'
+- error: Write access is denied for user 'guest' to space '_space_sequence'
 ...
 gs = box.schema.space.create('guest_space')
 ---
@@ -666,6 +666,8 @@ box.space._space:select()
         'type': 'string'}]]
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
         'type': 'unsigned'}]]
+  - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
+      {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]]
 ...
 box.space._func:select()
 ---
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index faf3f1a5dab828f197ca6e6d6aa854c2a872dd74..99ab3b954cada177450728806bc5002027166b6e 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -230,11 +230,11 @@ box.session.su('guest')
 ...
 #box.space._vspace:select{}
 ---
-- 16
+- 17
 ...
 #box.space._vindex:select{}
 ---
-- 37
+- 39
 ...
 #box.space._vuser:select{}
 ---
@@ -262,7 +262,7 @@ box.session.su('guest')
 ...
 #box.space._vindex:select{}
 ---
-- 37
+- 39
 ...
 #box.space._vuser:select{}
 ---
diff --git a/test/box/alter.result b/test/box/alter.result
index 3bf1c1a10dde0ea5fb7ab12871880ff76ea867ba..b46e703f4ea7f88101d64343479e1bb10d3004ab 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -107,7 +107,7 @@ space = box.space[t[1]]
 ...
 space.id
 ---
-- 331
+- 341
 ...
 space.field_count
 ---
@@ -152,7 +152,7 @@ space_deleted
 ...
 space:replace{0}
 ---
-- error: Space '331' does not exist
+- error: Space '341' does not exist
 ...
 _index:insert{_space.id, 0, 'primary', 'tree', 1, 1, 0, 'unsigned'}
 ---
@@ -210,6 +210,8 @@ _index:select{}
   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [340, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]]
 ...
 -- modify indexes of a system space
 _index:delete{_index.id, 0}
diff --git a/test/box/sequence.result b/test/box/sequence.result
index cd5dd471bd06d58b9e8cc1f8cead3a2cdb185c5b..3210c7667a15130b531e264cf47d1a5a99222c49 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -563,8 +563,562 @@ s:drop()
 ---
 ...
 --
+-- Attaching a sequence to a space.
+--
+-- Index create/modify checks.
+s = box.schema.space.create('test')
+---
+...
+sq = box.schema.sequence.create('test')
+---
+...
+sq:set(123)
+---
+...
+s:create_index('pk', {parts = {1, 'string'}, sequence = 'test'}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+s:create_index('pk', {parts = {1, 'scalar'}, sequence = 'test'}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+s:create_index('pk', {parts = {1, 'number'}, sequence = 'test'}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+pk = s:create_index('pk', {parts = {1, 'integer'}, sequence = 'test'}) -- ok
+---
+...
+pk:drop()
+---
+...
+pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok
+---
+...
+pk:drop()
+---
+...
+pk = s:create_index('pk') -- ok
+---
+...
+s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = 'test'}) -- error
+---
+- error: 'Can''t create or modify index ''secondary'' in space ''test'': sequence
+    cannot be used with a secondary key'
+...
+pk:drop()
+---
+...
+pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok
+---
+...
+pk:alter{parts = {1, 'string'}} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+box.space._index:update({s.id, pk.id}, {{'=', 6, {{0, 'string'}}}}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+box.space._index:delete{s.id, pk.id} -- error
+---
+- error: 'Can''t modify space ''test'': can not drop primary key while space sequence
+    exists'
+...
+pk:alter{parts = {1, 'string'}, sequence = false} -- ok
+---
+...
+sk = s:create_index('sk', {parts = {2, 'unsigned'}})
+---
+...
+sk:alter{sequence = 'test'} -- error
+---
+- error: 'Can''t create or modify index ''sk'' in space ''test'': sequence cannot
+    be used with a secondary key'
+...
+box.space._space_sequence:insert{s.id, sq.id, false} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+sk:drop()
+---
+...
+pk:drop()
+---
+...
+s:create_index('pk', {sequence = {}}) -- error
+---
+- error: 'Illegal parameters, options parameter ''sequence'' should be one of types:
+    boolean, number, string'
+...
+s:create_index('pk', {sequence = 'abc'}) -- error
+---
+- error: Sequence 'abc' does not exist
+...
+s:create_index('pk', {sequence = 12345}) -- error
+---
+- error: Sequence '12345' does not exist
+...
+pk = s:create_index('pk', {sequence = 'test'}) -- ok
+---
+...
+s.index.pk.sequence_id == sq.id
+---
+- true
+...
+pk:drop()
+---
+...
+pk = s:create_index('pk', {sequence = sq.id}) -- ok
+---
+...
+s.index.pk.sequence_id == sq.id
+---
+- true
+...
+pk:drop()
+---
+...
+pk = s:create_index('pk', {sequence = false}) -- ok
+---
+...
+s.index.pk.sequence_id == nil
+---
+- true
+...
+pk:alter{sequence = {}} -- error
+---
+- error: 'Illegal parameters, options parameter ''sequence'' should be one of types:
+    boolean, number, string'
+...
+pk:alter{sequence = 'abc'} -- error
+---
+- error: Sequence 'abc' does not exist
+...
+pk:alter{sequence = 12345} -- error
+---
+- error: Sequence '12345' does not exist
+...
+pk:alter{sequence = 'test'} -- ok
+---
+...
+s.index.pk.sequence_id == sq.id
+---
+- true
+...
+pk:alter{sequence = sq.id} -- ok
+---
+...
+s.index.pk.sequence_id == sq.id
+---
+- true
+...
+pk:alter{sequence = false} -- ok
+---
+...
+s.index.pk.sequence_id == nil
+---
+- true
+...
+pk:drop()
+---
+...
+sq:next() -- 124
+---
+- 124
+...
+sq:drop()
+---
+...
+s:drop()
+---
+...
+-- Using a sequence for auto increment.
+sq = box.schema.sequence.create('test')
+---
+...
+s1 = box.schema.space.create('test1')
+---
+...
+_ = s1:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'})
+---
+...
+s2 = box.schema.space.create('test2')
+---
+...
+_ = s2:create_index('pk', {parts = {2, 'integer'}, sequence = 'test'})
+---
+...
+s3 = box.schema.space.create('test3')
+---
+...
+_ = s3:create_index('pk', {parts = {2, 'unsigned', 1, 'string'}, sequence = 'test'})
+---
+...
+s1:insert(box.tuple.new(nil)) -- 1
+---
+- [1]
+...
+s2:insert(box.tuple.new('a', nil)) -- 2
+---
+- ['a', 2]
+...
+s3:insert(box.tuple.new('b', nil)) -- 3
+---
+- ['b', 3]
+...
+s1:truncate()
+---
+...
+s2:truncate()
+---
+...
+s3:truncate()
+---
+...
+s1:insert{nil, 123, 456} -- 4
+---
+- [4, 123, 456]
+...
+s2:insert{'c', nil, 123} -- 5
+---
+- ['c', 5, 123]
+...
+s3:insert{'d', nil, 456} -- 6
+---
+- ['d', 6, 456]
+...
+sq:next() -- 7
+---
+- 7
+...
+sq:reset()
+---
+...
+s1:insert{nil, nil, 'aa'} -- 1
+---
+- [1, null, 'aa']
+...
+s2:insert{'bb', nil, nil, 'cc'} -- 2
+---
+- ['bb', 2, null, 'cc']
+...
+s3:insert{'dd', nil, nil, 'ee'} -- 3
+---
+- ['dd', 3, null, 'ee']
+...
+sq:next() -- 4
+---
+- 4
+...
+sq:set(100)
+---
+...
+s1:insert{nil, 'aaa', 1} -- 101
+---
+- [101, 'aaa', 1]
+...
+s2:insert{'bbb', nil, 2} -- 102
+---
+- ['bbb', 102, 2]
+...
+s3:insert{'ccc', nil, 3} -- 103
+---
+- ['ccc', 103, 3]
+...
+sq:next() -- 104
+---
+- 104
+...
+s1:insert{1000, 'xxx'}
+---
+- [1000, 'xxx']
+...
+sq:next() -- 1001
+---
+- 1001
+...
+s2:insert{'yyy', 2000}
+---
+- ['yyy', 2000]
+...
+sq:next() -- 2001
+---
+- 2001
+...
+s3:insert{'zzz', 3000}
+---
+- ['zzz', 3000]
+...
+sq:next() -- 3001
+---
+- 3001
+...
+s1:insert{500, 'xxx'}
+---
+- [500, 'xxx']
+...
+s3:insert{'zzz', 2500}
+---
+- ['zzz', 2500]
+...
+s2:insert{'yyy', 1500}
+---
+- ['yyy', 1500]
+...
+sq:next() -- 3002
+---
+- 3002
+...
+sq:drop() -- error
+---
+- error: 'Can''t drop sequence ''test'': the sequence is in use'
+...
+s1:drop()
+---
+...
+sq:drop() -- error
+---
+- error: 'Can''t drop sequence ''test'': the sequence is in use'
+...
+s2:drop()
+---
+...
+sq:drop() -- error
+---
+- error: 'Can''t drop sequence ''test'': the sequence is in use'
+...
+s3:drop()
+---
+...
+sq:drop() -- ok
+---
+...
+-- Automatically generated sequences.
+s = box.schema.space.create('test')
+---
+...
+sq = box.schema.sequence.create('test')
+---
+...
+sq:set(123)
+---
+...
+pk = s:create_index('pk', {sequence = true})
+---
+...
+sq = box.sequence.test_seq
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- 1
+- 1
+- 9223372036854775807
+- 1
+- false
+...
+s.index.pk.sequence_id == sq.id
+---
+- true
+...
+s:insert{nil, 'a'} -- 1
+---
+- [1, 'a']
+...
+s:insert{nil, 'b'} -- 2
+---
+- [2, 'b']
+...
+s:insert{nil, 'c'} -- 3
+---
+- [3, 'c']
+...
+sq:next() -- 4
+---
+- 4
+...
+pk:alter{sequence = false}
+---
+...
+s.index.pk.sequence_id == nil
+---
+- true
+...
+s:insert{nil, 'x'} -- error
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected unsigned'
+...
+box.sequence.test_seq == nil
+---
+- true
+...
+pk:alter{sequence = true}
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- 1
+- 1
+- 9223372036854775807
+- 1
+- false
+...
+sq = box.sequence.test_seq
+---
+...
+s.index.pk.sequence_id == sq.id
+---
+- true
+...
+s:insert{100, 'abc'}
+---
+- [100, 'abc']
+...
+s:insert{nil, 'cda'} -- 101
+---
+- [101, 'cda']
+...
+sq:next() -- 102
+---
+- 102
+...
+pk:alter{sequence = 'test'}
+---
+...
+s.index.pk.sequence_id == box.sequence.test.id
+---
+- true
+...
+box.sequence.test_seq == nil
+---
+- true
+...
+pk:alter{sequence = true}
+---
+...
+s.index.pk.sequence_id == box.sequence.test_seq.id
+---
+- true
+...
+pk:drop()
+---
+...
+box.sequence.test_seq == nil
+---
+- true
+...
+pk = s:create_index('pk', {sequence = true})
+---
+...
+s.index.pk.sequence_id == box.sequence.test_seq.id
+---
+- true
+...
+s:drop()
+---
+...
+box.sequence.test_seq == nil
+---
+- true
+...
+sq = box.sequence.test
+---
+...
+sq:next() -- 124
+---
+- 124
+...
+sq:drop()
+---
+...
+-- Sequences are compatible with Vinyl spaces.
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+_ = s:create_index('pk', {sequence = true})
+---
+...
+s:insert{nil, 'a'} -- 1
+---
+- [1, 'a']
+...
+s:insert{100, 'b'} -- 100
+---
+- [100, 'b']
+...
+box.begin()
+---
+...
+s:insert{nil, 'c'} -- 101
+---
+- [101, 'c']
+...
+s:insert{nil, 'd'} -- 102
+---
+- [102, 'd']
+...
+box.rollback()
+---
+...
+box.begin()
+---
+...
+s:insert{nil, 'e'} -- 103
+---
+- [103, 'e']
+...
+s:insert{nil, 'f'} -- 104
+---
+- [104, 'f']
+...
+box.commit()
+---
+...
+s:select() -- {1, 'a'}, {100, 'b'}, {103, 'e'}, {104, 'f'}
+---
+- - [1, 'a']
+  - [100, 'b']
+  - [103, 'e']
+  - [104, 'f']
+...
+s:drop()
+---
+...
+--
 -- Check that sequences are persistent.
 --
+s1 = box.schema.space.create('test1')
+---
+...
+_ = s1:create_index('pk', {sequence = true})
+---
+...
+s1:insert{nil, 'a'} -- 1
+---
+- [1, 'a']
+...
+box.snapshot()
+---
+- ok
+...
+s2 = box.schema.space.create('test2')
+---
+...
+_ = s2:create_index('pk', {sequence = true})
+---
+...
+s2:insert{101, 'aaa'}
+---
+- [101, 'aaa']
+...
 sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true})
 ---
 ...
@@ -591,3 +1145,31 @@ sq:next()
 sq:drop()
 ---
 ...
+s1 = box.space.test1
+---
+...
+s1.index.pk.sequence_id == box.sequence.test1_seq.id
+---
+- true
+...
+s1:insert{nil, 'b'} -- 2
+---
+- [2, 'b']
+...
+s1:drop()
+---
+...
+s2 = box.space.test2
+---
+...
+s2.index.pk.sequence_id == box.sequence.test2_seq.id
+---
+- true
+...
+s2:insert{nil, 'bbb'} -- 102
+---
+- [102, 'bbb']
+...
+s2:drop()
+---
+...
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
index ef49bd0e5758555a1f6236c7d4ff62eaa8f7d6b5..1b4f2fbcf27dec6fa54f6118fe207940e61239d1 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -183,10 +183,192 @@ sq1:drop()
 sq2:drop()
 s:drop()
 
+--
+-- Attaching a sequence to a space.
+--
+
+-- Index create/modify checks.
+s = box.schema.space.create('test')
+sq = box.schema.sequence.create('test')
+sq:set(123)
+
+s:create_index('pk', {parts = {1, 'string'}, sequence = 'test'}) -- error
+s:create_index('pk', {parts = {1, 'scalar'}, sequence = 'test'}) -- error
+s:create_index('pk', {parts = {1, 'number'}, sequence = 'test'}) -- error
+
+pk = s:create_index('pk', {parts = {1, 'integer'}, sequence = 'test'}) -- ok
+pk:drop()
+pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok
+pk:drop()
+
+pk = s:create_index('pk') -- ok
+s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = 'test'}) -- error
+pk:drop()
+
+pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok
+pk:alter{parts = {1, 'string'}} -- error
+box.space._index:update({s.id, pk.id}, {{'=', 6, {{0, 'string'}}}}) -- error
+box.space._index:delete{s.id, pk.id} -- error
+pk:alter{parts = {1, 'string'}, sequence = false} -- ok
+sk = s:create_index('sk', {parts = {2, 'unsigned'}})
+sk:alter{sequence = 'test'} -- error
+box.space._space_sequence:insert{s.id, sq.id, false} -- error
+sk:drop()
+pk:drop()
+
+s:create_index('pk', {sequence = {}}) -- error
+s:create_index('pk', {sequence = 'abc'}) -- error
+s:create_index('pk', {sequence = 12345}) -- error
+pk = s:create_index('pk', {sequence = 'test'}) -- ok
+s.index.pk.sequence_id == sq.id
+pk:drop()
+pk = s:create_index('pk', {sequence = sq.id}) -- ok
+s.index.pk.sequence_id == sq.id
+pk:drop()
+pk = s:create_index('pk', {sequence = false}) -- ok
+s.index.pk.sequence_id == nil
+pk:alter{sequence = {}} -- error
+pk:alter{sequence = 'abc'} -- error
+pk:alter{sequence = 12345} -- error
+pk:alter{sequence = 'test'} -- ok
+s.index.pk.sequence_id == sq.id
+pk:alter{sequence = sq.id} -- ok
+s.index.pk.sequence_id == sq.id
+pk:alter{sequence = false} -- ok
+s.index.pk.sequence_id == nil
+pk:drop()
+
+sq:next() -- 124
+sq:drop()
+s:drop()
+
+-- Using a sequence for auto increment.
+sq = box.schema.sequence.create('test')
+s1 = box.schema.space.create('test1')
+_ = s1:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'})
+s2 = box.schema.space.create('test2')
+_ = s2:create_index('pk', {parts = {2, 'integer'}, sequence = 'test'})
+s3 = box.schema.space.create('test3')
+_ = s3:create_index('pk', {parts = {2, 'unsigned', 1, 'string'}, sequence = 'test'})
+
+s1:insert(box.tuple.new(nil)) -- 1
+s2:insert(box.tuple.new('a', nil)) -- 2
+s3:insert(box.tuple.new('b', nil)) -- 3
+s1:truncate()
+s2:truncate()
+s3:truncate()
+s1:insert{nil, 123, 456} -- 4
+s2:insert{'c', nil, 123} -- 5
+s3:insert{'d', nil, 456} -- 6
+sq:next() -- 7
+sq:reset()
+s1:insert{nil, nil, 'aa'} -- 1
+s2:insert{'bb', nil, nil, 'cc'} -- 2
+s3:insert{'dd', nil, nil, 'ee'} -- 3
+sq:next() -- 4
+sq:set(100)
+s1:insert{nil, 'aaa', 1} -- 101
+s2:insert{'bbb', nil, 2} -- 102
+s3:insert{'ccc', nil, 3} -- 103
+sq:next() -- 104
+s1:insert{1000, 'xxx'}
+sq:next() -- 1001
+s2:insert{'yyy', 2000}
+sq:next() -- 2001
+s3:insert{'zzz', 3000}
+sq:next() -- 3001
+s1:insert{500, 'xxx'}
+s3:insert{'zzz', 2500}
+s2:insert{'yyy', 1500}
+sq:next() -- 3002
+
+sq:drop() -- error
+s1:drop()
+sq:drop() -- error
+s2:drop()
+sq:drop() -- error
+s3:drop()
+sq:drop() -- ok
+
+-- Automatically generated sequences.
+s = box.schema.space.create('test')
+sq = box.schema.sequence.create('test')
+sq:set(123)
+
+pk = s:create_index('pk', {sequence = true})
+sq = box.sequence.test_seq
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+s.index.pk.sequence_id == sq.id
+s:insert{nil, 'a'} -- 1
+s:insert{nil, 'b'} -- 2
+s:insert{nil, 'c'} -- 3
+sq:next() -- 4
+
+pk:alter{sequence = false}
+s.index.pk.sequence_id == nil
+s:insert{nil, 'x'} -- error
+box.sequence.test_seq == nil
+
+pk:alter{sequence = true}
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq = box.sequence.test_seq
+s.index.pk.sequence_id == sq.id
+s:insert{100, 'abc'}
+s:insert{nil, 'cda'} -- 101
+sq:next() -- 102
+
+pk:alter{sequence = 'test'}
+s.index.pk.sequence_id == box.sequence.test.id
+box.sequence.test_seq == nil
+
+pk:alter{sequence = true}
+s.index.pk.sequence_id == box.sequence.test_seq.id
+pk:drop()
+box.sequence.test_seq == nil
+
+pk = s:create_index('pk', {sequence = true})
+s.index.pk.sequence_id == box.sequence.test_seq.id
+s:drop()
+box.sequence.test_seq == nil
+
+sq = box.sequence.test
+sq:next() -- 124
+sq:drop()
+
+-- Sequences are compatible with Vinyl spaces.
+s = box.schema.space.create('test', {engine = 'vinyl'})
+_ = s:create_index('pk', {sequence = true})
+
+s:insert{nil, 'a'} -- 1
+s:insert{100, 'b'} -- 100
+
+box.begin()
+s:insert{nil, 'c'} -- 101
+s:insert{nil, 'd'} -- 102
+box.rollback()
+
+box.begin()
+s:insert{nil, 'e'} -- 103
+s:insert{nil, 'f'} -- 104
+box.commit()
+
+s:select() -- {1, 'a'}, {100, 'b'}, {103, 'e'}, {104, 'f'}
+s:drop()
+
 --
 -- Check that sequences are persistent.
 --
 
+s1 = box.schema.space.create('test1')
+_ = s1:create_index('pk', {sequence = true})
+s1:insert{nil, 'a'} -- 1
+
+box.snapshot()
+
+s2 = box.schema.space.create('test2')
+_ = s2:create_index('pk', {sequence = true})
+s2:insert{101, 'aaa'}
+
 sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true})
 sq:next()
 
@@ -197,3 +379,13 @@ sq.step, sq.min, sq.max, sq.start, sq.cycle
 
 sq:next()
 sq:drop()
+
+s1 = box.space.test1
+s1.index.pk.sequence_id == box.sequence.test1_seq.id
+s1:insert{nil, 'b'} -- 2
+s1:drop()
+
+s2 = box.space.test2
+s2.index.pk.sequence_id == box.sequence.test2_seq.id
+s2:insert{nil, 'bbb'} -- 102
+s2:drop()
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index 96516f7b18dbc8fe9ee9a05551d4ad00413565e3..260eeb29aa19d60b763f2411eb54ca041ee3611f 100644
--- a/test/engine/iterator.result
+++ b/test/engine/iterator.result
@@ -4212,7 +4212,7 @@ s:replace{35}
 ...
 state, value = gen(param,state)
 ---
-- error: 'builtin/box/schema.lua:743: usage: next(param, state)'
+- error: 'builtin/box/schema.lua:829: usage: next(param, state)'
 ...
 value
 ---
diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result
index 7c5ae26f9089b184876bafc34e80a8f08e9d1f70..7ac001ec0bd3da6906dd99abf906b5d170f0b094 100644
--- a/test/wal_off/alter.result
+++ b/test/wal_off/alter.result
@@ -28,7 +28,7 @@ end;
 ...
 #spaces;
 ---
-- 65518
+- 65517
 ...
 -- cleanup
 for k, v in pairs(spaces) do
diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
index 8e366a1481d1349b031069cd9838c02b84fb4354..dd08961bc967963467b30a2c1131d7c22a160ace 100644
--- a/test/xlog/upgrade.result
+++ b/test/xlog/upgrade.result
@@ -84,6 +84,8 @@ box.space._space:select()
         'type': 'string'}]]
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
         'type': 'unsigned'}]]
+  - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
+      {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]]
   - [512, 1, 'distro', 'memtx', 0, {}, [{'name': 'os', 'type': 'str'}, {'name': 'dist',
         'type': 'str'}, {'name': 'version', 'type': 'num'}, {'name': 'time', 'type': 'num'}]]
   - [513, 1, 'temporary', 'memtx', 0, {'temporary': true}, []]
@@ -128,6 +130,8 @@ box.space._index:select()
   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [340, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [512, 0, 'primary', 'hash', {'unique': true}, [[0, 'string'], [1, 'string'], [
         2, 'unsigned']]]
   - [512, 1, 'codename', 'hash', {'unique': true}, [[1, 'string']]]