Add a new index to improve query performance
What does this MR do?
This MR adds a new multicolumn index and replaces existing index to achive INDEX ONLY queries when querying notification settings.
I've also noticed that there was a redundant index on user_id
so I'm removing that one too.
We also have index on (user_id, source_id, source_type)
so we have two similar index one starting from user_id
and another one starting from source_id
. I wonder if there is better way to handle this.
Related to #326045 (closed)
Example queries
explain SELECT "notification_settings".user_id
FROM "notification_settings"
WHERE "notification_settings"."source_id" = 278964
AND "notification_settings"."source_type" = 'Project'
AND "notification_settings"."level" = 5
BEFORE: 58.132ms - https://explain.depesz.com/s/BBlv
AFTER: 0.211ms - https://explain.depesz.com/s/mfnh
explain SELECT "notification_settings".user_id
FROM "notification_settings"
WHERE "notification_settings"."source_id" = 9970
AND "notification_settings"."source_type" = 'Namespace'
AND "notification_settings"."level" = 3
BEFORE: 89.414ms - https://explain.depesz.com/s/xyMU
AFTER: 13.221ms - https://explain.depesz.com/s/iaA8
!-- I'm working on rewriting the ruby logic into sql query to avoid returning massive list of user_ids only to feed back into the query later. This is one of those queries I'm working on.
explain SELECT "notification_settings".user_id
FROM "notification_settings"
WHERE
(
(
"notification_settings"."source_id" = 278964
AND "notification_settings"."source_type" = 'Project'
AND "notification_settings"."level" = 5
)
OR (
"notification_settings"."source_id" = 278964
AND "notification_settings"."source_type" = 'Project'
AND "notification_settings"."level" = 3
AND EXISTS(
SELECT 1
FROM "notification_settings" n2
WHERE n2.user_id = "notification_settings"."user_id"
AND n2."source_id" IS NULL
AND n2."source_type" IS NULL
AND n2."level" = 5
)
)
OR (
"notification_settings"."source_id" = 9970
AND "notification_settings"."source_type" = 'Namespace'
AND "notification_settings"."level" = 5
)
OR (
"notification_settings"."source_id" = 9970
AND "notification_settings"."source_type" = 'Namespace'
AND "notification_settings"."level" = 3
AND EXISTS(
SELECT 1
FROM "notification_settings" n2
WHERE n2.user_id = "notification_settings"."user_id"
AND n2."source_id" IS NULL
AND n2."source_type" IS NULL
AND n2."level" = 5
)
)
);
BEFORE: 830.092ms - https://explain.depesz.com/s/938d
AFTER: 311.206ms - https://explain.depesz.com/s/EUxr
Migration
UP
== 20210402005225 AddSourceAndLevelIndexOnNotificationSettings: migrating =====
-- transaction_open?()
-> 0.0000s
-- index_exists?(:notification_settings, [:source_id, :source_type, :level, :user_id], {:name=>"index_notification_settings_on_source_and_level_and_user", :algorithm=>:concurrently})
-> 0.0029s
-- execute("SET statement_timeout TO 0")
-> 0.0006s
-- add_index(:notification_settings, [:source_id, :source_type, :level, :user_id], {:name=>"index_notification_settings_on_source_and_level_and_user", :algorithm=>:concurrently})
-> 0.0036s
-- execute("RESET ALL")
-> 0.0005s
-- transaction_open?()
-> 0.0000s
-- indexes(:notification_settings)
-> 0.0019s
-- remove_index(:notification_settings, {:algorithm=>:concurrently, :name=>"index_notification_settings_on_source_id_and_source_type"})
-> 0.0021s
-- transaction_open?()
-> 0.0000s
-- indexes(:notification_settings)
-> 0.0016s
-- remove_index(:notification_settings, {:algorithm=>:concurrently, :name=>"index_notification_settings_on_user_id"})
-> 0.0012s
== 20210402005225 AddSourceAndLevelIndexOnNotificationSettings: migrated (0.0171s)
DOWN
== 20210402005225 AddSourceAndLevelIndexOnNotificationSettings: reverting =====
-- transaction_open?()
-> 0.0000s
-- indexes(:notification_settings)
-> 0.0027s
-- execute("SET statement_timeout TO 0")
-> 0.0007s
-- remove_index(:notification_settings, {:algorithm=>:concurrently, :name=>"index_notification_settings_on_source_and_level_and_user"})
-> 0.0044s
-- execute("RESET ALL")
-> 0.0007s
-- transaction_open?()
-> 0.0000s
-- index_exists?(:notification_settings, [:source_id, :source_type], {:name=>"index_notification_settings_on_source_id_and_source_type", :algorithm=>:concurrently})
-> 0.0013s
-- add_index(:notification_settings, [:source_id, :source_type], {:name=>"index_notification_settings_on_source_id_and_source_type", :algorithm=>:concurrently})
-> 0.0038s
-- transaction_open?()
-> 0.0000s
-- index_exists?(:notification_settings, [:user_id], {:name=>"index_notification_settings_on_user_id", :algorithm=>:concurrently})
-> 0.0016s
-- add_index(:notification_settings, [:user_id], {:name=>"index_notification_settings_on_user_id", :algorithm=>:concurrently})
-> 0.0036s
== 20210402005225 AddSourceAndLevelIndexOnNotificationSettings: reverted (0.0217s)
Does this MR meet the acceptance criteria?
Conformity
-
📋 Does this MR need a changelog?-
I have included a changelog entry. -
I have not included a changelog entry because _____.
-
- [-] Documentation (if required)
-
Code review guidelines -
Merge request performance guidelines -
Style guides -
Database guides -
Separation of EE specific content
Availability and Testing
-
Review and add/update tests for this feature/bug. Consider all test levels. See the Test Planning Process. -
Tested in all supported browsers -
Informed Infrastructure department of a default or new setting change, if applicable per definition of done
Security
If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:
-
Label as security and @ mention @gitlab-com/gl-security/appsec
-
The MR includes necessary changes to maintain consistency between UI, API, email, or other methods -
Security reports checked/validated by a reviewer from the AppSec team