메인 콘텐츠로 건너뛰기
OpenAI

2026년 10월 23일

엔지니어링

10억 명 이상의 ChatGPT 사용자를 위한 온라인 스토리지의 빠른 확장(2부)

애플리케이션 스토리지 플랫폼 Habitat에서 Azure Cosmos DB를 최적화해 전례 없는 성장에 대응한 방법을 소개합니다.

글: 기술팀 Jon Lee, Hrachya Hakobyan, Chaomin Yu

로딩 중...

OpenAI의 모든 제품에는 빠르고 안정적인 데이터 접근이 필요하며, 스토리지 플랫폼 Habitat이 이를 제공합니다. Habitat은 40개 지리적 리전에 걸쳐 10억 명이 넘는 사용자에게 밀리초 단위로 응답해야 합니다. 우리 인프라에는 매우 어려운 과제가 주어졌습니다. 폭발적인 성장과 새로운 사용 사례를 지원하면서도 안정성, 성능, 제품 개발 속도를 계속 높여야 했습니다. 이 과제를 해결하려면 온라인 스토리지 플랫폼의 설계와 운영 방식을 다시 생각해야 했습니다. 우리는 끊임없이 절충안을 검토하며 다음 순서로 나아갔습니다. 먼저 핵심 제품 트래픽을 감당할 만큼 안정성을 확보하고, 다음으로 전 세계 사용자에게 충분한 속도를 제공하며, 마지막으로 대규모 환경에서도 능숙하게 운영하는 것입니다.

2부로 구성된 이 시리즈에서는 단순한 클라이언트 라이브러리와 데이터베이스였던 Habitat을 500페타바이트 이상의 데이터를 제공하는 전 세계 분산 스토리지 플랫폼으로 발전시킨 과정을 설명합니다. 1부에서는 Habitat이 Python 클라이언트에서 서비스로 발전한 과정에 초점을 맞췄습니다. 또한 Python 기반 서비스를 많은 사람이 현실적이라고 여기는 한계보다 훨씬 더 확장할 수 있게 해준 기법들을 다뤘습니다.

이번 2부에서는 기반 스토리지 구성 요소인 Azure Cosmos DB를 어떻게 확장했는지 살펴봅니다. Azure 팀과 긴밀히 협력한 끝에, 공동 관리 컨테이너를 활용하는 실용적인 멀티 테넌시 접근법을 마련했고 전 세계 대규모 환경에서도 뛰어난 읽기 성능을 확보했습니다.

그림 01 · Habitat이란?

온라인 스토리지 플랫폼

Habitat은 OpenAI 제품이 필요한 정보에 빠르고 안정적으로 접근할 수 있도록 구축한 온라인 스토리지 플랫폼입니다.

  • 요청
  • 응답
  • 변경 사항(CDC)

클라이언트

온라인 스토리지 플랫폼

스토리지 리소스

  • ChatGPT
  • API
  • Codex
  • 내부 서비스
  • 기타 기능

Habitat

  • 캐싱캐시
  • ACL 정책권한 부여
  • 배치 및 데이터 레지던시데이터 레지던시
  • 암호화데이터 보안
  • 격리멀티테넌시
  • 속도 제한요청 셰이핑
  • 라우팅스키마 조회 · 데이터 레지던시
  • Azure Cosmos DB온라인 스토리지
  • Nanobase온라인 스토리지
  • Valkey캐시
  • Blob 스토리지스토리지 리소스
CDC 서비스변경 데이터 캡처
  • Databricks
  • Rockset
  • Kafka
  • 기타 기능

대규모 환경에서 멀티 테넌시의 안정성 확보하기

기업이 성장하면 인프라팀은 흔히 싱글 테넌트 시스템을 운영하다가 멀티 테넌트 시스템으로 전환합니다. 제품 영역별 팀이 자체 시스템을 유지하는 싱글 테넌시는 장애의 영향 범위를 줄이고, 담당 영역을 명확히 나누며 워크로드를 손쉽게 격리할 수 있게 합니다. 하지만 팀과 제품 영역이 늘어나면 운영 비용이 커집니다. 싱글 테넌시에서는 모든 제품팀이 용량 계획을 책임져야 하며, 상당한 과잉 프로비저닝으로 이어지는 경우가 많습니다.

반대로 멀티 테넌시는 리소스 프로비저닝에 더 풍부한 지식과 경험을 갖춘 중앙 데이터베이스팀에 제어 권한을 부여합니다. 하지만 멀티 테넌시에는 고유한 문제가 있습니다. 예를 들어 한 테넌트가 리소스를 지나치게 많이 소모해 다른 테넌트에 피해를 주는 노이지 네이버 문제가 발생할 수 있습니다.

Habitat에서는 안정적이고 효율적인 대규모 스토리지 시스템을 설계하기 위해 이러한 장단점을 모두 따져봐야 했습니다. 현재 Habitat은 여러 OpenAI 제품의 멀티 테넌트 데이터베이스 계층입니다. 하지만 여기까지 오는 길은 한 바퀴를 도는 여정이었습니다. 하나의 멀티 테넌트 Azure Cosmos DB 컨테이너를 사용하다가 싱글 테넌트 기반 컨테이너를 프로비저닝하는 방식으로 바꿨고, 결국 멀티 테넌트 컨테이너를 기본으로 사용하는 방식으로 돌아왔습니다. Habitat의 멀티 테넌시는 아직 초기 단계입니다. 다양한 사용 사례를 지원하지만, 가능한 한 Azure Cosmos DB의 기본 제공 기능을 활용해 이 영역에 대한 투자를 미룰 수 있었습니다.

Azure Cosmos DB 컨테이너 간단히 알아보기

Azure Cosmos DB를 활용해 온 과정을 살펴보기 전에, 기본 개념부터 짚어보겠습니다.

  • Azure Cosmos DB 계정은 컨테이너 집합과 복제 정책을 정의합니다.

  • 각 컨테이너에는 초당 요청 단위(RU/s)로 일정한 처리량이 프로비저닝됩니다.

  • 각 컨테이너의 데이터는 지정된 논리 파티션 키에 따라 샤딩하는 일반적인 방식으로 저장되며, 논리 파티션 키별 스토리지 및 처리량 한도가 문서에 명시되어 있습니다.

  • Azure Cosmos DB는 논리 파티션 키들을 그보다 수가 적은 기반 물리 파티션에 나누어 배치합니다. 저장량이 임계값을 넘거나 컨테이너가 프로비저닝된 처리량을 유지하는 데 더 많은 물리 파티션이 필요하면, 기존 물리 파티션을 여러 개로 분할합니다.

1단계: Azure Cosmos DB의 기본 기능에 의존

처음에는 클라이언트 부하를 제한 없이 받아들이고, 명시된 논리 파티션 키당 10K RU/s 한도가 백프레셔를 제공하도록 맡겼습니다. 또한 초기에는 계정마다 기본 Azure Cosmos DB 컨테이너 하나만 사용했습니다. 어느 정도 잘 작동했지만, 몇 가지 이유로 문제가 드러났습니다.

논리 파티션 키 한도

초기 API 설계 선택에 따라 클라이언트가 논리 파티션 키를 직접 선택할 수 있게 했습니다. 그 결과 서로 다른 제품 사용 사례에서 user_id나 workspace_id처럼 자주 쓰이는 파티션 키가 빈번하게 충돌합니다. 이러한 사용 사례들이 동일한 논리 파티션 키에 적용되는 Azure Cosmos DB 한도를 공유했기 때문에, 이 설계는 사용자별 전체 처리량을 제한했습니다.

Habitat과 Azure Cosmos DB로 이전하기 전 Postgres를 확장하면서 자주 겪었던 문제 중 하나는, 요청이 집중되는 조직이나 핫 키에서 발생한 대량 요청이 데이터베이스 전체를 마비시킬 수 있다는 점이었습니다. 한 테넌트가 다른 테넌트에 비해 지나치게 큰 부하를 만들어내는 상황입니다. 이런 키를 캐싱하고 캐시 미스 처리에 리스 메커니즘을 도입하는 데 많은 공동 노력을 기울였습니다. 캐시 미스로 발생하는 부하가 기반 데이터베이스를 압도하지 않도록 하기 위해서였습니다.

Figure 02a · Logical partition key limits

Different clients, the same partition key

Cosmos DB containerPhysical partitionPK: user-id-28104CapacityMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedMemory / dreaming : capacity usedConversationsSuccess rateResponses returned in this animation loop—AuthSuccess rateResponses returned in this animation loop—Memory / dreamingSuccess rateResponses returned in this animation loop—

노이지 네이버

문서에 명시된 데이터 크기와 처리량 한도는 논리 파티션 키에 적용되지만, 실제로 한도가 적용되는 단위는 물리 파티션입니다. 즉, 실제로는 논리 파티션이 10,000 RU/s에 도달할 수 없습니다. 대신 같은 물리 파티션에 배치된 다른 모든 논리 파티션 키와 그 물리 파티션의 한도를 공유합니다.

