ingest: sweep_deleted_files 구간이 진행바·로그에 표시되지 않아 hang 으로 오인된다 #228

Closed
opened 2026-08-05 02:12:15 +00:00 by altair823 · 1 comment
Owner

증상

대량 삭제가 발생한 워크스페이스에서 kebab ingest 를 돌리면 진행바가 0/N 에 고정된 채 수십 분 ~ 수십 시간 동안 아무 출력이 없다. 사용자는 hang 으로 판단하고 강제 종료한다 (실제 도그푸딩에서 3회 연속 Ctrl-C).

❯ kebab ingest
임베딩 백엔드: ollama · 모델 snowflake-arctic-embed2 (1024-dim)
ingest [                              ] 0/12115     ← 32시간 동안 이 상태

원인

ingest_with_config_opts 는 walker 가 끝난 직후 sweep_deleted_files 를 호출한다 (crates/kebab-app/src/lib.rs:419). 이 루프(crates/kebab-app/src/lib.rs:2038-2125)는

  • 진행바를 갱신하지 않는다 — 진행바는 walker 총계(0/12115)만 표시한 채 정지해 보인다.
  • ingest ndjson 로그에 아무 이벤트도 쓰지 않는다 — 로그 파일이 0바이트로 남는다. tracing::info! 은 나가지만 ndjson wire 이벤트가 아니므로 사용자가 볼 수 있는 산출물이 없다.

즉 관측 가능한 신호가 완전히 0인 구간이 존재한다. 프로세스는 CPU 100% 로 정상 동작 중인데 외부에서는 hang 과 구별할 수 없다.

실측 (도그푸딩, v0.28.0)

vault 에서 jira/ 하위 5,833개 문서가 재크롤로 사라진 상태:

조건 purge 속도 sweep 총 소요
Lance 벡터 스토어 존재 (fragment 11,892개) 0.05건/초 약 32시간
reset --vector-only 후 (Lance 비어 있음) 6.0건/초 약 16분

두 경우 모두 진행바는 0/12115, 로그는 0바이트.

스택 샘플(sample <pid>) — 이 구간에서 메인 스레드가 100% 여기에 있음을 확인:

kebab_app::ingest_with_config_opts
  → kebab_app::sweep_deleted_files
    → kebab_store_sqlite::purge_deleted_workspace_path → sqlite3_step → fts5NextMethod → pread

제안

  1. sweep 을 진행바의 별도 phase 로 노출. purge 후보 총계는 루프 진입 전에 이미 알 수 있다 (all_workspace_paths() 결과 길이 − scanned_paths 교집합), 따라서 sweep [x/y] 형태의 확정 분모를 낼 수 있다.
  2. ndjson 로그에 sweep 이벤트 추가. 최소한 phase 시작/종료 + purge 건수, 이상적으로는 {"kind":"purge","ts":...,"doc_path":...} 건별 이벤트. 지금은 0바이트 파일이라 사후 진단 근거가 전무하다.
  3. --summary-only 와의 상호작용은 기존 items 목록 규칙을 따르면 된다.

참고

같은 루프 구조가 reset --orphans-only (crates/kebab-app/src/reset.rs:execute_orphans_only) 에도 있어 동일하게 무표시로 장시간 돈다.

## 증상 대량 삭제가 발생한 워크스페이스에서 `kebab ingest` 를 돌리면 진행바가 `0/N` 에 고정된 채 수십 분 ~ 수십 시간 동안 아무 출력이 없다. 사용자는 hang 으로 판단하고 강제 종료한다 (실제 도그푸딩에서 3회 연속 Ctrl-C). ``` ❯ kebab ingest 임베딩 백엔드: ollama · 모델 snowflake-arctic-embed2 (1024-dim) ingest [ ] 0/12115 ← 32시간 동안 이 상태 ``` ## 원인 `ingest_with_config_opts` 는 walker 가 끝난 직후 `sweep_deleted_files` 를 호출한다 (`crates/kebab-app/src/lib.rs:419`). 이 루프(`crates/kebab-app/src/lib.rs:2038-2125`)는 - 진행바를 갱신하지 않는다 — 진행바는 walker 총계(`0/12115`)만 표시한 채 정지해 보인다. - ingest ndjson 로그에 아무 이벤트도 쓰지 않는다 — 로그 파일이 **0바이트**로 남는다. `tracing::info!` 은 나가지만 ndjson wire 이벤트가 아니므로 사용자가 볼 수 있는 산출물이 없다. 즉 관측 가능한 신호가 완전히 0인 구간이 존재한다. 프로세스는 CPU 100% 로 정상 동작 중인데 외부에서는 hang 과 구별할 수 없다. ## 실측 (도그푸딩, v0.28.0) vault 에서 `jira/` 하위 5,833개 문서가 재크롤로 사라진 상태: | 조건 | purge 속도 | sweep 총 소요 | |---|---|---| | Lance 벡터 스토어 존재 (fragment 11,892개) | 0.05건/초 | **약 32시간** | | `reset --vector-only` 후 (Lance 비어 있음) | 6.0건/초 | 약 16분 | 두 경우 모두 진행바는 `0/12115`, 로그는 0바이트. 스택 샘플(`sample <pid>`) — 이 구간에서 메인 스레드가 100% 여기에 있음을 확인: ``` kebab_app::ingest_with_config_opts → kebab_app::sweep_deleted_files → kebab_store_sqlite::purge_deleted_workspace_path → sqlite3_step → fts5NextMethod → pread ``` ## 제안 1. sweep 을 진행바의 별도 phase 로 노출. purge 후보 총계는 루프 진입 전에 이미 알 수 있다 (`all_workspace_paths()` 결과 길이 − `scanned_paths` 교집합), 따라서 `sweep [x/y]` 형태의 확정 분모를 낼 수 있다. 2. ndjson 로그에 sweep 이벤트 추가. 최소한 phase 시작/종료 + purge 건수, 이상적으로는 `{"kind":"purge","ts":...,"doc_path":...}` 건별 이벤트. 지금은 0바이트 파일이라 사후 진단 근거가 전무하다. 3. `--summary-only` 와의 상호작용은 기존 items 목록 규칙을 따르면 된다. ## 참고 같은 루프 구조가 `reset --orphans-only` (`crates/kebab-app/src/reset.rs:execute_orphans_only`) 에도 있어 동일하게 무표시로 장시간 돈다.
Author
Owner

PR #236 으로 닫는다.

이슈가 제안한 세 가지를 모두 반영했다. 진행 이벤트로 sweep_started / sweep_progress / sweep_completed 를, ndjson 로그로 purge / purge_failed / sweep_summary 를 추가했다. 후보 총계는 all_workspace_paths() 에서 이번 스캔이 덮은 경로를 뺀 값이라 루프 진입 전에 확정 분모가 나온다.

검사한 후보마다 이벤트를 내보낸다. 지울 때만 내보내면, 수천 건을 훑고 하나도 안 지우는 sweep 에서 다시 멈춘 것처럼 보이기 때문이다. 그래서 sweep_progressremoved 로 '정말 없어서 지웠다' 와 '아직 디스크에 있어 그대로 뒀다' 를 가른다. purged 를 두 이벤트에서 다른 타입으로 쓰지 않으려고 이름을 나눴다 — sweep_completed 쪽이 정수 purged 다.

리뷰에서 세 가지가 더 나와 같이 처리했다.

첫째, sweep 이 진행바를 빌려 쓰면서 라벨과 분모를 바꿔 놓고 돌려주지 않아, sweep 이 한 번 돌면 그 뒤 색인 구간 전체가 sweep [====] 4213/21 로 그려졌다. 스타일 교체가 v0.26.1 의 {asset_elapsed} 하트비트까지 날려서, 이 이슈가 sweep 구간에서 없앤 '멈춘 것처럼 보임' 을 asset 구간에 새로 만드는 셈이었다. 비-TTY 실측만 보고 있어서 놓쳤다.

둘째, sweep 이 취소 플래그를 아예 보지 않았다. CLI 의 첫 Ctrl-C 는 '현재 파일까지만 하고 중단' 을 찍는데 sweep 중에는 그 안내가 사실이 아니었고, 남은 수단인 두 번째 Ctrl-C 는 exit(130) 이라 버퍼에 쌓인 벡터 삭제가 고아로 남는다. 이 이슈 자체가 'sweep 중 Ctrl-C 를 세 번 눌러 죽였다' 는 보고이므로 눌렀을 때의 동작도 같이 고쳤다. 덧붙여 스캔이 0건이면 asset 루프 본문이 안 돌아 취소가 기록되지 않던 것도 고쳤다 — 하필 그게 sweep 이 가장 커지는 경우다.

