Cell-Based Architecture: 한 테넌트의 장애가 전체 SaaS를 삼키지 못하도록 셀 경계로 묶는 법
SaaS를 운영하다 보면 어느 날 갑자기 "특정 고객 때문에 전체 서비스가 느려졌어요"라는 Slack 알림을 받는 순간이 찾아옵니다. 노이지 네이버(Noisy Neighbor) 문제, 잘못된 배포로 인한 전체 장애, 단일 가용 영역 네트워크 문제가 수많은 사용자에게 전파되는 상황. Slack이 셀 기반 아키텍처로 이전하게 만든 장애가 딱 그 케이스였습니다.
Cell-Based Architecture(셀 기반 아키텍처)는 이 문제에 대한 구조적 대답입니다. 장애가 발생하지 않게 막는 것이 아니라, 장애가 퍼지는 범위를 셀 경계로 봉쇄하는 설계입니다. AWS re:Invent 2024의 SaaS meets cell-based architecture 세션을 계기로 이 패턴이 멀티테넌트 SaaS에 왜 그렇게 자연스러운 핏인지를 다시 정리해봤습니다. (링크의 URL 경로에 2024/2025가 섞여 있는데, 세션 자체는 2024년 re:Invent 발표입니다.)
이 글에서는 셀이 정확히 무엇이고 어떤 구성 요소로 이루어지는지, 실제로 어떻게 요청을 셀로 라우팅하는지, 그리고 Shuffle Sharding이 왜 단순 샤딩보다 장애 격리에 강한지를 코드와 다이어그램으로 풀어봅니다.
셀이란 무엇인가 — 완전한 우주를 담은 격리 단위
셀의 정의
셀(Cell)은 요청 하나를 끝까지 처리하는 데 필요한 모든 구성 요소를 자체적으로 보유한 독립 배포 단위입니다. 컴퓨팅(서비스 인스턴스들), 데이터베이스, 캐시, 메시지 큐까지 한 셀 안에 다 들어있습니다.
전통적인 멀티테넌트 아키텍처에서는 모든 테넌트가 같은 데이터베이스와 서비스를 공유합니다. 하나의 테넌트가 DB 커넥션 풀을 고갈시키면 전체가 영향을 받습니다. 셀 기반 아키텍처는 이 공유 지점을 근본적으로 없애는 접근입니다.
테넌트 격리 vs. 셀 격리 — 헷갈리면 안 되는 두 개념
처음 셀 기반 아키텍처를 배울 때 가장 많이 혼동하는 부분입니다.
| 구분 | 테넌트 격리 | 셀 격리 |
|---|---|---|
| 질문 | 누가 무엇을 읽을 수 있는가? | 누구의 장애가 어디까지 영향을 미치는가? |
| 해결 대상 | 데이터 접근 권한, 보안 경계 | 장애 전파, 성능 노이즈 |
| 구현 위치 | 애플리케이션 레이어, IAM | 인프라, 배포 경계 |
| 목적 | 정보 보안 | 가용성 보장 |
두 개념은 보완적이지만 서로 대체하지 않습니다. 셀 격리를 잘 구현해도 애플리케이션 레이어에서 테넌트 간 데이터를 잘못 노출할 수 있고, 반대로 테넌트 격리를 완벽하게 해도 같은 셀 안의 테넌트가 서로의 성능을 갉아먹을 수 있습니다.
왜 지금인가
AWS Well-Architected Framework가 "Reducing the Scope of Impact with Cell-Based Architecture"를 공식 백서로 등재한 것이 하나의 신호였습니다. 과거에는 AWS, Netflix, Amazon 같은 하이퍼스케일 기업만 운영하던 패턴이었는데, Kubernetes 생태계가 충분히 성숙하고 플랫폼 엔지니어링 문화가 퍼지면서 중소 규모 팀에도 확산되고 있습니다.
2025년 6월에는 AWS가 API Gateway 사용자 정의 도메인에 라우팅 규칙 기능을 추가했습니다. MatchHeaders, MatchBasePaths 조건으로 대상 API와 스테이지를 지정하는 방식인데, 이게 사실상 관리형 셀 라우터로 활용 가능합니다. 인프라팀 입장에서는 셀 라우터를 직접 구축하는 비용이 크게 낮아진 셈입니다.
핵심 구성 요소와 라우팅 흐름
Cell Router — 모든 것의 시작점
셀 라우터는 들어오는 요청을 보고 어느 셀로 보낼지 결정하는 인텔리전트 트래픽 디렉터입니다. 라우팅 키는 보통 테넌트 ID, 사용자 ID, 또는 지역 정보를 사용합니다.
아래는 개념적 예시입니다. 실제 프로덕션에서는 DynamoDB나 Redis 같은 분산 저장소를 셀 레지스트리로 사용합니다.
# 개념적 예시 — 실제 프레임워크 API가 아닙니다
from dataclasses import dataclass
from typing import Literal, Optional
Health = Literal["healthy", "degraded", "unhealthy"]
@dataclass
class CellInfo:
cell_id: str
endpoint: str
health: Health
current_load: float # 0.0 ~ 1.0
class CellRouter:
def __init__(self, registry: "CellRegistry"):
self.registry = registry
def route(self, tenant_id: str) -> Optional[CellInfo]:
cell_id = self.registry.get_cell_for_tenant(tenant_id)
if not cell_id:
return None
cell = self.registry.get_cell_info(cell_id)
if cell.health == "unhealthy":
# Shuffle Sharding 환경이라면 같은 테넌트에게
# 할당된 다른 셀 중 healthy 상태인 것을 선택
cell = self.registry.find_healthy_cell_for_tenant(tenant_id)
return cellfind_healthy_cell_for_tenant는 뒤에 나올 Shuffle Sharding 할당 결과(테넌트가 여러 셀에 걸쳐 있을 때)에서 healthy 상태인 셀 하나를 골라주는 메서드입니다. 단일 셀 할당 방식이라면 폴백 대신 회로차단이나 재시도 정책으로 대응해야 합니다.
Cell Registry — 글로벌 메타데이터 저장소
셀 레지스트리는 어느 테넌트가 어느 셀에 있는지, 각 셀의 현재 상태는 어떤지를 관리하는 글로벌 저장소입니다. AWS 환경에서는 DynamoDB가 이 역할에 자주 쓰입니다. 낮은 지연 시간과 높은 가용성이 핵심이기 때문입니다.
# DynamoDB를 셀 레지스트리로 사용하는 개념적 예시
from datetime import datetime, timezone
import boto3
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('CellRegistry')
def get_cell_for_tenant(tenant_id: str) -> dict:
response = table.get_item(Key={'tenant_id': tenant_id})
return response.get('Item', {})
def register_tenant(tenant_id: str, cell_id: str, cell_endpoint: str):
table.put_item(Item={
'tenant_id': tenant_id,
'cell_id': cell_id,
'cell_endpoint': cell_endpoint,
'assigned_at': datetime.now(timezone.utc).isoformat(),
})Shuffle Sharding — 단순 샤딩보다 장애 격리에 강한 이유
기존 샤딩의 한계
일반적인 샤딩은 테넌트를 하나의 셀에 고정 배치합니다. 셀이 4개라면 테넌트는 셀 1, 셀 2, 셀 3, 셀 4 중 하나에 속합니다. 이 경우 셀 1이 장애 나면 셀 1에 있는 모든 테넌트가 그대로 영향을 받습니다. 셀이 N개면 장애 시 영향 비율은 대략 1/N입니다.
Shuffle Sharding의 핵심 아이디어
Shuffle Sharding은 각 테넌트에게 고유한 셀 조합을 할당합니다. 예를 들어 셀이 8개 있고 각 테넌트가 그 중 2개를 사용한다면, 테넌트 A는 셀 1과 셀 3을, 테넌트 B는 셀 2와 셀 5를, 테넌트 C는 셀 1과 셀 7을 사용합니다.
여기서 중요한 건 "테넌트가 서로 셀을 얼마나 겹치느냐"보다 셀 하나가 죽었을 때 완전히 서비스 불가 상태가 되는 테넌트 비율입니다. 위 예에서 셀 1이 죽어도 테넌트 A와 C는 각자 남아 있는 다른 셀(3, 7)로 라우팅되어 부분 가용성을 유지합니다. 두 테넌트가 할당된 모든 셀이 동시에 죽는 경우에만 완전 장애를 겪는데, 셀 8개 중 2개를 뽑는 조합이 C(8,2)=28가지이므로 임의의 두 테넌트가 같은 조합에 배치될 확률은 1/28입니다.
셀 개수 N과 테넌트당 셀 개수 k를 늘리면 조합 수 C(N,k)가 급격히 증가하여, 특정 셀 그룹이 동시에 죽었을 때 그 그룹을 완전히 공유하는 테넌트 수는 매우 적어집니다. 일반 샤딩에서 셀 1개 다운 = 테넌트의 1/N 완전 장애였다면, Shuffle Sharding에서는 그 자리에 부분 성능 저하가 들어서고 완전 장애를 겪는 테넌트 수는 훨씬 작은 값으로 떨어집니다. AWS Route 53 인프라 팀이 Shuffle Sharding을 초기부터 활용해온 이유도 이 지점입니다.
# Shuffle Sharding 테넌트-셀 할당 개념적 예시
import hashlib
from itertools import combinations
def assign_cells_to_tenant(
tenant_id: str,
all_cell_ids: list[str],
cells_per_tenant: int = 2,
) -> list[str]:
"""
테넌트 ID를 시드로 결정론적 셀 조합을 선택하는 예시.
주의: 이 함수는 all_cell_ids의 순서와 원소 집합이 절대 변하지 않을 때만
같은 결과를 보장합니다. 셀이 하나만 추가/제거되어도 combinations 인덱스가
전부 재배열되어 기존 테넌트 할당이 뒤바뀝니다. 프로덕션에서는 이 계산을
최초 프로비저닝 시점에만 수행하고 결과를 레지스트리에 저장한 뒤,
이후 라우팅은 레지스트리 값을 우선 사용해야 합니다.
"""
hash_val = int(hashlib.sha256(tenant_id.encode()).hexdigest(), 16)
all_combos = list(combinations(all_cell_ids, cells_per_tenant))
selected = all_combos[hash_val % len(all_combos)]
return list(selected)
all_cells = ["cell-1", "cell-2", "cell-3", "cell-4",
"cell-5", "cell-6", "cell-7", "cell-8"]
tenant_a_cells = assign_cells_to_tenant("tenant-a", all_cells)
tenant_b_cells = assign_cells_to_tenant("tenant-b", all_cells)실제 사례 — Slack, DoorDash, Netflix는 어떻게 했나
Slack: 셀 이전을 결정하게 만든 지역 장애
Slack은 단일 AWS 가용 영역의 네트워크 이슈가 전체 서비스로 전파되는 장애를 겪은 뒤, Cellular Architecture로의 이전을 결정했다고 엔지니어링 블로그에서 밝혔습니다. 워크스페이스가 자연스러운 격리 경계였기 때문에, 워크스페이스 단위를 독립 장애 도메인으로 분리하는 접근이 도메인과 잘 맞아떨어진 케이스입니다. (일부 글에서 "73시간" 같은 특정 수치가 회자되지만 원문에서 그대로 확인되는 수치는 아니므로, 이 글에서는 사건의 성격 위주로 인용합니다.)
DoorDash: 셀 기반 격리로의 전환
DoorDash 엔지니어링 팀도 단일 대규모 시스템에서 소수의 독립 셀 구조로 전환하는 여정을 블로그로 공유해왔습니다. 각 서비스를 특정 셀 내 Kubernetes 클러스터에 배포하고 존-인식 라우팅을 결합하여 트래픽 불균형과 장애 폭발 반경을 셀 경계 안으로 국한하는 방향입니다. (내부 프로젝트 코드네임까지 특정한 인용은 공식 자료에서 그대로 확인되지 않아 이번 글에서는 사용하지 않습니다.)
Netflix: 지역별·기능별 파티셔닝
Netflix는 지역별, 기능별로 워크로드를 파티셔닝하여 다수의 셀을 운영합니다. 각 셀은 자체적인 비디오, 추천, 텔레메트리 서비스를 가지며, 인프라 장애·트래픽 급증·애플리케이션 버그가 전 세계로 전파되는 것을 구조적으로 방지하는 것이 목적입니다.
세 회사에서 공통적으로 관찰되는 지점은, 셀 이전이 예방적 리팩터링이 아니라 실제 대형 장애 이후 구조적 해결책으로 선택되었다는 사실입니다. 도입 시점을 판단할 때 참고할 만한 신호입니다.
트레이드오프와 안티패턴
장단점 요약
| 항목 | 내용 |
|---|---|
| 장애 격리 | 셀 단위로 장애가 봉쇄되어 전체 서비스 중단 방지 |
| 노이지 네이버 제거 | 서비스 티어별 SLA 보장 |
| 점진적 배포 | 카나리 배포를 셀 단위로 수행, 배포 위험 최소화 |
| 독립 스케일링 | 특정 셀만 수직/수평 확장 가능 |
| 규정 준수 | GDPR 같은 데이터 레지던시 요구사항을 셀 단위로 충족 |
| 운영 복잡도 | 다수 셀을 일관되게 관리하려면 정교한 자동화 필수 |
| 비용 증가 | 셀마다 DB·캐시·서비스가 중복 프로비저닝, 관찰가능성 데이터도 셀 수에 비례해 증가 |
| 크로스셀 데이터 처리 | 전체 테넌트 분석, 교차 셀 리포팅이 가장 어려운 기술적 문제 |
실무에서 자주 만나는 안티패턴
1. 셀 간 데이터베이스 공유
RDS 비용을 아끼자는 명분으로 여러 셀이 같은 DB를 바라보게 하는 순간 모든 격리 이점이 사라집니다. 비용이 아깝다면 PostgreSQL을 Aurora Serverless로 교체하거나 셀 크기 정책을 재검토하는 것이 맞습니다.
2. 동기 크로스셀 호출
셀 A의 서비스가 셀 B의 서비스를 동기적으로 호출하면 두 셀의 장애가 연결됩니다. 크로스셀 통신이 반드시 필요하다면 비동기 이벤트 기반으로 설계하고, 그것도 최소화해야 합니다.
3. 모든 셀 동시 배포
셀이 여러 개면 배포가 빠르겠지라는 착각으로 전체 셀에 동시 배포하면 셀 구조를 만든 의미가 없습니다. 카나리가 의미를 가지려면 하나의 셀에 먼저 배포하고 관찰한 후 순차 롤아웃해야 합니다.
4. 섣부른 도입
소규모 팀, 낮은 트래픽, 단순한 도메인에서는 명백히 오버엔지니어링입니다. 넷플릭스처럼 해야 한다는 동기로 접근하면 운영 부담만 늘어납니다.
셀 크기 결정 — 가장 자주 받는 질문
테넌트가 늘어날 때 새 셀을 추가할지, 기존 셀을 확장할지는 명확한 정답이 없습니다. 일반적으로 고려하는 기준은 이렇습니다.
- 셀당 목표 테넌트 수를 미리 정하고, 이 수에 도달하면 새 셀을 프로비저닝
- 엔터프라이즈 티어 고객은 전용 셀 할당
- AWS EKS 기반 아키텍처에서는 각 셀을 별도 AWS 계정으로 분리하여 IAM, 서비스 한도, 빌링까지 독립적으로 관리하는 패턴을 레퍼런스로 제시
도구 선택 — 어떤 스택이 이 패턴과 잘 맞는가
인프라 프로비저닝: Terraform, Pulumi, AWS CloudFormation으로 셀을 코드로 정의합니다. IaC가 없으면 수십 개의 셀을 일관되게 관리하는 것이 불가능에 가깝습니다.
컨테이너 오케스트레이션: Kubernetes / AWS EKS가 사실상 표준입니다. AWS는 EKS 기반 셀 아키텍처 가이던스를 공식으로 제공하고 있습니다.
트래픽 라우팅: Amazon API Gateway(2025년 라우팅 규칙 기능 포함), Amazon Route 53 DNS 기반 라우팅, 또는 Istio/Linkerd 서비스 메시.
셀 레지스트리: DynamoDB가 낮은 지연 시간과 글로벌 가용성으로 자주 쓰입니다.
관찰가능성: OpenTelemetry로 셀을 넘나드는 분산 추적을 구성하고, Prometheus + Grafana로 셀별 메트릭을 시각화합니다. 셀 수에 비례해 관찰가능성 데이터가 늘어나는 만큼 비용 계획을 미리 잡아둬야 합니다.
마무리
셀 기반 아키텍처의 요체는 장애를 없애는 것이 아니라 가두는 것입니다. 아무리 잘 만든 시스템도 장애는 납니다. 중요한 것은 그 장애가 셀 3에서만 머물고 셀 1, 셀 2의 테넌트들은 멀쩡히 서비스를 이어갈 수 있는 구조라는 점입니다.
이 관점을 지금 운영 중인 시스템에 대입해보면 판단이 뚜렷해집니다. 최근 6개월 동안의 인시던트 리포트를 열어보고, 그중 "한 테넌트/한 배치/한 배포"에서 시작한 장애가 다른 무관한 테넌트까지 물고 늘어진 사례가 몇 건이나 되는지 세어보세요. 셋 이상 발견된다면 이미 팀은 셀 경계가 필요한 규모에 도달해 있는 것이고, 그 사례들이 곧 첫 번째 셀 경계를 어디에 그을지에 대한 힌트가 됩니다. 반대로 그런 사례가 거의 없다면, 지금 필요한 것은 셀 이전이 아니라 테넌트 격리 레이어와 배포 파이프라인의 정돈이라는 신호입니다.
셀은 언젠가 도입해야 할 유행이 아니라, 조직이 마주한 인시던트가 요구할 때 꺼내드는 도구입니다. 그 시점을 데이터로 판단할 수 있게 인시던트 기록과 테넌트별 영향도 지표를 지금부터 남겨두는 것이 가장 확실한 준비입니다.
참고 자료
- AWS Well-Architected: Reducing the Scope of Impact with Cell-Based Architecture
- AWS re:Invent 2024 - SaaS meets cell-based architecture: A natural multi-tenant fit
- Guidance for a Cell-Based Architecture for Amazon EKS (AWS 공식)
- GitHub - aws-solutions-library-samples/guidance-for-cell-based-architecture-on-aws
- Slack's Migration to a Cellular Architecture
- Workload Isolation Using Shuffle Sharding — Amazon Builders' Library
- AWS Architecture Blog: Containers and Cell-Based Design for Resiliency
- Cell-based architectures and Akka
- Cell-Based Architecture: Comprehensive Guide - DZone
- WSO2 Reference Architecture - Cell-Based