Understand it step by step (한국어로 이해 → 영어로 말하기)
- 1
요구사항부터 명확히 한다. 기능: (1) 집주인이 주소를 넣으면 추정 가격을 보여준다. (2) 정식 오퍼를 생성하면 확정 가격을 붙인다. (3) 모든 가격은 컴포넌트별 내역(base + renovation + risk + market)으로 설명 가능해야 한다. 비기능: 가격은 돈이라 정확성과 설명가능성이 최우선이다. 추정 가격은 빠르면 좋지만(저지연), 확정 오퍼 가격은 조금 느려도 되니 정확성/일관성을 택한다. 같은 입력 + 같은 모델 버전이면 같은 가격이 나와야 한다(재현성).
⚖️ Trade-off: 즉석 추정은 가용성/저지연 쪽, 확정 오퍼 가격은 강한 일관성/감사 쪽으로 나눈다. 하나의 SLA로 묶으면 둘 다 망친다.
🔧 도구:explainability/auditstrong vs eventual consistency
🗣 영어로 말해“Pricing is money, so I prioritize correctness and explainability over latency for binding offers.”
checking microphone…
- 2
규모를 대충 잡는다. 미국 전체 약 1억 4천만 가구, 하지만 매일 가격을 다시 매기는 건 활성 시장 + 관심 매물 위주다. 가정: 하루 추정 요청 100만 건 → 평균 약 12 QPS, 피크 약 50 QPS (낮 시간 몰림). 정식 오퍼 생성은 하루 5만 건 정도 → 1 QPS 미만. 읽기:쓰기 = 압도적 읽기 (가격은 한 번 계산하고 여러 번 본다, 대략 100:1). 가격 레코드 한 건 약 2KB(내역 + 모델 버전 + 피처), 하루 100만 건이면 약 2GB/일, 1년이면 약 700GB → 버전을 다 보관해도 한 클러스터로 감당.
⚖️ Trade-off: QPS가 낮아서(피크 50) 즉석 ML 추론을 매 요청마다 동기로 돌릴 수도 있지만, 모델 추론은 느리고(수백 ms~초) 비싸니 결과를 캐시/사전계산하는 게 이득이다.
✅ Fix: 추정 요청은 캐시 우선(인기 우편번호는 사전계산), 캐시 미스만 비동기 워커로 모델 추론.
🔧 도구:back-of-enveloperead-heavy 100:1
🗣 영어로 말해“About fifty peak QPS, heavily read-skewed, so I cache priced results instead of recomputing per request.”
checking microphone…
- 3
API와 데이터 모델. 엔드포인트: POST /valuations (주소/집 특성 넣고 추정 요청, 비동기면 valuationId 반환) · GET /valuations/{id} (가격 + 내역 조회) · POST /offers (오퍼 생성, 현재 valuation을 스냅샷으로 붙임). 핵심: 가격 테이블은 불변(immutable)으로 버전마다 새 행을 쌓는다 — 절대 덮어쓰지 않는다. 파티션 키는 home_id, 정렬 키는 created_at(또는 version)으로 두면 '이 집의 가격 히스토리'를 한 번에 읽는다. breakdown은 JSON으로 같이 저장해 설명가능성을 보장.
⚖️ Trade-off: 가격 행을 덮어쓰면 저장은 작아지지만 '예전엔 왜 이 가격이었나'를 못 따진다 — 오퍼 분쟁/감사에서 치명적.
✅ Fix: append-only로 모든 버전 보관 + 오퍼는 valuation_id로 특정 버전을 스냅샷 → 모델이 바뀌어도 오퍼가 본 가격은 고정.
🔧 도구:Postgresprimary key (home_id, created_at)append-only / immutable rowsJSONB breakdown
POST /valuations { homeId, features } -> 202 { valuationId } GET /valuations/{id} -> { price, modelVersion, breakdown:{base,renovation,risk,market} } -- valuations (append-only, Postgres) home_id TEXT -- group key created_at TIMESTAMPTZ -- newest-first read valuation_id UUID price_cents BIGINT model_version TEXT breakdown JSONB PRIMARY KEY (home_id, created_at)🗣 영어로 말해“The valuation table is append-only and an offer snapshots a specific valuation id, so its price never silently changes.”
checking microphone…
- 4
상위 설계, 요청 흐름. 클라이언트 → 로드밸런서 → Valuation API → 먼저 Redis 캐시 확인(home_id+modelVersion 키). 히트면 즉시 반환. 미스면 가격 레코드를 PENDING으로 만들고 Kafka에 가격계산 작업을 넣고 valuationId를 바로 돌려준다(202). Pricing Worker가 큐에서 꺼내 → 외부 데이터(비교 매물, 시세 API) 모으고 → ML 추론 서버 호출 → base+renovation+risk+market 합산 → 결과를 가격 테이블에 READY로 쓰고 캐시 채움. 오퍼 서비스는 이 가격 파이프라인과 완전히 분리 — 오퍼는 사용자에게 보이는 상태(CREATED→SENT→ACCEPTED)만 책임지고, 가격은 valuation_id로 가져온다.
⚖️ Trade-off: 동기로 ML을 부르면 코드가 단순하지만 추론 서버가 느려지거나 죽으면 사용자 요청 전체가 막힌다.
✅ Fix: PENDING 행 + 큐 + 워커로 무거운 계산을 비동기로 빼서, 사용자 레코드는 먼저 안전하게 커밋되고 가격은 뒤따라 채워진다.
🔧 도구:Redis cacheKafkaasync workerML inference serviceoutbox/queue
🗣 영어로 말해“On a cache miss I commit a PENDING record, enqueue the compute, and a worker fills in the price asynchronously.”
checking microphone…
- 5
딥다이브 1 — 핫 키와 캐시 스탬피드. 인기 지역(예: LA 한 우편번호)의 같은 집/같은 비교군에 추정 요청이 동시에 몰리면, 캐시가 비어 있는 순간 수십 요청이 전부 '내가 계산해야지' 하고 같은 무거운 ML 추론을 N번 돌린다 → 추론 서버 폭발. 해결: 캐시 미스 시 home_id+modelVersion에 분산 락(또는 single-flight)을 걸어 첫 요청만 계산하게 하고, 나머지는 그 결과를 기다리거나 PENDING valuationId만 받아간다. 인기 우편번호는 야간 배치로 사전계산해 캐시를 미리 데워 둔다. 모델 버전을 캐시 키에 포함해, 새 모델 배포 시 자동으로 키가 갈려 stale 가격이 안 나간다.
⚖️ Trade-off: 락/single-flight는 코드와 조율 지점이 늘고, 락 주인이 죽으면 대기자가 멈출 수 있다.
✅ Fix: 락에 lease(TTL)를 걸어 주인이 죽어도 자동 해제 + 계산은 멱등이라 재시도 안전. 사전계산으로 핫 키는 애초에 미스가 안 나게.
🔧 도구:distributed lock / single-flightcache stampedelease TTLprecompute/warm cacheversion in cache key
key = `val:${homeId}:${modelVersion}` if (cache.has(key)) return cache.get(key) if (!lock.tryAcquire(key, leaseMs=30000)) return pendingValuationId // others wait compute(); cache.set(key, result); lock.release(key)🗣 영어로 말해“On a hot key I single-flight the compute under a leased lock so ten callers don't trigger ten model runs.”
checking microphone…
- 6
딥다이브 2 — 모델 버전 변경과 가격 일관성. 새 ML 모델을 배포하면 같은 집의 가격이 바뀐다. 이미 SENT된 오퍼가 새 모델로 가격이 출렁이면 사용자 신뢰가 깨지고 법적 문제까지 간다. 해결: 모델 버전을 일급 시민으로 다룬다 — 모든 가격 행에 model_version을 박고, 오퍼는 생성 시점의 valuation_id(=특정 모델 버전)를 스냅샷으로 고정한다. 새 모델은 shadow로 먼저 돌려 옛 모델과 가격을 비교(reconciliation)하고, 큰 차이가 나는 집은 사람 검토 큐로 보낸다. 배포는 카나리로 일부 지역만 새 버전 → 모니터링 후 확대.
⚖️ Trade-off: 버전을 다 보관하고 shadow까지 돌리면 저장/연산 비용이 늘고, 옛 버전 정리 정책이 필요하다.
✅ Fix: 오퍼는 스냅샷이라 모델이 바뀌어도 본 가격 불변 + shadow/카나리로 위험을 점진 노출 + 차이 큰 집만 사람 검토로 비용 한정.
🔧 도구:model versioningshadow deploycanary by regionreconciliationhuman review queue
🗣 영어로 말해“An offer pins the valuation id at creation, so deploying a new model never moves a price someone already saw.”
checking microphone…
- 7
트레이드오프 + 모니터링. 포기한 것: 즉석 강한 일관성 — 추정 가격은 캐시/비동기라 잠깐 stale일 수 있고 PENDING 상태가 사용자에게 보인다(저지연/가용성과 맞바꿈). 단순함도 포기 — 큐, 워커, 버전 테이블, 락이 운영 표면을 늘린다. 볼 지표: 캐시 적중률(낮아지면 추론 서버 과부하 신호), 워커 큐 깊이/지연(가격이 안 채워지는지), ML 추론 p99 지연과 에러율, shadow 모델과 현 모델의 가격 차이 분포(드리프트), PENDING→READY 전환 시간, breakdown 합이 최종가와 항상 일치하는지(설명가능성 불변식). 알림: 큐 깊이 급증, 추론 에러율 상승, 가격 드리프트 임계 초과.
⚖️ Trade-off: 비동기/캐시로 저지연·가용성을 얻는 대신 즉석 일관성과 운영 단순함을 내줬다.
✅ Fix: 지표로 약점을 감시 — 캐시 적중률, 큐 깊이, 추론 p99, 가격 드리프트, breakdown 합 일치 불변식.
🔧 도구:cache hit ratequeue depth/lagp99 latencydrift monitoringalerts
🗣 영어로 말해“I'd watch cache hit rate, queue depth, inference p99, and price drift between the live and shadow models.”
checking microphone…
7단계 영어를 다 말하면 → 이 메커니즘 전체를 영어로 설명할 수 있게 된다.