Spring Boot + Flink 기반 실시간 추천시스템 Flink로 구축한 추천 엔진 비용과 효율을 동시에

궁금하면 500원·2025년 2월 25일
0

미생의 개발 이야기

목록 보기
29/58

들어가며: 실시간 추천 시스템, 이론과 현실 사이의 간극

추천 시스템은 오늘날 디지털 서비스의 핵심 경쟁력입니다. Netflix, Amazon, Spotify 같은 거대 기업들이 막대한 자본과 인력을 투입해 구축한 복잡한 시스템들은 이 분야의 이상적인 모델을 제시합니다. 하지만 대부분의 조직, 특히 스타트업이나 중소기업의 현실은 다릅니다. 이론적으로 완벽한 시스템을 구축하기에는 여러 가지 제약과 한계가 존재하기 때문이죠.

8년차 개발자로 다양한 조직을 경험하며 깨달은 것은, "이론적으로 완벽한 시스템보다 운영 가능한 시스템이 더 가치있다"는 점입니다.
이 회고록은 복잡한 AI/ML 기술보다는 '현실적인 선택''점진적 개선'에 초점을 맞춰 Spring Boot와 Apache Flink를 활용해 실시간 추천 시스템을 구축한 과정을 담고 있습니다.


왜 Spring Boot와 Flink였는가?

1. Spring Boot: 팀의 익숙함을 무기로

추천 시스템 개발의 첫 번째 관문은 사용자 행동 데이터를 수집하는 것입니다. 이 과정에서 우리는 팀원 모두가 가장 익숙하고, 안정성이 검증된 Spring Boot를 선택했습니다.

@RestController
@RequestMapping("/api/v1/behavior")
@RequiredArgsConstructor
public class UserBehaviorController {

    private final UserBehaviorService behaviorService;

    @PostMapping("/track")
    public ResponseEntity<String> trackUserBehavior(@RequestBody UserBehaviorEvent event) {
        // 이벤트 유효성 검증
        if (event == null || event.getUserId() == null || event.getActionType() == null) {
            return ResponseEntity.badRequest().body("Invalid event data");
        }

        behaviorService.sendBehaviorEvent(event);
        return ResponseEntity.accepted().body("Event tracked successfully");
    }
}
  • 팀 기술 스택과의 시너지: 새로운 기술 스택을 도입하는 데 드는 학습 비용과 리스크를 최소화했습니다. Spring Boot의 풍부한 생태계(Security, Data JPA, Kafka 등)는 데이터 수집부터 API 개발까지 전반적인 시스템 구축을 빠르게 진행할 수 있게 했습니다.
  • 운영 안정성: 오랜 기간 검증된 Spring의 안정성은 프로덕션 환경에서의 예상치 못한 문제를 줄여주며, 팀이 오직 비즈니스 로직에만 집중할 수 있는 환경을 제공했습니다.

기존 배치 처리 방식의 한계(신규 사용자 콜드 스타트, 실시간 반영 불가 등)를 극복하기 위해 실시간 스트리밍 처리가 필요했습니다. 다양한 선택지 중 Apache Flink를 최종적으로 채택했습니다.

public DataStream<UserPreferenceUpdate> buildRealtimeRecommendationStream(DataStream<HomestayBehaviorEvent> behaviorStream) {
    // 1분 텀블링 윈도우를 사용해 사용자 행동 집계 및 선호도 업데이트
    return behaviorStream
            .filter(event -> event.getActionType() != ActionType.VIEW) // 단순 VIEW는 필터링
            .keyBy(HomestayBehaviorEvent::getUserId)
            .window(TumblingEventTimeWindows.of(Time.minutes(1)))
            .process(new PreferenceUpdateProcessor()) // 선호도 점수 계산 및 업데이트 로직
            .uid("realtime-preference-processor");
}
  • Exactly-once 처리 보장: Flink의 강점인 정확히 한 번 처리 보장은 데이터 유실이나 중복 처리 없이 신뢰성 높은 추천 로직을 구현하는 데 필수적이었습니다.
  • 유연한 윈도우 연산: 사용자 행동 데이터는 연속된 스트림으로 들어오기 때문에, 특정 시간 단위로 데이터를 집계하는 윈도우 연산은 필수적입니다. Flink는 다양한 윈도우 연산 기능을 제공하여 복잡한 로직을 쉽게 구현할 수 있게 했습니다.
  • Kafka와의 뛰어난 호환성: 이벤트 소싱 패턴을 구현하기 위해 Kafka를 사용했고, Flink는 Kafka와의 연동이 매끄러워 파이프라인 구축이 용이했습니다.

실전에서 마주한 현실적인 문제와 해결책

1. 콜드 스타트와 데이터 희소성 문제

문제: 신규 사용자에게는 행동 데이터가 없어 개인화된 추천이 불가능합니다.

해결책: "완벽한 개인화"보다 "빠르고 나쁘지 않은" 추천을 제공하는 데 집중했습니다.

public List<Property> getRecommendations(String userId) {
    // 1. 사용자 프로필 및 최근 행동 데이터 조회
    UserContext userContext = userProfileRepository.findById(userId)
            .orElseGet(UserContext::new);

    // 2. 신규 사용자 또는 행동 데이터가 부족한 경우
    if (userContext.isColdStartUser()) {
        // 지역별/테마별 인기 숙소를 추천
        return propertyRepository.findTopByLocationAndTheme(userContext.getPreferredLocation(), userContext.getPreferredTheme(), 20);
    }

    // 3. 행동 데이터를 기반으로 개인화된 추천
    return collaborativeFilteringEngine.recommend(userContext);
}
  • 인기 아이템 추천: 신규 사용자에게는 전반적인 인기 아이템이나 지역별/카테고리별 베스트셀러를 추천했습니다.
  • 단계적 접근: 초기에는 단순한 추천 로직으로 시작하고, 사용자의 행동 데이터가 쌓이면서 점진적으로 복잡한 개인화 모델을 적용했습니다.

2. 실시간 처리 비용과 효율성 문제

문제: 모든 사용자 행동 이벤트를 실시간으로 처리하는 것은 막대한 컴퓨팅 비용을 초래합니다.

해결책: 비즈니스 임팩트가 큰 **"고가치 이벤트"**만 선별적으로 실시간 처리했습니다.

// 모든 이벤트를 실시간 처리할 필요는 없다
DataStream<HomestayBehaviorEvent> filteredStream = behaviorStream
    .filter(event -> event.getActionType() == ActionType.BOOK || event.getActionType() == ActionType.FAVORITE)
    .name("high-value-event-filter");
  • 이벤트 필터링: '상품 조회(VIEW)' 같은 빈번한 이벤트는 배치 처리에 맡기고, '구매(BOOK)'나 '장바구니 담기(ADD_TO_CART)' 같은 중요한 이벤트만 실시간으로 처리했습니다.
  • 윈도우 크기 최적화: '바로 구매' 같은 즉각적인 반응이 중요한 로직에는 짧은 윈도우를, 장기적인 선호도 파악에는 긴 윈도우를 사용하는 등 상황에 맞는 유연한 접근을 시도했습니다.

결론: 기술은 비즈니스 문제를 해결하는 도구일 뿐

이 프로젝트를 통해 8년차 개발자로서의 핵심 가치관을 다시 한번 확인했습니다.

  • "기술은 목적이 아니라 수단이다.": 최신 기술이나 복잡한 알고리즘을 무작정 도입하기보다, 현재 팀의 역량과 비즈니스가 처한 문제를 가장 효율적으로 해결할 수 있는 기술 스택을 선택하는 것이 중요합니다.
  • "완벽보다 가치가 있는 것은 '작동'이다.": 이론적으로 완벽한 시스템을 만들기 위해 시간을 낭비하기보다, 빠르게 프로토타입을 만들어 시장과 사용자에게 피드백을 받는 것이 훨씬 더 중요합니다.
  • "모니터링과 디버깅에 투자하라.": 분산 시스템에서 예상치 못한 문제는 언제든 발생할 수 있습니다. 코드를 짜는 시간만큼, 시스템의 상태를 한눈에 파악하고 문제를 신속하게 해결할 수 있는 모니터링 체계를 구축하는 데 투자해야 합니다.

이 포트폴리오 프로젝트는 단순히 기술적인 성취를 보여주는 것을 넘어, 현업의 냉혹한 현실 속에서 제약 조건을 극복하고 문제를 해결해 나간 과정을 담고 있습니다. 사용자에게 가치를 제공하고, 비즈니스 성장에 기여하는 것이야말로 개발자의 궁극적인 목표임을 다시 한번 깨달았습니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글