diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index a0430fdcf3cfe049fe9642a1e531a6b821f3e430..8967fd2f20744e86dd45dfcf449457027393f027 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -589,9 +589,26 @@ vy_tx_rollback_after_prepare(struct vy_tx *tx)
 
 	struct tx_manager *xm = tx->xm;
 
-	/* Expect cascading rollback in the reverse order. */
-	assert(xm->last_prepared_tx == tx);
-	xm->last_prepared_tx = NULL;
+	/*
+	 * There are two reasons of rollback_after_prepare:
+	 * 1) Fail in the middle of vy_tx_prepare call.
+	 * 2) Cascading rollback after WAL fail.
+	 *
+	 * If a TX is the latest prepared TX and the it is rollbacked,
+	 * it's certainly the case (2) and we should set xm->last_prepared_tx
+	 * to the previous prepared TX, if any.
+	 * But doesn't know the previous TX.
+	 * On the other hand we may expect that cascading rollback will
+	 * concern all the prepared TXs, all of them will be rollbacked
+	 * and xm->last_prepared_tx must be set to NULL in the end.
+	 * Thus we can set xm->last_prepared_tx to NULL now and it will be
+	 * correct in the end of the cascading rollback.
+	 *
+	 * We must not change xm->last_prepared_tx in all other cases,
+	 * it will be changed by the corresponding TX.
+	 */
+	if (xm->last_prepared_tx == tx)
+		xm->last_prepared_tx = NULL;
 
 	struct txv *v;
 	stailq_foreach_entry(v, &tx->log, next_in_log) {
diff --git a/test/vinyl/errinj.result b/test/vinyl/errinj.result
index 40d797527228fd97f3fc2c99c2f883b79c5d1f0b..1752bc220b4123eaac0d9651899f1cd740d9c227 100644
--- a/test/vinyl/errinj.result
+++ b/test/vinyl/errinj.result
@@ -878,3 +878,33 @@ box.snapshot()
 s:drop()
 ---
 ...
+s = box.schema.space.create('test', {engine = 'vinyl'})
+---
+...
+_ = s:create_index('i1', {parts = {1, 'unsigned'}})
+---
+...
+c = 10
+---
+...
+errinj.set("ERRINJ_WAL_WRITE_DISK", true)
+---
+- ok
+...
+for i = 1,10 do fiber.create(function() pcall(s.replace, s, {i}) c = c - 1 end) end
+---
+...
+while c ~= 0 do fiber.sleep(0.001) end
+---
+...
+s:select{}
+---
+- []
+...
+errinj.set("ERRINJ_WAL_WRITE_DISK", false)
+---
+- ok
+...
+s:drop()
+---
+...
diff --git a/test/vinyl/errinj.test.lua b/test/vinyl/errinj.test.lua
index bddc2936b6f730206639942a4caee212650e68d5..3a599218f55c79a81ac1d2d149ace6edc04ad278 100644
--- a/test/vinyl/errinj.test.lua
+++ b/test/vinyl/errinj.test.lua
@@ -353,3 +353,15 @@ errinj.set('ERRINJ_WAL_IO', false)
 _ = s:create_index('pk')
 box.snapshot()
 s:drop()
+
+s = box.schema.space.create('test', {engine = 'vinyl'})
+_ = s:create_index('i1', {parts = {1, 'unsigned'}})
+
+c = 10
+errinj.set("ERRINJ_WAL_WRITE_DISK", true)
+for i = 1,10 do fiber.create(function() pcall(s.replace, s, {i}) c = c - 1 end) end
+while c ~= 0 do fiber.sleep(0.001) end
+s:select{}
+errinj.set("ERRINJ_WAL_WRITE_DISK", false)
+
+s:drop()
diff --git a/test/vinyl/gh.result b/test/vinyl/gh.result
index 15570d6118f1c995db79043e0b87d9dc8cb95580..2ca16889e7092a6ec89eb6761c9d89da10e479d7 100644
--- a/test/vinyl/gh.result
+++ b/test/vinyl/gh.result
@@ -640,3 +640,17 @@ s.index.i1:count() == s.index.i2:count()
 s:drop()
 ---
 ...
+-- https://github.com/tarantool/tarantool/issues/2588
+s = box.schema.space.create('vinyl', { engine = 'vinyl' })
+---
+...
+i = box.space.vinyl:create_index('primary')
+---
+...
+s:replace({1, string.rep('x', 10 * 1024 * 1024)})
+---
+- error: Failed to allocate 10485799 bytes in lsregion_alloc for mem_stmt
+...
+s:drop()
+---
+...
diff --git a/test/vinyl/gh.test.lua b/test/vinyl/gh.test.lua
index c14bab432303f4dd8f03fff89fa9c342742ab853..9756d153b8e10c98fa69c68bd73bfc76f9915d75 100644
--- a/test/vinyl/gh.test.lua
+++ b/test/vinyl/gh.test.lua
@@ -266,3 +266,9 @@ test_run:cmd("setopt delimiter ''");
 
 s.index.i1:count() == s.index.i2:count()
 s:drop()
+
+-- https://github.com/tarantool/tarantool/issues/2588
+s = box.schema.space.create('vinyl', { engine = 'vinyl' })
+i = box.space.vinyl:create_index('primary')
+s:replace({1, string.rep('x', 10 * 1024 * 1024)})
+s:drop()