블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 




스팍에 대한 간단한 개념과 장점 소개


조대협 (http://bcho.tistory.com)



스팍의 개념과 주요 기능


요즘 주변에서 아파치 스팍을 공부하는 사람도 많고, 스팍을 기반으로한 Zeppelin을 이용하여 데이타 분석을 하는 경우도 많아서, 오늘부터 다시 Spark을 들여다 보기 시작했습니다.


스팍은 예전에도 몇번 관심을 가진적이 있는데, Storm과 같은 데이타 스트리밍 프레임웍에서 Storm과 같이 언급 되기도 하고, 머신 러닝 프레임웍을 볼때도  스팍 ML  라이브러리 기능이 언급 되기도 하고, 예전 모 회사의 데이타 분석 아키텍쳐를 보니, 카산드라에 저장된 데이타를 스팍/Shark라는 프레임웍으로 분석을 하더군요. 또 누구는 메모리 기반의 하둡이라고도 합니다.


스팍의 정의를 내려보면 한마디로

범용적 목적의 분산 고성능 클러스터링 플랫폼 (General purpose high performance distributed platform)

입니다 말이 정말 길고 모호한데, 달리 설명할만한 단어가 없습니다.


스팍은 범용 분산 플랫폼입니다. 하둡과 같이 Map & Reduce 만 돌리는 것도 아니고, Storm 과 같이 스트리밍 처리만 하는게 아니라, 그냥 분산된 여러대의 노드에서 연산을 할 수 있게 해주는 범용 분산 클러스터링 플랫폼으로, 이 위에, Map & Reduce나, 스트리밍 처리등의 모듈을 추가 올려서 그 기능을 수행하게 하는 기능을 제공합니다.


특히나, 메모리 하둡이라고도 불리는데, 이 스팍은 기존의 하둡이 MR(aka. Map & Reduce) 작업을 디스크 기반으로 수행하기 때문에 느려지는 성능을 메모리 기반으로 옮겨서 고속화 하고자 하는데서 출발하였습니다.


그 플랫폼 위에 SQL 기반으로 쿼리를 할 수 있는 기능이나, 스트리밍 기능등등을 확장하여 현재의 스팍과 같은 모습을 가지게 되었습니다.

스팍의 주요 기능은 앞에서 언급하였듯이 

  • Map & Reduce (cf. Hadoop)
  • Streaming 데이타 핸들링 (cf. Apache Storm)
  • SQL 기반의 데이타 쿼리 (cf. Hadoop의 Hive)
  • 머신 러닝 라이브러리 (cf. Apache Mahout)
등을 제공합니다.

스팍의 장점

빠른 속도라는 장점은 필수 이고, 왜 다들 스팍! 스팍 하는가 봤더니, 플랫폼으로써의 특성이 장점으로 작용합니다.
예를 들어 기존의 빅데이타 분석 플랫폼의 구현 구조를 보면, 배치 분석시 ETL 등을 써서 데이타를 로딩한 후에, Hadoop의 MR을 이용하여 데이타를 정재한후, 이 데이타를 OLAP 등의 데이타 베이스에 넣은 후에, 리포팅 툴로 그래프로 표현했습니다. 여기에 만약 실시간 분석을 요한다면 Storm을 연결해서 실시간 데이타 분석 내용을 더하는 일을 했습니다. 
사용되는 프레임웍만해도 몇가지가 되고, 이를 공부하는 시간과 시스템을 배포 운영하는데 여러가지 노력이 들어갔습니다만, 스팍은 하나로 이 모든것이 가능하다는 겁니다.

즉 한번 배워놓으면, 하나의 플랫폼내에서 여러가지가 가능한데다가 속도까지 빠릅니다.
그리고 한 플랫폼안에 배치,스트리밍, 머신 러닝등 다양한 처리를 제공하기 때문에, 하나의 데이타로 여러가지 형태로 데이타를 처리할 수 있는 기능을 가집니다.

연동성 부분에서도 장점을 볼 수 가 있는데, 스팍은 스칼라로 구현되었지만, 파이썬,자바,스칼라등 다양한 언어를 지원하기 위한 SDK를 가지고 있고, 데이타 저장단으로는 하둡, 아마존  S3, 카산드라등 다양한 데이트 스토리지를 지원합니다.
이런 연동성으로 다양한 언어로 다양한 데이타 저장소를 연동하여 데이타를 분석 및 처리할 수 있는 장점을 가지고 있습니다.


하이버네이트 입문하기 (기초 #1)

MyBatis만 사용하다가 근래에 들어서 Hibernate를 봐야하는 일이 생겨서, 늙은 나이에, Hibernate를 처음부터 보고 있다.

한국에서는 거의 MyBatis가 대세를 이루지만 해외의 경우 높은 생산성등을 이유로 Hibernate가 거의 대세를 이룬다.

그러나, 한국에서 안사용하는 이유 처럼, Hibernate는 자체적으로 쿼리를 생성하고, OR Mapper로써, 객체들을 DB에서 로딩할때, 레퍼런스된 객체등을 로딩하는등, 제대로 특성을 이해하고 사용하지 않으면 장애를 일으키는 경우가 많지 않기 때문에, 해외에 비해서 한국에서는 많이 사용되지 않는다. (SI에서 기피)


이글에서는 하이버네이트에 대한 간단한 사용법에 대해서 소개하고자 한다.

하이버네이트는 자바 기반의 ORM (Object Relationship Mapper)이다. 쉽게 이야기 해서, 자바 객체를 RDBMS의 하나의 ROW로 맵핑해준다.

이 ORM 개념은 상당히 오래전부터 나왔는데, 처음의 시초는 EJB Entity Beans라고 할 수 있는데, 이 EJB는 여러가지 강력한 기능을 가졌음에도 불구하고, 사용법이 너무 어려워서, 근래에는 거의 사용되지 않는다. 이렇게 사용법이 어려워서 이를 간편화 하여 사용법은 편하게 하면서도, 더 많은 기능을 지원하게 한것이 ORM인데, 그중에서 대표적인 것이 Hibernate이다.

Hibernate가 개발된후에, Java 스펙에도 ORM의 표준 스펙을 정의했는데 이것이 JPA (Java Persistence API)이다. JPA는 하나의 스펙으로 JPA에 대한 구현체가 필요하다. 실제 구현체는 TopLink, OpenJPA등 여러가지가 있는데, 이렇게 ORM API를 JPA를 통해서 추상화 시켜놓음으로써, 다른 ORM과 쉽게 교체가 가능하게 만든 개념이지만, 실제로 ORM은 거의 Hibernate만 사용되기 때문에, JPA를 사용하는 것은 큰 의미가 없다.

JPA는 기본적으로 JBoss나 Weblogic과 같은 JEE 컨테이너를 가정으로 개발되어서 JTS/JTA, EJB3등과 사용하기에는 좋지만, 컨테이너 없이 Tomcat과 같은 Servlet 컨테이너만 사용할 경우, 큰 이득을 보기 어렵다.


1. 개발환경 

하이버네이트를 조금 더 쉽게 개발하려면 이클립스에 JBoss Tool이라는 것을 설치한다. (이 안에 있는 Hibernate Tool만 골라서 설치해도 된다.)

다음에, 이클립스에서 Hibernate Perspective를 추가 한다. 그리고, Hibernate lib와, JDBC 라이브러리를 다운받아서 이클립스 클래스 패스에 추가한다.


2. hibernate configuration 설정

hibernate를 사용하기 위해서는 hibernate에 대한 설정을 해야 한다. 사용하는 DB 정보등을 정해야 하는데, 앞에서 설치한 Hibernate Tool을 이용하면 간편하게 설정할 수 있다.



다음과 같이 데이타 베이스 환경등을 설정하자.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

                                         "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

 <session-factory >

  <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

  <property name="hibernate.connection.password">asdf1234</property>

  <property name="hibernate.connection.url">jdbc:mysql://localhost:3306</property>

  <property name="hibernate.connection.username">terry</property>

  <property name="hibernate.default_schema">terry</property>

  <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

<mapping resource="terry/hibernate/simple/model/User.hbm.xml"/>

 </session-factory>

</hibernate-configuration>


다음으로, 데이타베이스에, User 라는 테이블을 생성한다.

create table terry.USER (EMAIL varchar(255) not null, TENANT_ID varchar(255), LASTNAME varchar(255), FIRSTNAME varchar(255), COMPANY varchar(255), primary key (EMAIL))


3. Model Class 생성

모델 클래스는 POJO 클래스로, 하나의 테이블 ROW에 맵핑되는 자바 클래스이다.

데이타를 저장하는 attribute와 set/getter로 구성한다.

package terry.hibernate.simple.model;

public class User {
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getLastName() {
		return LastName;
	}
	public void setLastName(String lastName) {
		LastName = lastName;
	}
	public String getFirstName() {
		return FirstName;
	}
	public void setFirstName(String firstName) {
		FirstName = firstName;
	}
	public String getCompany() {
		return company;
	}
	public void setCompany(String company) {
		this.company = company;
	}
	public String getTenant_id() {
		return tenant_id;
	}
	public void setTenant_id(String tenant_id) {
		this.tenant_id = tenant_id;
	}
	String email;
	String LastName;
	String FirstName;
	String company;
	String tenant_id;
	
}


4. Model Class to DB Table 맵핑

이렇게 클래스를 생성했으면, 이 클래스가 DBMS의 테이블과 어떻게 맵핑이 되는지를 명시해야 하는데, 이는 *.hbm.xml 파일을 사용하는 방법과 Java Annotation을 사용하는 방법 두가지가 있다. 일반적으로 annotation을 사용하는 방법이 많이 쓰이지만 여기서는 *.hbm.xml 을 어떻게 사용하는지 소개한다.

이클립스에서 New 를 선택하면 아래 그림과 같이 Hibernate 메뉴 아래에 "Hibernate XML Mapping file"을 선택할 수 있다.

그후에 다음으로 넘어가면, hbm 파일을 생성할 자바 클래스를 고르도록 하는데, 클래스를 생성하면, 자동으로 *.hbm.xml 파일이 생성된다.

다음은 자동으로 생성된 User.hbm.xml 파일이다.

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<!-- Generated 2014. 5. 16 ???? 12:36:25 by Hibernate Tools 3.4.0.CR1 -->

<hibernate-mapping>

    <class name="terry.hibernate.simple.model.User" table="USER">

        <id name="email" type="java.lang.String">

            <column name="EMAIL" />

            <generator class="assigned" />

        </id>

        <property name="tenant_id" type="java.lang.String">

            <column name="TENANT_ID" />

        </property>

         <property name="LastName" type="java.lang.String">

            <column name="LASTNAME" />

        </property>

        <property name="FirstName" type="java.lang.String">

            <column name="FIRSTNAME" />

        </property>

        <property name="company" type="java.lang.String">

            <column name="COMPANY" />

        </property>

    </class>

</hibernate-mapping>


여기서, DB의 PrimaryKey의 경우에는 위와 같이 <id> 태그로 지정해줘야하고, 나머지의 경우에는 <property> 로 지정해주면 된다.
다음으로, 하이버네이트에, 이 hbm 파일의 위치를 알려줘야 하는데, 앞에서 생성한, hibernate.cfg.xml에 그 내용을 아래와 같이 추가한다.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

                                         "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

 <session-factory >

  <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

  <property name="hibernate.connection.password">asdf1234</property>

  <property name="hibernate.connection.url">jdbc:mysql://localhost:3306</property>

  <property name="hibernate.connection.username">terry</property>

  <property name="hibernate.default_schema">terry</property>

  <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

<mapping resource="terry/hibernate/simple/model/User.hbm.xml"/>

 </session-factory>

</hibernate-configuration>


5. Session Factory 생성


하이버네이트는 SessionFactory에서 Session을 생성해서 (DB Connection과 유사-나중에 디테일은 좀 찾아봐야 겠음). 데이타베이스에 대한 명령을 처리하는데, 여기서는 아래와 같이 Singletone 패턴을 이용해서 생성하였다.
package terry.hibernate.simple.utils;

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;

public class HibernateUtil {
	static SessionFactory sessionFactory;
	static ServiceRegistry serviceRegistry;
	
	static{
		try{
			Configuration configuration = new Configuration().configure();
			serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
			sessionFactory = configuration.buildSessionFactory(serviceRegistry);
		}catch(HibernateException e){
			e.printStackTrace();
		}
	}
	
	public static SessionFactory getSessionFactory(){ 
		return sessionFactory;
	}

}


6. Insert , Select & Update

다음으로, 실제로 insert & select를 하는 코드를 구현해보자


package terry.hibernate.simple;

package terry.hibernate.simple;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

import terry.hibernate.simple.model.User;
import terry.hibernate.simple.utils.HibernateUtil;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
        User user = new User();
        user.setEmail("cath@gmail.com");
        user.setFirstName("Terry");
        user.setLastName("Cho");
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(user);
        session.getTransaction().commit();
        System.out.println("Insert completed");
     
        session.close();
        sessionFactory.close();
    }
}

