이론 정리/대규모 시스템 설계

대규모 시스템 설계 공부 001

철매존 2022. 4. 21. 22:20
728x90

사용자 수에 따른 시스템의 확장성에 관하여 알아본다.

단일 서버

모든 웹, 앱, DB, 캐시 등이 한개의 서버에서 실행되는 사례이다.

  1. 사용자가 도메인 이름을 통해 웹사이트 접속. 이 때 도메인 이름을 도메인 이름 서비스(DNS)에 질의하여 IP로 변환하는데, 이 DNS는 외부 서비스를 사용하게 되므로 해당 서비스의 일부는 아님.
  2. DNS조회를 통한 IP주소 반환
  3. 해당 주소로 HTTP요청 전달
  4. 요청 결과 반환

해당 사진에서 웹 브라우저 / 모바일 앱 의 두 종류 단말로부터 요청이 들어온다.

  • 웹 브라우저
    • BE(자바, 파이썬 등) 과 FE(HTML, JS) 사용
  • 모바일 앱
    • 모바일 앱과 웹 서버간 통신을 위해서는 HTTP프로토콜 이용
    • 반환 응답은 주로 JSON이 사용된다.

DB

트래픽 처리 서버(웹계층)과 DB서버(데이터계층)을 분리하면 각각 독립적인 확장이 가능하다.

이 DB는 RDB / NoSql중에 선택 가능하다.

  • RDB
    • 자료를 테이블과 열/컬럼으로 표현하고, join을 통해 관계를 표현 가능하다.
  • NoSql
    • Key-value store
    • graph store
    • column store
    • document store

보통은 RDB(관계형 DB)가 사용되고, 특정 상황에서 NoSQL을 쓴다.

  • 아주 낮은 응답 지연시간(latency)이 요구됨.
  • 다루는 데이터가 비정형임.
  • 데이터를 직렬화하거나 역직렬화 할 수 있기만 하면 됨.
  • 아주 많은 양의 데이터 저장이 요구됨.

수직적 규모 확장 VS 수평적 규모 확장

수직적 규모 확장

  • scale up이라고 한다.
  • 서버에 고사양 자원(더 좋은 CPU, 더 많은 RAM 등)을 추가하는 행위
  • 서버로 유입되는 트래픽의 양이 적을 때 사용하면 좋은 방법이다.
    • 단순하게 적용 가능하다는 장점이 있다.
    • CPU나 RAM을 마음대로 증설할 수 없기에 한계가 있다.
    • 수직적 규모 확장법은 장애에 대한 자동복구(failover)방안이나 다중화(re-dundancy)방안을 제시하지 않는다. 서버에 장애가 발생하면 웹사이트/앱은 완전히 중단된다.

수평적 규모 확장

  • scale out이라고 한다.
  • 더 많은 서버를 추가하여 성능을 개선하는 행위
  • 대규모 애플리케이션 지원에 있어서 해당 방법이 더 적절하다.

로드밸런서

위의 설계에서, 사용자는 웹 서버에 바로 연결되는데 이 서버가 다운되면 사용자는 웹 사이트에 접속할 수 없다.
또한 트래픽이 과도하게 몰리면 응답 속도가 느려지거나 서버 접속이 불가능해 질 수 있다.
로드밸런서는 부하 분산 집합에 속한 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할을 한다.

  1. 사용자가 로드밸런서의 공개주소(public IP)에 접속
  2. 서버 간 통신에서 사설 IP주소(private IP)사용. 해당 IP는 같은 네트워크에 속한 서버 사이의 통신에만 쓰일 수 있는 IP주소로, 인터넷을 통해서는 접속 불가능
  3. 로드밸런서가 웹서버와의 통신을 위해 해당 private IP를 사용함

이런 식으로 부하 분산 집합에 웹 서버를 추가하면 no failover의 해결, 그리고 웹 계층 가용성 향상에 도움을 줄 수 있다.

  • failover의 경우
    • 서버 1이 다운되면 모든 트래픽을 서버2로 전송한다. 이를 통해 웹 사이트 전체가 다운되는 일을 방지한다.
  • 가용성 향상
    • 만약 이 사진처럼 두개의 서버로 모든 트래픽을 감당하기 어려운 경우가 생긴다고 하면, 그냥 서버를 추가해주기만 하면 로드밸런서가 잘 처리해 줄 것이다.

