책 리뷰: <가상 면접 사례로 배우는 대규모 시스템 설계 기초>

김동균·2022년 11월 20일
1

책 리뷰

목록 보기
2/5

개인적으로 대규모 트래픽을 감당할 수 있는 시스템 설계에 관심이 있는데, 재직 중인 회사에서는 아직 유의미한 트래픽을 발생시키는 서비스를 운영하지 않고 있기에 소프트웨어 아키텍팅 역량을 쌓고자 이 책을 읽었다.

책에서는 크게 2가지 내용을 다룬다.
첫 번째는 규모 확장성을 이루기 위한 방법에 대한 것이고, 두 번째는 시스템 설계 면접 대비를 위한 개략적인 규모 추정 방법 및 여러 시스템(알림, 뉴스피드 시스템, ..)에 대한 설계 방법이다.

책 내용을 복습할 겸, 규모 확장성을 이루기 위한 방법에 대해 이해한 바를 간략하게 정리해보겠다.

1. 서버 규모 확장

사용자가 늘어남에 따라 단일 서버로 트래픽을 감당하기 힘들어지면 서버의 규모 확장을 해야 하는데, scale up이라고 하는 수직적 규모 확장 방식과, scale out이라고 하는 수평적 규모 확장 방식이 대표적이다.

단일 서버의 사양을 업그레이드하는 scale up 방식이 가장 단순한데, 한 대의 서버에 cpu나 memory 등을 무한대로 증설할 수 없고, 장애 발생 시 failover 방안이나 다중화 처리가 불가능하여 spof를 방지할 수 없다는 단점이 있다.
이런 단점 때문에 scale up 방식으로 대규모 애플리케이션을 지원하기에는 적절하지 않고, 더 많은 서버를 추가하여 성능을 개선하는 scale out 방식이 주로 사용된다.

서버를 scale out 하는 경우 트래픽을 각 서버에 분산하여 처리하는데, 이때 로드 밸런서를 사용한다.
로드밸런서는 부하 분산 집합에 속한 서버들에게 트래픽 부하를 고르게 분산하는 역할을 하고, 동작 방식은 아래와 같다.

이제 서버 1에 장애가 발생해도 로드밸런서가 서버 2로 트래픽을 라우팅하여 처리하기 때문에 failover 방안이 마련되어 spof를 방지할 수 있게 된다.

2. 데이터베이스 다중화

애플리케이션 서버와 마찬가지로 데이터베이스 서버 또한 spof의 방지와 부하 분산을 위한 다중화가 필요하다.
일반적으로 사용되는 많은 RDB들은 다중화 기능을 지원하는데, 서버 사이에 master(주)-slave(부) 관계를 설정하고 데이터 원본은 주 서버에, 데이터 사본은 부 서버에 저장하는 방식이 주로 사용된다.
대부분의 서비스에서는 쓰기 연산보다 읽기 연산이 더 높은 비율로 발생하기 때문에 master보다 slave 데이터베이스의 수를 늘리고, 쓰기 연산을 master에서만 지원하고 slave에서는 master에서 데이터의 사본을 전달받고 읽기 연산만을 지원한다.

위와 같이 데이터베이스를 다중화함으로써 트래픽이 분산되어 병렬로 처리될 수 있는 질의의 수가 늘어나 성능이 향상되고, 특정 데이터베이스 서버에 장애가 발생해도 나머지 정상 동작하는 서버로 트래픽이 라우팅되어 spof을 방지함으로써 서비스의 높은 가용성을 확보할 수 있다.
또한 데이터베이스를 지역적으로 나누어 다중화한다면 자연재해 등의 이유로 일부 서버가 파괴되어도 데이터를 보존할 수 있을 것이다.

애플리케이션 서버를 scale out 하고 로드밸런서를 도입, 데이터베이스 다중화가 적용된 설계안은 아래와 같다.

3. 캐시

웹 계층과 데이터 계층의 규모 확장을 이루었으니, latency를 개선할 차례다.
매번 동일한 웹 페이지가 새로고침 될 때마다 표시할 데이터를 가져오기 위해 한 번 이상의 데이터베이스 호출이 발생한다.
애플리케이션의 성능은 데이터베이스를 얼마나 자주 호출하느냐에 크게 좌우되는데, 캐시를 통해 해당 문제를 완화할 수 있다.

