MongoDB 스키마 디자인을 위한 6가지 규칙 요약

MongoDB에서의 관계 구성, 비정규화 전략을 6가지 규칙으로 정리.

MongoDB를 개인 프로젝트에서 자주 사용하긴 하는데 항상 쓰던 방식대로만 사용하고 있어서 스키마를 제대로 구성하고 있는지 검색하다가 이 글을 찾게 되었다. MongoDB 블로그에 올라온 포스트인 6 Rules of Thumb for MongoDB Schema Design을 읽고 나서 SQL과 어떻게 다른 전략을 갖고 스키마를 구성해야 하는지 생각하는데 도움이 많이 되었다. 원글은 세 부분으로 나눠 게시되어 있어서 주제를 더 상세하게 다루고 있으므로 이 요약이 불충분하다면 해당 포스트를 확인하자.


SQL에 경험이 있지만 MongoDB가 처음이라면, MongoDB에서 일대다(One-to-N, 왜 N인지는 보면 안다.) 관계를 어떻게 작성할지 자연스레 궁금증을 갖게 된다. 이 글의 주제는 객체 간의 관계를 다루는 방법에 대한 이야기다.

기초

다음 세 가지 방법으로 관계를 작성할 수 있다.

  • One to Few 하나 당 적은 수
  • One to Many 하나 당 여럿
  • One to Squillions 하나 당 무지 많은 수

각각 방법은 장단점을 갖고 있어서 상황에 맞는 방법을 활용해야 하는데 One-to-N에서 N이 어느 정도 규모/농도 되는지 잘 판단해야 한다.

One-to-Few

// person
{
  name: "Edward Kim",
  hometown: "Jeju",
  addresses: [
    { street: 'Samdoil-Dong', city: 'Jeju', cc: 'KOR' },
    { street: 'Albert Rd', city: 'South Melbourne', cc: 'AUS' }
  ]
}

하나 당 적은 수의 관계가 필요하다면 위 같은 방법을 쓸 수 있다. 쿼리 한 번에 모든 정보를 갖을 수 있다는 장점이 있지만, 내포된 엔티티만 독자적으로 불러올 수 없다는 단점도 있다.

One-to-Many

// 편의상 ObjectID는 2-byte로 작성, 실제는 12-byte
// parts
{
  _id: ObjectID('AAAA'),
  partno: '123-aff-456',
  name: 'Awesometel 100Ghz CPU',
  qty: 102,
  cost: 1.21,
  price: 3.99
}

// products
{
  name: 'Weird Computer WC-3020',
  manufacturer: 'Haruair Eng.',
  catalog_number: 1234,
  parts: [
    ObjectID('AAAA'),
    ObjectID('DEFO'),
    ObjectID('EJFW')
  ]
}

부모가 되는 문서에 배열로 자식 문서의 ObjectID를 저장하는 방식으로 구현한다. 이 경우에는 DB 레벨이 아닌 애플리케이션 레벨 join으로 두 문서를 연결해 사용해야 한다.

// category_number를 기준으로 product를 찾음
> product = db.products.findOne({catalog_number: 1234});
// product의 parts 배열에 담긴 모든 parts를 찾음
> product_parts = db.parts.find({_id: { $in : product.parts } } ).toArray() ;

각각의 문서를 독자적으로 다룰 수 있어 쉽게 추가, 갱신 및 삭제가 가능한 장점이 있지만 여러번 호출해야 하는 단점이 있다. join이 애플리케이션 레벨에서 처리되기 때문에 N-to-N도 쉽게 구현할 수 있다.

One-to-Squillions

이벤트 로그와 같이 엄청나게 많은 데이터가 필요한 경우, 단일 문서의 크기는 16MB를 넘지 못하는 제한이 있어서 앞서와 같은 방식으로 접근할 수 없다. 그래서 부모 참조(parent-referencing) 방식을 활용해야 한다.

// host
{
  _id : ObjectID('AAAB'),
  name : 'goofy.example.com',
  ipaddr : '127.66.66.66'
}
// logmsg
{
  time : ISODate("2015-09-02T09:10:09.032Z"),
  message : 'cpu is on fire!',
  host: ObjectID('AAAB')       // Host 문서를 참조
}

다음과 같이 Join한다.

// 부모 host 문서를 검색
> host = db.hosts.findOne({ipaddr : '127.66.66.66'});  // 유일한 index로 가정
// 최근 5000개의 로그를 부모 host의 ObjectID를 이용해 검색
> last_5k_msg = db.logmsg.find({host: host._id}).sort({time : -1}).limit(5000).toArray()

숙련

앞서 살펴본 기초 방법과 함께, 양방향 참조와 비정규화를 활용해 더 세련된 스키마 디자인을 만들 수 있다.

양방향 참조 Two-Way Referencing

// person
{
  _id: ObjectID("AAF1"),
  name: "Koala",
  tasks [ // task 문서 참조
    ObjectID("ADF9"), 
    ObjectID("AE02"),
    ObjectID("AE73") 
  ]
}

// tasks
{
  _id: ObjectID("ADF9"), 
  description: "Practice Jiu-jitsu",
  due_date:  ISODate("2015-10-01"),
  owner: ObjectID("AAF1") // person 문서 참조
}

One to Many 관계에서 반대 문서를 찾을 수 있게 양쪽에 참조를 넣었다. Person에서도 task에서도 쉽게 다른 문서를 찾을 수 있는 장점이 있지만 문서를 삭제하는데 있어서는 쿼리를 두 번 보내야 하는 단점이 있다. 이 스키마 디자인에서는 단일로 atomic한 업데이트를 할 수 없다는 뜻이다. atomic 업데이트를 보장해야 한다면 이 패턴은 적합하지 않다.

Many-to-One 관계 비정규화

앞서 Many-to-One에서 필수적으로 2번 이상 쿼리를 해야 하는 형태를 벗어나기 위해, 다음과 같이 비정규화를 할 수 있다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

// products - before
{
  name: 'Weird Computer WC-3020',
  manufacturer: 'Haruair Eng.',
  catalog_number: 1234,
  parts: [
    ObjectID('AAAA'),
    ObjectID('DEFO'),
    ObjectID('EJFW')
  ]
}

// products - after
{
  name: 'Weird Computer WC-3020',
  manufacturer: 'Haruair Eng.',
  catalog_number: 1234,
  parts: [
    { id: ObjectID('AAAA'), name: 'Awesometel 100Ghz CPU' }, // 부품 이름 비정규화
    { id: ObjectID('DEFO'), name: 'AwesomeSize 100TB SSD' },
    { id: ObjectID('EJFW'), name: 'Magical Mouse' }
  ]
}

애플리케이션 레벨에서 다음과 같이 사용할 수 있다.

// product 문서 찾기
> product = db.products.findOne({catalog_number: 1234});  
// ObjectID() 배열에서 map() 함수를 활용해 part id 배열을 만듬
> part_ids = product.parts.map( function(doc) { return doc.id } );
// 이 product에 연결된 모든 part를 불러옴
> product_parts = db.parts.find({_id: { $in : part_ids } } ).toArray();

비정규화로 매번 데이터를 불러오는 비용을 줄이는 장점이 있다. 하지만 part의 name을 갱신할 때는 모든 product의 문서에 포함된 이름도 변경해야 하는 단점이 있다. 그래서 비정규화는 업데이트가 적고, 읽는 비율이 높을 때 유리하다. 업데이트가 잦은 데이터에는 부적합하다.

One-to-Many 관계 비정규화

// parts - before
{
  _id: ObjectID('AAAA'),
  partno: '123-aff-456',
  name: 'Awesometel 100Ghz CPU',
  qty: 102,
  cost: 1.21,
  price: 3.99
}

// parts - after
{
  _id: ObjectID('AAAA'),
  partno: '123-aff-456',
  name: 'Awesometel 100Ghz CPU',
  product_name: 'Weird Computer WC-3020', // 상품 문서 비정규화
  product_catalog_number: 1234,           // 얘도 비정규화
  qty: 102,
  cost: 1.21,
  price: 3.99
}

앞과 반대로 비정규화를 하는 방법인데 이름 변경 시 Many-to-One에 비해 수정해야 하는 범위가 더 넓은 단점이 있다. 앞에서 처리한 비정규식과 같이 업데이트/읽기 비율을 고려해서 이 방식이 적절한 패턴일 때 도입해야 한다.

One-to-Squillions 관계 비정규화

Squillions를 비정규화한 결과는 다음과 같다.

// logmsg - before
{
  time : ISODate("2015-09-02T09:10:09.032Z"),
  message : 'cpu is on fire!',
  host: ObjectID('AAAB')
}

// logmsg - after
{
  time : ISODate("2015-09-02T09:10:09.032Z"),
  message : 'cpu is on fire!',
  host: ObjectID('AAAB'),
  ipaddr : '127.66.66.66'
}

> last_5k_msg = db.logmsg.find({ipaddr : '127.66.66.66'}).sort({time : -1}).limit(5000).toArray()

사실, 이 경우에는 둘을 합쳐도 된다.

{
    time : ISODate("2015-09-02T09:10:09.032Z"),
    message : 'cpu is on fire!',
    ipaddr : '127.66.66.66',
    hostname : 'goofy.example.com'
}

코드에서는 이렇게 된다.

// 모니터링 시스템에서 로그 메시지를 받음.
logmsg = get_log_msg();
log_message_here = logmsg.msg;
log_ip = logmsg.ipaddr;

// 현재 타임 스탬프를 얻음
now = new Date();
// 업데이트를 위한 host의 _id를 찾음
host_doc = db.hosts.findOne({ ipaddr: log_ip },{ _id: 1 });  // 전체 문서를 반환하지 말 것
host_id = host_doc._id;