이 때문에 노이지 네이버가 서로 무관한 여러 논리 파티션 키에 영향을 줄 수 있었습니다. 소수만 사용하는 제품 기능의 데이터를 처리하는 워크로드가 핵심 로그인 및 가입 흐름을 방해하기도 했습니다.

Figure 02b · Noisy neighbors

Different users, the same physical partition

Cosmos DB containerPhysical partitionPKs: user-id-28104, user-id-10384Conversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversations user-id-28104: capacity usedConversationsuser-id-28104Success rateResponses returned in this animation loop—Conversationsuser-id-10384Success rateResponses returned in this animation loop—

처리 용량이 낮은 파티션

각 물리 파티션의 처리량 한도도 Azure Cosmos DB 컨테이너에 할당된 총처리량에 따라 달라집니다. 컨테이너에 프로비저닝된 처리량은 해당 컨테이너의 물리 파티션들에 대체로 균등하게 분배됩니다.

데이터양이 늘면 물리 파티션이 분할되어 전체 파티션 수는 증가하지만, 프로비저닝된 총처리량은 그대로 유지됩니다. 그 결과 데이터 크기가 증가할수록 파티션당 실질적인 처리량 한도가 줄어듭니다.

블롭처럼 스토리지를 많이 사용하는 용도에서는 이것이 장애 요인이 됩니다. 요청률과 사용량이 일정해도 저장 데이터가 늘어나는 것만으로 실행 중인 워크로드의 처리 용량이 줄어들기 때문입니다.

Figure 02C · Weak partitions

The limit falls below demand

A partition splits when it reaches its storage limit. Each child receives half the throughput capacity.

Before split: 10K RU/s capacity

After split: 5K RU/s capacity × 2 partitions

RU/s05K6K10K200200429

2단계: 워크로드 격리와 싱글 테넌시 시도

컨테이너를 분리하면 이러한 파티션 키 한도와 노이지 네이버 문제를 부분적으로 해결할 수 있습니다. 별도의 컨테이너를 사용하면 각각에 독립적인 물리 파티션을 할당할 수 있습니다. 따라서 노이지 네이버가 되거나 파티션 키 공간이 겹칠 수 있는 범위가 같은 컨테이너 안의 워크로드로 제한됩니다. 반면 새 컨테이너를 프로비저닝하는 데도 오버헤드가 있습니다. 컨테이너마다 별도로 용량을 계획해야 하므로, 서비스나 사용 사례를 구분하기 위해 별도 컨테이너를 만드는 것이 합리적인 경우에만 컨테이너를 만들고자 했습니다.

강력한 격리가 필요한, 가장 중요한 신규 사용 사례에 별도의 Azure Cosmos DB 컨테이너를 사용하기 시작했습니다. 또한 각 컨테이너에 프로비저닝된 총처리량을 물리 파티션 수에 맞춰 조정하는 스크립트로 처리 용량이 낮은 파티션을 관리했습니다. 이 두 가지를 결합해 당시 겪던 문제를 대부분 피할 수 있었습니다.

하지만 처음에는 기본 컨테이너로 시작했기 때문에, 기존의 중요한 사용 사례 중 상당수는 데이터를 마이그레이션하지 않는 한 그곳에 남아 있을 수밖에 없었습니다. 여전히 공유 기본 컨테이너의 데이터에 의존하는 핵심 서비스를 보호하기 위해, 새 서비스나 주요 제품 기능에는 별도 컨테이너를 만들도록 팀들에 자주 권고했습니다. 규모가 커지면서 기본 컨테이너에 가해지는 부하가 늘었고, 프로덕션 장애도 증가했습니다. 그래서 기본 컨테이너에 부하가 더해지는 것을 막기 위해, 새 서비스나 대규모 제품 기능마다 컨테이너를 프로비저닝하기 시작했습니다.

덕분에 사이트 전체 장애를 많이 피할 수 있었고, 장애의 영향 범위를 개별 기능이나 서비스의 성능 저하로 한정할 수 있었습니다. 하지만 결국 새 컨테이너를 프로비저닝하는 데 따르는 부담이 문제로 드러났습니다.

전체 사용량 증가세의 불안정성

기존 공유 기본 컨테이너에서는 특정 신규 사용 사례의 요청이 0 QPS에서 100만 QPS로 늘어나도 전체 사용량에 큰 변화를 줄 가능성이 낮았습니다. 하지만 격리된 컨테이너에서는 신규 사용 사례가 0에서 100만 QPS로 늘어나는 것이 내부 테스트만 진행하던 기준 부하의 1,000배에 해당할 수 있었습니다.

