클라우드 컴퓨팅 & NoSQL/CouchBase

카우치베이스(Couchbase) 서버-#4 뷰(View) 이해하기

Terry Cho 2014. 7. 13. 22:58

Couchbase Server

#4. 뷰(View) 이해하기

조대협 http://bcho.tistory.com


뷰는 카우치베이스의 아주 강력한 기능중의 하나이다. RDBMS의 뷰의 개념과 유사한 개념으로, 원본 데이터로부터, 필터링을 통하여 원하는 형태의 데이터로 변환하여 보여주는 일종의 읽기 전용 테이블과 유사한 개념으로 보면 된다. 이를 통해서 키-밸류 스토어 기능만 제공하는 일반 NoSQL에 비해서 filtering 뿐만 아니라, Indexing,grouping,ordering과 같은 다양한 기능을 이 뷰를 이용하여 사용할 수 있다.

카우치베이스의 뷰는 원본 데이터에서 자바스크립트로된 맵&리듀스(Map&Reduce) 함수를 통해서 데이터를 정재한 후에, 뷰로 만들어낸다. 간단하게 개념을 잡아보면 다음과 같다.

 


좌측 그림과 같이 이름을 ID로 하고, Country,Role,Age라는 필드를 가지고 있는 값을 VALUE로 저장하고 있다고 하자, 필터링해서 보고 싶은 데이터는 여기서 Country와 Role만을 필터링 하고 싶다. 그래서 맵&리듀스 함수(여기서는 맵 함수만을 사용하였음)를 이용해서 오른쪽과 같은 뷰를 만들어낼 수 있다.


맵&리듀스(Map&Reduce)함수


뷰를 이해하려면 맵&리듀스함수에 대해서 정확하게 이해해야 한다. 맵함수는 버킷에 저장된 모든 데이터에 대해서 맵함수를 실행한다. 뷰를 정의할 때 맵함수는 반드시 정의해야 한다.

function(doc,meta){

 emit(doc.role,doc.country);

}

맵함수는 두개의 인자를 전달 받는다. “doc”는 버킷내의 저장된 개별 데이타로 각 데이터별로 id와 JSON 도큐먼트의 값을 갖는다. “meta” 는 그 데이터에 대한 메타 데이터 (flag,cas 값등)을 리턴한다.

맵함수에서는 이렇게 받은 개별 데이터를 emit이라는 함수로 가공하여 리턴한다. emit 이라는 함수에 필터링등의 로직을 적용할 수 가 있다. emit(인자1,인자2)의 인자1은 뷰의 KEY값을 리턴하고, 인자2는 뷰의 밸류값을 리턴한다.


 emit 함수를 거쳐 맵함수를 끝내면 ID,KEY,VALUE 형식의 데이터 셋이 나오는데, 이를 카우치베이스에서는 “Index”라고 부른다. ID는 원래 데이터에 대한 ID이고, KEY는 뷰의 키값이다. Index는 이 KEY에 따라서 정렬(sorting)된 형태로 리스팅 된다.  그리고 마지막에는 뷰의 값(VALUE)가 저장된다. 이렇게 생성된 Index는 디스크에 저장된다.


저장된 index를 이용해서 리듀스함수(Reduce)를 실행할 수 있는데, 리듀스 함수를 이용해서는 Grouping 기능을 사용할 수 있다. RDBMS의 group by sum이나 group by count와 같은 간단한 기능에서부터 복잡한 계산을 자바스크립트를 이용해서 구현하는 것이 가능하다.



정리하자면 뷰에는 각 버킷내의 개별 데이터를 변환하는 맵함수와, 변환된 개별 데이터를 그룹별로 모아서 처리할 수 있는 리듀스 함수를 갖는다.


Design Document


하나의 버킷에는 여러 개의 뷰를 생성할 수 있다. 아래 그림과 같이 뷰는 Design document라는 단위로 묶이게 되고 각 뷰는 그에 대칭 되는 맵&리듀스 함수를 가지고 있다.

 

 

카우치베이스의 문서를 보면 http://blog.couchbase.com/10-things-developers-should-know-about-couchbase 성능상 무리가 없게 하기 위해서는 하나의 버킷당 4개의 Design Document 정도가 적절하며, 하나의 Design Document당 최대 10개 정도의 뷰를 구현하는 것이 적절하다고 권고하고 있다. (즉 버킷당 최대 40대 정도의 뷰가 적절)


콘솔에서 뷰 만들기


개념을 이해 했으면 이제 간단한 뷰를 만들어보자, 카우치베이스는 웹콘솔에서 뷰생성 및 개발 테스트를 할 수 있도록 지원한다. 카우치베이스 웹콘솔로 이동한후 상단메뉴에서 “Views” 메뉴를 선택한후 아래에 나오는 리스트박스에서 뷰를 생성하고 싶은 버킷을 선택한다. 

다음으로, 아래에 나오는 “Create Development View” 버튼을 선택하면 뷰를 생성할 수 있다.


 

생성 버튼을 누르면 아래와 같이 대화상자가 뜨는데, 첫번째는 Design Document 명이다. 그리고 다음 텍스트 상자가 뷰 이름을 넣는 상자이다.

 


이때, design document를 넣는 텍스트박스에서 Design Document 이름이 “dev_”로 시작하는 것을 볼 수 있는데, Design document는 두 가지 타입이있다. 개발을 위한 개발용 “Development view”와 실제 운영에서 사용할 수 있는 “Production view”가 있다. 이 둘의 차이점은 다음과 같다.

  • Development View : 처음에 뷰를 생성하면 Development View로 생성된다. Design document 이름은 “_design/dev_”로 시작된다. Development View의 경우 버킷의 모든데이타에 대해서 Index를 만드는 것이 아니라 개발 목적이기 때문에 일부의 데이터에 대해서만 Index를 생성한다.
  • Production View : Development View를 이용해서 개발과 테스트를 완료하면 Publish하여 Production View로 전환한다,  Production View는 버킷의 전체 데이터로 Index를 생성한다. 

※ Development View에서 개발 및 테스트가 완료되었으면 반드시 Production View로 배포를 진행해야 한다.

뷰가 생성되었으면 콘솔에서 아래와 같이 뷰의 맵&리듀스 코드를 작성하고 테스트를 해볼 수 있다.



위에 "▼cath”라는 창에 맵&리듀스 코드 작성을 돕기위해서 버킷에 저장된 데이터 중 random으로 출력해서 샘플로 보여준다. “cath”는 샘플로 출력되는 도큐먼트의 ID이다.

아래 “▼View Code” 창 부분에는 왼쪽에는 맵 함수를 오른쪽에는 리듀스 함수를 직접 코딩해볼 수 있다. 그리고 맨아래 부분에는 이 맵&리듀스 함수를 실행했을 때의 결과를 출력해준다.

그 아래 부분에는 Filter Result라는 버튼이 있는데, 이 버튼을 통해서 맵&리듀스에 의해 생성된 결과를 필터링 할 수 있다. 도큐먼트 ID나 KEY의 범위등 지정해서 pagination등에 활용할 수 있다. 자세한 필터에 대한 내용은 향후에 설명하도록 한다.

 





뷰를 이용하여 다양한 기능 구현하기

이러한 뷰를 이용해서 카우치베이스는 다양한 데이터 조작이 가능한데, 대부분의 NoSQL은 키밸류 형태로 RDBMS에서 지원하는 Indexing, Grouping, Sorting 등 강력한 기능을 지원하지 않는 경우가 많다. 카우치베이스에서는 뷰를 이용해서 이러한 기능등을 구현할 수 가 있다.


Indexing 

RDBMS의 Index와 같은 개념으로, 도큐먼트 ID이외에 다른 필드로 검색이 가능하게 해주는 기능이다. NoSQL에서는 이를 Secondary Index라고도 부르는데, Secondary Index를 지원하는 다른 NoSQL의 경우, 제대로 성능이 나도록 설계되어 있는 경우도 있지만, Secondary Index를 기반으로 검색을 할 경우, 분산된 전체 노드를 FULL SCAN해서 (전체 목록을 뒤져서) 검색 결과를 한 노드로 합쳐서 리턴하는 형태로 구현되어 있는 경우가 있다. (Riak의 예전 Secondary Index의 구조) 이 경우, 전체 노드가 O(N)으로 전체 레코드를 검색해야 하기 때문에, 전체적인 성능 저하가 매우 심하다. 앞에서도 언급했지만, 카우치베이스는 이를 Incremental view의 개념을 사용해서, Secondary Index에 해당 하는 필드를 키로 잡은 새로운 물리적인 테이블을 내부적으로 만들기 때문에, Index를 이용한 검색도 타 NoSQL에 비해서 성능이 좋은 편에 속한다.


Extracting & Filtering

필터링은 SQL문장에서 select where라고 보면 된다. 조건에 맞는 특정한 도큐먼트만 select하는 것이 가능하다.

다음과 같은 도규먼트들이 버킷에 저장되어 있다고 할때

{

   "name": "yuna",

   "country": "us",

   "ssn": "140515-1234123",

   "sex": "female"

}

