DataBase

[DB]MongoDB

지성현 2022. 6. 21. 11:37

 

 

  • 학습 내용

NoSQL은 뛰어난 범용성과 비교적 쉬운 확장성으로 인해 최근 엄청나게 대규모 데이터를 다루는 때 많이 쓰인다고 들었다. 데이터를 사용해서 가치를 만드는 일을 하고 싶은 내게 어떻게 보면 NoSQL은 반드시 공부가 필요한 종목이다. 이번에는 내가 수료한 코드스테이츠에서 학습한 NoSQL의 MongoDB에 대해서 정리하고자 한다. MongoDB에 대한 기본적인 내용들이다. 더 깊은 내용은 앞으로 공식문서나 여러 서적을 통해서 차차 공부해나갈 계획이다. 학습을 진행하면 할수록 공식문서를 읽어내는 능력이 얼마나 중요한지 새삼 느낀다. 

 MongoDB는 대표적인 NoSQL 도큐먼트 데이터베이스이다. 도큐먼트 데이터베이스는 데이터를 테이블이 아닌 문서처럼 저장하는 데이터베이스를 말한다. 일반적으로 도큐먼트 데이터베이스에서 JSON 유사 형식으로 데이터를 문서화한다. 각각의 도큐먼트는 데이터를 필드-값의 형태로 가지고 있고 컬렉션이라고 하는 그룹으로 묶어서 관리한다.

 

  • NoSQL 데이터베이스

NoSQL은 매우 넓은 범위에서 사용하는 용어로 관계형 테이블의 레거시한 방법을 사용하지 않는 데이터 저장소를 말한다. 주로 NoSQL 데이터베이스는 비구조적인 대용량의 데이터를 저장하는 경우, 클라우드 컴퓨팅 및 저장 공간을 최대한 활용하는 경우, 빠르게 서비스를 구축하고 데이터 구조를 자주 업데이트하는 경우에 사용한다.

비구조적인 대용량 데이터를 저장하는 경우 NoSQL 데이터베이스는 관계에 중점을 둔 SQL 데이터베이스보다 자유로운 형태로 데이터를 저장할 수 있으므로 필요에 따라서 새로운 데이터 유형을 추가할 수 있다. 소프트웨어 개발에 정형화되지 않은 많은 양의 데이터가 필요한 경우, NoSQL이 효율적일 수 있다.
클라우드 컴퓨팅 및 저장 공간을 최대한 활용하는 경우 NoSQL 데이터베이스는 데이터베이스를 클라우드 기반으로 쉽게 분리할 수 있도록 지원하여, 저장 공간을 효율적으로 사용한다. 시스템이 커지면서 DB를 증설해야 하는 시점이 오면 SQL 데이터베이스에서는 수직적 확장의 형태로 DB를 증설한다. 수직적으로 확장된 데이터베이스는 관리가 어려워질 수 있는 데에 반해 NoSQL은 수평적 확장의 형태로 증설하므로 이론상 무한대로 서버를 계속 분산시켜 DB를 증설할 수 있다.
빠르게 서비스를 구축하고 데이터 구조를 자주 업데이트 하는 경우 NoSQL 데이터베이스의 경우 스키마를 미리 준비할 필요가 없어서 개발을 빠르게 해야 하는 경우에 매우 적합하다. 시작에 빠르게 프로토타입을 출시해야 하는 경우나 소프트웨어 버전별로 많은 다운타임(데이터베이스의 서버를 오프라인으로 전환하여 작업하는 시간)없이 데이터 구조를 자주 업데이트해야 하는 경우에는 일일이 스키마를 수정해 주어야 하는 관계형 데이터베이스보다 NoSQL 기반의 비관계형 데이터베이스가 더 효율적이다. 

 

  • Atlas Cloud

MongoDB에서는 아틀라스(Atlas)로 클라우드에 데이터베이스를 설정한다. 아틀라스는 GUI와 CLI로 데이터를 시각화, 분석, 내보내기 그리고 빌드하는 데에 사용할 수 있다. 아틀라스 사용자는 클러스터를 배포할 수 있으며 클라스터는 그룹화된 서버에 데이터를 저장한다. 서버는 레플리카 세트(Replica set)로 구성되어 있다. 여기서 레플리카 세트란 동일한 데이터를 저장하는 몇 개의 연결된 MongoDB 인스턴스의 모음을 말한다. 도큐먼트나 컬렉션을 변경할 경우 변경된 데이터의 중복 사본이 레플리카 세트에 저장된다. 이 설정 덕분에 레플리카 세트의 인스턴스 중 하나에 문제가 발생하더라도 데이터는 그대로 유지되며 레플리카 세트의 앱에서 나머지 작업들 할 수 있다. 이 과정을 위해 클러스터(서버 그룹)를 배포하면 자동으로 레플리카 세트가 구성된다.

 

  • MongoDB Document

도큐먼트는 객체와 같이 데이터를 필드-값 쌍(Field - Value pair)으로 저장하고 구성한다. 도큐먼트에서 필드는 데이터의 고유한 식별자이고 값은 주어진 식별자의 관련된 데이터를 뜻한다.

{
  <field> : <value>,
  <field> : <value>,
  "name" : "seonghyeon",
  "title" : "Chapter Lead",
  "age" : 30
}

이런 도큐먼트의 모음을 컬렉션이라고 한다. 그리고 데이터베이스는 여러 개의 컬렉션으로 구성된다.

도큐먼트(Document) 필드 - 값 쌍으로 저장된 데이터
필드(Field) 데이터 포인트를 위한 고유한 식별자
값(Value) 주어진 식별자와 연결된 데이터
컬렉션(Collection) MongoDB의 도큐먼트로 구성된 저장소이다. 일반적으로 도큐먼트 간의 공통 필드가 있다. 데이터베이스 당 많은 컬렉션이 있고 컬렉션 당 많은 도큐먼트가 있을 수 있다.

 

  • JSON vs BSON

shell을 이용하여 도큐먼트를 조회하거나 업데이트할 때 도큐먼트는 JSON(JavaScript Object Notation) 형식으로 출력된다. JSON 형식은 읽기 쉽고 많은 개발자들이 사용하기 편리한 형태를 가지고 있다. 그렇기 때문에 JSON 형식은 데이터를 저장하는 좋은 방법 중 하나이다. 그러나 JSON의 형태로 데이터를 저장할 때도 단점이 존재하는데, JSON은 텍스트 형식이기 때문에 읽기 쉽지만 파싱이 느리고 메모리 사용이 비효율적이다. 그리고 JSON은 기본 데이터 타입만을 지원하기 때문에 사용할 수 있는 데이터 타입에 제약이 있다. 이런 문제점을 해결하기 위한 방안으로 BSON(Binary JSON) 형식을 도입했다.

BSON은 컴퓨터의 언어에 가까운 이진법에 기반을 둔 표현법이다. 따라서 JSON보다 메모리 사용이 효율적이며 빠르고, 가볍고, 유연하다. 뿐만 아니라 BSON의 사용으로 더 많은 데이터 타입을 사용할 수 있다. MongoDB는 JSON 형식으로 작성된 것은 무엇이든 데이터베이스에 추가할 수 있고 쉽게 조회할 수 있다. 그러나 내부에서는 속도, 효율성, 유연성의 장점이 있는 BSON으로 데이터를 저장, 사용한다.

 

  • MongoDB Importing & Exporting

데이터를 가져오거나(import), 내보내는(export) 경우에 따른 효율적인 데이터 형식이 존재한다. MongoDB의 데이터는 BSON의 형태로 저장되고 보통 읽기 쉬운 JSON의 형태로 출력된다. JSON 형식으로 데이터를 가져오고 내보내기 위한 명령어로 mongoimport와 mongoexport가 있고 BSON 형식으로 가져오고 내보내기 위한 명령어로 mongostore와 mongodump가 있다.

 먼저 내보내기(export)를 살펴보면 사용하기 위해서 먼저 Atlas Cluster URI가 필요하다. 해당 URI는 일반 웹의 URI와 형식이 같고 username, password, cluster 주소로 이루어져 있다. BSON의 mongodump는 별다른 쿼리가 없지만 JSON의 mongoexport를 하는 경우에는 해당 데이터베이스의 컬렉션 이름, 파일 이름까지 정확하게 작성해줘야 한다.

 

 가져오기(import)의 경우 내보내기(export)와 마찬가지로 먼저 URI를 사용해서 작성하고 기준에 있는 데이터를 삭제하기 위한 옵션인 drop 쿼리문을 선택적으로 사용할 수 있다.

 

 

  • mongoDB CRUD

모든 MongoDB 도큐먼트는 _id 필드를 기본값으로 반드시 가지고 있어야 한다. 이 _id 필드의 값은 각 도큐먼트를 구별하는 역할을 한다. 도큐먼트 내부의 필드와 값이 똑같다 할지라도 _id 값이 다르면 서로 다른 도큐먼트로 간주한다. 반면에 도큐먼트 내 필드와 값이 다르다고 하더라도 _id 값이 같으면 서로 같은 도큐먼트로 여겨 에러를 발생시킨다. 새로운 도큐먼트를 추가할 때 _id 값에 임의적으로 고유한 값을 생성해서 사용할 수도 있지만 보통은 ObjectId 타입(12byte, 24char)의 값으로 사용한다. 도큐먼트를 추가할 때 _id 필드와 값을 특정하지 않았다면 자동적으로 _id 필드가 생성되고 값에 ObjectId 타입이 할당된다.

1) CREATE

도큐먼트를 삽입하기 위해서는 insert 명령어를 사용한다. insert()의 괄호 안에 삽입하고자 하는 도큐먼트를 작성하면 된다. 이 명령어에 따른 결과물은 WriteResult로 출력된다. 다수의 도큐먼트를 삽입할 때는 배열 안에 해당하는 도큐먼트를 담아줘야 한다. 만약 다수의 도큐먼트를 삽입할 때 배열에 순서에 의해서 진행되는 중 에러가 나면 삽입이 모두 중단된다. 그때 insert 명령어의 2번째 인자에 순서 옵션인 ordered를 추가해 주면 고유한 _id를 가진 도큐먼트들은 모두 컬렉션에 삽입된다.

2) READ

데이터를 조회하기 위해서는 find 명령어를 사용한다. db.collection_name.find(<쿼리문>) 이렇게 사용하면 된다. 데이터의 조회 결과는 JSON으로 출력된다. 만약 두 가지의 조건을 주고 싶으면 그 조건을 find(<쿼리문1, 쿼리문2>)의 형태로 작성하면 된다. 그리고 쿼리문 없이 find 명령어를 사용하면 해당 데이터베이스의 모든 데이터를 조회할 수 있다. 조회된 데이터의 형태가 복잡하거나 깔끔하지 않아서 가독성이 떨어지는 경우에는 pretty()라는 명령어를 덧붙이면 더 깔끔한 데이터를 볼 수 있다. 그리고 조회 결과 데이터의 수를 조회하기 위해서 count() 명령어를 사용하면 된다. 특정한 1개의 데이터를 조회하는 경우에는 findOne 명령어를 사용하면 된다.

3) UPDATE

도큐먼트를 업데이트하기 위한 방법에는 updateOne과 updateMany 명령어 두 가지를 사용할 수 있다. updateOne은 주어진 기준에 맞는 다수의 도큐먼트 중 첫 번째 도큐먼트만 업데이트하고 updateMany는 쿼리문과 일치하는 모든 도큐먼트를 업데이트한다.

4) DELETE

도큐먼트 및 컬렉션을 삭제할 때는 deleteOne()과 deleteMany() 명령어 두 가지를 사용할 수 있다. deleteOne은 주어진 기준에 맞는 다수의 도큐먼트 중 첫 번째 도큐먼트 하나를 삭제하고 deleteMany는 쿼리문과 일치하는 모든 도큐먼트를 삭제한다. 컬렉션을 삭제하기 위해서는 drop 명령어를 사용하면 된다.

 

  • 비교 연산자

mongoDB 비교 연산자에는 $eq, $ne, $gt, $lt, $gte, $lte가 있다. 사용 방법은 {<filed> : {<operator> : <value>}} 이렇다.

$eq 같은 지 여부 확인
$ne 같지 않은지 여부 확인
$gt 큰지 확인
$lt 작은지 확인
$gte 크거나 같은지 확인
$lte 작거나 같은지 확인

 

  • 논리 연산자

논리 연산자에는 $and, $or, $nor, $not이 있다. 사용 방법은 $and, $or, $nor의 경우 {<operator> : [{statement1}, {statement2}, ...]} 이고 $not의 경우 {$not: {statement}}이다.

$and 주어진 모든 쿼리절과 일치하는가
$or 주어진 쿼리절이 하나라도 일치하는가
$nor 주어진 모든 쿼리절과 일치하지 않는가
$not 주어진 쿼리와 일치하지 않는가

 

  • 표현 연산자

표현 연산자에는 $expr이 있다. 표현 연산자는 쿼리 내에서 집계 표현식을 사용할 때 {$expr : {expression}}, 변수와 조건문을 표현할 때, 같은 도큐먼트 내의 필드들을 비교할 때 사용한다. $는 연산자를 나타낼 뿐만 아니라 필드의 값을 참조할 때 사용한다. $을 사용하여 각각의 도큐먼트마다 달라지는 특정 필드의 값을 변수처럼 비교할 수도 있다. 집계 표현식에서 비교 연산자는 {<operator> : {<field>, <value>}} 와 같이 쓰인다.

 

  • 배열 연산자

배열 연산자에 $push, $all, $size가 있다.

$push 배열의 마지막 위치에 엘리먼트를 넣음. 배열이 아닌 필드에 사용했을 경우 필드의 타입을 배열로 바꾼다.
$size 지정된 배열 필드가 주어진 길이와 정확히 일치하는 모든 도큐먼트들이 있는 커서를 반환함. {<array field> : {"$size" : <number>}}
$all 지정된 배열 필드의 배열 순서와 관계없이 지정된 모든 요소가 포함된 모든 도큐먼트들이 있는 커서를 반환함. {<array field> : {"$all" : <array>}}

 

  • 배열 연산자와 Projection

도큐먼트에 너무 많은 필드와 정보가 있기 때문에 그 안의 정보를 확인하기 어려울 때가 있다. 이걸 완화하기 위해 find 쿼리에 projection을 추가해 현재 관심 있는 필드만 결과로 가져올 수 있다. find의 두 번째 인자로 projection을 추가하면 해당 필드만 결과 커서에 포함된다. projection을 사용할 때는 0과 1을 사용해 결과에 표시하거나 표시하지 않을 필드를 지정할 수 있다. 1을 사용하면 지정한 필드와 _id 필드만 가져오고 0을 사용하면 지정한 필드를 제외한 모든 필드가 표시된다. 한 번의 projection에 0과 1을 혼합할 수 없다. db.<collection>.find({query}, {<field1> : 0, <field2> : 0}) 다만 디폴트로 포함되는 _id의 경우 예외적으로 0과 1을 혼용하여 사용할 수 있다.

 만약 배열 필드의 서브 도큐먼트에 있는 요소에 접근하고자 할 때는 elemMatch 명령어를 사용하면 좋다. {<field> : {"$elemMatch" : {<field> : <value>}}} 만약 첫 번째 인자에서 쓰일 경우에는 배열 필드의 서브 도큐먼트 필드가 쿼리와 일치하는 문서를 찾는 것이고 두 번째 인자에서 쓰일 경우에는 지정된 기준과 일치하는 요소가 하나 이상 있는 배열 요소만 프로젝션 한다.

 

  • 배열과 서브 도큐먼트 쿼리하기

MongoDB는 유연한 데이터 모델링을 통해 개발자가 데이터를 저장할 방법을 결정할 수 있다. 그래서 MongoDB에서는 일반적으로 서브 도큐먼트 또는 배열로 저장한다. 서브 도큐먼트로 접근하고자 하는 경우 먼저 dot notation을 사용할 수 있다. 배열에서 dot notation을 사용하고자 할 때는 요소의 위치를 지정해야 한다. db.collection.find("filed 1.other filed.also a field" : "value"})

 

  • aggregation Framework

Aggregation Framework는 MongoDB에서 데이터를 파이프라인에 따라 처리할 수 있는 강력한 프레임워크이다. 데이터를 아주 간단히 쿼리 할 수 있다. find 명령어 대신 aggregate 명령어를 사용한다. aggregate를 사용하면 도큐먼트를 필터링하지 않고 그룹으로 데이터를 집계하거나 데이터를 수정할 수 있다. 또한 데이터 찾기 및 프로젝션 없이 작업을 수행하거나 계산할 수 있다. aggregate를 사용할 때는 대괄호를 이용해 배열을 인자로 사용한다. 이는 파이프라인처럼 배열 요소의 순서대로 작업하기 때문이다. 여기에 두 개의 필터가 존재한다. 첫 번째 필터는 $match 단계로 앞선 필드를 통과하지 못하면 파이프라인의 다음 단계를 통과하지 못하도록 필터 역할을 한다. 두 번째로 $group단계는 들어온 데이터 스트림을 여러 개로 그룹화하는 연산자이다. 만약 필터링 단계 없이 원본 데이터를 바로 받아오면 원본 데이터를 수정하지 않는다. 필터링 단계가 있으면 이를 통과한 데이터를 다음 단계에서 작업한다. 

{ $group : 
  {
    _id : <expression>,
    <field 1> : { <accumulator 1> : <expression 1> },
    ...}}

각 단계의 이름 앞에는 $가 있고 그 뒤에는 실행할 작업에 대한 설명이 온다.