상권's

TIL 40 (NoSQL, MongoDB, Aggregation)(2021.11.19) 본문

~2022 작성 글/TIL

TIL 40 (NoSQL, MongoDB, Aggregation)(2021.11.19)

라마치 2021. 11. 19. 16:58
문제
정수를 요소로 갖는 배열을 입력받아 오름차순으로 정렬하여 리턴해야 합니다.

입력
인자 1 : arr
number 타입을 요소로 갖는 배열arr[i]는 -100,000 이상 100,000 이하의 정수arr.length는 100,000 이하

출력
number 타입을 요소로 갖는 배열을 리턴해야 합니다.

주의사항
힙 정렬을 구현해야 합니다.arr.sort 사용은 금지됩니다.입력으로 주어진 배열은 중첩되지 않은 1차원 배열입니다.최소 힙(min heap)을 구현해야 합니다.최소 힙 구현을 위해 선언된 함수들(getParentIdx, insert, removeRoot)을 전부 완성해야 합니다.swap, getParentIdx, insert, removeRoot를 전부 사용해야 합니다.swap, binaryHeap을 수정하지 않아야 합니다.테스트 케이스에서 힙 함수들을 정확히 구현했는지 함께 테스트합니다.removeRoot의 시간 복잡도는 O(logN)입니다.
// 아래 코드는 수정하지 마세요.
function swap(idx1, idx2, arr) {
  [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];
}

function getParentIdx(idx) {
  return Math.floor((idx - 1) / 2)
}

function insert(heap, item) {
  // TODO: 여기에 코드를 작성합니다.
  heap.push(item);
  if (heap.length > 1) {
    let curIdx = heap.length - 1;
    let pIdx = getParentIdx(curIdx);
    while (pIdx >= 0 && heap[curIdx] < heap[pIdx]) {
      // 부모 노드를 찾아서 해당 값보다 작다면 swap한다.
      swap(curIdx, pIdx, heap);
      curIdx = pIdx;
      pIdx = getParentIdx(curIdx);
    }
  }
  return heap;
}

function removeRoot(heap) {
  // TODO: 여기에 코드를 작성합니다.
  swap(0, heap.length - 1, heap);
  heap.pop();
  if (heap.length === 0) return [];
  let curIdx;
  let minIdx = 0;
  while (curIdx !== minIdx) {
    // 자식들을 다 살펴본 경우, minidx에 대한 변경이 없기 때문에
    // 둘이 같아서 while문이 끝나게 된다.
    curIdx = minIdx;
    let left = curIdx * 2 + 1;
    // 자식 노드를 찾는다. 좌
    let right = curIdx * 2 + 2;
    // 자식 노드를 찾는다. 우
    if (left < heap.length && heap[left] < heap[minIdx]) {
      minIdx = left;
    }
    if (right < heap.length && heap[right] < heap[minIdx]) {
      minIdx = right;
    }
    swap(curIdx, minIdx, heap);
  }
  return heap;
}

// 아래 코드는 수정하지 마세요.
const binaryHeap = function (arr) {
  return arr.reduce((heap, item) => {
    return insert(heap, item);
  }, []);
};

const heapSort = function (arr) {
  let minHeap = binaryHeap(arr);
  // TODO: 여기에 코드를 작성합니다.
  const sorted = [];
  while (minHeap.length > 0) {
    sorted.push(minHeap[0]);
    minHeap = removeRoot(minHeap);
    // 루트가 없어지면 제일 작은 수가 맨 위로 올라오기 때문에 0번째를 push 해도 정렬이 이뤄진다.
  }
  return sorted;
};

=> 오늘은 최소힙을 이용해 heapsort를 구현해보았습니다. 어제 문제와 비슷해서 insert부분은 쉽게 구현할 수 있었는데, remove 기능 구현이 어려워서 레프런스의 도움을 받게 되었습니다. 다른 함수들을 통해서 정렬하는 함수 구현은 어렵지 않았습니다. 트리에서 부모, 자식을 찾는 것에 많은 어려움을 느꼈었는데, 어제 오늘 문제를 통해서 해당 방법을 숙지해야겠습니다.


NoSQL 기반의 비관계형 데이터베이스 사용하는 경우

 

