NoSQL MongoDB 스키마 디자인의 함정

2016.06.06 17:00

졸리운_곰 조회 수:1273

MongoDB 스키마 디자인의 함정

 

역주: http://d.hatena.ne.jp/hiroppon/20130326/1364265864 의 글을 번역.
위의 글은 다음 글을 일본어로 번역하면서 코멘트를 추가한 것임
http://blog.serverdensity.com/mongodb-schema-design-pitfalls/ 

MongoDB를 간단하게 시작할 수 있는 이유 중 하나는 “스키마 디자인을 생각하지 않아도 된다"는 것이다. 단순히 데이터를 넣고 나중에 쿼리하면 된다. 따라서 초기에 개발을 시작할 때 편리하며 나중에 문서의 구조를 변경할 때에도 이점이 된다. 그러나…

Schemaless가 스키마 설계를 하지 않아도 된다는 의미는 아니다!

따라서 다른 데이터베이스들처럼 성능 향상 및 확장 가능하게 하기 위해서는 역시 스키마 디자인을 고려해야 한다.
이러한 점은 다음의 기사에서도 언급되고 있다.

본 글에서는 MongoDB의 스키마 설계의 함정에 대해 다룬다.

문서를 키우지 않는다

기존 문서에 새로운 필드를 추가하거나 크기를 크게 하거나 (필드 이름 + 값) 하면 원래의 영역에 맞지 않게 되어 문서를 데이터 파일의 빈 영역으로 이동해야 한다. 이 작업은 데이터가 다시 쓰이게 되므로 성능에 영향을 준다. 또한 이러한 일이 대량으로 발생하면 MongoDB는 문서에 대한 여백 비율 (padding factor)을 조정한다. 따라서 문서는 기본적으로 더 많은 공간을 소비하게 된다. 그러나 원 영역 내에서의 (in-place) 업데이트는 빠르다.

프로파일러를 사용해서 문서가 이동된 것을 감지 할 수 있다. 만약 moved 필드가 true인 경우 문서는 재작성 (이동) 된 것이다. 이것을 시정하면 성능 개선의 여지가 있다. (아래)

MongoDB 문서를 디스크에 저장할 때 어느 정도의 패딩을 한다. 따라서 추후 문서를 업데이트할 때 약간의 크기 증가가 있었다고 해도 in-place update가 가능하다. 패딩 양은 문서 크기에 대한 비율로, 예를 들면 1.001과 같이 설정되어 있다.

위와 같이 업데이트에 따른 문서 이동이 많으면 이 값이 커진다 (1.0 ~ 2.0, default : 1.0 = nopad). 만약 이 값이 2.0이라면 문서를 저장할 때는 그 2배의 공간이 필요하게 된다. 당연히 이렇게 되면 디스크나 메모리의 소비가 효율적이지 않고 성능이 떨어진다.

이전에는 이 값은 커지기만 하고 완전히 제어할 수 없었지만, 2.2에서 compact나 각종 tool 등으로 지정할 수 있도록 했다. 또한 2.4 소스에서 업데이트시 축소 방향으로도 조정이 들어가 있는 것으로 보인다. (별로 신경 쓰지 않아도 되게 바뀌었는지도 모른다 → 확인 필요)

프로필의 예

// 이 DB의 프로필 (레벨 2)를 활성화
PRIMARY> db.setProfilingLevel (2);

// 필드 추가
PRIMARY> db.testcol.update ({_id : ObjectId ( "514ac4666bff1b5721ca1bc1")}, {$ set {value3 : "a"}})

// 프로파일 결과는 system.profile에 저장되어 있다

PRIMARY> db.system.profile.find ({op : 'update'})
{
     "op": "update" 
     "ns": "testdb.testcol"
     "query": { "_id": ObjectId ( "514ac4666bff1b5721ca1bc1")}
     "updateobj": { "$ set": { "value3": "a"}}
     "idhack": true, 
     "moved": true,
     "nmoved": 1,
     "nupdated": 1,
     "keyUpdates": 0,
     "numYield": 0,
     "lockStats": { "timeLockedMicros": { "r": NumberLong (0) "w": NumberLong (394)} "timeAcquiringMicros": { "r": NumberLong (0) "w": NumberLong (8 )}}
     "millis": 0,
     "ts": ISODate ( "2013-03-22T04 : 30:35.504 Z")
     "client": "192.168.159.142", "allUsers": [{ "user": "crumb", "userSource": "admin"}], "user": "crumb @ admin" 
}

paddingFactor의 증감 확인

매번 update마다 평가되어 적당한 값으로 조정되는 모양.

또한 compact로 지정할 수 있는 paddingFactor는 compact를 위한 일시적인 값이며, 위의 paddingFactor에 관계없이 padding한다. 또한 범위도 1.0 ~ 4.0이다. 머리가 좋아진 반면 예측이 어려워진 것일까?

