Commit 08c3e59a authored by Stan Hu's avatar Stan Hu

Optimize /admin/applications so that it does not timeout

On our dev instance, /admin/applications as not loading because:

1. There was an unindexed query by `application_id`.
2. There was an expensive query that attempted to load 1 million
   unique entries via ActiveRecord just to find the unique count.

We fix the first issue by adding an index for that column.

We fix the second issue with a simple SELECT COUNT(DISTINCT
resource_owner_id) SQL query.

In addition, we add pagination to avoid loading more than 20
applications at once.

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/67228
parent ffa5328c
......@@ -7,7 +7,9 @@ class Admin::ApplicationsController < Admin::ApplicationController
before_action :load_scopes, only: [:new, :create, :edit, :update]
def index
@applications = ApplicationsFinder.new.execute
applications = ApplicationsFinder.new.execute
@applications = Kaminari.paginate_array(applications).page(params[:page])
@application_counts = OauthAccessToken.distinct_resource_owner_counts(@applications)
end
def show
......
......@@ -6,6 +6,8 @@ class OauthAccessToken < Doorkeeper::AccessToken
alias_attribute :user, :resource_owner
scope :distinct_resource_owner_counts, ->(applications) { where(application: applications).distinct.group(:application_id).count(:resource_owner_id) }
def scopes=(value)
if value.is_a?(Array)
super(Doorkeeper::OAuth::Scopes.from_array(value).to_s)
......
......@@ -19,7 +19,8 @@
%tr{ :id => "application_#{application.id}" }
%td= link_to application.name, admin_application_path(application)
%td= application.redirect_uri
%td= application.access_tokens.map(&:resource_owner_id).uniq.count
%td= @application_counts[application.id].to_i
%td= application.trusted? ? 'Y': 'N'
%td= link_to 'Edit', edit_admin_application_path(application), class: 'btn btn-link'
%td= render 'delete_form', application: application
= paginate @applications, theme: 'gitlab'
---
title: Optimize /admin/applications so that it does not timeout
merge_request: 32852
author:
type: performance
# frozen_string_literal: true
class AddIndexOnApplicationIdOnOauthAccessTokens < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :oauth_access_tokens, :application_id
end
def down
remove_concurrent_index :oauth_access_tokens, :application_id
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_09_05_223900) do
ActiveRecord::Schema.define(version: 2019_09_10_000130) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -2390,6 +2390,7 @@ ActiveRecord::Schema.define(version: 2019_09_05_223900) do
t.datetime "revoked_at"
t.datetime "created_at", null: false
t.string "scopes"
t.index ["application_id"], name: "index_oauth_access_tokens_on_application_id"
t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
......
......@@ -10,6 +10,16 @@ describe Admin::ApplicationsController do
sign_in(admin)
end
describe 'GET #index' do
render_views
it 'renders the application form' do
get :index
expect(response).to have_http_status(200)
end
end
describe 'GET #new' do
it 'renders the application form' do
get :new
......
# frozen_string_literal: true
require 'spec_helper'
describe OauthAccessToken do
let(:user) { create(:user) }
let(:app_one) { create(:oauth_application) }
let(:app_two) { create(:oauth_application) }
let(:app_three) { create(:oauth_application) }
let(:tokens) { described_class.all }
before do
create(:oauth_access_token, application_id: app_one.id)
create_list(:oauth_access_token, 2, resource_owner: user, application_id: app_two.id)
end
it 'returns unique owners' do
expect(tokens.count).to eq(3)
expect(tokens.distinct_resource_owner_counts([app_one])).to eq({ app_one.id => 1 })
expect(tokens.distinct_resource_owner_counts([app_two])).to eq({ app_two.id => 1 })
expect(tokens.distinct_resource_owner_counts([app_three])).to eq({})
expect(tokens.distinct_resource_owner_counts([app_one, app_two]))
.to eq({
app_one.id => 1,
app_two.id => 1
})
end
end
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