Skip to content

Resolve "N+1 database queries on the snippets dashboard"

Andreas Brandl requested to merge 40755-snippets-author-n+1 into master

What does this MR do?

Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/40755.

This change fixes a N+1 query problem when showing snippets in /dashboard/snippets. It reduces the number of queries by

  • Passing a user object instead of an email to avatar_icon to avoid the User#find_by_any_email calls
  • Eagerly load the author relation along with each Snippet

Here's how I observe the change locally with N=3 snippets:

Without the change, the database interaction wrt the user model looks like this:

  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 50]]
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 50]]
  User Load (1.3ms)  SELECT  "users".* FROM (SELECT "users".* FROM "users" WHERE "users"."email" = 'abrandl@gitlab.com'
SELECT "users".* FROM "users" INNER JOIN "emails" ON "emails"."user_id" = "users"."id" WHERE "emails"."email" = 'abrandl@gitlab.com') users LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 50]]
  CACHE (0.0ms)  SELECT  "users".* FROM (SELECT "users".* FROM "users" WHERE "users"."email" = 'abrandl@gitlab.com'
SELECT "users".* FROM "users" INNER JOIN "emails" ON "emails"."user_id" = "users"."id" WHERE "emails"."email" = 'abrandl@gitlab.com') users LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 50]]
  CACHE (0.0ms)  SELECT  "users".* FROM (SELECT "users".* FROM "users" WHERE "users"."email" = 'abrandl@gitlab.com'
SELECT "users".* FROM "users" INNER JOIN "emails" ON "emails"."user_id" = "users"."id" WHERE "emails"."email" = 'abrandl@gitlab.com') users LIMIT 1
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 50]]
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 50]]
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 50]]
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 50]]
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 50]]
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", "50"]]

Notice the N=3 calls to User#find_by_any_email.

With this change applied, this boils down to:

  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 50]]
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (50)
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 50]]
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 50]]
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 50]]
  CACHE (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", "50"]]

Notice that the number of calls is now unrelated to the number of snippets N being shown.

Does this MR meet the acceptance criteria?

What are the relevant issue numbers?

Closes #40755 (closed)

Edited by Yorick Peterse

Merge request reports