블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2018. 11. 16. 16:07 Meteor/React + Meteor

Meteor에서 Redux 환경을 설정하고, 애플리케이션에 적용해 본다.


  - Meteor Collection의 Pub/Sub 설정

  - Collection의 CRUD 서비스 클래스 개발

  - Redux 환경 설정

  - Redux의 action, reducer 개발 및 store 설정

  - 애플리케이션에 Redux action 호출하기

  - 소스코드 




MongoDB Collection Pub/Sub 설정


기본설치를 하게 되면 autopublish 미티어 패키지가 설치되어 있다. 삭제한다.

$ meteor remove autopublish


삭제를 하면 links 컬렉션으로 부터 데이터를 받지 못한다. 이제 필요한 것을 받기 위해 publish, subscribe를 설정한다. 

  - server/main.tsx에서 insertLink 코드를 제거한다.

  - server/main.tsx에서 links 코드 import 문을 수정한다. 

// main.tsx 전체코드

import { Meteor } from 'meteor/meteor';

import '../imports/api/links';  // 서버에서 사용하기 위해 반드시 import해야 한다. 하지 않으면 collection 생성, 제어를 못 한다.


Meteor.startup(() => { }); // 텅빈 코드 블록


  - imports/api/links.ts 에 publish 한다. 

if (Meteor.isServer) {

  Meteor.publish('links', () => Links.find());

}


  - subscribe를 사용할 때 미티어의 Tracker.autorun을 사용하지 않고, React의 props에 subscribe정보를 바로 맵핑해 주는 withTracker를 사용하기 위해 react-meteor-data 패키지를 사용한다.

$ meteor npm install --save react-meteor-data


  - imports/api/ui/Info.tsx에 withTracker를 설정한다. 

import { Meteor } from 'meteor/meteor';

import Links from '../api/links';


interface InfoProps {

links: any;

loading: boolean;

}


// 맨 하단

export default withTracker(() => {

       const handle = Meteor.subscribe('links');

return {

links: Links.find().fetch(),

loading: !handle.ready()

}

})(Info);




MongoDB Collection CRUD


간단한 Link CRUD 애플리케이션을 만든다. 


  - Collection의 CRUD 로직을 담은 service를 생성한다. imports/ui밑에 link폴더를 생성한다. 

// imports/ui/link/link.service.ts

import Links from '../api/links';


class LinkService {

  addLink({title, url}) {

    Links.insert({title, url, createAt: new Date()});

  }


  removeLink(_id: string) {

    Links.remove(_id);

  }


  updateLink(_id: string, {title, url}) {

    Links.update(_id, {title, url, updateAt: new Date()});

  }

}


export const linkService = new LinkService();


  - imports/ui/link/AddLink.tsx 파일 추가

import * as React from 'react';

import { linkService } from './links.service';

export interface AddLinkProps {

}

export default class AddLink extends React.Component<AddLinkProps, any> {

  handleSubmit = (e: any) => {

    //ignore validation

    e.preventDefault();

    const param = {

      title: e.target.title.value,

      url: e.target.url.value

    }

    linkService.addLink(param);

  };


  public render() {

    return (

      <form onSubmit={this.handleSubmit} >

        <input type="text" name="title" placeholder="title" />

        <input type="text" name="url" placeholder="url" />

        <button>Add Link</button>

      </form>

    )

  }

}


  - imports/ui/App.tsx에서 <Info/> 만 남기고 삭제하고, Info.tsx에 AddLink를 추가한다. 

// App.tsx

class App ... {

  render() { 

    return (

      <div></InfoContainer/></div>

    )

  }

}


// Info.tsx
class Info ... {
  render() {
    return(
      <div> 
          <AddLink />
          ....
       </div>
    )
  }
}


  - link폴더 밑에 LinkList.tsx와 Link.tsx를 추가한다. 

// Link.tsx

import * as React from 'react';

export interface LinkProps {

  link: any

}


export default class Link extends React.Component<LinkProps, any> {

  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

        <a href={link.url} target="_blank">{link.title}</a>

      </li>

    );

  }

}


// LinkList.tsx

import * as React from 'react';

import Link from './Link';

export interface LinkListProps {

  links: any[]

}


export default class LinkList extends React.Component<LinkListProps, any> {

  public render() {

    const links = this.props.links.map(

      link => <Link link={link}/>

    );

    return (

      <div>

        <h2>Links</h2>

        <ul>{links}</ul>

      </div>

    );

  }

}



  - Info.tsx에서 LinkList를 사용토록 변경한다. 

class Info extends React.Component<InfoProps, any> {

  linkList() {

    const { links, loading } = this.props;

    if (loading) {

      return <div>loading...</div>

    } else {

      return <LinkList links={links} />

    }

  }


  render() {

    const { links } = this.props;

    return (

      <div>

        <AddLink />

        {this.linkList()}

      </div>

    );

  }

}


  - 마지막으로 link 삭제를 한다. 

// imports/ui/link.Link.tsx

import * as React from 'react';

import { linkService } from './links.service';


export interface LinkProps {

  link: any

}


export default class Link extends React.Component<LinkProps, any> {

  removeLink = () => {

    const { link } = this.props;

    linkService.removeLink(link._id);

  }


  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

        <a href={link.url} target="_blank">{link.title}</a>

        <button onClick={this.removeLink}> x </button>

      </li>

    );

  }

}





Redux 관련 패키지 설치


redux 기본 패키지를 설치한다. 

$ meteor npm install --save redux react-redux react-router-redux

$ meteor npm install --save-dev @types/react-redux  @types/react-router-redux


redux action, reducer을 보다 편하게 사용할 수 있는 typesafe-actions 모듈을 설치한다.

$ meteor npm install --save typesafe-actions 


redux에서 비동기를 처리할 수 있는 redux-observable 과 rxjs 모듈을 설치한다. 

$ meteor npm install --save redux-observable rxjs


state 갱신으로 인한 반복 rendering을 제거해 성능향상을 위한 reselect 모듈도 설치한다. 

$ meteor npm install --save reselect




Redux 코드 개발


link action 개발

  - action type constants 정의

  - action Model 정의

  - action  에 대한 정의

// imports/ui/pages/link/link.action.ts

import { action } from 'typesafe-actions';


/**************************

 * constants, model

 **************************/

// constants

export const ADD_REQUEST = '[link] ADD_REQUEST';

export const ADD_SUCCESS = '[link] ADD_SUCCESS';

export const ADD_FAILED = '[link] ADD_FAILED';

export const DELETE_REQUEST = '[link] DELETE_REQUEST';

export const DELETE_SUCCESS = '[link] DELETE_SUCCESS';

export const DELETE_FAILED = '[link] DELETE_FAILED';

export const CHANGE = '[link] CHANGE';


// model

export type LinkModel = {

  _id?: string;

  title?: string;

  url?: string;

  visited?: boolean;

  error?: boolean;

  errorMsg?: any;

  success?: boolean;

  createdAt?: any;

  updatedAt?: any;

};


/**************************

 * actions, action-type

 **************************/

export const addLink = (params: LinkModel) => action(ADD_REQUEST, params);

export const addLinkSuccess = (params: LinkModel) => action(ADD_SUCCESS, params);

export const addLinkFailed = (params: LinkModel) => action(ADD_FAILED, params);

export const removeLink = (id: string) => action(DELETE_REQUEST, id);

export const removeLinkSuccess = (id: string) => action(DELETE_SUCCESS, id);

export const removeLinkFailed = (id: string) => action(DELETE_FAILED, id);

export const changeLink = (id: string) => action(CHANGE, id);


link reducer 개발

  - Link State 정의

  - LinkAction 타입 

  - Link reducer 분기

  - reselect를 이용한 selector는 별도로 만들지 않고, link.reduer.ts 파일이 함께 둔다. (파일이 너무 많아져서...)

// imports/ui/pages/link/link.reducer.ts

import { ActionType } from 'typesafe-actions';

import { combineReducers } from 'redux';

import { createSelector } from 'reselect';

import * as actions from './link.action';


/**************************

 * state

 **************************/

// state

export type LinkState = {

  list: actions.LinkModel[],

  linkFilter: string

};


/**************************

 * reducers

 **************************/

export type LinkAction = ActionType<typeof actions>;

export const linkReducer = combineReducers<LinkState, LinkAction>({

  list: (state = [], action) => {

    switch (action.type) {

      case actions.ADD_FAILED:

        return state;

      case actions.ADD_SUCCESS:

        return [...state, action.payload];

      case actions.DELETE_FAILED:

        return state;

      case actions.DELETE_SUCCESS:

        return state.filter(item => item._id === action.payload);

      case actions.CHANGE:

        return state.map(

          item =>

            item._id === action.payload

              ? { ...item, visited: !item.visited }

              : item

        );

      default:

        return state;

    }

  },

  linkFilter: (state = '', action) => {

    switch (action.type) {

      case actions.CHANGE:

        if (action.payload === 'visited'){

          return '';

        } else {

          return 'visited';

        }

      default:

        return state;

    }

  }

})


/**************************

 * selectors

 **************************/

export const getLinks = (state: LinkState) => state.list;

export const getLinkFilter = (state: LinkState) => state.linkFilter;

export const getFilteredLinks = createSelector(getLinks, getLinkFilter, (links, linkFilter) => {

  switch (linkFilter) {

    case 'visited':

      return links.filter(t => t.visited);

    default:

      return links;

  }

});


redux-observable을 이용한 epic을 만든다.

  - async 처리

  - rxjs 이용

  - takeUntil은 cancel을 위한 장치이다.

// imports/ui/pages/link/link.epic.ts

import { Epic, combineEpics } from 'redux-observable';

import { filter, map, switchMap, takeUntil } from 'rxjs/operators';

import { isOfType } from 'typesafe-actions';


import Links from '../../../api/links';

import { insertCollection, removeCollection, RequestModel } from '../../sdk';

import * as actions from './link.action';


const addLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.ADD_REQUEST)),

    switchMap(action => {

      const { title, url } = action.payload;

      return insertCollection(Links, { title, url, createdAt: new Date() })

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.addLinkFailed({ ...response.result })

      }

      return actions.addLinkSuccess(response.result)

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );


const removeLink: Epic = (

  action$,

  store

) =>

  action$.pipe(

    filter(isOfType(actions.DELETE_REQUEST)),

    switchMap(action => {

      return removeCollection(Links, action.payload);

    }),

    map((response: RequestModel) => {

      if (response.error) {

        return actions.removeLinkFailed({ ...response.result, ...response.params })

      }

      return actions.removeLinkSuccess(response.params._id);

    }),

    // takeUntil(action$.pipe(

    //   filter(isOfType(actions.ADD_REQUEST))

    // ))

  );


export const linkEpic = combineEpics(addLink, removeLink);


Meteor Client Collection 을 Observable로 전환

  - meteor client collection의 insert, remove시에 call 결과를 Observable로 변환하여 반환하는 유틸을 만든다. 

  - 이는 redux-observable의 epic에서 async 데이터를 switchMap 오퍼레이터로 다루기 위함이다. 

// imports/sdk/util/ddp.util.ts

import { from, Observable } from 'rxjs';


export type RequestModel = {

  error?: boolean;

  success?: boolean;

  result: any;

  params?: any;

}


export function insertCollection(collection: any, params: any): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    collection.insert(params, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { ...params } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: {...params} });

      } else {

        resolve({ success: true, result: { ...result }, params: { ...params } });

      }

    });

  }));

}


export function removeCollection(collection: any, _id: string): Observable<RequestModel> {

  return from(new Promise((resolve, reject) => {

    collection.remove(_id, (error, result) => {

      if (error) {

        reject({ error: true, result: { ...error }, params: { _id } });

      }

      if (typeof result === 'string' || typeof result === 'number') {

        resolve({ success: true, result, params: { _id } });

      } else {

        resolve({ success: true, result: { ...result }, params: { _id } });

      }

    });

  }));

}




RootReducer와 Store 설정


link관련 action, reducer, epic 개발이 끝나면 이를 등록하는 설정을 한다. 

  - root Reducer 정의

  - root State  타입 정의

  - root Action 타입 정의

  - root Epic 정의

  - root Store 설정

// imports/ui/store.ts

import { combineEpics, createEpicMiddleware } from 'redux-observable';

import { createStore, applyMiddleware, combineReducers, compose } from 'redux';

import { RouterAction, LocationChangeAction } from 'react-router-redux';

import { routerReducer } from 'react-router-redux';

import { StateType } from 'typesafe-actions'; 


import { linkEpic } from './pages/link/link.epic';

import { linkReducer, LinkAction } from './pages/link/link.reducer';


/***********************

 * root reducer

 ***********************/

const rootReducer = combineReducers({

  router: routerReducer,

  links: linkReducer

});


/***********************

 * root state

 ***********************/

export type RootState = StateType<typeof rootReducer>;


type ReactRouterAction = RouterAction | LocationChangeAction;

export type RootAction = ReactRouterAction | LinkAction;


/***********************

 * root epic

 ***********************/

