diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 1b1d3a01305986af510c951b84d8e4682d5bb1d4..f043ef7772f950ce762283b4665c2d0205462043 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -82,6 +82,7 @@ add_library(box STATIC
     vy_read_set.c
     space.cc
     space_def.c
+    sequence.c
     func.c
     func_def.c
     alter.cc
@@ -113,6 +114,7 @@ add_library(box STATIC
     lua/slab.c
     lua/index.c
     lua/space.cc
+    lua/sequence.c
     lua/misc.cc
     lua/info.c
     lua/stat.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 0bae54aad88a2cbb4d36750718c882cbea3c6a33..dfb725cccabd8a2c485ce6c04ea44b8db5d636b3 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -49,6 +49,7 @@
 #include "iproto_constants.h"
 #include "memtx_tuple.h"
 #include "version.h"
+#include "sequence.h"
 
 /**
  * chap-sha1 of empty string, i.e.
@@ -1868,12 +1869,13 @@ bool
 user_has_data(struct user *user)
 {
 	uint32_t uid = user->def->uid;
-	uint32_t spaces[] = { BOX_SPACE_ID, BOX_FUNC_ID, BOX_PRIV_ID, BOX_PRIV_ID };
+	uint32_t spaces[] = { BOX_SPACE_ID, BOX_FUNC_ID, BOX_SEQUENCE_ID,
+			      BOX_PRIV_ID, BOX_PRIV_ID };
 	/*
 	 * owner index id #1 for _space and _func and _priv.
 	 * For _priv also check that the user has no grants.
 	 */
-	uint32_t indexes[] = { 1, 1, 1, 0 };
+	uint32_t indexes[] = { 1, 1, 1, 1, 0 };
 	uint32_t count = sizeof(spaces)/sizeof(*spaces);
 	for (uint32_t i = 0; i < count; i++) {
 		if (space_has_data(spaces[i], indexes[i], uid))
@@ -2562,6 +2564,175 @@ on_replace_dd_cluster(struct trigger *trigger, void *event)
 
 /* }}} cluster configuration */
 
+/* {{{ sequence */
+
+/** Create a sequence definition from a tuple. */
+static struct sequence_def *
+sequence_def_new_from_tuple(struct tuple *tuple, uint32_t errcode)
+{
+	uint32_t name_len;
+	const char *name = tuple_field_str_xc(tuple, BOX_USER_FIELD_NAME,
+					      &name_len);
+	if (name_len > BOX_NAME_MAX) {
+		tnt_raise(ClientError, errcode,
+			  tt_cstr(name, BOX_INVALID_NAME_MAX),
+			  "sequence name is too long");
+	}
+	size_t sz = sequence_def_sizeof(name_len);
+	struct sequence_def *def = (struct sequence_def *) malloc(sz);
+	if (def == NULL)
+		tnt_raise(OutOfMemory, sz, "malloc", "sequence");
+	auto def_guard = make_scoped_guard([=] { free(def); });
+	memcpy(def->name, name, name_len);
+	def->name[name_len] = '\0';
+	def->id = tuple_field_u32_xc(tuple, BOX_SEQUENCE_FIELD_ID);
+	def->uid = tuple_field_u32_xc(tuple, BOX_SEQUENCE_FIELD_UID);
+	def->step = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_STEP);
+	def->min = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_MIN);
+	def->max = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_MAX);
+	def->start = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_START);
+	def->cache = tuple_field_i64_xc(tuple, BOX_SEQUENCE_FIELD_CACHE);
+	def->cycle = tuple_field_bool_xc(tuple, BOX_SEQUENCE_FIELD_CYCLE);
+	if (def->step == 0)
+		tnt_raise(ClientError, errcode, def->name,
+			  "step option must be non-zero");
+	if (def->min > def->max)
+		tnt_raise(ClientError, errcode, def->name,
+			  "max must be greater than or equal to min");
+	if (def->start < def->min || def->start > def->max)
+		tnt_raise(ClientError, errcode, def->name,
+			  "start must be between min and max");
+	def_guard.is_active = false;
+	return def;
+}
+
+/** Argument passed to on_commit_dd_sequence() trigger. */
+struct alter_sequence {
+	/** Trigger invoked on commit in the _sequence space. */
+	struct trigger on_commit;
+	/** Trigger invoked on rollback in the _sequence space. */
+	struct trigger on_rollback;
+	/** Old sequence definition or NULL if create. */
+	struct sequence_def *old_def;
+	/** New sequence defitition or NULL if drop. */
+	struct sequence_def *new_def;
+};
+
+/**
+ * Trigger invoked on commit in the _sequence space.
+ */
+static void
+on_commit_dd_sequence(struct trigger *trigger, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	struct alter_sequence *alter = (struct alter_sequence *) trigger->data;
+
+	if (alter->new_def != NULL && alter->old_def != NULL) {
+		/* Alter a sequence. */
+		sequence_cache_replace(alter->new_def);
+	} else if (alter->new_def == NULL) {
+		/* Drop a sequence. */
+		sequence_cache_delete(alter->old_def->id);
+	}
+
+	trigger_run(&on_alter_sequence, txn_last_stmt(txn));
+}
+
+/**
+ * Trigger invoked on rollback in the _sequence space.
+ */
+static void
+on_rollback_dd_sequence(struct trigger *trigger, void * /* event */)
+{
+	struct alter_sequence *alter = (struct alter_sequence *) trigger->data;
+
+	if (alter->new_def != NULL && alter->old_def == NULL) {
+		/* Rollback creation of a sequence. */
+		sequence_cache_delete(alter->new_def->id);
+	}
+}
+
+/**
+ * A trigger invoked on replace in space _sequence.
+ * Used to alter a sequence definition.
+ */
+static void
+on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	txn_check_singlestatement(txn, "Space _sequence");
+	struct txn_stmt *stmt = txn_current_stmt(txn);
+	struct tuple *old_tuple = stmt->old_tuple;
+	struct tuple *new_tuple = stmt->new_tuple;
+
+	struct alter_sequence *alter =
+		region_calloc_object_xc(&fiber()->gc, struct alter_sequence);
+
+	struct sequence_def *new_def = NULL;
+	auto def_guard = make_scoped_guard([=] { free(new_def); });
+
+	if (old_tuple == NULL && new_tuple != NULL) {		/* INSERT */
+		new_def = sequence_def_new_from_tuple(new_tuple,
+						      ER_CREATE_SEQUENCE);
+		assert(sequence_by_id(new_def->id) == NULL);
+		sequence_cache_replace(new_def);
+		alter->new_def = new_def;
+	} else if (old_tuple != NULL && new_tuple == NULL) {	/* DELETE */
+		uint32_t id = tuple_field_u32_xc(old_tuple,
+						 BOX_SEQUENCE_DATA_FIELD_ID);
+		struct sequence *seq = sequence_by_id(id);
+		assert(seq != NULL);
+		if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id))
+			tnt_raise(ClientError, ER_DROP_SEQUENCE,
+				  seq->def->name, "the sequence has data");
+		alter->old_def = seq->def;
+	} else {						/* UPDATE */
+		new_def = sequence_def_new_from_tuple(new_tuple,
+						      ER_ALTER_SEQUENCE);
+		struct sequence *seq = sequence_by_id(new_def->id);
+		assert(seq != NULL);
+		alter->old_def = seq->def;
+		alter->new_def = new_def;
+	}
+
+	def_guard.is_active = false;
+
+	trigger_create(&alter->on_commit,
+		       on_commit_dd_sequence, alter, NULL);
+	txn_on_commit(txn, &alter->on_commit);
+	trigger_create(&alter->on_rollback,
+		       on_rollback_dd_sequence, alter, NULL);
+	txn_on_rollback(txn, &alter->on_rollback);
+}
+
+/**
+ * A trigger invoked on replace in space _sequence_data.
+ * Used to update a sequence value.
+ */
+static void
+on_replace_dd_sequence_data(struct trigger * /* trigger */, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	struct txn_stmt *stmt = txn_current_stmt(txn);
+	struct tuple *old_tuple = stmt->old_tuple;
+	struct tuple *new_tuple = stmt->new_tuple;
+
+	uint32_t id = tuple_field_u32_xc(old_tuple ?: new_tuple,
+					 BOX_SEQUENCE_DATA_FIELD_ID);
+	struct sequence *seq = sequence_cache_find(id);
+	if (seq == NULL)
+		diag_raise();
+	if (new_tuple != NULL) {			/* INSERT, UPDATE */
+		int64_t value = tuple_field_i64_xc(new_tuple,
+				BOX_SEQUENCE_DATA_FIELD_VALUE);
+		sequence_set(seq, value);
+	} else {					/* DELETE */
+		sequence_reset(seq);
+	}
+}
+
+/* }}} sequence */
+
 static void
 unlock_after_dd(struct trigger *trigger, void *event)
 {
@@ -2618,6 +2789,14 @@ struct trigger on_replace_cluster = {
 	RLIST_LINK_INITIALIZER, on_replace_dd_cluster, NULL, NULL
 };
 
+struct trigger on_replace_sequence = {
+	RLIST_LINK_INITIALIZER, on_replace_dd_sequence, NULL, NULL
+};
+
+struct trigger on_replace_sequence_data = {
+	RLIST_LINK_INITIALIZER, on_replace_dd_sequence_data, 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 be480763cd7aeb28ee5d2405e4b26ac1968dc2d7..4835a0c7c90ae50f3e03513c06a266d0df8f8eb1 100644
--- a/src/box/alter.h
+++ b/src/box/alter.h
@@ -40,6 +40,8 @@ extern struct trigger on_replace_user;
 extern struct trigger on_replace_func;
 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_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 8ddf41ab6fcccb66284d44e85888a18277fe150f..1977f0c8a3cda0165b0e67ae23f4e520907eefaf 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 0a416234727cfc251f25f0079ba3187c57c246f4..fb145d773a30e05e0ee742da753e4baf14c101e6 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -69,6 +69,7 @@
 #include "systemd.h"
 #include "call.h"
 #include "func.h"
+#include "sequence.h"
 
 static char status[64] = "unknown";
 
@@ -941,6 +942,89 @@ box_truncate(uint32_t space_id)
 	}
 }
 