전용 컨테이너를 사용하면서 각 팀은 출시 시 예상 처리량에 맞춰 컨테이너를 미리 프로비저닝하는 일을 잊지 말아야 했습니다. 제품팀은 사전 확장을 잊은 채 기능을 프로덕션에 배포하곤 했고, 우리는 출시 당일 장애 대응 상황실에서 막판에 컨테이너를 확장하느라 애를 써야 했습니다.

여러 워크로드를 함께 운영하는 것은 멀티 테넌트 시스템의 핵심 이점이지만, 이 컨테이너 전략에서는 더 나은 격리를 얻는 대신 그 이점을 잃었습니다.

Figure 03 · Lack of stable aggregate growth

The same workload, different headroom

1K RU/s during load testing → 1M RU/s at launch

Shared container

100M RU/s capacity · 80% utilized

Load test

1K20M=

0.005%

of headroom

Launch

1M20M=

5%

of headroom

New dedicated container

10K RU/s capacity · no other workloads

Load test

1K10K=

10%

of headroom

Launch

1M10K=

10,000%

of headroom

용량 계획

시간이 지나면서 전용 컨테이너의 사용량을 모니터링하는 데도 트래픽이 언제 바뀌거나 증가할지에 대한 깊은 제품 지식이 필요해졌습니다.

증가 추세에 따라 각 컨테이너의 프로비저닝된 처리량을 늘리는 기본적인 제어 영역 스크립트는 제공할 수 있습니다. 하지만 전체 사용량이 안정적으로 증가하지 않는 상황에서 정확하게 확장하려면, 사실상 OpenAI의 모든 제품팀이 용량 계획에 관여해야 했습니다.

각 서비스 담당팀이 Azure Cosmos DB 프로비저닝을 얼마나 꼼꼼히 관리하는지 편차가 컸기 때문에, 조직 규모가 커질수록 이 방식은 한계에 부딪혔습니다.

3단계: 데이터 이동 기능을 갖추고 멀티 테넌시를 기본으로 사용

조직이 성장하면서 이러한 운영 문제에 대응하며 규모를 확장하기가 어려워졌습니다. 계획을 전담하는 중앙팀이 없다 보니 많은 팀이 계획을 제대로 세우지 못했습니다. 결국 멀티 테넌트 솔루션으로 다시 방향을 돌렸습니다. Azure 팀과 긴밀히 협력해 공유 테넌시 설계를 바탕으로 기존 멀티 테넌시 문제의 해결책을 구현했습니다.

핫 키 스로틀링

요청이 가장 많이 몰리는 키부터 스로틀링하면 노이지 네이버가 특정 물리 파티션의 가용 처리량을 모두 소모하는 것을 막을 수 있습니다. 이를 통해 하나의 핫 키가 사이트 전체에 영향을 미칠 수 있는 노이지 네이버 문제를 완화합니다.

서비스 품질(QoS)

요청을 등급별로 나누는 QoS 역시 대량 삽입이나 데이터 추출 같은 오프라인 워크로드가 특정 물리 파티션의 처리량을 모두 사용하는 것을 막아줍니다. 이러한 워크로드의 요청에 낮은 우선순위를 부여하면 해당 요청이 먼저 스로틀링됩니다. 따라서 최종 사용자를 위한 우선순위가 높은 대화형 요청이 스로틀링되는 것을 방지할 수 있습니다.

서버리스 모드

Azure Cosmos DB에는 프로비저닝 모드에서 겪던 처리 용량이 낮은 파티션 문제를 해결할 수 있는 서버리스 모드가 이미 있었습니다. 하지만 큰 단점이 있었습니다. 스토리지 사용량만을 기준으로 확장하며, RU 사용량이 물리 파티션당 5K RU/s로 제한된다는 점이었습니다. 처리 용량이 낮은 파티션 문제는 해결할 수 있지만, RU를 많이 사용하는 사례를 확장할 수 없게 되는 방식이었습니다.

Azure Cosmos DB 팀이 우리를 위해 개발한 개선된 서버리스 모드에서는 모든 물리 파티션을 최대 10K RU/s까지 확장할 수 있게 되었습니다. 또한 스토리지 사용량 기반 분할에 RU 사용량 기반 분할을 추가해, 더 이상 컨테이너를 수동으로 모니터링하고 확장할 필요가 없어졌습니다.

이러한 변화 덕분에 하나의 공유 컨테이너를 사용하는 방식으로 돌아갈 수 있었습니다. 워크로드 간 격리는 유지하면서, 제품팀이 사전 또는 지속적으로 수행해야 하는 용량 계획 부담을 크게 줄였습니다. 이후 수개월 동안 제품 개발이 더 빨라졌는데도 프로비저닝이나 노이지 네이버 관련 장애는 한 번도 발생하지 않았습니다.

