검색엔진·MCP 서버
0ccab07 기준입니다.xerj는 내 컴퓨터나 서버에 있는 자료를 색인하고, 질문에 맞는 문서·코드 조각을 찾아주는 검색엔진입니다. 여기에 MCP 서버가 들어 있어 AI 에이전트도 검색 도구를 호출할 수 있습니다. AI에게 폴더 전체를 읽히는 대신, 질문에 필요한 근거를 먼저 검색해 전달하는 것이 핵심입니다.
예를 들어 “WAL 파일을 언제 지우는가?”라는 질문을 받으면, 모든 Rust 파일을 대화에 넣을 필요가 없습니다. 관련 함수와 설명을 검색하고 파일 경로·줄 번호를 확인하면 됩니다. 이때 답을 생성하는 주체는 연결한 AI이고, xerj는 근거를 찾는 계층을 담당합니다.
사서는 책을 모두 외워 말하는 대신, 질문을 듣고 해당 책의 해당 쪽을 펼쳐 줍니다. xerj의 색인은 도서관의 찾아보기이고, 검색 결과는 펼쳐 준 페이지입니다.
에이전트는 그 페이지를 읽고 답하거나 코드를 작성합니다. 따라서 사서의 품질은 말솜씨보다 올바른 근거를 빠짐없이 찾는 능력으로 평가해야 합니다.
| 단계 | 사용자 관점 | 코드에서 볼 곳 |
|---|---|---|
| 노드 실행 | 자료를 저장하고 질의에 응답하는 서버를 켬 | engine/crates/xerj-server/src/main.rs |
| 폴더 등록 | xerj autoindex가 형식을 판별하고 레코드를 보냄 | engine/crates/xerj-autoindex/src/lib.rs의 run_index_report_inner() |
| 근거 검색 | 단어·벡터·혼합 검색으로 필요한 부분을 찾음 | engine/crates/xerj-engine/src/index.rs의 Index |
| 도구 연결 | MCP 클라이언트가 검색 도구를 호출 | engine/crates/xerj-mcp/src/lib.rs의 run() |
본문의 경로는 저장소 루트 기준이며, 주요 Rust 크레이트는 engine/crates/ 아래에 있습니다. 공통 의존성과 빌드 프로필은 engine/Cargo.toml에서, 파일 링크는 마지막 참고 목록에서 확인할 수 있습니다.
# 설치된 xerj가 있다는 전제의 로컬 학습 예시
# docs와 저장 데이터를 서로 다른 폴더에 둡니다.
xerj --insecure --data-dir ./xerj-lab/data
# 다른 터미널: 먼저 예상 작업량을 확인합니다.
xerj autoindex ./xerj-lab/docs --prefix study --dry-run
xerj autoindex ./xerj-lab/docs --prefix study
xerj autoindex map
xerj search --prefix study "write ahead log"
위 예시는 혼자 쓰는 로컬 학습 환경용입니다. 기본 바인딩 주소는 127.0.0.1입니다. 인증 없이 접근 가능한 호출자는 자료를 읽거나 지울 수 있습니다. 서버 데이터 폴더를 색인 대상 안에 넣으면 저장 파일까지 다시 읽을 수 있으므로 두 폴더를 분리합니다.
실행 파일 하나와 프로세스 하나는 다른 말입니다. xerj mcp는 표준 입출력으로 MCP를 제공하는 별도 실행 모드이며, 이미 실행 중인 검색 노드에 연결합니다. 검색 노드를 자동으로 시작하는 기능으로 이해하면 연결 단계에서 혼동하기 쉽습니다.
문자열 검색은 “어느 파일에 이 단어가 있나?”를 잘 해결합니다. 하지만 질문이 “이 코드베이스는 쓰기 실패를 어떻게 복구하나?”라면, 단어가 나타난 위치를 모은 뒤 중요한 구현을 골라내는 일이 남습니다. xerj는 순위와 문서 조각, 코드 선언 위치를 제공해 이 후속 작업을 줄이려 합니다.
소스에서 차별점이 드러나는 곳은 engine/crates/xerj-autoindex/src/extract/code.rs입니다. 파일 본문뿐 아니라 tree-sitter로 얻은 심볼과 선언별 레코드를 만듭니다. name, kind, line, code 같은 필드가 생기므로 검색 결과를 다시 소스 위치로 연결할 수 있습니다.
검색 결과를 에이전트가 곧바로 읽을 수 있는 단위로 만드는 것이, 이 프로젝트의 실용적인 차별점입니다. 다만 구문 트리에서 선언을 찾는 것과 컴파일러처럼 타입·호출 관계를 완전히 해석하는 것은 구별해야 합니다.
작은 RAG 실험에서도 파일 파서, 전문 검색, 벡터 저장소, 임베딩 연결, 도구 서버를 조립하면 설정이 늘어납니다. xerj는 이를 한 제품의 기본 흐름으로 연결합니다. 다음 비교는 일반적인 사용 방식과 조합 비용에 대한 비교이며, 각 제품의 전체 기능을 나열한 표는 아닙니다.
| 접근 방식 | 잘 맞는 질문 | xerj가 줄이는 일 / 남는 부담 |
|---|---|---|
| grep · ripgrep | 정확한 문자열이 어디에 있나? | 검색 순위·추출 조각을 제공. 반면 색인 준비와 최신성 관리가 필요 |
| Elasticsearch 중심 구성 | 검색·집계 API를 서비스에 연결하고 싶다 | 단일 바이너리와 폴더 입력 흐름 제공. 호환 API의 세부 범위는 따로 확인 |
| 벡터 저장소 중심 구성 | 가까운 벡터를 찾고 싶다 | BM25·파일 추출·MCP도 연결. 임베딩 품질 평가는 여전히 필요 |
| xerj | 내 코드와 문서를 에이전트의 근거로 쓰고 싶다 | 입력부터 검색까지 경로가 짧음. 정식 출시 전 RC 버전의 운영 특성은 검증 필요 |
개별 부품을 사서 공구함을 조립하는 방식과, 자주 쓰는 부품이 이미 연결된 작업대를 받는 방식의 차이입니다. 연결 비용은 줄어들지만, 작업대의 각 공구가 내 재료에 잘 맞는지는 직접 확인해야 합니다.
TrendShift Weekly #17 선정과 2026-10-23 GitHub 기준 스타 13,210개·포크 950개는 관심도의 신호입니다. 2026-06-30 생성된 저장소가 10월에 이 규모의 관심을 얻었다는 점은 눈에 띕니다. 그러나 관심이 늘어난 정확한 인과관계나 실제 서비스 채택률을 이 수치만으로 알 수는 없습니다.
README는 참조 코드를 검색하는 실험에서 토큰 사용을 줄였다고 소개합니다. 이는 프로젝트가 공개한 특정 과제·모델·자료에서의 결과입니다. 모든 에이전트 작업에 같은 절감률이 생긴다는 약속으로 읽으면 안 됩니다. 반복 질문의 정답 자료를 정해 두고 내 문서에서 검색 품질과 총 작업 시간을 함께 재는 편이 학습에 도움이 됩니다.
검색 시간만 짧아도 잘못된 조각을 전달하면 전체 작업은 길어집니다. 정답 근거 포함률, 반환한 본문 길이, AI의 최종 정답률을 함께 기록하면 제품의 장점을 과장 없이 이해할 수 있습니다.
중심은 Rust입니다. 의존성 선언은 허용 범위를, 잠금 파일은 실제로 선택된 버전을 보여줍니다. engine/Cargo.toml에는 내부 크레이트 18개와 ES YAML 테스트 실행기를 합쳐 작업 공간 구성원 19개가 있습니다. 패키지 공통 버전은 1.0.0-rc.98, 에디션은 2021입니다. 크레이트는 Rust의 라이브러리·실행 파일 묶음 단위입니다.
| 영역 | 선언 → 잠금 버전 | 실제 역할과 위치 |
|---|---|---|
| 비동기 실행 | Tokio 1 → 1.52.0 | 서버·HTTP 클라이언트의 비동기 작업. 공통 의존성 |
| HTTP 라우팅 | Axum 0.7 → 0.7.9 | xerj-api의 요청 핸들러와 라우터 |
| 미들웨어 | Tower 0.4 → 0.4.13tower-http 0.5 → 0.5.2 | 요청을 감싸는 공통 처리. 잠금 파일에는 전이 의존성의 다른 버전도 존재 |
| 직렬화 | Serde 1 → 1.0.228serde_json 1 → 1.0.149 | 설정 구조체와 JSON 요청·응답 변환 |
| 병렬 계산 | Rayon 1.10 → 1.12.0 | 계산·자료 처리에서 CPU 병렬성 활용 |
| 공유 자료구조 | DashMap 6 → 6.1.0arc-swap 1 → 1.9.1 | 동시 접근 맵과 참조 카운트 포인터의 원자적 교체 |
| HTTP 클라이언트 | reqwest 0.12 → 0.12.28 | autoindex·MCP·외부 임베딩 연결 |
| gRPC | Tonic 0.12 → 0.12.3Prost 0.13 → 0.13.5 | engine/proto/xerj.proto 기반 서비스 |
이 표의 선언은 engine/Cargo.toml, 해석된 버전은 engine/Cargo.lock 기준입니다. 예를 들어 rayon = "1.10"은 정확히 1.10.0만 쓰겠다는 뜻이 아닙니다. 허용 범위 안의 실제 선택을 잠금 파일에서 확인해야 합니다.
| 부품 | 실제 의존성 버전 | 공부할 연결점 |
|---|---|---|
| 전문 검색 | fst 0.4.7 · roaring 0.10.12 | xerj-fts의 용어 사전·문서 집합·포스팅 |
| 압축 | lz4_flex 0.11.6 · zstd 0.13.3 | WAL·저장 데이터의 크기와 CPU 비용 균형 |
| 코드 구문 분석 | tree-sitter 0.26.12Rust 문법 선언 0.23 | engine/crates/xerj-autoindex/Cargo.toml과 engine/crates/xerj-autoindex/src/extract/code.rs |
| 파일 감시 | notify 8.2.0 | engine/crates/xerj-autoindex/src/watch.rs의 변경 반영 |
pdf_oxide =0.3.75 | engine/crates/xerj-autoindex/src/extract/pdf.rs의 별도 작업 프로세스 | |
| 스프레드시트 | calamine =0.37.0 | 레거시 Excel 추출. zip 선언 9, 잠금 버전 9.0.5 |
| 선택형 신경망 | Candle 0.9.2 · tokenizers 0.21.4 | xerj-ai의 BERT 문장 인코더 |
BM25 점수와 HNSW 검색 구현은 각각 xerj-fts, xerj-vector에 있습니다. 다른 검색 서버를 내부에서 실행하는 구조로 이해하면 안 됩니다. 반대로 모든 바이트 처리까지 직접 만든 것도 아닙니다. 사전·압축·파서 같은 하위 라이브러리를 조합해 엔진을 구현합니다.
engine/crates/xerj-server/Cargo.toml의 기본 기능은 neural과 decide-local이지만, 실행 시 신경망 임베딩·로컬 판단 모델이 자동으로 선택된다는 뜻은 아닙니다.xerj-ux/는 HTML·CSS·브라우저 ES 모듈로 된 콘솔입니다. xerj-ux/src/app.js는 대시보드 등록부, 질의 계층, 화면 렌더러를 가져옵니다. 이 커밋의 추적 파일에는 package.json이 없으며, React·Vue 기반 앱으로 분류할 근거도 없습니다.
engine/crates/xerj-console-api/build.rs는 자산을 찾아 Rust 상수 테이블로 묶습니다. 확장자 허용 목록과 파일당 4 MiB 상한을 두며, engine/crates/xerj-console-api/src/spa.rs의 xerj_console_index()·serve()가 내장 자산을 제공합니다. 자산 폴더가 없을 때 빈 테이블을 만드는 예외 경로도 있습니다.
# engine/Cargo.toml의 배포 설정 발췌
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
strip = true
panic = "abort"
링크 시점 최적화를 넓게 적용하는 대신 빌드가 무거워지는 선택입니다. 개발용 quick은 lto = false, codegen-units = 16, opt-level = 2입니다. 테스트용 ci-test는 테스트에 필요한 panic = "unwind"를 명시합니다. 프로필이 다르면 성능 숫자를 그대로 비교할 수 없습니다.
| 파일 | 확인할 설정 | 학습 포인트 |
|---|---|---|
.github/workflows/ci.yml | 주요 Rust 작업의 1.97.1 툴체인 고정, YAML 호환 테스트 | 컴파일 성공과 API 의미 호환을 다른 단계로 검사 |
.github/workflows/release.yml | Linux GNU·musl, macOS, Windows의 두 아키텍처로 8개 타깃 | 태그 배포와 야간 크로스 빌드 분리. 툴체인은 stable |
engine/Dockerfile | rust:1.94-slim → debian:bookworm-slim | 빌드·실행 단계 분리. CI와 컴파일러 버전이 같지 않음 |
deploy/helm/xerj/ | 기본 복제 수 1, 영속 볼륨·상태 점검 경로 | 배포 설정의 존재가 고가용성 보장은 아님 |
wrangler.toml | pages_build_output_dir = "landing" | 홍보·문서 사이트는 Cloudflare Pages. 로컬 검색 노드와 역할이 다름 |
engine/crates/xerj-server/build.rs는 protox 0.7.2로 Protocol Buffers 정의를 읽고 Tonic 코드를 생성합니다. 호스트에 별도 protoc 실행 파일을 요구하지 않는 선택도 크로스 빌드의 복잡도를 줄입니다.
도식의 세 포트는 기본값입니다. 기본 바인딩 주소는 루프백이며, 포트 충돌 시 자동 변경은 기본 동작이 아닙니다. --auto-port를 켠 구성에서는 실제로 바인딩된 포트를 확인해야 합니다. 콘솔이 활성화되면 두 HTTP 포트의 /_xerj-console/ 경로에서 제공됩니다.
xerj-autoindex의 의존성에는 xerj-engine이 없습니다. 파일을 읽고 서버의 HTTP API로 보내는 클라이언트이기 때문입니다. PDF 파싱 실패를 검색 서버 내부의 함수 호출 실패와 같은 것으로 다루지 않아도 되는 구조입니다.
MCP 쪽도 같은 원리를 따릅니다. engine/crates/xerj-mcp/src/lib.rs의 run()이 입력 줄을 읽고, handle_message()가 initialize·tools/list·tools/call을 나눕니다. call_tool()은 도구 이름을 검증하고 검색 요청을 구성합니다.
// handle_message()의 분기 구조를 줄인 개념 예시
match method.as_str() {
"tools/list" => tool_specs(),
"tools/call" => call_tool(ctx, &msg).await,
_ => method_not_found(),
}
위 코드는 구조 설명용이며 원문 전체가 아닙니다. 실제 구현은 요청의 id와 알림 여부, JSON-RPC 응답 외피까지 처리합니다. 도구 실행 오류는 isError: true인 도구 결과로 돌려주며, 알 수 없는 RPC 메서드와 구분합니다.
모든 MCP 도구가 단순 프록시는 아닙니다. build_semantic()은 검색 JSON을 조립하지만, 코드 검색·Hub 도구는 결과 정리나 별도 검색 흐름을 가집니다. “같은 API를 재사용한다”와 “도구가 아무 처리도 하지 않는다”를 구별해 읽어야 합니다.
engine/crates/xerj-ai/src/embedder.rs의 Embedder는 Lexical, Proxy, 기능 조건부 Neural·Onnx를 열거형으로 표현합니다. 호출자는 embed_batch()를 통해 벡터를 받아 검색 흐름을 이어갑니다.
기본 local_embed()는 단어와 문자 3개 묶음을 해시 버킷에 넣습니다. 단어 가중치는 1.0, 문자 묶음은 0.35, 기본 차원은 384입니다. 마지막에는 L2 정규화로 벡터 길이를 맞춥니다. 학습된 언어 모델이 아니라 어휘 겹침을 숫자로 바꾼 것입니다.
// engine/crates/xerj-ai/src/local.rs의 실제 함수 형태
pub fn local_embed(text: &str, dims: usize) -> Vec<f32>
// 동작을 이해하기 위한 요약
단어 분리 -> 단어·문자 묶음 생성 -> 해시 버킷에 누적 -> L2 정규화
"running" -> "#ru", "run", "unn", "nni", "nin", "ing", "ng#"
신경망 모드는 --embed-mode neural로 선택하며 기본 모델 ID는 sentence-transformers/all-MiniLM-L6-v2입니다. engine/crates/xerj-ai/src/neural.rs의 MAX_TOKENS는 512이고, 같은 설정의 모델은 공유 레지스트리와 OnceCell을 통해 중복 로드를 줄입니다.
384칸짜리 서랍을 두 개 쓴다고 서랍의 분류법까지 같아지는 것은 아닙니다. 어휘 해시와 신경망 모델은 차원 수가 같아도 서로 다른 좌표계를 만듭니다. 모델을 바꾸면 기존 문서와 질문을 같은 방식으로 다시 벡터화해야 합니다.
engine/crates/xerj-storage/src/wal.rs의 WalWriter::append()는 변경을 로그에 남깁니다. 로그는 ZWAL 식별자, 세대, 순번, 작업 코드, 본문, 체크섬으로 구성됩니다. 재시작 시 WalReader::replay()가 로그를 읽고 아직 반영되지 않은 변경을 복구합니다.
Index::index_batch_turbo()에서는 collection_publication.begin()으로 공개 구간을 시작하고 store.wal_append_batch()를 호출한 뒤 메모리 검색 구조를 갱신합니다. 저장용 상태와 검색용 상태가 어긋난 순간을 읽기 요청이 완성된 결과로 받아들이지 않도록 순서를 관리합니다.
engine/crates/xerj-engine/src/collection_publication.rs의 try_admit_reader()·validate_reader()는 진행 중 쓰기 수와 세대 번호를 확인합니다. 가드가 완료·취소 표시 없이 해제되면 poisoned 상태를 남깁니다. 이는 일부 상태만 공개된 결과를 정상처럼 반환하지 않으려는 실패 처리입니다.
쓰기 성공, 디스크 내구성, 검색에서 보이는 시점은 서로 다른 계약이므로 각각 확인해야 합니다. 기본 WAL 정책은 batched이며 동기화 주기는 100ms입니다. 커널에 썼다는 사실과 전원 장애에도 살아남는 저장 장치 반영은 같은 말이 아닙니다.
또 Index::refresh()는 메모리 자료를 디스크 세그먼트로 쓰는 경로를 호출합니다. “refresh는 디스크에 쓰지 않는다”는 다른 엔진의 설명을 여기에 그대로 적용하면 틀립니다. WalWriter::prune_verified()도 로그 세대 전체를 지우기 전에 각 항목의 영속화 여부를 검증합니다.
engine/crates/xerj-fts/src/bm25.rs의 Bm25Scorer::score_term()은 드문 단어의 중요도와 단어 빈도, 문서 길이를 결합합니다. 기본 상수는 k1 = 1.2, b = 0.75입니다. 반복 횟수가 늘어도 점수가 무한히 비례하지 않도록 포화시킵니다.
벡터 검색은 가까운 좌표를 찾습니다. Index::run_knn_hnsw()는 조건이 맞으면 HNSW 후보 탐색을 사용하고 저장 벡터로 다시 점수를 계산합니다. 하지만 후보의 점수가 정확하다고 후보 집합까지 전체 정답인 것은 아닙니다. HNSW는 근사 탐색입니다.
기본 RRF 하이브리드는 두 점수를 바로 더하지 않습니다. run_hybrid_with_deadline()이 하위 검색 목록을 만들고 fuse_rrf()가 순위를 합칩니다. 하위 목록의 요청 크기는 max(50, from + size)이며 페이지 자르기는 융합 후에 합니다.
# 기본 RRF: 순위는 1부터, k는 60
최종 점수 = 각 검색 목록의 weight / (60 + 해당 목록에서의 순위)를 합산
문서 A: BM25 1위, 벡터 10위 -> 1/61 + 1/70 ≈ 0.03068
문서 B: BM25 2위, 벡터 2위 -> 1/62 + 1/62 ≈ 0.03226
결과: 두 목록에서 고르게 높은 B가 A보다 앞섭니다.
현재 구현은 하이브리드의 집계 요청과 fusion: learned를 오류로 거절합니다. 같은 파일의 오래된 요약 주석에는 learned 방식의 대체 동작을 암시하는 설명이 남아 있지만, 실제 match 분기는 오류를 반환합니다. 주석 다음에 실행 분기까지 읽어야 하는 사례입니다.
| 알고 싶은 것 | 읽는 순서 | 끝에서 답할 질문 |
|---|---|---|
| 파일은 어떻게 들어가나? | engine/crates/xerj-autoindex/src/walk.rs → engine/crates/xerj-autoindex/src/sniff.rs → engine/crates/xerj-autoindex/src/extract/mod.rs → engine/crates/xerj-autoindex/src/ids.rs | 파일 한 개가 레코드 몇 개가 되는가? |
| 질문은 어떻게 실행되나? | engine/crates/xerj-query/src/parser.rs → engine/crates/xerj-query/src/ast.rs → engine/crates/xerj-engine/src/index.rs | 문자열 질의가 어떤 내부 타입으로 바뀌는가? |
| 재시작해도 남는가? | engine/crates/xerj-storage/src/wal.rs → engine/crates/xerj-storage/src/index_store.rs → engine/crates/xerj-storage/src/segment.rs | 언제 응답하고, 무엇을 재생하는가? |
engine/crates/xerj-engine/src/index.rs는 매우 큰 통합 파일입니다. 처음부터 끝까지 읽기보다 index_batch_turbo, refresh, run_hybrid, fuse_rrf처럼 함수 하나씩 입출력을 적는 방식이 현실적입니다. 인라인 테스트와 구현도 같은 파일 안에 섞여 있습니다.
| 확장자 기준 언어 | 파일 수 | 물리적 줄 수 |
|---|---|---|
| Rust · .rs | 592개 | 587,728줄 · 약 58.8만 줄 |
| JavaScript · .js + .mjs | 159개 | 49,372줄 · 약 4.9만 줄 |
| Python · .py | 210개 | 42,949줄 |
| HTML · .html | 210개 | 94,306줄 |
| CSS · .css | 7개 | 7,488줄 |
| 셸 · .sh | 73개 | 11,010줄 |
집계 범위는 커밋 0ccab07의 Git 추적 파일이며, 각 파일의 줄바꿈으로 나눈 물리적 줄 수입니다. 빈 줄·주석·테스트·예제·추적된 생성 파일을 포함합니다. Rust 58.8만 줄을 전부 제품 구현이라고 보거나, HTML 9.4만 줄을 전부 웹 콘솔이라고 해석하면 안 됩니다.
저장소에는 웹 문서와 평가용 자료도 많습니다. 코드 규모는 학습 범위를 가늠하는 정보이지 성숙도나 품질의 점수가 아닙니다. 짧은 함수와 그 함수를 지키는 테스트를 묶어 읽는 것이 더 유익합니다.
이 저장소에서 배울 가장 큰 주제는 검색 부품을 연결하면서도 각 부품의 약속을 지키는 방법입니다. 다음 항목은 작은 별도 프로그램으로 옮겨 실험하기 좋습니다.
Embedder는 선택 가능한 백엔드를 타입으로 드러내고, ReadAdmission은 읽기 허용·쓰기 진행 중·오염 상태를 구별합니다. 문자열 플래그와 true/false만으로 표현할 때보다 빠뜨린 분기를 발견하기 쉽습니다.
값 있음·값 없음·갱신 중을 열거형으로 표현하고 각 경우의 반환값을 적습니다. 여기에 오류 상태를 추가했을 때 match가 어디서 수정을 요구하는지 확인해 보세요.
하이브리드 검색의 run_hybrid_with_deadline() 주석은 하위 질의의 동시 진행과 CPU의 병렬 실행을 명확히 구분합니다. 임베딩 응답을 기다리는 동안 다른 검색을 진행할 수 있어도, 모든 검색 계산이 서로 다른 코어에서 실행된다는 뜻은 아닙니다.
xerj-autoindex는 동기식 HTTP와 작업 스레드를 사용합니다. “Rust 서버니까 모든 함수가 async일 것”이라는 예상도 맞지 않습니다. 파일 파싱과 CPU 작업을 어느 실행기에서 처리하는지부터 추적해 보세요.
대기 100ms 두 개와 CPU 계산 두 개를 구분한 시간표를 그립니다. 순차 실행, 대기만 겹친 실행, 계산까지 병렬화한 실행의 예상 시간을 적어 보면 async의 역할이 분명해집니다.
| 읽을 코드 | 배울 내용 | 손으로 해볼 실험 |
|---|---|---|
engine/crates/xerj-fts/src/analyzer.rs | 입력 문자열을 검색 용어로 바꾸는 분석기 | 대소문자·기호가 다른 식별자를 어떤 단위로 나눌지 적기 |
engine/crates/xerj-fts/src/bm25.rs | 드문 단어, 빈도 포화, 길이 보정 | 같은 단어가 1·3·10번 나오는 문서의 점수 계산 |
engine/crates/xerj-vector/src/hnsw.rs | 그래프를 통한 근사 후보 탐색 | 2차원 점 20개의 이웃을 선으로 연결하기 |
engine/crates/xerj-engine/src/index.rs의 fuse_rrf() | 서로 다른 점수 척도를 순위로 합치기 | 두 검색 목록을 만들고 중복 문서의 기여도 더하기 |
RRF는 같은 문서가 두 목록에 나타나면 기여도를 더합니다. 동점일 때의 순서도 결과 재현성에 영향을 주므로, 점수 수식뿐 아니라 최종 정렬 기준까지 읽어 보세요.
engine/crates/xerj-autoindex/src/ids.rs의 doc_id()는 데이터셋 이름, 파일 식별자, 레코드 위치로 ID를 결정합니다. 같은 입력을 다시 보내면 같은 ID를 덮어쓰므로 재시도 때 중복을 줄일 수 있습니다. 이 성질을 멱등성이라고 합니다.
다만 이 파일의 file_key()는 파일 앞 64KiB와 크기로 식별합니다. 같은 크기에서 뒷부분만 바뀌면 이 값만으로는 변경을 증명할 수 없습니다. 전체 증분 동기화는 별도의 상태·세대·내용 검증 코드를 함께 읽어야 합니다. 해시 함수 하나를 보고 전체 변경 감지가 완벽하다고 결론 내리면 안 됩니다.
# ids.rs의 식별자 조합을 설명한 개념식
file_key = hash(파일 앞 64KiB) + 파일 크기
doc_id = hash("ax1" + 데이터셋 이름 + file_key + 레코드 위치)
같은 레코드 재전송 -> 같은 doc_id -> 중복 생성 대신 같은 대상 갱신
engine/crates/xerj-autoindex/src/extract/mod.rs의 RawRecord는 필드와 locator, 그룹, 필드 출처를 담습니다. CSV 한 행, PDF의 문서 구간, 코드 선언은 서로 다른 단위지만 같은 후속 처리 경로로 보낼 수 있습니다.
특히 FieldOrigin::Data와 FieldOrigin::Extractor를 구분합니다. CSV 헤더처럼 원본에서 나온 필드와, 파서가 추가한 title 같은 필드를 섞으면 파서 개선만으로 데이터셋 분류와 ID가 바뀔 수 있기 때문입니다. 데이터 모델의 “출처”가 재시도 안정성까지 연결되는 사례입니다.
engine/crates/xerj-autoindex/src/extract/pdf.rs의 extract()는 spawn_worker()를 통해 별도 프로세스에서 PDF를 읽습니다. 크기·페이지·추출 텍스트·작업 출력을 제한하며, Unix에서는 주소 공간도 제한합니다. 코드 주석도 이것을 충돌과 자원 사용의 격리라고 설명하며 보안 샌드박스라고 부르지 않습니다.
프로세스가 분리돼도 같은 사용자 권한으로 실행된다면 모든 파일 접근이 차단되는 것은 아닙니다. 격리의 목적을 “서버와 함께 죽지 않게 한다”와 “악성 코드를 가둔다”로 나누는 것이 중요한 학습 포인트입니다.
engine/crates/xerj-ai/src/local.rs의 lexical_vector_space_is_frozen()은 고정 입력의 벡터 비트와 지문을 검사합니다. 단순히 출력 길이가 384인지 확인하는 것보다 강합니다. 토큰화나 가중치가 바뀌어 기존 벡터와 새 벡터가 섞이는 문제를 잡으려는 테스트입니다.
engine/crates/xerj-mcp/tests/published_schema_drift.rs는 공개 도구 스키마와 서버 도구 정의의 차이를 검사합니다. ES YAML 테스트는 호환 요청의 의미를 검사합니다. 각각 벡터 공간의 안정성, 문서와 제품의 일치, 외부 API 계약이라는 서로 다른 성질을 지킵니다.
| 항목 | 소스·설정에서 확인되는 내용 | 독자가 판단할 것 |
|---|---|---|
| 운영체제 | 릴리스 설정에 Linux·macOS·Windows, 각각 x86-64·arm64 | 운영체제와 CPU에 맞는 배포 파일 선택 |
| x86-64 | engine/.cargo/config.toml의 target-cpu=x86-64-v3 | AVX2·BMI2·FMA 등 해당 기준 지원 여부 |
| ARM | Linux·Windows ARM 설정은 기본 타깃, Apple ARM은 apple-m1 | arm64라는 이름만으로 운영체제별 바이너리를 혼용하지 않기 |
| GPU | 기본 어휘 임베딩에는 학습 모델이 없음. 내장 neural 경로는 CPU 실행 지원 | 이 학습 흐름에 별도 GPU를 전제할 필요 없음 |
| 네트워크 | 어휘 임베딩은 모델 다운로드 불필요. neural 첫 사용은 모델 자산 필요 | 오프라인 준비 시 모델 캐시와 선택 기능 점검 |
README의 “단일 정적 바이너리”라는 소개를 모든 배포 타깃의 동일한 링크 조건으로 일반화하면 안 됩니다. 릴리스에는 Linux GNU·musl 타깃이 따로 있고 Docker 실행 이미지는 Debian 기반입니다. 소스 빌드 환경도 배포 바이너리의 실행 환경보다 많은 도구와 공간을 요구합니다.
원본 파일, 벡터, 검색 자료구조, 모델, 작업 중 버퍼를 각각 더해 용량을 생각해야 합니다. 다음은 압축 전 f32 벡터 본문만의 산술 예시이며, xerj 전체 메모리 실측값이 아닙니다.
벡터 본문 바이트 = 벡터 수 × 차원 수 × 4
100,000개 × 384차원 × 4 = 153,600,000바이트 ≈ 146.5MiB
1,000,000개 × 384차원 × 4 ≈ 1.43GiB
추가 비용: HNSW 연결 + 문서·메타데이터 + 역색인 + WAL·캐시
+ 동시 작업 버퍼 + 선택한 신경망 모델
문서 한 개를 여러 구간으로 나누면 벡터 수가 파일 수보다 많아집니다. 반대로 어휘 검색만 사용하면 신경망 가중치를 메모리에 올릴 필요가 없습니다. README 기준 MiniLM의 약 90MB도 다운로드 자산 규모이며 프로세스 전체 RSS의 상한이 아닙니다.
| 설정 또는 제한 | 확인값 | 해석 |
|---|---|---|
| 예시 저장 설정 | flush_size_mb = 512flush_interval_secs = 30 | 버퍼를 세그먼트로 내리는 크기 기준과 점검 주기. 전체 메모리의 고정 상한은 아님 |
| 예시 질의 제한 | max_query_memory_mb = 512 | 단일 질의의 중간 결과 예산. 전체 프로세스 예산과 구분 |
| Helm 기본 자원 | 요청 메모리 1Gi, 제한 8GiCPU 요청 500m, 제한 4000m | 배포 차트의 시작 설정이며 공식 최소·보장 용량이 아님 |
| Helm 기본 디스크 | PVC 50Gi | 원본·색인·WAL·백업을 고려해 조정할 배포 값 |
| PDF 작업 제한 | Unix 주소 공간 1536 MiB, 입력 상한 512 MiB | Unix 외 플랫폼에는 OS 메모리 상한이 없음. 이 수치는 상시 사용량이나 시스템 최소 RAM을 뜻하지 않음 |
작은 텍스트 수십 개로 시작해 자료량과 동시 작업 수를 점진적으로 늘리세요. PDF 처리·신경망 모드·큰 로그 입력은 서로 자원 사용 패턴이 다릅니다. 최소 요구 RAM을 하나의 숫자로 확정할 만한 보편적인 근거는 없습니다.
engine/xerj.default.toml은 hnsw_m, hnsw_ef_construction, hnsw_ef_search가 이 빌드의 엔진에 연결되지 않았다고 명시합니다. 엔진은 고정 M = 16, 구성 폭 200을 사용하며, 질의 폭은 num_candidates를 받아 최소 800으로 올립니다.
engine/crates/xerj-engine/src/index.rs의 HNSW_MIN_DOCS = 1024와 HNSW_EF_FLOOR = 800도 함께 확인할 수 있습니다. 작은 벡터 집합·필터·일부 벡터 설정에서는 정확 탐색 경로를 사용하므로, 100개짜리 실습으로 HNSW의 성능을 측정했다고 말하면 안 됩니다.
단일 노드 학습 결과만으로 대용량 복구 시간이나 다중 노드 내구성을 예상할 수 없습니다. 차트에 남아 있는 과거 버전의 주석도 현재 보장으로 사용하지 마세요. 데이터 증가·동시 쓰기·정상 종료·재시작을 각각 평가해야 합니다.
실습은 설치된 바이너리와 개인용 로컬 노드를 전제로 합니다. 명령은 학습용 예시이며 결과 수나 실행 시간을 보장하지 않습니다. 각 과제에서 입력·기대 결과·관찰 결과를 남기면 다음 실험의 비교 기준이 됩니다.
engine/crates/xerj-fts/src/bm25.rs의 idf(), tf_norm(), score_term()을 읽습니다. 전체 문서 10개, 해당 단어 포함 문서 2개, 평균 길이 100을 놓고 문서 길이 50·100·200에서 단어 빈도 1의 점수를 계산합니다.
완료 기준: 같은 빈도에서도 문서 길이에 따라 점수가 달라지는 이유와, 빈도를 열 배 늘려도 점수가 열 배가 되지 않는 이유를 설명합니다. 작은 계산기나 별도 연습 프로그램만으로도 가능한 과제입니다.
학습 문장 두 개가 있는 Markdown, 함수 하나가 있는 Rust 파일, 헤더가 있는 CSV를 ./xerj-lab/docs에 준비합니다. --dry-run 후 전용 접두사 study로 색인하고 데이터 지도를 확인합니다.
완료 기준: 파일 수와 레코드 수가 다른 이유를 설명하고, 아래 질의에서 고유한 표식 study_unique_42를 넣은 자료를 찾아 원본 위치를 확인합니다. 코드 선언이 별도 결과로 나오는지도 살펴봅니다.
xerj autoindex ./xerj-lab/docs --prefix study --dry-run
xerj autoindex ./xerj-lab/docs --prefix study
xerj autoindex map
curl -sS 'http://127.0.0.1:9200/study-*/_search' \
-H 'Content-Type: application/json' \
-d '{"query":{"query_string":{"query":"study_unique_42"}},"size":5}'
인증을 켠 노드라면 해당 API 키를 헤더와 CLI 설정에 전달해야 합니다. 실제 생성 인덱스 이름은 데이터 지도에서 확인합니다. 접두사가 같은 다른 실험 자료를 섞지 않는 것이 결과를 해석하기 쉽습니다.
독립 인덱스 study-rag에 text 필드는 text, vec는 2차원 dense_vector로 매핑합니다. 아래 세 레코드를 문서 ID a·b·c로 넣고, 검색 전에 POST /study-rag/_refresh를 호출합니다.
완료 기준: BM25만, kNN만, 하이브리드의 순서를 각각 기록하고 RRF 계산과 비교합니다. 작은 데이터이므로 이 과제는 HNSW 속도가 아니라 점수 융합을 배우는 실험입니다.
// PUT /study-rag
{"mappings":{"properties":{
"text":{"type":"text"},
"vec":{"type":"dense_vector","dims":2,"similarity":"cosine"}
}}}
// 각각 PUT /study-rag/_doc/a, /b, /c 에 넣을 JSON
{"text":"E_CONN_RESET retry budget", "vec":[1.0,0.0]}
{"text":"retry after disconnect", "vec":[0.7,0.7]}
{"text":"network recovery guide", "vec":[0.0,1.0]}
// POST /study-rag/_search
{
"query":{"hybrid":{
"queries":[
{"query":{"match":{"text":"E_CONN_RESET"}},"weight":1.0},
{"query":{"knn":{
"field":"vec","query_vector":[0.0,1.0],"k":3
}},"weight":1.0}
],
"fusion":{"type":"rrf","k":60}
}},
"size":3
}
임의로 정한 2차원 벡터는 신경망 임베딩이 아닙니다. 벡터의 의미를 사람이 정했기 때문에 거리와 순위를 눈으로 확인할 수 있습니다. 이어서 두 검색의 가중치를 바꾸며 최종 순위가 왜 달라지는지 설명해 보세요.
xerj mcp를 실행하는 클라이언트 설정을 만들고, tools/list에서 검색 도구의 스키마를 확인합니다. 그다음 xerj_search를 호출해 같은 HTTP 질의와 문서 ID가 일치하는지 비교합니다.
완료 기준: MCP 프로세스와 검색 노드의 역할을 나눠 그립니다. 도구 이름을 틀렸을 때와 질의가 잘못됐을 때 응답이 어떻게 달라지는지도 기록합니다. Hub나 외부 모델 없이 로컬 색인으로 수행할 수 있습니다.
// MCP 클라이언트 설정 예시: command는 실제 설치 절대 경로
{
"mcpServers":{
"xerj":{
"command":"/absolute/path/to/xerj",
"args":["mcp"],
"env":{"XERJ_URL":"http://127.0.0.1:9200"}
}
}
}
전용 데이터 디렉터리의 작은 인덱스에 고정 ID 문서 20개를 넣고 같은 ID로 다시 씁니다. 정상 재시작 후 문서 수와 각 ID의 최종 값을 확인합니다. 그 뒤 별도 실험 복제본에서 sync·batched의 로그 순서와 응답 시점을 추적합니다.
완료 기준: 재전송 중복 방지와 장애 복구를 별개의 성질로 설명합니다. 강제 종료 실험을 추가한다면 폐기 가능한 전용 프로세스만 대상으로 삼고, 프로세스 종료를 전원 장애의 재현이라고 부르지 않습니다.
추가 도전으로 engine/crates/xerj-ai/src/local.rs의 고정 벡터 테스트를 읽고 “차원은 그대로인데 단어 가중치만 바뀌는 경우”를 생각해 보세요. 어떤 식별자를 바꾸고 어떤 자료를 다시 색인해야 할지 설계 메모를 작성하면 데이터 호환성 공부로 이어집니다.
Rust 문법부터 분산 시스템까지 한 번에 공부하면 연결 고리를 놓치기 쉽습니다. 작은 검색기를 먼저 이해한 뒤, 입력 형식과 장애 조건을 하나씩 늘리는 순서가 좋습니다. 아래 시간은 학습 계획용이며 숙련도에 따라 조정합니다.
| 주차 | 핵심 질문과 배경 | xerj에서 읽을 곳 | 남길 결과물 |
|---|---|---|---|
| 1주 · Rust 기초 | 값의 주인은 누구인가?String, 참조, Result, 열거형, match | engine/crates/xerj-ai/src/local.rsengine/crates/xerj-autoindex/src/ids.rs | 입력 문자열을 벡터·ID로 바꾸는 작은 연습 프로그램과 설명 |
| 2주 · 전문 검색 | 단어를 어떻게 색인하고 순위를 매기는가? 토큰화·역색인·TF·IDF | engine/crates/xerj-fts/src/analyzer.rsengine/crates/xerj-fts/src/bm25.rs·engine/crates/xerj-fts/src/postings.rs | 문서 5개의 단어→문서 표와 BM25 계산표 |
| 3주 · 벡터 검색 | 좌표가 비슷하면 내용도 비슷한가? 코사인·정규화·정확/근사 탐색 | engine/crates/xerj-vector/src/distance.rsengine/crates/xerj-vector/src/hnsw.rs·fuse_rrf() | 2차원 실험과 하이브리드 순위 비교표 |
| 4주 · 입력 처리 | 파일을 어떤 레코드로 나눌 것인가? 스키마·형 변환·멱등성 | engine/crates/xerj-autoindex/src/sniff.rsengine/crates/xerj-autoindex/src/extract/mod.rs·engine/crates/xerj-autoindex/src/coerce.rs | 자기 자료 3종의 레코드 경계·필드·식별자 설계 |
| 5주 · 저장과 동시성 | 언제 성공이라고 응답할 것인가? WAL·fsync·가드·공개 장벽 | engine/crates/xerj-storage/src/wal.rsengine/crates/xerj-engine/src/collection_publication.rs | 정상·재시도·중간 실패별 상태 전이 그림 |
| 6주 · 도구와 제품 | 외부 호출자와 어떤 약속을 할 것인가? JSON-RPC·스키마·오류·권한 | engine/crates/xerj-mcp/src/lib.rsengine/crates/xerj-api/src/authz.rs | 로컬 검색 도구 1개와 입력·응답·오류 계약 문서 |
Arc, 원자 연산, 비동기 수명까지 첫 주에 완전히 이해하려 하지 마세요. 처음에는 “입력을 받아 무엇을 반환하는가?”, “실패는 어떤 타입인가?”, “이 함수는 파일·네트워크에 접근하는가?”의 세 질문에 답하면 충분합니다.
2주차에는 외부 검색엔진 없이 작은 역색인을 종이에 만들어도 됩니다. 문서 ID를 정수로 정하고, 단어마다 포함 문서 목록을 만들면 postings가 무엇인지 이해할 수 있습니다. 자료구조의 목적을 안 다음 압축 코드를 읽는 편이 부담이 적습니다.
내 문서에서 질문 20개를 고르고 각 질문의 정답 문서 ID를 기록합니다. BM25·어휘 벡터·신경망·하이브리드별로 상위 5개 안에 정답이 있는 비율을 비교하세요. 모델을 바꾼 실험은 자료 전체를 같은 임베딩으로 다시 준비합니다.
커널 쓰기, 동기화, 세그먼트 공개, 로그 정리를 시간축에 놓습니다. 각 지점에서 실패하면 무엇을 다시 읽을지 적습니다. 처리량을 높이려는 변경이 내구성·읽기 일관성을 건드리는 순간을 찾아보세요.
engine/crates/xerj-mcp/src/token_budget.rs와 코드 검색 결과를 읽습니다. 본문을 줄여도 문서 ID·원본 위치·오류·잘림 여부가 남아 있는지 살펴보세요. 검색 품질과 응답 예산을 함께 다루는 작은 도구를 설계할 수 있습니다.
기능을 많이 재현하는 것보다 “왜 이 순서로 처리하는가?”를 설명하는 것이 목표입니다. 입력 하나와 실패 하나를 끝까지 추적할 수 있으면 다음 주제로 넘어갈 준비가 된 것입니다.
| 용어 | 쉬운 설명 |
|---|---|
| 로컬 우선 | 내 기기·내 서버에서 자료를 보관하고 검색하는 흐름을 중심으로 설계하는 것. 선택한 외부 기능까지 자동으로 오프라인이 되는 것은 아닙니다. |
| 노드 | 자료를 보관하고 요청에 응답하는 실행 중인 검색 서버 한 개입니다. |
| 크레이트 · workspace | Rust 코드 묶음과, 관련 묶음을 함께 관리하는 작업 공간입니다. |
| Cargo feature | 빌드할 때 포함할 기능을 고르는 스위치입니다. 실행 시 그 기능을 사용하는지는 별도 설정입니다. |
| 색인 | 검색을 빠르게 하도록 원문에서 미리 만든 보조 자료입니다. |
| 역색인 · postings | 단어에서 문서로 찾아가는 표와, 단어별 문서·빈도·위치 목록입니다. |
| 분석기 · 토큰화 | 문장을 검색에 쓸 단어 조각으로 나누고 정리하는 처리입니다. 문서와 질문에 일관되게 적용해야 합니다. |
| BM25 | 희귀한 단어, 단어 빈도, 문서 길이를 보고 관련도를 계산하는 전문 검색 점수입니다. |
| 임베딩 | 텍스트 같은 자료를 고정 길이 숫자 배열로 바꾸는 것입니다. 방식에 따라 어휘 또는 의미 정보를 반영합니다. |
| 특징 해싱 | 단어·문자 조각을 해시로 정해진 칸에 넣는 벡터화 방식입니다. 기본 xerj 임베딩이며 신경망 학습은 없습니다. |
| 코사인 유사도 | 벡터의 길이보다 방향이 얼마나 비슷한지 보는 값입니다. |
| L2 정규화 | 벡터 길이가 1이 되게 모든 좌표를 같은 비율로 나누는 처리입니다. |
| kNN · ANN | 가까운 이웃 k개를 찾는 문제와, 속도를 위해 정답 후보를 근사적으로 찾는 방법입니다. |
| HNSW | 여러 층의 이웃 연결을 따라 가까운 벡터 후보를 찾는 그래프 자료구조입니다. |
| 정확 재점수화 | 찾아낸 후보의 저장 벡터로 점수를 다시 계산하는 것입니다. 탐색에서 빠진 후보까지 복구하는 것은 아닙니다. |
| 하이브리드 검색 | 전문 검색과 벡터 검색처럼 다른 방법의 결과를 합치는 검색입니다. |
| RRF | 서로 다른 검색 점수 대신 순위의 역수를 사용해 결과 목록을 합치는 방법입니다. |
| 재순위화 | 이미 찾은 후보를 추가 모델이나 평가기로 다시 정렬하는 단계입니다. |
| RAG | 검색한 자료를 생성 모델의 입력에 붙여 답을 만드는 방식입니다. 검색과 생성의 품질은 따로 평가해야 합니다. |
| AST · tree-sitter | 코드의 문법 구조를 나무처럼 표현한 결과와, 그 구조를 얻는 구문 분석 도구입니다. |
| 스키마 · 매핑 | 어떤 필드가 문자열·숫자·벡터인지 정한 약속입니다. 같은 JSON이라도 필드 타입에 따라 검색 방식이 달라집니다. |
| 멱등성 | 같은 작업을 재시도해도 결과가 중복으로 늘어나지 않는 성질입니다. |
| WAL | 메모리 자료구조를 바꾸기 전에 남기는 변경 기록입니다. 재시작 복구의 근거가 됩니다. |
| memtable · segment | 쓰기 중인 메모리 자료와, 이를 디스크에 정리해 만든 자료 묶음입니다. |
| fsync | 운영체제에 저장 장치 반영을 요청하는 동기화 작업입니다. 일반적인 메모리 버퍼 비우기와 구분합니다. |
| 체크섬 | 읽은 바이트가 기록된 것과 같은지 확인하는 짧은 검사용 값입니다. |
| 스냅샷 · 공개 | 독자가 읽을 상태를 묶고, 새 상태를 읽을 수 있게 내놓는 일입니다. 백업 스냅샷과는 문맥을 구분해야 합니다. |
| Arc · RAII | 여러 소유자가 값을 공유하는 참조 카운트 포인터와, 값이 해제될 때 정리 작업을 자동 수행하는 패턴입니다. |
| MCP · stdio | AI 도구 연결 프로토콜과, 프로세스 표준 입력·출력으로 통신하는 방식입니다. |
| JSON-RPC | 메서드·인자·요청 ID·결과 또는 오류를 JSON으로 주고받는 원격 호출 형식입니다. |
| 네임스페이스 | 메모리 자료에 붙이는 논리적인 이름 범위입니다. 이름만 나눠 놓는 것과 접근 권한을 검사하는 것은 다릅니다. |
| RC | 정식 배포 후보 버전입니다. 1.0.0-rc.98을 안정판 1.0.0과 같은 뜻으로 읽으면 안 됩니다. |
| 항목 | 기준 시점의 상태 |
|---|---|
| 프로젝트 | xerj-org/xerj · 로컬 우선 검색엔진과 MCP 서버 |
| 선정 | TrendShift Weekly #17 · 분석일 2026-10-23 |
| 스타 / 포크 | 13,210 / 950 · 2026-10-23 GitHub 기준 |
| 생성일 | 2026-06-30 |
| 최신 릴리스 | v1.0.0-rc.98 · 2026-10-22 · 정식 버전 전의 RC |
| 최근 push | 2026-10-23 · GitHub 기준 |
| 최근 커밋 | 0ccab07 · 2026-10-23 · rc.98에서 LZ4 자료를 검색한 현장 보고 문서 추가 (#2252) |
| 주 언어 / 규모 | Rust · 추적된 .rs 592개, 587,728줄. 주석·빈 줄·테스트·예제·생성 파일 포함 |
| 그 밖의 규모 | JavaScript(.js/.mjs) 49,372줄 · Python 42,949줄 · HTML 94,306줄 · CSS 7,488줄 · 셸 11,010줄 |
| 구성 | 내부 크레이트 18개 + YAML 테스트 실행기 1개. 웹 콘솔은 브라우저 JavaScript 모듈 |
| 라이선스 | Apache-2.0 · LICENSE · NOTICE |
| 한국어 자료 | 저장소에 README.ko.md 있음. 상세 구현 설명은 영어 주석·문서도 함께 참조 |
GitHub 메타 수치와 최신 릴리스 날짜는 2026-10-23 기준입니다. 의존성·설정·코드 규모·구현 판단은 커밋 0ccab07 기준입니다. 이후 버전의 동작까지 이 표로 단정하지 마세요.
| 눈에 보이는 표현 | 현재 코드에서 확인할 사실 |
|---|---|
| semantic 검색 | 기본은 local_embed()의 어휘 해시. 도구 이름이 신경망 선택을 증명하지 않음 |
| HNSW 조정 옵션 | 설정 파일이 세 옵션의 미연결을 명시. 실제 상수와 요청별 경로를 확인해야 함 |
xerj-wasm 이름 | 현재는 네이티브 변환과 플러그인 인터페이스. 실제 WASM 실행기는 미래 연결 대상으로 설명됨 |
| 메모리 API의 옛 주석 | 파일 머리의 “네임스페이스 권한 없음” 설명과 달리, store()·recall() 등은 authorize_memory_namespace()를 호출 |
| 오래된 RAG 예제의 범위 | 외부 벡터 입력을 설명하며 청킹을 앱 몫으로 서술. 현재는 engine/crates/xerj-ai/src/chunker.rs와 내장 임베딩 경로도 있으므로 제품 전체의 부재 주장으로 확대하면 안 됨 |
메모리 권한 검사가 있다는 사실 역시 일반적인 다중 사용자 RBAC 전체를 보장하지는 않습니다. engine/crates/xerj-api/src/authz.rs는 예약 메모리 인덱스와 범위가 지정된 키의 경계를 설명하며, 일반 인덱스에 대한 포괄적인 권한 모델에는 남은 범위가 있음을 명시합니다.
① 기본 벡터는 의미 이해 모델이 아닙니다. 어휘 겹침이 작은 한국어 질문·동의어·번역 질의가 잘 맞는다고 보장할 수 없습니다. 신경망을 선택해도 한국어 검색 품질은 내 질문과 정답 자료로 별도 평가해야 합니다.
② 로컬 우선과 외부 통신 없음은 다릅니다. 외부 임베딩 프록시·재순위화 평가기·Hub 등은 연결을 만들 수 있습니다. 어떤 기능을 켰고 어떤 자료가 전달되는지 선택별로 확인해야 합니다.
③ API 호환과 완전한 대체 가능성은 다릅니다. 하이브리드 집계와 learned 융합처럼 명시적으로 거절하는 기능이 있습니다. 호환 테스트의 존재만으로 모든 클라이언트·모든 쿼리·모든 운영 방식의 호환을 보장할 수 없습니다.
④ RC와 저장 계약을 함께 봐야 합니다. 기본 batched WAL의 100ms는 설정된 동기화 주기입니다. 모든 상황에서 관측되는 손실 상한을 실측한 값이 아닙니다. 노드·파일시스템·장치 조건에 맞는 복구와 백업 검증이 필요합니다.
⑤ 벤치마크와 용량은 조건을 동반합니다. 성능·복구 시간·전체 메모리 사용량은 하드웨어와 자료·설정에 따라 달라집니다. 줄 수는 물리적 집계, 벡터 용량은 산술 예시, 토큰 절감 이야기는 README의 실험 주장입니다. 각각을 내 서비스의 보장치로 옮기지 마세요.