Skip to content

Perf/after optimization#35

Closed
HeoJunHyoung wants to merge 33 commits intodevfrom
perf/after-optimization
Closed

Perf/after optimization#35
HeoJunHyoung wants to merge 33 commits intodevfrom
perf/after-optimization

Conversation

@HeoJunHyoung
Copy link
Owner

📌 개요

| 이번 PR의 핵심 변경 사항을 한 문장으로 요약하여 작성해주세요.

ex) JWT 기반의 자체/소셜 로그인, 토큰 재발급, 로그아웃 등 핵심 인증/인가 기능을 구현합니다.


📋 작업 내용

| 이번 PR에서 작업한 내용들을 작성해주세요.

ex)

  1. Spring Security 기본 설정 및 JWT 검증을 위한 JWTFilter 추가
  2. JSON 기반 자체 로그인을 위한 LoginFilter 및 LocalLoginSuccessHandler 구현
  3. 소셜 로그인(Google, Naver) 처리를 위한 SocialLoginService, SocialLoginSuccessHandler 구현
  4. Refresh Token Rotation 전략을 적용한 토큰 재발급 API (/jwt/refresh) 구현
  5. Refresh Token을 DB에서 삭제하는 로그아웃 핸들러(RefreshTokenLogoutHandler) 추가

🔗 관련 이슈

| GitHub 이슈 번호가 있다면 Closes #이슈번호 형식으로 태그해주세요.

ex) Closes #15

💡 이슈 태그란?
Closes, Fixes, Resolves 같은 키워드를 이슈 번호 앞에 붙이면, 이 Pull Request가 머지(Merge)될 때 해당 이슈가 자동으로 닫힙니다.
예를 들어, Closes #15 라고 적으면, 이 PR이 dev 브랜치에 머지되는 순간 GitHub가 알아서 15번 이슈를 "Closed" 상태로 변경해줍니다. 프로젝트 관리가 매우 편리해지는 기능입니다.

✅ PR 체크리스트

  • 기능이 정상 동작함
  • 불필요한 코드/콘솔 제거함
  • 스타일/포맷팅 문제 없음

💬 기타 사항

ex)

  • Refresh Token 저장소로 초기에는 MySQL을 사용했으나, 리뷰 후 Redis로 전환할 예정입니다.
  • JWTUtil 클래스의 토큰 생성 및 검증 로직이 보안적으로 민감한 부분이 존재합니다.

[내용]
1. Redis CacheManager 설정 추가
   - RedisConfig에 JSON 직렬화(GenericJackson2JsonRedisSerializer) 및 JavaTimeModule을 적용한 CacheManager Bean 등록
   - 기본 TTL 1시간 설정

2. 공지사항(Notice) 조회 최적화 (Over-fetching 해결)
   - 목록 조회용 DTO(NoticeListResponse)와 상세 조회용 DTO(NoticeResponse) 분리
   - 목록 조회 시 불필요한 본문(Content) 데이터 제외하여 네트워크 페이로드 감소

3. 캐싱(@Cacheable) 및 정합성 관리(@CacheEvict) 적용
   - NoticeService: 목록(페이징 키 적용) 및 상세 조회 캐싱 적용
   - FaqService: 전체 목록 및 카테고리별 조회 캐싱 적용
   - 데이터 생성/수정/삭제 시 관련 캐시(목록/상세) 초기화 로직 구현
1. MyPage 조회 성능 개선
   - '내가 쓴 글', '북마크한 글' 조회용 경량 DTO(MyPostResponse, MyBookmarkPostResponse) 도입
   - DB 레벨에서 Content를 요약(SUBSTRING)하여 네트워크 페이로드 감소
2. N+1 문제 해결 및 로직 단순화
   - PostRepositoryImpl에 QueryDSL Projections 및 Left Join 적용
   - PostService의 어플리케이션 레벨 조인 로직(mapWriterInfo 등) 제거 및 쿼리 이관
   - 게시글 상세 조회 시 작성자 정보 및 댓글 목록을 효율적으로 가져오도록 구조 개선
