diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 98020848997baf23b5339a0755f013ca9ce2084c..e2f7011e5d716be7904d6e7fb0a66ba9e4a4ff02 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -59,6 +59,7 @@ set (common_sources
      object.cc
      exception.cc
      ipc.cc
+     iovec.c
      errinj.cc
      errcode.c
      assoc.c
diff --git a/src/box/box.cc b/src/box/box.cc
index 5f079c7e811580fecec6fc870c593077dc56f2ba..3b059b3d5a3406ab2a4a79453d77756a2428eac6 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -299,8 +299,9 @@ boxk(enum iproto_type type, uint32_t space_id, const char *format, ...)
 	}
 	va_end(ap);
 	assert(data <= buf + sizeof(buf));
-	req.tuple = buf;
-	req.tuple_end = data;
+	req.tuple_cnt = 1;
+	req.tuple[0].iov_base = (void *) buf;
+	req.tuple[0].iov_len = data - buf;
 	process_rw(&null_port, &req);
 }
 
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 538bbe29453e27ce2abf0988bef8c6128921a7a5..282dc2633ae22022a9678a431087f94cd0fb6bc5 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -255,10 +255,12 @@ lbox_request_create(struct request *request,
 	if (tuple > 0) {
 		struct tbuf *tuple_buf = tbuf_new(&fiber()->gc);
 		luamp_encode(L, luaL_msgpack_default, tuple_buf, tuple);
-		request->tuple = tuple_buf->data;
-		request->tuple_end = tuple_buf->data + tuple_buf->size;
-		if (mp_typeof(*request->tuple) != MP_ARRAY)
+		assert(tuple_buf->size > 0);
+		if (mp_typeof(*tuple_buf->data) != MP_ARRAY)
 			tnt_raise(ClientError, ER_TUPLE_NOT_ARRAY);
+		request->tuple_cnt = 1;
+		request->tuple[0].iov_base = tuple_buf->data;
+		request->tuple[0].iov_len = tuple_buf->size;
 	}
 }
 
@@ -563,7 +565,9 @@ box_lua_call(struct request *request, struct port *port)
 	 */
 	SetuidGuard setuid(name, name_len, user, PRIV_X);
 	/* Push the rest of args (a tuple). */
-	const char *args = request->tuple;
+	size_t args_len;
+	const char *args = (char *) iovec_join(&fiber()->gc, request->tuple,
+					       request->tuple_cnt, &args_len);
 	uint32_t arg_count = mp_decode_array(&args);
 	luaL_checkstack(L, arg_count, "call: out of stack");
 
diff --git a/src/box/request.cc b/src/box/request.cc
index f8a19d2aa510610c5c809815a4026cfe5aef969f..5d577734907f2622da83fa57172aa1250778bbfc 100644
--- a/src/box/request.cc
+++ b/src/box/request.cc
@@ -54,8 +54,8 @@ execute_replace(struct request *request, struct port *port)
 	struct space *space = space_cache_find(request->space_id);
 
 	access_check_space(space, PRIV_W);
-	struct tuple *new_tuple = tuple_new(space->format, request->tuple,
-					    request->tuple_end);
+	struct tuple *new_tuple = tuple_newv(space->format, request->tuple,
+					     request->tuple_cnt);
 	TupleGuard guard(new_tuple);
 	space_validate_tuple(space, new_tuple);
 	enum dup_replace_mode mode = dup_replace_mode(request->type);
@@ -86,13 +86,23 @@ execute_update(struct request *request, struct port *port)
 		return;
 	}
 
+	/*
+	 * tuple_update() currently doesn't support scattered tuples.
+	 * Update expression usually fits to first iovec and it's not
+	 * a problem. For huge expressions prepare contiguous chunk of memory
+	 * from vector.
+	 */
+	size_t expr_len;
+	const char *expr = (const char *) iovec_join(&fiber()->gc,
+		request->tuple, request->tuple_cnt, &expr_len);
+
 	/* Update the tuple. */
 	struct tuple *new_tuple = tuple_update(space->format,
 					       region_alloc_cb,
 					       &fiber()->gc,
-					       old_tuple, request->tuple,
-					       request->tuple_end,
+					       old_tuple, expr, expr + expr_len,
 					       request->field_base);
+
 	TupleGuard guard(new_tuple);
 	space_validate_tuple(space, new_tuple);
 	txn_replace(txn, space, old_tuple, new_tuple, DUP_INSERT);
@@ -166,7 +176,17 @@ execute_auth(struct request *request, struct port * /* port */)
 {
 	const char *user = request->key;
 	uint32_t len = mp_decode_strl(&user);
-	authenticate(user, len, request->tuple, request->tuple_end);
+
+	/*
+	 * authenticate() doesn't support scattered tuple and requires
+	 * contiguous chunk of memory. Since size of first iovec is usually
+	 * enough to fit credentials, a slow path of iovec_join() is not a
+	 * problem.
+	 */
+	size_t credentials_len;
+	const char *credentials = (const char *) iovec_join(&fiber()->gc,
+		request->tuple, request->tuple_cnt, &credentials_len);
+	authenticate(user, len, credentials, credentials + credentials_len);
 }
 
 /** }}} */
@@ -228,8 +248,9 @@ request_decode(struct request *request, const char *data, uint32_t len)
 			request->iterator = mp_decode_uint(&value);
 			break;
 		case IPROTO_TUPLE:
-			request->tuple = value;
-			request->tuple_end = data;
+			request->tuple_cnt = 1;
+			request->tuple[0].iov_base = (void *) value;
+			request->tuple[0].iov_len = data - value;
 			break;
 		case IPROTO_KEY:
 		case IPROTO_FUNCTION_NAME:
@@ -276,11 +297,10 @@ request_encode(struct request *request, struct iovec *iov)
 		pos += key_len;
 		map_size++;
 	}
-	if (request->tuple) {
+	if (request->tuple_cnt > 0) {
 		pos = mp_encode_uint(pos, IPROTO_TUPLE);
-		iov[1].iov_base = (void *) request->tuple;
-		iov[1].iov_len = (request->tuple_end - request->tuple);
-		iovcnt++;
+		iovec_copy(iov + iovcnt, request->tuple, request->tuple_cnt);
+		iovcnt += request->tuple_cnt;
 		map_size++;
 	}
 
@@ -289,5 +309,6 @@ request_encode(struct request *request, struct iovec *iov)
 	iov[0].iov_base = begin;
 	iov[0].iov_len = pos - begin;
 
+	assert(iovcnt <= REQUEST_IOVMAX);
 	return iovcnt;
 }
diff --git a/src/box/request.h b/src/box/request.h
index e257109f65fc3b07820e2d040831cc57b7c77b8c..fb4964005efb5bf9c99423f95975b9f454b2baa7 100644
--- a/src/box/request.h
+++ b/src/box/request.h
@@ -29,7 +29,9 @@
  * SUCH DAMAGE.
  */
 #include <stdbool.h>
+#include "iovec.h"
 #include "xrow.h"
+#include "small/region.h"
 
 struct txn;
 struct port;
@@ -57,8 +59,8 @@ struct request
 	const char *key;
 	const char *key_end;
 	/** Insert/replace tuple or proc argument or update operations. */
-	const char *tuple;
-	const char *tuple_end;
+	struct iovec tuple[TUPLE_IOVMAX];
+	int tuple_cnt;
 	/** Base field offset for error messages, e.g. 0 for C and 1 for Lua. */
 	int field_base;
 
diff --git a/src/box/tuple.cc b/src/box/tuple.cc
index 4c656f84c8b1a98910f805380f6c73949eb54a63..8110a53da1652f3db44a3967ac19a8c908a0a91d 100644
--- a/src/box/tuple.cc
+++ b/src/box/tuple.cc
@@ -396,12 +396,17 @@ tuple_update(struct tuple_format *format,
 }
 
 struct tuple *
