Lucene 기초 다지기
출처 : 실전비급 아파치 루씬 7: 엘라스틱서치 검색 엔진을 향한 첫걸음
목차
루씬 동작 방식 이해하기
1. 세그먼트
1) 세그먼트란?
- 세그먼트 정의
- 루씬의 색인은 물리적으로 세그먼트 (Segment) 로 구성되어 있다.
- 세그먼트를 간단히 정의하면, 색인된 도큐먼트의 모음
- 논리 단위와 물리 단위
- 색인에 저장되는 논리 단위는 도큐먼트 (Document)
- 도큐먼트는 지정된 디렉토리 (Directory) 에 저장되고, 디렉토리는 메모리 (RAM) 파일 로 보관된다.
- 색인은 물리적으로 세그먼트에 저장
- 따라서 색인은 하나 이상의 세그먼트의 집합 이다.
- 색인 > 세그먼트 > 도큐먼트
- 세그먼트 안에는 수많은 도큐먼트가 저장되어 있다.
- 이렇게 구성된 세그먼트는 완전히 독립적으로 동작하며, 별도로 검색할 수 있다.
- 세그먼트에는 도큐먼트의 필드 내용 뿐 아니라, 효과적인 검색에 필요한 다양한 정보 또한 담겨있다.
- 색인에 저장되는 논리 단위는 도큐먼트 (Document)
- 색인이 변경되는 경우
- 새로 추가된 도큐먼트로 인해 새로운 세그먼트가 생성 되는 경우
- 기존 세그먼트를 병합 (Merge) 하는 경우
- 세그먼트 파일
- 세그먼트 파일을 여러 개의 파일로 생성, 관리된다.
- 세그먼트 관련
Segments File
: 커밋 포인트에 대한 정보 저장Lock File
:IndexWriter
가 같은 파일을 쓰지 못하도록 잠금 (Lock) 관리Segment Info
: 세그먼트의 메타 데이터 저장
- 도큐먼트 관련
Norms
: 도큐먼트와 필드의 길이, 추가 점수 요소 정보를 인코딩해 저장Per-Document Values
: 추가 점수 요소 또는 기타 도큐먼트별 정보를 인코딩해 저장Term Vector Index
: 도큐먼트 데이터 파일 오프셋 저장Term Vector Data
: 텀벡터 데이터 저장Live Documents
: 현재 게시 중인 도큐먼트의 정보 저장
- 필드 관련
Fields
: 필드 정보 저장Field Index
: 필드 데이터의 포인터 저장Field Data
: 도큐먼트에 저장된 필드 저장
- 텀 관련
Term Dictionary
: 텀 사전Term Index
: 텀 사전에 대한 색인Frequencies
: 빈도와 함께 각 텀이 포함된 도큐먼트 목록 저장Positions
: 색인에서의 텀 위치 정보 저장
2) 세그먼트 병합
-
세그먼트는 주기적으로 병합된다.
-
세그먼트는 불변이다
-
색인 변경은 검색 및 추가 색인의 성능에 영향이 크게 때문에, 세그먼트에 대해서는 단 한번 쓰기만 허용
-
색인에 도큐먼트를 추가하거나, 기존 도큐먼트에 변경사항이 생기면
- 세그먼트를 직접 수정하는 게 아니라
- 새로운 세그먼트를 생성해 수정 사항을 반영하고, 기존 세그먼트를 삭제한다.
-
세그먼트 안의 도큐먼트를 삭제할 때
-
세그먼트는 불변이므로 세그먼트 안의 도큐먼트를 바로 삭제할 수 없다.
-
삭제할 도큐먼트를 별도로 기록 하고, 검색 시 이를 읽지 않는 것으로 처리
→ 삭제했다고 표시만 하고 실제로 삭제하지는 않음!
-
근데 이렇게 하면, 실제로 삭제되지 않은 도큐먼트가 쌓인다
→ 세그먼트 파일의 용량과, 조회해야 할 세그먼트 수가 끝없이 늘어나 검색 성능 악화
-
-
-
세그먼트를 정기적으로 병합 해 세그먼트 수를 줄이고, 삭제 표시한 도큐먼트는 병합 시 실제로 삭제 한다.
- 병합이 계속될수록 세그먼트의 크기는 점점 더 커진다
-
Pros : 여러개의 세그먼트를 읽는 것 보다는 용량이 커도 단일 세그먼트를 읽는 게 성능 측면에 유리
-
Cons : 세그먼트 병합 과정은 물리적인 저장 공간에서 이루어져, 많은 디스크 I/O와 시스템 자원을 소모함
- 너무 잦은 병합은 오히려 서버에 부담이 된다. → 적절한 전략과 스케줄링에 따라 진행
3) 세그먼트 병합 정책
- LogMergePolicy : Merge Factor (세그먼트 병합의 기준 수치)를 기반으로 하는 병합 정책
- Merge Factor : 세그먼트는 이것보다 작은 수의 세그먼트 조각을 가지고, 이 수를 넘어서면 병합을 진행
- 작으면 : 병합이 자주 수행됨 → 검색 속도 ↑ / 자원 및 시간 소모 ↑
- 크면 : 병합이 적게 발생함 → 색인 빈도 ↓ / 검색 성능 ↓
- LogByteSizeMergePolicy : 세그먼트 파일의 총 바이트 수를 기준으로 병합
- LogDocMergePolicy : 세그먼트 크기를 계산하는 대신, 세그먼트의 도큐먼트 수를 기준으로 병합
- Merge Factor : 세그먼트는 이것보다 작은 수의 세그먼트 조각을 가지고, 이 수를 넘어서면 병합을 진행
- TieredMergePolicy : 세그먼트 용량을 계층으로 구성하도록 병합하고, 비슷한 용량의 세그먼트끼리 병합
- 세그먼트 병합에 커다란 성능 향상을 가져왔다.
4) 세그먼트 병합 스케줄러
- 병렬 병합 스케줄러 : (기본값이다) 독자적인 스레드로 동작한다! 지정된 스레드 수만큼 동시에 병합한다.
- 직렬 병합 스케줄러 : 현재 스레드를 사용해 순차적으로 병합한다.
2. 색인과 동시성
1) 준 실시간 검색
- 준 실시간 검색 (Near Real Time, NRT) : 검색과 색인을 동시에 하는 것!
- 준 실시간은, 그 당시의 정확한 순간은 아닐 수 있지만 실시간에 가까운 시간을 말함
- 루씬의 준 실시간 검색은 색인을 생성한 직후에 도큐먼트를 검색할 수 있음을 의미
- 핵심 원리는
Thread-safe
- 동시에 같은 자원 (Resource)을 읽고 쓰기 위해서는 동기화 (Synchronization)가 필요함!
- 동시에 같은 자원을 사용할 때 반드시 락 (Lock) 을 걸어
- 다른 사람이 그 자원을 못 쓰게 막고,
- 자원을 다 쓴 후에 해당 자원을 반환해 다른 작업에 처리되도록 해야 함
- 이 과정은 순서대로 처리 되어야 하고, 모든 스레드가 동기화를 따라야 함!
- 특정 시점의 뷰 (Point of View)
- 루씬은 대용량 데이터를 동기화하기 위해 읽기 전용 복제본을 생성 한다
- 색인을 변경하는 시점에 색인의 읽기 전용 복제본을 만든다!
- 이 복제본으로 그 시점까지의 색인을 조회 하고 색인 작업은 별도로 이루어지기 떄문에
Thread-safe
하게 된다
2) 검색과 색인 작동 과정
-
검색 시 :
IndexReader
IndexReader
로 특정 시점의 색인에 접근할 수 있다.- 이는 특정 시점의 색인 생성 시까지 있었던 도큐먼트만 검색 가능 하다는 의미이다.
- 색인이 일어날 때, 커밋되지 않은 이전 버전의
IndexReader
를 사용해 검색하기 때문이다. - 색인이 끝나면 새로운
IndexSearcher
객체로 도큐먼트를 검색할 수 있다.- 새로운
IndexReader
는IndexWriter.commit()
또는IndexWriter.close()
로 얻을 수 있다.
- 새로운
-
색인 시 :
Flush
와Commit
- 루씬은 RAM와 하드디스크를 적절히 조합해 준 실시간 검색을 제공한다
- Flush : 색인 대상 도큐먼트를 RAM과 같은 휘발성 메모리 버퍼에서 물리적인 세그먼트로 옮기는 것
- 변경 사항은 메모리에 버퍼링되고, 주기적으로 플러시 → 플러시해도 메모리 내 세그먼트는 검색할 수 있다.
- 플러시는 RAM의 데이터를 물리적인 세그먼트로 옮기는 것일 뿐, 색인과 동기화된 것은 아님
-
발생 시점 : 메모리 버퍼가 꽉 찼을 때 일정 시간이 지나면 세그먼트 병합 시
- Commit : 색인은 하드디스크와 같이 안전한 저장소에 커밋된 후 검색할 수 있다
- 커밋 시점 :
IndexWriter.commit()
,IndexWriter.close()
호출 시… - 커밋은 I/O 측면에서 부담되므로, 색인을 빠르게 처리하려면 대형 RAM 버퍼로 RAM 사용량을 늘려야 함
- 커밋 시점 :
-
루씬은 추가하거나 삭제할 도큐먼트를 메모리 버퍼 (RAM)에 보관
addDocument()
: 도큐먼트 추가하기- 보류 중인 도큐먼트를 디렉토리에 주기적으로 플러시
- 사용 중인 MergePolicy에 따라 주기적으로 세그먼트 병합
3) 준 실시간 검색 과정
-
결론 : 루씬은 매우 빠르지만 휘발성인 RAM과 안정적이지만 느린 하드디스크 모두에 색인을 저장한다
- 세그먼트 측면에서 검색을 보장하면서, 성능을 최적화하기 위해 플러시와 커밋을 해줘야 한다.
-
Q1) 색인 시 다른 색인을 쓰려고 하면 어떻게 될까?
다른 스레드가 색인하는 동안에는 다른
IndexWriter
객체를 만들 수 없다.IndexWriter
객체를 사용 중이면 색인 디렉토리에 .lock 파일 (잠금 파일)이 생성되기 때문만약
IndexWriter
객체를 사용 중인데, 다른IndexWriter
객체를 사용하려 시도하면→
LockObtaionFailedException
오류가 발생하게 된다! -
Q2)
IndexSearcher
는 스레드에 안전할까?YES! 여러 검색 스레드는 문제 없이
IndexSearcher
인스턴스를 동시에 사용할 수 있다.메모리를 절약하기 위해서는 모든 스레드에서 하나의
IndexSearcher
를 사용하는 것이 좋다. -
Q3) 진행 중인 검색에 영향을 주지 않으면서 색인하려면?
별도의 디렉토리에 새로운 색인을 만들고, 색인 생성 프로세스가 끝날 때 새 색인을 현재 색인으로 바꾼다
-
Q4) 준 실시간 검색의 장점은?
세그먼트 커밋 시 파일을 동기화하고, 세그먼트 병합 완료를 기다리는 비싼 비용을 제거한다.
- I/O 오버헤드 최소화
- RAM 메모리의 효율적 관리
- 필드 캐시
4) Elasticsearch의 준 실시간 검색
- 리플래시 (Refresh) : es는 뷰를 주기적으로 갱신 하고, 검색 가능한 상태 (Searchable) 를 만드는 방법으로 구현
- 리플래시는 검색은 가능하나 디스크 저장가지 보장하지는 X
- 매 초마다 리플래시가 자동으로 발생해, 거의 실시간으로 도큐먼트를 검색할 수 있다.
- 커밋 (Commit) : 루씬과 마찬가지로, 디스크 저장을 보장하는 것
- 트랜잭션 로그 : 아직 커밋되지 않은 쓰기 작업을 저장하는 샤드당 트랜잭션 (= 사실상 루씬의 색인) 로그 추가!
- 동기식 입출력 함수인
fsync()
로 안전해, 아직 커밋되지 않은 도큐먼트의 안전성 보장 - 도큐먼트가 손실되더라도 트랜잭션 로그를 재생해 복원할 수 있다.
- 동기식 입출력 함수인
- 플러시 (Flush) : 루씬이 커밋하게 한 다음, 트랜잭션 로그를 비운다.
- 데이터가 루씬 수준에서 커밋되면, 트랜잭션 로그 없이 루씬 자체로 데이터를 보장할 수 있음
- 트랜잭션 로그에 추가되는 작업 수, 크기 및 마지막 플러시가 발생한 시기에 따라 자동으로 발생한다.
- 절차 정리
- ES API
refresh()
→ 루씬 APIflush()
→ 검색 가능 (searchable) - ES API
flush()
→ 루씬 APIcommit()
→ 데이터 보장 (durable)
- ES API
3. 순위 점수 계산 모델
1) BM25
- Best Matching의 약어로, 사용자 질의와 일치하는 도큐먼트의 관련성을 계산하는 확률적 모델이다.
- 점수 계산에 도큐먼트의 텀 빈도수를 이용한다
- 계산 방법
- 출현 빈도가 높은 텀에 역 가중치를 둔다.
- 자주 발견되는 텀과 그렇지 않은 텀을 구별한다.
- 역 도큐먼트 빈도 (Inverse Document Frequency, IDF)
- 점수에 상한선을 설정한다.
- 텀의 점수가 너무 낮지 않게 보정해, 반복되는 텀의 중요도를 보정해준다.
- 짧은 필드가 긴 필드보다 높은 가중치를 가진다.
- 필드의 텀 빈도는 필드 길이만큼 상쇄된다.
- Ex) 제목 필드는 본문 필드보다 중요하다!
- 출현 빈도가 높은 텀에 역 가중치를 둔다.
- 결론 : 쿼리 텀이 포함된 도큐먼트와 질의의 유사도를 계산한다
2) TF-IDF
- 벡터 공간을 이용한 유사도 모델이다.
Similarity
클래스를 상송해 구현한다. - 질의는 텀의 출현 빈도와 관련이 있다
- 계산 방법
- TF (Term’s Frequency)
- 도큐먼트에서 텀이 얼마나 많이 나오는가!
- 출현 빈도가 높을수록 관련이 있는 도큐먼트다.
- 하지만 이 수치는 무한대로 늘어날 수 있기 때문에 IDF로 보정해준다.
- IDF (Inverse Document Frequency)
- 너무 많이 등장하는 텀은 유사도 점수 계산에서 중요하지 않다
- Ex) a, the, or, who …
- 도큐먼트의 길이
- TF는 필드 길이가 긴 본문 필드에 관련이 높고,
- IDF는 짧은 도큐먼트에 가중치를 둬서 도큐먼트의 길이를 보정해준다.
- TF (Term’s Frequency)
- 결론 : 루씬은 다른 도큐먼트에서 자주 언급되지 않고, 해당 도큐먼트에서 많이 언급되는 텀의 점수를 더 높게 계산함!