Skip to content

[Refactoring] 오픈런 게시글 캐싱 기능 수정 #159#160

Open
kmchaejin wants to merge 47 commits intomainfrom
feat#159/caching
Open

[Refactoring] 오픈런 게시글 캐싱 기능 수정 #159#160
kmchaejin wants to merge 47 commits intomainfrom
feat#159/caching

Conversation

@kmchaejin
Copy link
Copy Markdown
Collaborator

@kmchaejin kmchaejin commented Aug 5, 2025

📌 관련 이슈

✨ 기능 요약

항목 내용
🆕 기능명 오픈런 게시글 조회 성능 개선
🔍 목적 대용량 트래픽 발생 시의 성능 최적화
🛠️ 변경사항 요청 스레드 수가 증가하면 캐시 스탬피드로 인해 캐시 적용 전보다 성능 저하 발생, 따라서 이를 개선함

📝 상세 내역

번호 내용
1️⃣ 게시글에 최초 접근하는 요청(캐시 없는 상황)에 대해 스핀락으로 동시성 제어 -> 최초 1건만 DB 접근하여 캐시 저장, 나머지 요청은 DB 접근 X

📸 포스트맨 캡처 사진

  • 코드 변경 후에도 정상 동작 확인
image

🙋‍♀️ 리뷰어 참고사항

image

Summary by CodeRabbit

  • 신규 기능
    • 캐시 스탬피드(동시 접근) 방지를 위한 Redis 분산 락 기반 캐시 조회 기능이 추가되었습니다.
  • 버그 수정
    • 캐시 관련 오류 메시지가 "캐시가 존재하지 않습니다."에서 "캐시가 준비되지 않았습니다."로 변경되었습니다.
  • 기타
    • 내부적으로 캐시 조회 방식이 개선되어, 다수의 사용자가 동시에 접근할 때의 안정성이 향상되었습니다.

kmchaejin added 30 commits June 9, 2025 12:22
@kmchaejin kmchaejin self-assigned this Aug 5, 2025
@kmchaejin kmchaejin added the Refactoring 리팩토 label Aug 5, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 5, 2025

Walkthrough

Redis 락을 활용한 캐시 스탬피드 방지 로직이 BoardCacheService에 도입되었습니다. 새로운 캐시 조회 및 저장 메서드가 추가되고, BoardService의 캐시 접근 방식이 변경되었습니다. Redis 관련 상수와 에러 메시지도 함께 업데이트되었습니다.

Changes

Cohort / File(s) Change Summary
Redis 상수 및 에러 코드 업데이트
src/main/java/com/example/taste/common/constant/RedisConst.java, src/main/java/com/example/taste/common/exception/ErrorCode.java
Redis 락 키 프리픽스 상수 추가, 캐시 관련 에러 코드명 및 메시지 수정
BoardCacheService 캐시 스탬피드 방지 도입
src/main/java/com/example/taste/domain/board/service/BoardCacheService.java
Redis 락을 활용한 캐시 조회(getCachedBoardResponseDto) 및 저장(setCacheBoardResponseDto) 메서드 추가
BoardService 캐시 접근 방식 변경
src/main/java/com/example/taste/domain/board/service/BoardService.java
기존 캐시 조회 방식에서 새로운 캐시 스탬피드 방지 메서드로 변경

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BoardService
    participant BoardCacheService
    participant Redis
    participant DB

    Client->>BoardService: findBoardInCache(boardId)
    BoardService->>BoardCacheService: getCachedBoardResponseDto(boardId)
    BoardCacheService->>Redis: GET cache:board:{boardId}
    alt Cache Hit
        Redis-->>BoardCacheService: BoardResponseDto
        BoardCacheService-->>BoardService: BoardResponseDto
    else Cache Miss
        BoardCacheService->>Redis: SETNX lock:cache:board:{boardId}
        alt Lock Acquired
            BoardCacheService->>DB: fetch board entity
            BoardCacheService->>Redis: SET cache:board:{boardId} (with TTL)
            BoardCacheService->>Redis: DEL lock:cache:board:{boardId}
            BoardCacheService-->>BoardService: BoardResponseDto
        else Lock Not Acquired
            loop 최대 3회 재시도
                BoardCacheService->>Redis: GET cache:board:{boardId}
                alt Cache Ready
                    Redis-->>BoardCacheService: BoardResponseDto
                else
                    BoardCacheService->>BoardCacheService: 50ms 대기
                end
            end
            alt 여전히 캐시 없음
                BoardCacheService-->>BoardService: throw CACHE_NOT_READY
            end
        end
    end
    BoardService-->>Client: BoardResponseDto or Error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Assessment against linked issues

