본문으로 건너뛰기

[Ray] Dashboard 죽은 노드 캐시의 변수 섀도잉 버그 수정

PR 링크: ray-project/ray#61185 상태: Merged | 변경: +99 / -4

들어가며

Ray Dashboard의 node_head API는 클러스터 노드 정보를 관리하며, 죽은 노드의 수를 MAX_DEAD_NODES_TO_CACHE로 제한합니다. 캐시가 가득 차면 가장 오래된 죽은 노드를 제거(evict)하는데, 여기서 변수 섀도잉 버그가 있어 방금 죽은 노드의 ID가 제거 대상 노드의 ID로 덮어써지는 문제가 있었습니다. 이로 인해 노드가 살아있으면서 동시에 죽은 것으로 표시될 수 있었습니다.

핵심 코드 분석

Before: 변수 섀도잉

async def _update_node(self, node: dict):
    node_id = node["nodeId"]
    # ... 노드 상태 처리 ...

    self._dead_node_queue.append(node_id)
    if len(self._dead_node_queue) > node_consts.MAX_DEAD_NODES_TO_CACHE:
        node_id = self._dead_node_queue.popleft()  # node_id를 덮어씀!
        DataSource.nodes.pop(node_id, None)
        self._stubs.pop(node_id, None)
    DataSource.nodes[node_id] = node  # 제거된 ID로 등록됨

node_id가 evict된 노드의 ID로 덮어써진 후 DataSource.nodes[node_id] = node에서 잘못된 키로 현재 노드가 등록됩니다.

After: 별도 변수명 사용

async def _update_node(self, node: dict):
    node_id = node["nodeId"]
    # ... 노드 상태 처리 ...

    self._dead_node_queue.append(node_id)
    if len(self._dead_node_queue) > node_consts.MAX_DEAD_NODES_TO_CACHE:
        evicted_node_id = self._dead_node_queue.popleft()
        DataSource.nodes.pop(evicted_node_id, None)
        self._stubs.pop(evicted_node_id, None)
    DataSource.nodes[node_id] = node  # 원래 node_id로 정확히 등록

왜 이게 좋은가

1. 데이터 정합성 복원

이 버그로 인해 Dashboard API에서 동일한 노드가 ALIVE와 DEAD 상태로 동시에 나타날 수 있었습니다. 모니터링 도구에서 혼란을 야기하고, Autoscaler의 판단에도 영향을 줄 수 있는 심각한 문제입니다.

2. 변수 섀도잉의 위험성

Python에서 변수 섀도잉은 린터 경고 없이 발생합니다. 동일 스코프 내에서 같은 이름을 재사용하면 의도치 않게 원래 값을 잃게 됩니다. 명시적으로 다른 이름(evicted_node_id)을 사용하면 의도가 명확해집니다.

3. 철저한 테스트 추가

def test_dead_node_cache_contains_latest_dead_node_if_cache_overflows():

MAX_DEAD_NODES_TO_CACHE=1로 설정하고, 노드 2개를 순차적으로 추가/제거하여 캐시 오버플로우 시 최신 죽은 노드만 유지되는지 검증하는 테스트가 추가되었습니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글