본문으로 건너뛰기

추천 서비스 (Recommendation)

개요

추천 서비스는 사용자 행동 데이터를 기반으로 개인화된 상품 추천을 제공합니다. DocumentDB에 사용자 활동을 저장하고, ElastiCache(Valkey)를 통해 추천 결과를 캐싱하여 빠른 응답 속도를 보장합니다.

항목
언어Python 3.11
프레임워크FastAPI
데이터베이스DocumentDB (MongoDB 호환)
캐시ElastiCache (Valkey)
네임스페이스mall-services
포트8000
헬스체크GET /health

아키텍처

API 엔드포인트

추천 API

메서드경로설명
GET/api/v1/recommendations/{user_id}개인화 추천
GET/api/v1/recommendations/trending트렌딩 상품
GET/api/v1/recommendations/similar/{product_id}유사 상품 추천

요청/응답 예시

개인화 추천

요청:

GET /api/v1/recommendations/user_001?limit=10

응답:

{
"user_id": "user_001",
"recommendations": [
{
"product_id": "prod_101",
"score": 0.95,
"reason": "Based on your browsing history",
"category": "electronics"
},
{
"product_id": "prod_205",
"score": 0.87,
"reason": "Based on your browsing history",
"category": "fashion"
},
{
"product_id": "prod_089",
"score": 0.82,
"reason": "Based on your browsing history",
"category": "electronics"
}
],
"generated_at": "2024-01-15T10:00:00Z"
}

트렌딩 상품

요청:

GET /api/v1/recommendations/trending

응답:

{
"products": [
{
"product_id": "prod_001",
"name": "삼성 갤럭시 S24",
"category": "electronics",
"score": 0.98,
"view_count": 15420,
"purchase_count": 2341
},
{
"product_id": "prod_042",
"name": "나이키 에어맥스",
"category": "fashion",
"score": 0.94,
"view_count": 12890,
"purchase_count": 1876
},
{
"product_id": "prod_078",
"name": "애플 에어팟 프로",
"category": "electronics",
"score": 0.91,
"view_count": 11200,
"purchase_count": 1543
}
],
"generated_at": "2024-01-15T10:00:00Z"
}

유사 상품 추천

요청:

GET /api/v1/recommendations/similar/prod_001?limit=10

응답:

{
"product_id": "prod_001",
"similar": [
{
"product_id": "prod_002",
"score": 0.89,
"reason": "Users who viewed this also viewed",
"category": "electronics"
},
{
"product_id": "prod_015",
"score": 0.76,
"reason": "Users who viewed this also viewed",
"category": "electronics"
},
{
"product_id": "prod_023",
"score": 0.71,
"reason": "Users who viewed this also viewed",
"category": "accessories"
}
],
"generated_at": "2024-01-15T10:00:00Z"
}

데이터 모델

Recommendation

class Recommendation(BaseModel):
product_id: str
score: float = Field(ge=0.0, le=1.0) # 0.0 ~ 1.0
reason: str # 추천 이유
category: Optional[str] = None

UserActivity

class UserActivity(BaseModel):
user_id: str
product_id: str
action: str # view, click, purchase, add_to_cart
timestamp: datetime
metadata: Optional[dict] = None

TrendingProduct

class TrendingProduct(BaseModel):
product_id: str
name: str
category: str
score: float
view_count: int
purchase_count: int

RecommendationResponse

class RecommendationResponse(BaseModel):
user_id: str
recommendations: list[Recommendation]
generated_at: datetime

TrendingResponse

class TrendingResponse(BaseModel):
products: list[TrendingProduct]
generated_at: datetime

SimilarProductsResponse

class SimilarProductsResponse(BaseModel):
product_id: str
similar: list[Recommendation]
generated_at: datetime

추천 알고리즘

행동 가중치

행동가중치설명
purchase1.0구매 완료
add_to_cart0.7장바구니 담기
click0.3상품 클릭
view0.1상품 조회

개인화 추천 로직

def _generate_recommendations(activities: list[dict], limit: int) -> list[Recommendation]:
product_scores: dict[str, float] = {}
action_weights = {
"purchase": 1.0,
"add_to_cart": 0.7,
"click": 0.3,
"view": 0.1
}

for activity in activities:
product_id = activity.get("product_id")
action = activity.get("action", "view")
weight = action_weights.get(action, 0.1)
product_scores[product_id] = product_scores.get(product_id, 0) + weight

sorted_products = sorted(product_scores.items(), key=lambda x: x[1], reverse=True)[:limit]

return [
Recommendation(
product_id=pid,
score=min(score / 10.0, 1.0),
reason="Based on your browsing history"
)
for pid, score in sorted_products
]

캐싱 전략

ElastiCache (Valkey) 키 구조

키 패턴설명TTL
recommendations:{user_id}개인화 추천 결과1시간
recommendations:trending트렌딩 상품 목록1시간
recommendations:similar:{product_id}유사 상품 목록1시간
leaderboard:popular인기 상품 정렬 집합실시간

캐시 로직

CACHE_TTL_SECONDS = 3600  # 1시간

async def get_personalized_recommendations(user_id: str, limit: int = 10):
cache_key = f"recommendations:{user_id}"

# 캐시 확인
cached = await valkey.get_json(cache_key)
if cached:
return RecommendationResponse(**cached)

# 추천 생성
activities = await repo.get_user_activities(user_id)
recommendations = _generate_recommendations(activities, limit)

response = RecommendationResponse(
user_id=user_id,
recommendations=recommendations,
generated_at=datetime.utcnow()
)

# 캐시 저장
await valkey.set_json(cache_key, response.model_dump(mode="json"), CACHE_TTL_SECONDS)

return response

환경 변수

변수명설명기본값
SERVICE_NAME서비스 이름recommendation
PORT서비스 포트8080
AWS_REGIONAWS 리전us-east-1
REGION_ROLE리전 역할 (PRIMARY/SECONDARY)PRIMARY
DB_HOSTDocumentDB 호스트localhost
DB_PORTDocumentDB 포트27017
DB_NAME데이터베이스 이름recommendations
DB_USER데이터베이스 사용자mall
DB_PASSWORD데이터베이스 비밀번호-
DOCUMENTDB_HOSTDocumentDB 호스트localhost
DOCUMENTDB_PORTDocumentDB 포트27017
CACHE_HOSTElastiCache 호스트localhost
CACHE_PORTElastiCache 포트6379
KAFKA_BROKERSKafka 브로커 주소localhost:9092
LOG_LEVEL로그 레벨info

서비스 의존성

의존하는 서비스

  • DocumentDB: 사용자 활동 데이터 저장
  • ElastiCache (Valkey): 추천 결과 캐싱, 인기 상품 리더보드
  • Product Catalog: 상품 메타데이터 조회

의존받는 서비스

  • API Gateway: 홈/상품 페이지에서 추천 표시
  • Search Service: 검색 결과에 개인화 적용

기능 상세

트렌딩 상품 계산

  • 최근 24시간 조회수/구매수 기준
  • ElastiCache Sorted Set으로 실시간 순위 관리
  • 키: leaderboard:popular

유사 상품 추천

  • "이 상품을 본 사람들이 함께 본 상품" 기반
  • 협업 필터링 알고리즘 적용
  • 동일 카테고리 상품 우선

다양성 보장

  • 같은 카테고리 상품이 연속 3개 이상 나오지 않도록 조정
  • 이미 구매한 상품은 제외