DX Data School

NoSQL (MongoDB)

Kim J 2024. 1. 30. 09:38

1.데이터 베이스 분류

1) RDBMS : 테이블 기반, SQL을 이용해서 작업하는 관계형 데이터베이스(트리구조)

삽입 갱신 삭제(C U D) 작업에는 관계형 데이터베이스가 좋다

2) NoSQL (Not Only SQL) :

RDBMS 시스템의 주요 특성을 보장하는 ACID(Atomicity, Consistency, Isolation, Durability - Transaction의 주요 성질) 특성을 제공하지 않는 확장성이나 성능의 특성을 갖는 비관계형 데이터베이스

읽기(R) 작업에는 비관계형 데이터베이스가 좋다. 해싱을 통해 조회속도가 빠르다

틀이 없기 때문에 모든 데이터가 다 들어간다 But, 유효성 검사가 어렵다

주로 빅데이터, 분산 시스템 환경에서 대용량의 데이터를 처리하는데 적합함.

- 최근에는 NoSQL에 관계형 데이터베이스의 트랙잭션의 개념을 도입시키고 관계형 데이터베이스에도 NoSQL의 개념을 도입하는 형태로 발전하고 있다.

2. No SQL 종류

1) Key Value DB

- Key 와 Value 형태로 저장하는 데이터베이스 (dict, table, map)

Redis가 대표적인 Key Value인 데이터베이스

2) Document DB

- 하나의 데이터를 하나의 문서로 취급, MongoDB가 대표적인 Document DB

3) Wide Column Store

- 열의 집합체를 만들 수 있는 데이터베이스

HBase 나 Cassandra 가 대표적인 Wide Column Store

4) Graph DB

- 데이터에 그래프 자료구조를 도입한 데이터베이스

SNS에 주로 이용 ( 링크 리스트 구조 [정해진 방향이 없다])

3. MongoDB

1) 개요

- 데이터를 하나의 문서로 취급하는데 데이터를 표현할 때 Json 표기법(자바스크립트 객체 표현법, 파이썬도 동일) 사용

** Json표기법 - ex) 배열 : [데이터 나열] / 객체(dict) : { 키 : 값, 키 : 값 ...}

Mongo DB는 자바스크립트 문법으로 데이터를 다룬다.

MEAN [Mongo DB / Express.js / Angular.js / Node.js] : 자바스크립트 만으로 Wep application을 제작하는 기술, Angular.js 서비스가 종료 됨에 따라 현재는 MER(React.js)N

2) docker를 이용한 MongoDB 설치

- mongo 이미지를 다운로드 받아 컨테이너 이름으로 컨테이너 생성 후 data/ dat/db 라는 디렉토리에 데이터를 저장, 백그라운드로 실행하고 27017 번 포트를 27017 포트로 접속할 수 있도록 실행

docker run --name 컨테이너 이름 -v ~/data/data/db -d -p 27017:27017 mongo

docker run --name JHMongoDB -v ~/data/data/db -d -p 27017:27017 mongo

3) bash shell 에 접속

** shell : 운영체제와 사용자 사이의 인터페이스

** bash shell : shell 종류 중에 가장 많이 사용되는 shell

- docker 컨테이너 안에 접속

docker exec -it 컨테이너 이름 bash

docker exec -it JHMongoDB bash

4) Mongo DB Manual

- https://docs.mongodb.com/manual/crud/

Mongo DB Document

 

5) Mongo DB 작업

- 로컬에서 직접 설치한 경우 터미널에서 접속해 사용할 수 있고 도커에 설치한 경우 BASH SHELL에 접속해서 명령어를 실행할 수 있다.

Compass 나 Robo 3T와 같은 GUI Tool을 이용해서 작업을 수행할 수 있다.

6) 구성요소

- 데이터베이스 : 가장 큰 단위

- 컬렉션 : 테이블의 개념, 미리 생성하거나 구조를 만들 필요가 없다.

- 도큐먼트 : 하나의 데이터로 관계형 데이터베이스의 행의 개념

- 필드 : 관계형 데이터베이스에서 열의 개념

- 인덱스 : 데이터에 빠르게 접근하기 위한 객체

- Join이 없는 대신 Embedding 이나 Linking의 개념을 가짐.

(데이터 안의 필드에 다른 테이블을 포함 시킬수 있다.)

- select 문의 결과로 관계형 데이터 베이스는 Row의 집합을 반환 하지만 Cursor를 반환

** RDBMS 와 Mongo DB 용어 비교

RDBMS -> Mongo DB

Database -> Database

Table -> Collection

Row -> Document

Column -> Field

Index -> Index

Join -> Embedding & Linking

Select 구문의 결과로 Row의 집합을 반환 -> Cursor 반환

7) CRUD

(Create - 데이터 삽입, Read - 데이터 조회, Update - 데이터 갱신, Delete - 데이터 삭제)

> 데이터 입력

db.컬렉션이름.insertOne({ 키:값, 키:값 ...})

db.컬렉션이름.insertMany([{ 키:값, 키:값 ...},{ 키:값, 키:값 ...}..])

db.컬렉션이름.insertOne({})

db.mycollection.insertOne({"name" : "Kim J"})

db.mycollection.insertOne({"height" : 170, "weight" : 66})

키는 문자열이고 값은 null, 숫자, 문자, 객체, 배열, boolean, 날짜 등이 가능

데이터를 삽입할 때 Objectld 라는 타입의 _id 라는 키 값이 같이 삽입된다.

ex)

db.users.insertOne({"name":"park", "age":34}) - 1

db.users.insertOne({"nik":"kim", "height":170}) - 2

db.users.insertOne({"name":"park", "age":"34"}) - 3

1,2 / 1,3 / 1,2,3

모두 입력이 되긴 하지만 합당하지 않다. 비교하기가 어렵다. 따라서 입력 시 주의해야 함

> 데이터 확인

db.컬렉션이름.find({});

db.mycollection.find({});

> 현재 데이터베이스 확인

db;

> 전체 데이터베이스 확인 (데이터가 존재하는 것 만 확인가능)

show dbs;

> 데이터베이스 생성 및 사용

use 데이터베이스이름;

use jhmongo;

switched to db jhmongo / 만들고 쓰는게 아니라 그냥 쓰면 만들어진다.

> 데이터베이스 삭제

db.dropDatabase();

> Capped Collection

정해진 크기를 초과하면 자동으로 가장 오래된 데이터를 삭제하는 Collection (관계형 데이터베이스와 다른점 중 하나)

디스크가 한정 된 상황에서 로그 데이터나 분석 결과를 저장해야 하는 경우 사용함

-- Capped Collection 생성

db.createCollection('cappedCollection',{capped:true, size:10000});

-- 데이터 삽입

db.cappedCollection.insertOne({x:1});

-- 데이터 확인

db.cappedCollection.find({});

-- 데이터 여러개 삽입 ( 반복문 가능 )

for(i=0, i<1000; i++){db.cappedCollection.insertOne({x:1})};

> View

[★★관계형 데이터베이스의 View와 다름]

데이터베이스 안에 쓸수는 없고 읽을 수만 있는 데이터베이스 개체

View 를 만들게 되면 읽기 속도가 빨라지고 보안을 유지할 수 있다.

(메모리에 저장해 두고 불러오기 때문에 파일에서 불러오는 것 보다 빠름)

8) Thread 스레드

- Mongo DB는 기본적으로 싱글 스레드 / 멀티 스레드로 작업 가능

- Program, Process, Thread

Program : 동일한 목적을 달성하기 위한 파일의 집합

 

Process : 실행 중인 프로그램 ( 프로그램을 실행하면 프로세스 )

Thread : 프로세스 안에서 자원을 할당해 수행하는 작업 단위

실행 중간에 다른 스레드로 제어권을 넘길 수 있다.

** Process와 다른점 : 단독으로 실행 될 수 없고 반드시 프로세스 안에서 실행되어야 함

프로세스 안에는 반드시 1개 이상의 스레드가 존재한다.

** 함수를 그냥 실행하면 함수는 수행이 종료 될 때까지 다른 함수를 수행하지 못하지만 함수를 스레드로 실행하면 수행 중간에 다른 스레드를 실행하고 돌아올 수 있다.

(쉽게 말해서 집에서 밥을 밥솥에 취사해두고 청소를 하고 빨래를 하러 세탁기에 다녀올 수 있다. cpu는 1개지만 여러작업 수행. but 이것저것 불필요한 부가적인 작업이 많으면 왔다갔다 하느라 시간을 다보낸다. )

> python에서 스레드 프로그래밍

#스레드를 만들지 않고 2개의 함수를 호출 > 20초 정도

import time

def threadex(id):

for i in range(10):

print("id={0} --> {1}".format(id,i))

time.sleep(1) # 1초 쉬었다 반복 한다

for i in range(2):

threadex("{0}번 스레드".format(i))

# 2개의 스레드를 만들어서 함수를 호출 > 10초 정도

import time, threading

def threadex(id):

for i in range(10):

print("id={0} --> {1}".format(id,i))

time.sleep(1) # 1초 쉬었다 반복 한다

for i in range(2):

id = ("{0}번 스레드".format(i))

th = threading.Thread(target=threadex, args=(id, )) # 스레드를 만든 것 args 튜플로 줘야함

th.start() # 스레드 실행

> Asynchronous(비동기)

동기 : 순서대로 하나씩 실행

비동기 : 수행 중에도 다른 작업으로 제어권을 넘길 수 있는 것

스레드 프로그래밍과 유사하게 사용

인터넷이 느린곳에서는 비동기를 사용할 경우 오래걸림

(한국에서 쓰는 네이버 메인 포탈에서는 많이 쓰지만, 전 세계에서 많이 쓰는 구글 메인에는 없다.)

> 스레드 프로그래밍에서 주의할 점

공유 자원 수정 문제 발생 시

Lock 활용하여 세마포어 형식 적용

import time, threading
# 전역 변수로 사용할 변수
g_count = 0
# 공유자원을 제어하기 위한 변수
lock = threading.Lock()

# 상속을 이용한 스레드 클래스 생성
class threadex(threading.Thread):
        # 스레드로 수행 할 메서드
    def run(self):
        # 외부에서 만든 g_count, lock 사용
        global g_count
        global lock

             for i in range(10):
                 lock.acquire()
                 print("id={0} 증가하기 전 --> {1}".format(self.getName(), g_count))
                 g_count = g_count + 1
                 time.sleep(1)
                 print("id={0} 증가한 후 --> {1}".format(self.getName(), g_count))
                 lock.release()
                 time.sleep(1)

for i in range(2):
    th = threadex()
    th.start()

생산자와 소비자 문제

Dead Lock

9) Mongo DB에서의 멀티 스레드를 이용한 데이터 삽입

> name을 인덱스로 설정해 유일무이하게 저장하도록 컬렉션을 생성

db.컬렉션이름.createIndex({name:1},{unique:true});

db.sample.createIndex({name:1},{unique:true});

> 데이터 삽입

db.sample.insertOne({name:"adam"});

> 여러 개의 데이터 삽입

db.sample.insert({name : "itstudy"}, {name : "adam"}, {name : "ggangpae1"});

adma이 중복이기 때문에 오류 발생 후 rollback 된다.

> 데이터 확인

db.sample.find({});

데이터 삽입 중 오류가 발생 했지만, NoSQL은 싱글 스레드 형태로 동작하기 때문에 itstudy는 삽입이 되고 ggangpae1은 삽입이 되지 않음

> 멀티 스레드를 이용해 삽입을 원할경우, {ordered : false} 라는 옵션 추가

> 멀티 스레드를 이용해 여러개의 데이터 삽입

db.sample.insert([{name : "itstudy"}, {name : "adam"}, {name : "ggangpae1"}], {ordered:false});

> 데이터 확인

db.sample.find({});

모든 데이터가 각각의 스레드로 동작하기 때문에 하나의 스레드에 오류가 발생 하더라도 나머지 스레드는 정삭적으로 수행한다. itstudy와 adam을 삽입하려고 하는 것은 오류가 발생하지만 ggangpae1은 별개로 삽입된다.

** CQRS 패턴

CRUD(Create, Read, Update, Delete)에서CUD(Command)와 R(Query)을 구분하자는 이야기다.구분하는 이유는,우리가 Database로부터 데이터를 읽어오고 처리를 하게 되면이미 그 사이에 데이터가 변경이 되었을 가능성이 높다.CQRS는 이런 변경 가능성을 인정하고 어차피 Read와 CUD 사이에는 delay가 존재할 수 있음을 인정하는 것이다.이를 통해서 R과 CUD를 구분함으로써 얻는 이점을 설명하는 것이 CQRS패턴이다.

 

데이터 작업을 위한 Application ↔ RDBMS

↓↑

Message Broker = Kafka

↓↑

데이터 읽기을 위한 Application ↔ NoSQL

CQRS 패턴을 통해 얻을 수 있는 이점은 여러 가지가 있다.

Read와 CUD 각각에 더 최적화된 Database 구성을 통해서 성능을 더 향상시킬 수 있다.

Read와 CUD에서 필요한 데이터 형식이 다를 수 있고, 특히 Read는 aggregation(집계 함수) 등의 부가적인 attribute들이 Entity에 필요하게 될 수 있다. R과 CUD를 분리함으로써 R로 인해 Entity의 구조가 변경되는 것을 막을 수 있다.

R과 CUD를 분리함으로써 과도하게 복잡한 모델을 덜 복합하게 만듦으로서 시스템 복잡도를 줄일 수 있다.

 

'DX Data School' 카테고리의 다른 글

Python 과 Mongo DB 연동  (1) 2024.01.30
Python의 Exception Handling(예외처리)  (1) 2024.01.30
Windows Function  (0) 2024.01.30
Transaction 실습  (0) 2024.01.30
[SQL]DDL, DML, DCL, DQL, TCL 개념과 종류  (0) 2024.01.30