Watch at Ease세상을 읽고, 일상의 답을 찾다.

기술 · 실용 가이드

Linux 파일 감시 한도 ENOSPC인가요? sysctl을 올리기 전에 inotify 소비자 원장을 만드세요

같은 ENOSPC도 감시 마크, inotify 인스턴스, 큐 또는 같은 사용자의 다른 프로세스가 원인일 수 있습니다. 먼저 읽기 전용으로 조사하세요.

여러 프로세스 소비자, 읽기 전용 감사 원장과 공유 inotify 풀을 보여 주는 오리지널 개념 일러스트.
Watch at Ease 오리지널 개념 일러스트: 인스턴스, 마크와 가시성을 원장에 기록한 뒤 다음 단계를 정합니다. Watch at Ease

Linux 개발 도구의 `ENOSPC: System limit for number of file watchers reached`는 보통 디스크가 아니라 inotify 자원 고갈을 뜻합니다. 오류 경로는 다음 등록이 실패한 위치일 뿐, 그 프로세스가 가장 많이 사용했다는 증거가 아닙니다.

한도는 실제 사용자 ID별로 부과되므로 편집기, 언어 서버, 개발 서버, 테스트 실행기, 동기화 클라이언트와 컨테이너 프로세스가 같은 풀을 공유합니다. `max_user_watches`가 높아도 `max_user_instances`가 고갈될 수 있고 큐 오버플로는 또 다른 실패입니다.

sysctl을 올리거나 재부팅하거나 프로세스를 일괄 종료하기 전에 `/proc`에서 읽기 전용 소비자 원장을 만드세요. 한도, 인스턴스, 마크, 프로세스, 권한 누락과 네임스페이스를 비교한 뒤 한 가지 되돌릴 수 있는 변경만 재측정합니다.

inotify 계열 ENOSPC인지 확인합니다

정확한 오류, 명령, 운영체제와 경로를 기록합니다. `fs.watch`, Chokidar, Watchpack, 편집기 등 Linux 파일 감시 등록에서 실패했다면 inotify 고갈 가능성이 큽니다. 파일 쓰기 실패라면 `df -h`와 `df -i`도 확인합니다.

`id -ru`로 실제 사용자 ID를 기록하고 `max_user_watches`, `max_user_instances`, `max_queued_events`를 읽습니다. 앞의 둘은 등록 실패를 일으키며 큐 오버플로는 이벤트 손실과 `IN_Q_OVERFLOW`를 일으킵니다.

인스턴스와 감시 마크를 따로 셉니다

inotify 인스턴스는 `anon_inode:inotify` 디스크립터지만 하나에 많은 마크가 들어갈 수 있습니다. 디스크립터 수는 인스턴스 수만 보여 주며 `max_user_watches`까지 남은 양은 보여 주지 않습니다.

읽을 수 있는 각 inotify `fdinfo`에서 `inotify`로 시작하는 행을 세고 PID와 실제 UID별로 합산합니다. 디스크립터 수는 별도의 `instances` 열로 보존합니다.

권한 누락을 표현하는 소비자 원장을 만듭니다

검사 가능한 각 PID의 실제 UID, 프로세스 이름, 인스턴스 수와 마크 수를 기록합니다. `/proc/<pid>/cmdline`에서 읽은 텍스트를 실행하지 마세요. 스캔 중 종료된 프로세스는 경쟁 상태로 기록하고 0으로 만들지 않습니다.

권한 거부 또는 읽을 수 없는 `fdinfo`도 셉니다. 자신의 프로세스만 보여도 사용자별 풀은 진단할 수 있지만 시스템 전체에 다른 소비자가 없다는 증거는 아닙니다. hidepid, 컨테이너, 네임스페이스 경계에서는 대상 프로세스가 속한 네임스페이스에서 다시 실행합니다.

테스트된 읽기 전용 원장을 실행합니다

다음 스크립트를 `inotify-ledger.sh`로 저장하고 검토한 뒤 `bash inotify-ledger.sh`를 실행하세요. procfs와 sysctl 파일만 읽고 sysctl 쓰기, 프로세스 종료, 서비스 재시작, 파일 삭제, 개인 경로 해석은 하지 않습니다.