+/** Update a record in _sequence_data space. */
+static int
+sequence_data_update(uint32_t seq_id, int64_t value)
+{
+	size_t tuple_buf_size = (mp_sizeof_array(2) +
+				 2 * mp_sizeof_uint(UINT64_MAX));
+	char *tuple_buf = (char *) region_alloc(&fiber()->gc, tuple_buf_size);
+	if (tuple_buf == NULL) {
+		diag_set(OutOfMemory, tuple_buf_size, "region", "tuple");
+		return -1;
+	}
+	char *tuple_buf_end = tuple_buf;
+	tuple_buf_end = mp_encode_array(tuple_buf_end, 2);
+	tuple_buf_end = mp_encode_uint(tuple_buf_end, seq_id);
+	tuple_buf_end = (value < 0 ?
+			 mp_encode_int(tuple_buf_end, value) :
+			 mp_encode_uint(tuple_buf_end, value));
+	assert(tuple_buf_end < tuple_buf + tuple_buf_size);
+	return box_replace(BOX_SEQUENCE_DATA_ID,
+			   tuple_buf, tuple_buf_end, NULL);
+}
+
+/** Delete a record from _sequence_data space. */
+static int
+sequence_data_delete(uint32_t seq_id)
+{
+	size_t key_buf_size = mp_sizeof_array(1) + mp_sizeof_uint(UINT64_MAX);
+	char *key_buf = (char *) region_alloc(&fiber()->gc, key_buf_size);
+	if (key_buf == NULL) {
+		diag_set(OutOfMemory, key_buf_size, "region", "key");
+		return -1;
+	}
+	char *key_buf_end = key_buf;
+	key_buf_end = mp_encode_array(key_buf_end, 1);
+	key_buf_end = mp_encode_uint(key_buf_end, seq_id);
+	assert(key_buf_end < key_buf + key_buf_size);
+	return box_delete(BOX_SEQUENCE_DATA_ID, 0,
+			  key_buf, key_buf_end, NULL);
+}
+
+int
+box_sequence_next(uint32_t seq_id, int64_t *result)
+{
+	struct sequence *seq = sequence_cache_find(seq_id);
+	if (seq == NULL)
+		return -1;
+	int64_t value;
+	if (sequence_next(seq, &value) != 0)
+		return -1;
+	if (sequence_data_update(seq_id, value) != 0)
+		return -1;
+	*result = value;
+	return 0;
+}
+int
+box_sequence_get(uint32_t seq_id, int64_t *result)
+{
+	struct sequence *seq = sequence_cache_find(seq_id);
+	if (seq == NULL)
+		return -1;
+	return sequence_get(seq, result);
+}
+
+int
+box_sequence_set(uint32_t seq_id, int64_t value)
+{
+	struct sequence *seq = sequence_cache_find(seq_id);
+	if (seq == NULL)
+		return -1;
+	sequence_set(seq, value);
+	return sequence_data_update(seq_id, value);
+}
+
+int
+box_sequence_reset(uint32_t seq_id)
+{
+	struct sequence *seq = sequence_cache_find(seq_id);
+	if (seq == NULL)
+		return -1;
+	sequence_reset(seq);
+	return sequence_data_delete(seq_id);
+}
+
 static inline void
 box_register_replica(uint32_t id, const struct tt_uuid *uuid)
 {
diff --git a/src/box/box.h b/src/box/box.h
index 3a9231de97c434ea465999f6809d1ed09196861c..460ac7fad8d56cec1a272dab44496518c63f6ba8 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -314,6 +314,52 @@ box_upsert(uint32_t space_id, uint32_t index_id, const char *tuple,
 API_EXPORT int
 box_truncate(uint32_t space_id);
 
+/**
+ * Advance a sequence.
+ *
+ * \param seq_id sequence identifier
+ * \param[out] result pointer to a variable where the next sequence
+ * value will be stored on success
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ */
+API_EXPORT int
+box_sequence_next(uint32_t seq_id, int64_t *result);
+
+/**
+ * Get the last value returned by a sequence.
+ *
+ * \param seq_id sequence identifier
+ * \paramp[out] result pointer to a variable where the last sequence
+ * value will be stored on success
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ */
+API_EXPORT int
+box_sequence_get(uint32_t seq_id, int64_t *result);
+
+/**
+ * Set a sequence value.
+ *
+ * \param seq_id sequence identifier
+ * \param value new sequence value; on success the next call to
+ * box_sequence_next() will return the value following \a value
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ */
+API_EXPORT int
+box_sequence_set(uint32_t seq_id, int64_t value);
+
+/**
+ * Reset a sequence.
+ *
+ * \param seq_id sequence identifier
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ */
+API_EXPORT int
+box_sequence_reset(uint32_t seq_id);
+
 /** \endcond public */
 
 /**
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 90f15015b35fc47eda7566ee9bc49daefb6a04df..ae67b5277fa6e3a64bda4c3bcf68d37373d83af1 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -194,6 +194,13 @@ struct errcode_record {
 	/*139 */_(ER_VINYL_MAX_TUPLE_SIZE,	"Failed to allocate %u bytes for tuple: tuple is too large. Check 'vinyl_max_tuple_size' configuration option.") \
 	/*140 */_(ER_WRONG_DD_VERSION,		"Wrong _schema version: expected 'major.minor[.patch]'") \
 	/*141 */_(ER_WRONG_SPACE_FORMAT,	"Wrong space format (field %u): %s") \
+	/*142 */_(ER_CREATE_SEQUENCE,		"Failed to create sequence '%s': %s") \
+	/*143 */_(ER_ALTER_SEQUENCE,		"Can't modify sequence '%s': %s") \
+	/*144 */_(ER_DROP_SEQUENCE,		"Can't drop sequence '%s': %s") \
+	/*145 */_(ER_NO_SUCH_SEQUENCE,		"Sequence '%s' does not exist") \
+	/*146 */_(ER_SEQUENCE_EXISTS,		"Sequence '%s' already exists") \
+	/*147 */_(ER_SEQUENCE_OVERFLOW,		"Sequence '%s' has overflowed") \
+	/*148 */_(ER_SEQUENCE_NOT_STARTED,	"Sequence '%s' is not started") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 8dc03ddedd02b8825868f21e7ca3aa442f5505f2..a6a23696fb390103de7caa4f720fc46a0c459c29 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -48,6 +48,7 @@
 #include "box/lua/slab.h"
 #include "box/lua/index.h"
 #include "box/lua/space.h"
+#include "box/lua/sequence.h"
 #include "box/lua/misc.h"
 #include "box/lua/stat.h"
 #include "box/lua/info.h"
@@ -236,6 +237,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_slab_init(L);
 	box_lua_index_init(L);
 	box_lua_space_init(L);
+	box_lua_sequence_init(L);
 	box_lua_misc_init(L);
 	box_lua_info_init(L);
 	box_lua_stat_init(L);
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index 9f5f1749da20139ce1d78c50699cd49c5dd2aa69..60549939d4f72cc985bd4a9fe5fa2bc1d664024a 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -347,6 +347,7 @@ setmetatable(box, {
 })
 
 local function load_cfg(cfg)
+    box.internal.schema.init()
     cfg = upgrade_cfg(cfg, translate_cfg)
     cfg = prepare_cfg(cfg, default_cfg, template_cfg, modify_cfg)
     apply_default_cfg(cfg, default_cfg);
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 61dba31f87389c3d50858cac0ec1626d0f701ed1..8fb898dea4302fa2a6a33f688567d3f662fac5df 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -19,6 +19,9 @@ local tuple_bless = box.tuple.bless
 local is_tuple = box.tuple.is
 assert(tuple_encode ~= nil and tuple_bless ~= nil and is_tuple ~= nil)
 
+local INT64_MIN = tonumber64('-9223372036854775808')
+local INT64_MAX = tonumber64('9223372036854775807')
+
 ffi.cdef[[
     struct space *space_by_id(uint32_t id);
     extern uint32_t box_schema_version();
@@ -142,6 +145,31 @@ local function user_resolve(name_or_id)
     end
 end
 
+local function sequence_resolve(name_or_id)
+    local _sequence = box.space[box.schema.SEQUENCE_ID]
+    local tuple
+    if type(name_or_id) == 'string' then
+        tuple = _sequence.index.name:get{name_or_id}
+    elseif type(name_or_id) ~= 'nil' then
+        tuple = _sequence:get{name_or_id}
+    end
+    if tuple ~= nil then
+        return tuple[1], tuple
+    else
+        return nil
+    end
+end
+
+-- Same as type(), but returns 'number' if 'param' is
+-- of type 'cdata' and represents a 64-bit integer.
+local function param_type(param)
+    local t = type(param)
+    if t == 'cdata' and tonumber64(param) ~= nil then
+        t = 'number'
+    end
+    return t
+end
+
 --[[
  @brief Common function to check table with parameters (like options)
  @param table - table with parameters
@@ -180,7 +208,7 @@ local function check_param_table(table, template)
             -- any type is ok
         elseif (string.find(template[k], ',') == nil) then
             -- one type
-            if type(v) ~= template[k] then
+            if param_type(v) ~= template[k] then
                 box.error(box.error.ILLEGAL_PARAMS,
                           "options parameter '" .. k ..
                           "' should be of type " .. template[k])
@@ -188,7 +216,7 @@ local function check_param_table(table, template)
         else
             local good_types = string.gsub(template[k], ' ', '')
             local haystack = ',' .. good_types .. ','
-            local needle = ',' .. type(v) .. ','
+            local needle = ',' .. param_type(v) .. ','
             if (string.find(haystack, needle) == nil) then
                 good_types = string.gsub(good_types, ',', ', ')
                 box.error(box.error.ILLEGAL_PARAMS,
@@ -205,7 +233,7 @@ end
  @example: check_param(user, 'user', 'string')
 --]]
 local function check_param(param, name, should_be_type)
-    if type(param) ~= should_be_type then
+    if param_type(param) ~= should_be_type then
         box.error(box.error.ILLEGAL_PARAMS,
                   name .. " should be a " .. should_be_type)
     end
@@ -1163,6 +1191,147 @@ function box.schema.space.bless(space)
     end
 end
 
+local sequence_mt = {}
+sequence_mt.__index = sequence_mt
+
+sequence_mt.next = function(self)
+    return internal.sequence.next(self.id)
+end
+
+sequence_mt.get = function(self)
+    return internal.sequence.get(self.id)
+end
+
+sequence_mt.set = function(self, value)
+    return internal.sequence.set(self.id, value)
+end
+
+sequence_mt.reset = function(self)
+    return internal.sequence.reset(self.id)
+end
+
+sequence_mt.alter = function(self, opts)
+    box.schema.sequence.alter(self.id, opts)
+end
+
+sequence_mt.drop = function(self)
+    box.schema.sequence.drop(self.id)
+end
+
+local function sequence_tuple_decode(seq, tuple)
+    seq.id, seq.uid, seq.name, seq.step, seq.min, seq.max,
+        seq.start, seq.cache, seq.cycle = tuple:unpack()
+end
+
+local function sequence_new(tuple)
+    local seq = setmetatable({}, sequence_mt)
+    sequence_tuple_decode(seq, tuple)
+    return seq
+end
+
+local function sequence_on_alter(old_tuple, new_tuple)
+    if old_tuple and not new_tuple then
+        local old_name = old_tuple[3]
+        box.sequence[old_name] = nil
+    elseif not old_tuple and new_tuple then
+        local seq = sequence_new(new_tuple)
+        box.sequence[seq.name] = seq
+    else
+        local old_name = old_tuple[3]
+        local seq = box.sequence[old_name]
+        if not seq then
+            seq = sequence_new(seq, new_tuple)
+        else
+            sequence_tuple_decode(seq, new_tuple)
+        end
+        box.sequence[old_name] = nil
+        box.sequence[seq.name] = seq
+    end
+end
+
+box.sequence = {}
+local function box_sequence_init()
+    -- Install a trigger that will update Lua objects on
+    -- _sequence space modifications.
+    internal.sequence.on_alter(sequence_on_alter)
+end
+
+local sequence_options = {
+    step = 'number',
+    min = 'number',
+    max = 'number',
+    start = 'number',
+    cache = 'number',
+    cycle = 'boolean',
+}
+
+local create_sequence_options = table.deepcopy(sequence_options)
+create_sequence_options.if_not_exists = 'boolean'
+
+local alter_sequence_options = table.deepcopy(sequence_options)
+alter_sequence_options.name = 'string'
+
+box.schema.sequence = {}
+box.schema.sequence.create = function(name, opts)
+    opts = opts or {}
+    check_param(name, 'name', 'string')
+    check_param_table(opts, create_sequence_options)
+    local ascending = not opts.step or opts.step > 0
+    local options_defaults = {
+        step = 1,
+        min = ascending and 1 or INT64_MIN,
+        max = ascending and INT64_MAX or -1,
+        start = ascending and (opts.min or 1) or (opts.max or -1),
+        cache = 0,
+        cycle = false,
+    }
+    opts = update_param_table(opts, options_defaults)
+    local id = sequence_resolve(name)
+    if id ~= nil then
+        if not opts.if_not_exists then
+            box.error(box.error.SEQUENCE_EXISTS, name)
+        end
+        return box.sequence[name], 'not created'
+    end
+    local _sequence = box.space[box.schema.SEQUENCE_ID]
+    _sequence:auto_increment{session.uid(), name, opts.step, opts.min,
+                             opts.max, opts.start, opts.cache, opts.cycle}
+    return box.sequence[name]
+end
+
+box.schema.sequence.alter = function(name, opts)
+    check_param_table(opts, alter_sequence_options)
+    local id, tuple = sequence_resolve(name)
+    if id == nil then
+        box.error(box.error.NO_SUCH_SEQUENCE, name)
+    end
+    if opts == nil then
+        return
+    end
+    local seq = {}
+    sequence_tuple_decode(seq, tuple)
+    opts = update_param_table(opts, seq)
+    local _sequence = box.space[box.schema.SEQUENCE_ID]
+    _sequence:replace{seq.id, seq.uid, opts.name, opts.step, opts.min,
+                      opts.max, opts.start, opts.cache, opts.cycle}
+end
+
+box.schema.sequence.drop = function(name, opts)
+    opts = opts or {}
+    check_param_table(opts, {if_exists = 'boolean'})
+    local id = sequence_resolve(name)
+    if id == nil then
+        if not opts.if_exists then
+            box.error(box.error.NO_SUCH_SEQUENCE, name)
+        end
+        return
+    end
+    local _sequence = box.space[box.schema.SEQUENCE_ID]
+    local _sequence_data = box.space[box.schema.SEQUENCE_DATA_ID]
+    _sequence_data:delete{id}
+    _sequence:delete{id}
+end
+
 local function privilege_resolve(privilege)
     local numeric = 0
     if type(privilege) == 'string' then
@@ -1491,6 +1660,10 @@ local function drop(uid, opts)
     for k, tuple in pairs(grants) do
         revoke(tuple[2], tuple[2], uid)
     end
+    local sequences = box.space[box.schema.SEQUENCE_ID].index.owner:select{uid}
+    for k, tuple in pairs(sequences) do
+        box.schema.sequence.drop(tuple[1])
+    end
     box.space[box.schema.USER_ID]:delete{uid}
 end
 
@@ -1651,3 +1824,8 @@ local function box_space_mt(tab)
 end
 
 setmetatable(box.space, { __serialize = box_space_mt })
+
+box.internal.schema = {}
+box.internal.schema.init = function()
+    box_sequence_init()
+end
diff --git a/src/box/lua/sequence.c b/src/box/lua/sequence.c
new file mode 100644
index 0000000000000000000000000000000000000000..63d3471773e745de2966a7d7827552b944f41aa2
--- /dev/null
+++ b/src/box/lua/sequence.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "box/lua/sequence.h"
+#include "box/lua/tuple.h"
+#include "lua/utils.h"
+#include "lua/trigger.h"
+
+#include "diag.h"
+#include "box/box.h"
+#include "box/schema.h"
+#include "box/txn.h"
+
+static int
+lbox_sequence_next(struct lua_State *L)
+{
+	uint32_t seq_id = luaL_checkinteger(L, 1);
+	int64_t result;
+	if (box_sequence_next(seq_id, &result) != 0)
+		luaT_error(L);
+	luaL_pushint64(L, result);
+	return 1;
+}
+
+static int
+lbox_sequence_get(struct lua_State *L)
+{
+	uint32_t seq_id = luaL_checkinteger(L, 1);
+	int64_t result;
+	if (box_sequence_get(seq_id, &result) != 0)
+		luaT_error(L);
+	luaL_pushint64(L, result);
+	return 1;
+}
+
+static int
+lbox_sequence_set(struct lua_State *L)
+{
+	uint32_t seq_id = luaL_checkinteger(L, 1);
+	int64_t value = luaL_checkint64(L, 2);
+	if (box_sequence_set(seq_id, value) != 0)
+		luaT_error(L);
+	return 0;
+}
+
+static int
+lbox_sequence_reset(struct lua_State *L)
+{
+	uint32_t seq_id = luaL_checkinteger(L, 1);
+	if (box_sequence_reset(seq_id) != 0)
+		luaT_error(L);
+	return 0;
+}
+
+static int
+lbox_sequence_push_on_alter_event(struct lua_State *L, void *event)
+{
+	struct txn_stmt *stmt = (struct txn_stmt *) event;
+	if (stmt->old_tuple) {
+		luaT_pushtuple(L, stmt->old_tuple);
+	} else {
+		lua_pushnil(L);
+	}
+	if (stmt->new_tuple) {
+		luaT_pushtuple(L, stmt->new_tuple);
+	} else {
+		lua_pushnil(L);
+	}
+	return 2;
+}
+
+static int
+lbox_sequence_on_alter(struct lua_State *L)
+{
+	return lbox_trigger_reset(L, 2, &on_alter_sequence,
+				  lbox_sequence_push_on_alter_event);
+}
+
+void
+box_lua_sequence_init(struct lua_State *L)
+{
+	static const struct luaL_Reg sequence_internal_lib[] = {
+		{"next", lbox_sequence_next},
+		{"get", lbox_sequence_get},
+		{"set", lbox_sequence_set},
+		{"reset", lbox_sequence_reset},
+		{"on_alter", lbox_sequence_on_alter},
+		{NULL, NULL}
+	};
+	luaL_register(L, "box.internal.sequence", sequence_internal_lib);
+	lua_pop(L, 1);
+}
diff --git a/src/box/lua/sequence.h b/src/box/lua/sequence.h
new file mode 100644
index 0000000000000000000000000000000000000000..14c41ffc1279cb3e94fcb6df3b7dabd0438553fd
--- /dev/null
+++ b/src/box/lua/sequence.h
@@ -0,0 +1,47 @@
+#ifndef INCLUDES_TARANTOOL_MOD_BOX_LUA_SEQUENCE_H
+#define INCLUDES_TARANTOOL_MOD_BOX_LUA_SEQUENCE_H
+/*
+ * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct lua_State;
+
+void
+box_lua_sequence_init(struct lua_State *L);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* INCLUDES_TARANTOOL_MOD_BOX_LUA_SEQUENCE_H */
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 44893c8be37e87552f6db4a49092d3f7ca2b06d1..ecaa6bccb6dca010c3e2aac4aec85dea5e8db6f2 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -355,6 +355,10 @@ box_lua_space_init(struct lua_State *L)
 	lua_setfield(L, -2, "CLUSTER_ID");
 	lua_pushnumber(L, BOX_TRUNCATE_ID);
 	lua_setfield(L, -2, "TRUNCATE_ID");
+	lua_pushnumber(L, BOX_SEQUENCE_ID);
+	lua_setfield(L, -2, "SEQUENCE_ID");
+	lua_pushnumber(L, BOX_SEQUENCE_DATA_ID);
+	lua_setfield(L, -2, "SEQUENCE_DATA_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 1328a1e049517282b9e4cd65143c935144d1f43a..fb5d1dd06c0e0c704d9d4817a22797fec6333267 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -86,6 +86,8 @@ local function erase()
     truncate(box.space._user)
     truncate(box.space._func)
     truncate(box.space._priv)
+    truncate(box.space._sequence_data)
+    truncate(box.space._sequence)
     truncate(box.space._truncate)
     --truncate(box.space._schema)
     box.space._schema:delete('version')
@@ -837,8 +839,41 @@ end
 -- Tarantool 1.7.6
 --------------------------------------------------------------------------------
 
+local function create_sequence_space()
+    local _space = box.space[box.schema.SPACE_ID]
+    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 MAP = setmap({})
+
+    log.info("create space _sequence")
+    _space:insert{_sequence.id, ADMIN, '_sequence', 'memtx', 0, MAP,
+                  {{name = 'id', type = 'unsigned'},
+                   {name = 'owner', type = 'unsigned'},
+                   {name = 'name', type = 'string'},
+                   {name = 'step', type = 'integer'},
+                   {name = 'min', type = 'integer'},
+                   {name = 'max', type = 'integer'},
+                   {name = 'start', type = 'integer'},
+                   {name = 'cache', type = 'integer'},
+                   {name = 'cycle', type = 'boolean'}}}
+    log.info("create index _sequence:primary")
+    _index:insert{_sequence.id, 0, 'primary', 'tree', {unique = true}, {{0, 'unsigned'}}}
+    log.info("create index _sequence:owner")
+    _index:insert{_sequence.id, 1, 'owner', 'tree', {unique = false}, {{1, 'unsigned'}}}
+    log.info("create index _sequence:name")
+    _index:insert{_sequence.id, 2, 'name', 'tree', {unique = true}, {{2, 'string'}}}
+
+    log.info("create space _sequence_data")
+    _space:insert{_sequence_data.id, ADMIN, '_sequence_data', 'memtx', 0, MAP,
+                  {{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'}}}
+end
+
 local function upgrade_to_1_7_6()
--- Trigger space format checking by updating version in _schema.
+    create_sequence_space()
+    -- Trigger space format checking by updating version in _schema.
 end
 
 --------------------------------------------------------------------------------
diff --git a/src/box/schema.cc b/src/box/schema.cc
index e5c83f62cb3c242319303db6c2b88b83433fd583..c4837dead583d6fed730f3d9fa9c56cc00139a02 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -33,6 +33,7 @@
 #include "engine.h"
 #include "memtx_index.h"
 #include "func.h"
+#include "sequence.h"
 #include "tuple.h"
 #include "assoc.h"
 #include "alter.h"
@@ -58,10 +59,12 @@
 static struct mh_i32ptr_t *spaces;
 static struct mh_i32ptr_t *funcs;
 static struct mh_strnptr_t *funcs_by_name;
+static struct mh_i32ptr_t *sequences;
 uint32_t schema_version = 0;
 uint32_t dd_version_id = version_id(1, 6, 4);
 
 struct rlist on_alter_space = RLIST_HEAD_INITIALIZER(on_alter_space);
+struct rlist on_alter_sequence = RLIST_HEAD_INITIALIZER(on_alter_sequence);
 
 /**
  * Lock of scheme modification
@@ -251,6 +254,7 @@ schema_init()
 	spaces = mh_i32ptr_new();
 	funcs = mh_i32ptr_new();
 	funcs_by_name = mh_strnptr_new();
+	sequences = mh_i32ptr_new();
 	/*
 	 * Create surrogate space objects for the mandatory system
 	 * spaces (the primal eggs from which we get all the
@@ -282,6 +286,14 @@ schema_init()
 	sc_space_new(BOX_TRUNCATE_ID, "_truncate", key_def,
 		     &on_replace_truncate, &on_stmt_begin_truncate);
 
+	/* _sequence - definition of all sequence objects. */
+	sc_space_new(BOX_SEQUENCE_ID, "_sequence", key_def,
+		     &on_replace_sequence, NULL);
+
+	/* _sequence_data - current sequence value. */
+	sc_space_new(BOX_SEQUENCE_DATA_ID, "_sequence_data", key_def,
+		     &on_replace_sequence_data, NULL);
+
 	/* _user - all existing users */
 	sc_space_new(BOX_USER_ID, "_user", key_def, &on_replace_user, NULL);
 
@@ -335,6 +347,14 @@ schema_free(void)
 		func_cache_delete(func->def->fid);
 	}
 	mh_i32ptr_delete(funcs);
+	while (mh_size(sequences) > 0) {
+		mh_int_t i = mh_first(sequences);
+
+		struct sequence *seq = ((struct sequence *)
+					mh_i32ptr_node(sequences, i)->val);
+		sequence_cache_delete(seq->def->id);
+	}
+	mh_i32ptr_delete(sequences);
 }
 
 void
@@ -425,3 +445,57 @@ schema_find_grants(const char *type, uint32_t id)
 	index->initIterator(it, ITER_EQ, key, 2);
 	return it->next(it);
 }
+
+struct sequence *
+sequence_by_id(uint32_t id)
+{
+	mh_int_t k = mh_i32ptr_find(sequences, id, NULL);
+	if (k == mh_end(sequences))
+		return NULL;
+	return (struct sequence *) mh_i32ptr_node(sequences, k)->val;
+}
+
+struct sequence *
+sequence_cache_find(uint32_t id)
+{
+	struct sequence *seq = sequence_by_id(id);
+	if (seq == NULL)
+		tnt_raise(ClientError, ER_NO_SUCH_SEQUENCE, int2str(id));
+	return seq;
+}
+
+void
+sequence_cache_replace(struct sequence_def *def)
+{
+	struct sequence *seq = sequence_by_id(def->id);
+	if (seq == NULL) {
+		/* Create a new sequence. */
+		seq = (struct sequence *) calloc(1, sizeof(*seq));
+		if (seq == NULL)
+			goto error;
+		struct mh_i32ptr_node_t node = { def->id, seq };
+		if (mh_i32ptr_put(sequences, &node, NULL, NULL) ==
+		    mh_end(sequences))
+			goto error;
+	} else {
+		/* Update an existing sequence. */
+		free(seq->def);
+	}
+	seq->def = def;
+	return;
+error:
+	panic_syserror("Out of memory for the data "
+		       "dictionary cache (sequence).");
+}
+
+void
+sequence_cache_delete(uint32_t id)
+{
+	struct sequence *seq = sequence_by_id(id);
+	if (seq != NULL) {
+		mh_i32ptr_del(sequences, seq->def->id, NULL);
+		free(seq->def);
+		TRASH(seq);
+		free(seq);
+	}
+}
diff --git a/src/box/schema.h b/src/box/schema.h
index a631de8d434ae52b23cc1e2c122fb4338c25e16c..6e35961575b46f42bf4a27c2eb44696dd89e1c13 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -154,6 +154,34 @@ func_by_name(const char *name, uint32_t name_len);
 bool
 schema_find_grants(const char *type, uint32_t id);
 
+/**
+ * Find a sequence by id. Return NULL if the sequence was
+ * not found.
+ */
+struct sequence *
+sequence_by_id(uint32_t id);
+
+/**
+ * A wrapper around sequence_by_id() that raises an exception
+ * if the sequence was not found in the cache.
+ */
+struct sequence *
+sequence_cache_find(uint32_t id);
+
+/**
+ * Insert a new sequence object into the cache or update
+ * an existing one if there's already a sequence with
+ * the given id in the cache.
+ */
+void
+sequence_cache_replace(struct sequence_def *def);
+
+/** Delete a sequence from the sequence cache. */
+void
+sequence_cache_delete(uint32_t id);
+
+#endif /* defined(__cplusplus) */
+
 /**
  * Triggers fired after committing a change in space definition.
  * The space is passed to the trigger callback in the event
@@ -162,6 +190,10 @@ schema_find_grants(const char *type, uint32_t id);
  */
 extern struct rlist on_alter_space;
 
-#endif /* defined(__cplusplus) */
+/**
+ * Triggers fired after committing a change in sequence definition.
+ * It is passed the txn statement that altered the sequence.
+ */
+extern struct rlist on_alter_sequence;
 
 #endif /* INCLUDES_TARANTOOL_BOX_SCHEMA_H */
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index 591133762a28072de3ec93f631ca4769e5ffecb1..046dd5306fcb3e04b395ae7165c45ef9d7c3cf93 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -72,6 +72,10 @@ enum {
 	BOX_SPACE_ID = 280,
 	/** Space id of _vspace view. */
 	BOX_VSPACE_ID = 281,
+	/** Space id of _sequence. */
+	BOX_SEQUENCE_ID = 284,
+	/** Space id of _sequence_data. */
+	BOX_SEQUENCE_DATA_ID = 285,
 	/** Space id of _index. */
 	BOX_INDEX_ID = 288,
 	/** Space id of _vindex view. */
@@ -166,6 +170,25 @@ enum {
 	BOX_TRUNCATE_FIELD_COUNT = 1,
 };
 
+/** _sequence fields. */
+enum {
+	BOX_SEQUENCE_FIELD_ID = 0,
+	BOX_SEQUENCE_FIELD_UID = 1,
+	BOX_SEQUENCE_FIELD_NAME = 2,
+	BOX_SEQUENCE_FIELD_STEP = 3,
+	BOX_SEQUENCE_FIELD_MIN = 4,
+	BOX_SEQUENCE_FIELD_MAX = 5,
+	BOX_SEQUENCE_FIELD_START = 6,
+	BOX_SEQUENCE_FIELD_CACHE = 7,
+	BOX_SEQUENCE_FIELD_CYCLE = 8,
+};
+
+/** _sequence_data fields. */
+enum {
+	BOX_SEQUENCE_DATA_FIELD_ID = 0,
+	BOX_SEQUENCE_DATA_FIELD_VALUE = 1,
+};
+
 /*
  * Different objects which can be subject to access
  * control.
diff --git a/src/box/sequence.c b/src/box/sequence.c
new file mode 100644
index 0000000000000000000000000000000000000000..b43bb9217c85f72b36af684403ff9e75a82bd8fa
--- /dev/null
+++ b/src/box/sequence.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "sequence.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "diag.h"
+#include "error.h"
+#include "errcode.h"
+
+int
+sequence_next(struct sequence *seq, int64_t *result)
+{
+	int64_t value;
+	struct sequence_def *def = seq->def;
+	if (!seq->is_started) {
+		value = def->start;
+		seq->is_started = true;
+		goto done;
+	}
+	value = seq->value;
+	if (def->step > 0) {
+		if (value < def->min) {
+			value = def->min;
+			goto done;
+		}
+		if (value >= 0 && def->step > INT64_MAX - value)
+			goto overflow;
+		value += def->step;
+		if (value > def->max)
+			goto overflow;
+	} else {
+		assert(def->step < 0);
+		if (value > def->max) {
+			value = def->max;
+			goto done;
+		}
+		if (value < 0 && def->step < INT64_MIN - value)
+			goto overflow;
+		value += def->step;
+		if (value < def->min)
+			goto overflow;
+	}
+done:
+	assert(value >= def->min && value <= def->max);
+	*result = seq->value = value;
+	return 0;
+overflow:
+	if (!def->cycle) {
+		diag_set(ClientError, ER_SEQUENCE_OVERFLOW, def->name);
+		return -1;
+	}
+	value = def->step > 0 ? def->min : def->max;
+	goto done;
+}
+
+int
+sequence_get(struct sequence *seq, int64_t *result)
+{
+	if (!seq->is_started) {
+		diag_set(ClientError, ER_SEQUENCE_NOT_STARTED, seq->def->name);
+		return -1;
+	}
+	*result = seq->value;
+	return 0;
+}
diff --git a/src/box/sequence.h b/src/box/sequence.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ea016265a22e1ff3a5789b17bcafcfaf4df0626
--- /dev/null
+++ b/src/box/sequence.h
@@ -0,0 +1,128 @@
+#ifndef INCLUDES_TARANTOOL_BOX_SEQUENCE_H
+#define INCLUDES_TARANTOOL_BOX_SEQUENCE_H
+/*
+ * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/** Sequence metadata. */
+struct sequence_def {
+	/** Sequence id. */
+	uint32_t id;
+	/** Owner of the sequence. */
+	uint32_t uid;
+	/**
+	 * The value added to the sequence at each step.
+	 * If it is positive, the sequence is ascending,
+	 * otherwise it is descending.
+	 */
+	int64_t step;
+	/** Min sequence value. */
+	int64_t min;
+	/** Max sequence value. */
+	int64_t max;
+	/** Initial sequence value. */
+	int64_t start;
+	/** Number of values to preallocate. Not implemented yet. */
+	int64_t cache;
+	/**
+	 * If this flag is set, the sequence will wrap
+	 * upon reaching min or max value by a descending
+	 * or ascending sequence respectively.
+	 */
+	bool cycle;
+	/** Sequence name. */
+	char name[0];
+};
+
+/** Sequence object. */
+struct sequence {
+	/** Sequence definition. */
+	struct sequence_def *def;
+	/** Last value returned by the sequence. */
+	int64_t value;
+	/** True if the sequence was started. */
+	bool is_started;
+};
+
+static inline size_t
+sequence_def_sizeof(uint32_t name_len)
+{
+	return sizeof(struct sequence_def) + name_len + 1;
+}
+
+/** Reset a sequence. */
+static inline void
+sequence_reset(struct sequence *seq)
+{
+	seq->is_started = false;
+}
+
+/** Set a sequence value. */
+static inline void
+sequence_set(struct sequence *seq, int64_t value)
+{
+	seq->value = value;
+	seq->is_started = true;
+}
+
+/**
+ * Advance a sequence.
+ *
+ * On success, return 0 and assign the next sequence to
+ * @result. If the sequence isn't cyclic and has reached
+ * its limit, return -1 and set diag.
+ */
+int
+sequence_next(struct sequence *seq, int64_t *result);
+
+/**
+ * Get the last value returned by a sequence.
+ *
+ * Return 0 and assign the last sequence value to @result
+ * if the sequence was started. If it was not, return -1
+ * and raise ER_SEQUENCE_NOT_STARTED error.
+ */
+int
+sequence_get(struct sequence *seq, int64_t *result);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* INCLUDES_TARANTOOL_BOX_SEQUENCE_H */
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 2a5632ab552b4444ee0ff1f38ee944bc2c6ef749..366137a08a0773fdf51541adb3febc6d9e5080a5 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -663,6 +663,52 @@ tuple_field_with_type(const struct tuple *tuple, uint32_t fieldno,
 	return field;
 }
 
+/**
+ * A convenience shortcut for data dictionary - get a tuple field
+ * as bool.
+ */
+static inline int
+tuple_field_bool(const struct tuple *tuple, uint32_t fieldno, bool *out)
+{
+	const char *field = tuple_field_with_type(tuple, fieldno, MP_BOOL);
+	if (field == NULL)
+		return -1;
+	*out = mp_decode_bool(&field);
+	return 0;
+}
+
+/**
+ * A convenience shortcut for data dictionary - get a tuple field
+ * as int64_t.
+ */
+static inline int
+tuple_field_i64(const struct tuple *tuple, uint32_t fieldno, int64_t *out)
+{
+	const char *field = tuple_field(tuple, fieldno);
+	if (field == NULL) {
+		diag_set(ClientError, ER_NO_SUCH_FIELD, fieldno);
+		return -1;
+	}
+	uint64_t val;
+	switch (mp_typeof(*field)) {
+	case MP_INT:
+		*out = mp_decode_int(&field);
+		break;
+	case MP_UINT:
+		val = mp_decode_uint(&field);
+		if (val <= INT64_MAX) {
+			*out = val;
+			break;
+		}
+		FALLTHROUGH;
+	default:
+		diag_set(ClientError, ER_FIELD_TYPE, fieldno + TUPLE_INDEX_BASE,
+			 field_type_strs[FIELD_TYPE_INTEGER]);
+		return -1;
+	}
+	return 0;
+}
+
 /**
  * A convenience shortcut for data dictionary - get a tuple field
  * as uint64_t.
@@ -860,6 +906,26 @@ tuple_field_with_type_xc(const struct tuple *tuple, uint32_t fieldno,
 	return out;
 }
 
+/* @copydoc tuple_field_bool() */
+static inline bool
+tuple_field_bool_xc(const struct tuple *tuple, uint32_t fieldno)
+{
+	bool out;
+	if (tuple_field_bool(tuple, fieldno, &out) != 0)
+		diag_raise();
+	return out;
+}
+
+/* @copydoc tuple_field_i64() */
+static inline int64_t
+tuple_field_i64_xc(const struct tuple *tuple, uint32_t fieldno)
+{
+	int64_t out;
+	if (tuple_field_i64(tuple, fieldno, &out) != 0)
+		diag_raise();
+	return out;
+}
+
 /* @copydoc tuple_field_u64() */
 static inline uint64_t
 tuple_field_u64_xc(const struct tuple *tuple, uint32_t fieldno)
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index 1ce4964946a95a7d0cdb8f78da1bc489ca9e59a4..c1982bed663bf24f01575d5b636f5d707f89a4ea 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", 13)
-            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 32)
+            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 15)
+            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 36)
         end)
     end)
 
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 2a40bb60cb2a4a2fdec6b5613eabc5962a1c0aaa..d6ef7d692b8d707060d06e254670c48d43779790 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -22,6 +22,13 @@ box.space._space:select{}
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine',
         'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags',
         'type': 'map'}, {'name': 'format', 'type': 'array'}]]
+  - [284, 1, '_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
+        'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'step',
+        'type': 'integer'}, {'name': 'min', 'type': 'integer'}, {'name': 'max', 'type': 'integer'},
+      {'name': 'start', 'type': 'integer'}, {'name': 'cache', 'type': 'integer'},
+      {'name': 'cycle', 'type': 'boolean'}]]
+  - [285, 1, '_sequence_data', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
+      {'name': 'value', 'type': 'integer'}]]
   - [288, 1, '_index', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'iid',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type',
         'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]]
@@ -60,6 +67,10 @@ box.space._index:select{}
   - [281, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [281, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [281, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
+  - [284, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [284, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
+  - [284, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
+  - [285, 0, 'primary', 'hash', {'unique': true}, [[0, 'unsigned']]]
   - [288, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]
   - [288, 2, 'name', 'tree', {'unique': true}, [[0, 'unsigned'], [2, 'string']]]
   - [289, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 455ec1f9a43ecf6115699da61e68f266d1ed740a..a1080c5dae243499d8059cd70b11fbfdc871ada1 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -631,6 +631,13 @@ box.space._space:select()
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine',
         'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags',
         'type': 'map'}, {'name': 'format', 'type': 'array'}]]
+  - [284, 1, '_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
+        'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'step',
+        'type': 'integer'}, {'name': 'min', 'type': 'integer'}, {'name': 'max', 'type': 'integer'},
+      {'name': 'start', 'type': 'integer'}, {'name': 'cache', 'type': 'integer'},
+      {'name': 'cycle', 'type': 'boolean'}]]
+  - [285, 1, '_sequence_data', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
+      {'name': 'value', 'type': 'integer'}]]
   - [288, 1, '_index', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'iid',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type',
         'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]]
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index b7328e61e73b4b2b44fc56a9d921cd05e3291d22..faf3f1a5dab828f197ca6e6d6aa854c2a872dd74 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{}
 ---
-- 14
+- 16
 ...
 #box.space._vindex:select{}
 ---
-- 33
+- 37
 ...
 #box.space._vuser:select{}
 ---
@@ -262,7 +262,7 @@ box.session.su('guest')
 ...
 #box.space._vindex:select{}
 ---
-- 33
+- 37
 ...
 #box.space._vuser:select{}
 ---
diff --git a/test/box/alter.result b/test/box/alter.result
index 9401122600b0b28d15acd079e972c2ef29d36904..3bf1c1a10dde0ea5fb7ab12871880ff76ea867ba 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -179,6 +179,10 @@ _index:select{}
   - [281, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [281, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [281, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
+  - [284, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [284, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
+  - [284, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
+  - [285, 0, 'primary', 'hash', {'unique': true}, [[0, 'unsigned']]]
   - [288, 0, 'primary', 'tree', 1, 2, 0, 'unsigned', 1, 'unsigned']
   - [288, 2, 'name', 'tree', {'unique': true}, [[0, 'unsigned'], [2, 'string']]]
   - [289, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]
diff --git a/test/box/misc.result b/test/box/misc.result
index 6b362c0dca5b54e8b73aef4bf6ae93da93377b4d..f904fa43a2528326afc9e8b76ff1bf9cd0007d0b 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -70,6 +70,7 @@ t
   - runtime
   - savepoint
   - schema
+  - sequence
   - session
   - slab
   - snapshot
@@ -306,12 +307,14 @@ t;
 - - 'box.error.UNKNOWN_REPLICA : 62'
   - 'box.error.WRONG_INDEX_RECORD : 106'
   - 'box.error.NO_SUCH_TRIGGER : 34'
+  - 'box.error.SEQUENCE_EXISTS : 146'
   - 'box.error.CHECKPOINT_IN_PROGRESS : 120'
   - 'box.error.FIELD_TYPE : 23'
   - 'box.error.WRONG_SPACE_FORMAT : 141'
   - 'box.error.UNKNOWN_UPDATE_OP : 28'
   - 'box.error.CURSOR_NO_TRANSACTION : 80'
   - 'box.error.TUPLE_REF_OVERFLOW : 86'
+  - 'box.error.ALTER_SEQUENCE : 143'
   - 'box.error.INVALID_XLOG_NAME : 75'
   - 'box.error.SAVEPOINT_EMPTY_TX : 60'
   - 'box.error.NO_SUCH_FUNCTION : 51'
@@ -378,11 +381,16 @@ t;
   - 'box.error.IDENTIFIER : 70'
   - 'box.error.NO_SUCH_ENGINE : 57'
   - 'box.error.COMMIT_IN_SUB_STMT : 122'
+  - 'box.error.injection : table: <address>
+  - 'box.error.SEQUENCE_NOT_STARTED : 148'
   - 'box.error.LAST_DROP : 15'
+  - 'box.error.SEQUENCE_OVERFLOW : 147'
   - 'box.error.DECOMPRESSION : 124'
+  - 'box.error.CREATE_SEQUENCE : 142'
   - 'box.error.CREATE_USER : 43'
+  - 'box.error.INJECTION : 8'
   - 'box.error.INSTANCE_UUID_MISMATCH : 66'
-  - 'box.error.injection : table: <address>
+  - 'box.error.FUNCTION_MAX : 54'
   - 'box.error.SYSTEM : 115'
   - 'box.error.KEY_PART_IS_TOO_LONG : 118'
   - 'box.error.TRUNCATE_SYSTEM_SPACE : 137'
@@ -401,7 +409,7 @@ t;
   - 'box.error.ALTER_SPACE : 12'
   - 'box.error.ACTIVE_TRANSACTION : 79'
   - 'box.error.EXACT_FIELD_COUNT : 38'
-  - 'box.error.FUNCTION_MAX : 54'
+  - 'box.error.DROP_SEQUENCE : 144'
   - 'box.error.DROP_SPACE : 11'
   - 'box.error.UPSERT_UNIQUE_SECONDARY_KEY : 105'
   - 'box.error.UNKNOWN_REQUEST_TYPE : 48'
@@ -423,7 +431,7 @@ t;
   - 'box.error.NO_CONNECTION : 77'
   - 'box.error.DROP_PRIMARY_KEY : 17'
   - 'box.error.TUPLE_FORMAT_LIMIT : 16'
-  - 'box.error.INJECTION : 8'
+  - 'box.error.NO_SUCH_SEQUENCE : 145'
   - 'box.error.PROC_RET : 21'
   - 'box.error.INVALID_UUID : 64'
   - 'box.error.INVALID_ORDER : 68'
diff --git a/test/box/sequence.result b/test/box/sequence.result
new file mode 100644
index 0000000000000000000000000000000000000000..a3e91282ade6187414510f835df2abd0ddf65ffe
--- /dev/null
+++ b/test/box/sequence.result
@@ -0,0 +1,625 @@
+test_run = require('test_run').new()
+---
+...
+-- Options check on create.
+box.schema.sequence.create('test', {abc = 'abc'})
+---
+- error: Illegal parameters, unexpected option 'abc'
+...
+box.schema.sequence.create('test', {step = 'a'})
+---
+- error: Illegal parameters, options parameter 'step' should be of type number
+...
+box.schema.sequence.create('test', {min = 'b'})
+---
+- error: Illegal parameters, options parameter 'min' should be of type number
+...
+box.schema.sequence.create('test', {max = 'c'})
+---
+- error: Illegal parameters, options parameter 'max' should be of type number
+...
+box.schema.sequence.create('test', {start = true})
+---
+- error: Illegal parameters, options parameter 'start' should be of type number
+...
+box.schema.sequence.create('test', {cycle = 123})
+---
+- error: Illegal parameters, options parameter 'cycle' should be of type boolean
+...
+box.schema.sequence.create('test', {name = 'test'})
+---
+- error: Illegal parameters, unexpected option 'name'
+...
+box.schema.sequence.create('test', {step = 0})
+---
+- error: 'Failed to create sequence ''test'': step option must be non-zero'
+...
+box.schema.sequence.create('test', {min = 10, max = 1})
+---
+- error: 'Failed to create sequence ''test'': max must be greater than or equal to
+    min'
+...
+box.schema.sequence.create('test', {min = 10, max = 20, start = 1})
+---
+- error: 'Failed to create sequence ''test'': start must be between min and max'
+...
+-- Options check on alter.
+_ = box.schema.sequence.create('test')
+---
+...
+box.schema.sequence.alter('test', {abc = 'abc'})
+---
+- error: Illegal parameters, unexpected option 'abc'
+...
+box.schema.sequence.alter('test', {step = 'a'})
+---
+- error: Illegal parameters, options parameter 'step' should be of type number
+...
+box.schema.sequence.alter('test', {min = 'b'})
+---
+- error: Illegal parameters, options parameter 'min' should be of type number
+...
+box.schema.sequence.alter('test', {max = 'c'})
+---
+- error: Illegal parameters, options parameter 'max' should be of type number
+...
+box.schema.sequence.alter('test', {start = true})
+---
+- error: Illegal parameters, options parameter 'start' should be of type number
+...
+box.schema.sequence.alter('test', {cycle = 123})
+---
+- error: Illegal parameters, options parameter 'cycle' should be of type boolean
+...
+box.schema.sequence.alter('test', {name = 'test'})
+---
+...
+box.schema.sequence.alter('test', {if_not_exists = false})
+---
+- error: Illegal parameters, unexpected option 'if_not_exists'
+...
+box.schema.sequence.alter('test', {step = 0})
+---
+- error: 'Can''t modify sequence ''test'': step option must be non-zero'
+...
+box.schema.sequence.alter('test', {min = 10, max = 1})
+---
+- error: 'Can''t modify sequence ''test'': max must be greater than or equal to min'
+...
+box.schema.sequence.alter('test', {min = 10, max = 20, start = 1})
+---
+- error: 'Can''t modify sequence ''test'': start must be between min and max'
+...
+box.schema.sequence.drop('test')
+---
+...
+-- Duplicate name.
+sq1 = box.schema.sequence.create('test')
+---
+...
+box.schema.sequence.create('test')
+---
+- error: Sequence 'test' already exists
+...
+sq2, msg = box.schema.sequence.create('test', {if_not_exists = true})
+---
+...
+sq1 == sq2, msg
+---
+- true
+- not created
+...
+_ = box.schema.sequence.create('test2')
+---
+...
+box.schema.sequence.alter('test2', {name = 'test'})
+---
+- error: Duplicate key exists in unique index 'name' in space '_sequence'
+...
+box.schema.sequence.drop('test2')
+---
+...
+box.schema.sequence.drop('test')
+---
+...
+-- Check that box.sequence gets updated.
+sq = box.schema.sequence.create('test')
+---
+...
+box.sequence.test == sq
+---
+- true
+...
+sq.step
+---
+- 1
+...
+sq:alter{step = 2}
+---
+...
+box.sequence.test == sq
+---
+- true
+...
+sq.step
+---
+- 2
+...
+sq:drop()
+---
+...
+box.sequence.test == nil
+---
+- true
+...
+-- Attempt to delete a sequence that has a record in _sequence_data.
+sq = box.schema.sequence.create('test')
+---
+...
+sq:next()
+---
+- 1
+...
+box.space._sequence:delete(sq.id)
+---
+- error: 'Can''t drop sequence ''test'': the sequence has data'
+...
+box.space._sequence_data:delete(sq.id)
+---
+- [1, 1]
+...
+box.space._sequence:delete(sq.id)
+---
+- [1, 1, 'test', 1, 1, 9223372036854775807, 1, 0, false]
+...
+box.sequence.test == nil
+---
+- true
+...
+-- Default ascending sequence.
+sq = box.schema.sequence.create('test')
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- 1
+- 1
+- 9223372036854775807
+- 1
+- false
+...
+sq:get()  -- error
+---
+- error: Sequence 'test' is not started
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:get()  -- 1
+---
+- 1
+...
+sq:next() -- 2
+---
+- 2
+...
+sq:set(100)
+---
+...
+sq:get()  -- 100
+---
+- 100
+...
+sq:next() -- 101
+---
+- 101
+...
+sq:next() -- 102
+---
+- 102
+...
+sq:reset()
+---
+...
+sq:get()  -- error
+---
+- error: Sequence 'test' is not started
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:next() -- 2
+---
+- 2
+...
+sq:drop()
+---
+...
+-- Default descending sequence.
+sq = box.schema.sequence.create('test', {step = -1})
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- -1
+- -9223372036854775808
+- -1
+- -1
+- false
+...
+sq:get()  -- error
+---
+- error: Sequence 'test' is not started
+...
+sq:next() -- -1
+---
+- -1
+...
+sq:get()  -- -1
+---
+- -1
+...
+sq:next() -- -2
+---
+- -2
+...
+sq:set(-100)
+---
+...
+sq:get()  -- -100
+---
+- -100
+...
+sq:next() -- -101
+---
+- -101
+...
+sq:next() -- -102
+---
+- -102
+...
+sq:reset()
+---
+...
+sq:get()  -- error
+---
+- error: Sequence 'test' is not started
+...
+sq:next() -- -1
+---
+- -1
+...
+sq:next() -- -2
+---
+- -2
+...
+sq:drop()
+---
+...
+-- Custom min/max.
+sq = box.schema.sequence.create('test', {min = 10})
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- 1
+- 10
+- 9223372036854775807
+- 10
+- false
+...
+sq:next() -- 10
+---
+- 10
+...
+sq:next() -- 11
+---
+- 11
+...
+sq:drop()
+---
+...
+sq = box.schema.sequence.create('test', {step = -1, max = 20})
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- -1
+- -9223372036854775808
+- 20
+- 20
+- false
+...
+sq:next() -- 20
+---
+- 20
+...
+sq:next() -- 19
+---
+- 19
+...
+sq:drop()
+---
+...
+-- Custom start value.
+sq = box.schema.sequence.create('test', {start = 1000})
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- 1
+- 1
+- 9223372036854775807
+- 1000
+- false
+...
+sq:next() -- 1000
+---
+- 1000
+...
+sq:next() -- 1001
+---
+- 1001
+...
+sq:reset()
+---
+...
+sq:next() -- 1000
+---
+- 1000
+...
+sq:next() -- 1001
+---
+- 1001
+...
+sq:drop()
+---
+...
+-- Overflow and cycle.
+sq = box.schema.sequence.create('test', {max = 2})
+---
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:next() -- 2
+---
+- 2
+...
+sq:next() -- error
+---
+- error: Sequence 'test' has overflowed
+...
+sq:alter{cycle = true}
+---
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:next() -- 2
+---
+- 2
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:alter{step = 2}
+---
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:alter{cycle = false}
+---
+...
+sq:next() -- error
+---
+- error: Sequence 'test' has overflowed
+...
+sq:drop()
+---
+...
+-- Setting sequence value outside boundaries.
+sq = box.schema.sequence.create('test')
+---
+...
+sq:alter{step = 1, min = 1, max = 10}
+---
+...
+sq:set(-100)
+---
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:set(100)
+---
+...
+sq:next() -- error
+---
+- error: Sequence 'test' has overflowed
+...
+sq:reset()
+---
+...
+sq:next() -- 1
+---
+- 1
+...
+sq:alter{min = 5, start = 5}
+---
+...
+sq:next() -- 5
+---
+- 5
+...
+sq:reset()
+---
+...
+sq:alter{step = -1, min = 1, max = 10, start = 10}
+---
+...
+sq:set(100)
+---
+...
+sq:next() -- 10
+---
+- 10
+...
+sq:set(-100)
+---
+...
+sq:next() -- error
+---
+- error: Sequence 'test' has overflowed
+...
+sq:reset()
+---
+...
+sq:next() -- 10
+---
+- 10
+...
+sq:alter{max = 5, start = 5}
+---
+...
+sq:next() -- 5
+---
+- 5
+...
+sq:drop()
+---
+...
+-- number64 arguments.
+INT64_MIN = tonumber64('-9223372036854775808')
+---
+...
+INT64_MAX = tonumber64('9223372036854775807')
+---
+...
+sq = box.schema.sequence.create('test', {step = INT64_MAX, min = INT64_MIN, max = INT64_MAX, start = INT64_MIN})
+---
+...
+sq:next() -- -9223372036854775808
+---
+- -9223372036854775808
+...
+sq:next() -- -1
+---
+- -1
+...
+sq:next() -- 9223372036854775806
+---
+- 9223372036854775806
+...
+sq:next() -- error
+---
+- error: Sequence 'test' has overflowed
+...
+sq:alter{step = INT64_MIN, start = INT64_MAX}
+---
+...
+sq:reset()
+---
+...
+sq:next() -- 9223372036854775807
+---
+- 9223372036854775807
+...
+sq:next() -- -1
+---
+- -1
+...
+sq:next() -- error
+---
+- error: Sequence 'test' has overflowed
+...
+sq:drop()
+---
+...
+-- Using in a transaction.
+s = box.schema.space.create('test')
+---
+...
+_ = s:create_index('pk')
+---
+...
+sq1 = box.schema.sequence.create('sq1', {step = 1})
+---
+...
+sq2 = box.schema.sequence.create('sq2', {step = -1})
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.begin()
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+box.rollback();
+---
+...
+box.begin()
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+box.commit();
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+s:select() -- [4, -4], [5, -5], [6, -6]
+---
+- - [4, -4]
+  - [5, -5]
+  - [6, -6]
+...
+sq1:drop()
+---
+...
+sq2:drop()
+---
+...
+s:drop()
+---
+...
+--
+-- Check that sequences are persistent.
+--
+sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true})
+---
+...
+sq:next()
+---
+- 15
+...
+test_run:cmd('restart server default')
+sq = box.sequence.test
+---
+...
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+---
+- 2
+- 10
+- 20
+- 15
+- true
+...
+sq:next()
+---
+- 17
+...
+sq:drop()
+---
+...
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ca1412f4b0f812a41e38aa76b866b687769ed3a0
--- /dev/null
+++ b/test/box/sequence.test.lua
@@ -0,0 +1,207 @@
+test_run = require('test_run').new()
+
+-- Options check on create.
+box.schema.sequence.create('test', {abc = 'abc'})
+box.schema.sequence.create('test', {step = 'a'})
+box.schema.sequence.create('test', {min = 'b'})
+box.schema.sequence.create('test', {max = 'c'})
+box.schema.sequence.create('test', {start = true})
+box.schema.sequence.create('test', {cycle = 123})
+box.schema.sequence.create('test', {name = 'test'})
+box.schema.sequence.create('test', {step = 0})
+box.schema.sequence.create('test', {min = 10, max = 1})
+box.schema.sequence.create('test', {min = 10, max = 20, start = 1})
+
+-- Options check on alter.
+_ = box.schema.sequence.create('test')
+box.schema.sequence.alter('test', {abc = 'abc'})
+box.schema.sequence.alter('test', {step = 'a'})
+box.schema.sequence.alter('test', {min = 'b'})
+box.schema.sequence.alter('test', {max = 'c'})
+box.schema.sequence.alter('test', {start = true})
+box.schema.sequence.alter('test', {cycle = 123})
+box.schema.sequence.alter('test', {name = 'test'})
+box.schema.sequence.alter('test', {if_not_exists = false})
+box.schema.sequence.alter('test', {step = 0})
+box.schema.sequence.alter('test', {min = 10, max = 1})
+box.schema.sequence.alter('test', {min = 10, max = 20, start = 1})
+box.schema.sequence.drop('test')
+
+-- Duplicate name.
+sq1 = box.schema.sequence.create('test')
+box.schema.sequence.create('test')
+sq2, msg = box.schema.sequence.create('test', {if_not_exists = true})
+sq1 == sq2, msg
+_ = box.schema.sequence.create('test2')
+box.schema.sequence.alter('test2', {name = 'test'})
+box.schema.sequence.drop('test2')
+box.schema.sequence.drop('test')
+
+-- Check that box.sequence gets updated.
+sq = box.schema.sequence.create('test')
+box.sequence.test == sq
+sq.step
+sq:alter{step = 2}
+box.sequence.test == sq
+sq.step
+sq:drop()
+box.sequence.test == nil
+
+-- Attempt to delete a sequence that has a record in _sequence_data.
+sq = box.schema.sequence.create('test')
+sq:next()
+box.space._sequence:delete(sq.id)
+box.space._sequence_data:delete(sq.id)
+box.space._sequence:delete(sq.id)
+box.sequence.test == nil
+
+-- Default ascending sequence.
+sq = box.schema.sequence.create('test')
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq:get()  -- error
+sq:next() -- 1
+sq:get()  -- 1
+sq:next() -- 2
+sq:set(100)
+sq:get()  -- 100
+sq:next() -- 101
+sq:next() -- 102
+sq:reset()
+sq:get()  -- error
+sq:next() -- 1
+sq:next() -- 2
+sq:drop()
+
+-- Default descending sequence.
+sq = box.schema.sequence.create('test', {step = -1})
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq:get()  -- error
+sq:next() -- -1
+sq:get()  -- -1
+sq:next() -- -2
+sq:set(-100)
+sq:get()  -- -100
+sq:next() -- -101
+sq:next() -- -102
+sq:reset()
+sq:get()  -- error
+sq:next() -- -1
+sq:next() -- -2
+sq:drop()
+
+-- Custom min/max.
+sq = box.schema.sequence.create('test', {min = 10})
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq:next() -- 10
+sq:next() -- 11
+sq:drop()
+sq = box.schema.sequence.create('test', {step = -1, max = 20})
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq:next() -- 20
+sq:next() -- 19
+sq:drop()
+
+-- Custom start value.
+sq = box.schema.sequence.create('test', {start = 1000})
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+sq:next() -- 1000
+sq:next() -- 1001
+sq:reset()
+sq:next() -- 1000
+sq:next() -- 1001
+sq:drop()
+
+-- Overflow and cycle.
+sq = box.schema.sequence.create('test', {max = 2})
+sq:next() -- 1
+sq:next() -- 2
+sq:next() -- error
+sq:alter{cycle = true}
+sq:next() -- 1
+sq:next() -- 2
+sq:next() -- 1
+sq:alter{step = 2}
+sq:next() -- 1
+sq:alter{cycle = false}
+sq:next() -- error
+sq:drop()
+
+-- Setting sequence value outside boundaries.
+sq = box.schema.sequence.create('test')
+
+sq:alter{step = 1, min = 1, max = 10}
+sq:set(-100)
+sq:next() -- 1
+sq:set(100)
+sq:next() -- error
+sq:reset()
+sq:next() -- 1
+sq:alter{min = 5, start = 5}
+sq:next() -- 5
+sq:reset()
+
+sq:alter{step = -1, min = 1, max = 10, start = 10}
+sq:set(100)
+sq:next() -- 10
+sq:set(-100)
+sq:next() -- error
+sq:reset()
+sq:next() -- 10
+sq:alter{max = 5, start = 5}
+sq:next() -- 5
+sq:drop()
+
+-- number64 arguments.
+INT64_MIN = tonumber64('-9223372036854775808')
+INT64_MAX = tonumber64('9223372036854775807')
+sq = box.schema.sequence.create('test', {step = INT64_MAX, min = INT64_MIN, max = INT64_MAX, start = INT64_MIN})
+sq:next() -- -9223372036854775808
+sq:next() -- -1
+sq:next() -- 9223372036854775806
+sq:next() -- error
+sq:alter{step = INT64_MIN, start = INT64_MAX}
+sq:reset()
+sq:next() -- 9223372036854775807
+sq:next() -- -1
+sq:next() -- error
+sq:drop()
+
+-- Using in a transaction.
+s = box.schema.space.create('test')
+_ = s:create_index('pk')
+sq1 = box.schema.sequence.create('sq1', {step = 1})
+sq2 = box.schema.sequence.create('sq2', {step = -1})
+
+test_run:cmd("setopt delimiter ';'")
+box.begin()
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+box.rollback();
+box.begin()
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+s:insert{sq1:next(), sq2:next()}
+box.commit();
+test_run:cmd("setopt delimiter ''");
+
+s:select() -- [4, -4], [5, -5], [6, -6]
+
+sq1:drop()
+sq2:drop()
+s:drop()
+
+--
+-- Check that sequences are persistent.
+--
+
+sq = box.schema.sequence.create('test', {step = 2, min = 10, max = 20, start = 15, cycle = true})
+sq:next()
+
+test_run:cmd('restart server default')
+
+sq = box.sequence.test
+sq.step, sq.min, sq.max, sq.start, sq.cycle
+
+sq:next()
+sq:drop()
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index df770cf5a593ec6123361a06978447d81e28720d..96516f7b18dbc8fe9ee9a05551d4ad00413565e3 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:715: usage: next(param, state)'
+- error: 'builtin/box/schema.lua:743: usage: next(param, state)'
 ...
 value
 ---
diff --git a/test/engine/savepoint.result b/test/engine/savepoint.result
index d28c16ac57fb4cc9c95ba2f68f30b7766325cb96..9eb4a6df9618666f1c892af96bb844ce7d628971 100644
--- a/test/engine/savepoint.result
+++ b/test/engine/savepoint.result
@@ -14,7 +14,7 @@ s1 = box.savepoint()
 ...
 box.rollback_to_savepoint(s1)
 ---
-- error: 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)'
+- error: 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)'
 ...
 box.begin() s1 = box.savepoint()
 ---
@@ -323,27 +323,27 @@ test_run:cmd("setopt delimiter ''");
 ok1, errmsg1
 ---
 - false
-- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)'
+- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)'
 ...
 ok2, errmsg2
 ---
 - false
-- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)'
+- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)'
 ...
 ok3, errmsg3
 ---
 - false
-- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)'
+- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)'
 ...
 ok4, errmsg4
 ---
 - false
-- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)'
+- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)'
 ...
 ok5, errmsg5
 ---
 - false
-- 'builtin/box/schema.lua:260: Usage: box.rollback_to_savepoint(savepoint)'
+- 'builtin/box/schema.lua:288: Usage: box.rollback_to_savepoint(savepoint)'
 ...
 s:select{}
 ---
diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result
index 66d7167a4b174f460aaeb95c675ca6a8c7899188..7c5ae26f9089b184876bafc34e80a8f08e9d1f70 100644
--- a/test/wal_off/alter.result
+++ b/test/wal_off/alter.result
@@ -28,7 +28,7 @@ end;
 ...
 #spaces;
 ---
-- 65520
+- 65518
 ...
 -- cleanup
 for k, v in pairs(spaces) do
diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
index f37c34fdd2fb181b6f2751688ea82ff4261c3290..8e366a1481d1349b031069cd9838c02b84fb4354 100644
--- a/test/xlog/upgrade.result
+++ b/test/xlog/upgrade.result
@@ -49,6 +49,13 @@ box.space._space:select()
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine',
         'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags',
         'type': 'map'}, {'name': 'format', 'type': 'array'}]]
+  - [284, 1, '_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
+        'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'step',
+        'type': 'integer'}, {'name': 'min', 'type': 'integer'}, {'name': 'max', 'type': 'integer'},
+      {'name': 'start', 'type': 'integer'}, {'name': 'cache', 'type': 'integer'},
+      {'name': 'cycle', 'type': 'boolean'}]]
+  - [285, 1, '_sequence_data', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
+      {'name': 'value', 'type': 'integer'}]]
   - [288, 1, '_index', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'iid',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type',
         'type': 'string'}, {'name': 'opts', 'type': 'map'}, {'name': 'parts', 'type': 'array'}]]
@@ -90,6 +97,10 @@ box.space._index:select()
   - [281, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [281, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [281, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
+  - [284, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [284, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
+  - [284, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
+  - [285, 0, 'primary', 'hash', {'unique': true}, [[0, 'unsigned']]]
   - [288, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]
   - [288, 2, 'name', 'tree', {'unique': true}, [[0, 'unsigned'], [2, 'string']]]
   - [289, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'unsigned']]]