요청을 받은 서버는 캐시에 응답이 저장되어 있는지 확인하고 저장되어 있는 경우 해당 데이터를 클라이언트에게 반환, 저장되어 있지 않은 경우 데이터베이스 질의를 통해 데이터를 찾아 캐시에 저장한 뒤 클라이언트에게 반환한다.

이처럼 캐시는 값비싼 연산 결과 혹은 자주 참조되는 데이터를 메모리 안에 두어 데이터베이스의 부하를 줄여주는데, 사용하기 전 아래 사항들을 고려하여야 한다.

  • 데이터 갱신은 자주 발생하지 않지만 참조는 빈번하게 일어나는 경우 사용
  • 영속적으로 보관할 데이터가 아닌 경우 사용
  • 만료 정책을 고려
    만료 기한이 너무 짧은 경우 데이터베이스를 자주 읽고, 너무 긴 경우 원본 데이터와 차이가 발생할 가능성이 높아짐
  • 데이터베이스의 원본 데이터와 캐시 내의 사본과의 일관성 유지
    원본 갱신과 캐시 갱신이 단일 트랜잭션으로 처리되어야 함
  • 캐시 서버 spof 방지
  • 캐시 메모리 산정
    너무 작으면 의도하지 않은 cache eviction이 발생하여 캐시 성능 저하
  • cache eviction 정책
    LRU, LFU 정책이 일반적으로 널리 사용됨

4. CDN

정적 콘텐츠를 전송하기 위해 CDN 서버를 별도로 구성하여 애플리케이션 서버의 부하를 줄일 수 있다.

CDN 서비스 사업자가 제공하는 URL 도메인을 통해 image.png에 접근한다.
CDN 서버의 캐시에 해당 이미지가 없는 경우, origin 서버에 요청하여 파일을 응답받아 TTL을 설정하여 캐시한다.
이후 같은 이미지에 대한 요청이 CDN 서버에 들어오면 이미지가 만료되지 않은 이상 캐시를 통해 응답받게 된다.

CDN 서버를 사용하고자 하는 경우 아래 사항들을 고려하여야 한다.

  • CDN으로 들어가고 나가는 데이터 전송 양에 따라 CDN 서비스 사업자에게 비용을 지불하기 때문에, 자주 사용되는 컨텐츠만 캐싱
  • time-sensitive한 컨텐츠의 경우 만료 시한을 적절하게 설정
    만료 시한이 너무 길다면 컨텐츠의 신선도가 떨어지고, 너무 짧다면 origin 서버에 빈번이 접속하게 됨
  • CDN 장애 대처 방안

캐시, CDN 서버가 적용된 설계안은 아래와 같다.

5. stateless한 웹 계층

웹 계층을 수평적으로 확장하기 위해서는 상태 정보를 웹 계층에서 제거하여야 한다.
세션 등을 사용하는 상태 정보 의존적인 아키텍처에서는 서버에서 사용자의 상태 정보를 가지고 있기 때문에, 같은 클라이언트로부터의 요청은 항상 같은 서버로 전송되어야 하는 문제가 발생한다.

대부분의 로드밸런서는 이런 문제를 해결하기 위해 sticky session이라는 기능을 지원하는데, 로드밸런서에 부담을 주고 서버를 scale out 방식으로 추가하거나 제거하기 까다로워지며, 서버 장애를 파악하기 어렵다는 단점이 있다.

반면에 무상태 아키텍처는 클라이언트로부터의 요청이 어떤 서버로도 전달될 수 있고, 상태 정보가 필요한 경우 서버로부터 분리되어 있는 별도의 공유 저장소를 사용하여 해결할 수 있다.

이와 같은 무상테 아키텍처는 단순하고, 안정적이며, 규모 확장이 쉽다는 장점이 있다.
공유 저장소는 관계형 스키마가 필요하지 않은 이상 NoSQL를 사용하여 간편한 규모 확장을 도모할 수 있다.

stateless한 웹 계층, 공유 저장소가 적용된 설계안은 다음과 같다.

