Skip to content

Conversation

@chaeyuuu
Copy link
Collaborator

@chaeyuuu chaeyuuu commented Dec 25, 2025

🔥Pull requests

closed #10

👷 과제 구현

필수과제

  • 앱잼 신청!!
  • 기획 경선과 밋업에 열정적으로 참여하기!!
  • 댓글 기능 추가. 댓글을 작성, 수정, 삭제, 조회할 수 있어야 합니다.

선택과제

  • 캐싱으로 성능 최적화
  • 대용량 데이터 처리 및 정렬 최적화
  • 인프라 설계 시각화
  • 테스트 코드 도입

구현한 내용에 대해서 설명해주세요

  • 댓글 기능

    • 댓글 작성 및 조회 API: 댓글은 특정 게시물에 종속되기에 작성 및 조회 API는 ArticleController 하위에 배치하였습니다.
    • 댓글 수정 및 삭제 API: 개별 댓글의 권한이 중요한 수정 및 삭제 API는 CommentController 내부로 분리하였습니다.
    • 댓글 삭제 API (deleteComment) 에서는 소프트 딜리트 방식을 사용했습니다. '@SQLRestriction' 어노테이션을 활용해서 삭제 처리되지 않은 댓글만 조회되도록 하였습니다.
    • 댓글 조회 시 연관된 Member 정보를 가져올 때 발생하는 N+1 문제 해결을 위해서 JOIN FETCH를 적용했습니다.
  • 게시물 DTO 분리

    • 전체 게시물 조회 시 댓글 내용이 포함되지 않아 전체 게시물 조회 DTO와 게시물 상세 조회 DTO를 분리하였습니다.
  • 게시물 검색 API

    • 이전 과제였던 게시물 검색 API를 이번 과제에서 구현하였습니다.
    • 키워드(제목, 작성자 이름)을 기준으로 하는 검색 기능을 구현하였습니다.
    • 대용량 데이터를 효율적으로 처리하기 위해 Pageable을 도입하였습니다.
    • 'countQuery'를 명시적으로 분리하여 Fetch Join시 발생할 수 있는 카운트 쿼리 성능 저하를 방지했습니다.
  • Redis 캐시 도입

    • 조회 빈도가 높은 게시물 전체 조회 및 상세 조회 API에 Redis 캐시를 적용하였습니다.
    • 새로운 게시물을 작성하는 API에 '@CacheEvict' 어노테이션을 통해 게시물 전체 목록 조회 API 캐시를 무효화했습니다.
    • 댓글 수정, 삭제 시에는 명시적으로 캐시를 무효화도록 했습니다.
    • 테스트 코드를 통해 첫 조회 시에는 DB에 접근하고 이후에는 Redis에 접근하는 것을 확인했습니다.
    • 또, 새로운 게시글을 생성하는 경우에는 게시물 전체 조회 API 캐시가 무효화되어 DB에 올바른 횟수만큼 접근하는 것을 확인했습니다.
  • 대용량 데이터 처리

    • 대용량 처리를 위해서 인덱스를 도입하였습니다.
    • Article에는 'created_at' 칼럼에 인덱스를 도입하였고, Comment에는 'article_id'로 게시글 별 댓글 조회를 최적화하도록 하였습니다.
  • 테스트 코드 작성

    • member, article 관련해서만 작성했습니다.

구현하며 고민했던 내용을 적어주세요 (사소한 것도 좋아요)

  • 캐시 무효화 전략 (@CacheEvict vs 수동 호출)
    : 댓글 수정 및 삭제 파라미터에는 articleId가 별도로 없어서 캐시를 어떻게 무효화할지 고민하였습니다.

    • 댓글 수정과 삭제 시에는 수동 호출로 캐시 무효화 전략을 수행하였습니다.
    • 댓글 수정과 삭제에는 파라미터에 articleId가 없어서 commentId로 캐시 키를 설정할 수 없는 문제가 있었습니다. 즉, 로직을 수행해야 삭제할 캐시의 키를 알 수 있는 상황이였스빈다.
    • 이를 해결하기 위해 아래와 같은 메서드를 내부에 구현하여 update와 delete 시에 수동으로 호출하도록 하였습니다.
      private void evictArticleCache(Long articleId) {
      Cache cache = cacheManager.getCache("article");
      if (cache != null) {
      cache.evict(articleId);
      }
      }
  • Redis vs Caffeine
    : Redis와 Caffeine 둘 중 어느 것을 사용할지 고민하였습니다.

    • Redis 특징
      • 별도의 외부 캐시 서버
      • 모든 서버 인스터스가 동일한 데이터 공유 가능
    • Caffeine 특징
      • 인메모리 캐시 라이브러리
      • 서버마다 캐시를 따로 가짐 -> 서버 간 데이터 공유 불가

    이번 프로젝트에서는 실제 서비스 운영 환경에서는 여러 대의 서버를 사용하는 것을 고려하여 Redis를 선택했습니다. 만약 서버가 단일 인스턴스이고 이 과제 하나로 끝난다면 Caffeine을 도입했을 것 같은데, 요구사항에서도 Redis를 도입하라고 되어있어서 선택하였습니다.

  • Redis 테스트 코드 작성 시 발생한 문제

    • Redis를 사용해 테스트를 해보던 중 'MismatchedInputException' 오류가 발생하였습니다.
      : 기존에 ArticleListResponseDto는 record 형식으로 구현되어있었습니다. record는 빈 객체라는 개념이 없기에 Jackson에서 올바르게 읽지 못하는 문제가 발생했습니다. 이를 위해 class + @NoArgsConstructor 로 변경하였습니다.
  • 테스트 코드 작성

    • 테스트 코드를 작성하면서 실제 비지니스 오류를 발견할 수 있었습니다. 기존 validateAge에서 나이가 어려서 발생한 예외가 try-catch 구문에 걸려서 날짜 형식이 잘못되었다는 오류 메세지로 나가고 있었습니다.
    • 날짜 파싱과 나이 계산 로직을 분리하여 오류를 수정할 수 있었습니다.
      시간 상 모든 테스트 코드 로직을 작성하지 못했지만 실제 비지니스 오류를 발견하면서 테스트 코드의 중요성을 느꼈습니다 👀


🚨 참고 사항

마지막 과제!! 고생 많으셨습니다 🤶🏻💚 메리크리스마스

- 상세 조회: articleId를 키로 개별 캐싱
- 전체 목록 조회: 'all' 키를 사용해 전체 목록 캐싱
- @CacheEvict 를 사용해 새 글 작성 시 articleList 캐시 삭제
- ObjectMapper에 날짜 처리 설정 추가
- ArticleListResponseDto record -> dto로 변경
- 목록 조회 시 최초 1회만 DB에 접근하고 이후 캐시 사용하는지
- 게시글 생성 시 기존 목록 캐시 초기화 확인
- validateAge 메서드 내 예외 문제 해결
- 날짜 형식 오류와 나이 제한 오류가 각각 정확한 에러 코드를 던지도록 해결
@chaeyuuu chaeyuuu self-assigned this Dec 25, 2025
@chaeyuuu chaeyuuu linked an issue Dec 25, 2025 that may be closed by this pull request
6 tasks
@chaeyuuu chaeyuuu requested a review from 88guri December 25, 2025 14:40
@chaeyuuu chaeyuuu marked this pull request as ready for review December 25, 2025 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SEMINAR] 7주차 세미나

2 participants