반응형
Why Reactive? 책 요약(책은 여기에서 다운로드 가능)
1장. 소개
시스템은 다음과 같아야 함
- 사용자에게 응답해야 함
- 실패를 다루고 사용불능 상태에서도 동작해야 함
- 다양한 부하 상황에서 견뎌야 함
- 다양한 네트워크 상황에서 메시지를 보내고, 받고, 전달할 수 있어야 함
이를 위해 다음 필요
- 하드웨어 자원 활용 제어 -> 응답성
- 구성 요소를 분리 -> 독립적인 확장
- 시스템 간 비동기 통신 -> 복원력, 탄력성(resilience)
리액티브 시스템의 이점이 여기에 있음.
왜 지금인가?
다음 몇 가지 움직임이 리액티브 프로그래밍이 뜨는데 일조함
- IoT와 모바일 : 서버가 동시에 수 백만의 연결된 장치를 처리해야 함 -> 병행과 분산 어플리케이션에 대한 요구가 급격히 증가
- 클라우드, 컨테이너 : 경량의 가상화와 컨테이너 기술로 세밀한 범위로 더 빠르게 배포 가능 -> 더 편하게 할 수 있는 도구
과거부터 존재한 개념이 요즘 뜨는 이유
- 더 나은 자원 활용과 확정성에 대한 필요성 증가. 이를 위한 도구가 사용 가능해짐
- 모든 구현체가 상호운영할 수 있는 표준으로 특정 구현체에 얽매이는 위험 감소
2장. 어플리케이션 수준에서의 리액티브
리액티브와 비동기
- 리액티브 프로그래밍에 있어 핵심 요소 중 하나는 태스크를 비동기로 실행할 수 있는 것
- 리액티브 라이브러리, 구현의 공통 테마는 어떤 종류의 쓰레드 풀에 기반한 이벤트 루프나 공유 디스패처 인프라를 사용하는 것. 저렴한 구성 요소 간에 비싼 자원(thread)을 공유함으로써 단일 어플리케이션을 멀티 코어로 확장할 수 있음
- 이런 멀티플렉싱 기술은 단일 장비로 백 만 엔티티를 다룰 수 있게 함
- 쓰레드를 직접 사용하면, 사용자 당 액터를 만들 수 없고, 너무 무거워서 빨리 할 수 없고, 또한 쓰레드를 직접 다루는 게 간단하지 않으며, 프로그램이 다른 쓰레드 간 데이터 동기화 코드로 빠르게 지배당함
- 비동기에서 주의할 점은 블로킹임. 파일, 네트워크 IO와 같은 블로킹 연산은 CPU 자원을 사용하지 않는데, 쓰레드가 블로킹되면서 다른 액터(또는 작업)을 실행하지 못하게 됨
- 블로킹을 별도 쓰레드 풀로 분리해서 실행 -> 메인 이벤트 루프나 디스패처를 블록하면 안 됨
USL(Universal Scalability Law)
- 통신 비용, 데이터 동기화 비용까지 고려
- 적정 지점 이상으로 시스템 자원을 사용하게 밀어 붙이면, 더 이상 속도가 빨리지 않을 뿐 아니라, 전체 처리량에 부정적인 영향을 줌(백그라우드에서 일어나는 모든 종류의 조정 때문에 발생, 예 네트워크나 메모리 포화로 이런 조정 발생 가능)
- 다양한 자원에 대한 경쟁이 발생할 수 있고, 과사용 문제는 CPU부터 네트워크까지 적용
역압(backpressure)
- 최적 사용 상태 유지를 위한 역압 사용
- 동기 API는 블록 연산으로 절로 역압이 된다
- 비동기 API는 최대 성능을 낼 수 있는데, 이는 느린 하류 스트림이나 다른 어플리케이션을 압도할 수 있음
- 리액티브 스트림 스펙은 스트림 라이브러리 사용자에게 투명하게 역압을 적용할 수 있도록 요구
- 비동기 + 역압 -> 시스템의 한계까지 push할 수 있으나, 한계를 넘지는 않게 할 수 있음
- 스트리밍 API와 제한된 메모리
- 스트리밍의 두 면 : 소비 API, 생성 API
- 스트리밍 API와 메모리에 제한된 처리 : 필요 이상 데이터를 메모리에 적재하지 않게 해 줌 -> 이는 용량 설계에 유용한 속성
- 하류가 느릴 때 버퍼를 두어 하류에서 데이터를 처리할 수 있게 함
- 무한정 버퍼에 담을 수 없으므로 제한된 크기의 버퍼 사용
- 버퍼에 적재된 메시지의 비율로 지표 측정 가능(클라이언트가 얼마나 느린지, 서비스 레벨, 노드 활용 등)
- 큐 버퍼를 모니터링해서 이벤트 발생 가능(예, 다 차면 클라이언트를 끊거나 오래된 메시지 삭제)
3장, 시스템 수준에서의 리액티브
분산 시스템과 메시징
- 요청-응답 API의 문제 : 개발자가 할 수 있는 것을 제한
- 예, HTTP는 이벤트 스트림을 받으려면 일부 hack이 필요, HTTP로 Job을 제출하고 제출 완료를 polling해야 함
- 메시지 기반 시스템(akka 등)을 사용하면 어렵지 않게 할 수 있음
- 예, Job 제출 세미지를 fire-and-forget으로 보내고 진행 상태를 메시지로 받으면 됨
- 메시지를 받을 수 있는 addressable 엔티티만 있으면 됨
- 요점: 매우 작음. 다른 시스템과 리액티브하게 상호작용하기 위한 비용 작음 (연결된 커넥션 X, 라운드 트림 X, 폴링 X)
- HTTP로 메시징 같은 API를 설계 하려면 많은 고민(블로킹, 웹소켓, 폴링, SSE 선택) 필요하고 확장성도 떨어짐
부하에 살아남고 요금 아끼기
- 최근 추세 : 컨테이너, 클러스터 오케스트레이션 -> 유연함(elastic)이 중요해짐
- 스케일 up/out 만큼 scale down도 중요
- 부하에 맞게 확장/축소해서 적정 비용 유지
- 사전(proactively) 확정 : 언제 확장할지 결정할 수 있는 기술
- 리액티브 서비스는 서비스 확장을 위한 성공 요인
탄력성(resilience)이 없으면 다른 것은 아무것도 아님
- 리액티브의 여러 특징(성능, 확장성, 메시징 패턴 등) 중 가장 중요한 것은 탄력성
- 시스템에 문제가 있을 때 완전히 불능 상태가 되면 제 아무리 빠르고 확장 가능한 시스템도 의미 없음
- 공통의 비싼 자원에 접근하면, 그 자원이 단일 실패 지점이 될 수 있음
- 리액티브 시스템은 "own your own data" 패턴을 따른다. 시스템이 주변에 대한 상태를 내부에 보관한다면 외부 정지 상황에서 살아남을 수 있음
4장, 리액티브 시스템의 빌딩 블록
- 모든 것이 비동기인 경우는 흔치 않으므로, 점진적으로 리액티브 아키텍처로 넘어가기 위한 생산적인 방법 필요(예, Strangler Pattern: 점진적으로 블로킹/동기 앱을 비동기 앱으로 교체하는 패턴)
- 리액티브로 넘어가기 전에 리액티브에 대해 고민하고 원칙을 이해하는 것이 중요 (단순히 기술만 추종하면 안 됨. 애자일에 대한 이해없이 애자일을 도입하고선 뭔가 잘 안 될 때 애자일을 탓하는 것과 동일한 상황이 발생할 수 있음)