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 |
|---|---|
![]() |
![]() |
How to set up and validate locally
Setup GDK in self-managed mode with a Premium license.
- Create a test user and do not assign any projects or groups to it
- Create a test user on the UI and it to a group with access level
Minimal Access - 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
- 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

