Re-architecting Affirm’s Upfunnel Platform: How We Cut Experiment Cycle Time from Months to Days
Author: Sidharth Pardeshi (Affirm) | Source: Medium (Affirm Engineering) | Published: 2026-06-18
한 줄 요약
수억 row짜리 정적 설정 테이블을 룰 엔진으로 바꾸고 shadow-validation + CDN 리라이트로 트래픽을 옮겨, 머천트가 아무것도 바꾸지 않은 채 실험 셋업을 2개월에서 4일로 줄인 모놀리스 탈출기.
핵심 주장/내용
- 레거시 병목 3가지: ① 머천트마다 페이지 표면×사용자 상태×로케일 조합을 eager하게 전부 채우는 설정 테이블(80%는 프로덕션에서 읽히지 않음) ② 메시징 변경마다 코드 변경 + 설정 백필 + 실험 계획 + 롤아웃 → 평균 2개월 ③ 모놀리스와 배포 파이프라인·리소스를 공유해 선택하지 않은 blast radius
- 재작성 전에 레거시를 정직하게 만들기: 설계상 DB가 전체 설정을 가져야 했지만 실제로는 수년치 인라인 코드 오버라이드 때문에 렌더 결과가 “DB + 긴 코드 경로”의 함수였음. 오버라이드 한 종류씩 → DB를 의도값으로 벌크 업데이트 → 렌더 결과 불변 확인 → 오버라이드 제거. 무결성 체크(누락/중복/무효 row 없음)를 추가해 재drift 방지. 내부 문서 제목이 “Restore the database as the single source of truth”
- 정적 테이블 → 룰 엔진: 머천트마다 순서 있는 룰 목록, 각 룰은 criteria(페이지 표면, 사용자 상태, 로케일…) + payload(렌더할 메시징). 요청 시 우선순위대로 걸어 첫 매치 반환. O(1)을 O(#rules)로 바꾸지만 ① 룰 수가 작고 ② 인메모리 평가 ③ cache-aside 99%+ 히트로 저렴
- 데이터 백필의 실용주의 — 꼬리를 재현하지 말고 통합하라: 읽히지 않는 80%는 쉬운 부분이었고, 진짜 아픈 건 drift했지만 실제 머천트가 의존하는 롱테일. Track 1은 대다수에게 “best-in-class” 기본 룰 세트 적용, Track 2는 커스터마이즈가 실제로 load-bearing인 머천트만 감사 가능한 bash 스크립트로 포팅. 오래된 설정 테이블의 drift 대부분은 의도치 않은 것이므로 충실히 재현하면 버그까지 복제
- 호출자를 바꾸지 않는 트래픽 이전: 라이브 트래픽을 샘플링해 신규 Kotlin 엔드포인트로 replay하고 응답을 비동기·오프패스로 비교하는 shadow 파이프라인 → 머천트별 parity 신호가 롤아웃 결정을 주도. CDN URL 리라이트(
/api/v1/…→/api/v2/…)로 이전하되, 규칙 하나에 수십만 머천트 ID를 못 넣으므로 Phase A 소규모 allowlist → Phase B 로직 반전(핵심/엔터프라이즈만 blocklist, 나머지는 기본 신규 라우팅 후 blocklist 배수) - Sandbox는 도구상자가 더 작다: 분석 웨어하우스·벌크 오케스트레이션이 없어서 애플리케이션 로그로 최근 해당 엔드포인트를 실제로 사용한 머천트를 식별해 그 작은 집합만 백필. “load-bearing만 포팅, 나머지는 통합” 원칙은 동일
주요 수치 / 사실
- 실험 셋업 사이클 ~2개월 → 4일, P99 482ms → 254ms (47% 감소), 설정 footprint 수억 row → 수백만 row
- 머천트당 룰 수 ~100 → ~5
- 머천트 수 2020년 말 ~6,500 → 500K+ (약 75배). 그때는 합리적이던 비정규화 트레이드오프가 규모에서 유지보수 리스크로
- P99 개선의 대부분은 재작성이 아니라 오케스트레이터 작업에서 나옴. 첫 시도는 개선 0 — 엔드포인트 레이어의 coroutine context가 사실상 단일 스레드라 “병렬” 호출이 같은 스레드를 번갈아 쓰고 있었음. 고치니 스레드풀 사이징 문제(8→24로 2.25s P99 스파이크 제거)가, 그 다음 RPC 클라이언트의 blocking-wait 패턴(blocking 대신 suspend로 I/O 중 디스패처 스레드 반환)이 드러남
- Python 95%+ 회사에서 Kotlin 선택 — 결정 요인은 raw throughput이 아니라 production model(structured concurrency, 타입 RPC 클라이언트, 명시적 스레드풀 제어). 첫 분기엔 업펀넬 비즈니스 로직보다 Gradle 빌드·Dagger 모듈 PR이 더 많았고 내부 Kotlin 클라이언트 라이브러리가 없어 2개 분기 동안 gRPC stub에 얇은 coroutine 래퍼를 직접 씀. 1년 뒤 Kotlin이 신규 서비스 기본값이 됨
- 신규 리전마다 레거시는 최소 5 엔지니어링 주 소요, 신규는 스키마·UI가 로케일 인지
관련 위키
- Large-Scale Data Migration
- Replay Testing
- A-B Testing and Experimentation
- Data Pipeline Testing
- Data Modeling
Source: 원문 보기