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.2appearance_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_scopereturns all records regardless of the secondary's selective sync configuration, the*_statestable isgitlab_main_cell_settingwith 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.
Appearanceenforces a single-row constraint, so the factory reuses the existingAppearancerow (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
- Related to #589905 (closed)
- Parent epic: &20933
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).
-
On both sites, run migrations:
bin/rake db:migrate db:migrate:geo -
On the primary, enable replication:
Feature.enable(:geo_appearance_upload_replication) -
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
Appearancerecord directly). -
On the secondary, confirm the registry row was created and replication plus verification succeeded:
Geo::AppearanceUploadRegistry.last # => state: 'synced', verification_state: 'succeeded' -
Confirm the file is present at the secondary's upload path and that
verification_checksummatches the primary's. -
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.