DB다중화

서버 사이에 주(master) - 부(slave) 관계를 설정하고 데이터 원본은 주서버, 사본은 부서버에 저장하는 방식

  • 주서버
    • CUD 연산 가능
  • 부서버
    • 주서버에서 이루어진 사본 전달받음.
    • 읽기 연산만을 지원한다.

참고로 DB에서는 쓰기/변경/삭제 보다 읽기연산이 훨씬 자주 이루어지므로 주서버보다 부서버의 개수가 훨씬 많을 것이다.

요런 느낌으로

이러한 다중화에는 여러가지 이점이 있다.

  • 성능 향상
    • 데이터 변경 연산-주 / 데이터 읽기 연산-부 으로 연산이 분산된다. 따라서 병렬로 처리될 수 있는 질의(query)의 수가 늘어나므로 성능이 좋아진다.
  • 안정성
    • 데이터가 여러 장소에 보관되었기 때문에 어떤 사고로 인해 DB서버가 파괴되어도 보존될 수 있을 것이다.
  • 가용성
    • 데이터를 여러 지역에 복제해 둠으로서, 하나의 DB서버에 장애가 발생해도 다른 서버에 있는 데이터를 가져와서 계속 서비스할 수 있도록 한다.

만약 부서버가 한 대 뿐인데, 이게 다운되면 일단 읽기 연산이 한시적으로 주 데이터베이스로 전달된다.
그리고 즉시 새로운 부 데이터베이스 서버가 장애서버를 대체한다.
부서버가 여러 대인 경우 읽기 연산은 나머지 부서버로 분산된다.

주서버가 다운되면 만약 부서버가 한개만 있는 경우, 이 부서버가 주서버로 대체된다.
참고로 이거 실제로는 부서버의 데이터가 최신이 아닌 가능성이 있기 때문에 복구 스크립트를 통해 돌려서 추가해야 한다…


로드밸런서 + DB다중화 적용

이 두가지를 적용하면 이렇게 될 것이다.

  1. 사용자는 DNS를 통해 로드밸런서의 public IP주소를 받는다.
  2. 해당 주소를 이용하여 로드밸런서 접속
  3. HTTP요청이 서버1/2로 전달됨
  4. Read연산은 부서버, 변경연산은 주서버에서 이루어진다.

이제 여기서 추가로 응답시간을 개선하기 위한 방법을 진행한다.


캐시

값비싼 연산 결과 혹은 자주 참조되는 데이터를 메모리 안에 두고, 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소이다.

읽기 주도형 캐시 전략(read-through caching strategy)

  1. 사용자로부터 요청이 들어올 때, 캐시에 해당 응답이 저장되어 있는지 확인
  2. 저장되어 있다면 그 데이터를 클라이언트측에 바로 반환한다.
  3. 없는 경우에 DB질의를 통해 데이터를 찾아서 반환한다.

주의점

  • 데이터 갱신이 자주 일어나지는 않고, 참조는 자주 일어나는 경우에 사용을 고려한다.
  • 캐시는 휘발성 메모리에 위치하므로, 중요한 데이터는 DB등의 지속적 저장소에 두어야 한다.
  • 캐시에 보관되는 데이터 만료 정책을 마련해 두어야 한다.
    • 너무 데이터가 오래 남아있으면 원본이 변경되었는데 캐시가 호출될 가능성이 있다.
    • 너무 데이터가 짧게 남아있으면 이걸 쓰는 이유가 없을것이다.
  • 일관성 유지를 신경써야 한다.
    • DB의 원본과 캐시의 사본이 동일한지의 여부인데, 원본 갱신 연산과 캐시 갱신 연산이 단일 트랜잭션으로 처리되지 않으면 일관성이 깨질 수 있다.
  • 장애 대처 방안
    • 캐시 서버를 한개만 두개 되면 해당 서버가 단일 장애 지점(Single Point of Failure, SPOF)가 되어 버릴 수가 있다.
      • 어떤 지점에서의 장애가 전체 시스템의 동작을 중단시켜버릴 수 있다면 해당 지역을 SPOF라 부른다.
        • 이를 위해 여러 지역에 걸쳐 캐시 서버를 분산시켜야 한다.
  • 캐시 메모리 크기
    • 메모리가 너무 작으면 데이터가 살제되어버려서 성능이 떨어지게 된다.
  • 데이터 방출 정책 관련
    • 위의 내용에서 필요한 것인데, 캐시가 가득 차버린 상태에서 새로운 데이터를 넣어야 하는 경우 기존 데이터를 방출해야 한다.
      • LRU(Least Recently Used)
        • 가장 널리 쓰이는 방식. 마지막으로 사용한 시점이 가장 오래된 데이터 삭제
      • LFU(Least Frequently User)
        • 사용 빈도가 가장 낮은 데이터 삭제
      • FIFO(First In First Out)
        • 선입선출

