What is NOSQL?
NOSQL이름의 뜻: Not Only SQL
- 특징: DB긴 한데, RDBMS처럼 고정된 스키마가 존재하지않는다. DB의 역할은 충실히 해낸다.
- 예시: Redis, HBase, mongoDB, ES
MongoDB란?
몽고DB(MongoDB←HUMONGOUS)는 크로스 플랫폼 도큐먼트 지향 데이터베이스 시스템이다. NoSQL 데이터베이스로 분류되는 몽고DB는 JSON과 같은 동적 스키마형 도큐먼트들(몽고DB는 이러한 포맷을 BSON이라 부름)을 선호함에 따라 전통적인 테이블 기반 관계형 데이터베이스 구조의 사용을 삼간다. 이로써 특정한 종류의 애플리케이션을 더 쉽고 더 빠르게 데이터 통합을 가능케 한다. 아페로 GPL과 아파치 라이선스를 결합하여 공개된 몽고DB는 자유-오픈 소스 소프트웨어이다. (출처: 위키피디아)
특징: Document-oriented DB
- RDB보다 Scale-out에 강점을 가진다
- Row대신에 유연한 모델인 Document 를 사용한다
- Document의 Key값을 미리 정의해두지않아서 고정된 스키마가 없다
- → 쉽게 필드를 추가하거나 제거할수 있다.
기능: CRUD와 DBMS의 대부분의 기능을 제공함
- Indexing: 기본인덱싱 + 보조인덱싱을 지원함. Unique, Compound, full-text index를 제공. nested document(중첩 도큐먼트) 보조인덱스를 지원
- Aggregation: 집계 파이프라인은 DB최적화를 지원해서, 서버측에서 데이터처리를 하여 분석엔진을 구착시켰음
- Collection: 특정 시간에 만료해야하는 데이터에 대한 유효시간(TTL) 컬렉션을 지원함.
- 고성능: 동시성과 처리량을 극대화하기위한 WiredTiger Storage Engine + Opportunistic Locking을 사용함. 캐시처럼 제한된 용량의 램으로, 쿼리에 알맞는 인덱스를 사용할수 있는 정도임
개념: MongoDB instance(Collection(Document)) + MongoShell
- Document: mongoDB에서 데이터의 기본단위(RDB에서는 row에 해당)
- Collection: 동적 스키마를 지원하는 묶음(RDB에서는 table에 해당)
- MongoDB - DB Hosting: MongoDB 단일 instance는 자체컬렉션을 갖는 여러개의 독립적인 DB를 호스팅한다.
- Mongo Shell: mognoDB와 함께 배포되는 도구. 몽고DB instance를 관리하고, 해당 쿼리언어로 데이터를 조작하기 위한 내장지원을 제공한다. Javascript interpreter역할을 같이하고있다.
Document
- 핵심: 정렬된 Key, 연결된 value의 집합으로 구성.
- 표현방식: Map, Hash, Dictionary
#예시
{"greeting" : "Hello, world!"}
{"greeting" : "Hello, world!", "views" : 3}
#특징: 대소문자를 구별한다
{"**c**ount" : 5} != {"**C**ount" : 5}
#특징: 키를 중복시킬수 없다
~~{"greeting" : "Hello, world!", "greeting" : "Hello, world!"}~~
Collection
- 핵심: Document의 모음
- 스키마: 동적인 스키마를 가진다. 하나의 컬렉션 내 도큐먼트들이 모두 다른구조를 가질수 있다.
{"greeting" : "Hello, world!", "views" : 3} {"signoff" : "Good night, and good luck"}
- 특징: Document의 Key, Key갯수, Data type은 모두 다를수 있다. 다른 구조의 Document라도 같은 컬렉션에 저장하는게 가능하다
Q) 다른 구조의 Document를 하나의 Collection에 저장가능하지만, 여러개의 Collection이 필요한이유
- 하나의 Collection에 여러종류의 Document를 저장하면, 관리상 불편할수있다(쿼리가 특정 스키마를 고수해야하는데, 그게 안되서 불편한다던지…)
- Collection별로 목록을 뽑으면 특정 Data형 별로 Query해서 목록을 뽑을때보다 빠르다
- 같은종류의 Data를 하나의 Collection에 모아두면 데이터지역성(Data locatlity)에도 강점을 가진다.
- Collection에 Index를 만들면, Document는 항상 특정된 구조를 가져야한다
Collection Naming
- Collection 은 이름으로 식별한다. 하나의 이름에는 무조건 하나의 Collection이 지정된다.
- subcollection의 Namespace에 .(마침표) 문자를 사용해서 컬렉션을 체계화 시킨다.
- 이는 체계화시키기 위해서 사용하는거지, 상위컬렉션과 하위컬렉션은 아무런 관계가 없다
Database(mongoDB instance)
- Collection에 Document를 그룹화시키고, DB에 Collection을 그룹화시켜놓는다
- mongoDB 단일 Instnace는 여러개의 DB를 호스팅할수 있다. 또한 각각의 DB를 독립적으로 취급시킬수 있다.
- Collection처럼 이름으로 식별된다. 예약어(admin, local, config)를 제외하고 지정할 수 있다.
- admin: 인증, 권한부여 역할을 한다.
- local: 단일서버에 대한 데이터 저장.
- replicaset 에서 local은 복제 프로세스에 사용된 데이터를 저장
- local 데이터베이스 자체는 복제되지않는다.
- config: Sharding된 mongoDB Cluster는 config DB를 사용해 Shard의 정보를 저장한다.
mongoDB Shell
- mongoDB instance와 상호작용하는 자바스크립트 셸을 제공한다
- id와 ObjectId의 구분
- 하나의 Collection에서 모든 Document는
“_id”
키를 가진다. 이를통해 Collection내의 모든 Document를 고유하게 식별하게만든다. - ObjectId: “_id”의 기본 데이터 형. 자동증가하는 기본키가 아닌 ObjectId를 사용해서 분산Scaleout에 강점을 가졌다.
- 여러 Instance에 걸쳐 자동으로 증가하는 기본키를 동기화 하는것이 어렵다.
- mongoDB는 분산 DB로 설계되었기때문에, Sharding되었을때 고유식별자를 생성하는게 매우 어렵다
- 하나의 Collection에서 모든 Document는
Percona Server for MongoDB(PSMDB)
문서 링크: https://docs.percona.com/percona-server-for-mongodb/6.0/comparison.html
- Percona Server for MongoDB(PSMDB) 6.0은 기본적으로 mongoDB 6.0을 기반으로 함.
- PSMDB는 mongoDB CE(Community Edition)의 확장판 개념임
설치 링크: https://docs.percona.com/percona-server-for-mongodb/6.0/install/apt.html
- 현재 문서에서는 PSMDB를 Operator로 생성하는것에 초점을 둔다
Percona Operator for MongoDB
Percona Operator: PSMDB를 생성,변경,삭제 등 관리하는 역할을 담당 + PSMDB 의 replicaset까지 담당
- 버전: mongoDB 4.4, 5.0, 6.0
- Resouce제한: 최소3개노드
- 깃헙링크: https://github.com/percona/percona-server-mongodb-operator
- Architecture: https://docs.percona.com/percona-operator-for-mongodb/architecture.html
- Operator로 PSMDB의 Replicaset 혹은 Shared Cluster를 구성할수 있다.
Replicaset 형태
- 구성: 하나의 Primary server + 여러개의 Secondary server
- Sharding: 각각의 샤드(위 경우에는 공유된 Pod1, 3을 의미) 는 원본데이터의 일부분을 가지고있고,
mongo query router
가 Entrypoint역할을 진행한다.
Shared Cluster형태
- HA: Operator 가 NodeAffinity로 mongoDB instance를 분리시킴. 각각의 Replicaset(위 경우 DB Pod 하나씩을 의미)은 최소 3개의 노드에 분리됨.
- Primary(위 경우 Pod2)가 죽었을땐, Replicaset(위 경우 Pod1, 3)가 Primary로 선출됨
- 구성: 이 경우 Primary에 대한 정보를 갖고있는 DB Proxy/Router와 RW를 진행하는 형태
PSMDB 설치
- Helmchart: https://artifacthub.io/packages/helm/percona/psmdb-db
- 이번문서에서는 Cluster배포, Client배포 후 연결, CRUD작업정도까지 하는것을 목표로합니다.
사전설정
#alias 설정
alias k=kube
alias MYNICK=jjongguet
#만약 jq가 없다면 진행 (JSON)
brew install jq
#만약 yh가 없다면 진행 (YAML)
brew install yh
CRD설치 및 배포
# 설치
curl -LO https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/crd.yaml
# CRD배포
k apply --server-side -f crd.yaml
# 배포확인
k get crd | grep psmdb
기본 namespace 변경
- kubectl 의 모든 명령어는 context(컨텍스트) 가 지정되어있는 namespace로 배포가 된다.
- 아무것도 설정하지 않은 경우 default 로 지정되어있으며, kubeconfig 파일을 수정해서 context를 psmdb로 바꾸려고한다.
# 현재 컨텍스트 확인
kubectl config current-context
# 컨텍스트 변경
kubectl config set-context {현재 컨텍스트} --namespace={바꾸려는 컨텍스트}
# 변경된 config 확인
kubectl config view
BRAC, Operator 설치
# BRAC설치
kubectl apply -f https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/rbac.yaml
# Operator설치
curl -s -O https://raw.githubusercontent.com/gasida/DOIK/main/psmdb/operator.yaml
k apply -f operator.yaml
클러스터 닉네임 지정
#임의의 닉네임을 아무거나 지정
MYNICK=jjongguet
echo "export MYNICK=jjongguet" >> /etc/profile
#permission denied날경우 직접 진행
sudo vi /etc/profile
#i 로 입력모드 전환
export MYNICK=jjongguet
#wq! 로 저장
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
- DATABASE ADMIN: databaseAdmin / databaseAdmin123456
- CLUSTER ADMIN : clusterAdmin / clusterAdmin123456
- USER ADMIN: userAdmin /userAdmin123456
클러스터
#신규터미널띄워서 모니터링하기
**watch kubectl get psmdb,sts,pod,svc,ep,pvc**
#replica 생성
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 | sed -e "s/my-cluster-name/$MYNICK/" | **kubectl apply -f -** && kubectl get psmdb -w
#클러스터 생성 정보 확인
kubectl get perconaservermongodbs
#축약: kubectl get psmdb
#클러스터 삭제
cat cluster1.yaml | sed -e "s/my-cluster-name/$MYNICK/" | **kubectl delete -f -**
헤드리스 서비스 접속
#정보확인
**kubectl get svc,ep**
#엔드포인트 정보확인
**kubectl get endpointslices**
# netshoot 이미지로 netdebug 파드에 zsh 실행
**kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh**
# netshoot 이미지로 netdebug 파드에 zsh 실행
**kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh**
--------------------
## 변수 지정
**MYNICK=jjongguet**
*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**
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()
...**
## 데이터베이스를 사용할 유저 생성
rs0:PRIMARY> **db.createUser({user: "doik" , pwd: "qwe123" , roles: [ "userAdminAnyDatabase", "dbAdminAnyDatabase","readWriteAnyDatabase"]})**
## 복제 정보 확인 시도
rs0:PRIMARY> **rs.status()**
- $MYNICK을 인식 못하길래, 그냥 이름을 직접 지정해줘서 해결했음
**# [터미널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()**
CRUD1: Collection
**# [터미널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()**
CRUD2: insert
#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()**
...
CRUD2: search
#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*
CRUD2: update
#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})**
CRUD2: remove
#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()**
로그확인
# **오퍼레이터** 로그
**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?'
'외부활동' 카테고리의 다른 글
[DOIK2] 스터디: 조금 자세하게 설명한 Kafka (0) | 2023.11.15 |
---|---|
구글 클라우드 스터디잼: GenAI 수료후기 (1) | 2023.11.11 |
[DOIK2] 스터디: GKE에서 CloudNativePG + Promethues + Grafana 연결하기 (0) | 2023.11.05 |
[DOIK2] 스터디: Operator 를 사용하는 이유, InnoDB 주요 Component (1) | 2023.10.29 |
[DOIK2] 스터디: Stateless와 Storage의 관계 (2) | 2023.10.28 |