Add Namespaces::StatePropagationWorker

What does this MR do and why?

Implements Namespaces::StatePropagationWorker, the idempotent Sidekiq worker that performs propagation-based state inheritance through the namespace hierarchy (Organization > Group > Subgroup > Project), as described in ADR 003.

The worker:

  1. Finds the pending outbox record (Namespaces::StatePropagation) by namespace_id + target_state.
  2. Computes overwritable_states from source_state/target_state via Namespaces::Stateful::StatePrecedence. When empty, the outbox record is dropped without a status write.
  3. Marks it processing and sets started_at.
  4. Drives Gitlab::Database::Namespaces::StatePropagationIterator in batches of 500, applying a conditional bulk update SET state = target_state WHERE id IN (ids) AND state IN (overwritable_states).
  5. Deletes the outbox record on completion. On error the record is left in place (processing) for the CRON worker to recover.

Worker attributes

  • idempotent! + deduplicate :until_executed, including_scheduled: true
  • data_consistency :sticky, urgency :low, concurrency_limit -> { 200 }
  • Registered in app/workers/all_queues.yml

Database review

Two new queries are introduced.

1. Outbox lookup (find_pending_propagation)

EXPLAIN
SELECT namespace_state_propagations.*
FROM namespace_state_propagations
WHERE namespace_state_propagations.status = 0
  AND namespace_state_propagations.namespace_id = 1
  AND namespace_state_propagations.target_state = 1
LIMIT 1;

Expected index: idx_namespace_state_propagations_unique_pending_processing (unique partial on (namespace_id, target_state) WHERE status IN (0, 1)).

https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/53857/commands/156182

namespace_state_propagations is empty on production for now, so this plan only confirms index selection. Real-scale cost and timing numbers will be available once the table is populated.

2. Conditional bulk state update (apply_state)

Scoped to a batch of at most 500 descendant IDs produced by the iterator:

EXPLAIN
UPDATE "namespaces"
SET "state" = 1
WHERE "namespaces"."id" IN (9970, 9971, 9972, 9973, 9974)
  AND "namespaces"."state" IN (0);

Expected index: namespaces_pkey for the batched id IN (...).

https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/53857/commands/156183

References

Closes #599298 Parent epic: &21922

How to set up and validate locally

bundle exec rspec spec/workers/namespaces/state_propagation_worker_spec.rb
Edited by Aakriti Gupta

Merge request reports

Loading