카테고리 없음

Percona Operator for MongoDB

참도다리 2023. 11. 12. 08:27
목차
1. Percona Operator for MongoDB
2. 복제
3. 장애 발생
4. 샤딩

 

 

 

NoSQL (Not Only SQL)

RDBMS(MySQL,PostgreSQL)처럼 고정된 스키마가 존재하지 않기 때문에 자유롭게 내용을 넣을 수 있다.

대표적으로  mongoDB를 많이사용함 (join기능 제공되지 않다가 추가되었다)

NoSQL 종류 : Key-Value Store(Redis), Wide Column Store(HBase, Cassandra), Document Store(mongoDB, CouchDB), Graph Store(Neo4j)

 

mongoDB

document db : JSON 기반의 Document 기반 데이터 관리이다. (키-값)

 

예시)

JSON document = {"id":"01", "languange":"java"}

 

기능

- 다양한 확장기능이 있으며 분산 확장(scale-out) 을 유연하게 할 수 있다. 또한 몽고DB는 분산 확장을 염두에 두고 도큐먼트 지향 데이터 모델로 설계되었기 때문에 데이터를 여러 서버에 더 쉽게 분산하게 해준다.

 

확장기능 소개

  • 확장 기능 : 보조 인덱스 secondary index , 범위 쿼리 range query , 정렬 sorting , 집계 aggregation , 공간 정보 인덱스 geospatial index 등
  • 손쉬운 사용 : 도큐먼트 지향 데이터베이스 document-oriented database. 관계형 모델을 사용하지 않은 주된 이유는 분산 확장 scale-out 을 쉽게 하기 위함
    • 행 개념 대신에 보다 유연한 모델인 도큐먼트 document 를 사용.
    • 내장 도규먼트와 배열을 허용함으로써 도큐먼트 지향 모델은 복잡한 계층 관계 hierarchical relationship 를 하나의 레코드로 표현할 수 있다.
    • 도큐먼트의 키와 값을 미리 정의 하지 않음. 따라서 고정된 스키마가 없다. 고정된 스키마가 없으므로 필요할 때마다 쉽게 필드를 추가하거나 제거할 수 있다.
  • 확장 가능한 설계 : 몽고DB는 분산 확장을 염두에 두고 설계됨, 도큐먼트 지향 데이터 모델은 데이터를 여러 서버에 더 쉽게 분산하게 해줌.  도큐먼트를 자동으로 재분배하고 사용자 요청을 올바른 장비에 라우팅함으로써 클러스터 내 데이터 양과 부하를 조절할 수 있다
  • 다양한 기능 : CRUD 이외도 DBMS 의 대부분의 기능을 제공
    • 인덱싱 : 보조 인덱스를 지원하며 고유 unique, 복합 compound, 공간 정보, 전문 full-text 인덱스 기능도 제공. 중첩된 도큐먼트 nested document 보조 인덱스도 지원
    • 집계 : 집계 파이프라인 aggregation pipeline 은 데이터베이스 최적화를 활용해, 서버 측에서 간단히 데이터를 처리하여 복잡한 분석 엔진 analytics engine 을 구착하게 해줌.
    • 특수한 컬렉션 유형 : 로그와 같은 최신 데이터를 유지하고자 세션이나 고정 크기 컬렉션(제한 컬렉션 capped collection)과 같이 특정 시간에 만료해야 하는 데이터에 대한 유효 시간(TTL) 컬렉션을 지원함. 또한 기준 필터 criteria filter 와 일치하는 도큐먼트에 한정된 부분 인덱스 partial index 를 지원함으로써 효율성을 높이고 필요한 저장 공간을 줄임.
    • 파일 스토리지 : 큰 파일과 파일 메타데이터를 편리하게 저장하는 프로토콜을 지원.
    • 고성능 : 동시성 ****concurrency 과 처리량을 극대화하기 위해 와이어드타이거 WiredTiger 스토리지 엔진에 기회적 락 opportunistic locking 을 사용함.
      • 따라서 캐시처럼 제한된 용량의 램으로 퀴리에 알맞은 인덱스를 자동으로 선택할 수 있다 → 요약하면 모든 측면에서 고성능을 유지하기 위해 설계됨.

 

mongoDB

 

기본 개념

  • 몽고DB 데이터의 기본 단위는 도큐먼트이며, 이는 관계형 데이터베이스의 과 유사함
  • 같은 맥락에서 컬렉션 collection 은 동적 스키마 dynamic schema 가 있는 테이블과 같다
  • 몽고DB의 단일 인스턴스는 자체적인 컬력션을 갖는 여러 개의 독립적인 데이터베이스를 호스팅한다
  • 모든 도큐먼트는 컬렉션 내에서 고유한 특수키인 “_id” 를 가진다
  • 몽고DB는 몽고 셸 The mongo Shell 이라는 간단하지만 강력한 도구와 함께 배포된다
    • mongo 셸은 몽고DB 인스턴스를 관리하고 몽고DB 쿼리 언어로 데이터를 조작하기 위한 내장 지원을 제공
    • 또한 사용자가 다양한 목적으로 자신의 스크립트를 만들고 로드할 수 있는 완전한 기능의 자바스크립트 해석기 JavaScript interpreter 다

도큐먼트

  • 몽고DB의 핵심은 정렬된 키와 연결된 값의 집합으로 이뤄진 도큐먼트다. 표현 방식은 맵 map, 해시 hash, 딕셔너리 dictionary 와 같은 자료 구조를 가짐.
  • 예를 들어 자바스크립트에서 도큐먼트는 객체로 표현된다.
# 키는 \0(null 문자)을 포함하지 않는다. \0은 키의 끝을 나타내는 데 사용
# .과 $ 문자는 몇 가지 특별한 속성을 가지며, 특정 상황에서 사용되며 보통 예약어 reserved word 로 취급됨
{"greeting" : "Hello, world!"}
{"greeting" : "Hello, world!", "views" : 3}

# 데이터형과 대소문자를 구별함
{"count" : 5} 와 {"count" : "5"} 는 다르다
{"count" : 5} 와 {"Count" : 5} 는 다르다

# 키가 중복될 수 없다, 아래는 올바른 도큐먼트가 아니다
{"greeting" : "Hello, world!", "greeting" : "Hello, world!"}

 

컬렉션

  • 도큐먼트모음. 컬렉션은 RDBMS 의 테이블에 대응됨
  • 컬렉션은 동적 스키마를 가진다. 하나의 컬렉션 내 도큐먼트들이 모두 다른 구조를 가질 수 있다는 의미다. 예를 들어 다음 도큐먼트들을 하나의 컬렉션에 저장할 수 있다
{"greeting" : "Hello, world!", "views" : 3}
{"signoff" : "Good night, and good luck"}

 

 

  • 도큐먼트들의 키, 키의 개수, 데이터형의 값은 모두 다르다. 다른 구조의 도큐먼트라도 같은 컬렉션에 저장할 수 있는데 “왜 별도의 컬렉션이 필요하지?” 합당한 이유가 있다
    • 같은 컬렉션에 다른 종류의 도큐먼트를 저장하면 개발자와 관리자에게 번거로운 일이 생길 수도 있다. 각 퀴리가 특정 스키마를 고수하는 도큐먼트를 반환하는지, 혹은 쿼리한 코드가 다른 구조의 도큐먼트를 다룰 수 있는지 확실히 확인하자.
    • 컬렉션별로 목록을 뽑으면 한 컬렉션 내 특정 데이터형별로 쿼리해서 목록을 뽑을 때보다 휠씬 빠르다.
    • 같은 종류의 데이터를 하나의 컬렉션에 모아두면 데이터 지역성 data localoty 에도 좋다.
    • 인덱스를 만들면 도큐먼트는 특정 구조를 가져야 한다 (고유 인덱스일 경우 특히 더 그렇다)
  • 스키마를 만들고 관련된 유형의 도큐먼트를 그룹화하는 데는 타당한 이유가 있다. 애플리케이션 스키마는 기본적으로 필요하지는 않지만 정의하면 좋다.

네이밍

  • 컬렉션은 이름으로 식별된다. 컬렉션명은 어떤 UTF-8 문자열이든 쓸수 있지만 제약 조건이 있다.
  • 서브컬렉션 subcollection 의 네임스페이스 namespace 에 .(마침표) 문자를 사용해 컬렉션을 체계화한다.
  • 예를 들어 블로그 기능이 있는 애플리케이션은 blog.posts 와 blog.authors 라는 컬렉션을 가질 수 있다.
  • 이는 단지 체계화를 위함이며 blog 컬렉션이나 자식 컬렉션 child collection 과는 아무런 관계가 없다 (심지어 blog 컬렉션은 없어도 된다)

데이터베이스

  • 몽고DB는 컬렉션에 도큐먼트를 그룹화할 뿐 아니라 데이터베이스에 컬렉션을 그룹 지어 놓는다.
  • 몽고DB의 단일 인스턴스는 여러 데이터베이스를 호스팅할 수 있으며, 각 데이터베이스를 완전히 독립적으로 취급할 수 있다.
  • 데이터베이스는 컬렉션과 마찬가지로 이름으로 식별된다. 데이터베이스명은 어떤 UTF-8 문자열이든 쓸수 있지만 제약 조건이 있다.
  • 예약된 데이터베이스 이름 : admin , local ,
    • admin : 인증권한 부여 역할을 함.
    • local : 단일 서버에 대한 데이터를 저장. 복제 셋 replica set 에서 local 은 복제 프로세스에 사용된 데이터를 저장. local 데이터베이스 자체는 복제되지 않는다.
    • config : 샤딩 sharding 된 몽고DB 클러스터는 config 데이터베이스를 사용해 각 샤드 shard 의 정보를 저장한다.
  • 컬렉션을 저장하는 데이터베이스의 이름을 컬렉션명 앞에 붙이면 올바른 컬렉션명인 네임스페이스를 얻는다.

몽고DB 셸 : 명령행에서 몽고DB 인스턴스와 상호작용하는 자바스크립트 셸을 제공

 

기본 사용 + CRUD : Skip

데이터형 : null, 불리언(참, 거짓), 숫자, 문자열, 날짜, 정규 표현식, 배열(셋, 리스트), 내장 도큐먼트, 객체 ID, 이진 데이터, 코드

id 와 ObjectId : 하나의 컬렉션에서 모든 도큐먼트는 “_id” 키를 가지며, 이 값은 컬렉션 내 모든 도큐먼트가 고유하게 식별되게 한다.

  • ObjectId 는 “_id” 의 기본 데이터형이다. 자동 증가하는 기본 키가 아닌 ObjectId 를 사용하는 주요 이유는 몽고DB의 분산 특성 때문이다.
  • 여러 서버에 걸쳐 자동으로 증가하는 기본 키를 동기화하는 작업은 어렵고 시간이 걸린다.
  • 몽고DB는 분산 데이터베이스로 설계됐기 때문에 샤딩된 환경에서 고유 식별자를 생성하는 것이 매우 중요했다
  • ObjectId 는 12바이트 스토리즈를 사용하며 24자리 16진수 문자열 표현이 가능하다. 바이트당 2자리를 사용한다.
# 연결없이 셸 실행
mongosh --nodb
exit

# 셸에 전달하여 스크립트 실행
mongosh server-1:30000/foo --quite script1.js

# 
mongosh
--------
help
db.help()     # 데이터베이스 수준의 도움말
db.foo.help() # 컬렉션 수준의 도움말
db.movies.updateOne # 함수의 기능 도움말, 함수명을 괄호 없이 입력

# load 함수로 스크립트 실행
load("script1.js")

 

 

몽고DB를 사용을 권장하지 않는 경우

  • 다양한 유형의 데이터를 여러 차원에 걸쳐 조인하는 작업은 관계형 데이터베이스에 적합하다. 몽고DB는 이러한 작업을 잘 처리하지 못하며 거의 다룰 일이 없다.
  • 몽고DB보다 관계형 데이터베이스를 사용하는 가장 큰 이유는 몽고DB를 지원하지 않는 도구를 사용할 수 있기 때문이다. 워드프레스등 아직 관계형 데이터베이스 생태계를 따라가지 못한다.

Percona Operator for MongoDB

  • Percona Server for MongoDB (PSMDB) vs MongoDB - 링크
    • Percona Server for MongoDB 6.0 is based on MongoDB 6.0.
    • Percona Server for MongoDB extends MongoDB Community Edition to include the functionality that is otherwise only available in MongoDB Enterprise Edition.
  • Installing Percona Server for MongoDB - 링크
  • Percona Memory Engine - 링크
  • Hot Backup - 링크
  • Authentication - 링크
  • HashiCorp Vault integration - 링크
  • Profiling rate limit - 링크
  • Tune parameters - 링크
  • (참고) 와이어드타이거 스토리지 엔진 WiredTiger Storage Engines : 몽고DB의 기본 스토리지 엔진
    • 서버가 시작되면 데이터 파일을 열고 체크포인팅 checkpointing 과 저널링 프로세스를 시작한다.
    • 운영체제와 함께 작동하며, 운영체제는 데이터를 디스크로 플러시하는 것뿐 아니라 데이터를 안팎으로 페이징하는 데 중점을 준다.
    • 압축컬렉션인덱스에 대해 기본적으로 설정돼 있다. 기본 압축 알고리즘은 구글의 스내피 snappy 다.
    • 압축은 데이터베이스의 스토리지 사용을 최소화하는 대신 추가적인 CPU 요구사항이 있다.
    • 도큐먼트 수준의 동시성은 컬렉션에 있는 여러 클라이언트의 서로 다른 도큐먼트를 동시에 갱신할 수 있게 한다.
    • 와이어드타이거는 다중 버전 동시성 제어 MultiVersion Concurrency Control (MVCC) 을 사용해 읽기와 쓰기 작업을 격리함으로써, 작업 시작 시 데이터의 일관된 특정 시점 뷰를 클라이언트가 볼 수 있게 한다.
    • 체크포인팅은 데이터의 일관된 특정 시점 스냅샷을 생성하며 60초마다 발생한다. 스냅샷의 모든 데이터를 디스크에 쓰고 관련 메타데이터를 갱신하는 작업이 포함한다.
    • 체크포인팅을 사용하는 저널링은 mongod 프로세스 실패 시 데이터가 손실되는 시점이 없게 한다. 와이어드타이거는 수정 사항을 적용하기 전에 저장하는 로그 선행 기입(저널)을 사용한다.

