서브 도큐먼트 검색 후 값 변경 시키기

다음과 같이 서브 스키마가 정의되어 있는 부모 스키마가 있다고 가정해보자.

A = {
  B = [
    new Schema({
      content: String
    })
  ]
}

다음과 같은 query로 B의 unique id를 가진 부모 도큐먼트를 검색할 수 있다.

A.findOneAndUpdate({"B._id": sub_uid})

만약, 찾은 후에 서브 도큐먼트의 값을 변경시키고 싶다면 어떻게 해야할까? 방법이야 많겠지만 내가 찾은 방법은 아래의 방법이다.

A.findOneAndUpdate({"B._id": sub_uid}, {$set: {"B.$.content": content}})

정리

다음의 .$. 을 이용하여 $set 옵션 사용시 해당 필드의 값을 변경할 수 있다. 서브 도큐먼트 없이 부모 도큐먼트 하나로 값을 관리할 수 있다면 좋겠지만, 가끔씩 서브 도큐먼트를 사용해야 할 경우가 생기면 위와 같은 방법을 사용해면 좋겠다.

'IT > Mongodb' 카테고리의 다른 글

도큐먼트 필드 삭제하기  (0) 2017.12.30
$elemMatch vs $in  (0) 2017.10.31
ISO Date 값이 한국 시간과 다를때  (1) 2017.10.31
스키마 모델링 어떻게 해야 하나  (0) 2017.10.21

도큐먼트들로부터 특정 필드 삭제하기

만약 user이라는 모델에 특정 필드 A를 삭제하고 싶다면 mongodb 에 다음과 같은 명령어를 사용할 수 있다.

db.users.update({}, {$unset: {A:1}})

위의 명령어가 의미하는 것은 그냥 아무거나 하나의 도큐먼트의 A 필드를 삭제하겠다는 것을 의미한다.

만약 다수의 도큐먼트를 처리하기 위해서는 다음과 같이 옵션을 추가하면 된다.

db.users.update({}, {$unset: {A:1}}, {multi: true})

정리

$unset 옵션은 특정 필드의 삭제를 의미한다.

반대로 $set은 특정 필드의 값을 정한다.

위의 원리를 이용하면 특정 필드의 값도 한꺼번에 set할 수 있다.

아래 예제는 모든 도큐먼트에 A 필드에 3이라는 값을 set 하는 예제이다.

db.users.update({}, {$set: {A:3}}, {multi: true})


'IT > Mongodb' 카테고리의 다른 글

서브 도큐먼트 검색 후 값 변경  (0) 2018.02.07
$elemMatch vs $in  (0) 2017.10.31
ISO Date 값이 한국 시간과 다를때  (1) 2017.10.31
스키마 모델링 어떻게 해야 하나  (0) 2017.10.21
var Group = new Schema({
  "group": [{
    tag: String,
    name: String
  }]
})

위와 같은 스키마가 있을 때 find를 통해 같은 tag 값을 갖는 리스트를 얻고 싶을 때 다음과 같은 query를 작성할 수 있다.

Group.find({group: {$elemMatch : {tag: tag}}})

$elemMatch를 통해 group 배열 안에 있는 {tag: tag}에 해당하는 도큐먼트가 있다면 데이터를 찾아내 리스트로 출력한다.


$in 이 좀 헛갈렸는데 $in은 해당 키 값에 해당하는 value 값들 중에서 일치하는 documents를 찾는다.

이게 뭔 차이냐먼.. 내 생각에는 $in을 사용해보니 계속 parse error 나는 것 보니까 {tag: tag} 를 value로써 인식하지 못하는 것 같다.

$elemMatch를 사용하지 않는 또 다른 방법이 있다.

Group.find({"group.tag": tag})

위 방식은 '.' 으로 path를 지정해서 찾는 경우이다.

mongodb에서 Date 타입을 사용하다보면 시간 기준이 한국 시간과 다르다는 것을 확인할 수 있다. 한국 시간보다 7시간이 더 늦음을 확인할 수 있다.

Date.UTC() 함수가 있는데 Date.UTC 함수는 현지 시간 대신 국제 표준시(UTC)를 사용한다. 그래서 위 함수를 이용해서 표준 시를 구할 수 있다.

function getCurrentDate() {
  var date = new Date();
 
  var year = date.getFullYear();
  var month = date.getMonth();
  var today = date.getDate();
  var hours = date.getHours();
  var minutes = date.getMinutes();
  var seconds = date.getSeconds();
  var milliseconds = date.getMilliseconds();
 
  return new Date(Date.UTC(year, month, today, hours, minutes, seconds, milliseconds));
}


'IT > Mongodb' 카테고리의 다른 글

서브 도큐먼트 검색 후 값 변경  (0) 2018.02.07
도큐먼트 필드 삭제하기  (0) 2017.12.30
$elemMatch vs $in  (0) 2017.10.31
스키마 모델링 어떻게 해야 하나  (0) 2017.10.21

스키마 모델링

일반 RDBMS는 중복을 최소화하기 위해 정규화를 한다. 하지만 Mongodb는 물리적인 특성상 객체를 내장할 경우 디스크에서 같은 곳에 위치하기 때문에 더 효율적이다.

예를 들어 같은 컬렉션내에 있는 도큐먼트에 접근하기 위해서는 내장되어 있을 때 같은 위치에 접근하기 때문에 효율적이지만, 내장되어있는 도큐먼트가 다른 컬렉션을 참조하는 경우에는 추가적인 비용이 필요하다.

스키마 디자인 할 때 일반적인 규칙이 있는데 고민하게 만든 사항을 살펴보겠다.

  1. 한 객체에 포함관계로 모델링된 객체들은 내장

  2. 다대다 관계는 보통 참조

  3. 성능 이슈가 생기면 무조건 내장한다.

여기서 내장한다는게 정확히 무슨 말은 한다는 걸까?

아래와 같이 서브 도큐먼트를 내장한다는 걸까?

collection = {
  doc : new Schema {
    name: String
  }
}

아래와 같이 다른 컬렉션에 대한 도큐먼트의 id를 내장한다는 걸까?

collection2 = {
  doc2 : [
    {type: Schema.ObjectId}
  ]
}

그리고 일반 임베디드 도큐먼트라는 타입이 있는데 이걸 말하는걸가?

collection3 = {
  doc3 : {
    name: String
  }
}

각 타입에 대한 설명은 다음 링크를 참조한다.

mongoose와 유용한 schema 기능

모델링 타입은 2가지가 있다.

  1. Normalized Data Models

    • 장점 : 다대다 관계, 복잡한 계층을 표현할 수 있다.

    • 단점 : 컬렉션에 중복된 데이터가 있어서 전체 읽기 성능은 떨어질 수 있다.

  2. Embedded Data Models

    • 장점 : 1대1, 1대다 관계를 표현할 수 있다.

    • 단점 : 내장 도큐먼트의 크기는 한계가 있다. 따라서 도큐먼트의 크기가 생성 이후 계속 커진다면 사용을 지양해야 한다.

각 타입에 대한 자세한 설명은 다음 링크를 참조한다.

데이터 모델링 타입

스키마 디자인 팁

우선 객체간에 관계를 생각해보자.

객체간에 관계에서 생각해보는 입장을 취해보면 다음과 같이 나뉘어진다.

  1. One-to-Few

  2. One-to-Many

  3. One-to-Squillions

  4. 양방향 참조

그리고 읽기 대 쓰기 효율을 고려하여 비정규화를 시킬 수 있다.

  1. Many-to-One 관계 비정규화

  2. One-to-Many 관계 비정규화

  3. One-to-Squillions 관계 비정규화

하나씩 살표보자

One-to-Few

{
  name:"John",
  address: [
    { p1: 'village', p2: 'village', p3: 'village'}
    { p1: 'pocket', p2: 'pocket2', p3: 'pocket3'}
  ]
}

하나 당 적은 관계의 수는 내포하여 한 번의 쿼리에 모든 정보를 보낼 수 있다. 하지만 내포된 데이터만 독자적으로 불러올 수 없다.

One-to-Many

//comments
{
  _id: ObjectId('AAA'),
  name: "John"
}
 
//boards
{
  _id: ObjectId('BOA');
  comments: [
    ObjectId('AAA'),
    ObjectId('BBB')
  ]
}

각각의 문서를 독자적으로 다룰 수 있지만 여러번 호출해야 하는 단점이 있다. 이 경우 DB 레벨이 아닌 어플리케이션 레벨의 join으로 두 문서를 연결해야 한다.

  boards = db.boards.findOne({_id: ObjectId('BOA')})
 
  comments_array = db.comments.find({_id: {$id: boards.comments}}).toArray();

One-to-Squillions

