feat(kebab-app + kebab-store-sqlite): p9-fb-19 search LRU cache + corpus_revision #78
Reference in New Issue
Block a user
Delete Branch "feat/p9-fb-19-cache"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
도그푸딩 item 15 — TUI / 같은 process 안에서 동일 query 반복 시 SQLite FTS + Lance + RRF 재계산이 매번 발생하던 비용 해소. in-process LRU 캐시 + monotonic
corpus_revision카운터로 ingest commit 발생 시 모든 entry 자동 stale.변경
kv (key TEXT PK, value TEXT) STRICT+corpus_revision = '0'seedSqliteStore::corpus_revision()/bump_corpus_revision()— atomic UPDATEingest_with_config_cancellable—new + updated > 0시 bump, no-op reingest 보존App.search_cache: Option<Mutex<LruCache<...>>>— capacity fromconfig.search.cache_capacity(default 256, 0 비활성)SearchCacheKey= query_norm (NFKC + trim + lowercase) + mode + k + snippet_chars + embedding_version + chunker_version + corpus_revisionApp::search— cache lookup → miss 시search_uncached→ putsearch_uncached_with_configfacade + CLI--no-cacheflagcorpus_revisionrow테스트
cargo test --workspace --no-fail-fast -j 1exit 0cargo clippy --workspace --all-targets -- -D warningsclean문서
kebab search: 캐시 동작 +--no-cache안내[search] cache_capacity = 256Out of scope
회차 1 — cache + corpus_revision 패턴 정석. SQLite atomic UPDATE, NFKC + trim + lowercase 정규화, lexical 모드 embedding_version 빈 문자열 처리, run-loop 통합, --no-cache CLI 까지 spec 의 모든 facet cover. 7 신규 테스트 (3 store + 4 app integration) 도 fresh / stale / NFKC / parity / bump 골고루.
actionable nit 2 건 — (a) mutex poison 시 silently bypass 의 가시성 부족, (b) spec naming (index_version) 과 impl (corpus_revision) 차이의 doc 누락.
@@ -88,1 +129,4 @@// `None` (cache disabled — every search hits the retrievers).let search_cache = NonZeroUsize::new(config.search.cache_capacity).map(|cap| Mutex::new(LruCache::new(cap)));Ok(Self {spec p9-fb-19 의 제목
"Search result cache (in-memory LRU + index_version invalidation)"는 무효화 카운터를index_version으로 칭하지만, 구현은corpus_revision으로 다른 이름을 채택했습니다 (이미 §9 에 다른 의미의index_version라벨이 있어 충돌 회피).SearchCacheKeydoc 에 한 줄 추가하면 미래 reader 가 spec / impl 사이의 naming 차이를 즉시 파악할 수 있습니다:선택:
tasks/HOTFIXES.md에 한 줄 entry 추가 ("p9-fb-19 spec 의 index_version → impl 의 corpus_revision 으로 rename").if let Ok(mut guard) = cache.lock()가 mutex poison 시 silently fall through 합니다 (cache 활성인데 lock 실패 = 마치 cache 비활성처럼 동작). 정확성 측면에선 OK 이지만 (search 결과 항상 신선), poison 자체가 이전 panic 의 흔적이라 한 번은 알리는 게 좋습니다.제안: 두 lock 호출 모두
.lock().unwrap_or_else(|e| { tracing::warn!(...); e.into_inner() })로 recovery. cache 가 poison 됐어도 다음 호출이 정상 사용 가능하고, 한 번은 warn 로그가 남음.또는 그냥
.expect("search_cache mutex poisoned")로 fail-fast — single-process facade 의 invariant 위반은 실제로 fatal 이라는 입장도 가능. (1) 이 graceful, (2) 가 명시적.- `App::search` 의 두 cache.lock() 호출이 mutex poison 시 silently bypass 하던 것을 `unwrap_or_else(|e| { warn!; e.into_inner() })` recovery 로 교체. cache 가 poison 됐어도 다음 호출은 정상이고 한 번은 warn 로그가 남아 panic 흔적 추적 가능. lookup 후 lock drop → retriever 호출 → 재 lock 으로 lock granularity 도 짧게. - `clear_search_cache` 도 같은 recovery 패턴. - `SearchCacheKey` doc 에 spec 와 impl 의 naming 차이 (index_version vs corpus_revision) 명시 + HOTFIXES entry 추가. spec 의 index_ version 명칭이 design §9 의 기존 `IndexVersion` newtype (embedding -index identity 라벨) 과 충돌해서 corpus_revision 으로 rename. 7 tests/search_lexical 통과. clippy clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>회차 2 — nit 2 건 깔끔히 반영.
추가 지적 없음. 머지 OK.