안녕하세요 주간SaaS 입니다. 오늘은 The single-tenancy to multi-tenancy spectrum라는 제목의 글을 소개 합니다.
싱글 테넌시와 멀티 테넌시에 관해 읽어봤던 어떤 자료와 글 보다도 핵심 사항을 저자가 구현한 서비스 사례와 함께 촌철살인으로 전달합니다. 글에서는 특정 클라우드 서비스를 사용하는 것을 예시로 들었으나 현실에서 만나는 문제와 고민은 똑같을거라 생각 됩니다. 분명 도움이 되실거라 생각합니다.
꼭 읽어 보시면 좋겠습니다. 그럼 좋은 하루 보내세요!
지난 몇 년 동안 저는 싱글 테넌트 아키텍처로 다중 소비자 애플리케이션을 구축해 왔습니다. 각 소비자는 별개의 독립된 리소스 스택을 가지고 있기 때문에 단순성과 작은 장애 폭발 반경을 갖습니다. 하지만 확장성의 한계에 부딪혀 곧 멀티테넌트 아키텍처로 전환할 예정입니다. 이 글에서는 싱글 테넌시 아키텍처의 장단점과 싱글 테넌시와 멀티 테넌시가 이분법적인 선택이 아닌 이유를 살펴보겠습니다.
배경
우리가 현재 다루는 애플리케이션은 이벤트 브로커 이커머스, 즉 EBE입니다. 이 내부 플랫폼은 여러 애플리케이션에서 이벤트를 수신하고 구독한 애플리케이션에 적합한 이벤트를 라우팅하는 역할을 합니다.
이 애플리케이션을 개발하기 시작할 때 우리는 싱글 테넌시 아키텍처를 선택했습니다. 실제로 이는 다이어그램 왼쪽의 모든 엔드포인트와 왼쪽의 모든 구독이 이벤트를 수신, 검증, 전달하는 데 필요한 리소스를 각각 고유한 CloudFormation 스택으로 관리하고 있다ㄴ 의미입니다. 당연히 이 환경에는 많은 수의 스택과 상당한 양의 중복이 있다는 것을 의미합니다.
싱글 테넌시의 장점
싱글 테넌시 아키텍처를 선택한 가장 큰 이유는 개발 속도입니다. 싱글 테넌시는 본질적으로 이해하기 쉽고 유지 관리가 더 간단합니다. 대기열, 함수 또는 S3 버킷을 보면 해당 책임과 범위(역할과 권한의 범위)가 단일 테넌트에 적용된다는 것을 100% 확실하게 알 수 있습니다. 이러한 리소스가 실수로 다른 테넌트의 엔드포인트에 연결되거나, 다른 테넌트의 리소스에 액세스하거나, 다른 테넌트의 구성을 사용할 위험이 없습니다. 또한 싱글 테넌시를 사용하면 복잡한 라우팅 로직을 피할 수 있습니다. 리소스가 이벤트를 수신하면 처리 경로가 하나만 존재합니다. 발신자/수신자 및 해당 권한에 따라 조건부 로직을 구축하거나 유지할 필요가 없습니다.
사실, 이를 통해 우리는 배포 시점에 한 번 설정되고 변경할 수 없는 설정을 가진 상태 없는 자원을 만들 수 있습니다. 이 자원들은 외부 컨트롤 플레인에 대한 의존성이 없습니다. 람다 환경 변수에서 이를 잘 보여주고 있는데, 모든 소비자는 다른 BACKOFF_RATE를 가질 수 있으며, 이는 각각의 단일 테넌트 스택에 단순히 내장되어 있습니다.
테넌트의 애플리케이션 코드를 업데이트하고 그 행동을 확인한 후에 다음 테넌트로 넘어갈 수 있습니다. 이렇게 하면 '대규모' 배포가 한 번에 모든 테넌트를 마비시킬 위험이 크게 줄어듭니다. 또한, 하나의 잘못 행동하는 테넌트가 다른 테넌트에 미치는 영향이 적습니다 - 싱글 테넌시는 시끄러운 이웃 문제를 최소화합니다.
싱글 테넌시의 단점
싱글 테넌시 방식을 통해 우리는 시장에 빠르게 진입할 수 있었습니다. 거기에서 우리는 사용자의 요구 사항에 기반하여 빠르게 성장하고 로드맵을 조정할 수 있었습니다. 그러나 우리는 해결해야 할 여러 제약 사항에도 직면했습니다. 이 제약 사항들은 크게 두 가지 범주로 나눌 수 있습니다:
- AWS 계정에서의 최대 리소스 수
- AWS 서비스에서의 최대 동시 작업 수
첫 번째 제한 중 하나는 IAM 역할의 최대 갯수 였습니다. AWS 계정은 5000개 이상의 역할을 가질 수 없으며, 표준 관행은 리소스별로 고유한 역할을 권장합니다 (예: 상태 머신 / 람다 함수). 저희는 560개의 스택을 사용하기 때문에 , 스택당 최대 8개의 IAM 역할로 제한해야 합니다. 우리가 이 제한에 거의 도달했을 때, 우리는 최소 권한 원칙을 희생하면서 일부 역할을 통합해야 했습니다. 이제 일부 람다 함수들은 동일한 역할과 권한을 공유하고, 때때로 실제로 함수가 필요하지 않은 리소스에도 접근이 가능했습니다.
동시 작업의 최대 수는 더 복잡한 문제입니다. 애플리케이션 업데이트가 있을 때, 우리는 Canary stack의 작은 수를 통해 기능을 확인합니다. 그리고 그 결과가 Healthy할 때, 다른 스택 대부분을 병렬로 업데이트합니다. 그런데, 약 100개의 스택에 도달했을 때, 이것이 예상치 못한 문제를 만났습니다. 즉, CodeBuild 컨테이너의 최대 동시 수, API Gateway의 최대 배포 속도, CloudFormation에서의 최대 동시 스택 작업 수, 람다에서의 초당 최대 컨트롤 플레인 오퍼레이션 요청 수에 관한 문제가 발생되었습니다.
람다 제한은 특히나 풀기 어려운 문제였습니다. 우리는 동시에 100개의 CloudFormation 스택을 업데이트할 수 있었으며, 각각의 스택은 여러 람다 함수를 가지고 있었습니다. 모두 동시에 업데이트하면 초당 15개의 람다 컨트롤 플레인 요청의 최대치를 넘어서기 때문에, CloudFormation의 UPDATE_FAILED 상태가 발생했습니다. CloudFormation은 롤백 모드로 들어갔지만, 롤백도 람다 함수를 업데이트하면서 추가적인 제한에 직면하고, 결국 UPDATE_ROLLBACK_FAILED 상태에 이르렀습니다.😱 거기에 있었던 사람은 그 고통을 알 것입니다.
결국 우리는 배포의 동시성을 50으로 제한함으로써 이러한 모든 문제를 완화할 수 있었습니다. 이를 달성하기 위해 우리는 외부 및 내부 Step Functions 상태 머신을 사용합니다. 내부 상태 머신은 실제 CDK 배포와 관련된 모든 작업을 수행합니다. 외부 상태 머신은 외부 시스템에 의해 트리거되며, 먼저 내부 상태 머신의 실제 동시성을 확인합니다. 만약 50을 초과하면, 슬롯이 사용 가능할 때까지 백오프하고 재시도합니다.
동시성을 제한하는 것으로 모든 AWS 의 rate limiting 문제를 해결했지만, 분명히 배포 속도는 길어졌습니다. 실제로, 큰 업데이트는 500개의 스택을 배포해야 할 수도 있는데, 최대 동시성은 50이므로, 배포는 대략 10개의 wave로 나뉘어야 하고, 각 wave의 완료 소요 시간은 최대 5분이므로, 전체 재배포는 거의 한 시간까지 걸릴 수 있습니다.
멀티 테넌시 솔루션
우리는 싱글 테넌시 구조가 한계에 도달했다는 것을 알고 있습니다. IAM Role 제한은 우리가 넘을 수 없는 한계를 보여주는 분명한 예입니다. 또 다른 한계의 예시로 CloudFormation 스택의 최대 수 또는 CloudWatch 경보 및 메트릭의 양을 들 수 있습니다. 싱글 테넌시로 더 이상 충분하지 않다면 멀티 테넌시가 논리적으로 다음 솔루션이 될거라 생각합니다.
이 설계에서, 우리 고객들은 더 이상 전용 인프라를 가지고 있지 않습니다. 대신, 단일 입구 서비스가 모든 이벤트를 받고, 단일 출구 서비스가 모든 이벤트를 전달하는 역할을 합니다.
- 멀티 테넌트 솔루션의 주요 이점은 다음과 같습니다:
- 리소스의 감소
- 동시성 제한에 도달할 가능성 감소
- 배포 속도 증가
그러나, 이것은 새로운 도전과제도 함께 가져옵니다:
- 서비스는 더 이상 배포 시점에서 설정될 수 없습니다. 대신, 실행 시간에 최신 설정을 푸시 또는 풀 메커니즘을 통해 검색해야 하며, 이를 로컬 상태에 유지해야 합니다.
- 영향 범위가 크게 증가합니다. 특히:
- 배포
- 보안
- 제한(Throttling) (시끄러운 이웃들)
- 서비스의 복잡성이 증가하고, 따라서 버그의 위험도 증가합니다.
모든 것은 타협의 문제 입니다
아키텍처에 관한 글은 타협에 대해 이야기하지 않고는 완성될 수 없으며, 이 글도 예외는 아닙니다. 우리가 위에서 배운 바와 같이, 단일 테넌트와 멀티 테넌트 솔루션 모두 장단점이 있습니다. 순수하게 기술적인 관점에서 보면, 멀티 테넌트 시스템이 선호되는 접근 방식입니다. 이들은 확장 가능하며 부작용이 제한적입니다. 실제로, 모든 단일 AWS 서비스는 멀티 테넌트 솔루션이며, 그들이 운영하는 규모를 달성하기 위한 다른 방법은 단순히 없습니다.
그러나 우리 모두가 AWS는 아니며, 모든 시스템이 백만 명의 사용자를 위해 설계될 필요는 없습니다. 우리의 경우, 단일 테넌트는 최소한의 노력으로 훌륭한 테넌트간 격리를 달성할 수 있게 해주었습니다. 또한, 시장 진입 시간을 크게 단축시키고 가능한 한 빨리 제품 적합성을 찾을 수 있게 해주었습니다. 이런 접근은 우리를 현재의 위치에 데려다주었고, 이제는 다음 단계로 진화할 시간입니다.
이 차트는 싱글 테넌시와 멀티 테넌시 사이의 (비과학적인) 관계를 보여줍니다. 이는 스펙트럼의 양 끝을 선택하거나 그 사이의 모든 것과 관련된 타협(Trade-off)을 시각화합니다. 어디에 속하는지는 오직 당신만이 결정할 수 있습니다. 테넌트의 수가 항상 제한될 것이라고 확신할 수 있다면, 차트의 왼쪽에 머무를 수도 있습니다. 짧은 시간 내에 많은 테넌트를 기대한다면, 오른쪽으로 이동해야 합니다.
이 선택은 이분법이 아닙니다
이 글에서 우리는 싱글 테넌트 아키텍처와 멀티 테넌트 아키텍처를 대비시켰습니다. 이것들은 스펙트럼의 두 극단입니다: 한쪽 끝에서는 모든 테넌트가 자신의 인프라를 가지고 있고, 다른 한쪽 끝에서는 모든 인프라가 공유됩니다. 우리가 다룬 바와 같이, 양쪽 끝은 모두 장단점을 가지고 있습니다. 멀티 테넌트 솔루션의 가장 중요한 단점은 사고의 광범위한 영향 가능성입니다.
다행히도, 스펙트럼 중간에도 솔루션이 있습니다. 이러한 아키텍처는 멀티 테넌트 시스템의 확장성과 단일 테넌트 시스템과 관련된 격리를 균형있게 조화시킵니다. 예시로는 지역적(regional), 영역적(zonal), 계층적(tiered) 또는 세포 기반 아키텍처 등이 있습니다. 이러한 솔루션들은 모두 같은 기본 접근 방식을 따릅니다; 그들은 큰 멀티 테넌트 시스템을 작은 조각으로 나눕니다. 이러한 조각들은 데이터센터 내의 특정 랙이나 심지어 물리적 서버처럼 매우 작을 수도 있고, 완전히 별개의 두 지역처럼 매우 클 수도 있습니다. 크기에 상관없이, 이러한 작은 조각들은 잘 구현된 격리 및 일관성 조치를 가진 멀티 테넌트 시스템이어야 합니다.
EBE의 경우, 우리는 계층적 아키텍처를 고려하고 있습니다: 3등급은 내부 테넌트로, 순전히 테스트 및 검증을 위한 것입니다. 2등급은 미션 크맅티컬하지 않은 테넌트를 포함하고, 1등급에는 미션 크리티컬한 운영 워크로드가 포함됩니다. 이러한 각 계층은 다양하고 널리 변화하는 테넌트 구성을 다루는 멀티 테넌트 서비스입니다. 모든 배포는 먼저 상위 계층을 통과하고, 안정성이 입증되면 다음 계층으로 승격됩니다. 결과적으로 훨씬 더 작은 영향 범위와 훨씬 더 나은 격리를 제공하지만, 약간 더 많은 리소스와 느린 배포 시간을 대가로 합니다.
결론
이 글에서 우리는 싱글 테넌트와 멀티 테넌트 솔루션의 장단점을 다뤘습니다. 우리는 싱글 테넌트가 빠르게 시작하기에 좋은 옵션이지만, 명확하고 고정된 성장 한계가 있음을 보았습니다. 고객 수가 제한적일 것이라는 것을 알고 있다면, 싱글 테넌트는 장기적인 해결책이 될 수 있습니다.
멀티 테넌트 아키텍처는 무제한적인 규모가 필요한 서비스에 선호되는 솔루션입니다. 멀티 테넌트는 클라우드 제공업체가 설정한 리소스 제한 내에서 많은 고객을 서비스하는 유일한 방법입니다. 그러나 멀티 테넌트 시스템은 본질적으로 더 복잡하며, 이는 개발 속도와 운영 부담에 영향을 미칩니다. 또한 사고의 잠재적 영향 범위를 증가시킵니다.
Zonal 또는 Cellular 디자인을 멀티 테넌트 시스템에 적용함으로써 영향 범위를 줄일 수도 있습니다. 이는 사고의 영향을 제한하고, 개발자가 가장 중요한 시스템에 문제가 도달하기 전에 문제를 감지할 수 있게 해줍니다. 그러나 이는 추가적인 복잡성을 추가하며, 멀티 테넌트에서 얻은 일부 이점을 감소시킵니다.
결국 어떤 솔루션이 귀하의 애플리케이션에 가장 적합한지는 완전히 여러분의 비즈니스 맥락, 애플리케이션의 생명주기 단계, 그리고 이해관계자가 설정한 우선순위에 달려 있습니다. 이 글이 정보에 입각한 결정을 내리는 데 도움이 되길 바랍니다.
의견을 남겨주세요