복제
    가장 큰 목적 : 서비스 중인 mongodb 인스턴스에 문제가 생겼을 때, 정보가 복제된 다른 인스턴스를 이용해서 문제가 생긴 인스턴스를 대체하는 것이다.
    
    하지만, 복제된 인스턴스는 변경된 정보를 동기화하는데 시간이 걸린다.

    복제를 수행 하기 위해서는 여러 mongod 인스턴스가 모인 "묶음"이 필요하다. 이 묶음에 속하게 되면 서로의 정보를 동기화한다. 이런 묶음을 복제세트라고하고 이에는 3가지로 구성된다.
    1. 클라이언트와 읽기 및 쓰기 작업을 하는 '프라이머리 구성원' 
    2. 프라이머리 구성원의 정보를 동기화하는 '세컨더리 구성원'
    3. 정보를 저장하지는 않고 복제 세트의 복구를 돕는 '아비터 구성원' 이렇게 세 가지 종류의 구성원이 복제 세트를 구성한다.

    서로 살아 있는지 10초마다 핑을 보내는 작업을 수행한다. 이를 하트비트라고한다.

    복제 세트 구성원의 기능

                프라이머리                  세컨더리                아비터

    주요역할    클라이언트와 정보교환       프라이머리 정보 복제        투표진행
    정보저장            O                           O                   X
    선거에서역할    문제가 발생              피선거권,선거권 보유      선거권 보유


    새컨더리가 프라이머리가 되고 프라미어리보다 최신의 정보는 지운다. 그래서 롤백이 되는데 소실되어서는 안되는 민감한 정보를 저장해야 하는 경우에는 WriteConcern을 보수적으로 설정해야한다.

    mongod
        --replSet <복제 세트 이름>
        --port <포트번호>
        --bind_ip <연결할아이피>
        --dbpath <정보를 저장할 경로>
        --oplogSize <오피로그 크기>
    
    mkdir -p /db/data/rs-0 /db/data/rs-1 /db/data/rs-2
    
    포트 27018, 27019, 27020

    mongo --port 27018 실행 
    
    //rs()이라는 이름의 복제 세트로 묶는다
    rs.initiate({
        _id:"rs0",
        members:[
            {_id:0,host:"localhost:27018"},
            {_id:0,host:"localhost:27019"},
            {_id:0,host:"localhost:27020"},
        ]

    })

    rs.status() // 상세 정보
    프라이머리에서 데이터 넣고, 세컨더리에서 조회
    rs.slaveOk() //세컨더리는 기본값으로 셸을 사용하지 못하게 되어있다. 사용가능하게 하기

    
    배포 환경에 복제 세트 구성
        mongod.conf 파일을 수정해서 인스턴스의 기본 값이 외부와 연결이 가능하도록 설정해야 한다.
        /etc/mongod.conf 결로에 설정파일 존재

        net:
            bindIp: 127.0.0.1
            port: 27017

    복제 세트 세부 설정
        rs.conf()
        rs.reconfig(<복제옵션>) // 세컨더리의 우선순위 변경 가능

    새로운 구성원을 복제 세트에 추가하려고 할때 add 명령어를 이용하면 추가 할 수 있다.
    rs.add({
    {_id:,
    host:,
    설정값들..
    }
    })
    
    rs.remove("128.20.1.33") //복제 세트에서 제외 하고자 할 떄에는 해당 구성원의 인스턴스를 종류하고, remove 명령어를 주어서 구성원을 제외하면 된다.


    읽기 선호 설정이 가능하다 
        기본은 프라이머리에서 읽어오지만, 읽기 작업을 분산시킬수 있다. ( 드라이버에서 관리, 식제 개발할 떄에는 드라이버의 설정에 맞추어 개발해야 한다.)
    
    1.프라이머리 구성원에서 읽는 상태
    2.프라이머리 구성원에서 우선적으로 읽는 상태(읽기 작업이 밀리게 되면 세컨더리로부터 정보를 불러오기 때문에 변경사항 반영이 좀 늦을수 있다.)
    3.세컨더리 구성원에서 읽는 상태
    4.세컨더리 구성원에서 우선적으로 읽는 상태
    5.가까운 구성원에서 읽는 상태 ( 정보 변경 반영이 클라이언트마다 제각각이 될 우려가 있다.)


동기화 작동 방식
    오피로그 
        Mongodb 인스턴스 내에서 변경된 정보 내역을 저장한 특별한 Capped 컬렉션이다.
        db.oplog.rs.find().limit(1).pretty() // 조회
        
        //사진
    초기 동기화
        새로운 세컨더리를 추가했을 때 오피로그의 정보만으로 동기화를 수행할 수 없다면 '초기 동기화'를 수행하게 된다.

    ReadConcern 
        그냥 프라이머리에 기록된 정보를 가져올지, 아니면 대다수의 복제 세트에 기록된 정보를 가져올지 정할 수있다.
        local 단계 majority(여러 개의 도큐먼트를 작업하는 트랜잭션에 이 옵션을 사용하려면) 단계 linearizable 단계 (시간옵션을 설정 해주어야함)

        cursor.readConcern("linearizable").maxTimeMS(10000)
        //쿼리의 결과로 나온 커서 뒤에 readConcern이라는 명령어를 이용하면 읽어오는 정보에 관련 옵션을 붙일수있다.

        db.rating.aggregate(
            [{$match:{rating:{$lt:5}},
            readConcern:{level:"majority"}          
            }]
        )

        session =db.getMongo().startSession()
        session.startTransaction({readConcern:{lecel:"snapshot"}},
        WriteConcern:{w:"majority"}});
        session.commitTransaction();
        session.endSession();

    WriteConcern
        복제 세트에 장애가 발생했을 때 중요한 정보가 사라질지, 유지될지 결정할 수 있다.
        w : 복제 세트의 어느정도의 구성원에 쓰기 작업이 완료되어야 전체 쓰기 작업이 완료되었다고 판단할지 결정하는 옵션
        j : 쓰기 작업은 메모리에 변경 사항을 남겼다가 일정 주기로 디스크에 변경 사항을 기록 디스크 변경사항을 임시로 저장하는 작업을 저널링이라고 한다. 저널링은 장애가 발생해도 기록이 남기 때문에 복구가 가능하다.
        wtimeout : w 옵션에서 설정한 구성원들을 기다릴 수 있는 최대시간 , 시간이 지나면 에러 반환 이미 실행한 쓰기 작업 자체는 취소되지 않는다.

        db.rating.aggregate(
            [ {$match:{rating:{$lt:5}}}],{
                writeConcern:{w:"majority",j:t}
            }
        )

        ession =db.getMongo().startSession()
        session.startTransaction({readConcern:{lecel:"snapshot"}},
        WriteConcern:{w:"majority"}});
        session.commitTransaction();
        session.endSession();

    오피로그의 크기
        세컨더리가 동기화를 끝내지 못했는데 오피로그의 크기가 작아 동기화할 데이터가 지워진다면 가지고 있는 데이터를 모두 지우고 초기 동기화 과정을 가지게 된다.
        다행히 4.0 버전 이상부터는 대다수의 세컨더리에서 오피로그를 동기화하지 못했으면 프라이머리의 오피로그를 삭제하지 않는 기술이 탑재 되었다

        - 세컨더리를 잠깐 복제 세트에서 제외 했다가 다시 포함시키는 상황
        - 물리적으로 백업된 정보를 인스턴스에 덮어씌우고 세컨더리를 복제 세트에 추가하는 상황

        오피로그 크기 수정하기
        use local
        db.oplog.rs.stats().maxSize

        db.adminCommand({replSetResizeOplog:ture,size:16834})

    동기화 상태 확인
    rs.printSlaveReplicationInfo()

1. 스키마가 자주 바뀌는 환경
ex) 
1.애자일 개발 방법론이나 나선형 모델의 시제품을 만들 때와 같이 최종적인 개발 명세가 확정되지 않는 상황
2.로깅을 하기 위한 용도로 사용할 수도 있다. 다양한 형식의 로그를 기록할 때에도 매우 유용하다.
2. 분산 컴퓨팅 환경
샤딩과 복제를 DBMS 수준에서 지원해서 여러 관련 기능들을 보다 효과적으로 적용할 수 있다.
-복제는 '고가용성' 환경이 필요할때 고려해볼만하다. (항상 사용할 수 있는 상태)
-샤딩은 저장용량이 늘어나면서 읽기,쓰기 속도가 필요한 만큼 나오지 않을 때 적용할 수 있다.

* 리눅스에서 mongodb설치시 권한 설정 (sudo chmod -R go+w /data/db


도큐먼트는 BSON 구조는 값으로 매우 다양한 데이터 타입을 가진다.

db.dropDatabase() 
db.collection.drop()
db.collection.renameCollection(바꿀이름)

Capped 컬렉션
정해진 크기를 초과하게 되면, 자동으로 가장 오래된 데이터를 삭제하게된다
db.createCollection(<컬렉션이름>,{capped:true, size:<제한할 크기>)
capped 컬렉션은 로그 데이터나, 일정 시간 동안만 보관하는 통계 데이터를 보관하고 싶을 때 유용하게 사용 할 수 있다.


집계 파이프라인 문법 사용


WriteConcern
mongodb는 기본적으로 메모리메 미리 저장한 후, 천천히장기 저장장치로 데이터를 옮긴다. 따라서 데이터베이스에 갑저가 문제(정전 등)이 생기면 메모리에만 담겨 있어
데이터가 손실 된다.


db.user.insertMany({다큐먼트1},{다큐먼트2} ....)
여러 다큐먼트를 한번에 넣기

 

find 
    - 쿼리 
        db.containerBox.find({name:"가위"})
    - 점연산자 
        - {name:{firstName:"Karoid",lastName:"Jeong"}}
          db.A.find({"name.firstName"::Karoid"})
        - {numbers: [101,32,21,11]}
          db.A.find({"numbers.0":52)
    - 프로젝션
        - 어떤 도큐먼트를 불러올지를 결정하는 파라미터
        db.containerBox.find(null,{name:true})
        db.containerBox.find(null,{name:1})

        null => {name:'가위'}  조건 찾기
    - cursor 커서 
        모든 정보를 직접 반환하지 않고 커서를 반환한다.
        toArray()를 메소드를 사용하면 커서로부터 정보를 전부 가져올 수 있다.
            - 만약 find문의 모든 값이 다 필요하지 않다면 toArray 메소드는 비효율적이다.
            - 또, 필요한 도큐먼트의 총 크기가 매우 크다면 toArray 메소드를 사용 할 시 메모리 용량을 초과 할수도있다.

수정
    replaceOne (대체)
    기존의 값 유지 되지않고, 교체되어 버린다.
    - db.user.replaceOne({username:"karoid"},{
        username:"Karpoid",
        status:"Sleep",
        points:100,
        password:2222
    },(upsert:true))

        - upsert 
            참/거짓 값을 설정할 수 있다, 참값으로 설정된 경우 쿼리로 찾은 도큐먼트가 없다면, 찾은 내용으로 도큐먼트를 생성해서 수정하게 된다. 
    
    updateOne, updateMany (수정)
        -db.containerBox.updateMany({name:"bear"},{$set:{name:"teddy bear",category:"toy"}})
            ($unset 도큐먼트상의 field 필드를 제거한다.)

수정 배열 연산자
    db.character.insertMany([
        {name:'x',inventory:['pen','cloth','pen']},
        {name:'y',inventory:['book','cloth'],position:{x:1,y:5}},
        {name:'z',inventory:['wood','pen'],position:{x:0,y:9}}
    ])

    db.character.updateMany({},{
        $set:{"inventory.$[penElem]":"pencil"},
        {arrayFilters:[{penElem:'pen'}]}
    })

    $set:{"inventory.$[]":"pencil"} -> 배열에 pen 요소가 두개 있어도 모두 pencil로 바뀌게 된다.

삭제는 수정과 매우 유사하다

트랜잭션 명령어
    session = db.getMongo()startSesstion()
    session().startTransaction({readConcern :{level:"snapshot"},
    writeConcern:{w:"majority"}});
    
    <원하는 작업을 수행>
    session.commitTransaction();
    session.endSession();    
    <문제가 생길 경우 중간에 작업을 취소>
    session.abortTransaction();


$text 연산자
    MongoDB의 $text 연산자는 해당 컬렉션의 텍스트 인덱스 안에서만 작동하기 때문에, 문자열 인덱스를 생성해야 한다.
    
    db.stores.insertMany([
        {_id:1, name :"java Hut",description:"Coffee and cakes"},
        {_id:2, name :"asdf",description:"Coffee and cakes"},
        {_id:3, name :"xcxc",description:"Coffee and cakes"},
        {_id:4, name :"hhh",description:"Coffee and cakes"},
        {_id:5, name :"jjj",description:"Coffee and cakes"},
        {_id:6, name :"jkkt",description:"Coffee and cakes"},
        {_id:7, name :"ccc",description:"Coffee and cakes"},
    ])

    db.stores.createIndex({name:"text",description:"text"}) - 문자열 인덱스를 생성
    db.stores.find({$text:{$search:"bake coffee cake"}})

    - 대소문자를 구분하지 않고 검색을 한다.

    db.stores.find({$text:{$search:"\"coffee shop\""}})
    여러 단어를 띄어쓰기와 함께 사용하게 되면 해당 단어들 중 일부만 포함된 도큐먼트라도 불러오게 된다.

    db.stores.find({$text :{$search:"shopped"}})
    shop이라는 단어의 진행형인 shopping이 포함된 도큐먼트를 불러온다

배열 연산자
    db.inventory.find({tags:{$elemMatch:{$gt:10,%lt:5}}})
    하나라도 두 조건을 동시에 만족하는 값을 가져옴

    db.inventory.find({tags:{$all:["red","blank"]}})
    순서와 상관없이 모든 요소를 가지는 값을 가져옴

    db.inventory.find({"tags":{size:3},....})
    배열의 해당 크기와 같은 도큐먼트를 찾는다

프로젝션 연산자
    프로젝션에서는 점연산자가 첫번째 배열을 가르키지 않는다.
    
    $slice등을 사용
        db.inventory.find({},tags:{$slice:1}})
        tags 필드의 첫 번째 요소만 출력 된다.

        마지막 요소로부터 n개를 출력하고 싶다면 연산자의 값으로 -n을 설정하면 된다.
        {$slice:[1,2]}

    elemMatch
        배열 속에서 특정 조건을 만족하는 요소만 노출

    $연산자 
        tags 필드의 값 중 "red"를 갖는 도큐먼트를 찾는경우
        db.inventory.find({tags:"red"},{"tags.$":true})



집계 명령어
    집계 방법론과 효율성
         도큐먼트를 집계하는 방법은 크게 세 가지가 있다.
         1. 데이터베이스의 모든 정보를 불러와 애플리케이션 단게에서 집계하는 방법
         2. MongoDB의 맵-리듀스 기능을 이용하는 방법
         3. MongoDB의 집계 파이프라인 기능을 이용하는 방법 

         집계 파이프라인은 MongoDB 내부에서 작동, 다른 방법들은 정보교환을 위해 메모리가 필요하게 된다.
         집계 파이프라인 방식으로 원하는 결과를 얻지 못할 수도 있다.
         그럴 경우, 맵-리듀스 방식을 사용하면 된다.

        - 맵-리듀스의 작동 방식 1
        
        Rating
        {_id:1,rating:1,user_id:2}        -->       {1:2}           -->     {_id:1,value:2}
        {_id:1,rating:1,user_id:2}        map       {2:3}           -->     {_id:2,value:3}
                                                    {3:[4,1]}       -->     {_id:3,value:2}
                                                    {4:[5,8]}       -->     {_id:4,value:2}
        
        db.rating.mapReduce(mapper, reducer, {out:{inline:1}})

        - 맵-리듀스의 작동 방식 2
        
        Rating                                                     reduce           중간 결과물
        {_id:1,rating:1,user_id:2}        -->       {1:2}           -->       ----- {_id:1,value:2}
        {_id:1,rating:1,user_id:2}        map       {2:3}           -->       |     {_id:2,value:3}
                                                                              | 
                                          -->       {3:[4,1]}       -->       V     {_id:3,value:2}
                                                    {4:[5,8]}       -->     --->    {_id:4,value:2}  최종결과물
                                                                           reduce 

        - 파이프라인 방식
        $project (어떤 필드를 만들고 어떤 필드를 숨길지 설정)
            db.rating.aggregate([
                {
                    $project:{_id:0,rating:1,hello:"new field"}
                }
            ])
            {"rating":1,"hello":"new field"}    

            db.rating.aggregate([
                {
                    $project:{_id:0,multiply:{
                        $multiply:[$_id","user_id"]
                    }}
                }
            ])
            {"multiply":2} 

        $group(그룹화이다.)
            db.rating.aggregate([
                {
                    $group:{_id:"$rating",count:{$sum:1}}
                }
            ])

        $match(도큐먼트를 필터링해서 반환한다. find문과 비슷한 역할이다.)
            db.rating.aggregate([
                {
                    $match:{rating:{$gte:4}}
                },
                {
                    $group:{_id:"$rating",user_ids:{$push:"$user_id"}}
                }
            ])

            {"_id" :5 ,"user_ids:[11,12,10,9]}

            평점이 4 이상인 사용자의 id들을 배열의 형태로 정리

             db.rating.aggregate([
                {
                    $match:{rating:{$gte:4}}
                },
                {
                    $group:{_id:"$rating",user_ids:{$push:"$user_id"}}
                },
                {
                $unwind:"$user_ids"
                }
            ])

            {"_id" :5 ,"user_ids:9}
            {"_id" :5 ,"user_ids:10}
            {"_id" :5 ,"user_ids:11}

        $out(도큐먼트를 저장)
            
             db.rating.aggregate([
                {
                    $match:{rating:{$gte:4}}
                },
                {
                    $group:{_id:"$rating",user_ids:{$push:"$user_id"}}
                },
                {
                $unwind:"$user_ids"
                },
                {
                $out:"user_ids_by_rating"    
                }
            ])

        $limit(입력 도큐먼트 중 지정된 숫자만큼만 출력)
        $skip(입력 도큐먼트 중 지정된 숫자만큼 건너뛰고 출력)
        $sort(도큐먼스틑 정렬 1이면 오름차순,-1이면 내림차순)
        
뷰 생성하고 삭제하기
    db.createView(<뷰 이름>,<출처 컬렉션>,<파이프라인 스테이지 배열>,)
    프로젝트 연산자 전부 사용하지 못하고, 사용할 수있는 명령어가 제한적이다.

데이터 모델링과 인덱싱
    - 레퍼런스 방식
        정보의 양이 늘어날수록 크기가 작은 도큐먼트의 개수가 늘어나게 된다.
        도큐먼트의 크기는 작고, 정보를 추가하려면 도큐먼트를 추가한다.
        연결된 정보를 모두 불러오는데 더 오래 걸린다.
        연결된 정보를 일부만 불러올 때 더 빠르다.

    - 임베디드 방식
        도큐먼트의 크기가 무한히 커질 수도 있다.
        도큐먼트의 크기는 크고, 정보를 추가하려면 도큐먼트를 수정한다.
        임베디드 되어 있는 정보를 더 빨리 불러 올 수 있다.
        도큐먼트 전체를 읽어야 해서 느리다.
        장점 
            기본적인 도큐먼트 생성 및 삭제 작업은 하나의 도큐먼트에 대해 원자성이 지켜진다. (하지만 최근 mongodb가 트랜잭션을 지원하면서 크게 장점이 되지 않는다. 다수의 도큐먼트도 원자성을 보존할 수 있기 때문)
            거의 대부분의 글을 노출시켜야 한다면 레퍼런스 방식이 좋은 성능
            하나의 서버에서는 읽는 다면 속도차이가 별로 나지 않지만 여러대의 서버로 샤딩 되어 있다면 속도 차이가 날 수 밖에 없다.


    결론                주로 읽기 작업 위주                         주로 쓰기 작업 위주
    도큐먼트가 크다     임베디드 된 정보를 대부분 불러올때 유리         불리하다.
    도큐먼트가 작다     임베디드 된 정보를 자주 불러와야 하면 유리      조금 불리하다.


다중키 인덱스

    배열 값에 대한 검색 상황을 대비에 '배열에 대한 인덱스'를 지원
텍스트 인덱스

    $text 연산자를 사용하기 위해서 생성 되어야 하는 인덱스가 바로 텍스트 인덱스, 영어의 경우 단어의 원형으로 인덱스를 생성 한국어는 형태소별 지원이 되지 않음
해시 인덱스
    핵시인덱스를 포함한 복합인덱스 생성불가, 배열을 값으로 가지는 필드에 설정 불가
    주로 해시샤딩으로 사용하는 것이 일반적

인덱스 명령어
    단일키 인덱스
        db.movie.createIndex(
        {평점:1 },)
    복학키 인덱스
        db.movie.createIndex(
            {평점:1,점수:-1},
        )
    다중키 인덱스 (임베디드 도큐먼트)
        db.movie.createIndex(
            {"리뷰.제목":1},
        )
    텍스트 인덱스가
        db.movie.createIndex(
            {제목:"text"},
        )
    해시 인덱스
        db.movie.createIndex(
            {배급시 :"hashed"},
        )

    {name:"배급사 해시 인덱스"} -> 인덱스 이름 설정가능

    TTL 인덱스
        일정 시간이 지나면 자동으로 도큐먼트를 삭제하도록 만들어줌
    
    고유 인덱스 (같은 값이 저장되는 것을 방지할 수있게 도와준다.)
        {unique: true}

    인덱스 조회 및 삭제 명령
    db.collection.getIndexes() 컬렉션의 모든 인덱스를 보여준다
    db.collection.dropIndex(인덱스 설정 혹은 이름) 인덱스 삭제
    db.collection.dropIndexs() 모든 인덱스 삭제


    find.explain() 어떤 과정을 거쳐서 쿼리가 진행되었는지 확인 가능하다.
    만약 인덱스가 사용되었다면 IXSCAN 스테이지가 포홤된 쿼리 계획이 나타나야 한다.

    (*파이프라인에서는 db.rating.explain().aggregate)

    모든 쿼리를 explain으로 확인 할수 없으니 프로파일러를 통하여 확인가능
    db.setProfilingLevel(2)
    db.setProfilingLevel(1,50) 50ms 이상인 작업은 모두 기록하게 된다.

    현재 실행되고 있는 잠금필드
    db.currentOp()
    

혁신이란 늘 한 발 먼저 움직이고 빠르게 행동 할 때 가능하다.

기술 ( 구현 가능성) 사업(시장성) 인간적 가치(사용성,적합성)

혁신을 원한다면 이 세가지의 균형이 중요하다.

만드는 역량보다 더 중요한 것은 만들대상이다. 사람들이 필요로 하는 부분을 만들 때 지속가능성 역시 생길 수 있다.

1. 사무실에서 알 수 있는 것은 없으니 현장으로 나가라.
2. 고객 개발에 애자일 개발을 접목하라.
3. 실패는 탐색 절차의 필수적인 요소이다.
4. 끊임없이 반복하고 전환하라.
5. 고객과 만나는 순간 어떤 사업 계획도 무의미하므로 비즈니스 모델 캔버스를 활용하라.
6. 가설을 검증하고자 실험과 테스트를 설계하라.
7. 시장 유형에 맞춰라, 시장 유형에 따라 모든 게 바뀐다.
8. 스타트업은 기존 기업과 다른 지표를 쓴다.
9. 빠른 의사 결정, 순환 주기, 속도, 박자를 중시한다.
10. 열정이 가장 중요하다.
11. 스타트업의 직책은 대기업의 직책과 다르다.
12. 필요할 때만 쓰고 아껴라.
13. 배운 것을 소통하고 공유하라.
14. 성공적인 고객 개발은 합의에서 시작한다.

+ Recent posts