최용수 팀장 인스웨이브 연구개발본부

▲ 최용수 팀장 인스웨이브 연구개발본부
[컴퓨터월드] 웹 애플리케이션을 구축하기 위해서 데이터 저장소로 RDBMS(Oracle, DB2, Sybase, MySQL 등)을 이용하고 WAS(WebLogic, WebSphere, JEUS, JBoss등) 위에 J2EE 프레임워크(Spring 등)를 사용하여 Java로 서버 프로그램을 구현하는 것이 우리에게 친숙한, 지금껏 해 오던 방식이라고 할 수 있다.

웹 애플리케이션의 클라이언트는 브라우저 위에 HTML, JavaScript, CSS 등을 이용하여 구현하는 것이 일반적이다. ActiveX나 플러그인 등으로 구성된 UI툴 들도 있지만, 최근에는 사용자 접근성 및 편리성을 위해 클라이언트에 별도의 프로그램 또는 모듈 설치는 배제되는 추세이므로 이에 대해서는 이 글에서 제외한다. 물론 JSP나 PHP를 이용한 환경도 많이 있지만 Layer 간, Layer 내부에서도 MV* 패턴을 적용하려는 큰 흐름에 비추어 이 부분도 이 글에서 제외하기로 하겠다.

위와 같은 아키텍처 및 구조, 솔루션들이 강력한 것은 사실이지만 단순한 웹 애플리케이션을 작성하려고 해도 개발자가 데이터 조작을 위해서는 각 Tier에 맞춰 SQL과 Java, JavaScript를 익히고 사용할 줄 알아야 한다.

동일한 데이터가 각 Tier에 맞춰 각기 다른 유형의 Object에 담겨 있어 Object 간 변환 작업이 필요하거나, serialization과 deserialization을 수행하거나, 한 쪽 Object에서 다른 Object로 get/set을 하기도 한다. 이는 결과적으로 overhead를 발생시킬 수밖에 없다.

예를 들면 Query를 수행해서 얻은 ResultSet을 순환하면서 Java 객체에 값을 담을 수 있다. 공통 로직으로 상화를 하거나 iBatis 같은 솔루션을 이용하여 자동 매핑 등을 할 수 있지만 본질적인 작업을 생략할 수는 없다. 업무 로직에서 Java로 조작한 JSON을 DB에 String으로 serialize를 하여 저장하고 반대로 deserialize하여 다시 JSON 객체로 얻어 오기도 한다. 업무 로직에서 List나 Map에 담겨 있는 데이터를 클라이언트로 반환할 때 JSON으로 변환하기도 한다.

이를 해결할 방법은 없을까? 아래 소개하는 솔루션들을 이용해서 동일한 개발 언어와 데이터 오브젝트 유형으로 Server-Side 및 Front-End[각주1]를 모두 구현할 수 있다. 여기서 말하는 하나의 개발 언어는 JavaScript이고 데이터 오브젝트의 유형은 JSON이다.

Front-End에서는 이미 JavaScript와 JSON 이 널리 사용되고 있고 한 번쯤은 사용해 보았을 것이다. Server-Side에서 이를 가능케 하는 것은 MongoDB와 Node.js이다. Server-Side에서는 Node.js Web Application Framework인 Express를, Front-End에서는 Backbone.js를 이용하여 RESTful JSON Service를 구축하는 예제를 연재하도록 하겠다.


1) 각 솔루션들의 특징
아래 솔루션들을 이용하여 이미 프로젝트를 수행하거나 사용해 본 이들도 있겠지만 실제 구현에 앞서 생소한 이들을 위해 각 솔루션들에 대해 간략히 소개하도록 하겠다.

1. Node.js
Node.js를 거칠게 한 마디로 표현하자면 ’Server-Side JavaScript’이다. 크롬의 V8 자바스크립트 엔진을 기반으로 구성되어 있고 브라우저에서 JavaScript를 사용하듯 JavaScript를 이용해서 서버 프로그램을 작성할 수 있다.