- PortfolioService: 상세 조회 시 DB Update 제거 및 Redis 캐싱 적용
- PortfolioService: 응답 시 DB 값 + Redis 캐시 값 합산 반환
- PortfolioBatchScheduler: 조회수 동기화 및 인기 점수 재계산 스케줄러 구현
- PortfolioEntity: 배치용 메서드 추가 및 점수 계산 로직 리팩토링
- PortfolioRepositoryImpl: 정렬 조건 최적화
- 상세 조회(getPortfolioDetails) 시 정적/동적 데이터 분리 조회 구현
  - 정적 데이터(본문): Redis Cache-Aside 패턴 적용 (TTL 1시간)
  - 동적 데이터(조회수, 좋아요): Redis Hash 구조로 실시간 관리

- 좋아요(Bookmark) 기능 Write-Through 전략 적용
  - DB 즉시 반영 및 Redis 캐시 실시간 동기화로 정합성 보장

- 조회수(ViewCount) Write-Back 배치 처리 구현
  - Redis에 버퍼링된 조회수를 10분 주기 스케줄러로 DB 일괄 반영
  - 사용자 Display용 키와 배치 처리용 키 분리하여 안정성 확보

- 포트폴리오 목록 조회(getPortfolioList) 최적화
  - 메인 페이지(Page 0)에 한해 Redis 캐싱 적용 (TTL 3분)
1. Redis 캐싱 전략 재설계 (Split & Merge)
   - 게시글 본문(변경 빈도 낮음)과 통계/댓글(변경 빈도 높음)을 분리하여 관리
   - 본문 정보는 post:info:{id} 키로 캐싱하여 DB 부하 감소
   - 댓글 목록은 캐싱하지 않고 DB에서 실시간 조회하여 데이터 정합성 보장

2. 조회수 Write-Back(쓰기 지연) 구현
   - 조회수 증가 요청 시 DB Update 없이 Redis에만 카운트 누적
   - PostBatchScheduler 추가: 10분 주기로 Redis의 누적 조회수를 DB에 일괄 반영
   - PostRepository: 대량 업데이트를 위한 incrementViewCount 쿼리 추가

3. DB 인덱스(Index) 최적화 적용
   - PostEntity: 카테고리별 조회순/최신순 정렬 및 유저별 조회 인덱스 추가 (idx_post_category_view, idx_post_category_date 등)
   - PostCommentEntity: 게시글별 댓글 목록 조회를 위한 복합 인덱스 추가 (idx_comment_post)
   - PostBookmarkEntity: 내 북마크 목록 최신순 조회를 위한 인덱스 추가 (idx_bookmark_user_date)

4. Service 및 Entity 리팩토링
   - PostEntity: 조회수, 댓글수, 북마크수 증감 편의 메서드 추가
   - PostService: 댓글 생성/삭제 시 게시글 본문 캐시를 불필요하게 삭제하지 않도록 개선
- Projections.fields 사용 시 기본 생성자(NoArgsConstructor) 부재로 인한 런타임 예외 해결
- 컴파일 시점에 타입 체크가 가능한 Projections.constructor로 변경하여 안정성 확보
- DTO(PostResponse 등)에 QueryDSL 조회용 전체 생성자 추가
1. Service (PortfolioLikeService):
  - 트랜잭션 내 부모 엔티티(Portfolio) Update 로직 제거 (Lock 회피)
  - Redis에 실시간 조회용(stats)과 배치 동기화용(delta) 카운트를 각각 업데이트하도록 변경

2. Scheduler (PortfolioBatchScheduler):
  - syncLikeCounts 메서드 추가
  - 10분 주기로 Redis에 누적된 좋아요 증감분(Delta)을 읽어 DB에 일괄 업데이트(UPDATE ... SET count = count + :delta) 수행

