Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] feat: Spring Cache 적용 #792

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open

Conversation

donghoony
Copy link
Contributor

@donghoony donghoony commented Oct 8, 2024


🚀 어떤 기능을 구현했나요?

  • 캐싱 기능을 구현했습니다. ConcurrentHashMap을 기반으로 함에 유의하세요.

  • 반복되는 쿼리를 방지하는 것이지, 한 번에 여러 쿼리가 나가는 것에 대한 근본적인 해결책이 아님에 유의하세요.

  • 그럼에도 리뷰 작성은 자주 발생하는 상황이므로, 이를 캐싱하는 것이 효율이 좋을 것이예요.

  • ConcurrentHashMap이 너무 나이브하고 추가 기능이 없다면, 로컬에서 작동하는 다른 캐시 라이브러리를 고려해도 좋습니다. 그 중 하나로 ehcache가 있습니다, 이 친구도 캐싱 자바 표준(JSR-107)을 지원해 스프링과 호환됩니다. 확인해보시고 이게 더 낫겠다고 생각하시면 RC 주세요. 만약 추가된다면, 캐싱에 대한 추가 메타데이터를 얻을 수 있습니다. (Cache Hit 비율, Cache log 등)

🔥 어떻게 해결했나요 ?

  • spring-boot-starter-cache 의존성과 어노테이션 세 개...

📚 참고 자료, 할 말

@donghoony donghoony linked an issue Oct 8, 2024 that may be closed by this pull request
Copy link

github-actions bot commented Oct 8, 2024

Test Results

153 tests   150 ✅  4s ⏱️
 58 suites    3 💤
 58 files      0 ❌

Results for commit 3ce1703.

♻️ This comment has been updated with latest results.

@donghoony donghoony self-assigned this Oct 8, 2024
@donghoony donghoony added this to the 6차 스프린트: 최종장 milestone Oct 8, 2024
@donghoony
Copy link
Contributor Author

성능 부하 테스트 하면서 전/후 비교해봐도 좋겠습니다.

@donghoony donghoony requested review from skylar1220, nayonsoso and Kimprodp and removed request for skylar1220 and nayonsoso October 12, 2024 13:34
@donghoony donghoony changed the base branch from develop-x to develop October 24, 2024 17:07
@skylar1220
Copy link
Contributor

skylar1220 commented Nov 3, 2024

캐시 라이브러리 방식 고민

후보

  1. concurrentHashMap
  • TTL 설정이 불가능
  • 최대 크기 제한 설정 불가능이라 무제한으로 데이터가 쌓이다보면 메모리가 부족해질 수 있음.
  • cache hit, miss 비율을 제공해주지 않아 캐시 효율성을 모니터링할 수 없음
  1. Ecache: 로컬 캐시 저장소로 후보1
  2. Caffein: 로컬 캐시 저장소로 후보2
  3. Redis
  • 데이터양이 많은 것도 아니고, 서버간 정합성 문제가 일어날 가능성이 적어서 굳이 필요없음

우리 상황

  1. 다중 서버이지만, 캐시에 올라간 템플릿 관련 데이터는 변경 가능성 없음. 템플릿이 추가되면 캐시에 추가되는 형식.
  2. 따라서 현재 정책에서는, 서버간 정합성을 크게 걱정하지 않아도 됨.

Ecache vs Caffein 비교

  1. 성능 개선을 빠르게 확인하기 위해 쉬운 적용
    둘 다 cacheConfig 파일 하나로 설정양 비슷

  2. 빠른 캐시 성능
    결론: caffein이 빠름

  • Ehcache는 전통적으로 동기화된 LinkedHashMap을 사용하여 여러 스레드가 동시에 캐시에 접근할 때 병목 현상을 유발할 수 있습니다.
  • Caffeine은 내부적으로 ConcurrentHashMap을 사용하여 락-프리(lock-free) 또는 최소한의 락을 사용하여 동시성을 관리하므로, 여러 스레드가 동시에 캐시에 접근하더라도 병목 현상이 최소화됩니다.
  • ref: 성능 비교
  • ref: Ecache와 직접 비교
  1. 공식문서, 레퍼런스
    공식문서 둘 다 잘 되어있음, caffein이 요즘거라 더 잘 되어있는 것 같기도?
    레퍼런스는 Ecache가 오래된 라이브러리라 적용 사례는 많지만, caffein이 신흥강자라 만만치않게 최신 자료가 많음.
  1. 다중 서버 고려
  • Ecache는 한 쪽 서버에서 생긴 캐시 변경사항을 다른 서버에 반영할 수 있는 기능이 있음, caffein은 없음
  • 하지만, 앞에서 말했듯이 캐시에 올라간 템플릿 관련 데이터는 변경 가능성 없기에 크게 상관없음. 오타같은 작은 변경사항은 evict로 기존 캐시를 지우고 새로 로딩하는 방법으로 해결할 수 있음.
  • 만약 변경 가능성이 있는 데이터를 나중에 캐시에 추가하고 싶으면 어떡해?: 이렇게 다중 서버를 고려한다면 차라리 redis를 나중에 사용하는 게 나을듯.

