스키마 모델링

일반 RDBMS는 중복을 최소화하기 위해 정규화를 한다. 하지만 Mongodb는 물리적인 특성상 객체를 내장할 경우 디스크에서 같은 곳에 위치하기 때문에 더 효율적이다.

예를 들어 같은 컬렉션내에 있는 도큐먼트에 접근하기 위해서는 내장되어 있을 때 같은 위치에 접근하기 때문에 효율적이지만, 내장되어있는 도큐먼트가 다른 컬렉션을 참조하는 경우에는 추가적인 비용이 필요하다.

스키마 디자인 할 때 일반적인 규칙이 있는데 고민하게 만든 사항을 살펴보겠다.

  1. 한 객체에 포함관계로 모델링된 객체들은 내장

  2. 다대다 관계는 보통 참조

  3. 성능 이슈가 생기면 무조건 내장한다.

여기서 내장한다는게 정확히 무슨 말은 한다는 걸까?

아래와 같이 서브 도큐먼트를 내장한다는 걸까?

collection = {
  doc : new Schema {
    name: String
  }
}

아래와 같이 다른 컬렉션에 대한 도큐먼트의 id를 내장한다는 걸까?

collection2 = {
  doc2 : [
    {type: Schema.ObjectId}
  ]
}

그리고 일반 임베디드 도큐먼트라는 타입이 있는데 이걸 말하는걸가?

collection3 = {
  doc3 : {
    name: String
  }
}

각 타입에 대한 설명은 다음 링크를 참조한다.

mongoose와 유용한 schema 기능

모델링 타입은 2가지가 있다.

  1. Normalized Data Models

    • 장점 : 다대다 관계, 복잡한 계층을 표현할 수 있다.

    • 단점 : 컬렉션에 중복된 데이터가 있어서 전체 읽기 성능은 떨어질 수 있다.

  2. Embedded Data Models

    • 장점 : 1대1, 1대다 관계를 표현할 수 있다.

    • 단점 : 내장 도큐먼트의 크기는 한계가 있다. 따라서 도큐먼트의 크기가 생성 이후 계속 커진다면 사용을 지양해야 한다.

각 타입에 대한 자세한 설명은 다음 링크를 참조한다.

데이터 모델링 타입

스키마 디자인 팁

우선 객체간에 관계를 생각해보자.

객체간에 관계에서 생각해보는 입장을 취해보면 다음과 같이 나뉘어진다.

  1. One-to-Few

  2. One-to-Many

  3. One-to-Squillions

  4. 양방향 참조

그리고 읽기 대 쓰기 효율을 고려하여 비정규화를 시킬 수 있다.

  1. Many-to-One 관계 비정규화

  2. One-to-Many 관계 비정규화

  3. One-to-Squillions 관계 비정규화

하나씩 살표보자

One-to-Few

{
  name:"John",
  address: [
    { p1: 'village', p2: 'village', p3: 'village'}
    { p1: 'pocket', p2: 'pocket2', p3: 'pocket3'}
  ]
}

하나 당 적은 관계의 수는 내포하여 한 번의 쿼리에 모든 정보를 보낼 수 있다. 하지만 내포된 데이터만 독자적으로 불러올 수 없다.

One-to-Many

//comments
{
  _id: ObjectId('AAA'),
  name: "John"
}
 
//boards
{
  _id: ObjectId('BOA');
  comments: [
    ObjectId('AAA'),
    ObjectId('BBB')
  ]
}

각각의 문서를 독자적으로 다룰 수 있지만 여러번 호출해야 하는 단점이 있다. 이 경우 DB 레벨이 아닌 어플리케이션 레벨의 join으로 두 문서를 연결해야 한다.

  boards = db.boards.findOne({_id: ObjectId('BOA')})
 
  comments_array = db.comments.find({_id: {$id: boards.comments}}).toArray();

One-to-Squillions

//host
{
  _id: ObjectId('AAA'),
  ipAddr: '111.111.111.111'
}
 
//logs
{
  time: ISODate()
  host: ObjectId('AAA')
}

엄청나게 큰 많은 데이터가 필요한 경우 단일 문서의 크기가 한정되어 있기 때문에 부모 참조 방식을 활용해야 한다. 조인 방식은 다음과 같다.

host = db.hosts.findOne({_id: ObjectId('AAA')})
 
last_logs = db.logs.find({host: host._id}).sort({time:-1}).limit(5000);

양방향 참조

//comments
{
  parent: ObjectId('BOA')
  _id: ObjectId('AAA'),
  name: "John"
}
 
//boards
{
  _id: ObjectId('BOA');
  comments: [
    ObjectId('AAA'),
    ObjectId('BBB')
  ]
}

양방향 참조는 One-to-Many에서 서로 참조하는 경우이다. 각 컬렉션에서 다른 컬렉션에 쉽게 접근할 수 있지만 문서를 삭제하는데 있어 쿼리를 두 번 보내야 한다.

그 외에 비정규화의 관계를 사용하게 되면(중복을 허용) 쓰기 성능보다 읽기 성능이 높아진다.

다음 링크에서 관련된 자세한 내용을 확인할 수 있다.

스키마 디자인 6가지 전략

성능을 높이기 위해 Index를 사용할 때 주의할 점.

Mongodb의 물리적 데이터 저장 구조를 살펴 보면 아래와 같다.


먼저 살펴보자면, 데이터 저장 시 논리적 메모리 공간에 write하고 일정 주기에 따라서 disk로 flsuh를 하는 Write Back 방식을 사용한다.(이 때문에 메모리에만 적재하면 되는 특성으로 인해 Write 속도가 빠르다는 말이 나오는 것 같다.)

데이터의 Read시에도 파일의 index를 메모리에 로딩해놓고 그 후 검색을 한다. 이 점에서 메모리에 저장되는 내용을 실제 데이터 블럭과 index 자체가 저장된다. 이 때문에 index를 남용하면 안된다. index를 생성하거나 업데이트할 때는 자원이 들어가며 index가 메모리에 상주하고 있어야 제대로 된 성능을 낸다.

메모리에 공간이 부족하면 page fault가 일어나게 되고 메모리에서 disk로부터 해당 데이터 블록을 요청하여 page와 disk 사이에 스위칭이 일어나게 되고 disk i/o 가 발생하여 성능을 떨어뜨리게 된다.

Index 선택에도 다음과 같은 규칙이 있다.

  1. 찾고자 하는 키에 대한 필드는 인덱스 한다.

  2. 보통 정렬하는 필드에 인덱스 한다.

인덱스를 사용하게 되면 쓰기의 성능은 떨어지기에 쓰기보다 읽기 비율이 높은 컬렉션에 인덱스를 사용하면 좋다.(위에서 말한것처럼 인덱스를 과용 사용하게 되면 메모리에 과잉문제가 생길 수 있다.)

결론

답은 없는 것 같다. 우선 구현 해보고 쿼리를 이용해 성능 테스트 해보면서 서비스 특성에 맞게 조정해봐야 할 것 같다. 여기에 많은 시간을 사용하였지만 아직 잘 모르겠다. 다음에 알아봐야 할 점으로는 다음과 같다.

  • Mongodb는 많은 수의 collection을 제공한다. collection의 수가 성능에 미치는 영향이 있는가?(하나의 컬렉션에다가 관리하는 것보다 각 컬렉션에서 쿼리를 사용하는게 더 효율적인 것 같지만.. 컬렉션이 많다고 좋은 점만 있는 것 같지는 않다.)

  • 네트워크 호출 비용 vs 몽고디비 쿼리 조인 비용(이건 네트워크 호출보다 쿼리로 조인하는게 좋다고 생각하지만 알아볼 가치는 있는 것 같다.)

참조링크 :

Mongodb의 물리 데이터 저장 구조

스키마 디자인 6가지 전략

스키마 디자인

mongoose 스키마와 유용한 기능

데이터 모델링 타입

'IT > Mongodb' 카테고리의 다른 글

서브 도큐먼트 검색 후 값 변경  (0) 2018.02.07
도큐먼트 필드 삭제하기  (0) 2017.12.30
$elemMatch vs $in  (0) 2017.10.31
ISO Date 값이 한국 시간과 다를때  (1) 2017.10.31