여기서 country="us" 인 것만을 쿼리하고, 전체 필드중 일부 필드(컬럼)만을 가지고 리턴하는 뷰를 만들려고 하면, 다음과 같은 맵 함수를 이용하면 된다.
function (doc, meta) {
  if(doc.country=="us") emit(doc.name,doc.country,doc.ssn);
}
emit에 오는 첫번째 필드가 KEY로 사용된다. 
단 뷰 함수에 의해서 필터링이 된 경우에는 물리적인 Index가 생기기 때문에, country를 Korea,Canada등과 같이 동적으로 변경하는 것이 불가능하다.
이런 동적인 필터링은 카우치베이스의 필터를 이용해서 가능한데, 동적으로 나라에 따라서 필터링을 하고 싶다면, 조건을 적용할 필드를 키로 리턴한 후, 키에 대한 필터링을 하면 되는데, 다음과 같이 맵 함수를 작성한 후에 뷰를 생성하고
function (doc, meta) {
  emit(doc.country,doc.name,doc.ssn);
}
아래와 같이 필터 조건에 key를 "us"로 지정하면 doc.country가 "us" 인 도큐먼트만 쿼리가 된다.
※ 문자열은 반드시 "" 을 사용해서 지정해야 한다.


Sorting
카우치베이스에서 소팅은 뷰의 키값을 기반으로 한다. 소팅의 기준으로 하고자하는 필드를 KEY로 잡으면, 뷰에서는 디폴트로 오름차순으로 키에 따라 정렬을 해준다. 내림 차순으로 정렬을 하기 위해서는 위의 필터에서 descending이라는 옵션을 체크 해주면 된다.

리듀스(Reduce)함수를 이용한 Grouping 연산
Grouping은 RDBMS의 group by와 유사한 기능으로 같은 country인 도큐먼트 수를 카운트하거나, 태어난 해별 사용자 수를 하는 것과 같은 그룹 단위의 연산을 할 수 있다.
먼저 Grouping의 개념을 사용하기 위해서는 Compound Key라는 개념을 이해해야 하는데, 카우치베이스의 Grouping 연산은 이 Compound Key를 기본으로 한다. Compound Key랑 뷰의 키를 [] (배열) 형태로 정의한 경우이다. 예를 들어 키를
["Country","생년","성별"] 과 같은 형태의 배열 형태로 정의하는 것이다. Grouping은 이 Compound Key의 Level 단위 (즉 배열의 첫번째 값으로만 그룹을 만들것인가? 아니면 첫번째,두번째 값을 포함해서 그룹으로 잡을 것인가) 로 처리한다.
실제 예제를 살펴보자. 다음과 같은 JSON 도큐먼트가 있을때,
{
   "name": "yuna",
   "country": "us",
   "ssn": "140515-1234123",
   "sex": "female"
}
앞에서 언급한 형태의 Compound Key를 만드는 함수는 다음과 같다, 
function (doc, meta) {
  var year = doc.ssn.substring(0,2);
  emit([doc.country,year,doc.sex],1);
}
이 맵함수를 이용하면 뷰는 다음과 같아진다.
["canada","69","male"] 1
["korea","75","male"] 1
["us","08","female"] 1
["us","14","female"] 1
["us","75","female"] 1
그러면 국가별로 사용자 수를 통계를 내려면, 리듀스 함수에 "_count" 라고 정의하고
필터에 group 옵션을 선택한후에, group level을 1로 지정한다.

이렇게 하면, Compound Key의 첫번째 필드 (country)를 기반으로 그룹핑을 하고, 리듀스 함수에서 이 그룹단위로 _count 연산을 수행한다.

카우치베이스에는 리듀스함수에서 사용할 수 있는 기본적인 함수를 미리 정의해놨는데, 아래와 같다.

  • _sum : 밸류값을 더한다. (밸류 타입이 Integer라야 함)
  • _count : 그룹별로 묶인 값들의 수를 카운트 한다.
  • _stats : 각종 그룹값을 계산해준다, 위에서 나온 sum뿐만 아니라, sum,count,min(최소값),max(최대값),sumsqr(Sum of square : 제곱합) 값을 출력해준다.
Pagination 
뷰에서 만들어진 데이타는 쿼리시에 pagination을 지원할 수 있다. 필터를 통해서, 뷰의 start/end key값을 정의하거나, start/end 도큐먼트ID를 지정하면 그 범위내의 도큐먼트만 쿼리할 수 있다. (위의 필터 설정 그림 참고) 콘솔에서는 나와 있지 않지만 필터에 추가로 limit라는 옵션을 주면, 최대한 읽어올 수 있는 도큐먼트 수를 정의할 수 있다.
예를 들어 뷰의 키로 1~100까지 100개의 도큐먼트가 있을때, start key=11,end key=50으로 하면 11~50까지의 50개의 레코드가 리턴된다. 여기에 limit=10을 추가하면 11~20까지의 레코드만 리턴되낟. start/end와 limit를 적절하게 사용하면 페이징 기능을 구현할 수 있다. limit를 설정하는 방법은 SDK를 이용하여 뷰를 호출하는 방법에서 알아보도록 한다.

다음번에는 SDK를 이용해서 뷰를 생성하고, 뷰에 대한 쿼리를 수행하는 방법에 대해서 알아보도록 하겠다.