Featured image of post 15. 구글 드라이브 설계

15. 구글 드라이브 설계

가상 면접 사례로 배우는 대규모 시스템 설계 기초

클라우드 저장소 서비스는 최근 높은 인기를 누리게 된 대표적 클라우드 서비스다.

그 가운데 구글 드라이브 서비스를 설계해 본다.

구글 드라이브는 파일 저장 및 동기화 서비스로, 문서, 사진, 비디오, 기타 피일을 클라우드에 보관할 수 있도록 한다.

  • 이 파일은 컴퓨터, 스마트폰, 태블릿 등 어떤 단말에서도 이용 가능해야한다.
  • 보관된 파일은 친구, 가족, 동료 들과 손쉽게 공유할 수 있어야 한다.

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

  • Q. 지원해야할 기능?
    • A. 파일 업로드/다운로드, 파일 동기화, 알림
  • Q. 모바일 앱, 웹 앱 중 지원 범위
    • A. 웹, 앱 둘다
  • Q. 파일 암호화 처리?
    • A. Y
  • Q. 파일 크기 제한?
    • A. 10GB
  • Q. 사용자는?
    • A. DAU 천만명

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

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

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

  • 안정성
    • 저장소 시스템의 안정성은 아주 중요하다.
    • 데이터 손실이 발생하면 안된다.
  • 빠른 동기화
    • 동기화 사긴이 너무 많이 걸리면 인내심을 읽고 제품을 더 이상 사용하지 않을 것
  • 네트워크 대역폭
    • 네트워크 대역폭을 불필요하게 많이 소모한다면 사용자가 좋아하지 않을 것
    • 모바일 데이터 플랜이라면 더욱더
  • 규모 확장성
    • 아주 많은 양의 트래픽도 처리 가능해야 한다.
  • 높은 가용성
    • 일부 서버의 장애, 지연, 네트워크 이슈가 발생해도 계속 사용 가능해야한다.

개략적 추정치

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

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

모든 것을 담은 한 대 서버에서 출발해 점진적으로 천만 사용자 지원이 가능한 시스템으로 발전시켜 나간다.

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

웹 서버, 데이터베이스 설치 후, 파일이 저장될 drive/ 디렉터리를 준비한다.

파일 저장 예시

  • 디렉터리 안에는 네임스페이스라 불리는 하위 디렉터리를 둔다.
  • 각 네임스페이스 안에는 특정 사용자가 올린 파일이 보관된다.
  • 파일들은 원래 파일과 같은 이름을 갖는다.
  • 각 파일과 폴더는 그 상대 경로를 네임스페이스 이름과 결합하면 유일하게 식별할 수 있다.

API

이 시스템은 3가지 API가 필요하다.

파일 업로드 API

단순 업로드

파일 크기가 작을 때 사용한다.

이어 올리기(resumable upload)

파일 사이즈가 크고 네트워크 문제로 업로드가 중단될 가능성이 높다고 생각되면 사용한다.

1
https://api.example.com/files/upload?uploadType=resumable
  • 인자
    • uploadType=resumable
    • data

이어올리기는 다음 세 단계 절차로 이루어진다.

  1. 이어 올리기 URL을 받기 위한 최초 요청 전송
  2. 데이터를 업로드하고 업로드 상태 모니터링
  3. 업로드에 장애가 발생하면 장애 발생시점부터 업로드를 재시작

파일 다운로드 API

1
https://api.example.com/files/download

인자

  • path
    • 다운로드할 파일의 경로
1
2
3
{
  "path": "/recipes/soup/best_soup.txt"
}

파일 갱신 히스토리

1
https://api.exmaple.com/files/list_revisions

인자

  • path: 갱신 히스토리를 가져올 파일의 경로
  • limit: 히스토리 길이의 최대치
1
2
3
4
{
    "path": "/recipes/soup/best_soup.txt",
    "limit": 20
}

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

한 대 서버의 제약 극복

