티스토리 뷰

반응형

구글 드라이브, 드롭박스, 마이크로소프트 원드라이브, 애플 아이클라우드 등의 클라우드 저장소 서비스는 최근 높은 인기를 누리게 된 대표적 클라우드 서비스다. 이번 장에서는 그 가운데 구글 드라이브 서비스를 설계해 보도록 할 것이다.

설계에 들어가기 앞서 일단 구글 드라이브 서비스가 어떤 서비스 인지 알아보자. 구글 드라이브는 파일 저장 및 동기화 서비스로, 문서, 사진, 비디오, 기타 파일을 클라우드에 보관할 수 있도록 한다. 이 파일은 컴퓨터, 스마트폰, 태블릿 등 어떤 단말에서도 이용 가능해야 한다. 아울러 보관된 파일은 친구, 가족, 동료 들과 손쉽게 공유할 수 있어야 한다. 

1 문제 이해 및 설계 범위 확정

구글 드라이브를 설계하는 것은 큰 프로젝트다. 그러니 질문을 통해 설계 범위를 좁혀야 한다.

지원자: 가장 중요하게 지원해야 할 기능들은 무엇인가요?
면접관: 파일 업로드/다운로드, 파일 동기화, 그리고 알림(notification)입니다.
지원자: 모바일 앱이나 웹 앱 가운데 하나만 지원하면 되나요, 아니면 둘 다 지원해야 합니까?
면접관: 둘 다 지원해야 합니다.
지원자: 파일을 암호화해야 할까요?
면접관: 
지원자: 파일 크기에 제한이 있습니까?
면접관: 10GB 제한이 있습니다.
지원자: 사용자는 얼마나 됩니까?
면접관: 일간 능동 사용자(DAU) 기준으로 천만명입니다.

이번 장에서는 다음 기능의 설계에 집중할 것이다.

- 파일 추가. 가장 쉬운 방법은 파일을 구글 드라이브 안으로 떨구는(drag-and-drop) 것이다.
- 파일 다운로드.
- 여러 단말에 파일 동기화. 한 단말에서 파일을 추가하면 다른 단말에도 자동으로 동기화되어야 한다.
- 파일 갱신 이력 조회(revision history)
- 파일 공유
- 파일이 편집되거나 삭제되거나 새롭게 공유되었을 때 알림 표시.

이번 장에서는 다음 기능은 논의하지 않을 것이다.

- 구글 문서(Google doc) 편집 및 협업(collaboration) 기능. 구글 문서는 여러 사용자가 같은 문서를 동시에 편집할 수 있도록 하는데, 이 부분은 설계 범위에서 제외한다.

기능적 요구사항 이외에, 다음의 비 기능적 요구사항을 이해하는 것도 중요하다. 

- 안정성: 저장소 시스템에서 안정성은 아주 중요하다. 데이터 손실은 발생하면 안 된다.
- 빠른 동기화 속도: 파일 동기화에 시간이 너무 많이 걸리면 사용자는 인내심을 잃고 해당 제품을 더 이상 사용하지 않게 될 것이다.
- 네트워크 대역폭: 이 제품이 네트워크 대역폭을 불필요하게 많이 소모한다면 사용자는 좋아하지 않을 것이다. 모바일 데이터 플랜을 사용하는 경우라면 더욱 그렇다.
- 규모 확장성: 이 시스템은 아주 많은 양의 트래픽도 처리 가능해야 한다.
- 높은 가용성: 일부 서버에 장애가 발생하거나, 느려지거나, 네트워크 일부가 끊겨도 시스템은 계속 사용 가능해야 한다.

개략적 추정치

- 가입 사용자는 오천만 명이고 천만 명의 DAU 사용자가 있다고 가정
- 모든 사용자에게 10GB 무료 저장공간 할당
- 매일 각 사용자가 평균 2개의 파일을 업로드 한다고 가정. 각 파일의 평균 크기는 500KB
- 읽기:쓰기 비율은 1:1
- 필요한 저장공간 총량=5천만 사용자 * 10GB = 500페타바이트(Petabyte)
- 업로드 API QPS = 1천만 사용자 * 2회 업로드 / 24시간 / 3600 초 = 약 240
- 최대 QPS = QPS * 2 = 480

