diff --git a/src/box/alter.cc b/src/box/alter.cc
index 095131e2c5b48db5c321e839c2af09aff91fe6fc..c48e89f9e65a5c0df3dcbc6c2a0d7c9f6e8c5be7 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1113,7 +1113,8 @@ ModifyIndex::alter(struct alter_space *alter)
 	struct index *new_index = space_index(alter->new_space,
 					      new_index_def->iid);
 	assert(new_index != NULL);
-	index_def_swap(old_index->def, new_index->def);
+	SWAP(old_index->def, new_index->def);
+	index_update_def(new_index);
 }
 
 void
@@ -1131,7 +1132,8 @@ ModifyIndex::rollback(struct alter_space *alter)
 	struct index *new_index = space_index(alter->new_space,
 					      new_index_def->iid);
 	assert(new_index != NULL);
-	index_def_swap(old_index->def, new_index->def);
+	SWAP(old_index->def, new_index->def);
+	index_update_def(old_index);
 }
 
 ModifyIndex::~ModifyIndex()
diff --git a/src/box/index.cc b/src/box/index.cc
index d124583697cecb803d563b28f937d5891f79fffb..69fc76116e878f0625da66896098d49dd37da5ad 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -545,6 +545,11 @@ generic_index_commit_drop(struct index *)
 {
 }
 
