Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Design Instagram

아래 영상을 분석하려 한다.

이 영상은 인터뷰 관점에서 시스템 설계를 다루고 있다. 요약해보자면
  • 시스템 요구사항을 어떻게 수집할 것인가.
  • 면접관과 어떻게 피드백을 주고 받을 것인가.
  • 디자인을 구체화 해가는 방법
  • 설계를 재검토하고 잠재적인 개선 사항을 제안하는 방법
이다. 나는 인터뷰 관점이 아닌, 시스템 설계 관점에서 정리해보려 한다.

요구사항 수집

요구사항을 수집하는 여러 가지 방법들이 있을 것이다. 나는 User Persona부터 접근 할 것이다. 유저 페르소나는 고객의 특성을 깊이 이해하기 위해서 사용하는 제품 설계 방법으로 통상 제품 기획단계에서 UX 디자이너들이 사용한다.

제품의 목표는 유저가 만족하는 디자인과 기능, 성능을 가지는 제품을 출시하는 것이다. 이를 위해서는 "사용자 관점"에서의 접근이 필요하다. 페르소나는 특정한 성향을 가지는 가상의 사용자인데, 이 가상의 사용자를 만들어서 그들이 요구하는 것을 찾아내고 그걸 제품 디자인에 반영한다. User Persona 부터 접근을 하고 싶지만, 문서의 범위를 넘어가는 것 같아서 건너뛰고 기능위주로 접근을 하겠다.

"사용자들이 핵심적으로 원하는 것"에서 요구사항을 수집하면 된다. 이 서비스에서 사용자(User)는 Posters와 Followers로 나눌 수 있을 것이다.

 요구사항

Posters
  • 사진을 업로드 한다.
  • Follower의 피드를 읽는다.
  • 사진에 인용구를 넣는다.
  • 사진에 필터를 적용 한다.
  • 사진에 캡션을 넣는다.
  • 해시태그를 붙인다.
Followers
  • Posters가 올린 사진에 댓글을 올린다.
  • Posters가 올린 사진에 Like 한다.
  • 맘에 드는 Posters를 following 한다.
  • 인스타그램의 다른 Posters의 feed를 읽는다.

High-Level Component Diagram

보통 시스템 디자인은 높은 수준에서 시작해서 드릴다운 하는 방법을 사용한다. 높은 수준인 시스템 관점에서 사고를 할 때, 전체 시스템의 복잡도와 비용을 효과적으로 계산 할 수 있기 때문이다. 전체 조감도를 만든 후 각 조감도의 작은 부분들을 정밀하게 설계해 나갈 것이다.

 Component diagram

다이어그램은 작업을 수행하는 엔티티를 식별하고 이들 엔티티간의 상호작용을 이해하는데 도움이 된다. 여러가지 다이어그램이 있는데 그중 컴포넌트 다이어그램은 시스템을 구성하는 소프트웨어, 내장된 컨트롤러 이들간의 조직과 종속성을 보여준다. 클래스 다이어그램 보다 높은 수준의 추상화를 가진다. 전체를 조망하고자 할 때 유용한 툴이다. 다이어그램을 분석해보자.

인스타그램의 서비스는 사용자로부터 시작한다. 사용자는 Poster 혹은 Follower로써 사진을 게시하거나 피드를 읽고 쓸수 있다. 사용자의 요청은 로드밸런서로 전달된다. 로드밸런서는 사용자의 요청을 읽어서 Instagram Server로 라우팅한다. Instagram Server는 높은 확장성과 가용성을 가진 Instagram의 웹 애플리케이션 서버의 집합이다.

Instagram Server에서 사용자의 요청은 크게 두 가지 프로세스로 처리한다.

먼저 동기적(Synchronous) 프로세스가 있다. 예를 들어 Bob은 Alice의 사진에 댓글을 남기거나 Like를 할 수 있다. 이러한 작업은 본질적으로 동기적이다. 즉 액션을 취하면 즉시 성공 또는 실패를 반환한다. 기술적으로 보자면 사용자가 HTTP 요청을 보내면 그 요청을 처리하고, 성공 혹은 실패를 리턴한다. 요청, 처리, 응답이 한번의 API 호출에서 끝난다.

(처리 할)데이터를 스트리밍(Kafka나 Kinises)로 보내는 것은 본질적으로 비동기적(Asynchronous)이다. 비동기적인 작업은 요청과 처리가 분리된다.

