배경
요즘은 소위 특정 유명한 주제의 'Best Practice' 라고 부르는 구현 방법을 쉽게 찾을 수 있다.
근데 이 구현 방법들이 본인이 속한 상황에 맞지 않을 수 있다.
예를들면 특정 인프라를 요구하는 구현들이 있는데, 해당 인프라가 익숙하지 않다면 도입하는 비용과 관리 및 유지보수의 비용까지 생각 했을 때 해당 인프라를 사용하는 것이 본인이 속한 상황에 맞지 않을 수 있다.
그럴때 반드시 Best Practice에서 이야기 하는 구현들을 사용해야 하는가? 라고 한다면 아니라고 생각한다.
그래서 특정 예시를 바탕으로 상황에 맞게 적절한 구현이 뭐가 있을지 탐색 해보고자한다.
선정 주제
특정 분류의 최소값 혹은 상하위 N개의 목록을 구해야하는 요구사항이 있다고 가정하고, 상황에 맞게 어떻게 풀어가야 할 지 고민을 해봤다.
단순 구현
요청마다 N개의 목록을 가져오는 쿼리로 해결한다. MySQL RDB같은 경우에는 값을 기준으로 Order By하고 offset, limit으로 가져오면 쉽게 해결 될 것 같다.
뭐가 됐든, SQL로 푸는 방식이다.
Application에서 해결하기에는 데이터 수가 많으면 메모리에 많은 load를 줄 수 있어서 가져 올 때 해결하는 방식이 적절해보인다.
이 방법에서 개선이 필요한 부분을 생각해보면
- 정렬을 DB에서 진행 -> CPU Load가 DB로 가게 됨
- 상대적으로 오래 걸리는 연산 -> 커넥션을 물고 있음 -> 요청을 많이 처리할 수 없게 됨
다음과 같지만 해당 개선은 서비스의 상황에 따라 개선해야 하는 부분일수도 있고 아닐수도 있다.
요청량이 적은 서비스에서는 고민이 되지 않는 부분이다.
개선 1 - Local Cache
DB 부하를 주지 않는 방법은 캐싱을 도입하는 것이다.
먼저 어플리케이션 레벨에서 프로세스 내부의 메모리로 캐싱하는 방식으로 해당 문제점들을 해결할 수 있어보인다.
하지만 어플리케이션 레벨에서 캐싱을 한다면 다음과 같은 문제가 있을 수 있다.
- Write시 동기화 문제
- Look Aside 방식으로 캐싱을 하는경우 값을 가져와서 캐싱 대상 데이터를 갱신을 한다.
- 멀티 쓰레딩 환경에서 이 방식으로 갱신을 한다고 하면, 데이터 정합성이 안 맞을 수 있다.
- 두 개의 연산(DB 조회 -> Cache 갱신)이기 때문에 write시 lock을 잡는다고 해도 정합성 보정이 어렵다.
- 그렇다고 전체 트렌젝션 과정에서 어플리케이션 락을 잡기에는 성능저하를 감수해야한다.
- 분산 환경 동기화 문제
- 서비스 규모 확장으로 scale out 하는 경우 어플리케이션은 별개의 프로세스가 되기 때문에 메모리 공간이 각자 할당 된다.
- 즉 캐싱하는 데이터가 인스턴스마다 다를 수 있게 된다.
캐시 미스일 경우에 lock을 잡고 조회 + 갱신을 진행한다면 단일 인스턴스만 쓰는 경우에 많은 개선이 이루어 질 수 있다.
본인 서비스 환경이 분산 환경을 고려하지 않아도 될 정도의 규모라면 해당 개선까지 진행해도 상당한 효율을 낼 수 있을 것으로 보인다.
개선 2 - Global Cache
서비스의 규모가 커지고 서비스 특성상 특정 시간대에만 요청이 몰리는 케이스 같은 경우 scale in / out이 자유로운 구조여야 유리하다.
개선 1의 경우 scale out을 하게 되면 각 어플리케이션의 메모리에 존재하는 캐싱 데이터가 다를 수 있기 때문에 이 구조에서 데이터 정합성 문제가 생긴다.
해결을 하기 위해선 Global Cache를 도입한다.
우리는 분산 환경의 동기화 문제를 해결하고자 하기 때문에 Cache 저장소는 어플리케이션 외부로 두어야 한다.
Materialized View를 사용해도 괜찮지만, 우리의 흐름상 서비스의 규모가 커진다고 했으니 이 부분은 제외하고 보면
새로운 저장소를 도입하는 것이 적절하게 부하 분산을 시킬 수 있을 것으로 보인다.
새로운 저장소는 처리량을 높이기 위해 빠른 접근이 가능한 것으로 선정하는게 좋아보이고 In-Memory 저장소가 좋아보인다.
여기서 global cache에서 lock을 획득해서 특정 캐시 동기화시 락을 잡고 갱신할 수 있도록 처리를 해준다면 기존의 동기화 문제도 해결된다.
이제 남은 문제는 변경이 잦은 데이터일시(그래도 조회가 압도적으로 많다는 가정하에) 실시간 동기화를 어떻게 처리할 수 있을지에 관한 고민이 남았다. 해당 내용은 다음 글로 이어서 작성하려고한다.
'허브 살리기 프로젝트' 카테고리의 다른 글
Redis로 Rate Limit 구현 (0) | 2024.06.23 |
---|---|
TopN 구하기 (2) (1) | 2024.06.16 |
로컬에서만 Spring Boot에서 HTML Resource 파일이 제공 가능 했던 이유 (1) | 2024.05.02 |
Spring Kafka Record 삭제하기 (1) | 2024.05.01 |
Spring Boot Warm Up (0) | 2024.04.17 |