공동 관리 서비스로 안정성 높이기

기업이 OpenAI 규모로 성장하면 관리형 서비스도 더 이상 온전히 ‘관리’해 주지는 못합니다. 많은 워크로드가 클라우드 제공업체의 인프라에서 일반적인 범주를 벗어나기 때문입니다. OpenAI가 Azure Cosmos DB의 최대 사용자 중 하나가 되면서, 성공적인 운영을 위해서는 다른 방식이 필요해졌습니다. 테스트와 대시보드를 공유하고, 엔지니어링팀 간 직접 소통 채널을 마련하며, 우리 규모에서만 나타나는 장애 유형에 공동으로 책임지는 방식입니다. 현재 가장 큰 Azure Cosmos DB 계정은 수십 페타바이트의 데이터를 저장하며 2,000만 QPS 이상을 처리합니다. 우리가 아는 한, 이 규모로 Azure Cosmos DB를 구축한 기업은 OpenAI뿐입니다.

2024년 말, 한 리전의 장애로 이러한 협력이 왜 필요한지 분명해졌습니다. 당시 가장 큰 Azure Cosmos DB 계정 중 하나의 기본 Azure 리전에서 정전이 발생했습니다. 그에 따른 장애 조치 과정에서, 대형 계정들을 보조 리전으로 전환하려면 몇 시간이 걸린다는 사실을 알게 되었습니다. 우리 팀도 Azure Cosmos DB 팀도 이 규모에서는 테스트해 보지 못했던 상황이었습니다.

약 한 달 뒤 해당 계정들을 원래 리전으로 복귀시키는 과정에서 또 다른 심각한 장애가 발생했습니다. 원래 리전으로 연결이 한꺼번에 몰리면서 해당 리전의 Azure Cosmos DB 게이트웨이가 과부하에 빠졌습니다. 게이트웨이 모드 Azure Cosmos DB 클라이언트의 장애 조치 처리 방식에 특이한 동작이 있었기 때문입니다. 일부 물리 파티션만 원래 리전으로 돌아왔는데도 모든 클라이언트가 즉시 해당 리전에 연결하고 요청을 보내기 시작했습니다.

각 장애 때마다 OpenAI와 Azure는 모두 사고 관리 절차를 가동했고, 여러 근본 원인을 찾아 수정 사항을 배포했습니다. 하지만 이러한 장애는 우리 팀과 Azure Cosmos DB 팀의 운영 및 테스트 방식 사이에 존재하는 더 큰 구조적 문제의 징후였습니다. 초기에는 관리형 서비스를 활용해 빠르게 움직이고 비즈니스 목표에 집중할 수 있었습니다. 하지만 규모가 커지면 관리형 서비스에 운영을 맡기는 것만으로는 부족하며, 설계부터 운영까지 모든 단계에서 서비스 제공업체와 긴밀하게 연계해야 합니다.

현재 Habitat과 Azure 엔지니어링팀은 서비스 계층부터 Azure Cosmos DB의 물리 스토리지 계층까지 Habitat의 모든 계층을 공동으로 모니터링합니다. 프로덕션과 같은 규모의 비프로덕션 Azure Cosmos DB 계정을 운영하며 변경 사항을 테스트합니다. SDK를 함께 테스트하고 개선하며, Azure Cosmos DB 계정의 규모를 산정하고 확장하는 모범 사례를 정합니다. Teams를 통해 엔지니어들이 서로 직접 소통하며, 지원 단계를 거치지 않고 상대 팀에 바로 긴급 호출을 보낼 수 있는 수단까지 마련했습니다.

지난 1년 동안 Azure Cosmos DB의 제어 영역과 게이트웨이 확장성을 개선하기 위해 많은 공동 작업을 진행했습니다. Azure Cosmos DB 메타데이터 파티션을 보호하도록 게이트웨이에 부하 보호 기능과 다양한 메타데이터 캐싱 기능을 추가했습니다. 연결 풀링과 연결 재사용 확대를 통해 Python habitat-service 프로세스군이 가하는 부하를 줄였고, 가능한 경우 워크로드를 같은 위치에 배치했으며, 서로 다른 VPC를 사용해 핵심 워크로드를 오프라인 워크로드와 격리했습니다.

이처럼 긴밀한 공동 엔지니어링은 지난 1년간 Azure Cosmos DB의 안정성을 유지하는 데 핵심적인 역할을 했습니다. 규모가 크거나 중요도가 높은 시스템에는 이러한 팀워크가 필요합니다. 담당 영역 간의 벽을 허물고 조직의 경계를 넘어 협력해야 합니다.

전 세계 규모에서 성능 개선하기

1부에서 언급했듯이 우리의 핵심 철학은 제대로 작동하게 만들고, 빠르게 만들고, 비용을 낮추는 것입니다. 안정성이 먼저이고, 그다음은 성능, 마지막은 효율성입니다. Habitat은 OpenAI의 핵심 인프라가 되었으므로, 안정성 확보가 최우선 과제였습니다. 2025년 시스템이 안정화되고 OpenAI가 전 세계로 확장하면서, 사용자와 제품팀에게는 지연 시간이 다음 제약 요인으로 떠올랐습니다.

컴퓨팅과 스토리지 용량이 서로 다른 리전에 있는 경우가 많아 리전 간 요청이 불가피했습니다.

Figure 04 · Improving performance at a global scale

Global traffic

Compute and storage capacity were often located in different regions, making cross-region requests unavoidable.

ComputeStorageIllustrative routes · Longer journeys take more time

컴퓨팅과 스토리지의 지리적 위치는 이해를 돕기 위한 예시입니다.

데이터가 저장된 위치와 요청이 처리되는 위치에 따라 지연 시간은 크게 달라졌습니다. 제품 개발자들은 Habitat 요청의 성능을 높이기 위해 클라이언트 측 캐싱 추가, 요청 일괄 처리, 데이터베이스 읽기·쓰기 최소화, 가능한 작업을 핵심 처리 경로 밖으로 옮기는 방식에 의존했습니다. 덕분에 데이터베이스 문제에 더 잘 견디는 흥미로운 제품 설계도 나왔지만, 제품이 복잡해지고 개선 주기가 느려지는 결과도 낳았습니다. 데이터베이스를 이해할 필요 없이 빠르게 제품을 개발하도록 돕겠다는 Habitat의 목표를 달성하지 못한 것입니다.

2026년에는 이 문제에 집중하면서, Habitat 요청의 90% 이상을 차지하는 읽기 성능을 최적화하기 위한 다층적 전략을 마련했습니다.

용량이 허용하는 한 사용자 가까이에 데이터 저장하기

데이터 레지던시 요구 사항이 있는 사용자를 위해 전 세계 여러 Azure 리전에 Azure Cosmos DB 계정을 운영하고 있습니다. 엄격한 데이터 레지던시 요구 사항이 없는 사용자도 데이터를 가까이에 배치하면 지연 시간이 줄어 더 나은 경험을 누릴 수 있습니다.

하지만 실제로는 물리적인 클라우드 용량이 배치를 제약하기 때문에, 데이터 레지던시가 필요한 경우 외에는 이 방식을 널리 적용하지 못하고 있습니다. 데이터 세트가 워낙 크다 보니, 많은 리전이 자신에게 배정되는 사용자 트래픽 비중에 맞춰 필요한 막대한 스토리지 용량을 수용하지 못합니다.

데이터센터 용량이나 전력, 냉각에 한계가 있는 리전에서는 때때로 직접적인 선택이 필요합니다. 같은 위치의 스토리지에 용량을 확보해 Habitat 지연 시간을 줄일 것인지, 아니면 그 용량을 서비스 제공용 컴퓨팅에 추가로 할당하고 스토리지 요청은 더 먼 곳으로 보낼 것인지 결정해야 합니다.

결국 데이터를 어디에 배치할지는 A) 제품과 사용자 집단별 지연 시간을 최적화하는 것과 B) 가용 스토리지 및 컴퓨팅 용량을 확보하는 것 사이에서 절충해야 하는 경우가 많습니다.

가장 작고 지연 시간에 가장 민감한 데이터 세트만 광범위하게 복제하기

모든 요청에 필요한 인증 메타데이터처럼 지연 시간이 특히 중요한 워크로드에는 보조 리전이 많은 고도로 복제된 Azure Cosmos DB 계정을 사용해 컴퓨팅과 Habitat 사이의 왕복 시간(RTT)을 줄입니다. 일부 계정은 트래픽을 처리하는 Azure 리전 40곳 전체에 데이터를 복제하고, 다른 계정은 대륙마다 최소 한 곳의 리전에 복제합니다.

복제 토폴로지가 서로 다른 계정 간에 데이터 세트를 옮길 수 있도록, 클라이언트가 의식할 필요 없는 데이터 이동 및 라우팅 계층을 계획보다 일찍 Habitat에 구축해야 했습니다.

하지만 이 접근법 역시 궁극적으로 용량 제약을 받기 때문에, 엄선한 소수의 데이터 세트에만 적용하고 있습니다.

나머지 데이터에는 통합 캐싱 활용하기

Habitat 워크로드는 읽기 비중이 압도적으로 높습니다. 애플리케이션이 설정, 상태 및 기타 속성을 끊임없이 가져오며, 자주 접근하는 유형의 비교적 작은 작업 데이터 집합에 트래픽이 크게 집중됩니다. 이런 특성 때문에 이 시스템은 분산 캐싱에 특히 적합합니다. 캐싱은 단일 리전 인프라에서도 성능을 개선하지만, 여러 리전에 걸쳐 운영할 때는 리전 간 지연 시간을 ‘숨기는’ 데 활용할 수 있어 효과가 더 커집니다.

우리는 Habitat에 분산 룩어사이드 캐싱 계층을 통합했습니다. 이 계층은 최근 접근한 객체와 에지의 복사본을 Valkey 서버에 저장합니다. 서버는 사용자 요청이 처리되는 곳과 가까운 인프라 내에 배치됩니다. 일반적으로 이러한 캐시는 TTL을 사용해 적중률과 데이터 노후화 사이에서 균형을 잡습니다. TTL이 짧으면 오래된 데이터를 줄일 수 있지만, 항목이 캐시에서 빨리 만료되어 더 자주 다시 가져와야 하므로 캐싱 효과가 떨어집니다. TTL이 길면 적중률은 높아지지만 오래된 데이터를 더 오랫동안 제공하게 되어, 사용자 경험이 나빠지고 제품 동작을 예측하기 어려워집니다.

또 다른 방법은 애플리케이션이 동일한 원본 데이터에 쓰기 작업을 수행할 때 명시적으로 캐시를 무효화하는 것입니다. 이 방식은 캐시 데이터의 최신성을 크게 높이지만, 정확하게 구현하기가 어려울 수 있습니다. 또한 부분 장애처럼 캐시에 오래된 데이터가 남게 하는 여러 예외 상황도 여전히 존재합니다. 멀티 리전 배포에서는 리전 간 네트워크의 안정성이 낮고 캐시와 기반 스토리지 복제 사이에 경합이 발생하므로 문제가 더 복잡해집니다. TTL을 함께 사용하면 오래된 데이터가 남는 최대 시간을 제한할 수 있지만, 앞서 설명한 TTL의 근본적인 절충 문제는 그대로 남습니다.

우리는 Meta의 TAO 같은 시스템을 참고해 더 발전된 무효화 방식을 택했습니다. Habitat의 통합 캐시는 Azure Cosmos DB의 변경 피드를 통한 변경 데이터 캡처(CDC)를 활용해 수초 내에 전 세계로 무효화를 전파합니다. 이를 통해 데이터 노후화는 최소화하면서 높은 적중률을 달성합니다. 인프라 내의 서비스가 이 변경 피드를 소비하고, 원본 데이터가 갱신되면 로컬 캐시를 무효화합니다. 그러면 다음번에 데이터를 읽을 때 캐시 미스가 발생하고, 최신 버전의 데이터로 캐시를 채웁니다.

Rust로 프록시 오버헤드 줄이기

기존 Python보다 성능이 뛰어난 Rust로 코드를 재작성해 Habitat 서비스 자체의 오버헤드를 제거하면서 지연 시간도 크게 줄였습니다.

이러한 변화가 합쳐져 전 세계 전체 읽기 지연 시간의 p50이 약 120ms에서 약 10ms로 줄었습니다. 올해 초 성능 개선에 집중하기 시작한 이후 12배 향상된 결과입니다.

Figure 05 · Improving performance at a global scale

p50 latency for reads

Three major design changes improved read latency

roughly 120 ms → roughly 10 ms

12x improvement

Milliseconds020406080100120140160시간123
  1. critical data replicated into nearby geos
  2. in-region caching launched
  3. rust rewrite launched

캐싱으로 지연 시간의 중앙값은 크게 줄었지만, 가장 느린 요청은 여전히 캐시에 없는 데이터를 기반 스토리지에서 가져와야 합니다. 먼 리전을 오가야 하는 경우도 많습니다. 추가 용량이 가동되고 스토리지 효율성이 높아짐에 따라, 더 근본적인 과제에 대응할 계획입니다. 처음부터 데이터가 사용될 위치 가까이에 배치하는 것입니다.

변경 데이터 캡처 확장하기