Percona Operator for MongoDB - 링크 Github RN

 

Percona Operator for MongoDB

Contact Us For free technical help, visit the Percona Community Forum. To report bugs or submit feature requests, open a JIRA ticket. For paid support and managed or consulting services , contact Percona Sales.

docs.percona.com

    • Percona Operator 는 PSMDB 를 생성 변경 삭제를 관리 + Percona Server for MongoDB replica set.
    • PSMDB 몽고 지원 버전 : MongoDB v4.4 , v5.0 , v6.0 - Link
    • 공식 지원 플랫폼 : GKE 1.24~1.28 , AWS EKS 1.24~1.28, Azure AKS 1.25~1.28, Minikube 1.31.2 등 - Link
    • 리소스 : 최소 3개 노드 - 샤딩 미사용(램 2GB, 2CPU, 볼륨 60GB) , 샤딩 사용(램 6GB, 4CPU)
    • Design overview - 링크

    • one primary server and several secondary ones (two in the picture), and the client application accesses the servers via a driver.
      • Sharded cluster 구성
      •  
      •  
        • the case of a sharded cluster, each shard is a replica set which contains a subset of data stored in the database, and the mongos query router acts as an entry point for client applications - Link
      • 오퍼레이터 구성

      •  
        • 오퍼레이터는 PerconaServerMongoDB 오브젝트를 사용 하여, 각 Percona Server for MongoDB 를 관리하며 적절한 Replica Set 을 제공한다


오퍼레이터 설치

# Install Percona server for MongoDB on Kubernetes - EKS Generic 파라미터 이미지버전 Chart
# CRD 설치
kubectl apply --server-side -f https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/crd.yaml
kubectl get crd | grep psmdb

# namespace 생성
kubectl create ns psmdb

# 실습 편리를 위해서 네임스페이스 변경
kubectl get pod
kubectl ns psmdb       # 꼭! 꼭! psmdb 로 네임스페이스 변경을 하시고 실습 진행을 하셔야 됩니다!
kubectl get pod

# RBAC 설치
kubectl apply -f https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/rbac.yaml
kubectl get-all -n psmdb

# 오퍼레이터 설치
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/operator.yaml
cat operator.yaml | yh
kubectl apply -f operator.yaml
kubectl get deploy,pod
kubectl get-all -n psmdb

# 각자 닉네임 변수 지정 : 클러스터 이름으로 사용됨
MYNICK=<각자 자신의 닉네임>
echo "export MYNICK=<각자 자신의 닉네임>" >> /etc/profile
MYNICK=gasida
echo "export MYNICK=gasida" >> /etc/profile

# 계정 정보를 위한 secret 생성
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/secrets.yaml
cat secrets.yaml
cat secrets.yaml | sed -e "s/my-cluster-name/$MYNICK/" | kubectl apply -f -
cat secrets.yaml
kubectl get secret $MYNICK-secrets
kubectl get secret $MYNICK-secrets -o json | jq .data
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_DATABASE_ADMIN_USER | base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_DATABASE_ADMIN_PASSWORD| base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_CLUSTER_ADMIN_USER | base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_CLUSTER_ADMIN_PASSWORD| base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_USER_ADMIN_USER | base64 -d ; echo
kubectl get secret $MYNICK-secrets -o json | jq -r .data.MONGODB_USER_ADMIN_PASSWORD| base64 -d ; echo

# 신규 터미널 : 모니터링
watch kubectl get psmdb,sts,pod,svc,ep,pvc

# 클러스터 생성 : 복제 세트(3개 파드) replsets(rs0, size 3)
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/cr.yaml
cat cr.yaml
grep ^[^#] cr.yaml | yh

curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/cluster1.yaml
cat cluster1.yaml | yh
cat cluster1.yaml | sed -e "s/my-cluster-name/$MYNICK/" | kubectl apply -f - && kubectl get psmdb -w

# 클러스터 생성 정보 확인 : 약자 psmdb
kubectl get perconaservermongodbs
kubectl get psmdb
NAME        ENDPOINT                                STATUS   AGE
gasida-db   gasida-db-rs0.psmdb.svc.cluster.local   ready    160m

## 클러스터 상세 정보 확인
kubectl get psmdb gasida -o yaml | kubectl neat | yh

# 클러스타 파드 정보 확인
kubectl get sts,pod -owide
kubectl get svc,ep
kubectl df-pv
kubectl get pvc,pv

# 노드 정보 확인 : affinity.antiAffinityTopologyKey: "kubernetes.io/hostname" 이해하기
# https://docs.percona.com/percona-operator-for-mongodb/constraints.html#affinity-and-anti-affinity
kubectl describe node | more
kubectl get node --label-columns=kubernetes.io/hostname,topology.kubernetes.io/zone

# mongodb 이미지 버전 확인
kubectl get perconaservermongodbs $MYNICK -o jsonpath={.spec.image} ; echo
percona/percona-server-mongodb:6.0.9-7

# (옵션) 설치 리소스 확인
kubectl get-all --namespace=psmdb
kubectl get-all --since 10m
kubectl get-all --only-scope=cluster

## (참고) psmdb 클러스터 삭제 시
cat cluster1.yaml | sed -e "s/my-cluster-name/$MYNICK/" | kubectl delete -f -

 

 

헤드리스 서비스 접속 정보 확인

 

# 헤드리스 서비스 확인 : ClusterIP None
kubectl get svc,ep

# 엔드포인트 슬라이스 정보 확인 
kubectl describe endpointslices
kubectl get endpointslices

# netshoot 이미지로 netdebug 파드에 zsh 실행
kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh
--------------------
## 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=gasida

## 헤드리스 서비스 접속 도메인 확인
nslookup $MYNICK-rs0
nslookup -type=srv $MYNICK-rs0
nslookup $MYNICK-rs0-0.$MYNICK-rs0
nslookup $MYNICK-rs0-1.$MYNICK-rs0
nslookup $MYNICK-rs0-2.$MYNICK-rs0

exit
--------------------

 

 

몽고DB 기본사용

# myclient 데몬셋 배포
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/myclient.yaml
cat myclient.yaml | yh
VERSION=4.4.24-23 envsubst < myclient.yaml | kubectl apply -f -
kubectl get pod -l name=mongodb -owide

# 몽고 config 확인 : CLUSTER_USER로 접속 후 확인
kubectl get cm $MYNICK-rs0-mongod -o yaml | kubectl neat | yh

# [터미널1] 클러스터 접속(ADMIN_USER)
cat secrets.yaml | yh
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://userAdmin:userAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
----------------------
rs0:PRIMARY> show dbs
admin   0.000GB
config  0.000GB
local   0.001GB

rs0:PRIMARY> db
admin

rs0:PRIMARY> db.getUsers()
...

## 데이터베이스를 사용할 유저 생성
rs0:PRIMARY> db.createUser({user: "doik" , pwd: "qwe123" , roles: [ "userAdminAnyDatabase", "dbAdminAnyDatabase","readWriteAnyDatabase"]})

## 복제 정보 확인 시도
rs0:PRIMARY> rs.status()

# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> db
rs0:PRIMARY> rs.status()
rs0:PRIMARY> rs.status()['members']

# 몽고 config 적용 확인
rs0:PRIMARY> db.getProfilingLevel()

 

 

몽고DB CRUD 1편 : db, collection 으로 구성 → 데이터는 각 collection 에 document 형식(python dictionary) 로 저장, collection 들의 논리적인 집합이 database

# [터미널3] 클러스터 접속(doik)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://doik:qwe123@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
# doik 테이터베이스 선택(없으면 데이터베이스 생성됨) 및 test 콜렉션에 도큐먼트 1개 넣기
rs0:PRIMARY> use doik

# 콜렉션 확인
rs0:PRIMARY> show collections

# 콜렉션 생성
## (옵션) capped:true 최초 제한된 크기로 생성된 공간에서만 데이터를 저장하는 설정 (고성능, 저장공간차면 기존 공간 재사용, 일정시간만 저장하는 로그에 적합)
rs0:PRIMARY> db.createCollection("test", {capped:true, size:10000})

# 콜렉션 확인
rs0:PRIMARY> show collections
test

# 콜렉션에서 도큐먼트 조회
rs0:PRIMARY> db.test.find()

# 콜렉션에 도큐먼트 추가
rs0:PRIMARY> db.test.insertOne({ hello: 'world' })

# 콜렉션에서 도큐먼트 조회
rs0:PRIMARY> db.test.find()
rs0:PRIMARY> db.test.find({},{_id:0})
{ "hello" : "world" }

# 현재 데이터베이스의 콜렉션 통계 정보 확인
rs0:PRIMARY> db.test.stats()

# 콜렉션 삭제하기
rs0:PRIMARY> db.test.drop()

# 콜렉션 확인
rs0:PRIMARY> show collections

# 현재 데이터베이스의 통계 정보 확인
rs0:PRIMARY> db.stats()

 

  • 몽고DB CRUD 2편
    • PRIMARY KEY를 위한 별도 컬럼 만들 필요 없음, mongodb는 collection에서 _id가 각 Document마다 자동생성되어 primary key 역햘을 함
    • 컬럼마다 데이터 타입을 정할 필요 없음 ("컬럼명": 컬럼값 이 기본 형태임)
    • collection 구조 변경 : RDBMS 처럼 ALTER TABLE 은 기본적으로 collection 에서는 필요 없음
  •  

Create 생성 Document 입력 : insertOne, insertMany

# 콜렉션 생성
db.createCollection("users")

# Document 입력
db.users.insertOne(
 {
  name: "gasida",
  age: 30,
  status: "pending",
 }
)

# 콜렉션 확인
db.users.find()

# insertMany : 파이썬의 List [] 문법을 사용 >> RDBMS 처럼 컬럼(스키마)를 편하게 입력 가능
db.users.insertMany(
 [
  { subject: "abc1", author: "xyz1", views: 10 },
  { subject: "abc2", author: "xyz2", views: 20 },
  { subject: "abc3", author: "xyz3", views: 30 }
 ]
)

# 콜렉션 확인
db.users.find()
...

 

Search 읽기/검색 Document 읽기(검색) : find(), findOne

 

# 콜렉션 생성
db.createCollection("employees")

# insertMany
db.employees.insertMany(
[
  { user_id: "user01", age: 45, status: "A" },
  { user_id: "user02", age: 35, status: "A" },
  { user_id: "user03", age: 25, status: "B" },
  { user_id: "user04", age: 20, status: "A" },
  { user_id: "abcd01", age: 28, status: "B" }
]
)

# 검색
db.employees.find()

# 검색 예제 : 몽고DB vs RDBMS
db.employees.find()  # SELECT * FROM people
db.employees.find({ }, { user_id: 1, status: 1 })  # SELECT _id, user_id, status FROM people
db.employees.find({ },{ user_id: 1, status: 1, _id: 0 })  # SELECT user_id, status FROM people
db.employees.find({ status: "A" })  # SELECT * FROM people WHERE status = "A"
db.employees.find({ status: "A", age: 20 }) # SELECT * FROM people WHERE status = "A" AND age = 20
db.employees.find({ $or: [ { status: "A" } , { age: 25 } ] }) # SELECT * FROM people WHERE status = "A" OR age = 25

# 비교 문법 : equal , greater than , greater than equal , less than , less than or equal , not equal, nin
$eq     =    Matches values that are equal to a specified value.
$gt     >    Matches values that are greater than a specified value.
$gte    >=   Matches values that are greater than or equal to a specified value.
$in          Matches any of the values specified in an array.
$lt     <    Matches values that are less than a specified value.
$lte    <=   Matches values that are less than or equal to a specified value.
$ne     !=   Matches all values that are not equal to a specified value.
$nin         Matches none of the values specified in an array.

# 비교 예제
db.employees.find({ age: { $gt: 25 } })  # SELECT * FROM people WHERE age > 25
db.employees.find({ age: { $lt: 25 } })  # SELECT * FROM people WHERE age < 25
db.employees.find({ age: { $gt: 25, $lte: 50 } }) # SELECT * FROM people WHERE age > 25 AND age <= 50
db.employees.find( { user_id: /use/ } )
db.employees.find( { user_id: { $regex: /use/ } } )  # SELECT * FROM people WHERE user_id like "%use%"
db.employees.find( { user_id: /^use/ } )
db.employees.find( { user_id: { $regex: /^use/ } } )  # SELECT * FROM people WHERE user_id like "use%"
db.employees.find( { status: "A" } ).sort( { user_id: 1 } )  # SELECT * FROM people WHERE status = "A" ORDER BY user_id ASC 
db.employees.find( { status: "A" } ).sort( { user_id: -1 } )  # SELECT * FROM people WHERE status = "A" ORDER BY user_id DESC
db.employees.find().count()  # SELECT COUNT(*) FROM people
db.employees.count()
db.employees.count( { user_id: { $exists: true } } )
db.employees.find( { user_id: { $exists: true } } ).count()  # SELECT COUNT(user_id) FROM people
db.employees.count( { age: { $gt: 30 } } )
db.employees.find( { age: { $gt: 30 } } ).count()  # SELECT COUNT(*) FROM people WHERE age > 30
db.employees.distinct( "status" )  # SELECT DISTINCT(status) FROM people
db.employees.findOne()
db.employees.find().limit(1)       # SELECT * FROM people LIMIT 1

Update 수정 Document 수정: updateOne, updateMany

$set: field 값 설정
$inc: field 값을 증가시키거나, 감소시킴
 예) $inc: { age: 2} - age 값을 본래의 값에서 2 증가

# 다음 Document 데이터 수정하기
age 가 30 보다 큰 Document 의 status 를 B 로 변환하기
**db.employees.updateMany( { age: {$gt: 30} }, { $set: {status: "B"} } )**

# 실습 결과 확인
**db.employees.find({}, {_id:0})**

Delete 삭제 Document 삭제 - removeOne, removeMany

# 삭제1
db.employees.deleteMany( { status: "A" } )  # SQL로 변환하면, DELETE FROM people WHERE status = "A"
# 실습 결과 확인
db.employees.find({}, {_id:0})

# 삭제2
db.employees.deleteMany({})  # SQL로 변환하면, DELETE FROM people
# 실습 결과 확인
db.employees.find({}, {_id:0})

# 콜렉션 삭제하기
rs0:PRIMARY> db.users.drop()
rs0:PRIMARY> db.employees.drop()

 

 

로깅 https://docs.percona.com/percona-operator-for-mongodb/debug-logs.html

 

Percona Operator for MongoDB

Contact Us For free technical help, visit the Percona Community Forum. To report bugs or submit feature requests, open a JIRA ticket. For paid support and managed or consulting services , contact Percona Sales.

docs.percona.com

# 오퍼레이터 로그
kubectl logs -l name=percona-server-mongodb-operator -f

# Check logs of the mongod container, parsing the output with jq JSON processor
kubectl logs gasida-rs0-0 -c mongod --since=1m | jq -R 'fromjson?'
kubectl logs gasida-rs0-1 -c mongod --since=1m | jq -R 'fromjson?'
kubectl logs gasida-rs0-2 -c mongod --since=1m | jq -R 'fromjson?'

#
kubectl logs gasida-rs0-0 -c mongod -f | jq -R 'fromjson?'
kubectl logs gasida-rs0-1 -c mongod -f | jq -R 'fromjson?'
kubectl logs gasida-rs0-2 -c mongod -f | jq -R 'fromjson?'

#
kubectl logs -l app.kubernetes.io/component=mongod -c mongod --since=1m | jq -R 'fromjson?'
kubectl logs -l app.kubernetes.io/component=mongod -c mongod --since=10s | jq -R 'fromjson?'
kubectl logs -l app.kubernetes.io/component=mongod -c mongod -f | jq -R 'fromjson?'

 

  • Pause/resume Percona Server for MongoDB - Link
  • 외부 서비스 Expose 설정 후 접속 확인 해보자 : LoadBalancer(CLB or NLB) → ExternalDNS 연결

복제

실습을 위한 터미널 접속 정보

# [터미널1] 클러스터 접속(ADMIN_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://userAdmin:userAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"

# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"

# [터미널3] 클러스터 접속(doik)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://doik:qwe123@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"

 

복제 셋 확인 : 오피로그 확인

- 복제 세 정보 상세 확인

# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY>

# 복제 셋 정보 확인 : 구성원 상태 확인
rs0:PRIMARY> rs.status()
rs0:PRIMARY> rs.status()['members']
[
	{
		"_id" : 0,
		"name" : "gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"health" : 1,
		"state" : 1,
		"stateStr" : "PRIMARY",
		"uptime" : 6217,
		"optime" : {
			"ts" : Timestamp(1654993085, 1),
			"t" : NumberLong(1)
		},
...

# 복제 셋 정보 확인* : 간략히 확인, 자주 확인하는 명령어
rs0:PRIMARY> db.isMaster()
{
	"topologyVersion" : {
		"processId" : ObjectId("62a5187aac216c48b5515735"),
		"counter" : NumberLong(8)
	},
	"hosts" : [
		"gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"gasida-db-rs0-1.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"gasida-db-rs0-2.gasida-db-rs0.psmdb.svc.cluster.local:27017"
	],
	"setName" : "rs0",
	"setVersion" : 120025,
	"ismaster" : true,
...

# 오피로그 정보 확인 : 크기, 연산 시간
rs0:PRIMARY> rs.printReplicationInfo()
configured oplog size:   1794.9248046875MB
log length start to end: 27832secs (7.73hrs)
oplog first event time:  Fri Jun 10 2022 04:23:06 GMT+0000 (UTC)
oplog last event time:   Fri Jun 10 2022 12:06:58 GMT+0000 (UTC)
now:                     Fri Jun 10 2022 12:07:02 GMT+0000 (UTC)

# 동기화 상태 확인 : 세컨더리 구성원이 프라이머리의 어디까지 정보를 동기화했는지 확인
rs0:PRIMARY> rs.printSecondaryReplicationInfo()
source: my-db-psmdb-db-rs0-1.my-db-psmdb-db-rs0.psmdb.svc.cluster.local:27017
	syncedTo: Fri Jun 10 2022 01:32:51 GMT+0000 (UTC)
	0 secs (0 hrs) behind the primary
source: my-db-psmdb-db-rs0-2.my-db-psmdb-db-rs0.psmdb.svc.cluster.local:27017
	syncedTo: Fri Jun 10 2022 01:32:51 GMT+0000 (UTC)
	0 secs (0 hrs) behind the primary

# 복제 옵션 정보 확인
rs0:PRIMARY> rs.conf()
{
	"_id" : "rs0",    # 복제 세트 이름
	"version" : 99912,
	"members" : [
		{
			"_id" : 0,
			"host" : "my-db-psmdb-db-rs0-0.my-db-psmdb-db-rs0.psmdb.svc.cluster.local:27017",   # 구성원 호스트
			"arbiterOnly" : false,
			"buildIndexes" : true,
			"hidden" : false,
			"priority" : 2,
			"tags" : {
				"podName" : "my-db-psmdb-db-rs0-0",
				"serviceName" : "my-db-psmdb-db"
			},
			"secondaryDelaySecs" : NumberLong(0),
			"votes" : 1
		},

 

 

oplog : 오피로그 상세 확인

 

# [터미널2] 클러스터 접속(CLUSTER_USER)# 
rs0:PRIMARY> use local
switched to db local

rs0:PRIMARY> db
local

# 오피로그 사이즈 확인
rs0:PRIMARY> db.oplog.rs.stats().maxSize
1882136320

# 오피로그 확인
rs0:PRIMARY> db.oplog.rs.find().limit(2).pretty()
{
	"op" : "n",
	"ns" : "",
	"o" : {
		"msg" : "initiating set"
	},
	"ts" : Timestamp(1654814641, 1),   # 오피로그의 타임스탬프 값, 동기화 기준값으로 중복되지 않음
	"v" : NumberLong(2),
	"wall" : ISODate("2022-06-09T22:44:01.228Z")
}
{
	"op" : "c",   # Operation Type 어떤 종류의 작업 수행, i 삽입, u 수정, d 삭제, c 명령, n 아무 작업도 안함
	"ns" : "config.$cmd",   # 이 작업으로 영향ㅂ다은 데이터베이스와 컬렉션
	"ui" : UUID("718f318e-1bf2-445e-bf17-dfc5d6e2b987"),
	"o" : {
		"create" : "transactions",
		"idIndex" : {
			"v" : 2,
			"key" : {
				"_id" : 1
			},
			"name" : "_id_"
		}
	},
	"ts" : Timestamp(1654814641, 3),
	"t" : NumberLong(1),              # Primary Term 현재 복제세트에서 몇 번째로 선출된 프라이머리인지 구분하는 값
	"v" : NumberLong(2),
	"wall" : ISODate("2022-06-09T22:44:01.259Z")
}
...
{
	"op" : "i",
	"ns" : "admin.system.keys",
	"ui" : UUID("b237feb9-2ab1-46c4-92f7-12b91ad5ece8"),
	"o" : {
		"_id" : NumberLong("7107462145146617861"),
		"purpose" : "HMAC",
		"key" : BinData(0,"2ynIIoLIe+kzTe7hG6ZhqeNOCFA="),
		"expiresAt" : Timestamp(1662610986, 0)
	},
	"ts" : Timestamp(1654834986, 10),
	"t" : NumberLong(1),
	"v" : NumberLong(2),
	"wall" : ISODate("2022-06-10T04:23:06.051Z")
}

 

 

복제 테스트* : rs0:PRIMARY> rs.status()['members'] 에서 프라이머리 파드 확인해두기

# [터미널1] 프라이머리 파드 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-0.$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> use doik
rs0:PRIMARY> db.test.insertOne({ reptest: 1 })
rs0:PRIMARY> db.test.find()
rs0:PRIMARY> db.test.count()

# [터미널2] 세컨더리 파드1 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- bash -il
--------------------------------------
# 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=gasida
echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-1.$MYNICK-rs0.psmdb.svc/admin?ssl=false"
while true; do echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-1.$MYNICK-rs0.psmdb.svc/admin?ssl=false" | grep -v Error; date; sleep 1; done
--------------------------------------

# [터미널3] 세컨더리 파드2 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- bash -il
--------------------------------------
# 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=gasida
echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false"
while true; do echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false" | grep -v Error; date; sleep 1; done
--------------------------------------

# [터미널1] 프라이머리 파드 접속(doik) : 대량의 도큐먼트 생성 및 복제 확인
rs0:PRIMARY> for (i=0; i<1000; i++) {db.test.insert({count: i, "created_at" : new Date()})}
rs0:PRIMARY> db.test.find({},{_id:0})
...
Type "it" for more

 

 

장애 발생 1 및 동작 확인: 강제로 rs0-Y 프라이머리 파드 1개 삭제

# [터미널3] 세컨더리 파드2 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- bash -il
--------------------------------------
# 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=gasida
echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false"
while true; do echo $'rs.secondaryOk()\nuse doik\ndb.test.count()' | mongo --quiet "mongodb://doik:qwe123@$MYNICK-rs0-2.$MYNICK-rs0.psmdb.svc/admin?ssl=false" | grep -v Error; date; sleep 1; done
--------------------------------------

# [터미널1] 모니터링
watch -d "kubectl get psmdb;echo; kubectl get pod,pvc -l app.kubernetes.io/component=mongod -owide"

# [터미널2] 클러스터 접속(CLUSTER_USER) : 프라이머리 파드 확인
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> rs.status()['members']
rs0:PRIMARY> db.isMaster()

# 오퍼레이터 로그
kubectl logs -l name=percona-server-mongodb-operator -f

# 프라이머리 파드가 배포 정보 확인
kubectl get pod -l app.kubernetes.io/instance=$MYNICK -owide

# 강제로 rs0-Y 프라이머리 파드 1개 삭제
kubectl delete pod $MYNICK-rs0-0
kubectl delete pod $MYNICK-rs0-1
kubectl delete pod $MYNICK-rs0-2

# [터미널2] 클러스터 접속(CLUSTER_USER) : 프라이머리 파드 확인
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> rs.status()['members']
rs0:PRIMARY> db.isMaster()

 

 

장애 발생 2 : 강제로 프라이머리 파드가 있는 노드를 drain

 

# 오퍼레이터 로그
kubectl logs -l name=percona-server-mongodb-operator -f

# [터미널2] 클러스터 접속(CLUSTER_USER) : 프라이머리 파드 확인
kubectl exec ds/myclient -it -- mongo --quiet "mongodb+srv://clusterAdmin:clusterAdmin123456@$MYNICK-rs0.psmdb.svc.cluster.local/admin?replicaSet=rs0&ssl=false"
rs0:PRIMARY> rs.status()['members']
rs0:PRIMARY> db.isMaster()

# 프라이머리 파드가 배포 정보 확인
kubectl get psmdb
kubectl get pod -l app.kubernetes.io/instance=$MYNICK -owide

# 워커노드 drain
# kubectl drain <<노드>> --ignore-daemonsets --delete-emptydir-data
kubectl get node
NODE=<각자 자신의 EC2 노드 이름 지정>
NODE=ip-192-168-3-96.ap-northeast-2.compute.internal
kubectl drain $NODE --delete-emptydir-data --force --ignore-daemonsets && kubectl get node -w

# [터미널1] 클러스터 접속(CLUSTER_USER) : 장애 상태 확인
rs0:PRIMARY> rs.status()['members']
[
	{
		"_id" : 0,
		"name" : "gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017",
		"health" : 0,
		"state" : 8,
		"stateStr" : "(not reachable/healthy)",
		"uptime" : 0,
		"optime" : {
			"ts" : Timestamp(0, 0),
			"t" : NumberLong(-1)
		},
		"optimeDurable" : {
			"ts" : Timestamp(0, 0),
			"t" : NumberLong(-1)
		},
		"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
		"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
		"lastAppliedWallTime" : ISODate("2022-06-12T01:32:45.665Z"),
		"lastDurableWallTime" : ISODate("2022-06-12T01:32:45.665Z"),
		"lastHeartbeat" : ISODate("2022-06-12T01:33:37.678Z"),
		"lastHeartbeatRecv" : ISODate("2022-06-12T01:32:50.668Z"),
		"pingMs" : NumberLong(0),
		"lastHeartbeatMessage" : "Error connecting to gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017 :: caused by :: Could not find address for gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017: SocketException: Host not found (authoritative)",
		"syncSourceHost" : "",
		"syncSourceId" : -1,
		"infoMessage" : "",
		"configVersion" : 120025,
		"configTerm" : -1
	},

# 동작 확인 후 uncordon 설정
kubectl get psmdb
kubectl uncordon $NODE


# 장애 발생 3 및 동작 확인: 강제로 **노드 2개를 drain** : 직접 테스트 해보세요! → 1대만 정상일 경우, Read 가능한지? Write 가능한지?
# 다음 실습을 위해서 삭제 
kubectl delete psmdb $MYNICK 삭제 후 1분 정도 후 입력 ⇒ kubectl delete pvc -l app.kubernetes.io/instance=$MYNICK

 

샤딩

설치

# 신규 터미널 : 모니터링
watch kubectl get psmdb,sts,pod,svc,ep,pvc,pv


# 클러스터 생성 : 복제 셋 2개(rs-0, rs1), mongos(파드 3개), cfg(파드 3개) >> 6분 정도 소요
kubectl get secret $MYNICK-secrets
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/cluster2.yaml
cat cluster2.yaml | yh
cat cluster2.yaml | sed -e "s/my-cluster-name/$MYNICK/" | kubectl apply -f -

# 클러스터 생성 정보 확인
kubectl get psmdb
kubectl get psmdb gasida -o yaml | kubectl neat | yh

# 클러스타 파드 정보 확인
kubectl get sts,pod -owide
kubectl get svc,ep
kubectl df-pv
kubectl get pvc,pv

Percona Server for MongoDB Sharding : https://docs.percona.com/percona-operator-for-mongodb/sharding.html

 

Percona Operator for MongoDB

Contact Us For free technical help, visit the Percona Community Forum. To report bugs or submit feature requests, open a JIRA ticket. For paid support and managed or consulting services , contact Percona Sales.

docs.percona.com

 

샤드 정보 확인

- **Shard** : 데이터베이스의 Replica Set
- **Mongos** : 클라이언트 애플리케이션의 쿼리를 처리하는 **라우터**
- **Config Servers** : Replica Set 의 메타데이터와 샤드 클러스터의 정보를 저장
- **샤드 접근** : `mongos` Pods - query routers, which acts as an entry point for client applications,

 

 

# mongos 라우터 접속 서비스 정보 확인
kubectl get svc,ep $MYNICK-mongos

# [터미널1] 클러스터 접속(ADMIN_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb://userAdmin:userAdmin123456@$MYNICK-mongos.psmdb.svc.cluster.local/admin?ssl=false"
mongos> db
mongos> show dbs
# 데이터베이스를 사용할 유저 생성
mongos> db.createUser({user: "doik" , pwd: "qwe123" , roles: [ "userAdminAnyDatabase", "dbAdminAnyDatabase","readWriteAnyDatabase"]})


# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb://clusterAdmin:clusterAdmin123456@$MYNICK-mongos.psmdb.svc.cluster.local/admin?ssl=false"
mongos> use config
# 샤드 목록 정보 확인
mongos> db.shards.find().pretty()
{
	"_id" : "rs0",
	"host" : "rs0/gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017,gasida-db-rs0-1.gasida-db-rs0.psmdb.svc.cluster.local:27017,gasida-db-rs0-2.gasida-db-rs0.psmdb.svc.cluster.local:27017",
	"state" : 1,
	"topologyTime" : Timestamp(1655003176, 1)
}
{
	"_id" : "rs1",
	"host" : "rs1/gasida-db-rs1-0.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-1.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-2.gasida-db-rs1.psmdb.svc.cluster.local:27017",
	"state" : 1,
	"topologyTime" : Timestamp(1655003178, 2)
}

# (옵션) 설정 서버에 저장된 메타데이터 확인
mongos> show collections
changelog  # 메타메이터가 변경된 내용을 기록한 목록
chunks     # 샤딩된 컬렉션의 청크 정보, 어떤 샤드에 어떤 범위로 있는지 확인 가능
collections   # 샤드 클러스터 컬렉션 목록
image_collection  #
lockpings  # 샤드 클러스터의 구성원이 서로의 연결상태를 확인한 일시가 있는 목록
locks      # 컬렉션 잠금에 대한 목록. 서로 다른 mongos 가 보낸 명령 충돌을 방지한다
migrations
mongos     # 실행중인 라우터 mongos 목록
reshardingOperations
settings
shards     # 샤드 클러스터에 등록된 샤드 목록
tags
transactions
version    # 샤드 클러스터 메타데이터 전체에 대한 버전 정보, 동기화를 위한 필요

mongos> db.changelog.find().pretty()  # 메타메이터가 변경된 내용을 기록한 목록
mongos> db.chunks.find().pretty()     # 샤딩된 컬렉션의 청크 정보, 어떤 샤드에 어떤 범위로 있는지 확인 가능

# 샤드 클러스터 상태 확인 : 기본 정보, 샤드 정보, 밸런서 정보, 샤딩 설정이 된 컬렉션 정보, 청크 정보 등 출력
mongos> sh.help()
mongos> sh.status({"verbose":1})  # 모든 정보 출력
mongos> sh.status()
--- Sharding Status ---
  sharding version: {
  	"_id" : 1,
  	"minCompatibleVersion" : 5,
  	"currentVersion" : 6,
  	"clusterId" : ObjectId("62a557d217d6c043a26e39c8")
  }
  shards:           # 샤드 정보, 샤드의 상태 state 정보
        {  "_id" : "rs0",  "host" : "rs0/gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017,gasida-db-rs0-1.gasida-db-rs0.psmdb.svc.cluster.local:27017,gasida-db-rs0-2.gasida-db-rs0.psmdb.svc.cluster.local:27017",  "state" : 1,  "topologyTime" : Timestamp(1655003176, 1) }
        {  "_id" : "rs1",  "host" : "rs1/gasida-db-rs1-0.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-1.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-2.gasida-db-rs1.psmdb.svc.cluster.local:27017",  "state" : 1,  "topologyTime" : Timestamp(1655003178, 2) }
  active mongoses:  # 라우터 정보
        "5.0.7-6" : 3
  autosplit:
        Currently enabled: yes
  balancer:         # 밸런서 정보 : 활성화 여부, 현재 작동 여부, 밸런싱 실패한 횟수나 24시간 이내에 몇번이나 '청크 이동 migration'이 있었는지 확인 >> 불필요한 청크 이동 점검 확인
        Currently enabled: yes
        Currently running: no
        Failed balancer rounds in last 5 attempts: 0
        Migration results for the last 24 hours:
                512 : Success
  databases:        # 데이터베이스 정보 : 샤드 클러스터에서 admin 과 config 를 제외한 데이터베이스 정보를 표시
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }
                config.system.sessions
                        shard key: { "_id" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rs0	512
                                rs1	512
...

 

샤딩 테스트

# [터미널1] 클러스터 접속(doik)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb://doik:qwe123@$MYNICK-mongos.psmdb.svc.cluster.local/admin?ssl=false"
mongos> db
mongos> show dbs
# doik 테이터베이스 선택(없으면 데이터베이스 생성됨)
mongos> use doik
# 도큐먼트 추가
mongos> db.test.insertOne({ hello: 'world' })
# 콜렉션에서 도큐먼트 조회
mongos> db.test.find()
mongos> db.test.find({},{_id:0})
{ "hello" : "world" }


# [터미널2] 클러스터 접속(CLUSTER_USER)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb://clusterAdmin:clusterAdmin123456@$MYNICK-mongos.psmdb.svc.cluster.local/admin?ssl=false"
mongos> use config

# 샤드 클러스터 상태 확인 : 기본 정보, 샤드 정보, 밸런서 정보, 샤딩 설정이 된 컬렉션 정보, 청크 정보 등 출력
mongos> sh.status({"verbose":1})  # 모든 정보 출력
mongos> sh.status()

# doik 데이터베이스에서 샤딩을 활성화
mongos> sh.enableSharding("doik")

# chunks 사이즈가 64MB(기본값)을 테스트를 위해서 1M 줄이기 - 링크
# 기본 청크사이즈가 64MB 여서 10만 도큐먼트(레코드)는 분할이 되지 않았습니다.
# 그래서 테스트를 위해 청크사이즈를 1MB로 변경하고 테스트 하시면 분할 확인이 가능합니다.
mongos> db.settings.save({_id: "chunksize", value: 1})
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "chunksize" })

혹은
db.settings.updateOne(
   { _id: "chunksize" },
   { $set: { _id: "chunksize", value: 1 } },
   { upsert: true }
)
{
	"acknowledged" : true,
	"matchedCount" : 0,
	"modifiedCount" : 0,
	"upsertedId" : "chunksize"
}

# chunks 사이즈 설정 정보 확인 
mongos> db.settings.find()
{ "_id" : "chunksize", "value" : 1 }


	# [터미널1] 클러스터 접속(doik)
# 샤딩 활성화를 위해서 샤딩하려는 키에 해시 인덱스를 생성
mongos> db.test.createIndex({"username" : "hashed"})
혹은 인덱스 생성
mongos> db.test.createIndex({"username" : 1})


# [터미널2] 클러스터 접속(CLUSTER_USER)
# 이제 "username" 으로 컬렉션을 샤딩할 수 있다
mongos> sh.shardCollection("doik.test", {"username" : "hashed"})
혹은
mongos> sh.shardCollection("doik.test", {"username" : 1})
# 몇 분 기다렸다가 다시 샤드 클러스터 상태 확인 : 휠씬 많은 정보가 표시됨
mongos> sh.status()


# [터미널1] 클러스터 접속(doik)
# 대량의 도큐먼트 생성 : 20분 넘게 시간 소요
mongos> for (i=0; i<10; i++) {db.test.insert({"username" : "user"+i, "created_at" : new Date()})}
mongos> for (i=10; i<100000; i++) {db.test.insert({"username" : "user"+i, "created_at" : new Date()})}


# [터미널3] 클러스터 접속(doik)
kubectl exec ds/myclient -it -- mongo --quiet "mongodb://doik:qwe123@$MYNICK-mongos.psmdb.svc.cluster.local/admin?ssl=false"
mongos> use doik
mongos> db.test.find({},{_id:0})
mongos> db.test.count()

# 데이터가 여러 샤드에 분산됐으므로 몇 가지 쿼리를 시도해서 확인 : 쿼리 정상 작동 확인
mongos> db.test.find({username: "user1234"})
# 쿼리 내부 수행 작업 확인
mongos> db.test.find({username: "user1234"}).explain()
{
	"queryPlanner" : {
		"mongosPlannerVersion" : 1,
		"winningPlan" : {
			"stage" : "SINGLE_SHARD",
			"shards" : [
				{
					"shardName" : "rs1",
					"connectionString" : "rs1/gasida-db-rs1-0.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-1.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-2.gasida-db-rs1.psmdb.svc.cluster.local:27017",
					"serverInfo" : {
						"host" : "gasida-db-rs1-2",
						"port" : 27017,
						"version" : "5.0.7-6",
						"gitVersion" : "5215aa3f853ea97640cdabf0b48861b0d53bfac3"
					},
					"namespace" : "doik.test",
					"indexFilterSet" : false,
					"parsedQuery" : {
						"username" : {
							"$eq" : "user1234"
						}
					},
					"queryHash" : "379E82C5",
					"planCacheKey" : "F335C572",
					"maxIndexedOrSolutionsReached" : false,
					"maxIndexedAndSolutionsReached" : false,
					"maxScansToExplodeReached" : false,
					"winningPlan" : {
						"stage" : "FETCH",
						"filter" : {
							"username" : {
								"$eq" : "user1234"
							}
						},
						"inputStage" : {
							"stage" : "IXSCAN",
							"keyPattern" : {
								"username" : "hashed"
							},
							"indexName" : "username_hashed",
							"isMultiKey" : false,
							"isUnique" : false,
							"isSparse" : false,
							"isPartial" : false,
							"indexVersion" : 2,
							"direction" : "forward",
							"indexBounds" : {
								"username" : [
									"[8720327145141812260, 8720327145141812260]"

mongos> db.test.find({username: "user9851"}).explain()

mongos> db.test.find().explain()

mongos> db.test.count()
mongos> db.test.count()
mongos> db.test.count()
...

# [터미널2] 클러스터 접속(CLUSTER_USER)
# 클러스터 내 모든 샤드 정보 출력
mongos> use config
mongos> db.shards.find()

# 클러스터가 알고 있는 모든 샤딩 데이터베이스 출력 : enableSharding 실행된 데이터베이스이며, partitioned 가 true
mongos> db.databases.find()
{ "_id" : "doik", "primary" : "rs1", "partitioned" : true, "version" : { "uuid" : UUID("2257d7a8-26f5-4bdd-ae19-d380b220790d"), "timestamp" : Timestamp(1655005281, 1), "lastMod" : 1 } }

# 샤딩된 컬렉션 출력
mongos> db.collections.find().pretty()
...
{
	"_id" : "doik.test",
	"lastmodEpoch" : ObjectId("62a56a2f769020e0a51a1e38"),
	"lastmod" : ISODate("2022-06-12T04:23:12.019Z"),
	"timestamp" : Timestamp(1655007791, 8),
	"uuid" : UUID("64debe3c-79c4-4fd8-9669-9ab7e75e7b44"),
	"key" : {
		"username" : "hashed"
	},
	"unique" : false,
	"noBalance" : false
}

# 모든 컬렉션 내의 청크 기록
mongos> db.chunks.find().skip(1).limit(1).pretty()
mongos> db.chunks.find().skip(1).limit(10).pretty()

# 분할과 마이그레이션 기록
mongos> db.changelog.find().pretty()

# 
mongos> use doik
mongos> db.test.getShardDistribution()

Shard rs0 at rs0/gasida-db-rs0-0.gasida-db-rs0.psmdb.svc.cluster.local:27017,gasida-db-rs0-1.gasida-db-rs0.psmdb.svc.cluster.local:27017,gasida-db-rs0-2.gasida-db-rs0.psmdb.svc.cluster.local:27017
 data : 104B docs : 2 chunks : 2
 estimated data per chunk : 52B
 estimated docs per chunk : 1

Shard rs1 at rs1/gasida-db-rs1-0.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-1.gasida-db-rs1.psmdb.svc.cluster.local:27017,gasida-db-rs1-2.gasida-db-rs1.psmdb.svc.cluster.local:27017
 data : 3.07MiB docs : 48952 chunks : 3  # 현재 청크 3개로 분할
 estimated data per chunk : 1.02MiB      # 청크 사이즈 1MB
 estimated docs per chunk : 16317

Totals
 data : 3.07MiB docs : 48954 chunks : 5
 Shard rs0 contains 0% data, 0% docs in cluster, avg obj size on shard : 52B
 Shard rs1 contains 99.99% data, 99.99% docs in cluster, avg obj size on shard : 65B