ch01 대규모 시스템 설계 기초
1장 사용자 수에 따른 규모 확장성
한 명의 사용자를 지원하는 시스템에서 시작하여, 몇백만 사용자를 지원하는 시스템을 설계한다.
규모 확장성과 관계된 설계 문제를 푸는 데 쓰일 유용한 지식들을 설명한다.
단일 서버
- 사용자는 도메인 이름을 이용하여 웹사이트에 접속한다. 이 접속을 위해서는 도메인 이름을 도메인 이름 서비스에 질의하여 IP 주소로 변환하는 과정이 필요하다.
- DNS 조회로 웹 서버의 IP 주소가 반환된다.
- 해당 IP 주소로 HTTP 요청이 전달된다.
- 요청을 받은 웹 서버는 HTML 페이지나 JSON 형태의 응답을 반환한다.
데이터베이스
사용자가 늘어나면 서버 하나로는 충분하지 않아 서버를 여러개로 두어야 한다. 하나는 웹 / 모바일 트래픽 처리 용도고, 다른 하나는 데이터베이스 용이다.
RDBMS, NoSQL 2가지로 나눌 수 있다.
대체적으로 RDBMS로 해결할 수 있는 상황이지만, 아래와 같은 경우 NoSQL이 적절한 선택일 수 있다.
- 아주 낮은 응답 지연시간이 요구됨
- 다루는 데이터가 비정형이라 관계형 데이터가 아님
- 데이터를 직렬화하거나 역직렬화할 수 있기만 하면 됨
- 아주 많은 양의 데이터를 저장할 필요가 있음
비-관계형 데이터베이스(NoSQL) 대표적으로 CouchDB, Neo4j, Cassandra, HBase, Amazon DynamoDB 등이 있다.
- 키-값 저장소(key-value store)
- 그래프 저장소(graph store)
- 칼럼 저장소(column store)
- 문서 저장소(document store)
비-관계형 데이터베이스는 일반적으로 조인연산을 지원하지 않음
수직적 규모 확장 vs 수평적 규모 확장
수직적 확장(scale up) : 서버의 하드웨어 성능(CPU, 메모리 등)을 향상
수평적 확장(scale out) : 서버를 추가하여 처리 능력을 분산
무엇이 더 적절할까?
서버로 유입되는 트래픽의 양이 적을 때는 수직적 확장이 좋은 선택이며, 이 방법의 가장 큰 장점은 단순함이다. 그러나 스케일 업에는 심각한 단점이 존재한다.
- 한 대의 서버에 CPU나 메모리를 무한대로 증설할 방법은 없다.
- 장애에 대한 자동복구(failover) 방안이나 다중화 방안을 제시하지 않는다. 서버에 장애가 발생하면 웹사이트 / 앱은 완전히 중단된다.
위와 같은 단점 때문에, 대규모 애플리케이션을 지원하는 데는 수평적 규모 확장법이 보다 적절하다.
로드밸런서
사용자는 로드밸런서의 Public IP
로 접속한다. 서버 간 통신에는 Private IP
가 이용된다. 위처럼 스케일 아웃을 통해서 서버를 여러 대로 늘리게 되면 no failover도 해소할 수 있고, 웹 계층의 가용성은 향상된다.
가용성이 어떻게 향상될까?
- 서버 1이 다운되면 모든 트래픽은 서버 2로 전송된다. 따라서 웹 사이트 전체가 다운되는 일이 방지된다. 부하를 나누기 위해 새로운 서버를 추가할 수도 있다.
- 웹사이트로 유입되는 트래픽이 가파르게 증가하면 두 대의 서버로 트래픽을 감당할 수 없는 시점이 오는데, 로드밸런서가 있으므로 우아하게 대처할 수 있다. 웹 서버 계층에 더 많은 서버를 추가하기만 하면 된다. 그러면 로드밸런서가 자동적으로 트래픽을 분산하기 시작할 것이다.
의문: 클라우드 서비스만을 사용해서 로드밸런서를 사용해야할까?
[데이터 계층]
데이터베이스 다중화
데이터베이스를 Master-Slave
로 나누는데 쓰기 연산
은 Master
에서만 지원하고, 읽기 연산
은 Slave
에서만 지원한다. 대부분의 애플리케이션에서는 쓰기 작업
보다는 읽기 작업
이 훨씬 많기 때문에 Slave 데이터베이스가 훨씬 많다.
select
는 slave DB
update
는 master DB
데이터베이스 다중화의 장점
- 더 나은 성능 : 모든 데이터 변경 연산은 Master 서버로만 전달되는 반면 읽기 연산은 Slave 데이터베이스 서버들로 분산된다. 병렬로 처리될 수 있는 쿼리의 수가 늘어나므로 성능이 좋아진다.
- 안정성 : 데이터베이스 서버 가운데 일부가 파괴되어도 데이터는 보존될 것이다. 데이터를 지역적으로 떨어진 여러 장소에 다중화 시켜놓을 수 있기 때문이다.
- 가용성 : 데이터를 여러 지역에 복제해 둠으로써, 하나의 데이터베이스 서버에 장애가 발생하더라도 다른 서버에 있는 데이터를 가져와 계속 서비스할 수 있게 된다.
질문 : 데이터베이스 서버 가운데 하나가 다운되면 무슨 일이 벌어지는가?
아래와 같은 상황을 감당할 수 있다.
- 부 서버가 한 대뿐인데 다운된 경우, 읽기 연산은 한시적으로 모두 주 데이터베이스로 전달됨
- 즉시 새로운 부 데이터베이스 서버가 장애 서버를 대체
- 부 서버가 여러대인 경우, 읽기 연산은 나머지 부 데이터베이스 서버들로 분산됨
- 새로운 부 데이터베이스 서버가 장애 서버를 대체
- 주 데이터베이스 서버가 다운된 경우, 한 대의 부 데이터베이스만 있는 경우 해당 부 데이터베이스 서버가 새로운 주 서버가 됨, 모든 데이터베이스 연산은 새로운 주 서버가 됨
- 새로운 부서버가 추가됨
- 부 서버에 보관된 데이터가 최신 상태가 아닐 수 있음. 없는 데이터는 복구 스크립트를 돌려 추가해야함
- 다중 마스터(multi-masters), 원형 다중화(circular replication) 방식을 도입하면 도움될 수 있음. 하지만 구성이 훨씬 복잡함
- 참고문헌 4 5
로드밸런서와 데이터베이스 다중화
- 사용자는 DNS로부터 로드밸런서의 공개 IP 주소를 받는다.
- 사용자는 해당 IP 주소를 사용해 로드밸런서에 접속한다.
- HTTP 요청은 서버 1이나 서버 2로 전송된다.
- 웹 서버는 사용자의 데이터를 부 데이터베이스 서버에서 읽는다.
- 웹 서버는 데이터 변경 연산은 주 데이터베이스로 전달한다.
캐시
캐시는 값 비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고, 뒤 이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소다. 애플리케이션의 성능은 데이터베이스를 얼마나 자주 호출하느냐에 크게 좌우되는데, 캐시는 그런 문제를 완화할 수 있다.
캐시 계층
캐시 계층은 데이터가 잠시 보관되는 곳으로 데이터베이스보다 훨씬 빠르다.
- 요청을 받은 웹 서버는 캐시에 응답이 저장되어 있는지를 확인한다.
- 만일 저장되어 있다면 해당 데이터를 클라이언트에 반환한다.
- 없는 경우에는 데이터베이스 쿼리를 통해 데이터를 찾아 캐시에 저장한 뒤 클라이언트에 반환한다.
캐시 사용 시 유의할 점
- 캐시는 갱신은 자주 일어나지 않지만 참조는 빈번하게 일어난다면 고려해볼만 하다.
- 영속적으로 보관할 데이터를 캐시에 두는 것은 바람직하지 않다.
- 캐시는 데이터를 휘발성 메모리에 두기 때문에, 캐시 서버가 재시작되면 모든 데이터는 사라진다.
- 만료된 데이터는 캐시에서 삭제되어야 한다.
- 만료 기한에 대한 정책을 마련하는 것이 좋다.
- 만료 기한이 너무 짧으면, 데이터베이스를 너무 자주 읽게 되며, 너무 길면 원본과 차이가 날 가능성이 높아진다.
- 데이터 저장소의 원본과 캐시 내의 사본 일관성을 확인해야 한다.
- 캐시 서버를 한 대만 두는 경우 해당 서버는 단일 장애 지점(SPOF)이 되어 버릴 수 있다.
- 캐시 메모리 크기는 너무 크지도 작지도 않게 적절하게 잡아야 한다.
- 캐시가 꽉 차버리면 LRU, LFU, FIFO 같은 정책들을 사용해야 한다.
콘텐츠 전송 네트워크(CDN)
CDN은 정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크이다. 이미지, 비디오, CSS, JavaScript 파일 등을 캐시할 수 있다.
- 사용자 A가 이미지 URL을 이용해 image.png에 접근한다.
- CDN 서버의 캐시에 해당 이미지가 없는 경우, 서버는 원본 서버에 요청하여 파일을 가져온다.
- 원본 서버가 파일을 CDN 서버에 반환한다.
- CDN 서버는 파일을 캐시하고 사용자 A에게 반환한다.
- 사용자 B가 같은 이미지에 대한 요청을 CDN 서버에 전송한다.
- 만료되지 않은 이미지에 대한 요청은 캐시를 통해 처리된다.
CDN 사용 시 고려해야 할 사항
- 비용적인 측면
- CDN은 제3 사업자에 의해 운영되며, 들어가고 나가는 데이터 전송 양에 따라 요금을 내게 된다.
- 자주 사용되지 않는 콘텐츠를 캐싱하는 것은 이득이 크지 않으므로 빼는 것을 고려해야한다.
- 적절한 만료 시한은?
- 시의성이 중요한 콘텐츠의 경우 만료 시점을 잘 정해야 하는데, 너무 길지도 않고 짧지도 않아야한다.
- 너무 길면 콘텐츠의 신선도가 떨어질 것이고, 너무 짧으면 원본 서버에 빈번히 접속하게 되어서 좋지 않다.
- CDN 장애에 대한 대처 방안
- CDN 자체가 죽었을 경우, 웹 애플리케이션이 어떻게 동작해야 하는지 고려해야 한다.
- 일시적으로 CDN이 응답하지 않을 경우, 원본 서버로부터 직접 콘텐츠를 가져오도록 클라이언트를 구성하는 것이 필요할 수 있다.
- 콘텐츠 무효화 방법
- 만료되지 않은 콘텐츠라도 아래 방법 중 하나를 쓰면 CDN에서 제거할 수 있다.
- CDN 서비스 사업자가 제공하는 API를 이용하여 콘텐츠 무효화
- 콘텐츠의 다른 버전을 서비스하도록 오브젝트 버저닝 이용. 콘텐츠의 새로운 버전을 지정하기 위해서는 URL 마지막에 버전 번호를 인자로 주면 된다. ex) image.png?v=2
CDN + 캐시
- 정적 컨텐츠(JS, CSS, 이미지)는 더 이상 웹 서버를 통해 서비스하지 않으며, CDN을 통해 제공하여 더 나은 성능을 보장한다.
- 캐시가 데이터베이스 부하를 줄여준다.
무상태(stateless) 웹 계층
이제는 웹 계층을 수평적으로 확장하는 방법을 고민해보자. HTTP 같은 경우는 stateless 하기 때문에 세션 같은 정보는 저장을 해두어야 한다. 세션 정보를 서버 내부 메모리와 같은 곳에 사용할 수 있는데, 그런데 만약 서버가 여러 대라면 어떻게 세션 정보를 유지할 수 있을까?
세션은 요청에 대한 Context다.
위와 같이 세션 저장소를 서버 내부 메모리를 사용하지 않고, RDBMS
or Redis/Memcached
와 같은 외부 공유 저장소이다.
무상태 Stateless 클라이언트와 서버 관계에서 서버가 클라이언트의 상태를 보존하지 않음을 의미한다. Stateless 구조에서 server는 단순히 요청이 오면 응답을 보내는 역할만 수행하며, 세션 관리는 client에게 책임이 있다. 따라서 이러한 Stateless 구조는 client와의 세션 정보를 기억할 필요가 없으므로, 이러한 정보를 서버에 저장하지 않는다. 대신 필요에 따라 외부 DB에 저장하여 관리할 수 있다. 웹 서버 통신(http) 특성 상 사용자(브라우저)의 이전 상태 client(쿠키) or server(세션) 정보를 기록하지 않는 접속이란 의미이다. 브라우저가 데이터를 전송할 때마다 연결하고 바로 끊어버리는 방식이다. 장점 : 서버의 확장성이 높기 때문에 대량의 트래픽 발생 시에도 대처를 수월하게 할 수 있다. 단점 : 클라이언트의 요청에 상대적으로 Stateful 보다 더 많은 데이터가 소모된다. (매번 요청할 때 마다 자신의 부가 정보를 줘야 하니까)
상태유지 Stateful 상태 유지라 함은 클라이언트와 서버 관계에서 서버가 클라이언트의 상태를 보존함을 의미한다. 클라이언트의 이전 요청이 서버에 전달되었을 때, 클라이언트의 다음 요청이 이전 요청과 관계가 이어지는 것을 의미한다. 웹서버가 사용자(브라우저)의 상태 client(쿠키) or server(세션) 정보를 기억하고 있다가 유용한 정보로써 활용한다는 의미이다. 클라이언트에서 다른 클라이언트로, 또는 서버에서 특정 클라이언트로 메시지를 전송할 수 있다. 서버에서 클라이언트 세션을 유지할 필요가 없을 때 서버 리소스를 절약하는 장점이 있다. Stateful 방식은 하나의 서버가 1만 명의 클라이언트를 처리할 능력이 있을 때 그보다 많은 수의 클라이언트가 몰리면, 이미 연결된 1만 명의 클라이언트 중 일부가 빠져야 다음 클라이언트가 처리된다. 하지만 Stateless 방식은 순간 접속 요청 인원을 기준으로 하므로 많은 클라이언트가 몰려도 할당된 처리량이 끝나면 다음 처리가 가능하다.
무상태 웹 계층으로 기존 설계를 변경
- 세션 데이터를 웹 계층으로 분리, 지속성 데이터 보관소를에 저장하도록 만들었다.
- 공유 저장소는 관계형 데이터베이스, Memcached/Redis 캐시 시스템, NoSQL일 수 있다.
- 위 설계에서는 NoSQL을 도입하였다.
- ➀의 자동 규모 확장은 트래픽 양에 따라 웹 서버를 자동으로 추가하거나 삭제하는 기능이다.
데이터 센터
장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내되는데, 통상 이 절차를 지리적 라우팅이라고 부른다.
만약 데이터 센터 중 하나에 심각한 장애가 발생하면 모든 트래픽은 장애가 없는 데이터 센터로 전송된다.
다중 데이터 센터를 만들기 위해 고려해야 할 조건
- 트래픽 우회 : 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야 한다.
- 데이터 동기화 : 데이터 센터마다 별도의 데이터베이스를 다중화하기
- 테스트와 배포 : 여러 위치에서 애플리케이션을 테스트 해보고, 자동화 배포가 모든 데이터 센터에 동일하게 설치되도록 하는 것이 중요하다.
메시지 큐
메시지 큐는 메시지의 무손실, 즉 메시지 큐에 일단 보관된 메시지는 소비자가 꺼낼 때까지 안전히 보관된다는 특성을 보장하는 비동기 통신을 지원하는 컴포넌트이다.
Publish/Producer 는 메시지 큐에 발행한다. 큐에는 보통 Consumer / Subscribe가 메시지를 꺼내서 동작을 수행한다.
메시지 큐를 이용하면 서비스 또는 서버 간 결합이 느슨해져서, 규모 확장성이 보장되어야 하는 안정적 애플리케이션을 구성하기 좋다.
로그, 메트릭 그리고 자동화
비즈니스의 규모가 커지면 로그, 메트릭, 자동화 도구에 필수적으로 투자해야한다
- 로그 : 에러 모니터링을 위해. 로그를 단일 서비스로 모아주는 도구를 사용할 수도 있음
- 메트릭 : 유용한 정보, 시스템의 현 상태를 파악하기 위해
- 호스트 단위 메트릭 : CPU, 메모리, 디스크 I/O
- 종합 메트릭 : 데이터베이스 계층의 성능, 캐시 계층의 성능
- 핵심 비즈니스 메트릭 : 일별 능동 사용자, 수익, 재방문
- 자동화 : 생산성과 안정성을 높이기 위해
- 빌드, 테스트 배포 자동화
메시지 큐, 로그, 메트릭, 자동화를 반영하여 수정한 설계
- 메시지 큐는 각 컴포넌트가 보다 느슨한 결합이 될 수 있도록 하고, 결함에 대한 내성을 높인다.
- 로그, 모니터링, 메트릭, 자동화 등을 지원하기 위한 장치를 추가하였다.
데이터베이스의 규모 확장
저장할 데이터가 많아지면 데이터베이스에 대한 부하도 증가한다. 규모를 확장하는 데는 두 가지 접근법이 있다. 하나는 수직적 규모 확장법이고 다른 하나는 수평적 규모 확장법이다.
데이터베이스에도 수직적 확장보다는 수평적 확장인 샤딩이 적절하다. 샤딩은 대규모 데이터베이스를 샤드라고 부르는 작은 단위로 분할하는 기술을 말한다. 모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다.
어떻게 저장할까?
사용자 데이터를 어느 샤드에 넣을지 사용자 ID에 따라 정한다고 했을때, user_id % 4를 해시함수로 사용하여 데이터를 보관하는 방식이다. 0이면 0번 샤드에 1이면 1번 샤드에 보관하는 방식이다.
샤딩 전략을 구현할 때 중요한 것은 샤딩 키 이다. 샤딩 키에 따라서 한 곳으로만 부하가 집중될 수 있고 여러 곳으로 적절히 잘 분산될 수도 있다.
샤딩을 위해서 고려해야 할 조건
- 데이터의 재 샤딩
- 데이터가 너무 많아져서 하나의 샤드로는 더 이상 감당하기 어려울 때
- 샤드 간 데이터 분포가 균등하지 못하여 어떤 샤드에 할당된 공간 소모가 다른 샤드에 비해 빨리 진행될 때
- 유명인사문제: 핫스팟 키 문제라고도 부르는데, 특정 샤드에 쿼리가 집중되어 서버에 과부하가 걸리는 문제
- 조인과 비정규화 : 일단 하나의 데이터베이스를 여러 샤드 서버로 쪼개고 나면, 여러 샤드에 걸친 데이터를 조인하기가 힘들어진다. 이를 해결하는 한 가지 방법은 데이터베이스를 비정규화하여 하나의 테이블에서 쿼리가 수행될 수 있도록 하는 것이다.
데이터베이스 샤딩을 적용한 아키텍처
정리
- 웹 계층은 무상태 계층으로
- 모든 계층에 다중화 도입
- 가능한 한 많은 데이터를 캐시할 것
- 여러 데이터 센터를 지원할 것
- 정적 콘텐츠는 CDN을 통해 서비스할 것
- 데이터 계층은 샤딩을 통해 그 규모를 확장할 것
- 각 계층은 독립적으로 서비스로 분할할 것
- 시스템을 지속적으로 모니터링하고, 자동화 도구들을 활용할 것
Ref
- 가상면접사례로 배우는 대규모 시스템 설계 기초