티스토리 뷰

반응형
내 언어의 한계는 내 세계의 한계를 의미한다.
- 루트비히 비트겐슈타인

데이터 모델은 아마도 소프트웨어 개발에서 제일 중요한 부분일 것이다. 왜냐하면 데이터 모델은 소프트웨어가 어떻게 작성됐는지 뿐만 아니라 해결하려는 문제를 어떻게 생각해야 하는지에 대해서도 지대한 영향을 미치기 때문이다.

대부분의 애플리케이션은 하나의 데이터 모델을 다른 데이터 모델 위에 계층을 둬서 만든다. 각 계층의 핵심적인 문제는 다음 하위 계층 관점에서 데이터 모델을 표현하는 방법이다. 예를 들어보자.

- 애플리케이션 개발자는 현실(사람, 조직, 상품, 행동, 자금 흐름, 센서 등)을 보고 객체나 데이터 구조, 그리고 이러한 데이터 구조를 다루는 API를 모델링한다. 이런 구조는 보통 애플리케이션에 특화돼 있다.
- 데이터 구조를 저장할 때는 JSON이나 XML 문서, 관계형 데이터베이스 테이블이나 그래프 모델 같은 범용 데이터 모델로 표현한다.
- 데이터베이스 소프트웨어를 개발하는 엔지니어는 JSON/XML/관계형/그래프 데이터를 메모리나 디스크 또는 네트워크 상의 바이트 단위로 표현하는 방법을 결정한다. 이 표현은 다양한 방법으로 데이터를 질의, 탐색, 조작, 처리 할 수 있게 한다.
- 더 낮은 수준에서 하드웨어 엔지니어는 전류, 빛의 파동, 자기장 등의 관점에서 바이트를 표현하는 방법을 알아냈다.

복잡한 애플리케이션에서는 여러 API를 기반으로 만든 API 처럼 중간 단계를 더 둘 수 있지만 기본 개념은 여전히 동일하다. 각 계층은 명확한 데이터 모델을 제공해 하위 계층의 복잡성을 숨긴다. 이 추상화는 다른 그룹의 사람들(예를 들어 데이터베이스 벤더의 엔지니어와 데이터베이스를 사용하는 애플리케이션 개발자)이 효율적으로 함께 일할 수 있게 끔 한다.

다양한 유형의 데이터 모델이 있고 각 데이터 모델은 사용 방법에 대한 가정을 나타낸다. 어떤 종류의 사용법은 쉽고 어떤 동작은 지원하지 않는다. 어떤 연산은 빠르지만 또 어떤 연산은 매우 느리게 작동한다. 어떤 데이터 변환은 자연스럽지만 다른 어떤 데이터 변환은 부자연 스럽다.

하나의 데이터 모델만을 완전히 익히는 데도 많은 노력이 필요하다(관계형 데이터 모델링 책이 얼마나 많은지 생각해보자). 데이터 모델을 하나만 사용하고 내부 동작에 대한 걱정이 없을지라도 소프트웨어 작성은 그 자체로 충분히 어렵다. 그러나 데이터 모델은 그 위에서 소프트웨어가 할 수 있는 일과 할 수 없는 일에 지대한 영향을 주므로 애플리케이션에 적합한 데이터 모델을 선택하는 작업은 상당히 주용하다.

이번 장에서는 데이터 저장과 질의(앞의 목록에서 2번 항목)를 위한 다양한 범용 데이터 모델을 살펴본다. 특히 관계형 모델(relational model)과 문서 모델(document model), 그리고 몇 가지 그래프 기반 데이터 모델(graph-based data model)을 비교한다. 또한 다양한 질의 언어를 살펴보고 사용 사례도 비교한다. 3장에서는 이런 데이터 모델이 실제로 어떻게 구현(앞의 목록에서 3번 항목)되는지 저장소 엔진의 동작 방식을 통해 설명하겠다.

관계형 모델과 문서 모델

오늘날 가장 잘 알려진 데이터 모델은 1970년 에드가 코드(Edgar Codd)가 제안한 관계형 모델을 기반으로 한 SQL이다. 데이터는 (SQL에서 테이블이라 불리는) 관계(relation)로 구성되고 각 관계는 순서없는 튜플(tuple)(SQL에서 로우(row)) 모음이다.

관계형 모델은 이론적 제안이었고 당시 많은 사람은 관계형 모델을 효율적으로 구현할 수 있을지 의문을 제기했다. 하지만 1980년대 중반에 관계형 데이터베이스 관리 시스템(relational database management system, RDBMS)과 SQL은 정규화된 구조로 데이터를 저장하고 질의할 필요가 있는 사람들 대부분이 선택하는 도구가 됐다. 관계형 데이터베이스의 우위는 약 25 ~ 30년 정도 (컴퓨터 역사에서는 아주 오랜 시간) 지속 됐다.

관계형 데이터베이스의 근원은 1960년대와 1970년대에 메인프레임 컴퓨터에서 수행된 비즈니스 데이터 처리에 있다. 이 사용 사례는 보통 트랜잭션 처리(영업이나 은행 거래, 항공 예약, 창고에 재고 보관)일괄처리(고객 송장 작성, 급여 지불, 보고)로 오늘날의 관점에서는 일상적으로 수행되는 일이다.

당시 다른데이터베이스를 사용하는 애플리케이션 개발자는 데이터베이스의 내부 데이터 표현에 대해 많이 고민해야 했지만 관계형 모델의 목표는 정리된 인터페이스 뒤로 구현 세부 사항을 숨기는 것이다.

수년 동안 데이터 저장과 질의를 위해 많은 접근 방식이 경쟁했다. 1970년대와 1980년대 초반에는 네트워크 모델계층 모델이 주요 대안이었지만 결국 관계형 모델이 우위를 차지했다. 객체 데이터베이스는 1980년대 후반과 1990년 초반에 등장했다가 다시 사라졌다. XML 데이터베이스는 2000년대 초반에 등장했지만 매우 적게 채택되었다 관계형 모델의 경쟁자들은 당시에 대대적으로 과장 광고를 했지만 얼마 가지 않았다.

컴퓨터가 훨씬 더 강력해지고 네트워크화됨에 따라 컴퓨터를 점점 더 다양한 목적으로 사용하기 시작했다. 놀랍게도 관계형 데이터베이스가 비즈니스 데이터 처리라는 본래 영역을 넘어 폭넓은 다양한 사용 사례에도 보편화되는 것으로 나타났다. 오늘날 웹에서 볼 수 있는 대부분의 서비스(예를 들어 온라인 게시물, 토론, 소셜 네트웤, 전자 상거래, 게임, Saas(software-as-a-service) 생산성 애플리케이션 등)는 여전히 관계형 데이터베이스를 통해 제공된다.

NoSQL의 탄생

현재 2010년대에 NoSQL은 관계형 모델의 우위를 뒤집으려는 가장 최신 시도다. "NoSQL"이라는 이름은 실제로 어떤 특정 기술을 참고한 것이 아니기에 적절하지 않다. 원래 NoSQL은 2009년에 오픈소스, 분산환경, 비관계형 데이터베이스 밋업(meetup)용 인기 트위터 해시태그였다. 그럼에도 이 용어는 신경을 자극했고 웹 스타트업 커뮤니티를 넘어 바르게 확산되었다. 지금은 인기있는 여러 데이터베이스 시스템과 #NoSQL 해시태그가 연관돼 있다. 그래서 NoSQL은 다시 거슬러 올라가 Not Only SQL로 재해석됐다. 

NoSQL 데이터베이스 채택한 데는 다음과 같은 다양한 원동력이 있다.

- 대규모 데이셋이나 매우 높은 쓰기 처리량 달성을 관계형 데이터베이스보다 쉽게 할 수 있는 뛰어난 확장성의 필요
- 상용 데이터베이스 제품보다 무료 오픈소스 소프트웨어에 대한 선호도 확산
- 관계형 모델에서 지원하지 않는 특수 질의 동작
- 관계형 스키마의 제한에 대한 불만과 더욱 동적이고 표현력이 풍부한 데이터 모델에 대한 바람

애플리케이션은 저마다 요구사항이 다르다. 한 사용 사례에 맞는 최적의 기술 선택은 또 다른 사용사레에 맞는 최적의 선택과는 다를 수 있다. 그러므로 가까운 미래에는 관계형 데이터베이스가 폭 넓은 다양함을 가진 비관계형 데이터스토어와 함께 사용 될 것이다. 이런 개념을 종종 다중 저장소 지속성(polyglot persistence)이라 한다.

객체 관계형 불일치

오늘날 대부분의 애플리케이션은 객체지향 프로그래밍 언어로 개발한다. 이는 SQL 데이터 모델을 향한 공통된 비판을 불러온다. 데이터를 관계형 테이블에 저장하려면 애플리케이션 코드와 데이터베이스 모델 객체(테이블, 로우, 컬럼) 사이에 거추장 스러운 전환 계층이 필요하다. 이런 모델 사이의 분리를 종종 임피던스 불일치(impednace mismatch)라고 부른다. 임피던스란 전자공학에서 빌려온 용어다. 모든 전기 회로는 입력과 출력에 일정한 임피던스(교류에 대한 저항)를 갖고 있다. 한 회로의 출력을 또 다른 회로의 입력으로 현결했을 때 두 회로의 출력과 입력 임피던스가 일치하면 연결을 통한 전력전달은 최대가된다. 하지만 임피던스가 불일치하면 신호 반사 및 여러 문제를 일으킬 수 있다.

액티브레코드(ActiveRecord)나 하이버네이트(Hibernate) 같은 객체 관계형 매핑(ORM) 프레임워크는 전환계층에 필요한 사용구 코드(boilerpalte code)의 양을 줄이지만 두 모델 간의 차이를 완벽히 숨길 수 없다.

예를 들어 다음 그림은 스키마에서 이력서(링크드인 프로필)를 어떻게 표현하는지 보여준다. 프로필 전체는 고유 식별자인 user_id로 식별한다. first_name과 last_name 같은 필드는 사용자마다 정확하게 하나씩 있어 users 테이블의 칼럼으로 모델링 할 수 있다. 하지만 대부분의 사람들은 결겨(직위)에 넣을 직업이 하나 이상이며 학력 기간과 연락처 정보도 다양하다. 사용자와 이들 항목은 일대다(one-to-many) 관계다. 이 관계는 다양한 방법으로 나타 낼 수 있다.

- 전통적인 SQL 모델(SLQ:1999 이전)에서 가장 일반적인 정규화 표현은 직위, 학력, 연락처 정보를 개별 테이블에 넣고, 외래 키로 그림과 같이 users 테이블으 참조하는 방식이다.
- SQL 표준의 마지막 버전에서 구조화된 데이터타입과 XML 데이터에 대한 지원을 추가했다. 이 지원으로 단일 로우에 다중 값을 저장할 수 있고 문서 내 질의와 색인이 가능해졌다. 이런 기능은 오라클, IBM DB2, 마이크로소프트 SQL 서버, 포스트그레스큐엘(PostreSQL) 같은 데이터베이스마다 제각각 다양한 형태로 지원한다. JSON 데이터타입도 IBM DB2, MySQL과 포스트그레스큐엘을 포함한 여러 데이터베이스에서 지원한다.
- 세번쨰 방법으로 직업, 학력, 연락처 정보를 JSON이나 XML 문서로 부호화해 데이터베이스의 텍스트 칼럼에 저장한 다음 애플리케이션이 구조와 내용을 해석하게 하는 방식이다. 이 방식을 쓰면 일반적으로 부호호화된 칼럼의 값을 질의하는 데 데이터베이스를 사용할 수 없다.

이력서 같은 데이터 구조는 모든 내용을 갖추고 있는 문서라서 JSON 표현에 매우 적합하다. 다음 예제를 보자. JSON은 XML보다 훨씬 더 간단해 매력적이다. 몽고DB(MongoDB), 리싱크 DB(RethinkDB), 카우치 DB(CouchDB)와 에스프레소(Espresso) 같은 문서 지향(document-oriented) 데이터베이스 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 모델이 애플리케이션 코드와 저장 계층 간 임피던스 불일치를 줄인다고 생각한다. 4장에서 설명하겠지만 데이터 부호화 형식으로서 JSON이 가진 문제도 있다. 스키마의 부족을 장점으로 종종 인용하기도 하는데, 이에 대해 39쪽의 '문서 모델에서의 스키마 유연성'에서 설명하겠다.

JSON 표현은 그림 2-1 다중 테이블(multi-table) 스키마보다 더 나은 지역성(locality)을 갖는다. 관계형 에제에서 프로필을 가져오려면 다중 질의(각 테이블에 user_id로 질의)를 수행하거나 users 테이블과 그 하위 테이블 간에 난잡한 다중 조인을 수행해야 한다. JSON 표현에서는 모든 관련 정보가 한 곳에 있어 질의 하나로 충분하다.

사용자 프로필에서 사용자에서 직위, 학력 기록, 연락처 정보로 대응되는 일대다 관계는 의미상 데이터 트리 구조와 같다. 이 트리 구조는 JSON 표현에서 명시적으로 드러난다.

다대일과 다대다 관계

앞 절의 예제에서 region_id와 industry_id는 평문인 '그레이터 시애틀 구역'과 '자선활동'이 아닌 ID로 주어졌다. 왜일까?

사용자 인터페이스에 지역과 업계를 입력할 수 있는 자유 텍스트 필드가 있다면 평문으로 저장하는 편이 합리적이지만 지리적 지역과 업계의 표준 목록으로 드롭다운 리스트나 자동 완성 기능을 만들어 사용자가 선택하게 하는 데는 다음과 같은 장점이 있다.

- 프로필 간 일관된 스타일과 절차
- 모호함 회피(예를 들어 이름이 같은 여러 도시가 있는 경우)
- 갱신의 편의성 이름이 한곳에 저장되므로 이름을 변경해야 하는 경우 전반적으로 갱신하기 쉽다(예를 들어 정치적 사건으로 도시 이름이 변경된 경우)
- 현지화 지원. 사이트를 다른 언어로 번역할 때 표준 목록을 현지화해 지역과 업계를 사이트를 보는 사람의 언어로 표시할 수 있다.
- 더 나은 검색. 예를 들어 워싱턴 주에 있는 자선가를 검색하려 할 때 지역 목록에 시애틀이 워싱턴에 있다는 사실을 부호화("그레이터 시애틀 구역" 문자열로는 "워싱턴"을 식별하지 못함)할 수 있기 때문에 원하는 프로필을 찾을 수 있다.

ID나 텍스트 문자열의 저장 여부는 중복의 문제다. ID를 사용하는 경우 (자선 활동이라는 단어처럼) 사람에게 의미 있는 정보는 한 곳에만 저장하고 그것을 참조하는 모든 것은 ID를 사용한다(데이터베이스 내에서만 의미가 있음) 텍스트를 직접 저장한다면 그것을 사용하는 모든 레코드에서 사람을 의미하는 정보를 중복해서 저장하게 된다.

ID를 사용하는 장점으로 ID 자체는 아무런 의미가 없기 때문에 변경할 필요가 없다. 즉 식별 정보를 변경해도 ID는 동일하게 유지할 수 있다. 하지만 의미를 가지는 경우라면 미래에 언젠가는 ID를 변경해야 할 수도 있다. 만약 정보가 중복돼 있으면 모든 중복 항목을 변경해야 한다. 이것은 쓰기 오버헤드와 불일치(정보의 일부 중복은 갱신됐지만 다른 중복 항목이 갱신되지 않음) 위험이 있다. 이런 중복을 제거하는 일이 데이터베이스의 정규화 이면에 놓인 핵심 개념이다. 관계형 모델에 대한 문헌은 정규화 유형을 나누어 구분하지만 이런 구분은 실용성 면에서 중요치 않다. 경험상 한 곳에 저장할 수 있는 값이 중복된다면 그 스키마는 정규화되지 않았ㄷ.

중복된 데이터를 정규화 하려면 다대일(many-to-one) 관계 (많은 사람들은 한 특 특정 지역에 살고 많은 사람들은 한 특정 업계에서 일한다)가 필요한데 안타깝게도 다대일 관계는 문서 모델에 적합하지 않다. 관계형 데이터베이스에서는 조인이 쉽기 때문에 ID로 다른 테이블의 로우를 참조하는 방식은 일반적이다. 문서 데이터베이스에서는 일대다 트리 구조를 위해 조인이 필요하지 않지만 조인에 대한 지원이 보통 약하다.

데이터베이스 자체가 조인을 지원하지 않으면 데이터베이스에 대한 다중 질의를 만들어서 애플리케이션 코드에서 조인을 흉내 내야 한다. (예제의 경우 애플리케이션이 지역과 업계 목록을 간단히 메모리에 유지할 수 있을 정도로 충분히 작고 천천히 변경된다. 그럼에도 조인을 만드는 작업은 데이터베이스에서 애플리케이션 코드로 옮겨야 한다.)

더욱이 애플리케이션의 초기 버전이 조인 없는(join-free) 문서 모델에 적합하더라도 애플리케이션에 기능을 추가하면서 데이터는 점차 상호  연결되는 경향이 있다. 이력서 예제에 몇 가지 변경 사항을 생각해보라.

엔티티(entity)로서의 조직과 학교

이전 설명에서 organization(사용자가 일하는 회사)과 school_name(사용자가 공부한 곳)은 문자열이다 이것도 문자열 대신 엔티티로 참조하면 어떨까? 각 조직, 학교 또는 대학에서는 웹 페이지(로고와 뉴스 피드 등도 포함)가 있을 것이다. 조직과 학교를 엔티티로 참조하면 각 이력서에는 언급된 조직과 학교로 연결하는 링크를 포함하거나 조직의 로고 및 추가 정보를 포함할 수 있다.

추천서

새로운 기능으로 한 사용자가 또 다른 사용자를 위해 추천서를 작성하는 기능을 넣는다고 해보자. 추천서는 추천받은 사용자 이력서에는 추천인의 이름과 사진이 함께 보여진다. 만약 추천인이 작성한 모든 추천서에 새로운 사진을 반영해야한다. 따라서 추천서는 작성자(추천인) 프로필을 참조해야한다.

추천서와 같은 기능이 다대다(many-to-many) 관계를 어떻게 필요로 하는지 생각해볼 수 있다. 조직, 학교, 기탙 사용자는 참조로 표현해야 하고 질의할 때는 조인이 필요하다.

문서 데이터베이스는 역사를 반복하고 있나?

역사는 나선형?
In e.g. PHP SSR -> React CSR -> Next SSR
- 누군가가

관계형 데이터베이스는 일상적으로 다대다 관계와 조인을 사용하지만 문서 데이터베이스와 NoSQL은 데이터베이스에서 다대다 관계를 표현하는 제일 좋은 방법에 대한 논쟁을 다시 열었다. 이 논쟁은 NoSQL보다 훨씬 오래됐으며 사실상 가장 초기의 전산화 데이터베이스 시스템으로 돌아간다.

1970년대 비즈니스 데이터 처리를 위해 가장 많이 사용한 데이터베이스는 IBM의 정보 관리 시스템(Information Management System, IMS)으로, 원래는 아폴로 우주프로그램에서 재고 관리를 위해 개발됐고 1968년 처음 상업적으로 출시됐다. IMS는 IBM 메인프레임 상의 OS/390에서 오늘날에도 사용 중이고 유지보수 되고 있다.

IMS의 설계는 계층 모델이라 부르는 상당히 간단한 데이터 모델을 사용했다. 계층 모델은 문서 데이터 베이스에서 사용하는 JSON 모델과 놀랍게도 비슷하다. JSON 구조와 마찬가지로 모든 데이터를 레코드 내에 중첩된 레코드 트리로 표현한다.

문서 데이터베이스처럼 IMS도 일대다 관계에서는 잘 동작한다. 하지만 다대다 관계 표현은 어려웠고 조인은 지원하지 않았다. 개발자는 (비정규화된) 데이터를 중복할지 한 레코드와 또 다른 레코드의 참조를 수동으로 해결할지 결정해야했다. 1960년대와 70년대의 이 문제는 오늘날 문서 데이터베이스를 사용해서 작업 중인 개발자가 풀어야 할 무제와 매우 비슷하다.

계층 모델의 한계를 해결하기 위해 다양한 해결책이 제안됐다. 가장 두드러지는 해결책 두 가지는 관계형 모델(SQL이 되어 세상을 지배했다)과 네트워크 모델(초기에는 많은 신봉자들이 있었지만 결국 희미하게 잊혀졌다)이다. 두 진영간 '대논쟁'은 1970년대 대부분 동안 지속됐다.

두 모델이 해결하려는 문제가 오늘날에도 여전히 관련이 많기 때문에 이 논쟁을 현재 관점으로 간략하게 다시 살펴볼 가치가 있다.

네트워크 모델

네트워크 모델은 코다실(Conference on Data Systems Languages, CODASYL)이라 불리는 위원회에서 표준화했다. 다양한 여러 데이터베이스 벤더가 네트워크 모델을 구현했고 코다실 모델이라 부른다.

코다실 모델은 게층 모델을 일반화한다. 계츠 모델의 트리 구조에서 모든 레코드는 정확하게 하나의 부모가 있다. 네트워크 모델에서 레코드는 다중 부모가 있을 수 있다. 예를 들어 '그레이터 시애틀 구역' 지역을 위한 하나의 레코드는 이 지역에 사는 모든 사용자에 연결될 수 있다. 코다실 모델은 다대일과 다대다 관계를 모델링 할 수 있다.

네트워크 모델에서 레코드 간 연결은 외래 키보다는 프로그래밍 언어의 포인터와 더 비슷하다(여전히 디스크에 저장된다.) 레코드에 접근하는 유일한 방법은 최상위 레코드(root record)에서부터 연속된 연결 경로를 따르는 방법이다. 이를 접근 경로라 한다.

가장 간단한 경우는 접근 경로가 연결 목록의 순회와 같을 때다. 목록의 맨 앞에서 시작해서 원하는 레코드를 찾을 때까지 한 번에 하나의 레콛르르 보는 방식이다. 하지만 다대다 관계의 세계에서는 다양한 다른 경로가 같은 레코드로 이어질 수 있고 네트워크 모델을 사용하는 프로그래머는 경로의 맨 앞에서 이런 다양한 접근 경로를 계속 추적해야한다.

코다실에서 질의는 레코드 목록을 반복해 접근 경로를 따라 데이터베이스의 끝에서 끝까지 커서를 움직여 수행한다. 만약 레코드가 다중 부모(즉, 다른 레코드에서 다중으로 유입된 포인터)를 가진다면 애플리케이션 코드는 다양한 관계를 모두 추적해야 한다. 코다실 위원회도 이 방식이 n차원 데이터 공간을 항해하는 것과 같다고 인정했다.

수동 접근 경로 선택은 1970년대에는 (탐색이 매우 느린 테이프 드라이브와 같이) 매우 제한된 하드웨어 성능을 가장 효율적으로 사용할 수 있었지만 데이터베이스 질의와 갱신을 위한 코드가 복잡하고 유연하지 못한 문제가 있었다. 계층 모델과 네트워크 모델 모두, 원하는 데이터에 대한 경로가 없다면 어려운 상황에 놓인다. 접근 경로를 변경할 수있지만 아주 많은 수작업 데이터베이스 질의 코드를 살펴봐야하고 새로운 접근 경로를 다루기 위해 재작성해야 한다. 애플리케이션의 데이터 모델을 바꾸는 작업은 매우 어려운 일이었다.

관계형 모델

대조적으로 관계형 모델이 하는 일은 알려진 모든 데이터를 배치하는 것이다. 관계(테이블)는 단순히 튜플(로우)의 컬렉션이 전부다. 얽히고 설킨 중첩 구조와 데이터를 보고싶을 때 따라가야할 복잡한 접근 경로가 없다. 임의 조건과 일치하는 테이블의 일부 또는 모든 로우를 선택해서 읽을 수 있고 일부 칼럼을 키로 지정해 칼럼과 일치하는 특정 로우를 읽을 수 있다. 다른 테이블과의 외래키 관계에 대해 신경쓰지 않고 임의 테이블에 새 로우를 삽입할 수 있다.

관계형 데이터베이스에서 질의 최적화기(query optimizer)는 질의의 어느 부분을 어떤 순서로 수행할지를 결정하고 사용할 색인을 자동으로 결정한다. 이 선택을 실제로 '접근 경로'다. 하지만 큰 차이점은 접근 경로를 애플리케이션 개발자가 아니라 질의 최적화기가 자동으로 만든다는 점이다. 그래서 접근 경로를 따로 생각할 필요가 없다.

새로운 방식으로 데이터에 질의하고 싶은 경우 새로운 색인을 선언하기만 하면 질의는 자동으로 가장 적합한 색인을 사용한다. 새로운 색인을 사용하기 위해 질의를 바꿀 필요가 없다. 따라서 관계형 모델은 애플리케이션에 새로운 기능을 추가하는 작업이 훨씬 쉽다.

관계형 데이터베이스를 위한 질의 최적화기는 복잡해서 수년 간의 연구와 개발 노력을 필요로 한다. 관계형 모델에서 주목할 만한 점은 질의 최적화기를 한번 만들면 데이터베이스를 사용하는 모든 애플리케이션이 혜택을 받을 수 있다는 점이다. 만약 질의 최적화기가 없다면 범용 최적화기를 만들기보다 특정 질의를 위한 접근 경로를 직접 코딩하는 게 쉽지만 장기적으로 범용 솔루션이 유리하다.

문서 데이터베이스와 비교

문서 데이터베이스는 한 가지 측면에서 계층 모델로 되돌아 갔다. 문서 데이터베이스는 별도 테이블이 아닌 상위 레코드 내에 중첩된 레코드를 저장한다.

하지만 다대일과 다대다 관계를 표현할 때 관계형 데이터베이ㅡㅅ와 문서 데이터베이스는 근본적으로 다르지 않다. 둘 다 관련 항목은 고유한 식별자로 참조한다. 관계형 모델에서는 외래 키라 부르고 문서 모델에서는 문서 참조(document reference)라 부른다. 이 식별자는 조인이나 후속 질의를 사용해 읽기 시점에 확인한다. 현재까지는 문서 데이터베이스가 코다실의 전철을 밟지 않고 있다.

관계형 데이터베이스와 오늘날의 문서 데이터베이스

관계형 데이터베이스와 문서 데이터베이스를 비교하는 경우 내결함성과 동시성 처리를 포함해 고려해야 할 차이점이 많이 있다. 이번 장에서는 데이터 모델의 차이점에만 집중한다.

문서 데이터 모델을 선호하는 주요 이유는 스키마 유연성, 지역성에 기인한 더 나은 성능 때문이고 일부 애플리케이션의 경우 애플리케이션에서 사용하는 데이터 구조와 더 가깝기 떄문이다. 관계형 모델은 조인, 다대일, 다대다 관계를 더 잘 지원함으로써 문서 데이터 모델에 대항한다.

어떤 데이터 모델이 애플리케이션 코드를 더 간단하게 할까?

애플리케이션에서 데이터가 문서와 비슷한 구조(일대다 관계 트리로 보통 한 번에 전체 트리를 적재)라면 문서 모델을 사용하는 것이 좋다. 문서와 비슷한 구조를 여러 테이블로 나누어 찢는(shredding) 관계형 기법은 다루기 힘든 스키마와 불필요하게 복잡한 애플리케이션 코드를 발생시킨다.

문서 모델에는 제한이 있다. 예를 들어 문서 내 중첩 항목을 바로 참조할 수는 없어서, 사용자 251의 직위 목록의 두 번쨰 항목 과 같이 표현해야 한다.(계층 모델에서 접근 경로와 매우 유사) 하지만 문서가 너무 깊게 중첩되지 않으면 일반적으로 문제가 되지 않는다.

문서 데이터베이스의 미흡한 조인 지원은 애플리케이션에 따라 문제일 수도 있고 아닐 수도 있다. 예를 들어 어떤 시점에 발생한 이벤트를 기록하는 문서 데이터베이스를 사용하는 분석 애플리케이션에서는 다대다 관계가 결코 필요하지 않다. 

하지만 애플리케이션에서 다대다 관계를 사용한다면 문서 모델은 매력이 떨어진다. 비정규화로 조인의 필요성 줄이기가 가능하지만 애플리케이션 코드는 비정규화된 데이터의 일관성을 유지하기 위해 추가 작업을 해야 한다. 조인은 애플리케이션 코드에서 데이터베이스에 다중 요청을 만들어 낼 수 있지만 복잡도가 애플리케이션으로 이동할 뿐만 아니라 보통 데이터베이스 내 특화된 코드로 수행되는 조인보다 더 느리다. 이런 경우에 문서 모델을 사용하는 것은 훨씬 더 복잡한 애플리케이션 코드와 나쁜 성능으로 이어질 수 있다.

일반적으로 어떤 데이터 모델이 애플리케이션 코드를 더 간단하게 만드는지 말할 수 없다. 데이터 항목 간에 존재하는 관계 유형에 따라 다르다. 상호 연결이 많은 데이터의 경우 문서 모델은 곤란하지만 관계형 모델은 무난하며 그래프 모델은 매우 자연스럽다.

문서 모델에서의 스키마 유연성

대부분의 문서 데이터베이스와 관계형 데이터베이스에서 지원하는 JSON은 문서의 데이터에 어떤 스키마를 강요하지 않는다. 보통 관계형 데이터베이스에서 제공하는 XML은 선택적으로 스키마 유효성 검사를 포함할 수 있따. 스키마가 없다는 뜻은 임의의 키와 값을 문서에 추가할 수 있고 읽을 때 클라이언트는 문서에 포함된 필드의 존재 여부를 보장하지 않는다는 의미다.

문서 데이터베이스는 종종 스키마리스(schemaless)로 불리지만 이는 오해의 소지가 있다. 데이터를 읽는 코드는 보통 구조의 유형을 어느 정도 가정한다. 즉 암묵적인 스키마가 있지만 데이터베이스는 이를 강요하지 않는다. 조금 더 정확한 용어로 말하자면 쓰기 스키마(schema-on-write)(관계형 데이터베이스의 전통적인 접근 방식으로 스키마는 명시적이고 데이터 베이스는 쓰여진 모든 데이터가 스키마를 따르고 있음을 보장한다)와 반대되는 읽기 스키마(schema-on-read)(데이터 구조는 암묵적이고 데이터를 읽을때만 해석된다)

읽기 스키마는 프로그래밍 언어에서 동적(런타임) 타입 확인과 유사하고 쓰기 스키마는 정적(컴파일 타임) 타입 확인과 비슷하다. 정적 타입 확인의 지지자와 동적 타입 확인 지지자 사이에서 상대적인 장점에 관해 열띤 논쟁을 하는 것 처럼 데이터베이스에서 스키마 강제는 논쟁의 있는 주제이며 일반적으로 옳고 그른 정답은 없다.

접근 방식 간 차이는 애플리케이션이 데이터 타입을 변경하고자 할 때 특히 뚜렷이 나타난다. 에를 들어 현재 하나으 ㅣ필드에 사용자의 전체 이름을 저장하고 있지만 성과 이름을 분리해서 저장하고 싶다고 가정해보자. 문서 데이터베이스에서는 새로운 필드를 가진 새로운 문서를 작성하기 시작하고 애플리케이션에는 예전 문서를 읽는 경우를 처리하는 코드만 있으면 된다.

if (user && user.name && !user.first_name) {
	user.first_name = user.name.split(" ")[0];
}

반면 '정적 타입'의 데이터베이스 스키마에서는 보통 다음 행과 같이 마이그레이션(migration)을 수행한다.

ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = split_part(name, ' ', 1);
UPDATE useres SET first_name = substring_index(name, ' ', 1);

스키마 변경은 느리고 중단시간을 요구하기 때문에 평판이 나쁘다. 전적으로 이런 평판을 받을 정도는 아니다. 대부분의 관계형 데이터베이스 시스템은 ALTER TABLE 문을 수 밀리초 안에 수행한다. MySQL은 상당히 예외적이다. MySQL은 ALTER TABLE 시에 전체 테이블을 복사한다. 이는 큰 테이블을 변경할 때 수 분에서 수 시간 까지 중단시간이 발생한다는 의미다. 이런 제약을 회피할 수 있는 다양한 도구가 있음에도 말이다.

큰 테이블에 UPDATE 문을 실행하면 모든 로우가 재작성 될 수 있기 때문에 어떤 데이터베이스는 오래 걸릴 수 있다. 이 방식을 수용할 수 없다면 애플리케이션은 first_name이 기본값인 널(NULL)로 설정되게 남겨두고 문서 데이터베이스처럼 읽는 시점에 채울 수 있다.

읽기 스키마 접근 방식은 컬렉션 안의 항목이 어떤 이유로 모두 동일한 구조가 아닐 때(즉 데이터가 여러 다른 유형으로 구성돼 있음) 유리 하다. 그 이유는 다음과 같다.

- 다른 여러 유형의 오브젝트가 있고 각 유형의 오브젝트 별로 자체 테이블에 넣는 방법은 실용적이지 않다.
- 사용자가 제어할 수 없고 언제나 변경 가능한 외부 시스템에 의해 데이터 구조가 결정된다.

이 같은 상황에서 스키마는 득보다 실이 많다. 오히려 스키마리스 문서가 더 자연스러운 데이터 모델이다. 하지만 모든 레코드가 동일한 구조라서 예상 가능하다면 스키마가 문서화와 구조를 강제하기 위한 유용한 메커니즘이다. 스키마와 스키마 발전(schema evolution)은 4장에서 더 자세히 다룬다.

질의를 위한 데이터 지역성

문서는 보통 JSON, XML로 부호화된 단일 연속 문자열이나 (MongoDB의 BOSN 같은) JSON 또는 XML의 이진 변형으로 저장된다. 웹 페이지 상에 문서를 보여주는 동작처럼 애플리케이션이 자주 전체 문서에 접근해야 할 때 저장소 지역성(storage locality)을 활용하면 성능 이점이 잇다. 만약 데이터가 다중 테이블로 나눠졌다면 전체를 검색하기 위해 다중 색인 검색이 필요하다. 이 경우 더 많은 디스크 탐색이 필요하고 더 많은 시간이 소요된다.

지역성의 이점은 한 번에 해당 문서의 많은 부분을 필요로 하는 경우에만 적용된다. 데이터베이스는 대개 문서의 작은 부분에만 접근해도 전체 문서를 적재해야 하기 때문에 큰 문서에는 낭비일 수 있다. 문서를 갱신하 때도 보통 전체 문서를 재작성해야 한다. 부호화된 문서의 크기를 바꾸지 않는 수정은 쉽게 수행할 수 있다. 이런 이유로 일반적으로 문서를 아주 적게 유지하면서 문서의 크기가 증가하는 쓰기를 피하라고권장한다. 이 성능 제한 때문에 문서 데이터베이스가 유용한 상황이 많이 줄어든다. 

지역성을 위해 관련 데이터를 함꼐 그룹화하는 개념이 문서 모델에만 국한되지 않는다는 점이 중요하다. 에를 들어 구글의 Spanner 데이터베이스는 부모 테이블 내에 (중첩되게) 테이블의 로우를 교차 배치되게끔 선언하는 스키마를 허용해 관계형 데이터 모델에서 지역성 특성을 동일하게 제공한다. 오라클은 다중 테이블 색인 클러스터 테이블(multi-table index cluster table) 기능을 사용해 동일한 특성을 제공한다. 빅테이블(Bigtable) 데이터 모델의 칼럼 패밀리(column-family) 개념(카산드라와 HBase에서 사용)이 지역성 관리와 유사한 목적이 있다.

문서 데이터베이스와 관계형 데이터베이스의 통합

대부분의 관계형 데이터베이스 시스템(MySQL은 제외)은 2000년대 중반 이후로 XML을 지원한다. 여기에는 XML 문서의 지역적 수정과 XML 문서 내부에서 색인하고 질의하는 기능을 포함한다. 그래서 문서 데이터베이스를 사용할 떄와 매우 비슷한 데이터 모델을 애플리케이션이 사용할 수 있다.

PostgreSQLdms 9.3, MySQL은 5.7, IBM DB2는 10.5 버전부터 JSON 문서에 대해 비슷한 수준의 지원 기능을 제공한다. 웹 API용 JSON의 인기를 고려할 때, 그 밖의 관계형 데이터베이스도 선례를 쫓아 JSON 지원 기능을 추가할 가능성이 높다.

문서 데이터베이스 쪽에서 본다면 리싱크DB는 질의 언어에서 관계형 조인을 지원하고 몽고DB 드라이버는 자동으로 데이터베이스 참조를 확인한다.(실제로는 클라이언트 측 조인을 수행한다. 네트워크 왕복이 추가로 필요하고 최적화가 덜 되기 때문에 데이터베이스에서 수행되는 조인보다 느릴 수 있다.)

관계형 데이터베이스와 문서 데이터베이스는 시간이 지남에 따라 점점 더 비슷해지고 있다. 이런 현상은 긍정적이다. 각 데이터 모델이 서로 부족한 부분을 보완해 나가고 있기 때문이다. 만약 데이터베이스가 데이터를 문서처럼 다룰 수 있고 관계형 질의를 수행할 수 있다면 애플리케이션은 필요에 따라 가장 적합한 기능을 조합해 사용하면 된다.

관계형과 문서의 혼합 모델은 미래 데이터베이스들이 가야 할 올바른 길이다.

데이터를 위한 질의 언어

관계형 모델이 등장했을 때 데이터를 질의하는 새로운 방법도 함께 나타났다. SQL은 선언형 질의 언어인 반면 IMS와 코다실은 명령형 코드를 사용해 데이터베이스에 질의한다. 이것은 무엇을 의미할까? 일반적으로 많이 사용하는 프로그래밍 언어는 명령형 언어다. 예를 들어 동물의 종 목록이 있을 때 목록에서 상어만 반환하는 코드는 다음과 같다.

function getSharks(){
	var sharks = [];
    for (var i=0; i< animals.length; i++) {
    	if (animals[i].family === 'sharks') {
        	sharks.push(animals[i]);
        }
    }
    return sharks;
}

관계대수 구조와 굉장히 유사한 SQL을 이용하여 코드를 사용하면 다음과 같다.

SELECT * FROM animals WHERE family = 'sharks';

명령형 언어는 특정 순서로 특정 연산을 수행하게끔 컴퓨터에게 지시한다. 지시의 예로 코드를 한줄씩 단계별로 실행하고 조건을 평가하고 변수를 갱신하고 루프를 한 번더 실행할지 여부를 결정하는 것을 들 수 있다.

SQL이나 관계 대수 같은 선언형 질의 언어에서는 목표를 달성하기 위한 방법이 아니라 알고자 하는 데이터의 패턴, 즉 결과가 충족해야 하는 조건과 데이터를 어떻게 변환(예를 들어 정렬, 그룹화, 집계)할지를 지정하기만 하면 된다. 어떤 색인과 어떤 조인 함수를 사용할지, 질의의 다양한 부분을 어떤 순서로 실행할지를 결정하는 일은 데이터베이스 시스템의 질의 최적화기가 할 일 이다.

선언현 잊ㄹ의 언어는 일반적으로 명령형 API보다 더 간결하고 쉽게 작업할 수 있기 때문에 매력적이다. 하지만 더 중요한 점은 데이터베이스 엔진의 상세 구현이 숨겨져 있어 질의를 변경하지 앟고도 데이터베이스 시스템의 성능을 향상시킬 수 있다는 점이다.

예를 들어 이번 절을 시작할 때 나왔던 명령형 코드에는 동물 목록이 특정 순서로 나타난다. 만약 데이터베이스 내부적으로 사용되지 않는 디스크 공간을 회수하고 싶다면 레코드를 옮겨야 할 수 있다. 하지만 그러면 동물이 나타나는 순서가 바뀔지도 모른다. 이 작업을 데이터베이스가 질의에 영향을 주지 않고 안전하게 수행할 수 있을까?

SQL 예제는 특정 순서를 보장하지 않으므로 순서가 바뀌어도 상관없다. 하지만 질의가 명령형 코드로 작성됐다면 데이터베이스는 코드가 순서에 의존하는지 여부를 확신할 수 없다. SQL이 기능적으로 더 제하적이라는 사실은 데이터베이스에게 자동으로 최적화할 수 있는 여지를 더 많이 준다는 의미다. 

마지막으로 선언형 언어는 종종 병렬 실행에 적합하다. 오늘날 CPU는 이전보다 훨씬 더 높은 클록 속도로 실행해 빨라지기보다 더 많은 코어를 추가해 빨라지고 있다. 명령형 코드는 명령어를 특정 순서로 수행하게끔 지정하기 때문에 다중 코언 ㅏ다중 장비에서 병렬 처리가 매우 어렵다. 선언형 언어는 결과를 결정하기 위한 알고리즘을 지정하는 게 아니라 결과의 패턴만 지정하기 때문에 병렬 실행으로 더 빨라 질 가능성이 크다. 가능한 경우 데이터베이스는 질의 언어의 병렬 구현을 마음껏 사용할 수 있다.

웹에서의 선언형 질의

선언형 질의 언어의 장점은 데이터베이스에만 국한되지 않는다. 요점을 설명하기 위해 완전히 다른 환경(웹 브라우저)에서 선언형 접근 방식과 명령형 접근 방식을 비교해보자.

바다에 사는 동물에 대한 웹 사이트가 있다고 가정해 보자. 사용자는 현재 상어에 대한 페이지를 보고, 다음 처럼 현재 선택된 '상어' 내비게이션 항목을 표시한다.

<ul>
	<li class='selected'>
    	<p>sharks</p>
        <ul>
        	<li>Great White Shrks</li>
            <li>Tiger Shark</li>
            <li>Hammehead 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;
}

여기서 CSS 선택자(selector) li.selected > p 는 파란 스타일을 적용하려는 엘리먼트의 패턴을 선언한다. 다시 말해, selected라는 CSS 클래스를 가진 <li> 엘리먼트가 <p> 엘리먼트들의 부모가 된다. 예제의 <p>sharks</p> 엘리먼트는 이 패턴과 일치하지만 <p>Whales</p>의 부모 <li> 엘리먼트는 class='selected'가 없으므로 이 패턴과 일치하지 않는다.

자바스크립트 코드로 작성하려면 코드량이 엄청날 뿐만 아니라, CSS 또는 XSL보다 이해하는 데 더 오래 걸리고 어려울 뿐만 아니라 몇 가지 심각한 문제가 있다.

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 클래스가 삭제된 경우(예를 들어 사용자가 다른 페이지를 클릭한 경우) 코드가 재실행 되더라도 파란색은 삭제되지 않는다. 그래서 전체 페이지가 다시 로딩될 까지 하이라이트 표시가 유지된다. CSS를 사용하면 브라우저는 li.selected > p 규칙이 더 이상 적용되지 않을 때 자동으로 감지하여  selected 클래스가 삭제되자마자 파란 배경을 삭제한다.
- Document.getElementsByClassName("selected")나 document.evalute() 같은 새로운 API의 장점(예를 들어 성능 향상)을 취하고 싶다면 코드를 재작성해야한다. 반면 브라우저 벤더는 호환성을 깨뜨리지 않고 CSS와 XPath의 성능을 향상할 수 있다.

웹 브라우저에서 선언형 CSS 스타일을 사용하는 편이 자바스크립트에서 명령형으로 스타일을 다루기보다 훨씬 낫다. 마찬가지로 데이터베이스에서는 SQL 같은 선언형 질의 언어가 명령형 질의 API보다 훨씬 좋다고 나타났다.

반응형