Record Orbit beta and trial enrollments

What does this MR do and why?

Records why a top-level group can use Orbit: the beta or a trial. The trial flow reads it first.

  • The orbit_namespace_enrollments table and Analytics::Orbit::NamespaceEnrollment record why a group can use Orbit (source is direct, beta, or trial, plus trial_ended_at). direct is for groups that turn on Orbit after GA. The row is keyed by namespace_id, so each group has one row. Unlike the knowledge_graph_enabled_namespaces row, this row stays when Orbit is off.
  • A post-deploy backfill, and NamespaceEnrollment.record_beta on every enable (Owner, admin API, and the Self-Managed auto-index worker). The enable and the enrollment write happen in one transaction.
  • Auditors get read_orbit_trial, as requested in the !258545 (merged) review.
Event Enrollment
Orbit is on before this MR beta (backfill on GitLab.com)
Orbit is turned on beta
The group has a row No change, so a trial row stays trial
Orbit is turned off No change
The group moves under another group Row deleted
Subgroup or user namespace No row

The row points to the group, so it stays when Orbit is off. A later MR writes the trial row.

References

https://gitlab.com/gitlab-org/orbit/-/work_items/19

Closes https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/work_items/1335

Closes https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/work_items/1336

How to set up and validate locally

Run bin/rails db:migrate. Then, in the Rails console:

Analytics::Orbit::NamespaceEnrollment.group(:source).count

Turn Orbit on for a top-level group as an Owner, and check the row again:

mutation { orbitUpdate(input: { groupPath: "toolbox", enabled: true }) { errors } }

Demo against the local stack: the backfill, Orbit off and on (the row stays), a new beta row, a trial row that stays, and the admin API for a group and a user namespace.

Migration output
main: == 20260929214327 CreateOrbitNamespaceEnrollments: migrating ==================
main: -- create_table(:orbit_namespace_enrollments)
main: == 20260929214327 CreateOrbitNamespaceEnrollments: migrated (0.0657s) =========
main: == 20260929214328 BackfillOrbitNamespaceEnrollmentsBeta: migrated (0.0897s) ===

main: == 20260929214328 BackfillOrbitNamespaceEnrollmentsBeta: reverted (0.0194s) ===
main: == 20260929214327 CreateOrbitNamespaceEnrollments: reverted (0.0657s) =========

Query plans

The backfill is safe for GitLab.com. On a production clone, a batch of 1,000 groups takes 3.5 s with a cold cache, and the full table takes 9.2 s in one statement.

Query Time (cold cache) Plan
One batch (1,000 IDs) 3.5 s Plan
Full table, one statement 9.2 s Plan

NamespaceEnrollment.record_beta runs two queries. On GitLab.com, the toggle service and the admin API pass one group. The auto-index worker runs only on Self-Managed and passes up to 1,000 groups.

SELECT "namespaces"."id" FROM "namespaces"
WHERE "namespaces"."type" = 'Group' AND "namespaces"."parent_id" IS NULL AND "namespaces"."id" IN (9970);

INSERT INTO "orbit_namespace_enrollments" ("namespace_id","source","created_at","updated_at")
VALUES (9970, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT ("namespace_id") DO NOTHING RETURNING "namespace_id";
record_beta queries Time Plan
Select top-level groups, 1 ID 2.4 ms Plan
Select top-level groups, 1,000 IDs 9.1 ms Plan
Insert 1 row 4.0 ms Plan
Plan details
  • Index scans only: knowledge_graph_enabled_namespaces_pkey for the batch, and index_groups_on_parent_id_id for the join to namespaces.
  • Most of the time is cold reads: 1.0 s of disk I/O on namespaces, and 2.4 s in the foreign key trigger (one check for each row). The production cache for namespaces is warm.
  • The migration runs 4 batches outside a DDL transaction, so each batch commits by itself.
  • Locks: AccessShareLock on the tables, and FOR KEY SHARE from the foreign key check on at most 1,000 namespaces rows for each batch. These row locks block only deletes and key changes of those groups.

MR acceptance checklist

See the MR acceptance checklist.

Edited by Jean-Gabriel Doyon

Merge request reports

Loading
Loading