데이터 모델은 소프트웨어 개발에서 중요하다. 문제를 어떻게 생각해야 하는지에 대해 지대한 영향을 미치기 때문이다.
보통 데이터 모델은 계층을 사용해서 디자인한다. 각 계층에서의 디자인 핵심 개념은 다음 아래 계층에서 어떻게 표현되는가이다.
계층 깊이 | 계층 | 데이터 모델, 표현 |
---|---|---|
0 | 현실 | 사람, 조직 상품, 행동, 자금 흐름, 센서 |
1 | 애플리케이션 | 객체, 데이터 구조, API 로 표현 |
2 | 범용 데이터 모델 | JSON, XML 와 같은 document model, relational (table) model, graph model 로 표현 |
3 | 데이터베이스 | 메모리, 디스크, 네트워크 상의 바이트 단위로 표현 |
4 | 하드웨어 | 전류, 빛의 파동, 자기장으로 바이트를 표현 |
각 계층은 데이터모델을 제공함으로써 하위 계층의 복잡성을 숨긴다. 추상화는 다른 그룹의 사람들이 효율적으로 일할 수 있게 한다.
데이터 모델은 그 위에서 소프트에어가 할 수 있는 일과 할 수 없는 일에 지대한 영향을 주므로 애플리케이션에 적합한 데이터 모델을 선택하는 작업는 상당히 중요하다.
2장에서는 다음의 것들을 살펴본다.
1. 데이터 저장과 질의를 위한 다양한 범용(general-purpose) 데이터 모델 을 살펴본다.
2. 다양한 질의 언어를 살펴보고 사용 사례도 비교한다.
3장에서는 이런 데이터 모델이 실제로 어떻게 구현되는지 저장소 엔진의 동작 방식을 통해 설명한다.
관계형 모델을 기반으로 한 SQL 의 데이터 모델
데이터(SQL에서는 테이블이라 부름)는 관계(relation) 로 구성되고, 각 관계는 순서 없는 튜플(SQL에서는 로우(row))의 모음이다.
RDBMS 와 SQL 은 보통 다음의 경우 선택되었다.
관계형 이전에 애플리케이션 개발자는 데이터의 내부 표현에 대해 많이 고민해야 했다. 관계형 모델의 목표는 정리된 인터페이스 뒤로 이러한 구현 세부 사항을 숨기는 것이었다.
NoSQL 은 관계형 모델의 우위를 뒤집으려는 2010년대의 시도다.
Not Only SQL 을 의미한다.
다음의 이유로 NoSQL 을 선택하곤 한다.
관계형 데이터베이스가 비관계형 데이터스토어와 함께 사용하는 개념을 다중 저장소 지속성(polyglot persitence) 이라 한다.
객체 지향 프로그래밍 언어로 개발된 애플리케이션에선 데이터 모델을 객체형으로 정의하지만, 이를 관계형 모델로 저장하려면 전환 계층이 필요하다. 이를 임피던스 불일치(impedance mismatch) 라고 부른다.
ORM(객체 관계형 맵핑) 프레임워크가 전환 계층에 필요한 boilerplate code 의 양을 줄이지만 두 모델 간의 차이를 완전히 숨길 수 없다.
이력서 같은 데이터 구조는 모든 내용을 갖추고 있는 문서라서 JSON 표현에 매우 적합하다. JSON 은 XML 보다 훨씬 더 간단하다. MongoDB, RethinkDB, CouchDB, Espresso 같은 docuemnt-oriented DB 는 JSON 데이터 모델을 지원한다.
JSON 이 임피던스 불일치를 줄인다고 생각할 수도 있지만, 데이터 부호화 형식에 대한 JSON 의 문제가 있다. 4장에서 살펴본다.
JSON 표현은 다중 테이블 스키마보다 더 나은 지역성(locality) 을 갖는다. 다중 테이블 스키마의 쿼리는 다중 조인을 수행해야하지만, JSON 표현에서는 모드 관련 정보가 한 곳에 있어 질의 하나로 충분하다.
데이터를 id 로 표기하는 것과 텍스트로 표기하는 것의 차이는 저장의 중복이다. 데이터를 id 로 표기하는 것은 정보를 한 곳에만 저장하고 그것을 참조하는 모든 곳에서 id 로 표기하는 것이고, 텍스트로 표기하는 것은 정보를 사용하는 모든 곳에서 동일한 정보를 중복해서 저장하게 된다.
id 를 사용하면 id 자체는 아무런 의미가 없기 때문에 id 를 변경할 필요가 없다. 의미를 가지는 경우라면 id 는 언젠가 변경해야할 수도 있다. 정보가 중복된 경우는 모든 중복 항복을 변경해야 하므로, 1) 쓰기 오버헤드와 2) 비일관성의 위험이 있다. 이런 중복을 제거하는 것이 DB 정규화의 핵심 목표 중 하나다.
중복된 데이터를 정규화하는 것은 다대일(many-to-one) 관계가 필요한데, 다대일 관계는 document 모델에는 적합하지 않다. document DB 에서는 일대다 트리 구조를 위해 조인이 필요하지 않기에, 조인에 대한 지원이 약한 편이다. DB 가 조인을 지원하지 않는 경우 애플리케이션 코드에서 다중 질의 후 조인하는 코드를 작성하기도 한다.
애플리케이션의 초기 버전에선 조인 없는 모델이 적합하더라도, 기능을 추가하면서 데이터가 점차 상호 연결되는 경향이 있다.
다대다 관계를 표현하는 제일 좋은 방법이 과연 관계형 모델일까?
1970년 대 다대다 관계를 표현하고자, 관계형 모델과 네트워크 모델이 대두되었고, 두 진영 간에 어떤 방법이 더 최적인지 논쟁이 있었다.
관계별 표현 방법
관계 | 네트워크 모델 | 관계형 모델 | 문서 모델 |
---|---|---|---|
일대다 | (일반화된 계층형 모델 방식) 상위 레코드 내에 중첩된 레코드를 저장 | (참조) 별도의 테이블을 두고 외래키로 참조 | (계층형 모델 방식) 상위 레코드 내에 중첩된 레코드를 저장 |
다대일, 다대다 | (포인터 접근) 접근 경로를 타고 들어가서 접근 | (참조) 별도의 테이블을 두고 외래키로 참조 | (참조) 문서 참조 |
코다실(CODASYL, Conference on Data Systems Languages)이라 불리는 위원회에서 표준화한 모델로써, 코다실 모델이라고도 부른다.
코다실 모델은 계층 모델을 일반화한다. 트리 구조에서 모든 레코드가 정확하게 하나의 부모를 가지는 계층 구조와는 다르게, 다중 부모를 갖을 수 있다. 예를 들어, "그레이터 시애틀 구역" 지역을 위한 하나의 레코드는 하나의 사용자 뿐 아니라 이 지역에 사는 모든 사용자에 연결될 수 있다.
계층 모델은 1970년 대 사용된 모델로써, 레코드 트리로 데이터를 표현한다. JSON 처럼 일대다 관계는 잘 표현하지만, 다대다 관계는 잘 표현하지 못한다. 이후 관계형 모델과 네트워크 모델 진영의 논쟁이 시작되었다.
네트워크 모델에서 레코드 간 연결은 왜래 키보다는 포인터와 더 비슷하다. 레코드에 접근하는 유일한 방법은 최상위 레코드(root record)에서부터 연속된 경로를 따르는 방법이다. 이를 접근 경로(access path) 라고 한다.
코다실 모델에서 질의는 레코드 목록을 반복해 접근 경로를 따라 데이터베이스의 끝에서 끝가지 커서를 움직여 수행한다. 이런 수동 접근 경로 선택은 데이터베이스 징의와 갱신을 위한 코드가 복잡하고 유연하지 못한 문제가 있었다. 데이터 모델을 바꾸는 작업이 매우 어려운 일이었다.
관계형 모델은 단순히 외래 키를 사용해 특정 로우를 읽을 수 있다. 왜래 키 관계에 대해 신경 쓰지 않고 임의 테이블에 새 로우를 삽입할 수 있다.
query optimizer 는 질의의 실행 순서와 사용할 색인을 자동으로 결정한다. 이 선택이 실제로 "접근 경로" 지만, query optimizer 가 자동으로 선택하기에 애플리케이션 개발자가 고민할 필요가 없다.
문서 모델는 계층 모델과 마찬가지로 별도 테이블이 아닌 상위 레코드 내에 중첩된 레코드를 저장하는 방식으로 일대다 관계를 잘 표현한다.
다대일, 다대다 관계를 표현할 때, 관계형 모델과 문서 모델은 고유한 식별자로 참조하는 방식을 사용한다.
문서 모델 | 관계형 모델 | |
---|---|---|
장점 | - 스키마 유연성 - 지역성에 기인한 더 나은 성능: 데이터가 모여있어서 여러 쿼리와 조인을 수행하지 않고 한 번의 쿼리로 여러 데이터를 조회할 수 있음 - 애플리케이션에서 사용하는 데이터 구조와 비슷함 | - 조인, 다대일, 다대다 관계를 더 잘 지원함 |
읽기 스키마(schema-on-read)
문서 DB 는, 데이터 구조가 암묵적이고 데이터를 읽을 때만 해석되는 읽기 스키마를 사용한다.
읽기 스키마는 동적 타입 확인과 유사하다.
데이터 타입을 변경시, 단순히 코드를 추가하기만 하면 된다.
컬렉션 안의 항목이 모두 동일한 구조가 아닐 때 유리하다.
쓰기 스키마(scheam-on-write)
관계형 DB 는, 명시적이고, DB에 쓰여진 모든 데이터가 스키마를 따르고 있음을 보장하는 쓰기 스키마를 사용한다.
쓰기 스키마는 정적 타입 확인과 유사하다.
데이터 타입 변경시, ALTER TABLE
처럼 마이그레이션을 수행한다. 대부분의 관계형 DBS 은 수 밀리초 안에 ALTER TABLE
을 수행하지만, MySQL 은 전체 테이블을 복사하기 때문에 수 시간까지 중단시간이 발생할 수 있다.
스키마가 정적인 것은 득보다 실이 많다. 하지만, 모든 레코드가 동일한 구조이며 예상 가능하다면, 수조를 강제하기 위한 유용한 메커니즘이다.
DB 에서는 SQL 같은 선언형 질의 언어가 명령형 질의 API 보다 훨씬 좋다고 나타났다.
다대다 관계가 매우 일반적인 경우 그래프형 데이터 모델을 고려하라.
그래프형 데이터 모델은 두 유형의 객체로 이뤄진다.
정점(vertex) or 노드 or 엔티티
간선(edge) or 관계 or 호(arc)
그래프형 데이터 모델을 사용하는 대표적인 모델링 예시
그래프 모델 종류
그래프 선연형 질의 언어 종류
정점의 구성 요소
간선의 구성 요소
사이퍼(Cypher)는 속성 그래프를 위한 선언형 질의 언어.
모든 정보를 주어(subject) 서술어(predicate) 목적어(object) 세 부분 구문(three-part statements) 형식으로 저장한다.
subject 는 정점과 동등하다.
predicate 와 object 가 primitiv 값인 경우, subject 의 속성의 키, 값을 의미한다.
object 가 다른 subject 인 경우, subject 는 tail vertex, object 는 head vertex, predicate 은 edge 이다.
스파클 질의 언어는 RDF 데이터 모델을 사용한 트리플 저장소 질의 언어다.
...