1장 - 사용자 수에 따른 규모 확장성
1장에서는 규모 확장성과 관계된 설계 문제를 푸는 데 쓰일 유용한 지식들을 알아본다.
📖 단일 서버
모든 컴포넌트가 단 한 대의 서버에서 실행되는 간단한 시스템을 설계해보면 위와 같다. 웹 앱, 데이터베이스, 캐시 등이 전부 서버 한 대에서 실행된다. 사용자 요청 흐름은 다음과 같다.
사용자 요청 흐름
- 사용자는 도메인 이름(~.com)을 이용하여 웹사이트에 접속한다. 접속을 위해서 도메인 이름을 도메인 이름 서비스 (DNS,Domain Name Service) 에 질의하여 IP주소로 변환한다.
- DNS 조회 결과로 IP 주소가 반환된다.
- 해당 IP주소로 HTTP(HyperText Transfer Protocol) 요청이 전달된다.
- 요청 받은 웹 서버는 HTML 페이지나 JSON 형태의 응답을 반환한다.
또한 실제 요청은 웹 앱과 모바일 앱 두 가지 종류의 단말로부터 온다.
웹 애플리케이션 (Web Application) | 모바일 앱 (Mobile Application) |
- 비즈니스 로직, 데이터 저장등을 처리하기 위해 서버 구현용 언어 (Python, Java …) 사용 - 프레젠테이션용으로는 클라이언트 구현용 언어 사용 |
- 모바일 앱과 웹 서버간 통신을 위해 HTTP 프로토콜을 이용 - 반환될 응답 데이터 포맷으로는 JSON이 널리 사용됨 |
📖 데이터베이스
실제 요청은 사용자가 늘면 서버 하나로는 충분하지 않다. 따라서 하나는 웹/모바일 트래픽 처리 용도 서버(웹 계층), 다른 하나는 데이터베이스 용 서버(데이터 계층)를 둔다. 이렇게 분리해두면 각각을 독립적으로 확장해 나갈 수 있게 된다.
어떤 데이터베이스를 사용할 것인가?
이는 실제관계형 데이터베이스와 비-관계형 데이터베이스 사이에서 고를 수 있다.
RDBMS(Relational Database Management System)로는 MySQL, Oracle Database, PostgreSQL 등이 있다.
RDBMS는 자료를 table, row, column으로 표현하며, 여러 테이블에 있는 자료를 join 가능하다.
NoSQL 로는 CouchDB, Neo4j, Cassandra 등이 있다. NoSQL은 다시 4가지로 분류로 나눌 수 있다(key-value store, graph store, column store, document store).
보통 RDBMS를 많이 선택하지만 다음과 같은 경우 NoSQL이 좋은 선택일 수 있다.
💡비-관계형 데이터베이스가 바람직한 경우
- 아주 낮은 응답 지연시간(latency) 요구됨
- 다루는 데이터가 비정형(unstructured) 일때
- 데이터 (JSON, YAML, XML 등)를 직렬화(serialize)하거나 역직렬화(deserialize)할 수 있기만 하면 됨
- 아주 많은 양의 데이터를 저장할 필요가 있음
📖 수직적 vs 수평적 규모 확장
스케일 업(scale up)
- 수직적 규모 확장(vertical scaling). 서버에 고사양 자원(더 좋은 CPU, 더 많은 RAM)등을 추가하는 행위
스케일 아웃(scale out)
- 수평적 규모 확장. 더 많은 서버를 추가하여 성능을 개선하는 행위
서버로 유입되는 트래픽 양이 적을때는 수직적 확장이 좋은 선택이며, 단순함이 장점이다. 하지만 한 대의 서버에 CPU나 메모리를 무한대로 증설 불가하며, 서버에 장애가 발생하면 웹사이트/앱은 완전히 중단된다는 한계가 있다.
따라서 대규모 애플리케이션을 지원하는 데에는 수평적 규모 확장법이 적절하다.
로드 밸런서
앞서 본 서버가 한계상황이 되면 응답속도가 느려지거나 접속이 불가능해질 수 있다.이를 해결하기 위해 로드밸런서를 도입한다. 이는 부하 분산 집합(load balancing set)에 속한 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할을 한다.
웹 서버는 클라이언트 접속을 직접 처리하지 않는다. 사용자가 로드밸런서의 공개 IP주소(public IP address)로 접속한다.
사설 IP 주소
- 같은 네트워크에 속한 서버 사이의 통신에만 쓰일 수 있는 IP 주소
- 인터넷을 통해서 접속할 수 없다.
부하 분산 집합에 웹 서버를 추가하면 장애를 자동복구하지 못하는 문제는 해소되고, 웹 계층 가용성은 향상된다.
- 서버 1 다운시 모든 트래픽은 서버 2로 전송됨
- 트래픽이 가파르게 증가하면 웹 서버 계층에 더 많은 서버를 추가 (로드밸런서가 자동적으로 트래픽 분산)
데이터베이스 다중화
데이터베이스 다중화는 장애의 자동복구나 다중화를 지원하는 기술이다. 서버 사이에 주(master) - 부(slave) 관계를 설정하고 데이터 원본은 주 서버에, 사본은 부 서버에 저장하는 방식이다.
쓰기 연산(write)을 비롯한 데이터베이스 변경 명령어들(insert, delete, update)은 마스터에서만 지원하고, 부 데이터베이스는 사본을 전달받으며 읽기 연산(read)만을 지원한다.대부분의 애플리케이션은 읽기가 쓰기 연산보다 비중이 높기 때문에 부 데이터베이스의 수가 주 데베보다 많다.
💡 데이터 베이스 다중화 이점
- 더 나은 성능: 읽기 연산은 부 DB 서버들로 분산되며, 병렬로 처리될 수 있는 질의(query) 수가 늘어나므로 성능이 좋아진다.
- 안정성(realibility): 데이터를 지역적으로 떨어진 여러 장소에 다중화시켜 놓을 수 있기때문에, 재해 등으로 서버 일부가 파괴되어도 데이터 보존이 가능하다.
위 그림은 로드밸런서와 데이터베이스 다중화를 고려한 설계안이다.주요 동작은 다음과 같다.
- user는 DNS로부터 로드밸런서의 Public IP주소를 받음
- 해당 IP주소를 사용해 로드밸런서에 접속
- HTTP요청은 서버 1 또는 서버 2로 전달
- 웹 서버는 사용자의 데이터를 부 데이터베이스 서버에서 읽음
- 웹 서버는 데이터 변경 연산은 주 데이터 베이스로 전달 (insert, delete, update)
응답 시간은(cache)를 붙이고 정적 콘텐츠를 콘텐츠 전송 네트워크(Content Delivery Network, CDN)로 옮기면 개선할 수 있다.
📖 캐시
캐시는 값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고, 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소다. 애플리케이션의 성능은 DB를 얼마나 자주 호출하느냐에 크게 좌우되는데,캐시는 이런 문제를 완화한다.
캐시 계층
캐시 계층은 DB보다 훨씬 빠르다. 위같은 캐시 전략을 읽기 주도형 캐시 전략(read-through caching strategy)이라고 부른다.대부분의 프로그래밍언어로 캐시서버 API를 제공하기때문에 이용이 간단하다.
💡 캐시 사용 시 유의할 점
- 캐시는 어떤 상황에 바람직한가?
- 데이터 갱신은 자주 일어나지 않지만, 참조는 빈번하게 일어날 때
- 어떤 데이터를 캐시에 두어야 하는가?
- 캐시는 데이터를 휘발성 메모리에 두므로, 중요 / 영속적 보관 데이터는 캐시에 두면 안된다.
- 캐시보관 데이터는 어떻게 만료(expire)되는가?
- 만료 정책을 세워야한다. 만료 정책이 없으면 데이터는 캐시에 계속 남게 된다.
- 만료 기한이 너무 짧을 때: 데이터베이스를 너무 자주 읽게 된다.
- 만료 기한이 너무 길 때: 원본과 차이가 날 가능성이 높아진다.
- 일관성(consistency)은 어떻게 유지되는가?
- 일관성: 데이터 저장소의 원본과 캐시 내의 사본이 같은지 여부
- 저장소의 원본을 갱신하는 연산과 캐시를 갱신하는 연산이 단일 트랜잭션으로 처리되지 않는 경우, 일관성이 깨질 수 있다.캐시와 저장소 사이의 일관성을 유지하는 것을 어려운 문제이다. (Scaling Memcache at Facebook 논문 참고)
- 장애는 어떻게 대처할 것인가?
- 캐시 서버를 한 대만 두는 경우 해당 서버는 단일 장애 지점(Single Point of Failure, SPOF)이 되어버릴 가능성이 있다. 따라서 이를 피하려면 여러 지역에 걸쳐 캐시 서버를 분산해야 한다.
- 단일 장애 지점: 어떤 특정 지점에서의 장애가 전체 시스템의 동작을 중단시켜버릴 수 있는 경우
- 캐시 메모리는 얼마나 크게 잡을 것인가?
- 캐시 메모리가 너무 작으면, 데이터가 너무 빨리 캐시에서 밀려나버려(eviction), 캐시 성능이 저하된다.
캐시 메모리 과할당 (overprovision)으로 이를 해결할 수 있다.
- 캐시 메모리가 너무 작으면, 데이터가 너무 빨리 캐시에서 밀려나버려(eviction), 캐시 성능이 저하된다.
- 데이터 방출(eviction) 정책은 무엇인가?
- 캐시 데이터 방출 정책: 캐시가 꽉 차버리면 추가로 캐시에 데이터를 넣어야 할 경우 기존 데이터를 내보내야 함
- LRU(Least Recently Used) : 마지막으로 사용된 시점이 가장 오래된 데이터를 내보내는 정책
- LFU(Least Frequently Used) : 사용된 빈도가 가장 낮은 데이터를 내보내는 정책
- FIFO(First In Fisrt Out) : 가장 먼저 캐시에 들어온 데이터를 가장 먼저 내보내는 정책
📖 콘텐츠 전송 네트워크(CDN)
정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크이다. CDN의 동작 원리는 다음과 같다. 사용자가 웹 사이트를 방문하면, 사용자에게 가장 가까운 CDN 서버가 정적 콘텐츠를 전달하게 된다.이때 사용자가 CDN 서버로부터 멀수록 웹사이트는 천천히 로드된다.
그림 1-10의 3에서, 원본 서버가 파일을 CDN 서버에 반환할때 응답의 HTTP 헤더에는 해당 파일이 얼마나 오래 캐시될 수 있는지를 설명하는 TTL(Time-To-Live) 값이 들어 있다.
💡 CDN 사용 시 고려해야 할 사항
- 비용
- CDN은 보통 third-party provider에 의해 운영되며 CDN 데이터 전송 양에 따라 요금을 낸다.
- 자주 사용되지 않는 콘텐츠를 캐싱하는 것은 이득이 크지 않으므로, CDN에서 빼는 것을 고려하자.
- 적절한 만료 시한 설정
- time-sensitive 콘텐츠의 경우 만료시점을 잘 정해야 한다.
- 너무 길면 콘텐츠의 신선도가 떨어지고, 너무 짧으면 원본에 빈번하게 접속해야 한다.
- CDN 장애에 대한 대처 방안
- 일시적으로 CDN이 응답하지 않을 경우, 해당 문제를 감지하여 원본 서버로부터 직접 콘텐츠를 가져오도록 클라이언트를 구성하는 것이 필요할 수 있다.
위는 CDN과 캐시가 추가된 설계이다. 정적 컨텐츠는 CDN을 통해 제공하여 더 나은 성능을 보장하며,캐시가 데이터베이스 부하를 줄여준다.
📖 무상태(stateless) 웹 계층
웹 계층을 수평적으로 확장하는 방법을 고민해 볼 순서다.이를 위해서 상태 정보(사용자 세션 데이터와 같은)를 웹 계층에서 제거해야 한다. 바람직한 전략은 상태 정보를 RDBMS나 NoSQL 같은 지속성 저장소에 보관하고, 필요할 때 가져오는 것이다. 이렇게 구성된 웹 계층을 무상태 웹 계층이라 부른다.
상태 정보 의존적인 아키텍처는 같은 클라이언트로부터의 요청은 항상 같은 서버로 전송되어야 한다.(사용자 B로부터의 HTTP 요청은 전부 서버 2로 전송되어야 함) 대부분의 로드밸런서는 이를 지원하기 위해 고정 세션(sticky session)이라는 기능을 제공하는데, 이는 로드밸런서에 부담을 주며 장애 처리 또한 복잡해진다.
무상태 아키텍처는 사용자로부터의 HTTP 요청은 어떤 웹 서버로도 전달 될 수 있다. 웹 서버는 상태 정보 필요시 공유 저장소로부터 데이터를 가져온다. 상태 정보는 웹 서버로부터 물리적으로 분리되어 있기때문에, 이런 구조는 단순하고, 안정적이며, 규모 확장이 쉽다.
위는 무상태 웹 계층을 갖도록 기존 설계를 변경한 결과이다. 세션 데이터를 웹 계층에서 분리해 지속성 데이터 보관소에 저장하도록 만들었다. 여기서는 규모확장이 간편한 NoSQL을 사용하였다.
①의 자동 규모 확장(autoscaling)은 트래픽 양에 따라 웹 서버를 자동으로 추가하거나 삭제하는 기능이다.
📖 데이터 센터
웹 사이트가 더욱 확장되어, 전 세계 사용자를 받기 위해서는 여러 데이터 센터 지원이 필수이다.
장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내되며, 이 절차를 지리적 라우팅(geoDNS-routing 또는 geo-routing)이라고 부른다. 이는 사용자의 위치에 따라 도메인 이름을 어떤 IP주소로 변환할지 결정 할 수 있도록 해주는 DNS 서비스이다. 데이터 센터 중 하나에 심각한 장애가 발생시, 모든 트래픽은 장애가 없는 데이터 센터로 전송된다.
💡 다중 데이터센터 아키텍처 고려사항
- 트래픽 우회
- 올바른 데이터 센터로 트래픽을 보내는 효과적인 방법을 찾아야 한다.
- GeoDNS는 사용자에게서 가장 가까운 데이터센터로 트래픽을 보낼 수 있도록 한다.
- 데이터 동기화(synchronization)
- 데이터 센터마다 별도의 DB를 사용한다면, 장애가 자동으로 복구되어(failover) 트래픽이 다른 DB로 우회된다해도, 해당 데이터센터에는 찾는 데이터가 없을 수 있다.
- 이런 상황을 막는 보편적 전략은 데이터를 여러 데이터센터에 걸쳐 다중화 하는 것이다. (?)
- 테스트와 배포(deployment)
- 웹 사이트 또는 애플리케이션을 여러 위치에서 테스트 해보는 것이 중요
- 자동화된 배포 도구는 모든 데이터 센터에 동일한 서비스가 설치되도록 하는데 중요한 역할을 함
📖 메시지 큐
메시지 큐는 메시지의 무손실(durability.메시지 큐에 일단 보관된 메시지는 소비자가 꺼낼 때까지 안전히 보관된다는 특성)을 보장하는, 비동기 통신(asynchronous communication)을 지원하는 컴포넌트이다. 메시지의 버퍼 역할을 하며, 비동기적으로 전송한다.
웹 서버는 사진 보정작업(job)들을 메시지 큐에 넣는다. 사진 보정 작업 프로세스들(소비자들)은 작업을 메시지 큐에서 꺼내어 비동기적으로 완료한다. 이렇게 하면 생산자와 소비자 서비의 규모는 각기 독립적으로 확장될 수 있다.
💡 메시지 큐의 아키텍처
- 생산자 또는 발행자(producer/publisher)라고 불리는 입력 서비스가 메시지를 만들어 메시지 큐에 발행(publish)
- 큐에는 보통 소비자 혹은 구독자(consumer/subscriber)라 불리는 서비스 혹은 서버가 연결되어 있는데, 메시지를 받아 그에 맞는 동작을 수행
💡 메시지 큐의 이점
- 서버 간 결합이 느슨해져서, 규모 확장성이 보장되어야 하는 안정적 애플리케이션 구성 가능(결함에 대한 내성 ↑)
- 생산자는 소비자 프로세스가 다운되어 있어도 메시지 발행 가능
- 소비자는 생산자 서비스가 가용한 상태가 아니더라도 메시지를 수신 가능
📖 로그, 메트릭 그리고 자동화
사이트 큐모가 커지면 로그나 메트릭(metric), 자동화(automation) 같은 도구는 필수적이다.
- 로그
- 에러 로그를 모니터링하는 것은 중요하다.이는 시스템의 오류와 문제들을 보다 쉽게 찾아낼 수 있도록 한다.
- 서버 단위로도 모니터링 가능하지만, 로그를 단일 서비스로 모아주는 도구를 활용하면 더 편리하다.
- 메트릭
- 메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보 획득 가능, 시스템의 현재 상태를 손쉽게 파악 가능하다.
- 메트릭 중에 특별히 유용한 것들은 다음과 같다.
- 호스트 단위 메트릭 : CPU, 메모리, 디스크I/O에 관한 메트릭
- 종합(aggregated) 메트릭 : 데이터베이스 계층의 성능, 캐시 계층의 성능 등
- 핵심 비즈니스 메트릭 : 일별 능동 사용자(daily active user), 수익(revenue), 재방문(retention) 등
- 자동화
- 생산성을 높이기 위해 자동화 도구를 활용해야한다. 가령 CI/CD등 지속적 통합을 도와주는 도구를 활용하면 개발 생산성이 크게 향산된다.
📖 데이터베이스의 규모 확장
저장데이터가 늘어나면 이를 증설해야한다.데이터베이스의 규모확장에는 수직,수평적 규모 확장법 두가지가 있다.
수직적 확장
스케일 업이라고도 부른다. 고성능 DB 서버는 많은 양의 데이터를 보관하고 처리할 수 있다. 하지만 한계가 있다.
💡 수직적 접근법의 한계점
- 데이터베이스 서버 하드웨어에는 한계가 있으므로 CPU, RAM등을 무한 증설 불가
- SPOF(Single Point of Failure)로 인한 위험성이 크다.
- 고성능서버로 갈수록 가격이 오른다. 비용이 많이 든다.
수평적 확장
샤딩(sharding)이라고도 부른다. 더많은 서버를 추가함으로써 성능을 향상시킨다. 샤딩은 대규모 DB를 샤드(shard)라 부르는 작은 단위로 분할하는 기술이다.
모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다. 또한 가장 중요한 것은 샤딩키(sharding key.partition key라고도 부른다.) 설정이다. 데이터가 어떻게 분산될지 정하는 하나 이상의 칼럼으로 구성된다(위의 경우 user_id). 샤딩 키를 정할 때는 데이터를 고르게 분할 할 수 있도록 하는게 가장 중요하다.
💡 샤딩 도입 시 풀어야할 문제들
- 데이터의 재 샤딩(resharding)
- 데이터가 너무 많아져서 하나의 샤드로는 더 이상 감당하기 어려울 때
- 샤드 간 데이터 분포가 균등하지 못하여, 특정 샤드에 할당된 공간 소모가 다른 샤드에 비해 빨리 진행 될 떄 (샤드 소진, shard exhaustion). 샤드 소진 시에는 샤드 키를 계산하는 함수를 변경하고 데이터 재배치를 한다. (5장에서 다룰 안정 해시(consistent hashing) 기법을 활용하여 해결 가능)
- 유명인사(celebrity) 문제
- hotspot key문제 라고도 부르며, 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제
- SNS 앱에서 특정 셀럽들이 전부 같은 샤드에 저장되면 read 연산때문에 과부하가 걸릴 수 있다.
- 조인과 비정규화 (join and denornalization)
- 일단 하나의 데이터베이스를 여러 샤드 서버로 쪼개고 나면, 여러 샤드에 걸친 데이터를 조인하기가 힘들어진다.
- 이를 해결하기 위해선, 데이터베이스를 비정규과하여 하나의 테이블에서 질의가 수행될 수 있도록 해야한다. (?)
📖 백만 사용자, 그리고 그 이상
💡 시스템 규모 확장을 위해 살펴본 기법들 정리
- 웹 계층은 무상태(stateless) 계층으로
- 모든 계층에 다중화 도입
- 가능한 한 많은 데이터를 캐시할 것
- 여러 데이터 센터를 지원할 것
- 정적 콘텐츠는 CDN을 통해 서비스할 것
- 데이터 계층은 샤딩을 통해 그 규모를 확장할 것
- 각 계층은 독립적 서비스로 분할할 것
- 시스템을 지속적으로 모니터링하고, 자동화 도구들을 활용할 것
'시스템 설계' 카테고리의 다른 글
[가상 면접 사례로 배우는 대규모 시스템 설계 기초] 개략적인 규모 추정 (1) | 2023.10.10 |
---|