데이터 중심 애플리케이션 설계 2장
1. 데이터 모델
- 다양한 유형의 데이터 모델이 있고 각 데이터 모델은 사용 방법에 대한 가정이 있다.
즉 어떤 동작은 쉽고 어떤 연산은 빠르고 다른 연산은 느리고, 어떤 데이터 변환은 자연스럽고 어떤 데이터 변환은 부자연스럽다.
- 하나의 데이터 모델을 완전히 익히는 것도 어려운 일이다. 그러나 데이터 모델은 그 위에서 소프트웨어가 할 수 있는 일과 할 수 없는 일에 많은 영향을 주므로 애플리케이션에 적합한 데이터 모델을 선택하는 작업이 상당히 중요하다.
- 관계형 데이터 모델, 문서 모델, 그래프 기반 데이터 모델(속성 그래프, 트리플 저장소 모델)을 2장에서 다룬다.
관계형 모델(SQL)
- 관계(relation=sql에서 table)로 구성되고 각 관계는 순서없는 튜플(tuple, sql = row)의 모음
비관계형 데이터 모델(NoSQL)
- 임피던스 불일치(impedence mismatch) 해결을 위한 NoSQL 출현
* 임피던스 불일치: 애플리케이션 코드와 데이터베이스 모델 객체(테이블,로우,컬럼)사이에 전환 계층이 필요한 경우
- NoSQL 모델을 쓰면 불필요한 계층 변환 작업이 사라짐
1 문서 모델
- 애플리케이션이 주로 일대다 관계(트리 구조 데이터)거나 레코드간 관계가 없는 경우 적합
- 데이터가 문서 자체에 포함돼 있으면서 하나의 문서와 다른 문서 간 관계가 거의 없는 경우
2 그래프형 데이터 모델
- 다대다 관계가 매우 일반적일 경우 적합
어떤 모델을 사용하는 것이 적합할까?
- 예시: 관계형 스키마에서 이력서의 표현

방법 1: 전통적인 SQL 모델을 사용,
정규화된 테이블을 만들기(상단 그림)
방법 2: XML 데이터 지원이 추가된 SQL 데이터타입 사용,
단일 로우에 다중 값을 저장하고 문서 내 질의와 색인이 가능해짐
방법 3: 직업,학력,연락처 정보를 json이나 xml 문서로 부호화하여 데이터베이스 텍스트 칼럼에 저장하고, 애플리케이션이 구조와 내용을 해석하게 함
일반적으로 부호화된 칼럼의 값을 질의하는 데 데이터베이스를 사용할 수 없음
방법 4: 문서 데이터베이스 사용(지역성 UP)
{
"user_id": 251,
"first_name": "Bill",
"last_name": "Gates",
"summary": "Co-chair of the Bill & Melinda Gates... Active blogger.",
"region_id": "us:91",
"industry_id": 131,
"photo_url": "/p/7/000/253/05b/308dd6e.jpg",
"positions": [
{"job_title": "Co-chair", "organization": "Bill & Melinda Gates Foundation"},
{"job_title": "Co-founder, Chairman", "organization": "Microsoft"}
],
"education": [
{"school_name": "Harvard University", "start": 1973, "end": 1975},
{"school_name": "Lakeside School, Seattle", "start": null, "end": null}
],
"contact_info": {
"blog": "http://thegatesnotes.com",
"twitter": "http://twitter.com/BillGates"
}
}
관계형 데이터베이스와 달리 조인 필요 X 질의 하나로 충분
문서 데이터베이스
- 스키마리스(Schemaless)?
정확히는 schema-on-read (반대말 schema-on-write)
스키마리스는 언제 강점을 가지는가?
- 예시: 하나의 필드에 사용자의 전체 이름 저장 --> 성과 이름을 분리하여 저장하도록 변경
- schema on read의 경우: 간단함
if (user && user.name && !user.first_name) {
// Documents written before Dec 8, 2013 don't have first_name
user.first_name = user.name.split(" ")[0];
}
- schema on write(rdbms)의 경우: migration 필요, 느리고 중단시간을 요구하여 좋지 않음
ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL
UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL
2. 질의 언어
명령형 언어
- 보통의 프로그래밍 언어
- 특정 순서로 특정 연산을 수행하도록 컴퓨터에 지시함. (방법)
선언형 언어
- SQL, (CSS, XSL : 문서의 스타일을 지정하기 위 한 선언형 언어 )
- 알고자 하는 데이터의 패턴, 즉 결과가 충족해야 하는 조건과 데이터를 어떻게 변환(예를 들어 정렬, 그룹화, 집계) 할지를 지정 (목적)
- 어떤 색인과 어떤 조인 함수를 사용할지, 질의의 다양한 부분을 어떤 순서로 실행할지(방법)를 결정하는 것은 데이터베이스 시스템의 질의 최적화기가 할 일이다.
- 장점
- 일반적으로 명령형 API보다 더 간결하고 쉽게 작업할 수 있음
- 데이터베이스 엔진의 상세 구현이 숨겨져 있어 질의를 변경하지 않고도 데이터베이스 시스템의 성능을 향상시킬 수 있음
명령형 언어 vs 선언형 언어
- 예시: 동물 목록에서 상어만 반환하기
- 명령형 언어 (대부분의 프로그래밍 언어가 명령형 언어임)
function getSharks() {
var sharks = [];
for (var i = 0; i < animals.length; i++) {
if (animals[i].family === "Sharks") {
sharks.push(animals[i]);
}
}
return sharks;
}
- 선언형 언어
SELECT * FROM animals WHERE family = 'Sharks';
선언형, 명령형 질의
- 선언형: DB, Web Browser에서 Good
- 명령형: 문서형 DB에서 Good (절대적인것은 아님)
- ex. CSS, XSL
- ex. 다음 웹 문서에서
<ul>
<li class="selected">
<p>Sharks</p>
<ul>
<li>Great White Shark</li>
<li>Tiger Shark</li>
<li>Hammerhead Shark</li>
</ul>
</li>
<li>
<p>Whales</p>
<ul>
<li>Blue Whale</li>
<li>Humpback Whale</li>
<li>Fin Whale</li>
</ul>
</li>
</ul>
- 선언형 질의 사용시(CSS) : 간단
li.selected > p {
background-color: blue;
}
- 명령형 질의 사용시 : 복잡
var liElements = document.getElementsByTagName("li");
for (var i = 0; i < liElements.length; i++) {
if (liElements[i].className === "selected") {
var children = liElements[i].childNodes;
for (var j = 0; j < children.length; j++) {
var child = children[j];
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "P") {
child.setAttribute("style", "background-color: blue");
}
}
}
}
맵리듀스 질의
* postgre sql : 선언형
SELECT date_trunc('month', observation_timestamp) AS observation_month,
sum(num_animals) AS total_animals
FROM observations
WHERE family = 'Sharks'
GROUP BY observation_month;
* MongoDB 맵리듀스 기능
db.observations.mapReduce(
function map() {
var year = this.observationTimestamp.getFullYear();
var month = this.observationTimestamp.getMonth() + 1;
emit(year + "-" + month, this.numAnimals);
},
function reduce(key, values) {
return Array.sum(values);
},
{
query: { family: "Sharks" },
out: "monthlySharkReport"
}
);
* 데이터 예시(문서 2개)
{
observationTimestamp: Date.parse("Mon, 25 Dec 1995 12:34:56 GMT"),
family: "Sharks",
species: "Carcharodon carcharias",
numAnimals: 3
}
{
observationTimestamp: Date.parse("Tue, 12 Dec 1995 16:17:18 GMT"),
family: "Sharks",
species: "Carcharias taurus",
numAnimals: 4
}
* 맵리듀스 질의 결과
emit("1995-12", 3)
emit("1995-12", 4)
=> reduce("1995-12", [3,4])
=> return 7 (monthlySharkReport)
* 몽고 DB에서 지원하는 집계파이프라인 (선언형 질의)
db.observations.aggregate([
{ $match: { family: "Sharks" } },
{ $group: {
_id: {
year: { $year: "$observationTimestamp" },
month: { $month: "$observationTimestamp" }
},
totalAnimals: { $sum: "$numAnimals" }
} }
]);
-> 분산환경이라고 꼭 맵리듀스를 사용하는 건 아님
-> map, reduce 함수 선정이 어려워서 몽고DB에서 집계 파이프라인 제공하고 있음. (질의 최적화기가 질의 성능을 높임)
3. 그래프형 데이터 모델
속성 그래프

