Make minimal access and users without memberships non-billable

Resolves Make `minimal access users` or `users not assig... (#330663 - closed).

What does this MR do and why?

This MR makes minimal access users and users without group/project memberships non-billable on Premium self-managed.

Currently on Premium SM, these users consume license seats which creates a barrier for SSO and Restricted Access adoption and causes customers to pay for seats that provide no value. More details in #330663 (closed).

NOTE: Ultimate and GitLab.com already work correctly and are not in the scope of this change.

References

Database review

 Nested Loop  (cost=1428653.67..3107524.94 rows=846470 width=1509) (actual time=28415.319..264349.218 rows=10424572 loops=1)
   Buffers: shared hit=60956387 read=14379658 dirtied=12
   WAL: records=13 fpi=12 bytes=92437
   I/O Timings: read=141853.663 write=0.000
   ->  HashAggregate  (cost=1428653.23..1438837.48 rows=1018425 width=4) (actual time=28415.093..41830.451 rows=12279298 loops=1)
         Group Key: members.user_id
         Buffers: shared hit=25843194 read=375197
         I/O Timings: read=3926.800 write=0.000
         ->  Index Only Scan using index_members_on_user_id_and_access_level_requested_at_is_null on public.members  (cost=0.56..1283893.81 rows=57903766 width=4) (actual time=0.215..15028.866 rows=57376816 loops=1)
               Index Cond: (members.access_level > 5)
               Heap Fetches: 2319975
               Buffers: shared hit=25843194 read=375197
               I/O Timings: read=3926.800 write=0.000
   ->  Index Scan using users_pkey on public.users  (cost=0.44..1.69 rows=1 width=1509) (actual time=0.018..0.018 rows=1 loops=12279298)
         Index Cond: (users.id = members.user_id)
         Filter: (((users.state)::text = 'active'::text) AND (users.user_type = ANY ('{0,6,4,13}'::integer[])) AND (users.user_type = ANY ('{0,4,5,15,17}'::integer[])))
         Rows Removed by Filter: 0
         Buffers: shared hit=35113174 read=14004461 dirtied=12
         WAL: records=13 fpi=12 bytes=92437
         I/O Timings: read=137926.863 write=0.000
Settings: seq_page_cost = '4', work_mem = '100MB', effective_cache_size = '472585MB', jit = 'off', random_page_cost = '1.5'

Screenshots or screen recordings

Before After
image image

How to set up and validate locally

Setup GDK in self-managed mode with a Premium license.

  1. Create a test user and do not assign any projects or groups to it
  2. Create a test user on the UI and it to a group with access level Minimal Access
  3. On the console, find the test users and verify billable status:
no_membership_user = # Find user created above by ID
minimal_user = # Find user created above by ID

User.billable.include?(minimal_user)      # Should be false
User.billable.include?(no_membership_user) # Should be false

minimal_user.using_license_seat?      # Should be false
no_membership_user.using_license_seat? # Should be false
  1. Check Admin UI:
    • Go to Admin Area → Users
    • Verify minimal access user has no "Is using seat" badge
    • Click on user → Verify "Is using license seat: No"

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Paulo Barros

Merge request reports

Loading