diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index f13fb1df373164e278e0cf6e276d9e4c7ad95df4..6069e620ba26199e57ff7abc7d3744e8d765d295 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -43,7 +43,9 @@ class Projects::BuildsController < Projects::ApplicationController
   def trace
     respond_to do |format|
       format.json do
-        render json: @build.trace_with_state(params[:state].presence).merge!(id: @build.id, status: @build.status)
+        state = params[:state].presence
+        render json: @build.trace_with_state(state: state).
+          merge!(id: @build.id, status: @build.status)
       end
     end
   end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index f9aa1984c1fc3f4185b9ff9a9e8a1cba6b19f1e3..13f101cbecbfe760d99e61e09fc01d79a37395d9 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -128,13 +128,14 @@ module Ci
       latest_builds.where('stage_idx < ?', stage_idx)
     end
 
-    def trace_html
-      trace_with_state[:html] || ''
+    def trace_html(**args)
+      trace_with_state(**args)[:html] || ''
     end
 
-    def trace_with_state(state = nil)
-      if trace.present?
-        Ci::Ansi2html.convert(trace, state)
+    def trace_with_state(state: nil, last_lines: nil)
+      trace_ansi = trace(last_lines)
+      if trace_ansi.present?
+        Ci::Ansi2html.convert(trace_ansi, state)
       else
         {}
       end
@@ -220,9 +221,10 @@ module Ci
       raw_trace.present?
     end
 
-    def raw_trace
+    def raw_trace(last_lines: nil)
       if File.exist?(trace_file_path)
-        File.read(trace_file_path)
+        Gitlab::Ci::TraceReader.new(trace_file_path).
+          read(last_lines: last_lines)
       else
         # backward compatibility
         read_attribute :trace
@@ -237,8 +239,8 @@ module Ci
       project.ci_id && File.exist?(old_path_to_trace)
     end
 
-    def trace
-      result = raw_trace
+    def trace(last_lines: nil)
+      result = raw_trace(last_lines)
       if project && result.present? && project.runners_token.present?
         result.gsub(project.runners_token, 'xxxxxx')
       else
diff --git a/lib/gitlab/ci/trace_reader.rb b/lib/gitlab/ci/trace_reader.rb
new file mode 100644
index 0000000000000000000000000000000000000000..37e51536e8fc1b21ddd94a2f9712f801c18e3f04
--- /dev/null
+++ b/lib/gitlab/ci/trace_reader.rb
@@ -0,0 +1,49 @@
+module Gitlab
+  module Ci
+    # This was inspired from: http://stackoverflow.com/a/10219411/1520132
+    class TraceReader
+      BUFFER_SIZE = 4096
+
+      attr_accessor :path, :buffer_size
+
+      def initialize(new_path, buffer_size: BUFFER_SIZE)
+        self.path = new_path
+        self.buffer_size = Integer(buffer_size)
+      end
+
+      def read(last_lines: nil)
+        if last_lines
+          read_last_lines(last_lines)
+        else
+          File.read(path)
+        end
+      end
+
+      def read_last_lines(max_lines)
+        File.open(path) do |file|
+          chunks = []
+          pos = lines = 0
+          max = file.size
+
+          # We want an extra line to make sure fist line has full contents
+          while lines <= max_lines && pos < max
+            pos += buffer_size
+
+            buf = if pos <= max
+                    file.seek(-pos, IO::SEEK_END)
+                    file.read(buffer_size)
+                  else # Reached the head, read only left
+                    file.seek(0)
+                    file.read(buffer_size - (pos - max))
+                  end
+
+            lines += buf.count("\n")
+            chunks.unshift(buf)
+          end
+
+          chunks.join.lines.last(max_lines).join
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f06d78694d60022d4da37695196260e2b2e5162f
--- /dev/null
+++ b/spec/lib/gitlab/ci/trace_reader_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::TraceReader do
+  let(:path) { __FILE__ }
+  let(:lines) { File.readlines(path) }
+  let(:bytesize) { lines.sum(&:bytesize) }
+
+  it 'returns last few lines' do
+    10.times do
+      subject = build_subject
+      last_lines = random_lines
+
+      expected = lines.last(last_lines).join
+
+      expect(subject.read(last_lines: last_lines)).to eq(expected)
+    end
+  end
+
+  it 'returns everything if trying to get too many lines' do
+    expect(build_subject.read(last_lines: lines.size * 2)).to eq(lines.join)
+  end
+
+  it 'raises an error if not passing an integer for last_lines' do
+    expect do
+      build_subject.read(last_lines: lines)
+    end.to raise_error(ArgumentError)
+  end
+
+  def random_lines
+    Random.rand(lines.size) + 1
+  end
+
+  def random_buffer
+    Random.rand(bytesize) + 1
+  end
+
+  def build_subject
+    described_class.new(__FILE__, buffer_size: random_buffer)
+  end
+end