Commit ff26ea81 authored by Francisco Javier López's avatar Francisco Javier López Committed by Douwe Maan

Resolve "Performance issues when loading large number of wiki pages"

parent 88d29775
...@@ -74,7 +74,11 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -74,7 +74,11 @@ class Projects::WikisController < Projects::ApplicationController
def history def history
@page = @project_wiki.find_page(params[:id]) @page = @project_wiki.find_page(params[:id])
unless @page if @page
@page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]),
total_count: @page.count_versions)
.page(params[:page])
else
redirect_to( redirect_to(
project_wiki_path(@project, :home), project_wiki_path(@project, :home),
notice: "Page not found" notice: "Page not found"
...@@ -101,7 +105,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -101,7 +105,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized # Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki @project_wiki.wiki
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15)) @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15))
rescue ProjectWiki::CouldNotCreateWikiError rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project) redirect_to project_path(@project)
......
...@@ -76,8 +76,8 @@ class ProjectWiki ...@@ -76,8 +76,8 @@ class ProjectWiki
# Returns an Array of Gitlab WikiPage instances or an # Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages. # empty Array if this Wiki has no pages.
def pages def pages(limit: nil)
wiki.pages.map { |page| WikiPage.new(self, page, true) } wiki.pages(limit: limit).map { |page| WikiPage.new(self, page, true) }
end end
# Finds a page within the repository based on a tile # Finds a page within the repository based on a tile
......
...@@ -127,19 +127,24 @@ class WikiPage ...@@ -127,19 +127,24 @@ class WikiPage
@version ||= @page.version @version ||= @page.version
end end
# Returns an array of Gitlab Commit instances. def versions(options = {})
def versions
return [] unless persisted? return [] unless persisted?
wiki.wiki.page_versions(@page.path) wiki.wiki.page_versions(@page.path, options)
end end
def commit def count_versions
versions.first return [] unless persisted?
wiki.wiki.count_page_versions(@page.path)
end
def last_version
@last_version ||= versions(limit: 1).first
end end
def last_commit_sha def last_commit_sha
commit&.sha last_version&.sha
end end
# Returns the Date that this latest version was # Returns the Date that this latest version was
...@@ -151,7 +156,7 @@ class WikiPage ...@@ -151,7 +156,7 @@ class WikiPage
# Returns boolean True or False if this instance # Returns boolean True or False if this instance
# is an old version of the page. # is an old version of the page.
def historical? def historical?
@page.historical? && versions.first.sha != version.sha @page.historical? && last_version.sha != version.sha
end end
# Returns boolean True or False if this instance # Returns boolean True or False if this instance
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
= link_to wiki_page.title, project_wiki_path(@project, wiki_page) = link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format}) %small (#{wiki_page.format})
.pull-right .pull-right
%small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.commit.authored_date) }).html_safe %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
%th= _("Last updated") %th= _("Last updated")
%th= _("Format") %th= _("Format")
%tbody %tbody
- @page.versions.each_with_index do |version, index| - @page_versions.each_with_index do |version, index|
- commit = version - commit = version
%tr %tr
%td %td
...@@ -37,5 +37,6 @@ ...@@ -37,5 +37,6 @@
%td %td
%strong %strong
= version.format = version.format
= paginate @page_versions, theme: 'gitlab'
= render 'sidebar' = render 'sidebar'
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
.nav-text .nav-text
%h2.wiki-page-title= @page.title.capitalize %h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by %span.wiki-last-edit-by
= (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author_name}</strong>" }).html_safe = (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.commit.authored_date)} #{time_ago_with_tooltip(@page.last_version.authored_date)}
.nav-controls .nav-controls
= render 'main_links' = render 'main_links'
......
---
title: Performance issues when loading large number of wiki pages
merge_request: 15276
author:
type: performance
...@@ -10,4 +10,30 @@ module Gollum ...@@ -10,4 +10,30 @@ module Gollum
index.send(name, *args) index.send(name, *args)
end end
end end
class Wiki
def pages(treeish = nil, limit: nil)
tree_list((treeish || @ref), limit: limit)
end
def tree_list(ref, limit: nil)
if (sha = @access.ref_to_sha(ref))
commit = @access.commit(sha)
tree_map_for(sha).inject([]) do |list, entry|
next list unless @page_class.valid_page_name?(entry.name)
list << entry.page(self, commit)
break list if limit && list.size >= limit
list
end
else
[]
end
end
end
end
Rails.application.configure do
config.after_initialize do
Gollum::Page.per_page = Kaminari.config.default_per_page
end
end end
...@@ -58,12 +58,12 @@ module Gitlab ...@@ -58,12 +58,12 @@ module Gitlab
end end
end end
def pages def pages(limit: nil)
@repository.gitaly_migrate(:wiki_get_all_pages) do |is_enabled| @repository.gitaly_migrate(:wiki_get_all_pages, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled if is_enabled
gitaly_get_all_pages gitaly_get_all_pages
else else
gollum_get_all_pages gollum_get_all_pages(limit: limit)
end end
end end
end end
...@@ -88,14 +88,23 @@ module Gitlab ...@@ -88,14 +88,23 @@ module Gitlab
end end
end end
def page_versions(page_path) # options:
# :page - The Integer page number.
# :per_page - The number of items per page.
# :limit - Total number of items to return.
def page_versions(page_path, options = {})
current_page = gollum_page_by_path(page_path) current_page = gollum_page_by_path(page_path)
current_page.versions.map do |gollum_git_commit|
gollum_page = gollum_wiki.page(current_page.title, gollum_git_commit.id) commits_from_page(current_page, options).map do |gitlab_git_commit|
new_version(gollum_page, gollum_git_commit.id) gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id)
Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format)
end end
end end
def count_page_versions(page_path)
@repository.count_commits(ref: 'HEAD', path: page_path)
end
def preview_slug(title, format) def preview_slug(title, format)
# Adapted from gollum gem (Gollum::Wiki#preview_page) to avoid # Adapted from gollum gem (Gollum::Wiki#preview_page) to avoid
# using Rugged through a Gollum::Wiki instance # using Rugged through a Gollum::Wiki instance
...@@ -110,6 +119,22 @@ module Gitlab ...@@ -110,6 +119,22 @@ module Gitlab
private private
# options:
# :page - The Integer page number.
# :per_page - The number of items per page.
# :limit - Total number of items to return.
def commits_from_page(gollum_page, options = {})
unless options[:limit]
options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page
options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i
end
@repository.log(ref: gollum_page.last_version.id,
path: gollum_page.path,
limit: options[:limit],
offset: options[:offset])
end
def gollum_wiki def gollum_wiki
@gollum_wiki ||= Gollum::Wiki.new(@repository.path) @gollum_wiki ||= Gollum::Wiki.new(@repository.path)
end end
...@@ -126,8 +151,17 @@ module Gitlab ...@@ -126,8 +151,17 @@ module Gitlab
end end
def new_version(gollum_page, commit_id) def new_version(gollum_page, commit_id)
commit = Gitlab::Git::Commit.find(@repository, commit_id) Gitlab::Git::WikiPageVersion.new(version(commit_id), gollum_page&.format)
Gitlab::Git::WikiPageVersion.new(commit, gollum_page&.format) end
def version(commit_id)
commit_find_proc = -> { Gitlab::Git::Commit.find(@repository, commit_id) }
if RequestStore.active?
RequestStore.fetch([:wiki_version_commit, commit_id]) { commit_find_proc.call }
else
commit_find_proc.call
end
end end
def assert_type!(object, klass) def assert_type!(object, klass)
...@@ -185,8 +219,8 @@ module Gitlab ...@@ -185,8 +219,8 @@ module Gitlab
Gitlab::Git::WikiFile.new(gollum_file) Gitlab::Git::WikiFile.new(gollum_file)
end end
def gollum_get_all_pages def gollum_get_all_pages(limit: nil)
gollum_wiki.pages.map { |gollum_page| new_page(gollum_page) } gollum_wiki.pages(limit: limit).map { |gollum_page| new_page(gollum_page) }
end end
def gitaly_write_page(name, format, content, commit_details) def gitaly_write_page(name, format, content, commit_details)
......
...@@ -373,7 +373,7 @@ describe WikiPage do ...@@ -373,7 +373,7 @@ describe WikiPage do
end end
it 'returns commit sha' do it 'returns commit sha' do
expect(@page.last_commit_sha).to eq @page.commit.sha expect(@page.last_commit_sha).to eq @page.last_version.sha
end end
it 'is changed after page updated' do it 'is changed after page updated' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment