Cap milestone show page MR and issue count queries to prevent timeouts

What does this MR do and why?

Groups::MilestonesController#show timed out with an HTTP 500 for large group hierarchies. Loading https://gitlab.com/groups/gitlab-org/-/milestones/136 produced db_main_replica_duration_s: 15.064 and PG::QueryCanceled: ERROR: canceling statement due to statement timeout.

The page ran unbounded COUNT(*) queries for the merge requests and issues visible to the current user. For a group with a large descendant namespace hierarchy, these counts join through the full visibility/authorization tree and include a banned_users anti-join with a + 0 trick that forces nested loops — there's no way for the planner to terminate early, so the query scans everything before it can return a number nobody actually needs precisely (the UI only ever displays a capped badge).

Changes:

  1. app/models/concerns/milestoneish.rb

    • Add DISPLAY_MERGE_REQUESTS_LIMIT = 1000.
    • Add merge_requests_count_for_display(user), capped at the limit.
    • A plain .limit(1001).count was tried first and rejected: the planner still drove the query from the namespace hierarchy (walking all 8,825 visible gitlab-org projects, ~680k buffers), and the retained ORDER BY id DESC forced full evaluation of all 6,523 matching merge requests before the top-N sort could apply — the limit never terminated early. The method instead materializes the milestone's merge requests in a CTE (Gitlab::SQL::CTE, aliased back to merge_requests so the finder's existing conditions still apply), drops the ordering with reorder(nil), and counts with LIMIT 1001. This is a plan-stabilization CTE used as a last resort, per the database guidelines: it forces a plan driven by index_merge_requests_on_milestone_id instead of the hierarchy scan. Measured on a production clone: 17,864 ms → 41 ms warm.
    • Re-add total_merge_requests_count, backed by the Redis-cached Milestones::MergeRequestsCountService. Master removed this as unused in efe5bb9af005 while this MR was in flight; it's used again here by the header view.
  2. app/helpers/timeboxes_helper.rb

    • Add milestone_merge_requests_count_for_display, returning '1000+' once the count exceeds the cap.
    • Cap milestone_visible_issues_count with LIMIT 501 before counting (previously an unbounded .size).
    • milestone_issues_count_message now reuses the memoized capped count instead of running a separate unbounded COUNT(*) — display_issues_count_warning? already executed that query, so no extra query is added — and renders '500+' as the total. This unbounded count was the exact query class this MR eliminates: it fired whenever a milestone had more than 500 issues.
  3. app/views/shared/milestones/_tabs.html.haml: the merge requests tab badge now uses milestone_merge_requests_count_for_display instead of calling .size on the unbounded relation.

  4. app/views/shared/milestones/_header.html.haml: use milestone.total_issues_count / milestone.total_merge_requests_count (Redis-cached count services) instead of the uncached milestone.issues.count / milestone.merge_requests.count.

How to set up and validate locally

  1. Seed a milestone with more than 1000 merge requests and more than 500 issues (or lower DISPLAY_MERGE_REQUESTS_LIMIT / the issues cap locally to make seeding cheaper).
  2. Visit the group or project milestone show page for that milestone.
  3. Confirm the page loads without timing out, the merge requests tab badge shows 1000+, and the issues count warning shows 500+.
  4. Confirm the header counts (issues/merge requests totals) still render correctly and match the Redis-cached count services.

References

Screenshots or screen recordings

Before After
Milestone show page (large group, e.g. gitlab-org) HTTP 500 / statement timeout Page loads
Merge requests tab badge Unbounded count, drives the timeout Capped, shows 1000+ past the limit
Issues count warning Unbounded COUNT(*) when >500 issues Reuses capped count, shows 500+

Database review

All measurements taken on a Database Lab thin clone of the production database (data state 2026-08-12), against group gitlab-org (namespace 9970), milestone %19.2 (id 6177888, iid 136; 6,523+ visible merge requests), as a user with member access. The clone's I/O is slower than production replicas, so the cold numbers below are pessimistic relative to production.

Each row is one query the milestone show page runs, before and after this MR. Timings are cold / warm on the clone.

Page element Before this MR After this MR
Merge requests tab badge Unbounded COUNT(*) across the visibility tree; times out in production (>15 s replica time, PG::QueryCanceled). Even with a naive LIMIT 1001 bolted on it stays at 39,197 ms / 17,864 ms (rejected approach, plan below) Materialized CTE + LIMIT 1001: ~1,268 ms / 41 ms
Issues count warning Same unbounded COUNT(*) shape, fired whenever a milestone has >500 issues Capped LIMIT 501 count: 1,175 ms / 6.6 ms
Header issues total Uncached COUNT(*) on every request Redis-cached count service; underlying query 329 ms / 2.0 ms
Header merge requests total Uncached COUNT(*) on every request Redis-cached count service; underlying query 338 ms / 2.5 ms
Merge requests tab badge — BEFORE: unbounded count (shown with a naive LIMIT 1001, which does not help — rejected approach)

Query

SELECT COUNT(*) FROM (SELECT 1 AS one FROM "merge_requests" INNER JOIN "projects" ON "projects"."id" = "merge_requests"."target_project_id" LEFT JOIN project_features ON projects.id = project_features.project_id WHERE (NOT EXISTS (SELECT 1 FROM "banned_users" WHERE (banned_users.user_id = (merge_requests.author_id + 0)))) AND "projects"."namespace_id" IN (SELECT "namespaces"."id" FROM UNNEST(
  COALESCE(
    (SELECT ids FROM (SELECT "namespace_descendants"."self_and_descendant_group_ids" AS ids FROM "namespace_descendants" WHERE "namespace_descendants"."outdated_at" IS NULL AND "namespace_descendants"."namespace_id" = 9970) cached_query),
    (SELECT ids FROM (SELECT ARRAY_AGG("namespaces"."id") AS ids FROM (SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{9970}'))) namespaces) consistent_query))
) AS namespaces(id)
) AND (EXISTS (SELECT 1 FROM "project_authorizations" WHERE "project_authorizations"."user_id" = 3483274 AND (project_authorizations.project_id = projects.id) AND (project_authorizations.access_level >= 20)) OR projects.visibility_level IN (10, 20)) AND ("project_features"."merge_requests_access_level" IS NULL OR "project_features"."merge_requests_access_level" IN (20, 30) OR ("project_features"."merge_requests_access_level" = 10 AND EXISTS (SELECT 1 FROM "project_authorizations" WHERE "project_authorizations"."user_id" = 3483274 AND (project_authorizations.project_id = project_features.project_id) AND (project_authorizations.access_level >= 20)))) AND "merge_requests"."milestone_id" = 6177888 ORDER BY "merge_requests"."id" DESC LIMIT 1001) subquery_for_count;

Plan (warm)

The retained ORDER BY id DESC forces evaluation of all matching merge requests, and the planner drives the scan from the namespace hierarchy rather than the milestone index — the limit never gets to short-circuit.

Aggregate  (cost=8191.55..8191.56 rows=1 width=8) (actual time=17862.786..17862.825 rows=1 loops=1)
   Buffers: shared hit=114024 read=707381
   I/O Timings: shared read=15329.676
   ->  Limit  (cost=8191.53..8191.54 rows=1 width=12) (actual time=17862.559..17862.750 rows=1001 loops=1)
         Buffers: shared hit=114024 read=707381
         I/O Timings: shared read=15329.676
         InitPlan 5
           ->  Index Scan using namespace_descendants_12_pkey on namespace_descendants_12 namespace_descendants  (cost=0.14..3.16 rows=1 width=680) (actual time=0.123..0.129 rows=1 loops=1)
                 Index Cond: (namespace_id = 9970)
                 Filter: (outdated_at IS NULL)
                 Buffers: shared hit=3 read=2
                 I/O Timings: shared read=0.100
         InitPlan 6
           ->  Aggregate  (cost=1854.65..1854.66 rows=1 width=32) (never executed)
                 ->  Bitmap Heap Scan on namespaces namespaces_1  (cost=170.78..1849.32 rows=1067 width=28) (never executed)
                       Recheck Cond: ((traversal_ids @> '{9970}'::integer[]) AND ((type)::text = 'Group'::text))
                       ->  Bitmap Index Scan on index_namespaces_on_traversal_ids_for_groups  (cost=0.00..170.52 rows=1067 width=0) (never executed)
                             Index Cond: (traversal_ids @> '{9970}'::integer[])
         ->  Sort  (cost=6333.70..6333.71 rows=1 width=12) (actual time=17862.557..17862.662 rows=1001 loops=1)
               Sort Key: merge_requests.id DESC
               Sort Method: top-N heapsort  Memory: 115kB
               Buffers: shared hit=114024 read=707381
               I/O Timings: shared read=15329.676
               ->  Nested Loop Anti Join  (cost=2.29..6333.69 rows=1 width=12) (actual time=27.646..17853.149 rows=6523 loops=1)
                     Buffers: shared hit=114021 read=707381
                     I/O Timings: shared read=15329.676
                     ->  Nested Loop Left Join  (cost=1.86..6331.77 rows=1 width=16) (actual time=27.601..17796.867 rows=6523 loops=1)
                           Filter: ((project_features.merge_requests_access_level IS NULL) OR (project_features.merge_requests_access_level = ANY ('{20,30}'::integer[])) OR ((project_features.merge_requests_access_level = 10) AND EXISTS(SubPlan 3)))
                           Buffers: shared hit=94951 read=706882
                           I/O Timings: shared read=15319.643
                           ->  Nested Loop  (cost=1.29..6327.46 rows=1 width=20) (actual time=27.369..17710.843 rows=6523 loops=1)
                                 Buffers: shared hit=61215 read=706327
                                 I/O Timings: shared read=15291.823
                                 ->  Nested Loop  (cost=0.72..1825.80 rows=203 width=4) (actual time=1.475..1252.138 rows=8825 loops=1)
                                       Buffers: shared hit=30981 read=27377
                                       I/O Timings: shared read=1074.494
                                       ->  HashAggregate  (cost=0.15..0.26 rows=10 width=8) (actual time=1.128..3.604 rows=1560 loops=1)
                                             Group Key: namespaces.id
                                             Batches: 1  Memory Usage: 153kB
                                             Buffers: shared hit=29 read=9
                                             I/O Timings: shared read=0.383
                                             ->  Function Scan on unnest namespaces  (cost=0.03..0.13 rows=10 width=8) (actual time=0.662..0.743 rows=1560 loops=1)
                                                   Buffers: shared hit=29 read=9
                                                   I/O Timings: shared read=0.383
                                       ->  Index Scan using index_projects_on_namespace_id_and_id on projects  (cost=0.56..182.35 rows=20 width=8) (actual time=0.154..0.798 rows=6 loops=1560)
                                             Index Cond: (namespace_id = namespaces.id)
                                             Filter: (EXISTS(SubPlan 1) OR (visibility_level = ANY ('{10,20}'::integer[])))
                                             Buffers: shared hit=30952 read=27368
                                             I/O Timings: shared read=1074.111
                                             SubPlan 1
                                               ->  Index Only Scan using index_project_authorizations_on_project_user_access_level on project_authorizations  (cost=0.58..3.60 rows=1 width=0) (actual time=0.094..0.094 rows=1 loops=8825)
                                                     Index Cond: ((project_id = projects.id) AND (user_id = 3483274) AND (access_level >= 20))
                                                     Heap Fetches: 1266
                                                     Buffers: shared hit=26442 read=16797
                                                     I/O Timings: shared read=716.266
                                 ->  Index Scan using index_merge_requests_on_target_project_id_and_squash_commit_sha on merge_requests  (cost=0.57..22.17 rows=1 width=24) (actual time=0.696..1.864 rows=1 loops=8825)
                                       Index Cond: (target_project_id = projects.id)
                                       Filter: (milestone_id = 6177888)
                                       Rows Removed by Filter: 76
                                       Buffers: shared hit=30234 read=678950
                                       I/O Timings: shared read=14217.329
                           ->  Index Scan using index_project_features_on_project_id on project_features  (cost=0.56..0.69 rows=1 width=8) (actual time=0.010..0.010 rows=1 loops=6523)
                                 Index Cond: (project_id = projects.id)
                                 Buffers: shared hit=32060 read=555
                                 I/O Timings: shared read=27.820
                           SubPlan 3
                             ->  Index Only Scan using index_project_authorizations_on_project_user_access_level on project_authorizations project_authorizations_1  (cost=0.58..3.60 rows=1 width=0) (actual time=0.005..0.005 rows=1 loops=417)
                                   Index Cond: ((project_id = project_features.project_id) AND (user_id = 3483274) AND (access_level >= 20))
                                   Heap Fetches: 0
                                   Buffers: shared hit=1676
                     ->  Index Only Scan using banned_users_pkey on banned_users  (cost=0.43..1.33 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=6523)
                           Index Cond: (user_id = (merge_requests.author_id + 0))
                           Heap Fetches: 0
                           Buffers: shared hit=19070 read=499
                           I/O Timings: shared read=10.033
 Planning:
   Buffers: shared hit=2365 read=634
   I/O Timings: shared read=23.393
 Planning Time: 40.448 ms
 Execution Time: 17863.512 ms
Merge requests tab badge — AFTER: materialized CTE + LIMIT 1001 (this MR)

Query

SELECT COUNT(*) FROM (WITH "milestone_merge_requests" AS MATERIALIZED (SELECT "merge_requests"."id", "merge_requests"."milestone_id", "merge_requests"."target_project_id", "merge_requests"."author_id" FROM "merge_requests" WHERE "merge_requests"."milestone_id" = 6177888) SELECT 1 AS one FROM "milestone_merge_requests" AS "merge_requests" INNER JOIN "projects" ON "projects"."id" = "merge_requests"."target_project_id" LEFT JOIN project_features ON projects.id = project_features.project_id WHERE (NOT EXISTS (SELECT 1 FROM "banned_users" WHERE (banned_users.user_id = (merge_requests.author_id + 0)))) AND "projects"."namespace_id" IN (SELECT "namespaces"."id" FROM UNNEST(
  COALESCE(
    (SELECT ids FROM (SELECT "namespace_descendants"."self_and_descendant_group_ids" AS ids FROM "namespace_descendants" WHERE "namespace_descendants"."outdated_at" IS NULL AND "namespace_descendants"."namespace_id" = 9970) cached_query),
    (SELECT ids FROM (SELECT ARRAY_AGG("namespaces"."id") AS ids FROM (SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{9970}'))) namespaces) consistent_query))
) AS namespaces(id)
) AND (EXISTS (SELECT 1 FROM "project_authorizations" WHERE "project_authorizations"."user_id" = 3483274 AND (project_authorizations.project_id = projects.id) AND (project_authorizations.access_level >= 20)) OR projects.visibility_level IN (10, 20)) AND ("project_features"."merge_requests_access_level" IS NULL OR "project_features"."merge_requests_access_level" IN (20, 30) OR ("project_features"."merge_requests_access_level" = 10 AND EXISTS (SELECT 1 FROM "project_authorizations" WHERE "project_authorizations"."user_id" = 3483274 AND (project_authorizations.project_id = project_features.project_id) AND (project_authorizations.access_level >= 20)))) AND "merge_requests"."milestone_id" = 6177888 LIMIT 1001) subquery_for_count /*application:web,db_config_database:gitlabhq_dblab,db_config_name:main,line:/app/models/concerns/milestoneish.rb:96:in `merge_requests_count_for_display'*/;

Plan (warm)

Materializing the milestone's merge requests first and dropping the ordering lets the planner use index_merge_requests_on_milestone_id directly instead of walking the visible-project hierarchy.

Aggregate  (cost=12356.30..12356.31 rows=1 width=8) (actual time=40.964..40.969 rows=1 loops=1)
   Buffers: shared hit=19134
   ->  Limit  (cost=12145.80..12356.29 rows=1 width=4) (actual time=0.522..40.848 rows=1001 loops=1)
         Buffers: shared hit=19134
         CTE milestone_merge_requests
           ->  Index Scan using index_merge_requests_on_milestone_id on merge_requests merge_requests_1  (cost=0.57..10286.38 rows=8661 width=32) (actual time=0.057..3.809 rows=1001 loops=1)
                 Index Cond: (milestone_id = 6177888)
                 Buffers: shared hit=1013
         InitPlan 6
           ->  Index Scan using namespace_descendants_12_pkey on namespace_descendants_12 namespace_descendants  (cost=0.14..3.16 rows=1 width=680) (actual time=0.010..0.010 rows=1 loops=1)
                 Index Cond: (namespace_id = 9970)
                 Filter: (outdated_at IS NULL)
                 Buffers: shared hit=2
         InitPlan 7
           ->  Aggregate  (cost=1854.65..1854.66 rows=1 width=32) (never executed)
                 ->  Bitmap Heap Scan on namespaces namespaces_1  (cost=170.78..1849.32 rows=1067 width=28) (never executed)
                       Recheck Cond: ((traversal_ids @> '{9970}'::integer[]) AND ((type)::text = 'Group'::text))
                       ->  Bitmap Index Scan on index_namespaces_on_traversal_ids_for_groups  (cost=0.00..170.52 rows=1067 width=0) (never executed)
                             Index Cond: (traversal_ids @> '{9970}'::integer[])
         ->  Nested Loop Semi Join  (cost=1.59..212.08 rows=1 width=4) (actual time=0.520..40.739 rows=1001 loops=1)
               Join Filter: (namespaces.id = projects.namespace_id)
               Rows Removed by Join Filter: 187974
               Buffers: shared hit=19134
               ->  Nested Loop Anti Join  (cost=1.56..211.82 rows=1 width=4) (actual time=0.257..21.574 rows=1001 loops=1)
                     Buffers: shared hit=19099
                     ->  Nested Loop Left Join  (cost=1.13..206.37 rows=1 width=12) (actual time=0.213..16.800 rows=1001 loops=1)
                           Filter: ((project_features.merge_requests_access_level IS NULL) OR (project_features.merge_requests_access_level = ANY ('{20,30}'::integer[])) OR ((project_features.merge_requests_access_level = 10) AND EXISTS(SubPlan 4)))
                           Buffers: shared hit=16096
                           ->  Nested Loop  (cost=0.56..202.06 rows=1 width=16) (actual time=0.172..13.287 rows=1001 loops=1)
                                 Buffers: shared hit=10809
                                 ->  CTE Scan on milestone_merge_requests merge_requests  (cost=0.00..194.87 rows=1 width=16) (actual time=0.061..4.197 rows=1001 loops=1)
                                       Filter: (milestone_id = 6177888)
                                       Buffers: shared hit=1013
                                 ->  Index Scan using idx_projects_on_repository_storage_last_repository_updated_at on projects  (cost=0.56..7.19 rows=1 width=8) (actual time=0.009..0.009 rows=1 loops=1001)
                                       Index Cond: (id = merge_requests.target_project_id)
                                       Filter: (EXISTS(SubPlan 2) OR (visibility_level = ANY ('{10,20}'::integer[])))
                                       Buffers: shared hit=9796
                                       SubPlan 2
                                         ->  Index Only Scan using index_project_authorizations_on_project_user_access_level on project_authorizations  (cost=0.58..3.60 rows=1 width=0) (actual time=0.004..0.004 rows=1 loops=1001)
                                               Index Cond: ((project_id = projects.id) AND (user_id = 3483274) AND (access_level >= 20))
                                               Heap Fetches: 37
                                               Buffers: shared hit=4754
                           ->  Index Scan using index_project_features_on_project_id on project_features  (cost=0.56..0.69 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=1001)
                                 Index Cond: (project_id = projects.id)
                                 Buffers: shared hit=5005
                           SubPlan 4
                             ->  Index Only Scan using index_project_authorizations_on_project_user_access_level on project_authorizations project_authorizations_1  (cost=0.58..3.60 rows=1 width=0) (actual time=0.002..0.002 rows=1 loops=65)
                                   Index Cond: ((project_id = project_features.project_id) AND (user_id = 3483274) AND (access_level >= 20))
                                   Heap Fetches: 0
                                   Buffers: shared hit=282
                     ->  Index Only Scan using banned_users_pkey on banned_users  (cost=0.43..3.45 rows=1 width=8) (actual time=0.005..0.005 rows=0 loops=1001)
                           Index Cond: (user_id = (merge_requests.author_id + 0))
                           Heap Fetches: 0
                           Buffers: shared hit=3003
               ->  Function Scan on unnest namespaces  (cost=0.03..0.13 rows=10 width=8) (actual time=0.000..0.009 rows=189 loops=1001)
                     Buffers: shared hit=35
 Planning:
   Buffers: shared hit=3026
 Planning Time: 14.199 ms
 Execution Time: 41.283 ms
Issues count warning — AFTER: capped LIMIT 501 (this MR; before, the same query ran unbounded)

Query

SELECT COUNT(*) FROM (SELECT 1 AS one FROM "issues" INNER JOIN "projects" ON "projects"."id" = "issues"."project_id" LEFT JOIN project_features ON projects.id = project_features.project_id WHERE (NOT EXISTS (SELECT 1 FROM "banned_users" WHERE (banned_users.user_id = (issues.author_id + 0)))) AND (namespace_traversal_ids[1] = 9970) AND ("project_features"."issues_access_level" > 0 OR "project_features"."issues_access_level" IS NULL) AND "issues"."state_id" IN (1, 2) AND "issues"."milestone_id" = 6177888 ORDER BY "issues"."id" DESC LIMIT 501) subquery_for_count;

Plan (warm)

Aggregate  (cost=133.67..133.68 rows=1 width=8) (actual time=6.405..6.408 rows=1 loops=1)
   Buffers: shared hit=7291
   ->  Limit  (cost=2.13..133.65 rows=1 width=8) (actual time=0.153..6.351 rows=501 loops=1)
         Buffers: shared hit=7291
         ->  Nested Loop Anti Join  (cost=2.13..133.65 rows=1 width=8) (actual time=0.151..6.303 rows=501 loops=1)
               Buffers: shared hit=7291
               ->  Nested Loop Left Join  (cost=1.70..128.19 rows=1 width=8) (actual time=0.074..4.006 rows=501 loops=1)
                     Filter: ((project_features.issues_access_level > 0) OR (project_features.issues_access_level IS NULL))
                     Buffers: shared hit=5785
                     ->  Nested Loop  (cost=1.13..127.54 rows=1 width=12) (actual time=0.045..2.509 rows=501 loops=1)
                           Buffers: shared hit=3280
                           ->  Index Scan Backward using index_issues_on_milestone_id_and_id on issues  (cost=0.57..123.96 rows=1 width=12) (actual time=0.020..1.153 rows=527 loops=1)
                                 Index Cond: (milestone_id = 6177888)
                                 Filter: ((state_id = ANY ('{1,2}'::integer[])) AND (namespace_traversal_ids[1] = 9970))
                                 Buffers: shared hit=535
                           ->  Index Only Scan using idx_projects_on_repository_storage_last_repository_updated_at on projects  (cost=0.56..3.58 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=527)
                                 Index Cond: (id = issues.project_id)
                                 Heap Fetches: 424
                                 Buffers: shared hit=2745
                     ->  Index Scan using index_project_features_on_project_id on project_features  (cost=0.56..0.64 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=501)
                           Index Cond: (project_id = projects.id)
                           Buffers: shared hit=2505
               ->  Index Only Scan using banned_users_pkey on banned_users  (cost=0.43..3.45 rows=1 width=8) (actual time=0.004..0.004 rows=0 loops=501)
                     Index Cond: (user_id = (issues.author_id + 0))
                     Heap Fetches: 0
                     Buffers: shared hit=1506
 Planning:
   Buffers: shared hit=2342
 Planning Time: 7.553 ms
 Execution Time: 6.594 ms
Header issues total — AFTER: Redis-cached count service (underlying query; before, an uncached count ran per request)

Query

SELECT COUNT(*) FROM "issues" WHERE "issues"."milestone_id" = 6177888;

Plan (warm)

Aggregate  (cost=8.02..8.03 rows=1 width=8) (actual time=1.879..1.880 rows=1 loops=1)
   Buffers: shared hit=2381
   ->  Index Only Scan using index_issues_on_milestone_id_and_id on issues  (cost=0.57..7.81 rows=84 width=0) (actual time=0.067..1.741 rows=2452 loops=1)
         Index Cond: (milestone_id = 6177888)
         Heap Fetches: 71
         Buffers: shared hit=2381
 Planning:
   Buffers: shared hit=883
 Planning Time: 4.151 ms
 Execution Time: 1.989 ms
Header merge requests total — AFTER: Redis-cached count service (underlying query; before, an uncached count ran per request)

Query

SELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."milestone_id" = 6177888;

Plan (warm)

Aggregate  (cost=424.52..424.53 rows=1 width=8) (actual time=2.363..2.364 rows=1 loops=1)
   Buffers: shared hit=651
   ->  Index Only Scan using index_merge_requests_on_milestone_id on merge_requests  (cost=0.57..402.87 rows=8661 width=0) (actual time=0.063..2.023 rows=6523 loops=1)
         Index Cond: (milestone_id = 6177888)
         Heap Fetches: 106
         Buffers: shared hit=651
 Planning:
   Buffers: shared hit=671
 Planning Time: 4.822 ms
 Execution Time: 2.470 ms
Edited by Alexandru Croitor

Merge request reports

Loading
Loading