3. Repository (PortfolioRepository):
  - 배치 처리를 위한 updateLikeCount Modifying 쿼리 추가
1. DTO (PostResponse, PostDetailResponse):
  - commentCount 필드 추가 및 QueryDSL Projections 생성자 매핑 순서 수정.

2. Service (PostService):
  - 댓글 작성/삭제, 북마크 토글 시 실행되던 부모 엔티티(Post)의 직접 Update 로직 제거 (Lock-Free).
  - Redis에 실시간 카운트(stats)와 배치용 델타(delta)를 기록하도록 변경.
  - 상세 조회 시 Redis에 저장된 최신 카운트(조회수, 북마크, 댓글)를 병합하여 반환.

3. Scheduler (PostBatchScheduler):
  - 기존 조회수 외에 북마크 수, 댓글 수도 주기적으로 DB에 일괄 반영하도록 확장.
  - Atomic Rename을 활용하여 배치 처리 중 데이터 유실 방지.

4. Repository (PostRepository, Impl):
  - 배치 처리를 위한 updateCommentCount, updateBookmarkCount 쿼리 추가.
  - QueryDSL 조회 시 commentCount 필드 매핑 추가.
[Performance Tuning]
- 병목 해결 및 리소스 최적화를 위한 HikariCP 최대 풀 사이즈 조정:
  - portfolio-service: 10 -> 30 (부하 집중 구간 증설)
  - community-service: 10 -> 20 (부하 집중 구간 증설)
  - auth-service: 10 -> 5 (저부하 구간 축소)

[Refactoring]
- 미사용 spring-cloud-starter-openfeign 의존성 제거:
  - auth-service
  - portfolio-service
  - user-service
- Application 클래스에서 @EnableFeignClients 어노테이션 제거
- �pplication.yml 내 불필요한 Feign 설정 정리
- PageImpl 역직렬화(Deserialization) 500 에러 해결을 위해 CustomPageResponse Wrapper 도입
- @Cacheable 어노테이션 제거 및 RedisTemplate을 사용한 수동 캐싱 로직으로 변경 (PostService와 동일 패턴 적용)
- @CacheEvict 동작 보장을 위해 캐시 키 생성 규칙 변경 ('noticeList:...' -> 'noticeList::...')
- JSON 문자열 처리에 최적화된 StringRedisTemplate 적용
- OpenFeign 라이브러리의 SortJsonComponent가 Page 객체의 Sort 필드 직렬화에 간섭하여 발생하는 500 에러(EncodeException) 해결
- 응답 시 Spring Data Page 인터페이스를 직접 반환하지 않고, 순수 POJO인 CustomPageResponse<T> 래퍼 클래스로 변환하여 반환하도록 변경
- PostController 내 목록 조회 API(전체/내 글/북마크) 반환 타입 수정
- 직렬화 전용 CustomPageResponse DTO 추가
[Support Service]
- CustomPageResponse: Redis 캐싱 역직렬화 오류(NPE) 수정을 위한 @Setter 추가
- NoticeEntity: 공지사항 목록 조회 성능 개선을 위한 복합 인덱스(isImportant, createdAt) 추가

[Test Data Setup]
- Community/Portfolio TestSetupController: 인덱스 효율성 검증을 위해 더미 데이터 생성 시 카테고리 및 직군이 고르게 분산되도록 개선 (Randomize)

[k6 Load Test]
- Community Workload: 실제 유저 패턴(카테고리 필터링)을 반영하여 인덱스를 정상적으로 타도록 스크립트 수정
- 집중 테스트 스크립트 추가: 서비스별 병목 구간 정밀 타격을 위한 community_load_test.js, portfolio_load_test.js 추가
- 테스트 결과 업데이트 (global_load_test_after)

[DB Hikari connections]
- 기존 20, 30개였던 connection을 10으로 고정(default 값)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant