- 홈
- Documentation
- 구성
- 파일시스템 스캔 캐시 아키텍처 계약
파일시스템 스캔 캐시 아키텍처 계약
이 문서는 Rust(crates/pi-natives/src/fs_cache.rs)로 구현된 공유 파일시스템 스캔 캐시와 packages/coding-agent에 노출되는 네이티브 디스커버리/검색 API에서 소비되는 현재 계약을 정의합니다.
이 캐시의 정의
섹션 제목: “이 캐시의 정의”캐시는 스캔 범위와 탐색 정책을 키로 하는 전체 디렉토리 스캔 항목 목록(GlobMatch[])을 저장하며, 상위 수준 작업(glob 필터링, 퍼지 스코어링, grep 파일 선택)이 이 캐시된 항목에 대해 실행될 수 있도록 합니다.
주요 목표:
- 반복적인 디스커버리/검색 호출에 대한 반복적인 파일시스템 워킹 방지
- 동일한 스캔 정책을 공유하는
glob,fuzzyFind,grep간의 일관성 유지 - 빈 결과에 대한 명시적 비활성 복구 및 파일 변경 후 명시적 무효화 허용
소유권 및 공개 인터페이스
섹션 제목: “소유권 및 공개 인터페이스”- 캐시 구현 및 정책:
crates/pi-natives/src/fs_cache.rs - 네이티브 소비자:
crates/pi-natives/src/glob.rscrates/pi-natives/src/fd.rs(fuzzyFind)crates/pi-natives/src/grep.rs
- JS 바인딩/내보내기:
packages/natives/src/glob/index.ts(invalidateFsScanCache)packages/natives/src/glob/types.tspackages/natives/src/grep/types.ts
- 코딩 에이전트 변경 무효화 헬퍼:
packages/coding-agent/src/tools/fs-cache-invalidation.ts
캐시 키 파티셔닝 (하드 계약)
섹션 제목: “캐시 키 파티셔닝 (하드 계약)”각 항목은 다음을 키로 합니다:
- 정규화된
root디렉토리 경로 include_hidden불리언use_gitignore불리언
의미:
- 숨김 파일 포함 스캔과 미포함 스캔은 항목을 공유하지 않습니다.
- gitignore 준수 스캔과 무시 비활성화 스캔은 항목을 공유하지 않습니다.
- 소비자는 hidden/gitignore 동작에 대해 안정적인 의미론을 전달해야 합니다. 어느 하나의 플래그를 변경하면 다른 캐시 파티션이 생성됩니다.
node_modules 포함 여부는 캐시 키에 포함되지 않습니다. 캐시는 node_modules가 포함된 항목을 저장하며, 소비자별 필터링은 조회 후에 적용됩니다.
스캔 수집 동작
섹션 제목: “스캔 수집 동작”캐시 채우기는 include_hidden과 use_gitignore로 구성된 결정론적 워커(ignore::WalkBuilder)를 사용합니다:
follow_links(false)- 파일 경로 기준 정렬
.git은 항상 건너뜀node_modules는 캐시 스캔 시점에 항상 수집됨 (이후 선택적으로 필터링)- 항목 파일 유형 +
mtime은symlink_metadata를 통해 캡처됨
검색 루트는 resolve_search_path에 의해 해석됩니다:
- 상대 경로는 현재 cwd 기준으로 해석됨
- 대상은 기존 디렉토리여야 함
- 루트는 가능한 경우 정규화됨
신선도 및 제거 정책
섹션 제목: “신선도 및 제거 정책”전역 정책 (환경 변수로 재정의 가능):
FS_SCAN_CACHE_TTL_MS(기본값1000)FS_SCAN_EMPTY_RECHECK_MS(기본값200)FS_SCAN_CACHE_MAX_ENTRIES(기본값16)
동작:
get_or_scan(...)- TTL이
0인 경우: 캐시를 완전히 우회하고, 항상 새로운 스캔 수행 (cache_age_ms = 0) - TTL 내의 캐시 히트: 캐시된 항목 + 0이 아닌
cache_age_ms반환 - 만료된 히트: 키를 제거하고, 재스캔하여 새 항목 저장
- TTL이
- 최대 항목 수 적용은
created_at기준 가장 오래된 것부터 제거
빈 결과 빠른 재확인 (일반 히트와 별도)
섹션 제목: “빈 결과 빠른 재확인 (일반 히트와 별도)”일반 캐시 히트:
- TTL 내의 캐시 히트는 캐시된 항목을 반환하며 다른 작업을 수행하지 않습니다.
빈 결과 빠른 재확인:
- 이것은
ScanResult.cache_age_ms를 사용하는 호출자 측 정책입니다 - 필터링/쿼리 결과가 비어있고 캐시된 스캔 나이가 최소
empty_recheck_ms()인 경우, 호출자는force_rescan(...)을 한 번 수행하고 재시도합니다 - 파일이 최근에 추가되었지만 캐시가 아직 TTL 내에 있는 경우 stale-negative 결과를 줄이기 위한 것입니다
현재 소비자:
glob: 필터링된 매치가 비어있고 스캔 나이가 임계값을 초과할 때 재확인fuzzyFind(fd.rs): 쿼리가 비어있지 않고 스코어링된 매치가 비어있을 때만 재확인grep: 선택된 후보 파일 목록이 비어있을 때 재확인
소비자 기본값 및 캐시 사용
섹션 제목: “소비자 기본값 및 캐시 사용”캐시는 모든 노출된 API에서 옵트인입니다 (cache?: boolean, 기본값 false).
네이티브 API의 현재 기본값:
glob:hidden=false,gitignore=true,cache=falsefuzzyFind:hidden=false,gitignore=true,cache=falsegrep:hidden=true,cache=false, 캐시 스캔은 항상use_gitignore=true사용
현재 코딩 에이전트 호출자:
- 대량 멘션 후보 디스커버리는 캐시를 활성화:
packages/coding-agent/src/utils/file-mentions.ts- 프로필:
hidden=true,gitignore=true,includeNodeModules=true,cache=true
- 도구 수준
grep통합은 현재 스캔 캐시를 비활성화 (cache: false):packages/coding-agent/src/tools/grep.ts
무효화 계약
섹션 제목: “무효화 계약”네이티브 무효화 진입점:
invalidateFsScanCache(path?: string)path제공 시: 루트가 대상 경로의 접두사인 캐시 항목 제거path미제공 시: 모든 스캔 캐시 항목 정리
경로 처리 세부사항:
- 상대 무효화 경로는 cwd 기준으로 해석됨
- 무효화 시 정규화를 시도함
- 대상이 존재하지 않는 경우 (예: 삭제), 폴백으로 부모를 정규화하고 가능한 경우 파일명을 재연결함
- 한쪽이 존재하지 않을 수 있는 생성/삭제/이름변경에 대한 무효화 동작을 보존함
코딩 에이전트 변경 흐름 책임
섹션 제목: “코딩 에이전트 변경 흐름 책임”코딩 에이전트 코드는 성공적인 파일시스템 변경 후 반드시 무효화해야 합니다.
중앙 헬퍼:
invalidateFsScanAfterWrite(path)invalidateFsScanAfterDelete(path)invalidateFsScanAfterRename(oldPath, newPath)(경로가 다를 때 양쪽 모두 무효화)
현재 변경 도구 호출 지점:
packages/coding-agent/src/tools/write.tspackages/coding-agent/src/patch/index.ts(hashline/patch/replace 흐름)
규칙: 파일시스템 내용이나 위치를 변경하는 흐름이 이 헬퍼들을 우회하면, 캐시 비활성 버그가 예상됩니다.
새로운 캐시 소비자를 안전하게 추가하기
섹션 제목: “새로운 캐시 소비자를 안전하게 추가하기”새로운 스캐너/검색 경로에 캐시 사용을 도입할 때:
-
안정적인 스캔 정책 입력 사용
- hidden/gitignore 의미론을 먼저 결정
- 캐시 파티션이 의도적이 되도록
get_or_scan/force_rescan에 일관되게 전달
-
캐시 데이터를 탐색 정책에 의해서만 사전 필터링된 것으로 취급
- 도구별 필터링(glob 패턴, 유형 필터, node_modules 규칙)은 조회 후 적용
- 캐시된 항목이 이미 상위 수준 필터를 반영한다고 절대 가정하지 않기
-
stale-negative 위험에 대해서만 빈 결과 빠른 재확인 구현
scan.cache_age_ms >= empty_recheck_ms()사용force_rescan(..., store=true, ...)로 한 번 재시도- 이 경로를 일반 캐시 히트 로직과 분리 유지
-
캐시 미사용 모드를 명시적으로 준수
- 호출자가 캐시를 비활성화하면,
force_rescan(..., store=false, ...)를 호출 - 캐시 미사용 요청 경로에서 공유 캐시를 채우지 않기
- 호출자가 캐시를 비활성화하면,
-
새로운 쓰기 경로에 대한 변경 무효화 연결
- 성공적인 쓰기/편집/삭제/이름변경 후, 코딩 에이전트 무효화 헬퍼를 호출
- 이름변경/이동의 경우, 이전 경로와 새 경로 모두 무효화
-
호출별 TTL 조정 기능을 추가하지 않기
- 현재 계약은 전역 정책만 지원 (환경 변수로 구성), 요청별 TTL 재정의 없음
알려진 경계
섹션 제목: “알려진 경계”- 캐시 범위는 프로세스 로컬 인메모리(
DashMap)이며, 프로세스 재시작 간에 유지되지 않습니다. - 캐시는 스캔 항목을 저장하며, 최종 도구 결과는 저장하지 않습니다.
glob/fuzzyFind/grep은 키 차원(root,hidden,gitignore)이 일치할 때만 스캔 항목을 공유합니다..git은 호출자 옵션에 관계없이 스캔 수집 시점에 항상 제외됩니다.