단일 서버
- 모든 컴포넌트가 단 한대의 서버에서 실행되는 간단한 시스템부터 설계해 보기
- 웹 앱, 데이터베이스, 캐시 등이 전부 서버 한 대에서 실행됨
사용자 요청 처리 흐름
- 사용자는 도메인 이름을 이용하여 웹사이트에 접속. 도메인 이름을 `도메인 이름 서비스(Domain Name Service, DNS)`에 질의하여 IP 주소로 변환하는 과정을 거침. DNS는 보통 제3 사업자(third party)가 제공하는 유료 서비스를 이용하게 되므로, 우리 시스템의 일부는 아님.
- DNS 조회 결과로 IP 주소가 반환됨
- 해당 IP 주소로 HTTP(HyperText Transfer Protocol) 요청이 전달됨
- 요청을 받은 웹 서버는 HTML 페이지나 JSON 형태의 응답을 반환
이 요청들은 `웹 앱`과 `모바일 앱` 두 가지 종류의 단말로부터 온다.
- 웹 애플리케이션 : 비즈니스 로직, 데이터 저장 등을 처리하기 위해서 `서버 구현용 언어(자바, 파이썬 등)`를 사용하고, 프레젠테이션 용으로는 `클라이언트 구현용 언어(HTML, 자바스크립트 등)`를 사용
- 모바일 앱 : 모바일 앱과 웹 서버 간 통신을 위해서는 `HTTP 프로토콜`을 이용. HTTP 프로토콜을 통해서 반환될 응답 데이터의 포맷으로는 보통 `JSON(JavaScript Object Notation)`이 널리 쓰임.
데이터베이스
- 사용자 증가에 따라 서버 하나로는 충분하지 않아서 여러 서버를 두어야 함
- 하나는 웹/모바일 트래픽 처리 용도
- 다른 하나는 데이터베이스용
- 따라서 웹/모바일 트래픽 처리 서버(웹 계층)와 데이터베이스 서버(데이터 계층)를 분리하여 각각을 독립적으로 확장 가능
어떤 데이터베이스를 사용할 것인가?
`관계형 데이터베이스`와 `비-관계형 데이터베이스` 사이에서 고를 수 있음
관계형 데이터베이스
- 관계형 데이터베이스 관리 시스템(Relational Database Management System, RDBMS)이라고도 불림
- MySQL, 오라클 데이터베이스, PostgreSQL 등이 있음
- 자료를 테이블과 열, 칼럼으로 표현
- SQL을 사용하여 여러 테이블에 있는 데이터를 조인(join)할 수 있음
비-관계형 데이터베이스
- NoSQL이라고도 부름
- CouchDB, Neo4j, Cassandra, HBase, Amazon DynamoDB 등이 있음
- NoSQL은 다시 네 부류로 나눌 수 있음
- 키-값 저장소(key-value store), 그래프 저장소(graph store), 칼럼 저장소(column store), 문서 저장소(document store)
- 일반적으로 조인 연산은 지원하지 않음
❓비-관계형 데이터베이스에서 조인연산을 지원하는 경우는?
> 데이터 모델링과 애플리케이션 수준에서 조인 연산을 구현할 수 있다고 함
비-관계형 데이터베이스가 바람직한 경우
대부분의 상황에서는 관계형 데이터베이스가 최선이나, 아래와 같은 경우에는 비-관계형 데이터베이스가 옳은 선택일 수 있음
- 아주 낮은 응답 지연시간(latency)이 요구됨
- 다루는 데이터가 비정형(unstructured)이라 관계형 데이터가 아님
- 데이터(JSON, YAML, XML 등)를 직렬화하거나(serialize) 역직렬화(deserialize) 할 수 있기만 하면 됨
- 아주 많은 양의 데이터를 저장할 필요가 있음
수직적 규모 확장 vs 수평적 규모 확장
수직적 규모 확장(vertical scaling)
스케일 업(scale up)이라고도 불림. 서버에 고사양 자원(더 좋은 CPU, 더 많은 RAM 등)을 추가하는 행위
- 서버로 유입되는 양이 적을 때
- 가장 큰 장점 : 단순함
- 단점은 아래와 같음
- 한 대의 서버에 CPU나 메모리를 무한대로 증설할 방법은 없음
- 장애에 대한 자동복구(failover) 방안이나 다중화(redundancy) 방안을 제시하지 않음 >> 서버에 장애가 나면 웹사이트/앱이 완전히 중단됨
수평적 규모 확장
스케일 아웃(scale out)이라고도 불림. 더 많은 서버를 추가하여 성능을 개선하는 행위
- 스케일 업의 단점 때문에, 대규모 애플리케이션을 지원하는 데에는 `스케일 아웃`이 더 적절함
로드밸런서(load balancer)
- 위에서 나온 설계는 사용자가 웹 서버에 바로 연결되어 있기 때문에, 웹 서버가 다운되면 사용자는 웹 서버에 접속할 수 없음
- 또한, 너무 많은 사용자가 접속하여 웹 서버가 한계 상황에 도달하게 되면 응답 속도가 느려지거나 서버 접속이 불가능해질 수 있음
위와 같은 문제들로 로드 밸런서를 도입하는 것이 최선이다.
- 그림 1-4와 같이 사용자는 로드밸런서의 공개 IP 주소(public IP address)로 접속함
- 웹 서버는 클라이언트의 접속을 직접 처리하지 않음
- 로드밸런서는 웹 서버와 사설 IP 주소(private IP address)를 이용하여 통신한다.
- 위와 같이 부하 분산 집합에 웹 서버를 여러 대 두면, 장애를 자동복구하지 못하는 문제(no failover)가 해소됨
데이터베이스 다중화
- 보통은 서버 사이에 `주(master)-부(slave) 관계`를 가짐
- 데이터 원본은 주 서버에, 사본은 부 서버에 저장하는 방식
주(master) 데이터베이스 | 부(slave) 데이터베이스 | |
쓰기 연산(write operation) | O (only) | X |
데이터베이스 변경 명령어(insert, delete, update, ...) | O (only) | X |
읽기 연산(read operation) | △(가능은 하나 성능상 slave에서만) | O |
설명 | 쓰기 연산, 데이터베이스를 변경하는 명령어들(insert, delete, update, ...)은 master에서만 지원 slave는 master로부터 사본을 전달받으며, 읽기 연산만을 지원 |
더 나은 성능을 위하여,
- master-slave 다중화 모델에서 모든 데이터 변경 연산은 master 서버에만 전달됨
- 읽기 연산은 slave 서버들로 분산되어 병렬로 처리할 수 있는 qeury의 수가 늘어나 성능이 좋아짐
- 안정성(reliability) : 자연재해 등의 이유로 데이터베이스 서버 가운데 일부가 파괴되어도 데이터는 보존될 것
- 가용성(availability) : 하나의 데이터베이스 서버에 장애가 발생하여도 다른 서버에 있는 데이터를 가져와 계속 서비스 가능
그림 1-6은 로드밸런서와 데이터베이스 다중화를 고려한 설계안이다.
- 사용자는 DNS로부터 로드밸런서의 공개 IP 주소를 받음
- 사용자는 해당 IP 주소를 통해 로드밸런서에 접속
- HTTP 요청은 서버 1이나 서버 2로 전달됨
- 웹 서버는 사용자의 데이터를 부 데이터베이스 서버에서 읽음
- 웹 서버는 데이터 변경 연산(데이터 추가, 삭제, 갱신 연산 등)은 주 데이터베이스로 전달
캐시(cache)
응답시간 개선을 위해 캐시를 붙이고 정적 콘텐츠를 콘텐츠 전송 네트워크(Content Delivery Network, CDN)로 옮기면 개선할 수 있음
캐시란?
- 값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고, 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소이다.
- 애플리케이션의 성능은 데이터베이스를 얼마나 자주 호출하느냐에 크게 좌우되는데, 캐시는 이 문제를 완화할 수 있다.
캐시 계층(cache tier)
- 캐시 계층은 데이터가 잠시 보관되는 곳으로 데이터베이스보다 훨씬 빠름
- 별도의 캐시 계층을 두면 성능이 개선될 뿐 아니라 데이터베이스의 부하를 줄일 수 있고,
- 캐시 계층의 규모를 독립적으로 확장시키는 것도 가능
읽기 주도형 캐시 전략(read-through caching strategy)
- 요청을 받은 웹 서버는 캐시에 응답이 저장되어 있는지 확인
- 저장되어 있다면 해당 데이터를 클라이언트에 반환
- 없는 경우에는 데이터베이스 질의를 통해 데이터를 찾아 캐시에 저장한 뒤 클라이언트에 반환
다양한 캐시 전략이 존재함. 캐시 할 데이터의 종류, 크기, 액세스 패턴에 맞는 캐시 전략을 선택하면 됨
캐시 사용 시 유의할 점
캐시는 어떤 상황에 바람직한가?
- 데이터 갱신은 자주 일어나지 않지만, 참조는 빈번하게 발생
어떤 데이터를 캐시에 두어야 하는가?
- 캐시는 데이터를 휘발성 메모리에 두기 때문에, 영속적으로 보관할 데이터를 캐시에 두는 것은 바람직하지 않음
캐시에 보관된 데이터는 어떻게 만료(expire)되는가?
- 만료된 데이터는 캐시에서 삭제되어야 한다.
- 만료 정책이 없으면 데이터는 캐시에 계속 남게 됨
- 만료 기한이 너무 짧으면, 데이터베이스를 너무 자주 읽게 될 것이기 때문에 좋지 않음
- 또한, 너무 길어도 원본과 차이가 날 가능성이 높아지기 때문에 문제가 있음
일관성(consistency)은 어떻게 유지되는가?
- 일관성은 데이터 저장소의 원본과 캐시 내의 사본이 같은지 여부임
- 여러 지역에 걸쳐 시스템을 확장해 나가는 경우 캐시와 저장소 사이의 알관성을 유지하는 것은 어려운 문제가 됨
이에 대해서는 페이스북에서 내놓은 논문 <Scaling Memcache at Facebook>[7]을 참고하라고 책에 나와있음
장애에는 어떻게 대처할 것인가?
- 캐시 서버를 한 대만 두는 경우 해당 서버는 `단일 장애 지점(Single Point of Failure, SPOF)`이 되어버릴 가능성이 있음
- 어떤 특정 지점에서의 장애가 전체 시스템의 동작을 중단시켜 버릴 수 있는 경우를 `단일 장애 지점`이라고 부름
- SPOF를 피하려면 여러 지역에 걸쳐 캐시 서버를 분산시켜야 함
캐시 메모리는 얼마나 크게 잡을 것인가?
- 캐시 메모리가 너무 작으면 액세스 패턴에 따라서 데이터가 너무 자주 캐시에서 밀려나버려(eviction) 캐시의 성능이 떨어짐
- 이를 막기 위한 방법으로는 캐시 메모리를 과할당(overprovision)하는 것임
데이터 방출(eviction) 정책은 무엇인가?
- 캐시가 꽉 찼을 때 추가로 캐시에 데이터를 넣어야 할 경우 기존 데이터를 내보내야 함
- 이것을 캐시 데이터 방출 정책이라 함
- 가장 널리 쓰이는 것은 `LRU(Least Recently Used - 마지막으로 사용된 시점이 가장 오래된 데이터를 내보내는 정책)`임
- 다른 정책들
- `LFU(Least Frequently Used - 사용된 빈도가 가장 낮은 데이터를 내보내는 정책)`
- `FIFO(First In First Out - 가장 먼저 캐시에 들어온 데이터를 가장 먼저 내보내는 정책)`
콘텐츠 전송 네트워크(CDN)
CDN은 정적 콘텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크임.
이미지, 비디오, CSS, JavaScript 파일 등을 캐시 할 수 있음.
CDN 사용 시 고려해야 할 사항
- 비용
- CDN은 보통 제3 사업자(third-party providers)에 의해 운영됨
- 자주 사용되지 않는 콘텐츠를 캐싱하는 것은 이득이 크지 않으므로, CDN에서 빼는 것을 고려해야 함
- 적절한 만료 시한 설정
- 시의성이 중요한(time-sensitive) 콘텐츠의 경우 만료 시점을 잘 정해야 함
- 너무 길지도, 짧지도 않아야 함 (앞에 나온 캐시 설명과 동일)
- CDN 장애에 대한 대처 방안
- CDN 자체가 죽었을 경우 웹사이트/애플리케이션이 어떻게 동작해야 하는지 고려해야 함
- 가령 CDN이 응답하지 않을 경우, 해당 문제를 감지하여 원본 서버로부터 직접 콘텐츠를 가져오도록 클라이언트를 구성하는 것이 필요할 수 있음
- 콘텐츠 무효화(invalidation) 방법
- 아직 만료되지 않은 콘텐츠라 하더라도 아래 방법 가운데 하나를 쓰면 CDN을 제거할 수 있음
- CDN 서비스 사업자가 제공하는 API를 이용하여 콘텐츠 무효화
- 콘텐츠의 다른 버전을 서비스하도록 오브젝트 버저닝(object versioning) 이용. 콘텐츠의 새로운 버전을 지정하기 위해서는 URL 마지막에 버전 번호를 인자로 주면 된다. 예를 들어, `image.png?v=2`와 같은 방식이다.
- 아직 만료되지 않은 콘텐츠라 하더라도 아래 방법 가운데 하나를 쓰면 CDN을 제거할 수 있음
그림 1-11은 CDN과 캐시가 추가된 설계이다.
- 정적 콘텐츠(JS, CSS, 이미지 등)는 더 이상 웹 서버를 통해 서비스하지 않으며, CDN을 통해 제공하여 더 나은 성능을 보장
- 캐시가 데이터베이스 부하를 줄여줌
무상태(stateless) 웹 계층
- 웹 계층을 수평적으로 확장하기 위해서, 상태 정보(사용자 세션 데이터와 같은)를 웹 계층에서 제거해야 함
- 바람직한 전략은 상태 정보를 관계형 데이터베이스나 NoSQL 같은 지속성 저장소에 보관하고, 필요할 때 가져오도록 하는 것
- 이렇게 구성된 웹 계층을 무상태 웹 계층이라고 부름
상태 정보 의존적인 아키텍처
- 상태 정보를 보관하는 서버는 클라이언트 정보, 즉 상태를 유지하여 요청들 사이에 공유되도록 함. 무상태 서버에는 이러한 장치가 없음.
- 그림 1-12는 상태 정보 의존적인 아키텍처를 보여준다.
- 이와 같은 아키텍처의 문제는, 같은 클라이언트로부터의 요청은 항상 같은 서버로 전송되어야 한다는 것임.
- 대부분의 로드밸런서가 이를 지원하기 위해 `고정 세션(sticky session)`이라는 기능을 제공하고 있는데, 이는 로드밸런서에 부담을 줌
- 게다가 로드밸런서 뒷단에 서버를 추가하거나 제거하기도 까다로워진다.
무상태 아키텍처
그림 1-13은 무상태 아키텍처를 보여준다.
- 이 구조에서 사용자로부터의 HTTP 요청은 어떤 웹 서버로도 전달될 수 있음
- 웹 서버는 상태 정보가 필요할 경우 공유 저장소(shared storage)로부터 데이터를 가져옴
- 따라서 상태 정보는 웹 서버로부터 물리적으로 분리되어 있음
- 이런 구조는 단순하고, 안정적이며, 규모 확장이 쉽다.
그림 1-14는 무상태 웹 계층을 갖도록 기존 설계를 번경한 결과이다.
- 세션 데이터를 웹 계층에서 분리하고 지속성 데이터 보관소에 저장하도록 만들었음
- 이 공유 저장소는
- 관계형 데이터베이스일 수도 있고,
- Memcachec/Redis 같은 캐시 시스템일 수도 있으며
- NoSQL일 수도 있다.
데이터 센터
- 그림 1-15는 두 개의 데이터 센터를 이용하는 사례다.
- 장애가 없는 상황에서 사용자는 가장 가까운 데이터 센터로 안내됨
- 통상 이러한 절차를 `지리적 라우팅(geoDNS-routing 또는 geo-routing)`이라고 부른다.
메시지 큐
- 메시지 큐는 메시지의 무손실(durability, 메시지 큐에 일단 보관된 메시지는 소비자가 꺼낼 때까지 안전히 보관된다는 특성)을 보장하는, 비동기 통신(asynchronous communication)을 지원하는 컴포넌트다.
- 메시지의 버퍼 역할을 하며, 비동기적으로 전송한다.
- 생산자 또는 발행자(producer/publisher)라고 불리는 입력 서비스가 메시지를 만들어 메시지 큐에 발행(publish)함
- 큐에는 소비자 혹은 구독자(consumer/subscriber)라 불리는 서비스 혹은 서버가 연결되어 있음
- 메시지 큐를 이용하면 서비스 또는 서버 간 결합이 느슨해져서, 규모 확장성이 보장되어야 하는 안정적 애플리케이션을 구성하기 좋음
- 생산자는 소비자 프로세스가 다운되어 있어도 메시지를 발행할 수 있고,
- 소비자는 생산자 서비스가 가용한 상태가 아니어도 메시지를 수신할 수 있다.
로그, 메트릭 그리고 자동화
로그
- 에러 로그를 모니터링하는 것은 중요함
- 로그를 단일 서비스로 모아주는 도구를 활용하면 더 용이
메트릭
메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻을 수도 있고, 시스템의 현재 상태를 손쉽게 파악할 수도 있다.
- 호스트 단위 메트릭 : CPU, 메모리, 디스크 I/O에 관한 메트릭
- 종합(aggregated) 메트릭 : 데이터베이스 계층의 성능, 캐시 계층의 성능 같은 것
- 핵심 비즈니스 메트릭 : 일별 능동 사용자(daily active user), 수익(revenue), 재방문(retention) 같은 것
자동화
- 시스템이 크고 복잡해지면 생산성을 높이기 위해 자동화 도구를 활용해야 함
- 지속정 통합(continuous integration), 빌드, 테스트, 배포 등의 절차 자동화를 통해 개발 생산성을 크게 향상 시킬 수 있음
메시지 큐, 로그, 메트릭, 자동화 등을 반영하여 수정한 설계안
- 메시지 큐는 각 컴포넌트가 보다 느슨히 결합(loosely coupled)될 수 있도록 하고, 결함에 대한 내성을 높인다.
- 로그, 모니터링, 메트릭, 자동화 등을 지원하기 위한 장치를 추가
데이터베이스의 규모 확장
저장할 데이터가 많아지면 데이터베이스에 대한 부하도 증가 -> 데이터베이스를 증설 필요
데이터베이스의 규모를 확장하는 방법 2가지 : `1. 수직적 규모 확장법` `2. 수평적 규모 확장법`
수직적 확장
- 스케일 업이라고도 부름
- 기존에 더 많은, 또는 고성능의 자원(CPU, RAM, 디스크 등)을 증설하는 방법
- 수직적 확장의 몇 가지 심각한 약점
- 데이터베이스 서버 하드웨어에는 한계가 있으므로 무한 증설할 수는 없음
- 사용자가 계속 늘어나면 한 대 서버로는 결국 감당하기 힘듦
- SPOF(Single Poin of Failure)로 인한 위험성이 큼
- 비용이 많이 듦. 고성능 서버일수록 비용 증가
수평적 확장
- 데이터베이스의 수평적 확장은 `샤딩(sharding)`이라고도 부름
- 더 많은 서버를 추가함으로써 성능 향상
- 샤딩은 대규모 데이터베이스를 샤드(shard)라고 부르는 작은 단위로 분할하는 기술을 말함
- 모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없음
- 샤딩 전략을 구현할 때 고려해야 할 가장 중요한 것은 `샤딩 키(sharding key)`를 어떻게 정하느냐임
- 샤딩 키는 파티션 키(partition key)라고도 불림
- 샤딩을 도입하면 시스템이 복잡해지고 풀어야 할 새로운 문제도 생김
- 데이터의 재 샤딩(resharding)
- 유명인사(celebrity) 문제
- 조인과 비정규화(join and de-normalization)
그림 1-20은 수직적 확장법과 수평적 확장법이 어떻게 다른지 보여준다.
그림 1-23은 데이터베이스 샤딩을 적용한 아키텍처이다.
또한, 데이터베이스에 대한 부하를 줄이기 위해 굳이 관계형 데이터베이스가 요구되지 않는 기능들은 NoSQL로 이전하였다.
1장을 마무리하며
시스템 규모 확장을 위해 살펴본 기법들은 아래와 같다.
- 웹 계층은 무상태 계층으로
- 모든 계층에 다중화 도입
- 가능한 한 많은 데이터를 캐시 할 것
- 여러 데이터 센터를 지원할 것
- 정적 콘텐츠는 CDN을 통해 서비스할 것
- 데이터 계층은 샤딩을 통해 그 규모를 확장할 것
- 각 계층은 독립적 서비스로 분할할 것
- 시스템을 지속적으로 모니터링하고, 자동화 도구들을 활용할 것
'IT' 카테고리의 다른 글
[가상면접 사례로 배우는 대규모 시스템 설계 기초] 3장. 시스템 설계 면접 공략법 (0) | 2024.05.25 |
---|---|
[가상면접 사례로 배우는 대규모 시스템 설계 기초] 2장. 개략적인 규모 추정 (0) | 2024.05.18 |
[Pandas] 4. 결측치 처리 (0) | 2024.03.27 |
[Pandas] 3. DataFrame 조회(필터링), 변경 (0) | 2024.03.27 |
[Pandas] 2. DataFrame 결합과 정렬 (0) | 2024.03.27 |