1. 비구조적인 대용량의 데이터를 저장하는 경우

 소프트웨어 개발에 정형화되지 않은 많은 양의 데이터가 필요한 경우, NoSQL이 효율적일 수 있습니다.

 

2. 클라우드 컴퓨팅 및 저장 공간을 최대한 활용하는 경우

SQL 데이터베이스에서는 수직적 확장의 형태로 DB를 증설합니다.

NoSQL은 수평적 확장의 형태로 증설하므로, 이론상 무한대로 서버를 계속 분산시켜 DB를 증설할 수 있습니다.

 

3. 빠르게 서비스를 구축하고 데이터 구조를 자주 업데이트 하는 경우

시장에 빠르게 프로토타입을 출시해야 하는 경우나, 소프트웨어 버전별로 많은 다운타임(데이터베이스의 서버를 오프라인으로 전환하여 작업하는 시간) 없이 데이터 구조를 자주 업데이트해야 하는 경우에는 일일이 스키마를 수정해 주어야 하는 관계형 데이터베이스 보다 NoSQL 기반의 비관계형 데이터베이스가 더 효율적입니다.


MongoDB

 

MongoDB는 NoSQL 도큐먼트 데이터베이스입니다. MongoDB는 데이터를 도큐먼트의 형태로 저장합니다.
도큐먼트는 컬렉션에 저장되며, 이것이 MongoDB가 NoSQL 도큐먼트 데이터베이스로 분류되는 이유입니다.

 

Atlas Cloud

MongoDB에서는 아틀라스(Atlas)로 클라우드에 데이터베이스를 설정합니다.
아틀라스는 GUI와 CLI로 데이터를 시각화, 분석, 내보내기, 그리고 빌드하는 데에 사용할 수 있습니다. 아틀라스 사용자는 클러스터를 배포할 수 있으며, 클러스터는 그룹화된 서버에 데이터를 저장합니다.

 

이 서버는 레플리카 세트(Replica set)로 구성되어 있으며, 레플리카 세트는 동일한 데이터를 저장하는 몇 개의 연결된 MongoDB 인스턴스의 모음입니다.

  • 인스턴스는 특정 소프트웨어를 실행하는 로컬 또는 클라우드의 단일 머신입니다. 이 경우에서 인스턴스는 클라우드에서 실행되는 MongoDB 데이터베이스입니다.

 

 

도큐먼트나 컬렉션을 변경할 경우, 변경된 데이터의 중복 사본이 레플리카 세트에 저장됩니다.

 

이 설정 덕분에 레플리카 세트의 인스턴스 중 하나에 문제가 발생하더라도 데이터는 그대로 유지되며, 레플리카 세트의 애플리케이션에서 나머지 작업을 할 수 있습니다. 이 과정을 위해 클러스터(서버 그룹)를 배포하면, 자동으로 레플리카 세트가 구성됩니다.

 

  • 레플리카 세트
    동일한 데이터를 저장하는 소수의 연결된 머신을 뜻합니다. 레플리카 세트 중 하나에 문제가 발생하더라도, 데이터를 그대로 유지할 수 있습니다.
  • 인스턴스
    로컬 또는 클라우드에서 특정 소프트웨어를 실행하는 단일 머신, MongoDB에서는 데이터베이스입니다.
  • 클러스터
    데이터를 저장하는 서버 그룹으로 여러 대의 컴퓨터를 네트워크를 통해 연결하여 하나의 단일 컴퓨터처럼 동작하도록 제작한 컴퓨터를 뜻합니다.
  • 도큐먼트(Document)
    필드 - 값 쌍으로 저장된 데이터
  • 필드(Field)
    데이터 포인트를 위한 고유한 식별자
  • 값(Value)
    주어진 식별자와 연결된 데이터
  • 컬렉션(Collection)
    MongoDB의 도큐먼트로 구성된 저장소입니다.
    일반적으로 도큐먼트 간의 공통 필드가 있습니다.
    데이터베이스 당 많은 컬렉션이 있고, 컬렉션 당 많은 도큐먼트가 있을 수 있습니다.
  • 커서(cursor)
    커서는 find 메서드를 실행하면 반환되는 MongoDB document 입니다.

