주간SaaS 입니다.
공동 주택에서 층간 소음 때문에 이웃간 분쟁이 생겨 주민도 관리자도 곤란을 겪는 것 처럼 멀티테넌트 아키텍처를 기반으로하는 SaaS에서도 역시 유사한 문제를 겪습니다. 저희는 이를 시끄러운 이웃 문제(Noisy Neighbor Problem)이라고 하는데요, 이에 대한 해결책은 SaaS 아키텍트에게 늘 고민거리 입니다.
오늘 소개하는 “Mitigating the Noisy Neighbour Multitenancy Problem”라는 제목의 아티클은 시끄러운 이웃 문제를 해결하는 대표적인 방법들을 쉽고 간단하게 정리해 소개 합니다. 저자가 .NET, Azure의 예를 들지만 그 외 기술 스택에서도 물론 적용이 가능한 현실적인 대안들 입니다. 도움 되셨으면 좋겠습니다. 그럼 좋은 하루 보내십쇼.
멀티테넌트 시스템에서는 동일한 컴퓨팅 리소스를 공유하는 여러 개의 서로 다른 '테넌트'(종종 '고객')가 있습니다. 이렇게 하면 모든 고객을 위해 완전히 별도의 애플리케이션 인스턴스를 배포하는 것보다 비용을 크게 절감할 수 있을 뿐만 아니라 많은 인프라를 관리하는 데 드는 운영 오버헤드도 줄일 수 있습니다.
멀티테넌시에는 몇 가지 단점이 있습니다. 한 테넌트가 너무 많은 작업을 수행하여 다른 테넌트의 시스템 성능이 저하될 가능성이 있다는 것입니다. 이는 시끄러운 이웃이 다른 세입자에게 부정적인 영향을 미치는것 처럼, 아파트에서 생길법한 문제에 비유하여 "시끄러운 이웃" 문제라고도 합니다.
한 세입자가 시끄러운 이웃 문제를 일으킬 수 있는 일반적인 상황은 '대량 섭취(리소스를 단독으로 많은 양을 사용하는 경우)' 작업이 일어날 가능성이 있는 경우입니다. 때때로 한쪽에서는 새 테넌트가 온보딩 중인데, 대량의 데이터를 시스템으로 가져오려고 하면서 큰 부하를 만들 수도 있습니다. 또는 시스템이 테넌트가 직접 호출할 수 있는 통합 API를 외부에 노출해 의도치 않게 이 API를 사용하여 모든 테넌트에 영향을 미치는 시스템 서비스 거부 공격을 일으킬 가능성이 있습니다.
저는 최근에 이 문제에 대하여 상당히 집중하고 있었는데, 시끄러운 이웃 문제를 해결할 수 있는 단일 기술은 없다는 결론에 도달했습니다. 대신 적용할 수 있는 여러 가지 전략이 있습니다. 이 글에서는 이러한 전략 중 몇 가지를 간략하게 소개하고 어떻게 적용할 수 있는지에 대해 생각해 보겠습니다.
1. 스케일링
이상적인 세계에서는 시스템이 추가 부하를 처리하기 위해 동적으로 확장되기 때문에 이웃 테넌트가 시끄러워도 문제를 일으키지 않습니다. Azure Functions와 같은 서버리스 플랫폼은 이미 이 기능을 매우 잘 수행하여 API의 부하 또는 대기열의 백로그가 너무 커지면 이를 감지하고 추가 서버를 자동으로 프로비저닝할 수 있습니다. 물론 Kubernetes의 horizontal pod autoscaler 또는 Azure App Service 플랜의 built-in autoscaling과 같은 기능도 비슷한 기능을 제공합니다.
물론, 실제 환경에서는 스케일링이 노이즈가 많은 이웃 문제를 처리하기에 충분하지 않을 수 있습니다. 막대한 클라우드 요금을 지불하지 않으려면 확장하는 호스트 수에 상한을 설정하여 데이터베이스가 이 막대한 수의 호스트의 동시 연결 요청을 처리하는데 따른 과부하를 방지해야 합니다(이로 인해 스케일 아웃을 통해 얻은 이점이 상쇄될 수 있음).
2. 속도 제한 API
노이즈 이웃 문제를 완화하기 위한 또 다른 옵션은 API에 테넌트별 속도 제한을 추가하는 것입니다. 이 접근 방식을 사용하면 한 테넌트가 API 엔드포인트를 너무 자주 호출하는 경우 해당 테넌트에 대한 일부 호출을 거부하고 다른 테넌트의 호출은 허용하도록 선택할 수 있습니다.
예를 들어, 클라이언트에게 일정 시간 동안 뒤로 물러나라고 알려주는 "재시도 후" 헤더와 함께 HTTP 429 "Too Many Requests" 응답 코드를 반환할 수 있습니다. 이는 데이터 수집 API 호출을 하는 쪽에서 오히려 '역압박'에 대응하여 API 호출 속도를 줄이도록 하기 때문에 특히 데이터를 대량 수집 하는 시나리오에서 유용할 수 있습니다.
하지만 테넌트별 속도 제한을 구현하는 것이 반드시 간단한 것은 아닙니다. 우선 한 테넌트가 실제로 컴퓨팅 리소스를 독점하고 있는지 확인해야 합니다. 한 가지 접근 방식은 각 테넌트가 호출 속도가 제한되기 전에 일정 기간 동안 특정 수의 작업을 허용하는 일종의 "Quota(할당량)"을 설정하는 것입니다.
가장 최근의 .NET 7에는 rate-limiting middleware가 포함되어 있어 속도 제한을 구현하는 작업을 크게 단순화할 수 있을 것으로 보입니다.
이 접근 방식의 단점은 테넌트가 작업을 수행하지 못하도록 사실상 거부하는 것이기때문에 고객 경험에 좋지 않다는 것입니다. 따라서 다른 전략은 가능한 한 테넌트의 요청을 수락은 하지만, 대기열을 사용하여 작업을 비동기적으로 구현하는 것입니다. 이에 대해 이어서 살펴보죠.
3.대기열 우선순위 지정
이론적으로 비동기식으로 작업할 때는 시끄러운 이웃 문제는 큰 문제가 되지 않습니다. 메세지는 대기열에 배치되고 메시지 처리기는 백로그에 쌓인 메세지를 처리 함으로써 작업을 완수 합니다. 일반적으로 큐를 사용하면 스케일 아웃이 매우 쉽습니다. 그리고 다른 테넌트들은 평소보다 시간이 조금 더 걸린다는 사실조차 눈치채지 못할 수도 있습니다.
하지만 한 테넌트가 수백만 개의 항목을 대기열에 추가하여 이를 처리하는 데 많은 시간이 걸리는 경우 어떻게 될까요? 이런 상황에서는 다른 테넌트들이 서비스 저하를 알아차릴 가능성이 높습니다. 그렇다면 어떤 옵션이 있을까요?
생각할 수 있는 한 가지 옵션은 테넌트별로 대기열을 만드는 것입니다. 모든 대기열을 병렬로 처리하거나 각 대기열을 차례로 폴링하는 "라운드 로빈" 접근 방식을 사용하여 각 테넌트가 "상위" 메시지를 처리할 수 있는 동등한 기회를 갖도록 할 수 있습니다. 안타깝게도 이 접근 방식에는 많은 수의 테넌트가 있는 경우 많은 대기열을 한꺼번에 처리하는 데 많은 비용과 리소스가 소요될 수 있다는 큰 단점이 있습니다.
또 다른 접근 방식은 "우선순위 대기열"을 사용하는 것입니다. 너무 많은 작업을 제출한 테넌트의 새 메시지는 우선 순위가 높은 대기열이 비워진 후에만 서비스되는 "우선 순위가 낮은" 대기열에 자동으로 배치됩니다. 이 접근 방식에는 여러 가지 변형이 가능하며, 제대로 구현 하려면 상당히 복잡할 수 있습니다.
또 다른 옵션은 단일 테넌트가 큐에 메시지를 가득 채우도록 허용하되, 메시지를 처리하는 시점에 속도를 제한하는 것입니다. 지난 한 시간 동안 단일 테넌트에 대해 너무 많은 메시지를 처리한 경우에는 해당 테넌트에 대한 추가 메시지를 큐의 끝으로 다시 게시하거나 다른 방식으로 저장하여 해당 테넌트에 대한 추가 메시지를 "연기(defer)"합니다. 물론 이 방법은 대기열에 다른 테넌트에 대한 유효한 작업이 있다는 것을 알고 있다고 가정하므로 지능적으로 구현하기가 까다로울 수 있습니다.
(🐶지난 5월 주간SaaS에서 소개한 “멀티 테넌시에서 테넌트에게 공정한 서비스 제공하기”라는 글을 추가로 읽어보시면 멀티테넌시 환경에서 대기열을 전략적으로 사용하는 방법을 만나실 수 있습니다)
4.예약된 작업
멀티테넌트 시스템에서 고려해야 할 또 다른 사항은 예약된 작업에서 일어나는 일입니다. 예를 들어 새벽 1시에 '청소(clean up)' 작업이 예정되어 있는 경우가 종종 있습니다. 여기서의 위험은 때때로 한 명의 테넌트가 특정 날짜에 비정상적으로 많은 양의 작업을 수행하여 다른 테넌트에게 영향을 미칠 수 있다는 것입니다.
여기서 간단한 접근 방식은 각 테넌트에 대해 예약된 작업을 처리하기 위해 페이징 또는 시간 상자 방식으로 접근하는 것입니다. 예를 들어, 다음 테넌트로 넘어가기 전에 한 테넌트에 대해 최대 10,000개의 정리 항목을 처리하도록 결정할 수 있습니다. 그런 다음 다른 모든 테넌트도 정리 작업을 실행할 기회를 얻은 후 다시 돌아와 바쁜 테넌트를 위해 추가 배치를 수행할 수 있습니다.
5.테넌트 마이그레이션
극단적인 상황에서는 특정 테넌트가 너무 많은 문제를 일으켜 해당 테넌트를 완전히 별도의 시스템으로 옮기는 결정을 해야할 수 도 있습니다. 만약 해당 테넌트가 주요 고객이기 때문에 리소스를 너무 많이 소비하고 있다면 단일 테넌트 환경으로 분리해 만드는데 따른 비용은 합당한 비용일 수 있습니다.
물론 이 경우 애플리케이션의 한 '배포'에서 다른 배포로 테넌트를 마이그레이션할 수 있는 기능이 있다고 가정합니다. 테넌트별 데이터베이스와 같이 데이터를 별도로 저장하여 다른 컴퓨팅 리소스 세트에 간단히 다시 연결할 수 있다면 이 작업이 훨씬 쉬워질겁니다.
요약
멀티테넌트 시스템을 운영하는 경우, 단일 테넌트가 컴퓨팅 리소스를 독점할 경우 다른 테넌트에 어떤 영향을 미칠지 고려해야 합니다. 지금까지 제가 작업한 멀티테넌트 시스템에서 사용했거나 고려했던 여러 가지 기술과 전략을 설명했지만, 다른 분들은 멀티테넌트 시스템에서 어떤 것이 효과가 있었는지(그리고 어떤 것이 효과가 없었는지) 경험담을 듣고 싶습니다!
댓글
의견을 남겨주세요