Percona Server for MongoDB 개요
오늘 소개할 데이터베이스 오퍼레이터는 percona 사에서 만든, Percona Server for MongoDB이다.
이름이 길어 PSMDB라고 하겠다.
https://docs.percona.com/percona-server-for-mongodb/6.0/comparison.html
NoSQL 데이터베이스인 MongoDB 6.0을 기반으로 만들어졌으며, CE(Community Edtion) 외의 추가적인 기능들을 제공한다.
- Installing Percona Server for MongoDB - 링크
- Percona Memory Engine - 링크
- Hot Backup - 링크
- Authentication - 링크
- HashiCorp Vault integration - 링크
- Profiling rate limit - 링크
- Tune parameters - 링크
- (참고) 와이어드타이거 스토리지 엔진 WiredTiger Storage Engines : 몽고DB의 기본 스토리지 엔진
Percona Server for MongoDB 구성
클러스터 구성
PSMDB의 클러스터는 ReplicaSet Cluster 클러스터와 Shard Cluster로 구성이 가능하다.
ReplicaSet Cluster
하나의 Primary 서버와 여러 개의 Secondary 서버로 구성되고, 클라이언트 애플리케이션은 MongoDB driver를 통해 서버에 접근한다.
Shard Cluster
데이터 하위 집합들을 포함하는 샤드로 구성된 복제본 세트들이 있으며, Mongos라는 라우터가 클라이언트 애플리케이션의 진입점 역할을 한다.
오퍼레이터 구성
PerconaServerMongoDB 오브젝트를 통해 PSMDB를 관리(생성,변경,스케일링 등)한다.
Percona Server for MongoDB 설치
Primary 1개, Secondary 2개의 복제 세트로 구성한다.
# 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 # 기본 네임스페이스 변경(kubectl krew 플러그인)
kubectl get pod
# RBAC 설치
kubectl apply -f https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/rbac.yaml
kubectl get-all -n psmdb # 모든 리소스 조회(kubectl krew 플러그인)
# 오퍼레이터 설치
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=jsp
echo "export MYNICK=jsp" >> /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
jsp jsp-mongos.psmdb.svc.cluster.local ready 67m
## 클러스터 상세 정보 확인
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" = 각 노드의 하나의 db pod 스케줄링
# 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 -
헤드리스 서버 접속 확인
netshoot 파드를 이용하여 nslookup명령어로 파드의 헤드리스 서비스의 접속 정보를 확인한다.
# 헤드리스 서비스 확인 : ClusterIP None
kubectl get svc,ep
# 엔드포인트 슬라이스 정보 확인
kubectl describe endpointslices
qqqqqqqkubectl get endpointslices
# netshoot 이미지로 netdebug 파드에 zsh 실행
kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh
--------------------
## 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=jsp
## 헤드리스 서비스 접속 도메인 확인
nslookup $MYNICK-rs0
nslookup -type=srv $MYNICK-rs0
Server: 10.100.0.10
Address: 10.100.0.10#53
jsp-rs0.psmdb.svc.cluster.local service = 0 33 27017 jsp-rs0-0.jsp-rs0.psmdb.svc.cluster.local.
jsp-rs0.psmdb.svc.cluster.local service = 0 33 27017 jsp-rs0-1.jsp-rs0.psmdb.svc.cluster.local.
jsp-rs0.psmdb.svc.cluster.local service = 0 33 27017 jsp-rs0-2.jsp-rs0.psmdb.svc.cluster.local.
nslookup $MYNICK-rs0-0.$MYNICK-rs0
nslookup $MYNICK-rs0-1.$MYNICK-rs0
nslookup $MYNICK-rs0-2.$MYNICK-rs0
exit
MongoDB 기본 사용
간단한 명령어를 사용해보면서 실습에서 사용할 유저와 설정을 살펴본다.
# 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()
...
## 데이터베이스를 사용할 유저 생성
drs0: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()
Percona Server for MongoDB 복제
Mysql에서는 바이너리 로그, Postgres에서는 WAL파일은 이전 포스팅에서 복제에 관련해서 다루었던 내용들이다.
이와 같이 MongoDB에서는 OpLog가 복제를 위해 사용된다.
OpLog (Operation Log)
Primary 서버에서 Secondary 서버로 비동기적으로 데이터를 동기화하기 위한 로그이다. 복제를 위해 사용되며, Secondary 멤버 간 OpLog를 재생할 수도 있다.
복제 테스트
복제 세트(Primary,Secondary)가 실시간으로 데이터가 잘 복제되는지 확인하기 위해 터미널 3개를 열어 각각의 파드에 접속해 보자.
# [터미널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=jsp
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=
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
Primary 파드에서 Insert문과 Drop문을 실행하였을 때 Secondary파드에도 데이터가 곧바로 동기화가 되는 것을 볼 수 있다.
Percona Server for MongoDB - 장애 및 복구 테스트
파드와 노드의 장애가 생겼을 때 PSMDB가 제대로 작동하는지 확인해 보자.
Case 1 : 강제로 Primary 파드 1개 삭제
primary 파드 rs0-0을 강제로 삭제 한다.
# [터미널3] 세컨더리 파드2 접속(doik) : 헤드리스 서비스 주소
kubectl exec ds/myclient -it -- bash -il
--------------------------------------
# 변수 지정
MYNICK=<각자 자신의 닉네임>
MYNICK=jsp
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()
강제로 파드를 죽였을 때 오퍼레이터 로그에 reconcile 함수가 출력이 되고, 파드가 다시 복구된다.
rs0-1 파드로 Primary가 위임되었다.
Case 2 : 강제로 Primary 파드가 있는 노드를 drain
primary가 파드(rs0-0)가 동작하는 노드(192-168-3-174)를 drain 한다.
rs0-0파드는 nodeAffinity에 의해 스케줄링 노드가 없어 pending 상태가 되고, rs0-1 파드로 primary의 역할이 위임되는 것을 볼 수 있다.
Case 3 : 강제로 노드를 하나 더 drain
이전 drain 한 노드에 이어, 노드를 하나 더 drain 하여 파드를 1대일 경우에 정상적으로 동작하는지 확인해 보자.
노드는 잘 drain 되었지만 , `Cannot evict pod as it would violate the pod's disruption budget.` 에러가 발생하면서 psmdb 파드가 축출(evict) 되지 않는다.
kubectl get poddisruptionbudgets.policy
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
jsp-mongod-rs0 N/A 1 0 14h
Pod Disruption Budget 라는 정책이 설정되어 있었다.
DisruptionBudget은 비자발적 중단(인프라 문제, 리소스 부족)이 아닌 자발적 중단(파드 삭제, 노드 트레이닝)으로 동시에 다운된 복제된 애플리케이션의 포드 수를 제한할 수 있는 리소스라고 한다.
MAX UNAVAILABLE 필드의 경우 허용된 중단된 포드의 최대 수를 나타내는데, PSMDB는 1개로 설정되어있기 때문에 노드를 1개 더 drain 하였을 때 위와 같이 파드가 중단되지 않았음을 볼 수 있다.
샤딩(Sharding)
샤딩과(Sharding) 복제(Replication)
복제(Replication)는 데이터의 가용성 향상을 위해 데이터의 복사를 유지하지만, 샤딩(Sharding)은 대용량 및 쓰기 연산이 많은 상황에서의 확장성을 향상하기 위해 데이터를 여러 부분으로 분할하여 분산 저장한다. 두 기술은 데이터베이스 시스템의 성능 및 안정성을 향상하는 데 사용된다.
소규모 데이터의 경우 샤딩을 사용할 필요가 없지만, 대규모 웹 애플리케이션이나 빅테이터 및 로깅 등 데이터 분석에 샤딩이 활용된다고 한다.
샤딩(Sharding) 설치
복제 세트 2개(rs-0, rs1), mongos(파드 3개), cfg(파드 3개)로 구성된다.
-
- Shard : 데이터베이스의 Replica Set
- Mongos : 클라이언트 애플리케이션의 쿼리를 처리하는 라우터
- Config Servers : Replica Set의 메타데이터와 샤드 클러스터의 정보를 저장
# 신규 터미널 : 모니터링
watch kubectl get psmdb,sts,pod,svc,ep,pvc,pv
# ebs gp3 스토리지 클래스 : 삭제 정책 변경 >> 삭제 시 번거로워서 아래 절차는 삭제하겠습니다
kubectl get sc gp3
kubectl delete sc gp3
kubectl apply -f https://raw.githubusercontent.com/gasida/DOIK/main/1/gp3-sc-retain.yaml
kubectl get sc gp3
# 컴퓨팅 리소스 부족으로 myclient 데몬셋 삭제
kubectl delete ds myclient
# 클러스터 생성 : 복제 셋 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 -
# 클러스터 생성 정보 확인 initializing => Ready
kubectl get psmdb
NAME ENDPOINT STATUS AGE
jsp initializing 28s
kubectl get psmdb gasida -o yaml | kubectl neat | yh
kubectl get svc,ep
kubectl df-pv
kubectl get pvc,pv
# 클러스터 파드 정보 확인
kubectl get sts,pod -owide
NAME READY AGE CONTAINERS IMAGES
statefulset.apps/jsp-cfg 3/3 2m10s mongod percona/percona-server-mongodb:6.0.9-7
statefulset.apps/jsp-rs0 3/3 2m10s mongod percona/percona-server-mongodb:6.0.9-7
statefulset.apps/jsp-rs1 3/3 2m10s mongod percona/percona-server-mongodb:6.0.9-7
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/jsp-cfg-0 1/1 Running 0 2m10s 192.168.1.132 ip-192-168-1-155.ap-northeast-2.compute.internal <none> <none>
pod/jsp-cfg-1 1/1 Running 0 108s 192.168.3.224 ip-192-168-3-174.ap-northeast-2.compute.internal <none> <none>pod/jsp-cfg-2 1/1 Running 0 80s 192.168.2.197 ip-192-168-2-68.ap-northeast-2.compute.internal <none> <none>
pod/jsp-rs0-0 1/1 Running 0 2m10s 192.168.3.188 ip-192-168-3-174.ap-northeast-2.compute.internal <none> <none>
pod/jsp-rs0-1 1/1 Running 0 97s 192.168.1.235 ip-192-168-1-155.ap-northeast-2.compute.internal <none> <none>
pod/jsp-rs0-2 1/1 Running 0 57s 192.168.2.127 ip-192-168-2-68.ap-northeast-2.compute.internal <none> <none>
pod/jsp-rs1-0 1/1 Running 0 2m10s 192.168.1.143 ip-192-168-1-155.ap-northeast-2.compute.internal <none> <none>
pod/jsp-rs1-1 1/1 Running 0 95s 192.168.3.78 ip-192-168-3-174.ap-northeast-2.compute.internal <none> <none>
pod/jsp-rs1-2 1/1 Running 0 57s 192.168.2.106 ip-192-168-2-68.ap-northeast-2.compute.internal <none> <none>
pod/myclient-lgj6w 1/1 Running 0 15h 192.168.3.219 ip-192-168-3-174.ap-northeast-2.compute.internal <none> <none>
pod/myclient-lz6l2 1/1 Running 0 15h 192.168.2.112 ip-192-168-2-68.ap-northeast-2.compute.internal <none> <none>
pod/myclient-r72zg 1/1 Running 0 15h 192.168.1.67 ip-192-168-1-155.ap-northeast-2.compute.internal <none> <none>
pod/percona-server-mongodb-operator-9db496d46-kz7gk 1/1 Running 0 118m 192.168.2.111 ip-192-168-2-68.ap-northeast-2.compute.internal <none> <none>
# mongos 서비스 생성 후 아래 도메인 연결 >> 기존 실습 도메인 연결에서 삭제 시 번거로워서 도메인 연결은 삭제하겠습니다
kubectl annotate service $MYNICK-mongos "external-dns.alpha.kubernetes.io/hostname=mongos.$MyDomain"
kubectl get psmdb
샤딩(Sharding) 접속 및 설정 확인
Mongos 라우터를 통해 PSMDB 파드에 접속한다.
# mongos 라우터 접속 서비스 정보 확인
kubectl get svc,ep $MYNICK-mongos
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/jsp-mongos ClusterIP 10.100.192.216 <none> 27017/TCP 4m25s
NAME ENDPOINTS AGE
endpoints/jsp-mongos 192.168.1.94:27017,192.168.2.181:27017,192.168.3.44:27017 4m25s
# [터미널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()
{ │mongos> show dbs
"_id" : "rs0", │admin 0.000GB
"host" : "rs0/jsp-rs0-0.jsp-rs0.psmdb.svc.cluster.local:27017,jsp-rs0-1.jsp-rs0.psmdb.svc.cluster.local:27017,jsp-rs0-2│config 0.002GB
.jsp-rs0.psmdb.svc.cluster.local:27017", │mongos> db.createUser({user: "doik" , pwd: "qwe123" , roles: [ "userAdminAnyDatabase", "dbAdminAnyDatabase","readWriteAn
"state" : 1, │yDatabase"]})
"topologyTime" : Timestamp(1699739308, 7) │Successfully added user: {
} │ "user" : "doik",
{ │ "roles" : [
"_id" : "rs1", │ "userAdminAnyDatabase",
"host" : "rs1/jsp-rs1-0.jsp-rs1.psmdb.svc.cluster.local:27017,jsp-rs1-1.jsp-rs1.psmdb.svc.cluster.local:27017,jsp-rs1-2│ "dbAdminAnyDatabase",
.jsp-rs1.psmdb.svc.cluster.local:27017", │ "readWriteAnyDatabase"
"state" : 1, │ ]
"topologyTime" : Timestamp(1699739309, 4) │}
}
# (옵션) 설정 서버에 저장된 메타데이터 확인
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("654ff6159c887770200795fb")
}
shards:
{ "_id" : "rs0", "host" : "rs0/jsp-rs0-0.jsp-rs0.psmdb.svc.cluster.local:27017,jsp-rs0-1.jsp-rs0.psmdb.svc.cluster.local:27017,jsp-rs0-2.jsp-rs0.psmdb.svc.cluster.local:27017", "state" : 1, "topologyTime" : Timestamp(1699739308, 7) }
{ "_id" : "rs1", "host" : "rs1/jsp-rs1-0.jsp-rs1.psmdb.svc.cluster.local:27017,jsp-rs1-1.jsp-rs1.psmdb.svc.cluster.local:27017,jsp-rs1-2.jsp-rs1.psmdb.svc.cluster.local:27017", "state" : 1, "topologyTime" : Timestamp(1699739309, 4) }
active mongoses:
"6.0.9-7" : 3
autosplit:
Currently enabled: yes
balancer:
Currently enabled: yes
Currently running: no
Failed balancer rounds in last 5 attempts: 0
Migration Results for the last 24 hours:
No recent migrations
databases:
{ "_id" : "config", "primary" : "config", "partitioned" : true }
config.system.sessions
shard key: { "_id" : 1 }
unique: false
balancing: true
chunks:
...
샤딩(Sharding) 테스트
- 청크(Chunk): 샤드 사이에서 분할된 정보를 효과적으로 관리하기 위해 만들어진 개념으로 컬렉션에서 샤드키 값을 기준으로 한 도큐먼트의 묶음이다.
# [터미널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"})
{
"collectionsharded" : "doik.test",
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1699739967, 30),
"signature" : {
"hash" : BinData(0,"An5fzwT7HVsg0ccP1DzrF2F16fQ="),
"keyId" : NumberLong("7300324091045609486")
}
},
"operationTime" : Timestamp(1699739967, 26)
}
혹은
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/jsp-db-rs1-0.jsp-db-rs1.psmdb.svc.cluster.local:27017,jsp-db-rs1-1.jsp-db-rs1.psmdb.svc.cluster.local:27017,jsp-db-rs1-2.jsp-db-rs1.psmdb.svc.cluster.local:27017",
"serverInfo" : {
"host" : "jsp-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/jsp-db-rs0-0.jsp-db-rs0.psmdb.svc.cluster.local:27017,jsp-db-rs0-1.jsp-db-rs0.psmdb.svc.cluster.local:27017,jsp-db-rs0-2.jsp-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/jsp-db-rs1-0.jsp-db-rs1.psmdb.svc.cluster.local:27017,jsp-db-rs1-1.jsp-db-rs1.psmdb.svc.cluster.local:27017,jsp-db-rs1-2.jsp-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
느낀 점
아직 NoSQL 기반의 데이터베이스를 사용하고 있지 않지만, 이번 실습을 통해 사용해보고 싶다는 생각이 들었다.
또한 이번 실습을 진행하면서 Disruption Budget 같은 노드 스케줄링(Node Affinity) 등 쿠버네티스에 대해 조금 더 깊게 알게 된 것 같아 도움이 된 것 같다.
Reference
https://docs.percona.com/percona-operator-for-mongodb/architecture.html
https://kmaster.tistory.com/84 - DisruptionBudget
'Kubernetes > Database Operator' 카테고리의 다른 글
Stackable Operator(Data Platform) - DOIK2_6주차 (1) | 2023.11.26 |
---|---|
Kafka Operator(Strimzi) - DOIK2_5주차 (0) | 2023.11.19 |
PostgreSQL Operator - CloudNative PostgreSQL - DOIK2_3주차 (0) | 2023.11.05 |
쿠버네티스 MySQL InnoDB Cluster - DOIK2_2주차 2 (2) | 2023.10.28 |
쿠버네티스 오퍼레이터란? (Kubernetes Operator) - DOIK_2주차 1 (2) | 2023.10.28 |