개발 일지/대규모 시스템 설계

[대규모 시스템 설계] 1-3. 아키텍처 설계

배발자 2023. 7. 21.
반응형

무상태 (stateless) 웹 계층

오늘의 포스팅은 무상태 (stateless) 웹 계층을 시작으로 천천히 알아보자. 이전 포스팅에서 생각보다 많은 것을 배워봤다. CDN을 통해 정적 컨텐츠를 캐시하고 로드밸런서를 통해 웹 서버의 분산, 데이터베이스의 다중화 및 캐시 활용 등을 배웠다. 
 
이제 웹 계층을 수평적으로 확장하는 방법을 배워볼 순서인데, 사용자 세션 데이터와 같은 상태정보를 웹 계층에서 제거해야한다. 예를들면, 상태 정보를 관계형 데이터베이스나 NoSQL 같은 지속성 저장소에 보관하여 필요시 가져오게 하는 방법으로 말이다. 이렇게 상태정보를 웹 계층에서 제거하여 설계한 웹 계층을 무상태 웹 계층이라고 부른다. 
 

1. 상태 정보 의존적인 아키텍처 

 
무상태 웹 계층을 배우기전에 상태를 저장하는 웹 계층은 무엇인가? 
쉽게 설명하면 상태 정보를 보관하는 서버는 클라이언트 정보, 즉 상태를 유지하여 요청들 사이에 공유되도록 하는 것이다. 다음 그림을 보고 이해해보자. 
 

 
각 서버마다 사용자의 정보를 저장한다. 예를 들면, 사용자 A는 서버 1로, 사용자 B는 서버 2로 HTTP 요청을 보내게 된다. 만약 사용자 A가 서버 2로 HTTP 요청을 보낸다면 인증은 실패할 것이다. 
여기서 문제는 같은 클라이언트로부터의 요청은 항상 같은 서버로 전송한다는 점이다. 이전 포스팅에서 '로드밸런서'의 역할을 배웠었는데 로드밸런서는 고정 세션(sticky session)이라는 기능을 제공한다. 하지만 이는 로드밸런서에게 부담을 준다는 것이다. 
 

2. 무상태 아키텍처

 

 
위의 그림을 보면 상태 정보 의존적인 아키텍처와 다를 것이다. 이 구조에서는 각 사용자마다 정해진 고정된 웹 서버는 없다. 사용자의 요청들이 웹 서버에 도달하면 상태 정보가 필요할 경우 관계형 데이터베이스나 NoSQL 같은 지속성 저장소에 보관하여 필요시 가져오게 하면 된다. 물론, Memcached/Redis 같은 캐시 시스템일 수 있다. 따라서 상태 정보는 웹 서버로부터 물리적으로 분리하여 단순하고, 안정적인 규모 확장을 할 수 있다. 
 

 
 
위의 그림처럼 아키텍처 설계가 완료된다면, 한 번 상상해보자. 
사용자들의 상태 정보를 웹 계층에 저장되어있지 않기때문에 고정된 특정 서버로 요청을 보낼 필요가 없다. 즉, 서버 1, 서버 2, ..... 등 로드밸런서가 알아서 매칭시키는 것이다. 
만약, 트래픽 양이 일정 한도를 넘어선다면, 웹 서버를 자동으로 추가하기만 된다. 그게 쉽냐고? 상태 정보가 웹 계층엔 저장되어있지 않잖아~ 그냥 공유 저장소가서 찾아가면 끝인데 뭘! 
위의 그림에선 NoSQL로 상태 정보를 공유하고 있다. 
 
 

메시지 큐 

 큐는 큐인데 메시지 큐는 무엇인가? 
BFS 알고리즘을 할 때 가장 많이 쓰이는 자료구조 큐를 떠올릴 수 있는데, 비슷하다. 
필자는 프로젝트를 진행하면서 메시지큐의 일종인 카프카라는 기술을 사용해본 경험이 있다. 정말 간단하게 설명하자면, 큐라는 통로에 생산자들이 사과, 배, 키위 라는 품목으로 차례대로 넣는다고 치자. 그러면 사과 작업 서비스 A, 배 작업 서비스 B, 키위 작업 서비스 C 가 큐에 담긴 내용물이 차례대로 들어오면 작업 서비스에 맞는 품목들만 하나씩 들고와서 사악~ 사악~ 하는 것이다. 
 
메시지 큐는 메시지의 무손실(Durability, 메시지 큐에 일단 보관된 메시지는 소비자가 꺼낼 때까지 안전히 보관된다는 특성)을 보장하는, 비동기 통신을 지원하는 컴포넌트이다. 메시지 큐에서 기본적으로 생산하는 놈을 생산자 또는 발행자(producer/publisher)라고 불리는 입력 서비스가 메시지를 만들어 메시지 큐에 발행한다. 그러면, 이 메시지를 받는 놈들이 있는데, 그 친구들을 소비자 혹은 구독자 (consumer/subscriber) 불리는 서비스 혹은 서버가 연결되어있는데, 메시지를 받아 그에 맞는 동작을 수행하는 역할을 한다. 
 

 
MSA 구조에서는 여러 서비스마다 서버를 나누어서 실행하게 된다. 하지만 서비스마다 무엇인가 추가적으로 작업을 해야하는 내용들을 동기적으로 통신하게 되면 자원을 많이 소모하게 될거다. 
사진 보정 애플리케이션을 예로 들어보겠다. 사진 보정을 하는 작업은 시간이 꽤나 걸리는 프로세스이다. 웹 서버는 사진 보정 작업을 메시지 큐에 넣는다. 그리고 사진 보정 작업만을 하는 또 다른 서버 프로세스가 '오! 작업들어왔다' 외치면서 비동기적으로 완료하면 된다. 
 
이렇게 하면 서버와 서버 사이에 메시지 큐라는 미들웨어를 두어 서버 간의 결합을 느슨하게 하고, 규모의 확장성을 높일 수있다는 것이다. 만약, 큐의 크기가 커지면 더 많은 작업 프로세스를 추가하면되고, 큐가 거의 항상 비어있는 상태라면? 작업 프로세스의 수를 줄이면 되는 것이다. 즉, 자동 규모 확장(autoscaling)하면 된다. 
 
 

로그, 메트릭, 자동화 

 사용자 관점에서 눈에 보이는 기능만 구현하는 것도 중요하지만 백엔드 개발자는 뒷단에 숨겨진 무언가를 고민하고 새로운 것에 도전하는 것도 중요하다. 사실, 프로젝트 기간이 워낙 짧게 진행하다보니, 파고들어가는 것도 한계치가 있다. 하지만, 기본적인 API 구현 뿐만이 아니라 '개발자는 로그로 대화한다', '성능 측정', '속도 향상', '모니터링' 등 한 번 도전해보는 것도 좋을 것이다. 필자는 과거로 돌아간다면, 메트릭에 대해서 더 학습하여 적용해봤을 거 같다. 
 
로그
보통 스프링을 많이들 다루실텐데 많이 아실거다. slf4j 어노테이션을 달고 로깅 레벨 설정을 통해 비교적 log.info를 정말 많이 사용했던 기억이 난다. 사용자들은 해당 로그들을 볼 일이 없을거다. 대신 개발자들이 이 로그를 분석해야한다. 즉, 에러 로그를 모니터링 하는 것이 매우 중요하다는 뜻인데, 시스템의 오류와 문제들을 보다 쉽게 찾아낼 수 있도록 환경을 구축해야하는 것이다. 서버 단위로 모니터링 할 수 있지만, 로그를 단일 서비스로 모아주는 도구를 활용할 수 있다. 필자는 마지막으로 진행했던 프로젝트에서 ELK-Stack을 접목한 로깅 시스템을 구축한 경험이 있다. 궁금한 분들은 한 번 검색해보자.
 
메트릭
메트릭을 잘 수집하면 사업 현황에 관한 유용한 정보를 얻을 수도 있고, 시스템의 현재 상태를 손쉽게 파악할 수 있다.
호스트 단위  : CPU, 메모리, 디스크 I/O 
종합 메트릭 : 데이터베이스 계층의 성능, 캐시 계층의 성능
핵심 비즈니스 메트릭 : 일별 능동 사용자, 수익, 재방문
 
자동화
시스템이 점점 복잡해지면 자동화 도구를 활용하여 생산성을 높여야한다. CI/CD 활용 도구로 젠킨스를 많이 활용하는 것으로 알고 있다. 개발자가 만드는 코드가 어떤 검증 절차를 자동으로 거치도록 할 수 있고 빌드, 테스트, 배포 등의 절차를 자동화하여 개발 생산성을 향상 시키는 것이다.
 

으어.. 만들기 너무 힘들다.............................

 
 
 

데이터베이스의 규모확장 

 데이터가 점점 더 많아지고하다보면 데이터 베이스에 대한 부하도 증가하게 된다. 그렇게 된다면, 데이터베이스 또한 증설할 방법을 찾아야하는데, 두 가지 접근법이 있다. 이미 언급했던 적이 있다. 수직적 규모 확장과 수평적 규모 확장이다. 
 

1. 수직적 확장

기존 서버에 더 많은 고성능 자원(CPU, RAM, 디스크 등)을 증설하는 방법이다. 이전에 단점으로 하드웨어의 한계, SPOF(Single Point Of Failure:단일 장애점)로 인한 위험성, 비용 문제 등을 설명한 바 있다. 
 

2. 수평적 확장

수평적 확장은 샤딩이라고도 부르는데, 더 많은 서버를 추가하면서 성능을 향상 시키는 방법이다. 샤딩은 데이터베이스를 샤드라고 부르는 작은 단위로 분할한다. 특징은 모든 샤드는 같은 스키마를 쓰지만 샤드에 보관되는 데이터는 중복이 없다는 것이다. 예를들면, user_id % 3 으로 해시 함수로 사용하면 데이터가 보관된 샤드가 정해진다. 
 

 
샤딩 전략은 샤딩 키(파티션 키)가 매우 중요한데, 데이터가 어떻게 분산될 지 하나 이상의 칼럼으로 구성된다. 위의 그림은 샤딩 키를 user_id로 정한 것으로 추측할 수 있다. 그러니까 샤딩 키를 정할 때는 데이터가 특정 서버에만 몰리지 않도록 고르게 분할 할 수도 있도록 하는 것이 핵심이다. 
 
하지만 샤딩을 도입하면 시스템이 복잡해지고 다음과 같은 새로운 문제도 생긴다. 
 
데이터의 재 샤딩
데이터가 너무 많아져서 하나의 샤드로는 더 이상 감당하기 어려울 때나 샤드 간 데이터 분포가 균등하지 못하여 특정 샤드에서 공간 소모가 다른 샤드에 비해 빠를 때이다. 
이런 경우를 샤드 소진이라고 불리는데 이때 샤드 키를 계산하는 함수를 변경하고 데이터를 재배치해야한다. 추후 안정 해시 기법을 포스팅할텐데 이 기법을 활용하면 이 문제를 해결할 수 있다. 
 
유명인사 문제
핫스팟 키 문제라고도 불리는데, 특정 샤드에 질의가 집중되어 서버에 과부하가 걸리는 것이다. 
아이유, 개발자 배씨 등과 같은 유명 인사들이 특정 샤드에 저장된 데이터베이스가 있다면 사회 관계망 애플리케이션 같은 경우 해당 샤드에 과도한 read 연산 때문에 과부하가 걸릴 수도 있다. 이럴 때는 유명인사들을 샤드 하나씩 균등하게 할당해야할 것이다. 
 
조인과 비정규화
여러 샤드로 나누게 되면, 데이터를 조인하기 힘들어진다. 이를 해결하는 방법은 데이터베이스를 비정규화하여 하나의 테이블엥서 질의가 수행될 수 있도록 한다. 
 
대규모 시스템을 개발하기 위해 지금까지 아키텍처 설계에 대해서 하나하나 깊게 살펴보았다. 
 

- 웹 계층은 무상태 계층으로
- 모든 계층에 다중화 도입
가능한 많은 데이터를 캐시할 것
정적 콘텐츠는 CDN을 통해 서비스할 것
데이터 계층은 샤딩을 통해 그 규모를 확장할 것
각 계층은 독립적 서비스로 분할할 것
시스템을 지속적으로 모니터링하고, 자동화 도구들을 활용할 것

 
다음 포스팅은 처리율 제한 장치에 대해서 어떻게 설계를 해야하는지 살펴볼 예정이다. 

 

 
 
 
 

반응형

댓글