## 도입: 왜 워커가 멈췄을까?
분산 시스템에서 워커(Worker) 프로세스는 백그라운드 작업을 처리하는 핵심 컴포넌트입니다. 그런데 어느 날 갑자기 워커가 무한 재시작 루프에 빠지고, 408번의 연속 실패가 발생했다면 어떻게 해야 할까요? 이 글에서는 실제 프로덕션 환경에서 겪은 4가지 크리티컬 이슈와 해결 과정을 공유합니다.
---
## 1. CLI 실행 경로 불일치 문제
### 문제 상황
워커가 외부 CLI 도구를 호출할 때 **경로가 잘못 설정**되어 있었습니다.
- **기존 경로**: `C:/nvm4w/nodejs/cli-tool.cmd` (npm 설치 버전)
- **실제 경로**: `C:/Users/PC/.local/bin/cli-tool.exe` (네이티브 바이너리)
- **결과**: 408번 연속 실패, 모든 메시지 처리 중단
### 해결 방법
1. **환경 설정 파일 수정**: `.env` 파일에서 CLI 경로를 실제 바이너리 경로로 업데이트
2. **프로세스 매니저 설정 동기화**: PM2 등의 설정 파일에서도 동일하게 반영
3. **자동 탐지 로직 개선**: 래퍼 스크립트에 Windows 환경에서 `.local/bin` 경로를 우선 탐색하도록 수정
```python
# 래퍼 스크립트 예시
import os
def find_cli_path():
if os.name == 'nt': # Windows
local_bin = os.path.expanduser('~/.local/bin/cli-tool.exe')
if os.path.exists(local_bin):
return local_bin
return 'cli-tool' # fallback
```
**교훈**: CLI 도구를 재설치하거나 버전을 변경할 때는 설정 파일의 절대 경로도 함께 확인해야 합니다.
---
## 2. 중첩 세션 오류
### 문제 상황
`Error: CLI tool cannot be launched inside another session`
PM2로 워커를 실행할 때 부모 프로세스의 환경변수가 자식 프로세스로 그대로 전달되어, CLI 도구가 "이미 실행 중"이라고 오판했습니다.
### 해결 방법
워커가 CLI를 실행하기 전에 **특정 환경변수를 제거**합니다.
```python
import os
import subprocess
def spawn_cli():
worker_env = os.environ.copy()
# 중첩 세션 감지용 환경변수 제거
for key in ['TOOL_SESSION', 'TOOL_ENTRYPOINT']:
worker_env.pop(key, None)
subprocess.run(['cli-tool', 'command'], env=worker_env)
```
**교훈**: `os.environ.copy()`로 환경을 복사할 때는 부모 프로세스의 컨텍스트 정보도 함께 복사됩니다. 자식 프로세스가 독립적으로 실행되어야 한다면 불필요한 플래그는 제거해야 합니다.
---
## 3. 환경변수 중복 관리 (DRY 원칙 위반)
### 문제 상황
7개의 설정값이 `.env`와 `ecosystem.config.js` 두 곳에 중복 선언되어 있었고, 심지어 `.env` 내부에서도 같은 변수가 두 번 선언되어 충돌했습니다.
```env
# .env 파일
KAFKA_SERVERS=localhost:9092
SESSION_CHECK=true # 51번 줄
...
SESSION_CHECK=false # 55번 줄 - 충돌!
```
### 해결 방법
**단일 진실 공급원(Single Source of Truth)** 원칙 적용:
- `.env`: 애플리케이션 설정 (DB URL, 카프카 주소, 워커 설정 등)
- `ecosystem.config.js`: PM2/OS 전용 설정 (시스템 경로, 버퍼 모드 등)
```javascript
// ecosystem.config.js - PM2 전용 설정만
module.exports = {
apps: [{
env: {
PYTHONUNBUFFERED: '1', // PM2 전용
PATH: process.env.PATH + ';C:\\Tools' // OS 경로
// 애플리케이션 설정은 .env에서 load_dotenv()로 로드
}
}]
};
```
**교훈**: 설정 관리는 계층별로 명확히 분리해야 합니다. 중복은 버그의 온상입니다.
---
## 4. 하드코딩된 슬롯 설정
### 문제 상황
워커의 동시 처리 슬롯 중 일부는 하드코딩되어 있고, 일부는 환경변수로 관리되어 일관성이 없었습니다.
```python
URGENT_SLOTS = 1 # 하드코딩
NORMAL_SLOTS = int(os.getenv("NORMAL_SLOTS", "2")) # 환경변수
```
### 해결 방법
모든 슬롯 설정을 환경변수로 통일:
```python
URGENT_SLOTS = int(os.getenv("URGENT_SLOTS", "1"))
NORMAL_SLOTS = int(os.getenv("NORMAL_SLOTS", "2"))
BATCH_SIZE = int(os.getenv("BATCH_SIZE", "3"))
```
`.env`에서 중앙 관리:
```env
URGENT_SLOTS=1
NORMAL_SLOTS=2
BATCH_SIZE=3
```
**교훈**: 운영 중 조정이 필요한 값은 처음부터 설정 파일로 외부화해야 합니다.
---
## 검증 결과
모든 수정 후:
- 워커 상태: 정상 가동, 재시작 0회
- 메시지 처리: 1시간당 6건 정상 처리, 대기 0건
- 오류: CLI 경로 오류 0건, 중첩 세션 오류 0건
---
## 결론: 안정적인 워커 운영을 위한 체크리스트
1. **경로 검증**: 외부 도구 경로는 절대 경로로 명시하고, 변경 시 모든 설정 파일 동기화
2. **환경 격리**: 자식 프로세스 실행 시 불필요한 부모 컨텍스트 제거
3. **설정 단일화**: 중복 선언 금지, 계층별 책임 분리 (앱 설정 vs 시스템 설정)
4. **외부화 원칙**: 하드코딩 최소화, 운영 파라미터는 환경변수로 관리
다음 글에서는 워커 모니터링과 자동 복구 메커니즘에 대해 다뤄보겠습니다.