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
| In | Out |
|---|---|
| Cutover from prelaunch placeholder web app to first prod release | Migration of authenticated user accounts (none in Phase 1; Phase 2 separate plan) |
| SEO redirects from prior marketing landing pages | Migration of legacy hotel inventory (handled by property-service migration) |
| Cookie state from soft-launch beta to GA | Tenant onboarding (handled by tenant-service migration) |
| Telemetry pipeline from beta event taxonomy to GA taxonomy | Search 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 ────────────────────┘
| Phase | Duration | Exit criteria |
|---|---|---|
| 0 | continuous (until Phase 3) | placeholder served; SEO indexed |
| 1 | 2 weeks | k6 + chaos profiles green; SLOs met for 14 d |
| 2 | 4 weeks | beta DAU ≤ 1k; conversion-funnel parity vs. baseline ≥ 95%; zero P1 incidents |
| 3 | 1 day | DNS flip; 24-hour war-room |
| 4 | 4 weeks | retention 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 source | Prod source |
|---|---|
beta-launch-newsletter | newsletter |
beta-launch-x | social_x |
beta-launch-linkedin | social_linkedin |
beta-launch-google-ads | google_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:
| From | To | Status |
|---|---|---|
/preview/hotels-kabul | /hotels/kabul | 301 |
/preview/hotels-herat | /hotels/herat | 301 |
/preview/hotel/{slug} | /hotel/{slug} | 301 |
/preview/wishlist | /wishlist | 301 |
Redirects implemented at the GCLB URL-map level (zero BFF involvement). Sitemap regenerated; submitted via Google Search Console + Bing Webmaster.
5. Cookie + session migration
The beta web app set a gms_beta_<ulid> cookie. The migration treats gms_beta_* as a read-only alias on first GA visit:
- GA BFF receives request with
gms_beta_*cookie only (nogms_*). - BFF mints a fresh
gms_*ULID, attaches to response. - If a
beta_session_blob:<betaUlid>key exists in Memorystore, copylocalePreference,currencyPreference,recentlyViewed[](capped 50),wishlist[](capped 100) into the new session blob. - Emit
melmastoon.bff.consumer.session.migrated.v1with{ from: betaUlid, to: newGmsId }. - Set both cookies in response:
gms_*(long-lived) andgms_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:
- Continue dual-publish for 30 days (
bff.consumer.*+beta.bff.*from BFF; analytics consumes both). - Analytics joins both feeds in BigQuery for funnel-comparison dashboards.
- After 30 days, beta dual-publish removed; analytics consumes
bff.consumer.*only. - Beta topics set to drain-then-delete after a further 30 days.
7. Schema migrations (this service)
Listed in commit order:
| Migration | Description |
|---|---|
0001_init.sql | Schema bootstrap: outbox, inbox, idempotency, schema_migrations |
0002_wishlist_anonymous.sql | Anonymous wishlist mirror table |
0003_handoff_replay_log.sql | Handoff replay-protection ledger |
0004_bot_score_log.sql | Bot scoring audit log |
0005_meta_page_views_archive.sql | Cold-archive partition for completeness reports |
0006_session_migration_audit.sql | Beta → GA session migration audit |
0007_indexes_pass_2.sql | Index pass after first prod load test |
0008_seed_campaign_map.sql | Campaign 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
openrows 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):
- Promote prod release via canary 5% / 25% / 100% over 30 min, with metric guardrails.
- Flip DNS for
api.melmastoon.ghasi.iofrom prelaunch placeholder to GCLB serving the BFF. - Restore DNS TTL to 1 h after stable for 4 h.
- 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.