Geo: Replicate Appearance uploads (appearance_uploads)

What does this MR do and why?

Adds Geo SSF (self-service framework) replication and verification for the appearance_uploads partition table, so Appearance attachments (instance branding: logo, PWA icon, header logo, favicon) are mirrored from the primary to secondary Geo sites alongside the other upload partitions.

Generated with the geo:blob_replicator Rails generator:

rails generate geo:blob_replicator appearance_upload \
  --table-name=appearance_uploads \
  --upload-partition \
  --no-sharding-key \
  --milestone=19.2

appearance_uploads is the only upload partition with no sharding key: it is classified gitlab_main_cell_setting (instance-wide, not owned by an organization, namespace, or project), and a database CHECK constraint keeps its organization_id, namespace_id, and project_id columns NULL. Appearance is also an instance-wide singleton.

The first commit extends the generator with a --no-sharding-key mode for cell-setting upload partitions. Two adaptations sit on top of the generated output:

  • Always replicated. Because the data has no owner, selective_sync_scope returns all records regardless of the secondary's selective sync configuration, the *_states table is gitlab_main_cell_setting with no sharding column, foreign key, or trigger, and the model spec asserts the always-replicated behavior instead of the shared selective-sync examples.
  • Singleton parent. Appearance enforces a single-row constraint, so the factory reuses the existing Appearance row (Appearance.current_without_cache || create(:appearance)) and multiple uploads attach to that one row.

The change is split into two commits: the generator extension (reviewable on its own) and the generated appearance_upload replicable.

References

Query plans

Geo::AppearanceUpload.selective_sync_scope does not filter by selective sync, because appearance uploads are instance-wide and always replicated. The scope is a plain read of the partition, with only the optional primary-key range used for batching:

-- selective_sync_scope(node) for a node with selective sync enabled
SELECT "appearance_uploads".* FROM "appearance_uploads";

-- selective_sync_scope(node, primary_key_in: 1..1000)
SELECT "appearance_uploads".* FROM "appearance_uploads"
WHERE "appearance_uploads"."id" BETWEEN 1 AND 1000;

There is no organization, namespace, or project join to analyze (unlike the sharded upload partitions). The primary-key range is served by the partition's primary key, so selective sync needs no new index.

How to set up and validate locally

Requires a primary and secondary Geo setup (for example, two GDKs).

  1. On both sites, run migrations:

    bin/rake db:migrate db:migrate:geo
  2. On the primary, enable replication:

    Feature.enable(:geo_appearance_upload_replication)
  3. On the primary, create an appearance upload: go to Admin Area > Settings > Appearance and set a logo, header logo, PWA icon, or favicon (or attach an upload to the Appearance record directly).

  4. On the secondary, confirm the registry row was created and replication plus verification succeeded:

    Geo::AppearanceUploadRegistry.last
    # => state: 'synced', verification_state: 'succeeded'
  5. Confirm the file is present at the secondary's upload path and that verification_checksum matches the primary's.

  6. Optionally re-verify:

    Geo::AppearanceUploadRegistry.last.verification_pending!

MR acceptance checklist

This change is based on the documented Geo "Replicate a new blob type" template and the geo:blob_replicator generator. Please evaluate it against the MR acceptance checklist.

Merge request reports

Loading