기술블로그

네이버 메인 페이지의 트래픽 처리 - 본편 2

ignuy 2023. 7. 13.

네이버 메인 페이지의 트래픽 처리 - 본편 1에서 정리한 네이버 메인 페이지의 서비스 요구사항과 기술 스택은 다음과 같다.

🔔 서비스 요구사항 🔔
❗어떤 서버로 접속해도 동일한 내용을 보여 주어야 하며, 특정 상탯값(사용자의 로그인 여부 등)에 의존하지 말아야 한다.
❗ 무슨 일이 있어도 사용자에게 서비스가 제공되어져야 한다.
      => 브라우저에 빈 페이지가 나타나선 안된다.
      => 메인 페이지에서 연동하는 외부 시스템은 늘 접속 불안정을 가정하고 빠른 실패 전략을 실행한다.
❗ 트래픽 증가에 탄력적으로 대처할 수 있어야 한다.
      => 트래픽이 폭주할 때 서버 증설만으로도 대응할 수 있어야 한다.
      => 각 컴포넌트(Web server, WAS)의 효율성을 극대화할 수 있어야 한다. 
1. GCDN(Global CDN)
2. SSI(Server Side Includes)
3. Apache 커스텀 모듈
4. 마이크로 서비스(부분 도입)
    => 서킷 브레이커(circuit breaker)
    => 서비스 디스커버리(service discovery)

본편 1에서는 1, 2, 3 기술 스택을 다루었고 이번 시간엔 마이크로 서비스의 부분 도입을 다룰 예정이다.

 

네이버 메인 페이지의 분산 처리 기술

4. 마이크로 서비스(부분 도입)

메인 페이지에서 다른 시스템과 연관성이 적은 독립적인 기능을 별도 서비스로 분리해 구축했다.

외부 시스템과 API 연동을 담당하는 부분은 특히 Node.js로 구축했다. 네이버 메인 페이지에서는 동시에 여러 대의 외부 시스템과 API 연동을 실행해야 하는 경우가 많다. Node.js를 사용하면 병렬 처리 시 스레드 문제 등을 고려할 필요가 없고, 비동기식으로 외부 시스템 연동 시 병렬로 여러 개의 요청을 한 번에 처리할 수 있다. 그래서 이러한 기능을 구현하기에는 기존에 사용하던 Java보다는 Node.js로 구현하는 것이 더 유리하다고 판단했다. 또한 부서 내 언어적 다양성을 확보해 용도에 맞는 적절한 기술을 사용할 수 있고, 부서원의 기술 역량 향상에 도움을 주는 장점도 있다.

병렬 처리 시 스레드 문제
루프를 병렬화하는 작업은 실행에 복잡도를 증가시킨다. 여러 스레드가 해당 변수에 동시에 액세스할 때마다 경합 상태가 발생할 가능성이 커지므로 잠금(Lock)을 사용하여 변수에 대한 액세스를 동기화할 수 있지만 동기화의 비용은 시스템 상 오버헤드로 작동하게 된다. 또한, 병렬 루프에서 스레드로부터 안전하지 않은 인스턴스 메서드를 작성하면 프로그램에서 감지하거나 감지하지 않을 수도 있는 데이터 손상이 발생할 수 있다. 즉, 예외가 발생할 수 있다.
-마이크로소프트, 데이터 및 작업 병렬 처리에서 발생할 수 있는 문제

 

API 서버에는 또한 서킷 브레이커를 적용했다. WAS에는 서버의 모니터링과 관리를 위해 서비스 디스커버리를 적용했다.

 

1. 서킷 브레이커

서킷 브레이커는 외부 서비스의 장애로 인한 연쇄적 장애 전파를 막기 위해 자동으로 외부 서비스와 연결을 차단 및 복구하는 역할을 한다. 서킷 브레이커를 사용하는 목적은 애플리케이션의 안정성과 장애 저항력을 높이는 데 있다. 분산 환경에서는 네트워크 일시 단절 또는 트래픽 폭증으로 인한 간헐적 시간 초과 등의 상황이 종종 발생한다. 이로 인해 다음과 같은 연쇄 장애가 발생할 가능성이 있다.

💡1. 트래픽 폭증으로 인해 API 서버 등 외부 서비스의 응답이 느려진다.
💡2. 외부 서비스의 응답이 타임아웃 시간을 초과하면 데이터를 받아오기 위해 외부 서비스를 다시 호출한다.
💡3. 이전에 들어온 트래픽을 다 처리하지 못한 상태에서 재시도 트래픽이 추가로 적체되어 외부 서비스에 장애가 발생한다.
💡4. 외부 서비스의 장애로 인하여 데이터를 수신하지 못해 네이버 메인 서비스에도 장애가 발생한다.

일시적인 경우라면 2,3회 재시도로 정상 데이터를 수신할 수 있다. 하지만 장애 상황이라면 계속 재시도하는 것이 의미도 없을 뿐 아니라 외부 서비스의 장애 복구에 악영향을 끼칠 수 있다. 이럴 때에는 외부 서비스에서 데이터를 받아오는 것을 포기하고 미리 준비된 응답을 사용자에게 전달하는 것이 시스템 안정성 및 사용 편의성 측면에서 옳다고 할 수 있다.

 

아래 그림은 서킷 브레이커에서 서킷의 상태 변화를 나타내는 그림이다.

서킷 브레이커에서 서킷의 상태 변화

서킷은 다음과 같은 세가지 상태를 가진다.(회로이므로 닫힌(close) 상태가 정상, 열린(open) 상태가 비정상)

  • Closed 상태 : 메서드가 정상적으로 작동해 서킷이 닫힌 상태
  • Open 상태 : 메서드에 문제가 생겨서 서킷이 열린 상태
  • Half - Open 상태 : Open 상태와 Closed 상태의 중간 상태. 메서드를 주기적으로 확인해 정상이라고 판단되면 Closed 상태로 상태를 전환하고, 정상이 아니라면 Open 상태를 유지한다.

서킷이 Closed 상태에서 잘 작동하고 있다가 오류가 발생하기 시작해서 일정 횟수에 도달하면 일단 서킷을 Open 상태로 전환한다. 이때에는 메서드를 호출해도 실제로 메서드가 실행되는 대신 서킷 브레이커가 개입해 미리 지정된 응답(단순 실패를 반환할 수도 있고 특정한 값을 반환할 수도 있음)을 반환한다.

특정 주기 동안 Open 상태로 유지하고 있다가 서킷이 자체적으로 메서드가 정상으로 작동하는지 확인하는데, 이 상태가 Half - Open 상태이다. Half-Open 상태에서 메서드를 몇 번 호출해서 정상 응답이 돌아온다면 서킷은 다시 Closed 상태로 전환되어 요청을 처리한다. 몇 번 호출했는데 계속 실패가 반환된다면 서킷은 Open 상태로 유지된다.

기존에는 이런 로직을 구현하기 위해 개발자가 수많은 if-else 구문을 작성해야 했다. 하지만 Netflix OSS의 서킷 브레이커 라이브러리인 Netflix OSS Hystrix가 등장한 이후 많은 개발자가 Netflix OSS Hystrix를 적극적으로 사용하고 있다. 또한 Netflix OSS를 Spring Cloud 프로젝트에서 사용할 수 있도록 래핑한 Spring Cloud Netflix를 활용하면 Spring 개발자는 자신의 애플리케이션에서 서킷 브레이커를 손쉽게 활용할 수 있다. 네이버 메인 개발팀은 Node.js에서 서킷 브레이커를 사용해야 해서 HystrixJS를 사용했다.

 

네이버 메인 페이지에서는 서킷 브레이커를 다음과 같이 활용한다.

  • 정적 파일을 배포하는 Data Publisher를 통해 사전에 정상 응답 결과값을 주기적으로 저장해 둔다.
  • 외부 시스템에 이상이 있다고 판단되면(Open 상태) 서킷 브레이커가 개입해 바로 연결 시도를 포기하고 미리 준비해둔 정상 응답 결과값을 사용해 HTML 화면을 랜더링한다.
  • 이후 계속 상태를 지켜보다(Half-Open 상태) 외부 시스템이 정상으로 돌아오면 서킷 브레이커가 다시 연결을 시도한다.

서비스 디스커버리

서비스 디스커버리는 동적으로 생성, 삭제되는 서버 인스턴스에 대한 IP 주소와 포트를 자동으로 찾아 설정할 수 있게 하는 기능이다.

 

아래 상황은 서비스 디스커버리를 사용하지 않는 서로 다른 기능을 수행하는 두 개의 서버군의 환경을 나타내고 있다.

서비스 디스커버리를 사용하지 않는 환경

서버군 2에 10.0.0.5 서버가 신규로 생성됐지만, 서버군 1에서는 신규 서버로 연결을 맺을 수 없다. 서버군 1이 최초로 실행됐을 당시에는 10.0.0.5 서버가 없었기 때문에 서버 목록에 10.0.0.5 서버가 없기 때문이다. 서버 목록을 갱신하기 위해선 서버군 1에 있는 서버를 다시 실행해야 한다. ← 서비스 운영 도중에 서버를 끄고 키는 행위는 매우 위험하다.

 

아래는 서비스 디스커버리를 사용하는 개발 환경이다.

서비스 디스커버리를 사용하는 환경

서비스 디스커버리 서버는 각 서버군의 서버 목록을 관리한다. 서버는 시작할 때 자신의 정보(IP 주소, 포트)를 서비스 디스커버리 서버로 보내고, 서비스 디스커버리 서버는 이 정보를 받아 서버군의 서버 목록을 최신으로 갱신한다. 각 서버군에 있는 서버는 주기적으로 서비스 디스커버리 서버로 목록을 요청해 서버 목록을 최신으로 유지한다. 이러한 방법으로 서버는 재시작 등 외부의 개입이 없어도 자동으로 항상 최신 서버 목록을 확보할 수 있다.

서비스 디스커버리는 특히 클라우드 환경에서 빛을 발한다. 기존 온프레미스(on-premises) 환경에서는 서버를 추가, 삭제하는 일이 많지 않았다. 서버를 추가, 삭제하는 경우에도 통제된 환경에서 주로 작업하므로 갱신 문제가 크지 않다. 하지만 클라우드 환경에서는 수시로 서버가 추가, 삭제된다(특히 오토스케일링 등의 기능을 사용한다면 더욱 더). 이를 사람이 일일이 확인해 서버를 다시 실행하는 것은 불가능에 가깝다. 서비스 디스커버리는 이런 상황에서 아주 유용하게 사용할 수 있는 기술이다.

댓글