본문으로 건너뛰기

DCS·MES·ERP 연동: DeltaV, Siemens, SAP

📍 현재 위치: 제4부 · 현실과 마주하기 — 검증된 히스토리안(historian)과 다리를 놓은 데 이어, 이제 우리는 오픈소스 스택을 그것이 결코 대체하지 못할 세 시스템에 연결합니다. 공장을 돌리는 DCS, 레시피를 실행하는 MES, 그리고 자재와 주문을 소유한 ERP입니다.

쉽게 말하면

병원을 떠올려 보세요. 수술실(DCS)은 환자를 분 단위로 통제합니다. 수술 워크플로 보드(MES)는 어떤 시술이 어느 방에서 일어나는지를 정하고 각 단계가 수행되었음을 서명합니다. 병원의 청구·물품 담당 부서(ERP, 즉 SAP)는 재고, 병상 배정, 그리고 서류를 소유합니다. 아무리 영리한 새 앱이라도 마취를 직접 운용하거나, 수술 기록에 서명하거나, 물품 주문을 발행하게 두지는 않을 것입니다. 그것들은 생명과 기록에 결정적이며 이미 책임 소재가 정해져 있기 때문입니다. 새 앱이 할 수 있는 일은 듣고(수술실이 무엇을 하는지 읽고), 워크플로 보드를 *미러링(mirror)*하여 분석이 볼 수 있게 하고, 공식 우편 투입구를 통해 물품 부서와 메모를 주고받는 것입니다. 이 장은 그 세 개의 청취 초소와 우편 투입구를 만듭니다 — 그리고 여기서 오픈소스가 진정으로 줄 수 없는 한 가지에 대해 솔직합니다. 바로 신뢰할 만한 GxP MES입니다.

이 장에서 다루는 내용

열일곱 개 장에 걸쳐 우리는 완전한 오픈소스 플랫폼을 만들었고, 지난 장에서는 그것이 상용 히스토리안 곁에서 정중하게 공존하도록 가르쳤습니다. 그 히스토리안은 우호적인 상용 시스템이었습니다. 개방형 프로토콜로 말하고, 우리가 이미 이해하는 시계열을 주고받습니다. 이 장은 더 험한 지형으로 들어갑니다. 여기서 정직한 하이브리드(honest-hybrid)의 경계는 취향이 아니라 필연으로 그어집니다.

  • DeltaVSiemens 제어 데이터를, 검증된 제어 루프에 손대지 않고 OPC UA로 OSS 스택에 읽어 들이기.
  • 신뢰할 만한 오픈소스 GxP MES는 존재하지 않는다는 냉정한 결론, 그리고 그것이 여러분의 아키텍처에 의미하는 바.
  • 자재, 로트, **작업 지시(work order)**를 IDoc과 OData 위의 B2MML/ISA-95 메시지로 SAP/ERP와 교환하기.
  • 이 모든 교환이 왜 멱등(idempotent)하고 조정 가능(reconcilable)해야 하는지, 그리고 우리가 이미 PostgreSQL에 만들어 둔 ISA-88/95 모델이 어떻게 그 모든 것의 착륙장이 되는지.

이 모든 것을 하나로 묶는 실은, 어떤 설계 검토에서도 꺼내 들 수 있는 한 문장입니다. 검증된 DCS, MES, ERP는 여전히 기록 시스템(systems of record)으로 남고, 오픈소스 계층은 그것들을 미러링할 뿐 대체하지 않는다 [12].

세 시스템, 세 개의 다른 문

히스토리안에는 문이 두 개 있었습니다. DCS, MES, ERP는 각자 자신의 문을 가지며, 그것들은 서로 호환되지 않습니다. 함정은 이들을 하나의 "통합" 문제로 취급하는 것입니다. 이들은 세 개의 계약, 세 단계의 신뢰 수준, 세 개의 정직한 결론을 가진 세 개의 문제입니다.

각 상용 시스템은 표준 문을 노출하고, OSS 계층은 그 문을 통해 읽어 결과를 ISA-88/95 모델에 착륙시킵니다. 모든 화살표가 미러를 향해 들어옴 에 주목하세요 — 여기서 OSS 계층은 설계상 거의 읽기 전용입니다.

Purdue/ISA-95 레벨은 방향을 자명하게 만듭니다. DCS는 레벨 1-2에, MES는 레벨 3에, ERP는 레벨 4에 자리합니다 [1]. 데이터와 신뢰는 그 검증된 시스템들로부터 우리의 분석 미러로 아래로 흐릅니다 — 거의 결코 그 반대로는 흐르지 않습니다. 그 단 하나의 설계 규칙이 우리를 곤경에서 벗어나게 합니다.

