/인프라/fork: retry: Resource temporarily unavailable 30초 진단 런북
인프라Resource temporarily unavailablenproc 한도

fork: retry: Resource temporarily unavailable 30초 진단 런북

fork: retry, pthread_create failed, unable to create new native thread는 nproc 한도 고갈 아니면 메모리 부족입니다. ulimit·ps·free·dmesg로 30초 진단하고 limits.conf·TasksMax·cgroup pids.max로 영구 해결하는 복붙 런북.

fork: retry: Resource temporarily unavailable 30초 진단 런북

fork: retry: Resource temporarily unavailable 30초 진단 런북

지금 터미널이나 로그에서 아래 셋 중 하나를 보고 복붙 검색으로 들어왔다면, 결론부터 말합니다. 거의 항상 둘 중 하나입니다 — nproc(프로세스/스레드 생성) 한도 고갈, 아니면 메모리 부족.

  • bash: fork: retry: Resource temporarily unavailable (셸/스크립트)
  • pthread_create failed (EAGAIN) (C/네이티브)
  • java.lang.OutOfMemoryError: unable to create new native thread (JVM)

이름과 표현은 다르지만 셋 다 커널이 새 실행 흐름을 만들려다 EAGAIN(=Resource temporarily unavailable)을 돌려준 결과입니다. 즉 메시지가 달라도 뿌리는 동일합니다. 아래 순서대로 명령만 던지면 30초 안에 "한도냐 메모리냐"가 갈립니다.

단계명령판정
1ulimit -u vs ps -eLf 카운트한도≈사용량 → nproc 고갈
2free -h / dmesg | grep oom메모리 빠듯·OOM → 메모리 부족
3systemctl show / pids.max영구 상향 위치 결정

30초 1차 분기: nproc 한도부터 때려본다 🟢

설명은 나중에. 일단 진단부터 (전부 읽기 전용, 안전):

Bash
# 🟢 현재 셸의 soft / hard nproc 한도
ulimit -u
ulimit -Hu

# 🟢 특정 유저가 지금 쓰고 있는 "스레드(LWP) 총합"
ps -eLf | grep '^appuser ' | wc -l
#   또는 전체 LWP 수
ps -eLf --no-headers | wc -l

# 🟢 문제 프로세스 1개의 스레드 수
ps -o nlwp= -p <pid>
cat /proc/<pid>/status | grep Threads

# 🟢 시스템 전역 천장 (개별 유저 한도와 별개)
cat /proc/sys/kernel/threads-max
cat /proc/sys/kernel/pid_max

판정 기준은 단순합니다. ulimit -u로 나온 값과 ps -eLf | grep <user> | wc -l 값이 거의 같으면(예: 한도 4096, 사용 4090) → nproc 한도 고갈 확정입니다. 여기서 핵심은 리눅스의 RLIMIT_NPROC이 "프로세스 수"가 아니라 사실상 유저별 스레드(LWP) 총합을 센다는 점입니다. 그래서 멀티스레드 런타임(JVM, Go, Node 워커)이 스레드를 폭증시키면 프로세스 개수는 몇 개 안 돼도 한도에 부딪힙니다.

2차 분기: 한도는 넉넉한데 또 터진다 → 메모리 부족 🟡

ulimit -u는 만 단위인데도 fork가 실패한다? 그럼 두 번째 용의자는 메모리입니다.

Bash
# 🟡 가용 메모리 / 스왑 — available가 바닥이면 의심
free -h

# 🟡 OOM Killer가 다녀갔는지 흔적 확인
dmesg -T | grep -i -E 'oom|out of memory|killed process'

# 🟡 스택 크기 확인 (스레드당 가상메모리 소비량)
ulimit -s
cat /proc/sys/vm/max_map_count
cat /proc/sys/vm/overcommit_memory

pthread_create는 스레드마다 스택(기본 ulimit -s, 보통 8MB)을 가상메모리로 예약합니다. 8MB × 수천 스레드 = 수십 GB 가상메모리 요구가 overcommit 정책이나 max_map_count에 막혀 EAGAIN이 나는 것이죠. Java라면 스레드 스택은 -Xss로 잡히고, 이 메모리는 힙(-Xmx) 바깥의 네이티브 영역에서 나옵니다. 그래서 unable to create new native thread는 힙이 멀쩡해도 터집니다 — -Xmx를 키우면 오히려 네이티브 여유가 줄어 악화될 수 있습니다.

실무 팁(경험담): JVM에서 이 에러가 나면 반사적으로 -Xmx부터 올리는 분이 많은데, 십중팔구 역효과입니다. 먼저 스레드 수(jstack <pid> | grep -c 'java.lang.Thread.State')를 세서 커넥션 풀·스레드 풀이 새는지 확인하고, 그래도 정상 부하면 -Xss를 512k로 낮춰 스레드당 메모리부터 줄이는 게 빠른 응급 처치였습니다.

라벨링: nproc 멀쩡 + 메모리 빠듯/OOM 흔적 = 네이티브 메모리·스택 고갈.

