diff --git a/changelogs/unreleased/gh-10555-fix-vinyl-compaction-crash-on-read-error.md b/changelogs/unreleased/gh-10555-fix-vinyl-compaction-crash-on-read-error.md
new file mode 100644
index 0000000000000000000000000000000000000000..5eb560ed1e683f7fe9c72f6b29132b28b50a978f
--- /dev/null
+++ b/changelogs/unreleased/gh-10555-fix-vinyl-compaction-crash-on-read-error.md
@@ -0,0 +1,4 @@
+## bugfix/vinyl
+
+* Fixed a bug when a compaction task could crash on a disk read error
+  (gh-10555).
diff --git a/src/box/vy_run.c b/src/box/vy_run.c
index 159e72b6a20bf775a06422a324943ce2352c07a1..f0ac7ff97c31adfd7f0f77c781219e47a1dc32cd 100644
--- a/src/box/vy_run.c
+++ b/src/box/vy_run.c
@@ -2668,11 +2668,12 @@ vy_slice_stream_next(struct vy_stmt_stream *virt_stream, struct vy_entry *ret)
 {
 	assert(virt_stream->iface->next == vy_slice_stream_next);
 	struct vy_slice_stream *stream = (struct vy_slice_stream *)virt_stream;
-	*ret = vy_entry_none();
 
 	/* If the slice is ended, return EOF */
-	if (stream->page_no > stream->slice->last_page_no)
+	if (stream->page_no > stream->slice->last_page_no) {
+		*ret = vy_entry_none();
 		return 0;
+	}
 
 	/* If current page is not already read, read it */
 	if (stream->page == NULL && vy_slice_stream_read_page(stream) != 0)
@@ -2689,6 +2690,7 @@ vy_slice_stream_next(struct vy_stmt_stream *virt_stream, struct vy_entry *ret)
 	    stream->page_no >= stream->slice->last_page_no &&
 	    vy_entry_compare(entry, stream->slice->end, stream->cmp_def) >= 0) {
 		tuple_unref(entry.stmt);
+		*ret = vy_entry_none();
 		return 0;
 	}
 
diff --git a/src/box/vy_stmt.c b/src/box/vy_stmt.c
index f4adf263bc3e60dc3e7f9ee918b2f0f2bd56a333..bf0331ec00e892261c5c3458c84fe2291c71ec5c 100644
--- a/src/box/vy_stmt.c
+++ b/src/box/vy_stmt.c
@@ -729,6 +729,11 @@ vy_stmt_encode_secondary(struct tuple *value, struct key_def *cmp_def,
 struct tuple *
 vy_stmt_decode(struct xrow_header *xrow, struct tuple_format *format)
 {
+	ERROR_INJECT_COUNTDOWN(ERRINJ_VY_STMT_DECODE_COUNTDOWN, {
+		diag_set(ClientError, ER_INJECTION,
+			 "vinyl statement decode");
+		return NULL;
+	});
 	struct vy_stmt_env *env = format->engine;
 	struct request request;
 	uint64_t key_map = dml_request_key_map(xrow->type);
diff --git a/src/lib/core/errinj.h b/src/lib/core/errinj.h
index 903de158a050f3e40a0113651baa282c141aa26f..4c51d1c383bf8343ff3fa19d21a17e0223cf03fa 100644
--- a/src/lib/core/errinj.h
+++ b/src/lib/core/errinj.h
@@ -172,6 +172,7 @@ struct errinj {
 	_(ERRINJ_VY_SCHED_TIMEOUT, ERRINJ_DOUBLE, {.dparam = 0}) \
 	_(ERRINJ_VY_SQUASH_TIMEOUT, ERRINJ_DOUBLE, {.dparam = 0}) \
 	_(ERRINJ_VY_STMT_ALLOC_COUNTDOWN, ERRINJ_INT, {.iparam = -1})\
+	_(ERRINJ_VY_STMT_DECODE_COUNTDOWN, ERRINJ_INT, {.iparam = -1})\
 	_(ERRINJ_VY_TASK_COMPLETE, ERRINJ_BOOL, {.bparam = false}) \
 	_(ERRINJ_VY_WRITE_ITERATOR_START_FAIL, ERRINJ_BOOL, {.bparam = false})\
 	_(ERRINJ_WAIT_QUORUM_COUNT, ERRINJ_INT, {.iparam = 0}) \
diff --git a/test/vinyl-luatest/gh_10555_compaction_read_error_test.lua b/test/vinyl-luatest/gh_10555_compaction_read_error_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..94ba163e53b425b8f0eb16b928f44f3c028bc9ed
--- /dev/null
+++ b/test/vinyl-luatest/gh_10555_compaction_read_error_test.lua
@@ -0,0 +1,55 @@
+local server = require('luatest.server')
+local t = require('luatest')
+
+local g = t.group()
+
+g.before_all(function(cg)
+    t.tarantool.skip_if_not_debug()
+    cg.server = server:new()
+    cg.server:start()
+end)
+
+g.after_all(function(cg)
+    cg.server:drop()
+end)
+
+g.after_each(function(cg)
+    cg.server:exec(function()
+        if box.space.test ~= nil then
+            box.space.test:drop()
+        end
+        box.error.injection.set('ERRINJ_VY_COMPACTION_DELAY', false)
+        box.error.injection.set('ERRINJ_VY_STMT_DECODE_COUNTDOWN', -1)
+    end)
+end)
+
+g.test_compaction_read_error = function(cg)
+    cg.server:exec(function()
+        local s = box.schema.space.create('test', {engine = 'vinyl'})
+        s:create_index('pk')
+
+        -- Delay compaction and create a few runs.
+        box.error.injection.set('ERRINJ_VY_COMPACTION_DELAY', true)
+        for _ = 1, 5 do
+            box.begin()
+            for k = 1, 100 do
+                s:replace({k})
+            end
+            box.commit()
+            box.snapshot()
+        end
+
+        -- Make compaction fail while decoding a random statement.
+        box.error.injection.set('ERRINJ_VY_STMT_DECODE_COUNTDOWN',
+                                math.random(500))
+
+        -- Resume compaction and wait for it to fail.
+        box.stat.reset()
+        t.assert_equals(box.stat.vinyl().scheduler.tasks_failed, 0)
+        s.index.pk:compact()
+        box.error.injection.set('ERRINJ_VY_COMPACTION_DELAY', false)
+        t.helpers.retrying({}, function()
+            t.assert_ge(box.stat.vinyl().scheduler.tasks_failed, 1)
+        end)
+    end)
+end