diff --git a/src/lib/salad/grp_alloc.h b/src/lib/salad/grp_alloc.h
new file mode 100644
index 0000000000000000000000000000000000000000..2084d7bb0fd968b1aed3fd167853105f8dd2f3c5
--- /dev/null
+++ b/src/lib/salad/grp_alloc.h
@@ -0,0 +1,138 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#pragma once
+
+#include <stddef.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Group alloc is a data structure that is designed for simplification of
+ * allocation of several objects in one memory block. It could be anything,
+ * but special attention is given to null-terminating strings. Each string is
+ * allocated with a personal null termination symbol, in the end memory block
+ * while all other objects will be placed in the beginning of the block. This
+ * guarantee allows to play with objects' alignment.
+ * Typical usage consist of two phases: gathering total needed size of memory
+ * block and creation of objects in given block.
+ *
+ * Example of usage:
+ * struct object {
+ *	int *array;
+ *	const char *name;
+ * };
+ *
+ * struct object *
+ * object_new(int *array, size_t array_size, const char *name, size_t name_len)
+ * {
+ *	// Gather required memory size.
+ *	struct grp_alloc all = grp_alloc_initializer();
+ *	size_t array_data_size = array_size * sizeof(*array);
+ *	grp_alloc_reserve_data(&all, array_data_size);
+ *	grp_alloc_reserve_str(&all, name_len);
+ *	struct test *res = malloc(sizeof(struct test) + grp_alloc_size(&all));
+ *	// Use memory block of required size.
+ *	grp_alloc_use(&all, res + 1);
+ *	res->array = grp_alloc_create_data(&all, array_data_size);
+ *	memcpy(res->array, array, array_data_size);
+ *	res->name = grp_alloc_create_str(&all, name, name_len);
+ *	assert(grp_alloc_size(&all) == 0);
+ *	return res;
+ * }
+ *
+ */
+struct grp_alloc {
+	/**
+	 * Points to the beginning of remaining memory block. Can be NULL in
+	 * the first phase (gathering required memory).
+	 */
+	char *data;
+	/**
+	 * End of required remaining memory block.
+	 */
+	char *data_end;
+};
+
+/**
+ * Default grp_alloc initializer. Just assign it to new grp_alloc and it's
+ * ready for the first phase.
+ */
+static inline struct grp_alloc
+grp_alloc_initializer(void)
+{
+	struct grp_alloc res = {NULL, NULL};
+	return res;
+}
+
+/**
+ * Phase 1: account am arbitrary data of @a size that is needed to be allocated.
+ */
+static inline void
+grp_alloc_reserve_data(struct grp_alloc *bank, size_t size)
+{
+	bank->data_end += size;
+}
+
+/**
+ * Phase 1: account a string of @a size that is needed to be allocated,
+ * including char for null-termination.
+ */
+static inline void
+grp_alloc_reserve_str(struct grp_alloc *bank, size_t size)
+{
+	bank->data_end += size + 1;
+}
+
+/**
+ * Phase 1 end: get total memory size required for all data.
+ */
+static inline size_t
+grp_alloc_size(struct grp_alloc *bank)
+{
+	return bank->data_end - bank->data;
+}
+
+/**
+ * Phase 2 begin: provide a block of memory of required size.
+ */
+static inline void
+grp_alloc_use(struct grp_alloc *bank, void *data)
+{
+	bank->data_end = (char *)data + (bank->data_end - bank->data);
+	bank->data = (char *)data;
+}
+
+/**
+ * Phase 2: allocate an arbitrary data block with given @a size.
+ */
+static inline void *
+grp_alloc_create_data(struct grp_alloc *bank, size_t size)
+{
+	char *res = bank->data;
+	bank->data += size;
+	return res;
+}
+
+/**
+ * Phase 2: allocate and fill a string with given data @a src of given size
+ * @a src_size, including null-termination. Return new string.
+ */
+static inline char *
+grp_alloc_create_str(struct grp_alloc *bank, const char *src, size_t src_size)
+{
+	bank->data_end--;
+	*bank->data_end = 0;
+	bank->data_end -= src_size;
+	memcpy(bank->data_end, src, src_size);
+	return bank->data_end;
+}
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 1f0eee4f718579937520becf92f76b4070e608c5..81ba9aa503961364aa76a2f9d7d831f38c2e5134 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -295,3 +295,6 @@ target_link_libraries(serializer.test unit box ${LUAJIT_LIBRARIES})
 
 add_executable(watcher.test watcher.c)
 target_link_libraries(watcher.test unit box)