가. 특징
 event-driven concurrency
 asynchronous non-blocking model

이 솔루션의 특징을 설명하기 위해 Java와 비교를 해보면, Java의 경우 request 하나 당 하나의 thread가 할당되고(multi threading), 각 request는 동기 방식으로 동작하기 때문에 프로세스가 완료될 때까지 각각의 thread는 묶여 있다. 반면 Node.js는 하나의 thread가 모든 request를 받아 처리하고 IO 작업을 비동기 방식으로 호출하여 해당 작업이 완료되면 event callback이 호출되는 형태로 진행된다.

multi threading은 서버 성능에 따라 차이가 있겠지만 기본적으로 concurrency에 제약을 받을 수밖에 없다. Thread Pool을 이용해서 효율을 올릴 수는 있지만 각 thread가 synchronous(blocking) model로 동작하기 때문에 동시에 처리할 수 있는 request는 한계가 있다. 또한 thread가 많아 질수록 context switch가 자주 발생하고 이는 고비용의 결과를 초래한다.

Node.js 가 'IO 작업'을 비동기 방식으로 처리한다는 것에 주목할 필요가 있는데 여기서 IO 작업이란 대표적으로 DB 쿼리, Network, File 핸들링 등이다. 이는 웹 애플리케이션이 수행하는 주요 작업들이며 이를 비동기 방식으로 처리하기 때문에 높은 concurrency와 성능을 보장받을 수 있다.

그렇다고 장점만 있는 것은 아니다. 단일 thread로 동작하기 때문에 CPU를 많이 사용하는 작업이 있다면 다른 요청이 지연되어 전체적인 성능 저하를 가져온다. 따라서 파일을 읽거나 데이터베이스에 쿼리를 하는 등의 I/O 작업이 많이 발생하는 웹 애플리케이션에 적합하다.

또한 Callback이 비동기로 호출되기 때문에 실행 순서 등을 보장하기 위해 중첩된 구조, 즉 흔히 얘기하는 'callback hell' 또는 'callback pyramid'로 구현되기 십상이다. 하지만 JavaScript에서 제공하는 Promises나 Generators[각주2]를 이용하면 가독성 높고 flow를 쉽게 제어할 수 있는 견고한 프로그램을 작성할 수 있다. 이 밖에도 많은 단점들이 있는데 웹에 자세히 정리된 글들을 참조하기 바란다.

나. 성능
그렇다면 정말 Node.js가 성능상 유리한가? 특정 케이스이기는 하지만 참조할 만한 자료는 다음과 같다. 먼저 기존 Java에서 Node.js로 애플리케이션 플랫폼을 전환하여 화제가 된 페이팔 사례이다.

 

이들에 의하면 기존 애플리케이션에 비해 초당 두 배의 요청을 처리하고 평균 응답 시간이 35% 줄었다고 한다. 개발 측면에서도 기존보다 더 적은 인원으로 2배 빠르게 구축하고 더 적은 loc와 파일만으로 충분했다고 보고하고 있다.

다음은 IBM 사례이다. IBM Passes라는 기존 솔루션을 Node.js와 MongoDB로 구성한 일종의 파일럿 사례이다.

 

concurrency가 50 미만일 때는 Java가 더 빠르고 concurrency가 높은 경우 Node.js가 우수한 것으로 나타났다.

 

Node.js가 CPU나 메모리 사용량에서도 월등한 이점을 보이고 있다.


2. NPM(Node Package Manager)[각주3]
NPM을 간단히 설명하면 노드로 작성된 모듈(라이브러리) 관리자라 할 수 있다. NPM을 이용해서 NPM registry에 등록된 모듈들을 손쉽게 설치하여 이용하고 업데이트하고 관리할 수 있다. NPM자체는 노드 설치 시 같이 설치된다.

전반적인 경향을 보기 위해 모듈 숫자를 비교하면 다음과 같다.

 

Node.js가 가파른 상승세로 증가하고 이밖에 Java와 ruby가 상위권에 위치하고 있다. 수집 방식이나 대상에 따라 다른 결과가 나올 수 있겠지만 Node.js가 유행의 한복판에 있다는 것만은 확실한 것 같다.


3. Express
Node.js는 내장 HTTP 서버 라이브러리를 포함하고 있어 웹 서버로도 사용 가능하지만 다이내믹한 웹 서비스를 구축하기 위해서는 별도의 패키지를 이용한다. 최근 더 유행하는 모듈들이 있지만 이 글에서는 그 중 가장 많이 알려진 Express를 이용하여 웹 애플리케이션을 만들어 보도록 하겠다. Express는 web application framework이며 단순히 기존 WAS의 역할을 하는 것 정도로 이해해도 좋을 것 같다. Express 또한 Node.js로 작성된 모듈이므로 NPM를 이용해서 설치하고 관리한다.


4. MongoDB
MongoDB는 Document database이자 NoSQL database이다. document는 모든 데이터가 JSON 형태로 저장되고 프로세싱 된다는 의미이다. 정확하게는 BSON[각주4]을 사용한다. BSON은 Binary JSON의 약자로, JSON 객체를 바이너리로 인코딩한 포맷이다. MongoDB가 고성능과 기존 RDBM의 용량 한계를 극복하기 위해 출현했기 때문에 설계와 구성에 따라 차이가 있을 수 있지만 데이터들이 네트워크 상에 분산되어 있을 가능성이 크다. 네트워크에서 빈번하게 데이터를 주고 받아야 하기 때문에 BSON을 고안한 것이 아닌지가 필자의 추측이다. 하지만 사용자는 그냥 자바스크립트에서 JSON 다루듯 사용하면 된다.

NoSQL[각주5] 의 특징으로 여러 가지가 있겠지만 기존 RDBMS와 가장 차별되는 점은 Schema 제약이 없다는 것이다. 복잡도를 줄이는 장점을 가져다 주지만 이는 기존 관점에서 단점으로도 작용한다. (아래 단점을 정리할 때 같이 살펴 보기로 하겠다) Schema 제약이 없다는 의미는 동일한 테이블(MongoDB에서는 Collection이라 부른다)에 컬럼ID 나 Type이 다른 데이터를 저장할 수 있고 심지어 컬럼 개수가 다른 Row를 저장할 수도 있다. 이런 특징 때문에 기존 방식으로 컬럼 추가나 데이터 타입 변경은 비용이 많이 드는 작업이었지만 손쉽게 처리할 수 있다. Sharding이라는 Scale-Out 구조를 채택하여 기존 데이터 용량의 한계를 극복한다.

올해 IT업계의 가장 큰 화두는 IoT(사물인터넷)였다. IoT 세상에서는 “사람이 원하는 것을 직접 제어하지 않아도 사물이 스스로 주변 환경을 분석하고 서비스를 제공해 준다”고 한다. 쉽게 말해서 안경, 시계 같은 '웨어러블'한 제품은 물론 가전제품, 자동차 등이 별도의 입력장치가 없이 센서를 이용하여 사람과 상호 작용하고, 이는 결국 기존 데이터 량과 비교할 수 없을 정도로 엄청난 데이터를 발생시킨다는 것이다.

기존에는 데이터 용량의 한계에 달했을 경우, 사용하던 장비의 업그레이드를 통해 고사양으로 전환함으로써 처리 용량을 증대시키는 Scale-UP 방식이 선호되었다. 일반적으로 시스템 구축 시 안정성을 위해 고사양의 장비를 사용하기 때문에 이 위에 다시 업그레이드를 하는 것은 물리적으로나 비용적으로 큰 어려움에 직면하게 된다. 이에 반해 Scale-Out은 장비를 추가하여 데이터를 나눠서 처리하도록 하는 것이다. 자세한 Sharding과 여기서 언급하지 않은 Replica Set 내용은 이 글의 범위를 벗어나기 때문에 생략하도록 하겠다.

인덱스는 메모리에 저장되기 때문에 성능이 빠른 반면 많은 인덱스를 사용하기 위해서는 충분한 메모리를 확보해야 한다. 2.4 버전부터는 Node.js 처럼 V8 JavaScript Engine을 채택했고 ECMAscript 5의 기능들이 도입되었다.

단점
Global Lock
2.2 버전부터 Global Lock이 제거되었다고는 하지만, write 명령이 수행되는 동안 lock 되는 범위가 넓어서 동시성 향상이 더 필요하다. 먼저 메모리에 write를 하고 나중에 disk에 옮기는 구조와 Page Faults 시, 즉 Disk에 직접 write를 해야 하는 상황에서는 lock을 양보하는 개선 등이 있었다.

Transaction
transactions 지원이 RDBMS에 비해 미약하다.

join
RDBMS처럼 Foreign key 등을 이용해서 데이터 간의 관계를 정의하지 않기 때문에 join이 일반적이지 않다. join에 대한 여러 가지 방법들이 존재하지만 join을 회피하는 구조로 설계하는 것을 권장하는 의견이 많다.[각주6]

데이터 공간
JSON을 기반으로 하기 때문에 key-value 형식의 데이터 포맷을 사용한다. 이는 데이터를 저장할 때 필드네임(key)도 같이 저장되므로, 데이터 공간을 RDBMS에 비해 더 많이 소모할 수 있다.

이밖에 SQL을 대체할 수 없는 많은 영역이 존재한다. Node.js 처럼 이에 대해 잘 정리된 글들이 웹에 많이 존재하므로, 참고하기 바란다.

5. Mongo
MongoDB에 query하기 위한 Shell 타입의 툴이다. V8 엔진을 기반으로 하고 있어 Javascript 문법으로 명령을 실행한다. Mongoose 같은 object modeling tool들도 존재하지만 이 글에서는 생략한다.


2) 설치
설치 환경은 Mac OS X 10.8.2 이다.

1. MongoDB
MongoDB 버전은 v2.4.9 이다.

가. 다운로드
MongoDB 공식 사이트(http://www.mongodb.org/downloads)에서 다운로드 받아서 압축을 푼다. 압축 해제 후 홈디렉토리나 /usr/local로 이동시킨다.


$ cd ~/Download
$ tar xzf mongodb-osx-x86_64-2.4.9.tgz
$ mv mongodb-osx-x86_64-2.2.9 ~/mongodb
or
$ sudo mv mongodb-osx-x86_64-2.2.9 /usr/local/mongodb
 

나. 데이터 디렉토리
MongoDB 는 기본적으로 '/data/db' 디렉토리에 데이터를 관리한다. 디렉토리를 생성하고 적절한 권한을 부여한다.


$ sudo mkdir -p /data/db
$ whoami
maninzoo
$ sudo chown maninzoo /data/db
 

[Note]
권한이 없는 경우 아래와 같은 locking error가 발생한다.


Unable to create/open lock file: /data/db/mongod.lock
 

다. $PATH 설정
MongoDB 명령을 쉽게 하기 위해 mongodb/bin 을 $PATH 환경변수에 추가한다. `.bash_profile` 이 존재하지 않는 경우 추가한다.


$ cd ~
$ pwd
/Users/maninzoo
$ vi .bash_profile

export MONGO_PATH=/Users/maninzoo/mongodb
export PATH=$PATH:$MONGO_PATH/bin

## restart terminal

$ mongo -version
MongoDB shell version: 2.4.9

라. MongoDB 실행
`mongod` 로 MongoDB 를 실행하고 `mongo` 로 연결한다. 아래에서 보는 것처럼 기본적으로 27017 port를 이용한다.

Terminal 1

$ mongod
MongoDB starting : pid=3417 port=27017 dbpath=/data/db/ 64-bit host=Macintosh.local

waiting for connections on port 27017

Terminal 2

$ mongo
MongoDB shell version: 2.4.9
connecting to: test
> show dbs
local (empty)
 

[Note]
기본 데이터 디렉토리가 아닌 다른 폴더를 이용하기 위해서는 `--dbpath`에 원하는 path를 지정하면 된다.


$ mongod --dbpath /any-directory
 

마. MongoDB 종료
단순히 Ctrl+C 를 입력하면 된다.

[Note]
우아하게 종료하려면 mongo 콘솔 창에서 admin DB 로 이동 후 db.shutdownServer() 를 실행한다.


> use admin
switched to db admin
> db.shutdownServer()
server should be down...
 


2. Node

가. 다운로드
Node.js 공식 사이트에서 설치 프로그램을 다운로드 받는다.

나. 설치
Installer (.pkg)를 실행하여 설치를 한다.

 

다. NPM 실행
예전에는 별도로 NPM을 설치해 주어야 했지만 Node.js 설치 시 NPM도 같이 설치된다.


[/Users/maninzoo]npm

Usage: npm <command>

npm@1.4.9 /usr/local/lib/node_modules/npm

 

3. NPM

가. 모듈 설치
command line(터미널이나 명령 창)에 install 명령을 이용하여 사용하고자 하는 모듈을 설치한다. working directory(일반적으로 프로젝트 root) 하위의 `node_modules` 라는 폴더(없으면 생성)에 모듈과 해당 모듈이 종속성을 갖는 모듈들이 전부 설치된다.


$ npm install [module name]
 

나. 모듈 설정
모듈을 이용할 프로젝트 정의 및 설정은 프로젝트 루트 디렉토리의 `package.json` 파일을 이용한다. 파일은 하나의 JSON 객체로 이루어졌으며 프로젝트 자체 정의와 프로젝트의 종속성 등을 설정한다. 이렇게 구성된 프로젝트는 별도의 모듈로 배포될 수 있다.[각주 7] 아래는 Backbone.js의 package.json 파일 예이다.


{
"name" : "backbone",
"description" : "Give your JS App some Backbone with Models, Views, Collections, and Events.",
"url" : "http://backbonejs.org",
"keywords" : ["model", "view", "controller", "router", "server", "client", "browser"],
"author" : "Jeremy Ashkenas <jeremy@documentcloud.org>",
"dependencies" : {
"underscore" : ">=1.5.0"
},
"devDependencies": {
"phantomjs": "1.9.0-1",
"docco": "0.6.1",
"coffee-script": "1.6.1"
},
"scripts": {
"test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true && coffee test/model.coffee",
"build": "uglifyjs backbone.js --mangle --source-map backbone-min.map -o backbone-min.js",
"doc": "docco backbone.js && docco examples/todos/todos.js examples/backbone.localstorage.js",
"lint": "jsl -nofilelisting -nologo -conf docs/jsl.conf -process backbone.js"
},
"main" : "backbone.js",
"version" : "1.1.0",
"license" : "MIT",
"repository": {
"type": "git",
"url": "https://github.com/jashkenas/backbone.git"
}
}
 

init 명령을 통해 package.json을 interactive하게 생성할 수 있다.

$npm init

install 시점에 --save 파라미터를 주어 모듈의 설치와 동시에 종속성에 대한 설정을 프로젝트의 package.json 에 추가할 수 있다. 해당 설정은 package.json 파일의 `dependencies` name(key)의 value로 추가된다.

$ npm install [module name] --save
 

--save 가 product 기준의 종속성이라면 --save-dev 파라미터로 개발 기준의 종속성을 추가할 수 있다. package.json 파일의 `devDependencies` name의 value가 업데이트 된다.


다. Global (-g) 옵션
일반적으로 프로젝트 root에서 파라미터 없이 `npm install` 을 실행하여 package.json에 정의된 종속성을 모두 설치한다. 이런 경우 해당 프로젝트만을 위해 모듈들이 설치되고 프로젝트 별로 종속적인 모듈들을 관리한다. 프로젝트 root를 기준으로 설치된 모듈들을 상대 경로로 참조하는 것이 불편하기 때문에 `-g` 글로벌 옵션을 주어 설치를 하면 사용자 `PATH`에 추가되고 단순히 모듈의 scripts name만으로 실행할 수 있다.


npm install -g grunt
 

하지만 글로벌 옵션을 남용할 경우, 해당 프로젝트를 다른 사용자가 사용하거나 다른 장비로 옮겼을 때 예상과는 다른 결과를 낳을 수 있어 이식성 제약을 가져온다. 이는 보통 `node_modules`를 제외한 프로젝트만의 파일들로 배포, 이식된 후 `install` 명령을 통해 종속적인 모듈을 설치하기 때문이다.

라. 모듈 업데이트
설치된 모듈의 업데이트는 다음과 같다.

npm update [module name]
 


3) 글을 맺으며
Java와 RDBMS가 우리에게 여전히 표준(De facto)이자 친숙한 상황에서, 엔터프라이즈 웹 애플리케이션을 구축하는데 많은 단점을 안고 있는 Node.js나 MongoDB가 주목 받을 수 있을까?

단점에도 불구하고 동일한 개발환경에 따른 생산성 향상과 유지보수의 편리함, 성능, 쉬운 빌드와 배포 때문인지 최근 Walmart, General Motors, LinkedIn 등이 아키텍처 변경을 진행하고 있다.

AWS[각주 8] 등의 기존 클라우드 서비스 업체뿐만 아니라 2014년 8월 구글도 그들의 클라우드 서비스에 MongoDB, Node.js, Express와 Angular.js로 구성된 개발 플랫폼(PaaS[각주 9])인 mean.io를 포함시켰다.

Java 진영에서도 JDK 6부터 Rhino 엔진을 번들로 포함시켜 JavaScript를 지원하던 것을 JDK 8부터 Nashorn 엔진을 임베드시켜 강화시켰다.

분야에 상관없이 모두 JavaScript를 끌어안으려는 움직임이 활발한 것은 확실하다. 이렇게 주목 받고 있는 솔루션들을 이용해서 웹 애플리케이션을 만들어 보자.

 

 

< 각주 >
[1] 최근 클라이언트라는 용어보다 더 일반적으로 사용되기 때문에 이 글에서는 혼용해서 사용할 예정이다.
[2] v0.11.2 버전 이상 지원. 글 작성 시점의 버전은 v0.10.31 이므로 개발 버전에서 확인할 수 있다. 또한 Generators 는 ES6(Harmony 프로젝트)부터 지원되므로 실행 시 다음과 같이 해야 한다 : node --harmony ***.js
[3] 'Node Packaged Modules'의 약자로도 사용된다.
[4] http://bsonspec.org/#/specification
[5] NoSQL 이라는 용어가 기존 SQL의 언어를 사용할 수 있다는 측면에서 'Not only SQL'으로도 불린다.
[6] RDBMS에서는 정규화를 통해 중복되는 부분을 제거한다면 MongDB에서는 그런 부분을 허용하고 성능을 취하는 것으로 보인다. 기존 개념과 배치되는 이런 부분이 혼란스럽게 하는 요소이다.
[7] Node.js 모듈을 npm 저장소에 배포하기(http://blog.outsider.ne.kr/829) 참조
[8] Amazon Web Services
[9] Platform-as-a-Service

 

저작권자 © 아이티데일리 무단전재 및 재배포 금지