스트리밍이 필요한 이유를 살펴보자. 스트리밍은 프로듀서(producer)와 컨슈머(consumer)로 구성되는 구독 모델이다. 프로듀서가 처리할 데이터를 Kafka 등으로 보내면, 컨슈머가 이를 구독하고 있다가 데이터를 처리하는 방식이다. 일종의 공유 데이터 파이프라인인셈이다. 따라서 개발자는 서비스 종류에 따라서 다양한 컨슈머를 개발 할 수 있다. 이러한 모델은 다른 마이크로 서비스를 추가하여 시스템을 확장 할 수 있는 유연성을 제공한다.

데이터가 흐르는 파이프라인을 구성했으니, 어떤 데이터를 파이프라인에 보낼지를 결정하면 된다. 예를 들어 Like와 관련된 데이터는 마이크로 서비스에서 처리 할 수 있다. 위 그림에서 우리는 Media Counter Service를 만들었는데, 이 카운터 서비스가 Like 수를 업데이트 하는 작업을 실행한다. 마찬가지로 User Feed 서비스는 Poster나 Follower에게 Feed 업데이트 이벤트를 처리하는 작업을 할 수 있다.

Graph Data Store를 살펴보자. 인스타그램은 소셜 네트워크 서비스(SNS)다. 소셜 네트워크 서비스는 사용자 간의 관계를 나타낼 수 있어야 한다. 이러한 사용자의 관계는 그래프 형태를 가질 것이고 우리는 neo4j를 이용해서 관계 데이터베이스를 구축했다. 그래프 데이터베이스는 복잡한 관계를 처리하는데 효과적이며, 누가 누구의 게시물을 좋아하는지, 누가 누구와 가까운지(친구 추천, 컨텐츠 추천등)등의 관계 기반 서비스 개발에 도움을 준다.

집계 데이터는 카산드라(cassandra) 데이터베이스에 저장한다. DynamoDB, MongoDB와 같은 NoSQL 데이터베스다. 단일 실패 지점이 없으며, 여러 노드에 자동으로 데이터를 복제하는 방식으로 내 결함성을 보장한다. 다운타임 없이 수평확장을 할 수 있기 때문에 대량의 데이터를 유지하는 서비스에 적합하다.

"모든 데이터를 저장하는데 사용 할 수 있는 속편한 단일의 데이터베이스를 지향하지 않는다"는게 핵심이 될 것 같다.

MSA 관점에서 컴포넌트 식별

MSA에서는 특정 도메인 혹은 특정 기능을 전문적으로 처리하는 마이크로 서비스로 구성된다. 앞서 작성한 High-Level Component Diagram과 기능 요구사항을 이용해서 마이크로서비스 컴포넌트를 정의한다. Kubernetes를 기반으로 한다고 가정하면, 이들이 쿠버네티스 서비스가 될 것이다

 MSA Component

개발해야할 컴포넌트들을 식별했다. 이제 이 컴포넌트들에 대한 상세 구현 계획을 수립하면 된다. 그리고 데이터 저장소 역할을 할 데이터베이스도 식별했다. 이렇게 컴포넌트들을 식별했다면, Kubernetes를 기반으로 하는 좀 더 낮은 레벨의 시스템 설계를 할 수 있을 것이다.

 Kubernetes 서비스

시퀀스 다이어 그램

이렇게 해서 핵심 기능과 각 기능을 처리하기 위한 컴포넌트가 정리됐다. 이제 각 기능이 수행되는 과정과 그 과정에 참여하는 컴포넌트들을 포함하는 시퀀스 다이어그램을 그린다.

 Sequence Diagram

  1. 사용자가 이미지를 업로드를 요청한다. 여기에는 이미지외에 다른 정보들도 포함될 것이다.
  2. 사용자 요청을 받은 Posts_Service는 컨텐츠를 Media_Hosting_Service 컴포넌트로 전송한다.
  3. Media_hosting_Service는 이미지를 처리한 다음 File_Storage에 저장한다. 페이스북에 인수되기 전에는 AWS S3에 저장을 했는데, 지금은 페이스북의 자체 스토리지를 사용하는 것으로 보인다.
  4. 저장이 끝나면, 이미지 URL와 메타정보들을 리턴한다.
  5. Posts_Service는 리턴 정보를 graph data storage에 저장한다.
  6. 지금까지의 사용자 활동 정보를 User_Activity_Service_Queue로 전송한다.
  7. 사용자에게 이미지 업로드 작업이 성공했음을 리턴한다.
이 과정은 동기적으로 진행된다. 유저입장에서의 요청 처리는 여기에서 끝난다. 나머지 작업은 비동기적으로 진행된다.
  1. 앞 단계에서 사용자 활동 정보를 User_Activity_Service_Queue로 전송했다. 이 정보는 Kafka와 같은 메시지큐에 저장되어 있을 것이다. User_Activity_Service는 kafka 큐에 있는 정보를 읽어서 처리한다.
  2. 처리한 정보는 User_Activity_Cassandra_Storage 에 저장한다.
  3. 사용자의 followers들은 어떤 컨텐츠가 업로드 됐는지 궁금해 할 것이다. followers에게 피드를 만들어주기 위해서 사용자의 followers의 가져온다.
  4. 이제 follower에게 Feed를 전송해야 한다. 하루에 수십/수백만의 Feed가 올라오고 이 중에서 사용자가 관심있어 할 만한 Feed를 보여줘야 하기 때문에 많은 연산이 필요 할 것이다. 이 연산을 수행하도록 User_Feed_Service_Queue에 작업을 밀어 넣는다.
왜 이런 과정을 비동기적으로 처리해야 하는지 궁금 할 수 있다. 사용자가 인스타그램 앱을 실행 할 때를 생각해보자. 앱이 실행되면 가장 먼저 자신이 following 하고 있는 poster의 feed를 읽을 것이다. Feed를 만들기 위해서 시스템은 다양한 작업을 하게 될테고 많은 시간이 걸릴 것이다. 사용자가 많아지면 대기 시간은 점점 길어질 것이다. 비동기적인 프로세스를 통해서 Feed를 미리 만들어 놓는다면 사용자는 빠르게 Feed를 확인 할 수 있을 것이다.

User_Feed_Service_Queue 컴포넌트는 사용자가 관심있어 할 만한 Feed를 만드는 작업을 한다. 이 작업은 아래와 같은 과정을 거친다.

 seq diagram
  1. Post가 만들어졌다. 이 Post에 대한 정보는 User_Feed_Service_Queue에 들어있다. User_Feed_Service 서비스가 큐에서 Post 정보를 읽어온다.
  2. 해당 Post를 작성한 사용자(Poster)의 follower 목록을 얻어야 한다. Followers_Service에 요청한다.
  3. Followers_Service 서비스는 Graph_Data_Storage에서 Follower 목록을 가져온다.
  4. Follower 목록이 리턴된다.
  5. Follower 목록에 있는 모든 follower에게 전송할 feed를 만든다. 만들어진 feed는 User_Feed_Cassandra_Storage에 저장한다.
해당 Poster의 follower에 대한 feed 정보가 카산드라 디비에 저장되어 있으므로, follower가 feed를 요청할 경우 카산드라 디비에서 즉시 조회 할 수 있다.

Graph DB

이 서비스에서 가장 중요한 정보는 Feed다. 이 Feed는 사람과 사람과의 복잡한 관계 속에서 만들어진다. 전통적인 RDBMS는 이러한 관계 정보를 저장하기에는 적합하지 않다. 우리는 이러한 (그래프 속성을 가지는)관계정보를 처리하기 위한 데이터베이스를 선택해야 한다.

먼저 인스타그램에서 사용할 그래프 데이터 모델을 그려보자.

 Graph Data Model

각각의 정보가 노드가 되고 이들 노드가 연결되는 걸 확인 할 수 있다.
  • Bill Gates가 Post_id_001 을 게시한다.
  • 게시된 내용은 follower인 Alice에게 Feed 형태로 전달될 것이다. Alice는 이 post에 like 한다.
  • Jeff Bezos도 Bill Gates의 post에 Like를 하고 comment를 작성한다.
  • Jeff Bezos의 follower인 Bob은 Jeff Bezos의 comment와 post를 feed로 받고, 답글 형식으로 comment를 작성한다.
