Project

General

Profile

Actions

Feature #44374

open

Cache builtin group instances per request to avoid repeated GroupAnonymous and GroupNonMember queries

Added by Go MAEDA 3 days ago. Updated about 2 hours ago.

Status:
New
Priority:
Normal
Assignee:
-
Category:
Performance
Target version:
Resolution:

Description

Group.anonymous and Group.non_member perform the same Active Record lookup every time they are called. Although the SQL result is usually served from the Active Record query cache, each lookup still adds CPU overhead. They are called repeatedly when building visibility conditions and determining roles, so the same work is repeated many times in a single request.

The attached patches fix this in two steps:

1. 0001-Cache-builtin-group-instances-per-request-to-avoid-r.patch memoizes the instances loaded by GroupBuiltin.load_instance in an ActiveSupport::CurrentAttributes store.
2. 0002-Use-cached-builtin-group-instances-in-User-roles-and.patch makes the remaining direct lookups in User#roles and User#project_ids_by_role use the cache.

Measured on GET /issues in the test environment (fixtures, SQLite), median of 20 requests:

User Builtin group lookups per request Median response time
admin (administrator) 0 → 0 18.0 ms → 18.0 ms
jsmith (project member) 15 → 1 23.5 ms → 21.3 ms
someone (no memberships) 30 → 1 23.4 ms → 20.6 ms
anonymous 30 → 1 23.4 ms → 20.3 ms

Admin users are not affected because Project.allowed_to_condition returns early for them. The improvement applies to every page that builds visibility conditions, not just /issues.


Files

Actions #1

Updated by Go MAEDA 2 days ago

The following shows the change in Active Record lookups when processing GET /issues for GroupNonMember, before and after applying the patches.

Before:

  GroupNonMember Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  GroupNonMember Pluck (0.1ms)  SELECT "users"."id" FROM "users" WHERE "users"."type" = ? LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]
  CACHE GroupNonMember Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]

After:

  GroupNonMember Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."type" = ? ORDER BY "users"."id" ASC LIMIT ?  [["type", "GroupNonMember"], ["LIMIT", 1]]

Actions #2

Updated by Go MAEDA about 2 hours ago

  • Target version set to 7.1.0

Setting the target version to 7.1.0.

Actions

Also available in: Atom PDF