DeltaV와 Siemens: 제어 계층을 OPC UA로 읽기

분산 제어 시스템(distributed control system, DCS)은 바이오리액터(bioreactor) 스위트의 검증된 두뇌입니다 — BR101을 37 °C, pH 7.0으로 유지하는 루프들을 담고 있습니다. 여러분은 그 루프들을 오픈소스로 다시 구현하지 않으며, 파이썬(Python) 스크립트에서 GMP 제어 시스템으로 설정값(setpoint)을 쓰지도 않습니다. 여러분이 하는 일은 읽는 것입니다.

두 주요 벤더 모두 깔끔한 읽기 경로를 건네줍니다. Emerson의 DeltaV는 런타임 파라미터, 알람과 이벤트, 이력 데이터를 OPC UA 서버를 통해 네이티브로 노출하므로, 어떤 표준 OPC UA 클라이언트든 제어 시스템을 변경하지 않고도 DeltaV 주소 공간을 탐색하고 값을 구독할 수 있습니다 [5]. Siemens SIMATIC 제어기도 정확히 같은 방식으로 OPC UA 서버 역할을 하며, 제조사·플랫폼에 독립적이고 보안이 적용된 상위 계층으로의 채널을 제공합니다 [6]. OPC UA 자체가 이를 이식 가능하게 만드는 표준입니다. 플랫폼 독립적이고, 서비스 지향적이며, 보안성을 갖춘 아키텍처로 IEC 62541로 표준화되어 있습니다 [4].

우리 바이오리액터는 이미 OPC UA로 말하므로 — 5장에서 opcua-server를 대상으로 opcua-collector를 만들었습니다 — DCS 다리는 클라이언트 측에서 거의 비용이 들지 않습니다. 오픈소스 SDK들은 성숙해 있습니다. node-opcua(MIT)는 DCS/PLC OPC UA 서버를 탐색하고, 읽고, 쓰고, 구독할 수 있으며 [8], 우리가 이미 쓰는 파이썬 asyncua 라이브러리도 같은 일을 합니다. 이 다리는 모든 포착 장과 동일한 형태입니다. 노드를 구독하고, 값 + 품질 + 타임스탬프를 받아, 한 행을 씁니다.

노트북에서는 DeltaV 서버도 SIMATIC 서버도 돌아가지 않으므로, 동반 저장소는 PI와 SAP에 쓰는 것과 같은 정직한 하이브리드 패턴을 따릅니다. 작은 dcs-mock(commercial 프로파일 뒤에서 DeltaV/PCS7 스타일 노드를 노출하는 asyncua 서버)이 빌드 로드맵에 올라 있어, 라이선스가 걸린 하드웨어가 손닿는 곳에 없어도 다리를 개발하고 계약 테스트(contract-test)할 수 있게 됩니다. 이 글을 쓰는 시점에 저장소는 commercial 프로파일 아래에 pi-web-api-stub을 제공하지만 아직 dcs-mock은 제공하지 않으므로, 아래의 DCS 노드 주소들은 여러분이 고정하고 그것을 향해 빌드하는 *목표 계약(target contract)*으로 다루십시오 — 운영 전환은 서버 URL과 인증서를 바꾸는 일이지 코드를 바꾸는 일이 아닙니다. OPC UA로 읽은 DeltaV 값은, 우리 히스토리안이 이미 저장하는 것과 정확히 같은 형태로 도착합니다(예시적인 OPC UA 읽기 결과, 실제 DeltaV Edge 서버가 지키는 계약):

{ "NodeId": "ns=3;s=BR101/PID-101/PV", "DisplayName": "BR101.Temp.PV",
"Value": 37.02, "StatusCode": "Good", "SourceTimestamp": "2026-01-12T08:30:00Z",
"Unit": "degC" }

그것은 지난 장에서 로더가 사용하는 것을 본 ts.sensor_reading 행 계약(ts, tag, value, unit, quality, batch_id)에 일대일로 매핑됩니다. StatusCode "Good"은 OPC 품질 192가 되며, DCS 태그 BR101.Temp.PV는 이미 4장의 태그 사전이 인식하는 이름입니다. DCS 다리는 의도적으로 이 책에서 가장 덜 흥미로운 코드입니다 — 그리고 바로 그것이 핵심입니다.

다리 코드는 이미 존재합니다 — 작동하는 템플릿으로서. 여러분은 DCS 다리를 상상할 필요가 없습니다. 그것은 우리가 지난 장에서 출하하고 테스트한 PI 다리의 거의 복제본입니다. 그 다리는 examples/chapters/17-bridge-pi-historian/pi_bridge.py에 살고 있으며, examples/tests/test_bridges.pypi-web-api-stub을 대상으로 그것을 작동시킵니다. 그 두 개의 핵심 함수는 DCS 다리가 필요로 하는 바로 그 형태입니다 — 벤더의 품질 플래그를 OPC 품질 코드로 매핑한 다음, 정규(canonical) 행 튜플을 내보냅니다.

# examples/chapters/17-bridge-pi-historian/pi_bridge.py
def quality_code(item: dict) -> int:
"""PI Good/Questionable/Substituted -> OPC UA quality code."""
if item.get("Questionable"):
return 64 # Uncertain
return 192 if item.get("Good", True) else 0 # Good / Bad


def to_sensor_rows(items: list[dict], tag: str, batch_id: str) -> list[tuple]:
"""Map PI recorded values to ts.sensor_reading rows (ts, tag, value, unit, quality, batch_id)."""
return [(it["Timestamp"], tag, it["Value"], it.get("UnitsAbbreviation"),
quality_code(it), batch_id) for it in items]

DCS 다리는 이것을 정확히 복제합니다. pi_bridge.read_recorded()가 PI Web API를 대상으로 client.get(".../streams/{wid}/recorded")를 하는 자리에서, DCS 버전은 asyncua로 OPC UA 노드를 구독하고 value + StatusCode + SourceTimestamp를 받습니다. quality_code()는 PI의 플래그 대신 DCS의 "Good"192로 매핑하며, to_sensor_rows()는 그대로 재사용됩니다. 정직한 상태는 이렇습니다. 전용 opcua_dcs_bridge.py와 그 dcs-mockcommercial 프로파일이 dcs-mock 서버를 얻을 때에야 비로소 저장소에 들어옵니다(pi-web-api-stub은 이미 들어 있습니다. 아래 참조). 그때까지는 pi_bridge.py가 다리의 형태를 정의하는, 커밋되고 테스트된 산출물입니다 — DCS 포팅은 동일한 quality_code / to_sensor_rows 코어를 감싼 서로 다른 전송 호출일 뿐입니다.

OPC UA가 제공되지 않을 때. 더 오래된 Siemens 스키드는 때때로 앞단에 OPC UA 서버 없이 네이티브 S7 프로토콜만 노출합니다. 여기서 오픈소스의 답은 Apache PLC4X(Apache 2.0)입니다. 벤더 중립적인 라이브러리로, 그 S7(Step7) 드라이버가 Siemens S7 PLC를 직접 읽고 씁니다 [7]. 수십 가지 레거시 프로토콜로 말하므로, OPC UA가 잊고 간 브라운필드(brownfield) 스키드를 위한 최후의 다리입니다. 9장에서는 레거시 Modbus 스키드에 같은 태그-읽고-행-쓰기 패턴을 사용했습니다(examples/chapters/09-legacy-skids-modbus-s7/modbus_reader.py에서 pymodbus로). DCS 경우는 그 패턴을 PLC4X를 통해 적용하되, 독립 스키드가 아니라 제어 시스템을 향하게 할 뿐입니다.

연결성 장들에서 이어 온 한 가지 정직한 주석. 모니터링 계층으로 올라가는 읽기 전용 OPC UA 채널은 바로 NAMUR 개방 아키텍처(NAMUR Open Architecture) 개념입니다 — 검증된 코어를 그대로 둔 채 분석을 위한 두 번째, 거의 읽기 전용인 데이터 경로입니다. 그것은 DCS를 재검증하지 않고 DCS 데이터를 얻는, 아키텍처적으로 승인된 방법입니다.

MES: 정직한 결론은 "신뢰할 만한 OSS GxP 선택지는 없다"

이제 그 어려운 문장입니다. ISA-95 레벨 3의 제조 실행 시스템(Manufacturing Execution System, MES)은 마스터 레시피를 실행되고 전자 서명된 배치 기록으로 바꾸는 것입니다. 작업 지시에 맞춰 자재를 불출하고, 작업 순서를 강제하며, 작업자 전자 서명을 포착하고, 품질 부서가 출하 승인하는 검토된 전자 배치 기록(electronic batch record, EBR)을 산출합니다. 그것은 현장에서 가장 무겁게 검증되고, Part-11이 가장 짙게 배어 있는 시스템입니다.

GMP 바이오 제조에서 이 자리를 신뢰할 만하게 채우는 오픈소스 제품은 없습니다. 이것은 이 책의 도구 조사가 빠뜨린 부분이 아니라, 그 조사의 발견입니다. 여러분은 조각들을 모을 수 있습니다 — 여기 워크플로 엔진 하나, 저기 eLN 하나, 그리고 PostgreSQL에 만든 우리 자신의 ISA-88/95 모델 — 하지만 그중 어느 것도 상용 MES(또는 검증된 페이퍼 온 글래스(paper-on-glass) 시스템)가 제공하는, 검증되고 벤더가 책임지며 Part-11이 완비된 실행-및-전자-서명 패키지를 담고 있지 않습니다. GAMP 5 제2판은 오픈소스가 GxP에서 사용될 수 있음을 명시하지만, 오직 검증된 생애 주기 안에서, 사용에 비례하는 위험 기반(risk-based)·비판적 사고(critical-thinking) 보증과 함께여야 한다고 못박습니다 [12] — 그리고 수제 MES를 조립해 그 기준까지 검증하는 일은, 어떤 분석 팀도 부업으로 이길 수 있다고 자처해서는 안 되는 수년짜리 프로그램입니다.

그래서 아키텍처는 그 결론을 따릅니다. 상용 MES(또는 페이퍼 온 글래스)는 실행의 기록 시스템으로 남습니다. 대신 우리 오픈소스 계층은 정당한 두 가지 일을 합니다.

  1. MES의 구조적 산출물 — 레시피, 오퍼레이션과 페이즈, 배치와 그 실제 페이즈 구간 — 을 우리가 이미 만든 관계형 모델로 미러링하여, 분석과 대시보드가 맥락을 갖게 합니다.
  2. 실행 기록을 결코 발원시키지 않습니다. 전자 서명도, 자재 처분(material disposition)도, 출하 결정도 OSS 계층에 살지 않습니다.

좋은 소식은 그 미러가 이미 만들어져 있다는 것입니다. MES의 세계는 ISA-88/95이며, 우리는 3장에서 ISA-88/95를 PostgreSQL에 모델링했습니다. 다음은 examples/platform/db/10-isa88-95.sql에서 가져온 실제 골격입니다 — MES가 내보내는 장비 계층과 절차 모델이 이 테이블들에 곧바로 매핑됩니다.

-- 10-isa88-95.sql — the relational backbone (Chapter 3).
CREATE TABLE s88.unit ( -- the equipment a phase runs on
unit_id text PRIMARY KEY, -- e.g. BR101
area_id text NOT NULL REFERENCES s88.area,
name text NOT NULL,
unit_type text NOT NULL, -- bioreactor | chromatography | tff | fill_line ...
vendor text,
model text
);

CREATE TABLE s88.operation ( -- an ordered step of the recipe
operation_id text PRIMARY KEY,
recipe_id text NOT NULL REFERENCES s88.recipe,
seq_no int NOT NULL,
name text NOT NULL, -- Inoculation | Fed-batch | Harvest | ProteinA ...
unit_type text NOT NULL
);

CREATE TABLE s88.phase ( -- the smallest procedural element
phase_id text PRIMARY KEY,
operation_id text NOT NULL REFERENCES s88.operation,
seq_no int NOT NULL,
name text NOT NULL
);

그리고 배치 — 작업 지시가 만들어 내는 실행 — 와, 완성된 로트를 그 시드 트레인(seed train)까지 거슬러 추적하게 해 주는 계보(genealogy)는, MES가 기록할 그대로입니다(역시 examples/platform/db/10-isa88-95.sql에서):

CREATE TABLE s88.batch (
batch_id text PRIMARY KEY,
product_id text NOT NULL,
recipe_id text NOT NULL REFERENCES s88.recipe,
unit_id text NOT NULL REFERENCES s88.unit,
lot text,
status text NOT NULL DEFAULT 'in_progress', -- in_progress | complete | released | rejected
start_ts timestamptz NOT NULL,
end_ts timestamptz
);

-- lot genealogy: directed edges child -> parent (seed -> bioreactor -> pool -> DS -> DP)
CREATE TABLE s88.genealogy (
batch_id text REFERENCES s88.batch,
child text NOT NULL,
child_type text NOT NULL,
parent text NOT NULL,
parent_type text NOT NULL,
PRIMARY KEY (child, parent)
);

이것이 이 장에 나오는 모든 MES와 ERP 메시지의 수신단입니다. SAP가 생산 주문을 보내면, 그것은 s88.batch의 한 행이 됩니다. MES가 실제 페이즈 구간을 보고하면, 그것은 s88.batch_phase에 착륙합니다. ERP가 어떤 구성품 로트가 어떤 제품 로트를 먹였는지 조정하면, 그 엣지들은 s88.genealogy에 착륙합니다. 우리의 시드 데이터는 진행 중인 사례를 위해 이것을 이미 채워 둡니다 — ACME Biologics 엔터프라이즈, Newark DE Plant 사이트, BR101(Sartorius Biostat STR 50), CHO-MAB-001 페드배치(fed-batch) 레시피, 그리고 의도적으로 거부된 BATCH-2026-004를 포함한 여섯 개의 캠페인 배치입니다. 그 미러는 오늘 실재하고 조회 가능합니다. 그것이 해서는 안 되는 일은, 배치가 실행되고 서명되는 장소가 되는 것입니다.

세 개의 차선이 하나의 오픈소스 미러로 흘러든다. OT 차선은 DeltaV와 Siemens를 OPC UA로 TimescaleDB에 읽어 들이고, MES 레벨-3 차선은 배치 구조를 B2MML로 PostgreSQL ISA-88/95 모델에 내보내며, 빨간 점선 장벽이 오픈소스에는 전자 서명도 출하 승인도 없음을 표시한다. ERP 레벨-4 차선은 SAP 자재와 주문을 IDoc과 OData로 교환한다. 모든 화살표가 미러를 향해 들어온다.

레벨 1-4에서의 정직한 하이브리드 경계: 오픈소스는 DCS를 OPC UA로 읽고, MES 배치 구조를 B2MML로 미러링하며, ERP 메시지를 교환합니다 — 그러나 검증된 시스템들은 실행, 서명, 처분을 그대로 유지합니다. OSS 계층은 미러일 뿐, 결코 원본이 아닙니다. Original diagram by the authors, created with AI assistance.

SAP/ERP: 자재, 로트, 작업 지시를 B2MML로 교환하기

ERP는 — 바이오파마에서는 압도적으로 SAP S/4HANA입니다 — 비즈니스의 진실을 소유합니다. 어떤 자재가 존재하는지, 어떤 로트가 출하 승인되었거나 격리되었는지, 어떤 작업 지시가 어떤 배치를 승인하는지입니다. OSS 계층은 센서 판독값을 의미 있게 만들기 위해 그 맥락이 필요하며, 때때로 결과를 다시 보고할 필요도 있습니다. 이를 수행하는 표준적이고 벤더 중립적인 방법은 B2MML(Business To Manufacturing Markup Language)로 직렬화된 ISA-95 / IEC 62264 객체 모델입니다. B2MML은 ISA-95의 XML 구현으로, 어떤 회사든 MESA에 출처를 표기하면 로열티 없이 사용할 수 있습니다 [1][2][3]. 문헌도 이 패턴을 뒷받침합니다. 동료 심사를 거친 ERP↔MES 통합 사례들은 ISA-95 객체 모델을 B2MML XML 트랜잭션 메시지로 구현합니다 [11].

SAP 자체는 두 개의 구체적인 문을 제공합니다. 고전적인 것은 IDoc(Intermediate Document)으로, 자재·로트·주문 데이터를 교환하는 SAP의 구조화된 메시지이며, 통합 계층이 이를 B2MML로 그리고 B2MML에서 변환합니다 [10]. 현대적인 것은 OData입니다. SAP S/4HANA는 생산 주문과 관련 자재 데이터를 문서화된 OData API — 예를 들어 API_PRODUCTION_ORDER_2_SRV — 를 통해 노출하며, OSS 클라이언트는 이를 소비해 ERP 상태를 미러링합니다 [9]. PI 및 DCS와 마찬가지로, 동반 저장소의 계획은 sap-mock(commercial 프로파일 뒤에서 OData 엔드포인트와 IDoc XML 드롭 폴더를 제공하는 FastAPI 서비스)이어서, SAP 라이선스 없이 교환을 만들고 계약 테스트할 수 있게 합니다. 이것은 로드맵에 있을 뿐 아직 출하되지 않았으므로, 여기의 코드 조각들은 목표 계약입니다.

OData 문에서 끌어온 생산 주문은 이렇게 보입니다(문서화된 API_PRODUCTION_ORDER_2_SRV 형태에 맞춘 예시적인 SAP OData JSON):

