Scaling up your microservice testing with Apache Spark — Part 2
Author: Stripe Engineering | Source: stripe.dev/blog | Published: (미기재)
한 줄 요약
replay harness의 핵심은 “서비스의 두 번째 구현을 만들지 않는 것” — 의존성 상태를 데이터셋으로 표현하고 Spark 잡은 데이터 로드·조인·객체 구성·출력만 담당하며 결정 로직은 서비스 라이브러리에 그대로 둔다.
핵심 주장/내용
- 주된 구현 난제는 의존성 상태: 프로덕션 서비스는 요청 페이로드만으로 결정하지 않음(DB row, 설정, 피처 플래그, 다른 서비스 호출, 이전 계산 상태). replay에서 이를 데이터셋으로 전환 — 라이브 read는 히스토리 스냅샷 조인으로, 버전 룰은 replay 입력으로, 머천트 설정은 머천트+유효일자 조인으로, 프로그램 자격은 time-aware 상태로, 타 서비스 호출은 로깅된 request/response 쌍으로, 부수효과는 실행 대신 출력 row로 기록
- 필요한 fidelity는 테스트하려는 결정에 달림: rate table 리팩터 검증과 새 인증 관련 비용 룰 모델링은 서로 다른 컨텍스트를 요구. harness는 프로덕션 환경 전체를 시뮬레이션할 필요가 없고 테스트 대상 결정에 영향을 주는 상태만 포착하면 됨
- 코드 구조가 관건: 코어 로직이 명시적 입력·의존성을 받아야 하고, 요청 스코프 프레임워크 객체·전역 상태·라이브 DB·외부 서비스에 직접 손을 뻗으면 안 됨. 부수효과는 인터페이스 뒤로 격리해 replay 환경에서 교체/캡처 가능하게
- 추출 전:
loadContext(request)→evaluateCost(request, context)→storage.write→publisher.publish→metrics.emit이 한 함수에 뒤섞임 - 추출 후:
decisionEngine.evaluate(input)가 완전한 입력을 받아 결과를 반환하는 라이브러리가 되고, 온라인 서비스는 요청 파싱·라이브 의존성 로드·쓰기·발행·메트릭을 계속 소유
- 추출 전:
- Spark wrapper는 얇아야 한다: 히스토리 이벤트/rule snapshot/merchant context 테이블을 읽어
ReplayInput을 만들고, 같은 입력에currentEngine과candidateEngine을 돌려ReplayDiff를 쓰는 형태. Spark 잡이 룰을 재구현하면 replay 시스템이 프로덕션과 drift해 거짓 확신을 생산 - 출력 스키마는 비교가 아니라 디버깅을 위해 설계: 레코드마다 원본 입력을 추적할 식별자, 현재 출력, 후보 출력, 선택된 중간값, 취한 룰 경로의 설명 필드. 집계 분석용 차원도 포함. “비용이 바뀌었다”만 말하는 diff는 부족하고 왜 바뀌었는지 설명하는 diff가 유용
- replay의 실질적 한계: 재구성 가능한 결정 상태만큼만 좋음 — 컨텍스트가 불완전하면 결과가 정밀해 보이면서 틀릴 수 있음. 분산 상태·이벤트 구동 의존성·비동기 워크플로·시간에 따라 변하는 라이브 의존성이 있으면 미래 입력을 바꾸는 효과를 깔끔히 격리할 수 없음. 외부 행위자(발급사 행동, 레이턴시, 승인률, 고객 믹스 변화)에 미래가 달린 경우 replay는 전체 검증 전략이 아니라 증거의 한 레이어
- harness는 테스팅 도구를 넘어 더 나은 서비스 경계를 강제하는 forcing function. 마이그레이션이 최적 시점 — 어차피 새 구현을 만들고 새 경계를 정의하는 중이고, harness는 그 마이그레이션의 리스크를 즉시 줄여줌
- 시작 방법: 회귀 비용이 비싼 결정 경로 하나를 골라 → 결정 로직을 명확한 인터페이스 뒤로 추출 → 작은 프라이버시 안전 golden dataset을 만들어 CI에 넣고 → 유용한 신호가 나오면 의존성 모델을 확장해 대규모 히스토리 replay로
관련 위키
Source: 원문 보기