const composeEnhancers =

  (process.env.NODE_ENV === 'development' &&

    (window as any) &&

    (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||

  compose;


const rootEpic = combineEpics(linkEpic);

const epicMiddleware = createEpicMiddleware();


/***********************

 * root store

 ***********************/

function configureStore(initialState?: object) {

  // configure middlewares

  const middlewares = [epicMiddleware];

  // compose enhancers

  const enhancer = composeEnhancers(applyMiddleware(...middlewares));

  // create store

  const store = createStore(rootReducer, enhancer);

  // run epic: https://redux-observable.js.org/docs/basics/SettingUpTheMiddleware.html

  epicMiddleware.run(rootEpic);

  return store;

}


const store = configureStore();

export default store;


애플리케이션에 store를 최종 설정한다. 

// client/main.tsx

import * as React from 'react';

import { Meteor } from 'meteor/meteor';

import { render } from 'react-dom';

import { Provider } from 'react-redux';


import App from '../imports/ui/App'

import store from '../imports/ui/store';


const Root = (

  <Provider store={store}>

    <App />

  </Provider>

);


Meteor.startup(() => {

  render(Root, document.getElementById('react-target'));

});


window 객체 인식을 위해 type definition을 정의한다. 

  - 루트에 typings 폴더 생성

  - typings/index.d.ts 파일 생성

declare var window: any;




Application에서 Redux Action 호출하기


add/remove link 리덕스 액션을 애플리케이션에 적용한다. link.service.ts 는 사용하지 않는다. 

  - AddLink.tsx에 addLink 액션 적용

// imports/ui/link/AddLink.tsx

import * as React from 'react';

import { connect } from 'react-redux';

import { addLink } from './link.action';


export interface AddLinkProps {

  addLink: Function;

}


class AddLink extends React.Component<AddLinkProps, any> {

  handleSubmit = (e: any) => {

    //ignore validation

    e.preventDefault();

    const param = {

      title: e.target.title.value,

      url: e.target.url.value

    }

    const { addLink } = this.props;

    addLink(param);

  };


  public render() {

    return (

      <form onSubmit={this.handleSubmit} >

        <input type="text" name="title" placeholder="title" />

        <input type="text" name="url" placeholder="url" />

        <button>Add Link</button>

      </form>

    )

  }

}


export default connect(undefined, { addLink })(AddLink);


  - Link.tsx에 removeLink 액션 적용

// imports/ui/pages/link/Link.tsx

import * as React from 'react';

import { removeLink } from './link.action';

import { connect } from 'react-redux';


export interface LinkProps {

  link: any,

  removeLink: Function

}

class Link extends React.Component<LinkProps, any> {

  removeLink = () => {

    const { link, removeLink } = this.props;

    removeLink(link._id);

  }


  public render() {

    const { link } = this.props;

    return (

      <li key={link._id}>

        <a href={link.url} target="_blank">{link.title}</a>

        <button onClick={this.removeLink}> x </button>

      </li>

    );

  }

}


export default connect(undefined, { removeLink })(Link);





<참조>

- 블로그 소스 코드

react-redux-typescript-guide 소스

- redux-actions 사용하기

- redux-observable 사용하기

- typesafe-actions 사용하기

- reselect 사용하여 성능 최적화 하기

posted by 윤영식
2017. 11. 8. 17:20 Meteor/Angular + Meteor

Ionic CLI 와 Meteor CLI 로 프로젝트 구성하기는 모바일 프로젝트를 진행할 때 사용하면 되고, 이번에는 Angular CLI 와 Meteor CLI를 통해 프로젝트 구성을 어떻게 하는지 살펴본다. 




Webpack 기반 프로젝트 초기화


Angular CLI를 통해 프로젝트를 생성한다. @angular/cli v1.5.0이 설치되고, webpack은 v3.8.1 이고, 내부적으로 @angular-devkit, @ngtools/webpack, @schematics등이 사용된다.

$ npm install -g @angular/cli


@angular/CLI 설치후 프로젝트를 생성한다. ng <command>의 상세 내용은 위키를 참조한다.

$ ng new <projectName>


Webpack 환경파일을 수정해야 하므로, eject 명령을 수행하고, 결과로 출력된 가이드에 따라 "npm install" 명령을 수행한다. eject 명령에 대한 다양한 options은 위키를 참조한다. eject시에 옵션을 주면 옵션이 적용된 webpack.config.js가 생성된다.

$ ng eject --aot --watch


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

Ejection was successful.


To run your builds, you now need to do the following commands:

   - "npm run build" to build.

   - "npm test" to run unit tests.

   - "npm start" to serve the app using webpack-dev-server.

   - "npm run e2e" to run protractor.


Running the equivalent CLI commands will result in an error.

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

Some packages were added. Please run "npm install".


$ npm install

$ npm run build


eject를 수행한 경우에는 "ng serve" 명령으로 테스트 서버를 뛰울 수 없다. package.json에 적용된 스크립트인 "npm start"를 수행하고 4200 port로 브라우져에서 

$ npm start


 10% building modules 3/3 modules 0 activeProject is running at http://localhost:4200/

webpack output is served from /

....


Webpack 환경 내역은 크게 entry, output, module (for loader), plugins 로 구성된다. (참조)

  - entry: 파일 위치

  - output: 결과 위치

  - module: css, .ts, .html 파일 관리 및 변환기 -> 자바스크립트 모듈로 만들기 위한 것. postfix가 "-loader" 이다. 로더는 파일단위 처리

  - plugins: 압축, 핫로딩, 복사, 옮기기등. 플러그인은 번들된 결과물을 처리





Angular CLI 환경에 Meteor 설정



이전 포스트처럼 루트에 api 폴더를 만들고 이를 Meteor의 백앤드로 사용토록 설정한다.

// webpack.config.js


const webpack = require('webpack');

...

resolve: {

  alias: {

    'api': path.resovle(__dirname, 'api/server'),

    ...

  }

}


externals: [ resolveExternals ],


plugins: [ ..., new webpack.ProvidePlugin({ __extends: 'typescript-extends' }) ],


node: { ..., __dirname: true }


// 맨 마지막에 넣음 

function resolveExternals(context, request, callback) {

  return resolveMeteor(request, callback) ||

    callback();

}

 

function resolveMeteor(request, callback) {

  var match = request.match(/^meteor\/(.+)$/);

  var pack = match && match[1];

 

  if (pack) {

    callback(null, 'Package["' + pack + '"]');

    return true;

  }

}


루트에 있는 tsconfig.json에 Meteor 백앤드 관련 내용을 추가한다.

"compilerOptions: {

"baseUrl": ".",

"module": "commonjs",

...

"skipLibCheck": true,

"stripInternal": true,

"noImplicitAny": false,

"types": [ "@types/meteor" ]

},

"include": [ ..., "api/**/*.ts" ],

"exclude": [ ..., "api/node_modules", "api" ]


src/tsconfig.app.json과 tsconfig.spec.json안에 api에 대한 exclude도 설정해야 한다.

// src/tsconfig.app.json

"exclude": [

   ...,

   "../api/node_modules"

]


// src/tsconfig.spec.json

"exclude": [ "../api/node_modules" ]


관련 패키지를 설치한다.

$ npm install --save-dev typescript-extends

$ npm install --save-dev @types/meteor

$ npm install --save-dev tmp




Meteor Server API 생성 및 설정


Meteor CLI를 설치하고 api 명으로 Meteor 프로젝트를 생성한다.

$ curl https://install.meteor.com/ | sh

$ meteor create api


api 폴더 밑의 필요없는 폴더를 삭제하고 루트에 있는 것으로 대체한다.

$ cd api

// 삭제

api$ rm -rf node_modules client package.json package-lock.json


// 심볼릭 링크

api$ ln -s ../package.json

api$ ln -s ../package-lock.json

api$ ln -s ../node_modules

api$ ln -s ../src/declarations.d.ts


Meteor 백앤드를 Typescript 기반으로 개발하기 위한 패키지를 설치한다.

api$ meteor add barbatus:typescript


api$ cd ..

$ npm install --save babel-runtime

$ npm install --save meteor-node-stubs

$ npm install --save meteor-rxjs


Typescript의 tsconfig.json 파일을 api 폴더안에 생성하고 다음 내역을 붙여넣는다.

{

  "compilerOptions": {

    "allowSyntheticDefaultImports": true,

    "declaration": false,

    "emitDecoratorMetadata": true,

    "experimentalDecorators": true,

    "lib": [

      "dom",

      "es2017"

    ],

    "module": "commonjs",

    "moduleResolution": "node",

    "sourceMap": true,

    "target": "es6",

    "skipLibCheck": true,

    "stripInternal": true,

    "noImplicitAny": false,

    "types": [

      "@types/meteor"

    ]

  },

  "exclude": [

    "node_modules"

  ],

  "compileOnSave": false,

  "atom": {

    "rewriteTsconfig": false

  }

}


Meteor의 server/main.js를 main.ts로 바꾼다. 위에서 설치한 meteor-rxjs는 클라이언트단의 Meteor를 RxJS Observable기반으로 사용할 수 있도록 한다.

// 예) Meteor 클라이언트단 Collection 구성

import { MongoObservable } from 'meteor-rxjs';


export const Chats = new MongoObservable.Collection('chats');




Meteor Client 준비


Meteor Server <-> Client 연동을 위해 Client를 Bundling한다.

$ sudo npm install -g meteor-client-bundler


번들링시에 Meteor Server 기본 주소는 localhost:3000 으로 설정된다. Meteor Client는 DDP를 이용하기 때문에 번들할 때 --config 옵션 또는 --url 옵션으로 Meteor Server 위치를 지정한다.  -s 옵션을 주면 Client<->Server 같은 버전의 패키지를 사용하고 -s 옵션을 주지않으면 config 설정내용을 참조한다. (참조)

$ meteor-client bundle --destination meteor.bundle.js --config bundler.config.json


// config file

{ "release": "1.6", "runtime": { "DDP_DEFAULT_CONNECTION_URL": "http://1.0.0.127:8100" }, "import": [ "accounts-base", "mys:accounts-phone", "jalik:ufs@0.7.1_1", "jalik:ufs-gridfs@0.1.4" ] }


package.json에 번들링 명령을 등록하고 수행한다. 

"scripts": {

   ...,

   "meteor-client:bundle": "meteor-client bundle -s api"

}


//번들링 - 최초 한번만 수행한다.

$ npm run meteor-client:bundle


Angular 클라이언트에서 Meteor Client를 사용하기 위해 src/main.ts에서 "meteor-client"를 import한다.

import "meteor-client";




Collection 생성하고 Meteor 기능 사용 테스트


api/server 에 model을 하나 만든다. Angular와 Meteor가 같이 사용하는 모델 타입이다.

// api/server/models.ts

export enum MessageType {

  TEXT = <any>'text'

}


export interface Chat {

  _id?: string;

  title?: string;

  picture?: string;

  lastMessage?: Message;

  memberIds?: string[];

}


export interface Message {

  _id?: string;

  chatId?: string;

  senderId?: string;

  content?: string;

  createdAt?: Date;

  type?: MessageType;

  ownership?: string;

}


api/server/collections 폴더를 생성하고 Chat 컬렉션을 생성한다. meteor-rxjs 는 RxJS로 Mongo Client를 wrapping해 놓은 것으로 Rx방식으로 Mongo Client를 사용할 수 있게 한다.

// api/server/collections/chats.ts

import { MongoObservable } from 'meteor-rxjs';

import { Chat } from '../models';


export const Chats = new MongoObservable.Collection<Chat>('chats'); 


// api/server/collections/messages.ts

import { MongoObservable } from 'meteor-rxjs';

import { Message } from '../models';


export const Messages = new MongoObservable.Collection<Message>('messages');


api/server/main.ts안에 샘플 데이터를 넣는다.

$ npm install --save moment


// api/server/main.ts

import { Meteor } from 'meteor/meteor';

import { Chats } from './collections/chats';

import { Messages } from './collections/messages';

import * as moment from 'moment';

import { MessageType } from './models';


Meteor.startup(() => {

  // code to run on server at startup

  if (Chats.find({}).cursor.count() === 0) {

    let chatId;


    chatId = Chats.collection.insert({

      title: 'Ethan Gonzalez',

      picture: 'https://randomuser.me/api/portraits/thumb/men/1.jpg'

    });


    Messages.collection.insert({

      chatId: chatId,

      content: 'You on your way?',

      createdAt: moment().subtract(1, 'hours').toDate(),

      type: MessageType.TEXT

    });


    chatId = Chats.collection.insert({

      title: 'Bryan Wallace',

      picture: 'https://randomuser.me/api/portraits/thumb/lego/1.jpg'

    });


    Messages.collection.insert({

      chatId: chatId,

      content: 'Hey, it\'s me',

      createdAt: moment().subtract(2, 'hours').toDate(),

      type: MessageType.TEXT

    });

  }

});


다음으로 Angular에 Chat 컬렉션을 사용한다.

// src/app/app.component.ts

import { Component, OnInit } from '@angular/core';

import { Chats } from '../../api/server/collections/chats';

import { Chat } from '../../api/server/models';


@Component({

  selector: 'app-root',

  templateUrl: './app.component.html',

  styleUrls: ['./app.component.css']

})

export class AppComponent implements OnInit {

  title = 'app';

  chats: Chat[];

  ngOnInit() {

    Chats.find({}).subscribe((chats: Chat[]) => this.chats = chats );

  }

}


// src/app/app.component.html 에 추가

<div> {{ chats | json }} </div>




Angular & Meteor 기동


Meteor 기동

$ cd api

api$ meteor


Angular 기동

$ npm start


Webpack dev server는 4200이고meteor client는 server에 websocket 3000 port로 접속을 한다. Meteor 의 mongo로 접속해서 chats collection을 확인해 본다.

$ meteor mongo

MongoDB shell version: 3.2.15

connecting to: 127.0.0.1:3001/meteor

Welcome to the MongoDB shell.

For interactive help, type "help".

For more comprehensive documentation, see

http://docs.mongodb.org/

Questions? Try the support group

http://groups.google.com/group/mongodb-user

meteor:PRIMARY> show collections

chats

messages



하단에 Chat 내역이 json 형식으로 출력된다.




<참조>


- Webpack v3 환경설정 요약

- Meteor Client bundler의 작도방식 by Uri

- Meteor Client Bundler Github


posted by 윤영식
2017. 11. 6. 15:57 Meteor/Angular + Meteor

angular-meteor.com의 Socially Merge를 따라하며 내용을 요약해 보았다. socially merge는 meteorionic의 조합을 webpack으로 묶어 프로젝트를 구성해보는 과정이다. 여기서 익힐 수 있는 것은 Angular 로 작성된 ionic을 프론트앤드로 사용하고 metetor를 백앤드로 구성하는 방법을 배울 수 있다.




Webpack 기반 프로젝트 초기화


meteor와 ionic는 서로의 CLI를 제공한다. ionic을 먼저 설정한다.

$ npm install -g ionic cordova


초기 프로젝트 구성을 ionic CLI를 통해 생성한다.

ionic start whatsapp blank --cordova --skip-link

...

? Install the free Ionic Pro SDK and connect your app? (Y/n) n


$ cd whatsapp

$ ionic serve


Ionic은 Angular를 사용하기 때문에 Typescript 최신버전을 설치한다.

$ npm install --save typescript


타입스크립트기반에서 외부라이브러리를 사용하기 위해서 src/declarations.d.ts 파일을 생성해 놓는다. d.ts작성법은 공식문서를 참조한다. 다음으로 Ionic 3가 Webpack을 기반으로 빌드되는 설정을 package.json에 추가한다.

//  package.json

"config": {

    "ionic_webpack": "./webpack.config.js"

}


Ionic은 webpack config에 대한 샘플 파일을 제공하고 이를 복사한다. webpack.config.js안에는 dev와 prod환경 기본값이 설정되어 있다.

$ cp node_modules/@ionic/app-scripts/config/webpack.config.js .




Ionic 에 Meteor 설정


프로젝트 폴더의 루트에 api 폴더를 만들고 이를 Meteor의 백앤드로 사용하는 설정을 webpack.config.js에 한다. meteor 관련된 것은 external로 취급하는 것 같다.

// dev, prod양쪽에 넣는다.

resolve: {

...

alias: {

'api': path.resolve(__dirname, 'api/server')

}

},


externals: [ resolveExternals ],


plugins: [ ..., new webpack.ProvidePlugin({ __extends: 'typescript-extends' }) ],


node: { ..., __dirname: true }


// 맨 마지막에 넣음 

function resolveExternals(context, request, callback) {

  return resolveMeteor(request, callback) ||

    callback();

}

 

function resolveMeteor(request, callback) {

  var match = request.match(/^meteor\/(.+)$/);

  var pack = match && match[1];

 

  if (pack) {

    callback(null, 'Package["' + pack + '"]');

    return true;

  }

}


다음으로 Meteor를 외부 의존하는 것으로 tsconfig.json에 Meteor관련 내용을 추가한다.

"compilerOptions": {

  "baseUrl": ".",

  "modules": "commonjs",

  "paths": { "api/*": ["./api/server/*"],

  ...,

  "skipLibCheck": true,

  "stripInternal": true,

  "noImplicitAny": false,

  "types": [

      "@types/meteor"

  ] 

},

"include": [ ..., "api/**/*.ts" ],

"exclude": [ ..., "api/node_modules", "api" ]


typescript-extends 패키지와 meteor 타입 패키지를 설치한다.

$ npm install --save-dev typescript-extends

$ npm install --save-dev @types/meteor


Ionic serve를 실행하고 브라우져의 console을 보면 다음 오류가 나온다. 이를 위해 src/app/app.component.ts에 cordova 설정을 한다.

// Chrome devtools 메세지

Native: tried calling StatusBar.styleDefault, but Cordova is not available. Make sure to include cordova.js or run in a device/simulator


// src/app/app.component.ts 설정




Meteor Server 생성 및 설정


Ionic의 webpack과 tsconfig안에 Meteor 서버 설정을 했고, Meteor CLI를 이용해 백앤드를 구성한다.

// 미티어 설치

$ curl https://install.meteor.com/ | sh


// api 폴더 미티어 초기화

$ meteor create api


api 폴더 밑으로 생성된 node_modules, client 폴더를 삭제하고, package.json, package-lock.json 파일도 삭제한다.

$ rm -rf api/node_modules

$ rm -rf api/client

$ rm api/package.json

$ rm api/package-lock.json


Meteor에서 사용하는 것을 Ionic 으로 심볼릭 링크를 건다.

$ cd api

api$ ln -s ../package.json

api$ ln -s ../pack-lock.json

api$ ln -s ../node_modules

api$ ln -s ../src/declarations.d.ts


Meteor 백앤드를 Typescript기반으로 개발하기 위해 다음 패키지를 설치한다.

api$ meteor add barbatus:typescript


// 별도 필요 패키지 설치 

$ npm install --save babel-runtime

$ npm install --save meteor-node-stubs

$ npm install --save meteor-rxjs


Typescript의 tsconfig.json 파일을 api 폴더안에 생성하고 다음 내역을 붙여넣는다.

{

  "compilerOptions": {

    "allowSyntheticDefaultImports": true,

    "declaration": false,

    "emitDecoratorMetadata": true,

    "experimentalDecorators": true,

    "lib": [

      "dom",

      "es2015"

    ],

    "module": "commonjs",

    "moduleResolution": "node",

    "sourceMap": true,

    "target": "es5",

    "skipLibCheck": true,

    "stripInternal": true,

    "noImplicitAny": false,

    "types": [

      "@types/meteor"

    ]

  },

  "exclude": [

    "node_modules"

  ],

  "compileOnSave": false,

  "atom": {

    "rewriteTsconfig": false

  }

}


Meteor의 server/main.js를 main.ts로 바꾼다. 위에서 설치한 meteor-rxjs는 클라이언트단의 Meteor를 RxJS Observable기반으로 사용할 수 있도록 한다.

// 예) Meteor 클라이언트단 Collection 구성

import { MongoObservable } from 'meteor-rxjs';


export const Chats = new MongoObservable.Collection('chats');




Meteor Client 준비


Meteor Server에 접속하기 위해 Meteor Client가 필요하기 때문에 하나의 bundler로 만들어 import해서 사용한다. 번들링도구를 설치한다.

$ sudo npm install -g meteor-client-bundler


// 필요 패키지를 설치

$ npm install --save-dev tmp


package.json 에 번들링 명령어를 설정하고, 번들링 명령을 수행한다.

// package.json

"scripts": {

...,

"meteor-client:bundle": "meteor-client bundle -s api"

}


// 번들링

$ npm run meteor-client:bundle


클라이언트 main.ts에 번들 내역을 import한다. 

// src/app/main.ts

import "meteor-client";




Ionic & Meteor 기동


Ionic 기동

$ ionic serve


Meteor 기동

$ cd api

api$ meteor


ionic HTTP는 8100이고, meteor client는 server에 websocket 3000 port로 접속을 한다.


posted by 윤영식
2013. 11. 25. 09:36 AngularJS/Start MEAN Stack

MEAN Stack을 가지고 서비스를 만드는데 사용을 하려면 어떤 순서로 익혀야 할까? 각각의 기술을 따로 배우는 것은 어차피 전문서적들이 있기 때문에 여기서 다시 상세히 설명하는 것은 의미가 없을 것 같고, 서비스를 기획, 개발, 런칭까지의 과정을 알아보자. 모든 과정의 내용은 웹 서비스쪽에 공개되어 공유될 것이다 




Sprint-1) Full Stack 개발자 되기

  - MEAN 개념과 개요

    + AngularJS

    + NodeJS + ExpressJS

    + MongoDB + Redis(Advanced)

  - MEAN 이외에 알아야 할 것들

    + JavaScript 

    + Bootstrap (or Zurb Foundation

    + NodeJS Modules

    + Mongoose



Sprint-1) 개발 준비 단계

  - 로컬 개발 환경 만들기 

    + Vagrant + Linux 환경 구성하기

    + 개발환경에 Node.js, MongoDB 설치하기 

    + Trello 에서 Scrum 개발하기 

  - MEAN Stack 개발도구 개발환경에 설치하고 익히기 

    + Yeoman : 클라이언트 라이브러리관리 및 배포 Grunt, Bower, Yo 설치하기 

    + NPM : 서버 라이브러리 관리

  - Git & GitHub 저장소 만들기 

    + Git Branch 전략 

    + GitHub 사용방법

  - 클라우드 환경에 배포하여 테스트 하기

    + Cloud Development(Nitrous.io) 를 통한 협업개발 및 테스트하기

    + Heroku같은 PaaS에 배포하여 테스트하기 

 


Sprint-1) MEANK Stack 개발 환경 구성하기

  - SRS 작성하기 

    + Software Request Requirement란 무엇인가?

    + Trello 에 소프트웨어 요구사항 정의서 작성하기 

    + Balsamiq 도구 사용하여 Wireframe & Mockup 그리기 화면 그리기

  - AngularJS Scaffolding 코드 사용하기 

    + angular-generator 알아보기 

    + angular 기본골격 만들기

    + NodeJS로 테스트 하기 

  - MEAN Stack 테스트 전략 

    + AngularJS에서의 테스트 방법 : Karma Framework

    + NodeJS에서의 테스트 방법 : Mocha 그외 



Sprint-2) 서비스 프로토타입 개발하기

  - 가장 중요한 화면 설계하기

    + 클라이언트 화면 Mockup 구체화 하기 

    + NodeJs에서 처리 할 RESTful API 설계하기 

    + MongoDB 에 저장 할 Document Schema 설계하기

  - MongoDB Store 개발 및 테스트

    + Dynamic Schema, Embedded vs Reference 에 대한 이해 

    + Mongoose 기반으로 Schema 만들기

    + Test Framework 이용한 Model 단위 테스트하기 

  - Node.js 기반 RESTful API 개발 및 테스트

    + RESTful API 코드 뼈대 만들기 in NodeJS

    + Test Framework 이용한 서비스 단위 테스트하기

    + RESTful Client 테스트 도구 또는 curl 로 호출 테스트하기

  - Angular.js 기반 화면 개발 및 테스트

    + AngularJS와 Bootstrap 이용하여 화면 구성하기

    + AngularJS 애플리케이션 구조 개발 및 RESTful API 서비스 개발 

    + Test Framework 이용한 화면 단위 테스트하기 

  - Heroku 같은 PaaS에 올려서 통합 테스트 하기 



Sprint-3) 서비스 개발 반복하기

  - 반복하기 

    + sprint-01에서 반복하였던 과정을 지속하기 

  - 메인 및 로그인 화면 만들기 

    + Bootstrap의 다양한 도구 활용

    + OAuth 기능 로그인 하기  

  - 채팅기능 만들기 

    + 채팅 Mockup 만들기

    + NodeJS Socket.io API 구현

    + MongoDB 채팅 Schema 설계

    + AngularJS Socket.io 멀티 케스팅 구현 

    + Bootstrap 통한 화면 구현 및 AngularJS 연동

 - Heroku 같은 PaaS에 올려서 프로토타입 테스트 하기 



Sprint-3) 서비스 런칭하기

  - 서비스 일일 빌드시스템 구성하기 

    + Travis (또는 Jenkins)기반 자동 배포 기능 만들기 

    + Travis 에서 빌드된 것을 클라우드시스템(PaaS) 자동 배포하기 

  - 런칭 페이지 만들기 

    + GitHub Pages를 이용한 런칭 만드는 방법

    + Bootstrap을 이용하여 런칭 페이지만들어 GitHub에 반영하기 

  - 모니터링 하기

    + 클라이언트 AngularJS Log 모니터링 하기
    + NodeJS Log 모니터링 하기 

    + MEAN Stack 운영 및 장애 모니터링 전략 



Next Project) MEAN Stack Advanced

  - 채팅기능 고도화하기 

    + SNS 서비스 아키텍쳐들 알아보기  

    + 대용량 데이터를 처리를 위한 아키텍쳐 설계하기

  - NodeJS + Redis + MongoDB 연동하기 

    + Redis 알아보기 

    + NodeJS-Redis 연동하기 

  - 정제된 데이터 RDBMS와 연동하기

    + NodeJS - MySQL 연동하기 

    + MongoDB의 MapReduce를 이용하여 데이터 마이닝하기 

    + NodeJS에서 MongoDB 마이닝 데이터 MySQL에 넣기 

  - SNS 서비스 운영 고도화

    + NodeJS와 Nginx 연동하기 

    + NodeJS를 Proxy 서버로 만들어 Clustering 하기

    + MongoDB Sharding 구조 만들기 

    + Redis Master/Slave구조 만들기 

  - 유지보수 전략

    + 테스트 환경과 프로덕션 환경의 재구성

    + NodeJS Scale-out 확장 전략

    + MongoDB Sharding 및 Data Backup 전략 

    + Redis Scale-out 전략 

  - 빅데이터의 활용

    + 개인화를 위한 데이터 활용 방안

    + MongoDB Integration Framework을 이용한 MapReducing


전체 서비스를 만들고 런칭하기까지 참 많은 것들을 익히고 개발해야 하지만, 누군가에게 의미있는 가치를 줄 수 있는 SNS(Social Network Service)를 할 수 있다는 것에 만족을 느낀다. 


posted by 윤영식
2013. 11. 20. 10:58 AngularJS/Start MEAN Stack

갑작이 떠오른 아이디어를 모바일 서비스로 빠르게 만들고 싶다. 클라이언트/서버/스토어 이런 것을 해줄 사람은 없다. 나 홀로 만들어 보고 프로토타입핑해서 스타팅해보고 싶을 때 MEAN Stack을 사용하자 




1. 스프링같은 "WebApp Framework" 에 대한 고민

  클라이언트단의 Android 또는 iOS 네이티브 코드는 익히는데 시간은 없고, 다양한 스마트 기기에 대응을 했으면 한다. 그리고 JavaScript를 어느 정도 할 수 있다. 이때 생각할 수 있는 대안이 "웹앱 사이트" 또는 "모바일 웹앱"을 만드는 것이다. 그러나 예전 jQuery 코드를 생각해 보자. 조금만 복잡해 지면 쉽게 스파게티가 되고, 테스트 코드는 엄두도 못내며 결국 유지보수의 골칫덩어리로 남게 된다. 

그렇다면 자바에서 Spring Framework과 같이 DI가 되면서 MVC처럼 모듈화 개발을 가능하게 해주는 클라이언트단 프레임워크는 없을까? 처음엔 Backbone.js를 살펴 보았고, 예로 Trello와 같은 서비스에서 사용을 하고 있다. 하지만 코드의 길이가 길어지고 불필요한 중복코드의 남발이 야기되어 좀 더 간결하면서 예전 Flex Framework과 같은 것은 없을지 고민해 보았다. HTML을 확장해서 사용해도 자동 해석을 하여 브라우져가 이해할 수 있는 HTML로 바꾸어 주면 되는 그런 프레임워크 말이다. 역시 구글 형님들도 똑같은 고민을 하고 프로젝트에 적용하고 놀라운 효과를 본 새로운 프레임워크를 공식 런칭하게 되니 이름하여 AngularJS





2. 새로운 "I/O Machine" 에 대한 고민

  서비스에서 요하는 기능중 가장 중요한 부분은 Push 기능이 아닐까 싶다. 실시간으로 변화하는 정보를 수많은 유저에게 바로 Push 서비스해줄 수 있는 서버가 필요하다. 하지만 Push를 위하여 별도의 Port를 사용하긴 싫고 웹앱이므로 요청하였던 HTTP Port를 사용하고 싶다면 어떤 것을 선택해야 할까?

서버단이 Java라면 vert.x도 고려해 볼 만하다. 하지만 아직 레퍼런스와 문서가 부족하다. 하지만 여기에 우리에게 구세주와 같은 세로운 I/O 머신이 나왔으니 V8엔진을 기반으로 돌아가는 Node.js이다. Node.js를 사용하는 가장 큰 목적중 하나가 Socket.io와 같은 이벤트 기반의 Push 모듈들이 아닐까 생각이든다. Node.js는 모듈기반으로 필요한 것들을 추가하여 사용할 수 있다. 이를 위하여 NPM(Node Package Manager)이 존재하고 방대한 모듈과 레퍼런스들이 존재한다. 또한 Chrome에 적용된 V8 자바스크립트 해석기를 달고 있으므로 모든 코딩은 JavaScript 만으로 이루어진다





3. 스키마에 자유로운 "Store"에 대한 고민 

  서비스를 빠르게 만들고 싶은데 개발을 진행하다 보면 자주 테이블이 변경되고 이에 맞추어 서버코드들도 수정을 해주거나 새롭게 테이블을 생성해 주는 번거로운 작업들을 거친다. 따라서 스키마가 자유로운 데이터베이스를 원한다. 그리고 서비스이므로 사용자의 데이터를 많이 쌓아 두고 싶다. 이를 통해 향후 마케팅 용으로 사용하려 한다. 이왕이면 클라이언트/서버가 모두 자바스크립트이므로 데이터베이스 핸들링도 자바스크립트 이면 좋겠다. 이를 위해 새롭게 나온 NoSQL 이 있으니 이름하여 MongoDB. JSON형태로 데이터를 저장하고 정규화 없이 도큐먼트 형태로 저장을 할 수 있어서 스키마의 제약이 없다. 또한 테라급의 데이터 정도는 MongoDB를 Sharding구조로 만들어 MapReduce를 이용하여 처리할 수 있다. 그리고 Node.js 처럼 V8 해석기를 탑재하여 데이터 핸들링을 JavaScript로 할 수 있다. 마치 Oracle의 SqlPlus에서 Sql을 수행하듯 MongoDB가 정의한 형식의 쿼리를 수행할 수 있다. 





4. Node.js를 기업용 WAS(Web Application Server)로 만들기  

  초창기 Java를 떠올려 보자. 처음엔 Applet에 열광하여 업무를 애플릿으로 만든적이 있다. 그때 서버단은 Apache + PHP정도 였을 것이다. 그리고 SUN에서 J2EE 스팩을 발표하면서 Tomcat, JBoss, WebSphere, WebLogic 같은 WAS가 나왔으며, 이 또한 C계열의 TP-Monitor처럼 Java기반 I/O Machine 이 나오게 된 것이다. Java진영의 WAS는 나름의 공통된 스팩을 기반으로 웹을 이끌어 갈 수 있는 새로운 I/O 머신으로 떠올랐고, 기업에서 요구하는 Transaction 처리에 대한 스팩도 제안하여 많이 사용하게 되었다. 최근에는 Tomcat같은 Servlet 엔진만 갖춘 WAS에 Spring Framework를 사용하면 충분히 기업용 Transaction을 처리하는 업무를 개발할 수 있는 환경까지 오게 되었다. 그러나 Java의 WAS가 web1.0의 중심 머신이었다면 Node.js기반의 WAS가 web2.0의 중심 머신으로 되기 위한 조건은 무엇일까?

  결국 Node.js위에 올라가는 Java의 J2EE 스팩과 같은 견고한 스팩이 필요하다. 현재까지는 아직 그러한 스팩이 나오진 않으나 Java의 Servlet 스팩에 비견할 만한 프레임워크가 나왔으니 그것이 Express.js 이다. 이제 web2.0의 I/O 머신은 Node.js가 될 것이고, 여기에 웹에 대한 표준 스팩구현체는-사실 Node.js를 위한 공통된 스팩은 없다- 아니지만 가장 많이 사용하는 Express.js를 통하여 Node.js가 Tomcat과 같은 기능을 보유하게 되었다.




Angular.js -> Node.js + Express.js -> MongoDB 를 사용하게 되면 

  - JavaScript로 모두 개발을 한다. 클라이언트/서버 분리하여 개발할 필요가 없다. 그냥 FullStack JavaScript개발을 하면 된다

  - 클라이언트 개발이 MV* 개발이 되어 서버 개발에 익숙한 개발자가 쉽게 접근할 수 있다 

  - 서버에서 Push 기능을 쉽게 적용할 수 있으므로 다양한 서비스의 응용이 가능하다 

  - 자바의 서블릿 엔진 구현체인 Tomcat과 같이 충실한 RESTful I/O 머신을 쉽게 구현할 수 있다

  - 서버의 멀티 쓰레드 문제에서 해방될 수 있다. Asynch의 세상에서 놀자 

  - 데이터 스키마를 미리 정의하지 않고 에자일하게 개발을 진행할 수 있다

  - 큰 규모의 데이터를 저장하고 처리 할 수 있다  



그러나 우리에게 남은 과제는 이것들을 어떻게 배우고 사용하는지 잘 모른다는 것이다. 각자를 심도 있게 배우는 것은 별도의 가이드북을 따로 보길 바란다. 대신 여기서는 실전 서비스를 어떻게 하면 빠르게 만들 수 있을지 노하우를 적어보려한다. 낮에는 회사에서 업무를 보면서 향후 스타트업을 준비하는 개발자 또는 사람들에게 좀 더 이로운 가치를 전달해 주고 싶은 개발자가 3개월안에 개발할 수 있는 서비스를 이 책을 통해 런칭할 수 있는 것이 가능해 질 수 있길 바란다. 그래서 책을 쓰면서 동시에 작은 서비스를 만들어 보려 한다. 책은 가이드이고 그물일 뿐이다. 실제 돌아가는 서비스가 물고기이다. 즉, 실제 서비스를 낚지 못하면 이 책의 가치도 없다고 생각한다. 실제로 이것을 증명해 보이고 싶다. 



Getting Start MEAN

          


posted by 윤영식
2013. 10. 10. 11:19 MongoDB/Concept

MongoDB가 나오게 된 이유와 개념/구성에 대하여 알아보자



어느 방향을 선택할 것인가

  - 500만 : trend

  - 1000만 : culture

  - 2000만 : 안끼면 소외된다 

  소셜네트워크가 IT와 만나고 다시 스마트폰을 만나면서 새로운 방향을 제시하였다. 이런 방향을 지원하기 위하여 많은 OSS(Open Source Software)가 나왔고, HTTP 통신(Stateless)을 통하여 유입되는 어마어마한 사용자를 수용하기(규모의 확장) 위하여 사람들의 생각을 저장하고 활용해야 하는 필요성이 커졌다. 사람의 생각을 Key=Value로 많은 데이터를(BigData) 저장할 수 있어야 한다. 이러한 사상은 예전의 Client-Server 사상이 아닌 HTTP Web상의 통합과 유입을 처리하는 SNS(Social Network Service), SND(Social Network Data), SNG(Social Network Game) 사상으로 가고 있다. SNS는 HTTP 프로토콜을 통하여 Big Data Stream을 만들어 Real Time으로 반응하는 것이다. 



기술의 흐름에 따른 선택

1) 전통적 웹기술

  - 전통 pc application 개발 기술

  - static 기술

  - dynamic 기술


3) 모바일 기술

  - embedded 기술 (고립된 기술) : iphone, android 등 mobile application 개발 기술 또는 Sencha 기술 

  - open api 기술 : web services 기술 


4) 웹앱(WebApp) 기술 : 3세대 웹

  - SPA(스타) : single page application 기술 (javascript, json)

  - MVC framework을 client 사이트로 내림 = Fluent MVC = Stream

    (스트림은 융합을 쉽게 이루어지게 한다. 예) 디지털TV, 무인자동차) 

    + client Controller는 없다. View가 Controller를 대신한다 

    + MOV : Model + Operation + View가 있다. 여기서 Operation = Functional 이 된다. (Functional Language, Async) 

    + DRY : Don't Repeatly Yourself 반복하지 않는 프레임워크    

    



  - SNS : Modern Web = Smart Device (TV, Phone등), 반대개념 일반 Web 임

  - Real Time 기술 : websocket 기술이 들어감 

  - Javascript가 소통 언어가 되고 JSON이 데이터 포멧이 되어 end-to-end에서 커뮤니케이션의 실체가 된다. 

    + 기존 ORM-Object Relational Mapping- 기술은 사용하지 말자~~

    + client : 메모리, 브라우져 사용

    + server : 모듈 서비스. 일반 모듈의 대표는 Node.js 이고, special module = DB module    

  - Stream의 통합 : Edge 통합

    + Facebook이 초창기 사용하였으나 지금은 웹앱, mongodb를 사용하지 않는다 (초기 기술 적용에 대한 실패일까?)


  - 최종 모습

    + client : Backbone, Handlebar(mustache)

    + server : Node.js, Redis(맵데이터=view테이블), MongoDB(MapReducing, NoSQL)

      MapReduce를 통하면 BI(Business Intelegence)를 구현한다

    + bigdata : 큰단위 - Hadoop + HBase,  작은단위 - Redis + MongoDB로 보아도 된다

    + Agile 개발 : Schemaless 로 에자일 개발 프로세스에 적합하다. 작은 변화에도 기만하게 반응할 수 있다

     


MongoDB 이해하기 

  - 기존 RDB

    + join = projection 

    + 성능 optimization(최적화)를 위하여 schema와 join이 중요함 : 정규화의 필요(normalization)   

    

 

  - 현재 상황 

    + 성능 최적화를 위하여 찢어 놓았던(normalization)것을 고객 서비스를 위하여 다시 합쳐야 한다

    + SQL문이 8Kbytes 가 넘어 가고 현재 무한히 SQL을 쓸 수 있지만 늪이 된다. (튜닝을 위하여 컨설턴트가 필요해짐)

   - MongoDB

    + Table = Collection

    + Tuple = Document    

    

    


MongoDB 구성

  - Replica Set을 기본으로 가져가자 : HTTP를 통하여 묶는다 (Primary-Secondary)

  - Fail-Over를 통하여 High Availability(HA)를 충족한다 

    

  

  - Scale-Out 확장을 통하여 무한히 대응할 수 있다

    + Sharding을 통하여 데이터를 분산하여 처리하고 그안에 Replica-Set이 구성된다 

    + mongos는 router 이다     

    


  - 전체 MongoDB 구조 

    + Config Server : Replica-set, Shard 구성

    + Replica-set : Fail-Over (primary, secondary)

    + Sharding : Data Scale-out 

    

  


SNS는 이제 RealTime Stream Data 시대

  - 하나로 보이지만 여러곳에서 데이터가 통합되어 온다. 

    하기 그림에서 영상 화면 하나가 여러 곳에서 데이터가 모여져서 보여지듯이.

  - mongoDB의 GridFS가 된다 

  

   


MongoDB 개념 및 용어 정리 


MongoDB는 Service로 접근해야 한다! (애플리케이션 접근 옳지 않아~~)



<참조>

  - .Net Framework Frontier Facebook Group

  - KTH  MongoDB 어떻게 사용할 것인가?

posted by 윤영식
2013. 9. 13. 21:19 MongoDB/Prototyping

MongoDB에 구글의 도서검색 내역을 넣고, 여기서 도서의 Description을 하둡으로 분석하여 추천도서를 만들어 보자 



구글도서에서 Description을 MongoDB에 저장하기 

  - 이클립스에서 Maven Project를 하나 생성하고, pom.xml 을 다음과 같이 구성한다 

    자바에서 몽고디비를 사용하기 위한 드라이버와 구글 검색결과(JSON)을 파싱하기위한 JSON라이브러리를 추가한다 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mobiconsoft</groupId>

  <artifactId>booksearch</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

  <dependency>

  <groupId>org.mongodb</groupId>

  <artifactId>mongo-java-driver</artifactId>

  <version>2.11.2</version>

  </dependency>


  <dependency>

  <groupId>junit</groupId>

  <artifactId>junit</artifactId>

  <version>4.10</version>

  </dependency>

 

  <dependency>

  <groupId>org.json</groupId>

  <artifactId>json</artifactId>

  <version>20090211</version>

  </dependency>

  </dependencies>

</project>

  - BookSearcher.java 코딩 (편의상 import 구문 제외)

public class BookSearcher {


  // 도서검색 

  public String searchBooks(String keyword) {

    URL url = null;

    try {

      url = new URL("https://www.googleapis.com/books/v1/volumes?q=" + keyword);

    } catch (MalformedURLException e) {

      e.printStackTrace();

    }

    

    StringBuffer sb = new StringBuffer();

    String line;

    try {

      URLConnection urlConn = url.openConnection();

      BufferedReader br = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), "utf-8"));

      while((line = br.readLine()) != null) sb.append(line);

    } catch(IOException e) {

      e.printStackTrace();

    }

    

    return sb.toString();

  }

  

  // 도서 검색 결과에서  items찾아와 저장한다 

  public void saveBooks(String books) {

    Mongo mongo = null;

    try{

      mongo = new MongoClient("localhost", 27017);

    } catch(Exception e) {

      e.printStackTrace();

      throw new RuntimeException();

    }

    

   // 몽고디비 db는 books-db 이고 컬렉션은 books 로 만들어짐 

    mongo.setWriteConcern(new WriteConcern(1, 2000));

    DB bookDB = mongo.getDB("books-db");

    DBCollection bookColl = bookDB.getCollection("books");

    

    try{

      JSONObject json = new JSONObject(books);

      JSONArray items = json.getJSONArray("items");

      for( int i=0; i<items.length(); i++) {

        DBObject doc = new BasicDBObject();

        // search-book key로 value가 들어간다

        doc.put("search-book", (DBObject)JSON.parse(items.getJSONObject(i).toString()));

        bookColl.save(doc);

      }

    } catch(JSONException e) {

      e.printStackTrace();

    }

  }

}

  - 테스트 해보자 

    JUnit 테스트전에 mongodb를 기동한다 

// 몽고디비 

$ ../bin/mongod -dbpath=/Users/dowon/Documents/mongodb/database


// 테스트 

public class BookSearcherTest {

  

    private BookSearcher bookSearcher;

    

    @Before

    public void setUp() throws Exception {

      this.bookSearcher = new BookSearcher();

    }


    // search 결과 보기 

    @Test

    public void testSearchBooks() throws Exception {

      String result = this.bookSearcher.searchBooks("nosql");

      //assertNotNull(result);

      System.out.println(result);

    }

    

    // 데이터 저장하기 

    @Test

    public void testSaveBooks() throws Exception {

      String result = this.bookSearcher.searchBooks("nosql");

      this.bookSearcher.saveBooks(result);

    }

}


// 테스트 성공후 mongo 쉘을 통하여 확인 

> use books-db

switched to db books-db

> show collections

books

system.indexes

> db.books.find().length();

10

> db.books.find()

{ "_id" : ObjectId("5232e69cda06561b2e11306c"), "search-book" : { "saleInfo" : { "saleability" : "NOT_FOR_SALE", "isEbook" : false, "country" : "KR" }, "id" : "tv5iO9MnObUC", "searchInfo" : { "textSnippet" : "They provide examples, practical solutions, and expert education in new technologies, all designed to help programmers do a better job. wrox.com Programmer Forums Join our Programmer to Programmer forums to ask and answer programming ..." }, "etag" : "HX8hesQgrJM", "volumeInfo" : { "pageCount" : 408, "averageRating" : 3, "infoLink" : "http://books.google.co.kr/books?id=tv5iO9MnObUC&dq=nosql&hl=&source=gbs_api", "printType" : "BOOK", "publisher" : "John Wiley & Sons", "authors" : [  "Shashank Tiwari" ], "canonicalVolumeLink" : "http://books.google.co.kr/books/about/Professional_NoSQL.html?hl=&id=tv5iO9MnObUC", "title" : "Professional NoSQL", "previewLink ... 중략 ...



MongoDB Hadoop Connector 사용하기  

  - 몽고디비와 하둡을 연결하는 방법을 제공한다 

     https://github.com/mongodb/mongo-hadoop 에서 1.1.x 의 Core 다운로드 한다 (mongo-hadoop-core_1.1.2-1.1.0.jar)

  - Input-Output으로 몽고디비를 사용할 경우   

    

 - 분석을 위하여 Pig, MR을 할 경우

    

  - ETL처럼 처리후 별도의 저장소로 던져질 경우

    ETL from MongoDB

      

    ETL to MongoDB

        


  - Eclipse에 새로운 Book Search Mapper와 Reducer 프로젝트를 만들고 pom.xml 을 만든다 

    mongo-hadoop-core  파일을 maven에 등록되어 있지 않기때문에 수동으로 .m2/repository에 만들어 주어야 한다 

    예)

    > 레파지토리 : /Users/dowon/.m2/repository

    > 파일위치 : mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mobiconsoft</groupId>

  <artifactId>booksearch_mapreduce</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

    <dependency>

      <groupId>org.apache.hadoop</groupId>

      <artifactId>hadoop-core</artifactId>

      <version>1.1.2</version>

    </dependency>

    

    <dependency>

      <groupId>org.mongodb</groupId>

      <artifactId>mongo-java-driver</artifactId>

      <version>2.11.2</version>

    </dependency>

    

    <!-- 수동 설정 --> 

    <dependency>

      <groupId>mongo-hadoop-core</groupId>

      <artifactId>mongo-hadoop-core_1.1.2</artifactId>

      <version>1.1.0</version>

    </dependency>

  </dependencies>

  

  <build>

    <plugins>

      <plugin>

        <artifactId>maven-antrun-plugin</artifactId>

        <configuration>

          <tasks>

            <copy file="target/${project.artifactId}-${project.version}.jar"

              tofile="/Users/dowon/Documents/input/${project.artifactId}-${project.version}.jar" />

          </tasks>

        </configuration>

        <executions>

          <execution>

            <phase>install</phase>

            <goals>

              <goal>run</goal>

            </goals>

          </execution>

        </executions>

      </plugin>

    </plugins>

  </build>

  

</project>

  - Mapper와 Reducer 클래스를 코딩 

// Mapper

public class BookSearchMapper extends Mapper<Object, BSONObject, Text, IntWritable> {

  

  private final static IntWritable ONE = new IntWritable();

  private Text word = new Text();

  

  protected void map(Object key, BSONObject value, Context context) 

    throws IOException, InterruptedException {

    BasicDBObject anItem = (BasicDBObject)value.get("search-book");

    BasicDBObject volumeInfo = (BasicDBObject)anItem.get("volumeInfo");

    String description = volumeInfo.getString("description");

    if(description == null || description.trim().length() <= 0) return;

    

    StringTokenizer st = new StringTokenizer(description);

    while(st.hasMoreTokens()) {

      word.set(st.nextToken());

      context.write(word, ONE);

    }

  }

}


// Reducer

public class BookSearcherReducer extends

  Reducer<Text, IntWritable, Text, IntWritable> {

  

  protected void reduce(Text key, Iterable<IntWritable> values, Context context) 

    throws IOException, InterruptedException {

    int sum = 0;

    for(final IntWritable value : values) sum += value.get();

    context.write(key, new IntWritable(sum));

  }

}

  - Job을 만든다

public class MongoJob extends MongoTool {

  static {

    Configuration.addDefaultResource("mongo-default.xml");

    Configuration.addDefaultResource("mongo-book.xml");

  }

  

  public static void main(String[] args) throws Exception {

    Configuration conf = new Configuration();

    

    JobHelper.addJarForJob(conf, "/Users/dowon/.m2/repository/mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar:"

          + "/Users/dowon/.m2/repository/org/mongodb/mongo-java-driver/2.11.2/mongo-java-driver-2.11.2.jar");

    

    System.exit(ToolRunner.run(conf, new MongoJob(), args));

  }

}

  - 리소스 xml을 만든다 

    https://github.com/mongodb/mongo-hadoop/blob/master/examples/treasury_yield/src/main/resources/mongo-defaults.xml 

   에서 xml 정보를 copy 하여 mongo-book.xml 을 만든 후 하기 내용을 수정하여 입력해야 한다

<property>

    <!-- Class for the mapper -->

    <name>mongo.job.mapper</name>

    <value>booksearch_mapreduce.BookSearchMapper</value>

  </property>

  <property>

    <!-- Reducer class -->

    <name>mongo.job.reducer</name>

    <value>booksearch_mapreduce.BookSearcherReducer</value>

  </property>

  <property>

    <!-- InputFormat Class -->

    <name>mongo.job.input.format</name>

    <value>com.mongodb.hadoop.MongoInputFormat</value>

  </property>

  <property>

    <!-- OutputFormat Class -->

    <name>mongo.job.output.format</name>

    <value>com.mongodb.hadoop.MongoOutputFormat</value>

  </property>

  <property>

    <!-- Output key class for the output format -->

    <name>mongo.job.output.key</name>

    <value>org.apache.hadoop.io.Text</value>

  </property>

  <property>

    <!-- Output value class for the output format -->

    <name>mongo.job.output.value</name>

    <value>com.mongodb.hadoop.io.BSONWritable</value>

  </property>

  <property>

    <!-- Output key class for the mapper [optional] -->

    <name>mongo.job.mapper.output.key</name>

    <value>org.apache.hadoop.io.Text</value>

  </property>

  <property>

    <!-- Output value class for the mapper [optional] -->

    <name>mongo.job.mapper.output.value</name>

    <value>org.apache.hadoop.io.IntWritable</value>

  </property>

  <property>

    <!-- Class for the combiner [optional] -->

    <name>mongo.job.combiner</name>

    <value>booksearch_mapreduce.BookSearcherReducer</value>

  </property>

  - "Mave Build..." clean install 하여 .jar  파일을 만든다 (참조에 첨부파일)

 - 다음 하둡 runtime(start-all.sh) 을 수행한다 

  - 하둡 수행 쉘을 만든다 

//////////////////////

// mongodb.sh 내역 

#!/bin/sh


export REPO=/Users/dowon/.m2/repository

export MONGO_DRIVER=$REPO/org/mongodb/mongo-java-driver/2.11.2/mongo-java-driver-2.11.2.jar

export MONGO_HADOOP=$REPO/mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar

export HADOOP_CLASSPATH=$MONGO_DRIVER:$MONGO_HADOOP

export HADOOP_USER_CLASSPATH_FIRST=true


hadoop jar booksearch_mapreduce-0.0.1-SNAPSHOT.jar booksearch_mapreduce.MongoJob



/////////////

/// 수행하기 

$ mongodb.sh

2013-09-13 20:53:24.630 java[1431:1203] Unable to load realm info from SCDynamicStore

13/09/13 20:53:24 INFO util.MongoTool: Created a conf: 'Configuration: core-default.xml, core-site.xml, mongo-default.xml, mongo-book.xml, mapred-default.xml, mapred-site.xml' on {class booksearch_mapreduce.MongoJob} as job named '<unnamed MongoTool job>'

13/09/13 20:53:24 INFO util.MongoTool: Mapper Class: class booksearch_mapreduce.BookSearchMapper

13/09/13 20:53:24 INFO util.MongoTool: Setting up and running MapReduce job in foreground, will wait for results.  {Verbose? false}

13/09/13 20:53:25 INFO util.MongoSplitter: MongoSplitter calculating splits

13/09/13 20:53:25 INFO util.MongoSplitter: use range queries: false

.. 중략 ...

13/09/13 20:53:41 INFO mapred.JobClient:     Spilled Records=1428

13/09/13 20:53:41 INFO mapred.JobClient:     Map output bytes=14049

13/09/13 20:53:41 INFO mapred.JobClient:     Total committed heap usage (bytes)=269619200

13/09/13 20:53:41 INFO mapred.JobClient:     Combine input records=1299

13/09/13 20:53:41 INFO mapred.JobClient:     SPLIT_RAW_BYTES=195

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce input records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce input groups=714

13/09/13 20:53:41 INFO mapred.JobClient:     Combine output records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce output records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Map output records=1299



MongoDB에서 결과값 확인하기 

  - 브라우져에서 결과값을 확인하고 싶다면 몽고디비에 옵션으로 --rest 를 주면 28017 포트로 RESTful 하게 호출할 수 있다 

 $ ./bin/mongod -dbpath=/Users/dowon/Documents/mongodb/database --rest 

  - 결과 화면 

    결과값은 out 컬렉션에 생성이 된다


<참조>

  - 검색 책 정보 

books.json


  - 이클립스 project workspace

booksearch.tar


  - 하둡기동후 수행하는 쉘 

mongodb.sh


  - 반출한 booksearch mapreducer jar 파일 

booksearch_mapreduce-0.0.1-SNAPSHOT.jar






posted by 윤영식
2013. 9. 12. 20:45 Big Data

몽고디비를 하둡의 Input/Output의 Store를 사용하면 어떨까? 어차피 몽고디비는 Document Store 이며 Scale-Out을 위한 무한한 Sharding(RDB의 Partitioning) 환경을 제공하니 충분히 사용할 수 있을 것이다. Store에 저장된 데이터의 Batch Processing Engine으로 하둡을 사용하면 될 일이다. 




Mongo-Hadoop Connector 소개

  - Hadoop을 통하면 Mongo안에 있는 데이터를 전체 코어를 사용하면서 병렬로 처리할 수 있다 

  - 하둡포멧으로 Mongo를 BSON format을 파일로 저장하거나, MongoDB에 바로 저장할 수 있는 Java API존재

  - Pig + Hive를 사용할 수 있음 

  - AWS의 Amazon Elastic MapReduce 사용



Batch Processing Model 종류
  - 사실 MongoDB에서도 Aggregation Framework을 제공하여 MapReduce프로그래밍을 JavaScript로 개발 적용할 수 있다다
  - 시간단위 Batch Processing은 요렇게도 사용할 수 있겠다 


  - 데이터가 정말 Big 이면 하둡을 이용하여 Batch Processing을 해야겠다. 여기서 몽고디비를 "Raw Data Store" 와 "Result Data Store"로 사용한다 


  - MongoDB & Hadoop : Batch Processing Model 전체 내역을 보자 



<참조>

  - 결국 처리된 데이터는 표현되어야 한다 : Data Visualization Resources

  - MongoDB 넌 뭐니? NoSQL에 대한 이야기 (조대협)

posted by 윤영식
2013. 9. 4. 14:05 AngularJS/Start MEAN Stack

Mobile 서비스 개발을 위하여 JavaScript 기반의 기술 스택을 선택하여 사용하다 보니 1년사이에 MongoDB, Express.js, Angular.js, Node.js를 공부하고 솔루션에 적용하게 되었다. 그간 해당 기술들을 추상화한 프레임워크들을 사용하여 개발을 진행해 왔다. 그러나 가끔 풀리지 않는 문제에 대한 근원적인 해결책을 찾는데는 다시 처음부터 추상화 내역에 대한 이해가 바탕이 되지 않으면 풀리지 않았다. 


즉, 기본 스택을 추상화한 스택사용시에는 추상화 영역에 대한 사용경험과 전반적인 이해가 없이는 오히려 시간공수만 더 늘어나는 격이 된다. 예로 Express와 MongoDB를 좀 더 수월하게 사용키위하여 Trains, Sails Framework들을 사용해 보았고 나름의 장점은 있으나 왠지 남의 옷을 입은듯한 느낌이랄까? 문제 발생시 빠른 대응이 어려웠다 (개인적으론 Trains Framework의 Node.js단의 IoC 방식을 좋아한다)


따라서 소규모 팀으로 개발하면서 하나의 가치를 빠른 시간안에 전달하고자 한다면 기본 Stack기반으로 진행하면서 필요한 시점에 스스로 추상화 모듈을 만들어 사용하는 방법이 좋지 않을까 생각한다. (Don't Invent Wheel 이니 초기엔 쓸만한 것을 GitHub에서 찾아 응용하고 없으면 개발하자) 그런 의미에서 mean.io 에서 기본 Stack에 충실하면서 현재 사용되는 최신 개발 기술들 - Jade, Bootstrap, Mongoose, Bower, Grunt 같은 - 도 함께 접목되어 적당히 어려운 상태이므로 개발시 집중(Flow)을 가능케 하는 구조를 가지고 있다. (너무 어려우면 흥미를 읽고, 너무 쉬우면 긴장감이 떨어진다. 적당히 어려우면 흥미와 긴장감을 주어 개발시 집중을 가능케 한다) 



1. 설치

  - 홈페이지에서 zip 파일 다운로드 

  - 프로젝트로 이동해서 

$ cd mean


// 사전에 node.js 설치 

// Node, npm 설치 shell

$ npm install .

 

// 사전에 mongodb 설치하고 start

$ grunt



2. 접속 

  - http://localhost:3000/

    + Sign Up 할 수 있다

    + 테스트 글을 CRUD 할 수 있다 

    


  - MongoDB 

> show collections

articles

sessions

system.indexes

users


해당 스택을 통해 SPA(Single Page Application) 개발에 대한 Lean(신속한) 스타트업을 시도하자.

 


<참조>

  - mean.io

posted by 윤영식
2013. 8. 12. 17:39 Dev Environment/Docker

가상머신 관리도구인 Vagrant를 통하여 Node.js + MongoDB 개발환경을 구축해 본다.



1. 설치 

  - VirtualBox 다운로드 및 설치

  - Vagrant 다운로드 및 설치

  - Vagrant 환경 설정

    + 프로젝트 디렉토리를 하나 만든다. 또는 기존 Project가 있으면 디렉토리로 이동한다. VirtualBox에 원하는 이미지를 다운로드하여 설치한다. 이미지는 Vagrant에서 패키징한 Box를 다운로드할 수 있는 별도 사이트 제공한다 

    + Box는 기본설정과 OS가 설치된 VM 템플릿 이미지이다 

// 형식 : vagrant box add [title] [download-url] 

$ vagrant box add centos64 http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20130427.box

Downloading or copying the box...

    + 프로젝트를 초기화 한다

vagrant init centos64


// 결과 : 환경설정 파일 1개 생성됨 

Vagrantfile 



2. 가상머신 기동

  - Vagrant 통해 가상머신 기동하기 

// 기동

$ vagrant up

Bringing machine 'default' up with 'virtualbox' provider...

[default] Importing base box 'centos64'...

[default] Matching MAC address for NAT networking...

[default] Setting the name of the VM...

[default] Clearing any previously set forwarded ports...

[default] Creating shared folders metadata...

[default] Clearing any previously set network interfaces...

[default] Preparing network interfaces based on configuration...

[default] Forwarding ports...

[default] -- 22 => 2222 (adapter 1)

[default] Booting VM...

[default] Waiting for VM to boot. This can take a few minutes.

[default] VM booted and ready for use!

[default] Mounting shared folders...

[default] -- /vagrant


// VirtualBox VM이 자동으로 수행된 것을 볼 수 있다 

// VM 들어가기 : 같은 디렉토리면 ssh를 n개까지 open 가능 

// ssh를 통하여 별도의 VM 으로 들어갈 수가 있는 것이다. 

// 단, vagrant init [title] 된 Vagrantfile 파일이 같은 디렉토리에 있어야 함

$ vagrant ssh

Welcome to your Vagrant-built virtual machine.

[vagrant@localhost ~]$ 


// 정지

$ vagrant halt



3. Node.js & MongoDB, etc 개발환경 구축하기 

  - 제일 먼저 yum update 수행, 프로젝트 파일과 관계없는 운영 미들웨어 및 데이터베이스 설정이다. 

  - CentOS 64bit Node.js 설치하기 

    + 컴파일해서 설치함

  - CentOS 64bit MongoDB 설치하기 

  - CentOS 64bit Git 설치하기

  - Yeoman work flow 환경도 설치

    + yeoman, grunt, bower 설치 : sudo npm install -g yo grunt-cli bower

    + yeoman generator 설치 : sudo npm install -g generator-webapp

  - Sails.js Framework 기반 개발을 위하여 설치

    + sudo npm install -g sails@0.8.9

    + 버전 지정안하면 최신 버전인 0.9.5 설치됨



4. 애플리케이션 활용하기 

  - 개발환경을 구축하고 자신의 로컬머신에 있는 프로젝트 파일을 VM에도 설치해야 하는가?

    재배포 필요없이 로컬에 있는 파일을 VM에 sync folder 기능을 이용하여 Share 할 수 있다 

  - 프로젝트 파일 공유 : Vagrantfile 내역 (참조)

// 형식 

config.vm.synced_folder "[내 로컬머신의 디렉토리 절대경로]", "[VM에 로딩할 절대경로와 가상디렉토리명 지정]"


// 설정 예

config.vm.synced_folder "/Users/development/smart-solutions/SmartStatistics", "/home/vagrant/SmartStatistics"


// VM reloading 및 결과 

/Users/development/smart-solutions/SmartStatistics> vagrant reload

[default] Attempting graceful shutdown of VM...

[default] Setting the name of the VM...

.. 중략 ..

[default] Mounting shared folders...

[default] -- /vagrant

[default] -- /home/vagrant/SmartStatistics


/Users/development/smart-solutions/SmartStatistics> vagrant ssh

Last login: Mon Aug 12 08:09:35 2013 from 10.0.2.2

Welcome to your Vagrant-built virtual machine.

[vagrant@localhost ~]$ sudo iptables -F

[vagrant@localhost ~]$ cd SmartStatistics/

[vagrant@localhost SmartStatistics]$ pwd

/home/vagrant/SmartStatistics

  - 로컬과 VM의 파일을 서로 Share하였고 서버를 뛰우면 VM에서도 동일 Port로 뜰 것이다. 예) Sails는 default 1337 port를 사용한다 

     VM은 1337 port 를 사용하고 로컬 머신은 1338 port를 사용해서 port forwarding을 한다. 

     즉, 로컬 머신의 브라우져에서 http://localhost:1338 호출하면 VM의 1337 port를 통하여 서비스가 이루어진다(Port Forwarding)

  - 포트 충돌 해결 : Vagrantfile 내역 (참조)

// 형식 

config.vm.network :forwarded_port, guest: [VM에서 사용할 port번호], host: [내 로컬머신에서 사용할 port 번호]


// 설정 예 

config.vm.network :forwarded_port, guest: 1337, host: 1338


// VM reloading 및 결과

/Users/development/smart-solutions/SmartStatistics> vagrant reload

[default] Attempting graceful shutdown of VM...

[default] Setting the name of the VM...

[default] Forwarding ports...

.. 중략 ..

[default] -- 22 => 2222 (adapter 1)

[default] -- 1337 => 1338 (adapter 1)

[default] Booting VM...

[default] Mounting shared folders...

[default] -- /vagrant

[default] -- /home/vagrant/SmartStatistics

 

- vagrant VM

[vagrant@localhost SmartStatistics]$ netstat -na | grep 1337

tcp        0      0 0.0.0.0:1337                0.0.0.0:*                   LISTEN


- local my machine

/Users/development/smart-solutions/SmartStatistics> netstat -na|grep 1338

tcp4       0      0  *.1338                 *.*                    LISTEN

  - 테스트 수행 : port forwarding이 안될 경우 "sudo iptables -F" 를 통하여 강제 재설정을 해준다. 그리고 다시 curl 수행하여 체크 

// curl 이용하여 호출을 했는데 결과값이 나오지 않으면 iptables 에 대한 설정을 해준다 

/Users/development/smart-solutions/SmartStatistics> curl -v http://localhost:1338/

* About to connect() to localhost port 1338 (#0)

*   Trying ::1...

* Connection refused

*   Trying 127.0.0.1...

* connected

* Connected to localhost (127.0.0.1) port 1338 (#0)


// vagrant ssh (port forwarding이 안될 경우)

// 하기 명령을 .bash_profile 에 넣어서 자동화 한다 

/Users/development/smart-solutions/SmartStatistics> vagrant ssh

Last login: Mon Aug 12 08:09:35 2013 from 10.0.2.2

Welcome to your Vagrant-built virtual machine.

[vagrant@localhost ~]$ sudo iptables -F

  - 로컬 머신의 브라우져에서 호출 : "http://localhost:1338"



5. Package 만들기 

  - 기존에 쓰던 VM 이미지를 Vagrant의 Box로 만들어서 개발환경을 미리 패키징할 수 있다  

// 형식 : vagrant package --base <target> --output <output>.box



6. Provisioning 하기

   

  - Vagrant up 수행시 최초 실행되는 매크로 관리 도구인 Chef를 사용한다

  - Chef Solo Provisioning 을 하면 Chef Server가 필요없이 사용할 수 있다 

  - Opscode Cookbooks 에서 원하는 receipe 를 내려받아서 설정해 놓으면 자동 실행된다 



<참조>

  - Vagrant 설치 및 기동

  - SKPlanet의 Vagrant 설치 및 자료

  - Vagrant, Chef 살펴보기 

  - Chef Server 설치하기 튜토리얼

  - KTH의 Chef 블로깅

    


posted by 윤영식
2013. 8. 3. 17:52 MongoDB/MapReduce

10Gen에서 제공하는 Aggregation Framework에 대한 동영상 소개 예제를 실습해 본다 



1. 준비 

  - 10Gen 예제

  - http://media.mongodb.org/zips.json  데이터를 mongoimport 명령을 이용하여 넣기 

// zip.json 예제 mongoimport 

$ mongoimport --db bigdata --collection zipcodes --file zip.json

connected to: 127.0.0.1

Sat Aug  3 14:55:27.002 Progress: 1919717/2871006 66%

Sat Aug  3 14:55:27.002 19700 6566/second

Sat Aug  3 14:55:28.258 check 9 29470

Sat Aug  3 14:55:28.259 imported 29470 objects


// mongo shell

> use bigdata

switched to db bigdata

> show collections

system.indexes

zipcodes



2. 간단 수행

  - $group -> $match로 pipeline stream 처리

  - aggregate 메소드에 {} 객체를 넣고 콤마(,) 로 구분하면 왼쪽-> 오른쪽으로 pipeline 됨  

db.zipcodes.aggregate(

         { $group :

                         { _id : "$state",

                           totalPop : { $sum : "$pop" } } 

         },

         { $match : 

                         {totalPop : { $gte : 10*1000*1000 } }

         } );


SELECT state, SUM(pop) AS totalPop FROM zips GROUP BY state HAVING totalPop > (10*1000*1000)



3. 동영상 따라쟁이

  - 10Gen 동영상 예제


  - 샘플 수행 순서 : 일반 operator -find()같은- 와 aggregation framework operator 비교 수행 

 

  - $match == find와 같은 결과 그러나 다른 페러다임에서 처리된다는게 함정!

> db.zipcodes.aggregate({$match: {state:'NY', pop: {$gt:110000}}});

{

"result" : [

{

"city" : "BROOKLYN",

"loc" : [

-73.956985,

40.646694

],

"pop" : 111396,

"state" : "NY",

"_id" : "11226"

}

],

"ok" : 1

}


  - $match 후에 pipeline을 통하여 $sort 하기 : 단지 콤마(,)로 구분하면 된다 

> db.zipcodes.aggregate({$match: {state:'NY'}}, {$sort: {pop:-1}});


  - $match | $sort | $limit | $skip pipeline stream 처리 

> db.zipcodes.aggregate({$match: {state:'NY'}}, {$sort: {pop:-1}}, {$limit:5}, {$skip: 2});

{

"result" : [

{

"city" : "NEW YORK",

"loc" : [

-73.968312,

40.797466

],

"pop" : 100027,

"state" : "NY",

"_id" : "10025"

},

{

"city" : "JACKSON HEIGHTS",

"loc" : [

-73.878551,

40.740388

],

"pop" : 88241,

"state" : "NY",

"_id" : "11373"

},

{

"city" : "BROOKLYN",

"loc" : [

-73.914483,

40.662474

],

"pop" : 87079,

"state" : "NY",

"_id" : "11212"

}

],

"ok" : 1

}


  - 데이터에 $ 부호 사용하기 : 기존 document에는 population 필드가 존재하지 않는다 

> db.zipcodes.aggregate({$limit:1}, {$project: {city:1, state:1, pop:1, population:'$pop'}});

{

"result" : [

{

"city" : "ACMAR",

"pop" : 6055,

"state" : "AL",

"_id" : "35004",

"population" : 6055

}

],

"ok" : 1

}


$project가 하는 일은 무엇이 있을까?

- 필드 넣기 : Include fields from the original document.

- 계산된 필드 삽입 : Insert computed fields.

- 필드 이름 변경 : Rename fields.

- 서브도큐멘트에 대한 생성과 조작 : Create and populate fields that hold sub-documents.


  - 데이터에 $를 사용해 연산하여 결과를 얻기 

> db.zipcodes.aggregate({$limit:1}, {$project: {city:1, state:1, pop:1, population:'$pop', popSquared: {$multiply: ['$pop', '$pop']}} });

{

"result" : [

{

"city" : "ACMAR",

"pop" : 6055,

"state" : "AL",

"_id" : "35004",

"population" : 6055,

"popSquared" : 36663025

}

],

"ok" : 1

}

> 6055*6055

36663025


  - 기존 필드에 sub document 넣기  .' ' 사용한다 

> db.zipcodes.aggregate({$limit:1}, {$project: {city:1, state:1, population:'$pop', 'pop.Squared': {$multiply: ['$pop', '$pop']}} });

{

"result" : [

{

"city" : "ACMAR",

"pop" : {

"Squared" : 36663025

},

"state" : "AL",

"_id" : "35004",

"population" : 6055

}

],

"ok" : 1

}


  - pipeline 해보자  : $group -> $sort -> $limit 

> db.zipcodes.aggregate( {$group: {_id: '$state', pop: {$sum: '$pop'}}}, {$sort: {pop:-1}}, {$limit:3});

{

"result" : [

{

"_id" : "CA",

"pop" : 29760021

},

{

"_id" : "NY",

"pop" : 17990455

},

{

"_id" : "TX",

"pop" : 16986510

}

],

"ok" : 1

}


// 하나의 document마다 모든 city가 다 포함되어 결과 검출됨 (실습해 보삼^^)

> db.zipcodes.aggregate( {$group: {_id: '$state', pop: {$sum: '$pop'}, cities:{$addToSet: '$city'}}}, {$sort: {pop:-1}}, {$limit:3});


  - _id를 조합한 형태로 Pipeline 해보기 : $group -> $sort -> $limit 

> db.zipcodes.aggregate(

{$group: 

    {

        _id: {city: '$city', state: '$state'}, 

        pop: {$sum: '$pop'}, 

        avgPop: {$avg: '$pop'}

    }

},  

{$sort: {pop: -1}}, 

{$limit: 1}

)

// 결과 

{

"result" : [

{

"_id" : {

"city" : "CHICAGO",

"state" : "IL"

},

"pop" : 2452177,

"avgPop" : 52173.97872340425

}

],

"ok" : 1

}



<참조>

  - 예제 json 파일

zip.json

  - DZon의 Aggregation Framework 소개 기사

'MongoDB > MapReduce' 카테고리의 다른 글

[MongoDB] Aggregation Framework 이해하기  (0) 2013.08.03
[MongoDB] GridFS 사용하기  (0) 2013.02.23
[MongoDB] GridFS 개념잡기  (0) 2013.02.23
posted by 윤영식
2013. 7. 27. 16:06 MongoDB/Prototyping

몽고디비에서 Shard 1개 ( = ReplicaSet 1개)를 구성하였다면 이제는 n개의 Shard를 구성하여 mongos를 통하여 연결해 본다




1. Shard n개 구성하기

  - Shard Server 1개에 mongod 1개만 만듦 (mongod n개 구성은 다음 블로깅에서)

  - db11, db22, db33 디렉토리를 미리 만들어 놓는다

/mongodb_2.4.5> mongod --shardsvr --dbpath /mongodb_2.4.5/db11 --port 10000
Sat Jul 27 15:15:40.854 [initandlisten] MongoDB starting : pid=71105 port=10000 dbpath=/mongodb_2.4.5/db11 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 15:15:41.141 [initandlisten] command local.$cmd command: { create: "startup_log", size: 10485760, capped: true } ntoreturn:1 keyUpdates:0  reslen:37 271ms
Sat Jul 27 15:15:41.141 [websvr] admin web console waiting for connections on port 11000
Sat Jul 27 15:15:41.142 [initandlisten] waiting for connections on port 10000


/mongodb_2.4.5> mongod --shardsvr --dbpath /mongodb_2.4.5/db22 --port 20000
Sat Jul 27 15:16:38.842 [initandlisten] MongoDB starting : pid=71106 port=20000 dbpath=/mongodb_2.4.5/db22 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 15:16:39.105 [websvr] admin web console waiting for connections on port 21000
Sat Jul 27 15:16:39.105 [initandlisten] waiting for connections on port 20000


/mongodb_2.4.5> mongod --shardsvr --dbpath /mongodb_2.4.5/db33 --port 30000
Sat Jul 27 15:18:04.778 [initandlisten] MongoDB starting : pid=71120 port=30000 dbpath=/mongodb_2.4.5/db33 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 15:18:05.031 [websvr] admin web console waiting for connections on port 31000
Sat Jul 27 15:18:05.031 [initandlisten] waiting for connections on port 30000



2. Config 환경 구성하기

   - config1, config2, config3 디렉토리를 미리 만들어 놓는다

/mongodb_2.4.5> mongod --help
.. 중략 ..
Master/slave options (old; use replica sets instead):
  --master              master mode
  --slave               slave mode
  --source arg          when slave: specify master as <server:port>
  --only arg            when slave: specify a single database to replicate
  --slavedelay arg      specify delay (in seconds) to be used when applying
                        master ops to slave
  --autoresync          automatically resync if slave data is stale

Replica set options:
  --replSet arg           arg is <setname>[/<optionalseedhostlist>]
  --replIndexPrefetch arg specify index prefetching behavior (if secondary)
                          [none|_id_only|all]

Sharding options:
  --configsvr           declare this is a config db of a cluster; default port
                        27019; default dir /data/configdb
  --shardsvr            declare this is a shard db of a cluster; default port
                        27018


  - config 서버 기동하기

/mongodb_2.4.5> mongod --configsvr --dbpath /mongodb_2.4.5/config1 --port 40000
Sat Jul 27 15:22:12.404 [initandlisten] MongoDB starting : pid=71141 port=40000 dbpath=/mongodb_2.4.5/config1 master=1 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 15:22:12.544 [initandlisten] creating replication oplog of size: 5MB...
Sat Jul 27 15:22:12.547 [initandlisten] ******
Sat Jul 27 15:22:12.547 [websvr] admin web console waiting for connections on port 41000
Sat Jul 27 15:22:12.547 [initandlisten] waiting for connections on port 40000



3. Mongos로 Shard 제어하기

  - 명령어 : mongos <config server>

/mongodb_2.4.5> mongos --help
.. 중략 ..
Sharding options:
  --configdb arg      1 or 3 comma separated config servers
  --localThreshold arg 

                              ping time (in ms) for a node to be considered local (default 15ms)
  --test                   just run unit tests
  --upgrade             upgrade meta data version
  --chunkSize arg     maximum amount of data per chunk (기본 : 64kbytes)
  --ipv6                   enable IPv6 support (disabled by default)
  --jsonp                 allow JSONP access via http (has security implications)
  --noscripting          disable scripting engine


  - mongos 연결하기

/mongodb_2.4.5> mongos --configdb localhost:40000 --chunkSize 1 --port 50000
Sat Jul 27 15:31:45.926 [mongosMain] options: { chunkSize: 1, configdb: "localhost:40000", port: 50000 }
Sat Jul 27 15:31:45.929 [Balancer] about to contact config servers and shards
Sat Jul 27 15:31:45.929 [websvr] admin web console waiting for connections on port 51000
Sat Jul 27 15:31:45.929 [Balancer] config servers and shards contacted successfully
Sat Jul 27 15:31:45.929 [mongosMain] waiting for connections on port 50000
Sat Jul 27 15:31:45.932 [Balancer] distributed lock 'balancer/nulpulum-mac-13-retina.local:50000:1374906705:16807' acquired, ts : 51f36951133d3c7f4fe7b076
Sat Jul 27 15:31:45.932 [Balancer] distributed lock 'balancer/nulpulum-mac-13-retina.local:50000:1374906705:16807' unlocked.



4. REPL로 데이터 제어하

  - mongo로 들어가면 mongos 프롬프트가 나온다

mongodb_2.4.5> mongo localhost:50000
MongoDB shell version: 2.4.5
connecting to: localhost:50000/test
mongos>

mongos> show dbs
admin    (empty)
config    0.046875GB


  - admin db로 들어가서 config server에 shard환경을 구성한다

mongos> show dbs
admin    (empty)
config    0.046875GB
mongos> use admin
switched to db admin
mongos> show collections
mongos> db
admin
mongos> db.runCommand({ addShard: 'localhost:10000' });
{ "shardAdded" : "shard0000", "ok" : 1 }
mongos> db.runCommand({ addShard: 'localhost:20000' });
{ "shardAdded" : "shard0001", "ok" : 1 }
mongos> db.runCommand({ addShard: 'localhost:30000' });
{ "shardAdded" : "shard0002", "ok" : 1 }


  - 동적으로 mongod를 Shard에 포함시키기

/////////////// --shardsvr 옵션없이 mongod를 띄운다

/mongodb_2.4.5> mongod --dbpath /mongodb_2.4.5/db111 --port 60000
Sat Jul 27 15:43:48.510 [initandlisten] MongoDB starting : pid=71193 port=60000 dbpath=/mongodb_2.4.5/db111 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 15:43:48.542 [websvr] admin web console waiting for connections on port 61000
Sat Jul 27 15:43:48.542 [initandlisten] waiting for connections on port 60000
Sat Jul 27 15:43:57.418 [initandlisten] connection accepted from 127.0.0.1:51723 #1 (1 connection now open)


/////////////// runCommand를 수행한다

mongos> show collections
mongos> db.runCommand({ addShard: 'localhost:60000' });
{ "shardAdded" : "shard0003", "ok" : 1 }


/////////////// mongos 의 system out 출력값
Sat Jul 27 15:43:57.419 [conn1] going to add shard: { _id: "shard0003", host: "localhost:60000" }



5. 데이터 Sharding 하기

  - mongos를 통하여 데이터를 Sharding하여 나누어서 넣는 방법 : 한곳으로 데이터가 들어갈때 트래픽이 감당하기 힘들때 다른 샤드로 데이터를 보낸다 (10gen)

mongos> use dowonDB
switched to db dowonDB
mongos> db
dowonDB
mongos> db.person.save({age:1, name:'youngsik', address:'seoul'});
mongos> db.person.find();
{ "_id" : ObjectId("51f36cfb065189f0c02620d4"), "age" : 1, "name" : "youngsik", "address" : "seoul" }
mongos> use admin
switched to db admin
mongos> db
admin
mongos> db.runCommand({ enablesharding: 'dowonDB' })
{ "ok" : 1 }
mongos> db.runCommand({ shardcollection: 'dowonDB.person', key:{_id:1} })
{ "collectionsharded" : "dowonDB.person", "ok" : 1 }


  - 데이터 넣어서 샤드 점검하기 : 여러 mongod 서버로 데이터가 나뉘어서 들어갈 것이다. 즉, mongos가 로드발샌싱하여 데이터를 라우팅하여 줄 것이다. (5백만건 넣기)

mongos> use dowonDB
switched to db dowonDB
mongos> db
dowonDB
mongos> for(var i=0; i < 5000000 ; i++) { db.person.save({ age: i, name:i+9, address: i+1000}); };

mongos> db.person.find().size();
5000001

mongos> db.person.find().skip(1000000).limit(10);
{ "_id" : ObjectId("51f36fc4065189f0c037d1d1"), "age" : 1159420, "name" : 1159429, "address" : 1160420 }
{ "_id" : ObjectId("51f36f8d065189f0c02b7e4c"), "age" : 351607, "name" : 351616, "address" : 352607 }
{ "_id" : ObjectId("51f37031065189f0c04dc534"), "age" : 2597983, "name" : 2597992, "address" : 2598983 }
{ "_id" : ObjectId("51f36fab065189f0c032416e"), "age" : 794777, "name" : 794786, "address" : 795777 }
{ "_id" : ObjectId("51f36fc4065189f0c037d1d2"), "age" : 1159421, "name" : 1159430, "address" : 1160421 }
{ "_id" : ObjectId("51f36f8d065189f0c02b7e4d"), "age" : 351608, "name" : 351617, "address" : 352608 }
{ "_id" : ObjectId("51f37031065189f0c04dc535"), "age" : 2597984, "name" : 2597993, "address" : 2598984 }
{ "_id" : ObjectId("51f36fab065189f0c032416f"), "age" : 794778, "name" : 794787, "address" : 795778 }
{ "_id" : ObjectId("51f36fc4065189f0c037d1d3"), "age" : 1159422, "name" : 1159431, "address" : 1160422 }
{ "_id" : ObjectId("51f36f8d065189f0c02b7e4e"), "age" : 351609, "name" : 351618, "address" : 352609 }


  - db11, db22, db33, db111 각 디렉토리에 1Gbytes 가량의 데이터가 쌓여진다.



<참조>

  - 10Gen ReplicaSet 프리젠테이션

posted by 윤영식
2013. 7. 27. 14:56 MongoDB/Prototyping

몽고디비에서 Replica Set 환경을 구성해 본다. ReplicaSet을 하나의 Shard로 만들어서 여러개의 Shard가 구성되면 하기와 같이 routing은 mongos가 해준다




1. Replica Set 만들기

  - 옵션 : --replSet 

  - 디렉토리 만들어 놓기 : db1, db2, db3을 미리 만들어 놓는다

mongodb_2.4.5> mongod --replSet dowonSet --port  20000  -dbpath /mongodb_2.4.5/db1
Sat Jul 27 14:11:58.007 [initandlisten] MongoDB starting : pid=71010 port=20000 dbpath=/mongodb_2.4.5/db1 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 14:11:58.299 [websvr] admin web console waiting for connections on port 21000
Sat Jul 27 14:11:58.300 [initandlisten] waiting for connections on port 20000
Sat Jul 27 14:12:08.302 [rsStart] replSet can't get local.system.replset config from self or any seed (EMPTYCONFIG)


mongodb_2.4.5> mongod --replSet dowonSet --port  30000  -dbpath /mongodb_2.4.5/db2
Sat Jul 27 14:32:08.934 [initandlisten] MongoDB starting : pid=71062 port=30000 dbpath=/mongodb_2.4.5/db2 64-bit host=nulpulum-mac-13-retina.local


/mongodb_2.4.5> mongod --replSet dowonSet --port  40000  -dbpath /mongodb_2.4.5/db3
Sat Jul 27 14:32:16.317 [initandlisten] MongoDB starting : pid=71063 port=40000 dbpath=/mongodb_2.4.5/db3 64-bit host=nulpulum-mac-13-retina.local



2. Replica Set 환경설정하기

  - mongo REPL 로 연결

  - config 환경 구성

/mongodb_2.4.5> mongo localhost:20000
MongoDB shell version: 2.4.5
connecting to: localhost:20000/test
> var config={_id:'dowonSet', members:[{_id:0, host:'localhost:20000'}, {_id:1, host:'localhost:30000'}, {_id:2, host:'localhost:40000'}] };


///////////// Replica set help 보기

> rs.help();
    rs.status()                         { replSetGetStatus : 1 } checks repl set status
    rs.initiate()                         { replSetInitiate : null } initiates set with default settings
    rs.initiate(cfg)                   { replSetInitiate : cfg } initiates set with configuration cfg
    rs.conf()                           get the current configuration object from local.system.replset
    rs.reconfig(cfg)                  updates the configuration of a running replica set with cfg (disconnects)
    rs.add(hostportstr)             add a new member to the set with default attributes (disconnects)
    rs.add(membercfgobj)        add a new member to the set with extra attributes (disconnects)
    rs.addArb(hostportstr)        add a new member which is arbiterOnly:true (disconnects)
    rs.stepDown([secs])          step down as primary (momentarily) (disconnects)
    rs.syncFrom(hostportstr)    make a secondary to sync from the given member
    rs.freeze(secs)                  make a node ineligible to become primary for the time specified
    rs.remove(hostportstr)       remove a host from the replica set (disconnects)
    rs.slaveOk()                      shorthand for db.getMongo().setSlaveOk()

    db.isMaster()                     check who is primary
    db.printReplicationInfo()       check oplog size and time range

    reconfiguration helpers disconnect from the database so the shell will display
    an error, even if the command succeeds.
    see also http://<mongod_host>:28017/_replSet for additional diagnostic info


///////////// 환경 적용하기

> rs.initiate(config);
{
    "info" : "Config now saved locally.  Should come online in about a minute.",
    "ok" : 1
}


///////////// Enter를 치면 프롬프트가 변경되어 나온다

dowonSet:PRIMARY>
dowonSet:PRIMARY> show dbs
local    0.328125GB
dowonSet:PRIMARY> use dowonDB
switched to db dowonDB
dowonSet:PRIMARY> db
dowonDB
dowonSet:PRIMARY>



3. REPL 조작하기

  - PRIMARY에서 데이터 조작하기

dowonSet:PRIMARY> db.person.save({age:23, name:'youngsik', title:'hi'});
dowonSet:PRIMARY> show collections
person
system.indexes
dowonSet:PRIMARY> db.person.find();
{ "_id" : ObjectId("51f35c76076489dd83ccb46d"), "age" : 23, "name" : "youngsik", "title" : "hi" }


  - SECONDARY 들어가 보기 : PRIMARY가 다운되면 show collections에서 에러가 발생하지 않고 자신이 PRIMARY로 된다

    PRIMARY 1개 + SECONDARY 2개 + 여분 n개

/mongodb_2.4.5> mongo localhost:30000
MongoDB shell version: 2.4.5
connecting to: localhost:30000/test
dowonSet:SECONDARY> show dbs
dowonDB    0.203125GB
local    0.328125GB
test    (empty)
dowonSet:SECONDARY> use dowonDB
switched to db dowonDB
dowonSet:SECONDARY> db
dowonDB
dowonSet:SECONDARY> show collections
Sat Jul 27 14:42:38.244 JavaScript execution failed: error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:L128

///////////// 20000 번 포트의 mongod를 강제로 다운시켰을 때 : 40000 포트의 mongod가 PRIMARY가 된다

/mongodb_2.4.5> mongo localhost:40000
MongoDB shell version: 2.4.5
connecting to: localhost:40000/test
dowonSet:PRIMARY>
dowonSet:PRIMARY> use dowonDB
switched to db dowonDB
dowonSet:PRIMARY> show collections
person
system.indexes
dowonSet:PRIMARY> db.person.find();
{ "_id" : ObjectId("51f35c76076489dd83ccb46d"), "age" : 23, "name" : "youngsik", "title" : "hi" }


  - SECONDARY로 들어가서 show collections가 안될 경우 rs.slaveOk() 명령수행

/mongodb_2.4.5> mongo localhost:30000
MongoDB shell version: 2.4.5
connecting to: localhost:30000/test
dowonSet:SECONDARY>
dowonSet:SECONDARY> show dbs
dowonDB    0.203125GB
local    0.328125GB
test    (empty)
dowonSet:SECONDARY> use dowonDB
switched to db dowonDB
dowonSet:SECONDARY> db
dowonDB
dowonSet:SECONDARY> show collections
Sat Jul 27 14:48:38.663 JavaScript execution failed: error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:L128
dowonSet:SECONDARY> rs.slaveOk();
dowonSet:SECONDARY> show collections
person
system.indexes
dowonSet:SECONDARY> db.person.find();
{ "_id" : ObjectId("51f35c76076489dd83ccb46d"), "age" : 23, "name" : "youngsik", "title" : "hi" }


현재까지 Shard 1개를 만들었다. (구현되지 않는 것은 거짓말이다)



<참조>

  - 10gen Sharding 메뉴얼

posted by 윤영식
2013. 7. 27. 12:28 MongoDB/Prototyping

몽고디비의 마스터/슬레이브 환경을 만들어서 fault tolerance 시스템을 구성해 본다



1. 마스터 만들기

  - 옵션 : --master

mongodb_2.4.5> mongod --dbpath /mongodb_2.4.5/master --master --port 38000
Sat Jul 27 11:50:39.279 [initandlisten] MongoDB starting : pid=70914 port=38000 dbpath=mongodb_2.4.5/master master=1 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 11:50:39.279 [initandlisten] db version v2.4.5
.. 중략 ..
Sat Jul 27 11:50:39.347 [websvr] admin web console waiting for connections on port 39000
Sat Jul 27 11:50:39.347 [initandlisten] waiting for connections on port 38000
Sat Jul 27 11:51:47.344 [initandlisten] connection accepted from 127.0.0.1:63461 #1 (1 connection now open)


mongodb_2.4.5> mongo --port 38000
MongoDB shell version: 2.4.5
connecting to: 127.0.0.1:38000/test
> show dbs
local    0.328125GB

> db.printReplicationInfo()
configured oplog size:   192MB
log length start to end: 317secs (0.09hrs)
oplog first event time:  Sat Jul 27 2013 11:50:16 GMT+0900 (KST)
oplog last event time:   Sat Jul 27 2013 11:55:33 GMT+0900 (KST)
now:                     Sat Jul 27 2013 11:55:36 GMT+0900 (KST)

> db.printSlaveReplicationInfo()
local.sources is empty; is this db a --slave?
> db.printShardingStatus()
printShardingStatus: this db does not have sharding enabled. be sure you are connecting to a mongos from the shell and not to a mongod.



2. slave 만들기

  - 옵션 : --slave  --source <master 위치>

mongodb_2.4.5> mongod --dbpath /mongodb_2.4.5/slave --slave --port 48000 --source localhost:38000
Sat Jul 27 12:04:55.853 [initandlisten] MongoDB starting : pid=70927 port=48000 dbpath=mongodb_2.4.5/slave slave=1 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 12:04:55.853 [initandlisten] options: { dbpath: "/mongodb_2.4.5/slave", port: 48000, slave: true, source: "localhost:38000" }
Sat Jul 27 12:04:56.891 [replslave] build index done.  scanned 0 total records. 0 secs


  - master의 내역확인: slave하나가 연결되었다

Sat Jul 27 11:51:47.344 [initandlisten] connection accepted from 127.0.0.1:63461 #1 (1 connection now open)
Sat Jul 27 12:04:56.889 [initandlisten] connection accepted from 127.0.0.1:63547 #2 (2 connections now open)
Sat Jul 27 12:04:57.896 [slaveTracking] build index local.slaves { _id: 1 }
Sat Jul 27 12:04:57.897 [slaveTracking] build index done.  scanned 0 total records. 0 secs


  - slave 하나더 만들기

mongodb_2.4.5> mongod --dbpath /mongodb_2.4.5/slave2 --slave --port 58000 --source localhost:38000
Sat Jul 27 12:08:53.817 [initandlisten] MongoDB starting : pid=70932 port=58000 dbpath=mongodb_2.4.5/slave2 slave=1 64-bit host=nulpulum-mac-13-retina.local
Sat Jul 27 12:08:53.818 [initandlisten] options: { dbpath: "/mongodb_2.4.5/slave2", port: 58000, slave: true, source: "localhost:38000" }
Sat Jul 27 12:08:55.069 [replslave] build index local.me { _id: 1 }
Sat Jul 27 12:08:55.070 [replslave] build index done.  scanned 0 total records. 0 secs



3. REPL로 작업하고 확인하기

  - 연결 : mongo <주소:port>

////////////////// slave 1번

mongodb_2.4.5> mongo  localhost:48000
MongoDB shell version: 2.4.5
connecting to: localhost:48000/test
> db.printSlaveReplicationInfo()
source:   localhost:38000
     syncedTo: Sat Jul 27 2013 12:09:53 GMT+0900 (KST)
         = 74 secs ago (0.02hrs)
> ^C
bye

////////////////// slave 2번
mongodb_2.4.5> mongo  localhost:58000
MongoDB shell version: 2.4.5
connecting to: localhost:58000/test
> db.printSlaveReplicationInfo()
source:   localhost:38000
     syncedTo: Sat Jul 27 2013 12:08:53 GMT+0900 (KST)
         = 144 secs ago (0.04hrs)
> ^C
bye

////////////////// master
mongodb_2.4.5> mongo  localhost:38000
MongoDB shell version: 2.4.5
connecting to: localhost:38000/test
> db.printSlaveReplicationInfo()
local.sources is empty; is this db a --slave?


  - master에 데이터 넣고 master에서 save한것이 slave로 복제되는지 확인하기

////////////////// master

> use dowon
switched to db dowon
> db.youngsik.save({name:'dowon', age:22});
> db.youngsik.find();
{ "_id" : ObjectId("51f33a81dbed34cf65d102d0"), "name" : "dowon", "age" : 22 }
> show dbs
config    (empty)
dowon    0.203125GB
local    0.328125GB
> ^C
bye

////////////////// slave 1번
mongodb_2.4.5> mongo  localhost:48000
MongoDB shell version: 2.4.5
connecting to: localhost:48000/test
> show dbs
dowon    0.203125GB
local    0.078125GB
> use dowon
switched to db dowon
> show collections
system.indexes
youngsik
> db.youngsik.find();
{ "_id" : ObjectId("51f33a81dbed34cf65d102d0"), "name" : "dowon", "age" : 22 }
>

////////////////// slave 2번

mongodb_2.4.5> mongo  localhost:58000
MongoDB shell version: 2.4.5
connecting to: localhost:58000/test
> use dowon
switched to db dowon
> show collections
system.indexes
youngsik
> db.youngsik.find();
{ "_id" : ObjectId("51f33a81dbed34cf65d102d0"), "name" : "dowon", "age" : 22 }


  - 파일 시스템 확인하기 : master, slave, slave2

mongodb_2.4.5/slave2>

-rwxr-xr-x   1 nulpulum  staff          6  7 27 12:08 mongod.lock
drwxr-xr-x   4 nulpulum  staff        136  7 27 12:12 journal
-rw-------   1 nulpulum  staff  134217728  7 27 12:12 dowon.1
drwxr-xr-x   2 nulpulum  staff         68  7 27 12:12 _tmp
-rw-------   1 nulpulum  staff   16777216  7 27 12:12 local.ns
-rw-------   1 nulpulum  staff   16777216  7 27 12:12 dowon.ns
-rw-------   1 nulpulum  staff   67108864  7 27 12:12 dowon.0
-rw-------   1 nulpulum  staff   67108864  7 27 12:21 local.0

mongodb_2.4.5/slave>
-rwxr-xr-x   1 nulpulum  staff          6  7 27 12:04 mongod.lock
drwxr-xr-x   4 nulpulum  staff        136  7 27 12:12 journal
-rw-------   1 nulpulum  staff  134217728  7 27 12:12 dowon.1
drwxr-xr-x   2 nulpulum  staff         68  7 27 12:12 _tmp
-rw-------   1 nulpulum  staff   16777216  7 27 12:12 local.ns
-rw-------   1 nulpulum  staff   16777216  7 27 12:13 dowon.ns
-rw-------   1 nulpulum  staff   67108864  7 27 12:13 dowon.0
-rw-------   1 nulpulum  staff   67108864  7 27 12:23 local.0


mongodb_2.4.5/master>
-rwxr-xr-x   1 nulpulum  staff          6  7 27 11:50 mongod.lock
drwxr-xr-x   4 nulpulum  staff        136  7 27 11:51 journal
-rw-------   1 nulpulum  staff   67108864  7 27 12:05 local.0
-rw-------   1 nulpulum  staff  134217728  7 27 12:12 dowon.1
drwxr-xr-x   2 nulpulum  staff         68  7 27 12:12 _tmp
-rw-------   1 nulpulum  staff   16777216  7 27 12:12 dowon.ns
-rw-------   1 nulpulum  staff   67108864  7 27 12:12 dowon.0
-rw-------   1 nulpulum  staff   16777216  7 27 12:22 local.ns
-rw-------   1 nulpulum  staff  268435456  7 27 12:22 local.1



<참조>

  - 10Gen Master/Slave 메뉴얼

posted by 윤영식
2013. 7. 27. 11:18 MongoDB/Prototyping

몽고디비에서 인덱스를 만들고 사용하는 간단한 방법에 대해 실습해 본다.


1. 인덱스 만들기

  - <collection>.ensureIndex({key:1 or -1})   예) db.things.ensureIndex({age:1});

  - 1 (양수) : ascending,  -1 (음수) : descending

> use indextest
switched to db indextest
> db.things.save({age:12, name:'dowon', title:'hi'});
> db.things.save({age:22, name:'dowon2', title:'hi2'});
> db.things.ensureIndex({age:1});
> db.system.indexes.find();
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "indextest.things", "name" : "_id_" }
{ "v" : 1, "key" : { "age" : 1 }, "ns" : "indextest.things", "name" : "age_1" }



2. 인덱스 삭제하기

  - <collection>.dropIndexes({key:1 or -1})  예) db.things.dropIndexes({age:1});

> db.things.dropIndexes({age:1});
{
    "nIndexesWas" : 3,
    "msg" : "non-_id indexes dropped for collection",
    "ok" : 1
}
> db.system.indexes.find();
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "indextest.things", "name" : "_id_" }



