diff --git a/cfg/core_cfg.cfg_tmpl b/cfg/core_cfg.cfg_tmpl
index 9c695ebef3606f2f2159cbbc4acb0dbf8554b02a..fca66315d3388760c59f32705b4159dfa8fcb804 100644
--- a/cfg/core_cfg.cfg_tmpl
+++ b/cfg/core_cfg.cfg_tmpl
@@ -71,10 +71,6 @@ snap_io_rate_limit=0.0
 # Write no more rows in WAL
 rows_per_wal=500000, ro
 
-# OBSOLETE
-# Starting from 1.4.5, this variable has no effect.
-wal_writer_inbox_size=16384, ro
-
 # Defines fiber/data synchronization fsync(2) policy:
 #   "none":           does not write to WAL
 #   "write":          fibers wait for their data to be written to the log.
diff --git a/cfg/tarantool_box_cfg.c b/cfg/tarantool_box_cfg.c
index 7ceaca74cd242c3698f8ff107cc37b0ac4d92700..045aac656263fbf4aa0a386079cebdbef6fa8951 100644
--- a/cfg/tarantool_box_cfg.c
+++ b/cfg/tarantool_box_cfg.c
@@ -50,7 +50,6 @@ init_tarantool_cfg(tarantool_cfg *c) {
 	c->readahead = 0;
 	c->snap_io_rate_limit = 0;
 	c->rows_per_wal = 0;
-	c->wal_writer_inbox_size = 0;
 	c->wal_mode = NULL;
 	c->wal_fsync_delay = 0;
 	c->wal_dir_rescan_delay = 0;
@@ -66,7 +65,6 @@ init_tarantool_cfg(tarantool_cfg *c) {
 	c->memcached_expire_per_loop = 0;
 	c->memcached_expire_full_sweep = 0;
 	c->replication_source = NULL;
-	c->space = NULL;
 }
 
 int
@@ -100,7 +98,6 @@ fill_default_tarantool_cfg(tarantool_cfg *c) {
 	c->readahead = 16320;
 	c->snap_io_rate_limit = 0;
 	c->rows_per_wal = 500000;
-	c->wal_writer_inbox_size = 16384;
 	c->wal_mode = strdup("fsync_delay");
 	if (c->wal_mode == NULL) return CNF_NOMEMORY;
 	c->wal_fsync_delay = 0;
@@ -117,7 +114,6 @@ fill_default_tarantool_cfg(tarantool_cfg *c) {
 	c->memcached_expire_per_loop = 1024;
 	c->memcached_expire_full_sweep = 3600;
 	c->replication_source = NULL;
-	c->space = NULL;
 	return 0;
 }
 
@@ -128,32 +124,6 @@ swap_tarantool_cfg(struct tarantool_cfg *c1, struct tarantool_cfg *c2) {
 	*c2 = tmpcfg;
 }
 
-static int
-acceptDefault_name__space(tarantool_cfg_space *c) {
-	c->enabled = -1;
-	c->arity = -1;
-	c->estimated_rows = 0;
-	c->index = NULL;
-	return 0;
-}
-
-static int
-acceptDefault_name__space__index(tarantool_cfg_space_index *c) {
-	c->type = strdup("");
-	if (c->type == NULL) return CNF_NOMEMORY;
-	c->unique = -1;
-	c->key_field = NULL;
-	return 0;
-}
-
-static int
-acceptDefault_name__space__index__key_field(tarantool_cfg_space_index_key_field *c) {
-	c->fieldno = -1;
-	c->type = strdup("");
-	if (c->type == NULL) return CNF_NOMEMORY;
-	return 0;
-}
-
 static NameAtom _name__username[] = {
 	{ "username", -1, NULL }
 };
@@ -220,9 +190,6 @@ static NameAtom _name__snap_io_rate_limit[] = {
 static NameAtom _name__rows_per_wal[] = {
 	{ "rows_per_wal", -1, NULL }
 };
-static NameAtom _name__wal_writer_inbox_size[] = {
-	{ "wal_writer_inbox_size", -1, NULL }
-};
 static NameAtom _name__wal_mode[] = {
 	{ "wal_mode", -1, NULL }
 };
@@ -268,52 +235,6 @@ static NameAtom _name__memcached_expire_full_sweep[] = {
 static NameAtom _name__replication_source[] = {
 	{ "replication_source", -1, NULL }
 };
-static NameAtom _name__space[] = {
-	{ "space", -1, NULL }
-};
-static NameAtom _name__space__enabled[] = {
-	{ "space", -1, _name__space__enabled + 1 },
-	{ "enabled", -1, NULL }
-};
-static NameAtom _name__space__arity[] = {
-	{ "space", -1, _name__space__arity + 1 },
-	{ "arity", -1, NULL }
-};
-static NameAtom _name__space__estimated_rows[] = {
-	{ "space", -1, _name__space__estimated_rows + 1 },
-	{ "estimated_rows", -1, NULL }
-};
-static NameAtom _name__space__index[] = {
-	{ "space", -1, _name__space__index + 1 },
-	{ "index", -1, NULL }
-};
-static NameAtom _name__space__index__type[] = {
-	{ "space", -1, _name__space__index__type + 1 },
-	{ "index", -1, _name__space__index__type + 2 },
-	{ "type", -1, NULL }
-};
-static NameAtom _name__space__index__unique[] = {
-	{ "space", -1, _name__space__index__unique + 1 },
-	{ "index", -1, _name__space__index__unique + 2 },
-	{ "unique", -1, NULL }
-};
-static NameAtom _name__space__index__key_field[] = {
-	{ "space", -1, _name__space__index__key_field + 1 },
-	{ "index", -1, _name__space__index__key_field + 2 },
-	{ "key_field", -1, NULL }
-};
-static NameAtom _name__space__index__key_field__fieldno[] = {
-	{ "space", -1, _name__space__index__key_field__fieldno + 1 },
-	{ "index", -1, _name__space__index__key_field__fieldno + 2 },
-	{ "key_field", -1, _name__space__index__key_field__fieldno + 3 },
-	{ "fieldno", -1, NULL }
-};
-static NameAtom _name__space__index__key_field__type[] = {
-	{ "space", -1, _name__space__index__key_field__type + 1 },
-	{ "index", -1, _name__space__index__key_field__type + 2 },
-	{ "key_field", -1, _name__space__index__key_field__type + 3 },
-	{ "type", -1, NULL }
-};
 
 #define ARRAYALLOC(x,n,t,_chk_ro, __flags)  do {                    \
    int l = 0, ar;                                                   \
@@ -655,20 +576,6 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 			return CNF_RDONLY;
 		c->rows_per_wal = i32;
 	}
-	else if ( cmpNameAtoms( opt->name, _name__wal_writer_inbox_size) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		c->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		long int i32 = strtol(opt->paramValue.scalarval, NULL, 10);
-		if (i32 == 0 && errno == EINVAL)
-			return CNF_WRONGINT;
-		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
-			return CNF_WRONGRANGE;
-		if (check_rdonly && c->wal_writer_inbox_size != i32)
-			return CNF_RDONLY;
-		c->wal_writer_inbox_size = i32;
-	}
 	else if ( cmpNameAtoms( opt->name, _name__wal_mode) ) {
 		if (opt->paramType != scalarType )
 			return CNF_WRONGTYPE;
@@ -886,201 +793,6 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 		if (opt->paramValue.scalarval && c->replication_source == NULL)
 			return CNF_NOMEMORY;
 	}
-	else if ( cmpNameAtoms( opt->name, _name__space) ) {
-		if (opt->paramType != arrayType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__enabled) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		bool bln;
-
-		if (strcasecmp(opt->paramValue.scalarval, "true") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "yes") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "enable") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "on") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "1") == 0 )
-			bln = true;
-		else if (strcasecmp(opt->paramValue.scalarval, "false") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "no") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "disable") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "off") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "0") == 0 )
-			bln = false;
-		else
-			return CNF_WRONGRANGE;
-		if (check_rdonly && c->space[opt->name->index]->enabled != bln)
-			return CNF_RDONLY;
-		c->space[opt->name->index]->enabled = bln;
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__arity) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		long int i32 = strtol(opt->paramValue.scalarval, NULL, 10);
-		if (i32 == 0 && errno == EINVAL)
-			return CNF_WRONGINT;
-		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
-			return CNF_WRONGRANGE;
-		if (check_rdonly && c->space[opt->name->index]->arity != i32)
-			return CNF_RDONLY;
-		c->space[opt->name->index]->arity = i32;
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__estimated_rows) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		long int i32 = strtol(opt->paramValue.scalarval, NULL, 10);
-		if (i32 == 0 && errno == EINVAL)
-			return CNF_WRONGINT;
-		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
-			return CNF_WRONGRANGE;
-		if (check_rdonly && c->space[opt->name->index]->estimated_rows != i32)
-			return CNF_RDONLY;
-		c->space[opt->name->index]->estimated_rows = i32;
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__index) ) {
-		if (opt->paramType != arrayType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index, 1, _name__space__index, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__index__type) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index, opt->name->next->index + 1, _name__space__index, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		if (check_rdonly && ( (opt->paramValue.scalarval == NULL && c->space[opt->name->index]->index[opt->name->next->index]->type == NULL) || strcmp(opt->paramValue.scalarval, c->space[opt->name->index]->index[opt->name->next->index]->type) != 0))
-			return CNF_RDONLY;
-		 if (c->space[opt->name->index]->index[opt->name->next->index]->type) free(c->space[opt->name->index]->index[opt->name->next->index]->type);
-		c->space[opt->name->index]->index[opt->name->next->index]->type = (opt->paramValue.scalarval) ? strdup(opt->paramValue.scalarval) : NULL;
-		if (opt->paramValue.scalarval && c->space[opt->name->index]->index[opt->name->next->index]->type == NULL)
-			return CNF_NOMEMORY;
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__index__unique) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index, opt->name->next->index + 1, _name__space__index, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		bool bln;
-
-		if (strcasecmp(opt->paramValue.scalarval, "true") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "yes") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "enable") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "on") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "1") == 0 )
-			bln = true;
-		else if (strcasecmp(opt->paramValue.scalarval, "false") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "no") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "disable") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "off") == 0 ||
-				strcasecmp(opt->paramValue.scalarval, "0") == 0 )
-			bln = false;
-		else
-			return CNF_WRONGRANGE;
-		if (check_rdonly && c->space[opt->name->index]->index[opt->name->next->index]->unique != bln)
-			return CNF_RDONLY;
-		c->space[opt->name->index]->index[opt->name->next->index]->unique = bln;
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__index__key_field) ) {
-		if (opt->paramType != arrayType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index, opt->name->next->index + 1, _name__space__index, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index[opt->name->next->index]->key_field, 1, _name__space__index__key_field, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__index__key_field__fieldno) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index, opt->name->next->index + 1, _name__space__index, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index[opt->name->next->index]->key_field, opt->name->next->next->index + 1, _name__space__index__key_field, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		long int i32 = strtol(opt->paramValue.scalarval, NULL, 10);
-		if (i32 == 0 && errno == EINVAL)
-			return CNF_WRONGINT;
-		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
-			return CNF_WRONGRANGE;
-		if (check_rdonly && c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->fieldno != i32)
-			return CNF_RDONLY;
-		c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->fieldno = i32;
-	}
-	else if ( cmpNameAtoms( opt->name, _name__space__index__key_field__type) ) {
-		if (opt->paramType != scalarType )
-			return CNF_WRONGTYPE;
-		ARRAYALLOC(c->space, opt->name->index + 1, _name__space, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index, opt->name->next->index + 1, _name__space__index, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->index[opt->name->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		ARRAYALLOC(c->space[opt->name->index]->index[opt->name->next->index]->key_field, opt->name->next->next->index + 1, _name__space__index__key_field, check_rdonly, CNF_FLAG_STRUCT_NEW | CNF_FLAG_STRUCT_NOTSET);
-		if (c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->__confetti_flags & CNF_FLAG_STRUCT_NEW)
-			check_rdonly = 0;
-		c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NOTSET;
-		errno = 0;
-		if (check_rdonly && ( (opt->paramValue.scalarval == NULL && c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type == NULL) || strcmp(opt->paramValue.scalarval, c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type) != 0))
-			return CNF_RDONLY;
-		 if (c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type) free(c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type);
-		c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type = (opt->paramValue.scalarval) ? strdup(opt->paramValue.scalarval) : NULL;
-		if (opt->paramValue.scalarval && c->space[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type == NULL)
-			return CNF_NOMEMORY;
-	}
 	else {
 		return opt->optional ? CNF_OPTIONAL : CNF_MISSED;
 	}
@@ -1219,7 +931,6 @@ typedef enum IteratorState {
 	S_name__readahead,
 	S_name__snap_io_rate_limit,
 	S_name__rows_per_wal,
-	S_name__wal_writer_inbox_size,
 	S_name__wal_mode,
 	S_name__wal_fsync_delay,
 	S_name__wal_dir_rescan_delay,
@@ -1235,24 +946,11 @@ typedef enum IteratorState {
 	S_name__memcached_expire_per_loop,
 	S_name__memcached_expire_full_sweep,
 	S_name__replication_source,
-	S_name__space,
-	S_name__space__enabled,
-	S_name__space__arity,
-	S_name__space__estimated_rows,
-	S_name__space__index,
-	S_name__space__index__type,
-	S_name__space__index__unique,
-	S_name__space__index__key_field,
-	S_name__space__index__key_field__fieldno,
-	S_name__space__index__key_field__type,
 	_S_Finished
 } IteratorState;
 
 struct tarantool_cfg_iterator_t {
 	IteratorState	state;
-	int	idx_name__space;
-	int	idx_name__space__index;
-	int	idx_name__space__index__key_field;
 };
 
 tarantool_cfg_iterator_t*
@@ -1504,17 +1202,6 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 			}
 			sprintf(*v, "%"PRId32, c->rows_per_wal);
 			snprintf(buf, PRINTBUFLEN-1, "rows_per_wal");
-			i->state = S_name__wal_writer_inbox_size;
-			return buf;
-		case S_name__wal_writer_inbox_size:
-			*v = malloc(32);
-			if (*v == NULL) {
-				free(i);
-				out_warning(CNF_NOMEMORY, "No memory to output value");
-				return NULL;
-			}
-			sprintf(*v, "%"PRId32, c->wal_writer_inbox_size);
-			snprintf(buf, PRINTBUFLEN-1, "wal_writer_inbox_size");
 			i->state = S_name__wal_mode;
 			return buf;
 		case S_name__wal_mode:
@@ -1677,140 +1364,8 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 				return NULL;
 			}
 			snprintf(buf, PRINTBUFLEN-1, "replication_source");
-			i->state = S_name__space;
+			i->state = _S_Finished;
 			return buf;
-		case S_name__space:
-			i->state = S_name__space;
-		case S_name__space__enabled:
-		case S_name__space__arity:
-		case S_name__space__estimated_rows:
-		case S_name__space__index:
-		case S_name__space__index__type:
-		case S_name__space__index__unique:
-		case S_name__space__index__key_field:
-		case S_name__space__index__key_field__fieldno:
-		case S_name__space__index__key_field__type:
-			if (c->space && c->space[i->idx_name__space]) {
-				switch(i->state) {
-					case S_name__space:
-					case S_name__space__enabled:
-						*v = malloc(8);
-						if (*v == NULL) {
-							free(i);
-							out_warning(CNF_NOMEMORY, "No memory to output value");
-							return NULL;
-						}
-						sprintf(*v, "%s", c->space[i->idx_name__space]->enabled == -1 ? "false" : c->space[i->idx_name__space]->enabled ? "true" : "false");
-						snprintf(buf, PRINTBUFLEN-1, "space[%d].enabled", i->idx_name__space);
-						i->state = S_name__space__arity;
-						return buf;
-					case S_name__space__arity:
-						*v = malloc(32);
-						if (*v == NULL) {
-							free(i);
-							out_warning(CNF_NOMEMORY, "No memory to output value");
-							return NULL;
-						}
-						sprintf(*v, "%"PRId32, c->space[i->idx_name__space]->arity);
-						snprintf(buf, PRINTBUFLEN-1, "space[%d].arity", i->idx_name__space);
-						i->state = S_name__space__estimated_rows;
-						return buf;
-					case S_name__space__estimated_rows:
-						*v = malloc(32);
-						if (*v == NULL) {
-							free(i);
-							out_warning(CNF_NOMEMORY, "No memory to output value");
-							return NULL;
-						}
-						sprintf(*v, "%"PRId32, c->space[i->idx_name__space]->estimated_rows);
-						snprintf(buf, PRINTBUFLEN-1, "space[%d].estimated_rows", i->idx_name__space);
-						i->state = S_name__space__index;
-						return buf;
-					case S_name__space__index:
-						i->state = S_name__space__index;
-					case S_name__space__index__type:
-					case S_name__space__index__unique:
-					case S_name__space__index__key_field:
-					case S_name__space__index__key_field__fieldno:
-					case S_name__space__index__key_field__type:
-						if (c->space[i->idx_name__space]->index && c->space[i->idx_name__space]->index[i->idx_name__space__index]) {
-							switch(i->state) {
-								case S_name__space__index:
-								case S_name__space__index__type:
-									*v = (c->space[i->idx_name__space]->index[i->idx_name__space__index]->type) ? strdup(c->space[i->idx_name__space]->index[i->idx_name__space__index]->type) : NULL;
-									if (*v == NULL && c->space[i->idx_name__space]->index[i->idx_name__space__index]->type) {
-										free(i);
-										out_warning(CNF_NOMEMORY, "No memory to output value");
-										return NULL;
-									}
-									snprintf(buf, PRINTBUFLEN-1, "space[%d].index[%d].type", i->idx_name__space, i->idx_name__space__index);
-									i->state = S_name__space__index__unique;
-									return buf;
-								case S_name__space__index__unique:
-									*v = malloc(8);
-									if (*v == NULL) {
-										free(i);
-										out_warning(CNF_NOMEMORY, "No memory to output value");
-										return NULL;
-									}
-									sprintf(*v, "%s", c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique == -1 ? "false" : c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique ? "true" : "false");
-									snprintf(buf, PRINTBUFLEN-1, "space[%d].index[%d].unique", i->idx_name__space, i->idx_name__space__index);
-									i->state = S_name__space__index__key_field;
-									return buf;
-								case S_name__space__index__key_field:
-									i->state = S_name__space__index__key_field;
-								case S_name__space__index__key_field__fieldno:
-								case S_name__space__index__key_field__type:
-									if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field && c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]) {
-										switch(i->state) {
-											case S_name__space__index__key_field:
-											case S_name__space__index__key_field__fieldno:
-												*v = malloc(32);
-												if (*v == NULL) {
-													free(i);
-													out_warning(CNF_NOMEMORY, "No memory to output value");
-													return NULL;
-												}
-												sprintf(*v, "%"PRId32, c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->fieldno);
-												snprintf(buf, PRINTBUFLEN-1, "space[%d].index[%d].key_field[%d].fieldno", i->idx_name__space, i->idx_name__space__index, i->idx_name__space__index__key_field);
-												i->state = S_name__space__index__key_field__type;
-												return buf;
-											case S_name__space__index__key_field__type:
-												*v = (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type) ? strdup(c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type) : NULL;
-												if (*v == NULL && c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type) {
-													free(i);
-													out_warning(CNF_NOMEMORY, "No memory to output value");
-													return NULL;
-												}
-												snprintf(buf, PRINTBUFLEN-1, "space[%d].index[%d].key_field[%d].type", i->idx_name__space, i->idx_name__space__index, i->idx_name__space__index__key_field);
-												i->state = S_name__space__index__key_field;
-												i->idx_name__space__index__key_field++;
-												return buf;
-											default:
-												break;
-										}
-									}
-									else {
-										i->state = S_name__space__index;
-										i->idx_name__space__index++;
-										i->idx_name__space__index__key_field = 0;
-										goto again;
-									}
-								default:
-									break;
-							}
-						}
-						else {
-							i->state = S_name__space;
-							i->idx_name__space++;
-							i->idx_name__space__index = 0;
-							i->idx_name__space__index__key_field = 0;
-							goto again;
-						}
-					default:
-						break;
-				}
-			}
 		case _S_Finished:
 			free(i);
 			break;
