제목 그대로의 문제 상황에 맞닥뜨렸다고 가정해 보자. 이러한 서비스의 성능을 개선해야 할 때 고려할 수 있는 방식은 정말 많을 것이다. 그 중 어떤 방식이 성능을 개선하는데 큰 영향을 줄 수 있을지 고민해보고, 각 개선안에 장단점을 비교해보자.
이는 오직 백엔드 개발 관점에서 생각하고 작성한 글이다. 이와 같은 문제를 개선하는데엔 프론트엔드 측면이나, 정책적인 변경 등 다양한 해결책이 있을 수 있다는 점을 염두에 두자.
페이지네이션만 잘 적용해도 성능이 상당히 좋아질 질 것 같은데?
처음부터 어렵게 접근하지 말고, 아주 단순한 게시판 서비스라고 생각해보자. 게시글이 늘어날 수록 처음 게시글 목록의 가짓 수도 늘어날텐데, 만약 페이지네이션이 적용이 되어있지 않고, 모든 게시글 데이터를 읽어오는 상황일 수 있다. 그렇다면, 어플리케이션 레벨에서 페이지네이션만 잘 적용해줘도 성능이 크게 올라갈 것이다.
첫 페이지를 최신순으로 조회하려니까 여전히 똑같이 느린데?
일반적인 게시글 목록을 생각해 봤을 때, 정렬 기준을 나열해 볼 수 있다. 좋아요 순, 조회순, 등록일 순. 문제를 단순화 하기 위해 오직 등록일 순이 최신인 게시글을 먼저 불러와야 한다고 생각해 보자.
첫번째 페이지를 불러오는 SQL 쿼리는 다음과 같을 것이다.
SELECT * FROM Posts
ORDER BY created_at DESC
LIMIT 0, 10
고작 10개의 데이터를 불러오는데 왜 여전히 느린걸까? 문제는 ORDER BY 에 있다. SQL 쿼리에서 ORDER BY 절을 사용할 때 해당 컬럼에 인덱스가 설정되어 있지 않다면, 데이터베이스는 테이블의 모든 행을 스캔하여 정렬을 수행해야 한다. 즉 10개의 데이터를 불러오는데 수백만개의 데이터를 먼저 살펴봐야 할 수 있다는 거다.
따라서, 이 경우에는 created_at 에 인덱스를 추가해주는 것이 적절한 해결책일 것이다.
다만, 인덱스를 추가할 경우에는 쓰기 연산의 오버헤드가 증가한다는 것을 항상 염두에 두고 있어야 한다.
이미 페이지네이션이랑 인덱스 잘 적용해 놓았는데도 느린건데..
그렇다. 사실, 대부분의 서버 개발자라면 페이지네이션과 인덱스 정도는 적용해 두었을 것이라고 생각한다. 그럼에도 유저가 많이 증가하여 서비스가 느리다면, 어떤 방식을 적용할 수 있을까?
가장 먼저 떠올려 볼 것은 캐싱이다.
사용자가 몰릴 때, 모든 요청에 대한 데이터 처리를 RDBMS 에서 진행하는 것은 비교적 느릴 수 있다. RDBMS 는 디스크에 데이터를 저장하고 읽어오는 방식으로 동작하기 때문이다.
이럴 경우에는 인메모리 데이터 저장소 Redis 등을 활용하는 것이 성능 개선에 큰 도움을 줄 수 있다. 특히, 이 서비스의 요청과 같이 많은 유저들이 같은 결과를 요청하는 경우에는 더욱 효과가 좋을 것이다.
그래서 레디스는 어떻게 어떤식으로 활용하면 되는걸까? 늘 성능 개선안으로 캐싱, 레디스가 언급되지만 어떤 usecase 에 맞게 어떤식으로 처리해 줘야하는지 전혀 감이 안온다. 이는 그만큼 각 usecase 에 따라 다른 방식이 적용되기 때문일 것 같기도 한데, 이를 조금 더 잘 이해해 보기 위해 지금 포스팅의 주제에서는 어떤식으로 캐시를 활용해야 하는 것일지 고민해 보았다.
최신순으로 몇개의 페이지를 통으로 캐시하면 어떨까?
말 그대로 최신 게시글 목록을 페이지별로 캐시해 둘 수도 있다. 그런데 이 방법에는 문제가 좀 있어 보인다.
- 페이지 사이즈는 항상 고정될 것인가? 유저마다, 디바이스 사이즈 마다 변경되면 어떡할 것인가?
- 사용자가 많이 들어와서 캐싱을 적용하는건데, 첫번째 페이지도 빠르게 변화되지 않을까? 초 단위로 게시글이 수정되고, 삭제되고, 추가될텐데 이 때마다 캐시를 비워주고 새롭게 채우는것이면 너무 비효율적이지 않을까?
- 심지어 게시글 하나가 수정된건데 그 페이지를 통으로 비웠다 채운다. 너무 멍청한 방법 아닐까?
- 그렇다고 TTL (Time-To-Live) 를 길게 설정하는 방식이나 수정되어도 나중에 처리하는 방식은 데이터 일관성에 문제가 있지 않은가?
- 내가 유저라면 게시글을 삭제하거나 추가했을 때 첫 페이지에 내 변경사항이 적용되길 기대할텐데 그렇지 않아서 불만족스럽지 않을까?
- 이렇게 되면 캐싱에 의미가 있는것일까?
이러한 의구점들을 해소할 수 있는 좀 더 나은 방법이 있을까?
각각의 게시글들을 따로 캐시하고, 최신 게시글 목록의 id 값도 따로 캐시하는건 어떨까?
이렇게 되면 위 의구심들을 대부분 해소할 수 있을 것 같다.
- 페이지 사이즈가 고정되지 않아도 된다. 디바이스나 유저의 선호에 따라 사이즈를 조절하고, 그 사이즈를 key 값으로 각각 다른 게시글 목록 id 를 가져오게 할 수 있을 것이다.
- 그렇게 가져온 목록 id 를 통해 다시한번 캐시에서 게시글 데이터를 가져오고 이를 리턴하면 데이터베이스를 거치지 않고 여전히 빠르게 응답할 수 있다.
- 게시글 하나가 수정되거나, 삭제되거나, 추가되었을 경우 해당 게시글 각각에 대한 캐시만 관리해 주면 되고, 삭제나 추가된 경우에는 게시글 목록 id 값을 즉각 수정해 주면 된다.
이 외에도 캐시와 관련해서 고려할 것은 많다. 조회수가 오를 때 마다 데이터베이스를 업데이트 하는 것은 비효율 적이기 때문에 먼저 캐시에 업데이트하고 Write-back 전략을 통해 저장해 주는 것이 좋을 것이다. 좋아요 수, 댓글 수는 유저가 보다 더 민감해 할 수 있는 데이터이기 때문에 Write-Through 전략을 고민해 볼 수도 있다.
그래서 얼마나 빨라지는데?
솔직히 몇배 빨라진다, 몇퍼센트 좋아진다 라고 어림잡아 말하는 것은 감조차 오지 않는다. 주어진 리소스 상황마다 다를 것이고 시스템이 복잡하게 엮여있기 때문에 고려할 요소가 너무 많다.
이를 알아보는 가장 좋은 방법은 성능 테스트를 해보는 것일 것 같다. 이에 대해서는 나중에 직접 다뤄보며 공부해 보기로 하자.
더 고려해 볼 것들..
이 글의 내용에선 일반적으로 고려해 볼 만한 요소들을 다뤘다고 할 수 있을 것 같다. 하지만, 진정한 성능 개선을 위해선 모니터링이 먼저여야 하지 않을까?
생각만으로 병목이 어디서 발생할지는 어림잡을 순 있어도 정확하게 알 수 없다. 어플리케이션 서버가 부족한 것일 수도 있고, 데이터베이스 성능이 딸리는 것일 수도 있다. 서버를 스케일 아웃 하고, 데이터베이스의 레플리케이션을 만들거나 샤딩을 하는 방법 등 이를 개선할 수 있는 방법은 많고 복잡하다.
이에 대한 이론적인 공부도 필수이고, 모니터링을 통해 이를 개선해 나가는 실습 과정도 중요할 것 같다고 생각한다.
'시스템 디자인' 카테고리의 다른 글
샤딩에 대한 이해 (+레플리케이션에 대한 이해) (0) | 2024.04.30 |
---|