top bar

글 목록

2015년 5월 30일 토요일

[Elasticsearch] Inverted Index

개요



 이전 포스팅에서, Elasticsearch의 'Full Text Search' 라는 것이 어떠한 관점에서 수행되는지 살펴 보았다.

 몇개의 검색어로 자신이 찾고자 하는 정보를 정확하게 찾기는 매우 힘들겠지만, 사용자는 자신이 의도한 바와 '가까운' 검색결과를 얻기를 원한다. 이러한 정확도가 높을 수록 좋은 검색엔진인 것이다.

 이러한 'Full Text Search'라는 맥락에서, Elasticsearch는 정보를 색인할때 'Inverted Index' 라는 구조를 생성한다. 이것이 무엇일까? 오늘은 이 'Inverted Index' 라는것이 무엇인지 살펴 보고자 한다.

Inverted Index



원문 - https://www.elastic.co/guide/en/elasticsearch/guide/master/inverted-index.html

 이 개념을 이해하기는 쉽지 않았다. 인덱스면 인덱스지 'Inverted' 인덱스라..? 우리말로 억지로 해석하자면 '역인덱스' 정도가 되는건가? 아무튼 공식 가이드에 아주 친절하게 설명되어있다. (발 번역은 양해바란다)

 가령 2개의 document가 있고 'content'라는 field를 가지고 있다고 하자. 각 document의 content field에는 아래와같은 문장들이 각각 저장되어 있다.
 1. The quick brown fox jumped over the lazy dog
 2. Quick brown foxes leap over lazy dogs in summer
이때 Elasticsearch는 Inverted Index를 생성하기 위해 각 문장의 단어(term 또는 token이라고도 한다)를 분리하고 정렬한 다음, 각 단어가 어느 document에 있는지 표시한다. 그렇게되면 아래와같은 표가 완성될 것이다.
Term      Doc_1  Doc_2
-------------------------
Quick   |       |  X
The     |   X   |
brown   |   X   |  X
dog     |   X   |
dogs    |       |  X
fox     |   X   |
foxes   |       |  X
in      |       |  X
jumped  |   X   |
lazy    |   X   |  X
leap    |       |  X
over    |   X   |  X
quick   |   X   |
summer  |       |  X
the     |   X   |
------------------------
자, 이제 'quick brown' 이라는 검색어를 입력하였다고 치자.
그러면 아래와 같은 매칭 리스트가 만들어질 것이다.
Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
quick   |   X   |
------------------------
Total   |   2   |  1
'brown'과 'quick' 이란 단어는 Doc_1에서 는 모두 검색이 되었고, Doc_2에서는 'brown'이라는 단어 하나만 검색이 되었다. 따라서 Doc_1이라는 문서가 우리가 의도했던 검색결과에 가장 가까운 문서가 될 것이다.

이것이 'Inverted Index'를 생성해 검색을 수행하는 기본적인 원리이다. 그렇지만 계속 드는 의문중 하나, 왜 'Inverted' 일까? 그냥 인덱스와 '역'인덱스는 무슨 차이가 있을까? 아래 URL을 참고하자.

http://www.quora.com/Information-Retrieval/What-is-inverted-index

정말 헷갈렸지만, 위 URL에서 말하고자 하는바를 간단히 요약하자면,

  • 인덱스 = 책 앞부분의 목차
  • 역인덱스 = 책 뒷부분의 색인

인 것 이다!! 왠만한 기술서적은 맨 뒷부분에 해당 단어가 어떤 페이지에 나와있는지를 list up 한 '색인' 이라는 페이지가 있다. 바로 이것이 '역'인덱스 인것이다. 예를 들어 아래사진과 같다!


한마디로 걍(?)인덱스는 페이지를 어떠한 topic에 매핑 시킨다. 하지만 '역'인덱스는 단어(term)를 페이지에 매핑 시키는것이다. 어떻게보면 서로 반대되는 개념이다. 그래서 '역'이라는 단어가 붙었나 보다.

책 전체 에서 어떠한 단어를 찾을때 책 첫장부터 한장씩 넘기는 멍청한짓을 하는 사람은 없을 것이다. 따라서 이러한 '역'인덱스는 단어를 통해서 문서를 찾는 Full Text Search 에 최적의 속도를 내는 방식이라고 할 수 있다.

하지만 위의 예제는 아래와 같은 몇가지 문제점이 있다.

  • 'Quick'과 'quick'은 따로 리스트에 있지만 사실 같은 단어이다 (대소문자)
  • 'fox'와 'foxes'는 매우 비슷한 단어이고, 'dog'와 'dogs'도 마찬가지다. (복수형)
  • 'jumped'와 'leap'이라는 단어도 같은 의미이기에 'jumped'으로도 'leap' 매칭되어야 하는것이 아닌가? (동의어)

만약의 위의 역인덱스를 바탕으로 'Quick fox' 를 검색한다면 (두개의 단어가 AND 조건일때) 검색결과는 하나도 나타나지 않을 것이다.

 Doc_1은 'quick fox' 가 포함되어있고 Doc_2는 'Quick foxes' 가 포함되어 있기때문이다. 따라서 어디에도 매칭되지 않는다.

이러한 문제를 해결하기 위해 Elasticsearch는 'Token Filter'라는 모듈을 제공하여 단어를 가공한다. 예를들어 아래와 같은 작업을 하는 것이다.

  • 대문자로 시작하는 'Quick'은 lowercasing하여 'quick'으로 색인한다.
  • 'foxes'와 'dogs'는 그 뿌리가 되는 단어인 'fox'와 'dog'으로 색인한다.
  • 'jumped'와 'leap'은 동의어 이므로, 하나의 단어인 'jump'로 색인한다.

이러한 작업을 마치게되면 아래와같은 역인덱스가 완성된다.
Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
dog     |   X   |  X
fox     |   X   |  X
in      |       |  X
jump    |   X   |  X
lazy    |   X   |  X
over    |   X   |  X
quick   |   X   |  X
summer  |       |  X
the     |   X   |  X
------------------------
색인된 단어들이 정제되고 가공 되었다. 하지만 여전히 'Quick fox'는 검색되지 않는다. 가공하는 과정에서 'Quick'이라는 단어는 lowercasing 때문에 색인되지 않았기 때문이다. 이때 Elasticsearch는 위의 필터링규칙을 우리가 사용한 query string에 그대로 적용시킨다. 그렇게되면 우리가 입력한 검색어는 'quick fox'로 가공되고 그때야 비로소 Doc_1, Doc_2 두개의 문서가 검색결과로 나타나게 될 것이다.

이것이 Elasticsearch의 'Anaysis' 과정이다.

대략 두가지의 과정 즉, 단어의 분리와 가공의 과정을 거치는데 전자는 'Tokenizer', 후자는 'Token Filter'가 담당한다. 이러한 'Analysis'의 구체적인 과정은 다음 포스팅으로 미루겠다. 어짜피 나도 공신력(?) 있는 Elasticsearch 공식문서에서 거의 모든 정보를 얻고있다.

참고하자. https://www.elastic.co/guide/index.html

PS : 이러한 Inverted Index 원리는 비단 Elasticsearch에서만 사용되는것이 아니다. 그러므로 잘 알아두면 분명 좋을 것이라고 믿는(?)다.

2015년 5월 24일 일요일

[JAVA] 컨테이너 - 기초

개요



 이번엔 벼르고 별러왔던 자바 컨테이너(Container)에 대한 정리를 하려고 한다. 객체의 '저장' 이라는 관점에서 우리가 흔히 생각하는 것은 배열(array)이다. 특히나 원시(primitive)타입의 값들을 저장하여 다룰때 배열을 많이 사용한다.

 하지만 코드를 작성하는 시점에서, 우리가 필요홀 하는 객체들이 얼마나 되는지, 또는 객체 저장을 위해 더 복잡한 방법이 필요한지 알 수 없다.  따라서 '크기가 한번 정해지면 바꿀 수 없다' 라고 하는 것은 배열의 가장 큰 단점이며, 그로인한 제약은 상당히 크다.

 이러한 문제의 해결 방안으로 java.util 라이브러리에는 컨테이너(container) 클래스 들이 있으며, 그것의 기본 타입들은 List, Set, Queue, Map 이다.

 이번 포스트에는 컨테이너의 기본적인 원리들을 살펴보고, 다음 포스트에 본격적인 컨테이너의 Usage를 정리하려고 한다.


1. Generics와 컨테이너



아래 코드를 보자.

import java.util.ArrayList;

class Apple {

    private static long counter;
    private final long id = counter++;

    public long id() {
        return id;
    }
}

class Orange {
}

/**
 * @author Jupyo Hong
 */
public class ApplesAndOrangesWithoutGenerics {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        
        @SuppressWarnings("rawtypes")
        ArrayList apples = new ArrayList();
        
        for(int i = 0; i < 3; i++) {
            apples.add(new Apple());
        }
        
        // 같은 컨테이너에 다른 타입의 객체(Orange)를 추가해도 막지 않는다
        apples.add(new Orange());
        
        for(int i = 0; i < apples.size(); i++) {
            // Orange 타입의 요소를 가져와 id() 메서드를 호출할때 
            // RuntimeException 이 발생한다.
            ((Apple)apples.get(i)).id(); 
        }
    }
}

위 코드의 주석처럼, ArrayList 컨테이너를 생성할때 Generic을 지정하지 않으면, 아무 Type 이나 객체를 저장할 수 있다. 이는 당연히 두번째 for 문에서처럼 Apple 타입으로 캐스팅 후에 id() 메서드를 호출할때 발생하는 RuntimeException 때문에 문제가 된다. 왜냐하면 Orange 클래스에는 id() 메서드가 없기 때문이다.

따라서 아래와 같이 제네릭을 지정한다

ArrayList<Apple> apples = new ArrayList<Apple>();

apples.add(new Orange()); // Compile Error 발생!

제네릭을 지정하면, 지정한 Type외에 다른타입을 추가하려고 할때 Compile 시점에서 Error를 발생시켜 실수를 방지 할 수 있다. 또한 컨테이너에서 요소를 꺼낼때도, 캐스팅을 할 필요없이 제네릭으로 지정한 Type으로 알아서 캐스팅해준다. 아래와 같다.

ArrayList<apple> apples = new ArrayList<apple>();
        
for(int i=0; i<3; i++) {
    apples.add(new Apple());
}       
        
for(int i=0; i<apples.size(); i++) {
    System.out.println(apples.get(i).id()); // 캐스팅을 하지 않는다.
}

따라서 제네릭을 사용하면 컨테이너에 넣는 객체의 Type을 컴파일러가 확인해주고, 구문도 더 깔끔해진다.


2. 기본 개념들



 자바 컨테이너 라이브러리는 다음과 같은 두 개의 서로 다른 개념으로 나뉜다.

Collection
Map
하나 이상의 규칙이 적용되는 개별적인 요소들을 모아 놓은 것이다. List Sequencial 하게 객체들을 저장하며, Set은 중복요소를 가질 수 없다. QueueQueuing Discipline 에 의해 결정된 순서로 요소들을 산출 한다
(key)와 그에 대응되는 (value)의 쌍으로 구성되는 객체들을 모아 놓은 것으로, key를 사용하여 값을 검색할 수 있다. 연관 배열(associative array)이라고도 한다.


3. 컨테이너를 사용한 객체 저장과 출력



다음 코드는 Collection(List, Set)과 Map을 사용하여 객체를 저장하고, 콘솔로 출력하는 예제다. 코드와 출력결과를 잘 살펴보면 각 컨테이너의 특징을 알 수 있다.

public class PrintingContainers {
 
 static Collection fill(Collection<string> collection) {
  collection.add("dog");
  collection.add("rat");
  collection.add("cat");
  collection.add("dog");
  return collection;  
 }
 
 static Map fill(Map<String, String> map) {
  map.put("rat", "Fuzzy");
  map.put("cat", "Rags");
  map.put("dog", "Bosco");
  map.put("dog", "Spot");
  return map;
 }
 
 public static void main(String[] args) {
  
  System.out.println("ArrayList : \t" + fill(new ArrayList<string>()));
  System.out.println("LinkedList : \t" + fill(new LinkedList<string>()));
  System.out.println("HashSet : \t" + fill(new HashSet<string>()));
  System.out.println("TreeSet : \t" + fill(new TreeSet<string>()));
  System.out.println("LinkedHashSet : " + fill(new LinkedHashSet<string>()));
  System.out.println("HashMap : \t" + fill(new HashMap<String, String>()));
  System.out.println("TreeMap : \t" + fill(new TreeMap<String, String>()));
  System.out.println("LinkedHashMap : " + fill(new LinkedHashMap<String, String>()));
 }
}

## 출력결과
ArrayList :     [dog, rat, cat, dog]
LinkedList :    [dog, rat, cat, dog]
HashSet :       [dog, cat, rat]
TreeSet :       [cat, dog, rat]
LinkedHashSet : [dog, rat, cat]
HashMap :       {dog=Spot, cat=Rags, rat=Fuzzy}
TreeMap :       {cat=Rags, dog=Spot, rat=Fuzzy}
LinkedHashMap : {rat=Fuzzy, cat=Rags, dog=Spot}
ArrayList, LinkedList : 중복된 요소가 있으며 요소를 추가한 순서대로 출력된다.
HashSet : 중복이 허용되지 않았고, 순서가 보장되지 않았다.
TreeSet : 중복이 허용되지 않았고, 요소들이 오름차순으로 정렬되어 출력되었다.
HashMap : 키(key)와 값(value)가 '=' 로 묶여서 출력되었다. 순서를 보장하지 않는다.
TreeMap : HashMap과 같은 형식으로출력되며, key들이 정렬되어 출력된다.
LinkedHashMap : HashMap과 같은형식으로 출력되며, 추가된 순서를 보장한다.

 참고로 우리가 컨테이너를 쓰는 가장 큰 이유중 하나는 '크기를 지정할 필요가 없다' 라는 것이다. 이들 컨테이너는, 요소가 추가될 때마다 크기를 동적으로 증가시킨다.

2015년 5월 17일 일요일

[Elasticsearch] Exact Values Versus Full Text

1. 기본 개념


원문 - Exact Values Versus Full Text

요즘 Elasticsearch 공부에 푹 빠져있다. 회사업무가 바쁜터라, 많은 시간을 Elasticsearch 공부에 할애하진 못하지만, 공부할 때마다 흥미진진한 것 같다.

오늘은 Elasticsearch을 '검색엔진' 답게 해주는 근본 원리에 대한 내용을 포스팅 할까 한다.

(1) Exact Values

공식홈페이지의 원문 단어 그대로 인용해 보았는데, Exact Values란 말 그대로 '정확한 값' 이다. 예를들자면 사용자 ID라던가, 사용자이름, email 주소 등이 되겠다.
Exact Value의 관점에서 보았을때, 'Foo'와 'foo'는 같은 값이 아니다. 같은 논리로, '2014'와, '2014-09-15'도 같은 값이 아닌 것이다.

(2) Full Text

이와는 반대로 Full Text개념은 인간언어로 작성된 'textual data'라고 할 수 있다. 예를 들어 email안의 body내용과 같은 것이다.


Note:  full text를 흔히 '비구조화된 데이터'(unstructured data)라고 하는데, 이는 엄밀히 말하면 잘못된 용어다. 왜냐하면 자연어(natural language)는 굉장히 '구조적'인 양상을 띄고 있기 때문이다. 문제는 이러한 자연어는 매우 복잡한 구조를 가지고 있고, 컴퓨터가 이를 정확히 parsing하기 어렵다는 것이다. 예를들어보자면 아래와 같은 문장이 있다.

'May is fun but June bores me'

여기서 May, June은 사람을 나타내는 것일까 아니면 '달'(月)을 나타내는 것일까?


2. 질의(Query) 관점에서 Exact Values와 Full Text 비교



Exact Values는 query하기가 쉽다. '이 값이 query 조건과 맞는가 맞지 않는가?' 라고하는 이분법적인 비교를 하기 때문이다. 이러한 query는 아래의 SQL문으로 간단히 표현할 수 있다.
WHERE name    = "John Smith"
  AND user_id = 2
  AND date    > "2014-09-15"
하지만, full-text 데이터를 query하는 것은 좀더 복잡 미묘하다. 우리는 단순히, '이 document가 query조건에 맞는가?' 라는 질문을 던질 수 없다. 대신 '이 document가 query조건에 얼마나 가까운가?' 라고 질문할 수 있을 것이다.

보통 검색을 할때, '완전한 문장'을 정확히 찾으려는 시도는 별로 없을 것이다. 하지만, 검색어에 좀더 가까운, 즉 우리가 의도한 대로 검색결과가 나오길 원할 것이다.
예를들어 아래와 같은 경우다

  • 'UK' 라는 검색어를 입력했다면 'United Kingdom'과 관련된 검색결과가 나와야한다
  • 'jump'라는 검색어는 'jumped', 'jumps', 'jumping' 등과 매칭되어야 하며, 심지어는 동의어인 'leap'과도 매칭되어야 한다.
  • 'johnny walker'는 'Johnnie Walker', 그리고 'johnnie depp'은 'Johnny Dep'과 매칭되어야한다
  • 'fox new hunting'이라는 검색어는 Fox News의 사냥관련 정보들과 매칭되어야 한다. 반대로, 'fox hunting news'는 조금 이상하긴하지만 여우들의 사냥 news 관련 정보들이 매칭되어야 할 것이다.

이러한 유형의 query들이 동작하기위해 Elasticsearch는 첫번째로 색인된 text를 'Analyze'하고, 이렇게 분석한 데이터로 'Inverted Index'를 생성한다. 

다음 포스팅에서, inverted index와 이를 바탕으로한 analysis process를 살펴 보겠다.

to be continue...

2015년 5월 9일 토요일

[Elasticsearch] Fielddata (2) - CircuitBreaker와 Limiting Memory Usage

1. Fielddata의 Memory 관련 이슈



이전 글에서, Fielddata는 모든 document의 field 값을 메모리에 적재한다고 하였다. 

여기서 언급 되지 않는 부분이 있는데, Elasticsearch 클러스터에는 새로운 document가 끊임 없이 색인 된다는 것이다. 그렇게되면 당연히 Fielddata의 크기도 점점 커질 것이고, 점점 더 많은 메모리를 사용할 것이다.

Elasticsearch는 알다시피 자바 환경에서 실행되는 어플리케이션이고, 할당된 메모리 즉 heap size가 존재한다. 이러한 환경에서, Fielddata의 크기가 할당된 heap size를 넘어서면 당연히 'Out Of Memory' 가 발생할 것이다.

이러한 이유로 인해, Elasticsearch에서는 'Circuit Breaker'(차단기) 라는 장치를 마련해 두고 있다.

2. Circuit Breaker와 Limiting Memory Usage



원문 - Limiting Memory Usage


* Circuit Breaker

circuit breaker는 이러한 메모리 이슈에 대응하기 위해 디자인된 일종의 예방 장치이다.
현재 수행하는 query가 얼마만큼 메모리를 필요로하는지 미리 계산하고 실제로 fielddata가
미리 설정된 메모리 상한이 넘어가도록 로딩되는지 검사한다. 이를 초과하게되면 해당 쿼리를 중지시키고
Exception을 발생시킨다.

한마디로 OOM이 발생하여 node가 죽어버리는 것을 방지하기 위한 장치인 셈이다.

Elasticsearch의 공식 문서에는 이에 대하여, OOM발생으로 인해 node가 뒤지는(?) 것보다 차라리 Exception이 발생하여 쿼리가 중지되는 편이 낫다(;;).. 라고 말하고 있다.

아래는 필자가 실무에서 실제로 만났던 CircuitBreakingException(이하 CBE)이다.


Circuit Breaker가 작동하는 메모리 사용 기준은 아래 api로 설정 할 수 있다.
$ curl -XPUT 'localhost:9200/_cluster/settings?pretty'
{
  "persistent" : {
    "indices.breaker.fielddata.limit" : "[percentage of heap size]"
  }
}
위 설정의 default 값은 "60%" 이다. 따라서 필자가 경험했던 이슈는 당시 Elasticsearch 각 node의 heap size가 8G였고, query에 사용되는 Fielddata의 크기가 8G의 60% 즉, 약 4.7G 을 넘었기 때문에 CBE가 발생한 것이었다.

위에서 언급했다 시피, CBE가 발생하면 query는 수행되지 않는다. 일단 필자가 취한 조치는 heap size의 크기를 16G로 늘리고, 'indice.breaker.fielddata.limit'의 percentage를 70%로 늘린 것이었다.

하지만 이것이 근본적인 해결책은 아니다. 왜냐하면 어찌됐든 새로운 document는 계속하여 색인될 것이고, 그에따라 Fielddata의 크기도 계속 커지게되면, 언젠간 heap size의 70%
도 넘어서게 될 것이기 때문이다. 그렇게되면 다시 CBE를 만나게 될것이다.

이를 해결 하는 방법은 Fielddata의 Cache size를 설정하여, 오래된 Field 값을 'Evict' 하는것이다. 이는 conf/elasticsearch.yml 파일에서 위의 설정처럼 heap size 대비 %로 아래와 같이 정의할 수 있다.
## conf/elasticsearch.yml

indices.fielddata.cache.size: [percentege of heap size]

이 값은 percentage값 뿐만 아니라 명시적인 용량 값(ex 5g 등..) 으로도 설정 할 수 있다. 이를 설정하지 않으면, Fielddata는 breaker가 작동하는 기준까지 계속하여 field값을 메모리에 로딩해 버린다. 

주의해야 할것은 'indices.fielddata.cache.size'값이 'indices.breaker.fielddata.limit'값보다 크면 안된다는 것이다. 그렇게 되어 버리면 fieldata에 로딩된 데이터를 evict 시키지 못하고 CBE가 발생해 버리기 때문이다.

위의 2가지 설정으로 CBE이슈는 해결할 수 있었다.

하지만 메모리 사용 이슈에 대한 가장 좋은 대응 방법은 이전 글에서 언급했다시피 node 확장을 통한 메모리 사용을 분산 시키는 것 이다. 물론 장비를 증설 해야 되므로 돈이 좀 들어가겠지만...

ES에는 메모리를 사용하는것 대신에 disk를 사용하는 'Doc Value' 라는 개념이 있다. disk에 여유가 있다면 사용해 볼만한 장치다. 나중포스트에 이 개념도 살펴 보도록 하겠다. (언제가될진 모르겠지만..)

2015년 5월 3일 일요일

[Elasticsearch] Fielddata (1) - 개요

1. Fielddata 란?


 만약 특정 field로 정렬을 수행할때, query에 매칭되는 모든 document의 정렬 기준 field에 접근을 해야한다. 역인덱스(inverted index)라고 불리는 이러한 구조는 검색을 수행할때 탁월하지만, 정렬을 수행할 경우 이상적인 방법은 아니다.

역인덱스 개념 참고 - http://www.elastic.co/guide/en/elasticsearch/guide/master/inverted-index.html

 다시말해, 검색을 할때는 해당 term을 document에 mapping (inverted index) 시키지만, 정렬을 수행 할때는 document를 term에 mapping 시키는것이 더 효율 적이라는 말이다. 

바로 'uninverted index' 개념이 필요한 것이다.

이러한 uninverted index 개념을 실현하여 정렬을 더 효율적으로 하기 위해, Elasticsearch는 정렬 기준이 되는 field를 메모리에 적재(load)하는데, 이때 특정 query에 매치되는 document의 field 뿐아니라, 모든 document의 field를 메모리에 적재 한다

모든 field를 적재하는 이유는, 요청 때마다 매번 메모리에 적재하여 사용하는 것보다, 미리 모든 field를 몽창 적재해 놓으면, 그 다음에 수행되는 query에도 사용하기 용이하기 때문이란다. 물론 새로운 document가 인덱싱 되면 그 document의 field도 적재가 된다.

이것이 Fielddata의 기본적인 개념이다.

Fielddata는 다음과 같은 경우 사용하게 된다.

  • Sorting on a field
  • Aggregations on a field
  • Certain filters(예를 들어 geolocation filters와 같은)
  • Scripts that refer to fields

 색인되는 모든 field를 메모리에 적재하는 특징 때문에, 굉장히 많은 메모리를 소비하는것은 물론 문제다. 이는 다음 포스팅에 살펴볼 Memory 부족으로 인한 OOM이나, CircuitBreakingExcpetion의 원인이 될 수도 있다.

하지만, 이러한 메모리 부족 문제는 클러스터에 노드를 추가 하는 등의 수평적 확장(Horizontal scaling)을 통해 해결 할 수 있다.

2. Fielddata Monitoring



Fielddata에 의해서 어느정도의 메모리가 사용되고 있는지 모니터링 할 수 있다.
아래와 같은 api를 사용하자

① index 레벨에서의 조회

$ curl -XGET 'localhost:9200/_stats/fielddata?fields=*&pretty'

위 api는 각 index별로 전체적인 fielddata 상태를 출력한다

② node 레벨에서의 조회

$ curl -XGET 'localhost:9200/_nodes/stats/indices/fielddata?fields=*&pretty'

위 api는 클러스터 내의 각 node별로 사용되고 있는 fielddatad 상태를 출력한다.