//host
{
  _id: ObjectId('AAA'),
  ipAddr: '111.111.111.111'
}
 
//logs
{
  time: ISODate()
  host: ObjectId('AAA')
}

엄청나게 큰 많은 데이터가 필요한 경우 단일 문서의 크기가 한정되어 있기 때문에 부모 참조 방식을 활용해야 한다. 조인 방식은 다음과 같다.

host = db.hosts.findOne({_id: ObjectId('AAA')})
 
last_logs = db.logs.find({host: host._id}).sort({time:-1}).limit(5000);

양방향 참조

//comments
{
  parent: ObjectId('BOA')
  _id: ObjectId('AAA'),
  name: "John"
}
 
//boards
{
  _id: ObjectId('BOA');
  comments: [
    ObjectId('AAA'),
    ObjectId('BBB')
  ]
}

양방향 참조는 One-to-Many에서 서로 참조하는 경우이다. 각 컬렉션에서 다른 컬렉션에 쉽게 접근할 수 있지만 문서를 삭제하는데 있어 쿼리를 두 번 보내야 한다.

그 외에 비정규화의 관계를 사용하게 되면(중복을 허용) 쓰기 성능보다 읽기 성능이 높아진다.

다음 링크에서 관련된 자세한 내용을 확인할 수 있다.

스키마 디자인 6가지 전략

성능을 높이기 위해 Index를 사용할 때 주의할 점.

Mongodb의 물리적 데이터 저장 구조를 살펴 보면 아래와 같다.


먼저 살펴보자면, 데이터 저장 시 논리적 메모리 공간에 write하고 일정 주기에 따라서 disk로 flsuh를 하는 Write Back 방식을 사용한다.(이 때문에 메모리에만 적재하면 되는 특성으로 인해 Write 속도가 빠르다는 말이 나오는 것 같다.)

데이터의 Read시에도 파일의 index를 메모리에 로딩해놓고 그 후 검색을 한다. 이 점에서 메모리에 저장되는 내용을 실제 데이터 블럭과 index 자체가 저장된다. 이 때문에 index를 남용하면 안된다. index를 생성하거나 업데이트할 때는 자원이 들어가며 index가 메모리에 상주하고 있어야 제대로 된 성능을 낸다.

메모리에 공간이 부족하면 page fault가 일어나게 되고 메모리에서 disk로부터 해당 데이터 블록을 요청하여 page와 disk 사이에 스위칭이 일어나게 되고 disk i/o 가 발생하여 성능을 떨어뜨리게 된다.

Index 선택에도 다음과 같은 규칙이 있다.

  1. 찾고자 하는 키에 대한 필드는 인덱스 한다.

  2. 보통 정렬하는 필드에 인덱스 한다.

인덱스를 사용하게 되면 쓰기의 성능은 떨어지기에 쓰기보다 읽기 비율이 높은 컬렉션에 인덱스를 사용하면 좋다.(위에서 말한것처럼 인덱스를 과용 사용하게 되면 메모리에 과잉문제가 생길 수 있다.)

결론

답은 없는 것 같다. 우선 구현 해보고 쿼리를 이용해 성능 테스트 해보면서 서비스 특성에 맞게 조정해봐야 할 것 같다. 여기에 많은 시간을 사용하였지만 아직 잘 모르겠다. 다음에 알아봐야 할 점으로는 다음과 같다.

  • Mongodb는 많은 수의 collection을 제공한다. collection의 수가 성능에 미치는 영향이 있는가?(하나의 컬렉션에다가 관리하는 것보다 각 컬렉션에서 쿼리를 사용하는게 더 효율적인 것 같지만.. 컬렉션이 많다고 좋은 점만 있는 것 같지는 않다.)

  • 네트워크 호출 비용 vs 몽고디비 쿼리 조인 비용(이건 네트워크 호출보다 쿼리로 조인하는게 좋다고 생각하지만 알아볼 가치는 있는 것 같다.)

참조링크 :

Mongodb의 물리 데이터 저장 구조

스키마 디자인 6가지 전략

스키마 디자인

mongoose 스키마와 유용한 기능

데이터 모델링 타입

'IT > Mongodb' 카테고리의 다른 글

서브 도큐먼트 검색 후 값 변경  (0) 2018.02.07
도큐먼트 필드 삭제하기  (0) 2017.12.30
$elemMatch vs $in  (0) 2017.10.31
ISO Date 값이 한국 시간과 다를때  (1) 2017.10.31

+ Recent posts