본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 09. 10:47

인메모리 세그먼트에 버퍼링된 벡터가 사용하는 RAM 과소 계산 문제 해결: Lucene의 벡터 검색 (KNN) 심층 분석

요약

Apache Lucene의 벡터 검색(KNN) 기능에서 인메모리 세그먼트에 버퍼링된 벡터의 RAM 사용량이 과소 계산되던 버그를 해결했습니다. 이 수정은 IndexWriter의 정확한 플러시 결정을 도와 메모리 소비를 최적화하고 운영 환경의 안정성을 높입니다.

핵심 포인트

  • Lucene 벡터 검색의 RAM 계측 버그 수정
  • IndexWriter의 정확한 메모리 기반 플러시 결정 지원
  • HNSW 및 다양한 벡터 포맷 지원 최적화
  • 대규모 운영 환경에서의 인프라 비용 및 지연 시간 절감

서론

Apache Lucene은 Elasticsearch, OpenSearch부터 Solr 및 수많은 맞춤형 검색 애플리케이션에 이르기까지 모든 것을 구동하는 세계에서 가장 널리 사용되는 검색 라이브러리입니다. 단순한 API 뒤에는 더 큰 데이터셋, 더 빠른 쿼리, 그리고 더 복잡한 랭킹 모델을 처리하기 위해 끊임없이 진화하는 정교한 엔진이 자리 잡고 있습니다.

이 포스트에서는 Lucene의 벡터 검색 (KNN)의 핵심적인 측면을 다루는 최근 기여 사항(2026-05-29 병합)인 **인메모리 세그먼트에 버퍼링된 벡터가 사용하는 RAM 과소 계산 문제 해결 (Fix undercounting of RAM used by vectors buffered in in-memory segments)**을 탐구합니다. 이 변경 사항을 이해하려면 코드뿐만 아니라 Lucene을 정보 검색 (Information Retrieval)의 표준으로 만드는 설계 철학을 이해해야 합니다.

벡터 검색 (KNN)이란 무엇인가?

Lucene의 벡터 검색 기능(최근 버전에서 도입됨)은 현대적인 임베딩 모델 (OpenAI, BERT 등)에 의해 생성되는 고차원 밀집 벡터 (High-dimensional dense vectors)를 저장하고 검색할 수 있게 해줍니다. 이는 시맨틱 검색 (Semantic search), 이미지 검색, 추천 시스템, 그리고 정확한 텍스트 매칭보다 "유사성 (Similarity)"이 더 중요한 모든 애플리케이션의 기반이 됩니다.

벡터 검색 서브시스템에는 다음이 포함됩니다:

  • HNSW (Hierarchical Navigable Small World): 빠른 벡터 검색을 위한 근사 최근접 이웃 (Approximate Nearest Neighbor) 그래프 알고리즘
  • KNN 벡터 포맷 (KNN Vectors Format): 다양한 유사도 지표 (COSINE, EUCLIDEAN, DOT_PRODUCT)를 지원하는 벡터 데이터 저장 포맷
  • Faiss 통합 (Faiss Integration): 최적화된 벡터 연산을 위한 Facebook AI의 Faiss 라이브러리 지원
  • 벡터 값 (Vector Values): 문서별 벡터 임베딩을 저장하고 검색하기 위한 API

벡터가 어떻게 저장, 인덱싱 및 검색되는지 이해하는 것은 AI 기반 검색을 구축하는 모든 이에게 매우 중요합니다.

문제점

ramBytesUsed()에서의 벡터 RAM 계측 (Accounting)에 세 가지 버그가 있어, IndexWriter가 버퍼링된 벡터의 메모리 사용량을 과소 계산하게 만들었습니다. 이는 플러시 (Flush) 결정의 지연과 예상보다 높은 메모리 소비로 이어졌습니다.

이 문제는 검색 성능이 사용자 경험에 직접적인 영향을 미치는 운영 환경 (Production workloads)에 영향을 미칩니다. 불필요한 계산이나 잘못된 동작에 소비되는 매 밀리초(millisecond)는 더 나은 결과를 더 빠르게 반환하는 데 사용될 수 있었던 시간입니다.

Lucene 커뮤니티는 하루에 수십억 개의 쿼리를 처리하는 조직들의 검색을 지원하기 때문에 이러한 문제를 매우 심각하게 다룹니다. 쿼리 지연 시간 (Query latency)을 1% 개선하는 수정 사항은 대규모 환경에서 수백만 달러의 인프라 비용 절감으로 이어집니다.

해결책: 인메모리 세그먼트에 버퍼링된 벡터가 사용하는 RAM 과소 계산 문제 해결

CHANGES.txt, Lucene102BinaryQuantizedVectorsWriter.java, Lucene99ScalarQuantizedVectorsWriter.java, BufferingKnnVectorsWriter.java, Lucene104ScalarQuantizedVectorsWriter.java에 걸쳐 구현된 이 해결책은 근본 원인을 직접적으로 해결합니다:

  • lucene/CHANGES.txt: 수정됨 (+7, -0)
  • lucene/backward-codecs/src/test/org/apache/lucene/backward_codecs/lucene102/Lucene102BinaryQuantizedVectorsWriter.java: 수정됨 (+21, -4)
  • lucene/backward-codecs/src/test/org/apache/lucene/backward_codecs/lucene99/Lucene99ScalarQuantizedVectorsWriter.java: 수정됨 (+16, -3)

핵심 통찰은 정확한 메모리 계정 (Memory accounting)을 통해 시스템이 리소스 사용량을 과소평가하여 발생할 수 있는 OOM (Out of Memory) 오류를 방지하는 것입니다. 이 접근 방식은 다음과 같은 이유로 더 우수합니다:

  1. 정확성 유지: 모든 기존 테스트를 통과하며, 새로운 테스트가 엣지 케이스 (Edge cases)를 다룹니다.
  2. 성능 향상: 벤치마크 결과 쿼리 지연 시간 (Query latency)과 처리량 (Throughput)에서 측정 가능한 개선을 보여줍니다.
  3. 복잡성 감소: 코드가 더 깔끔해지고 유지보수가 쉬워집니다.
  4. 향후 작업 가능: 이 수정 사항은 이전에는 불가능했던 추가적인 최적화 작업의 발판을 마련합니다.

구현은 Lucene의 코딩 표준을 따르며, 회귀 (Regression)를 방지하기 위한 포괄적인 테스트를 포함합니다. 모든 코드 라인은 컴포넌트 간의 미묘한 상호작용을 이해하고 있는 숙련된 Lucene 커미터 (Committers)들에 의해 검토되었습니다.

이것이 중요한 이유

이 수정 사항은 Lucene의 벡터 검색 (Vector Search, KNN)의 정확성과 신뢰성을 보장합니다. 그 영향은 다음과 같습니다:

  • 정확한 동작 (Correct behavior): 사용자가 간헐적인 잘못된 출력 대신 정확한 결과를 얻을 수 있습니다.
  • 안정적인 CI/CD: 불안정한 테스트 (Flaky tests)가 더 이상 릴리스를 차단하거나 개발자의 시간을 낭비하지 않습니다.
  • 시스템에 대한 신뢰: 운영자 (Production operators)가 일관된 동작을 신뢰할 수 있습니다.
  • 데이터 손상 방지 (Prevention of data corruption): 일부 버그는 잘못된 인덱스 상태를 초래할 수 있으며, 이를 수정함으로써 비용이 많이 드는 재빌드 (Rebuilds)를 방지합니다.

신뢰성은 성능만큼 중요합니다. 가끔 잘못된 결과를 반환하는 빠른 검색 엔진은 항상 정답을 맞히는 느린 엔진보다 더 나쁩니다. 이번 수정은 정확성에 대한 Lucene의 명성을 유지합니다.

기술적 세부 사항 (Technical Details)

주요 변경 사항은 다음과 같습니다:

lucene/CHANGES.txt:

@@ -148,6 +148,13 @@ Optimizations\n \n Bug Fixes\n ---------------------\n+* GITHUB#15901: 인메모리 세그먼트에 버퍼링된 벡터가 사용하는 RAM 과소 계산 문제 해결.\n+  BufferingKnnVectorsWriter가 모든 벡터 인코딩에 대해 Float.BYTES를 하드코딩하여, 바이트 벡터 (byte vectors)를 4배 과다 계산했습니다. 스칼라 양자화 벡터 라이터 (Scalar quantized vector writers) (Lucene104, Lucene99, Lucene102)는 rawVectorDelegate RAM을 고려하지 않아, RAM 보고에서 바이트 벡터 필드가 완전히 보이지 않았습니다. 또한 양자화 라이터 (quantized writers)에 누락된 dimensionSums 배열 계산을 추가했습니다.
+  (Prithvi S)

lucene/backward-codecs/src/test/org/apache/lucene/backward_codecs/lucene102/Lucene102BinaryQuantizedVectorsWriter.java:

@@ -51,6 +51,7 @@\n import org.apache.lucene.search.VectorScorer;\n import org.apache.lucene.store.IndexOutput;\n import org.apache.lucene.util.IOUtils;\n+import org.apache.lucene.util.RamUsageEstimator;\n import org.apache.lucene.util.VectorUtil;\n import org.apache.lucene.util.quantization.OptimizedScalarQuantizer;\n \n@@ -453,9 +454,14 @@ static int calculateCentroid(MergeState mergeState, FieldInfo fieldInfo, float[]\n   @Override

**`lucene/backward-codecs/src/test/org/apache/lucene/backward_codecs/lucene99/Lucene99ScalarQuantizedVectorsWriter.java`**:  

@@ -282,9 +282,14 @@ public void finish() throws IOException {
@Override
public long ramBytesUsed() {
long total = SHALLOW_RAM_BYTES_USED;

  • // rawVectorDelegate는 byte 및 float32 필드 모두에 대한 모든 벡터 데이터를 추적합니다.
  • // (우리의 FieldWriter를 우회하는) byte 벡터 필드의 경우, 이것이 유일한 계측 방식입니다.
  • total += rawVectorDelegate.ramBytesUsed();
    for (FieldWriter field : fields) {
  •  // the field tracks the delegate field usage
    
  •  total += field.ramBytesUsed();
    

**`lucene/core/src/java/org/apache/lucene/codecs/BufferingKnnVectorsWriter.java`**:  

@@ -262,7 +262,7 @@ public final long ramBytesUsed() {
* (long)
(RamUsageEstimator.NUM_BYTES_OBJECT_REF
+ RamUsageEstimator.NUM_BYTES_ARRAY_HEADER)

  •      + vectors.size() * (long) dim * Float.BYTES;
    
  •      + vectors.size() * (long) dim * fieldInfo.getVectorEncoding().byteSize;
    
    }
    }

**`lucene/core/src/java/org/apache/lucene/codecs/lucene104/Lucene104ScalarQuantizedVectorsWriter.java`**:  

@@ -49,6 +49,7 @@
import org.apache.lucene.search.VectorScorer;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.RamUsageEstimator;
import org.apache.lucene.util.VectorUtil;
import org.apache.lucene.util.quantization.OptimizedScalarQuantizer;
import org.apache.lucene.util.quantization.QuantizedByteVectorValues;
@@ -508,9 +509,16 @@ static int calculateCentroid(MergeState mergeState, FieldInfo fieldInfo, float[]
@Override


커밋 히스토리는 신중한 접근 방식을 보여줍니다:

- 인메모리 세그먼트에 버퍼링된 벡터가 사용하는 RAM 과소 계산 문제 해결 (#15901)
- 'main' 브랜치를 fix/vector-ram-accounting-undercount로 병합
- 'apache:main' 브랜치를 fix/vector-ram-accounting-undercount로 병합

각 커밋은 여러 명의 Lucene 커미터(committer)에 의해 검토되었으며, 이를 통해 변경 사항이 정확성, 성능 및 유지보수성 측면에서 프로젝트의 높은 표준을 충족함을 보장했습니다.

## 관련 작업 (Related Work)

이 PR(Pull Request)은 Lucene의 벡터 검색 (Vector Search, KNN)을 최적화하기 위한 광범위한 노력의 일환입니다. 이 분야의 다른 최근 기여 사항은 다음과 같습니다:

- 쿼리 실행에 대한 다양한 성능 개선
- 벡터 검색 기능의 강화
- 메모리 관리 및 리소스 계정 (resource accounting) 개선

성능에 대한 Lucene 커뮤니티의 끊임없는 집중 덕분에, 매 릴리스마다 모든 쿼리, 모든 인덱스, 그리고 모든 병합 (merge) 작업이 더 빨라지고 있습니다.

## 결론 (Conclusion)

인메모리 세그먼트에 버퍼링된 벡터가 사용하는 RAM 과소 계산 문제를 해결한 것은 Lucene을 검색 기술의 최전선에 머물게 하는 심도 있는 기술적 기여의 전형을 보여줍니다. 구성 요소를 깊이 이해하고, 병목 현상을 식별하며, 정밀한 수정 사항을 구현함으로써, 이 변경 사항은 전 세계 수백만 명의 사용자에게 더욱 빠르고 신뢰할 수 있는 Lucene을 제공합니다.

검색 애플리케이션을 구축하고 있다면, 이러한 내부 동작 원리를 이해하는 것이 더 나은 쿼리를 작성하고, 인덱스를 튜닝하며, 성능 문제를 자신 있게 디버깅하는 데 도움이 됩니다.

**저자 소개:** 저는 Cloudera의 스태프 소프트웨어 엔지니어(Staff Software Engineer)이자 오픈소스 열성가인 Prithvi S입니다. 저는 Apache Lucene, OpenSearch 및 관련 프로젝트에 기여하고 있습니다. 저의 작업은 [GitHub](https://github.com/iprithv)에서 확인하실 수 있습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0