2 개략적 설계안 제시 및 동의 구하기

개략적 설계안의 다이어그램을 제시하고 시작하는 대신, 이번에는 다른 접근법을 취해보겠다. 모든 것을 담은 한 대 서버에서 출발해 점진적으로 천만 사용자 지원이 가능한 시스템으로 발전시켜 나가는 것이다. 이 연습문제를 풀어 나가다 보면 앞에서 다룬 여러 가지 중요한 주체들이 다시 떠오를 것이다.

우선 아래와 같은 구성의 서버 한 대로 시작해보자.

- 파일을 올리고 다운로드 하는 과정을 처리할 웹 서버
- 사용자 데이터, 로그인 정보, 파일 정보 등의 메타데이터를 보관할 데이터베이스
- 파일을 저장할 저장소 시스템. 파일 저장을 위해 1TB의 공간을 사용할 것 이다.

몇 시간 정도 들여서 아파치 웹 서버를 설치하고, MySQL 데이터베이스를 깔고, 업로드되는 파일을 저장할 drive/ 라는 디렉터리를 준비한다. drive/ 디렉터리 안에는 네임스페이스(namespace)라 불리는 하위 디렉터리들을 둔다. 각 네임스페이스 안에는 특정 사용자가 올린 파일이 보관된다. 이 파일들은 원래 파일과 같은 이름을 갖는다. 각 파일과 폴더는 그 상대 경로를 네임스페이스 이름과 결합하면 유일하게 식별해 낼 수 있다.

API

이 시스템은 어떤 API를 제공해야 할까? 기본적으로 세 가지 API가 필요하다. 파일 업로드 API, 다운로드 API, 그리고 파일 갱신 히스토리 제공 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": "/recieps/soup/best_soup.txt"
}

* 3. 파일 갱신 히스토리 API

https://api.example.com/files/list_revisions 

인자

- path: 갱신 히스토리를 가져올 파일의 경로
- limit: 히스토리 길이의 최대치

{
	"path": "/recipes/soup/best_soup.txt",
    "limit": 20
}

지금까지 나열한 모든 API는 사용자 인증을 필요로 하고 HTTPS 프로토콜을 사용해야한다. SSL을 지원하는 프로토콜을 이용하는 이유는 클라이언트와 백엔드 서버가 주고받는 데이터를 보호하기 위한것이다.

한 대 서버의 제약 극복

업로드되는 파일이 많아지다 보면 결국에는 파일 시스템은 가득 차게 된다. 이렇게 되면 사용자는 더 이상 파일을 올릴 수 없게 되므로, 긴급히 문제를 해결해야 한다. 가장 먼저 떠오르는 해결책은 데이터를 샤딩하여 여러 서버에 나누어 저장하는 것이다. 

밤새워 시스템 구성을 변경하고 주의깊게 모니터링하는 상황을 떠올려보자. 모든 것이 다시 안정적으로 움직일 것이다. 급한 불은 껐지만, 아마 여러분은 서버에 장애가 생기면 데이터를 잃게 되지 않을까 여전히 걱정하고 있을 것이다. 해결책을 알아보려 백엔드 전문가인 친구에게 물었더니, 넷플릭스나 에어비앤비 같은 시장 주도 기업들은 저장소로 아마존 S3를 사용한다고 한다. 아마존 S3는 업계 최고 수준의 규모 확장성, 가용성, 보안, 성능을 제공하는 객체 저장소 서비스다. 좀 더 알아보니 우리 서비스에도 잘 어울릴 것 같다는 생각이 든다.

