diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 6c7ac2b2f11a7f2803d35e8e4289f46ef26f3a9c..08d5e374118da91019a09a12dcc9633f4e7dccbd 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -37,10 +37,13 @@ add_library(tuple STATIC
     tuple_compare.cc
     tuple_hash.cc
     key_def.cc
+    coll_def.c
+    coll.c
+    coll_cache.c
     field_def.c
     opt_def.c
 )
-target_link_libraries(tuple box_error core ${MSGPUCK_LIBRARIES} misc bit)
+target_link_libraries(tuple box_error core ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES} misc bit)
 
 add_library(xlog STATIC xlog.c)
 target_link_libraries(xlog core box_error crc32 ${ZSTD_LIBRARIES})
@@ -86,8 +89,6 @@ add_library(box STATIC
     sequence.c
     func.c
     func_def.c
-    coll_def.c
-    coll.c
     alter.cc
     schema.cc
     schema_def.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4f30624f2d564a42b8545075b51144e07fa5922a..f62fe1a99ad0ba4f1e45d1bf7b42ac5f673a563d 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -34,6 +34,7 @@
 #include "space.h"
 #include "memtx_index.h"
 #include "func.h"
+#include "coll_cache.h"
 #include "txn.h"
 #include "tuple.h"
 #include "fiber.h" /* for gc_pool */
@@ -2043,6 +2044,183 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 	}
 }
 
+/** Create a collation definition from tuple. */
+void
+coll_def_new_from_tuple(const struct tuple *tuple, struct coll_def *def)
+{
+	memset(def, 0, sizeof(*def));
+	uint32_t name_len, locale_len, type_len;
+	def->id = tuple_field_u32_xc(tuple, BOX_COLLATION_FIELD_ID);
+	def->name = tuple_field_str_xc(tuple, BOX_COLLATION_FIELD_NAME, &name_len);
+	def->name_len = name_len;
+	uint32_t owner_id = tuple_field_u32_xc(tuple, BOX_COLLATION_FIELD_UID);
+	const char *type = tuple_field_str_xc(tuple, BOX_COLLATION_FIELD_TYPE,
+					      &type_len);
+	def->type = STRN2ENUM(coll_type, type, type_len);
+	if (def->type == coll_type_MAX)
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "unknown collation type");
+	def->locale = tuple_field_str_xc(tuple, BOX_COLLATION_FIELD_LOCALE,
+					 &locale_len);
+	def->locale_len = locale_len;
+	const char *options =
+		tuple_field_with_type_xc(tuple, BOX_COLLATION_FIELD_OPTIONS,
+					 MP_MAP);
+
+	if (name_len > BOX_NAME_MAX)
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "collation name is too long");
+	if (locale_len > BOX_NAME_MAX)
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "collation locale is too long");
+
+	assert(def->type == COLL_TYPE_ICU); /* no more defined now */
+	if (opts_decode(&def->icu, coll_icu_opts_reg, &options,
+			ER_WRONG_COLLATION_OPTIONS,
+			BOX_COLLATION_FIELD_OPTIONS, NULL) != 0)
+		diag_raise();
+
+	if (def->icu.french_collation == coll_icu_on_off_MAX) {
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "ICU wrong french_collation option setting, "
+				  "expected ON | OFF");
+	}
+
+	if (def->icu.alternate_handling == coll_icu_alternate_handling_MAX) {
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "ICU wrong alternate_handling option setting, "
+				  "expected NON_IGNORABLE | SHIFTED");
+	}
+
+	if (def->icu.case_first == coll_icu_case_first_MAX) {
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "ICU wrong case_first option setting, "
+				  "expected OFF | UPPER_FIRST | LOWER_FIRST");
+	}
+
+	if (def->icu.case_level == coll_icu_on_off_MAX) {
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "ICU wrong case_level option setting, "
+				  "expected ON | OFF");
+	}
+
+	if (def->icu.normalization_mode == coll_icu_on_off_MAX) {
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "ICU wrong normalization_mode option setting, "
+				  "expected ON | OFF");
+	}
+
+	if (def->icu.strength == coll_icu_strength_MAX) {
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "ICU wrong strength option setting, "
+				  "expected PRIMARY | SECONDARY | "
+				  "TERTIARY | QUATERNARY | IDENTICAL");
+	}
+
+	if (def->icu.numeric_collation == coll_icu_on_off_MAX) {
+		tnt_raise(ClientError, ER_CANT_CREATE_COLLATION,
+			  "ICU wrong numeric_collation option setting, "
+				  "expected ON | OFF");
+	}
+
+	access_check_ddl(owner_id, SC_COLLATION);
+
+}
+
+/** Rollback change in collation space. */
+static void
+coll_cache_rollback(struct trigger *trigger, void *event)
+{
+	struct coll *old_coll = (struct coll *)trigger->data;
+	struct txn_stmt *stmt = txn_last_stmt((struct txn*) event);
+	struct tuple *new_tuple = stmt->new_tuple;
+
+	if (new_tuple != NULL) {
+		uint32_t new_id = tuple_field_u32_xc(new_tuple,
+						     BOX_COLLATION_FIELD_ID);
+		struct coll *new_coll = coll_cache_find(new_id);
+		coll_cache_delete(new_coll);
+		coll_delete(new_coll);
+	}
+
+	if (old_coll != NULL) {
+		struct coll *replaced;
+		int rc = coll_cache_replace(old_coll, &replaced);
+		assert(rc == 0 && replaced == NULL);
+		(void)rc;
+	}
+}
+
+/** Delete a collation. */
+static void
+coll_cache_delete_coll(struct trigger *trigger, void */* event */)
+{
+	struct coll *old_coll = (struct coll *)trigger->data;
+	coll_delete(old_coll);
+}
+
+/**
+ * A trigger invoked on replace in a space containing
+ * collations that a user defined.
+ */
+static void
+on_replace_dd_collation(struct trigger * /* trigger */, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	struct txn_stmt *stmt = txn_current_stmt(txn);
+	struct tuple *old_tuple = stmt->old_tuple;
+	struct tuple *new_tuple = stmt->new_tuple;
+
+	struct coll *old_coll = NULL;
+	if (old_tuple != NULL) {
+		/* TODO: Check that no index uses the collation */
+		uint32_t old_id = tuple_field_u32_xc(old_tuple,
+						     BOX_COLLATION_FIELD_ID);
+		old_coll = coll_cache_find(old_id);
+		assert(old_coll != NULL);
+		access_check_ddl(old_coll->owner_id, SC_COLLATION);
+
+		struct trigger *on_commit =
+			txn_alter_trigger_new(coll_cache_delete_coll, old_coll);
+		txn_on_commit(txn, on_commit);
+	}
+
+	if (new_tuple == NULL) {
+		/* Simple DELETE */
+		assert(old_tuple != NULL);
+		coll_cache_delete(old_coll);
+
+		struct trigger *on_rollback =
+			txn_alter_trigger_new(coll_cache_rollback, old_coll);
+		txn_on_rollback(txn, on_rollback);
+		return;
+	}
+
+	struct coll_def new_def;
+	coll_def_new_from_tuple(new_tuple, &new_def);
+	struct coll *new_coll = coll_new(&new_def);
+	if (new_coll == NULL)
+		diag_raise();
+	auto def_guard = make_scoped_guard([=] { coll_delete(new_coll); });
+
+	struct coll *replaced;
+	if (coll_cache_replace(new_coll, &replaced) != 0)
+		diag_raise();
+	if (replaced == NULL && old_coll != NULL) {
+		/*
+		 * ID of a collation was changed.
+		 * Remove collation by old ID.
+		 */
+		coll_cache_delete(old_coll);
+	}
+
+	struct trigger *on_rollback =
+		txn_alter_trigger_new(coll_cache_rollback, old_coll);
+	txn_on_rollback(txn, on_rollback);
+
+	def_guard.is_active = false;
+}
+
 /**
  * Create a privilege definition from tuple.
  */
@@ -2687,6 +2865,10 @@ struct trigger on_replace_func = {
 	RLIST_LINK_INITIALIZER, on_replace_dd_func, NULL, NULL
 };
 
+struct trigger on_replace_collation = {
+	RLIST_LINK_INITIALIZER, on_replace_dd_collation, NULL, NULL
+};
+
 struct trigger on_replace_priv = {
 	RLIST_LINK_INITIALIZER, on_replace_dd_priv, NULL, NULL
 };
diff --git a/src/box/alter.h b/src/box/alter.h
index 423648793603e006bf98ce3a97f508f184893245..fb5f65a68a31e7c575c30d0a132b415fa996b321 100644
--- a/src/box/alter.h
+++ b/src/box/alter.h
@@ -38,6 +38,7 @@ extern struct trigger on_replace_truncate;
 extern struct trigger on_replace_schema;
 extern struct trigger on_replace_user;
 extern struct trigger on_replace_func;
+extern struct trigger on_replace_collation;
 extern struct trigger on_replace_priv;
 extern struct trigger on_replace_cluster;
 extern struct trigger on_replace_sequence;
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index a8098ed560e3aace1126cf15c73c36f80d4287ad..4877919ffcfb8e5e08fd9d6bd22b12a63b9dd4a9 100644
Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ
diff --git a/src/box/coll_cache.c b/src/box/coll_cache.c
new file mode 100644
index 0000000000000000000000000000000000000000..137f5d717c25ce74de4034ae3bf8fda60423f619
--- /dev/null
+++ b/src/box/coll_cache.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2016, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "coll_cache.h"
+#include "diag.h"
+#include "assoc.h"
+
+/** mhash table (id -> collation) */
+static struct mh_i32ptr_t *coll_cache_id = NULL;
+
+/** Create global hash tables if necessary. */
+int
+coll_cache_init()
+{
+	coll_cache_id = mh_i32ptr_new();
+	if (coll_cache_id == NULL) {
+		diag_set(OutOfMemory, sizeof(*coll_cache_id), "malloc",
+			 "coll_cache");
+		return -1;
+	}
+	return 0;
+}
+
+/** Delete global hash tables. */
+void
+coll_cache_destroy()
+{
+	mh_i32ptr_delete(coll_cache_id);
+}
+
+/**
+ * Insert or replace a collation into collation cache.
+ * @param coll - collation to insert/replace.
+ * @return - NULL if inserted, replaced collation if replaced.
+ */
+int
+coll_cache_replace(struct coll *coll, struct coll **replaced)
+{
+	const struct mh_i32ptr_node_t node = {coll->id, coll};
+	struct mh_i32ptr_node_t repl_node = {0, NULL};
+	struct mh_i32ptr_node_t *prepl_node = &repl_node;
+	if (mh_i32ptr_put(coll_cache_id, &node, &prepl_node, NULL) ==
+	    mh_end(coll_cache_id)) {
+		diag_set(OutOfMemory, sizeof(node), "malloc", "coll_cache");
+		return -1;
+	}
+	*replaced = repl_node.val;
+	return 0;
+}
+
+/**
+ * Delete a collation from collation cache.
+ * @param coll - collation to delete.
+ */
+void
+coll_cache_delete(const struct coll *coll)
+{
+	mh_int_t i = mh_i32ptr_find(coll_cache_id, coll->id, NULL);
+	if (i == mh_end(coll_cache_id))
+		return;
+	mh_i32ptr_del(coll_cache_id, i, NULL);
+}
+
+/**
+ * Find a collation object by its id.
+ */
+struct coll *
+coll_cache_find(uint32_t id)
+{
+	mh_int_t pos = mh_i32ptr_find(coll_cache_id, id, NULL);
+	if (pos == mh_end(coll_cache_id))
+		return NULL;
+	return mh_i32ptr_node(coll_cache_id, pos)->val;
+}
diff --git a/src/box/coll_cache.h b/src/box/coll_cache.h
new file mode 100644
index 0000000000000000000000000000000000000000..b982ec285059e450e6e1b972bc8a8edaf6a0e6c3
--- /dev/null
+++ b/src/box/coll_cache.h
@@ -0,0 +1,77 @@
+#ifndef TARANTOOL_BOX_COLL_CACHE_H_INCLUDED
+#define TARANTOOL_BOX_COLL_CACHE_H_INCLUDED
+/*
+ * Copyright 2010-2016, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "coll.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * Create global hash tables.
+ * @return - 0 on success, -1 on memory error.
+ */
+int
+coll_cache_init();
+
+/** Delete global hash tables. */
+void
+coll_cache_destroy();
+
+/**
+ * Insert or replace a collation into collation cache.
+ * @param coll - collation to insert/replace.
+ * @param replaced - collation that was replaced.
+ * @return - 0 on success, -1 on memory error.
+ */
+int
+coll_cache_replace(struct coll *coll, struct coll **replaced);
+
+/**
+ * Delete a collation from collation cache.
+ * @param coll - collation to delete.
+ */
+void
+coll_cache_delete(const struct coll *coll);
+
+/**
+ * Find a collation object by its id.
+ */
+struct coll *
+coll_cache_find(uint32_t id);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* TARANTOOL_BOX_COLL_CACHE_H_INCLUDED */
diff --git a/src/box/coll_def.c b/src/box/coll_def.c
index 1a469b70d5b37787b059c9ca3b13cb6803f1844b..f849845b3afa630afaf954386989228959c2d3fa 100644
--- a/src/box/coll_def.c
+++ b/src/box/coll_def.c
@@ -63,3 +63,48 @@ const char *coll_icu_strength_strs[] = {
 	"IDENTICAL"
 };
 
+static int64_t
+icu_on_off_from_str(const char *str, uint32_t len)
+{
+	return strnindex(coll_icu_on_off_strs + 1, str, len,
+			 coll_icu_on_off_MAX - 1) + 1;
+}
+
+static int64_t
+icu_alternate_handling_from_str(const char *str, uint32_t len)
+{
+	return strnindex(coll_icu_alternate_handling_strs + 1, str, len,
+			 coll_icu_alternate_handling_MAX - 1) + 1;
+}
+
+static int64_t
+icu_case_first_from_str(const char *str, uint32_t len)
+{
+	return strnindex(coll_icu_case_first_strs + 1, str, len,
+			 coll_icu_case_first_MAX - 1) + 1;
+}
+
+static int64_t
+icu_strength_from_str(const char *str, uint32_t len)
+{
+	return strnindex(coll_icu_strength_strs + 1, str, len,
+			 coll_icu_strength_MAX - 1) + 1;
+}
+
+const struct opt_def coll_icu_opts_reg[] = {
+	OPT_DEF_ENUM("french_collation", coll_icu_on_off, struct coll_icu_def,
+		     french_collation, icu_on_off_from_str),
+	OPT_DEF_ENUM("alternate_handling", coll_icu_alternate_handling, struct coll_icu_def,
+		     alternate_handling, icu_alternate_handling_from_str),
+	OPT_DEF_ENUM("case_first", coll_icu_case_first, struct coll_icu_def,
+		     case_first, icu_case_first_from_str),
+	OPT_DEF_ENUM("case_level", coll_icu_on_off, struct coll_icu_def,
+		     case_level, icu_on_off_from_str),
+	OPT_DEF_ENUM("normalization_mode", coll_icu_on_off, struct coll_icu_def,
+		     normalization_mode, icu_on_off_from_str),
+	OPT_DEF_ENUM("strength", coll_icu_strength, struct coll_icu_def,
+		     strength, icu_strength_from_str),
+	OPT_DEF_ENUM("numeric_collation", coll_icu_on_off, struct coll_icu_def,
+		     numeric_collation, icu_on_off_from_str),
+	OPT_END,
+};
diff --git a/src/box/coll_def.h b/src/box/coll_def.h
index f62e794033241cd80f3dbcd9fb507e0b13c7b099..7a1027a1e10d17c711602a135c459ee98adf0945 100644
--- a/src/box/coll_def.h
+++ b/src/box/coll_def.h
@@ -33,6 +33,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include "opt_def.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -128,6 +129,8 @@ struct coll_def {
 	struct coll_icu_def icu;
 };
 
+extern const struct opt_def coll_icu_opts_reg[];
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/box/errcode.h b/src/box/errcode.h
index aaaa9127c546fd3b4b77fabba802a1ea13bb227b..bca4cbd0c705c4d24ea4c8afb06bbb92d496f577 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -203,6 +203,7 @@ struct errcode_record {
 	/*148 */_(ER_SEQUENCE_ACCESS_DENIED,	"%s access is denied for user '%s' to sequence '%s'") \
 	/*149 */_(ER_SPACE_FIELD_IS_DUPLICATE,	"Space field '%s' is duplicate") \
 	/*150 */_(ER_CANT_CREATE_COLLATION,	"Failed to initialize collation: %s.") \
+	/*151 */_(ER_WRONG_COLLATION_OPTIONS,	"Wrong collation options (field %u): %s") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 1ee6b0dc5c13776e3c30ae31764905a8f7e0b2fb..73b82a2e4d8d86682e5b09135a0ea0e1921e62b3 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1611,6 +1611,66 @@ end
 
 box.schema.func.reload = internal.func_reload
 
+box.internal.collation = {}
+box.internal.collation.create = function(name, coll_type, locale, opts)
+    opts = opts or setmap{}
+    if type(name) ~= 'string' then
+        box.error(box.error.ILLEGAL_PARAMS,
+        "name (first arg) must be a string")
+    end
+    if type(coll_type) ~= 'string' then
+        box.error(box.error.ILLEGAL_PARAMS,
+        "type (second arg) must be a string")
+    end
+    if type(locale) ~= 'string' then
+        box.error(box.error.ILLEGAL_PARAMS,
+        "locale (third arg) must be a string")
+    end
+    if type(opts) ~= 'table' then
+        box.error(box.error.ILLEGAL_PARAMS,
+        "options (fourth arg) must be a table or nil")
+    end
+    local lua_opts = {if_not_exists = opts.if_not_exists }
+    check_param_table(lua_opts, {if_not_exists = 'boolean'})
+    opts.if_not_exists = nil
+    opts = setmap(opts)
+
+    local _coll = box.space[box.schema.COLLATION_ID]
+    if lua_opts.if_not_exists then
+        local coll = _coll.index.name:get{name}
+        if coll then
+            return
+        end
+    end
+    _coll:auto_increment{name, session.uid(), coll_type, locale, opts}
+end
+
+box.internal.collation.drop = function(name, opts)
+    opts = opts or {}
+    check_param_table(opts, { if_exists = 'boolean' })
+
+    local _coll = box.space[box.schema.COLLATION_ID]
+    if opts.if_exists then
+        local coll = _coll.index.name:get{name}
+        if not coll then
+            return
+        end
+    end
+    _coll.index.name:delete{name}
+end
+
+box.internal.collation.exists = function(name)
+    local _coll = box.space[box.schema.COLLATION_ID]
+    local coll = _coll.index.name:get{name}
+    return not not coll
+end
+
+box.internal.collation.id_by_name = function(name)
+    local _coll = box.space[box.schema.COLLATION_ID]
+    local coll = _coll.index.name:get{name}
+    return coll[1]
+end
+
 box.schema.user = {}
 
 box.schema.user.password = function(password)
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 94a88c7e58e6a87d6fe1d91949a12593d9c155dd..3da7934b9413b92ed47e0d2eaf1ddaa9bda20386 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -351,6 +351,8 @@ box_lua_space_init(struct lua_State *L)
 	lua_setfield(L, -2, "VUSER_ID");
 	lua_pushnumber(L, BOX_FUNC_ID);
 	lua_setfield(L, -2, "FUNC_ID");
+	lua_pushnumber(L, BOX_COLLATION_ID);
+	lua_setfield(L, -2, "COLLATION_ID");
 	lua_pushnumber(L, BOX_VFUNC_ID);
 	lua_setfield(L, -2, "VFUNC_ID");
 	lua_pushnumber(L, BOX_PRIV_ID);
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index 537e282c84291acbfe20a12356f49a2182425053..ae382eda9c455a27a3021de1d023a75cbebe7a69 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -837,8 +837,6 @@ end
 
 --------------------------------------------------------------------------------
 -- Tarantool 1.7.6
---------------------------------------------------------------------------------
-
 local function create_sequence_space()
     local _space = box.space[box.schema.SPACE_ID]
     local _index = box.space[box.schema.INDEX_ID]
@@ -882,8 +880,32 @@ local function create_sequence_space()
     _index:insert{_space_sequence.id, 1, 'sequence', 'tree', {unique = false}, {{1, 'unsigned'}}}
 end
 
+local function create_collation_space()
+    local _collation = box.space[box.schema.COLLATION_ID]
+
+    log.info("create space _collation")
+    box.space._space:insert{_collation.id, ADMIN, '_collation', 'memtx', 0, setmap({}),
+        { { name = 'id', type = 'unsigned' }, { name = 'name', type = 'string' },
+          { name = 'owner', type = 'unsigned' }, { name = 'type', type = 'string' },
+          { name = 'locale', type = 'string' }, { name = 'opts', type = 'map' } } }
+
+    log.info("create index primary on _collation")
+    box.space._index:insert{_collation.id, 0, 'primary', 'tree', {unique = true}, {{0, 'unsigned'}}}
+
+    log.info("create index name on _collation")
+    box.space._index:insert{_collation.id, 1, 'name', 'tree', {unique = true}, {{1, 'string'}}}
+
+    log.info("create predefined collations")
+    box.space._collation:replace{0, "unicode", ADMIN, "ICU", "", setmap{}}
+    box.space._collation:replace{1, "unicode_s1", ADMIN, "ICU", "", {strength='primary'}}
+
+    local _priv = box.space[box.schema.PRIV_ID]
+    _priv:insert{ADMIN, PUBLIC, 'space', _collation.id, 2}
+end
+
 local function upgrade_to_1_7_6()
     create_sequence_space()
+    create_collation_space()
     -- Trigger space format checking by updating version in _schema.
 end
 
@@ -912,7 +934,7 @@ local function upgrade(options)
         {version = mkversion(1, 7, 1), func = upgrade_to_1_7_1, auto = false},
         {version = mkversion(1, 7, 2), func = upgrade_to_1_7_2, auto = false},
         {version = mkversion(1, 7, 5), func = upgrade_to_1_7_5, auto = true},
-        {version = mkversion(1, 7, 6), func = upgrade_to_1_7_6, auto = false}
+        {version = mkversion(1, 7, 6), func = upgrade_to_1_7_6, auto = false},
     }
 
     for _, handler in ipairs(handlers) do
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 71f3621678ac3c9f017455d40e3192314fa370cc..ff8ce0dc9cb460dec37c67ba714f6a94a61f0f38 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -279,6 +279,11 @@ schema_init()
 	/* _space - home for all spaces. */
 	key_def_set_part(key_def, 0 /* part no */, 0 /* field no */,
 			 FIELD_TYPE_UNSIGNED);
+
+	/* _collation - collation description. */
+	sc_space_new(BOX_COLLATION_ID, "_collation", key_def,
+		     &on_replace_collation, NULL);
+
 	sc_space_new(BOX_SPACE_ID, "_space", key_def,
 		     &alter_space_on_replace_space, &on_stmt_begin_space);
 
diff --git a/src/box/schema_def.c b/src/box/schema_def.c
index ad3d6832fbb0a0c2076e63c95b2a4fe1c121737e..492c593dba98ac20c22a85e91a6d28e1f7692f1d 100644
--- a/src/box/schema_def.c
+++ b/src/box/schema_def.c
@@ -40,6 +40,7 @@ static const char *object_type_strs[] = {
 	/* [SC_USER]            = */ "user",
 	/* [SC_ROLE]            = */ "role",
 	/* [SC_SEQUENCE]        = */ "sequence",
+	/* [SC_COLLATION]       = */ "collation",
 };
 
 enum schema_object_type
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index b97c81c7de1789e3f88340118c8399022446cfcf..528b8c4c2ce91cd7529749afc0783d1b97e1069f 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -68,6 +68,8 @@ enum {
 	BOX_SYSTEM_ID_MIN = 256,
 	/** Space id of _schema. */
 	BOX_SCHEMA_ID = 272,
+	/** Space id of _collation. */
+	BOX_COLLATION_ID = 276,
 	/** Space id of _space. */
 	BOX_SPACE_ID = 280,
 	/** Space id of _vspace view. */
@@ -155,6 +157,16 @@ enum {
 	BOX_FUNC_FIELD_LANGUAGE = 4,
 };
 
+/** _collation fields. */
+enum {
+	BOX_COLLATION_FIELD_ID = 0,
+	BOX_COLLATION_FIELD_NAME = 1,
+	BOX_COLLATION_FIELD_UID = 2,
+	BOX_COLLATION_FIELD_TYPE = 3,
+	BOX_COLLATION_FIELD_LOCALE = 4,
+	BOX_COLLATION_FIELD_OPTIONS = 5,
+};
+
 /** _schema fields. */
 enum {
 	BOX_SCHEMA_FIELD_KEY = 0,
@@ -213,6 +225,8 @@ enum schema_object_type {
 	SC_USER = 4,
 	SC_ROLE = 5,
 	SC_SEQUENCE = 6,
+	SC_COLLATION = 7,
+	schema_object_type_MAX = 8
 };
 
 enum schema_object_type
diff --git a/src/box/tuple.c b/src/box/tuple.c
index 98ce12dd92f8ff6168e2632d97b1ed888039a23e..0455469043dbc5272675e7095b0306c60218d49a 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -38,6 +38,7 @@
 #include "small/small.h"
 
 #include "tuple_update.h"
+#include "coll_cache.h"
 
 static struct mempool tuple_iterator_pool;
 static struct small_alloc runtime_alloc;
@@ -402,6 +403,9 @@ tuple_init(field_name_hash_f hash)
 
 	box_tuple_last = NULL;
 
+	if (coll_cache_init() != 0)
+		return -1;
+
 	return 0;
 }
 
@@ -451,6 +455,8 @@ tuple_free(void)
 	small_alloc_destroy(&runtime_alloc);
 
 	tuple_format_free();
+
+	coll_cache_destroy();
 }
 
 box_tuple_format_t *
diff --git a/src/trivia/util.h b/src/trivia/util.h
index beaa4ad1dc7cb4011ff213b9b6acfb17cbc3fb8b..08b89f9beb715cf340eacbc9636ed8c3164c56a9 100644
--- a/src/trivia/util.h
+++ b/src/trivia/util.h
@@ -92,6 +92,7 @@ extern "C" {
 	const char *enum_name##_strs[(unsigned) enum_name##_MAX + 1] = {enum_members(ENUM_STRS_MEMBER) 0}
 #endif
 #define STR2ENUM(enum_name, str) ((enum enum_name) strindex(enum_name##_strs, str, enum_name##_MAX))
+#define STRN2ENUM(enum_name, str, len) ((enum enum_name) strnindex(enum_name##_strs, str, len, enum_name##_MAX))
 
 uint32_t
 strindex(const char **haystack, const char *needle, uint32_t hmax);
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index 38945ec5ba89cd678a243c164e4073ed1162786d..d75753012924d02e697c423a4065ca3008e42f95 100755
--- a/test/app-tap/tarantoolctl.test.lua
+++ b/test/app-tap/tarantoolctl.test.lua
@@ -338,8 +338,8 @@ do
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0)
-            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 16)
-            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 38)
+            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 17)
+            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 40)
         end)
     end)
 
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 0994b084dbf5e31d4d3dea76479fb0c39eb159f1..d5fa6579fb60ca0e49262e5c5767ca30b1ff6619 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -14,6 +14,10 @@ box.space._cluster:select{}
 box.space._space:select{}
 ---
 - - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
+  - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {
+        'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'},
+      {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, {
+        'name': 'opts', 'type': 'map'}]]
   - [280, 1, '_space', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine',
         'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags',
@@ -63,6 +67,8 @@ box.space._space:select{}
 box.space._index:select{}
 ---
 - - [272, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]]