파일이 많아지면 결국 가득 차게되어 더 이상 파일을 올릴 수 없으므로 긴급히 문제를 해결해야한다.

쉬운 해결책으로 샤딩을 통해 데이터를 여러 서버에 나누어 저장하는 방법을 고려할 수 있다.

샤딩

서버를 직접 관리했을 때, 장애가 생기면 데이터를 잃게될 수 있으므로 불안하다.

넷플릭스, 에어비엔비 같은 시장 주도 기업들은 저장소로 아마존 S3를 사용한다.

  • 업계 최고 수준의 규모 확장성, 가용성, 보안, 성능을 제공하는 객체 저장소 서비스
  • 같은 지역 안에서 다중화, 여러 지역에 걸쳐 다중화가 가능하다.

다중화

따라서 S3를 사용을 결정한다. 하지만 미래에 비슷한 문제(사용자 증가로 인해 발생하는 문제)가 벌어지는 것을 막기 위해, 개선할 부분을 더 찾아보면 아래와 같다.

수정한 설계안

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

동기화 충돌

구글 드라이브 같은 대형 저장소 시스템의 경우 동기화 충돌이 발생할 수 있다.

  • 두 명 이상의 사용자가 같은 파일이나 폴더를 동시에 업데이트하려고 하는 경우

충돌 해소 전략

먼저 처리되는 변경은 성공한 것으로 보고, 나중에 처리되는 변경은 충돌이 발생한 것으로 표시하는 전략을 사용할 수 있다.

  1. 사용자 1과 2는 같은 파일을 동시에 갱신을 시도한다.
  2. 사용자 1의 요청이 먼저 처리된다.
  3. 사용자 2는 동기화 충돌 오류가 발생한다.

충돌 오류 해소

오류가 발생한 시점에 이 시스템에는 같은 파일의 두가지 버전이 존재하게 된다.

  • 사용자 2가 가지고 있는 로컬 사본과 서버에 있는 최신 버전

이 상태에서는 두 파일을 하나로 합칠지 아니면 둘 중 하나를 다른 파일로 대체할지를 결정해야한다.

개략적 설계안

개략적 설계안

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

3단계: 상세 설계

블록 저장소 서버

정기적으로 갱신되는 큰 파일들은 업데이트가 일어날 때마다 전체 파일을 서버로 보내면 네트워크 대역폭을 많이 사용하게된다.

이를 최적화하는 방법으로 두 가지 정도를 생각해 볼 수 있다.

  • 델타 동기화(delta sync)
    • 파일이 수정되면 전체 파일 대신 수정이 일어난 블록만 동기화
  • 압축(compression)
    • 블록 단위로 압축해 두면 데이터 크기를 많이 줄일 수 있다.
    • 압축 알고리즘은 파일 유형에 따라 정한다.
    • 텍스트: gzip, bzip2 등

블록 저장소 동작

블록 저장소 서버는 파일 업로드에 관계된 힘든 일을 처리하는 컴포넌트이다.

  • 클라이언트가 보낸 파일을 블록 단위로 나눈다.
  • 각 블록에 압축 알고리즘을 적용한다.
  • 암호화한다.
  • 전체 파일을 저장소 시스템으로 보내는 대신 수정된 블록만 전송한다.

델타 동기화 전략 동작

갱신된 부분만 동기화해야 하므로 이 두 블록만 클라우드 저장소에 업로드한다.

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

높은 일관성 요구사항

구글 드라이브 시스템은 강한 일관성(string consistency) 모델을 기본으로 지원해야한다.

  • 같은 파일이 단말이나 사용자에 따라 다르게 보이는 것을 허용할 수 없다.

메모리 캐시는 보통 결과적 일관성(eventual consistency) 모델은 지원하기 때문에 강한 일관성을 달성하려면 다음 사항을 보장해야한다.

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

관계형 데이터베이스는 ACID를 보장하므로 강한 일관성을 보장하기 쉽다.

