NoSQL — スキーマが消えたのではなく責任が移った
한국어 원문으로 표시합니다.
한 줄 요약
NoSQL 은 관계형의 상위 호환이 아니라 특정 제약을 포기하고 특정 능력을 얻는 거래이며, 무엇을 포기했는지 모르고 고르면 그 대가를 나중에 치른다.
왜 이게 필요했나
관계형 데이터베이스는 강력하지만 몇 가지를 전제한다. 스키마가 미리 정해져 있고, 데이터가 한 대에 들어가거나 적어도 조인이 가능한 범위 안에 있으며, 강한 일관성이 필요하다는 전제다.
2000년대 후반 웹 서비스들이 이 전제를 하나씩 못 지키게 되면서 다른 선택지가 나왔다. 핵심 동기는 수평 확장이었다. 한 대를 더 크게 만드는 대신 여러 대에 나누려면 조인과 트랜잭션 같은 기능이 걸림돌이 되었고, 그것을 포기한 설계들이 등장했다.
어떻게 동작하나
부류별 성격은 이렇게 갈린다.
| 부류 | 데이터 모델 | 강점 | 대가 |
|---|---|---|---|
| 키값 | 키 하나에 값 하나 | 단순 조회가 매우 빠름 | 값 내부로 질의 불가 |
| 문서 | JSON 유사 문서 | 스키마 유연, 한 문서에 다 담아 조회 | 문서 간 조인과 정합성은 앱 책임 |
| 와이드 컬럼 | 행 키 + 컬럼 패밀리 | 대량 쓰기, 시계열 | 질의 패턴을 미리 정해 설계해야 함 |
| 그래프 | 노드와 간선 | 관계 탐색 | 대량 집계는 약함 |
여기서 가장 널리 퍼진 오해를 짚어야 한다. "스키마가 없다"는 말은 스키마가 사라졌다는 뜻이 아니라 스키마를 강제하는 주체가 데이터베이스에서 애플리케이션으로 옮겨 갔다는 뜻이다. 필드 이름을 오타 낸 문서가 조용히 들어가고, 몇 달 뒤 그 필드를 읽는 코드가 조용히 실패한다. 데이터베이스가 막아 주던 것을 이제 코드 리뷰와 테스트가 막아야 한다.
CAP 도 자주 오용된다. 네트워크 분단이 실제로 일어난 순간에 일관성과 가용성 중 무엇을 고를지의 문제이지, 평상시에 셋 중 둘을 고르는 이야기가 아니다. 분단이 없을 때는 대부분의 시스템이 둘 다 제공한다.
현장에서 만나는 모습
선택의 기준은 유행이 아니라 질의 패턴이다. 몇 가지 실용적인 신호가 있다.
- 여러 엔티티를 조합해 보는 질의가 자주 바뀐다면 관계형이 여전히 유리하다. 문서 저장소는 저장할 때 정해 둔 형태로만 잘 읽힌다.
- 한 번 쓰고 그 형태 그대로 자주 읽는다면 문서 저장소가 자연스럽다.
- 캐시나 세션처럼 키로만 접근한다면 키값 저장소가 맞다.
- 관계의 깊이를 따라가는 질의(친구의 친구, 권한 상속)라면 그래프가 압도적으로 낫다.
그리고 현대 관계형 데이터베이스가 상당 부분을 흡수했다는 점도 감안해야 한다. PostgreSQL 의 jsonb 는 문서 저장소의 유연성을 상당히 제공하면서 트랜잭션과 조인을 유지한다. 다만 남용하면 안 된다. 조회하거나 정렬하거나 제약이 필요한 필드는 컬럼으로 승격하고 나머지 원본만 jsonb 에 남기는 것이 실용적인 경계다. jsonb 안의 값에는 정확한 통계가 없어 플래너의 행 수 추정이 어긋나고, 그 결과 조인 방식 선택이 틀어지기 때문이다.
고르기 전에 답해야 할 다섯 질문
부류별 성격을 외우는 것보다, 지금 만들려는 것에 대해 다음 다섯 가지를 먼저 적어 보는 편이 훨씬 빨리 답에 닿는다.
- 어떤 질의로 읽을 것인가. 키 하나로 찾는가, 조건으로 거르는가, 관계를 따라가는가. 문서 저장소와 와이드 컬럼은 저장할 때 정해 둔 접근 경로로만 잘 읽히므로, 이 답이 "아직 모른다" 라면 관계형이 안전하다.
- 데이터가 얼마나 커지는가. 한 대에 들어갈 크기라면 분산의 대가를 치를 이유가 없다. 요즘 서버 한 대는 수 테라바이트를 다룬다.
- 함께 바뀌어야 하는 것이 무엇인가. 이 목록이 길면 트랜잭션이 필요하다는 뜻이다.
- 옛 값을 잠시 보여 줘도 되는가. 조회 수나 추천 목록은 괜찮지만 잔액과 재고는 안 된다.
- 누가 운영하는가. 새 저장소는 새 백업 절차, 새 지표, 새 장애 유형을 함께 데려온다.
다섯 질문에 답해 보면 대부분의 경우 결론이 하나로 모입니다. 처음에는 관계형 하나로 시작하고, 특정 질의 패턴이 명확히 아파지면 그 부분만 다른 저장소로 떼어 내는 것입니다. 세션은 키값 저장소로, 전문 검색은 검색 엔진으로 옮기는 식입니다. 반대 순서, 즉 처음부터 여러 저장소를 깔아 두고 시작하는 방식은 아직 존재하지도 않는 문제에 운영 부담부터 지불하는 선택입니다.
분산이 되는 순간 달라지는 것들
한 대에서 돌던 데이터베이스가 여러 대로 나뉘면, 익숙하던 성질 몇 가지가 조용히 사라진다. 이것을 모르고 옮기면 코드는 그대로인데 결과만 이상해진다.
첫째, 쓴 값을 바로 못 읽을 수 있다. 복제본이 여러 개인 저장소에서 쓰기는 한쪽에, 읽기는 다른 쪽에 갈 수 있다. 프로필을 저장하고 곧바로 조회하는 화면이 옛 값을 보여 주는 사고가 여기서 나온다. 대부분의 저장소는 "방금 내가 쓴 것은 내가 읽을 수 있다" 를 옵션으로 제공하므로, 그 옵션을 켜거나 쓰기 직후의 읽기만 주 복제본으로 보내면 된다. 옵션 이름은 제품마다 다르지만 대가는 같다. 그 읽기는 느려진다.
둘째, 여러 문서를 한 번에 바꾸는 일이 어려워진다. 관계형에서는 트랜잭션 하나로 끝나던 작업이, 문서가 서로 다른 노드에 흩어져 있으면 부분적으로만 성공할 수 있다. 그래서 문서 저장소는 함께 바뀌어야 하는 것은 같은 문서에 담는다 는 원칙으로 설계한다. 주문과 주문 항목을 한 문서에 넣는 이유가 성능이 아니라 원자성이라는 점이 중요하다.
셋째, 샤드 키를 나중에 바꿀 수 없다. 데이터를 어느 노드로 보낼지 정하는 키는 사실상 되돌릴 수 없는 결정이다. 잘못 고르면 특정 노드에만 부하가 몰리고(핫 샤드), 그 노드를 늘려도 해결되지 않는다. 대표적인 실패는 시각을 샤드 키로 쓰는 것이다. 최근 데이터가 전부 한 노드로 몰려서, 노드를 열 대로 늘려도 아홉 대가 논다.
정리하면 이렇습니다. 관계형에서 NoSQL 로 옮기는 결정은 저장소를 고르는 일이 아니라, 데이터베이스가 대신 지켜 주던 규칙 중 무엇을 애플리케이션이 직접 지킬지 정하는 일입니다. 그 목록을 적어 보지 않고 옮기면, 옮긴 뒤에 하나씩 사고로 발견하게 됩니다.
이어지는 퀴즈에서 확인할 것
각 부류가 무엇을 포기하고 무엇을 얻는지, 그리고 "스키마 없음"이 실제로 무엇을 뜻하는지 설명할 수 있는지 확인한다.