많은 자료를 검토한 결과 S3 서비스에 대해 잘 알게 되었고, 결국 우리 서비스의 파일도 S3에 저장하기로 한다. S3는 다중화를 지원하는데, 같은 지역 안에서 다중화를 할 수도 있고 여러 지역에 걸쳐 다중화를 할 수도 있다. AWS 서비스 지역(region)은 아마존 AWS가 데이터 센터를 운영하는 지리적 영역이다. 다음 그림과 같이, 데이터를 다중화 할 때는 같은 지역안에서만 할 수도 있고(왼쪽 그림) 여러 지역에 걸쳐 할 수도 있다.(오른쪽 그림) 여러 지역에 걸쳐 다중화하면 데이터 손실을 막고 가용성을 최대한 보장할 수 있으므로 그렇게 하기로 한다. S3 버킷(bucket)은 마치 파일 시스템의 폴더와도 같은 것이다.

파일을 S3에 넣고 나니 이제 데이터 손실 걱정 없이 잠을 청할 수 있다. 그러나 미래에 비슷한 문제가 벌어지는 것을 막기 위해, 개선할 부분을 좀 더 찾아보기로 한다. 그리고 다음과 같은 부분을 좀 더 연구해 보기로 결정했다.

- 로드밸런서: 네트워크 트래픽을 분산하기 위해 로드밸런서를 사용한다. 로드밸런서는 트래픽을 고르게 분산할 수 있을 뿐 아니라, 특정 웹 서버에 장애가 발생하면 자동으로 해당 서버를 우회해준다.
- 웹 서버: 로드밸런서를 추가하고 나면 더 많은 웹 서버를 손쉽게 추가할 수 있다. 따라서 트래픽이 폭증해도 쉽게 대응이 가능하다.
- 메타데이터 데이터베이스: 데이터베이스를 파일 저장 서버에서 분리하여 SPOF를 회피한다. 아울러 다중화 및 샤딩 정책을 적용하여 가용성과 규모 확장성 요구사항에 대응한다.
- 파일 저장소: S3를 파일 저장소로 사용하고 가용성과 데이터 무손실을 보장하기 위해 두 개 이상의 지역의 데이터를 다중화 한다.

이 모든 부분을 개선하고 나면 웹 서버, 메타데이터 데이터베이스, 파일 저장소가 한 대 서버에서 여러 서버로 잘 부니로디었을 것이다. 다음 그림은 이에 맞게 수정한 설계안이다.

동기화 충돌

구글 드라이브 같은 대형 저장소 시스템의 경우 때때로 동기화 충돌이 발생할 수 있다. 두 명 이상의 사용자가 같은 파일이나 폴더를 동시에 업데이트하려고 하는 경우다. 이런 충돌은 어떻게 해소할 수 있을까? 여기서는 다음 전략을 사용할 것이다. 먼저 처리되는 변경은 성공한 것으로 보고, 나중에 처리되는 변경은 충돌이 발생한 것으로 표시하는 것이다.

사용자 1과 2는 같은 파일을 동시에 갱신하려 한다. 하지만 시스템은 사용자 1의 파일을 먼저 처리했다. 따라서 사용자 1의 파일 갱신 시도는 정상적으로 처리되지만 사용자 2에 대해서는 동기화 충돌 오류가 발생할 것이다. 이 오류는 어떻게 해결해야 하나? 오류가 발생한 시점에 이 시스템에는 같은 파일의 두 가지 버전이 존재하게 된다. 즉 사용자 2가 가지고 있는 로컬 사본과 서버에 있는 최신 버전이 그것이다. 이 상태에서 사용자는 두 파일을 하나로 합칠지 아니면 둘 중 하나를 다른 파일로 대체할지를 결정해야 한다. 

여러 사용자가 같은 문서를 편집할 때 발생할 수 있는 동기화 문제를 해결하는 것은 흥미로운 과제다. https://neil.fraser.name/writing/sync/ 관심있는 독자는 이 문서를 읽어보자.

개략적 설계안

다음 그림은 이번 면접 문제에 대한 개략적 설계안이다. 지금부터 각각의 컴포넌트에 대해 조금 더 상세히 알아보자.

