본문 바로가기
IT/엘리스 AI 트랙

[엘리스 AI 트랙 2기] 5주차-mongoDB (07/20)

by 무녈 2021. 7. 21.

자료의 출처는 '엘리스 AI 트랙 2기 (https://aitrack.elice.io/)' 'mongoDB'이며, 학습 후 정리한 내용입니다.

⚡️올바르지 않은 내용이 있을 경우 댓글로 남겨주시면 감사하겠습니다.⚡️


1. MongoDB 개요

01. NoSQL

NoSQL DBMS

NoSQL DBMS
Not Only SQL DataBase Management System
전통적인 관계형 데이터베이스 보다
덜 제한적인 일관성 모델을 제공하는 DB
데이터를 저장하고 관리하는 시스템
실생활에 간략하게 DB라 함

특징

기존 RDBMS가 일관성 모델 때문에 가질 수 없었던 확장성, 유연성, 고성능, 고기능성을 확보

NoSQL DBMS의 유행 배경

RDBMS 대표: MySQL, PostgreSQL, Oracle DB

NoSQL DMBS 대표: mongDB(2009년 출범), elasticsearch(2010년 출법), Redis(2009년 출범)

 

아이폰 및 스마트폰의 등장  -> 인터넷의 확산 -> 많은 데이터베이스가 필요 -> 새로운 DBMS의 탄생

서버에 요구되는 정보 처리량의 급속한 증가 정보 처리량을 늘리려면 규칙을 깨야 한다

 

전통적 RDBMS의 규칙

  • ACID 원칙 준수
  • 2차원 테이블 형태
  • SQL을 통한 질의
    ACID(원자성, 일관성, 고립성, 지속성)는 데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가리키는 약어

    • 원자성(Atomicity):트랜잭션과 관련된 작업들이 부분적으로 실행되다가 중단되지 않는 것을 보장하는 능력
      예를 들어, 자금 이체는 성공할 수도 실패할 수도 있지만 보내는 쪽에서 돈을 빼 오는 작업만 성공하고 받는 쪽에 돈을 넣는 작업을 실패해서는 안된다. 원자성은 이와 같이 중간 단계까지 실행되고 실패하는 일이 없도록 하는 것이다.
    • 일관성(Consistency): 트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지하는 것을 의미
      무결성 제약이 모든 계좌는 잔고가 있어야 한다면 이를 위반하는 트랜잭션은 중단된다.
    • 독립성(Isolation): 트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것을 의미
      이것은 트랜잭션 밖에 있는 어떤 연산도 중간 단계의 데이터를 볼 수 없음을 의미한다. 은행 관리자는 이체 작업을 하는 도중에 쿼리를 실행하더라도 특정 계좌간 이체하는 양 쪽을 볼 수 없다. 공식적으로 고립성은 트랜잭션 실행내역은 연속적이어야 함을 의미한다. 성능관련 이유로 인해 이 특성은 가장 유연성 있는 제약 조건이다. 자세한 내용은 관련 문서를 참조해야 한다.
    • 지속성(Durability): 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함을 의미
      시스템 문제, DB 일관성 체크 등을 하더라도 유지되어야 함을 의미한다. 전형적으로 모든 트랜잭션은 로그로 남고 시스템 장애 발생 전 상태로 되돌릴 수 있다. 트랜잭션은 로그에 모든 것이 저장된 후에만 commit 상태로 간주될 수 있다.

      (출처: ACID-위키백과)

RMDBS와 NoSQL의 차이

RDBMS NoSQL
안정성에 중점 확장성과 성능 최적화에 특화
범용적 활용 가능 각각의 기능에 특화
  • mongoDB: 범용적 활용
  • redis: 캐싱 특화
  • elasticsearck: 전문 검색 특화

 

02. MongoDB 소개 및 분산 컴퓨팅

MongoDB

MongoDB는 가장 유명한 NoSQL DBMS

현대적 설계로 고성능을 내면서 동시에 많은 기능과 안정화가 이루어진 다목적 DB

MongoDB의 현대적 설계

과거에는 하나의 DBMS로 처리하는 상황이 빈번했다

기존 RDBMS는 하나의 인스턴스에서 작동하는 것이 기본값

 

요즘은 처리할 데이터양이 많아서 분산 컴퓨팅이 빈번

현대적 DBMS는 분산 컴퓨팅을 하는 것이 기본값

분산 컴퓨팅(distributed computing):
분산 시스템(distributed systems)을 연구하는 
컴퓨터 과학의 한 분야로, 인터넷에 연결된 여러 컴퓨터들의 처리 능력을 이용하여 메시지를 하나에서 다른 하나로 보냄(message passing)으로써 거대한 계산 문제를 해결하려는 분산처리 모델

(출처: 분산컴퓨팅 - 위키백과)

분산 컴퓨팅의 방식

MongoDB에서 지원하는 분산 컴퓨팅

복제, 샤딩

복제 샤딩
안정성을 높이기 위한 방식
원본서버가망가져도정상서비스가능
성능을 향상하기 위한 방식
읽기, 쓰기 성능 향상 기능

분산 컴퓨팅 관련 MongoDB 주의점

단일 인스턴스 DBMS에서 만약 쓰기 작업을 하다가 서버가 갑자기 꺼진다면?

mongoDB

데이터가 의도치 않게 중간 상태로 저장될 수 있다.

# 안전수준을 높이면 저장되거나 실패한 상태로 존재할 수 있음 - 하지만 기본값은 안전수준이 낮은 상태

MySQL

데이터가 항상 저장 완료하거나 저장 실패한 상태로만 존재

# 규칙을 지키고있기 때문에 안전성이 높다.

 

* MongoDB는 분산 컴퓨팅을 하는 것을 가정하고 쓰기 기본 설정이 되어 있다

Write-Concern, Read-Concern 설정을 알아야 제대로 사용 가능

03. JS 친화적 MongoDB

V8엔진의 성능향상으로 인한 웹 개발자의 대규모 업종전환