+
+add_executable(grp_alloc.test grp_alloc.c box_test_utils.c)
+target_link_libraries(grp_alloc.test unit)
diff --git a/test/unit/grp_alloc.c b/test/unit/grp_alloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..eafa6772e876c31e554fdb7763b0732e827d13f3
--- /dev/null
+++ b/test/unit/grp_alloc.c
@@ -0,0 +1,95 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+
+#include "salad/grp_alloc.h"
+#include "trivia/util.h"
+
+#include "unit.h"
+
+struct test {
+	int *array;
+	size_t array_size;
+	const char *name;
+	const char *description;
+};
+
+struct test *
+test_new(int *array, size_t array_size,
+	 const char *name, size_t name_len,
+	 const char *description, size_t description_len)
+{
+	struct grp_alloc bank = grp_alloc_initializer();
+	size_t array_data_size = array_size * sizeof(*array);
+	grp_alloc_reserve_data(&bank, array_data_size);
+	grp_alloc_reserve_str(&bank, name_len);
+	grp_alloc_reserve_str(&bank, description_len);
+	size_t total_size = sizeof(struct test) + grp_alloc_size(&bank);
+	struct test *res = xmalloc(total_size);
+	grp_alloc_use(&bank, res + 1);
+	res->array = grp_alloc_create_data(&bank, array_data_size);
+	memcpy(res->array, array, array_data_size);
+	res->name = grp_alloc_create_str(&bank, name, name_len);
+	res->description = grp_alloc_create_str(&bank, description,
+						description_len);
+	return res;
+}
+
+static void
+check_test_new(int *array, size_t array_size,
+	       const char *name, size_t name_len,
+	       const char *description, size_t description_len)
+{
+	header();
+	plan(11);
+
+	struct test *t = test_new(array, array_size, name, name_len,
+				  description, description_len);
+	size_t array_data_size = array_size * sizeof(*array);
+	size_t total = array_data_size + sizeof(*t) +
+		       name_len + 1 + description_len + 1;
+	char *b = (char *)t;
+	char *e = b + total;
+
+	ok((char *)t->array > b, "location");
+	ok((char *)t->array < e, "location");
+	ok((char *)t->name > b, "location");
+	ok((char *)t->name < e, "location");
+	ok((char *)t->description > b, "location");
+	ok((char *)t->description < e, "location");
+	is(memcmp(t->array, array, array_data_size), 0, "data");
+	is(memcmp(t->name, name, name_len), 0, "data");
+	is(t->name[name_len], 0, "null-termination symbol");
+	is(memcmp(t->description, description, description_len), 0, "data");
+	is(t->description[description_len], 0, "null-termination symbol");
+
+	check_plan();
+	footer();
+}
+
+static void
+test_simple(void)
+{
+	header();
+	plan(3);
+
+	int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+	check_test_new(arr, 3, "test", 4, "abc", 3);
+	check_test_new(arr, 10, "alligator", 9, "x", 1);
+	check_test_new(arr, 1, "qwerty", 6, "as", 2);
+
+	check_plan();
+	footer();
+}
+
+int
+main(void)
+{
+	header();
+	plan(1);
+	test_simple();
+	footer();
+	return check_plan();
+}
diff --git a/test/unit/grp_alloc.result b/test/unit/grp_alloc.result
new file mode 100644
index 0000000000000000000000000000000000000000..98c00f4eb353f0e0085146ed67970e3f00525574
--- /dev/null
+++ b/test/unit/grp_alloc.result
@@ -0,0 +1,52 @@
+	*** main ***
+1..1
+	*** test_simple ***
+    1..3
+	*** check_test_new ***
+        1..11
+        ok 1 - location
+        ok 2 - location
+        ok 3 - location
+        ok 4 - location
+        ok 5 - location
+        ok 6 - location
+        ok 7 - data
+        ok 8 - data
+        ok 9 - null-termination symbol
+        ok 10 - data
+        ok 11 - null-termination symbol
+    ok 1 - subtests
+	*** check_test_new: done ***
+	*** check_test_new ***
+        1..11
+        ok 1 - location
+        ok 2 - location
+        ok 3 - location
+        ok 4 - location
+        ok 5 - location
+        ok 6 - location
+        ok 7 - data
+        ok 8 - data
+        ok 9 - null-termination symbol
+        ok 10 - data
+        ok 11 - null-termination symbol
+    ok 2 - subtests
+	*** check_test_new: done ***
+	*** check_test_new ***
+        1..11
+        ok 1 - location
+        ok 2 - location
+        ok 3 - location
+        ok 4 - location
+        ok 5 - location
+        ok 6 - location
+        ok 7 - data
+        ok 8 - data
+        ok 9 - null-termination symbol
+        ok 10 - data
+        ok 11 - null-termination symbol
+    ok 3 - subtests
+	*** check_test_new: done ***
+ok 1 - subtests
+	*** test_simple: done ***
+	*** main: done ***