필드 연산자를 사용

전체 문서를 업데이트하지 않고 필드 지정을 사용하여 필요한 필드만 업데이트할 수 있다. 새 문서 전체를 전송하는 대신 업데이트할 필드에 setremove 연산자를 지정할 수있다. 또한 increment 와 같이 특정 작업을 수행하는 연산자도 몇 가지 있다. 이들은 데이터베이스 통신뿐만 아니라 데이터 파일 처리면에서도 매우 효율적이다.

BSON 데이터 형식에 유의

문서는 필드의 데이터 형식을 변경하기만 해도 이동될 수 있다. 데이터를 저장할 때 그 형식도 고려할 필요가 있다. 예를 들면 (float) 0.0 (int) 0으로 한 경우 BSON data type이 달라서 문서의 이동을 일으키는 경우가 있다.

문서를 사전 할당(preallocate)한다

나중에 문서의 필드가 추가될 것을 알고 있다면 임시 값으로 미리 할당해 두었다가 나중에 $set 연산자로 실제 값으로 업데이트하여 문서의 크기가 증가하는 것을 막을 수 있다. 이때 위와 같이 데이터 형식을 명심해야 한다. 특히 null은 다른 데이터 형식이다!

그러나 사전 할당은 랜덤하게 일으켜야 한다. 갑자기 대량의 문서 insert가 발생하면 시스템에 부담이 될 것이다. 한번에 모든 작업이 일어나게 하지 말고, 미리 적절한 시간대에 작업해두는 것이 좋을 것이다.

여기서는 한 문서의 필드를 미리 할당할 뿐만 아니라 앞으로 사용할 예정인 레코드도 어느 정도 사전 배분 해두라고 하는 것 같다. 확실히 피크 시간대에는 "쓰기 작업이 한계다"라거나 "데이터 파일 추가 (2GB)"와 같은 갑작스런 I/O가 문제다 라고는 생각할 수 있지만, 솔직히 별로 어려운 점은 없었다.

단지 (나의 환경에서도) 새 insert보다 (in-place) update 쪽이 (모든 필드를 업데이트했다고 해도) 배 가까이 빠르기 때문에 곤란한 사람은 해볼만한 가치가 있을 것 같다.

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

필드 이름도 공간을 차지한다

수백만개 정도의 레코드 수에서는 별로 중요하지 않다. 그러나 수십억의 레코드를 처리할 경우 인덱스 크기에도 꽤나 영향을 끼친다. 디스크 용량은 상관없다 해도 메모리는 다르다. 데이터가 가능한 메모리에 올라와 있기를 바랄 것이다.

_id를 활용할 것

모든 컬렉션은 _id 인덱스를 가지고 있다. 따라서 이것을 어플리케이션 용도의 고유 인덱스로 활용할 수 있다. 예를 들면 (자사의) Server Density 용 서버 모니터링 정보로 날짜, 계정 ID, 서버 ID와 같은 구조를 가진 컬렉션의 경우, 문서를 확인하기 위해 이러한 필드 복합 인덱스 (날짜, 계정 ID, 서버 ID) 대신 _id를 사용하여 쿼리 할 수​​ 있다.

당연하다면 당연한 것이지만 애플리케이션 차원에서 _id를 적극적으로 쓰세요- 라는 것이다. 귀가 아플 정도

covered index를 사용하고 있는가?

만약 쿼리를​​ 실행할 때 사용할 인덱스에 모든 반환 필드가 포함 된 경우 쿼리는 인덱스 참조로 끝나며 MongoDB는 데이터 파일을 읽을 필요가 없다. 따라서 성능을 최대한 높이기 위해 모든 데이터를 메모리에 올릴 필요성이 줄어든다. (역주: 인덱스만 메모리에 올려도 된다는 말) 이것을 covered query라고 한다. covered query를 사용하면 explain 결과가 indexOnly = true 로 나온다.

일반적인 RDBMS 에도 비슷한 기능이 있기 때문에 MongoDB가 특별한 것은 아니고 오히려 일반적인 방법. 적극적으로 사용할 것.

컬렉션이나 데이터베이스를 최대한 잘 사용한다

여러 컬렉션 및 데이터베이스에 데이터를 분할하는 것을 고려한다.

컬렉션의 drop은 모든 문서를 remove하는 것보다 훨씬 빠르다. 이것은 데이터 보존 기간을 다룰 때 도움이 된다. 예를 들어 일 단위로 컬렉션을 나누어 대량의 컬렉션을 다루더라도 일반적인 처리 방법과 크게 다르지 않다. 단지 네임스페이스의 제한 등 몇 가지 주의가 필요한 항목이다.