Objective Addressed Explanation
캐시 저장 로직에 락 적용 (#159)
BoardService, BoardCacheService 수정 (#159)
동작 테스트 및 성능 테스트 (#159) 코드 레벨의 테스트 및 성능 테스트 관련 구현/코드가 포함되어 있지 않음.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
없음

Suggested reviewers

  • Chaemin07
  • mo00ai

Poem

🐇
캐시 스탬피드, 이제는 안녕
락 걸고 기다리면, DB도 평온
게시글 캐싱에 질서가 생겼네
Redis와 Rabbit, 오늘도 한 팀
성능 향상, 모두 함께 뿌듯한 하루!
🗝️✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat#159/caching

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/main/java/com/example/taste/domain/board/service/BoardService.java (1)

131-132: 캐시 메서드 변경이 적절합니다.

캐시 스탬피드 방지를 위한 새로운 메서드로 변경되었습니다. 주석 처리된 기존 코드는 향후 정리할 것을 권장합니다.

src/main/java/com/example/taste/domain/board/service/BoardCacheService.java (2)

70-101: 캐시 스탬피드 방지 로직이 잘 구현되었습니다.

분산 락을 사용한 캐시 스탬피드 방지 메커니즘이 적절히 구현되었습니다. 락 획득 실패 시 재시도 로직과 InterruptedException 처리도 올바릅니다.

다음 개선사항을 고려해보세요:

-		boolean hasLock = redisService.setIfAbsent(CACHE_LOCK_KEY_PREFIX + boardId, "Lock", Duration.ofMillis(3000));
+		boolean hasLock = redisService.setIfAbsent(CACHE_LOCK_KEY_PREFIX + boardId, "Lock", Duration.ofMillis(1000));

락 타임아웃을 1초로 줄여 불필요한 대기 시간을 감소시킬 수 있습니다.


87-98: Thread.sleep 사용 시 성능 영향을 고려하세요.

재시도 로직에서 Thread.sleep(50)을 사용하면 스레드가 블로킹됩니다. 높은 동시성 환경에서는 성능에 영향을 줄 수 있습니다.

비동기 처리나 더 짧은 간격을 고려해보세요:

-				Thread.sleep(50);
+				Thread.sleep(10);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46acc6b and 3fea244.

📒 Files selected for processing (4)
  • src/main/java/com/example/taste/common/constant/RedisConst.java (1 hunks)
  • src/main/java/com/example/taste/common/exception/ErrorCode.java (1 hunks)
  • src/main/java/com/example/taste/domain/board/service/BoardCacheService.java (3 hunks)
  • src/main/java/com/example/taste/domain/board/service/BoardService.java (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: mo00ai
PR: mo00ai/your-my-taste#34
File: src/main/java/com/example/taste/domain/pk/service/PkService.java:0-0
Timestamp: 2025-06-04T04:05:51.696Z
Learning: 이 프로젝트의 PkService에서는 PK 기준 조회 시 DB 접근 빈도가 높아서 성능 개선이 필요하며, 향후 Redis 캐시를 도입할 계획이 있다.
Learnt from: Chaemin07
PR: mo00ai/your-my-taste#141
File: src/main/java/com/example/taste/config/SecurityConfig.java:57-57
Timestamp: 2025-07-03T00:42:54.205Z
Learning: 사용자 Chaemin07는 보안 설정이나 리팩토링과 같은 개선 제안을 현재 PR에서 즉시 적용하기보다는 추후 수정하는 것을 선호함.
Learnt from: mo00ai
PR: mo00ai/your-my-taste#112
File: src/main/java/com/example/taste/domain/image/service/ImageService.java:0-0
Timestamp: 2025-06-22T18:33:56.980Z
Learning: 사용자 mo00ai는 성능 테스트 진행 중에는 코드 변경을 피하고, 테스트 완료 후에 수정 작업을 진행하는 것을 선호함.
Learnt from: Chaemin07
PR: mo00ai/your-my-taste#82
File: src/main/java/com/example/taste/domain/board/service/BoardService.java:0-0
Timestamp: 2025-06-15T15:10:27.636Z
Learning: BoardRepositoryImpl.findBoardListDtoByUserIdList 메서드의 성능 최적화가 완료되었습니다. 서브쿼리 방식에서 LEFT JOIN + GROUP BY 방식으로 변경하여 N+1 쿼리 문제를 해결했습니다.
Learnt from: mo00ai
PR: mo00ai/your-my-taste#45
File: src/main/java/com/example/taste/domain/pk/service/PkService.java:149-191
Timestamp: 2025-06-04T12:29:53.184Z
Learning: Spring에서 self-invocation 문제를 해결하기 위해 특정 메서드만 별도 서비스로 분리하는 것이 효과적인 방법이다. 전체 서비스를 분리할 필요 없이 캐시가 필요한 메서드만 분리해도 문제를 해결할 수 있다.
Learnt from: kmchaejin
PR: mo00ai/your-my-taste#33
File: src/main/java/com/example/taste/domain/store/service/StoreBucketService.java:127-139
Timestamp: 2025-06-04T02:13:29.504Z
Learning: kmchaejin은 코드에서 검증 로직을 별도 메서드로 분리하여 재사용 가능하게 구조화하는 것을 선호합니다.
📚 Learning: boardrepositoryimpl.findboardlistdtobyuseridlist 메서드의 성능 최적화가 완료되었습니다. 서브쿼리 방식에서 left join + group b...
Learnt from: Chaemin07
PR: mo00ai/your-my-taste#82
File: src/main/java/com/example/taste/domain/board/service/BoardService.java:0-0
Timestamp: 2025-06-15T15:10:27.636Z
Learning: BoardRepositoryImpl.findBoardListDtoByUserIdList 메서드의 성능 최적화가 완료되었습니다. 서브쿼리 방식에서 LEFT JOIN + GROUP BY 방식으로 변경하여 N+1 쿼리 문제를 해결했습니다.

Applied to files:

  • src/main/java/com/example/taste/domain/board/service/BoardService.java
  • src/main/java/com/example/taste/domain/board/service/BoardCacheService.java
📚 Learning: in src/main/java/com/example/taste/common/service/redisservice.java, the `notification:count:user:{u...
Learnt from: rlgkghkd
PR: mo00ai/your-my-taste#92
File: src/main/java/com/example/taste/common/service/RedisService.java:120-126
Timestamp: 2025-06-18T01:56:09.908Z
Learning: In src/main/java/com/example/taste/common/service/RedisService.java, the `notification:count:user:{userId}:{category}` count is used to track whether all notifications (both Redis and MySQL) have been viewed/read by the user, not to count the actual number of notifications stored in Redis. This count operates independently from Redis notification storage management.

Applied to files:

  • src/main/java/com/example/taste/domain/board/service/BoardCacheService.java
📚 Learning: 이 프로젝트의 pkservice에서는 pk 기준 조회 시 db 접근 빈도가 높아서 성능 개선이 필요하며, 향후 redis 캐시를 도입할 계획이 있다....
Learnt from: mo00ai
PR: mo00ai/your-my-taste#34
File: src/main/java/com/example/taste/domain/pk/service/PkService.java:0-0
Timestamp: 2025-06-04T04:05:51.696Z
Learning: 이 프로젝트의 PkService에서는 PK 기준 조회 시 DB 접근 빈도가 높아서 성능 개선이 필요하며, 향후 Redis 캐시를 도입할 계획이 있다.

Applied to files:

  • src/main/java/com/example/taste/domain/board/service/BoardCacheService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: generate-jooq
🔇 Additional comments (3)
src/main/java/com/example/taste/common/constant/RedisConst.java (1)

13-13: 상수 추가가 적절합니다.

캐시 락을 위한 Redis 키 prefix가 기존 네이밍 패턴을 따라 적절히 추가되었습니다.

src/main/java/com/example/taste/common/exception/ErrorCode.java (1)

30-30: 에러 코드 네이밍이 개선되었습니다.

CACHE_NOT_FOUND에서 CACHE_NOT_READY로 변경하여 캐시 스탬피드 방지 로직에서 캐시가 준비 중인 상태를 더 정확히 표현합니다.

src/main/java/com/example/taste/domain/board/service/BoardCacheService.java (1)

103-119: 캐시 저장 로직이 올바르게 분리되었습니다.

캐시 락 확보 후 실제 데이터 조회 및 캐시 저장 로직이 별도 메서드로 잘 분리되어 책임이 명확합니다. Duration 계산 로직도 기존과 동일하게 유지되었습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactoring 리팩토

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactoring] 오픈런 게시글 캐싱 기능 수정

1 participant