문제 이해 및 설계 범위 확정
지원해야 하는 범위?
중요한 기능은?
- 뉴스 피드 페이지에 새로운 스토리를 올릴 수 있어야 하고, 친구들이 올리는 스토리를 볼 수 있어야 함
뉴스 피드 스토리 정렬 기준은?
사용자당 최대 친구 숫자
트래픽 규모
이미지, 비디오 등도 스토리에 포함될 수 있나?
개략적 설계안 제시 및 동의 구하기
중요 API
피드 발행 API
- POST /v1/me/feed
- body에 포스팅 내용, Authorization헤더에 API 호출을 인증하기 위해 사용
피드 읽기 API
- GET /v1/me/feed
- Authorization 헤더에 인증 정보
피드 발행
- User는 모바일 앱이나 웹에서 새로운 포스팅을 올리는 주체이다.
- Post /v1/me/feed api를 사용한다.
- Load Balancer를 이용해서 트래픽을 웹 서버들로 분산한다.
- Web Servers http 요청을 내부 서비스로 중계한다.
- Post service는 새 포스팅을 데이터베이스 및 캐시에 저장
- Fanout service는 새 포스팅을 친구들의 뉴스 피드에 푸시하고 빠르게 읽어 갈 수 있도록 캐시에 보관한다.
- Notification service: 친구들에게 새 포스팅이 올라왔음을 알리거나 푸시 알림을 보내는 역할을 담당한다.
뉴스 피드 생성
- User: 뉴스 피드를 읽는 주체이고 Get /v1/me/feed API를 이용한다.
- Load Balancer를 이용해서 트래픽을 웹 서버들로 분산한다.
- Web Servers http 요청을 News feed 서비스로 보낸다.
- News feed service: 캐시에서 뉴스 피드를 가져오는 서비스.
- News feed cache: 뉴스 피드를 렌더링할 때 필요한 feed ID 보관한다.
상세 설계
피드 발행 흐름 상세 설계
웹 서버와 포스팅 전송 서비스 (fanout service)에 초점을 둔다.
웹 서버
- Authorization헤더 기반 인증 검증후 올바른 인증을 사용한 사용자만 포스팅이 가능하게 해야함
- 처리율 제한 장치를 두어 한 사용자가 올릴 수 있는 포스팅의 수를 제한해야 한다. (스팸 및 유해 콘텐츠 방지)
Fanout Service(포스팅 전송 서비스)
- 어떤 사용자의 새 포스팅을 그 사용자와 친구 관계에 있는 모든 사용자에게 전달하는 과정을 fanout이라고 부른다.
- fanout에는 두가지 모델이 있다. fanout-on-write (푸시 모델), fanout-on-read(pull model)
- fanout-on-write (푸시 모델)
- 새로운 포스팅이 기록되는 시점에 뉴스 피드를 갱신한다. 즉 포스팅 완료시 연관된 친구들의 뉴스피드 캐시를 갱신한다.
- 장점
- 뉴스 피드가 실시간 갱신되며 친구 목록에 있는 사용자에게 즉시 전송
- 뉴스 피드를 읽는데 드는 시간이 짧아진다. 왜냐하면 기록되는 순간에 뉴스피드가 이미 갱신되었음으로
- 단점
- 친구가 많은 사용자의 경우 친구 목록을 가져오고 그 목록에 있는 모든 친구들의 뉴스 피드를 갱신하는데 많은 시간이 소요 될 수 있다. hotkey 문제
- 서비스를 자주 이용하지 않는 사용자의 피드를 갱신함으로 컴퓨팅 자원 낭비
- fanout-on-read (풀 모델)
- 피드를 읽어야 하는 시점에 뉴스 피드를 생성한다.
- 장점
- 비활성화된 사용자 또는 거의 사용하지 않는 사용자의 경우에 유리하다 왜냐하면 읽기 전까지 컴퓨팅 자원이 소모되지 않음으로
- hotkey문제도 없다 왜냐하면 사전에 친구들에게 데이터 푸시 작업이 없기 때문이다.
- 단점
- 뉴스 피드를 읽는 데 많은 시간이 소요될 수 있다.
- 본 설계안에서는 두가지 방법을 결합한다. celebrities(follwers가 많은 사용자)를 제외한 사용자에 대해서는 push 모델을 사용한다. 하지만 유명인들의 posts는 followers가 가져갈때 pull 모델로 가져가서 news feed를 생성한다.
- 안정해시 기법으로 유명인들의 request또는 데이터를 보다 고루 분배할 수 있다.
- Graph DB에서 친구 ID 목록을 가져온다.
- 사용자 정보 캐시에서 친구들의 정보를 가져온다. 그후 친구들중 피드 업데이트 무시 또는 일부 사용자에게만 공개등 설정을 확인 한다.
- 친구 목록과 새 스토리의 포스팅 ID를 메시지 큐에 넣는다.
- 메시지 큐에서 꺼내어 뉴스 피드 캐시 넣는다.
- <post_id, user_id>를 쌍으로 넣는다. 모든 post 정보나 user 정보를 넣지 않는 이유는 메모리 크기를 적정 수준으로 유지하기 위해서이다.
- 몇천개의 post를 유저가 스크롤링 하면서 읽을 가능성은 낮기 때문에 캐시 미스가 일어날 확률은 적다.
피드 읽기 흐름 상세 설계
- 사용자가 피드를 읽는 요청을 보낸다.
- 로드 밸런서가 요청을 웹 서버 가운데 하나로 보낸다.
- 웹 서버는 피드를 가져오기 위해 뉴스 피드 서비스 호출한다.
- 뉴스 피드 서비스는 뉴스피드 캐시에서 포스팅 ID 목록을 가져온다.
- 뉴스 피드에 표시할 사용자 이름, 사용자 사진, 포스팅 컨텐르, 이미지 등을 사용자 캐시와 포스팅 캐시에서 가져와 완전한 뉴스 피드를 만든다.
- 생선도니 뉴스 피드를 JOSN 형태로 클라이언트에 응답한다.
캐시 구조
- News Feed: 피드 아이디를 보관한다.
- Content: post 내용을 보관하고 인기가 많은 즉 많이 조회된는 피드 내용은 hot cache에 보관한다.
- Social Graph: 사용자간의 관계 정보를 보관한다.
- Action: 사용자가 post에 대한 행위를 보관한다. "좋아요, 답글, 등등"
- Counter: 좋아요 횟수, 응답 수, 팔로워 수, 팔로잉 수 등의 정보를 보관한다.
마무리
추후로 논의하면 좋을 사항들
데이터베이스 규모 확장
- 수직적 규모 확장 vs 수평적 규모 확장
- SQL vs NoSQL
- Master/Slave 다중화
- 복제본에 대한 읽기 연산
- 일관성 모델
- 데이터베이스 샤딩
기타
- 웹 계층 무상태로 운영하기
- 가능한 많은 데이터를 캐시할 벙법
- 여러 데이터 센터 지원 방법
- 메시지 큐를 사용하여 컴포넌트 사이의 결합도 낮추기
- 핵심 메트릭 모니터링.ex) 트래픽이 몰리느 시간대의 QPS, 사용자가 뉴스 피드 새로고침시 지연시간
질문
- NewsFeed Cache 구조 단순 <post_id, user_id>구조가 적당할까?
- 조회시 friend id를 key로 조회가능?
- 가능하다고 해도 freind 너무 많으면 in 절로 전체 조회가 가능한가?
- user_id(feedOwner):<post_id, user_id(friend)> 요구조는 더 낫지 않을까?
- <post_id, user_id(friend)> pair가 중복되어서 들어갈 수는 있지만 조회시 성능을 위한다면 감수 할 수 있지 않을까? 그리고 id만 들어가 공간이 많이 낭비되지는 않지 않을까?
- fanout on read도 지금 fanout 서비스 똑같은 구조에서 가능할까?
- 조회시 캐시에 없으면 해당 유저 친구들의 모든 피드를 조회 한후 캐시에 넣는다? 그리고 친구 한명이라도 feed를 생성하면 전체 캐시를 또 날리는 구조?
- 아님 조회할때 마다 새로 생성?
- 하이브리드 구조에서 유명인의 글을 뉴스 피드를 생성해 놓지 않는다면 어떤 구조일까?
- 유명인의 팔로워 -> 해당 유명인의 피드를 그때 캐시에서 모든 친구들에게 갱신??
- 캐시 구조란? 5계층이란 무슨 의미 각 계층이 하위 계층이랑 연관 있음?
twiter 구조?