@@ -1824,136 +1379,24 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 /************** Checking of required fields  **************/
 int
 check_cfg_tarantool_cfg(tarantool_cfg *c) {
-	tarantool_cfg_iterator_t iterator, *i = &iterator;
 	int	res = 0;
 
 	if (c->primary_port == 0) {
 		res++;
 		out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__primary_port));
 	}
-	i->idx_name__space = 0;
-	while (c->space && c->space[i->idx_name__space]) {
-		if (c->space[i->idx_name__space]->__confetti_flags & CNF_FLAG_STRUCT_NOTSET) {
-			(void)0;
-		} else {
-			if (c->space[i->idx_name__space]->enabled == -1) {
-				res++;
-				_name__space__enabled->index = i->idx_name__space;
-				out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__space__enabled));
-			}
-			if (c->space[i->idx_name__space]->index == NULL) {
-				res++;
-				_name__space__index->index = i->idx_name__space;
-				out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__space__index));
-			}
-			i->idx_name__space__index = 0;
-			while (c->space[i->idx_name__space]->index && c->space[i->idx_name__space]->index[i->idx_name__space__index]) {
-				if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->__confetti_flags & CNF_FLAG_STRUCT_NOTSET) {
-					res++;
-					_name__space__index->next->index = i->idx_name__space__index;
-					_name__space__index->index = i->idx_name__space;
-					out_warning(CNF_NOTSET, "Option '%s' is not set", dumpOptDef(_name__space__index));
-				} else {
-					if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->type != NULL && strcmp(c->space[i->idx_name__space]->index[i->idx_name__space__index]->type, "") == 0) {
-						res++;
-						_name__space__index__type->next->index = i->idx_name__space__index;
-						_name__space__index__type->index = i->idx_name__space;
-						out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__space__index__type));
-					}
-					if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->unique == -1) {
-						res++;
-						_name__space__index__unique->next->index = i->idx_name__space__index;
-						_name__space__index__unique->index = i->idx_name__space;
-						out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__space__index__unique));
-					}
-					if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field == NULL) {
-						res++;
-						_name__space__index__key_field->next->index = i->idx_name__space__index;
-						_name__space__index__key_field->index = i->idx_name__space;
-						out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__space__index__key_field));
-					}
-					i->idx_name__space__index__key_field = 0;
-					while (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field && c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]) {
-						if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->__confetti_flags & CNF_FLAG_STRUCT_NOTSET) {
-							res++;
-							_name__space__index__key_field->next->next->index = i->idx_name__space__index__key_field;
-							_name__space__index__key_field->next->index = i->idx_name__space__index;
-							_name__space__index__key_field->index = i->idx_name__space;
-							out_warning(CNF_NOTSET, "Option '%s' is not set", dumpOptDef(_name__space__index__key_field));
-						} else {
-							if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->fieldno == -1) {
-								res++;
-								_name__space__index__key_field__fieldno->next->next->index = i->idx_name__space__index__key_field;
-								_name__space__index__key_field__fieldno->next->index = i->idx_name__space__index;
-								_name__space__index__key_field__fieldno->index = i->idx_name__space;
-								out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__space__index__key_field__fieldno));
-							}
-							if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type != NULL && strcmp(c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type, "") == 0) {
-								res++;
-								_name__space__index__key_field__type->next->next->index = i->idx_name__space__index__key_field;
-								_name__space__index__key_field__type->next->index = i->idx_name__space__index;
-								_name__space__index__key_field__type->index = i->idx_name__space;
-								out_warning(CNF_NOTSET, "Option '%s' is not set (or has a default value)", dumpOptDef(_name__space__index__key_field__type));
-							}
-						}
-
-						i->idx_name__space__index__key_field++;
-					}
-
-				}
-
-				i->idx_name__space__index++;
-			}
-
-		}
-
-		i->idx_name__space++;
-	}
-
 	return res;
 }
 
 static void
-cleanFlags(tarantool_cfg* c, OptDef* opt) {
-	tarantool_cfg_iterator_t iterator, *i = &iterator;
-
-
-	if (c->space != NULL) {
-		i->idx_name__space = 0;
-		while (c->space[i->idx_name__space] != NULL) {
-			c->space[i->idx_name__space]->__confetti_flags &= ~CNF_FLAG_STRUCT_NEW;
-
-
-			if (c->space[i->idx_name__space]->index != NULL) {
-				i->idx_name__space__index = 0;
-				while (c->space[i->idx_name__space]->index[i->idx_name__space__index] != NULL) {
-					c->space[i->idx_name__space]->index[i->idx_name__space__index]->__confetti_flags &= ~CNF_FLAG_STRUCT_NEW;
+cleanFlags(tarantool_cfg* c __attribute__((unused)), OptDef* opt __attribute__((unused))) {
 
-
-					if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field != NULL) {
-						i->idx_name__space__index__key_field = 0;
-						while (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field] != NULL) {
-							c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->__confetti_flags &= ~CNF_FLAG_STRUCT_NEW;
-
-
-							i->idx_name__space__index__key_field++;
-						}
-					}
-
-					i->idx_name__space__index++;
-				}
-			}
-
-			i->idx_name__space++;
-		}
-	}
 }
 
 /************** Duplicate config  **************/
 
 int
 dup_tarantool_cfg(tarantool_cfg* dst, tarantool_cfg* src) {
-	tarantool_cfg_iterator_t iterator, *i = &iterator;
 
 	if (dst->username) free(dst->username);dst->username = src->username == NULL ? NULL : strdup(src->username);
 	if (src->username != NULL && dst->username == NULL)
@@ -1993,7 +1436,6 @@ dup_tarantool_cfg(tarantool_cfg* dst, tarantool_cfg* src) {
 	dst->readahead = src->readahead;
 	dst->snap_io_rate_limit = src->snap_io_rate_limit;
 	dst->rows_per_wal = src->rows_per_wal;
-	dst->wal_writer_inbox_size = src->wal_writer_inbox_size;
 	if (dst->wal_mode) free(dst->wal_mode);dst->wal_mode = src->wal_mode == NULL ? NULL : strdup(src->wal_mode);
 	if (src->wal_mode != NULL && dst->wal_mode == NULL)
 		return CNF_NOMEMORY;
@@ -2016,59 +1458,6 @@ dup_tarantool_cfg(tarantool_cfg* dst, tarantool_cfg* src) {
 	if (src->replication_source != NULL && dst->replication_source == NULL)
 		return CNF_NOMEMORY;
 
-	dst->space = NULL;
-	if (src->space != NULL) {
-		i->idx_name__space = 0;
-		ARRAYALLOC(dst->space, 1, _name__space, 0, 0);
-
-		while (src->space[i->idx_name__space] != NULL) {
-			ARRAYALLOC(dst->space, i->idx_name__space + 1, _name__space, 0, 0);
-
-			dst->space[i->idx_name__space]->__confetti_flags = src->space[i->idx_name__space]->__confetti_flags;
-			dst->space[i->idx_name__space]->enabled = src->space[i->idx_name__space]->enabled;
-			dst->space[i->idx_name__space]->arity = src->space[i->idx_name__space]->arity;
-			dst->space[i->idx_name__space]->estimated_rows = src->space[i->idx_name__space]->estimated_rows;
-
-			dst->space[i->idx_name__space]->index = NULL;
-			if (src->space[i->idx_name__space]->index != NULL) {
-				i->idx_name__space__index = 0;
-				ARRAYALLOC(dst->space[i->idx_name__space]->index, 1, _name__space__index, 0, 0);
-
-				while (src->space[i->idx_name__space]->index[i->idx_name__space__index] != NULL) {
-					ARRAYALLOC(dst->space[i->idx_name__space]->index, i->idx_name__space__index + 1, _name__space__index, 0, 0);
-
-					dst->space[i->idx_name__space]->index[i->idx_name__space__index]->__confetti_flags = src->space[i->idx_name__space]->index[i->idx_name__space__index]->__confetti_flags;
-					if (dst->space[i->idx_name__space]->index[i->idx_name__space__index]->type) free(dst->space[i->idx_name__space]->index[i->idx_name__space__index]->type);dst->space[i->idx_name__space]->index[i->idx_name__space__index]->type = src->space[i->idx_name__space]->index[i->idx_name__space__index]->type == NULL ? NULL : strdup(src->space[i->idx_name__space]->index[i->idx_name__space__index]->type);
-					if (src->space[i->idx_name__space]->index[i->idx_name__space__index]->type != NULL && dst->space[i->idx_name__space]->index[i->idx_name__space__index]->type == NULL)
-						return CNF_NOMEMORY;
-					dst->space[i->idx_name__space]->index[i->idx_name__space__index]->unique = src->space[i->idx_name__space]->index[i->idx_name__space__index]->unique;
-
-					dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field = NULL;
-					if (src->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field != NULL) {
-						i->idx_name__space__index__key_field = 0;
-						ARRAYALLOC(dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field, 1, _name__space__index__key_field, 0, 0);
-
-						while (src->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field] != NULL) {
-							ARRAYALLOC(dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field, i->idx_name__space__index__key_field + 1, _name__space__index__key_field, 0, 0);
-
-							dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->__confetti_flags = src->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->__confetti_flags;
-							dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->fieldno = src->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->fieldno;
-							if (dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type) free(dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type);dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type = src->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type == NULL ? NULL : strdup(src->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type);
-							if (src->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type != NULL && dst->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type == NULL)
-								return CNF_NOMEMORY;
-
-							i->idx_name__space__index__key_field++;
-						}
-					}
-
-					i->idx_name__space__index++;
-				}
-			}
-
-			i->idx_name__space++;
-		}
-	}
-
 	return CNF_OK;
 }
 
@@ -2076,7 +1465,6 @@ dup_tarantool_cfg(tarantool_cfg* dst, tarantool_cfg* src) {
 
 void
 destroy_tarantool_cfg(tarantool_cfg* c) {
-	tarantool_cfg_iterator_t iterator, *i = &iterator;
 
 	if (c->username != NULL)
 		free(c->username);
@@ -2100,46 +1488,6 @@ destroy_tarantool_cfg(tarantool_cfg* c) {
 		free(c->custom_proc_title);
 	if (c->replication_source != NULL)
 		free(c->replication_source);
-
-	if (c->space != NULL) {
-		i->idx_name__space = 0;
-		while (c->space[i->idx_name__space] != NULL) {
-
-			if (c->space[i->idx_name__space]->index != NULL) {
-				i->idx_name__space__index = 0;
-				while (c->space[i->idx_name__space]->index[i->idx_name__space__index] != NULL) {
-					if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->type != NULL)
-						free(c->space[i->idx_name__space]->index[i->idx_name__space__index]->type);
-
-					if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field != NULL) {
-						i->idx_name__space__index__key_field = 0;
-						while (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field] != NULL) {
-							if (c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type != NULL)
-								free(c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]->type);
-
-							free(c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field[i->idx_name__space__index__key_field]);
-
-							i->idx_name__space__index__key_field++;
-						}
-
-						free(c->space[i->idx_name__space]->index[i->idx_name__space__index]->key_field);
-					}
-
-					free(c->space[i->idx_name__space]->index[i->idx_name__space__index]);
-
-					i->idx_name__space__index++;
-				}
-
-				free(c->space[i->idx_name__space]->index);
-			}
-
-			free(c->space[i->idx_name__space]);
-
-			i->idx_name__space++;
-		}
-
-		free(c->space);
-	}
 }
 
 /************** Compare config  **************/
@@ -2158,7 +1506,6 @@ confetti_strcmp(char *s1, char *s2) {
 
 char *
 cmp_tarantool_cfg(tarantool_cfg* c1, tarantool_cfg* c2, int only_check_rdonly) {
-	tarantool_cfg_iterator_t iterator1, iterator2, *i1 = &iterator1, *i2 = &iterator2;
 	static char diff[PRINTBUFLEN];
 
 	if (confetti_strcmp(c1->username, c2->username) != 0) {
@@ -2279,11 +1626,6 @@ cmp_tarantool_cfg(tarantool_cfg* c1, tarantool_cfg* c2, int only_check_rdonly) {
 
 		return diff;
 	}
-	if (c1->wal_writer_inbox_size != c2->wal_writer_inbox_size) {
-		snprintf(diff, PRINTBUFLEN - 1, "%s", "c->wal_writer_inbox_size");
-
-		return diff;
-	}
 	if (!only_check_rdonly) {
 		if (confetti_strcmp(c1->wal_mode, c2->wal_mode) != 0) {
 			snprintf(diff, PRINTBUFLEN - 1, "%s", "c->wal_mode");
@@ -2372,80 +1714,6 @@ cmp_tarantool_cfg(tarantool_cfg* c1, tarantool_cfg* c2, int only_check_rdonly) {
 }
 	}
 
-	i1->idx_name__space = 0;
-	i2->idx_name__space = 0;
-	while (c1->space != NULL && c1->space[i1->idx_name__space] != NULL && c2->space != NULL && c2->space[i2->idx_name__space] != NULL) {
-		if (c1->space[i1->idx_name__space]->enabled != c2->space[i2->idx_name__space]->enabled) {
-			snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->enabled");
-
-			return diff;
-		}
-		if (c1->space[i1->idx_name__space]->arity != c2->space[i2->idx_name__space]->arity) {
-			snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->arity");
-
-			return diff;
-		}
-		if (c1->space[i1->idx_name__space]->estimated_rows != c2->space[i2->idx_name__space]->estimated_rows) {
-			snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->estimated_rows");
-
-			return diff;
-		}
-
-		i1->idx_name__space__index = 0;
-		i2->idx_name__space__index = 0;
-		while (c1->space[i1->idx_name__space]->index != NULL && c1->space[i1->idx_name__space]->index[i1->idx_name__space__index] != NULL && c2->space[i2->idx_name__space]->index != NULL && c2->space[i2->idx_name__space]->index[i2->idx_name__space__index] != NULL) {
-			if (confetti_strcmp(c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->type, c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->type) != 0) {
-				snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->index[]->type");
-
-				return diff;
-}
-			if (c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->unique != c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->unique) {
-				snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->index[]->unique");
-
-				return diff;
-			}
-
-			i1->idx_name__space__index__key_field = 0;
-			i2->idx_name__space__index__key_field = 0;
-			while (c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field != NULL && c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field[i1->idx_name__space__index__key_field] != NULL && c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->key_field != NULL && c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->key_field[i2->idx_name__space__index__key_field] != NULL) {
-				if (c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field[i1->idx_name__space__index__key_field]->fieldno != c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->key_field[i2->idx_name__space__index__key_field]->fieldno) {
-					snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->index[]->key_field[]->fieldno");
-
-					return diff;
-				}
-				if (confetti_strcmp(c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field[i1->idx_name__space__index__key_field]->type, c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->key_field[i2->idx_name__space__index__key_field]->type) != 0) {
-					snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->index[]->key_field[]->type");
-
-					return diff;
-}
-
-				i1->idx_name__space__index__key_field++;
-				i2->idx_name__space__index__key_field++;
-			}
-			if (!(c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field == c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->key_field && c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field == NULL) && (c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field == NULL || c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->key_field == NULL || c1->space[i1->idx_name__space]->index[i1->idx_name__space__index]->key_field[i1->idx_name__space__index__key_field] != NULL || c2->space[i2->idx_name__space]->index[i2->idx_name__space__index]->key_field[i2->idx_name__space__index__key_field] != NULL)) {
-				snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->index[]->key_field[]");
-
-				return diff;
-			}
-
-			i1->idx_name__space__index++;
-			i2->idx_name__space__index++;
-		}
-		if (!(c1->space[i1->idx_name__space]->index == c2->space[i2->idx_name__space]->index && c1->space[i1->idx_name__space]->index == NULL) && (c1->space[i1->idx_name__space]->index == NULL || c2->space[i2->idx_name__space]->index == NULL || c1->space[i1->idx_name__space]->index[i1->idx_name__space__index] != NULL || c2->space[i2->idx_name__space]->index[i2->idx_name__space__index] != NULL)) {
-			snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]->index[]");
-
-			return diff;
-		}
-
-		i1->idx_name__space++;
-		i2->idx_name__space++;
-	}
-	if (!(c1->space == c2->space && c1->space == NULL) && (c1->space == NULL || c2->space == NULL || c1->space[i1->idx_name__space] != NULL || c2->space[i2->idx_name__space] != NULL)) {
-		snprintf(diff, PRINTBUFLEN - 1, "%s", "c->space[]");
-
-		return diff;
-	}
-
 	return 0;
 }
 
diff --git a/cfg/tarantool_box_cfg.h b/cfg/tarantool_box_cfg.h
index 2f698156450b4882a88b805498a0e07f77f2d8cb..39cbd4111db93ddc5a40cae4d18c9e8a02aa615e 100644
--- a/cfg/tarantool_box_cfg.h
+++ b/cfg/tarantool_box_cfg.h
@@ -13,30 +13,6 @@
  * Autogenerated file, do not edit it!
  */
 
-typedef struct tarantool_cfg_space_index_key_field {
-	unsigned char __confetti_flags;
-
-	int32_t	fieldno;
-	char*	type;
-} tarantool_cfg_space_index_key_field;
-
-typedef struct tarantool_cfg_space_index {
-	unsigned char __confetti_flags;
-
-	char*	type;
-	confetti_bool_t	unique;
-	tarantool_cfg_space_index_key_field**	key_field;
-} tarantool_cfg_space_index;
-
-typedef struct tarantool_cfg_space {
-	unsigned char __confetti_flags;
-
-	confetti_bool_t	enabled;
-	int32_t	arity;
-	int32_t	estimated_rows;
-	tarantool_cfg_space_index**	index;
-} tarantool_cfg_space;
-
 typedef struct tarantool_cfg {
 	unsigned char __confetti_flags;
 
@@ -126,12 +102,6 @@ typedef struct tarantool_cfg {
 	/* Write no more rows in WAL */
 	int32_t	rows_per_wal;
 
-	/*
-	 * OBSOLETE
-	 * Starting from 1.4.5, this variable has no effect.
-	 */
-	int32_t	wal_writer_inbox_size;
-
 	/*
 	 * Defines fiber/data synchronization fsync(2) policy:
 	 * "none":           does not write to WAL
@@ -204,7 +174,6 @@ typedef struct tarantool_cfg {
 	 * only accepts reads.
 	 */
 	char*	replication_source;
-	tarantool_cfg_space**	space;
 } tarantool_cfg;
 
 #ifndef CNF_FLAG_STRUCT_NEW
diff --git a/client/tarancheck/tc_generate.c b/client/tarancheck/tc_generate.c
index 214585db749f0f678ac8d434c6f3c2da97433c31..b478c4623fba552a5ad7bb2c40ebd91d78588d5c 100644
--- a/client/tarancheck/tc_generate.c
+++ b/client/tarancheck/tc_generate.c
@@ -439,11 +439,13 @@ int tc_generate(struct tc_options *opts)
 	int rc = tc_space_init(&s);
 	if (rc == -1)
 		return -1;
+#if 0
 	rc = tc_space_fill(&s, opts);
 	if (rc == -1) {
 		tc_space_free(&s);
 		return -1;
 	}
+#endif
 	printf("configured spaces: %d\n", mh_size(s.t));
 	printf("snap_dir: %s\n", opts->cfg.snap_dir);
 	printf("wal_dir: %s\n", opts->cfg.wal_dir);
diff --git a/client/tarancheck/tc_space.c b/client/tarancheck/tc_space.c
index 37a4e8ceb94708e86dce8d29865b32023df74a15..9635d6742b1f6c9b07704f65cfb68b60951c4241 100644
--- a/client/tarancheck/tc_space.c
+++ b/client/tarancheck/tc_space.c
@@ -122,6 +122,7 @@ tc_space_key_typeof(char *name)
 	return TC_SPACE_KEY_UNKNOWN;
 }
 
+#if 0
 static int
 tc_space_key_init(struct tc_space *s, tarantool_cfg_space *cs)
 {
@@ -196,3 +197,4 @@ int tc_space_fill(struct tc_spaces *s, struct tc_options *opts)
 	}
 	return 0;
 }
+#endif
diff --git a/client/tarancheck/tc_space.h b/client/tarancheck/tc_space.h
index 7c08701e04f249e1b31cecebcde997525908ec8b..6ec7ddea4a90f941645f480cf4b9f223356b84ac 100644
--- a/client/tarancheck/tc_space.h
+++ b/client/tarancheck/tc_space.h
@@ -35,6 +35,7 @@ void tc_space_free(struct tc_spaces *s);
 struct tc_space *tc_space_create(struct tc_spaces *s, uint32_t id);
 struct tc_space *tc_space_match(struct tc_spaces *s, uint32_t id);
 
+#if 0
 int tc_space_fill(struct tc_spaces *s, struct tc_options *opts);
-
+#endif
 #endif
diff --git a/client/tarancheck/tc_verify.c b/client/tarancheck/tc_verify.c
index 5ff94c6ded4cbe584d76ed50e4e3362c003522bc..cfa8c7101154fc8748efb28002261585a60a54b3 100644
--- a/client/tarancheck/tc_verify.c
+++ b/client/tarancheck/tc_verify.c
@@ -186,10 +186,12 @@ int tc_verify(struct tc_options *opts)
 	int rc = tc_space_init(&ss);
 	if (rc == -1)
 		return -1;
+#if 0
 	rc = tc_space_fill(&ss, opts);
 	if (rc == -1)
 		goto error;
 
+#endif
 	/* 2. load signature file */
 	uint64_t last_xlog_lsn = 0;
 	uint64_t last_snap_lsn = 0;
diff --git a/client/tarantar/main.c b/client/tarantar/main.c
index c2a5c5bb83a009262bd305c9050ac285f97fab83..76bb6fd317dc242237d20e250ec26c7810d7a32d 100644
--- a/client/tarantar/main.c
+++ b/client/tarantar/main.c
@@ -147,12 +147,14 @@ int main(int argc, char *argv[])
 		ts_options_free(&tss.opts);
 		return 1;
 	}
+#if 0
 	rc = ts_space_fill(&tss.s, &tss.opts);
 	if (rc == -1) {
 		ts_space_free(&tss.s);
 		ts_options_free(&tss.opts);
 		return 1;
 	}
+#endif
 
 	printf("snap_dir: %s\n", tss.opts.cfg.snap_dir);
 	printf("wal_dir:  %s\n", tss.opts.cfg.wal_dir);
diff --git a/client/tarantar/space.c b/client/tarantar/space.c
index 82ecbc9270b3cda3e68c63e17102d41888d99d75..7391dbfcbe38592b1be051545e4313888fd9a414 100644
--- a/client/tarantar/space.c
+++ b/client/tarantar/space.c
@@ -140,6 +140,7 @@ ts_space_key_typeof(char *name)
 	return TS_SPACE_KEY_UNKNOWN;
 }
 
+#if 0
 static int
 ts_space_key_init(struct ts_space *s, tarantool_cfg_space *cs)
 {
@@ -249,6 +250,7 @@ int ts_space_fill(struct ts_spaces *s, struct ts_options *opts)
 	}
 	return 0;
 }
+#endif
 
 static inline struct ts_key*
 ts_space_keyalloc_sha(struct ts_space *s, struct tnt_tuple *t, int fileid,
diff --git a/client/tarantar/space.h b/client/tarantar/space.h
index a338b9101c1af38195670ed3bb28b0bc6652792b..7ead69e40bc55863e279d1aa43f6e63558dca1a0 100644
--- a/client/tarantar/space.h
+++ b/client/tarantar/space.h
@@ -44,7 +44,9 @@ void ts_space_recycle(struct ts_spaces *s);
 struct ts_space *ts_space_create(struct ts_spaces *s, uint32_t id);
 struct ts_space *ts_space_match(struct ts_spaces *s, uint32_t id);
 
+#if 0
 int ts_space_fill(struct ts_spaces *s, struct ts_options *opts);
+#endif
 
 struct ts_key*
 ts_space_keyalloc(struct ts_space *s, struct tnt_tuple *t, int fileid,
diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake
index e1ca97b2791da6afb57cb9ea82b83edafb95296c..ccd9db5470c37f849f15363c642eeec676172d73 100644
--- a/cmake/compiler.cmake
+++ b/cmake/compiler.cmake
@@ -100,9 +100,6 @@ macro(enable_tnt_compile_flags)
         add_compile_flags("CXX" "-std=gnu++0x")
     endif()
 
-    # Disable Run-time type information
-    add_compile_flags("CXX" "-fno-rtti")
-
     add_compile_flags("C;CXX"
         "-Wall"
         "-Wextra"
diff --git a/doc/user/stored-procedures.xml b/doc/user/stored-procedures.xml
index 541b9a3d35af0fb8afc875be50c6c6d6b7f2c251..608d242c19bfc25830c5f65b75a32ec8cd8b66b6 100644
--- a/doc/user/stored-procedures.xml
+++ b/doc/user/stored-procedures.xml
@@ -1331,7 +1331,7 @@ lua box.cjson.decode('{"hello": "world"}').hello
             <emphasis role="lua"
             xml:id="box.space.select_reverse_range"
             xreflabel="box.space.select_reverse_range">
-            space:select_reverse_range(limit, key)</emphasis>
+            space:select_reverse_range(index_no, limit, key)</emphasis>
         </term>
         <listitem>
             <simpara>
diff --git a/extra/schema_erase.lua b/extra/schema_erase.lua
new file mode 100644
index 0000000000000000000000000000000000000000..eb8a1bf2d0ae08a5600e42b74b451da0f20eeca0
--- /dev/null
+++ b/extra/schema_erase.lua
@@ -0,0 +1,11 @@
+lua _schema = box.space[box.schema.SCHEMA_ID]
+lua _space = box.space[box.schema.SPACE_ID]
+lua _index = box.space[box.schema.INDEX_ID]
+-- destroy everything - save snapshot produces an empty snapshot now
+lua _schema:run_triggers(false)
+lua _schema:truncate()
+lua _space:run_triggers(false)
+lua _space:truncate()
+lua _index:run_triggers(false)
+lua _index:truncate()
+
diff --git a/extra/schema_fill.lua b/extra/schema_fill.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8e357085b8fcf78b842a7d0f53a8f5f04ad06675
--- /dev/null
+++ b/extra/schema_fill.lua
@@ -0,0 +1,20 @@
+lua _schema = box.space[box.schema.SCHEMA_ID]
+lua _space = box.space[box.schema.SPACE_ID]
+lua _index = box.space[box.schema.INDEX_ID]
+-- define schema version
+lua _schema:insert('version', 1, 6)
+-- define system spaces
+lua _space:insert(_schema.n, 0, '_schema')
+lua _space:insert(_space.n, 0, '_space')
+lua _space:insert(_index.n, 0, '_index')
+-- define indexes
+lua _index:insert(_schema.n, 0, 'primary', 'tree', 1, 1, 0, 'str')
+
+-- space name is unique
+lua _index:insert(_space.n, 0, 'primary', 'tree', 1, 1, 0, 'num')
+lua _index:insert(_space.n, 1, 'name', 'tree', 1, 1, 2, 'str')
+
+-- index name is unique within a space
+lua _index:insert(_index.n, 0, 'primary', 'tree', 1, 2, 0, 'num', 1, 'num')
+lua _index:insert(_index.n, 1, 'name', 'tree', 1, 2, 0, 'num', 2, 'str')
+-- 
diff --git a/include/errcode.h b/include/errcode.h
index c18e414a91852c5dc6ef11976744a71c75561e70..1af5bca5719130b24efa637be64092cd65b9cc67 100644
--- a/include/errcode.h
+++ b/include/errcode.h
@@ -55,10 +55,10 @@ enum { TNT_ERRMSG_MAX = 512 };
 	/*  2 */_(ER_ILLEGAL_PARAMS,		2, "Illegal parameters, %s") \
 	/*  3 */_(ER_SECONDARY,			2, "Can't modify data upon a request on the secondary port.") \
 	/*  4 */_(ER_TUPLE_IS_RO,		1, "Tuple is marked as read-only") \
-	/*  5 */_(ER_INDEX_TYPE,		2, "Unsupported index type: %s") \
+	/*  5 */_(ER_INDEX_TYPE,		2, "Unsupported index type supplied for index %u in space %u") \
 	/*  6 */_(ER_SPACE_EXISTS,		2, "Space %u already exists") \
 	/*  7 */_(ER_MEMORY_ISSUE,		1, "Failed to allocate %u bytes in %s for %s") \
-	/*  8 */_(ER_UNUSED8,			2, "Unused8") \
+	/*  8 */_(ER_CREATE_SPACE,		2, "Failed to create space %u: %s") \
 	/*  9 */_(ER_INJECTION,			2, "Error injection '%s'") \
 	/* 10 */_(ER_UNSUPPORTED,		2, "%s does not support %s") \
 		/* silverproxy error codes */ \
@@ -76,13 +76,13 @@ enum { TNT_ERRMSG_MAX = 512 };
 	/* 22 */_(ER_RESERVED22,		0, "Reserved22") \
 	/* 23 */_(ER_RESERVED23,		0, "Reserved23") \
 		/* end of silverproxy error codes */ \
-	/* 24 */_(ER_UNUSED24,			2, "Unused24") \
-	/* 25 */_(ER_UNUSED25,			2, "Unused25") \
+	/* 24 */_(ER_DROP_SPACE,		2, "Can't drop space %u: %s") \
+	/* 25 */_(ER_ALTER_SPACE,		2, "Can't modify space %u: %s") \
 	/* 26 */_(ER_FIBER_STACK,		2, "Can not create a new fiber: recursion limit reached") \
-	/* 27 */_(ER_UNUSED27,			2, "Unused27") \
+	/* 27 */_(ER_MODIFY_INDEX,		2, "Can't modify index %u in space %u: %s") \
 	/* 28 */_(ER_TUPLE_FORMAT_LIMIT,	2, "Tuple format limit reached: %u") \
-	/* 29 */_(ER_UNUSED29,			2, "Unused29") \
-	/* 30 */_(ER_UNUSED30,			2, "Unused30") \
+	/* 29 */_(ER_LAST_DROP,			2, "Can't drop the primary key in a system space, space id %u") \
+	/* 30 */_(ER_DROP_PRIMARY_KEY,		2, "Can't drop primary key in space %u while secondary keys exist") \
 	/* 31 */_(ER_UNUSED31,			2, "Unused31") \
 	/* 32 */_(ER_UNUSED32,			2, "Unused32") \
 	/* 33 */_(ER_UNUSED33,			2, "Unused33") \
diff --git a/include/memcached.h b/include/memcached.h
index f9e536eefdc8b535bed3ce9f1d26182c812754cb..cd3a2b7399a3a5140c549c4eb7774c646046f06f 100644
--- a/include/memcached.h
+++ b/include/memcached.h
@@ -35,13 +35,11 @@ struct tarantool_cfg;
 void
 memcached_init(const char *bind_ipaddr, int memcached_port);
 
-void
-memcached_space_init();
-
 int
 memcached_check_config(struct tarantool_cfg *conf);
 
-void memcached_start_expire();
-void memcached_stop_expire();
+int
+memcached_reload_config(struct tarantool_cfg *oldcfg,
+			struct tarantool_cfg *newcfg);
 
 #endif /* TARANTOOL_MEMCACHED_H_INCLUDED */
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 14603d831bff5ec114610b07dbab76cc8192fb54..1b39de179f5c28481264b10022b35d650a8235d0 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -5,6 +5,7 @@ endif()
 file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/src/box/lua)
 
 set(lua_sources)
+lua_source(lua_sources lua/schema.lua)
 lua_source(lua_sources lua/box.lua)
 lua_source(lua_sources lua/box_net.lua)
 lua_source(lua_sources lua/misc.lua)
@@ -25,6 +26,7 @@ tarantool_module("box"
     tree_index.cc
     bitset_index.cc
     space.cc
+    alter.cc
     schema.cc
     port.cc
     request.cc
diff --git a/src/box/alter.cc b/src/box/alter.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9d1c39f764217aa12c2be8f05b95bd60a7a4f56f
--- /dev/null
+++ b/src/box/alter.cc
@@ -0,0 +1,1024 @@
+/*
+ * 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 "alter.h"
+#include "schema.h"
+#include "space.h"
+#include "txn.h"
+#include "tuple.h"
+#include "fiber.h" /* for gc_pool */
+#include <new> /* for placement new */
+
+/** _space columns */
+#define ID 0
+#define ARITY 1
+#define NAME 2
+/** _index columns */
+#define INDEX_ID 1
+#define INDEX_TYPE 3
+#define INDEX_IS_UNIQUE 4
+#define INDEX_PART_COUNT 5
+
+/* {{{ Auxiliary functions and methods. */
+
+/**
+ * Create a key_def object from a record in _index
+ * system space.
+ *
+ * Check that:
+ * - index id is within range
+ * - index type is supported
+ * - part count > 0
+ * - there are parts for the specified part count
+ * - types of parts in the parts array are known to the system
+ * - fieldno of each part in the parts array is within limits
+ */
+struct key_def *
+key_def_new_from_tuple(struct tuple *tuple)
+{
+	uint32_t id = tuple_field_u32(tuple, ID);
+	uint32_t index_id = tuple_field_u32(tuple, INDEX_ID);
+	const char *type_str = tuple_field_cstr(tuple, INDEX_TYPE);
+	enum index_type type = STR2ENUM(index_type, type_str);
+	uint32_t is_unique = tuple_field_u32(tuple, INDEX_IS_UNIQUE);
+	uint32_t part_count = tuple_field_u32(tuple, INDEX_PART_COUNT);
+
+	struct key_def *key_def = key_def_new(index_id, type,
+					      is_unique > 0, part_count);
+	try {
+		struct tuple_iterator it;
+		tuple_rewind(&it, tuple);
+		uint32_t len; /* unused */
+		/* Parts follow part count. */
+		(void) tuple_seek(&it, INDEX_PART_COUNT, &len);
+
+		for (uint32_t i = 0; i < part_count; i++) {
+			uint32_t fieldno = tuple_next_u32(&it);
+			const char *field_type_str = tuple_next_cstr(&it);
+			enum field_type field_type =
+				STR2ENUM(field_type, field_type_str);
+			key_def_set_part(key_def, i, fieldno, field_type);
+		}
+		key_def_check(id, key_def);
+	} catch (...) {
+		key_def_delete(key_def);
+		throw;
+	}
+	return key_def;
+}
+
+/* }}} */
+
+/* {{{ struct alter_space - the body of a full blown alter */
+struct alter_space;
+
+class AlterSpaceOp {
+public:
+	struct rlist link;
+	virtual void prepare(struct alter_space * /* alter */) {}
+	virtual void alter_def(struct alter_space * /* alter */) {}
+	virtual void alter(struct alter_space * /* alter */) {}
+	virtual void commit(struct alter_space * /* alter */) {}
+	virtual void rollback(struct alter_space * /* alter */) {}
+	virtual ~AlterSpaceOp() {}
+	template <typename T> static T *create();
+	static void destroy(AlterSpaceOp *op);
+};
+
+template <typename T> T *
+AlterSpaceOp::create()
+{
+	return new (p0alloc(fiber->gc_pool, sizeof(T))) T;
+}
+
+void
+AlterSpaceOp::destroy(AlterSpaceOp *op)
+{
+	op->~AlterSpaceOp();
+}
+
+/**
+ * A trigger installed on transaction commit/rollback events of
+ * the transaction which initiated the alter.
+ */
+struct txn_alter_trigger {
+	struct trigger trigger;
+	struct alter_space *alter;
+};
+
+struct txn_alter_trigger *
+txn_alter_trigger(struct trigger *trigger)
+{
+	return (struct txn_alter_trigger *) trigger;
+}
+
+struct txn_alter_trigger *
+txn_alter_trigger_new(trigger_f run, struct alter_space *alter)
+{
+	struct txn_alter_trigger *trigger = (struct txn_alter_trigger *)
+		p0alloc(fiber->gc_pool, sizeof(*trigger));
+	trigger->trigger.run = run;
+	trigger->alter = alter;
+	return trigger;
+}
+
+struct alter_space {
+	/** List of alter operations */
+	struct rlist ops;
+	/** Definition of the new space - space_def. */
+	struct space_def space_def;
+	/** Definition of the new space - keys. */
+	struct rlist key_list;
+	/** Old space. */
+	struct space *old_space;
+	/** New space. */
+	struct space *new_space;
+};
+
+struct alter_space *
+alter_space_new()
+{
+	struct alter_space *alter = (struct alter_space *)
+		p0alloc(fiber->gc_pool, sizeof(*alter));
+	rlist_create(&alter->ops);
+	return alter;
+}
+
+/** Destroy alter. */
+static void
+alter_space_delete(struct alter_space *alter)
+{
+	/* Destroy the ops. */
+	while (! rlist_empty(&alter->ops)) {
+		AlterSpaceOp *op = rlist_shift_entry(&alter->ops,
+						     AlterSpaceOp, link);
+		AlterSpaceOp::destroy(op);
+	}
+	/* Delete the new space, if any. */
+	if (alter->new_space)
+		space_delete(alter->new_space);
+}
+
+/** Add a single operation to the list of alter operations. */
+static void
+alter_space_add_op(struct alter_space *alter, AlterSpaceOp *op)
+{
+	/* Add to the tail: operations must be processed in order. */
+	rlist_add_tail_entry(&alter->ops, op, link);
+}
+
+/**
+ * Commit the alter.
+ *
+ * Move all unchanged indexes from the old space to the new space.
+ * Set the newly built indexes in the new space, or free memory
+ * of the dropped indexes.
+ * Replace the old space with a new one in the space cache.
+ */
+static void
+alter_space_commit(struct trigger *trigger, void * /* event */)
+{
+	struct alter_space *alter = txn_alter_trigger(trigger)->alter;
+#if 0
+	/*
+	 * Clear the lock first - should there be an
+	 * exception/bug, the lock must not be left around.
+	 */
+	space_unset_on_replace(space, alter);
+#endif
+	/*
+	 * If an index is unchanged, all its properties, including
+	 * ID are intact. Move this index here. If an index is
+	 * changed, even if this is a minor change, there is a
+	 * ModifyIndex instance which will move the index from an
+	 * old position to the new one.
+	 */
+	for (uint32_t i = 0; i < alter->new_space->index_count; i++) {
+		Index *new_index = alter->new_space->index[i];
+		Index *old_index = space_index(alter->old_space,
+					       index_id(new_index));
+		/*
+		 * Move unchanged index from the old space to the
+		 * new one.
+		 */
+		if (old_index != NULL &&
+		    key_def_cmp(new_index->key_def,
+				old_index->key_def) == 0) {
+			space_swap_index(alter->old_space,
+					 alter->new_space,
+					 index_id(old_index),
+					 index_id(new_index));
+		}
+	}
+	/*
+	 * Commit alter ops, this will move the changed
+	 * indexes into their new places.
+	 */
+	struct AlterSpaceOp *op;
+	rlist_foreach_entry(op, &alter->ops, link) {
+		op->commit(alter);
+	}
+	/* Rebuild index maps once for all indexes. */
+	space_fill_index_map(alter->old_space);
+	space_fill_index_map(alter->new_space);
+	/*
+	 * Don't forget about space triggers.
+	 */
+	rlist_swap(&alter->new_space->on_replace,
+		   &alter->old_space->on_replace);
+	/*
+	 * The new space is ready. Time to update the space
+	 * cache with it.
+	 */
+	struct space *old_space = space_cache_replace(alter->new_space);
+	assert(old_space == alter->old_space);
+	space_delete(old_space);
+	alter->new_space = NULL; /* for alter_space_delete(). */
+	alter_space_delete(alter);
+}
+
+/**
+ * Rollback all effects of space alter. This is
+ * a transaction trigger, and it fires most likely
+ * upon a failed write to the WAL.
+ *
+ * Keep in mind that we may end up here in case of
+ * alter_space_commit() failure (unlikely)
+ */
+static void
+alter_space_rollback(struct trigger *trigger, void * /* event */)
+{
+	struct alter_space *alter = txn_alter_trigger(trigger)->alter;
+#if 0
+	/* Clear the lock, first thing. */
+		op->rollback(alter);
+	space_remove_trigger(alter);
+#endif
+	struct AlterSpaceOp *op;
+	rlist_foreach_entry(op, &alter->ops, link)
+		op->rollback(alter);
+	alter_space_delete(alter);
+}
+
+/**
+ * alter_space_do() - do all the work necessary to
+ * create a new space.
+ *
+ * If something may fail during alter, it must be done here,
+ * before a record is written to the Write Ahead Log.  Only
+ * trivial and infallible actions are left to the commit phase
+ * of the alter.
+ *
+ * The implementation of this function follows "Template Method"
+ * pattern, providing a skeleton of the alter, while all the
+ * details are encapsulated in AlterSpaceOp methods.
+ *
+ * These are the major steps of alter defining the structure of
+ * the algorithm and performed regardless of what is altered:
+ *
+ * - the input is checked for validity; each check is
+ *   encapsulated in AlterSpaceOp::prepare() method.
+ * - a copy of the definition of the old space is created
+ * - the definition of the old space is altered, to get
+ *   definition of a new space
+ * - an instance of the new space is created, according to the new
+ *   definition; the space is so far empty
+ * - data structures of the new space are built; sometimes, it
+ *   doesn't need to happen, e.g. when alter only changes the name
+ *   of a space or an index, or other accidental property.
+ *   If any data structure needs to be built, e.g. a new index,
+ *   only this index is built, not the entire space with all its
+ *   indexes.
+ * - at commit, the new space is coalesced with the old one.
+ *   On rollback, the new space is deleted.
+ */
+static void
+alter_space_do(struct txn *txn, struct alter_space *alter,
+	       struct space *old_space)
+{
+#if 0
+	/*
+	 * Mark the space as being altered, to abort
+	 * concurrent alter operations from while this
+	 * alter is being written to the write ahead log.
+	 * Must be the last, to make sure we reach commit and
+	 * remove it. It's removed only in comit/rollback.
+	 *
+	 * @todo This is, essentially, an implicit pessimistic
+	 * metadata lock on the space (ugly!), and it should be
+	 * replaced with an explicit lock, since there is nothing
+	 * worse than having to retry your alter -- usually alter
+	 * is done in a script without error-checking.
+	 * Plus, implicit locks are evil.
+	 */
+	if (space->on_replace == space_alter_on_replace)
+		tnt_raise(ER_ALTER, space_id(space));
+#endif
+	alter->old_space = old_space;
+	alter->space_def = old_space->def;
+	/* Create a definition of the new space. */
+	space_dump_def(old_space, &alter->key_list);
+	/*
+	 * Allow for a separate prepare step so that some ops
+	 * can be optimized.
+	 */
+	struct AlterSpaceOp *op, *tmp;
+	rlist_foreach_entry_safe(op, &alter->ops, link, tmp)
+		op->prepare(alter);
+	/*
+	 * Alter the definition of the old space, so that
+	 * a new space can be created with a new definition.
+	 */
+	rlist_foreach_entry(op, &alter->ops, link)
+		op->alter_def(alter);
+	/*
+	 * Create a new (empty) space for the new definition.
+	 * Sic: the space engine is not the same yet, the
+	 * triggers are not set.
+         */
+	alter->new_space = space_new(&alter->space_def, &alter->key_list);
+	/*
+	 * Copy the engine, the new space is at the same recovery
+	 * phase as the old one. Do it before performing the alter,
+	 * since engine.recover does different things depending on
+	 * the recovery phase.
+	 */
+	alter->new_space->engine = alter->old_space->engine;
+	/*
+	 * Change the new space: build the new index, rename,
+	 * change arity.
+	 */
+	rlist_foreach_entry(op, &alter->ops, link)
+		op->alter(alter);
+	/*
+	 * Install transaction commit/rollback triggers to either
+	 * finish or rollback the DDL depending on the results of
+	 * writing to WAL.
+	 */
+	struct txn_alter_trigger *on_commit =
+		txn_alter_trigger_new(alter_space_commit, alter);
+	trigger_set(&txn->on_commit, &on_commit->trigger);
+	struct txn_alter_trigger *on_rollback =
+		txn_alter_trigger_new(alter_space_rollback, alter);
+	trigger_set(&txn->on_rollback, &on_rollback->trigger);
+}
+
+/* }}}  */
+
+/* {{{ AlterSpaceOp descendants - alter operations, such as Add/Drop index */
+
+/** Change non-essential properties of a space. */
+class ModifySpace: public AlterSpaceOp
+{
+public:
+	/* New space definition. */
+	struct space_def def;
+	virtual void prepare(struct alter_space *alter);
+	virtual void alter_def(struct alter_space *alter);
+};
+
+/** Check that space properties are OK to change. */
+void
+ModifySpace::prepare(struct alter_space *alter)
+{
+	if (def.id != space_id(alter->old_space))
+		tnt_raise(ClientError, ER_ALTER_SPACE,
+			  (unsigned) space_id(alter->old_space),
+			  "space id is immutable");
+
+	if (def.arity != 0 &&
+	    def.arity > alter->old_space->def.arity &&
+	    alter->old_space->engine.state != READY_NO_KEYS &&
+	    space_size(alter->old_space) > 0) {
+
+		tnt_raise(ClientError, ER_ALTER_SPACE,
+			  (unsigned) def.id,
+			  "can not enlarge arity on a non-empty space");
+	}
+}
+
+/** Amend the definition of the new space. */
+void
+ModifySpace::alter_def(struct alter_space *alter)
+{
+	alter->space_def = def;
+}
+
+/** DropIndex - remove an index from space. */
+
+class AddIndex;
+
+class DropIndex: public AlterSpaceOp {
+public:
+	/** A reference to Index key def of the dropped index. */
+	struct key_def *old_key_def;
+	virtual void alter_def(struct alter_space *alter);
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter);
+};
+
+/*
+ * Alter the definition of the new space and remove
+ * the new index from it.
+ */
+void
+DropIndex::alter_def(struct alter_space * /* alter */)
+{
+	rlist_del_entry(old_key_def, link);
+}
+
+/* Do the drop. */
+void
+DropIndex::alter(struct alter_space *alter)
+{
+	/*
+	 * If it's not the primary key, nothing to do --
+	 * the dropped index didn't exist in the new space
+	 * definition, so does not exist in the created space.
+	 */
+	if (space_index(alter->new_space, 0) != NULL)
+		return;
+	/*
+	 * Deal with various cases of dropping of the primary key.
+	 */
+	/*
+	 * Dropping the primary key in a system space: off limits.
+	 */
+	if (space_is_system(alter->new_space))
+		tnt_raise(ClientError, ER_LAST_DROP,
+			  space_id(alter->new_space));
+	/*
+	 * Can't drop primary key before secondary keys.
+	 */
+	if (alter->new_space->index_count) {
+		tnt_raise(ClientError, ER_DROP_PRIMARY_KEY,
+			  (unsigned) alter->new_space->def.id);
+	}
+	/*
+	 * OK to drop the primary key. Put the space back to
+	 * 'READY_NO_KEYS' state, so that:
+	 * - DML returns proper errors rather than crashes the
+	 *   server (thanks to engine_no_keys.replace),
+	 * - When a new primary key is finally added, the space
+	 *   can be put back online properly with
+	 *   engine_no_keys.recover.
+	 */
+	alter->new_space->engine = engine_no_keys;
+}
+
+void
+DropIndex::commit(struct alter_space *alter)
+{
+	/*
+	 * Delete all tuples in the old space if dropping the
+	 * primary key.
+	 */
+	if (space_index(alter->new_space, 0) != NULL)
+		return;
+	Index *pk = index_find(alter->old_space, 0);
+	if (pk == NULL)
+		return;
+	struct iterator *it = pk->position();
+	pk->initIterator(it, ITER_ALL, NULL, 0);
+	struct tuple *tuple;
+	while ((tuple = it->next(it)))
+		tuple_ref(tuple, -1);
+}
+
+/** Change non-essential (no data change) properties of an index. */
+class ModifyIndex: public AlterSpaceOp
+{
+public:
+	struct key_def *new_key_def;
+	struct key_def *old_key_def;
+	virtual void alter_def(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter);
+	virtual ~ModifyIndex();
+};
+
+/** Update the definition of the new space */
+void
+ModifyIndex::alter_def(struct alter_space *alter)
+{
+	rlist_del_entry(old_key_def, link);
+	rlist_add_entry(&alter->key_list, new_key_def, link);
+}
+
+/** Move the index from the old space to the new one. */
+void
+ModifyIndex::commit(struct alter_space *alter)
+{
+	/* Move the old index to the new place but preserve */
+	space_swap_index(alter->old_space, alter->new_space,
+			 old_key_def->id, new_key_def->id);
+}
+
+ModifyIndex::~ModifyIndex()
+{
+	/* new_key_def is NULL if exception is raised before it's set. */
+	if (new_key_def)
+		key_def_delete(new_key_def);
+}
+
+/**
+ * Add to index trigger -- invoked on any change in the old space,
+ * while the AddIndex tuple is being written to the WAL. The job
+ * of this trigger is to keep the added index up to date with the
+ * state of the primary key in the old space.
+ *
+ * Initially it's installed as old_space->on_replace trigger, and
+ * for each successfully replaced tuple in the new index,
+ * a trigger is added to txn->on_rollback list to remove the tuple
+ * from the new index if the transaction rolls back.
+ *
+ * The trigger is removed when alter operation commits/rolls back.
+ */
+struct add2index_trigger {
+	struct trigger trigger;
+	Index *new_index;
+};
+
+struct add2index_trigger *
+add2index_trigger(struct trigger *trigger)
+{
+	return (struct add2index_trigger *) trigger;
+}
+
+struct add2index_trigger *
+add2index_trigger_new(trigger_f run, Index *new_index)
+{
+	struct add2index_trigger *trigger = (struct add2index_trigger *)
+		p0alloc(fiber->gc_pool, sizeof(*trigger));
+	trigger->trigger.run = run;
+	trigger->new_index = new_index;
+	return trigger;
+}
+
+/** AddIndex - add a new index to the space. */
+class AddIndex: public AlterSpaceOp {
+public:
+	/** New index key_def. */
+	struct key_def *new_key_def;
+	struct add2index_trigger *on_replace;
+	virtual void prepare(struct alter_space *alter);
+	virtual void alter_def(struct alter_space *alter);
+	virtual void alter(struct alter_space *alter);
+	virtual ~AddIndex();
+};
+
+/**
+ * Optimize addition of a new index: try to either completely
+ * remove it or at least avoid building from scratch.
+ */
+void
+AddIndex::prepare(struct alter_space *alter)
+{
+	AlterSpaceOp *prev_op = rlist_prev_entry_safe(this, &alter->ops,
+						      link);
+	DropIndex *drop = dynamic_cast<DropIndex *>(prev_op);
+
+	if (drop == NULL ||
+	    drop->old_key_def->type != new_key_def->type ||
+	    key_part_cmp(drop->old_key_def->parts,
+			 drop->old_key_def->part_count,
+			 new_key_def->parts,
+			 new_key_def->part_count) != 0) {
+		/*
+		 * The new index is too distinct from the old one,
+		 * have to rebuild.
+		 */
+		return;
+	}
+	/* Only index meta has changed, no data change. */
+	rlist_del_entry(drop, link);
+	rlist_del_entry(this, link);
+	/* Add ModifyIndex only if the there is a change. */
+	if (key_def_cmp(drop->old_key_def, new_key_def) != 0) {
+		ModifyIndex *modify = AlterSpaceOp::create<ModifyIndex>();
+		alter_space_add_op(alter, modify);
+		modify->new_key_def = new_key_def;
+		new_key_def = NULL;
+		modify->old_key_def = drop->old_key_def;
+	}
+	AlterSpaceOp::destroy(drop);
+	AlterSpaceOp::destroy(this);
+}
+
+/** Add definition of the new key to the new space def. */
+void
+AddIndex::alter_def(struct alter_space *alter)
+{
+	rlist_add_entry(&alter->key_list, new_key_def, link);
+}
+
+/**
+ * A trigger invoked on rollback in old space while the record
+ * about alter is being written to the WAL.
+ */
+static void
+on_rollback_in_old_space(struct trigger *trigger, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	Index *new_index = add2index_trigger(trigger)->new_index;
+	/* Remove the failed tuple from the new index. */
+	new_index->replace(txn->new_tuple, txn->old_tuple, DUP_INSERT);
+}
+
+/**
+ * A trigger invoked on replace in old space while
+ * the record about alter is being written to the WAL.
+ */
+static void
+on_replace_in_old_space(struct trigger *trigger, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	Index *new_index = add2index_trigger(trigger)->new_index;
+	/*
+	 * First set rollback trigger, then do replace, since
+	 * creating the trigger may fail.
+	 */
+	struct add2index_trigger *on_rollback =
+		add2index_trigger_new(on_rollback_in_old_space, new_index);
+	trigger_set(&txn->on_rollback, &on_rollback->trigger);
+	/* Put the tuple into thew new index. */
+	(void) new_index->replace(txn->old_tuple, txn->new_tuple,
+				  DUP_INSERT);
+}
+
+/**
+ * Optionally build the new index.
+ *
+ * During recovery the space is often not fully constructed yet
+ * anyway, so there is no need to fully populate index with data,
+ * it is done at the end of recovery.
+ *
+ * Note, that system  spaces are exception to this, since
+ * they are fully enabled at all times.
+ */
+void
+AddIndex::alter(struct alter_space *alter)
+{
+	/*
+	 * READY_NO_KEYS is when a space has no functional keys.
+	 * Possible both during and after recovery.
+	 */
+	if (alter->new_space->engine.state == READY_NO_KEYS) {
+		if (new_key_def->id == 0) {
+			/*
+			 * Adding a primary key: bring the space
+			 * up to speed with the current recovery
+			 * state. During snapshot recovery it
+			 * means preparing the primary key for
+			 * build (beginBuild()). During xlog
+			 * recovery, it means building the primary
+			 * key. After recovery, it means building
+			 * all keys.
+			 */
+			alter->new_space->engine.recover(alter->new_space);
+		} else {
+			/*
+			 * Adding a secondary key: nothing to do.
+			 * Before the end of recovery, nothing to do
+			 * because secondary keys are built in bulk later.
+			 * During normal operation, nothing to do
+			 * because without a primary key there is
+			 * no data in the space, and secondary
+			 * keys are built once the primary is
+			 * added.
+			 * TODO Consider prohibiting this branch
+			 * altogether.
+			 */
+		}
+		return;
+	}
+	Index *pk = index_find(alter->old_space, 0);
+	Index *new_index = index_find(alter->new_space, new_key_def->id);
+	/* READY_PRIMARY_KEY is a state that only occurs during WAL recovery. */
+	if (alter->new_space->engine.state == READY_PRIMARY_KEY) {
+		if (new_key_def->id == 0) {
+			/*
+			 * Bulk rebuild of the new primary key
+			 * from old primary key - it is safe to do
+			 * in bulk and without tuple-by-tuple
+			 * verification, since all tuples have
+			 * been verified when inserted, before
+			 * shutdown.
+			 */
+			index_build(new_index, pk);
+		} else {
+			/*
+			 * No need to build a secondary key during
+			 * WAL recovery.
+			 */
+		}
+		return;
+	}
+	/* Now deal with any kind of add index during normal operation. */
+	struct iterator *it = pk->position();
+	pk->initIterator(it, ITER_ALL, NULL, 0);
+	/*
+	 * The index has to be built tuple by tuple, since
+	 * there is no guarantee that all tuples satisfy
+	 * new index' constraints. If any tuple can not be
+	 * added to the index (insufficient number of fields,
+	 * etc., the build is aborted.
+	 */
+	new_index->beginBuild();
+	new_index->endBuild();
+	/* Build the new index. */
+	struct tuple *tuple;
+	while ((tuple = it->next(it))) {
+		/*
+		 * @todo:
+		 * tuple_format_validate(alter->new_space->format,
+		 * tuple)
+		 * @todo: better message if there is a duplicate.
+		 */
+		struct tuple *old_tuple =
+			new_index->replace(NULL, tuple, DUP_INSERT);
+		assert(old_tuple == NULL); /* Guaranteed by DUP_INSERT. */
+		(void) old_tuple;
+	}
+	on_replace = add2index_trigger_new(on_replace_in_old_space,
+					   new_index);
+	trigger_set(&alter->old_space->on_replace, &on_replace->trigger);
+}
+
+AddIndex::~AddIndex()
+{
+	/*
+	 * The trigger by now may reside in the new space (on
+	 * commit) or in the old space (rollback). Remove it
+	 * from the list, wherever it is.
+	 */
+	if (on_replace)
+		trigger_clear(&on_replace->trigger);
+	if (new_key_def)
+		key_def_delete(new_key_def);
+}
+
+/* }}} */
+
+/**
+ * A trigger invoked on commit/rollback of DROP/ADD space.
+ * The trigger removed the space from the space cache.
+ */
+static void
+on_drop_space(struct trigger * /* trigger */, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	uint32_t id = tuple_field_u32(txn->old_tuple ?
+				      txn->old_tuple : txn->new_tuple, ID);
+	struct space *space = space_cache_delete(id);
+	space_delete(space);
+}
+
+static struct trigger drop_space_trigger =  { rlist_nil, on_drop_space };
+
+/**
+ * A trigger which is invoked on replace in a data dictionary
+ * space _space.
+ *
+ * Generally, whenever a data dictionary change occurs
+ * 2 things should be done:
+ *
+ * - space cache should be updated, and changes in the space
+ *   cache should be reflected in Lua bindings
+ *   (this is done in space_cache_replace() and
+ *   space_cache_delete())
+ *
+ * - the space which is changed should be rebuilt according
+ *   to the nature of the modification, i.e. indexes added/dropped,
+ *   tuple format changed, etc.
+ *
+ * When dealing with an update of _space space, we have 3 major
+ * cases:
+ *
+ * 1) insert a new tuple: creates a new space
+ *    The trigger prepares a space structure to insert
+ *    into the  space cache and registers an on commit
+ *    hook to perform the registration. Should the statement
+ *    itself fail, transaction is rolled back, the transaction
+ *    rollback hook must be there to delete the created space
+ *    object, avoiding a memory leak. The hooks are written
+ *    in a way that excludes the possibility of a failure.
+ *
+ * 2) delete a tuple: drops an existing space.
+ *
+ *    A space can be dropped only if it has no indexes.
+ *    The only reason for this restriction is that there
+ *    must be no tuples in _index without a corresponding tuple
+ *    in _space. It's not possible to delete such tuples
+ *    automatically (this would require multi-statement
+ *    transactions), so instead the trigger verifies that the
+ *    records have been deleted by the user.
+ *
+ *    Then the trigger registers transaction commit hook to
+ *    perform the deletion from the space cache.  No rollback hook
+ *    is required: if the transaction is rolled back, nothing is
+ *    done.
+ *
+ * 3) modify an existing tuple: some space
+ *    properties are immutable, but it's OK to change
+ *    space name or arity. This is done in WAL-error-
+ *    safe mode.
+ *
+ * A note about memcached_space: Tarantool 1.4 had a check
+ * which prevented re-definition of memcached_space. With
+ * dynamic space configuration such a check would be particularly
+ * clumsy, so it is simply not done.
+ */
+static void
+on_replace_dd_space(struct trigger * /* trigger */, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	struct tuple *old_tuple = txn->old_tuple;
+	struct tuple *new_tuple = txn->new_tuple;
+	/*
+	 * Things to keep in mind:
+	 * - old_tuple is set only in case of UPDATE.  For INSERT
+	 *   or REPLACE it is NULL.
+	 * - the trigger may be called inside recovery from a snapshot,
+	 *   when index look up is not possible
+	 * - _space, _index and other metaspaces initially don't
+	 *   have a tuple which represents it, this tuple is only
+	 *   created during recovery from
+	 *   a snapshot.
+	 *
+	 * Let's establish whether an old space exists. Use
+	 * old_tuple ID field, if old_tuple is set, since UPDATE
+	 * may have changed space id.
+	 */
+	uint32_t old_id = tuple_field_u32(old_tuple ?
+					  old_tuple : new_tuple, ID);
+	struct space *old_space = space_by_id(old_id);
+	if (new_tuple != NULL && old_space == NULL) { /* INSERT */
+		struct space_def def;
+		def.id = old_id;
+		def.arity = tuple_field_u32(new_tuple, ARITY);
+		if (def.id > BOX_SPACE_MAX) {
+			tnt_raise(ClientError, ER_CREATE_SPACE,
+				  (unsigned) def.id,
+				  "space id is too big");
+		}
+		if (def.id >= SC_SYSTEM_ID_MIN && def.id < SC_SYSTEM_ID_MAX) {
+			say_warn("\n"
+"*******************************************************\n"
+"* Creating a space with a reserved id %3u.            *\n"
+"* Ids in range %3u-%3u may be used for a system space *\n"
+"* the future. Assuming you know what you're doing.    *\n"
+"*******************************************************",
+				 (unsigned) def.id,
+				 (unsigned) SC_SYSTEM_ID_MIN,
+				 (unsigned) SC_SYSTEM_ID_MAX);
+		}
+		struct space *space = space_new(&def, &rlist_nil);
+		(void) space_cache_replace(space);
+		/*
+		 * So may happen that until the DDL change record
+		 * is written to the WAL, the space is used for
+		 * insert/update/delete. All these updates are
+		 * rolled back by the pipelined rollback mechanism,
+		 * so it's safe to simply drop the space on
+		 * rollback.
+		 */
+		trigger_set(&txn->on_rollback, &drop_space_trigger);
+	} else if (new_tuple == NULL) { /* DELETE */
+		/* Verify that the space is empty (has no indexes) */
+		if (old_space->index_count) {
+			tnt_raise(ClientError, ER_DROP_SPACE,
+				  (unsigned) space_id(old_space),
+				  "the space has indexes");
+		}
+		/* @todo lock space metadata until commit. */
+		/*
+		 * dd_space_delete() can't fail, any such
+		 * failure would have to abort the server.
+		 */
+		trigger_set(&txn->on_commit, &drop_space_trigger);
+	} else { /* UPDATE, REPLACE */
+		assert(old_space != NULL && new_tuple != NULL);
+		/*
+		 * Allow change of space properties, but do it
+		 * in WAL-error-safe mode.
+		 */
+		struct alter_space *alter = alter_space_new();
+		try {
+			ModifySpace *modify =
+				AlterSpaceOp::create<ModifySpace>();
+			alter_space_add_op(alter, modify);
+			modify->def.id = tuple_field_u32(new_tuple, ID);
+			modify->def.arity = tuple_field_u32(new_tuple, ARITY);
+			alter_space_do(txn, alter, old_space);
+		} catch (...) {
+			alter_space_delete(alter);
+			throw;
+		}
+	}
+}
+
+/**
+ * Just like with _space, 3 major cases:
+ *
+ * - insert a tuple = addition of a new index. The
+ *   space should exist.
+ *
+ * - delete a tuple - drop index.
+ *
+ * - update a tuple - change of index type or key parts.
+ *   Change of index type is the same as deletion of the old
+ *   index and addition of the new one.
+ *
+ *   A new index needs to be built before we attempt to commit
+ *   a record to the write ahead log, since:
+ *
+ *   1) if it fails, it's not good to end up with a corrupt index
+ *   which is already committed to WAL
+ *
+ *   2) Tarantool indexes also work as constraints (min number of
+ *   fields in the space, field uniqueness), and it's not good to
+ *   commit to WAL a constraint which is not enforced in the
+ *   current data set.
+ *
+ *   When adding a new index, ideally we'd also need to rebuild
+ *   all tuple formats in all tuples, since the old format may not
+ *   be ideal for the new index. We, however, do not do that,
+ *   since that would entail rebuilding all indexes at once.
+ *   Instead, the default tuple format of the space is changed,
+ *   and as tuples get updated/replaced, all tuples acquire a new
+ *   format.
+ *
+ *   The same is the case with dropping an index: nothing is
+ *   rebuilt right away, but gradually the extra space reserved
+ *   for offsets is relinquished to the slab allocator as tuples
+ *   are modified.
+ */
+static void
+on_replace_dd_index(struct trigger * /* trigger */, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	struct tuple *old_tuple = txn->old_tuple;
+	struct tuple *new_tuple = txn->new_tuple;
+	uint32_t id = tuple_field_u32(old_tuple ? old_tuple : new_tuple, ID);
+	uint32_t index_id = tuple_field_u32(old_tuple ? old_tuple : new_tuple,
+					    INDEX_ID);
+	struct space *old_space = space_find(id);
+	Index *old_index = space_index(old_space, index_id);
+	struct alter_space *alter = alter_space_new();
+	try {
+		/*
+		 * The order of checks is important, DropIndex most be added
+		 * first, so that AddIndex::prepare() can change
+		 * Drop + Add to a Modify.
+		 */
+		if (old_index != NULL) {
+			DropIndex *drop_index = AlterSpaceOp::create<DropIndex>();
+			alter_space_add_op(alter, drop_index);
+			drop_index->old_key_def = old_index->key_def;
+		}
+		if (new_tuple != NULL) {
+			AddIndex *add_index = AlterSpaceOp::create<AddIndex>();
+			alter_space_add_op(alter, add_index);
+			add_index->new_key_def = key_def_new_from_tuple(new_tuple);
+		}
+		alter_space_do(txn, alter, old_space);
+	} catch (...) {
+		alter_space_delete(alter);
+		throw;
+	}
+}
+
+struct trigger alter_space_on_replace_space = {
+	rlist_nil, on_replace_dd_space
+};
+
+struct trigger alter_space_on_replace_index = {
+	rlist_nil, on_replace_dd_index
+};
+
+/* vim: set foldmethod=marker */
diff --git a/src/box/alter.h b/src/box/alter.h
new file mode 100644
index 0000000000000000000000000000000000000000..089412c59a3946c812294e805d03f24a45f97453
--- /dev/null
+++ b/src/box/alter.h
@@ -0,0 +1,36 @@
+#ifndef INCLUDES_TARANTOOL_BOX_ALTER_H
+#define INCLUDES_TARANTOOL_BOX_ALTER_H
+/*
+ * 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 "trigger.h"
+
+extern struct trigger alter_space_on_replace_space;
+extern struct trigger alter_space_on_replace_index;
+
+#endif /* INCLUDES_TARANTOOL_BOX_ALTER_H */
diff --git a/src/box/box.cc b/src/box/box.cc
index 107e5730f481f94bfa0ddc3bc5987caed8e6b973..ab20ae872615770e4baeba26a241006215dd9ae9 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -43,7 +43,6 @@ extern "C" {
 #include <stat.h>
 #include <tarantool.h>
 #include "tuple.h"
-#include "memcached.h"
 #include "box_lua.h"
 #include "schema.h"
 #include "space.h"
@@ -157,8 +156,6 @@ box_enter_master_or_replica_mode(struct tarantool_cfg *conf)
 	} else {
 		box_process = process_rw;
 
-		memcached_start_expire();
-
 		snprintf(status, sizeof(status), "primary%s",
 			 custom_proc_title);
 		title("primary%s", custom_proc_title);
@@ -219,22 +216,6 @@ box_check_config(struct tarantool_cfg *conf)
 		return -1;
 	}
 
-	/* check if at least one space is defined */
-	if (conf->space == NULL && conf->memcached_port == 0) {
-		out_warning(CNF_OK, "at least one space or memcached port must be defined");
-		return -1;
-	}
-
-	/* check configured spaces */
-	if (check_spaces(conf) != 0) {
-		return -1;
-	}
-
-	/* check memcached configuration */
-	if (memcached_check_config(conf) != 0) {
-		return -1;
-	}
-
 	return 0;
 }
 
@@ -255,10 +236,6 @@ box_reload_config(struct tarantool_cfg *old_conf, struct tarantool_cfg *new_conf
 
 			return -1;
 		}
-
-		if (!old_is_replica && new_is_replica)
-			memcached_stop_expire();
-
 		if (recovery_state->remote)
 			recovery_stop_remote(recovery_state);
 
@@ -283,8 +260,6 @@ box_init()
 
 	tuple_init();
 	schema_init();
-	/* configure memcached space */
-	memcached_space_init();
 
 	/* recovery initialization */
 	recovery_init(cfg.snap_dir, cfg.wal_dir,
@@ -337,6 +312,8 @@ snapshot_space(struct space *sp, void *udata)
 	struct tuple *tuple;
 	struct snapshot_space_param *ud = (struct snapshot_space_param *) udata;
 	Index *pk = space_index(sp, 0);
+	if (pk == NULL)
+		return;
 	struct iterator *it = pk->position();
 	pk->initIterator(it, ITER_ALL, NULL, 0);
 
diff --git a/src/box/box_cfg.cfg_tmpl b/src/box/box_cfg.cfg_tmpl
index 5b1e3864c7d7e77d396c50a72f9a23d4e21fac54..f135f6eb9e28e5a3ba108b0ee42b8b2f37cbf768 100644
--- a/src/box/box_cfg.cfg_tmpl
+++ b/src/box/box_cfg.cfg_tmpl
@@ -31,23 +31,3 @@ memcached_expire_full_sweep=3600.0
 # fetching records from it.. In replication mode the server
 # only accepts reads.
 replication_source=NULL
-
-space = [
-  {
-    enabled = false, required
-    arity = -1
-    estimated_rows = 0
-    index = [
-      {
-        type = "", required
-        unique = false, required
-        key_field = [
-          {
-            fieldno = -1, required
-            type = "", required
-          }, ro,  required
-        ], required
-      }, ro, required
-    ], required
-  }, ro
-], ro
diff --git a/src/box/box_lua.cc b/src/box/box_lua.cc
index ea79e7b8b79bb300bdc131a4ad88820db288554b..fef590d0dd93fee04156b9436ca9d97e0752ec6a 100644
--- a/src/box/box_lua.cc
+++ b/src/box/box_lua.cc
@@ -55,8 +55,8 @@ extern "C" {
 #include "scoped_guard.h"
 
 /* contents of box.lua, misc.lua, box.net.lua respectively */
-extern char box_lua[], box_net_lua[], misc_lua[], sql_lua[];
-static const char *lua_sources[] = { box_lua, box_net_lua, misc_lua, sql_lua, NULL };
+extern char schema_lua[], box_lua[], box_net_lua[], misc_lua[], sql_lua[];
+static const char *lua_sources[] = { schema_lua, box_lua, box_net_lua, misc_lua, sql_lua, NULL };
 
 /**
  * All box connections share the same Lua state. We use
@@ -1094,10 +1094,7 @@ static void
 port_add_lua_ret(struct port *port, struct lua_State *L, int index)
 {
 	struct tuple *tuple = lua_totuple(L, index);
-	auto scoped_guard = make_scoped_guard([=] {
-		if (tuple->refs == 0)
-			tuple_delete(tuple);
-	});
+	TupleGuard guard(tuple);
 	port_add_tuple(port, tuple, BOX_RETURN_TUPLE);
 }
 
@@ -1815,6 +1812,32 @@ static const struct luaL_reg boxlib[] = {
 	{NULL, NULL}
 };
 
+void
+schema_lua_init(struct lua_State *L)
+{
+	lua_getfield(L, LUA_GLOBALSINDEX, "box");
+	lua_newtable(L);
+	lua_setfield(L, -2, "schema");
+	lua_getfield(L, -1, "schema");
+	lua_pushnumber(L, SC_SCHEMA_ID);
+	lua_setfield(L, -2, "SCHEMA_ID");
+	lua_pushnumber(L, SC_SPACE_ID);
+	lua_setfield(L, -2, "SPACE_ID");
+	lua_pushnumber(L, SC_INDEX_ID);
+	lua_setfield(L, -2, "INDEX_ID");
+	lua_pushnumber(L, SC_SYSTEM_ID_MIN);
+	lua_setfield(L, -2, "SYSTEM_ID_MIN");
+	lua_pushnumber(L, SC_SYSTEM_ID_MAX);
+	lua_setfield(L, -2, "SYSTEM_ID_MAX");
+	lua_pushnumber(L, BOX_INDEX_MAX);
+	lua_setfield(L, -2, "INDEX_MAX");
+	lua_pushnumber(L, BOX_SPACE_MAX);
+	lua_setfield(L, -2, "SPACE_MAX");
+	lua_pushnumber(L, BOX_FIELD_MAX);
+	lua_setfield(L, -2, "FIELD_MAX");
+	lua_pop(L, 2); /* box, schema */
+}
+
 void
 mod_lua_init(struct lua_State *L)
 {
@@ -1822,6 +1845,7 @@ mod_lua_init(struct lua_State *L)
 	tarantool_lua_register_type(L, tuplelib_name, lbox_tuple_meta);
 	luaL_register(L, tuplelib_name, lbox_tuplelib);
 	lua_pop(L, 1);
+	schema_lua_init(L);
 	tarantool_lua_register_type(L, tuple_iteratorlib_name,
 				    lbox_tuple_iterator_meta);
 	luaL_register(L, "box", boxlib);
diff --git a/src/box/box_lua_space.cc b/src/box/box_lua_space.cc
index 443fe1d5effc397d5f587872061cea6e45924802..182866b6fbc4f9c401feb8a308abd26c2daf77e1 100644
--- a/src/box/box_lua_space.cc
+++ b/src/box/box_lua_space.cc
@@ -59,9 +59,8 @@ lbox_pushspace(struct lua_State *L, struct space *space)
 	lua_pushnumber(L, space_id(space));
 	lua_settable(L, -3);
 
-	/* all exists spaces are enabled */
 	lua_pushstring(L, "enabled");
-	lua_pushboolean(L, 1);
+	lua_pushboolean(L, space->engine.state != READY_NO_KEYS);
 	lua_settable(L, -3);
 
 	/* space.index */
@@ -115,12 +114,16 @@ lbox_pushspace(struct lua_State *L, struct space *space)
 	lua_settable(L, -3);	/* push space.index */
 
 	lua_getfield(L, LUA_GLOBALSINDEX, "box");
-	lua_pushstring(L, "bless_space");
+	lua_pushstring(L, "schema");
+	lua_gettable(L, -2);
+	lua_pushstring(L, "space");
+	lua_gettable(L, -2);
+	lua_pushstring(L, "bless");
 	lua_gettable(L, -2);
 
-	lua_pushvalue(L, -3);			/* box, bless, space */
+	lua_pushvalue(L, -5);	/* box, schema, space, bless, space */
 	lua_call(L, 1, 0);
-	lua_pop(L, 1);	/* cleanup stack */
+	lua_pop(L, 3);	/* cleanup stack - box, schema, space */
 
 	return 1;
 }
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 28d09b84b476d076e24d94813c657d42bca46374..c0b7f76b76db891967cd07a52c66e124b31a1bfd 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -28,6 +28,7 @@
  */
 #include "key_def.h"
 #include <stdlib.h>
+#include "exception.h"
 
 const char *field_type_strs[] = {"UNKNOWN", "NUM", "NUM64", "STR", "\0"};
 STRS(index_type, ENUM_INDEX_TYPE);
@@ -54,3 +55,102 @@ key_def_delete(struct key_def *key_def)
 {
 	free(key_def);
 }
+
+int
+key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
+	     const struct key_part *parts2, uint32_t part_count2)
+{
+	const struct key_part *part1 = parts1;
+	const struct key_part *part2 = parts2;
+	uint32_t part_count = MIN(part_count1, part_count2);
+	const struct key_part *end = parts1 + part_count;
+	for (; part1 != end; part1++, part2++) {
+		if (part1->fieldno != part2->fieldno)
+			return part1->fieldno < part2->fieldno ? -1 : 1;
+		if ((int) part1->type != (int) part2->type)
+			return (int) part1->type < (int) part2->type ? -1 : 1;
+	}
+	return part_count1 < part_count2 ? -1 : part_count1 > part_count2;
+}
+
+int
+key_def_cmp(const struct key_def *key1, const struct key_def *key2)
+{
+	if (key1->id != key2->id)
+		return key1->id < key2->id ? -1 : 1;
+	if (key1->type != key2->type)
+		return (int) key1->type < (int) key2->type ? -1 : 1;
+	if (key1->is_unique != key2->is_unique)
+		return (int) key1->is_unique < (int) key2->is_unique ? -1 : 1;
+
+	return key_part_cmp(key1->parts, key1->part_count,
+			    key2->parts, key2->part_count);
+}
+
+void
+key_list_del_key(struct rlist *key_list, uint32_t id)
+{
+	struct key_def *key;
+	rlist_foreach_entry(key, key_list, link) {
+		if (key->id == id) {
+			rlist_del_entry(key, link);
+			return;
+		}
+	}
+	assert(false);
+}
+
+void
+key_def_check(uint32_t id, struct key_def *key_def)
+{
+	if (key_def->id > BOX_INDEX_MAX) {
+		tnt_raise(ClientError, ER_MODIFY_INDEX,
+			  (unsigned) id, (unsigned) key_def->id,
+			  "index id too big");
+	}
+	if (key_def->part_count == 0) {
+		tnt_raise(ClientError, ER_MODIFY_INDEX,
+			  (unsigned) id, (unsigned) key_def->id,
+			  "part count must be positive");
+	}
+	for (uint32_t i = 0; i < key_def->part_count; i++) {
+		if (key_def->parts[i].type == field_type_MAX) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) id, (unsigned) key_def->id,
+				  "unknown field type");
+		}
+		if (key_def->parts[i].fieldno > BOX_FIELD_MAX) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) id, (unsigned) key_def->id,
+				  "field no is too big");
+		}
+	}
+	switch (key_def->type) {
+	case HASH:
+		if (! key_def->is_unique) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) id, (unsigned) key_def->id,
+				  "HASH index must be unique");
+		}
+		break;
+	case TREE:
+		/* TREE index has no limitations. */
+		break;
+	case BITSET:
+		if (key_def->part_count != 1) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) id, (unsigned) key_def->id,
+				    "BITSET index key can not be multipart");
+		}
+		if (key_def->is_unique) {
+			tnt_raise(ClientError, ER_MODIFY_INDEX,
+				  (unsigned) id, (unsigned) key_def->id,
+				  "BITSET can not be unique");
+		}
+		break;
+	default:
+		tnt_raise(ClientError, ER_INDEX_TYPE,
+			  (unsigned) id, (unsigned) key_def->id);
+		break;
+	}
+}
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 8dd62168078192f3583598a065525ed8c5328442..40aa20c8f1d987d2fd33e134a809104373883316 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -113,6 +113,35 @@ key_def_set_part(struct key_def *def, uint32_t part_no,
 	def->parts[part_no].type = type;
 }
 