BSON 

 

JSON 형식은 읽기 쉽고, 많은 개발자들이 사용하기 편리한 형태를 가지고 있어 데이터를 저장하는 좋은 방법 중 하나입니다.

 

JSON은 텍스트 형식이기 때문에 읽기 쉽지만, 파싱이 느리고 메모리 사용이 비효율적입니다. 그리고 JSON은 기본 데이터 타입만을 지원하기 때문에, 사용할 수 있는 데이터 타입에 제약이 있습니다.

 

이런 문제점을 해결하기 위한 방안으로 BSON(Binary JSON) 형식을 도입하였습니다.

 

BSON은 컴퓨터의 언어에 가까운 이진법에 기반을 둔 표현법입니다. 따라서 JSON 보다 메모리 사용이 효율적이며 빠르고, 가볍고, 유연합니다. 뿐만 아니라, BSON의 사용으로 더 많은 데이터 타입을 사용할 수 있습니다.

 

MongoDB는 JSON 형식으로 작성된 것은 무엇이든 데이터베이스에 추가할 수 있고, 쉽게 조회할 수 있습니다.
그러나 그 내부에서는 속도, 효율성, 유연성의 장점이 있는 BSON으로 데이터를 저장, 사용하고 있습니다.


MongoDB CRUD

 

collection 에 document, documents 삽입하기

db.collection.insert(
   <document or array of documents>,
   {
     writeConcern: <document>,
     ordered: <boolean>
   }
)

ordered는 중복되는 값이 있을 경우에 처리하는 방법에 대해서 정할 수 있는 option 입니다.

default 값으로는 true인데, 이는 _id 값이 같은 경우 duplicate key 에러가 발생하는 데 이러한 에러가 발생할 경우, insert 될 예정이었던, 즉 남아있는 documents는 처리하지 않고, insert 가 끝이 납니다.

반면에, false는, 에러가 발생하더라도 남아있는 documents에 대해서 insert를 처리를 하게 됩니다.

 

collection에서 document 찾기

db.collection_name.find({쿼리문}, projection)

쿼리문이 입력이 되면, 해당 조건에 맞는 결과를 찾아줍니다. 

리턴은 해당 조건에 맞는 결과물들을 랜덤하게 20개를 먼저 보여줍니다. 추가적인 20개를 보기 위해서는 iterate의 줄임말인 it 명령어를 사용합니다.

 

두번째 인자로 projection이 들어갈 경우, { <field_name> : 1 or 0 }

1을 사용할 경우, 해당 필드와 _id 필드만 가져옵니다.

0을 사용할 경우, 지정한 필드를 제외한 모든 필드를 가져옵니다.

 

출력물은 보기 편하게 하기 위해서는 뒤에 .pretty()를 입력하면 됩니다.

 

쿼리문에 해당하는 데이터의 갯수를 알기 원하면 find().count() 를 입력하면 됩니다.

 

특정한 1개의 데이터만을 조회하기 위해서는 아래와 같은 명령어를 사용하면 됩니다.

db.collection_name.findOne({쿼리문})

document update하기

db.collection_name.upadateOne({쿼리문},{업데이트 내용})
쿼리문에 충족하는 하나의 데이터를 업데이트 시킨다.
db.collection_name.updateMany({쿼리문},{업데이트 내용})
쿼리문에 충족하는 모든 데이터를 업데이트 시킨다.

MQL(mongodb query language)에 대해서도 알아보자
$inc:{필드:증가 원하는 크기} => 필드의 값을 원하는 크기만큼 증가시킨다.

$set:{필드:업데이트할 값} => 해당 필드의 값을 업데이트할 값으로 바꿔준다.
* 주어지는 필드가 없을 경우에는 새로운 필드로 만들어질 수 있다.

필드의 값이 배열일 경우에 요소를 추가할 수 있다.
$push:{배열 타입의 값을 갖고 있는 필드:{추가할 서브 document의 정보}}

 

collection과 document 삭제하기

db.collection_name.deleteOne({쿼리문})
쿼리문에 해당하는 첫번째 document를 삭제
db.collection_name.deleteMany({쿼리문})
쿼리문에 해당하는 모든 documents 삭제
db.collection_name.drop()
해당 collection이 삭제된다.

MongoDB 연산자 종류

 

비교연산자

{ <field> : { <operator> : <value> } }
$eq => equal to value *연산자가 지정되어 있지 않을 경우 기본 연산자로 사용
$ne => not equal to value
$gt => greater than value
$lt => less than value
$gte => greater than or equal to value
$lte => less than or equal to value

논리연산자

{ <operator> : [{statement1},{statement2}, ...]}
$and => 주어진 모든 쿼리절과 일치하는 지
* 연산자가 지정되지 않았을 때, 기본 연산자로 사용
  동일한 연산자를 두 번 이상 포함해야 할 때 명시해줘야 한다.
  
$or => 주어진 쿼리절 하나라도 일치하는 지
$nor => 주어진 모든 쿼리절과 일치하지 않는 지

{ $not: {statement} } => 주어진 쿼리와 일치하지 않는 지

표현연산자

{ $expr : { <expression> } }
$expr을 사용해서 쿼리 내에서 집계 표현식을 사용할 수 있습니다.
$expr을 이용해서 변수와 조건문을 사용할 수 있습니다.
$expr을 사용해서 같은 document 내의 필드들을 서로 비교할 수 있습니다.

$필드를 이용해서 필드의 값을 참조할 수 있습니다.
ex) {"$expr" : {
				"$and" : [
                		{ "$gt" : ["$field_name1", 비교 대상] },
                        { "$eq" : ["$field_name2", "$field_name3"]}
                        ]
                 }
       }
       
해당 collection 에서 field_name1은 비교 대상보다 크고,
field_name2의 갑과 field_name3의 값이 동일한 데이터들을 찾을 수 있습니다.

배열연산자

{ <array field> : { "$size" : <number> } }
지정된 배열 필드가 주어진 길이와 정확히 일치하는 모든 document들이 있는 커서를 반환합니다.
{ <array field> : { "$all" : <array> } }
지정된 배열 필드의 배열 순서와 관계없이 지정된 모든 요소가 포함된 모든 document들이 있는 커서를 반환합니다.

배열 필드를 배열 연산자를 쓰지 않고 쿼리할 경우
{ <array field> : <array> }
지정된 배열 필드에 요소와 순서가 정확히 일치하는 배열을 가진 document를 찾습니다.
{ <array field> : <string> }
지정된 배열 필드에 문자열로 주어진 요소가 포함된 모든 document를 찾습니다.

{ <array field> : { $elementMatch : {쿼리문} } }
첫번째 인자에서 쓰일 경우, 배열 필드의 서비 document 필드가 쿼리와 일치하는 문서를 찾습니다.
두번째 인자에서 쓰일 경우, 지정된 기준과 일치하는 요소 하나 이상있는 배열 요소만 프로젝션 합니다.

Aggregation Framework

 

MongoDB에서 데이터를 파이프라인에 따라 처리할 수 있는 강력한 프레임워크

MongoDB에서 데이터를 쿼리하는 가장 간단한 방법 중 하나이며, MQL에서 사용하는 모든 쿼리는 Aggregation 프레임워크에서도 사용할 수 있습니다.

예제
Amenities 중 하나로 WIFI가 포함되어 있는 document를 찾아, price, address 필드만 projection하기

db.list.find(
	{"amenities" : "WIFI"},
    {"price":1,"address":1,"_id":0}}.pretty()
    

db.list.aggregate([
	{ $match: {"amenities":"WIFI"} },
    { $project : {"price":1,"address":1,"_id":0} }
    ]).pretty()

aggregate를 사용하면,

도큐먼트를 필터링하지 않고 그룹으로 데이터를 집계하거나 데이터를 수정할 수 있습니다.

데이터 찾기 및 프로젝션 없이 작업을 수행하거나 계산할 수 있습니다.

aggregate를 사용할 땐 대괄호를 이용해 배열을 인자로 사용합니다.

 

각각의 처리 작업은 우리가 나열한 배열의 순서에 의해 결정합니다. 그리고 마지막으로 변환된 데이터가 파이프라인의 끝에 나타납니다.

Comments