본문 바로가기
개발 Story/몽고디비

07. Mongodb - 도큐먼트 지향 데이터

by niee 2016. 11. 26.

이제부턴 스터디에서 정리한 자료로 대체

스터디 github : https://github.com/KWSStudy

몽고디비 정리 wiki : https://github.com/KWSStudy/Mongodb/wiki


4.1 스키마 설계 원리

  • "이론에 따라 스키마를 설계해야 하지만 실제에서는 이론을 융통성 있게 적용해야 한다." (이에 따른 제기할수 있는 질답)

    • 데이터의 기본 단위는 무엇인가?
      • RDBMS에서는 행과 열로 이루어진 테이블이 있다. 키(값) 저장 시스템에서는 키와 값을 갖는데, 여기서 값은 비구조적이다. MongDB에서는 데이터 기본적 단위가 BSON도큐먼트다.
    • 데이터를 어떻게 쿼리하고 업데이트할 것인가?
      • RDBMS는 임의 쿼리와 조인이 특징. MongDB 또한 임의 쿼리를 허용하지만 조인은 지원하지 않는다. (간단한 키(값) 저장 시스템은 하나의 키에 대해서만 값을 가져올 수 있다.
      • MongDB는 ㅡ랜잭션을 지원하지는 않지만 복잡한 도큐먼트의 내부 구조에 대해 수행할 수 있는 원자적인 업데이트를 다양하게 지원한다.
      • 요점은 최적의 데이터 모델을 설계하려면 데이터베이스의 특징을 이해하고 있어야 한다는 것이다.
    • 애플리케이션의 액세스 패턴은 무엇인가?
      • 애플리케이션의 요구사항이 무엇인지 정확히 파악해야한다.
      • 읽기/쓰기 비율은 어떻게 되는가? 어떤 쿼리가 필요한가? 데이터는 어떻게 업데이트가 되는가? 동시성 문제는 어떻게 되는가? 데이터는 어느 정도로 잘 구조화할 수 있는가?

    4.2.1 상품과 카테고리

  • 도큐먼트에 대한 URL을 생성할 때는 슬러그(slug) 필드를 만들 것을 권한다. 그러한 필드에 대해서는 고유 인덱스를 만들어서 프라이머리 키로 사용할 수있다.
  • product 입력 데이터는 샘플 소스 참조
  • db.products.ensureIndex({slug:1}, {unique:true}) / slug 생성, 고유인덱스
  • 유니크 키
  • @products.insert({:name => "Extra Large Wheel Barrow"}, :sku => "9092", :slug => "wheel-barrow-9092"}, :safe => true)
  • :safe => true 라고 지정 위 삽입문이 예외를 발생하지 않고 성공한다면 슬러그 값이 고유한 값이라는 것을 알 수 있다. 예외가 발생하면 새로운 슬러그로 다시 시도해야 할 것이다.
  • detail 이라는 키는 여러가지 상품에 대한 자세한 정보를 갖는 서브도큐먼트를 가리킨다 (detail 속성은 이러한 동적인 속성을 갖기에 적합)
  • MongDB는 조인을 지원하지 않기 때문에 다대다 관계를 위해서는 다른 어떤것이 필요하다.
  • 상품 샘플 데이터를 보면 category_ids 라는 필드가 객체 ID를 가지고 있는 것을 볼 수 있다. 각 객체 ID는 카테고리 도큐먼트의 _id 필드에 대한 래퍼런스다.
doc =
{  _id: ObjectId("6a5b1476238d3b4dd5000048"),
   slug: "gardening-tools",
   ancestors: [{ name: "Home",
                 _id:  ObjectId("8b87fb1476238d3b4dd50003"),
                 slug: "home"
                },

                { name: "Outdoors",
                  _id:  ObjectId("9a9fb1476238d3b4dd500001"),
                  slug: "outdoors"
                }
   ],

   parent_id: ObjectId("9a9fb1476238d3b4dd500001"),

   name: "Gardening Tools",
   description: "Gardening gadgets galore!",
}
  • 책의 예제에서는 RDBMS의 in절 사용법이 루비로 나와있는데 자바에서 in절 사용법은http://rockfactory.tistory.com/81 를 참조

    4.2.2 유저와 오더

  • 유저와 오더로 일대다의 관계를 설명 할 수 있다.
  • 유저는 하나 이상의 오더를 가지고 있다.
  • 두번째 속성인 user_id는 유저의 id값을 가지고 있다.
  • db.orders.find({user_id:user['_id']}) / 어느 한 유저가 주만한 모든 오더를 조회
  • var user_id = order['user_id'] db.users.find({_id:user_id}) /특정 오더에 대한 유저의 도큐먼트를 얻기위한 쿼리
  • 따라서 오더와 유저사이에 존재하는 일대다 관계가 객체ID를 레퍼런스로 사용해서 쉽게 구현이 가능하다

4.2.3 상품평

  • 각 상품은 하나 이상의 리뷰를 가지고있다. 여기에서 관계는 review_id라는 객체 ID래퍼런스로 표현
  • review_template.js 참조
  • MongoDB에서는 조인이 없기 때문에 두 가지 방법 중 한가지를 써야한다. 각 리뷰마다 유저 컬렉션에 대해 질의하거나 비정규화를 해야한다.
  • 이것은 유저네임을 수정할 경우, 유저네임이 들어가 있는 모든 도큐먼트를 수정해야 하기 때문에 비용이 더 많이들어간다는 것을 의미하지만, 이런 경우는 드물기 때문에 이렇게 설계하는게 타당하다. (일반적으로 유저네임명(uniqe)값을 수정하는 일은 없기에)
  • 추천수를 배열로 저장한 이유는 한 유저가 두번의 추천을 입력하는것을 방지
  • 추천수를 저장하는것은 추천수에 따른 정렬를 위함

4.3 실제적 세부사항: 데이터베이스, 컬렉션, 도큐먼트

  • 데이터베이스, 컬렉션, 도큐먼트를 사용하는 것에 대해 실제 세부사항을 살펴보도록하자.
  • MongDB가 데이터 파일을 어떻게 할당하는지, 혹은 도큐먼트 내에서 어떤 데이터 타입이 허용되는지, 또는 캡드(?) 컬렉션을 사용하는 것이 어떤 이점이 있는지 알아보자

4.3.1 데이터베이스

  • 데이터베이스 관리

    • MongoDB에서는 데이터베이스를 생성하는 별도의 다른 방법이 없다.
    • 대신 데이터베이스 내의 컬렉션에 쓰기를 하면 자동으로 생성된다.
    • 상품컬렉션에 save를 호출할 때 (insert) MongoDB에게 garden.products 네임스페이스에 상품 도큐먼트를 저장하라고 명령 네임스페이스가 존재하지 않으면 네임스페이스가 먼저 생성되고, 이 과정에서 garden 데이터베이스가 디스크에 할당된다
    • 데이터베이스를 삭제하는 쉘 명령어는 use garden db.dropDatabase();
    • 데이터 베이스를 지우고 나면 취소할 수 없기 때문에 조심해야 한다.
  • 데이터 파일과 할당

    • 데이터베이스가 생성될 때 MongoDB는 몇가지 데이터 파일을 디스크에 할당한다. 모든 컬렉션, 인덱스, 데이터베이스에 대한 메타데이터가 여기에 저장된다.
    • 이 파일들은 mongod를 시작할 때 dbpath에서 지정한 디렉터리에 저장된다. sudo mongod --dbpath /data/db &;
    • dbpath가 지정되지 않으면 mongod는 모든 파일을 /data/db에 저장한다
      • mongod.lock : 서버의 프로세스 ID를 저장 (비정상적으로 셧다운된 후에 다시 부팅되는 경우가 아니라면 이파일을 절대 지우거나 변경하면 안된다 복구 프로세스는 10장에서 설명) garden 데이터 베이스가 처음 생성될때 garden.ns라는 파일이 가장 먼저 만들어진다. 파일 확장자 인 ns는 네임스페이스를 의미, 데이터베이스 내의 모든 컬렉션과 인덱스는 자신만의 네임스페이스를 갖는데, 각 네임스페이스에 대한 메타데이터를 이 파일에 저장. 디폴트로 크기가 16MB로 고정되어 있어서 약 24,000개의 네임스페이스를 저장할 수 있다 이것은 한 데이터베이스에서 인덱스와 컬렉션의 개수가 24,000을 넘어설 수 없다는 것을 의미. 더 많이 필요할 경우에는 --nssize옵션을 사용해서 이 제한을 조정할 수 있다.
      • garden.0(64MB), 1(128MB) : MongoDB는 최대한 많은 데이터가 디스크에 연속적으로 저장되도록 미리 할당 (연산이 디스크의 여기저기에 흩어져 있는 데이터가 아니라 인접된 데이터에 대해 수행되도록 하기위함)
      • stats 명령을 사용해서 사용하는 공간과 할당된 공간을 언제라도 확인할수있다. db.status
      • filesize필드는 이 데이터베이스에 대해 할당된 파일의 전체 크기를 보여준다 garden.0 + garden.1 파일의 용량의 합
      • dataSize : 데이터베이스에서 BSON오브젝트의 실제크기 storageSize : 컬렉션이 증가할 것을 대비한 여분의 공간과 삭제되었지만 아직 할당되지 않은 공간 indexSize : 데이터베이스 인덱스의 전체크기를 보여준다. 모든 인덱스가 램에 있을 때 데이터베이스 성능이 최적이 되기 때문에 indexSize 값을 주의 깊게 봐야한다. (7장과 10장에서 성능과 관계된 문제를 해결하기위한 기법들을 소개할때 자세히 설명)

4.3.2 컬렉션

  • 컬렉션은 구조적으로 혹은 개념적으로 유사한 도큐먼트를 담고 있는 컨테이너이다.
  • 컬렉션 관리

    • 컬렉션 생성 : db.createCollection("users")
    • 컬렉션 생성시 크기를 미리 할당하는 옵션 : db.createCollection("user", {size:20000})
    • 컬렉션의 이름은 숫자와 알파벳 또는 "." 으로 만들 수 있으나 반드시 알파벳이나 숫자로 시작해야한다.
    • 내부적으로 컬렉션 이름에는 자신이 속한 데이터베이스의 이름이 포함돼 있다.
    • 컬렉션의 전체 이름, 즉 네임스페이스는 128자 이내여야 한다
    • 컬렉션 이름에 "."을 사용하여 일종의 가상 네임스페이스를 만드는 것이 유용할 때가 있다. 예를들어 products.categories, products.images, products.reviews
    • 하지만 이것은 단지 편의를 위한 것이고 데이터베이스는 컬렉션 이름이 "."을 포함해도 다른 컬렉션과 똑같이 처리한다는 점을 명심
    • 컬렉션의 이름변경 : db.products.renameCollection("store_products")
  • 캡드 컬렉션

    • 높은 성능의 로깅 기능을 위해 설계되었다
    • 고정된 크기를 갖는점이 일단 컬렉션과 다르다
    • 공간이 더이상 없게되면 도큐먼트를 삽입할 때 컬렉션에 추가된 지 가장 오래된 도큐먼트를 덮어쓰게된다.
    • 이 기능은 수동으로 컬렉션의 오래된 데이터를 지워야한 하는 번거로움을 없애준다.
    • 도큐먼트를 삽입된 순서로 정렬하는 점과 인덱스를 생성하지 않는다는 점 외에도 캡트 컬렉션에서는 특정 CRUD연산이 제한되는 점 또한 알아야한다 개별도큐먼트를 지울 수 없고 도큐먼트의 크기가 커기는 결과를 초래하는 업데이트를 수행할 수 없다. (캡드 컬력센은 로깅을 위해 만들어진 것, 이것들을 구현하기위해서는 오래된 도큐먼트의 age를 핸들링하는 코드가 복잡해지기 떄문)
  • 시스템 컬렉션

    • MongDB 내부에서 컬렉션을 부분적으로 사용, 언제나 존재
    • system.namespace와 system.indexes다. system.namespace 는 현재 사용 중인 데이터 베이스에서 정의돈 모든네임스페이스를 보여준다

    -4.3.3 도큐먼트와 인서트

- 모든 도큐먼트는 MongoDB에 저장하기 전에 BSON으로 시리얼라이즈 되어야하고, BSON으로부터 프로그래밍 언어로 표현되는 도큐먼트 형식으로 디시리얼라이즈 된다. 예를들어 캡드 컬렉션에 대해 설명할 때 샘플 도큐먼트 크기가 약 100바이트 정도라고 추정했었는데 루비, 자바 등등 드라이버의 BSON 시러얼라이저를 이용하면 그 가정이 맞는지 확인할 수 있다.
- 문자열
  - 모든 문자열은 UTF-8 형식이어야 한다. 
  - MongoDB로 변환하기 전에 미리 UTF-8로 변환을 하든지, 그것이 여의치 않을 경우 텍스트를 BSON바이너리 타입으로 저장하면 된다.
- 숫자
  - BSON은 double, int, long의 세 가지 수 타입을 규정
  - IEEE실수와 signed정수를 8바이트 까지 인코딩할 수 있다는 것을 의미, 자바스크립트는 Number라는 수 타입을 하나만 가지고 있는데, 이것은 IEEE double 에 해당.
  - 따라서 셸을 통해 정수 값으로 저장하고 싶다면 NumberLong()이나 NumberInt를 써서 정수라는 것을 명시해야한다.
- 날짜와 시간
  - BSON datetime 타입은 시간이나 날짜에 관련된 값을 저장하는 데 사용된다.
  - 시간은 signed64비트 정수를 사용해서 UTC로 유닉스 에폭이후 지나간 밀리초로 표현한다.(유닉스 에폭은 1970년 1월 1일 자정)
  - 주의사항 : 자바스크립트에서 날짜를 생성하는 경우에는 월을 표현할 때 0부터 시작한다는점 ( new Date(2011, 5 ,11) -> 2011년 6월 11일), 루비 드라이버를 이용해서 시간 데이터를 저장할 경우 BSON datetime 은 타임 존을 인코드할 수 없기 때문에 이 데이터를 가지고 있는 date 클래스를 사용할 수 없다.

- 커스텀 타입 
  - 시간을 반드시 타임존과 함께 저장해야 할 필요가 있을때 BSON타입을 사용해서 가상의 타입을 만들어낼 수 있다.
- 도큐먼트 크기에 대한제약
  - 도큐먼트의 최대 크기는 16MB이다 
  - 1. 개발자가 효율적이지 못한 데이터 모델을 생성하는 것을 막기위함
  - 2. 성능과 관계
  - 일반적으로 큰 객체를 가지고 있다면 데이터 모델을 수정해서 한두 개의 컬렉션을 추가로 만들어 분할하는 것이 더 낫다. 이미지나 비디오같이 단순하게 크기가 큰 바이너리 객체를 저장하는 것은 조금 다른 경우다. 대용량 바이너리 데이터를 처리하기 위한 기법은 부록 c를 참조...

- 대량 삽입 연산
  - 모든 드라이버에서는 여러 개의 도큐먼트를 동시에 삽입하는 것이 가능하다. (초기화할 때 대량의 데이터를 임포트 한다든지, 다른 데이터 베이스 시스템에서 MongoDB로 옮겨운 경우 등 여러 상황에서 유용)
  - 미리 다수의 도큐먼트 배열을 만들고 insert메소드에 전체 도큐먼트의 배열을 넘겨준다.
  - 대량 삽입을 가장 효율적으로 하려면 16MB 제한 이하로 해야한다.