+/** Compare two key part arrays.
+ *
+ * This function is used to find out whether alteration
+ * of an index has changed it substantially enough to warrant
+ * a rebuild or not. For example, change of index id is
+ * not a substantial change, whereas change of index type
+ * or key parts requires a rebuild.
+ *
+ * One key part is considered to be greater than the other if:
+ * - its fieldno is greater
+ * - given the same fieldno, NUM < NUM64 < STRING
+ *   (coarsely speaking, based on field_type_maxlen()).
+ *
+ * A key part array is considered greater than the other if all
+ * its key parts are greater, or, all common key parts are equal
+ * but there are additional parts in the bigger array.
+ */
+int
+key_part_cmp(const struct key_part *parts1, uint32_t part_count1,
+	     const struct key_part *parts2, uint32_t part_count2);
+
+/**
+ * One key definition is greater than the other if it's id is
+ * greater, it's index type is greater (HASH < TREE < BITSET)
+ * or its key part array is greater.
+ */
+int
+key_def_cmp(const struct key_def *key1, const struct key_def *key2);
+
 /* Destroy and free a key_def. */
 void
 key_def_delete(struct key_def *def);
@@ -124,6 +153,19 @@ key_list_add_key(struct rlist *key_list, struct key_def *key)
 	rlist_add_entry(key_list, key, link);
 }
 
+/** Remove a key from the list of keys. */
+void
+key_list_del_key(struct rlist *key_list, uint32_t id);
+
+/**
+ * Check a key definition for violation of various limits.
+ *
+ * @param id        space id
+ * @param key_def   key_def
+ * @param type_str  type name (to produce a nice error)
+ */
+void
+key_def_check(uint32_t id, struct key_def *key_def);
 
 /** Space metadata. */
 struct space_def {
diff --git a/src/box/lua/box.lua b/src/box/lua/box.lua
index bf67a9ca563ab52f4134fd852a2614866de1576e..e43677b670661bec2b3a54907538d6da0cf674b3 100644
--- a/src/box/lua/box.lua
+++ b/src/box/lua/box.lua
@@ -96,117 +96,6 @@ function box.dostring(s, ...)
     return chunk(...)
 end
 
-function box.bless_space(space)
-    local index_mt = {}
-    -- __len and __index
-    index_mt.len = function(index) return #index.idx end
-    index_mt.__newindex = function(table, index)
-        return error('Attempt to modify a read-only table') end
-    index_mt.__index = index_mt
-    -- min and max
-    index_mt.min = function(index) return index.idx:min() end
-    index_mt.max = function(index) return index.idx:max() end
-    index_mt.random = function(index, rnd) return index.idx:random(rnd) end
-    -- iteration
-    index_mt.iterator = function(index, ...)
-        return index.idx:iterator(...)
-    end
-    --
-    -- pairs/next/prev methods are provided for backward compatibility purposes only
-    index_mt.pairs = function(index)
-        return index.idx.next, index.idx, nil
-    end
-    --
-    local next_compat = function(idx, iterator_type, ...)
-        local arg = {...}
-        if #arg == 1 and type(arg[1]) == "userdata" then
-            return idx:next(...)
-        else
-            return idx:next(iterator_type, ...)
-        end
-    end
-    index_mt.next = function(index, ...)
-        return next_compat(index.idx, box.index.GE, ...);
-    end
-    index_mt.prev = function(index, ...)
-        return next_compat(index.idx, box.index.LE, ...);
-    end
-    index_mt.next_equal = function(index, ...)
-        return next_compat(index.idx, box.index.EQ, ...);
-    end
-    index_mt.prev_equal = function(index, ...)
-        return next_compat(index.idx, box.index.REQ, ...);
-    end
-    -- index subtree size
-    index_mt.count = function(index, ...)
-        return index.idx:count(...)
-    end
-    --
-    index_mt.select_range = function(index, limit, ...)
-        local range = {}
-        for v in index:iterator(box.index.GE, ...) do
-            if #range >= limit then
-                break
-            end
-            table.insert(range, v)
-            iterator_state, v = index:next(iterator_state)
-        end
-        return unpack(range)
-    end
-    index_mt.select_reverse_range = function(index, limit, ...)
-        local range = {}
-        for v in index:iterator(box.index.LE, ...) do
-            if #range >= limit then
-                break
-            end
-            table.insert(range, v)
-            iterator_state, v = index:prev(iterator_state)
-        end
-        return unpack(range)
-    end
-    --
-    local space_mt = {}
-    space_mt.len = function(space) return space.index[0]:len() end
-    space_mt.__newindex = index_mt.__newindex
-    space_mt.select = function(space, ...) return box.select(space.n, ...) end
-    space_mt.select_range = function(space, ino, limit, ...)
-        return space.index[ino]:select_range(limit, ...)
-    end
-    space_mt.select_reverse_range = function(space, ino, limit, ...)
-        return space.index[ino]:select_reverse_range(limit, ...)
-    end
-    space_mt.select_limit = function(space, ino, offset, limit, ...)
-        return box.select_limit(space.n, ino, offset, limit, ...)
-    end
-    space_mt.insert = function(space, ...) return box.insert(space.n, ...) end
-    space_mt.update = function(space, ...) return box.update(space.n, ...) end
-    space_mt.replace = function(space, ...) return box.replace(space.n, ...) end
-    space_mt.delete = function(space, ...) return box.delete(space.n, ...) end
-    space_mt.truncate = function(space)
-        local pk = space.index[0]
-        while #pk.idx > 0 do
-            for t in pk:iterator() do
-                local key = {};
-                -- ipairs does not work because pk.key_field is zero-indexed
-                for _k2, key_field in pairs(pk.key_field) do
-                    table.insert(key, t[key_field.fieldno])
-                end
-                space:delete(unpack(key))
-            end
-        end
-    end
-    space_mt.pairs = function(space) return space.index[0]:pairs() end
-    space_mt.__index = space_mt
-
-    setmetatable(space, space_mt)
-    if type(space.index) == 'table' and space.enabled then
-        for j, index in pairs(space.index) do
-            rawset(index, 'idx', box.index.new(space.n, j))
-            setmetatable(index, index_mt)
-        end
-    end
-end
-
 -- User can redefine the hook
 function box.on_reload_configuration()
 end
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
new file mode 100644
index 0000000000000000000000000000000000000000..0a699bd9c0ef0dc1bc2f84d3bac37d3afef2d655
--- /dev/null
+++ b/src/box/lua/schema.lua
@@ -0,0 +1,183 @@
+-- schema.lua (internal file)
+--
+local ffi = require('ffi')
+ffi.cdef[[
+    struct space *space_by_id(uint32_t id);
+    void space_run_triggers(struct space *space, bool yesno);
+]]
+
+box.schema.space = {}
+box.schema.space.create = function(name, options)
+    local _space = box.space[box.schema.SPACE_ID]
+    if options == nil then
+        options = {}
+    end
+    local if_not_exists = options.if_not_exists
+    if if_not_exists and box.space[name] then
+        return box.space[name], "not created"
+    end
+    local id = box.unpack('i', _space.index[0]:max()[0])
+    if id < box.schema.SYSTEM_ID_MAX then
+        id = box.schema.SYSTEM_ID_MAX + 1
+    else
+        id = id + 1
+    end
+    if options.arity == nil then
+        options.arity = 0
+    end
+    _space:insert(id, options.arity, name)
+    box.space[name] = box.space[id]
+    return box.space[id], "created"
+end
+box.schema.create_space = box.schema.space.create
+box.schema.space.drop = function(space_id)
+    local _space = box.space[box.schema.SPACE_ID]
+    local _index = box.space[box.schema.INDEX_ID]
+    local keys = { _index:select(0, space_id) }
+    for i = #keys, 1, -1 do
+        local v = keys[i]
+        _index:delete(v[0], v[1])
+    end
+    _space:delete(space_id)
+end
+
+box.schema.index = {}
+
+box.schema.index.create = function(space_id, index_id, index_type, ...)
+    local _index = box.space[box.schema.INDEX_ID]
+    _index:insert(space_id, index_id, index_type, ...)
+end
+box.schema.index.drop = function(space_id, index_id)
+    local _index = box.space[box.schema.INDEX_ID]
+    _index:delete(space_id, index_id)
+end
+
+function box.schema.space.bless(space)
+    local index_mt = {}
+    -- __len and __index
+    index_mt.len = function(index) return #index.idx end
+    index_mt.__newindex = function(table, index)
+        return error('Attempt to modify a read-only table') end
+    index_mt.__index = index_mt
+    -- min and max
+    index_mt.min = function(index) return index.idx:min() end
+    index_mt.max = function(index) return index.idx:max() end
+    index_mt.random = function(index, rnd) return index.idx:random(rnd) end
+    -- iteration
+    index_mt.iterator = function(index, ...)
+        return index.idx:iterator(...)
+    end
+    --
+    -- pairs/next/prev methods are provided for backward compatibility purposes only
+    index_mt.pairs = function(index)
+        return index.idx.next, index.idx, nil
+    end
+    --
+    local next_compat = function(idx, iterator_type, ...)
+        local arg = {...}
+        if #arg == 1 and type(arg[1]) == "userdata" then
+            return idx:next(...)
+        else
+            return idx:next(iterator_type, ...)
+        end
+    end
+    index_mt.next = function(index, ...)
+        return next_compat(index.idx, box.index.GE, ...);
+    end
+    index_mt.prev = function(index, ...)
+        return next_compat(index.idx, box.index.LE, ...);
+    end
+    index_mt.next_equal = function(index, ...)
+        return next_compat(index.idx, box.index.EQ, ...);
+    end
+    index_mt.prev_equal = function(index, ...)
+        return next_compat(index.idx, box.index.REQ, ...);
+    end
+    -- index subtree size
+    index_mt.count = function(index, ...)
+        return index.idx:count(...)
+    end
+    --
+    index_mt.select_range = function(index, limit, ...)
+        local range = {}
+        for v in index:iterator(box.index.GE, ...) do
+            if #range >= limit then
+                break
+            end
+            table.insert(range, v)
+            iterator_state, v = index:next(iterator_state)
+        end
+        return unpack(range)
+    end
+    index_mt.select_reverse_range = function(index, limit, ...)
+        local range = {}
+        for v in index:iterator(box.index.LE, ...) do
+            if #range >= limit then
+                break
+            end
+            table.insert(range, v)
+            iterator_state, v = index:prev(iterator_state)
+        end
+        return unpack(range)
+    end
+    index_mt.drop = function(index)
+        return box.schema.index.drop(index.n, index.id)
+    end
+    --
+    local space_mt = {}
+    space_mt.len = function(space) return space.index[0]:len() end
+    space_mt.__newindex = index_mt.__newindex
+    space_mt.select = function(space, ...) return box.select(space.n, ...) end
+    space_mt.select_range = function(space, ino, limit, ...)
+        return space.index[ino]:select_range(limit, ...)
+    end
+    space_mt.select_reverse_range = function(space, ino, limit, ...)
+        return space.index[ino]:select_reverse_range(limit, ...)
+    end
+    space_mt.select_limit = function(space, ino, offset, limit, ...)
+        return box.select_limit(space.n, ino, offset, limit, ...)
+    end
+    space_mt.insert = function(space, ...) return box.insert(space.n, ...) end
+    space_mt.update = function(space, ...) return box.update(space.n, ...) end
+    space_mt.replace = function(space, ...) return box.replace(space.n, ...) end
+    space_mt.delete = function(space, ...) return box.delete(space.n, ...) end
+    space_mt.truncate = function(space)
+        local pk = space.index[0]
+        while #pk.idx > 0 do
+            for t in pk:iterator() do
+                local key = {};
+                -- ipairs does not work because pk.key_field is zero-indexed
+                for _k2, key_field in pairs(pk.key_field) do
+                    table.insert(key, t[key_field.fieldno])
+                end
+                space:delete(unpack(key))
+            end
+        end
+    end
+    space_mt.pairs = function(space) return space.index[0]:pairs() end
+    space_mt.drop = function(space)
+        return box.schema.space.drop(space.n)
+    end
+    space_mt.create_index = function(space, ...)
+        return box.schema.index.create(space.n, ...)
+    end
+    space_mt.run_triggers = function(space, yesno)
+        local space = ffi.C.space_by_id(space.n)
+        if space == nil then
+            box.raise(box.error.ER_NO_SUCH_SPACE, "Space not found")
+        end
+        ffi.C.space_run_triggers(space, yesno)
+    end
+    space_mt.__index = space_mt
+
+    setmetatable(space, space_mt)
+    if type(space.index) == 'table' and space.enabled then
+        for j, index in pairs(space.index) do
+            rawset(index, 'idx', box.index.new(space.n, j))
+            rawset(index, 'id', j)
+            rawset(index, 'n', space.n)
+            setmetatable(index, index_mt)
+        end
+    end
+end
+
diff --git a/src/box/request.cc b/src/box/request.cc
index 9e72464d7ee9cd4590412923d20953e93260a6f9..a23c872c05691d0c88b8cc3fec192fbad0f1bb9d 100644
--- a/src/box/request.cc
+++ b/src/box/request.cc
@@ -73,15 +73,10 @@ execute_replace(const struct request *request, struct txn *txn,
 
 	struct tuple *new_tuple = tuple_new(space->format, field_count,
 					    &tuple, request->r.tuple_end);
-	try {
-		space_validate_tuple(space, new_tuple);
-		enum dup_replace_mode mode = dup_replace_mode(request->flags);
-		txn_replace(txn, space, NULL, new_tuple, mode);
-
-	} catch (const Exception &e) {
-		tuple_delete(new_tuple);
-		throw;
-	}
+	TupleGuard guard(new_tuple);
+	space_validate_tuple(space, new_tuple);
+	enum dup_replace_mode mode = dup_replace_mode(request->flags);
+	txn_replace(txn, space, NULL, new_tuple, mode);
 }
 
 static void
@@ -110,13 +105,9 @@ execute_update(const struct request *request, struct txn *txn,
 					       fiber->gc_pool,
 					       old_tuple, request->u.expr,
 					       request->u.expr_end);
-	try {
-		space_validate_tuple(space, new_tuple);
-		txn_replace(txn, space, old_tuple, new_tuple, DUP_INSERT);
-	} catch (const Exception &e) {
-		tuple_delete(new_tuple);
-		throw;
-	}
+	TupleGuard guard(new_tuple);
+	space_validate_tuple(space, new_tuple);
+	txn_replace(txn, space, old_tuple, new_tuple, DUP_INSERT);
 }
 
 /** }}} */
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 2ef8f3cc1b380e808570ca627a7dfd98853896ee..5cc8b72ae373710a07b3182f7ca5a99db6f7aff4 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -28,20 +28,22 @@
  */
 #include "schema.h"
 #include "space.h"