3종 에러 = 1원인 매핑

보이는 메시지발생 위치실제 원인
fork: retry: Resource temporarily unavailablebash/셸RLIMIT_NPROC 또는 메모리로 fork() EAGAIN
pthread_create failed (EAGAIN)C/네이티브nproc 한도 또는 스택×스레드 가상메모리
OutOfMemoryError: unable to create new native threadJVM위 두 가지가 JVM에서 표출된 것

영구 해결: limits.conf vs systemd TasksMax 함정 🔴

원인을 잡았으면 한도를 올립니다. 단, 어디서 떴느냐에 따라 고칠 파일이 완전히 다릅니다. 적용 전 영역을 반드시 확인하세요.

(a) 가장 흔한 함정: limits.conf가 systemd 서비스엔 안 먹힌다

Bash
# /etc/security/limits.conf 또는 limits.d/*.conf  🔴 (재로그인 필요)
appuser  soft  nproc  65536
appuser  hard  nproc  65536

이건 PAM 로그인 세션(SSH, su 등)에만 적용됩니다. systemctl start로 뜬 서비스는 PAM을 거치지 않으므로 이 설정이 무시됩니다. "limits.conf 다 고쳤는데 왜 안 되지?"의 99%가 이 케이스입니다.

Bash
# 🟢 서비스에 실제로 적용된 값 확인 — 진실은 여기에
systemctl show myapp.service -p TasksMax -p LimitNPROC

DefaultTasksMax는 보통 시스템 nproc의 15%로 잡혀 있어, RHEL 8/9·Ubuntu 22.04+에서 스레드 천장으로 자주 걸립니다.

INI
# 🔴 /etc/systemd/system/myapp.service.d/override.conf  (drop-in 권장)
[Service]
TasksMax=infinity
LimitNPROC=65536
Bash
# 🔴 적용 (daemon-reload + 재시작 필수, 롤백: drop-in 파일 삭제 후 동일 절차)
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p TasksMax -p LimitNPROC  # 검증

(b) 컨테이너 차이 박스 📦

호스트 ulimit -u는 충분한데 파드/컨테이너 안에서만 fork 실패한다면? 범인은 ulimit이 아니라 **cgroup pids.max**입니다.

Bash
# cgroup v2 (최신 배포판 기본)  🟢
cat /sys/fs/cgroup/<슬라이스경로>/pids.max
# cgroup v1  🟢
cat /sys/fs/cgroup/pids/<경로>/pids.max
  • Docker: docker run --pids-limit=4096 ... (기본은 무제한이지만 cgroup 천장에 걸림)
  • Kubernetes: kubelet --pod-max-pids 또는 노드 설정으로 파드별 PID 제한

결론: 분기표 한 장으로 끝내기

CODE
fork/pthread/native thread 에러
        │
ulimit -u ≈ ps -eLf 사용량?
   ├─ YES → nproc 한도 고갈 → limits.conf(로그인) / TasksMax(systemd) / pids.max(컨테이너) 상향
   └─ NO  → free·dmesg에 OOM?
              ├─ YES → 메모리 부족 → 스레드 누수 점검 / -Xss·ulimit -s 축소 / 메모리 증설
              └─ NO  → 전역 천장(threads-max, pid_max) 확인

이 에러는 자원고갈 클러스터의 한 형제일 뿐입니다. 고갈된 자원만 다르고 진단 방식은 똑같습니다 — Too many open files(파일 디스크립터/EMFILE), No space left on device(inode·디스크), OutOfMemoryError: Java heap space(힙), connection refused(백로그·포트). "무엇이 고갈됐나"만 바꿔 같은 런북을 돌리면 됩니다.

자주 묻는 질문 (FAQ)

Q. limits.conf에서 nproc을 올렸는데 서비스가 계속 fork 실패합니다. 왜인가요? A. systemd로 뜬 서비스는 PAM을 거치지 않아 limits.conf가 적용되지 않습니다. systemctl show <svc> -p TasksMax -p LimitNPROC로 실제값을 확인하고, 유닛 drop-in에 TasksMax=LimitNPROC=를 넣은 뒤 daemon-reload+restart 하세요.

Q. 호스트 ulimit은 충분한데 컨테이너 안에서만 터집니다. A. 컨테이너의 진짜 천장은 호스트 ulimit이 아니라 cgroup pids.max입니다. /sys/fs/cgroup/.../pids.max를 확인하고 Docker는 --pids-limit, 쿠버네티스는 kubelet의 PID 제한을 조정하세요.

Q. Java에서 -Xmx를 올렸더니 native thread 에러가 더 자주 납니다. A. 정상입니다. 스레드 스택은 힙 바깥 네이티브 메모리에서 나오므로 힙을 키우면 네이티브 여유가 줄어듭니다. 스레드 수가 비정상이면 누수를 잡고, 정상 부하면 -Xss를 낮춰 스레드당 메모리를 줄이세요.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·
관련 공식 문서GNU/Linux man 페이지

댓글

첫 번째 댓글을 남겨보세요.