top bar

글 목록

2015년 8월 31일 월요일

[Performance] VisualVM + jstatd 연동을 통한 remote tomcat instance 모니터링

jstatd



 jstatd tool은 일종의 RMI server application이다. 원격지의 JVMs을 모니터링하거나, 원격지의 모니터링 툴이 로컬에 돌고있는 JVMs 에 접근할 수 있도록 해주는 인터페이스를 제공한다. 자세한 사항은 아래를 참고하자

http://docs.oracle.com/javase/7/docs/technotes/tools/share/jstatd.html


Steps



연동 절차는 의외로 간단하다.

1) 원격지의 tomcat이 구동되고있는 서버에서 jstatd를 실행한다
$ jstatd
하지만 다음과같은 에러가 떨어질 것이다.
Could not create remote object
access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
        at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
        at java.security.AccessController.checkPermission(AccessController.java:559)
        at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
        at java.lang.System.setProperty(System.java:783)
        at sun.tools.jstatd.Jstatd.main(Jstatd.java:139)
따라서 'jstatd.all.policy' 라는 파일을 만들고 다음과 같은 내용을 입력한다
grant codebase "file:/home/asuraiv/apps/jdk/lib/tools.jar" {
        permission java.security.AllPermission;
};
해당 파일을 저장후 다시 아래와 같이 명령어를 입력하자.
$ jstatd -J-Djava.security.policy=/경로/jstatd.all.policy &

2) VisualVM 을 실행하여 Remote Host를 추가한다















위의 창에서 'Host name'을 해당 서버의 ip주소를 입력한후 'OK'를 누르면 아래와 같이 해당 서버에서 돌고있는 Java 인스턴스의 목록이 노출된다.



하지만 치명적인 단점이 있는데, 바로 cpu와 memory의 모니터링은 불가하다는 것이다.
이는 JMX를 사용함으로서 어느정도 극복할 수 있는데, 다음에 포스팅 해보겠다.


2015년 8월 27일 목요일

[MySQL] 실행계획 (Query Plan)

원문 - http://www.sitepoint.com/using-explain-to-write-better-mysql-queries/

개요



 MySQL 쿼리 옵티마이저는 쿼리를 실행할때 최적의 계획을 세운다. 그 계획을 Database용어로 '실행계획'(Query Plan)이라고 하는데, MySQL에서는 'EXPLAIN' 키워드를 이용해 실행계획에 대한 정보를 살펴 볼 수 있다.

 이는 이슈가 발생하는 문제의 쿼리를 이해하고, 어떻게 최적화 할지에대한 insight를 제공하는 매우 강력한 도구가 될 수 있는데, 불행하게도 이를 잘 사용하는 개발자는 별로 없는것 같다. (나 포함 ㅠ)


Understanding EXPLAIN's Output



'EXPLAIN' 을 사용하면 쿼리를 실행하기전에 실행계획을 분석하여 출력한다. 아래와 같은 예를 들어보자
EXPLAIN SELECT * FROM categories
********************** 1. row **********************
           id: 1
  select_type: SIMPLE
        table: categories
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4
        Extra: 