하지만 NoSQL 데이터베이스는 이를 기본으로 지원하지 않으므로, 동기화 로직 안에 프로그램해 넣어야 한다.

이번 설계안에는 관계형 데이터베이스를 채택하여 높은 일관성 요구사항에 대응한다.

메타데이터 데이터베이스

중요한 것만 간추린 스키마를 그려보면 아래와 같다.

스키마 설계안

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

업로드 절차

업로드 절차

사용자가 파일을 올리면 두 개의 요청이 병렬적으로 수행되어야 한다.

  • 파일 메타데이터를 추가
    1. 새 파일의 메타데이터를 추가하기 위한 요청 전송
    2. 새 파일의 메타 데이터를 데이터베이스에 저장하고 업로드 상태를 대기중(pending)으로 변경
    3. 새 파일이 추가되었음을 알림 서비스에 통지
    4. 관련된 클라이언트(여기서는 클라이언트 2)에게 파일이 업로드되고 있음을 알림
  • 파일을 클라우드 저장소로 업로드
    1. 파일을 블록 저장소 서버에 업로드
    2. 파일을 블록 단위로 쪼갠 다음 압축, 암호화 후 클라우드 저장소에 전송
    3. 업로드 완료시 클라이드 스토리지는 완료 콜백 호출
    4. 메타데이터 DB에 기록된 해당 파일의 상태를 완료(uploaded)로 변경
    5. 알림 서비스에 파일 업로드가 끝났음을 통지
    6. 알림 서비스가 관련 클라이언트(2)에게 파일 업로드가 끝났음을 알림

수정도 이와 거의 동일하다.

다운로드 절차

파일 다운로드는 파일이 새로 추가되거나 편집되면 자동으로 시작된다.

다른 클라이언트가 파일을 편집하거나 추가했다는 사실은 두 가지 방법을 통해 이루어진다.

  • 클라이언트 A가 접속 중
    • 다른 클라이언트가 파일을 변경하면 알림 서비스가 클라이언트 A에게 변경이 발생했으니 새 버전을 끌어가야 한다고 알림
  • 클라이언트 A가 접속중이 아님
    • 데이터는 캐시에 보관
    • 해당 클라이언트 상태가 접속 중으로 바뀌면 그때 해당 클라이언트가 새 버전을 가져감

파일이 변경되었음을 감지한 클라이언트는

  1. API 서버를 통해 메타데이터를 새로 가져가야한다.
  2. 블록들을 다운받아 파일을 재구성해야 한다.

파일 재구성 흐름

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

알림 서비스

알림 서비스는 파일의 일관성 유지를 위해 사용된다.

  • 클라이언트가 로컬에서 파일이 수정되었음을 감지하는 순간 다른 클라이언트에 그 사실을 알려 충돌 가능성을 줄여야한다.

단순히 보면 알림 서비스는 이벤트 데이터를 클라이언트들로 보내는 서비스이다.

따라서 두 가지 정도 선택지가 있다.

  • 롱 폴링: 드롭박스가 채택하고있음
  • 웹소켓
    • 클라이언트와 서버 사이에 지속적인 통신 채널을 제공하므로 양방향 통신이 가능함

둘 다 좋은 방안이지만 이번 설계안에서는 롱 폴링을 사용한다.

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

따라서 롱 폴링을 채택하며, 아래와 같은 절차가 반복된다.

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

저장소 공간 절약

파일 갱신 이력을 보존하고 안정성을 보장하기 위해서 파일의 여러 버전을 데이터센터에 보관할 필요가 있다.

