//
요즘 개발자를 위한 시스템 설계 수업 · 디렌드라 신하, 테자스 초프라
책은 데이터베이스를 관계형과 NoSQL(비관계형)로 크게 나눈다. 관계형 데이터베이스의 핵심 개념은 익숙한 것들이다. 테이블, 행, 열, 키, SQL, 그리고 정규화와 ACID, 트랜잭션.
비관계형 데이터베이스는 저장과 검색에서 더 유연한 방식을 취하며, 네 가지 계열로 나뉜다.
| 계열 | 대표 제품 |
|---|---|
| 키-값 저장소 | Redis, DynamoDB, Riak |
| 문서 지향 | MongoDB, CouchDB, RavenDB |
| 컬럼 패밀리 | Cassandra, HBase, ScyllaDB |
| 그래프 기반 | Neo4j, Neptune, OrientDB |
트레이드오프를 정리하면 이렇다.
| 장점 | 단점 | |
|---|---|---|
| 관계형 | 데이터 중복 처리, 강력한 보안 기능, ACID 트랜잭션 | 성능, 메모리 소모량, 복잡성, 수평 확장의 어려움 |
| 비관계형 | 유연한 데이터 모델, 스키마 변경 용이, 수평 확장 | 표준화 부족(쿼리 언어가 제각각), ACID 트랜잭션 미지원 |
"NoSQL은 ACID를 지원하지 않는다"는 서술은 2010년대 초반 기준의 정리에 가깝다. 현재는 MongoDB가 다중 문서 트랜잭션을, DynamoDB가 트랜잭션 API를, Neo4j가 완전한 ACID를 제공한다. 다만 원칙적인 방향성은 여전히 유효하다. 분산 환경에서 강한 트랜잭션 보장을 켜는 순간 지연 시간 비용이 붙는다는 점은 PACELC가 말하는 그대로다.
키-값 저장소는 고유한 키가 특정 값과 연결되는 가장 단순한 데이터 모델이다. 구조가 간단한 덕에 읽기와 쓰기 효율이 높고, 분산시켰을 때 확장성, 성능, 유연성, 장애 허용성을 확보하기 쉽다.
책은 이 저장소를 직접 설계하는 과정을 따라간다. 시작은 언제나 요구 사항 정의다.
put(key, value), get(key), delete(key)데이터를 여러 노드에 나누는 수단이 앞 장에서 본 일관된 해싱과 가상 노드다. 그 위에 복제 전략을 얹는다.
| 전략 | 구조 |
|---|---|
| 주-종 모델(Primary-Secondary) | 주 저장소가 쓰기를 담당하고, 종 저장소는 복제와 읽기를 담당한다 |
| 동등 모델(Peer-to-Peer) | 모든 노드가 주 저장소로 동작하며 쓰기, 읽기, 복제를 모두 수행한다 |
주-종 모델은 쓰기 경로가 하나라 일관성 관리가 쉽지만 주 노드가 병목이자 단일 장애점이 된다. 동등 모델은 그 병목을 없애는 대신 쓰기 충돌을 해결하는 장치가 필요하다.
키-값 저장소 설계에서 가장 실용적인 손잡이는 R과 W다.
N = 레플리카 총 개수
W = 쓰기 성공으로 간주하기 위해 응답해야 하는 레플리카 수
R = 읽기 응답을 만들기 위해 조회하는 레플리카 수
R + W > N이면 읽기와 쓰기의 레플리카 집합이 반드시 하나 이상 겹치므로, 읽기가 최신 쓰기를 놓치지 않는다. 이 부등식을 어떻게 만족시키는지에 따라 시스템의 성격이 달라진다.
| 설정 | 성격 |
|---|---|
| W = N, R = 1 | 쓰기가 느리고 읽기가 매우 빠르다. 읽기 중심 워크로드 |
| W = 1, R = N | 쓰기가 매우 빠르고 읽기가 느리다. 쓰기 중심 워크로드 |
| W = R = (N+1)/2 | 균형형. N=3일 때 W=R=2 |
| R + W ≤ N | 강한 일관성을 포기하고 지연 시간을 얻는다. 최종 일관성 |
R, W, N은 데이터베이스 설정 화면에 있는 숫자 세 개로 보이지만, 사실 CAP와 PACELC의 선택을 그대로 노출한 손잡이다. 어떤 값을 넣을지는 "이 데이터가 잠깐 낡아 보여도 되는가"라는 도메인 질문에 먼저 답한 다음 결정해야 한다.
노드가 잠깐 응답하지 않는 상황은 쿼럼 기반 시스템으로 흡수한다. W개 이상의 노드가 응답하면 일부 노드가 응답하지 않아도 쓰기를 성공으로 처리하고, 나중에 복구된 노드를 따라잡게 만든다.
노드가 오래 죽어 있다 돌아왔을 때는 상황이 다르다. 레플리카 사이에 어떤 데이터가 어긋났는지를 찾아 동기화해야 하는데, 목표는 불일치를 빠르게 감지하면서 전송하는 데이터양을 최소화하는 것이다. 전체 데이터를 다 비교하는 것은 답이 아니다.
이 문제를 머클 트리(Merkle Tree) 로 해결한다.
동작 방식은 이렇다.
핵심은 3번과 4번이다. 대부분의 경우 레플리카는 동일하므로, 해시 하나만 주고받고 끝난다. 다를 때만 트리를 내려가며 어긋난 범위를 좁혀 가므로, 비교 비용이 데이터 크기가 아니라 차이의 개수에 비례한다.
책은 비관계형 계열 중 그래프 기반 데이터베이스를 별도로 다룬다. 복잡한 데이터 관계를 저장하고 관리하는 데 최적화된 데이터베이스다.
| 개념 | 내용 |
|---|---|
| 노드(Node) | 그래프의 기본 데이터 단위. 개체를 가리키며 키-값 쌍 속성을 가질 수 있다 |
| 에지(Edge) | 노드 간 연결. 두 대상의 연관성을 표현하며 속성을 가질 수 있다 |
| 레이블 | 노드와 에지를 특정 카테고리나 유형으로 분류한다 |
| 탐색 | 노드 관계를 따라가는 작업에 최적화되어 경로, 연결성, 패턴 쿼리를 효율적으로 처리한다 |
| 사이퍼(Cypher) | 복잡한 그래프 쿼리를 직관적으로 작성하도록 설계된 쿼리 언어 |
활용 사례는 복잡한 관계를 모델링하고 분석해야 하는 영역이다. 소셜 네트워크, 추천 엔진, 사기 탐지, 네트워크 및 인프라 관리, 지식 그래프 등이다. 그래프 데이터베이스가 특히 강한 지점은 패턴 매칭, 즉 그래프 내 특정 구조나 연결을 식별하는 작업이다.
대표 구현체인 Neo4j의 특징 중 눈여겨볼 것은 세 가지다.
관계형 방식으로 관계를 저장하는 방법은 두 가지다.
그래프 방식은 데이터를 이중 연결 리스트 형태의 그래프와 두 개의 저장소(노드 저장소, 관계 저장소)로 표현한다. 예를 들어 노드 4를 추가하고 "노드 2가 노드 4를 팔로우한다"는 관계를 넣는다면, 이중 연결 리스트에 노드를 추가하고, 노드 저장소에 개체 레코드를, 관계 저장소에 에지 레코드를 각각 넣는다.
책이 흥미롭게 다루는 부분은 레코드의 물리적 크기다.
| 저장소 | 레코드 크기 | 구조 |
|---|---|---|
| 노드 저장소 | 15바이트 | 사용 여부(1) + 노드 ID(4) + 첫 관계 ID(1) + 첫 속성 ID(1) + 레이블 저장소(5) + 예비(1) |
| 관계 저장소 | 34바이트 | 출발 노드 ID + 도착 노드 ID + 관계 유형 포인터 + 출발/도착 노드 각각의 이전·다음 관계 레코드 포인터 |
이 구조를 알면 그래프 DB가 왜 관계 탐색에 빠른지 이해된다. 관계 레코드가 이전·다음 관계 레코드를 직접 가리키는 포인터를 들고 있기 때문에, 한 노드의 관계를 순회하는 일이 연결 리스트를 따라가는 것과 같다. 관계형 데이터베이스에서 조인 테이블을 인덱스로 검색하는 것과 달리, 인덱스 조회 없이 포인터를 따라가는 것이다. 이것이 이른바 인덱스 없는 인접성(Index-free Adjacency) 이다.
그래프 DB의 이점은 관계의 개수보다 탐색 깊이에서 나온다. 1~2단계 조인은 관계형 데이터베이스가 충분히 잘 처리한다. 하지만 "친구의 친구의 친구"처럼 깊이가 늘어나면 관계형 쪽은 조인마다 결과 집합이 폭발하는 반면, 그래프 쪽은 포인터를 한 단계 더 따라갈 뿐이다. 도입 판단은 "관계가 많은가"가 아니라 "몇 단계를 넘어가는 탐색이 핵심 쿼리인가"로 해야 한다.
이 장은 데이터베이스 핵심 개념과 유형을 넓게 훑는다. 관계형과 비관계형의 구분, 키-값 저장소 설계, 그래프 데이터베이스가 주요 축이다. 기본적인 SQL이나 정규화 같은 내용은 이미 익숙한 영역이라 용어 확인 정도로 지나갔고, DynamoDB나 컬럼 패밀리, HBase 절은 개요 수준이라 깊이 파지 않았다.
건져 갈 만한 부분은 두 가지였다. 하나는 R과 W의 쿼럼 설정이 CAP의 선택을 숫자로 노출하는 지점이라는 점이고, 다른 하나는 머클 트리로 레플리카 차이만 골라 동기화하는 방식이다. 둘 다 이론이 실제 구현에 어떻게 내려앉는지 보여주는 좋은 예다.
Neo4j와 그래프 모델링은 개요에서 멈춘다. 레코드 크기와 포인터 구조를 짚어 준 것은 좋았지만, 실제로 그래프 DB를 도입할 판단을 하려면 사이퍼 쿼리 작성과 성능 특성을 별도 자료로 확인해야 할 것 같다.
이 글이 도움이 되셨나요?