Skip to main content

MIGRATION_PLAN — bff-consumer-service

Sibling: SERVICE_OVERVIEW · DATA_MODEL · DEPLOYMENT_TOPOLOGY

This BFF is a Phase-1 greenfield component — there is no legacy bff-consumer-service predecessor in production to migrate from. There is, however, a content-and-traffic ramp that this document treats as a migration: pre-launch traffic patterns, SEO indexes, and prior third-party meta-search referrers must be onboarded without breaking experience or analytics.

1. Scope

InOut
Cutover from prelaunch placeholder web app to first prod releaseMigration of authenticated user accounts (none in Phase 1; Phase 2 separate plan)
SEO redirects from prior marketing landing pagesMigration of legacy hotel inventory (handled by property-service migration)
Cookie state from soft-launch beta to GATenant onboarding (handled by tenant-service migration)
Telemetry pipeline from beta event taxonomy to GA taxonomySearch index population (handled by search-aggregation-service migration)
Wishlist mirror seeding from beta participants who opt-in

2. Phases

Phase 0 ── prelaunch placeholder ──────────────────────────────────────────────┐

Phase 1 ── canary on stage with synthetic traffic ─────────────────────────────┤

Phase 2 ── beta cohort (≤ 1k DAU) with feature flag bff_consumer_v1=on ───────┤

Phase 3 ── prod GA cutover (DNS flip, CDN cutover) ───────────────────────────┤

Phase 4 ── post-cutover hardening + retention enforcement ────────────────────┘
PhaseDurationExit criteria
0continuous (until Phase 3)placeholder served; SEO indexed
12 weeksk6 + chaos profiles green; SLOs met for 14 d
24 weeksbeta DAU ≤ 1k; conversion-funnel parity vs. baseline ≥ 95%; zero P1 incidents
31 dayDNS flip; 24-hour war-room
44 weeksretention sweeps run; cost stable; on-call burn < 1 incident/week

3. Pre-cutover data seeding

3.1 Wishlist (beta opt-in)

Beta participants who explicitly opted in (POST /api/beta/optin) have anonymous wishlists stored in a beta-only Postgres table (beta_wishlist_anonymous). Migration script copies into the prod wishlist_anonymous table:

INSERT INTO bff_consumer.wishlist_anonymous (id, guest_session_id, property_id, added_at, source)
SELECT
'wsh_' || encode(gen_random_bytes(16), 'hex'),
'gms_migrated_' || encode(sha256(b.beta_user_id::bytea), 'hex'),
b.property_id,
b.added_at,
'beta_migration'
FROM beta.beta_wishlist_anonymous b
WHERE b.consent_at IS NOT NULL;

Each migrated session gets a one-time landing-page banner offering to "save this wishlist by signing in" (Phase 2).

3.2 Campaign attribution

Beta UTM parameters are normalized to the prod taxonomy:

Beta sourceProd source
beta-launch-newsletternewsletter
beta-launch-xsocial_x
beta-launch-linkedinsocial_linkedin
beta-launch-google-adsgoogle_ads

Mapping lives in services/bff-consumer-service/migrations/0008_seed_campaign_map.sql.

4. SEO + redirect migration

The prelaunch landing pages live at https://melmastoon.ghasi.io/preview/*. Mapping to GA URLs:

FromToStatus
/preview/hotels-kabul/hotels/kabul301
/preview/hotels-herat/hotels/herat301
/preview/hotel/{slug}/hotel/{slug}301
/preview/wishlist/wishlist301

Redirects implemented at the GCLB URL-map level (zero BFF involvement). Sitemap regenerated; submitted via Google Search Console + Bing Webmaster.

The beta web app set a gms_beta_<ulid> cookie. The migration treats gms_beta_* as a read-only alias on first GA visit:

  1. GA BFF receives request with gms_beta_* cookie only (no gms_*).
  2. BFF mints a fresh gms_* ULID, attaches to response.
  3. If a beta_session_blob:<betaUlid> key exists in Memorystore, copy localePreference, currencyPreference, recentlyViewed[] (capped 50), wishlist[] (capped 100) into the new session blob.
  4. Emit melmastoon.bff.consumer.session.migrated.v1 with { from: betaUlid, to: newGmsId }.
  5. Set both cookies in response: gms_* (long-lived) and gms_beta_* (cleared, Max-Age=0).

Beta-blob retention: 30 days post-cutover; afterward the import script is removed and gms_beta_* becomes a fresh-session marker only.

6. Telemetry pipeline migration

Beta events live in a separate Pub/Sub topic family melmastoon.beta.bff.*. Cutover steps:

  1. Continue dual-publish for 30 days (bff.consumer.* + beta.bff.* from BFF; analytics consumes both).
  2. Analytics joins both feeds in BigQuery for funnel-comparison dashboards.
  3. After 30 days, beta dual-publish removed; analytics consumes bff.consumer.* only.
  4. Beta topics set to drain-then-delete after a further 30 days.

7. Schema migrations (this service)

Listed in commit order:

MigrationDescription
0001_init.sqlSchema bootstrap: outbox, inbox, idempotency, schema_migrations
0002_wishlist_anonymous.sqlAnonymous wishlist mirror table
0003_handoff_replay_log.sqlHandoff replay-protection ledger
0004_bot_score_log.sqlBot scoring audit log
0005_meta_page_views_archive.sqlCold-archive partition for completeness reports
0006_session_migration_audit.sqlBeta → GA session migration audit
0007_indexes_pass_2.sqlIndex pass after first prod load test
0008_seed_campaign_map.sqlCampaign attribution mapping seed

All migrations Drizzle-managed; CI gate verifies idempotency on every PR.

8. Cutover (Phase 3) runbook

T-7 days:

  • War-room channel created.
  • DR drill on stage executed.
  • Cost dashboard confirmed.
  • All risk-register open rows have an owner ack.

T-1 day:

  • Cloud Armor rules promoted from stage to prod with campaignMode=on.
  • Memorystore pre-warmed with brand-peek + facets cache via warmer job.
  • DNS TTLs reduced to 60 s.

T-0 (cutover):

  1. Promote prod release via canary 5% / 25% / 100% over 30 min, with metric guardrails.
  2. Flip DNS for api.melmastoon.ghasi.io from prelaunch placeholder to GCLB serving the BFF.
  3. Restore DNS TTL to 1 h after stable for 4 h.
  4. Sitemap re-submission to search consoles.

T+24 h:

  • War-room closed if SLOs met for 24 h.
  • Cost report sent to FinOps.

9. Rollback

Within 30 minutes of cutover, rollback is DNS revert:

gcloud dns record-sets transaction start --zone melmastoon-ghasi-io
gcloud dns record-sets transaction remove "<ga-ip>" --name api.melmastoon.ghasi.io. --type A --ttl 60 --zone melmastoon-ghasi-io
gcloud dns record-sets transaction add "<placeholder-ip>" --name api.melmastoon.ghasi.io. --type A --ttl 60 --zone melmastoon-ghasi-io
gcloud dns record-sets transaction execute --zone melmastoon-ghasi-io

Beyond 30 minutes: rollback retains all data written; BFF disabled by Cloud Run revision rollback to prior noop placeholder; war-room reconvenes.

10. Post-cutover hardening (Phase 4)

  • Tighten Cloud Armor rules based on first-week traffic shape.
  • Tune cache TTLs based on observed cache-hit ratios.
  • Review bot-detector false-positive rate weekly for first month.
  • Confirm Pub/Sub topic-level cost matches projection.
  • Run a second DR drill at T+30 days.
  • Update this document with any deviations and lessons learned.