+#include "tuple.h"
 #include "assoc.h"
 #include "lua/init.h"
 #include "box_lua_space.h"
 #include "key_def.h"
-extern "C" {
-#include <cfg/warning.h>
-#include <cfg/tarantool_box_cfg.h>
-} /* extern "C" */
+#include "alter.h"
+#include "scoped_guard.h"
 /**
  * @module Data Dictionary
  *
  * The data dictionary is responsible for storage and caching
  * of system metadata, such as information about existing
- * spaces, indexes, tuple formats.
+ * spaces, indexes, tuple formats. Space and index metadata
+ * is called in dedicated spaces, _space and _index respectively.
+ * The contents of these spaces is fully cached in a cache of
+ * struct space objects.
  *
  * struct space is an in-memory instance representing a single
  * space with its metadata, space data, and methods to manage
@@ -51,11 +53,15 @@ extern "C" {
 /** All existing spaces. */
 static struct mh_i32ptr_t *spaces;
 
-static void
-space_config();
+bool
+space_is_system(struct space *space)
+{
+	return space->def.id > SC_SYSTEM_ID_MIN &&
+		space->def.id < SC_SYSTEM_ID_MAX;
+}
 
 /** Return space by its number */
-struct space *
+extern "C" struct space *
 space_by_id(uint32_t id)
 {
 	mh_int_t space = mh_i32ptr_find(spaces, id, NULL);
@@ -71,9 +77,36 @@ void
 space_foreach(void (*func)(struct space *sp, void *udata), void *udata)
 {
 	mh_int_t i;
+	struct space *space;
+	struct { char len; uint32_t id; } __attribute__((packed))
+		key = { sizeof(uint32_t), SC_SYSTEM_ID_MIN };
+	/*
+	 * Make sure we always visit system spaces first,
+	 * in order from lowest space id to the highest..
+	 * This is essential for correctly recovery from the
+	 * snapshot, and harmless otherwise.
+	 */
+	space = space_by_id(SC_SPACE_ID);
+	Index *pk = space ? space_index(space, 0) : NULL;
+	if (pk) {
+		struct iterator *it = pk->allocIterator();
+		auto scoped_guard = make_scoped_guard([=] { it->free(it); });
+		pk->initIterator(it, ITER_GE, (char *) &key, 1);
+		struct tuple *tuple;
+		while ((tuple = it->next(it))) {
+			/* Get space id, primary key, field 0. */
+			uint32_t id = tuple_field_u32(tuple, 0);
+			space = space_find(id);
+			if (! space_is_system(space))
+				break;
+			func(space, udata);
+		}
+	}
+
 	mh_foreach(spaces, i) {
-		struct space *space = (struct space *)
-				mh_i32ptr_node(spaces, i)->val;
+		space = (struct space *) mh_i32ptr_node(spaces, i)->val;
+		if (space_is_system(space))
+			continue;
 		func(space, udata);
 	}
 }
@@ -123,6 +156,35 @@ do_one_recover_step(struct space *space, void * /* param */)
 		space->engine = engine_no_keys;
 }
 