콘텐츠 전송 네트워크(CDN)

정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크
이미지, 비디오, CSS, JS파일 등의 캐시가 가능하다.

 

참고로 이거 CDN서버가 사용자로부터 멀면 당연히 사용자는 데이터를 천천히 받게된다.

CDN사용시 고려사항

  • 비용
    • ㅇㅅㅇ
  • 적절한 만료 시한 설정
    • 만료 시점이 너무 길면 콘텐츠의 신선도가 떨어지고, 너무 짧으면 원본서버에 접속이 빈번해진다.
  • CDN장애 대처 방안
    • CDN자체가 죽었을 경우 웹사이트/애플리케이션 동작의 고려가 있어야한다.
      • 예를 들어 문제 발생시 원본 서버로부터 직접 클라이언트측에 콘텐츠 전달 등
  • 콘텐츠 무효화 방안
    • 아직 만료되지 않은 콘텐츠의 경우도 CDN에서 제거 가능
      • CDN서비스 사용자가 제공하는 API를 사용하여 무효화
      • object versioning사용
        • image.png?v=2 이런느낌으로 최신버전을 보여주게 하면 된다.

이 CDN이 적용되면 이렇게 된다.

  • 이제 정적 콘텐츠는 웹서버를 통해 서비스하지 않고, CDN을 통해 제공하여 더 나은 성능을 제공한다.
  • 캐시가 DB부하를 줄여준다.

무상태(stateless) 웹 계층

다음으로 웹 계층을 수평적으로 확장하는 방법이다.
먼저 수평적 확장을 위해 상태 정보(session등)를 웹 계층에서 제거해야 한다.
바람직한 전략은 상태 정보를 관계형 DB나 NoSQL같은 지속적 저장소에 보관하고 필요할 때 가져오는 것이다.

이러한 웹 계층을 무상태 웹 계층이라 한다.

상태 정보 의존적 아키텍처

해당 사진에서는 상태 정보를 유지시킨다.
만약 여기처럼 사용자 A의 정보를 서버1에서 가지고 있는 경우, 다른 서버로 연결시키면 인증이 실패하게 될 것이다.
이 경우 문제는 같은 클라이언트의 요청 -> 같은 서버로 전송 이 이루어져야 한다는 것이다.
해당 방식을 로드밸런서는 고정 세션(sticky sassion)을 통해 수행하는데, 이는 로드밸런서에 부담을 주고 서버 장애 처리도 복잡해진다.

무상태 아키텍처

여기 사진은 어떤 사용자의 요청이 특정 서버에 종속되지 않는다.
상태 정보가 필요한 경우 웹 서버는 공유저장소(shared storage)로부터 데이터를 가져온다.
이 상태 정보는 웹 서버로부터 물리적으로 분리되어 있다.
이는 단순하고, 안정적이며, 규모 확장이 쉽다.

무상태 웹 계층을 갖도록 하는 기존 설계 변경

데이터 센터

장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내된다.
해당 절차를 지리적 라우팅(geoDNS-routing / geo-routing)이라고 한다.
지리적 라우팅에서의 geoDNS는 사용자의 위치에 따라 도메인 이름을 어떤 IP주소로 변환할지 결정할 수 있도록 해주는 DNS서비스이다.

이 사진에서
x%사람 -> US-East
나머지사람 -> US-West
센터로 안내된다고 할 때, 만약 둘 중 하나의 센터에 심각한 장애가 발생하면 모든 트래픽이 장애가 없는 데이터 센터로 이동하게 된다.

이런 식으로 다중 데이터센터 아키텍처를 만들기 위해서는 몇 가지 문제를 해결해야 한다.

  • 트래픽 우회
    • 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야 한다.
    • GeoDNS는 사용자에게서 가장 가까운 데이터센터로 트래픽을 보낼 수 있도록 해준다.
  • 데이터 동기화(synchronization)
    • 데이터 센터마다 별도의 데이터베이스를 사용중이라면, 자동으로 장애가 복구되어(failover) 트래픽이 다른 DB로 우회된다 해도, 찾는 데이터가 해당 DB에 없을 수 있다.
    • 해당 상황을 막는 보편적 전략은 데이터를 여러 데이터센터에 걸쳐 다중화하는 것이다.
  • 테스트와 배포(deployment)
    • 여러 데이터 센터를 사용하도록 시스템이 구성된 상황이라면 웹 사이트 또는 애플리케이션을 여러 위치에서 테스트해 보는 것이 중요하다.
    • 자동화된 배포 도구는 모든 데이터 센터에 동일한 서비스가 설치되도록 하는 데 중요한 역할을 한다.

메시지 큐

시스템을 더 큰 규모로 확장하기 위해서는 시스템의 컴포넌트를 분리하여 각기 독립적으로 확장될 수 있도록 해야 한다.
해당 문제를 풀기 위해 많은 분산 시스템들은 메시지 큐를 사용한다.

메시지 큐란, 메시지의 무손실(durability, 메시지 큐에 일단 보관된 메시지는 소비자가 꺼낼 때 까지 안전히 보관되는 특성)을 보장하는, 비동기 통신 지원하는 컴포넌트이다.
메시지의 버퍼 역할을 하며 비동기적으로 전송한다.

메시지 큐의 기본 아키텍쳐는 다음과 같다.

  1. 생산자/발행자 라고 불리는 입력 서비스가 메시지를 생성하고 메시지 큐에 발행한다.
  2. 큐에는 보통 소비자/구독자 라고 불리는 서비스나 서버가 연결되어 있는데, 메시지를 받아 그에 맞는 동작을 수행한다.

메시지 큐를 이용하면 서비스/서버 간 결합이 느슨해져서 규모 확장성이 보장되어야 하는 안정적 애플리케이션 구성에 유리하다.

소비자의 프로세스가 다운되어 있어도 생산자는 메시지 발행이 가능하며, 반대로 생산자 서비스가 가용한 상태가 아니여도 소비자는 메시지를 수신할 수 있다.

사진 보정 작업을 예를 들어

  1. 보정 작업은 시간이 오래 걸릴 수 있는 프로세스이기 때문에 비동기적 처리가 편리함.
  2. 웹서버에서 사진 보정 작업을 메시지 큐에 넣는다.
  3. 사진 보정 작업 프로세스들은 해당 작업을 메시지 큐에서 꺼내어 비동기적으로 완료한다.

이런 식으로 진행하면 생산자와 소비자 서비스의 규모는 독립적으로 확장이 가능하다.

  • 큐의 크기가 커지면 더 많은 작업 프로세스를 추가해야 처리 시간을 줄일 수 있다.
  • 큐가 거의 항상 비어있는 상태이면 작업 프로세스의 수는 줄일 수 있다.

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

로그

에러 로그를 모니터링하는것은 중요하다. 이를 통해 시스템의 오류나 문제를 찾아낼 수 있기 때문이다.
에러 로그는 서버 단위로 모니터링 할 수도 있지만, 로그를 단일 서비스로 모아주는 도구를 활용하면 더 편하게 검색 및 조회할 수 있다.

메트릭(Metric)

코드 메트릭은 개발자가 개발 중인 코드에 대해 더 정확히 파악할 수 있도록 하는 소프트웨어 측정 방법이다.
이를 통해 위험 식별, 프로젝트 상태 이해, 개발 진행 상황 추적을 할 수 있다.

코드 메트릭은 소프트웨어의 크기/복잡성 등을 측정한다.

  • 호스트 단위 메트릭
    • CPU, 메모리, 디스크 I/O에 관한 메트릭
  • 종합(aggregated) 메트릭
    • DB계층의 성능, 캐시 계층의 성능 등등…
  • 핵심 비즈니스 메트릭
    • 일별 능동 사용자(daily active user)
    • 수익(revenue)
    • 재방문(retention)
    • 등등…

자동화

시스템의 생산성을 높이기 위해 자동화 도구의 활용이 가능하다.
CD/CI 등등

메시지큐, 로그,메트릭,자동화 적용

  • 메시지 큐는 각각의 컴포넌트가 느슨한결합을 가질 수 있도록 하고, 결함에 대한 내성을 높인다.

DB의 규모 확장

저장할 데이터가 많아지면 DB에의 부하가 늘어난다.
이를 대비하여 DB증설방법을 찾아야 한다.

이 방법은 수직적 규모 확장법 / 수평적 규모 확장법이 있다.

수직적 확장

스케일 업이라고 부른다.
기존 서버에 더 많은, 또는 더 성능이 좋은 자원(CPU, RAM, 디스크 등)을 증설하는 방법이다.

이는 다양한 문제가 있다.

  • CPU, RAM등을 막 증설할 수는 없다.
  • SPOF(Single Point of Failure) 위험성이 크다.
  • 비싸다.

수평적 확장

DB의 수평적 확장은 샤딩(sharding)이라고도 부른다.
이는 더 많은 서버를 추가함으로서 성능을 향상시키는 방법이다.

샤딩은, 대규모 데이터베이스를 샤드(shard)라고 부르는 작은 단위로 분할하는 기술을 일컫는다.
모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다.

이런 느낌으로 user id를 나누어서 각각의 샤드에 저장한다.

이 샤딩 전략을 구현할 때 중요한 것은 샤딩 키를 어떻게 정하는지이다.
샤딩 키는 파티션 키(partition key)라고도 부르는데, 데이터가 어떻게 분산될지 정하는 하나 이상의 칼럼으로 구성된다.
위의 사진에서 user_id가 샤딩 키이다.

그리고 샤딩 키를 도입하면 시스템이 복잡해지고, 풀어야 할 새로운 문제도 생긴다.

데이터의 재샤딩

다음과 같은 경우에 필요하다.

  1. 데이터가 너무 많아져서 하나의 샤드로는 감당할 수 없을때
  2. 샤드 소진(shared exhausion) : 샤드간 데이터 분포가 균등하지 못하여 어떤 샤드의 공간 소모가 다른 것에 비해 빨리 진행될 때

이러한 경우에 샤드 키 계산 함수를 변경하고 데이터를 재배치하여야 한다.
안정 캐시 기법을 활용하여 이 문제의 해결이 가능하다.

유명인사(celebrity)문제

핫스팟 키(hotspot key)문제라고도 한다.

  • 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제
    • 예를 들어 마이클 조던, 레이디 가가, 류찬, 비틀즈같은 유명인사가 모두 같은 샤드에 저장되는 DB가 있다면, 검색을 엄청 해서 과부하가 걸릴 것이다.

이런 문제의 해결을 위해 유명인사들의 경우 각각 나누어서 동일한 샤드에 들어가지 못하도록 할당해야 할 수도 있고, 더 잘게 쪼개야 할 수도 있다.

조인과 비정규화(join and de-normalization)

일단 하나의 DB를 여러 샤드로 쪼개고 나면, 이 여러 샤드에 분포된 데이터를 조인하기 힘들어진다…
이를 해결하는 방안중 하나는 DB를 비정규화하여 하나의 테이블에서 질의가 수행될 수 있도록 하는 것이다.

샤딩을 적용하면 이래 된다.

백만 이상의 사용자

시스템의 규모 확장은 지속적이고 반복적인 과정이다.
지금까지 공부한 것만으로도 규모 확장이 가능하지만, 만약 수백만 사용자 이상을 지원하려면 새로운 전략을 도입해야 할 것이다.