3. 인덱스 종류

  - 복합 인덱스 (Compound Key Index) : 여러개의 key를 기반으로 인데스 생성가능

     예) db.things.ensureIndex({age:1, name:-1});

  - 희소 인덱스 (Sparse Index) : 색인된 필드만 인덱스 한다

     예) db.things.ensureIndex({title:1}, {sparse:true}); 기본은 false

  - Unique 인덱스 : 색인된 키에 대해 이미 있거나 중복된 것은 insert되지 않는다

     예) db.things.ensureIndex({name:1}, {unique: true});

  * 하단 참조 Link



<참조>

  - 10Gen의 인덱스 한글 버전

posted by 윤영식
2013. 7. 20. 17:45 MongoDB/Concept

몽고디비 설치는 구글링을 통하여 다양한 환경에서 설치하는 블로그가 많이 있으니 별도로 작성하진 않는다. 수행하는 방법을 알아보자. 두번째 MongoDB 강의를 들으면서 인지하지 못했던 것을 알게되었고, 다시 글을 리팩토링하였다. (컴퓨터 언어도 커뮤니케이션 수단, 한글/영등도 커뮤니케이션 수단! 모두 리팩토링과 반복학습이 필요하다.) 


1. MongoDB 시작하기 

[mongodb@localhost ~]$ ll

