본문 바로가기
도서 리뷰/대규모 시스템 설계 기초

[대규모 시스템 설계] 대규묘서비스 설계시 고려 사항

by illlilillil 2022. 2. 27.

주요 기법 요약

  1. 웹 계층을 무상태 계층으로
  2. 모든 계층에 다중화 도입
  3. 최대한 많은 데이터를 캐시
  4. 여러 데이터 센터 지원
  5. 정적 콘텐츠는 CDN을 통해 서비스
  6. 데이터 계층은 샤딩을 통해 규모 확장
  7. 각 계층을 독립적인 서비스로 분할
  8. 지속적 모니터링과 자동화 도구 도입

 

단일 서버 - 바닥에서부터


우선 모든 컴포넌트가 한 대의 서버에 연결된 시스템 설계를 한다.

1. 사용자는 DNS(Domain Name Server)를 이용하여 웹사이트에 접속합니다. 도메인 이름을 dns에 질의하여 IP 주소로 변환하는 과정을 거칩니다.

2. DNS 조회 결과로 IP 주소가 반환됩니다.

3. 해당 IP 주소로 HTTP 요청을 보냅니다.

4. 요청받은 웹 서버는 JSON 형식의 응답을 반환합니다.

 

데이터베이스


사용자가 늘면 한 대의 서버로는 감당할 순 없습니다.

웹 서버로 트래픽을 처리하는 용도로 사용하고 다른 하나는 데이터베이스용으로 둡니다.

용도에 맞게 분리하면 독립적으로 확장할 수 있습니다.

어떤 데이터베이스를 선택할 것인가??

RDBMS가 있을 수 있습니다. Mysql, oracle, postgresql 등이 있습니다. 

또한 NoSQL이 있습니다. 몽고디비, amazon dynamodb 등이 있습니다. 조인 연산은 지원하지 않습니다.

NoSQL은 4가지 부류로 나눌 수 있습니다.

1. 키-값 저장소

2. 그래프 저장소

3. 컬럼 저장소

4. 문서 저장소

 

이런 경우일때 NoSQL을 선택합니다.

- 낮은 응답 지연시간이 요구

- 다루는 데이터가 비정형 데이터, RDBMS에 어울리지 않음

- 데이터를 직렬화 또는 역직렬화 할 수 있으면 됌

- 많은 양의 데이터를 저장하지 않음

 

수직적 규모(Scale Up) VS 수평적 규모 확장(Scale Out)


스케일 업은 더 좋은 사양의 자원을 추가하는 행위입니다. 대표적으로 aws 인스턴스에서 cpu 개수를 늘린다던지 메모리를 높인다던지 하는 행위입니다. 스케일 아웃은 같은 서버를 여러 대 두어 확장하는 행위입니다. 

스케일 업은 트래픽 양이 적을때 좋습니다. 단순하게 구성될 수 있습니다.

그러나 스케일 업의 확장 방식은 한계가 있습니다. 무한대로 cpu, memory를 증설할 순 없기 때문입니다.

또한 자동복구(failover) 이나 다중화를 지원하지 않습니다. 따라서 장애 발생 시 서비스가 중단되는 사태가 발생합니다.

대규모 어플리케이션 사용시엔 Scale Out 방식이 적절합니다.

 

너무 많은 사용자가 접속하게 되면 서버가 한계에 도달합니다. 따라서 로드밸런서를 도입하는 것이 최선입니다.

 

로드 밸런서


로드밸런서는 서버에게 부하된 트래픽을 고르게 분산하는 역할을 합니다.

사용자는 똑같이 공개 IP 주소로 접속을 합니다. 웹 서버는 직접 접속을 처리하지 않습니다. 보안을 위해 서버 간 통신은 사설 IP 주소로 통신합니다. 하나의 서버를 더 추가하게 되면 장애를 자동복구할 수 있으면 웹 계층의 가용성이 향상됩니다.

 

데이터베이스 다중화


많은 데이터베이스가 다중화를 지원합니다.

master-slave 관계를 설정하고 원본은 주 서버, 사본은 부 서버에 저장합니다. Write 연산은 마스터 서버에서만 지원합니다. slave 서버는 주로 읽기만 수행합니다. 대부분의 어플리케이션이 읽기 연산의 비중이 쓰기 연산보다 월등히 많습니다. 따라서 통상 slave의 데이터베이스의 수가 많습니다.

다중화의 장점

  • 더 나은 성능
    • 읽기 연산이 부 데이터베이스로 분산되고 병렬 처리될 수 있는 질의문의 수가 늘어나므로 성능이 좋아집니다.
  • 안정성
    • 서버 일부가 파괴되어도 데이터가 보존됩니다.
  • 가용성
    • 데이터가 여러 지역에 있으므로 장애가 있어도 다른 서버에서 데이터를 가져와서 사용할 수 있습니다.

 

부 서버가 한대뿐일떄 다운이 된다면 읽기 연산은 주 데이터베이스로 전달됩니다. 즉시 부 데이터베이스 서버가 장애 서버를 대체합니다. 부 서버가 여러 대인 경우엔 읽기 연산이 나머지 부 서버로 분산됩니다.

 

만약 주 서버가 다운된다면???

한 대의 부 서버만 있다면 부 서버가 주 서버가 될 것이고 모든 연산은 주 서버에서 실행됩니다. 그러나 실제 서비스 단계에선 더 복잡한 문제가 있습니다. 부 서버에 보관된 데이터가 최신이 아닐 수 있기 때문입니다.

없는 데이터는 복구 스크립트를 돌려 추가합니다. 다중 마스터나 원형 다중화 방식을 도입하면 되지만 취업도 안한 개발자에겐 너무 복잡한 구성입니다... 인지만 하고 있겠습니다.

 

이제 웹 계층, 데이터 계층의 다중화는 되었으니 응답시간 개선을 위해 캐시를 공부해보겠습니다.

 

캐시


값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두어 뒤 이은 요청이 보다 빨리 처리될 수 있게 하는 저장소입니다.

애플리케이션의 성능은 데이터베이스를 얼마나 자주 호출하느냐인데 캐시가 이러한 문제를 완화해줍니다.

데이터베이스의 부하도 줄고 캐시 서버의 규모 확장도 가능해집니다.

위 전략은 주도형 캐시 전략으로 부릅니다. 찾는 데이터가 캐시에 있으면 캐시에서 읽고 없다면 데이터베이스에서 데이터를 읽어 캐시에 씁니다. 그 후에 데이터를 반환합니다.

 

캐시 사용시 유의할 점

  • 데이터 갱신은 자주 일어나지 않지만 참조가 빈번하게 일어날때 쓸 수 있습니다.
  • 영구적으로 보관할 데이터는 캐시에 두지 않습니다.
  • 언제 만료될지에 대한 고민을 해야합니다. 만료된 데이터는 삭제되어야 합니다. 또한 기한이 너무 짧으면 데이터베이스를 너무 자주 읽습니다. 그러나 너무 길면 정합성에서 차이가 날 것입니다.
  • 일관성의 유지 데이터베이스와 캐시 데이터가 같은지 여부입니다. 저장소 원본 갱신 연산과 캐시 갱신 연산이 단일 트랜잭션으로 처리되지 않는다면 일관성이 깨질 수 있습니다.
  • 장애 대처법은? 캐시 서버가 하나일땐 장애가 났을때 단일 장애 지점이 되어버립니다. 여러 지역에 걸친 캐시 서버를 두어야 합니다.
  • 캐시 메모리를 얼마나 잡을지?? 캐시 메모리가 작으면 메모리가 eviction되어 성능이 떨어집니다. 이때는 캐시 메모리를 과할당해서 데이터가 늘어나도 감당할 수 있도록 합니다.
  • 캐시 데이터 방출 정책은? 캐시 데이터가 꽉 찼을땐 기존 데이터를 내보내야 합니다. 가장 널리 쓰이는 것은 LRU(Least Recently Used) 마지막 사용 시점이 오래된 데이터를 내보내는 정책 등 여러 가지 방법이 있습니다.

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

정적 콘텐츠를 전송하는데 쓰이는 분산 서버 네트워크입니다. 비디오 등을 캐시할 수 있습니다.

사용자가 웹사이트에 방문하면 가까운 CDN 서버가 정적 콘텐츠를 전달하게 됩니다. 상대적으로 먼 원본 서버보다 빠르게 정적 콘텐츠를 가져올 수 있습니다. 마치 스프링에서 redis를 사용하는 것 처럼 사용합니다.

 

고려해야 할 사항

  • 비용
    • 제3 사업자를 통해 운영되기 때문에 비용 문제가 발생할 수 있습니다.
  • 알맞은 만료 기한 설정
    • 만료 시점을 잘 정해야 한다. 너무 길면 신선도가 떨어지며 반대로 너무 짧으면 원본 서버에 빈번한 접속이 되어 좋지 않습니다.
  • 장애 대처 방안
    • CDN에 장애 발생 시 원본 서버에 접속하는 방안을 고려해야 합니다.

 

무상태 웹 계층

웹 계층을 수평적으로 확장하기 위한 방법입니다. 가능하게 하기 위해서는 사용자 세션 데이터는 제거해야 합니다. JWT 같이 클라이언트에 상태 정보 관리를 맡기거나 따로 NoSQL 저장소에 저장하여 관리하는 방식을 가져야 합니다.

클라이언트의 요청은 항상 같은 서버로 전송되어야 합니다. 따라서 로드밸런서가 고정 세션(Sticky Session)이라는 기능을 제공합니다.

그러나 이 부분이 로드밸런서에 많은 부담을 줄 수 있습니다.

세션 데이터를 NoSQL에 저장하거나 Redis처럼 캐시 시스템에 저장합니다. 위 그림에서는 NoSQL을 사용했는데 규모 확장이 간편해서 입니다.

1번에서 서버의 스케일 아웃으로 인한 자동 확장은 트래픽에 따라 추가 또는 삭제를 뜻합니다.

 

데이터 센터

전 세계가 이용하는 시스템을 만들고자 어디에서도 접근성이 좋도록 데이터 센터를 만드는 것은 필수입니다.

사용자는 로드밸런서를 통해서 가까운 데이터 센터로 안내됩니다. 이를 지리적 라우팅이라고 부릅니다.

 

데이터 센터를 위한 여러 문제들

  • 트래픽 우회: 가까운 데이터 센터로 트래픽을 보내는 방법에 대한 고민이 필요합니다.
  • 데이터 동기화: 데이터 센터마다 데이터베이스가 존재할텐데 장애가 발생해 다른 데이터베이스로 간다고 해도 찾는 데이터가 그 곳에는 없을 수 있습니다. 따라서 넷플릭스는 데이터 다중화를 통해서 이를 해결합니다.
  • 테스트와 배포: 자동화된 배포 도구와 테스트를 통해 모든 데이터 센서에서 동일한 서비스가 제공되도록 해야 합니다.

더 큰 규모의 확장을 위해서는 시스템의 컴포넌트를 분리하여 독립적인 확장을 해야 합니다. 분산 시스템에서는 메시지 큐를 통해 이를 해결합니다.

 

메시지 큐

메시지 큐는 메시지 무손실을 보장하는 비동기 통신을 지원하는 컴포넌트입니다. 메시지 버퍼 역할을 하면서 비동기적인 전송을 합니다.

기본 구조는 생산자가 메시지를 만들어 메시지 큐에 발행합니다. 큐에는 컨슈머가 원하는 토픽을 구독하며 메시지를 받아볼 수 있습니다.

메시지 큐를 이용한 서비스 또는 서버 간의 결합이 느슨해져 규모 확장에 용이합니다. 생산자는 소비자 즉 서버에 장애가 발생해도 안정적으로 메시지를 발행할 수 있습니다. 컨슈머 또한 생산자 서비스를 가용하지 않아도 메시지를 수신할 수 있습니다.

대표적으로는 RabbitMQ와 Kafka가 있습니다. 요즘 추세는 카프카를 더 많이 쓰는 추세같습니다.

 

스프링 배치 잡같은 어떤 일련의 과정에 유용할 수 있습니다. 

예를 들어 사진 보정 어플리케이션을 개발할때 사진 보정하는 프로세스는 시간이 꽤 걸리는 작업입니다. 그렇기에 생산자가 발행을 통해 큐에 할 일을 넣고 웹 서버는 작업들을 메시지 큐에서 꺼내며 비동기적인 처리를 수행합니다. 각각 독립적으로 발행과 소비가 가능해지는 것입니다. 큐의 크기가 커지면 더 많은 작업 프로세스를 추가해야 처리 시간을 줄일 수 있습니다. 그러나 큐가 거의 비어있다면 작업 프로세스 수는 줄 것입니다.

 

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

로그: 에러 로그를 모니터링하는 것은 중요합니다. 서버 단위로 모니터링할 수도 있지만 로그를 모아 주는 단일 서비스를 활용하면 효과적인 로그 관리를 할 수 있습니다. 저희는 Log Stash를 사용하고 있습니다. 사용만. 활용을 좀 해야할 거 같습니다.

 

메트릭:

  • 호스트 단위 메트릭: CPU, 메모리, 디스크 I/O에 관한 정보
  • 종합 메트릭: 데이터베이스 계층의 성능, 캐시 계층의 성능 정보
  • 핵심 비즈니스 메트릭: 일별 능동 사용자, 수익, 재방문 등의 정보

자동화:

  • 지속적 통합(CI)를 도와주는 도구를 통해 테스트 및 배포 자동화를 실행해 생산성을 향상 시킨다.

 

 

데이터베이스의 규모 확장

요즘 관심 있는 분야입니다. 적용시키고 싶은 욕구가 샘 솟습니다.

데이터베이스의 부하를 덜기 위해 규모 확장을 수행하는데 두 가지 방법이 있습니다.

 

스케일 업(수직적 확장)

기존 데이터베이스 성능보다 좋은 CPU,RAM, 디스크를 증설하는 방법입니다. 보통 AWS RDS를 램을 늘린다던가 cpu 코어 수를 늘린다던가 하는 방법입니다.

그러나 이 방법은 심각한 단점들이 몇 가지 존재합니다.

  • 무제한 확장이 불가능하다.
  • SPOF으로 인한 위험성이 크다. -> 데이터베이스가 하나이기 때문입니다.
  • 비용이 기하급수적으로 늘게 됩니다.

 

스케일 아웃(수평적 확장)

샤딩이라고 불리는 대규모 데이터베이스를 샤드라는 작은 단위로 분할하는 기술로 확장을 수행합니다.

모든 샤드는 같은 스키마를 쓰지만 중복 데이터는 없습니다. 예를 들어 user_id%4로 나눠 0~3번 데이터베이스에 나눕니다.

 

샤딩 전략을 구성할때 가장 중요한 사항은 샤딩 키를 정하는 것입니다. 샤딩 키는 파티션 키라고도 부르는데 user_id가 샤딩 키가 됩니다. 샤딩 키를 잘 정해야 효율이 좋습니다. 샤딩 키를 정할땐 데이터를 고르게 분할할 수 있어야 합니다.

 

그러나 샤딩을 도입할땐 여러 문제가 발생할 수 있습니다.

  • 데이터 재 샤딩
    • 재샤딩은 다음과 같은 경우 필요합니다.
      • 데이터가 너무 많아져 하나의 샤드로는 감당하기 어려울때
      • 샤드 간 데이터 분포가 균등하지 못해 한 곳의 샤드 공간의 소모가 빠를때(샤드 소진) - 이럴땐 샤드키를 재설정 해야합니다. 안정해시 기법으로 해결할 수 있다고 합니다. - 한 3년차 이상되었을때 접근해볼 수 있지 않을까 싶습니다.
      • 유명인사 문제: 핫스팟 키 문제라고 불리며 특정 샤드에 질의가 집중돼 부하가 걸리는 문제입니다. 해당 문제를 샤드를 더 쪼개는 방식으로 해결해야 합니다.
      • 조인과 비정규화: 하나의 데이터베이스를 여러 샤드로 쪼개면 나눠진 데이터를 조인하기가 힘들어집니다. 이 때는 데이터베이스를 비정규화하여 하나의 테이블에서 질의가 수행될 수 있도록 하는 것입니다. -> 가장 중요한 주제 같습니다. 여러 기업들이 RDBMS에서 NoSQL을 사용하는 이유도 여기에 있지 않을까 하는 생각입니다.

여기까지 진행되면 백만 사용자까지 감당할 수 있는 시스템이 구성이 되었습니다.

 

댓글