← 전체 글
Modbus/약 16분 읽기/ 조회

느린 Modbus TCP 장치 하나가 전체 스캔을 멈추는 이유

장치 한 대가 멈추거나 RTU 게이트웨이가 포화돼도 SCADA 드라이버 전체가 같이 느려지지 않도록 Modbus TCP 폴링 주기, 타임아웃, 재시도, 스캔 그룹을 정하는 법.

ModbusSCADA네트워킹문제 해결태그

장치 하나가 전체를 끌어내린다

전화 내용은 거의 항상 같다. “SCADA 전체가 느려요.” 드라이버 진단을 열어 보면 RTU 게이트웨이 뒤에 물린 유량계 한 대가 타임아웃 2초, 재시도 3회로 단일 스레드 채널을 붙잡고 있다. 같은 채널의 다른 장치들은 그 뒤에서 줄 서서 기다린다. 고장난 것은 없다. 장치 하나가 응답을 멈췄을 때 무슨 일이 생기는지를 고려하지 않고 폴링 스케줄을 짰을 뿐이다.

Modbus TCP 자체는 간단하다. 포트 502로 소켓을 열고, transaction ID와 unit ID를 붙인 요청을 보내고, 레지스터를 받아 온다. Modbus 애플리케이션 프로토콜 규격(V1.1b3)은 몇 페이지면 끝난다. 어려운 것은 “얼마나 자주 읽을지”와 “언제 죽었다고 볼지”를 정하는 일이다. 이 두 값이 재시도 횟수, 채널 스레딩과 얽히면서, 원인과 전혀 닮지 않은 증상을 만들기 때문이다.

  • 장치 하나가 매 스캔마다 채널의 요청 슬롯을 먹어서 화면이 멈춘다.
  • 큰 블록 서너 개로 끝날 읽기를 잘게 쪼개 보내 PLC나 게이트웨이 부하가 올라간다.
  • 실제 최악 응답보다 타임아웃을 빡빡하게 잡아서 정상 지터에도 통신 알람이 깜빡인다.
  • 재시도가 스캔을 주기 밖으로 밀어내 히스토리언에 구멍이 생긴다.
  • 아직 내려가는 중인 배치 읽기 뒤에 운전자 쓰기가 밀린다.

어느 것도 Modbus 문제가 아니다. 전부 스케줄링 문제고, 드라이버 기본값이 대신 풀어 주지 않는다.

태그 목적별로 스캔 그룹을 나눈다

모든 레지스터를 같은 주기로 읽으면 편해 보이지만, 실제 운전에는 맞지 않는다. 태그 목적에 따라 그룹을 나누는 편이 좋다.

스캔 그룹운용 기준
빠른 상태운전 피드백, 밸브 열림, 시퀀스 상태짧은 주기. 가능한 연속 블록으로 읽는다.
운전 화면 아날로그압력, 유량, 온도화면과 알람 필요 속도에 맞춘 중간 주기.
느린 진단값펌웨어, 장치 온도, 통신 카운터긴 주기. 공정값과 분리한다.
적산과 리포트전력량, 런타임, 배치 합계제어에 쓰지 않으면 느리게 읽는다.
쓰기 확인설정값 echo, 명령 수락 비트쓰기 시점 근처에서 확인하고 평상시 주기로 돌아간다.

이렇게 나누면 문제를 볼 때도 쉽다. 진단값이 늦게 갱신된다고 해서 공정 상태까지 통신 불량으로 보일 필요는 없다.

작은 읽기 여러 번보다 좋은 블록 한 번

많은 Modbus 드라이버는 가까운 레지스터를 한 번에 읽을 때 효율이 좋다. 레지스터 50개를 하나씩 읽는 것보다 50개 블록을 한 번 읽는 편이 보통 낫다.

현장에서 적용하는 기준은 다음과 같다.

  • 장치가 허용하면 SCADA 태그를 연속 레지스터 범위에 맞춘다.
  • 드라이버가 자동 최적화를 하지 않는다면 한 개짜리 읽기를 남발하지 않는다.
  • 장치와 드라이버가 허용하는 최대 블록 크기를 넘기지 않는다.
  • 불안정하거나 옵션에 따라 없는 주소는 핵심 블록과 분리한다.
  • 블록을 유지하기 위해 의도적으로 포함한 빈 주소는 레지스터 맵에 표시한다.

블록 크기는 두 가지 상한이 정해 준다. Function code 03(홀딩 레지스터 읽기)과 04(입력 레지스터 읽기)는 요청당 125개, FC 01/02(비트 읽기)는 2000개가 상한이다. Modbus PDU가 253바이트라서 드라이버가 뭐라고 하든 이 한계를 넘지 못한다. 블록 안의 빈 공간도 그만큼 중요하다. 홀딩 레지스터 40001–40060을 한 번에 읽는 편이, 그중 절반이 예비 주소라 해도 흩어진 열두 번 읽기보다 낫다. 대역폭 몇 레지스터 낭비하는 게 스캔마다 왕복 여섯 번 더 무는 것보다 낫다.

주소가 범위 여기저기 흩어져 있으면 드라이버는 결국 단건 읽기로 떨어지고, Wireshark 캡처도 읽기 어려워진다. 진짜 빈 구간인지 드라이버 특성인지 구분이 안 된다.

폴링 주기와 타임아웃은 다르다

폴링 주기는 드라이버가 요청을 시작하는 간격이다. 타임아웃은 응답을 기다리는 시간이다. 재시도 횟수는 실패로 판단하기 전에 몇 번 더 시도할지 정한다.

죽은 장치로 계산해 보자. 타임아웃 2초 × 재시도 3회 = 6초. 드라이버가 포기하고 넘어가기까지 채널이 6초를 잡혀 있다. 단일 스레드 채널이면 같은 채널의 다른 장치들은 매 스캔마다 그 6초를 통째로 기다린다. 첫 문단의 그 유량계다.

내가 잡는 시작값이고, 측정해서 조정한다.

  • 이더넷 PLC에 직접 붙는 건강한 LAN이면 타임아웃 200~500 ms면 충분하다. 실제 왕복은 한 자릿수 ms다.
  • RTU 게이트웨이 뒤라면 시리얼 구간을 잡아야 한다. 9600 baud에서 60개 레지스터 응답은 순수 전송만 대략 130 ms고, 여기에 turnaround와 PLC 스캔이 더 붙는다.
  • 재시도는 1회, 많아야 2회. 3회는 대개 실제 장애를 가리고 스톨만 길게 만든다. 3회 재시도해야 답하는 장치라면 그게 곧 알람거리지 고칠 대상이 아니다.
  • 패킷 하나 빠졌다고 알람하지 말고, 지속된 bad quality — 예컨대 연속 3스캔 실패 — 에 알람한다. 바쁜 스위치에서 재전송 한 번은 정상이다.
  • 오락가락하는 장치는 별도 채널로 빼서 건강한 장치를 굶기지 못하게 한다.

기본값 논쟁 전에 재라. 드라이버 진단 화면이나 tcp.port == 502로 필터한 5분짜리 Wireshark 캡처가 실제 응답 시간 분포를 보여 준다. 보통 데이터시트 값과는 딴판이다.

게이트웨이와 Unit ID를 따로 본다

이더넷 PLC에 직접 Modbus TCP로 붙는 것과, Modbus TCP-RTU 게이트웨이를 거쳐 RS-485 장치를 읽는 것은 다르다. 게이트웨이는 TCP 접속을 여러 개 받아도 뒤쪽 시리얼 구간은 한 줄로 처리한다.

확인할 항목은 다음과 같다.

  • 게이트웨이를 쓸 때 Unit ID가 실제 하위 장치 주소와 맞는가.
  • 게이트웨이가 동시에 받는 TCP 클라이언트 수를 감당하는가.
  • 시리얼 baud rate, parity, turnaround time이 타임아웃 계산에 들어갔는가.
  • 시리얼 네트워크가 끝내기 전에 다음 요청을 너무 빨리 보내지 않는가.
  • 지원하지 않는 function code나 broadcast를 실수로 쓰지 않는가.