total 30536

//////////////////////////////////////////////////////////

// db01, db02, db03 디렉토리를 만든다

drwxrwxr-x. 2 mongodb mongodb     4096 Jan 18 21:29 db01

drwxrwxr-x. 2 mongodb mongodb     4096 Jan 18 21:29 db02

drwxrwxr-x. 2 mongodb mongodb     4096 Jan 18 21:29 db03

drwx------. 8 mongodb mongodb     4096 Jan 18 21:29 .


//////////////////////////////////////////////////////////

// mongod를 시작한다. 시작시 특정 dbpath를 full로 지정한다 

[mongodb@localhost ~]$ ./mongod --dbpath /home/mongodb/db01

Fri Jan 18 21:36:13 

Fri Jan 18 21:36:13 warning: 32-bit servers don't have journaling enabled by default. Please use --journal if you want durability.

Fri Jan 18 21:36:13 

Fri Jan 18 21:36:13 [initandlisten] MongoDB starting : pid=7402 port=27017 dbpath=/home/mongodb/db01 32-bit host=localhost.localdomain

Fri Jan 18 21:36:13 [initandlisten] options: { dbpath: "/home/mongodb/db01" }

Fri Jan 18 21:36:13 [initandlisten] waiting for connections on port 27017  // connection 연결 맺는 포트

Fri Jan 18 21:36:13 [websvr] admin web console waiting for connections on port 28017  // 브라우져 관리 화면



2. 새로운 창에서 RELP Prompt 수행하기 

  - REPL = Repeat Evalution Print Loop = 프린트 가능문자로 계속 수행

  - SpiderMonkey라는 JavaScript Engine을 사용한다. SpiderMonkey는 C로 만든 Gecko의 JavaScript 엔진이다 (최신버전은 V8로 변경)

[mongodb@localhost ~]$ ./mongo

MongoDB shell version: 2.2.2

connecting to: test

Welcome to the MongoDB shell.



3. 명령어 수행전 JavaScript의 Stream 이해하기 

  - Server side event = event stream = node.js = functional

    + Functional Operation ()  ==> Task Operation : stream+sequence = Task + Task = activity (Transactional)

    + activity + activity 들의 집합체 = workflow 라고 한다 

    + 즉, Task가 모여서 Activity가 되고 Activity가 모여서 Workflow가 된다 

    + Javascript Object {} ==> {key:value} 로 표현한다. 이것을 functional operation ({}) 로 한다. 

      이렇게 하면 Stream Service가 될 수 있게 한다. 

  - 괄호안의 {} 를 Atrribute라고 한다 ({}, {}, {}) = One Task가 된다 

  - {} = Anonymous Object = Closure Object = Stream Object 가 될 수 있다

  - 보다 자세한 Stream 이해는 Substack의 Stream-Handbook 보자



4. CRUD를 위한 Operator 명령어들 

  - 명령어는 javascript 코딩으로 이루어진다

  - show dbs 또는 show collections 

  - use : db open과 같다 

  - 명령어가 mysql과 유사하다 

////////////////////////////////

// 디비의 상태 정보 보기 

db.stats();

{

"db" : "test",

"collections" : 0,

"objects" : 0,

"avgObjSize" : 0,

"dataSize" : 0,

"storageSize" : 0,

"numExtents" : 0,

"indexes" : 0,

"indexSize" : 0,

"fileSize" : 0,

"nsSizeMB" : 0,

"ok" : 1

}

////////////////////////////////

// 몽고디비 서버의 상태 정보 보기 

> db.serverStatus();
{
"host" : "localhost.localdomain",
"version" : "2.2.2",
"process" : "mongod",
"pid" : 7402,
"uptime" : 2089,
"uptimeMillis" : NumberLong(2089225),
"uptimeEstimate" : 1938,
"localTime" : ISODate("2013-01-19T06:11:02.307Z"),
"locks" : {
"." : {
"timeLockedMicros" : {
"R" : NumberLong(0),
"W" : NumberLong(830)
},
"timeAcquiringMicros" : {
"R" : NumberLong(0),
"W" : NumberLong(37)
}
},
... 중략 ...
>

////////////////////////////////

// 다큐먼트를 만들어 보자

> var person = {age:11, name:'dowon', sex:true};
> person
{ "age" : 11, "name" : "dowon", "sex" : true }

////////////////////////////////

// show 명령어 dbs, collections

show dbs
local (empty)
test (empty)
show collections

////////////////////////////////

// 데이터베이스 만들기

// save 할때  생성됨 

use dowonDB  <== 1) DB open 없으면 save시에 신규 생성해줌 

switched to db dowonDB

show dbs

local (empty)

test (empty)


///////////////////////////////

// db가 현재 디비의 포인터이다

db.colles.save({age:11, name:'dowon', sex:true});  <== 2) collection 생성

show dbs

dowonDB 0.0625GB

local (empty)

test (empty)

> db.colles.find().pretty()  <== formatting 해주기 pretty()

{

"_id" : ObjectId("510331d0dfdfcef5beb23d02"),

"age" : 11,

"name" : "dowon",

"sex" : true

}

> db.colles.save({age:222})

> db.colles.find()

{ "_id" : ObjectId("510331d0dfdfcef5beb23d02"), "age" : 11, "name" : "dowon", "sex" : true }

{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 222 }

> db.colles.find({age:222})

{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 222 }


////////////////////////////////

// 입력 컬럼 수정하기 update({}, {})

> db.colles.update({

... age:222}, {

... $set: {age:22}})

> db.colles.find()

{ "_id" : ObjectId("510331d0dfdfcef5beb23d02"), "age" : 11, "name" : "dowon", "sex" : true }

{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 22 } <==  222 에서 22 로 변경되었음 


// 새로운 document(==record) 입력

> db.colles.save({age:33, name:'sia'})

> db.colles.find()

{ "_id" : ObjectId("510331d0dfdfcef5beb23d02"), "age" : 11, "name" : "dowon", "sex" : true }

{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 22 }

{ "_id" : ObjectId("5103350fdfdfcef5beb23d04"), "age" : 33, "name" : "sia" }

// 걸러네기 : MongoDB의 결과값은 Map으로 나온다

> db.colles.find().skip(1).limit(1)

{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 22 }

> db.colles.find().skip(1)

{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 22 }

{ "_id" : ObjectId("5103350fdfdfcef5beb23d04"), "age" : 33, "name" : "sia" }


// 새로운 값 입력 

> db.colles.insert({value:4});

> db.colles.insert({value:5});

> db.colles.insert({value:6});

> db.colles.insert({value:7});

> db.colles.insert({value:1});

> db.colles.insert({value:2});

> db.colles.insert({value:0});

> db.colles.find().skip(1).forEach(printjson) <== 새로운 값입력후 forEach 사용하기 
{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 22 }
{ "_id" : ObjectId("5103350fdfdfcef5beb23d04"), "age" : 33, "name" : "sia" }
{ "_id" : ObjectId("51033646dfdfcef5beb23d05"), "value" : 4 }
{ "_id" : ObjectId("51033649dfdfcef5beb23d06"), "value" : 5 }
{ "_id" : ObjectId("5103364cdfdfcef5beb23d07"), "value" : 6 }
{ "_id" : ObjectId("51033652dfdfcef5beb23d08"), "value" : 7 }

/////////////////////////////////
// property operator 사용하기 
> db.colles.find({value: {$gt: 4}})  <== mongodb의 $는 operator가 된다. {$gt: 4}는 property operation이다
{ "_id" : ObjectId("51033649dfdfcef5beb23d06"), "value" : 5 }
{ "_id" : ObjectId("5103364cdfdfcef5beb23d07"), "value" : 6 }
{ "_id" : ObjectId("51033652dfdfcef5beb23d08"), "value" : 7 }
// 입력
> db.colles.insert({values: [a, b, c, d]});
Fri Jan 25 18:02:09 ReferenceError: a is not defined (shell):1
> db.colles.insert({values: ['a', 'b', 'c', 'd']});
> db.colles.insert({values: ['b', 'c']});
> db.colles.insert({values: ['d']});
> db.colles.insert({values: ['a', 'c']});
 db.colles.insert({values: ['a', 'c'], value:'dowon'});

> db.colles.find({values: {$in: ['a']}})  <== a가 포함됨 
{ "_id" : ObjectId("51033933dfdfcef5beb23d0c"), "values" : [ "a", "b", "c", "d" ] }
{ "_id" : ObjectId("51033955dfdfcef5beb23d0f"), "values" : [ "a", "c" ] }
{ "_id" : ObjectId("51033984dfdfcef5beb23d10"), "values" : [ "a", "c" ], "value" : "dowon" }
> db.colles.find({values: {$nin: ['a']}}) <== a만 안포함 not in
{ "_id" : ObjectId("510331d0dfdfcef5beb23d02"), "age" : 11, "name" : "dowon", "sex" : true }
{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 22 }
{ "_id" : ObjectId("5103350fdfdfcef5beb23d04"), "age" : 33, "name" : "sia" }
{ "_id" : ObjectId("51033646dfdfcef5beb23d05"), "value" : 4 }
{ "_id" : ObjectId("51033649dfdfcef5beb23d06"), "value" : 5 }
{ "_id" : ObjectId("5103364cdfdfcef5beb23d07"), "value" : 6 }
{ "_id" : ObjectId("51033652dfdfcef5beb23d08"), "value" : 7 }
{ "_id" : ObjectId("51033679dfdfcef5beb23d09"), "value" : 1 }
{ "_id" : ObjectId("5103367cdfdfcef5beb23d0a"), "value" : 2 }
{ "_id" : ObjectId("51033683dfdfcef5beb23d0b"), "value" : 0 }
{ "_id" : ObjectId("5103393ddfdfcef5beb23d0d"), "values" : [ "b", "c" ] }
{ "_id" : ObjectId("5103394cdfdfcef5beb23d0e"), "values" : [ "d" ] }

> db.colles.find({values: {$nin: ['a']}}, {_id:0}) <== _id값은 제외하고 출력
{ "age" : 11, "name" : "dowon", "sex" : true }
{ "age" : 22 }
{ "age" : 33, "name" : "sia" }
{ "value" : 4 }
{ "value" : 5 }
{ "value" : 6 }
{ "value" : 7 }
{ "value" : 1 }
{ "value" : 2 }
{ "value" : 0 }
{ "values" : [ "b", "c" ] }
{ "values" : [ "d" ] }

> db.colles.find({values: {$size: 2}}, {values: 1, _id: 0}) <== _id는 출력하지 말고 values 출력
{ "values" : [ "b", "c" ] }
{ "values" : [ "a", "c" ] }
{ "values" : [ "a", "c" ] }

> db.colles.find({value: {$exists: true}}, {value: 1, values: 1, _id: 0}) <== value 컬럼 있는 것만  출력
{ "value" : 4 }
{ "value" : 5 }
{ "value" : 6 }
{ "value" : 7 }
{ "value" : 1 }
{ "value" : 2 }
{ "value" : 0 }
{ "values" : [ "a", "c" ], "value" : "dowon" }

////////////////////////////////

// 저장된 디렉토리 정보

// BSON으로 저장된다 

[mongodb@localhost ~]$ cd db01
[mongodb@localhost db01]$ ll
total 65552
-rw-------. 1 mongodb mongodb 16777216 Jan 18 22:30 dowonDB.0
-rw-------. 1 mongodb mongodb 33554432 Jan 18 22:30 dowonDB.1
-rw-------. 1 mongodb mongodb 16777216 Jan 18 22:30 dowonDB.ns
-rwxrwxr-x. 1 mongodb mongodb        5 Jan 18 21:36 mongod.lock
drwxrwxr-x. 2 mongodb mongodb     4096 Jan 18 22:30 _tmp

////////////////////////////////

// 컬렉션 내역 찾기 

db.colles2.find();
{ "_id" : ObjectId("50fa3ef3a33733093b8f462f"), "age" : 11 }
{ "_id" : ObjectId("50fa3ef7a33733093b8f4630"), "age" : 22 }
{ "_id" : ObjectId("50fa3efaa33733093b8f4631"), "age" : 33 }
{ "_id" : ObjectId("50fa3f0aa33733093b8f4632"), "age" : 44, "name" : "young" }
db.colles2.find({age:11});
{ "_id" : ObjectId("50fa3ef3a33733093b8f462f"), "age" : 11 }

> db.colles.find({$where : "this.value > 3"});
{ "_id" : ObjectId("51033646dfdfcef5beb23d05"), "value" : 4 }
{ "_id" : ObjectId("51033649dfdfcef5beb23d06"), "value" : 5 }
{ "_id" : ObjectId("5103364cdfdfcef5beb23d07"), "value" : 6 }
{ "_id" : ObjectId("51033652dfdfcef5beb23d08"), "value" : 7 }
> db.colles.find({$where : "this.value > 3"}, {_id:0});
{ "value" : 4 }
{ "value" : 5 }
{ "value" : 6 }
{ "value" : 7 }

// 마치 Procedure처럼 코딩을 한  Callback function을 파라미터로 넣어준다.
> var func = function() {  return this.value > 3; } 
> db.colles.find(func, {_id:0});
{ "value" : 4 }
{ "value" : 5 }
{ "value" : 6 }
{ "value" : 7 }

////////////////////////////////
// 도큐먼트를 객체로 가져와 컬럼(변수) 출력하기
> var dw=db.colles.find({age:11});
> dw[0].name  <== javascript의 객체로 얻어와 컬렉션(배열) 형식으로 변수(컬럼) 출력가능
dowon
> var dw=db.colles.find({age:11});
> dw[0]
{
"_id" : ObjectId("510331d0dfdfcef5beb23d02"),
"age" : 11,
"name" : "dowon",
"sex" : true
}

////////////////////////////////
// RDMS처럼 join 만들기 
> db.colles2.save({age:1, otherid: dw[0]._id}); <== colles 컬렉션의 _id값을 colles 컬렉션에 연결하기 
> db.colles2.find()
{ "_id" : ObjectId("510342f4dfdfcef5beb23d12"), "age" : 1, "otherid" : ObjectId("510331d0dfdfcef5beb23d02") }
> var dw2 = db.colles2.find({_id : ObjectId("510342f4dfdfcef5beb23d12")})
> dw2[0].otherid
ObjectId("510331d0dfdfcef5beb23d02")

////////////////////////////////

// 인덱스를 만들고 find 하면 

// 인덱스를 참조함

// 인덱스를 바로 execution 하여 처리 할 수 있다. 따라서 몽고디비에서는 인덱스를 무한대로 만들 수 있다

// Execution 되고서 사라지는 것이다. 메모리 휘발성이다 

db.colles2.ensureIndex({age:1});  <--- execution으로 바로 인덱스를 생성함 
db.colles2.find({age:44});  <--- index 참조
{ "_id" : ObjectId("50fa3f0aa33733093b8f4632"), "age" : 44, "name" : "young" }



5. 심화 CRUD : update, index

////////////////////////////////

// $exists 오퍼레이터 사용하기 

> db.colles.save({_id: 1, names: ['yun', 'dowon', 'young', 'sik'], qtd: 3})

> db.colles.save({_id: 2, names: ['jung', 'si', 'a', 'yab'], qtd: 3})

> db.colles.save({_id: 3, names: ['yun', 'suk', 'kyung'], qtd: 4})

> db.colles.save({_id: 4, names: ['yun', 'kyung', 'su'], qtd: 4})

> db.colles.save({_id: 4, names: ['ahn', 'chul', 'su'], qtd: 5})

> db.colles.save({_id: 4, names: ['moon', 'jea', 'in'], qtd: 5})

> db.colles.find()

{ "_id" : ObjectId("510331d0dfdfcef5beb23d02"), "age" : 11, "name" : "dowon", "sex" : true }

{ "_id" : ObjectId("510332c0dfdfcef5beb23d03"), "age" : 22 }

{ "_id" : ObjectId("5103350fdfdfcef5beb23d04"), "age" : 33, "name" : "sia" }

{ "_id" : ObjectId("51033646dfdfcef5beb23d05"), "value" : 4 }

{ "_id" : ObjectId("51033649dfdfcef5beb23d06"), "value" : 5 }

{ "_id" : ObjectId("5103364cdfdfcef5beb23d07"), "value" : 6 }

{ "_id" : ObjectId("51033652dfdfcef5beb23d08"), "value" : 7 }

{ "_id" : ObjectId("51033679dfdfcef5beb23d09"), "value" : 1 }

{ "_id" : ObjectId("5103367cdfdfcef5beb23d0a"), "value" : 2 }

{ "_id" : ObjectId("51033683dfdfcef5beb23d0b"), "value" : 0 }

{ "_id" : ObjectId("51033933dfdfcef5beb23d0c"), "values" : [ "a", "b", "c", "d" ] }

{ "_id" : ObjectId("5103393ddfdfcef5beb23d0d"), "values" : [ "b", "c" ] }

{ "_id" : ObjectId("5103394cdfdfcef5beb23d0e"), "values" : [ "d" ] }

{ "_id" : ObjectId("51033955dfdfcef5beb23d0f"), "values" : [ "a", "c" ] }

{ "_id" : ObjectId("51033984dfdfcef5beb23d10"), "values" : [ "a", "c" ], "value" : "dowon" }

{ "_id" : ObjectId("51033df5dfdfcef5beb23d11"), "values" : [ "a", "b", "c", "v" ] }

{ "_id" : 1, "names" : [ "yun", "dowon", "young", "sik" ], "qtd" : 3 }

{ "_id" : 2, "names" : [ "jung", "si", "a", "yab" ], "qtd" : 3 }

{ "_id" : 3, "names" : [ "yun", "suk", "kyung" ], "qtd" : 4 }

{ "_id" : 4, "names" : [ "moon", "jea", "in" ], "qtd" : 5 }


// 불필요한 값이 나온다 

> db.colles.find({}, {names:1, _id:0})

{ }

{ }

... {} 중략 ...

{ }

{ "names" : [ "yun", "dowon", "young", "sik" ] }

{ "names" : [ "jung", "si", "a", "yab" ] }

{ "names" : [ "yun", "suk", "kyung" ] }

{ "names" : [ "moon", "jea", "in" ] }


// $exists 오퍼레이터를 사용한다 

> db.colles.find({names: {$exists: true}}, {names:1, _id:0})

{ "names" : [ "yun", "dowon", "young", "sik" ] }

{ "names" : [ "jung", "si", "a", "yab" ] }

{ "names" : [ "yun", "suk", "kyung" ] }

{ "names" : [ "moon", "jea", "in" ] }


////////////////////////////////

// update 명령  ({1}, {2}) : {1} - 찾는 조건 find, {2} - 업데이트 값

> db.colles.update({_id: 1}, {$inc: {qtd:1}})
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "dowon", "young", "sik" ], "qtd" : 4 }
> db.colles.update({_id: 1}, {$inc: {qtd:1}})
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "dowon", "young", "sik" ], "qtd" : 5 }
> db.colles.update({_id: 1}, {$inc: {qtd:1}})
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "dowon", "young", "sik" ], "qtd" : 6 }

// 값 증가시키기 
> db.colles.update({_id: 3}, {$inc: {qtd:10}})
> db.colles.find({_id: 3})
{ "_id" : 3, "names" : [ "yun", "suk", "kyung" ], "qtd" : 14 }
> db.colles.update({_id: 3}, {$inc: {qtd:10}})
> db.colles.find({_id: 3})
{ "_id" : 3, "names" : [ "yun", "suk", "kyung" ], "qtd" : 24 }

// 배열에 넣기 
> db.colles.update({_id: 1},{$push: {names:'jung2'}})
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "dowon", "young", "sik", "jung2" ], "qtd" : 6 }
// 이걸 pushAll로 하지 않고 push로 하면 배열로 들어감
> db.colles.update({_id: 1},{$pushAll: {names:['jung2', 'kim', 'choi']}}) 
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "dowon", "young", "sik", "jung2", "jung2", "kim", "choi" ], "qtd" : 6 }
> db.colles.update({_id: 1},{$push: {names:['jung3', 'kim3', 'choi3']}})
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "dowon", "young", "sik", "jung2", "jung2", "kim", "choi", [ "jung3", "kim3", "choi3" ] ], "qtd" : 6 }  <== 여러개를 push로 할 경우 별도 배열로 들어감 

// 배열에서 제거하기 
// $pop : 0, 1로 앞단, 뒷단에서 빼기만을 지정가능
// $pull : 컬럼을 지정하여 특정 컬럼을 빼기. 자바스크립트의 Array.slice 와 유사 
> db.colles.update({_id: 1}, {$pull: {names: 'dowon'}}) 
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "young", "sik", "jung2", "jung2", "kim", "choi", [ "jung3", "kim3", "choi3" ] ], "qtd" : 6 }

// 컬럼 넣고 빼기
> db.colles.update({_id: 1}, {$set: {qtddd:4}})
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "young", "sik", "jung2", "jung2", "kim", "choi", [ "jung3", "kim3", "choi3" ] ], "qtd" : 6, "qtddd" : 4 }
> db.colles.update({_id: 1}, {$unset: {qtddd:4}})
> db.colles.find({_id: 1})
{ "_id" : 1, "names" : [ "yun", "young", "sik", "jung2", "jung2", "kim", "choi", [ "jung3", "kim3", "choi3" ] ], "qtd" : 6 } <== qtddd 컬럼이 제거됨


////////////////////////////////

// Index 사용하기 : 1 - ascending, 0 - descending

> db.colles.ensureIndex({value:1}) 

> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles2", "name" : "_id_" }
{ "v" : 1, "key" : { "value" : 1 }, "ns" : "dowonDB.colles", "name" : "value_1" } <== 추가됨 
> db.colles.find({value:1}).explain()
{
"cursor" : "BtreeCursor value_1", <== BasicCursor에서 BtreeCursor로 바뀌었다 
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"value" : [
[
1,
1
]
]
},
"server" : "localhost.localdomain:27017"
}

> db.colles.find({value:1})  <== index에서 가져오게 된다 
{ "_id" : ObjectId("51033679dfdfcef5beb23d09"), "value" : 1 }

// index 삭제하기 
> db.colles.dropIndex({value: 1})
{ "nIndexesWas" : 2, "ok" : 1 }
> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles2", "name" : "_id_" }

// duplicate(중복)되지 않은 index 만들기. 
// unique index는 사용하면 key 개념이 된다
// sparse(희소) index는 값이 없는 것은 저장하지 않는다 
> db.colles.ensureIndex({value:1}, {sparse: true, unique:true, dropDup:true, background:true})
> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles2", "name" : "_id_" }
{ "v" : 1, "key" : { "value" : 1 }, "unique" : true, "ns" : "dowonDB.colles", "name" : "value_1", "sparse" : true }

// Shard와 같이 여러 부분에 걸쳐 동일 명령을 내리고 싶다면 runCommand를 사용한다
> db.runCommand({dropIndexes:'colles', index: {value:1}});
{ "nIndexesWas" : 2, "ok" : 1 }
> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles", "name" : "_id_" }
{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "dowonDB.colles2", "name" : "_id_" }


  - {} 브레이스의 집합체 = 몽고디비의 데이터


<참조>

  - MongoDB operation 메뉴얼

  - 간단한 CRUD

  - Index 만들기 (한글 번역)

  - MongoDB Query Pattern 트랜잭션처리를 중심으로

  - MongoDB $where 조건 

posted by 윤영식
2013. 5. 28. 16:32 MongoDB/Prototyping

Mongoose를 사용하여 개발하는중 외래키를 어떻게 잡는지 궁금하여 찾아보았다. 몽구스 현재 버전은 3.6.11 - GitHub



1. 외래키 잡기 

  - 메뉴얼 : http://mongoosejs.com/docs/populate.html

    + _id가 Number이므로 story에서도 Number로 타입을 가져가야 한다 

var mongoose = require('mongoose') , Schema = mongoose.Schema var personSchema = Schema({ _id : Number, name : String, age : Number, stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }] // 여러 이야기 - 배열 }); var storySchema = Schema({ _creator : { type: Number, ref: 'Person' }, // 글쓴이 한 사람 title : String, fans : [{ type: Number, ref: 'Person' }] // 해당 글에 대한 여러 팬들 - 배열 }); var Story = mongoose.model('Story', storySchema); 

var Person = mongoose.model('Person', personSchema);



2. 플러그인

  - 플로그인은 mongoose의 middleware 기능을 이용하여 만들 수 있다. 즉, 몽고디비에 대한 쿼리가 진행되기 전에 pre action을 수행하고 next할 수가 있는 것이다. Connect 프레임워크의 next와 유사하다고 보면 된다.

  - 플러그인 검색 사이트 : http://plugins.mongoosejs.com

    + 해당 사이트에서 검색을 통하여 플러그인을 찾을 수 있다. 

    + increment로 검색을 수행하여 auto increment 플러그인 찾음.

      https://github.com/mariodu/mongoose-id-autoinc

var dbName = 'id_autoinc_example',
    mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    db = mongoose.createConnection('127.0.0.1', dbName),
// 모듈 로딩
    autoinc = require('../index');

// 모듈 초기화
// Counter 테이블이 만들어 진다. 만일 Sequence 관리용 다른명칭을 주고 싶다면 
// autoinc.init(db, 'AnotherName');
autoinc.init(db);

var UserSchema = new Schema({
  name: String,
  email: String
});

// 플로그인 설정
UserSchema.plugin(autoinc.plugin, {
  model: 'User',
  field: 'seqnumber', // field 정보 안주면 기본 _id 를 Number의 sequence field로 사용한다
  start: 100,
  step: 10
});

var User = db.model('User', UserSchema);

console.log('Database: ' + dbName);
console.log('Collection: ' + User.collection.name);

var user_1 = new User({
  name: 'Dowon',
  email: 'dowon@google.com'
});
user_1.save(function (err, res) {
  console.log('New record added:');
  console.log(res);
});
var user_2 = new User({
  name: 'YoungSik',
  email: 'youngsik@apple.com'
});
user_2.save(function (err, res) {
  console.log('New record added:');
  console.log(res);
  mongoose.disconnect();

});


  - StackOverFlow 에서 도움을 받자 

    + http://stackoverflow.com/questions/tagged/mongoose



3. Smart Dashboard 외래키와 _id 숫자 증가 시키기 

  - service 모듈에서 mongoose와 db connection을 만들어서 전달 받았다. db connection을 두번 만들면 mongoose에서 오류 뱃음.

  - Dashboard안에는 여러 Component가 존재하고 Component는 여러 Dashboard에서 사용 될 수 있다

  - 하기와 같이 사용을 하면 mongo shell 을 통하여 direct insert를 하면 안된다 (주의)


<참조>

  - Connect의 next() 이해하기 (필독)

  - Auto Increment 소스

posted by 윤영식
2013. 5. 25. 17:11 Meteor

미티어는 Full Stack Framework를 지향한다. 미티어는 클라이언트와 서버단의 코드를 통합적으로 지원하며 Node.js와 MongoDB를 기본 스택으로 사용한다. 따라서 단일 언어로 자바스크립트를 사용한다. 미티어 개념을 이해하고 설치 사용해 본다.


1. 미티어 개념이해 

  - 사용하는 서브 프레임워크들

    + Connect, SocketJS, Handlebars, Stylus, CoffeeScript

    + Node.js, MongoDB

  - 박준태님의 파워포인트

    + 현재 버전 : v0.6.3

    + 윈도우 버전 설치 존재 



2. 설치하기 

  - 설치 : meteor를 설치하면 자체적으로 node, mongodb 의 binary가 함께 설치된다 (Linux, Mac기준)

$ curl https://install.meteor.com | /bin/sh


// 설치된 경로 : ~/.meteor 폴더 밑으로 설치됨

$ ls 

 meteor -> tools/latest/bin/meteor

 packages

 tools

 releases


// mongodb binary와 bin 밑에 node binary가 존재하여 별도로 수행한다

// 기본 포트는 node - 3000 port, mongodb - 3002 port 를 사용한다 

$ cd tools/latest && ls

LICENSE.txt examples launch-meteor mongodb tools

bin include lib share


  - 웹애플리케이션 하나 만들고 수행하기 

$ meteor --help

sage: meteor [--version] [--release <release>] [--help] <command> [<args>]


With no arguments, 'meteor' runs the project in the current

directory in local development mode. You can run it from the root

directory of the project or from any subdirectory.


Use 'meteor create <name>' to create a new Meteor project.


Commands:

   run             [default] Run this project in local development mode

   create          Create a new project

   update          Upgrade this project to the latest version of Meteor

   add             Add a package to this project

   remove          Remove a package from this project

   list            List available packages

   bundle          Pack this project up into a tarball

   mongo           Connect to the Mongo database for the specified site

   deploy          Deploy this project to Meteor

   logs            Show logs for specified site

   reset           Reset the project state. Erases the local database.

   test-packages   Test one or more packages


See 'meteor help <command>' for details on a command.


$ meteor create webapp && cd webapp

$ meteor run 또는 meteor


// 수행을 하면 어떤 프로세스가 수행이 될까?

// node 프로세스는 2개가 수행된다 (3000 port listen)

$ ps -ef | grep node

  ~/.meteor/tools/11f45b3996/bin/node /Users/nulpulum/.meteor/tools/11f45b3996/tools/meteor.js

  ~/.meteor/tools/11f45b3996/bin/node /Users/nulpulum/prototyping/meteor/webapp/.meteor/local/build/main.js --keepalive


// mongodb 가 3002 port로 webapp 수행한 위치 밑으로 mongodb file system 저장소를 자동으로 셋팅한다 

$ ps -ef | grep mongodb

~/.meteor/tools/11f45b3996/mongodb/bin/mongod --bind_ip 127.0.0.1 --smallfiles --port 3002 --dbpath /to/path/webapp/.meteor/local/db


  - 브라우져에서 http://localhost:3000/ 을 호출하면 기본 파일인 webapp.html이 수행된다 

  - 미티어가 사용하는 MongoDB쪽을 살펴보자 

// 미티어가 사용하는 

$ meteor mongo

MongoDB shell version: 2.4.3

connecting to: 127.0.0.1:3002/meteor

> show dbs

local 0.03125GB

meteor (empty)  <--- meteor 저장소가 새롭게 생성되었다 (주의 : 3번의 기존 프로그램 수정하기 해야 나옴)

> use meteor

switched to db meteor

> show collections


  - 애플리케이션을 만들고 다음과 같이 하위 디렉토리를 만들면 자동으로 파일을 인식한다 (참조)

webapp 디렉토리 밑으로 

  client      – This folder contains any JavaScript which executes only on the client.

  server    – This folder contains any JavaScript which executes only on the server.

  common – This folder contains any JavaScript code which executes on both the client and server.

  lib          – This folder contains any JavaScript files which you want to execute before any other JavaScript files.

  public     – This folder contains static application assets such as images.

  .meteor  - 미티어가 자동으로 만들어주는 폴더 

    local - build - app 밑에 webapp.js 가 존재하여 해당 파일을 사용함 

  



3. 기존 프로그램 수정해 보기 

  - 해당 동영상을 보고서 수정해 본다 


  - webapp.html 기존코드 삭제 후 수정

<head>
<title>webapp</title>
</head>
 
<body>
hi dowon
> : encoding sign sleeping
{{> color_list }}
</body>
 
<template name="color_list">
{{#each colors }}
<ul>
{{> color_info }}
</ul>
{{/each }}
 
<div class="footer">
<button>Like!</button>
</div>
</template>
 
<template name="color_info">
<li class="{{maybe_selected}}">
{{likes}} people like {{ name }}
</li>
</template>


  - webapp.js 기존코드 삭제 후 수정

// create common store collections to mongodb
Colors = new Meteor.Collection("colors");
 
if (Meteor.isClient) {
// UX Coding
Template.color_list.colors = function() {
// likes: -1 = descending
// name: 1 = ascending
// return Colors.find({},{sort:{likes:-1, name:1}});
return Colors.find({},{sort:{name:1}});
}
 
Template.color_info.maybe_selected = function() {
console.log('---> 2: ' + this._id);
return Session.equals('session_color', this._id) ? "selected" : '';
}
 
// UX event
Template.color_info.events = {
'click' : function() {
console.log('---> 1: ' + this._id);
Session.set('session_color', this._id);
}
}
 
Template.color_list.events = {
'click button' : function() {
// 정보를 업데이트하면 동시에 열려 있는 브라우져 정보가 업데이트 된다
Colors.update(Session.get('session_color'), {$inc: {likes:1}})
}
}
}
 
if (Meteor.isServer) {
// 서버를 시작한다
Meteor.startup(function () {
// code to run on server at startup
});
}


  - webapp.css 수정

.selected {
background-color: yellow;

}


 - 테스트

    + Like! 버튼을 누르면 양쪽의 브라우져로 숫자 증가값이 Multicasting 된다. 

    + 크롬의 Dev Tool에서 직접 insert 하여 테스팅. ex) Colors.insert({ likes : 1, name : 'blue' })



4. 클라우드에 배포하여 테스트하기

  - 클라우드에 배포하기 : 스마트 패키징

// xxx.meteor.com 포멧으로 자신만의 xxx를 지정한다

$ meteor deploy dowon-color.meteor.com

Deploying to dowon-color.meteor.com.  Bundling...

Uploading...

Now serving at dowon-color.meteor.com


  - 브라우져에서 dowon-color.meteor.com을 호출한다 

    + 크롬 DevTools에서 MongoDB  API와 똑같이 insert를 한다. (클라이언트에서 서버의 MongoDB로 저장!!!)

    



<참조>

  - 미티어 한글 번역 : http://docs-ko.meteor.com/

  - 미티어 디렉토리 구분하여 프로젝트 만들기

  - 채팅프로그램 만들기

  - Introduction to Meteor (필독)

  - 미티어는 이미 백본에 대해서 패키지로 통합을 하였다. AngularJS에 대한 통합은 현재 투표중이다.

     + meteor와 angularjs에 대한 통합 시도 소스

     + 미티어 로드맵에서 Wishlist에서 볼 수 있다. (Trello에서 로드맵을 추천받고 있습니다. 저도 한표. 현재 총 52표!)

     

posted by 윤영식
2013. 3. 15. 17:49 Backbone.js

서점 샘플 응용하기 - 01 에 이어서 02 에서는 서버-Node.js & MongoDB- 와 붙이는 작업을 한다. 원문이 과거 버전이어서 좀 더 이해하기 쉽도록 재구성하였고, RESTful API 테스트 방법도 새롭게 시도해 보았다



1) RESTful API 와 Node.js 환경 구성하기 

  - 호출하는 API 목록

/api/rides  GET 모든 라이딩정보의 배열을 얻는다.

/api/rides/:id  GET 아이디가 :id인 라이딩정보을 얻는다.

/api/rides  POST  새로운 라이딩정보를 추가하고 아이디가 추가된 그 정보를 반환한다.

/api/rides/:id  PUT 아이디가 :id인 라이딩정보를 수정한다.

/api/rides/:id  DELETE  아이디가 :id인 라이딩정보를 삭제한다.


  - node.js & mongodb 설치되어 있음을 가정하고 node.js 모듈을 설치한다

// 모듈 설치

$ npm install express

$ npm install mongoose


  - public 디렉토리를 생성하여 img, js, css, index.html 파일을 public 디렉토리 밑으로 옮긴다 

  


  - server.js 코딩하기

// 모듈 의존성

var express = require("express"); //웹 프레임워크


// 서버를 생성한다

var app = express();


// 서버를 설정한다

app.configure(function () {

    app.use(express.bodyParser()); //요청 바디를 파싱해서 req.body를 만든다

    app.use(express.methodOverride()); //HTTP 메쏘드를 덮어쓰기 위해서 req.body를 확인한다

    app.use(app.router); //url과 HTTP 메쏘드에 기반한 라우팅을 수행한다

    app.use(express.static(__dirname + '/public')); //정적 자원을 제공하는 곳

    app.use(express.errorHandler({ dumpExceptions:true, showStack:true })); //개발시점에 모든 에러를 보여준다

});


// 서버를 구동한다

app.listen(8080, function () {

    console.log("Express server listening on port %d in %s mode", 8080, app.settings.env);

});


  - nodemon 을 통해서 구동하기 : 수정된 코딩이 있으면 자동으로 restart node 수행 (nodemon 블로깅)

nodemon server.js

15 Mar 14:37:11 - [nodemon] v0.7.2

15 Mar 14:37:11 - [nodemon] watching: ~/backbone/riding

15 Mar 14:37:11 - [nodemon] starting `node server.js`

Express server listening on port 8080 in development mode


  - MongoDB 구동하기 

// mongod 데몬 실행 

$ mongod --dbpath /mongodb/ride_database/

Fri Mar 15 15:33:19 [initandlisten] MongoDB starting : pid=50361 port=27017 dbpath=/Users/nulpulum/mongodb/ride_database/ 64-bit host=mac-42-retina.local

Fri Mar 15 15:33:19 [initandlisten] db version v2.2.3, pdfile version 4.5

... 중략 ..

Fri Mar 15 15:33:19 [websvr] admin web console waiting for connections on port 28017

Fri Mar 15 15:33:19 [initandlisten] waiting for connections on port 27017

// Node.js 서버가 구동 되어 있으면 연결 정보가 출력된다 

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61129 #1 (1 connection now open)

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61130 #2 (2 connections now open)

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61131 #3 (3 connections now open)

Fri Mar 15 15:34:27 [initandlisten] connection accepted from 127.0.0.1:61132 #4 (4 connections now open)



2) Mongoose 통하여 MongoDB 접근하기 

  - biz.js 파일을 생성하고 mongoose 코딩하기

    + Mongoose 용 schema와 model을 만든다 

    + MongoDB 에 접속한다 

    + 모듈패턴으로 업무 callback CRUD function 들을 GET, POST, PUT, DELTE 메소드를 정의한다

  - server.js 안에 RESTful API 추가하기

.. 중략 ..

// 서버를 설정한다

app.configure(function () {

    app.use(express.bodyParser()); //요청 바디를 파싱해서 req.body를 만든다

    app.use(express.methodOverride()); //HTTP 메쏘드를 덮어쓰기 위해서 req.body를 확인한다

    app.use(app.router); //url과 HTTP 메쏘드에 기반한 라우팅을 수행한다

    app.use(express.static(__dirname + '/public')); //정적 자원을 제공하는 곳

    app.use(express.errorHandler({ dumpExceptions:true, showStack:true })); //개발시점에 모든 에러를 보여준다

});


// mongoose 업무모듈인 biz.js 가져오기 

var biz = require("./biz");


// RESTful API에 대한 호출에 대하여 biz.js 모듈의 callback 펑션을 설정함

app.get( '/api/rides', biz.readAll);

app.post('/api/rides', biz.create);

app.get( '/api/rides/:id', biz.read);

app.put( '/api/rides/:id', biz.update);

app.delete('/api/rides/:id', biz.delete);


// 서버를 구동한다

app.listen(8080, function () {

    console.log("Express server listening on port %d in %s mode", 8080, app.settings.env);

});



3) RESTful API 테스트하기 

  - Chrome extention 인 "Advanced REST client" 설치한다 : Chrome Web Store에서 검색하고 설치하면 됨


  - Advanced REST client 실행

    + POST 메소드를 선택 (GET, PUT, DELETE 순으로 실행하여 본다)

    + 하단의 Form 탭을 선택하여 key=value 를 추가한다 

    + "Send" 버튼을 클릭하면 결과가 최하단에 나온다 (200 OK)

          

 - MongoDB에서 결과 조회

$ mongo

MongoDB shell version: 2.2.3

connecting to: test

> show dbs

local (empty)


// POST가 성공하면 데이터베이스가 보인다 

> show dbs

local (empty)

ride_database 0.203125GB

> use ride_database

switched to db ride_database

> show collections

rides

system.indexes

> db.rides.find().toArray();

[

{

"title" : "투어 드 코리아",

"rider" : "윤복자",

"ridingDate" : ISODate("2013-06-01T00:00:00Z"),

"keywords" : "코리아 싸이클링",

"_id" : ObjectId("5142ccbd1b06d36acc000001"),

"__v" : 0

}

]

>


  - PUT 은 URL 뒤에 MongoDB document의 _id 값인  5142ccbd1b06d36acc000001  을 붙여준다. (DELETE 도 동일)

   



4) Backbone.js 와 Node.js 연결하기 

  - 클라이언트 <-> 서버 연결

    + MongoDB사용시 : _id 지정하기 

    + url 설정 : /api/rides

    + fetch() 호출

    + reset callback으로 render 설정

  - ride.js 백본파일을 수정 : 라이딩 정보 목록 가져오기 == 컬렉션 모델에서 url 설정

   var Ride = Backbone.Model.extend({

        defaults:{

            coverImage:"img/my_cycle.png",

            title:"2011년 대관령 대회",

            rider:"Yun YoungSik",

            ridingDate:"2011",

            keywords:"대관령 힐크라이밍 대회"

        },

        idAttribute: "_id"  // MongoDB 사용시 바꾸어줌 

        // idAttribute 와 같은 효과를 같는다 

        /*parse:function (response) {

            console.log(response);

            response.id = response._id;

            return response;

        }*/

    });

.. 중략 ..  

    // rides 

   /* var rides = [{title:"대관령 힐크라이밍 대회", rider:"Yun YoungSik", ridingDate:"2010", keywords:"대관령"},

        {title:"미시령 힐크라이밍 대회", rider:"Yun DoWon", ridingDate:"2011", keywords:"미시령"},

        {title:"투어 드 코리아", rider:"Yun YoungSik", ridingDate:"2012", keywords:"코리아"}];*/

    var rides = [];   // 위의 샘플은 사용하지 않고 빈 배열을 만들어 준다 

  

    ////////////////////

    // Ride Collection

    var Rides = Backbone.Collection.extend({

        model : Ride,

        url : '/api/rides'  // 서버 호출 API

    });


    ///////////////////

    // Collection View

    var RidesView = Backbone.View.extend({

        el: $("#rides"),


        initialize: function(){

            this.collection = new Rides(rides);

            // 서버로 부터 데이터 가져오기 

            this.collection.fetch();

            this.render();

            // 컬렉션 add가 호출되면 renderRide를 trigger 한다 

            this.collection.on("add", this.renderRide, this);

            this.collection.on("remove", this.removeRide, this);

            // fetch()가 비동기적이기 때문에 Backbone이 reset을 호출하면 trigger 될 수 있도록 한다 

            this.collection.on("reset", this.render, this);  

        },

.. 중략 ..


  - 브라우져 호출 : MongoDB 의 데이터를 가져와서 Backbone이 데이터를 reset 하게 되면 입력한 라이딩정보가 출력된다 

   


  -  날짜가 이상하게 나오므로 jQuery를 이용하여 날짜를 포멧팅 해준다 

<script id="rideTemplate" type="text/template">

        <img src="<%= coverImage %>"/>

        <ul>

            <li><%= title %></li>

            <li><%= rider %></li>

            <!-- 년/월/일 만 표현 --> 

            <li><%= $.format.date(new Date(ridingDate), 'yyyy/MM/dd') %></li>

            <li><%= keywords %></li>

        </ul>

        <button class="delete">Delete</button>

    </script>

</div>

<script src="js/jquery.js"></script>

<!-- 다운로드 받아서 js 폴더에 놓는다 -->

<script src="js/jquery-dateformat.js"></script>

<script src="js/underscore.js"></script>

<script src="js/backbone.js"></script>

<script src="js/ride.js"></script>


  - 브라우져 화면에서 MongoDB로 add 하기 : Backbone의 ride.js 에서 addRide 수정

var RidesView = Backbone.View.extend({

        el: $("#rides"),


        initialize: function(){

            this.collection = new Rides(rides);

            // 서버로 부터 데이터 가져오기 

            this.collection.fetch();

            this.render();

            // 컬렉션 add가 호출되면 renderRide를 trigger 한다 

            this.collection.on("add", this.renderRide, this);

            this.collection.on("remove", this.removeRide, this);

            // fetch()가 비동기적이기 때문에 Backbone이 reset을 호출하면 trigger 될 수 있도록 한다 

            this.collection.on("reset", this.render, this);

        },


        render: function(){

            var that = this;

            _.each(this.collection.models, function(item){

                that.renderRide(item);

            }, this);

        },


        renderRide: function(item){

            var rideView = new RideView({

                model: item

            });

            this.$el.append(rideView.render().el);

        },


        addRide: function(e){

            e.preventDefault();


            var formData = {};

            // jQuery의 each로 formData key=value 객체를 만듦

            $("#addRide").children("input").each(function (i, el) {

                if ($(el).val() !== "") {

                    formData[el.id] = $(el).val();

                }

            });


            // rides 배열에 저장

            rides.push(formData);

            // 컬렉션 객체에 저장

            // 서버로 저장하기 기존 add를 create 으로 바꾸면 된다 

            this.collection.create(formData); 

            //this.collection.add(new Ride(formData));

        }, 


  - 브라우져 결과 화면 : "평택 대회" 입력

   


  - MongoDB의 결과값 : 3개의 도큐먼트가 존재한다 

> db.rides.find().toArray();

[

{

"title" : "투어 드 코리아3",

"rider" : "윤영식",

"ridingDate" : ISODate("2012-06-01T00:00:00Z"),

"keywords" : "싸이클링 코리아 우승",

"_id" : ObjectId("5142dc4ae196a916e0000001"),

"__v" : 0

},

{

"title" : "대관령 힐크라이밍 대회",

"rider" : "윤도원",

"ridingDate" : ISODate("2013-07-01T00:00:00Z"),

"keywords" : "강원도 우승목표",

"_id" : ObjectId("5142dd8cba03b734e1000001"),

"__v" : 0

},

{

"title" : "평택 대회",

"rider" : "윤복자",

"ridingDate" : ISODate("2013-03-01T00:00:00Z"),

"keywords" : "고속도로 개통기념",

"_id" : ObjectId("5142de7351b4341ee2000001"),

"__v" : 0

}

]

>


  - 원문에 포함된 날짜 포함하기와 키워드배열로 만들기는 패스~~~ ^^;


** 전체 소스 코드 

riding.zip



<참조>

  - 원문 : Backbone.js Developing 번역글

  - Node.js & Express & Mongoose ToDo 예제

  - Handlebars 홈페이지

  - Tool : Advanced Rest Client

posted by 윤영식
2013. 2. 23. 16:53 MongoDB/MapReduce

GridFS에 대하여 샘플을 돌려보자


1) 예제 다운로드 

  - git 복제한다

[~/development/mongodb]git clone https://github.com/jamescarr/nodejs-mongodb-streaming.git gridfs_mongoose

Cloning into 'gridfs_mongoose'...

remote: Counting objects: 44, done.

remote: Compressing objects: 100% (30/30), done.

remote: Total 44 (delta 12), reused 40 (delta 8)

Unpacking objects: 100% (44/44), done.


  - 파일을 JetBrains WebStorm에서 열어보았다 

    + coffee-script 이용하여 app.js 를 app.coffee 로 작성

  


  - 실행하기전 express, mongoose, request, jade 모듈 설치

    + npm install express

    + npm install mongoose

    + npm install request


  - 실행 : http://localhost:3000 호출

    + 호출하기전 MongoDB를 start 해 놓아야 한다 

[~/development/mongodb/gridfs_mongoose]coffee app

Server running. Navigate to localhost:3000

  

    + 브라우져 호출하면 파일 업로드 화면이 나온다

  

 


2) GridFS 파일 업로드 하기 

  - 화면에서 Name을 입력하고 File 을 선택한 후 "제출" 버튼을 클릭한다 


  - MongoDB의 mongo에서 확인을 한다 : dowonFile 은 프로그램안에서 사용한 Collection 명칭이다

[~/mongodb]mongo

MongoDB shell version: 2.2.3

connecting to: test


///////////////

//  제출전 상태

> show dbs

local (empty)


///////////////

//  제출후 상태

> show dbs

dowonFile 0.203125GB

local (empty)

> use dowonFile

switched to db dowonFile

> show collections

applications

fs.chunks

fs.files

system.indexes

> db.applications.find();

{ "name" : "dowonGridFileSample", "_id" : ObjectId("51392f28dfa5f69404000001"), "files" : [ { "md5" : "6dda9a5cd37e113b246fd00604bf3638", "uploadDate" : ISODate("2013-03-08T00:22:01.077Z"), "chunkSize" : 262144, "length" : 7772701, "contentType" : "binary/octet-stream", "_id" : ObjectId("51392f28dfa5f69404000002") } ] }



3) 예제 분석하기 

  - *.coffee 를 컴파일하여 분석할 수도 있다 : coffee -c *.coffee 수행하면 *.js 파일 생성됨

  - 가급적 coffee-script에 익숙해 지도록 하자 (참조)

  - app.coffee 

    + 기본 환경 셋업

    + 스키마 생성

    + 호출을 위한 get/post 구성 : /new/:id 값을 RESTful 로 호출하면 파일을 다운로드 받을 수 있다 (id는 몽고디비에서 _id 참조)

  

  - gridfs.coffee

    + gridfs 전용 put, find 관련 메소드를 정의하여 모듈화 한다 


4) Mocha 테스트 

  - git clone 받은 디렉토리에서 Mocha 테스트를 수행한다 : current 디렉토리 밑으로 test 디렉토리의 테스트 파일을 자동 수행한다

  - 테스트 파일 형식

    + .js : mocha

    + .coffee : mocha --compilers coffee:coffee-script



<참조>

  - 원문 : nodejs-mongodb-streaming (Node.js mongoose+GridFS 예제)


'MongoDB > MapReduce' 카테고리의 다른 글

[MongoDB] Aggregation Framework 실습하기  (0) 2013.08.03
[MongoDB] Aggregation Framework 이해하기  (0) 2013.08.03
[MongoDB] GridFS 개념잡기  (0) 2013.02.23
posted by 윤영식
prev 1 2 next