사이퍼 질의 언어 : 속성 그래프에 적합
데이터 생성
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..]-> (us:Location {name:'United States'}),
(person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
RETURN person.name
관계형 스키마를 사용해 속성 그래프 표현하기
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);
SQL로 속성 그래프 질의 : with recursive - 복잡도 UP
WITH RECURSIVE
-- in_usa is the set of vertex IDs of all locations within the United States
in_usa(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties->>'name' = 'United States'
UNION
SELECT edges.tail_vertex FROM edges
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'within'
),
-- in_europe is the set of vertex IDs of all locations within Europe
in_europe(vertex_id) AS (
SELECT vertex_id FROM vertices WHERE properties->>'name' = 'Europe'
UNION
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'within'
),
-- born_in_usa is the set of vertex IDs of all people born in the US
born_in_usa(vertex_id) AS (
SELECT edges.tail_vertex FROM edges
JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
WHERE edges.label = 'born_in'
),
-- lives_in_europe is the set of vertex IDs of all people living in Europe
lives_in_europe(vertex_id) AS (
SELECT edges.tail_vertex FROM edges
JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
WHERE edges.label = 'lives_in'
)
SELECT vertices.properties->>'name'
FROM vertices
-- join to find those people who were both born in the US *and* live in Europe
JOIN born_in_usa ON vertices.vertex_id = born_in_usa.vertex_id
JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id;
트리플 저장소 모델
(주어, 서술어, 목적어) 형식으로 저장함
- 터틀 형식으로 데이터 표현시
@prefix : <urn:example:>.
_:lucy a :Person; :name "Lucy"; :bornIn _:idaho.
_:idaho a :Location; :name "Idaho"; :type "state"; :within _:usa.
_:usa a :Location; :name "United States"; :type "country"; :within _:namerica.
_:namerica a :Location; :name "North America"; :type "continent".
스파클 질의 언어
- RDF(Resource Description Framework. 자원 기술 프레임워크) 데이터 모델을 사용한 트리플 저장소 질의 언어
- 사이퍼와 구조가 매우 유사함
