Optimize column selection for articles belonging to "any" given tag (#12420)

When `acts-as-taggable-on`'s `.tagged_with()` is used with `any: true`,
the gem will use `SELECT *` regardless of any previous (or following) requests
of selecting a limited amount of columns.

Given that the `articles` table has [73 columns](https://dev.to/admin/blazer/queries/314-number-of-columns-in-all-tables)
that will amount to wasted RAM memory for columns we don't need.

By "unscoping" any previous `select()` we can optimize used memory.

Before:

```ruby
[24] pry(main)> Article.tagged_with([:ruby], any: true).select(:id, :name).to_sql
=> "SELECT \"articles\".*, \"articles\".\"id\", \"name\" FROM \"articles\" WHERE EXISTS (SELECT * FROM \"taggings\" WHERE \"taggings\".\"taggable_id\" = \"articles\".\"id\" AND \"taggings\".\"taggable_type\" = 'Article' AND \"taggings\".\"tag_id\" IN (SELECT \"tags\".\"id\" FROM \"tags\" WHERE (\"tags\".\"name\" LIKE 'ruby' ESCAPE '!')))"
```

Note, how the SQL query is `articles.*, articles.column_a`

After:

[25] pry(main)> Article.tagged_with([:ruby], any: true).unscope(:select).select(:id, :name).to_sql
=> "SELECT \"articles\".\"id\", \"name\" FROM \"articles\" WHERE EXISTS (SELECT * FROM \"taggings\" WHERE \"taggings\".\"taggable_id\" = \"articles\".\"id\" AND \"taggings\".\"taggable_type\" = 'Article' AND \"taggings\".\"tag_id\" IN (SELECT \"tags\".\"id\" FROM \"tags\" WHERE (\"tags\".\"name\" LIKE 'ruby' ESCAPE '!')))"
```

`articles.*` is gone :-)

- https://github.com/mbleigh/acts-as-taggable-on/issues/936
- 47da5036de/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb (L2-L8)
This commit is contained in:
rhymes 2021-01-25 17:34:10 +01:00 committed by GitHub
parent b13c76f405
commit def0cf2437
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 13 additions and 8 deletions

View file

@ -73,7 +73,7 @@ module Admin
def articles_satellite
Article.published.where(last_buffered: nil)
.includes(:user, :buffer_updates)
.tagged_with(Tag.bufferized_tags, any: true)
.tagged_with(Tag.bufferized_tags, any: true).unscope(:select)
.limited_columns_internal_select
.order(hotness_score: :desc)
.page(params[:page])

View file

@ -304,7 +304,8 @@ class User < ApplicationRecord
end
def followed_articles
Article.tagged_with(cached_followed_tag_names, any: true)
Article
.tagged_with(cached_followed_tag_names, any: true).unscope(:select)
.union(Article.where(user_id: cached_following_users_ids))
.where(language: preferred_languages_array, published: true)
end

View file

@ -77,7 +77,7 @@ class ArticleApiIndexService
def tagged_articles
articles = Article.published.includes(:user, :organization)
articles = articles.tagged_with(tags, any: true) if tags
articles = articles.tagged_with(tags, any: true).unscope(:select) if tags
articles = articles.tagged_with(tags_exclude, exclude: true) if tags_exclude
articles

View file

@ -158,12 +158,14 @@ module Articles
.order(hotness_score: :desc)
when "only_followed_tags" # equivalent to base but only on tags user follows (if user follows enough)
followed_tags = @user.cached_followed_tag_names
articles = Article.published.limited_column_select.includes(top_comments: :user)
articles = Article.published.includes(top_comments: :user)
.page(@page).per(@number_of_articles)
.where("score >= ? OR featured = ?", SiteConfig.home_feed_minimum_score, true)
.order(hotness_score: :desc)
# We only want to limit the posts to tagged_with if the participant follows enough tags.
articles = articles.tagged_with(followed_tags, any: true) if followed_tags.size > 4
articles = articles.unscope(:select).limited_column_select
when "top_articles_since_last_pageview_3_days_max" # Top articles since last page view (max 3 days)
start_time = [(@user.page_views.last&.created_at || 3.days.ago) - 12.hours, 3.days.ago].max
articles = Article.published.limited_column_select.includes(top_comments: :user)
@ -179,12 +181,14 @@ module Articles
when "combination_only_tags_followed_and_top_max_7_days" # Top articles since last page view (max 7 days)
start_time = [(@user.page_views.last&.created_at || 7.days.ago) - 12.hours, 7.days.ago].max
followed_tags = @user.cached_followed_tag_names
articles = Article.published.limited_column_select.includes(top_comments: :user)
articles = Article.published.includes(top_comments: :user)
.where("published_at > ?", start_time)
.page(@page).per(@number_of_articles)
.order(score: :desc)
# We only want to limit the posts to tagged_with if the participant follows enough tags.
articles = articles.tagged_with(followed_tags, any: true) if followed_tags.size > 4
articles = articles.unscope(:select).limited_column_select
else # "base"
articles = Article.published.limited_column_select.includes(top_comments: :user)
.page(@page).per(@number_of_articles)

View file

@ -6,8 +6,8 @@ module Articles
author
.articles
.published
.tagged_with(article_tags, any: true).unscope(:select)
.limited_column_select
.tagged_with(article_tags, any: true)
.where.not(id: article.id)
.order(published_at: :desc)
.limit(3)

View file

@ -25,7 +25,7 @@ module Articles
Article
.published
.tagged_with(article_tags, any: true)
.tagged_with(article_tags, any: true).unscope(:select)
.limited_column_select
.where("public_reactions_count > ? OR comments_count > ?", reaction_count_num, comment_count_num)
.where.not(id: article.id)
@ -40,7 +40,7 @@ module Articles
Article
.published
.tagged_with(SUGGESTION_TAGS, any: true)
.tagged_with(SUGGESTION_TAGS, any: true).unscope(:select)
.limited_column_select
.where("comments_count > ?", comment_count_num)
.where.not(id: article.id)