데이터 중심 애플리케이션 설계를 독파하며 정리하는 글입니다.
데이터 모델은 소프트웨어 개발에서 중요한 부분임.
소프트웨어가 어떻게 작성됐는지 뿐만 아니라, 해결하려는 문제를 어떻게 생각해야 하는지에 대해서도 영향을 미치기 때문.
또한, 데이터 모델은 그 위에서 소프트웨어가 할 수 있는 일과 할 수 없는 일에 영향을 주므로 애플리케이션에 적합한 데이터 모델을 선택하는 작업이 상당히 중요함.
# 관계형 모델과 문서 모델
오늘날 가장 잘 알려진 데이터 모델은 관계형 모델을 기반으로 한 SQL임.
SQL에서 데이터는 테이블(table)이라 불리는 관계(relation)로 구성되고, 각 관계는 로우(row)라 불리는 순서 없는 튜플(tuple)의 모음이다.
관계형 데이터 베이스의 근원은 비즈니스 데이터 처리에 있음.
- 트랜잭션 처리 : 영업이나 은행 거래, 항공 예약, 창고에 재고 보관
- 일괄 처리 : 고객 송장 작성, 급여 지불, 보고
관계형 데이터 베이스의 목표는 정리된 인터페이스 뒤로 구현 세부 사항을 숨기는 것.
수년 동안 데이터 저장과 질의를 위해 많은 접근 방식이 경쟁했지만, 여전히 관계형 모델과 관계형 데이터베이스를 통해 대부분의 서비스가 제공되고 있음.
NoSQL의 탄생
2010년대에 관계형 모델의 우위를 뒤집기 위해 NoSQL이 등장했다.
이름에 대해 말이 많은데 NoSQL은 원래 오픈소스, 분산 환경, 비관계형 데이터베이스 밋업용 인기 트위터 해시태그 였고, 이게 확산되어 인기 있는 여러 데이터베이스 시스템과 NoSQL이 연관되어 버림.
그래서 Not Only SQL로 재해석됨.
NoSQL 데이터베이스가 채택된 데는 다음과 같은 이유가 있음.
- 관계형 모델에서 지원하지 않는 특수 질의 동작
- 관계형 스키마의 제한에 대한 불만과 더욱 동적이고 표현력이 풍부한 데이터 모델의 니즈
애플리케이션은 저마다 요구사항이 다르므로, 미래에는 관계형 데이터베이스가 폭넓은 다양함을 가진 비관계형 데이터베이스와 함께 사용될 것임.
이런 개념을 다중 저장소 지속성(polyglot persistence)라 함.
객체 관계형 불일치
오늘날 대부분의 애플리케이션은 객체지향 프로그래밍 언어로 개발하고,
그에 따라 데이터를 관계형 테이블에 저장하려면 애플리케이션 코드와 데이터베이스 모델 객체(테이블, 로우, 칼럼) 사이에 거추장스러운 전환 계층이 필요하다.
이런 모델 사이의 분리를 임피던스 불일치(impedance mismatch)라고 함.
ActiveRecord나 Hibernate 같은 ORM 프레임워크가 전환 계층에 필요한 boilerplate 코드의 양을 줄이지만, 두 모델 간의 차이를 완벽히 숨길수는 없음.
위 그림은 링크드인 프로필로 관계형 스키마에서 이력서를 어떻게 표현하는지 보여준다.
프로필 전체는 고유 식별자인 user_id로 식별한다.
first_name과 last_name 같은 필드는 사용자마다 정확하게 하나씩 존재해 users 테이블의 칼럼으로 모델링이 가능하지만,
경력과 학력 기간, 연락처 정보는 하나 이상인 일대다(one-to-many) 관계여서 first_name, last_name처럼 모델링이 불가능하다.
이런 관계를 구현하기 위한 방법으로는 다음과 같다.
- 경력, 학력 기간, 연락처 정보를 개별 테이블로 만들어 외래키로 users 테이블을 참조함. (일반적인 방법)
- SQL 표준 마지막 버전에서 구조화된 데이터타입과 XML 데이터에 대한 지원이 추가되어, 단일 로우에 다중 값을 저장할 수 있음.
- JSON이나 XML로 부호화해서 데이터베이스에 저장한 다음 애플리케이션이 구조와 내용을 해석하게 함. (데이터베이스에서 부호화된 칼럼 값 질의 불가능)
근데, 이런 이력서와 같은 데이터 구조는 모든 내용을 갖추고 있는 문서라서 JSON 표현에 매우 적합함.
몽고 DB, 리싱크 DB, 카우치 DB 같은 문서 지향 데이터베이스는 JSON 데이터 모델을 지원하므로, 다음과 같이 데이터를 저장할 수 있다.
{
"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"
}
}
이와 같은 JSON 표현은 다중 테이블 스키마보다 더 나은 지역성(locality)을 갖는데,
관계형 예제에서 프로필을 가져오려면 다중 질의(각 테이블마다 user_id로 질의)하거나, users 테이블과 그 하위 테이블 간에 다중 조인을 수행해야 한다. 그러나 JSON 표현에서는 모든 관련 정보가 한 곳에 있어 질의 하나로 충분하다.
다대일과 다대다 관계
앞전 그림을 보면 region_id와 industry_id는 문자열 데이터가 아닌 ID로 주어진 걸 볼 수 있다.
그 이유는 지역과 업계를 입력하는 텍스트 필드가 있다면 문자열 저장이 합리적이지만, 드롭다운 리스트나 자동 완성 기능으로 표준 목록을 제공하여 사용자가 선택하게 하기 위해서이다. 이 방식의 장점은 다음과 같다.
- 프로필 간 일관된 스타일과 철자
- 모호함 회피 (이름이 같은 여러 도시가 있는 경우)
- 갱신의 편의성 (이름이 한 곳에만 저장되므로 변경하기 쉽다)
- 현지화 지원 (다른 언어로 번역할 때)
- 더 나은 검색 (시애틀이 워싱턴에 있다는 사실을 부호화, 시애틀이 워싱턴에 있지만, 그레이터 시애틀 구역이라는 문자열만으로는 워싱턴을 식별하지 못함)
또한 ID와 텍스트 문자열의 저장 여부는 중복의 문제임.
- ID 사용 : 의미 있는 정보를 한 곳에 저장한 후 그것을 참조하는 곳들에 해당 ID 사용
- 텍스트 문자열 사용 : 해당 정보를 사용하는 모든 레코드에서 중복 저장됨
다만 ID를 사용할지라도 ID자체가 의미를 가지는 경우 ID 자체를 변경해야 할 수도 있으며, 정보가 중복돼 있을 시 모든 중복 항복을 변경해야 한다. (이는 쓰기 오버헤드와 불일치의 위험이 있으며, 이런 중복을 제거하는 것이 데이터 정규화의 핵심 개념이다.)
중복된 데이터를 정규화하려면 다대일(many-to-one) 관계가 필요한데, 다대일 관계는 문서 모델에 적합하지 않다.
관계형 데이터베이스에선 조인을 쓰는 방식이 쉽고 일반적이지만, 문서 데이터베이스는 조인 지원을 잘 안 해주기 때문이다.
(대신 문서 데이터베이스에서는 일대다 트리 구조를 위해 조인이 필요하지 않다.)
만약, 데이터베이스 자체가 조인을 지원하지 않는다면 다중 질의를 통해 애플리케이션 코드단에서 조인 로직을 만들어야 한다.
위 그림은 다대다(many-to-many) 관계가 왜 필요한지 보여준다.
각 점선 내의 데이터는 하나로 묶을 수 있지만 조직과 학교, 다른 사용자는 참조로 표현해야 하며 질의 시 조인이 필요하다.
관계형 데이터베이스와 오늘날의 문서 데이터베이스
둘을 비교하는 경우 내결함성과 동시성 처리를 포함해 고려해야 할 차이점이 많지만, 다음 포스팅에서 다뤄보기로 하고,
우선 데이터 모델의 차이점에만 집중해 본다.
관계형 데이터베이스를 선호하는 주요 이유는 다음과 같다.
- 조인, 다대일, 다대다 관계를 더 잘 지원함
문서 데이터베이스를 선호하는 주요 이유는 다음과 같다.
- 스키마 유연성, 지역성에 기인한 더 나은 성능
- 애플리케이션에 사용하는 데이터 구조와 가까움
애플리케이션에서 데이터가 문서와 비슷한 구조(일대다 관계 트리로 보통 한 번에 전체 트리를 적재)라면 문서 모델이 좋다.
문서와 비슷한 구조를 여러 테이블로 나누어 찢는 관계형 기법은 다루기 힘든 스키마와 불필요하게 복잡한 애플리케이션 코드를 발생시킨다.
다만, 문서 모델에는 제한이 있는데 문서 내 중첩 항복을 바로 참조할 수 없고, 조인 지원이 미흡하기에 적용할 애플리케이션에 적합한지 따져봐야 한다. (예를 들어 이벤트를 기록하는 분석 애플리케이션에서는 다대다 관계가 필요하지 않다.)
또한, 다대다 관계를 사용한다면 문서 모델은 매력이 떨어지는데, 비정규화로 조인의 필요성을 줄일 순 있지만 애플리케이션 코드 단에서 비정규화된 데이터의 일관성 유지를 위해 추가 작업이 필요하다.
그리고 조인 또한 애플리케이션 코드단에서 흉내 낼 수 있지만 복잡도가 애플리케이션으로 이동하고 데이터베이스에 특화된 코드로 수행되는 조인보다 성능이 안 좋기 때문이다.
어떤 데이터 모델이 애플리케이션에 적합한지는 데이터 항목 간에 존재하는 관계 유형에 따라 다르다.
상호 연결이 많은 데이터의 경우 문서 모델은 별로고, 관계형 모델은 무난하며 그래프 모델이 적합하다.
문서 모델에서의 스키마 유연성
문서 데이터베이스는 종종 스키마리스(schemaless)로 불리지만 오해의 소지가 있다.
구조의 유형을 어느 정도 가정한 암묵적인 스키마가 있지만 강요하지 않을 뿐이고, 조금 더 정확한 용어로는 다음과 같다.
- 쓰기 스키마(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.sptit(" ")[ ];
}
반면, 정적 타입의 데이터베이스 스키마에서는 다음과 같이 마이그레이션(migration)을 수행해야만 한다.
ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = sptit_part(name, ' ', ); -- PostgreSQL
UPDATE users SET first_name = substring_index(name, ' ', ); -- MySQL
이러한 스키마 변경은 느리고 중단시간을 요구하는 문제가 있다. (현재 대부분의 관계형 데이터베이스 시스템은 ALTER TABLE문을 수 밀리초 안에 수행한다.)
MySQL은 예외적으로 ALTER TABLE시에 전체 테이블을 복사해(COPY 방식의 ALGORITHM) 큰 테이블을 변경할 때 수 분에서 수 시간까지 중단시간이 발생할 수도 있다.
다만 5.6 이상의 버전에서는 LOCK(NONE)과 ALGORITHM(INPLACE) 절을 이용하여 온라인 스키마 변경이 가능하다.
(default는 INPLACE이며, INPLACE로 처리가 불가능할 시 COPY방식을 사용한다.)
따라서 서비스의 데이터 구조가 동일한지 유동적인지를 판단하여, 스키마리스 데이터베이스를 사용하는 것이 적합한지 아닌지 판단해야 한다.
문서 데이터 베이스와 관계형 데이터베이스의 통합
관계형 데이터베이스와 문서 데이터베이스는 시간이 지남에 따라 점점 비슷해지고 있다.
관계형 데이터베이스인 PostgreSQL은 9.3 버전부터 MySQL은 5.7 버전부터 JSON 문서 지원 기능을 제공하고, 문서 데이터베이스인 RethinkDB는 질의 언어에서 관계형 조인을, MongoDB 드라이버는 자동으로 데이터베이스 참조를 확인한다.
이러한 관계형과 문서의 혼합 모델(데이터를 문서처럼 다룰 수 있고 관계형 질의를 수행함)은 애플리케이션이 필요에 따라 가장 적합한 기능을 조합해 사용할 수 있게 하며, 미래 데이터베이스들이 가야 할 올바른 길로써 긍정적이라 볼 수 있다.
# 데이터를 위한 질의 언어
관계형 모델이 등장했을 때 선언형 질의 언어인 SQL과 같이 데이터를 질의하는 새로운 방법도 함께 나타남.
일반적으로 많이 사용하는 프로그래밍 언어는 명령형 언어로, 특정 순서로 특정 연산을 수행하게끔 컴퓨터에게 지시한다.
SQL이나 관계 대수 같은 선언형 질의 언어에서는 목표를 달성하기 위한 방법이 아니라 알고자 하는 데이터의 패턴 같이 결과가 충족해야 하는 조건과 데이터를 어떻게 변환(정렬, 그룹화, 집계)할지를 정하기만 하면 된다.
어떤 색인과 어떤 조인 함수를 사용할지, 어떤 순서로 질의를 실행할지는 데이터베이스 시스템의 질의 최적화기(query optimizer)가 하는 일이다.
선언형 질의 언어의 이점은 다음과 같다.
- 명령형 API보다 더 간결하고 쉽게 작업할 수 있다.
- 데이터베이스 엔진의 상세 구현이 숨겨져 있어 질의를 변경하지 않고 데이터베이스 시스템의 성능을 향상시킬 수 있다.
- 병렬 실행에 적합하다.
명령형과 선언형 질의 언어의 차이를 좀 더 이해하기 위해, 웹에서의 선언형 접근방식과 명령형 접근방식을 비교해 보자.
(선언형 질의 언어의 장점은 완전히 다른 환경인 웹 브라우저 같이 데이터베이스에만 국한되지 않는다.)
바다에 사는 동물에 대한 웹사이트가 있고, 현재 상어 내비게이션 항목을 선택한 상황이라 가정하자.
<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;
}
만약, 명령형 접근 방식을 사용한다면 JavaScript를 사용해 DOM API로 접근해야 할 것이다.
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");
}
}
}
}
코드를 보면 알겠지만 일단 코드량부터 엄청나며 몇 가지 크리티컬 한 문제도 있다.
- selected 클래스가 삭제된 경우 (사용자가 다른 페이지를 클릭한 경우) 코드가 재실행되더라도 파란색이 삭제되지 않는다.
- Document.getElementsByClassName()이나 document.evaluate()와 같은 새로운 API의 장점(성능 향상과 같은)을 취하고 싶다면 코드를 재작성해야 한다.
이 처럼 선언형 접근 방식이 명령형 접근 방식보다 이점이 많고, 데이터베이스에서 또한 SQL 같은 선언형 질의 언어가 명령형 질의 API보다 훨씬 좋다.
# 그래프형 데이터 모델
위에서 봤듯이 애플리케이션이 주로 일대다 관계(트리 구조)이거나 레코드 간 관계가 없다면 문서 모델이 적합하다.
그런데 만약 데이터에서 다대다 관계가 매우 일반적이고 복잡해질 경우엔 문서 모델도 관계형 모델도 아닌 그래프로 데이터를 모델링하는 것이 자연스럽다.
그래프로 모델링할 수 있는 데이터는 다음과 같다.
- 소셜 그래프 : 정점은 사람이고 간선은 사람들이 서로 알고 있음을 나타냄
- 웹 그래프 : 정점은 웹 페이지고 간선은 다른 페이지에 대한 HTML 링크를 나타냄
- 도로나 철도 네트워크 : 정점은 교차로이고 간선은 교차로 간 도로나 철로 선을 나타냄
뿐만 아니라 그래프는 이런 동종 데이터에 국한되지 않고 표현이 가능한데,
페이스북의 경우 사람, 장소, 이벤트, 체크인, 사용자가 작성한 코멘트를 정점으로,
친구 관계, 체크인 발생 위치 등을 간선으로 나타낸다.
다음 그림은 그래프 구조의 데이터 예시를 나타낸다.
그래프에서 데이터를 구조화하고 질의하는 방법에는 다음과 같은 모델이 존재한다.
- 속성 그래프 모델
- 트리플 저장소 모델
그래프용 선언형 질의 언어에는 다음과 같은 세 가지가 존재한다.
- 사이퍼(Cypher)
- 스파클(SPARQL)
- 데이터로그(Datalog)
'독서 > 데이터 중심 애플리케이션 설계' 카테고리의 다른 글
[데이터 중심 애플리케이션 설계] 저장소와 검색 (0) | 2023.01.29 |
---|---|
[데이터 중심 애플리케이션 설계] 신뢰성, 확장성, 유지보수성 (1) | 2022.12.27 |