이 때 모든 버전을 자주 백업하면 저장 용량이 너무 빨리 소진될 가능성이 있으므로 아래와 같은 방식을 활용한다.

  • 중복 제거(de-dupe)
    • 중복된 파일 블록을 계정 차원에서 제거한다.
    • 해시 값을 비교해서 판다.
  • 지능적 백업 전략 도입
    • 한도 설정
      • 파일 버전 개수에 상한을 둔다.
      • 제일 오래된 버전은 버린다.
    • 중요한 버전만 보관
      • 특정 파일은 단기간에 자주 변경되므로 업데이트될 때마다 새로운 버전으로 관리하는 것이 아닌, 중요한 것만 골라내어 보관한다.
  • 아카이빙 저장소(cold storage) 활용
    • 자주 쓰이지 않는 데이터는 아카이빙 저장소로 옮긴다.
    • 아마존 S3 글래지어 같은 아카이빙 저장소 이용료는 매우 저렴하다.

장애 처리

장애는 대규모 시스템이라면 피할 수 없으므로, 설계 시 그 점을 반드시 고려해야한다.

  • 로드밸런서 장애
    • 부(Secondary) 로드밸런서가 활성화되어 트래픽을 이어받아야한다.
    • 로드밸런서끼리 박동 신호를 주기적으로 보내 상태를 모니터링한다.
  • 블록 저장소 서버 장애
    • 다른 서버가 미완료 상태 또는 대기 상태인 작업을 이어받아야한다.
  • 클라우드 저장소 장애
    • S3 버킷은 여러 지역에 다중화 할 수 있으므로, 한 지역에서 장애가 발생하였다면 다른 지역에서 파일을 가져온다.
  • API 서버 장애
    • API 서버는 무상태로, 로드밸런서가 해당 서버에 요청을 보내지 않음으로 장애 서버와 격리한다.
  • 메타데이터 캐시 장애
    • 다중화하여 다른 노드에서 데이터를 가져온다.
    • 장애가 발생한 서버는 새 서버로 교체한다.
  • 메타데이터 데이터베이스 장애
    • 주 데이터베이스 장애
      • 부 데이터베이스 서버 가운데 하나를 주 데이터베이스로 바꾼 후, 부 데이터베이스를 추가한다.
    • 부 데이터베이스 장애
      • 다른 부 데이터베이스 서버가 읽기 연산을 처리하도록 한다.
      • 부 데이터베이스 서버를 새로 하나 추가한다.
  • 알림 서비스 장애
    • 접속중인 모든 사용자가 알림 서버와 롱 폴링 연결을 하나씩 유지하므로, 많은 사용자와의 연결을 유지하고 관리해야한다.
    • 따라서 장애가 발생하면 롱 폴링 연결을 다시 만들어야 하므로 복구하는 것이 상대적으로 느릴 수 있다.
    • 유지는 쉽지만, 연결을 새로 만드는 것은 오래걸린다.
  • 오프라인 사용자 백업 큐 장애
    • 다중화를 통해 구독 중인 클라이언트들은 백업 큐로 구독 관게를 재설정해야한다.

4단계: 마무리

높은 수준의 일관성, 낮은 네트워크 지연, 빠른 동기화가 요구된다는 점이 핵심이었다.

  • 구글 드라이브 시스템은 크게 두 가지 부분으로 구성된다.
    • 파일의 메타데이터를 관리
    • 파일 동기화를 처리
  • 알림 서비스는 이 두 부분과 변존하는 또 하나의 중요 컴포넌트
    • 롱 폴링을 사용하여 클라이언트로 하여금 파일의 상태를 최신으로 유지할 수 있도록 한다.

설계안에 어떤 다른 선택지가 있었는지 살펴보면 좋다.

  • 블록 저장소 서버를 거치지 않고 파일을 클라우드 저장소에 직접 업로드한다면?
    • 업로드 시간이 빨라질 수 있다.
    • 분할, 압축, 암호화 로직이 클라이언트에서 처리되어야한다.
    • 따라서 플랫폼별로 따로 구현해야한다.
    • 클라이언트가 해킹 당할 가능성이 있으므로 암호화 로직이 클라이언트에 두는 것이 적절치 않을 수 있다.
  • 접속상태 관리 로직을 별도 서비스로 옮기는것은?
    • 관련 조릭을 알림 서비스에서 분리하면, 다른 서비스에서도 쉽게 활용할 수 있게될 것이다.