Session Factory에서 session을 가져온 후에, User Model 객체를 생생한후, beginTransaction으로 트렌젝션을 시작하고, save를 한후에, 트렌젝션을 commit한후, session과 sessionFactory를 close한다. 데이타 베이스를 확인해보면, 하나의 ROW가 Insert 되었음을 확인할 수 ㅇ

이때 HibernateTool을 사용했으면 아래와 같은 에러가 날 수 있는데,

org.hibernate.engine.jndi.JndiException: Error parsing JNDI name [] 이는 hibernate.cfg.xml에서 위자드로 자동 생성될때 아래와 같이 생성되는데 <session-factory name=""> name 부분이 “” 으로 정의되어 있어서 그렇다. <session-factory>로 수정해주면 된다.(Hibernate tools의 버그인듯)


다음으로, select와 update를 추가해보자

select는 session.get(클래스명,PrimaryKey값)을 사용하면 된다. 또는 session.load를 사용해도 된다 select된 model 객체는 값을 set을 한후에, session.commit을 하게 되면, 데이타베이스에 그 값이 update된다.

public class App 
{
    public static void main( String[] args )
    {
        SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
        User user = new User();
        user.setEmail("cath@gmail.com");
        user.setFirstName("Terry");
        user.setLastName("Cho");
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(user);
        session.getTransaction().commit();
        System.out.println("Insert completed");

        session.beginTransaction();
        User myuser = (User)session.get(User.class, "cath@gmail.com");
        System.out.println("user name:"+myuser.getFirstName());
        myuser.setFirstName("Cath");
        session.getTransaction().commit();
        
        session.close();
        sessionFactory.close();
    }
}


7. 테이블 자동 생성
하이버네이트에서는, 편한 개발을 위해서, 데이타베이스에 테이블을 자동으로 생성하는 기능을 제공한다.
이 기능은, 단지 개발에서만 사용해야 하며, (데이타타입이나 길이가 의되하지 않은 형태로 생성됨), 운영 환경에서는 사용하지 않도록 하자.
이 기능을 사용하기 위해서는Hibernate configuration의 SessionFactory property에서 hdm2ddl auto를 선택해준다.
4가지 옵션이 있는데,
  • Validate - 테이블을 전혀 손대지 않고, 프로그램에 필요한 테이블 구조와 일치하는지 확인한후, 일치하지 않으면 에러를 출력한다.
  • Update - 기존 테이블이 현재 실행에 필요한 테이블 구조와 다를 경우 수정한다. (데이타는 지우지 않는다.)
  • Create - 기존 테이블을 지운 후, 실행할때 마다 매번 생성한다.
  • Create Drop - 테이블을 생성해서 프로그램을 실행한 후에, 테이블을 다시 지워버림
아래에서는 매번 테이블을 새로 생성하도록 create를 사용했다.

<hibernate-configuration> <session-factory name=""> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.password">asdf1234</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306</property> <property name="hibernate.connection.username">terry</property> <property name="hibernate.default_schema">mydb</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.hbm2ddl.auto">create</property> <mapping resource="terry/hibernate/simple/model/User.hbm.xml"/> </session-factory> </hibernate-configuration>


8. 하이버네이트에 의해서 수행되는 SQL 쿼리 보기
앞에서도 언급하였듯이, 하이버네이트는 자동으로, SQL 쿼리를 생성하기 때문에, 어떤 쿼리가 수행되었는지 확인하고 싶을 수 가 있다.
이때는 config 파일에 다음과 같은 property를 추가한다.
<property name="hibernate.show_sql">true</property>

그후에 실행을 해보면, 

다음과 같이 실행되는 SQL을 확인할 수 있다.

5 16, 2014 11:20:41 오후 org.hibernate.tool.hbm2ddl.SchemaExport execute

INFO: HHH000227: Running hbm2ddl schema export

Hibernate: drop table if exists terry.USER

Hibernate: create table terry.USER (EMAIL varchar(255) not null, TENANT_ID varchar(255), LASTNAME varchar(255), FIRSTNAME varchar(255), COMPANY varchar(255), primary key (EMAIL))

5 16, 2014 11:20:41 오후 org.hibernate.tool.hbm2ddl.SchemaExport execute

INFO: HHH000230: Schema export complete

Hibernate: insert into terry.USER (TENANT_ID, LASTNAME, FIRSTNAME, COMPANY, EMAIL) values (?, ?, ?, ?, ?)

Insert completed

user name:Terry

Hibernate: update terry.USER set TENANT_ID=?, LASTNAME=?, FIRSTNAME=?, COMPANY=? where EMAIL=?

5 16, 2014 11:20:41 오후 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop

INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306]



※ 참고 : 하이버네이트에서는 자체적으로 c3p0 connection pool을 사용한다. 이 Pool은 개발용으로는 충분할지 몰라도, 기능들이 부족하여, 운영환경에서는 적합하지 않다. 반드시 운영환경에서는 dbcp와 같은 connection pool로 변경하기 바란다.(다음에 기회 되면 포스팅)


간단하게, hibernate에 대한 기본 사용법에 대해 알아보았다. 

실제로 hibernate는 spring-hibernate 프레임웍과 많이 사용되지만, hibernate에 대한 개념을 조금더 명확히 이해하기 위해서 spring을 배제하고, 설명하였다. (나중에 기회되면, spring-hibernate 연동에 대해서도 포스팅 예정)

빠르게 훝어보는 node.js

#1 node.js의 소개와 내부 구조

조대협 (http://bcho.tistory.com)


요즘 들어서 새로운 기술에 대한 인식도 많이 떨어지고, 공부하는 것도 게을러 져서, 어쩌다 보니 우연한 기회에 스터디를 하게 되었습다. 스터디 주제는 팀원들이 골랐기 때문에 자연히 따라가게 되었는데, 주제는 무려 node.js. 때 맞침 vert.x를 보고 있었기 때문에, 유사 솔루션을 보는 것도 괜찮겠다고 해서 스터디를 시작했는데, 몇주가 지난후에 지금까지 스터디를 하면서 node.js에 대한 내용을 가볍게 정리해보고자 한다.


node.JS에 대한 소개

node.js single thread 기반으로 동작하는 고성능의 비동기 IO (Async / Non-blocking IO)를 지원하는 네트워크 서버이다. 2009 Ryan Dahl에 의해서 개발이 시작되어 있으며, 현재 수많은 지원 모듈과, 레퍼런스, 에코 시스템을 가지고 있는 오픈 소스 프로젝트 중에 하나이다.

Google Chrome V8 엔진으로 개발되어 있으며, 프로그래밍 언어로는 Java script를 사용하며, Event 기반의 프로그래밍 모델을 사용한다. (나중에 자세한 사항을 설명하도록 하자). 근래에 들어서 많은 인터넷 기업들이 node.js를 도입하고 있다. Linked in이나 Paypal 그리고 얼마전에는 그루폰까지 상당 부분의 내부 시스템을 node.js로 전환하였다.


node.JS 의 장점

먼저 node.js의 장점을 짚고 넘어가보면 다음과 같다.

Javascript 기반이고, 개발 구조가 매우 단순화 되어 있어서 빠르게 개발이 가능하다. 즉 클라이언트에서 front end를 자바스크립트를 통해서 개발하던 FE(front end) 개발자들도 손쉽게 서버 프로그래밍이 가능하다는 것이고, 조직의 입장에서도 FE BE(BackEnd) 엔지니어의 기술셋을 나눌 필요가 없다는 것이다. node.js가 빠르다고는 하지만, 실제 성능보다는 이러한 Learning curve, 조직내의 FE/BE 기술 통합에서 오는 장점이 더 큰이유가 아닐까 싶다.

다음으로는 socket.io를 이용한 웹 push 구현이 매우 쉽게 구현이 가능하다. 여타 플랫폼도 WebSocket을 이용한 Push 를 지원하기는 하지만,WebSocket은 브라우져 종류나 버전에 따라서 제한적으로 동작한다. node.js의 경우 웹브라우져의 종류에 따라서 WebSocket뿐만 아니라, Long Polling등 다른 push 메커니즘을 브라우져 종류에 따라서 자동으로 선택하여 사용하고 있으며, 이러한 push 메커니즘은 socket.io API 내에 추상화 되어 있기 때문에, 어떤 기술로 구현이 되어 있던간에 개발자 입장에서는 socket.io만 쓰면 간단하게 웹 기반의 push 서비스가 구현이 가능하다.

마지막으로, non-blocking IO 모델을 지원하는데, 뒤에서 자세하게 설명하겠지만, 일반적인 서버들은 io 요청을 보낸후, 요청을 보낸 thread process io 요청이 끝날 때 까지 io wait 상태로 응답을 기다리고 있다. 이로 인해서, 동시에 서비스할 수 있는 클라이언트 수 (thread가 계속 기다리기 때문에)에 제약이 있고, CPU 사용 효율에도 제약을 갖는다. 이러한 문제를 해결하기 위해서 node.js non-blocking io 컨셉을 사용하는데, io 요청이 있으면, io 처리를 던져 놓고, thread process는 다른 일을 하고 있다가, io 처리가 끝나면 이에 대한 이벤트를 받아서, 응답을 처리하는 형태가 된다.


node.js의 내부 작동 원리 구조

다음으로는 node.js의 내부 구조에 대해서 가볍게 살펴보도록 하자.

Node.js Google Chrome V8 자바스크립트 엔진을 기본으로 동작한다. 이를 기반으로 Single Thread 기반의 Event Loop (libuv)가 돌면서 요청을 처리하며, 시스템적으로 non-blocking io를 지원하지 않는 io 호출이 있는 경우, 이를 비동기 처리 하기 위해서 내부의 Thread pool (libio)을 별도 이용하여 처리한다.

그 위에 네트워크 프로토콜을 처리하는 socket, http 바인딩 모듈이 로드 되고, 맨 윗단에, node.js에서 제공하는 standard library (파일 핸들링, console)이 로드 된다.



이제부터 각각의 중요한 내부 작동원리에 대해서 조금 더 자세하게 알아보도록 하자.

C10K

인터넷이 발전하고 서비스가 거대화 되면서, 서버 대당 처리할 수 있는 동시접속자수에 대한 한계가 재기 되었고, 이를 정의한 문제가 C10K (Connection 10,000) 문제이다. 즉 서버에서 10,000 개 이상의 소켓을 생성하고 처리를 할 수 있느냐 에 대한 문제이다. 인터넷 전이나 초기 같으면,동시에 하나의 서버에서 10,000개의 connection을 처리한 것은 아주 초대용량의 서비스 였지만, 요즘 같은 SNS 시대나, 게임만해도 동접 수만을 지원하는 시대에, 동시에 많은 클라이언트를 처리할 수 있는 능력이 요구 되었다. 메모리나 CPU가 아무리 높다하더라도 많은 수의 소켓을 처리할 수 없다면, 동시에 많은 클라이언트를 처리할 수 없다는 문제이다.

Unix IO 방식이 이 문제의 도마위에 올랐는데, 기존의 Unix System Call select()함수를 이용하더라도, 프로세스당 최대 2048개의 소켓 fd (file descriptor) 밖에 처리를 할 수 없었다. 이를 위한 개선안으로 나온 것이 비동기 IO를 지원하는 API인데, Windows iocp와 같은 비동기 시스템 호출이다. 세부적은 동작 방식은 다르지만 이러한 비동기 방식의 IO의 개념을 설명하면 다음과 같다.

Async / Non blocking IO

먼저 동기식 IO는 다음과 같이 동작한다. file write io를 예를 들어보면, file_write를 호출하면, 디스크에 파일 쓰기 요청을 하고, 디스크가 파일을 쓰는 동안 프로그램은 file_write 부분에 멈춰서 대기하게 된다. (블록킹상태). 파일을 쓰는 동안에는 CPU가 사용되지 않기 때문에, CPU는 놀고,파일이 다 써지만 디스크에서 리턴해서 file_write 함수 다음 코드로 진행을 하게 된다.



 비동기식 IO는 어떻게 처리가 될까?



파일 쓰기 요청을 할 때, 파일 요청이 끝나면 호출될 함수(callback)를 같이 넘긴다.

파일 쓰기 요청이 접수되면 프로그램은 파일이 다 써지는 것을 기다리지 않고, 요청만 던지고 다음 코드로 진행을 계속하낟. 파일을 다 쓰고 나면 앞에서 등록했던 callback 함수를 호출하여 파일 쓰기가 다 끝났음을 알리고 다음 처리를 한다.

비교를 해서 설명하자면 파일 쓰기가 떡뽁이를 주문하는 과정이라면, 앞의 블록킹 IO 방식의 경우에는 떡뽁이를 주문해놓고, 나오기 까지 기다리고 있는 형태라면, 비동기식 방식은 떡뽁이를 주문해놓고, 나가서 다른 일들을 하다가 나오면, 떡뽁이가 나왔으니 가져가라고 전화(이벤트)를 하는 형태가 된다.

Single Thread Model

Tomcat,JBoss와 같은 웹애플리케이션 서버나 Apache와 같은 일반적인 웹서버는 Multi Process 또는 Multi Thread의 형태를 가지고 있다.



톰캣과 같은 서버는 위의 그림과 같이 Client에서부터 요청이 오면, Thread를 미리 만들어 놓은 Thread Pool에서 Thread를 꺼내서 Client의 요청을 처리하게 하고, 요청이 끝나면 Thread Pool로 돌려보낸 후, 다른 요청이 오면 다시 꺼내서 요청을 처리하게 하는 구조이다. 동시에 서비스 할 수 있는 Client의 수는 Thread Pool Thread 수와 같은데,물리적으로 생성할 수 있는 Thread의 수는 한계가 있다. 예를 들어 Tomcat의 경우 500개 정도의 쓰레드를 생성할 수 있다. (물론 2,000개 정도까지도 생성할 수 있지만, 한계가 있다.) 즉 동시에 처리할 수 있는 Client 수에 한계가 있다.

또한 IO 효율면에서도 보면, 아래 그림과 같이 Client에 할당된 Thread IO 작업 (DB,Network,File)이 있을 경우 IO 호출을 해놓고, Thread CPU를 사용하지 않는 Wait상태로 빠져 버리게 된다.



이런 문제를 해결 하기 위한 것이 Single Thread 기반의 비동기 서버인데, 하나의 Thread만을 사용해서 여러 Client로부터 오는 Request를 처리한다. , IO 작업이 있을 경우 앞에서 설명한 비동기 IO방식으로 IO 요청을 던져놓고, 다시 돌아와서 다른 작업을 하다가 IO 작업이 끝나면 이벤트를 받아서 처리하는 구조이다.

아래 그림에서 처럼, Client A가 요청을 받으면, CPU 작업을 먼저하다가 IO작업을 던져놓고, Client B에서 요청이 오면, CPU작업을 하다가 IO작업을 던져놓고, Client A IO작업이 끝나면 이를 받아서 Client A에 리턴하는 식의 구조이다. IO작업시 기다리지 않기 때문에(Block 되지 않기 때문에), 하나의 Thread가 다른 요청을 받아서 작업을 처리할 수 있는 구조가 된다.  이 요청을 받아서 처리하는 Thread ELP (Event Loop Thread)라고 한다.



Thread pool

Node.jssingle thread만 사용하는 것이 아니라 내부적으로 multi thread pool을 사용하기는 한다. 예를 들어 file open등과 같은 일부 IO OS에 따라서 nonblocking function을 지원하지 않는 경우가 있기 때문에, 이러한 blocking io function을 호출할 경우에는 어쩔 수 없이 blocking이 발생하는 데, 이 경우 single thread로 구현된 event loop thread가 정지되기 때문에 이러한 문제를 해결 하기 위해서 내부적으로 thread pool을 별도로 운영하면서 blocking function call의 경우에는 thread pool thread를 이용하여 IO 처리를 하여 event loop thread io에 의해서 block되지 않게 한다.



Event Loop

그러면 이 하나의 Thread로 여러 클라이언트의 요청, 즉 여러 개의 socket connection을 어떻게 처리할까? 방법은 Multiplexing에 있다. 여러 개의 socket이 동시에 연결되어 있는 상태에서 하나의 Thread는 어느 socket으로부터 메시지가 들어오는 지 보다가, socket에서 메시지가 들어오면, 그 메시지를 꺼내 받아서 처리를 하는 방식이다. (epoll, kqueue, dev/poll ,select등을 이용)



개념적으로 생각하면,

socket fd = array[연결된 socket connections]

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

if(fd 가 이벤트가 있으면){

   알고리즘 처리

}// if

}// if

와 같이 표현할 수 있다. (실제 구현체는 다르지만.).

단 이런 single thread 모델에서 주의해야 하는 점은 CPU 작업이 길어질 경우에는 다음 request를 처리하지 못하기 때문에, 다음 request처리가 줄줄이 밀려버릴 수 있다는 것이다. 예를 들어 보자 커피 전문점이 있다고 보자, 주문을 받는 사람이 Single Thread이다. 커피 주문이 들어오면 들어오면 주방에서 일하는 사람에게 커피 주문을 넘기고, 다음 고객의 주문을 받는다. 앞에서 주문한 커피가 주방에서 나오면 이를 주문한 사람에게 커피를 넘겨준다.

커피 주문을 request, 커피를 response, 그리고 커피를 만드는 과정을 IO라고 생각해보자. 만약에 커피를 주문받거나 커피를 건네주는 과정에 많은 시간이 소요된다면 (CPU 작업이 많다면), 뒤에 손님이 기다리는 일이 발생하게 된다. 예를 들어 주문에 1분씩 소요된다면, 첫번째 손님은 1분을, 두번째는 2분을… 60번째는 60분을 기다리게 된다. 그래서 이러한 single thread model에서는 각 request CPU를 많이 사용하는 경우 request 처리가 줄줄이 지연되면서 성능에 심각한 영향을 줄 수 있기 때문에 CPU intensive한 작업에는 적절하지가 않다.


언제 node.js를 쓰거나 쓰지 말아야 할까?

node.js의 사용 용도에 대해서는 논쟁이 많기는 하지만, 공통적으로 공감하는 부분은 prototyping에는 무지 빠르다. mysql이나 mongodb같은  persistence를 이용해서 CRUD rest api implementation하는데, 코딩양이 20~30? 정도밖에 안된다. (자동 생성되는 코드를 빼면 이것 보다 적을지도.)

Async IO를 사용하기 때문에, file upload/download와 같은 network streaming 서비스에 유리하다. 또한 real time web application, 예를 들어 채팅 서비스 같은 곳에 socket.io를 이용하면 쉽게 만들 수 있으며, Single page app 개발에 좋다. 가볍고 생산성이 높은 웹 개발 프레임웍을 가지고 있고, 간단한 로직을 가지면서 대용량 그리고 빠른 응답 시간을 요구로 하는 애플리케이션에 적절하다.

그러면 어디에 쓰지 말아야 할것인가?

공통적으로 대답하는 것은. CPU 작업이 많은 애플리케이션에는 절대 적당하지 않다. Node.js single thread 구조이다. 그래서 하나의  request를 처리할 때 CPU를 많이 사용하면 다른 요청 처리가 지연되게 되고, 전체적인 응답시간 저하로 연결된다.

그리고 다소 이견이 있기는 하지만 CRUD가 많고 페이지가 많은 웹개발에는 적절하지 않다고 한다일단 기존의 Ruby on Rails Python,PHP 등의 웹 프레임웍의 성숙도가 높기 때문이라고 한다.  (직접 테스트 해보니, express와 같은 많은 웹 개발 프레임웍이 있는데, 사실 보면 성숙도가 꽤 높다. 다른 이유가 있는지는 모르겠지만 일반적인 웹 개발에는 추천하지 않는다.)

마지막으로, 초보자나 초보팀이 쓰기에는 적절하지 않다는 것이 본인의 의견이다. javascript언어의 특성상 에러를 가지고 있는 코드 위치에 진입할 때 그때 에러가 나고, 에러가 나면 대부분 서버가 죽어버리기 때문에 운영 관점에서 trouble shooting등이 어려울 수 있으며,  single event loop의 특성상, 하나의 코드가 잘못되서 시스템이 느려지게 되면 전체 request 처리에 문제가 올 수 있기 때문에, 잘 짜야진 코드는 필수이다. 물론 자바 기반의 일반 application server 나 다른 application server도 마찬가지이기는 하지만, 코드가 잘못되었다고 node.js처럼 무작정 서버가 내려않거나, 전체 시스템이 hang up (멈춤)지는 않는다.


#1 – node.js의 소개와 내부 구조 http://bcho.tistory.com/881

#2 - 설치와 개발환경 구축 http://bcho.tistory.com/884

#3 - Event,Module,NPM  http://bcho.tistory.com/885

#4 - 웹 개발 프레임웍 Express 1/2 - http://bcho.tistory.com/887