검색의 기본, 여러 데이터 중에 특정 대상만 불러오는것

기본이자 가장 어려운 파트라고도 할 수 있다.

 

특히, 보기좋은 코드가 개발자를 기분좋게하는데 검색쿼리를 짜다보면 자칫 코드가 길어질수있다.

여러가지 조건들이 붙기 때문이다.

 

그와중이 addFields를 남발하여  길어져버린 나의 코드가 눈에띄었다.

어디에 주로 addFields를 썼나 보니,

$lookup을 통해 다른 collection과 join한뒤 특정 document만 불러오고자 할때 리스트를 벗겨내는 과정에서 남용했다.

 

{
  $lookup: {
    from: 'users',
    localField: 'userId',
    foreginField: '_id',
    as: 'user'
  }
}

// 결과 => [{ _id: ObjectId('...'), name: '...', ... }]
// 원하는 데이터는 => { _id: ObjectId('...'), name: '...', ... }
// 따라서, 사용했던 코드

{
  $lookup: {
    from: 'users',
    localField: 'userId',
    foreginField: '_id',
    as: 'user'
  }
},
{
  $addFields: {
    user: {
      $arrayElemAt: ['$user', 0]
    }
  }
}

// 결과 => { _id: ObjectId('...'), name: '...', ... }

 

원하는 document만 불러오기 위해 코드가 무려 7줄이나 길어지는 문제가 발생했다.

$addFields는 이런식으로 쓰라고 있는 기능도 아닐것으로 생각이 든다.

$addFields는 저장된 데이터는 아니지만 클라이언트에 필요한 데이터, 예를들어 sum, count 등의 데이터들을 추가하기 위해 존재할것이다.

 

그렇다면, 무엇이 적절할까. 정답은 이미 나와있다.

 

{
  $lookup: {
    from: 'users',
    localField: 'userId',
    foreignField: '_id',
    as: 'user'
  }
},
{
  $project: {
    ...,
    user: { $arrayElemAt: ['$user', 0] }
  }
}

 

7줄의 코드가 아닌 단 한줄의 코드면 충분하다.

 

728x90
반응형

mongo를 쓰다보면,

list내에서 특정 값 혹은 document를 제거해야되는 경우가 굉장히많다.

 

이때는, 원본 자료를 불러와 해당 값을 제거한뒤 업데이트 시키는 방법 (덮어씌우기)을 주로 이용했다.

 

그런데, 자료를 불러올 필요가 없는 상황에서 해당 값만 바로 제거할수는 없을까? 하는 생각이 들었다.

찾아보니, 이때는  $pull 키워드로 해당 작업이 가능하다.

늘상 써오던 $set과 크게 다르지 않다

 

간단한 예제와 함께 포스팅을 마무리하기겠다.

// collection 이름이 Orders인 경우

Orders = [ 
  {
    _id: ...,
    menus: ['coffee', 'tea'],
  },
  {
    _id: ...,
    menus: ['bread', 'tea'],
  },
  {
    _id: ObjectId('abc'),
    menus: ['coffee', 'fruit'],
  },
];

// 특정 대상에 대한 처리 - ObjectId('abc')의 fruit을 제거할때
db.Orders.findOneAndUpdate({ _id: ObjectId('abc') }, { $pull: { menus: { $in: ['fruit']} }  })

// 전체 대상에서 fruit을 제거
db.Orders.updateMany({}, { $pull: { menus: { $in: ['fruit'] } } })


menu가 document형태로 들어가있을 경우?
Orders = [ 
  {
    _id: ...,
    menus: [
      {
        _id: ...,
        name: 'coffee',
        price: 100,
      },
      {
        _id: ...,
        name: 'tea',
        price: 200,
      }
    ],
  },
  {
    _id: ...,
    menus: [
      {
        _id: ...,
        name: 'bread',
        price: 150,
      },
      {
        _id: ...,
        name: 'tea',
        price: 200,
      }
    ],
  },
  {
    _id: ObjectId('abc'),
    menus: [
      {
        _id: ...,
        name: 'coffee',
        price: 100,
      },
      {
        _id: ...,
        name: 'fruit',
        price: 500,
      }
    ],
  },
];

// 특정 대상에 대한 처리 - ObjectId('abc')의 fruit을 제거할때
db.Orders.findOneAndUpdate({ _id: ObjectId('abc') }, { $pull: { menus: { name: 'fruit' } }  })

// 전체 대상에서 fruit을 제거
db.Orders.updateMany({}, { $pull: { menus: { name: 'fruit' } } })

 

 

[참고자료]

docs.mongodb.com/manual/reference/operator/update/pull/

728x90
반응형

Rest API서버와 DB서버가 각기 다른 서버에서 운영되는건 매우 흔한일이며

지극히 정상적인 일이라고 볼수있다.

 

서비스의 규모가 절대로 커질일이 없는것이 아니라면 일단은 분리하는게 좋다는 생각이다.

그럼에도 개발속도와 편의성 측면에서 한 서버에서 API서버와 DB를 운영하고는 하는데 (제가 그랬습니다...)

 

두 서버를 분리시키고 데이터 마이그레이션하는 방법에대해 소개하도록 하겠습니다.

 

 

1.  DB서버 생성 및 몽고 환경 셋업

이 부분에서 사실 제일많은 시간비용이 소모됩니다.

왜냐면 1~10까지 구구절절 설명해주는 자료가 많이 부족하기 때문이죠.

 

아래와 같은 작업을 해야합니다.

1) EC2환경설정

2) 몽고디비 설치

3) 몽고디비 실행

4) 몽고디비 관리자 계정 설정 및 readwrite계정 추가

5) .conf파일 수정

6) 몽고디비 재실행

7) DB서버에 연결 테스트

8) 데이터 덤프 및 마이그레이션

 

1) EC2환경설정

여기서 할것은 보안그룹설정인데요

몽고의 기본포트인 27017포트를 개방해줍니다.

 

 

2) 몽고디비 설치

각 OS마다 설치방법은 다르지만, 설치와 관련해서는 Mongo에서 매우 친절하게 안내해주고있으니 참고하시기 바랍니다

docs.mongodb.com/manual/installation/

 

 

3) 몽고디비 실행

실행, 정지, 재실행, 상태확인에 대해서 간단히 명령어를 알아보도록 하겠습니다.

두가지 버전이 있는데요, systemctl / service 두개의 키워드를 통해 가능합니다.

 

(실행) stystemctl start mongod

(중지) systemctl stop mongod

(재실행) systemctl restart mongod

(상태확인) systemctl status mongod

 

일단 설치했으면 실행해줍니다.

systemctl status mongod 를 입력해 정상적으로 동작중이며, mongo를 입력했을때 몽고디비로 진입된다면

잘 설치 및 실행되었다고 볼 수 있습니다

 

 

4) 몽고디비 관리자 계정 설정 및 readwrite계정추가

여기서 많이 헤매게됩니다.

기본적으로 로컬에 있는 몽고에서는 해주지않아도 되는데 (단, 하지않았을때 보안의 위험이있습니다)

리모트 서버의 DB에 연결하려면 반드시 계정설정을 해주어야됩니다.

외부에서 연결을 요청하면 몽고에서 사용자 권한을 확인하기 때문입니다.

 

먼저, 관리자 계정을 생성해주어야합니다.

관리자 계정이란 정말로 슈퍼어드민을 의미하며 DB를 드랍,추가 하는것까지 가능하니 계정관리에 주의해야합니다.

 

몽고에는 기본적으로 admin이라는 db가 생성되어있는데요. 다음과 같은 절차를 밟도록 하겠습니다.

가) admin db 사용: use admin

나) 어드민 계정 생성: db.createUser({ user: 'admin', pwd: '패스워드', roles: [{ role: 'root', db: 'admin' }] })

다) 생성된 계정 확인: db.getUser('admin')

