목차
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 는 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 - 링크
- 오퍼레이터는 Percona Server for MongoDB Replica set 혹은 Sharded cluster 으로 구성 가능
- 클라이언트 애플리케이션은 mongo+srv URI 주소로 접속
- Replica set cluster 구성
- 이미지 출처 : https://docs.percona.com/percona-operator-for-mongodb/architecture.html
- 클라이언트 애플리케이션은 mongo+srv URI 주소로 접속
- 오퍼레이터는 Percona Server for MongoDB Replica set 혹은 Sharded cluster 으로 구성 가능
-
- 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 을 제공한다
- one primary server and several secondary ones (two in the picture), and the client application accesses the servers via a driver.
오퍼레이터 설치
# 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
# 오퍼레이터 로그
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
샤드 정보 확인
- **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