시스템 설계 고민 2

가장 간단하게 구현한 시스템 부터 대규모 트래픽을 감당할 수 있을 정도의 시스템 까지
생각하면서..
시스템 설계 방법을 나열해보겠다..
(아래 내용은 트랜잭션 격리 수준에 따라 내용이 좀 달라질 수 있다.)
(틀린 점이 있을 수 있다.. 있으면 댓글 부탁드립니다.)
 
기술 스택 : Spring, JPA, MySQL
 

요구사항

타임라인 기능 설계에 대해 고민해보자..
타임라인 기능이란..
내가 어떤 게시글을 작성하면..
나를 팔로우하는 사람들은 내 게시글을
각자의 타임라인 페이지(보통 home 페이지이다.)에서 볼 수 있는...
기능이다. 
 
 
각 방법에서 쓰기 관점과 조회 관점으로 나눠서 설명하겠다.
A 사용자가 B 사용자를 팔로우 하였고,
B 사용자가 게시글을 쓰는 상황이라 생각하자.
 
 

첫번째 방법

가장 간단하게 생각할 수 있는 설계 방법이다.

post Table 을 위와 같이 설계한다.
 

follow Table 은 위와 같이 설계한다.
 

시스템은 위와 같다.
 

쓰기 관점

자 이제..
B 사용자가 게시물을 작성하면
post Table 에 row 가 하나 추가될 것이다.
 

조회 관점

문제는 조회 관점이다.
A 가 타임라인 페이지를 진입 하는 순간..
다음과 같은 과정이 일어난다.
 
1. 우선 A 가 팔로우 하는 모든 member_id 를 follow Table 에서 조회 한다.
2. 조회한 follow list 를 post Table 로 가서 적재된 게시물을 싹 가져와야 한다.
 
참고
원래 정렬도 해야하겠지만, 주제와 상관 없어서 생략한다.
조인을 하건 쿼리 두번을 하건 주제와 상관 없어서 생략한다.
 
즉, user Table(그림엔 없음) 과 follow Table 이 일대다 관계이고,
follow Table 과 post Table 이 일대다 연관관계를 형성하고 있어서
조회 시점에 큰 부하가 존재하는 것이다. (fan-out 문제)
-> A 가 팔로우 한 사람이 엄청 많다고 생각해보자.. 
 
 
첫번째 방법의 문제점은..
보통 sns 는 게시글을 작성하는 것보다 게시글을 조회하는 상황이 훨씬 많다.
보통 조회와 쓰기는 서로의 성능을 희생시키면 본인의 성능이 올라가는 상황이 많다. (Trade-off)
두번째 방법에서 알아보자..
 
 

두번째 방법

조회 관점에서 일대다 연관관계를 없애보자..
 

위와 같이 timeline Table 을 추가하였다.
 

쓰기 관점

B 사용자가 게시물을 쓰면.. 다음 과정을 따른다.
 
1. post Table 에 row 하나를 저장
2. follow Table 에서 나(B)를 팔로우하는 사용자(A) 들을 리스트로 조회한다.
3. 나를 팔로우하는 사용자 리스트를  member_id 로 하여 timeline Table 에 하나씩 적재한다.
 
첫번째 방법과 비교하여
2번 과정이 조회에서 쓰기로 넘어왔고,
쓰기 성능에 부하가 생기게 되었다.
또한, timeline 이라는 Table 이 추가 되어 대용량 데이터 고민도 필요하게 되었다.
-> 실제 Twitter 에선 Redis 로 해결하며 적절한 TTL 을 정해둔 것 같다.
 

조회 관점

조회 관점에서는..
 
A 사용자가 타임라인 페이지에 들어가면..
다음 과정을 따른다.
 
1. timeline Table 에서 A 의 member_id 로 적재된, 게시물 post_id 를 리스트로 조회한다.
2. post_id 리스트로 post Table 에 가서 게시물을 조회한다.
 
첫번째 방법과 비교하여..
조회 관점의 부하가 많이 준 것을 생각해 볼 수 있다.
특히, 2번 로직은 일대일 매핑으로 이루어졌으며,
팔로우 관련 fan-out 문제가 타임라인 조회에서 게시물 쓰기로 넘어갔다.
 
하지만, 데이터 정합성(일관성) 문제가 생길 수 있다.
첫번째 방법에서 테이블이 추가되었고 그 데이터는 모두 다른 테이블에서 복제한 값이다.
 
 
정리 (시간복잡도, logN 은 생략하였다.)

 게시물 쓰기타임라인 조회
첫번째 방법(fan out on read, pull model)O(1)O({내가 팔로우한 수} * {게시글 수})
두번째 방법(fan out on write, push model)O({나를 팔로우한 수})O({게시글 수})

-> 성능에 가장 영향을 많이 미치는 요소가 팔로우 수이고,
게시글 수는 캐시 외에 어찌할 방법이 없어보인다. (접근을 해야 보여주니..)
 
참고>
twitter 는 push model, facebook 은 pull model 을 따른다고 한다..
 
 

세번째 방법

두번째 방법에서 게시물 쓰기시..
나를 팔로우한 수만큼 timeline Table 에 쓰기 연산을 하는데..
이 로직은 굳이.. 게시물 쓰기 시점에 완료할 필요는 없다.
특히, 엄청난 팔로워를 보유한 연예인들이 게시물 하나 쓰면 대기시간이 엄청나게 오래 걸리니까..
비동기로 빼도 된다.
-> 그러면 첫번째 방법처럼 Post Table 에 row 하나만 추가하면 되는 수준의 시간복잡도를 기대할 수 있다.
 
그러나..
두번째 방법보다 더욱 데이터 일관성을 깊게 생각해야한다.
중복데이터를 비동기로 뺀 덕분에 데이터 일관성은 더 고민해봐야 한다.
-> 시스템 복잡도가 높다..

위 사진은 Twitter 의 System Design 이다.
쓰기 시점에 Tweet(게시물) 은 DB 로 적재하고
팔로워에 해당 게시물을 알리는 로직은 비동기로 빠져있는 것을 볼 수 있다.
조회 시점에도 Redis 를 이용하여 두번째 방법보다 빠르게 조회 할 수 있도록 한 것을 볼 수 있다.
 
 
참고>
아래 링크를 보면.. twitter 의 시스템을 설명해준다.
https://www.youtube.com/watch?v=KmAyPUv9gOY
 
 
 

'대규모 시스템 설계' 카테고리의 다른 글

Layered architecture faults and improvement  (0) 2023.06.22
시스템 설계 고민 1  (0) 2023.06.08
Event-Driven 아키텍처 와 Pub/Sub 모델  (0) 2023.06.03
Hexagonal Architecture 정리  (0) 2023.05.15
CQRS Pattern  (0) 2023.02.15