먼저 읽는 30초 요약
이 글에서 가져갈 것
- 프로세스가 응답하는 온라인 상태와 지금 새 작업을 안전하게 맡을 수 있는 상태는 다릅니다.
- 누락된 RAM·AC·사용자 유휴 값은 낙관적으로 채우지 않고 신규 배정을 막는 보수적 기본값으로 해석합니다.
- models_ready·active_job과 reason_code는 데이터에 존재하지만 현재 claim 판단이나 상태 화면에 모두 연결된 것은 아닙니다.
ping이 성공해도 작업은 실패할 수 있습니다
처음에는 워커 프로세스가 응답하면 온라인으로 표시하고 작업 후보에 넣는 것으로 충분해 보였습니다. 하지만 응답 가능한 프로세스가 곧바로 모델을 실행할 수 있다는 뜻은 아닙니다. 모델 파일을 내려받는 중일 수 있고, 필요한 모델이 아직 메모리에 없거나 RAM이 한도에 가까울 수 있습니다. 노트북이 배터리로 동작하거나 사용자가 직접 작업 중인 상황도 단순 ping에는 나타나지 않습니다.
이 상태에서 조정자가 새 작업을 보내면 실패 이유가 모델 품질, 네트워크, 자원 부족 중 무엇인지 구분하기 어렵습니다. 특히 idle_learning 같은 긴 작업은 AC 전원과 충분한 사용자 유휴 시간이 필요하지만 대화 작업은 같은 조건을 요구하지 않을 수 있습니다. 그래서 heartbeat는 생존 확인뿐 아니라 역할별 배정 판단에 필요한 능력 보고서로 정의했습니다.
다만 heartbeat가 풍부하다고 해서 실제 상태를 자동으로 보장하지는 않습니다. 각 운영체제에서 RAM, 온도, 전원과 사용자 활동을 정확히 수집하는 코드가 필요하고 측정 실패도 별도 상태로 표현해야 합니다. 현재 스타터는 JSON 보고서를 받아 정책에 적용하는 조정기이며, 센서 수집기 자체는 제공하지 않습니다.
스타터가 실제로 받는 보고 형식
예제 heartbeat에는 worker_id와 roles가 필수로 들어갑니다. roles는 chat, embed, idle_learning처럼 이 워커가 맡을 수 있다고 선언한 작업 종류입니다. models_ready는 현재 준비된 모델 목록, memory_used_pct는 메모리 사용률, temperature_c와 temperature_state는 온도 값과 그 값의 해석 상태를 담습니다.
on_ac_power와 user_idle_seconds는 특히 백그라운드 작업 판단에 사용됩니다. active_job은 현재 작업을 관찰하기 위한 필드입니다. 조정자는 JSON 안의 시각을 신뢰하는 대신 heartbeat를 받은 시각을 workers.last_seen에 별도로 기록합니다. 이 값으로 마지막 보고가 60초를 넘었는지 계산해 오래된 워커의 claim을 거부합니다.
모든 필드가 현재 claim 조건에 사용되는 것은 아닙니다. _eligible은 roles, memory_used_pct, temperature_state, on_ac_power와 user_idle_seconds를 확인합니다. models_ready와 active_job은 보고서에 보존되지만 현재 적격성 판단에는 연결되지 않았습니다. 따라서 ‘필드가 존재한다’와 ‘배정 정책이 사용한다’를 표에서 분리했습니다.
{
"worker_id": "worker-a",
"roles": ["chat", "embed", "idle_learning"],
"models_ready": ["small-chat"],
"memory_used_pct": 61.2,
"temperature_c": 54,
"temperature_state": "measured",
"on_ac_power": true,
"user_idle_seconds": 920,
"active_job": null
}누락값의 기본 동작도 코드에서 확인했습니다
운영 데이터에서 누락값은 정상값이 아닙니다. memory_used_pct가 없을 때 0%로 해석하면 실제 메모리 상태를 모르는 워커가 가장 여유 있는 장비처럼 선택될 수 있습니다. 스타터는 이 값을 100%로 간주해 85% 한도에서 신규 claim을 막습니다. 측정 실패를 작업 가능으로 오해하지 않는 보수적 기본값입니다.
on_ac_power가 없으면 false, user_idle_seconds가 없으면 0초로 처리합니다. 그 결과 AC가 필요한 idle_learning과 최소 600초 유휴 조건이 있는 역할은 배정되지 않습니다. 반면 chat처럼 해당 정책을 요구하지 않는 역할은 RAM과 온도 등 다른 조건을 통과하면 후보가 될 수 있습니다. 누락값의 영향은 필드 하나가 아니라 요청 역할과 정책 조합으로 결정됩니다.
온도는 temperature_c 숫자를 직접 비교하지 않고 temperature_state가 정책의 차단 목록에 포함되는지 봅니다. 기본 차단 상태는 over_limit과 sensor_error입니다. sensor_unavailable은 현재 목록에 없으므로 자동 차단되지 않습니다. 센서 없는 장비를 제한하려면 정책에 상태를 추가하거나 roles를 줄여야 하며, 지금 구현이 하지 않는 판단을 이미 된 것처럼 설명해서는 안 됩니다.
| 항목 | 누락 시 해석 | 현재 영향 |
|---|---|---|
| RAM 사용률 | 100% | 모든 신규 claim 차단 |
| AC 전원 | false | AC 필수 역할 차단 |
| 사용자 유휴 | 0초 | 유휴 기준 역할 차단 |
| 온도 상태 | 정책 목록과 비교 | 명시된 상태만 차단 |
reason_code는 다음 단계의 관측 항목입니다
‘조금 뜨거움’이나 ‘메모리가 부족해 보임’ 같은 문장은 사람이 읽기에는 자연스럽지만 자동 분류와 통계에는 불리합니다. _eligible은 role_mismatch, memory_limit, thermal_limit, ac_power_required, user_active 같은 고정 코드를 반환합니다. 같은 거부 원인을 장비와 시간대별로 집계하려면 이런 제한된 어휘가 필요합니다.
그러나 현재 claim은 allowed 값만 사용하고 _reason은 버립니다. 작업을 받지 못한 워커는 단순히 null을 돌려받고, status 응답에도 마지막 거부 코드가 저장되지 않습니다. 따라서 ‘reason_code를 사용해 대시보드에서 설명한다’는 문장은 현재 범위를 넘습니다. 정확한 표현은 내부 판단 함수가 코드를 반환하지만 아직 관측 경로에 연결되지 않았다는 것입니다.
다음 구현은 worker_id, job_id 또는 required_role, reason_code, 판단 시각과 정책 버전을 함께 저장하는 것입니다. 그래야 메모리 한도 때문인지 역할 불일치 때문인지 화면에서 구분할 수 있습니다. 다만 모든 claim 시도를 무제한 로그로 남기면 DB가 빠르게 커질 수 있으므로 최근 상태와 집계 이벤트의 보존 기간도 함께 설계해야 합니다.
| 내부 코드 | 현재 의미 | 아직 필요한 연결 |
|---|---|---|
| memory_limit | RAM 한도 초과 | 상태 응답·통계 저장 |
| thermal_limit | 차단 온도 상태 | 센서 원인과 함께 표시 |
| ac_power_required | AC 필수 역할 | 전원 회복 안내 |
| user_active | 유휴 시간 부족 | 재시도 예상 시각 |
위험값을 일부러 넣어 배정 거부를 확인했습니다
자동시험은 정상 heartbeat만 확인하지 않습니다. memory_used_pct를 90으로 바꾼 보고서를 등록하고 chat 작업을 enqueue한 뒤 같은 워커가 claim하지 못하는지 확인합니다. 정책의 max_memory_used_pct는 85이므로 90은 분명한 차단 조건입니다. 이 시험으로 RAM 값이 단순 표시가 아니라 실제 배정 경로에 연결됐음을 확인합니다.
heartbeat 신선도는 claim 코드 경로에서 확인했습니다. 마지막 수신 시각과 현재 시각의 차이가 heartbeat_timeout_seconds인 60초를 넘으면 트랜잭션을 rollback하고 작업을 반환하지 않습니다. ‘여러 번 누락되면’처럼 횟수 기반으로 동작하는 것이 아니라 마지막 수신 시각 기준입니다. 정확히 60초인 경계와 그 이후 값은 별도 경계 시험으로 추가할 가치가 있습니다.
정상값으로 복귀했을 때 다시 후보가 되는 과정은 현재 전용 자동시험이 아니라 실습 통과 기준입니다. 85% 미만 RAM, 허용 온도 상태와 역할 조건을 다시 보고한 뒤 claim 성공을 확인해야 합니다. 차단 시험만 있으면 워커가 영구적으로 제외되는 회귀를 놓칠 수 있으므로 다음 테스트 확장에서는 차단과 회복을 한 쌍으로 다루는 편이 좋습니다.
# 위험 보고: memory_used_pct = 90
python3 coordinator.py --db demo.db heartbeat worker-heartbeat.blocked.json
python3 coordinator.py --db demo.db claim worker-a
# 기대 결과: null
# 정상 보고 뒤 재시도는 실습 통과 기준
python3 coordinator.py --db demo.db heartbeat worker-heartbeat.example.json확인한 것과 아직 남은 구현
자동시험으로 직접 확인한 범위는 RAM 90%에서 신규 claim이 거부되는 동작입니다. 코드 대조로 확인한 범위는 60초 heartbeat timeout, 역할 불일치, 온도 차단 상태, AC 필수 역할과 최소 사용자 유휴 시간입니다. 누락된 worker_id 또는 비어 있는 roles는 heartbeat 등록 단계에서 ValueError로 거부됩니다.
아직 필요한 첫 번째 구현은 운영체제별 수집기입니다. macOS, Windows와 Linux는 메모리·전원·온도·사용자 유휴 시간을 읽는 방법과 권한이 다릅니다. 센서가 없는 장비, 권한 오류, 일시적인 명령 실패를 같은 0 값으로 보내지 말고 measured, sensor_unavailable, sensor_error처럼 측정 상태와 함께 보내야 합니다.
두 번째 구현은 보고 필드와 실제 배정 판단의 연결입니다. models_ready를 사용하면 필요한 모델이 준비된 워커를 우선하거나 모델 적재 작업을 별도로 만들 수 있고, active_job은 보고 상태와 DB lease가 어긋나는지 찾는 데 쓸 수 있습니다. 현재는 둘 다 JSON에 보존될 뿐 claim 게이트가 아닙니다. reason_code 노출, 정상 복귀 자동시험과 실제 장비에서의 측정 정확도도 남은 범위입니다.
- 자동시험: RAM 90% claim 거부
- 코드 확인: 60초 stale·역할·온도·AC·유휴 조건
- 실습 기준: 정상 보고 뒤 후보 복귀
- 미구현: OS별 센서 수집기
- 미구현: models_ready·active_job 판단과 거부 이유 노출
자주 묻는 질문
heartbeat 간격은 얼마가 적당한가요?
작업 시간과 네트워크 안정성에 따라 정합니다. 이 스타터는 마지막 heartbeat가 60초를 넘기면 stale로 처리합니다. 실제로 적용할 때는 본문의 ‘ping이 성공해도 작업은 실패할 수 있습니다’ 절차를 따라 한 조건씩 확인하고 결과를 기록하세요.
온도 센서가 없으면 워커를 쓰면 안 되나요?
측정 불가 상태를 명시하고 역할과 부하를 보수적으로 제한할 수 있습니다. 누락된 RAM·AC·사용자 유휴 값은 낙관적으로 채우지 않고 신규 배정을 막는 보수적 기본값으로 해석합니다. 실제로 적용할 때는 본문의 ‘스타터가 실제로 받는 보고 형식’ 절차를 따라 한 조건씩 확인하고 결과를 기록하세요.
LLM이 상태 이유를 작성해도 되나요?
운영 판단에 쓰는 reason_code는 정해진 코드로 만들고 설명 문장만 별도로 생성하는 편이 안전합니다. models_ready·active_job과 reason_code는 데이터에 존재하지만 현재 claim 판단이나 상태 화면에 모두 연결된 것은 아닙니다. 실제로 적용할 때는 본문의 ‘누락값의 기본 동작도 코드에서 확인했습니다’ 절차를 따라 한 조건씩 확인하고 결과를 기록하세요.
공식 출처
세부 동작과 최신 버전은 아래 원문을 함께 확인하세요.
systemd Watchdog