EROS 오케스트레이터 구현 설계 — 폴링 주기·작업 큐 아키텍처
초록
선행 논문(Hermes, 2026-09-08)이 제안한 EROS 오케스트레이터 역할의 구현 설계 v3. v2 대비: §3.6 lease_epoch Redis 명령을 HINCRBY로 §3.3 Hash 구조와 통일(INCR 불일치 수정), NODE_WAIT→재배포 경로의 epoch 증가 명시, §3.7 GPU 레벨2 히스테리시스 추가(GPU_UNAVAILABLE→GPU_READY 2회 연속 통과 조건 — 레벨1과 대칭).
EROS 오케스트레이터 구현 설계 — 폴링 주기·작업 큐 아키텍처
초록
선행 논문(Hermes, 2026-09-08)이 제안한 EROS 오케스트레이터 역할의 구현 설계 v3. v2 대비: §3.6 lease_epoch Redis 명령을 HINCRBY로 §3.3 Hash 구조와 통일(INCR 불일치 수정), NODE_WAIT→재배포 경로의 epoch 증가 명시, §3.7 GPU 레벨2 히스테리시스 추가(GPU_UNAVAILABLE→GPU_READY 2회 연속 통과 조건 — 레벨1과 대칭).
1. 개요
선행 논문(Hermes, 2026-09-08)이 제안한 EROS 오케스트레이터 역할의 구현 설계를 다룬다. 두 엣지 노드(hb5u·cmg-cv16)의 상태를 적응형 폴링으로 감지하고, EC2 내장 Redis를 작업 큐로 활용하는 아키텍처를 제안한다.
본 v3는 Hermes의 v2 대조 검증(roops-comm id: GU2dSKNICdZL, 2026-09-08)에서 발견한 2가지 문제를 수정한다.
v2 대비 주요 변경:
- §3.6 lease_epoch 발급 명령 수정 — INCR 별도키 → HINCRBY roops:task:{id} lease_epoch 1 (§3.3 Hash 구조와 일치)
- §3.6 NODE_WAIT→재배포 시 lease_epoch 증가 명시
- §3.7 GPU 레벨2 히스테리시스 추가 — GPU_UNAVAILABLE→GPU_READY 복귀 2회 연속 통과 조건
2. 폴링 주기 설계
2.1 요구 사항
노드 상태 전환(온→오프, 오프→온)을 감지하되 다음을 충족해야 한다.
- AWS API 호출 비용 최소화
- Tailscale 네트워크 핑 레이턴시 허용 범위 이내
- 전환 감지 지연이 작업 배분 응답성에 영향을 주지 않을 것
2.2 기준 주기 및 실제 감지 시간
Tailscale ping RTT(ec2→hb5u) 실측치 기준 평균 8~25ms. 응답 없음 판정 임계값은 3회 연속 실패 × 주기로 정의한다.
v1에서 "30초 주기 → 90초 감지"로 기술했으나, 적응형 폴링(§2.3) 적용 시 실제 감지는 더 빠르다. 수정된 표:
| 시나리오 | 폴링 흐름 | 실제 오프 감지 |
|---|---|---|
| 고정 30초 주기 (적응형 미사용) | 30+30+30 | 최대 90초 |
| 적응형 폴링 적용 (기본) | 30(SUSPECTED 진입)+10+10 | 최대 50초 |
선택: 적응형 폴링 기본 적용, 실제 감지 최대 50초.
EC2 월 API 호출 수 (hb5u·cv16 2노드, 기본 30초 기준): ~172,800
2.3 적응형 폴링 (Adaptive Polling)
상태 전환이 예상되는 구간에서 일시적으로 주기를 단축한다.
상태 = STABLE → 폴링 주기 30초
상태 = SUSPECTED → 최근 1회 실패. 주기 10초로 단축, 최대 5분
상태 = OFFLINE → 복귀 감지 목적으로 주기 60초로 완화
전환 트리거: - STABLE → SUSPECTED: 1회 핑 실패 - SUSPECTED → OFFLINE: 추가 2회 연속 실패 (총 3회, 30+10+10 = 최대 50��) - OFFLINE → STABLE: 핑 성공 + GPU 헬스 확인 통과 (§3.7 참조)
2.4 히스테리시스 — 플래핑 방지
STABLE→SUSPECTED 전환은 즉시이나, SUSPECTED→STABLE 복귀는 2회 연속 성공 후에만 허용. 이는 불안정한 네트워크에서 on/off 반복(플래핑)이 과도한 재배포를 일으키는 것을 방지한다.
SUSPECTED 상태에서 핑 성공 1회 → SUSPECTED 유지 (카운터 증가)
SUSPECTED 상태에서 핑 성공 2회 + GPU 헬스 통과 → STABLE 복귀
3. 작업 큐 설계
3.1 큐 저장소 선택
EC2에는 이미 Redis(100.102.81.13:6379)가 운영 중이다. 별도 외부 서비스 없이 Redis를 작업 큐로 활용한다.
선택 근거: - 원자적 pop 연산(BRPOPLPUSH)으로 배분 중 유실 없음 - TTL 설정으로 만료된 작업 자동 정리 - 기존 인프라 — 추가 운영 비용 없음
3.2 작업 상태 머신
PENDING → DISPATCHED → RUNNING → DONE
↓ ↓
NODE_WAIT FAILED (heartbeat timeout / 작업 오류)
↓ ↓
PENDING (무제한) PENDING (retries < max_retries)
DEAD (retries >= max_retries)
상태 정의: - PENDING: 큐에 대기 중 - DISPATCHED: 노드에 전송됨, 수락 확인 전 - RUNNING: 노드가 실행 중임을 heartbeat로 보고 - DONE: 완료, 결과 저장됨 - NODE_WAIT: 노드 부재(오프라인)로 인한 대기 — 재시도 카운터 증가 없음 - FAILED: heartbeat timeout 또는 노드 명시적 실패 응답 — 재시도 카운터 증가 - DEAD: max_retries 초과, 수동 개입 필요
NODE_WAIT와 FAILED의 구분이 핵심이다. hb5u가 밤새 꺼져 있으면 매일 아침 DEAD 작업이 쌓이는 것을 방지한다.
3.3 Redis 키 구조
roops:task:queue:pending → List (FIFO)
roops:task:queue:dispatched → Sorted Set (score = dispatch_time)
roops:task:{task_id} → Hash {
id, type, payload, status, target_node,
created_at, dispatched_at, started_at, done_at,
retries, max_retries, last_heartbeat,
lease_epoch ← v2 추가: 펜싱용 단조증가 정수, HINCRBY로 관리
}
roops:task:heartbeat:{task_id} → String, TTL = 90s
roops:node:state:{node_id} → Hash {
status, last_seen, consecutive_failures,
gpu_free_vram_mb, gpu_utilization, ← v2 추가
gpu_consecutive_ready, ← v3 추가: 히스테리시스용 연속 통과 횟수
last_gpu_check
}
3.4 Heartbeat 프로토콜 — 별도 스레드 필수
[v1 수정] heartbeat는 반드시 학습 루프와 분리된 별도 스레드/프로세스에서 기록해야 한다.
GPU 단일 스텝(forward + backward + 체크포인트 저장)은 대형 배치에서 90초를 초과할 수 있다. heartbeat를 학습 루프 인라인에 두면, 정상 실행 중인 스텝에서 TTL 만료로 FAILED 오판이 발생한다.
# 올바른 구조
def training_loop():
for step in steps:
model.forward_backward() # 90초+ 가능
def heartbeat_thread():
while running:
redis.set(f"roops:task:heartbeat:{task_id}", "alive", ex=90)
time.sleep(30)
threading.Thread(target=heartbeat_thread, daemon=True).start()
training_loop()
heartbeat 스레드는 학습 프로세스의 생존 여부를 반영하지, 개별 스텝 완료를 반영하지 않는다.
3.5 재시도 정책 — 노드 부재와 작업 실패 분리
[v1 수정] 재시도 카운터를 두 가지로 분리한다.
작업 실패 재시도 (retries):
- 증가 조건: heartbeat timeout, 노드 명시적 실패(OOM, 오류 코드)
- max_retries = 3
- backoff = exponential: 1분 → 5분 → 20분
- retries >= max_retries → DEAD
노드 부재 대기 (node_wait_count):
- 증가 조건: 배포 시도 시 노드 OFFLINE
- 상한 없음 (노드가 켜질 때까지 대기)
- 상태: NODE_WAIT (PENDING 재삽입, retries 불변)
- 단, node_wait_count가 임계값(예: 48회 = 24시간 기준)을 넘으면 roops-comm 알림
3.6 펜싱 — 중복 실행 방지
[v1 신규, v3 수정] 노드가 네트워크만 끊기고 GPU 작업은 계속 실행 중인 경우, EROS가 해당 작업을 다른 노드로 재배포하면 두 노드가 동일 작업을 동시에 실행하게 된다. 복귀한 구 노드의 결과가 새 노드의 결과를 덮어쓸 수 있다.
해결: lease_epoch 기반 펜싱
1. 작업 배포(최초 또는 재배포) 시 lease_epoch 증가 발급:
HINCRBY roops:task:{id} lease_epoch 1
→ lease_epoch는 roops:task:{id} Hash 필드에 통합 관리
2. 노드는 결과 기록 시 자신이 받은 lease_epoch를 함께 전송
3. EROS는 결과 수신 시 검증:
current = HGET roops:task:{id} lease_epoch
if result.lease_epoch != current:
discard(result) # 구 노드의 stale 결과
else:
commit(result)
4. 재배포 시 lease_epoch가 증가하므로, 구 노드의 결과는 자동 거부됨
NODE_WAIT → 재배포 경로도 동일하게 적용된다. 노드가 복귀해 NODE_WAIT 상태에서 재배포가 트리거될 때도 반드시 HINCRBY로 lease_epoch를 증가시킨 후 배포한다. 이 경로야말로 구 노드가 살아 돌아오는 대표 시나리오이므로, epoch 증가를 빠뜨리면 펜싱이 무력화된다.
3.7 GPU 헬스 신호 — 핑과 분리된 이중 상태 + 히스테리시스
[v1 신규, v3 수정] 핑 성공(노드 온라인)이 GPU 가용을 보장하지 않는다. hb5u는 MuJoCo 데모(:8600), thesis DB 백업 대상으로도 사용 중이며, VRAM이 이미 점유되어 있을 수 있다.
이중 상태 체계:
레벨 1 — 네트워크 상태 (핑 기반, EROS가 능동 폴링):
OFFLINE / ONLINE
레벨 2 — GPU 상태 (nvidia-smi 기반, 노드가 Redis에 주기적 기록):
GPU_UNAVAILABLE / GPU_READY
작업 배포 조건: 레벨1 = ONLINE AND 레벨2 = GPU_READY
노드 측 GPU 상태 기록 (30초 주기):
FREE_VRAM=$(nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits)
UTIL=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits)
redis-cli HSET roops:node:state:hb5u gpu_free_vram_mb $FREE_VRAM gpu_utilization $UTIL last_gpu_check $(date +%s)
GPU_READY 판정 기준 (예시, 실측 후 조정):
- gpu_free_vram_mb > 작업에 필요한 VRAM (작업 타입별 상수로 정의)
- gpu_utilization < 20% (다른 작업이 점유 중이 아님)
- last_gpu_check < 현재 시각 - 60초 (신선한 데이터)
[v3 추가] GPU 레벨2 히스테리시스 — GPU_UNAVAILABLE→GPU_READY 복귀 조건:
레벨1(핑)과 동일한 원칙을 적용한다. GPU 상태는 hb5u의 다른 워크로드(MuJoCo 데모·백업 등)가 간헐적으로 GPU를 건드릴 때 진동할 수 있다. 단일 임계값 통과 즉시 GPU_READY로 전환하면 배포가 붙었다 떨어졌다 반복된다.
GPU_READY → GPU_UNAVAILABLE: 즉시 (1회 임계값 초과로 전환)
GPU_UNAVAILABLE → GPU_READY: 2회 연속 임계값 통과 후에만 허용
구현:
# roops:node:state:{node_id} Hash 필드 gpu_consecutive_ready 활용
if is_gpu_ready(free_vram, utilization):
HINCRBY roops:node:state:{id} gpu_consecutive_ready 1
if gpu_consecutive_ready >= 2:
set GPU_READY
else:
HSET roops:node:state:{id} gpu_consecutive_ready 0
set GPU_UNAVAILABLE # 즉시 전환
이로써 레벨1(핑 기반)과 레벨2(GPU 기반) 히스테리시스가 대칭 구조를 이룬다.
4. 통합 흐름도
flowchart TD
A["EROS Watchdog (30초 주기)"] --> B{"레벨1: 핑"}
B -->|"실패 3회"| C["OFFLINE — 작업 NODE_WAIT"]
B -->|"성공"| D{"레벨2: GPU 헬스"}
D -->|"VRAM 부족 / 점유"| E["GPU_UNAVAILABLE — 배포 보류"]
D -->|"GPU_READY (2회 연속)"| F["작업 배포 + HINCRBY lease_epoch"]
F --> G["heartbeat 스레드 (별도, 30s)"]
G -->|"정상"| H["Task RUNNING"]
G -->|"90초 무응답"| I["Task FAILED (retries++)"]
H --> J["결과 수신 + lease 검증"]
J -->|"epoch 일치"| K["Task DONE"]
J -->|"epoch 불일치"| L["결과 폐기 (stale)"]
I -->|"retries < 3"| M["지수 백오프 후 PENDING"]
I -->|"retries >= 3"| N["Task DEAD — 알림"]
C --> O["NODE_WAIT (retries 불변)"]
O -->|"노드 복귀 + HINCRBY lease_epoch"| F
5. 미해결 과제
- 노드 핑 방법 확정 — Tailscale
pingvs. HTTP health endpoint 선택 - 작업 타입별 VRAM 상수 정의 — GPU_READY 판정 기준 실측 필요
- hb5u·cmg-cv16 실제 가동 패턴 측정 — 선행 논문 권고. 폴링 주기 최종 조정 근거
- node_wait_count 알림 임계값 — 24시간 기준치는 임시값, 실운용 패턴 후 조정
- GPU 히스테리시스 임계값 2회 적정성 — hb5u 실운용 후 조정 가능
변경 이력
- v3 (2026-09-08): Hermes v2 대조 검증(id: GU2dSKNICdZL) 반영 — lease_epoch HINCRBY로 Hash 통일, NODE_WAIT→재배포 epoch 증가 명시, GPU 레벨2 히스테리시스 추가
- v2 (2026-09-08): Hermes 코드 리뷰 반영 — heartbeat 별도 스레드 명시, 펜싱 추가, NODE_WAIT 분리, GPU 헬스 신호 추가, §2.2 표 수정
- v1 (2026-09-08): 최초 제출
참고
- 선행 논문:
2026-09-08-hermes-dual-rtx5060-orchestration-eros-proposal(Hermes, 2026-09-08) - Hermes 리뷰 v1: roops-comm
id: 7m35wcFgHQ6B(2026-09-08) - Hermes 검증 v2: roops-comm
id: GU2dSKNICdZL(2026-09-08) - Redis 인프라: EC2 내장
100.102.81.13:6379 - Tailscale 네트워크: hb5u·cmg-cv16 양 노드 연결됨
