brunch

You can make anything
by writing

C.S.Lewis

by myner Dec 09. 2021

트래픽이 증가할 때 ...

feat. 제발~~ 그만해~~ 이러다 다 죽어~~!!!

필자는 트래픽이 급증하는 케이스를 크게 두 가지로 분리할 수 있다고 본다.


1. 정해진 날짜 없이 갑작스러운 이벤트 발생

  - ex: 긴급 뉴스, 재해재난 상황, 올림픽 경기중 특정 이벤트 발생 시

2. 정해진 날짜의 예견된 트래픽 증가

  - ex: 마케팅 이벤트, 새해맞이 메신저, 복날의 배달, 올림픽 경기


우리는 어떻게 대응해야 할까? 가 이번 글의 주제이다



갑작스러운 이벤트 발생으로 발생하는 트래픽 증가



갑작스러운 이벤트는 순간적으로 엄청난 트래픽이 발생하게 되고 평소 트래픽의 몇 배가 뛸지도 모르는 상황이 된다. 예를들면... 월드컵이나 올림픽 같은 경우 이미 예견된 트래픽 증가가 있는 상황에서 주요 순간 때마다 트래픽이 더 오를 수 있다. 트래픽이 많은 서비스일수록 트래픽의 변동 폭이 크게 나타날 수 있는데, 이럴 때 여러 모니터링 시스템과 분산처리를 통해 이를 극복해 내야 한다.


분산처리를 어떻게 할까?

이렇게 Load Balancer를 이용하며 분산처리를 해서 FailOver를 하도록 구성할 수 있다. 그런데 이러한 각 구성 요소에 문제가 생긴다면 어떻게 할까? LB의 경우 LB를 미리 다중화해서 대응이 가능할 것이다.


하지만 어플리케이션 서버에서 문제가 생기면 어떻게 할까? 우선 어플리케이션 서버에 문제가 생기면 아래와 같은 상황을 고려해야 한다

  1. 다른 어플리케이션 서버를 통해서 서비스를 지속해야 한다.

  2. 로그인이 가능한 서비스에서 유저가 로그인 중이라면 세션 클러스터링 또는 레디스등을 활용한 세션관리가 되고 있을 테고 이에 대응해야 한다


답은 없지만 대체로 복잡한 문제는 '중간 레이어'를 가지게 되면 심플하게 해결될 때가 많다. 이경우에도 미리 대응 가능한 형태는 중간에 레이어를 두는 것인데, LB -> VIP/DNS ->  Server[1...n]  이렇게 처리가 가능할 것이다(DNS의 경우 TTL이 존재하고 특정 어플리케이션은 DNS를 영구 캐싱하기도 하므로 사용 시 유의가 필요하다).


데이터 스토리지의 경우 어떤걸 고려해야 할까?

  1. 데이터 스토리지 레이어는 동기화 문제로 다중화가 어려운 부분이다.

  2. RDB라면 다중화를 어떻게 할지? 예를 들면 샤딩을 도입했는데 샤드를 추가해야 한다면 마이그레이션 전략 등이 있다

  3. NoSQL이라면 정합성, 동기화, 다수결에 의한 데이터 오염 가능성도 고려 해야 한다


간략히 생각해보면 이 정도인데, 서비스마다 특성이 다르고 항상 정답이 있는 것이 아니다. 그러나 필자의 경우 트래픽이 증가 시 아래의 요구사항을 만족시키는게 좋다고 생각한다.


1. 유저에게는 서비스가 제공되어야 한다

  - 빈 결과나 에러가 유저에게 제공되면 안 된다.

  - 외부 연동 API, 시스템의 경우 언제든 장애가 날 수 있으니 Fast Fail 전략을 가지고 있어야 한다

2. 유저의 서비스 요청 시 (어느 서버로 요청되든) 동일한 결과를 받아야 하며, 로그인 여부 등의 특정 상태값에 의존되면 안 된다.

3. 트래픽 증가에도 대처가 가능해야 한다(ex: 서버 증설만으로도 대응 가능)



위에 정리된 요구사항을 만족시키려면 여러 가지가 떠오르는데 정리해보자


1. 자주 호출되고 한번 업로드되면 잘 변하지 않는 리소스 등은 따로 분리


2. 웹 서버 등의 레이어를 추가하여 서버의 특정 파일을 읽어오거나 쿠키 유무의 판별 등 간단한 기능 분리 

(ex: SSI: 서버사이드 스크립트 언어 사용), WAS 모두가 장애 나더라도 웹서버만 살아있다면 최소한의 서비스를 제공할 수 있는 모듈 구성(ex: Apache 커스텀 모듈)


3. 트래픽이 증가할만한 서비스, 독립적인 기능 구성이 가능한 서비스 등은 마이크로 서비스 도입


4. 서비스 디스커버리 도입

  - 서비스 디스커버리는 서비스의 주소, 포트, 프로토콜 등을 알려주는 방법이다

  - LB와의 차이는 LB는 부하를 어떻게 나눌 것인가에 대한 부분이고, Service Discovery는 어떤 서비스가 있고, 해당 서비스에 어떤 서버나 end points가 있는가를 찾기 위한 것.

  - 어떤 서비스가 있는지, 서비스의 접속방법을 알려줘야 하고, 서비스 내의 서버의 추가 제거가 있을 때 이벤트를 publish 해줘야 한다 (Server Side Load Balancing에서는 LB의 주소와 Port를, Client Side 에서Load Balancing는 해당 서버 목록과 Port를)


5. 서킷브레이커

  - 서킷브레이커는 연쇄적 장애 전파를 막기위해 자동으로 서비스의 연결을 차단 복구하는 역할을 한다  

  - 정상적이지 않으면, Timeout 등으로 처리량이 줄어들고, 부하가 늘어날 수 있다. Fas Fail Back 하자

  - 장애는 연쇄반응으로 A 서비스가 장애가 날 경우 의존하는 B 서비스도 느려지게 된다.

  - 트래픽 증가로 서버 등 외부 서비스 응답이 느려진 상황에서 외부 서비스 응답이 타임아웃 시간 초과 시 다시 호출하게 되고 외부 서비스는 이러한 요청이 쌓이게 되어 장애가 발생하여 이를 호출한 메인 서비스도 장애가 발생한다

  - 외부 서비스 데이터 받아오는 것을 포기하고 미리 준비된 데이터를 유저에게 전달해야 한다 (또는 정상 응답 결괏값을 주기적으로 저장해 사용도 가능하다)


6. 비동기 큐 사용

  - API 호출 시에 DB에 Write를 하는 경우, API의 Latency나 처리량은 결국 DB의 속도에 좌우된다

  - API 호출 시에 DB에 Wirte를 바로 하면 DB 부하 조절이 어렵다

  - 꼭 DB작업이 아니더라도 속도가 오래 걸리는 작업을 비동기로 후처리 할 수 있다

  - 비동기 큐를 이용하여 무거운 작업을 처리하도록 하자

   - Queue를 이용해 비동기 처리를 하면 DB에 Write전에 Return이 일어나므로 Latency가 줄어들고, 실제 Write 작업이 비동기로 Worker에 의해 처리되므로, 실제 DB부하를 조절할 수 있다.

  - 무거운 작업을 뒤에서 적정 수준으로 실행할 수 있어서 부하를 줄일 수 있고, Worker개수에 따라 작업 처리량을 조절이 가능하다. 그러나 비동기로 동작하고, 실제 DB 처리가 늦어지므로 내가 쓴 데이터를 바로 볼 수 없을 수도 있다. 바로 봐야하는경우에는 서버에서 Cache에 먼저 저장하고 해당 결과를 볼 수 있도록 한다(Write - Back 형태).

   


7. FailOver 전략 수립

  - Active 한 시스템에 장애 발생 시 Stanby 서버가 Active로 전환해서 서비스가 계속 운영되게 하는 것으로 서비스 가용성을 제공하기 위해서 사용한다

  - 자동화된 FailOver방식을 위해 Coordinator를 이용한 방식(Service Discovery 형태)을 사용한다

  - 또는 VIP(Virtual IP)와 DNS를 사용하도록 레이어를 추가한다


8. 모니터링 전략

  - 서비스 지표 수집

    :: API Call 수 => ex: 현재 요청중인 초당 Call수, 실패한 초당 Call수

    :: API Latency => ex: 현재 API들의 속도 Median 값, 99% 값, 최댓값 등

  - 서비스 노드 지표 

   :: 서버의 상태, CPU 사용량(DB 및 어플리케이션 서버, Cache 서버 등), 메모리 사용량, 디스크 사용량, 네 트워크 사용량(파일이나, 데이터 전송량이 얼마나 되는가), 현재 동작중인 정상적인 서버의 수

  - 에러 수집

  - 위 지표들을 Subscibe 할 수 있도록 설정



적다 보니 많아졌는데... 이렇게 구성하고 자동으로 모니터링하며 대응하는 시스템이 있으면 좋을 듯하다. 실시간 트래픽을 수집하고 트래픽이 폭증할 때 또는 이동평균(주식(ㅠ_ㅠ)에서 자주 보는 이평선)이나 머신러닝을 이용해 트래픽 예측을 하여 대응이 가능할 것이다. 트래픽 한계치 도달이 임박하거나 예상되면 다른 서비스의 연쇄 장애를 막고, 서비스의 성능 및 가용성 향상하기 위해 방어 전략을 펼칠 수 있는데, 우리에겐 위에서 구성한 Coordinator가 있기 때문에 이를 활용해서 Configuration을 바꿔서 대응이 가능하다.


이렇게 고가용성을 위한 구축이 가능한데 2번 정해진 날짜의 예견된 트래픽 증가에 대해 얘기해보자



정해진 날짜의 예견된 트래픽 증가


예를 들어 서비스의 특정 마케팅 이벤트가 예정되어 있다고 해보자.

유저에게 푸시가 보내지고 유저는 푸시를 타고 랜딩페이지를 보게 될 텐데, 여기서는 관심 없던 사람도 해당 랜딩페이지를 볼 수 있다는 것이다. 그 이야기인즉 트래픽이 높다는 것이다. 그러면 그 랜딩페이지만 서빙하는 서버와 실제 이벤트 참여 서버를 분리해서 랜딩페이지 서버만 미리 대응 구축하여 트래픽을 받아 낼 수 있을 것이다.


이번엔 새해의 메신저를 생각해보자. 새해가 되고 0시가 되면 메신저들은 긴장을 하게 된다. 새해맞이 인사를 메신저로 주고받기에 순간적으로 트래픽이 아주 큰 폭으로 증가하게 된다.


이렇게 관측가능 하고 특정 시점이 정해진 경우 이벤트 발생 시 관측된 트래픽 패턴과 특이사항을 정리해놓고 이 정보를 바탕으로 예견된 트래픽 증가를 대비하곤 한다. 물론 예측하는 것이고 예측만큼 중요한것이 서버의 가용량을 정확히 파악하는 것이다. 어플리케이션 서버와 DB의 현재 가용량을 판단하고 개선해야 한다.


현재의 서버가 증가될 트래픽을 문제없이 처리할 수 있는지 확인하려면 예측한 트래픽을 정확히 시뮬레이션하는 것이 중요하다(물론 시뮬레이션 과정에서 발생하지 않던 문제가 실제에서는 종종 벌어지기도 한다ㅠㅠ). 위에서도 보았듯이 분산처리가 되어 있다고 가정할 경우 애플리케이션 서버는 각 서버에 트래픽이 분배되는 구조이기 때문에 특정 서버로 유입되는 트래픽을 늘리는 방법으로 테스트가 진행 가능하다.


트래픽 증가 시뮬레이션에서는 두 가지 방법으로 테스트를 해보아야 한다고 생각한다. 1. 점진적 트래픽 증가 (예측 트래픽과 예측보다 더 많은 트래픽) 2. 순간 트래픽 증가 (예측 트래픽과 예측보다 더 많은 트래픽) 이렇게 점진적 트래픽 증가와 순간 트래픽 증가를 모두 테스트해봐야 하는 이유는 어플리케이션서버의 트래픽 처리 뿐만아니라 타 서비스와의 커넥션에서도 문제가 발 생 할 수도 있기 때문이다. 예를 들어 타 서비스에 요청을 보내기 위해 해당 컴포넌트에 급격하게 많은 Connection이 한꺼번에 생성되고, 해당 컴포넌트의 Heap메모리 사용률이 급격하게 늘어나면 Full GC가 발생해 성능이 급격히 떨어질 수도 있기 때문이다. 물론 한정된 수의 Connection만 생성하도록 설정이 되어있으면서 목표한 트래픽을 처리 가능하다면 문제가 되지 않겠지만 설정이 빠져있거나 목표한 트래픽을 처리 못할 수 도 있다. 또한 소켓 파일 리미트를 넘어서서 장애가 발생 하기도 한다.


Storage(DB, Cache)의 경우는 어떠할까? Storage의 경우 클러스터 형태로 구성이 되어있다면 클러스터 별로 트래픽이 유입되었을대 받는 부하가 크게 늘어나는 클러스터가 있고 그렇지 않은 클러스터가 있다. 이 경우 트래픽 패턴을 살펴볼 때는 1번의 모니터링에서도 설명하였던 초당 요청 횟수와 메모리 사용량을 살펴보게 된다. 만약 현재 구성된 Storage 클러스터로 가용량이 예측되는 가용량보다 부족할 경우 1. 트래픽 부하 분산을 위해 클러스터 확장 작업을 진행 2. 여러 역할을 담당하고 있는 클러스터의 경우 별도 클러스터 구성하기 3. 어플리케이션 수준에서 클러스터를 효율적으로 사용할 수 있는 최적화 방법 찾아서 변경 등의 처리를 해야 한다.



이렇게 예견된 트래픽 증가에 대한 대응과 갑작스러운 이벤트로 트래픽이 증가할 때 어떻게 대응할 수 있는지 알아보았다. 결국 두 경우 미리 준비해 놔야 할 것은 비슷하다. 갑작스러운 이벤트로 트래픽이 증가하는 부분이 대응된 상태 여야 하며, 예견된 트래픽 증가에 대한 대응은 추가로 수행해야 한다.


대규모 트래픽을 견디고 가용성을 높이며 유저에게 서비스를 서빙하기 위해 여러 Layer들이 필요하다. '캐싱', '데이터의 샤딩', '모니터링', '비동기 작업을 이용한 부하의 조절', 'Primary Write, Secondary Read'에 필요시 'VIP', 'DNS', 'Service Discovery', 'Coordinator'등이 있다. 미약한 경험이지만 코드레 벨이든 인프라 레벨이든 문제 상황에서 중간에 Layer를 추가하게 되면 심플하게 해결되는 경우가 꽤 있었다. 우리가 이렇게 레이어를 쌓으면서 운영전략을 꾸린 것처럼 말이다.



언제나 글은 급 마무리이다.


세줄 요약

1. 대규모 트래픽은 도전적이다.

2. 대규모 트래픽은 지속적인 계량과 끝없는 개선이 요구된다.

3. 죽지 마... 서버들아 내가 잘할게








<참고>

https://jung-jilol.tistory.com/11

https://chagokx2.tistory.com/m/92

https://junshock5.tistory.com/81

https://d2.naver.com/helloworld/6070967

https://www.slideshare.net/charsyam2/webservice-scaling-for-newbie

https://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html

https://engineering.linecorp.com/ko/blog/how-line-messaging-servers-prepare-for-new-year-traffic/

https://n1tjrgns.tistory.com/156

https://12bme.tistory.com/100

https://techblog.woowahan.com/2514/?fbclid=IwAR1TqZMjuArF8Z7H34NRarsUGQ9Hn2zjGpjrWFnH6j2fq-sRZh0NVzx-U1s

스페셜땡스 투 김잉여님 두뇌



        

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari