MongoDB in Action 4

김하영·2022년 1월 24일
0

4. 도큐먼트 지향 데이터

4.1 스키마 설계 원리

데이터베이스 스키마 설계는 데이터베이스 시스템의 기능과 데이터의 특성, 애플리케이션의 요구사항이 주어졌을 때 데이터에 대한 최적의 표현을 찾아내는 과정이다.

"이론에 따라 스키마를 설계해야 하지만, 실제에서는 이론을 융통성 있게 적용해야 한다" 라는 전제를 바탕으로 한다.

애플리케이션의 액세스 패턴은 무엇인가?

애플리케이션의 요구사항이 무엇인지 정확히 파악할 필요가 있고, 이는 스키마 설계뿐만 아니라 여러분이 선택한 데이터베이스에도 고려되어야 하는 사항이다.

애플리케이션 액세스(접근) 패턴을 이해하는 것은 스키마 설계에서 가장 중요한 부분이다.

애플리케이션의 이질적 특성으로 인해 데이터 모델링에 관한 강고한 원리들에 역행하는 스키마가 필요하게 되었다.
그러므로 데이터 모델을 결정하는 데 도움을 얻기 위해서는 애플리케이션에 관해서 여러 가지 질문을 해야한다.

  • 읽기 / 쓰기 비율은 어떻게 되는가?
  • 쿼리는 키를 찾는 정도로 쉬운가 아니면 더 복잡한가?
  • 집계는 필요한가?
  • 데이터는 얼마나 저장되는가?

( 애플리케이션 액세스 패턴은 애플리케이션에서 데이터를 접근하는 패턴을 의미하는 건가? )

데이터베이스에는 어떤 기능이 있는가?

보통 간단한 키-값 저장 시스템이 하나의 키에 대해서만 값을 가져올 수 있는 반면에
RDMS는 애드훅 쿼리와 조인이라는 특징이 있다.

MongoDB 또한 애드훅 쿼리를 허용하지만 조인은 지원하지 않는다.

RDBMS에서는 SQL문을 사용해서 정교하게 데이터를 수정할 수 있고, 원자성과 롤백을 위해
업데이트 문을 트랜잭션 안에 포함시킬 수도 있다.

MongoDB는 기존 방식의 트랜잭션을 지원하지 않지만, 복잡한 도큐먼트의 내부 구조에 대해 수행할 수 있는 원자적인 업데이트를 다양하게 지원한다. 간단한 키-값 구조에서는 값을 수정할 수 있지만, 모든 업데이트문은 값을 완전히 대치한다.

좋은 고유 식별자(unique id)와 프라이머리 키를 무엇으로 만드는가?

데이터베이스에 관계없이 많은 스키마들이 고유한 키를 갖는다.
이러한 키를 고르는 것은 데이터를 접근하는 방식과 저장하는 방식에서 큰 차이를 만들어 낼 수 있다.

MongoDB에서 프라이머리 키를 선택하는 것은 어떤 값이 _id 필드에 채워질 것인지 고르는 것을 뜻한다.
자동으로 생성되는 객체 id는 기본 설정으로 나쁘지 않지만, 모든 경우에 이상적인 것은 아니다.
여러 장비에 데이터를 샤딩하는 경우라면 이는 특히 중요한데, 특정 행이 저장될 곳을 결정하기 때문이다.

최적의 스키마 설계는 사용하는 데이터베이스에 대한 깊은 지식과 애플리케이션의 요구사항에 대한
현명한 판단 그리고 오랜 경험으로 부터 나온다.

좋은 설계는 애플리케이션 확장과 성능을 고려한 변경과 같은 실험과 반복으로 부터 나온다.

4.2 전자상거래 데이터 모델 설계

MongoDB에서는 객체-관계 매핑 툴에 대한 필요성이 상대적으로 낮다. 도큐먼트가 이미 객체와 같은 표현이기 때문이고, 다른 한편으로는 사용하기 쉬운 드라이버가 MongoDB에 대해 상위 계층 인터페이스를 제공하기 때문이다. 말할 필요도 없이 드라이버 하나만 가지고도 전체 애플리케이션을 구축할 수 있다.

4.2.1 스키마 기본

MongoDB에서는 상품을 모델링하는 것이 좀 더 단순하다. 컬렉션에 대한 스키마가 필요하지 않으므로 어떠한 상품 도큐먼트라도 상품이 필요로 하는 동적인 속성을 받아들일 수 있다.

도큐먼트 내에서 배열을 사용하면, RDBMS에서 여러 개의 테이블로 표현되는 것이 MongoDB에서는 하나의 컬렉션으로 줄어들 수 있다.

{  
   _id: new ObjectId("4c4b1476238d3b4dd5003981"),
  slug: "wheel-barrow-9092",
  sku: "9092",
  name: "Extra Large Wheel Barrow",
  description: "Heavy duty wheel barrow...",
  details: {
    weight: 47,
    weight_units: "lbs",
    model_num: 4039283402,
    manufacturer: "Acme",
    color: "Green"
  },
  total_reviews: 4,
  average_review: 4.5,
  pricing:  {
    retail: 589700,
    sale: 489700,
  },
  price_history: [
      {retail: 529700,
       sale: 429700,
       start: new Date(2010, 4, 1),
       end: new Date(2010, 4, 8)
      },

      {retail:  529700,
       sale:  529700,
       start: new Date(2010, 4, 9),
       end: new Date(2010, 4, 16)
      },
  ],
  category_ids: [new ObjectId("6a5b1476238d3b4dd5000048"),
                    new ObjectId("6a5b1476238d3b4dd5000049")],
  main_cat_id: new ObjectId("6a5b1476238d3b4dd5000048"),
  tags: ["tools", "gardening", "soil"],
}

고유한 슬러그

의미 있는 URL을 위해 정의한 slug 필드에는 'gardening-tools' 라는 값이 있다.
URL에 객체 ID가 들어가 있으면 보기 싫기 때문에, 의미 있는 ID 인 슬러그 필드에 값을 저장한다.

ex) http://mygardensite.org/products/gardening-tools

도큐먼트에 대한 URL을 생성할 때는 슬러그(slug) 필드를 만들 것을 권한다.
그러한 필드에 대해서는 고유 인덱스를 만들어서 해당 필드의 값이 빠른 쿼리 접근성을 갖는 동시에 고유성을 보장하도록 한다. 또한 슬러그를 _id 필드에 저장하여 프라이머리 키로 사용할 수 있다.

다음과 같이 고유 인덱스를 생성할 수 있다.

db.products.ensureIndex({slug:1}, {unique:true}) / slug 생성, 고유인덱스

slug에 대한 고유 인덱스를 생성했다면 중복된 값을 삽입했을 때, 예외가 발생할 것이다.

중첩 도큐먼트

detail 라는 키의 값을 보면, 여러 가지 상품에 대한 자세한 정보를 갖는 서브 도큐먼트(subdocument)를 가리킨다.
pricing 키는 소비자 가격과 세일 가격을 가지고 있는 객체를 가리킨다.
반면에 price_history 는 가격의 변천사를 배열로 저장하고 있다.
이렇게 도큐먼트의 복사본을 저장하는 것을 도큐먼트를 버전별로 관리하는 일반적인 기법이다.

상품에 대한 태그의 배열은 배열의 키에 대해 인덱스를 만들 수 있으므로 이것은 상품에 대한 관련 태그를 저장하고 동시에 효율적인 쿼리를 보장해주는 가장 간단하고 좋은 방법이다.

일대다 관계

종종 다른 컬렉션에 있는 도큐먼트와 관계를 설정해야만 할 때가 있다.
상품이 오직 하나의 주요 카테고리만 가지고 있지만, 카테고리는 많은 상품들의 주요 카테고리가 될 수 있으므로
이는 일대다 관계다.

다대다 관계

각 상품이 하나 이상의 카테고리에 속하고, 각 카테고리 역시 하나 이상의 상품을 가지므오 다대다 관계다.
RDBMS 에서는 이런 다대다 관계를 표현하기 위해 조인 테이블을 이용한다.
MongoDB는 조인을 지원하지 않으므로 다대다 관계를 위해서는 다른 어떤 것이 필요하다.
우리는 객체 ID를 가지고 있는 category_ids 라는 필드를 정의한 바 있다.
각 객체 ID는 카테고리 도큐먼트의 _id 필드에 대한 레퍼런스다.

도큐먼트에 대한 생각

도큐먼트로 인한 표현은 아래와 같은 장점을 갖는다.

  1. 사람이 이해하기 쉽다.
  2. 전반적인 개념이 하나의 객체 안에 들어가 있다.
  3. 도큐먼트 내에서 효과적으로 동결된다.
  4. 도큐먼트에 대한 질의와 수정이 용이하다.

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

4.3.1 데이터베이스

데이터베이스는 컬렉션과 인덱스의 물리적인 모음이며, 동시에 네임스페이스다.

데이터베이스 관리

MongoDB에서는 데이터베이스를 생성하는 별도의 다른 방법이 없다. 대신 데이터베이스 내의 컬렉션에 쓰기를 하면 자동으로 생성된다.

데이터 파일과 할당

데이터베이스가 생성될 때, MongoDB는 몇 가지 데이터 파일을 디스크에 할당한다.
모든 컬렉션, 인덱스, 데이터베이스에 대한 메타데이터가 여기에 저장된다.

이 파일들은 mongod를 시작할 때, dbPath에서 지정한 디렉터리에 저장된다.
만약 지정되지 않으면 /data/db에 저장한다.

자바 스크립트 셸상에서 stats 명령을 사용해서 사용하는 공간과 할당된 공간을 언제라도 확인할 수 있다.

> db.stats(1024);
{
        "db" : "test",
        "collections" : 12,
        "objects" : 50796,
        "avgObjSize" : 579.9073155366565,
        "dataSize" : 28766,
        "storageSize" : 50168,
        "numExtents" : 24,
        "indexes" : 13,
        "indexSize" : 2283,
        "fileSize" : 65536,
        "nsSizeMB" : 16,
        "dataFileVersion" : {
                "major" : 4,
                "minor" : 5
        },
        "extentFreeList" : {
                "num" : 0,
                "totalSize" : 0
        },
        "ok" : 1
}
  • fileSize : 데이터베이스에 할당된 파일의 전체 크기를 보여줌
  • dataSize : 데이터베이스에서 BSON 객체의 실제 크기
  • storageSize : 컬렉션이 증가할 것을 대비한 여분의 공간과 삭제되었지만 아직 할당되지 않은 공간을 포함
  • indexSize : 데이터베이스 인덱스의 전체크기

4.3.2 컬렉션

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

컬렉션 관리

별도의 명령어 없이 도큐먼트를 네임스페이스에 삽입하는 것만으로도 컬렉션이 생성된다.
그렇지만 하나 이상의 컬렉션 타입이 있으므로 MongoDB에는 컬렉션을 생성하는 별도의 명령어가 있다.

db.createCollection("users")

표준적인 컬렉션을 생성할 때, 크기를 미리 할달하는 옵션이 있다.

db.createCollection("users",{size:20000})

컬렉션 이름은 숫자와 알파벳 또는 '.'으로 만들 수 있으나 반드시 알파벳이나 숫자로 시작해야 한다.

캡드 컬렉션