MongoDB도 이런 대세를 활용하여 SQL을 몰라도 개발 가능한 V8엔진의 JS 기반 쉬운 DBMS를 표방

내부 명령어가 JS로 구성

MongDB Shell

use myDatabase
db.initialCollection.insertOne({hello: "world"}) 
db.initialCollection.find()

JSON과 비슷한 BSON 자료 구조

{
  name: "sue",                  --field: value
  age: 26,						--field: value
  status: "A",					--field: value
  groups: ["news", "sports"]	--field: value
}

 

JSON과 유사한 BSON 구조로 정보를 저장

BSON(Binary JSON)

BSON은 JSON 형태의 문서를 바이너리 형태로 인코딩한 바이트 문자열

인코딩과 디코딩 과정이 필수이기에, 이를 수행하는 서비스가 필요

BSON을 사용하는 mongoDB 경우, 애플리케이션과 mongoDB 데이터베이스 서버를 연결하는 드라이버는 주어진 문서를 insert 하거나 쿼리할 때 그 문서를 서버에 보내기 전에 BSON으로 인코딩하는 역할을 담당

디코딩 또한 드라이버 역할이며, 데이터베이스 서버에서 내려준 BSON 형태의 바이너리 데이터를 드라이버가 디코딩하여, 데이터를 요청한 측에 평문으로 돌려줌

 

BSON의 특징

  • 문서 자체의 여백을 줄여 효율적으로 데이터 저장 공간을 절약하는 경량 형태
    네트워크를 통해 데이터 전달하고 표현하는 방식에 적합
  • BSON 데이터 형태가 C언어의 데이터 형태를 사용하는 덕분에, 인코딩/디코딩 속도 매우 빠름
    BSON을 사용하는 것은 전반적 성능에 나쁜 영향 X
  • 문자열 값에 길이를 접두사로 붙이는 기능이 있어, 문자열의 끝을 빠르게 파악 
    데이터 탐색에 도움

(참고: https://bsonspec.org/)

(참고: https://www.educba.com/json-vs-bson/)

MongDB가 JS를 사용해서 얻은 특징

  1. 웹 개발자에게 쉬운 입문이 가능하다
  2. BSON 자료형 사용
  3. 내부 명령어를 JS 형식으로 사용

04. MongDB 활용

MongoDB가 대체 가능한 기능

MongoDB는 범용적인 DBMS로 활용 가능

, 일반적인 상황에서 활용하기에는 무리

MongoDB의 특장점

  1. JS에 친화적이다
  2. 성능 확장이 쉽다
  3. 높은 성능을 낼 수 있다
  4. 유연한 구조로 저장할 수 있다
  5. 다양한 자료형을 지원

JS 기반 프로젝트

프로젝트 언어를 JS로 통일시키고자 할때

- 정보 변환 없이 전달 가능 (React - nodeJS - mongoDB)

기존의 JSON 방식의 DB에서 좀 더 많은 기능과 성능이 필요할 때

- 입문용 DB에서 실전용 DB로 전환

저장할 정보의 형태가 자주 변경되는 경우

파일럿 프로젝트

앞으로 몇 명이 이용할지 예측이 안되는 프로젝트 

한참 개발중이고 상황에 따라 데이터구조가 쉽게 바뀔수 있는 신규 프로젝트

 

비 정형화된 정보를 전산화

각종 예외사항이 적혀있는 수기로 작성된 과거의 주문정보

로그 데이터 형식의 정보를 저장할때
정보를 정형화하기에 어려운 경우

 

높은 성능이 필요한 경우

안정성보다 높은 성능이 필요한 경우

NoSQL이 기존 DBMS보다 수십배는 더 높은 성능을 가짐 안정성보다 높은 성능이 필요할 때
ex) 카카오 모빌리티의 지도정보, SNS 정보 저장

활용하면 안되는 경우

높은 성능보다 안정성, 무결성이 중요한 경우

결제 시스템, 예약 시스템

데이터 무결성이 중요한 경우

복잡한 쿼리가 빈번한 경우

JOIN, UNION과 같은 연산을 처리할 때 좀 더 복잡해진다

RDB와 비교했을 때 없는 함수가 꽤있다


2. CRUD

01. MongoDB의 구조

MongoDB의 기본 구조

JSON과 비슷한 BSON 자료 구조

{
  name: "sue",                  --field: value
  age: 26,						--field: value
  status: "A",					--field: value
  groups: ["news", "sports"]	--field: value
}

Pymongo

Python 코드

import pymongo
connection = pymongo.MongoClient("mongodb://localhost:27017/")
											#내 PC내부 # MongoDB 기본포트

 

pymongomongoDB를 사용할 수 있게 도와주는 파이썬 모듈

Pymongo로 DB 접속

import pymongo
connection = pymongo.MongoClient("mongodb://localhost:27017/")
db = connection.get_database("testDB") # 접속하고자 하는 db의 이름

 

접속할 데이터베이스로 접근
만약 데이터베이스가 없으면 자동으로 생성한 후 접속

컬렉션에 도큐먼트 삽입하기

import pymongo
connection = pymongo.MongoClient("mongodb://localhost:27017/") 
db = connection.get_database("testDB")
collection = db.get_collection("testCollection") # collection 이름
collection.insert_one({ "hello": "world" }) # 다큐먼트 삽입

 

컬렉션에 도큐먼트 저장
만약 컬렉션이 없다면 자동으로 생성됨

데이터베이스, 컬렉션, 도큐먼트 확인하기

python 코드

# 데이터베이스 목록 조회
print(connection.list_database_names()) 
#컬렉션목록조회
print(db.list_collection_names())
# pprint로 도큐먼트 목록 조회
pprint(list(collection.find())) 
			# Cursor 출력

결과 출력

['admin', 'config', 'local', 'testDB'] 
			#기본 DB들
['testCollection']
[{'_id': ObjectId('...'), 'hello': 'world'}] 
 # 도큐먼트를 식별하기 위한 값

02. BSON 데이터 삽입

BSON이란?

BSON = Binary JSON의 의미

JSON(JavaScript object notation)

JSON의 일부로써 MongoDB 도큐먼트로 데이터를 저장하기 위한 형식

 

#참고

BSON(Binary JSON)

BSON의 대표적 데이터타입

NULL 아무것도 없다
Undefined 정의 되지 않음
Double/Integer 123.42, 12
String "hello"/ 'hello'
Object {"field": "value"}
Array [1, 2, {"hi": "helli"}
Boolean true/fasle
Date ISODate("2017-10-24T05:02:46.395Z")
ObjedcId ObjectId("5431adac bac056 4834 120ads")

ObjectId의 구성

MongoDB에서 각 Document 의 primary key의 값으로 사용

ObjectID( "542c2b97 bac059 5474 108b48")
  유닉스 시간 기기 id 프로세스 id 카운터

*

각각의 Doument가 가지는 값, 16진수로 표현

왜 1, 2, 3 처럼 단순하게 표현하지 않을까?
->확정성 때문!
- mongoDM가 하나의 instance에서 시행된다면, 굳이 ObjectId가 필요하지 않았곘지만,
샤딩이나 복제를 한다면, 동일한 시간에 도큐멘트가 생성될 수 있는데 이에 대한 차이를 두기위함.

 

모든 도큐먼트에는 _id 필드가 있다.
한 컬렉션에 _id 필드를 같게 설정할 수 없다.
_id 필드 값을 따로 설정하지 않으면 ObjectId 객체가 저장된다.
따로 설정한 경우 해당 값으로 저장할 수 있다.
자동 생성된 ObjectId 객체는 항상 서로 다른 값을 가지도록 설계되었다.
도큐먼트를 구분하기 위한 필드를 primary key라고 한다.

 

BSON 구조 예시

{
  _id: ObjectId("542c2b97 bac059 5474 108b48"), 
  name: {first: "sue", last: "Turing" },
  age: 26,
  is_alive: true,
  groups: ["news", "sports"],
  viewTime: ISODate("2017-10-24T05:02:46.395Z")
}

Pymongo에서 사용하기

from bson import ObjectId  # bson에만 있는 자료형
from datetime import datetime #  python 내부 자료형
collection.insert_one({
  "_id": ObjectId("542c2b97bac0595474108b48"), 
  "name": {"first": "sue", "last": "Turing" }, 
  "age": 26,
  "is_alive": True,
  "groups": ["news", "sports"],
  "viewTime": datetime(2017, 10, 24, 5, 2, 46)
})

# pymongo가 필요하며, pymongo가 mongoDB의 BSON 자료를 넘기게되면, python dictionary형태로 전달

03. 도큐먼트 생성

도큐먼트를 보기 좋게 출력하기

 from pprint import pprint 
 pprint({ BSON document })

pretty print의 의미를 가진 명령어 pprint

컬렉션에 도큐먼트 삽입하기

import pymongo
connection = pymongo.MongoClient("mongodb://localhost:27017/") 
db = connection["testDB"]
collection = db["testCollection"]
collection.insert_one({ "hello": "world" })

컬렉션에 하나의 도큐먼트 삽입

명령어

from pprint import pprint
result = collection.insert_one(
  { document } 
)
print(result.inserted_id)
pprint(result.inserted_id)

결과

# 입력된 도큐먼트의 _id 값
609fce475cfb9675580a6efc
ObjectId('609fce475cfb9675580a6efc')

하나의 도큐먼트 객체를 넘긴다

컬렉션에 다수의 도큐먼트 삽입

명령어

result = collection.insert_many(
  [ { document }, { document }, ... ]
)
print(result.inserted_ids)

결과

# 입력된 도큐먼트의 _id 값들
[
  ObjectId('542c2b97bac0595474108b48'), 
  ObjectId('609fcdb30c1a70ffb15f4306')
]

다수의 도큐먼트 객체를 넘김

웹 서버에 도큐먼트 생성 예시

웹서버    
1. 주어진 정보로 게시글을 
inser_one으로 저장
<------------------ 게시글 정보
2. 반환된 ObjectId로 해당 게시글 접속    
3. URL 반환 ------------------> 작성된 글 URL

게시글을 작성하는 웹 서버를 구현한다고 가정하면 다음과 같은 로직을 만들 수 있다.

04. 도큐먼트 검색 기초

컬렉션에서 도큐먼트 조회

import pymongo
from pprint import pprint
connection = pymongo.MongoClient("mongodb://localhost:27017/") 
db = connection["testDB"]
collection = db["testCollection"]
collection.insert_one({ "hello": "world" }) 
print(list(collection.find()))

컬렉션에서 도큐먼트 검색하기

명령어

result = collection.find( 
  { query },        # 어떤 정보를 걸러낼지
  { projection }    # 걸러낼 정보를 어떻게 표현할지
)
print(result) 
print(list(result))

결과

# Cursor를 반환
<pymongo.cursor.Cursor object at
0x7fc6b31659b0>
# list로 내용물을 불러올 수 있다
[ { document }, { document }, ... ]

 

find 명령어는 컬렉션 내에 query 조건에 맞는 다수의 도큐먼트를 검색

커서란?

batch: 101 documents
←------------------------------------------------------------------------------------------------------------→
20 document 20 document 20 document ... .....
       
Cursor        

커서란 쿼리 결과에 대한 포인터
도큐먼트의 위치정보만을 반환하여 작업을 효율적으로 만들어준다

커서에서 도큐먼트 불러오는 부분

명령어

result = collection.find( 
  { query }
)
print(list(result))
for document in result:
	print(document)

결과

# list로 내용물을 한번에 불러올 수 있다
[ { document }, { document }, ... ]
# for문으로 내용물을 하나씩 처리할 수 있다
{ document } 
{ document } 
...

list 명령어로 커서를 활용해 모든 데이터를 불러온다

for 문으로 하나씩 데이터를 불러올 수 있다

query(쿼리)

쿼리란 원하는 정보를 걸러내기 위한 깔때기

검색하고자 하는 내용을 쿼리로 표현할 수 있어야 한다

기본 Query

{"field": value, "field": value, ...}

 

Query는 그 field에 맞는 value 값으로 필터링

find문 예시

명령어

 users.find(
  {"username": "karoid"}
)

결과

 { "_id" : ObjectId("59ef45b4ddf91a3b998ee9ed"), "username" : "karoid", "password" : "1111" }

명령어

 users.find(
  {"password": "1111"}
)

결과

 { "_id" : ObjectId("59ef45b4ddf91a3b998ee9ed"), "username" : "karoid", "password" : "1111" } 
 { "_id" : ObjectId("59f02d5a36e39687dea2cea2"), "username" : "hello", "password" : "1111" }

Projection

{"field": boolean, "field": boolean, ...}

Projection은 그 field를 보여줄지 말지를 알려준다

boolean이 true이면 해당 field를 표현하고

falsefield를 제외한 결과를 출력

Projection 예시

명령어

 users.find(
  {"username": "karoid"},
  {"username": True}
)

결과

 { "_id" : ObjectId("59ef45b4ddf91a3b998ee9ed"), "username" : "karoid"}

# projection 필드 값이 True이면, 해당 field: value만 표시한다

명령어

 users.find(
  {"password": "1111"},
  {"username": False}
)

결과

 { "_id" : ObjectId("59ef45b4ddf91a3b998ee9ed"), "password" : "1111" } 
 { "_id" : ObjectId("59f02d5a36e39687dea2cea2"), "password" : "1111" }

# projection 필드 값이 False이면, 해당 field: value만 제외한다

Projection의 잘못된 예

명령어

 users.find(
  {"password": "1111"},
  {"username": False, "_id": True}
)

결과

ERROR

# projection 안에 false true가 혼용되면 error 발생
value는 false 또는 true 한종류만 올 수 있다.

05. 도큐먼트 수정

하나의 도큐먼트 찾아 수정

명령어

result = collection.update_one( 
  { query },
  { update },
  upsert:Boolean
) 
print(result.matched_count) 
print(result.modified_count)

결과

#찾은도큐먼트수
1
#변경된도큐먼트수
1

 

query로 검색하고, update에 변경할 사항을 적는다

 

# 하나의 도큐먼트를 수정하기 위한 메소드: update_one()

# 찾은 도큐먼트 수: cursor.matched_count

# 변경된 도큐먼트 수: cursor.modified_count

다수의 도큐먼트 찾아 수정

명령

result = collection.update_many( 
  { query },
  { update },
  upsert:Boolean
) 
print(result.matched_count) 
print(result.modified_count)

결과

#찾은도큐먼트수
12
#변경된도큐먼트수
5

# 다수의 도큐먼트 수정 메소드: update_many()

 

이미 이 도큐먼트가 들어있다고 가정하자

inventory.insert_many( [
  { "item": "canvas", "qty": 100, "size": { "h": 28, "w": 35.5, "uom": "cm" }, "status": "A" },
  { "item": "journal", "qty": 25, "size": { "h": 14, "w": 21, "uom": "cm" }, "status": "A" },
  { "item": "mat", "qty": 85, "size": { "h": 27.9, "w": 35.5, "uom": "cm" }, "status": "A" },
  { "item": "mousepad", "qty": 25, "size": { "h": 19, "w": 22.85, "uom": "cm" }, "status": "P" },
  { "item": "notebook", "qty": 50, "size": { "h": 8.5, "w": 11, "uom": "in" }, "status": "P" },
  { "item": "paper", "qty": 100, "size": { "h": 8.5, "w": 11, "uom": "in" }, "status": "D" },
  { "item": "planner", "qty": 75, "size": { "h": 22.85, "w": 30, "uom": "cm" }, "status": "D" },
  { "item": "postcard", "qty": 45, "size": { "h": 10, "w": 15.25, "uom": "cm" }, "status": "A" },
  { "item": "sketchbook", "qty": 80, "size": { "h": 14, "w": 21, "uom": "cm" }, "status": "A" },
  { "item": "sketch pad", "qty": 95, "size": { "h": 22.85, "w": 30.5, "uom": "cm" }, "status": "A" }
])

특정 field 값 업데이트 하기

명령

inventory.update_one(
  {"item": "canvas"},
  {"$set": {"qty": 10} }  #$set: 필드 값 변경
)

결과

 { item: "canvas", qty: 100, size: { h: 28, w: 35.5, uom: "cm" }, status: "A" } 
 									↓
 { item: "canvas", qty: 10, size: { h: 28, w: 35.5, uom: "cm" }, status: "A"

특정 field 제거하기

명령

 inventory.update_one(
  {"item": "canvas"},
  {"$unset": {"qty": ""} } #$unset: 필드를 없앰
)

결과

 { item: "canvas", qty: 100, size: { h: 28, w: 35.5, uom: "cm" }, status: "A" } 
 									↓
 { item: "canvas", size: { h: 28, w: 35.5, uom: "cm" }, status: "A" }

 

해당되는 document가 없다면 새로 추가하기

명령

inventory.update_one(
  { "item": "flash" },
  { "$set": {"size": {"h": 10, "w": 8}
  ,"status": "F"} },
  True
 )

결과

 Null
  ↓ 
{ item: "flash", size: {h: 10, w: 8} ,status: "F"} }

# upsert값을 True로 설정할 경우, query가 없을 때 해당 도큐먼트를 생성함

update 연산자

PowerPoint 프레젠테이션

연산자 명 설명 형식
$currentDate <field>필드의 값으로 현재 timestampdate 값을 갖도록 추가한다. $currentDate: { <field>: <typeSpecification1>, ... }
$inc 도큐먼트의 <field>필드의 값을 <증가시킬 값> 값만큼 증가시킨다. $inc: {<field>:<증가시킬 값>,...}
$min 도큐먼트의 <field>필드의 값이 <최소값>이하면 <최소값>으로 설정한다. $min: {<field>:<최소값>,...}
$max 도큐먼트의 <field>필드의 값이 <최대값>이상이면 <최대값>으로 설정한다. $min: {<field>:<최대값>,...}
$mul 도큐먼트의 <field>필드의 값을 <배수>와 곱한 값으로 수정한다. $mul: {<field>:<배수>,...}
$rename 도큐먼트의 <field>필드의 이름을 <새이름>으로 바꾼다 $rename: {<field>:<새이름>,...}
$setOnInsert Upsert 값이 true 이면서 문서를 생성할 때만 사용되는 연산자. 쿼리와 $set 연산자에 나온필드와값이외의필드와값을함께설정한다. $setOnInsert: {<field>:<value>,...}

 

연산자 명 설명 형식
$addToSet 배열안에 해당 값이 없다면 추가하고, 있다면 추가하지 않는다. { $addToSet: { <field1>: <value1>, ... } }
$pop 배열의 첫번째 혹은 마지막 요소를 삭제한다. { $pop: { <field>: <-1 | 1>, ... } }
$pull 쿼리에 해당하는 요소 하나를 제거한다. { $pull: { <field1>: <value|condition>, <field2>: <value|condition>, .. .} }
$push 해당 요소를 배열에 추가한다. { $push: { <field1>: <value1>, ... } }
$pullAll 해당하는 값을 가지는 요소 전부를 제거한다. { $pullAll: { <field1>: [ <value1>, <value2> ... ], ... } }
$addToSet 배열안에 해당 값이 없다면 추가하고, 있다면 추가하지 않는다. { $addToSet: { <field1>: <value1>, ... } }
$pop 배열의 첫번째 혹은 마지막 요소를 삭제한다. { $pop: { <field>: <-1 | 1>, ... } }

06. 도큐먼트 삭제

하나의 도큐먼트 찾아 삭제하기

명령

result = collection.delete_one(
  { query }
)
print(result.deleted_count)

결과

#삭제된도큐먼트수
1

query로 검색하고 첫번째 도큐먼트를 삭제

 

# 하나의 도큐먼트 삭제 메소드: delete_one()

# 삭제된 도큐먼트 수 메소드: cursor.deleted_count

다수의 도큐먼트 찾아 삭제하기

명령

result = collection.delete_many(
  { query }
)
print(result.deleted_count)

결과

#삭제된도큐먼트수
12

# 다수의 도큐먼트 삭제 메소드: delete_many()

특정 field 값을 가진 document 삭제

명령

user.delete_one(
  {"username": "karoid"}
)

결과

{username:"karoid",password:"1111"}
				↓
                Null

 

명령

user.delete_many(
  {"password": "1111"}
)

결과

{username:"hello",password:"1111"},
{username:"hi",password:"1111"}
↓
Null

3. 쿼리 연산자

01. 쿼리의 구조

SQL의 모습

SELECT * FROM table WHERE column="value"

몽고디비 쿼리 모습

 collection.find({"field": "value"}, {})

쿼리의 형식

몽고디비 쿼리

{ <field>: {<operator1>: <value>, <operator2>: <value>}, <field>: ... }

기본적으로 쿼리는 필드가 가장 바깥에 있고, 안쪽에 연산자가 들어감

예시

 { "height": {"$gte": 175, "$lte": 180}, "width": {"$gte": 60} }

$or, $and, $nor이 들어간 쿼리의 형식

 {
       <$or,$and,$nor>: [<query>, <query>, ...],
       <field>: {<operator1>: <value>, <operator2>: <value>}, <field>: ...
}

 

예외적으로 $or, $and, $nor 세 개의 연산자는 가장 바깥에 쓰임

예시

 { "$or": [ { "status": "A" }, { "qty": { "$lt": 30 } } ] }

형식을 지키지 않은 예시

{"name": {"first": 'Karoid', "last": 'Jeong'}} # 잘못된 예
 
{"name.first": 'Karoid', "name.last": 'Jeong'}} # 올바른 예

 

정해진 형식을 지키지 않은 쿼리는 사용될 수 없다

02. 점 표기법

점표기법

BSON 내부의 Object에 접근하기 위한 방법

객체 내부로 접근하기

BSON 구조 예시

{
  "_id": ObjectId("542c2b97 bac059 5474 108b48"), 
  "name": {"first": "sue", "last": "Turing" }, 
  "age": 26,
  "is_alive": true,
  "groups": ["news", "sports"],
  "viewTime": ISODate("2017-10-24T05:02:46.395Z")
}

name 안의 first 값을 조회하려면 어떻게 할까?

{"name.first": 'sue'}

점 연산자로 내부에 접근

배열의 요소에 접근하기

배열의 첫 번째 요소로 news 값을 갖는 도큐먼트를 찾고 싶다면

{"groups.0": "news"}

더 복잡한 구조로 검색하기

{
  "_id" : ObjectId("5a105606e8762a54e6c1ee78"), 
  "item" : "paper",
  "qty" : 100,
  "size" : { "h" : 8.5, "w" : 11, "uom" : "in" }, 
  "status" : "D",
  "contribs" : [
	{ "name" : "karoid", "password" : 123 }, 
    { "name" : "db", "password" : 4433 },
	{ "size" : 3 }
  ] 
}

올바른 쿼리

{"contribs.0.name": "karoid"}

03. 비교 연산자

operator  설명
$eq (equals) 주어진 값과 일치하는 값
$gt (greater than) 주어진 값보다 큰 값
$gte (greather than or equals) 주어진 값보다 크거나 같은 값
$lt (less than) 주어진 값보다 작은 값
$lte (less than or equals) 주어진 값보다 작거나 같은 값
$ne (not equal) 주어진 값과 일치하지 않는 값
$in 주어진배열안에속하는값
$nin 주어진배열안에속하지않는값

대소 비교 쿼리

articles.find( { "likes": { "$gt": 10, "$lt": 30 } } )

좋아요 수가 10초과 30미만인 도큐먼트 검색

 articles.find( { "likes": { "$gte": 10, "$lte": 30 } } )

좋아요 수가 10이상 30이하인 도큐먼트 검색

숫자 외 다른 타입 비교

a [0, 1, 2, 10] {"1": 6, "a": 20}
"banana"  II  II     II    II   
c [0, 1, 3, 1] {"1": 6, "c": 10}
"desk"    

포함 쿼리 예시

inventory.find( { "qty": { "$in": [ 5, 15 ] } } )

수량이 5 또는 15인 아이템 도큐먼트

import re

inventory.find( { "tags": { "$nin": [
   re.compile("^be"),
   re.compile("^st")
]} } )

 

태그가 정규표현식 ^be 또는 ^st에 일치하지 않는 도큐먼트

04. 논리 연산자

operator 설명
$or 주어진조건중하나라도true일때true
$and 주어진모든조건이true일때true
$nor 주어진조건중하나라도false일때true
$not 주어진 조건이 false 일 때 true

연산자의 위치

 articles.find({ "$or": [ { "title": "article01" }, { "writer": "Alpha" } ] })

 

게시글 중 제목이 article01 이거나 작가가 Alpha인 도큐먼트

articles.find({ "likes": { "$not": { "$lte": 11 } } })

 

좋아요 수가 11 이하가 아닌 도큐먼트

 

복합적이 논리 연산자의 사용

 inventory.find( {
    "$and" : [
        { "$or" : [ { "price" : 0.99 }, { "price" : 1.99 } ] },
        { "$or" : [ { "sale" : True }, { "qty" : { "$lt" : 20 } } ] }
    ]
})

05. 문자열 연산자

operator 설명
$mod 그 필드에 modulo operation을 통해 특정 결과가 나온 Document를 선택한다.
$regex 특정 정규 표현식과 맞는 Document를 선택한다
$text 문자열 검색의 기능을 수행한다
$where 자바스크립트로 알맞은 Document를 선택한다

정규표현식 연산자

{ <field>: { "$regex": 'pattern', "$options": '<options>' } }
options 설명
i 대소문자 무시
m 정규식에서 anchor(^) 를 사용할 때 값에 \n 이 있다면 무력화
x 정규식 안에 있는 whitespace를 모두 무시
s dot(.)사용할때\n을포함해서매치

Text 연산자

 {
  "$text":    { "$search": <string>, "$language": <string>,
      "$caseSensitive": <boolean>, "$diacriticSensitive": <boolean> }
}
options 설명
$search 검색할 내용 # 기본값
$language 선택적.검색하는 언어  # 옵션
$caseSensitive 선택적.False일 경우 대소문자 무시. False가 기본값  # 옵션
$diacriticSensitive 선택적. g 같이 diacritical mark를 구분할지 선택. False가 기본값  # 옵션

컬렉션당 하나만 만들 수 있는 문자열 인덱스에서만 작동함

문자열 인덱스 설정

collection.create_index([('field', pymongo.TEXT)], default_language='english')

컬렉션당 하나만 만들 수 있는 문자열 인덱스에서만 작동함

안타깝게도 한국어는 문자열 인덱스로 지원하지 않음

 

*

텍스트 연산자를 사용하기 위해 기본적으로 문자열 인덱스를 먼저 설정해야함.
문자열 인덱스가 사전에 설정되어 있지 않고 텍스트 연산자를 사용하게 되면, 
검색이 되지 않고 에러를 내게됨. 
텍스트 연산자는 따로 필드를 지정하지 않았는데, 이유는 사전에 문자열 인덱스로 어떤 필드에서
전문검색할 지 사전에 정의를 내려놓았지 때문에 어떤 필드에서 검색할지 지정해놓지 않은 것.

Text 연산자 예시: 구절 검색

articles.find( { "$text": { "$search": "\"coffee shop\"" } } )

06. 배열 연산자

도큐먼트에서 배열의 의미

쿼리

inventory.find({"tags":"school"})

결과

{ "_id" : ObjectId("5a11937eabccf6b418a70421"),
 "tags" : [ "school", "book", "bag", "headphone", "appliance" ] }
{ "_id" : ObjectId("5a119396abccf6b418a70422"), 
 "tags" : [ "appliance", "school", "book" ] }

 

배열은 값이 여러 개(순서 상관 있음)인 것으로 인식

연산자 소개

operator 설명
$all 순서와 상관없이 배열 안의 요소가 모두 포함되면 선택한다
$elemMatch $elemMatch 조건과 맞는 배열 속 요소를 가진 Document를 선택한다.
$size 해당 배열의 크기가 같은 Document를 선택한다.

$all: 배열 속 모든 값을 포함하는 Document를 찾는다

{ <field>: { "$all": [ <value1> , <value2> ... ] } }

value1, value2가 포함된 도큐먼트를 찾는다

$elemMatch: 해당 field가 query들을 모두 만족하는 값을 갖는 도큐먼트를 검색

{ <field>: { "$elemMatch": { <query1>, <query2>, ... } } }

쿼리

score.find({ "results": { "$elemMatch": { "$gte": 80, "$lt": 85 } } })

결과

{ "_id" : 1, "results" : [ 82, 85, 88 ] }

 

$size 연산자: 해당 field가 모든 query를 만족하는 값을 갖는 Document를 선택

{ <field>: { "$size": <array size> } }

4. 고급 활용 기능

01. Flask와 연결하기

import pymongo
from bson import ObjectId
import csv
from flask import Flask, render_template, request, redirect

app = Flask(__name__)
client = pymongo.MongoClient('localhost', 27017) 
db = client.get_database("elice")
col = db.get_collection("post")

글 목록 보기

app.py

@app.route("/", methods=['GET'])
def index():
	documents = col.find()
	return render_template('index.html', documents=documents)

index.html

{% for doc in documents %}
  <li><a href="/post/{{ doc._id }}">{{ doc.title }}</a></li>
{% endfor %}

글 작성하기

app.py

@app.route("/new")
def new():
    return render_template('main.html')

main.html

<h2>Add a Post</h2>
<form action="/create" method="POST">
  <table>
   <tr><td>Title: </td><td><input name="title"></td></tr> 
   <tr><td>Content: </td><td><input name="content"></td></tr>
  </table> 
</form>

글 생성하기

@app.route("/create", methods=['POST'])
def save():
	data = {"title": request.form['title'], 
    	"content": request.form['content']}
    res = col.insert_one(data)
    return redirect(f"/post/{res.inserted_id}")

단일 글 읽기

app.py

 @app.route("/post/<_id>", methods=['GET']) 
 def show(_id):
    post = col.find({ "_id": ObjectId(_id) })[0]
    return render_template('show.html', post=post)

show.html

<h2>{{ post.title }}</h2>
_id: {{ post._id }}<br>
Content: {{ post.content }}<br>

02. 세 가지 집계 방법론과 효율성

집계 명령 수행 방법

도큐먼트를 집계하는 방법은 크게 세가지가 있다

  1. 데이터베이스의 정보를 불러와 애플리케이션 단계에서 집계하는 방법
  2. MongoDB의 맵-리듀스 기능을 이용하는 방법
  3. MongoDB의 집계 파이프라인 기능을 이용하는 방법

집계 명령의 특징

원본 데이터보다 결과 데이터의 양이 더 적다
집계 연산을 데이터 처리 초기 단계에서 할수록 유리

MongoDB와 웹 클라이언트의 통신 구조

처리 위치: 애플리케이션 내부, JS 엔진, MongoDB 내부

집계 방식에 따른 처리 속도

처리 속도: 애플리케이션 < 맵-리듀스 < 집계 파이프라인

*

집계연산 처리 위치가 장기저장 장치에 가까울 수록 빠르다
쿼리 처리기 - C언어 -> 속도 빠르다 -> 집계 파이프라인 속도 높다

집계 방식에 따른 자유도

자유도: 애플리케이션 > -리듀스 > 집계 파이프라인

*

자유도: 자기가 원하는 대로 집계 연산을 하려는 의도에 따라
100가지를 수행하려고 할때 100가지 모두 수행한다면 자유도가 매우 높은 것
우리가 원하는대로 데이터를 가공할 수 있는지 여부

맵-리듀스

맵핑함수: 관련된 정보끼리 그룹화

리듀스함수:그룹내정보들을집계연산(ex.평균,길이,합산...)

맵-리듀스 함수의 데이터 처리 과정

Limit 작업 <- Sort 작업 <- Query 작업    
           
Map 함수 -> 같은 키 값끼리
그루핑
-> Reduce 함수    
    /    
Scope로 정의된 변수 Finalizer
함수
-> Out 작업

파란색으로 표시된 단계가 필수 단계

집계 파이프라인

한 데이터 처리 단계의 출력이 다음 단계의 입력으로 연결된 구조

# 집계 파이프라인을 배워야지만 그룹화에 대한 모든 작업을 할 수 있다.

03. 인덱스 개요

인덱스의 기능

인덱스는 색인과 거의 같은 기능을 수행

인덱스는 검색과 순서 정렬을 효율적으로 만들어준다

인덱스의 종류

- 단순 인덱스: 하나의 필드를 기준으로 생성한 인덱스

- 복합 인덱스: 다수의 필드를 기준으로 생성한 인덱스

인덱스 특징

쿼리를 수행할 때 인덱스가 없다면 모든 도큐먼트를 일일이 조회해야 한다

인덱스는 쿼리 작업을 매우 효율적으로 만든다

 

인덱스를 만들면 도큐먼트 생성 수정 시 인덱스를 업데이트해야 하기 때문에 속도 저하가 있다

 

하나의 필드만 조회할 때는 단순 인덱스로 충분하지만 다수의 필드를 대상으로 조회를 할 때는 복합 인덱스가 유용

 

a-b 복합 인덱스는 a 단순 인덱스와 같은 기능을 하므로 대체할 수 있다

 

  1. 인덱스는 쿼리 작업을 매우 효율적으로 만든다
  2. 인덱스를 만들면 도큐먼트 생성 수정 시 속도 저하가 생긴다 
  3. 다수의 필드를 대상으로 조회를 할 때는 복합 인덱스가 유용하다
  4. a-b 복합 인덱스는 a 단순 인덱스와 같은 기능을 할 수 있다

04. 복제 세트 이해하기

복제 세트

복제 세트는 같은 정보를 공유하는 Data Set

 

왜굳이복제를할까?

1. 높은 가용성을 위해서! # 가용성: 항상 사용할 수 있는 상태 -> 안전하다
2. 정보의 안전한 보호를 위해

 

3. Read 속도를 빠르게 하기 위해서 # 선택사항

복제 세트의 구성원

복제 세트는 프라이머리, 세컨더리, 아비터 구성원으로 이루어져 있다

Heartbeat으로 서로의 상태를 확인한다

 

# 프라이머리: 클라이언트로부터 직접 RW요청수행

# 세컨더리: 프라이머리의 정보 상태를 갱신하며 복제 상태를 유지

# 아비터구성원은 세컨더리와 달리 정보를 가지고 있지 않다.
선거를 위해 존재

복제 세트의 선거

프라이머리가 무슨 이유에서든지 죽게 되면,
복제 세트 구성원 중 과반수의 세컨더리가 이를 감지하여

선거를 개최하기로 결정한다

 

세컨더리와 아비터는 새로운 프라이머리를 뽑는 투표를 하게 된다

우선순위가 높은 순서대로 세컨더리는 프라이머리 후보가 되고,

과반수의 찬성표를 받은 세컨더리는 프라이머리가 된다

 

만약 별문제가 없으면 투표권자들은 찬성표를 던지지만,

다음과 같은 경우에는 반대표를 던진다

PowerPoint 프레젠테이션 primary가 아직 제대로 작동하는데?
후보 너보다 내가 더 최신 데이터를 전달받았어
...등등

복제 세트의 장점

세컨더리를 활용해 읽기 기능을 확장할 수 있다

# 세컨더리로 읽기를 할 경우 “읽기 지연”이라는 단점이 생김

05. Read-Concern과 Write-Concern

Read-Concern과 Write-Concern이 필요한 이유

복제 세트에서 구성원의 정보가 동기화되는 데에는 필연적으로 시간이 필요

 

Read-Concern

어느 정도 동기화 수준을 기준으로 쓰기 작업을 마무리할지 설정

Write-Concern

어느 정도 동기화 수준을 기준으로 정보를 읽어올지 설정

Read-Concern과 Write-Concern 설정 방법

import pymongo
from pymongo.write_concern import WriteConcern
from pymongo.read_concern import ReadConcern

client = pymongo.MongoClient('localhost', 27017)
db = client.get_database("elice")

rc = ReadConcern(level='majority')
wc = WriteConcern(w=1, wtimeout=200, j=True)
col = db.get_collection("post", write_concern=wc, read_concern=rc)

Read-Concern 설정

 ReadConcern(level='majority')

 

  • local: 연결된 인스턴스에서만 정보를 불러온다
  • majority: 복제 세트 대다수에 저장된 정보로 불러온다
  • linearizable: 시간 제한 내에 복제 세트 구성원의 정보를 확인해서 대다수에 저장된 정보로 불러온다

복제세트는 세컨더리로부터 정보를 불러올 수 있다.
세컨더리로 부터 불러올 때 단점은 지연이 발생하는 것
지연이 발생할 때 서로 다른 클라이언트에게 다른 정보를 제공할 수 있다.
이러한 것들을 고려해야한다…
client가 primary에 연결되어있다면 굳이 ReadConcern은 필요없다.

MongoDB의 쓰기 작업과 저널링

메모리에 정보를 저장하는 시간이 디스크에 저장하는 시간보다 훨씬 빠르다

쓰기 작업은 메모리에 변경 사항을 남겼다가 일정 주기(50ms)로 디스크에 변경 사항을 기록한다

 

이처럼 디스크에 변경사항을 임시로 저장하는 작업을 저널링이라고 한다

# 저널링: 메모리에서 장기저장 장치로 저장할 때 사용하는 임시 저장 기술

Write-Concern과 저널링

WriteConcern(w=1, wtimeout=200, j=True)
필드 설명
w 복제 세트의 어느 정도의 구성원에 쓰기 작업이 완료되어야 전체 쓰기 작업이 완료되었다고 판단할지 결 정하는 옵션, 숫자나 문자열로 지정할 수 있다
j 이 옵션이 true 값을 가지면 변경 사항을 바로 저널링해서 만약 장애가 발생하더라도 문제가 없게 만든다, 기본값은 false
# True: 중요한 정보 저장(결제 데이터) / False: 메모리 저장 X => 저널링 X
wtimeout w 옵션에서 설정한 구성원들을 기다릴 수 있는 최대 시간(ms), 주어진 시간이 지나도 정해진 구성원에 쓰기 작업이 마무리되지 않으면 에러를 반환하지만 이미 실행한 쓰기 작업 자체는 취소되지 않는다
쓰기작업이무한정지연되는것을막기위한옵션으로w값이1보다커야사용할수있다

Write-Concern의 W 옵션

설명
0 쓰기 작업이 실제로 수행됐는지 확인하지 않고 쓰기 작업을 완료한다
1 (기본값) 클라이언트와 연결된 인스턴스의 쓰기 작업을 수행하면 전체 쓰기 작업이 완료된다
1보다 큰 자연수 값으로갖는숫자가복제세트에서쓰기작업을완료한구성원수와같으면전체쓰기작업이완료된다
예를 들어 프라이머리 1개와 세컨더리 2개로 이루어진 복제 세트에서 w: 2로 설정되었다면 쓰기 작업은 한 개의 프라이머리와 한 개의 세컨더리에서 쓰기 작업이 실행되면 전체 쓰기 작업이 완료된다
majority 복제 세트에서 대다수의 구성원이 쓰기 작업을 수행하면 전체 쓰기 작업이 완료된다

과반수가 왜 중요한가?
- 선거할 때 찬,반대표 기준
- 자신이 원본 데이터보다 최신 정보를 갖고있는지 여부 중요
- 대다수에 저장된 정보면 제대로 복구가 되지만, 소수만 저정되어 있다면 정보가 제대로 복구가 안될 수 있다.
-> write-concern을 알아야 mongoDB를 알 수 있다.

06. 샤드 클러스터 이해하기

샤드 클러스터의 필요

컴퓨터의 모든 장비는 고성능이 될수록 가성비가 안 좋아진다!

성능 2배 좋은 서버를 쓰는 것 보다 2개의 서버를 쓰는게 경제적

샤딩의 기준

범위 샤딩/ 해시 샤딩/ 구역 샤딩

샤딩은 특정 필드 값을 기준으로 정보를 분산시킨다

3가지 기준으로 도큐먼트를 분산시킬 수 있다

 

샤드에 정보가 골고루 분산되어서 연산이 골고루 이루어져야 한다

범위 샤딩

범위에 따라 분산하기 때문에 범위 조회시 유리
하지만 특정 범위에 도큐먼트가 쏠리면 해당 샤드만 계속 바빠질 수 있다

# 값이 똑같아 차이가 안날 경우 범위 샤딩이 의미가 없는 경우가 생김.

해시 샤딩

장점: 두루두루 저장되어서 한쪽에 몰릴 가능성이 낮다
단점: 범위를 찾는 쿼리를 수행할 때마다 다수의 클러스터에서 찾아야한다

구역 샤딩

개발자가 존을 정해서 분배하는 방식

손이 많이 가지만 최적화된 샤드 구성을 만들 수 있다

 

*

샤드 클러스터의 Config 서버는 클러스터의 설정값과 샤드의 메타데이터를 보유하고 있다.
범위 샤딩은 필드의 범위에 따라 분산하기 때문에 범위 조회 시 유리하다.
해시 샤딩은 두루두루 저장되어서 한쪽에 몰릴 가능성이 적다.
해시 샤딩은 범위를 찾는 쿼리를 수행할 때마다 다수의 클러스터에서 찾아야 해 불리하다.
구역 샤딩은 존을 정의한 후 해당 존에 맞게끔 샤드를 구성할 수 있는 방식이다.

 

반응형

댓글