// 로그 메시지, 부모 참조, many의 비정규화된 데이터를 넣음
db.logmsg.save({time : now, message : log_message_here, ipaddr : log_ip, host : host_id ) });
// `one`에서 비정규화된 데이터를 push함
db.hosts.update( {_id: host_id }, {
    $push : {
      logmsgs : {
        $each:  [ { time : now, message : log_message_here } ],
        $sort:  { time : 1 },  // 시간 순 정렬
        $slice: -1000          // 마지막 1000개만 뽑기
      }
    }
  });

정리

6가지 원칙

장미빛 MongoDB를 위한 6가지 원칙은 다음과 같다.

  1. 피할 수 없는 이유가 없다면 문서에 포함할 것.
  2. 객체에 직접 접근할 필요가 있다면 문서에 포함하지 않아야 함.
  3. 배열이 지나치게 커져서는 안됨. 데이터가 크다면 one-to-many로, 더 크다면 one-to-squillions로. 배열의 밀도가 높아진다면 문서에 포함하지 않아야 함.
  4. 애플리케이션 레벨 join을 두려워 말 것. index를 잘 지정했다면 관계 데이터베이스의 join과 비교해도 큰 차이가 없음.
  5. 비정규화는 읽기/쓰기 비율을 고려할 것. 읽기를 위해 join을 하는 비용이 각각의 분산된 데이터를 찾아 갱신하는 비용보다 비싸다면 비정규화를 고려해야 함.
  6. MongoDB에서 어떻게 데이터를 모델링 할 것인가는 각각의 애플리케이션 데이터 접근 패턴에 달려있음. 어떻게 읽어서 보여주고, 어떻게 데이터를 갱신한 것인가.

생산성과 유연성

이 모든 내용의 요점은 MongoDB가 데이터베이스 스키마를 작성할 때 애플리케이션에서 필요로 하는 모든 요구를 만족할 수 있도록 기능을 제공하고 있다는 점이다. 애플리케이션에 필요로 하는 데이터를 필요한 구조에 맞게 불러올 수 있어 쉽게 활용할 수 있다.

 

[출처] https://www.haruair.com/blog/3055

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
공지 오라클 기본 샘플 데이터베이스 졸리운_곰 2014.01.02 25084
공지 [SQL컨셉] 서적 "SQL컨셉"의 샘플 데이타 베이스 SAMPLE DATABASE of ORACLE 가을의 곰을... 2013.02.10 24563
공지 [G_SQL] Sample Database 가을의 곰을... 2012.05.20 25942
422 mongoDB 모델링해보기~ file 졸리운_곰 2018.04.15 78
» MongoDB 스키마 디자인을 위한 6가지 규칙 요약 졸리운_곰 2018.04.15 253
420 MongoDB Schema 디자인 하기 file 졸리운_곰 2018.04.15 88
419 TensorFlow Lite 101 - MoblieNet 맛보기 file 졸리운_곰 2018.04.07 125
418 MySQL OR MariaDB에서 프로시저(Procedure)를 만들어보자. 졸리운_곰 2018.03.25 264
417 R로 하는 텍스트마이닝(2016_08_15 대통령 광복절 기념사 분석) file 졸리운_곰 2018.03.17 190
416 한글 텍스트 마이닝 : Text Mining For Korean 졸리운_곰 2018.03.17 583
415 R plot으로 그래프 그리기: 점 여러 개 찍기 ๑•‿•๑ file 졸리운_곰 2018.02.21 463
414 데이터 분석 어디에 집중할 것인가? 가장 먼저 실험에 집중하라 file 졸리운_곰 2018.02.06 103
413 품질경영기사 자료와 정보 (응시자격, 출제기준, 시험과목, 시험방법, 합격기준, 합격률 등) file 졸리운_곰 2018.02.05 484
412 품질경영기사 독학 난이도 file 졸리운_곰 2018.02.05 980
411 품질경영기사_공식요약(필기, 실기)_요점 및 핵심정리 file 졸리운_곰 2018.02.05 2456
410 품질경영기사 요점정리 file 졸리운_곰 2018.02.04 404
409 [품질경영산업기사]직접 시험 본 후기 및 조언입니다 file 졸리운_곰 2018.02.03 333
408 <품질경영기사 독학하시는 분들을 위한 공부방법 TIP!> file 졸리운_곰 2018.02.03 811
407 [Oracle] 조횟수, 다운횟수 자동증가 하기 졸리운_곰 2018.01.22 109
406 오라클 SELECT결과로 UPDATE 하기 졸리운_곰 2018.01.22 377
405 DBMS별 기존테이블 SELECT해서 새 테이블에 INSERT하여 데이터 ... file 졸리운_곰 2018.01.22 136
404 오라클 Select 해서 Update 하기 졸리운_곰 2018.01.22 51
403 [SQL] select 한 결과로 update 처리, SQL한문장, How to UPDATE from SELECT in SQL Server 졸리운_곰 2018.01.22 243
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED