[데이터베이스] NoSQL

Ethan KIM·2022년 8월 15일
0

Achievement Goals

목록 보기
4/7

MONGO DB

NoSQL 의 장점 및 특징에 대해 설명하시오

NoSQL은 넓은 범위에서 사용하는 용어로, SQL로 표현하지 않고, 테이블을 사용하는 방법을 체택하지 않음. 그래서 행과 열의 방식으로 데이터를 저장하는것이 아닌, 다른 체계적인 방식으로 저장함. MongoDB는 NoSQL 의 한 종류인 Document DB로 분류됨.

이전에 SQL 에서 이미 둘의 차이점을 언급하며 NoSQL의 장점 및 특징을 설명했지만, 다시한번 짚고 넘어가고자함.
1. 비 구조적 대용량 데이터 저장시 NoSQL을 사용.
구체적인 이유는, 구조가 없으니 SQL 보다 훨씬 자유롭게 데이터를 저장할 수 있다. 필요에 따라 새로운 데이터 유형을 추가할 수도 있음.

2. 클라우드 컴퓨팅 및 저장공간을 최대한 활용하는 경우
NoSQL 데이터베이스는 기본적으로 클라우드 기반으로 이루어져 데이터베이스를 쉽게 분리 할 수 있도록 지원함. => 저장 공간을 효율적으로 사용 가능. 수평적 확장의 형태로 증설하기 때문에 무한대로 서버를 분산시켜 DB를 증설할 수 있다.

3. 빠르게 서비스를 구축하고 데이터 구조를 자주 업데이트 하는 경우
스키마가 필요없음. 그렇다고 아예 없는건 아니지만, SQL 은 스키마가 없으면 DB의 효율이 안나오는데, NoSQL은 스키마가 없어도 원하는 효율이 나옴. 데이터 구조를 자주 업데이트 하는 경우에도 스키마를 일일히 수정해야하는데, NoSQL 은 그럴 필요 없음.


MongoDB의 도큐먼트와 컬렉션을 설명하시오

아래 스샷이 도큐먼트임.

도큐먼트는 객체와 같이 데이터를 필드-값 쌍으로 저장하고 구성한다.
필드 : 데이터의 고유한 식별자
값 : 주어진 식별자와 관련된 데이터.
컬렉션 : 도큐먼트로 구성된 저장소. 일반적으로 도큐먼트 간의 공통 필드가 있음.

데이터 베이스당 많은 컬렉션이 존재하고, 컬렉션당 많은 도큐먼트가 존재한다고 보면됨

  • JSON 과 BSON의 차이점을 설명하고, 도큐먼트를 가져오거나 내보내기를 설명하라.
  • 차이점

    JSON은 텍스트 형식 -> 읽기 쉬움. 범용성 최고. 근데 파싱이 느리고 메모리 사용이 비효율적. 데이터타입도 기본 데이터 타입만 지원함.

    BSON은 Binary JSON임. 컴퓨터가 읽기 편하게 만든것. 메모리 사용이 효율적, 빠름, 가벼움, 유연함. 근데? MongoDB에서만 사용 가능함. 즉, 범용성이 좀 낮음.

    그래서 뭐 어떻게 사용하냐?
    두개 합쳐서 씀.
    데이터 저장할땐, BSON으로 저장. 출력할떈, JSON형태로 출력.
    단순 백업하려면 BSON. 데이터 export후 조회나 출력해야되면, JSON

    이런식으로 쓰면됨.

    그럼 이제 Document Import 랑 export에 대해 알아보자.

    무조건 클러스터 입장임. 클러스터에서 Local 로가는건, export. Local에서 클러스터로 가는건 Import

    Import

    mongoimport : JSON, mongorestore : BSON

    mongoimport --uri "Atlas Cluster URI"
    			--drop=FILE_NAME.json
    이때, 데이터의 형식은 JSON 뿐 아니라 csv와 같은 데이터 형식일 수도 있음.
    
    mongorestore --uri "Atlas Cluster URI"
    			 --drop dump
    
    ** drop : 기존에 있는 데이를 삭제하기 위한 옵션. 선택적으로 사용 가능.

    Export

    mongoexport : JSON, mongodump : BSON

    mongoexport --uri "Atlas Cluster URI"
    			--collection=COLLECTION_NAME
    			--out=FILE_NAME.json
    mongoexport를 하는 경우엔, 해당 데이터 베이스의 컬렉션 이름, 파일 이름까지 정확하게 작성해줘야함.
                
    mongodump -- uri "Atlas Cluster URI"
    
    공통적으로 사용되는 Atals Cluster URI는 일반 웹의 URI와 형식이 같음.
    근데, username, password, cluster주소로 되어있음.
    "mongodb + srv://USER_NAME:USER_PASSWORD@USER_CLUSTER.mongodb.net/DATABASE_NAME"

    MongoDB의 Atlas에 대해 설명하시오

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


  • 클러스터나 레플리카 세트에 대해 설명하시오.
  • 클러스터 배포 : 클러스터 배포란, MongoDB Atlas Cluster을 사용해서 하는것인데, 이게 public cloud(Microsoft, Google, AWS)에 DB 서비스를 제공하는것임.

    그럼 클러스터가 뭔데?

    FROM Mongo DB official

    In the context of MongoDB, “cluster” is the word usually used for either a replica set or a sharded cluster. A replica set is the replication of a group of MongoDB servers that hold copies of the same data; this is a fundamental property for production deployments as it ensures high availability and redundancy, which are crucial features to have in place in case of failovers and planned maintenance periods.
    서버의 오류나, 정비 기간에 서버를 닫아야할 수도 있는데, MongoDB로 클러스터를 하면 레플리카라는 것이 자동으로 생김. 이건 Primary 데이터를 완전 복제한거라, Primary 데이터에 데이터를 추가 변경할 때마다 자동으로 레플리카도 복제됨.

    일단 아래 그림은, Cluster을 만들었을때 구조임.
    그럼 여기서 뭘 얻을수 있냐?

    바로 이걸 얻을 수 있음. Primary가 고장나거나, 서버에 이상이 생기면 곧바로 새로운 Primary를 지정해서 그 Primary에서 작업하게됨. 그래서 결국 서버가 3개있다고 보면됨. 중요한건 Primary가 고장이 났을때만, Secondary가 필요해짐.
    뭐 Secondary에만 데이터를 추가할 수 있다고 나오긴 하는데 그건 나중에 알아볼까함.
    TO THE TOP

  • Atlas를 GUI와 shell 쿼리문 둘다 사용해봐라
  • MongoDB에서 CRUD 해보시오

  • Insert Find Update Delete에 대한 쿼리문 작성
  • 모든 MongoDB 도큐먼트는 모든 도큐먼트가 _id 필드를 기본값으로 가지고 있어야 한다.

    도큐먼트 내부의 필드 값이 같다고 하더라도, _id 값이 다르면 서로 다른 도큐먼트임.
    반면 도큐먼트 내에 필드 값이 다르다 하더라도, _id 값이 같으면 서로 같은 도큐먼트임. => error

    그래서, 도큐먼트를 추가할 때 _id 필드의 값을 특정하지 않았다면, 자동적으로 _id가 생성되고 값에 ObjectId 타입이 할당된다.

    Insert

    DATABASE_NAME.COLLECTION_NAME.insert(DOCUMENT) 방식으로 Insert 가능.
    insert 할때, 다수의 도큐먼트를 삽입 하려면 배열 안에 넣어줘서 한번에 DOCUMENT에 넣어준다.

    Insert 명령어를 사용하면, 주어진 도큐먼트 배열의 인덱스 순서로 작업이 실행된다.
    그래서, 배열의 순서로 작업하는 과정에, 중복이 발생하면 그 이후로 insert하지 않고, 중복발생 전까지만 insert된다.
    그러나, ordered를 추가하면 순서에 상관 없이 고유한 _id 를 가진 도큐먼트는 모두 컬렉션에 추가됨.

    insert(DOCUMENT,{"ordered":false}) 이런식으로 작성. 이게 왜 되냐면, 삽입하는 작업에 순서가 없기 때문임. 원래는 위에처럼 인덱스 순서대로 하면 중단되는데, ordered 옵션을 추가하므로서 삽입되는 순서를 바꾸는 개념임.

    Find

    db.COLLECTION_NAME.find(<쿼리문>) 방식으로 현재 사용하고 있는 db의 collection에 <쿼리문> 조건에 해당하는 데이터 찾을 수 있음.
    ex) db.collection.find({"state": "NY"})
    쿼리문을 좀 더 자세하게 파고들 수 있음. {state: NY, post: 29182}이면, NY state의 우편번호 29182인 데이터를 찾을 수 있다.

    pretty()를 붙혀주면, 데이터가 자동으로 sort 된다.
    count()를 붙혀주면, collection안에 있는 전체 데이터의 갯수를 알려준다.
    db.collection.findOne()을 사용하면, 랜덤으로 collection안에 있는 하나의 데이터만 추출 할 수 있고, 여기에 쿼리문을 데이터의 고유 id 값으로 작성하게되면 고유 id 값을 가진 데이터 하나만 추출 할 수 있다.

    Update

    업데이트를 하는 이유는, 데이터베이스에 있는 데이터의 정보가 바뀌거나, 데이터 베이스에 있는 데이터 정보가 오래되어, 최신화를 시켜주어야 하기 때문에 업데이트를 해야한다.

    그렇다면, 데이터가 어떻게 구성되어 있고, 그 데이터의 정보를 알고, 해당 데이터만 바꾸어 주어야 하기 때문에 find가 꼭 선행 되어야 한다.

    db.collection.updateOne OR updateMany(
    {document},{연산자 : { <fireld값> : <change value 1>,<fireld2값>: <change value 2> } } )
    : 주어진 기준에 맞는 첫번째 도큐먼트 OR 다수의 도큐먼트 필드 데이터를 업데이트
    연산자의 종류는 $inc, $set, $push $unset 등이 있는데, $set으로 inc를 대체 할 수 있고, remove 할때에는 $unset으로 함. $push 는 배열에서 사용됨. 일반적으로 grade를 나타내는 도큐먼트에 사용할 수 있음.

    예시)

    db.collectoin.find({"state":"NY"}) // state가 NY 인 도큐먼트를 찾은후 리스트를 본다.
    //필드 state 가 NY인 도큐먼트 중에 첫번째 도큐먼트를 선택하여, 연산자 $inc로 <field>값인 "pop"을 10만큼 증가시킨다.
    db.collection.updateOne({"state":"NY"}, {"$inc" : {"pop":10}})
    .
    //필드 state가 NY인 도큐먼트들을, 연산자 $inc로 <field>값 "pop"을 10만큼 증가시키고 city<field>를 seoul로 업데이트 해준다.
    db.collection.updateMany({"state":"NY"}, {"$inc" : {"pop":"10"}, {"city":"seoul"}})
    .
    //데이터 베이스에 있는 도큐먼트들 중, 필드값 score가 배열로 구성되어 있을때,
    {"score": [{"type":"homework", "score": 92}, {"type": "exam","score": 30}, ....]}
    .
    // collection중에 student_id가 250이고 class_id가 250인 도큐먼트를 찾고, 거기에 배열로 작성된
    // score 값에 extraCredit score 10 을 push 해준다.
    db.collection.updateOne(
      {"student_id": 250, "class_id": 250},{"$push":
      {"score": {"type":"extraCredit", "score":"10"}}})
      //

    Delete

    작동방식은 위의 update와 동일. 종류는
    deleteOne() : 쿼리문과 일치하는 첫번째 도큐먼트 삭제.
    deleteMany() : 쿼리문과 일치하는 모든 도큐먼트의 필드 삭제
    drop : collection delete


  • 연산자와 프로젝션을 사용해봐
  • 비교연산자

    "$eq" : equal to
    "$ne" : not equal to
    "$gt" : greater than
    "$gte" : greatere than or equal
    "$lt" : less than
    "$lte" : less than or equal
    "$in" : find values that in array
    "$nin" : find values that not in array
    <field값> : {"operator":"value","operator" : "value", "operator2" : "value", ...}
    여러개의 operator 을 사용할 수 있음. 최종 목표는 데이터를 거르고 걸러서 원하는 데이터 값을 찾을 수 있게됨.
    그럼 결국엔, 최소한의 쿼리문 operator을 이용해 데이터를 필터링 하는것이 중요하게 되는것 같다.
    ex)

    {"tripduration" : {"$lt" : "70"}} // tripduration에서 value값이 연산자 70 미만인 도큐먼트.
    db.collection.find({"tripduration" : {"lt" : "80"}}) // 이렇게 사용 가능.
    .
    // 연산자가 없을 경우엔, "$eq"가 기본연산자.
    db.collection.find({"tripduration" : "70"}) === {"tripduration" : {"$eq" : "70"}}

    논리연산자

    "$not" : {"not" : {statement}}
    "$and"
    "$or"
    "$nor"
    "and, or , nor"은 모두 동일한 방식으로 사용된다."oprator" : [{"statement1"}, {"statement2"}, ....]

    예시) airbnb mongoDB 를 사용한다고 치자. 웹 브라우저에서 여행이 가능한 여행지를 찾는 쿼리를 서버에 보냈을때, 서버에선 데이터베이스에서 여행이 가능한 여행지를 불러오는 쿼리를 작성해서 다시 웹 브라우저에 전달해주어야 하는 상황이다. 이럴경우 데이터베이스에서 필터링을 거쳐서 서버에 전달해 주어야하는데, 이때 필요한 것이 논리 연산자 이다. 물론 비교 연산자로 할 수 있지만, 논리 연산자로 할 경우, 좀 더 편리하게 필터링 할 수 있다.

    // 도큐먼트 필드중에 여행이 가능한 여부를 판단하기 위한 필드를, result 라고 가정해서 진행해보면,
    db.document.find({"$nor" :
    [{"result":"Violation Issued"}, {"result":"Not allowed"}, {"result": "Temporary close"}}])
    -
    // 위의 코드는 필드 result 의 value 가 Violation Issued, Not allowed, Temporary close
    // 인 도큐먼트를 제외한 document를 찾는다. nor 은 and or not 이 포함된 값임. 사실.
    -
    //또한 "$and" 연산자도 비교연사자 중 "$eq"와 동일하게 기본값으로 세팅 되어있다.
    //따라서 논리연산자 값을 작성해 주지 않는다면 "$and"연산자를 이용한 결과값이 출력 된다.
    예시)
    -
    db.collection.find({"$and": 
      [{"total_employee": {"$gt": 25}},{"total_employee": {"lt": 70}}]})
    // 이 결과값이 의미하는것은 25 < total_employee < 70 인 document를 찾는 과정이다.
    -
    // 이건 사실 아래의 코드와 동일하다.
    {"total_employee" : {"$gt": 25, "$lt": 70}} 간편하게 사용 가능.
    -
    하지만, 동일한 연산자를 2번 이상 사용할 경우엔 "$and 를 붙혀줘야한다."
    db.collection.find({"$and": [
    {"$or": [{"dst_airport": "ICN","src_airport": "ICN"}]},
    {"$or": [{"dst_airport": "BKK", "src_airport": "ICN"}]}
    ] // and statement
    })

    표현연산자

    aggregation framework

    "$expr"
    "$expr"을 이용해 변수와 조건문을 사용 가능.
    "$expr"을 사용하여 같은 도큐먼트 내 필드들을 서로 비교할 수 있음.
    다양성을 가지고 있음.
    표현력이 풍부하여 하나 이상의 작업을 수행할 수 있다.
    {"$expr": {expression}} 구문을 사용.
    필드값에 $를 붙혀주면 그 필드의 value 값에 접근 할 수 있음. 그래서 아래의 예시에도 $start station id 의 값과 $end station id 의 값에 접근하는 것.
    ex)

    //$expr 을 사용하면 해당 값이 어떤 필드와 같아야 하는지 지정하지 않고도 자체적으로
    //동일한 도큐먼트 내에서 필드값을 비교 할 수있다.
    //start station과 end station이 동일한 도큐먼트를 찾는 표현식.
    db.collection.find({"$expr" :{"$eq" :{"$start station id", "$end station id"}}})

    위의 표현연산자 $expr을 사용하는 방식이 집계 표현식이라는 방식인데 이는 MQL 방식과 살짝 다름.
    원래의 MQL syntax는 필드 명을 먼저 입력하고 비교 연산자를 작성하는 방식임
    {"FIELD_NAME" : {"OPERATOR": "VALUE"}}

    하지만 표현 연산자에선,
    {"OPERATOR" : {"FIELD_NAME" : "VALUE"}}를 사용하게됨.

    배열연산자

    배열의 값을 쿼리할 때, 배열이 아닌 다른 유형의 필드에 대해 쿼리하는 방법을 사용하면, 불편함. {"array field" : "array"}{"array field" : "string"}으로 나누어 볼 수 있는데, {"array field" : "array"} 이건 배열의 순서가 굉장히 중요,
    {"array field" : "string"} 이건, 문자열로 주어진 요소가 포함된 모든 도큐먼트를 찾음.

    예를 들어, 에어비앤비 데이터베이스에 호텔Info 도큐먼트의 필드값 amenities가
    배열로 만들어져 있고, 이 amenities에 Shampoo의 유무에 따라 숙소를 고르고 싶을때, 해당
    amenities는 배열인데, 다른 유형의 필드에 접근하는 방식대로 쿼리하면, 
    amenities가 Shampoo값이 포함된 모든 숙소를 나타내게됨. ===> 차별화가 효과가 없음.

    그러면, 쿼리값에 배열을 통째로 넣어줘도 되는데 이는 필드 순서도 굉장히 중요함. 필드 순서가 바뀌면 필터링을 못함.


    "$push" : push를 사용하면, 배열의 마지막 위치에 엘리먼트를 넣거나, 배열이 아닌 필드에 사용했을 경우, 필드의 타입을 배열로 바꾼다.
    도큐먼트가 배열이 아닌 문자열, 숫자 및 boolean으로 만 구성되어 있을때, push 를 사용해서,
    "$all" : 지정된 배열 필드의 배열 순서와 관계 없이 지정된 모든 요소가 포함된 모든 도큐먼트들이 있는 커서를 반환한다.
    "$size" : 지정된 배열 필드가 주어진 길이와 정확히 일치하는 모든 도큐먼트들이 있는 커서를 반환.

    $size 와 $all 로 배열로 이루어진 필드를 쿼리하면
    db.collection.find({"amenities" : {"$size" : 20, "$all" : 
      ["Wifi", "Internet", "Kitchen", ...}})
    //이런식으로 amenities의 size가 딱 20 인것만 추리고 === 갯수가 20개인것,
    //$all 로 순서에 관계 없이 지정된 모든 요소가 포함된 도큐먼트들의 커서를 반환.

    Projection

    원하는 필드만 가져올 수 있는 방법.
    db.COLLECTION_NAME.find({"QUERY"}, {"field1": 1, "field2": 1})
    쿼리 조건에 맞는 필드 1과 필드 2만 collection에서 뽑아옴.
    1 : 지정한 필드 포함
    0 : 지정한 필드 제외.
    이 1과 0을 혼용할 수는 없지만, 디폴트로 포함하는 _id필드를 제외하도록 특별히 요청할 경우, 혼용 가능.

    배열 안에 있는 도큐먼트를 쿼리하는 방법.

    "$elemMatch"(projection) : elemMatch 가 projection에 사용될때

    //class_id가 431이고 scores 필드가 존재하고, 조건에 맞는 경우에만 필드를 결과에 포함.
    db.collection.find({"class_id": 431},{"scores": {"$elemMatch"}:
    {"score": {"$gt": 85}}})				<projection>으로 사용.
    이럴경우 일부 도큐먼트의 경우 class_id가 431을 가졌기 때문에 결과엔 포함되었지만,
    score가 85점이라는 조건에 부합되지 않았기 때문에 scores필드를 결과에 포함 시키지 않음. _id 디폴트값만 나옴.

    "$elemMatch"(query) : 쿼리문에 사용될 때.

    //쿼리로 사용한 elemMatch. scores 배열에 타입이 extra credit인 학생을 찾는 쿼리문.
    {"field" : {"$elemMatch": {"field" :"value"}}}
    db.collection.find({"scores": {"$elemMatch": {"type": "extra credit"}}})
  • 배열과 서브 도큐먼트를 쿼리해보시오
  • MongoDB에서는 유연한 데이터 모델링을 통해 개발자가 데이터를 저장할 방법을 결정할 수 있어서, 데이터 저장 형식이 다양함. 배열이 될 수도 있고 그냥 문자열 이 될수도, 숫자가 될수도 있는데, 서브 도큐먼트가 될 수도있음.
    이경우 .Notation방법으로 서브 도큐먼트에 접근 가능.

    배열의 중첩 도큐먼트에 접근하려면,

    //각 도큐먼트에서 relationships 배열의 첫번째 요소이며 이름이 Mark인 CEO찾기.
    db.companies.find({"relationships.0.person.first_name": "Mark",
      "relationships.0.title": {"$regex" : "CEO"}},
      {"name" : 1})
    .
    //각 도큐먼트에서 relationships배열에서 회사를 떠난 사람중 이름이 Mark인 사람찾기.
    회사 현재 떠난사람 + 이름이 Mark인 사람. $elemMatch로 묶어 준다.
    db.companies.find({"relationships": {"$elemMatch": {
      										"is_past": true,
      										"person.first_name": "Mark"}}},
    									  {"name" : 1})

    Aggregation Framework를 사용하여 aggregate명령어로 쿼리 하시오.

    Aggregation Framework 는 MongoDB 데이터를 쿼리하는 가장 간단한 방법중 하나임.
    MQL을 이용한 모든 쿼리는 Aggregation Framework 에서도 할 수 있다.
    다만 aggregate를 사용할땐, 대괄호를 이용해 배열을 인자로 사용해야함.

    PipeLine

    Aggregation Framework 에서는 파이프라인의 단계에 따라 데이터를 처리할 수 있다. 위에서 aggregate를 사용할땐, 대괄호를 사용해야한다고 했는데, 그 배열의 순서가 바로 pipeline의 순서이다. 배열의 순서대로 작업이 처리됨.

  • $match, $project, $group 연산자를 사용해보시오.
  • $group

    group

    "match"라는 필터가 있어서, 그룹화는 match 필터를 완료하고 나온 필터링된 데이터를 그룹화함. 필터가 없으면 원본 데이터를 그룹화함.
    {$group : "_id": "$field", "new_field" : {"accumulator" : "value"}}
    그룹화 해주면, 새로운 필드를 하나 생성해서 그안에 aggregation을 통한 필터된 값을 넣어 줄 수 있음.

    스테이지 별로 어떤값을 필터링해줄지를 판단하는게 가장 중요. 여기서 스테이지란 그룹스테이지, 프로젝트 스테이지, 매치스테이지 등 다양함.

    예시

    {
      $group:
        {
          _id: <expression>, // Group key
          <field1>: { <accumulator1> : <expression1> },
          ...
        }
     }
    db.collection.aggregate([ // aggregate를 써야함.
        {
         $group : {
            _id : $<field.name>,
            count : {$count : { }}
          }
        }
    ])
    profile
    좋아하는것만 함

    0개의 댓글