6. 데이터 센터

전 세계 사용자가 서비스를 이용하게 되었다면, 가용성을 높이고 전 세계 어디에서도 쾌적한 사용자 경험을 제공하기 위해 여러 데이터 센터를 지원해야 한다.
장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내되는데, 이 절차를 geo-routing이라고 한다.
GeoDNS는 사용자의 위치에 따라 도메인 이름을 어떠한 IP 주소로 변환할지 결정할 수 있도록 해 주는 DNS 서비스이다.

여러 데이터 센터 중 하나에 심각한 장애가 발생하면 모든 트래픽은 장애가 없는 데이터 센터로 전송된다.

위와 같은 다중 데이터센터 아키텍처를 만들기 위해서는 몇 가지 기술적인 난제를 해결해야 한다.

  • 트래픽 우회
    올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야 한다.
    GeoDNS는 사용자에게서 가장 가까운 데이터센터로 트래픽을 보낼 수 있도록 해 준다.
  • 데이터 동기화
    데이터 센터마다 별도의 데이터베이스를 사용하고 있다면, failover되어 트래픽이 우회된다 하더라도 해당 데이터센터에는 찾는 데이터가 없을 수 있다.
    이 문제를 해결하기 위해 보편적으로 데이터를 여러 데이터센터에 걸쳐 다중화하는 전략을 사용한다.
  • 테스트와 배포
    여러 데이터 센터를 사용하도록 시스템이 구성되었다면, 서비스를 여러 위치해서 테스트해 보는 것이 중요하다.
    자동화된 배포 파이프라인을 통해 모든 데이터 센터에 동일한 서비스가 설치되도록 한다.

7. 메시지 큐

메시지 큐는 메시지의 무손실을 보장하는 비동기 통신을 지원하는 컴포넌트이다.
producer라고 하는 서버가 메시지를 만들어 메시지 큐에 publish하고, consumer라고 하는 서버가 메시지를 수신하여 그에 맞는 동작을 수행하는 역할을 한다.

메시지 큐를 사용하게 되면 서버 간 결합이 느슨해지기 때문에 규모 확장성이 부장되어야 하는 안정적인 애플리케이션을 구성하기 좋다.
producer는 consume 프로세스가 다운되어 있어도 메시지를 발행할 수 있고, consumer는 publish 프로세스가 다운되어 있어도 메시지를 수신할 수 있다.

책에서는 사진 보정 애플리케이션으로 예를 들었는데, 사진 보정은 시간이 오래 걸릴 수 있는 프로세스이기 때문에 비동기적으로 처리하면 편리하다.
웹 서버는 사진 보정 작업(job)을 메시지 큐에 넣고, 사진 보정 작업 서버(worker)는 작업을 메시지 큐에서 꺼내어 비동기적으로 수행한다.

이런 구조를 가지게 되면 producer, consumer 서버의 규모는 각기 독립적으로 확장될 수 있다.
메시지 큐의 크기가 커지면 worker 서버의 규모를 확장하고, 큐가 자주 비어 있는 상태라면 worker 서버의 규모를 줄일 수 있을 것이다.

8. 로그, 메트릭 그리고 자동화

소규모 서비스의 경우 로그, 메트릭, 자동화는 있으면 좋지만 필수는 아니다.
하지만 서비스가 성장함에 따라 사업 규모가 커지면 그런 도구들에 필수적으로 투자해야 한다.
로그 : 시스템의 오류와 문제들을 보다 쉽게 찾아낼 수 있도록 로그를 단일 서비스로 모아주는 도구를 활용하면 좋다.
메트릭 : 메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻거나, 시스템의 현재 상태를 손쉽게 파악할 수도 있다.
자동화 : 시스템이 크고 복잡해지면 생산성을 높이기 위해 자동화 도구를 사용해야 한다.
continuous integration 도구를 사용하여 작성된 코드가 어떠한 검증 절차를 자동으로 거치도록 할 수 있어 문제를 쉽게 발견할 수 있고, continuous deployment 도구를 사용하여 안정적으로 프로덕션에 제품의 변경사항을 릴리즈할 수 있다.

메시지 큐, 로그, 메트릭, 자동화가 적용된 설계안은 아래와 같다.

9. 데이터베이스 규모 확장

저장할 데이터가 많아져 데이터베이스에 대한 부하가 증가하면 데이터베이스를 증설해야 한다.
웹 계층 서버와 마찬가지로 데이터베이스도 scale up, scale out의 규모 확장 방식을 사용한다.
데이터베이스의 scale up에 따른 장점은 단순하여 쉽다는 것이고, 하드웨어의 한계가 있어 성능을 무한대로 증설할 수 없다는 점과 spof의 위험, 비용이 많이 발생하는 문제가 있다.
데이터베이스의 scale out은 sharding이라고도 부르는데, 더 많은 서버를 추가함으로써 성능을 향상시키는 방식이다.

샤딩은 대규모 데이터베이스를 shard라고 부르는 작은 단위로 분할하는 기술이다.
모든 샤드는 같은 스키마를 사용하지만 샤드에 보관되는 데이터 사이에는 중복이 존재하지 않는다.
아래는 user_id % 4를 해시 함수로 사용하여 데이터가 보관되는 샤드를 정하는 데이터베이스의 예시이다.

샤딩 전략을 구현할 때 고려해야 할 가장 중요한 요소는 샤딩 키의 선정 방식이다.
샤딩 키는 파티션 키라고도 부르는데, 데이터가 어떻게 분산될지 정하는 하나 이상의 컬럼으로 구성된다.
위 예시의 경우 샤딩 키는 user_id이다.
샤딩 키를 선정하는 기준은 데이터를 고르게 분할할 수 있도록 하는 것이 가장 중요하다.
샤딩은 데이터베이스 규모 확장을 실현하는 훌륭한 기술이지만, 도입하게 되면 시스템이 복잡해지고 풀어야 할 문제들이 새롭게 발생한다.

  • 데이터의 재 샤딩(resharding)
    데이터가 너무 많아져서 샤드를 늘려야 하는 경우, 샤드 간 데이터 분포가 고르지 못한 경우 등이 발생하면 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치하여야 한다.
  • celebrity 문제(hotspot key 문제)
    특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제이다.
    여러 유명인사들이 모두 같은 샤드에 저장된 데이터베이스가 있다고 가정했을 때, 이 데이터로 social app을 구축하게 되면 해당 샤드는 read 연산 때문에 과부하가 걸리게 될 것이다.
  • 조인과 비정규화
    데이터베이스를 여러 샤드로 쪼개면 여러 샤드에 걸친 데이터를 조인하기가 힘들어진다.
    이를 해결하기 위한 한 가지 방법은 데이터베이스를 비정규화하여 하나의 테이블에서 질의가 수행될 수 있도록 하는 것이다.

데이터베이스 샤딩을 적용한 설계안은 아래와 같다.

책에서 다룬 규모 확장성을 이루기 위한 방법은 여기서 마무리된다.
이제까지 다룬 내용들을 짧게 요약하면 다음과 같다.

  • 웹 계층을 stateless하게 구성
  • 모든 계층에 다중화 도입
  • 가능한 한 많은 데이터를 캐시
  • 여러 데이터 센터를 지원
  • 정적 컨텐츠는 CDN을 통해 서비스
  • 데이터 계층을 샤딩을 통해 규모를 확장
  • 각 계층은 독립적 서비스로 분할
  • 시스템을 지속적으로 모니터링하고, 자동화 도구들을 활용

이후 책에서는 시스템 설계 면접 대비를 위한 개략적인 규모 추정 방법과 여러 시스템들을 설계하는 방법에 대해 소개한다.
설계 과정은 다음과 같다.

  • 문제를 이해하고 설계 범위를 확정
  • 개략적인 설계안을 제시 및 동의 구하기
  • 상세 설계

시스템 설계에 대한 지식 뿐만이 아니라, 실제 시스템 설계 면접에서 이루어지는 대화처럼 내용을 구성하여 어떠한 논리적인 과정을 거쳐 시스템 설계 결과물이 도출되는지 자세히 알 수 있던 부분이 특히 좋았다.

profile
백엔드 개발자

0개의 댓글