DynamoDB Export/Import + AWS Glue 기반 마이그레이션 수행과 성과
Jayon • Jinyoung Park, Backend Enginner
안녕하세요, 채널톡 백엔드 엔지니어 제이온입니다.
지난 1편, ‘메시지 전송 트래픽 100배에도 끄떡 없는 User 테이블로 뜯어고치기 (1)’에서는 Badge 업데이트가 16억 8천만 레코드의 User 테이블 전체를 느리게 만든 원인을 분석하고, UserBadge 테이블을 분리하기로 결정했습니다. DynamoDB Export/Import와 AWS Glue를 조합한 온라인 마이그레이션 파이프라인도 설계했죠.
그런데 설계와 실행 사이에는 늘 간극이 있기 마련입니다.
설계 단계에서는 “이 정도면 깔끔하게 끝나겠는데? 해치웠나?”라고 생각했지만, 역시나 그런 플래그 뒤에는 다양한 시행착오가 저를 기다리고 있었습니다.
2편에서는 파이프라인을 실제로 수행한 과정과 그 속에서 마주한 기술적 디테일, 그리고 마이그레이션 전후의 비용 및 성능 변화를 구체적인 숫자와 함께 공유합니다.
1편에서 설계한 파이프라인의 전체 흐름을 다시 한번 짚어 보겠습니다.
미리 TmpUserBadge 테이블을 생성해 둔다.
(1차 애플리케이션 배포) User#Badge 필드가 변경되면 TmpUserBadge에 동시에 쓰기 로직을 작성한다.
(1차 마이그레이션) 기존 User 테이블을 S3로 DynamoDB Export하고 AWS Glue ETL 통해 UserBadge 테이블에 넣을 데이터로 변환한다.
(2차 마이그레이션) S3 올라간 User#Badge 데이터를 기반으로 S3에서 DynamoDB Import 기능을 사용하여 UserBadge 테이블을 생성한다. (DynamoDB Import 하면 곧바로 테이블이 만들어짐)
(2차 애플리케이션 배포) User#Badge 필드가 변경되면 UserBadge에 동시에 쓰기 로직을 작성한다.
(3차 마이그레이션) TmpUserBadge에 있던 레코드를 Full Scan하면서 UserBadge에 변경 사항을 반영한다.
(3차 애플리케이션 배포) User 테이블을 바라보던 로직을 UserBadge 테이블을 바라보도록 수정한다. (읽기 & 쓰기)
각 단계별 수행한 과정을 하나씩 공유하겠습니다.
기존 User#Badge 업데이트 로직이 성공한 뒤, TmpUserBadge에 upsert하는 구조입니다. Dual Write 대상이 실패하더라도 원본 User 업데이트에는 영향을 주지 않도록 try-catch로 감싸 두었습니다.
여기서 한 가지 주의할 점이 있었습니다. TmpUserBadge 입장에서는 서로 다른 버전의 User 변경 요청이 순서와 관계없이 도착할 수 있습니다. 예를 들어 User version 5의 Badge 업데이트가 version 3보다 먼저 도착하는 상황이죠.
이를 해결하기 위해 userVersion 속성을 TmpUserBadge에 추가하고, 최신 버전의 변경 사항만 받아들이는 LWW(Last Writer Wins) 전략을 적용했습니다.
ConditionExpression을 통해 기존에 저장된 userVersion보다 새로운 버전일 때만 업데이트가 수행됩니다. 아이템이 아직 존재하지 않는 경우(attribute_not_exists)에는 조건 없이 생성됩니다.
이 LWW 전략은 이후 3차 마이그레이션(TmpUserBadge → UserBadge 병합)에서도 동일하게 사용됩니다.
DynamoDB Export를 실행하려면 먼저 내보내려는 테이블에 PITR(Point-In-Time Recovery)을 활성화해야 합니다. 이후 AWS 콘솔에서 Export 버튼을 클릭하고, S3 버킷 경로를 지정하면 됩니다.
Export가 완료되면 S3 버킷에 다음과 같은 폴더 구조가 생성됩니다.
위 사진에서 AWSDynamoDB 폴더 안에 타고 타고 들어가면 원본 데이터가 담긴 data/ 폴더가 있습니다.
data/ 폴더 내부에는 DynamoDB 아이템을 JSON 형태로 gzip 압축한 .gz 파일들이 나열되어 있습니다.
각 줄이 하나의 DynamoDB 아이템을 나타내는 JSON Lines 형식입니다.
1.8TB가 들어있는 테이블이었지만 gzip 압축 덕분에 약 280GB 수준으로 줄었고, Export 작업은 약 30분 만에 끝났습니다. 무엇보다 RCU를 전혀 소모하지 않았기 때문에 프로덕션 테이블에 영향이 없었습니다.
Export된 데이터에는 User 테이블의 모든 필드가 포함되어 있습니다.
여기서 Badge에 해당하는 필드만 추출하고, DynamoDB Import가 읽을 수 있는 JSON 형식으로 변환하는 작업이 필요합니다. 이 ETL 작업을 AWS Glue로 수행했습니다.
전체 흐름
S3 (DynamoDB JSON 형식, gzip 압축)
↓
Glue DynamicFrame으로 읽기
↓
User 스키마 → UserBadge 스키마 매핑
↓
NULL 필드 제거 + DynamoDB Import 포맷 변환
↓
데이터 품질 검증
↓
S3 (UserBadge Import용 JSON, gzip 압축)
Glue는 Visual 에디터에서 드래그앤드롭으로 파이프라인을 구성할 수도 있지만, 저희는 LLM의 도움을 받아 커스텀 스크립트를 작성하는 방식을 선택했습니다. Glue는 Apache Spark 기반이라 PySpark 문법으로 작성합니다.
위 사진에서 Script 탭을 클릭하면 됩니다.
이제 스크립트의 각 단계를 하나씩 살펴보겠습니다.
1단계: S3에서 DynamoDB Export 데이터 읽기
DynamicFrame은 Glue 전용 데이터 구조로, 스키마가 불완전하거나 필드가 누락된 데이터도 유연하게 처리할 수 있습니다. 16억 8천만 레코드 중 일부 아이템에만 존재하는 필드가 있을 수 있어서 이런 유연성이 중요했습니다.
2단계: UserBadge 스키마 매핑
User 테이블의 수십 개 필드 중 Badge에 해당하는 필드만 추출합니다. DynamoDB JSON은 {"N": "5"}처럼 타입 정보가 포함된 구조이므로, 이를 적절한 타입으로 매핑해 주는 것이 ApplyMapping의 역할입니다.
3단계: NULL 필드 제거
이 단계에서 시행착오를 겪었습니다.
Spark는 아이템에 특정 필드가 존재하지 않으면 해당 값을 NULL로 채웁니다. 예를 들어 user_B에 alert 필드가 없으면 이렇게 됩니다.
id | alert |
|---|---|
user_A | 3 |
user_B | NULL |
문제는 DynamoDB에서는 NULL 값을 저장하지 않는다는 점입니다. 필드가 없으면 그냥 없는 것이지, NULL로 표현하지 않습니다. 이 상태 그대로 Import를 시도하면 Import 자체가 실패합니다.
그래서 레코드를 하나씩 순회하며 NULL이 아닌 필드만 DynamoDB JSON 포맷으로 구성하는 로직을 추가했습니다.
4단계: 데이터 품질 검증
AWS Glue의 Data Quality 기능을 사용해 변환된 데이터에 대한 기본 품질 검증을 수행했습니다. 여기서는 RowCount > 0이라는 단순한 규칙을 사용했지만, DQDL 문법에 따라 다양한 규칙을 정의할 수 있습니다.
이때 dataQualityResultsPublishing.strategy를 BEST_EFFORT로 설정하면, 데이터 품질 규칙이 맞지 않더라도 Job 자체를 중단하지는 않습니다. 추후 Glue 콘솔의 Data Quality 탭에서 품질 검사에 실패한 데이터를 확인할 수 있습니다.
5단계: S3에 저장
변환된 데이터를 gzip 압축된 JSON 형식으로 S3에 저장합니다. 이 파일이 다음 단계인 DynamoDB Import의 소스가 됩니다.
Worker 설정 최적화
Glue ETL을 처음 실행했을 때 타임아웃이 발생했습니다. Worker type을 G 1X, Worker 수를 10으로 설정한 상태였는데, 1.8TB 데이터를 처리하기에는 턱없이 부족했습니다.
Glue Job 설정에서 튜닝한 주요 항목은 다음과 같습니다.
Worker type: G 1X → G 2X로 변경. 대용량 I/O를 더 효율적으로 처리합니다.
Worker 수: 10 → 80으로 증가.
Auto Scaling: 활성화. 비용 효율성을 위해 실제 필요한 만큼만 Worker가 투입됩니다.
Job timeout: 기본값 → 2880분(최댓값)으로 변경. 기본값으로 두었다가 타임아웃이 발생한 경험이 있어서 안전하게 최댓값으로 설정했습니다.
여기서 Worker 수는 어떤 기준으로 잡아야 할까요?
처음에는 적당히 적은 수치로 설정하고 Glue Job을 실행한 뒤, CloudWatch Metrics의 "Maximum Needed Executors" 그래프를 확인합니다. 이 메트릭은 작업을 최적으로 처리하기 위해 실제로 필요한 Worker 수를 보여줍니다.
이 값을 기준으로 Worker 수를 조정하고 재실행하면 비용과 성능 사이에서 적절한 균형을 잡을 수 있습니다.
최종적으로 Glue ETL은 약 30분 만에 완료되었습니다.
이렇게 Glue ETL이 완료되면 5단계에서 설정한 target S3 버킷에 변환된 json.gz이 업로드됩니다.
Glue ETL로 변환된 S3 데이터를 DynamoDB Import로 가져옵니다.
AWS 콘솔에서 "S3에서 가져오기" 버튼을 누르고, 소스 S3 경로와 Import할 파일 형식을 지정합니다.
Import는 새 테이블을 자동으로 생성합니다. S3 데이터를 기반으로 스키마를 자동 감지하기 때문에 별도로 정의할 필요가 없었습니다. Primary Key만 지정하면 됩니다.
Import가 완료되자 UserBadge 테이블이 생성되었습니다. 16억 8천만 레코드가 모두 들어갔고, 약 4시간 30분 정도 소요되었습니다.
이번 마이그레이션에서는 해당되지 않았지만, 나중에 알게 된 내용이 있어서 공유합니다.
DynamoDB Import 시 데이터가 특정 순서로 정렬되어 있으면 Rolling Hot Partition 문제가 발생할 수 있습니다. 정확히는 다음 조건이 모두 충족될 때입니다.
테이블이 PK + SK 구조
하나의 PK에 아이템이 많이 몰려 있음
Import 파일이 PK 순으로 정렬되어 있음
예를 들어 채팅 메시지 테이블(PK: channelId, SK: messageId)을 Import한다고 가정해 보겠습니다.
Export 파일이 channelId 순으로 정렬되어 있다면, channel_001의 10만 건이 특정 물리적 파티션에 순차적으로 쓰이고, 다 끝나면 channel_002의 10만 건이 또 다른 파티션에 몰리는 식입니다.
특정 시점에 하나의 파티션에 쓰기가 집중되면서 쓰로틀링이 발생할 수 있습니다.
이를 방지하려면 Glue ETL 단계에서 데이터를 랜덤하게 섞어주면 됩니다.
UserBadge 테이블은 순차적이지 않은 UUID PK(userId)만 존재하고 SK가 없어서 이 문제가 발생하지 않았습니다. 하지만 PK+SK 구조의 테이블을 Import할 일이 있다면 꼭 기억해 두시면 좋겠습니다.
Import로 UserBadge 테이블이 생성되었으니, Dual Write 대상을 TmpUserBadge에서 UserBadge로 전환합니다.
1차 배포에서 TmpUserBadge를 대상으로 작성했던 코드와 구조가 동일합니다. Dao 클래스명이 TmpUserBadgeDao에서 UserBadgeDao로 바뀌었을 뿐, LWW 전략과 ConditionExpression은 그대로 유지됩니다.
1편에서 동기 Dual Write를 선택한 이유로 "최종 구조로 전환하기 위한 사전 리허설"이라고 언급했는데, 바로 이 단계가 그 리허설입니다. 이미 TmpUserBadge에서 검증된 코드를 그대로 UserBadge에 적용하는 것이므로, 코드 전환 과정에서의 리스크가 적었습니다.
DynamoDB Export 시점과 UserBadge Dual Write 시작 시점 사이에 발생한 Badge 변경분은 TmpUserBadge에만 존재합니다. 이 변경분을 UserBadge에 반영해야 데이터 정합성이 보장됩니다.
TmpUserBadge 테이블을 Full Scan하면서 UserBadge 테이블에 Full Write하는 형태로 진행했습니다. 이때 UserBadge는 2차 배포 이후 Dual Write로 계속 업데이트되고 있으므로, TmpUserBadge의 데이터가 반드시 최신이라고 보장할 수 없습니다. 따라서 여기서도 userVersion을 비교하여 최신 버전의 변경 사항만 받아들이도록 스크립트를 작성했습니다.
TmpUserBadge는 마이그레이션 기간 동안의 변경분만 담고 있었기 때문에 레코드 수는 약 1,100만 건으로, 16억 8천만 건의 원본에 비하면 매우 적었습니다. 서비스에 영향을 주지 않는 테이블이어서 온디맨드로 빠르게 처리할 수 있었고, 약 30분 만에 병합이 완료되었습니다.
애플리케이션에서 User#Badge 관련 조회 및 수정 로직을 모두 제거하고, UserBadge 테이블을 바라보도록 일괄 변경했습니다.
이 배포가 완료되면서 Badge 읽기와 쓰기 모두 UserBadge 테이블에서 이루어지게 되었고, User 테이블은 Badge 트래픽으로부터 완전히 분리되었습니다.
이로써 길고 길었던 온라인 마이그레이션 파이프라인이 종료되었습니다!
마이그레이션을 완료하고 실제 과금과 소요 시간을 비교해 보니 꽤 의미 있는 차이가 나왔습니다.
과금 비교
항목 | AS-IS (java-migration) | TO-BE (Export+Glue+Import) | 차이 |
|---|---|---|---|
DDB 스캔/쓰기 | $366.71 | $0 | -$366.71 |
EC2 | $27.42 | $0 | -$27.42 |
DDB Export | $0 | $183.55 | +$183.55 |
Glue ETL | $0 | $19.93 | +$19.93 |
DDB Import | $0 | $41.93 | +$41.93 |
TmpUserBadge 병합 | $0 | $8.33 | +$8.33 |
합계 | $394.13 | $253.74 | -$140.40 (36%) |
AS-IS의 DDB 스캔/쓰기 $366.71은 User 테이블 Full Scan에 필요한 RCU 비용($11.84)과 UserBadge 테이블 Full Write에 필요한 WCU 비용($354.87)을 합산한 금액입니다. 1.8TB를 500 RCU/초로 스캔하고, 16억 8천만 레코드를 3,000 WCU/초로 쓰는 기준으로 계산했습니다.
TO-BE에서 가장 큰 비용은 DDB Export($183.55)입니다. GB당 약 $0.1 수준으로, 1.8TB 테이블을 통째로 내보내는 비용입니다. Glue ETL은 DPU 시간 기준으로 과금되는데, G 2X Worker 80대로 30분 돌린 비용이 $19.93이었습니다. DDB Import는 압축 해제 기준 용량으로 과금되며, $41.93이 나왔습니다.
시간 비교
작업 단계 | AS-IS (java-migration) | TO-BE (Export+Glue+Import) |
|---|---|---|
메인 테이블 스캔 및 쓰기 | 7일 (168시간) | - |
DDB Export | - | 30분 |
Glue ETL 변환 | - | 30분 |
DDB Import | - | 4시간 30분 |
TmpUserBadge 병합 | - | ~30분 |
총 소요 시간 | 7일 (168시간) | 약 6시간 |
단축률 | - | 96% |
비용 절감도 의미 있었지만, 저희에게 가장 큰 변화는 시간이었습니다. java-migration이었다면 일주일 내내 스크립트를 돌리면서 모니터링해야 했을 겁니다. 중간에 EC2에서 OOM이 발생하거나 코드 결함이 발견되면 처음부터 다시 시작해야 할 수도 있었습니다.
반면 AWS 관리형 서비스 방식은 6시간 만에 끝났고, 중간에 문제가 생기더라도 실패한 단계만 따로 재시도할 수 있었습니다. 예를 들어 Glue ETL에서 타임아웃이 발생했을 때 DDB Export부터 다시 할 필요 없이, Glue Job만 설정을 수정해서 다시 돌리면 되었습니다.
안정성
비교 항목 | AS-IS (java-migration) | TO-BE (Export+Glue+Import) |
|---|---|---|
프로덕션 테이블 RCU/WCU 영향 | 높음 (500 RCU + 3,000 WCU 추가 부하) | Zero (PITR 백업 기반) |
쓰로틀링 위험 | 있음 (마이그레이션 트래픽이 운영 트래픽과 경합) | 없음 |
휴먼 에러 | 높음 (Full Scan/Write 코드 결함, OOM 등) | AWS 관리형 서비스로 최소화 |
실패 시 재시도 | 처음부터 다시 시작 | 실패한 단계만 재시도 |
UserBadge 분리 후 User 테이블의 안정성이 크게 개선되었습니다. 핵심은 메시지 전송 트래픽이 100배, 그 이상으로 증가하더라도 UserBadge 테이블의 프로비저닝 용량만 조절하면 대응할 수 있는 구조가 되었다는 점입니다.
기존에는 User 테이블의 프로비저닝 용량을 아무리 높여도 Badge 트래픽 스파이크에 대응할 수 없었습니다. 두 가지 이유가 있었습니다.
첫째, 트랜잭션 충돌로 인한 WCU 낭비입니다. Badge 업데이트는 ChatSession과 함께 TransactWriteItems로 묶여 있었는데, 충돌이 발생할 때마다 재시도하면서 WCU가 4, 8, 16… 으로 누적되었습니다. 프로비저닝 용량을 높여봤자 충돌로 낭비되는 WCU를 막을 수는 없었습니다.
둘째, GSI Back-Pressure는 프로비저닝 용량과 무관합니다. 1편에서 다루었듯이 GSI 파티션당 1,000 WCU 제한은 테이블의 전체 용량 설정과 상관없이 적용됩니다.
UserBadge를 분리하면서 이 두 가지 제약이 모두 해소되었습니다. 아래에서 하나씩 살펴보겠습니다.
기존 User 테이블은 Badge 외에도 Profile, Type, 태그 등 수정 경로가 다양했습니다. Badge 업데이트 트랜잭션이 이들과 같은 아이템을 건드리다 보니 충돌이 최대 1분에 10,000번까지 발생했습니다.
[User 테이블 — 분리 전 트랜잭션 충돌 그래프]
UserBadge로 분리하면서 User 테이블에서는 Badge 관련 트랜잭션 로직이 완전히 사라졌고, 충돌로부터 자유로워졌습니다.
UserBadge 테이블 내에서 Badge 업데이트 간 충돌은 여전히 존재하지만, 1분에 80번 내외로 크게 줄었습니다.
[UserBadge 테이블 — 분리 후 트랜잭션 충돌 그래프]
User 테이블의 WCU 사용량도 줄어들었습니다.
AS-IS 대비 약 25% 절감 (메인 테이블 쓰기 전파로 인한 GSI WCU 포함)
대신 UserBadge 테이블이 새로 추가되므로, 실제 순 절감 효과는 약 12~13% 수준입니다.
하지만 사용량 절감보다 중요한 건 Badge 트래픽이 User 테이블에 영향을 주지 않게 되었다는 점입니다.
메시지 전송 트래픽으로 인해 Badge 업데이트가 100배 증가하더라도, UserBadge 테이블의 프로비저닝 용량만 높이면 곧바로 대응할 수 있게 되었습니다.
Badge 업데이트로 인한 쓰로틀링이 User 테이블과 UserBadge 테이블 모두에서 발생하지 않게 되었습니다.
UserBadge 테이블에는 GSI가 없습니다. 따라서 한 채널에 Badge 업데이트가 집중되더라도 GSI 파티션 병목이 발생하지 않고, Back-Pressure 문제는 근본적으로 해결되었습니다.
다만 User 테이블의 GSI는 여전히 channelId를 파티션 키로 사용하고 있어서, Badge 외의 다른 원인으로 Back-Pressure가 발생할 가능성은 남아 있습니다. 이 부분은 후속 작업으로 GSI 구조를 개선하여 해결할 예정입니다.
UserBadge 분리로 User 테이블의 안정성은 크게 개선되었지만, 아직 해결해야 할 과제들이 남아 있습니다.
UserProfile 분리
User 테이블에는 고객사가 직접 관리하는 Profile 필드가 있습니다. 자유도가 높은 필드라 아이템마다 크기 편차가 큽니다. 작은 프로필 데이터 하나를 수정하는데 전체 Profile 필드를 덮어쓰는 구조라 불필요한 WCU가 소모되고 있습니다. UserBadge와 마찬가지로 별도 테이블로 분리할 계획입니다.
이 작업은 Core API 셀의 비타(Vita)께서 작업하시고 테크 블로그에 글을 작성하실 예정입니다.
GSI Back-Pressure 해결
Badge로 인한 Back-Pressure는 해결되었지만, User 테이블의 GSI는 여전히 channelId를 파티션 키로 사용하고 있습니다. 한 채널에서 대량의 User 업데이트가 발생하면 동일한 문제가 재발할 수 있습니다.
이를 해결하기 위해 두 가지 전략을 검토하고 있습니다.
첫째는 GSI를 별도 테이블로 분리하고, Kinesis Data Streams For DynamoDB Streams를 통해 비동기로 동기화하는 방식입니다. 이렇게 하면 GSI 역할을 하는 테이블에 지연이 생겨도 메인 테이블은 영향을 받지 않습니다.
둘째는 GSI 해시 키를 샤딩하는 방식입니다. 쓰기 트래픽이 한 파티션에 몰리지 않고 고르게 분산됩니다.
상황에 따라 두 전략을 함께 적용하거나 선택적으로 적용할 계획입니다.
이 작업은 Core API 셀의 저와 이노(Ino)가 함께 작업한 뒤 테크 블로그에 글을 작성할 예정입니다.
Badge 트랜잭션 제거
현재 Badge 업데이트는 ChatSession과 TransactWriteItems로 묶여 있습니다. 낙관적 동시성 제어 방식은 충돌이 빈번한 상황에 적합하지 않고, 충돌이 발생할 때마다 WCU가 낭비됩니다. DynamoDB Lock Client를 활용하여 분산 락을 구현하는 방식을 검토하거나 스트리밍 방식을 검토 중입니다.
이 작업은 Core API 셀의 저와 테드(Ted)가 함께 작업한 뒤 테크 블로그에 글을 작성할 예정입니다.
이번 마이그레이션을 하면서 몇 가지 깨달은 점이 있습니다.
쓰기 패턴이 테이블 설계를 결정할 수도 있다.
흔히 NoSQL의 테이블은 읽기 패턴을 분석하여 가능한 원 테이블 전략을 따르는 것이 좋다고 이야기합니다.
DynamoDB도 마찬가지로 Sort Key를 활용하여 테이블을 분리하지 않고 아이템 분리를 통해 조회하는 패턴을 주로 채택합니다.
하지만 User 테이블의 문제는 읽기가 아니라 쓰기였습니다. 결국 DynamoDB 테이블 설계에서 중요한 것은 읽기 패턴만큼 쓰기 패턴도 같이 분석해야 한다는 것을 느꼈습니다.
익숙한 방법이 항상 최선은 아니다
처음에는 java-migration이 더 안전해 보였습니다. 팀에서 여러 번 써 본 방식이었고, 예상치 못한 변수도 적을 것 같았습니다.
하지만 16억 레코드라는 규모 앞에서는 익숙한 방법이 최선이 아니었습니다. AWS 관리형 서비스는 RCU/WCU를 소모하지 않았고, 휴먼 에러도 최소화했으며, 시간과 비용까지 절감했습니다.
물론 Glue ETL 스크립트를 작성하는 데 시간이 걸렸습니다. 하지만 한 번 만들어두니 앞으로 비슷한 마이그레이션이 필요할 때 재사용할 수 있게 되었습니다. 팀 차원에서 보면 장기적으로 이득이었던 셈입니다.
숫자가 설득한다
AS-IS와 TO-BE의 비용과 시간을 구체적으로 계산한 것이 큰 도움이 되었습니다.
“새로운 서비스를 배워봅시다”보다 “7일을 5.5시간으로 줄이고, $140를 절감할 수 있습니다”가 팀 내 합의를 훨씬 빠르게 이끌어냈습니다. 정량적 근거는 의사 결정의 속도와 품질을 모두 높여주었습니다.
채널톡에서는 수십억 건 규모의 테이블을 실제로 운영하며, 대규모 분산 시스템을 설계하고 개선하는 경험을 쌓을 수 있습니다. 또한 감이나 관행이 아닌 데이터와 기술적 근거를 바탕으로 의사 결정을 내리고, AWS의 최신 서비스를 적극적으로 도입해 비용 최적화와 시스템 안정성을 함께 달성하는 것을 중요하게 생각합니다.
대규모 트래픽과 복잡한 문제를 함께 고민하고 해결해 나가고 싶다면, 언제든 채널톡 엔지니어링 팀의 문을 두드려 주세요! https://channel.io/ko/careers
We Make a Future Classic Product