캡드 컬렉션은 원래 높은 성능의 로깅 기능을 위해 설계되었다. 일반 컬렉션과 다른 점은 고정된 크기를 갖는다는 점이다.
일단 캡드 컬렉션이 더 이상의 공간이 없게 되면 도큐먼트를 삽입할 때 컬렉션에 추가된 지 가장 오래된 도큐먼트를 덮어쓰게 된다. 이 기능은 수동으로 컬렉션의 오래된 데이터를 지워야만 하는 번거로움을 없애 준다.

캡드 컬렉션은 일반적인 보통 컬렉션에 모든 연산이 가능하도록 허용하지는 않는다.
한 가지 예로, 캡드 컬렉션에서 개별 도큐먼트를 삭제할 수 없고,
마찬가지로 도큐먼트의 크기를 증가 시키는 어떤 업데이트도 수행할 수 없다.
캡드 컬렉션은 원래 로깅을 위해 만들어진 것이므로 도큐먼트를 삭제하거나 업데이트하는 것을 구현할 필요가 없었다.

TTL 컬렉션

TTL(Time-To-Live) 컬렉션은 특정 시간이 경과한 도큐먼트를 만료시킬 수 있는 기능을 제공한다.
실제로 이 기능은 특별한 인덱스를 사용하여 구현한 것 이다.

db.reviews.createIndex({time_field: 1},{expireAfterSeconds: 3600})

시스템 컬렉션

MongoDB 내부에서 컬렉션을 부분적으로 사용한다. 이러한 특별한 시스템 컬렉션 중 두가지로 system.namespace 와 system.indexes를 들 수 있다.

  • system.namespace : 현재 사용 중인 데이터베이스에서 정의된 모든 네임스페이스를 출력
  • system.indexes : 각 인덱스에 대한 정의를 저장

4.3.3 도큐먼트와 인서트

도큐먼트 시리얼라이제이션, 타입 그리고 한계

모든 도큐먼트는 MongoDB에 저장하기 전에 BSON으로 시리얼라이즈(serialize)되고, 나중에 BSON으로 부터 디시얼라이즈(deserialize) 된다.
드라이버는 이러한 과정을 처리하고 그 프로그래밍 언어를 통해 적절한 데이터 타입으로 변환하게 된다.

문자열

모든 문자열은 UTF-8 형식이어야 한다. 이전 방식의 인코딩이 여전히 사용되는 상황도 많이 있기 때문에
MongoDB로 변환하기 전에 미리 UTF-8로 변환을 하든지, 그것이 여의치 않을 경우 텍스트를 BSON 바이너리 타입으로 저장하면 된다.

숫자

BSON은 double,int,long의 세 가지 수 타입을 규정한다. 이것은 BSON이 IEEE 실수와 signed 정수를 8바이트까지 인코딩할 수 있다는 것을 뜻한다.

BSON 수 타입에서 발생하는 한 가지 다른 문제점은 decimal을 지원하지 않는다는 점이다.
MongoDB 에서 통화(currency) 값을 저장하려고 하다면 정수 타입을 사용해서 센트를 표현해야한다.

날짜와 시간

BSON datetime 타입은 시간이나 날짜에 관련된 값을 저장하는 데 사용된다.
시간은 signed 64비트 정수를 사용해서 유닉스 에폭 이후 지나간 밀리초로 표현한다.

가상 타입

BSON의 데이터 타입만으로는 충분하지 않는 경우가 있다. BSON 타입을 임의로 만드는 것은 불가능하지만,
여러 가지의 기본적인 BSON 타입을 사용해서 가상의 타입을 만들어 낼 수는 있다.

도큐먼트 크기에 대한 제약

MongoDB v2.0과 그 이후 버전에서 도큐먼트의 최대크기는 16MB다.
이 제한은 서로 관련되어 있는 두 가지 이유 때문이다.
첫 번째로는 개발자가 효율적이지 못한 데이터 모델을 생성하는 것을 막기 위해서다.
두 번째는 성능 이슈가 발생할 수 있기 때문이다.

profile
Back-end Developer

0개의 댓글