레퍼런스 아키텍처: 한 스택, 계층별로
📍 현재 위치: 청사진입니다. 명령어를 단 한 줄도 입력하기 전에, 우리는 플랫폼 전체를 한 페이지에 펼쳐 봅니다. 모든 계층, 모든 오픈소스 도구, 모든 ISA-95 레벨을 펼쳐 놓고, 오픈소스가 끝나고 상용 시스템이 시작되는 경계선을 그어 봅니다.
머리말은 여러분에게 한 가지 약속을 했습니다. 저장소(repository) 하나를 클론(clone)해서, 계층별로 오픈소스로 동작하는 생물공정 데이터 플랫폼(bioprocess data platform)을 만들어 보겠다는 약속이었습니다. 이 장은 바로 그 플랫폼의 지도입니다. 공사를 시작하기 전에 벽에 붙여 두는 설계 도면, 즉 이후 모든 장이 조용히 다시 가리키게 될 그 도면이라고 생각하면 됩니다.
아직은 아무것도 실행하지 않습니다. 실행은 2장에서 합니다. 여기서는 더 어려운 일을 합니다. 첫 조각을 만들기 전에, 스택 전체의 형태를 여러분이 볼 수 있게 하는 일입니다. pH 프로브(probe)에서 태어난 숫자 하나가 어떻게 메시지 버스(message bus)를 거쳐 히스토리안(historian)에 안착하고, 배치 기록(batch record)에 꿰매지고, 지식 그래프(knowledge graph)의 트리플(triple)이 되며, 마침내 규제 제출 문서(regulatory submission)를 조립하는 데 도움을 주는지를 말입니다. (트리플(triple)은 주어-술어-목적어로 된 하나의 사실이고, 지식 그래프(knowledge graph)는 그 사실들이 엮어 내는 그물입니다. 우리는 시맨틱 계층: 지식 그래프에서 산업 온톨로지에 정렬된 지식 그래프를 실제로 구축하므로, 끝에서 끝까지의 계보를 수작업으로 재구성하는 대신 SPARQL로 질의할 수 있습니다.)
8층짜리 건물을 떠올려 보세요. 1층은 원자재(센서 측정값)가 도착하는 하역장입니다. 그 위의 각 층은 의미를 하나씩 더합니다. 어떤 층은 배송물에 라벨을 붙이고 경로를 정해 주고, 어떤 층은 그것들을 창고에 보관하고, 어떤 층은 모든 상자를 그것이 속한 주문과 대조하며, 어떤 층은 검사관이 어떤 상자든 그 출처까지 추적할 수 있게 합니다. 맨 위 층들은 규제 당국이 방문하는 곳입니다. 이 장에서 우리가 할 일은 평면도를 그리고, 각 층에 배치된 오픈소스 도구의 이름을 붙이고, 그리고 빨간 잉크로 표시하는 것입니다. 무료 인력만으로는 검사를 통과할 수 없어 면허를 가진 전문가를 고용해야 하는 층이 어디인지를 말입니다.
이 장에서 다루는 내용
- 위에서 아래로 이어지는 계층형 청사진, 그리고 동반 저장소(companion repo)의 빌드 순서가 왜 그것을 따르는지.
- 각 계층을 ISA-95 / 퍼듀(Purdue) 레벨, 그리고 우리가 그 계층에 고른 오픈소스 도구에 매핑하기.
- 정직한 오픈소스 소프트웨어(open-source software, OSS)↔상용 경계가 어디에 떨어지는지, 그리고 왜 거기에 떨어지는지.
- 스택 전체를 조인 가능(joinable)하게 만드는 단 하나의 설계 결정: 히스토리안과 배치 모델(batch model)이 같은 데이터베이스 안에 사는 것.
- 컴포넌트 및 라이선스 목록, 그리고 모든 장을 관통하며 되풀이되는 데이터 무결성(data integrity) 질문.
한 페이지짜리 청사진
플랫폼의 모든 계층은 동일한 직무 명세를 가집니다. 아래 계층에서 데이터를 받아, 한 종류의 의미를 더하고, 위로 넘긴다. 아래에서부터 읽어 보세요.
| 계층 | 무엇을 더하는가 | ISA-95 / 퍼듀 레벨 | 이 책의 OSS 도구 |
|---|---|---|---|
| 엣지 연결(Edge connectivity) | 센서를 읽는 표준적이고 자기 기술적인 방식 | Level 0–2 (센서, 제어) | OPC UA (asyncua, 파이썬 라이브러리 경유) |
| 메시지 버스(Message bus) | 모든 값의 이름 붙은 실시간 스트림 | Level 2–3 경계 | MQTT + Sparkplug B (Mosquitto) |
| 히스토리안 / TSDB (시계열 데이터베이스) | 대규모로 내구성 있고 질의 가능한 시계열 | Level 3 | TimescaleDB hypertable |
| 배치 및 장비 모델(Batch & equipment model) | 숫자에 의미를 부여하는 맥락 | Level 3 | PostgreSQL (ISA-88/95) |
| 맥락화(Contextualization) | 조인: 이 값, 이 배치, 이 단계 | Level 3 | SQL views |
| 시맨틱(Semantics) | 시스템을 가로지르는 기계 추적 가능한 계보 | Level 3–4 | RDF / SPARQL (Apache Jena Fuseki) |
| 컴플라이언스 / 신뢰(Compliance / trust) | 감사 추적된 진실의 기록 | Level 3–4 | Postgres audit + hash chain |
| 분석(Analytics) | 예측과 공정 이해 | Level 3–4 | Python (SPC = 통계적 공정 관리 차트, PLS soft-sensor) |
"Level 0–4", "퍼듀(Purdue)", "DCS"가 처음 보는 용어라면, 표를 미리 보기로 읽으세요. 다음 소절 — ISA-95 사다리 읽기 — 이 그 가로대들을 정의합니다.
ISA-95 — 기업 시스템(생산을 계획하고 추적하는 비즈니스 소프트웨어)과 제어 시스템(장비를 물리적으로 돌리는 자동화)을 통합하기 위한 국제 표준(IEC 62264)으로, 2025년 판에서 갱신되었습니다 — 에 매핑하는 것은 장식이 아닙니다 [1]. 그것은 각 도구가 어디에 정당하게 사는지를, 그리고 결정적으로 그들 사이의 경계가 어디에 놓이는지를 알려 줍니다. 이 표 전체에서 가장 중요한 단 하나의 경계는 Level 2(실제로 바이오리액터(bioreactor) — 세포가 자라서 약을 만드는 용기 — 를 돌리는 제어 시스템)와 Level 3(이 책에서 우리가 만드는 모든 것) 사이의 경계입니다. 검증된(validated) 제어 시스템은 신성합니다. 우리는 결코 그 안으로 쓰지 않습니다. 우리는 그것으로부터 읽습니다.
그 원칙에는 이름이 있습니다. NAMUR — 유럽 자동화 기술 사용자 협회 — 는 바로 이를 위해 NAMUR 오픈 아키텍처(NAMUR Open Architecture, NOA) 개념을 발표했습니다. 검증된 핵심 공정 제어 시스템을 변경하지 않으면서 모니터링, 히스토리화, 최적화를 먹여 주는 두 번째의 읽기 위주(read-mostly) 데이터 채널입니다 [2]. 이 책의 거의 모든 것은 그 경계선의 NOA 쪽에 삽니다. 우리가 "우리는 결코 DCS(distributed control system, 분산 제어 시스템 — 공장의 장비를 실시간으로 자동으로 돌리는 컴퓨터 시스템)를 건드리지 않는다"고 말하는 것을 들을 때, 그것을 축복하는 표준이 바로 NOA입니다.
ISA-95 사다리 읽기: 각 도구가 정당하게 사는 곳
ISA-95는 다섯 개의 가로대를 가진 사다리이며, 가로대의 가치는 각 도구를 그저 설치된 곳이 아니라 정당한 자리에 핀으로 고정한다는 데 있습니다. Level 0–2는 검증된 세계입니다. 센서와 액추에이터(Level 0–1), 그리고 공정을 실시간으로 돌리는 기본·감독 제어(Level 2) — DCS 또는 PLC(programmable logic controller, 프로그래머블 로직 컨트롤러 — 개별 기계를 구동하는 견고한 산업용 컴퓨터), 즉 규제 당국이 자격을 부여(formally proven, 문서화된 증거와 함께 정확히 해야 할 일을 하도록 공식적으로 입증)했고 여러분이 교란해서는 안 되는 부분입니다. Level 3은 제조 운영입니다. 히스토리화, 배치 기록, 스케줄링, 디스패치 — 이 책의 모든 오픈소스 도구가 사는 계층입니다. Level 4는 비즈니스 계획과 물류, 즉 ERP(enterprise resource planning, 전사적 자원 관리) 세계입니다. 청사진 표를 그 사다리에 대조해 읽으면, OPC UA 엣지는 Level 0–2에 걸치고, MQTT/Sparkplug 버스는 Level 2–3 경계에 앉으며, 히스토리안 위로는 전부 Level 3 이상입니다 [1]. 사다리가 강제하는 규율은 단순합니다. 도구는 아래 레벨에서 읽을 수는 있지만, 검증된 레벨은 결코 위로부터 쓰기를 받지 않습니다. 컴포넌트를 한 가로대 너무 낮게 두면, 여러분은 분석 편의를 자격이 부여된 시스템에 대한 변경으로 조용히 바꿔 버린 것입니다.
Level 2/3 경계: 왜 우리는 결코 DCS에 쓰지 않는가 (NOA)
사다리 위의 네 경계 중 Level 2/3 선은 이 책 전체가 존중하도록 지어진 경계입니다. 그 아래에는 검증된 제어 시스템이, 그 위에는 우리가 자유롭게 만들 수 있는 모든 것이 있습니다. 그 선을 가로질러 쓰는 것 — Level 3 도구에서 값이나 설정값, 또는 구성 변경조차 DCS로 밀어 내려 보내는 것 — 은 검증되지 않은 코드를 검증된 경계 안에 들이는 일이며, 제어 시스템을 재자격화하는 것이야말로 NOA가 피하기 위해 존재하는 바로 그 비용입니다. NAMUR 오픈 아키텍처 개념은 그 탈출구를 정형화합니다. 두 번째의 읽기 위주 채널이 모니터링과 최적화를 위해 제어 시스템에서 공정 데이터를 빼내는 동안, 검증된 핵심은 단 하나의 자격이 부여된 제어 경로를 유지합니다 [2]. 이 스택의 모든 수집기, 히스토리안, 뷰는 그 두 번째 채널 위의 소비자입니다. 그런 다음 OT/IT 보안 표준 ISA/IEC 62443 — OT(operational technology, 운영 기술)는 장비를 돌리는 현장 시스템, IT(information technology, 정보 기술)는 그 위의 비즈니스·데이터 시스템 — 이 통로 자체 — 방화벽, 데이터 다이오드(data diode, 트래픽을 물리적으로 한 방향으로만 허용하는 하드웨어), 또는 단방향 게이트웨이 — 를 통제하여, "읽기 위주"가 단지 선한 의도가 아니라 네트워크에 의해 강제되도록 합니다 [10]. 이후 장에서 DCS 목(mock)으로부터 읽는 코드를 보여 줄 때, 그것이 지키고 있는 규칙이 바로 이것입니다. 들어오기만, 결코 나가지 않기.
레퍼런스 아키텍처, 아래에서 위로. 데이터는 위로 흐른다. 각 계층은 한 종류의 의미를 더해 다음 계층으로 넘긴다. 음영이 없는 계층들은 노트북에서 직접 만들고 실행하는 순수 오픈소스이다. 제출 문서 근처의 음영 처리된 컴플라이언스 띠는 정직한 하이브리드이다. 검증, 자격을 갖춘 서명, 그리고 벤더 책임이라는 GxP의 마지막 1마일로, 오픈소스만으로는 전달할 수 없는 영역이다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.
같은 스택, 데이터플로우로 본다면
표는 아래에서 위로, 여러분이 만드는 순서대로 읽힙니다. 데이터는 물론 반대 방향으로 흐릅니다. 다음은 같은 아키텍처를 바이오리액터 BR101 위의 pH 측정값 하나가 떠나는 여정으로 나타낸 것입니다.
히스토리안과 배치 모델이 나란히 놓여 있고, 화살표가 아니라 그냥 선으로 연결되어 있다는 점에 주목하세요. 그것이 이 설계의 핵심(keystone)이며, 그 자체로 별도의 절을 가질 자격이 있습니다.
히스토리안과 배치 모델을 위한 하나의 데이터베이스
대부분의 시설에서 히스토리안과 배치/관계형 세계는 서로 다른 벤더의 서로 다른 두 제품이며, 이 둘을 조인하는 것은 누군가가 돌봐야 하는 야간 ETL(extract-transform-load, 추출-변환-적재 — 한 시스템에서 데이터를 꺼내 재구성하여 다른 시스템에 적재하는 예약된 작업) 작업입니다. 사후에, 일정에 따라, 애초에 서로 합의하도록 설계되지 않은 두 시스템 사이에서 돌기 때문에 바로 그만큼 깨지기 쉽습니다. 우리는 그 분리를 거부합니다. 동반 저장소가 데이터베이스를 어떻게 정의하는지, examples/platform/compose/compose.yaml에서 보세요. 이것은 Docker Compose 파일입니다 — 각 서비스(컨테이너)를 핀된 image, 노출하는 ports, 마운트하는 volumes로 선언하는 단일 YAML 파일입니다. 2장이 이것을 실행하므로, 여기서는 그저 스택의 명세서(manifest)로 읽으세요.
services:
# --- core --------------------------------------------------------------
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
TimescaleDB는 별도의 데이터베이스가 아니라, PostgreSQL 확장(extension)입니다. 그 핵심 추상화인 하이퍼테이블(hypertable) 은 시간에 따라 자동으로 청크(chunk)로 파티셔닝되는 평범한 PostgreSQL 테이블입니다. 그래서 고속 센서 데이터가 다른 관계형 테이블과 똑같이 동작하면서도 대규모에서 빠른 속도를 유지합니다 [3]. 히스토리안이 바로 PostgreSQL이기 때문에, 시계열과 배치 맥락은 서로 대화하는 척하는 두 시스템이 아닙니다. 그것들은 평범한 SQL로 조인할 수 있는 하나의 데이터베이스 안의 두 스키마(schema)입니다. (데이터베이스에서 스키마는 하나의 데이터베이스 안에서 관련 테이블들을 묶는 이름 붙은 네임스페이스이지, 다이어그램이 아닙니다.) 히스토리안은 ts 스키마에, 배치 모델은 s88 스키마에 삽니다. 다음은 하이퍼테이블로, 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
);
SELECT create_hypertable('ts.sensor_reading', 'ts', chunk_time_interval => INTERVAL '1 day');
저 quality 컬럼은 작지만 하중을 견디는 기둥입니다. OPC UA — 센서로부터 Level 3까지 데이터와 그 메타데이터를 실어 나르는 플랫폼 독립적이고 서비스 지향적인 프로토콜 [4] — 는 모든 값에 품질 상태(quality status)를 붙이며, 스키마는 이를 압축된 레거시 OPC DA 인코딩으로 저장합니다. Good에는 192, Uncertain에는 64, Bad에는 0입니다(OPC DA 대 OPC UA의 구분은 아래에서 풀어 봅니다). 우리는 그것을 히스토리안까지 내내 보존합니다. 나중에 검사관이 어떤 값이 기록된 그 순간에 신뢰할 만했는지를 물을 때, 답은 추측이 아니라 하나의 컬럼입니다. 이것은 ALCOA+ 속성인 "Original(원본)"을 구체화한 것입니다. ALCOA+는 신뢰할 수 있는 데이터가 가져야 할 속성에 대한 규제 당국의 약칭으로 — 귀속 가능(Attributable), 판독 가능(Legible), 동시 기록(Contemporaneous), 원본(Original), 정확(Accurate), 그리고 완전(Complete), 일관(Consistent), 영속(Enduring), 가용(Available) — 이 책의 나머지를 떠받치는 데이터 무결성 가이던스는 원본 측정값과 그 맥락이 손상 없이 살아남기를 기대합니다 [5].
ts.sensor_reading 한 행의 해부
스택 전체가 위로 실어 나르려는 단 하나는 센서 측정값입니다. 그러니 ts.sensor_reading의 한 행을 컬럼별로 천천히 해부해 볼 가치가 있습니다. 이후 모든 장이 정확히 이 형태로 쓰거나, 읽거나, 확장하기 때문입니다. 아래의 행은 시드된 골든 배치 — 이후의 배치들이 그 궤적과 비교되는 모범 기준 실행을 가리키는 업계 용어 — 의 첫 용존산소 측정값으로, examples/datasets/fedbatch_timeseries_10min.sample.csv에 들어 있는 배치 시작 시점 2026-01-05 00:00의 BR101.DO.PV 행이며, 예시가 아니라 실제 값입니다.
Level 3에 안착하는
ts.sensor_reading의 한 행: 어떤 배치 맥락도 조인되기 전, 여섯 개의 컬럼이 각각 다른 종류의 의미를 실어 나른다.
저자가 AI의 도움을 받아 직접 제작한 그림입니다.
여섯 개의 컬럼을 히스토리안이 모든 값에 대해 하는 여섯 가지 약속으로 읽어 보세요. ts는 소스 타임스탬프입니다 — 측정이 데이터베이스에 도착한 시점이 아니라 현장에서 일어난 시점이며, 오프셋이 결코 모호하지 않도록 timestamptz 타입입니다. tag는 신호의 정체성을 텍스트로 담은 것입니다. BR101.DO.PV는 자산(BR101, ISA-95 유닛), 측정(DO, 용존산소), 역할(PV, 프로세스 값 — 즉 증거 — 이며 레시피 설정값인 .SP가 아님)로 분해됩니다. value는 숫자 40.8224로, double precision으로 저장됩니다. unit은 %sat로, "40.8224, 무엇의?"에 대한 답입니다. 단위 없는 숫자는 터지기를 기다리는 결함입니다. quality는 신뢰 플래그입니다 — 다음 절이 그것만을 따로 해부합니다. 그리고 batch_id는 BATCH-2026-001로, 이 측정값을 나중에 쓰기 시점의 외래 키 격식 없이 GMP(Good Manufacturing Practice, 우수 제조 관리 기준 — 생산이 따라야 하는 규제된 품질 규칙) 배치 기록에 꿰맬 수 있게 하는 단 하나의 텍스트 컬럼입니다(외래 키는 한 행이 다른 테이블의 기존 행을 참조하도록 강제하는 관계형 데이터베이스 제약입니다. 이를 "쓰기 시점에" 건너뛰면, 히스토리안이 배치 기록이 존재하기도 전에 측정값을 빠르게 기록할 수 있습니다). 여섯 컬럼, 그리고 그중 어느 하나도 장식이 아닙니다. 하나라도 빼면 하류의 어떤 질문이 답할 수 없게 됩니다.
이 행은 다른 두 책이 시작하는 사슬의 끝입니다. BR101.DO.PV 측정값은 교반식 바이오리액터의 물리적 프로브에서 태어납니다 — Book 1이 생산 바이오리액터에서 짚는 바로 그 단계입니다. 그런 다음 Book 2는 그 측정값이 무엇인지를 이름 붙입니다. 공장이 스스로 드리우는 데이터 그림자이며, 그것에 신뢰할 수 있는 맥락을 부여하는 과제이지요. 위에서 해부한 것은 그 데이터 포인트를 마침내 구현하는 오픈소스 코드입니다 — 그리고 똑같은 ts.sensor_reading 형태가 바로 Book 2의 실시간 출하 비전이 다시 읽어 내는 것입니다.
quality 컬럼, 필드별로
여섯 컬럼 중 quality는 정작 중요해질 때까지는 거의 보이지 않음으로써 제 자리를 얻는 컬럼입니다. 그 선언은 quality smallint NOT NULL DEFAULT 192이며, 그 한 줄이 데이터 무결성 결정을 인코딩합니다. 세 값은 임의가 아닙니다. 이것들은 고전적인 OPC DA 품질 코드로 — 192(0xC0)는 Good, 64는 Uncertain, 0은 Bad — 위의 스키마 주석이 이름 붙인 레거시 8비트 인코딩이며, 히스토리안은 이를 압축된 smallint로 저장합니다 [4]. OPC DA(Classic)는 이 스택의 다른 모든 곳에서 쓰이는 현대 OPC UA 프로토콜의, Windows에 묶인 더 오래된 선조입니다. 그 간결한 품질 바이트는 저장하기 편해서, 우리는 그것을 컬럼의 기본값으로 유지합니다. 이것들은 OPC UA StatusCode의 실제 값이 아닙니다. 네이티브 OPC UA Good StatusCode는 32비트 0x00000000이며, 심각도는 그 상위 두 비트에 들어 있습니다. 우리는 압축된 OPC DA smallint를 저장하고, 프로토콜 경계에서 그것을 온전한 OPC UA StatusCode로 매핑합니다. 동반 저장소의 OPC UA 서버 examples/chapters/05-connectivity-opcua-mqtt/opcua_server.py는 히스토리안의 smallint를 프로토콜의 StatusCode로, 그리고 다시 반대로 매핑합니다. 그래서 값의 신뢰성이 프로브에서 데이터베이스를 거쳐 구독자에 이르는 왕복을 살아남습니다 — 품질은 각 홉에서 Good으로 다시 주장되는 것이 아니라 끝에서 끝까지 흘러갑니다.
애초에 왜 Good이 아닌 모든 포인트를 버리지 않고 컬럼으로 저장할까요? 어떤 포인트가 불확실했는지, 그리고 언제 그랬는지가 그 자체로 규제 대상 증거이기 때문입니다. ALCOA+ 속성 "Original(원본)"은 측정의 최초 캡처 — 그것을 한정하는 메타데이터를 포함하여 — 가 손상 없이 살아남기를 요구하며, MHRA(영국 의약품·보건의료제품 규제청, Medicines and Healthcare products Regulatory Agency) 데이터 무결성 가이던스는 데이터의 의미와 맥락이 어떤 처리 과정을 거치든 보존되어야 한다고 명시합니다 [5]. Uncertain 측정값을 단계 평균에 조용히 섞어 넣는 히스토리안은 Original 데이터를 파괴한 것입니다. value 옆에 quality를 보관하는 히스토리안은 분석 계층이 그것을 제외하기로 선택할 수 있게 하고, 검사관이 그것이 제외되었음을 볼 수 있게 합니다. 컬럼은 작지만, 그것이 이행하는 의무는 작지 않습니다. 골든 배치에서 이는 가정이 아닙니다. 짧은 7일차 온도 이탈(2026-01-12에 설정값이 36.5 °C로 내려감)이 용존산소 측정값들을 몇 시간 동안 Uncertain(quality 64)으로 표시하며, 바로 그 포인트들이 분석가가 제외하거나 검사관이 볼 수 있도록 히스토리안에 살아남습니다.
숫자를 지식으로 바꾸는 계층
ts.sensor_reading의 한 행은 BR101.DO.PV = 41.3 %sat at 2026-01-05T08:00:00Z라고 말합니다. 사실이긴 하지만, 그것만으로는 거의 쓸모가 없습니다. 그 배치는 성장(세포를 불리는 중) 중이었나요, 생산(항체를 만드는 중) 중이었나요? 이 용존산소(DO) 측정값은 그 단계에 대해 통제된 범위 안에 있었나요? 답하려면 배치 모델이 필요합니다. 맥락화 계층은 그것을 공급하는 조인이며, examples/platform/db/60-views.sql에서 한 번 정의됩니다.
-- A reading with its full batch + phase context.
CREATE OR REPLACE VIEW s88.v_batch_sensor AS
SELECT r.ts, r.tag, r.value, r.unit, r.quality, r.batch_id,
b.product_id, b.recipe_id, b.unit_id,
bp.phase_id, ph.name AS phase_name
FROM ts.sensor_reading r
JOIN s88.batch b ON b.batch_id = r.batch_id
LEFT JOIN s88.batch_phase bp ON bp.batch_id = r.batch_id
AND r.ts >= bp.start_ts AND (bp.end_ts IS NULL OR r.ts < bp.end_ts)
LEFT JOIN s88.phase ph ON ph.phase_id = bp.phase_id;
이 단 하나의 뷰(view)는 모든 것을 하나의 데이터베이스에 담아 둔 것의 아키텍처적 보상입니다. 그것은 시계열 측정값(ts.sensor_reading)을 그것이 속한 배치(s88.batch)에, 그리고 그 순간에 활성화되어 있던 ISA-88 단계(s88.batch_phase → s88.phase)에 조인합니다. 배치 모델 자체는 결합된 계층 구조를 따릅니다. ISA-95 장비 모델(기업 → 사이트 → 영역 → 유닛)과 ISA-88 절차/레시피 모델(레시피 → 오퍼레이션 → 단계), 두 표준이 unit에서 만나며, examples/platform/db/10-isa88-95.sql에 모델링되어 있습니다. (ISA-95는 물리적 장비 사다리를 다스리고, ISA-88은 레시피가 절차적으로 어떻게 구조화되는지를 다스립니다. 그래서 이 책은 둘을 짝지웁니다.) 저장소는 그것을 구체적인 유가식(fed-batch) CHO(Chinese hamster ovary, 중국 햄스터 난소 — 항체 생산의 일꾼 숙주 세포주) 라인으로 — 유가식은 배양물에 영양분을 주기적으로 먹여 생산 수명을 늘린다는 뜻입니다 — examples/platform/db/seed/seed_cho_line.sql에서 시드(seed)합니다.
INSERT INTO s88.unit VALUES
('BR101', 'UPSTREAM', 'Production Bioreactor 101', 'bioreactor', 'Sartorius', 'Biostat STR 50'),
('N1SEED', 'UPSTREAM', 'N-1 Seed Bioreactor', 'bioreactor', 'Sartorius', 'Biostat STR 10'),
('PA01', 'DOWNSTREAM', 'Protein A Capture Skid', 'chromatography', 'Cytiva', 'AKTA process'),
('TFF01', 'DOWNSTREAM', 'UF/DF Skid', 'tff', 'Cytiva', 'AKTA flux'),
('FILL-LINE-01', 'FILL', 'Aseptic Fill Line', 'fill_line', 'Bausch+Stroebel', 'KSF');
이것이 데이터로 표현한 우리의 실행 사례입니다. 생산 바이오리액터 하나, N-1 시드, 단백질 A(Protein A) 캡처 스키드, UF/DF 스키드, 그리고 충전 라인입니다. 트릴로지 전체가 따라가는 유가식 CHO + 단백질 A 단일클론항체(monoclonal antibody, mAb) 공정입니다. PA01은 정화된 하베스트(harvest) — 세포가 걸러진 뒤 남는 세포 없는 액체 — 에서 항체를 붙잡아 내는 단백질 A 친화성 캡처 단계이며(저장소의 protein_a_summary는 ~58 g/L 동적 결합 용량 — 즉 수지의 항체 보유 한계 — 에서 92% 회수율을 시드합니다), TFF01은 용출액(eluate, 캡처 컬럼에서 방출된 항체를 담은 액체)을 농축하고 완충액을 교환하는 접선 흐름 한외여과/정용여과(UF/DF) 단계입니다. 데이터 형태는 단위공정의 물리를 함께 실어 나릅니다. 단백질 A 사이클은 결합-세척-용출-재생(bind-wash-elute-strip) 시퀀스이므로, PA01의 신호는 컬럼 부피에 대한 크로마토그램의 UV A280 트레이스, 전도도, pH이며, 동적 결합 용량은 적재 한계(파과 전에 수지 1리터가 보유하는 항체의 그램 수)라서 센서 태그가 아니라 회수율 곁에 자리합니다. TFF01의 신호는 막간 차압, 교차 흐름율, 그리고 교환된 완충액의 다이아볼륨(diavolume)이며, 같은 다운스트림 화학이 이후 분석·시맨틱 계층이 게이트하는 바로 그 출하 CQA(단량체 순도, HMW 응집체, 숙주세포 단백질)를 만들어 내기 때문에, 이 단계들을 포착하는 데이터 모델이 곧 다운스트림 일탈을 그 원인까지 추적 가능하게 만드는 것이기도 합니다. Book 1은 각 단계의 메커니즘을 짚습니다 — 단백질 A 캡처, 바이러스 불활화와 여과, 폴리싱, UF/DF — 여기서 우리는 그것들의 데이터만 실어 나릅니다. (같은 라인의 관류(perfusion) / 다중 컬럼 연속 변형판은 이후 장들에서 사이드바로 등장합니다. 같은 관계형 스키마를 재사용하되, 연속 공정 사례는 관류 고유의 신호 — 용기 부피당 일일 관류율, 세포 비례 관류율, 세포 블리드(cell-bleed)율 — 를 공유 테이블 위에 추가합니다.) 맥락화 뷰가 일단 존재하면, 한때는 고고학 프로젝트였던 질문 — "골든 배치(golden batch)에서 용존산소가 단계별로 어떻게 움직였나?" — 은 make contextualize가 s88.v_batch_sensor에 대해 실행하는 한 줄짜리 쿼리가 됩니다.
맥락화된 측정값 한 행의 해부
앞 절의 행을 머릿속에 붙들어 둔 채로, 조인이 그것을 무엇으로 바꾸는지 보세요. 뷰는 측정값을 다시 쓰지 않습니다. 여섯 컬럼 전부를 그대로 상속하고, 배치 모델에서 끌어온 다섯 개의 새 컬럼을 덧붙입니다. 아래 카드는 그 결과를 해부합니다 — 같은 BR101.DO.PV 측정값이 이제 그 제품, 레시피, 장비, 단계를 실어 나릅니다.
s88.v_batch_sensor 이후의 같은 측정값: 히스토리안의 여섯 컬럼이 손상 없이 통과하고, 야간 ETL 파이프라인이 아니라 단 하나의 SQL 조인이 숫자를 지식으로 바꾸는 다섯 필드를 덧붙인다.
저자가 AI의 도움을 받아 직접 제작한 그림입니다.
덧붙은 다섯 필드는 각각 가공되지 않은 행이 답할 수 없던 질문에 답합니다. product_id(MAB-001), recipe_id(CHO-MAB-001), unit_id(BR101)는 평범한 JOIN s88.batch ON b.batch_id = r.batch_id에서 옵니다 — 이 로트가 무엇을 만드는지, 어떤 ISA-88 레시피가 그것을 통제했는지, 어떤 ISA-95 유닛에서 돌았는지를 말해 줍니다. 마지막 둘, phase_id와 phase_name은 더 미묘합니다. 측정값의 ts를 각 단계의 start_ts/end_ts 창에 대조하는 LEFT JOIN s88.batch_phase, 그리고 사람이 읽는 이름을 풀어 주는 LEFT JOIN s88.phase에서 옵니다. 2026-01-05 00:00:00+00으로 찍힌 우리 예시 행의 경우, 그 시간 조인은 Inoculate 창(PH1) 안에 떨어집니다. 그래서 히스토리안에서는 단계를 알지 못했던 같은 측정값이 이제 그것이 접종(inoculation) 중에 취해졌음을 압니다. LEFT 조인은 의도적입니다. 단계 창이 시드되기 전에 도착한 측정값도 뷰에서 사라지지 않고, 단계 필드가 null인 채로 여전히 나타납니다.
아키텍처적 요점은 이 장 전체가 도는 바로 그것입니다. 두 번째 카드는 첫 번째 카드 더하기 조인이 더하는 의미입니다 — 그리고 그 "더하기"는 쿼리 시점에 평가되는 CREATE VIEW입니다. 히스토리안과 배치 모델이 하나의 데이터베이스 안의 두 스키마이기 때문이지요. 더 흔한 두 제품 세계에서는, 같은 그 보강이 시계열 행을 관계형 저장소로(또는 그 반대로) 복사하는 예약된 ETL 작업이며, 그 자체의 실패 양상과 지연과 정합성 부담을 가집니다. 하나의 데이터베이스를 유지하면 파이프라인이 조인으로 무너져 내리고, 그 조인이 청사진의 핵심 보상입니다.
같은 행, 한 단 위로: 트리플 하나, 셰이프 하나, 역량 질문 하나
맥락화된 행은 시맨틱 계층으로 넘어가는 자연스러운 인계 지점이기도 하며, 시맨틱 장이 이를 온전히 구축하기 전에 그 다리를 축소판으로 보아 둘 가치가 있습니다. s88.v_batch_sensor의 각 행은 시맨틱하게 보면 한 주어 — 측정값 — 를 공유하는 RDF 트리플(triple)의 작은 묶음이므로, 관계형 (tag, value, unit, quality, batch_id, phase_id) 튜플은 거의 필드 대 필드로 주어-술어-목적어 사실에 매핑됩니다.
# 같은 맥락화된 측정값을 트리플로 표현 (예시).
bp:reading-BR101-DO-20260105T0000 bp:onTag "BR101.DO.PV" ;
bp:value 40.8224 ;
bp:unit unit:PERCENT ; # QUDT
bp:quality "Good" ;
bp:fromBatch bp:BATCH-2026-001 ;
bp:duringPhase bp:PH1-Inoculate .
unit 컬럼은 QUDT로 타입이 지정된 양량이 되고, quality 플래그는 일급(first-class) 사실이 되며, batch_id/phase_id는 그래프가 걸어갈 수 있는 엣지가 됩니다 — 외래 키와, 순회할 수 있는 관계 사이의 차이로, 클래스와 분류 체계와 관계와 계보에서 풀어 놓습니다. 그러면 두 가지 온톨로지 규율이 그 다리를 따라 함께 옵니다. 첫째, NOT NULL DEFAULT 192와 unit 컬럼은 닫힌 세계 완전성 의무이며 — 이는 바로 SHACL 셰이프(Shapes Constraint Language, 그래프가 갖춰야 할 구조를 가졌는지 검증하는 언어)의 역할로, unit과 quality에 대한 그 sh:minCount 1은 스키마의 NOT NULL을 그래프 쪽에서 비춘 거울이며, 출하 게이트와 SHACL 장이 출하 결정을 내리는 방식과 같습니다. 둘째, 맥락화 뷰는 그래프가 SPARQL로 답하는 역량 질문(competency question) — "골든 배치에서 용존산소가 단계별로 어떻게 움직였나?" — 을 SQL로 답하며, 그런 질문 하나가 양쪽 모두로 답해질 수 있다는 것은 관계형 모델과 미래의 그래프가 측정값이 무엇인지에 합의한다는 가장 값싼 검사입니다. 더 깊은 요점은, 식별자와 단위 장이 정직하게 짚듯, BATCH-2026-001은 모두가 같은 방식으로 그것을 해석할 때에만 전역 식별자라는 것입니다. 그래서 두 PostgreSQL 스키마를 조인하는 바로 그 텍스트 키가 두 조직을 조인하는 IRI가 됩니다.
메시지 버스: 점대점 연결의 엉킴이 아닌, 이름 붙은 스트림
센서와 히스토리안 사이에 메시지 버스가 자리합니다. 그 역할은 점대점(point-to-point) 연결의 혼돈을, 어떤 소비자(consumer)든 구독(subscribe)할 수 있는 하나의 이름 붙은 실시간 스트림으로 바꾸는 것입니다. 우리는 경량 발행/구독(publish/subscribe) 프로토콜인 MQTT를 사용하며, 이를 Mosquitto 브로커(broker)가 실어 나릅니다. 같은 compose 파일에 핀(pin)되어 있습니다.
mosquitto:
image: eclipse-mosquitto:2.0.22
profiles: ["core"]
ports: ["1883:1883"]
volumes:
- ../mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
하지만 가공되지 않은 MQTT 토픽(topic)은 무법지대이고, 무법지대는 데이터 무결성의 적입니다. 그래서 MQTT 위에 우리는 Sparkplug B를 채택합니다. 표준화된 토픽 네임스페이스(namespace), 압축된 페이로드(payload), 그리고 — 가장 중요하게는 — 탄생/소멸(birth/death) 세션 상태를 부과하는 Eclipse 명세로, 소비자가 디바이스가 살아 있는지와 무엇을 보고하고 있는지를 항상 알 수 있게 합니다 [6]. Sparkplug는 5장에서 만나게 될 "통합 네임스페이스(Unified Namespace)" 개념의 배후 메커니즘입니다. 공장 전체를 위한 하나의 자기 기술적이고 브로커가 매개하는 주소 공간이지요. OPC UA → Sparkplug 수집기(collector)는 7장에서 만들 것입니다. 여기서의 요점은 그저 이 버스가 즉흥적인 배관이 아니라 표준을 갖춘 이름 붙은 계층이라는 것입니다.
여기서 솔직한 범위 설명을 하나 덧붙입니다. MQTT/Sparkplug는 우리가 구축하는 OT 측 버스로, 엣지에 가까운 경량 발행/구독에 잘 맞아 선택했습니다. 많은 기업은 이와 병행하여 두 번째 IT 측 스트리밍 백본 — 가장 흔하게는 Apache Kafka — 을 비즈니스 시스템 간 고처리량 이벤트 파이프라인용으로 운영하며, 둘은 OT/IT 경계의 MQTT-to-Kafka 커넥터로 흔히 연결됩니다. 우리는 의도적으로 그 선의 OT 쪽에 머무릅니다. Kafka는 데이터가 엔터프라이즈로 넘어가는 순간 알아야 할 이름입니다.
오픈소스가 멈추는 곳: 정직한 경계
이제 빨간 잉크입니다. 데이터플로우를 다시 위로 따라가면서, 순수 오픈소스가 길이 끊기는 지점이 어디인지 지켜보세요.
캡처, 히스토리화, 맥락화, 시각화, 추론, 그리고 분석 — 음영 처리된 컴플라이언스 띠 아래의 모든 계층 — 은 순수하고 실행 가능한 오픈소스입니다. 여러분은 그 전부를 노트북에서 만들 것이고, make test가 그것이 동작함을 증명합니다. 그것이 대략 플랫폼의 첫 80%입니다.
컴플라이언스 및 신뢰 띠는 정직한 하이브리드가 시작되는 곳입니다. 어떤 오픈소스 컴포넌트도 별도의 작업 없이 그대로 21 CFR Part 11을 준수하지 않으며 [7] — Part 11은 신뢰할 수 있는 전자 기록과 전자 서명을 다스리는 미국 FDA(Food and Drug Administration, 식품의약국)의 규정입니다 — 그 유럽 짝꿍인 전산화 시스템용 EU Annex 11 체제도 충족하지 않습니다 — 다운로드만으로 그렇게 되지도 않습니다. 우리는 신뢰 계층 — PostgreSQL 안의 시스템 버전 관리(system-versioned) 감사 추적과 암호학적 해시 체인(hash chain)(23–24장) — 을 시연하며, 그것은 진정으로 유용합니다. 변조를 탐지 가능하게 만들기 때문입니다. 그러나 그것이 변조를 불가능하게 만들지는 않습니다. 데이터베이스 슈퍼유저(superuser)는 감사 행을 쓰는 트리거(trigger)를 비활성화할 수 있습니다. Part 11 준수는 어떤 단일 도구의 속성이 아니라 검증된 시스템과 그 절차의 속성입니다 [7]. 현대의 가이던스는 그 방법론에 동의합니다. GAMP 5(전산화 시스템을 검증하는 데 널리 쓰이는 업계 프레임워크) 제2판은 위험 기반의, 비판적 사고(critical-thinking) 접근법(그리고 오픈소스 부록)을 추가하여, OSS가 GxP — "우수 관리(Good Practice)" 품질 규제 계열(우수 제조·시험·임상 관리 기준) — 에 검증된 라이프사이클 안에서, 즉 시스템이 규제된 용도에 적합하다는 공식 증거를 만들어 내는 통제 아래에서 구축·시험·문서화되는 가운데 사용될 수 있게 합니다 [8]. 그리고 FDA의 컴퓨터 소프트웨어 보증(Computer Software Assurance) 가이던스는 그 라이프사이클을, 문서를 위한 문서가 아니라 로그, 감사 추적, 공급자 증거를 활용하는 위험 비례적 보증으로 재구성합니다 [9]. 그것이 바로 우리가 보여 주는 작업이며, 어떤 도구도 준수된 채로 도착하지 않는 정확한 이유입니다.
상용 시스템은 하이브리드의 다른 한쪽입니다. AVEVA PI, SAP, Emerson DeltaV, Siemens, 그리고 상용 LIMS(laboratory information management systems, 실험실 정보 관리 시스템) — 각각 히스토리안, ERP, 두 개의 제어 시스템, 그리고 실험실 데이터 소프트웨어 — 는 노트북에서 실행될 수 없고 라이선스로 잠겨 있습니다. 우리가 작성하는 통합 코드는 진짜입니다. 그 상대편은 같은 API 계약과 문서화된 프로덕션 교체(production swap)를 갖춘, 명확히 라벨이 붙은 목(mock)입니다(20–22장). 그리고 아키텍처 전체가 걸쳐 있는 OT/IT 경계 자체가 하나의 보안 경계선이며, 이는 ISA/IEC 62443 — 퍼듀 레벨 사이의 어디에 방화벽, 데이터 다이오드(data diode), 또는 단방향 NOA 채널이 속하는지를 말해 주는 존-앤-컨듀잇(zones-and-conduits) 표준(이전의 ISA-99) — 에 의해 통제됩니다 [10]. 마지막 장은 각 경계에서 여러분이 정확히 무엇을 맞바꾸는지를 한 줄씩 채점합니다.
컴포넌트 및 라이선스 목록
우리가 특정 도구를 추천하기 때문에, 여러분에게 그것들의 라이선스 — 그리고 2026년의 함정 — 를 빚지고 있습니다. examples/platform/compose/compose.yaml의 핵심 스택은 작고 의도적입니다.
| 컴포넌트 | 핀된 이미지 | 라이선스 | 2026년 참고 사항 |
|---|---|---|---|
| PostgreSQL + TimescaleDB | timescale/timescaledb:2.17.2-pg17 | PostgreSQL License + Apache-2.0 core + TSL Community | Apache-2.0 코어에 더해 무료 TSL Community 자동화(연속 집계, 보존 정책)를 사용합니다 — 소스 공개(source-available)(코드를 읽고 실행할 수 있음)이지만 OSI 승인 오픈소스는 아닙니다(라이선스가 일부 상업적 사용을 제한함). 우리는 오직 TSL Hypercore 컬럼스토어/압축/HA(high availability, 고가용성 — 중복 페일오버) 기능만 피합니다(16장 참고). |
| MQTT 브로커 | eclipse-mosquitto:2.0.22 | EPL-2.0 / EDL-1.0 | 재현성을 위해 안정적인 2.0.x 라인에 핀했습니다. EPL-2.0/EDL-1.0은 SaaS 호스팅 제한이 없는 관대한 Eclipse Foundation 오픈소스 라이선스입니다. |
| 대시보드 | grafana/grafana-oss:11.4.0 | AGPL-3.0 | 로컬 사용은 괜찮습니다. 재배포하거나 타인을 위한 SaaS로 호스팅하면 AGPL 의무가 발동됩니다. |
| 트리플스토어 | apache/jena-fuseki:5.2.0 | Apache-2.0 | 다이제스트(digest)를 검증하세요. 커뮤니티 이미지가 이동했습니다. |
| 메트릭 저장소 | victoriametrics/victoria-metrics:v1.108.1 | Apache-2.0 | v3 라이선스 전환을 피하려고 InfluxDB 3 대신 탑재했습니다 — InfluxDB의 v3가 관대한 오픈소스 라이선스에서 벗어났기 때문에, 메트릭 저장소를 자유롭게 쓸 수 있도록 Apache-2.0 VictoriaMetrics를 사용합니다. |
모든 이미지는 태그(tag)로 핀되어 있고, 저장소의 락 파일(lock file)에서는 다이제스트로도 핀되어 있습니다. 그래서 실행 중인 스택, 라이선스 목록, 그리고 25장이 검증을 위해 생성하는 공급자 등록부(supplier register)가 조용히 서로 어긋날 수 없습니다. 히스토리안의 OSS 입장은 examples/platform/db/20-historian.sql의 스키마 주석에 바로 적혀 있습니다.
-- Apache-2.0 core (hypertables, create_hypertable, time_bucket, drop_chunks) plus
-- free TimescaleDB Community (TSL) automation: continuous aggregates and
-- add_retention_policy. TSL is free-to-use and source-available, but NOT OSI
-- open source. We deliberately do NOT use the TSL Hypercore columnstore/compression,
-- so a strictly Apache-2.0 build is one cron-driven drop_chunks away — see Chapter 16.
저 주석이 이 책의 라이선스 철학 전체를 담고 있습니다. 우리는 무료이며 소스가 공개된 기능을 의도적으로 사용하고 라이선스가 걸린 것은 큰 소리로 표시합니다. 그래서 "오픈"이라는 이유로 채택한 도구가 나중에 여러분을 기습하지 않도록 말입니다.
왜 중요한가
아키텍처 다이어그램은 장식이 아니라 결정 기록(decision record)입니다. 이후의 모든 장은 이 하나의 공유된 청사진 위에 놓인 얇은 조각입니다. 동반 저장소는 정확히 "두꺼운 공유 플랫폼 위의 얇은 장들(thin chapters over a thick shared platform)" 로 만들어졌습니다. 7장은 히스토리안을 재정의하지 않습니다. 위에서 본 ts.sensor_reading에 씁니다. 17장은 조인을 발명하지 않습니다. s88.v_batch_sensor 뷰를 확장합니다. 23장은 새 데이터베이스를 덧붙이지 않습니다. 이미 거기 있는 스키마들 옆에 감사 스키마를 추가합니다. 29장은 분석을 발명하지 않습니다. s88.v_batch_sensor 뷰 위에서 SPC 차트와 PLS 소프트 센서를 훈련하며, 홀드아웃(held-out) 검증과 실행-대-모델(run-to-model) 계보를 갖춥니다 — 공정 분석: SPC, MVDA & 소프트 센서를 보세요. 계층들이 한 번 정의되고 재사용되기 때문에, 빌드 순서가 곧 아키텍처입니다. make up은 코어(데이터베이스, 브로커, 대시보드)를 띄우고, make seed는 ISA-88/95 라인을 적재하며, 그 이후의 모든 장은 프로파일(profile)을 하나씩 더 켭니다.
분석 계층이 그 아래 두 컬럼을 필요로 하는 이유
분석 계층은 청사진의 규율이 선택 사항이 아닌 유일한 곳입니다 — 그것이 모델을 신뢰할 수 있게 유지하는 것입니다 — 그리고 여러분이 이미 만난 두 컬럼이 그것을 가능하게 합니다. batch_id는 통계적 독립성의 단위입니다. 하나의 유가식 실행 안의 행들은 자기상관된 거의 중복이므로, 이 데이터로 정직하게 검증된 모델은 배치 전체로 분할해야 하며(scikit-learn의 GroupKFold/LeaveOneGroupOut를 batch_id로 그룹화), 결코 행 단위로 분할해서는 안 됩니다. 그러지 않으면 모든 테스트 점의 거의 쌍둥이가 훈련으로 새어 들어가 허구의 R²를 보고하게 됩니다 — 이는 학습 문제와 모델과 검증에서 풀어 놓는, 이 분야 단연 가장 흔한 검증 오류입니다. quality 컬럼은 입력에 대한 게이트입니다. 적합 전에 Uncertain(64)과 Bad(0) 점을 떨어뜨리는 것은 데이터 정돈이 아니라 모델의 적용 범위(applicability domain) — 모델이 보정된, 그리고 그 안에서 신뢰할 수 있는 입력 영역 — 를 강제하는 것이며, 그래서 7일차 일탈의 Uncertain DO가 소프트 센서를 조용히 훈련하거나 채점하는 일이 결코 없습니다.
같은 맥락화된 행은 또한 평평한 히스토리안이 할 수 없는 일, 즉 두 종류의 드리프트(drift)를 분리 가능하게 만듭니다. 공정 드리프트(process drift)는 살아 있는 시스템이 진짜로 변하는 것 — 새 원자재 로트, 계대(passage)에 따라 적응하는 세포주 — 으로, 단계 안의 value 분포 이동으로 보입니다. 모델 드리프트(model drift)는 그 움직이는 세계에 맞서 소프트 센서가 낡아 가는 것입니다. 입력 분포에 대한 모니터(Population Stability Index, 라벨이 필요 없는 선행 지표)는 첫 번째를 잡고, 느린 오프라인 분석에 대한 예측 잔차의 관리도는 두 번째를 잡습니다 — MLOps와 라이프사이클에 근거한 두-탐지기 설계이며, 하이브리드 모델이 물리 백본이 블랙박스보다 더 느리게 드리프트하는 이유입니다. 그리고 감사 추적과 데이터셋이 같은 데이터베이스에서 버전으로 핀 고정되어 있기 때문에, 한 예측은 그것을 만들어 낸 정확한 행, 스키마 버전, 모델 버전까지 되짚을 수 있습니다 — 시맨틱 계층이 걸어가는 로트 계보의 분석 쪽 메아리인 모델 계보(model lineage)입니다. 청사진은 모델을 좋게 만들지 않습니다. 그것은 모델을 통제 가능하게 만들며, 이는 GxP 아래에서 더 어렵고 더 가치 있는 속성입니다.
청사진을 일찍 제대로 잡는 것은 또한 규제된 끝단을 애초에 달성 가능하게 만드는 일입니다. 만약 히스토리안과 배치 모델이 서로 연결되지 않은 두 제품이었다면, 맥락화 뷰 — 그리고 그 위에 세워진 모든 감사 추적, 계보 쿼리, 골든 배치 비교 — 는 한 줄짜리 SQL 조인이 아니라 깨지기 쉬운 ETL 파이프라인이 되었을 것입니다. 아키텍처는 3주가 걸리는 배치 조사와 한나절이 걸리는 배치 조사 사이의 차이입니다.
"3주 대 한나절" 뒤의 근거
그 대비는 수사적 장식이 아닙니다. 생물의약품 제조에서의 데이터 사이언스를 다룬 리뷰들은 이 분야의 결정적 장애물을 정확히 이 파편화로 짚습니다. 공정 데이터는 호환되지 않는 형식과 공유된 맥락이 없는, 이질적이고 벤더마다 다른 시스템들에 흩어져 있어, 어떤 분석이나 조사든 상당 부분이 분석이 아니라 실제 질문을 던지기도 전에 데이터를 수작업으로 찾고, 정렬하고, 다시 맥락화하는 데 쓰입니다 [11]. 히스토리안과 배치 모델이 별개의 제품일 때, 모든 교차 시스템 조사는 그 세금을 먼저 치릅니다 — 시계열 내보내기를 배치 기록과 수작업으로 상관시키고, 타임스탬프를 정합시키고, 어느 단계가 활성이었는지를 다시 도출하면서 말입니다 — 그것이 바로 수 주(週)라는 수치가 나오는 곳입니다. 이 청사진의 한-데이터베이스 조인은 그 세금을 스키마 설계 시점에 한 번만 치르므로, 조사 자체는 하나의 쿼리입니다. 문헌이 끌어내는 교훈은 이 장이 끌어내는 것과 같습니다. 연결되지 않은 데이터의 비용은 매 조사마다 치러지는 반면, 설계에 의한 맥락화의 비용은 단 한 번만 치러집니다 [11].
실제 현장에서는
현대의 바이오 제조 시설에 들어가 보면, 비록 아무도 그것을 벽에 그려 놓지 않았더라도, 바로 이 계층 구조를 발견하게 됩니다. 스키드에는 OPC UA 서버가 있고, 히스토리안(흔히 상용)이 있으며, 배치 모델을 담은 관계형 MES(manufacturing execution system, 제조 실행 시스템 — 배치 기록을 보유하고 생산을 디스패치하는 Level-3 소프트웨어)가 있고, 그리고 — 점점 더 — Grafana와 PostgreSQL 같은 오픈소스 도구가 검증된 시스템과 나란히 돌아갑니다. 업계가 실제로 돈을 지불하는 기술은 그들 사이의 경계가 어디에 놓이고 왜 거기에 놓이는지를 아는 것입니다. ISA-95는 그 경계에 어휘를 줍니다 [1]. ISA/IEC 62443은 OT/IT 경계선에 통제 수단을 줍니다 [10]. NOA는 읽기 위주의 분석 채널에 정당성을 줍니다 [2].
이 청사진이 의도적으로 지면에서 빼놓은 경계가 하나 있는데, Level 4 위의 엔터프라이즈 IT 데이터 평면(data plane)입니다. 대기업에서는 여기서 구축하는 온프레미스 스택이 보통 데이터를 그 위로 — Apache Airflow 같은 파이프라인 오케스트레이터와 dbt 같은 SQL 변환 계층을 거쳐 — 교차 사이트 분석과 AI가 사는 클라우드 데이터 레이크 또는 레이크하우스(Snowflake, Databricks / Delta Lake, 또는 AWS·Azure 등가물)로 넘깁니다. 우리의 make 타깃과 SQL 뷰는 그 기계장치의 노트북 규모 대역이며, 한 사이트를 넘어 규모를 키우는 순간 이 아키텍처가 연결되는 도구가 바로 그것들입니다. 그 인계 지점이 놀라움이 아니라 알려진 경계가 되도록 여기서 이름을 밝혀 둡니다.
제도적 추진력은 실재합니다. 새로운 파일럿 규모 cGMP(current Good Manufacturing Practice, 현행 우수 제조 관리 기준) 시설들이야말로 이런 아키텍처가 실제의 규제된 생산에 맞서 단련되는 곳입니다. 그리고 그런 모든 공장에서 같은 질문이 되풀이됩니다. 이 책이 매 장마다 되돌아가는 질문입니다. 어느 계층이 신뢰할 수 있고 감사 추적된 진실의 기록을 보유하는가? 여러분이 방금 읽은 청사진은 그 기록이 어디에 살 수 있는지에 대한 우리의 답입니다. 신뢰 장들은 오픈소스가 그것을 어디까지 데려갈 수 있고 어디서 데려갈 수 없는지에 대한 우리의 정직한 회계입니다.
핵심 용어
- 레퍼런스 아키텍처(Reference architecture): 플랫폼 전체의 계층형 청사진으로, 각 계층은 한 종류의 의미를 더하고 ISA-95 레벨과 오픈소스 도구에 매핑된다.
- ISA-95(IEC 62264): 기업 시스템과 제어 시스템을 통합하기 위한 표준 계층 모델(Levels 0–4)로, 각 도구를 배치하는 데 사용된다.
- 퍼듀 모델 / ISA-99 → ISA/IEC 62443(Purdue model): OT/IT 계층 구조와 그 존-앤-컨듀잇 보안 표준으로, 경계 통제 수단이 어디에 속하는지를 정의한다.
- NAMUR 오픈 아키텍처(NAMUR Open Architecture, NOA): 검증된 제어 시스템을 결코 변경하지 않으면서 모니터링과 최적화를 위한 두 번째의 읽기 위주 데이터 채널이라는 개념.
- OPC UA: 각 값과 그 품질 상태를 센서에서 애플리케이션까지 실어 나르는 플랫폼 독립적이고 자기 기술적인 프로토콜.
- MQTT / Sparkplug B: 경량 발행/구독 전송(Mosquitto)과, 그것에 표준화된 네임스페이스와 탄생/소멸 상태를 부여하는 명세로, 통합 네임스페이스의 기반이 된다.
- 하이퍼테이블(Hypertable): 시간에 따라 자동 파티셔닝되는 TimescaleDB 테이블로, 고속 센서 데이터를 PostgreSQL 안에 머무르게 하는 히스토리안 추상화.
- 맥락화(Contextualization): 가공되지 않은 측정값을 그것의 배치, 장비, ISA-88 단계에 묶는 SQL 조인(
s88.v_batch_sensor). - 시맨틱(Semantics, RDF / SPARQL): 맥락화된 각 사실을 지식 그래프 안의 주어-술어-목적어 트리플(triple)로 표현하여(Apache Jena Fuseki가 서빙), 계보를 SPARQL 질의 언어로 시스템을 가로질러 질의할 수 있게 하는 것 — 시맨틱 장에서 완전히 구축된다.
- SHACL 셰이프 / 역량 질문(competency question): 스키마의
NOT NULL을 그래프 쪽에서 비춘 거울 — 필요한 사실(단위, 품질, 출하 결과)이 존재하는지를 보는 닫힌 세계 완전성 검사 — 과, 모델이 답할 수 있어야 하는 질문으로, 관계형 뷰와 그래프 뷰가 합의하는지를 보는 가장 값싼 검사. - 적용 범위(Applicability domain): 모델이 보정되었고 그 안에서 신뢰할 수 있는 입력 영역.
quality컬럼은 분석이 적합 전에 범위 밖(Uncertain/Bad) 점을 배제하게 해 주는 것. - 그룹화(leave-one-batch-out) 교차검증: 자기상관된 실행 내 행들이 결코 훈련/테스트 선을 가로지르지 않도록 검증 데이터를
batch_id단위로 분할하는 것 — 소프트 센서의 보고된 성능을 부풀리는 누설에 대한 방어. - 공정 드리프트 대 모델 드리프트(process drift vs. model drift): 살아 있는 시스템이 진짜로 변하는 것(
value분포의 이동) 대 그것에 맞서 모델이 낡아 가는 것. 맥락화된 행이 둘을 분리 가능하게 하며, 각각은 서로 다른 탐지기가 잡는다. - 모델 계보(Model lineage): 한 예측에서 그것을 만들어 낸 정확한 행, 스키마 버전, 모델 버전까지 되짚는 감사 추적된 연결 — 로트 계보의 분석 쪽 메아리.
- ALCOA+: 신뢰할 수 있는 데이터가 가져야 할 속성에 대한 규제 당국의 약칭 — 귀속 가능(Attributable), 판독 가능(Legible), 동시 기록(Contemporaneous), 원본(Original), 정확(Accurate), 그리고 완전(Complete), 일관(Consistent), 영속(Enduring), 가용(Available).
- 품질 코드(Quality code, OPC DA / OPC UA StatusCode): 센서에서 히스토리안까지 실려 오는 값별 신뢰 플래그. 히스토리안은 압축된 레거시 OPC DA 인코딩 —
192(Good),64(Uncertain),0(Bad) — 으로 저장하고, 프로토콜 경계에서 그것을 온전한 32비트 OPC UA StatusCode로 매핑한다. ALCOA+ "Original" 속성을 하나의 컬럼으로 구체화한 것. - ETL(Extract–Transform–Load): 별개의 두 제품 사이에서 데이터를 복사하고 정합시키는 예약된 파이프라인. 히스토리안과 배치 모델을 하나의 조인 가능한 데이터베이스에 두어 이 청사진이 피하는 깨지기 쉬운 대안.
- GxP: 규제된 생산과 그 기록을 다스리는 "우수 관리(Good Practice)" 품질 규제 계열(우수 제조·시험·임상 관리 기준).
- HA(고가용성, high availability): 보통 중복되고 자동으로 페일오버하는 인스턴스를 통해, 컴포넌트 장애가 나도 서비스를 잃지 않고 견디는 시스템 설계.
- 정직한 하이브리드(Honest hybrid): 순수 OSS가 스택의 약 80%를 덮고, GxP의 마지막 1마일(검증, 전자 서명, HA, 벤더 책임)은 강화(hardening)나 상용 시스템으로 충족된다는 입장.
- 진실의 기록(Record of truth): Part 11 / Annex 11의 적용을 받는, 감사 추적되고 신뢰할 수 있는 전자 기록 — 어느 계층이 그것을 보유하는가라는 되풀이되는 질문.
다음 이야기
이제 여러분은 지도를 손에 쥐었습니다. 센서에서 제출 문서까지 여덟 개의 계층, 각각 ISA-95 레벨과 오픈소스 도구에 핀되어 있고, OSS 대 상용의 경계가 잉크로 그려져 있습니다. 다음 장, 스택 세우기: 단 한 번의 docker compose up, 은 그 지도를 돌아가는 기계로 바꿉니다. 우리는 단 하나의 핀된 명령으로 코어 프로파일 — PostgreSQL + TimescaleDB, Mosquitto, 그리고 Grafana — 을 띄우고, 그 옆에서 CHO 시뮬레이터(Compose 서비스가 아니라 파이썬 패키지)를 시작하며, 첫 데이터 포인트 스모크 테스트(smoke test)로 스택이 살아 있음을 증명하고, 여러분이 방금 설계한 히스토리안으로 첫 측정값이 흘러 들어가는 것을 지켜봅니다.