diff --git a/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml b/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4137050a0771eaca8375ebbc4d063f11fbe2e23a
--- /dev/null
+++ b/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml
@@ -0,0 +1,4 @@
+---
+title: Fix the last coverage in trace log should be extracted
+merge_request: 11128
+author: dosuken123
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index fa462cbe095054545a1ccd7fde7de5cab1087727..c4c0623df6c559df9e761da009dee6dd77662a5d 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -73,7 +73,7 @@ module Gitlab
 
           match = ""
 
-          stream.each_line do |line|
+          reverse_line do |line|
             matches = line.scan(regex)
             next unless matches.is_a?(Array)
             next if matches.empty?
@@ -86,34 +86,39 @@ module Gitlab
           nil
         rescue
           # if bad regex or something goes wrong we dont want to interrupt transition
-          # so we just silentrly ignore error for now
+          # so we just silently ignore error for now
         end
 
         private
 
-        def read_last_lines(last_lines)
-          chunks = []
-          pos = lines = 0
-          max = stream.size
-
-          # We want an extra line to make sure fist line has full contents
-          while lines <= last_lines && pos < max
-            pos += BUFFER_SIZE
-
-            buf =
-              if pos <= max
-                stream.seek(-pos, IO::SEEK_END)
-                stream.read(BUFFER_SIZE)
-              else # Reached the head, read only left
-                stream.seek(0)
-                stream.read(BUFFER_SIZE - (pos - max))
-              end
-
-            lines += buf.count("\n")
-            chunks.unshift(buf)
+        def read_last_lines(limit)
+          to_enum(:reverse_line).first(limit).reverse.join
+        end
+
+        def reverse_line
+          stream.seek(0, IO::SEEK_END)
+          debris = ''
+
+          until (buf = read_backward(BUFFER_SIZE)).empty?
+            buf += debris
+            debris, *lines = buf.each_line.to_a
+            lines.reverse_each do |line|
+              yield(line.force_encoding('UTF-8'))
+            end
           end
 
-          chunks.join.lines.last(last_lines).join
+          yield(debris.force_encoding('UTF-8')) unless debris.empty?
+        end
+
+        def read_backward(length)
+          cur_offset = stream.tell
+          start = cur_offset - length
+          start = 0 if start < 0
+
+          stream.seek(start, IO::SEEK_SET)
+          stream.read(cur_offset - start).tap do
+            stream.seek(start, IO::SEEK_SET)
+          end
         end
       end
     end
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 40ac5a3ed375f43ad95d3c7bd0853c507776750d..bbb3f9912a3e92401c0e86ba2af992fa7b194c1b 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -240,9 +240,50 @@ describe Gitlab::Ci::Trace::Stream do
     end
 
     context 'multiple results in content & regex' do
-      let(:data) { ' (98.39%) covered. (98.29%) covered' }
+      let(:data) do
+        <<~HEREDOC
+          (98.39%) covered
+          (98.29%) covered
+        HEREDOC
+      end
+
+      let(:regex) { '\(\d+.\d+\%\) covered' }
+
+      it 'returns the last matched coverage' do
+        is_expected.to eq("98.29")
+      end
+    end
+
+    context 'when BUFFER_SIZE is smaller than stream.size' do
+      let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
+      let(:regex) { '\(\d+.\d+\%\) covered' }
+
+      before do
+        stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+      end
+
+      it { is_expected.to eq("98.29") }
+    end
+
+    context 'when regex is multi-byte char' do
+      let(:data) { '95.0 ゴッドファット\n' }
+      let(:regex) { '\d+\.\d+ ゴッドファット' }
+
+      before do
+        stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+      end
+
+      it { is_expected.to eq('95.0') }
+    end
+
+    context 'when BUFFER_SIZE is equal to stream.size' do
+      let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
       let(:regex) { '\(\d+.\d+\%\) covered' }
 
+      before do
+        stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', data.length)
+      end
+
       it { is_expected.to eq("98.29") }
     end