Last active
March 11, 2026 20:55
-
-
Save ASRagab/9b0dc9a679a27c5e66091e70e2c6df1f to your computer and use it in GitHub Desktop.
Embedding Feature Store Migration - Visual Explainer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Embedding Feature Store Migration: Optimized to Bigtable</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --font-body: 'DM Sans', system-ui, sans-serif; | |
| --font-mono: 'Fira Code', 'SF Mono', Consolas, monospace; | |
| --bg: #f7f8fa; | |
| --surface: #ffffff; | |
| --surface2: #f0f2f5; | |
| --surface-elevated: #ffffff; | |
| --border: rgba(0, 0, 0, 0.07); | |
| --border-bright: rgba(0, 0, 0, 0.14); | |
| --text: #1a1d23; | |
| --text-dim: #6b7280; | |
| --red: #be123c; | |
| --red-dim: rgba(190, 18, 60, 0.08); | |
| --green: #059669; | |
| --green-dim: rgba(5, 150, 105, 0.08); | |
| --blue: #0369a1; | |
| --blue-dim: rgba(3, 105, 161, 0.08); | |
| --amber: #b45309; | |
| --amber-dim: rgba(180, 83, 9, 0.08); | |
| --slate: #475569; | |
| --slate-dim: rgba(71, 85, 105, 0.08); | |
| --teal: #0f766e; | |
| --teal-dim: rgba(15, 118, 110, 0.08); | |
| --gold: #92400e; | |
| --gold-dim: rgba(146, 64, 14, 0.08); | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| --bg: #0f1117; | |
| --surface: #181b23; | |
| --surface2: #1e2130; | |
| --surface-elevated: #22263a; | |
| --border: rgba(255, 255, 255, 0.06); | |
| --border-bright: rgba(255, 255, 255, 0.12); | |
| --text: #e2e5eb; | |
| --text-dim: #8b919e; | |
| --red: #fb7185; | |
| --red-dim: rgba(251, 113, 133, 0.1); | |
| --green: #34d399; | |
| --green-dim: rgba(52, 211, 153, 0.1); | |
| --blue: #38bdf8; | |
| --blue-dim: rgba(56, 189, 248, 0.1); | |
| --amber: #fbbf24; | |
| --amber-dim: rgba(251, 191, 36, 0.1); | |
| --slate: #94a3b8; | |
| --slate-dim: rgba(148, 163, 184, 0.08); | |
| --teal: #5eead4; | |
| --teal-dim: rgba(94, 234, 212, 0.1); | |
| --gold: #fbbf24; | |
| --gold-dim: rgba(251, 191, 36, 0.1); | |
| } | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| background: var(--bg); | |
| background-image: | |
| radial-gradient(ellipse at 15% 5%, var(--blue-dim) 0%, transparent 45%), | |
| radial-gradient(ellipse at 85% 95%, var(--green-dim) 0%, transparent 35%); | |
| color: var(--text); | |
| font-family: var(--font-body); | |
| padding: 40px; | |
| min-height: 100vh; | |
| line-height: 1.5; | |
| } | |
| @keyframes fadeUp { | |
| from { opacity: 0; transform: translateY(14px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes fadeScale { | |
| from { opacity: 0; transform: scale(0.92); } | |
| to { opacity: 1; transform: scale(1); } | |
| } | |
| .anim { animation: fadeUp 0.45s ease-out both; animation-delay: calc(var(--i, 0) * 0.06s); } | |
| .anim-scale { animation: fadeScale 0.4s ease-out both; animation-delay: calc(var(--i, 0) * 0.06s); } | |
| @media (prefers-reduced-motion: reduce) { | |
| *, *::before, *::after { | |
| animation-duration: 0.01ms !important; | |
| animation-delay: 0ms !important; | |
| transition-duration: 0.01ms !important; | |
| } | |
| } | |
| /* ===== NAV ===== */ | |
| .wrap { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| display: grid; | |
| grid-template-columns: 180px 1fr; | |
| gap: 0 40px; | |
| } | |
| .main { min-width: 0; } | |
| .toc { | |
| position: sticky; | |
| top: 24px; | |
| align-self: start; | |
| padding: 14px 0; | |
| grid-row: 1 / -1; | |
| max-height: calc(100dvh - 48px); | |
| overflow-y: auto; | |
| } | |
| .toc::-webkit-scrollbar { width: 3px; } | |
| .toc::-webkit-scrollbar-thumb { background: var(--surface-elevated); border-radius: 2px; } | |
| .toc-title { | |
| font-family: var(--font-mono); | |
| font-size: 9px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| color: var(--text-dim); | |
| padding: 0 0 10px; | |
| margin-bottom: 8px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .toc a { | |
| display: block; | |
| font-size: 11px; | |
| color: var(--text-dim); | |
| text-decoration: none; | |
| padding: 5px 8px; | |
| border-radius: 5px; | |
| border-left: 2px solid transparent; | |
| transition: all 0.15s; | |
| line-height: 1.4; | |
| margin-bottom: 1px; | |
| } | |
| .toc a:hover { color: var(--text); background: var(--surface2); } | |
| .toc a.active { color: var(--text); border-left-color: var(--blue); background: var(--blue-dim); } | |
| @media (max-width: 1000px) { | |
| .wrap { grid-template-columns: 1fr; padding-top: 0; } | |
| body { padding: 20px; padding-top: 0; } | |
| .toc { | |
| position: sticky; top: 0; z-index: 200; | |
| max-height: none; display: flex; gap: 4px; | |
| align-items: center; overflow-x: auto; | |
| -webkit-overflow-scrolling: touch; | |
| background: var(--bg); border-bottom: 1px solid var(--border); | |
| padding: 10px 0; margin: 0 -20px; padding-left: 20px; padding-right: 20px; | |
| grid-row: auto; | |
| } | |
| .toc::-webkit-scrollbar { display: none; } | |
| .toc-title { display: none; } | |
| .toc a { white-space: nowrap; flex-shrink: 0; border-left: none; border-bottom: 2px solid transparent; border-radius: 4px 4px 0 0; padding: 6px 10px; font-size: 10px; } | |
| .toc a.active { border-left: none; border-bottom-color: var(--blue); background: var(--surface); } | |
| .main { padding-top: 20px; } | |
| .sec-head { scroll-margin-top: 52px; } | |
| } | |
| /* ===== TYPOGRAPHY ===== */ | |
| h1 { | |
| font-size: 36px; | |
| font-weight: 700; | |
| letter-spacing: -0.8px; | |
| margin-bottom: 6px; | |
| text-wrap: balance; | |
| line-height: 1.15; | |
| } | |
| .subtitle { | |
| color: var(--text-dim); | |
| font-size: 13px; | |
| font-family: var(--font-mono); | |
| margin-bottom: 32px; | |
| } | |
| .sec-head { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1.5px; | |
| padding: 20px 0 14px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .sec-head .dot { | |
| width: 8px; height: 8px; | |
| border-radius: 50%; | |
| display: inline-block; | |
| } | |
| h2 { | |
| font-size: 20px; | |
| font-weight: 700; | |
| letter-spacing: -0.3px; | |
| margin-bottom: 16px; | |
| } | |
| h3 { | |
| font-size: 14px; | |
| font-weight: 600; | |
| margin-bottom: 8px; | |
| } | |
| /* ===== CARDS ===== */ | |
| .card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 20px 24px; | |
| margin-bottom: 16px; | |
| } | |
| .card--hero { | |
| background: var(--surface-elevated); | |
| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); | |
| padding: 28px 32px; | |
| } | |
| .card--recessed { | |
| background: var(--surface2); | |
| box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.04); | |
| } | |
| .card--red { border-color: var(--red); border-left: 4px solid var(--red); } | |
| .card--green { border-color: var(--green-dim); border-left: 4px solid var(--green); } | |
| .card--blue { border-color: var(--blue-dim); border-left: 4px solid var(--blue); } | |
| .card--amber { border-color: var(--amber-dim); border-left: 4px solid var(--amber); } | |
| .card--teal { border-color: var(--teal-dim); border-left: 4px solid var(--teal); } | |
| /* ===== HERO DEPRECATION BANNER ===== */ | |
| .deprecation-banner { | |
| display: grid; | |
| grid-template-columns: 1fr auto; | |
| gap: 24px; | |
| align-items: start; | |
| } | |
| .deprecation-banner .timeline-kpis { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| } | |
| .kpi { | |
| text-align: center; | |
| padding: 12px 20px; | |
| border-radius: 10px; | |
| min-width: 140px; | |
| } | |
| .kpi--red { background: var(--red-dim); border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); } | |
| .kpi--amber { background: var(--amber-dim); border: 1px solid color-mix(in srgb, var(--amber) 30%, transparent); } | |
| .kpi--green { background: var(--green-dim); border: 1px solid color-mix(in srgb, var(--green) 30%, transparent); } | |
| .kpi .kpi-val { | |
| font-family: var(--font-mono); | |
| font-size: 22px; | |
| font-weight: 700; | |
| line-height: 1.2; | |
| } | |
| .kpi--red .kpi-val { color: var(--red); } | |
| .kpi--amber .kpi-val { color: var(--amber); } | |
| .kpi--green .kpi-val { color: var(--green); } | |
| .kpi .kpi-label { | |
| font-size: 10px; | |
| font-family: var(--font-mono); | |
| color: var(--text-dim); | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| margin-top: 2px; | |
| } | |
| .deprecation-text { font-size: 14px; line-height: 1.7; } | |
| .deprecation-text strong { color: var(--red); } | |
| .deprecation-text .tag { | |
| display: inline-block; | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| vertical-align: middle; | |
| } | |
| .tag--red { background: var(--red-dim); color: var(--red); } | |
| .tag--amber { background: var(--amber-dim); color: var(--amber); } | |
| .tag--green { background: var(--green-dim); color: var(--green); } | |
| /* ===== THREE STORES GRID ===== */ | |
| .stores-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 14px; | |
| margin-bottom: 16px; | |
| } | |
| .store-card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| padding: 18px 20px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .store-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 0; right: 0; | |
| height: 3px; | |
| } | |
| .store-card--safe::before { background: var(--green); } | |
| .store-card--danger::before { background: var(--red); } | |
| .store-card .store-name { | |
| font-family: var(--font-mono); | |
| font-size: 12px; | |
| font-weight: 600; | |
| margin-bottom: 6px; | |
| } | |
| .store-card--safe .store-name { color: var(--green); } | |
| .store-card--danger .store-name { color: var(--red); } | |
| .store-card .store-type { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| padding: 2px 7px; | |
| border-radius: 4px; | |
| display: inline-block; | |
| margin-bottom: 8px; | |
| } | |
| .store-card--safe .store-type { background: var(--green-dim); color: var(--green); } | |
| .store-card--danger .store-type { background: var(--red-dim); color: var(--red); } | |
| .store-card .store-detail { | |
| font-size: 12px; | |
| color: var(--text-dim); | |
| line-height: 1.6; | |
| } | |
| .store-card .store-detail strong { color: var(--text); font-weight: 500; } | |
| /* ===== INNER GRID ===== */ | |
| .inner-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 12px; | |
| } | |
| .inner-card { | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 14px 16px; | |
| } | |
| .inner-card .title { | |
| font-weight: 600; | |
| font-size: 13px; | |
| margin-bottom: 4px; | |
| } | |
| .inner-card .desc { | |
| color: var(--text-dim); | |
| font-size: 12px; | |
| line-height: 1.6; | |
| } | |
| .inner-card code { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| background: var(--blue-dim); | |
| color: var(--blue); | |
| padding: 1px 5px; | |
| border-radius: 3px; | |
| } | |
| /* ===== FLOW ARROW ===== */ | |
| .flow-arrow { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| gap: 8px; | |
| color: var(--text-dim); | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| padding: 4px 0; | |
| } | |
| .flow-arrow svg { | |
| width: 18px; height: 18px; | |
| fill: none; | |
| stroke: var(--border-bright); | |
| stroke-width: 2; | |
| stroke-linecap: round; | |
| stroke-linejoin: round; | |
| } | |
| /* ===== DATA FLOW PIPELINE ===== */ | |
| .pipeline { | |
| display: flex; | |
| gap: 0; | |
| align-items: stretch; | |
| overflow-x: auto; | |
| padding-bottom: 6px; | |
| } | |
| .pipeline-step { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 12px 14px; | |
| min-width: 110px; | |
| flex-shrink: 0; | |
| text-align: center; | |
| } | |
| .pipeline-step .step-num { | |
| font-family: var(--font-mono); | |
| font-size: 9px; | |
| font-weight: 600; | |
| margin-bottom: 4px; | |
| } | |
| .pipeline-step .step-name { | |
| font-size: 12px; | |
| font-weight: 600; | |
| margin-bottom: 3px; | |
| } | |
| .pipeline-step .step-detail { | |
| font-size: 10px; | |
| color: var(--text-dim); | |
| line-height: 1.4; | |
| } | |
| .pipeline-step--blue { border-color: var(--blue-dim); } | |
| .pipeline-step--blue .step-num { color: var(--blue); } | |
| .pipeline-step--green { border-color: var(--green-dim); } | |
| .pipeline-step--green .step-num { color: var(--green); } | |
| .pipeline-step--amber { border-color: var(--amber-dim); } | |
| .pipeline-step--amber .step-num { color: var(--amber); } | |
| .pipeline-step--teal { border-color: var(--teal-dim); } | |
| .pipeline-step--teal .step-num { color: var(--teal); } | |
| .pipeline-step--red { border-color: var(--red-dim); } | |
| .pipeline-step--red .step-num { color: var(--red); } | |
| .pipeline-arrow { | |
| display: flex; | |
| align-items: center; | |
| padding: 0 3px; | |
| color: var(--border-bright); | |
| font-size: 16px; | |
| flex-shrink: 0; | |
| } | |
| /* ===== PHASE CARDS ===== */ | |
| .phase-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 16px; | |
| margin-bottom: 16px; | |
| } | |
| .phase-card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 22px 24px; | |
| position: relative; | |
| } | |
| .phase-card .phase-num { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| margin-bottom: 6px; | |
| } | |
| .phase-card .phase-title { | |
| font-size: 16px; | |
| font-weight: 700; | |
| margin-bottom: 4px; | |
| letter-spacing: -0.2px; | |
| } | |
| .phase-card .phase-repo { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| display: inline-block; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| margin-bottom: 10px; | |
| } | |
| .phase-card .phase-files { | |
| font-size: 11px; | |
| color: var(--text-dim); | |
| line-height: 1.8; | |
| list-style: none; | |
| margin-bottom: 10px; | |
| } | |
| .phase-card .phase-files li { | |
| padding-left: 14px; | |
| position: relative; | |
| } | |
| .phase-card .phase-files li::before { | |
| content: ''; | |
| width: 5px; height: 5px; | |
| border-radius: 50%; | |
| position: absolute; | |
| left: 0; top: 7px; | |
| } | |
| .phase-card .phase-desc { | |
| font-size: 12px; | |
| color: var(--text-dim); | |
| line-height: 1.6; | |
| } | |
| .phase-card .phase-desc strong { color: var(--text); } | |
| .phase-card--blue .phase-num { color: var(--blue); } | |
| .phase-card--blue { border-top: 3px solid var(--blue); } | |
| .phase-card--blue .phase-repo { background: var(--blue-dim); color: var(--blue); } | |
| .phase-card--blue .phase-files li::before { background: var(--blue); } | |
| .phase-card--teal .phase-num { color: var(--teal); } | |
| .phase-card--teal { border-top: 3px solid var(--teal); } | |
| .phase-card--teal .phase-repo { background: var(--teal-dim); color: var(--teal); } | |
| .phase-card--teal .phase-files li::before { background: var(--teal); } | |
| .phase-card--amber .phase-num { color: var(--amber); } | |
| .phase-card--amber { border-top: 3px solid var(--amber); } | |
| .phase-card--amber .phase-repo { background: var(--amber-dim); color: var(--amber); } | |
| .phase-card--amber .phase-files li::before { background: var(--amber); } | |
| .phase-card--red .phase-num { color: var(--red); } | |
| .phase-card--red { border-top: 3px solid var(--red); } | |
| .phase-card--red .phase-repo { background: var(--red-dim); color: var(--red); } | |
| .phase-card--red .phase-files li::before { background: var(--red); } | |
| /* ===== TIMELINE ===== */ | |
| .timeline { | |
| position: relative; | |
| padding-left: 32px; | |
| margin: 16px 0; | |
| } | |
| .timeline::before { | |
| content: ''; | |
| position: absolute; | |
| left: 10px; top: 0; bottom: 0; | |
| width: 2px; | |
| background: linear-gradient(to bottom, var(--red), var(--amber), var(--green)); | |
| border-radius: 1px; | |
| } | |
| .tl-item { | |
| position: relative; | |
| margin-bottom: 20px; | |
| padding: 14px 18px; | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| } | |
| .tl-item::before { | |
| content: ''; | |
| position: absolute; | |
| left: -26px; top: 18px; | |
| width: 10px; height: 10px; | |
| border-radius: 50%; | |
| border: 2px solid var(--bg); | |
| } | |
| .tl-item--past::before { background: var(--red); } | |
| .tl-item--soon::before { background: var(--amber); } | |
| .tl-item--future::before { background: var(--green); } | |
| .tl-item--past { border-left-color: var(--red); } | |
| .tl-item--soon { border-left-color: var(--amber); } | |
| .tl-item--future { border-left-color: var(--green); } | |
| .tl-date { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-weight: 600; | |
| margin-bottom: 3px; | |
| } | |
| .tl-item--past .tl-date { color: var(--red); } | |
| .tl-item--soon .tl-date { color: var(--amber); } | |
| .tl-item--future .tl-date { color: var(--green); } | |
| .tl-desc { font-size: 13px; color: var(--text-dim); } | |
| .tl-desc strong { color: var(--text); } | |
| /* ===== RISK TABLE ===== */ | |
| .risk-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 13px; | |
| } | |
| .risk-table thead th { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-dim); | |
| text-align: left; | |
| padding: 10px 14px; | |
| border-bottom: 2px solid var(--border-bright); | |
| position: sticky; | |
| top: 0; | |
| background: var(--surface); | |
| } | |
| .risk-table tbody td { | |
| padding: 12px 14px; | |
| border-bottom: 1px solid var(--border); | |
| vertical-align: top; | |
| line-height: 1.5; | |
| } | |
| .risk-table tbody tr:nth-child(even) td { | |
| background: var(--surface2); | |
| } | |
| .risk-table .severity { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| white-space: nowrap; | |
| } | |
| .severity--high { background: var(--red-dim); color: var(--red); } | |
| .severity--med { background: var(--amber-dim); color: var(--amber); } | |
| .severity--low { background: var(--green-dim); color: var(--green); } | |
| /* ===== STAKEHOLDER TABLE ===== */ | |
| .stakeholder-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 13px; | |
| } | |
| .stakeholder-table thead th { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-dim); | |
| text-align: left; | |
| padding: 10px 14px; | |
| border-bottom: 2px solid var(--border-bright); | |
| position: sticky; | |
| top: 0; | |
| background: var(--surface); | |
| } | |
| .stakeholder-table tbody td { | |
| padding: 10px 14px; | |
| border-bottom: 1px solid var(--border); | |
| vertical-align: top; | |
| } | |
| .stakeholder-table tbody tr:nth-child(even) td { | |
| background: var(--surface2); | |
| } | |
| .stakeholder-table .role-tag { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| padding: 2px 7px; | |
| border-radius: 3px; | |
| display: inline-block; | |
| } | |
| .role-tag--owner { background: var(--blue-dim); color: var(--blue); } | |
| .role-tag--impl { background: var(--teal-dim); color: var(--teal); } | |
| .role-tag--consumer { background: var(--amber-dim); color: var(--amber); } | |
| .role-tag--infra { background: var(--slate-dim); color: var(--slate); } | |
| /* ===== FILE REF LIST ===== */ | |
| .file-list { | |
| list-style: none; | |
| font-size: 12px; | |
| line-height: 2; | |
| } | |
| .file-list li { | |
| padding-left: 14px; | |
| position: relative; | |
| overflow-wrap: break-word; | |
| } | |
| .file-list li::before { | |
| content: ''; | |
| width: 5px; height: 5px; | |
| border-radius: 50%; | |
| position: absolute; | |
| left: 0; top: 10px; | |
| } | |
| .file-list code { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| padding: 1px 5px; | |
| border-radius: 3px; | |
| } | |
| .file-list--blue li::before { background: var(--blue); } | |
| .file-list--blue code { background: var(--blue-dim); color: var(--blue); } | |
| .file-list--teal li::before { background: var(--teal); } | |
| .file-list--teal code { background: var(--teal-dim); color: var(--teal); } | |
| .file-list--amber li::before { background: var(--amber); } | |
| .file-list--amber code { background: var(--amber-dim); color: var(--amber); } | |
| /* ===== LEGEND ===== */ | |
| .legend { | |
| display: flex; | |
| gap: 20px; | |
| flex-wrap: wrap; | |
| margin-bottom: 16px; | |
| } | |
| .legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 11px; | |
| color: var(--text-dim); | |
| font-family: var(--font-mono); | |
| } | |
| .legend-swatch { | |
| width: 12px; height: 12px; | |
| border-radius: 3px; | |
| } | |
| /* ===== MERMAID ===== */ | |
| .mermaid-wrap { | |
| position: relative; | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 24px; | |
| overflow: hidden; | |
| margin-bottom: 16px; | |
| } | |
| .mermaid-inner { | |
| overflow: auto; | |
| cursor: grab; | |
| transform-origin: 0 0; | |
| } | |
| .mermaid-inner.dragging { cursor: grabbing; } | |
| .mermaid-zoom { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| display: flex; | |
| gap: 4px; | |
| z-index: 10; | |
| } | |
| .mermaid-zoom button { | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| width: 28px; height: 28px; | |
| font-family: var(--font-mono); | |
| font-size: 14px; | |
| color: var(--text-dim); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.15s; | |
| } | |
| .mermaid-zoom button:hover { color: var(--text); background: var(--surface-elevated); } | |
| .mermaid .nodeLabel { color: var(--text) !important; } | |
| .mermaid .edgeLabel { color: var(--text-dim) !important; background-color: var(--bg) !important; } | |
| .mermaid .edgeLabel rect { fill: var(--bg) !important; } | |
| .mermaid .node rect, .mermaid .node circle, .mermaid .node polygon { stroke-width: 1.5px; } | |
| .mermaid .edge-pattern-solid { stroke-width: 1.5px; } | |
| .mermaid .edgeLabel { font-family: var(--font-mono) !important; font-size: 12px !important; } | |
| .mermaid .nodeLabel { font-family: var(--font-body) !important; font-size: 14px !important; } | |
| /* ===== CALLOUT ===== */ | |
| .callout { | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 0 8px 8px 0; | |
| padding: 14px 18px; | |
| font-size: 13px; | |
| line-height: 1.6; | |
| color: var(--text-dim); | |
| margin-bottom: 16px; | |
| } | |
| .callout strong { color: var(--text); font-weight: 600; } | |
| .callout code { font-family: var(--font-mono); font-size: 10px; background: var(--blue-dim); color: var(--blue); padding: 1px 5px; border-radius: 3px; } | |
| .callout--red { border-left: 3px solid var(--red); } | |
| .callout--blue { border-left: 3px solid var(--blue); } | |
| .callout--amber { border-left: 3px solid var(--amber); } | |
| .callout--green { border-left: 3px solid var(--green); } | |
| /* ===== RESPONSIVE ===== */ | |
| @media (max-width: 768px) { | |
| .stores-grid { grid-template-columns: 1fr; } | |
| .phase-grid { grid-template-columns: 1fr; } | |
| .inner-grid { grid-template-columns: 1fr; } | |
| .deprecation-banner { grid-template-columns: 1fr; } | |
| .deprecation-banner .timeline-kpis { flex-direction: row; flex-wrap: wrap; } | |
| .pipeline { flex-wrap: wrap; gap: 6px; } | |
| .pipeline-arrow { display: none; } | |
| } | |
| /* ===== DETAILS/SUMMARY ===== */ | |
| details { | |
| margin-bottom: 16px; | |
| } | |
| details summary { | |
| cursor: pointer; | |
| font-family: var(--font-mono); | |
| font-size: 12px; | |
| font-weight: 500; | |
| color: var(--text-dim); | |
| padding: 8px 0; | |
| list-style: none; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| details summary::-webkit-details-marker { display: none; } | |
| details summary::before { | |
| content: '+'; | |
| font-size: 14px; | |
| width: 18px; height: 18px; | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 4px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| transition: transform 0.2s; | |
| } | |
| details[open] summary::before { | |
| content: '-'; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <nav class="toc" id="toc"> | |
| <div class="toc-title">Migration Guide</div> | |
| <a href="#s1">1. The Problem</a> | |
| <a href="#s2">2. Current State</a> | |
| <a href="#s3">3. Data Flow</a> | |
| <a href="#s4">4. Migration Phases</a> | |
| <a href="#s5">5. Timeline</a> | |
| <a href="#s6">6. Risks</a> | |
| <a href="#s7">7. Stakeholders</a> | |
| <a href="#s8">8. Key Files</a> | |
| </nav> | |
| <div class="main"> | |
| <h1 class="anim" style="--i:0">Embedding Feature Store Migration</h1> | |
| <p class="subtitle anim" style="--i:1">vertex ai optimized online serving → bigtable — DAMP-242</p> | |
| <!-- ===== SECTION 1: THE PROBLEM ===== --> | |
| <div id="s1" class="sec-head anim" style="--i:2; color: var(--red);"> | |
| <span class="dot" style="background: var(--red);"></span> 1 — The Problem | |
| </div> | |
| <div class="card card--hero card--red anim" style="--i:3"> | |
| <div class="deprecation-banner"> | |
| <div class="deprecation-text"> | |
| <h2 style="margin-bottom: 12px;">Google is Sunsetting Optimized Online Serving</h2> | |
| <p> | |
| Vertex AI Feature Store <strong>Optimized Online Serving</strong> has been | |
| <span class="tag tag--red">deprecated</span> as of February 17, 2026. | |
| No new features will be added after <span class="tag tag--amber">May 17, 2026</span>, | |
| and the API will be <strong>fully removed</strong> on | |
| <span class="tag tag--red">February 17, 2027</span>. | |
| </p> | |
| <p style="margin-top: 10px;"> | |
| Handshake has <strong>one</strong> optimized online store: <code style="font-family: var(--font-mono); font-size: 11px; background: var(--red-dim); color: var(--red); padding: 1px 6px; border-radius: 3px;">online_feature_store_student_embeddings</code>. | |
| It serves all embedding models (GNNz, TwoTower, RGCN, content_mpnet) to Job Search, | |
| Content/Feed, and Promotion Service via Private Service Connect at 2-3ms latency. | |
| </p> | |
| <p style="margin-top: 10px;"> | |
| The other two stores (<code style="font-family: var(--font-mono); font-size: 11px; background: var(--green-dim); color: var(--green); padding: 1px 6px; border-radius: 3px;">tender_ostrich</code> and | |
| <code style="font-family: var(--font-mono); font-size: 11px; background: var(--green-dim); color: var(--green); padding: 1px 6px; border-radius: 3px;">sassy_jay</code>) | |
| are already on Bigtable and are <strong style="color: var(--green);">not affected</strong>. | |
| </p> | |
| </div> | |
| <div class="timeline-kpis"> | |
| <div class="kpi kpi--red anim-scale" style="--i:4"> | |
| <div class="kpi-val">DONE</div> | |
| <div class="kpi-label">Deprecated</div> | |
| </div> | |
| <div class="kpi kpi--amber anim-scale" style="--i:5"> | |
| <div class="kpi-val">~2 mo</div> | |
| <div class="kpi-label">Feature Freeze</div> | |
| </div> | |
| <div class="kpi kpi--green anim-scale" style="--i:6"> | |
| <div class="kpi-val">~11 mo</div> | |
| <div class="kpi-label">Until Sunset</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ===== SECTION 2: CURRENT STATE ===== --> | |
| <div id="s2" class="sec-head anim" style="--i:7; color: var(--blue);"> | |
| <span class="dot" style="background: var(--blue);"></span> 2 — Current Architecture | |
| </div> | |
| <div class="legend anim" style="--i:8"> | |
| <div class="legend-item"><div class="legend-swatch" style="background: var(--green-dim); border: 1px solid var(--green);"></div>Bigtable (safe)</div> | |
| <div class="legend-item"><div class="legend-swatch" style="background: var(--red-dim); border: 1px solid var(--red);"></div>Optimized (deprecated)</div> | |
| </div> | |
| <div class="stores-grid anim" style="--i:9"> | |
| <div class="store-card store-card--safe"> | |
| <div class="store-name">tender_ostrich</div> | |
| <div class="store-type">Bigtable</div> | |
| <div class="store-detail"> | |
| <strong>Autoscale:</strong> 4-6 nodes (prod)<br> | |
| <strong>Serves:</strong> student features, job features, user features<br> | |
| <strong>Consumers:</strong> ranking services | |
| </div> | |
| </div> | |
| <div class="store-card store-card--safe"> | |
| <div class="store-name">sassy_jay</div> | |
| <div class="store-type">Bigtable</div> | |
| <div class="store-detail"> | |
| <strong>Autoscale:</strong> 1-2 nodes<br> | |
| <strong>Serves:</strong> employer activity, search traffic, AIRA<br> | |
| <strong>Consumers:</strong> ranking services | |
| </div> | |
| </div> | |
| <div class="store-card store-card--danger"> | |
| <div class="store-name">student_embeddings</div> | |
| <div class="store-type">Optimized + PSC</div> | |
| <div class="store-detail"> | |
| <strong>Latency:</strong> 2-3ms via gRPC/PSC<br> | |
| <strong>Serves:</strong> ~42 feature views, 6-day retention<br> | |
| <strong>Models:</strong> GNNz, TwoTower, RGCN, content_mpnet<br> | |
| <strong>Consumers:</strong> Job Search, Content, Feed, Promo | |
| </div> | |
| </div> | |
| </div> | |
| <div class="callout callout--blue anim" style="--i:10"> | |
| <strong>Why PSC matters:</strong> The optimized store uses a <code>google_compute_forwarding_rule</code> to a | |
| Private Service Connect endpoint. Corgi connects via hardcoded IPs | |
| (<code>10.126.0.130:10002</code> prod, <code>10.154.0.105:10002</code> staging) | |
| using insecure gRPC. Bigtable stores use the standard Vertex API endpoint | |
| (<code>us-central1-aiplatform.googleapis.com:443</code>) instead — no PSC needed. | |
| </div> | |
| <div class="mermaid-wrap anim" style="--i:11"> | |
| <div class="mermaid-zoom"> | |
| <button onclick="zoomDiagram('arch', 1)">+</button> | |
| <button onclick="zoomDiagram('arch', -1)">-</button> | |
| <button onclick="zoomDiagram('arch', 0)">R</button> | |
| </div> | |
| <div class="mermaid-inner" id="arch"> | |
| <svg id="mermaid-svg-1" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 1076.08px; background-color: transparent;" viewBox="0 0 1076.078125 420" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#mermaid-svg-1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1 .error-icon{fill:#552222;}#mermaid-svg-1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1 .marker.cross{stroke:#333333;}#mermaid-svg-1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1 p{margin:0;}#mermaid-svg-1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1 .cluster-label text{fill:#333;}#mermaid-svg-1 .cluster-label span{color:#333;}#mermaid-svg-1 .cluster-label span p{background-color:transparent;}#mermaid-svg-1 .label text,#mermaid-svg-1 span{fill:#333;color:#333;}#mermaid-svg-1 .node rect,#mermaid-svg-1 .node circle,#mermaid-svg-1 .node ellipse,#mermaid-svg-1 .node polygon,#mermaid-svg-1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1 .rough-node .label text,#mermaid-svg-1 .node .label text,#mermaid-svg-1 .image-shape .label,#mermaid-svg-1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1 .rough-node .label,#mermaid-svg-1 .node .label,#mermaid-svg-1 .image-shape .label,#mermaid-svg-1 .icon-shape .label{text-align:center;}#mermaid-svg-1 .node.clickable{cursor:pointer;}#mermaid-svg-1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1 .arrowheadPath{fill:#333333;}#mermaid-svg-1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1 .cluster text{fill:#333;}#mermaid-svg-1 .cluster span{color:#333;}#mermaid-svg-1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-1 .icon-shape,#mermaid-svg-1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1 .icon-shape p,#mermaid-svg-1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1 .icon-shape .label rect,#mermaid-svg-1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"><g class="cluster" id="Services" data-look="classic"><rect style="" x="848.21875" y="22" width="219.859375" height="388"/><g class="cluster-label" transform="translate(929.0625, 22)"><foreignObject width="58.171875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel"><p>Services</p></span></div></foreignObject></g></g><g class="cluster" id="Corgi" data-look="classic"><rect style="" x="530.890625" y="8" width="267.328125" height="404"/><g class="cluster-label" transform="translate(646.0703125, 8)"><foreignObject width="36.96875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel"><p>Corgi</p></span></div></foreignObject></g></g><g class="cluster" id="Terraform" data-look="classic"><rect style="" x="8" y="8" width="472.890625" height="404"/><g class="cluster-label" transform="translate(209.0078125, 8)"><foreignObject width="70.875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel"><p>Terraform</p></span></div></foreignObject></g></g></g><g class="edgePaths"><path d="M455.891,82L460.057,82C464.224,82,472.557,82,480.891,82C489.224,82,497.557,82,505.891,82C514.224,82,522.557,82,530.227,82.314C537.896,82.629,544.901,83.258,548.404,83.572L551.907,83.887" id="L_TO_V2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_TO_V2_0" data-points="W3sieCI6NDU1Ljg5MDYyNSwieSI6ODJ9LHsieCI6NDgwLjg5MDYyNSwieSI6ODJ9LHsieCI6NTA1Ljg5MDYyNSwieSI6ODJ9LHsieCI6NTMwLjg5MDYyNSwieSI6ODJ9LHsieCI6NTU1Ljg5MDYyNSwieSI6ODQuMjQ0NDMyNzU0NjkwNTF9XQ==" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M436.57,210L443.957,210C451.344,210,466.117,210,477.671,210C489.224,210,497.557,210,505.891,210C514.224,210,522.557,210,541.008,197.604C559.459,185.207,588.027,160.415,602.311,148.018L616.595,135.622" id="L_SJ_V2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_SJ_V2_0" data-points="W3sieCI6NDM2LjU3MDMxMjUsInkiOjIxMH0seyJ4Ijo0ODAuODkwNjI1LCJ5IjoyMTB9LHsieCI6NTA1Ljg5MDYyNSwieSI6MjEwfSx7IngiOjUzMC44OTA2MjUsInkiOjIxMH0seyJ4Ijo2MTkuNjE1OTA3ODY2Mzc5MywieSI6MTMzfV0=" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M240.969,338L245.135,338C249.302,338,257.635,338,266.18,338C274.724,338,283.479,338,287.857,338L292.234,338" id="L_SE_PSC_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_SE_PSC_0" data-points="W3sieCI6MjQwLjk2ODc1LCJ5IjozMzh9LHsieCI6MjY1Ljk2ODc1LCJ5IjozMzh9LHsieCI6Mjk2LjIzNDM3NSwieSI6MzM4fV0=" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M450.625,338L455.669,338C460.714,338,470.802,338,480.013,338C489.224,338,497.557,338,505.891,338C514.224,338,522.557,338,531.844,338C541.13,338,551.37,338,556.49,338L561.609,338" id="L_PSC_EMB_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_PSC_EMB_0" data-points="W3sieCI6NDUwLjYyNSwieSI6MzM4fSx7IngiOjQ4MC44OTA2MjUsInkiOjMzOH0seyJ4Ijo1MDUuODkwNjI1LCJ5IjozMzh9LHsieCI6NTMwLjg5MDYyNSwieSI6MzM4fSx7IngiOjU2NS42MDkzNzUsInkiOjMzOH1d" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M773.219,84.244L777.385,83.87C781.552,83.496,789.885,82.748,798.219,82.374C806.552,82,814.885,82,823.219,82C831.552,82,839.885,82,850.236,82.113C860.586,82.225,872.954,82.45,879.137,82.563L885.321,82.675" id="L_V2_JS_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_V2_JS_0" data-points="W3sieCI6NzczLjIxODc1LCJ5Ijo4NC4yNDQ0MzI3NTQ2OTA1MX0seyJ4Ijo3OTguMjE4NzUsInkiOjgyfSx7IngiOjgyMy4yMTg3NSwieSI6ODJ9LHsieCI6ODQ4LjIxODc1LCJ5Ijo4Mn0seyJ4Ijo4ODkuMzIwMzEyNSwieSI6ODIuNzQ3Nzc5MTIwMTc2MjV9XQ==" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M702.329,133L718.311,149.5C734.292,166,766.256,199,786.404,215.5C806.552,232,814.885,232,823.219,232C831.552,232,839.885,232,847.556,232.382C855.227,232.765,862.234,233.53,865.738,233.912L869.242,234.295" id="L_V2_CT_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_V2_CT_0" data-points="W3sieCI6NzAyLjMyOTMxMzg1ODY5NTYsInkiOjEzM30seyJ4Ijo3OTguMjE4NzUsInkiOjIzMn0seyJ4Ijo4MjMuMjE4NzUsInkiOjIzMn0seyJ4Ijo4NDguMjE4NzUsInkiOjIzMn0seyJ4Ijo4NzMuMjE4NzUsInkiOjIzNC43MjkwMTcxMjc0MjUyfV0=" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M697.971,299L714.679,279.5C731.387,260,764.803,221,785.677,201.5C806.552,182,814.885,182,823.219,182C831.552,182,839.885,182,856.828,170.61C873.771,159.221,899.324,136.441,912.1,125.051L924.876,113.662" id="L_EMB_JS_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EMB_JS_0" data-points="W3sieCI6Njk3Ljk3MDcwMzEyNSwieSI6Mjk5fSx7IngiOjc5OC4yMTg3NSwieSI6MTgyfSx7IngiOjgyMy4yMTg3NSwieSI6MTgyfSx7IngiOjg0OC4yMTg3NSwieSI6MTgyfSx7IngiOjkyNy44NjE2ODY4NjIyNDQ5LCJ5IjoxMTF9XQ==" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M763.5,299.507L769.286,297.256C775.073,295.005,786.646,290.502,796.599,288.251C806.552,286,814.885,286,823.219,286C831.552,286,839.885,286,849.973,283.738C860.06,281.476,871.901,276.952,877.822,274.69L883.743,272.428" id="L_EMB_CT_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EMB_CT_0" data-points="W3sieCI6NzYzLjUsInkiOjI5OS41MDY4MDkyODE2NjQ2M30seyJ4Ijo3OTguMjE4NzUsInkiOjI4Nn0seyJ4Ijo4MjMuMjE4NzUsInkiOjI4Nn0seyJ4Ijo4NDguMjE4NzUsInkiOjI4Nn0seyJ4Ijo4ODcuNDc5MzUyNjc4NTcxNCwieSI6MjcxfV0=" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/><path d="M763.5,345.403L769.286,345.835C775.073,346.268,786.646,347.134,796.599,347.567C806.552,348,814.885,348,823.219,348C831.552,348,839.885,348,848.26,348C856.635,348,865.052,348,869.26,348L873.469,348" id="L_EMB_PS_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EMB_PS_0" data-points="W3sieCI6NzYzLjUsInkiOjM0NS40MDI1MzY2NzY2MDI5NX0seyJ4Ijo3OTguMjE4NzUsInkiOjM0OH0seyJ4Ijo4MjMuMjE4NzUsInkiOjM0OH0seyJ4Ijo4NDguMjE4NzUsInkiOjM0OH0seyJ4Ijo4NzcuNDY4NzUsInkiOjM0OH1d" marker-end="url(#mermaid-svg-1_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_TO_V2_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_SJ_V2_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_SE_PSC_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_PSC_EMB_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_V2_JS_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_V2_CT_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_EMB_JS_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_EMB_CT_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_EMB_PS_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="flowchart-TO-0" transform="translate(373.4296875, 82)"><rect class="basic label-container" style="" x="-82.4609375" y="-39" width="164.921875" height="78"/><g class="label" style="" transform="translate(-52.4609375, -24)"><rect/><foreignObject width="104.921875" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>tender_ostrich<br />Bigtable</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-SJ-1" transform="translate(373.4296875, 210)"><rect class="basic label-container" style="" x="-63.140625" y="-39" width="126.28125" height="78"/><g class="label" style="" transform="translate(-33.140625, -24)"><rect/><foreignObject width="66.28125" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>sassy_jay<br />Bigtable</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-SE-2" transform="translate(136.984375, 338)"><rect class="basic label-container" style="" x="-103.984375" y="-39" width="207.96875" height="78"/><g class="label" style="" transform="translate(-73.984375, -24)"><rect/><foreignObject width="147.96875" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>student_embeddings<br />OPTIMIZED</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-PSC-3" transform="translate(373.4296875, 338)"><rect class="basic label-container" style="" x="-77.1953125" y="-39" width="154.390625" height="78"/><g class="label" style="" transform="translate(-47.1953125, -24)"><rect/><foreignObject width="94.390625" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>PSC Endpoint<br />10.126.0.130</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-V2-4" transform="translate(664.5546875, 94)"><rect class="basic label-container" style="" x="-108.6640625" y="-39" width="217.328125" height="78"/><g class="label" style="" transform="translate(-78.6640625, -24)"><rect/><foreignObject width="157.328125" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>VertexFeatureStoreV2<br />Standard API</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-EMB-5" transform="translate(664.5546875, 338)"><rect class="basic label-container" style="" x="-98.9453125" y="-39" width="197.890625" height="78"/><g class="label" style="" transform="translate(-68.9453125, -24)"><rect/><foreignObject width="137.890625" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>EmbeddingProvider<br />gRPC to PSC</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-JS-6" transform="translate(958.1484375, 84)"><rect class="basic label-container" style="" x="-68.828125" y="-27" width="137.65625" height="54"/><g class="label" style="" transform="translate(-38.828125, -12)"><rect/><foreignObject width="77.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Job Search</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-CT-7" transform="translate(958.1484375, 244)"><rect class="basic label-container" style="" x="-84.9296875" y="-27" width="169.859375" height="54"/><g class="label" style="" transform="translate(-54.9296875, -12)"><rect/><foreignObject width="109.859375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Content / Feed</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-PS-8" transform="translate(958.1484375, 348)"><rect class="basic label-container" style="" x="-80.6796875" y="-27" width="161.359375" height="54"/><g class="label" style="" transform="translate(-50.6796875, -12)"><rect/><foreignObject width="101.359375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Promotion Svc</p></span></div></foreignObject></g></g></g></g></g></svg> | |
| </div> | |
| </div> | |
| <!-- ===== SECTION 3: DATA FLOW ===== --> | |
| <div id="s3" class="sec-head anim" style="--i:12; color: var(--teal);"> | |
| <span class="dot" style="background: var(--teal);"></span> 3 — Embedding Data Flow | |
| </div> | |
| <div class="card card--teal anim" style="--i:13"> | |
| <h3 style="margin-bottom: 14px;">Daily Embedding Publishing Pipeline</h3> | |
| <div class="pipeline"> | |
| <div class="pipeline-step pipeline-step--blue"> | |
| <div class="step-num">STEP 1</div> | |
| <div class="step-name">GCS</div> | |
| <div class="step-detail">Embeddings<br>from Anyscale</div> | |
| </div> | |
| <div class="pipeline-arrow">→</div> | |
| <div class="pipeline-step pipeline-step--blue"> | |
| <div class="step-num">STEP 2</div> | |
| <div class="step-name">Spanner</div> | |
| <div class="step-detail">Query for<br>unpublished</div> | |
| </div> | |
| <div class="pipeline-arrow">→</div> | |
| <div class="pipeline-step pipeline-step--teal"> | |
| <div class="step-num">STEP 3</div> | |
| <div class="step-name">BigQuery</div> | |
| <div class="step-detail">Load & flatten<br>to BQ table</div> | |
| </div> | |
| <div class="pipeline-arrow">→</div> | |
| <div class="pipeline-step pipeline-step--amber"> | |
| <div class="step-num">STEP 4</div> | |
| <div class="step-name">Feature View</div> | |
| <div class="step-detail">Create in<br>online store</div> | |
| </div> | |
| <div class="pipeline-arrow">→</div> | |
| <div class="pipeline-step pipeline-step--green"> | |
| <div class="step-num">STEP 5</div> | |
| <div class="step-name">Sync</div> | |
| <div class="step-detail">BQ → Online<br>Store</div> | |
| </div> | |
| <div class="pipeline-arrow">→</div> | |
| <div class="pipeline-step pipeline-step--blue"> | |
| <div class="step-num">STEP 6</div> | |
| <div class="step-name">Spanner</div> | |
| <div class="step-detail">Update<br>manifest</div> | |
| </div> | |
| <div class="pipeline-arrow">→</div> | |
| <div class="pipeline-step pipeline-step--red"> | |
| <div class="step-num">STEP 7</div> | |
| <div class="step-name">Cleanup</div> | |
| <div class="step-detail">Delete old<br>views > 6d</div> | |
| </div> | |
| </div> | |
| <div style="margin-top: 16px;"> | |
| <h3>DAGs that use this pipeline</h3> | |
| <div class="inner-grid" style="margin-top: 8px;"> | |
| <div class="inner-card"> | |
| <div class="title">twotower_embedding_generation</div> | |
| <div class="desc">TwoTower_v2_7day, TwoTower_v3 — daily 3:00 UTC</div> | |
| </div> | |
| <div class="inner-card"> | |
| <div class="title">gnn_embedding_generation</div> | |
| <div class="desc">RGCN_v2, GNNz_v1, GNNz_v2 — daily 23:00 UTC</div> | |
| </div> | |
| <div class="inner-card"> | |
| <div class="title">content_semantic_embedding_gen</div> | |
| <div class="desc">content_mpnet — daily</div> | |
| </div> | |
| <div class="inner-card"> | |
| <div class="title">dummy_embedding_generation</div> | |
| <div class="desc">All models (staging only) — daily 01:00 UTC</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="callout callout--amber anim" style="--i:14"> | |
| <strong>Spanner is untouched during migration.</strong> The <code>vertex_v2_student_embedding_versions</code> and | |
| <code>gcs_embedding_versions</code> tables in the <code>job_search</code> database track | |
| feature view names, not which store backs them. Clients query Spanner to resolve versions — | |
| the storage backend is opaque. This is why dual-write works without Spanner changes. | |
| </div> | |
| <!-- ===== SECTION 4: MIGRATION PHASES ===== --> | |
| <div id="s4" class="sec-head anim" style="--i:15; color: var(--blue);"> | |
| <span class="dot" style="background: var(--blue);"></span> 4 — Migration Phases | |
| </div> | |
| <div class="phase-grid"> | |
| <div class="phase-card phase-card--blue anim" style="--i:16"> | |
| <div class="phase-num">Phase 1</div> | |
| <div class="phase-title">Provision Bigtable Store</div> | |
| <span class="phase-repo">terraform</span> | |
| <ul class="phase-files"> | |
| <li><code>online_feature_stores.tf</code> — add new <code>google_vertex_ai_feature_online_store</code> with bigtable config</li> | |
| <li>Follow <code>tender_ostrich</code> pattern: autoscaling 4-6 nodes</li> | |
| <li>No PSC endpoint needed</li> | |
| <li>Update locals/outputs for downstream</li> | |
| </ul> | |
| <div class="phase-desc"> | |
| Create a new Bigtable-backed online store alongside the existing optimized one. | |
| Same module, same file, same pattern as the two existing Bigtable stores. | |
| </div> | |
| </div> | |
| <div class="phase-card phase-card--teal anim" style="--i:17"> | |
| <div class="phase-num">Phase 2</div> | |
| <div class="phase-title">Dual Write (6-7 days)</div> | |
| <span class="phase-repo">data-platform</span> | |
| <ul class="phase-files"> | |
| <li><code>embeddings_to_online_feature_store.py</code> — add second store target</li> | |
| <li>All embedding DAGs automatically pick up the change</li> | |
| <li>Run for minimum 6 days (full retention window)</li> | |
| <li>Spanner manifests unchanged</li> | |
| </ul> | |
| <div class="phase-desc"> | |
| Modify the reusable <code>gcs_embeddings_to_online_feature_store</code> utility to create | |
| feature views and sync to <strong>both</strong> the optimized and new Bigtable stores. | |
| Same feature view names in both stores. | |
| </div> | |
| </div> | |
| <div class="phase-card phase-card--amber anim" style="--i:18"> | |
| <div class="phase-num">Phase 3</div> | |
| <div class="phase-title">Switch Corgi Client</div> | |
| <span class="phase-repo">corgi</span> | |
| <ul class="phase-files"> | |
| <li><code>feature_store_v2_embeddings.go</code> — switch from PSC gRPC to standard Vertex API</li> | |
| <li>Point at new Bigtable store name</li> | |
| <li>Keep <code>EmbeddingProvider</code> interface unchanged</li> | |
| <li>Services bump corgi version to adopt</li> | |
| </ul> | |
| <div class="phase-desc"> | |
| Replace the hardcoded PSC endpoint connections with the standard Vertex AI API | |
| (<code>us-central1-aiplatform.googleapis.com:443</code>). The <code>EmbeddingProvider</code> | |
| interface stays the same — consumers just upgrade their corgi dependency. | |
| <strong>Latency goes from 2-3ms to ~30ms</strong> (accepted per team discussion). | |
| </div> | |
| </div> | |
| <div class="phase-card phase-card--red anim" style="--i:19"> | |
| <div class="phase-num">Phase 4</div> | |
| <div class="phase-title">Cleanup</div> | |
| <span class="phase-repo">all repos</span> | |
| <ul class="phase-files"> | |
| <li><code>terraform</code> — delete optimized store, PSC endpoint, forwarding rule, IP</li> | |
| <li><code>data-platform</code> — remove optimized store from dual-write config</li> | |
| <li><code>corgi</code> — remove hardcoded PSC endpoint map</li> | |
| <li>Update Datadog monitors if needed</li> | |
| </ul> | |
| <div class="phase-desc"> | |
| Once all consumers are on the Bigtable store and stable, tear down the deprecated | |
| optimized infrastructure. Delete <code>online_feature_store_endpoints.tf</code> resources | |
| and the optimized store resource from <code>online_feature_stores.tf</code>. | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mermaid-wrap anim" style="--i:20"> | |
| <div class="mermaid-zoom"> | |
| <button onclick="zoomDiagram('future', 1)">+</button> | |
| <button onclick="zoomDiagram('future', -1)">-</button> | |
| <button onclick="zoomDiagram('future', 0)">R</button> | |
| </div> | |
| <div class="mermaid-inner" id="future"> | |
| <svg id="mermaid-svg-2" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 861.156px; background-color: transparent;" viewBox="0 0 861.15625 420" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#mermaid-svg-2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2 .error-icon{fill:#552222;}#mermaid-svg-2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2 .marker.cross{stroke:#333333;}#mermaid-svg-2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2 p{margin:0;}#mermaid-svg-2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2 .cluster-label text{fill:#333;}#mermaid-svg-2 .cluster-label span{color:#333;}#mermaid-svg-2 .cluster-label span p{background-color:transparent;}#mermaid-svg-2 .label text,#mermaid-svg-2 span{fill:#333;color:#333;}#mermaid-svg-2 .node rect,#mermaid-svg-2 .node circle,#mermaid-svg-2 .node ellipse,#mermaid-svg-2 .node polygon,#mermaid-svg-2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2 .rough-node .label text,#mermaid-svg-2 .node .label text,#mermaid-svg-2 .image-shape .label,#mermaid-svg-2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2 .rough-node .label,#mermaid-svg-2 .node .label,#mermaid-svg-2 .image-shape .label,#mermaid-svg-2 .icon-shape .label{text-align:center;}#mermaid-svg-2 .node.clickable{cursor:pointer;}#mermaid-svg-2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2 .arrowheadPath{fill:#333333;}#mermaid-svg-2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2 .cluster text{fill:#333;}#mermaid-svg-2 .cluster span{color:#333;}#mermaid-svg-2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-2 .icon-shape,#mermaid-svg-2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2 .icon-shape p,#mermaid-svg-2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2 .icon-shape .label rect,#mermaid-svg-2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"><g class="cluster" id="subGraph2" data-look="classic"><rect style="" x="633.296875" y="22" width="219.859375" height="388"/><g class="cluster-label" transform="translate(693.8515625, 22)"><foreignObject width="98.75" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel"><p>Services After</p></span></div></foreignObject></g></g><g class="cluster" id="subGraph1" data-look="classic"><rect style="" x="315.96875" y="8" width="267.328125" height="404"/><g class="cluster-label" transform="translate(410.859375, 8)"><foreignObject width="77.546875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel"><p>Corgi After</p></span></div></foreignObject></g></g><g class="cluster" id="subGraph0" data-look="classic"><rect style="" x="8" y="8" width="257.96875" height="404"/><g class="cluster-label" transform="translate(82.8515625, 8)"><foreignObject width="108.265625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel"><p>After Migration</p></span></div></foreignObject></g></g></g><g class="edgePaths"><path d="M219.445,82L227.199,82C234.953,82,250.461,82,262.382,82C274.302,82,282.635,82,290.969,82C299.302,82,307.635,82,315.305,82.314C322.974,82.629,329.979,83.258,333.482,83.572L336.985,83.887" id="L_TO2_V2B_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_TO2_V2B_0" data-points="W3sieCI6MjE5LjQ0NTMxMjUsInkiOjgyfSx7IngiOjI2NS45Njg3NSwieSI6ODJ9LHsieCI6MjkwLjk2ODc1LCJ5Ijo4Mn0seyJ4IjozMTUuOTY4NzUsInkiOjgyfSx7IngiOjM0MC45Njg3NSwieSI6ODQuMjQ0NDMyNzU0NjkwNTF9XQ==" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/><path d="M200.125,210L211.099,210C222.073,210,244.021,210,259.161,210C274.302,210,282.635,210,290.969,210C299.302,210,307.635,210,326.086,197.604C344.537,185.207,373.105,160.415,387.389,148.018L401.673,135.622" id="L_SJ2_V2B_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_SJ2_V2B_0" data-points="W3sieCI6MjAwLjEyNSwieSI6MjEwfSx7IngiOjI2NS45Njg3NSwieSI6MjEwfSx7IngiOjI5MC45Njg3NSwieSI6MjEwfSx7IngiOjMxNS45Njg3NSwieSI6MjEwfSx7IngiOjQwNC42OTQwMzI4NjYzNzkzLCJ5IjoxMzN9XQ==" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/><path d="M240.969,338L245.135,338C249.302,338,257.635,338,265.969,338C274.302,338,282.635,338,290.969,338C299.302,338,307.635,338,316.922,338C326.208,338,336.448,338,341.568,338L346.688,338" id="L_SE2_EMBB_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_SE2_EMBB_0" data-points="W3sieCI6MjQwLjk2ODc1LCJ5IjozMzh9LHsieCI6MjY1Ljk2ODc1LCJ5IjozMzh9LHsieCI6MjkwLjk2ODc1LCJ5IjozMzh9LHsieCI6MzE1Ljk2ODc1LCJ5IjozMzh9LHsieCI6MzUwLjY4NzUsInkiOjMzOH1d" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/><path d="M558.297,84.244L562.464,83.87C566.63,83.496,574.964,82.748,583.297,82.374C591.63,82,599.964,82,608.297,82C616.63,82,624.964,82,635.314,82.113C645.664,82.225,658.032,82.45,664.215,82.563L670.399,82.675" id="L_V2B_JS2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_V2B_JS2_0" data-points="W3sieCI6NTU4LjI5Njg3NSwieSI6ODQuMjQ0NDMyNzU0NjkwNTF9LHsieCI6NTgzLjI5Njg3NSwieSI6ODJ9LHsieCI6NjA4LjI5Njg3NSwieSI6ODJ9LHsieCI6NjMzLjI5Njg3NSwieSI6ODJ9LHsieCI6Njc0LjM5ODQzNzUsInkiOjgyLjc0Nzc3OTEyMDE3NjI1fV0=" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/><path d="M487.407,133L503.389,149.5C519.371,166,551.334,199,571.482,215.5C591.63,232,599.964,232,608.297,232C616.63,232,624.964,232,632.634,232.382C640.305,232.765,647.313,233.53,650.817,233.912L654.32,234.295" id="L_V2B_CT2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_V2B_CT2_0" data-points="W3sieCI6NDg3LjQwNzQzODg1ODY5NTYsInkiOjEzM30seyJ4Ijo1ODMuMjk2ODc1LCJ5IjoyMzJ9LHsieCI6NjA4LjI5Njg3NSwieSI6MjMyfSx7IngiOjYzMy4yOTY4NzUsInkiOjIzMn0seyJ4Ijo2NTguMjk2ODc1LCJ5IjoyMzQuNzI5MDE3MTI3NDI1Mn1d" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/><path d="M483.049,299L499.757,279.5C516.465,260,549.881,221,570.756,201.5C591.63,182,599.964,182,608.297,182C616.63,182,624.964,182,641.906,170.61C658.849,159.221,684.402,136.441,697.178,125.051L709.954,113.662" id="L_EMBB_JS2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EMBB_JS2_0" data-points="W3sieCI6NDgzLjA0ODgyODEyNSwieSI6Mjk5fSx7IngiOjU4My4yOTY4NzUsInkiOjE4Mn0seyJ4Ijo2MDguMjk2ODc1LCJ5IjoxODJ9LHsieCI6NjMzLjI5Njg3NSwieSI6MTgyfSx7IngiOjcxMi45Mzk4MTE4NjIyNDQ5LCJ5IjoxMTF9XQ==" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/><path d="M548.578,299.507L554.365,297.256C560.151,295.005,571.724,290.502,581.677,288.251C591.63,286,599.964,286,608.297,286C616.63,286,624.964,286,635.051,283.738C645.138,281.476,656.98,276.952,662.9,274.69L668.821,272.428" id="L_EMBB_CT2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EMBB_CT2_0" data-points="W3sieCI6NTQ4LjU3ODEyNSwieSI6Mjk5LjUwNjgwOTI4MTY2NDYzfSx7IngiOjU4My4yOTY4NzUsInkiOjI4Nn0seyJ4Ijo2MDguMjk2ODc1LCJ5IjoyODZ9LHsieCI6NjMzLjI5Njg3NSwieSI6Mjg2fSx7IngiOjY3Mi41NTc0Nzc2Nzg1NzE0LCJ5IjoyNzF9XQ==" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/><path d="M548.578,345.403L554.365,345.835C560.151,346.268,571.724,347.134,581.677,347.567C591.63,348,599.964,348,608.297,348C616.63,348,624.964,348,633.339,348C641.714,348,650.13,348,654.339,348L658.547,348" id="L_EMBB_PS2_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EMBB_PS2_0" data-points="W3sieCI6NTQ4LjU3ODEyNSwieSI6MzQ1LjQwMjUzNjY3NjYwMjk1fSx7IngiOjU4My4yOTY4NzUsInkiOjM0OH0seyJ4Ijo2MDguMjk2ODc1LCJ5IjozNDh9LHsieCI6NjMzLjI5Njg3NSwieSI6MzQ4fSx7IngiOjY2Mi41NDY4NzUsInkiOjM0OH1d" marker-end="url(#mermaid-svg-2_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_TO2_V2B_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_SJ2_V2B_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_SE2_EMBB_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_V2B_JS2_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_V2B_CT2_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_EMBB_JS2_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_EMBB_CT2_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_EMBB_PS2_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="flowchart-TO2-0" transform="translate(136.984375, 82)"><rect class="basic label-container" style="" x="-82.4609375" y="-39" width="164.921875" height="78"/><g class="label" style="" transform="translate(-52.4609375, -24)"><rect/><foreignObject width="104.921875" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>tender_ostrich<br />Bigtable</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-SJ2-1" transform="translate(136.984375, 210)"><rect class="basic label-container" style="" x="-63.140625" y="-39" width="126.28125" height="78"/><g class="label" style="" transform="translate(-33.140625, -24)"><rect/><foreignObject width="66.28125" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>sassy_jay<br />Bigtable</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-SE2-2" transform="translate(136.984375, 338)"><rect class="basic label-container" style="" x="-103.984375" y="-39" width="207.96875" height="78"/><g class="label" style="" transform="translate(-73.984375, -24)"><rect/><foreignObject width="147.96875" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>student_embeddings<br />Bigtable NEW</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-V2B-3" transform="translate(449.6328125, 94)"><rect class="basic label-container" style="" x="-108.6640625" y="-39" width="217.328125" height="78"/><g class="label" style="" transform="translate(-78.6640625, -24)"><rect/><foreignObject width="157.328125" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>VertexFeatureStoreV2<br />Standard API</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-EMBB-4" transform="translate(449.6328125, 338)"><rect class="basic label-container" style="" x="-98.9453125" y="-39" width="197.890625" height="78"/><g class="label" style="" transform="translate(-68.9453125, -24)"><rect/><foreignObject width="137.890625" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>EmbeddingProvider<br />Standard API</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-JS2-5" transform="translate(743.2265625, 84)"><rect class="basic label-container" style="" x="-68.828125" y="-27" width="137.65625" height="54"/><g class="label" style="" transform="translate(-38.828125, -12)"><rect/><foreignObject width="77.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Job Search</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-CT2-6" transform="translate(743.2265625, 244)"><rect class="basic label-container" style="" x="-84.9296875" y="-27" width="169.859375" height="54"/><g class="label" style="" transform="translate(-54.9296875, -12)"><rect/><foreignObject width="109.859375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Content / Feed</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-PS2-7" transform="translate(743.2265625, 348)"><rect class="basic label-container" style="" x="-80.6796875" y="-27" width="161.359375" height="54"/><g class="label" style="" transform="translate(-50.6796875, -12)"><rect/><foreignObject width="101.359375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Promotion Svc</p></span></div></foreignObject></g></g></g></g></g></svg> | |
| </div> | |
| </div> | |
| <div class="callout callout--green anim" style="--i:21"> | |
| <strong>End state:</strong> All three online stores are Bigtable-backed. No PSC endpoints. | |
| All clients use the standard Vertex AI API. Uniform infrastructure, lower cost, simpler operations. | |
| </div> | |
| <!-- ===== SECTION 5: TIMELINE ===== --> | |
| <div id="s5" class="sec-head anim" style="--i:22; color: var(--amber);"> | |
| <span class="dot" style="background: var(--amber);"></span> 5 — Timeline | |
| </div> | |
| <div class="timeline anim" style="--i:23"> | |
| <div class="tl-item tl-item--past"> | |
| <div class="tl-date">February 17, 2026</div> | |
| <div class="tl-desc"><strong>Optimized Online Serving deprecated.</strong> Google announced deprecation. DAMP-242 created.</div> | |
| </div> | |
| <div class="tl-item tl-item--past"> | |
| <div class="tl-date">March 9, 2026</div> | |
| <div class="tl-desc"><strong>Migration squad kickoff.</strong> Ahmad, Noah, Jacob aligned on approach: provision Bigtable store, dual-write, switch client.</div> | |
| </div> | |
| <div class="tl-item tl-item--soon"> | |
| <div class="tl-date">March-April 2026</div> | |
| <div class="tl-desc"><strong>Phase 1-2:</strong> Terraform provisioning + DAG dual-write. Noah creates strategy ticket. Jacob starts Corgi client work.</div> | |
| </div> | |
| <div class="tl-item tl-item--soon"> | |
| <div class="tl-date">May 17, 2026</div> | |
| <div class="tl-desc"><strong>Feature freeze.</strong> No new features added to optimized serving. Critical patches only.</div> | |
| </div> | |
| <div class="tl-item tl-item--future"> | |
| <div class="tl-date">Q2-Q3 2026 (target)</div> | |
| <div class="tl-desc"><strong>Phase 3-4:</strong> Switch all consumers to Bigtable store. Cleanup optimized infrastructure. Well before sunset.</div> | |
| </div> | |
| <div class="tl-item tl-item--future"> | |
| <div class="tl-date">February 17, 2027</div> | |
| <div class="tl-desc"><strong>Full sunset.</strong> Optimized Online Serving APIs removed. Must be migrated before this date.</div> | |
| </div> | |
| </div> | |
| <!-- ===== SECTION 6: RISKS ===== --> | |
| <div id="s6" class="sec-head anim" style="--i:24; color: var(--red);"> | |
| <span class="dot" style="background: var(--red);"></span> 6 — Risks | |
| </div> | |
| <div class="card anim" style="--i:25"> | |
| <div style="overflow-x: auto;"> | |
| <table class="risk-table"> | |
| <thead> | |
| <tr> | |
| <th style="width: 80px;">Severity</th> | |
| <th style="width: 160px;">Risk</th> | |
| <th>Detail</th> | |
| <th style="width: 220px;">Mitigation</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><span class="severity severity--med">Medium</span></td> | |
| <td><strong>Latency regression</strong></td> | |
| <td>Optimized = 2-3ms. Bigtable = ~30ms (10x slower). All embedding consumers will see higher p50/p95.</td> | |
| <td>Team accepted this tradeoff. Monitor via Datadog corgi-client P95 monitors. Can tune Bigtable nodes if needed.</td> | |
| </tr> | |
| <tr> | |
| <td><span class="severity severity--high">High</span></td> | |
| <td><strong>Feature view cap (50/store)</strong></td> | |
| <td>Previously hit 50 feature view limit on optimized store. 7 models x 6 days = 42 views. With dual-write, new store will also accumulate.</td> | |
| <td>Cleanup step in pipeline already deletes views older than retention. Verify cleanup runs against new store too.</td> | |
| </tr> | |
| <tr> | |
| <td><span class="severity severity--high">High</span></td> | |
| <td><strong>PSC endpoint atomicity</strong></td> | |
| <td>Corgi has hardcoded IPs (10.126.0.130:10002). Client switch and store switch must be coordinated.</td> | |
| <td>Dual-write period ensures both stores have identical data. Switch Corgi first, then cleanup PSC. Never remove PSC before Corgi is off it.</td> | |
| </tr> | |
| <tr> | |
| <td><span class="severity severity--med">Medium</span></td> | |
| <td><strong>Dual-write window</strong></td> | |
| <td>Must run dual-write for full 6-day retention window before switching consumers. Cutting short = missing embeddings.</td> | |
| <td>Minimum 7 days dual-write. Verify all models have views in new store before switching. Check via Vertex API or Spanner query.</td> | |
| </tr> | |
| <tr> | |
| <td><span class="severity severity--low">Low</span></td> | |
| <td><strong>Bigtable cost</strong></td> | |
| <td>New Bigtable store adds nodes (4-6 in prod). During dual-write, running both stores temporarily.</td> | |
| <td>Bigtable is significantly cheaper than optimized. Net cost savings after cleanup. Dual-write period cost is minimal (1-2 weeks).</td> | |
| </tr> | |
| <tr> | |
| <td><span class="severity severity--low">Low</span></td> | |
| <td><strong>Idempotency issues</strong></td> | |
| <td>Past incidents: 409 ALREADY_EXISTS errors when feature views are created twice (ML-618).</td> | |
| <td>Already fixed — operator now skips to sync if view exists. Verify same fix applies to new store code path.</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- ===== SECTION 7: STAKEHOLDERS ===== --> | |
| <div id="s7" class="sec-head anim" style="--i:26; color: var(--slate);"> | |
| <span class="dot" style="background: var(--slate);"></span> 7 — Who Needs to Know | |
| </div> | |
| <div class="card anim" style="--i:27"> | |
| <div style="overflow-x: auto;"> | |
| <table class="stakeholder-table"> | |
| <thead> | |
| <tr> | |
| <th style="width: 180px;">Person / Team</th> | |
| <th style="width: 80px;">Role</th> | |
| <th>Why They Care</th> | |
| <th style="width: 200px;">Action Needed</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><strong>Noah Carniglia</strong><br><span style="font-size: 11px; color: var(--text-dim);">ML Infra Platform</span></td> | |
| <td><span class="role-tag role-tag--owner">Owner</span></td> | |
| <td>Built the original optimized stores, PSC endpoints, embedding operator, and Corgi client. Primary SME.</td> | |
| <td>Create strategy ticket. Review all PRs. Guide Terraform provisioning.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Jacob Rodriguez</strong><br><span style="font-size: 11px; color: var(--text-dim);">ML Infra Platform</span></td> | |
| <td><span class="role-tag role-tag--impl">Implementer</span></td> | |
| <td>Leading Corgi client update from PSC gRPC to standard Vertex API.</td> | |
| <td>Update <code style="font-family: var(--font-mono); font-size: 10px;">feature_store_v2_embeddings.go</code>. Test locally + staging.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Job Search team</strong><br><span style="font-size: 11px; color: var(--text-dim);">Core Relevance</span></td> | |
| <td><span class="role-tag role-tag--consumer">Consumer</span></td> | |
| <td>Primary consumer of GNNz and TwoTower embeddings. Will see latency change (2-3ms to ~30ms).</td> | |
| <td>Bump corgi dependency. Monitor latency post-switch. Adjust timeouts if needed.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Content / Feed team</strong><br><span style="font-size: 11px; color: var(--text-dim);">Feed & Content AI</span></td> | |
| <td><span class="role-tag role-tag--consumer">Consumer</span></td> | |
| <td>Consumer of content_mpnet embeddings. Also migrated to v2 operator recently.</td> | |
| <td>Bump corgi dependency. Monitor latency post-switch.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Promotion Service</strong><br><span style="font-size: 11px; color: var(--text-dim);">Recruiting ML</span></td> | |
| <td><span class="role-tag role-tag--consumer">Consumer</span></td> | |
| <td>Consumer of embeddings for promotion ranking.</td> | |
| <td>Bump corgi dependency. Monitor latency post-switch.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Data Platform team</strong></td> | |
| <td><span class="role-tag role-tag--impl">Implementer</span></td> | |
| <td>DAG changes for dual-write. Owns the embedding publishing infrastructure.</td> | |
| <td>Review dual-write PR. Validate DAG runs in staging. Monitor cleanup task.</td> | |
| </tr> | |
| <tr> | |
| <td><strong>Cloud Engineering</strong></td> | |
| <td><span class="role-tag role-tag--infra">Infra</span></td> | |
| <td>PSC endpoint and networking cleanup. VPC forwarding rules.</td> | |
| <td>Review Terraform PR for PSC decommission. Confirm no other services use the endpoint.</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- ===== SECTION 8: KEY FILES ===== --> | |
| <div id="s8" class="sec-head anim" style="--i:28; color: var(--teal);"> | |
| <span class="dot" style="background: var(--teal);"></span> 8 — Key Files by Repo | |
| </div> | |
| <div class="card card--blue anim" style="--i:29"> | |
| <h3 style="color: var(--blue); margin-bottom: 10px;">terraform</h3> | |
| <ul class="file-list file-list--blue"> | |
| <li><code>stacks/data/modules/ml-platform/online_feature_stores.tf</code> — All 3 online store definitions + feature groups + feature views</li> | |
| <li><code>stacks/data/modules/ml-platform/online_feature_store_endpoints.tf</code> — PSC endpoint, forwarding rule, internal IP (to be deleted)</li> | |
| <li><code>stacks/iam/vertex_ai_access.tf</code> — IAM bindings for Vertex AI access</li> | |
| <li><code>stacks/data/modules/datadog_monitors/json/vertex/</code> — Corgi + GIL client monitors (fetch errors, P95 latency)</li> | |
| </ul> | |
| </div> | |
| <div class="card card--teal anim" style="--i:30"> | |
| <h3 style="color: var(--teal); margin-bottom: 10px;">corgi</h3> | |
| <ul class="file-list file-list--teal"> | |
| <li><code>feature_store_v2_embeddings.go</code> — EmbeddingProvider using PSC gRPC (hardcoded IPs). THE critical file to change.</li> | |
| <li><code>feature_store_v2.go</code> — Standard V2 client (reference for Bigtable API pattern)</li> | |
| <li><code>feature_store_v2_batch.go</code> — Batch streaming client</li> | |
| <li><code>configuration.go</code> — FeatureStoreConfigV2 struct</li> | |
| <li><code>gax.go</code> — Retry configuration</li> | |
| </ul> | |
| </div> | |
| <div class="card card--amber anim" style="--i:31"> | |
| <h3 style="color: var(--amber); margin-bottom: 10px;">data-platform</h3> | |
| <ul class="file-list file-list--amber"> | |
| <li><code>handshake/airflow/dag_utils/embeddings_to_online_feature_store.py</code> — Core reusable utility. Modify for dual-write.</li> | |
| <li><code>handshake/relevance/new_vertex/feature_view_syncer.py</code> — Feature view creation + sync orchestration</li> | |
| <li><code>handshake/relevance/new_vertex/feature_store_client.py</code> — REST client for Vertex AI Feature Store API</li> | |
| <li><code>handshake/relevance/new_vertex/embeddings_job_utils.py</code> — Spanner queries, job config, cleanup</li> | |
| <li><code>airflow/core_relevance/twotower_embedding_generation.py</code> — TwoTower DAG</li> | |
| <li><code>airflow/jobs_digest/gnn_embedding_generation.py</code> — GNN DAG</li> | |
| <li><code>airflow/core_relevance/content_semantic_embedding_generation.py</code> — Content DAG</li> | |
| </ul> | |
| </div> | |
| <details class="anim" style="--i:32"> | |
| <summary>Spanner Tables (reference)</summary> | |
| <div class="card card--recessed" style="margin-top: 8px;"> | |
| <div class="inner-grid"> | |
| <div class="inner-card"> | |
| <div class="title">gcs_embedding_versions</div> | |
| <div class="desc">Tracks all embedding batches produced to GCS. Columns: model_name, model_id, batch_embedding_ts, model_gcs_location</div> | |
| </div> | |
| <div class="inner-card"> | |
| <div class="title">vertex_v2_student_embedding_versions</div> | |
| <div class="desc">Tracks embeddings published to Vertex (non-legacy). Columns: model_name, batch_embedding_ts, vertex_feature_view, ingestion_ts</div> | |
| </div> | |
| </div> | |
| <p style="font-size: 12px; color: var(--text-dim); margin-top: 12px;"> | |
| Both tables live in the <strong>job_search</strong> Spanner database. Neither table references the online store name — | |
| only feature view names. This is why the migration is transparent to Spanner. | |
| </p> | |
| </div> | |
| </details> | |
| </div><!-- /main --> | |
| </div><!-- /wrap --> | |
| <!-- Mermaid pre-rendered as inline SVG --> | |
| <script> | |
| // Scroll spy | |
| (function() { | |
| const toc = document.getElementById('toc'); | |
| const links = toc.querySelectorAll('a'); | |
| const sections = []; | |
| links.forEach(link => { | |
| const id = link.getAttribute('href').slice(1); | |
| const el = document.getElementById(id); | |
| if (el) sections.push({ id, el, link }); | |
| }); | |
| const observer = new IntersectionObserver(entries => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| links.forEach(l => l.classList.remove('active')); | |
| const match = sections.find(s => s.el === entry.target); | |
| if (match) { | |
| match.link.classList.add('active'); | |
| if (window.innerWidth <= 1000) { | |
| match.link.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); | |
| } | |
| } | |
| } | |
| }); | |
| }, { rootMargin: '-10% 0px -80% 0px' }); | |
| sections.forEach(s => observer.observe(s.el)); | |
| links.forEach(link => { | |
| link.addEventListener('click', e => { | |
| e.preventDefault(); | |
| const id = link.getAttribute('href').slice(1); | |
| const el = document.getElementById(id); | |
| if (el) { | |
| el.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| history.replaceState(null, '', '#' + id); | |
| } | |
| }); | |
| }); | |
| })(); | |
| // Zoom controls for Mermaid diagrams | |
| const zoomStates = {}; | |
| function zoomDiagram(id, dir) { | |
| if (!zoomStates[id]) zoomStates[id] = { scale: 1, x: 0, y: 0 }; | |
| const s = zoomStates[id]; | |
| if (dir === 0) { s.scale = 1; s.x = 0; s.y = 0; } | |
| else { s.scale = Math.max(0.5, Math.min(3, s.scale + dir * 0.25)); } | |
| const el = document.getElementById(id); | |
| const svg = el.querySelector('svg'); | |
| if (svg) { svg.style.transform = `scale(${s.scale})`; svg.style.transformOrigin = '0 0'; } | |
| } | |
| // Drag to pan on Mermaid diagrams | |
| document.querySelectorAll('.mermaid-inner').forEach(wrap => { | |
| let isDragging = false, startX, startY, scrollLeft, scrollTop; | |
| wrap.addEventListener('mousedown', e => { | |
| isDragging = true; wrap.classList.add('dragging'); | |
| startX = e.pageX - wrap.offsetLeft; startY = e.pageY - wrap.offsetTop; | |
| scrollLeft = wrap.scrollLeft; scrollTop = wrap.scrollTop; | |
| }); | |
| wrap.addEventListener('mouseleave', () => { isDragging = false; wrap.classList.remove('dragging'); }); | |
| wrap.addEventListener('mouseup', () => { isDragging = false; wrap.classList.remove('dragging'); }); | |
| wrap.addEventListener('mousemove', e => { | |
| if (!isDragging) return; e.preventDefault(); | |
| wrap.scrollLeft = scrollLeft - (e.pageX - wrap.offsetLeft - startX); | |
| wrap.scrollTop = scrollTop - (e.pageY - wrap.offsetTop - startY); | |
| }); | |
| // Ctrl/Cmd + scroll to zoom | |
| wrap.addEventListener('wheel', e => { | |
| if (e.ctrlKey || e.metaKey) { | |
| e.preventDefault(); | |
| zoomDiagram(wrap.id, e.deltaY < 0 ? 1 : -1); | |
| } | |
| }, { passive: false }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment