사용자 수에 따른 규모 확장성 - thought-corner/Backend-PlayGround GitHub Wiki
- 관계형 데이터베이스는 관계형 데이터베이스 관리 시스템(RDBMS)이라고 부른다.
- 관계형 데이터베이스는 자료를 테이블과 열, 칼럼으로 표현한다.
- SQL을 사용하면 여러 테이블에 있는 데이터를 그 관계에 따라 조인(JOIN)하여 합칠 수 있다.
- 비관계형 데이터베이스는 NoSQL이라고 부른다.
- NoSQL은 키-값 저장소(Key-Value Store), 그래프 저장소(Graph Store), 칼럼 저장소(Column Store), 문서 저장소(Document Store)로 분류할 수 있다.
- 이런 비관계형 데이터베이스는 일반적으로 조인 연산은 지원하지 않는다.
- 아래와 같은 경우에는 비관계형 데이터베이스가 바람직한 선택일 수 있다.
비관계형 데이터베이스가 바람직한 선택인 경우
- 아주 낮은 응답 지연시간이 요구된다.
- 다루는 데이터가 비정형(unstructured)이라 관계형 데이터가 아닌 경우
- 데이터(JSON, YAML, XML 등)를 직렬화하거나 역직렬화할 수 있기만 하면 됨
- 아주 많은 양의 데이터를 저장할 필요가 있다.
- 소위 '스케일 업(scale up)'이라고 하는 수직적 규모 확장은 서버에 고사양 자원(좋은 CPU, 더 많은 RAM)을 추가하는 행위를 말한다.
- 소위 '스케일 아웃(scale out)'이라고 하는 수평적 규모 확장은 더 많은 서버를 추가하여 성능을 개선하는 행위를 말한다.
- 수직적 규모 확장에는 한계가 있다. 한 대의 서버에 CPU나 메모리를 무한정 증설할 수 없다. 또한 장애에 대한 자동복구(failover) 방안이나 다중화(redundancy) 방안을 제시하진 않는다. 서버에 장애가 발생하면 웹/앱은 완전히 중단된다.
- 로드밸런서는 부하 분산 집합(Load Balancing Set)에 속한 웹 서버들에게 트래픽 부하를 고르게 분산하는 역할을 수행한다.
- 사용자는 로드밸런서의 공개 IP 주소로 접속을 하고 웹 서버는 클라이언트의 접속을 직접 처리하지 않는다.
- 서버 1이 다운되면 모든 트래픽은 서버 2로 전송된다. 따라서 웹 사이트 전체가 다운되는 일이 방지된다. 부하를 나누기 위해 새로운 서버를 추가할 수도 있다.
- 웹 사이트로 유입되는 트래픽이 가파르게 증가하면 두 대의 서버로 트래픽을 감당할 수 없는 시점이 오는데 로드밸런서가 있으므로 우아하게 대처할 수 있다. 웹 서버 계층에 더 많은 서버를 추가하기만 하면 된다. 그러면 로드밸런서가 자동적으로 트래픽을 분산하기 시작할 것이다.
- 이 로드밸런서는 '웹 계층'에서의 부하 분산을 위한 개념이다.
- 쓰기 연산(Write Operations)은 마스터에서만 지원한다. 부 데이터베이스는 주 데이터베이스로부터 그 사본을 전달받으며, 읽기 연산(Read Operations)만을 지원한다.
- 데이터베이스를 변경하는 명령어들, 가령 INSERT, DELETE, UPDATE 등은 주 데이터베이스로만 전달되어야 한다.
- 대부분의 애플리케이션은 읽기 연산 비중이 쓰기 연산보다 훨씬 높다. 그렇기에 부 데이터베이스의 수가 주 데이터베이스 수보다 많다.
- 더 나은 성능 : 주-부 다중화 모델에서 모든 데이터 변경 연산은 주 데이터베이스 서버로만 전달되는 반면 읽기 연산은 부 데이터베이스 서버들로 분산된다. 병렬로 처리될 수 있는 질의 수가 늘어나므로 성능이 좋아진다.
- 안정성 : 재해 등의 이유로 데이터베이스 서버 가운데 일부가 파괴되어도 데이터는 보존될 것이다. 데이터를 지역적으로 떨어진 여러 장소에 다중화시켜 놓을 수 있기 때문이다.
- 가용성 : 데이터를 여러 지역에 복제해 둠으로써, 하나의 데이터베이스 서버에 장애가 발생하더라도 다른 서버에 있는 데이터를 가져와 계속 서비스를 이어갈 수 있다.
- 사용자는 DNS로부터 로드밸런서의 공개 IP 주소를 받는다.
- 사용자는 해당 IP 주소를 사용해 로드밸런서에 접속한다.
- HTTP 요청은 웹 계층의 여러 서버 중 하나에 전달된다.
- 웹 서버는 사용자의 데이터를 부 데이터베이스 서버에서 읽는다.
- 웹 서버는 데이터 변경 연산은 주 데이터베이스로 전달한다. 데이터 추가, 삭제, 갱신 연산 등이 이에 해당한다.
- 캐시는 값비싼 연산 결과 또는 자주 참조되는 데이터를 메모리 안에 두고, 뒤이은 요청이 보다 빨리 처리될 수 있도록 하는 저장소이다.
- 웹 페이지를 새로고침 할 때마다 표시할 데이터를 가져오기 위해 한 번 이상의 데이터베이스 호출이 발생한다.
- 애플리케이션의 성능은 데이터베이스를 얼마나 자주 호출하느냐에 크게 좌우되는데 캐시는 그런 문제를 완화할 수 있다.
- 캐시 계층(Cache Tier)은 데이터가 잠시 보관되는 곳으로 데이터베이스보다 훨씬 빠르다.
- 별도의 캐시 계층을 두면 성능이 개선될 뿐만 아니라 데이터베이스의 부하를 줄일 수 있고, 캐시 계층 규모를 독립적으로 확장시키는 것도 가능해진다.
- 요청을 받은 웹 서버는 캐시에 응답이 저장되어 있는지를 본다.
- 만일 저장되어 있다면 해당 데이터를 클라이언트에 반환한다. 없는 경우에는 데이터베이스 질의를 통해 데이터를 찾아 캐시에 저장한 뒤 클라이언트에 반환한다.
캐시는 어떤 상황에서 사용하는 것이 좋을까?
- 데이터 변경은 드물고 조회가 매우 빈번한 경우
- 동일한 요청이 반복적으로 발생하는 경우
- 응답 속도가 중요한 API인 경우
어떤 데이터를 캐시에 두어야 하는가?
- 캐시는 데이터를 휘발성 메모리에 두므로, 영속적으로 보관할 데이터를 캐시에 두는 것은 바람직하지 않다.
- 중요 데이터는 여전히 지속적 저장소(persistent data store)에 두어야 한다.
캐시에 보관된 데이터는 어떻게 만료되는가?
- 만료된 데이터는 캐시에서 삭제되어야 한다.
- 만료 정책이 없으면 데이터는 계속 캐시에 남게 된다.
- 만료 기한은 너무 짧으면 곤란한데, 데이터베이스를 너무 자주 읽게 될 것이기 때문이다.
- 너무 길어도 곤란한데, 원본과 차이가 날 가능성이 높아지기 때문이다.
일관성(consistency)은 어떻게 유지되는가?
- 일관성은 데이터 저장소의 원본과 캐시 내의 사본이 같은지 여부이다.
- 저장소의 원본을 갱신하는 연산과 캐시를 갱신하는 연산이 단일 트랜잭션으로 처리되지 않은 경우 이 일관성은 깨질 수 있다.
장애에는 어떻게 대처할 것인가?
- 캐시 서버를 한 대만 두는 경우 해당 서버는 단일 장애 지점(SPOF, Single Point of Failure)이 되어버릴 가능성이 있다.
- 결과적으로 SPOF를 피하려면 여러 지역에 걸쳐 캐시 서버를 분산시켜야 한다.
캐시 메모리는 얼마나 크게 잡을 것인가?
- 캐시 메모리가 너무 작으면 액세스 패턴에 따라서 데이터가 너무 자주 캐시에서 밀려나버려 캐시 성능이 저하된다.
- 이를 막을 한 가지 방법은 캐시 메모리를 과할당하는 것이다. 이렇게 하면 캐시에 보관될 데이터가 갑자기 늘어났을 때 생길 문제도 방지할 수 있다.
데이터 방출(eviction) 정책은 무엇인가?
- 캐시가 꽉 차버리면 추가로 캐시에 데이터를 넣어야 할 경우 기존 데이터를 내보내야 한다.
- 이것을 캐시 데이터 방출 정책이라 하는데, 그 가운데 가장 널리 쓰이는 것이 바로 LRU(Least Recently Used)로 마지막으로 사용된 시점이 가장 오래된 데이터를 내보내는 것이다.
- 다른 정책으로는 LFU(Least Frequently Used)로 사용된 빈도가 가장 낮은 데이터를 내보내는 정책, FIFO(First In First Out)로 가장 먼저 캐시에 들어온 데이터를 가장 먼저 내보내는 정책이 있으며 경우에 맞게 적용 가능하다.
- CDN은 정적 컨텐츠를 전송하는 데 쓰이는, 지리적으로 분산된 서버의 네트워크이다.
- 이미지, 비디오, CSS, Javascript 파일 등을 캐시할 수 있다.
- 동작 방식
- 사용자 A가 이미지 URL을 이용해 image.png 파일에 접근한다. URL의 도메인은 CDN 서비스 사업자가 제공한다.
- CDN 서버 캐시에 해당 이미지가 없는 경우, 서버는 원본(Origin) 서버에 요청하여 파일을 가져온다. 원본 서버는 웹 서버일 수도 있고 Amazon S3와 같은 온라인 저장소일 수도 있다.
- 원본 서버가 파일을 CDN 서버에 반환한다. 응답의 HTTP 헤더에는 해당 파일이 얼마나 오래 캐시될 수 있는지를 설명하는 TTL 값이 들어있다.
- CDN 서버는 파일을 캐시하고 사용자 A에게 반환한다. 이미지는 TTL에 명시된 시간이 끝날 때까지 캐시된다.
- 사용자 B가 같은 이미지에 대한 요청을 CDN 서버에 전송한다.
- 만료되지 않은 이미지에 대한 요청은 캐시를 통해 처리된다.
비용
- CDN은 보통 제 3 사업자에 의해 운영되며, 여러분은 CDN으로 들어가고 나가는 데이터 전송 양에 따라 요금을 내게 된다.
- 자주 사용되지 않는 콘텐츠를 캐싱하는 것은 이득이 크지 않으므로, CDN에서 빼는 것을 고려한다.
적절한 만료 시한 설정
- 시의성이 중요한 컨텐츠의 경우 만료 시점을 잘 정해야 한다.
- 너무 길지도 않고 짧지도 않아야 하는데, 너무 길면 컨텐츠의 신선도는 떨어질 것이고, 너무 짧으면 원본 서버에 빈번히 접속하게 되어서 좋지 않다.
CDN 장애에 대한 대처 방안
- CDN 자체가 죽었을 경우 웹사이트/애플리케이션이 어떻게 동작하는지 고려해야 한다.
- 가령 일시적으로 CDN이 응답하지 않을 경우, 해당 문제를 감지하여 원본 서버로부터 직접 컨텐츠를 가져오도록 클라이언트를 구성하는 것이 필요할 수도 있다.
- 컨텐츠 무효화(Invalidation) 방법 : 아직 만료되지 않은 컨텐츠라 하더라도 아래 방법 가운데 하나를 사용하면 CDN에서 제거할 수 있다.
- CDN 서비스 사업자가 제공하는 API를 이용하여 컨텐츠 무효화
- 컨텐츠의 다른 버전을 서비스하도록 오브젝트 비저닝을 이용. 컨텐츠의 새로운 버전을 지정하기 위해서 URL 마지막에 버전 번호를 인자로 주면 된다.
- 예를 들어, image.png?v=2이다.
- 메시지 큐는 메시지의 무손실(durability, 즉 메시지 큐에 일단 보관된 메시지는 소비자가 꺼낼 때까지 안전하게 보관된다는 특성)을 보장하는 비동기 통신을 지원하는 컴포넌트이다.
- 메시지의 버퍼 역할을 하며 비동기적으로 전송한다.
- 생산자 또는 발행자라고 불리는 입력 서비스가 메시지를 만들어 메시지 큐에 발행한다.
- 큐에는 보통 소비자 혹은 구독자라고 불리는 서비스 혹은 서버가 연결되어 있는데, 메시지를 받아 그에 맞는 동작을 수행한다.
- 로그 : 에러 로그를 모니터링하는 것은 중요하다. 시스템의 오류와 문제들을 보다 쉽게 찾아낼 수 있도록 하기 때문이다.
- 메트릭 : 메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻을 수도 있고, 시스템의 현재 상태를 손쉽게 파악할 수도 있다.
- 자동화 : 시스템이 크고 복잡해지면 생산성을 높이기 위해 자동화 도구를 활용해야 한다.
수직적 확장
- 'Scale Up'이라고도 부르는 수직적 규모 확장법은 기존 서버에 더 많은, 또는 고성능의 자원(CPU, RAM, 디스크 등)을 증설하는 방법이다.
- 이런 수직적 접근법은 몇 가지 심각한 약점이 있다.
- 데이터베이스 서버 하드웨어에는 한계가 있으므로, CPU, RAM 등을 무한 증설할 수는 없다. 사용자가 계속 늘어나면 한 대 서버로는 결국 감당하기 어렵다.
- SPOF(Single Point of Failure)로 인한 위험성이 크다.
- 비용이 많이 든다. 고성능 서버로 갈수록 가격이 올라가기 마련이다.
수평적 확장
- 수평적 확장은 샤딩(Sharding)이라고도 부르는데, 더 많은 서버를 추가함으로써 성능을 향상시킬 수 있도록 한다.
- 샤딩은 대규모 데이터베이스를 샤드라고 부르는 작은 단위로 분할하는 기술을 일컫는다.
- 모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터 사이에는 중복이 없다.
- 샤딩 전략을 구현할 때 고려해야 할 가장 중요한 것은 샤딩 키(Sharding Key)를 어떻게 정하느냐 하는 것이다.
- 샤딩 키는 파티션 키(Partition Key)라고도 부르는데, 데이터가 어떻게 분산될지 정하는 하나 이상의 칼럼으로 구성된다.
- 샤딩 키를 통해 올바른 데이터베이스에 질의를 보내어 데이터 조회나 변경을 처리하므로 효율을 높일 수 있다.
- 샤딩 키를 정할 때는 데이터를 고르게 분할할 수 있도록 하는게 가장 중요하다.
샤딩을 도입하면 시스템이 복잡해지고 풀어야 할 새로운 문제들
- 데이터의 재샤딩(Resharding) : 재샤딩은 다음과 같은 경우에 필요하다.
- 데이터가 너무 많아져서 하나의 샤드로는 더 이상 감당하기 어려울 때
- 샤드 간 데이터 분포가 균등하지 못하여 어떤 샤드에 할당된 공간 소모가 다른 샤드에 비해 빨리 진행될 때, 즉 샤드 소진(Shard Exhaustion)이라고 부르는 이런 현상이 발생하면 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치하여야 한다.
- 유명인사(Celebrity) 문제
- 핫스팟 키(Hotspot Key) 문제라고도 부르는데, 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 문제이다.
- 조인과 비정규화(Join and DeNormalization)
- 하나의 데이터베이스를 여러 샤드 서버로 쪼개고 나면, 여러 샤드에 걸친 데이터를 조인하기가 힘들어진다.
- 이를 해결하는 한 가지 방법은 데이터베이스를 비정규화하여 하나의 테이블에서 질의가 수행될 수 있도록 하는 것이다.