Cloud Modernization 16
DynamoDB를 키-값, 용량, 일관성 옵션으로 읽기
NoSQL을 단순히 SQL을 안 쓰는 DB로만 보면 테이블, 키, 용량, 일관성 설계의 의미가 흐릿하다.
근거 · 교안 p62-p66
1장: 키-값 데이터 모델: 단순함 뒤에 숨겨진 힘
솔라는 모니터 한구석에 띄워 둔 메모를 노려봤다. 방금 읽은 기술 문서에서 그대로 복사해 온 문장이었다.
DynamoDB: 키-값(Key-Value) 기반의 NoSQL 데이터베이스...
분명 아는 단어들의 조합인데, 문장은 머릿속에서 겉돌았다. ‘키-값’. 열쇠와 값. 데이터를 이름표 붙여서 보관하는 방식이겠지. 너무 단순해서 오히려 이상했다. 이게 왜 그렇게 중요하다고 문서 첫머리에 나올까? 마치 대단한 비밀이라도 되는 것처럼. 솔라는 고개를 갸웃거렸다. ‘그냥 데이터를 넣고 빼는 여러 방법 중 하나일 뿐, 그 이상은 없을 텐데.’
그때 거실에서 책을 읽던 루나가 솔라 쪽을 힐끗 보았다. 미간을 찌푸린 채 모니터와 씨름하는 동생의 모습이 눈에 들어온 것이다.
“뭘 그렇게 심각하게 보고 있어?”
“아, 언니. 별건 아니고. 그냥 게임 데이터 저장할 방법을 찾아보다가 DynamoDB라는 걸 봤는데… ‘키-값’ 모델이 중요하다는 말을 이해 못 하겠어. 그냥 유저 ID: sola_01, 점수: 100점 이렇게 저장하는 거 아니야? 이게 뭐 특별한 원칙이라도 돼?”
솔라의 질문에 루나는 책을 덮고 잠시 생각에 잠겼다. 그러고는 솔라의 모니터 옆에 놓인 빈 노트와 펜을 가져와 테이블 가운데에 놓았다.
“네가 만들고 있다는 그 게임, 유저가 백만 명쯤 된다고 상상해 볼까?”
“백만 명? 그럼 좋겠다.” 솔라가 피식 웃으며 대답했다.
“그 백만 명의 데이터가 거대한 창고 안에 순서 없이 상자에 담겨 있다고 생각해 봐. 각 상자에는 유저 ID, 레벨, 점수 같은 정보가 적힌 종이가 들어 있고. 이제 ‘sola_01’ 유저의 점수를 찾아야 해. 어떻게 할래?”
“음…” 솔라는 거대한 창고와 산더미처럼 쌓인 상자들을 상상했다. 막막했다. “일단… 창고에 들어가서 상자를 하나씩 다 열어봐야겠지? ‘sola_01’이 나올 때까지.”
“맞아. 최악의 경우엔 백만 번째 상자에서 찾을 수도 있겠네. 게임에서 점수 한번 확인하는데 몇 시간이 걸릴 수도 있겠다.”
루나의 말에 솔라는 고개를 저었다. “말도 안 돼. 그럼 데이터를 유저 ID 순서대로 미리 정렬해두면 되잖아. 가나다순으로. 그럼 좀 빠르지 않을까?”
“좋은 생각이야. 도서관에서 책 제목 가나다순으로 찾는 것처럼. 하지만 새로운 유저가 가입할 때마다 그 거대한 목록의 중간에 끼워 넣고 전체를 다시 정렬해야겠지. 백만 명분의 데이터를 말이야. 그것도 꽤 힘든 작업 아닐까?”
솔라는 입을 다물었다. 간단한 문제가 아니었다. 데이터를 찾는 것도, 정리하는 것도 보통 일이 아니었다. 내 점수 하나 보려고 온 세상의 데이터를 뒤지는 기분이었다.
루나는 펜을 들어 노트에 열쇠 그림 하나를 쓱 그렸다. 그리고 그 옆에 ‘sola_01’이라고 적었다.
“‘키-값’ 모델의 ‘키’는 바로 이럴 때 쓰는 거야. 단순한 이름표가 아니야. 각 데이터 상자에 붙여주는 ‘고유 주소’ 같은 거지. DynamoDB는 ‘sola_01’이라는 키를 받으면, 백만 개의 상자를 다 뒤지는 대신 ‘sola_01’ 데이터가 보관된 선반 위치로 단번에 달려가. 마치 우리가 집 주소로 편지를 보내면 우체부가 수많은 집 중에서 정확히 그 집 우편함에 편지를 넣는 것처럼.”
루나는 말을 이었다. “이 ‘고유 주소’ 역할을 하는 키를 ‘파티션 키’라고 불러. 데이터를 어디에 저장하고 어떻게 찾을지에 대한 약속이자, 설계의 가장 첫 단추야. 이 키 덕분에 데이터가 백만 개가 되든, 일억 개가 되든 거의 동일한 속도로 원하는 정보를 꺼내 올 수 있어. 모든 상자를 열어볼 필요가 없으니까.”
그제야 솔라의 머릿속에서 흩어져 있던 단어들이 자리를 찾기 시작했다. ‘키-값’은 단순히 데이터를 ‘저장’하는 방식에 대한 설명이 아니었다. 데이터를 가장 빠르게 ‘접근’하는 방식에 대한 선언이었다. 키를 어떻게 설계하느냐에 따라 데이터베이스의 운명이, 서비스의 속도가 결정된다. 이름표가 아니라, 거대한 정보 창고의 모든 것을 지배하는 마스터키였던 것이다.
“아…! 그럼 키를 정하는 게 제일 중요한 거였네. 어떤 기준으로 데이터를 찾을지를 맨 처음에 결정하는 거니까. 그냥 이름표가 아니라 건물의 주소나 도서관의 청구기호처럼, 처음부터 성능을 결정하는 설계도였구나.”
솔라는 자신이 띄워 뒀던 메모를 다시 봤다. 이제 ‘키-값 기반’이라는 글자가 다르게 보였다. ‘빠른 조회를 약속하는 설계 기반’이라고 읽혔다.
한 가지 의문이 풀리자 곧바로 새로운 의문이 고개를 들었다.
“언니, 키를 써서 데이터를 빨리 찾는다는 건 이제 알겠어. 그런데 왜 자료들에서 ‘용량’이라는 말이 자꾸 같이 나오는 거야? 우체부가 주소만 알면 편지를 배달하는 거지, 편지를 몇 통 배달할 수 있는지 ‘능력’을 미리 정해둬야 해? 빨리 찾을 수 있으면 그냥 계속 쓰면 되는 거 아니야?”
2장: 용량 관리: 예측 가능한 성능을 위한 조절장치
솔라의 ‘우체부’ 비유에 루나는 잠시 생각에 잠겼다. 좋은 질문이었지만, 어딘가 결정적인 한 조각이 빠져 있었다. 루나는 솔라의 말에 고개를 끄덕이는 대신, 아까 키를 그렸던 노트 페이지를 넘겨 새 페이지를 펼쳤다. 그리곤 익숙한 손놀림으로 작은 카페 카운터 그림을 그리기 시작했다. 카운터 안쪽엔 바리스타 한 명이 서 있었다.
“우체부 비유도 좋지만, 데이터베이스는 편지를 배달하는 일과는 조금 달라. 편지는 주소만 맞으면 언젠가 도착하지. 하지만 데이터베이스는 수많은 손님들이 동시에 들이닥치는 인기 카페에 더 가까워.”
루나는 카운터 앞에 졸라맨 여러 명을 그려 넣었다. “네가 말한 ‘키’는 손님들이 주문할 메뉴 이름 같은 거야. ‘아이스 아메리카노 주세요!’ 하면 바리스타가 바로 그 메뉴를 만들 수 있지. 레시피를 다 뒤져볼 필요 없이. 지난 챕터에서 우리가 확인한 게 바로 이거고.”
루나가 펜 끝으로 카운터 안쪽의 바리스타를 톡톡 쳤다.
“문제는 이거야. 바리스타가 한 명뿐인데, 손님이 100명 몰려오면 어떻게 될까? 아무리 ‘아이스 아메리카노’라는 주문이 명확해도, 바리스타는 한 번에 한두 잔밖에 못 만들어. 나머지 98명은 줄을 서서 하염없이 기다려야 해. 여기서 속도는 바리스타의 손이 얼마나 빠르냐, 즉 ‘처리 능력’에 달려있어.”
솔라는 루나가 그린 그림을 빤히 들여다봤다. 우체부와 바리스타. 둘 다 무언가를 전달하고 처리하지만, 그 본질은 달랐다. 우체부는 혼자서도 정해진 일을 끝낼 수 있지만, 바리스타는 몰려드는 손님들을 감당해야 했다.
“아…! 그러니까, 키를 써서 데이터를 빨리 ‘찾는’ 것과, 그 요청을 실제로 ‘처리’하는 건 다른 문제라는 거구나. 내가 아무리 주소를 정확하게 외쳐도, 그걸 받아서 처리해 줄 사람이 없으면 소용없다는 거네.”
“바로 그거야.” 루나가 말했다. “DynamoDB에서 말하는 ‘용량’은 바로 이 바리스타의 수, 혹은 바리스타의 작업 속도를 미리 정해두는 개념이야. 서버가 몇 개의 요청을 동시에, 그리고 얼마나 빨리 처리할 수 있는지를 보장하는 거지. 이걸 ‘프로비저닝된 처리량’이라고 불러. 돈을 내고 바리스타를 미리 고용해두는 것과 같아.”
루나는 그림 옆에 간단히 메모를 추가했다.
읽기 요청 처리 = RCU (Read Capacity Unit)쓰기 요청 처리 = WCU (Write Capacity Unit)
“RCU는 초당 얼마나 많은 데이터를 ‘읽어갈’ 수 있는지, WCU는 초당 얼마나 많은 데이터를 ‘저장하거나 수정’할 수 있는지를 나타내는 단위야. 이걸 보고 ‘복잡한 과금 체계’라고 생각하기 쉬운데, 사실은 ‘보장된 성능’에 대한 약속이지. 내가 고용한 바리스타는 피곤하다고, 손님이 많다고 느려지지 않아. 약속한 만큼의 성능을 꾸준히 내줘.”
솔라는 자신의 게임 시나리오를 이 새로운 비유에 대입해 보았다. “그럼 만약에, 내 게임에서 유저 100명이 동시에 자기 프로필 정보를 읽고, 다른 10명은 동시에 자기 최고 점수를 막 경신하고 있다면…”
솔라는 스스로 계산을 시작했다. 이건 더 이상 추상적인 개념이 아니었다. 내 서비스의 성능과 직결된 구체적인 숫자였다.
“프로필 정보가 4KB보다 작다고 치면, 100명이 동시에 읽으니까 초당 100 RCU가 필요한 건가? 그리고 점수 업데이트는 작은 데이터니까, 10명이 쓰기를 하면 10 WCU… 와, 이렇게 필요한 만큼을 예측해서 설정해야 하는 거구나.”
‘RCU/WCU는 AWS가 돈 받으려고 만든 복잡한 숫자일 뿐’이라는 생각은 사라졌다. 그것은 비용 청구서가 아니라, 서비스의 성능 설계도였다. 얼마나 많은 트래픽을 감당할 수 있는지, 사용자가 얼마나 쾌적하게 서비스를 이용할 수 있는지를 결정하는 핵심 조절장치였다. 솔라는 이제 ‘용량’이라는 단어가 성능과 비용 사이의 트레이드오프를 의미한다는 것을 깨달았다.
생각이 정리되자, 루나가 덧붙인 메모의 한 부분이 눈에 들어왔다.
“언니, 그런데 여기 이상한 게 있어. RCU 설명을 보면… 데이터를 읽는 방식에 따라 용량 소모가 다르네? ‘강력한 읽기 일관성’은 1 RCU를 쓰고, ‘최종적 읽기 일관성’은 0.5 RCU만 쓴다고? 절반 값이잖아. 그럼 당연히 싼 걸 써야 하는 거 아니야? 데이터가 뭐 반만 오나?”
3장: 일관성 옵션: 성능과 정확성 사이의 줄타기
솔라의 질문은 타당했다. ‘강력한 읽기 일관성’은 1 RCU, ‘최종적 읽기 일관성’은 0.5 RCU. 같은 데이터를 읽는데 왜 비용이 두 배나 차이 날까. 당연히 더 저렴한 것을 선택해야 하는 것 아닐까? 솔라의 마음은 ‘최종적 일관성은 뭔가 문제가 있는, 불완전한 옵션’이라는 쪽으로 기울고 있었다.
루나는 솔라의 질문에 바로 답하는 대신, 지난 장에서 그렸던 카페 그림이 있는 노트를 가져왔다. 그리고 원래 있던 바리스타 한 명의 카페 그림 옆에, 똑같은 모습의 카페 두 개를 더 그렸다. 이제 노트 위에는 세 개의 동일한 카페가 나란히 서 있었다.
“솔라, 네가 만든 게임이 엄청난 성공을 거둬서, 갑자기 이용자가 폭주하고 있다고 상상해 봐. 카페 하나로는 도저히 감당이 안 돼. 그래서 우리는 똑같은 레시피와 재료를 가진 분점 두 개를 바로 옆에 더 열었어. 이제 손님들은 세 곳 중 아무 데나 가서 주문할 수 있게 된 거야. 대기 줄이 훨씬 짧아지겠지?”
루나의 손끝에서 태어난 세 개의 카페를 보며 솔라가 고개를 끄덕였다. “응. 당연하지. 처리할 수 있는 창구가 세 배로 늘어난 거니까.”
“맞아. DynamoDB도 비슷한 일을 해. 데이터의 안정성과 빠른 속도를 위해, 네가 저장한 데이터를 여러 곳에 복제해서 보관하거든.” 루나는 중앙에 있는 첫 번째 카페를 펜으로 동그라미 쳤다. “이곳이 ‘본점’이야. 새로운 데이터가 저장되거나 수정될 때, 즉 쓰기 요청(WCU)은 항상 이 본점에 먼저 기록돼.”
그러고는 양옆의 두 카페를 가리켰다. “그리고 본점은 즉시 다른 두 ‘분점’에 연락해서 ‘방금 새로운 기록이 들어왔으니 너희 메뉴판도 이걸로 바꿔!’라고 알려주지. 이 과정은 보통 1초 안에 일어나. 아주 빠르지.”
솔라는 세 개의 카페 그림을 뚫어지게 쳐다봤다. 본점과 두 개의 분점. 복제. 1초. 머릿속에서 단어들이 빙글빙글 돌았다. 아직 반값짜리 읽기의 비밀은 풀리지 않았다.
루나가 드디어 솔라의 질문과 정면으로 마주했다.
“이제 네가 던진 질문으로 돌아가 보자. 왜 읽기 비용이 다를까? 자, 시나리오를 하나 줄게. 네가 게임에서 신기록 1,000점을 달성했어. 이 ‘1,000점’이라는 정보는 ‘본점’에 기록됐고, 본점은 즉시 분점들에게 이 소식을 알리고 있어. 바로 그 찰나의 순간, 네 친구가 네 프로필을 조회한다고 해 봐.”
루나는 펜으로 시나리오를 그려나갔다.
“이때 친구에게 네 점수를 보여주는 방법은 두 가지야. 첫 번째, 분점들까지 모두 ‘1,000점’으로 업데이트가 완료되었는지 본점에 일일이 확인하고 가장 최신 데이터를 가져오는 것. 이게 ‘강력한 읽기 일관성’이야. 은행 계좌에서 돈을 인출하고 잔액을 확인할 때처럼, 단 1초의 오차도 허용할 수 없는 상황에 쓰지.”
“아… 그래서 바쁜 본점의 확인까지 거쳐야 하니까, 더 비싼 1 RCU를 다 쓰는 거구나.”
“정확해.” 루나가 말을 이었다. “두 번째 방법은, 그냥 가장 가까운 분점에 가서 ‘지금 보이는 점수 줘’라고 하는 거야. 만약 본점의 소식이 아직 도착하지 않았다면, 찰나의 순간 동안은 이전 점수인 990점을 보게 될 수도 있겠지. 하지만 곧 1,000점으로 바뀔 거야. 이게 ‘최종적 읽기 일관성’이야.”
솔라는 무릎을 쳤다. 데이터가 반만 오거나 깨져서 오는 게 아니었다. ‘최신 버전이라는 보증’이 있느냐 없느냐의 차이였다.
“그럼 게임 랭킹이나 친구의 프로필 조회처럼, 1초 정도 데이터가 늦게 보여도 아무 상관없는 경우에는 굳이 비싼 ‘강력한 읽기’를 쓸 필요가 없는 거네! 그냥 ‘최종적 읽기’를 쓰면 비용을 절반이나 아낄 수 있는 거고!”
‘강력한 일관성은 무조건 좋고, 최종적 일관성은 문제가 있는 옵션’이라는 생각은 산산이 부서졌다. 둘은 우열의 관계가 아니었다. 각자의 역할이 뚜렷한, 성능과 비용, 그리고 데이터의 성격 사이에서 내려야 하는 ‘설계상의 선택’이었다. 어떤 데이터를 다루고 있느냐에 따라 현명한 트레이드오프를 해야 했다.
모든 조각이 맞춰졌다. 솔라는 책상 위를 깨끗이 치우고, 새 노트를 펼쳤다. 이제는 루나의 그림을 보고만 있지 않았다. 직접 자신의 게임 데이터베이스를 설계해 볼 차례였다. 솔라는 망설임 없이 펜을 들고 적어 내려가기 시작했다.
[솔라의 게임 서버 설계 초안]
-
데이터 모델 (키 설계):
UserID를 파티션 키로 사용한다. 모든 유저 정보는 ID로 직접, 빠르게 조회해야 하니까. 이게 내 서비스 속도의 핵심이다.
-
용량 계획 (RCU/WCU):
- 대부분의 유저는 랭킹이나 정보를 읽기만 할 것이다. 쓰기보다 읽기 요청이 훨씬 많을 테니, 초기 용량은 RCU: 100, WCU: 20 정도로 설정하고 모니터링하며 조절하자. 바리스타(RCU)를 더 많이 고용하는 전략.
-
일관성 전략 (Trade-off):
- 유저 랭킹, 프로필 조회: 1초 정도의 지연은 괜찮다. **‘최종적 읽기 일관성’**을 사용해서 RCU를 절약하고 비용 효율을 높인다.
- 유료 아이템 구매, 재화 차감: 이건 돈 문제다. 단 1의 오차도 있어선 안 된다. 이 요청만큼은 반드시 **‘강력한 읽기 일관성’**을 사용해서 처리 직후의 정확한 데이터를 확인한다.
자신이 직접 정리한 세 가지 원칙을 보며 솔라는 미소 지었다. 더 이상 DynamoDB는 완전 관리형 NoSQL 서비스... 라는 문장이 막연하게 느껴지지 않았다. 키(Key)는 데이터의 주소를 정하는 설계도였고, 용량(Capacity)은 그 주소로 얼마나 많은 손님을 받을지 정하는 성능의 약속이었으며, 일관성(Consistency)은 손님에게 어떤 수준의 최신 정보를 제공할지 결정하는 서비스 품질과 비용의 저울이었다. 이 세 가지가 맞물려 돌아갈 때, 비로소 DynamoDB라는 도구를 제대로 사용하는 것이었다.