사용자 단말: 사용자가 이용하는 웹브라우저나 모바일 앱 등의 클라이언트.
블록 저장소 서버(block server): 파일 블록을 클라우드 저장소에 업로드하는 서버다. 블록 저장소는 블록 수준 저장소(block-level storage)라고도 하며, 클라우드 환경에서 데이터 파일을 저장하는 기술이다. 이 저장소는 파일을 여러개의 블록으로 나눠 저장하며, 각 블록에는 고유한 해시값이 할당된다. 이 해시값은 메타데이터 데이터베이스에 저장된다. 각 블록은 독립적인 객체로 취급되며 클라우드 저장소 시스템(본 설계안의 경우에는 S3)에 보관된다. 파일을 재구성하려면 블록들을 원래 순서대로 합쳐야 한다. 예시한 설계안의 경우 한 블록은 드롭박스의 사례를 참고하여 최대 4MB로 정했다.
클라우드 저장소: 파일은 블록 단위로 나눠져 클라우드 저장소에 보관된다.
아카이빙 저장소(cold storage): 오랫동안 사용되지 않은 비활성(inactive) 데이터를 저장하기 위한 컴퓨터 시스템이다.
로드밸런서: 요청을 모든 API 서버에 고르게 분산하는 구실을 한다.
API 서버: 파일 업로드 외에 거의 모든 것을 담당하는 서버다. 사용자 인증, 사용자 프로파일 관리, 파일 메타데이터 갱신 등에 사용된다.
메타데이터 데이터베이스: 사용자, 파일, 블록, 버전 등의 메타데이터 저보를 관리한다. 실제 파일은 클라우드에 보관하며, 이 데이터베이스에는 오직 메타 데이터만 둔다는 것을 명심하자.
메타데이터 캐시: 성능을 높이기 위해 자주 쓰이는 메타데이터는 캐시한다.
알림 서비스: 특정 이벤트가 발생했음을 클라이언트에게 알리는데 쓰이는 발생/구독 프로토콜 기반 시스템이다. 예시 설계안의 경우에는 클라이언트에게 파일이 추가되었거나, 편집되었거나, 삭제되었음을 알려, 파일의 최신 상태를 확인하도록 하는데 쓰인다.
오프라인 사용자 백업 큐(offline backup queue): 클라이언트가 접속 중이 아니라서 파일의 최신 상태를 확인할 수 없을 때는 해당 정보를 이 큐에 두어 나중에 클라이언트가 접속했을 때 동기화될 수 있도록 한다.

지금까지 구글 드라이브의 개략적 설계안을 살펴보았다. 핵심 컴포넌트 가운데 어떤 것은 복잡해서 좀 더 자세하게 들여다 볼 필요가 있다. 이에 대해서는 다음 절에서 다룰 것이다.

3 상세 설계

이번 절에서는 블록 저장소 서버, 메타데이터 데이터베이스 ,업로드 절차, 다운로드 절차, 알림 서비스, 파일 저장소 공간 및 장애 처리 흐름에 대해 좀 더 자세히 알아 볼 것이다.

블록 저장소 서버

정기적으로 갱신되는 큰 파일들은 업데이트가 일어날 때마다 전체 파일을 서버로 보내면 네트워크 대역폭을 많이 잡아먹게 된다. 이를 최적화하는 방법으로는 두 가지 정도를 생가해 볼 수 있다.

- 델타 동기화(delta sync): 파일이 수정되면 전체 파일 대신 수정이 일어난 블록만 동기화하는 것이다.
- 압축(compression): 블록 단위로 압축해 두면 데이터 크기를 많이 줄일 수 있다. 이때 압축 알고리즘은 파일 유형에 따라 정한다. 예를 들어 텍스트 파일을 압축할 때는 gzip이나 bzip2를 쓰고, 이미지나 비디오를 압축할 때는 다른 압축 알고리즘을 쓰는 것이다.

