Full Text Search in SmithDB: Designing an Inverted Index for Object Storage
Quick Summary
SmithDB는 객체 스토리지에 저장된 거대하고 중첩된 에이전트 trace JSON에서 빠른 전문 검색과 JSON 필터링을 제공하기 위해, Vortex 기반의 객체 스토리지 친화적 inverted index를 처음부터 설계했다.
🖼️ 인포그래픽
🖼️ 4컷 인포그래픽
💡 한 줄 요약
SmithDB는 객체 스토리지에 저장된 거대하고 중첩된 에이전트 trace JSON에서 빠른 전문 검색과 JSON 필터링을 제공하기 위해, Vortex 기반의 객체 스토리지 친화적 inverted index를 처음부터 설계했다.
📌 핵심 요약
- SmithDB는 대형 JSON 문서로 구성된 에이전트 trace를 객체 스토리지에 저장하면서도 전문 검색과 JSON 필터링에서 P50 400ms 수준의 지연시간을 목표로 한다.
- 에이전트 trace는 inputs와 outputs가 전체 바이트의 대부분을 차지하고, LLM 컨텍스트 확장과 장기 실행 에이전트 때문에 payload 크기가 계속 커져 일반 로그 검색과 다른 색인 경제성을 만든다.
- 검색은 path 존재 여부, 특정 JSON path의 값 검색, 전체 텍스트 검색이라는 세 가지 주요 형태를 지원해야 하며, phrase search를 위해 term·posting·position 구조가 필요하다.
- 객체 스토리지는 요청마다 수십~수백 ms 지연이 있고 큰 postings나 positions를 성급하게 읽으면 비용이 커지므로, 저장 레이아웃과 쿼리 실행은 요청 수와 읽기 바이트를 최소화하도록 설계되어야 한다.
- 초기 설계는 Vortex의 기본 인코딩과 고정 row group에 의존했지만 per-term 인코딩 제어 부재, term 빈도 편향을 반영하지 못한 row group 크기, compaction 시 positions 재구성 비용이라는 한계를 드러냈다.
🧩 주요 포인트
- SmithDB는 대형 JSON 문서로 구성된 에이전트 trace를 객체 스토리지에 저장하면서도 전문 검색과 JSON 필터링에서 P50 400ms 수준의 지연시간을 목표로 한다.
- 에이전트 trace는 inputs와 outputs가 전체 바이트의 대부분을 차지하고, LLM 컨텍스트 확장과 장기 실행 에이전트 때문에 payload 크기가 계속 커져 일반 로그 검색과 다른 색인 경제성을 만든다.
- 검색은 path 존재 여부, 특정 JSON path의 값 검색, 전체 텍스트 검색이라는 세 가지 주요 형태를 지원해야 하며, phrase search를 위해 term·posting·position 구조가 필요하다.
- 객체 스토리지는 요청마다 수십~수백 ms 지연이 있고 큰 postings나 positions를 성급하게 읽으면 비용이 커지므로, 저장 레이아웃과 쿼리 실행은 요청 수와 읽기 바이트를 최소화하도록 설계되어야 한다.
- 초기 설계는 Vortex의 기본 인코딩과 고정 row group에 의존했지만 per-term 인코딩 제어 부재, term 빈도 편향을 반영하지 못한 row group 크기, compaction 시 positions 재구성 비용이라는 한계를 드러냈다.
🧠 상세 정리
1. SmithDB 검색의 목표와 배경
원문은 SmithDB가 에이전트 trace에 대해 전문 검색과 JSON 필터링을 지원하며, 그럼에도 P50 기준 400ms 지연시간을 달성한다는 문제의식에서 출발한다. 저장되는 데이터는 단순한 로그 라인이 아니라 크고 깊게 중첩된 JSON 문서이며, 내구성 있는 저장 계층은 객체 스토리지다. Lucene, Tantivy, Quickwit처럼 이미 성숙한 검색 및 객체 스토리지 기반 색인 기술이 있지만, SmithDB는 기존 방식을 그대로 채택하지 않고 에이전트 trace라는 데이터 특성에 맞춰 문제를 다시 정의했다. 핵심은 검색 자체가 새로운 문제가 아니라, 매우 큰 반정형 payload와 객체 스토리지의 I/O 제약을 함께 만족해야 한다는 점이다.
2. 에이전트 trace가 기존 로그와 다른 이유
SmithDB가 다루는 LangSmith event에서는 inputs와 outputs 필드가 전체 데이터 크기의 압도적인 비중을 차지한다. 1MB가 넘는 payload가 흔하고, 일부는 압축 전 수백 MB까지 커질 수 있으며, identity나 timestamp 같은 메타데이터 컬럼보다 훨씬 크다. 또한 LLM의 context window가 커지고 에이전트가 더 긴 시간 동안 실행되면서 trace payload도 시간이 갈수록 커지는 방향으로 움직인다. 이 때문에 일반 로그 엔진처럼 작은 문서를 대량으로 색인하는 모델과 달리, SmithDB에서는 하나의 거대한 문서가 많은 작은 로그 라인보다 더 많은 index 데이터를 만들어낼 수 있다.
3. 색인 경제성의 역전과 세 가지 결과
원문은 일반 로그의 source:index 비율이 대략 1:1.25인 데 비해 LangSmith의 에이전트 trace에서는 평균적으로 1:1.9에 가깝다고 설명한다. 이 차이는 단순한 저장 공간 문제가 아니라 검색 구조 전체를 바꾸는 조건이다. 첫째, 색인 없이 content filter를 수행하면 후보 범위의 payload를 모두 스캔해야 하므로 몇 개의 결과를 찾기 위해 수 GB를 읽는 상황이 발생할 수 있다. 둘째, 자연어와 JSON payload의 term frequency는 Zipfian 분포를 따르므로 극히 흔한 token과 드문 token을 동시에 compact하고 prunable하게 다뤄야 한다. 셋째, 사용자는 path 존재 여부, 특정 값, 자유 텍스트처럼 서로 다른 검색 양식을 모두 필요로 한다.
4. 객체 스토리지가 만드는 쿼리 비용 구조
SmithDB는 내구성 있는 데이터를 객체 스토리지에 보관해 compute를 비교적 stateless하게 유지하고, 로컬 디스크 관리 없이 노드를 추가해 확장할 수 있도록 한다. 그러나 객체 스토리지에서 쿼리 비용은 대체로 객체 스토리지 요청 수와 요청당 읽는 바이트 수의 곱에 비례한다. 각 요청에는 수십 ms에서 수백 ms까지의 지연이 붙고, 요청 하나당 처리량도 제한적이다. 따라서 실제로 필요한지 알기 전에 큰 postings list나 positions list를 가져오면 그 I/O가 쿼리 지연의 대부분을 차지할 수 있다. SmithDB의 inverted index는 이러한 조건 때문에 저장 레이아웃부터 실행 방식까지 객체 스토리지 제약을 중심으로 설계된다.
5. SmithDB가 지원해야 하는 세 가지 검색 형태
원문은 SmithDB의 검색 표면을 세 가지 predicate family로 정리한다. 첫째는 json_key로, 특정 JSON key path가 문서에 존재하는지 확인하며 author.name 같은 정확한 path뿐 아니라 author.% 또는 %.user_id처럼 prefix, suffix, infix 패턴도 지원한다. 둘째는 json_key_search로, 특정 path의 값이 주어진 token이나 phrase와 일치하는지 확인한다. phrase variant에서는 latency regression처럼 두 단어가 문서 어딘가에 따로 존재하는 것이 아니라 값 안에서 인접해야 한다. 셋째는 search로, text column 자체 또는 JSON 내부의 모든 value를 대상으로 자유 텍스트 검색을 수행한다.
6. Inverted index의 기본 구조
SmithDB의 설계는 textbook inverted index의 핵심 개념인 term, posting, position 위에 놓인다. term은 색인 단위로서 JSON path, keyed value, text token이 될 수 있고, posting은 해당 term을 포함하는 document ID의 정렬된 집합이다. position은 문서 안에서 term이 나타나는 위치이며 phrase search를 가능하게 하는 정보다. 예를 들어 deep과 agents의 posting list를 교집합하면 두 term을 모두 포함하는 문서를 payload scan 없이 찾을 수 있다. 반면 langsmith engine 같은 phrase query는 단순히 두 term이 같은 문서에 있는지만으로는 부족하고, position offset이 연속되는지를 확인해야 정확히 매칭할 수 있다.
7. Tantivy를 직접 쓰지 않은 이유
원문은 Tantivy가 Rust 기반 Lucene 스타일 검색 라이브러리로 훌륭하며 SmithDB 설계에도 많은 영감을 주었지만, 직접 적용하기에는 몇 가지 제약이 있었다고 설명한다. 첫째, Tantivy는 mmap과 로컬 디스크 전제를 강하게 갖고 있어 임의 I/O가 매우 저렴한 환경에 적합하지만, SmithDB는 약 100ms 왕복 지연이 있는 객체 스토리지에서 레이아웃과 read coalescing이 지연시간을 좌우한다. 둘째, SmithDB 쿼리는 Apache DataFusion과 Vortex를 통과하는 columnar scan pipeline 안에서 실행되어야 했고, 별도의 segment model을 가진 병렬 검색 stack을 두고 싶지 않았다. 셋째, SmithDB는 doc ID를 Vortex data file의 row position과 직접 맞춰야 했기 때문에 Tantivy가 자체적으로 부여하고 merge 때 renumbering하는 segment-local doc ID 방식이 맞지 않았다.
8. Vortex 기반 첫 번째 시도와 한계
SmithDB의 첫 번째 구현은 textbook inverted index를 거의 그대로 옮긴 형태였다. term_key와 term_value 두 컬럼을 두어 path existence, keyed search, full-text search를 하나의 레이아웃으로 처리했고, postings는 List<u32>, positions는 List<List<u32>>로 저장했다. Vortex의 기본 FSST 인코딩, bitpacked 인코딩, zoned storage layout을 활용했으며, positions가 다른 모든 컬럼보다 훨씬 커서 core run data와 분리된 별도 index file에 저장했다. 그러나 규모가 커지자 per-term 인코딩 제어가 불가능했고, 고정 크기 row group이 term skew를 반영하지 못했으며, merge 과정에서 positions 전체를 decode하고 새 문서 순서에 맞춰 재배열해야 해 compaction 비용이 크게 증가했다.
🧾 핵심 주장 / 시사점
- 에이전트 trace 검색에서는 문서 수보다 payload 크기와 payload 내부의 term skew가 더 중요한 설계 변수가 되며, 일반 로그 검색의 저장·색인 비율 가정이 그대로 통하지 않는다.
- 객체 스토리지 기반 검색 시스템에서는 CPU보다 객체 요청 수, 읽기 바이트, 큰 postings와 positions를 언제 읽을지 같은 I/O 레이아웃 결정이 지연시간을 좌우한다.
- 기존 검색 라이브러리의 핵심 아이디어는 유효하지만, SmithDB처럼 columnar engine과 row-position 기반 doc ID, 객체 스토리지, 대형 JSON payload를 동시에 요구하는 경우에는 저장 형식과 색인 병합 전략을 별도로 설계해야 한다.
✅ 액션 아이템
- SmithDB의 목표 SLA인 P50 400ms를 기준으로 path 존재 여부, 값 검색, 전문검색 3종 쿼리의 지연분포를 같은 지표로 모니터링한다.
- inputs·outputs 비중이 큰 대형 중첩 trace JSON에서 JSON 필터링 비용을 줄이기 위해 인덱스 저장 레이아웃을 재설계한다.
- term·posting·position 구조와 per-term 인코딩 제어를 함께 반영해 row group 크기 편향 및 compaction 시 positions 재구성 비용을 점검한다.
❓ 열린 질문
- 객체 스토리지에서 큰 postings/positions를 무리하게 읽지 않으면서 path 존재 여부·값 검색·phrase search를 모두 충족하려면 어떤 인덱스 분할 전략이 적합한가?
- 고정 row group 방식의 한계를 극복하려면 compaction 주기별 term 빈도 편향을 반영해 row group 크기를 어떻게 동적으로 조정하는가?
- 요청당 수십~수백ms 지연이 존재할 때 읽기 바이트와 요청 수를 최소화하는 쿼리 실행 순서는 무엇으로 정리할 수 있는가?