OpenAI Habitat 스토리지 아키텍처: ChatGPT 10억 주간 사용자와 70M RPS를 버틴 방식
OpenAI가 공개한 Habitat 스토리지 글은 모델 성능 발표보다 실무 개발자에게 더 쓸모가 있다. ChatGPT, Codex 설정, 로그인, 대화 시작 같은 제품 기능은 겉으로는 “AI 응답”처럼 보이지만 실제로는 수많은 온라인 데이터 조회 위에서 돌아간다. OpenAI는 Habitat이 주간 10억 명 이상이 쓰는 제품을 지원하고, 거의 40개 리전에서 초당 7천만 건 이상의 요청을 처리하며, 500PB 이상 데이터를 다룬다고 밝혔다. 숫자가 크다는 감탄으로 끝낼 일이 아니다. 이 글의 핵심은 빠르게 커지는 제품에서 데이터 접근 계층을 어떻게 중앙화하고, Python 서비스의 꼬리 지연을 어떻게 다루며, 복잡한 쿼리를 의도적으로 제한했는가다.
왜 스토리지 계층이 AI 제품의 체감 속도를 결정하나
AI 제품의 응답 시간은 모델 추론 시간만으로 결정되지 않는다. 사용자가 대화를 열면 권한, 설정, 이전 대화, 워크스페이스 정책, 도구 연결, 암호화 상태, 캐시 여부 같은 데이터가 연쇄적으로 조회된다. 요청 하나가 수십~수백 번의 내부 데이터 접근을 만들 수 있다. 이때 가장 느린 조회 하나가 사용자가 느끼는 지연이 된다.
OpenAI가 Habitat을 만든 이유도 여기에 있다. 제품 엔지니어가 Azure Cosmos DB, 캐시, 암호화, 직렬화, 라우팅, 권한 확인을 매번 직접 다루면 속도도 느려지고 장애도 늘어난다. 초기 Habitat은 Python 클라이언트 라이브러리였다. 제품팀은 간단한 API로 데이터를 저장하고 읽었고, 내부 세부사항은 라이브러리가 처리했다. 작은 조직에서는 이 방식이 빠르다. 문제는 서비스 수가 늘고 프로토콜 변경이 잦아질 때 생긴다.
라이브러리에서 서비스로 옮긴 진짜 이유
중앙 라이브러리는 배포가 어렵다. 예를 들어 특정 데이터셋을 여러 리전의 Cosmos DB 계정으로 나눠 장애 반경을 줄이려면 클라이언트에 라우팅 로직을 넣어야 한다. 그 변경을 수십 개 서비스에 배포하고, 기능 플래그를 켜고, shadowing으로 검증하고, 버그를 고치고, 다시 배포해야 한다. 한 팀이 관련 없는 이유로 예전 클라이언트로 롤백하면 전체 안정성이 깨질 수도 있다.
Habitat을 별도 서비스로 만든 결정은 이 배포 팬아웃을 줄이기 위한 선택이다. 스토리지 접근 로직이 서비스 한 곳에 모이면 관측, 배포, 권한 정책, 감사 로그, 보안 통제를 중앙에서 다룰 수 있다. 특히 AI 에이전트가 내부 도구를 호출하는 환경에서는 “누가 어떤 데이터에 접근했는가”를 한 지점에서 남기는 것이 중요하다. 프롬프트로 막는 보안보다 접근 계층에서 강제하는 보안이 더 강하다.
Python asyncio의 함정: 동시성은 CPU 병렬성이 아니다
OpenAI가 흥미롭게 공개한 부분은 Python을 당장 버리지 않고 서비스화했다는 점이다. Python은 개발 속도가 빠르지만 고처리량 서비스에서는 CPU와 메모리 비용, 네트워크 지연이 불리하다. 그래도 OpenAI는 당시 목표가 비용 최적화가 아니라 제품팀의 속도와 플랫폼 안정성이라고 판단했다. 의도적인 기술부채를 받아들인 셈이다.
문제는 tail latency였다. asyncio는 I/O 대기 중 다른 작업을 진행하게 해주지만 CPU 병렬성을 제공하지 않는다. Habitat은 라우팅, 압축, 암호화, 체크섬, 헬스체크, request shadowing, hedging 같은 CPU 작업도 한다. CPU 작업이 길어지면 이미 저장소 응답이 돌아왔는데도 코루틴이 다시 스케줄링되지 못해 수백 밀리초, 심하면 몇 초가 밀린다. 그래서 OpenAI는 event loop scheduling delay를 별도 지표로 측정했다. 단순 CPU 사용률만 보면 놓치는 병목이다.
작은 설정 하나가 p99를 망칠 수 있다
초기 서비스에서 높은 지연의 원인 중 하나는 feature flag 설정 파싱이었다. Statsig 설정이 1분마다 갱신됐고, jitter 없이 모든 워커가 큰 JSON 파일을 비슷한 시점에 파싱했다. pod당 Python 프로세스를 여러 개 띄운 구조와 결합되자, 매분 일정 순간에 워커들이 사용자 요청 대신 설정 파싱에 CPU를 썼다. 해결책은 거창하지 않았다. 더 작은 대상 설정을 배포하고, 갱신 주기를 늘리고, background task에 jitter를 넣었다.
이 사례는 실무에서 바로 쓸 수 있다. 장애는 늘 복잡한 알고리즘에서만 오지 않는다. cron, config polling, cache refresh, metrics aggregation처럼 “부가 작업”으로 보는 코드가 동일 시점에 몰리면 p99가 튄다. 운영 지표에는 평균 latency보다 p95/p99, event loop lag, worker별 요청 분산, background task duration을 넣어야 한다.
LIFO 커넥션 풀이 만든 metastable failure
OpenAI는 aiohttp TCPConnector의 LIFO connection reuse도 문제로 지목했다. 최근 반환된 연결을 먼저 재사용하는 방식은 일반적으로 효율적이다. 하지만 느린 서버가 burst 이후 늦게 응답하고, 그 연결이 가장 최근에 풀로 돌아오면 다음 요청이 다시 느린 서버로 붙을 수 있다. 부하가 몰린 프로세스에 더 많은 요청이 가고, 더 느려지고, 다시 더 많은 연결이 그쪽으로 가는 피드백 루프가 생긴다.
이건 metastable failure의 전형적인 형태다. 원래 원인이 사라져도 시스템이 나쁜 균형 상태에 머문다. OpenAI는 max connection reuse duration을 제한해 의심을 검증했고, FIFO 재사용으로 피드백 루프를 끊었다. 지금은 Istio와 Envoy를 통해 더 나은 load-aware balancing과 connection pooling을 쓴다고 설명한다.
복잡한 쿼리를 막는 것도 확장성 설계다
Habitat은 강력한 SQL 인터페이스를 제공하지 않는다. OpenAI는 단순하고 예측 가능한 NoSQL API를 노출한다고 밝혔다. arbitrary SQL, 큰 table scan, 복잡한 join은 개발자가 쓰기 쉽지만 운영자는 감당하기 어렵다. 쓰는 비용은 싸고 실행 비용은 비싼 비대칭이 생긴다.
대신 Habitat은 object와 edge를 정의하고 직접 edge를 조회하는 그래프 유사 모델을 제공한다. 복잡한 분석이나 검색은 온라인 저장소에서 직접 처리하지 않고 change data capture를 통해 Rockset 같은 별도 뷰로 보낸다. 온라인 경로는 단순 조회에 집중하고, 무거운 읽기 작업은 격리한다. 이 원칙은 작은 서비스에도 그대로 적용된다. 사용자 요청 경로에서 리포트성 쿼리를 돌리지 말고, 별도 읽기 모델이나 배치 뷰를 두는 편이 낫다.
실무 적용 체크리스트
- 클라이언트 라이브러리 변경이 여러 서비스 배포를 요구한다면 서비스 계층 분리를 검토한다.
- CPU 사용률 외에 event loop lag, worker별 concurrency, p99 이상의 trace를 본다.
- feature flag, config polling, cache refresh에는 주기 분산과 jitter를 넣는다.
- 커넥션 풀이 LIFO로 느린 서버에 부하를 되돌려 보내는지 확인한다.
- 온라인 API는 constant-work 요청을 기본값으로 설계하고, 복잡한 조회는 별도 read path로 뺀다.
- AI 에이전트가 데이터에 접근한다면 프롬프트보다 권한 계층, 감사 로그, 중앙 chokepoint를 먼저 설계한다.
Habitat 글의 교훈은 “OpenAI처럼 70M RPS를 처리하자”가 아니다. 빠르게 성장하는 제품에서는 추상화가 편의 기능을 넘어 운영 통제 장치가 된다는 점이다. 처음에는 라이브러리로 충분하지만, 배포 조율과 보안 통제가 병목이 되는 순간 서비스화가 필요하다. 그리고 Python 같은 익숙한 도구도 지표를 제대로 잡으면 꽤 오래 버틸 수 있다. 단, 평균 latency만 보면서 버티면 안 된다.