```bash #!/usr/bin/env bash set -u printf 'setting\tvalue\n' for name in max_user_watches max_user_instances max_queued_events; do path="/proc/sys/fs/inotify/${name}" if [[ -r "$path" ]]; then IFS= read -r value < "$path" printf '%s\t%s\n' "$name" "$value" else printf '%s\tUNREADABLE\n' "$name" fi done printf '\npid\truid\tcomm\tinstances\twatches\tunreadable_fdinfo\n' for proc in /proc/[0-9]*; do pid=${proc##*/} [[ -r "$proc/status" ]] || continue ruid=$(awk '/^Uid:/{print $2; exit}' "$proc/status" 2>/dev/null || true) [[ -n "$ruid" ]] || continue comm=$(tr '\t\n' ' ' < "$proc/comm" 2>/dev/null || true) instances=0 watches=0 unreadable=0 shopt -s nullglob for fdinfo in "$proc"/fdinfo/*; do fd=${fdinfo##*/} target=$(readlink "$proc/fd/$fd" 2>/dev/null || true) [[ "$target" == "anon_inode:inotify" ]] || continue ((instances+=1)) if [[ -r "$fdinfo" ]]; then count=$(awk '/^inotify /{n++} END{print n+0}' "$fdinfo" 2>/dev/null || printf '0') ((watches+=count)) else ((unreadable+=1)) fi done if ((instances > 0 || watches > 0 || unreadable > 0)); then printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$pid" "$ruid" "$comm" "$instances" "$watches" "$unreadable" fi done | sort -t $'\t' -k5,5nr -k4,4nr printf '\nnamespace\ttarget\n' for ns in pid user mnt; do printf 'self_%s\t%s\n' "$ns" "$(readlink "/proc/self/ns/$ns" 2>/dev/null || printf 'UNREADABLE')" printf 'init_%s\t%s\n' "$ns" "$(readlink "/proc/1/ns/$ns" 2>/dev/null || printf 'UNREADABLE')" done ```

격리 테스트에서는 Node.js 프로세스 하나가 서로 다른 임시 디렉터리 세 개를 감시했고 원장은 인스턴스 1개와 마크 3개를 정확히 보고했습니다. PID 1 네임스페이스가 보이지 않을 때는 `UNREADABLE`을 보존해 사각지대를 0으로 꾸미지 않았습니다.

오류 경로와 최대 소비자를 혼동하지 마세요

예외 경로는 한도에 걸린 등록 위치입니다. 최대 소비자는 다른 작업 공간, 확장 호스트, 언어 서버, 동기화 클라이언트 또는 장기 실행 개발 프로세스일 수 있습니다. 공개 이슈에서는 확장이 수만 개 마크를 쓰는 동안 별도의 serve 프로세스가 먼저 실패했습니다.

원장을 마크 수로 정렬하고 확인된 비필수 소비자 하나를 정상 종료한 뒤 다시 실행합니다. 큰 감소가 인과관계를 만듭니다. 재부팅은 풀을 비울 수 있지만 귀속 증거를 지우고 넓은 감시 범위의 재발 여부를 알려 주지 않습니다.

한도를 바꾸기 전에 감시 범위를 감사합니다

최대 소비자의 감시 루트와 제외 규칙을 확인합니다. `.venv`, 빌드 출력, 캐시와 의존성 디렉터리는 지속 감시 가치가 낮을 수 있습니다. VS Code 공식 문서는 먼저 `files.watcherExclude`로 큰 불필요 폴더를 제외하라고 안내합니다.

일반적인 `node_modules` 제외가 모든 감시자에 적용된다고 가정하지 마세요. 편집기, 확장, 언어 서버, 테스트 실행기와 프레임워크 서버는 독립적으로 구독합니다. 마크 소유 구성 요소의 범위를 바꾸고 원장 감소와 필요한 이벤트 유지 여부를 확인합니다.

원장이 필요성을 증명한 한도만 올립니다

정당한 범위가 `max_user_watches`보다 크다면 메모리 여유를 반영한 값을 배포판의 sysctl 위치에 적용합니다. 인스턴스 고갈이라면 마크 한도만 바꿔도 소용없습니다. 이전 값, 설정 파일과 애플리케이션 버전을 보존하세요.

모든 호스트에 같은 숫자를 붙여 넣지 마세요. VS Code는 524,288을 가능한 값으로 소개하면서 최악의 메모리 비용도 경고하지만 컨테이너, 소형 VM, 다중 사용자 시스템의 예산은 다릅니다. 적용 후 실제 값, 원장과 메모리를 다시 비교합니다.

무작위 수정 대신 분기 행렬을 사용합니다

한 PID가 지배적이면 그 소비자의 범위를 줄이거나 업데이트합니다. 많은 정상 소비자가 합쳐 한도를 넘으면 `max_user_watches`를 보수적으로 올립니다. 인스턴스 수가 `max_user_instances`에 가까우면 객체 과다나 누수를 찾고, 원장이 불완전하면 권한이나 네임스페이스 가시성을 먼저 해결합니다.

둘 다 충분히 낮다면 최소 작업 공간에서 재현하고 애플리케이션의 감시 로그를 확인합니다. 에스컬레이션에는 전후 원장, 정확한 한도, 커널 및 앱 버전, 최소 재현을 포함하고 비밀이나 개인 경로 목록은 포함하지 않습니다.

단일 대형 소비자, 다수 소비자와 가시성 사각지대로 갈라지는 오리지널 개념 일러스트.
Watch at Ease 오리지널 개념 일러스트: 각 증거 분기는 소유자와 경계만 제시하며 자동 변경을 허가하지 않습니다. Watch at Ease