이 시스템에서 블록 저장소 서버는 파일 업로드에 관계된 힘든 일을 처리하는 컴포넌트다. 클라이언트가 보낸 파일을 블록 단위로 나눠야 하고, 각 블록에 압축 알고리즘을 적용해야 하고, 암호화까지 해야 한다. 아울러 전체 파일을 저장소 시스템으로 보내는 대신 수정된 블록만 전송해야 한다.

새 파일이 추가되었을 때 블록 저장소 서버가 어떻게 동작하는지 다음 그림에 나와있다.

- 주어진 파일을 작은 블록들로 분할한다.
- 각 블록을 압축한다.
- 클라우드 저장소로 보내기 전에 암호화한다.
- 클라우드 저장소로 보낸다.

다음 그림은 델타 동기화 전략이 어떻게 동작하는지를 보여준다. 검정색으로 표시된 블록 2와 5는 수정된 블록이다. 갱신된 부분만 동기화해야 하므로 이 두 블록만 클라우드 저장소에 업로드 하면 된다.

블록 저장소 서버에 델타 동기화 전략과 압축 알고리즘을 도입하였으므로, 네트워크 대역폭 사용량을 절감할 수 있다.

높은 일관성 요구사항

이 시스템은 강한 일관성(strong consistency) 모델을 기본으로 지원해야 한다. 같은 파일이 단말이나 사용자에 따라 다르게 보이는 것은 허용 할 수 없다는 뜻이다. 메타데이터 캐시와 데이터베이스 계층에도 같은 원칙이 적용되어야 한다.

메모리 캐시는 보통 최종 일관성(eventual consistency) 모델을 지원한다. 따라서 강한 일관성을 달성하려면 다음 사항을 보장해야 한다.

- 캐시에 보관된 사본과 데이터베이스에 있는 원본(master)이 일치한다.
- 데이터베이스에 보관된 원ㄴ본에 변경이 발생하면 캐시에 있는 사본을 무효화한다.

관계형 데이터베이스는 ACID(Atomicty, Consistency, Isolation, Durability)를 보장하므로 강한 일관성을 보장하기 쉽다. 하지만 NoSQL 데이터베이스는 이를 기본으로 지원하지 않으므로, 동기화 로직 안에 프로그램해 넣어야 한다. 본 설계안에서는 ACID를 기본 지원하는 관계형 데이터베이스를 채택하여 높은 일관성 요구사항에 대응할 것이다.

메타데이터 데이터베이스

다음 그림은 이 데이터베이스의 스키마 설계안이다. 중요한 것만 간추린, 아주 단순화된 형태의 스키마임에 유의하자

user: user 테이블에는 이름, 이메일, 프로필 사진 등 사용자에 관계된 기본적 정보들이 보관된다.
device: device 테이블에는 단말 정보가 보관된다. push_id는 모바일 푸시 알림을 보내고 받기 위한 것이다. 한 사용자가 여러 대의 단말을 가질 수 있음에 유의하자.
namespace: namespace 테이블에는 사용자의 루트 디렉터리 정보가 보관된다.
file: file 테이블에는 파일의 최신정보가 보관된다.
file_version: 파일의 갱신 이력이 보관되는 테이블이다. 이 테이블에 보관되는 레코드는 전부 읽기 전용이다. 이는 갱신 이력이 훼손되는 것을 막기 위한 조치다.
block: 파일 블록에 대한 정보를 보관하는 테이블이다. 특정 버전의 파일은 파일 블록을 올바른 순서로 조합하기만 하면 복원해 낼 수 있다.

업로드 절차

사용자가 파일을 업로드하면 무슨 일이 벌어지는지 자세히 살펴보자. 다음 그림은 시퀀스 다이어그램을 이용해 설명하겠다.

- 파일 메타데이터 추가
1. 클라이언트 1이 새 파일의 메타데이터를 추가하기 위한 요청 전송
2. 새 파일의 메타데이터를 데이터베이스에 저장하고 업로드 상태를 대기 중(pending)으로 변경
3. 새 파일이 추가되었음을 알림 서비스에 통지
4. 알림 서비스는 관련된 클라이언트(클라이언트 2)에게 파일이 업로드 되고 있음을 알림

