<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>꽁담</title>
    <link>https://mozi.tistory.com/</link>
    <description>'DBA 업무'와
'알게되는 정보'를 기록하는 공간</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 21:50:10 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>꽁담</managingEditor>
    <item>
      <title>[MongoDB] 백업 및 복구(1)</title>
      <link>https://mozi.tistory.com/666</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6J3j3/btsLd8ZkWac/dUcfnkUomqkSj566Z0BON1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6J3j3/btsLd8ZkWac/dUcfnkUomqkSj566Z0BON1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6J3j3/btsLd8ZkWac/dUcfnkUomqkSj566Z0BON1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6J3j3%2FbtsLd8ZkWac%2FdUcfnkUomqkSj566Z0BON1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;mongodump 와 mongorestore 를 이용한 논리 백업 및 복구&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;백업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodump 는 공식적으로 제공되는 유일한 백업 도구인데, 논리적인 수준의 백업을 실행하는 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 물리적인 파일을 복사하는게 아니라 서버에 로그인 후 도큐먼트를 한건씩 덤프해서 bson 파일로 저장하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 mongodump 는 백업 시간뿐 아니라 복구 시간도 상당히 오래 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodump 는 기본적으로 특정 시점의 스냅샷을 덤프하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, mongodump 도구가 데이터를 덤프하는 동안 변경되는 데이터에 대해서는 백업이 일관된 상태를 유지하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 mongodump 를 이용해 백업하는 경우 --oplog 옵션을 이용해서 실행해야만 덤프가 실행 중인 동안 변경되는 데이터의 OpLog 이벤트를 같이 백업할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시점의 일관된 상태를 백업하려면 --oplog 옵션을 사용해야 하지만, MongoDB 라우터를 통해서 mongodump 백업을 수행하는 경우 --oplog 옵션을 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 --oplog 옵션으로 스냅샷 백업을 실행할 때는 MongoDB 서버에 직접 로그인해서 백업을 수행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업이 실행되면 --out 파라미터에 지정한 디렉터리로 덤프한 BSON 도큐먼트를 기록하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덤프가 실행되는 동안 OpLog 이벤트를 덤프해서 &quot;oplog.bson&quot; 이라는 덤프 파일을 출력디렉터리에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업이 완료되면 각 데이터베이스의 컬렉션이 디렉터리 단위로 저장되고 OpLog 의 내용이 덤프돼서 oplog.bson 파일로 저장된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodump 를 이용해 백업된 데이터 파일을 복구하는 방법은 mongorestore 명령어를 사용하면 되며 매우 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--oplogReplay 옵션은 덤프된 데이터 파일을 모두 적재한 후에 백업 디렉터리의 oplog.bson 파일을 적재하도록 해주는 옵션인데, oplog.bson 파일은 --oplog 옵션을 사용해서 mongodump 를 실행한 경우에만 생성되는 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 만약 mongodump 로 백업하면서 --oplog 옵션을 주지 않았다면 --oplogReplay 옵션은 사용하면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongorestore 명령이 완료되면 백업된 시점의 데이터베이스 상태로 복구시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 mongodump 명령은 local 데이터베이스의 컬렉션은 별도로 백업하지 않기 때문에 백업을 수행했던 서버와 다른 상태일 수 있으나, 사용자 데이터를 저장하는 데이터베이스가 아니고 서버 시작에 아무 문제가 없으니 무시해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mongodump 로 ㅐㅂㄱ업된 데이터로부터 일부 데이터베이스나 컬렉션만 적재하고자 한다면 --nsInclude 나 --nsExclude 옵션을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 백업 디렉터리에서 특정 데이터베이스의 데이터를 다른 데이터베이스나 컬렉션으로 적재하고자 한다면 --nsFrom 과 --nsTo 옵션을 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongorestore 를 빠르게 실행하고자 한다면 --numParallelCollections 옵션을 활용하면 되고 기본값은 4로 4개의 쓰레드를 이용해서 데이터를 적재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;물리 백업 및 복구&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;셧다운 상태의 백업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 방법은 서비스에 투입되지 않은 세컨드리 멤버의 MongoDB 서버를 셧다운하고 데이터파일을 복사하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 셧다운 하는 경우 장애가 발생하면 새로운 프라이머리 선출을 하지 못할 수 있으므로 MongoDB 레플리카 셋의 고가용성을 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 만약 셧다운 한 후 백업을 수행하고자 한다면 백업 전용의 새로운 멤버를 추가해서 진행할 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복제 중지 상태의 백업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식적으로 복제를 멈추는 방법은 없기 때문에, db.fsyncLock 명령을 이용해 데이터 파일을 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료 후 db.fsyncUnLock 을 활용해 잠금을 해제해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsyncLock 명령을 이용하는 경우에도 복제가 동기화되지 않으므로 복제는 지연된 상태이며, 백업이 실행되는 동안 지연된 복제로 인해 새로운 프라이머리 멤버로 선출되지 못하거나 복제 동기화를 위해 매우 오랜 시간이 소요될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일시스템 스냅샷 백업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스의 LVM 과 같이&amp;nbsp; 파일 시스템 레벨에서 지원하는 스냅샷 기능을 이용해 물리 백업을 수행할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Percona 온라인 백업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Percona 에서는 위의 번거로움과 어려움을 해결하기 위해 온라인 백업 기능을 구현했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 사용하려면 Percona 에서 배포하는 Percona Server for MongoDB 배포판을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;물리 백업 복구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적으로 백업된 데이터 파일은 운영 중인 MongoDB 서버의 데이터 디렉터리 구조와 컬렉션의 데잍 ㅓ파일을 그대로 가지고 있기 때문에 복구가 매우 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복구하고자 하는 MongoDB 서버의 데이터 디렉터리에 백업된 데이터 파일을 그대로 복사하기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 샤딩된 MongoDB 클러스터에서 백업된 데이터 파일을 이용해서 복구하는 경우에는 백업된 데이터 파일을 사용해서 시작되는 MongoDB 서버가 아무런 응답도 없이 무한정 대기에 빠질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 백업을 실행했던 원본 MongoDB 서버가 소속된 클러스터의 구조를 확인하기 위해 컨피그 서버로 접속하려 하기 떄문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우recoverShardingState 옵션을 false 로 설정하면 기존 샤드 클러스터 컨피그 서버 정보를 무시하고 서버를 시작한다. ( 3.6 버전부터 옵션 삭제 됨 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PIT 복구 ( Point In Time )&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못 실행된 명령 직전까지의 데이터를 복구하는 것을 PIT 복구라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 PIT 복구는 물리 또는 논리 풀 백업의 최근의 OpLog 를 복구하는 방식으로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 절차는 백업 시점으로부터의 OpLog 를 가진 멤버가 있어야만 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 복구한 백업의 OpLog 시점을 확인하기 위해 서버에 로그인해서 local 데이터베이스의 oplog.rs 컬렉션의 가장 마지막 이벤트를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 가장 최근까지의 OpLog 를 가진 MongoDB 서버에서 OpLog 를 덤프한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 전체 OpLog 를 덤프하는 것이 아니라, 백업의 마지막 OpLog 이벤트부터 덤프하도록 한다. * 이렇게 안해도 복구에는 무관&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 덤프된 OpLog 에서 삭제한 이벤트의 시점을 찾아야 하는데 BSON 파일이어서 사람의 눈으로 확인이 불가능해 JSON 파일로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 으로 변환된 데이터에서 복구시점 ts 를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oplog 재생을 위해 mongorestroe 명령을 사용하는데, 이 때 백업 데이터 파일이 저장된 디렉터리에는 덤프 받은 oplog.bson 파일만 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않고 다른 데이터베이스의 컬렉션 덤프 파일이 있으면 이 데이터 파일들도 기존 컬렉션에 다시 적재되어서 데이터가 이중으로 적재될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 mongorestore 명령으로 OpLog 를 재실행할 때에는 --oplogReplay 옵션을 사용해야 하며, 재생을 멈춰야 할 OpLog 이벤트 지점을 --oplogLimit 옵션에 같이 지정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/666</guid>
      <comments>https://mozi.tistory.com/666#entry666comment</comments>
      <pubDate>Tue, 10 Dec 2024 18:29:16 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 보안(1)</title>
      <link>https://mozi.tistory.com/665</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQ7PKx/btsKXgDjLpQ/qi0KiokDWZWxsfZsQ5RlLK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQ7PKx/btsKXgDjLpQ/qi0KiokDWZWxsfZsQ5RlLK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQ7PKx/btsKXgDjLpQ/qi0KiokDWZWxsfZsQ5RlLK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQ7PKx%2FbtsKXgDjLpQ%2Fqi0KiokDWZWxsfZsQ5RlLK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 보안을 위해 크게 다음과 같이 5가지 형태의 코어 기능을 제공한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.8604%;&quot;&gt;형태&lt;/td&gt;
&lt;td style=&quot;width: 73.1396%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.8604%;&quot;&gt;인증 Authentication&lt;/td&gt;
&lt;td style=&quot;width: 73.1396%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.8604%;&quot;&gt;권한 Authorization&lt;/td&gt;
&lt;td style=&quot;width: 73.1396%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.8604%;&quot;&gt;암호화 Encryption&lt;/td&gt;
&lt;td style=&quot;width: 73.1396%;&quot;&gt;데이터 필드 단위의 암호화와 데이터 파일을 DBMS 서버 하단에서 처리해주는 TDE 로 나눠볼수 있는데, 일반적으로 DBMS 에서 지원하는 방식은 TDE 방식이 많이 사용됨&lt;br /&gt;&lt;br /&gt;현재 MongoDB 서버에서 TDE 는 엔터프라이즈 버전에서만 지원되는데, WiredTiger 스토리지 엔진의 플러그인 모듈로 직접 개발해 사용할 수 있음 ( 하단 설명 )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.8604%;&quot;&gt;감사 Auditing&lt;/td&gt;
&lt;td style=&quot;width: 73.1396%;&quot;&gt;어떤 사용자가 어떤 쿼리를 언제 실행했는지 모두 로그로 기록하여 DBMS 의 데이터에 문제가 발생했을 때 언제 누가 실행한 쿼리때문에 문제가 발생했는지 확인할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.8604%;&quot;&gt;데이터 관리 Data Governance&lt;/td&gt;
&lt;td style=&quot;width: 73.1396%;&quot;&gt;보안적인 관점보다는 데이터의 일관성을 유지하기 위해 사용하는 도큐먼트 체크 기능을 의미&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 자체 인증 방식과 외부 인증 방식 2개로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 인증은 LDAP 나 액티브 디렉터리를 이용해서 사용자 인증을 받는 방식을 의미로 외부 인증 방식은 별도의 솔류션이 필요하고 장애의 범위가 넓어져 자주 사용되지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 자체 인증 방식은 다시 '내부 인증'과 '사용자 인증'으로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내부 인증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩된 클러스터나 레플리카 셋에서 각 멤버는 데이터 동기화나 멤버들의 상태를 체크하기 위해 서로 통신이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 각 멤버가 서로 인증하는 방식을 내부 인증이라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 인증은 키 파일과 x.509 인증서 두 가지 방식을 선택해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증을 활성화하려면 서버의 설정 파일에서 인증과 관련된 옵션을 활성화해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;security.authorization 옵션을 enabled 로 설정하면 MongoDB 서버 간의 통신을 위핸 내부 인증뿐 아니라 사용자의 로그인을 위한 인증까지 모두 활성화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 결국 내부 통신을 위한 인증과 사용자 인증을 개별로 설정할 수 없다는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 enabled 로 설정하면 클러스터 멤버 간 통신을 위한 내부 인증과 관련된 clusterAuthMode 옵션과 keyFile 옵션이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clusterAuthMode 는 keyFile 과 x.509 둘로 나누어 볼 수 있는데 keyFile 은 평문의 단순 문자열로 구성된 비밀번호 파일을 MongoDB 서버가 내부 인증으로 사용하도록 하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x.509 는 CA 의 인증을 받는 인증서를 이용해 클러스터 멤버 간 통신을 인증하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyFile 을 생성할 때 주의해야 할 점으로 다음과 같이 4가지 사항이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- keyFile 은 MongoDB 서버 프로세스가 읽을 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- keyFile 의 접근 권한은 반드시 600 또는 700 으로 파일의 소유주만 접근할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- keyFile 의 내용에서 공백문자는 자동으로 무시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- keyFile 은 6개 이상 1024 개 이항의 문자로 구성돼야 하며 BASE-64 셋에 포함되는 문자만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 준비된 키 파일은 클러스터에 참여하는 모든 MongoDB 서버와 MongoDB 라우터 서버가 공유해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 모든 멤버의 서버에 복사해서 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 인증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버 자체적으로 지원하는 사용자 인증은 다른 DBMS 와 같이 아이디/패스워드 기반의 인증을 사용하는데 MongoDB 서버는 인증 데이터베이스 정보를 추가로 더 필요로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서는 사용자를 생성할 때 반드시 특정 데이터베이스로 이동해서 생성해야 하는데, 이 때 데이터베이스를 인증 데이터베이스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 하나의 사용자 계정이 여러 다른 데이터베이스에 대해 권한을 가질 수는 있지만, 인증 데이터베이스는 사용자 계정으로 로그인할 때 인증을 위한 데이터베이스이므로 하나만 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 사용자 인증을 사용하려면 MongoDB 설정 파일에 아래의 내용을 활성화해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;security:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; authorization: enabled&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서 사용자 계정을 생성할 때는 특정 데이터베이스로 이동하여 계정을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제에서 user 사용자 계정의 인증 데이터베이스는 mysns 데이터베이스가 되는것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1732601715361&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use mysns

db.createUser ({user:&quot;user&quot;, pwd:&quot;mypassword&quot;, roles:[ &quot;readWrite&quot; ]})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db.createUser 명령으로 사용자 계정을 생성하는데 roles 필드에는 user 계정이 mysns 데이터베이스에 대해 어떤 권한을 부여할 것인지 설정한다. MongoDB 서버의 사용자 계정은 인증 데이터가 달라지만 다른 계정으로 인식된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 2개의 사용자 계정의 이름과 비밀번호가 같다 하더라도 서로 다른 데이터베이스에 생성한다면 MongoDB 서버는 서로 다른 계정으로 인식한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 하나의 사용자 계정이 여러 데이터베이스에 대해 권한을 가지도록 하고자 한다면 다음과 같이 createUser 명령에 다른 데이터베이스에 대한 권한을 같이 설정하거나 db.grantRolesToUser 명령으로 권한을 추가해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생성된 사용자 계정의 비밀번호와 권한 정보는 인증 데이터베이스와 무관하게 admin 데이터베이스에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;admin 데이터베이스의 system.users 컬렉션에 저장된 도큐먼트에 db 필드가 명시돼 있는데 이는 사용자 계정의 인증 데이터베이스를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;credentials 필드에는 SCRAM-SHA-1 이 저장되는데 이는 해당 사용자 계정을 인증할 방식을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버가 자체적으로 지원하는 인증 방식을 사용하는 경우에는 다음 3가지 메커니즘을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SCRAM-SHA-1 ( 기본값 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MONGODB-CR&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- X.509 Certificate Authentication&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권한&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 RDBMS 서버의 권한은 각 실행 명령어 단위로 권한이 부여되는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 권한은 명령과 권한이 1:1 관계가 아니다. 사용자가 실행할 수 있는 명령과는 별개로 액션이 정의되고, 이런 액션을 묶어서 역할을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제 사용자가 사용하는 명령이 어떤 액션들로 구성되는지 알아야 명령을 위해 어떤 역할이 필요한지 식별할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;액션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서는 역할 기반의 권한 부여 방식을 사용하는데, 역할은 특정 리소스에 대해 액션을 미리 매핑해 둔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서는 다음과 같이 액션이 이미 정의되어 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.7442%;&quot;&gt;액션&lt;/td&gt;
&lt;td style=&quot;width: 68.2558%;&quot;&gt;MongoDB 명령&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.7442%;&quot;&gt;find&lt;/td&gt;
&lt;td style=&quot;width: 68.2558%;&quot;&gt;aggregate, count, dataSize, distinct, find, group , 등등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.7442%;&quot;&gt;insert&lt;/td&gt;
&lt;td style=&quot;width: 68.2558%;&quot;&gt;insert, create, clone , 등등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.7442%;&quot;&gt;remove&lt;/td&gt;
&lt;td style=&quot;width: 68.2558%;&quot;&gt;delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.7442%;&quot;&gt;bypassDocumentValidation&lt;/td&gt;
&lt;td style=&quot;width: 68.2558%;&quot;&gt;aggregate, applyOps, clone, insert, update 등등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 주의해야 할 사항이 액션이 서버 명령들의 묶음이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 MongoDB 명령은 여러 개의 액션을 필요로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 Aggregate 명령은 find, insert, bypassDocumentValidation 을 필요로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내장된 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 역할은 이렇게 정의된 액션을 묶어서 하나의 그룹으로 만든 것으로 볼 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.907%;&quot;&gt;역할&lt;/td&gt;
&lt;td style=&quot;width: 67.093%;&quot;&gt;액선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.907%;&quot;&gt;read&lt;/td&gt;
&lt;td style=&quot;width: 67.093%;&quot;&gt;collStats, dbHash, find, 등등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.907%;&quot;&gt;readWrite&lt;/td&gt;
&lt;td style=&quot;width: 67.093%;&quot;&gt;collStats, dbHash, dbStats, find, insert, remove 등등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 32.907%;&quot;&gt;dbAdmin&lt;/td&gt;
&lt;td style=&quot;width: 67.093%;&quot;&gt;collStats, dbHash, find, cillCursors, listIndexes 등등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서 사용자 계정을 생성하고 사용자별로 권한을 할당할 때는 다음 예제와 같이 액션의 모음인 역할을 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 정의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 이미 많은 역할을 정의해서 사용자가 쉽게 선택해서 사용할 수 있도록 준비했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 100 % 만족할 수 없을경우를 위해 사용자가 자신의 서비스나 요건에 맞게 새롱누 역할을 정의해서 사용할 수 있도록 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 직접 정의하는 역할은 크게 2가지로 '전역 역할' 과 '데이터베이스 단위 역할' 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;admin 데이터베이스에서 생성한 역할은 전역적으로 모든 데이터베이스의 오브젝트에 대한 권한을 포함할 수 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;admin 이 외 데이터베이스에서 생성된 역할은 해당 데이터베이스의 오브젝트에 대한 권한만 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할은 db.createRole 명령으로 생성하며 생성된 사용자 역할에 새로운 액션을 추가하고 제거하는 작업은 db.grantPrivilegesToRole() 명령과 revokePrivilegesFromRole() 명령을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;암호화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 암호화 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBMS 서버에서 데이터 암호화는 크게 응용 프로그램 수준의 암호화와 데이터베이스 서전 수준의 암호화로 나눠볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용 프로그램 수준의 암호화는 응용 프로그램을 개발하는 개발자가 직접 프로그램의 코드상에서 데이터를 암호화하고 복호화 하는 작업을 수행하므로 암호화와 관련된 지식이 필요할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 익숙해지면 데이터베이스에 의존하지 않고 암호화/복호화 할 수 있기 때문에 개발 생산성이 더 높아질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 DBMS 수준에서 복호화가 필요한 것이 아니라면 응용 프로그램에서 암호화하는 것이 더 쉬운 방법일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 암호화된 데이터는 인덱스를 이용한 범위검색을 수행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 인덱스로 검색이나 정렬 작업을 필요로 하는 경우를 위해 많은 DBMS 서버들이 TDE 기능을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDE 기능은 프로그램 코드로부터 투명하게 작동하며 실제 DBMS 서버의 내부적인 처리에서도 투명하게 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 TDE 는 WiredTiger 스토리지 엔진의 블록 캐시에서 디스크로 데이터 블록이 기록될 때 암호화해서 저장하고 블록 캐시가 디스크의 데이터 블록을 읽을 때 암호화를 해제해서 블록 캐시에 적재해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 서버가 메모리상의 데이터를 읽고 변경할 때는 암호화와 관련된 작업이 전혀 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB TDE 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커뮤니티 버전의 MongoDB 서버에서는 데이터 파일 암호화 기능을 사용할 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다힝애 커뮤니티 버전의 MongoDB 서버에 내장된 WiredTiger 스토리지 엔진은 데이터 파일 암호화를 위한 인터페이스가 제공되고 있고 커뮤니티 버전에서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;TDE for wiredTiger storage engine&quot; 이라는 제목으로 깃헙 사이트에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이건 MongoDB 에서 공식지원하는건 아니기 때문에 주의하는게 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/665</guid>
      <comments>https://mozi.tistory.com/665#entry665comment</comments>
      <pubDate>Tue, 26 Nov 2024 15:55:06 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 실행 계획 및 쿼리 최적화(2)</title>
      <link>https://mozi.tistory.com/664</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZJhmN/btsKUxr73tL/xq37ZEqV15QUea0VF2hpZ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZJhmN/btsKUxr73tL/xq37ZEqV15QUea0VF2hpZ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZJhmN/btsKUxr73tL/xq37ZEqV15QUea0VF2hpZ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZJhmN%2FbtsKUxr73tL%2Fxq37ZEqV15QUea0VF2hpZ0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 쿼리 튜닝에서 주의 깊게 살펴봐야 할 부분과 성능 튜닝이 필요한 쿼리를 수집하는 방법을 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 계획의 쿼리 튜닝 포인트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리가 인덱스를 사용하는가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도큐먼트 정렬이 인덱스를 사용하는가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필드 프로젝션이 인덱스를 사용하는가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스 키 엔트리와 도큐먼트를 얼마나 읽었는가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스의 선택도는 얼마나 좋은가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어떤 스테이지가 가장 많은 시간을 소모하는가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;슬로우 쿼리 로그 분석 및 튜닝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서 실행된 쿼리 중 100 밀리초가 넘게 걸린 쿼리는 MongoDB 서버의 로그 파일에 모두 로깅된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100 밀리초는 기본값으로 slowMs 옵션을 조정하면 로깅 시간을 조정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 로그 파일에 기록된 슬로우 쿼리 로그에서 주로 성능과 관련해 살펴봐야 할 부분은 planSummary, keysExamined, docsExamined, numYields 정도이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 83.1395%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;planSummary&lt;/td&gt;
&lt;td style=&quot;width: 83.1395%;&quot;&gt;인덱스 레인지 스캔을 실행했는지 또는 컬렉션 풀 스캔을 사용하는지 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;keysExamined&lt;br /&gt;docExamined&lt;/td&gt;
&lt;td style=&quot;width: 83.1395%;&quot;&gt;쿼리를 처리하기 위해 읽은 인덱스 키의 개수와 도큐먼트 건수를 보여줌&lt;br /&gt;이 두 값의 차이를 비교해 사용한 인덱스가 얼마나 효율적인지 실제 랜덤 액세스 방식으로 도큐먼트를 얼마나 읽었는지 확인할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.8605%;&quot;&gt;numYields&lt;/td&gt;
&lt;td style=&quot;width: 83.1395%;&quot;&gt;쿼리가 장시간 실행되면서 다른 커넥션들이 쿼리를 실행할 수 있도록 잠금을 해제했다가 다시 잠금을 획득하는 과정을 얼마나 자주 반복했는지 알 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 로그 레벨을 디폴트 값보다 더 자세히 또는 더 다순한게 변경하고자 한다면 setLogLevel 명령을 이용해 서브 모듈별로 로그 레벨을 조정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿼리 프로파일링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 파일에는 매우 많은 정보가 기록되며, 때로는 MongoDB 서버의 로그 파일로 슬로우 쿼리 로그 정보를 수집하는 것이 어려울 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 데이터베이스별로 저장되는 쿼리 프로파일링 정보를 확인하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 데이터베이스별로 50~100밀리초 이상 수행된 쿼리들에 대해 슬로우 쿼리 정보와 함께 쿼리의 프로파일링 정보를 동시에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;system.profile 컬렉션은 Cap 컬렉션으로 1MB 정도의 슬로우 쿼리만 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 1MB 를 넘어서면 오래된 로그는 삭제하고 새로 발생한 슬로우 쿼리 로그를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 system.profile 컬렉션의 슬로우 쿼리 로그를 확인할 때는 다음과 같이 역순으로 정렬해야 시간순으로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 system.profile 은 컬렉션이나 커맨드의 종류별로 필터링해서 슬로우 쿼리 로그를 확인할 수 있는 장점도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 힌트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 경우 옵티마이저가 최적의 인덱스를 이용해서 쿼리를 처리하지만 항상 그런 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔씩 쿼리의 검색 조건이나 정렬 조건을 만족할 수 있는 다양한 인덱스의 컬렉션에서는 옵티마이저가 좋지 않은 인덱스를 사용해서 쿼리를 실행하는 경우도 있다. 이런경우 인덱스 힌트를 이용해 쿼리의 실행 계획을 다른 방향으로 유도할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 인덱스 힌트는 인덱스의 이름을 직접 사용할 수도 있고 인덱스의 형태를 명시할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 힌트에 인덱스의 구조를 사용하는 방법은 인덱스가 변경되면 응용 프로그램에서 사용되는 쿼리문장의 인덱스 힌트도 항상 같이 변경되어서 배포되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 통해 많은 도큐먼트를 랜덤 액세스 방식으로 찾는 것은 상당히 고비용의 작업이기 때문에 때로는 인덱스를 사용하지 않고 컬렉션을 풀 스캔하는 것이 더 빠른 방법이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 현상은 주로 배치 작업이나 통계성 작업의 무거운 쿼리에서 자주 발생하는데, 이런 경우 다음과 같이 $natural 힌트를 명시한다.&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/664</guid>
      <comments>https://mozi.tistory.com/664#entry664comment</comments>
      <pubDate>Mon, 25 Nov 2024 11:23:49 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 실행 계획 및 쿼리 최적화(1)</title>
      <link>https://mozi.tistory.com/663</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boXFw4/btsKCY5g0Ee/nb5Zgwcv0K0A23tBiPf9yk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boXFw4/btsKCY5g0Ee/nb5Zgwcv0K0A23tBiPf9yk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boXFw4/btsKCY5g0Ee/nb5Zgwcv0K0A23tBiPf9yk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboXFw4%2FbtsKCY5g0Ee%2Fnb5Zgwcv0K0A23tBiPf9yk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버도 세컨드리 인덱스를 지원하므로 하나의 컬렉션은 최소 1개 이상의 인덱스를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 인덱스를 가지고 있을 때는 각 쿼리를 실행할 때 어떤 인덱스를 사용하는 것이 최적인지 확인하고 사용할 인덱스를 선별하는 작업이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 같은 패턴의 쿼리는 같은 실행 계획을 재활용할 수 있도록 실행계획을 캐시하는 기능이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 계획&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿼리의 처리 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 쿼리를 처리하기 위해 실행 계획을 사용하는데, 이 실행 계획은 트리 구조의 스테이지로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행계획 예시를 들어본다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;실행계획&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;FETCH -&amp;gt; IXSCAN&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;인덱스 레인지 스캔을 실행한 다음 컬렉션 데이터 파일에서 도큐먼트를 읽음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;SORT -&amp;gt; COLLSCAN&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;컬렉션 풀 스캔으로 조건에 일치하는 도큐먼트를 읽은 다음 정렬을 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;SORT -&amp;gt; FETCH -&amp;gt; IXSCAN&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;인덱스 레인지 스캔을 실행한 다음 컬렉션 데이터 파일에서 도큐먼트를 읽고 그 결과를 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.0698%;&quot;&gt;FETCH -&amp;gt; SORT_MERGE -&amp;gt; IXSCAN&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%;&quot;&gt;인덱스 인터섹션으로 레인지 스캔을 실행한 다음 컬렉션 데이터 파일에서 도큐먼트를 읽음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획의 트리는 최상위의 스테이지를 루트 스테이지라고 하며, 쿼리 실행이 시작되면 루트스테이지는 자신의 자식 스테이지를 호출한다. 이 때 각 스테이지를 호출하는 API 의 이름이 work 인데 실행계획에서는 이를 works 라는 단어로 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 스테이지의 work 함수 호출은 대표적으로 다음 3종류의 리턴 값을 반환한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.2326%;&quot;&gt;리턴 값&lt;/td&gt;
&lt;td style=&quot;width: 79.7674%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.2326%;&quot;&gt;ADVANCE&lt;/td&gt;
&lt;td style=&quot;width: 79.7674%;&quot;&gt;스테이지 처리 결과 한 건의 도큐먼트 또는 ID 값을 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.2326%;&quot;&gt;NEED_TIME&lt;/td&gt;
&lt;td style=&quot;width: 79.7674%;&quot;&gt;스테이지 처리는 완료했지만 결과 도큐먼트나 ID 값은 반환되지 않음&lt;br /&gt;주로 블록킹 스테이지일 때 발생하는데, 대표적인 블록킹 스테이지로는 인덱스를 사용하지 못하는 정렬이나 그룹핑 스테이지가 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.2326%;&quot;&gt;IS_EOF&lt;/td&gt;
&lt;td style=&quot;width: 79.7674%;&quot;&gt;스테이지 처리는 완료됐으며 더이상 읽을 도큐먼트나 ID 값이 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 스테이지에서 하위 스테이지의 work 함수를 호출하는 것은 하나의 단위 작업인데, work 함수의 호출은 주로 도큐먼트 단위로 호출이 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행계획을 보여주는 결과는 explain(&quot;executionStats&quot;) 를 실행할 때만 표시되며, 실행 계획상 표시되는 모든 스테이지에 대해 내용들이 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획 결과에서 works 와 advanced 그리고 needTime 값은 실행 계획상 각 스테이지의 어떤 단계에서 얼마나 도큐먼트를 읽고 버렸는지 얼마나 정렬을 수행했는지 등을 확인할 수 있는 중요한 정보이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 계획 수립&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 한번 실행됐던 쿼리의 실행 계획은 캐시에 저장해두고 같은 패턴의 쿼리가 다시 요청되면 캐시된 실행 계획을 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청된 쿼리가 캐시에 저장된 실행계획과 같은 패턴인지 비교하기 위해 아래 3가지 정보를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리 조건&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정렬 조건&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조회 필드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 3가지 내용을 묶어 MongoDB 에서는 &quot;Query Shape&quot; 라고 하는데 검색과 정렬 조건에 사용된 필드가 같고 조회하는 필드가 같으면 같은 &quot;Query Shape&quot; 로 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIMIT 이나 SKIP 조건은 포함되지 않으므로 모두 같은 패턴의 쿼리로 판단하고 같은 실행 계획을 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 실행 계획이 저장된 캐시가 사용되는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 쿼리가 요청되면 옵티마이저는 플랜 캐시에서 같은 패턴의 쿼리가 이미 캐시되어있는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 일치하는 쿼리 패턴의 실행 계획이 없으면 새롭게 쿼리의 실행 계획을 수립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. MongoDB 서버는 성능과 관계없이 가능한 모든 실행 계획을 준비하고 실제로 각 후보 실행 계획들을 이용해서 쿼리를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 제일 먼저 쿼리가 실행 완료되거나 지정된 도큐먼트 건수를 가장 먼저 반환하는 실행 계획을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이렇게 선택된 실행 계획을 Winning Plan 이라 하고 버려진 실행 계획을 Rejected Plan 이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 선택된 실행 계획은 이후 같은 패턴의 쿼리 실행을 위해 플랜 캐시에 저장되고 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 실행하고자 하는 쿼리와 일치하는 쿼리 패턴의 실행 계획이 있으면 MongoDB 서버는 캐시된 실행 계획의 성능을 재확인하는 과정을 거친다. 이 과정을 통과하면 서버는 그 실행 계획을 이용해 쿼리 실행 단계로 바로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실패하게 되면 캐시된 실행 계획이 없을 때와 동일하게 가능한 실행 계획을 준비하고 최종 실행 계획을 선택해서 쿼리를 실행하는 과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션이 삭제되거나 해당 컬렉션의 인덱스가 추가 생성 또는 삭제되는 경우 해당 컬렉션의 캐시된 실행 계획은 모두 삭제되고 이후 실행되는 쿼리에 대해 실행 계획이 다시 수립되어 캐시되는 과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵티마이저 옵션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 각 컬렉션에 대한 통계 정보가 거의 없다. 그나마 유일한 통계 정보가 컬렉션의 전체 도큐먼트 건수 정도인데, 이 수치는 실제 쿼리 실행을 위해 효율적인 인덱스를 선택하는 데 큰 도움이 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 쿼리의 검색과 정렬조건 그리고 조회하고자 하는 필드를 이용해 거의 기계적으로 사용 가능한 실행 계획을 생성한다. 여기저 기계적이라는 표현은 규칙 기반의 옵티마이저를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 규칙 기반의 실행 계획은 허점이 많아 MongoDB 서버는 준비된 실행 계획 후보들을 모두 동시에 실행해보는 형태로 규칙 기반의 실행 계획의 단점을 보완한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적의 실행 계획을 찾는 과정에서 다음과 같은 내부 설정값을 사용한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;설정 명&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;기본 값&lt;/td&gt;
&lt;td style=&quot;width: 46.5698%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;internalQueryPlanEvaluationCollFraction&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;0.3&lt;/td&gt;
&lt;td style=&quot;width: 46.5698%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;internalQueryPlanEvaluationMaxResults&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;101&lt;/td&gt;
&lt;td style=&quot;width: 46.5698%;&quot;&gt;옵티마이저가 최적의 실행 계획을 선택할 때 얼마나 많은 도큐먼트가 반환될 때까지 실행할 것인지 결정하는 옵션&lt;br /&gt;&lt;br /&gt;여러개의 실행 계획 중 101건을 먼저 반환하는 실행 계획을 최적의 실행 계획으로 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;internalQueryPlanEvaluationWorks&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;10000&lt;/td&gt;
&lt;td style=&quot;width: 46.5698%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;internalQueryPlanOrChildrenIndependently&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;true&lt;/td&gt;
&lt;td style=&quot;width: 46.5698%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;internalQueryPlannerEnableHashIntersection&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;false&lt;/td&gt;
&lt;td style=&quot;width: 46.5698%;&quot; rowspan=&quot;2&quot;&gt;인덱스 인터섹션 최적화를 사용할 것인지 결정&lt;br /&gt;&lt;br /&gt;인덱스 인터섹션은 활성화되어 있지만 해시 인터섹션은 비활성 상태&lt;br /&gt;&lt;br /&gt;여러 인덱스를 핸들링해서 결과를 병합해야 하므로 성능이 좋지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;internalQueryPlannerEnableIndexIntersection&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 42.4419%;&quot;&gt;internalQueryPlannerMaxIndexedSolutions&lt;/td&gt;
&lt;td style=&quot;width: 10.9884%;&quot;&gt;64&lt;/td&gt;
&lt;td style=&quot;width: 46.5698%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 실행 계획은 works 상태 값과 advance 상태 값의 비율을 이용해서 최적의 실행 계획을 수립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 internalQueryPlanEvaluationCollFraction 과 internalQueryPlanEvaluationWorks 옵션을 이용해 실행 계획 수립 과정에서 사용할 수 있는 최대 works 의 횟수를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 지정된 work 함수 호출 횟수 내에 internalQueryPlanEvaluationMaxResults 옵션에 명시된 건수만큼 도큐먼트를 먼저 반환하는 실행 계획을 최적 실행 계획으로 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플랜 캐시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적의 쿼리 실행 계획을 수립하기 위해 사용할 수 있는 실행 계획을 모두 직접 실행해보고 그중 최적의 계획을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 쿼리가 느리다면 실제 실행계획을 수립하는 과정도 많은 시간이 걸리기도 하며, 이러한 이유로 선택된 실행 계획은 플랜 캐시에 저장하고 이후 쿼리들이 재사용할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 아래 조건의 이벤트가 발생하면 플랜 캐시에서 해당 컬렉션과 관련된 실행 계획이 모두 제거되고 다시 최적의 실행 계획을 찾는 과정이 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컬렉션의 인덱스가 생성되거나 삭제되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컬렉션의 reIndex() 명령이 실행되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MongoDB 서버가 재시작되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.0 버전부터 컬렉션의 쓰기 횟수에 관계없이 플랜 캐시의 실행 계획은 그대로 유지하도록 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 많은 쓰기 오퍼레이션 이후에 데이터 분포도가 변경될 때를 대비해서 쿼리가 실행될 때마다 플랜 캐시의 실행 계획에 대해서 간단히 평가하는 단계를 구현했다. 이렇게 매번 쿼리가 플랜 캐시의 실행 계획을 평가하고 성능이 나쁜 경우에는 해당 실행 계획을 처음부터 다시 수집해서 최적의 계획을 선택하는 과정을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리가 실행될 때마다 플랜 캐시에서 가져온 실행 계획은 재평과 가정을 거치는데, 이때 재평가 과정은 통과(Pass) 또는 실패(Not Pass)로 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획의 재평가는 지정된 만큼의 도큐먼크만 읽어서 internalQueryPlanEvaluationMaxREsults 옵션에 정의한 수만큼 도큐먼트를 가져올 수 있는지 판단하는 형태로 지정되는데, 이 때 최대 읽을 수 있는 &quot;지정된 만큼의 도큐먼트&quot; 는 아래 수식으로 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 최초 실행 계획을 수립할 때 work() 함수의 호출 횟수) * internalQueryCacheEvictionRatio&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 지정된 도큐먼트 건수만큼 읽었는데 internalQueryCacheEvictionRatio 에 설정된 건수만큼의 도큐먼트를 가져오지 못하면 현재 실행 계획을 버리고 새롭게 실행 계획 후보를 뽑아서 그중 최적의 실행 계획을 선택하는 과정을 밟게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랜 캐시는 최대 5천개의 실행 계획을 저장할 수 있도록 초기 설정돼 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획을 캐시해야 하는 쿼리 패턴이 5천개가 넘는 경우 사용되지 않는 쿼리의 실행 계획이 플랜 캐시에서 삭제되고 새로운 실행 계획이 캐시에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 패턴이 5천개를 넘는다면 internalQueryCacheSize 값을 변경해 플랜캐시에 저장할 수 있는 실행 계획의 수를 늘릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 관리자가 플랜 캐시의 내용을 확인할 수 있도록 PlanCache 객체와 관련된 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PlanCache 객체는 컬렉션 단위로 캐시된 실행 계획을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 한번이라도 실행됐다고 해서 모든 쿼리의 실행 계획이 PlanCache 에 저장되는 것은 아니며 실행계획이 단 하나밖에 없는 경우에는 PlanCache 에 저장되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시에 저장된 실행 계획 중에서 특정 쿼리 패턴으로 상세한 실행 계획을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랜 캐시에서 특정 실행 계획을 삭제하고자 한다면 다음과 같이 clear() 명령이나 clearPlansByQuery() 명령을 이용하면 된다. clearPlansByQuery() 명령은 쿼리의 패턴으로 특정 실행 계획만 삭제하지만 clear 명령은 현재 컬렉션의 모든 실행 계획을 삭제하므로 많은 쿼리를 처리하고 있는 MongoDB 서버에서는 사용을 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 계획 스테이지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획을 이용해서 쿼리의 성능을 가늠하려면 각 스테이지가 어떤 역할을 수행하는지에 대해 알아야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;역할&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;COLLSCAN&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;컬렉션 풀 스캔 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;IXSCAN&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;컬렉션의 인덱스를 이용해서 인덱스 레인지 스캔 접근 방식으로 데이터를 읽는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;QUEUED_DATA&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;입력 데이터 없이 데이터를 생성해내는 스테이지 중 하나로&lt;br /&gt;컬렉션 없이 자체적으로 임시 데이터를 만듬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;FETCH&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;인덱스 레인지 스캔으로 읽은 인덱스 키와 RecordId 를 이용해 컬렉션의 도큐먼트를 읽는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;AND_SORTED&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;인덱스 인터섹션 실행 계획을 사용할 때, 각 인덱스를 통해 읽은 도큐먼트의 교집합을 찾는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;COUNT&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;자식 스테이지에 반환된 도큐먼트의 건수를 누적하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;COUNT_SCAN&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;count() 명령이 인덱스를 사용할 수 있을 때 사용되는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;DISTINCT_SCAN&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;인덱스 키를 순차적으로 읽으면서 유니크한 값이 나타날 때만 부모 스테이지로 결과를 반환하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;GROUP&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;db.collection.group 명령이나 Aggregation 의 $group 파이프라인을 위해 사용되는 스테이지로&lt;br /&gt;그룹핑 필드별로 전체 결과를 모아서 한번에 부모 스테이지로 념겨주는 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;IDHACK&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;_id 필드를 동등 비교로 검색하는 쿼리는 빠른 경로로 쿼리를 처리하는 최적화를 수행하여 IDHACK 스테이지라 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;INDEX_ITERATOR&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;인덱스 스캔을 이용하는 커서를 이용해 끝까지 인덱스 키를 읽는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;LIMIT&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;쿼리에서 limit 이 사용된 경우 지정된 N 건의 도큐먼트만 반환하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;SKIP&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;쿼리에서 skip 이 사용된 경우 지정된 M 건의 도큐먼트를 버리고 나머지를 반환하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;SORT_MERGE&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;두 개 이상의 자식 노드에서 반환된 결과 집합을 병합하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;SORT&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;인덱스를 이용하지 못하는 정렬 처리를 위해 MongoDB 서버가 쿼리 실행 시점에 도큐먼트의 정렬을 수행하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;TEXT&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;쿼리의 전문 검색 조건은 항상 TEXT 스테이지로 시작해 TEXT_MATCH 그리고 TEXT_OR 스테이지로 구성됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;UPDATE&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;도큐먼트의 데이터를 변경하는 작업을 처리하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;DELETE&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;도큐먼트의 데이터를 삭제하는 작업을 처리하는 스테이지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분은 FETCH, UPDATE, DELETE, IXSCAN 으로 될 것 같고 SORT, GROUP, LIMIT, SKIP 이 뜸뜸하게 보일 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿼리 실행 계획 해석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획은 cursor.explain() 으로 확인할 수 있는데 크게 3가지 모드를 지원한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;모드&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;queryPlanner&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;cursor.explain() 명령은 아무런 옵션이 없으면 디폴트 모드인 queryPlanner 로 작동한다.&lt;br /&gt;&lt;br /&gt;queryPlanner 모드에서는 해당 쿼리를 위해 선택된 최적의 실행 계획만 보여주는데, 3가지 모드 중 가장 단순한 실행 계획 결과를 보여준다.&lt;br /&gt;&lt;br /&gt;이 결과로 아래 튜닝 정보를 분석할 수 있다.&lt;br /&gt;- 쿼리가 의도했던 인덱스를 제대로 활용하는지&lt;br /&gt;- 쿼리가 정렬 작업을 인덱스를 활용하는지&lt;br /&gt;- 쿼리의 프로젝션이 인덱스를 이용해 처리되는지&lt;br /&gt;&lt;br /&gt;수행하면 queryPlanner 필드가 최상위에 나타나고 winingPlan 필드가 표시되는데 이 하위의 내용이 최적으로 선택된 실행 계획의 내용을 보여준다.&lt;br /&gt;winningPlan 하위에 트리 형태의 스테이지 구성이 표시되는데 현재 쿼리를 실행하기 위해 사용되는 각 단계를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;executionStats&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;executionStats 모드에서는 queryPlanner 모드의 모든 내용을 포함하고&lt;br /&gt;더불어 선택된 최적실행계획을 실행하고 실행된 내역을 상세히 보여주는 실행 계획 모드&lt;br /&gt;&lt;br /&gt;이 결과로 아래 튜닝 정보를 분석할 수 있다.&lt;br /&gt;- 인덱스의 선택도가 좋은지&lt;br /&gt;- 실행 계획의 각 처리 단계에서 어떤 스테이지가 느린지&lt;br /&gt;&lt;br /&gt;수행하면 executionStats 모드의 실행 계획이 추가로 표시되고 work 함수가 몇 번 호출되었는지 응답으로 ADVANCE 나 NEED_TIME 등이 몇 번 반환됐는지 등의 상세한 정보를 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;allPlansExecution&lt;/td&gt;
&lt;td style=&quot;width: 79.186%;&quot;&gt;옵티마이저가 최적으로 선택한 실행 계획과 그 실행 계획의 상세 내역을 포함하며,&lt;br /&gt;더불어 옵티마이저가 최적의 실행 계획을 선택하기 위해 평가했던 나머지 후보 실행 계획들의 내용도 모두 포함&lt;br /&gt;&lt;br /&gt;이 결과로 아래 튜닝 정보를 분석할 수 있다.&lt;br /&gt;- 옵티마이저가 여러 실행 계획들을 검토했는지&lt;br /&gt;- 여러 실행 계획 중 왜 최적 실행 계획이 선택되었는지&lt;br /&gt;&lt;br /&gt;allPlansExecution 필드가 추가로 표시되며 이 필드 하위에는 후보실행 계획별로 하나씩 배열로 표시된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;queryPlanner 모드의 출력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;winningPlan 필드에는 스테이지가 트리 형태로 표시되는데, 이는 최적의 실행 계획이 사용하는 스테이지별로 정보를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행계획을 보면 SORT -&amp;gt; SORT_KEY_GENERATOR -&amp;gt; FETCH -&amp;gt; IXSCAN 스테이지가 트리 형태로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SORT 스테이지는 SORT_KEY_GENERATOR 스테이지를 포함하고 있으므로 SORT 스테이지가 부모 스테이지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버가 쿼리를 실행할 때 호출 순서는 부모에서 자식 스테이지로 흘러가지만, 실제로 처리되는 순서는 반대로 자식 스테이지가 제일 먼저 실행되고 그 다음에 부모 스테이지가 실행되는 순서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 경우 IXSCAN 이 가장 먼저 실행되고 그 결과르 검색된 도큐먼트의 ID 가 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FETCH 스테이지에서 도큐먼트 ID 를 이용해서 실제 컬렉션 데이터 파일에서 도큐먼트를 가져오는 작업을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어온 도큐먼트에 대해 SORT_KEY_GENERATOR 스테이지가 정렬 조건을 이용해 정렬 키를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 최상위 SORT 스테이지는 정렬 키를 이용해 정렬을 수행하고 클라이언트로 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스와 관련된 indexBounds 필드에는 인덱스 레인지 스캔을 위한 범위를 보여주는데 이 범위는 실제 인덱스를 읽는 범위를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;executionStats 모드의 출력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;executionStats 는 우선 n4Returned, executionTimeMillis, totalKeysExamined, totalDocsExamined 은 똑같이 표시되는데 전체적인 실행결과 상태 정보를 보여주는 것이므로 하위의 각 스테이지에 표시되는 상태 값들의 합계로 생각하면 된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.1628%;&quot;&gt;상태&lt;/td&gt;
&lt;td style=&quot;width: 78.8372%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.1628%;&quot;&gt;n4Returned&lt;/td&gt;
&lt;td style=&quot;width: 78.8372%;&quot;&gt;실제 클라이언트로 반환된 도큐먼트의 건수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.1628%;&quot;&gt;executionTimeMillis&lt;/td&gt;
&lt;td style=&quot;width: 78.8372%;&quot;&gt;전체 쿼리의 실행 시간 (밀리초)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.1628%;&quot;&gt;totalKeysExamined&lt;/td&gt;
&lt;td style=&quot;width: 78.8372%;&quot;&gt;쿼리를 처리하기 위해 인덱스에서 읽은 인덱스 키의 개수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.1628%;&quot;&gt;totalDocsExamined&lt;/td&gt;
&lt;td style=&quot;width: 78.8372%;&quot;&gt;쿼리를 처리하기 위해 컬렉션의 데이터 파일에서 읽은 도큐먼트의 개수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;executionStats 필드하위에 executionStages 필드가 표시되는데, 쿼리의 실행계획에 표시되었던 각 스테이지들이 실행되는 동안 처리한 상세한 내용이 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;executionStages 하위 스테이지들이 트리 구조로 표시된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 180px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;상태&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;stage&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지의 타입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;executionTimeMillisEstimate&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지(자식 스테이지를 포함)를 처리하는데 걸린 밀리초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;executionTimeMillisEstimated&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지를 처리하는데 걸린 밀리초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;works&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지의 work 함수가 호출된 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;advanced&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지가 도큐먼트를 반환한 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;needTime&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지가 도큐먼트를 반환하지 못한 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;needYield&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지가 처리되면서 Yield 를 실행한 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 30.6976%; height: 20px;&quot;&gt;isEOF&lt;/td&gt;
&lt;td style=&quot;width: 69.3024%; height: 20px;&quot;&gt;현재 스테이지가 EOF 를 반환한 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9-4 쿼리를 실행하기 위해 4개의 스테이지를 거쳐서 실행됐는데, SORT_KEY_GENERATOR 와 FETCH , IXSCAN 스테이지는 5~6번만 호출되었고 이 스테이지들의 work 함수 리턴 값은 대부분 ADVANCED 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이 스테이지들은 대부분 work 함수가 호출될 때마다 도큐먼트 결과를 한 건씩 리턴했다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 제일 앞쪽의 SORT 스테이지는 12번이 호출되었고 그 중 6번은 NEED_TIME 을 리턴하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서도 실제 리턴된 도큐먼트 건수는 4건밖에 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 work 함수의 호출 횟수가 성능을 결정하는 것은 아니지만, 내부적으로 각 스테이지에서 어떤 작업을 얼마나 반복해서 많이 수행하는지 판단할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이지별로 작업의 특성에 따라 추가로 표시되는 내용도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SORT 경우 memUsage 와 memLimit 필드가 출력되는데 정렬을 처리하기 위해 사용할 수 있는 최대 메모리 버퍼 크기와 처리를 위해 사용한 정렬용 메모리 버퍼의 크기를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;allPlansExecution 모드의 출력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;allPlansExecution 필드는 옵티마이저가 검토했던 모든 예비 실행 계획 후보들을 실행한 후, 각 실행 계획별로 실행 상태 정보를 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;{ score:1 , name:1 }&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;{ name:1 }&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;totalKeysExamined&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;totalDocsExamined&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;works&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;advanced&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;needTime&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;11&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;isEOF&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 처리하는데 있어 {name:1} 인덱스를 사용한 실행 계획은 인덱스 키 엔트리를 단지 4개만 읽고, 컬렉션의 데이터 파일에서도 4개의 도큐먼트만 읽어서 쿼리를 종료했다는 것을 알 수 있다. 반면 {score:1 , name:1} 은 11개씩 읽었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 루트 스테이지의 work 함수 호출 횟수는 두 실행 계획 모두 11이라는 값을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 11번의 work 함수가 호출되었는데 name:1 이 효율적이라 판단한 이유는 실행 계획 수립 과정에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 옵티마이저는 최적의 실행계획을 선택하기 위해 가능한 모든 실행 계획 후보를 동시에 병렬로 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 돌아가면서 각 실행계획의 루트 스테이지에 대해 work 함수를 균등하게 한 번씩 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 반복해서 실행 계획의 work 함수를 호출하면서 2개 조건 중 하나라도 충족되는 실행계획이 나올때까지 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 실행 계획의 처리가 완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 실행 계획이 101건(internalQueryPlanEvaluationMaxResults 의 값)의 도큐먼트를 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서는 인덱스를 사용하는 실행 계획이 11번의 work 함수 호출 후 처리가 완료되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더이상 다른 실행계획을 평가하지 않고 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 후보 실행 계획 중 먼저 완료된 실행 계획이 있는 경우에는 isEOF 필드의 값이 1인 경우가 있는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 쿼리가 무겁고 많은 도큐먼트를 처리해야 하는 쿼리일수록 실행 계획을 선정하는 작업이 시간이 오래 걸릴 수밖에 없는 이유이다. 또한 쿼리의 실행 계획을 처음 수립하는 과정에서 가능한 모든 후보 실행 계획을 동시에 실행해야 하므로 서버에 부담이 간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UPDATE 와 REMOVE 그리고 AGGREGATION 쿼리의 실행계획&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UPDATE 나 REMOVE 명령에 대해서는 explain().update()&amp;nbsp; 혹은 explain().delete() 형태로 실행 계획을 확인할 수 있는 문법을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/663</guid>
      <comments>https://mozi.tistory.com/663#entry663comment</comments>
      <pubDate>Mon, 18 Nov 2024 17:16:16 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 쿼리 개발과 튜닝(1)</title>
      <link>https://mozi.tistory.com/662</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BLh7x/btsJW9Ev323/At74A1T8uMCSsXOqfRRlOk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BLh7x/btsJW9Ev323/At74A1T8uMCSsXOqfRRlOk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BLh7x/btsJW9Ev323/At74A1T8uMCSsXOqfRRlOk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBLh7x%2FbtsJW9Ev323%2FAt74A1T8uMCSsXOqfRRlOk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 CRUD 쿼리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 언어로 MongoDB 쿼리를 작성하는 것은 쿼리 빌더와 같은 래퍼 클래스의 도움을 받으면 되기 때문에 그다지 어렵지 않으나, BSON 쿼리를 직접 손으로 입력해야 하는 경우에는 괄호 쌍을 맞추어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BSON 쿼리는 기존 RDBMS 의 SQL 과 문법과 오퍼레이터만 다를 뿐 실제 대부분의 기능을 제공하고 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 119px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;RDBMS SQL&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;MongoDB BSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;INSERT&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;db.collection.insert()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Batch DML&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;db.collection.bulkWrite()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;UPDATE REPLCAE&lt;br /&gt;(INSERT ON DUPLICATE KEY UPDATE)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;db.collection.update()&lt;br /&gt;db.collection.update( { }, { $set : { } }, { upsert : true } )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;DELETE&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;db.collection.remove()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;SELECT&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;db.collection.find()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;SELECT GROUP BY&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;db.collection.aggregate() MapReduce&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿼리 작성&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;INSERT&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InsertOne, InsertMany 의 함수도 지원하나 기본인 Insert 에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 두 개의 명령 인자를 사용하는데, 첫 번째 인자는 저장하고자 하는 도큐먼트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 인자는 INSERT 를 처리할 때 사용할 여러 가지 옵션을 명시할 수 있는데 writeConcern 과 ordered 로&amp;nbsp;필수옵션이 아니기 때문에 생략이 가능하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.6744%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 82.3256%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.6744%;&quot;&gt;writeConcern&lt;/td&gt;
&lt;td style=&quot;width: 82.3256%;&quot;&gt;INSERT 명령이 어떤 조건에서 완료 응답을 반환할지를 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.6744%;&quot;&gt;ordered&lt;/td&gt;
&lt;td style=&quot;width: 82.3256%;&quot;&gt;첫 번째 인자가 도큐먼트의 배열일 때 INSERT 명령을 배치로 처리하는데, ordered 값이 true 인 경우 도큐먼트를 순서대로 저장하기 위해 단일 쓰레드로 하나씩 INSERT 를 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ordered 가 false 라면 실제 컬렉션에 도큐먼트가 저장되는 순서가 사용자가 명시한 순서와 다르게 저장되므로 중간에 INSERT 에 실패했을 때 재처리를 어렵게 하거나 불필요한 작업을 해야할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ordered 가 false 라면 에러가 발생한 경우 그냥 무시하고 나머지 도큐먼트를 모두 INSERT 한다. 만약 배열의 하나라도 INSERT 에 실패했을 때 즉시 작업을 취소해야 한다면 ordered 를 true 로 하는게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 마이너한 실패는 무시하고 최대한 빨리 저장하고 싶다면 ordered 를 false 로 하여 INSERT 시 여러 쓰레드를 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;INSERT 도큐먼트의 ObjectId 조회&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 RDBMS 서버가 AUTO INCREMENT 또는 시퀀스와 같이 자동으로 아이디 값이 증가되는 기능을 제공한다. 값의 포맷은 조금 다르지만 MongoDB 도 12바이트로 구성된 ObjectId 라는 AUTO INCREMENT 아이디 값을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 샤딩 환경을 고려해야 하므로 유일한 값을 구성하는 자체가 기존 RDBMS 와 조금 다르지만 기본적인 용도는 동일하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법은 미리 _id 필드에 ObjectId 를 생성해서 할당하는 방식으로 INSERT 될 _id 필드의 값을 미리 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 조회 시 실제 Mongo 서버가 할당한 값이 아니라 MongoDB 드라이버에서 할당한 값이지만, 이 값을 MongoDB 서버가 값 자체를 변경하는 것은 아니기 때문에 아무런 차이가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ObjectId 는 어느 서버에서 생성하든지 유일성이 보장되는 구조라서 굳이 단일 서버에서 발급해야 하는 제약 사항이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UPDATE&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;updateOne, updateMany, replaceOne 의 함수도 있으나 UPDATE 에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INSERT 와 달리 3개의 인자를 사용하는데 세 번째 인자는 선택 옵션으로 '업데이트 대상 도큐먼트 검색 조건' 과 '업데이트 내용' 만 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 인자에는 변경할 도큐먼트를 검색하는 조건을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 인자는 변경할 내용을 기술하는데 set 옵션이 없으면 기존 도큐먼트를 통째로덮어써 버리기 때문에 주의해야 한다. 이 외 다양한 오퍼레이터를 사용할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;오퍼레이터&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;$inc&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;필드의 값을 주어진 값만큼 증가시켜서 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;$mul&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;필드의 값을 주어진 값만큼 배수로 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;$rename&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;필드의 이름을 변경&lt;br /&gt;&lt;br /&gt;_id 를 입력하여 특정 도큐먼트의 필드명만 변경할수도 있으며&lt;br /&gt;전체 도큐먼트 변경도 할수 있는데 이는 서비스에 상당한 영향을 미치므로 주의해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;$setOnInsert&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;set 과 동일한 방식으로 사용하지만 upsert 옵션이 true 인 UPDATE 명령이 업데이트 해야 할 도큐먼트를 찾지 못해서 INSERT 를 실행해야 할 때에만 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;$set&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;도큐먼트 필드 값을 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;$unset&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;도큐먼트의 필드를 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.2558%;&quot;&gt;$currentDate&lt;/td&gt;
&lt;td style=&quot;width: 81.7442%;&quot;&gt;필드의 값을 현재 시각으로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 인자는 UPDATE 를 처리할 때 적용할 여러가지 옵션을 설정한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;upsert&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;upsert 옵션을 false 로 설정하면 UPDATE 명령이 조건에 맞는 도큐먼트를 찾지 못하더라도 어떤 변경을 가하지 않으나 true 인 경우 새로운 도큐먼트를 INSERT 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;multi&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;기본적으로 단일 도큐먼트의 업데이트만 수행하여 검색 조건에 일치하는 도큐먼트가 2개 이상이더라도 그 중 하나만 변경&lt;br /&gt;true 로 설정하면 조건에 일치하는 모든 도큐먼트를 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;writeConcern&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;UPDATE 명령이 어떤 조건에서 '완료' 응답을 반환할지를 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;collation&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;UPDATE 명령이 변경할 대상 도큐먼트를 검색하 때 사용할 문자 셋과 콜레이션을 명시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배열 필드 업데이트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 배열필드에 대한 많은 오퍼레이션을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 배열 필드의 데이터를 변경하기 위해 도큐먼트의 클라이언트로 가져온 다음 가공해서 다시 변경하는 형태로 업데이트 하지 않고도 특정 위치의 배열 엘리먼트를 수정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 엘리먼트를 추가하고자 하는 경우 $push 명령을 이용해 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라 배열 엘리먼트 중 특정 필드값인 엘리먼트만 새로운 값으로 대체할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 배열 타입의 값을 가지는 필드에서는 유일성이 보장되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하나의 도큐먼트 내에서 배열 필드의 유일성을 보장해야 하는 경우에는 배열을 다시 새로운 서브 도큐먼트로 처리하면 데이터를 변경할 때 유일성을 보장받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;REMOVE&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 도큐먼트를 삭제하는 방법으로는 deleteOne, deleteMany 도 있지만 기본명령어인 remove 에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REMOVE 명령은 2개의 도큐먼트 인자를 필요로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 인자는 삭제할 필드의 검색조건이며, 두 번째 인자는 선택 옵션을 사용할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3953%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 83.6047%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3953%;&quot;&gt;justOne&lt;/td&gt;
&lt;td style=&quot;width: 83.6047%;&quot;&gt;REMOVE 명령은 여러 도큐먼트가 삭제&lt;br /&gt;만약 justOne 옵션을 명시하지 않으면 조건에 맞는 모든 도큐먼트를 삭제하나, true 로 설정하면 조건에 일치하는 첫 번째 도큐먼트만 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3953%;&quot;&gt;writeConcern&lt;/td&gt;
&lt;td style=&quot;width: 83.6047%;&quot;&gt;REMOVE 명령이 어떤 조건에서 완료 응답을 반환할지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3953%;&quot;&gt;collation&lt;/td&gt;
&lt;td style=&quot;width: 83.6047%;&quot;&gt;REMOVE 명령이 삭제할 대상 도큐먼트를 검색할 때 사용할 문자 셋과 콜레이션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;격리된 UPDATE 와 REMOVE&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기 오퍼레이션은 도큐먼트 단위의 원자성만 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 쓰기 오퍼레이션으로 여러 도큐먼트를 변경하거나 삭제하더라도 내부적으로 하나의 트랜잭션으로 하나의 도큐먼트만 처리하는 방식으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여러 도큐먼트를 변경하면 오퍼레이션이 완료되기도 전에 먼저 변경된 데이터들은 다른 커넥션에서 즉시 조회할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 하나의 명령이 완료되기 전까지 다른 커넥션에서 변경 내용을 확인하지 못하게 하려면 격리된 UPDATE 또는 REMOVE 명령을 사용해야 한다. 이 명령 모두 업데이트 대상 도큐먼트를 검색하는 조건과 함께 $isolated : 1 옵셩르 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션을 사용하면 변경 작업이 완료될때까지 다른 커넥션이 변경중인 데이터를 볼 수는 없지만, 업데이트 명령의 트랜잭션을 보장하지는 않는다. 이 말은 작업 도중 에러가 발생하면 이미 처리된 도큐먼트에 대해서는 롤백을 보장하지 않는다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BulkWrite&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경 명령을 모아서 한 번에 실행할 수 있는 명령으로&amp;nbsp;반드시 하나의 컬렉션에 대해서만 데이터를 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령의 결과는 명령 단위로 정리해서 적용된 건수를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리결과의 insertedIds 서브 도큐먼트에는 INSERT 된 도큐먼트들의 프라이머리 키 (_id ) 값을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upsertedIds 서브 도큐먼트에는 UPDATE 명령의 upsert 플래그가 true 일 때 INSERT 된 도큐먼트들의 프라이머리 키 값을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BulkWrite 명령도 다른 CRUD 명령과 같이 ordered 옵션을 사용할 수 있다. 이 때 ordered 옵션이 true 이면 BulkWrite 명령에 주어진 각 하위 명령이 순차적으로 실행되며, 중간에 실패 돜큐먼트가 발생하면 지금까지 변경된 도큐먼트는 그대로 유지하고 남은 작업은 모두 멈춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ordered 옵션을 false 로 설정하면 여러 쓰레드로 나누어 병렬로 처리하며, 중간에 에러가 발생해도 나머지 작업을 멈추지 않고 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 라우터를 사용해야 하는 클러스터 환경이면 ordered 옵션으로 인한 성능 차이는 더 크게 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 WriteConcer 옵션도 같이 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FIND&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Find 는 데이터를 조회하는 명령으로, MongoDB 에서 사용하는 명령 중 가장 다양한 조건이나 옵션을 사용할 수 있는 명령이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIND 명령은 2개의 인자를 사용하는데, 첫 번째 인자는 도큐먼트를 검색할 때 사용할 조건을 명시하며, 두 번째 인자에는 클라이언트로 반환할 필드를 명시한다. 이 두 인자는 모두 선택 옵션이므로 아무 인자 없이 명령을 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIND 명령이 아무 조건이 주어지지 않으면 컬렉션을 풀 스캔한 결과를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 컬렉션의 모든 도큐먼트를 가져오지만 도큐먼트의 지정된 필드들만 가져오고자 한 다면, 검색 조건은 빈 도큐먼트로 설정하고, 두 번째 인자에 원하는 필드만 선택하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIND 명령에서 사용하는 두 번째 인자는 쿼리가 반환할 필드의 목록을 선택하는데, 이를 프로젝션(Projection) 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 프로젝션에서 가져오고자 하는 필드는 1로 표시하며 그렇지 않은 필드는 0 으로 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 프로젝션에서 0 과 1을 같이 사용할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 필드에 대해 프로젝션 옵션을 명시하면 나머지 명시되지 않은 필드는 그 반대로 자동으로 설정돼서 프로젝션할 필드가 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 _id 필드에 대해서는 이 제한이 예외로 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FIND 연자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 조건에 사용할 수 있는 오퍼레이터를 MongoDB 매뉴얼에서는 크게 7가지 정도로 나눠서 구분한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 굳이 검색조건을 사용하면서 사용하는 오퍼레이터가 어떤 그룹의 오퍼레이터인지 식별할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오퍼레이터는 비교 / 논리 결합 / 필드 메타 / 평가 / 공간 / 배열 / 비트 로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 검색 조건은 모두 JSON 포맷으로 사용해야 하므로 RDBMS 와 같은 비교 연산자 그대로를 사용할 수는 없고, 이를 모두 $ 로 시작하는 오퍼레이터로 새로 만들고 JSON 문법에서 사용하도록 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 오퍼레이터&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;연산자&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$eq&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$gt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$gte&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;gt;=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$lt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;lt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$lte&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;lt;=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$ne&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;lt;&amp;gt; 또는 !=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$in&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;IN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$nin&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;NOT IN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리 결합 오퍼레이터&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;연산자&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$or&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OR&lt;br /&gt;예) db.collection.find( { $or : [ { name : &quot;mozi&quot; } , { blog : &quot;tistory&quot; } ] } )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$and&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;AND&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$not&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;부정 연산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$nor&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;배열로 주어진 모든 표현식에 일치하지 않는지 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드 메타 오퍼레이터&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;연산자&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$exists&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드를 가졌는지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$type&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드의 데이터 타입을 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평가 오퍼레이터&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;연산자&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$mod&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$regex&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;정규 표현식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$text&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;전문 검색 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;$where&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;표현식에 일치하는 도큐먼트만 필터링해서 반환&lt;br /&gt;where 절에 주어진 조건은 인덱스를 사용하지 못함 - 성능느림&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 오퍼레이터&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 174px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;연산자&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 40px;&quot;&gt;$all&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 40px;&quot;&gt;배열 타입의 필드가 파라미터로 주어진 배열의 모든 엘리먼트를 가졌는지 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 60px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 60px;&quot;&gt;$elemMatch&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 60px;&quot;&gt;모든 조건에 일치하는 엘리먼트를 하나라도 가진 도큐먼트를 검색&lt;br /&gt;배열에서 자주사용되는 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;$size&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;배열의 엘리먼트 개수를 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FIND 조건&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 형태의 도큐먼트를 검색할 수 있도록 배열이나 서브 도큐먼트 조건을 활용할 수 있게 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 쿼리를 작성할 때 가장 중요한 점은 하나의 필드에 대한 조건은 반드시 하나의 서브 쿼리 도큐먼트로 작성해야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db.collection.find ( { name : ${ gte : &quot;m&quot; } , name : { $lte : &quot;u&quot; } } )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령은 name 필드의 값이 m 보다 크거나 같고 u 보다 작거나 같은 도큐먼트를 검색하는 쿼리이나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 의도와 다르게 출력되는데 쿼리 두개 조건 중 하나는 버려지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 필드에 대한 조건은 항상 하나의 서브도큐먼트에 전부 포함되어야 하기에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 변경되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db.collection.find ( { name : { ${ gte : &quot;m&quot; } , { $lte : &quot;u&quot; } } )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 기억해야 할 점은 별도의 논리 연산을 포함하지 않으면 AND 연산을 수행한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 포맷별로 FIND 명령의 조건이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스칼라 필드 : 단순히 정수나 문자열 등과 같이 하나의 값만 가지는 필드를 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서브 도큐먼트 필드 : 서브도큐먼트의 필드를 검색한다.&amp;nbsp; 서브도큐먼트는 . 으로 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열 필드 : 배열 필드의 값을 검색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;커서 옵션 및 명령&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIND 쿼리는 항상 커서를 반환하는데, 커서를 통해 쿼리 결과 도큐먼트를 하나씩 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커서는 단순히 검색 결과 도큐먼트를 읽는 수단으로만 사용되는 것은 아니며, FIND 쿼리의 검색 결과를 정렬하거나 지정된 건수의 도큐먼트를 건너뛰거나 제한하는 등의 기능도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 정렬 ( cusor.sort() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 결과 데이터를 정렬하려면 sort 커서옵션을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜레이션 변경 ( cursor.collation() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 콜레이션을 명시하지 않으면 FIND 쿼리검색 조건은 컬렉션의 콜레이션을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Concern ( cursor.readConcern() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 처리 구조로 되어 있기 때문에 여러 레플리카 멤버 중에 어떤 멤버를 선택하는지에 따라 FIND 쿼리의 결과가 달라진다. 결과가 달라지는걸 막기위해 복제 동기화 상황에 따른 읽기를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Preferenced ( cursor.readPref() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리가 아닌 세컨드리 멤버로 접속해서 쿼리를 실행하게 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 코멘트 ( cursor.comment() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 BSON 포맷을 사용하므로 주석이 허용되지 않는다. 그래서 FIND 쿼리의 커서 옵션으로 코멘트를 추가하면 쿼리 로그에 기록된 주석을 로그파일에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 실행계획 ( cursor.explain() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 실행계획을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 힌트 ( cursor.hint() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 인덱스를 선택하지 못할경우 옵티마이저에 특정 인덱스를 사용하도록 강제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 배치크기 ( cursor.batchSize() )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 결과를 몇 건 단위로 잘라서 가져올지 결정한다. 쉘에서는 일반적으로 20건씩 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FindAndModify&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 명령을 하나의 트랜잭션으로 묶어서 사용할 수 없기 때문에 변경 직전이나 직후의 도큐먼트 데이터를 확인하기가 쉽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 직전의 데이터를 확인하는 기능을 위해 FindAndModify 명령을 제공하는데, 검색 조건에 일치하는 도큐먼트를 검색하고 그 도큐먼트를 변경하거나 삭제하는 후속 오퍼레이션을 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;findAndModify 명령은 변경되거나 삭제된 도큐먼트를 결과로 반환하는데, 이때 new 옵션과 upsert 옵션에 따라 반환되는 도큐먼트가 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;확장 검색 쿼리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량 데이터 분석 및 통계 작업을 위한 맵 리듀스와 어그리게이션 기능을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;맵리듀스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 맵 리듀스는 아키텍처 특성상 도큐먼트의 데이터를 자바스크립트 엔진의 변수로 할당하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 엔진의 처리가 완료되면 결과를 다시 도큐먼트로 변환하는 작업이 매우 빈번하게 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Map 함수와 Reduce 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Map 단계와 Reduce 단계로 나누어지는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 Map 단계를 위한 함수와 Reduce 단계를 위한 함수를 자바스크립트로 개발해서 맵리듀스 명령을 호출해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Map 함수는 맵리듀스 엔진에 의해 도큐먼트마다 한 번씩 호출되는데, Map 함수가 도큐먼트별로 한 번씩 emit 함수를 호출해야 하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵리듀스 엔진은 Map 함수의 결과를 Key 로 그룹핑해서 VAlue 의 배열을 Reduce 함수르 존달한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reduce 함수는 Key 단위로 전달된 Value 의 배열을 풀어서 적당한 형태로 가공하고 그 결과를 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;맵리듀스 명령의 사용법과 옵션&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;out&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;결과를 저장하거나 출력할 위치를 명서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;query&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;실행할 대상 도큐먼트를 검색하는 조건을 명시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;sort&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;도큐먼트를 먼저 정렬해서 맵리듀스 엔진으로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;limit&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;엔진으로 전달할 도큐먼트의 개수를 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;finalize&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;Reduce 함수의 결과를 변경하거나 보완해서 최종 결과를 만들 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;scope&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;글로벌 변수를 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;jsMode&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;자바스크립트 엔진 사이에서 변환되는 과정을 막음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;verbose&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;처리 결과에 단계별 처리 과정 및 소요 시간에 대한 정보포함 여부 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Finalizer 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵리듀스 작업에서는 처리의 마지막에 실행되는 Finalizer 함수를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 잘 사용하지 않지만 처리를 더하거나 최종 결과를 변경하고자 한다면 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Finalize 함수는 두 개의 인자를 사용하는데 모두 Reduce 함수의 리턴 값이 주어지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;증분 맵 리듀스 작업&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 컬렉션의 도큐먼트가 계속 증가할 때, 매일 전체 데이터에 대해 맵리듀스를 수행하는 것이 아니라 증가한 만큼의 도큐먼트만 맵리듀스를 수행해서 그 결과를 기존의 맵리듀스 결과와 병합하는 방식의 작업이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 증분 맵리듀스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증분 맵리듀스를 사용할 때 주의할 사항이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이전 맵리듀스 결과가 저장된 컬렉션의 데이터가 임의로 변경되면 증분 맵리듀스의 결과에도 영향을 미친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 처음 전체 데이터에 대해 맵리듀스를 실행할 때는 out 컬렉션만 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 두번째 맵리듀스 작업부터는 증분으로 실행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;맵리듀스 함수 개발 시 주의 사항 및 디버깅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Map 함수에서 호출하는 emit() 함수의 두 번째 인자와 Reduce 함수의 리턴 값은 같은 포맷이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Reduce 함수의 연산 작업은 멱등이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;맵리듀스 성능튜닝&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵리듀스 작업을 컬렉션의 모든 데이터가 아니라 일부 조건에 일치하는 도큐먼트들에 대해서만 처리해야 하는 경우라면 맵리듀스 명령의 query 옵션을 사용하면 된다. 이 때 query 옵션의 검색 조건을 위한 인덱스를 준비하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 맵리듀스 엔진은 조건에 일치하는 도큐먼트를 무조건 지정된 개수씩 모아서 청크 단위로 Map 함수와 Reduce 함수를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 Map 함수는 도큐먼트의 건수만큼 호출되지만 Reduce 함수는 그 청크에서 유니크한 키 개수만큼 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵리듀스 엔진은 Reduce 함수를 호출하기 전에 임시 중간 저장소에서 이미 동일한 키의 중간 결과가 있는지 확인해야 하고, Reduce 함수의 결과를 받으면 다시 임시 중간 ㅓㅈ장소에서 동일한 키를 찾아서 덮어쓰기 하는 과정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 작업은 상당히 부담스럽기 때문에 Reduce 함수의 호출 횟수를 줄일 수 있도록 맵리듀스 명령에는 sort 옵션을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sort 옵션에 키를 이용해 먼저 정렬을 수행하면 Reduce 함수의 호출 횟수가 줄어들고 그만큼 임시 저장소를 찾는 작업이 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵리듀스 처리는 하나의 샤드 서버 내에서는 병렬로 처리되지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 맵리듀스를 병렬로 사용하고 싶다면 여러 쓰레드로 실행하되 각 쓰레드가 처리해야 할 데이터 영역을 조건으로 설정하는 방식을 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Aggregation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIND 명령으로는 데이터를 그룹핑해서 특정 조건에 일치하는 도큐먼트의 개수를 확인하거나 하는 복잡한 처리는 수행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Aggregaion 은 복잡한 데이터 분석 기능을 제공하는데, 일반적으로 SQL 에서 GROUP BY 절로 처리할 수 있는 기능들을 샤딩된 환경에서 실행할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Aggregation 의 목적&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 맵리듀스라는 분석 기능을 제공하는데, 새롭게 Aggregation 기능을 도입한 데에는 대표적으로 두 가지 이유를 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 간단한 분석 쿼리에도 자바스크립트를 이용해 맵리듀스 프로그램을 작성해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 멥리듀스 작업은 자바스크립트 엔진과 MongoDB 엔진 간의 빈번한 데이터 매핑으로 인한 성능제약이 심함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Aggregation 의 작동 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩되지 않은 MongoDB 서버에서의 Aggregation 실행은 다른 오퍼레이션과 비교할 때 특별한 차이는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 샤딩된 환경에서는 Aggregation 파이프라인의 각 스테이지가 어떤 샤드에서 실행되는지가 부하 분산 차원에서 상당히 중요한 문제가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 이렇게 여러 샤드에서 데이터를 수집해야 하는 처리를 위해 프라이머리 샤드를 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 라우터는 사용자로부터 Aggregation 쿼리를 전달받으면 우선 요청된 Aggregation 쿼리의 파이프라인 선두에 match 스테이지가 있는지와 검색 조건니 샤드 키를 포함하는 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 검색 조건이 필요로 하는 도큐먼트가 단일 샤드에만 있다면 해당 샤드로만 쿼리를 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않다면 쿼리를 대표 샤드로 전달한다. 대표 샤드는 쿼리를 전달받으면 나머지 샤드로 쿼리를 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 요청을 전달받은 샤드들이 쿼리 결과를 반환하면 대표 샤드는 그 결과를 병합하고 정렬하는 작업을 수행해서 MongoDB 라우터로 최종 결과를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 이하버전에서는 Aggregation 이 프아리머리 샤드에서 진행되었으나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리에만 부하 불균형이 발생하여 3.2버전 이후 각 샤드가 개별적으로 처리하지 못하는 단계를 처리할 대표샤드를 랜덤하게 선정하도록 개선되어 특정 샤드가 과부하를 받게 되는 현상은 많이 줄었으나, 여전히 $out , $lookup 과 같은 단계를 사용하는 쿼리는 프라이머리 샤드에 의존할 수밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단일 목적의 Aggregation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서는 Aggregate 와 같이 집계 처리를 수행하는 2개의 명령이 있는데, 일반적인 Aggregate 보다는 조금 단순하지만 더 사용 빈도가 높아 쉽게 Aggregate 기능을 사용할 수 있도록 별도의 명령을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count() 와 distinct() 2가지가 지원되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 명령은 특정 조건에 부합하는 도큐먼트의 건수를 확인하는 명령이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;distinct 명령은 지정된 필드의 유니크한 값들만 배열로 반환하는데, 만약 유니크한 값의 개수를 확인하고자 한다면 결과에 length 를 출력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;범용 Aggregation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 목적뿐 아니라 다양한 서비스 요건을 위해 사용자가 직접 작업 내용을 구현할 수 있도록 일반적인 Aggreegation 기능도 제공하는데 이를 범용 Aggregation 이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범용 Aggregation 은 사용자가 필요한 데이터 가공 작업을 직접 작성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 데이터를 가공하는 작업은 스테이지라는 단위 작업들로 구성되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터는 이렇게 작성된 스테이지들을 하나의 관처럼 흘러가면서 원하는 형태의 데이터로 변환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 스테이지를 파이프라인 이라고도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Aggregate 명령은 pipeline 과 options 두 개의 파라미터를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pipeline 인자는 2개의 스테이지를 가지는 배열로 구성되어 있다. 그리고 두 번째 options 인자는 allowDiskuUse 옵션이 true 로 설정되어 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;explain&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;실행 계획을 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;allowDiskUse&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;Aggregate 시 100MB 의 메모리까지만 사용가능한데 디스크도 활용한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;cursor&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;커서 배치 사이즈를 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%;&quot;&gt;maxTimeMS&lt;/td&gt;
&lt;td style=&quot;width: 80.3488%;&quot;&gt;실행 될 최대 시간을 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 문장에서 일반적으로 사용하는 GROUP BY 에 해당하는 Aggregate 를 살펴본다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 80.5815%;&quot;&gt;쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;SQL&lt;/td&gt;
&lt;td style=&quot;width: 80.5815%;&quot;&gt;SELECT name, COUNT(*) FROM users GROUP BY name;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4185%;&quot;&gt;Mongo&lt;/td&gt;
&lt;td style=&quot;width: 80.5815%;&quot;&gt;db.collection.aggregate( [ { $group : {_id: &quot;$name&quot; , counter : { $num : 1 } } } ] )&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3024%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 80.6976%;&quot;&gt;쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3024%;&quot;&gt;SQL&lt;/td&gt;
&lt;td style=&quot;width: 80.6976%;&quot;&gt;SELECT name, AVG(score) FROM users WHERE score &amp;gt; 50 GROUP BY name;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3024%;&quot;&gt;Mongo&lt;/td&gt;
&lt;td style=&quot;width: 80.6976%;&quot;&gt;db.collection.aggregate( [ { $match : { score : { $gt : 50 } } } , { $group : {_id: &quot;$name&quot; ,&amp;nbsp; avg : { $avg : &quot;$score&quot; } } } ] )&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;SQL&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;SELECT name, count(*), AVG(score) FROM users GROUP BY name;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Mongo&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;db.collection.aggregate( [ { $group : {_id: &quot;$name&quot; , counter : { $sum : 1 } , avg : { $avg : &quot;$score&quot; } } } ] )&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;SQL&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;SELECT COUNT(DISTINCT name) FROM users;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.186%;&quot;&gt;Mongo&lt;/td&gt;
&lt;td style=&quot;width: 80.814%;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;db.collection.aggregate( [ { $group : {_id: &quot;$name&quot; } } , { $group : {_id : 1 , cnt : { $sum : 1 } } } ] )&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서는 도큐먼트의 특정 필드 값이 NULL 또는 NOT NULL 일 수 있지만 필드 자체가 없을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ifNull 연산자를 이용해서 필드의 값이 NULL 인지 아닌지만 판단하며 필드 자체가 존재하지 않으면 TRUE 를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 ifNull 로만 체크하면 필드가 없는경우도 체크하고 싶은경우 결과가 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NULL 인지 아닌지만 구분해서 그룹하는&amp;nbsp; 방법과, 필드 존재 여부까지 체크하는 방법이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.8372%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 81.1628%;&quot;&gt;쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.8372%;&quot;&gt;NULL 체크&lt;/td&gt;
&lt;td style=&quot;width: 81.1628%;&quot;&gt;db.collection.aggregation ( [&lt;br /&gt;{&lt;br /&gt;&amp;nbsp; $project : { _id : 0, name:1, has_phone: { &quot;$cond&quot; : [ { &quot;$ifNull&quot; : [ &quot;$phone&quot;, false ] } , true , false ] } }&lt;br /&gt;&amp;nbsp; , { $group : { _id : &quot;$has_phone&quot; , count : { $sum : 1 } } &lt;br /&gt;}&lt;br /&gt;] )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.8372%;&quot;&gt;필드유무까지 체크&lt;/td&gt;
&lt;td style=&quot;width: 81.1628%;&quot;&gt;db.collection.aggregation ( [&lt;br /&gt;{&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; $project : {&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;_id : 0, name:1, has_phone: { $concat : [ $gt : [ &quot;$phone&quot;, null ]}, &quot;NOT-NULL&quot; , &quot;&quot; ] } , &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;$concat : [ $gt : [ &quot;$phone&quot;, null ]}, &quot;NULL&quot; , &quot;&quot; ] } , &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;$concat : [ $gt : [ &quot;$phone&quot;, undefined ]}, &quot;NOT-EXIST&quot; , &quot;&quot; ] } ] }&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp; , { $group : { _id : &quot;$has_phone&quot; , count : { $sum : 1 } }&lt;br /&gt;}&lt;br /&gt;] )&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Aggregation 파이프라인 스테이지(Pipeline)와 표현식&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;스테이지&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$project&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;입력으로 주어진 도큐먼트에서 필요한 필드만 선별해서 다음 스테이지로 넘겨주는 작업을 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$addFields&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;입력으로 주어진 도큐먼트에 새로운 필드를 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$replaceRoot&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;입력으로 주어진 도큐먼트에서 특정 필드를 최상위 도큐먼트로 만들고 나머지는 버림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$match&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;컬렉션 또는 직전 스테이지에서 넘어온 도큐먼트에서 조건에 일치하는 도큐먼트만 다음 스테이지로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$limit&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;입력으로 주어진 도큐먼트에서 처음 몇 건의 도큐먼트만 다음 스테이지로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$out&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;처리의 결과를 컬렉션으로 저장하거나 클라이언트로 직접 전달&lt;br /&gt;&lt;br /&gt;주의사항&lt;br /&gt;- 저장할 컬렉션이 존재하지 않는다면 컬렉션을 생성해서 데이터를 저장&lt;br /&gt;- 컬렉션이 있다면 기존 컬렉션의 데이터를 모두 삭제하고 대롭게 데이터를 저장 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$wind&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;입력 도큐먼트가 배열로 구성된 필드를 가지고 있으면 이를 여러 도큐먼트로 풀어서 다음 스테이지로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$group&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;입력으로 주어진 도큐먼트를 지정된 조건에 맞게 그룹핑해서 카운트나 합계 또는 평균 등의 계산을 처리&lt;br /&gt;- $sum : 숫자 타입의 필드 합계를 계산&lt;br /&gt;- $avg : 숫자 타입의 평균을 계산&lt;br /&gt;- $first : 각 그룹의 첫번째 값을 반환&lt;br /&gt;- $last : 각 그룹의 마지막 값을 반환&lt;br /&gt;- $max : 각 그룹의 최댓값을 반환&lt;br /&gt;- $min : 각 그룹의 최솟값을 반환&lt;br /&gt;- $push : 각 그룹에 속한 모든 도큐먼트의 필드 값을 배열로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$sample&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;주어진 입력 도큐먼트 중 임의의 몇 개의 도큐먼트만 샘플링해서 다음 스테이지로 전달&lt;br /&gt;&lt;br /&gt;다음 3가지 조건을 만족하는 경우에는 랜덤 커서를 이용해서 도큐먼트를 조회&lt;br /&gt;- $sample 이 파이프라인의 첫 스테이지로 사용된 경우&lt;br /&gt;- 샘플링하고자 하는 도큐먼트가 컬렉션의 전체 도큐먼트의 5% 미만인 경우&lt;br /&gt;- 컬렉션이 100개 이상의 도큐먼트를 가진 경우&lt;br /&gt;&lt;br /&gt;이 3가지 조건을 만족하지 못하면, $sample 스테이지는 입력된 도큐먼트에 랜덤값의 필드를 부여한 다음 그 필드를 기준으로 정렬을 수행하고 지정된 건수의 도큐먼트만 다음 스테이지로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$sort&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;주어진 입력 도큐먼트를 정렬해서 다음 스테이지로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$count&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;주어진 입력 도큐먼트의 개수를 세어서 다음 스테이지로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$lookup&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;주어진 입력 도큐먼트와 다른 컬렉션과 LEFT OUTER 조인을 실행하여 결과를 다음 스테이지로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.7209%;&quot;&gt;$facet&lt;/td&gt;
&lt;td style=&quot;width: 81.2791%;&quot;&gt;하나의 스테이지로 다양한 차원의 그룹핑 작업을 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;MongoDB Agggregation 에서는 일반적으로 사용되는 사칙연산 연산자를 그대로 사용할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;실제 사칙연산자 대신 지정된 연산자를 대체해서 사용해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;주요한 사칙연산 4가지의 명령어는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;사칙연산&lt;/td&gt;
&lt;td style=&quot;width: 80.5814%;&quot;&gt;명령어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;width: 80.5814%;&quot;&gt;$add&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;-&lt;/td&gt;
&lt;td style=&quot;width: 80.5814%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;width: 80.5814%;&quot;&gt;$multiply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.4186%;&quot;&gt;/&lt;/td&gt;
&lt;td style=&quot;width: 80.5814%;&quot;&gt;$divide&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-size: 1.25em;&quot;&gt;Aggregation 파이프라인 최적화&lt;/span&gt;&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 Aggregation 은 각 스테이지가 순차적으로 처리되며, 그 결과를 다음 스테이지로 전달하면서 사용자의 요청을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 각 스테이지의 배열 순서는 처리 성능에 많은 영향을 미친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 필요한 도큐먼트만 필터링하는 스테이지는 데이터를 그룹핑하는 스테이지보다 앞쪽에 위치해야 그룹핑해야 할 도큐먼트의 건수를 줄일 수 있고 Aggregation 의 성능을 높일 수 있다.&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버도 내부적으로 이런 형태의 기본적인 최적화 기능을 내장하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 최적화를 자동으로 처리할 수 있는지와 어떤 최적화가 불가능한지 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 Aggregation 최적화를 해준다고 하도라도 스테이지 구성에 따라 자동 최적화가 불가능할 수도 있고 매뉴얼에 명시된 대로 작동하지 않을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;$project 스테이지&lt;/td&gt;
&lt;td style=&quot;width: 82.7907%;&quot;&gt;
&lt;div id=&quot;__endic_crx__&quot; style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;&lt;span&gt;전체 도큐먼트에서 필요한 필드만 뽑아서 다음 스테이지로 전달하는 역할을 한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;하지만 MongoDB 서버는 파이프라인을 실행하기 전에 먼저 각 스테이지를 스캔해서 사용되는 필드를 먼저 확인하고 원본 도큐먼트에서 필요한 필드만 뽑아서 처리한다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;span&gt;그래서 단순히 다음 스테이지로 전달할 필드를 필터링해서 처리해야 할 도큐먼트의 크기를 줄이기 위한 용도라면 굳이 $project&amp;nbsp; 스테이지를 명시하지 않아도 된다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;기존 도큐먼트의 필드를 조합해서 새로운 서브 도큐먼트나 배열 필드 또는 가공된 값을 생성하는 경우가 아니라면 불필요하다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;스테이지 순서 최적화&lt;br /&gt;($match 와 $sort ,&lt;br /&gt;$project 와 $skip )&lt;/td&gt;
&lt;td style=&quot;width: 82.7907%;&quot;&gt;$sort 스테이지와 $match 스테이지가 순서대로 연결된 경우 먼저 $match 조건에 일치하는 도큐먼트만 필터링 한 다음 남은 도큐먼트만 정렬하도록 최적화한다.&lt;br /&gt;&lt;br /&gt;&lt;span&gt;$project 스테이지 뒤에 바로 $skip 스테이지가 사용되면 서버는 $skip 을 $project 스테이지 앞쪽으로 옮겨서 실행한다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;그래서 다음 스테이지에서 버려질 도큐먼트를 불필요하게 가공하는 일을 줄여준다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;스테이지 결합&lt;/td&gt;
&lt;td style=&quot;width: 82.7907%;&quot;&gt;MongoDB 서버는 처리 성능을 향상시키기 위해 파이프라인에서 2개 이상의 스테이지를 하나의 스테이지로 결합해 처리하기도 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;인덱스 사용&lt;/td&gt;
&lt;td style=&quot;width: 82.7907%;&quot;&gt;각 스테이지도 가능하다면 인덱스를 활용할 수 있는 형태로 최적화된다.&lt;br /&gt;&lt;br /&gt;하지만 각 스테이지는 이전 스테이지의 가공된 결과를 전달받기 때문에 파이프라인의 앞쪽 스테이지 한두 개만 인덱스를 활용해서 최적화할 수 있는 경우가 대부분이다.&lt;br /&gt;&lt;br /&gt;그리고 Aggregation 처리과정에서 한번 데이터를 가공하면 그 데이터는 컬렉션이 아니라 메모리나 디스크의 임시 버퍼 공간에 저장되므로 인덱스를 사용할 수 없게 된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.2093%;&quot;&gt;메모리 사용&lt;/td&gt;
&lt;td style=&quot;width: 82.7907%;&quot;&gt;Aggregate 명령은 내부적으로 그룹핑 작업을 처리하기 위해 메모리를 사용하는데 100MB 로 제한되어 있다.&lt;br /&gt;쿼리 결과의 정렬 작업에 사용되는 메모리 공간과 그룹핑 작업에 사용되는 메모리 공간의 크기 제한은 서로 다르게 적용된다.&lt;br /&gt;&lt;br /&gt;만약 메모리 공간이 부족하다는 에러가 발생하면 allowDiskUse:true 를 사용하여 실행하는 것이 좋다.&lt;br /&gt;이 옵션을 사용하면 데이터 디렉터리 하위에 _tmp 라는 디렉터리를 만들어 임시 가공용 데이터 파일을 저장한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;$lookup 과 $graphLookup&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$lookup 과 $graphLookup 스테이지는 파이프라인에서 사용할 수 있는 조인 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스테이지는 FIND 쿼리에서는 사용할 수 없고 Aggregation 에서만 사용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션이 샤딩되어 있지 않다면 스테이지를 조인의 용도로 사용하는 데 있어 어떤 제약사항도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 샤딩된 클러스터에서는 조인으로 연결되는 컬렉션이 샤딩되지 않은 경우에만 스테이지를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 로컬 컬렉션(드라이빙 테이블)은 샤딩 여부와 관계없지만, 외래 컬렉션(드리븐 테이블)은 샤딩되지 않은 컬렉션만 사용할 수 있다. 문제는 일반적으로 샤딩되지 않은 컬렉션은 샤드 중에서 특정 샤드(프라이머리)에만 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 때는 프라이머리 샤드의 고부하를 반드시 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Aggregation 쿼리의 $lookup 스테이지는 아우터 조인을 수행할 컬렉션과 그 컬렉션의 조인필드 그리고 Aggregation 컬렉션의 조인필드가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$graphLookup 스테이지는 주로 RDBMS 의 재귀 커리 형태의 셀프 조인을 처리할 수 있는 기능이다. Aggregation 쿼리에서만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 조인이다 보니 무한 반복될 수 있어 maxDepth 옵션을 설정해 최대 실행 제한을 두면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;$facet&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 그룹을 선택하면 다시 하위의 다른 기준으로 상품을 그룹핑해서 보여주는 기능을 Facet Query 라고 하는데, 일반적으로 RDBMS 에서는 하나의 쿼리로 다양한 기준의 그룹핑 쿼리를 수행할 수 없지만 MongoDB 에서는 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Facet Aggregation 쿼리는 하나의 쿼리에 여러 개의 파이프라인을 나열해서 한 번에 여러 기준의 그룹핑 기능을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 outputField 에는 기준별 그룹핑 결과를 담을 필드명을 명시하는데, 배열로 주어진 스테이지를 서브 파이프라인이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Facet Aggregation 쿼리의 각 서브 파이프라인은 다음 3개 중 하나의 Facet 서브 스테이지를 반드시 가져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 서브 스테이지는 서로의 결과를 다른 서브 스테이지와 공유하거나 참조할 수 없으며, 각 서브 스테이지는 Aggregation 쿼리에서 사용되던 다른 스테이지를 같이 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- $bucket : 사용자가 지정한 범위별로 특정 필드 값의 건수나 합계 산출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- $bucketAuto : 특정 필드값을 사용자가 지정한 개수만큼 자동으로 범위를 나누어 그룹핑하고 건수나 함계를 산출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- $sortByCount : 특정 필드나 표현식의 값으로 그룹을 만들어서 그룹별 건수를 산출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Facet 쿼리는 내부적으로 몇 개의 서브 스테이지를 가지고 있는지와 관계없이 컬렉션의 도큐먼트는 1번만 읽어서 처리된다. 그래서 서브 스테이지의 개수가 많아져도 컬렉션의 도큐먼트를 여러 번 참조하지는 않기 때문에 효율적으로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fulltext Search&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 처럼 MongoDB 도 전문 검색을 위해 전문 검색 인덱스와 전무 검색을 위한 쿼리 문법을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스를 생성할 때는 인덱스를 생성할 필드이름 뒤에 전문 검색 인덱스를 의미하는 키워드인 text 를 입력하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 쿼리를 작성할 때는 $text 연산자를 이용해서 검색어를 입력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스는 컬렉션당 하나만 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;불리언 검색&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 기능에서 많이 사용되는 기능은 검색어에 대한 불리언 연산과 필드별 중요도 설정일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불리언 연산은 &quot;-&quot; 부호를 이용해서 검색 대상에서 제외할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특별히 불리언 연산자 없이 검색을 실행하면 나열된 모든 단어의 OR 연산을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 특정 단어 앞에 - 부호를 넣어 검색하면 다른 단어는 포함하지만 해당 단어는 포함하지 않는 결과를 검색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부정 검색을 위해서는 - 부호앞에 반드시 공백을 추가해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 OR 연산이 아니라 AND 연산자을 수행하고자 한다면 문장 검색을 실행한다. 문장 검색을 위해서는 &quot; 로 감싸주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;중요도 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 쿼리를 수행할 때 일치하는 검색어가 어떤 필드에 저장된 값인지에 따라서 중요도를 설정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요도가 높을수록 검색 일치 영향도가 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 중요도에 따라 결과가 다르게 나오진 않을텐데,,, 정렬할때인가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한글과 전문 검색&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색을 수행하기 위한 쿼리의 기능도 중요하지만 전문 인덱스를 어떻게 구성하는지도 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트가 저장될 때 각 필드의 값을 분석해서 전문 ㅇ니덱스를 구성하는 부분을 일반적으로 전문 파서라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 인덱스는 주여 언어에 대해 형태소 분석 작업을 거쳐 각 단어의 원형을 인덱스에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서 한글을 위한 전문 검색 기능은 n-Gram 형태의 전문 파서가 도입되지 않는 이상 어려울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 n-Gram 은 MongoDB 서버가 제공하고 있는 구분자 기준의 전문 파서보다 훨씬 많은 인덱스 키를 만들어내기 때문에 성능적인 이슈가 문제될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 MongoDB 에 한글을 위한 전문 검색 기능이 추가되기 전에 한글 전문 검색 기능이 필요하다면 전문 검색 필드의 값을 배열 타입의 값으로 변환해서 정규 표현식 검색을 적용하는 것도 방법일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규 표현식은 반드시 프리픽스 일치형태를 사용해야 인덱스를 정상적으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 정규 표현식 조건은 ^ 로 시작하고 검색어 다음에는 아무런 정규 표현식이 없어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 ^aa 는 RDMBS 의 LIKE aa% 와 같음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한글과 n-Gram 인덱스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이유로 MongoDB 서버가 가진 형태소 분석 기반의 전문 검색 기능은 한글에 적합하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여기에서 MongoDB 서버의 코드에 한글을 위한 n-Gram 전문 인덱스를 위한 기능을 직접 추가해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스를 관리하기 위해 구분자와 토크나이저가 적절히 입력된 문자열에서 단위문자열을 분리하는 과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분리된 단어 문자열을 전문 인덱스에 저장하고 사용자가 검색어를 입력하면 그 검색어 또한 같은 과정을 거쳐 단위 문자열로 분리되고 검색을 수행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n-Gram 은 2글자씩 잘라서 인덱싱하는 bi-Gram 토크나이저를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 한 글자로 구성된 검색어는 어떤 경우에도 일치된 결과를 가져오지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n-Gram 인덱스의 특성상 토크나이저가 사용하는 문자열의 길이보다 짧은 길이의 문자열은 검색할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 1글자 키워드로 검색할 수 있게 하고자 한다면 NGRAM_TOKEN_SIZE 를 1로 변경해서 다시 컴파일 하면되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 사이즈가 줄어들수록 인덱스의 크기는 더 작아질 수 있지만 검색어에 일치하는 결과가 많아져서 내부적인 필터링 시간이 오래 걸리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전문 인덱스 성능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 인덱스는 입력된 도큐먼트에서 검색 대상이 되는 필드의 값들을 파싱하고 형태소 분석을 거친 다음 유니크한 키워드만 모아서 인덱스 엔트리로 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 전문 인덱스는 주로 크기가 큰 도큐먼트를 파싱하여 인덱싱하므로 인덱스가 매우 커지고 처리 시간이 많이 소요될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버를 일반적인 서비스용 데이터베이스로 사용하면서 보조적인 기능으로 전문 검색 기능을 사용하는 경우라면 이런 대용량의 전문 검색 기능은 피하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 전문 검색 인덱스를 검색한 결과는 어떤 형태로든지 정렬이 보장되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 전문 검색을 수행하면서 정렬이 필요한 경우 별도의 정렬 처리를 수행한 후에야 결과를 반환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합인덱스에서 인덱스 필드를 전문 인덱스 앞쪽에 위치시켜 인덱스를 생성하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 쿼리에서 반드시 선행 필드의 조건을 포함해야만 전문 인덱스를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 수행하지 않으면 성능이 느리게 결과가 나오는 것이 아니라 쿼리 자체가 실패하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스키마 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 보다 자유롭지만 MongoDB 에서도 스키마 변경 작업은 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 컬렉션의 샤딩이나 보조 인덱스 생성 및 삭제 작업이 있기 때문에 DDL 작업은 많은 주의를 필요로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 데이터베이스를 생성하는 명령은 별도로 제공되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 데이터를 별도로 생성하지 않아도 되고 존재하지 않는 데이터베이스에 대해 권한을 부여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게 생성된 데이터베이스는 샤딩 관련 옵션이 모두 꺼진 상태이므로 별도로 샤딩을 활성화하거나 권한을 부여한느 작업이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터베이스 생성 및 삭제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 생성은 지원하지 않고 컬렉션을 생성하거나 도큐먼트를 INSERT 하면 자동으로 데이터베이스가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제는 명시적으로 dropDatabase 명령을 실행하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터베이스 복사&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 데이터베이스를 통째로 복사할 수 있는 CopyDatabase 명령을 지웒나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 서버에서 뿐 아니라 다른 서버의 데이터베이스도 복사할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령은 fromdb 와 todb 의 데이터베이스와 컬렉션에 별도의 잠금을 획득하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 데이터베이스가 복사되는 도중 변경될 수 있는데, 복사 도중 변경되는 데이터는 모두 무시하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 컬렉션의 데이터를 모두 복사하고 마지막에 인덱스를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;todb 에서 마지막 인덱스를 생성하는 과정은 백그라운드가 아닌 포그라운드로 진행되기 때문에 이 때는 todb 의 데이터를 변경하거나 조회할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;샤딩 활성화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 최초 생성하면 샤딩이 활성화되어있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩 활성화를 하고싶으면 데이터베이스에 enableSharding 으로 샤딩을 적용해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터베이스 컴팩션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 컬렉션에서 대량의 데이터를 삭제하는 경우에 WiredTiger 스토리지 엔진을 사용하는 컬렉션의 도큐먼트가 디스크 파일의 각 위치에 골고루 분포돼 있으면 용량이 자동으로 줄지 않는다. 즉 데이터 파일의 프레그멘테이션이 매우 많아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 주기적인 데이터 삭제로 인해 데이터파일의 크기가 커진 경우에는 데이터베이스 또는 컬렉션 단위로 컴팩션을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 repairDatabase 명령어로 진행하며 글로벌 쓰기 잠금을 획득하고, 데이터베이스의 컬렉션을 임시 공간에 복사하는 방식으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 명령을 실행하는 동안에는 다른 처리가 실행되지 못하고 모두 대기하게 되므로 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령은 세컨드리에서도 실행할 수 있어서, 세컨더리 작업 후 마스터를 스위칭 한 후 새로운 세컨더리에서 작업하는 형태로 가능하다. 이 작업은 데이터베이스 크기가 크다면 오래걸리므로 세컨드리 멤버에서 repairDatabaes 를 실행하고 그 이후 컴팩션된 데이터 파일을 그대로 다른 세컨드리 멤버로 복사하여 사용하면 빠르게 컴팩션 효과를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버에서&amp;nbsp; 진행할 때도 잠금을 걸고 진행되기 때문에 OpLog 를 처리하지 못하고 잠금 대기를 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로인해 복제 지연이 심해지고 멤버의 상태가 SECONDARY 가 아니라 RECOVERY 상태로 전환될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬렉션 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 컬렉션은 RDBMS 의 테이블과는 달리 도큐먼트가 저장되는 시점에 자동으로 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 목록은 show tables 명령으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컬렉션 생성과 삭제 그리고 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 명시적으로 컬렉션을 생성하고자 할 때는 createCollection 명령을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션을 자동으로 생성할때는 옵션이 모두 디폴트 값이므로, 각 옵션이 디폴트 값이 아닌 컬렉션을 생성하고자 할 때는 명시적으로 createCollection 명령을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 삭제는 drop 명령을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컬렉션 복사 및 이름 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cloneCollectionAsCapped 명령은 복사하는 대상 컬렉션을 일반 컬렉션이 아니라 Capped 컬렉션으로 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 하나의 컬렉션의 데이터를 복사할 수 있는 직접적인 기능은 제공하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 export / import 나 find / insert 로 우회하여 데이터를 복사해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Aggregation 을 이용하면 원하는 도큐먼트만 선별하여 새로운 컬렉션으로 복사할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 이미 컬렉션이 존재하면 기존 컬렉션의 모든 도큐먼트를 삭제하고 새롭게 도큐먼트를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 기존 컬렉션이 가지고 있던 인덱스는 그대로 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서 컬렉션의 이름을 변경하는 작업은 renameCollection 으로 간단하게 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 데이터베이스 내에서 컬렉션의 이름만 변경하는 작업은 스토리지 엔진의 메타 정보만 변경하면 되므로 매우 빠르게 처리되지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 데이터베이스인 경우 새로운 컬렉션을 생성하고 원본 컬렉션에서 도큐먼트를 한 건씩 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복사 작업이 완료되면 원본 컬렉션을 삭제하고 컬렉션 이름 변경 작업을 완료하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 과정에서 글로벌 쓰기 잠금을 걸기 때문에 주의해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컬렉션 상태 및 메타 정보&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에 관련된 상세 정보는 stats 명령을 이용해 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령은 데이터 파일의 크기나 도큐먼트의 크기와 같은 기본적인 정보를 먼저 표시하고 그 밑에 스토리지 엔진이 가진 상세한 메타 정보를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 stats 옵션으로는 아래가 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;creationString&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;WiredTiger 스토리지 엔진이 이 컬렉션을 생성할 때 사용했던 옵션의 상세한 내용을 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;type&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;file ?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;uri&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;이 컬렉션이 사용 중인 데이터 파일의 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;block-manager&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;블록 매니저가 수행했던 처리 내용을 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;btree&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;도큐먼트를 저장히기 위한 B-Tree 의 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;cache&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;캐시가 운영체제의 데이터 파일로부터 읽어드린 페이지의 개수나 다시 데이터 파일로 기록한 횟수 메모리의 캐시에서 참조된 횟수를 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;cache-walk&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;각 컬렉션의 브랜치 노드나 리프 노드가 얼마나 상주하고 있는지&lt;br /&gt;페이지가 얼마나 생성되고 스플릿됐고 데이터 파일로 기록됐는지를 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;compression&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;압축된 페이지가 얼마나 데이터 파일로부터 읽혔는지 페이지 크기로 인해 압축이 스킵됐는지를 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;cursor&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;커서 오퍼레이션이 얼마나 수행됐는지 정보를 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;reconciliation&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;변경 내용을 원본 이미지에 병합하는 과정동안 발생하는 작업 내용을 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스에 대해 stats 도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컬렉션의 프레그멘테이션과 컴팩션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 파일에서 사용되지 않는 빈 공간이 많아져 디스크 공간이 낭비되는 경우가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 프레그멘테이션은 단순히 디스크 공간만 낭비하는 것이 아니라 ㅔㅁ모리 효율까지 떨어트린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 데이터 파일에서 사용되지 않는 빈 공간을 최소화하기 위해 최적화 기능을 제공하는 이를 일반적으로 컴팩션이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴팩션은 다음 2가지 관점에서 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 파일의 크기가 커져서 디스크의 여유 공간이 부족한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 장착된 메모리보다 데이터 파일이 커져서 쿼리 실행 시 디스크 읽기가 많이 발생하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진을 사용하는 경우 일반적으로 컴팩션 필요성이 많지 않으나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량으로 컬렉션 도큐먼트가 삭제되는 경우에는 빈 공간이 자동으로 운영 체제로 반납되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 샤드가 새로 추가되어 많은 청크가 다른 샤드로 이동할 때에도 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compact 명령은 데이터베이스의 쓰기 잠금을 획득하므로 컴팩션이 실행되는 동안 같은 데이터베이스의 컬렉션에 대한 쿼리는 처리할 수 없다. 그래서 기본적으로 프라이머리 멤버에서는 실행할 수 없지만 실행하고자 한다면 force 옵션을 true 로 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컬렉션 샤딩&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩은 컬렉션 단위로 적용된다. 즉 하나의 데이터베이스에 여러 개의 컬렉션이 있다고 해서 모든 컬렉션을 동일한 샤드 키로 샤딩해야 하는 것은 아니다. 또한 하나의 데이터베이스에서 일부 컬렉션은 샤딩돼 있더라도 일부는 샤딩되지 않은 상태로 프라이머리 샤드에만 저장할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 샤딩된 컬렉션은 다시 샤드 키를 변경해서 샤딩할 수 없기 때문에 샤드 키를 선정하는 작업은 신중해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩을 할 것인지 여부는 '쿼리 빈도나 패턴', '데이터 크기' 의 두 가지 측면을 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 쿼리를 샤딩하면 쿼리의 조건이 샤드 키를 가지고 있는지 아닌지에 따라 특정 샤드로 전송하거나 모든 샤드로 전송하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 쿼리를 타겟쿼리, 브로드캐스트 쿼리라고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 샤딩에서 해시샤딩과 레인지샤딩이 있고,&amp;nbsp; 레인지 샤딩은 청크를 미리 스플릿해두는 것이 자동으로 처리되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 샤딩은 서버가 MD5 해시 함수를 이용해 샤드 키 값의 해시 키를 생성하기 때ㅔ문에 이미 해시 함수의 결과 값에 대한 범위가 결정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 서버가 지정된 개수만큼 청크를 미리 스플릿하는 것이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shardCollection 명령어에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;numInitialChunks 옵션은 해시 샤딩인 경우메나 사용할 수 있고, 샤딩을 적용함과 동시에 미리 청크를 스플릿해둘 수 있는데 몇 개의 청크를 미리 생성할 것인지 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권장 청크 개수의 산술식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 개수 = 도쿠먼트 건수 * 도큐먼트 크기 / 64MB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 샤딩과 비교했을 때 레인지 샤딩은 샤드 간 데이터 불균형이 심해질 수 있으며 별도로 샤드를 추가하고 제거하는 작업이 아니어도 샤드 간 청크 이동이 심해질 수 있다. 그래서 가능하면 레인지 샤딩보다는 해시 샤딩을 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 데이터를 가진 컬렉션을 샤딩하는 방법도 빈 컬렉션과 동일하게 shardCollection 명령을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이미 도큐먼트를 가진 컬렉션에 대해 해시 샤딩을 적용하는 경우는 numInitialChunks 옵션을 이용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션이 이미 도큐먼트를 가지고 있을 때는 레인지 해시 샤딩 모두 컬렉션을 풀 스캔해서 스플릿하는 방법만 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 가진 컬렉션을 샤딩할 때는 아래와 같은 과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 스플릿 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 밸런싱&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shardCollection 명령은 이 두개의 과정 중 첫 번째 과정인 청크의 스플릿만 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 하나의 샤드에만 집중된 청크를 전체 샤드로 분산해 밸런싱하는 작업은 샤딩이 적용된 이후 천천히 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 데이터를 가진 컬렉션을 샤딩할 때, 컬렉션에 저장된 도큐먼트를 일정 크기 기준으로 청크를 스플릿할 지점을 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 해당 컬렉션을 풀 스캔하고 이 결과에 대해 split 명령을 실행함으로써 컨피그 서버에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컨피그 서버에 청크 정보가 저장되면 비로소 샤딩이 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 상태는 컬렉션이 샤딩된 것으로 표시되고 청크는 스플릿 됐지만 실제 컬렉션의 모든 도큐먼트는 하나의 샤드 서버에만 저장돼 있기 때문에 샤딩을 적용하기 전과 아무런 차이가 없는 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 부하를 분산하려면 청크를 다른 샤드로 분산해야 하는데 이렇게 청크가 이동되는 과정은 상당한 시간이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 RDBMS 와 동일한 기능의 인덱스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 특성으로 인해 MongoDB 서버는 퀄 ㅣ튜닝 및 실행 계획 분석이 동일하게 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 생성 및 삭제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 백그라운드와 포그라운드 방식의 인덱스 생성 방식을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성 시 background 옵션을 true 로 설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이는 인덱스를 생성하는 도중 다른 커렉션의 쿼리를 실행할 수 있는지 여부이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포그라운드는 다른 커넥션의 쿼리 실행을 모두 막고 변경되지 않는 상태에서 빌드하기 때문에 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백그라운드는 내부적으로 컬렉션이 소속된 데이터베이스에 잠금을 걸고 컬렉션의 부분 데이터를 읽어 인덱스를 빌드하는 작업을 진행한다. 그리고 잠금을 해제하여 다른 컨넥션들이 쿼리를 실행할 수 있도록 여유 시간을 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 인덱스 생성 시간이 길어지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 삭제하는 작업은 메타 정보만 변경하고 인덱스와 연관된 데이터 파일만 삭제하면 되므로 매우 빠르게 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 삭제는 dropIndex 명령으로 삭제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 목록 조회&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션이 가진 인덱스는 getIndexes 명령이나 getIndexKeys 명령으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getIndexes 는 상세한 정보를 표시해 주지만, 어떤 필드로 구성되었는지 간략한 정보만 보고자 할때는 getIndexKeys 명령이 편한다.&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/662</guid>
      <comments>https://mozi.tistory.com/662#entry662comment</comments>
      <pubDate>Sun, 6 Oct 2024 19:52:39 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 데이터 모델링</title>
      <link>https://mozi.tistory.com/661</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buSNCJ/btsJE1URoZr/6ajQ8EeiVGrlRehggDrm4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buSNCJ/btsJE1URoZr/6ajQ8EeiVGrlRehggDrm4K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buSNCJ/btsJE1URoZr/6ajQ8EeiVGrlRehggDrm4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuSNCJ%2FbtsJE1URoZr%2F6ajQ8EeiVGrlRehggDrm4K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터베이스와 컬렉션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서도 다른 RDBMS 와 동일하게 하나의 인스턴스에 여러 개의 데이터베이스를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 데이터베이스는 다시 여러 개의 테이블을 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 NoSQL 을 지향하기에 테이블이라는 이름보다는 컬렉션이라는 이름을 공식적으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네임스페이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 데이터베이스 이름과 컬렉션의 이름 조합을 네임스페이스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스에서 데이터베이스와 컬렉션 이름 사이는 반드시 . 으로 구분해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 네임스페이스는 컬렉션의 네임스페이스에 추가로 인덱스의 이름을 붙인 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하면 '데이터베이스.컬렉션.$인덱스' 와 같이 부여된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스가 중요한 이유는 MongoDB 내부적으로 데이터베이스 이름이나 컬렉션 이름이 단독으로 사용되지 않고 항상 네임스페이스로 각 객체가 관리 참조되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진에서는 데이터베이스별로 네임스페이스 목록을 저장하는 네임스페이스 파일(*.ns)이 생성되고 이 파일의 최대 크기는 2GB 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진에서는 별도의 네임스페이스 파일을 사용하지 않기 때문에 WiredTiger 스토리지 엔진을 사용하는 MongoDB 에서는 MMAPv1 스토리지 엔진과 달리 최대로 생성할 수 있는 컬렉션이나 인덱스의 개수에 제약이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스는 주로 서비스나 데이터의 그룹을 만들기 위해 사용하는 물리적인 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 데이터베이스는 동시 처리 성능과 연관된다. 다만 2.8 버전 이상에서는 동시처리가 도입되어 데이터베이스를 억지로 분리할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 데이터베이스를 짧은 혹은 긴 시간동안 잠금하는 명령어이기 때문에 주의해야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;장시간&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;db.collection.createIndex()&lt;br /&gt;db.runCommand({ reIndex: &quot;collection&quot; })&lt;br /&gt;db.runCommand({ compact: &quot;collection&quot; })&lt;br /&gt;db.createCollection() -&amp;gt; 대용량의 Capped Collection 생성 시&lt;br /&gt;db.collection.validate()&lt;br /&gt;db.repairDatabase()&lt;br /&gt;db.copyDatabase()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;단시간&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;db.collection.dropIndex()&lt;br /&gt;db.getLastError()&lt;br /&gt;db.isMaster()&lt;br /&gt;rs.status()&lt;br /&gt;db.serverStatus()&lt;br /&gt;db.auth()&lt;br /&gt;db.addUser()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 너무 잘게 분리하면 관리의 편의성을 떨어뜨리고, 분리된 데이터베이스 때문에 컬렉션의 개수가 늘어난다면 MongoDB 컨피그 서버가 관리해야 할 메타 정보가 그만큼 늘어나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬렉션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 에서 주로 테이블이라 부르는 객체를 MongoDB 는 컬렉션이라 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 매뉴얼에서 조인을 지원하지 않기 때문에 컬렉션은 가능하면 많은 데이터를 내장할 것을 권장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이는 모델링적인 측면에서 맞는 이야기일지 모르나 성능적인 측면에서는 그렇지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 많은 데이터를 하나의 도큐먼트에 내장할수록 도큐먼트 하나하나의 크기가 커지고, 그로 인해 더 많은 디스크 읽기 오퍼레이션이 필요하며, 같은 쿼리를 위해 더 많은 데이터를 읽었기 때문에 메모리의 캐시 효율이 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더군다나 일반적으로 컬렉션에서 읽은 데이터를 한 번에 가져가는 형태의 프로그램은 네트워크 사용량도 많이 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 내부적으로 이미 샤딩 기능을 가지고 있기 떄문에 컬렉션 단위의 샤딩은 별도로 크게 고려하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 정기적으로 대량의 데이터를 삭제해야 하는 요건이 있다면, 삭제 단위로 컬렉션을 분리하는 방법이 좋은 선택이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 하나의 컬렉션에 저장되는 도큐먼트들의 액세스 패턴이 많이 다를 때에도 컬렉션을 물리적으로 분리하고 자주 읽히는 데이터 위주로 메모리 캐시를 활용하도록 유도하면 성능상 이점을 기대할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진을 사용하는 컬렉션에서 청크 이동이 발생하는 경우 원본 청크가 저장된 샤드에서는 도큐먼트 삭제 작업이 진행되는데, 이 때 엔진 특성상 데이터 파일의 용량이 오히려 증가할 수 있다.&amp;nbsp; (왜?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 컬렉션의 설계에서 가장 중요한 것은 샤드 키의 선정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키를 잘못 선정하면 데이터 분산 효과를 무효로 만들게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰는 복잡한 형태의 데이터 가공 로직을 캡슐화해서 사용자의 접근 용이성을 향상한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블의 일부 데이터에 대해서만 접근 권한을 허용하여 보안을 강화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 뷰는 일단 생성되면 show collections 명령으로 기존 컬렉션과 동일하게 목록을 확인할 수 있고 FIND 나 Aggregation 명령을 사용해서 데이터를 조회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰를 초기 생성할때는 createView 명령을 사용하지만 한번 생성되면 컬렉션과 같이 취급하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰를 삭제할 때도 컬렉션을 삭제할 때처럼 drop 명령을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰에 대한 메타 정보는 system.views 컬렉션에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 뷰에서 주의해야 할 사항이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 뷰 쿼리는 항상 Aggregation 으로 처리되므로 FIND 에서 사용하는 조건에 일치하는 인덱스가 있어야 상대적으로 빠른 결과를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 중첩된 뷰는 중첩된 Aggregation 쿼리가 실행되는 것과 동일하기 때문에 외부의 뷰 쿼리는 인덱스가 없는 상태의 컬렉션에 대해 쿼리를 실행하는 것과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MongoDB 의 뷰는 구체화된 데이터를 별도의 저장소에 저장하지 않기 때문에 핫아 쿼리가 실행될 때마다 뷰를 생성할 때 사용했던 가공 작업이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰를 생성할 때 3가지 인자가 필요한데, 첫 번째 인자는 생성될 뷰의 이름, 두 번째 인자는 생성될 뷰가 참조할 컬렉션의 이름, 세 번째는 Aggregation 명령에서 사용하는 파이프라인을 명시하면 된다. 이 파이프라인으로 원본 컬렉션에서 조회할 도큐먼트를 선별하거나 도큐먼트들이 가져올 필드들을 가공하는 작업을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩된 클러스터 환경의 MongoDB 서버에서 뷰의 샤딩 여부는 뷰가 참조하는 컬렉션이 샤딩된 컬렉션인지 아닌지에 의존적이다. 즉 뷰가 참조하는 컬렉션이 샤딩된 컬렉션이면 이 컬렉션을 기반으로 하는 뷰도 동일하게 샤딩된 뷰로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BSON 도큐먼트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Binary JSON 의 약자로 JSON 형태의 도큐먼트를 바이너리 포맷으로 인코딩한 도큐먼트를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BSON 은 JSON 과 비교했을 때 다음과 같은 장점이 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.8372%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 86.1628%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.8372%;&quot;&gt;Lightweight&lt;/td&gt;
&lt;td style=&quot;width: 86.1628%;&quot;&gt;BSON 은 각 필드의 값을 단순히 문자열만으로 저장하는 것이 아니라, 정수와 부동 소수점 날짜 등과 같이 이진데이터 타입을 이용해서 데이터를 저장한다.&lt;br /&gt;&lt;br /&gt;따라서 저장공간을 절약하여 가볍고 효율적으로 처리할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.8372%;&quot;&gt;Traversable&lt;/td&gt;
&lt;td style=&quot;width: 86.1628%;&quot;&gt;BSON 도큐먼트의 각 필드는 항상 필드 값의 데이터 타입과 필드 값의 길이가 먼저 저장돼 있기 때문에 복잡한 파싱 처리 과정 없이 불필요한 필드는 건너뛰고 필요한 필드만 빠르게 찾아갈 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.8372%;&quot;&gt;Efficient&lt;/td&gt;
&lt;td style=&quot;width: 86.1628%;&quot;&gt;BSON 은 기본 데이터 타입으로 C 언어의 원시타입을 사용하고 있기 때문에 어떤 개발 언어에서도 매우 빠르게 인코딩 및 디코딩 처리를 할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BSON 은 아래 4종류의 기본 데이터 타입을 사용한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.3023%;&quot;&gt;타입&lt;/td&gt;
&lt;td style=&quot;width: 85.6977%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.3023%;&quot;&gt;BYTE&lt;/td&gt;
&lt;td style=&quot;width: 85.6977%;&quot;&gt;일반적인 문자열 데이터를 저장하기 위한 저장공간으로 BSON 에서는 주로 바이트의 배열로 사용한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.3023%;&quot;&gt;INT32&lt;/td&gt;
&lt;td style=&quot;width: 85.6977%;&quot;&gt;32비트 부호를 가지는 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.3023%;&quot;&gt;INT64&lt;/td&gt;
&lt;td style=&quot;width: 85.6977%;&quot;&gt;64비트 부호를 가지는 정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.3023%;&quot;&gt;DOUBLE&lt;/td&gt;
&lt;td style=&quot;width: 85.6977%;&quot;&gt;8바이트 부동 소수점&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BSON 도큐먼트는 이 4가지 기본 타입을 이용해서 만들어지는 파생된 Boolean 이나 String , Timestamp 등의 데이터 타입을 사용할 수 있도록 지원한다. 그뿐만 아니라 BSON 도큐먼트는 배열이나 서브-도큐먼트와 같은 또 다른 도큐먼트를 중첩해서 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BSON 도큐먼트의 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BSON 도큐먼트는 4바이트 정수로 시작하고, 항상 도큐먼트의 마지막은 0x00 으로 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BSON 도큐먼트의 시작 4바이트는 전체 도큐먼트의 크기를 저장하는데, 4바이트 정수이기 때문에 BSON 도큐먼트의 전체 크기에 대한 제한은 2^(32-1) 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하지만 MongoDB 에서는 BSON 도큐먼트의 최대 크기를 16MB 로 제한하고 있다. 전체 크기를 제한한 이유는 너무 큰 도큐먼트로 인한 성능 저하를 막기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BSON 도큐먼트의 실제 데이터 영역은 각 엘리먼트 단위로 '데이터 타입' 과 '엘리먼트 이름', '값' 순서로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘리먼트 이름은 NULL(0x00) 으로 끝나는 문자열이며 '값' 은 '데이터 타입' 에 따라서 저장되는 포맷이 조금씩 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BSON 도큐먼트는 반드시 필드를 하나씩 읽고 지나가야만 원하는 엘리먼트를 읽을 수 있으며, 또 하나는 EMBED_DOCUMENT(서브 도큐먼트, ARRAY) 는 제일 앞쪽에 길이가 저장되므로 불필요하면 한번에 건너뛸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제한사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mongo 셀이나 프로그램 언어를 이용해서 MongoDB 와 통신하는 경우 저장하고자 하는 도큐먼트 또는 검색 조건을 표현하는 도큐먼트는 JSON 으로 표시되는 경우가 많다. 하지만 드라이버 내부적으로 모두 JSON 을 BSON 으로 변환해서 MongoDB 서버와 통신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 에서 사용되는 모든 JSON 은 BSON 포맷과 호환되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 특성은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 도큐먼트는 반드시&amp;nbsp; { 로 시작해서 } 로 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도큐먼트의 모든 원소는 반드시 키와 값의 쌍으로 구성되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 중첩된 도큐먼트의 깊이는 100레벨까지 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도큐먼트의 전체 크기는 16MB 까지만 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 최종적으로 데이터를 BSON 으로 인코딩해서 디스크에 저장하므로 결국 BSON 이 지원하는 데이터 타입만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 사용되는 데이터 타입에 대해 살펴본다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;데이터 타입&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;ObjectId&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;12바이트의 Binary Data 타입을 원시타입으로 사용하는 데이터 타입이다.&lt;br /&gt;_id 필드의 값으로 자주 사용된다.&lt;br /&gt;&lt;br /&gt;ObjectId 는 멀티쓰레드나 분산 시스템에서도 고유한 값을 보장해주도록 설계되었기 때문에 일반적으로 샤딩이 적용된 MongoDB 에서 유니크한 값을 생성하는 목적으로 활용한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;Integer &amp;amp;&lt;br /&gt;Double&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;정수형 타입은 저장 공간의 크기에 따라 32비트와 64비트 정수로 나뉜다.&lt;br /&gt;MongoDB 는 부호없는 정수 타입을 별도로 지원하지 않는다.&lt;br /&gt;&lt;br /&gt;MongoDB 에서 지원하는 Numeric 타입으로 Double 과 32비트 64비트 정수가 있는데, 이들을 비교할 때 Double 타입으로 전환된 후에 비교된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;Decimal&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;Decimal 타입을 이용해 고정 소수점 데이터를 저장할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;String&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;MongoDB 의 문자열은 UTF-8 문자셋을 사용한다.&lt;br /&gt;만약 응용 프로그램이 UTF-8 문자열을 사용하지 않는경우 MongoDB 드라이버가 이를 UTF-8 로 전환한 후 BSON 도큐먼트를 생성한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;Timestamp&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;8바이트 저장공간으로, 처음 4바이트는 유닉스 타임스탬프이며 뒤 4바이트는 자동으로 증가하는 시퀀스 값을 저장한다.&lt;br /&gt;&lt;br /&gt;Timestamp 타입은 클라이언트에서 객체가 만들어지는 시점이 아니라 MongoDB 서버에서 저장되는 시점의 시각 정보를 저장한다.&lt;br /&gt;&lt;br /&gt;즉 Timesatmp 는 하나의 서버에서 반드시 유일한 값을 보장하게 되는데, 사용되는 대표적인 곳은 oplog 의 ts 필드이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.1163%;&quot;&gt;Date&lt;/td&gt;
&lt;td style=&quot;width: 84.8837%;&quot;&gt;내부적(BSON)으로 밀리초 단위의 유닉스타임스탬프 값을 64비트 정수로 저장한다.&lt;br /&gt;&lt;br /&gt;Date 타입은 부호를 가지는 정수인데, 양의정수는 유닉스 타임스탬프 시작 시점이후를 의미하며,&lt;br /&gt;음의 정수는 유닉스 타임스탬프 시작 시점 이전의 시각을 의미한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 타입 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드 값의 데이터 타입을 검색할 수 있는데 이 때 $type 연산자와 함께 데이터 타입의 별명이나 데이터 타입 아이디를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필드 값의 비교 및 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 한 컬렉션에 있는 각 도큐먼트 필드가 서로 다른 데이터 타입의 값을 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다른 필드를 비교 정렬할 수 있는 기준이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 타입이 서로 다른 경우 정렬이 큰 의미를 가지지 않지만 세컨드리 인덱스를 지원하는 솔루션에서 아키텍처적으로 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 도큐먼트 필드의 데이터 타입이 다른 경우 다음의 데이터 타입 순서로 정렬을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. MinKey&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Null&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Numbers&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Symbol, String&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Object&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Array&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. BinData&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. ObjectId&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. Boolean&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. Date&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. Timestamp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 타입이 서로 다른 경우 서버는 단순히 필드 값의 타입만으로 정렬을 수행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자셋과 콜레이션&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;콜레이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 비교에서 사용자가 원하는 언어에 의존적이 규칙을 적용할 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 컬렉션이나 뷰 그리고 콜레이션을 지원하는 오퍼레이션 단위로 콜레이션 옵션을 명시해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜레이션을 명시하려면 MongoDB 의 지정된 콜레이션 도큐먼트의 포맷을 사용해야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;포맷옵션&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;locale (필수, 이하 선택)&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;컬렉션이나 인덱스에서 사용할 로케일을 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;strength&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;문자열의 비교를 1~5중 어떤 강도로 할건지를 설정. 숫자 값이 낮을수록 느슨한 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;caseLevel&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;대소문자와 발음 기호의 비교를 포함할 건지 추가설정 strength 가 1~2일때 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;caseFirst&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;정렬에서 대문자와 소문자중 어떤 문자를 앞쪽으로 정렬할지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;numericOrdering&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;숫자 값으로 구성된 문자열의 정렬 규칙을 숫자처럼 비교할 것인지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;alternate&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;공백이나 구두점 문자를 비교 대상에 포함할 것인지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;maxVariable&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;문장 부호만 비교 문자에서 제외할 것인지 공백 문자만 비교문자에서 제외할 것인지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;backwards&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;악센트가 있는 문자의 정렬 규칙을 거꾸로 수행할 것인지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.0465%;&quot;&gt;normalization&lt;/td&gt;
&lt;td style=&quot;width: 78.9535%;&quot;&gt;문자열 비교를 위해 정규화 과정을 거침&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;strength 값&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;기본 문자만 비교 대상으로 포함&lt;br /&gt;a 와 A 는 같음. &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;a 와&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #252630; text-align: start;&quot;&gt;&amp;agrave; 는 같음&lt;/span&gt;&amp;nbsp;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;기본 문자와 발음 부호만 비교 대상으로 포함&lt;br /&gt;a 와 A 는 같음. a 와 &lt;span style=&quot;background-color: #ffffff; color: #252630; text-align: start;&quot;&gt;&amp;agrave; 는 다름&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;MongoDB 의 기본값으로 기본문자와 발음 부호 그리고 대소문자까지 비교 대상으로 포함&lt;br /&gt;a 와 A 는 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;발음 부호 등을 고려한 비교 수행&lt;br /&gt;ab &amp;lt; a-b &amp;lt; aB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;유니코드의 코드 값을 이용한 비교 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.4 이상부터 콜레이션을 컬렉션과 뷰 그리고 인덱스를 생성할때 설정 할 수 있으며 쿼리나 DML 에서도 콜레이션을 지정해 오퍼레이션을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한글 콜레이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글 콜레이션은 3개의 변형을 지원한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2791%;&quot;&gt;변형&lt;/td&gt;
&lt;td style=&quot;width: 83.7209%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2791%;&quot;&gt;search&lt;/td&gt;
&lt;td style=&quot;width: 83.7209%;&quot;&gt;범용적인 검색을 위해 지원하는 콜레이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2791%;&quot;&gt;searchji&lt;/td&gt;
&lt;td style=&quot;width: 83.7209%;&quot;&gt;한글의 자음 순서를 우선해서 정렬하는 콜레이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2791%;&quot;&gt;unihan&lt;/td&gt;
&lt;td style=&quot;width: 83.7209%;&quot;&gt;한중일 언어의 유니코드 통합 콜레이션으로 한자의 획순을 기준으로 정렬을 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 콜레이션을 별도로 지정하지 않으면 UTF8 문자셋의 인코딩에 기반해 자동으로 정렬이 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 기본 정렬 방식에서는 한글보다 항상 영문이 우선순위를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영문보다 한글을 먼저 정렬하고자 할 때는 다음과 같이 컬렉션을 생성할 때 한글 콜레이션을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 확장 JSON (Extended Json)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 JSON 도큐먼트를 STRICT 모드와 Mongo 셸 모드 두 종류의 모드로 구분해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STRICT 모드는 MongoDB 도구 뿐 아니라 외부믜 모든 JSON 도구들이 JSON 도큐먼트를 파싱할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여전히 JSON 이 지원하지 않는 Binary 나 ObjectId 와 같은 데이터 타입은 인식하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 MongoDB 셸 모드는 Mongo 셸이나 mongoimport 와 같은 MongoDB 의 도구들만 인식할 수 있는 포맷이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.031%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 40.4263%;&quot;&gt;STRICT 모드&lt;/td&gt;
&lt;td style=&quot;width: 40.5426%;&quot;&gt;Mongo 셸 모드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.031%;&quot;&gt;바이너리 타입&lt;/td&gt;
&lt;td style=&quot;width: 40.4263%;&quot;&gt;{ &quot;$binary} : &quot;&amp;lt;bindata&amp;gt;&quot; , &quot;$type&quot; : &quot;&amp;lt;t&amp;gt;&quot; }&lt;/td&gt;
&lt;td style=&quot;width: 40.5426%;&quot;&gt;BinData ( &amp;lt;t&amp;gt; , &amp;lt;bindata&amp;gt; )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.031%;&quot;&gt;날짜 타입&lt;/td&gt;
&lt;td style=&quot;width: 40.4263%;&quot;&gt;{ &quot;$date&quot; : &quot;&amp;lt;date&amp;gt;&quot; }&lt;/td&gt;
&lt;td style=&quot;width: 40.5426%;&quot;&gt;new Date ( &amp;lt;date&amp;gt; )&lt;br /&gt;ISODate ( &amp;lt;date&amp;gt; )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.031%;&quot;&gt;타임스탬프 타입&lt;/td&gt;
&lt;td style=&quot;width: 40.4263%;&quot;&gt;{ &quot;$timestamp&quot; : { &quot;t&quot; : &amp;lt;t&amp;gt;, &quot;i&quot; : &amp;lt;i&amp;gt; } }&lt;/td&gt;
&lt;td style=&quot;width: 40.5426%;&quot;&gt;Timestamp ( &amp;lt;t&amp;gt; , &amp;lt;i&amp;gt; )&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이너리 타입의 bindata 는 바이너리 값을 Base64 로 인코딩한 값이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;t 는 한 바이트의 데이터 타입을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongoimport 와 mongoexport 도구 그리고 REST 인터페이슨는 STRICT 모드의 JSON 을 지워하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bsondump 와 mongoimport 그리고 Mongo 셸은 Mongo 셸 모드를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongo 셸 모드를 지원하지 않는 mongodump 나 mongoexport 에서 셸 모드의 JSON 표기법을 사용하면 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모델링 고려 사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 MongoDB 는 주로 NoSQL 의 범주에 포함해서 언급되며 NoSQL 은 데이터모델링이 중요하지 않게 생각되곤 한다. 그러나 MongoDB 역시 RDBMS 만큼이나 모델링이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도큐먼트의 크기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 MongoDB 에 저장되는 도큐먼트 데이터는 RDBMS 의 레코드보다 큰 경향이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 사용자가 도큐먼트 데이터베이스라는 생각으로 많은 정보를 하나의 도큐먼트에 모아서 저장하는 경향도 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 도큐먼트 하나하나의 크기가 커지면 체감이 될 정도의 문제를 유발하고 있을 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 도큐먼트 크기로 인해 발생할 수 있는 문제점을 간단한 예제로 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 게시물이라는 컬렉션에 대략 1억건 정도가 저장되어 있고, 게시물 한 건의 평균 크기가 1KB 정도라고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 게시판 서비스에서 사용자가 게시물을 조회할 때마다 조회수를 업데이트하는 기능을 만들려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회수를 저장하는 필드를 기존 게시물 컬렉션에 저장하는게 좋을지, 아니면 별도의 분리된 카운터 컬렉션을 생성하는 것이 좋을지 고민한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 게시물 컬렉션에 저장하면 아래와 같은 장단점이 있다. 별도 분리 컬렉션은 반대로 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게시물과 조회수를 하나의 쿼리로 동시에 가져올 수 있기 때문에 응용 프로그램의 코드가 간단해지고 그만큼 페이지 조회도 빨라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 일반적인 온라인 트랜잭션 서비스의 특성상 게시물을 작성하는 것보다 조회하는 경우가 훨씬 많다. 그래서 사용자들이 게시물을 조회할 때마다 게시물 테이블의 도큐먼트가 변경되고 저장되어야 한다. 그런데 카운터 필드 하나만 변경하면 되지만 카운터 필드가 게시물 컬렉션에 저장되어 있으므로 게시물 도큐먼트를 통째로 변경해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회수를 저장하는 컬렉션은 게시물 아이디(8byte)와 조회수(4byte)만 있다고 가정하여 12 byte 가 저장된다고 가정한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;사이즈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;게시물 컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;1KB * 1억 = 95GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;게시물 + 조회수 컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;( 1KB + 4byte ) * 1억 = 95GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.9535%;&quot;&gt;조회수 컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 76.0465%;&quot;&gt;( 12Byte * 1억 ) + 1.5GB(프라이머리 인덱스) = 2.62GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 성능그래프를 보면 통합된 컬렉션이 더 많은 쿼리를 처리하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 분리된 컬렉션은 조회를 2번해야 하나 통합된 컬렉션은 1번의 조회를 하면 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 통합컬렉션은 주기적인 성능저하가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 그래프를 보면 통합된 컬렉션에서는 주기적으로 엄청난 양의 디스크 쓰기가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 WiredTiger 스토리지 엔진은 MySQL 이나 오라클과 같은 RDBMS 와는 달리 샤프 체크포인트 방식을 사용하는데, 체크포인트가 발생할 때마다 캐시의 변경된 페이지를 일괄적으로 디스크에 동기화해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때마다 WiredTiger 가 엄청난 양의 디스크 쓰기를 유발하면서 읽기 쿼리를 실행하지 못하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분리된 컬렉션의 경우 조회수만 저장하는 컬렉션의 크기가 크지 않아 많은 변경 쿼리가 유입되더라도 실제 WiredTiger 스토리지 엔진의 캐시에 변경이 가해지는 블록의 수가 통합된 컬렉션의 경우보다 훨씬 적어 디스크 부하를 덜 일으킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 를 선택하는 이유 중 하나로 도큐먼트 포맷의 자율성도 상당 부분을 차지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이유로 일반적으로 MongoDB 도큐먼트의 하나의 크기가 상당히 비대해지는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 응용 프로그램의 각 위치에서 꼭 필요로 하는 필드만 선택해서 가져가도록 개발하지 않는다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로인해 MongoDB 서버에서 네트워크 전송량 제한이 자주 병목이 되고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 예방하기 위해 3.6 버전부터 MongoDB 와 라우터 간 데이터 전송뿐 아니라 클라이언트 드라이버까지의 전송 데이터도 압축이 가능하도록 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규화와 역정규화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 MongoDB 에서 종속적인 정보를 부모 컬렉션에 포함하도록 설계하는 이유로 크게 2가지를 언급한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조인을 지원하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜잭션을 지원하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 에서 조인으로 여러 테이블을 동시에 읽는 형태의 쿼리가 필요하다면 MongoDB 에서는 여러 컬렉션의 데이터를 하나의 도큐먼트로 생성하라고 가이드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 서브 도큐먼트로 내장하면 MongoDB 에서는 하나의 Find 오퍼레이션으로 게시물과 그에 딸린 댓글들을 모두 한 번에 가져갈 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 MongoDB 에서는 트랜잭션을 지워하지 않고, 하나의 도큐먼트 처리에 대해서만 원자적인 오퍼레이션을 보장한다. 그래서 2개 이상의 도큐먼트를 원자적으로 저장하거나 삭제해야 한다면 MongoDB 에서는 그 도큐먼트를 하나의 도큐먼트로 묶어서 저장하는 방법을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이런 방법은 잘못된 모델링 습관을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 종속적인 컬렉션의 데이터를 부모 컬렉션에 내장할 경우에 발생할 수 있는 성능적인 문제점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도큐먼트의 크기가 계속 증가하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도큐먼트의 일부 정보에 접근하기 위해서 전체 도큐먼트를 읽고 써야 하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도큐먼트에 포함된 필드를 동시에 읽고 쓰는 데 제한되는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 의 내장된 캐시 메모리에서 데이터를 디스크로 기록하기 위해 기존 데이터와 변경된 데이터의 병합 작업이 발생한다. 이 때 하나의 도큐먼트가 내장된 서브 도큐먼트를 많이 가지면 가질수록 병합해야 하는 데이터가 커지고 캐시 메모리 효율이 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 도큐먼트 크기가 커지면 특정 필드를 읽기 위해 수 킬로에서 수십 킬로바이트의 도큐먼트를 매번 읽어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 몇 배의 데이터 블록을 읽어야 하므로 디스크 읽고/쓰기가 늘어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 변경 요청이 들어오면 둘 중 하나의 요청은 '처리 실패' 가 반환된다. 그리고 자동으로 MongoDB 서버로 재요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 실패되었다는 응답은 없으나 처리시간은 그만큼 늦어지게 되며 MongoDB 서버는 그만큼 더 많은 요청을 받게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 하나의 컬렉션을 다르 부모 컬렉션의 서브 도큐먼트로 내장할지 말지는 응용 프로그램에서 데이터를 어떻게 읽어 가느냐에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서브 도큐먼트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 데이터를 저장하더라도 다음과 같이 여러 가지 방법으로 BSON 도큐먼트의 포맷을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 필드를 나열하는 방법과&amp;nbsp;각 필드의 성격별로 그룹을 만들어 서브 도큐먼트를 만들수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 필드를 성격별로 묶어 서브 도큐먼트로 생성하는 방법은 가독성과 식별성을 높이며 메모리 적재 시 크기도 줄여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 에서 불가한 복잡한 형태의 데이터 모델을 가능하게 해주는 것이 바로 MongoDB 의 배열 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 A 필드는 단순한 문자열을 배열 타입으로 저장하고 있으며, 정규화되는 RDBMS 에서는 불가한(배열) 모델이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 사용하게 되면 여러 개의 컬렉션으로 분리되어야 할 데이터가 하나의 컬렉션에 모두 저장되므로 한 번의 쿼리로 조회하거나 변경할 수 있고, 컬렉션이 여러 개일 때보다 빠르게 개발할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이러한 배열 타입에 멀티키 인덱스를 만들수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 도큐먼트에 대해 원자 처리를 지워하는 MongoDB 배열 타입은 트랜잭션이 지원되지 않는 단점을 보완해줄수 있는 좋은 해결책이지만, 배열의 데이터가 많아지면 성능적인 문제점을 유발할 수 있으므로 모델링 시 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;도큐먼트의 크기 증가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 타입으로 데이터가 계속 추가되는 경우를 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 늘어나면 도큐먼트의 크기가 증가되는데, WiredTiger 스토리지 엔진은 트랜잭션을 처리할 수 있는 데이터베이스 엔진으로 설계되어 RDBMS 와 같이 트랜잭션을 지원하는데, 이를 위해 WAL 로그뿐 아니라 Undo 로그도 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열에 데이터가 계속 변경되면 변경 히스토리를 위한 메모리 공간의 낭비가 심해지고 그만큼 자주 변경 히스토리를 병합하는 작업을 수행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배열 관련 연산자 선택&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 타입은 아이템을 추가하거나 삭제할 때 다음과 같은 연산자를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;addSet 과 pull 은 데이터체크를 위해 모든 아이템과 비교 작업을 수행해야 하므로 선택하지 않는것이 좋다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.21702%;&quot;&gt;타입&lt;/td&gt;
&lt;td style=&quot;width: 17.7519%;&quot;&gt;연산자&lt;/td&gt;
&lt;td style=&quot;width: 74.031%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.21702%;&quot; rowspan=&quot;2&quot;&gt;추가&lt;/td&gt;
&lt;td style=&quot;width: 17.7519%;&quot;&gt;push&lt;/td&gt;
&lt;td style=&quot;width: 74.031%;&quot;&gt;배열의 마지막에 새로운 아이템을 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.7519%;&quot;&gt;addtoSet&lt;/td&gt;
&lt;td style=&quot;width: 74.031%;&quot;&gt;추가하는 아이템이 기존 배열에 있는지 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 8.21702%;&quot; rowspan=&quot;2&quot;&gt;삭제&lt;/td&gt;
&lt;td style=&quot;width: 17.7519%;&quot;&gt;pop&lt;/td&gt;
&lt;td style=&quot;width: 74.031%;&quot;&gt;배열의 첫 번째 아이템을 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.7519%;&quot;&gt;pull&lt;/td&gt;
&lt;td style=&quot;width: 74.031%;&quot;&gt;삭제해야 하는 아이템이 기존 배열에 있는지 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배열과 복제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에서 MongoDB 의 복제로그는 다른 DBMS 와 달리 멱등의 원칙을 지켜야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 복제 로그가 여러 번 수행되더라도 동일한 결과를 보장할 수 있도록 하기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멱등의 원칙을 준수하기 위해 MongoDB 는 복제로그에 사용자로부터 유입된 쿼리를 그대로 기록하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 요청과 원본 데이터를 조금 가공해서 복제로그에 기록하는데 어떻게 기록되는지 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 로그에 기록된 내용에서 o 필드의 값이 실제 변경되는 내용을 저장하는 곳인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UPDATE 명령은 단순히 push 로 배열의 마지막에 추가만 했지만, 복제 로그에는 배열의 위치와 값이 매핑되어 있음을 알 수 있다. 이는 복제로그가 아무리 많이 반복되어도 같은 결과를 보장하도록 MongoDB 가 로그를 변형해서 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 앞에 데이터를 추가해도 복제로그에는 멱등성을 위해 모든 배열의 포지션들이 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 요소가 많지 않고 값들이 크지 않으면 이런 방식이 문제가 없으나, 배열 요소가 많을수록 성능에 상당한 걸림돌로 작용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배열과 관련된 성능 테스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 크기가 커질수록 디스크에 써야 하는 데이터량도 늘어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 배열 타입은 적절한 개수의 아이템을 저장하고 관리하는 용도로 사용해야 하며 도큐먼트의 변경이 얼마나 빈번한지 그리고 데이터를 읽을 때 배열 전체 아이템이 필요한지에 따라 적절하게 설계해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필드 이름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 를 포함한 NoSQL DBSM 는 모두 정해진 스키마를 가지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 컬럼의 이름이나 타입이 별도의 메타 정보로 관리되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 컬렉션에 새로운 필드를 추가하거나 타입을 변경하는 것이 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이런 장점은 MongoDB 서버에서 필드명=필드값을 모두 저장하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 컬럼 이름의 길이가 MongoDB 에서는 새로운 튜닝포인트 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 데이터 파일을 압축할 수 있기 때문에 실제 디스크의 데이터가 차지하는 공간은 크지 않을 것으로 예측할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 데이터 블록 단위로 중복된 필드 이름은 압축으로 최소화되어 실제 필드명의 길이가 데이터 파일의 크기에 크게 영향을 미치지 않는다. 그러나 메모리 캐시는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진에 자체 내장된 1차 캐시(L1)를 먼저 활용한다. 그런데 2차 캐시로 활용하는 운영 체제의 페이지 캐시는 디스크의 데이터 파일을 그대로 복사해서 캐시하므로 압축된 상태를 유지하지만, WioredTiger 스토리지 엔진의 페이지 캐시는 압축되지 않은 상태로 데이터를 풀어서 메모리에 관리한다. 그래서 필드 이름이 길면 빈번하게 활용하는 1차 캐시의 공간에 낭비가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프레그멘테이션과 패딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 도큐먼트를 저장할 때 데이터 파일에서 저장하고자 하는 도큐먼트의 사이즈와 같거나 조금 큰 빈 공간을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 그런 공간을 찾을 수 없으면 서버는 데이터 파일의 마지막에 추가 공간을 생성한 후에 도큐먼트를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 처음 컬렉션을 생성하고 계속 INSERT 만 수행하면 저장한 순서대로 데이터 파일에 전혀 공간낭비 없이 차곡차곡 데이터를 저장할 수 있다. 그러나 UPDATE 되어 도큐먼트 크기가 더 커지면 위치한 블록공간이 없을 때 새로운 크기에 맞는 빈 공간으로 옮기게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 데이터파일에서 도큐먼트가 이동하면 MongoDB 서버는 인덱스에서 D4 도큐먼트를 가리키고 있던 주소를 새로운 주소로 변경한다. 이렇게 도큐먼트가 이동하는 작업으로 인해 처음에 도큐먼트가 사용하던 공간은 빈 상태가 되었다. 나중에 새로운 도큐먼트가 이 공간을 활용할 수 있지만 100% 활용할 가능성은 낮다. 이런 이동 과정이 반복되면 데이터 파일에서 도큐먼트 사이 빈 공간이 남게되는데 이런 빈 공간을 프레그멘테이션이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 데이터 파일의 프레그멘테이션이 심해지면 자연스럽게 컬렉션을 스캔하는 쿼리의 성능이 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레그멘테이션의 문제점을 보완하기 위해 패딩이라는 기능이 도입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패딩은 MongoDB 가 가진 패딩 기능을 활용할 수도 있으며, 사용자가 수동으로 의미없는 데이터를 덧붙여서 패딩할수도 있다. MongoDB 서버는 내부적으로 데이터 파일의 도큐먼트가 변경될 때마다 도큐먼트가 이동할 확률을 계산해서 컬렉션 단위로 paddingFactor 라는 값을 관리한다. 그리고 새로운 도큐먼트가 저장될 때마다 MongoDB 서버가 paddingFactor 값에 준하는 여분의 공간을 미리 데이터팡리에 준비한다. 앞으로 크기가 증가할 것을 대비해 빈 공간을 미리 만들어 저장하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 서버는 기존의 자동 패딩을 대체할 &quot;power of 2 size&quot; 라는 새로훈 형태의 공간할당 전략을 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 공간 할당 전략은 무조건 2바이트 단위로 도큐먼트의 저장 공간을 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 전략으로 인해 도큐먼트의 크기가 늘어나더라도 실제 데이터 파일에서 위치를 이동해야 하는 경우는 줄어들고 프레그멘테이션은 최소화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도큐먼트 유효성 체크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에 속한 필드 값에 대해서는 어떠한 제약도 가지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로인해 MongoDB 에서는 필드를 추가하기 위해 서비스를 멈추고 ALTER 와 같은 DDL 을 실행할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 때로는 NoSQL 에서도 RDBMS 에서와 같은 정규화된 제약이 필요할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서도 저장되는 도큐먼트의 규칙을 설정할 수 있는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트의 유효성 체크 규직은 컬렉션을 생성할 때 뿐만 아니라 이미 사용되고 있는 컬렉션에 대해 새로운 규칙을 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 MongoDB 의 도큐먼트 유효성 체크에는 쿼리 문장에 사용할 수 있는 대부분의 TRUE FALSE 표현식을 사용할 수 있어 아주 다양하고 복잡한 형태의 체크도 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 각 필드의 유효성 체크 결과를 AND 나 OR 로 연산해 도큐먼트의 유효성 결과를 판단하게 할수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유효성 체크는 컬렉션의 도큐먼트 단위로 설정되기 때문에 특정 필드의 유효성 체크에 대해 다른 도큐먼트 또는 다른 컬렉션의 도큐먼트에 있는 필드 값을 참조해서 비교할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 유효성 체크에는 두 개의 추가 옵션이 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;validationLevel&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;moderate : INSERT 와 이미 유효성 체크를 만족하는 UPDATE 에 대해서만 유효성을 체크하고, 유효성 체크를 만족하지 못한 도큐먼트의 UPDATE 에 대해서는 규칙에 어긋나더라도 별도의 조치를 취하지 않고 무시한다.&lt;br /&gt;&lt;br /&gt;strict : validateionLevel 의 기본값이며, INSERT 나 UPDATE 로 변경되는 모든 도큐먼트에 대해 유효성 체크 규칙에 위배되면 validationAction 에 명시된 대로 작동한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;validationAction&lt;/td&gt;
&lt;td style=&quot;width: 84.3023%;&quot;&gt;warn : INSERT 나 UPDATE 시 유효성 체크 규칙에 위배되면 단순히 규칙에 위배된다는 메시지만 로그 파일로 기록하고 사용자의 요청을 성공으로 완료한다.&lt;br /&gt;&lt;br /&gt;error : valicationAction 의 기본값이며, 새롭게 저장되거나 변경되는 도큐먼트가 유효성 규칙에 위배되면 사용자 요청을 실패 처리한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버도 조인을 지원하지 않는다. 그래서 조인이 필요하다고 생각되면 하나의 도큐먼트에 조인 대상 데이터를 내장할 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 $lookup 이라는 보조적인 조인 기능을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lookup 기능은 Aggregation 기능의 일부로 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$lookup 기능의 제약사항으로는 3가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- INNER JOIN 은 지원하지 않으며 OUTER JOIN 만 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조인되는 대상 컬렉션은 같은 데이터베이스에 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤딩되지 않은 컬렉션만 $lookup 오퍼레이션을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2 제약조건은 크게 문제되지 않을 수 있으나 3 의 제약조건은 MongoDB 를 사용하는 일반적인 이유가 샤딩인 것을 감안하면 치명적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩된 컬렉션을 $lookup 의 대상 컬렉션으로 사용하지 못하는 이유는 오퍼레이션이 라우터에서 처리되는 게 아니라 샤드 서버 단위로 처리되기 때문이다. 이 제약사항은 같은 샤드 키로 샤딩된 컬렉션이라 하더라도 피할 수 없다. 같은 샤드 키로 샤딩됐다 하더라도 컬렉션이 다르다면 청크가 서로 다르게 분산되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$lookup 은 샤드에서 해당 DB 의 프라이머리 샤드로 요청되며, 프라이머리 샤드는 우선 드라이빙 컬렉션 검색을 각 샤드로 전송하고 결과를 수집한다. 그리고 수집된 결과와 드리븐 컬렉션을 조인하게 되는데, 이 때 드리븐 컬렉션은 프라이머리 샤드에 저장돼 잇기 때문에 로컬 샤드에서 조인 처리를 수행할 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 $lookup 을 사용한 조인 쿼리가 많아지면 프라이머리 샤드 서버만 많은 처리를 담당하게 되므로 부하의 불균형이 심해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/661</guid>
      <comments>https://mozi.tistory.com/661#entry661comment</comments>
      <pubDate>Thu, 19 Sep 2024 08:54:13 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 잠금과 트랜잭션(2)</title>
      <link>https://mozi.tistory.com/660</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOnmSc/btsJvoW0p3I/5v7YexkA45m7dKKL2DaoNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOnmSc/btsJvoW0p3I/5v7YexkA45m7dKKL2DaoNk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOnmSc/btsJvoW0p3I/5v7YexkA45m7dKKL2DaoNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOnmSc%2FbtsJvoW0p3I%2F5v7YexkA45m7dKKL2DaoNk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Read &amp;amp; Write Concern 과 Read Preference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 분산처리를 기본 아키텍처로 선택하고 있기 때문에 단일 노드에서 단일 노드의 격리 수준뿐 아니라 레플리카 셋을 구성하는 멤버들 간의 동기화까지 제어할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 서버는 데이터의 Durability 수준에 따라 데이터를 변경하거나 조회할 수 있도록 Read, Write Concern 옵션을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 프로그램에서 쿼리 단위로 다르게 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Write Concern&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 시작과 종료를 명시적으로 실행할 방법이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 MongoDB 서버는 도큐먼트를 저장할 때 사용자의 데이터 변경 요청에 응답이 반환되는 시점이 트랜잭션의 커밋으로 간주된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용자의 변경 요청에 응답하는 시점을 결정하는 옵션을 WriteConcern 이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트, 데이터베이스 ,컬렉션 레벨의 3가지 방법으로 WriteConcern 을 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단일 노드 동기화 제어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버 내부적으로 서버 내부적으로 변경된 데이터가 어느정도 디스크에 동기화되었을때 사용자의 변경 요청에 '완료' 를 보낼 것인지 판단하는 기준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일노드 동기화 제어옵션으로는 아래 4가지가 있는데 이중 FSYNC 옵션은 MMAPv1 스토리지 엔진에서 사용되던 옵션이고 WiredTiger 에서는 거의 사용되지 않고 의미도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.8837%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 80.1163%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.8837%;&quot;&gt;UNACKNOWLEDGED&lt;/td&gt;
&lt;td style=&quot;width: 80.1163%;&quot;&gt;초창기 버전에 자주 사용되던 옵션으로, 사용자가 데이터를 저장 변경하는 요청을 보내기만 하고 실제 MongoDB 서버의 응답을 기다리지 않는다.&lt;br /&gt;&lt;br /&gt;즉, 클라이언트가 서버로 전송된 명령이 정상적으로 처리되었는지 아니면 에러로 중간에 작업이 멈췄는지 관심을 가지지 않는다.&lt;br /&gt;&lt;br /&gt;따라서 결과를 확인하기 위해 getLastError 명령을 실행해야 했다. 2.6 버전 이후로는 통신 프로토콜이 개선되어 별도로 getLastError 명령을 실행하지 않고도 데이터 변경 요청의 성공 및 실패 여부를 받는 형태가 되었다.&lt;br /&gt;&lt;br /&gt;UNACKNOWLEDGED 모드에서는 실제 데이터 변경 요청이 성공, 실패했는지 확인하지 않고 다음 쿼리를 실행하기 때문에 실제 데이터 변경이 적용되지 않았는데 같은 세션에서 그 데이터를 조회하는 쿼리가 실행될 수 있다. ( 이럴 수 있음 ?? )&lt;br /&gt;&lt;br /&gt;그래서 변경한 데이터가 그 이후 실행된 조회 쿼리에서 보이지 않는 문제점이 있었다.&lt;br /&gt;데이터 조회를 거의 사용하지 않거나 로그나 분석용으로는 사용할 만하, 그 외에는 사용하지 말아야 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.8837%;&quot;&gt;ACKNOWLEDGED&lt;/td&gt;
&lt;td style=&quot;width: 80.1163%;&quot;&gt;최근 버전의 MongoDB 서버에서 사용하는 WriteConcern 의 기본값이다.&lt;br /&gt;클라이언트가 변경 요청을 전송하면 변경 내용을 메모리상에서만 적용하고 바로 클라이언트로 성공 또는 실패 응답을 반환한다.&lt;br /&gt;&lt;br /&gt;일단 데이터 변경요청이 성공하면 적어도 서버의 메모리상에는 적용된 상태이다.&lt;br /&gt;그래서 즉시 변경된 데이터를 조회해도 변경된 내용이 반환된다.&lt;br /&gt;&lt;br /&gt;하지만 메모리상에서 변경된 데ㅣㅇ터가 디스크로 동기화 되는 것을 보장하지는 않는다.&lt;br /&gt;그래서 메모리상의 변경 데이터가 디스크로 동기화 되기 전에 서버에 문제가 생기거나 비정상적으로 종료되면 변경된 데이터가 손실될 위험이 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.8837%;&quot;&gt;JOURNALED&lt;/td&gt;
&lt;td style=&quot;width: 80.1163%;&quot;&gt;저널로그에 기록후에 성공응답을 반환한다.&lt;br /&gt;&lt;br /&gt;서버가 비정상적으로 종료되더라도 이미 저널 로그에 기록되었기 때문에 다시 재시작하면 저널 로그의 데이터를 복구할 수 있다.&lt;br /&gt;&lt;br /&gt;다만 단일 서버로 운영될 때는 발생하지 않던 문제가 레플리카 셋을 사용하는 경우 문제가 될 수 있다.&lt;br /&gt;아래 '레플리카 셋 간의 동기화 제어' 에서 추가로 정리한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.8837%;&quot;&gt;FSYNC&lt;/td&gt;
&lt;td style=&quot;width: 80.1163%;&quot;&gt;저널 로그뿐 아니라 데이터 파일까지 모두 디스크로 동기화하고 난 이후 클라이언트로 성공, 실패 여부를 반환하는 방식이다.&lt;br /&gt;&lt;br /&gt;FSYNC 는 저널로그를 지원하지 않던 시점에 도입된 방법인데, 비정상적인 종료로부터 데이터를 보호할 방법이 없어 데이터 파일 자체를 동기화하는 방식으로 데이터를 유지했다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;레플리카 셋 간의 동기화 제어&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋으로 구축한 경우 프라이머리 멤버가 비정상적으로 종료되거나 네트워크에 연결이 실패하면 세컨드리 멤버가 즉시 새로운 프라이머리로 선출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 새롭게 프라이머리로 선출된 멤버가 기존 프라이머리의 모든 OpLog 를 가져오지 못하면 어떻게 되는가 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황을 예로들어본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새롭게 프라이머리로 선출된 B 멤버가 기존 프라이머리의 모든 OpLog 를 가져오지 못한 상태에서 기존 프라이머리 멤버A 가 연결할 수 없는 상태이면 MongoDB 서버가 선택할 수 있는 것이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최종 데이터를 가지고 있지 않지만, 그나마 레플리카 셋에서 살아남은 멤버 중에서 최신의 데이터를 가진 B 멤버가 새로운 프라이머리로 선출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프라이머리 멤버로 선출됨과 동시에 클라이언트로부터 새로운 변경 요청을 받아 처리하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 흘러 기존 프라이머리였던 멤버A가 다시 네트워크에 연결되고 레플리카 셋에 다시 조인하면 A 멤버는 자신의 OpLog 와 새로운 프라이머리인 B 멤버의 OpLog 를 맞추는 작업을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 A 멤버는 장애가 발생했던 시점을 기준으로 볼 때 자신이 B 멤버보다 더 많은 데이터를 가지고 있던 것을 알게된다. 그러면 A 멤버는 더 가지고 있던 OpLog 를 모두 롤백하고 B 멤버에 있고 동기화시점까지였던 이후부터의 OpLog 를 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 A 멤버의 OpLog 일부는 손실되는데, 이 과정은 저널 로그를 동기화하고 있었는지 여부와 무관하게 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 막기 위해 MongoDB 서버는 단순히 단일 노드의 WriteConcern 뿐 아니라 레플리카 셋 전체에 걸쳐 작동하는 모드가 필요해진 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋의 여러 노드에 대해 WriteConcern 을 설정하는 방법은 &quot;{w : ?}&quot; 옵션을 사용하는 것이다. 이 때 w 필드의 값은 숫자 또는 문자열을 설정할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.3721%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 86.6279%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.3721%;&quot;&gt;숫자 값&lt;/td&gt;
&lt;td style=&quot;width: 86.6279%;&quot;&gt;레플리카 셋에서 데이터를 동기화해야 할 멤버의 개수를 설정한다.&lt;br /&gt;&lt;br /&gt;이 값을 2로 설정하면 레플리카 셋 멤버 중에서 자신을 포함해 2개의 멤버가 사용자의 변경 요청을 필요한 수준까지 처리했을 때 클라이언트로 성공 또는 실패 메시지를 반환한다.&lt;br /&gt;&lt;br /&gt;즉 프라이머리 멤버와 세컨드리 멤버 중 1대가 정상적으로 처리해야 w:2 를 만족한다는 것이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.3721%;&quot;&gt;majority&lt;/td&gt;
&lt;td style=&quot;width: 86.6279%;&quot;&gt;w 옵션에 명시적으로 동기화할 레플리카 셋의 멤버 수를 설정하면 레플리카 셋의 개수가 변경될 때마다 응용 프로그램의 코드를 변경해야 할 필요가 있을 수 있다.&lt;br /&gt;&lt;br /&gt;majority 는 말 그대로 레플리카 셋을 몇 개의 멤버로 구성했는지와 관계없이 레플리카 셋 멤버 중 과반수가 동기화되면 클라이언트로 데이터 변경 요청 결과를 반환하는 writeConcern 옵션이다.&lt;br /&gt;&lt;br /&gt;프라이머리 스취치로 인해 롤백될 가능성이 있는 데이터는 클라이언트로 보내지 않도록 ReadConcern 을 설정할 수 있다. ReadConcern 도 WriteConcern 과 동일하게 읽기를 실행할 멤버의 개수를 설정할 수 있으며 majorit 로 설정할 수도 있다. 하지만 데이터의 변경이 롤백으로 손실되지 않을 정도가 보장됐을 때 클라이언트로 결과를 반환하고 조회도 롤백되지 않을 데이터만 반환하도록 하려면 Read / Write Concern 을 모두 majority 로 설정해야 한다.&lt;br /&gt;&lt;br /&gt;읽기 시 majority 이기 때문에 과반수 이상의 멤버에서 동기화된 데이터를 가져가게 된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 레플리카 셋을 구성하는 멤버들이 여러 IDC 에 걸쳐 배포된 경우에는 각 IDC 별로 레플리카 셋의 각 멤버에 태그를 할당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;WriteConcern 에 따른 응답 시간 비교&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 기다리지 않는 UNACKNOWLEDGED 모드는 매우 빠른 성능을 보여주나, MongoDB 의 응답을 받아야 하는 모드들은 낮은 성능을 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ACKNOWLEDGED 나 JOURNAL 과 비교했을 때 성능이 거의 절반으로 떨어지는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Read Concern&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 레플리케이션을 Eventual Consistency (최종 동기화) 모델로 표현하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터를 읽어 가는 쿼리 입장에서는 최종 동기화가 수많은 문제점을 유발할 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 동기화 과정 중 데이터 읽기를 일관성 있게 유지할 수 있도록 MongoDB 서버에서는 ReadConcern 옵션을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReadConcern 옵션은 WriteConcern 옵션과는 달리 레플리카 셋 간의 동기화 이슈만 제어한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 ReadConcern 옵션은 다음 3가지 중 선택이 가능하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 85.2326%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;local&lt;/td&gt;
&lt;td style=&quot;width: 85.2326%;&quot;&gt;local 모드의 ReadConcern 에서는 쿼리가 실행되는 MongoDB 서버가 가진 최신의 데이터를 반환하는 방식으로 작동한다.&lt;br /&gt;&lt;br /&gt;MongoDB 서버의 디폴트 ReadConcern 옵션인데 local 모드에서는 레플리카 셋의 다른 멤버가 가진 데이터 상태를 확인하지 않기 때문에 최신 데이터를 프라이머리 멤버만 가진 상태에서 프라이머리 멤버가 비정상적으로 종료되거나 연결이 끊어지면 그 데이터는 롤백 되어 Phantom Read 와 비슷한 상황이 발생한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;majority&lt;/td&gt;
&lt;td style=&quot;width: 85.2326%;&quot;&gt;레플리카 셋에서 다수의 멤버들이 최신의 데이터를 가졌을 때만 읽기 결과가 반환된다.&lt;br /&gt;&lt;br /&gt;레플리카 셋에서 다수의 멤버가 가진 데이터에 대해 쿼리 결과가 반환되므로 클라이언트가 읽었던 데이터가 롤백으로 인해 사라질 가능성은 상당히 낮다.&lt;br /&gt;&lt;br /&gt;하지만 일부 레플리카 셋의 멤버가 동시에 연결할 수 없는 상태가되면 Phantom Read 현상이 발생할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.7674%;&quot;&gt;linearizable&lt;/td&gt;
&lt;td style=&quot;width: 85.2326%;&quot;&gt;ReadConcern 은 래플리카 셋의 모든 멤버가 가진 변경 사항에 대해서만 쿼리결과를 반환한다.&lt;br /&gt;&lt;br /&gt;즉 클라이언트가 한번 읽어간 데이터는 이미 모든 레플리카 셋 멤버에 반영되었기 때문에 프라이머리가 스위칭된다 하더라도 절대 롤백되지 않는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReadConcern 이 majority 인 경우 MongoDB 서버는 레플리카 셋 멤버들의 복제 상태 표만 참조해서 클라이언트로 반환할 데이터를 판단하고 즉시 결과를 반환한다. 즉 특정 시점의 데이터를 반환하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ReadConcern 이 linearizable 인 경우 MongoDB 서버는 자신이 가진 최신의 OpLog 까지 모든 세컨드리로 전파될 때까지 기다렸다가 결과를 반환하기 때문에 majority 보다 더 최신의 반환하나 응답이 훨씬 느려지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 linearizable 모드를 사용하는 경우에는 반드시 쿼리의 타임아웃 시간을 설정하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;majority 모드의 ReadConcern 을 사용하려면 반드시 다음과 같이 MongoDB 서버의 enableMajorityReadConcern 옵션이 활성화된 상태로 시작해야 한다. 레플리카 셋이 프로토콜 버전1 이상을 사용해야 하며 WiredTiger 에서만 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Read Preference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트의 쿼리를 어떤 MongoDB 서버로 요청해서 실행할 것인지 결정하는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Concern 은 읽기의 일관성이 목적이지만, Read Preference 는 데이터 읽기로 인한 부하의 분산이 주목적인 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 드라이버는 5개의 Read Prefercne 모드를 지원하며 조회쿼리에만 영향을 미친다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 81.9767%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;primary&lt;/td&gt;
&lt;td style=&quot;width: 81.9767%;&quot;&gt;기본값으로 프라이머리 멤버로만 쿼리를 요청한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;primaryPreferred&lt;/td&gt;
&lt;td style=&quot;width: 81.9767%;&quot;&gt;가능하면 프라이머리 멤버로 쿼리를 전송한다.&lt;br /&gt;하지만 레플리카 셋에 프라이머리 멤버가 없는 경우 세컨드리 멤버로 쿼리를 요청한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;secondary&lt;/td&gt;
&lt;td style=&quot;width: 81.9767%;&quot;&gt;세컨드리 멤버로만 쿼리를 요청한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;secondaryPreferred&lt;/td&gt;
&lt;td style=&quot;width: 81.9767%;&quot;&gt;쿼리를 요청할 수 있는 세컨드리 멤버가 없으면 프라이머리 멤버로 쿼리를 요청한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.0233%;&quot;&gt;nearest&lt;/td&gt;
&lt;td style=&quot;width: 81.9767%;&quot;&gt;레플리카 셋에서 쿼리의 응답 시간이 빠른 멤버로 쿼리를 요청한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 메뉴얼에서는 세컨드리 읽기를 가능하면 사용하지 않는 것을 권장하고 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 멤버만으로 충분히 처리할 수 있는 부하라면 굳이 세컨드리를 사용하지 않는것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 무거운 배치작업이나 통계성 작업들은 복제 지연에 민감하지 않으므로 세컨드리 멤버를 사용해도 무방하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;maxStalenessSeconds 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 허용 가능한 복제지연시간을 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 드라이버나 Mongo 라우터가 지정된 시간보다 복제 지연이 심한 경우에 해당 세컨드리 멤버를 접속 가능한 대상 서버 목록에서 제거하고 접속하지 못하도록 차단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;maxStalenessSeconds 옵션은 주기적으로 각 세컨드리 멤버의 마지막 쓰기 시점을 이용해서 복제 지연을 측정하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 지연이 maxStalenessSeconds 보다 큰 경우에 해당 멤버로의 연결을 사용하지 못하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 지연은 레플리카 셋의 모든 멤버에 접속해 현재 opLog 의 최종 시각을 확인한 다음 프라이머리와 세컨드리 멤버 간의 시간 차이를 확인하는 방식으로 측정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값은 최소 90초 이상으로 설정해야 하며 아래로는 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤딩 환경의 중복 도큐먼트 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩이 적용된 MongoDB 서버는 내부적으로 데이터의 균등 분산을 위해 밸런서가 각 샤드의 청크를 이동시키는 과정이 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 밸런싱 작업으로 인해 동일 도큐먼트가 2개의 샤드에 동시에 존재할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개 이상의 샤드에 같은 도큐먼트가 존재하는 경우는 청크 이동과 같은 일시적 상황 뿐 아니라 알수없는 오류로 인해 청크이동이 실패 혹은 라우터를 거치지않고 직접 MongoDB 서버에 데이터를 저장하는 경우에도 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 여러 샤드 서버가 같은 도큐먼트를 가진 상태라 하더라도 실제 쿼리를 실행해보면 중복된 도큐먼트가 사용자에게 보이지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 MongoDB 서버가 쿼리를 처리하면서 사용자에게 결과를 반환할 때 자기 자신이 가진 청크 목록에 소속된 도큐먼트인지 검증하기 때문이다. 만약 검증에 통과되지 못하면 서버는 버리고 무시하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 항상 이런 체크를 수행하지 않는 점이 문제이다. 아래의 2가지 경우에만 샤드의 소유권을 체크한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리의 샤드에 메타 정보 버전이 포함된 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리의 조건에 샤드 키가 포함된 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩이 적용된 MongoDB 서버는 청크가 이동될 때마다 컨피그 서버의 메타 정보가 변경되는데, 이때마다 메타 정보의 버전이 1씩 증가한다. 즉 한 시점의 청크 분배 상태는 하나의 버전을 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버로 쿼리를 요청할 때 이 버전을 같이 전송하는데 MongoDB 서버는 그 버전의 청크 분배 상태에 맞게 자기 자신의 처리 결과를 필터링해서 결과를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 경우는 특정 샤드로만 쿼리가 전달되므로 결과적으로 소유권을 체크하는 과정이 자연적으로 포함된다.&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/660</guid>
      <comments>https://mozi.tistory.com/660#entry660comment</comments>
      <pubDate>Mon, 9 Sep 2024 11:21:20 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] OpLog 로 현재실행중인 쿼리 상태조회</title>
      <link>https://mozi.tistory.com/659</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNaPQo/btsI99GT11e/kbPg7nA36Yr4EhE442F4J0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNaPQo/btsI99GT11e/kbPg7nA36Yr4EhE442F4J0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNaPQo/btsI99GT11e/kbPg7nA36Yr4EhE442F4J0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNaPQo%2FbtsI99GT11e%2FkbPg7nA36Yr4EhE442F4J0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB OpLog&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 를 통해 현재 실행중인 쿼리의 클라이언트 정보, 락의 유형 등을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. OpLog 확인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. Session 1 에서 인덱스 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1724231901959&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.uniqueIndex.createIndex( { dummy : 1 } )
dummy_1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. Session 2 에서 OpLog 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 인덱스가 수행되는 동안 OpLog 를 조회하면 아래와 같은 정보가 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1724231864300&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; db.currentOp()
{
      shard: 'rs1',
      type: 'op',
      host: 'ip-172-31-7-169.ap-northeast-2.compute.internal:30001',
      desc: 'IndexBuildsCoordinatorMongod-7',
      active: true,
      currentOpTime: '2024-08-21T09:11:39.672+00:00',
      opid: 'rs1:29291449',
      secs_running: Long('0'),
      microsecs_running: Long('133039'),
      op: 'command',
      ns: 'indexDB.uniqueIndex',
      redacted: false,
      command: {
        createIndexes: 'uniqueIndex',
        indexes: [ { v: 2, key: { dummy: 1 }, name: 'dummy_1' } ],
        readConcern: { level: 'local', provenance: 'implicitDefault' },
        writeConcern: { w: 'majority', wtimeout: 0, provenance: 'implicitDefault' },
        shardVersion: {
          e: ObjectId('000000000000000000000000'),
          t: Timestamp({ t: 0, i: 0 }),
          v: Timestamp({ t: 0, i: 0 })
        },
        databaseVersion: {
          uuid: UUID('4bdeceab-3d12-4c6d-b79e-94f8a094e3f0'),
          timestamp: Timestamp({ t: 1722313944, i: 1 }),
          lastMod: 1
        },
        lsid: {
          id: UUID('de0d01b9-df88-488a-9d82-417268031a5d'),
          uid: Binary.createFromBase64('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=', 0)
        },
        '$clusterTime': {
          clusterTime: Timestamp({ t: 1724231498, i: 2 }),
          signature: {
            hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
            keyId: Long('0')
          }
        },
        '$configTime': Timestamp({ t: 1724231498, i: 2 }),
        '$topologyTime': Timestamp({ t: 1719896932, i: 2 }),
        '$client': {
          application: { name: 'mongosh 2.2.10' },
          driver: { name: 'nodejs|mongosh', version: '6.7.0|2.2.10' },
          platform: 'Node.js v20.12.2, LE',
          os: {
            name: 'linux',
            architecture: 'x64',
            version: '3.10.0-327.22.2.el7.x86_64',
            type: 'Linux'
          },
          mongos: {
            host: 'ip-172-31-7-169.ap-northeast-2.compute.internal:20001',
            client: '127.0.0.1:48032',
            version: '7.0.12'
          }
        },
        mayBypassWriteBlocking: false,
        '$db': 'indexDB'
      },
      queryFramework: 'classic',
      msg: 'Index Build: draining writes received during build',
      numYields: 0,
      locks: {},
      waitingForLock: false,
      lockStats: {
        FeatureCompatibilityVersion: { acquireCount: { r: Long('3'), w: Long('6') } },
        ReplicationStateTransition: { acquireCount: { w: Long('9') } },
        Global: { acquireCount: { r: Long('3'), w: Long('6') } },
        Database: { acquireCount: { r: Long('2'), w: Long('6') } },
        Collection: {
          acquireCount: { r: Long('1'), w: Long('4'), R: Long('1'), W: Long('2') }
        },
        Mutex: { acquireCount: { r: Long('15') } }
      },
      waitingForFlowControl: false,
      flowControlStats: { acquireCount: Long('4') }
    },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;numYields : Yield 가 발생한 횟수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;locks : 잠금유형&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lockStats : r, w, R, W 잠금이 얼마나 획득되었는지&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb oplog 확인</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/659</guid>
      <comments>https://mozi.tistory.com/659#entry659comment</comments>
      <pubDate>Wed, 21 Aug 2024 18:21:25 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] Yield 잠금 테스트</title>
      <link>https://mozi.tistory.com/658</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAPeRX/btsJaaTjhs1/SiucVskMnPpqyGeNzEFV60/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAPeRX/btsJaaTjhs1/SiucVskMnPpqyGeNzEFV60/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAPeRX/btsJaaTjhs1/SiucVskMnPpqyGeNzEFV60/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAPeRX%2FbtsJaaTjhs1%2FSiucVskMnPpqyGeNzEFV60%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB Yield 잠금&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 쿼리를 실행하는 도중 잠깐 쉬었다 실행을 재개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 잠금과 스냅샷을 모두 해제하고 지정된 시간동안 쉬다 다시 잠금을 획득 후 다음 실행을 이어가게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 트랜잭션의 동시성 처리를 우수하게 만들기 위함이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. Yield 와 관련된 설정 값&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Yield 에서 특정 건수를 읽으면 특정 시간을 쉬는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;internalQueryExecYieldPeriodMS 와 internalQueryExecYieldIterations 값을 기준으로 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값은 1000 건을 읽으면 10ms 를 쉬게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1724230642584&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 특정 건수를 읽으면
[direct: mongos] test&amp;gt; db.adminCommand( { getParameter : 1, &quot;internalQueryExecYieldIterations&quot; : 1 } )
{
  internalQueryExecYieldIterations: 1000,
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1724230607, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1724230607, i: 1 })
}

# 특정 시간을 쉰다.
[direct: mongos] test&amp;gt; db.adminCommand( { getParameter : 1, &quot;internalQueryExecYieldPeriodMS&quot; : 1 } )
{
  internalQueryExecYieldPeriodMS: 10,
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1724230578, i: 2 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1724230578, i: 2 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Yield 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Yield 테스트는 대량의 데이터가 있는경우에 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. Session 1 에서 데이터 적재&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 1만건을 적재한다.&lt;/p&gt;
&lt;pre id=&quot;code_1724293248375&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] transactionDB&amp;gt; for (var idx=0; idx&amp;lt;10000; idx++) { db.consistency.insert ( { fd : idx } ) } ; 
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66c69fc8e648fd165b1b3c8c') }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. Session 1 에서 미정렬 데이터 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 명령을 이용하여 풀 스캔 조회하면 모두 출력하지 않고 커서를 반환하므로 20건만 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 find 명령을 사용하였으므로 풀스캔인 상태이나 Yield 에 의해&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1724293271907&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] transactionDB&amp;gt; db.consistency.find()
[
  { _id: ObjectId('66c69fa0e648fd165b1b157d'), fd: 0 },
  { _id: ObjectId('66c69fa0e648fd165b1b157e'), fd: 1 },
  { _id: ObjectId('66c69fa0e648fd165b1b157f'), fd: 2 },
  { _id: ObjectId('66c69fa0e648fd165b1b1580'), fd: 3 },
  { _id: ObjectId('66c69fa0e648fd165b1b1581'), fd: 4 },
  { _id: ObjectId('66c69fa0e648fd165b1b1582'), fd: 5 },
  { _id: ObjectId('66c69fa0e648fd165b1b1583'), fd: 6 },
  { _id: ObjectId('66c69fa0e648fd165b1b1584'), fd: 7 },
  { _id: ObjectId('66c69fa0e648fd165b1b1585'), fd: 8 },
  { _id: ObjectId('66c69fa0e648fd165b1b1586'), fd: 9 },
  { _id: ObjectId('66c69fa0e648fd165b1b1587'), fd: 10 },
  { _id: ObjectId('66c69fa0e648fd165b1b1588'), fd: 11 },
  { _id: ObjectId('66c69fa0e648fd165b1b1589'), fd: 12 },
  { _id: ObjectId('66c69fa0e648fd165b1b158a'), fd: 13 },
  { _id: ObjectId('66c69fa0e648fd165b1b158b'), fd: 14 },
  { _id: ObjectId('66c69fa0e648fd165b1b158c'), fd: 15 },
  { _id: ObjectId('66c69fa0e648fd165b1b158d'), fd: 16 },
  { _id: ObjectId('66c69fa0e648fd165b1b158e'), fd: 17 },
  { _id: ObjectId('66c69fa0e648fd165b1b158f'), fd: 18 },
  { _id: ObjectId('66c69fa0e648fd165b1b1590'), fd: 19 }
]
Type &quot;it&quot; for more&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-3. Session 2 에서 데이터 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 세션에서 fd 가 50인 데이터를 삭제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1724293359736&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] transactionDB&amp;gt; db.consistency.remove ( { fd : 50 } )
{ acknowledged: true, deletedCount: 1 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-4. Session 1 에서 데이터 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 호출이 계속되고 있지만 it 으로 다음결과를 가져올 때 50의 값을 조회하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 잠금이 해제되기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1724293379552&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] transactionDB&amp;gt; it
[
  { _id: ObjectId('66c69fa0e648fd165b1b1591'), fd: 20 },
  { _id: ObjectId('66c69fa0e648fd165b1b1592'), fd: 21 },
  { _id: ObjectId('66c69fa0e648fd165b1b1593'), fd: 22 },
  { _id: ObjectId('66c69fa0e648fd165b1b1594'), fd: 23 },
  { _id: ObjectId('66c69fa0e648fd165b1b1595'), fd: 24 },
  { _id: ObjectId('66c69fa0e648fd165b1b1596'), fd: 25 },
  { _id: ObjectId('66c69fa0e648fd165b1b1597'), fd: 26 },
  { _id: ObjectId('66c69fa0e648fd165b1b1598'), fd: 27 },
  { _id: ObjectId('66c69fa0e648fd165b1b1599'), fd: 28 },
  { _id: ObjectId('66c69fa0e648fd165b1b159a'), fd: 29 },
  { _id: ObjectId('66c69fa0e648fd165b1b159b'), fd: 30 },
  { _id: ObjectId('66c69fa0e648fd165b1b159c'), fd: 31 },
  { _id: ObjectId('66c69fa0e648fd165b1b159d'), fd: 32 },
  { _id: ObjectId('66c69fa0e648fd165b1b159e'), fd: 33 },
  { _id: ObjectId('66c69fa0e648fd165b1b159f'), fd: 34 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a0'), fd: 35 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a1'), fd: 36 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a2'), fd: 37 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a3'), fd: 38 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a4'), fd: 39 }
]
Type &quot;it&quot; for more
[direct: mongos] transactionDB&amp;gt; it
[
  { _id: ObjectId('66c69fa0e648fd165b1b15a5'), fd: 40 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a6'), fd: 41 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a7'), fd: 42 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a8'), fd: 43 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a9'), fd: 44 },
  { _id: ObjectId('66c69fa0e648fd165b1b15aa'), fd: 45 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ab'), fd: 46 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ac'), fd: 47 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ad'), fd: 48 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ae'), fd: 49 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b0'), fd: 51 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b1'), fd: 52 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b2'), fd: 53 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b3'), fd: 54 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b4'), fd: 55 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b5'), fd: 56 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b6'), fd: 57 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b7'), fd: 58 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b8'), fd: 59 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b9'), fd: 60 }
]
Type &quot;it&quot; for more&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-5. Session 1 에서 정렬 데이터 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬을 사용하는 경우에는 모든 데이터를 메모리에 올려놓고 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 올라간 데이터는 정렬버퍼(메모리)에 임시로 복사된 결과에 적용되지 않기에 일관된 결과를 보여준다.&lt;/p&gt;
&lt;pre id=&quot;code_1724302566154&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] transactionDB&amp;gt; db.consistency.find().sort({fd:1})
[
  { _id: ObjectId('66c69fa0e648fd165b1b157d'), fd: 0 },
  { _id: ObjectId('66c69fa0e648fd165b1b157e'), fd: 1 },
  { _id: ObjectId('66c69fa0e648fd165b1b157f'), fd: 2 },
  { _id: ObjectId('66c69fa0e648fd165b1b1580'), fd: 3 },
  { _id: ObjectId('66c69fa0e648fd165b1b1581'), fd: 4 },
  { _id: ObjectId('66c69fa0e648fd165b1b1582'), fd: 5 },
  { _id: ObjectId('66c69fa0e648fd165b1b1583'), fd: 6 },
  { _id: ObjectId('66c69fa0e648fd165b1b1584'), fd: 7 },
  { _id: ObjectId('66c69fa0e648fd165b1b1585'), fd: 8 },
  { _id: ObjectId('66c69fa0e648fd165b1b1586'), fd: 9 },
  { _id: ObjectId('66c69fa0e648fd165b1b1587'), fd: 10 },
  { _id: ObjectId('66c69fa0e648fd165b1b1588'), fd: 11 },
  { _id: ObjectId('66c69fa0e648fd165b1b1589'), fd: 12 },
  { _id: ObjectId('66c69fa0e648fd165b1b158a'), fd: 13 },
  { _id: ObjectId('66c69fa0e648fd165b1b158b'), fd: 14 },
  { _id: ObjectId('66c69fa0e648fd165b1b158c'), fd: 15 },
  { _id: ObjectId('66c69fa0e648fd165b1b158d'), fd: 16 },
  { _id: ObjectId('66c69fa0e648fd165b1b158e'), fd: 17 },
  { _id: ObjectId('66c69fa0e648fd165b1b158f'), fd: 18 },
  { _id: ObjectId('66c69fa0e648fd165b1b1590'), fd: 19 }
]
Type &quot;it&quot; for more&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4-6. Session 2 에서 데이터 삭제&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다른 세션에서 fd 가 70인 데이터를 삭제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1724302598374&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] transactionDB&amp;gt; db.consistency.remove ( { fd : 70 } )
{ acknowledged: true, deletedCount: 1 }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4-7. Session 1 에서 데이터 조회&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 70 의 값이 메모리에 상주해 있기 때문에 계속 조회할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1724302635802&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] transactionDB&amp;gt; it
[
  { _id: ObjectId('66c69fa0e648fd165b1b1591'), fd: 20 },
  { _id: ObjectId('66c69fa0e648fd165b1b1592'), fd: 21 },
  { _id: ObjectId('66c69fa0e648fd165b1b1593'), fd: 22 },
  { _id: ObjectId('66c69fa0e648fd165b1b1594'), fd: 23 },
  { _id: ObjectId('66c69fa0e648fd165b1b1595'), fd: 24 },
  { _id: ObjectId('66c69fa0e648fd165b1b1596'), fd: 25 },
  { _id: ObjectId('66c69fa0e648fd165b1b1597'), fd: 26 },
  { _id: ObjectId('66c69fa0e648fd165b1b1598'), fd: 27 },
  { _id: ObjectId('66c69fa0e648fd165b1b1599'), fd: 28 },
  { _id: ObjectId('66c69fa0e648fd165b1b159a'), fd: 29 },
  { _id: ObjectId('66c69fa0e648fd165b1b159b'), fd: 30 },
  { _id: ObjectId('66c69fa0e648fd165b1b159c'), fd: 31 },
  { _id: ObjectId('66c69fa0e648fd165b1b159d'), fd: 32 },
  { _id: ObjectId('66c69fa0e648fd165b1b159e'), fd: 33 },
  { _id: ObjectId('66c69fa0e648fd165b1b159f'), fd: 34 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a0'), fd: 35 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a1'), fd: 36 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a2'), fd: 37 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a3'), fd: 38 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a4'), fd: 39 }
]
Type &quot;it&quot; for more
[direct: mongos] transactionDB&amp;gt; it
[
  { _id: ObjectId('66c69fa0e648fd165b1b15a5'), fd: 40 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a6'), fd: 41 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a7'), fd: 42 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a8'), fd: 43 },
  { _id: ObjectId('66c69fa0e648fd165b1b15a9'), fd: 44 },
  { _id: ObjectId('66c69fa0e648fd165b1b15aa'), fd: 45 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ab'), fd: 46 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ac'), fd: 47 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ad'), fd: 48 },
  { _id: ObjectId('66c69fa0e648fd165b1b15ae'), fd: 49 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b0'), fd: 51 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b1'), fd: 52 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b2'), fd: 53 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b3'), fd: 54 },
  { _id: ObjectId('66c69fa0e648fd165b1b15b4'), fd: 55 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b5'), fd: 56 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b6'), fd: 57 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b7'), fd: 58 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b8'), fd: 59 },
  { _id: ObjectId('66c69fa1e648fd165b1b15b9'), fd: 60 }
]
Type &quot;it&quot; for more
[direct: mongos] transactionDB&amp;gt; it
[
  { _id: ObjectId('66c69fa1e648fd165b1b15ba'), fd: 61 },
  { _id: ObjectId('66c69fa1e648fd165b1b15bb'), fd: 62 },
  { _id: ObjectId('66c69fa1e648fd165b1b15bc'), fd: 63 },
  { _id: ObjectId('66c69fa1e648fd165b1b15bd'), fd: 64 },
  { _id: ObjectId('66c69fa1e648fd165b1b15be'), fd: 65 },
  { _id: ObjectId('66c69fa1e648fd165b1b15bf'), fd: 66 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c0'), fd: 67 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c1'), fd: 68 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c2'), fd: 69 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c3'), fd: 70 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c4'), fd: 71 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c5'), fd: 72 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c6'), fd: 73 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c7'), fd: 74 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c8'), fd: 75 },
  { _id: ObjectId('66c69fa1e648fd165b1b15c9'), fd: 76 },
  { _id: ObjectId('66c69fa1e648fd165b1b15ca'), fd: 77 },
  { _id: ObjectId('66c69fa1e648fd165b1b15cb'), fd: 78 },
  { _id: ObjectId('66c69fa1e648fd165b1b15cc'), fd: 79 },
  { _id: ObjectId('66c69fa1e648fd165b1b15cd'), fd: 80 }
]
Type &quot;it&quot; for more&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb yield</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/658</guid>
      <comments>https://mozi.tistory.com/658#entry658comment</comments>
      <pubDate>Wed, 21 Aug 2024 18:04:59 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 글로벌 잠금 설정과 해제방법</title>
      <link>https://mozi.tistory.com/657</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyUcra/btsI9icIhv4/wPHoFc4k4BF7MNnm4Rzkpk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyUcra/btsI9icIhv4/wPHoFc4k4BF7MNnm4Rzkpk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyUcra/btsI9icIhv4/wPHoFc4k4BF7MNnm4Rzkpk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyUcra%2FbtsI9icIhv4%2FwPHoFc4k4BF7MNnm4Rzkpk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 잠금&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서는 멀티 쓰레드의 동시 처리 중에 발생할 수 있는 쓰레드 간의 충돌 문제를 방지하기 위해 잠금을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;1-1. MongoDB 잠금유형&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;묵시적 잠금&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;쿼리가 실행될 때 자동으로 획득했다 해제되는 잠금&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;명시적 잠금&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;사용자가 명령어로 잠금을 획득&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 글로벌 잠금 획득 해제&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3-1. fsyncLock 로 획득&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsyncLock 은 글로벌 잠금을 획득하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버 인스턴스에 단 하나만 있는 잠금으로, 이를 인스턴스 잠금이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 명령어로 잠금을 획득할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1723527771828&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.fsyncLock( { fsync : 1, lock : true } )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsync 옵션을 1로 설정하면 디스크에 기록되지 못한 데이터를 모두 디스크로 플러시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock 옵션이 true 이면 글로벌 잠금을 획득한다. 만약 false 면 데이터파일만 디스크로 플러시하고 잠금을 걸지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. fsyncUnLock 으로 해제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsyncUnLock 으로 글로벌 잠금을 해제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1724198145250&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.fsyncUnlock()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 글로벌 잠금 lock=true 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. Session1 적재&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌잠금이 활성화 되기 전 적재가 잘 되고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1724197801362&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] fsyncDB&amp;gt; db.fsynCollection.insert( { &quot;id&quot; : 1, &quot;description&quot; : &quot;Before fsyncLock&quot; } )
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66c52b881b30070ca91b157d') }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. Session 2 글로벌잠금 획득&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsyncLock 함수를 사용하여 글로벌 잠금을 획득한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌잠금을 획득하면 info 에 now locked against writes 가 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1724197864880&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; db.fsyncLock( { fsync : 1, lock : true } )
{
  numFiles: 1,
  all: {
    raw: {
      'rs2/127.0.0.1:40001': {
        info: 'now locked against writes, use db.fsyncUnlock() to unlock',
        lockCount: Long('1'),
        seeAlso: 'http://dochub.mongodb.org/core/fsynccommand',
        ok: 1
      },
      'rs1/127.0.0.1:30001': {
        info: 'now locked against writes, use db.fsyncUnlock() to unlock',
        lockCount: Long('1'),
        seeAlso: 'http://dochub.mongodb.org/core/fsynccommand',
        ok: 1
      },
      'configRepl/127.0.0.1:20000': {
        info: 'now locked against writes, use db.fsyncUnlock() to unlock',
        lockCount: Long('1'),
        seeAlso: 'http://dochub.mongodb.org/core/fsynccommand',
        ok: 1
      }
    }
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1724197771, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1724197771, i: 1 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 글로벌잠금을 획득한 세션도 쓰기를 할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1724198478516&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] fsyncDB&amp;gt; db.fsynCollection.insert( { &quot;id&quot; : 3, &quot;description&quot; : &quot;Global Lock Session&quot; } )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPJjKo/btsI81bgQTa/GHq0VIOz3FGuYDX3re6KK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPJjKo/btsI81bgQTa/GHq0VIOz3FGuYDX3re6KK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPJjKo/btsI81bgQTa/GHq0VIOz3FGuYDX3re6KK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPJjKo%2FbtsI81bgQTa%2FGHq0VIOz3FGuYDX3re6KK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;44&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-3. Session 1 적재대기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래의 적재 명령어는 완료되지 못하고 대기하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1724197808909&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] fsyncDB&amp;gt; db.fsynCollection.insert( { &quot;id&quot; : 2, &quot;description&quot; : &quot;After fsyncLock&quot; } )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAS6wK/btsI9NpKR4a/C6DzVLAkciURkA9gZQUpvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAS6wK/btsI9NpKR4a/C6DzVLAkciURkA9gZQUpvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAS6wK/btsI9NpKR4a/C6DzVLAkciURkA9gZQUpvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAS6wK%2FbtsI9NpKR4a%2FC6DzVLAkciURkA9gZQUpvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;38&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-5. Session 3 조회가능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 컬렉션과&amp;nbsp;다른 데이터베이스의 컬렉션을 조회할 때 모두 대기가 발생하지 않고 바로 조회된다.&lt;/p&gt;
&lt;pre id=&quot;code_1724198736196&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] fsyncDB&amp;gt; db.fsynCollection.find()
[
  {
    _id: ObjectId('66c52b881b30070ca91b157d'),
    id: 1,
    description: 'Before fsyncLock'
  }
]


[direct: mongos] fsyncDB&amp;gt; use indexDB()
switched to db indexDB

[direct: mongos] indexDB&amp;gt; db.ttlIndex.find()
[
  {
    _id: ObjectId('66baf3d401027809e11b1585'),
    Number: 4,
    createdAt: 'Sample String'
  },
  {
    _id: ObjectId('66bb014c28fe1d22af1b157e'),
    Number: 1,
    createdAt: '2024-08-10T05:42:36.666Z'
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-6. Session 2 글로벌잠금 해제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 잠금을 해제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1724198167793&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; db.fsyncUnlock()
{
  raw: {
    'rs2/127.0.0.1:40001': { info: 'fsyncUnlock completed', lockCount: Long('1'), ok: 1 },
    'configRepl/127.0.0.1:20000': { info: 'fsyncUnlock completed', lockCount: Long('1'), ok: 1 },
    'rs1/127.0.0.1:30001': { info: 'fsyncUnlock completed', lockCount: Long('1'), ok: 1 }
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1724197771, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1724197771, i: 1 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-7. Session 1 적재&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌잠금이 해제될 때 적재가 시도된 Session 1 의 적재가 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1724198221705&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] fsyncDB&amp;gt; db.fsynCollection.insert( { &quot;id&quot; : 2, &quot;description&quot; : &quot;After fsyncLock&quot; } )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NT05Z/btsI9PA50Ur/lgWXVkerVewnwN2P6niaM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NT05Z/btsI9PA50Ur/lgWXVkerVewnwN2P6niaM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NT05Z/btsI9PA50Ur/lgWXVkerVewnwN2P6niaM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNT05Z%2FbtsI9PA50Ur%2FlgWXVkerVewnwN2P6niaM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;78&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;5. 글로벌 잠금 lock=false 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. Session2 글로벌잠금 획득&lt;/h3&gt;
&lt;pre id=&quot;code_1724201758490&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; db.fsyncLock( { fsync : 1, lock : false } )
{
  numFiles: 1,
  all: {
    raw: {
      'rs2/127.0.0.1:40001': {
        info: 'now locked against writes, use db.fsyncUnlock() to unlock',
        lockCount: Long('1'),
        seeAlso: 'http://dochub.mongodb.org/core/fsynccommand',
        ok: 1
      },
      'configRepl/127.0.0.1:20000': {
        info: 'now locked against writes, use db.fsyncUnlock() to unlock',
        lockCount: Long('1'),
        seeAlso: 'http://dochub.mongodb.org/core/fsynccommand',
        ok: 1
      },
      'rs1/127.0.0.1:30001': {
        info: 'now locked against writes, use db.fsyncUnlock() to unlock',
        lockCount: Long('1'),
        seeAlso: 'http://dochub.mongodb.org/core/fsynccommand',
        ok: 1
      }
    }
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1724201731, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1724201731, i: 1 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2. Session1 적재대기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 적재는 대기한다. 스터디하는 책의 설명과는 다른부분이 있어 7버전에서는 변경되었나 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 아래 6. 에서 설명하는 currentOp 에서 fsyncLock 필드값은 true 로 되어있다.&lt;/p&gt;
&lt;pre id=&quot;code_1724201809039&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] fsyncDB&amp;gt; db.fsynCollection.insert( { &quot;id&quot; : 4, &quot;description&quot; : &quot;After fsyncLock lock=false&quot; } )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTElxy/btsI9ZDv372/1X93i9VdYCkobUQ9ckPiZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTElxy/btsI9ZDv372/1X93i9VdYCkobUQ9ckPiZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTElxy/btsI9ZDv372/1X93i9VdYCkobUQ9ckPiZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTElxy%2FbtsI9ZDv372%2F1X93i9VdYCkobUQ9ckPiZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;47&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 글로벌 잠금 설정 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;currentOp 명령으로 확인이 가능하다. 필드명중 fsyncLock 이 true 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 글로벌잠금이 해제되어 있다면 fsyncLock 필드가 조회되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1724200375568&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] fsyncDB&amp;gt; db.currentOp()
{
  inprog: [
    {
      shard: 'rs2',
      fsyncLock: true,
      type: 'op',
      host: 'ip-172-31-7-169.ap-northeast-2.compute.internal:40001',
      desc: 'Checkpointer',
      active: true,
      currentOpTime: '2024-08-21T00:26:38.863+00:00',
      opid: 'rs2:28779150',
      op: 'none',
      ns: '',
      redacted: false,
      command: {},
      numYields: 0,
      locks: {},
      waitingForLock: false,
      lockStats: {},
      waitingForFlowControl: false,
      flowControlStats: {}
    },
    {
      shard: 'rs2',
      fsyncLock: true,
      type: 'op',
      host: 'ip-172-31-7-169.ap-northeast-2.compute.internal:40001',
      desc: 'OplogCapMaintainerThread-local.oplog.rs',
      active: true,
      currentOpTime: '2024-08-21T00:26:38.863+00:00',
      opid: 'rs2:17',
      op: 'none',
      ns: '',
      redacted: false,
      command: {},
      numYields: 0,
      locks: {},
      waitingForLock: false,
      lockStats: {
        FeatureCompatibilityVersion: { acquireCount: { w: Long('1') } },
        ReplicationStateTransition: { acquireCount: { w: Long('1') } },
        Global: { acquireCount: { w: Long('1') } }
      },
      waitingForFlowControl: false,
      flowControlStats: {}
    },
    {
      shard: 'rs2',
      fsyncLock: true,

...
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb 글로벌잠금</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/657</guid>
      <comments>https://mozi.tistory.com/657#entry657comment</comments>
      <pubDate>Wed, 21 Aug 2024 09:35:34 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] TTL 인덱스 생성과 도큐먼트 적재</title>
      <link>https://mozi.tistory.com/656</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uh31n/btsI4chFbl1/6m3dJF6gPN5YpxrkbDXZj1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uh31n/btsI4chFbl1/6m3dJF6gPN5YpxrkbDXZj1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uh31n/btsI4chFbl1/6m3dJF6gPN5YpxrkbDXZj1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUh31n%2FbtsI4chFbl1%2F6m3dJF6gPN5YpxrkbDXZj1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB TTL 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스는 특정 시간이 지나면 도큐먼트를 자동으로 삭제하는 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스는 주로 로그 데이터나 세션 정보 등과 같이 일정 기간 후 자동으로 삭제해야 하는 데이터를 관리할 때 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 TTL 인덱스 필드값에 시계열 데이터가 적재되지 않으면 지워지지 않는다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;1-1. MongoDB 인덱스 유형&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;단일 필드 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;특정 필드에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;복합 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;여러 필드를 조합한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;해시 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;해시된 값으로 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;텍스트 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;텍스트 검색을 위한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;2dsphere 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;지리공간 데이터에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유일 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;유일한 값만 허용&lt;br /&gt;- 로컬서버 기준에 한정, 샤드서버로 데이터가 분산되는 경우에는 어플리케이션에서 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. TTL 인덱스 생성&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3-1. TTL인덱스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스를 적용하려면 인덱스 필드에 날짜/시간 데이터를 입력하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성 시 expireAfterSeconds 옵션을 지정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723527771828&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.ttlIndex.createIndex( { &quot;createdAt&quot; : 1 }, { expireAfterSeconds : 10 } )
createdAt_1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. TTL 인덱스에 적재 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. TTL 인덱스에 데이터를 적재&lt;/h3&gt;
&lt;pre id=&quot;code_1723528105871&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 현재시간으로 데이터 적재
[direct: mongos] indexDB&amp;gt; db.ttlIndex.insertMany( [ { &quot;Number&quot; : 1 , &quot;createdAt&quot; : new Date() } ,
                                                    { &quot;Number&quot; : 2 , &quot;createdAt&quot; : new Date() } ,
                                                    { &quot;Number&quot; : 3 , &quot;createdAt&quot; : new Date() } ] )
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('66baf24c01027809e11b157f'),
    '1': ObjectId('66baf24c01027809e11b1580'),
    '2': ObjectId('66baf24c01027809e11b1581')
  }
}

# 데이터 조회
[direct: mongos] indexDB&amp;gt; db.ttlIndex.find();
[
  {
    _id: ObjectId('66baf24c01027809e11b157f'),
    Number: 1,
    createdAt: ISODate('2024-08-13T05:42:36.666Z')
  },
  {
    _id: ObjectId('66baf24c01027809e11b1580'),
    Number: 2,
    createdAt: ISODate('2024-08-13T05:42:36.666Z')
  },
  {
    _id: ObjectId('66baf24c01027809e11b1581'),
    Number: 3,
    createdAt: ISODate('2024-08-13T05:42:36.666Z')
  }
]

# 10초후 데이터 조회 시 결과 없음
[direct: mongos] indexDB&amp;gt; db.ttlIndex.find();

[direct: mongos] indexDB&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. TTL 인덱스에 시계열 필드가 아닌 데이터 적재&lt;/h3&gt;
&lt;pre id=&quot;code_1723528234953&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Number 4 의 createdAt 필드에 문자열 데이터를 적재
[direct: mongos] indexDB&amp;gt; db.ttlIndex.insertMany( [ { &quot;Number&quot; : 1 , &quot;createdAt&quot; : new Date() } ,
                                                    { &quot;Number&quot; : 2 , &quot;createdAt&quot; : new Date() } ,
                                                    { &quot;Number&quot; : 3 , &quot;createdAt&quot; : new Date() } ,
                                                    { &quot;Number&quot; : 4 , &quot;createdAt&quot; : &quot;Sample String&quot; } ] )
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('66baf3d401027809e11b1582'),
    '1': ObjectId('66baf3d401027809e11b1583'),
    '2': ObjectId('66baf3d401027809e11b1584'),
    '3': ObjectId('66baf3d401027809e11b1585')
  }
}

# 데이터 조회
[direct: mongos] indexDB&amp;gt; db.ttlIndex.find();
[
  {
    _id: ObjectId('66baf3d401027809e11b1582'),
    Number: 1,
    createdAt: ISODate('2024-08-13T05:49:08.657Z')
  },
  {
    _id: ObjectId('66baf3d401027809e11b1583'),
    Number: 2,
    createdAt: ISODate('2024-08-13T05:49:08.657Z')
  },
  {
    _id: ObjectId('66baf3d401027809e11b1584'),
    Number: 3,
    createdAt: ISODate('2024-08-13T05:49:08.657Z')
  },
  {
    _id: ObjectId('66baf3d401027809e11b1585'),
    Number: 4,
    createdAt: 'Sample String'
  }
]

# 10초 후 데이터 조회 - 시계열이 아닌 데이터는 지워지지 않음
[direct: mongos] indexDB&amp;gt; db.ttlIndex.find();
[
  {
    _id: ObjectId('66baf3d401027809e11b1585'),
    Number: 4,
    createdAt: 'Sample String'
  }
]

[direct: mongos] indexDB&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb ttl 인덱스</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/656</guid>
      <comments>https://mozi.tistory.com/656#entry656comment</comments>
      <pubDate>Tue, 13 Aug 2024 14:51:29 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] Partial 인덱스 생성과 주의사항</title>
      <link>https://mozi.tistory.com/655</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wi6Yx/btsI26aRjN0/fiFJNWH35Q4wCXCOT06yK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wi6Yx/btsI26aRjN0/fiFJNWH35Q4wCXCOT06yK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wi6Yx/btsI26aRjN0/fiFJNWH35Q4wCXCOT06yK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwi6Yx%2FbtsI26aRjN0%2FfiFJNWH35Q4wCXCOT06yK1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB Partial 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 효율적으로 사용하기 위해 MongoDB 는 인덱스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 인덱스 유형은 매우 다양하다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;1-1. MongoDB 인덱스 유형&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;단일 필드 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;특정 필드에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;복합 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;여러 필드를 조합한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;해시 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;해시된 값으로 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;텍스트 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;텍스트 검색을 위한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;2dsphere 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;지리공간 데이터에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유일 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;유일한 값만 허용&lt;br /&gt;- 로컬서버 기준에 한정, 샤드서버로 데이터가 분산되는 경우에는 어플리케이션에서 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 인덱스의 옵션에서 Partial 을 제공하는데 , 이 때는 이 필드에 포함된 값이 특정 값 이상일 때만 인덱스에 포함시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Partial 인덱스 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. Partial 인덱스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;partialFilterExpression 에서 특정 필드가 특정 값 이상일 때 인덱스 구성에 포함되도록 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Score 인덱스를 만드는데 이 때 50 이상인 경우에 대해서만 인덱스를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 50 이상인 값을 조회할 때만 인덱스를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1723454467555&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.partialIndex.createIndex ( { Score : 1 } , { partialFilterExpression : { Score : { $gt : 50 } } } )
Score_1


[direct: mongos] indexDB&amp;gt; db.partialIndex.insert ( { &quot;Name&quot; : &quot;N1&quot; , &quot;Score&quot; : 39 } )
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b9d3ade51e7f66d81b157d') }
}

[direct: mongos] indexDB&amp;gt; db.partialIndex.insert ( { &quot;Name&quot; : &quot;N2&quot; , &quot;Score&quot; : 51 } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b9d3b3e51e7f66d81b157e') }
}

[direct: mongos] indexDB&amp;gt; db.partialIndex.insert ( { &quot;Name&quot; : &quot;N3&quot; , &quot;Score&quot; : 72 } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b9d3b8e51e7f66d81b157f') }
}

[direct: mongos] indexDB&amp;gt; db.partialIndex.insert ( { &quot;Name&quot; : &quot;N4&quot; , &quot;Score&quot; : 49 } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b9d3bee51e7f66d81b1580') }
}

[direct: mongos] indexDB&amp;gt; db.partialIndex.insert ( { &quot;Name&quot; : &quot;N5&quot; } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b9d3ebe51e7f66d81b1581') }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Partial 인덱스 조회&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 만들어 놓은 Partial 인덱스를 사용하는지 실행계획으로 확인해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. Partial 범위에 포함된 데이터 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;50 이상인 데이터를 조회하면 winningPlan.stage 에 FETCH 로 나온다.&lt;/p&gt;
&lt;pre id=&quot;code_1723514430738&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.partialIndex.find({&quot;Score&quot;: { $gt : 50 } } ).explain()
{
  queryPlanner: {
    mongosPlannerVersion: 1,
    winningPlan: {
      stage: 'SINGLE_SHARD',
      shards: [
        {
          shardName: 'rs1',
          connectionString: 'rs1/127.0.0.1:30001',
          serverInfo: {
            host: 'ip-172-31-7-169.ap-northeast-2.compute.internal',
            port: 30001,
            version: '7.0.12',
            gitVersion: 'b6513ce0781db6818e24619e8a461eae90bc94fc'
          },
          namespace: 'indexDB.partialIndex',
          indexFilterSet: false,
          parsedQuery: { Score: { '$gt': 50 } },
          queryHash: '14DDA81F',
          planCacheKey: '8FF5F16D',
          maxIndexedOrSolutionsReached: false,
          maxIndexedAndSolutionsReached: false,
          maxScansToExplodeReached: false,
          winningPlan: {
            stage: 'FETCH',
            inputStage: {
              stage: 'IXSCAN',
              keyPattern: { Score: 1 },
              indexName: 'Score_1',
              isMultiKey: false,
              multiKeyPaths: { Score: [] },
              isUnique: false,
              isSparse: false,
              isPartial: true,
              indexVersion: 2,
              direction: 'forward',
              indexBounds: { Score: [ '(50, inf.0]' ] }
            }
          },
          rejectedPlans: []
        }
      ]
    }
  },
  serverInfo: {
    host: 'ip-172-31-7-169.ap-northeast-2.compute.internal',
    port: 20001,
    version: '7.0.12',
    gitVersion: 'b6513ce0781db6818e24619e8a461eae90bc94fc'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600,
    internalQueryFrameworkControl: 'trySbeRestricted'
  },
  command: {
    find: 'partialIndex',
    filter: { Score: { '$gt': 50 } },
    lsid: { id: UUID('84e46ccb-a44d-48ca-817b-2a41d457dbf5') },
    '$clusterTime': {
      clusterTime: Timestamp({ t: 1723514261, i: 2 }),
      signature: {
        hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
        keyId: 0
      }
    },
    '$db': 'indexDB'
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1723514406, i: 2 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1723514401, i: 1 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. Partial 범위에 포함되지 않는 데이터 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;49 이상인 데이터를 조회하면 winningPlan.stage 에 COLLSCAN (테이블스캔) 으로 나온다.&lt;/p&gt;
&lt;pre id=&quot;code_1723514470141&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.partialIndex.find({&quot;Score&quot;: { $gt : 49 } } ).explain()
{
  queryPlanner: {
    mongosPlannerVersion: 1,
    winningPlan: {
      stage: 'SINGLE_SHARD',
      shards: [
        {
          shardName: 'rs1',
          connectionString: 'rs1/127.0.0.1:30001',
          serverInfo: {
            host: 'ip-172-31-7-169.ap-northeast-2.compute.internal',
            port: 30001,
            version: '7.0.12',
            gitVersion: 'b6513ce0781db6818e24619e8a461eae90bc94fc'
          },
          namespace: 'indexDB.partialIndex',
          indexFilterSet: false,
          parsedQuery: { Score: { '$gt': 49 } },
          queryHash: '14DDA81F',
          planCacheKey: '81864C8B',
          maxIndexedOrSolutionsReached: false,
          maxIndexedAndSolutionsReached: false,
          maxScansToExplodeReached: false,
          winningPlan: {
            stage: 'COLLSCAN',
            filter: { Score: { '$gt': 49 } },
            direction: 'forward'
          },
          rejectedPlans: []
        }
      ]
    }
  },
  serverInfo: {
    host: 'ip-172-31-7-169.ap-northeast-2.compute.internal',
    port: 20001,
    version: '7.0.12',
    gitVersion: 'b6513ce0781db6818e24619e8a461eae90bc94fc'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600,
    internalQueryFrameworkControl: 'trySbeRestricted'
  },
  command: {
    find: 'partialIndex',
    filter: { Score: { '$gt': 49 } },
    lsid: { id: UUID('84e46ccb-a44d-48ca-817b-2a41d457dbf5') },
    '$clusterTime': {
      clusterTime: Timestamp({ t: 1723514406, i: 2 }),
      signature: {
        hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
        keyId: 0
      }
    },
    '$db': 'indexDB'
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1723514456, i: 2 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1723514451, i: 1 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Partial 인덱스 주의사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial 인덱스는 NULL 비교가 상황에 따라 때로는 다르게 떄로는 같게 취급된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 조회되어야 하는 데이터가 조회되지 않을수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 Name 이 N5 인 도큐먼트를 적재할 때 Score 를 명시하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 N6 에서 Score 는 null 이라고 추가로 명시해 적재했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 후 Partial 인덱스 힌트를 사용하지 않을때와 사용할때 조회해본 결과를 비교해본다.&lt;/p&gt;
&lt;pre id=&quot;code_1723514824048&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.partialIndex.insert( { &quot;Name&quot; : &quot;N6&quot;, &quot;Score&quot;:null } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66babf0001027809e11b157d') }
}


# 일반적인 조회로는 N5 와 N6 이 출력된다.
[direct: mongos] indexDB&amp;gt; db.partialIndex.find({&quot;Score&quot;: null } )
[
  { _id: ObjectId('66b9d3ebe51e7f66d81b1581'), Name: 'N5' },
  {
    _id: ObjectId('66babf0001027809e11b157d'),
    Name: 'N6',
    Score: null
  }
]

# Partial 인덱스 힌트를 사용하면 조회되지 않는것을 확인할 수 있다.
# 결과 빈 값
[direct: mongos] indexDB&amp;gt; db.partialIndex.find({&quot;Score&quot;: null } ).hint( { &quot;Score&quot; : 1 } )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.2326%;&quot;&gt;힌트 없을 때&lt;/td&gt;
&lt;td style=&quot;width: 74.7674%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kP5VS/btsI05ybai6/9wJv6MWhMgeWISaCkLs68k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kP5VS/btsI05ybai6/9wJv6MWhMgeWISaCkLs68k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kP5VS/btsI05ybai6/9wJv6MWhMgeWISaCkLs68k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkP5VS%2FbtsI05ybai6%2F9wJv6MWhMgeWISaCkLs68k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;136&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.2326%;&quot;&gt;힌트 있을 때&lt;/td&gt;
&lt;td style=&quot;width: 74.7674%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FfWFN/btsI3ezZY64/vLQI46rseIvsZrHBa2KIkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FfWFN/btsI3ezZY64/vLQI46rseIvsZrHBa2KIkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FfWFN/btsI3ezZY64/vLQI46rseIvsZrHBa2KIkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFfWFN%2FbtsI3ezZY64%2FvLQI46rseIvsZrHBa2KIkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;74&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb partial 인덱스</category>
      <category>mongodb partial 인덱스 주의사항</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/655</guid>
      <comments>https://mozi.tistory.com/655#entry655comment</comments>
      <pubDate>Mon, 12 Aug 2024 18:30:23 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 유니크 인덱스 생성 방법</title>
      <link>https://mozi.tistory.com/654</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LuRfT/btsI1lAzpL1/sp832KO7zD2b1wqdFlREG0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LuRfT/btsI1lAzpL1/sp832KO7zD2b1wqdFlREG0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LuRfT/btsI1lAzpL1/sp832KO7zD2b1wqdFlREG0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLuRfT%2FbtsI1lAzpL1%2Fsp832KO7zD2b1wqdFlREG0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 유니크 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 효율적으로 사용하기 위해 MongoDB 는 인덱스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 인덱스 유형은 매우 다양하다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;1-1. MongoDB 인덱스 유형&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;단일 필드 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;특정 필드에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;복합 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;여러 필드를 조합한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;해시 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;해시된 값으로 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;텍스트 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;텍스트 검색을 위한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;2dsphere 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;지리공간 데이터에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유일 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;유일한 값만 허용&lt;br /&gt;- 로컬서버 기준에 한정, 샤드서버로 데이터가 분산되는 경우에는 어플리케이션에서 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 유니크 인덱스 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 유니크 인덱스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성 시 unique 옵션을 true 로 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Number 필드를 Unique 인덱스로 생성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1723453437165&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.uniqueIndex.createIndex( { &quot;Number&quot; : 1 }, { unique: true } )
Number_1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 유니크 인덱스 적재&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 단일 필드 인덱스 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 적재 시 인덱스의 필드를 지정하지 않더라도 NULL 값으로 지정된다.&lt;/p&gt;
&lt;pre id=&quot;code_1723453506208&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.uniqueIndex.insert( { &quot;Name&quot; : &quot;Hong&quot; } )
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b9cf8d23b56ce2101b157d') }
}


[direct: mongos] indexDB&amp;gt; db.uniqueIndex.insert( { &quot;Name&quot; : &quot;Park&quot; } )
Uncaught:
MongoBulkWriteError: E11000 duplicate key error collection: indexDB.uniqueIndex index: Number_1 dup key: { Number: null }
Result: BulkWriteResult {
  insertedCount: 0,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: {}
}
Write Errors: [
  WriteError {
    err: {
      index: 0,
      code: 11000,
      errmsg: 'E11000 duplicate key error collection: indexDB.uniqueIndex index: Number_1 dup key: { Number: null }',
      errInfo: undefined,
      op: { Name: 'Park', _id: ObjectId('66b9cf9c23b56ce2101b157e') }
    }
  }
]


[direct: mongos] indexDB&amp;gt; db.uniqueIndex.insert( { &quot;Name&quot; : &quot;Park&quot; , Number : null } )
Uncaught:
MongoBulkWriteError: E11000 duplicate key error collection: indexDB.uniqueIndex index: Number_1 dup key: { Number: null }
Result: BulkWriteResult {
  insertedCount: 0,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: {}
}
Write Errors: [
  WriteError {
    err: {
      index: 0,
      code: 11000,
      errmsg: 'E11000 duplicate key error collection: indexDB.uniqueIndex index: Number_1 dup key: { Number: null }',
      errInfo: undefined,
      op: {
        Name: 'Park',
        Number: null,
        _id: ObjectId('66b9cfaf23b56ce2101b157f')
      }
    }
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. 서브 도큐먼트 배열 유니크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 도큐먼트 배열의 유니크는 중복된 값을 체크하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1723453734330&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.subuniqueIndex.createIndex( { &quot;Number.ASC&quot; : 1 } , { unique : true } )
Number.ASC_1

[direct: mongos] indexDB&amp;gt; db.subuniqueIndex.insert( { &quot;Name&quot; : &quot;Park&quot;, 
                                                      &quot;Number&quot; : [ { &quot;ASC&quot; : 1 } , { &quot;ASC&quot; : 1 } ] } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b9d10a23b56ce2101b1580') }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb 유니크 인덱스</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/654</guid>
      <comments>https://mozi.tistory.com/654#entry654comment</comments>
      <pubDate>Mon, 12 Aug 2024 18:09:17 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 인덱스(3)</title>
      <link>https://mozi.tistory.com/653</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPmKbu/btsI1wud5lU/cfkDetgEH9i3JeCkrA9720/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPmKbu/btsI1wud5lU/cfkDetgEH9i3JeCkrA9720/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPmKbu/btsI1wud5lU/cfkDetgEH9i3JeCkrA9720/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPmKbu%2FbtsI1wud5lU%2FcfkDetgEH9i3JeCkrA9720%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스 속성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 필드가 NULL 이 아닌 경우나 지정된 조건을 만족하는 경우에만 인덱스에 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프라이머리 키와 세컨드리 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 프라이머리 키 필드는 사용자가 필드의 이름을 결정할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 키 필드는 무조건 _id 라는 이름으로 도큐먼트에 저장되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 MongoDB 가 자동으로 _id 라는 이름의 필드를 생성해서 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션마다 단 하나의 프라이머리 키만 가질 수 있고, 그 외의 인덱스는 모두 세컨드리 인덱스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 키는 반드시 유일한 값을 가져야 하므로 유니크 인덱스를 생성할 수 없는 해시 인덱스는 프라이머리 키로 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 하나의 샤드에서는 중복 값에 대한 체크를 MongoDB 가 해주지만, 샤딩 된 경우 샤드간 프라이머리 키 값의 중복체크를 응용프로그램에서 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 키는 _id 필드로 고정되어 있어 컴파운드 인덱스를 프라이머리 키로 선정할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 여러 필드를 묶어서 프라이머리 키로 설정할 수는 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우는 서브 도큐먼트를 _id 필드에 저장하는 방법을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 도큐먼트를 인덱스 키로 사용하는 경우에는 서브 도큐먼트의 필드 개수와 이름 값까지 같아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유니크 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블이나 인덱스에 같은 값을 2개 이상 저장할 수 없음을 의미하는데, MongoDB 에서는 인덱스 없이 유니크 제약을 설정하는 방법이 ㅇ벗다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유니크 인덱스라고 해서 NULL 값이 제한되지는 않고 프라이머리 키는 기본적으로 유니크 속성이 부여된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 유니크 인덱스는 NULL 이나 존재하지 않는 값을 같은 값으로 취급하기 때문에 하나 이상의 도큐먼트가 NULL 일 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니크 인덱스는 도큐먼트 간의 중복된 값은 체크하지만, 하나의 도큐먼트내에서 중복된 값을 체크하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩을 적용하지 않는 MongoDB 는 아무 필드나 선택해서 유니크 인덱스를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 샤딩을 적용한 경우에는 샤드키를 선행 필드로 가지는 인덱스에서만 유니크 인덱스를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Partial 인덱스와 Sparse 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 NULL 값을 가지는 필드에 대해 인덱싱할것인지 선택할 수 있는 Sparse 인덱스와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 조건에 따라 인덱싱하는 Partial 인덱스 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sparse 인덱스는 도큐먼트에 인덱스 대상 필드를 명시한 경우에만 인덱스에 키 엔트리를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드의 값이 NULL 이라 하더라도 필드가 명시되어 있으면 인덱싱 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 컴파운드 인덱스인 경우에는 인덱스를 구성하는 필드 중 하나라도 명시하면 인덱싱 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 컴파운드 인덱스를 구성하는 모든 필드가 누락된 경우에만 인덱싱이 제외된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 컬렉션의 많은 도큐먼트가 특정 필드를 가지지 않는데, 이 필드를 가지는 도큐먼트만 검색을 실행해야 하는 경우 Sparse 가 도움이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sparse 인덱스에서 한가지 주의해야 할 사항은 인덱스 필드가 없는 경우를 조회해야 하는 쿼리는 Sparse 인덱스를 사용하지 못한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 필드의 값이 NULL 인 경우를 검색하는 쿼리다. 이는 MongoDB 에서 존재하지 않는 필드의 비교와 NULL 비교가 상황에 따라 다르게 때로는 같게 취급되는 것을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전부터 Sparse 인덱스보다 다양한 기능을 활용할 수 있는 Partial 인덱스 기능이 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial 인덱스가 나오고 Sparse 인덱스 기능을 사용할 필요가 없어졌는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 인덱싱을 수행할 도큐먼트를 선택할 수 있고, 조건에 일치하는 도큐먼트에 대해서만 인덱스를 관리하므로 조건부 인덱스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성 시 partialFilterExpression 옵션을 이용해서 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건에 사용할 수 있는 연산자는 일반적으로 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 일치 표현식&amp;nbsp; $eq&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필드가 존재하는 경우 $exists : true&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 크다 또는 작다 비교 표현식 $gt, $gte, $lt, $lte&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필드 값의 데이터 타입 비교 표현식 $type&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 조건을 AND 로 결합하는 표신식 $and&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sparse 인덱스는 인덱싱 대상 필드의 존재 여부에 대해서만 선택할 수 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial 인덱스는 인덱스 대상 필드와 무관한 필드에 대한 표현식도 조건으로 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial 인덱스도 인덱스를 커버할 수 없는 조건의 쿼리는 인덱스를 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial 인덱스를 사용하려면 쿼리의 조건에 인덱스의 생성 조건과 일치 또는 부분 범위의 조건을 명시해야만 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial , Sparse 인덱스 모두 유니크 인덱스로 생성하면 조건에 만족해서 인덱싱되는 키 값에 대해서만 유니크 제약 사항이 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Partial 인덱스도 결과가 불완전해질수 있는 경우 Parital 인덱스를 활용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 사용자가 쿼리 힌트를 주면 불완전한 결과라고 하더라도 인덱스를 이용해서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TTL 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 도큐먼트가 언제까지 유효한지를 판단하여 더이상 유효하지 않은 도큐먼트는 자동으로 삭제되게 하는 기능의 인덱스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 TTL Monitor 라는 쓰레드가 지정된 시간 간격(기본값 1분)으로 1회씩 깨어나 지정된 시간보다 오래된 도큐먼트를 찾아 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 TTL 인덱스는 쿼리의 검색 성능 향상을 위한 인덱스가 아니라 Monitor 쓰레드가 삭제할 도큐먼트를 찾기 위한 인덱스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성 시 expireAfterSeconds 속성을 설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TTL Monitor 쓰레드의 삭제 주기 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ttlMonitorSleepSecs 파라미터 값을 조정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TTL Monitor 의 로그 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL Monitor 쓰레드가 깨어나 작업하는 내용은 기본적으로 MongoDB 서버의 로그에 기록되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 많은 도큐먼트를 삭제하거나 인덱스를 많이 가진 컬렉션에 대해 도큐먼트를 삭제하는 경우 서버 부하가 높아질 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서버 부하가 주기적으로 높아진다면 TTL Monitor 쓰레드의 도큐먼트 삭제를 의심할 수 있는데, 이 때는 Log 를 활성화 해 원인이 TTL 때문인지 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTLMonitor 라는 서두로 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 내용에는 언제 쓰레드가 깨어나 얼마나 많은 도큐먼트를 삭제했는지 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이를 계산하면 삭제하는데 걸린 시간을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 서버 구동 후 몇번이나 실행되었고 얼마나 많은 도큐먼트를 삭제했는지는 db.serverStatus() 결과로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TTL Monitor 쓰레드 정지 및 시작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드를 멈추는 방법은 2가지가 있는데, 시작 옵션을 이용해 서버가 시작될 때부터 비활성화 하는 방법이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 하나는 실행중인 서버의 TTL Montior 를 동적으로 중지시키는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ttlMonitorEnabled 로 설정이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TTL 인덱스와 Partial 인덱스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스 또한 다른 인덱스처럼 Partial 인덱스로 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TTL 인덱스와 복제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스를 이용해서 데이터를 삭제하는 TTL Monitor 쓰레드는 프라이머리와 세컨드리 멤버 모두 실행되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 TTL Monitor 쓰레드가 도큐먼트를 삭제하는 작업은 레플리카 셋의 프라이머리 멤버에서만 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL Monitor 쓰레드가 도큐먼트를 삭제하면 OpLog 에 기록되고, OpLog 를 세컨드리 멤버가 읽어 자신도 동일하게 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TTL 인덱스의 주의 사항&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스로 데이터를 삭제하는 작업은 주로 오래된 데이터가 삭제될 가능성이 높은데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크를 읽어야만 처리할 수 있는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 그 컬렉션에 있는 모든 인덱스에 키 엔트리를 삭제해야 하므로 많은 디스크 읽기를 유발하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTL 인덱스는 반드시 Date 타입의 단일 필드로만 인덱스를 생성할 수 있으며, 여러 필드를 묶어서 컴파운드 인덱스를 생성할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 타임스탬프 값을 내장하고 있는 _id 필드에 대해서도 TTL 인덱스를 생성할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 콜레이션 (대소문자 구분)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.2 버전 대소문자 검색&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전까지 기본적으로 대소문자를 구분하기 때문에&amp;nbsp; 모두 소문자로 통일한 문자열을 별도로 지정하곤 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4 버전부터 컬렉션과 인덱스에 대해 콜레이션을 지정할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;콜레이션과 인덱스 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜레이션은 문자열의 정렬과 비교 규칙을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성 시 특정 콜레이션을 지정하면 새로 생성하지 않는 이상 다른 콜레이션으로 변경이 불가하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 콜레이션을 이용해 조회하면 인덱스를 이용하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;외래 키&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외래 키에 대한 제약 기능을 지원하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/653</guid>
      <comments>https://mozi.tistory.com/653#entry653comment</comments>
      <pubDate>Mon, 12 Aug 2024 08:49:44 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 잠금과 트랜잭션(1)</title>
      <link>https://mozi.tistory.com/652</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ko6CR/btsI045i8pH/3jbaVpVQv2XSOgxlf52UN1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ko6CR/btsI045i8pH/3jbaVpVQv2XSOgxlf52UN1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ko6CR/btsI045i8pH/3jbaVpVQv2XSOgxlf52UN1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKo6CR%2FbtsI045i8pH%2F3jbaVpVQv2XSOgxlf52UN1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잠금&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 쓰레드의 동시 처리 중에 발생할 수 있는 쓰레드 간의 충돌 문제를 막기위해 데이터베이스와 컬렉션 그리고 도큐먼트들의 잠금을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 계층의 데이터베이스 오브젝트들에 대한 동시 처리를 위해서 인텐션 락과 다중 레벨의 잠금을 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 MongoDB 잠금은 서버 차원에서 처리되는 잠금과 스토리지 엔진에서 처리되는 잠금으로 나누어 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 엔진의 잠금&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시적 잠금과 묵시적 잠금으로 나눠볼 수 있는데, 명시적 잠금은 글로벌 잠금 뿐이며 나머지 모든 잠금은 묵시적 잠금이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스나 컬렉션에 대한 잠금은 모두 묵시적이며, 이는 쿼리가 실행될 때 자동으로 획득했다가 해제되는 잠금이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;글로벌 잠금&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 잠금은 MongoDB 서버 인스턴스에 단 하나만 있는 잠금으로 인스턴스 잠금이라고도 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1723198891155&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.fsyncLock({fsync:1, lock:true})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsync 옵션을 1로 설정하면 MongoDB 서버는 디스크에 기록되지 못한 데이터를 모두 디스크로 플러시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 lock 옵션이 true 로 설정되면 글로벌 잠금을 획득한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 lock 옵션이 false 이면 데이터 파일만 디스크로 플러시하고 잠금을 걸지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsyncLock 명령은 내부적으로 쓰기 잠금이 아닌 읽기 잠금에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 서버의 데이터가 변경되는 것을 막는 용도의 잠금이고 다른 커넥션의 데이터 읽기를 막지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 잠금이 걸리면 db.currentOp 명령으로 글로벌 잠금의 상태를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경과 읽기가 동시에 실행되는 커넥션의 경우,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경에서 대기하게 되므로 읽기 또한 모두 멈추게 되므로 사용시 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 잠금은 fsyncUnlock 명령으로 잠금을 풀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 커넥션에서 실행해도 특이사항은 없으나 설정한 세션에서 풀도록 권고하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;오브젝트 잠금&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S 잠금과 X 잠금에 더불어 IS 와 IX 잠금을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상호 호환 관계는 다음과 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Intent Shard&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Intent Exclusive&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Shared&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Exclusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Intent Shard&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Intent Exclusive&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Shared&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Exclusive&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 여러 커넥션에서 요청하는 잠금을 적절한 동시성을 유지하면서 서로 문제를 일으키지 않도록 각 오브젝트 단위로 잠금 요청 큐를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MongoDB 잠금 큐는 먼저 들어온 잠금 대기가 잠금을 획득하지 못할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IS IS X X S IS 일 때,&amp;nbsp; X 는 배타적이므로 어떤 잠금과도 호환되지 않기에 S IS 는 잠금획득을 기다려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MongoDB 서버는 동시 처리 성능을 높이기 위해 큐에 있는 호환되는 잠금을 모두 동시에 허용해준다. 즉 IS IS 가 허용될 때 S IS 도 허용된다. 그리고 X 잠금은 호환되지 않으므로 마지막에 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WiredTiger 스토리지 엔진의 잠금&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 기반의 잠금을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다양한 레벨의 오브젝트에 대한 잠금을 위해 다중 레벨의 잠금 방식도 같이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진은 글로벌과 데이터베이스와 컬렉션 레벨의 인텐션 잠금을 활용하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐션 잠금은 데이터베이스와 컬렉션 레벨의 명령과 도큐먼트 레벨의 명령이 최적의 동시성을 유지하면서 실행될 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐션 잠금은 이름 그대로 특정 오브젝트에 대해 배타적 또는 공유 잠금에 대한 의도를 가지고 있다는 것을 의미하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 현재 쓰레드가 특정 오브젝트에 대해 읽기 뿐 아니라 변경 의도를 가지고 있음에도 다른 변경 의도를 가진 쓰레드도 허용하는 형태의 잠금이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형태의 요건은 주로 계층형 데이터 구조에서 사용되는데, 대표적으로 데이터베이스와 컬렉션 그리고 도큐먼트간의 관계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 계층 구조에서 하위 오브젝트에 대해 잠금을 획득하려면 상위 계층의 인텐션 잠금을 먼저 획득해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 도큐먼트를 변경하려면 해당 도큐먼트에 대해 쓰기 잠금을 획득해야 하는데 이를 위해서 먼저 글로벌 인텐션 잠금과 데이터베이스 인텐션 잠금 컬렉션 인텐션 잠금을 가져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트를 읽기 위해서도 읽기 잠금을 획득해야 하는데 인텐션 잠금을 획득한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 도큐먼트의 데이터를 변경할 때에는 해당 도큐먼트에 대해 쓰기 잠금을 획득해야 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기만 하는경우에는 WiredTiger 스토리지 엔진에서 별도의 잠금을 이용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 MVCC 라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠금 관리 주체를 보면 오브젝트 잠금은 MongoDB 서버가 담당하고, 도큐먼트 잠금만 WiredTiger 스토리지 엔진이 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createIndex 는 컬렉션의 인덱스를 생성하는 명령으로 컬렉션의 메타 정보를 변경하므로 컬렉션에 대해 배타적 잠금을 걸게된다. 그런데 컬렉션의 인덱스 생성은 그 컬렉션을 포함하고 있는 데이터베이스의 변경과 충돌이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 인덱스 생성 작업은 그 컬렉션을 포함하는 데이터베이스에 대해 IX 잠금을 먼저 획득해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 글로벌 IX 잠금도 기본적으로 획득해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insert 나 update , remove 명령은 컬렉션의 개별 도큐먼트를 변경하므로 명령이 실행되는 동안 다른 커넥션에서 변경하지 못하도록 막으려면, 컬렉션과 데이터베이스, 글로벌에 대해 IX 잠금을 획득해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실제 insert 나 update 명령이 변경하고자 하는 도큐먼트에는 X 잠금을 걸어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스나 다른컬렉션에 대해서 X 잠금을 걸지 않고 IX 잠금을 거는 이유는 다른 도큐먼트를 변경하고자 하는 다른 커넥션들도 처리를 수행해야 할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IX 잠금은 서로 호환되므로 동시에 데이터 변경을 처리할 수 있음을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 명령은 데이터 변경이 아니므로 IS 잠금을 획득하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 WiredTiger 스토리지 엔진은 find 쿼리가 조회하는 도큐먼트들에 대해서 아무런 잠금을 걸지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 MongoDB 의 MVCC 덕분에 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 WiredTiger 스토리지 엔진은 도큐먼트를 변경할 때 기존 버전은 그대로 두고 새로운 버전을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 변경되는 내역을 모두 관리한다. 그래서 find 쿼리는 현재 트랜잭션에서 읽어야 할 도큐먼트의 버전을 찾아서 읽기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잠금 Yield&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 RDBMS 는 한번 잠금을 획득하면 처리가 완료될때까지 잠금을 다시 해제하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MongoDB 는 트랜잭션을 지원하지 않는 NoSQL 이었기 때문에 트랜잭션 요건이 크지 않았으며, 동시성 처리가 더 우선순위여서 설정된 조건보다 오랜 시간 실행되거나 많은 자원을 소모하는 경우에는 잠깐 쉬었다 다시 처리를 재개하도록 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 쿼리를 실행하는 도중 잠깐 쉬었다 쿼리 실행을 재개하는 것을 Yield 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Yield 는 단순히 쿼리의 처리를 잠깐 쉬는것이 아닌, 처리 중인 쿼리를 위해 획득했던 잠금까지 모두 해제하고 지정된 시간동안 쉬게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 인덱스를 가지지 않는 필드에 대해 검색을 수행하면 전체 컬렉션을 모두 읽어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리를 처리하는 커넥션은 IS 잠금을 걸게 된다. 그런데 쿼리를 처리하는 도중 일정 규칙에 맞으면 가지고 있는 모든 잠금을 반납하고 일정시간동안 쉬게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리가 지정된 건수의 도큐먼트를 읽은 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리가 지정된 시간동안 수행된 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값은 128개 도큐먼트를 읽거나 10밀리초 이상 실행했을 때 Yield 가 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경은 파라미터를 조정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 Sleep 이 쿼리의 성능 저하가 많이 발생하게 되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전부터 Yield 과정이 특정 시간동안 쉬는형태가 아니라 가지고 있던 잠금을 해제하고 가지고 있던 CPU 점유권을 놓고 다시 운영 체제의 쓰레드 스케줄을 기다리는 형태로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잠금 진단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db.currentOp 명령으로 현재 실행 중인 명령들의 목록을 조회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 잠금표시에서 배타적 잠금은 W , 공유 잠금은 R 로 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠금 표기에서 대소문자는 서로 다른 의미를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;r : Intention Shared Lock&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;w : Intention Exclusive Lock&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R : Shared Lock&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;W : Exclusive Lock&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;locks 필드에는 글로벌과 데이터베이스 컬렉션에 대해 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lockStats 필드에서는 글로벌 데이터베이스와 컬렉션별로 인텐션 잠금을 획득한 횟수를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 로그 파일에도 명령이 어떤 잠ㄱ므을 얼마나 획득했는지 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜잭션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진이 제공하는 트랜잭션의 ACID 속성은 다음과 같은 특성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최고 레벨의 격리 수준은 Snapshot (Repeatable-read )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜잭션의 커밋과 체크포인트 2가지 형태로 영속성 보장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 커밋되지 않은 변경 데이터는 공유 캐시 크기보다 작아야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 RDBMS 는 일반적으로 READ UNCOMMIT, READ COMMIT, REPEATABLE-READ , SERIALIZABLE 4가지 격리 수준을 제공하지만 WiredTiger 는 REPEATABLE-READ 격리수준이 최고 수준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 트랜잭션 로그 뿐만 아니라 체크포인트로 영속성이 보장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 트랜잭션 로그가 없어도 마지막 체크포인트 시점의 데이터를 복구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 트랜잭션이 커밋되기 전에는 로그를 디스크로 기록하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 트랜잭션이 변경할 수 있는 데이터 크기는 공유 캐시의 크기로 제한된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쓰기 충돌 ( Write Conflict )&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 변경하는 도중 쓰기 충돌이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 변경하고자 하는 도큐먼트가 이미 다른 커넥션에 의해 잠금이 걸려있으면 즉시 업데이트 실행을 취소한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 WriteConflict Exception 을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 업데이트 명령을 실행했던 세션은 메시지를 반환받고, 같은 업데이트 문장을 재실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 MongoDB 내부에서 실행되며 응용프로그램에서는 알지 못한다. 즉 응용프로그램에는 투명하게 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 심각한 문제가 될 수 있는데, 하나의 도큐먼트에 많은 쓰레드가 동시에 변경하려고 하면 UPDATE 문장의 실행이 폭증하는 현상이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 쓰기 충돌과 재처리 과정이 반복 실행되면서 CPU 사용량이 높아지지만 실제 사용자의 요청을 처리하는 성능은 떨어질 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WriteConflict Exception 이 얼마나 발생했는지는 serverStatus 명령으로 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단일 도큐먼트 트랜잭션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 도큐먼트의 트랜잭션만 지원하고 이는 원자 단위의 처리가 보장된다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터를 변경하면 실제 이 작업들은 잘게 쪼개진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 insert 를 실행하면 3개의 명령을 하나의 트랜잭션으로 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자 컬렉션 insert&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자 인덱스 insert&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. OpLog 컬렉션 insert&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 여러 도큐먼트간의 분산트랜잭션을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문장의 트랜잭션 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 하나의 INSERT 문장에 여러 도큐먼트가 저장되는 배치 INSERT 와 한번에 여러 도큐먼트를 변경하는 업데이트 문장의 트랜잭션의 처리는 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 MongoDB 에서는 작은 트랜잭션으로 다시 조개져 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 업데이트 중간에 에러가 발생하면 에러가 발생한 시점부터는 작업이 무시되지만, 이전에 변경된 도큐먼트는 되돌려지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;격리 수준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지는 기본적으로 Shared View 와 Private View 기능을 활용하기 때문에 READ COMMITED 격리 수준과 비슷하게 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진은 자체적으로 데이터 변경 이력을 관리하기에 READ UNCOMMITTED 와 READ COMMITTED , SNAPSHOT 격리 수준을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MongoDB 서버에 내장되면서 SNAPSHOT 격리 수준만 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;READ-COMMITTED&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 트랜잭션에서 같은 데이터를 여러 번 조회할 때, 다른 세션의 트랜잭션에서 변경하고 커밋한 데이터가 보이게 되는 격리수준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SNAPSHOT ( REPEATABLE-READ )&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 트랜잭션에서 쿼리는 항상 같은 결과를 반환해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 서버의 격리 수준&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 읽기와 변경이 서로 다른 격리 수준의 효과를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서는 모든 데이터의 변경을 단일 도큐먼트 수준으로 트랜잭션을 제어한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 하나의 업데이트 문장으로 여러 도큐먼트를 변경하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2건의 도큐넌트를 하나의 업데이트 명령으로 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 모든 데이터 변경이 도큐먼트 기반으로 트랜잭션을 생성하므로 다른 트랜잭션 범위 내에서 각 도큐먼트의 업데이트가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update 뿐 아니라 모든 데이터 변경은 하나의 도큐먼트를 변경할 때마다 내부적으로 트랜잭션이 커밋된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버에서 사용자가 직접 트랜잭션을 제어할 수 없는 것은 상당히 불편하나 얻는 이익도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장시간 실행되는 트랜잭션이 존재할 수 없으며, 이로인한 성능저하도 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 읽기 쿼리는 데이터 변경 쿼리와는 다른 형태로 트랜잭션이 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 데이터 읽기는 데이터 변경과는 달리 일정 단위로 트랜잭션을 시작하고 완료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진을 사용하는 경우 트랜잭션 개념이 없지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진을 사용하는 경우 특정 시점의 데이터를 읽게 되는데, 이를 스냅샷(Snapshot) 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시점의 데이터 상태를 그대로 유지할 수 있는 개념으로 원리는 특정 트랜잭션을 시작하고 그 태랜잭션에서 데이터를 읽는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 2가지 조건을 만족할 때까지 스냅샷을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리가 지정된 건수의 도큐먼트를 읽은 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿼리가 지정된 시간 동안 수행된 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 시간 또는 일정 개수 이상의 도큐먼트를 읽은 후에는 '잠금 Yield' 를 실행하는데 이 때 잠금뿐 아니라 스냅샷도 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 잠금 Yield 과정의 스냅샷 해제는 쿼리의 일관성에 심각한 문제가 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 쿼리는 internalQueryExectieldIterations 이나 internalQueryExecTieldPeriodMS 옵션을 조정하여 Yield 주기를 조금 더 뜸하게 설정하여 빈도를 낮추는 방법도 생각해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 서버의 격리 수준과 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 건수 이상을 조회하면 스냅샷이 해제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 실행한 쿼리가 정렬이 필요하면 다른 결과를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 격리 수준을 사용하지만 처리방식에 따라 쿼리 결과가 달라지는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 정렬이 수반되는 경우 서버가 컬렉션의 데이터를 모두 가져와 메모리(정렬 버퍼)에 적재하고 정렬을 실행하여 클라이언트로 보내주기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 다른 세션에서 데이터를 삭제하거나 변경해도 정렬버퍼에 임시로 복사된 결과에는 적용되지 않기에 일관된 결과를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/652</guid>
      <comments>https://mozi.tistory.com/652#entry652comment</comments>
      <pubDate>Fri, 9 Aug 2024 19:43:00 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 전문검색 인덱스 사용법과 주의사항</title>
      <link>https://mozi.tistory.com/651</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YwGXS/btsIVcwMi2v/kEEEYXAh2mfZywIT3ZWrK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YwGXS/btsIVcwMi2v/kEEEYXAh2mfZywIT3ZWrK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YwGXS/btsIVcwMi2v/kEEEYXAh2mfZywIT3ZWrK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYwGXS%2FbtsIVcwMi2v%2FkEEEYXAh2mfZywIT3ZWrK1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 전문검색 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 인덱스에서 전문검색 인덱스는 문자열을 빠르게 검색해 주는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태소 분석방식과 N-Gram 알고리즘 방식이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. MongoDB 테스트 데이터 적재&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성 시 text 옵션을 주면 전문검색 인덱스로 생성된다.&lt;/p&gt;
&lt;pre id=&quot;code_1722922410779&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.wordCollection.insert( { contents : &quot;Mozi Blog is one of Tech Blogs&quot; } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b1b515eba581d9fa1b1580') }
}

[direct: mongos] indexDB&amp;gt; db.wordCollection.insert( { contents : &quot;Mozi 블로그는 기술 블로그중 하나입니다.&quot; } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b1b521eba581d9fa1b1581') }
}

[direct: mongos] indexDB&amp;gt; db.wordCollection.createIndex ( { contents : &quot;text&quot; } )
contents_text&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 전문검색 인덱스 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 전문검색 인덱스 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문검색 시에는 find 에서 필드명을 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$text 와 $search 를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1722922518026&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.wordCollection.find ( { $text : { $search : &quot;Mozi&quot; } } )
[
  {
    _id: ObjectId('66b1b521eba581d9fa1b1581'),
    contents: 'Mozi 블로그는 기술 블로그중 하나입니다.'
  },
  {
    _id: ObjectId('66b1b515eba581d9fa1b1580'),
    contents: 'Mozi Blog is one of Tech Blogs'
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. 전문검색 인덱스 조회 불가경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 조회가 되지 않는경우도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기 ,블로그 ,on 등 일부 단어로만 검색시에는 조회가 되지 않는다는 점이다.&lt;/p&gt;
&lt;pre id=&quot;code_1722922640342&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.wordCollection.find ( { $text : { $search : &quot;기&quot; } } )

[direct: mongos] indexDB&amp;gt; db.wordCollection.find ( { $text : { $search : &quot;기술&quot; } } )
[
  {
    _id: ObjectId('66b1b521eba581d9fa1b1581'),
    contents: 'Mozi 블로그는 기술 블로그중 하나입니다.'
  }
]


[direct: mongos] indexDB&amp;gt; db.wordCollection.find ( { $text : { $search : &quot;블로그&quot; } } )

[direct: mongos] indexDB&amp;gt; db.wordCollection.find ( { $text : { $search : &quot;블로그중&quot; } } )
[
  {
    _id: ObjectId('66b1b521eba581d9fa1b1581'),
    contents: 'Mozi 블로그는 기술 블로그중 하나입니다.'
  }
]


[direct: mongos] indexDB&amp;gt; db.wordCollection.find ( { $text : { $search : &quot;on&quot; } } )

[direct: mongos] indexDB&amp;gt; db.wordCollection.find ( { $text : { $search : &quot;one&quot; } } )
[
  {
    _id: ObjectId('66b1b515eba581d9fa1b1580'),
    contents: 'Mozi Blog is one of Tech Blogs'
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 텍스트를 인덱스로 만들 때, 문자열 파싱을 어떻게 하냐에 따라 달라지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 전문검색 인덱스 생성 옵션&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. 컬렉션에 전문검색 인덱스를 설정하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에 전문검색을 설정하고 싶을 때는 필드부분에 $** 를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 기존에 전문검색 인덱스가 있다면 생성이 불가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1722922867174&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.wordCollection.createIndex ( { &quot;$**&quot; : &quot;text&quot; } )
$**_text&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2. 전문검색 중요도 할당&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문검색 시 중요도를 이용하여 정렬할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요도는 weights 를 사용하며 숫자값이 낮을수록 중요도가 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 기존에 전문검색 인덱스가 있다면 생성이 불가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1722923135858&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.wordCollection.createIndex ( { contents : &quot;text&quot; }, { weights: { contents : 1 } } )
contents_text&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb 전문검색 인덱스</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/651</guid>
      <comments>https://mozi.tistory.com/651#entry651comment</comments>
      <pubDate>Tue, 6 Aug 2024 14:46:06 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 멀티 키 인덱스 범위조건 사용하는 방법</title>
      <link>https://mozi.tistory.com/650</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRVsEj/btsIWFdz1Jv/1eVhX9h23nyzu8kxJiu8kk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRVsEj/btsIWFdz1Jv/1eVhX9h23nyzu8kxJiu8kk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRVsEj/btsIWFdz1Jv/1eVhX9h23nyzu8kxJiu8kk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRVsEj%2FbtsIWFdz1Jv%2F1eVhX9h23nyzu8kxJiu8kk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 멀티인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 인덱스에서 멀티 키 인덱스는 여러 개의 인덱스 키 엔트리가 하나의 도큐먼트를 가지기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 검색조건으로 멀티 키 인덱스를 사용할 때는 검색 조건을 주의해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. MongoDB 테스트 데이터 적재&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number 필드는 배열로 되어있고 number 필드를 인덱스로 생성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1722921460638&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.multiCollection.insert({ item: &quot;Mozi&quot; , number : [2, 9] })
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b1b1a4eba581d9fa1b157d') }
}

[direct: mongos] indexDB&amp;gt; db.multiCollection.insert({ item: &quot;Blog&quot; , number : [4, 3] })
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b1b1b2eba581d9fa1b157e') }
}

[direct: mongos] indexDB&amp;gt; db.multiCollection.createIndex( { number : 1 } )
number_1&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. MongoDB 멀티 키 테스트&lt;/h2&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 범위조건 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BETWEEN 조건 3 보다 크고 6보다 작은 범위를 검색해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 2, 9 의 값을 가진 도큐먼트도 출력이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1722921538065&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.multiCollection.find ( { number : { $gte : 3 , $lte : 6 } } )
[
  {
    _id: ObjectId('66b1b1a4eba581d9fa1b157d'),
    item: 'Mozi',
    number: [ 2, 9 ]
  },
  {
    _id: ObjectId('66b1b1b2eba581d9fa1b157e'),
    item: 'Blog',
    number: [ 4, 3 ]
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 멀티 키 인덱스에 대한 조건을 각가 따로 비교한 다음에 두 개의 결과를 병합(합집합)하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 3보다 큰 값이 있기에 9 가 대상이 되고 6보다 작은 값이 있기에 2가 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. $elemMatch 연산사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 elemMatch 연산을 사용하면 BETWEEN 와 같다. (교집합)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3보다 작은 값(=2)이 있기때문에, 그리고 6보다 큰 값(=9) 가 있기 때문에 item 이 Mozi 인 도큐먼트는 출력되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1722921756532&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.multiCollection.find ( { number : { $elemMatch : { $gte : 3 , $lte : 6 } } } )
[
  {
    _id: ObjectId('66b1b1b2eba581d9fa1b157e'),
    item: 'Blog',
    number: [ 4, 3 ]
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 두 조건을 모두 만족하는 엘리먼트를 하나라도 가지고 있다면 결과로 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 6보다 작은 값(2, 4)을 모두 만족하기 때문에 item 이 Tistory 가 출력이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1722921877962&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.multiCollection.insert({ item: &quot;Tistory&quot; , number : [2, 4] })
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66b1b386eba581d9fa1b157f') }
}

[direct: mongos] indexDB&amp;gt; db.multiCollection.find ( { number : { $elemMatch : { $gte : 3 , $lte : 6 } } } )
[
  {
    _id: ObjectId('66b1b1b2eba581d9fa1b157e'),
    item: 'Blog',
    number: [ 4, 3 ]
  },
  {
    _id: ObjectId('66b1b386eba581d9fa1b157f'),
    item: 'Tistory',
    number: [ 2, 4 ]
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>mongodb 멀티 키 주의사항</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/650</guid>
      <comments>https://mozi.tistory.com/650#entry650comment</comments>
      <pubDate>Tue, 6 Aug 2024 14:28:24 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 인덱스(2)</title>
      <link>https://mozi.tistory.com/649</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djAZon/btsIThxsKpl/Zv0wI2b7buCyld4IDdMHG0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djAZon/btsIThxsKpl/Zv0wI2b7buCyld4IDdMHG0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djAZon/btsIThxsKpl/Zv0wI2b7buCyld4IDdMHG0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjAZon%2FbtsIThxsKpl%2FZv0wI2b7buCyld4IDdMHG0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해시인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스는 B-Tree 인덱스만큼 범용적이지는 않지만 고유특성이 있는 인덱스 중 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일검색에는 최적화되지만 범위검색이나 정렬된 결과를 가져오는 목적으로는 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 검색 성능을 향상시키기 위한 인덱스의 목적보다는 해시 샤딩을 구현하기 위해 꼭 필요한 인덱스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해시 인덱스의 구조 및 특성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스의 가장 큰 장점은 빠르다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스는 트리 형태의 구조가 아니므로 검색하고자 하는 값이 주어지면 해시 함수를 거쳐 바로 버켓의 주소를 알아내며, 그 버켓을 읽어 즉시 해당 데이터의 레코드를 가져오기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 버켓은 메모리에 로드되어 있고 해시 인덱스로 데이터 레코드를 읽는것은 디스크를 한번만 읽어서 데이터를 가져올 수 있음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스는 B-Tree 에서 인덱스 키를 찾아가는 복잡한 과정이 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스에서 가장 중요한 것은 해시함수인데, 해시 함수는 어떤 입력 값을 받아 계산식을 통해 지정된 범위의 숫자 값으로 변환하는 함수이기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수에서 결과값 범위가 크면 그만큼 버켓이 많이 필요하므로 공간이 낭비되고, 범위가 너무 작으면 충돌하는 경우가 많이 발생해 인덱스의 장점이 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스는 인덱스 키의 원본 값 자체로 만들어진 인덱스가 아니므로 B-Tree 처럼 정렬을 보장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해시 인덱스의 가용성 및 효율성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르다는 장점이 있지만 키 값 자체가 아니라 해시 함수의 결과값으로 접근해야 하고, 정렬이 보장되지 않는다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단일 키 값을 검색하는 기능 이외에는 별로 쓸모는 없어&amp;nbsp; 해시 인덱스를 이용하는 경우는 오로지 키 값을 일치와 불일치 비교 연산자료 비교하는 경우뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 불일치 비교는 해시 인덱스를 이용하는 것보다 풀컬렉션 스캔으로 처리될 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 된 필드만으로 쿼리를 처리할 수 있는 경우에만 불일치 비교 쿼리에 해시 인덱스를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 해시 인덱스의 구조 및 특성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 해시 인덱스는 응용 프로그램에서는 해시 인덱스처럼 보이며 해시 인덱스의 제약 사항을 그대로 가져가지만, 내부적으로는 B-Tree 자료 구조로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스의 키 엔트리에는 필드의 원보 값이 아닌 MD5 해시 된 결과값의 하위64비트만으로 구성된 정수값이 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 도큐먼트 데이터가 저장되는 데이터 파일에는 해시 함수의 결과값은 관리되지 않고 사용자가 입력했던 도큐먼트의 필드 원본값만 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 검색하면 MongoDB 는 이 값을 검색하기 위해 해시 함수를 실행하고 그 결과값은 인덱스 검색에 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장단점으로는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본값을 그대로 인덱싱하는것이 아니라 해시 함수의 결과값을 인덱싱하므로 범위 검색을 처리하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 버켓을 사용하는 원래의 해시 인덱스는 B-Tree 에 비해 빠른 검색 성능을 보장하나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 경우 내부적으로 B-Tree 를 사용하므로 검색 성능이 B-Tee 인덱스와 비교해 차이가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 MongoDB 의 해시 인덱스는 B-Tree 인덱스 대비 장점보다는 단점만 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 만약 인덱스 키 값이 긴 경우, 해시함수를 통해 8바이트 정수형으로 변환해 키를 생성하므로 길이가 상대적으로 작아질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 인덱스는 키에 1KB 를 넘을 수 없어 원본길이를 저장하는 경우 불가능하나 해시값 변환된경우에는 저장이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 해시 인덱스의 제한 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 필드에 대해서만 해시 인덱스를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 필드에 해시 인덱스를 생성해야 할 때도 있는데, 이럴떄는 서브 도큐먼트를 저장하는 방식으로 우회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 서브도큐먼트의 순서를 맞춰서 검색해야만 결과를 조회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멀티 키 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 도큐먼트가 배열 형태의 데이터를 가지는 경우, 배열의 각 아이템을 검색할 수 있는 인덱스가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 는 멀티 키 인덱스가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 그대로 하나의 도큐먼트가 여러 개의 인덱스 키를 가지는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티 키 인덱스의 주의 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 키 인덱스는 여러 개의 인덱스 키 엔트리가 하나의 도큐먼트를 가리키고 있기 때문에, 검색 조건의 바운드에 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 조건에서 바운드는 쿼리의 조건이 원하는 결과를 얻기 위해서 인덱스를 스캔해야 하느 범위를 의미하는데, 멀티 키 인덱스의 스캔 범위 결정 방식은 일반 인덱스와는 조금 다르게 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 실행 결과에 원치 않는 결과도 같이 포함될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 각 조건을 따로 비교한 다음에, 두 개의 결과를 병합한다. 그래서 합집합이 결과로 리턴된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2, 9 는 3, 6 에 포함되지 않으나 출력되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1722838737085&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] multikeydb&amp;gt; db.multi.insert ( { _id: 1, item : &quot;ABC&quot; , ratings : [ 2, 9 ] })
{ acknowledged: true, insertedIds: { '0': 1 } }

[direct: mongos] multikeydb&amp;gt; db.multi.insert ( { _id: 2, item : &quot;DEF&quot; , ratings : [ 4, 3 ] })
{ acknowledged: true, insertedIds: { '0': 2 } }

[direct: mongos] multikeydb&amp;gt; db.multi.createIndex( { ratings : 1 } )
ratings_1

[direct: mongos] multikeydb&amp;gt; db.multi.find( { ratings : { $gte : 3, $lte : 6 } } )
[
  { _id: 1, item: 'ABC', ratings: [ 2, 9 ] },
  { _id: 2, item: 'DEF', ratings: [ 4, 3 ] }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 BTEWEEN 을 구현하기 위해선 elemMatch 를 사용해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1722838834127&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] multikeydb&amp;gt; db.multi.find( { ratings : { $elemMatch : { $gte : 3, $lte : 6 } } } )
[ { _id: 2, item: 'DEF', ratings: [ 4, 3 ] } ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티 키 인덱스의 성능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 포맷에 따라 멀티 키 엔트리를 수십개씩 가져가는 도큐먼트도 있을 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 추가/삭제해야 할 인덱스 키가 많을수록 데이터의 변경 성능은 큰 영향을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티 키 인덱스의 제한 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 멀티 키 인덱스는 샤드 키로 사용될 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 인덱스 엔트리가 같은 도큐먼트를 가리키므로 샤드 키로 사용될 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해시 알고리즘을 사용하는 인덱스는 멀티 키 인덱스로 정의될 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 멀티 키 인덱스는 커버링 인덱스 처리가 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전문 검색 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 엔진을 구축할 때 사용되는 알고리즘은 크게 형태소 분석과 N-Gram 으로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명사와 조사 사이에 구분문자를 사용하는 서구권 언어에서는 형태소 분석 방법이 많이 사용되지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아시아권 언어는 명사와 조사 사이 별도 구분문자가 없이 연결되므로 어근 분석이 까다로워 N-Gram 이 많이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;형태소 분석 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스는 2가지 과정을 거쳐 색인 작업이 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 불용어 처리 (Stop Word)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 형태소 분석 (Stemming)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불용어 처리는 검색에서 별 가치가 없는 단어를 모두 필터링해서 제거하는 작업을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태소 분석은 검색어로 선정된 단어의 어근을 찾는 작업이다. 형태소 분석은 snowball 의 오픈소스를 이용해서 구현되어잇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 색인 작업이 필요한 텍스트를 구분자를 이용해서 토큰으로 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분된 토큰 단위로 불용어에 등록된 단어인지 검색한다. * 불용어 : 대명사나 관사 전치사 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불용어 필터링이 완료되면 남은 각 단어의 형태소 분석 작업이 시작되어 단어의 원형을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트에서 검색어로 색인할만한 가치가 있는단어만 골라서 단어의 원형을 인덱스에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;N-Gram 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 개의 지정된 구분자로 전 세계 모든 언어에서 단어를 구분하기엔 쉽지 않으므로 이러한 부분을 보완하기 위해 지정된 규칙이 없는 텍스트를 분석하거나 검색할 수 있게 하는 방법이 N-Gram 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문을 무조건으로 몇 글자씩 잘라서 인덱싱하는 방법이다. ElasticSearch 도 N-Gram 알고리즘을 이용한 전문 검색 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N-Gram 에서 n 은 인덱싱할 키워드의 최소 글자 수를 의미하는데, 일반적으로 2-Gram 방식이 많이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2글자 단위의 최소 키워드에 대한 키를 관리하는 프론트 엔드와 2글자 이상의 키워드 묶음을 관리하는 백 엔드 인덱스로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;형태소 분석과 N-Gram 의 장단점&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.1705%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 40.0775%;&quot;&gt;형태소 분석&lt;/td&gt;
&lt;td style=&quot;width: 42.7519%;&quot;&gt;N-Gram&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.1705%;&quot;&gt;언어 의존도&lt;/td&gt;
&lt;td style=&quot;width: 40.0775%;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 42.7519%;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.1705%;&quot;&gt;인덱스 크기&lt;/td&gt;
&lt;td style=&quot;width: 40.0775%;&quot;&gt;작음&lt;/td&gt;
&lt;td style=&quot;width: 42.7519%;&quot;&gt;큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.1705%;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 40.0775%;&quot;&gt;빠름&lt;/td&gt;
&lt;td style=&quot;width: 42.7519%;&quot;&gt;느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.1705%;&quot;&gt;검색 품질&lt;/td&gt;
&lt;td style=&quot;width: 40.0775%;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 42.7519%;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전문 검색 인덱스의 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에 전문 검색 인덱스를 생성해야 하는데, 필드 레벨 또는 컬렉션 레벨로 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스를 삭제할 때는 인덱스의 필드 목록으로는 삭제할 수 없고 인덱스 이름을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;중요도 할당&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드별로 중요도를 설정할 수 있으며, 전문 검색 인덱스를 통해 데이터를 조회할 때 중요도를 기준으로 정렬해서 결과를 가져갈 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요도를 조회하거나 이용해 정렬하고 자 할때는 $meta 연산자를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컴파운드 인덱스와 인덱스 파티셔닝&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스가 다른 필드와 복합으로 컴파운드 인덱스를 구성하는 경우에는 반드시 선행되는 필드의 조건이 있어야만 전문 검색 인덱스로 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스에 선행필드를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 도큐먼트를 저장할 때 선행필드의 데이터를 가진 영역의 페이지들만 메모리에 로딩하면 되므로 빠르게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;언어 구분 및 대소문자 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스를 생성할 때는 인덱싱 대상 문자열이 어느 나라 언어인지 명시해야 정확한 분석이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성 시 특별히 언어를 지정하지 않으면 영어로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 인덱스를 생성하는 시점에 default_language 옵션을 설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어는 외부 라이브러리를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전문 인덱스의 한계와 회피&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어 문장에 대한 전문 검색 인덱스는 키워드로 원할하게 검색을 수행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규 표현식을 이용해 전방 잋리 조건을 사용하면 주요 명사 뒤에 붙은 조사는 무시하고 검색을 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부정 비교와 문장 검색&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 검색어를 동시에 검색할 수 있는데, 검색어에 여러 개의 단어를 나열하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색어에 나열한 단어들은 AND 조합이 아니라 OR 조합으로 연결되서 검색된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AND 조합으로도 전문 검색을 수행할 수 있는데 큰 따옴표로 검색어를 묶어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색된 결과에서 특정 단어를 포함한 도큐먼트는 제외하고 가져올 수 있는데 부정 비교 조건(-)을 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 중간에 하이픈이 되면 단순히 검색어의 일부로 인식한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 전문 검색 인덱스의 버전 호환성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 버전별로 전문 인덱스 버전이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4 버전에서 형태소 분석이 끝난 단어를 그대로 인덱싱한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.6 버전에서 단어의 최대 길이를 64글자로 제한하도록 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전에서 텍스트 인덱스 키의 길이를 256글자로 확장한 포맷을 사용하고 MD5 로 알고리즘이 변경되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전문 검색 인덱스의 제약사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션당 최대 1개만 생성가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문 검색 쿼리가 사용된 쿼리에서는 힌트를 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리결과의 정렬은 전문검색 인덱스를 이용해 처리할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 키나 공간검색 인덱스와 함께 컴파운드 인덱스로 생성할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접두어 일치는 사용할 수 없으며 항상 전체 일치 검색만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공간 검색 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2dsphere 인덱스는 S2 Geometry 라이브러리를 이용한 좌표 검색을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GeoHash 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시계 지도를 세로로 한번 양분하고 다시 그림과 같이 가로로 양분하는 작업을 반복하면서 각 영역의 식별자를 0과 1로 연결해 GeoHash 코드값을 생성하는 방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 양분하면서 할당되는 0과 1을 모두 연결해서 64비트 정수 타입으로 만들고 BASE-32 로 인코딩해서 문자열로 변환한 것이 GeoHash 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GeoHash 인덱스의 내부 작동&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근접한 지역별로 GeoHash 값 자체도 근접하다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 GeoHash 인코딩 값을 데이터베이스에 저장하고 이 값으로 문자열 패턴 일치 검색을 실행하면 근접 지역의 GeoHash 를 인덱스를 이용해 검색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 이 값을 이용해 다시 위도 경도 좌표를 재현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/649</guid>
      <comments>https://mozi.tistory.com/649#entry649comment</comments>
      <pubDate>Fri, 2 Aug 2024 17:07:13 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 샤딩 청크 샤드서버 할당하는 방법</title>
      <link>https://mozi.tistory.com/648</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8oTcx/btsIS7OrFGt/HxpnqN12ZUc7N7UwFuRpM1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8oTcx/btsIS7OrFGt/HxpnqN12ZUc7N7UwFuRpM1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8oTcx/btsIS7OrFGt/HxpnqN12ZUc7N7UwFuRpM1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8oTcx%2FbtsIS7OrFGt%2FHxpnqN12ZUc7N7UwFuRpM1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 청크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 데이터를 청크단위로 묶어서 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 밸런서에 의해 나뉘거나 샤드서버별로 옮겨다닐 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;3. 전제조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodb 는 2개의 샤드서버가 구성되어 있는 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레인지샤딩이 아래와 같이 구성되어있고 모든 청크는 rs1 샤드에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1722387822680&quot; class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; sh.status();
shardingVersion
{ _id: 1, clusterId: ObjectId('66838519ea100ebaf5ee3cc8') }
---
shards
[
  {
    _id: 'rs1',
    host: 'rs1/127.0.0.1:30001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896113, i: 1 })
  },
  {
    _id: 'rs2',
    host: 'rs2/127.0.0.1:40001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896932, i: 2 })
  }
]
---
active mongoses
[ { '7.0.12': 1 } ]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722388246970&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] rangeShardDB&amp;gt; sh.status()
  {
    database: {
      _id: 'rangeShardDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('d6db39d1-0b81-40a7-a539-327cf5158922'),
        timestamp: Timestamp({ t: 1722242803, i: 1 }),
        lastMod: 1
      }
    },
    collections: {
      'rangeShardDB.collection1': {
        shardKey: { index: 1 },
        unique: false,
        balancing: true,
        chunkMetadata: [ { shard: 'rs1', nChunks: 4 } ],
        chunks: [
          { min: { index: MinKey() }, max: { index: 1000 }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 1 }) },
          { min: { index: 1000 }, max: { index: 2000 }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 3 }) },
          { min: { index: 2000 }, max: { index: '가' }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 5 }) },
          { min: { index: '가' }, max: { index: MaxKey() }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 6 }) }
        ],
        tags: []
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 청크 샤드서버 이동&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 moveChunk 의 명령으로 다른 샤드서버로 이동할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크이동시 샤드서버 리소스와 데이터 중복조회 등이 발생할 수 있으므로 주의한다.&lt;/p&gt;
&lt;pre id=&quot;code_1722391042047&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 청크를 샤드2번서버로 이동
[direct: mongos] rangeShardDB&amp;gt; sh.moveChunk(&quot;rangeShardDB.collection1&quot;, { &quot;index&quot;: 1000 }, &quot;rs2&quot;)
{
  millis: 198,
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722390153, i: 50 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722390153, i: 50 })
}

# 청크조회
[direct: mongos] rangeShardDB&amp;gt; sh.status()
  {
    database: {
      _id: 'indexDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('4bdeceab-3d12-4c6d-b79e-94f8a094e3f0'),
        timestamp: Timestamp({ t: 1722313944, i: 1 }),
        lastMod: 1
      }
    },
    collections: {}
  },
  {
    database: {
      _id: 'rangeShardDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('d6db39d1-0b81-40a7-a539-327cf5158922'),
        timestamp: Timestamp({ t: 1722242803, i: 1 }),
        lastMod: 1
      }
    },
    collections: {
      'rangeShardDB.collection1': {
        shardKey: { index: 1 },
        unique: false,
        balancing: true,
        chunkMetadata: [ { shard: 'rs1', nChunks: 3 }, { shard: 'rs2', nChunks: 1 } ],
        chunks: [
          { min: { index: MinKey() }, max: { index: 1000 }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 2, i: 1 }) },
          { min: { index: 1000 }, max: { index: 2000 }, 'on shard': 'rs2', 'last modified': Timestamp({ t: 2, i: 0 }) },
          { min: { index: 2000 }, max: { index: '가' }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 2, i: 0 }) },
          { min: { index: '가' }, max: { index: MaxKey() }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 3 }) }
        ],
        tags: []
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>mongodb 청크이동</category>
      <category>movechunk</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/648</guid>
      <comments>https://mozi.tistory.com/648#entry648comment</comments>
      <pubDate>Wed, 31 Jul 2024 10:58:03 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 레인지샤딩 범위나누는 방법</title>
      <link>https://mozi.tistory.com/647</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYXvsu/btsIRNJ7Yv2/j4YHOkEUYRT7KAUCmNwqS0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYXvsu/btsIRNJ7Yv2/j4YHOkEUYRT7KAUCmNwqS0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYXvsu/btsIRNJ7Yv2/j4YHOkEUYRT7KAUCmNwqS0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYXvsu%2FbtsIRNJ7Yv2%2Fj4YHOkEUYRT7KAUCmNwqS0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 레인지샤딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 샤드별로 가지는 데이터 범위를 할당하여 샤드별로 데이터를 분산할 수 있는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;3. 전제조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodb 는 2개의 샤드서버가 구성되어 있는 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 rangeShardDB 데이터베이스는 레인지샤딩으로 구성되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1722387822680&quot; class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; sh.status();
shardingVersion
{ _id: 1, clusterId: ObjectId('66838519ea100ebaf5ee3cc8') }
---
shards
[
  {
    _id: 'rs1',
    host: 'rs1/127.0.0.1:30001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896113, i: 1 })
  },
  {
    _id: 'rs2',
    host: 'rs2/127.0.0.1:40001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896932, i: 2 })
  }
]
---
active mongoses
[ { '7.0.12': 1 } ]&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722387845197&quot; class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; sh.status()
  {
    database: {
      _id: 'rangeShardDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('d6db39d1-0b81-40a7-a539-327cf5158922'),
        timestamp: Timestamp({ t: 1722242803, i: 1 }),
        lastMod: 1
      }
    },
    collections: {
      'rangeShardDB.collection1': {
        shardKey: { index: 1 },
        unique: false,
        balancing: true,
        chunkMetadata: [ { shard: 'rs1', nChunks: 1 } ],
        chunks: [
          { min: { index: MinKey() }, max: { index: MaxKey() }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 0 }) }
        ],
        tags: []
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;4. 레인지샤딩 범위할당&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위는 splitAt 이라는 명령어를 사용하여 분할이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1722388088300&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# splitAt 은 '데이터베이스명.컬렉션명, 분할키 : 범위' 작성
[direct: mongos] rangeShardDB&amp;gt; sh.splitAt(&quot;rangeShardDB.collection1&quot;, { &quot;index&quot;: 1000 } )
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722388045, i: 5 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722388045, i: 5 })
}

[direct: mongos] rangeShardDB&amp;gt; sh.splitAt(&quot;rangeShardDB.collection1&quot;, { &quot;index&quot;: 2000 } )
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722388049, i: 10 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722388049, i: 10 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타정보를 확인하면 범위가 분할된 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1722388182098&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] rangeShardDB&amp;gt; sh.status()
  {
    database: {
      _id: 'rangeShardDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('d6db39d1-0b81-40a7-a539-327cf5158922'),
        timestamp: Timestamp({ t: 1722242803, i: 1 }),
        lastMod: 1
      }
    },
    collections: {
      'rangeShardDB.collection1': {
        shardKey: { index: 1 },
        unique: false,
        balancing: true,
        chunkMetadata: [ { shard: 'rs1', nChunks: 3 } ],
        chunks: [
          { min: { index: MinKey() }, max: { index: 1000 }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 1 }) },
          { min: { index: 1000 }, max: { index: 2000 }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 3 }) },
          { min: { index: 2000 }, max: { index: MaxKey() }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 4 }) }
        ],
        tags: []
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 꼭 같은 데이터유형 (숫자면 숫자) 로 지정할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자로 분할된 레인지샤딩에서 문자로도 추가할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1722388246970&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] rangeShardDB&amp;gt; sh.splitAt(&quot;rangeShardDB.collection1&quot;, { &quot;index&quot;: &quot;가&quot; } )
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722388105, i: 6 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722388105, i: 5 })
}

[direct: mongos] rangeShardDB&amp;gt; sh.status()
  {
    database: {
      _id: 'rangeShardDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('d6db39d1-0b81-40a7-a539-327cf5158922'),
        timestamp: Timestamp({ t: 1722242803, i: 1 }),
        lastMod: 1
      }
    },
    collections: {
      'rangeShardDB.collection1': {
        shardKey: { index: 1 },
        unique: false,
        balancing: true,
        chunkMetadata: [ { shard: 'rs1', nChunks: 4 } ],
        chunks: [
          { min: { index: MinKey() }, max: { index: 1000 }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 1 }) },
          { min: { index: 1000 }, max: { index: 2000 }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 3 }) },
          { min: { index: 2000 }, max: { index: '가' }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 5 }) },
          { min: { index: '가' }, max: { index: MaxKey() }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 6 }) }
        ],
        tags: []
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>mongodb 레인지샤딩</category>
      <category>splitat</category>
      <category>레인지샤딩</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/647</guid>
      <comments>https://mozi.tistory.com/647#entry647comment</comments>
      <pubDate>Wed, 31 Jul 2024 10:11:07 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 복합인덱스 서브필드 구성 조회 테스트</title>
      <link>https://mozi.tistory.com/646</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DmXH5/btsIR0oKMHn/MyakQijlAf7G1VYEOKJi71/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DmXH5/btsIR0oKMHn/MyakQijlAf7G1VYEOKJi71/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DmXH5/btsIR0oKMHn/MyakQijlAf7G1VYEOKJi71/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDmXH5%2FbtsIR0oKMHn%2FMyakQijlAf7G1VYEOKJi71%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 복합인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 인덱스에서는 여러 필드를 조합하여 인덱스를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 서브필드를 키로 인덱스로 만들 수 있는데 부모필드를 인덱스로 만들때와는 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. MongoDB 테스트 데이터 적재&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모필드와 서브필드에 다양한 방법으로 데이터를 적재한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722327429372&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# field1 와 field2 는 부모필드
# field1_1 와 field1_2 는 field1 의 서브필드

# 데이터 적재
[direct: mongos] indexDB&amp;gt; db.compositeCollection.insert ( 
  { field1: { field1_1: 123, field1_2: &quot;test&quot; }
  , field2 : &quot;2024-07-30&quot; } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a8a1676c7cfe6d2f1b157e') }
}

# 데이터 적재 - field1 의 field1_1 / field1_2 서브필드 순서를 반대로 적재
[direct: mongos] indexDB&amp;gt; db.compositeCollection.insert ( 
  { field1: { field1_2: &quot;test&quot;, field1_1: 123 }
  , field2 : &quot;2024-07-30&quot; } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a8a2a36c7cfe6d2f1b157f') }
}

# 데이터 적재 - field1 의 field1_1 과 field1_2 순서는 동일하지만, 새로운 서브필드를 추가적재
[direct: mongos] indexDB&amp;gt; db.compositeCollection.insert ( 
  { field1: { field1_1: 123, field1_2: &quot;test&quot;, field1_3: &quot;addfield&quot; }
  , field2 : &quot;2024-07-30&quot; } )
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a8a4d86c7cfe6d2f1b1580') }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. 복합인덱스 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 부모필드에 인덱스를 생성하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모필드로 생성되는 인덱스는 저장되는 값이 어떤 값이든지 BSON 으로 전환한 다음 하나의 바이트 배열 값으로 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 부모필드의 서브 도큐먼트에서 필드의 순서가 변경된다면 다른 바이트 배열이 되기 때문에 다른 값으로 인식한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 필드의 순서가 반대로 적재되거나, 다른 서브필드가 있는경우 이 도큐먼트는 조회되지 않는걸 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1722327509690&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 부모필드에 인덱스 생성
[direct: mongos] indexDB&amp;gt; db.compositeCollection.createIndex ( { &quot;field1&quot; : 1 }, { name: &quot;compositeIndex_field1&quot; } )
compositeIndex_field1


# 데이터 조회 ( 필드의 순서가 반대로 적재된 데이터는 조회되지 않음 )
[direct: mongos] indexDB&amp;gt; db.compositeCollection.find( { field1: { field1_1: 123, field1_2: &quot;test&quot;} } )
[
  {
    _id: ObjectId('66a8a1676c7cfe6d2f1b157e'),
    field1: { field1_1: 123, field1_2: 'test' },
    field2: '2024-07-30'
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. 서브필드에 인덱스를 생성하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브필드 각각에 인덱스를 생성하는 경우, 두개의 필드 값이 각각 인덱스 키 엔트리로 참여하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 조건에 주어지는 필드의 순서와 관계없이 같은 조건으로 두 도큐먼트를 모두 검색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 다른 서브필드가 있더라도 모두 조회가 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1722327556425&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 서브필드에 인덱스 생성
[direct: mongos] indexDB&amp;gt; db.compositeCollection.createIndex ( { &quot;field1.field1_1&quot; : 1, &quot;field1.field1_2&quot; : 1 }, { name: &quot;compositeIndex_field1_1_field1_2&quot; } )
compositeIndex_field1_1_field1_2

# 데이터 조회 ( 모두 출력됨 )
[direct: mongos] indexDB&amp;gt; db.compositeCollection.find( { &quot;field1.field1_1&quot;: 123, &quot;field1.field1_2&quot;: &quot;test&quot; } )
[
  {
    _id: ObjectId('66a8a1676c7cfe6d2f1b157e'),
    field1: { field1_1: 123, field1_2: 'test' },
    field2: '2024-07-30'
  },
  {
    _id: ObjectId('66a8a2a36c7cfe6d2f1b157f'),
    field1: { field1_2: 'test', field1_1: 123 },
    field2: '2024-07-30'
  },
  {
    _id: ObjectId('66a8a4d86c7cfe6d2f1b1580'),
    field1: { field1_1: 123, field1_2: 'test', field1_3: 'addfield' },
    field2: '2024-07-30'
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-3. 부모필드와 서브필드에 인덱스를 만들때의 차이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모필드로 인덱스를 생성하는 경우 모든 하위값을 BSON 으로 변환한 다음 인덱스 키 엔트리로 사용하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브필드에 인덱스를 생성하는 경우 각각의 필드값을 키 엔트리로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이는 검색의 차이로 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모필드로 인덱스를 생성하는 경우 새로운 서브필드명이 추가되는 순간 인덱스에 참여하게 되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브필드로 인덱스를 생성하는 경우 새로운 서브필드명이 추가되더라도 인덱스에 참여하지 않는다.&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>mongodb 복합인덱스</category>
      <category>mongodb 서브필드 인덱스</category>
      <category>MongoDB 인덱스</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/646</guid>
      <comments>https://mozi.tistory.com/646#entry646comment</comments>
      <pubDate>Tue, 30 Jul 2024 17:33:31 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 쿼리 실행계획 확인하는 방법 explain</title>
      <link>https://mozi.tistory.com/645</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NY79W/btsIPZLBWXC/gsYXpVRVCxPUWFVFMGd1Qk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NY79W/btsIPZLBWXC/gsYXpVRVCxPUWFVFMGd1Qk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NY79W/btsIPZLBWXC/gsYXpVRVCxPUWFVFMGd1Qk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNY79W%2FbtsIPZLBWXC%2FgsYXpVRVCxPUWFVFMGd1Qk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 실행계획&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리가 효율적으로 실행되었는지 알기위해 MongoDB 는 explain 이라는 명령을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행계획을 분석하는 방법은 내용이 방대하여 여기에서는 다루지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 실행계획 확인&lt;/h2&gt;
&lt;pre id=&quot;code_1722326514483&quot; class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 데이터베이스 이동
[direct: mongos] test&amp;gt; use explainDB
switched to db explainDB

# 데이터 적재
[direct: mongos] explainDB&amp;gt; db.explainCollection.insert ( { identity : 1, name : &quot;Park&quot; } )
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.

{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a89cc76d332c462a1b157d') }
}
[direct: mongos] explainDB&amp;gt; db.explainCollection.insert ( { identity : 2, name : &quot;Kim&quot; } )
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a89d7d6c7cfe6d2f1b157d') }
}

# 실행계획 조회
[direct: mongos] explainDB&amp;gt; db.explainCollection.insert ( { identity : 2, name : &quot;Kim&quot; } )
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a89d7d6c7cfe6d2f1b157d') }
}
[direct: mongos] explainDB&amp;gt; db.explainCollection.find ( { identity : 2 } ).explain()
{
  queryPlanner: {
    mongosPlannerVersion: 1,
    winningPlan: {
      stage: 'SINGLE_SHARD',
      shards: [
        {
          shardName: 'rs1',
          connectionString: 'rs1/127.0.0.1:30001',
          serverInfo: {
            host: 'ip-999-99-9-999.ap-northeast-2.compute.internal',
            port: 30001,
            version: '7.0.12',
            gitVersion: 'b6513ce0781db6818e24619e8a461eae90bc94fc'
          },
          namespace: 'explainDB.explainCollection',
          indexFilterSet: false,
          parsedQuery: { identity: { '$eq': 2 } },
          queryHash: '5AB21508',
          planCacheKey: '5AB21508',
          maxIndexedOrSolutionsReached: false,
          maxIndexedAndSolutionsReached: false,
          maxScansToExplodeReached: false,
          winningPlan: {
            stage: 'COLLSCAN',
            filter: { identity: { '$eq': 2 } },
            direction: 'forward'
          },
          rejectedPlans: []
        }
      ]
    }
  },
  serverInfo: {
    host: 'ip-999-99-9-999.ap-northeast-2.compute.internal',
    port: 20001,
    version: '7.0.12',
    gitVersion: 'b6513ce0781db6818e24619e8a461eae90bc94fc'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600,
    internalQueryFrameworkControl: 'trySbeRestricted'
  },
  command: {
    find: 'explainCollection',
    filter: { identity: 2 },
    lsid: { id: UUID('563d6010-4000-4a17-a431-9a64b2c44bad') },
    '$clusterTime': {
      clusterTime: Timestamp({ t: 1722326399, i: 1 }),
      signature: {
        hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
        keyId: 0
      }
    },
    '$db': 'explainDB'
  },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722326443, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722326443, i: 1 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB</category>
      <category>mongodb 실행계획</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/645</guid>
      <comments>https://mozi.tistory.com/645#entry645comment</comments>
      <pubDate>Tue, 30 Jul 2024 17:02:22 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 컬렉션에 인덱스 생성, 삭제, 조회 방법</title>
      <link>https://mozi.tistory.com/644</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGSbr/btsIRuje1Yh/1EGGH1pWmNh8I2LJedLfpK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGSbr/btsIRuje1Yh/1EGGH1pWmNh8I2LJedLfpK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGSbr/btsIRuje1Yh/1EGGH1pWmNh8I2LJedLfpK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGSbr%2FbtsIRuje1Yh%2F1EGGH1pWmNh8I2LJedLfpK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 효율적으로 사용하기 위해 MongoDB 는 인덱스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 인덱스 유형은 매우 다양하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. MongoDB 인덱스 유형&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;단일 필드 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;특정 필드에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;복합 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;여러 필드를 조합한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;해시 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;해시된 값으로 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;텍스트 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;텍스트 검색을 위한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;2dsphere 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;지리공간 데이터에 대한 인덱스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.907%;&quot;&gt;유일 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 72.093%;&quot;&gt;유일한 값만 허용&lt;br /&gt;- 로컬서버 기준에 한정, 샤드서버로 데이터가 분산되는 경우에는 어플리케이션에서 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 인덱스 생성&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 인덱스 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1722314137095&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; use indexDB
switched to db indexDB

# 싱글 인덱스
[direct: mongos] indexDB&amp;gt; db.singleCollection.createIndex( { name : 1 } )
name_1

# 복합 인덱스
[direct: mongos] indexDB&amp;gt; db.compositeCollection.createIndex( { name : 1, address : 1 } )
name_1_address_1

# 해시 인덱스
[direct: mongos] indexDB&amp;gt; db.hashCollection.createIndex( { number : &quot;hashed&quot; } )
number_hashed

# 유니크 인덱스
[direct: mongos] indexDB&amp;gt; db.uniqueCollection.createIndex( { identity : 1 }, { unique : true } )
identity_1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. 백그라운드 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 생성할 때는 잠금이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 쿼리는 도큐먼트에 접근할 수 없기때문에 백그라운드로 수행하는 옵션을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1722314247877&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.backCollection.createIndex( { grade : 1 }, { background: true } )
grade_1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-3. 인덱스 명칭 지정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스에 명칭을 지정하지 않으면 필드명 + 자동증가값이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스에 명칭을 지정하면 지정한 명칭으로 생성된다.&lt;/p&gt;
&lt;pre id=&quot;code_1722314989708&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.singleCollection.createIndex ( { address : 1 }, { name: &quot;singleCollection_index_address&quot; } )
singleCollection_index_address

[direct: mongos] indexDB&amp;gt; db.singleCollection.getIndexes()
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { address: 1 }, name: 'singleCollection_index_address' }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-4. 서브필드의 인덱스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 필드는 서브필드를 가질 수 있는데 서브필드에 대해서도 인덱스를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 서브필드를 가지는 필드에 인덱스를 생성하는 것과, 서브필드에 인덱스를 생성하는 것은 서로 다른 정렬로 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1722314790078&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.subfieldCollection.insert ( 
  { parentField : { childField1 : 123 , childField2 : &quot;test&quot; } 
    , parentField2 : &quot;2024-07-30&quot; } 
)
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a86f9cff05830fb31b157d') }
}

# 서브필드에 인덱스 생성
[direct: mongos] indexDB&amp;gt; db.subfieldCollection.createIndex ( { &quot;parentField1.childField1&quot; : 1, &quot;parentField1.childField2&quot; : 1 } )
parentField1.childField1_1_parentField1.childField2_1

# 부모필드에 인덱스 생성
[direct: mongos] indexDB&amp;gt; db.subfieldCollection.createIndex ( { &quot;parentField1&quot; : 1} )
parentField1_1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 인덱스 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 컬렉션에 인덱스를 확인하려면 getIndexes 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_id 는 문서의 고유값을 식별하는 MongoDB 내부값으로 인덱스가 기본으로 생성된다.&lt;/p&gt;
&lt;pre id=&quot;code_1722314335313&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.singleCollection.getIndexes()
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { name: 1 }, name: 'name_1' }
]

[direct: mongos] indexDB&amp;gt; db.hashCollection.getIndexes()
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { number: 'hashed' }, name: 'number_hashed' }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 인덱스 삭제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dropIndex 를 사용하여 인덱스를 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dropIndex ( ) 의 안에는 인덱스 이름을 넣어주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1722314445867&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] indexDB&amp;gt; db.singleCollection.dropIndex(&quot;name_1&quot;)
{
  raw: { 'rs1/127.0.0.1:30001': { nIndexesWas: 2, ok: 1 } },
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722314426, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722314426, i: 1 })
}

[direct: mongos] indexDB&amp;gt; db.singleCollection.getIndexes()
[ { v: 2, key: { _id: 1 }, name: '_id_' } ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>MongoDB 인덱스</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/644</guid>
      <comments>https://mozi.tistory.com/644#entry644comment</comments>
      <pubDate>Tue, 30 Jul 2024 13:47:13 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 레인지샤딩 만들기와 메타 조회방법</title>
      <link>https://mozi.tistory.com/643</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rXpNQ/btsIRXZwPOK/rfiqKjAn1XtKcQ30AlGQkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rXpNQ/btsIRXZwPOK/rfiqKjAn1XtKcQ30AlGQkK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rXpNQ/btsIRXZwPOK/rfiqKjAn1XtKcQ30AlGQkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrXpNQ%2FbtsIRXZwPOK%2FrfiqKjAn1XtKcQ30AlGQkK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;1. MongoDB 레인지샤딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터와 서버의 부하를 분산하기 위해 MongoDB 는 샤딩을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩 기법은 해시 / 레인지가 있고 이 포스팅에서는 레인지 샤딩을 구성해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;3. 전제조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodb 는 2개의 샤드서버가 구성되어 있는 상태이다.&lt;/p&gt;
&lt;pre id=&quot;code_1722242635886&quot; class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; sh.status();
shardingVersion
{ _id: 1, clusterId: ObjectId('66838519ea100ebaf5ee3cc8') }
---
shards
[
  {
    _id: 'rs1',
    host: 'rs1/127.0.0.1:30001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896113, i: 1 })
  },
  {
    _id: 'rs2',
    host: 'rs2/127.0.0.1:40001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896932, i: 2 })
  }
]
---
active mongoses
[ { '7.0.12': 1 } ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;4. 레인지샤딩 만들기&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 데이터베이스 샤드 활성화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 생성 후 샤드를 활성화 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩 활성화는 enableSharding 명령어를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1722242748802&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;[direct: mongos] admin&amp;gt; use rangeShardDB
switched to db rangeShardDB

[direct: mongos] rangeShardDB&amp;gt; sh.enableSharding(&quot;rangeShardDB&quot;)
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722242804, i: 6 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722242804, i: 2 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4-2. 컬렉션에 샤드 키 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션을 사딩하려면 shardCollection 명령어를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키에 1 혹은 -1 를 입력하면 입력한 키로 샤딩된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rangeShardDB 의 collection1 의 index 필드를 오름차순으로 정렬하며 샤딩하는 의미이다.&lt;/p&gt;
&lt;pre id=&quot;code_1722242748803&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;[direct: mongos] rangeShardDB&amp;gt; sh.shardCollection(&quot;rangeShardDB.collection1&quot;, {&quot;index&quot;: 1})
{
  collectionsharded: 'rangeShardDB.collection1',
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722242863, i: 33 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722242863, i: 33 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 하면 레인지샤딩 컬렉션이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;5. 레인지샤딩 메타정보 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레인지샤딩 메타정보는 라우터에서 sh.status 로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collections 를 보면 index 필드가 1 로 샤드키로 되어있으며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chunkMetadata 와 chunks 에서 각 청크별 값의 범위 위치한 샤드서버를 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 레인지범위가 분할되어있지 않기 때문에 1개의 범위만 나온다.&lt;/p&gt;
&lt;pre id=&quot;code_1722242748804&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; sh.status()
  {
    database: {
      _id: 'rangeShardDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('d6db39d1-0b81-40a7-a539-327cf5158922'),
        timestamp: Timestamp({ t: 1722242803, i: 1 }),
        lastMod: 1
      }
    },
    collections: {
      'rangeShardDB.collection1': {
        shardKey: { index: 1 },
        unique: false,
        balancing: true,
        chunkMetadata: [ { shard: 'rs1', nChunks: 1 } ],
        chunks: [
          { min: { index: MinKey() }, max: { index: MaxKey() }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 0 }) }
        ],
        tags: []
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>mongodb 레인지샤딩</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/643</guid>
      <comments>https://mozi.tistory.com/643#entry643comment</comments>
      <pubDate>Mon, 29 Jul 2024 17:45:03 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 해시샤딩 만들기와 메타 조회방법</title>
      <link>https://mozi.tistory.com/642</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1YtO6/btsIRgkXZVj/cMPV8og1Fb886X1uln4cYk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1YtO6/btsIRgkXZVj/cMPV8og1Fb886X1uln4cYk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1YtO6/btsIRgkXZVj/cMPV8og1Fb886X1uln4cYk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1YtO6%2FbtsIRgkXZVj%2FcMPV8og1Fb886X1uln4cYk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. MongoDB 해시샤딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터와 서버의 부하를 분산하기 위해 MongoDB 는 샤딩을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩 기법은 해시 / 레인지가 있고 이 포스팅에서는 해시 샤딩을 구성해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. MongoDB 테스트 버전&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongosh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2.2.10&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mongodb&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;7.0.12&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Config : 1개, 포트 20000&lt;br /&gt;Route : 1개, 포트 20001&lt;br /&gt;Shard1 : 1개, 포트 30001&lt;br /&gt;Shard2 : 1개, 포트 40001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 전제조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodb 는 2개의 샤드서버가 구성되어 있는 상태이다.&lt;/p&gt;
&lt;pre id=&quot;code_1722241217262&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; sh.status();
shardingVersion
{ _id: 1, clusterId: ObjectId('66838519ea100ebaf5ee3cc8') }
---
shards
[
  {
    _id: 'rs1',
    host: 'rs1/127.0.0.1:30001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896113, i: 1 })
  },
  {
    _id: 'rs2',
    host: 'rs2/127.0.0.1:40001',
    state: 1,
    topologyTime: Timestamp({ t: 1719896932, i: 2 })
  }
]
---
active mongoses
[ { '7.0.12': 1 } ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 해시샤딩 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 데이터베이스 샤드 활성화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 생성 후 샤드를 활성화 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩 활성화는 enableSharding 명령어를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1722241461733&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] admin&amp;gt; use hashShardDB
switched to db hashShardDB

[direct: mongos] hashShardDB&amp;gt; sh.enableSharding(&quot;hashShardDB&quot;)
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722241448, i: 6 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722241448, i: 2 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. 컬렉션에 샤드 키 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션을 사딩하려면 shardCollection 명령어를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키에 hashed 를 입력하면 입력한 키로 샤딩된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashShardDB 의 collection1 의 index 필드를 hashed 기준으로 샤딩하는 의미이다.&lt;/p&gt;
&lt;pre id=&quot;code_1722241604667&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] hashShardDB&amp;gt; sh.shardCollection(&quot;hashShardDB.collection1&quot;, {index: &quot;hashed&quot;})
{
  collectionsharded: 'hashShardDB.collection1',
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1722241591, i: 43 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1722241591, i: 43 })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 하면 해시샤딩 컬렉션이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-3. 컬렉션에 데이터 적재&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에 1000 건을 적재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722241877981&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] hashShardDB&amp;gt; for (var n = 1; n &amp;lt;= 1000; n++) { db.collection1.insert({index: n, value: &quot;Test Value&quot;}) }
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('66a7540d91ba31c2101b1d4c') }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-4. 샤드별로 데이터 조회&lt;/h3&gt;
&lt;pre id=&quot;code_1722242169773&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mongosh 127.0.0.1:30001
rs1 [direct: primary] test&amp;gt; use hashShardDB
switched to db hashShardDB
rs1 [direct: primary] hashShardDB&amp;gt; db.getCollection(&quot;collection1&quot;).find({}).count()
520


# mongosh 127.0.0.1:40001
rs2 [direct: primary] test&amp;gt; use hashShardDB
switched to db hashShardDB
rs2 [direct: primary] hashShardDB&amp;gt; db.getCollection(&quot;collection1&quot;).find({}).count()
480&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 해시샤딩 메타정보 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시샤딩 메타정보는 라우터에서 sh.status 로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collections 를 보면 index 필드가 hashed 로 샤드키 되어있으며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chunkMetadata 와 chunks 에서 각 청크별 값의 범위 위치한 샤드서버를 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1722242247407&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[direct: mongos] test&amp;gt; sh.status()
  {
    database: {
      _id: 'hashShardDB',
      primary: 'rs1',
      partitioned: false,
      version: {
        uuid: UUID('352fc813-f311-44f5-be6e-2e86c8e26380'),
        timestamp: Timestamp({ t: 1722241447, i: 1 }),
        lastMod: 1
      }
    },
    collections: {
      'hashShardDB.collection1': {
        shardKey: { index: 'hashed' },
        unique: false,
        balancing: true,
        chunkMetadata: [ { shard: 'rs1', nChunks: 2 }, { shard: 'rs2', nChunks: 2 } ],
        chunks: [
          { min: { index: MinKey() }, max: { index: Long('-4611686018427387902') }, 'on shard': 'rs2', 'last modified': Timestamp({ t: 1, i: 0 }) },
          { min: { index: Long('-4611686018427387902') }, max: { index: Long('0') }, 'on shard': 'rs2', 'last modified': Timestamp({ t: 1, i: 1 }) },
          { min: { index: Long('0') }, max: { index: Long('4611686018427387902') }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 2 }) },
          { min: { index: Long('4611686018427387902') }, max: { index: MaxKey() }, 'on shard': 'rs1', 'last modified': Timestamp({ t: 1, i: 3 }) }
        ],
        tags: []
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database/MongoDB 실습</category>
      <category>mongodb 해시샤딩 만들기</category>
      <category>mongodb 해시샤딩 메타</category>
      <category>mongodb 해시샤딩 적재</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/642</guid>
      <comments>https://mozi.tistory.com/642#entry642comment</comments>
      <pubDate>Mon, 29 Jul 2024 17:38:48 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 인덱스(1)</title>
      <link>https://mozi.tistory.com/641</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzQ0sn/btsIOmyIrqV/bzYh1cIWnNIY9Cf91rBHwk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzQ0sn/btsIOmyIrqV/bzYh1cIWnNIY9Cf91rBHwk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzQ0sn/btsIOmyIrqV/bzYh1cIWnNIY9Cf91rBHwk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzQ0sn%2FbtsIOmyIrqV%2FbzYh1cIWnNIY9Cf91rBHwk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 성능에 있어 인덱스는 빠질 수 없는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 사용할 수 있는 인덱스와 종류에 대해 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 랜덤 I/O 와 순차 I/O 에 대해 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스와 관련된 성능의 튜닝은 디스크 I/O 를 얼마나 줄이냐가 관건인 것들이 많이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디스크 읽기 방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디스크 저장 매체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 장치의 성능을 보면 기계식 장치인 디스크가 가장 취약한 성능을 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 이런 기계식에서 전자식으로 교체되고 있고 대표적으로 SSD 가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSD 는 D-RAM 과 달리 전원 공급에 관계없이 한번 기록한 내용을 영구적으로 보존하는 플래시 메모리를 내장하고 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D-RAM 보다는 떨어지지만 기계식 HDD 와 비교하면 월등하게 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSD 의 가장 큰 장점은 랜덤 I/O 에 매우 적합하다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순차 I/O 는 DB 에서 그다지 중요한 요소는 아니며 HDD 도 순차 I/O 성능은 크게 문제되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;랜덤 I/O 와 순차 I/O&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순차 I/O 는 3개의 페이지를 디스크에 기록하기 위해 시스템 콜을 1번한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에 기록해야 할 위치를 찾기 위해 디스크의 헤더를 1번만 움직인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤 I/O 는 3개의 페이지를 디스크에 기록하기 위해 시스템 콜을 3번한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 디스크의 헤더를 3번 움직인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크의 부하는 얼마나 많은 데이터를 한번에 기록하는지 보다는, 얼마나 자주 디스크에 기록을 요청하는지에 의존적이어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 번 쓰기나 읽기를 요청하는 작업이 부하가 훨씬 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 쿼리를 튜닝 시 랜덤 I/O 횟수를 얼마나 줄이는지에 따라 달려있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인덱스란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 저장 성능을 희생해서 상대적으로 읽기 속도를 향상시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 Key 가 Index 와 같은 의미이므로 함께 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 역할별로 구분하면 프라이머리 와 세컨드리 키로 구분된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 저장 방식은 B-Tree 와 Hash 인덱스가 대표적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서는 전문 인덱스나 공간 인덱스등의 알고리즘도 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 중복을 허용하는지 여부로 분류하면 유니크와 유니크하지 않은 인덱스로 구분이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 인덱스의 개요&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클러스터링 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터링 인덱스는 책에 정리되어 있지 않지만, 현재는 제공되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 요약하면 클러스터 인덱스 키 값을 기준으로 문서를 정렬하여 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터형 인덱스 키는 _id 이고, 문서는 하나의 순서로만 저장가능하므로 컬렉셔 클러스터 인덱스는 하나만 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드를 저장하는 시점에 인덱스 키 값 순서대로 데이터를 저장하기 때문에 INSERT 가 느리게 처리되지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터링 키 값을 기준으로 범위 검색을 수행하는 경우 별도의 랜덤 액세스 없이 레코드를 읽기 때문에 매우 빠르게 범위스캔이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/clustered-collections/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mongodb.com/ko-kr/docs/manual/core/clustered-collections/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721979767335&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;클러스터형 컬렉션 - MongoDB 매뉴얼 v7.0&quot; data-og-description=&quot;클러스터형 컬렉션은 인덱스 사양과 동일한 WiredTiger 파일에 인덱스된 문서를 저장합니다. 컬렉션의 문서와 인덱스를 동일한 파일에 저장하면 일반 인덱스에 비해 저장 및 성능상의 이점이 있습&quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/clustered-collections/&quot; data-og-url=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/clustered-collections/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iltMg/hyWG26VKGN/h5H46K2UzZPbeSOjmFdvK0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/bptBBd/hyWGOt4PiH/RO8kbsKWoCJYMFAEUrhn1K/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/clustered-collections/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/clustered-collections/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iltMg/hyWG26VKGN/h5H46K2UzZPbeSOjmFdvK0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/bptBBd/hyWGOt4PiH/RO8kbsKWoCJYMFAEUrhn1K/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;클러스터형 컬렉션 - MongoDB 매뉴얼 v7.0&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;클러스터형 컬렉션은 인덱스 사양과 동일한 WiredTiger 파일에 인덱스된 문서를 저장합니다. 컬렉션의 문서와 인덱스를 동일한 파일에 저장하면 일반 인덱스에 비해 저장 및 성능상의 이점이 있습&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 내부&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 엔진과 스토리지 엔진은 각각의 레이러로 구분되어 있고 플러그인 형태로 끼워넣을 수 있어 다양한 스토리지 엔진을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이런 스토리지 엔진들이 컬렉션과 인덱스를 구현하는 방법에는 조금씩 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브랜치 노드에 있는 인덱스 키 엔트리는 키와 값의 쌍을 가지며, 키는 사용자가 인덱스를 생성할 때 선택한 필드이며 값은 자식노드의 주소를 가리킨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프 노드에 있는 인덱스 키 엔트리도 키와 값의 쌍을 가지며, 키는 사용자가 인덱스를 생성할 때 선택한 필드이며 값은 내부적으로 키 값과 연결된 도큐먼트의 저장 주소를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브랜치 : 인덱스 키 -&amp;gt; 자식 노드 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 리프 : 인덱스 키 -&amp;gt; 레코드 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MMAPv1 스토리지 엔진의 Record-Id&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 키의 Record-Id 부분에 실제 도큐먼트가 저장된 물리적 주소를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진에서 도큐먼트의 이동을 경계하는 이유는 도큐먼트가 옮겨질때마다 도큐먼트의 물리 주소를 가지고 있는 인덱스의 엔트리를 찾아서 모두 변경해줘야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 많은 디스크 랜덤 액세스를 유발하므로 상당히 느리게 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;WiredTiger 스토리지 엔진의 Record-Id&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진과 달리 인덱스 키 엔트리에 논리 주소를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히 논리주소라기보다는 도큐먼트마다 고유의 식별자를 할당하여 Record-Id 로 부여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트의 고유 식별자는 자동 증가값인 Auto Increment 로 1씩 자동증가하는 시퀀스를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Record-Id 도 64비트 정수 타입을 사용하고 컬렉션 단위로 별도의 자동증가값을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 문서가 데이터파일 내에서 위치가 이동되더라도 처음 할당된 논리적인 주소값은 변하지 않고 계속 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이 자동증가값으로 어떻게 실제 데이터 파일의 도큐먼트를 찾는지는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진은 내부적으로 Record-Id 값을 인덱스로 가지는 내부 인덱스를 하나 더 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내부 인덱스는 Record-Id 값을 키로하는 클러스터링 인덱스를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 WiredTiger 에서 검색 시 두 번의 인덱스 검색을 수행해야 최종 결과를 얻을 수 있어 조회성능은 느려지나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경은 훨씬 유연하게 처리할 수 있게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로컬 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 형태의 세컨드리 인덱스를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 인덱스는 로컬인덱스로 관리되므로 각 샤드가 저장하고 있는 도큐먼트에 대한 인덱스만 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 프라이머리 인덱스나 유니크 인덱스는 샤드 키를 반드시 포함해야 하거나 응용 프로그램 수준에서 유니크함을 보장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 인덱스도 다른 DBMS 와 동일하게 쿼리 성능을 높이는 반면, 생성 및 변경처리 성능을 저하시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 샤드의 데이터를 균등하게 배치하기 위해 밸런서가 백그라운드로 샤드 간 데이터를 자동으로 분산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때도 데이터를 생성 및 삭제하는 작업을 수행하는데, 인덱스가 많은 컬렉션은 그만큼 데이터 밸런싱 작업을 지연시키고 부하를 일으키는 원인이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 키 엔트리 자료 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트는 MongoDB 내부적으로 BSON 이라는 형태의 JSON 에서 변형된 포맷으로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 도큐먼트는 키 값 쌍으로 된 JSON 포맷을 사용하므로 데이터 파일에 필드명과 필드 값이 같이 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 데이터는 스키마를 가지는 RDBMS 보다 디스크에 저장되는 데이터 용량이 더 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 키 값 쌍으로 구성된 구성된 도큐먼트는 MongoDB 의 컬렉션에만 해당되며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 내부 저장 구조는 도큐먼트나 BSON 자료 구조를 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 MongoDB 를 스키마프리 데이터베이스로 생각하나, 인덱스에서는 맞지 않는 이야기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 내부적으로 별도의 스키마를 가지고 있고 각 인덱스가 어떤 종류의 인덱스이고 이 인덱스를 구성하는 필드가 어떤것인지에 대한 메타정보를 가지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마를 가지고 있기에 인덱스의 각 키 엔트리에 필드명을 굳이 저장할 이유가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 B-Tree 인덱스에서는 필드의 명칭이 들어가 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;B-Tree 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 는 데이터베이스의 인덱싱 알고리즘으로 가장 일반적이고 오래되면서 범용적인 목적을 만족시키는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 에 변형된 형태가 있지만 조금씩 차이가 있을 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 구조 시 이진트리의 B 라고 생각할 수 있지만, 바이너리 트리와 일반 DBMS 에서 사용되는 B-Tree 는 많은 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은경우 B-Tree 를 2개의 자식 노드만 가진다고 생각하는 경우가 많지만 단순히 표현해서 이런거고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBMS 에서 사용하는 B-Tree 가 항상 자식노드를 2개만 가지는 바이너리 트리임을 의미하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구조 및 특성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 리프 노드의 각 키 값은 테이블의 데이터 레코드를 찾아가기 위한 물리적인 주소 값을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 키 값은 모두 정렬되어 있지만, 테이블의 데이터 레코드는 기본적으로 정렬되어 있지 않고 INSERT 된 순서대로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드 주소는 도큐먼트의 물리적인 위치일수도 있고 논리적인 시퀀스 값일수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 스토리지 엔진에 따라 조금씩 의미가 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 컬렉션의 인덱스는 항상 인덱스 필드의 값과 주소 값(RecordID) 의 조합이 인덱스 레코드로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 컬럼 명세를 가지지 않기 때문에 NOSQL 로 분류되나, 인덱스는 반드시 컬럼의 명세를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 데이터에는 필드 이름을 관리하지 않고 인덱스의 메타 정보에 필드 이름이 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 필드 이름이 포함되는 경우가 있는데, 필드에 서브 도큐먼트를 가지는 경우이다.&lt;/p&gt;
&lt;pre id=&quot;code_1721985927610&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  name: &quot;mozi&quot;,
  Phone: {
    brand: &quot;SKT&quot;,
    number: &quot;010-1234-5678&quot;
  }
}

createIndex({name:1})
createIndex({Phone:1})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name 은 인덱스에 필드가 들어가지 않지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Phone 은 서브 도큐먼트가 있고 인덱스에는 서브 도큐먼트의 필드 brand , number 가 모두 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B-Tree 인덱스 키 추가 및 삭제&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 키 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 값이 존재하지 않으면 리프 노드에 인덱스 값을 추가하고 그 하위에 데이터 레코드가 저장된 위치를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드가 꽉 차게되면 노드를 분리해야 하는데 이 경우에는 브랜치 노드의 변경이 필요할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 키 삭제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 의 키 값을 삭제할 때는 간단한데, 키 값을 찾아서 삭제 마크만 하면 작업이 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마킹 작업 또한 디스크 쓰기가 발생하지만, 내부적으로 캐시를 가지고 있기 때문에 변경된 데이터를 디스크에 기록하는 작업은 사용자 데이터 변경 요청과는 비동기로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 키 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 값을 변경하는 경우는 먼저 키 값을 삭제한 다음 새로운 키 값을 추가하는 형태로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 값의 삭제와 추가 작업은 위의 작업과 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 키 검색&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 검색하는 작업은 B-Tree 의 루트노드부터 시작해 브랜치노드를 거쳐 최종 리프노드까지 비교하는 과정을 통해 이동하는데, 이 과정을 트리검색이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 이용한 검색은 100% 일치 또는 값의 앞 부분이 일치할 때만 사용할 수 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부등호 비교에는 B-Tree 인덱스 검색 기능을 이용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 컬럼 값이 변형이 가해진 후 비교검색을 하게되면 B-Tree 기능을 사용할 수 없는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변형된 값은 인덱스에 존재하지 않기 때문에 B-Tree 특성을 이요할 수 없게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B-Tree 인덱스 사용에 영향을 미치는 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 구성하는 필드의 사이즈와 도큐먼트 건수, 유니크한 값 개수등에 따라 검색이나 변경 작업의 성능이 영향을 미친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 키 값의 사이즈&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에 데이터를 저장하는 가장 기본 단위를 페이지 또는 블록이라 표현하며, 읽기 및 쓰기의 최소 작업 단위이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지는 각 스토리지 엔진이 데이터를 관리하는 기본 단위로, 인덱스도 페이지 단위로 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 브랜치 리프 노드를 구분한 기준 또한 페이지 단위로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 노드를 몇개 가지는지는 인덱스 페이지 크기와 키 값의 사이즈에 따라 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 WiredTiger 스토리지 엔진에서 페이지 크기를 16KB 로 사용하는 경우,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 키가 16바이트이고 값을 12바이트라고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순계산하면 16*1024 / (16+12) = 585 개를 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 최종적으로 자식노드를 585개 가질 수 있는 B-Tree 가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 키 값이 커지면 자식노드를 그만큼 적개 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 페이지를 더 많이 읽어야 하고 그만큼 느려진다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;B-Tree 깊이 ( Depth )&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 키 값의 사이즈가 커질수록 하나의 페이지가 담을 수 있는 개수가 작아지고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로인해 동일한 데이터 건수라 하더라도 B-Tree 의 깊이가 깊어져 디스크 읽기가 더 많이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 인덱스 키 값의 사이즈는 가능하면 작게 만드는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 Depth 가 5 이상으로 깊어지는 경우는 거의 없어, Depth 로 인해 성능이 저하되는 현상은 많이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;선택도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택도는 모든 인덱스 키 값 중에서 유니크한 값의 개수를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 키 값 중에서 중복된 값이 많아지면 많아질수록 동시에 선택도가 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 선택도가 높아야 좋고 그만큼 빠른 검색이된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;읽어야 하는 레코드의 건수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 통해 컬렉션의 도큐먼트를 읽는것은 상당히 고비용 작업이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 이용한 읽기의 손익 분기점을 판단해야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 통한 도큐먼트 읽기 작업이 15~20 % 를 넘어서면 인덱스를 이용하지 않고 컬렉션 스캔으로 필요한 레코드만 가려내는 방식으로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어야 할게 많은 도큐먼트는 인덱스를 사용하도록 힌트를 추가해도 성능적으로 얻을 수 있는 이득이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B-Tree 인덱스를 통한 데이터 읽기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 레인지 스캔&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 레인지 스캔은 인덱스 접근 방법 중에서 가장 대표적인 접근 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 스캔을 시작할 위치를 어떻게 선택하고, 일치하는 건에 대해 데이터 레코드를 읽기 위해 어떤 작업이 필요한지 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 레인지 스캔은 검색해야 할 인덱스의 범위가 결정된 경우에 사용할 수 있는 방식으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색하고자 하는 값의 수나 검색 결과 레코드의 건수와 관계없이 레인지 스캔이라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 시작점을 찾기 위해 루트노드부터 시작해서 브랜치 노드를 거쳐 최종적으로 리프 노드의 시작 지점을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리프 노드에서 시작해야 할 위치를 찾게 되면 그때부터 리프 노드 간의 링크를 이용해서 리프 노드만 스캔한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 스캔을 멈춰야 할 위치에서 사용자에게 결과를 반환하면 처리가 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 것은 인덱스 자체가 정렬되어 있기 때문에 어떤 방식으로 인덱스를 스캔하든지 가져오는 레코드의 순서는 정순 또는 역순으로 정렬된 상태로 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 인덱스의 리프 노드에서 검색 조건과 일치하는 건들에 대해서 데이터 파일의 실제 데이터 레코드를 읽어오는 과정은 레코드 한 건 한건별로 랜덤 I/O 가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 3건의 레코드가 검색 조건과 일치한다고 가정하면 랜덤 I/O 는 3번이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 인덱스를 통해 데이터 레코드를 읽는 작업은 비용이 많이 드는 작업이라고 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 프리픽스 스캔&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 필드를 대상으로 일부만 일치하는 패턴을 검색하고자 할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열의 좌측 일치 검색을 수행하는 정규 표현식이 인덱스를 활용하려면 3가지 조건을 맞춰야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반드시 문자열의 처음부터 일치하도록 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 검색 문자열이 시작표시 이외의 정규 표현식을 포함하지 않아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문자열의 마지막을 표현하는 $ 표시가 없어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건들은 SQL 에서 자주 사용되는 좌측 일치 LIKE 연산과 같은 조건으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 레인지 스캔과 동일한 방식으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;커버링 인덱스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스만으로 쿼리를 처리할 수 있는 경우에는 도큐먼트가 저장된 컬렉션 데이터 파일을 읽지 않고 쿼리를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컬렉션의 데이터 파일은 전혀 참조하지 않고 인덱스만 읽어서 쿼리가 처리되는 최적화를 커버링 인덱스라고 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 쿼리를 인덱스 커버 쿼리라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 인터섹션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 하나의 쿼리는 하나의 인덱스를 이용해서 처리된다. * 여러 컬렉션을 사용하는 경우 컬렉션별 인덱스 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 인덱스 인터섹션 최적화를 사용하는 경우 2개의 인덱스를 사용하며 MongoDB 서버는 3.0 버전부터 인터섹션 최적화를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* OR 조건이 사용된 경우도 여러 개 인덱스를 이용하는 경우가 있지만 이는 INTERSECT 연산이 아닌&amp;nbsp; 각각의 하위 조건이 각각의 인덱스를 사용하여 결과를 나타내고 UNION 으로 병합하는 과정이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인덱스 인터섹션 최적화는 웬만해서는 경험하기 어려운 최적화로, 그 이유는 효율적인 경우가 별로 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 인터섹션이 사용되었는지 확인하고 싶다면 AND_SORTED 와 AND_HASHED 가 실행계획에 있는지 확인하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘의 가장 큰 차이는 각 인덱스 검색의 결과가 RecordId 를 기준으로 정렬되어 있는지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 인터섹션이 사용되는 경우는 어떤 인덱스로도 최적화하기가 어렵다고 판단되는 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 경우 쿼리가 가장 효율적으로 처리될 수 있는 방법은 여러 조건이 하나의 인덱스를 이용해서 검색 범위를 좁힐 수 있는 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 풀 스캔&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 리프 노드의 제일 앞 또는 제일 뒤로 이동한 다음 인덱스의 리프노드를 연결하는 링크드 리스트를 따라서 처음부터 끝까지 스캔하는 방식을 인덱스 풀 스캔이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 레인지 스캔보다는 빠르지 않지만 컬렉션 풀 스캔을 하는것보다는 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 인덱스에 포함된 필드만으로 쿼리를 처리할 수 있을 때에는 컬렉션의 도큐먼트를 읽을 필요가 없어, 디스크 I/O 를 작게 유발하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파운드 인덱스 (compound)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 인덱스는 모두 필드를 1개만 가진 인덱스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 실제로 2개 이상의 필드를 가지는 인덱스가 더 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 N 번째 컬럼은 N-1번째 컬럼이 같은 레코드 내에서 다시 정렬된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 N 번째 컬럼이 아무리 정렬 순서가 빠르다 하더라도 N-1 컬럼의 정렬순서가 늦다면 인덱스 뒤쪽에 위치하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 인덱스 내에서 각 필드의 위치가 상당히 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;복합 필드 인덱스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 필드로만 구성된 경우를 단일 필드 인덱스라고 하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 필드가 복합적으로 연결된 인덱스를 컴파운드 인덱스라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스는 단일필드로만 생성할 수 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 인덱스 알고리즘은 대부분 여러 필드를 복합적으로 묶어서 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 여러 필드를 이용해서 컴파운드 인덱스를 생성하는&amp;nbsp; 경우 인덱스를 구성하는 각 필드가 서로 달느 정렬 방식을 가질수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단일 필드와 복합 필드의 기준&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 도큐먼트를 가지는 도큐먼트 하나를 인덱스로 구성하면 단일 필드로 취급된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 필드 인덱스는 도큐먼트의 1레벨 필드 뿐 아니라 서브 도큐먼트의 필드도 포함할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 서브 도큐먼트 각 필드의 조합으로 컴파운드 이넫그슬ㄹ 생성할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 필드 인덱스는&amp;nbsp; 해당필드에 저장되는 값이 어떤 값이든간에 BSON 으로 전환한 다음, 하나의 바이트 배열 값으로 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 필드의 서부 도큐먼트에서 필드의 순서가 변경된다면 다른 바이트 배열이 되기 때문에 다른 값으로 인식한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 서브 도큐먼트의 순서를 다르게 저장하면 서로 다른 값으로 인식하므로 동일 검색 조건의 결과 값으로 조회할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 두 개의 필드 값이 각각 인덱스 키 엔트리로 참여하므로 검색 조건에 주어지는 필드의 순서와 관계없이 같은 조건으로 두 도큐먼트 모두 검색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 큰 차이는 서브 도큐먼트를 가지는 도큐먼트가 필드 인덱스인경우, 어떤 서브 도큐먼트가 저장되더라도 그 값을 모두 BSON 으로 변환한 다음 전체 BSON 을 인덱스 키 엔트리로 사용하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 도큐먼트를 인덱스로 하는경우 다른 서브 도큐먼트에 관계없이 그 중에서 인덱스로 구성되는 서브 필드 조합으로만 컴파운드 인덱스를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;복합 인덱스의 장점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 RDBMS 의 컴파운드 인덱스는 단일 값을 가지는 컬럼을 결합해서 B-Tree 인덱스를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MongoDB 인덱스는 여러 타입의 인덱스를 혼합해서 결합할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 인덱스와 공간 인덱스를 결합해서 하나의 인덱스를 생성할 수도 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 와 전문 인덱스를 결합해서 하나의 인덱스를 생성할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B-Tree 인덱스의 정렬 및 스캔 방향&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스의 정렬&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스에서 각 필드의 정렬을 오름차순 또는 내림차순으로 결정할 수 있는 것은 B-Tree 인덱스만 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 생성할 때 인덱스 대상 필드를 1 또는 -1 로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1은 오름차순이며 -1 은 내림차순이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일필드 인덱스인경우 -1, 1 은 인덱스를 어떻게 정렬하여 저장하는지 이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리가 어떤 정렬을 가지던 옵티마이저는 뒤에서 읽거나 앞에서 읽도록 적절하게 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 컴파운드 인덱스에서 오름차순과 내림차순을 혼합해서 사용하는 경우는 정렬을 맞춰서 진행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스 스캔의 방향&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스의 읽기 방향이 달라지는 것은 주로 쿼리 문장의 정렬 처리나 최대, 최소값 등의 최적화 처리가 수행되는 쿼리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B-Tree 인덱스의 가용성과 효율성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 조건이나 그룹 또는 정렬에 대한 요건이나 find 에서 가져와야 하는 필드에 따라서 해당 쿼리가 인덱스를 사용할 수 있는지, 사용할 수 있다면 어떠한 비교 조건으로 가능한지 식별할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 식별로 조건을 최적화하고 쿼리에 맞게 인덱스를 최적으로 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 조건에서 인덱스를 사용할 수 있고, 어떤 경우 사용할 수 없는지 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;비교 조건의 종류와 효율성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 필드로 구성된 컴파운드 인덱스에서 각 필드의 순서와 그 필드에 사용된 조건이 = 의 동등비교인지 &amp;gt; &amp;lt; 인지와 같은 범위비교인지에 따라 각 인덱스 필드의 비교 형태와 효율이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= 와 &amp;gt; &amp;lt; 를 사용하는 쿼리에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= 이 먼저 나오는 복합 인덱스 경우 작업의 범위를 하나의 값으로 결정하는 조건으로 해당 값만 읽을 수 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; &amp;lt; 이 먼저 나오는 복합 인덱스 경우 작업의 범위를 제한하지 못하고 단순히 걸러주는 역할만 하는 조건을 필터링(체크) 조건이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 범위 결정 조건은 많으면 많을수록 쿼리 처리 성능을 높이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크 조건은 많으면 많을수록 쿼리 처리 성능을 높이지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인덱스의 가용성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 인덱스의 특징은 왼쪽 값을 기준으로 오른쪽 값이 정렬되어 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 왼쪽 값 없이는 인덱스 검색이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 패턴 일치 검색에서 인덱스를 사용하려면 반드시 정규 표현식이 ^ 로 시작해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 복합 인덱스에서 후순위로 구성된 필드만 조건에서 사용하는 경우 인덱스를 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 선행 필드부터 정렬되어 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가용성과 효율성 판단&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 조건을 위해서는 인덱스를 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용할 수 없는 것은 작업 범위 결정 조건으로 사용할 수 없다는 의미로, 경우에 따라 인덱스의 체크 조건으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- NOT-EQUAL 로 비교된 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문자열 패턴 검색에서 프리픽스 일치가 아닌경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문자열 데이터 타입의 콜레시션이 컬렉션이나 인덱스의 콜레이션과 다른 경우&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/641</guid>
      <comments>https://mozi.tistory.com/641#entry641comment</comments>
      <pubDate>Fri, 26 Jul 2024 11:16:05 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 샤딩(3)</title>
      <link>https://mozi.tistory.com/640</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qDpGn/btsIGVvyg61/t6PTBNIkEdvcVgdazCeTE0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qDpGn/btsIGVvyg61/t6PTBNIkEdvcVgdazCeTE0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qDpGn/btsIGVvyg61/t6PTBNIkEdvcVgdazCeTE0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqDpGn%2FbtsIGVvyg61%2Ft6PTBNIkEdvcVgdazCeTE0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;청크 밸런싱&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 간 데이터 불균형 현상을 피하기 위해 최대한 각 샤드가 가진 청크의 개수를 동일하게 만드려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그뿐만 아니라 하나의 청크가 너무 커지면 샤드 서버는 청크 하나의 이동에 너무 많은 시스템 자원을 소모한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하나의 청크가 너무 비대해지는 것을 막기 위해 각 청크에 데이터가 저장되거나 변경될 때마다 청크를 스플릿해야할지 체크하는 작업을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤드 클러스터 밸런서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밸런서는 샤드간 청크의 개수를 모니터링하다가 불균등해지면 적절히 청크를 옮겨서 샤드 간 부하의 균형을 맞춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불균형을 판단하는 기준은 청크를 가장 많이 가진 샤드와 가장 적게 가진 샤드의 개수가 임계치 이상 차이가 나면 청크 이동을 실행한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;전체 청크 수&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;기준 치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;20개 미만의 청크를 가진 컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;20~79개 사이의 청크를 가진 컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;80개 이상의 청크를 가진 컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동이 시작되는 임계치를 정해둔 이유는 청크 이동비용이 매우 많이 드는 작업이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 저장되고 삭제되면서 청크는 계속 커질 수 있고, 이로인해 청크가 스플릿되어 전체적으로 샤드의 청크 개수가 늘어날 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 샤드의 청크가 항상 동일 시점에 스플릿되는것은 아니기 때문에 일시적으로 2~3개 이상의 차이가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작은 차이가 일시적으로 발생할 때마다 청크를 이동하는 것은 샤드 서버에 너무 많은 부담을 주기 때문에 컬렉션이 가진 청크의 개수를 기준으로 임계치를 설정해둔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 밸런서가 시작되면 각 샤드가 가진 청크의 개수 차이가 기준치 이하로 떨어질때까지 청크를 이동해서 분산시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외적인 케이스로 청크 이동에 실패하게 되면 밸런서 역시 멈추게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 밸런서가 청크의 불균형을 감지하여 청크 이동을 시작하고 끝날 때까지를 밸런싱 라운드라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전까지는 MongoDB 라우터에서 밸런서가 실행되었는데 라우터 인스턴스는 많이 있을 수 있으므로 lock 잠금 경합으로 좋지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4 버전부터 밸런서 작업을 컨피그 서버에서 수행하도록 아키텍처를 개선했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버에서는 프라이머리 멤버만 청크 분산을 담당하므로 청크의 이동을 하나의 멤버에서만 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로인해 잠금 경합도 없어졌으며,&amp;nbsp;컨피그 서버 자기 자신이 메타데이터를 가지고 있기 때문에 로컬 데이터를 변경하므로 가장 중요한 메타 데이터 변경 단계에서 실패할 가능성을 획기적으로 줄였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 청크 밸런싱 작업에 대해 라우터가 관여하는 부분이 없어졌고 라우터는 오직 사용자 쿼리를 샤드로 전달하는 역할만 처리하므로 언제든지 재시작해도 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전까지는 MongoDB 청크 밸런싱에서 한 시점에 하나만 실행할 수 있어 샤드가 많은 클러스터는 권장되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4 버전부터 청크의 이동이 다중으로 실행될 수 있게 개선되었다. 다만 다중이동이 허용된다 하더라도 청크 이동에 참여하는 샤드 서버가 같다면 동시에 실행될 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유는 청크 이동 자체가 샤드 서버에 많은 부하를 일으키기 때문에 하나의 샤드 서버는 한 시점에 하나만 처리하려고 제약을 둔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개 청크가 이동할 수 있는 밸런서의 작동 방식을 병렬 청크 마이그레이션이라 하며 컨피그 서버의 settings 컬렉션에서 밸런스 mode 가 full 로 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 밸런서의 상태를 확인하는 방법은 isBalancerRunning 과 getBalancerState 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isBalancerRunning 은 현재 밸런스가 청크 이동을 실행하거나 청크이동이 필요한지 모니터링 하고 있는지를 확인하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getBalancerState 는 밸런서 자체가 작동 중인지 확인하는 명령이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setBalancerState 는 플래그값 변경으로 밸런서를 켜고 끄는 명령이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비활성화 시 그 시점부터 청크 밸런스 작업은 시작되지 않으나 이미 시작된 청크 이동 작업이 중간에 멈추지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 startBalancer 와 stopBalancer 는 밸런서를 중지하거나 시작하는데 밸런서가 완전히 멈추거나 시작할때까지 기다린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밸런서의 청크 이동은 상당히 많은 시스템 자원을 사용하기 때문에 특정 시간대에만 실행되도록 제어하는 기능이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 config DB 로 이동 후 setting 컬렉션에서 balancer 와 관련된 도큐먼트의 activeWindow 정보를 UPDATE 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;waitForBalancer 명령을 사용하여 밸런서 작동여부에 따른 후속처리를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 데이터베이스와 컬렉션이 있을 때 특정 컬렉션만 청크 마이그레이션되기를 원할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 특정 컬렉션만 밸런싱을 실행하거나 끄고싶을 땐 enableBalancing 과 disableBalancing 을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;청크 스플릿&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 샤드 간의 데이터 균형을 위해서 관리되는 가장 작은 데이터 조각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 청크는 샤드 간을 이동할 때 샤드 서버에 최소한의 영향을 미치도록 적절한 크기를 유지해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 MongoDB 내부적으로 각 청크의 크기뿐 아니라 각 청크가 저장하고 있는 도큐먼트의 개수까지 균형을 이루도록 고려하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 청크는 자동 또는 수동으로 스플릿될수 있는데, 자동 스플릿은 청크에 도큐먼트가 INSERT 되거나 UPDATE 될때에만 해당 청크를 스플릿할지 결정하고 아래 2가지 기준을 따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컨피그 서버의 settings 컬렉션에서 balancer 에 설정된 청크 사이즈보다 현재 청크의 크기가 클 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 이동의 최대 도큐먼트 건수보다 많은 도큐먼트를 현재 청크가 가지고 있을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본옵션으로 하나의 청크는 64MB 정도의 데이터 조각으로 유지하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 서비스별 특성이나 잘못된 샤드 키 등의 이유로 특정 청크에 데이터가 집중될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 특정 청크가 커지면 커진 청크를 쪼개서 2개 이상의 새로운 청크로 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하나의 청크를 여러 개의 새로운 청크로 쪼개는 작업을 스플릿이라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 스플릿은 splitVector 와 splitChunk 두 단계로 나뉘어 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splitVector 명령은 특정 청크에서 스플릿이 필요한지 확인하고, 만약 스플릿해야 한다면 스플릿을 실행할 위치의 배열을 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splitVector 명령이 실행되는 대략적인 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 컬렉션의 전체 크기와 도큐먼트 개수를 이용해서 도큐먼트의 평균 크기 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 최대 청크 크기의 절반 크기에 저장될 수 있는 도큐먼트의 개수 계산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 청크의 크기가 청크의 최대 크기보다 작으면 splitVector 명령 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 청크에 저장된 샤드 키의 최솟값부터 인덱스를 스캔하면서 인덱스 키의 개수를 카운트한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 샤드 키 스캔이 완료되면 4번에서 기록된 샤드 키 값들을 배열로 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 splitVector 는 하나의 청크를 최대 청크 크기의 절반크기로 나누기 위해서 위치를 찾는과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 컬렉션의 샤드 키 값이 모두 한 값이라면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컬렉션에 있는 도큐먼트 하나의 평균 크기가 1KB 이면 splitVector 명령은 32MB 개의 인덱스 키 단위로 스플릿 포인트를 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 모두 하나의 값이므로 splitVector 에서 이렇게 중복된 값들은 모두 무시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 샤드 키 값을 2개 이상의 청크로 쪼갤 수 없기 때문에 기록할 필요가 없는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 32,768 번째 값이 다음값과 다르다면 이 지점을 스플릿 포인트로 기록하지만, 값이 모두 하나라면 이 청크는 스필릿 할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 스필릿 포인트를 찾지 못했다는 메시지가 MongoDB 라우터나 컨피그 서버의 로그 파일로 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splitVector 명령의 리턴 값이 청크를 스플릿할 수 있는 유효한 포인터를 포함하고 있다면 해당 지점을 기준으로 청크를 스플릿하도록 splitchunk 명령이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 컨피그 서버의 메타데이터 잠금 획득&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 메타 데이터 변경을 위해서 배치 업데이트 명령 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 컨피그 서버의 changelog 컬렉션에 변경 이력 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splitVector 과정에서도 샤드 키 인덱스의 일부만 스캔한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크를 스플릿하기 위해 데이터파일을 모두 스캔하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 청크의 스플릿 과정은 상대적으로 가볍게 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게다가 splitVector 명령은 샤드 키 인덱스를 스캔하는 과정 중이 빈번하게 Yield 를 수행하는데, Yield 는 자기 자신이 가지고 있던 잠금을 모두 반납하고 잠시 실행을 멈추는 과정을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 splitVector 명령이 장시간 실행되고 MongoDB 의 로그 파일에 슬로우 쿼리로 남는다고 하더라도 튜닝 대상으로 고려할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 스플릿은 자동으로 실행되기도 하지만, 관리자가 직접 특정 청크를 스플릿할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔은 샤드 클러스터가 비대해진 청크를 스플릿하지 못할 수도 있다. 그리고 대량의 데이터를 적재하기 전에 미리 청크를 잘게 쪼개서 분산할 수도 있다. 이런경우 관리자가 직접 청크를 split 명령어로 스플릿한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 옵션(find, middle, bounds)에 따라 조금씩 용도와 스필릿 결과가 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find : 주어진 조건을 이용하여 특정 청크를 찾은 다음 그 청크를 크기가 같은 2개의 청크로 스플릿한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 옵션에 주어진 조건을 기준으로 청크를 2개로 나누는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;middle : 주어진 조건을 이용해서 특정 청크를 찾은 다음 그 청크를 2개의 청크로 스플릿한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 와 달리 middle 옵션에 주어진 샤드 키 값을 기준으로 청크를 스플릿한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bounds : 해시 샤딩을 사용하는 컬렉션을 스플릿하기 위한 명령이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bounds 옵션은 샤드 키의 범위를 배열로 명시하면 되는데, 이 배열은 스플릿 대상 청크의 최솟값과 최대값을 사용해야 한다. 또한 bounds 옵션에 사용되는 샤드 키 값은 해시되기 전의 원본값이 아니라 해시값을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그서버의 chunks 컬렉션에서 스플릿하고자 하는 청크의 최소값과 최대값을 이용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 때로 모든 필드를 나열하지 못하거나 첫 번째 이후의 필드는 크게 중요하지 않을 수 있는데 이 때는 MinKey, MaxKey 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 스플릿은 하나의 청크를 풀 스캔해서 스플릿 포인트를 찾아야 하므로 시간이 조금 걸릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 해시 샤딩과 같에 데이터의 분산이 너무 균등할 때는 여러 청크가 동시에 스플릿 시점이 되곤한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 동시에 다수의 청크가 스플릿을 실행하면서 청크스플릿이 서비스 쿼리의 성능을 느리게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 서버에서는 청크 스플릿을 자동으로 실행하지 못하도록 비활성화 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밸런서 -&amp;gt; 비활성 / 활성 ( 청크를 샤드에 맞춰 밸런싱 함 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 스플릿 -&amp;gt; 비활성 / 활성 ( 청크를 나눔 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비활성 / 활성에 따라 샤드별 청크가 어떻게 나뉘는지 테스트해보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 청크가 어느 샤드에 있는지, 청크 크기는 어떤지 볼 수 있는 방법을 체크해보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;청크 머지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 컬렉션을 샤딩하면서 미리 청크를 스플릿해뒀는데, 의도한대로 데이터가 저장되지 않아서 빈 청크 상태이거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 삭제되면서 빈 청크가 되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이렇게 빈 청크는 샤드 서버 간의 부하를 균등하게 유지하기 어렵게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 에서는 이렇게 데이터를 전혀 가지지 않는 청크를 주위의 연속된 청크와 병합할 수 있도록 mergeChunks 를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제약사항은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 병합하고자 하는 청크는 같은 샤드에 존재해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 병합되는 청크 중에서 최소한 하나의 청크는 비어 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 연속된 샤드 키 범위의 청크만 병합할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크가 비어있는지 아닌지는 dateSize 명령을 이용해서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과값에서 size 와 numObjects 의 값이 모두 0 이면 해당 청크는 비어있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mergeChunks 시 기존의 청크 2개를 합쳐서 만드는 새로운 청크의 범위를 명시해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;청크 이동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밸런서는 각 샤드의 청크 개수를 비교해서 특정 샤드의 청크 개수가 다른 샤드에 비해서 많으면 샤드 간 청크 이동을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 간 청크의 불균형을 감지하고 청크 이동을 실행하는 것은 밸런서지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크이동이 시작되면 청크를 가진 샤드와 청크를 전달받을 샤드 서버 간에 데이터 이동과 관련된 모든 작업이 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 데이터 이동 자체에 대해 밸런서가 관여하는 부분은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크의 이동은 여러 단계로 나뉘는데, 청크를 보내는 샤드에서 청크를 받는 샤드에 걸쳐 처리된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;단계&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;처리 샤드&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;단계별 처리 내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;밸런서&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;From Shard 로 moveChunk 명령실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;From Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;이동 대상 청크의 상태와 moveChunk 의 파라미터 오류 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;From Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;To Shard 에 From Shard 로부터 청크를 복사하도록 지시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;To Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;From Shard 와 인덱스를 비교한 다음 필요하다면 인덱스 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;To Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;From Shard 로부터 청크 데이터를 가져와서 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;To Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;5번 단계까지 실행하는 동안 변경된 데이터를 From Shard 로부터 가져와 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;To Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;Steady 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;From Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;컨피그 서버의 청크 메타 데이터 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.8682%;&quot;&gt;9&lt;/td&gt;
&lt;td style=&quot;width: 18.4496%;&quot;&gt;From Shard&lt;/td&gt;
&lt;td style=&quot;width: 68.6821%;&quot;&gt;To Shard 로 이동된 청크의 데이터 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크의 이동 과정을 내부 상태별로 구분하면 다음과 같이 나눌 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;상태&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;CLONE&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;청크 데이터를 벌크로 가져와 복사하는 단계&lt;br /&gt;아무런 잠금이 걸리지 않고, From Shard 에서 데이터 읽기 쓰기가 모두 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;CATCHUP&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;CLONE 단계를 처리하는 중 From Shard 로 유입된 변경 내역을 To Shard 로 복사&lt;br /&gt;아무런 잠금이 걸리지 않고, 읽기와 쓰기 모두 From Shard 에서 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;STEADY&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;CATCHUP 에서 COMMIT_START 로 전이상태&lt;br /&gt;STEADY 상태에서는 CATCHUP 상태처럼 From Shard 의 변경 내역을 계속 To Shard 로 전달받다가&lt;br /&gt;From Shard 로부터 COMMIT_START 메시지가 오면 다음 단계로 상태를 전이하고 마지막으로 한번 더 From Shard 의 남은 변경 데이터를 To Shard 로 가져옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;COMMIT_START&lt;br /&gt;COMMIT&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;메타 정보 업데이트 상태&lt;br /&gt;From Shard 는 실제 변경된 샤드와 청크 정보를 컨피그 서버에 반영하는 작업을 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;DONE&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;청크의 이동이 완료된 상태&lt;br /&gt;From Shard 는 이동된 청크의 데이터를 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;FAIL&lt;br /&gt;ABORT&lt;/td&gt;
&lt;td style=&quot;width: 83.8372%;&quot;&gt;청크 이동 실패 혹은 포기 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크가 원천에서 대상으로 옮겨진 이후에 샤드 서버나 컨피그 서버는 라우터에 청크 이동에 대한 정보를 넘겨주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 라우터는 청크이동을 인지하지 못하고 여전히 해당 청크의 데이터를 조회하거나 변경하기 위해서 원천 샤드로 쿼리를 전달하게 된다. 하지만 그 청크는 이미 이동되었기에 쿼리는 실패한다. 이 때 원천샤드는 StaleConfigException 에러를 반환하는데 라우터는 이 에러를 받게 되면 즉시 컨피그 서버로 접속하여 해당 청크가 어디로 이동했는지 확인하고 대상 샤드로 쿼리를 다시 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 마이그레이션 기록은 config 데이터베이스이 changelog 컬렉션에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 2개의 도큐먼트를 기록하는데, 하나는 청크를 전송하는 쪽의 단계별 처리시간이며 다른 하나는 청크를 전송받는 쪽의 단계별 처리 시간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 청크가 완전히 이동한 건수는 what 필드의 값이 moveChunk.commit 인 도큐먼트의 건수를 확인하면 된다. 실패한건수는 moveChunk.commit 건수와 moveChun.start 건수의 차이를 확인하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;청크 아카이빙&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 2.6 버전부터 자동으로 이동된 청크 데이터를 movedChunk 라는 디렉터리에 컬렉션과 청크의 식별자로 명명된 파일로 백업한다. 이는 청크 이동이 실패했을 때 데이터를 복구할 수 있도록 구현된 일회성 백업 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세컨드리 쓰로틀링과 청크 데이터의 비동기 삭제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동 쓰레드에 의해 변경된 데이터는 복제를 통해서 세컨드리 멤버로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 청크 이동과 관련된 데이터 변경은 모두 OpLog 에 기록되어야 하고, 세컨드리에 상당한 영향을 미치게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 프라이머리에서 청크 이동을 위한 DML 이 너무 빠른 속도로 진행되어 버리면 레플리카 셋의 세컨드리 멤버는 프라이머리 변경을 따라가지 못할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 복제 지연을 막기 위해서 프라이머리와 세컨드리의 동기화 상태를 확인하면서 청크이동을 실행하도록 제한하는 기능을 추가했는데, 이 기능을 세컨드리 쓰로틀링이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능이 활성화되면 청크 이동을 위한 도큐먼트 복사 작업이 프라이머리와 세컨드리 멤버까지 동기화되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 복사를 위해 {w:2} 이상 수준의 WriteConcern 모드를 사용하는데 세컨드리 멤버의 복제 지연을 최소화하는 효과를 낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 쓰로틀링 기능은 전체적으로 청크의 이동 속도를 느리게 만들지만, 세컨드리 동기화를 필요로 하는 사용자 쿼리의 응답 시간을 최대한 평상시와 비슷한 수준으로 할 수 있도록 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.4 버전부터는 WiredTiger 스토리지 엔진을 사용하는 경우에는 세컨드리 쓰로틀링이 비활성화되어있고, MMAPv1 스토리지 엔진은 활성화되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 수동으로 관리자가 직접 청크이동을 실행할 때도 moveChunk 명령과 함께 _secondaryThrottle 옵션으로 직접 쓰로틀링 여부를 명시할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 명시된 옵션은 컨피그 서버 settings 컬렉션 balancer 도큐먼트에 설정된 쓰로틀링 옵션을 무시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;청크 마이그레이션 큐잉&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동을 처리할 때 청크 이동의 여러 가지 절차 중 마지막 단계인 데이터 삭제 단계를 미뤄서 처리할 수 있는 옵션을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동은 기본적으로 청크를 보내는 샤드에서 데이터를 삭제하는 작업을 모두 완료해야 다음 청크 이동을 시작할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 청크마이그레이션 큐잉 기능은 청크가 일단 다른 청크로 모두 옮겨지면 불필요한 데이터를 즉시 삭제하지 않고 큐에 담아둔다음 천천히 삭제하는 작업을 수행하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 샤드 서버는 삭제와 관련된 정보를 큐에 담는 즉시 다른 청크 이동을 시작할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동에서 옮겨진 청크의 데이터를 삭제하는 작업을 지연해서 처리하는 방법은 컨피그서버 settings 컬렉션에서 변경하거나 관리자가 직접 moveChunk 명령을 실행하면서 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크의 데이터를 삭제하는 작업이 너무 많이 지연되어 큐잉된 삭제 작업이 너무 많이 남아있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 큐잉된 삭제 작업을 완료하지 못한 상태에서 레플리카 셋의 프라이머리가 응답 불능이 되거나 비정상 종료를 하게되면 큐에 남은 정보도 같이 이러버리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 샤드 서버는 자기 자신이 관리하는 청크가 아님에도 실제 데이터가 자기 자신에 남게 되는 현상이 발생할 수 있는데 이런 도큐먼트를 고아 도큐먼트라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 삭제 작업을 뒤로 미루는 기능을 매뉴얼에서는 비동기 데이터 삭제라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;청크 이동 실패&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 청크를 다른 샤드로 옮길 수 있는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동이 실패하는 이유는 다양한데, 청크 자체적 원인이 아닌경우 재처리로 가능하나 아래의 경우는 청크의 자체적인 원인으로 청크 이동이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미 다른 청크 이동이 실행되고 있는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크의 점보 청크 플래그가 활성화된 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크가 너무 많은 도큐먼트를 가진 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트가 많아도 실패하는데, 청크가 다른 샤드로 이동하려면 도큐먼트 제약조건을 만족해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크의 도큐먼트가 25만건 이하&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크의 도큐먼트가 (기본 청크 사이즈 / 평균 도큐먼트 크기) * 1.3 건 이하&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 기본 청크 사이즈는 컨피그 서버 settings 컬렉션의 balancer 항목에 설정된 청크 크기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 도큐먼트 크기는 db.collections.stat 명령에서 조회한 avgOvSize 필드 값을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건을 만족하지 못하는 청크를 이동하려고 하면 Chunk too big to move 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;청크 사이즈 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 사이즈의 기본값은 64MB 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 크기의 도큐먼트 사이즈에서는 적절한 기본값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 사이즈는 샤드 클러스터에서 부하 분산의 가장 기본이 되는 옵션이며, 3가지를 결정하는 중요한 요소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 유연한 청크 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드 간 청크 이동 시 발생하는 부하 조절&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드 간 부하 분산의 정도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크의 크기는 하나의 청크가 가지는 도큐먼트의 개수를 결정하게 되는데, 도큐먼트 개수는 샤드 간 청크 이동이 가능한지 불가능한지 판단하는 요소로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 하나의 청크는 여러 샤드에 걸칠 수 없으므로 한번 이동이 시작된 청크는 서버의 부하에 상관없이 완료 혹은 취소되어야 한다. 그래서 청크사이즈가 크면 서버에 미치는 영향이 더 커지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크의 크기가 크면 클수록 클러스터에서 청크의 개수는 적어지지만 샤드 서버간 부하가 균등하지 않을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 청크 사이즈는 1MB ~ 1024 MB 까지 설정할 수 있는데, config 데이터베이스의 settings 컬렉션을 변경함으로써 조정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 사이즈를 변경할 때 자동 청크 스플릿은 다음과 같이 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해당 청크에 INSERT / UPDATE 가 실행될 때만 자동 청크 스플릿이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 사이즈를 줄이는 경우 즉시 청크가 스플릿 되는것이 아니라 INSERT / UPDATE 가 실행될 때 새로운 청크 사이즈를 기준으로 스플릿된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 사이즈를 늘리는 경우 청크는 새롭게 설정된 청크 사이즈만큼 커질 때까지 스플릿되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 늘렸어, 물리적으로 청크 옆에 다른 청크가 바로 있어. 사이즈가 커질때만큼 스플릿이 안되면 인접하지 않는 서로다른 블럭에 저장되나 ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;점보 청크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 샤드 키 값으로만 구성된 청크의 크기가 설정된 청크 크기보다 크거나 혹은 작더라도 도뮤먼트 건수가 많은 경우, MongoDB 는 그 청크의 점보 청크 플래그를 활성화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 점보 플래그가 활성화되면 그 청크는 더는 스플릿할 수 없을뿐 아니라 밸런서가 그 점보 청크를 다른 샤드로 옮기지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점보 청크는 sh.status 나 config 데이터베이스의 chunks 컬렉션을 직접 쿼리해서 확인할 수 있으며, status 명령은 샤드 클러스터의 전체적인 정보와 모든 컬렉션의 샤딩과 관련된 정보를 보여주므로 많은 정보를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 점보 필드가 true 인 청크만 찾아서 확인할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 점보 플래그가 활성화된 청크는 적절한 크기로 스플릿해줘야 향후 청크가 더 커졌을 때 자동으로 스플릿될 수 있고, 밸런서가 필요할 때 청크를 다른 샤드로 이동할 수 있게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스플릿이 가능한 점보청크&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크가 적절한 시점에 스플릿되지 못하거나 청크가 가진 도큐먼트 건수가 많으면 점보 청크가 되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sh.status(true) 나 config 데이터베이스의 chunks 컬렉션에서 점보 청크를 검색한 다음 sh.splitAt() 이나 sh.splitFind() 등의 청크 스플릿 명령으로 작은 청크들을 분리만 시켜두면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 스플릿이 성공하면 MongoDB 는 자동으로 해당 청크의 점보 플래그를 제거하고 정상적인 청크로 취급한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스플릿이 불가능한 점보청크&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 샤드 키는 두 개 이상의 청크에 포함될 수 없다. 그래서 샤드 키 값이 동일한 도큐먼트들이 많이 저장되면 MongoDB 는 해당 청크를 스플릿하려고 하지만 결국 실패하고 점보 청크가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 스플릿이 불가능한 점보 청크가 많으면 컬렉션의 샤드 키를 잘 못 선택할 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점보 플래그가 활성화된 청크는 다른 샤드로 이동할 수 없기 때문에, 분산을 위해서는 컨피그 서버가 가진 메타 정보를 강제로 변경해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 스플릿되지 못하는 점보 청크의 근본적인 해결책은 샤드 키를 변경하는 것인데, 컬렉션의 샤드 키를 변경하는 명령은 제공하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키를 변경하려면 서비스를 멈추고 컬렉션의 데이터를 모두 덤프한 다음 새로운 샤드 키로 생성된 컬렉션에 다시 적재해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 5.0 부터 가능 : &lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/sharding-change-a-shard-key/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mongodb.com/ko-kr/docs/manual/core/sharding-change-a-shard-key/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721632991030&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;샤드 키 변경 - MongoDB 매뉴얼 v7.0&quot; data-og-description=&quot;이상적인 샤드 키를 사용하면 MongoDB가 클러스터 전체에 문서를 고르게 분산하는 동시에 일반적인 쿼리 패턴을 용이하게 할 수 있습니다. 최적이 아닌 샤드 키를 사용하면 데이터 분포가 고르지 &quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/sharding-change-a-shard-key/&quot; data-og-url=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/sharding-change-a-shard-key/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LXlF6/hyWCNiyJew/pAUQhIxK7SttH0yBwfKRU0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/SsIbP/hyWCLrvzl1/9H5YpKFFFKvP7X3rlvOSk0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/sharding-change-a-shard-key/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/ko-kr/docs/manual/core/sharding-change-a-shard-key/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LXlF6/hyWCNiyJew/pAUQhIxK7SttH0yBwfKRU0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/SsIbP/hyWCLrvzl1/9H5YpKFFFKvP7X3rlvOSk0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;샤드 키 변경 - MongoDB 매뉴얼 v7.0&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이상적인 샤드 키를 사용하면 MongoDB가 클러스터 전체에 문서를 고르게 분산하는 동시에 일반적인 쿼리 패턴을 용이하게 할 수 있습니다. 최적이 아닌 샤드 키를 사용하면 데이터 분포가 고르지&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고아 도큐먼트 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동이 실패하면 많은 도큐먼트가 고아 상태로 남게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고아 데이터가 많아지면 디스크의 데이터량이 커지고 시스템 자원도 비효율적이며 샤드에 직접 접근하는경우 결과에 오류를 발생할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 cleanupOrphaned 명령으로 고아 도큐먼트만 삭제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 주어진 startingFromKey 필드의 값부터 샤드 키를 스캔하면서 청크의 범위에 소속되지 않는 도큐먼트를 찾아서 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;샤딩으로 인한 제약&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 트랜잭션 속성으로 ACID 를 주로 언급한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 트랜잭션을 지원하지 않는다고 언급하는 부분은 여러 개의 INSERT / UPDATE / DELETE 문장으로 구성된 트랜잭션을 지원하지 않는다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 도큐먼트에 대한 변경은 모두 트랜잭션을 지원하지만 롤백할 수 있는 방법은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 단일 도큐먼트는 원자성을 가지고 처리되지만 여러 도큐먼트를 변경하는 작업은 원자성을 가지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 트랜잭션을 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/v5.0/core/transactions/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mongodb.com/ko-kr/docs/v5.0/core/transactions/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721640430915&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;트랜잭션 - MongoDB 매뉴얼 v5.0&quot; data-og-description=&quot;MongoDB에서 단일 문서에 대한 작업은 원자적으로 이루어집니다. 임베디드 문서와 배열을 사용하면 여러 문서와 컬렉션에 걸쳐 정규화하는 대신 단일 문서 구조에서 데이터 간의 관계를 캡처할 &quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/ko-kr/docs/v5.0/core/transactions/&quot; data-og-url=&quot;https://www.mongodb.com/ko-kr/docs/v5.0/core/transactions/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/h4UDQ/hyWCAXPv2D/3og0J1MCUzS4ShJ9kL3AnK/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/cMkXAu/hyWCGRhzLM/ga9jwKE0M9QvvVRZPn3NM0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/ko-kr/docs/v5.0/core/transactions/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/ko-kr/docs/v5.0/core/transactions/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/h4UDQ/hyWCAXPv2D/3og0J1MCUzS4ShJ9kL3AnK/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/cMkXAu/hyWCGRhzLM/ga9jwKE0M9QvvVRZPn3NM0/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션 - MongoDB 매뉴얼 v5.0&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB에서 단일 문서에 대한 작업은 원자적으로 이루어집니다. 임베디드 문서와 배열을 사용하면 여러 문서와 컬렉션에 걸쳐 정규화하는 대신 단일 문서 구조에서 데이터 간의 관계를 캡처할&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤딩과 유니크 인덱스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 샤딩되면 샤딩된 데이터 간의 유니크 인덱스 생성은 제약이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩된 컬렉션에서 유니크 인덱스는 샤드 키를 포함하는 인덱스에 대해서만 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 키는 샤딩과 무관하게 항상 유니크해야 하며, 세컨드리 키 중에서도 유니크 옵션이 설정된 경우 중복허용하지 않도록 처리되어야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프라이머리 키의 중복체크 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 키 필드에 사용자가 직접 값을 설정하는 경우 프라이머리 키의 유니크함을 사용자가 직접 보장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_id 필드의 값을 설정하지 않으면 자동으로 만들어서 저장한다. 다만 _id 필드를 사용자가 직접 선택한 값을 저장할 수 있는데 이 때는 사용자가 직접 중복되지 않는다는걸 보장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 중복체크 기능은 샤드 단위로만 체크하고 전체 샤드에 대해서 체크를 수행하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세컨드리 키의 중복체크 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키 기준의 필드가 선행이 되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키 기준의 필드가 선행인 복합 인덱스만 유니크 옵션을 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 해시 인덱스는 유니크 옵션을 설정할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해시 샤딩을 적용한경우에는 별도로 인덱스를 생성해서 유니크 옵션을 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조인과 그래프 쿼리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 컬렉션의 데이터를 조인해서 쿼리할 수 있도록 $lookup 오퍼레이션을 제공하고 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층형 재귀 쿼리나 그래프 데이터를 쿼리할 수 있도록 $graphLookup 오퍼레이션을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 모두 from 인자에 주어지는 컬렉션은 샤딩된 컬렉션을 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 라우터가 아니라 샤드 서버에서 실행되는데, 샤드 서버는 데이터 처리를 위해 다른 샤드의 데이터를 참조할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 컬렉션에 샤딩 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩되지 않은 상태로 데이터를 가지고 있는 컬렉션을 샤딩할 때에는 샤딩을 적용할 수 있는 컬렉션의 사이즈에 제한이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩되지 않은 컬렉션에 대해 샤딩을 적용하면 우선 그 컬렉션에 대해 청크 스플릿을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 청크 스플릿을 위해 splitVector 명령을 실행하는데 이 때 실행되는 명령이 반환할 수 있는 결과는 하나의 BSON 도큐먼트로 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령의 결과가 BSON 도큐먼트이기 때문에 이 결과는 16MB 를 초과할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적으로 초기 샤딩을 적용할 수 있는 최대 컬렉션의 크기를 계산하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 샤딩 가능한 컬렉션 크기 = ( 16MB / 샤드 키 길이 / 2 ) * 청크 사이즈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이보다 큰 컬렉션에 대해 샤딩을 적용해야 한다면 청크 사이즈를 일시적으로 늘린 후 샤딩하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩이 완료되면 다시 청크 사이즈를 원래대로 되돌려서 설정한다.&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/640</guid>
      <comments>https://mozi.tistory.com/640#entry640comment</comments>
      <pubDate>Mon, 22 Jul 2024 11:05:07 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 샤딩(2)</title>
      <link>https://mozi.tistory.com/639</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pWckt/btsIoYF6Epk/F9cGq0xY7yHd14N7cs1gjK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pWckt/btsIoYF6Epk/F9cGq0xY7yHd14N7cs1gjK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pWckt/btsIoYF6Epk/F9cGq0xY7yHd14N7cs1gjK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpWckt%2FbtsIoYF6Epk%2FF9cGq0xY7yHd14N7cs1gjK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;샤딩 알고리즘&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;청크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 컬렉션은 샤드 키를 기준으로 잘게 쪼개져 여러 샤드에 분산되어 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 쪼개진 컬렉션의 조각(파티션)들을 청크라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 샤드 키의 원본 값 또는 해시 값의 일정 범위를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키에서 가장 작은 값은 MinKey, 가장 큰 값은 MaxKey 로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트의 필드가 가질수 있는 최솟값과 최댓값을 지칭하는 가상의 값을 의미하는 예약어인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 청크는 MinKey 와 MaxKey 사이의 각 영역을 담당하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 어떠한 물리적인 의미를 가지지 않으며, 논리적으로만 존재하는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 청크 단위로 데이터파일이 생성되거나 데이터가 모여있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 청크에 관계없이 하나의 컬렉션에 속한 데이터는 하나의 데이터 파일에 섞여서 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 컬렉션의 각 인덱스는 개별 파일에 저장되기는 하지만, 청크 단위로 다른 파일로 분리되어서 저장되지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 각 청크가 개별 데이터 파일로 저장된다면 데이터 파일의 개수가 매우 많아질 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로 인해 데이터를 검색할 때 수많은 파일을 다 검색해야만 데이터를 찾을 수 있게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 이렇게 물리적인 파일로 청크가 저장된다면 세컨드리 인덱스에 대한 지원을 하지 못할것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 컨피그 서버의 메타 데이터로만 존재하고, 실제 샤드 서버는 청크라는 개념에 대해 알 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 논리적으로만 존재하는 개념이기 때문에 샤드 간 청크 밸런싱 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불균형이 발생하면 청크가 다른 샤드로 옮겨지게 되고, 이로인해 샤드 서버에서는 INSERT / DELETE 가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 청크는 컬렉션의 도큐먼트 자체를 파티션하는 개념이며 세컨드리 인덱스까지 파티션하는 개념은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 세컨드리 인덱스는 도큐먼트가 다른 샤드로 옮겨지면 그 도큐먼트를 가리키는 인덱스 엔트리도 같이 다른 샤드로 옮겨진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 샤딩과 세컨드리 인덱스가 무관하다는 말은 세컨드리 인덱스 자체도 샤딩을 하거나 청크를 나누는 것은 불가능하며 적용되지 않음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 A 샤드 서버에 있는 세컨드리 인덱스 엔트리가 B 샤드 서버에 있는 도큐먼트를 가리킬 수는 없으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 세컨드리 인덱스 항목은 컬렉션의 도큐먼트에 종속되어 샤드 서버에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 샤드 클러스터에서 세컨드리 인덱스는 항상 로컬 인덱스로 간주된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 기본값으로 64MB 까지 커질 수 있으며, 그 이상으로 커지면 밸런서에 의해 자동으로 스플릿된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크가 빈번하게 스플릿되면 각 샤드 서버 간의 청크 개수가 불균형가게 될 가능성이 높고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;균형이 맞지 않으면 밸런서가 청크를 이동시키면서 계속 규형된 상태를 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 이동 자체가 매우 고비용이기 때문에 청크 이동 발생 시 서버의 부하를 장시간 유지하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 청크가 크면 클수록 샤드 간 부하를 조절하기가 어려우며, 청크가 작으면 작을수록 청크이동이 빈번하게 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레인지 샤딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 청크의 범위를 표시할 때는 대괄호와 소괄호를 구분해서 사용하는데, 이는 경계에 있는 값을 포함하는지 포함하지 않는지 표시하기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대괄호 [ ] 는 경계값이 현재 범위에 포함되는 것을 의미하며 소괄호 ( ) 는 포함하지 않는것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키 값은 MinKey 보다 작을 수 없으며 MaxKey 보다 클 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MinKey 와 MaxKey 는 범위를 표기하기 위해 만든 의사 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 MinKey 는 포함할 수 있지만 MaxKey 는 포함할 수 없다. 다만 가상의 최솟값과 최댓값이므로 포함/불포함의 여부는 의미를 가지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레인지 샤딩 예제에서 가장 중요한 부분은 샤드 키 값이 별도의 변형 과정을 거치지 않고 그 자체로 정렬돼서 각 청크의 범위가 결정된다. 이런 특성으로 인해 장단점이 결정되는데, 가장 큰 장점은 범위검색 쿼리를 타겟 쿼리로 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제점은 각 샤드에 데이터가 균형 있게 분산되지 않을 가능성이 높다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 특정문자로만 데이터가 들어오고 이 문자가 청크1번으로만 레인지가 잡혀있다면 특정 청크만 커지고 다른 청크는 아주 적은 데이터를 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레인지 샤딩은 샤드 키 값이 아주 균등하게 분산돼 있지 않는 이상 언젠가는 청크나 샤드 간 데이터의 불균형이 발생할 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 데이터 분산의 불균형이 발생하면 MongoDB 는 청크를 스플릿하고, 그럼으로써 각 샤드 서버 간의 청크 개수가 불균형 상태가 된다. 불균형이 되면 밸런서는 청크를 다른 샤드 서버로 옮기는 작업을 계속 수행하는데 이로인해 샤드 서버의 자원의 사용률을 높이게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크의 분산 상태는 MongoDB 라우터로 접속하여 db.collection.getShardDistribution() 명령으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레인지 샤딩은 가능하다면 해시 샤딩을 사용하고 해시 샤딩을 사용할 수 없을 때 레인지 샤딩을 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해시 샤딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키 값을 그대로 청크 할당에 사용하는 것이 아닌, 샤드 키 값의 해시값을 이용하여 청크를 할당하는 샤딩 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 해시함수는 결과값이 전반적으로 골구루 분산될 수 있는 암호화 해시 함수를 주로 사용하는데 MongoDB 는 MD5 해시 함수를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MD5 는 보안상 역해시가 가능하나 샤드 키는 비밀번호 목적이 아닌 데이터의 분산이 주목적이기 때문에 특별히 문제되지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 샤딩은 샤드 키 값의 해시값을 계산한 다음 해시값의 앞쪽 64bit 만 잘라서 64bit 정수형으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해시 샤드 키가 가질 수 있는 값의 범위는 -2^63 에서 2^63-1 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한것은 해시 샤딩도 결국 레인지 샤딩의 일종이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 샤딩의 청크도 64bit 정수의 연속된 값을 담당하고 있으므로 레인지 샤딩과 동일하게 볼 수 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MD5 가 적용된 해시값은 지정된 범위 내에서 매우 균등하게 분포된 값을 반환하기 때문에 샤드 키 값이 비슷한 값을 가진다 하더라도 해시함수의 결과값은 매우다른 값을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 해시 샤딩은 레인지 샤딩대비 아래 장점을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드 키 값이 특정 범위에 집중돼 있을 때 발생하는 데이터 불균형&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 연속된 샤드 키 액세스로 인한 특정 샤드 서버의 부하 편중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 해시 샤딩이라 하더라도 샤드 키의 원본 값이 같은 경우에는 해시 결과값도 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 키 값의 다양성이 떨어지면 아무리 해시 샤딩을 적용해도 특정 청크로 몰릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 해시 샤딩 사용은 제약사항이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 범위 검색 쿼리는 브로드캐스트 쿼리로 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드 키 필드에 대해서 해시 인덱스를 생성해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브로드캐스트로 실행되는 이유는 해원본 값이 아니다보니 범위 검색을 할 때 특정 청크를 한정할 수 없어서 타겟 쿼리로 실행하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 제약사항은 해시 샤딩을 하고자 하는 경우에는 샤드 키에 대해서 해시 인덱스의 제약 사항이 동일하게 적용된다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 단일 필드에 대해서만 해시 인덱스를 생성할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 멀티 키 필드에 대해서는 해시 인덱스 생성 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 부동 소수점 필드는 소수점 이하를 버리고 해시 함수 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2^53 보다 큰 부동 소수점에 대해서는 해시 인덱스를 지원하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db.users.insert({&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name : &quot;matt&quot;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;country : &quot;korea&quot;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;composite_field: {name: &quot;matt&quot;, country: &quot;korea&quot;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;});&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 필드에 대해서만 해시 인덱스를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 필드를 합쳐서 해시 인덱스를 생성하는 것을 불가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(x) db.users.createIndex({name:&quot;hashed&quot;, country:&quot;hashed&quot;});&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(o) db.users.createIndex({compsite_field : &quot;hashed&quot;});&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회 시 해시 인덱스를 사용할 수 있고 타겟 쿼리로 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(o) db.users.find({key:{ &quot;name&quot; : &quot;matt&quot;, &quot;country&quot; : &quot; korea&quot; }});&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 쿼리와 세 번째 쿼리는 해시 인덱스를 사용하지 못하며 타겟 쿼리로도 작동하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(x) db.users.find({&quot;key.name&quot; : &quot;matt&quot;, &quot;key.country&quot; : &quot; korea&quot; }});&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(x) db.users.find({key:{ &quot;name&quot; : &quot;matt&quot; }});&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스의 두 번째 제약사항은 멀티 키 필드에 대해서 해시 인덱스를 생성하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라 서 여러 개의 값을 가지는 배열필드에 대한 멀티 키 인덱스는 지원할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 인덱스를 가진 필드에 배열 값을 저장하려고 하면 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수는 부동 소수점의 소수점 이하 부분은 버리고 해시 함수를 적용하는데, 이로 인해 2.1 과 2.2 와 같은 정수부가 같은 경우에는 동일한 해시 결과값을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 부동 소수점을 최대한 정수화한 다음에 (배수곱으로 소수를 정수화) 샤딩이나 인덱스를 생성하는 것이 좋으나 번거롭다면 3.4 버전부터 지원되는 Decimal 타입을 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 샤딩에서 가끔 문제 되는 부분은 로그 파일에 출력되는 메시지의 내용으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 청크가 너무 커지면 다음과 같은 경고 메시지가 서버 에러 로그에 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 메시지에서 해시 함수의 결과값이 나오기 때문에 어떤 키 값인지 찾을 방법이 없어서 필드 값을 집계쿼리를 샐행해서 같은 user_name 필드 값을 가진 도큐먼트의 개수를 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 각 청크의 실제 사이즈를 확인해 볼수도 있지만 상당한 시간과 서버 자원이 소모된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 샤딩에서 해시된 결과값은 지정된 범위 내의 값이라는 것을 MongoDB 가 이미 알고 있기 때문에 청크를 미리 스플릿해둘 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;numInitialChunks 에는 미리 스플릿해 둘 청크의 개수를 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 하나의 샤드 서버에서 생성하고 청크를 이동하려고 할 때 실패한다면 하나의 샤드에만 남아있을 수 있기 때문에 반드시 shardCollection 명령을 실행한 다음 여러 샤드 서버에 골구루 분산되었는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수는 균등분포이기 때문에 스플릿 되는 시점이 한번에 몰릴 수 있는 단점도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 가능성이 높진 않지만 한 번에 몰리는걸 방지하기 위해 수동으로 사용량이 낮은 시점에 청크를 조금씩 미리 스플릿해두는 것도 안정된 서비스를 운영하는데 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* mongodb 모니터링 시, 청크 별 스플릿 시점 (MB 사이즈 체크)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지역 기반 샤딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 기반 샤딩은 레인지 샤딩이나 해시 샤딩처럼 독립적으로 사용할 수 있는 샤딩 방식이 아니라 레인지 샤딩이나 해시 샤딩과 반드시 함께 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 레인지 샤딩이나 해시 샤딩은 샤딩 알고리즘이 모든 데이터를 커버할 수 있어야 하지만 지역 기반 샤딩은 선택적으로 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 지역 기반 샤딩은 관심 대상의 데이터에만 샤딩 알고리즘을 적용할 수 있다. 그래서 더욱 지역 기반 샤딩은 단독으로 사용할 수 없는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 기반 샤딩은 레인지 샤딩이나 해시 샤딩을 적용한 상태에서 데이터를 저장할 샤드를 한번 더 조정할 수 있는 옵션이라고 이해해도 된다. 지약기반 샤딩은 국가나 지역 기반으로 데이터의 저장소를 분리하기 위함이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림 4-31 은 사용자 아이디 값을 이용해서 레인지 샤딩 알고리즘을 사용하는 클러스터의 청크 분산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서는 사용자의 아이디가 1부터 600까지만 가질 수 있고, 각 청크의 범위를 100씩 스플릿 해 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스는 대부분 사용자가 한국에 있고 나머지 일부 사용자가 미국에 있다고 가정하여 5개 청크 중 3개는 한국에 배치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 기반 샤딩에서는 단순히 샤드 키의 범위로만 청크를 식별하는 것이 아니다, 샤드 키가 어느 지역에 속할지 결정하는 지역 범위도 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;300보다 작은 user_id 는 KR 이라는 태그가 붙은 샤드에 저장하도록 했고, 300보다 크거나 같은 아이디는 US 라는 태그가 붙은 샤드에 저장하도록 지역&amp;nbsp; 범위를 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 기반 샤딩을 사용하려면 레인지나 해시 샤딩을 한 상태에서 추가로 두 가지 준비가 더 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드별로 태그를 할당하고, 샤드 키 범위별로 태그를 할당하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드에 태그를 할당하는 것은 addShardTag 명령을 이용하며, 범위별로 태그를 할당하는 것은 addTagRange 명령을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 샤드와 사용자 데이터는 태그를 연결 고리로 서로 매핑되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 샤드 키의 범위가 반드시 연속되어야 하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 지역 범위가 반드시 MinKey 부터 MaxKey 까지 모든 영역을 커버해야 하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 1개 이상의 태그와 다중으로 매핑될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 기반 샤딩의 주요 사용 목적은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지역 기반으로 사용자 데이터 구분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 사용자 데이터를 지정된 샤드 서버로 구분해서 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드 서버의 클래스(샤드 서버의 처리 능력이나 저장공간)뼐로 저장할 데이터를 구분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 한다면 샤드에 연결된 태그 확인, 지역 범위 확인이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤드 키&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 샤딩에서 가장 중요한 것은 데이터를 분산하는 기준인 샤드 키와 데이터를 어떤 방식으로 분산할 것인지 결정하는 샤딩 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키가 중요한 이유는 사용자 데이터를 여러 서버에 분산하는 방식을 결정하는 요소이기도 하지만, 더 중요한 이유는 한번 설정한 샤드 키는 컬렉션을 완전히 새로 생성하지 않는 이상 변경할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키는 컬렉션 단위로 설정되며, 샤딩을 하지 않는 컬렉션에 대해서는 샤드 키가 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키는 두가지 특성을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드 키가 설정된 컬렉션에 대해 새로운 샤드 키를 설정하는 것은 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컬렉션의 샤드 키가 되는 필드의 값은 NULL 이며 변경할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드키가 MongoDB 클러스터에 미치는 영향은 매우 광범위하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타겟 쿼리와 브로드 캐스트 쿼리 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소량의 데이터를 읽는 쿼리가 아주 빈번하게 유입되는 서비스에서는 가능하면 타겟 쿼리를 유도할 수 있도로 샤드키를 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 샤드 서버의 부하 분산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 샤드 서버는 균등하게 데이터를 분산해서 가지게 되지만, 균등한지 않은지는 각 샤드 서버가 가진 청크의 개수로만 판단.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 청크에 데이터가 몰리면 부하가 분산되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 밸런스 작업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밸런서는 각 샤드가 가진 청크의 개수를 비교해서 청크의 개수를 균등하게 유지하기 위해서 샤드 간 청크 이동을 실행한다. 그런데 샤드 간 청크 이동은 부하가 높은 작업이어서 사용자 쿼리의 성능에 악영향을 미친다. 또한 이런 형태의 샤딩에서 항상 INSERT 는 마지막 청크를 가진 샤드 서버에서만 실행되므로 하나으 ㅣ청크가 핫 존이되고 INSERT 성능은 아무리 많은 샤드를 추가한다 하더라도 빨라지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프라이머리 샤드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 클러스터의 모든 데이터베이스는 프라이머리 샤드를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 샤드는 샤드 클러스터에서 샤딩되지 않은 컬렉션들을 저장하는 샤드를 의미하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카셋의 프라이머리와는 전혀 무관한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 데이터베이스의 프라이머리샤드는 다음과 같이 config 데이터베이스의 databases 컬렉션을 조회하면 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 primary 가 프라이머리 샤드 정보를 저장하는 필드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 클러스터에서 처음 데이터베이스가 생성되면 MongoDB 는 각 샤드 중에서 데이터를 가장 적게 가진 샤드를 선택해서 생성되는 데이터베이스의 프라이머리 샤드로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 샤드클러스터라고 해서 모든 컬렉션이 샤딩되어야 하는건 아니지만, 이렇게 샤딩되지 않은 컬렉션을 어느 샤드에 저장할지 결정해야 하는데, 이 때 기준이 되는 정보가 각 데이터베이스의 프라이머리 샤드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 샤드는 다른 샤드로 옮겨질 수 있는데 이렇게 옮기는 작업은 특정 샤드를 제거하고자 할 때 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;removeShard 명령으로 해당 샤드의 샤딩된 컬렉션 데이터를 다른 샤드로 이동시키고 최종적으로 서비스에서 샤드를 제거할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제거하고자 하는 샤드가 샤딩되지 않은 컬렉션을 가지고 있다면 먼저 프라이머리 샤드를 다른 샤드로 옮겨야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 이용하는 명령이 movePrimary 명령이고 MongoDB 라우터에서만 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;movePrimary 명령으로 옮겨지는 컬렉션에 대해서는 데이터의 일관성 보장을 MongoDB 가 책임지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 movePrimary 중 DML 이 실행될 때 중간에 변경된 데이터를 새로운 프라이머리 샤드로 적용해주지 않는다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 샤딩되지 않은 컬렉션이 새로운 프라이머리 샤드로 옮겨지고난 이후 해당 컬렉션의 인덱스 생성이 실행되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 인덱스 생성은 포그라운드 모드로 생성되므로 인덱스가 생성되는 시간 동안은 해당 컬렉션과 그 컬렉션이 가진 데이터베이스에 대해서 읽기와 데이터 변경을 수행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 샤드의 이동에서 또 하나 주의해야 할 점은 연결된 모든 MongoDB 라우터에게 특정 데이터베이스의 프라이머리 샤드가 변경됐다는 것을 알려줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 기존의 MongoDB 라우터는 샤딩되지 않은 컬렉션의 데이터를 읽고 변경하기 위해 기존의 프라이머리 샤드로 계속 접근하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 샤드가 변경된 것을 MongoDB 라우터가 새로 갱신하도록 하는 방법은 2가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 MongoDB 라우터 재시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 MongoDB 라우터에서 flushRouterConfig 명령 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flushRouterConfig 는 프라이머리 샤드 정보만 갱신하는 것이 아니라 샤딩된 모든 컬렉션의 메타 정보를 모두 갱신한다. 그래서 동시에 많은 라우터에서 실행할 땐느 컨&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>레인지샤딩</category>
      <category>샤딩</category>
      <category>해시샤딩</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/639</guid>
      <comments>https://mozi.tistory.com/639#entry639comment</comments>
      <pubDate>Fri, 5 Jul 2024 18:55:26 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 샤딩</title>
      <link>https://mozi.tistory.com/638</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH2blh/btsIfVieTIR/BpgYLBXgYAUbc9yzTtXdkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH2blh/btsIfVieTIR/BpgYLBXgYAUbc9yzTtXdkk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH2blh/btsIfVieTIR/BpgYLBXgYAUbc9yzTtXdkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH2blh%2FbtsIfVieTIR%2FBpgYLBXgYAUbc9yzTtXdkk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;샤딩이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 여러 서버에 분산해서 저장하고 처리할 수 있도록 하는 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 샤딩을 적용하려면 샤드 클러스터를 구축해야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 파티션된 데이터의 범위와 샤드 위치 정보 등의 메타 정보를 저장하기 위한 컨피그 서버가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용 프로그램이 필요한 데이터를 조회하거나 저장하려면 mongos 라고 불리는 라우터서버가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 쿼리 수행에 있어 프록시 역할을 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버의 메타 정보를 참조하여 필요한 데이터가 저장된 샤드로 쿼리를 전달하고 그 결과를 다시 응용 프로그램으로 반환하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤딩의 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷이 발달하고 인터넷이 가능한 단말기가 기하급수적으로 늘어나면서 서버에서는 더욱 많은 요청을 처리해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 특별한 설정이 필요한 상태를 가지지 않기 때문에 손쉽게 서버를 추가하거나 삭제가능하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 서버는 모든 데이터를 영구적으로 가지고 있어야 하므로 단순히 서버만 투입한다고 해서 확장되지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 서버를 확장하려면 데이터베이스의 데이터가 여러 서버로 분산될 수 있게 미리 응용 프로그램을 설계하고 개발해야 하는데, 이를 샤딩이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 처리량을 더 받기 위해 서버의 성능을 높이는 방법(스케일 업)도 있으나 이 방법은 한계에 상당이 빨리 이르게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩을 위해서는 응용 프로그램의 코드를 상당히 많이 변경해야 하므로 서비스가 한번 시작되면 중단하지 않고 샤딩방식으로 변경하기는 매우 어렵다. 즉 처음에 설계 시 샤딩을 고려해서 개발을 진행할지를 거쳐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤딩의 종류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩은 어떤 형태로 파티션할지에 따라서 수평샤딩 또는 수직샤딩으로 구분할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.6511%;&quot;&gt;샤딩 종류&lt;/td&gt;
&lt;td style=&quot;width: 85.3489%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.6511%;&quot;&gt;수직 샤딩&lt;/td&gt;
&lt;td style=&quot;width: 85.3489%;&quot;&gt;기능별로 컬렉션을 그룹핑하여 그룹별로 샤드를 할당하는 방식을 의미한다.&lt;br /&gt;샤드 간 부하의 불균형이 자주 발생한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.6511%;&quot;&gt;수평 사딩&lt;/td&gt;
&lt;td style=&quot;width: 85.3489%;&quot;&gt;하나의 컬렉션에 저장된 도큐먼트들을 영역별로 파티셔닝해서 1/N 씩 각 샤드가 나눠 가진다.&lt;br /&gt;파티셔닝의 기준이 되는 필드 선정이 매우 중요하며, 이 파티션 키에 따라서 각 샤드의 부하가 균등해질수도 아닐수도 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 샤딩 아키텍처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 클러스터의 가장 중요한 3가지 구성은 샤드 서버와 컨피그 서버 그리고 라우터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 샤드 클러스터에 샤드 서버는 레플리카 셋 형태로 1개 이상 존재할 수 있으며, 라우터 서버도 1개 이상 존재할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 컨피그 서버는 하나의 샤드 클러스터에 단 하나의 레플리카 셋만 존재할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤드 클러스터 컴포넌트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터 : 영구적인 데이터를 가지지 않으며 사용자의 쿼리 요청을 어떤 샤드로 전달할지 정하고 각 샤드로부터 받은 쿼리 결과 데이터를 병합해서 사용자에게 되돌려 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 각 샤드가 균등하게 데이터를 가지고 있는지 모니터링하면서 데이터의 밸런싱 작업도 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버 : 샤드 서버에 저장된 사용자 데이터가 어떻게 분산되어 있는지에 관한 메타 저보를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 서버 : 실제 사용자의 데이터를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤드 클러스터의 쿼리 수행 절차&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버는 샤드 클러스터에서 사용자가 생성한 데이터베이스와 컬렉션들의 목록을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 모든 목록을 관리하는 것은 아니고 샤딩이 활성화된 데이터베이스, 컬렉션의 정보만 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩이 되지 않는 객체들은 컨피그 서버가 아니라 각 샤드 서버가 로컬로 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 컬렉션이 여러 샤드 서버로 분산될 수 있게 분산하기 위한 정보를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 큰 컬렉션을 여러 조각으로 파티션하고 그 조각을 여러 샤드 서버에 분산해서 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 각 데이터 조각을 MongoDB 에서는 청크라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 쿼리 요청 시 라우터 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 샤용자 쿼리가 참조하는 컬렉션의 청크 메타 정보를 컨피그 서버로부터 가져와서 라우터의 메모리에 캐시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자 쿼리의 조건에서 샤딩 키 조건을 찾음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1. 쿼리 조건에 샤딩 키가 있으면 해당 샤딩 키가 포함된 청크 정보를 라우터의 캐시에서 검색하여 해당 샤드 서버로만 사용자 쿼리를 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-2. 쿼리 조건에 샤딩 키가 없으면 모든 샤드 서버로 사용자의 쿼리 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 쿼리를 전송한 대상 샤드 서버로부터 쿼리 결과가 도착하면 결과를 병합하여 사용자에게 쿼리 결과를 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 1번 과정은 라우터가 청크 메타 정보를 가지고 있지 않거나, 라우터가 가진 청크 메타 정보가 오래되어서 맞지 않을 때에만 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨피그 서버&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버는 샤딩된 클러스터를 운영하는데 있어 필요한 모든 정보를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버의 데이터는 컨피그 서버로 직접 로그인해서 확인할 수도 있지만, 라우터(mongos) 를 이용해서 클러스터에 로그인한 다음 config 데이터베이스로 이동하면 필요한 데이터를 조회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 라우터(mongos) 의 config 데이터베이스는 샤드 서버가 아니라 컨피그 서버로 접속해서 결과를 가져오며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외는 모든 사용자 데이터베이스와 컬렉션은 샤드 서버로 쿼리를 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버는 버전에 따라 조금씩 차이가 있찌만 대부분 아래 컬렉션을 가지고 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;config DB 컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;databases&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;샤드 클러스터가 가지고 있는 데이터베이스 목록을 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;collections&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;샤드 클러스터가 가지고 있는 컬렉션의 목록을 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;chunks&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;샤딩된 컬렉션의 모든 청크 정보를 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;shards&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;샤드 클러스터에 등록된 모든 샤드 서버의 정보를 레플리카 셋 단위로 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;mongos&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;현재 컨피그 서버와 한 번이라도 연결했던 모든 mongos 의 목록을 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;settings&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;청크의 밸런싱과 관련된 작업의 설정이 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;version&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;컨피그 서버가 가지고 있는 샤드 클러스터의 메타 데이터 전체에 대한 버전 정보가 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;lockpings&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;어떤 멤버가 언제 연결 상태를 확인했는지를 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;locks&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;많은 샤드 서버, 라우터 멤버들이 서로의 작업을 동기화하면서 처리하는데 이 때 같은 작업을 동시에 시작하면서 충돌이 발생할 수 있다. 따라서 각 멤버들은 컨피그 서버의 locks 컬렉션을 이용해서 이런 작업들을 동기화해서 처리하게 된다.&lt;br /&gt;이런 충돌을 막으려면 반드시 컨피그 서버의 lcosk 컬렉션에서 잠금을 획득한 후에 진행한다.&lt;br /&gt;* 3.4 버전부터는 동시에 여러 개의 청크를 하번에 이동시킬 수 있게 병렬 청크 이동 기능이 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;changelog&lt;/td&gt;
&lt;td style=&quot;width: 80%;&quot;&gt;컨피그 서버의 메타 정보 변경을 유발한 이벤트에 관해 정보성 이력을 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨피그 서버의 복제 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버는 샤딩이 활성화된 유저 데이커베이스와 컬렉션의 정보 그리고 각 컬렉션이 가지는 수많은 청크에 대한 메타 정보를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 메타 정보는 사용자 데이터의 일관성을 유지하기 위한 매우 중요한 정보로 반드시 3대 이상으로 복제할 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 이전 버전까지는 컨피그 서버가 미러링 된 형태의 데이터 복제 방식을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 3.2 버전부터는 사용자 데이터와 동일하게 레플리카 셋으로 배포하는 방법을 제공하고 설치 시 기본적으로 레플리카 셋 형태의 컨피그 서버를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미러링 된 형태의 컨피그 서버 방식을 SCCC ( Sync Cluster Connection Config ), 레플리카 셋 형태의 컨피그 서버 방식을 CSRS ( Config Servers as Replica Sets ) 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SCCC&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미러링 방식으로 컨피그 서버의 데이터를 동기하 하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미러링 방식이란 서로 전혀 관계 없이 작동하는 컨피그 서버 3대를 각각 따로 설치하고 응용 프로그램에서 3대의 컨피그 서버에 모두 접속하여 각 서버의 데이터를 동기화하는 방식을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 응용 프로그램은 컨피그 서버의 클라이언트인 MongoDB 서버와 라우터서버를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCCC 방식에서는 라우터 서버가 청크 정보를 변경하고자 할 때, 3개의 컨피그 서버에 접속하여 청크 정보를 변경(UPDATE)하는 문장을 각각 실행하고, 모두 성공적으로 완료되면 커밋을 수행하는 분산 트랜잭션을 실행하는 방식으로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이런 분산 트랜잭션 처리 방식은 컨피그 서버의 데이터가 복잡해지고, 변경 쿼리가 복잡해질수록 구현이 어려워짐과 동시에 동기화 문제들을 자주 유발시키는 원인이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 3.4 버전부터는 SCCC 방식의 컨피그 서버 구성은 완전히 없어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CSRS&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4 버전부터 일반 사용자 데이터를 저장하는 샤드 서버처럼 레플리카 셋으로 구현할 수 있도록 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋으로 컨피그 서버를 구축해야 하는 경우 아래 조건을 만족해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컨피그 서버는 반드시 WiredTiger 엔진을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 레플리카 셋은 아비터를 가질 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 레플리카 셋은 지연된 멤버를 가질 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최소 3개 이상의 멤버로 구성하는걸 매우 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋 방식으로 구축된 컨피그 서버는 클라이언트가 더 이상 모든 멤버에게 직접 접속하여 쿼리를 실행하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 컨피그 서버의 프라이머리 멤버로 접속하여 쿼리를 실행하는데 Read / Write Concern 레벨을 majority 로 설정하여 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 레플리카 셋의 프라이머리 멤버가 일시적인 장애로 복구됐을 때 롤백되는 데이터가 발생하지 않기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨피그 서버 가용성과 쿼리 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버로 투입할 수 있는 멤버의 수는 컨피그 서버의 구성 방식에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미러링 서버의 컨피그 서버의 수는 반드시 1대 또는 3대만 허용되는데 소스 코드 상에 고정된 상숫값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋 방식의 컨피그 서버에서의 수는 일반적인 레플리카 셋 구성에 필요한 멤버의 수와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋 방식의 컨피그 서버에서는 모든 메타 정보 조회 및 변경 쿼리의 경우 Concern 이 majority 인데 이는 전체 레플리카 셋 멤버의 과반수에 접근할 수 있어야만 쿼리를 수행할 수 있다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 컨피그 서버가 2대일때는 멤버 중 하나만 연결되지 않아도 메타 정보 조회와 삭제를 할 수없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 컨피그 서버가 항상 필요한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 사용자 쿼리는 라우트(mongos) 를 통해 처리되는데, 라우터 서버는 처음 기동될 때 컨피그 서버의 메타 정보를 일괄적으로 로드해서 자신의 캐시 메모리에 적재해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 새로운 멤버나 컬렉션이 추가/삭제, 청크의 분리 이동 시점에만 라우터 서버가 컨피그 서버에 데이터 변경 쿼리를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 때 컬렉션이 샤딩되지 않는 경우 실제 청크 변화가 없기에 컨피그 서버의 메타 데이터 변경을 필요로 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버의 데이터를 변경하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 마이그레이션 실행 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 청크 스플릿 실행 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버의 데이터를 조회하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 라우터 서버가 새로 시작되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 컨피그 서버의 메타 데이터가 변경된 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자 인증 처리 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.0 버전 이후로 샤딩된 MongoDB 서버에서 사용자의 인증 정보는 컨피그 서버에 저장되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보를 읽을 수가 없으면 라우터는 MongoDB 서버로 새로운 커넥션을 생성할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 컨피그 서버가 응답이 없으면 이미 생성된 커넥션은 괜찮지만 신규 커넥션은 더 이상 열 수 없는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 실제 서비스 상황에서 장애 상황이 될 수 있고, 이러한 이유로 MongoDB 3.2 버전의 메뉴얼에서는 컨피그 서버가 모두 응답 불능 상황이 되면 클러스터도 서비스 불가한 상황이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라우터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터 역할은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자 쿼리를 전달해야 할 샤드 서버를 결정하고 해당 샤드로 쿼리 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드 서버로부터 반환된 결과를 조합하여 사용자에게 결과 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 샤드간 청크 밸런싱 및 청크 스플릿 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터 서버는 샤드 서버로부터 받은 결과를 병합하여 사용자에게 주기도 하지만, 각 샤드가 내려준 결과가 실제 그 샤드 서버가 가지지 말아야 할 데이터인지 판단하고 가지지 말아야 할 데이터라면 데이터를 제거하는 작업도 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가지지 말아야 할 데이터는 청크 마이그레이션 중이거나, 청크 마이그레이션 도중 실패하거나, 사용자가 각 샤드로 접속하여 강제로 샤드키에 맞지 않는 데이터를 저장하는 경우에 발생할 수 있는데, 이런 필터링을 처리하지 않으면 중복된 데이터나 삭제된 데이터가 사용자에게 반환될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라우터의 쿼리 분산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 쿼리를 샤드 서버로 전달할 때, 쿼리의 조건을 기준으로 특정 샤드에 쿼리를 요청할 것인지 전체 샤드로 요청할 것인지 판단하는 것도 라우터의 중요한 역할 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터가 사용자의 쿼리를 특정 샤드로만 요청하는 형태를 타겟 쿼리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 샤드서버로 요청하는 경우를 브로드캐스트 쿼리라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소량의 도큐먼트를 아주 빈번하게 읽어가는 쿼리를 처리하는 서버에서는 타겟 쿼리가 효율적이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 많은 도큐먼트를 한 번에 읽는 쿼리가 가끔 실행되는 서버에서는 타겟 쿼리보다 브로드캐스트 쿼리가 서버의 자원을 더 효율적으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;타겟 쿼리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키 조건을 가진 사용자 쿼리에 대해 라우터는 조건을 분석해서 쿼리가 원하는 데이터가 있는 샤드 서버들을 제한할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;브로드퀘스트 쿼리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 키를 쿼리 조건으로 가지지 않는 경우에는 라우터가 작업 범위를 특정 샤드로 줄일 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 라우터는 쿼리를 모든 샤드로 요청해야 하고, 모든 샤드로부터 반환된 결과를 병합해서 사용자에게 반환해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 어떤 쿼리는 샤드 키를 어떻게가지든 무관하게 항상 브로드캐스트 쿼리방식으로만 처리되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다중 업데이트 : 업데이트 대상 검색 조건에 샤드 키를 포함하든 그렇지 않든 항상 브로드캐스트 쿼리로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UpdateMany() , DeleteMany() : 업데이트 대상 검색 조건이 반드시 샤드 키를 모두 포함하는 경우에만 타겟 쿼리로 실행될 수 있다. 복합 필드를 샤드 키로 가지는 컬렉션에서 일부 샤드 키만 사용하는 경우 브로드캐스트로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라우터 배포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 라우터는 N 개이상의 클라이언트와 연결을 맺고, 그와 동시에 샤드 서버와 연결을 맺는 서버이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 샤드 서버가 새로 추가되면 자동으로 그 샤드 서버와 커넥션을 연결하므로 라우터 서버와 샤드 서버 간의 커넥션 개수는 많아질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;응용 프로그램 서버와 함께 라우터 배포&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용 프로그램 서버에 MongoDB 라우터를 같이 실행하는 방법으로, MongoDB 매뉴얼에서 권장하는 가장 일반적인 형태의 배포 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 형태는 각 응용 프로그램 서버에서 실행 중인 라우터는 로컬 서버에서 실행 중인 응용 프로그램 서버로부터의 연결만 처리하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 MongoDB 라우터는 많은 시스템 자원을 사용하지 않도록 설계되었기 떄문에 응용 프로그램 서버가 많은 메모리와 CPU 를 사용한다고 해도 라우터가 미치는 영향은 미미하다고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴얼에서 권장하는 일반적인 형태이지만 이 배포 형태에 전혀 문제가 없지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용 프로그램 서버의 대수가 늘어나면 늘어날수록 MongoDB 샤드 서버 입장에서는 커넥션 개수가 ㄴ르어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용 프로그램 서버가 100대가 있고, 각 라우터가 샤드와 30개 정도의 커넥션을 가진다고 가정하면 모든 샤드 서버는 3000 개 정도의 연결을 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 샤드의 처리가 조금만 지연되어도 커넥션이 순식간에 1~2만개도 증가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전용의 라우터 서버 배포&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이유로 MongoDB 라우터를 전용 서버에서 실행하고 응용 프로그램은 하나 이상의 MongoDB 라우터 서버로 접속하여 쿼리를 실행하는 방법을 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형태는 응용 프로그램 서버 수만큼 라우터가 실행되지 않으므로 샤드 서버와의 커넥션도 줄어들 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 응용 프로그램은 드라이버의 연결부분에 모든 라우터 주소를 명시해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 MongoDB 에서 추천하는 방식은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식에서는 라우터간에 부하를 적절하게 분산해야 하며, 문제가 발생한 라우테에 대해 요청을 중지하고 라우터가 되살아났을 때 요청을 다시 전달해야하는 등의 문제점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 Atlas 서비스는 MongoDB 라우터를 샤드 서버와 컨피그 서버에 배포하고, 응용 프로그램은 클라이언트 드라이버에 이 라우터 목록을 설정해서 사용하도록 배포하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;L4 와 함께 라우터 배포&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 라우터가 많을 때에는 응용 프로그램의 MongoDB 드라이버에서 모든 MongoDB 라우터를 명시하기가 쉽지 않다. 그래서 MongoDB 라우터를 L4 스위치로 묶어서 사용하는 방법을 고려해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법은 L4 가 추가되면서 네트워크 시간을 길게 만들고, 또 하나의 문제점은 Find 쿼리와 GetMore 명령이 서로 다른 MongoDB 라우터로 요청되는 경우가 발생할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커서를 예로들면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리가 실행되면 MongoDB 라우터로 쿼리를 전송하고, 쿼리의 결과를 커서로 반환받는다. 이 때 쿼리 결과를 반환하는 것이 아니라 쿼리 결과를 가져올 수 있는 커서를 반환하며, 내부적으로 커서는 커서 Id 로 식별된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 쿼리 결과가 필요할 때마다 cursor.next() 함수를 호출해서 결과를 가져오다가 캐시한 결과가 더 없으면 커서 ID 를 이용해 라우터로 다시 결과를 요청한다. 이 때 서로 다른 라우터로 요청하게 되면 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 MongoDB 라우터를 L4 스위치로 묶어서 사용하고자 한다면 각 클라이언트 드라이버의 도큐먼트 패치 사이즈를 쿼리에서 조회하는 도큐먼트 수 만큼 크게 설정하거나 드 요청이 같은 MongoDB 라우터로 전송되도록 L4 스위치의 옵션을 조정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;샤드 서버나 컨피그 서버와 함께 라우터 배포&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 라우터를 샤드 서버나 컨피그 서버에 배포할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 자원을 많이 사용하지 않기 때문에 크게 문제가 되지는 않는다. 실제 Atlas 서비스(MongoDB 회사)는 이렇게 라우터를 MongoDB 서버가 설치된 서버에 같이 사용하도록 구성하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 고려사항은 네트워크 사용량이 적절한지 인데, MongoDB 는 도큐먼트에 많은 데이터를 저장하고 각 도큐먼트는 BSON 포맷이며 네트워크 사용량이 RDBMS 대비 높은 것이 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 다른 샤드 서버에 데이터를 조회해야 하고 클라이언트로 결과도 보내야 하기 때문에 네트워크 사용량이 2배까지 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 하나의 라우터는 관리자 용도로 생성하여 주로 관리자용 쿼리 및 밸런싱을 위한 MongoDB 라우터를 분리해서 배포하는데, 컨피그 서버가 배포된 서버에 같이 배포하곤 한다. ( 저자 기준 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&amp;nbsp;MongoDB 3.4 버전부터는 컨피그 서버가 밸런싱을 처리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자가 대용량 덤프나 적재작업을 하는 데에터 MongoDB 라우터가 필요한데, 이를 위해 라우터를 유지하기는 비용적인 문제가 있기에 컨피그 서버의 특정 멤버에 관리자용 라우터를 동시에 할당해서 사용하는 것이 목적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커넥션 풀 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 드라이버와 샤드 서버를 중계하는 역할을 수행하므로 클라이언트와 서버 쪽의 커넥션을 모두 가지고 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 클라이언트 쪽 커넥션이 서버 쪽 커넥션에 영향을 미치지 않고 독립적으로 관리되기 때문에 커넥션 풀에 유지되는 커넥션의 개수를 제어하기 쉽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MongoDB 클라이언트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 드라이버를 이용해서 MongoDB 샤드 서버나 라우터로 연결할 때 샤드 서버의 레플리카 셋&amp;nbsp; 목록을 나열할 수도 있고, 라우터를 나열할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에 접속할 때 레플리카 셋의 이름이 명시되지 않으면 클라이언트 드라이버는 최초 접속 시에 주어진 서버로만 접속하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MongoDB 라우터 - MongoDB 샤드 서버&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 클라이언트와의 커넥션과는 별개로 MongoDB 샤드 서버와의 연결을 맺는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 클라이언트 연결이 많아진다고 해서, 라우터와 샤드 간의 커넥션도 그만큼 생성되는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 클라이언트로부터 요청되는 쿼리들을 처리하기 위해서 내부적으로 TaskExecutorPool 을 서버의 CPU 코어 개수만큼 준비한다. TaskExecutorPool 은 일반적인 스레드 풀과 동일한 개념으로 이해해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 TaskExecutorPool 은 샤드 서버와의 연결 정보를 가지는 커넥션 풀을 하나씩 가지며, 커넥션 풀은 내부적으로 다시 서브 커넥션 풀을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 서브 커넥션 풀은 샤드 서버당 하나씩 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드 서버가 2개이므로 하나의 커넥션 풀은 2개의 서버 커넥션 풀을 가지고 있다. 이 서브 커넥션 풀을 소스 코드에서는 specific-pool 이라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TaskExecutorPool 은 서버에 장착된 CPU 코어의 개수만큼 설정되는데 개수를 명시적으로 제한하고자 한다면 다음과 같이 서버 설정 파일에서 파라미터를 추가하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1719820560669&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setParameter :
  taskExecutorPoolSize : 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 커넥션 풀(SpecificPool) 은 minConnections, maxConnections hostTimeout 이라는 옵션으로 커넥션 풀의 커넥션을 얼마나 보유할지 결정하는데, 기본값은 min 이 1개 이고 hostTimeout 은 5 분으로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 각 서브 커넥션 풀에 커넥션이 min 값보다 많으면 자동으로 그 커넥션을 끊어버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 커넥션 풀에 커넥션이 min 보다 적다 하더라도 일정시간 동안 쿼리 요청이 없으면 서브 커넥션 풀 자체를 종료하도록 설계되었는데 이 시간이 hostTimeout 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커넥션이 급증하는 시점은 그만큼 사용자 쿼리의 처리가 지연되고, 클라이언트에서는 쿼리나 큐 타임아웃 현상이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 현상을 막으려면 라우터와 샤드 서버간의 커넥션을 미리 준비해두는 것이 유일한 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.2.11 부터 라우터와 샤드 간의 커넥션을 조정할 수 있는 옵션을 사용자가 직접 변경할 수 있도록 기능이 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 커넥션을 제어하는 옵션은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ShardingTaskExecutorPoolHostTimeoutMS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 커넥션의 hostTimeout 옵션을 밀리초 단위로 설정하는 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ShardingTaskExecutorPoolMaxSize&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ShardingTaskExecutorPoolMinSize&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서브 커넥션 풀 별로 최소, 최대 커넥션의 개수를 지정하는 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ShardingTaskExecutorPoolRefreshRequirementMS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ShardingTaskExecutorPoolRefreshTimeoutMS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;백업 복구 시 주의 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩이 적용된 클러스터 멤버에서 백업된 데이터를 복구하는 경우에 주의해야 할 사항이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버에서 LVM 과 같은 스냅샷 도구를 이용해서 데이터 파일을 백업하거나&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 자체를 셧다운하고 백업해둔 데이터 파일을 이용해서 복구하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복구된 MongoDB 서버느 ㄴ백업되기 전의 MongoDB 클러스터 컨피그 서버로 접속해서 샤딩 구성을 복구하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 기존의 멤버를 대체하기 위해 복구하는 경우라면 문제가 없으나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 없어진 클러스터이거나 컨피그 서버가 변경되었다면 백업된 데이터 파일을 이용해서 시작된 MongoDB 서버는 시작되지 못하고 기존 컨피그 서버의 응답을 계속 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우 MongoDB 시작 옵션에 recoverShardingState 옵션을 false 로 설정하면 기존 컨피그 서버의 응답을 무한대로 대기하는 현상을 회피할 수 있다.&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/638</guid>
      <comments>https://mozi.tistory.com/638#entry638comment</comments>
      <pubDate>Fri, 28 Jun 2024 17:33:55 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 복제(2)</title>
      <link>https://mozi.tistory.com/637</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복제 로그 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 는 큐처럼 작동하는 Cap 컬렉션으로 관리되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버가 OpLog 를 가져간다고 해서 컬렉션의 오래된 OpLog 가 자동으로 지워지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 OpLog 를 저장하는 컬렉션은 항상 최대 크기를 명시해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;oplog.rs 컬렉션을 포함한 Cap 컬렉션은 INSERT 이외의 데이터 변경을 허용하지 않으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션이 지정된 크기를 넘어서면 MongoDB 서버가 자동으로 가장 오래된 데이터를 삭제하면서 지정된 크기를 넘지 않도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpLog 컬렉션 크기 설정&lt;/h3&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 크기가 중요한 이유는 이 컬렉션이 OpLog 를 얼마나 담을 수 있느냐에 따라 세컨드리가 허용 가능한 지연 시간이 결정되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 OpLog 가 1 2 3 4 5 가 있을 때, 세컨드리는 1 2 까지 반영된 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 프라이머리의 OpLog 가 가득 차 1 2 3 을 지우면 세컨드리는 동기화를 할 수 없게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 세컨드리 멤버는 자신의 데이터 디렉터리에 있던 모든 데이터 파일을 버리고 초기 동기화를 다시 수행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 3 번의 데이터를 가진 다른 세컨드리 멤버가 있다면 그 세컨드리 멤버로부터 OpLog 를 동기화 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 컬렉션을 명시적으로 설정하지 않으면 MongoDB 는 디스크의 사용되지 않는 공간에서 5% 정도를 OpLog 크기로 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 무조건 여유공간의 5% 는 아니고 990MB ~&amp;nbsp; 50GB 이내에서 여유 공간의 5% 로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 50GB 이상의 크기를 설정하고자 할 때는 oplogSizeMB 옵션을 이용하여 설정 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 OpLog 크기와 며칠 동안의 Log 를 담고있는지는 아래의 명령어로 확인 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1716729064318&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mongo&amp;gt; db.getReplicationInfo()
{
  &quot;logSizeMB&quot; : 20000,
  &quot;usedMB&quot; : 200910.19,
  &quot;timeDiff&quot; : 2660247,
  &quot;timeDiffHours&quot; : 738.96,
  &quot;tFirst&quot; : &quot;Wed Oct 19 2016 16:37:07 GMT+0900 (KST)&quot;,
  &quot;tLast&quot; : &quot;Sat Nov 19 2016 11:34:34 GMT+0900 (KST)&quot;,
  &quot;now&quot; : &quot;Sat Nov 19 2016 11:34:34 GMT+0900 (KST)&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;logSizeMB&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;oplog.rs 컬렉션의 최대 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;usedMB&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;현재 저장된 OpLog 의 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;timeDiff&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;tFirst 와 tLast 의 시간 차이를 초단위로 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;timeDiffHours&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;tFirst 와 tLast 의 시간 차이를 시간단위로 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;tFirst&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OpLog 컬렉션에 저장된 첫 Log 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;tLast&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OpLog 컬렉션에 저장된 마지막 Log 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;now&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;현재시간&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;printReplicationInfo() 를 사용하여 텍스트 형태로도 조회가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전까지는 한번 설정한 OpLog 의 크기를 쉽게 변경할 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기를 변경하는 방법은 레플리카 셋에서 제거하고 데이터를 복사한 다음 다시 레플리카 셋에 투입하는 방법인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.6 버전부터 MongoDB 서버의 설정 변경만으로 파일의 크기를 재설정할 수 있도록 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복제 동기화 상태 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버가 프라이머리 멤버의 OpLog 를 어디까지 동기화했는지 또는 복제가 지연이 발생하고 있는지 확인하려면 rs.printSlaveReplicationInfo() 명령을 활용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1716729631941&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mongo&amp;gt; rs.printSlaveReplicationInfo()
source: test-mongo2:27017
  syncedTo: Sat Nov 19 2016 11:34:57 GMT+0900 (KST)
  0 secs (0 hrs) behind the primary
source: test-mongo3:27017
  syncedTo: Sat Nov 19 2016 11:34:57 GMT+0900 (KST)
  1 secs (0 hrs) behind the primary&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test-mongo2 멤버는 프라이머리의 OpLog 동기화에서 지연이 0초이며, test-mongo3 은 지연이 1초이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 지연 시간이 0초라고 표시되어도 실제로 모든 데이터가 동기화되었음을 의미하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밀리초나 그 이하의 지연시간은 0 초로 표기되며 진짜 동기화가 되었는지 확인을 위해서는 ts 필드의 값을 서로 비교해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1716729812082&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mongo&amp;gt; use local
mongo&amp;gt; db.oplog.rs.find().sort({$natural:-1}).limit(1).pretty()
{
  &quot;ts&quot; : Timestamp(1478933458, 1),
  &quot;t&quot; : NumberLong(1),
  &quot;h&quot; : NumberLong(&quot;-77407784728272165880&quot;),
  &quot;v&quot; : 2,
  &quot;op&quot; : &quot;c&quot;,
  &quot;ns&quot; : &quot;test.$cmd&quot;,
  &quot;o&quot; : {
    &quot;create&quot; : &quot;user&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 레플리카 셋에 포함된 서버들이라 하더라도 각 서버의 시간 동기화 서비스(NTP) 작동 시점에 따라 멤버간의 시간 차이가 발생할&amp;nbsp; 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 세컨드리 멤버가 프라이머리 멤버보다 이전&amp;nbsp; 시간을 가진다면 복제결과의 지연시간이 음수로 표시될 수도 잇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpLog 컬렉션과 백업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제가 가장 일반적으로 사용되는 MySQL 서버에서는 복제 로그를 별도의 로그 파일로 기록하고 있는데, 이를 바이너리 로그라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 복제를 위한 데이터 변경 로그를 단순 로그 파일이 아니라 MongoDB 내에서 하나의 컬렉션으로 관리하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컬렉션으로 관리함으로써 MongoDB 의 스토리지 엔진이 제공하는 디스크 읽고 쓰기의 최적화 로직들을 모두 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 MongoDB 의 OpLog 는 데이터파일의 일부이기 때문에 OpLog 로그가 저장된 oplog.rs 컬렉션의 데이터 파일이 백업에 반드시 포함되어야 하는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제점은 OpLog 컬렉션의 최대 크기를 설정하는 부분이 백업과 허용 가능한 복제지연간의 상충 관계가 될 수 있는지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- OpLog 컬렉션의 크기가 작으면 백업해야 할 OpLog 크기는 줄어들지만, 인덱스 생성과 같은 관리자 작업이나 과도한 복제 지연 시 세컨드리 멤버의 복제 쓰레드가 프라이머리 멤버의 OpLog 를 제 때 가져가지 못해서 복제 동기화에 실패할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- OpLog 컬렉션의 크기가 크다면 관리 작업이나 복제 지연 이슈는 해결되지만 그만큼 백업해야 할 데이터 파일의 크기가 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 컬렉션은&amp;nbsp; MongoDB 서버가 실제 가지고 있는 최종 데이터 파일의 크기와 무관하며, 얼마나 변경되는 데이터가 많은가에 따라 크기가 커질수도 작아질수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LVM 과 같이 파일 시스템 스냅샷을 이용한 물리백업에서는 OpLog 를 저장하는 컬렉션의 데이터 파일을 제외하고 백업하는 방향을 고려해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 적절한 방법이 아닐 수 있는데 (3.2버전 기준), 예를들어 MMAPv1 스토리지 엔진을 사용하는 서버는 일부 컬렉션의 데이터파일이 없어도 나머지 컬렉션은 아무 이슈 없이 처리될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 WiredTiger 엔진은 스토리지 엔진이 초기화되는 시점에 자신의 딕셔너리 정보에 등록된 컬렉션 중에서 데이터 파일이 없거나 손상된 것을 알게되면 즉시 엔진이 멈춰버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 나머지 컬렉션의 데이터 파일에 아무 문제가 없다고 하더라도 MongoDB 서버 자체가 시작을 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 데이터파일이 하나라도 누락되면 나머지 문제없는 컬렉션의 데이터를 덤프해서 재구축한다거나 하는 작업을 할 수가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.0 에서는 해결된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레플리카 셋 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 레플리카 셋 설정은 rs.conf() 명령으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제와 관련된 설정은 버전에 따라 조금씩 차이가 있는데, 대략적인 설정 내용을 살펴보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;version 필드는 레플리카 셋 설정의 버전인데, 이 버전은 레플리카 셋 설정이 변경될 때마다 1씩 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에 멤버가 추가되거나 제거 또는 각 멤버들의 설정이나 하트비트와 관련된 설정이 변경되는 모두 version 이 증가하고, 각 멤버는 이 버전을 이용해서 각자 최종의 레플리카 셋 정보를 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하트비트 메시지 주기와 프라이머리 선출 타임아웃&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protocolVersion 은 레플리카 셋의 각 멤버들이 프라이머리를 선출하는데 사용하는 프로토콜의 버전을 의미하는데, 현재 이 값은 0 또는 1만 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.2 버전부터 1 이 지원되며 레플리카 셋에 포함된 멤버들의 장애 감지와 프라이머리를 빠르게 선출할 수 있도록 하기 위한 개선작업이 있었느넫, 기존의 프라이머리 선출 방식에 호환되지 않아서 이렇게 별도의 protocolVersion 필드를 관리하게 된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rs.conf 에 protocolVersion 필드가 없다면 이는 0 임을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 1 부터 electionTimeoutMillis 옵션을 이용하여 프라이머리의 장애 감지 시간을 사용자가 설정할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chaningAllowed 필드는 MongoDB 의 복제를 체인 구조로 할 수 있는지 아닌지를 나타내는 필드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 MongoDB 는 세컨드리가 항상 프라이머리 멤버로부터 OpLog 를 가져와야 데이터를 동기화 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 프라이머리 멤버의 부하를 높이며 때로 가까이 있는 세컨드리 멤버로부터 데이터 동기화하는 것이 더 빠르게 동기화되는 경우도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 3.2 부터는 세컨드리 멤버가 다른 세컨드리 멤버의 OpLog 를 가져와서 동기화할 수 있게 되었는데, 이런 형태를 체인 구조(토폴로지) 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋은 아래 옵션값으로 상태를 체크한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;heartbeatIntervalMillis : 얼마나 자주 하트비트 메시지를 전송할 것인지 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;heartbeatTimeoutSecs : protocolVersion 0 일 때 전송한 하트비트 메시지에 대해 몇 초 동안 응답이 없을 때 해당 멤버가 죽었다고 판단할 것인지 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;electionTimeoutMillis : protocolVersion 1 일 때 전송한 하트비트 메시지에 대해 몇 밀리초 동안 응답이 없을 때 해당 멤버가 죽었다고 판단할 것인지 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;electionTimeoutMillis 값이 크면 클수록 프라이머리 선출이 늦어지지만 네트워크 영향에 덜 민감하게 반응한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 값이 작을수록 선출은 빨라지지만 네트워크 영향에 민감하게 반응하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레플리카 셋 멤버 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rs.conf() 명령으로 레플리카 셋에 대한 설정뿐만 아니라 레플리카 셋에 포함된 각 멤버의 설정도 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;멤버 우선순위 ( Priority )&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;priority 는 해당 멤버가 프라이머리 노드가 될 수 있는 우선순위를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 동일한 priority 를 가진 상황에서 세컨더리의 priority 를 명시적으로 올리면 MongoDB 는 priority 가 가장 높은 멤버에게 프라이머리가 될 수 있는 우선권을 부여하려고 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 기존의 프라이머리는 세컨드리로 바뀌고 그와 동시에 새로운 프라이머리를 선출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하나의 priority 의 우선순위가 높다면 서비스 요청을 잘 처리하고 있는 노드도 세컨드리에서 프라이머리로 변경될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 별로 좋지 않은 방법인데, 특정 서버가 반드시 프라이머리가 되어야 할 필요가 있다면 priority 값으로 강제하는 방법이 좋은 해결책이 될 수 있으나 특별한 이유가 없다면 프라이머리 역할이 옮겨다니는 것은 서비스 안정성에 악영향을 미쳐 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;투표권 ( votes )&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋의 각 멤버가 가지는 투표권의 개수를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.0 이전 버전에서는 투표권을 2개 이상 가지는 설정이 가능했으나 선출 과정을 복잡하게 만들기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전부터 모든 레플리카 셋 멤버는 0 또는 1개의 투표권만 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 투표권은 MongoDB 레플리카 셋 멤버가 최대 50개 까지 되는 반면 투표권은 7개만 허용되므로 이런 경우 투표권을 가지지 못하도록 하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 레플리카 셋이 7개 이하의 멤버로 구성된다면 굳이 투표권을 조정할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;히든 멤버 ( Hidden Member )&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 레플리카 셋에 포함된 각 멤버는 용도별로 나눠서 활용해야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 용도는 '사용자 쿼리를 빠르게 처리 / 배치나 통계용 쿼리 / 관리자를 위한 백업이나 복구 용도' 가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로 이런 다양한 용도(백업/복구)는 MongoDB 서버를 셧다운 해야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작업을 위해 MongoDB 레플리카 셋은 특정 멤버를 클라이언트로부터 숨길 수 있는 기능을 제공하는데, 이럴 때 레플리카 셋의 멤버 속성을 히든 멤버로 설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에게 해당 서버를 노출하지 않기 때문에 클라이언트는 멤버를 볼 수가 없어 쿼리를 실행할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 히든 멤버에 직접 접근하여 수행하는 경우에는 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;히든 멤버는 사용자의 쿼리를 처리하지 않지만, 프라이머리의 복제로그는 계속 가져와 재생하여 최신의 데이터를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 관리자가 대량의 데이터 검증이나 확인을 할 때도 유용하며 사용자의 쿼리가 유입되지 않기 때문에 서버를 셧다운해서 데이터 파일을 백업하는 작업도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 MongoDB 에서는 서비스 중에 백그라운드 모드로 인덱스를 생성할 수 있지만 이 또한 부담이 될 수 있어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;히든 멤버에서 인덱스를 포어그라운드 모드로 빠르게 생성하고 다른 서비스 노드로 물리적인 데이터 파일을 복사하는 방식으로 빠르게 대처할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(데이터파일 복사할때 서비스 다운 안하고?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;지연된 복제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 사용자나 관리자의 실수 또는 응용 프로그램의 오류로 인해서 데이터가 손상되었을 때 복제로 이런 실수를 복구할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 에서는 이러한 실수를 최소한으로 보호해줄 방법으로 지연된 복제 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연된 복제는 말그래도 일정 시간동안 데이터 복제를 지연시키는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연된 복제 처리는 레플리카 셋 설정에서 settings.slaveDelay 옵션에 지연시키고자 하는 시간만큼 초 단위로 설정하면 된다. 즉 3600 초로 설정하면 그 멤버는 프라이머리로부터 1시간 이전의 복제로그 중에서 자신이 재생하지 않은 것들만 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 지연된 복제가 설정된 경우 관리자나 사용자의 실수를 1시간 이내 알아차리면 지연된 복제를 수행하고 있는 멤버의 데이터로 복구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 지연된 복제는 결국 프라이머리 멤버가 가진 복제로그 범위 안에서만 가능하므로 몇주 몇달 지연시키기는 쉽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 가능하다면 오프라인 백업은 별도로 유지하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런 오프라인 백업이 불가하다면 최소한의 복구 수단으로 지연된 백업을 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레플리카 셋 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 멤버의 수는 레플리카 셋의 데이터 복사본의 수를 결정하는 중요한 요소이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 복사본의 수는 레플리카 셋에서 최대 허용 가능한 장애 멤버의 수를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 멤버 수가 많을수록 역할을 분담하여 처리할 수 있기 때문에 서비스를 위한 전용 멤버를 보장할 수 있고 서비스 품질을 향상시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 프라이머리 선출을 위한 투표멤버가 되기 때문에 멤버가 많고 다양한 위치로 분산될수록 클라이언트 연결을 더 안정적으로 보장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레플리카 셋 멤버의 수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋 멤버의 수를 결정하는데 있어 아래 사항을 고려해야 서버 장애시 레플리카 셋의 잘못된 선택을 막을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;투표 가능한 최대 멤버 수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투표 가능한 멤버 수가 7개를 넘어 갈 때, 서버의 성능이 나은 멤버한테 투표권을 부여해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 성능이 떨어지면 복제 지연도 심해지고 투표 수행 자체도 느려지게 되어 선출 과정도 느려져 서비스 품질이 떨어지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;홀수 멤버 유지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에서 프라이머리 선출은 반드시 과반 수 이상의 멤버가 투표에 참여할 수 있어야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 스플릿이 발생하면서 반반씩 나뉘어 버리면 어느쪽도 프라이머리를 선출하지 못하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 통계나 분석등을 위해 짝수 멤버가 필요한 경우라면 아비터를 투입해서 투표 가능 멤버는 홀수가 되도록 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋을 홀수 멤버로 유지하는 것을 권장하는 이유는 고가용성 수준을 유지하는데 필요한 멤버의 수가 홀수일 때보다 짝수일 때 더 많이 필요하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 2대로 구성된 레플리카 셋은 하나의 멤버만 장애가 발생해도 프라이머리를 선출하지 못하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 1대로 구성된 레플리카 셋과 가용성 수준이 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;읽기 쿼리 분산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋의 멤버를 추가하는 이유 중 하나는 읽기 쿼리를 분산하기 위한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버에서 데이터 읽기가 필요하다면 프라이머리에 변경된 데이터가 세컨드리로 동기화되는 지연 시간을 반드시 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 복제 지연이 허용되지 않는 서비스라면 프라이머리에서 데이터 변경 쿼리를 실행할 때 WriteConcern 옵션을 이용해서 세컨드리까지 변경이 완료되어야 프라이머리의 쿼리가 완료되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 충분히 프라이머리가 처리할 수 있을 정도의 읽기 쿼리는 굳이 세컨드리를 활용하지 않는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;레플리카 셋의 멤버 확장을 위한 여분의 멤버&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 MongoDB 레플리카 셋에서 가장 이상적인 멤버의 수는 3개 정도이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 멤버 수가 많으면 좋으나 비용의 문제를 무시할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 레플리카 셋을 2대로 구성하는 것을 감안했을 때 고려사항을 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 멤버 + 아비터&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;서버 비용 절감&lt;br /&gt;- 2대 서버 투입으로 고가용성 유지&lt;br /&gt;- 아비터를 위한 장비는 가상 서버나 아비터 공용 서버 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;백업을 위한 멤버 부족&lt;br /&gt;- 3.2 버전 물리적인 수준의 백업 도구를 제공하지 않고 있어 세컨드리 멤버를 셧다운 하고 변경되지 않는 상태에서 데이터 파일 복사로 백업을 수행해야 할 수도 있음&lt;br /&gt;- 백업 수행 중 프라이머리 장애 시 서비스 불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;새로운 멤버를 추가할 때 부하 이슈&lt;br /&gt;- 프라이머리가 서비스 쿼리를 처리하고 세컨드리 멤버는 대기중이라면 새로운 멤버를 추가할 때 새로운 멤버가 대기중인 세컨드리로부터 동기화 가능&lt;br /&gt;- 데이터를 가진 두 개의 멤버 중 하나의 멤버가 장애로 데이터를 잃어버리면 프라이머리와 아비터만 남게 됨, 이 때 새로운 세컨드리 멤버를 투입하게 되면 세컨드리는 반드시 기존 멤버로부터 데이터를 동기화 해야 함&lt;br /&gt;이 경우, 프라이머리로부터 동기화해야하는데 상당히 많은 시스템의 부하를 유발하므로 사용자의 쿼리를 느리게 만들 가능성이 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 멤버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 멤버로 구성할 때는 2개멤버 + 아비터로 구성했을때의 장단점과 반대가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여유 서버를 확보할 수 있는 반면 구축 비용이 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;레플리카 셋의 이름&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 특성에 따라 규모는 작아도 여러 개로 구성된 레플리카 셋이 필요할 수도 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 드라이버는 레플리카 셋의 이름을 커넥션 식별자로 사용하기로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런 요건을 대비하기 위해 레플리카 셋의 이름을 유니크하게 부여하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DR 구성 ( Disaster Recovery )&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DR 은 하나의 IDC 가 재해로 인해 완전히 사용할 수 없는 상황이 되었을 때, 다른 IDC 에 복제된 서버들로 서비스를 재개할 수 있도록 복구하는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3개 멤버로 구성된 레플리카 셋&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 가능한 IDC 가 2개일 때, 프라이머리 + 세컨드리 2개는 A IDC 에, 나머지 세컨드리는 B IDC 에 구축한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B IDC 가 문제일 때 A IDC 에 2대가 살아있으므로 서비스 요청을 처리하는 데에는 아무런 문제가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 A IDC 가 문제가 발생하면 B IDC 는 한 대만 남게되어 과반수 충족을 못해 프라이머리 선출이 불가능해지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에서 프라이머리가 없기 때문에 클라이언트 변경 요청을 처리할 수 없으며 읽기 쿼리만 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 가능한 IDC 가 3개일 때, 프라미러는 A IDC, 세컨드리1 은 B IDC, 세컨드리2 는 C IDC 에 구축한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 하나의 IDC 가 문제있더라도 2개의 IDC 가 살아있기 때문에 프라이머리 선출이 가능하고 서비스도 지속적으로 이어갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5개 멤버로 구성된 레플리카 셋&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 가능한 IDC 가 2개일 때, 프라이머리+세컨드리 2개는 A IDC 에, 나머지 2개 세컨드리는 B IDC 에 구축한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 역시 A IDC 문제가 발생하면 과반수 충족을 못해 프라이머리 선출을 할 수 없게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 가능한 IDC 가 3개일 때, 프라이머리+세컨드리1 는 A IDC , 세컨드리 2개는 B IDC , 세컨드리 1개는 C IDC 에 구축한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 IDC 가 문제있더라도 3대 이상의 서버는 연결가능하기 때문에 프라이머리 선출이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDC 를 분리하는 경우에는 Priority 를 설정하여 응답속도가 빠른 서버에 우선순위를 부여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레플리카 셋 배포 시 주의사항&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세컨드리 멤버의 장비 사양&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 서비스 환경의 레플리카 셋 구축에는 최소 3개의 멤버가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 프라이머리-세컨드리 2개 멤버는 필수이고 대부분 스펙을 거의 동일하게 투입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 나머지 세컨드리 1개의 멤버도 같은 스펙으로 해야하는지 고민이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 같은 스펙혹은 OpLog 가 지연되지 않게 처리할 수 있는 스펙으로 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 프라이머리의 변경 내용을 복제하는데 많은 시스템 자원을 소모하게 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로 인해서 레플리카 셋 사이의 하트비트 메시지를 처리하는 시간이 지연되며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프라이머리 선출 과정 또한 지연되는 결과가 나타나기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 컬렉션에 5개의 인덱스가 있는 레플리카 셋을 가정해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리에서 1개의 INSERT 가 발생하면 개개의 인덱스마다 엔트리를 저장할 위치를 검색해야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 랜덤 디스크 검색을 해야 하기 때문에 복제 지연을 피하기 어려울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 3.2 버전부터 저널 로그의 디스크 동기화가 매우 빈번하게 발생되기 때문에 성능이 낮으면 지연이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;레플리카 셋 멤버의 네트워크 분산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 MongoDB 레플리카 셋이 3개의 멤버로 구성되어 있는데, 그 중 2개의 멤버가 동일한 스위치에 연결되어 있다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 스위치가 재시작되거나 망가지면 가용성을 보장할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 하나의 IDC 내에서 레플리카 셋을 구축하는 경우라 하더라도 반드시 각 멤버가 서로 다른 네트워크 스위치로 연결되어 서버를 배포하는 것을 필수 사항이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/637</guid>
      <comments>https://mozi.tistory.com/637#entry637comment</comments>
      <pubDate>Sun, 26 May 2024 22:53:26 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 복제</title>
      <link>https://mozi.tistory.com/636</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AMFH0/btsHvrnlYue/KtcRkafu2ZZgtEq3dESrl1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AMFH0/btsHvrnlYue/KtcRkafu2ZZgtEq3dESrl1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AMFH0/btsHvrnlYue/KtcRkafu2ZZgtEq3dESrl1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAMFH0%2FbtsHvrnlYue%2FKtcRkafu2ZZgtEq3dESrl1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;복제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 서버가 서로의 데이터를 동기화하는 것을 의미하는데 서로 주고받는 데이터에 따라 논리 복제와 물리 복제로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DRBD 와 같이 리눅스 서버가 데이터 내부를 전혀 모르는 상태에서 디스크의 블록만 복제하는 형태를 물리복제라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 서버가 직접 서 버간 데이터를 동기화하는 방식을 논리적 복제라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 복제는 크게 2가지 '마스터-슬레이브 복제', '레플리카 셋 복제' 로 나뉘는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전 이후로 마스터-슬레이브 복제는 사용되고 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;컨센서스 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 서버가 복제에 참여해서 서로 같은 데이터를 동기화하는데, 이렇게 데이터를 공유하는 그룹을 레플리카 셋이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 하나의 레플리카 셋에는 프라이머리와 세컨드리로 각자의 역할이 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에 참여하는 각 멤버들은 각자의 역할에 맞게 작동하면서 서로의 데이터를 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 특정 노드가 응답 불능 상태가 됐을 때 어떻게 대처할 것인지를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 어떻게 작동할지 결정하는 것을 컨센서스 알고리즘이라 하는데 MongoDB 는 확장된 형태의 Raft 컨센서스 모델을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 MySQL 은 Paxos 알고리즘을 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Raft 컨센서스 알고리즘&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 특징은 리더 기반의 복제와 각 멤버 노드가 상태를 가진다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 레클리카 셋에는 반드시 하나의 리더만 존재할 수 있고, 리더는 사용자의 모든 데이터 변경 요청을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리더는 사용자와 요청 내용을 로그에 기록하고 모든 팔로워는 리도의 로그를 가져와서 동기화를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Raft 컨센서스 알고리즘의 리더를 MongoDB 에서는 Primary, 팔로워를 Secondary 노드라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 로그를 MongoDB 에서는 OpLog 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;OpLog 와 Journal Log 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 OpLog 는 일반적인 RDBMS 의 리두로그(Redo Log)와는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서의 리두로그는 저널로그라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 와 Journal Log 2개가 사용되는 이유는, OpLog 는 컬렉션의 레코드 형태로 저장되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 OpLog 와 Journal Log 가 도입된 시점과 관리주체가 다르기 때문으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 는 MongoDB 엔진이 처리하는 반면 Journal Log 는 각 스토리지 엔진이 처리해야 하는 부분이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;복제의 목적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제의 가장 큰 목적은 동일한 데이터를 이중, 삼중으로 유지함으로 써 레플리카 셋의 특정 멤버에서 데이터 손실이 발생하더라도 다른 멤버의 데이터로 대체할 수 있도록 하기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 고가용성을 위해서 중복된 데이터 셋을 준비하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 고가용성을 위해 레플리카 셋의 각 멤버는 서로 다른 멤버가 살아있는지 계속 확인 메시지를 주고받는데, 이를 하트비트(HeartBeat) 메시지라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 특정 멤버가 통신이 되지 않으면 다른 멤버들이 새로운 프라이머리 멤버를 선출해서 서비스가 지속적으로 처리될 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 새로운 프라이머리 멤버를 선출하는 과정은 MongoDB 샤딩이나 컨피그 서버와 무관하게 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제의 또 다른 목적으로는 데이터 조회 쿼리의 로드 분산을&amp;nbsp; 생각해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 고가용성만을 위한 레플리카 셋은 3대 정도의 서버로 구성하는데, 만약 데이터 조회 쿼리가 많은 서비스에서는 복제 멤버를 더 추가할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에서 쓰기 쿼리를 처리할 수 있는 프라이머리 멤버는 하나만 존재할 수 있다. 그래서 레플리카 셋에 멤버를 추가한다고 해서 쓰기 쿼리의 처리를 확장할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 세컨드리 멤버가 늘어나면 늘어난 멤버 수 만큼 읽기 쿼리를 분산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 쿼리를 프라이머리에서 수행할지 세컨드리에서 수행할지 결정할 수 있도록 MongoDB 클라이언트 드라이버는 Read Preference 옵션을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버는 아직 서비스 도중이 가능한 물리적인 백업기능(온라인 물리백업)을 제공하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongoexport 와 같은 도구를 이용해서 서비스 도중 논리적인 백업은 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 논리적인 백업은 데이터를 복구하기 위한 시간이 상대적으로 오래걸리기 때문에 긴급하게 복구해야 할 때는 도움이 되지 못할 수 있어, 세컨드리 멤버를 멈추고 데이터 파일을 복사해야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;레플리카 셋 멤버&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 레플리카 셋에는 최대 50개까지의 멤버가 복제에 참여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋의 모든 멤버는 서로의 상태를 주기적으로 체크해야 하기 때문에, 멤버가 많을수록 주기체크에 더 큰 비용이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 50개까지의 멤버가 레플리카 셋에 참여한다 하더라도 프라이머리 멤버 선출에 참여할 수 있는 멤버는 7개까지만 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 선출작업 또한 복잡한 과정이라 너무 많은 멤버간의 조율이 필요하지 않도록 제한했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 멤버가 7개 이상이라면 반드시 추가되는 멤버들은 Non-Voting 멤버로 설정되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런이유로 불필요하게 많은 멤버를 추가하지 않는게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터변경을 처리할 수 있는 멤버이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 멤버가 처리한 변경 데이터를 실시간으로 가져와서 프라이머리와 동일한 데이터 셋을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 멤버가 응답 불능상태가 되면 투표를 통해서 세컨드리 멤버 중 하나가 프라이머리 멤버가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 역할이 변경되는 것을 프로모션 또는 스텝업이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아비터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋의 멤버로 참여해서 프라이머리 선출에는 관여하지만, 실제 사용자데이터를 전혀 가지지 않고 프라이머리 멤버로부터 OpLog 를 가져오지도 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정족수를 채우기 위해 추가로 멤버가 필요한 경우에는 아비터를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;프라이머리 선출&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋이 프라이머리를 선출해야 하는 이유는 한 가지 인데, 해당 레플리카 셋에 현재 프라이머리 멤버가 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 이유에 의해 프라이머리 멤버가 없어지거나 연결되지 안하고 나올 수 있는데 어찌되었든 프라이머리 멤버가 없으면 데이터 변경 요청을 처리할 수 없게되며 옵션에 따라 때로는 읽기 쿼리조차도 불가능 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 레플리카 멤버들은 프라이머리가 없어진 것을 알아채면 즉시 새로운 프라이머리를 선출하는 로직을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.0 까지는 각 서버가 인지하고 있는 시각(Wall Clock)에 의존했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제의 시각은 서버마다 차이가 있을 수 있고 정확하지 않을 수 있어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 선출을 일정시간 주기로 한 번만 실행할 수 있도록 설계했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이런 방식은 많은 문제점을 안고있어 3.2 버전부터는 새롭게 논리적인 시간을 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.0 버전까지의 프라이머리 선출 방식을 Protocol Version 0 이고 3.2 부터 Protocol Version 1 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;프라이머리 텀(Primary Term)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.0 까지는 프라이머리 텀이라는 개념이 없었다. 그래서 여러 멤버가 동시에 투표를 하면 중복 투표의 위험이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 막기위해 3.2 이전 버전에서는 프라이머리 선출 투표가 30초에 한 번만 실행될 수 있게 설계되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 프라이머리 선출을 위한 투표가 한번 실패하면 그 레플리카 셋은 30초동안 프라이머리가 없는 상태로 대기해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기간동안 사용자의 데이터 변경 요청을 처리할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 이전 버전까지는 이런 중복투표나 30초 대기시간이 최대한 발생하지 않게 실제 내부적으로는 2단계 투표를 실행하도록 설계되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계 : 사전투표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리가 되고자 하는 세컨드리는 먼저 다른 세컨드리 멤버에게 자기 자신이 프라이머리가 되려고 한다면 반대하지 않을지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계 : 본투표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 세컨드리 멤버들이 반대하지 않으면 그때 본 선거를 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 사전선거를 통해 본 선거의 실패 상황(프라이머리를 선출하지 못하는 상황)을 최소화하고자 한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.2 부터는 프라이머리 텀이라는 개념이 도입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 텀은 투표 식별자이며, 레플리카 셋의 각 멤버들이 프라이머리 선출을 시도할 때마다 1씩 증가하는 논리적인 시간값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 각 멤버들은 투표요청이 오면 30초동안 기다리는 것이 아니라 그 투표의 식별자를 기준으로 자기가 이미 투표를 했는지 아니면 다시 투표에 참여해야 하는지 결정할 수 있게 된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 프라이머리 텀은 투표할 때만 사용되는 것이 아니라 프라이머리 멤버가 사용자의 데이터 변경 요청을 실행한 다음 변경내용을 OpLog 에 기록할 때마다 현재 텀(Term) 식별자를 같이 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보를 이용해 특정 OpLog 가 어느 멤버가 프라이머리였을 때의 로그인지 식별할 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 투표가 시작되는 시점에 프라이머리 텀이 시작되고 그 텀이 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 어떤 이유에서든 프라이머리에 연결을 할 수 없게 되면 새로운 프라이머리를 선출하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 프라이머리 텀이 1 증가하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 선출을 위한 투표가 항상 성공하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우에는 프라이머리 텀 값만 증가하고 끝나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;프라이머리 스텝 다운(Primary Step Down)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋에서 프라이머리가 없으면 다른 세컨드리 멤버들은 모두 자신의 레플리카 셋에 프라이머리 멤버가 없다고 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 프라이머리가 레플리카 셋 설정에 명시된 electionTimeoutMillis 내에 응답이 없으면 레플리카 셋의 각 멤버는 프라이머리가 없다고 판단하고 즉시 새로운 프라이머리를 선출하기 위한 투표를 시작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 아래 명령을 이용해 관리자가 의도적으로 기존의 프라이머리를 세컨드리로 내리는(Step Down)것도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘은 관리 작업을 위해 프라이머리를 세컨드리로 전환하기 위해 사용하는 명령이기도 하다.&lt;/p&gt;
&lt;pre id=&quot;code_1716188738233&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;-- 프라이머리를 스텝 다운
rs.stepDown(stepDownSecs, secondaryCatchUpPeriodSecs)

-- 레플리카 셋 멤버의 우선순위 변경
rs.reconfig()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;rs.stepDown()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프라이머리인 멤버에서만 실행할 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령이 실행되면 즉시 프라이머리를 내려놓고 stepDownSecs 파라미터에 지정된 시간 동안 다시 프라이머리가 될 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 다른 세컨드리 멤버가 프라이머리가 될 수 있는 여유 시간을 설정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 stepDownSecs 시간동안 다른 세컨드리 중에서 새로운 프라이머리가 선출되지 못하면 원래 프라이머리였던 멤버가 다시 프라이머리로 선출될 가능성도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리가 없는 시간을 최소화하면 할수록 사용자의 요청을 빨리 처리할 수 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리가 다운되는 시점에 다른 세컨드리가 기존 프라이머리의 OpLog 에서 모든 변경 사항을 가져왔다고 보장하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터변경이 많아 복제가 지연된 상태라면 밀린 복제를 동기화하기 위해 더 많은 시간이 필요할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 두번째 인자인 secondaryCatchUpPeriodSecs 파라미터 시간동안 새로운 프라이머리를 선출하지 않고 기다리면서 밀려있던 복제가 동기화되기를 기다린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 무조건 이 시간동안 미루지 않고 그 전에 동기화가 완료되면 새로운 프라이머리를 선출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;rs.reconifg()&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리케이션 역할을 변경하는 직접적인 명령은 아니지만, 레플리카 셋 멤버의 priority 를 변경하면 기존의 프라이머리가 즉시 세컨드리로 전환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 내부적으로 reconfig 나 stepDown 의 처리로직은 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점으는 reconfig 명령으로 롤이 변경될 때 stepDown(60, 10, true) 를 실행하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 인자 true 는 강제 모드를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reconfig 명령으로 Priority 를 변경하는 작업은 secondaryCatchUpPeriodSecs 를 무시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 reconfig 는 secondary 가 복제를 동기화할 시간을 주지 않고 프라이머리 선출이 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 데이터 변경이 빈번한 경우 데이터의 롤백이 발생할 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 3.6 버전에서 수정될 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;프라이머리 선출 시나리오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적인 상태의 레플리카 셋에서 각 세컨드리들은 프라이머리가 처리한 데이터 변경 내역을 복제하면서 데이터를 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 레플리카 셋의 각 멤버들은 서로 하트비트 메시지를 주고받으면서 정상적인 상태인지 주기적으로 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 3개의 멤버로 구성되어 있다면 한 주기에 6개의 하트비트 메시지가 오가고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7개의 멤버로 구성되어 잇으면 42개의 하트비트 메시지(7개 멤버가 6개의 하트비트 메시지를 전송)가 오고간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버는 다른 멤버로 전송한 하트비트 메시지에 대해서 지정된 시간(electionTimeoutMillis)동안 응답을 기다린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 시간동안 다른 세컨드리 멤버가 응답이 없으면 그 멤버가 응답 불능상태라고만 인지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 프라이머리 멤버가 응답이 없다면 즉시 새로운 투표를 시작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 프라이머리 멤버가 정상적으로 작동하고 있지만, 멤버간의 네트워크 이슈로 문제가 있을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 스플릿 브레인(Split-brain) 현상이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 에서는 스플릿 브레인 현상을 막기 위해서 전체의 과반수 멤버와 통신이 되지 않을 때 자동으로 프라이머리에서 세컨드리 멤버로 강등되게 설계되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 멤버가 서비스가 불가한 상태가 되면, 레플리카 셋에 남은 두 세컨드리는 둘 중 하나를 프라이머리로 선출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 Self-Election 이 발생하여 다른 세컨드리 멤버를 프라이머리 후보로 추천하지 않고 자기 자신이 바로 프라이머리 선출 투표를 게시하는데 이 때 후보는 반드시 자기 자신이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Self-Election 방식을 채택한 이유는 프라이머리 선출 과정이 복잡하지 않고 그만큼 쉽게 구연가능하며 직관적으로 작동하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Replica01 (R01) 과 Replica02 (R02) 간에 어떻게 선출되는지를 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. R01 은 R02 에게 '내가 이번 텀(Term) 의 프라이머리가 되고자 한다. 찬성하는가?' 의 메시지를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. R02 는 몇 가지 상태를 체크한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- R01 이 현재 나(R02) 와 같은 레플리카 셋에 소속된 멤버인지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- R01 의 우선수위가 현재 레플리카 셋에 있는 모든 멤버의 우선순위와 같거나 더 큰 값을 가지는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- R01 이 요청한 텀이 내(R02)가 지금까지 참여했던 투표의 텀보다 큰 값인지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- R01 이 요청한 텀에 내(R02)가 투표한 적이 없는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- R01 이 나(R02)보다 더 최신 데이터를 가지고 있거나 동등한 데이터를 가지고 있는지 ( OpLog 의 OpTime 시점 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. R02 에서 체크한 사항이 모두 TRUE 이면 R02 는 R01 에게 찬성 메시지를 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. R01 는 새로운 프라이머리가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 체크사항 중 하나라고 거짓이 있으면 R02 는 R01 에게 거부 의사를 표한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋의 멤버 중에서 과반수 이상의 멤버가 통신이 가능한 상태에서만 투표를 실시할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 멤버들 중 하나라도 거부하게 되면 프라이머리 선출은 실패하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;프라이머리 선출 시 정족수 의미&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 레플리카 셋의 각 멤버는 투표 옵션의 값으로 0 또는 1 을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0 인 멤버는 Non-Voting 멤버라고 표현하는데, Non-Voting 멤버는 프라이머리 선출 시 정족수를 판단하는 기준에 포함되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 레플리카 셋 멤버는 3개로 구성되어 있지만, 한 멤버의 votes 값이 0인 경우에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;votes 가 1인 멤버중 하나가 응답불능 상태가 되면 정족수를 채우지 못하기 때문에 자동으로 세컨드리로 스텝다운된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 운좋게 0 인 멤버가 응답 불능 상태가 된다면 프라이머리 역할을 계속 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 투표권을 가지고 있다고 해서 모든 멤버가 프라이머리 선출에 참여하지는 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 셋의 멤버는 데이터 복제의 동기화 상태에 따라 여러 상태를 가질 수 있는데, 아래 상태인 경우에만 투표에 참여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PRIMARY&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SECONDARY&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RECOVERING&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ARBITER&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ROLLBACK&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 멤버가 시작되면서 데이터를 가지지 않는 경우 초기 동기화를 수행하는데, 이 때 상태는 STARTUP 이며 투표권을 가진 멤버라 하더라도 실제 프라이머리 선출에는 참여할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 주의해야 할 점은 투표권을 가지지 않은 레플리카 멤버라 하더라도 프라이머리 선출 투표에서 거부권을 행사할 수는 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 후보 이외의 다른 멤버가 더 높은 우선순위를 가지고 이싿거나 더 최신의 데이터를 가지고 있다는 것을 알게되면 바로 프라이머리 선출을 취소한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 잘못된 멤버가 프라이머리로 선출되는 것을 방지하기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;롤백&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 롤백은 관계형 데이터베이스에서 트랜잭션 커밋의 반대표현인 롤백과는 전혀 다른 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더구나 MongoDB 에서는 트랜잭션이라는 개념이 없기 때문에 트랜잭션 롤백은 있을 수도 없는 기능이다. (작성시점의 버전은 3 으로 현재는 될듯)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 롤백은 레플리카 셋의 각 멤버끼리 데이터를 동기화하는 과정에서 이미 저장된 데이터를 다시 삭제하는 과정을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롤백은 언제 발생할 수 있고 어떻게 실행되는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용된 OpLog Identity 가 아래와 같다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;P01 1 2 3 4 5&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R01 1 2 3 4&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R02 1 2 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 멤버로 구성된 레플리카 셋이 복제지연으로 위와 같은 상태의 데이터를 가지고 있다고 해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R01 은 5 가 프라이머리로부터 동기화되지 않고 R02 는 1 2 3 까지만 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 갑자기 프라이머리 멤버가 네트워크 장애로 인해 연결불가상태가 되었다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 두 세컨드리 노드끼리 새로운 프라이머리를 선출하는 과정을 거치며 더 최신 데이터를 가진 R02 가 새로운 프라이머리로 선출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R02 가 새로운 프라이머리로 선출되었기 때문에 사용자 데이터 요청이 R02 에서 이루어지고, 자신의 OpLog 에 새로운 데이터 변경 이력을 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R01 1 2 3 4 6 7&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 프라이머리였던 P01 멤버가 정상상태가 되고 다시 레플리카 셋으로 참여하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참여 시 자신의 OpLog 와 새로운 프라이머리인 멤버와의 OpLog 를 동기화 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이전 프라이머리였던 멤버는 스위칭이 발새하기 전의 OpLog 마지막 시점을 R02 노드에 맞춰야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않고 새로운 프라이머리 멤버에서 새로 쌓이기 시작한 OpLog 를 가져오기만 하면 서로 데이터가 달라지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이전 프라이머리였던 멤버는 자신의 OpLog 를 마지막부터 시작해서 검색 비교작업을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 OpLog 를 시간 역순으로 정렬해서 일겅온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 역순의 OpLog 를 한 건씩 가져와서 새로운 프라이머리인 R02 멤버의 OpLog 에 있는지 없는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없으면 무시하고 다음 OpLog 로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 순간 공통되는 OpLog 를 찾으면 이전 프라이머리였던 멤버는 자기 자신의 OpLog 에서 공통으로 있는 OpLog 이후의 모든 OpLog 를 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 단순히 OpLog 만 삭제하는게 아니라 내용을 참조해 실제 컬렉션의 도큐먼트를 찾아서 같이 삭제하거나 변경 전 데이터로 되돌리는 작업을 진핸한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 OpLog 내용을 기반으로 이전 데이터 상태로 되돌리는 롤백작업이 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 롤백 과정을 거치면서 삭제되거나 변경된 도큐먼트들은 MongoDB 의 데이터 디렉터리의 rollback 이라는 경로에 보관된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 관리자나 개발자는 이 롤백 디렉터리의 데이터를 이용해서 필요한 재처리 작업을 수동으로 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 롤백은 최대 300MB 까지만 가능하다. 만약 세컨듸로 동기화되지 못한 데이터가 300MB 를 넘을 정도로 많은 상태에서 스텝 다운이 발생하면 이 레플리카 셋 멤버는 새로운 프라이머리 멤버와의 동기화 지점을 찾지 못하고 에러메시지를 로그에 출력하고 중간에 복구를 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 수동 동기화 작업을 해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1716192332287&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;replSEt syncThread: 13410 replSet too much data to roll back&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 이렇게 자동 롤백에 실패한 경우에는 복구해야 할 데이터가 rollback 경로에 기록되지 않기 때문에 복구를 위해서 전체 데이터 파일을 백업하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;롤백 데이터 재처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롤백 데이터는 MongoDB 서버의 데이터 디렉터리 하위에 rollback 이라는 이름으로 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 롤백으로 인해서 취소된 변경 데이터를 컬렉션 단위로 BSON 파일 형식으로 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기록은 bsondump 유틸리티를 이용해 JSON 포맷으로 변환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복제 아키텍처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간으로 변경되는 데이터는 세컨드리 멤버들이 프라이머리의 OpLog 를 가져온 다음 재생하면서 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리는 프라이머리로 접속해서 OpLog 를 복제할수도 있지만, 다른 세컨드리 멤버의 OpLog 를 재생할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 OpLog 의 재생은 실시간으로 변경되는 데이터에 동기화하는 것인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 동기화는 초기 동기화와 실시간 복제 두 단계로 나누어서 생각해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복제로그(OpLog) 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제용 로그를 MongoDB 에서는 OpLog 라고 하는데, 다른 DBMS 와는 달리 MongoDB 는 이 로그를 데이터베이스 서버의 oplog.rs 라는 이름의 컬렉션으로 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;oplog.rs 컬렉션은 다음과 같은 필드를 가지는데 모든 필드가 항상 존재하는 것은 아니며 필요한 경우에만 저장되는 필드도 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드명&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;ts(Timestamp)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OpLog 의 저장 순서를 결정하는 기준이 되는 필드&lt;br /&gt;&lt;br /&gt;이 필드는 2개의 값으로 구성되어 있는데 첫 번째 값은 초 단위의 Unix Epoch 을 표현하고, 두 번째 값은 동일시간에 발생된 이벤트의 논리 시간을 표현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;t(Primary Term)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;레플리카 셋의 프라이머리를 선출하는 투표가 실행될 때마다 증가하는 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;h(Hash)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OpLog 의 각 도큐먼트는 프라이머리 멤버에서 실행된 변경작업을 의미, 각각의 작업에는 OpLog 의 해시 값을 이용해서 식별자가 할당되는데 이 식별자가 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;v(Version)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OpLog 도큐먼트의 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;op(Operation Type)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;프라이머리 멤버에서 실행된 오퍼레이션 종류를 저장&lt;br /&gt;i : insert&lt;br /&gt;d: delete&lt;br /&gt;u : update&lt;br /&gt;c : command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;ns(Namespace)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;데이터가 변경된 대상 컬렉션의 네임스페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;o(Operation)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;op 필드에 저장된 오퍼레이션 타입별로 실제 변경된 정보가 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;o2(Operation 2)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;o 필드에는 변경될 값들을 저장하는데, u 인경우 변경될 대상 도큐먼트에 대한 정보가 필요하다.&lt;br /&gt;그래서 op 필드가 u 인경우 업데이트 될 대상 도큐먼트의 프라이머리 키인 _id 필드의 정보를 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 모든 컬렉션은 기본적으로프라이머리 키 역할을 하는 _id 필드가 같이 저장되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 OpLog 컬렉션은 특별한 형태의 컬렉션이기 때문에 _id 필드를 가지지 않고 별도의 인덱스도 가질 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 의 내용을 조회해보면 2개의 도큐먼트가 저장된 것을 확인할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 도큐먼트는 레플리카 셋이 초기화되었다는 내용이며 두 번째 도큐먼트는 현재 레플리카 멤버가 새로운 프라이머리가 되었다는 내용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 명령어는 저장 및 변경된 데이터를 확인해 보기 위한 것으로 OpLog 에 저장되는 변경 이력과는 아무런 관계가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;op 필드가 c 인 OpLog 도큐먼트는 test 데이터베이스에서 user 컬렉션이 생성했음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;o 필드에는 변경할 값을 가지고 있고 o2 필드는 변경할 도큐먼트를 찾기 위한 조건이 저장되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 OpLog 는 Cap 컬렉션으로 생성되는데, Cap 컬렉션의 경우 Tailable Cursor 를 이용하여 OpLog 에 기록되는 내용을 조회하면서 MongoDB 서버의 모든 데이터의 변경을 추적할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Tailable Cursor 를 이용하여 특정 컬렉션의 변경 내용을 검색엔진으로 전달해서 전문 검색이 가능하도록 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;local 데이터베이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 복제 로그는 oplog.rs 라는 컬렉션을 통해서 세컨드리 멤버로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 oplog.rs 컬렉션도 결국 데이터베이스에 존재하는 하나의 테이블에 속하는데, oplog.rs 컬렉션에 저장되는 INSERT 처리까지 세컨드리 멤버로 전달되어 버리면 이중으로 데이터가 전달되는 것이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 사용자가 도큐먼트 1건을 INSERT 하면 대상 컬렉션에 1건의 INSERT 를 수행하고, 세컨드리로 전달하기 위해 oplog.rs 컬렉션에도 1건의 INSERT 를 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 사용자 프라이머리 멤버는 사용자 컬렉션에 저장되는 도큐먼트는 oplog.rs 컬렉션에도 복사해서 저장하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;oplog.rs 컬렉션에 INSERT 되는 데이터는 중복해서 저장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 세컨드리 멤버는 프라이머리 멤버의 oplog.rs 컬렉션에 저장되는 데이터만 가져가므로 2건의 도큐먼트 INSERT 가 세컨드리로 전달되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 를 처음 시작하면 기본적으로 local 이라는 이름의 데이터베이스를 생성하는데, local 데이터베이스는 oplog.rs 를 포함해서 몇 개의 서버 자신을 위한 컬렉션을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 local 이라는 이름의 데이터베이스에 저장된 컬렉션의 변경 내용은 oplog.rs 에 기록하지 않기 때문에 세컨드리로 전달되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;local 데이터베이스가 시스템 데이터베이스이긴 하지만, 사용자가 특별한 목적을 위해 local 데이터베이스 내에 다른 컬렉션을 만들어서 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 복제가 필요하지 않는 데이터들은 local 데이터베이스를 활용할 수 있다. 그뿐만 아니라 local 데이터베이스의 컬렉션은 세컨드리 멤버도 INSERT, UPDATE, DELETE 를 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초기 동기화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버를 처음 설치하고 레플리카 셋에 투입하면 이미 투입되어 있던 멤버로부터 모든 데이터를 일괄로 가져오는데 이를 초기동기화 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 초기 동기화 작업은 레플리카 셋에 처음 추가되거나 기존에 투입되어 있던 멤버를 다시 시작하면서 레플리카 셋에 투입되면 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 투입되는 멤버의 데이터 디렉터리가 완전히 비어있는 경우에는 초기 동기화를 수행하고, 데이터 디렉터리에 이미 데이터가 있다면 초기 동기화 과정을 건너뛴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이미 데이터를 가지고 있는 MongoDB 서버를 복제의 새로운 멤버로 투입하는 것을 부트스트랩이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 3.2 시점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 초기동기화 작업은 단일 쓰레드로 실행되기에 상당한 시간이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 복제도 단일 쓰레드이지만 인덱스 생성도 단일 쓰레드로 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 초기 동기화 작업은 중간에 멈췄다가 다시 시작하는 경우 처음부터 다시 시작해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 동기화 작업을 멈추고 다시 시작하는 명령은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수동 초기 동기화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 정상적인 레플리카 멤버의 데이터 파일을 그대로 복사해서 새로운 멤버의 데이터 디렉터리로 복사하는 수동 초기 동기화 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 수동으로 복사하고 초기동기화를 수행하는 방식을 부투스트랩이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 데이터 파일을 물리적으로 복사하려면 기존 멤버의 MongoDB 서버를 종료하고 데이터가 변경되지 않는 상태에서 복사해야 한다. 따라서 데이터 파일을 복사하는 동안 일시적으로 레플리카 셋의 멤버가 통신할 수 없는 상태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 파일을 수동으로 복사하는 방법으로 동기화를 수행하는 경우 반드시 레플리카 셋의 멤버 중 최소 하나이상의 멤버는 백업된 시점의 OpLog 를 가지고 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자동 초기 동기화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버가 자동으로 다른 멤버로부터 데이터베이스를 복사하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 관리자가 복사하는 것이 아니기 때문에 관리자 운영을 필요로 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동동기화는 다음과 같은 과정을 거쳐 다른 멤버가 가진 데이터를 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 데이터베이스 복제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 추가되는 멤버는 레플리카 셋의 특정 멤버를 복제소스로 선택하고 그 멤버에 접속하여 모든 데이터베이스의 모든 컬렉션을 읽어온 다음 자신의 데이터베이스 컬렉션에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 프라이머리 인덱스(_id) 만 생성한 다음 복사를 실행한다. 인덱스가 많을수록 INSERT 성능이 느려지므로 데이터베이스를 복제하는 시간이 오래 걸리게 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. OpLog 를 이용한 일시적인 데이터 동기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 복제하는 과정은 오랜 시간이 필요하기에, 복제기간동안 다른 멤버에서 추가되어 밀린 OpLog 를 재 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 인덱스 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 동기화가 완료되면 필요한 모든 인덱스를 생성하는 작업을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실시간 복제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 멤버는 사용자의 요청을 처리하고 그 내용을 다시 OpLog 에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버는 초기 동기화가 완료되면 프라이머리나 다른 더 최신의 변경 정보를 가지고 있는 세컨드리 멤버로 연결한 다음 OpLog 를 가져와서 재생하고, 자기 자신의 OpLog 에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 동기화되는 것을 최종 일관성이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;복제 아키텍처&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버가 처리한 모든 데이터 변경 내용은 Cap 컬렉션 구조의 oplog.rs 컬렉션ㅇ 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cap 컬렉션은 일반적인 컬렉션(테이블)과는 달리 큐(Queue) 형태로 데이터가 저장되며, 데이터를 조회하는 작업 또한 큐와 동일한 형태로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cap 컬렉션의 가장 큰 특징 중 하나는 '테일러블 커서' 기능인데, 로그 파일에 추가되는 내용을 실시간으로 보여주는 tail 명령어와 비슷한 형태로 작동하는 커서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cap 컬렉션에서만 사용할 수 있는 커서로 oplog.rs 컬렉션에 대해 커서를 생성하면 oplog.rs 컬렉션에 데이터가 추가될 때마다 커서를 통해서 최신 데이터를 보내주는 형태로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버의 OpLog 수집을 위한 백그라운드 쓰레드(Observer)는 복제소스로부터 OpLog 를 가져와서 로컬 MongoDB 의 메모리 큐에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵저버는 동기화 대상 멤버로부터 OpLog 를 가져와서 큐에 다믄ㄴ 역할만 수행한다. 이 큐는 최대 256MB 를 넘길 수 없고 큐에 쌓인 OpLog 를 OpLog 적용쓰레드가 빠르게 가져가지 못하면 옵저버 쓰레드는 큐에 여유공간이 생길 때까지 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;래플리케이션 배치 쓰레드는 큐에서 일정 개수의 OpLog 를 가져와서 OpLog 적용쓰레드 개수에 맞게 작업량을 나눈 다음 OpLog 적용 쓰레드들에게 작업을 요청한다. 기본적으로 적용 쓰레드는 16개를 사용한다. 그리고 OpLog 적용 쓰레드는 각각 5,000 개 정도의 OpLog 아이템을 담을 수 있는 자체 캐시 메모리가 있으며, OpLog 아이템의 도큐먼트 크기가 너무 클 경우 과다한 메모리 사용을 막기 위해 각 OpLog 적용 쓰레드는 최대 512MB 의 메모리만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 16개의 OpLog 적용 쓰레드 전체적으로 최대 8GB 의 캐시 메모리를 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 개수를 조절하고 싶은경우 설정파일에 setParameter 로 적용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1716197994285&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setParameter:
    replWriterThreadCount: 8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리와 세컨드리의 동기화 정보 교환 과정을 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리 멤버는 모든 멤버의 복제 상태(각 멤버가 OpLog 어디까지 동기화했는지) 를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리는 OpLog 6 까지 생성되었고, 세컨드리는 OpLog 4 까지 복제된 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 세컨드리 멤버의 복제 쓰레드는 프라이머리 멤버의 OpLog 에 대해서 Tailable Cursor 를 생성하여 프라이머리의 OpLog 에 새로운 데이터가 기록되었는지 모니터링 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tailable Cursor 는 프라이머리의 oplog.rs 컬렉션에 5, 6 OpLog 이벤트가 기록된 것을 확인하고 세컨드리의 복제 쓰레드로 결과를 내려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리의 복제 쓰레디는 큐에 프라이머리로부터 가져온 OpLog 를 저장하고 다시 프라이머리의 OpLog 이벤트를 대기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐에 OpLog 이벤트가 저장되면 레플리케이션 배치 쓰레드는 큐에서 OpLog 이벤트를 가져와서 OpLog 적용쓰레드들에게 적절하게 작업을 분산시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버가 OpLog 이벤트를 가져왔지만 프라이머리 복제상태 정보에는 세컨드리의 복제 동기화 상태가 여전히 4 값을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpLog 적용 쓰레드는 각자 자기 자신에게 부여된 OpLog 이벤트를 서로 간섭없이 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 OpLog 적용 쓰레드가 실제 작업을 처리하기 직전에 MongoDB 사용자의 쿼리 요청을 처리하지 못하도록 글로벌 잠끔 쓰기를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 OpLog 적용 쓰레드가 할당받은 OpLog 이벤트를 모두 처리하면 글로벌 쓰기 잠금을 해제하여 사용자의 쿼리 요청을 처리할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버에서 이렇게 OpLog 가 재생될 때마다 글로벌 쓰기잠금을 걸면 이 순간동안은 읽기 쿼리가 처리되지 못하고 잠금 해제를 기다리게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정단위의 OpLog 재생이 완료되면 세컨드리는 즉시 자기 자신이 OpLog 를 어디까지 처리했는지 프라이머리멤버에 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리는 세컨드리 멤버의 OpLog 동기화위치 정보를 복제상태 정보에 업데이트해서 세컨드리 멤버들의 동기화정보를 최신상태로 갱신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한 싸이클이 지나면 다시 프라이머리의 복제 OpLog 를 tailable Cursor 로 모니터링하다가 새롭게 저장된 OpLog 를 세컨드리의 큐로 가져오고 처리하는 작업을 반복하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세컨드리 멤버의 읽기 일관성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버에서 OpLog 를 재생하는 시점에 이렇게 사용자의 쿼리를 블록킹하는 이유는 세컨드리의 읽기쿼리 결과가 프라이머리에서 나타날 수 없는 상태를 세컨드리 멤버에서 보여주지 않기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 프라이머리에서는 멀티 쓰레드로 사용자의 데이터 변경이 적용되고 적용된 순서대로 OpLog 에 숱아적으로 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 세컨드리 멤버의 복제 쓰레드는 프라이머리로부터 OpLog 를 가져와서 다시 멀티쓰레드로 실행하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리에서 OpLog 가 재생되는 순서와 프라이머리에서 실행된 데이터의 변경요청 처리 순서를 동일하게 유지하는 것이 불가능하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 에서는 세컨드리 멤버가 OpLog 를 재생할 때 OpLog 이벤트를 특정 단위로 묶어서 한번에 재생하는데, 이 순간에는 글로벌 쓰기 잠금을 이용해서 사용자 쿼리 요청을 중단하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 OpLog 의 재생이 완료되고 잠금이 해제되면 최소한 이 시점에 프라이머리와 동일한 상태의 데이터를 보여주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 잠금이 해제되는 시점에는 이미 세컨드리의 OpLog 재생이 완료된 시점이므로 프라이머리에서와 동일한 상태의 데이터를 보여줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 MongoDB 세컨드리가 OpLog 를 적용할 때 글로벌 잠금을 걸지 않았다면 OpLog 1번과 OpLog 3번만 적용된 상태에서도 사용자가 읽을 수 있게 된다. 하지만 이 상태를 프라이머리에서는 나타날 수 없는 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 서버는 이런 문제를 원천적으로 차단하기 위해서 OpLog 를 청크 단위로 나눠서 각 청크를 멀티 쓰레드로 실행하며 그동안은 글로벌 잠금을 걸어서 사용자가 읽어가지 못하게 하는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리 멤버에서 OpLog 재생을 위해 글로벌 잠금을 거는 횟수와 시간은 db.serverStatus() 명령의 결과로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;serverStatus 결과에서 다음 2개 필드의 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metrics.repl.apply.batches.num : 세컨드리 멤버에서 OpLog 재생은 몇 개의 OpLog 를 소그룹으로 나눠서 실행하는데, 소그룹의 OpLog 재생 프로세스가 실행된 횟수 (즉, 글로벌 잠금이 걸린 횟수 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emtrics.repl.apply.batches.totalMillis : OpLog 재생 프로세스가 실행되는데 소요된 시간의 합 (글로벌 잠금이 걸렸다가 풀린 시간의 합계 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/636</guid>
      <comments>https://mozi.tistory.com/636#entry636comment</comments>
      <pubDate>Mon, 20 May 2024 07:19:45 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] WiredTiger 의 내부 작동방식</title>
      <link>https://mozi.tistory.com/635</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r8mP7/btsHnwXH88H/eY6mdNXUFmCPwM2o2YUHOk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r8mP7/btsHnwXH88H/eY6mdNXUFmCPwM2o2YUHOk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r8mP7/btsHnwXH88H/eY6mdNXUFmCPwM2o2YUHOk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr8mP7%2FbtsHnwXH88H%2FeY6mdNXUFmCPwM2o2YUHOk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WiredTiger 엔진&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진은 트랜잭션을 지원하는 임베디드 데이터베이스 엔진으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진은 내장된 캐시를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 캐시는 디스크의 인덱스나 데이터 파일을 메모리에 캐시하여 빠르게 쿼리를 처리하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 변경을 모아 한번에 디스크로 기록하는 쓰기 배치 기능을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 쿼리를 실행하면 WiredTiger 스토리지 엔진은 블록매니저(Block Manager)를 통해서 필요한 데이터 블록을 디스크에서 읽어서 공유 캐시에 적재하여 쿼리를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 사용자가 데이터를 변경하면 WiredTiger 스토리지 엔진은 트랜잭션을 시작하고 커서를 이용해 원하는 도큐먼트의 내용을 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트의 변경 내용은 먼저 공유 캐시에 적용되는데, 변경된 데이터가 디스크에 기록되는 과정을 기다리지 않고 저널 로그에 기록한 다음 처리 결과를 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 공유 캐시가 어느정도 쌓이면 체크포인트를 발생시켜 공유 캐시의 더티 페이지를 모아 디스크에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 요청 쿼리가 실행되면서 블록매니저는 디스크의 새로운 데이터 페이지를 계속 캐시로 읽어드리는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 엔진의 공유 캐시는 사용자가 설정한 크기의 메모리 내에서 처리가 수행되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 캐시에 더이상 데이터 페이지를 읽어들일 공간이 없으면 사용자 쿼리를 처리할 수 없으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eviction 모듈은 공유 캐시가 적절한 메모리 사용량을 유지하도록 공유캐시에서 자주 사용되지 않은 데이터 페이지를 제거하는 작업을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 는 블록사이즈를 가변적으로 사용하는데 이는 압축 기능을 편리하게 만들어 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 RDBMS 와 같은 고정사이즈인경우 만약 16KB 데이터 페이지를 압축하면 결과의 크기는 모두 가변적인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 다시 고정된 크기의 데이터 블록에 저장해야 하므로 오히려 압축 효율이 떨어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 가변 크기의 블록을 사용하는 WiredTiger 는 데이터 파일의 압축이 선택이 아니라 기본 옵션처럼 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진의 블록매니저는 변경된 블록을 기록할 때, 프레그멘테이션을 최소화하면서 기록되는 데이터 블록의 크기에 최적인 위치를 찾아서 저장한다. 그 뿐만 아니라 데이터 블록의 압축과 암호화 등과 같은 기능을 내장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저널 파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 DBMS 와 동일하게 B-Tree 구조의 데이터 파일과 비정상 종료로부터 데이터를 복구하기 위한 WAL 을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 다른 DBMS 처럼 WAL 이 로테이션되면서 재사용되지 않고 새로운 로그파일이 계속 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 체크포인트 시점 이전의 저널 로그는 더이상 사용되지 않으므로 자동으로 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 엔진은 3~10 개 정도의 로그 파일을 미리 만들어 두고, 기존 저널 로그 파일을 다 사용하면 미리 만들어 둔 로그 파일의 이름을 뒷 숫자로 변경하여 트랜잭션 로그를 기록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715583045975&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ll
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000001
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000002
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000003
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000004
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000005
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000006
..

파일이 다 사용되면
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000001
-&amp;gt;
-rw-r--r-- 1 root root 3005 2024-05-10 10:00 100M WiredTigerLog.000000007&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;저널파일 설정 방법&lt;/h3&gt;
&lt;pre id=&quot;code_1715583599959&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;storage:
    journal:
        enabled: true
    engine: wiredTiger
    wiredTiger:
        engineConfig:
            cacheSizeGB: 10
            configString: &quot;log=(archive=true,enabled=true,file_max=100MB,path=/MongoDB/journal/)&quot;
        collectionConfig: snappy&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;enabled&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;저널 로그를 활성화할 것인지 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;archive&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;체크포인트 이전의 저널 로그는 자동으로 삭제하는데, 이렇게 삭제된 저널 로그를 아카이빙하여 다른 용도로 사용하고자 하는 경우에는 archive 옵션을 true 로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;file_max&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;저러 파일의 최대 크기를 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;path&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;저널 로그의 디렉터리 경로를 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공유캐시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 엔진에서 사용자의 쿼리는 공유 캐시를 거치지 않고 처리할 수 없으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 하나의 쿼리를 처리하기 위해 공유 캐시의 데이터 페이지를 참조해야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 공유캐시 사용량이 매우 중요하고 이를 보여주는 명령어가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 WiredTiger 엔진처리가 원할하지 못한 경우 공유 캐시 사용량 그래프에 변화를 보이는 경우가 많다.&lt;/p&gt;
&lt;pre id=&quot;code_1715584964668&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Total cache bytes
db.serverStatus().wiredTiger.cache.&quot;maximum bytes configured&quot;

-- Useed cache bytes
db.serverStatus().wiredTiger.cache.&quot;bytes currently in the cache&quot;

-- Dirty cache bytes
db.serverStatus().wiredTiger.cache.&quot;tracked dirty bytes in the cache&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 버전 내장된 WiredTiger 엔진의 공유 캐시 크기는 장착된 메모리의 60%-1GB 이며, 만약 이 값이 1GB 보다 적으면 1GB 로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4 버전부터는 메모리의 50%-1GB 이며, 만약 이 값이 256MB 보다 작다면 256MB 로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 공유 캐시의 크기를 변경하고자 한다면 다음과 같이 설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재시작은 필요하지 않지만, 공유 캐시 크기 조정 시 많은 내부 동시 처리 작업을 멈추고 크기를 변경하므로 부하가 작을 때 해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715585114486&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wiredTiger:
    engineConfig:
        cacheSizeGB: 20&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 엔진은 디스크의 데이터 페이지를 공유 캐시 메모리에 적재하면서 메모리에 적합한 트리 형태로 재구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 캐시에서 적재된 페이지를 찾아가는 과정은 모두 별도의 맵핑 과정 없이 메모리 주소(Pointer)를 이용해서 바로 검색할 수 있기 때문에 맵핑 테이블의 경합이나 오버헤드가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진은 디스크에 저장되는 데이터 페이지의 레코드 인덱스를 별도로 관리하지 않고 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 데이터 페이지를 공유캐시 메모리에 적재할 때 레코드 인덱스를 새롭게 생성해서 메모리에 적재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 메모리로 적재하는 과정에서 WiredTiger 엔진은 여러 가지 변환 과정을 거치기 때문에 데이터 페이지를 디스크에서 공유캐시로 읽어 들이는 과정이 기준 RDBMS 보다는 느리게 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 한번 공유 캐시 메모리에 적재된 데이터 페이지에서 필요한 레코드를 검색하고 변경하는 작업은 기존의 RDBMS 보다 훨씬 빠르고 효율적으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잠금경합 알고리즘 Lock-Free&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 엔진은 공유 캐시의 잠금경합을 최소화하기 위해서 Lock-Free 알고리즘을 채용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lock-Free 알고리즘은 잠금을 전혀 사용하지 않는 시스템을 의미하는 것이 아니라 잠금 경합을 최소화하는 알고리즘을 의미하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 '하자드 포인터'와 '스킵 리스트' 자료구조를 활용하여 Lock-Free 콘셉트를 구현하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하자드 포인터&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 쓰레드는 사용자의 쿼리를 처리하기 위해 WiredTiger 캐시를 참조하는 스레드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이빅션 쓰레드는 캐시가 다른 데이터페이지를 읽어들일 수 있도록 빈 공간을 만들어주는 역할을 담당하는 스레드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 쓰레드는 캐시의 데이터 페이지를 참조할 때 먼저 하자드 포인터에 자신이 참조하는 페이지를 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 쓰레드가 쿼리를 처리하는 동시에 이빅션 쓰레드는 캐시에서 제거해야 할 데이터 페이지를 골라서 캐시에서 삭제하는 작업을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 이빅션 쓰레드는 제거해도 될만한 페이지를 골라서 먼저 하자드포인트에 등록되 있는지 확인 후 없으면 캐시에서 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 기술덕분에 다양한 쓰레드들이 캐시의 데이터 페이지들에 대해서 잠금 대기 없이 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하자드 포인터의 최대 개수는 1000 개이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 포인터의 개수가 부족하여 엔진의 처리량이 느려진다면 hazard_max 의 설정값을 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스킵 리스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킵 리스트는 링크드 리스트를 변환한 리스트 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 8번의 노드 검색을 거쳐야 하는 작업을 스킵리스트를 하면 4번만 거칠 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 검색 성능이 단순 링크드인경우 O(N) 인 반면 스킵리스트는 O(log(n)) 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킵 리스트는 B-Tree 에 비해 검색 성능이 조금 떨어지지만 구현이 간단하고 메모리 공간도 많이 필요하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그뿐만 아니라 새로운 노드를 추가하기 위해서 별도의 잠금을 필요로 하지 않고 검색 또한 잠금을 필요로 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킵 리스트의 노드 삭제는 잠금을 필요로 하지만 B-Tree 자료 구조보다는 잠금을 덜 필요로 하기에 큰 성능 저하 이슈도 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 쓰레드가 동시에 하나의 스킵 리스트에 노드를 저장하거나 검색해도 서로 잠금 경합을 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WiredTiger 의 언두관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 도 변경되기 전 레코드를 별도의 저장 공간(언두 로그, Undo Log)에 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 언두 로그를 관리하는 이유는 트랜잭션이 롤백될 때 기존 데이터를 복구하기 위함인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 RDBMS 에서는 언드 로그를 잠금 없는 데이터 읽기(MVCC) 용도로도 같이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 는 언두 로그를 스킵 리스트로 관리하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 독특하게 데이터페이지의 레코드를 직접 변경하지 않고 변경 이후의 데이터를 스킵 리스트에 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 는 데이터가 변경되어도 디스크에서 읽어 들인 데이터 페이지에 변경된 내용을 직접 기록하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경된 데이터 내용을 스킵 리스트에 차곡차곡 기록해 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 사용자 쿼리가 데이터를 읽을 때에는 변경 이력이 저장된 스킵 리스트를 검색해서 원하는 시점의 데이터를 가져간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 직접 데이터페이지에 쓰지 않고 별도의 리스트로 관리하는 이유는 쓰기 처리를 빠르게 하기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 RDBMS 에서 데이터가 변경되면 기존의 레코드보다 데이터의 크기가 더 커져서 데이터 페이지 내에서 레코드의 위치를 옮겨야 할 수 있다. 이런 과정을 처리하는 동안 사용자가 기다려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진에서는 변경되는 내용을 스킵 리스트에 추가하기만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 추가하는 작업은 매우 빠르게 처리되므로 사용자의 응답 시간도 훨씬 빨라지고 여러 쓰레드가 하나의 페이지를 동시에 읽거나 쓸 수 있어 동시성능 처리가 향상된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;캐시 이빅션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 캐시를 위해서 지정된 크기의 메모리 공간만 사용해야 하는데, 이를 위해서 빈 공간을 적절하게 유지해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 데이터 페이지를 디스크에서 가져오지 못하기 때문에 쿼리의 응답속도가 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이빅션은 백그라운드 쓰레드로 실행되는데, 캐시에서 자주 사용되지 않는 데이터 페이지 위주로 캐시에서 제거하는 작업을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 최근 SSD 와 같이 매우 빠르게 읽고 쓸수있는 저장장치가 나오면서, 한번에 읽어들일 수 있는 데이터 페이지 수가 많아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만큼 빠르게 캐시에서 데이터페이지가 제거되어야 하는데, 가끔 이빅션이 지우는 속도가 캐시로 읽어들이는 속도를 따라가지 못해 캐시의 사용량이 급증하는 경우도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 현상은 3.2 버전대에서 자주 발생했는데 3.4 버전 이후로는 많이 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 백그라운드에서 수행되는 이빅션 쓰레드가 캐시 여유공간을 만들어 내지 못하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포그라운드 쓰레드에서 직접 캐시 이빅션을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 쿼리를 처리해야 할 쓰레드들이 캐시 이빅션까지 처리해야 하기 때문에 쿼리 성능이 현저하게 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715591188020&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Evicted by Application Thread
db.serverStatus().wiredTiger.cache.&quot;pages evicted by application threads&quot;

-- Evicted by Worker Thread
db.serverStatus().wiredTiger.cache.&quot;eviction worker thread evicting pages&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 정상적인&amp;nbsp; 상황이라면 By App 의 수치는 0에 가깝고 By Worker 그래프의 수치만 보여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이빅션 모듈의 작동 방식을 튜닝할 수 있는 옵션이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;threads_max&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;데이터 페이지를 제거하는 이빅션 쓰레드를 최대 몇 개까지 사용할지 설정. 1~20개까지 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;threads_min&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;데이터 페이지를 제거하는 이빅션 쓰레드를 최소 몇 개부터 사용할지 설정. 1~20개까지 가능&lt;br /&gt;&lt;br /&gt;처음에는 min 값으로 시작되고 더 빨리 제거가 필요한 경우 max 까지 증가함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;eviction_dirty_target&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;더티 페이지의 비율이 설정한 비율을 넘지 않도록 유지. 기본값은 80%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;eviction_target&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;공유 캐시에서 데이터페이지의 비율이 eviction_dirty_target 에 설정한 비율을 넘지 않도록 유지 ( * 더티페이지가 아님 )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;eviction_trigger&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;전체 공유 캐시 크기 대비 데이터 페이지의 사용률이 eviction_trigger 을 넘어서면 사용자 쓰레드의 이빅션을 시작&lt;br /&gt;&lt;br /&gt;이빅션 쓰레드(백그라운드)의 페이지 제거작업은 무관하게 항상 작동&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715591470261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;storage:
   engine:wiredTiger
   wiredTiger:
       engineConfig:
           cacheSizeGB: 10
               configString: &quot;eviction=(threads_max=10,threads_min=1),eviction_dirty_target=80)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;체크포인트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋된 트랜잭션의 영속성을 보장하기 위해 트랜잭션 로그를 먼저 기록하고, 데이터파일에 기록하는 작업은 사용자의 트랜잭션과 관계없이 뒤로 미뤄서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크포인트는 데이터 파일과 트랜잭션 로그가 동기화되는 시점을 의미하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크포인트는 주기적으로 실행되는데 체크포인트가 실행되어야만 오래된 트랜잭션 로그를 삭제하거나 새로운 트랜잭션 로그로 덮어쓸 수 있게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크포인트는 DBMS 서버가 크래시되거나 응답 불능으로 인해 비정상 종료 후 재시작될 때 복구를 시작할 시점을 결정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 체크포인트의 간격이 너무 길면 복구시간이 길어지게 되고, 너무 빈번하게 발생하면 서버가 쿼리를 처리하는 능력이 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 는 Fuzzy&amp;nbsp; 체크포인트 방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fuzzy 체크포인트는 조금 오래전 시점에 발생했던 트랜잭션을 체크포인트 기준점으로 선택하는 방식을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식은 문제발생시 복구시간이 길어지지만, 체크포인트 시점에 과다한 디스크 쓰기를 피할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 WiredTiger 스토리지 엔진은 '샤프 체크포인트' 방식을 채택하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤크 체크포인트는 평상시에는 디스크 쓰기가 별로 많지 않지만 체크포인트가 실행되는 시점에 한번에 모아서 더티 페이지를 기록하는 패턴을 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 한번씩 디스크가 튀는 패턴을 그리는데 아직 체크포인트 시점에 쓰기 양을 제어할 수 있는 옵션은 제공되고 있지 않다. (MongoDB 7버전에서도?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;체크포인트 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 엔진의 체크포인트는 RDBMS 와는 조금 다르게 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간브랜치 노드와 리프노드의 데이터가 변경되었고, 특정 중간브랜치 노드 하위에 새로운 페이지가 추가되었다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 체크포인트가 발생하면 리프노드만 먼저 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 생성된 페이지는 데이터 파일 내에 새로운 페이지 공간을 할당받아서 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 기존에 있던 페이지 내용이 변경된 경우에도 기존의 데이터페이지를 덮어쓰지 않고 새로운 페이지 공간을 할당받아 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 리프 페이지의 데이터 파일의 저장이 완료되면 변경된 B-Tree 의 브랜치 노드를 데이터 파일에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경되지 않은 리프노드 페이지는 새로 생성된 브랜치 노드에서 바라보도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 브랜치 노드가 모두 디스크에 기록되면 마지막으로 새로 만들어진 루트노드를 디스크에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업이 완료되면 최종적으로 하나의 컬렉션에 대해 두 개의 B-Tree 가 남게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 데이터의 디스크 기록이 완료되면 엔진은 메타 데이터가 새로운 루트 노드를 가리키도록 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타 정보가 가리키는 루트노드가 변경되면 이때부터 모든 사용자가 새로운 B-Tree 에서 데이터를 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 쓸모없어진 기존 B-Tree 는 삭제하고 빈 공간으로 반납된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 때 새로생성된 B-Tree 에서 바라보는 리프노드들은 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 과정으로 기존 B-Tree 를 덮어쓰지 않기 때문에 어떤 상태로 크래시되더라도 트랜잭션 로그의 복구 과정 없이 마지막 체크포인트의 데이터 상태를 유지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 공간 활용에서 보면 조금 불리한 부분도 있다. 모든 페이지가 변경되었다고 가정하면 완전히 새로운 B-Tree 를 만들어야 하기 때문에 기존 데이터 * 2배가 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;체크포인트 제어 옵션&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;log_size&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;얼마나 자주 체크포인트를 실행할지 결정하는 값으로 설정값만큼 트랜잭션 로그 쓰기가 발생하면 체크포인트가 발생&lt;br /&gt;기본값은 0으로 엔진이 체크포인트 시점을 결정하도록 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;wait&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설정된 시간초동안 대기했다가 주기적으로 체크포인트를 실행&lt;br /&gt;기본값은 0으로 엔진이 체크포인트 시점을 결정하도록 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;name&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;체크포인트 이름을 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715594799294&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;storage:
   engine:wiredTiger
   wiredTiger:
       engineConfig:
           cacheSizeGB: 10
               configString: &quot;checkpoint=(log_size=2G,wait=60)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 로그 사이즈가 2GB 를 넘거나 체크포인 시점으로부터 60초가 넘으면 체크포인트가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복제노드의 체크포인트&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.2 버전부터 레클리카 셋의 데이터 일관성과 빠른 페일오버를 위해 세컨드리의 데이터가 디스크에 영구적으로 보관되도록 강제하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 WriedTiger 스토리지 엔진의 저널 로그가 활성화되지 않은 경우에는 데이터의 영구적인 보관을 위해 변경된 데이터를 항상 파일에 동기화해두어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 센컨드리 멤버가 저널 로그가 없는 경우에는 계속해서 체크포인트를 실행하도록 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로인해 체크포인트가 끊임없이 발생하고 디스크 쓰기가 계속해서 많이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 writeConcern 이 {w:2, j:true} 이상으로 설정되면 복제의 각 멤버는 저널로그가 없기 때문에 동기화를 달성할 수 없어 오류가 발생하는데 이 에러를 막기 위해 writeConcernMajorityJournalDefault 옵션을 false 로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 노드는 매우 빈번한 체크포인트가 수행되므로 storage.journal.commitIntervalMs 옵션에 설정된 시간과 무관하게 매우 빈번한 디스크 쓰기가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MVCC&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVCC(Multi Version Concurrency Control) 는 하나의 도큐먼트에 대해 여러 개의 버전을 동시에 관리하면서 필요에 따라 적절한 버전을 사용할 수 있게 해주는 기술이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 변경되면 변경 된 데이터는 '스킵 리스트' 에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근의 변경은 스킵 리스트의 앞쪽으로 정렬하여 최근의 데이터를 더 빠르게 검색할 수 있도록 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 새로운 버전을 데이터를 계속 리스트에 추가하기만 하면 상당히 많은 메모리가 추가로 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔진은 변경 이력이 늘어나 memory_page_max 설정값보다 큰 메모리를 사용하는 페이지를 찾아서 자동으로 디스크에 기록하는 작업을 수행한다. 이 때 리컨실리에이션(Reconciliation) 과정을 거치며 원래의 데이터 페이지 내용과 변경된 내용이 병합되어 디스크에 기록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 몇 개의 트랜잭션 ID 로 인해 하나의 데이터가 여러번 변경된 경우,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회해야 하는 트랜잭션 ID 가 변경될 때의 트랜잭션 ID 사이의 값을 조회하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 검색을 실행하는 커넥션은 자신의 트랜잭션 번호보다 낮은 트랜잭션이 변경한 마지막 데이터만 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 REPEATABLE-READ 격리 수준과 동일한 처리 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 엔진은 READ_UNCOMMITTED, READ_COMMITTED, SNAPSHOT 3가지 격리 수준을 제공하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 격리엔진 수준이 SNAPSHOT 이며 REPEATABLE-READ 와 동일한 격리 수준을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 블록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 는 데이터를 저장하기 위해 고정된 크기의 블록을 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 하나의 페이지가 너무 커지는 것을 방지하기 위해 최대 크기 제한은 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 가변적인 크기덕분에 WiredTiger 의 B-Tree 의 브랜치 노드와 리프 노드의 크기를 다르게 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 다르게 설정하는 이유는 브랜치노드는 리프 노드를 구분하는 인덱스 키만 가지기 때문에 저장하는 데이터가 많지 않아도 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 WiredTiger 의 자체적인 기본값은 브랜치 4KB, 리프 32KB 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 컬렉션의 데이터 페이지와 인덱스 페이지의 크기를 다르게 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1715597101273&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;storage:
   engine:wiredTiger
   wiredTiger:
       engineConfig:
           cacheSizeGB: 10
       collectionConfig:
           configString: &quot;internal_page_max=4K,leaf_page_max=64K&quot;
       indexConfig:
           configString: &quot;internal_page_max=4K,leaf_page_max=16K&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량의 insert 와 분석, 배치작업을 위한 대량 조회가 주로 이루어진다면 데이터페이지를 크게 가져가는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 OLTP 성으로 운영되는 환경에서는 가능하면 페이지 크기를 작게 설정하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량의 경우에도 인덱스페이지는 기본값이 적절한데, 이는 데이터가 적재될 때 랜덤 액세스로 인한 정렬비용 그리고 B-Tree 인덱스 모든 페이지들이 메모리에 적재, 읽고 쓰는 작업이 많아지며 캐시 부하등이 발생하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영체제 페이징 캐시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 는 내장된 공유 캐시를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 MongoDB 에 내장된 WiredTiger 스토리지 엔진은 운영체제 캐시를 경유하는 Cached IO 를 기본옵션으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은 WiredTiger 스토리지 엔진에 참조하고자 하는 데이터 페이지는 리눅스 커널이 먼저 디스크에서 읽어서 자신의 페이지에 캐싱하고, 리눅스 페이지캐시에 있는 데이터를 다시 자신의 내장 캐시에 복사하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 참조하고자 하는 데이터는 리눅스 페이지 캐시와 WiredTiger 스토리지 엔진 두 곳에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 더블 버퍼링(Double Buffering) 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 DBMS 에서 이런 더블 버퍼링 문제를 해결하기 위해 Direct IO 방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Direct IO 를 사용하면 더블버퍼링 문제도 없어지며 리눅스서버 캐시 사용량도 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리눅스 커널의 메모리 사용으로 인한 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스는 먼저 물리적으로 장착된 전체 메모리에서 커널에 필요한 메모리 공간을 예약한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남는 메모리 공간에서 응용 프로그램이 요구할 때마다 조금씩 할당해주는 방식으로 메모리를 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데도 남은 미사용 공간이 있다면 이 공간을 디스크의 데이터파일 캐시 용도로 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 사용되는 메모리 공간을 리눅스 페이지 캐시라고 하며, 다양한 형태의 디스크 읽고 쓰기 작업의 버퍼링을 담당하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 미사용 공간의 크기가 줄어들면 페이지 캐시로 사용되고 있는 공간을 반납받아 미사용 공간으로 확보한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 리눅스 서버가 페이지 캐시공간을 디스크로 스왑아웃 시켜 갑자기 느려지는 경우가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WiredTiger 컨셉충돌&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스의 페이지 캐시를 사용하지 않으려면 Direct IO 를 사용해야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진은 Direct IO 를 사용할 수 있도록 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Direct IO 를 사용하려면 데이터 읽고 쓰기 작업이 4KB 크기에 맞춰줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 가변크기 페이지를 포기해야 하는데, MongoDB 서버에서 검증되지 않은 디스크 읽고 쓰기 방식이고 변경하는 것을 권장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Direct IO 읽고 쓰기 모드에서는 데이터페이지 크기가 정확하게 디스크 블록의 크기와 일치하지 않으면 데이터가 손실되거나 오히려 성능 역효과가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리눅스 페이지 캐시 Write-back 모드 작동의 위험성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스의 페이지 캐시가 Write-back 모드로 작동하는 것도 위험하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cached IO 를 사용하는 WritedTiger 스토리지 엔진의 디스크 데이터 쓰기 과정을 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진에서 데이터 쓰기를 실행하면 리눅스 서버는 자신의 페이지 캐시에 기록하고 즉시 WiredTiger 스토리지 엔진으로 성공 여부를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 데이터는 아직 디스크에 완전히 기록되지 않은 상태인데, 이 상태에서 리눅스 서버가 크래시되거나 응답 불능 상태가 되면 디스크에 완전히 기록되지 못한 데이터는 손실되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 트랜잭션 내용을 항상 저널로그에 먼저 기록하기 때문에 비정상 종료 이후 다시 실행될 때 자동으로 복구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 리눅스 서버는 WiredTiger 스토리지 엔진과는 무관하게 적절한 시점에 데이터를 디스크에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 WiredTiger 스토리지 엔진의 디스크 쓰기 과정이 리눅스 페이지 캐시까지만 저장되면 완료되므로 쓰기가 매우 빠르게 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Direct IO 는 리눅스 페이지 캐시를 거치지 않고 즉시 디스크로 쓰기를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 내장 캐시를 가진 RAID 컨트롤러도 write-back 모드로 작동하면서 자신의 캐시에 복사하는 즉시 성공여부로 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 RAID 컨트롤러는 자체적으로 버퍼링된 데이터를 실제 디스크에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 상태에서 서버에 크래시가 발생하더라도 RAID 에 내장된 배터리가 전원을 공급하면서 데이터가 손실되지 않도록 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 서버에 다시 전원이 제공되고 작동이 시작되면 RAID 컨트롤러의 캐시에 남아있는 데이터를 자동으로 디스크에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;압축&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 압축 알고리즘과 데이터 입출력 레이어에서 압축을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 의 고정된 크기의 페이지는 16KB 의 페이지를 압축하더라도 4KB 혹은 8KB 로 떨어져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않은경우 16KB 페이지르 2개의 16KB 로 나눈 후 다시 압축하는 과정을 거치는데 이러한 과정은 처리성능을 상당히 떨어트려 이런 지연을 회피하기 위해 16KB 페이지를 비워두는 패딩전력을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 는 16KB 페이지의 데이터를 압축했을 때 4KB 나 8KB 이하의 사이즈가 아니더라도 굳이 페이지를 분할하고 다시 압축하는 과정을 거칠필요가 없다. 모든 페이지가 가변사이즈이기 때문에 압축 결과 페이지를 그대로 디스크에 기록하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 장점은 스토리지 엔진이 데이터를 읽고 쓰는 시점에서 데이터의 압축과 해제가 처리된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 캐시에서 디스크에 저장될 때 블록매니저는 압축하여 디스크의 저장공간을 줄이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에서 읽을때 블록매니저가 압축을 해제하여 캐시에 올린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째 장점은 4가지 형태의 압축 기능이 제공된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 현재 MongoDB 에 내장된 WiredTiger 스토리지 엔진에서 사용할 수 있는 압축 형태는 블록과 인덱스 프리압축이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 블록 압축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스 프리압축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사전압축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 허프만 인코딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 압축은 가장 일반적인 압축 형태인데, 데이터가 저장된 페이지 단위로 압축을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4 버전의 서버에 내장된 블록압축 방법은 zlib 와 snappy 압축 알고리즘만 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진에서는 컬렉션, 인덱스, 저널로그에 대해 각각 압축을 어떻게 적용할 것인지 결정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1715600539684&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wiredTiger:
    engineConfig:   
       collectionConfig:
           blockCompressor: snappy
       indexConfig:
           prefixCompression: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 MongoDB 컬렉션에 저장되는 도큐먼트는 키와 값의 쌍으로 저장되므로 하나의 페이지에서 반복되는 키 값이 상당히 많이 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MongoDB 데이터베이스에서는 압축이 필수적인 요소 중 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 MongoDB 인덱스는 키와 값의 쌍으로 저장되지 않고 일반적인 RDBMS 와 동일한 형태인 키 엔트리로 인덱스가 구성된다. 즉 MongoDB 에서 인덱스는 snappy 나 zlib 압축이 큰 효과가 없을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 인덱스 파일의 압축을 위해 프리픽스 압축 기능이 제공된다. 이름 그대로 인덱스 키에서 왼쪽 부분의 중복 영역을 생략하는 압축 방식을&amp;nbsp; 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;압축 전&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;압축 후&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Apple&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Apple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;AppleLeaf&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;*Leaf&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;AppleTree&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;*Tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Banana&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Banana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Bay&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Bay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Car&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Car&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;CarDriver&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;*Driver&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통되는 글자는 * 로 생략하나 만약 공통되는 길이가 너무 짧은경우에는 프리픽스 압축이 생략되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리픽스 압축은 블록 압축과 달리 WiredTiger 공유캐시에서도 압축 상태를 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 디스크 뿐만 아니라 메모리에서도 사용량을 절약할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점으로 데이터를 읽을 때 항상 완전한 키 값을 얻기 위해 조립 과정을 거쳐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운이 나쁜 경우 제일 첫 번째 인덱스&amp;nbsp; 키부터 재조립 과정을 거쳐야 할 수도 있어 키를 읽는 속도가 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프리픽스 압축의 이런 오버헤드는 인덱스를 역순으로 읽을때 더 심해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;암호화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서도 데이터 파일과 인덱스 데이터의 암호화를 위한 기능을 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 디스크에서 데이터 페이지를 읽어오는 순간 암호화된 내용을 복호화해서 메모리에 적재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메모리 스토리지 엔진&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 엔터프라이즈 버전에서 메모리 기반의 스토리지 엔진을 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 최근 percona 에서 만든 percona MongoDB 서버에서도 메모리 스토리지 엔진을 라이센스 제약없이 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 스토리지 엔진을 실행하고자 한다면 MongoDB 서버 시작시 --storageEngine=inMemory 를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Percona 는 WriedTiger 스토리지 엔진이 메모리 기반으로 작동할 수 있게 인터페이스를 개발한 것이기 때문에 WiredTiger 와 거의 동일하게 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점으로는 아래 2가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 스토리지 엔진은 체크포인트가 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 공유캐시의 페이지 이빅션이 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Percona 의 가장 큰 문제점은 OpLog 를 디스크로 기록하지 않고 공유캐시 메모리에 저장한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 OpLog 크기를 너무 크게 설정하면 메모리 부족으로 구동이 실패한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 데이터가 메모리에 저장되므로 복제 구조와 관련된 메타정보까지 메모리에 저장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 복제, 샤드 클러스터가 한번에 종료되면 메모리데이터가 유실되어 메타정보가 없어 복구가 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기타 스토리지 엔진&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RocksDB : 페이스북에서 개발해서 배포 중, LSM 기반의 데이터 저장소를 가지는데, 느린 쿼리 성능을 보완하기 위해 로우 키를 기준으로 여러 범위로 나누고 각 범위별로 LSM-Tree 를 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PerconaFT : 퍼코나에서 배포, Fractal Tree 인덱스의 구조적인 장점으로 인해 빠른 INSERT 성능이 강점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>mongodb 이빅션</category>
      <category>wiredtiger 공유캐시</category>
      <category>wiredtiger 알고리즘</category>
      <category>wiredtiger 엔진</category>
      <category>wiredtiger 읽는방법</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/635</guid>
      <comments>https://mozi.tistory.com/635#entry635comment</comments>
      <pubDate>Mon, 13 May 2024 16:28:34 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] WiredTiger 스토리지 엔진, 데이터파일 구조</title>
      <link>https://mozi.tistory.com/634</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqVHZo/btsHmRAYU2W/ksMQjsKTXyfhwk9rewVL0k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqVHZo/btsHmRAYU2W/ksMQjsKTXyfhwk9rewVL0k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqVHZo/btsHmRAYU2W/ksMQjsKTXyfhwk9rewVL0k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqVHZo%2FbtsHmRAYU2W%2FksMQjsKTXyfhwk9rewVL0k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WiredTiger 스토리지 엔진&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Berkeley DB 개발자들에 의해서 개발된 임베디드 데이터베이스 엔진으로 현재 MongoDB 의 기본엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 엔진이 도입되기 전 MMAPv1 스토리지 엔진을 사용했는데 문제점이 많아 이 엔진으로 변경되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 엔진은 내부적인 잠금 경합 최소화를 위해 '하자드 포인터' 나 '스킵 리스트' 와 같은 많은 신기술을 채택하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 MVCC 와 데이터파일 압축 그리고 암호화 기능들을 모두 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WiredTiger 스토리지 엔진 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;storage 의 enigine 필드를 wiredTiger 로 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 이 외의 필드는 성능에 크게 영향을 미치지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1715533673364&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;storage:
    engine:&quot;wiredTiger&quot;
    wiredTiger:
        engineConfig:
            cacheSizeGB:
            journalCompressor:
            directoryForIndexes:
        collectionConfig:
            blockCompressor:
        indexConfig:
            prefixCompression:
        ...
    ...
 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;engineConfig.cacheSizeGB&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger 스토리지 엔진의 공유 캐시가 어느정도의 메모리를 사용하게 할 것인지 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;collectionConfig.blockCompressor&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger 스토리지 엔진의 데이터 파일을 압축할 것인지, 압축한다면 어떤 알고리즘을 사용할 것인지 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;indexConfig.prefixCompression&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger 스토리지 엔진의 인덱스는 기본적으로 데이터 블록단위의 압축은 지원하지 않고 프리픽스 압축을 지원하는데 프리픽스 압축을 사용할 것인지 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WiredTiger 스토리지 저장 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 엔진은 3가지 타입의 저장소를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 레코드(Row) 스토어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 컬럼 스토어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) LSM(Log Structured Merge Tree) 스토어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레코드 스토어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 RDBMS 가 사용하는 저장 방식으로, 테이블의 레코드를 한꺼번에 같이 저장하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체로는 B-Tree 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼 스토어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량의 분석용도로 자주 사용되는데, 테이블의 레코드와 상관없이 각 컬럼 단위 또는 컬럼의 그룹 단위로 데이터 파일을 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼 스토어는 특정 컬럼 단위로 데이터 파일을 생성하므로 데이터 파일으 크기가 작아지고 데이터를 읽어 들이는 속도도 매우 빨라져 대용량 분석에 적합한 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LSM 스토어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카산드라와 같은 NoSQL 데이터베이스에서 자주 사용하는 저장 방식으로 읽기보다는 쓰기 능력에 집중한 저장방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LSM 은 내부적으로 B-Tree 알고리즘을 사용하지 않고 순차파일 형태로 데이터를 저장한다.&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 저장 가능한 크기의 조각으로 데이터 파일을 관리하는데, 메모리에 저장 가능한 하계를 넘어서면 이를 디스크에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 메모리에서 디스크로 저장된 파일은 Level-0 파일이 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Level-0 데이터 파일 조각이 많아지면 이를 모아서 Level-1 데이터 파일 조각을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 로그처럼 계속해서 기록되는 파일들을 병하여 Level-n 까지의 데이터 파일로 계속 성장하는 방식으로 작동하므로 로그 기반의 병합 트리(Log Structured Merge Tree) 라 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;쓰기 장점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 는 레코드가 저장될 때마다 B-Tree 를 유지해야 하는 비용이 필요하고 트리도 커지기 때문에 INSERT 비용이 증가하는 구조인 반면, LSM 은 새로운 데이터는 메모리에 저장하고 메모리에 저장된 데이터가 커지면 이 데이터 파일만 메모리에서 디스크로 저장하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 B-Tree 와 다르게 INSERT 성능이 떨어지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;읽기 단점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 LSM 에선 데이터를 읽기 위해서 N 개의 데이터파일을 모두 검색해야만 원하는 데이터를 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree 는 하나의 트리만 검색하면 되는 반면 LSM 은 모든 파일을 읽기 때문에 성능이 느리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WiredTiger 데이터 파일 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진과는 완전히 다른 구조를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;명칭&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;별도의 확장자를 가지지 않는 텍스트 파일로 현재 실행중인 스토리지 엔진의 버전을 저장하고 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;storage.bson&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;BSON 포맷으로 WiredTiger 디렉터리 구조를 설명하는 옵션의 내용이 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;sizeStorer.wt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger 스토리지 엔진을 사용하는 컬렉션의 전체 도큐먼트 건수와 각 컬렉션의 데잍 파일 크기를 저장&lt;br /&gt;&lt;br /&gt;이 정보를 이용하여 전체 도큐먼트 건수를 확인하는 쿼리는 메타 정보만 조회하여 매우 빠른 결과를 보여줌&lt;br /&gt;&lt;br /&gt;다만 정확한 컬렉션의 도큐먼트 건수를 보여주지 않을 수 있음&lt;br /&gt;- 샤딩된 클러스터 환경일 때&lt;br /&gt;- 샤드간 재분산이 실행될 때&lt;br /&gt;- 고아가 된 도큐먼트가 있을 때&lt;br /&gt;- 서버가 비정상적으로 종료되었을 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger.lock&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;다른 MongoDB 서버의 인스턴스가 동시에 사용하지 못하도록 잠금 역할을 하기 위한 파일&lt;br /&gt;&lt;br /&gt;MongoDB 가 정상적으로 종료되었는지 판단하는 파일&lt;br /&gt;&lt;br /&gt;만약 서버가 올라올 때 WiredTiger.lock 파일이 있다면 비정상적으로 종료되었다고 판단하고 복구를 수행 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger.turtle&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger 스토리지 엔진의 설정 내용을 담음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger.wt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger 스토리지 엔진의 메타 데이터를 저장하는 컬렉션의 데이터 파일&lt;br /&gt;&lt;br /&gt;컬렉션에서 uri 필드에 각 컬렉션이나 인덱스의 파일경로가 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;_mdb_catalog.wt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTiger 스토리지 엔진 컬렉션과 인덱스의 목록 그리고 각 인덱스나 컬렉션이 사용하는 데이터 파일의 목록을 관리하는 파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;WiredTigerLAS.wt&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;스토리지 엔진의 캐시에서 재활용할 수 있는 공간이 부족하면 필요한 만큼의 여유공간을 만들어야 사용자 요청 쿼리를 처리할 수 있음&lt;br /&gt;&lt;br /&gt;이 때 캐시에서 제거해야 하는 데이터페이지들이 더티상태여서 디스크에 기록해야 할 때 필요하다면 임시로 WiredTigerLAS.wt 데이터 파일을 사용함&lt;br /&gt;&lt;br /&gt;이렇게 임시로 기록된 더티페이지는 나중에 다시 원래 데이터파일로 기록되거나 캐시로 읽어와짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;diagnostic.data&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;내부 정보를 1초에 한번씩 모아서 별도의 파일로 기록&lt;br /&gt;&lt;br /&gt;- 운영체제의 상태정보&lt;br /&gt;- 서버 상태 정보&lt;br /&gt;- 복재 상태&lt;br /&gt;- 호스트정보&lt;br /&gt;등&lt;br /&gt;&lt;br /&gt;3.2버전이 상에서 getDiagnosticData 명령을 사용하여 정보를 바로 가져올 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>mongodb wiredtiger</category>
      <category>wiredtiger 데이터파일 구조</category>
      <category>wiredtiger 엔진</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/634</guid>
      <comments>https://mozi.tistory.com/634#entry634comment</comments>
      <pubDate>Mon, 13 May 2024 03:45:44 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 데이터를 읽고 쓰는 방법과 프레그멘테이션 관리</title>
      <link>https://mozi.tistory.com/633</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1e3w1/btsHorHE8KZ/EKq9oPG5k2tswYzOCyHh60/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1e3w1/btsHorHE8KZ/EKq9oPG5k2tswYzOCyHh60/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1e3w1/btsHorHE8KZ/EKq9oPG5k2tswYzOCyHh60/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1e3w1%2FbtsHorHE8KZ%2FEKq9oPG5k2tswYzOCyHh60%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영체제 캐시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞 포스팅에서 말한 것처럼 MongoDB 의 MMAPv1 스토리지는 운영체제 캐시를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 OS 캐시정책이 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;페이지 캐시의 데이터 읽기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;읽는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진은 리눅스의 캐시를 거쳐서 데이터를 MongoDB 서버로 읽어들인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 순서로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) MongoDB 서버가 필요로 하는 데이터는 먼저 리눅스의 페이지 캐시에 적재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 그 데이터를 MongoDB 서버가 가져감&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 그 데이터를 다시 읽어야 하는 경우 디스크에서 읽지 않고 리눅스 페이지 캐시에 적재된 페이지를 조회함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 리눅스의 디스크 읽기는 주변의 일부 페이지들을 같이 읽어 들이는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 Read-Ahead 라고 하는데, 데이터 파일에서 필요로 하는 페이지 주변의 페이지들 일부가 캐시에 이미 적재되어 있으면 리눅스의 Read-Ahead 알고리즘이 작동하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 알고리즘이 작동하게 되면 MongoDB 서버가 필요로 하지 않더라도 주변의 데이터를 읽어 캐시페이지로 올린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스의 Read-Ahead 알고리즘은 일반적으로 대량의 데이터를 읽을 때 성능을 획기적으로 높여주지만, DB 와 같이 랜덤 읽기 위주(연속된 페이지를 읽지 않는)의 처리를 필요로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 꼭 필요한 페이지만 메모리로 적재하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 리눅스의 Read-Ahead 설정은 128~256 으로 설정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 512 바이트 섹터를 한 번에 몇 개까지 디스크에서 읽을 것인지 결정하는 옵션인데, 이 알고리즘이 작동하면 64 ~ 128KB 의 데이터를 읽게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진은 4KB 나 8KB 의 데이터를 읽으면 되는데 무의미하게 많이 읽게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무의미하게 많이 읽는다는건 DB의 메모리 사용 효율이 떨어진다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무분별하게 많은 데이터를 페이지캐시에 올림으로써 필요한 데이터들이 메모리에서 사라지게 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Read-Ahead 값을 MMAPv1 에서 읽는 크기에 맞춰 (데이터 4KB, 인덱스 8KB) 16이나 32로 조정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;페이지 케시의 데이터 쓰기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쓰는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 데이터를 변경하게 되면 MMAPv1 스토리지 엔진은 리눅스의 페이지 캐시 내용을 변경한다.&lt;br /&gt;이렇게 메모리상의 데이터는 변경되었지만 디스크로 동기화되지 않은 페이지는 더티 페이지라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크로 동기화되기 전 컴퓨터가 비정상적으로 종료되면 메모리의 데이터는 유실된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 유실을 방지하기 위해 MongoDB 는 WAL(Write Ahead Log) 로그를 별도로 기록하여 손실된 데이터를 복구할 수 있도록 준비한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 엔진은 변경된 내용을 즉시 디스크로 기록하지 않는데 이유는 조금 더 많은 쓰기작업을 모아 한번에 기록함으로써 서버 시스템의 자원을 효율적으로 사용할 수 있게 해주기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 리눅스의 페이지 캐시를 거쳐서 쓰기를 실행하면 비동기 모드로 디스크 쓰기를 실행할 수 있어 사용자 쿼리가 데이터 변경이 디스크에 동기화될 때까지 기다리지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더티 페이지를 디스크에 기록하여 동기화 해야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 페이지가 너무 자주 디스크에 동기화되면 불필요하게 시스템 자원을 많이 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 너무 동기화를 안하면 더티 페이지가 많아져 그로인해 의도치 않은 데이터 손실 혹은 복구시간을 길어지게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 커널 파라미터에서 dirty 와 관련된 커널 파라미터를 조정하여 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 프레그멘테이션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진은 도큐먼트가 저장되는 순서대로 데이터 파일에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 중간에 도큐먼트가 삭제되면 빈 공간을 기록해두고 새로운 도큐먼트가 저장되면 이 공간을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 새로운 도큐먼트가 삭제된 도큐먼트보다 작은 경우에만 해당되고 그렇지 않다면 신규 저장공간을 계속 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 데이터 파일에서 사용하지 못하는 공간이 늘어나게 되고 실제 데이터 크기보다 파일 크기가 훨씬 커지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 프레그맨테이션이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레그멘테이션이 좋지않은 이유는 똑같은 페이지를 읽더라도 가져올 수 있는 데이터의 수가 줄어든다는 의미이므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 많은 데이터페이지를 캐시로 올려야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프레그멘테이션을 알기위해서는 stats 로 확인해 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프레그멘테이션 확인&lt;/h3&gt;
&lt;pre id=&quot;code_1715531853510&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mongo&amp;gt; db.mozi.stats()
{
  &quot;ns&quot; :&quot;mozi.tistory&quot;,
  &quot;count&quot; : 11,
  &quot;size&quot; : 531144,
  &quot;avgObjSize: 48200,
  &quot;storageSize&quot; : 989711,
  &quot;paddingFactor&quot; : 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;count&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;컬렉션이 가진 도큐먼트 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;size&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;컬렉션의 전체 도큐먼트 크기, 도큐먼트에 패딩된 바이트까지 합한 값, 인덱스 크기는 포함하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;storageSize&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;컬렉션을 위해 할당된 전체 디스크 데이터 파일의 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;avgObjSize&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;도큐먼트 하나의 평균 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;paddingFactor&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;MMAPv1 스토리지 엔진을 사용하는 컬렉션만 패딩이 사용되는데 도큐먼트를 저장할 때 처음부터 일정 크기의 여유공간을 만들어서 저장하는데 이 때 얼마나 덧붙일지 결정하는 값&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션을 컴팩션하는 동안에는 대상 컬렉션이 저장된 데이터베이스의 모든 운영이 잠긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 반드시 점검 시간을 이용하여 작업하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴팩션을 한다고 해서 디스크 파일의 크기 자체가 줄어드는 건 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 빈 공간이 없도록 데이터를 파일 앞으로 뭉치기 때문에 다음에 들어오는 데이터는 순차적으로 데이터 파일에 적재된다.&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>mongodb 데이터 쓰기</category>
      <category>mongodb 데이터 읽기</category>
      <category>mongodb 프레그멘테이션</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/633</guid>
      <comments>https://mozi.tistory.com/633#entry633comment</comments>
      <pubDate>Mon, 13 May 2024 01:41:48 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MMAPv1 스토리지 엔진</title>
      <link>https://mozi.tistory.com/632</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tZ2II/btsHniRVzk3/BqpPRWx9EwDbcCg2caUNiK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tZ2II/btsHniRVzk3/BqpPRWx9EwDbcCg2caUNiK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tZ2II/btsHniRVzk3/BqpPRWx9EwDbcCg2caUNiK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtZ2II%2FbtsHniRVzk3%2FBqpPRWx9EwDbcCg2caUNiK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MMAPv1 스토리지 엔진&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진은 MongoDB 초찾익부터 3.0 까지 주로 사용되던 스토리지 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.6 버전까지는 데이터베이스 단위의 잠금을 사용했으나, 3.0 으로 업그레이드되면서 컬렉션 수준의 잠금으로 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 컬렉션 수준의 잠금 또한 동시성 처리에 많은 걸림돌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 스토리지 엔진은 자체 캐시 기능이 없어 운영체제의 캐시를 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 커널이 제공하는 시스템 콜을 거치게 되므로 오버헤드가 상대적으로 큰 편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 운영체제의 캐시 기능은 관리가 데이터베이스가 직접하는 것 보다 안정적이지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MMAPv1 스토리지 전망&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 MongoDB 서버가 업그레이드 되면서 MMAPv1 스토리지 엔진의 사용이 줄어들고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 MMAPv1 이 가진 단점이 많고 MongoDB 에서도 현재 기본엔진인 WiredTiger 의 기능과 안정성에 집중하고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MMAPv1 스토리지 엔진 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 설정 파일을 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- storage 파트 engine 옵션을 mmapv1 으로 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- mmapv1 파트에서 MMAPv1 스토리지 엔진의 옵션을 설정&lt;/p&gt;
&lt;pre id=&quot;code_1715526243087&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;storage:
    engine:&quot;mmapv1&quot;
    mmapv1:
        preallocDataFiles: &amp;lt;boolean&amp;gt;
        ...
    ...
 ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능을 좌우하는 파라미터는 딱히 없으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;preallocDataFiles 는 빈 데이터 파일을 미리 생성하여 데이터가 사용할 공간을 미리 예약해 둘 것인지 결정하는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외 여러 옵션들도 있으며 대부분 파일과 관련된 설정들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 엔진의 경우 MongoDB 서버 자체의 설정보단 리눅스의 커널 튜닝이 더 많이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MMAPv1 데이터 파일 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터파일은 데이터베이스 단위로 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 데이터는 하나의 데이터베이스 파일에만 저장되는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 크기에 따라 여러 개의 데이터 파일로 나눠진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 이름이 데이터 파일의 이름으로 사용되며 각 파일은 자동으로 증가하는 순번을 확장자로 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 파일은 64MB 로 생성되며 그 다음 증가는 64MB, 128MB 순으로 할당되고 2GB 까지 증가하면 그 이후에는 더 이상 증가하지 않고 새로운 디스크 파일을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션의 데이터는 많지 않지만 데이터베이스 개수가 많은 경우에는 공간 낭비가 심하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;storage.smallFiles 옵션을 이용하여 생성되는 파일의 크기를 작게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 storage.directoryperDB 옵션을 이용해서 디스크의 데이터 파일을 데이터베이스 단위로 별도의 디렉터리에 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tmp 디렉터리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 데이터베이스 디렉터리에는 _tmp 디렉터리가 있는데 RepiareDatabase 명령을 실행할 때 사용하는 임시 작업경로이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ns 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 데이터베이별로 ns 파일을 확인할 수 있는데, 이 파일은 데이터베이스와 컬렉션, 인덱스 정보를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pid, lock 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 가 실행중인 프로세스의 번호를 저장하고 데몬 프로그램의 프로세스를 관리하는데 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 lock 파일은 비정상적인 종료를 체크하고 복구를 수행할 것인지 판단하는 용도로 활용되므로 주의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;log 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 로그를 기록하는 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 서버 상태 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 확인할 수 있는 mongostat 이라는 도구를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 도구는 현재 쿼리처리량과 메모리 사용량 등과 같은 MongoDB 전체적인 상태를 확인할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;mongodbstat 지표&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 157px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;열&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;flush&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;몇 번이나 데이터파일이 디스크에 동기화 되었는지 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 40px;&quot;&gt;mmaped&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 40px;&quot;&gt;MMAPv1 스토리지 엔진에서만 보여지는 매트릭으로 페이지 캐시에 얼마나 매핑되어 있는지 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;vsize&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;MongoDB 프로세스가 현재 사용중인 가상 메모리를 보여줌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;faults&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;디스크에서 데이터 페이지를 읽은 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;locked db&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;데이터베이스 중에서 가장 잠금이 심한 데이터베이스를 보여&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>mongodb mmapv1</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/632</guid>
      <comments>https://mozi.tistory.com/632#entry632comment</comments>
      <pubDate>Mon, 13 May 2024 01:07:14 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB 아키텍처 MMAPv1, WiredTiger</title>
      <link>https://mozi.tistory.com/631</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VKexL/btsHma8kuLr/I1xg6ikCgbpyLqricCwYxk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VKexL/btsHma8kuLr/I1xg6ikCgbpyLqricCwYxk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VKexL/btsHma8kuLr/I1xg6ikCgbpyLqricCwYxk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVKexL%2FbtsHma8kuLr%2FI1xg6ikCgbpyLqricCwYxk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아키텍처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초보자 입장에서 바라보는 아키텍처이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDnhOe/btsHlEIA6Hk/VWlU9a2UkhNAoJ7mLZDn01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDnhOe/btsHlEIA6Hk/VWlU9a2UkhNAoJ7mLZDn01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDnhOe/btsHlEIA6Hk/VWlU9a2UkhNAoJ7mLZDn01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDnhOe%2FbtsHlEIA6Hk%2FVWlU9a2UkhNAoJ7mLZDn01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;707&quot; height=&quot;680&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 프로그래머는 프로그래밍 언어를 사용하여 개발을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 이 코드는 MongoDB Driver 를 거쳐 컴파일 되고 MongoDB 를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 네트워크를 통해 MongoDB 요청을 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) MongoDB 의 쿼리프로세서, 샤드 매니저 등등은 이 데이터가 어디에 위치하는지 쿼리는 괜찮은지 유무를 파악한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 모든게 통과되면 스토리지엔진 API 를 통해 데이터를 디스크로부터 읽어들인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스토리지 엔진&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 하위에 위치한 MMAPv1 과 WiredTiger 등을 스토리지 엔진이라고 하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에 영구적으로 기록하거나 다시 읽어와서 메모리에 적재하는 역할을 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 서버와 같이 다양한 스토리지 엔진을 사용할 수 있도록 플러그인 형태로 되어있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 MySQL 과 달리 하나의 인스턴스에서 하나의 스토리지만 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 하나의 클러스터에서 서로 다른 노드(=인스턴스가 다른)는 서로 다른 스토리지를 사용해도 무방하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 제일많이 사용되는 엔진은 WiredTiger 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스토리지 엔진 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 거와 같이 MongoDB 는 다양한 스토리지 엔진을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 사용자들이 실행하는 쿼리는 스토리지 엔진과 직접적인 연관은 없기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MMAPv1 에서 WiredTiger 로 엔진을 변경하더라도 응용 프로그램은 거의 변경하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MMAPv1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 가 처음 출시됐을 때부터 사용되던 스토리지 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점이 명확하여 현재는 대부분 WiredTiger 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WiredTiger&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 3.0 부터 도입된 새로운 스토리지 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;In-Memory&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WiredTiger 스토리지 엔진을 변형하여 데이터를 디스크에 기록하지 않고 메모리에만 보관하는 스토리지 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RocksDB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이스북에서 LevelDB 를 커스터마이징하여 개선한 스토리지 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TokuDB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Percona 에서 개발중인 스토리지 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스토리지 엔진 비교&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;기능&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;MMAPv1&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;WiredTiger (in-memory)&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;RocksDB&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;TokuDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;잠금 수준&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;컬렉션&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;도큐먼트&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;도큐먼트&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;도큐먼트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;데이터 구조&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;B-tree&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;B-tree&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;LSM&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;Fractal-Tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;캐시&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X (운영체제 캐시를 사용)&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;세컨드리 인덱스&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;데이터 압축&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;인덱스 압축&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;암호화&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;&amp;nbsp;&lt;/div&gt;</description>
      <category>Database/MongoDB</category>
      <category>mmapv1</category>
      <category>mmapv1 과 wiredtiger 차이</category>
      <category>mongodb 스토리지 엔진</category>
      <category>MongoDB 아키텍처</category>
      <category>wiredtiger</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/631</guid>
      <comments>https://mozi.tistory.com/631#entry631comment</comments>
      <pubDate>Fri, 10 May 2024 22:24:42 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB 의 CRUD 사용 ( SELECT, INSERT, UPDATE, DELETE )</title>
      <link>https://mozi.tistory.com/629</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXYxa0/btsGK3grgRf/HImVCvpTvCTqAzKw77FKLK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXYxa0/btsGK3grgRf/HImVCvpTvCTqAzKw77FKLK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXYxa0/btsGK3grgRf/HImVCvpTvCTqAzKw77FKLK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXYxa0%2FbtsGK3grgRf%2FHImVCvpTvCTqAzKw77FKLK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 의 CRUD&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 도 RDB 와 마찬가지로 SELECT, INSERT, UPDATE, DELETE 를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점이라면 RDB 는 테이블이 없는경우 오류를 뱉지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 컬렉션이 없으면 컬렉션을 생성하면서 적재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 자바스크립트 기반 명령어와 JSON 을 인자로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SELECT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 라는 명령어를 사용한다. id = mozi 인 값을 찾는다.&lt;/p&gt;
&lt;pre id=&quot;code_1713421875358&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM mozi WHERE id = 'mozi'&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1713421804686&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.mozi.find ( {
  id : 'mozi'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;INSERT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insert 라는 명령어를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713421897181&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO mozi (id, type, version) VALUES ('mozi', 'tistory', 1)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1713421771465&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.mozi.insert {
  id: 'mozi',
  type: 'tistory',
  version : 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UPDATE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update 라는 명령어를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;set 은 값을 변경하겠다는 의미이고 $gt 는 자바스크립트에서 보다 큰 을 의미한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713421987710&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPDATE mozi SET type='naxer' WHERE version &amp;gt; 1&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1713422058952&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.mozi.update {
  { version: { $gt: 1 } },
  { $set: { type: 'naxer' } }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DELETE&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remove 라는 명령어를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713422157651&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DELETE FROM mozi WHERE id = 'mozi'&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1713422180701&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.mozi.remove ( {
  id: 'mozi'
} )&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>MongoDB CRUD</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/629</guid>
      <comments>https://mozi.tistory.com/629#entry629comment</comments>
      <pubDate>Thu, 18 Apr 2024 15:36:44 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB 와 RDBMS 차이</title>
      <link>https://mozi.tistory.com/628</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQNpoD/btsGH3P0WVR/2Gjs9DacKlplNQhV0RqyT1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQNpoD/btsGH3P0WVR/2Gjs9DacKlplNQhV0RqyT1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQNpoD/btsGH3P0WVR/2Gjs9DacKlplNQhV0RqyT1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQNpoD%2FbtsGH3P0WVR%2F2Gjs9DacKlplNQhV0RqyT1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 와 RDBMS 의 명칭 차이&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;RDBMS&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;MongoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;데이터베이스 ( Database )&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;데이터베이스 ( Database )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;테이블 ( Table )&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;컬렉션 ( Collection )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;레코드 ( Row )&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;도큐먼트 ( Document )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;컬럼 ( Column )&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;필드 ( Field )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;인덱스 ( Index )&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;인덱스 ( Index )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;쿼리의 결과로 레코드가 반환&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;쿼리의 결과로 커서가 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명칭은 다르나 의미는 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 MongoDB 에서 Collection 명칭은 RDBMS 의 Table 명칭과 같은 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼리의 결과 커서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 쿼리의 결과로 커서가 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 프로그램은 커서를 통해 반복적으로 실제 도큐먼트를 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 결과를 가져오지 않는이유는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리의 결과를 클라이언트 서버의 메모리에 모두 담아두지 않아도 처리할 수 있게 하기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 커서를 읽을 때마다 서버에서 도큐먼트를 가져오는 것은 아니고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정된 페이지 사이즈 단위로 서버로부터 전송받아 클라이언트 서버에 캐싱한 후 서비스된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 특성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 를 NoSQL / 스키마프리 / 비관계형 데이터베이스로 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NoSQL&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 SQL 을 사용하지 않기 때문에 NoSQL 로 분류한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 요즘은 타사에서 개발한 MongoDB 와 호환되는 SQL Driver 을 이용하면 SQL 쿼리처럼 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 RDBMS 에서도 NoSQL 인터페이스를 제공하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스키마프리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 처럼 테이블-컬럼을 지정하지 않고 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS 는 컬럼이 정수형인 경우 해당컬럼에는 정수타입만 넣을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 MongoDB 는 Document 가 다르다면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;같은 필드명이라고 하더라도 서로다른 타입을 저장할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다만 타 NoSQL(예 : Redis)와는 조금 다르게 봐야하는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;MongoDB 는 인덱스를 제공하고 있고, 필드에 인덱스 구성 시 같은 타입만 사용되어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;따라서 완벽한 스키마프리라고는 보기 어려울 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비관계형 데이터베이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDB 는 테이블, 행, 열의 정보를 구조화하고 조인하여 정보 간 관계 또는 링크를 만들어 낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 외래키를 명시적으로 지원하지 않지만 논리적인 도큐먼트간의 관계를 만들어 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$lookup 이라는 집합기능을 이용하면 RDBMS 와 비슷한 느낌의 조인을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런이유로 MongoDB 는 NoSQL 이긴 하나 SQL 과의 경계가 허물어지고 있다.&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>MongoDB vs RDBMS</category>
      <category>NoSQL vs SQL</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/628</guid>
      <comments>https://mozi.tistory.com/628#entry628comment</comments>
      <pubDate>Thu, 18 Apr 2024 15:26:27 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB 마이너버전 업그레이드 방법</title>
      <link>https://mozi.tistory.com/626</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lgoPY/btsGKfImMQK/2loxSOXDht2KJgZj68iIA1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lgoPY/btsGKfImMQK/2loxSOXDht2KJgZj68iIA1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lgoPY/btsGKfImMQK/2loxSOXDht2KJgZj68iIA1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlgoPY%2FbtsGKfImMQK%2F2loxSOXDht2KJgZj68iIA1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마이너버전 업그레이드 정책&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 마이너버전 업그레이드는 롤링(Rolling-upgrade)방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롤링방식이란 서비스를 중지하지 않고 멤버들을 순차적으로 업그레이드하는 방식을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;롤링방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롤링방식 순서는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 세컨드리 멤버의 버전을 업그레이드 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 업그레이드가 된 세컨드리 멤버와 현재의 프라이머리 멤버를 스위칭한다. (서비스 입장에서는 순간적인 단절이 발생)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 업그레이드가 된 세컨드리 멤버에서 서비스를 이어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 세컨드리가 된 기존 프라이머리 버전을 업그레이드 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;롤링방식의 리스크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롤링방식으로 순차적으로 업그레이드를 하는동안 서비스는 이어갈 수 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세컨드리와 프라이머리간 버전이 일치하지 않는 기간이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 업그레이드가 된 버전의 기능을 사용하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업그레이드가 되지 못한 멤버에 대해서 동기화가 깨져 복구를 해야할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 리스크를 방지하기 위해 서로다른 버전이 공존하는경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 하위버전의 기능만 사용하도록 하여 동기화가 끊어지지 않도록 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업그레이드가 완료되고 난 후 업그레이드 기능을 활성화 해주는 작업을 해주면 그때부터 신규 기능을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;setFeatureCompatibilityVersion&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션을 사용하여 신규버전의 기능을 활성화해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 현재 MongoDB 는 7.0 버전 호환성이며 7.2 호환성으로 활성화 해주는 명령어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한번 실행하고 나면 업그레이드 이전 버전으로는 되돌아 갈 수 없으므로 주의한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713419974942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;admin&amp;gt; db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1} )
{ featureCompatibilityVersion: { version: '7.0' }, ok: 1 }

admin&amp;gt; db.adminCommand( { setFeatureCompatibilityVersion: &quot;7.2&quot; } )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>MongoDB 업그레이드</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/626</guid>
      <comments>https://mozi.tistory.com/626#entry626comment</comments>
      <pubDate>Thu, 18 Apr 2024 15:01:39 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB 라이센스와 버전정책</title>
      <link>https://mozi.tistory.com/625</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmbAnU/btsGKuFt96O/AwCdkQ1399TtgM3lAbBkK0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmbAnU/btsGKuFt96O/AwCdkQ1399TtgM3lAbBkK0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmbAnU/btsGKuFt96O/AwCdkQ1399TtgM3lAbBkK0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmbAnU%2FbtsGKuFt96O%2FAwCdkQ1399TtgM3lAbBkK0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라이센스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 MongoDB, Inc 회사에 의해 개발 및 유지보수되는 오픈소스 데이터베이스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 기능을 모두 오픈 소스로 관리하며 누구든지 별도의 비용 없이 MongoDB 를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요할 때에는 직접 소스 코드를 수정하여 MongoDB 를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도 비용이 없는 오픈소스 MongoDB 는 커뮤니티 버전이라 부르고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 지원과 추가 기능을 사용할 수 있는 유료 라이센스 모델인 프로페셔널과 엔터프라이즈 버전을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 요즘 클라우드 환경의 PAAS 로 인해 언제 라이센스 정책을 변경할지는 잘 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;버전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 숫자로 구성된 버전 체계를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7.0.6 버전을 예로들면 앞의 7 은 메이저, 0 은 마이너, 6은 패치버전이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이너 버전에서 개발버전은 릴리즈버전보다 높기 때문에, 릴리즈가 2 라면 개발버전은 3 이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이너 버전은 홀수는 개발 버전, 짝수는 릴리즈(안정적)버전을 의미하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이너는 짝수버전을 사용하는게 좋으며 MongoDB 를 사용할 때 마지막 짝수버전을 빌드해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>MongoDB 라이센스</category>
      <category>MongoDB 버전</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/625</guid>
      <comments>https://mozi.tistory.com/625#entry625comment</comments>
      <pubDate>Thu, 18 Apr 2024 14:48:39 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 스키마를 설계해보자</title>
      <link>https://mozi.tistory.com/624</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zpKhB/btsFm5t9Qc3/PkkSURNcD50HQeawXONxX0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zpKhB/btsFm5t9Qc3/PkkSURNcD50HQeawXONxX0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zpKhB/btsFm5t9Qc3/PkkSURNcD50HQeawXONxX0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzpKhB%2FbtsFm5t9Qc3%2FPkkSURNcD50HQeawXONxX0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NoSQL 에서 스키마?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NoSQL 은 스키마라는 개념이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 NoSQL 인 MongoDB 에서 스키마를 설계한다는 말이 이해가 안 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 RDBMS 와 같은 스키마 개념은 없다고 보는게 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄밀이 비유하면 Table 은 Collection , Row 는 Document 로 표현은 가능하지만 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그렇다고 스키마를 설계하지는 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그렇다면 여기에서 말하는 스키마란 무엇일까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 스키마 디자인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 스키마 디자인은 RDBMS 의 디자인과 많이 다르게 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 규칙, 정규화에 대한 개념이 없고&amp;nbsp;Document 에 Key-Value 구조로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 데이터의 경우 RDBMS 라면 정규화를 거쳐 배열데이터를 별도의 테이블/로우로 분리 할 수도 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 모든 데이터를 하나의 Document 에 담고 한번에 불러온다.&lt;/p&gt;
&lt;pre id=&quot;code_1709186540472&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;Site&quot;: &quot;Tistory&quot;,
  &quot;BlogName&quot;: &quot;Mozi&quot;,
  &quot;Catalog&quot;: [&quot;Oracle&quot;, &quot;MySQL&quot;, &quot;MongoDB&quot;],
  &quot;Title&quot;: [
    {
      &quot;Catalog&quot;: &quot;MongoDB&quot;,
      &quot;Name&quot;: &quot;Title1&quot;
    },
    {
      &quot;Catalog&quot;: &quot;MongoDB&quot;,
      &quot;Name&quot;: &quot;Title2&quot;
    }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 Document 를 _id 를 참조하는 방식으로 설계할 수는 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 임베딩과 참조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;임베딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 하나의 Document 에 모든 데이터를 담는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709187419190&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Document 1

{
  &quot;_id&quot;: &quot;A12&quot;,
  &quot;Name&quot;: &quot;Mozi&quot;,
  &quot;Category&quot;: &quot;MongoDB&quot;,
  &quot;Title&quot;: &quot;MongoDB Schema&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Doucment 를 생성하면 _id 라는 고유한 값이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 Document 에서 이 _id 값을 참조로 넣는 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709187402118&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Document 1

{
  &quot;_id&quot;: &quot;A12&quot;,
  &quot;Name&quot;: &quot;Mozi&quot;,
}

# Document 2
{
  &quot;_id&quot;: &quot;TX10&quot;,
  &quot;Category&quot;: &quot;MongoDB&quot;,
  &quot;Title&quot;: &quot;MongoDB Schema&quot;,
  &quot;ParentId&quot;: &quot;A12&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;임베디드&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;참조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;하나의 호출로 모든 관련된 정보를 확인&lt;br /&gt;애플리케이션 코드에서 조인 구현 불필요&lt;br /&gt;관련 정보를 단일 원자성 작업으로 업데이트&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;하나의 Document 는 작은 문서를 갖게 됨&lt;br /&gt;16MB 제약도달 가능성 낮아짐&lt;br /&gt;자주 액세스하지 않는 정보는 호출하지 않음&lt;br /&gt;데이터 중복량을 줄일 수도 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;대부분 필드가 의미없을 때 많은 오버헤드 발생&lt;br /&gt;하나의 Document 크기는 16MB 가 최대로, 너무 많은 데이터를 담으면 잠재적 제한에 도달&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;최소 두개의 쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제사용해?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 언제 임베딩을 언제 참조를 사용해야 좋을까?&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1:1 관계&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;임베딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1:N ( Few or Many )&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;임베딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1:N ( &lt;span style=&quot;color: #000000;&quot;&gt;Squillions&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp; )&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;참조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;N:N&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;참조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/developer/products/mongodb/mongodb-schema-design-best-practices/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mongodb.com/developer/products/mongodb/mongodb-schema-design-best-practices/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709187890574&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;MongoDB Schema Design Best Practices | MongoDB&quot; data-og-description=&quot;Have you ever wondered, &amp;quot;How do I model a MongoDB database schema for my application?&amp;quot; This post answers all your questions!&quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/developer/products/mongodb/mongodb-schema-design-best-practices/&quot; data-og-url=&quot;https://www.mongodb.com/developer/products/mongodb/mongodb-schema-design-best-practices/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mm9I2/hyVqjpXqvY/PGXlhHqOkta31C0BPlso0K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/developer/products/mongodb/mongodb-schema-design-best-practices/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/developer/products/mongodb/mongodb-schema-design-best-practices/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mm9I2/hyVqjpXqvY/PGXlhHqOkta31C0BPlso0K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB Schema Design Best Practices | MongoDB&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Have you ever wondered, &quot;How do I model a MongoDB database schema for my application?&quot; This post answers all your questions!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>MongoDB 임베딩</category>
      <category>MongoDB 참조</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/624</guid>
      <comments>https://mozi.tistory.com/624#entry624comment</comments>
      <pubDate>Thu, 29 Feb 2024 15:14:28 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] 몽고DB 의 데이터는 유실될 수 있나</title>
      <link>https://mozi.tistory.com/623</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biBiHv/btsFoECIB5U/WCPrKf76MCbrtimTrjHabk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biBiHv/btsFoECIB5U/WCPrKf76MCbrtimTrjHabk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biBiHv/btsFoECIB5U/WCPrKf76MCbrtimTrjHabk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiBiHv%2FbtsFoECIB5U%2FWCPrKf76MCbrtimTrjHabk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;몽고DB 데이터는 유실된다?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마전 한 유튜브 강의에서 몽고DB 는 데이터가 유실될 수 있다는(왜?는 말씀하지 않으심) 이야기를 듣고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 의아하여 구글링을 조금 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬더니 많은 블로그에서 데이터 유실이 있다는 부분을 작성하였는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제, 얼마나, 왜 발생하는지에 대한 내용은 작성되어 있지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB관리자로써 나는 이걸알고 싶은데..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 한국문서를 넘어 해외문서를 뒤적거리고나서 어느정도 원하는 답을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 찾아본 자료가 이분들이 말하는 유실과 같은의미를 담고있는지는 잘 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 꼬꼬마로써 발견하지 못한 자료가 더 있을지는 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른점으로도 Memory Map 을 사용하고 주기적으로 flush 하는 동작이기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사이에 발생하는 장애로 인한 유실도 배제할 수는 없겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;몽고DB 데이터는 유실되나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MongoDB 는 ACID 트랜잭션을 통해 '강력한 데이터 일관성, 정확성, 안정성을 보장' 한다고 말하고 있는 분산 문서 데이터베이스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 MongoDB 의 기본쓰기 수준은 단일 노드에 의한 승인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기 작업이 보조 작업에 복제되기 전에 중단되면 데이터가 롤백될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 MongoDB 의 기본읽기 수준은 Dirty Read 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋되지 않은 데이터를 읽기 때문에 나중에 롤백될 수 있는 데이터를 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 클라이언트 입장에서는 일관되지 않은 데이터를 얼마든지 조회할 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련해서 아래 사이트에서 매우 자세하게 작성해 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 영문이라 머리에서 열나는 건 덤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jepsen.io/analyses/mongodb-4.2.6&quot;&gt;https://jepsen.io/analyses/mongodb-4.2.6&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709172958771&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Jepsen: MongoDB 4.2.6&quot; data-og-description=&quot;2020-05-26: MongoDB identified a bug in the transaction retry mechanism which they believe was responsible for the anomalies observed in this report; a patch is scheduled for 4.2.8. Our most recent report on MongoDB 3.6.4 focused on causal consistency and &quot; data-og-host=&quot;jepsen.io&quot; data-og-source-url=&quot;https://jepsen.io/analyses/mongodb-4.2.6&quot; data-og-url=&quot;https://jepsen.io/analyses/mongodb-4.2.6&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cxbw0m/hyVukOddeZ/wcJI9skVDp8ty7aSsOaRc0/img.png?width=1600&amp;amp;height=988&amp;amp;face=0_0_1600_988&quot;&gt;&lt;a href=&quot;https://jepsen.io/analyses/mongodb-4.2.6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jepsen.io/analyses/mongodb-4.2.6&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cxbw0m/hyVukOddeZ/wcJI9skVDp8ty7aSsOaRc0/img.png?width=1600&amp;amp;height=988&amp;amp;face=0_0_1600_988');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Jepsen: MongoDB 4.2.6&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2020-05-26: MongoDB identified a bug in the transaction retry mechanism which they believe was responsible for the anomalies observed in this report; a patch is scheduled for 4.2.8. Our most recent report on MongoDB 3.6.4 focused on causal consistency and&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jepsen.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;몽고DB 가 말하고자 하는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성한 것처럼 기본 쓰기/읽기 레벨로 인해 일관성을 유지하지 못하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일관성을 유지하기 위해서는 더 높은 격리레벨을 지정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 MongoDB 는 설계된 대로 작동하고 있다고 마감했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;MongoDB Jira Report&quot; href=&quot;https://jira.mongodb.org/browse/SERVER-35316?focusedCommentId=2008354&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-2008354&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jira.mongodb.org/browse/SERVER-35316?focusedCommentId=2008354&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-2008354&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709173177498&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Loading...&quot; data-og-description=&quot;Original Problem: When running a test against 2 replica set shards, with rc &amp;ldquo;local&amp;rdquo; and wc &amp;ldquo;w1&amp;rdquo; we get reads returning the base value of the document, nil, despite occurring after acknowledged writes in the session. Each single threaded client is w&quot; data-og-host=&quot;jira.mongodb.org&quot; data-og-source-url=&quot;https://jira.mongodb.org/browse/SERVER-35316?focusedCommentId=2008354&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-2008354&quot; data-og-url=&quot;https://jira.mongodb.org/browse/SERVER-35316?focusedCommentId=2008354&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-2008354&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://jira.mongodb.org/browse/SERVER-35316?focusedCommentId=2008354&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-2008354&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jira.mongodb.org/browse/SERVER-35316?focusedCommentId=2008354&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-2008354&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Loading...&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Original Problem: When running a test against 2 replica set shards, with rc &amp;ldquo;local&amp;rdquo; and wc &amp;ldquo;w1&amp;rdquo; we get reads returning the base value of the document, nil, despite occurring after acknowledged writes in the session. Each single threaded client is w&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jira.mongodb.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 속도가 매우 빠른 데이터베이스 중 하나로 알려져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도를 빠르게 하기위해 격리성을 낮춰 응답속도를 높였다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자고로 격리성을 높이다 보면 동시성은 약해지지만 응답시간이 늘어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 이러한 부분을 어쩌면 내려놓지 않았나 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 데이터 유실이 심각하다면 이렇게 유명해지지도 못했고 기업에서도 사용하지 못하겠지,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에 대해 알고 잘 이용한다면 얼마나 좋은 데이터베이스인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB Developer 사이트에서 Myth 7: MongoDB Loses Data 에 대해 짧게 작성해놓아 가져와봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/developer/products/mongodb/everything-you-know-is-wrong/#myth-1--mongodb-is-on-v3-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mongodb.com/developer/products/mongodb/everything-you-know-is-wrong/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709171299855&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Everything You Know About MongoDB is Wrong! | MongoDB&quot; data-og-description=&quot;There are a bunch of myths floating around about MongoDB. Here's where I bust them.&quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/developer/products/mongodb/everything-you-know-is-wrong/#myth-1--mongodb-is-on-v3-2&quot; data-og-url=&quot;https://www.mongodb.com/developer/products/mongodb/everything-you-know-is-wrong/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cjXm34/hyVqhyPOuK/ydTQXLjLjcCJPLNG7xqNd0/img.png?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/developer/products/mongodb/everything-you-know-is-wrong/#myth-1--mongodb-is-on-v3-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/developer/products/mongodb/everything-you-know-is-wrong/#myth-1--mongodb-is-on-v3-2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cjXm34/hyVqhyPOuK/ydTQXLjLjcCJPLNG7xqNd0/img.png?width=1201&amp;amp;height=631&amp;amp;face=0_0_1201_631');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Everything You Know About MongoDB is Wrong! | MongoDB&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;There are a bunch of myths floating around about MongoDB. Here's where I bust them.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 MongoDB 초보자 입니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 못 작성된 부분이 있다면 말씀주시면 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>MongoDB Data Loss</category>
      <category>MongoDB 데이터 유실</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/623</guid>
      <comments>https://mozi.tistory.com/623#entry623comment</comments>
      <pubDate>Thu, 29 Feb 2024 10:46:28 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB 의 파일구조</title>
      <link>https://mozi.tistory.com/622</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PIXeE/btsE935iXMt/CmFou2hqG5kvQ4JjQgEoG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PIXeE/btsE935iXMt/CmFou2hqG5kvQ4JjQgEoG1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PIXeE/btsE935iXMt/CmFou2hqG5kvQ4JjQgEoG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPIXeE%2FbtsE935iXMt%2FCmFou2hqG5kvQ4JjQgEoG1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일별 용도는 다시정리해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 데이터폴더 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh3Bwo/btsE95hKp4k/iznx0u1BooWGZFaAAJwiKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh3Bwo/btsE95hKp4k/iznx0u1BooWGZFaAAJwiKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh3Bwo/btsE95hKp4k/iznx0u1BooWGZFaAAJwiKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh3Bwo%2FbtsE95hKp4k%2Fiznx0u1BooWGZFaAAJwiKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;884&quot; height=&quot;468&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 로그폴더 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oTuzK/btsFbinukrx/pclHLtwJmLNGSlrxRth3C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oTuzK/btsFbinukrx/pclHLtwJmLNGSlrxRth3C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oTuzK/btsFbinukrx/pclHLtwJmLNGSlrxRth3C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoTuzK%2FbtsFbinukrx%2FpclHLtwJmLNGSlrxRth3C1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;74&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 속성폴더 구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSJyLz/btsFbh91uDr/fe8xIX3KPeZIFvramJ8C40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSJyLz/btsFbh91uDr/fe8xIX3KPeZIFvramJ8C40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSJyLz/btsFbh91uDr/fe8xIX3KPeZIFvramJ8C40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSJyLz%2FbtsFbh91uDr%2Ffe8xIX3KPeZIFvramJ8C40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;64&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/622</guid>
      <comments>https://mozi.tistory.com/622#entry622comment</comments>
      <pubDate>Thu, 22 Feb 2024 18:58:28 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] mongosh OpenSSL configuration error 해결</title>
      <link>https://mozi.tistory.com/621</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kyn7c/btsFcjlGWDI/lVcWxalN8RwUBEOVXcSEX0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kyn7c/btsFcjlGWDI/lVcWxalN8RwUBEOVXcSEX0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kyn7c/btsFcjlGWDI/lVcWxalN8RwUBEOVXcSEX0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKyn7c%2FbtsFcjlGWDI%2FlVcWxalN8RwUBEOVXcSEX0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;mongosh&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몽고DB 에 접속하는 툴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;mongosh 시 SSL 에러&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenSSL configuration error: digital envelope routines: ... 에러가 발생&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;57&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5gEYF/btsFeFuwAf1/QSONvLUkdGWdkuNQZU5FP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5gEYF/btsFeFuwAf1/QSONvLUkdGWdkuNQZU5FP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5gEYF/btsFeFuwAf1/QSONvLUkdGWdkuNQZU5FP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5gEYF%2FbtsFeFuwAf1%2FQSONvLUkdGWdkuNQZU5FP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1289&quot; height=&quot;57&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;57&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;openssl3 의 mongosh 로 재설치&lt;/h2&gt;
&lt;pre id=&quot;code_1708591069076&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo yum install mongodb-mongosh-shared-openssl3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;mongosh 로 접속&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접속 성공&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eD9PzP/btsFbTAYBTD/YAs44Qc1gfoxAiZq02XH81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eD9PzP/btsFbTAYBTD/YAs44Qc1gfoxAiZq02XH81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eD9PzP/btsFbTAYBTD/YAs44Qc1gfoxAiZq02XH81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeD9PzP%2FbtsFbTAYBTD%2FYAs44Qc1gfoxAiZq02XH81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1180&quot; height=&quot;287&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>mongoDB SSL 오류</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/621</guid>
      <comments>https://mozi.tistory.com/621#entry621comment</comments>
      <pubDate>Thu, 22 Feb 2024 17:40:52 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] Amazon Linux 2023 리눅스에 MongoDB 설치</title>
      <link>https://mozi.tistory.com/620</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvDkt1/btsFbgQCylf/bSqNiYcoJTssFiFUaz892K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvDkt1/btsFbgQCylf/bSqNiYcoJTssFiFUaz892K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvDkt1/btsFbgQCylf/bSqNiYcoJTssFiFUaz892K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvDkt1%2FbtsFbgQCylf%2FbSqNiYcoJTssFiFUaz892K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 지원 OS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 총 4개의 운영체제를 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Windwos / Linux / Unix Solaries / Mac&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 를 사용하는 대부분의 유저는 90% 가 리눅스 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 빅데이터 처리를 위해서는 많은 수의 머신이 필수적으로 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 운영체제에 대한 비용이 상대적으로 적은 리눅스를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 는 메모리 관리를 운영체제에 맡김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상대적으로 메모리 관리에 뛰어난 unix 베이스의 운영체제를 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 지원 언어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원하는 언어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Java / Java Script&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Perl / PHP / Python&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Ruby / Scala / Erlang / Haskell&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 설치방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Linux 2023 에 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/try/download/community&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mongodb.com/try/download/community&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708589545262&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Try MongoDB Community Edition&quot; data-og-description=&quot;Try MongoDB Community Edition on premise non-relational database including the Community Server and Community Kubernetes Operator for your next big project!&quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/try/download/community&quot; data-og-url=&quot;https://www.mongodb.com/try/download/community-edition&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/L5z0h/hyVmVCyQEH/D5rSdKA2aGusqEaBCR6xOK/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/try/download/community&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/try/download/community&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/L5z0h/hyVmVCyQEH/D5rSdKA2aGusqEaBCR6xOK/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Try MongoDB Community Edition&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Try MongoDB Community Edition on premise non-relational database including the Community Server and Community Kubernetes Operator for your next big project!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드 페이지에서 운영체제에 맞는 배포판 선택 및 다운로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 압축 파일 다운로드 및 압축 해제로 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 리눅스는 tar gz 으로 다운로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 윈도우는 설치프로그램(msi) 다운로드 및 설치로 끝&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zwKPE/btsFeQW0P42/xZSnLryskTKNyU9udGA0vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zwKPE/btsFeQW0P42/xZSnLryskTKNyU9udGA0vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zwKPE/btsFeQW0P42/xZSnLryskTKNyU9udGA0vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzwKPE%2FbtsFeQW0P42%2FxZSnLryskTKNyU9udGA0vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1316&quot; height=&quot;1265&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yum 으로 설치하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-amazon/&quot;&gt;https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-amazon/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708589894771&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install MongoDB Community Edition on Amazon Linux &amp;mdash; MongoDB Manual&quot; data-og-description=&quot;Docs Home &amp;rarr; MongoDB Manual MongoDB AtlasMongoDB Atlas is a hosted MongoDB service option in the cloud which requires no installation overhead and offers a free tier to get started.Use this tutorial to install MongoDB 7.0 Community Edition on Amazon Linux&quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-amazon/&quot; data-og-url=&quot;https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-amazon/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/broWOu/hyVqrNmk7U/lVDoMI5zbuMaGLc68qyzh1/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/53RIM/hyVm5kWFyO/00An8mAb3JoK1ZGCaOZf0k/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-amazon/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-amazon/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/broWOu/hyVqrNmk7U/lVDoMI5zbuMaGLc68qyzh1/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601,https://scrap.kakaocdn.net/dn/53RIM/hyVm5kWFyO/00An8mAb3JoK1ZGCaOZf0k/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Install MongoDB Community Edition on Amazon Linux &amp;mdash; MongoDB Manual&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docs Home &amp;rarr; MongoDB Manual MongoDB AtlasMongoDB Atlas is a hosted MongoDB service option in the cloud which requires no installation overhead and offers a free tier to get started.Use this tutorial to install MongoDB 7.0 Community Edition on Amazon Linux&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yum 으로 설치하기 위해 repo 파일 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708589947580&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# cat /etc/yum.repos.d/mongodb-org-7.0.repo
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/amazon/2023/mongodb-org/7.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-7.0.asc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 버전을 설치하고 싶은경우 org 뒤에 버전을 작성 예) mongodb-org-7.0.6&lt;/p&gt;
&lt;pre id=&quot;code_1708590013958&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo yum instlal -y mongodb-org&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2536&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HOd8r/btsFeP4SZpS/DVnwLYKKkBkrRVKbpJG481/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HOd8r/btsFeP4SZpS/DVnwLYKKkBkrRVKbpJG481/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HOd8r/btsFeP4SZpS/DVnwLYKKkBkrRVKbpJG481/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHOd8r%2FbtsFeP4SZpS%2FDVnwLYKKkBkrRVKbpJG481%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2536&quot; height=&quot;722&quot; data-origin-width=&quot;2536&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터파일 경로 : /var/lib/mongo&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그파일 경로 : /var/log/mongodb&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속성파일 경로 : /etc/mongod.conf ( 속성파일에서 경로 변경 가능 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodb 시작&lt;/p&gt;
&lt;pre id=&quot;code_1708590179672&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl start mongod&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;95&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1zc6C/btsFclDOLak/905j6G3edriNukr99AK2Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1zc6C/btsFclDOLak/905j6G3edriNukr99AK2Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1zc6C/btsFclDOLak/905j6G3edriNukr99AK2Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1zc6C%2FbtsFclDOLak%2F905j6G3edriNukr99AK2Q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1036&quot; height=&quot;95&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;95&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>Mongodb 설치</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/620</guid>
      <comments>https://mozi.tistory.com/620#entry620comment</comments>
      <pubDate>Thu, 22 Feb 2024 16:56:51 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] MongoDB 의 기본개념</title>
      <link>https://mozi.tistory.com/619</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2IfoA/btsE92yyNKe/8sHsVh18FXFWZ3F9isgKnk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2IfoA/btsE92yyNKe/8sHsVh18FXFWZ3F9isgKnk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2IfoA/btsE92yyNKe/8sHsVh18FXFWZ3F9isgKnk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2IfoA%2FbtsE92yyNKe%2F8sHsVh18FXFWZ3F9isgKnk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;368&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key Value 와 다르게 여러 용도로 사용이 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마가 고정되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스키마 변경으로 이슈가 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터를 구조화해서 json 형태로 저장 ( key - value 화 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조인이 불가능하기 때문에 조인이 필요없도록 설계 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리맵 형태의 파일엔진 DB 이기 때문에 메모리에 의존적임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 크기가 성능을 좌우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리를 넘어서는 경우 성능이 급격히 저하됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쌓기만 하고 삭제가 업는 로직에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 로그 데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이벤트 참여 내역&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 세션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 필요한 금융, 결제, 빌링, 회원정보등에는 부적합 RDBMS 가 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 데이터 모델을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 속성의 이름과 값으로 이루어진 쌍의 집합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 속성은 문자열이나 숫자, 날짜 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열 또는 다른 도큐먼트를 지정하는것도 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 도큐먼트에 필요한 정보를 모두 담아야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 원쿼리로 해결되게끔 컬렉션 모델을 설계해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 조인이 불가능하므로 미리 삽입시켜야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 형태의 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 밖 { } 안의 데이터가 하나의 도큐먼트&lt;/p&gt;
&lt;pre id=&quot;code_1708587124077&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;_id&quot; : ObjectId(&quot;xxx&quot;),
  &quot;title&quot; : &quot;Mozi Blog&quot;,
  &quot;DB&quot; : [ &quot;MongoDB&quot;, &quot;MySQL&quot; ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 도큐먼트는 Json (JavaScript Object Notation) 형태로 만들어짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 경량 데이터 교환 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사람이 읽고 쓰기 쉽고, 기계가 파싱하고 생성하기 쉬움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클라이언트에서 처리 퍼포먼스 높음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- XML 이 표현하는 구조적인 정보 모두 표현 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마리스 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다양한 형태의 데이터 저장 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 모델의 유연한 변화 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read/Write 성능이 좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scale Out 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 많은 데이터 저장 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 장비 확장이 간단함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Json 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 직관적 이해 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 방법 쉽고 개발 편리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빅데이터 처리에 특화됨&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Memory Mapped 방식을 사용 (데이터 쓰기 시 OS 의 가상 메모리에 데이터를 넣은 후 비동기로 디스크에 기록한느 방식)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;방대한 데이터를 빠르게 처리 가능&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;OS 메모리를 활용하기 떄문에 메모리가 차면 하드디스크로 데이터 처리하여 속도가 급격히 느림&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하드웨어적인 측면에서 투자가 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 업데이트 중 장애 발생 시 데이터 손실 가능 - 분산처리가능 시스템의 부분결함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 인덱스 사용 시 충분한 메모리 확보 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 공간 소모가 RDBMS 에 비해 많음 ( 비효율적인 Key 중복 입력 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 JOIN 사용시 성능 제약이 따름 - 클라이언트 입장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transactions 지원이 RDBMS 대비 미약함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공되는 MapReduce 작업이 Hadoop 에 비해 성능이 떨어짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 불안정성 - 오픈소스의 한계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 양이 많을 경우 손실 가능성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩의 비정상적인 동작 가능성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레플리카 프로세스의 비정상 동작 가능성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 성능&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MongoDB vs MySQL&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 에서 동일한 데이터를 가지고 CRUD 를 수행할 때 대부분 MongoDB 가 빠름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 의 경우 싱글노드와 멀티노드 간에 성능 차이는 거의 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 멀티노드의 Insert 연산 중 연산 실패가 일어나는 경우 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산을 목적으로 한 DBMS 선택할 경우 RDBMS 에 비해 낮은 비용과 빠른 성능을 제공하는 MongoDB 가 유리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MongoDB vs MSSQL 2008&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기작업 성능 비교 : MongoDB 가 평균 100 배 이상 빠름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기작업 성능 비교 : MongoDB 가 평균 3배 이상 빠름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 쿼리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Json 형태로 명령어를 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C (Create) - save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R (Read) - find&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;U (Update) - update&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D (Delete) - remove&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수 인덱스 설정 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 검색 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트에 저장된 데이터와 중복 저장 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리가 부족한 시스템에서는 검색 속도 저하 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 복제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 분산시스템이기 때문에 복제가 상당히 중요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master-Slave 구조 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 복사본을 Slave 에 배치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 장애에 따른 데이터 손실 없이 Slave 데이터 사용 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Master 장애가 발생했을 때, Slave 에서 MAster 를 선출 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 손실을 최소화하기 위해 저널링 지원 ( 데이터 변화에 따른 모든 연산에 대한 로그 적재 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 샤딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량의 데이터를 저장하기 위한 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 소프트웨어적으로 데이터베이스를 분산시켜 처리하는 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤딩방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터베이스가 저장하고 있는 테이블을 테이블 단위로 분리하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터베이스가 저장하고 있는 테이블 자체를 분할하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 데이터베이스의 전통적인 분할 3계층 구조 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 응용 계층, 중개자 계층, 데이터 계층&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 응용 계층은 데이터에 접근하기 위해 중개자를 통해 모든 데이터의 입출력을 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 추상화된 한개의 데이터베이스가 존재하는 것처럼 운용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client - 응용 계층 ( mongos 로 요청 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mognos - 중계자 계층 ( client 요청 시 mongod 의 config 를 조회하여 데이터계층의 데이터를 조회 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongod - 데이터 계층 ( 샤딩 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 맵리듀스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량의 데이터를 안전하고 빠르게 처리하기 위한 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터를 분산하여 연산하고 다시 합치는 기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 맵과 리듀스 단계로 나누어 처리하며 사용자가 임의 코딩 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 입출력 데이터는 key value 형태로 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한대 이상의 하드웨어를 활용하는 분산 프로그래밍 모델&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 분산을 통해 분할된 조각으로 처리한 다음, 다시 모아서 훨씬 짧은 시간에 계산을 완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량 파일에 대한 로그 분석, 색인 구축 검색 등에 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일괄처리 방식으로 전체 데이터 셋을 분석할 필요가 있는 문제에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;MongoDB 구성&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB 는 수평적 확장이라는 특징을 가짐&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 대 이상의 서버로 구성하는 것이 일반적&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB 는 메모리 사용 가능량에 대비하여 성능이 좌우되기 때문에 독립된 서버에서 실행을 권장&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB 는 mongod 란느 실행파일을 단위로 실행&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 한곳에만 저장하면 데이터 손상 시 복구가 불가능하기 때문에 보통 Replica 단위를 구성하여 데이터를 복제&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;mongos 서버를 통해 마치 한대의 데이터베이스 서버처럼 사용 가능&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>MongoDB</category>
      <category>MongoDB 단점</category>
      <category>mongodb 복제</category>
      <category>MongoDB 샤딩</category>
      <category>MongoDB 인덱스</category>
      <category>MongoDB 장점</category>
      <category>MongoDB 특징</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/619</guid>
      <comments>https://mozi.tistory.com/619#entry619comment</comments>
      <pubDate>Thu, 22 Feb 2024 16:25:01 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 파이썬 문자를 숫자로, 숫자를 문자로 변경하기</title>
      <link>https://mozi.tistory.com/618</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xir2r/btr8LOxtO60/a8NAxiKc3dGNsH2fclEoO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xir2r/btr8LOxtO60/a8NAxiKc3dGNsH2fclEoO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xir2r/btr8LOxtO60/a8NAxiKc3dGNsH2fclEoO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxir2r%2Fbtr8LOxtO60%2Fa8NAxiKc3dGNsH2fclEoO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;211&quot; height=&quot;71&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이썬의 변수 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에 변수를 대입할 때는 문자나 숫자를 고려할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 대입될 때 타입이 정해지기 때문에 이 후 변수를 사용할 때는 타입을 고려해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 보면 10의 문자열 숫자와, 5의 정수형 숫자를 더하려고 하면 오류가 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680942380952&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;strnum=&quot;10&quot;
intnum=5

print(strnum+intnum)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4ny4F/btr8LKBMbGF/kQ3BFUcc5g2Mpmmk5FkLB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4ny4F/btr8LKBMbGF/kQ3BFUcc5g2Mpmmk5FkLB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4ny4F/btr8LKBMbGF/kQ3BFUcc5g2Mpmmk5FkLB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4ny4F%2Fbtr8LKBMbGF%2FkQ3BFUcc5g2Mpmmk5FkLB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;120&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문자를 숫자로 변경하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int 함수나 float 함수를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680942614067&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;strnum=&quot;10&quot;
intnum=5

print(int(strnum)+intnum)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBd73o/btr8Lf9UDcv/Ti872yxc6vrOIsOxQQTK2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBd73o/btr8Lf9UDcv/Ti872yxc6vrOIsOxQQTK2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBd73o/btr8Lf9UDcv/Ti872yxc6vrOIsOxQQTK2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBd73o%2Fbtr8Lf9UDcv%2FTi872yxc6vrOIsOxQQTK2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1235&quot; height=&quot;48&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수점이 있는 경우 int 함수를 사용하면 소수점 아랫자리는 없어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수점을 포함하고 싶은 경우 float 함수를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;숫자를 문자로 변경하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;str 함수를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외 format 이나 f-string 함수를 사용해서도 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680942686649&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;strnum=&quot;10&quot;
intnum=5

print(strnum+str(intnum))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx4BW2/btr8P0jytsg/pAyeJMSxchzKbIPQ22Kbd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx4BW2/btr8P0jytsg/pAyeJMSxchzKbIPQ22Kbd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx4BW2/btr8P0jytsg/pAyeJMSxchzKbIPQ22Kbd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx4BW2%2Fbtr8P0jytsg%2FpAyeJMSxchzKbIPQ22Kbd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1251&quot; height=&quot;56&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10 과 5 가 합쳐진 문자열 입니다.&lt;/p&gt;</description>
      <category>Computer Language/Python</category>
      <category>파이썬 int</category>
      <category>파이썬 str</category>
      <category>파이썬 문자를 숫자로</category>
      <category>파이썬 숫자를 문자로</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/618</guid>
      <comments>https://mozi.tistory.com/618#entry618comment</comments>
      <pubDate>Sat, 8 Apr 2023 17:32:11 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 파이썬 키보드로 값 입력받기</title>
      <link>https://mozi.tistory.com/617</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lBg3n/btr8UXUkcdH/SKuZ3yHDvp6LZfIiE02ck1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lBg3n/btr8UXUkcdH/SKuZ3yHDvp6LZfIiE02ck1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lBg3n/btr8UXUkcdH/SKuZ3yHDvp6LZfIiE02ck1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlBg3n%2Fbtr8UXUkcdH%2FSKuZ3yHDvp6LZfIiE02ck1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;211&quot; height=&quot;71&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;키보드로 값 입력받기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드상에서 변수에 값을 대입하는 경우도 있지만, 외부에서 값을 입력받아 대입하는 경우도 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input 함수는 키보드에서 입력받은 값을 변수에 대입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680942212785&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name = input('이름을 입력해보세요')
num = input('지역코드를 입력해보세요')

print(name, '/', num)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EAWLP/btr8LgOv5y8/yAbXBmjKSelmI81cQwt0r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EAWLP/btr8LgOv5y8/yAbXBmjKSelmI81cQwt0r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EAWLP/btr8LgOv5y8/yAbXBmjKSelmI81cQwt0r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEAWLP%2Fbtr8LgOv5y8%2FyAbXBmjKSelmI81cQwt0r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1251&quot; height=&quot;92&quot; data-origin-width=&quot;1251&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Computer Language/Python</category>
      <category>파이썬 키보드 입력받기</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/617</guid>
      <comments>https://mozi.tistory.com/617#entry617comment</comments>
      <pubDate>Sat, 8 Apr 2023 17:24:25 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 파이썬 변수에 값 대입하기</title>
      <link>https://mozi.tistory.com/616</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtNeGg/btr8Nebn4Us/VQxxTopBIr4V5Uak7dTAHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtNeGg/btr8Nebn4Us/VQxxTopBIr4V5Uak7dTAHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtNeGg/btr8Nebn4Us/VQxxTopBIr4V5Uak7dTAHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtNeGg%2Fbtr8Nebn4Us%2FVQxxTopBIr4V5Uak7dTAHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;211&quot; height=&quot;71&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터는 여러 값을 기억하기 위해 내부에 메모리라는 장치를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 변수는 컴퓨터의 메모리에 값을 저장하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터나 처리 결과를 기억합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수의 이름을 정하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 사용하려면 변수의 이름을 정해야 하는데, 아래와 같은 규칙이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영문사, 숫자, 언더스코어(_) 를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자로 시작할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대소문자가 구분됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드상 의미를 가지는 예약어는 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수에 값을 대입하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 = 값&amp;nbsp; 의 구조를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 들어간 값은 로직에 의해 변경될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수 사용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수에 값을 대입하고 출력하는 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680941608212&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int_var = 123
str_var = '가나다'

print(int_var, '/', str_var)

str_var = '하파카'
print(int_var, '/', str_var)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddTY7D/btr8LNL78gw/wKaRkIKkXCyKcdLKRsaJ6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddTY7D/btr8LNL78gw/wKaRkIKkXCyKcdLKRsaJ6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddTY7D/btr8LNL78gw/wKaRkIKkXCyKcdLKRsaJ6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddTY7D%2Fbtr8LNL78gw%2FwKaRkIKkXCyKcdLKRsaJ6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;71&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이썬 변수 주요 형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 변수의 주요한 형 입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 224px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;타입&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;int&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;정수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;bool&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;부울값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;float&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;부동 소수점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;complex&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;복소수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;list&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;리스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;tuple&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;튜플&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;str&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;문자열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;bytes&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;바이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;set&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;세트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;dict&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;딕셔너리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;class&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;클래스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;instance&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;인스턴스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 변수에 정수나 문자열 등 여러 형의 값을 바꿔 대입해도 프로그램에는 문제가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 변수를 사용할 때에는 어떤 종류의 값이 저장되어 있는지 주의해서 사용해야 합니다.&lt;/p&gt;</description>
      <category>Computer Language/Python</category>
      <category>파이썬 변수</category>
      <category>파이썬 변수 대입</category>
      <category>파이썬 변수 타입</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/616</guid>
      <comments>https://mozi.tistory.com/616#entry616comment</comments>
      <pubDate>Sat, 8 Apr 2023 17:17:20 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 파이썬 한 줄 또는 여러 줄 주석하기</title>
      <link>https://mozi.tistory.com/615</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFnblc/btr8UV3gIFD/JoozX6kPk1DJYVzqJxyF40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFnblc/btr8UV3gIFD/JoozX6kPk1DJYVzqJxyF40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFnblc/btr8UV3gIFD/JoozX6kPk1DJYVzqJxyF40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFnblc%2Fbtr8UV3gIFD%2FJoozX6kPk1DJYVzqJxyF40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;211&quot; height=&quot;71&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주석이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성하다 보면, 코드에 대한 설명을 하거나 코드를 임시로 비활성화 해야 하는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 주석 기능을 이용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주석처리 하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한줄만 하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 문자열을 이용하여 주석으로 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 이 붙은 뒤의 문자열은 주석으로 인지하여 처리하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680939981429&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 작성일자 : 2023.04.08
# 기능 : 함수 테스트

def func(name):
    print(name)   # name 의 문자열을 출력합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여러줄을 하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;' 혹은 &quot; 문자 세 개를 사용하여 처리할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1680940249112&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 작성일자 : 2023.04.08
# 기능 : 함수 테스트

def func(name):
    '''
      여기는 주석입니다.
      print('single', name);
    '''

    &quot;&quot;&quot;
      여기는 주석입니다.
      print('double', name)
    &quot;&quot;&quot;
    print('out', name)   # name 의 문자열을 출력합니다.


if __name__ == '__main__':
    func('Comment Test')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lVk1B/btr8NcklxNs/ZajCf8tayJ2q4JK08y0Fak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lVk1B/btr8NcklxNs/ZajCf8tayJ2q4JK08y0Fak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lVk1B/btr8NcklxNs/ZajCf8tayJ2q4JK08y0Fak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlVk1B%2Fbtr8NcklxNs%2FZajCf8tayJ2q4JK08y0Fak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1153&quot; height=&quot;63&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Language/Python</category>
      <category>파이썬 여러 줄 주석</category>
      <category>파이썬 주석</category>
      <category>파이썬 한 줄 주석</category>
      <author>꽁담</author>
      <guid isPermaLink="true">https://mozi.tistory.com/615</guid>
      <comments>https://mozi.tistory.com/615#entry615comment</comments>
      <pubDate>Sat, 8 Apr 2023 16:51:58 +0900</pubDate>
    </item>
  </channel>
</rss>