부하 테스트 결과 클라이언트 스레드가 5~10개일때도 타임슬롯이 많이 포함된 약속의 경우 CPU사용량이 100%에 수렴하는 결과를 볼 수 있었습니다.
이는 조회 로직 자체가 DB bound 작업이 아니라 CPU bound 작업이라는 의미로, 전체 로직 분석 결과 DB에서 가져온 엔티티들을 응답 스펙에 맞게 DTO로 변환하는 과정에서 CPU시간을 사용하는 것으로 예상할 수 있었습니다.
따라서, 응답 JSON 자체를 캐싱해 두고, 약속에 새로운 일정 정보가 등록되기 전까지 캐싱된 데이터를 응답한다면 동일한 약속에 대한 반복적인 조회에서 CPU병목을 해결할 수 있습니다.
만약 활성 사용자 수 자체가 많아져 여러 약속에 걸친 일정 조회 API 호출 횟수가 늘어난다면, 이는 수평 확장의 영역으로, WAS 인스턴스의 수를 증가시켜야 합니다.
추천로직에서 사용하는 쿼리는 다른 쿼리들에 비해 실행시간이 긴 편으로, 사용자 수가 많아졌을 때 잠재적으로 DB 커넥션을 장시간 점유할 우려가 있습니다.
일정 추천 결과는 수정은 잘 일어나지 않지만 조회가 매우 빈번한 대표적인 API 중 하나로, 캐싱을 적용하기 적절한 구간이라고 판단했습니다.
1차적으로는 사용자의 추천 조회 요청을 수신했는데, 캐시가 적절한 엔트리가 준비되지 않았다면 추천 일정을 계산해서 캐싱 후 응답을 주는 형태로 구현할 수 있습니다.
만약 이러한 구조 때문에 첫 추천 조회 요청에 대한 응답이 지연되는 문제가 비즈니스적으로 큰 불편이 예상된다면, 사용자의 일정 정보가 수정되었을 때를 기점으로 캐시를 다시 계산하는 전략을 택할 수 있습니다.
하지만 이 구현도 캐싱 로직 때문에 일정 등록 API의 처리 속도가 느려질 수 있다는 문제점을 안고 있으므로, 추천 일정을 미리 계산하는 로직을 사용자의 요청 트랜잭션에서 분리하여 비동기로 처리하는 방법도 고려해 볼 만 합니다. 만약 비동기 처리에 실패한 경우 failover하는 선택지도 있지만, 캐시가 준비되지 않더라도 사용자 요청 시점에 계산하여 캐싱할 수 있으므로 개발 공수에 따라 선택적으로 구현해도 될 것이라 생각합니다.
한 미팅에 어느 일정이라도 수정되는 순간, 해당 미팅 관련 추천 정보는 valid bit
등을 활용하여 모두 캐시 무효화를 진행하고, 약속 일정 투표는 해당 약속의 초대 URL이 공유된 직후에 가장 많이 일어난다는 점을 활용하여 엔트리의 TTL을 적절히 설정해 주면 캐시가 너무 많은 메모리를 점유하지 않도록 조절할 수 있습니다.
현재 was 인스턴스는 t4g.small
타입 인스턴스를 사용합니다. 이는 듀얼코어, 램2GB 인스턴스로 절대적인 성능이 좋다고 할 수 없습니다. 실제로 부하 발생 시 SWAP 파일을 활용하는 것을 확인할 수 있었습니다. 메모리 뿐만 아니라 CPU 바운드 작업에서 CPU 사용량이 최대치로 올라가는 것으로 볼 때, 인스턴스 자체의 성능 한계로 인해 TPS 가 더 이상 증가하지 않는 것으로 판단할 수 있습니다.
수평확장으로 부하를 분산할 수도 있지만, 메모리가 부족한 상황이므로 인스턴스 타입을 한 단계 조정하는 방식으로도 성능 개선이 가능할 것으로 보입니다.