셋째, cargo clippy --workspace --all-targets -- -D warnings 가 main 에서 실패하고 있었다. 툴체인이 올라가면서 새 lint 둘이 기존 코드에 걸린 것이고 각각 한 줄이다. 그 그늘에 PR #235 에서 들어온 위반이 숨어 있었으므로 셋 다 고쳤고, 지금은 워크스페이스 전체가 통과한다.

이슈가 참고로 적은 reset --orphans-only 는 그대로 뒀다. reset 에는 진행 채널 자체가 없어 배선을 새로 깔아야 하는데, #229#230 이 머지된 지금 이 경로의 문서당 비용이 약 800배 떨어져 '몇 시간 무표시' 상황이 애초에 안 나온다. 필요해지면 별 건으로 다룬다.

PR #236 으로 닫는다. 이슈가 제안한 세 가지를 모두 반영했다. 진행 이벤트로 `sweep_started` / `sweep_progress` / `sweep_completed` 를, ndjson 로그로 `purge` / `purge_failed` / `sweep_summary` 를 추가했다. 후보 총계는 `all_workspace_paths()` 에서 이번 스캔이 덮은 경로를 뺀 값이라 루프 진입 전에 확정 분모가 나온다. 검사한 후보마다 이벤트를 내보낸다. 지울 때만 내보내면, 수천 건을 훑고 하나도 안 지우는 sweep 에서 다시 멈춘 것처럼 보이기 때문이다. 그래서 `sweep_progress` 는 `removed` 로 '정말 없어서 지웠다' 와 '아직 디스크에 있어 그대로 뒀다' 를 가른다. `purged` 를 두 이벤트에서 다른 타입으로 쓰지 않으려고 이름을 나눴다 — `sweep_completed` 쪽이 정수 `purged` 다. 리뷰에서 세 가지가 더 나와 같이 처리했다. 첫째, sweep 이 진행바를 빌려 쓰면서 라벨과 분모를 바꿔 놓고 돌려주지 않아, sweep 이 한 번 돌면 그 뒤 색인 구간 전체가 `sweep [====] 4213/21` 로 그려졌다. 스타일 교체가 v0.26.1 의 `{asset_elapsed}` 하트비트까지 날려서, 이 이슈가 sweep 구간에서 없앤 '멈춘 것처럼 보임' 을 asset 구간에 새로 만드는 셈이었다. 비-TTY 실측만 보고 있어서 놓쳤다. 둘째, sweep 이 취소 플래그를 아예 보지 않았다. CLI 의 첫 Ctrl-C 는 '현재 파일까지만 하고 중단' 을 찍는데 sweep 중에는 그 안내가 사실이 아니었고, 남은 수단인 두 번째 Ctrl-C 는 `exit(130)` 이라 버퍼에 쌓인 벡터 삭제가 고아로 남는다. 이 이슈 자체가 'sweep 중 Ctrl-C 를 세 번 눌러 죽였다' 는 보고이므로 눌렀을 때의 동작도 같이 고쳤다. 덧붙여 스캔이 0건이면 asset 루프 본문이 안 돌아 취소가 기록되지 않던 것도 고쳤다 — 하필 그게 sweep 이 가장 커지는 경우다. 셋째, `cargo clippy --workspace --all-targets -- -D warnings` 가 main 에서 실패하고 있었다. 툴체인이 올라가면서 새 lint 둘이 기존 코드에 걸린 것이고 각각 한 줄이다. 그 그늘에 PR #235 에서 들어온 위반이 숨어 있었으므로 셋 다 고쳤고, 지금은 워크스페이스 전체가 통과한다. 이슈가 참고로 적은 `reset --orphans-only` 는 그대로 뒀다. reset 에는 진행 채널 자체가 없어 배선을 새로 깔아야 하는데, #229 와 #230 이 머지된 지금 이 경로의 문서당 비용이 약 800배 떨어져 '몇 시간 무표시' 상황이 애초에 안 나온다. 필요해지면 별 건으로 다룬다.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#228