IT/AWS

[Netflix] Part 4 - Fault Tolerance

물통꿀꿀이 2018. 12. 19. 23:51

*https://medium.com/netflix-techblog/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a

이번 글에서는 Netflix 분산 시스템에서 fault tolerance 수행하기 위해 고민했던 흔적을 소개해보려고 한다.


이전 글에도 언급했듯이, API는 서비스에 문제가 발생했을 때 영향을 받을 수 있다. 

특히, SOA(Service-Oriented Architecture) 환경에서는 더더욱이나 서비스의 문제가 API를 더 취약하게(vulnerable)하게 만들 수 있다.

Circuit breaker 패턴을 사용하여 API를 resilient하도록 한 것 이외에도 내부적으로 장애를 어떻게 격리하고 부하를 줄일 수 있는지 등에 대해 기술적으로 알아보려고 한다.


Fault Tolerance* is a Requirement, Not a Feature

* Fault Tolerance를 단순히 번역하면 장애 허용이라는 뜻으로 보다 자세한 것은 아래 URL 참조

https://en.wikipedia.org/wiki/Fault_tolerance


Netflix는 하루에 10억건 이상의 요청을 받고 내부 서브 시스템과 통신한다.

그림 1. Netflix Requests and Access over Network


그림 1에서 확인 할 수 있듯이, App Container에서 사용자의 요청을 받고 내부 의존 서비스*와 연결한다.

*내부 서비스는 클라우드 환경에서 수 천개의 EC2 인스턴스를 사용한다.


그런데 fault tolerance를 보장하지 않으면, 서비스의 중단 시간이 발생한다. (아래 원문 참조)

"Without taking steps to ensure fault tolerance, 30 dependencies each with 99.99% uptime would result in 2+ hours downtime/month 

(99.99%30= 99.7% uptime = 2+ hours in a month)."


그림 1을 바탕으로 한 가지 예를 들어보면, latency가 높아진 상황에서 API가 실패하면 모든 Tomcat(또는 Jetty와 같은 다른 컨테이너) 요청 스레드를 포화(saturate)시키고 결과적으로 전체 API를 멈추게 할 수 있다. (그림 2)

그림 2. Request blocked by Latency in Single Network Call

그림 2만 확인하여도 사용자의 요청이 내부 시스템의 문제로 인해 막힐 수 있다.
그렇기 때문에 high volume, high availability 어플리케이션은 fault tolerance가 필수 이다. (모든 것은 사용자를 위해)

Netflix DependencyCommand Implementation
Netflix에서 몇몇 고려 사항*을 바탕으로 fault tolerance를 구현하기 위해 아래와 같은 접근 방식을 취했다.
* 내부 API 팀 당 프로토콜이 다를 수 있고, 서드 파티 라이브러리에 의존
- Network timeouts and retries
- separate threads on per-dependency thread pools
- semaphores
- circuit breakers
위의 언급한 4가지 방식은 장단점이 존재하지만, 함께 사용하게 되면 사용자 요청과 의존성 사이에 완충제*가 될 수 있다. (그림 3 참조)
*4가지 방식을 동시에 사용하면 많이 좋아진다는 의미

그림 3. DependencyCommand

그림 3에서도 확인 할 수 있듯이, DependencyCommand는 별도의 스레드를 사용한다. (Dependency A Thread -> Dependency A Client 확인)
각 스레드에서는 Network binding 및 fail, reject에 대한 fallback 로직*을 정의한다. 
*관련된 부분은 그림 4 참조

그림 4. DependencyCommand Thread Logic

이렇듯 의존성 호출(DependencyCommand)를 별도의 스레드로 분리하면 단점보다는 장점이 더 많다고 한다.
특히, API의 동시성(concurrency)이 점차 많아지고 있는 상황에서 별도 스레드의 내부 알고리즘(그림 4)으로 fault tolerance 및 performance를 얻을 수 있다. 
(Parallel한 스레드 사용으로 인한 장점)

때문에 아래 그림 1이 그림 5와 같이 바뀐다. (Thread pool을 통한 routing)

그림 5. Thread Pool Routing

DependencyCommand의 별도의 스레드가 만들어지면서, 내부 시스템에서 문제가 발생했을 때 timeout 또는 reject가 발생한다. 
즉, 이전에는 blocking을 사용하여 대기 시간을 발생시켰다면 지금은 fault 
tolerance를 통해 대응한다.

그림 6. Throws an Exception

How do we respond to a user request when failure occurs?

아래에서 볼 수 있듯이 Netflix에서 사용하는 fallback 접근 방법이 있다. (사용자에게 영향을 미치는 순서)
- Cache : "Retrieve data from local or remote caches if the realtime dependency is unavailable, even if the data ends up being stale"
- Eventual Consistency : "Queue writes (such as in SQS) to be persisted once the dependency is available again"
- Stubbed Data : "Revert to default values when personalized options can't be retrieved"
- Empty Response (Fail Silent) : "Return a null or empty list which UIs can then ignore"

위에서 언급한 방식을 통해 Netflix는 내부 서비스의 기능을 최대한 유지하면서 동시에 사용자의 최대 가용 시간 또한 유지하려 하였다.
즉, fault tolerance 시스템을 구축하면서 문제가 발생했을 때, fallback 로직을 통해 실제 서비스가 제공하는 것과 같은 응답을 유지하려 하였다.

이제까지 설명했던 것을 바탕으로 한 가지 예로 확인해보면 아래와 같다.

그림 7. Use Case