pm2란?

  • pm2는 생산 프로세스 관리자로 서버 인스턴스들에 대한 로드 밸런싱과 더불어 Node.js의 스케일 업이나 스케일 다운을 돕는다.

  • 프로세스들이 계속 실행할 수 있는 환경을 제공한다.

  • 처리하지 못한 예외에 의해 쓰레드가 죽음으로 인해 어플리케이션이 죽는 현상을 방지한다.

  • 로드 밸러서 역할도 할 수 있다.

Node.js의 단일 쓰레드 어플리케이션과 예외

Node.js는 어플리케이션은 단일 쓰레드로 실행된다. 이것은 Node.js가 동시성을 지원하지 않는다는 의미가 아니다.(어플리케이션이 병렬로 실행된다는 것을 의미)

이는 다음을 암시한다.

예외가 처리되지 않으면 어플리케이션은 죽는다.

따라서 예외를 처리하기 위해 bluebird와 같은 promise library를 사용하여 성공과 실패에 대한 핸들러를 추가한다. 예외를 발생시키지 않음으로 어플리케이션이 죽는 경우를 방지한다.

하지만 처리할 수 없는 오류가 존재하기 때문에 pm2와 같은 태스크 러너를 사용하여 해결한다.

pm2 명령어

  1. app.js를 pm2에서 관리(app.js는 포크 모드에서 실행 - pm2가 로드 밸러서로 동작하지 않고 앱을 포크만 했다는 것을 의미)

pm2 start app.js

pm2 show [id] 을 통해 실행되고 있는 id에 해당하는 정보를 출력할 수 있다.

  1. pm2가 실행하는 앱의 개수를 모니터

pm2 monit
  1. logs를 확인

pm2 logs
  1. 어플리케이션 재시작 어플리케이션을 다운타임 없이 재시작을 보장한다.

pm2는 들어오는 요청을 큐에 대기시키고, 앱이 다시 반응하게 되면 앱의 재시작을 보장

pm2 reload all

pm2의 클러스터 기능

pm2는 클러스터 모드로 수행할 수 있는 기능을 제공한다. 클러스터 모드에서는 pm2는 지정된 대로 컨틀로러 프로세스와 많은 작업 프로세스를 생성한다. > Node.js 의 단일 쓰레드 기술로 멀티코어 CPU의 혜택을 누릴 수 있다.

  1. 실행중인 어플리케이션을 중지한다.

      pm2 stop all
  1. 클러스터 모드로 앱을 실행하기 위해 앱에 대한 정보를 지운다.

    pm2 delete all
  1. 다음 명령어는 세 개의 작업 프로세스 사이에서 라운드 로빈 방식으로 동작함을 의미한다. 동시에 3개의 요청에 대응할 수 있다.

    pm2 start app.js -i 3


다음 명령어를 통해 작업 프로세스의 수를 줄이거나 늘릴 수 있다.

    pm2 scale app 2

'Framework > Nodejs' 카테고리의 다른 글

error handler  (0) 2018.01.14
express-session  (0) 2018.01.03
[마이크로서비스] 세네카와 익스프레스 연동  (0) 2017.10.02
[마이크로서비스] Seneca  (0) 2017.10.02
비동기와 CPU Bound  (0) 2017.09.04

세네카와 익스프레스 연동

세네카는 웹 프레임워크가 아니기에 Express와 같은 웹 프레임워크와 연동을 해야 한다.

필요한 모듈을 설치해보자

npm install --save express
npm install --save seneca-web
npm install --save seneca-web-adapter-express

익스프레스와 세네카를 연동시키기 위해서는 seneca-web 모듈뿐만 아니라 adapter가 필요하다. seneca-web-adapter-express 모듈 역시 설치하고 세네카에 적용시켜주도록 하자.

var SenecaWeb = require('seneca-web');
var Express = require('express');
var Router = Express.Router;
var context = new Router();
 
var senecaWebConfig = {
      context: context,
      adapter: require('seneca-web-adapter-express'),
      options: { parseBody: false } // so we can use body-parser
};
 
var app = Express()
      .use( require('body-parser').json() )
      .use( context )
      .listen({port:3000});
 
var seneca = require('seneca')()
      .use(SenecaWeb, senecaWebConfig )
      // api 모듈
      .use('api')
      .client( { type:'tcp', pin:'role:math' } );

세네카와 익스프레스의 연동을 위한 부분은 아래와 같다.

.use(SenecaWeb, senecaWebConfig)
//This code means
//seneca.act('role:web', {routes:routes})

api 테스트

테스트를 위해 간단한 api를 작성해보도록 하자.

//api.js
 
module.exports = function api(options) {
  var valid_ops = { sum:'sum', product:'product' };
 
  this.add('role:math, cmd:product', function(msg, respond) {
    var product = msg.left * msg.right;
    respond(null, { answer: product});
  });
 
  this.add('role:math, cmd:sum', function(msg, respond) {
    var sum = parseInt(msg.left) + parseInt(msg.right);
    respond(null, {answer: sum});
  });
 
  this.add('role:api, path:calculate', function (msg, respond) {
    var operation = msg.args.params.operation;
    console.log(operation);
 
    var left = msg.args.query.left;
    var right = msg.args.query.right;
 
    this.act('role:math', {
      cmd:   valid_ops[operation],
      left:  left,
      right: right,
    }, respond);
  });
 
  this.add('init:api', function (msg, respond) {
    this.act('role:web',{routes:{
      prefix: '/api',
      pin:    'role:api,path:*',
      map: {
        //GET, POST 모두 호출 가능하게 함.
        calculate: { GET:true, POST: true, suffix:'/:operation' }
      }
    }}, respond);
  });
};

위의 role:web 패턴은 routes의 속성을 정의하고 있습니다.

  • prefix : URL

  • pin : map으로 전달될 패턴 집합

  • map : pin에서 * 로 매칭된 집합 리스트이며 URL의 엔드포인터로서의 값

init은 그냥 패턴일 줄 알았는데 세네카의 속성값이었다. url호출시 init:api 부분을 호출한다.

this.add('init:api')

POST로 요청해보자

GET과 같은 원리로 POST를 추가해보았는데 라우터 설정은 잘 되어있는 거 같은데 계속 body를 읽을 수 없다고 한다. 그래서 express처럼 bodyParser추가하는 방식으로 해보았더니 잘 되었다.

//app.js
 
...
var bodyParser = require('body-parser');
...
 
var app = Express();
 
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
 
app.use( context );
app.listen(3000);
 
...

this.add(init:api)의 map 부분에 user라는 path를 추가하였다.

//api.js
 
...
 
this.add('init:api', function (msg, respond) {
  this.act('role:web',{routes:{
    prefix: '/api',
    pin:    'role:api,path:*',
    map: {
      calculate: { GET:true, suffix:'/:operation' },
      user: { POST:true }
    }
  }}, respond);
});

seneca에 해당 패턴을 추가한다.

//api.js
 
this.add('display:userPw', function(msg, respond) {
  respond(null, {userPw: msg.userPw});
});
 
this.add('role:api, path:user', function (msg, respond) {
  console.log(msg);
  this.act('display:userPw', {
    userPw: msg.args.body.userPw
  }, respond);
});

테스트를 위해 포스트맨으로 x-www-form-urlencoded 방식으로 호출하였더니 다음과 같이 출력이 됨을 확인할 수 있다.

{
  "userPw": 1234
}

마치며

post로 데이터 전송을 하는데 계속 데이터 출력이 안되서 엄청 삽질을 하였다. 결국 익스프레스 사용할 때처럼 bodyParser를 적용하였다. 그래도 seneca 설정부분에서 options: { parseBody: false } 은 해줘야하는 것 같다. 이 부분을 제거해보니 전송이 안됨을 확인하였다.

'Framework > Nodejs' 카테고리의 다른 글

express-session  (0) 2018.01.03
pm2에 대해 알아보자  (0) 2017.10.06
[마이크로서비스] Seneca  (0) 2017.10.02
비동기와 CPU Bound  (0) 2017.09.04
node js, mongodb 연동 with mongoose  (0) 2017.08.16

Seneca란?

세네카란 마이크로서비스를 구축하기 위해 작성된 프레임워크이다. 코드에서 전송을 추상화하는 정교한 패턴 매칭 인터페이스를 마이크로서비스에 연결된다. 따라서 세네카를 이용하면 확장성이 높은 아키텍쳐를 쉽게 구축할 수 있다.

install

npm install -g seneca

패턴매칭

seneca.add 를 통해 패턴을 세네카에 패턴이 집합으로 새로운 함수를 추가한다.seneca.act 를 통해 전송된 패턴과 일치하는 서비스에 의해 명령을 실행될 세네카로 전송한다.

var seneca = require('seneca')();
 
seneca.add({role: 'math', cmd: 'sum'}, function(msg, respond) {
  var sum = Number(msg.left) + Number(msg.right);
  respond(null, {answer: sum});
});
 
seneca.add({role: 'math', cmd: 'product'}, function(msg, respond) {
  var product = msg.left * msg.right;
  respond(null, {answer: product});
});
 
// {answer: 3}
seneca.act({role: 'math', cmd: 'sum', left: 1, right: 2}, console.log);
 
// {answer: 12}
seneca.act({role: 'math', cmd: 'product', left: 3, right: 4}, console.log);

패턴 재사용

등록한 패턴을 다음과 같이 재사용할 수 있다.

seneca.add({role: 'math', cmd: 'sum'}, function(msg, respond) {
  var sum = msg.left + msg.right;
  respond(null, {answer: sum});
});
 
seneca.add({role: 'math'}, function(msg, respond) {
  this.act({
    role: 'math',
    cmd: 'sum',
    left: msg.left,
    right: msg.right
  }, respond);
});
 
//{answer: 7}
seneca.act({role: 'math', left: 3, right: 4}, console.log);

매턴 매칭은 어떻게 동작하는가?

매칭된 가장 긴 체인이 적합한 패턴이 된다.

{x:1}과 {x:1, y:2}가 패턴으로 등록되어있다고 가정해보자.

  • {x:1} 요청 -> {x:1}가 가장 적합

  • {x:1, y:2} 요청 -> {x:1, y:2}와 적합

  • {x:1, y:2, z:3} 요청 -> {x:1, y:2}와 적합

  • {y:2} -> 매칭된 패턴 없음.(앞에서부터 매칭 필요)

플러그인

아래와 같이 세네카 외부 함수를 만들고 use를 통해 세네카에 적용시켜 사용할 수 있다.

//math.js
 
function math(name) {
  var name = name;
 
  this.add('role:math, cmd:sum', function(msg, respond) {
    respond(null, {name: Number(msg.left) + Number(msg.right)});
  });
 
  this.add('role:math, cmd:product', function(msg, respond) {
    respond(null, {name: msg.left * msg.right});
  });
}
 
//{bbo: 3}
require('seneca')()
  .use(math, 'bbo')
  .act('role:math, cmd:sum, left:1, right:2', console.log);

마이크로서비스 작성 예시

module.exports = function math(options) {
 
  this.add('role:math,cmd:sum', function sum(msg, respond) {
    respond(null, { answer: Number(msg.left) + Number(msg.right) });
  });
 
  this.add('role:math,cmd:product', function product(msg, respond) {
    respond(null, { answer: msg.left * msg.right });
  });
 
  this.wrap('role:math', function (msg, respond) {
    msg.left  = Number(msg.left).valueOf();
    msg.right = Number(msg.right).valueOf();
    this.prior(msg, respond);
  });
};

위의 코드에서 주의깊게 봐야할 2개의 함수가 있다.

  • seneca.wrap 함수는 패턴과 매칭된 집합들을 같은 action으로 묶는다.

  • seneca.prior 함수는 이전의 정의된 action 을 호출한다. wrap을 통해 확장된 action을 호출후 내부에서 패턴과 일치하는 이전 action을 호출!!

require('seneca')()
  .use('math')
  .act('role:math, cmd:sum, left:3, right:4', console.log);
 
require('seneca')()
  .use('math')
  .act('role:math, cmd:product, left:3, right:4', console.log);

서버 실행 - listen()

require('seneca')()
  .use(math)
  .listen({type: 'tcp', pin: 'role:math'});

curl을 이용하여 패턴과 함께 url을 실행시킨다.(url/act)

$ curl -d '{"role":"math", "cmd":"sum", "left":1, "right":2}' http://localhost:3000/act

클라이언트 접속 - client()

require('seneca')()
// 로컬에 패턴 추가
.add('say:hello', function(msg, respond) {
  respond(null, {text: "Hi!"});
})
// 서버의 listen 매칭과 일치해야 한다.
// 위의 listen() 시 pin: 'role:math'로 하였으므로
// client() 에도 pin: 'role:math'가 되어야 한다.
.client({type: 'tcp', pin: 'role:math'})
// math 모듈의 패턴과 매칭
.act('role:math, cmd:sum, left:1, right:2',console.log)
// 로컬의 패턴과 매칭
.act('say:hello', console.log)
.act('role:math, cmd:product, left:3, right:3', console.log);

출력은 다음과 같을 것이다.

{text: 'hello'}
{answer: 3}
{answer: 9}

역시 비동기로 출력이 됨을 알 수가 있다.

마치며..

마이크로 서비스를 작성하기 앞서 세네카라는 프레임워크에 대해서 조사를 해보았다. 세네카를 사용하면 모듈단위로 확장할 때 패턴 매칭을 이용하기 때문에 확장성과 테스트 단위가 쉬울 것 같다. 다음 시간에는 express와 세네카를 적용해보아야겠다.

'Framework > Nodejs' 카테고리의 다른 글

pm2에 대해 알아보자  (0) 2017.10.06
[마이크로서비스] 세네카와 익스프레스 연동  (0) 2017.10.02
비동기와 CPU Bound  (0) 2017.09.04
node js, mongodb 연동 with mongoose  (0) 2017.08.16
이메일 발송  (0) 2017.07.13
1. Cliam 기반의 토큰
JWT는 Claim을 JSON형태로 표현(개행문자 등이 있으면 Header 파일에 넣기 어렵기 때문에 base64 인코딩을 하여 하나의 문자열로 변환)




1) 사용자 인증 후 토큰 생성
- 서버에서 별도로 저장 x, 사용자 정보나 권한을 토큰에 넣어서 저장

2) API 호출시 token과 함께 보낸다.


2. 변조 방지

토큰이 변조 되지 않는다고 증명 -> 무결성

이 무결성은 signature나 HMAC을 사용하여 보장한다.
(원본 메시지에서 해쉬값 추출 + 비밀키로 복호화 -> 생성된 문자열을 토큰의 뒤에 삽입)
이로써 비밀키를 모르면 HMAC을 얻을 수 없다.

궁금점) 왜 비밀키로 암호화를 하지 않고 복호화를 하는가? 해쉬값이 이미 암호화가 되어서 그런가?


3. 서명 생성 방식

메시지를 HMAC 을 이용하여 무결성 보장했지만 보안을 위해 서명 방식을 추가했다. 서명 알고리즘 정보를 JSON으로 표 후 BASE64 인코딩한 후 문자열을 claim 정보 앞에 붙인다.




참고사이트


http://bcho.tistory.com/999

[문제]

45656이란 수를 보자.

이 수는 인접한 모든 자리수의 차이가 1이 난다. 이런 수를 계단 수라고 한다.

그럼, 오늘도 역시 세준이는 0부터 9까지 모든 한 자리수가 자리수로 등장하면서, 수의 길이가 N인 계단 수가 몇 개 있는지 궁금해졌다.

N이 주어질 때, 길이가 N이면서 0에서 9가 모두 등장하는 계단 수가 총 몇 개 있는 지 구하는 프로그램을 작성하시오. (0으로 시작하는 수는 없다.)



[풀이]

필요한 입력데이터를 간추려 보면 현재 자릿수, 값, 그리고 각 자리수를 한번이라도 넣었는지 확인하는 변수가 필요합니다.

chairCount(location, value, state)는 해석하면 현재 자릿수에서 해당 값을 넣었을 때의 경우의 수에 대한 정보를 가지고 있습니다. 


주의 깊게 봐야할 점은 2가지가 있습니다.

1. long long 타입을 사용. 경우의 수가 상당히 크기 때문에 long long 타입을 사용하였고 출력서식형태는 %lld 입니다.

2. state는 해당 값을 이전에 넣었다면 true/false로 관리하기 위해 배열을 사용할 수 있지만 아래와 같이 비트마스크를 이용하여 관리할 수 있습니다. (state | 1<<value) 로 하여 value 자리에 1을 채워넣을 수 있습니다.



#include <stdio.h>

#include <string.h>

#define MOD 1000000000


int n;

long long cache[101][11][1<<11];


