[가상 면접 사례로 배우는 대규모 시스템 설계기초] 1장 사용자 수에 따른 규모 확장성

매번 개발이 새롭다·2022년 11월 21일
0

글의 시작

  • 최근에 취업준비 스터디를 시작하면서 몇 분은 저와 같이 취업을 하게 되었습니다. 하지만 이번에 취업을 하고 난 결과 저는 타이밍이 맞아서 합격한 것이지 시간이 흐른 후 제가 원하는 회사를 갈 수 있겠다는 확신이 서지 않았습니다. 그래서 스터디원 분들의 의견도 있고 해서 "가상 면접 사례로 배우는 대규모 시스템 설계기초"라는 책을 같이 읽어보기로 했습니다.

  • 책에 1장은 다음과 같습니다. 말 그대로 사용자 수에 따른 규모를 확장하는 케이스에 적법한 솔루션들을 이야기 하고 있고 6-7년전에 이 책을 읽었다면 해당 시기에 제 개인적으로 국내에 좋은 회사에 취업하는데 좋은 기반이 되었을것이라고 생각합니다. 다만 지금은 너무 기본적인 내용으로 취급 될 것이라고 생각해요.

해당 chapter를 읽고 나서 정리

모든 것은 하나로

  • 처음에 서비스를 시작하면 다음과 같습니다. 처음에는 가볍게 프로토타입 개념 및 빠른 배포 및 서비스를 돌아가게 하기 위해서 다음과 같이 간단한 구조로 Monolithic 아키텍쳐 구조로 시작하게 됩니다. 사용자는 dns를 통해서 도메인 정보를 ip주소로 변환한뒤 클라이언트(브라우저, 앱 etc)에서 해당 ip의 서버로 요청을 하게 되고 html/json/xml 과 같은 형태로 일반적으로 응답을 받아서 처리한다.

갑자기 안된데.. 데이터베이스 서버를 분리하자

  • 사용자가 늘어나게 되었고 하나의 서버에서 DB에 무거운 배치 쿼리가 실행된다고 가정해보자. DB에 실행되는 쿼리 때문에 웹서버나 웹어플리케이션서버들이 영향을 받아 정적인 데이터마저 제대로 된 응답을 주지 못하게 되고 그럼으로 인해서 DB를 분리하게 될것이다. 어떤 DB를 선택하는 방향은 여러가지 이지만 일반적으로는 RDBMS(mysql, oracle)의 종류 중 하나를 선택하게 되고 용도에 따라 선택하는 전략은 다양하다.

사용자가 많이 생겼네요? 음 서버가 죽으면 어떻게 하지?

  • 관련되서 문제를 해결했지만 사용자의 사용요청양이 늘어나고 DB가 아닌 웹서버(or 웹어플리케이션서버)의 부하가 늘어나게 되어 해당 영역에 대한 고민이 시작된다. 여기서 "수직적 규모 확장 vs 수평적 규모 확장"에 대한 고민이 시작되게 되는데 당연히 수직적 규모의 확장(해당 장비의 하드웨어 스팩을 업그레이드)가 관리 포인트에서는 적은 리소스가 든다고 생각이 들지만, 언제까지 이렇게 하드웨어 스팩을 올릴것인지, 서버가 갑자기 디스크 문제라도 발생하게 되면 자동복구나 다중화 방안의 대안이 없게 되므로 수평적 확장을 고민하게 된다. 결국 수평적 확장을 선택한 뒤, 확장된 서버에 트래픽을 분산 시켜야 하므로 로드밸런서의 도입이 함께 된다. 해당 케이스에서는 DNS를 통해 노출 되는 아이피는 로드밸런서의 아이피이고 이전에는 웹서버(or 웹어플리케이션서버)가 직접 사용자의 요청을 직접 받았다면 로드밸런서가 해당 요청을 받아 웹서버(or 웹어플리케이션서버)의 내부 아이피를 통해 전달할수 있고 한대의 서버가 down되어도 서비스는 아직 지속적인 상태이다. 배포도 한대를 배포한뒤 문제가 없다면 로드밸런서에 연결시킨뒤 다른 한대를 제외하고 배포가 가능하다.

그럼 DB는? 장애 발생 안해?

  • 서버가 이중화 되어서 너무나 아름다운 상태이지만 역시 데이터베이스는 한대 밖에 존재하지 않는다. 이 정도 구성할 정도의 서비스가 되었다면, 우리가 운영하는 서비스의 가치는 어느정도 올라간 상태, 이제 우리의 데이터도 지켜야한다. 보통 M(aster, 주, 한대만 가능)-S(lave, 부, 여러대가 될수 있다.)구조로 DB를 이중화를 시도하게 되고 M는 원본, S는 사본을 가지게 되고(S는 M으로 부터 데이터를 전달받아 동기화한다.) 데이터의 동기화를 위해서 M에는 쓰기/읽기 등의 모든 연산이 가능하고 S는 읽기 연산을 가능하게 유지한다. S도 쓰기 연산을 하면 M에도 동기화를 해야하는 어려움이 발생하기 때문이다. 통상적으로 일반적인 서비스에는 다른 연산보다 읽기 연산이 많으므로 읽기 자체가 분산이 되서 더 나은 성능을 보장하게 되고 M가 다운된다면 S중에 한대가 M로 변신(?)을 하게 된다. 이로 인해서 안정성과 가용성을 얻게 된다. 보통 해당 케이스에 데이터 누락건이 발생할수 있는데 이는 데이터 복구 또는 동기화의 추가적인 작업을 통해서 동기화를 시킨다.(해당 작업은 서비스의 상황에 따라 복잡한 케이스로 발전할수 있다.) 또한 multi-master, circular replication등의 방식으로 대체할수 있지만, 해당 방법은 구성을 하기 위한 리소스가 많이 들게 된다.

더 적은 비용으로 성능을 늘릴수 없을까? 캐시!!

  • 캐시를 적용한다는 것은 어느 정도 지속력이 있는 값 비싼 연산데이터(ex> 쿼리 summation 결과등) 및 자주 참조 되는 데이터를 메모리에 두고 DB에서 조회를 하거나 was같은 서버에서 연산을 보다 빨리 처리 할수 있는 목적으로 사용된다. 대다수의 어플리케이션에서는 DB에서의 데이터를 얼마나 빠르게 처리하느냐로 크게 좌우되기 때문이다. 캐시 계층은 데이터가 잠시 보관 되는 곳으로 일반적으로 메모리에 저장하고 가져오기 때문에 빠르고 데이터 베이스의 부하를 줄일수 있다. 캐시레이어만 독립적으로 관리 하기 때문에 확장에도 유리하다. 아래와 같은 "읽기 주도형 캐시 전략"을 주로 사용하며 다양한 전략을 캐시 데이터 종류/크기/엑세스 패턴에 맞게 전략을 설계한다. 대표적인 솔루션으로 redis/memcached를 쓴다.
  • cache 사용시 유의점
    - 갱신이 자주 일어나지 않지만 참조가 빈번한 데이터가 적합하다.
    - 캐시는 메모리 같은 곳에 주로 저장하므로 영속적으로 저장할 데이터는 따로 추가로 저장해야한다.
    - 캐시는 만료정책 및 방출(eviction) 중요하다. 캐시 서버의 자원과 hit rate의 조율을 통해서 적은 리소스로 많은 성능 향상을 가져올수 있다. (메모리는 비싸니까, 방출에 대해서는 LRU, LFU, FIFO 등의 전략이 있다.)
    - 데이터 일관성이 중요하다. 당연히 사본들이 캐시에 저장되기 때문에 원본과 동일해야 서비스 품질에 영향이 없다.
    - Single Point of Failure(단일 장애 지점)이 되어버려 전체 장애가 올수 있으므로 어떻게 분산시키고 fail_over 할지에 대한 노하우가 필요하다. 장애 케이스에 얼마나 버텨줄수 있는지 관련 고민도 필요하다.
    - 캐시 메모리 장비의 메모리 할당이 중요하다. 당연히 장비의 메모리 할당이 적으면 적은 데이터를 가지고 처리를 하기 때문에 제대로 된 성능을 만들수 없다. 보통 과할당 하는 편이다.

그럼 이제 더 빠르게 할게 없는거야? CDN

  • queryString, cookie, header 값들을 기준으로 정적 컨텐츠(이미지, 비디오, css, html, javascript)를 캐시하여 전송하는데 쓰이며, 지리적으로 분산된 서버의 네트워크이다.일반적으로 해당 컨텐츠는 일반적인 서버에서 가공된 데이터보다 무겁고 변경이 거의 없기 때문에 지역에 맞춰 분산되어 전송시 사용된다. 사용자가 특성 서비스에 접근하면 가장 지역적으로 가까운 CDN을 통해 정적 컨텐츠를 전달 받으며 원본서버(origin)을 통해서 컨텐츠가 없거나 만료된 경우 가져온다.

  • 주의점
    • 해당 컨텐츠의 무효화가 가능해야한다. 일반적으로 제공되는 api나 버져닝을 통해 해결
    • CDN 장애시 대처 방안, 문제시 origin 서버에서 가져오도록 개선
    • 데이터 변경에 따른 ttl전략
    • CDN 비용, 생각보다 비싸며 무료 버젼도 있다. ( 관련 : cdnjs )

서버를 증설했는데 사용자에 정보 요청에 처리가 효율적이지 않아.. 무상태 웹계층 만들기

  • 장애에 대한 대응과 시스템 성능을 어느정도 향상 시켰지만 이제 트래픽이 너무 기하급수적으로 늘고 있다. web or application server를 무제한으로 수평적으로 확장하는 전략이 필요하다. 또한 지금 배포를 할 경우 사용자의 로그인이 중간에 끓겨버리는(?) 상황이 발생한다. 이를 위해서 가장 중요한 사용자의 상태정보(ex> 세션데이터)와 같은 데이터를 기존에 웹(or was)에서 담당하던 것을 분리하여 특정 저장소에 저장하여 사용 및 관리 하는 방향을 선택한다. 해당 전략 선택 후 특정 서버가 장애가 발생해도 인증이나 인가를 무리 없이 서비스를 할수 있게 된다. 로드 밸런서의 sticky policy에 의존하지 않게 되어서 단순하고 안정적이며 확장이 쉬워진다.

현재까지 정리

  • 우리는 DB와 web or application 서버를 분리 시켜 둘중에 하나가 리소스를 많이 사용 하는 상황에서 장애에서 좀 더 분리 시켰다.
  • web or application 서버를 다중화 하고 LB를 통해 라우팅하여 특정 서버가 장애 및 문제가 있을 경우 문제가 없는 다른 서버로 서비스를 하게 되었다.
  • DB를 이중화 하여서 DB가 장애날 경우 slave DB로 데이터 백업 및 서비스 장애에서 빠른 fail over가 가능하게 되었다.
  • 트래픽이 많거나 연산이 많은 요청이 올 경우 DB의 부하와 느린 응답에 대해서 Cache를 통해 서비스 품질을 향상 시키고 DB의 부하를 줄이게 되었다.
  • 글로벌 서비스를 하거나 웹서버의 성능이 부족해 CDN도입을 통해서 고객에서 빠른 정적 컨텐츠를 서빙하여 서비스의 품질을 높일수 있게 되었다.
  • 세션 스토리지를 도입하여 LB의 부하를 줄이고 배포시에 고객의 이용 품질 향상에 기여하게 되었다.

우리는 이제 카카오 같은 전국민 서비스가 됬어!! 데이터 센터 이중화가 필요해

  • 이제 우리는 카카오톡 같은 서비스가 됬다고 가정을 하고 해외에도 서비스를 하는 등의 잃을 것이 많은 서비스로 되었다고 가정해 보겠습니다. 지진이나 데이터 센터에 화재가 발생해서 우리의 서버가 있는 IDC에 문제가 생길수도 있는데요. 이런 케이스를 대비해 두개의 IDC를 이용해 사용자가 가까운 곳의 IDC로 라우팅이 되거나 장애가 발생하지 않은 IDC로 라우팅을 하는 것이 필요해졌습니다. geo-DNS를 통해서 지리적 라우팅을 하여 서비스가 가능합니다. 다만 여기서 고민 해야할 상황은 다음과 같습니다. 두개의 IDC의 데이터를 동기화 하거나 배포를 동일하게 하는 것에 대한 노하우가 필요합니다.

메시지 큐

  • 시스템을 더 큰 규모로 확장하기 위해서(특히 분산 시스템) 채택하는 방법중에 하나로 해당 솔루션은 무손실(메시지 큐에 보관도니 메시지는 소비자가 꺼낼때까지 안전함)을 보장 하는 하나의 구성이다. 생산자/소비자의 구조로 구성되어 있으며 메시지의 버퍼 역활을 해 비동기로 진행되도록 구성한다. 요청은 많고 처리량이 정해져 있는 당장 급하게 처리 해도 되지 않지만 언젠가는 처리 되어야 할 케이스에 적합하다.

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

  • 점점 서비스 규모가 커지면 작은 하나의 항목을 확인하는게 점점 어려워지게 되고 결국 도구를 도입하고 필수적으로 투자하게 된다.
    - 어플리케이션 로그
    - 메트릭
    1) 호스트 단위 메트릭 ( cpu, 메모리, 디스크 io ... )
    2) 종합 메트릭 ( 데이터베이스, 캐시의 성능 관련 지표 .. )
    3) 핵심 비지니스 메트릭 (DAU, MAU ...)

데이터베이스 규모 확장

  • 서비스가 커지면 저장해야 할 데이터가 늘어나거나 DB에서 처리 해야할 트래픽이 늘어난다. 수직적 확장을 통해서 운영할수도 있지만 결국 비용 때문에 수평적 확장을 선택하게 되고 Single Point of Failure)에 대한 위험이 크므로 수평적 확장을 선택한다. 해당 안에 대한은 샤딩(데이터 베이스를 샤드라고 부르는 작은 단위로 분할 하여 운영)에 대한 방법론이 대표적이다. 예를 들자면 다음과 같이 모듈러 연산을 통해서 분산해서 저장한다.

현재까지 최종

  • 결국 여러가지 방법을 적용해 다음과 같은 구조가 된다.
  • 사실 이전에 이러한 경험을 해보았지만 이렇게 정리해 보는 경험을 가져감으로써 좀 더 면접에서 잘 설명을 할 수 있을것 같다.
profile
기억력이 좋지 않은 개발자, 직장인 그리고 꿈이 있다.

0개의 댓글