Skip to content

Conversation

@leesewon00
Copy link
Member

@leesewon00 leesewon00 commented Sep 2, 2024

관련 이슈

작업 내용

  • 기본 캐싱 어노테이션 개발
  • Thundering Herd 문제를 해결하는 캐싱 어노테이션 개발
  • 지원자 조회 기능에 캐싱 적용
  • 공통 추천학교 조회 기능에 캐싱 적용
  • 대학 정보 조회 기능에 캐싱 적용
  • 테스트코드 작성

모든 테스트 정상 작동합니다.
image

특이 사항

캐싱 도입 이유

근 7일간 측정된 모니터링 자료

구글이 발표한 자료에 따르면 응답시간이 길어지면 길어질수록 사용자 이탈율이 증가함에 따라서 권장하는 max 응답시간은 2.5s이다.
따라서 모니터링 자료에 따라 자주 접근하는 경로이면서, 응답시간이 긴 API 위주로 캐싱을 도입한다.

image
image

도입 전후 응답속도 비교

테스트는 k6로 가상사용자 생성하여 진행하였습니다.
상황은 50rps가 5m 유지되는 상황으로 진행하였습니다. (api-server가 너무 급격히 느려져서 5분간만 수행하였습니다.)

before

api-server: p(95)=40s, p(99)=45s
local-server: p(95)=300ms, p(99)=400ms

after

api-server: to be continue,,
local-server: p(95)=55ms, p(99)=70ms

Thundering Herd 문제 해결 과정 공유

Thudering Herd 문제

캐시아웃 시점에 다수의 동시요청이 발생하면 모든 요청이 db 접근하여 db 부하 발생

해결방안

lock(생성락) 이용하여 하나의 요청만 db 접근하여 데이터를 가져온 뒤, 캐싱 후 대기중인 요청들에게 event publish 진행
대기중이던 요청들은 event 발생 시 캐싱된 데이터 접근하여 반환

추가적으로 cache hit율을 더 높이기 위해서 TTL 만료 직전에 요청이 들어오면 TTL 갱신하도록 함
이또한 동시 요청 고려하여 다수의 요청이 동시에 들어오면 하나의 요청만 lock(갱신락) 잡아서 TTL 갱신 작업을 수행하도록 하고, 나머지 요청은 바로 캐시의 값 반환하도록 진행

동작과정 이해를 위한 로그 스크린샷

image
image
image

추가

Lock 잡은 요청이 db 접근하여 데이터를 가져오는 동안 다른 요청들을 대기시키는 방안들은 많이 있었지만 위와같은 방안을 도입한 이유는 아래와 같습니다.

단순 Sleep으로 대기: 0.01초뒤에 캐시값이 생성될 수 있음에도 miss&&lock 못잡는 요청의 경우 무조건적으로 지정한 시간만큼 sleep 해야하는 문제 발생

future 단일 사용: scale out시 특정 서버에서만 알려줄 수 있기에 확장성 떨어짐

pub/sub + future 사용: scale out 가능, 캐시값 생성 후 바로 대기중인 요청들이 값을 가져갈 수 있음

리뷰 요구사항 (선택)

ㅇ...어렵다..

@leesewon00 leesewon00 self-assigned this Sep 2, 2024
@leesewon00 leesewon00 requested review from nayonsoso and wibaek and removed request for wibaek September 2, 2024 06:22
@nayonsoso
Copy link
Collaborator

nayonsoso commented Sep 2, 2024

ㅇ...어렵다..

정말 어렵군요! 😲


제가 아는 수준을 넘어서는 코드라 피드백을 드릴게 없네요, 그래도 세원님이 캐싱에 대해서 고민해주신 덕분에 저도 이런 방법이 있다는 것을 새롭게 배울 수 있었습니다.

세원님이 사용하신 캐싱 전략을 정리하자면, 아래 내용이 맞나요?

  1. 캐싱을 적용한다.
  2. 캐시 미스가 일어난다면 DB 에 접근해서 값을 가져온다.
  3. 하지만 이때 DB 부하가 발생할 수 있다.
  4. 따라서 락을 사용해서 하나의 요청만 DB에 접근하게 한다.
  5. 이 요청은 데이터를 캐싱에 추가하고, 대기중인 요청들에게 event publish 를 한다.
  6. 따라서 대기중인 요청들은 캐싱에서 값을 가져올 수 있다.

(+첨부해주신 사진을 보니 더 이해가 잘 되었습니다🤗)


그리고 이것은 개인적인 질문이긴 한데요, 저한테 캐싱을 도입하라고 한다면 아래와 같은 사고 흐름을 따랐을 것 같아요.

응답 속도를 개선해보자! ☝️
캐싱을 도입해야겠다!
어떤 캐싱을 도입할 수 있을까?
DB 에서 가져온 데이터들을 메모리에서 관리하게 해야겠네!
그럼 어플리케이션 시작 시, 데이터들을 Map 에 저장하게 해야겠다. Map 이 그나마 조회가 빨리 되니까!
야호 코드 작성하기 시작해야지~~ 🕺

그런데 세원님은 어떻게 캐싱을 적용할지에 대해 훨씬 깊이 고민하신게 느껴졌어요. 세원님은 어떤 사고과정을 거치며 위와 같은 코드를 작성하셨는지 궁금해요! 🥹✨

@leesewon00
Copy link
Member Author

leesewon00 commented Sep 3, 2024

세원님이 사용하신 캐싱 전략을 정리하자면, 아래 내용이 맞나요?

  1. 캐싱을 적용한다.
  2. 캐시 미스가 일어난다면 DB 에 접근해서 값을 가져온다.
  3. 하지만 이때 DB 부하가 발생할 수 있다.
  4. 따라서 락을 사용해서 하나의 요청만 DB에 접근하게 한다.
  5. 이 요청은 데이터를 캐싱에 추가하고, 대기중인 요청들에게 event publish 를 한다.
  6. 따라서 대기중인 요청들은 캐싱에서 값을 가져올 수 있다.

(+첨부해주신 사진을 보니 더 이해가 잘 되었습니다🤗)

그리고 이것은 개인적인 질문이긴 한데요, 저한테 캐싱을 도입하라고 한다면 아래와 같은 사고 흐름을 따랐을 것 같아요.

응답 속도를 개선해보자! ☝️
캐싱을 도입해야겠다!
어떤 캐싱을 도입할 수 있을까?
DB 에서 가져온 데이터들을 메모리에서 관리하게 해야겠네!
그럼 어플리케이션 시작 시, 데이터들을 Map 에 저장하게 해야겠다. Map 이 그나마 조회가 빨리 되니까!
야호 코드 작성하기 시작해야지~~ 🕺

그런데 세원님은 어떻게 캐싱을 적용할지에 대해 훨씬 깊이 고민하신게 느껴졌어요. 세원님은 어떤 사고과정을 거치며 위와 같은 코드를 작성하셨는지 궁금해요! 🥹✨

우선 제가 적용한 캐싱전략의 큰 틀은 해당 내용이 맞습니다.
조금 더 구체화 하자면, 캐시아웃된 특정 키A와 키B에 동시다발적으로 수많은 요청이 오게될 때,
A로온 수많은 요청들 중 하나만 락을 흭득하여 DB를 접근하게되고, B로온 수많은 요청들 중 하나만 락을 흭득하여 DB접근이 이루어집니다.
또 이 과정은 병렬수행될 수 있습니다.
해당 과정이 끝나면 A를 기다리던 요청들에게, B를 기다리던 요청들에게 publish 하는 형식입니다!

두번째 질문의 답변으로는,,
저도 초기에는 map 등을 활용하여 local memory를 사용해야겠다! 라고 떠올렸습니다.
하지만, 요즘에는 정합성, 확장성, 가용성 등을 고민하다보니 local storage는 scale out시 문제가 발생하기 때문에,
Redis와 같은 중앙집중형 저장소를 사용하게되었습니다.
물론 Redis외에도 다양한 sw들이 존재하지만, 이미 서비스에 redis가 도입되어있어 도입이 용이하기에 활용하게 되었습니다.

아래 블로그는 제가 이번에 캐싱 도입을 하게 되면서 생각한 내용들을 정리한 글입니다.
pr에 작성한 내용과 대부분 겹치지만 조금은 더 구체적이라 생각되어 공유합니다!
https://velog.io/@dltpdnjs2000/%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%BA%90%EC%8B%B1-%EC%A0%84%EB%9E%B5-%EB%8F%84%EC%9E%85-feat.-Thundering-Herd

@leesewon00 leesewon00 merged commit e25f011 into solid-connection:main Sep 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

캐싱 도입

2 participants