int chairCount(int location, int value, int state) {

if(value<0 || value>9) return 0;

if(location == n) {

if((state | (1<<value)) == (1<<10)-1) return 1;

else return 0;

}

long long & ret = cache[location][value][state];

if(ret!=-1) return ret;


state |= (1<<value);


return ret = (chairCount(location+1, value+1, state) + chairCount(location+1, value-1, state) ) % MOD;

}


int  main() {

scanf("%d", &n);

memset(cache, -1, sizeof(cache));

long long count = 0;

for(int i=1 ; i<=9 ; i++) {

count = (count + chairCount(1, i, 0)) % MOD;

}

printf("%lld", count);


return 0;

}


'IT > Algorithm' 카테고리의 다른 글

[더블릿] fac  (0) 2017.04.08
[더블릿] koi 수열/series  (0) 2017.04.08

비동기에 대해 생각해보자

nodejs가 흔히 비동기로 동작을 한다고 알고 있습니다. 비동기와 동기를 비교해 보면 동기는 함수가 호출되어 반환될 때까지 대기시간이 발생하지만 비동기는 동기적 코드의 반환값이 비동기적 코드에서는 콜백의 인자로 넘어오게 됩니다. 이는 반환값이 올때까지 기다리는 불필요한 대기시간을 해결해줍니다.

CPU Bound vs IO Bound

비동기로 작동하게 되면 위와 같은 좋은 점도 있지만 흐름을 제어하기가 어렵다는 점이 있습니다. 그래서 Promise 과 같은 모듈을 사용하지만 사용하기 전에 CPU bound와 IO Bound에 대해서 알고 사용해야 한다는 것을 알게 되었습니다.

아래와 같은 코드는 어떻게 동작할까요?

hashMap.forEach(function(value, key) {
  console.log(key + ' : ' + value);
})

hashMap에 존재하는 key, value값을 차례대로 출력하게 됩니다. 분명 forEach안에서 callback으로 작동하는데 말이죠. hashMap이라고 다른게 아니라 이는 접근하려고 하는 메모리가 inMemory이기 때문입니다.(인 것 같습니다..)

우선 이를 이해하기 위해서는 CPU bound와 I/O Bound란 무엇인지 알아야합니다.

  • CPU Bound는 CPU 자원을 사용하는 Task이며 작업 속도가 빠르며 javascript의 v8엔진에 의해 처리가 됩니다.

  • I/O Bound는 Input/Output이 Disk, network, Database와 관련된 Task이며 Event Loop가 돌면서 Event Queue에 쌓인 Message를 처리합니다.

CPU 자원을 사용하는 CPU Bound는 v8엔진에 의해 처리되는데 이는 동기적으로 처리가 됩니다. 그리고 우리가 아는 nodejs가 비동기적으로 처리한다는 것은 I/O Bound을 말합니다. 이를 잘 이해하여 동기적으로 돌아가는 것과 비동기적으로 돌아가는 코드를 구분할 수 있어야 할 것 같습니다...

Node js는 CPU Bound 작업을 처리하는데 적합한가

먼저 nodejs는 싱글 스레드 기반(Event Loop)으로 돌아갑니다. (내부적으로는 Non-Blocking을 지원하지 않는 I/O를 처리하기 위해 Multi Thread Pool을 이용합니다.)

다른언어와 비교해 보면 다른 언어는 두 개의 요청이 오면 번갈아 가면서 요청을 처리하지만 node js는 하나의 작업을 처리하고 다음 요청을 처리합니다. 이는 node js가 요청간의 cpu 낭비를 최소화하기 때문에 빠르다고 할 수 있지만 하나의 작업의 CPU burst time이 길어지면 그만큼 다른 요청의 처리가 늦어지고 성능의 저하로 느껴질 수 있습니다.




참고 사이트


'Framework > Nodejs' 카테고리의 다른 글

[마이크로서비스] 세네카와 익스프레스 연동  (0) 2017.10.02
[마이크로서비스] Seneca  (0) 2017.10.02
node js, mongodb 연동 with mongoose  (0) 2017.08.16
이메일 발송  (0) 2017.07.13
MVC model  (0) 2017.07.07

Mongodb 연동

node js 와 mongodb 를 mvc 패턴에 맞게 설계를 하였습니다. 같은 mvc 패턴이어도 model 부분에다가 mongoose.model 을 exports 하는 경우가 일반적이던데 어떤 방식이 맞다 틀리다는 아직 모르겠습니다.

app.js

app.js에서는 각종 모듈을 불러옵니다. catalog는 라우팅을 위한 모듈로 정의하였습니다.

var express = require('express');
var app = express();
var catalog = require('./api/routes/catalog');
var bodyParser = require('body-parser');
 
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
 
// mongoose require
var mongoose = require('mongoose');
 
//testDB setting
mongoose.connect('mongodb://localhost:27017/testDB');
 
// 3. use testDB
var db = mongoose.connection;
 
// 4. connect error
db.on('error', function(){
    console.log('Connection Failed!');
});
 
// 5. connect success
db.once('open', function() {
    console.log('Connected!');
});
 
app.use('/', catalog);
 
app.listen(3000, function() {
  console.log('server port 3000');
});

catalog.js

라우팅 모듈로서 정의하였습니다.

var express = require('express');
var router = express.Router();
 
var userController = require('../controllers/userController');
 
router.get('/', function (req, res) {
  console.log('hello world');
  res.end('hello world');
});
 
//user router
router.post('/users', userController.createUser);
router.get('/users/:uid', userController.getUser);
router.put('/users/:uid', userController.updateUser);
router.delete('/users/:uid', userController.deleteUser);
 
module.exports = router;

userModel.js

mongoose.Schema 를 통해 Schema를 정의합니다. mongoose.model을 통해 mongoDB의 collection을 생성할 수 있습니다. 아래 예제를 통해 mongoDB 콜솔에서 testDB에 show collections 을 입력하면 users라는 collection이 생성이 되었음을 확인할 수 있습니다.

!model을 user라고 정의하니 users라는 복수형의 컬렉션 네임이 지정되었습니다. 이 이유는 잘 모르겠네요

var mongoose = require('mongoose');
 
var Schema = mongoose.Schema;
 
var userSchema = new Schema({
  "userId": String,
  "userPw": String,
  "modifyDate" : Date
});
 
module.exports = mongoose.model('user', userSchema);

userController.js

해당 모듈을 불러옵니다.

var mongoose = require('mongoose');
var User = require('../models/userModel');

데이터 저장을 위해 save()함수를 이용합니다.

exports.createUser = function(req, res) {
  console.log(req.body);
  var newUser = new User(req.body);
  newUser.modifyDate = new Date();
 
  newUser.save(function(error, data) {
    if(error) {
      console.log(error);
    } else {
      console.log('Saved');
    }
  });
  res.end('createUser');
};

데이터를 얻기 위해 findOne() 함수를 이용합니다. findById 는 _id의 데이터를 기준으로 하여 얻을 수 있습니다. 데이터 전체를 얻기 위해서는 find() 함수를 이용합니다.

exports.getUser = function(req, res) {
  User.findOne({_id : req.params.uid}, function(error, user) {
    console.log('--- Read one user---');
    if(error) {
      console.log(error);
    } else {
      console.log(user);
    }
  });
  res.end('getUser');
};

특정 데이터 수정 부분입니다.

exports.updateUser = function(req, res) {
  User.findById({_id : req.params.uid}, function(error, user) {
    console.log('--- Update user ---');
    if(error) {
      console.log(error);
    } else {
      user.userPw = req.body.userPw;
      user.modifyDate = new Date();
 
      user.save(function(error, modify_user) {
        if(error) {
          console.log(error);
        } else {
          console.log(modify_user);
        }
      });
    }
  });
  res.end('updateUser');
};

특정 데이터 삭제 부분입니다.

exports.deleteUser = function(req, res) {
  User.remove({_id : req.params.uid}, function(error, output) {
    console.log('--- Delete ---');
    if(error) {
      console.log(error);
    } else {
      console.log(output);
    }
  });
 
  res.end('deleteUser');
};


'Framework > Nodejs' 카테고리의 다른 글

[마이크로서비스] Seneca  (0) 2017.10.02
비동기와 CPU Bound  (0) 2017.09.04
이메일 발송  (0) 2017.07.13
MVC model  (0) 2017.07.07
mocha  (0) 2017.07.05

+ Recent posts