Feature #44374
openCache builtin group instances per request to avoid repeated GroupAnonymous and GroupNonMember queries
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
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]]
Updated by Go MAEDA about 1 hour ago
- Target version set to 7.1.0
Setting the target version to 7.1.0.