라) 일반 readwrite계정 생성: db.createUser({ user: 'manager', pwd: '패스워드', roles: [{ role: 'readWrite', db: 'myFirstDb' }, { role: 'readWrite', db: 'mySecondDb' } ] })

마)  생성된 계정 확인: db.getUser('manager')

 

 

5) .conf 파일 설정

거의 다 왔습니다. 리모트에서 연결해주기 위해서는 보안설정을 셋업한 상태로 몽고를 실행해주어야만 합니다.

방법은 두가진데요. 제가 확실히 말씀드릴수 있는건 .conf 파일 설정이니 이것을 따르시길 추천드립니다.

 

mongod.conf 파일은 기본적으로 /etc/mongod.conf 경로에 생성됩니다.

몽고를 설치했지만 해당위치에 mongod.conf파일이 보이지 않는다면 찾아보시기 바랍니다.

 

mongod.conf 파일에서 아래의 파트를 정확하게, 아주 똑같이 작성합니다.

 

net:
  port: 27017
  bindIp: 0.0.0.0

 

security:
  authorization: enabled

 

(mongod --auth 라는 방법도 있다는데 제가 확신할수없어 아주 개인적으로 권장하지 않습니다.)

 

여기서 0.0.0.0은 모든 IP에서의 요청을 받겠다는 의미인데, 보안계정을 생성해주었으니 믿고 가도록 합니다.

API서버 주소인 13.x.x.x,192.x.x.x 등을 입력할수도 있다고 하지만 에러가 나는 경험을 했습니다.

관련하여 아시는 분이 있다면 댓글 부탁드립니다.

 

 

6) 몽고디비 재실행

systemctl restart mongo

 

재실행 했다면 반드시 상태도 확인해주세요

systemctl status mongo

 

여기까지 잘 되셨나요?

 

 

7) DB서버에 연결 테스트

자, 대망의 연결 테스트입니다.

로컬 서버에서 테스트하셔도 전혀 무방합니다.

 

연결 주소는 아래와 같이 작성합니다.

mongod://아이디:패스워드@도메인혹은아이피:27017/디비명

 

예제입니다.

mongod://manager:openpassword@cup.camil.com:27017/coffee

 

API서버에서 직접 연결을 체크해봐도 좋고

MongoBB Compass같은 툴을 통해 연결을 시도해 봐도 괜찮은 방법입니다.

후자의 경우 다른 코드에서 발생하는 에러와 분리하여 테스트할 수 있다는 장점이 있습니다.

 

 

8) 데이터 덤프 및 마이그레이션

여기는, 기존의 DB가 있는경우에만 진행해주시면 됩니다.

 

기존의 API서버에서 먼저 데이터를 덤프하는데요, 덤프란 기존의 데이터를 백업하기위해 카피한다고 생각하시면 됩니다.

BSON 타입으로 하는방법과 JSON 타입으로 하는방법 2가지가 있는데요.

여기서는 속도가 더 빠르다고하는 BSON 타입을 채택하여 진행하도록 하겠습니다.

 

아래의 명령어를 사용하면 내 현재 위치(경로)에 dump라는 폴더 내에 데이터가 덤프가 됩니다.

mongodump --host 127.0.0.1 --out /data/db/backup

 

덤프된 데이터를 FileZila와 같은 프로그램을 이용해 새 DB서버로 이전해줍니다.

이 때, 덤프된 파일들을 어디로 이전하는지 정확히 위치를 체크하도록 합니다.

 

* /home/root/  정도의 경로에 backups폴더를 생성하여 이전하는것을 추천드립니다.

 

데이터를 옮겼다면, 콘솔에서 해당 경로로 이동합니다.

위와 같이 했다면

cd /home/root/backups

가 되겠죠?

 

마지막입니다. 데이터 마이그레이션을 진행해주면 되는데요, 지금 디비가 텅텅 비어있으니 간단히 처리할 수 있습니다.

단, 진행 전 잠시 mongod.conf 파일의 security파트를 주석처리 하도록 합니다.

 

#security:
  #authorization: enabled

 

systemctl restart mongo

systemctl status mongo

 

몽고의 상태가 정상이라면 마이그레이션을 진행합니다.

mongorestore --host 127.0.0.1 --port 27017 --db 복구하려는 db명 덤프된 디렉터리 위치

 

예제입니다.

mongorestore --host 127.0.0.1 --port 27017 --db coffee ./coffee

 

*********** 수정***********

덤프될 디렉터리 위치를 잡을 필요없이 간단히 아래와 같이 복원가능합니다.

mongorestore -d db db

************************************

 

정상적으로 마이그레이션 됐는지를 먼저 체크해볼까요?

mongo

show databases

>> coffee DB가 존재하나요?

 

use coffee

show collections

>> collection들은 잘 이전되었나요?

 

잘 되었다면 다시 security설정을 해줍니다.

 

security:
  authorization: enabled

 

systemctl restart mongo

systemctl status mongo

 

 

============================

 

고생하셨습니다. 여기까지입니다.

이 글이 몽고 원격 연결 및 마이그레이션의 성지가 되기를 바라며

부족한 글 많은 분들의 댓글로 꽉꽉 채워지기를 바라겠습니다.

 

728x90
반응형

몽고DB에서 어떤 Array 필드에 아무런 값도 존재하지 않는 경우에는 필터링하는 방법에 대해 알아보겠습니다.

 

일반적으로 필드에 값이 존재하는지 판단할때는 $exists를 사용합니다.

...,
{
  $match: {
    name: { $exists: true }
  }
},
...

이와 같이 아주 간단히 처리할수 있습니다.

 

그치만,  필드 타입이  Array인 경우 또 다른 조치를 취해주어야합니다.

빈 값이라도 []가 들어가있다면 true로 인식하기 때문입니다.

 

따라서, 다음과 같이 처리할수 있습니다.

// 첫번째 방법
...,
{
  $match: {
    foods: {
      $exists: true,
      $ne: []
    }
  },
},
...

// 두번째 방법
...,
{
  $match: {
    foods: {
      $exists: true,
      $not: { $size: 0 }
    }
  },
},
...

두 방법다 유효하며, 저는 두번째 방법을 즐겨씁니다.

두 방법의 연산속도의 차이나 이런 부분에 대해서는 아직 잘 모르지만,

일반적으로 단순값을 통한 비교(여기서는 [] )보다는 아래와 같은 방식이 더 정확하다고 생각하기 때문입니다.

728x90
반응형

몽고 빽업에 대해 알아보려고 한다.

 

가장 확실한 방법은 docs를 확인하는 것이다.

https://docs.mongodb.com/manual/reference/program/mongorestore/

 

mongorestore — MongoDB Manual

Insert Only mongorestore can create a new database or add data to an existing database. However, mongorestore performs inserts only and does not perform updates. That is, if restoring documents to an existing database and collection and existing documents

docs.mongodb.com

 

다만, 우리가 왜 블로그를 찾아보겠는가.

내게 맞는게 어떤것인지 쉽게 알아보기 위함이 아닌가

 

알아보니 mongo의 백업에는 5가지 정도의 백업/복원 방법이 존재한다.

그 중에서 서비스 운영중에도 사용할수 있는 2가지의 방법을 소개하고자한다.

 

1. mongodump / mongorestore

> 해당 방법으로 컬렉션, DB, 전체 등 옵션을 통해 원하는 파트를 백업 가능하다.

> 백업의 데이터 형태는 BSON이다. 빠른 백업과 복구가 가능하다.

> 백업 - mongodump / 복원 - mongorestore

> 사용이 매우 간편하다.

dump_ ex) mongodump --host 127.0.0.1 --out /data/db/backup

restore_ ex) mongorestore --host 127.0.0.1 --port 27017 --db dbname backup_db_path

 

2. mongoexport / mongoimport

> 위의 방법과의 차이점은 추출된 자료의 형태가 csv, json등 친숙한 자료형이다.

> BSON보다 속도는 느리며, 텍스트 형태로 저장이 된다.

> 데이터를 복원할때 외부의 툴을 이용할수도 있다고한다.

> 백업 - mongoexport / 복원 - mongoimport

 

 

mongo가 위치하는 서버 외에 다른서버에 백업할 필요가 있는데,

단순히 IP만 지정하면 되는지, 접근성과 관련하여 보안적인 조치가 필요한지 등에 대해 더 알아볼 예정이다.

728x90
반응형

mongo에서 join을 위해 aggregate하는 경우

원하는 값만 얻기위해 $project를 쓰고는 한다.

 

한걸음 나아가 $project를 응용하여 사용하는법을 알아보고자 한다.

 

이번에 다소 특수한 경우를 처리해야하는 상황이 발생했다.

예를들어, aggregate한 데이터들 중

...
$project: {
  "오늘의 요리사": {
    "반찬 담당자": [...], // 10명
    "찌개 담당자": [...], // 10명
    "밥 담당자": [...], // 3명
    ...
  }
}
...

위와 같은 상황이다. (이는 단지 예시이다)

나의 경우에는 각 데이터들의 개수가 1:1:1로 유지되어야하는데 밥 담당자의 크기가 다른 데이터들과 달라 null값에 대한 처리를 해줘야하는 경우였다.

 

검색을 해본 결과, 무려 $map이라는 기능이 있음을 발견했다. 친숙한 키워드가 나타나자 너무 행복했다.

...
$project: {
  "오늘의 요리사": {
    "반찬 담당자": "오늘의 요리사.반찬 담당자",
    "찌개 담당자": "오늘의 요리사.찌개 담당자,
    "밥 담당자": {
      $map: {
        $input: "오늘의 요리사",
        as: "e",
        in: {
          $cond: {
            if: { $eq: [ "$$e.밥 담당자", null ] },
            then: { "밥 담당자": "-" },
            else: { "밥 담당자": "$$e.밥 담당자" }
          }
        }
      }
    },
    ...
  }
}
...

위와 같이 조건식을 적용하여 해결할 수 있었다.

예시가 다소 이상하여 언제쓰는건지 헷갈릴수 있으나,

이런식의 array타입에 대한 조건식은 분명히 많이쓰일것으로 생각되어 정리해본다.

728x90
반응형

종종 쓰일것 같은 검색기능이 있어서 간략히 정리해본다.

 

mongo에는 엄청난 Text검색기능이 있다

mongoDB 메뉴얼의 예제를 체크해본다.

db.articles.insert(
   [
     { _id: 1, subject: "coffee", author: "xyz", views: 50 },
     { _id: 2, subject: "Coffee Shopping", author: "efg", views: 5 },
     { _id: 3, subject: "Baking a cake", author: "abc", views: 90  },
     { _id: 4, subject: "baking", author: "xyz", views: 100 },
     { _id: 5, subject: "Café Con Leche", author: "abc", views: 200 },
     { _id: 6, subject: "Сырники", author: "jkl", views: 80 },
     { _id: 7, subject: "coffee and cream", author: "efg", views: 10 },
     { _id: 8, subject: "Cafe con Leche", author: "xyz", views: 10 }
   ]
)

db.articles.find( { $text: { $search: "coffee" } } )

{ "_id" : 2, "subject" : "Coffee Shopping", "author" : "efg", "views" : 5 }
{ "_id" : 7, "subject" : "coffee and cream", "author" : "efg", "views" : 10 }
{ "_id" : 1, "subject" : "coffee", "author" : "xyz", "views" : 50 }

 

검색이 이렇게 편해도되는걸까?

728x90
반응형

NoSQL을 다루다보면 제목과 같은 일이 제법 빈번히 발생한다.

const ProductSchmea = new mongoose.Schema({
  ...
  comments: [
  	{
      userId: ...,
      comment: ...,
      createdAt: ...,
      updatedAt: ...,
    }
  ]
});

 

위와 같은 설계를 하는일이 제법 많기때문이다.

상품에 대한 댓글을 위처럼 관리하면 매우 쉽기 때문이다.

그런데 comments를 수정하려면 어떻게 할까...? 가 문제다.

 

먼저, comments를 추가하는것은 제법 간단하다.

...
try {
  const commentInfo = {
    userId: ...,
    comment: ...,
  };
  
  await Prodcut.findOneAndUpdate({ _id: id }, { $push: { comments: commentInfo } }).exec();
} catch(error) {
  ...
}
...

이런식으로 push해서 넣으면 끝..!

 

대망의 수정이다.

알고나면, ? 쉽네 뭐야 어떻게 된거지? 라고 할만큼 쉽다.

하지만, 모르면 도저히 그 방법을 찾을수가없다.

 

...

try {
  await Product.findOneAndUpdate(
    { _id: id, 'comments._id': commentId }, 
    { $set: { 'comments.$.comment': '수정해버림' } 
  }).exec();
  
} catch(error) {
  ...
}

...

포인트는 두가지이다.

1. comment._id를 알아야한다는 것.

2. comments.$.comment

여기서 .$은 javascript의 [n]을 대신해준다.

 

위의 정보는 은근히 찾기 어려워 이렇게 간단히 주석을 남긴다.

728x90
반응형

안녕하세요, react-naitve의 webview를 마스터해보는 시간을 갖겠습니다.

 

저는 유튜브 영상처리를 위해 webview를 써오고 있는데요.

webview는 생각보다 많은 처리가 가능하더라고요~

그래서 한번 정리해보려 합니다.

 

1. source

source는 유일한 필수 property입니다. 따라서, 반드시 지정해주어야 합니다.

source에는 uri 또는 html이 올 수 있습니다.

말 그대로 page url이나 html을 작성하면 되는대요.

// uri 예제
<WebView 
	source={{ uri: 'https://naver.com' }}
/>

// html예제
<WebView 
	source={{ html: "<p>여기는 다음 티스토리</p>" }}
/>

 

2. useWebkit

공식문서에서는 해당 속성의 설명을 다음과 같이 하고있습니다.

"If true, use WKWebView instead of UIWebView."

그런데, 최신의 ios버전(아마 12부터)에서는 UIWebView 를 지원하지 않기 때문에 필수적으로 useWebkit을 사용해야합니다.

꼭, true로 값을 지정해주시기 바랍니다.

 

3. injectedJavaScript

webview의 source에서 지정한 것이 로드된 후에, 해당 페이지 또는 html에 javascript를 넣어주는것인데요.

다양한 용도로 쓰일 수 있겠죠?

응용해보시기 바랍니다.

(javaScriptEnabled를 함께 true로 지정해주어야 합니다.)

 

4. allowsInlineMediaPlayback

html의 비디오 태그에는 playsinline라는 속성이 있습니다.

ios에서 영상이 자동으로 fullscreen이 되는것을 막아주는 속성인데요.

webview에서도 해당 속성을 true로 지정해주면 같은 효과를 낼 수 있습니다.

 

5. 그 외

mixedContentMode, domStorageEnabled, mediaPlaybackRequiresUserAction 과 같은 다양한 속성이 있습니다.

다방면으로 대응 가능한 유용한 WebView!!

잘 활용해보시기 바랍니다.

 

 

728x90
반응형

NoSQL에서 배열은 DB의 자료형으로써 매우 자주 쓰이는 편입니다.

 

어떻게 array에 특정 조건을 걸어서 값을 필터링하는지 간단히 살펴보겠습니다.

 

* 키워드는 elemMatch

const filtered_examples = await ExampleCollection.aggregate([
	{
		$lookup: {
			from: 'joinedCollection',
			localField: 'localField,
			foreignField: 'joinedCollectionField',
			as: 'retunName',
		},
	},
	{
		$match: {
			examples: { $elemMatch: { name: 'johndoe' } },
		},
	},
])

 

728x90
반응형