Split View: 토스뱅크 ML Backend Engineer 합격을 위한 완벽 가이드: 서버 아키텍처·ML 서빙·공부 로드맵
토스뱅크 ML Backend Engineer 합격을 위한 완벽 가이드: 서버 아키텍처·ML 서빙·공부 로드맵
- 1. 토스뱅크 ML Service Team 분석
- 2. JD 완전 해부: 라인 바이 라인
- 3. 필수 기술스택 딥다이브
- 4. 면접 예상 질문 30선
- 5. 6개월 학습 로드맵
- 6. 이력서 작성 전략
- 7. 포트폴리오 프로젝트 아이디어
- 실전 퀴즈
- 참고 자료
1. 토스뱅크 ML Service Team 분석
1-1. 팀의 미션과 역할
토스뱅크 ML Service Team은 은행 서비스 전반에 걸쳐 AI/ML 기술을 프로덕션 레벨로 서빙하는 핵심 팀입니다. 단순히 모델을 만드는 것이 아니라, 수천만 사용자가 실시간으로 이용하는 금융 서비스에 ML 모델을 안정적으로 배포하고 운영하는 것이 이 팀의 존재 이유입니다.
금융 도메인의 특수성을 고려하면, ML Service Team이 다루는 기술적 난이도는 일반 IT 기업보다 한 단계 높습니다.
- 가용성 요구사항: 은행 서비스는 24/7 무중단 운영이 필수이며, 서비스 중단은 곧 금전적 손실과 규제 이슈로 직결됩니다
- 응답 속도 제약: 신용평가, 이상거래 탐지 등은 밀리초 단위의 응답 속도가 요구됩니다
- 규제 준수: 금융 데이터 처리에 관한 엄격한 규제를 기술적으로 충족해야 합니다
- 대규모 트래픽: 토스 생태계의 수천만 사용자 트래픽을 안정적으로 처리해야 합니다
1-2. Data Scientist + ML Engineer 협업 구조
ML Service Team의 가장 큰 특징은 Data Scientist와 ML Engineer가 명확하게 역할을 분리하면서도 긴밀하게 협업하는 구조라는 점입니다.
Data Scientist의 역할:
- 비즈니스 문제를 ML 문제로 정의
- 모델 실험 및 학습 (실험 환경)
- 모델 성능 지표 정의 및 평가
- A/B 테스트 설계 및 결과 분석
ML Backend Engineer의 역할:
- 학습된 모델을 프로덕션에 서빙하는 인프라 구축
- 모델 서빙 API 설계 및 개발
- 서빙 성능 최적화 (지연시간, 처리량)
- 모델 버전 관리 및 배포 파이프라인 구축
- 모니터링 및 장애 대응
이 구조의 핵심은 **"Data Scientist가 모델에 집중하고, ML Backend Engineer가 서빙에 집중"**하는 분업 체계입니다. 서로의 전문 영역을 존중하면서 협업하기 때문에, ML Backend Engineer는 반드시 ML 전문가가 아니어도 됩니다. 대신 서버 아키텍처와 인프라에 대한 깊은 이해가 필수입니다.
1-3. 은행 업무 AI 제품
토스뱅크에서 ML이 적용되는 대표적인 영역을 살펴보겠습니다.
신용평가 모델 서빙:
- 대출 신청 시 실시간으로 신용점수를 산출해야 합니다
- P99 지연시간 100ms 이하가 일반적인 요구사항입니다
- 모델 업데이트 시 무중단 배포가 필수입니다
이상거래 탐지 (FDS):
- 모든 금융 거래를 실시간으로 분석합니다
- 초당 수만 건의 거래를 처리하는 처리량이 필요합니다
- False Positive를 최소화하면서 실제 사기를 놓치지 않아야 합니다
개인화 추천:
- 금융 상품 추천, 소비 패턴 분석 등
- 사용자별 실시간 피처를 기반으로 추론합니다
- Feature Store를 통한 피처 관리가 핵심입니다
AI 챗봇:
- 고객 문의에 자동으로 응답하는 시스템
- LLM 기반 서빙은 GPU 리소스 관리가 중요합니다
- RAG(Retrieval-Augmented Generation) 아키텍처 적용
1-4. 크로스펑셔널 협업
ML Backend Engineer는 기술만 잘한다고 되는 포지션이 아닙니다. 토스뱅크는 PO(Product Owner), 디자이너, 프론트엔드/백엔드 개발자, Data Scientist가 하나의 스쿼드를 구성하여 제품을 만드는 구조입니다.
- PO와의 소통: 비즈니스 요구사항을 기술적 제약 조건과 함께 논의
- Data Scientist와의 소통: 모델 서빙 요구사항 (지연시간, 배치 크기, 모델 포맷) 조율
- 프론트엔드 개발자와의 소통: API 스펙 정의, 응답 포맷 협의
- DevOps/SRE와의 소통: 인프라 요구사항, 배포 전략, 모니터링 기준 설정
따라서 면접에서도 기술적 깊이뿐만 아니라 커뮤니케이션 역량을 평가할 가능성이 높습니다.
2. JD 완전 해부: 라인 바이 라인
이 섹션에서는 토스뱅크 ML Backend Engineer JD의 핵심 키워드를 하나씩 뜯어보며, 각 항목이 실제로 어떤 기술과 역량을 요구하는지 분석합니다.
2-1. "서버 아키텍처를 설계하고 개발"
이 문구는 단순한 CRUD API 개발이 아닙니다. 시스템 수준의 아키텍처 설계 능력을 요구합니다.
구체적으로 의미하는 것:
- 서비스 간 통신 방식 선택 (동기 vs 비동기, REST vs gRPC)
- 데이터 흐름 설계 (어떤 데이터가 어디서 생성되어 어디로 흐르는가)
- 장애 격리 설계 (하나의 서비스 장애가 전체 시스템에 영향을 주지 않도록)
- 확장성을 고려한 설계 (수평 확장이 가능한 구조)
핵심 패턴:
- CQRS (Command Query Responsibility Segregation): 읽기와 쓰기를 분리하여 각각 최적화
- Event Sourcing: 상태 변경을 이벤트로 기록하여 추적 가능성 확보
- Saga Pattern: 분산 트랜잭션을 관리하는 패턴
- Strangler Fig Pattern: 레거시 시스템을 점진적으로 마이그레이션하는 패턴
2-2. "대규모 트래픽을 안정적으로"
토스 생태계의 MAU(월간 활성 사용자)를 고려하면, "대규모"는 수천만 단위의 사용자 트래픽을 의미합니다.
기술적 요구사항:
- 초당 수만~수십만 건의 요청 처리 능력
- 피크 시간대(급여일, 이벤트 등) 급격한 트래픽 증가 대응
- 99.99% 이상의 서비스 가용성 유지
- 장애 발생 시 빠른 복구 (MTTR 최소화)
필요한 기술:
- 로드 밸런싱 전략 (L4/L7)
- 오토스케일링 (HPA, VPA, KEDA)
- 서킷 브레이커를 통한 장애 전파 방지
- 속도 제한(Rate Limiting)으로 시스템 보호
- 커넥션 풀링으로 리소스 효율화
- 비동기 처리로 처리량 극대화
2-3. "무거운 AI 모델을 안정적인 응답 속도로 서빙"
이 부분이 ML Backend Engineer의 핵심 차별점입니다. 일반 백엔드 엔지니어와 다른 점은 바로 ML 모델 서빙에 대한 전문성입니다.
"무거운 AI 모델"이 의미하는 것:
- 대형 딥러닝 모델 (수백 MB ~ 수 GB 크기)
- GPU 추론이 필요한 모델
- LLM 기반 모델 (수십억 파라미터)
- 앙상블 모델 (여러 모델의 조합)
"안정적인 응답 속도"를 위한 기술:
- 모델 최적화: ONNX 변환, TensorRT 가속, 양자화(Quantization)
- 배치 추론: 여러 요청을 묶어서 한 번에 처리
- 모델 캐싱: 자주 사용되는 추론 결과를 캐싱
- GPU 리소스 관리: GPU 메모리 최적화, Multi-Instance GPU(MIG) 활용
- 모델 서빙 프레임워크: Triton Inference Server, BentoML 등
2-4. "MSA 환경에서 분산 추적"
MSA 환경에서는 하나의 사용자 요청이 10개 이상의 마이크로서비스를 거칠 수 있습니다. 이때 문제가 발생하면 어디서 병목이 생겼는지, 어떤 서비스에서 에러가 발생했는지 추적하는 것이 Observability입니다.
3가지 핵심 요소 (Three Pillars of Observability):
-
Traces (추적): 요청의 전체 경로를 시각화
- OpenTelemetry를 통한 계측(Instrumentation)
- Jaeger, Tempo 등으로 시각화
- Span, Trace ID, Parent-Child 관계 이해
-
Metrics (메트릭): 시스템 상태를 숫자로 표현
- Prometheus를 통한 메트릭 수집
- RED 메트릭: Rate, Errors, Duration
- USE 메트릭: Utilization, Saturation, Errors
- Grafana로 대시보드 구성
-
Logs (로그): 이벤트 기록
- 구조화된 로깅 (JSON 포맷)
- 로그 집계: ELK Stack, Loki
- 로그와 트레이스의 상관관계(Correlation)
2-5. "ML 경험 없어도 OK"
이 문구는 매우 중요한 시그널입니다. 토스뱅크가 이 포지션에서 진정으로 원하는 것은 견고한 서버 개발 능력이라는 뜻입니다.
이것이 의미하는 바:
- ML/DL 이론(CNN, RNN, Transformer 등)을 깊이 알 필요는 없습니다
- 대신 모델을 서빙하는 인프라, 서버, DevOps에 대한 전문성이 핵심입니다
- ML 도메인 지식은 입사 후 팀에서 학습할 수 있습니다
- 하지만 ML 서빙 프레임워크(Triton 등) 사용 경험이 있으면 큰 플러스입니다
성장 기회:
- ML 도메인 지식을 팀에서 자연스럽게 습득
- 최신 ML 인프라 기술을 실무에서 경험
- Data Scientist와 협업하며 ML 파이프라인 전체를 이해
- 금융 도메인 전문성도 함께 성장
3. 필수 기술스택 딥다이브
3-1. 서버 개발 (Python/Kotlin/Go)
토스뱅크 JD에서 언급된 세 가지 언어를 각각 깊이 분석합니다. 모든 언어를 다 알 필요는 없지만, 최소 하나는 깊이 있게, 나머지 하나는 기본적으로 다룰 수 있어야 합니다.
Python: ML 서빙의 핵심 언어
Python은 ML 생태계의 중심 언어이므로, ML Backend Engineer에게 가장 중요한 언어입니다.
FastAPI (비동기 웹 프레임워크):
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import asyncio
app = FastAPI()
class PredictionRequest(BaseModel):
user_id: str
features: list[float]
class PredictionResponse(BaseModel):
score: float
model_version: str
latency_ms: float
@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
start = asyncio.get_event_loop().time()
# 모델 추론 로직
score = await run_inference(request.features)
elapsed = (asyncio.get_event_loop().time() - start) * 1000
return PredictionResponse(
score=score,
model_version="v2.1.0",
latency_ms=round(elapsed, 2)
)
asyncio 기반 비동기 프로그래밍:
import asyncio
import aiohttp
async def fetch_features(user_id: str) -> dict:
"""Feature Store에서 사용자 피처를 비동기로 조회"""
async with aiohttp.ClientSession() as session:
async with session.get(
f"http://feature-store/v1/features/user_id/{user_id}"
) as response:
return await response.json()
async def parallel_feature_fetch(user_ids: list[str]) -> list[dict]:
"""여러 사용자의 피처를 병렬로 조회"""
tasks = [fetch_features(uid) for uid in user_ids]
return await asyncio.gather(*tasks)
Pydantic을 활용한 데이터 검증:
from pydantic import BaseModel, Field, validator
class ModelConfig(BaseModel):
model_name: str = Field(..., description="모델 이름")
model_version: str = Field(..., regex=r"^v\d+\.\d+\.\d+$")
batch_size: int = Field(default=32, ge=1, le=512)
timeout_ms: int = Field(default=100, ge=10, le=5000)
@validator("model_name")
def validate_model_name(cls, v):
allowed = ["credit_score", "fds", "recommender", "chatbot"]
if v not in allowed:
raise ValueError(f"허용되지 않는 모델: {v}")
return v
추천 학습 자료 (Python):
- FastAPI 공식 문서 (fastapi.tiangolo.com)
- "Architecture Patterns with Python" - Harry Percival, Bob Gregory
- Real Python: asyncio 튜토리얼
- uvicorn + gunicorn 배포 가이드
Kotlin: 엔터프라이즈 서버 개발
Kotlin은 토스 생태계에서 핵심 언어 중 하나이며, Spring Boot 기반의 서비스 개발에 사용됩니다.
Spring Boot + Coroutines:
@RestController
@RequestMapping("/api/v1/models")
class ModelServingController(
private val inferenceService: InferenceService,
private val featureStore: FeatureStore
) {
@PostMapping("/predict")
suspend fun predict(
@RequestBody request: PredictionRequest
): PredictionResponse {
val startTime = System.nanoTime()
// 비동기로 피처 조회
val features = featureStore.getFeatures(request.userId)
// 모델 추론
val result = inferenceService.infer(
modelName = request.modelName,
features = features
)
val latencyMs = (System.nanoTime() - startTime) / 1_000_000.0
return PredictionResponse(
score = result.score,
modelVersion = result.version,
latencyMs = latencyMs
)
}
}
Coroutines를 활용한 병렬 처리:
suspend fun fetchMultipleFeatures(
userIds: List<String>
): List<FeatureSet> = coroutineScope {
userIds.map { userId ->
async(Dispatchers.IO) {
featureStore.getFeatures(userId)
}
}.awaitAll()
}
추천 학습 자료 (Kotlin):
- Kotlin 공식 문서 (kotlinlang.org)
- "Kotlin in Action" - Dmitry Jemerov
- Spring Boot + Kotlin 공식 가이드
- Kotlin Coroutines 공식 가이드
Go: 고성능 시스템 프로그래밍
Go는 인프라 도구, API 게이트웨이, 사이드카 프록시 등 성능이 중요한 컴포넌트에 사용됩니다.
Gin 기반 API 서버:
package main
import (
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type PredictionRequest struct {
UserID string `json:"user_id" binding:"required"`
Features []float64 `json:"features" binding:"required"`
}
type PredictionResponse struct {
Score float64 `json:"score"`
ModelVersion string `json:"model_version"`
LatencyMs float64 `json:"latency_ms"`
}
func predictHandler(c *gin.Context) {
var req PredictionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
start := time.Now()
ctx, cancel := context.WithTimeout(
c.Request.Context(),
100*time.Millisecond,
)
defer cancel()
score, err := runInference(ctx, req.Features)
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": "inference failed"})
return
}
latency := float64(time.Since(start).Microseconds()) / 1000.0
c.JSON(http.StatusOK, PredictionResponse{
Score: score,
ModelVersion: "v2.1.0",
LatencyMs: latency,
})
}
goroutine과 channel을 활용한 동시성:
func batchInference(
ctx context.Context,
requests []InferenceRequest,
) ([]InferenceResult, error) {
results := make([]InferenceResult, len(requests))
errChan := make(chan error, len(requests))
for i, req := range requests {
go func(idx int, r InferenceRequest) {
result, err := runSingleInference(ctx, r)
if err != nil {
errChan <- err
return
}
results[idx] = result
errChan <- nil
}(i, req)
}
for range requests {
if err := <-errChan; err != nil {
return nil, err
}
}
return results, nil
}
추천 학습 자료 (Go):
- Go 공식 투어 (tour.golang.org)
- "Go Programming Language" - Donovan, Kernighan
- "Concurrency in Go" - Katherine Cox-Buday
- Go by Example (gobyexample.com)
언어 선택 가이드
| 기준 | Python | Kotlin | Go |
|---|---|---|---|
| ML 생태계 | 최고 | 보통 | 제한적 |
| 서버 성능 | 보통 | 좋음 | 최고 |
| 동시성 모델 | asyncio | Coroutines | goroutine |
| 학습 곡선 | 낮음 | 중간 | 낮음 |
| 토스 생태계 | ML 서빙 | 비즈니스 로직 | 인프라 도구 |
| 추천 대상 | ML 서빙 중심 | 서비스 개발 중심 | 인프라/도구 중심 |
권장 조합:
- 메인 언어 1개 + 서브 언어 1개 전략
- ML 서빙 중심이라면: Python(메인) + Kotlin 또는 Go(서브)
- 서비스 개발 중심이라면: Kotlin(메인) + Python(서브)
3-2. 견고한 서버 아키텍처
Design Patterns
CQRS (Command Query Responsibility Segregation):
읽기와 쓰기를 분리하는 패턴입니다. ML 서빙에서는 모델 업데이트(Command)와 추론 요청(Query)을 분리할 수 있습니다.
[모델 업데이트 요청] --> Command Service --> Model Registry --> Event Bus
[추론 요청] --> Query Service --> Model Cache --> 추론 결과 반환
Event Sourcing:
상태 변경을 이벤트로 기록합니다. 모델 서빙에서는 모델 배포 이력, A/B 테스트 결과 등을 이벤트로 관리할 수 있습니다.
Event 1: ModelDeployed(v1.0, timestamp, config)
Event 2: TrafficSplit(v1.0=90%, v1.1=10%)
Event 3: ModelPromoted(v1.1, reason="A/B test passed")
Event 4: ModelDeprecated(v1.0)
Saga Pattern:
분산 트랜잭션을 관리하는 패턴입니다. 예를 들어 모델 배포 프로세스에서 활용합니다.
Step 1: 모델 파일 업로드 → 성공
Step 2: 설정 파일 업데이트 → 성공
Step 3: 카나리 배포 시작 → 실패!
→ 보상 트랜잭션: Step 2 롤백 → Step 1 롤백
API Design: REST vs gRPC vs GraphQL
REST API:
- 범용적이고 클라이언트(웹/앱)와의 통신에 적합
- OpenAPI(Swagger) 명세를 통한 문서화가 용이
- JSON 기반으로 디버깅이 쉬움
gRPC:
- 서비스 간 내부 통신에 최적
- Protocol Buffers 기반으로 직렬화/역직렬화 성능이 우수
- 양방향 스트리밍 지원
- ML 모델 서빙에서 자주 사용 (Triton의 기본 프로토콜)
syntax = "proto3";
service ModelServing {
rpc Predict(PredictRequest) returns (PredictResponse);
rpc StreamPredict(stream PredictRequest) returns (stream PredictResponse);
rpc GetModelStatus(ModelStatusRequest) returns (ModelStatusResponse);
}
message PredictRequest {
string model_name = 1;
string model_version = 2;
repeated float features = 3;
}
GraphQL:
- 클라이언트가 필요한 데이터만 정확히 요청 가능
- 모델 메타데이터 조회 등에 활용 가능
추천 전략: 외부 API는 REST, 내부 서비스 간 통신은 gRPC, 메타데이터 조회는 GraphQL
인증/인가
- OAuth2: 토큰 기반 인증/인가 프레임워크
- JWT (JSON Web Token): 상태를 서버에 저장하지 않는 토큰 방식
- mTLS (mutual TLS): 서비스 간 상호 인증 (Service Mesh에서 자동 적용)
- API Key: 간단한 서비스 인증에 사용
캐싱 전략
ML 서빙에서 캐싱은 성능 최적화의 핵심입니다.
[요청] → Cache Hit? → YES → 캐시된 결과 반환 (1ms 이하)
→ NO → 모델 추론 수행 → 결과 캐싱 → 반환 (50~100ms)
캐싱 레이어:
- L1 Cache (Application): 인메모리 캐시 (예: Python의 lru_cache)
- L2 Cache (Redis/Memcached): 분산 캐시, 서비스 인스턴스 간 공유
- L3 Cache (CDN): 정적 콘텐츠나 변하지 않는 추론 결과
Redis 활용 패턴:
import redis.asyncio as redis
class InferenceCache:
def __init__(self, redis_url: str, ttl: int = 300):
self.redis = redis.from_url(redis_url)
self.ttl = ttl
async def get_or_compute(
self, cache_key: str, compute_fn
):
# 캐시 조회
cached = await self.redis.get(cache_key)
if cached:
return json.loads(cached)
# 추론 수행
result = await compute_fn()
# 결과 캐싱
await self.redis.setex(
cache_key, self.ttl, json.dumps(result)
)
return result
메시지 큐
비동기 통신과 이벤트 기반 아키텍처의 핵심 인프라입니다.
Kafka:
- 대용량 이벤트 스트리밍에 최적
- 모델 추론 결과 로깅, 피처 파이프라인에 활용
- 높은 처리량과 내구성
- 파티셔닝을 통한 병렬 처리
사용 시나리오:
- 비동기 배치 추론 요청 처리
- 모델 학습 데이터 파이프라인
- 실시간 피처 업데이트
- 이벤트 기반 모델 재학습 트리거
Database
PostgreSQL:
- 모델 메타데이터, 실험 결과, 사용자 데이터 저장
- JSON 컬럼으로 유연한 스키마 지원
- 성능 최적화: 인덱싱, 파티셔닝, 커넥션 풀링(PgBouncer)
MongoDB:
- 모델 설정, 피처 정의 등 비정형 데이터에 적합
- 문서 기반이라 스키마 변경이 자유로움
Redis:
- 실시간 피처 서빙
- 세션 관리, 속도 제한 카운터
- 모델 추론 결과 캐싱
3-3. ML 모델 서빙
ML Backend Engineer의 핵심 전문 영역입니다. 이 섹션은 특히 깊이 있게 학습해야 합니다.
Triton Inference Server (NVIDIA)
가장 널리 사용되는 프로덕션 레벨 모델 서빙 프레임워크입니다.
핵심 기능:
- 다중 프레임워크 지원 (TensorFlow, PyTorch, ONNX, TensorRT)
- Dynamic Batching: 여러 요청을 자동으로 배치로 묶어 처리
- Model Pipeline: 여러 모델을 순차적으로 실행
- 모델 앙상블: 여러 모델의 결과를 조합
- GPU/CPU 동시 서빙
- 모델 핫 리로드 (무중단 모델 업데이트)
모델 저장소 구조:
model_repository/
credit_score/
config.pbtxt
1/
model.onnx
2/
model.onnx
fds_detector/
config.pbtxt
1/
model.plan
config.pbtxt 예시:
name: "credit_score"
platform: "onnxruntime_onnx"
max_batch_size: 64
input [
{
name: "features"
data_type: TYPE_FP32
dims: [ 128 ]
}
]
output [
{
name: "score"
data_type: TYPE_FP32
dims: [ 1 ]
}
]
dynamic_batching {
preferred_batch_size: [ 16, 32 ]
max_queue_delay_microseconds: 5000
}
instance_group [
{
count: 2
kind: KIND_GPU
gpus: [ 0 ]
}
]
TorchServe / TensorFlow Serving
TorchServe:
- PyTorch 모델 전용 서빙 프레임워크
- 커스텀 핸들러를 통한 전/후처리 로직 구현
- 모델 버전 관리 기본 지원
TensorFlow Serving:
- TensorFlow/Keras 모델 전용
- SavedModel 포맷 지원
- gRPC, REST 인터페이스 제공
BentoML, Ray Serve, Seldon Core
BentoML:
- Python 네이티브 모델 패키징 프레임워크
- 간단한 데코레이터로 모델 서빙 API 생성
- 컨테이너화 자동화
import bentoml
from bentoml.io import JSON
runner = bentoml.onnx.get("credit_score:latest").to_runner()
svc = bentoml.Service("credit-scoring", runners=[runner])
@svc.api(input=JSON(), output=JSON())
async def predict(input_data: dict) -> dict:
features = input_data["features"]
result = await runner.predict.async_run(features)
return {"score": float(result[0])}
Ray Serve:
- Ray 기반 분산 모델 서빙
- 파이프라인 구성이 유연
- 오토스케일링 기본 지원
- 여러 모델을 하나의 서빙 그래프로 구성 가능
Seldon Core:
- Kubernetes 네이티브 ML 서빙 플랫폼
- A/B 테스트, 카나리 배포 기본 지원
- 모델 설명(Explainability) 기능 내장
- 다양한 추론 그래프 패턴 지원
모델 최적화
ONNX (Open Neural Network Exchange):
- 프레임워크 간 모델 호환성을 위한 표준 포맷
- PyTorch, TensorFlow 등에서 변환 가능
- ONNX Runtime으로 최적화된 추론
import torch
import onnx
# PyTorch 모델을 ONNX로 변환
dummy_input = torch.randn(1, 128)
torch.onnx.export(
model, dummy_input, "credit_score.onnx",
input_names=["features"],
output_names=["score"],
dynamic_axes={"features": {0: "batch_size"}},
)
TensorRT:
- NVIDIA GPU 전용 최적화 엔진
- 레이어 융합, 커널 자동 튜닝 등으로 추론 속도 2~5배 향상
- INT8/FP16 양자화 지원
양자화 (Quantization):
- FP32 → FP16: 메모리 절반, 속도 약 2배 향상
- FP32 → INT8: 메모리 1/4, 속도 약 3~4배 향상
- 정확도 손실을 최소화하면서 성능을 극대화
Feature Store
ML 모델이 추론을 수행하려면 입력 피처가 필요합니다. Feature Store는 이 피처를 중앙에서 관리합니다.
Feast (Feature Store):
- 오프라인 스토어: 학습용 피처 (BigQuery, S3 등)
- 온라인 스토어: 실시간 서빙용 피처 (Redis, DynamoDB)
- 피처 정의를 코드로 관리 (Feature Definition as Code)
from feast import Entity, FeatureView, Field
from feast.types import Float32, String
user = Entity(
name="user_id",
description="은행 고객 ID",
)
user_features = FeatureView(
name="user_credit_features",
entities=[user],
schema=[
Field(name="avg_balance_30d", dtype=Float32),
Field(name="transaction_count_7d", dtype=Float32),
Field(name="credit_utilization", dtype=Float32),
Field(name="payment_history_score", dtype=Float32),
],
online=True,
source=user_credit_source,
)
A/B Testing 프레임워크
새로운 모델을 배포할 때 기존 모델과 비교 실험을 수행합니다.
사용자 요청 → 트래픽 분배기 → 80% → 모델 v1 (기존)
→ 20% → 모델 v2 (신규)
→ 결과 비교 분석 → 승자 결정
핵심 고려사항:
- 통계적 유의성 확보 (p-value, 신뢰 구간)
- 사용자 경험 일관성 (같은 사용자는 같은 모델로)
- 실시간 모니터링 (조기 종료 기준 설정)
- 비즈니스 메트릭과 모델 메트릭 동시 추적
3-4. Kubernetes 운영
K8s 아키텍처 기초
핵심 리소스:
# Deployment: ML 서빙 서버 배포
apiVersion: apps/v1
kind: Deployment
metadata:
name: credit-score-serving
namespace: ml-serving
spec:
replicas: 3
selector:
matchLabels:
app: credit-score
template:
metadata:
labels:
app: credit-score
spec:
containers:
- name: triton
image: nvcr.io/nvidia/tritonserver:23.10-py3
ports:
- containerPort: 8000
name: http
- containerPort: 8001
name: grpc
resources:
requests:
cpu: '2'
memory: '4Gi'
nvidia.com/gpu: '1'
limits:
cpu: '4'
memory: '8Gi'
nvidia.com/gpu: '1'
readinessProbe:
httpGet:
path: /v2/health/ready
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /v2/health/live
port: 8000
initialDelaySeconds: 30
periodSeconds: 15
# Service: 내부 통신용 ClusterIP
apiVersion: v1
kind: Service
metadata:
name: credit-score-service
spec:
selector:
app: credit-score
ports:
- name: http
port: 8000
targetPort: 8000
- name: grpc
port: 8001
targetPort: 8001
type: ClusterIP
# Ingress: 외부 트래픽 라우팅
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ml-serving-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: ml-serving.tossbank.internal
http:
paths:
- path: /credit-score
pathType: Prefix
backend:
service:
name: credit-score-service
port:
number: 8000
오토스케일링
HPA (Horizontal Pod Autoscaler):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: credit-score-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: credit-score-serving
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: inference_requests_per_second
target:
type: AverageValue
averageValue: '100'
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
KEDA (Kubernetes Event-Driven Autoscaling):
- Kafka 큐 길이, Prometheus 메트릭 등을 기반으로 스케일링
- 0으로 스케일 다운 가능 (비용 절약)
- 이벤트 기반 배치 추론에 적합
Helm Charts & Kustomize
Helm:
- Kubernetes 리소스를 패키지로 관리
- 환경별(dev/staging/prod) 값 분리
- 차트 저장소를 통한 버전 관리
Kustomize:
- 베이스 설정 + 오버레이로 환경별 관리
- YAML 패치 방식으로 직관적
- kubectl에 기본 내장
Service Mesh
Istio:
- 서비스 간 mTLS 자동 적용
- 트래픽 라우팅 (카나리 배포, A/B 테스트)
- 서킷 브레이커, 재시도, 타임아웃 정책
- 분산 추적 자동 주입
# VirtualService: 카나리 배포 트래픽 분배
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: credit-score-routing
spec:
hosts:
- credit-score-service
http:
- route:
- destination:
host: credit-score-service
subset: stable
weight: 90
- destination:
host: credit-score-service
subset: canary
weight: 10
GitOps: ArgoCD
# ArgoCD Application 정의
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: credit-score-serving
namespace: argocd
spec:
project: ml-serving
source:
repoURL: https://github.com/tossbank/ml-serving-manifests
targetRevision: main
path: credit-score/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: ml-serving
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
GitOps 워크플로:
코드 변경 → Git Push → ArgoCD 감지 → 자동 배포 → 상태 검증
→ 실패 시 자동 롤백
GPU Scheduling on K8s
ML 모델 서빙에서 GPU 관리는 핵심 과제입니다.
GPU 리소스 요청:
resources:
limits:
nvidia.com/gpu: 1 # GPU 1개 할당
Multi-Instance GPU (MIG):
- 하나의 물리 GPU를 여러 인스턴스로 분할
- 작은 모델 여러 개를 하나의 GPU에서 동시 서빙
- 비용 효율성 극대화
GPU Node Pool:
- GPU 노드와 CPU 노드를 분리하여 관리
- 모델 추론은 GPU 노드, 전/후처리는 CPU 노드
- 노드 어피니티(Affinity)를 통한 스케줄링 제어
3-5. MSA & 분산 추적
OpenTelemetry
분산 추적, 메트릭, 로그를 통합하는 표준 프레임워크입니다.
Python에서의 계측:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
# 트레이서 설정
provider = TracerProvider()
processor = BatchSpanProcessor(
OTLPSpanExporter(endpoint="http://otel-collector:4317")
)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("ml-serving")
async def predict(request):
with tracer.start_as_current_span("predict") as span:
span.set_attribute("model.name", request.model_name)
span.set_attribute("model.version", request.model_version)
with tracer.start_as_current_span("feature_fetch"):
features = await fetch_features(request.user_id)
with tracer.start_as_current_span("model_inference"):
result = await run_inference(features)
span.set_attribute("inference.latency_ms", result.latency)
return result
Prometheus + Grafana
주요 메트릭:
from prometheus_client import (
Counter, Histogram, Gauge, start_http_server
)
# 추론 요청 수
INFERENCE_REQUESTS = Counter(
"inference_requests_total",
"Total inference requests",
["model_name", "model_version", "status"]
)
# 추론 지연시간
INFERENCE_LATENCY = Histogram(
"inference_latency_seconds",
"Inference latency in seconds",
["model_name"],
buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0]
)
# 활성 모델 수
ACTIVE_MODELS = Gauge(
"active_models",
"Number of actively serving models"
)
# GPU 사용률
GPU_UTILIZATION = Gauge(
"gpu_utilization_percent",
"GPU utilization percentage",
["gpu_id"]
)
Grafana 대시보드 구성:
- 서비스 헬스 패널: 요청률(RPS), 에러율, P50/P95/P99 지연시간
- 모델 성능 패널: 모델별 추론 시간, 배치 크기 분포
- 인프라 패널: CPU/GPU/메모리 사용률, 파드 상태
- 비즈니스 패널: 모델 정확도 모니터링, A/B 테스트 결과
로그 수집 및 분석
구조화된 로깅:
import structlog
logger = structlog.get_logger()
async def predict(request):
logger.info(
"inference_started",
model_name=request.model_name,
user_id=request.user_id,
request_id=request.request_id,
)
try:
result = await run_inference(request)
logger.info(
"inference_completed",
model_name=request.model_name,
latency_ms=result.latency_ms,
score=result.score,
)
return result
except Exception as e:
logger.error(
"inference_failed",
model_name=request.model_name,
error=str(e),
error_type=type(e).__name__,
)
raise
ELK Stack vs Loki:
- ELK Stack (Elasticsearch + Logstash + Kibana): 전문(Full-text) 검색에 강점, 대규모 로그 분석에 적합
- Loki (Grafana): 라벨 기반 로그 집계, Grafana와 네이티브 통합, 비용 효율적
Alerting
알림 전략:
- P1 (즉시 대응): 서비스 다운, 에러율 급증, 데이터 유실
- P2 (30분 내 대응): 지연시간 SLA 위반, 디스크 사용률 90% 초과
- P3 (업무 시간 내 대응): 메모리 사용률 증가 추세, 모델 성능 저하
# Prometheus AlertManager 규칙 예시
groups:
- name: ml-serving-alerts
rules:
- alert: HighInferenceLatency
expr: |
histogram_quantile(0.99,
rate(inference_latency_seconds_bucket[5m])
) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: 'P99 추론 지연시간이 100ms를 초과했습니다'
- alert: HighErrorRate
expr: |
rate(inference_requests_total{status="error"}[5m])
/ rate(inference_requests_total[5m]) > 0.01
for: 2m
labels:
severity: critical
annotations:
summary: '추론 에러율이 1%를 초과했습니다'
3-6. 대규모 트래픽 & SLA
Load Balancing
L4 (Transport Layer):
- TCP/UDP 레벨에서 트래픽 분배
- 매우 빠른 처리 속도
- IP + Port 기반 라우팅
L7 (Application Layer):
- HTTP 헤더, URL 경로 기반 라우팅
- gRPC, WebSocket 지원
- 모델 이름에 따른 라우팅 가능
알고리즘:
- Round Robin: 순차적 분배 (기본)
- Least Connections: 연결 수가 가장 적은 서버로
- Weighted Round Robin: 서버 성능에 따라 가중치 부여
- Consistent Hashing: 같은 사용자를 같은 서버로 (캐시 효율)
Rate Limiting
Token Bucket 알고리즘:
import time
import asyncio
class TokenBucket:
def __init__(self, rate: float, capacity: int):
self.rate = rate # 초당 토큰 생성 속도
self.capacity = capacity
self.tokens = capacity
self.last_refill = time.monotonic()
self.lock = asyncio.Lock()
async def acquire(self) -> bool:
async with self.lock:
now = time.monotonic()
elapsed = now - self.last_refill
self.tokens = min(
self.capacity,
self.tokens + elapsed * self.rate
)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
Sliding Window 알고리즘:
- 고정 윈도우의 경계 효과를 해결
- Redis ZSET을 활용한 분산 환경 구현
- 더 정밀한 속도 제한 가능
Circuit Breaker
import asyncio
from enum import Enum
from dataclasses import dataclass
class CircuitState(Enum):
CLOSED = "closed" # 정상 상태
OPEN = "open" # 차단 상태
HALF_OPEN = "half_open" # 복구 확인 중
@dataclass
class CircuitBreaker:
failure_threshold: int = 5
recovery_timeout: float = 30.0
state: CircuitState = CircuitState.CLOSED
failure_count: int = 0
last_failure_time: float = 0
async def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if time.monotonic() - self.last_failure_time > self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
else:
raise CircuitOpenError("Circuit is open")
try:
result = await func(*args, **kwargs)
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.CLOSED
self.failure_count = 0
return result
except Exception as e:
self.failure_count += 1
self.last_failure_time = time.monotonic()
if self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
raise
성능 테스트
k6 로드 테스트 스크립트:
import http from 'k6/http'
import { check, sleep } from 'k6'
export const options = {
stages: [
{ duration: '2m', target: 100 }, // 점진적 증가
{ duration: '5m', target: 1000 }, // 피크 부하
{ duration: '2m', target: 0 }, // 점진적 감소
],
thresholds: {
http_req_duration: ['p(99)<100'], // P99 100ms 이하
http_req_failed: ['rate<0.01'], // 에러율 1% 미만
},
}
export default function () {
const payload = JSON.stringify({
user_id: `user_${__VU}`,
features: Array.from({ length: 128 }, () => Math.random()),
})
const params = {
headers: { 'Content-Type': 'application/json' },
}
const res = http.post('http://ml-serving.internal/v1/predict', payload, params)
check(res, {
'status is 200': (r) => r.status === 200,
'latency < 100ms': (r) => r.timings.duration < 100,
})
}
Locust 파이썬 기반 테스트:
from locust import HttpUser, task, between
class MLServingUser(HttpUser):
wait_time = between(0.1, 0.5)
@task(weight=10)
def predict_credit_score(self):
self.client.post("/v1/predict", json={
"model_name": "credit_score",
"user_id": "test_user",
"features": [0.5] * 128,
})
@task(weight=5)
def predict_fds(self):
self.client.post("/v1/predict", json={
"model_name": "fds_detector",
"transaction_id": "txn_123",
"features": [0.3] * 64,
})
4. 면접 예상 질문 30선
서버 아키텍처 (10문항)
Q1. REST API와 gRPC의 차이점을 설명하고, ML 모델 서빙에서 어떤 것을 선택하겠습니까?
핵심 포인트: gRPC는 Protocol Buffers 기반으로 직렬화 성능이 우수하고, 양방향 스트리밍을 지원하므로 내부 서비스 간 통신에 적합합니다. 외부 클라이언트 API는 REST로, 내부 모델 서빙은 gRPC로 구성하는 것이 일반적입니다.
Q2. CQRS 패턴을 ML 서빙 시스템에 어떻게 적용할 수 있습니까?
핵심 포인트: 모델 배포(Command)와 추론(Query)를 분리합니다. Command 측은 모델 레지스트리 업데이트, 설정 변경을 담당하고, Query 측은 읽기 전용으로 최적화된 추론 서비스를 운영합니다.
Q3. 서킷 브레이커의 세 가지 상태(CLOSED, OPEN, HALF_OPEN)를 설명하고, ML 서빙에서의 활용 사례를 들어주세요.
핵심 포인트: Feature Store 장애 시 캐시된 피처를 사용하거나, 외부 모델 서비스 장애 시 폴백 모델로 전환하는 등의 사례를 설명합니다.
Q4. 동기 통신과 비동기 통신의 트레이드오프를 설명하고, 각각 어떤 상황에서 사용하겠습니까?
핵심 포인트: 실시간 추론은 동기(gRPC/REST), 배치 추론이나 로깅은 비동기(Kafka)로 처리합니다. 비동기 통신은 서비스 간 결합도를 낮추고 장애 전파를 방지합니다.
Q5. 캐싱 전략에서 Cache Invalidation 문제를 어떻게 해결하겠습니까?
핵심 포인트: TTL 기반 만료, 이벤트 기반 무효화, Write-through/Write-behind 패턴 등을 설명합니다. ML 서빙에서는 모델 업데이트 시 관련 캐시를 이벤트로 무효화합니다.
Q6. Connection Pooling이 왜 중요하고, 어떻게 구현하겠습니까?
핵심 포인트: DB 커넥션, gRPC 채널, HTTP 커넥션 등의 생성 비용을 줄이고, 리소스를 효율적으로 관리합니다. 풀 크기 설정이 중요합니다 (너무 작으면 병목, 너무 크면 리소스 낭비).
Q7. API 버전 관리 전략을 설명해주세요.
핵심 포인트: URL 경로 기반(/v1/predict, /v2/predict), 헤더 기반 등의 전략이 있습니다. 하위 호환성을 유지하면서 점진적으로 새 버전으로 마이그레이션합니다.
Q8. Saga 패턴의 두 가지 구현 방식(Choreography vs Orchestration)의 차이는 무엇입니까?
핵심 포인트: Choreography는 이벤트 기반으로 각 서비스가 자율적으로 동작하고, Orchestration은 중앙 조율자가 흐름을 제어합니다. 복잡한 워크플로에는 Orchestration이 적합합니다.
Q9. 데이터베이스 인덱싱 전략을 설명하고, 추론 로그 테이블에 어떤 인덱스를 설계하겠습니까?
핵심 포인트: B-Tree, Hash, GIN 인덱스의 차이를 설명합니다. 추론 로그는 timestamp, model_name, user_id 등에 복합 인덱스를 설정하고 파티셔닝을 적용합니다.
Q10. 마이크로서비스 간 데이터 일관성을 어떻게 보장하겠습니까?
핵심 포인트: 강한 일관성(분산 트랜잭션)보다 최종적 일관성(Eventual Consistency)을 선택하는 것이 일반적입니다. Outbox 패턴, CDC(Change Data Capture) 등을 활용합니다.
K8s & 인프라 (10문항)
Q11. Pod의 라이프사이클과 Probe(readinessProbe, livenessProbe, startupProbe)의 차이를 설명해주세요.
핵심 포인트: livenessProbe는 컨테이너 재시작 여부 결정, readinessProbe는 트래픽 수신 가능 여부 결정, startupProbe는 초기화 완료 여부 결정입니다. ML 서빙에서 모델 로딩 시간이 긴 경우 startupProbe가 중요합니다.
Q12. HPA와 VPA의 차이점과 각각의 적합한 사용 사례는 무엇입니까?
핵심 포인트: HPA는 파드 수를 조절(수평 확장), VPA는 파드의 리소스 요청을 조절(수직 확장)합니다. ML 서빙은 HPA를 기본으로 사용하고, GPU 리소스 최적화에 VPA를 보조적으로 사용합니다.
Q13. Kubernetes에서 무중단 배포(Zero-Downtime Deployment)를 어떻게 구현하겠습니까?
핵심 포인트: Rolling Update 전략, readinessProbe 설정, PodDisruptionBudget 설정, Graceful Shutdown(preStop hook) 구현을 설명합니다.
Q14. Helm Chart와 Kustomize의 차이점은 무엇이고, 어떤 상황에서 각각을 사용하겠습니까?
핵심 포인트: Helm은 템플릿 기반 패키징으로 복잡한 애플리케이션 배포에 적합하고, Kustomize는 YAML 패치 기반으로 단순한 환경별 설정 관리에 적합합니다.
Q15. Service Mesh(Istio)가 ML 서빙에 제공하는 이점은 무엇입니까?
핵심 포인트: mTLS 자동 적용, 트래픽 관리(카나리 배포, A/B 테스트), 서킷 브레이커, 분산 추적 자동 주입 등을 설명합니다.
Q16. GitOps(ArgoCD)의 원칙과 장점을 설명해주세요.
핵심 포인트: Git을 single source of truth로 사용하여 선언적 배포를 관리합니다. 감사 추적, 롤백 용이성, 재현 가능성이 핵심 장점입니다.
Q17. Kubernetes에서 GPU를 효율적으로 관리하는 방법은 무엇입니까?
핵심 포인트: NVIDIA Device Plugin, MIG(Multi-Instance GPU), GPU 타임셰어링, 노드 어피니티를 통한 GPU 노드 전용 스케줄링을 설명합니다.
Q18. Pod 스케줄링에서 Affinity, Taint, Toleration의 차이를 설명해주세요.
핵심 포인트: Node Affinity는 특정 노드에 스케줄링을 선호하고, Taint/Toleration은 특정 노드를 특정 워크로드에 예약합니다. GPU 노드에 ML 서빙 파드만 스케줄링하는 등에 활용합니다.
Q19. ConfigMap과 Secret의 차이점과 보안 관련 고려사항은 무엇입니까?
핵심 포인트: Secret은 base64 인코딩(암호화 아님), RBAC으로 접근 제어, etcd 암호화 설정, External Secrets Operator를 통한 외부 비밀 관리 도구 연동을 설명합니다.
Q20. Kubernetes의 리소스 요청(Requests)과 제한(Limits)을 어떻게 설정하겠습니까?
핵심 포인트: Requests는 스케줄링 기준, Limits는 최대 사용량 제한입니다. ML 서빙에서는 GPU 리소스의 정확한 설정이 중요하며, CPU/메모리는 프로파일링을 통해 적정값을 찾습니다.
ML 서빙 & 시스템 설계 (10문항)
Q21. 실시간 신용평가 모델 서빙 시스템을 설계해주세요.
핵심 포인트: API Gateway → Feature Store 조회 → 모델 추론 → 결과 반환의 플로우를 설계합니다. P99 100ms SLA, 99.99% 가용성, 무중단 모델 업데이트를 충족하는 아키텍처를 제시합니다.
Q22. Dynamic Batching이 무엇이고, 어떤 트레이드오프가 있습니까?
핵심 포인트: 여러 요청을 모아서 배치로 처리하면 GPU 활용률이 높아지지만, 개별 요청의 지연시간이 증가할 수 있습니다. max_queue_delay로 최대 대기 시간을 설정합니다.
Q23. 모델 A/B 테스트를 어떻게 설계하고 운영하겠습니까?
핵심 포인트: 트래픽 분배(해시 기반), 통계적 유의성 검정, 조기 종료 기준, 안전 가드레일(에러율 급증 시 자동 롤백) 등을 설명합니다.
Q24. ONNX와 TensorRT를 활용한 모델 최적화 과정을 설명해주세요.
핵심 포인트: PyTorch 모델 → ONNX 변환 → ONNX 최적화 → TensorRT 엔진 생성 → 벤치마크 → 정확도 검증의 과정을 설명합니다.
Q25. Feature Store의 온라인/오프라인 구분과 각각의 역할은 무엇입니까?
핵심 포인트: 오프라인은 학습용(대용량, 배치 처리), 온라인은 서빙용(저지연, 실시간 조회)입니다. Feature Consistency(학습과 서빙 시 동일한 피처 보장)가 핵심 과제입니다.
Q26. 이상거래탐지(FDS) 시스템의 아키텍처를 설계해주세요.
핵심 포인트: 실시간 스트리밍(Kafka) → 피처 엔지니어링 → ML 추론 → 알림/차단의 파이프라인입니다. 초당 수만 건 처리, 밀리초 단위 응답, 높은 재현율(Recall)이 요구됩니다.
Q27. 카나리 배포와 블루-그린 배포의 차이점과 각각의 장단점은 무엇입니까?
핵심 포인트: 카나리는 점진적 트래픽 전환(위험 최소화), 블루-그린은 즉시 전환(빠른 배포/롤백). ML 모델 서빙에서는 카나리 배포가 일반적입니다(모델 성능을 점진적으로 검증).
Q28. P99 지연시간이 급증했을 때 어떻게 디버깅하겠습니까?
핵심 포인트: 분산 추적으로 병목 구간 확인 → 메트릭으로 리소스 사용률 확인 → 로그로 에러/경고 확인 → 프로파일링으로 코드 레벨 원인 분석의 순서로 접근합니다.
Q29. LLM 기반 AI 챗봇의 서빙 아키텍처를 설계해주세요.
핵심 포인트: RAG(Retrieval-Augmented Generation) 아키텍처, 벡터 DB 활용, GPU 메모리 관리, 스트리밍 응답, 토큰 레벨 속도 제한 등을 포함합니다.
Q30. ML 모델의 모니터링 전략을 설계해주세요. 모델 드리프트를 어떻게 감지하겠습니까?
핵심 포인트: 입력 데이터 분포 변화(Data Drift), 모델 예측 분포 변화(Concept Drift)를 모니터링합니다. KS Test, PSI(Population Stability Index) 등의 통계 기법을 활용합니다.
5. 6개월 학습 로드맵
월별 상세 계획
| 월 | 주제 | 구체적 목표 |
|---|---|---|
| 1개월 | 언어 선택 + 서버 기초 | FastAPI 또는 Spring Boot로 REST API 프로젝트 완성 |
| 2개월 | DB + 캐시 + 메시지 큐 | Redis, PostgreSQL, Kafka 실습 프로젝트 |
| 3개월 | K8s + 배포 | Minikube에서 EKS/GKE로, Helm 차트 작성 |
| 4개월 | ML 서빙 기초 | Triton, BentoML로 실제 모델 서빙 구현 |
| 5개월 | MSA + 분산 추적 | OpenTelemetry, Grafana 대시보드 구성 |
| 6개월 | 시스템 설계 면접 + 포트폴리오 | 모의면접 실전, 이력서 최종 완성 |
1개월차: 언어 선택 + 서버 기초
주차별 계획:
Week 1-2: 메인 언어 기초 다지기
- Python 선택 시: FastAPI 공식 튜토리얼 완주
- Kotlin 선택 시: Spring Boot + Kotlin 기본 프로젝트
- 기본 CRUD API 개발 (모델 메타데이터 관리 API)
Week 3-4: 비동기 프로그래밍 + 테스트
- asyncio/Coroutines/goroutine 심화 학습
- 단위 테스트, 통합 테스트 작성
- Docker로 개발 환경 구성
이달의 산출물: 모델 메타데이터 관리 REST API (GitHub에 공개)
2개월차: DB + 캐시 + 메시지 큐
주차별 계획:
Week 1-2: 데이터베이스 심화
- PostgreSQL: 스키마 설계, 인덱싱, 쿼리 최적화
- Redis: 캐싱 패턴, 데이터 구조 활용
- SQLAlchemy/Exposed ORM 사용법
Week 3-4: 메시지 큐 + 이벤트 기반 아키텍처
- Kafka 기초: Producer, Consumer, Topic, Partition
- 이벤트 기반 비동기 처리 구현
- docker-compose로 통합 환경 구성
이달의 산출물: 1개월차 프로젝트에 DB, 캐시, 비동기 이벤트 처리 추가
3개월차: K8s + 배포
주차별 계획:
Week 1-2: Kubernetes 기초
- Minikube로 로컬 클러스터 구성
- Pod, Deployment, Service, Ingress 실습
- ConfigMap, Secret 관리
Week 3-4: 배포 자동화
- Helm Chart 작성
- ArgoCD로 GitOps 배포 구현
- HPA 오토스케일링 설정
- CI/CD 파이프라인 (GitHub Actions)
이달의 산출물: K8s에 배포된 애플리케이션 + Helm Chart + ArgoCD 설정
4개월차: ML 서빙 기초
주차별 계획:
Week 1-2: 모델 서빙 기초
- Triton Inference Server 설치 및 기본 사용
- ONNX 모델 변환 실습
- BentoML로 간단한 모델 서빙
Week 3-4: 서빙 최적화
- Dynamic Batching 설정 및 벤치마크
- 모델 버전 관리 구현
- Feature Store(Feast) 기초 설정
- K8s에 Triton 배포
이달의 산출물: K8s 위에서 동작하는 ML 모델 서빙 파이프라인
5개월차: MSA + 분산 추적
주차별 계획:
Week 1-2: Observability 구축
- OpenTelemetry 계측 구현
- Prometheus + Grafana 모니터링 대시보드
- Jaeger로 분산 추적 시각화
Week 3-4: MSA 패턴 적용
- 서킷 브레이커 구현
- 서비스 간 gRPC 통신
- 알림(Alerting) 규칙 설정
- 성능 테스트(k6/Locust) 실행
이달의 산출물: 완전한 Observability 스택이 갖추어진 ML 서빙 시스템
6개월차: 시스템 설계 면접 + 포트폴리오
주차별 계획:
Week 1-2: 시스템 설계 연습
- 면접 예상 질문 30선 모두 연습
- 화이트보드 시스템 설계 연습
- 동료/스터디 그룹과 모의면접
Week 3-4: 포트폴리오 + 이력서
- GitHub 프로젝트 README 정비
- 기술 블로그 작성 (학습 과정 기록)
- 이력서 작성 및 리뷰
- 토스뱅크 외 관심 기업 지원 준비
이달의 산출물: 완성된 이력서 + 포트폴리오 + 모의면접 피드백
6. 이력서 작성 전략
STAR 기법으로 경험 정리
이력서에서 가장 중요한 것은 **"무엇을 했는가"가 아니라 "어떤 문제를 어떻게 해결했는가"**입니다.
STAR 프레임워크:
- S (Situation): 어떤 상황이었나
- T (Task): 어떤 과제가 주어졌나
- A (Action): 구체적으로 무엇을 했나
- R (Result): 어떤 결과를 만들었나
좋은 예시:
[프로젝트] ML 모델 서빙 플랫폼 구축
상황: 데이터팀이 학습한 모델을 프로덕션에 배포하는 데 평균 2주가 소요
과제: 모델 배포 시간을 단축하고 서빙 안정성을 확보
행동:
- FastAPI + Triton 기반 모델 서빙 API 개발
- Helm Chart로 K8s 배포 자동화
- ArgoCD GitOps 파이프라인 구축
- OpenTelemetry 기반 모니터링 대시보드 구성
결과:
- 모델 배포 시간 2주 → 30분으로 단축 (97% 감소)
- P99 추론 지연시간 50ms 달성
- 서비스 가용성 99.95% 유지
트레이드오프 분석 강조
면접관이 가장 보고 싶어하는 것은 기술적 의사결정 과정입니다.
작성 팁:
- "A 대신 B를 선택한 이유"를 명확히 기술
- 고려했던 대안과 각각의 장단점을 언급
- 성능, 비용, 운영 복잡성 등 다차원적 비교
예시:
gRPC를 서비스 간 통신 프로토콜로 선택
- REST 대비 직렬화 성능 3배 향상 확인
- 단, 디버깅 복잡성 증가를 고려하여 gRPC 리플렉션과
grpcurl을 활용한 디버깅 환경 구축
- HTTP/2 기반 다중화로 커넥션 관리 효율화
비즈니스 임팩트를 수치로 증명
수치화 가이드:
- 성능 개선: "P99 지연시간 200ms에서 50ms로 75% 감소"
- 비용 절감: "GPU 활용률 최적화로 월 클라우드 비용 30% 절감"
- 효율성: "모델 배포 자동화로 엔지니어 시간 주당 10시간 절약"
- 안정성: "서비스 가용성 99.9%에서 99.99%로 향상"
모니터링에서 개선까지의 사이클 보여주기
단순히 "만들었다"가 아니라 "운영하면서 개선했다"는 스토리가 강력합니다.
배포 → 모니터링 → 이슈 발견 → 원인 분석 → 개선 → 재배포 → 검증
예시:
1. Grafana 대시보드에서 특정 시간대 P99 지연시간 급증 발견
2. Jaeger 분산 추적으로 Feature Store 조회 병목 확인
3. Redis 캐시 히트율 분석 → 캐시 키 전략 개선
4. 결과: Feature Store 조회 지연시간 80% 감소,
캐시 히트율 60%에서 92%로 향상
7. 포트폴리오 프로젝트 아이디어
프로젝트 1: ML 모델 서빙 플랫폼
기술스택: FastAPI + Triton Inference Server + Kubernetes + ArgoCD
구현 범위:
- FastAPI 기반 모델 서빙 Gateway API
- Triton Inference Server로 실제 모델 서빙
- ONNX 모델 변환 및 최적화 파이프라인
- Dynamic Batching 설정 및 벤치마크
- K8s Deployment + HPA 오토스케일링
- Helm Chart로 패키징
- ArgoCD GitOps 배포
- Prometheus + Grafana 모니터링
- OpenTelemetry 분산 추적
차별화 포인트:
- 모델 버전 관리 및 카나리 배포 구현
- A/B 테스트 프레임워크 통합
- 성능 벤치마크 결과 포함 (P50, P95, P99 지연시간, 처리량)
프로젝트 2: 실시간 이상 탐지 시스템
기술스택: Kafka + Python + ML 추론 + Redis + PostgreSQL
구현 범위:
- Kafka Consumer로 실시간 거래 이벤트 수집
- Feature 실시간 계산 (시간 윈도우 기반 통계)
- Redis를 활용한 온라인 Feature Store
- ML 모델로 이상 거래 판별
- 판별 결과 DB 저장 및 알림 발송
- Grafana 대시보드로 실시간 모니터링
차별화 포인트:
- 실시간 스트리밍 처리 경험 증명
- 초당 수천 건 처리 성능 벤치마크
- 모델 재학습 트리거 구현 (Data Drift 감지)
프로젝트 3: AI 챗봇 백엔드 (RAG + LLM 서빙)
기술스택: FastAPI + LangChain + 벡터 DB + GPU 서빙
구현 범위:
- RAG(Retrieval-Augmented Generation) 아키텍처 설계
- 문서 임베딩 파이프라인 (청킹, 벡터화, 저장)
- 벡터 DB (Milvus, Qdrant, Pinecone 중 선택)
- LLM 서빙 (vLLM 또는 TGI 활용)
- 스트리밍 응답 구현 (SSE)
- 대화 히스토리 관리
- 속도 제한(Rate Limiting) 구현
- K8s GPU 노드 스케줄링
차별화 포인트:
- LLM 서빙은 최신 트렌드이며 토스뱅크 AI 챗봇과 직결
- GPU 리소스 관리 경험 증명
- 토큰 사용량 모니터링 및 비용 최적화
실전 퀴즈
배운 내용을 점검해 봅시다.
Q1. Triton Inference Server의 Dynamic Batching에서 max_queue_delay_microseconds를 5000으로 설정했을 때, 이것이 의미하는 바는 무엇인가요?
정답: 요청이 도착한 후 최대 5ms(5000마이크로초) 동안 추가 요청을 기다리면서 배치를 구성합니다. 이 시간 내에 더 많은 요청이 도착하면 하나의 배치로 묶어 GPU에서 한 번에 처리합니다.
핵심 트레이드오프:
- 값을 높이면: 배치 크기가 커져 GPU 활용률은 높아지지만, 개별 요청의 지연시간이 증가합니다
- 값을 낮추면: 개별 요청의 지연시간은 줄어들지만, 배치 크기가 작아져 GPU 활용률이 낮아집니다
- P99 SLA 요구사항에 맞게 조절해야 합니다
Q2. 서킷 브레이커의 HALF_OPEN 상태에서는 무엇이 일어나나요?
정답: HALF_OPEN 상태는 서킷 브레이커가 OPEN(차단) 상태에서 설정된 복구 대기 시간이 지난 후 진입하는 상태입니다.
이 상태에서는 제한된 수의 요청만 통과시켜 하류 서비스가 정상으로 복구되었는지 확인합니다.
- 테스트 요청이 성공하면: CLOSED(정상) 상태로 전환하여 모든 트래픽을 다시 통과시킵니다
- 테스트 요청이 실패하면: 다시 OPEN 상태로 돌아가 복구 대기 시간을 리셋합니다
이를 통해 장애가 복구된 서비스에 갑자기 대량 트래픽이 유입되는 "thundering herd" 문제를 방지합니다.
Q3. Feature Store에서 온라인 스토어와 오프라인 스토어를 분리하는 이유는 무엇인가요?
정답: 온라인 스토어와 오프라인 스토어는 요구사항이 완전히 다르기 때문입니다.
오프라인 스토어 (학습용):
- 대용량 데이터 저장 (수 TB ~ PB)
- 배치 처리로 대량 읽기 가능
- 지연시간은 크게 중요하지 않음 (초~분 단위 허용)
- 보통 BigQuery, S3, Hive 등 사용
온라인 스토어 (서빙용):
- 실시간 개별 조회 (key-value 패턴)
- 밀리초 단위의 낮은 지연시간 필수
- 비교적 작은 데이터 (최신 피처 값만)
- 보통 Redis, DynamoDB 등 사용
가장 중요한 원칙: Training-Serving Skew(학습-서빙 불일치)를 방지하기 위해, 같은 피처 정의를 양쪽 스토어에서 공유해야 합니다.
Q4. P99 지연시간과 평균 지연시간 중 프로덕션 모니터링에서 더 중요한 지표는 무엇이고 그 이유는?
정답: P99 지연시간이 더 중요합니다.
평균 지연시간은 극단적으로 빠르거나 느린 요청이 서로 상쇄되어 실제 사용자 경험을 왜곡합니다. 예를 들어 평균 50ms라도, 1%의 사용자가 2초 이상 기다릴 수 있습니다.
P99 지연시간은 "99%의 요청이 이 시간 안에 응답된다"는 의미이므로, 대부분의 사용자 경험을 보장합니다.
금융 서비스에서 SLA를 정의할 때도 P99 (또는 P99.9)를 기준으로 하는 것이 일반적입니다. 100명 중 1명이라도 느린 응답을 받으면 그것이 바로 서비스 품질 문제이기 때문입니다.
추가로 알면 좋은 점: P99가 높다면 GC(Garbage Collection) 정지, 콜드 스타트, 캐시 미스 등 간헐적 원인을 조사해야 합니다.
Q5. 카나리 배포에서 새 모델 버전의 트래픽 비율을 10%로 시작하는 이유는 무엇인가요?
정답: 새 모델에 예상치 못한 문제가 있을 때 영향 범위를 최소화하기 위해서입니다.
단계적 증가 전략:
- 10%로 시작하여 에러율, 지연시간, 비즈니스 메트릭 관찰
- 문제가 없으면 25% → 50% → 75% → 100%으로 점진적 증가
- 각 단계에서 충분한 관찰 시간을 확보 (통계적 유의성 확보)
- 문제 발생 시 즉시 0%로 롤백
ML 모델 특유의 고려사항:
- 모델 정확도는 A/B 테스트 기간 동안 충분한 데이터가 모여야 평가 가능합니다
- 일부 edge case에서만 성능이 떨어지는 모델 문제를 사전에 발견할 수 있습니다
- 금융 도메인에서는 모델 오류가 직접적인 금전적 손실로 이어지므로 더욱 보수적인 접근이 필요합니다
10%는 업계에서 일반적으로 사용하는 초기 비율이며, 서비스 특성에 따라 5% 또는 1%로 더 보수적으로 시작하기도 합니다.
참고 자료
서적
- "Designing Data-Intensive Applications" - Martin Kleppmann: 분산 시스템의 바이블로, 데이터 시스템 설계의 핵심 원칙을 학습할 수 있습니다
- "System Design Interview Vol.1 & Vol.2" - Alex Xu: 시스템 설계 면접 준비의 필수서로, 실전 설계 문제와 풀이를 다룹니다
- "Building Machine Learning Pipelines" - Hannes Hapke: ML 파이프라인의 전체 라이프사이클을 다루는 실무서입니다
- "Kubernetes in Action" - Marko Luksa: K8s 아키텍처부터 실전 운영까지 체계적으로 학습할 수 있습니다
- "Designing Machine Learning Systems" - Chip Huyen: ML 시스템 설계의 핵심 원칙과 패턴을 배울 수 있습니다
온라인 강좌
- Stanford CS329S: Machine Learning Systems Design: ML 시스템 설계에 관한 스탠포드 강의로, ML 서빙 아키텍처를 깊이 학습할 수 있습니다
- Google SRE Book (sre.google/books): Site Reliability Engineering의 원칙과 사례를 다루며, SLA 관리와 장애 대응에 대한 인사이트를 제공합니다
- Kubernetes 공식 문서 (kubernetes.io/docs): K8s의 모든 리소스와 개념을 참고할 수 있는 공식 레퍼런스입니다
기술 블로그 및 자료
- NVIDIA Triton Inference Server Documentation: Triton의 모든 기능과 설정을 상세히 다루는 공식 문서입니다
- OpenTelemetry 공식 문서 (opentelemetry.io): 분산 추적 표준의 개념, SDK, Collector 설정을 학습할 수 있습니다
- Feast Documentation (feast.dev): Feature Store의 아키텍처와 사용법을 학습하는 공식 가이드입니다
- BentoML 공식 문서 (docs.bentoml.com): Python 기반 모델 서빙 프레임워크의 튜토리얼과 레퍼런스입니다
- 토스 기술 블로그 (toss.tech): 토스 그룹의 기술 문화와 엔지니어링 사례를 파악하는 데 필수적인 자료입니다
- MLOps Community (mlops.community): ML 운영에 관한 실무 사례와 최신 트렌드를 공유하는 커뮤니티입니다
- The ML Engineer Newsletter: ML 엔지니어링 분야의 최신 논문, 도구, 사례를 정리한 뉴스레터입니다
실습 플랫폼
- Katacoda/Killercoda: 브라우저에서 바로 K8s를 실습할 수 있는 인터랙티브 환경입니다
- k6 Documentation (k6.io): 성능 테스트 도구의 사용법과 스크립트 작성 가이드입니다
- Grafana Play (play.grafana.org): Grafana 대시보드를 직접 체험해볼 수 있는 데모 환경입니다
Toss Bank ML Backend Engineer Study Guide: Server Architecture, ML Serving, and Career Roadmap
- Introduction: Where ML Meets Banking at Scale
- 1. JD Deep Analysis: What Toss Bank Actually Wants
- 2. Tech Stack Deep Dive
- 3. Interview Preparation: 30 Questions
- 4. Six-Month Study Roadmap
- 5. Resume Strategy
- 6. Portfolio Project Ideas
- 7. Quiz: Test Your Knowledge
- 8. References and Resources
- Conclusion
Introduction: Where ML Meets Banking at Scale
Machine learning in production is not about Jupyter notebooks. It is about serving millions of predictions per second with sub-10ms latency while maintaining model accuracy, handling graceful degradation when models fail, and operating in a regulatory environment where every prediction must be explainable and auditable.
Toss Bank's ML Service Team sits at this exact intersection. They build the backend infrastructure that serves AI models for credit scoring, fraud detection, recommendation systems, and real-time risk assessment. When Toss Bank posts an ML Backend Engineer position, they are not looking for someone who can train a model — they are looking for someone who can build and operate the production systems that serve those models to millions of users.
This is a role that requires deep backend engineering skills (server architecture, distributed systems, performance optimization) combined with practical ML serving knowledge (model deployment, A/B testing infrastructure, feature stores). You need to be equally comfortable designing a high-availability microservice architecture and deploying a TensorFlow Serving cluster on Kubernetes.
This guide breaks down every requirement in the job description, maps each one to specific technologies and study materials, and provides a 6-month plan to get you interview-ready.
1. JD Deep Analysis: What Toss Bank Actually Wants
1.1 Core Responsibilities
"Design and implement robust server architectures for ML model serving"
This is the primary responsibility. You will build the systems that accept inference requests, route them to the appropriate model, return predictions, and handle failures gracefully. This involves:
- Designing APIs for synchronous (REST, gRPC) and asynchronous (event-driven) inference
- Building model registries that track model versions, metadata, and deployment status
- Implementing A/B testing and canary deployment infrastructure for models
- Managing model lifecycle: training pipeline integration, model validation, promotion, and rollback
- Shadow mode deployments for new models (serve traffic but do not use predictions)
"Handle large-scale traffic with high availability and low latency"
Toss Bank serves millions of users. The ML backend must:
- Handle tens of thousands of requests per second per model endpoint
- Maintain p99 latency under 10ms for critical inference paths (fraud detection)
- Achieve 99.99% availability (less than 52 minutes of downtime per year)
- Implement circuit breakers, bulkheads, and fallback strategies
- Auto-scale based on traffic patterns (morning peaks, payday spikes)
"Deploy and operate AI model serving infrastructure on Kubernetes"
Kubernetes is the deployment platform. You need to:
- Deploy model serving frameworks (TensorFlow Serving, Triton Inference Server, TorchServe) on K8s
- Configure GPU scheduling and resource quotas for model workloads
- Implement custom HPA (Horizontal Pod Autoscaler) metrics based on inference latency and queue depth
- Manage model artifacts with persistent volumes or object storage integration
- Set up canary deployments using Istio or Argo Rollouts
"Build and maintain MSA (Microservice Architecture) with distributed tracing"
The ML platform is built as microservices. You need to:
- Design service boundaries for feature computation, model inference, post-processing, and logging
- Implement distributed tracing with OpenTelemetry across all services
- Set up service mesh (Istio) for traffic management, mTLS, and observability
- Build centralized logging with ELK or Loki stack
- Implement health checks, readiness probes, and liveness probes for all services
"Develop core platform services in Python, Kotlin, and Go"
Toss Bank uses a polyglot tech stack:
- Python: ML model serving, feature engineering, data pipelines
- Kotlin: Core backend services, Spring Boot applications, API gateways
- Go: High-performance infrastructure services, CLI tools, operators
You are not expected to be an expert in all three, but you need working proficiency in at least two and willingness to learn the third.
1.2 Required Qualifications
"5+ years of backend engineering experience"
This is a mid-to-senior role. They want someone who has built and operated production systems, not just written code. Experience with production incidents, performance optimization under pressure, and architectural decision-making is expected.
"Experience designing and operating microservice architectures"
You should be able to discuss service decomposition strategies, inter-service communication patterns (sync vs async), distributed transaction management (saga pattern, eventual consistency), and the operational overhead of microservices (deployment pipelines, monitoring, debugging).
"Proficiency in at least one of Python, Kotlin, or Go"
Deep expertise in one language is more valuable than shallow knowledge of all three. However, you should be able to read and understand code in the other two languages. The interview will likely focus on your strongest language but may include pair programming in another.
"Understanding of ML model serving concepts"
You do not need to train models, but you need to understand:
- Model formats (SavedModel, ONNX, TorchScript) and their trade-offs
- Batch inference vs online inference
- Feature stores and feature computation pipelines
- Model monitoring (data drift, concept drift, performance degradation)
- A/B testing methodology for ML models
"Kubernetes operational experience"
This means more than kubectl apply. You should understand:
- StatefulSets vs Deployments for model serving
- Resource requests and limits for GPU workloads
- Network policies and service mesh configuration
- Custom Resource Definitions (CRDs) and operators
- Cluster autoscaling and node pool management
1.3 Preferred Qualifications
"Experience with GPU infrastructure for ML inference"
GPU resource management is a specialized skill. Understanding NVIDIA GPU scheduling, CUDA, multi-instance GPU (MIG) partitioning, and GPU monitoring (DCGM) sets you apart.
"Contributions to open-source ML infrastructure projects"
Projects like KServe, Seldon Core, MLflow, or Kubeflow contributions demonstrate that you understand the ML infrastructure ecosystem.
"Experience with real-time feature serving"
Feature stores like Feast or Tecton, and the ability to serve features with sub-millisecond latency, are highly valued for online ML inference.
2. Tech Stack Deep Dive
2.1 Server Architecture Patterns for ML Serving
Synchronous Inference Architecture
The most common pattern for real-time ML inference:
Client -> API Gateway -> Model Router -> Model Server -> Response
|
Feature Store
(Redis/DynamoDB)
// Kotlin - Model Router Service
@RestController
@RequestMapping("/api/v1/predict")
class PredictionController(
private val modelRouter: ModelRouter,
private val featureService: FeatureService,
private val metricsService: MetricsService
) {
@PostMapping("/{modelName}")
suspend fun predict(
@PathVariable modelName: String,
@RequestBody request: PredictionRequest
): ResponseEntity<PredictionResponse> {
val timer = metricsService.startTimer("prediction_latency")
try {
// 1. Fetch features
val features = featureService.getFeatures(
request.entityId,
modelName
)
// 2. Route to appropriate model version
val modelEndpoint = modelRouter.route(
modelName,
request.headers
)
// 3. Call model server
val prediction = modelEndpoint.predict(features)
// 4. Log prediction for monitoring
metricsService.recordPrediction(modelName, prediction)
return ResponseEntity.ok(prediction)
} catch (e: ModelUnavailableException) {
// Fallback to default model or rule-based system
return ResponseEntity.ok(fallbackPrediction(modelName, request))
} finally {
timer.stop()
}
}
}
Asynchronous Inference Architecture
For non-real-time workloads (batch scoring, pre-computation):
Producer -> Kafka Topic -> Flink/Consumer -> Model Server -> Result Topic
|
Feature Store
# Python - Async inference worker
from confluent_kafka import Consumer, Producer
import tritonclient.grpc as grpc_client
class InferenceWorker:
def __init__(self, config):
self.consumer = Consumer(config.kafka_consumer_config)
self.producer = Producer(config.kafka_producer_config)
self.triton = grpc_client.InferenceServerClient(
url=config.triton_url
)
def process_batch(self, messages):
# Batch multiple requests for efficient GPU utilization
inputs = self._prepare_batch_inputs(messages)
# Triton Inference Server call
result = self.triton.infer(
model_name="fraud_detection_v3",
inputs=inputs,
outputs=[grpc_client.InferRequestedOutput("predictions")]
)
predictions = result.as_numpy("predictions")
# Publish results
for msg, pred in zip(messages, predictions):
self.producer.produce(
topic="prediction-results",
key=msg.key(),
value=self._serialize_prediction(pred)
)
self.producer.flush()
2.2 Kubernetes for ML Workloads
GPU Scheduling on Kubernetes
# GPU-enabled model serving deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: fraud-model-serving
labels:
app: fraud-model
version: v3
spec:
replicas: 3
selector:
matchLabels:
app: fraud-model
template:
metadata:
labels:
app: fraud-model
version: v3
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '8002'
spec:
containers:
- name: triton
image: nvcr.io/nvidia/tritonserver:24.01-py3
ports:
- containerPort: 8000
name: http
- containerPort: 8001
name: grpc
- containerPort: 8002
name: metrics
resources:
requests:
cpu: '4'
memory: '16Gi'
nvidia.com/gpu: '1'
limits:
cpu: '8'
memory: '32Gi'
nvidia.com/gpu: '1'
volumeMounts:
- name: model-store
mountPath: /models
readinessProbe:
httpGet:
path: /v2/health/ready
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /v2/health/live
port: 8000
initialDelaySeconds: 30
periodSeconds: 15
volumes:
- name: model-store
persistentVolumeClaim:
claimName: model-artifacts-pvc
nodeSelector:
gpu-type: nvidia-a100
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
Custom HPA for ML Workloads
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: fraud-model-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: fraud-model-serving
minReplicas: 3
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: inference_request_queue_depth
target:
type: AverageValue
averageValue: '10'
- type: Pods
pods:
metric:
name: inference_latency_p99_ms
target:
type: AverageValue
averageValue: '8'
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 120
2.3 Model Serving Frameworks
Triton Inference Server
Triton (by NVIDIA) supports multiple frameworks and provides advanced features:
# Model configuration for Triton
# config.pbtxt in model repository
"""
name: "fraud_detection"
platform: "onnxruntime_onnx"
max_batch_size: 64
input [
{
name: "features"
data_type: TYPE_FP32
dims: [ 128 ]
}
]
output [
{
name: "probability"
data_type: TYPE_FP32
dims: [ 1 ]
}
]
instance_group [
{
count: 2
kind: KIND_GPU
}
]
dynamic_batching {
preferred_batch_size: [ 16, 32, 64 ]
max_queue_delay_microseconds: 100
}
"""
TensorFlow Serving
# Model export for TF Serving
import tensorflow as tf
model = tf.keras.models.load_model("fraud_model")
# Export as SavedModel with signature
tf.saved_model.save(
model,
"exported_model/1",
signatures={
"serving_default": model.predict.get_concrete_function(
tf.TensorSpec(shape=[None, 128], dtype=tf.float32, name="features")
)
}
)
KServe on Kubernetes
KServe (formerly KFServing) provides a standardized ML serving layer on K8s:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: fraud-detection
spec:
predictor:
model:
modelFormat:
name: onnx
storageUri: 's3://models/fraud-detection/v3'
resources:
requests:
cpu: '2'
memory: '8Gi'
nvidia.com/gpu: '1'
minReplicas: 2
maxReplicas: 10
transformer:
containers:
- name: feature-transformer
image: registry.tossbank.com/feature-transformer:v1
resources:
requests:
cpu: '1'
memory: '2Gi'
2.4 Feature Store Architecture
A feature store provides consistent feature computation for training and serving:
# Feature definition with Feast
from feast import Entity, Feature, FeatureView, FileSource
from feast.types import Float32, Int64
# Define entity
user = Entity(
name="user_id",
join_keys=["user_id"],
value_type=Int64,
)
# Define feature view
user_transaction_features = FeatureView(
name="user_transaction_features",
entities=[user],
ttl=timedelta(hours=24),
schema=[
Feature(name="transaction_count_7d", dtype=Float32),
Feature(name="avg_transaction_amount_30d", dtype=Float32),
Feature(name="max_transaction_amount_7d", dtype=Float32),
Feature(name="unique_merchants_30d", dtype=Int64),
Feature(name="late_night_transaction_ratio", dtype=Float32),
],
online=True,
source=FileSource(
path="s3://features/user_transactions.parquet",
timestamp_field="event_timestamp",
),
)
// Go - High-performance feature serving endpoint
package main
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/redis/go-redis/v9"
)
type FeatureServer struct {
redis *redis.Client
}
type FeatureRequest struct {
EntityID string `json:"entity_id"`
Features []string `json:"features"`
}
type FeatureResponse struct {
Features map[string]float64 `json:"features"`
Latency int64 `json:"latency_us"`
}
func (s *FeatureServer) GetFeatures(
w http.ResponseWriter, r *http.Request,
) {
start := time.Now()
var req FeatureRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Batch fetch from Redis
ctx := context.Background()
pipe := s.redis.Pipeline()
cmds := make([]*redis.StringCmd, len(req.Features))
for i, feature := range req.Features {
key := req.EntityID + ":" + feature
cmds[i] = pipe.Get(ctx, key)
}
_, err := pipe.Exec(ctx)
if err != nil && err != redis.Nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
features := make(map[string]float64)
for i, cmd := range cmds {
val, err := cmd.Float64()
if err == nil {
features[req.Features[i]] = val
}
}
resp := FeatureResponse{
Features: features,
Latency: time.Since(start).Microseconds(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
2.5 Distributed Tracing with OpenTelemetry
Tracing across ML microservices is essential for debugging latency issues:
// Kotlin - OpenTelemetry instrumentation
import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.api.trace.StatusCode
class TracedModelInference(
private val modelClient: ModelClient
) {
private val tracer = GlobalOpenTelemetry.getTracer("ml-inference")
suspend fun infer(
modelName: String,
features: Map<String, Float>
): PredictionResult {
val span = tracer.spanBuilder("model-inference")
.setSpanKind(SpanKind.CLIENT)
.setAttribute("model.name", modelName)
.setAttribute("model.features.count", features.size.toLong())
.startSpan()
return try {
span.makeCurrent().use {
val result = modelClient.predict(modelName, features)
span.setAttribute("model.version", result.modelVersion)
span.setAttribute(
"model.latency_ms",
result.inferenceLatencyMs.toDouble()
)
span.setStatus(StatusCode.OK)
result
}
} catch (e: Exception) {
span.setStatus(StatusCode.ERROR, e.message ?: "Unknown error")
span.recordException(e)
throw e
} finally {
span.end()
}
}
}
2.6 Circuit Breaker Pattern for Model Serving
When a model server becomes unhealthy, the circuit breaker prevents cascading failures:
// Kotlin - Resilience4j circuit breaker for model inference
import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig
import java.time.Duration
val circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50f)
.slowCallRateThreshold(80f)
.slowCallDurationThreshold(Duration.ofMillis(200))
.waitDurationInOpenState(Duration.ofSeconds(30))
.slidingWindowSize(100)
.minimumNumberOfCalls(20)
.build()
val modelCircuitBreaker = CircuitBreaker.of(
"fraud-model", circuitBreakerConfig
)
// Usage with fallback
fun predictWithFallback(
features: Map<String, Float>
): PredictionResult {
return try {
modelCircuitBreaker.executeSupplier {
modelClient.predict("fraud_detection_v3", features)
}
} catch (e: Exception) {
// Fallback to rule-based system when model is unavailable
ruleBasedFraudDetection(features)
}
}
2.7 Model Monitoring and Observability
# Python - Model monitoring with custom metrics
from prometheus_client import Counter, Histogram, Gauge
import numpy as np
# Metrics
prediction_counter = Counter(
"model_predictions_total",
"Total predictions",
["model_name", "model_version"]
)
prediction_latency = Histogram(
"model_prediction_latency_seconds",
"Prediction latency",
["model_name"],
buckets=[0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5]
)
feature_drift_gauge = Gauge(
"model_feature_drift_score",
"Feature drift score (PSI)",
["model_name", "feature_name"]
)
prediction_distribution = Histogram(
"model_prediction_distribution",
"Distribution of prediction values",
["model_name"],
buckets=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
)
class ModelMonitor:
def __init__(self, model_name, reference_data):
self.model_name = model_name
self.reference_distributions = self._compute_distributions(
reference_data
)
def record_prediction(self, features, prediction, latency):
prediction_counter.labels(
model_name=self.model_name,
model_version="v3"
).inc()
prediction_latency.labels(
model_name=self.model_name
).observe(latency)
prediction_distribution.labels(
model_name=self.model_name
).observe(prediction)
def check_feature_drift(self, recent_features):
"""Calculate PSI (Population Stability Index) for drift detection"""
for feature_name, values in recent_features.items():
psi = self._calculate_psi(
self.reference_distributions[feature_name],
values
)
feature_drift_gauge.labels(
model_name=self.model_name,
feature_name=feature_name
).set(psi)
if psi > 0.2:
self._alert_drift(feature_name, psi)
def _calculate_psi(self, expected, actual, bins=10):
expected_hist, edges = np.histogram(expected, bins=bins)
actual_hist, _ = np.histogram(actual, bins=edges)
# Avoid division by zero
expected_pct = (expected_hist + 1) / (sum(expected_hist) + bins)
actual_pct = (actual_hist + 1) / (sum(actual_hist) + bins)
psi = sum(
(actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
)
return psi
3. Interview Preparation: 30 Questions
3.1 Backend Architecture (Questions 1-10)
Q1. How would you design a model serving system that handles 50,000 requests per second with p99 latency under 10ms?
Start with a multi-tier architecture: an API gateway (Kong or Envoy) for rate limiting and routing, a model router service that directs requests to the correct model version, and model server pods running Triton or TF Serving. Use gRPC instead of REST for inter-service communication to reduce serialization overhead. Pre-load models into GPU memory and use dynamic batching to maximize GPU utilization. Cache frequently used features in Redis with sub-millisecond access. Horizontal scaling with custom HPA metrics based on inference queue depth.
Q2. Explain the differences between online inference, batch inference, and near-real-time inference. When would you use each?
Online inference serves predictions synchronously per request, used for real-time decisions like fraud detection (latency requirement: less than 10ms). Batch inference processes large datasets periodically, used for pre-computed recommendations or credit score updates (latency: minutes to hours). Near-real-time inference processes streaming data with small delays, used for event-driven scoring where slight delay is acceptable (latency: 100ms to seconds). The choice depends on latency requirements, cost efficiency, and data freshness needs.
Q3. How do you implement graceful degradation when a model server fails?
Layer 1: Circuit breaker per model endpoint that opens after a threshold of failures. Layer 2: Fallback to a simpler model (a lightweight model or rule-based system) when the primary model is unavailable. Layer 3: Cached predictions for frequently seen inputs. Layer 4: Default safe values for critical paths (e.g., flag for manual review instead of auto-approve). Monitor fallback rates and alert when they exceed thresholds.
Q4. Describe the saga pattern and how you would apply it to an ML pipeline that writes to multiple services.
The saga pattern manages distributed transactions by breaking them into a sequence of local transactions, each with a compensating action. In an ML pipeline: Step 1 writes the prediction to the database (compensate: delete). Step 2 publishes the prediction event to Kafka (compensate: publish rollback event). Step 3 updates the feature store with the new data point (compensate: revert feature). If any step fails, the compensating actions execute in reverse order. Use an orchestrator (saga execution coordinator) or choreography (event-driven).
Q5. How would you handle zero-downtime deployment of a new model version?
Use canary deployment: deploy the new model version alongside the current one. Route a small percentage (1-5%) of traffic to the new version. Monitor prediction quality metrics (accuracy, latency, error rate) for the canary. Gradually increase traffic to the new version if metrics are healthy. Use Argo Rollouts or Istio traffic splitting for Kubernetes-native canary deployments. Maintain the ability to instantly rollback by keeping the old model version running.
Q6. What is the difference between horizontal and vertical scaling for model serving? When would you prefer each?
Horizontal scaling adds more model server replicas (more pods). It is preferred for CPU-bound models and when you need to handle more concurrent requests. Vertical scaling adds more resources (GPU, memory) to existing instances. It is preferred for large models that do not fit in a single GPU (model parallelism) or when model loading time makes horizontal scaling slow. In practice, use horizontal scaling for most workloads and vertical scaling for very large models (LLMs).
Q7. How do you implement request-level tracing across an ML inference pipeline with multiple microservices?
Use OpenTelemetry as the instrumentation standard. Inject a trace context (trace ID, span ID) at the API gateway. Each service extracts the context, creates a child span, and propagates the context to downstream calls. Instrument key operations: feature fetching, model inference, post-processing. Send traces to Jaeger or Tempo. Add custom attributes to spans: model name, model version, feature count, prediction value. Use trace-based alerting for latency anomalies.
Q8. Explain the CAP theorem trade-offs in a feature store that serves both training and online inference.
A feature store must be consistent (training and serving use the same features), available (online inference cannot wait), and partition-tolerant. For online serving: prioritize AP with eventual consistency — serve from Redis with best-effort freshness. For training: prioritize CP — use a batch store (S3, BigQuery) with strict point-in-time correctness. Bridge the gap with a dual-write architecture: features are written to both the online store (Redis) and the offline store (S3) with reconciliation checks.
Q9. How do you prevent cascading failures in a microservice architecture?
Implement bulkheads to isolate failure domains (separate thread pools per downstream service). Use circuit breakers with configurable thresholds. Implement timeouts on all external calls (no unbounded waits). Use retry with exponential backoff and jitter. Limit request queues with bounded buffers (reject excess load instead of queuing indefinitely). Health check endpoints that downstream services can probe. Load shedding at the API gateway when the system is overloaded.
Q10. Describe your approach to API versioning for ML model endpoints.
Use URL-based versioning (e.g., /v1/predict, /v2/predict) for breaking changes. Use header-based versioning for minor changes. The model router maps API versions to model versions. Maintain backward compatibility: v1 callers should continue working when v2 is deployed. Deprecation policy: announce 3 months before removing an API version. Include version information in all responses for debugging. Run both versions simultaneously during the migration period.
3.2 ML Serving (Questions 11-15)
Q11. Compare TensorFlow Serving, Triton Inference Server, and TorchServe. When would you use each?
TensorFlow Serving is optimized for TF models, has mature gRPC API, and supports model versioning out of the box. Use it for TF-only shops. Triton Inference Server supports multiple frameworks (TF, PyTorch, ONNX, TensorRT), offers dynamic batching, and has the best GPU utilization. Use it for multi-framework environments or when GPU efficiency matters most. TorchServe is the native serving solution for PyTorch models, integrates with TorchScript, and has the simplest setup. Use it for PyTorch-only workloads with simpler requirements.
Q12. What is dynamic batching and why is it important for GPU-based inference?
Dynamic batching groups multiple individual inference requests into a single batch before sending to the GPU. GPUs are optimized for parallel computation and process a batch of 32 almost as fast as a single input. Without batching, GPU utilization can be as low as 5-10%. Dynamic batching adds a small delay (configurable, typically 100-500 microseconds) to wait for more requests before executing. The trade-off is slightly higher latency per request but dramatically higher throughput.
Q13. Explain the ONNX format and when you would use it.
ONNX (Open Neural Network Exchange) is a cross-framework model format. A model trained in PyTorch can be exported to ONNX and served by TF Serving, Triton, or ONNX Runtime. Benefits: framework-agnostic serving, optimizations through ONNX Runtime (graph optimization, quantization), deployment flexibility. Use ONNX when you want to decouple training framework choice from serving infrastructure, or when ONNX Runtime provides better performance than native serving.
Q14. How do you implement A/B testing for ML models in production?
At the infrastructure level: deploy both model versions as separate endpoints. The model router splits traffic based on a hash of the user ID (ensures consistent treatment per user). Log predictions with model version, user ID, and timestamp. On the analytics side: define success metrics (click-through rate, conversion, fraud detection rate), calculate statistical significance, and monitor guardrail metrics (latency, error rate). Use Bayesian or frequentist methods depending on traffic volume. Minimum experiment duration: 1-2 weeks for most ML models.
Q15. What is model warm-up and why does it matter?
Model warm-up is the process of sending dummy inference requests to a newly deployed model before routing real traffic. Without warm-up, the first real requests experience high latency because: the model must be loaded from disk to memory (or GPU), TensorFlow and PyTorch perform lazy initialization (compilation on first call), caches (CPU cache, GPU cache) are cold. Warm-up eliminates this cold-start penalty. In Triton, use the --load-model flag. In TF Serving, configure warm-up data in the model directory.
3.3 Kubernetes and Infrastructure (Questions 16-20)
Q16. How do you manage GPU resources on Kubernetes for model serving?
Install the NVIDIA device plugin for K8s. Define GPU resource requests and limits in pod specs. Use node selectors and tolerations to schedule GPU workloads on GPU nodes. Implement MIG (Multi-Instance GPU) for sharing a single A100 across multiple pods. Monitor GPU utilization, memory, and temperature with DCGM-exporter and Prometheus. Set up cluster autoscaler with GPU node pools that scale based on pending GPU pod requests.
Q17. Compare StatefulSet and Deployment for model serving. When would you use each?
Use Deployments for stateless model servers where any replica can handle any request (most common). Use StatefulSets when models require persistent local storage for model caching (each pod maintains its own model cache), when you need stable network identities for model ensembles, or when model sharding requires deterministic pod-to-shard mapping. In practice, most model serving workloads use Deployments with external storage (S3, PVC) for model artifacts.
Q18. How would you implement canary deployment for a model on Kubernetes?
Option 1 — Istio traffic splitting: Deploy new model version as a separate deployment. Create Istio VirtualService rules that split traffic by percentage. Gradually shift traffic from stable to canary. Option 2 — Argo Rollouts: Define a Rollout resource with canary strategy. Configure analysis templates that check model metrics. Auto-promote or rollback based on analysis results. Option 3 — KServe canary: Use KServe InferenceService with canaryTrafficPercent field. Both options integrate with Prometheus metrics for automated decision-making.
Q19. How do you handle model artifact storage and versioning on Kubernetes?
Use an object store (S3, GCS, MinIO) as the source of truth for model artifacts. Each model version is stored at a unique path: s3://models/model-name/version/. Use a model registry (MLflow Model Registry, custom) to track metadata: version, metrics, training run ID, approval status. For K8s: use init containers that download the model from object storage on pod startup, or mount object storage directly using CSI drivers. Cache frequently used models on local NVMe storage for faster loading.
Q20. Explain how you would set up a service mesh for ML microservices.
Install Istio with sidecar injection. Configure mTLS for all service-to-service communication (zero-trust security). Use VirtualService for traffic routing (canary, A/B testing). Use DestinationRule for circuit breaking and outlier detection. Enable Envoy access logging for request-level visibility. Set up Kiali for service mesh visualization. Configure rate limiting at the mesh level. Use PeerAuthentication for strict mTLS enforcement. Monitor mesh health with Istio metrics exported to Prometheus.
3.4 Distributed Systems (Questions 21-25)
Q21. How do you ensure exactly-once processing in an ML prediction pipeline that spans multiple services?
True exactly-once is very hard across services. Instead, aim for effectively-once by making each service idempotent. Use a unique request ID generated at the gateway and propagated through all services. Each service checks if it has already processed this request ID (idempotency key in Redis or database). For Kafka-based pipelines, use Kafka transactions. For database writes, use upserts with the request ID as the unique constraint. Log and reconcile to catch edge cases.
Q22. How would you design a feature computation pipeline that serves both real-time and batch consumers?
Use the Lambda or Kappa architecture. Lambda: a speed layer (Flink) computes real-time features from streaming data, while a batch layer (Spark) recomputes features periodically for accuracy. Kappa: use only Flink with a reprocessing capability. In both cases, write features to a dual-store: Redis for online serving, S3/BigQuery for offline training. Ensure schema consistency between real-time and batch features with a shared feature definition registry. Run automated consistency checks between the two stores.
Q23. Explain the challenges of distributed logging in a microservice architecture and your solution.
Challenges: log volume (millions of log lines per minute), correlation across services, log format inconsistency, and storage cost. Solution: structured logging in JSON format with mandatory fields (trace ID, service name, timestamp, severity). Ship logs via Fluent Bit sidecar to Kafka, then to OpenSearch or Loki. Implement sampling for verbose logs (log 10% of debug-level requests). Use log-based alerting for error patterns. Set retention policies: 7 days hot, 30 days warm, 90 days cold in S3.
Q24. How do you handle clock skew in distributed tracing across services?
Use NTP synchronization on all nodes with tight drift thresholds (less than 1ms). In trace collection, use the Jaeger/Tempo agent-side timestamp adjustment. For causality ordering when clocks disagree, use Lamport timestamps or vector clocks. In practice, ensure all K8s nodes use the same NTP server pool, configure chrony with tight maxdist settings, and alert when clock drift exceeds 5ms. OpenTelemetry SDKs handle most cases by using monotonic clocks for duration measurement.
Q25. How would you design a rate limiter for an ML prediction API?
Use a token bucket or sliding window algorithm. Implement at two levels: per-client rate limiting at the API gateway (Kong, Envoy) using Redis for shared state, and per-model rate limiting at the model router to prevent a single model from consuming all resources. Configuration: allow burst traffic (bucket capacity), set sustained rate per client, and implement priority queues for critical requests (fraud detection gets higher priority than recommendations). Return 429 status with Retry-After header when limits are exceeded.
3.5 Python, Kotlin, and Go (Questions 26-30)
Q26. Compare Python's asyncio, Kotlin coroutines, and Go goroutines for concurrent ML serving.
Python asyncio is single-threaded with cooperative multitasking. Good for I/O-bound work (waiting for model server responses) but limited by the GIL for CPU-bound work. Use with uvicorn/FastAPI for async inference endpoints. Kotlin coroutines are lightweight threads on the JVM with structured concurrency. Good for Spring Boot services that need to call multiple downstream services concurrently. Go goroutines are lightweight green threads with preemptive scheduling. Excellent for high-concurrency infrastructure services (feature servers, API gateways) with minimal overhead.
Q27. How would you implement a connection pool for gRPC model server calls in Kotlin?
Use a channel pool that maintains multiple gRPC channels to the model server. Each channel can multiplex multiple requests. Configure max connections per endpoint, idle timeout, and health checking:
class GrpcChannelPool(
private val endpoint: String,
private val poolSize: Int = 10
) {
private val channels = ConcurrentLinkedQueue<ManagedChannel>()
init {
repeat(poolSize) {
channels.offer(
ManagedChannelBuilder
.forTarget(endpoint)
.usePlaintext()
.keepAliveTime(30, TimeUnit.SECONDS)
.maxInboundMessageSize(16 * 1024 * 1024)
.build()
)
}
}
fun getChannel(): ManagedChannel {
return channels.poll() ?: createNewChannel()
}
fun returnChannel(channel: ManagedChannel) {
if (channel.getState(false) != ConnectivityState.SHUTDOWN) {
channels.offer(channel)
}
}
}
Q28. Write a Go middleware that implements request logging and latency tracking for an ML API.
func MetricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
traceID = uuid.New().String()
}
// Wrap response writer to capture status code
wrapped := &statusResponseWriter{ResponseWriter: w, status: 200}
next.ServeHTTP(wrapped, r)
duration := time.Since(start)
// Record metrics
httpRequestsTotal.WithLabelValues(
r.Method, r.URL.Path,
strconv.Itoa(wrapped.status),
).Inc()
httpRequestDuration.WithLabelValues(
r.Method, r.URL.Path,
).Observe(duration.Seconds())
// Structured log
log.Info().
Str("trace_id", traceID).
Str("method", r.Method).
Str("path", r.URL.Path).
Int("status", wrapped.status).
Dur("latency", duration).
Msg("request completed")
})
}
Q29. How would you structure a Python ML serving application for testability?
Separate concerns into layers: API layer (FastAPI routes), service layer (business logic, feature assembly), model layer (model loading, inference). Use dependency injection for all external dependencies (model client, feature store, database). Create interfaces (Protocol classes in Python) for model inference so you can mock them in tests. Test each layer independently: unit tests for business logic, integration tests with a test model server, end-to-end tests with the full pipeline. Use pytest fixtures for common test setup.
Q30. When would you use Python vs Kotlin vs Go for an ML platform service?
Python: model serving wrappers, feature engineering pipelines, data validation, any service that needs tight integration with ML libraries (scikit-learn, pandas, TensorFlow). Kotlin: API gateways, orchestration services, anything that benefits from the JVM ecosystem (Spring Boot, Kafka client, JDBC), services that need to integrate with existing Java infrastructure. Go: high-performance stateless services (feature servers, request routers), CLI tools, Kubernetes operators, any service where startup time and memory footprint matter. The key principle: use the right language for the job, not the language you are most comfortable with.
4. Six-Month Study Roadmap
Month 1: Backend Fundamentals
Week 1-2: Server Architecture
- Study clean architecture and hexagonal architecture patterns
- Build a REST API in Kotlin with Spring Boot (CRUD, validation, error handling)
- Implement the same API in Go with Gin or Echo framework
- Compare performance characteristics using load testing with k6
Week 3-4: Concurrency and Performance
- Study Kotlin coroutines with the official guide and Kotlin in Action
- Implement a concurrent HTTP client in Go using goroutines and channels
- Build a Python async API with FastAPI and asyncio
- Profile all three implementations: memory usage, latency under load, CPU utilization
Month 2: Kubernetes Deep Dive
Week 1-2: Core K8s Concepts
- Complete the CKA (Certified Kubernetes Administrator) curriculum
- Deploy a multi-service application on Minikube or kind
- Implement Deployments, Services, ConfigMaps, Secrets, and PVCs
- Practice with resource requests, limits, and QoS classes
Week 3-4: Advanced K8s for ML
- Set up GPU scheduling with NVIDIA device plugin (on cloud or local GPU)
- Configure HPA with custom metrics from Prometheus
- Implement canary deployments with Argo Rollouts
- Set up Istio service mesh with traffic splitting
Month 3: ML Serving Fundamentals
Week 1-2: Model Serving Frameworks
- Deploy TensorFlow Serving with a sample model
- Deploy Triton Inference Server and test with ONNX model
- Compare latency and throughput between the two
- Implement dynamic batching and measure the throughput improvement
Week 3-4: KServe and Model Management
- Install KServe on a Kubernetes cluster
- Deploy a model with InferenceService
- Implement canary deployment for a model version upgrade
- Set up model monitoring with Prometheus metrics
Month 4: Distributed Systems
Week 1-2: Theory and Patterns
- Read "Designing Data-Intensive Applications" chapters 5, 8, 9
- Study microservice patterns: circuit breaker, bulkhead, saga, CQRS
- Implement a circuit breaker in Kotlin using Resilience4j
- Practice distributed systems design questions
Week 3-4: Observability
- Set up OpenTelemetry instrumentation in a multi-service application
- Deploy Jaeger or Tempo for distributed tracing
- Set up Prometheus + Grafana for metrics collection and dashboards
- Implement structured logging with correlation IDs across services
Month 5: Feature Stores and Data Pipelines
Week 1-2: Feature Store
- Deploy Feast locally and define feature views
- Implement online feature serving with Redis backend
- Build a feature computation pipeline with Python
- Test point-in-time correctness for training data
Week 3-4: End-to-End ML Pipeline
- Build a complete ML serving pipeline: feature store to model server to API
- Implement A/B testing infrastructure with traffic splitting
- Add model monitoring (data drift, prediction distribution)
- Set up automated model retraining triggers
Month 6: Integration and Interview Prep
Week 1-2: Portfolio Project
- Build a production-quality ML serving platform with all components
- Document the architecture with diagrams and decision records
- Create a monitoring dashboard with key ML metrics
- Write load tests that validate SLA requirements
Week 3-4: Interview Preparation
- Practice all 30 interview questions with a partner
- Do 5 system design mock interviews focused on ML infrastructure
- Review and refine your resume with quantifiable metrics
- Prepare a 5-minute architecture walkthrough of your portfolio project
- Study Toss Bank tech blog posts for culture and technical context
5. Resume Strategy
What Toss Bank Wants to See
Your resume should prove that you can build and operate ML infrastructure at scale. Frame everything in terms of impact.
Lead with Scale Metrics
- "Designed and operated ML serving infrastructure handling 100K predictions per second with p99 latency under 8ms"
- "Reduced model deployment time from 2 days to 15 minutes by building a KServe-based CI/CD pipeline"
- "Built a feature store serving 50M features per day with sub-millisecond latency using Redis cluster"
Show Polyglot Experience
- List primary language first with depth indicators ("5 years Python, production ML systems")
- Include secondary languages with context ("Kotlin for Spring Boot microservices", "Go for high-performance tools")
- Highlight language selection decisions: "Chose Go for the feature server due to low latency requirements, reducing p99 from 5ms to 0.8ms"
Demonstrate Operational Maturity
- On-call experience for ML systems (model failures, data drift incidents)
- Cost optimization: GPU utilization improvements, right-sizing model serving infrastructure
- Zero-downtime deployments and migration stories
- Monitoring and alerting setup for ML-specific metrics
Resume Format
- 2 pages maximum
- "Technical Skills" section that maps directly to the JD keywords
- XYZ format for achievements: "Accomplished X by implementing Y, resulting in Z"
- Include links to relevant open-source contributions or public talks
- GitHub profile with at least one relevant project
6. Portfolio Project Ideas
Project 1: Full-Stack ML Serving Platform
Build a complete ML serving platform that demonstrates all the JD requirements:
- Model Server: Triton Inference Server on Kubernetes with GPU support
- API Gateway: Kotlin Spring Boot with gRPC model client
- Feature Store: Redis-backed feature serving with Python feature pipelines
- Monitoring: Prometheus + Grafana dashboards with model-specific metrics
- Canary Deployments: Argo Rollouts with automated analysis
- Distributed Tracing: OpenTelemetry across all services
Project 2: Real-Time Fraud Detection System
Build an end-to-end fraud detection system:
- Kotlin API that accepts transaction events
- Go feature server that computes real-time features
- Python model serving with ensemble of models
- A/B testing infrastructure for model comparison
- Circuit breaker with fallback to rule-based detection
- Dashboard showing detection rate, false positive rate, latency
Project 3: Model Deployment Pipeline
Build a CI/CD pipeline for ML models:
- MLflow for model tracking and registry
- KServe for model serving with canary support
- Automated model validation (schema check, performance benchmark)
- Shadow mode deployment for new models
- Automated rollback on metric degradation
- Slack/webhook notifications for deployment events
Project 4: Kubernetes Operator for ML Workloads
Build a custom K8s operator in Go:
- Custom Resource Definition for MLModel resources
- Controller that manages model serving deployments
- Automatic scaling based on inference metrics
- Model artifact syncing from S3
- Health checking and auto-recovery for model pods
- Integration with Prometheus for metrics
7. Quiz: Test Your Knowledge
Q1: Your ML serving system handles 10K requests per second. You deploy a new model version that increases inference latency from 5ms to 50ms. What happens to the system and how do you respond?
With 10K RPS and 50ms latency per request, the system needs 500 concurrent connections (up from 50). This can exhaust thread pools, connection limits, and downstream capacity, causing cascading timeouts. Immediate response: roll back the canary deployment to the old model version. Then investigate: Is the model larger (more GPU memory needed)? Is it not optimized (needs TensorRT optimization or quantization)? Is it making extra network calls? Fix the latency issue before redeploying. Always have automated canary analysis that catches latency regressions before they reach 100% traffic.
Q2: You notice that your fraud detection model's precision drops from 95% to 80% over two weeks, but the model itself has not changed. What is happening?
This is data drift or concept drift. Possible causes: (1) Input feature distributions have shifted — new transaction patterns, seasonality, or a business change (new merchant category, promotional event). (2) Upstream data pipeline bug producing incorrect feature values. (3) Feature store stale data (TTL too long, pipeline failure). Diagnosis: check feature drift metrics (PSI for each feature), compare recent vs training data distributions, verify upstream pipeline health. Solution: retrain on recent data, implement automated drift detection alerts, set up a regular retraining schedule.
Q3: You need to serve an LLM (3B parameters, 6GB model) on Kubernetes. The model takes 30 seconds to load. How do you handle scaling and deployment?
Use StatefulSets or Deployments with PVC-backed model storage to avoid downloading the model on each pod start. Pre-pull the model image during off-peak hours. Set minimum replicas high enough to handle baseline traffic (avoid scaling from zero). Configure HPA to scale up aggressively (preemptive) and scale down slowly (stabilization window of 10+ minutes) to avoid cold-start latency. Use pod disruption budgets to ensure rolling updates never take all replicas offline. Consider model warm-up with health checks that only pass after the model has served a test inference.
Q4: Your Kubernetes cluster has 8 A100 GPUs shared by 5 ML model serving teams. How do you manage GPU resources fairly?
Implement resource quotas per namespace (each team gets a namespace). Set GPU limits: e.g., team A gets 2 GPUs, team B gets 2, and 2 are shared. Use NVIDIA MIG to partition A100s into smaller instances for models that do not need a full GPU. Implement priority classes: production models get higher priority than development workloads. Use cluster autoscaler to add GPU nodes when demand exceeds capacity. Monitor GPU utilization per team and reclaim underutilized allocations quarterly. Set up a GPU scheduling dashboard with Grafana.
Q5: You are designing the interface between the feature store and the model server. What protocol and format would you choose, and why?
Use gRPC with Protocol Buffers. gRPC provides lower latency than REST due to HTTP/2 multiplexing and binary serialization. Protocol Buffers are strongly typed, which prevents schema mismatches between the feature store and model server. The schema definition serves as a contract between teams. For the feature tensor format, use a flat buffer or numpy-compatible binary format to avoid deserialization overhead. Cache frequently requested feature sets in the model server's local memory (LRU cache with TTL). For batch requests, support streaming gRPC to avoid large single-message overhead.
8. References and Resources
Books
- Martin Kleppmann — Designing Data-Intensive Applications, O'Reilly, 2017
- Sam Newman — Building Microservices, 2nd Edition, O'Reilly, 2021
- Chip Huyen — Designing Machine Learning Systems, O'Reilly, 2022
- Chris Richardson — Microservices Patterns, Manning, 2018
Official Documentation
- Kubernetes Documentation — https://kubernetes.io/docs/
- NVIDIA Triton Inference Server — https://github.com/triton-inference-server/server
- KServe Documentation — https://kserve.github.io/website/
- Feast Feature Store — https://feast.dev/
- OpenTelemetry Documentation — https://opentelemetry.io/docs/
Courses and Tutorials
- Made With ML — MLOps course — https://madewithml.com/
- Full Stack Deep Learning — https://fullstackdeeplearning.com/
- Google ML Engineering Best Practices — https://developers.google.com/machine-learning/guides/rules-of-ml
- CKA Exam Preparation — https://kubernetes.io/docs/reference/kubectl/
Community and Talks
- MLOps Community — https://mlops.community/
- KubeCon + CloudNativeCon recordings — https://www.cncf.io/kubecon/
- Chip Huyen's blog — https://huyenchip.com/blog/
- Toss Tech Blog — https://toss.tech/
- Netflix Tech Blog — https://netflixtechblog.com/
Conclusion
The Toss Bank ML Backend Engineer role demands a rare combination of strong backend engineering fundamentals, practical ML serving expertise, and Kubernetes operational skills. You are not being hired to train models — you are being hired to build the reliable, scalable, observable systems that serve those models to millions of banking customers.
The six-month roadmap focuses on building depth in backend architecture first, then layering on ML-specific infrastructure knowledge. This sequence matters: a solid backend engineer who learns ML serving will outperform an ML specialist who struggles with distributed systems.
Your portfolio should demonstrate three things: you can build high-performance systems in multiple languages, you can deploy and operate ML models on Kubernetes, and you understand the operational realities of running ML in production (monitoring, scaling, incident response).
The demand for ML Backend Engineers will only increase as financial services companies integrate more AI into their core operations. Toss Bank is at the forefront of this trend in Korea. Prepare thoroughly, build real projects, and demonstrate that you can operate at the intersection of backend engineering and machine learning.
Start building. The models are waiting to be served.