ch15 구글 드라이브 설계
구글 드라이브 설계
1단계 문제 이해 및 설계 범위 확정
가장 중요하게 지원해야할 기능 -> 파일 업로드 / 다운로드, 파일 동기화, 알림 모바일 앱, 웹 앱 가운데 하나만 지원해야 하는지? 파일을 암호화해야 하는지? 파일 크기에 제한은? 일간 능동 사용자는?
제안된 기능 설계내용
- 파일 추가
- 쉬운 방법은 파일을 구글 드라이브 안으로 drag-and-drop 하는 것
- 파일 다운로드
- 여러 단말에 파일 동기화 : 한 단말에서 파일을 추가하면, 다른 단말에도 자동으로 동기화
- 파일 갱신 이력 조회
- 파일 공유
- 파일이 편집, 삭제, 새롭게 공유되었을때 알림 표시
논의 하지 않는 기능
구글 문서 편집 및 협업 기능 : 여러 사용자가 같은 문서를 동시에 편집할 수 있도록
비기능 요구사항
- 안정성 : 데이터 손실은 발생하면 안된다.
- 빠른 동기화 속도 : 파일 동기화에 시간이 많이 걸리면 사용자는 인내심을 잃는다. 해당 제품을 더 이상 사용하지 않게 된다.
- 네트워크 대역폭 : 대역폭을 불필요하게 사용한다면 사용자는 좋아하지 않을거다.
- 규모 확장성 : 아주 많은 양의 트래픽도 처리 가능해야 한다.
- 높은 가용성 : 일부 서버에 장애가 발생하거나, 느려지거나, 네트워크 일부가 끊겨도 시스템은 계속 사용 가능해야 한다.
개략적 추정치
- 가입 사용자는 오천만(50million), 천만명의 DAU
- 모든 사용자에게 10GB 무료 저장공간 할당
- 매일 각 사용자 평균 2개의 파일 업로드, 파일 평균 크기는 500KB
- 읽기:쓰기 비율은 1:1
- 필요한 저장공간 총량 = 5천만 사용자 X 10GB = 500PB
- 업로드 API QPS = 1천만 사용자 X 2회 업로드 / 24시간 / 3600 초 = 약 240
- 최대 QPS = QPS X 2 = 480
2단계 개략적 설계안 제시 및 동의 구하기
한 대 서버에서 출발해 점진적으로 천만 사용자 지원이 가능한 시스템으로 발전시켜나가기
-
파일을 올리고 다운로드 하는 과정을 처리할 웹 서버
-
사용자 데이터, 로그인 정보, 파일 정보 등의 메타데이터를 보관할 데이터베이스
-
파일을 저장할 저장소 시스템, 파일 저장을 위해 1TB의 공간을 사용
-
drive/ 디렉터리
- 네임스페이스(하위 디렉터리)를 둠
- 각 네임스페이스 안에는 특정 사용자가 올린 파일이 보관
- 이 파일들은 원래 파일과 같은 이름을 가짐
- 각 파일과 폴더는 상대 경로를 네임스페이스 이름과 결합하면 유일하게 식별할 수 있음
API
1. 파일 업로드 API
파일 크기가 작을때 단순한 업로드 파일 사이즈가 크고 업로드가 도중 중단될 가능성이 높다고 생각되면 이어 올리기(resumable upload)
이어 올리기 API 예 https://api.example.com/files/upload?uploadType=resumable
인자
-
uploadType=resumable
-
data : 업로드할 로컬 파일
-
이어올리기 URL을 받기 위한 최초 요청 전송
-
업로드 후 상태 모니터링
-
업로드 장애가 발생, 장애 발생시점부터 재시작
2. 파일 다운로드 API
예: https://api.example.com/files/download
인자
- path : 다운로드할 파일의 경로
예
{
"path" : "/recipes/soup/files/list_revisions
}
3. 파일 갱신 히스토리 API
예 : https://api.example.com/files/list_revisions
인자
- path : 갱신 히스토리를 가져올 파일 경로
- limit : 히스토리 길이의 최대치
예
{
"path" : "/recipes/soup/best_soup.txt",
"limit" : 20
}
이러한 API 는 사용자 인증을 필요로 하고 HTTPS 을 사용해야 한다. SSL(Secure Socket Layer)를 지원하는 프로토콜을 이용하는 이유
한대 서버의 제약 극복
업로드 하다보면 가득 차게 되는데
위와 같이 애매하게 10MB 여유공간 밖에 남지 않은 상태일때는 결국 사용자는 더 이상 파일을 올릴 수 없게 되므로, 긴급히 문제를 해결해야 한다.
해결책1
샤딩 하여 여러 서버에 나누어 저장
서버 장애가 생기면 데이터를 잃을 수 있다.
해결책2
넷플릭스, 에어비엔비 같은 기업들은 저장소로 amazon S3 를 사용한다.
amazonS3 : 업계 최고 수준의 규모 확장성, 가용성, 보안, 성능을 제공하는 객체 저장소 서비스
S3는 다중화를 지원함, 같은 지역 안에서 다중화를 할 수도 있고, 여러 지역에 걸쳐 다중화를 할 수도 있다.
같은 지역 안에서 다중화를 할 수도 있고, 여러 지역에 걸쳐 다중화를 할 수도 있다.
여러 지역에 걸쳐 다중화를 하면 데이터 손실을 막을 수 있고, 가용성을 최대한 보장할 수 있다.
=> 데이터 손실 걱정없어짐
개선할 부분을 추가로 찾아보기
- 로드밸런서, 웹 서버 : 트래픽 고르게 분산 및 장애 난 서버 우회
- 메타데이터 데이터베이스 : 데이터베이스, 파일 저장 서버를 분리하여 SPOF를 회피, 다중화 샤딩 고려해볼 수 있음
- 파일 저장소 : S3를 두 개 이상의 지역에 데이터를 다중화
동기화 충돌
구글 드라이브 같은 대형 저장소 시스템은 때때로 동기화 충돌이 발생할 수 있다.
동기화 충돌 사례
두 명 이상의 사용자가 같은 파일이나 폴더를 동시에 업데이트하려고 하는 경우
어떻게 해소할 수 있을까?
전략
먼저 처리되는 변경은 성공한 것으로 보고, 나중에 처리되는 변경은 충돌이 발생한 것으로 표시
- 사용자 1과 2는 같은 파일을 동시에 갱신하려함
- 시스템은 사용자 1의 파일을 먼저 처리
- 사용자 2에 대해서는 동기화 충돌 오류가 발생할 것임
- 오류가 발생한 지점에 이 시스템에는 같은 파일의 두 가지 버전이 존재함
- 사용자 2가 가지고 있는 로컬 사본과 서버에 있는 최신 버전이 그것임
- 사용자는 두 파일을 하나로 합칠지 아니면 둘 중 하나를 다른 파일로 대체할지 결정해야함
'[4]''[5]' 참고
개략적 설계안
-
사용자 단말 : 웹 브라우저, 모바일 앱
-
블록 저장소 서버(Block servers) : 파일 블록을 클라우드 저장소에 업로드 하는 서버
- 데이터 파일을 저장하는 기술
- 파일을 여러개의 블록으로 나눠 저장, 각 블록에는 고유한 해시값 할당
- 해시값은 메타데이터 데이터베이스에 저장
- 각 블록은 독립적인 객체로 취급, 클라우드 저장소 시스템(S3)에 보관, 파일을 재구성하기 위해 블록들을 원래 순서대로 합쳐야함.
- ex) 한 블록은 드롭박스의 사례를 참고하여 최대 4MB로 정함
-
클라우드 저장소(Cloud storage) : 블록 단위로 나눠져 클라우드 저장소에 보관
-
아카이빙 저장소(cold storage) ; 오랫동안 사용되지 않은 비활성 데이터를 저장하기 위한 컴퓨터 시스템
-
로드밸런서 : 요청 분산
-
API 서버 : 파일 업로드 외 모든 것을 담당, 사용자 인증, 사용자 프로파일 관리, 파일 메타데이터 갱신
-
메타데이터 데이터베이스 : 사용자, 파일, 블록, 버전 등의 메타데이터 정보를 관리
- 실제 파일은 클라우드에 보관,
-
메타데이터 캐시 : 성능을 높이기 위해 자주 쓰이는 메타데이터는 캐싱
-
알림 서비스 : 특정 이벤트가 발생했음을 클라이언트에게 알림, pub/sub 프로토콜 기반
- 클라이언트에게 파일 추가, 편집, 삭제됨을 알려, 파일의 최신 상태를 확인하도록
-
오프라인 사용자 백업 큐(offline backup queue) : 클라이언트가 접속 중이 아닐때, 최신 상태를 확인할 수 없을 때는 큐에 두어 나중에 클라이언트가 접속했을 때 동기화될 수 있도록
핵심 컴포넌트들 중 어떤 것은 복잡해서 좀 더 자세하게 들여다 보자
3단계 상세 설계
- 블록 저장소 서버
- 메타데이터 데이터베이스
- 업로드 절차
- 다운로드 절차
- 알림 서비스
- 파일 저장소 공간 및 장애 처리 흐름
블록 저장소 서버
정기적으로 갱신되는 큰 파일은 업데이트가 일어날 때마다 전체 파일을 서버로 보내면 네트워크 대역폭을 많이 먹는다.
이를 최적화 하기 위한 방법을 제시한다.
- 델타 동기화(delta sync) : 파일이 수정되면 전체 파일 대신 수정이 일어난 블록만 동기화
- 압축(compression) : 블록 단위로 압축해 두면 데이터 크기를 많이 줄일 수 있음
- 압축 알고리즘은 파일 유형에 따라 정함
- 텍스트 파일 압축 -> gzip, bzip2
- 이미지, 비디오 압축 -> 다른 압축 알고리즘,,
블록 저장소 서버는 파일 업로드에 관련된 어려운 일을 처리하는 컴포넌트.
- 주어진 파일을 작은 블록들로 분할(split)
- 각 블록을 압축(compress)
- 클라우드 저장소로 보내기 전에 암호화(encrypt)
- 클라우드 저장소로 보냄
- 검정색이 수정된 블록, 갱신된 부분만 동기화해야 하므로 해당 블록들을 클라우드 저장소로 업로드
=> 네트워크 대역폭 사용량을 절감
높은 일관성 요구사항
강한 일관성 모델을 기본으로 지원해야 한다.
같은 파일이 단말이나 사용자에 따라 다르게 보이는 것은 허용할 수 없음
메타데이터 캐시와 데이터베이스 계층에도 같은 원칙이 적용되어야 함
메모리 캐시는 결과적 일관성 모델을 지원
강한 일관성을 달성하기 위해서
- 캐시에 보관된 사본과 데이터베이스에 있는 원본이 일치해야한다.
- 데이터베이스에 보관된 원본에 변경이 발생하면 캐시에 있는 사본을 무효화한다.
RDB는 ACID를 보장하기에 강환 일관성을 보장하기 쉬움 하지만, NOSQL DB는 기본으로 지원하지 않기에, 동기화 로직으로 처리해야함
해당 설계안은 RDB를 채택하여 일관성 요구사항에 대응함
메타데이터 데이터베이스
- user : 이름, 이메일, 프로파일 사진
- device : 단말 정보, push_id는 모바일 푸시알림, 한 사용자가 여러 단말 가질 수 있게
- namespace : 사용자 루트 디렉터리 정보
- file : 파일의 최신 정보
- file_version : 파일 갱신 이력, 레코드 전부 읽기 전용, 갱신 이력이 훼손되는 것을 막기 위해
- block : 파일 블록에 대한 정보, 특정 버전의 파일은 파일 블록을 올바른 순서로 조합하기만 하면 복원 가능
업로드 절차
두 개 요청이 병렬적으로 전송된 상황을 보여줌 첫번째 요청은 파일 메타데이터를 추가하기 위한 것 두번째 요청은 파일을 클라우드 저장소로 업로드하기 위한 것 두 요청 모두 클라이언트 1이 보냄
- 파일 메타데이터 추가
- 새 파일 메타데이터 추가하기 위한 요청
- 새 파일 메타데이터를 데이터베이스에 저장, 업로드 상태 대기중으로 변경
- 새 파일이 추가되었음을 알림 서비스에 통지
- 알림 서비스는 관련된 클라이언트(client2)에게 파일이 업로드 되고 있음을 알림
- 파일을 클라우드 저장소에 업로드 2.1 클라이언트 1이 파일을 블록 저장소 서버에 업로드 2.2 블록 저장소 서버는 파일을 블록 단위로 쪼갠 다음 압축하고 암호화 하고 클라우드 저장소에 전송 2.3 업로드가 끝나면 클라우드 스토리지는 완료 콜백(callback)을 호출, 콜백 호출은 API 서버로 전송 2.4 메타데이터 DB에 기록된 해당 파일의 상태를 완료(uploaded)로 변경 2.5 알림 서비스에 파일 업로드가 끝났음을 통지 2.6 알림 서비스는 관련된 클라이언트(client 2)에게 파일 업로드가 끝났음을 알림
다운로드 절차
클라이언트는 다른 클라이언트가 파일을 편집하거나 추가했다는 사실을 어떻게 감지하는 것일까?
- A가 접속중, 다른 클라이언트가 파일 변경하면 알림 서비스가 A에게 변경이 발생해서 새 버전을 끌어가야 한다고 알림
- A가 네트워크 연결 상태가 아닐 경우, 데이터는 캐시에 보관, 상태가 접속 중으로 바뀌면 새 버전을 가져감
어떤 파일이 변경되었음을 감지한 클라이언트는 API서버를 통해 메타데이터를 새로 가져와야 하고, 그 다음 블록들을 다운받아 파일을 재구성해야 한다.
- 알림 서비스가 클라이언트 2에게 누군가 파일을 변경했음을 알림
- 알림을 확인한 클라이언트 2는 새로운 메타데이터를 요청
- API 서버는 메타데이터 데이터베이스에게 새 메타데이터 요청
- API 서버에게 새 메타데이터가 반환됨
- 클라이언트 2에게 새 메타데이터가 반환됨
- 클라이언트 2는 새 메타데이터를 받는 즉시 블록 다운로드 요청 전송
- 블록 저장소 서버는 클라우드 저장소에서 블록 다운로드
- 클라우드 저장소는 블록 서버에 요청된 블록 반환
- 블록 저장소 서버는 클라이언트에게 요청된 블록 반환. 클라이언트 2는 전송된 블록을 사용하여 파일 재구성
알림 서비스
파일 일관성을 유지하기 위해, 클라이언트는 로컬에서 파일이 수정되었음을 감지하는 순간 다른 클라이언트에 사실을 알려 충돌 가능성을 줄여야 한다.
- 롱 폴링(long polling) : 드롭박스가 이 방식을 채택중
- 웹소켓(WebSocket) : 클라이언트와 서버 사이에 지속적인 통신 채널을 제공한다. 양방향 통신이 가능함
해당 설계안은 롱폴링을 사용할 것이다.
이유
- 알림 서비스와 양방향 통신이 필요하지 않음, 서버는 클라이언트에게 파일 변경사실을 알려주어야하지만 반대는 요구되지 않음
- 알림을 보낼 일이 채팅 시스템만큼 자주 발생하지 않음. 보낼때도 단시간에 많은 양의 데이터를 보낼 일은 없음
클라이언트는 롱 폴링연결로 메타데이터 서버와 연결해 파일 최신 내역을 다운로드 해야한다. 다운로드 작업이 끝났거나 타임아웃에 도달하면 즉시 새 요청을 보내어 연결을 복원하고 유지해야 한다.
저장소 공간 절약
파일 갱신 이력 보존, 안정성 보장하기 위해 여러 버전을 여러 데이터센터에 보관할 필요가 있음
문제
자주 백업하게 되면 저장 용량이 너무 빨리 소진될 가능성이 있음
해결
비용을 절감하기 위해
- 중복 제거(de-dupe) : 중복된 파일 등록을 계정 차원에서 제거, 두 블록이 같은 블록인지는 해시 값을 비교하여 판단
- 지능적 백업 전략 도입
- 한도 설정 : 보관해야 하는 파일 버전 개수에 상한을 둠, 상한에 도달하면 제일 오래된 버전 버림
- 중요한 버전만 보관 : 아주 자주 바뀌는 파일, ex) 편집 중인 문서가 업데이트 될때마다 새로운 버전으로 관리하면 짧은 시간 동안 1000개가 넘게 생길수도 있음, 그중 제일 중요한 것만 골라내기
- 자주 쓰이지 않는 데이터는 아카이빙 저장소(cold storage)로 옮김, 몇달 혹은 수년간 이용되지 않은 데이터가 해당됨, S3 글래시어(glacier) 이용료는 S3보다 저렴
장애 처리
면접관이 관심있어 할 만한 부류의 장애로는 다음과 같은 것이 있다.
- 로드밸런서 장애
- 장애가 발생할 경우, 부 로드밸런서가 활성화되어 트래픽을 받아야한다.
- 로드 밸런서끼리는 보통 박동신호를 주기적으로 보내어 상태를 모니터링한다.
- 일정 시간 동안 응답하지 않으면 장애가 발생한걸로 간주한다.
- 장애가 발생할 경우, 부 로드밸런서가 활성화되어 트래픽을 받아야한다.
- 블록 저장소 서버 장애
- 다른 서버가 미완료 상태 또는 대기 상태인 작업을 이어받아야한다.
- 클라우드 저장소 장애
- S3 버킷은 여러 지역에 다중화할 수 있으니, 다른 지역에서 파일가져오기
- API 서버 장애
- 무상태 서버
- 장애가 발생할 경우, 트래픽을 보내지 않고, 장애 서버를 격리
- 메타데이터 캐시 장애
- 다중화
- 장애가 생겨도 다른 노드에서 데이터 가져오기, 장애난 서버는 새 서버로 교체
- 메타데이터 데이터베이스 장애
- 주 데이터베이스 장애 : 부 데이터베이스 하나를 주 서버로 바꾸고, 부 데이터베이스 서버를 새로 하나 추가
- 부 데이터베이스 장애 : 다른 부 데이터베이스 서버가 읽기 연산을 처리하도록, 장애난 서버는 새것으로 교체
- 알림 서비스 장애
- 롱 폴링 연결을 하나씩 유지
- 드롭박스 행사 발표자료 참고 : 한 대 서버에 1백만이 넘는 사용자가 연결되어있음, 한대 서버에 장애가 발생하면 백만명 이상의 사용자가 롱 폴링 연결을 다시 만들어야함. 접속 유지는 가능하지만 도잇에 백만 개 접속을 '시작'하는 것은 불가능함. 복구하는게 상대적으로 느릴 수 있음
- 오프라인 사용자 백업 큐 장애
- 다중화
- 큐에 장애가 발생하면, 구독 중인 클라이언트들은 백업 큐로 구독 관계를 재설정해야함
4단계 마무리
이번 장 설계안은 아래와 같은 두 가지 부분으로 구성된다
- 파일의 메타데이터를 관리하는 부분
- 파일 동기화를 처리하는 부분
++ 알림서비스
다른 시스템 설계 면접 문제와 마찬가지로, 이번 장에서 다룬 문제에도 정답은 없다.
회사마다 요구하는 제약조건이 달라질 테니 그에 맞게 설계를 진행해야 한다.
그 과정에서 내린 결정들과 선택한 기술들 이면에 어떤 생각이 있었는지 면접관에게 설명할 수 있도록 잘 기억해 두도록 하자.
설계를 마치고 시간이 좀 남는다면, 설계안에 어떤 다른 선택지가 있었는지 논의해보면 좋을 것이다.
ex)
블록 저장소 서버를 거치지 않고 파일을 클라우드 저장소에 직접 업로드 한다면?
장점
파일 전송을 클라우드 저장소로 직접 하면 되니까 업로드 시간이 달라짐
단점
분할, 압축, 암호화 로직을 클라이언트에 두어야 하므로 플랫폼별로 따로 구현해야함 (ios, 안드로이드, 웹) 해당 설계안에서는 블록 저장소 서버라는 곳에 모아 뒀기에 그럴 필요가 없었음
클라이언트가 해킹 당할 가능성이 있으므로 암호화 로직을 클라이언트 안에 두는 것은 적절치 않은 선택일 수 있음
ex2) 접속상태를 관리하는 로직을 별도 서비스로 옮기는 것
관련 로직을 알림서비스에서 분리하면, 다른 서비스에서도 쉽게 활용할 수 있으므로 좋을 것이다.