파일 저장 및 동기화 서비스로, 문서, 사진, 비디오, 기타 파일을 클라우드에 보관할 수 있도록 한다. 아울러 보관된 파일은 친구, 가족, 동료 들과 손쉽게 공유할 수 있어야 한다.
1단계 문제 이해 및 설계 범위 확정
설계 범위
파일 업로드/다운로드, 파일 동기화, 알림 기능들을 지원해야 한다.
모바일 앱과 웹 모두 지원해야 한다.
파일 암호화 기능을 지원해야 한다.
파일 크기는 10GB로 제한해야 한다.
일간 능동 사용자 천만명을 지원해야 한다.
이번 장에서 집중할 기능들
파일 추가
가장 쉬운 방법은 파일을 구글 드라이브 안으로 떨구는 것이다.
파일 다운로드
여러 단말에 파일 동기화
한 단말에서 파일을 추가하면 다른 단말에도 자동으로 동기화되어야 한다.
파일 갱신 이력 조회
파일 공유
파일이 편집되거나 삭제되거나 새롭게 공유되었을 때 알림 표시
이번 장에서 논의하지 않을 기능
구글 문서 편집 및 협업 기능
비-기능적 요구사항들
안정성
빠른 동기화 속도
네트워크 대역폭
네트워크 대역폭을 불필요하게 많이 소모하면 안된다.개략
규모 확장성
높은 가용성
개략적 추정치
가입 사용자는 오천만명이고 천만 명의 DAU 사용자가 있다고 가정
모든 사용자에게 10GB의 무료 저장공간 할당
매일 각 사용자가 평균 2개의 파일을 업로드한다고 가정. 각 파일의 평균 크기는 500GB
읽기/쓰기 비율은 1:1
필요한 저장공간 총량 = 5천만 사용자 * 10GB = 500페타바이트
업로드 API QPS = 1천만 사용자 * 2회 업로드 / 24시간 / 3600초 = 약 240
최대 QPS = QPS * 2 = 480
2단계 개략적 설계안 제시 및 동의 구하기
이번 장에서는 모든 것을 한 대 서버에서 출바해 점진적으로 천만 사용자 지원이 가능한 시스템으로 발전시켜 나갈것이다.
우선 아래와 같은 구성의 서버 한 대로 시작한다.
파일을 올리고 다운로드 하는 과정을 처리할 웹 서버
사용자 데이터, 로그인 장보, 파일 정보 등의 메타데이터를 보관할 데이터베이스
파일을 저장할 저장소 시스템, 파일 저장을 위해 1TB의 공간을 사용함
figure 15-3은 driver/ 디렉터리에 실제 파일이 보관된 사례다.
API
1. 파일 업로드 API
이 시스템은 두 가지 종류의 업로드를 지원한다.
단순 업로드 : 파일 크기가 작을 때 사용한다.
이어 올리기 : 파일 사이즈가 크고 네트워크 문제로 업로드가 중단될 가능성이 높다고 생각되면 사용한다.
이어 올리기 API예
이어 올리기 단계
이어 올리기 URI을 받기 위한 최초 요청 전송
데이터를 업로드하고 업로드 상태 모니터링
업로드에 장애가 발생하면 장애 발생시점부터 업로드 재시작
2. 파일 다운로드 API
3. 파일 갱신 히스토리 API
한 대 서버의 제약 극복
업로드되는 파일이 많아 지다 보면 결국에는 figure 15-4와 같이 파일 시스템이 가득차게 된다.
가장 먼저 떠오르는 해결책은 데이터를 샤딩하여 여러 서버에 나누어 저장하는 것이다.
figure 15-5는 user_id를 기준으로 샤딩한 예제이다.
서비스의 파일은 아마존 S3저장소에 저장하기로 한다.
데이터 손실을 방지하기 위해 S3를 다중화 하기로 한다. figure 15-6은 다중화가 어떻게 이루어지는지 나타낸다.
미래에 비슷한 문제가 벌어지는 것을 방지하기 위해 아래와 같은 점들을 개선하기로 한다.
로드밸런서 : 네트워크 트래픽을 분산하기 위해 로드밸런서를 사용한다.
웹 서버 : 로드밸런서를 추가하고 나면 더 많은 웹 서버를 손쉽게 추가할 수 있다. 따라서 트래픽이 폭증해도 쉽게 대응이 가능하다.
메타데이터 데이터베이스 : 데이터베이스를 파일 저장 서버에서 분리하여 SPOF를 회피한다.
파일 저장소 : S3를 파일 저장소로 사용하고 가용성과 데이터 무손실을 보장하기 위해 두 개 이상의 지역에 데이터를 다중화한다.
figure 15-7은 위 수정사항들을 반영한 후 설계안이다.
동기화 충돌
구글 드라이브와 같은 대형 저장소 시스템의 경우 두 명 이상의 사용자가 같은 파일이나 폴더를 동시에 업데이트하려고 하면, 때때로 동기화 충돌이 발생할 수 있다.
이런 충돌을 해소하기 위해 다음의 전략을 사용할 것이다.
먼저 처리되는 변경은 성곤한 것으로 보고, 나중에 처리되는 변경은 충돌이 발생한 것으로 표시하는 것이다.
figure 15-8에서 사용자1과 2는 같은 파일을 동시에 갱신하려 한다. 하지만 이 시스템은 사용자1의 파일을 먼저 처리했다.
사용자 1의 파일 갱신 시도는 정상적으로 처리되지만 사용자 2에 대해서는 동기화 충돌 오류가 발생할것이다.
이 오류에 대한 해결책은 아래와 같다.
오류가 발생한 시점에 이 시스템에는 같은 파일의 두 가지 버전이 존재하게 된다.
즉, 사용자 1가 가지고 있는 로컬 사본과 서버에 있는 최신 버전이 그것이다. 이 상태에서 사용자는 두 파일을 하나로 합칠지 아니면 둘 중 하나를 다른 파일로 대체할지를 결정해야 한다.
개략적 설계안
figure 15-10은 이번 면접 문에에 대한 개략적 설계안이다.
사용자 단말 : 사용자가 이용하는 웹크라우저나 모바일 앱 등의 클라이언트.
블록 저장소 서버 : 파일 블록을 클라우드 저장소에 업로드하는 서버다. 블록 저장소는 블록 수준 저장소라고도 하며, 클라우드 환경에서 데이터 파일을 저장하는 기술이다.
클라우드 저장소 : 파일은 블록 단위로 나눠져 클라우드 저장소에 보관된다.
아카이빙 저장소 : 오랫동안 사용되지 않은 비활성 데이터를 저장하기 위한 컴퓨터 시스템이다.
로드밸런서 : 요청은 모든 API 서버에 고르게 분산하는 구심을 한다.
API 서버 : 파일 업로드 외에 거의 모든 것을 담당하는 서버다. 사용자 인증, 사용자 프로파일 관리, 파일 메타데이터 갱신 등에 사용된다.
메타데이터 데이터베이스 : 사용자, 파일, 블록, 버전 등의 메타데이터 정보를 관리한다. 실제 파일은 클라우드에 보관하며, 이 데이터베이스에는 오직 메타데이터만 둔다.
메타데이터 캐시 : 성능을 높이기 위해 자주 쓰이는 메타데이터는 캐싱한다.
알림 서비스 : 특정 이벤트가 발생했음을 클라이언트에게 알리는데 쓰이는 발생/구독 프로토콜 기반 시스템이다.
오프라인 사용자 백업 큐 : 클라이언트가 접속 중이 아니라서 파일의 최신 상태를 확인할 수 없을 때는 해당 정보를 이 큐에 두어 나중에 클라이언트가 접속했을 때 동기화될 수 있도록 한다.
3단계 상세 설계
블록 저장소 서버
정기적으로 갱신되는 큰 파일들은 업데이트가 일어날 때마다 전체 파일을 서버로 보내면 네트워크 대역폭을 많이 잡아먹게 된다.
이를 최적화하는 방법으로는 두 가지 정도를 생각해 볼 수 있다.
델타 동기화 : 파일이 수정되면 전체 파일 대신 수정이 일어난 블록만 동기화한다.
압축 : 블록 단위로 압축해 두면 데이터 크기를 많이 줄일 수 있다. 이때 압축 알고리즘은 파일 유형에 따라 정한다.
새 파일이 추가되었을 때 블록 저장소 서버가 어떻게 동작하는지 figure 15-11에 나와 있다.
주어진 파일을 작은 블록들로 분할한다.
각 블록을 압축한다.
클라우드 저장소로 보내기 전에 암호화한다.
클라우드 저장소로 보낸다.
figure 15-12는 델타 동기화 전략이 어떻게 동작하는지를 보여준다.
검정색으로 표시된 블록 2와 5는 수정된 블록이다.
갱신된 부분만 동기화해야 하므로 이 두 블록만 클라우드 저장소에 업로드하면 된다.
높은 일관성 요구사항
이 시스템은 강한 일관성 모델을 기본으로 지원해야 한다.
메타데이터 캐시와 데이터베이스 계층에도 같은 원칙이 적용되어야 한다.
메모리 캐시는 보통 최종 일관성 모델을 지원한다. 따라서 강한 일관성을 달성하려면 다음 사항을 보장해야 한다.
캐시에 보관된 사본과 데이터베이스에 있는 원본이 일치한다.
데이터베이스에 보관된 원본에 변경이 발생하면 캐시에 있는 사본을 무효화한다.
관계형 데이터베이스는 ACID(Atomicity, Consistency, Isolation, Durability)를 보장하므로 강한 일관성을 보장하기 쉽다.
NoSQL 데이터베이스는 ACID를 지원하지 않으므로, 동기화 로직 안에 프로그램해 넣어야 한다.
본 설계에서는 ACID를 지원하는 관계형 데이터베이스를 채택할 것이다.
메타데이터 데이터베이스
figure 15-13은 이 데이터베이스의 스키마 설계안이다.
user : 이름, 이메일, 프로파일 사진 등 사용자에 관계된 기본적 정보들이 보관된다.
device : 단말 정보가 보관된다.
namespace : 사용자의 루트 디렉터리 정보가 보관된다.
file : 파일의 최신 정보가 보관된다.
file_version : 파일의 갱신 이력이 보관되는 테이블이다.
block : 파일 블록에 대한 정보를 보관하는 테이블이다.
업로드 절차
figure 15-14는 사용자가 파일을 업로드하면 무슨 일이 벌어지는지 설명하는 시퀀스 다이어그램이다.
위 다이어그램은 두 개 요청이 병렬적으로 전송된 상황을 보여준다.
첫 번째 요청은 파일 메타데이터를 추가하기 위한 것이고, 두 번째 요청은 파일을 클라우드 저장소로 업로드하기 위한 것이다.
파일 메타데이터 추가
클라이언트 1이 새 파일의 메타데이터를 추가하기 위한 요청 전송
새 파일의 메타데이터를 데이터베이스에 저장하고 업로드 상태를 대기중으로 변경
새 파일이 추가되었음을 알림 서비스에 통지
알림 서비스는 관련된 클라이언트에게 파일이 업로드되고 있음을 알림
파일을 클라우드 저장소에 업로드
클라이언트 1이 파일을 블록 저장소 서버에 업로드
블록 저장소 서버는 파일을 블록 단위로 쪼갠 다음 압축하고 암호화 한 다음에 클라우드 저장소에 전송
업로드가 끝나면 클라우드 스토리지는 완료 콜백을 호출, 이 콜백 호출은 API 서버로 전송됨
메타데이터 DB에 기록된 해당 파일의 상태를 완료로 변경
알림 서비스에 파일 업로드가 끝났음을 통지
알림 서비스는 관련된 클라이언트에게 파일 업로드가 끝났음을 알림
파일 수정하는 경우도 위의 흐름과 비슷하다.
다운로드 절차
클라이언트는 다른 클라이언트가 파일을 편집하거나 추가했다는 사실을 아래 두 가지 방법들을 사용해서 감지한다.
클라이언트 A가 접속 중이고 다른 클라이언트가 파일을 변경하면 알림 서비스가 클라이언트 A에게 변경이 발생했으니 새 버전을 끌어가야 한다고 알린다.
클라이언트 A가 네트워크에 연결된 상태가 아닐 경우에는 데이터는 캐시에 보관될 것이다.
figure 15-15는 어떤 파일이 변경되었음을 감지한 클라이언트는 우선 API 서버를 통해 메타데이터를 새로 가져가야 하고, 그 다음에 블록들을 다운받아 파일을 재구성하는 흐름을 보여준다.
알림 서비스가 클라이언트 2에게 누군가 파일을 변경했음을 알림
알림을 확인한 클라이언트 2는 새로운 메타데이터를 요청
API 서버는 메타데이터 데이터베이스에게 새 메타데이터 요청
API 서버에게 새 메타데이터가 반환됨
클라이언트 2에게 새 메타데이터가 반환됨
클라이언트 2는 새 메타데이터를 받는 즉시 블록 다운로드 요청 전송
블록 저장소 서버는 클라우드 저장소에서 블록 다운로드
클라우드 저장소는 블록 서버에 요청된 블록 반환
블록 저장소 서버는 클라이언트에게 요청된 블록 반환, 클라이언트 2는 전송된 블록을 사용하여 파일 재구성
알림 서비스
파일의 일관성을 유지하기 위해, 클라이언트는 로컬에서 파일이 수정되었음을 감지하는 순간 다른 클라이언트에 그 사실을 알려서 충돌 가능성을 줄여야 한다. 알림 서비스는 그 목적으로 이용된다.
알림 서비스는 이벤트 데이터를 클라이언트에게 보내는 서비스가. 따라서 아래 두 가지 정도의 선택지가 있다.
롱 폴링
웹소켓
본 설계안의 경우에는 롱 폴링을 사용할 것인데 이유는 아래와 같다.
채팅 서비스와 달리, 본 시스템의 경우 알림 서비스와 양방향 통신이 필요하지 않다.
웹소켓은 실시간 양방향 통신이 요구되는 채팅 같은 응용에 적합하다. 구글 드라이브의 경우 알림을 보낼 일은 그렇게 자주 발생하지 않으며, 알림을 보내야 하는 경우에도 단시간에 많의 양의 데이터를 보낼 일은 없다.
롱 폴링 방안을 쓰게 되면 각 클라이언트는 알림 서버와 롱 폴링용 연결을 유지하다가 특정 파일에 대한 변경을 감지하면 해당 연결을 끊는다.
이때 클라이언트는 반드시 메타데이터 서버와 연결해 파일의 최신 내역을 다운로드 해야한다.
저장소 공간 절약
저장용량을 너무 빨리 소진하지 않기 위해 아래와 같은 방법을 사용한다.
중복 제거 : 중복된 파일 블록을 계정 차원에서 제거하는 방법이다. 두 블록이 같은 블록인지는 해시 값을 비교하여 판단한다.
지능적 백업 전략을 도입한다.
한도 설정
중요한 버전만 보관
자주 쓰이지 않는 데이터는 아카이빙 저장소로 옮긴다. 몇달 혹은 수년간 이용되지 않은 데이터가 이에 해당한다. 아마존 S3 글래시어같은 아카이빙 저장소 이용료는 S3보다 훨씬 저렴하다.
장애 처리
로드밸런서 장애 : 로드밸런서에 장애가 발생할 경우 부 로드밸런서가 활성화되어 트래픽을 이어받아야 한다.
블록 저장소 서버 장애 : 블록 저장소 서버에 장애가 발생하였다면 다른 서버가 미완료 상태 또는 대기 상태인 작업을 이어받아야 한다.
클라우드 저장소 장애 : S3 버킷은 여러 지역에 다중화할 수 있으므로, 한 지역에서 장애가 발생하였다면 다른 지역에서 파일을 가져오면 된다.
API 서버 장애 : 로드밸런서는 API 서버에 장애가 발생하면 트래픽을 해당 서버로 보내지 않음으로써 장애 서버를 격리할 것이다.
메타데이터 캐시 장애 : 메타데이터 캐시 서버도 다중화한다.
메타데이터 데이터베이스 장애
주 데이터베이스 서버 장애 : 부 데이터베이스 서버 가운데 하나를 주 데이터베이스 서버로 바꾸고, 부 데이터베이스 서버를 새로 하나 추가한다.
부 데이터베이스 서버 장애 : 다른 부 데이터베이스 서버가 읽기 연산을 처리하도록 하고 그동안 장애 서버는 새 것으로 교체한다.
알림 서비스 장애 : 한 대 서버에 장애가 발생하면 사용자들의 롱 폴링 연결을 다시 만들어야 한다. 주의할 것은 한 대 서버로 백만 개 이상의 접속을 유지하는 것은 가능하지만, 동시에 여러 개 접속을 시작하는 것은 불가능하다는 것이다. 따라서 롱 폴링 연결을 복구하는 것은 상대적으로 느릴 수 있다.
댓글 영역