Могу ли я реорганизовать контроллер, чтобы уменьшить количество запросов к базе данных? - PullRequest
1 голос
/ 18 июня 2020

У моего приложения rails проблемы с производительностью. Страница root моего приложения загружает несколько изображений, которые были прикреплены к статьям с помощью Active Storage. Действие (индекс) для страницы выглядит следующим образом:

class ArticlesController < ApplicationController

  def index
    @articles = Article.with_attached_image.all
    @top = Article.with_attached_image.last
    @news = Article.with_attached_image.where(category: 'news').last
    @business = Article.with_attached_image.where(category: 'business').last
    @ent = Article.with_attached_image.where(category: 'entertainment').last
    @tech = Article.with_attached_image.where(category: 'tech').last
    @sports = Article.with_attached_image.where(category: 'sports').last
    @op = Article.with_attached_image.where(category: 'opinion').last
  end
end

Соответствующее представление выглядит следующим образом:

<div class="grid-container">
  <div class="main" style="background-image: url(<%= (url_for(@top.image)) %>)">
  <div class="top-article" >
    <h2><%= @top.title %></h2>
    <p><%= link_to "Read more..." %></p>
  </div>
  </div>
  <div class="sports" style="background-image: url(<%= (url_for(@sports.image)) %>)">
    <h2><%= link_to "Sports", sports_path %></h2>
  </div>
  <div class="tech" style="background-image: url(<%= (url_for(@tech.image)) %>)">
    <h2><%= link_to "Technology", tech_path %></h2>
  </div>
  <div class="opinion" style="background-image: url(<%= (url_for(@op.image)) %>)">
    <h2><%= link_to "Opinion", opinion_path %></h2>
  </div>
  <div class="entertainment" style="background-image: url(<%= (url_for(@ent.image)) %>)">
    <h2><%= link_to "Entertainment", entertainment_path %></h2>
  </div>
  <div class="business" style="background-image: url(<%= (url_for(@business.image)) %>)">
    <h2><%= link_to "Business", business_path %></h2>
  </div>
  <div class="news" style="background-image: url(<%= (url_for(@news.image)) %>)">
    <h2><%= link_to "News", news_path %></h2>
  </div>
</div>

Я предполагаю, что лучший план действий по сокращению количество обращений к базе данных - это вложенный запрос. Однако я не уверен, как это реализовать и является ли это даже лучшим решением. Мы будем очень благодарны за любую помощь в рефакторинге этого действия индекса для уменьшения количества обращений к базе данных и повышения общей производительности приложения.

Ответы [ 3 ]

2 голосов
/ 18 июня 2020

Количество запросов не должно быть проблемой. Если у вас много статей и вы часто фильтруете по категориям, вы можете добавить индекс в столбец category, добавив его к миграции, например:

def change
  add_index :articles, :category
end

Ссылка: https://guides.rubyonrails.org/active_record_migrations.html


Вы также можете извлечь категории в их собственную модель / таблицу, как обычно для этого типа данных, и проиндексировать внешний ключ в таблице статей, которая указывает на таблица категорий:

# Create the categories table
def change
  create_table :categories do |t|
    t.name, null: false, index: {unique: true}
  end
end
# Add the foreign key to articles
def change
  change_table :articles do |t|
    t.references :category, foreign_key: true, index: true, null: false
  end
end
# Category model
class Category < ApplicationRecord
  has_many :articles
end
# Article model
class Article < ApplicationRecord
  belongs_to :category, required: true

  scope :by_category, ->(category_name) do
    joins(:category).where(categories: {name: category_name})
  end
end
# Controller
class ArticlesController < ApplicationController
  def index
    @news = Article.with_attached_image.by_category('news').last
    # etc...
  end
end

Кроме того, если вы вообще используете переменную @articles, рассмотрите возможность использования какой-либо разбивки на страницы (например, kaminari ), поэтому вы не выбираете и не инициализируете все записи в таблице.

1 голос
/ 18 июня 2020

Вы запрашиваете в базе данных разные фрагменты данных, которые у вас уже есть. Вы можете сделать большую часть этого, просто работая с @articles как с массивом.

def index
  @articles = Article.with_attached_image.all

  @top = @articles.last

  @categories = {}
  %w( news business entertainment tech sports opinion ).each do |category|
    @categories[category] = @articles.reverse.find do |article|
      article.category == category
    end
  end
end

Но если вы не используете @articles в вашем представлении, может быть быстрее просто получить каждая категория индивидуально, как и у вас, поскольку каждый запрос должен возвращать только одну запись. Это должно быть довольно быстро, если у вас правильно настроены индексы базы данных.

def index
  # Omit potentially unused call to: Article.with_attached_image.all
  @top = Article.with_attached_image.last
  @news = Article.with_attached_image.where(category: 'news').last
  @business = Article.with_attached_image.where(category: 'business').last
  @ent = Article.with_attached_image.where(category: 'entertainment').last
  @tech = Article.with_attached_image.where(category: 'tech').last
  @sports = Article.with_attached_image.where(category: 'sports').last
  @op = Article.with_attached_image.where(category: 'opinion').last
end
0 голосов
/ 19 июня 2020

Следующий запрос выполнит только два запроса и будет использовать постоянный объем памяти независимо от размера таблицы articles.

def index  
  # First query
  @top = Article.with_attached_image.last

  # Second query
  one_random_article_per_category = Article.find_by_sql(
    <<-SQL.squish
      select *
      from articles
      where category = 'news'
      limit 1

      union all

      select *
      from articles
      where category = 'business'
      limit 1

      union all
      -- etc ...
    SQL
  )
  articles_by_category = one_random_article_per_category.group_by(&:category)
  last_article_in_category = ->(cat) { articles_by_category.fetch(cat).last }
  @news = last_article_in_category['news']
  @business = last_article_in_category['business']
  @ent = last_article_in_category['entertainment']
  @tech = last_article_in_category['tech']
  @sports = last_article_in_category['sports']
  @op = last_article_in_category['opinion']
end

SQL повторяется, но эффективно. Генерация SQL из массива категорий, вероятно, желательна и оставлена ​​в качестве упражнения для читателя.

@articles не используется в шаблоне представления, поэтому я пропустил его здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...