+void
+generic_index_update_def(struct index *)
+{
+}
+
 ssize_t
 generic_index_size(struct index *index)
 {
diff --git a/src/box/index.h b/src/box/index.h
index 54a27f6f3bbc149581c05683ccac0188fd8e9f35..74b1c9ae94052581ce59094eb4cc0929027218c0 100644
--- a/src/box/index.h
+++ b/src/box/index.h
@@ -341,6 +341,11 @@ struct index_vtab {
 	 * Must not fail.
 	 */
 	void (*commit_drop)(struct index *);
+	/**
+	 * Called after index definition update that did not
+	 * require index rebuild.
+	 */
+	void (*update_def)(struct index *);
 
 	ssize_t (*size)(struct index *);
 	ssize_t (*bsize)(struct index *);
@@ -453,6 +458,12 @@ index_commit_drop(struct index *index)
 	index->vtab->commit_drop(index);
 }
 
+static inline void
+index_update_def(struct index *index)
+{
+	index->vtab->update_def(index);
+}
+
 static inline ssize_t
 index_size(struct index *index)
 {
@@ -555,6 +566,7 @@ index_end_build(struct index *index)
  */
 void generic_index_commit_create(struct index *, int64_t);
 void generic_index_commit_drop(struct index *);
+void generic_index_update_def(struct index *);
 ssize_t generic_index_size(struct index *);
 int generic_index_min(struct index *, const char *, uint32_t, struct tuple **);
 int generic_index_max(struct index *, const char *, uint32_t, struct tuple **);
diff --git a/src/box/index_def.c b/src/box/index_def.c
index 2c857295a05e7f75c1bd37660fa2e5de7196b0c7..c503c7879077f889e25634f33a8d90f87ef96735 100644
--- a/src/box/index_def.c
+++ b/src/box/index_def.c
@@ -136,20 +136,6 @@ index_def_dup(const struct index_def *def)
 	return dup;
 }
 
-void
-index_def_swap(struct index_def *def1, struct index_def *def2)
-{
-	/*
-	 * Swap const-size items and name. Keep the original key
-	 * definitions, they are used in the engines.
-	 */
-	struct index_def tmp_def = *def1;
-	memcpy(def1, def2, offsetof(struct index_def, key_def));
-	memcpy(def2, &tmp_def, offsetof(struct index_def, key_def));
-	key_def_swap(def1->key_def, def2->key_def);
-	key_def_swap(def1->cmp_def, def2->cmp_def);
-}
-
 /** Free a key definition. */
 void
 index_def_delete(struct index_def *index_def)
@@ -176,7 +162,7 @@ index_def_change_requires_rebuild(const struct index_def *old_index_def,
 {
 	if (old_index_def->iid != new_index_def->iid ||
 	    old_index_def->type != new_index_def->type ||
-	    old_index_def->opts.is_unique != new_index_def->opts.is_unique ||
+	    (!old_index_def->opts.is_unique && new_index_def->opts.is_unique) ||
 	    !key_part_check_compatibility(old_index_def->key_def->parts,
 					  old_index_def->key_def->part_count,
 					  new_index_def->key_def->parts,
diff --git a/src/box/index_def.h b/src/box/index_def.h
index cb191e7a571b9944b1131a124978fc05606d3f22..18f941364d0f56ed4a4d2552ce4ac9d545b6fef0 100644
--- a/src/box/index_def.h
+++ b/src/box/index_def.h
@@ -159,9 +159,6 @@ struct index_def {
 struct index_def *
 index_def_dup(const struct index_def *def);
 
-void
-index_def_swap(struct index_def *def1, struct index_def *def2);
-
 /* Destroy and free an index_def. */
 void
 index_def_delete(struct index_def *def);
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 3aad8bf0a620058210db70ff53f08fe27c0dec3f..f7f6c753a1d9cea5291504038d7098f988da8f4f 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -528,23 +528,6 @@ key_def_merge(const struct key_def *first, const struct key_def *second)
 	return new_def;
 }
 
-void
-key_def_swap(struct key_def *a, struct key_def *b)
-{
-	assert(a->part_count == b->part_count);
-	char buf[sizeof(*a)];
-	/* Swap fixed members. */
-	memcpy(buf, a, sizeof(*a));
-	memcpy(a, b, sizeof(*a));
-	memcpy(b, buf, sizeof(*a));
-	/* Swap parts. */
-	for (uint32_t i = 0; i < a->part_count; ++i) {
-		struct key_part tmp = a->parts[i];
-		a->parts[i] = b->parts[i];
-		b->parts[i] = tmp;
-	}
-}
-
 int
 key_validate_parts(const struct key_def *key_def, const char *key,
 		   uint32_t part_count, bool allow_nullable)
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 83af70df464fd9201bb72680bf325e7d57d2633b..201863f5a6b69b7f009f1c403ad7233b00544575 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -271,10 +271,6 @@ key_def_find(const struct key_def *key_def, uint32_t fieldno);
 struct key_def *
 key_def_merge(const struct key_def *first, const struct key_def *second);
 
-/** Swap content of key defs. */
-void
-key_def_swap(struct key_def *a, struct key_def *b);
-
 /*
  * Check that parts of the key match with the key definition.
  * @param key_def Key definition.
diff --git a/src/box/memtx_bitset.c b/src/box/memtx_bitset.c
index db7fa1f0eac987ee8bd3906868129519db468083..9216ed86c096a22d85c889823797c929c87698e1 100644
--- a/src/box/memtx_bitset.c
+++ b/src/box/memtx_bitset.c
@@ -458,6 +458,7 @@ static const struct index_vtab memtx_bitset_index_vtab = {
 	/* .destroy = */ memtx_bitset_index_destroy,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ generic_index_update_def,
 	/* .size = */ memtx_bitset_index_size,
 	/* .bsize = */ memtx_bitset_index_bsize,
 	/* .min = */ generic_index_min,
diff --git a/src/box/memtx_hash.c b/src/box/memtx_hash.c
index 3a37293ea86f80d95cf17533d20217d8b9e016c4..78f55eb7d4177e112ecc41b3d8d607dbf9dbea7c 100644
--- a/src/box/memtx_hash.c
+++ b/src/box/memtx_hash.c
@@ -376,6 +376,7 @@ static const struct index_vtab memtx_hash_index_vtab = {
 	/* .destroy = */ memtx_hash_index_destroy,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ generic_index_update_def,
 	/* .size = */ memtx_hash_index_size,
 	/* .bsize = */ memtx_hash_index_bsize,
 	/* .min = */ generic_index_min,
diff --git a/src/box/memtx_rtree.c b/src/box/memtx_rtree.c
index d52bd4d7124dbc60e8f4285296b9a21e3fac9ca9..74c1c3702b71b1531ada7e380ad2b75b9c356649 100644
--- a/src/box/memtx_rtree.c
+++ b/src/box/memtx_rtree.c
@@ -296,6 +296,7 @@ static const struct index_vtab memtx_rtree_index_vtab = {
 	/* .destroy = */ memtx_rtree_index_destroy,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ generic_index_update_def,
 	/* .size = */ memtx_rtree_index_size,
 	/* .bsize = */ memtx_rtree_index_bsize,
 	/* .min = */ generic_index_min,
diff --git a/src/box/memtx_tree.c b/src/box/memtx_tree.c
index 196a5364894fe23f38fc373986f9303db0ab4e6c..6ebdc90f5ab3390992c5f17ab4efa0635eb2afaf 100644
--- a/src/box/memtx_tree.c
+++ b/src/box/memtx_tree.c
@@ -308,6 +308,13 @@ memtx_tree_index_destroy(struct index *base)
 	free(index);
 }
 
+static void
+memtx_tree_index_update_def(struct index *base)
+{
+	struct memtx_tree_index *index = (struct memtx_tree_index *)base;
+	index->tree.arg = memtx_tree_index_cmp_def(index);
+}
+
 static ssize_t
 memtx_tree_index_size(struct index *base)
 {
@@ -575,6 +582,7 @@ static const struct index_vtab memtx_tree_index_vtab = {
 	/* .destroy = */ memtx_tree_index_destroy,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ memtx_tree_index_update_def,
 	/* .size = */ memtx_tree_index_size,
 	/* .bsize = */ memtx_tree_index_bsize,
 	/* .min = */ generic_index_min,
diff --git a/src/box/sysview_index.c b/src/box/sysview_index.c
index a76a67e444f20ea2701691179c06d8b555e2bcfe..0bec302827a94e71655ccf01b982ca650b2d1bc1 100644
--- a/src/box/sysview_index.c
+++ b/src/box/sysview_index.c
@@ -162,6 +162,7 @@ static const struct index_vtab sysview_index_vtab = {
 	/* .destroy = */ sysview_index_destroy,
 	/* .commit_create = */ generic_index_commit_create,
 	/* .commit_drop = */ generic_index_commit_drop,
+	/* .update_def = */ generic_index_update_def,
 	/* .size = */ generic_index_size,
 	/* .bsize = */ sysview_index_bsize,
 	/* .min = */ generic_index_min,
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index d69d51777625ffc2448aefa460ae6bd2f594d064..bd41829f188a2466ff1ff4d11a09dc0e9bba12e2 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -3995,6 +3995,7 @@ static const struct index_vtab vinyl_index_vtab = {
 	/* .destroy = */ vinyl_index_destroy,
 	/* .commit_create = */ vinyl_index_commit_create,
 	/* .commit_drop = */ vinyl_index_commit_drop,
+	/* .update_def = */ generic_index_update_def,
 	/* .size = */ generic_index_size,
 	/* .bsize = */ vinyl_index_bsize,
 	/* .min = */ generic_index_min,
diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result
index a0c41a1d2384f82533d303d0fdabe828aa721674..9fa3504cdfd195564e2a05392af24cece1ca48c5 100644
--- a/test/vinyl/ddl.result
+++ b/test/vinyl/ddl.result
@@ -716,3 +716,44 @@ box.space.test.index.pk
 box.space.test:drop()
 ---
 ...
+-- gh-2449 change 'unique' index property from true to false
+s = box.schema.space.create('test', { engine = 'vinyl' })
+---
+...
+_ = s:create_index('primary')
+---
+...
+_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}})
+---
+...
+s:insert{1, 10}
+---
+- [1, 10]
+...
+s.index.secondary:alter{unique = false} -- ok
+---
+...
+s.index.secondary.unique
+---
+- false
+...
+s.index.secondary:alter{unique = true} -- error
+---
+- error: Vinyl does not support changing the definition of a non-empty index
+...
+s.index.secondary.unique
+---
+- false
+...
+s:insert{2, 10}
+---
+- [2, 10]
+...
+s.index.secondary:select(10)
+---
+- - [1, 10]
+  - [2, 10]
+...
+s:drop()
+---
+...
diff --git a/test/vinyl/ddl.test.lua b/test/vinyl/ddl.test.lua
index 1761c4307f4d2f74ad84a182e41538e0675e3129..bb9e559688f1d78641164e89b8704c27ea6042eb 100644
--- a/test/vinyl/ddl.test.lua
+++ b/test/vinyl/ddl.test.lua
@@ -269,3 +269,16 @@ box.space._space:insert{512, 1, 'test', 'vinyl', 0, setmetatable({}, {__serializ
 box.space._index:insert{512, 0, 'pk', 'tree', {unique = true}, {{0, 'unsigned'}}}
 box.space.test.index.pk
 box.space.test:drop()
+
+-- gh-2449 change 'unique' index property from true to false
+s = box.schema.space.create('test', { engine = 'vinyl' })
+_ = s:create_index('primary')
+_ = s:create_index('secondary', {unique = true, parts = {2, 'unsigned'}})
+s:insert{1, 10}
+s.index.secondary:alter{unique = false} -- ok
+s.index.secondary.unique
+s.index.secondary:alter{unique = true} -- error
+s.index.secondary.unique
+s:insert{2, 10}
+s.index.secondary:select(10)
+s:drop()