고려사항
- 가용성, Availability: 서비스의 장애를 최소화하고, 장애가 생기더라도 빠른 복구 혹은 일부만으로 동작할 수 있는지.
- 확장성, Scalability: 기존 상황에서 부하가 늘었을 때 얼마나 쉽게 얼마나 많은 양을 처리할 수 있는지. 트래픽이나 트랜잭션이 늘었을 때, 혹은 저장공간을 추가해야할 때.
- 신뢰성, Reliability: 똑같은 요청에는 똑같은 결과가 나오도록, 데이터의 동기화 측면에서 중요하다.
- 관리성, Manageability: 시스템의 관리와 운용이 쉬운지. 유지와 업데이트가 편리하며, 문제 발생시 분석이 용이해야 한다.
- 성능, Performance: 빠른 응답 시간(낮은 지연시간 latency).
- 비용, Cost: 하드웨어/소프트웨어 비용을 포함해 시스템을 배포하고 관리하는 비용(시간, 노력 등)
Scale-up & Scale-out
짧은 시간 동안 요청되는 API 가 많을수록 서비스의 규모가 크다고 한다. 규모가 커지면서 서버 1대로 처리할 수 없는 부하가 생길 때 문제가 생기는데, 부하에도 장애가 생기지 않고 성능을 유지하는 방법, 즉 자원을 추가하는 Scalability 를 고려해야한다. 단순히는 서버의 성능을 높이는 Scale-up 을 생각할 수 있다. 이는 서버 환경을 새로 설정할 필요가 없어 편리하지만, 고성능일 수록 비용이 올라가고, 하드웨어에 확장할 수 있는 성능도 한계가 있기 때문에 완벽한 해결방안이 되지 못한다. 한편 서버의 개수를 늘리는 Scale-out 은 낮은 사양의 서버를 사용해도 되기에 저렴하고, 필요에 따라 서버를 증설/제거하는 것이 유연하다. 또한 한 서버에 장애가 생겨도 나머지가 작업을 처리하기 때문에 대처가 가능하다. 그러나 이와 같은 분산처리를 위해 사용자 요청을 어떤 서버에서 처리할지 지정해주는 로드 밸런싱이 필요하고, 분산된 DB 에 있는 데이터를 동기화하는 문제 등 새로이 생각할 부분이 생긴다.
Load Balancing
서버가 여러 대가 있다고 하더라도, 한 서버에 트래픽이 몰리는 것을 막기 위해, 그때그때 어떤 서버에 요청을 보낼지를 처리해 로드 밸런서가 필요하다. 요청을 서버에 균등하게 할당하기 위해 주로 라운드 로빈 방법(Round Robin Method)을 사용하는데, 여러 대의 서버에 돌아가면서 요청을 할당하는 방법이다. 이는 세션이 오래 지속되지 않은 경우에 활용하기 적합하고, 만약 세션이 길어질 경우 최소 연결 방법(Least Connection Method)으로 연결이 적은 서버에 할당하거나, 클라이언트의 IP 를 특정 서버로 매핑하는 IP 해시 방법(IP Hash Method)을 사용한다.
로드 밸런싱은 OSI 7계층 중 어디에서 이뤄지는지에 따라 종류가 나뉜다. L2 는 Mac 주소를 바탕으로, L3 는 IP 주소, L4 는 port, L7 은 url 에 따라 서버가 분산된다. L4 로드밸런서는 네트워크계층에서 IP 주소와 전송계층에서 TCP 포트 정보 등에 따라 트래픽을 분산한다. 패킷 레벨에서만 분산하기 때문에 속도가 빠르고 저렴한 장점이 있다. AWS 의 Network Load Balancer 의 경우 DNS 서버에서 로드밸런서 노드의 IP 주소를 반환하고, 클라이언트가 로드밸런서로 요청을 보내면 프라이빗 IP 주소를 사용하여 해당 대상으로 요청을 전송한다. L7 로드밸런서는 응용계층에서 URL 이나 HTTP 헤더를 사용해 부하를 분산한다. 헤더값을 사용하기 때문에 Dos/DDoS 와 같은 비정상적 트래픽을 필터링할 수 있다. AWS 의 Application Load Balancer 나 Classic Load Balancer 로 사용 가능하다.
장애 대비로는, 로드밸런서를 Active 와 Passive 으로 이중화하고, Health Check 를 하다가 Active 밸런서가 동작하지 않으면 여분의 로드밸런서로 운영하는 방식을 택한다.
DB분산처리
부하는 CPU 와 I/O 에서 일어나는 것으로 종류를 나눌 수 있다. 웹 서비스에서는 CPU 의 연산(http 요청/응답) 자체가 오래걸리기보다 대량의 데이터 내에서 검색을 하는 등 DB 에 접근하여 속도가 느려지는 경우가 많다. 이때 DB 에 있던 데이터는 메모리에 올라가는데, 데이터가 많아질수록 메모리의 용량을 초과하게 되고, 메모리가 아닌 디스크를 사용하게 되어 I/O 가 느려진다. 따라서 CPU 사용률이 높은 AP(Application) 부분과 I/O 대기율이 높은 DB 부분을 분리하여 서버를 구성하고, I/O 부하가 높은 서버는 메모리를 중요시하도록 하드웨어를 구성한다.
AP 서버는 데이터를 갖고 있는 것이 아니므로 서버의 개수를 늘리는 scale-out 으로 확장할 수 있다. 하지만 DB 서버는 한 서버의 메모리에 올릴 수 있는 데이터의 양이 많아질 때, 단순히 개수를 늘리는 것으로 성능 문제를 해결하려고 한다면 문제가 생긴다. 한 DB 에 데이터가 추가 될때 다른 DB 에도 추가되어야하기에 결국 원점으로 돌아간다. 또 분산된 서버에 있는 메모리에 동일한 데이터가 캐싱된다면 분산의 의미가 없어지기 때문에 특정 기준을 통해 서버마다 메모리에 다른 데이터를 캐싱하도록 한다.
우선 데이터의 동기화를 위해 Primary/Replica(primary/secondary, master/slave) 구조를 갖도록 시스템을 설계한다. 데이터를 읽는 쿼리는 replica 에서, 갱신하는 쿼리는 primary 에서 처리한다. replica 에서는 매번 데이터를 갱신하는 것이 아니라, primary 에서 갱신된 정보들을 기록한 로그를 받아 한번에 업데이트를 진행하여 동기화를 한다. replica 는 부하가 늘어날 수록 얼마든지 복제할 수 있어 대부분의 웹 서비스의 병목현상을 해결한다. 하지만 primary 는 확장을 할 수 없기 때문에, 갱신이 빈번한 서비스를 개발할 경우는 RDB 가 아닌 다른 데이터베이스를 사용하는 방안이 있다.
한편 대량의 데이터를 나누어 접근하는 기술로는 파티셔닝과 샤딩이 있다. 파티셔닝은 하나의 DB 서버 내에서, 샤딩은 여러 서버에 분산저장하는 방법이다. 파티셔닝은 인덱스를 관리하기 쉬운 크기로 만들기 위해 테이블을 분리하는 방법이다. 쿼리의 성능을 향상시키고, 트랜잭션이 일어날 경우 동시성을 향상할 수 있다. 테이블을 분리하는 기준에는 range partitioning(연속적인 숫자 기준), hash partitioning(key 의 해시값 기준) 등이 있다. 또한 서비스 요구사항에 따라 테이블 컬럼을 기준으로 나누거나(자주사용하는 컬럼 분리) 레코드 개수를 기준으로 나눌 수 있다. 레코드 기준으로 분리하는 수평 파티셔닝(horizontal partitioning) 의 방법은 샤딩과 동일하다. 하지만 샤딩은 분리된 테이블들을 다른 서버에 저장하기 때문에 성능과 확장성의 이점을 가져오는 반면, 데이터 동기화 문제가 발생할 수 있다.