- 파일을 클라우드 저장소에 업로드
2.1 클라이언트 1이 파일을 블록 저장소 서버에 업로드
2.2 블록 저장소 서버는 파일을 블록 단위로 쪼갠 다음 압축하고 암호화 한 다음에 클라우드 저장소에 전송
2.3 업로드가 끝나면 클라우드 스토리지는 완료 콜백(callback)을 호출. 이 콜백 호출은 API 서버로 전송됨
2.4 메타데이터 DB에 기록된 해당 파일의 상태를 완료(uploaded)로 변경
2.5 알림 서비스에 파일 업로드가 끝났음을 통지
2.6 알림 서비스는 관련된 클라이언트(클라이언트 2)에게 파일 업로드가 끝났음을 알림

파일을 수정하는 경우에도 흐름은 비슷하다. 따라서 따로 설명하지는 않겠다.

다운로드 절차

파일 다운로드는 파일이 새로 추가되거나 편집되면 자동으로 시작된다. 그렇다면 클라이언트는 다른 클라이언트가 파일을 편집하거나 추가했다는 사실을 어떻게 감지하는 것일까? 두 가지 방법을 사용한다.

- 클라이언트 A가 접속 중이고 다른 클라이언트가 파일을 변경하면 알림 서비스가 클라이언트 A에게 변경이 발생했으니 새 버전을 끌어가야 한다고 알린다.
- 클라이언트 A가 네트워크에 연결된 상태가 아닐 경우에는 데이터는 캐시에 보관될 것이다. 해당 클라이언트의 상태가 접속 중으로 바뀌면 그때 해당클라이언트는 새 버전을 가져갈 것이다.

어떤 파일이 변경되었음을 감지한 클라이언트는 우선 API 서버를 통해 메타데이터를 새로 가져가야하고, 그 다음에 블록들을 다운받아 파일을 재구성해야 한다. 다음 그림은 자세한 흐름을 보여준다. 지면 한계상 가장 중요한 컴포넌트 들만 그렸음에 유의하자.

1. 알림 서비스가 클라이언트 2에게 누군가 파일을 변경했음을 알림
2. 알림을 확인한 클라이언트 2는 새로운 메타데이터를 요청
3. API 서버는 메타데이터 데이터베이스에게 새 메타데이터 요청
4. API 서버에게 새 메타데이터가 반환됨
5. 클라이언트 2에게 새 메타데이터가 반환됨
6. 클라이언트 2는 새 메타데이터를 받는 즉시 블록 다운로드 요청 전송
7. 블록 저장소 서버는 클라우드 저장소에서 블록 다운로드
8. 클라우드 저장소는 블록 서버에 요청된 블록 반환
9. 블록 저장소 서버는 클라이언트에게 요청된 블록 반환. 클라이언트 2는 전송된 블록을 사용하여 파일 재구성

알림 서비스

파일의 일관성을 유지하기 위해, 클라이언트는 로컬에서 파일이 수정되었음을 감지하는 순간 다른 클라이언트에 그 사실을 알려서 충돌 가능성을 줄여야 한다. 알림 서비스는 그 목적으로 이용된다. 단순하게 보자면 알림 서비스는 이벤트 데이터를 클라이언트들로 보내는 서비스다. 따라서 다음 두 가지 정도의 선택지가 있다.

- 롱 폴링(long poling). 드롭박스가 이 방식을 채택하고 있다.
- 웹 소켓(web socket). 클라이언트와 서버 사이에 지속적인 통신 채널을 제공한다. 따라서 양방향 통신이 가능하다.

둘 다 좋은 방안이지만 본 설계안의 경우에는 롱 폴링을 사용할 것인데 이유는 다음과 같다.

- 채팅 서비스와는 달리, 본 시스템의 경우에는 알림 서비스와 양방향 통신이 필요하지 않다. 서버는 파일이 변경된 사실을 클라이언트에게 알려주어야 하지만 반대 방향의 통신은 요구 되지 않는다.
- 웹소켓은 실시간 양방향 통신이 요구되는 채팅 같은 응용에 적합하다. 구글드라이브의 경우 알림을 보낼 일은 그렇게 자주 발생하지 않으며, 알림을 보내야 하는 경우에도 단시간에 많은 양의 데이터를 보낼일은 없다.