포화된 게이트웨이의 신호는 예외 코드 0x0B, “gateway target device failed to respond”나 0x0A, “gateway path unavailable”다. 이 코드는 말단 장치가 아니라 게이트웨이가 보낸다. TCP 요청은 받았지만 시리얼 쪽에서 제때 답을 못 받은 것이다. 모든 클라이언트를 붙였을 때 0x0B가 늘어나면 폴링을 늦추거나 부하를 나눠라. 게이트웨이가 고장난 게 아니라 초과 가입된 상태다. 장치 하나만 읽으면 되고 다섯 대를 같이 켜면 같이 죽는다 — 그건 고장이 아니라 포화다.

HMI에는 통신 품질이 보여야 한다

Modbus에는 품질 필드가 없다. 홀딩 레지스터는 16비트 값이 전부다. 타임스탬프도, 상태 플래그도, OPC UA의 StatusCode 같은 장치도 없다. 그래서 SCADA 드라이버가 통신 상태, 타임아웃, 예외 응답, 마지막 갱신 이후 경과 시간으로 품질을 만들어 내야 하고, HMI가 그걸 실제로 보여 줘야 한다. 오래된 레지스터를 살아 있는 값처럼 다루면 운전자가 멈춘 숫자를 한 시간씩 믿게 된다.

화면과 알람에서는 다음 상태가 구분되어야 한다.

  • 최근에 갱신된 정상값.
  • 타임아웃 또는 무응답.
  • 장치가 보낸 Modbus exception 응답.
  • 통신 끊김 뒤 남아 있는 마지막 값.
  • 일부 레지스터 블록만 실패한 부분 장애.
  • 쓰기 실패 또는 쓰기 확인 실패.

운전 화면에서는 설비 요약이나 장치 faceplate 근처에 통신 품질을 보여 주는 편이 좋다. 드라이버 진단 페이지에만 숨겨 두면 운전자는 오래된 값을 정상값으로 믿을 수 있다.

쓰기 요청은 읽기와 다르게 다룬다

Modbus 쓰기는 보내기 쉽다. 하지만 패턴이 없으면 누가 무엇을 썼고 실제로 반영됐는지 추적하기 어렵다.

유용한 방식은 다음과 같다.

  • 운전자 명령은 백그라운드 읽기 폴링과 분리해서 다룬다.
  • echo 레지스터, 상태 비트, 실제 공정 피드백으로 쓰기 결과를 확인한다.
  • 스크립트나 faceplate에서 설정값을 반복해서 쓰지 않도록 제한한다.
  • SCADA가 지원하면 작업자, 이전 값, 새 값, 시각, 결과를 남긴다.
  • 쓰기는 성공했지만 확인 폴링이 실패했을 때의 화면 표시를 정한다.

HMI가 무엇을 하든 운전 모드, 퍼미시브, 범위 제한은 PLC가 래더나 ST에서 다시 검증한다. Modbus는 전송 수단이다. 안전 계층이 아니고, FC 06 코일 쓰기 요청은 그 주소 뒤에 있는 로직 이상의 권한을 갖지 않는다.

시운전 때 확인할 것

인수 전에 실제 부하와 비슷한 상태로 폴링 스케줄을 확인한다.

  1. 평상시에 접속할 SCADA 클라이언트, 히스토리언, 리포트, 엔지니어링 툴을 모두 켠다.
  2. 드라이버 진단에서 응답 시간, 재시도, 실패 요청, 큐 깊이를 본다.
  3. 장치 하나를 분리해서 관련 없는 장치까지 심하게 느려지지 않는지 확인한다.
  4. 안전한 범위에서 잘못된 테스트 주소를 읽어 Modbus exception 표시가 명확한지 본다.
  5. 오래된 값이 HMI와 히스토리언에서 stale 또는 bad로 남는지 확인한다.
  6. 쓰기를 수행하고 나가는 요청뿐 아니라 확인 경로까지 검증한다.
  7. 최종 폴링 주기, 타임아웃, 재시도, 블록 범위, Unit ID를 레지스터 맵과 함께 저장한다.

사람들이 빼먹는 게 3번인데, 그게 핵심이다. 장치 하나 케이블을 뽑고 나머지가 계속 갱신되는지 보는 것 — 그게 실패한 유량계 한 대에 관제실이 통째로 멈추는 드라이버와, 우아하게 성능만 조금 떨어지는 드라이버의 차이다. 건강한 장치들이 같이 버벅이면 채널 스레딩이나 재시도 계산이 틀린 것이다. 새벽 2시 호출이 아니라 벤치에서 고쳐라.