elasticsearch에 질의할 때, freemarker를 이용하여 아래와 같이 json 질의를 할 수 있다. 


{

  "query": {

    "filtered" : {

      "query" : {

        "query_string" : {

          "query" : "${queryString}"

        }

      }

    }

  }

}



queryString에 사용자가 별의 별 단어를 넣을 수 있는데. 이 때 잘못하면 큰 이상한 문제를 야기할 수 있다.

즉, 사용자가 입력한 모든 내용을 검색해주다가 lucene 문법에 따라 다양한 wildcard, 연산자(and, or, not)로 cpu를 많이 소비할 수 있는데, 이를 escape처리할 수 있다.


처리 방법은 다음과 같다. 


1. lucene 4에서는 QueryParser.escape() 메소드를 이용하여 escape 처리를 한다.

2. and, or, not 제거




참고:

http://lucene.apache.org/core/2_9_4/queryparsersyntax.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html

Reserved charactersedit

If you need to use any of the characters which function as operators in your query itself (and not as operators), then you should escape them with a leading backslash. For instance, to search for (1+1)=2, you would need to write your query as \(1\+1\)\=2.

The reserved characters are: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /


Failing to escape these special characters correctly could lead to a syntax error which prevents your query from running.



lucene4의 QueryParser의 escape()메소드


 
 /**
   * Returns a String where those characters that QueryParser
   * expects to be escaped are escaped by a preceding <code>\</code>.
   */
 
 public static String escape(String s) {
    StringBuilder
 sb = new StringBuilder();
   
 for (int i = 0; i < s.length(); i++) {
     
 char c = s.charAt(i);
     
 // These characters are part of the query syntax and must be escaped
     
 if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':'
        ||
 c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~'
        ||
 c == '*' || c == '?' || c == '|' || c == '&' || c == '/') {
       
 sb.append('\\');
      }
     
 sb.append(c);
    }
   
 return sb.toString();
  }



Posted by '김용환'
,


elasticsearch에서 특정 type의 모든 document를 삭제하려면, 다음과 같이 진행한다. 기존 DB의 truncate와 같은 기능으로 type을 delete하지 않고 사용할 수 있다.



간단하게 match_all 질의는 다음과 같다.


$ curl -XDELETE http://brown207.kr3.iwilab.com:9200/deleted_location_list/location/_query -d '{"query":{"match_all":{}}}'



또는 간단하게 match_all 질의에 bool연산자를 사용하여 지울 수 있다. 


$ curl -XDELETE http://es.google.com:9200/abtest/data/_query -d '{"query":{"bool":{"must": [ {"match_all":{}} ] }}}'




Posted by '김용환'
,


일래스틱서치는 fuzzy query를 지원한다. 


Fuzzy query와 Range query의 around 에서 사용하는 내용으로 조금 공부가 필요하다.


일래스틱서치는 내부적으로 사용하는 레펜스타인 알고리즘과 다메라우-레펜스타인 알고리즘, 자로 윙클러, ngram등을 지원하고 있으니, 아래 내용을 미리 살펴보고 이해하는 것이 좋다. 내용은 아래 설명이 잘 되어 있어서 특별히 하지 않는다.

http://ntz-develop.blogspot.de/2011/03/fuzzy-string-search.html

http://juggernaut.tistory.com/14 (한글 번역)