추가로 고려할 상황

만약 캐시에 질문지가 올라갔는데 오타가 있는 게 발견되어서 db에서 question의 content 수정한다면 어떻게 이 변경 사항을 캐시에 반영할 수 있을까?
: evict하고 다시 올리는 게 가장 간단하고, 두 방식 모두 가능해서 상관없음.

결론

설정 방법이나 난이도가 크게 차이나지 않는 상황에서, caffein이 동시성, 성능 문제가 적기때문에 더 효과적일 것이라고 생각합니다!

@skylar1220 skylar1220 self-assigned this Nov 3, 2024
@skylar1220
Copy link
Contributor

skylar1220 commented Nov 4, 2024

현재는 cahe hit,miss actuator로 확인하게만 설정
micrometer-registry-prometheus를 캐시 메트릭을 Prometheus로 연동하는 것 추가로 고려

@skylar1220
Copy link
Contributor


🚀 어떤 기능을 구현했나요?

  • 캐시 라이브러리를 caffeine으로 선택해 적용하고 ttl, max size, cache metric 노출 등 상세 설정을 했습니다.
  • template 관련해 db를 호출하는 부분을 모두 캐시 적용했습니다.
  • 캐시키를 동적으로 생성해주는 역할을 별도의 클래스로 분리하였습니다.

🔥 어떻게 해결했나요 ?

  • caffeine 라이브러리 의존성 주입과 캐시 어노테이션 적용

📚 참고 자료, 할 말

1. ttl은 30일로 설정했습니다.

  • 어플리케이션이 실행될 때마다 캐시가 초기화되기때문에 30일보다 더 자주 캐시는 사라지고 채워질 것 같습니다. 그래서 ttl이 크게 의미가 없겠다 싶긴 합니다. 그렇지만 ttl을 마지막 접근으로부터 30일로 설정해두었습니다. 그 이유는 만약 추후에 템플릿이 많아지고 오래된 템플릿으로 작성된 리뷰는 잘 조회되지 않아 30일 넘게 조회되지 않는 템플릿이 있다는 상황을 가정했고, 이런 경우 해당 템플릿 캐시는 제거한다.의 정책을 적용해보았습니다.

2. max size는 1000으로 설정했습니다.

캐시에 올라가있는 값들이 1000개를 넘어가면 접근빈도나 최신성을 카페인이 알아서 판단해 순차적으로 제거합니다..
1000개면 현재 템플릿 관련 캐시 데이터는 충분히 저장할 수 있는 양이고, 가장 컬럼이 많은 question 데이터 기준 1000개라고 하더라도 대략 3MB 정도를 차지해서 메모리에 부담이 없을 거라고 생각해요.

3. 캐시키 식별 방식

캐시는 key-value로 map에 저장되는 방식이라 어떤 메서드에서 어떤 목적으로 어떤 파라미터를 갖고 값을 저장하는지를 key로 식별해야합니다. 따라서 아래 두 캐시는 별도로 구분되지 않는 것이죠.

@Cacheable(value = "templateCache", key = "'#questionId")
@Query("""
        SELECT q FROM Question q
        WHERE ts.templateId = :templateId
        """)
List<Question> findAllById(long questionId);

@Cacheable(value = "templateCache", key = "'#questionId")
@Query("""
        SELECT q.id FROM Question q
        WHERE ts.templateId = :templateId
        """)
List<Long> findAllById(long questionId); 

따라서 이를 식별해주기 위해 key에 클래스명_메서드명을 추가해주었습니다. 최종은 keyGenerator로 동적으로 생성해주는 방식을 선택했습니다. 그 과정에서 여러 방식을 시도해보았는데 커밋 보면서 더 나은 방식이 있는지 알려주세요!

@skylar1220
Copy link
Contributor

skylar1220 commented Nov 5, 2024

팀 논의 결과

  1. template form 반환하는 부분은 캐시를 적용한다.
  2. caffeine이 제공하는 ttl, max size, hit miss 비율등이 필요없다. 성능도 비슷하니 concurrentHashMap으로 돌아가자.
  3. 리뷰 조회에 필요한 템플릿 관련 캐시는 현시점에서는 적용하지 않는다. 1. 도메인 리팩토링이 예정되어있고 2. 그에 따라 필요한 캐시 정책이 다를 수 있으며3. 캐시가 필요할 정도의 리뷰 조회 부하가 예상되지 않으므로

추가 작업

위 내용을 반영해서 push, merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

[BE] Spring Cache를 적용한다.
2 participants