{
"d": {
"ManufacturingOrder": "1000004711",
"Material": "MAB-001",
"ProductionPlant": "NEWARK",
"MfgOrderPlannedTotalQty": "1",
"ProductionUnit": "EA",
"MfgOrderScheduledStartDate": "2026-01-05T00:00:00Z",
"OrderIsReleased": true
}
}

다리의 일은, 결코 SAP인 척하지 않으면서 그것을 우리 모델에 착륙시키는 것입니다. 매핑은 작고 명시적입니다 — Materials88.batch.product_id, ProductionPlant → 사이트, 그리고 주문 ID는 미러가 SAP로 다시 조정될 수 있도록 참조로 보관합니다.

-- Mirror a released SAP production order into the ISA-88/95 batch table.
-- SAP stays the system of record; this row is a faithful copy keyed to the order.
INSERT INTO s88.batch (batch_id, product_id, recipe_id, unit_id, lot, status, start_ts)
VALUES ('BATCH-2026-007', 'MAB-001', 'CHO-MAB-001', 'BR101', 'L26007',
'in_progress', '2026-01-05T00:00:00Z')
ON CONFLICT (batch_id) DO UPDATE
SET status = EXCLUDED.status,
product_id = EXCLUDED.product_id; -- idempotent: re-applying the same order is a no-op

같은 B2MML 봉투가 반대 방향으로 자재-로트 정보를 실어 나릅니다. ISA-95의 MaterialLotMaterialDefinition 객체는 우리의 s88.genealogy 엣지에 매핑되므로, ERP가 포착 풀(capture pool) PApool-007이 바이오리액터 배치 BATCH-2026-007에서 왔음을 확인하면, 미러는 그 계보를 시드 데이터가 골든 배치(golden batch)에 대해 이미 하는 것과 정확히 같이 기록합니다.

batch_id,child,child_type,parent,parent_type
BATCH-2026-001,SEED-001,seed_train,WCB-CHO-001,wcb
BATCH-2026-001,BATCH-2026-001,bioreactor,SEED-001,seed_train
BATCH-2026-001,PApool-001,capture_pool,BATCH-2026-001,bioreactor
BATCH-2026-001,DS-001,drug_substance,PApool-001,capture_pool
BATCH-2026-001,DP-001,drug_product,DS-001,drug_substance

그것은 examples/datasets/lot_genealogy.csv에서 가져온 실제 데이터입니다 — 한 로트에 대한 시드 트레인 → 바이오리액터 → 포착 풀 → 원료의약품(drug substance) → 완제의약품(drug product)의 전체 사슬이며, ERP가 주도하는 계보 교환이 재구성할 바로 그 계보입니다.

멱등성과 조정: 미러를 정직하게 유지하는 규칙

이 장의 모든 교환은 복사본이며, 복사본은 어긋납니다(drift). 미러를 신뢰할 만하게 유지하는 규율은 히스토리안 장이 도입한 것과 동일합니다. 모든 쓰기를 멱등하게 만들고, 덮어쓰는 대신 조정하십시오.

멱등이란 같은 메시지를 다시 적용해도 아무것도 바뀌지 않는다는 뜻입니다. SAP는 IDoc을 재전송하고, OData 폴링은 겹치며, DCS 구독은 재연결 후 재생됩니다. "생산 주문 1000004711이 출하 승인되었다"가 세 번 도착하면, 여러분은 세 개가 아니라 하나의 배치 행으로 끝나야 합니다. 위의 ON CONFLICT (batch_id) DO UPDATE가 바로 그 보장입니다 — 주문은 키가 매겨져 있고, 그것을 다시 적용하는 것은 무동작(no-op)입니다. (시계열과의 비대칭에 주목하세요. 히스토리안의 하이퍼테이블(hypertable)은 (tag, ts)에 고유 키를 두지 않으므로, DCS 백필(backfill)은 17장에서 설명한 대로 *구간-삭제-후-삽입(delete-window-then-insert)*으로 멱등성을 얻는 반면, 관계형 배치/계보 테이블은 기본 키와 ON CONFLICT로 그것을 얻습니다.)

조정이란 두 시스템에 같은 질문을 주기적으로 던지고 그들이 일치함을 단언하는 것입니다. 출하 승인된 모든 SAP 주문에는 정확히 하나의 미러링된 배치가 있는가? 모든 s88.genealogy 엣지는 ERP가 확인한 자재 로트로 추적되는가? 불일치는 데이터 무결성(data-integrity) 이벤트로 기록되며, 결코 조용히 고쳐지지 않습니다 — 미러에서의 조용한 수정이 바로 *그림자 기록(shadow record)*이 태어나는 방식이고, 검증된 기록 시스템과 어긋나는 그림자 기록이야말로 조사관이 정확히 찾아내는 것이기 때문입니다. 규칙을 한 번 말하자면 이렇습니다. OSS 계층은 충실하게 동기화해야 하며, 결코 권한을 가진 병렬 기록이 되어서는 안 된다.

왜 중요한가

이 세 다리를 잘못 놓으면, 여러분은 알아볼 수 있는 두 가지 방식 중 하나로 실패합니다. 과욕을 부리거나 — 오픈소스 계층이 DCS에 설정값을 쓰거나, 배치 기록에 서명하거나, 자재를 처분하게 두어 — OSS 도구가 감당할 수 없는 검증과 Part-11 부담을, 환자 이익은 전혀 없이 상당한 규제 위험과 함께 떠안게 됩니다. 아니면 교환을 과소 설계하거나 — 멱등하지 않은 쓰기, 조정 없음 — 여러분의 미러가 SAP 및 MES로부터 조용히 어긋나다가, 출하 검토 중 대시보드가 공식 기록과 모순되는 지경에 이릅니다.

이들을 제대로 하면 분업은 깔끔하고 해방적입니다. DCS는 공정을 계속 제어하고, MES는 배치를 계속 실행하고 서명하며, SAP는 자재와 주문을 계속 소유합니다. 오픈소스 계층은 표준 문을 통해 세 가지를 모두 읽고, 모든 것을 하나의 ISA-88/95 모델에 착륙시키며, 신뢰할 수 있는 맥락 위에서 맥락화, SPC, 소프트 센싱(soft-sensing)을 수행하는 빠르고 저렴하며 제약 없는 장소가 됩니다. 각 상용 시스템은 자신이 책임지는 것에 대해 기록 시스템으로 남습니다 [12]. 표준들 — 제어를 위한 OPC UA [4], 비즈니스 교환을 위한 B2MML/ISA-95 [1] — 은 미러를 충실하게 만드는 이음새입니다.

실제 현장에서는

승인 제품을 만드는 mAb 공장에 들어가면, 여러분이 발견하는 토폴로지는 이렇습니다. 스위트를 돌리는 DeltaV 또는 PCS7 DCS, 전자 배치 기록을 보유한 상용 MES(Werum PAS-X, Körber, Tulip-on-glass 또는 유사 제품), 그리고 모든 재무와 자재를 소유한 최상단의 SAP입니다. 통합 팀은 바로 이 장이 설명하는 문들 — DCS에서 나오는 OPC UA, SAP를 오가는 B2MML/IDoc/OData — 에 자기 경력을 바칩니다. 그리고 그들이 그렇게 하는 것은 바로 아무도 그 아래의 검증된 시스템을 대체하도록 허용되지 않기 때문입니다.

이 계층에 대한 정직한 OSS-대-상용 결론은 이 책에서 가장 냉정합니다. DCS 읽기 경로에서 오픈소스는 탁월합니다. node-opcua [8], asyncua, Apache PLC4X [7]는 제어 데이터를 미러링하는 데 필요한 모든 것을 주며, 읽기 전용 NOA 패턴은 무엇도 재검증하지 않고 그것을 한다는 뜻입니다. ERP 교환에서 오픈소스는 충분히 유능합니다. B2MML은 무료이고 로열티 없는 스키마이며 [3], 파이썬 OData/IDoc 클라이언트는 1년이 아니라 한 주말짜리 일입니다. 하지만 MES 자리에는 신뢰할 만한 오픈소스 GxP 제품이 그저 없으며, 그렇지 않은 척하는 것은 이 책이 할 수 있는 가장 위험한 과장일 것입니다. 순수 오픈소스는 여러분에게 플랫폼의 대략 80%를 줍니다. MES는 마지막 GxP 1마일이 하이브리드가 아니라 확고하고 정직하게 상용인 곳들 중 하나입니다.

