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] 약속 조회와 약속 추천 서버 캐시 추가 :) #437

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

Conversation

ikjo39
Copy link
Contributor

@ikjo39 ikjo39 commented Nov 18, 2024

관련 이슈

작업 내용

  • 약속 조회와 약속 추천 로직에 대해 서버 캐시를 도입하였습니다.
  • 두 로직에 대해 로직 수행 시간을 단축했습니다.
    • Intel MacOS 로컬 IDLE 상태 (약속 1, 참가자 15, 가능일 15일, 00:00-23:30) 기준
      • 조회 로직 131 ms → 13 ms
      • 빠른 날짜 추천 로직 118 ms → 6 ms
      • 길게 볼 날짜 추천 로직 104 ms → 3 ms

개발 서버에서 CPU Bound 개선 측정 필요성

적용 사항이 실제 효율이 있는지 테스트가 필요합니다.
기존에 동시에 100개 내외의 요청부터 CPU Bound가 발생한 지점이 있었습니다.
이보다 얼마나 개선되었는지, 개선 되었다면 그 역치가 어디까지인지 Jmeter를 활용하여 테스트해볼 계획입니다.

리모트 캐시 선택 이유

서버 캐시를 도입하는 경우 로컬 캐시와 리모트 캐시에 대해 적용을 검토하여야 합니다.
현재 모모의 운영 서버 인프라는 분산 서버로 운영되어 있습니다.
때문에 로컬 캐시로 도입할 경우 서버 간 분산되어 있는 캐시에 대해 동기화 작업이 별도로 필요합니다.

그러나, 비즈니스 로직 특성상 약속 추천과 약속 조회는 데이터 정합성에 민감한 API 입니다.
때문에 추가적인 비용과 네트워크 지연성을 감안하더라도 데이터 정합성을 이유로 하나의 별도로 분리된 Remote 캐시를 도입하였습니다.

캐시 전략

서비스 특성상 약속 초기에 참가자들의 일정 추가/수정이 빈번하게 발생합니다.
때문에 TTL 설정과 캐시 전략을 세우는데 어려움이 있었습니다.

최종적으로 TTL은 약속에 머무는 기간 10분을,
캐시 전략은 읽기엔 Look Aside를, 쓰기 전략에는 Write Through 전략을 사용하였습니다.

대신, 일정이 추가/수정되는 경우 캐싱이 Miss임을 나타내기 위해 invalid라는 값을 추가하여 Hit 상태가 아니게끔 설계하였습니다.
이 경우, evict()를 통해 캐시 무효화 고려하였지만 이 경우 동시성 문제가 발생할 가능성이 있어 고려 대상에서 제외하였습니다.
(동시에 evict()와 캐시 조회 로직이 redis에 요청되는 경우 Hit이 될 가능성이 있다고 합니다.)

캐시 저장 데이터

value로 저장해야하는 데이터의 개수가 서비스 특성상 최악의 경우 Key 1개당 14400 개의 데이터가 저장되게 됩니다.
이때 객체를 그대로 Serialize하여 저장하는 경우 key 하나당 11.8 MB 의 용량을 차지하게 됩니다.
그러면 t4g.micro 기준 메모리가 1 GiB 인 것을 감안한다면

1.07 GB / 11.8 MB = 약 90
90개의 데이터만을 캐싱해야 합니다.

따라서 ObjectMapper를 이용하여 jsonRawString으로 변환한 후 이 String을 캐싱에 저장하도록 설계하였습니다.
jsonRawString 으로 변환한다면 chatGPT를 참고하여 절반 정도 용량의 이점을 볼 수 있음을 확인하였습니다.

특이 사항

  • 서브 모듈이 업데이트되었습니다.

테스트 코드 변경사항

CacheCleanupListener.class
기존에 있던 DBCleanupListener 처럼 캐시 격리 코드를 분리하기 위해 Listener를 사용하였습니다.
스프링 테스트 생명주기에 접근하여 Cache Clear를 통해 테스트 격리를 실행합니다.
@IsolateDatabaseAndCache
정의한 리스너를 스프링 리스너에 등록하는 어노테이션입니다.
데이터베이스와 캐시 격리가 필요한 테스트 클래스에 사용합니다.
PortKiller
테스트의 Embedded Redis의 경우 application.yml에 명시된 포트를 이용하여 Redis를 띄운 후 해당 포트를 사용하게 됩니다.
그러나, 테스트 중간에 예외가 발생하거나, 정지 버튼을 이용해 강제로 종료하게 되는 경우 테스트를 마친 후에도 Embedded Redis가 종료되지 않고 포트를 점유하게 됩니다.
이를 해결하기 위해선 3가지 방안이 있습니다.

  1. 사용하려는 포트가 점유 중인 경우 알려진 포트(1024번-49151번)와 동적 포트(49152번-65535번) 안에서 가용 포트를 찾은 후 그 포트에 Redis를 새로 띄운다.
  2. 사용하려는 포트가 점유 중인 경우 해당 프로세스를 강제 종료 후 테스트를 시도한다.
  3. Docker를 활용하여 Test Container를 구성한다.

3가지 방안 모두 마음에 들지 않았으나 1번은 문제 해결을 미루는 행위이고, 3번은 테스트 속도가 느려지는 원인이 되고, CI 과정에서 도커를 이용하여 추가적인 redis를 설치해야 하는 단점이 존재합니다.

테스트와 운영 환경의 Redis 포트가 중복되는 경우 그 포트를 강제로 제거하는 위험성이 있지만, 문서화를 통해 사용하면 안되는 포트를 알리면 가장 적합한 해결책이라 판단하여 2번을 사용하였습니다.

더 나은 해결책이 있다면 말씀해주세요 :)

리뷰 요구사항 (선택)

@ikjo39 ikjo39 added 🐈‍⬛ 백엔드 백엔드 관련 이슈에요 :) ♻️ 리팩터링 코드를 깎아요 :) labels Nov 18, 2024
@ikjo39 ikjo39 self-assigned this Nov 18, 2024
Copy link

Test Results

165 tests   165 ✅  24s ⏱️
 32 suites    0 💤
 32 files      0 ❌

Results for commit 5b39a04.

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.

1 participant