Quickwit: 객체 저장소 기반 고성능 분산 검색 엔진 (Rust 기반)

Quickwit란 무엇인가?

Quickwit은 객체 저장소(Amazon S3, GCS 등) 위에서 대규모 반정형 데이터를 빠르게 검색할 수 있도록 설계된 분산 검색 시스템입니다. Rust로 구현되어 고성능과 메모리 안정성을 제공하며, 계산과 저장을 완전히 분리함으로써 PB 규모의 데이터에서도 아세컨드 미만의 응답 지연을 달성합니다.

로그 수집, 이벤트 기반 분석, 분산 추적과 같은 불변 데이터 처리에 최적화되어 있으며, 특히 클라우드 환경에서 비용 효율적인 운영이 가능하도록 설계되었습니다.

기존 검색 엔진과의 차별점

  • 저장-계산 분리: 인덱스 데이터는 저비용 객체 저장소에 보관하고, 검색 및 인덱싱 노드는 무상태(stateless)로 유지하여 자유롭게 확장 가능
  • 객체 저장소 직접 쿼리: S3와 같은 스토리지에서 바로 검색 수행, 별도의 고비용 블록 스토리지 필요 없음
  • 무상태 아키텍처: 노드 추가/제거 시 데이터 리밸런싱 없이 몇 초 내 클러스터 조정 가능
  • 다중 테넌트 지원: 인덱스 단위로 리소스 격리 및 사용량 기반 과금 가능
  • 스키마 유연성: 스키마리스 모드 지원, JSON 문서의 필드 변화에 유연하게 대응

주요 사용 사례

  • 중앙 집중식 로그 관리 시스템
  • OpenTelemetry 기반 분산 추적 저장소
  • ClickHouse 등의 OLAP DB에 대한 풀텍스트 검색 계층 추가
  • 감사 로그 및 보안 이벤트 분석
  • 대규모 메시징 또는 커뮤니케이션 기록 검색

핵심 기능

  • Lucene급 풀텍스트 검색 및 Elasticsearch DSL 호환
  • JSON 기반 인덱싱 및 쿼리 API
  • S3/GCS/Azure Blob과 같은 객체 저장소 연동
  • Kafka, Pulsar, Kinesis 실시간 데이터 소스 지원
  • Grafana 플러그인 제공
  • Jaeger 및 OpenTelemetry 네이티브 통합
  • Helm 차트를 통한 Kubernetes 배포 지원
  • GDPR 준수를 위한 삭제 API

비추천 사용 시나리오

  • 고빈도 업데이트가 필요한 전자상거래 상품 카탈로그 검색
  • 데이터가 자주 변경되거나 삭제되는 애플리케이션

아키텍처 개요

Quickwit은 다음과 같은 주요 컴포넌트로 구성됩니다:

1. Searcher (검색 노드)

쿼리를 수신하고 Map-Reduce 방식으로 처리합니다. 요청을 받은 노드는 "루트" 역할을 하며, 관련 split 목록을 결정한 후 Rendezvous 해싱을 사용해 다른 searcher 노드(리프 노드)에 작업을 분배합니다. 결과는 루트 노드에서 병합되어 반환됩니다.

2. Indexer (인덱싱 노드)

외부 데이터 소스(Kafka 등)로부터 데이터를 읽어 인덱스를 생성합니다. 버퍼링된 문서가 설정된 크기에 도달하거나 커밋 타임아웃이 지나면 새로운 split을 생성합니다. 이후 머지 정책에 따라 작은 split들을 병합해 성숙한 split을 만듭니다.


// 예: 간단한 인덱싱 설정 (실제 Quickwit 구성 파일 형식)
let index_config = json!({
  "version": "0.4",
  "index_id": "logs",
  "doc_mapping": {
    "field_mappings": [
      { "name": "timestamp", "type": "datetime", "fast": true },
      { "name": "message", "type": "text", "tokenizer": "default" },
      { "name": "service", "type": "text", "aggregatable": "yes" }
    ],
    "tag_fields": ["service"]
  },
  "retention": { "period": "7 days" },
  "merge_policy": {
    "type": "stable_log",
    "min_level_num_docs": 1_000_000,
    "merge_factor": 10
  }
});
    

3. Metastore (메타스토어)

PostgreSQL 또는 로컬 파일 기반으로 인덱스 메타데이터를 저장합니다. 각 split의 상태, 시간 범위, 태그 정보 등을 관리하며, 검색 시 불필요한 split을 제거하는 데 사용됩니다.

4. Control Plane (제어 평면)

클러스터 전체의 인덱싱 작업을 스케줄링합니다. 매 3초마다 인덱서의 상태를 확인하고, 원하는 상태(desired state)와 비교해 필요한 작업을 재할당합니다.

5. Janitor (정리 서비스)

주기적으로 split 가비지 컬렉션, 보존 정책 적용, 삭제 작업 정리 등의 유지보수 작업을 수행합니다.

Split 기반 저장 구조

Quickwit은 인덱스를 여러 개의 독립된 split으로 나누어 관리합니다. 각 split은 다음과 같은 특징을 가집니다:

  • UUID로 식별되며, 자체적인 메타데이터를 포함
  • Hotcache 덕분에 S3에서도 60ms 이내에 오픈 가능
  • 최소/최대 타임스탬프 정보를 포함하여 시간 기반 쿼리 최적화 가능
  • 태그 메타데이터를 통해 다중 테넌트 환경에서 불필요한 split 제거 가능

고급 쿼리 최적화 기법

시간 기반 프루닝 (Time Sharding)

쿼리에 start_timestampend_timestamp를 지정하면, 해당 범위 외부의 split은 아예 조회하지 않음으로써 성능을 극대화합니다.

태그 기반 프루닝 (Tag Pruning)

특정 필드를 태그로 지정하면, split 생성 시 그 값이 메타스토어에 기록됩니다. 예를 들어 tenant_id를 태그로 설정하면, 특정 테넌트의 데이터만 포함된 split을 빠르게 필터링할 수 있습니다.

파티셔닝 (Partitioning)

문서를 특정 키 기반으로 서로 다른 split에 분산 저장할 수 있습니다. 파티셔닝 표현식은 아래와 같이 정의됩니다:

  • user_id → 각 사용자별로 하나의 파티션
  • user_id, app_id → 사용자와 앱 조합별 파티션
  • hash_mod(user_id, 8) → 최대 8개의 파티션으로 균등 분배
  • hash_mod((user_id, app_id), 50) → 조합 기준 50개 파티션

파티션 수는 max_num_partitions로 제한되어 과도한 split 생성을 방지합니다.

삭제 및 GDPR 준수

Delete API를 통해 특정 조건에 맞는 문서를 삭제할 수 있습니다. 삭제 작업은 opstamp(작업 스탬프)를 기반으로 추적되며, 기존 split에 점진적으로 적용됩니다. 단, 삭제는 비용이 큰 작업이므로 빈번한 호출은 권장되지 않습니다.

주의할 점은 삭제가 성숙한 split에만 적용된다는 것입니다. 머지 중인 immature split은 일단 성숙된 후에야 삭제 조건이 반영됩니다.

성능 최적화를 위한 캐싱

  • Hotcache: split 메타정보 캐싱 (split_footer_cache_capacity)
  • Fast Field Cache: 자주 조회되는 열 기반 필드 캐싱 (fast_field_cache_capacity)
  • Partial Request Cache: 유사 쿼리 패턴 캐싱 (partial_request_cache_capacity)

시작하기

로컬에서 Quickwit을 실행하려면 다음 명령어를 사용하세요:


curl -L https://install.quickwit.io | bash
./quickwit run
    

자세한 문서는 공식 문서를 참고하세요.

태그: Quickwit Rust 분산 검색 엔진 객체 저장소 Elasticsearch 대체

5월 28일 12:56에 게시됨