Canva는 어떻게 수억 건의 사용자 세션을 빠르고 안전하게 유지할까?
요약
Canva는 수억 명의 사용자 세션을 검증하는 과정에서 발생하는 MySQL 병목 현상을 S3 기반의 바이너리 청크 방식으로 해결했습니다. 12시간의 취소 기록을 30분 단위의 압축된 바이너리 객체로 관리하여 데이터베이스 부하를 줄이고 메모리 사용량을 87.5% 절감했습니다.
핵심 포인트
- MySQL의 대규모 읽기 부하를 S3의 압축 바이너리 다운로드로 전환
- 16바이트 압축 포맷과 이진 탐색을 통한 효율적인 메모리 내 세션 검증
- 30분 단위 슬라이딩 윈도우 청크 구조로 데이터 관리 최적화
- 메모리 사용량 87.5% 감소 및 단일 워커 초당 2,000건 쓰기 달성
- Canva는 초당 수십만 건의 요청이 쏟아지는
수억 명의 사용자 세션을 검증하면서, 배포 때마다 게이트웨이 수백 개가 MySQL에서 100만 건 이상의 취소 기록을 읽는 병목을 해결함 - 최근 12시간의 기록을
30분 단위 S3 객체로 나누고, 각 기록을 16바이트로 압축·정렬해 게이트웨이가 내려받은 바이트 배열에서 직접 이진 탐색하도록 설계함 - 비동기 워커가 신규 취소 기록을 배치로 반영하고, S3
조건부 PUT과 낙관적 동시성 제어로 여러 워커 사이의 갱신 유실을 방지함 - 게이트웨이는 시작할 때 필요한 청크만 내려받고 조건부 GET으로 변경된 최신 청크를 갱신해, MySQL이 전체 플릿에
10억 행 이상을 제공하던 부하를 S3의 압축 바이너리 다운로드로 전환함 - 전환 후 배포가 빨라지고 데이터베이스 읽기 복제본은 이중화용 2개만 남았으며, 메모리 사용량은
87.5% 감소하고 단일 워커도 초당 2,000건 이상의 쓰기 처리량을 달성함
기존 세션 취소 구조의 병목
-
Canva의 모든 백엔드 요청은 로그인 사용자를 식별해야 하며, 전체 규모에서는 이 검사를
초당 수십만 번 수행함 -
브라우저 쿠키에 사용자 ID, 권한, 역할 등의 세션 정보를 암호화해 저장하므로, 게이트웨이는 요청마다 네트워크 데이터 저장소를 조회하지 않고도 해당 정보를 신뢰할 수 있음
-
로그아웃이나 권한 변경이 발생하면 기존 쿠키를 거의 실시간으로 취소하거나 갱신해야 하므로, 각 게이트웨이가
취소된 세션 기록을 보유함 -
요청 경로에서는 네트워크 데이터 저장소보다 빠르고 안정적인 메모리 조회를 사용함
-
세션 쿠키가 주기적으로 갱신되므로 메모리에는 최근
12시간의 취소 기록만 저장함 -
갱신 대상 토큰은 항상 MySQL에서 확인하므로 메모리 캐시에만 의존하지 않음
-
게이트웨이 수백 개가 시작할 때 각각 100만 건 이상의 취소 기록을 MySQL에서 가져오면서 배포가 데이터베이스를 동시에 압박함
-
읽기 복제본을 대량 추가하면 일시적으로 완화할 수 있지만 지속 가능한 해법은 아니었음
-
배포 속도를 늦추지 않으면서 MySQL 부하와 메모리 캐시 크기를 함께 줄여야 했음
Redis 대신 S3를 선택한 이유
-
데이터베이스와 게이트웨이 사이에 Redis를 두는 방안을 검토함
-
각 게이트웨이가 시작할 때 Redis에서 전체 데이터셋을 받고 이후 신규 취소 기록을 주기적으로 폴링하는 구조였음
-
Redis는 일반적으로 완전한 내구성 구성으로 배포되지 않으며, 별도 클러스터 운영과 캐시 일관성 관리가 필요함
-
데이터 저장소 문제를 다른 저장소로 옮기면서 복잡성까지 늘어나는 선택이었음
-
필요한 조건은
강한 내구성 보장과 대량 데이터의 효율적인 읽기였으며, 크고 내구성 있게 저장된 파일을 저비용으로 내려주는 S3가 이에 부합함 -
세션 취소 데이터는 정적 바이너리 객체가 아니라 시간이 흐르면서 범위가 이동하는
슬라이딩 윈도우이므로, 객체 저장소에 맞는 변환이 필요했음
30분 청크와 16바이트 바이너리 형식
-
12시간 슬라이딩 윈도우를
30분 구간으로 나누고 각 구간을 하나의 S3 객체로 저장함 -
게이트웨이는 최신 청크만 가져오므로 오래된 취소 기록을 S3에서 개별 삭제할 필요가 없음
-
전체 다시간 윈도우 대신 작은 30분 블록만 갱신할 수 있음
-
다만 취소 기록 하나를 추가하려면 수십만 건이 담긴 청크를 내려받아 다시 올릴 수 있음
-
각 취소 기록은 적용 대상인
principal과 취소가 적용되는 로그인 시각의 상한을 포함함 -
일반적으로 특정 시각 이전에 시작된 모든 세션을 취소하는 방식임
-
비트 단위 조작으로 두 정보를 16바이트에 담음
-
청크는 16바이트 원소로 구성된 평면 배열이며, principal 기준으로 정렬해 특정 대상의 취소 기록을
이진 탐색할 수 있음 -
게이트웨이는 청크를 다른 표현으로 변환하지 않고 내려받은 바이트에서 직접 검색함
-
캐시 적재 비용은 사실상 S3 다운로드뿐임
-
기록마다 여러 Java 객체를 만들던 방식보다 메모리 사용량이 8분의 1로 줄어듦
-
단순 로그아웃 외에도 여러 취소 유형을 지원함
-
로그아웃 없이 쿠키에 캐시된 일부 정보만 무효화할 수 있음
-
단일 사용자가 아니라 전체 브랜드를 대상으로 지정할 수 있음
-
일부 비트를 플래그로 예약하되 principal 순서를 유지해 여러 취소 유형을 같은 평면 배열에 저장함
비동기 워커의 청크 갱신
-
취소 기록이 생길 때마다 S3 청크를 다시 쓰는 비용을 피하도록
비동기 워커가 갱신을 담당함 -
데이터베이스를 계속 스캔해 아직 S3에 업로드되지 않은 취소 기록을 큰 배치로 가져옴
-
최신 청크를 내려받거나 충분한 시간이 지났다면 새 청크를 생성함
-
신규 기록을 정렬 배열에 삽입한 뒤 청크를 다시 업로드함
-
고가용성과 배포 편의를 위해 워커를 여러 개 실행하지만, 단순 구현에서는 서로의 변경을 덮어써 취소 기록이 사라질 수 있음
-
S3
조건부 PUT으로 낙관적 동시성 제어를 구현함 -
청크를 처음 읽은 뒤 변경되지 않았다는 사전 조건을 모든 갱신에 적용함
-
새 청크를 만들 때도 다른 프로세스가 같은 객체를 먼저 생성하지 않았는지 검사함
-
각 읽기-수정-쓰기 작업이 S3의 취소 기록 집합에 항목을 추가하기만 하도록 보장해, 실행 순서가 섞여도 데이터를 잃지 않음
리더 선출과 정확성 보장
- ZooKeeper
리더 선출로 워커 사이의 지속적인 충돌과 추가 부하를 줄임 - 리더 선출은 최적화 수단일 뿐 정확성을 보장하는 장치로 사용하지 않음
- 한 노드가 쓰기 직전에 임의의 시간 동안 멈출 수 있음
- 그사이 다른 노드가 새 리더가 되어 변경을 기록할 수 있음
- 멈췄던 노드가 깨어나 과거 상태로 덮어쓰지 못하도록 조건부 PUT이 최종 안전장치가 됨
이론적 복잡도와 실제 처리량
- 고정 크기 배치를 추가할 때마다 전체 청크를 처리하므로, 취소 기록
N
개로 청크를 구성하는 시간은 대략 O(N²)
임
- 수평 확장을 단순 적용하면 갱신 유실 문제가 다시 발생하므로 확장도 쉽지 않음
- 실제로는 배치마다 수백 건을 처리해 최적화되지 않은 구현도
초당 2,000건 이상의 쓰기 처리량을 달성함 - 이 처리량은 예측 가능한 미래에 필요한 수준을 넘어섬
게이트웨이의 청크 다운로드와 갱신
-
각 청크에는 특정 30분 동안 생성된 모든 취소 기록이 들어 있음
-
모든 토큰이 12시간 안에 갱신되므로 게이트웨이 시작 시 최근
12시간에 해당하는 청크만 내려받음 -
객체 이름에 30분 구간의 시작 시각을 인코딩해, 기준 시각 이후의 키를 정렬 순서로 효율적으로 찾음
-
배포 때만 신규 기록을 받는 상황을 피하도록 S3
조건부 GET으로 실제 변경된 최신 청크만 다시 내려받음 -
30분 구간에 취소 기록이 100만 건이어도 청크 크기는 16MB임
-
이를 분당 몇 차례 전송해도 게이트웨이가 중계하는 전체 요청·응답 데이터에 비하면 작은 규모임
-
청크 구간의 종료 시점이 12시간보다 오래되면 메모리 캐시에서 바로 제거함
-
게이트웨이마다 여전히 같은 수의 취소 기록을 받지만, MySQL이 전체 플릿에 10억 행 이상을 제공하는 대신 S3에서 압축 바이너리를 내려받음
-
게이트웨이 하나에는 수십 MB지만 전체 플릿에서는 수십 GB에 해당함
운영 결과와 설계 교훈
-
새 시스템으로 이전한 뒤
배포 속도가 개선됐고, 세션 취소 데이터베이스의 읽기 복제본은 이중화를 위한 2개만 유지함 -
Java 힙에 취소 기록마다 여러 객체를 만들지 않으면서 캐시 메모리 사용량이
87.5% 감소함 -
이전 데이터베이스 부하는 지난 수시간의 쓰기 수와 동시에 캐시를 받는 게이트웨이 수에 좌우됐지만, 새 구조에서는 쓰기 처리량과 전체 사이트 트래픽에 따라 예측 가능하게 증가함
-
단일 워커가 수십만 건을 계속 정렬하는 구조도 실제 요구량을 충족했으며, 조밀한 배열을 처리하는 현대 시스템에서는 상수 계수가 크게 작용함
-
수십만 건이 한 배열에 있어도 워커의 병목은 계산보다
네트워크 지연 시간이었음 -
여러 분산 시스템 설계의 모의 구현을 실제 클라우드 인프라와 예상 규모에서 시험해 확장성 요구를 충족하는 방식을 선택함
-
개념 증명 이후 광범위한 종단 간 테스트와 신중한
사람의 검토를 거쳐 프로덕션 버전의 사용자 안전성을 확인함
댓글과 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기