+  - [276, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [276, 1, 'name', 'tree', {'unique': true}, [[1, 'string']]]
   - [280, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [280, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [280, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
@@ -119,6 +125,7 @@ box.space._priv:select{}
 - - [1, 0, 'role', 2, 4]
   - [1, 1, 'universe', 0, 7]
   - [1, 2, 'function', 1, 4]
+  - [1, 2, 'space', 276, 2]
   - [1, 2, 'space', 281, 1]
   - [1, 2, 'space', 289, 1]
   - [1, 2, 'space', 297, 1]
diff --git a/test/box/access.result b/test/box/access.result
index 8337bf1cd522db7650152141557234216c2778b2..d1bef1d7f367ed8a97e702566b9d9739319f0b30 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -780,3 +780,43 @@ c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
 c:close()
 ---
 ...
+session = box.session
+---
+...
+box.schema.user.create('test')
+---
+...
+box.schema.user.grant('test', 'read,write', 'universe')
+---
+...
+session.su('test')
+---
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+---
+...
+session.su('admin')
+---
+...
+box.internal.collation.drop('test') -- success
+---
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+---
+...
+session.su('test')
+---
+...
+box.internal.collation.drop('test') -- fail
+---
+- error: Create, drop or alter access on collation is denied for user 'test'
+...
+session.su('admin')
+---
+...
+box.internal.collation.drop('test') -- success
+---
+...
+box.schema.user.drop('test')
+---
+...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index cde4e0f9aa00aa04b4b4e4c946270d5f4e4c3062..761bb89041ebbe8b71530f9db5903f9f51c68b5a 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -303,3 +303,17 @@ c:_request("select", nil, 1, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
 c:_request("select", nil, 65537, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
 c:_request("select", nil, 4294967295, box.index.EQ, 0, 0, 0xFFFFFFFF, {})
 c:close()
+
+session = box.session
+box.schema.user.create('test')
+box.schema.user.grant('test', 'read,write', 'universe')
+session.su('test')
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+session.su('admin')
+box.internal.collation.drop('test') -- success
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+session.su('test')
+box.internal.collation.drop('test') -- fail
+session.su('admin')
+box.internal.collation.drop('test') -- success
+box.schema.user.drop('test')
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 2c585b06558ae0469f491b208f7907ffbeddf80e..d190c0e41c42eb6795d4315dc837acaabe89e4f9 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -623,6 +623,10 @@ box.space._user:select()
 box.space._space:select()
 ---
 - - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
+  - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {
+        'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'},
+      {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, {
+        'name': 'opts', 'type': 'map'}]]
   - [280, 1, '_space', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine',
         'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags',
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index 99ab3b954cada177450728806bc5002027166b6e..ca6e649b5717d3e3b441fbe5f5a172a9b6d5e422 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -138,11 +138,11 @@ box.session.su('guest')
 ...
 #box.space._vspace:select{}
 ---
-- 6
+- 7
 ...
 #box.space._vindex:select{}
 ---
-- 15
+- 17
 ...
 box.session.su('admin')
 ---
@@ -230,11 +230,11 @@ box.session.su('guest')
 ...
 #box.space._vspace:select{}
 ---
-- 17
+- 18
 ...
 #box.space._vindex:select{}
 ---
-- 39
+- 41
 ...
 #box.space._vuser:select{}
 ---
@@ -242,7 +242,7 @@ box.session.su('guest')
 ...
 #box.space._vpriv:select{}
 ---
-- 12
+- 13
 ...
 #box.space._vfunc:select{}
 ---
@@ -262,7 +262,7 @@ box.session.su('guest')
 ...
 #box.space._vindex:select{}
 ---
-- 39
+- 41
 ...
 #box.space._vuser:select{}
 ---
diff --git a/test/box/alter.result b/test/box/alter.result
index d4050fa05995a86433848aa86f4978509a7f76ee..583937b1158b09e0ec005942de5f5e299b2902eb 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -182,6 +182,8 @@ box.space._vspace.index.owner:alter{parts = {2, 'unsigned'}}
 _index:select{}
 ---
 - - [272, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]]
+  - [276, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [276, 1, 'name', 'tree', {'unique': true}, [[1, 'string']]]
   - [280, 0, 'primary', 'tree', 1, 1, 0, 'unsigned']
   - [280, 1, 'owner', 'tree', {'unique': false}, [{'field': 1, 'type': 'unsigned'}]]
   - [280, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
diff --git a/test/box/ddl.result b/test/box/ddl.result
index ed29097bc2c93d19a2acbbd822a04a156202d82d..1d9665bcf9fe750398a5ff74ae56f272c3182767 100644
--- a/test/box/ddl.result
+++ b/test/box/ddl.result
@@ -203,3 +203,276 @@ for i = 1, 2 do fiber.create(function() fiber.yield() space:format(format) ch:pu
 space:drop()
 ---
 ...
+-- collation
+function setmap(table) return setmetatable(table, { __serialize = 'map' }) end
+---
+...
+box.internal.collation.create('test')
+---
+- error: Illegal parameters, type (second arg) must be a string
+...
+box.internal.collation.create('test', 'ICU')
+---
+- error: Illegal parameters, locale (third arg) must be a string
+...
+box.internal.collation.create(42, 'ICU', 'ru_RU')
+---
+- error: Illegal parameters, name (first arg) must be a string
+...
+box.internal.collation.create('test', 42, 'ru_RU')
+---
+- error: Illegal parameters, type (second arg) must be a string
+...
+box.internal.collation.create('test', 'ICU', 42)
+---
+- error: Illegal parameters, locale (third arg) must be a string
+...
+box.internal.collation.create('test', 'nothing', 'ru_RU')
+---
+- error: 'Failed to initialize collation: unknown collation type.'
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', setmap{}) --ok
+---
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+---
+- error: Duplicate key exists in unique index 'name' in space '_collation'
+...
+box.internal.collation.drop('test')
+---
+...
+box.internal.collation.drop('nothing') -- allowed
+---
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', 42)
+---
+- error: Illegal parameters, options (fourth arg) must be a table or nil
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', 'options')
+---
+- error: Illegal parameters, options (fourth arg) must be a table or nil
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', {ping='pong'})
+---
+- error: 'Wrong collation options (field 5): unexpected option ''ping'''
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='german'})
+---
+- error: 'Failed to initialize collation: ICU wrong french_collation option setting,
+    expected ON | OFF.'
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='on'}) --ok
+---
+...
+box.internal.collation.drop('test') --ok
+---
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='supervillian'})
+---
+- error: 'Failed to initialize collation: ICU wrong strength option setting, expected
+    PRIMARY | SECONDARY | TERTIARY | QUATERNARY | IDENTICAL.'
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=42})
+---
+- error: 'Wrong collation options (field 5): ''strength'' must be enum'
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=2}) --ok
+---
+- error: 'Wrong collation options (field 5): ''strength'' must be enum'
+...
+box.internal.collation.drop('test') --ok
+---
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='primary'}) --ok
+---
+...
+box.internal.collation.drop('test') --ok
+---
+...
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+---
+...
+box.internal.collation.exists('test')
+---
+- true
+...
+test_run:cmd('restart server default')
+function setmap(table) return setmetatable(table, { __serialize = 'map' }) end
+---
+...
+box.internal.collation.exists('test')
+---
+- true
+...
+box.internal.collation.drop('test')
+---
+...
+box.space._collation:auto_increment{'test'}
+---
+- error: Tuple field count 2 is less than required by a defined index (expected 6)
+...
+box.space._collation:auto_increment{'test', 0, 'ICU'}
+---
+- error: Tuple field count 4 is less than required by a defined index (expected 6)
+...
+box.space._collation:auto_increment{'test', 'ADMIN', 'ICU', 'ru_RU'}
+---
+- error: Tuple field count 5 is less than required by a defined index (expected 6)
+...
+box.space._collation:auto_increment{42, 0, 'ICU', 'ru_RU'}
+---
+- error: Tuple field count 5 is less than required by a defined index (expected 6)
+...
+box.space._collation:auto_increment{'test', 0, 42, 'ru_RU'}
+---
+- error: Tuple field count 5 is less than required by a defined index (expected 6)
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 42}
+---
+- error: Tuple field count 5 is less than required by a defined index (expected 6)
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} --ok
+---
+- [2, 'test', 0, 'ICU', 'ru_RU', {}]
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}}
+---
+- error: Duplicate key exists in unique index 'name' in space '_collation'
+...
+box.space._collation.index.name:delete{'test'} -- ok
+---
+- [2, 'test', 0, 'ICU', 'ru_RU', {}]
+...
+box.space._collation.index.name:delete{'nothing'} -- allowed
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 42}
+---
+- error: 'Tuple field 6 type does not match one required by operation: expected map'
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 'options'}
+---
+- error: 'Tuple field 6 type does not match one required by operation: expected map'
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', {ping='pong'}}
+---
+- error: 'Wrong collation options (field 5): unexpected option ''ping'''
+...
+opts = {normalization_mode='NORMAL'}
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+---
+- error: 'Failed to initialize collation: ICU wrong normalization_mode option setting,
+    expected ON | OFF.'
+...
+opts.normalization_mode = 'OFF'
+---
+...
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} -- ok
+---
+...
+_ = box.space._collation.index.name:delete{'test'} -- ok
+---
+...
+opts.numeric_collation = 'PERL'
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+---
+- error: 'Failed to initialize collation: ICU wrong numeric_collation option setting,
+    expected ON | OFF.'
+...
+opts.numeric_collation = 'ON'
+---
+...
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+---
+...
+_ = box.space._collation.index.name:delete{'test'} -- ok
+---
+...
+opts.alternate_handling1 = 'ON'
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+---
+- error: 'Wrong collation options (field 5): unexpected option ''alternate_handling1'''
+...
+opts.alternate_handling1 = nil
+---
+...
+opts.alternate_handling = 'ON'
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+---
+- error: 'Failed to initialize collation: ICU wrong alternate_handling option setting,
+    expected NON_IGNORABLE | SHIFTED.'
+...
+opts.alternate_handling = 'SHIFTED'
+---
+...
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+---
+...
+_ = box.space._collation.index.name:delete{'test'} -- ok
+---
+...
+opts.case_first = 'ON'
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+---
+- error: 'Failed to initialize collation: ICU wrong case_first option setting, expected
+    OFF | UPPER_FIRST | LOWER_FIRST.'
+...
+opts.case_first = 'OFF'
+---
+...
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+---
+...
+_ = box.space._collation.index.name:delete{'test'} -- ok
+---
+...
+opts.case_level = 'UPPER'
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+---
+- error: 'Failed to initialize collation: ICU wrong case_level option setting, expected
+    ON | OFF.'
+...
+opts.case_level = 'DEFAULT'
+---
+...
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+---
+- error: 'Failed to initialize collation: ICU wrong case_level option setting, expected
+    ON | OFF.'
+...
+_ = box.space._collation.index.name:delete{'test'} -- ok
+---
+...
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}}
+---
+- [2, 'test', 0, 'ICU', 'ru_RU', {}]
+...
+box.space._collation:select{}
+---
+- - [0, 'unicode', 1, 'ICU', '', {}]
+  - [1, 'unicode_s1', 1, 'ICU', '', {'strength': 'primary'}]
+  - [2, 'test', 0, 'ICU', 'ru_RU', {}]
+...
+test_run:cmd('restart server default')
+box.space._collation:select{}
+---
+- - [0, 'unicode', 1, 'ICU', '', {}]
+  - [1, 'unicode_s1', 1, 'ICU', '', {'strength': 'primary'}]
+  - [2, 'test', 0, 'ICU', 'ru_RU', {}]
+...
+box.space._collation.index.name:delete{'test'}
+---
+- [2, 'test', 0, 'ICU', 'ru_RU', {}]
+...
diff --git a/test/box/ddl.test.lua b/test/box/ddl.test.lua
index 3d6ad008d9906dff0064caa971537ba1663d751e..34b71193c09382bc94b0da8fab12bbd2a4ff7bdb 100644
--- a/test/box/ddl.test.lua
+++ b/test/box/ddl.test.lua
@@ -101,3 +101,86 @@ for i = 1, 2 do fiber.create(function() fiber.yield() space:format(format) ch:pu
 {ch:get(), ch:get(), ch:get()}
 
 space:drop()
+
+-- collation
+function setmap(table) return setmetatable(table, { __serialize = 'map' }) end
+
+box.internal.collation.create('test')
+box.internal.collation.create('test', 'ICU')
+box.internal.collation.create(42, 'ICU', 'ru_RU')
+box.internal.collation.create('test', 42, 'ru_RU')
+box.internal.collation.create('test', 'ICU', 42)
+box.internal.collation.create('test', 'nothing', 'ru_RU')
+box.internal.collation.create('test', 'ICU', 'ru_RU', setmap{}) --ok
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+box.internal.collation.drop('test')
+box.internal.collation.drop('nothing') -- allowed
+box.internal.collation.create('test', 'ICU', 'ru_RU', 42)
+box.internal.collation.create('test', 'ICU', 'ru_RU', 'options')
+box.internal.collation.create('test', 'ICU', 'ru_RU', {ping='pong'})
+box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='german'})
+box.internal.collation.create('test', 'ICU', 'ru_RU', {french_collation='on'}) --ok
+box.internal.collation.drop('test') --ok
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='supervillian'})
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=42})
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength=2}) --ok
+box.internal.collation.drop('test') --ok
+box.internal.collation.create('test', 'ICU', 'ru_RU', {strength='primary'}) --ok
+box.internal.collation.drop('test') --ok
+
+box.internal.collation.create('test', 'ICU', 'ru_RU')
+box.internal.collation.exists('test')
+
+test_run:cmd('restart server default')
+function setmap(table) return setmetatable(table, { __serialize = 'map' }) end
+
+box.internal.collation.exists('test')
+box.internal.collation.drop('test')
+
+box.space._collation:auto_increment{'test'}
+box.space._collation:auto_increment{'test', 0, 'ICU'}
+box.space._collation:auto_increment{'test', 'ADMIN', 'ICU', 'ru_RU'}
+box.space._collation:auto_increment{42, 0, 'ICU', 'ru_RU'}
+box.space._collation:auto_increment{'test', 0, 42, 'ru_RU'}
+box.space._collation:auto_increment{'test', 0, 'ICU', 42}
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}} --ok
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}}
+box.space._collation.index.name:delete{'test'} -- ok
+box.space._collation.index.name:delete{'nothing'} -- allowed
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 42}
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', 'options'}
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', {ping='pong'}}
+opts = {normalization_mode='NORMAL'}
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+opts.normalization_mode = 'OFF'
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} -- ok
+_ = box.space._collation.index.name:delete{'test'} -- ok
+opts.numeric_collation = 'PERL'
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+opts.numeric_collation = 'ON'
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+_ = box.space._collation.index.name:delete{'test'} -- ok
+opts.alternate_handling1 = 'ON'
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+opts.alternate_handling1 = nil
+opts.alternate_handling = 'ON'
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+opts.alternate_handling = 'SHIFTED'
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+_ = box.space._collation.index.name:delete{'test'} -- ok
+opts.case_first = 'ON'
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+opts.case_first = 'OFF'
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+_ = box.space._collation.index.name:delete{'test'} -- ok
+opts.case_level = 'UPPER'
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts}
+opts.case_level = 'DEFAULT'
+_ = box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', opts} --ok
+_ = box.space._collation.index.name:delete{'test'} -- ok
+
+box.space._collation:auto_increment{'test', 0, 'ICU', 'ru_RU', setmap{}}
+box.space._collation:select{}
+test_run:cmd('restart server default')
+box.space._collation:select{}
+box.space._collation.index.name:delete{'test'}
diff --git a/test/box/misc.result b/test/box/misc.result
index f332fc156c796d3cd0b3ad503dbfbbd7b0255c7e..14c4c16f5465a4e521c2853617ac1ac58d8ce882 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -312,6 +312,7 @@ t;
   - 'box.error.FIELD_TYPE : 23'
   - 'box.error.WRONG_SPACE_FORMAT : 141'
   - 'box.error.UNKNOWN_UPDATE_OP : 28'