이런식으로 관계가 만들어진다. 단순한 모델이지만 여기에서 다른 관계들을 만들 수 있다. 이 그래프에서 Bob이 following 하고 있는 Jeff Bezos가 Bill Gates의 post에 comment를 했다. 따라서 Bob은 Bill Gates에게도 관심을 가질 수 있으며, Bill Gates를 following로 추천하거나 Feed에 노출 시킬 수 있다. Graph db는 노드를 중심으로 연결된 데이터를 가져올 수 있으며, 이를 이용한 추천 서비스를 만들 수 있다.

Streaming Data Model

Streaming 시스템은 Apache Kafka 혹은 AWS Kinesis로 구현 할 수 있다. 여기에 사용되는 데이터 모델은 아래와 같다.
type User struct {
	Id        string
	UserName  string
    FullName  string
    isPrivate bool
}

type Post struct {
    Id               string
    OwnerId          string
    MediaContentType string
}

type LikeEvent struct {
    LikeId                string 
    PostId                string
    PostOwnerId           string
    PostContents          Post 
    Liker                 User
    PostOwner             User
    IsFollowingMediaOwner bool
}

type Follow struct {
    Follower                 User
    FollowedUser             User
    IsFollowedUserCelebrity  bool
}
MediaCounter 서비스는 LikeEvent 스트럭처를 읽어서 처리한다. 여기에는 Like 이벤트가 발생한 post, like 버턴을 클릭한 사용자, post 작성자 정보가 있으므로 이를 기반으로 Like를 카운터할 수 있다. 이 데이터는 시간순으로 저장이되며, 통계적 방식과 기계학습등을 이용해서 사용자에게 적합한 Feed 목록을 만들 수 있다.

정리

내가 관심있는 것은 인스타그램의 상세 아키텍처나 인터뷰 패턴이 아니다. 서비스를 개발하기 위해서 어떤 방식의 접근이 효율적일지가 궁금했다.
  1. 고객 Persona를 작성한다. 서비스가 목표로하는 고객을 실제 만나서 인터뷰해서, 그들의 요구사항을 듣는 것이다. 고객을 만나는게 어렵다면, 다양한 방법으로 정보를 수집해서 가설적으로 고객 persona를 작성한다. 이를 통해서, 고객이 실제 원하는 것이 무엇인지를 알 수 있다. 이를 문서로 정리해 놓으면 사업가, 기획자, 개발자 모두의 방향을 일치시킬 수 있다.
  2. 요구사항을 구체화 한다 : 고객 Persona 만들면서 솔류션(고객의 고충을 어떻게 해결 할 것인지 혹은 고객이 만족할 만한 어떤 새로운 것을 주는 방법)을 정리했을 테고 이 과정에서 요구사항을 구체화 할 수 있다. 이 요구사항은 개발자도 이해할 수 있도록 개발자의 언어로 작성할 수 있다.
  3. High-Level Component Diagram : 요구사항을 처리할 시스템을 만들어야 할 것이다. 현재 단계에서 요구사항은 컴포넌트 레벨로 범주화 할 수 있을 것이다. 기획 단계에서의 요구사항을 상위 개발 수준에 범주화 하면서, 시스템 설계자와 개발자는 요구사항이 어떻게 코드화 될지를 그려볼 수 있다.
  4. MSA 관점에서 컴포넌트 식별 : MSA를 적용할거라면, Kubernetes service 단위로 배치를 계획 할 수 있을 것이다. 각 컴포넌트가 어떤 요구사항을 처리 하는지를 알고 있으므로 그에 맞는 데이터베이스를 계획 할 수 있다.
  5. 컴포넌트가 식별됐다. 이들 컴포넌트는 유저의 데이터(요청)을 받아서 처리하고 그 결과를 다른 컴포넌트로 보낸다. 데이터의 흐름인 것이다. 시퀀스 다이어 그램을 이용해서 컴포넌트를 시스템 차원에서 조직하며, 의존성을 확인 할 수 있다.
  6. 4번 단계쯤에서 개발팀은 user stroy를 만들어서 개발 sprint를 본격적으로 실행 할 수 있다.
아키텍처 관점에서는 서비스는 두 개 이상의 데이터 모델을 사용한다만 주목해서 보면 될 것 같다. 대략 보자면 OLTP, OLAP로 모델을 가져갈 수 있을테다. 서로 다른 데이터 모델은 처리 방식에 차이가 생기기 마련인데, 이 차이에 따라서 동기 / 비동기가 구분된다.