-tuple_new(struct tuple_format *format, const char *data, const char *end)
+tuple_newv(struct tuple_format *format, struct iovec *tbody, size_t tbody_cnt)
 {
-	size_t tuple_len = end - data;
-	assert(mp_typeof(*data) == MP_ARRAY);
-	struct tuple *new_tuple = tuple_alloc(format, tuple_len);
-	memcpy(new_tuple->data, data, tuple_len);
+	size_t len = iovec_len(tbody, tbody_cnt);
+	struct tuple *new_tuple = tuple_alloc(format, len);
+	char *data = new_tuple->data;
+	for (int i = 0; i < tbody_cnt; i++) {
+		memcpy(data, tbody->iov_base, tbody->iov_len);
+		data += tbody->iov_len;
+		tbody++;
+	}
+
 	try {
 		tuple_init_field_map(format, new_tuple, (uint32_t *)new_tuple);
 	} catch (...) {
@@ -532,7 +537,7 @@ tuple_init(float arena_prealloc, uint32_t objsize_min,
 		say_warn("disable shared arena since running under OpenVZ "
 		    "(https://bugzilla.openvz.org/show_bug.cgi?id=2805)");
 		flags = MAP_PRIVATE;
-        } else {
+	} else {
 		say_info("mapping %zu bytes for a shared arena...",
 			 prealloc);
 		flags = MAP_SHARED;
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 959a75da265aa6dca1af5e4e3d2d34673e74b8a7..cbe87345ead2a7393d4bdd935f8ab17cbe1f6e75 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -30,6 +30,7 @@
  */
 #include "trivia/util.h"
 #include "key_def.h" /* for enum field_type */
+#include "iovec.h"
 
 enum { FORMAT_ID_MAX = UINT16_MAX - 1, FORMAT_ID_NIL = UINT16_MAX };
 enum { FORMAT_REF_MAX = INT32_MAX, TUPLE_REF_MAX = UINT16_MAX };
@@ -166,12 +167,17 @@ tuple_alloc(struct tuple_format *format, size_t size);
  * Create a new tuple from a sequence of BER-len encoded fields.
  * tuple->refs is 0.
  *
- * @post *data is advanced to the length of tuple data
- *
  * Throws an exception if tuple format is incorrect.
  */
 struct tuple *
-tuple_new(struct tuple_format *format, const char *data, const char *end);
+tuple_newv(struct tuple_format *format, struct iovec *tbody, size_t tbody_cnt);
+
+static inline struct tuple *
+tuple_new(struct tuple_format *format, const char *data, const char *end)
+{
+	struct iovec tuple = { (void *) data, (size_t) (end - data) };
+	return tuple_newv(format, &tuple, 1);
+}
 
 /**
  * Free the tuple.
diff --git a/src/box/xrow.h b/src/box/xrow.h
index 9cf1327779c3f39ba2964b68510cb2fe98d3ab0d..30f6fd1d24405440dc984a7a8c0d8e92a8e80141 100644
--- a/src/box/xrow.h
+++ b/src/box/xrow.h
@@ -37,8 +37,11 @@ extern "C" {
 #endif
 
 enum {
+	/* tuplmax <= obuf_alloc_factor << TUPLE_IOVMAX */
+	TUPLE_IOVMAX = 14,
+	REQUEST_IOVMAX = TUPLE_IOVMAX + 2,
 	XROW_HEADER_IOVMAX = 1,
-	XROW_BODY_IOVMAX = 2,
+	XROW_BODY_IOVMAX = REQUEST_IOVMAX,
 	XROW_IOVMAX = XROW_HEADER_IOVMAX + XROW_BODY_IOVMAX + 1
 };
 
diff --git a/src/iovec.c b/src/iovec.c
new file mode 100644
index 0000000000000000000000000000000000000000..97c5abf18b6fe0b5a7ef3cf9c5ad0f68c8911ed1
--- /dev/null
+++ b/src/iovec.c
@@ -0,0 +1,31 @@
+
+/*
+ * 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 "iovec.h"
diff --git a/src/iovec.h b/src/iovec.h
new file mode 100644
index 0000000000000000000000000000000000000000..38ac5bbf8b9620ed0c708c64aa3ca8077dc44590
--- /dev/null
+++ b/src/iovec.h
@@ -0,0 +1,111 @@
+#ifndef TARANTOOL_IOVEC_H_INCLUDED
+#define TARANTOOL_IOVEC_H_INCLUDED
+/*
+ * 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 <stddef.h>
+#include <sys/uio.h> /* struct iovec */
+
+#include "trivia/util.h"
+#include "small/region.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * \brief Calculate total length of \a iov
+ * \param iov iovec
+ * \param iovcnt size of \a iov
+ * \return total length of \a iov
+ */
+static inline size_t
+iovec_len(struct iovec *iov, int iovcnt)
+{
+	size_t len = 0;
+	for (int i = 0; i < iovcnt; i++)
+		len += iov[i].iov_len;
+	return len;
+}
+
+/**
+ * \brief Copy \a src into \a dst up to \a size bytes
+ * \param dst iovec
+ * \param src iovec
+ * \param size size of \a iov
+ */
+static inline size_t
+iovec_copy(struct iovec *dst, struct iovec *src, int iovcnt)
+{
+	size_t len = 0;
+	for (int i = 0; i < iovcnt; i++) {
+		len += src->iov_len;
+		*(dst++) = *(src++);
+	}
+	return len;
+}
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#if defined(__cplusplus)
+
+/**
+ * \brief Conventional function to join iovec into a solid memory chunk.
+ * For iovec of size 1 returns iov->iov_base without allocation extra memory.
+ * \param region region alloc
+ * \param iov vector
+ * \param iovcnt size of iovec
+ * \param[out] size calculated length of \a iov
+ * \return solid memory chunk
+ */
+static inline void *
+iovec_join(struct region *region, struct iovec *iov, int iovcnt, size_t *plen)
+{
+	assert(iovcnt > 0 && plen != NULL);
+	if (likely(iovcnt == 1)) {
+		/* Fast path for single iovec or zero size */
+		*plen = iov->iov_len;
+		return iov->iov_base;
+	}
+
+	size_t len = iovec_len(iov, iovcnt);
+	char *data = (char *) region_alloc(region, len);
+	char *pos = data;
+	for (int i = 0; i < iovcnt; i++)
+		memcpy(pos, iov[i].iov_base, iov[i].iov_len);
+
+	*plen = len;
+	return data;
+}
+
+#endif /* defined(__cplusplus) */
+
+#endif /* TARANTOOL_IOVEC_H_INCLUDED */