1. 프로젝트 개요
모뉴(Monew)는 여러 뉴스 API를 통합하여 사용자에게 맞춤형 뉴스를 제공하고, 의견을 나눌 수 있는 소셜 기능을 갖춘 서비스이다. 더 자세히 말하자면 네이버 뉴스 검색 API와 RSS 피드를 통해 뉴스 기사를 수집하고, 사용자가 등록한 관심사 키워드를 기반으로 관련 기사를 필터링하여 제공한다. 소셜 기능적인 측면에서는 댓글/좋아요/알림 등의 소셜 기능을 통해 사용자 간 상호작용이 가능하다.
2. 담당한 작업
관심사 도메인 전체와, 전체 API 부하테스트를 담당했다.
관심사 도메인
- 등록
- Levebshtien Distance 알고리즘을 활용한 유사도 검사 적용(80% 이상 유사 시 등록 불가)
- 동시에 여러 요청이 들어올 경우 중복 등록을 방지하기 위한 비관적 락 적용
- 수정
- 삭제
- 목록 조회
- 커서 기반 페이지네이션 구현
- 구독
- 중복 구독 방지를 위한 DB UNIQUE 제약 조건과 예외 처리 구현
- 구독 취소
부하테스트
- k6를 사용해 전체 API를 대상으로 300명 동시 접속 기준 진행
- 병목이 발견된 API에 캐싱을 적용해 성능 개선
- 자세한 사항은 링크 참조
3. 기술적 성과
기술 스택
- Java
- Spring Boot
- Spring Data JPA
- QueryDSL
- PostgreSQL
- MongoDB
- Docker
- K6
관심사 도메인 구현
- Apache Commons Text의 Levenshtein Distance: 유사도 검사에 활용
- QueryDSL: 커서 페이지네이션 구현
- atomic UPDATE 쿼리: 구독자 수 동시성 문제 해결
- keywordRepository.flush(): 키워드 수정 시 삭제 후 재삽입 순서 보장
부하테스트 및 성능 최적화
전체 API 부하테스트 진행 결과 두 가지 병목을 발견하고 캐싱으로 개선했다.
- 뉴스 기사 목록 조회 API
- 평균 응답시간 268ms → 2.97ms (약 100배 개선)
- 사용자 활동내역 조회 API
- 평균 응답시간 698ms → 1.28ms (약 540배 개선, 다른 팀원이 진행)
4. 문제점 및 해결 과정
4-1. 관심사 등록 동시성 문제
- 문제 상황
- 관심사 등록 시 유사도 검사를 위해 전체 관심사를 조회한 후 비교하는 로직 구현
- 동시에 여러 요청이 들어올 경우 두 요청이 동시에 유사도 검사를 통과하여 유사한 관심사가 중복 등록되는 문제 발생
- 해결 방법
- 유사도 검사 시 비관적 락(PESSIMISTIC_WRITE)를 적용
- 관심사 이름 목록을 조회할 때 락을 걸어 한 번에 하나의 요청만 유사도 검사를 수행할 수 있도록 해 동시성 문제 해결
- 다만, 이로 인해 부하테스트 환경에서 동시 요청이 많을 때 경합이 발생하여 에러율이 높아지는 문제가 발생함
- 하지만 관심사 등록이 실제 서비스에서 빈번하게 발생하는 작업이 아니므로, 허용 가능한 수준으로 판단함
4-2. 관심사 구독 500 에러
- 문제 상황
- 중복 구독 시 AlreadySubscribedException(409) 에러가 아닌, DataIntegrityViolationException(500) 에러가 발생
- 발생 원인
- subscriptionRepository.save() 호출 시 JPA가 즉시 SQL을 실행하지 않고 영속성 컨텍스트에 쌓아두다 트랜잭션 커밋 시점에 DB로 전송하기 때문에 작성한 try-catch 블록에서 예외가 잡히지 않음
- 해결 방법
- save을 saveAndFlush로 변경해 DB에 반영하도록 수정
4-3. 뉴스 기사 목록 조회 병목
- 문제 상황
- 300명 동시 접속 시 p95 응답시간이 788ms로 기준이었던 500ms를 초과
- 발생 원인
- 다중 테이블 JOIN, GROUP BY, totalElements 별도 쿼리가 동시에 실행되는 구조 때문에 응답 시간이 길어짐
- 해결 방법
- Caffeine 캐싱을 적용하되, 캐시 키에 모든 요청 파라미터를 포함하고 기사 등록/삭제, 댓글 등록/삭제, 사용자/관심사 삭제 시 캐시를 무효화하도록 구현
- 평균 응답시간 268ms → 2.97ms로 약 100배 개선
4-4. 캐시 적용 후 발생한 문제
- 문제 상황
- 문제 1) keyword, sourceIn 등 파라미터가 달라도 동일한 캐시를 반환하는 문제 발생
- 문제 2) keyword=null인 전체 조회와 keyword="null" 문자열 검색이 같은 캐시를 공유
- 문제 3) sourceIn=[NAVER, CHOSUN]과 sourceIn=[CHOSUN, NAVER]이 같은 결과임에도 서로 다른 캐시 키를 가져 불필요한 캐시 미스 발생
- 문제 4) 뷰 등록처럼 빈번하게 발생하는 이벤트에 캐시 무효화를 적용하면 캐싱 효과가 즐어드는 트레이드 오프 발생
- 발생 원인
- 문제 1) 초기 캐시 키에 일부 파라미터만 포함함
- 문제 2) SpEL에서 null이 "null" 문자열로 평가됨
- 문제 3) SpEL에서 List 타입이 순서에 의존함
- 문제 4) 뷰 등록은 사용자가 기사를 클릭할 때마다 발생하여 캐시 무효화 빈도가 매우 높음
- 해결 방법
- 문제 1) 모든 파라미터를 포함하도록 수정
- 문제 2, 3) KeyGenerator를 직접 구현하여 null 값은 빈 문자열로 처리하고, sourceIn 리스트는 정렬 후 캐시 키를 생성하도록 수정
- 문제 4) 뷰 등록 시 캐시 무효화를 제거하고 조회수는 TTL(10분) 이후 반영되는 방향으로 결정
5. 협업 및 피드백
5인 팀 프로젝트를 진행하며 각자 도메인을 나누어 개발했으며, 도메인 간 의존성이 있는 부분에서 활발한 소통이 필요했는데, 특히 부하테스트 진행 중 뉴스 기사 목록 조회 병목 개선을 위해 뉴스 기사 파트 담당자와 소통하며 캐싱을 적용했고, 사용자 활동내역 조회 병목은 담당자가 캐싱을 적용하여 해결했다.
코드래빗을 통해 자동 코드 리뷰를 받았는데, 캐시 키 설계 문제 등 미처 발견하지 못한 문제들을 발견하는 데 도움이 되었다. 자동화된 코드 리뷰 툴이 실제 개발 품질 향상에 도움이 된다는 것을 경험했다.
코드래빗과 더불어 한 명 이상의 코드 리뷰 후 머지가 가능하다는 팀 룰이 있어 많은 코드리뷰를 주고 받을 수 있었다. 단순히 평가를 한다거나 오류를 찾아준다...는 걸 넘어서 서로의 코드에 궁금한 점들을 자유롭게 질문할 수 있었고, 덕분에 많이 배우기도 했다. 왜 이 코드를 작성했는지, 왜 이렇게 설계를 했는지를 계속해서 따지고, 함께 고민할 수 있는 시간이었던 것 같다.
또 신기하게 충돌이 거의 나지 않았다. 도메인 계층 분리와 명확한 역할 분배 덕인 것 같다. 같은 파일을 수정해야 하는 상황이 예상될 때는 사전에 팀원에게 고지하고 작업 순서를 조율하는 방식으로 협업하기도 했는데, 이 덕분에 순조롭게 프로젝트를 진행할 수 있었다. 역시 소통이 정말 중요함을 느꼈다.
6. 코드 품질 및 최적화
- 테스트 진행
- 관심사 도메인 구현 시 단위 테스트와 통합 테스트를 작성하여 코드의 신뢰성을 높였다. 단위 테스트에서는 Mockito를 활용하여 서비스 레이어의 비즈니스 로직을 검증했고, 통합 테스트에서는 @DataJpaTest와 QueryDSL 설정을 함께 적용하여 실제 DB 환경에서의 쿼리 동작을 검증했다.
- 성능 최적화
- 캐싱 적용 시 단순히 성능만 고려하지 않고 데이터 정합성과의 트레이드오프를 고려했다. 뷰 등록 시 캐시 무효화를 적용했다가 뷰 등록이 빈번하게 일어나는 로직임을 고려하여 제거한 것이 그 예이다. 조회수는 TTL 이후 반영되는 것을 감수하고 캐싱 효과를 극대화하는 방향을 선택했다.
7. 향후 개선 사항 및 제안
- 캐시 무효화 전략 개선
- 현재 캐시 무효화 시 allEntries=true로 전체 캐시를 비우고 있어 불필요한 캐시 미스가 발생할 수 있다. 향후에는 변경된 데이터와 관련된 캐시만 선택적으로 무효화하는 전략을 적용하면 더욱 효율적인 캐싱이 가능할 것이다.
- 부하테스트 환경 개선
- 현재 부하테스트를 로컬 환경에서 진행하여 실제 운영 환경과 차이가 있었다. 또한, 실제 배포되는 사이트가 아니다보니 동시 최대 접속자 수나 시나리오를 작성하는 데에 한계가 있었다. 향후에는 배포 환경과 유사한 스펙의 서버에서 부하테스트를 진행하고, 실제 예측 트래픽을 기반으로 성능 목표를 설정하는 경험을 해보고 싶다.
- 관심사 유사도 검사 성능 개선
- 현재 관심사 등록 시 전체 관심사 이름을 조회하여 유사도를 검사하는 방식을 사용하고 있어 관심사가 많아질 수록 성능이 저하될 수 있다. 향후에는 인덱스 활용이나 캐싱을 통해 유사도 검사 성능을 개선하고 싶다.
'Project > Project' 카테고리의 다른 글
| [Findex] 개인 리포트 (2) | 2026.03.20 |
|---|