데이터베이스 변경 사항을 다운스트림 시스템에 전파하는 안정적이고 확장 가능한 CDC는 Habitat의 캐싱 전략뿐 아니라 다른 영역에서도 핵심 구성 요소입니다. 수천 개의 컨테이너와 수백만 개의 컨테이너 파티션에서 초당 수백만 건의 변경 사항을 캡처해 100개 이상의 Kafka 토픽으로 전달합니다. 이 데이터는 최종적 일관성을 갖는 보조 인덱스, 데이터 이동 파이프라인, 오프라인 분석 및 기타 실시간 제품 기능도 구동합니다. 기반 인프라가 전 세계로 확장되면서 행 변경 사항을 수신하는 작업도 점점 복잡해졌습니다. 클라이언트가 자신의 데이터 유형에 속한 행이 바뀔 때마다 실행할 콜백을 등록하려면 여러 작업이 필요합니다. 우선 데이터가 어느 Cosmos 계정과 컨테이너에 있는지, 각 리전에서 어떤 CDC 서비스 배포가 해당 컨테이너를 감시하는지 파악해야 합니다. 또한 CDC 서비스 워커군과 Kafka, 자체 소비자의 규모도 적절히 조정해야 합니다. 우리 CDC 플랫폼은 이 모든 복잡성을 제품 개발자에게 보이지 않게 추상화하고, 대신 간단한 인터페이스를 제공하는 것을 목표로 합니다. 클라이언트가 행 변경 시 호출할 함수를 등록하면, 내부 연결과 확장은 우리가 모두 처리합니다.

내부 제어 영역은 필요한 모든 컨테이너의 변경 사항이 캡처되고 있는지 지속적으로 확인하고, 읽을 Cosmos 리전을 선택하며, 여러 지리적 리전에 있는 수천 개의 워커에 작업을 효율적으로 분배합니다. 복제본 리전을 활용하고 피드 읽기의 우선순위를 낮춰, 지연 시간에 민감한 Habitat 트래픽을 백그라운드 캡처 워크로드로부터 보호합니다. 이처럼 큰 시스템에서는 워커 충돌, 컨테이너 문제, 리스 저장소 오류, 잘못된 구성, 대상 시스템 장애가 끊임없이 발생합니다. 우리는 CDC 워커 수명 주기와 리스 처리의 견고성을 높이고, 스트림 간 장애를 격리했으며, 구성 변경과 배포를 위한 안전장치를 마련했습니다. 영구적인 전달 실패가 후속 처리를 막지 않도록 데드 레터 큐를 사용합니다. 리더를 다른 Cosmos 리전으로 전환해 리전 장애에도 대응합니다. 우리 규모에서는 체크포인트 처리 효율을 높이기 위해 Azure의 CDC SDK까지 수정해야 했습니다. 체크포인트를 메모리에 모아두고 주기적으로만 플러시하도록 바꿨습니다.

Habitat 내부의 Azure Cosmos DB 사용 방식은 복잡하지만, 그 핵심 가치는 언제나 제품 개발자가 인프라의 세부 사항을 신경 쓰지 않도록 추상화해 제품 개발 속도를 높이는 데 있었습니다. CDC 플랫폼도 마찬가지입니다.

앞으로의 방향

소규모 팀이 빠르게 성장하는 스토리지 플랫폼을 지원할 수 있었던 것은 스토리지 API 범위를 의도적으로 좁게 유지하고, Python 서비스를 계속 확장하며, 가장 시급한 엔지니어링 과제를 우선했기 때문입니다. 우리는 먼저 핵심 제품 트래픽의 안정성에, 다음으로 전 세계 사용자의 읽기 성능에, 마지막으로 대규모 운영의 효율성에 집중했습니다. 이 접근법을 통해 빠른 성장과 새로운 사용 사례를 지원하면서도 안정성, 성능, 제품 개발 속도를 높일 수 있었습니다.

다음 10억 명의 사용자를 위해 Habitat과 대화 스토리지를 확장하려면, 더 큰 규모로 인프라 전반에 데이터를 복제하는 어려움을 해결해야 합니다. 이에 선제적으로 대응하기 위해, 용량이 큰 Habitat 데이터 유형에 최적화된 엑사바이트 규모의 맞춤형 온라인 스토리지 시스템을 자체 개발하고 있습니다. 현재 개발 중인 이 시스템은 Rockset의 RocksDB Cloud 코어와 Raft 기반 복제를 결합합니다. Azure Cosmos DB와 동일한 문서 모델을 구현하지만, 최대 QPS를 낮추는 대신 분리형 스토리지와 물리적 복제를 사용해 필요한 플래시 용량을 줄입니다.

이처럼 프런티어 규모의 데이터 인프라를 만드는 일에 관심이 있다면 채용 중인 직무를 확인해 보세요⁠.

작성자

OpenAI