Canva가 수억 명의 세션 취소 기록을 S3로 옮긴 이유

Canva는 초당 수십만 건의 요청에서 로그인 세션이 여전히 유효한지 확인해요. 요청마다 데이터베이스를 조회하면 느리고 장애 범위도 커져서, 게이트웨이 메모리에 최근 12시간의 세션 취소 기록을 보관해 왔어요. 문제는 배포 때 수백 개의 게이트웨이가 MySQL에서 100만 건이 넘는 기록을 한꺼번에 읽으면서 생겼어요. Canva는 이 시작 병목을 S3 객체와 16바이트 바이너리 배열로 바꿨어요. 1 2
핵심 요약
| 구분 | 핵심 | 왜 볼 만한가요 |
| 시작 병목 | 게이트웨이 수백 개가 배포 때 MySQL에서 취소 기록을 동시에 읽었어요 | 평소 빠른 인메모리 조회가 배포 순간에는 데이터베이스 부하로 돌아오는 구조를 보여줘요 |
| 저장 구조 | 최근 12시간을 30분 단위 S3 객체로 나누고, 각 기록을 16바이트로 압축했어요 | 객체 스토리지를 대용량 읽기 캐시로 쓰면서 메모리 사용량도 87.5% 줄였어요 |
| 동시성 제어 | 여러 워커가 조건부 PUT으로 같은 청크를 갱신해요 | 리더 선출만 믿지 않고 저장소의 쓰기 조건으로 갱신 유실을 막았어요 |
| 운영 결과 | 데이터베이스 읽기 복제본을 이중화용 2대로 줄였어요 | 부하가 게이트웨이 수보다 취소 기록 쓰기량과 전체 트래픽에 따라 예측 가능하게 늘어요 |
1. 배포 때마다 MySQL로 몰리던 읽기를 걷어냈어요
Canva의 세션 정보는 암호화된 브라우저 쿠키에 들어 있어요. 게이트웨이는 사용자 ID와 권한을 쿠키에서 확인하므로 매 요청마다 네트워크 저장소에 묻지 않아도 돼요. 로그아웃이나 권한 변경이 생기면 해당 쿠키를 즉시 무효화해야 해서, 각 게이트웨이는 최근 12시간의 취소 기록을 메모리에 들고 있어요. 그보다 오래된 토큰은 주기적인 갱신 과정에서 MySQL을 다시 확인해요. 2
인메모리 조회 자체는 빨랐지만 캐시를 처음 채우는 과정이 문제였어요. 배포로 게이트웨이 파드 수백 개가 동시에 시작하면 각 파드가 MySQL에서 100만 건 이상의 기록을 가져갔어요. 읽기 복제본을 늘리면 잠시 버틸 수 있어도, 배포 규모가 커질수록 같은 문제가 반복돼요. Canva는 배포 속도를 늦추는 대신 여러 게이트웨이가 동일한 데이터를 안전하게 내려받을 별도 경로를 만들었어요.
Redis보다 S3가 맞았던 조건
Redis도 중간 캐시 후보였어요. 다만 모든 게이트웨이가 시작할 때 전체 데이터셋을 네트워크로 읽어야 하고, 내구성과 일관성을 갖춘 Redis 클러스터를 별도로 운영해야 했어요. Canva가 필요로 한 건 작은 키를 매우 자주 읽는 저장소보다, 수십 MB의 같은 데이터를 많은 파드에 저렴하고 안정적으로 전달하는 저장소였어요. 이 조건에는 대용량 객체 다운로드와 내구성을 제공하는 S3가 더 잘 맞았어요.
Canva는 12시간짜리 이동 구간을 30분 단위 청크로 나눴어요. 게이트웨이는 시작 시점에서 필요한 최신 청크만 내려받고, 12시간이 지난 청크는 메모리에서 버려요. 객체 이름에 30분 구간의 시작 시각을 넣어 정렬된 키에서 필요한 범위를 찾을 수 있게 했어요. 최신 청크는 조건부 GET으로 변경됐을 때만 다시 내려받아요.
16바이트 배열을 변환 없이 검색해요
취소 기록에는 대상 사용자나 조직을 가리키는 식별자와, 어느 로그인 시각까지 취소할지 나타내는 값이 필요해요. Canva는 플래그를 포함한 이 정보를 16바이트에 담고 정렬된 평면 배열로 저장했어요. 게이트웨이는 S3에서 받은 바이트 배열을 다른 객체로 변환하지 않고 바로 이진 탐색해요.
Java 객체 여러 개로 기록 하나를 표현하던 방식과 비교하면 메모리 사용량이 87.5% 줄었어요. 30분 동안 취소 기록이 100만 건 생겨도 청크 크기는 약 16MB예요. 각 게이트웨이에는 수십 MB 수준이지만 전체 게이트웨이로 합치면 수십 GB가 되므로, 압축된 표현과 대용량 전송 경로가 함께 효과를 냈어요. 2
리더 선출보다 조건부 쓰기가 정확성을 지켜요
비동기 워커는 MySQL에서 아직 반영하지 않은 취소 기록을 묶어서 읽어요. 최신 S3 청크를 내려받은 뒤 새 기록을 정렬 배열에 합치고 다시 올려요. 기록 하나마다 객체를 다시 쓰지 않고 수백 건씩 배치해 처리 비용을 줄였어요. 구현이 충분히 최적화되지 않은 상태에서도 초당 2,000건이 넘는 쓰기 처리량을 냈고, Canva가 예상한 요구량을 넘겼어요.
워커는 가용성을 위해 여러 개가 돌아가요. 두 워커가 같은 청크를 읽고 차례로 덮어쓰면 먼저 반영한 기록이 사라질 수 있어요. Canva는 S3 조건부 PUT에 이전 객체가 읽은 뒤 바뀌지 않았다는 전제 조건을 걸었어요. 새 객체를 만들 때도 다른 워커가 먼저 만들지 않았는지 확인해요. 충돌하면 오래된 내용을 덮지 않고 다시 시도할 수 있어요.
ZooKeeper 리더 선출은 충돌 횟수를 줄이는 최적화로만 써요. 기존 리더가 쓰기 직전에 멈췄다가 늦게 깨어나면, 새 리더가 반영한 내용을 덮을 수 있어요. 조건부 PUT이 마지막 쓰기 경계에서 이런 갱신 유실을 막아요. 조정 시스템은 작업량을 줄이고, 저장소의 원자적 조건은 정확성을 책임지는 역할 분리가 선명해요.
왜 중요한가요
이 사례는 캐시 기술을 제품 이름으로 고르기보다 실제 읽기 모양에 맞춰야 한다는 점을 보여줘요. Canva의 게이트웨이는 같은 데이터 묶음을 배포 시점에 대량으로 읽어요. 이 패턴에서는 객체 스토리지의 내구성, 대용량 전송, 조건부 요청이 Redis의 범용 키 조회보다 유리했어요. 2
정확성도 리더 한 대에만 맡기지 않았어요. 분산 환경에서는 멈췄던 프로세스가 뒤늦게 돌아오는 상황까지 고려해야 해요. 리더 선출로 충돌을 줄이면서 조건부 PUT으로 오래된 쓰기를 거부한 설계는 다른 배치 갱신 시스템에도 적용할 수 있어요.
이론상 청크를 반복 정렬하는 비용이 커 보여도 실제 하드웨어에서 밀집 배열은 빠르게 처리됐어요. Canva 팀은 여러 구현을 실제 예상 규모로 시험했고, 네트워크 지연이 먼저 병목이 된다는 점을 확인했어요. 계산 복잡도만 보고 설계를 버리기보다 데이터 크기, 배치 크기, 메모리 배치와 네트워크 비용을 함께 측정해야 해요.
참고 자료
- Canva는 어떻게 수억 건의 사용자 세션을 빠르고 안전하게 유지할까? — GeekNews
- Session revocations at scale — Canva Engineering
'IT & AI' 카테고리의 다른 글
| 좋은 화면은 10가지 GUI 요소를 제대로 쓰는 데서 시작해요 (0) | 2026.08.05 |
|---|---|
| Netflix가 AI 시대에 시스템 사고자를 더 찾는 이유 (0) | 2026.08.05 |
| FFmpeg 9.0 Lei 공개, GPU 가속과 미디어 형식 지원이 넓어졌어요 (0) | 2026.08.05 |
| Kubernetes 플랫폼 부트스트랩을 GitOps 흐름으로 묶은 kubara (0) | 2026.08.05 |
| MiniMax H3가 ComfyUI에 들어왔어요: 영상·오디오 생성과 로컬 실행의 조건 (0) | 2026.08.04 |