엣지 게이트웨이: Node-RED·Telegraf·NiFi로 현장 데이터 라우팅
📍 현재 위치: 제2부 "공정 포착하기"입니다. 신호는 이미 생물반응기(bioreactor)에서 OPC UA로 빠져나와 MQTT 브로커(broker)에 도착했습니다(이전 장). 이제 우리는 그 데이터를 수집하고, 변환하고, 다음 단계로 라우팅하는 게이트웨이(gateway)를 구축하고, 세 가지 오픈소스 도구 중 어느 것이 그 일을 맡을지 결정합니다.
엣지 게이트웨이를 공장의 우편실(mailroom)이라고 생각해 보세요. 현장 곳곳의 센서들이 봉투(측정값)를 끊임없이 떨어뜨립니다. 우편실은 그것들을 분류하고, 이상한 형식으로 도착한 것은 다시 라벨을 붙이며, 어느 봉투를 어느 부서로 보낼지 정하고, 그리고 결정적으로 자신이 다룬 모든 봉투에 대한 기록부를 유지합니다. 엉성한 우편실은 우편물을 잃어버립니다. 규제(regulated) 우편실은 몇 달이 지난 뒤에도 정확히 어느 봉투를, 언제, 어디에서 받았고, 어디로 보냈는지를 말해 줄 수 있습니다. 이 장에서는 세 종류의 우편실을 만들고, 각 작업에 맞는 올바른 우편실을 고릅니다.
엣지 게이트웨이는 플랫폼 전체의 단층선 위에 자리합니다. OT(운영 기술, operational technology — 공정을 구동하는 제어기, 스키드(skid), 센서)와 IT(이를 의미 있게 만드는 데이터베이스, 대시보드, 분석)가 만나는 이음매입니다. OT 쪽에서 데이터는 OPC UA와 Modbus로 말하며 격리된 제어 네트워크 위에 살고, IT 쪽에서는 MQTT, SQL, HTTP로 말합니다. 무언가가 그 중간에 서서, 검증된(validated) 제어 루프(control loop, 공정을 실제로 구동하는 센서-제어기-액추에이터 폐루프로, GxP 하에서 올바르게 동작함이 정식으로 입증·문서화되어 재적격성 평가 없이는 변경할 수 없습니다 — 바이오의약품 제조의 품질, 규제, 데이터 참조)는 결코 건드리지 않으면서 통역하고, 버퍼링하고, 라우팅해야 합니다. 그 무언가가 바로 게이트웨이이며, 이것을 제대로 해내는 것이 규제 기관에 제출할 수 있는 데이터와 그저 완전하기를 바라는 데이터 사이의 차이를 만듭니다.
이 장에서 다루는 내용
- OT/IT 가교가 왜 존재하며, 게이트웨이가 그 이음매에서 무엇을 해야 하는지.
- 우리가 출하하는 세 가지 오픈소스 도구 — Node-RED(로우코드 플로우), Telegraf(선언적 수집), Apache NiFi(보장된 전달 + 재현 가능한 출처 정보) — 와 각각이 진정으로 잘하는 일.
- 이들이 브로커에서 히스토리안(historian,
ts.sensor_reading)과 배치 모델(batch model)로 데이터를 어떻게 라우팅하는지, 실제로 보게 될 롱 포맷(long-format) 행과 함께. - 솔직한 부분: 전달 의미론(at-least-once 대 exactly-once), 감사 추적(audit-trail)의 공백, 그리고 OSS 게이트웨이가 검증된 진실의 기록(record-of-truth)으로 작업을 넘기는 지점.
이음매: 게이트웨이가 실제로 하는 일
게이트웨이는 데이터베이스도 아니고 대시보드도 아닙니다. 그 역할을 압축하면 사우스바운드–변환–노스바운드(southbound–transform–northbound) 파이프라인입니다. 사우스바운드로는 현장 프로토콜을 말합니다. 생물반응기가 게시하는 OPC UA 주소 공간(address space, OPC UA 서버가 노출하는 읽을 수 있는 항목들의 구조화된 트리)을 구독하고 [1], 더 오래된 스키드(스키드는 프레임에 장착된 자기 완결적 공정 설비 단위)에 대해서는 Modbus 레지스터를 폴링(polling)합니다(Modbus는 더 오래되고 단순한 산업 프로토콜로, 데이터를 레지스터라 불리는 번호 매겨진 메모리 슬롯으로 노출하면 게이트웨이가 타이머에 맞춰 읽습니다). 중간에서는 정규화합니다. 3725라는 원시 레지스터 값이 37.25 degC가 되고(많은 디바이스는 소수점이 암묵적으로 정해진 정수를 보내므로 — 여기서는 100이라는 고정 배율 — 게이트웨이가 그 스케일을 적용합니다), 벤더의 태그(디바이스가 신호에 붙인 자체 이름)가 우리의 정규(canonical) BR101.Temp.PV(asset.measurement.role로 읽습니다: 생물반응기 BR101, 그 온도, 공정값)가 되며, 누락된 단위가 채워집니다. 노스바운드로는 정제된 레코드를 그것이 살아야 할 곳으로 라우팅합니다. 통합 네임스페이스(Unified Namespace) 토픽(이름 짓기에서 구축하는 단일 정규 실시간 토픽 계층)으로 MQTT 브로커에 올리거나, 곧장 TimescaleDB(시계열 확장을 갖춘 PostgreSQL, 즉 히스토리안 데이터베이스)로 보냅니다.
바로 이 형태 — Modbus/MQTT/OPC UA를 위한 사우스바운드 프로토콜 변환 계층과, 네트워크 끊김이 결코 샘플을 떨어뜨리지 않도록 수집과 전송을 분리하는 캐시를 갖춘 모듈형 엣지 게이트웨이 — 에 대한 동료 심사(peer-reviewed) 참조 설계가 2026년에 발표되었습니다 [2]. 그 분리가 핵심 전부입니다. 현장은 데이터 생산을 결코 멈추지 않지만, 네트워크는 때때로 그것을 실어 나르기를 멈춥니다. 로컬에 버퍼링했다가 링크가 돌아오면 전달하는 게이트웨이만이 완전한 기록을 보존합니다. 임베디드 하드웨어 위의 OPC UA 게이트웨이에 대한 또 다른 분석은 그 반대편의 긴장을 정량화합니다. OPC UA는 풍부하고 무겁고 자기 기술적인(self-describing) 스택인 반면, MQTT는 가벼운 발행/구독(pub/sub, 발행자는 이름 붙은 토픽에 메시지를 발행하고 수신자는 관심 있는 토픽을 구독하며, 둘 사이에 직접 연결은 없습니다) 전송입니다. "무겁다"는 것은 OPC UA가 교환마다 많은 구조와 메타데이터를 실어 나르며 더 많은 CPU와 대역폭을 쓴다는 뜻이고, "가볍다"는 것은 MQTT가 작은 메시지를 값싸게 옮긴다는 뜻이어서, 게이트웨이는 으레 무거운 OPC UA를 사우스바운드로 읽고 가벼운 MQTT를 노스바운드로 내보냅니다 [3].
OT/IT 이음매로서의 엣지 게이트웨이: 사우스바운드로 현장 프로토콜을 읽고, 중간에서 정규화·버퍼링하며, 노스바운드로 브로커와 히스토리안으로 라우팅합니다. Node-RED, Telegraf, NiFi는 같은 파이프라인 위에서 각기 다른 지점을 차지합니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.
사우스바운드, 변환, 노스바운드
세 단계를 정확히 이름 붙일 가치가 있습니다. 단계마다 실패 양상이 다르고, 이 장 뒤에서의 도구 선택이 바로 이 단계들에 직접 매핑되기 때문입니다.
- 사우스바운드(southbound)는 현장 쪽입니다. 게이트웨이는 클라이언트입니다. 생물반응기가 게시하는 OPC UA 주소 공간을 구독하거나 [1] Modbus 레지스터를 폴링하며,
value와StatusCode를 함께 읽습니다 — 결코 값만 읽지 않습니다. (StatusCode, 즉 품질 플래그는 판독값이 신뢰할 만한지에 대한 현장 자신의 판정입니다 — Good, Uncertain, Bad이며, 그 코드는 "데이터가 도착하는 곳"에서 풀어 설명합니다.) 여기서의 대죄는 숫자를 읽고 상태를 버리는 것인데, 그러면 모든 값이 똑같이 신뢰할 만해 보이기 때문입니다. 사우스바운드는 수집/전송 분리가 사는 곳이기도 합니다. 노스바운드 링크가 끊긴 동안에도 계속 수집하는 로컬 버퍼가, 기록을 단지 최신이 아니라 완전하게 만듭니다 [2]. - 변환(transform)은 중간입니다. 원시 레지스터
3725가37.25 degC가 되고, 벤더 태그가 정규BR101.Temp.PV가 되며, 누락 단위가 채워지고,quality플래그는 손대지 않고 그대로 실려 갑니다. OPC UA는 무겁고 자기 기술적인 스택이고 MQTT는 가벼운 전송이므로, 변환은 풍부한 사우스바운드 페이로드가 간결한 노스바운드 메시지로 얇아지는 곳이기도 합니다 [3]. 다음 절이 보여 주듯, 이 단계가 바로 처리 중 유실이 숨는 곳입니다. - 노스바운드(northbound)는 IT 쪽입니다. 정제된 레코드가 다음으로 라우팅됩니다 — 통합 네임스페이스 토픽으로 브로커에 올리거나, 곧장 TimescaleDB의
ts.sensor_reading행으로 갑니다. 노스바운드는 전달 의미론(QoS 선택)과 멱등성((ts, tag)에 대한 중복 제거)이 중요한 곳입니다. 이것이 네트워크를 건너 진실의 기록 시스템으로 들어가는 홉이기 때문입니다.
세 가지 도구, 세 가지 작업
세 가지를 모두 출하하는 이유는 어떤 단일 도구도 모든 축에서 이기지 못하기 때문입니다. 비결은 도구를 작업에 맞추는 것입니다.
Node-RED — 로우코드 플로우 편집기
Node-RED는 브라우저 기반의 로우코드(low-code) 편집기로, 작은 기능 노드들을 플로우(flow)로 연결합니다. 플로우 자체는 JSON으로 저장되며 Node.js 위에서 실행됩니다 [4]. 어떤 아이디어를 현장에 올리는 가장 빠른 방법입니다. mqtt in 노드, 페이로드를 재구성하는 function 노드, 그리고 postgres 노드를 끌어다 놓고 연결하면 바로 수집이 시작됩니다. 데몬(daemon)을 결코 직접 작성하지 않을 공정 엔지니어도 기꺼이 Node-RED 플로우를 만듭니다.
플로우가 JSON이기 때문에, 다른 코드형 구성(config-as-code) 산출물과 마찬가지로 Git 안에 살아 있습니다. 브로커에서 Sparkplug 페이로드(페이로드는 MQTT 메시지의 데이터 본문이며, Sparkplug B는 산업 데이터를 위한 spBv1.0/... 페이로드 명세로, 각 측정값과 그 타임스탬프, 그 품질이 그 본문 안에 어떻게 담기는지를 표준화합니다)를 받아 메트릭 하나를 골라내 히스토리안에 행을 삽입하는 최소 플로우는 다음과 같습니다 — 이것은 현실적인 Node-RED 플로우 익스포트로, 컴패니언 저장소에서 테스트된 산출물은 아닙니다. 이런 게이트웨이는 core 프로파일이 아니라 옵트인(opt-in) 프로파일 뒤에서 실행됩니다:
// edge/node-red/flows.json (realistic config — capture profile, not in the core compose)
[
{ "id": "mqtt-in", "type": "mqtt in", "topic": "spBv1.0/newark/DDATA/edge1/BR101",
"qos": "1", "broker": "mosquitto", "wires": [["to-row"]] },
{ "id": "to-row", "type": "function", "name": "sparkplug -> sensor_reading",
"func": "const m = msg.payload.metrics[0];\nmsg.payload = {\n ts: new Date(m.timestamp).toISOString(),\n tag: m.name, value: m.value, unit: m.properties.unit,\n quality: m.is_null ? 0 : 192, batch_id: flow.get('batch_id')\n};\nreturn msg;",
"wires": [["to-pg"]] },
{ "id": "to-pg", "type": "postgresql", "name": "ts.sensor_reading",
"query": "INSERT INTO ts.sensor_reading (ts,tag,value,unit,quality,batch_id) VALUES ($1,$2,$3,$4,$5,$6)" }
]
(quality: m.is_null ? 0 : 192 줄은 히스토리안의 레거시 OPC DA 관례 — 192 Good, 0 Bad — 를 사용하며, 이는 "데이터가 도착하는 곳"에서 온전히 풀어 설명합니다.)
Node-RED의 강점은 동시에 한계이기도 합니다. 기본 인증과 얇은 권한 모델만 제공하므로, GxP 환경(GxP는 규제 대상 업무를 다스리는 "good practice" 규정들의 총칭 — 제조의 GMP, 실험실의 GLP, 임상의 GCP)에서는 엔터프라이즈 애드온 없이는 누가 플로우를 바꿨는지 증명하거나 세밀한 역할을 부여할 수 없습니다. 우리는 이를 프로토타이핑 및 가벼운 접착(glue) 계층으로 취급하며, 그 점을 분명히 밝혀 둡니다.
Telegraf — 선언적 수집
Node-RED가 상호작용형이라면, Telegraf는 그 정반대입니다. 전적으로 TOML 파일로 구성되는 단일 Go 바이너리로, 입력(inputs)·프로세서(processors)·애그리게이터(aggregators)·출력(outputs)이라는 플러그인 모델을 선언적으로 조합합니다 [5]. 캔버스도 없고 클릭도 없습니다. 구성을 작성하고 버전 관리하면, 에이전트는 매번 정확히 파일이 말하는 대로 합니다. 그 결정성(determinism)이야말로 꾸준하고 높은 빈도의 메트릭 수집에서 바라는 바입니다.
브로커의 UNS 토픽을 소비해 PostgreSQL/TimescaleDB로 곧장 쓰는 Telegraf 구성은 짧습니다 — 역시 현실적인 capture 프로파일 산출물이며, 그렇게 표기되어 있습니다:
# edge/telegraf/telegraf.conf (realistic config — capture profile)
[agent]
interval = "5s"
flush_interval = "5s"
omit_hostname = true
[[inputs.mqtt_consumer]]
servers = ["tcp://mosquitto:1883"]
topics = ["newark/+/+/+"] # UNS path: site/area/asset/measurement (Ch.5); + matches one level
data_format = "json"
json_time_key = "ts"
json_time_format = "2006-01-02T15:04:05Z07:00" # Go's reference-time layout, not a literal date: it just spells out the ISO-8601 timestamp shape
[[outputs.postgresql]]
connection = "host=postgres user=bioproc password=bioproc dbname=bioproc"
table_template = "INSERT INTO ts.sensor_reading (ts,tag,value,unit,quality,batch_id) VALUES (...)"
그 단순함의 대가는 Telegraf가 수집하고 전달할 뿐, 메시지별 감사 추적은 제공하지 않는다는 것입니다. 파싱할 수 없는 메시지를 충실히 버리고 넘어갑니다. 스택 상태(health) 모니터링에는(운영 장에서 정확히 그 용도로 재사용합니다) 이상적입니다. 규제 배치 기록(batch record)에 대해서는 수집기(collector)이지 진실의 기록 시스템(system of record)은 아닙니다.
Apache NiFi — 보장된 전달과 재현 가능한 출처 정보
세 번째 도구는 데이터가 규제 대상일 때 제값을 하는 도구입니다. Apache NiFi는 데이터를 FlowFile로 삼아 프로세서들의 방향 그래프(directed graph)를 통해 라우팅하며, 자신이 생성·분기·복제·수정·전송하는 모든 FlowFile에 대해 질의하고 재현(replay)할 수 있는 저장소에 출처 이벤트(provenance event)를 기록합니다 [6]. 이것이 어떤 오픈소스 엣지 도구가 종단 간(end-to-end) 데이터 흐름 감사 추적에 가장 가까이 다가간 모습입니다. 사후에 NiFi에 "이 레코드의 계보(lineage)를 보여 줘"라고 물으면, 누가/무엇이/언제/무엇으로부터를 재구성해 줍니다 — W3C PROV 온톨로지(몇 가지 핵심 종류의 사물과 그들이 서로 관계 맺는 방식을 못박은, 공유되는 기계 판독 가능 어휘)가 엔터티(entities), 활동(activities), 에이전트(agents)로 정의하는 바로 그 형태입니다 [7]. 그 기록이 몇 달 뒤에도 방어 가능해야 할 때, 그 관리 연속성(chain of custody)이 바로 그 기능입니다.
NiFi의 더 가벼운 형제인 MiNiFi는 같은 발상을 소스까지 밀어 내립니다. "정보의 완전한 관리 연속성을 갖춘 데이터 출처의 생성"을 위해 명시적으로 설계된, 디바이스 바로 옆에서 동작하는 작은 풋프린트(footprint)의 에이전트입니다 [8]. 스키드 옆의 제약된(constrained) 엣지 박스에서 MiNiFi가 수집하고 출처를 찍은 뒤, 중앙 NiFi로 넘깁니다.
대가는 무게입니다. NiFi는 자체 출처 저장소와 콘텐츠 저장소를 갖춘 JVM(Java 가상 머신, Java Virtual Machine) 애플리케이션(Java 21, RAM 약 2 GB)이며, 그래서 이런 게이트웨이는 항상 켜져 있는 core 스택이 아니라 옵트인 프로파일 뒤에 자리합니다 — 이 게이트웨이 구성들은 예시일 뿐이고, 컴패니언 저장소의 capture 프로파일은 그 자리에 더 가벼운 OPC UA 서버+수집기 미러를 출하합니다. 그 출처 정보는 nifi.properties에서 구성되며, 관련 행 — 현실적인 구성이며 core 프로파일 산출물은 아닙니다 — 은 다음과 같습니다:
# edge/nifi/nifi.properties (realistic config — capture profile, provenance enabled)
nifi.provenance.repository.implementation=org.apache.nifi.provenance.WriteAheadProvenanceRepository
nifi.provenance.repository.directory.default=./provenance_repository
nifi.provenance.repository.max.storage.time=180 days
nifi.provenance.repository.indexed.fields=EventType, FlowFileUUID, filename, ProcessorID
max.storage.time을 보관 요건에 맞춰 설정하면, NiFi는 그 기간 동안 자신이 다룬 모든 FlowFile에 대한 재현 가능한 기록을 유지합니다.
데이터가 도착하는 곳
어느 도구가 라우팅하든, 데이터는 같은 대상으로 수렴합니다. 공유 플랫폼 스키마에 정확히 한 번 정의되어 배치 모델과 조인되는 히스토리안 하이퍼테이블(hypertable, 시간으로 자동 파티셔닝되는 TimescaleDB의 테이블로, 빠른 시계열 질의를 위해 하나의 논리 테이블이 시간 청크로 투명하게 쪼개집니다) ts.sensor_reading입니다. 대상 데이터베이스 자체는 항상 켜져 있는 core 프로파일로 올라옵니다. examples/platform/compose/compose.yaml에서:
# examples/platform/compose/compose.yaml
services:
postgres:
# timescale/timescaledb IS PostgreSQL + TimescaleDB, so the historian
# hypertable and the ISA-88/95 batch model live in one joinable database.
image: timescale/timescaledb:2.17.2-pg17
profiles: ["core"]
environment:
POSTGRES_USER: ${POSTGRES_USER:-bioproc}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-bioproc}
POSTGRES_DB: ${POSTGRES_DB:-bioproc}
ports: ["5432:5432"]
volumes:
- pgdata:/var/lib/postgresql/data
- ../db:/docker-entrypoint-initdb.d:ro # 00-60 schema files run on first init
mosquitto:
image: eclipse-mosquitto:2.0.22
profiles: ["core"]
ports: ["1883:1883"]
주석에 담긴 의도적 선택에 주목하세요. TimescaleDB는 곧 PostgreSQL이므로, 높은 빈도의 센서 이력과 ISA-88/95 배치 맥락(배치·장비 데이터 모델에서 구축하는 레시피-장비 모델)이 조인할 수 있는 하나의 데이터베이스에 함께 삽니다. 게이트웨이의 유일한 임무는 깨끗한 행을 안착시키는 것이며, 의미는 조인에서 나옵니다. 그 조인은 이후 장들이 만들어 갑니다.
게이트웨이가 만들어 내는 행은 더없이 단순합니다. 롱 포맷, 행당 측정값 하나입니다. 다음은 examples/datasets/fedbatch_timeseries_10min.sample.csv에서 가져온 실제 형태입니다:
ts,tag,value,unit,quality,batch_id
2026-01-05 00:00:00+00:00,BR101.Agitation.PV,81.4323,rpm,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.Agitation.SP,81.6008,rpm,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.DO.PV,40.8224,%sat,192,BATCH-2026-001
저 quality 열은 장식이 아닙니다. 192는 현장 쪽이 그대로 실어 나르는 레거시 OPC DA(Classic) Good 코드(0xC0, 16진수로 192)입니다. OPC DA(Classic)는 OPC 표준의 OPC UA 이전 세대로, 설치된 많은 디바이스가 여전히 그 상태 코드를 내보냅니다. 충돌에 주의하세요. 7장에서 보았듯 OPC UA 네이티브 품질은 Good이 0인 다른 StatusCode입니다 — 그래서 0은 OPC UA에서는 Good이지만 OPC DA에서는 Bad를 뜻합니다. 우리 히스토리안은 OPC DA 관례(192 Good)를 저장하고, 게이트웨이는 그 품질 플래그를 손대지 않고 그대로 실어 날라, 대시보드나 검토자가 나중에 진짜 판독값과 불확실하거나 나쁜 판독값을 구분할 수 있게 합니다. 그 필드를 엣지에서 버리는 것은 전형적인 데이터 무결성 자책골입니다 — 모든 값이 똑같이 신뢰할 만해 보이도록 소리 없이 만들어 버린 셈이죠.
sensor_reading 행 해부: 게이트웨이의 출력 계약
저 CSV 한 줄이 곧 게이트웨이의 산출물 전부이며, 단 하나의 행으로 압축되어 있습니다. 어느 도구가 라우팅하든 그것이 충족해야 하는 계약은 히스토리안 스키마로 고정되어 있고, examples/platform/db/20-historian.sql에 정확히 한 번 정의됩니다:
-- examples/platform/db/20-historian.sql
CREATE TABLE ts.sensor_reading (
ts timestamptz NOT NULL,
tag text NOT NULL,
value double precision,
unit text,
quality smallint NOT NULL DEFAULT 192, -- legacy OPC DA: 192 Good, 64 Uncertain, 0 Bad
batch_id text
);
여섯 개의 열, 그리고 그 하나하나가 제자리를 얻습니다. 필드별로 읽으면, 이것이 플랫폼이 신뢰하는 가장 작은 증거 단위입니다:
ts(timestamptz NOT NULL) — 동시대적(contemporaneous) 소스 타임스탬프로, ALCOA+의 "C"(Contemporaneous)와 "O"(Original) — 데이터 무결성 체크리스트(Attributable, Legible, Contemporaneous, Original, Accurate, 더해서 Complete, Consistent, Enduring, Available) — 입니다. 삽입된 시점이 아니라 값이 측정된 시점이며, OPC UA 서버나 브로커가 찍습니다. 또한 TimescaleDB 하이퍼테이블의 파티션 키이므로 결코 선택적이지 않습니다.tag(text NOT NULL) — 신호의 정규(canonical) 식별자, 예컨대BR101.DO.PV입니다. 게이트웨이의 변환 단계가 벤더 태그를 이asset.measurement.role형태로 다시 써서, 같은 신호의 두 소스가 하나의 이름으로 수렴하게 합니다..PV는 공정값(측정된 증거),.SP는 레시피 설정값 — 배치 레시피(ISA-88 의미의, 정의되고 매개변수화된 생산 절차)가 제어기에게 유지하라고 명령하는 목표값 — 으로, 같은 측정값의 다른 역할이며, 위 CSV는 둘을 별개의 행으로 보여 줍니다.value(double precision, nullable) — 숫자 그 자체일 뿐 그 이상이 아닙니다. 예컨대 위의BR101.DO.PV판독값40.8224는 40 %sat 설정값 근처로 유지되는 용존산소(dissolved oxygen)로 — 산소 독성 없이 배양을 호기성으로 유지하는 표준 CHO(Chinese Hamster Ovary, 차이니즈 햄스터 난소 세포, 지배적인 mAb 생산 세포주) 목표값이며 — 불투명한 부동소수점 수가 아닙니다. 일부러 nullable입니다. 탈락한 센서는 nullvalue와 Good이 아닌quality를 가진 행을 만들어 내며, 이는 행이 아예 없는 것보다 더 정직합니다.unit(text, nullable) —40.8224가 무엇을 뜻하는지입니다.%sat,rpm,kg,degC— 맨숫자는 측정값이 아닙니다. 게이트웨이는 정규화 중 누락된 단위를 채워, 히스토리안이 모호한 값을 결코 저장하지 않게 합니다.quality(smallint NOT NULL DEFAULT 192) — 신뢰 플래그이며, 가장 자주 버려지는 필드이기에 바로 그 이유로 행에서 가장 중요한 필드입니다. 스키마 자체의 주석이 이를 매핑합니다.192Good,64Uncertain,0Bad — 7장에서 확립했듯, 엣지 노드가 그대로 실어 나르는 레거시 OPC DA(Classic) 상태 코드입니다 [3].NOT NULL DEFAULT 192는 의도적이고 다소 위험한 선택입니다. quality를 생략한 부주의한 로더가 공짜로 "Good"을 얻는다는 뜻이므로, 실제 상태를 변환 단계까지 실어 나르는 규율은 데이터베이스가 아니라 게이트웨이의 몫입니다.batch_id(text, nullable) — 조인 키입니다. 행은 그 자체로는 시간 위의 숫자이지만,batch_id가 붙으면 특정 GMP(Good Manufacturing Practice, 우수 제조 관리 기준) 배치에 관한 증거가 되어, 같은 데이터베이스의 ISA-88/95 모델에 조인됩니다. 배치 사이에 취한 판독값(CIP 사이클, 유휴 스키드)도 여전히 실재하고 보관할 가치가 있으므로 nullable입니다. 또한 이것은 이 장이 포착하는 상류 신호를, 한 로트에 정체성을 부여하는 하류 정제 기록에 꿰매는 것이기도 합니다. 이 생물반응기 행들을 만들어 낸 바로 그BATCH-2026-001이 Protein A 캡처, 저(低)pH 바이러스 불활성화, 폴리싱, 바이러스 여과, UF/DF로 이어집니다 — 바이오의약품 제조가 캡처 크로마토그래피부터 UF/DF와 원료의약품까지 걸어가는 정제 트레인입니다. 게이트웨이는 크로마토그래피 컬럼이나 바이러스 필터를 결코 보지 못하지만, 처리 중 스트림에 같은batch_id를 찍음으로써 생물반응기 추세, 캡처 용출액(eluate) 역가, 최종 출하 패널을 조인할 수 있는 하나의 실로 엮습니다 — 이는 이후의 맥락화 및 지식 그래프 장들이 걸어가는 바로 그 계보입니다.
없는 것에 주목하세요. 대체 키(surrogate primary key)가 없습니다. 판독값의 정체성은 (ts, tag) 쌍이며, 그것이 바로 QoS 1(MQTT의 at-least-once 전달로, 메시지를 두 번 전달할 수 있습니다 — 아래 "전달 의미론"에서 정의합니다)의 중복 제거 전략 — "두 번 받고 한 번 보관" — 이 어떤 자동 증가 id가 아니라 (ts, tag)에 대한 ON CONFLICT나 dedup인 이유입니다. 롱 포맷(센서별 열의 넓은 테이블이 아니라 행당 측정값 하나)은 또 하나의 의도적 결정입니다. 새 태그는 그저 더 많은 행일 뿐 스키마 마이그레이션이 아니므로, 게이트웨이는 누구도 히스토리안의 DDL을 건드리지 않고 처음 보는 센서를 온보딩할 수 있습니다.
히스토리안 한 행을 필드별로: 게이트웨이의 출력 계약은 여섯 개의 열이며,
quality 플래그가 가장 자주 버려지는 필드입니다.
저자가 AI의 도움을 받아 직접 제작한 그림입니다.
이 여섯 개 열의 행은 다른 두 책이 들려주는 이야기를 오픈소스로 구현한 것입니다. 바이오의약품 제조에서는 같은 종류의 추적이 콜드체인 유통(distribution) 중 바이알을 따라다니는 데이터 로거 기록 — 측정되는 물리적 사건 — 입니다. 바이오의약품 제조에서의 데이터 관리에서는 그 측정값이 소스에서 태어난 데이터 포인트로, 그 모든 무결성의 함의와 함께 계측기와 센서에서 다뤄집니다. 그곳에서 물리적 판독값이었고 또 그곳에서 라벨링된 데이터 포인트였던 것이, 여기서는 게이트웨이가 완전하고 quality 플래그가 붙은 상태로 착지시켜야 하는 구체적인 ts.sensor_reading 행입니다.
컴패니언 저장소에서는 7장부터 16장까지가 이 수집 경로를 한 조각씩 쌓아 올립니다. 이후의 맥락화(contextualization)와 ALCOA+ 장들이 곧바로 질의할 대상을 갖도록, 저장소는 전체 적재를 한 번에 수행하는 스크립트 하나도 함께 출하합니다 — examples/tools/load_datasets.py입니다. 그 시계열 로더는 교과서적인 대량 수집(bulk ingest)으로, 운영용 게이트웨이가 노스바운드로 묶어 보내는 바로 그 모습입니다:
# examples/tools/load_datasets.py
def load_timeseries(conn) -> int:
df = pd.read_parquet(DATA / "fedbatch_timeseries.parquet")
buf = io.StringIO()
df[["ts", "tag", "value", "unit", "quality", "batch_id"]].to_csv(buf, index=False, header=False)
buf.seek(0)
with conn.cursor() as cur:
cur.execute("TRUNCATE ts.sensor_reading")
with cur.copy("COPY ts.sensor_reading (ts, tag, value, unit, quality, batch_id) "
"FROM STDIN WITH (FORMAT csv)") as copy:
copy.write(buf.read())
return len(df)
같은 스크립트는 오프라인 실험실 데이터를 다른 스키마로 라우팅하기도 하며, 그 세부는 게이트웨이 이야기에서 중요합니다. 오프라인 실험실 경로는 사람 행위자(app.user)를 이름 붙일 수 있어 귀속 가능(attributable)한 반면, 높은 빈도의 게이트웨이 센서 스트림은 그렇지 않습니다 — 바로 그래서 규제 감사 추적이 게이트웨이가 아니라 실험실·배치 테이블에 붙는 것입니다. 쓰기 전에 귀속 가능한 행위자를 찍는다는 점에 주목하세요 — set_config('app.user', 'loader', ...) — 덕분에 데이터베이스의 감사 트리거(트리거는 모든 삽입이나 갱신마다 데이터베이스가 자동으로 실행하는 작은 루틴으로, 이 트리거는 누가-무엇을 항목을 감사 추적에 기록합니다)가 누가 그 행을 들여왔는지 기록합니다:
# examples/tools/load_datasets.py
def load_offline(conn) -> int:
df = pd.read_csv(DATA / "offline_assays.csv", parse_dates=["sample_time"])
n = 0
with conn.cursor() as cur:
cur.execute("SELECT set_config('app.user', 'loader', false)")
for _, r in df.iterrows():
cur.execute(
"INSERT INTO lab.sample (sample_id, batch_id, sample_time, sample_point, sample_type) "
"VALUES (%s,%s,%s,%s,'in_process') ON CONFLICT (sample_id) DO NOTHING",
(r.sample_id, r.batch_id, r.sample_time.to_pydatetime(), r.sample_point))
...
return n
전체는 책이 그대로 인쇄하는 make 타깃 하나로 실행합니다:
make load # load the datasets into the running stack (historian + lab + genealogy)
# -> loaded: 322560 sensor readings, 1344 offline results, 66 release results, 30 genealogy edges
솔직한 부분: 전달 의미론과 감사 공백
여기서부터는 파는 일을 멈추고 고백을 시작합니다. 게이트웨이의 가장 중요한 약속은 완전성(completeness) — 현장이 만들어 낸 모든 측정값이 실제로 도착했다는 것 — 입니다. 그것이 ALCOA+의 "C"(Complete)이며, MHRA(영국 의약품 규제 기관)의 데이터 무결성 지침은 이를 명시적으로 거론합니다. 데이터는 완전해야 하며, 소리 없이 버려지는 것이 없어야 합니다 [9]. FDA의 cGMP(current Good Manufacturing Practice, 현행 우수 제조 관리 기준 — 미국 GMP 규정) 데이터 무결성 Q&A는 반대편에서 같은 요구를 합니다. 모든 cGMP 데이터는 완전하고, 신뢰할 수 있으며, 정확해야 합니다 [10].
전달 의미론: QoS와 유실이 실제로 일어나는 곳
엣지에서의 완전성은 결국 MQTT 서비스 품질(Quality of Service, QoS)로 귀결됩니다. MQTT 명세는 세 단계를 정의합니다. QoS 0(at-most-once — 쏘고 잊기, 메시지가 유실될 수 있음), QoS 1(at-least-once — 전달은 보장되나 중복 가능), QoS 2(exactly-once — 네 단계 핸드셰이크를 대가로 보장되고 중복 제거됨) [11]. 위의 Node-RED 플로우는 일부러 "qos": "1"로 설정했습니다. 바이오공정 데이터에서는 측정값을 한 번 잃느니 차라리 두 번 받아 (ts, tag)로 중복 제거하는 편이 낫기 때문입니다. 대역폭을 아끼려고 QoS 0을 고르는 것은, 규제 맥락에서는 자신의 기록을 불완전하게 만들기를 선택하는 셈입니다.
하지만 QoS는 전송만 — 발행자에서 구독자로 가는 단 하나의 홉만 — 보호합니다. 변환이 예외를 던지거나, 디스크가 가득 차거나, 프로세스가 흐름 중간에 재시작될 때 게이트웨이 내부에서 무슨 일이 일어나는지에 대해서는 아무 말도 하지 않습니다. 바로 그것이 함정입니다. 메시지 하나를 현장에서 히스토리안까지 추적하면 공백이 분명해집니다. QoS는 브로커-게이트웨이 홉을 지키지만, 브로커 위쪽의 모든 것 — 정규화 단계, 게이트웨이 내부 버퍼, 노스바운드 삽입 — 은 그것이 보호하지 않습니다. QoS 1로 완벽하게 도착한 메시지가 그다음 변환 예외에서 죽으면, 완전히 깨끗한 전송 로그를 남긴 채 유실됩니다.
바로 여기가 Node-RED와 Telegraf가 부족하고 NiFi가 빛나는 지점입니다. NiFi의 FlowFile 모델은 트랜잭션적이고 그 출처 저장소가 모든 메시지의 운명을 기록하므로, 버려지거나 다시 라우팅된 레코드가 소리 없이 사라지는 대신 보이고 재현 가능합니다 [6]. 감사받을 수 있는 기록에서 "다 통과한 것 같아요"는 답이 아닙니다. "모든 FlowFile에 대한 출처 이벤트가 여기 있고, 데드레터(dead-letter) 관계에 실패가 0건입니다"가 답입니다.
출처 이벤트 해부 (그리고 그것이 감사 추적이 아닌 이유)
NiFi가 FlowFile을 생성·분기·복제·수정·전송할 때, nifi.properties에 구성된 저장소에 출처 이벤트(provenance event) 하나를 기록합니다. 각 이벤트는 W3C PROV 온톨로지가 정의하는 세 가지 질문 — 엔터티, 활동, 에이전트 — 에 더해 PROV-O가 시간 속성으로 기록하는 언제에 답하며, 하나를 해부할 가치가 있습니다. 그 형태가 감사 추적에 거의 가깝고, 그 "거의"와 "그것이다" 사이의 간극이 곧 준수(compliance) 이야기 전체이기 때문입니다. 단일 RECEIVE/ROUTE/SEND 이벤트는 PROV-O 용어로 다음을 기록합니다 [7]:
- 엔터티(entity) — FlowFile 그 자체: UUID(
FlowFileUUID, 색인된 필드 중 하나), 파일명, 크기, 그리고 그 순간에 붙은 속성들. 이벤트가 다루는 대상, 즉 우리BR101.DO.PV판독값을 실은 메시지입니다. - 활동(activity) — EventType:
RECEIVE,ROUTE,CLONE,SEND,DROP등과, 그것을 수행한ProcessorID. 엔터티에 무슨 일이 일어났는지입니다. - 에이전트(agent) — 프로세서/컴포넌트: 어느 NiFi 프로세서가 책임을 졌는지. 이것은 사람이 아니라 소프트웨어 에이전트임에 주목하세요.
- 시간(time) — 언제로, PROV-O가 (네 번째 클래스가 아니라) 시간 속성으로 기록합니다: 이벤트 타임스탬프이며,
max.storage.time(우리 구성에서 180일) 동안 보관되고, 이 FlowFile이 파생된 원본 FlowFile로 거슬러 가는 계보 링크를 함께 가집니다.
형식적인 PROV-O 모델 그 자체 — 그리고 같은 prov:Activity/prov:Entity 패턴이 owl:sameAs 과병합 없이 서로 충돌하는 두 소스 주장을 어떻게 조화시키는지 — 는 바이오의약품 제조를 위한 온톨로지의 식별자와 단위와 유지보수: 발행과 FAIR를 참조하세요.
이 "거의-감사" 매핑을 RDF로 쓰면 그것은 비유가 아니라, 한 이벤트에 관한 네 개의 트리플이며, 그 트리플이 다루는 엔터티가 바로 게이트웨이 자신의 행입니다:
# the provenance event, as PROV-O triples (illustrative)
ex:event-7c3 a prov:Activity ; # the EventType (RECEIVE / ROUTE / SEND)
prov:used ex:flowfile-9af ; # the FlowFile carrying the BR101.DO.PV reading
prov:wasAssociatedWith ex:nifi-PutDatabaseRecord ; # a SOFTWARE agent, not a person
prov:endedAtTime "2026-01-05T00:00:00Z"^^xsd:dateTime .
ex:nifi-PutDatabaseRecord a prov:SoftwareAgent . # the gap: never a prov:Person
저 prov:SoftwareAgent 타이핑이 한 트리플 안에 담긴 준수 이야기 전체입니다. Part 11 감사 추적은 prov:Person(사람인 누가)과 사유(왜)가 필요한데, 출처 그래프는 구조적으로 그 둘 모두를 갖지 않습니다. 같은 모델링 패턴을 쓰면 게이트웨이의 출력 계약을 기계 검증 가능한 게이트로 바꿀 수 있습니다. "출하된 배치의 모든 판독값이 Bad가 아닌 품질 플래그를 달고 도착했는가?"라는 폐쇄세계(closed-world) 질문은 바로 SHACL(Shapes Constraint Language — 누락된 값이나 범위를 벗어난 값에서 실패하는 폐쇄세계 검증기로, 개방세계 OWL 추론기와 다릅니다) 셰이프가 표현하는 형태이며, 이는 바이오의약품 제조를 위한 온톨로지의 검증: 출하 게이트와 SHACL에서 구축하는 종류입니다:
# a SHACL shape over the gateway's row contract (illustrative)
ex:SensorReadingShape a sh:NodeShape ;
sh:targetClass ex:SensorReading ;
sh:property [ sh:path ex:quality ; sh:minCount 1 ;
sh:in ( 192 64 ) ; # Good or Uncertain — a Bad/0 row fails the gate
sh:message "Reading is missing its quality flag or is flagged Bad." ] ;
sh:property [ sh:path ex:ts ; sh:minCount 1 ; sh:datatype xsd:dateTime ] .
그리고 이 batch_id가 가능하게 하는 계보 추적 — "모든 판독값을 그것이 유래한 셀 뱅크까지 거슬러 추적하라" — 은 ex:derivedFrom에 대한 SPARQL 역량 질문(competency question)이며, 이는 바이오의약품 제조를 위한 온톨로지가 개념화: 관계와 계보에서 구축하고 이 책이 시맨틱과 디지털 스레드에서 살아 있는 그래프로 적재하는 계보 척추입니다. 게이트웨이는 행을 방출하고, 온톨로지 계층은 규제 기관이 그 행들에 질문을 던지고 증명 가능한 답을 얻게 하는 것입니다.
이를 데이터베이스의 감사 행, 즉 examples/platform/db/50-alcoa.sql의 audit.change_log 옆에 나란히 놓으면, 빠진 필드가 도드라집니다:
-- examples/platform/db/50-alcoa.sql (규제 감사 추적 — 대조용)
CREATE TABLE audit.change_log (
seq bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ts timestamptz NOT NULL DEFAULT clock_timestamp(),
db_user text NOT NULL DEFAULT current_user,
app_user text, -- WHO (SET app.user 로 설정)
table_name text NOT NULL,
action text NOT NULL,
row_key text, -- 어느 행인지(변경된 레코드의 식별자)
old_row jsonb, -- 변경 BEFORE
new_row jsonb, -- 변경 AFTER
reason text, -- WHY (SET app.reason 로 설정)
prev_hash text, -- 변조 증거: 이전 행에 체이닝됨
row_hash text NOT NULL
);
출처 이벤트는 엔터티, 활동, 소프트웨어 에이전트, 시간을 가집니다. Part 11(Part 11 = FDA 21 CFR Part 11, 신뢰할 수 있는 전자 기록 및 전자 서명에 대한 미국 규칙: 누가 무엇을 언제 왜 바꿨는지에 대한 불변의 감사 추적) 감사 행은 여기에 더해 사람인 app_user(누가), reason(왜), 규제 대상 값이 변경되는 old_row/new_row 전·후, 그리고 변조를 분명히 드러내는 prev_hash 체인을 가집니다. 출처 이벤트는 데이터가 흘렀음을 말해 주지만, 사람이 통제된 값을 바꿨다는 것도, 왜 바꿨는지도, 사후에 레코드가 편집되지 않았음도 입증하지 않습니다. 그래서 우리는 분명히 말합니다. 출처 정보는 훌륭한 데이터 흐름 계보이자 멍청한 수집기 대비 진짜 차별점이지만, 규제 감사 추적은 아닙니다. 감사 추적은 데이터베이스 안에, 트리거로 lab.result, s88.batch, s88.recipe_parameter에 붙어 살며 — 결코 게이트웨이에 붙지 않습니다.
그리고 NiFi의 출처 정보조차 Part 11 감사 추적이 아닙니다. 그것은 데이터 흐름이 무엇을 했는지를 말해 줄 뿐, 규제 대상 값이 변경된 데 대한 불변·서명된, 누가·왜·어떤 사유로의 기록을 그 자체로 제공하지는 않습니다. 그것은 데이터베이스의 몫입니다 — 신뢰(trust) 장들에서 우리가 구축하는 시스템 버전 관리 이력과 해시 체인(hash chain)이지, 게이트웨이의 몫이 아닙니다. 솔직한 역할 분담은 이렇습니다. 게이트웨이는 데이터가 도착함을 보장하고 어떻게 흘렀는지를 기록합니다. 히스토리안과 배치 모델은 검증되고, 감사 추적되며, 서명 가능한 진실의 기록이 됩니다. 어떤 엣지 도구도 그 자체로 준수(compliant) 기록은 아닙니다.
모델 계층이 어떤 도구를 골랐는지 신경 쓰는 이유
이것은 게이트웨이 장이므로, 모델링의 함의는 페이로드가 아니라 전제입니다 — 그러나 그 전제는 "모델에는 좋은 데이터가 필요하다"보다 더 날카롭습니다. 게이트웨이의 설계 선택 중 세 가지가 학습된 모델이 유효한지 여부에까지 곧장 닿으며, 그 연결을 이름 붙일 가치가 있습니다. 그러지 않으면 소프트 센서가 이미 운영에 들어간 뒤에야 그 실패가 보이기 때문입니다.
첫째, batch_id는 단순한 조인 키가 아니라 모델이 분할(split)해야 하는 그룹화 키입니다. 이 히스토리안으로 학습한 소프트 센서는 배치 전체를 홀드아웃(hold out)해 검증해야 합니다 — scikit-learn의 GroupKFold / LeaveOneGroupOut을, 게이트웨이가 찍은 바로 그 batch_id로 그룹화합니다 — 같은 런에서 한 시간 떨어진 두 BR101.DO.PV 행은 거의 중복이고, 그것을 학습/시험 경계에 흩뿌리면 모델이 배치 내부의 이웃을 암기하고 환상적인 R²를 보고하기 때문입니다. 게이트웨이가 batch_id를 떨어뜨리거나 잘못 라벨링하면, 그 누수 없는 분할은 소스에서부터 불가능해집니다. 바이오의약품 제조를 위한 머신러닝과 AI는 이를 책 전체의 첫 교훈으로 삼습니다 — 학습 문제와 모델과 검증의 그룹화된 누수-인지 분할기를 참조하세요.
둘째, quality 플래그는 장식이 아니라 적용 범위(applicability domain) 신호입니다. 모델은 자신의 학습 세트를 닮은 입력에 대해서만 신뢰할 수 있습니다. quality = 0(Bad)이나 64(Uncertain)로 도착하는 행은 모델이 소리 없이 점수 매기기보다 거부해야 하는 바로 그 입력입니다. 엣지에서 플래그를 손대지 않고 그대로 실어 나르는 것이, 하류 센서가 — 모델과 검증 장이 구축하는 Hotelling T²/SPE 적용 범위 점검으로 — 오염된 프로브에서 자신 있게 외삽하는 대신 자신의 예측을 게이팅하게 해 줍니다. 게이트웨이에서 플래그를 버리면 모든 판독값이 똑같이 적용 범위 안에 있어 보이는데, 이는 한 계층 위에서 벌어지는 데이터 무결성 자책골과 같습니다.
셋째, 드리프트 탐지는 이 장이 지키는 완전성 위에서 흥하거나 망합니다. 모델이 보는 입력 분포 P(X)를 지켜보는 모집단 안정성 지수(PSI) 모니터는, 진짜 공정 드리프트(새 원료 로트, 스케일 이동)와 데이터 드리프트(QoS 0 하에서 게이트웨이가 소리 없이 샘플을 떨어뜨리거나, 변환 예외가 스트림을 얇게 만드는 것)를 게이트웨이의 기록이 실제로 완전할 때에만 구분할 수 있습니다. 불완전한 피드는 건강한 공정을 드리프트하는 것처럼, 드리프트하는 공정을 건강한 것처럼 보이게 만듭니다. MLOps 루프 전체 — 드리프트 트리거, 잠긴 모델 재학습, 롤백 — 가 이 게이트웨이가 얻거나 깨는 가정 위에 놓입니다. 바이오의약품 제조를 위한 머신러닝과 AI가 그 루프를 MLOps와 생애주기에서 구축합니다. 인프라가 먼저라는 것은 여기서 구호가 아니라, 모델이 검증 가능하기 위한 문자 그대로의 전제 조건입니다.
왜 중요한가
하류의 모든 것 — 대시보드, 맥락화, 지식 그래프, 소프트 센서(soft-sensor), 감사 추적 검토 — 은 데이터가 완전하고 올바르게 라벨링되어 도착했다고 가정합니다. 엣지 게이트웨이는 그 가정이 얻어지거나 조용히 깨지는 곳입니다. 품질 플래그를 버리거나, QoS 0을 고르거나, 관리 연속성이 없는 수집기를 쓰면, 방어할 수 없는 데이터 위에 아름다운 플랫폼을 쌓을 수 있습니다. 각 작업에 맞는 올바른 도구를 고르세요 — 프로토타이핑에는 Node-RED, 대규모 수집에는 Telegraf, 흐름이 증명 가능해야 할 때는 NiFi — 그러면 책의 나머지는 쌓아 올릴 가치가 있는 기반을 갖게 됩니다.
실제 현장에서는
실제 mAb 라인에서 게이트웨이가 시뮬레이터와 대화하는 일은 드뭅니다. Emerson DeltaV 같은 DCS(Distributed Control System, 분산 제어 시스템 — 공정을 구동하는 공장 전역 제어기)나 Siemens 제어기와 OPC UA로 대화하고, 독립형 스키드와는 Modbus나 S7(Siemens 제어기 프로토콜)로, IT 쪽의 AVEVA PI(벤더 시계열 데이터베이스) 같은 상용 히스토리안과 대화합니다. 그런 시스템들은 노트북에서 돌아가지 않고 라이선스로 잠겨 있는데, 그래서 우리 컴패니언 저장소는 OPC UA 생물반응기를 모킹(mocking)하고 무거운 게이트웨이를 옵트인 프로파일 뒤에 출하합니다 — 통합 코드는 진짜지만, 벤더 고유의 까다로운 부분은 여기서 실제로 다뤄지지 않습니다.
이 계층에 대한 솔직한 OSS 대 상용 평결: 오픈소스 게이트웨이는 가교 작업을 진정으로 잘 해냅니다 — Node-RED, Telegraf, NiFi는 모두 운영 등급(production-grade)이며, NiFi의 출처 정보는 진짜 차별점입니다. 이들이 기본으로 주지 않는 것은 검증된 시스템 래퍼(wrapper)입니다. 벤더 책임성, 턴키(turnkey) Part 11 감사 추적, 적격성 평가된 고가용성(high availability), 그리고 IQ/OQ/PQ 문서 작업 — 설치 적격성 평가(Installation), 운영 적격성 평가(Operational), 성능 적격성 평가(Performance Qualification), 즉 시스템이 요구대로 설치되고 동작하며 성능을 낸다는 문서화된 증거이자, 바이오의약품 제조가 실험실 벤치에서 공장으로와 품질, 규제, 데이터에서 펼쳐 보이는 시설 적격성 평가 및 공정 검증 생애주기 — 이죠. 상용 엣지 플랫폼(Ignition, HighByte, 히스토리안 벤더 자체 커넥터)은 그 래퍼를 팝니다. OSS로도 훨씬 적은 라이선스 비용에 대략 같은 데이터 결과에 도달할 수 있지만 — GxP의 마지막 한 구간(last mile)은 여러분이 검증할 몫이며, 그 작업은 실재합니다.
핵심 용어
- 엣지 게이트웨이(edge gateway) — OT/IT 이음매에 있는 디바이스/소프트웨어로, 현장 프로토콜을 읽고, 데이터를 변환하고, 버퍼링하고, 제어 루프를 바꾸지 않으면서 다음 단계로 라우팅한다.
- OT / IT — 운영 기술(Operational Technology, 격리된 제어 네트워크의 제어기·스키드·센서) 대 정보 기술(Information Technology, 데이터베이스·대시보드·분석).
- 사우스바운드 / 노스바운드(southbound / northbound) — 현장 프로토콜 쪽(디바이스를 향해 아래로) 대 데이터 플랫폼 쪽(IT를 향해 위로)을 가리키는 게이트웨이 용어.
- Node-RED — 브라우저 기반 로우코드 플로우 편집기; 플로우는 JSON으로 저장되고 Node.js 위에서 실행됨; 프로토타이핑에 탁월하나 RBAC은 약함.
- Telegraf — 단일 바이너리, 플러그인 구동, TOML 구성 방식의 수집 에이전트; 결정적이며 메시지별 감사 추적은 없음.
- Apache NiFi / MiNiFi — 재현 가능한 FlowFile 단위 출처 정보(관리 연속성)를 갖춘 JVM 데이터흐름 도구; MiNiFi는 그 작은 풋프린트의 엣지 에이전트.
- 출처 정보 / 데이터 계보(provenance / data lineage) — 데이터 레코드의 누가/무엇이/언제/무엇으로부터 이력을 기록한 것; W3C PROV-O가 표준 어휘(엔터티, 활동, 에이전트).
- QoS (MQTT) — 전달 보장: QoS 0 at-most-once(유실 가능), QoS 1 at-least-once(중복 가능), QoS 2 exactly-once.
- 품질 플래그(quality flag) — 각 값과 함께 실려 소비자가 좋은 판독값과 불확실/나쁜 판독값을 구분하게 해 주는 레거시 OPC DA(Classic) 상태 코드(예:
192= Good;64Uncertain,0Bad). (OPC UA 네이티브 품질은 Good이0인StatusCode입니다. 7장 참조.) sensor_reading행(롱 포맷) — 게이트웨이의 출력 계약:ts, tag, value, unit, quality, batch_id. 행당 측정값 하나이므로 새 태그는 더 많은 행일 뿐 스키마 변경이 아니며, 정체성은(ts, tag)쌍이다.- 출처 이벤트(provenance event, PROV-O) — 엔터티, 활동, (소프트웨어) 에이전트, 시간으로 이루어진 FlowFile 계보 레코드. 데이터가 어떻게 흘렀는지는 증명하지만, 사람인 누가/왜와 변경된 값의 전·후가 없으므로 Part 11 감사 추적이 아니다.
- ALCOA+ "완전성(Complete)" — 어떤 데이터도 소리 없이 유실되지 않도록 요구하는 데이터 무결성 속성; 게이트웨이의 전달 보장이 이를 지킨다.
- 그룹화 분할 / 누수(grouped split / leakage) — 배치 전체를 홀드아웃해(즉
batch_id로 그룹화, 예: scikit-learnGroupKFold) 거의 중복인 배치 내부 행이 학습/시험 경계를 가로지르며 점수를 부풀리지 못하게 하는 모델 검증 방식; 게이트웨이의batch_id가 그 그룹화 키다. - 적용 범위(applicability domain) — 모델이 신뢰받는 입력 영역;
quality플래그가 붙거나 범위를 벗어난 판독값은 적용 범위 밖이므로 점수 매기지 말고 거부해야 하며 — 그래서 게이트웨이가 플래그를 그대로 실어 날라야 한다. - 공정 드리프트 대 데이터 드리프트(process drift vs. data drift) — 공정의 진짜 변화(새 로트, 스케일 이동) 대 게이트웨이 인공물(떨어뜨린 샘플, 변환 예외); 완전성을 보존하는 게이트웨이가 드리프트 모니터로 하여금 둘을 구분하게 해 준다.
- SHACL 셰이프(SHACL shape) — 누락되거나 범위를 벗어난 값에서 실패하는 폐쇄세계 제약(Shapes Constraint Language); 게이트웨이의 행 계약(quality가 존재하며 Bad가 아님,
ts가 타입 지정됨)이 그 위에 곧장 매핑된다.
다음 이야기
이제 게이트웨이는 깨끗하고 품질 플래그가 붙은 스트림을 히스토리안으로 안정적으로 라우팅합니다. 하지만 라우팅은 소스만큼만 좋습니다. 다음 장 상류 포착: 생산 생물반응기(Upstream Capture: The Production Bioreactor)에서는 파이프라인을 공정의 심장 — 유가식(fed-batch) CHO 생물반응기 그 자체 — 으로 겨누고, 14일 배치가 펼쳐지는 동안 그 설정값(setpoints), 공정값(process values), OPC UA 품질 코드, ISA-88 단계(phase) 맥락을 포착하여, 행의 스트림을 살아 있는 배양에 관한 이야기로 바꿔 놓습니다.