롱 폴링 방안을 쓰게 되면 각 클라이언트는 알림 서버와 롱 폴링용 연결을 유지하다가 특정 파일에 대한 변경을 감지하면 해당 연결을 끊는다. 이 때 클라이언트는 반드시 메타데이터 서버와 연결해 파일의 최신 내역을 다운로드 해야한다. 해당 다운로드 작업이 끝났거나 연결 타임아웃 시간에 도달한 경우에는 즉시 새 요청을 보내어 롱 폴링을 연결을 복원하고 유지해야 한다.

저장소 공간 절약

파일 갱신 이력을 보존하고 안정성을 보장하기 위해서는 파일의 여러 버전을 여러 데이터센터에 보관할 필요가 있다. 그런 상황에서 모든 버전을 자주 백업하게 되면 저장용량이 너무 빨리 소진될 가능성이 있다. 이런 문제를 피하고 비용을 절감하기 위해서는 보통 아래 세 가지 방법을 사용한다.

- 중복 제거(de-dupe): 중복된 파일 블록을 계정 차원에서 제거하는 방법이다. 두 블록이 같은 블록인지는 해시 값을 비교하여 판단한다.
- 지능적 백업 전략을 도입한다. 다음과 같은 전략을 생각해 볼 수 있다.
  - 한도 설정: 보관해야 하는 파일 버전 개수에 상한을 두는 것이다. 상한에 도달하면 제일 오래된 버전은 버린다.
  - 중요한 버전만 보관: 어떤 파일은 아주 자주 바뀐다. 예를 들어 편집 중인 문서가 업데이트 될 때마다 새로운 버전으로 관리한다면 짧은 시간 동안 1000개가 넘는 버전이 만들어 질 수도 있다. 불필요한 버전과 사본이 만들어지는 것을 피하려면 그 가운데 중요한 것만 골라내야한다.
- 자주 쓰이지 않는 데이터는 아카이빙 저장소(cold storage)로 옮긴다. 몇달 혹은 수년간 이용되지 않는 데이터가 이에 해당한다. 아마존 S3 글래시어(galcier) 같은 아카이빙 저장소 이용료는 S3보다 훨씬 저렴하다.

장애 처리

장애는 대규모 시스템이라면 피할 수 없는 것으로, 설계 시 그 점을 반드시 고려해야한다. 면접관이 관심 있어 할만한 부류의 장애로는 다음과 같은 것이 있다.
- 로드밸런서 장애: 로드밸런서에 장애가 발생할 경우 세컨더리 로드밸런서가 활성화되어 트래픽을 이어받아야 한다. 로드밸러서끼리는 보통 박동 신호를 주기적으로 본새ㅓ 상태를 모니터링한다. 일정 시간 동안 박동 신호에 응답하지 않은 로드밸런서는 장애가 발생한 것으로 간주한다.
- 블록 저장소 서버 장애: 블록 저장소 서버에 장애가 발생하였다면 다른 서버가 미완료 상태 또는 대기 상태인 작업을 이어받아야 한다.
- 클라우드 저장소 장애: S3 버킷은 여러 지역에 다중화할 수 있으므로, 한 지역에서 장애가 발생하였다면 다른 지역에서 파일을 가져오면 된다.
- API 서버 장애: API 서버들은 무상태 서버다. 따라서 로드밸런서는 API 서버에 장애가 발생하면 트래픽을 해당 서버로 보내지 않음으로써 장애 서버를 격리할 것이다.
- 메타데이터 캐시 장애: 메타데이터 캐시 서버도 다중화한다. 따라서 한 노드에 장애가 생겨도 다른 노드에 데이터를 가져올 수 있다. 장애가 발생한 서버는 새 서버로 교체하면 된다.
- 메타데이터 데이터베이스 장애
  - 주 데이터베이스 서버 장애: 부 데이터베이스 서버 가운데 하나를 주 데이터베이스 서버로 바꾸고, 부 데이터베이스 서버를 새로 하나 추가한다.
  - 부 데이터베이스 서버 장애: 다른 부 데이터베이스 서버가 읽기 연산을 처리하도록 하고 그 동안 장애 서버는 새 것으로 교체한다.
- 알림 서비스 장애: 접속 중인 모든 사용자는 알림 서버와 롱 폴링 연결을 하나씩 유지한다. 따라서 알림 서비스는 많은 사용자와의 연결을 유지하고 관리해야 한다. 2012년도에 있었던 드롭박스 행사의 발표자료에 따르면, 한대의 드롭박스 알림 서비스 서버가 관리하는 연결의 수는 1백만 개가 넘는다. 따라서 한 대 서버에 장애가 발생하면 백만 명 이상의 사용자가 롱 폴링 연결을 다시 만들어야 한다. 주의할 것은 한 대 서버로 백만 개 이상의 접속을 유지하는 것은 가능하지만, 동시에 백만 개 접속을 '시작'하는 것은 불가능하다는 점이다. 따라서 롱 폴링 연결을 복구하는 것은 상대적으로 느릴 수 있다.
- 오프라인 사용자 백업 큐 장애: 이 큐 또한 다중화해 두어야 한다. 큐에 장애가 발생하면 구독 중인 클라이언트들은 백업 큐로 구독 관계를 재설정해야 할 것이다.

4 마무리

이번 장에서 우리는 구글 드라이브 시스템을 설계해 보았다. 높은 수준의 일관성, 낮은 네트워크 지연, 그리고 빠른 동기화가 요구된다는 점이 설계 과정을 흥미진진하게 만들었다. 이번 장에서 만든 설계안은 크게 두 가지 부분으로 구서오딘다. 파일의 메타데이터를 관리하는 부분과, 파일 동기화를 처리하는 부분이 그것이다. 알림 서비스는 이 두부분과 병존하는 또 하나의 중요 컴포넌트다. 롱 폴링을 사용하여 클라이언트로 하여금 파일의 상태를 최신으로 유지할 수 있도록 한다.

다른 시스템 설계 면접 문제와 마찬가지로, 이번장에서 다룬 문제에도 정답은 없다. 회사마다 요구하는 제약조건이 달라질 테니 그에 맞게 설계를 진행해야 한다. 그 과정에서 내린 결정들과 선택한 기술들 이면에 어떤 생각이 있었는지 면접관에게 설명할 수 있도록 잘 기억해두도록 하자. 설계를 마치고 시간이 좀 남는다면 , 설계안에 어떤 다른 선택지가 있었는지 논의해보면 좋을 것이다.

예를 들어, 블록 저장소 서버를 거치지 않고 파일을 클라우드 저장소에 직접 업로드 한다면? 이 방법의 장점은, 파일 전송을 클라우드 저장소로 직접 하면 되니까 업로드 시간이 빨라질 수 잇다는 것이다. 하지만 이 방법에는 몇 가지 단점이 있다.

- 분할, 압축, 암호화 로직을 클라이언트에 두어야 하므로 플랫폼별로 따로 구현해야 한다.(iOS, 안드로이드, 웹 등). 당초 설계안에서는 이 모두를 블록 저장소 서버라는 곳에 모아뒀으므로 그럴 필요가 없었다.
- 클라이언트가 해킹 당할 가능성이 있으므로 암호화 로직을 클라이언트 안에 두는 것은 적절치 않은 선택일 수 있다.

또 하나 생가해 볼 만한 것은, 접속상태를 관리하는 로직을 별도 서비스로 옮기는 것이다. 그렇게 해서 관련 로직을 알림 서비스에 분리해 내면 다른 서비스에서도 쉽게 활용할 수 있게 되므로 좋을 것이다.

여기까지 성공적으로 마친 여러분 축하한다! 

반응형