Improve one of the slowest (on GitLab.com) queries: SELECT FROM members .. (WITH RECURSIVE "base_and_ancestors" AS ...)

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

  • Close this issue

This query group is in Top-5 of the slowest ones on GitLab.com by total_time according to pg_stat_statements:

(reformatted)

SELECT "members".*
FROM "members"
WHERE
    "members"."type" IN (?)
    AND "members"."source_type" = ?=
    AND (members.id IN (
    SELECT "members"."id"
        FROM "members"
        WHERE
            "members"."type" IN (?)
            AND "members"."source_id" = ?
            AND "members"."source_type" = ?
            AND "members"."type" IN (?)
            AND "members"."requested_at" IS NULL
        ) OR members.id IN (
            SELECT "members"."id"
            FROM "members"
            WHERE
                "members"."type" IN (?)
                AND "members"."source_type" = ?
                AND "members"."requested_at" IS NULL
                AND "members"."source_id" IN (
                    WITH RECURSIVE "base_and_ancestors" AS (
                        SELECT "namespaces".*
                        FROM "namespaces"
                        WHERE "namespaces"."type" IN (?) AND "namespaces"."id" = ?

                        UNION

                        SELECT "namespaces".*
                        FROM "namespaces", "base_and_ancestors"
                        WHERE "namespaces"."type" IN (?) AND "namespaces"."id" = "base_and_ancestors"."parent_id"
                    )
                    SELECT "id"
                    FROM "base_and_ancestors" AS "namespaces"
                ) AND ("members"."user_id" NOT IN (
                    SELECT "users"."id"
                    FROM "users"
                    INNER JOIN "members" ON "users"."id" = "members"."user_id"
                    WHERE
                        "members"."source_type" = ?
                        AND "members"."source_id" = ?
                        AND "members"."source_type" = ?
                        AND "members"."type" IN (?)
                        AND "members"."requested_at" IS NULL
                )
            )
        )
    ) AND ("members"."user_id" NOT IN (
        SELECT "members"."user_id"
        FROM "members"
        WHERE
            "members"."type" IN (?)
            AND "members"."source_id" = ?
            AND "members"."source_type" = ?
            AND "members"."type" IN (?)
            AND "members"."requested_at" IS NULL
            AND ("members"."user_id" IS NOT NULL)
        )
    )
;

Example registered in logs:

Apr 12 14:41:11 patroni-04-db-gprd postgres[1262]: [22-1] 2019-04-12 14:41:11 GMT [1262]: [11-1] LOG:  duration: 3427.163 ms  execute <unnamed>: SELECT "members".* FROM "members" WHERE "members"."type" IN ('GroupMember') AND "members"."source_type" = 'Namespace' AND (members.id IN (SELECT "members"."id" FROM "members" WHERE "members"."type" IN ('GroupMember') AND "members"."source_id" = 4280570 AND "members"."source_type" = 'Namespace' AND "members"."type" IN ('GroupMember') AND "members"."requested_at" IS NULL) OR members.id IN (SELECT "members"."id" FROM "members" WHERE "members"."type" IN ('GroupMember') AND "members"."source_type" = 'Namespace' AND "members"."requested_at" IS NULL AND "members"."source_id" IN (WITH RECURSIVE "base_and_ancestors" AS (SELECT "namespaces".* FROM "namespaces" WHERE "namespaces"."type" IN ('Group') AND "namespaces"."id" = 2365196
Apr 12 14:41:11 patroni-04-db-gprd postgres[1262]: [22-2]         UNION
Apr 12 14:41:11 patroni-04-db-gprd postgres[1262]: [22-3]         SELECT "namespaces".* FROM "namespaces", "base_and_ancestors" WHERE "namespaces"."type" IN ('Group') AND "namespaces"."id" = "base_and_ancestors"."parent_id") SELECT "id" FROM "base_and_ancestors" AS "namespaces") AND ("members"."user_id" NOT IN (SELECT "users"."id" FROM "users" INNER JOIN "members" ON "users"."id" = "members"."user_id" WHERE "members"."source_type" = 'Namespace' AND "members"."source_id" = 4280570 AND "members"."source_type" = 'Namespace' AND "members"."type" IN ('GroupMember') AND "members"."requested_at" IS NULL)))) AND ("members"."user_id" NOT IN (SELECT "members"."user_id" FROM "members" WHERE "members"."type" IN ('ProjectMember') AND "members"."source_id" = 10017127 AND "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."requested_at" IS NULL AND ("members"."user_id" IS NOT NULL)))

Query group profile (see the full report made on 2019-04-09):

# Calls ▼ Total time Rows shared_blks_hit shared_blks_read shared_blks_dirtied shared_blks_written blk_read_time blk_write_time kcache_reads kcache_writes kcache_user_time_ms kcache_system_time Query
3 327
0.05/sec
1.00/call
0.00%
1,315,927.27 ms
207ms/sec
4.024s/call
4.25%
123,678
19.51/sec
378.22/call
0.03%
597,272,881 blks
94.22K blks/sec
1.83M blks/call
5.12%
0 blks
0.00 blks/sec
0.00 blks/call
0.00%
190 blks
0.03 blks/sec
0.58 blks/call
0.00%
0 blks
0.00 blks/sec
0.00 blks/call
0.00%
0.00 ms
0s/sec
0s/call
0.00%
0.00 ms
0s/sec
0s/call
0.00%
0.00 bytes
0.00 bytes/sec
0.00 bytes/call
0.00%
0.00 bytes
0.00 bytes/sec
0.00 bytes/call
0.00%
0.00 ms
0s/sec
0s/call
0.00%
0.00 ms
0s/sec
0s/call
0.00%
SELECT "members".* FROM "members" WHERE "members"."type" IN (?) AND "members"."source_type" = ? AND (members.id IN (SELECT "members"."id" FROM "members" WHERE "members"."type" IN (?) AND "members"."source_id" = ? AND "members"."source_type" = ? AND "members"."type" IN (?) AND "members"."requested_at" IS NULL) OR members.id IN (SELECT "members"."id" FROM "members" WHERE "members"."type" IN (?) AND "members"."source_type" = ? AND "members"."requested_at" IS NULL AND "members"."source_id" IN (WITH RECURSIVE "base_and_ancestors" AS (SELECT "namespaces".* FROM "namespaces" WHERE "namespaces"."type" IN (?) AND "namespaces"."id" = ? UNION SELECT "namespaces".* FROM "namespaces", "base_and_ancestors" WHERE "namespaces"."type" IN (?) AND "namespaces"."id" = "base_and_ancestors"."parent_id") SELECT "id" FROM "base_and_ancestors" AS "namespaces") AND ("members"."user_id" NOT IN (SELECT "users"."id" FROM "users" INNER JOIN "members" ON "users"."id" = "members"."user_id" WHERE "members"."source_type" = ? AND "members"."source_id" = ? AND "members"."source_type" = ? AND "members"."type" IN (?) AND "members"."requested_at" IS NULL)))) AND ("members"."user_id" NOT IN (SELECT "members"."user_id" FROM "members" WHERE "members"."type" IN (?) AND "members"."source_id" = ? AND "members"."source_type" = ? AND "members"."type" IN (?) AND "members"."requested_at" IS NULL AND ("members"."user_id" IS NOT NULL)))

Execution plan:

                                                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using index_members_on_source_id_and_source_type on members  (cost=580.52..372484.04 rows=147081 width=168) (actual time=2055.689..4415.389 rows=3 loops=1)
   Index Cond: ((source_type)::text = 'Namespace'::text)
   Filter: ((NOT (hashed SubPlan 5)) AND ((type)::text = 'GroupMember'::text) AND ((hashed SubPlan 1) OR (hashed SubPlan 4)))
   Rows Removed by Filter: 2055358
   Buffers: shared hit=1641753
   SubPlan 5
     ->  Index Scan using index_members_on_source_id_and_source_type on members members_4  (cost=0.43..5.46 rows=3 width=4) (actual time=0.083..0.105 rows=3 loops=1)
           Index Cond: ((source_id = 10017127) AND ((source_type)::text = 'Project'::text))
           Filter: ((requested_at IS NULL) AND (user_id IS NOT NULL) AND ((type)::text = 'ProjectMember'::text))
           Buffers: shared hit=5
   SubPlan 1
     ->  Index Scan using index_members_on_source_id_and_source_type on members members_1  (cost=0.43..3.46 rows=1 width=4) (actual time=0.045..0.045 rows=0 loops=1)
           Index Cond: ((source_id = 4280570) AND ((source_type)::text = 'Namespace'::text))
           Filter: ((requested_at IS NULL) AND ((type)::text = 'GroupMember'::text))
           Buffers: shared hit=3
   SubPlan 4
     ->  Nested Loop  (cost=360.16..571.15 rows=2 width=4) (actual time=0.234..0.321 rows=3 loops=1)
           Buffers: shared hit=20
           ->  HashAggregate  (cost=352.81..353.01 rows=21 width=4) (actual time=0.146..0.146 rows=2 loops=1)
                 Group Key: namespaces_2.id
                 Buffers: shared hit=8
                 ->  CTE Scan on base_and_ancestors namespaces_2  (cost=352.12..352.54 rows=21 width=4) (actual time=0.103..0.145 rows=2 loops=1)
                       Buffers: shared hit=8
                       CTE base_and_ancestors
                         ->  Recursive Union  (cost=0.43..352.12 rows=21 width=295) (actual time=0.102..0.143 rows=2 loops=1)
                               Buffers: shared hit=8
                               ->  Index Scan using namespaces_pkey on namespaces  (cost=0.43..3.45 rows=1 width=295) (actual time=0.091..0.091 rows=1 loops=1)
                                     Index Cond: (id = 2365196)
                                     Filter: ((type)::text = 'Group'::text)
                                     Buffers: shared hit=4
                               ->  Nested Loop  (cost=0.43..34.83 rows=2 width=295) (actual time=0.018..0.018 rows=0 loops=2)
                                     Buffers: shared hit=4
                                     ->  WorkTable Scan on base_and_ancestors  (cost=0.00..0.20 rows=10 width=4) (actual time=0.001..0.001 rows=1 loops=2)
                                     ->  Index Scan using namespaces_pkey on namespaces namespaces_1  (cost=0.43..3.45 rows=1 width=295) (actual time=0.015..0.015 rows=0 loops=2)
                                           Index Cond: (id = base_and_ancestors.parent_id)
                                           Filter: ((type)::text = 'Group'::text)
                                           Buffers: shared hit=4
           ->  Index Scan using index_members_on_source_id_and_source_type on members members_3  (cost=7.35..10.38 rows=1 width=8) (actual time=0.072..0.085 rows=2 loops=2)
                 Index Cond: ((source_id = namespaces_2.id) AND ((source_type)::text = 'Namespace'::text))
                 Filter: ((requested_at IS NULL) AND (NOT (hashed SubPlan 2)) AND ((type)::text = 'GroupMember'::text))
                 Buffers: shared hit=12
                 SubPlan 2
                   ->  Nested Loop  (cost=0.86..6.92 rows=1 width=4) (actual time=0.003..0.003 rows=0 loops=1)
                         Buffers: shared hit=3
                         ->  Index Scan using index_members_on_source_id_and_source_type on members members_2  (cost=0.43..3.46 rows=1 width=4) (actual time=0.003..0.003 rows=0 loops=1)
                               Index Cond: ((source_id = 4280570) AND ((source_type)::text = 'Namespace'::text))
                               Filter: ((requested_at IS NULL) AND ((type)::text = 'GroupMember'::text))
                               Buffers: shared hit=3
                         ->  Index Only Scan using users_pkey on users  (cost=0.43..3.45 rows=1 width=4) (never executed)
                               Index Cond: (id = members_2.user_id)
                               Heap Fetches: 0
 Planning time: 8.151 ms
 Execution time: 4415.721 ms
(53 rows)
Edited Sep 13, 2025 by 🤖 GitLab Bot 🤖
Assignee Loading
Time tracking Loading