+/** A wrapper around space_new() for data dictionary spaces. */
+struct space *
+sc_space_new(struct space_def *space_def,
+	     struct key_def *key_def,
+	     struct trigger *trigger)
+{
+	struct rlist key_list;
+	rlist_create(&key_list);
+	rlist_add_entry(&key_list, key_def, link);
+	struct space *space = space_new(space_def, &key_list);
+	(void) space_cache_replace(space);
+	if (trigger)
+		trigger_set(&space->on_replace, trigger);
+	/*
+	 * Data dictionary spaces are fully built since:
+	 * - they contain data right from the start
+	 * - they are fully operable already during recovery
+	 * - if there is a record in the snapshot which mandates
+	 *   addition of a new index to a system space, this
+	 *   index is built tuple-by-tuple, not in bulk, which
+	 *   ensures validation of tuples when starting from
+	 *   a snapshot of older version.
+	 */
+	space->engine.recover(space); /* load snapshot - begin */
+	space->engine.recover(space); /* load snapshot - end */
+	space->engine.recover(space); /* build secondary keys */
+	return space;
+}
+
 /**
  * Initialize a prototype for the two mandatory data
  * dictionary spaces and create a cache entry for them.
@@ -134,8 +196,46 @@ schema_init()
 {
 	/* Initialize the space cache. */
 	spaces = mh_i32ptr_new();
-	space_config();
-	space_foreach(do_one_recover_step, NULL);
+	/*
+	 * Create surrogate space objects for the mandatory system
+	 * spaces (the primal eggs from which we get all the
+	 * chicken). Their definitions will be overwritten by the
+	 * data in the snapshot, and they will thus be
+	 * *re-created* during recovery.  Note, the index type
+	 * must be TREE and space identifiers must be the smallest
+	 * one to ensure that these spaces are always recovered
+	 * (and re-created) first.
+	 */
+	/* _schema - key/value space with schema description */
+	struct space_def space_def = { SC_SCHEMA_ID, 0 };
+	struct key_def *key_def = key_def_new(0 /* id */,
+					      TREE /* index type */,
+					      true /* unique */,
+					      1 /* part count */);
+	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */, STRING);
+	(void) sc_space_new(&space_def, key_def, NULL);
+
+	/* _space - home for all spaces. */
+	space_def.id = SC_SPACE_ID;
+	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */, NUM);
+
+	(void) sc_space_new(&space_def, key_def,
+			    &alter_space_on_replace_space);
+	key_def_delete(key_def);
+
+	/* _index - definition of indexes in all spaces */
+	key_def = key_def_new(0 /* id */,
+			      TREE /* index type */,
+			      true /* unique */,
+			      2 /* part count */);
+	/* space no */
+	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */, NUM);
+	/* index no */
+	key_def_set_part(key_def, 1 /* part no */, 1 /* field no */, NUM);
+	space_def.id = SC_INDEX_ID;
+	(void) sc_space_new(&space_def, key_def,
+			    &alter_space_on_replace_index);
+	key_def_delete(key_def);
 }
 
 void
@@ -173,263 +273,3 @@ schema_free(void)
 	}
 	mh_i32ptr_delete(spaces);
 }
-
-struct key_def *
-key_def_new_from_cfg(uint32_t id,
-		     struct tarantool_cfg_space_index *cfg_index)
-{
-	uint32_t part_count = 0;
-	enum index_type type = STR2ENUM(index_type, cfg_index->type);
-
-	if (type == index_type_MAX)
-		tnt_raise(LoggedError, ER_INDEX_TYPE, cfg_index->type);
-
-	/* Find out key part count. */
-	for (uint32_t k = 0; cfg_index->key_field[k] != NULL; ++k) {
-		auto cfg_key = cfg_index->key_field[k];
-
-		if (cfg_key->fieldno == -1) {
-			/* last filled key reached */
-			break;
-		}
-		part_count++;
-	}
-
-	struct key_def *key= key_def_new(id, type, cfg_index->unique,
-					 part_count);
-
-	for (uint32_t k = 0; k < part_count; k++) {
-		auto cfg_key = cfg_index->key_field[k];
-
-		key_def_set_part(key, k, cfg_key->fieldno,
-				 STR2ENUM(field_type, cfg_key->type));
-	}
-	return key;
-}
-
-static void
-space_config()
-{
-	extern tarantool_cfg cfg;
-	/* exit if no spaces are configured */
-	if (cfg.space == NULL) {
-		return;
-	}
-
-	/* fill box spaces */
-	for (uint32_t i = 0; cfg.space[i] != NULL; ++i) {
-		struct space_def space_def;
-		space_def.id = i;
-		tarantool_cfg_space *cfg_space = cfg.space[i];
-
-		if (!CNF_STRUCT_DEFINED(cfg_space) || !cfg_space->enabled)
-			continue;
-
-		assert(cfg.memcached_port == 0 || i != cfg.memcached_space);
-
-		space_def.arity = (cfg_space->arity != -1 ?
-				   cfg_space->arity : 0);
-
-		struct rlist key_defs;
-		rlist_create(&key_defs);
-		struct key_def *key;
-
-		for (uint32_t j = 0; cfg_space->index[j] != NULL; ++j) {
-			auto cfg_index = cfg_space->index[j];
-			key = key_def_new_from_cfg(j, cfg_index);
-			key_list_add_key(&key_defs, key);
-		}
-		space_cache_replace(space_new(&space_def, &key_defs));
-
-		say_info("space %i successfully configured", i);
-	}
-}
-
-int
-check_spaces(struct tarantool_cfg *conf)
-{
-	/* exit if no spaces are configured */
-	if (conf->space == NULL) {
-		return 0;
-	}
-
-	for (size_t i = 0; conf->space[i] != NULL; ++i) {
-		auto space = conf->space[i];
-
-		if (i >= BOX_SPACE_MAX) {
-			out_warning(CNF_OK, "(space = %zu) invalid id, (maximum=%u)",
-				    i, BOX_SPACE_MAX);
-			return -1;
-		}
-
-		if (!CNF_STRUCT_DEFINED(space)) {
-			/* space undefined, skip it */
-			continue;
-		}
-
-		if (!space->enabled) {
-			/* space disabled, skip it */
-			continue;
-		}
-
-		if (conf->memcached_port && i == conf->memcached_space) {
-			out_warning(CNF_OK, "Space %zu is already used as "
-				    "memcached_space.", i);
-			return -1;
-		}
-
-		/* at least one index in space must be defined
-		 * */
-		if (space->index == NULL) {
-			out_warning(CNF_OK, "(space = %zu) "
-				    "at least one index must be defined", i);
-			return -1;
-		}
-
-		uint32_t max_key_fieldno = 0;
-
-		/* check spaces indexes */
-		for (size_t j = 0; space->index[j] != NULL; ++j) {
-			auto index = space->index[j];
-			uint32_t key_part_count = 0;
-			enum index_type index_type;
-
-			/* check index bound */
-			if (j >= BOX_INDEX_MAX) {
-				/* maximum index in space reached */
-				out_warning(CNF_OK, "(space = %zu index = %zu) "
-					    "too many indexed (%u maximum)", i, j, BOX_INDEX_MAX);
-				return -1;
-			}
-
-			/* at least one key in index must be defined */
-			if (index->key_field == NULL) {
-				out_warning(CNF_OK, "(space = %zu index = %zu) "
-					    "at least one field must be defined", i, j);
-				return -1;
-			}
-
-			/* check unique property */
-			if (index->unique == -1) {
-				/* unique property undefined */
-				out_warning(CNF_OK, "(space = %zu index = %zu) "
-					    "unique property is undefined", i, j);
-			}
-
-			for (size_t k = 0; index->key_field[k] != NULL; ++k) {
-				auto key = index->key_field[k];
-
-				if (key->fieldno == -1) {
-					/* last key reached */
-					break;
-				}
-
-				if (key->fieldno >= BOX_FIELD_MAX) {
-					/* maximum index in space reached */
-					out_warning(CNF_OK, "(space = %zu index = %zu) "
-						    "invalid field number (%u maximum)",
-						    i, j, BOX_FIELD_MAX);
-					return -1;
-				}
-
-				/* key must has valid type */
-				if (STR2ENUM(field_type, key->type) == field_type_MAX) {
-					out_warning(CNF_OK, "(space = %zu index = %zu) "
-						    "unknown field data type: `%s'", i, j, key->type);
-					return -1;
-				}
-
-				if (max_key_fieldno < key->fieldno + 1) {
-					max_key_fieldno = key->fieldno + 1;
-				}
-
-				++key_part_count;
-			}
-
-			/* Check key part count. */
-			if (key_part_count == 0) {
-				out_warning(CNF_OK, "(space = %zu index = %zu) "
-					    "at least one field must be defined", i, j);
-				return -1;
-			}
-
-			index_type = STR2ENUM(index_type, index->type);
-
-			/* check index type */
-			if (index_type == index_type_MAX) {
-				out_warning(CNF_OK, "(space = %zu index = %zu) "
-					    "unknown index type '%s'", i, j, index->type);
-				return -1;
-			}
-
-			/* First index must be unique. */
-			if (j == 0 && index->unique == false) {
-				out_warning(CNF_OK, "(space = %zu) space first index must be unique", i);
-				return -1;
-			}
-
-			switch (index_type) {
-			case HASH:
-				/* check hash index */
-				/* hash index must be unique */
-				if (!index->unique) {
-					out_warning(CNF_OK, "(space = %zu index = %zu) "
-						    "hash index must be unique", i, j);
-					return -1;
-				}
-				break;
-			case TREE:
-				/* extra check for tree index not needed */
-				break;
-			case BITSET:
-				/* check bitset index */
-				/* bitset index must has single-field key */
-				if (key_part_count != 1) {
-					out_warning(CNF_OK, "(space = %zu index = %zu) "
-						    "bitset index must has a single-field key", i, j);
-					return -1;
-				}
-				/* bitset index must not be unique */
-				if (index->unique) {
-					out_warning(CNF_OK, "(space = %zu index = %zu) "
-						    "bitset index must be non-unique", i, j);
-					return -1;
-				}
-				break;
-			default:
-				assert(false);
-			}
-		}
-
-		/* Check for index field type conflicts */
-		if (max_key_fieldno > 0) {
-			char *types = (char *) alloca(max_key_fieldno);
-			memset(types, 0, max_key_fieldno);
-			for (size_t j = 0; space->index[j] != NULL; ++j) {
-				auto index = space->index[j];
-				for (size_t k = 0; index->key_field[k] != NULL; ++k) {
-					auto key = index->key_field[k];
-					if (key->fieldno == -1)
-						break;
-
-					uint32_t f = key->fieldno;
-					enum field_type t = STR2ENUM(field_type, key->type);
-					assert(t != field_type_MAX);
-					if (types[f] != t) {
-						if (types[f] == UNKNOWN) {
-							types[f] = t;
-						} else {
-							out_warning(CNF_OK, "(space = %zu fieldno = %zu) "
-								    "index field type mismatch", i, f);
-							return -1;
-						}
-					}
-				}
-
-			}
-		}
-	}
-
-	return 0;
-}
-
diff --git a/src/box/schema.h b/src/box/schema.h
index 89a9fbda24d213e5590737c11828d627b4c91465..15eb1653b84dc7156e3b4537ec52322415e3e523 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -30,6 +30,22 @@
  */
 #include "exception.h"
 
+enum schema_id {
+	/** Start of the reserved range of system spaces. */
+	SC_SYSTEM_ID_MIN = 256,
+	/** Space id of _schema. */
+	SC_SCHEMA_ID = 272,
+	/** Space id of _space. */
+	SC_SPACE_ID = 280,
+	/** Space id of _index. */
+	SC_INDEX_ID = 288,
+	/** End of the reserved range of system spaces. */
+	SC_SYSTEM_ID_MAX = 511
+};
+
+enum schema_field_id {
+};
+
 struct space;
 
 /** Call a visitor function on every space in the space cache. */
@@ -38,10 +54,11 @@ space_foreach(void (*func)(struct space *sp, void *udata), void *udata);
 
 /**
  * Try to look up a space by space number in the space cache.
+ * FFI-friendly no-exception-thrown space lookup function.
  *
  * @return NULL if space not found, otherwise space object.
  */
-struct space *
+extern "C" struct space *
 space_by_id(uint32_t id);
 
 static inline struct space *
@@ -66,6 +83,9 @@ space_cache_replace(struct space *space);
 struct space *
 space_cache_delete(uint32_t id);
 
+bool
+space_is_system(struct space *space);
+
 void
 schema_init();
 
@@ -86,9 +106,7 @@ space_end_recover_snapshot();
 void
 space_end_recover();
 
-struct tarantool_cfg;
+struct space *schema_space(uint32_t id);
 
-int
-check_spaces(struct tarantool_cfg *conf);
 
 #endif /* INCLUDES_TARANTOOL_BOX_SCHEMA_H */
diff --git a/src/box/space.cc b/src/box/space.cc
index 186a701358b7bcf87d2913e391fde8b04f868a04..9aca827cc003581734ea154ecc5bcc2022e694bc 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -80,6 +80,8 @@ space_new(struct space_def *space_def, struct rlist *key_list)
 	}
 	space_fill_index_map(space);
 	space->engine = engine_no_keys;
+	rlist_create(&space->on_replace);
+	space->run_triggers = true;
 	return space;
 error:
 	space_delete(space);
@@ -186,6 +188,12 @@ space_replace_all_keys(struct space *space, struct tuple *old_tuple,
 	return NULL;
 }
 
+uint32_t
+space_size(struct space *space)
+{
+	return space_index(space, 0)->size();
+}
+
 /**
  * Secondary indexes are built in bulk after all data is
  * recovered. This function enables secondary keys on a space.
@@ -289,4 +297,29 @@ space_validate_tuple(struct space *sp, struct tuple *new_tuple)
 
 }
 
+void
+space_dump_def(const struct space *space, struct rlist *key_list)
+{
+	rlist_create(key_list);
+
+	for (int j = 0; j < space->index_count; j++)
+		rlist_add_tail_entry(key_list, space->index[j]->key_def,
+				     link);
+}
+
+void
+space_swap_index(struct space *lhs, struct space *rhs, uint32_t lhs_id,
+		 uint32_t rhs_id)
+{
+	Index *tmp = lhs->index_map[lhs_id];
+	lhs->index_map[lhs_id] = rhs->index_map[rhs_id];
+	rhs->index_map[rhs_id] = tmp;
+}
+
+extern "C" void
+space_run_triggers(struct space *space, bool yesno)
+{
+	space->run_triggers = yesno;
+}
+
 /* vim: set fm=marker */
diff --git a/src/box/space.h b/src/box/space.h
index 91aa007054bb96fc203864afdf08604bc8f81a63..fa494e1bdb92b2cea8d5d1df2164e4d8768f137a 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -30,6 +30,7 @@
  */
 #include "index.h"
 #include "key_def.h"
+#include "rlist.h"
 #include <exception.h>
 
 typedef void (*space_f)(struct space *space);
@@ -83,6 +84,9 @@ struct space {
 	 * deletion and addition of indexes.
 	 */
 	struct engine engine;
+
+	/** Triggers fired after space_replace() -- see txn_replace(). */
+	struct rlist on_replace;
 	/**
 	 * The number of *enabled* indexes in the space.
 	 *
@@ -99,6 +103,8 @@ struct space {
 	uint32_t index_id_max;
 	/** Space meta. */
 	struct space_def def;
+	/** Enable/disable triggers. */
+	bool run_triggers;
 
 	/** Default tuple format used by this space */
 	struct tuple_format *format;
@@ -213,6 +219,9 @@ space_replace(struct space *space, struct tuple *old_tuple,
 				     mode);
 }
 
+uint32_t
+space_size(struct space *space);
+
 /**
  * Check that the tuple has correct arity and correct field
  * types (a pre-requisite for an INSERT).
@@ -232,6 +241,26 @@ space_new(struct space_def *space_def, struct rlist *key_list);
 void
 space_delete(struct space *space);
 
+/**
+ * Dump space definition (key definitions, key count)
+ * for ALTER.
+ */
+void
+space_dump_def(const struct space *space, struct rlist *key_list);
+
+/**
+ * Exchange two index objects in two spaces. Used
+ * to update a space with a newly built index, while
+ * making sure the old index doesn't leak.
+ */
+void
+space_swap_index(struct space *lhs, struct space *rhs, uint32_t lhs_id,
+		 uint32_t rhs_id);
+
+/** Rebuild index map in a space after a series of swap index. */
+void
+space_fill_index_map(struct space *space);
+
 /**
  * Get index by index id.
  * @return NULL if the index is not found.
@@ -258,4 +287,7 @@ index_find(struct space *space, uint32_t index_id)
 	return index;
 }
 
+extern "C" void
+space_run_triggers(struct space *space, bool yesno);
+
 #endif /* TARANTOOL_BOX_SPACE_H_INCLUDED */
diff --git a/src/box/tuple.cc b/src/box/tuple.cc
index ed370b1aee59791f3b960852ddcd84c3937a338d..d9b458b0deba9e8a040903e7a27c35c8fdf20976 100644
--- a/src/box/tuple.cc
+++ b/src/box/tuple.cc
@@ -341,6 +341,8 @@ const char *
 tuple_seek(struct tuple_iterator *it, uint32_t i, uint32_t *len)
 {
 	it->pos = tuple_field_old(tuple_format(it->tuple), it->tuple, i);
+	it->fieldno = it->pos == it->tuple->data + it->tuple->bsize ?
+		it->tuple->field_count : i;
 	return tuple_next(it, len);
 }
 
@@ -353,11 +355,69 @@ tuple_next(struct tuple_iterator *it, uint32_t *len)
 		const char *field = it->pos;
 		it->pos += *len;
 		assert(it->pos <= tuple_end);
+		it->fieldno++;
 		return field;
 	}
 	return NULL;
 }
 
+static const char *
+tuple_field_to_cstr(const char *field, uint32_t len, uint32_t field_index)
+{
+	if (field == NULL)
+		tnt_raise(ClientError, ER_NO_SUCH_FIELD, field_index);
+	static __thread char buf[256];
+	len = MIN(len, sizeof(buf) - 1);
+	memcpy(buf, field, len);
+	buf[len] = '\0';
+	return buf;
+}
+
+static uint32_t
+tuple_field_to_u32(const char *field, uint32_t len, uint32_t field_index)
+{
+	if (field == NULL)
+		tnt_raise(ClientError, ER_NO_SUCH_FIELD, field_index);
+	if (len != sizeof(uint32_t))
+		tnt_raise(ClientError, ER_FIELD_TYPE, field_index,
+			  field_type_strs[NUM]);
+	return pick_u32(&field, field + len);
+}
+
+const char *
+tuple_next_cstr(struct tuple_iterator *it)
+{
+	uint32_t len;
+	int fieldno = it->fieldno;
+	const char *field = tuple_next(it, &len);
+	return tuple_field_to_cstr(field, len, fieldno);
+}
+
+uint32_t
+tuple_next_u32(struct tuple_iterator *it)
+{
+	uint32_t len;
+	int fieldno = it->fieldno;
+	const char *field = tuple_next(it, &len);
+	return tuple_field_to_u32(field, len, fieldno);
+}
+
+uint32_t
+tuple_field_u32(struct tuple *tuple, uint32_t i)
+{
+	uint32_t len;
+	const char *field = tuple_field(tuple, i, &len);
+	return tuple_field_to_u32(field, len, i);
+}
+
+const char *
+tuple_field_cstr(struct tuple *tuple, uint32_t i)
+{
+	uint32_t len;
+	const char *field = tuple_field(tuple, i, &len);
+	return tuple_field_to_cstr(field, len, i);
+}
+
 /** print field to tbuf */
 static void
 print_field(struct tbuf *buf, const char *field, uint32_t len)
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 2ea4442ab48b6438fb89d0f073e87f481e3cc8ed..8e55eecc4a8bf818cd8da30120b378114586e280 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -175,6 +175,16 @@ tuple_new(struct tuple_format *format, uint32_t field_count,
 void
 tuple_ref(struct tuple *tuple, int count);
 
+void
+tuple_delete(struct tuple *tuple);
+
+/** Make tuple references exception-friendly in absence of @finally. */
+struct TupleGuard {
+	struct tuple *tuple;
+	TupleGuard(struct tuple *arg) :tuple(arg) {}
+	~TupleGuard() { if (tuple->refs == 0) tuple_delete(tuple); }
+};
+
 /**
 * @brief Return a tuple format instance
 * @param tuple tuple
@@ -217,6 +227,20 @@ tuple_field(const struct tuple *tuple, uint32_t i, uint32_t *len)
 	return NULL;
 }
 
+/**
+ * A convenience shortcut for data dictionary - get a tuple field
+ * as uint32_t.
+ */
+uint32_t
+tuple_field_u32(struct tuple *tuple, uint32_t i);
+
+/**
+ * A convenience shortcut for data dictionary - get a tuple field
+ * as a NUL-terminated string - returns a string of up to 256 bytes.
+ */
+const char *
+tuple_field_cstr(struct tuple *tuple, uint32_t i);
+
 /**
  * @brief Tuple Interator
  */
@@ -227,6 +251,8 @@ struct tuple_iterator {
 	/** Always points to the beginning of the next field. */
 	const char *pos;
 	/** @endcond **/
+	/** field no of the next field. */
+	int fieldno;
 };
 
 /**
@@ -251,6 +277,7 @@ tuple_rewind(struct tuple_iterator *it, const struct tuple *tuple)
 {
 	it->tuple = tuple;
 	it->pos = tuple->data;
+	it->fieldno = 0;
 }
 
 /**
@@ -270,6 +297,22 @@ tuple_seek(struct tuple_iterator *it, uint32_t field_no, uint32_t *len);
 const char *
 tuple_next(struct tuple_iterator *it, uint32_t *len);
 
+/**
+ * A convenience shortcut for the data dictionary - get next field
+ * from iterator as uint32_t or raise an error if there is
+ * no next field.
+ */
+uint32_t
+tuple_next_u32(struct tuple_iterator *it);
+
+/**
+ * A convenience shortcut for the data dictionary - get next field
+ * from iterator as a C string or raise an error if there is no
+ * next field.
+ */
+const char *
+tuple_next_cstr(struct tuple_iterator *it);
+
 /**
  * @brief Print a tuple in yaml-compatible mode to tbuf:
  * key: { value, value, value }
@@ -304,7 +347,6 @@ tuple_range_size(const char **begin, const char *end, uint32_t count)
 	return *begin - start;
 }
 
-void tuple_delete(struct tuple *tuple);
 
 /**
  * @brief Compare two tuples using field by field using key definition
diff --git a/src/box/txn.cc b/src/box/txn.cc
index 0214a4e5150499a05364c155d20c08385a63dd40..96f4f2316c911b13a682a5e2a80fbd972f466fa7 100644
--- a/src/box/txn.cc
+++ b/src/box/txn.cc
@@ -62,12 +62,20 @@ txn_replace(struct txn *txn, struct space *space,
 		tuple_ref(txn->new_tuple, 1);
 	}
 	txn->space = space;
+	/*
+	 * Run on_replace triggers. For now, disallow mutation
+	 * of tuples in the trigger.
+	 */
+	if (! rlist_empty(&space->on_replace) && space->run_triggers)
+		trigger_run(&space->on_replace, txn);
 }
 
 struct txn *
 txn_begin()
 {
 	struct txn *txn = (struct txn *) p0alloc(fiber->gc_pool, sizeof(*txn));
+	rlist_create(&txn->on_commit);
+	rlist_create(&txn->on_rollback);
 	return txn;
 }
 
@@ -93,8 +101,14 @@ txn_commit(struct txn *txn)
 			tnt_raise(LoggedError, ER_WAL_IO);
 
 	}
+	trigger_run(&txn->on_commit, txn); /* must not throw. */
 }
 
+/**
+ * txn_finish() follows txn_commit() on success.
+ * It's moved to a separate call to be able to send
+ * old tuple to the user before it's deleted.
+ */
 void
 txn_finish(struct txn *txn)
 {
@@ -107,7 +121,9 @@ void
 txn_rollback(struct txn *txn)
 {
 	if (txn->old_tuple || txn->new_tuple) {
-		space_replace(txn->space, txn->new_tuple, txn->old_tuple, DUP_INSERT);
+		space_replace(txn->space, txn->new_tuple,
+			      txn->old_tuple, DUP_INSERT);
+		trigger_run(&txn->on_rollback, txn); /* must not throw. */
 		if (txn->new_tuple)
 			tuple_ref(txn->new_tuple, -1);
 	}
diff --git a/src/box/txn.h b/src/box/txn.h
index 32520ca68d5336158e9576aacbba59466a6b0aff..3d6526d37fdb8eb819c81dd328e8b0fdd8fc0296 100644
--- a/src/box/txn.h
+++ b/src/box/txn.h
@@ -29,6 +29,7 @@
  * SUCH DAMAGE.
  */
 #include "index.h"
+#include "trigger.h"
 
 struct tuple;
 struct space;
@@ -39,6 +40,9 @@ struct txn {
 	struct tuple *old_tuple;
 	struct tuple *new_tuple;
 
+	struct rlist on_commit;
+	struct rlist on_rollback;
+
 	/* Redo info: binary packet */
 	const char *data;
 	uint32_t len;
diff --git a/src/lua/init.cc b/src/lua/init.cc
index 9462fc01939dc12daeb7176807409edfd1b77adf..aef9371b39e39a406f2e21e290dc63452b75e05d 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -528,7 +528,7 @@ tarantool_lua_init()
 	tarantool_lua_setpath(L, "cpath", LUA_LIBCPATH,
 	                      LUA_SYSCPATH, NULL);
 
-	/* Loadi 'ffi' extension and make it inaccessible */
+	/* Load 'ffi' extension and make it inaccessible */
 	lua_getglobal(L, "require");
 	lua_pushstring(L, "ffi");
 	if (lua_pcall(L, 1, 0, 0) != 0)
diff --git a/src/memcached.cc b/src/memcached.cc
index 3aa6836ef8eec77521612be1f70a0c42f3fdddc0..fc28c516c65bbc70013cdbc90d18a2404772b551 100644
--- a/src/memcached.cc
+++ b/src/memcached.cc
@@ -33,10 +33,10 @@
 
 #include "box/box.h"
 #include "box/request.h"
+#include "box/schema.h"
 #include "box/space.h"
 #include "box/port.h"
 #include "box/tuple.h"
-#include "box/schema.h"
 #include "fiber.h"
 extern "C" {
 #include <cfg/warning.h>
@@ -59,7 +59,6 @@ ENUM(memcached_stat, STAT);
 STRS(memcached_stat, STAT);
 
 static int stat_base;
-static struct fiber *memcached_expire = NULL;
 
 static Index *memcached_index;
 static struct iterator *memcached_it;
@@ -477,6 +476,8 @@ memcached_free(void)
 		memcached_it->free(memcached_it);
 }
 
+void
+memcached_start_expire();
 
 void
 memcached_init(const char *bind_ipaddr, int memcached_port)
@@ -488,15 +489,13 @@ memcached_init(const char *bind_ipaddr, int memcached_port)
 
 	stat_base = stat_register(memcached_stat_strs, memcached_stat_MAX);
 
-	struct space *sp = space_by_id(cfg.memcached_space);
-	memcached_index = space_index(sp, 0);
-
 	/* run memcached server */
 	static struct coio_service memcached;
 	coio_service_init(&memcached, "memcached",
 			  bind_ipaddr, memcached_port,
 			  memcached_handler, NULL);
 	evio_service_start(&memcached.evio_service);
+	memcached_start_expire();
 }
 
 void
@@ -555,6 +554,7 @@ memcached_delete_expired_keys(struct tbuf *keys_to_delete)
 void
 memcached_expire_loop(va_list ap __attribute__((unused)))
 {
+	memcached_space_init();
 	struct tuple *tuple = NULL;
 
 	say_info("memcached expire fiber started");
@@ -593,10 +593,10 @@ memcached_expire_loop(va_list ap __attribute__((unused)))
 
 void memcached_start_expire()
 {
+	struct fiber *memcached_expire;
 	if (cfg.memcached_port == 0 || cfg.memcached_expire == 0)
 		return;
 
-	assert(memcached_expire == NULL);
 	try {
 		memcached_expire = fiber_new("memcached_expire",
 						memcached_expire_loop);
@@ -607,11 +607,13 @@ void memcached_start_expire()
 	fiber_call(memcached_expire);
 }
 
-void memcached_stop_expire()
+int
+memcached_reload_config(struct tarantool_cfg *oldcfg,
+			struct tarantool_cfg *newcfg)
 {
-	if (cfg.memcached_port == 0 || cfg.memcached_expire == 0)
-		return;
-	assert(memcached_expire != NULL);
-	fiber_cancel(memcached_expire);
-	memcached_expire = NULL;
+	(void) oldcfg;
+	(void) newcfg;
+	if (newcfg->memcached_port == 0)
+		return 0;
+	return -1;
 }
diff --git a/src/tarantool.cc b/src/tarantool.cc
index 12ff10034021df8bc089ff11e5843906d1312802..f966168b80c70ceb480b41804c73d17d2c016b57 100644
--- a/src/tarantool.cc
+++ b/src/tarantool.cc
@@ -169,6 +169,10 @@ load_cfg(struct tarantool_cfg *conf, int32_t check_rdonly)
 	if (replication_check_config(conf) != 0)
 		return -1;
 
+	/* check memcached configuration */
+	if (memcached_check_config(conf) != 0)
+		return -1;
+
 	return box_check_config(conf);
 }
 
@@ -279,6 +283,9 @@ reload_cfg(struct tbuf *out)
 	/* Now pass the config to the module, to take action. */
 	if (box_reload_config(&cfg, &new_cfg) != 0)
 		return -1;
+
+	if (memcached_reload_config(&cfg, &new_cfg) != 0)
+		return -1;
 	/* All OK, activate the config. */
 	swap_tarantool_cfg(&cfg, &new_cfg);
 	tarantool_lua_load_cfg(tarantool_L, &cfg);
@@ -857,16 +864,12 @@ main(int argc, char **argv)
 		tarantool_L = tarantool_lua_init();
 		box_init();
 		atexit(tarantool_lua_free);
-		memcached_init(cfg.bind_ipaddr, cfg.memcached_port);
 		tarantool_lua_load_cfg(tarantool_L, &cfg);
 		/*
-		 * init iproto before admin and after memcached:
+		 * init iproto before admin:
 		 * recovery is finished on bind to the primary port,
 		 * and it has to happen before requests on the
 		 * administrative port start to arrive.
-		 * And when recovery is finalized, memcached
-		 * expire loop is started, so binding can happen
-		 * only after memcached is initialized.
 		 */
 		iproto_init(cfg.bind_ipaddr, cfg.primary_port,
 			    cfg.secondary_port);
@@ -880,6 +883,12 @@ main(int argc, char **argv)
 		 * initialized.
 		 */
 		tarantool_lua_load_init_script(tarantool_L);
+		/*
+		 * And when recovery is finalized, memcached
+		 * expire loop is started, so binding can happen
+		 * only after memcached is initialized.
+		 */
+		memcached_init(cfg.bind_ipaddr, cfg.memcached_port);
 		prelease(fiber->gc_pool);
 		say_crit("log level %i", cfg.log_level);
 		say_crit("entering the event loop");
diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py
index 34cbe57003bb885a83535c4b9ec96221c818e2b0..194668fc5b498beaeddd3c537b2787e869c06516 100644
--- a/test/lib/sql_ast.py
+++ b/test/lib/sql_ast.py
@@ -33,7 +33,7 @@ ER = {
     5: "ER_INDEX_TYPE"          ,
     6: "ER_SPACE_EXISTS"        ,
     7: "ER_MEMORY_ISSUE"        ,
-    8: "ER_UNUSED8"             ,
+    8: "ER_CREATE_SPACE"        ,
     9: "ER_INJECTION"           ,
    10: "ER_UNSUPPORTED"         ,
    11: "ER_RESERVED11"          ,
@@ -49,13 +49,13 @@ ER = {
    21: "ER_RESERVED21"          ,
    22: "ER_RESERVED22"          ,
    23: "ER_RESERVED23"          ,
-   24: "ER_UNUSED24"            ,
-   25: "ER_UNUSED25"            ,
+   24: "ER_DROP_SPACE"          ,
+   25: "ER_ALTER_SPACE"         ,
    26: "ER_FIBER_STACK"         ,
-   27: "ER_UNUSED27"            ,
+   27: "ER_MODIFY_INDEX"        ,
    28: "ER_TUPLE_FORMAT_LIMIT"  ,
-   29: "ER_UNUSED29"            ,
-   30: "ER_UNUSED30"            ,
+   29: "ER_LAST_DROP"           ,
+   30: "ER_DROP_PRIMARY_KEY"    ,
    31: "ER_UNUSED31"            ,
    32: "ER_UNUSED32"            ,
    33: "ER_UNUSED33"            ,