NIIMBL — 바이오 제조 발전을 위한 미국의 민관 협력 연구소 — 은 이 문들을 실재하게 만드는 상호운용성과 표준 작업에 자금을 댑니다. 그리고 그 SABRE 시설(델라웨어 대학교의 파일럿 규모 cGMP — 현행 우수 제조 관리 기준(current Good Manufacturing Practice) — 시설로, 2024년 4월에 착공했고 2026년 중반 현재 여전히 건설 중)은, 오픈소스 분석 계층이 검증된 DCS, MES, ERP 곁에 앉아 그것들을 대체하기보다 표준 문을 통해 읽는 그런 종류의 현장입니다. 우리 공정의 강화/연속(intensified/continuous) 변형 — 다중 컬럼 포착을 갖춘 관류(perfusion) — 은 이 이음새들을 가로질러 흐르는 태그와 주문을 늘리기만 하며, 그것은 규율 있고 멱등하며 조정 가능한 미러를 덜 가치 있게가 아니라 더 가치 있게 만듭니다.

핵심 용어

  • DCS(Distributed Control System, 분산 제어 시스템) — ISA-95 레벨 1-2에서 공정 루프를 돌리는 검증된 제어 계층(Emerson DeltaV, Siemens PCS7/SIMATIC). OSS 계층은 이를 OPC UA로 읽으며 [5][6], 결코 설정값을 쓰지 않는다.
  • MES(Manufacturing Execution System, 제조 실행 시스템) — 레시피를 실행하고, 전자 서명을 포착하며, 전자 배치 기록을 산출하는 레벨-3 시스템. 신뢰할 만한 오픈소스 GxP 선택지가 없어 상용으로 남는다 [12].
  • ERP(Enterprise Resource Planning, 전사적 자원 관리) — 자재, 로트, 작업 지시를 소유하는 레벨-4 비즈니스 시스템(SAP S/4HANA). IDoc과 OData로 교환된다 [9][10].
  • OPC UA — IEC 62541. DCS/PLC 제어 데이터를 OSS 스택으로 읽어 들이는, 플랫폼 독립적이고 보안성을 갖춘 프로토콜 [4].
  • Apache PLC4X — OPC UA 서버가 제공되지 않을 때 Siemens S7과 다수의 레거시 PLC 프로토콜을 읽는, 벤더 중립적 오픈소스 라이브러리(Apache 2.0) [7].
  • node-opcua — DCS OPC UA 서버를 탐색/읽기/구독하는 데 쓰이는 MIT 라이선스 Node.js OPC UA SDK [8].
  • B2MML(Business To Manufacturing Markup Language) — 자재/로트/작업 지시 교환에 쓰이는, 로열티 없는 ISA-95의 XML 구현 [2][3].
  • ISA-95 / IEC 62264 — B2MML이 직렬화하고 우리 PostgreSQL 모델이 구현하는, 엔터프라이즈-제어 통합을 위한 표준 모델과 용어 [1][11].
  • IDoc / OData — 주문과 자재 데이터를 교환하기 위한 SAP의 고전적 메시지 형식과 현대적 REST API [9][10].
  • 기록 시스템(system of record) — 어떤 종류의 데이터에 대한 권위 있고 검증된 출처. 여기서는 제어에 대한 DCS, 실행에 대한 MES, 자재에 대한 SAP. OSS 계층은 미러일 뿐, 결코 기록이 아니다 [12].
  • 멱등(idempotent) — 반복해도 안전한 쓰기. 관계형 미러는 기본 키와 ON CONFLICT로, 시계열은 구간-삭제-후-삽입으로 이를 얻는다.
  • 그림자 기록(shadow record) — 검증된 기록 시스템과 어긋나는, 통제되지 않은 병렬 복사본. 조정이 막기 위해 존재하는 실패 모드.
  • NAMUR 개방 아키텍처(NAMUR Open Architecture, NOA) — 검증된 제어 코어를 변경하지 않고 분석에 데이터를 공급하는, 승인된 거의-읽기-전용의 두 번째 채널.

다음 이야기

이제 제어 데이터와 비즈니스 데이터는 우리 스택으로 충실하게 미러링되지만, 한 기록 시스템은 여전히 바깥에 서 있습니다. 바로 실험실입니다. 출하 시험 — 한 로트가 출하될지를 결정하는 분석 시험 — 은 흔히 상용 LIMS에 살며, 그것이 서명하는 결과가 가장 중요한 결과입니다. 다음 장 **상용·오픈소스 LIMS 연동(Bridging to Commercial & Open-Source LIMS)**은 그 세계로의 시료-및-시험성적서(certificate-of-analysis) 교환을 만듭니다. 솔직한 주석과 함께 — LabKey의 Part 11 기능은 유료 장벽 뒤에 있고, SENAITE와 openBIS는 각각 QC와 공정 개발 자리에 들어맞는다는 점, 그리고 가장 위험이 높은 곳에 적용되는 동일한 대체-말고-미러링의 규율입니다.