일래스틱서치는 레펜스타인 알고리즘(https://en.wikipedia.org/wiki/Levenshtein_distance)은 편집 거리(modified distance)의 을 사용하고 있다. 이 편집 거리의 개념을 매개변수로 받아들인다.


0, 1, 2

the maximum allowed Levenshtein Edit Distance (or number of edits)

AUTO

generates an edit distance based on the length of the term. For lengths:


0..2

must match exactly

3..5

one edit allowed

>5

two edits allowed

AUTO should generally be the preferred value for fuzziness.

출처 : https://www.elastic.co/guide/en/elasticsearch/reference/1.7/common-options.html#_numeric_date_and_ipv4_fields



키워드의 유사도 검사시 단순히 유사도 검색 뿐 아니라 오타(전치)까지도 포함하는 알고리즘인  다메라우 레펜스타인 알고리즘은 

fuzziness 기본 값은 AUTO가 디폴트일 때 사용될 때 쓰이니 보는 것이 좋은 것 같다..

https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance




실제 elasticsearch에서 suggest 에 관련 거리(distance) 


https://github.com/elastic/elasticsearch/blob/5278cf0d5e1afa6cf6d0959e839c0ed2408908f8/core/src/main/java/org/elasticsearch/search/suggest/SuggestUtils.java


   public static StringDistance resolveDistance(String distanceVal) {

        if ("internal".equals(distanceVal)) {

            return DirectSpellChecker.INTERNAL_LEVENSHTEIN;

        } else if ("damerau_levenshtein".equals(distanceVal) || "damerauLevenshtein".equals(distanceVal)) {

            return new LuceneLevenshteinDistance();

        } else if ("levenstein".equals(distanceVal)) {

            return new LevensteinDistance();

          //TODO Jaro and Winkler are 2 people - so apply same naming logic as damerau_levenshtein  

        } else if ("jarowinkler".equals(distanceVal)) {

            return new JaroWinklerDistance();

        } else if ("ngram".equals(distanceVal)) {

            return new NGramDistance();

        } else {

            throw new IllegalArgumentException("Illegal distance option " + distanceVal);

        }

    }


일래스틱서치는 XFuzzySuggester 클래스에 다메라우 레펜스타인 알고리즘을 기반으로 하여 좀 더 놓은 Fuzzy 추천 알고리즘을 추가하기도 했다. 

https://github.com/elastic/elasticsearch/blob/b582de79ae1b669e76d0ffb70e90075bd9d0cddf/core/src/main/java/org/apache/lucene/search/suggest/analyzing/XFuzzySuggester.java





참고 :

https://www.elastic.co/guide/en/elasticsearch/reference/1.7/query-dsl-fuzzy-query.html

http://ko.cyclopaedia.asia/wiki/Jaro%E2%80%93Winkler_distance

https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance

https://en.wikipedia.org/wiki/Levenshtein_distance

Posted by '김용환'
,


elasticsearch 2.0.0-alpha가 나온 마당인데, 소스 master는 3.0.0(lucene 5.4.0)을 향하고 있다. ㅠㅠ



elasticsearch 소스 컴파일 후 올려보면 다음 결과를 확인할 수 있다. 



Posted by '김용환'
,




java 언어로 짠 multiget 슈도 코드이다. 쓸만하다. 


개발해보니, rest api와 달리 에러에 대한 처리 내용이 없는 것이 결정적인 단점이다. GetResponse의 api에 전적으로 의존하게 된다.  그래서, 잘못된 매개변수로 존재하지 않는 것인지, 진짜 데이터가 없어서 난 것인지 분간하기 어렵다는 단점이 있다. (차라리 exception이 발생하도록 설계되거나 getError() 메소드가 있었으면 좋았을 텐데..)


private TransportClient searchClient;

.... MultiGetRequestBuilder multiGetRequestBuilder = searchClient.prepareMultiGet();

MultiGetRequest.Item item1 = new MultiGetRequest.Item("myindex", "mytype", "id1"); MultiGetRequest.Item item2 = new MultiGetRequest.Item("myindex", "mytype", "id2");
multiGetRequestBuilder.add(item1); multiGetRequestBuilder.add(item2);

List<String> ids = Lists.newArrayList();
MultiGetResponse response = multiGetRequestBuilder.execute().actionGet(500);
if (response != null && response.getResponses() != null) {
MultiGetItemResponse[] multiGetItemResponse = response.getResponses();
for (MultiGetItemResponse multiGetResponse : multiGetItemResponse) {
GetResponse getResponse = multiGetResponse.getResponse();

if (getResponse != null) {

if (getResponse != null && getResponse.isExists()) {
ids.add(getResponse.getId());
}
} else {
logger.error("getResponse : null");
}
}

}



참고로 rest api는 다음과 같다.


성공 예)


$ curl -XGET 'http:/intern.google.com:9200/action_list/action/_mget' -d '{
    "ids" : ["1111"]
}'

{"_index":"action_list","_type":"action","_id":"1111","_version":1,"found":true,"_source":{}}
 

실패 예)  - json에 error 키를 발견할 수 있다.


$ curl -XGET http://intern.google.com:9200/action_list/action/_mget   -d '{
    "ids" : ["1111"]
}'

{"docs":[{"_index":"action_list","_type":"action","_id":"1111","error":"[action] missing"}]}



Posted by '김용환'
,


일래스틱서치에서 색인 삭제시 wildcard 와 _all 사용 못하게 설정할 수 있다. 



action.destructive_requires_name=true


설정을 true로 하면, _all 또는 * 를 사용한다.


참고 : https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html



코드를 살펴보면, DestructiveOperations 클래스의 hasWildcardUsage()메소드에서 _all, *에 대한 요청시 destructive_requires_names가 true이면 true임을 리턴한다. 



public final class DestructiveOperations implements NodeSettingsService.Listener {


    /**

     * Setting which controls whether wildcard usage (*, prefix*, _all) is allowed.

     */

    public static final String REQUIRES_NAME = "action.destructive_requires_name";
...


    private static boolean hasWildcardUsage(String aliasOrIndex) {

        return "_all".equals(aliasOrIndex) || aliasOrIndex.indexOf('*') != -1;

    }



...

if (hasWildcardUsage(aliasesOrIndex)) {

                    throw new ElasticsearchIllegalArgumentException("Wildcard expressions or all indices are not allowed");

                }
..
}


Posted by '김용환'
,



모든 다큐먼트는 uid라는 식별자를 가지고 있다. _id 말고, 사용자는 보이지 않지만 쓰이지 않는 키가 있다. 

다큐먼트 타입에 _id 값이 더하진 문자열이다. 타입이 location이고 _id가 12345 라면, _uid는 location#12345 이다. 


일래스틱서치는 색인을 주지 않더라도 타입과 다큐먼트 id만 줄 때, 검색할 수 있다. 최소한 이 때 사용한다고 할 수 있다. 


관련 테스트 코드는 SimpleValidateQueryTests.java에 있다.  


final String typeFilter = filter("_type:type1");

assertExplanation(QueryBuilders.queryString("_id:1"),        equalTo("filtered(ConstantScore(_uid:type1#1))->" + typeFilter));


assertExplanation(QueryBuilders.idsQuery("type1").addIds("1").addIds("2"),

 equalTo("filtered(ConstantScore(_uid:type1#1 _uid:type1#2))->" + typeFilter));




Posted by '김용환'
,


일래스틱서치가 초기화시, 


InternalSettingsPreparer 클래스의 prepareSettings() 메소드가 호출되며, 설정 관련 부분을 읽는다.


두 가지로 나눠지는데, 첫 번째는 시스템 설정 내용과 다양한 설정 파일(yml, json, properties, config)로부터 읽는 부분이다. 

SettingBuilder에 에 저장한다. 




public class InternalSettingsPreparer {


    public static Tuple<Settings, Environment> prepareSettings(Settings pSettings, boolean loadConfigSettings) {

        // ignore this prefixes when getting properties from es. and elasticsearch.

        String[] ignorePrefixes = new String[]{"es.default.", "elasticsearch.default."};

        boolean useSystemProperties = !pSettings.getAsBoolean("config.ignore_system_properties", false);

        // just create enough settings to build the environment

        ImmutableSettings.Builder settingsBuilder = settingsBuilder().put(pSettings);

        if (useSystemProperties) {

            settingsBuilder.putProperties("elasticsearch.default.", System.getProperties())

                    .putProperties("es.default.", System.getProperties())

                    .putProperties("elasticsearch.", System.getProperties(), ignorePrefixes)

                    .putProperties("es.", System.getProperties(), ignorePrefixes);

        }

        settingsBuilder.replacePropertyPlaceholders();


        Environment environment = new Environment(settingsBuilder.build());


        if (loadConfigSettings) {

            boolean loadFromEnv = true;

            if (useSystemProperties) {

                // if its default, then load it, but also load form env

                if (Strings.hasText(System.getProperty("es.default.config"))) {

                    loadFromEnv = true;

                    settingsBuilder.loadFromUrl(environment.resolveConfig(System.getProperty("es.default.config")));

                }

                // if explicit, just load it and don't load from env

                if (Strings.hasText(System.getProperty("es.config"))) {

                    loadFromEnv = false;

                    settingsBuilder.loadFromUrl(environment.resolveConfig(System.getProperty("es.config")));

                }

                if (Strings.hasText(System.getProperty("elasticsearch.config"))) {

                    loadFromEnv = false;

                    settingsBuilder.loadFromUrl(environment.resolveConfig(System.getProperty("elasticsearch.config")));

                }

            }

            if (loadFromEnv) {

                try {

                    settingsBuilder.loadFromUrl(environment.resolveConfig("elasticsearch.yml"));

                } catch (FailedToResolveConfigException e) {

                    // ignore

                } catch (NoClassDefFoundError e) {

                    // ignore, no yaml

                }

                try {

                    settingsBuilder.loadFromUrl(environment.resolveConfig("elasticsearch.json"));

                } catch (FailedToResolveConfigException e) {

                    // ignore

                }

                try {

                    settingsBuilder.loadFromUrl(environment.resolveConfig("elasticsearch.properties"));

                } catch (FailedToResolveConfigException e) {

                    // ignore

                }

            }

        }


        settingsBuilder.put(pSettings);

        if (useSystemProperties) {

            settingsBuilder.putProperties("elasticsearch.", System.getProperties(), ignorePrefixes)

                    .putProperties("es.", System.getProperties(), ignorePrefixes);

        }

        settingsBuilder.replacePropertyPlaceholders();


        // allow to force set properties based on configuration of the settings provided

        for (Map.Entry<String, String> entry : pSettings.getAsMap().entrySet()) {

            String setting = entry.getKey();

            if (setting.startsWith("force.")) {

                settingsBuilder.remove(setting);

                settingsBuilder.put(setting.substring(".force".length()), entry.getValue());

            }

        }

        settingsBuilder.replacePropertyPlaceholders();

...

}






두 번째는 노드 이름과 클러스터 이름을 정한다. name 또는 node.name 키에 할당된 값이 없다면, 


src/main/resources/config/names.txt 를 읽어 임의로 선택된 노드 이름을 얻는다. (names.txt 파일을 보면 별의 별 이름이 들어가 있다.. TransportClient, NodeClient를 이용하여 일래스틱서치 클러스터에 연결하면 이 이름 중의 하나를 받는다. 그래서 이상한 로그로 보이는 부분이 발견할 수 있다.)


클러스터 이름을 정의하지 않으면 디폴트 이름을 쓰게 되어 있다.





    public static Tuple<Settings, Environment> prepareSettings(Settings pSettings, boolean loadConfigSettings) {

...
        // generate the name

        if (settingsBuilder.get("name") == null) {

            String name = System.getProperty("name");

            if (name == null || name.isEmpty()) {

                name = settingsBuilder.get("node.name");

                if (name == null || name.isEmpty()) {

                    name = Names.randomNodeName(environment.resolveConfig("names.txt"));

                }

            }


            if (name != null) {

                settingsBuilder.put("name", name);

            }

        }


        // put the cluster name

        if (settingsBuilder.get(ClusterName.SETTING) == null) {

            settingsBuilder.put(ClusterName.SETTING, ClusterName.DEFAULT.value());

        }


        Settings v1 = settingsBuilder.build();

        environment = new Environment(v1);


        // put back the env settings

        settingsBuilder = settingsBuilder().put(v1);

        // we put back the path.logs so we can use it in the logging configuration file

        settingsBuilder.put("path.logs", cleanPath(environment.logsFile().getAbsolutePath()));


        v1 = settingsBuilder.build();


        return new Tuple<>(v1, environment);

    }




ClusterName은 바로 "elasticsearch"이다. 



public class ClusterName implements Streamable {


    public static final String SETTING = "cluster.name";


    public static final ClusterName DEFAULT = new ClusterName("elasticsearch".intern());

}


'Elasticsearch' 카테고리의 다른 글

[elasticsearch] action.destructive_requires_name  (0) 2015.09.16
[elasticsearch] 다큐먼트 uid  (0) 2015.09.15
[elasticsearch] mget  (0) 2015.09.09
[elasticsearch] 로그 설정  (0) 2015.09.08
[elasticsearch] 한 샤드 최대 크기  (0) 2015.09.04
Posted by '김용환'
,



elasticsearch mget의 성능은 괜찮은 것 같다. 하나 또는 여러 개를 얻어도(get/mget) 성능상 크게 느려지지 않는다.


테스트 결과, 1개의 document를 얻는 성능과 9개의 document를 얻는 성능은 거의 비슷했다.


참고

https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html



Posted by '김용환'
,


일래스틱서치에서 초기 로깅 설정은 INFO 이다. 

config/logging.yml 파일에서 확인가능하다.


es.logger.level: INFO

rootLogger: ${es.logger.level}, console, file




로그는 다음과 같이 나온다.


[2015-09-08 16:33:49,075][INFO ][node                     ] [node_1] version[1.4.1], pid[49549], build[89d3241/2014-11-26T15:49:29Z]

[2015-09-08 16:33:49,075][INFO ][node                     ] [node_1] initializing ...

[2015-09-08 16:33:49,091][INFO ][plugins                  ] [node_1] loaded [....]

[2015-09-08 16:33:50,984][INFO ][node                     ] [node_1] initialized

[2015-09-08 16:33:50,984][INFO ][node                     ] [node_1] starting ...

[2015-09-08 16:33:51,139][INFO ][transport                ] [node_1] bound_address {inet[/0:0:0:0:0:0:0:0:9300]}, publish_address {inet[/1.1.1.1:9300>

[2015-09-08 16:33:51,161][INFO ][discovery                ] [node_1] elasticsearch/js0vKWrgSuK9091fZT5TKg

[2015-09-08 16:33:54,175][INFO ][cluster.service          ] [node_1] new_master [node_1][js0vKWrgSuK9091fZT5TKg][Samuelui-MacBook-Pro.local][inet[/1.1.1>

[2015-09-08 16:33:54,206][INFO ][http                     ] [node_1] bound_address {inet[/0:0:0:0:0:0:0:0:9200]}, publish_address {inet[/1.1.1.1:9200>

[2015-09-08 16:33:54,206][INFO ][node                     ] [node_1] started



상세한 로그를 보려면 config/logging.yml 파일에서 root logger 의 로거 레벨을 DEBUG로 수정한다.


es.logger.level: DEBUG

rootLogger: ${es.logger.level}, console, file


일래스틱서치를 재시작하면, 엄청 많은 로그가 출력된다. 대충 살펴보니 다음 작업을 알려주는 로그가 출력한다. 

(하나 기준으로 잡고 소스 살펴보면 좋을 듯 하다.)


elasticsearch 커맨드 또는 admin 커맨드에 대한 쓰레드 풀 초기화,

네트워킹 초기화,

probe 초기화,

메타데이터 읽고, 소요되는 시간 측정,

색인 메모리 할당,

클러스터(샤드) 초기화,

색인 초기화 등이다. 


'Elasticsearch' 카테고리의 다른 글

[elasticsearch] InternalSettingsPreparer.java 분석  (0) 2015.09.15
[elasticsearch] mget  (0) 2015.09.09
[elasticsearch] 한 샤드 최대 크기  (0) 2015.09.04
elasticsearch 2.0 출시 (2015.8.26)  (0) 2015.08.27
[elasticsearch] optimize api  (0) 2015.08.26
Posted by '김용환'
,