데이터 중심 애플리케이션 설계 | 02장. 데이터 모델과 질의 언어
발표자 : 김정수, 박수민
챕터의 목적
- 각 데이터 모델의 대한 차이점과 특성을 이해한다.
- 만들고자 하는 애플리케이션에 어떠한 데이터 모델이 적절한지 판단할 수 있게 한다.
RDB vs NoSQL
- 2장에서는 데이터 모델의 차이점에만 집중한다.
- 그 외 것: 내결함성(5장), 동시성 처리(7장)
- NoSQL
- 스키마 유연성, 지역성에 기인한 더 나은 성능
- 일부 애플리케이션의 경우, 애플리케이션에서 사용하는 데이터 구조와 더 가깝다
- RDB
- 조인, N:1, N:N 관계를 잘 지원함
NoSQL
문서와 비슷한 구조를 여러 테이블로 분리하는 관계형 기법의 문제점
- 다루기 힘든 스키마
- 불필요하고 복합한 애플리케이션 코드 발생
문서 모델의 제한
- 문서 내 중첩(nested) 항목을 바로 참조할 수 없다. (관계형은 관련된 key만 있다면 바로 참조 가능, 그러나 key를 찾기 위한 불필요한 선회가 발생할 수 도 있다.)
- 예) 사용자의 상세 주소를 참조해야할 경우
- user.address.road.detail
- 중첩이 너무 깊지 않다면 일반적으로 문제가 되지 않는다.
- 예) 사용자의 상세 주소를 참조해야할 경우
- 미흡한 조인 지원
- 애플리케이션의 경우에 따라 문제가 될 수도 아닐 수도 있다.
- 다대다 관계가 필요할 경우 비효율
- 애플리케이션의 복잡도가 증가
- 애플리케이션에서 처리하는 것은 데이터베이스 내의 특화된 코드로 수행되는 것 보다 성능이 안 좋다.
Tip
상호 연결이 많은 경우
그래프 모델 > 관계형 모델 »> 문서 모델
스미카 유연성
특정 스키마를 강요하지 않는다.
- 임의의 키와 값을 문서에 추가할 수 있다.
- 문서에 포함된 필드의 존애 여부를 보장하지 않는다.
Tip
스키마를 강요하지 않는 것이지 스키마가 없는 것이 아니다.
- 관계형 모델 = 쓰기 스키마(schema-on-write)
- 데이터 구조는 명시적
- 데이터베이스는 모든 데이터가 스키마를 따르고 있음을 보장한다.
- 문서형 모델 = 읽기 스키마(schema-on-read)
- 데이터 구조는 암묵적
- 데이터를 읽을 때만 해석된다. (쓰기에는 아무런 제약이 없다.)
접근 방식에 따른 처리 방법 차이
예) user에 first_name을 추가할 경우
// 문서형 모델
// 애플리케이션에서 데이터를 읽는 경우를 처리하는 코드만 있으면 된다.
// (데이터베이스의 변경을 요구하지 않는 방법이지만 좋은 방법 같진 않다. 그냥 방법의 차이를 이해하는 정도로만 여기자.)
if (user?.name && user?.first_name == null) {
user.first_name = user.name.split(" ")[0]
}
// 관계형 모델
// 별도의 스키마 변경 및 데이터 마이그레이션 작업 필요
ALTER TABLE users ADD COLUMN first_name test;
UPDATE users SET first_name = substring_index(name, ' ', 2);
Tip
스키마 변경은 느리고 중단시간을 요구한다.
- 예외적으로 MySQL의 경우 스키마 변경은 성능이 매우 안 좋다.
Tip
컬럼 추가의 경우 기존의 모든 레코드에 데이터가 없으므로
- 일단 nullable 속성으로 null 데이터를 가지게한 후
- 데이터를 업데이트하고 (데이터베이스 크기가 큰 경우 업데이트 작업이 오래걸릴 수 있다.)
- 다시 본래의 속성(nullable or non-null)로 변경해야 한다.
저장소 지역성
지역성: 데이터, 프로그램 등에 대해서 특정 부분에 집중적으로 접근하는 성질
자주 전체 문서에 접근해야 하는 경우 저장소 지역성 활용할 수 있다.
- 정규화된 관계형 모델의 구조보다 역정규화된 문서형 모델의 구조가 성능상 이점이 있다
- 관계형 모델은 검색을 위해 다중 색인 검색이 필요 → 더 많은 디스크 탐색 필요 → 더 많은 시간 소요
- 한 번에 해당 문서의 많은 부분을 필요로 하는 경우에만 적용
- 문서형 모델에서는 작은 부분에만 접근해야 하는 경우에도 전체 문서를 저장해야 하기 때문에 큰 문서에서는 낭비일 수 있다.
일반적으로 문서를 최대한 작게 유지하면서 문서 크기의 증가를 최소화할 것을 권장
- 이러한 성능 제한 때문에 문서형 모델이 유용한 상황이 많이 줄어든다.
Tip
관계형 모델에서도 지역성이 적용된 개념이 존재한다.
- 스패너(구글의 데이터베이스): 인터리브 테이블
- 오라클: 다중 테이블 색인 클러스터 테이블
- 카산드라, HBase: 컬럼 패밀리
RDB와 NoSQL의 통합
(서로 비슷한 기능을 제공한다는 의미)
RDB
- MySQL을 제외한 대부분의 RDB는 XML을 지원하여 문서형 모델과 매우 비슷한 데이터 모델을 사용할 수 있다.
- Postgre 9.3+, MySQL 5.7+, DB2 10.5+: JSON 문서에 대해 비슷한 기능을 제공
NoSQL
- 리싱크DB: 쿼리에서 관계형 조인을 지원
- 몽고DB: 드라이버가 자동으로 데이터베이스 참조를 확인 (클라이언트에서 조인 수행, 네트워크 왕복이 추가로 필요, 최적화가 덜 되어 있어서 조인 성능이 좋지 않다)
질의 언어(Query Language)
선언형 vs 명령형
- SQL의 선언형
- IMS, 코다실의 명령형 (안 중요)
선언형 질의 언어의 장점
- 알고자 하는 데이터의 패턴만 지정하면 된다. (어떤 색인과 조인 함수를 사용할지, 어떤 순서로 실행할 지는 데이터베이스의 질의 최적화가 할 일이다.)
- 충족해야 하는 조건
- 데이터의 변환 (정렬, 그룹화, 집계 등)
- 일반적으로 명령형 질의 API보다 더 간결하고 쉽게 작업할 수 있다.
- 데이터베이스 엔진의 상세 구현이 추상화되어 있어서 쿼리를 변경하지 않고도 데이터베이스의 성능을 향상시킬 수 있다.
웹의 예제
HTML에 style을 적용할 경우
- CSS는 선언형
- JS로 DOM API를 사용하는 것은 명령형
- 선언형이 명령형보다 가독성, 생산성, 유지보수성에서 우수하다.
Tip
데이터베이스에서도 SQL같은 선언형 질의 언어가 명령형 질의 API보다 “훨씬” 좋다고 한다.맵리듀스 질의
(안 중요)
함수형 프로그래밍에 있는 map, reduce 함수를 기반으로 한다.
단점
- 질의를 작성하는 것 보다 어렵다
- 선언형 질의 언어는 질의 최적화기가 질의 성능을 높일 수 있는 기회를 제공한다.
몽고DB 2.2에서 집계 파이프라인(aggregate pipeline)이라는 선언형 질의 언어 지원을 추가하였다.
그래프형 데이터 모델
애플리케이션의 데이터 모델이 주로 1:N(트리 구조 데이터)거나 엔티티간 관계가 없다면 문서형 모델이 적합하다.
그러나 N:N 관계가 매우 일반적인 경우라면 그래프형 모델이 적합하다.
Tip
관계형 모델은 단순한 N:N 관계까지는 적합하나 그 이상의 복잡한 N:N 관계를 다루기에는 적합하지 않다. (복잡도가 상승)그래프 구성 요소
정점(Vertex, 혹은 노드나 엔티티) SpringData에서는 노드엔티티라고 한다. (@NodeEntity) 간선(Edge 혹은 관계나 호(arc))
예시)
- 소셜 그래프
- 정점 = 사람, 간선 = 친구관계
- 웹 그래프
- 정점 = 웹페이지, 간선 = 링크
- 도로 네트워크
- 정점 = 교차로, 간선 = 도로
- 페이스북
- 여러 유형의 정점과 간선을 단일 그래프로 유지
- 정점 = 사람, 장소, 이벤트, 체크인, 코멘트 등
- 간선 = 사람간 관계, 체크인이 발생한 위치, 누가 어떤 포스트에 코멘트 했는지, 누가 이벤트에 참석했는지
만약 페이스북을 관계형 모델로 만든다면?
- 사람, 장소, 이벤트, 체크인, 코멘트 등등이 모두 테이블로 정의될 것이다.
- 그리고 각 테이블 간의 엄청 복잡한 관계들이 필요할 것이다.
- 그래프 모델을 적용하는 순간 이러한 복잡성들이 단순화된다.
그래프 모델의 종류
- 속성 그래프 모델
- 트리플 저장소 모델 (안 중요)
그래프용 선언형 질의 언어
- 사이퍼(Cypher)
- 스파클
- 데이터로그
속성 그래프
정점의 구성 요소
- id
- 유출(outgoing) 간선 집합
- 유입(incoming) 간선 집합
- 속성 컬렉션 (키-값 쌍)
간선의 구성 요소
- id
- 간선이 시작하는 정점(tail vertex)
- 간선이 끝나는 정점(head vertex)
- 두 정점 간 관계 유형을 설명하는 레이블
- 속성 컬렉션 (키-값 쌍)
Tip
간선의 방향은 tail -> head 방향이다 (그래서 tail이 시작, head가 끝)관계형 스키마를 사용해 속성 그래프 표현하기
CREATE TABLE vertices (
vertex_id integer PRIMARY KEY,
properties json
)
CREATE TABLE edges (
edge_id integer PRIMARY KEY,
tail_vertex integer REFERENCES vertices(vertex_id),
head_vertex integer REFERENCES vertices(vertex_id),
label text,
properties json
)
CREATE INDEX edges_tails ON edges(tail_vertex)
CREATE INDEX edges_heads ON edges(head_vertex)
- 정점은 다른 정점과 간선으로 연결된다
- 특정 유형과 관련 여부를 제한하는 스키마는 없다.
- 정점이 주어지면 정점의 유입과 유출 간선을 효율적으로 찾을 수 있고 그래프를 순회할 수 있다.
- 다른 유형의 관계에 서로 다른 레이블을 사용하면 단일 그래프에 다른 유형의 정보를 저장하면서 데이터 모델을 깔끔하게 유지할 수 있다.
이런 기능을 통해 그래프는 데이터 모델링을 위한 많은 유연성을 제공한다.
그래프는 발전성이 좋아서 애플리케이션에 기능을 추가하는 경우 데이터 구조 변경을 수용하게끔 그래프를 쉽게 확장할 수 있다.
사이퍼(Cypher)
속성 그래프를 위한 선언형 질의 언어
- 네오포제이(Neo4j) 그래프 데이터베이스용으로 만들어졌다.
- wiki: 사이퍼는 식으로 나타내는, 프로퍼티 그래프의 효율적인 질의 및 업데이트를 허용하는 선언형 그래프 질의어이다. 사이퍼는 상대적으로 단순하지만 매우 강력한 언어이다. 매우 복잡한 데이터베이스 쿼리들은 사이퍼를 통해 쉽게 표현이 가능하다.
데이터 모델 생성
CREATE
(NAmerica:Location {name:’North America’, type:’continent’}),
(USA:Location {name:’United States’, type:’country’}),
(Idaho:Location {name:’Idaho’, type:’state’}),
(Lucy:Person {name:’Lucy’}),
(Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
(Lucy) -[:BORN_IN]-> (Idaho)
문제. 미국에서 유럽으로 이민 온 모든 사람들의 이름 찾기
MATCH
(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (USA:Location {name:’United States’}),
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (EU:Location {name:’Europe’})
RETURN person.name
질의 실행 방법
- 모든 사람을 조회로 시작 -> 사람들의 출생지와 거주지를 확인 -> 맞는 사람들만 반환
- 2개의 Location으로 시작 -> 미국과 유럽의 모든 위치 찾기를 진행 -> leaf 에 해당하는 정점 중 하나에 BORN_IN, LIVES_IN 유입 간선을 통해 발견된 사람들을 반환
선언형의 장점
- 수행 방법에 대해서 자세히 기술할 필요가 없음
- 질의 최적화기가 알아서 가장 효율적인 전략을 자동으로 선택
관계형 모델에서 위의 쿼리를 한다면?
- 가능하지만 어려움
- 보통 관계형 모델에서는 쿼리에 필요한 조인을 미리 알고 있다. (FROM 절에서 선언)
- 그러나 그래프 쿼리에서는 찾고자 하는 정점을 위해 여러 간선을 순회해야 한다.
- 순회가 몇 번 인지 모른다.
- 조인 수를 미리 고정할 수 없다.
사이버 쿼리에서 -[:WITHIN*0..]->
로 순회를 매우 간결하게 표현한다. (*0은 0회 이상을 의미)
SQL1999 이후로 가변 순회 경로에 대한 쿼리를 재귀 공통 테이블 식(Recursive common table expression, 이하 Recursive CTE)(WITH RECURSIVE 문)을 사용해 표현할 수 있다.
- Postgre, DB2, Oracle, SQL Server에서 지원함
- MySQL 5.7+ 에서는 지원
트리플 저장소와 스파클
(안 중요)
속성 그래프 모델과 거의 동일
- 단지 동일 개념에 대한 용어만 다르다
데이터를 주어(subject), 서술어(predicate), 목적어(object)로 매우 간단한 세 부분 구문(three-part statements) 형식으로 저장한다.
- 주어 = 정점
- 목적어 = 다른 정점 or primitive datatype의 데이터
- 서술어 = 간선
Turtle
- wiki: Terse RDF Triple Language (Turtle)는 Resource Description Framework 데이터 모델에서 데이터를 표현하기 위한 구문 및 파일 형식입니다. Turtle 구문은 RDF 쿼리 언어인 SPARQL의 구문과 유사하다.
스파클은 RDF 데이터 모델을 사용한 트리플 저장소 질의 언어이다.
데이터 로그
데이터 모델이 트리플 저장소 모델과 유사
주어, 서술어 목적어 -> 서술어(주어, 목적어)
그래프 데이터베이스 순위
https://db-engines.com/en/ranking/graph+dbms
한 줄 요약 : 그래프 데이터베이스를 사용하고 싶을 경우 Neo4J를 사용하면 된다.
Neo4J
SpringData 지원
https://spring.io/guides/gs/accessing-data-neo4j/
spring-boot-starter-data-neo4j
@NodeEntity
data class Food(
@Id
val id: Long? = null,
val name: String,
}
interface FoodRepository : Neo4jRepository<Food, Long>
@NodeEntity
data class Store(
@Id
val id: Long? = null,
val name: String,
@Relationship(type = "has")
val foods: Set<Food>,
}
interface StoreRepository : Neo4jRepository<Store, Long>
Summary
- 역사적으로 데이터를 하나의 큰 트리로 표현하려고 노력
- N:N 관계 표현에 적절하지 않음 → 관계형 모델 등장
- 최근(?) 관계형 모델에도 적합하지 않은 애플리케이션이 있다는 사실을 발견 → 비관계형 데이터 모델인 NoSQL 등장
NoSQL은 두 가지의 주요 갈래가 있다.
- 문서형 모델
- 모든 데이터가 문서에 포함하고 문서간의 관계가 거의 없는 경우 사용
- 그래프형 모델
- 문서형 모델과 정반대로 모든 것이 잠재적으로 관련 있는 경우 사용
세 가지 모델 모두 현재 널리 사용
- 한 모델을 다른 모델로 흉내낼 수 있지만 대부분 그 결과는 엉망이다.
문서형 모델, 그래프형 모델의 장점
- 저장할 데이터를 위한 스키마를 강제하지 않음 → 변화하는 요구사항에 맞춰 애플리케이션을 쉽게 변경할 수 있다.