+  - 'box.error.WRONG_COLLATION_OPTIONS : 151'
   - 'box.error.CURSOR_NO_TRANSACTION : 80'
   - 'box.error.TUPLE_REF_OVERFLOW : 86'
   - 'box.error.ALTER_SEQUENCE : 143'
diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result
index 7ac001ec0bd3da6906dd99abf906b5d170f0b094..c48294b3c8ef99544400384651eb38f7688ee02c 100644
--- a/test/wal_off/alter.result
+++ b/test/wal_off/alter.result
@@ -28,7 +28,7 @@ end;
 ...
 #spaces;
 ---
-- 65517
+- 65515
 ...
 -- cleanup
 for k, v in pairs(spaces) do
diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
index dd08961bc967963467b30a2c1131d7c22a160ace..d1cc579111eb52475791be0d65571bd1ebb770f0 100644
--- a/test/xlog/upgrade.result
+++ b/test/xlog/upgrade.result
@@ -41,6 +41,10 @@ box.space._schema:select()
 box.space._space:select()
 ---
 - - [272, 1, '_schema', 'memtx', 0, {}, [{'type': 'string', 'name': 'key'}]]
+  - [276, 1, '_collation', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {
+        'name': 'name', 'type': 'string'}, {'name': 'owner', 'type': 'unsigned'},
+      {'name': 'type', 'type': 'string'}, {'name': 'locale', 'type': 'string'}, {
+        'name': 'opts', 'type': 'map'}]]
   - [280, 1, '_space', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
         'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'engine',
         'type': 'string'}, {'name': 'field_count', 'type': 'unsigned'}, {'name': 'flags',
@@ -93,6 +97,8 @@ box.space._space:select()
 box.space._index:select()
 ---
 - - [272, 0, 'primary', 'tree', {'unique': true}, [[0, 'string']]]
+  - [276, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
+  - [276, 1, 'name', 'tree', {'unique': true}, [[1, 'string']]]
   - [280, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [280, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [280, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
@@ -152,12 +158,18 @@ box.space._func:select()
   - [2, 4, 'somefunc', 1, 'LUA']
   - [3, 1, 'someotherfunc', 0, 'LUA']
 ...
+box.space._collation:select()
+---
+- - [0, 'unicode', 1, 'ICU', '', {}]
+  - [1, 'unicode_s1', 1, 'ICU', '', {'strength': 'primary'}]
+...
 box.space._priv:select()
 ---
 - - [1, 0, 'role', 2, 4]
   - [1, 1, 'universe', 0, 7]
   - [1, 2, 'function', 1, 4]
   - [1, 2, 'function', 2, 4]
+  - [1, 2, 'space', 276, 2]
   - [1, 2, 'space', 281, 1]
   - [1, 2, 'space', 289, 1]
   - [1, 2, 'space', 297, 1]
diff --git a/test/xlog/upgrade.test.lua b/test/xlog/upgrade.test.lua
index c89e2cc0f753f10bbcb56cc7c8d9c8988d010fb6..0be2d34e95f543197069363614372c5a5d05c53d 100644
--- a/test/xlog/upgrade.test.lua
+++ b/test/xlog/upgrade.test.lua
@@ -25,6 +25,7 @@ box.space._space:select()
 box.space._index:select()
 box.space._user:select()
 box.space._func:select()
+box.space._collation:select()
 box.space._priv:select()
 
 box.space._vspace ~= nil