1 row in set (0.00 sec)
이제부터 해당 항목들에대해 자세히 살펴보도록 한다


  • id : 쿼리 안에 있는 각 select 문에 대한 순차 식별자이다. 이순서대로 select문이 실행된다고 생각하면 된다.
  • select_type : select 문의 유형을 말한다. 각 유형은 아래와 같다
    • SIMPLE : 서브쿼리나 'union'이 없는 가장 단순한 select문을 말한다
    • PRIMARY : 가장 바깥에 있는 select 문을 말한다
    • DERIVED : form 문 안에있는 서브쿼리의 select 문이다.
    • SUBQUERY : 가장 바깥의 select 문에 있는 서브쿼리이다.
    • DEPENDENT SUBQUERY : 기본적으로 SUBQUERY와 같은 유형이며, 가장 바깥의 select문에 '의존성'을 가진 서브쿼리의 select문이다.
    • UNCACHEABLE SUBQUERY
    • UNION : union 문의 두번째 select 문을 말한다
    • DEPENDENT UNION : 바깥 쿼리에 의존성을 가진 union문의 두번째 select문을 말한다
  • table : 참조되는 테이블을 말한다
  • type : MySQL이 어떤식으로 테이블들을 조인하는지를 나타내는 항목이다. 이는 매우 중요한데, 이유는 이 타입을 분석함으로써 어떤 인덱스가 사용되고 사용되지 않았는지를 알 수 있고, 이를통해 어떤식으로 쿼리가 튜닝되어야하는지에 대한 insight를 제공하기 때문이다. 각 유형은 아래와 같다
    • system : 0개 또는 하나의 row를 가진 테이블이다.
    • const 
    • eq_ref : primary key나 unique not null column으로 생성된 인덱스를 사용해 조인을 하는 경우이다. const 방식 다음으로 빠른 방법이다.
    • ref : 인덱스로 지정된 컬럼끼리의 '=' , '<=>' 와 같은 연산자를 통한 비교로 수행되는 조인이다
    • index_merge 
    • unique_subquery : 오직 하나의 결과만을 반환하는 'IN'이 포함된 서브쿼리의 경우이다.
    • index_subquery : unique_subquery와 비슷하지만 여러개의 결과를 반환한다
    • range : 특정한 범위의 rows들을 매칭시키는데 인덱스가 사용된 경우이다. BETWEEN이나 IN, '>', '>=' 등이 사용될 때이다.
    • all : 조인시에 모든 테이블의 모든 row를 스캔하는경우이다. 물론 성능이 가장 좋지 않다.
  • possible_keys : 테이블에서 row를 매핑시키기 위해 사용 가능한 (사용하지 않더라도) 키를 보여준다.  
  • key : 실제적으로 쿼리 실행에 사용된 key의 목록이다. 이 항목에는 possible_keys 목록에 나타지 않은 인덱스도 포함 될 수 있다.
  • ref : key column에 지정된 인덱스와 비교되는 column 또는 constants를 보여준다.
  • rows : 결과 산출에 있어서 접근되는 record의 숫자이다. 조인문이나 서브쿼리 최적화에 있어서 중요한 항목이다.
  • Extra : 실행계획에 있어서 부가적인 정보를 보여준다.
    • http://dev.mysql.com/doc/refman/5.6/en/explain-output.html#explain-extra-information

2015년 8월 26일 수요일

[WebDev] $TOMCAT_HOME/work 디렉토리의 용도?

The work directory, as its name suggests, is where Tomcat writes any files that it needs during run time, such as the generated servlet code for JSPs, the class files for the same after they are compiled, the serialized sessions during restarts or shutdowns (SESSIONS.ser).

Also, if you have Tomcat configured not to unapack war files when deployed, your web app's directory structure will be created under the work directory instead of the "webapps" directory.

http://www.coderanch.com/t/87174/Tomcat/Purpose-work-directory-Tomcat

work 디렉토리는 그 이름에서 알 수 있듯이 runtime에 필요한 파일들, 예를들어 JSP로부터 변환된 서블릿코드(java파일), 또 그것을 compile하여 발생된 class파일 그리고 세션 파일들이 상주하는 공간이다.

따라서 실행시간에 다이나믹하게 class파일, 세션파일들이 저장된다.

또 한가지 흥미로운 사실은, server.xml 파일의 unpackWARs 속성과 autoDeploy 속성을 'false'로 해놓으면, 서버가 재시작할때 웹 어플리케이션은 'webapps' 디렉토리 대신에 'work' 디렉토리에 배포된다는 것이다.

아래는 unpackWARs 속성이 true 일때이다.
$ tree ./work/
./work/
└── Catalina
    └── localhost
        └── _
work 디렉토리 밑에 web resources는 생성되지 않고 jsp나 세션파일과 같은 캐시성 파일들이 실행시간에 저장될 공간만 생성되었다.

아래는 unpackWARs 속성이 false 일때이다. (autoDeploy속성도 false)

일단 webapps 디렉토리에 아래와 같이 war파일만 그대로 있고 배포되지 않았다
$ tree ./webapps/
./webapps/
└── ROOT.war
하지만 work 디렉토리에 아래와 같이 WEB-INF (web resources)가 배포된 것을 확인 할 수 있다.
$ tree ./work/
./work/
└── Catalina
    └── localhost
        └── _
            └── WEB-INF
                ├── classes
                │   ├── META-INF
                │   ├── applicationContext.xml
                │   ├── com
                │   │   └── ntscorp
                │   │       └── rnote
                │   │           ├── home
                │   │           │   └── controller
                │   │           │       └── HomeController.class
                │   │           ├── message
                │   │           │   ├── bo
                │   │           │   │   └── MessageBO.class
                │   │           │   ├── controller
                │   │           │   │   └── MessageController.class
                │   │           │   └── model
                │   │           │       └── MessageModel.class
                │   │           ├── mgr
                │   │           │   └── QueueManager.class
                │   │           ├── user
                │   │           │   └── controller
                │   │           │       └── UserController.class
                │   │           └── utils
                │   │               └── LoggedInUserManager.class
                │   └── log4j.xml
                └── lib
                    ├── amqp-client-3.3.5.jar
                    ├── aopalliance-1.0.jar
                    ├── aspectjrt-1.6.10.jar
                    ├── jackson-core-asl-1.9.13.jar
                    ├── jackson-mapper-asl-1.9.13.jar
                    ├── javax.inject-1.jar
                    .
                    .
                    .
                    .
클래스파일과 jar 라이브러리 파일등이 모두 배포되었다.

server.xml의 설정에 따라서 work디렉토리는 실행시간에 생성되는 파일들을 캐시해두는 용도, 또는 웹어플리케이션이 배포되는 용도로 쓰이는것이다



2015년 8월 12일 수요일

[Elasticsearch] NoNodeAvailableException: None of the configured nodes are available

Elasticsearch를 무리 없이 운영하다가 별안간 아래와 같은 Exception을 만나게 되었다.

아래는 서버 log 이다.
org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: []
        at org.elasticsearch.client.transport.TransportClientNodesService.ensureNodesAreAvailable(TransportClientNodesService.java:298) ~[elasticsearch-1.3.2.jar:na]
위와 같은 Exception이 발생하며 쿼리가 되지 않았다.
설정된 노드중에 사용가능한 노드가 없다 라는 메시지 인것 같다

Elasticsearch log파일에는 아래와 같은 에러 메시지가 있었다.
[2015-08-13 11:33:02,020][WARN ][transport.netty          ] [es-node1] exception caught on transport layer [[id: 0x31ef8136, /10.112.133.167:44057 :> /10.112.133.166:9300]], closing connection
java.io.StreamCorruptedException: invalid internal transport message format, got (ff,ed,ff,fd)
아무튼 클러스터에 정상적으로 접근을 못하는 것 같다.

음.... 첫번째 로그 파일(서버로그)을 좀더 조금더 살펴보니, 이상한점을 발견했다.





뭔가 클러스터 이름에 엄청난 공백이 있었다... 알고보니..






JAVA client api를 사용해 커넥션을 생성할때, properties 파일에 있는 Elasticsearch 정보 속성값을 이용해 생성한다. 이때 클러스터이름에 위 properties 파일에서 보이는것처럼 공백이 포함된 클러스터이름으로 커넥션을 생성해 버렸고, 따라서 그 클러스터에 제대로 접근하지 못해 발생했던 이슈였던 것이다..

공백을 제거하니 정상적으로 커넥션이 생성되어 쿼리가 되었다.

2015년 8월 7일 금요일

[Elasticsearch] Logstash와 Elasticsearch의 연동

Elasticsearch output Plugin

Elasticsearch의 output 플러그인은 'node', 'transport', 'http' 의 3가지 프로토콜이 있다.
여기서는 Elasticsearch 클러스터의 9200번포트로 직접 접속하여 데이터를 전송하는 방식인 'http' 프로토콜을 통해서, Logstash와 연동해보도록 한다.


1. 기초

아래와 같은 클러스터 구조가 있다.









일단 간단하게 stdin 으로 json 데이터를 입력받고, Elasticsearch에 색인하는 작업부터 해보겠다.

logstash.conf 파일은 아래와 같이 작성한다.
input {
        stdin {
                codec => json
        }
}

output {
        elasticsearch {
                host => "localhost"
                index => "school"
                index_type => "students"
                protocol => "http"
        }
        stdout {
                codec => rubydebug { }
        }
}
위와 같이 output 설정에 elasticsearch { } 를 작성하고, 중괄호 안에 각종 설정을 세팅한다. protocol은 'http' 로 작성한다. 이 protocol을 생략하게되면 default 설정인 'node'로 세팅되는데, 이 프로토콜은 로그스태쉬를 하나의 node로 실행시켜, 색인작업을 하고자 하는 elasticsearch cluster에 합류시킨다.

일단 지금은 http 프로토콜을 사용한다 host, index, index_type 등의 설정은 localhost의 elasticsearch 서버로, shcool이라는 인덱스의 students 타입에 데이터를 색인 시키는 설정이다.

입력된 값들이 제대로 인식이 되었는지 확인하기 위해서, stdout 설정도 추가하였다.

실행시켜본다.
./logstash -f logstash.test.conf
Logstash startup completed
그리고 { "class" : "A", "name" : "john" } 를 입력해보자
{ "class" : "A", "name" : "john" } ## 입력
{ ## 여기서부터 stdout rubydebug codec에 의해 출력되는 부분.
         "class" => "A",
          "name" => "john",
      "@version" => "1",
    "@timestamp" => "2015-08-07T07:11:41.975Z",
          "host" => "cweb02.ami",
     "@metadata" => {
        "retry_count" => 0
    }
}
출력을 보니 제대로 색인이 된것 같다.
head 플러그인 화면으로 가서 확인해보자.

위 logstash.conf 파일에서 설정해 준 대로, 'school' 인덱스와 'students' 타입이 동적으로 생성되며, 색인이 성공했음을 확인 할 수 있다.


2. Template 사용

템플릿을 사용하여 미리 index와 type의 스키마를 정해놓고 데이터를 색인 할 수 있다.
아래와 같이 템플릿을 정의하자.
curl -XPUT 'localhost:9200/_template/school?pretty' -d '{
    "template" : "school",
    "settings" : { "index.refresh_interval" : "5s" },
    "mappings" : {
        "students" : {
            "properties" : {
                "class" : { "type" : "string", "store" : "yes", index : "not_analyzed" },
                "name" : { "type" : "string",  "store" : "yes" },
                "address" : { "type" : "string",  "store" : "yes" }
            }
        }
    }
}'
'students' 라는 인덱스 템플릿을 하나 생성하였다. 인덱스 템플릿에 관한 자세한 내용은 아래의 doc 문서를 확인해보자

https://www.elastic.co/guide/en/elasticsearch/reference/1.6/indices-templates.htm

템플릿을 생성하였으면, Logstash가 해당 템플릿을 사용하여 색인을 할 수 있도록 설정해 놓아야 한다. 바로 아래와 같이 설정한다.
elasticsearch {
                host => "localhost"
                index => "school"
                index_type => "students"
                protocol => "http"
                template_name => "school" # 바로 여기 설정
        }
}
자, 다시 Logstash를 실행시키고 { "class" : "A", "name" : "iron man", "address" : "newyork" } 와 같은 데이터를 입력한다
{ "class" : "A", "name" : "iron man", "address" : "newyork" }
{
         "class" => "A",
          "name" => "iron man",
       "address" => "newyork",
      "@version" => "1",
    "@timestamp" => "2015-08-07T07:50:33.537Z",
          "host" => "cweb02.ami",
     "@metadata" => {
        "retry_count" => 0
    }
}
템플릿대로 document가 색인되었는지 head플러그인에서 확인해보자.

아래는 템플릿을 사용하지 않았을때와 사용했을때의 인덱스 메타데이터의 차이이다.

1) 템플릿을 미적용한 경우





















Elasticsearch의 기본 idea는 realtime 으로 document를 색인하는것이다. 따라서 동적으로 인덱스와 타입이 생성된다. 위의 경우는 정해진 스키마없이 동적으로 인덱스와 타입이 생성된 경우이며 dynamic_templates 라는 기본 템플릿이 적용된다.

2) 템플릿을 적용한 경우





















위의 경우는 템플릿을 적용한 경우이다. 위에서 생성한 템플릿의 필드설정대로, 'class' 필드는 index 설정이 'not_analyzed'로 되어있다. 템플릿이 정상적으로 적용되어 데이터가 색인되었다는 증거다.

2015년 8월 5일 수요일

[Maven] 메이븐 모듈 (Module)

1. 개요



 프로젝트 진행시 초기에는 하나의 프로젝트만으로 개발하다가 규모가 커지거나 새로운 기능을 요구하는 경우에는 프로젝트를 분리하게 되는데, 이때 모듈 구조를 사용한다.

메이븐 모듈 방식을 사용하면 하나의 프로젝트가 어려개의 모듈을 가지고 여러개의 프로젝트(모듈)을 한번에 빌드할 수 있게 된다.


2. 개념



1) 상속

메이븐에서 모든 설정 파일은 최상위 pom.xml 파일을 상속하고 있다.
최상위 pom.xml 의 정보를 출력하기 위해서는 아래와 같은 명령어를 사용한다
$ mvn help:effective-pom
이렇게 기본으로 최상위 pom.xml 을 상속하듯이 프로젝트에서 공통으로 사용하는 설정은 공통 pom.xml 파일을 만들어 관리하고, 하위 모듈에서 이 pom.xml 파일을 상속 할 수 있다.
<!-- 부모 POM 파일 예제 -->
 
<project>
   <modelVersion>1.0.0</modelVersion>
   <groupId>com.mycorp</groupId>
   <artifactId>parent</artifactId>
   <packaging>pom</packaging>           <!-- packaging 엘리먼트 값을 'pom'으로 설정 -->
   <version>1.0-SNAPSHOT</version>
   .
   .
   .
이때 위의 설정처럼 부모 pom.xml 파일의 packaging 태그 값은  'pom'으로 설정 하여야한
다. 자식 pom.xml 파일은 <parent/> 엘리먼트를 이용하여 부모 pom.xml 파일을 상속한다
<!-- 자식 POM 파일 예제 -->
<project>
   .
   .
   <parent>
      <artifactId>parent</artifactId>
      <groupId>com.mycorp</groupId>    
      <version>1.0-SANPSHOT</version>
   </parent>
   . 
   .
</project>
모듈 구조는 일단 이러한 식으로 구성된다.

파일 tree로 보자면 일반적인 모듈 구조는 아래와 같다.
parent
├── batch
│   ├── src
│   └── pom.xml
├── core
│   ├── src
│   └── pom.xml
├── web
│   ├── src
│   └── pom.xml
└── pom.xml
일단 최상위 디렉토리인 parent에 부모 pom.xml 파일이 있다. 이를 위에서 예로 든 것처럼 batch, core, web 의 3개의 모듈의 pom.xml에서 상속하고 있다.

2) 집합

느낌상으로 알겠지만 core 모듈은 batch 와 web 모듈에서 공통적으로 쓰이는 클래스를 제공한다. 다시말해 각각 batch와 web은 core 모듈에 의존성이 존재한다.

한마디로 web을 빌드하려면 core -> web 순으로 빌드되어야 하고, batch를 빌드하려면 core -> batch 순으로 빌드가 되어야 하는것이다. 이런식으로 빌드가 되게 하기 위해 부모 pom.xml 파일은 아래와 같이 작성되어야 한다.
<project>
   <modelVersion>1.0.0</modelVersion>
   <groupId>com.mycorp</groupId>
   <artifactId>parent</artifactId>
   <packaging>pom</packaging>           
   <version>1.0-SNAPSHOT</version>
   <modules>
      <id>web-build</id>
      <module>core</module>  <!-- 모듈 목록을 등록한다 -->
      <module>web</module>
   </modules>
   <modules>
      <id>batch-build</id>
      <module>core</module>  <!-- 모듈 목록을 등록한다 -->
      <module>batch</module>
   </modules>
   .
   .
   .
각각 id로 구분하여 빌드해야할 모듈 목록을 등록한다. mvn 명령어로 빌드시에, 위에 설정된 대로 id값을 빌드파라메터로 전달하면, 해당 id를 가진 모듈들이 위에서부터 아래로 순서대로 빌드 된다.

다시 정리하자면, 하나의 프로젝트가 여러 모듈로 분리될 경우, 모듈간에 의존관계가 발생하므로 빌드를 개개의 모듈별로 진행하면 빌드가 실패할 수 있다. 따라서 여러 모듈을 빌드할때 같은 단위로 빌드 할 수 있도록 하는 기능이 '집합' 이다.

예를들어 web 프로젝트를 빌드하고 싶다면 아래와 같이 빌드한다!
$ mvn -P web-build clean package



[Maven] package org.junit does not exist

 메이븐 빌드시에 junit 의존성 패키지를 찾지 못하며 빌드가 실패 할때가 있다. 문제 해결을 위해서 compiler 플러그인, shade 플러그인 설정 등등을 찾아봤지만 삽질의 연속..

일단 문제는 아래와 같다.
5,209  SUCCESS>[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project amigo-service-broker: Compilation failure: Compilation failure:
  5,210  SUCCESS>[ERROR] xxx.java:[21,17] package org.junit does not exist
  5,211  SUCCESS>[ERROR] xxx.java:[21,17] package org.junit does not exist
알고보니 매우 흔한 이슈이다. 아래 Stack Overflow에 간단한 이슈 해결법이 나와있다.

http://stackoverflow.com/questions/5845990/maven-3-and-junit-4-compilation-problem-package-org-junit-does-not-exist

확인해보니 maven의 pom.xml파일에 JUnit 관련 의존성 설정이 아래와 같이 되어있었다.
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope> <!-- 이 설정을 제거! -->
</dependency>
test로 설정된 scope를 제거하면 된다. JUnit 라이브러리를 테스트 시에만 사용하도록 하는 설정인데, 빌드할때 해당 라이브러리가 포함되지 않았기 때문에 JUnit에 대한 패키지를 찾을 수 없다는 메시지가 나오며 빌드가 실패한 것이다.

2015년 8월 3일 월요일

[Elasticsearch] document routing

1. 개요



Elasticsearch는 기본적으로 'shard' 라는 개념으로 구성되어 있다. 이 'shard'라는것은 저장공간을 논리적으로 나눈 단위이다. document가 색인될때, 이 shard에 저장되는데, 이때 내부적인 routing 동작을 통해 각 shard에 분산 저장된다. 즉 유입되는 document들이 각 shard로 퍼지는 것이다.

이런경우, search 동작시에 성능상의 이슈를 불러올 수 있다. 왜냐하면 document를 검색할때에 모든 shard를 다 뒤질 것이기 때문이다. shard의 갯수가 많다면, 그리고 각 shard의 크기가 크다면, 검색은 상당한 시간이 걸릴 것이다.


2. Route



Elasticsearch에서는 이러한 검색 성능 튜닝 방법중의 하나로 'routing'이라는 기능을 제공한다. 이 방식은 routing 기준이 되는 field를 정하고, 그 기준 field의 값에 따라서 하나의 shard로 document를 색인하는 것이다.


3. 예제를 통해 살펴보자



아주 생각하기 쉬운 예를 들어 보겠다. 누구나 학창시절, 학기 초 반배정의 두근두근함을 기억할 것이다. 내가 몇반이 되었을까, 내가 원하는 친구랑 같은 반일까 하는... 기억들 말이다. 뭐 이건 잡설이고..

학생 10명이 있다고 하자, 그중 5명은 'A' 반으로, 3명은 'B' 반으로 그리고 2명은 'C' 반으로 배정을 받았다. 새학기 등교 첫날, 학생들은 각자 배정받은 반의 교실로 입실하여 수업준비를 할 것이다.

이 간단한 예시에서 학생은 'document', 반은 routing 기준이 되는 field, 교실은 'shard'에 비유할 수 있을 것이다.

직접 예제를 수행해 보자.

일단 아래와 같은 bulk 데이터를 준비하자
{"index": {"_index": "school", "_type": "students", "_id": 1}}
{"class": "A", "name": "albert"}
{"index": {"_index": "school", "_type": "students", "_id": 2}}
{"class": "A", "name": "david"}
{"index": {"_index": "school", "_type": "students", "_id": 3}}
{"class": "A", "name": "brown"}
{"index": {"_index": "school", "_type": "students", "_id": 4}}
{"class": "A", "name": "jimmy"}
{"index": {"_index": "school", "_type": "students", "_id": 5}}
{"class": "A", "name": "jennifer"}
{"index": {"_index": "school", "_type": "students", "_id": 6}}
{"class": "B", "name": "hong"}
{"index": {"_index": "school", "_type": "students", "_id": 7}}
{"class": "B", "name": "kim"}
{"index": {"_index": "school", "_type": "students", "_id": 8}}
{"class": "B", "name": "john"}
{"index": {"_index": "school", "_type": "students", "_id": 9}}
{"class": "C", "name": "tomas"}
{"index": {"_index": "school", "_type": "students", "_id": 10}}
{"class": "C", "name": "jamie"}
10명의 학생들에대한 간단한 정보다. 위에 예를 들었던 대로 5명은 A, 3명은 B, 2명은 C 클래스로 배정을 했다.

이제 위의 bulk 데이터를 인덱싱할 인덱스를 만들어야 하겠다. 아래와 같이 인덱스를 생성하자.
$ curl -XPUT 'localhost:9200/school?pretty' -d '{
    "mappings": {
        "students": {
            "_routing": {
                "required": true,
                "path": "class"
            },
            "properties": {
                "class": {
                    "type": "string",
                    "index": "not_analyzed"
                },
                "name": {
                    "type": "string"
                }
            }
        }
    }
}'
주목해야 할 것은 바로 '_routing' 설정이다. required 와 path 두가지 설정을 가지는데, 여기서 'path' 설정에 들어가는 값이 routing 기준이 되는 field 인 것이다.

그 밑에 'properties' 로 각 필드에 대한 설정을 하는데, 여기서 중요한 것은 'path' 설정에 들어간 field의 store 속성은 반드시 'true'로 해야하고 index 속성은 'not_analyzed'로 해야 한다는 것이다. 위의 예시에서 store는 기본값이 true이므로 생략하였고, index 속성은 'not_analyzed'로 설정하였다.

이제 아래의 curl을 이용하여 bulk 데이터를 색인해 보자.
curl -XPUT 'localhost:9200/_bulk?pretty' --data-binary @data.json
참고로 @파일이름 와 같은 형식으로 bulk 데이터 파일을 지정한다. data.json파일은 위의 10명의 학생 정보에 대한 bulk 데이터 파일이다.

일단 아래와 같이 school indice에 10개의 shard가 있다고 가정하자.













기준값 (class) 별로 routing이 잘 되었을까? shard를 하나씩 클릭해봄으로 확인 할 수 있다.














각각 0번, 8번, 9번 shard에 2개, 5개, 3개의 document가 색인 된 것을 볼 수 있다.

조회는 아래와같이 _search api 사용시 query string으로 routing 파라메터를 주어 조회 할 수 있다.
$ curl -XGET 'localhost:9200/school/students/_search?pretty&routing=B'
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "school",
      "_type" : "students",
      "_id" : "6",
      "_score" : 1.0,
      "_source":{"class": "B", "name": "hong"}
    }, {
      "_index" : "school",
      "_type" : "students",
      "_id" : "7",
      "_score" : 1.0,
      "_source":{"class": "B", "name": "kim"}
    }, {
      "_index" : "school",
      "_type" : "students",
      "_id" : "8",
      "_score" : 1.0,
      "_source":{"class": "B", "name": "john"}
    } ]
  }
}
routing파라메터 값을 'B'로 주었고, 그에따라 search api는 모든 shard를 검색하는것이 아니라 class : B 로 routing된 shard만 검색하여 결과를 보내준다.

이러한 routing을 통해서, 특정 데이터를 검색할때 성능을 향상 시킬 수 있는 것이다.