Projects/[Spring] Coffee Shop Project

[트러블슈팅] soft delete된 메뉴의 인기 랭킹 처리, 어떻게 할까?

montmer27 2026. 4. 5. 03:15

상황

Redis 인기 메뉴 랭킹은 과거 주문 이력을 기반으로 계산된다. 어제 많이 팔렸던 메뉴가 오늘 soft delete될 수 있는데, 기존 서비스 코드는 deleted 여부를 확인하지 않아 삭제된 메뉴가 랭킹에 포함되는 문제가 있었다.

선택지 비교

첫 번째 선택지는 예외를 던지는 방법이다. 삭제된 메뉴를 발견하면 MENU_ALREADY_DELETED 예외를 발생시키는 것이다. 하지만 삭제된 메뉴 하나 때문에 전체 랭킹 조회가 실패하는 건 부적절하다. 랭킹 API는 단일 메뉴의 상태 때문에 전체 응답을 실패시키면 안 된다.

두 번째 선택지는 삭제된 메뉴를 건너뛰는 방법이다. 살아있는 메뉴만 연속 순위로 반환하는 것이다.

나의 선택

삭제된 메뉴는 조용히 건너뛰고, 나머지 메뉴의 순위를 연속으로 부여하는 방식을 선택했다. 이때 rank를 단순히 i + 1로 계산하면 삭제된 메뉴가 있을 때 순위에 공백이 생긴다. 별도 카운터로 rank를 관리해서 순위 공백을 방지했다.

// Before: i + 1이 rank → 삭제된 메뉴가 있으면 rank 공백 발생
for (int i = 0; i < menuIdList.size(); i++) {
    Menu menu = menuRepository.findById(Long.parseLong(menuIdList.get(i)))
            .orElseThrow(() -> new BusinessException(ErrorCode.MENU_NOT_FOUND));
    result.add(MenuRankingResponse.of(i + 1, menu));
}

// After: 삭제된 메뉴 skip, 연속 rank 유지
int rank = 1;
for (String menuId : menuIdList) {
    Menu menu = menuRepository.findById(Long.parseLong(menuId))
            .orElseThrow(() -> new BusinessException(ErrorCode.MENU_NOT_FOUND));
    if (menu.isDeleted()) {
        continue;
    }
    result.add(MenuRankingResponse.of(rank++, menu));
}

예를 들어 1위 메뉴가 삭제된 경우 기존 방식은 2위, 3위가 그대로 표시되지만, 개선된 방식은 다음 메뉴가 1위로 올라오고 순위가 연속으로 유지된다.

인사이트

랭킹처럼 여러 항목을 묶어서 반환하는 API는 개별 항목의 상태 때문에 전체 응답을 실패시키는 것보다, 문제가 있는 항목을 건너뛰고 나머지를 정상 반환하는 방어적 설계가 더 적절하다. 단, 이 판단은 도메인에 따라 다를 수 있다. 금융이나 결제처럼 정확성이 중요한 도메인에서는 일부 항목에 문제가 생겼을 때 오히려 전체를 실패시키는 게 맞을 수 있다.