데이터베이스 수준 잠금이 있으므로, 데이터베이스를 분리하여 부하 집중을 피할 수 있다. 예를 들어 인증 DB에서 처리량이 높은 로그 DB를 분리할 수 있다.

전체적으로 약간 알기 어려워서 대강 설명

일마다 컬렉션을 나누어두면 보관 기간이 만료된 문서의 삭제 처리는 컬렉션 drop으로 끝이므로 빨라진다. 컬렉션이 늘어나고 복잡해지는 것은 별거 아니니까 앱으로 처리하라고..

네임스페이스의 제한

- nssize 옵션에서 네임스페이스 의 데이터 파일 크기를 지정할 수 있다. 이 크기는 데이터베이스에 저장할 수있는 컬렉션 수와 직결된다. 보통 초기 값 16M으로 충분하지만 (나는 4M에서 사용함) 수만의 컬렉션을 만들고 싶다면 튜닝이 필요

데이터베이스 수준 잠금

본문의 인증 DB의 예는 이해가 어렵다. 인증 DB (admin)가 잠기면 모든 쿼리가 멈추잖아! 라고 말하고 싶은가?

MongoDB 2.2부터 전역 잠금 (mongod 전체 잠금)에서 데이터베이스 수준 잠금 (특정 DB의 처리를 잠금)로 변경되었다.
알기 쉬운 예로, compact 명령이나 repairDatabase 등의 명령으로 디스크 공간의 청소를 하려면 게시된 데이터베이스는 청소가 끝날 때까지 잠기지만 다른 데이터베이스 작업은 잠금의 영향을 받지 않는다.
그 밖에도 데이터 파일이 추가될 때의 잠금 등 여러가지 고려해야 하는 것이 잠금 타이밍이다.

어? 그런데 compact 명령이 어느새 DB를 잠그지 않는 모양.

모든 것을 테스트하라

많은 사람들이 MongoDB의 확장성을 오해하고 있다. 그 자체가 간단한 것은 아니다. 잘 생각하고 이해하고 테스트하는 수밖에 없다. 프로파일러 및 explan을 활용하여 생각대로 동작하는지 시험하라. 그리고 라이브 코드에서도 일정 기간 동안은 벤치마크를 해볼 것. 이 글에서 다루지 못한 훌륭한 예제를 다룬 링크가 있다.

 

[출처] http://wonnyz.tumblr.com/post/50724564555/mongodb-%EC%8A%A4%ED%82%A4%EB%A7%88-%EB%94%94%EC%9E%90%EC%9D%B8%EC%9D%98-%ED%95%A8%EC%A0%95

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
공지 오라클 기본 샘플 데이터베이스 졸리운_곰 2014.01.02 86310
공지 [SQL컨셉] 서적 "SQL컨셉"의 샘플 데이타 베이스 SAMPLE DATABASE of ORACLE 가을의 곰을... 2013.02.10 78764
공지 [G_SQL] Sample Database 가을의 곰을... 2012.05.20 95512
13 [데이터 수집 및 전처리] 주식 전종목 어떻게 불러올까? 거래소 종목 불러오기 file 졸리운_곰 2023.12.09 1701
12 [데이터 수집 및 전처리] [Python/파이썬]네이버증권API 활용 - 회사명, 종목코드 받아오기 file 졸리운_곰 2023.12.08 1509
11 [데이터 수집 및 전처리] 네이버 금융(차트)에서 주가 갈무리(크롤링)하기 file 졸리운_곰 2023.12.08 1495
10 [데이터 수집 및 전처리] 네이버 증권에서 일봉, 주봉 데이터 가져오기 file 졸리운_곰 2023.12.08 1500
9 [데이터 수집 및 전처리] (놀라운) 한글 데이터 짱! AwesomeKorean_Data file 졸리운_곰 2023.03.07 1198
8 [데이터 수집 및 전처리] Crawling, Scraping file 졸리운_곰 2022.05.21 1476
7 [데이터분석][데이터수집 전처리] MS 엑셀(Excel)에서 UTF-8 로 된 csv 파일 가져오기 file 졸리운_곰 2021.09.30 1415
6 카프카 설치 시 가장 중요한 설정 4가지 졸리운_곰 2021.07.13 1928
5 Prometheus Query(PromQL) 기본 이해하기 file 졸리운_곰 2020.12.17 1414
4 [인프라 모니터링 오픈소스] Prometheus 를 알아보자 file 졸리운_곰 2020.12.17 1907
3 Prometheus + Grafana 대시보드 file 졸리운_곰 2020.12.17 2489
2 Grafana란? file 졸리운_곰 2020.12.17 2159
1 Importing wikipedia dump to MySql 졸리운_곰 2020.10.04 2494
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED