본문으로 바로가기
반응형

Document의 Field type을 보다보면 xxxDocValue란 타입이 나옵니다.

이 Field는 실제 값을 저장하지 않기 때문에 실제로 검색시 Document.get()으로 값을 받아올수도 없는 field인데 언제 쓰는걸까요?

먼저 DocValue라는게 왜 생겨났는지 부터 보려면 Inverted index 얘기를 먼저 해 봐야 합니다.

이 글은 하기 링크를 번역 및 의역 하였습니다.

https://blog.trifork.com/2011/10/27/introducing-lucene-index-doc-values/


Inverted index는 term으로 document를 찾기에 좋은 형태입니다.

(inverted idnex는 https://tourspace.tistory.com/245 글 확인)

Inverted index에는 추가적으로 boosts나 frequency 정보가 저장되어 term으로 검색하기에는 매우 좋으며, scoring 역시 부가정보를 이용하여 빠르게 진행할수 있습니다.

하지만, 그외의 정보로 scoring이나 filtering을 custom하게 진행하려면 Stored document를 하나씩 전부 읽어서 진행해야 합니다.. 

(StoredField는 bulk read를 위해 design 되었으므로..)


FieldCache

특정한 filed가 처음으로 요청되거나 index가 reopen될때 inverted index로부터 FieldCache가 RAM(Heap) 공간에 생성됩니다.

내부적으로 이런 동작을 uninverting the field라고 부르는데, 이는 inverted index와는 달리 FieldCache는 document to value

의 구조로 구성되기 때문입니다.

(Inverted index는 Term to Document id로 구성됨)


일단 FieldCache가 load 되면 lucene은 해당 field의 모든 terms를 순회하면서 document id를 기준으로 값을 채웁니다.

즉 key는 document id, value는 해당 filed의 value들로 구성 array를 갖게 됩니다.

따라서 scoring을 특정 factors가지고 하도록 customizing 하거나, document단위로 Filed의 value에 접근하는 경우 FieldCache는 매우

효율적으로 이를 수행할 수 있습니다.

하지만 FieldCache는 생성하는데 많은 시간이 필요하며, 환경이 빈번하게 바뀌는경우 심각한 bottleneck을 유발할 수 있습니다.


DocValues

Lucene 4.0 이상부터는 이런 FieldCache의 단점을 극복하기 위해서 index time에 document를 기준으로 value를 mapping 하는 작업을 수행합니다.

DocValues는 어떤값을 넣을지 또한 어떻게 값을 넣는지가 일반 Field와는 다릅니다.

예를 들어 Integer type의 경우 압축된 packedInts 뿐만 아니라 DocValues는 8, 16, 32, 64bit들을 위한 byte 정렬을 제공합니다.

(소수점의 경우 float32와 float64만 제공합니다.)

Byte array의 경우 값의 길이를 고정하거나 가변으로 둘수도 있고, 고유값이 적을때 압축률 향상을 위해 일반적인 참조 방식이나 역참조 방식으로 지정이 가능합니다.


DoceValue는 FieldCache처럼 inverted index를 다시 uninverting 하거나 values를 parsing 할 필요가 없습니다.

(이미 그렇게 저장해 놨기 때문이죠)

벤치마크 결과로 Float32 기준 DocValues의 loading이 FieldCache의 로딩보다 80~100배더 빠르다고 합니다

(원문에 이렇게 쓰여 있네요;;)


IndexDocValues는 하나의 interface로 RAM에 상주되는 형태나 disk에 상주하는 형태를 access할 수 있습니다.

  • RAM에 상주할 경우 Field 및 segment당 하나의 instance를 공유하여 사용
  • Disk에 상주할 경우 Thread당 하나의 instance를 공유하여 사용

따라서 어디에 상주된 형태이든, 동일한 API로 document id를 이용하여 해당 filed에 속한값을 검색할 수 있습니다.


추가적으로 원문 링크의 내용은 2011년에 쓰여진것으로 현재 lucene버전 기준 (v8.2.0) 과는 좀 다를수 있습니다.

실제 API 문서로 https://lucene.apache.org/core/8_2_0/core/org/apache/lucene/index/DocValues.html 를 참고하시기 바랍니다.


그래서 DocValues가 뭡니까?

위 링크의 원문을 열심히 읽었는데, 저 역시 잘 이해가 가지 않았습니다.

일단 아래 내용을 보고 나면 한번에 이해가 확! 되기 때문에 부가적인 설명을 곁들여서 우리말로? 쉽게 정리해 보면 아래와 같습니다.


먼저 StoredField가 저장되는 구조는 아래와 같습니다.

여기서 A,B,C는 Field, 숫자는 각 Field에 해당하는 값 입니다.

key 

 value

 doc1

 A:1, B:2, C:3 

 doc2

 A:4, B:5, C:6 

 doc3

 A:7, B:8, C:9  


Inverted index를 이용하여 term(key)를 기준으로 value(Doc id)를 찾으면 StoredField에서 해당 Doc을 읽습니다.

예를 들어 위 테이블에서 doc1을 읽을 경우 빨간색으로 표시한 라인을 읽어 오기 때문에 해당 document에 들어있는 모든 field값을 한번에 가져올수 있습니다.

그래서 앞서 포스팅해 놓은 다른 lucene  예제코드에서 searcher로 검색한 결과를 TopDocs로 받아온 후에 document.get("필드명")을 통해서 문서에 포함된 값을 읽어올 수 있는거죠~

이런 데이터 구조를 같는 형태를 "row 형태로 저장한다" 라고 여기저기 원문들에서 표현합니다.


하지만 DocValues로 저장된 Field는 아래와 같이 저장됩니다.

 Key

doc1 

doc2 

doc3 

 A

 1

 B

 2

5

 C

 3

따라서 만약 "A Field의 값을 정렬하고 싶다!!", 아니면 "A Field의 값을 Grouping 하고 싶다!!"라고 할때는 StoredField를 이용하는게 아니라 DocValue로 저장해서 사용하는 게 훨씬 이득이겠죠?


예를 들어 DocValues로 저장한 A란 필드를 읽으면 각 문서들이 가지고 있는 A field의 값들을 한번에 가지고 올수 있는 겁니다.

이런 구조로 저장하는 형태를 "Column 형태로 저장한다" 라고 말합니다.

실제로 위 Table처럼 구조를 만들어서 저장하지는 않습니다^^a

참고: https://stackoverflow.com/questions/51925871/what-are-docvalues-in-solr-when-should-i-use-them

위 링크를 보면 json으로 두개의 data 구조를 설명해 놨는데, 혹 위 테이블이 이해가 안간다면 해당 페이지로 들어가서 확인해 보시기 바랍니다.

 

그럼 DocValues는 언제 씁니까?

Sorting / Grouping / Faceting / Filtering / Function queries 를 할때 사용하면 매우 효율적입니다.

만약 StordField를 사용하여 위 작업을 한다면 FieldCache를 써야 하며, 이는 CPU와 Heap 메모리를 매우 많이 사용합니다.

다르게 얘기하면 CPU를 많이 사용해야 하고 Heap 메모리도 많이 사용하면서 느립니다.(부하도 많이 걸리겠죠?)

하지만, DocValues는 최소한의 Heap memory를 사용하면서 OS에서 제공하는 system file cache를 사용합니다.


Scenario 1: 하드웨어의 제약이 있을때,

Field단위로 해야하는 작업들이 존재하지만(sorting / grouping...) CPU나 Memory의 교체나 확장이 어려운 경우 DocValues를 사용하면 효과적입니다.

또한 위에서 언급한대로 OS의 System file cache를 사용하기 때문에 heap 메모리 사용량이 최소화 되고, 이로 인해 GC의 속도향상도 부수적인 효과로 누릴 수 있습니다.


Scenario 2: Batch 작업

Batch 작업은 빠른 결과를 요구하는 작업이 아닙니다. 만약 매일 어떤 batch 작업을 돌려야 한다면 클러스터에 부하를 적게 두면서 Heap 메모리의 사용을 최소화하는 작업에 잘 부합니다.


마지막으로 DocValues는 StoredField를 대체하는값이 아닙니다. 따라서 필요해 따라서 값을 저장해야 한다면 StoredField를 따로 저장해서 써야하며, indexing을 해야 한다면 indexable field도 추가해야 합니다.

반응형