의미론과 디지털 스레드: 온톨로지와 지식그래프
📍 현재 위치: 3부 · 저장과 연결 — 16장. 히스토리안(historian)은 수치를 담고 관계형 모델은 배치(batch)를 담는다. 이 장에서는 배치, 장비, 원료, 레시피, 결과 등 모든 것을 하나의 탐색 가능한 그래프로 엮어, 단 하나의 질문으로 제품 수명주기 전체를 따라 걸을 수 있게 한다.
관계형 데이터베이스는 서류 캐비닛이다. 어느 서랍을 열어야 할지 이미 알고 있다면 더없이 훌륭하다. 하지만 조사관의 질문은 "7번 서랍 안에 뭐가 있지?"가 아니다. "이 의약품(drug product) 바이알이 출하 시험을 통과하지 못했다. 여기까지 오는 동안 거쳐 간 모든 것을 보여 달라"이다. 이는 조회(lookup)가 아니라 *걷기(walk)*다. 의약품 → 원료의약품(drug substance) → 캡처 풀(capture pool) → 바이오리액터 배치 → 종균 배양(seed train) → 모든 것이 시작된 셀뱅크(cell bank)로 이어진다. 지식그래프(knowledge graph)는 이러한 관계를 일급(first-class) 사실로 저장한다 — DS-001 derivedFrom PApool-001, PApool-001 derivedFrom BATCH-2026-001 — 그래서 단 한 번의 쿼리로, 몇 단계가 됐든 데이터베이스가 직접 그 사슬을 따라가게 할 수 있다. 처음부터 끝까지 추적한 그 사슬, 그것이 바로 업계에서 말하는 **디지털 스레드(digital thread)**다.
이 장에서 다루는 내용
14장에서는 히스토리안을 PostgreSQL의 배치와 결합했고, 3장에서는 ISA-88/95를 관계형 테이블로 모델링했다. SQL 조인(join)은 질문의 형태를 미리 알고 있을 때 완벽하다. 그러나 질문이 *재귀적(recursive)*이고 시스템을 가로지르는 순간 어색해진다. "이 로트(lot)는 무엇으로부터 파생되었는가, 몇 단계를 거치든 끝까지 거슬러 올라가서?"가 그런 질문이다. 이 장은 정확히 그러한 부류의 질문에 답한다.
우리는 다음을 할 것이다.
- 공장을 작은 온톨로지(ontology) — 배치, 캡처 풀, 종균 배양이 무엇인지에 대한 공유되고 기계가 읽을 수 있는 어휘 — 로 모델링하고, 모두가 수렴해 가는 개방형 표준(RDF, 산업 온톨로지 파운드리, Allotrope, QUDT)에 어떻게 정렬되는지 설명한다.
- 관계형 사실(배치, 로트 계보, 출하 결과)을 실제로 동작하고 검증된 파이썬(Python)으로 RDF 지식그래프에 적재한다.
- 한 배치의 전체 계보를 단 하나의 구문으로 추적하는 SPARQL 디지털 스레드 쿼리를 실행하고 실제 출력을 살펴본다.
- 그리고 오픈소스 트리플스토어(triplestore)가 진정으로 운영 수준에 도달한 지점과 GxP 래퍼(wrapper)가 여전히 직접 구축해야 할 영역을 솔직하게 짚는다.
이 장의 실행 가능한 코드는 단 하나의 파일 — examples/chapters/16-semantics-knowledge-graph/build_graph.py — 이며, RDFLib로 인프로세스(in-process)에서 그래프를 구축하므로 서비스가 전혀 없이도 노트북에서 실행된다. 온톨로지 Turtle, SHACL 형상(shape), Apache Jena Fuseki 배포는 등장하는 곳에서 예시용 구성으로 제시된다. 이들은 이 그래프를 공장 규모로 제공하는 방식이며, 그렇게 명시되어 있다.
왜 그래프인가, 이미 SQL이 있는데
이 책에서 지금까지 다룬 모든 것은 테이블 안에 살았고, 테이블은 우리에게 충분히 잘 봉사해 왔다. 그렇다면 굳이 왜 그래프를 더하는가?
의미(meaning)는 시스템 사이를 스스로 건너가지 않기 때문이다. 바이오리액터 DCS는 측정값을 BR101.Temp.PV라고 부르고, LIMS는 같은 로트를 DS-2026-001이라고 부르며, ERP는 그것을 원료 1000457이라고 부르고, CofA PDF는 "Lot 26-001"이라고 부른다. 각 시스템은 내부적으로는 일관되지만 서로 알아듣지 못하며, 그래프는 이들을 화해시키는 공유 모델이 거주할 수 있는 장소다.
그래프의 데이터 모델은 RDF(Resource Description Framework, 자원 기술 프레임워크)이며, 모든 사실을 트리플(triple) — 주어, 술어, 목적어 — 로 표현한다 [1]. DS-001 derivedFrom PApool-001이 하나의 트리플이다. BATCH-2026-001 monomerPct 98.611이 또 다른 트리플이다. 새로운 관계를 추가할 때 마이그레이션해야 할 고정된 스키마(schema)란 없다. 그냥 트리플을 더 추가하면 된다. 그리고 관계 자체가 외래 키(foreign key)에 의해 암시되는 것이 아니라 데이터로 저장되기 때문에, RDF를 위한 W3C 쿼리 언어인 SPARQL로 그래프에게 그 관계들을 재귀적으로 순회하도록 요청할 수 있다 [2] — SQL은 어색한 재귀 CTE로만, 그것도 미리 작성해 두었을 때만 할 수 있는 일이다. 누구나 추가할 수 있는 그래프에는 관문도 필요하기에, RDF에는 형상에 대해 트리플을 검증하는 제약 언어인 SHACL이 함께 따라온다 [3]. SHACL은 이 장 뒷부분에서 본격적으로 활용한다.
온톨로지: 사물이 무엇인지에 합의하기
사실들을 연결하기 전에, Batch가 무엇인지, CapturePool이 무엇인지, 그리고 derivedFrom이 자식을 부모에 연결한다는 것을 말해 주는 어휘가 필요하다. 그 어휘가 **온톨로지(ontology)**다. 아주 작은 로컬 온톨로지를 직접 쓸 수도 있고 — 우리도 그렇게 할 것이다 — 하지만 진짜 가치는 업계의 나머지가 이미 공유하는 온톨로지에 닻을 내릴 때 나온다.
여기서 중요한 세 가지 개방형 표준이 있고, 이들은 층층이 쌓인다.
- **Allotrope(AFO)**는 실험실 분석(laboratory-analytics) 온톨로지다. 결과, 기기, 시료, 방법에 표준화된 의미를 부여하므로, HPLC 결과는 그것이 여러분의 LIMS에서 나왔든 위탁 실험실에서 나왔든 동일한 것을 의미한다 [4].
- QUDT는 모든 양(quantity)과 단위(unit)에 타입을 부여하므로,
98.611 %는 숫자에 덧붙은 문자열이 아니라 기계가 읽을 수 있는 사실로서 그 차원(dimension)과 단위를 지닌다 [5]. 단위 없는 값은 일어나기를 기다리는 미래의 일탈(deviation)이다. - **산업 온톨로지 파운드리(Industrial Ontologies Foundry, IOF)**는 제조를 위한 BFO 기반의 중간 수준 Core 온톨로지 — 장비, 원료, 공정, 그리고 그들 사이의 관계에 대한 원칙에 입각한 어휘 — 를 발행하며, 바로 우리의 mAb 공정 같은 라인을 겨냥한 바이오의약품(BMIC) 릴리스를 포함한다 [6]. 그 설계 근거(공유된 상위 기반이 왜 "모두가 각자의 클래스를 발명하는" 혼란을 막는지)는 IOF Core 논문에 정리되어 있다 [7].
운영 환경에서는 그러한 온톨로지를 임포트(import)하고 여러분의 공장을 거기에 매핑(map)한다. 노트북에서 실행 가능한 이 장에서는 작은 로컬 네임스페이스(namespace)를 유지하고 개념적으로 정렬한다 — 이것이 리포지토리가 전반에 걸쳐 따르는 솔직한 패턴이다. Turtle로 작성한 어휘는 다음과 같다.
# Illustrative — platform/ontology/bioproc.ttl (the shape you would import & align to IOF/AFO).
@prefix bp: <https://example.org/bioproc#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
bp:Batch a owl:Class ; rdfs:label "Bioreactor batch" .
bp:CapturePool a owl:Class ; rdfs:label "Protein A capture pool" .
bp:SeedTrain a owl:Class ; rdfs:label "Seed train" .
bp:DrugSubstance a owl:Class ; rdfs:label "Drug substance lot" .
bp:DrugProduct a owl:Class ; rdfs:label "Drug product lot" .
bp:derivedFrom a owl:ObjectProperty , owl:TransitiveProperty ;
rdfs:comment "Child material/lot derived from a parent." .
bp:monomerPct a owl:DatatypeProperty ;
rdfs:comment "SEC %monomer release CQA, typed against QUDT." .
derivedFrom이 owl:TransitiveProperty로 선언된 것에 주목하라. 그 단어 하나가 전체의 비결이다. 그것은 어떤 추론기(reasoner)에게든, 의약품이 원료의약품으로부터 파생되고 그 원료의약품이 캡처 풀로부터 파생된다면, 의약품 또한 캡처 풀로부터 파생된다는 것을 — 추이적으로(transitively), 사슬의 맨 아래까지 — 알려 준다.
테이블에서 스레드로: 관계형 사실이 RDF 트리플로 적재되고, 계보는 derivedFrom 엣지의 사슬이 되며, 하나의 SPARQL 쿼리가 그 사슬을 걸어 로트의 전체 혈통을 재구성한다.
Original diagram by the authors, created with AI assistance.
그래프로 바꿀 데이터
그래프는 시뮬레이터가 생성한 세 개의 커밋된 골든 데이터셋(golden dataset)(SIM_SEED=2026)으로 구축되므로, 아래의 모든 숫자는 재현 가능하다. 먼저, 배치와 그 출하 판정 — examples/datasets/batches.csv에서 가져온다.
batch_id,role,release
BATCH-2026-001,golden,PASS
BATCH-2026-002,sibling,PASS
BATCH-2026-003,sibling,PASS
BATCH-2026-004,sibling,OOS
BATCH-2026-005,sibling,PASS
BATCH-2026-006,sibling,PASS
둘째, 로트 계보 — 스레드 자체인 부모/자식 엣지 — 는 examples/datasets/lot_genealogy.csv에서 가져온다.
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
이 다섯 행을 작은 공급망(supply chain)으로 읽어 보라. 하나의 작업 세포은행(working cell bank)(WCB-CHO-001)이 종균 배양에 접종하고, 종균 배양은 바이오리액터 배치에 접종하며, 그 배치의 수확물(harvest)은 Protein A 캡처 풀이 되고, 이것이 정제되어 원료의약품이 되며, 원료의약품이 충전되어 의약품이 된다. 모든 배치가 같은 WCB-CHO-001로 거슬러 올라간다는 점에 주목하라 — 그 공유된 뿌리가 바로 캠페인(campaign) 전체에 걸쳐 셀뱅크 수준의 조사에 답할 수 있게 만든다.
셋째, 출하 분석(assay) — examples/datasets/hplc_results.csv에서 가져온다.
batch_id,test,value,unit,spec_low,spec_high,result
BATCH-2026-001,SEC_monomer_pct,98.611,%,95.0,100.0,PASS
BATCH-2026-001,SEC_HMW_pct,1.287,%,0.0,3.0,PASS
BATCH-2026-001,CEX_main_pct,70.686,%,60.0,80.0,PASS
우리는 각 배치 노드에 SEC %monomer 결과를 핵심품질특성(Critical Quality Attribute, CQA)으로 매달아, 혈통 걷기와 품질 결과가 같은 그래프 안에 살게 할 것이다. 솔직한 한 가지 단서: %monomer는 출하/원료의약품 단계의 SEC 순도 결과지만, 이 장의 단순함을 위해 로더는 그것을 상류의 바이오리액터 배치 노드(build_graph.py는 BP[r.batch_id]에 bp:monomerPct를 추가한다)에 붙인다. 충실한 모델이라면 원료의약품 로트에 붙였을 것이다. 여기서는 바이오리액터 배치 자체가 모노머에 대해 분석되었음을 암시하기보다는 리포지토리의 단순화를 그대로 반영한다.
RDFLib로 그래프 구축하기
완전한 동반 스택(companion stack)에서는 이 트리플들이 Apache Jena Fuseki — 영속적인 트리플스토어에 의해 뒷받침되는 성숙한 오픈소스 SPARQL 1.1 서버 [8] — 에 의해 제공되며, 이는 아래에서 배포한다. 그러나 이 장과 그 테스트를 서비스 없이 실행 가능하게 유지하기 위해, 우리는 RDF를 구성, 직렬화, 쿼리하는 파이썬 라이브러리인 RDFLib [9]로 동일한 그래프를 인프로세스에서 구축한다. 여기 이 장의 핵심 — 로더 — 가 examples/chapters/16-semantics-knowledge-graph/build_graph.py에서 등장한다. 세 개의 CSV를 읽고 트리플을 방출한다.
from rdflib import Graph, Literal, Namespace, RDF
from rdflib.namespace import RDFS, XSD
DATA = Path(__file__).resolve().parents[2] / "datasets"
BP = Namespace("https://example.org/bioproc#")
def build_graph() -> Graph:
g = Graph()
g.bind("bp", BP)
batches = pd.read_csv(DATA / "batches.csv")
gen = pd.read_csv(DATA / "lot_genealogy.csv")
rel = pd.read_csv(DATA / "hplc_results.csv")
for _, b in batches.iterrows():
s = BP[b.batch_id]
g.add((s, RDF.type, BP.Batch))
g.add((s, RDFS.label, Literal(b.batch_id)))
g.add((s, BP.releaseStatus, Literal(b.release)))
각 g.add((subject, predicate, object))는 말 그대로 그래프에 기록되는 하나의 트리플이다. 첫 번째 루프는 모든 배치를, 출하 상태를 지닌 타입이 부여되고 라벨이 붙은 노드로 바꾼다.
계보 루프가 바로 스레드가 짜이는 곳이다. 각 행에 대해 자식과 부모 모두에 타입을 부여한 다음, 둘을 연결하는 derivedFrom 엣지를 추가한다.
# genealogy edges: child bp:derivedFrom parent
for _, e in gen.iterrows():
child, parent = BP[e.child], BP[e.parent]
g.add((child, RDF.type, BP[e.child_type.title().replace("_", "")]))
g.add((parent, RDF.type, BP[e.parent_type.title().replace("_", "")]))
g.add((child, BP.derivedFrom, parent))
if e.parent_type == "bioreactor":
g.add((child, BP.fromBatch, BP[e.parent]))
.title().replace("_", "")는 CSV의 seed_train을 클래스 SeedTrain으로, capture_pool을 CapturePool로 바꾼다 — 관계형 어휘에서 온톨로지 클래스로의 작고 결정론적인(deterministic) 매핑이다. 마지막으로, 출하 결과는 XSD.float로 타입이 부여된(QUDT 정렬은 온톨로지에 산다) CQA를 붙여, 값의 데이터 타입이 추측되는 것이 아니라 명시적이 되게 한다.
# release results: titer attaches to the batch
titer = rel[rel.test == "SEC_monomer_pct"] # any release assay; use monomer as a CQA
for _, r in titer.iterrows():
g.add((BP[r.batch_id], BP.monomerPct, Literal(float(r.value), datatype=XSD.float)))
return g
디지털 스레드 쿼리
이제 보상이다. 그래프가 구축되었으니, RDF를 위한 W3C 표준 쿼리 언어인 SPARQL [2]로 하나의 재귀적 질문을 던진다. 같은 파일에서 가져온 이 쿼리는 원료의약품 로트로부터 derivedFrom 사슬을 거꾸로 거슬러 걷는다.
PREFIX bp: <https://example.org/bioproc#>
SELECT ?step ?type WHERE {
bp:DS-001 (bp:derivedFrom)+ ?step .
?step a ?type .
} ORDER BY ?type
하중을 지탱하는 토큰은 (bp:derivedFrom)+다. 그것은 SPARQL **프로퍼티 경로(property path)**다. +는 "하나 이상의 derivedFrom 홉(hop)을 따라가라"를 뜻한다. 그래서 이 한 줄은 "DS-001이 파생되어 나온 모든 단계를, 몇 단계 떨어져 있든 찾아라"라고 말한다 — 평범한 SQL에서는 고통스러운 바로 그 재귀적 혈통 걷기다.
파일을 처음부터 끝까지 실행하면 — python chapters/16-semantics-knowledge-graph/build_graph.py — 이 실제 출력이 나온다.
graph: 91 triples
digital thread — what DS-001 derives from:
BATCH-2026-001 (Batch)
BATCH-2026-001 (Bioreactor)
PApool-001 (CapturePool)
SEED-001 (SeedTrain)
WCB-CHO-001 (Wcb)
그것이 그래프가 하나의 쿼리로 재구성한, 한 원료의약품 로트의 완전한 혈통이다. 그것이 정제되어 나온 캡처 풀, 그것을 생산한 바이오리액터 배치, 그것에 접종한 종균 배양, 그리고 맨 뿌리의 작업 세포은행이다. 배치가 두 번 나타나는데 — 한 번은 Batch로 타입이 부여되고(배치 루프에서), 한 번은 Bioreactor로(계보 루프에서) — 이는 그 자체로 다중 소스 모델링(multi-source modeling)의 솔직하고 충실한 모습이다. 동일한 물리적 사물이 하나 이상의 클래스를 지니며, 그래프는 둘 다 기꺼이 담는다. 이것이 바로 벤더(vendor)들이 "제조 지식그래프(manufacturing knowledge graph)"로 파는 것의 오픈소스 구현이다.
출하 CQA가 같은 그래프 안에 살기 때문에, 걷기를 확장하여 조사관이 실제로 던지는 질문 — 그리고 그것의 품질 결과는 무엇이었는가? — 에 답할 수 있다. 동반 파일은 두 번째로 검증된 쿼리 THREAD_WITH_CQA_QUERY를 담고 있는데, 이는 혈통을 앞으로 걸어 CQA를 지닌 상류 배치까지 가서 같은 그래프에서 그 %monomer를 읽는다.
PREFIX bp: <https://example.org/bioproc#>
SELECT ?batch ?monomer WHERE {
bp:DS-001 (bp:derivedFrom)+ ?batch .
?batch bp:monomerPct ?monomer .
}
방향이 중요하다. 모노머를 지닌 배치는 DS-001의 *조상(ancestor)*이므로, 경로는 자식들로 거꾸로 되돌아가는 대신 (bp:derivedFrom)+ — 혈통 쿼리와 동일한 연산자 — 로 앞을 가리킨다. 처음부터 끝까지 실행하면 기원이 된 배치와 그 결과를 반환한다.
lineage + quality — originating batch and its release %monomer:
BATCH-2026-001 monomerPct=98.611
혈통과 품질, 하나의 그래프, 하나의 쿼리.
그래프로 그리면, 쿼리가 걷는 사슬은 그저 한 줄의 derivedFrom 엣지이고 출하 CQA가 배치에 매달려 있는 모습이다.
SHACL로 그래프 검증하기
누구나 트리플을 추가할 수 있는 그래프에는 관문이 필요하다, 그렇지 않으면 썩는다. **형상 제약 언어(Shapes Constraint Language, SHACL)**는 형상 제약에 대해 RDF 그래프를 검증하는 W3C 표준이다 — 예를 들어, 모든 Batch가 출하되었다고 주장하기 전에 반드시 출하 상태와 정확히 하나의 모노머 결과를 가져야 한다는 것을 강제하는 방식이다 [3]. 이는 Allotrope의 데이터 모델이 자신의 온톨로지가 어떻게 적용될 수 있는지를 제약하기 위해 사용하는 것과 동일한 메커니즘이다. 최소한의 형상은 다음과 같다.
# Illustrative — platform/ontology/shapes.ttl
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix bp: <https://example.org/bioproc#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
bp:BatchShape a sh:NodeShape ;
sh:targetClass bp:Batch ;
sh:property [ sh:path bp:releaseStatus ; sh:minCount 1 ;
sh:in ( "PASS" "OOS" "PENDING" ) ] ;
sh:property [ sh:path bp:monomerPct ;
sh:datatype xsd:float ; sh:maxCount 1 ] .
SHACL이 바로 지식그래프를 편리한 조회에서 데이터 품질 통제로 바꾸는 것이다. 형상을 위반하는 트리플은 조사 도중 모순으로 떠오르는 대신 적재 시점에 잡힌다.
규모에 맞게 제공하기: Fuseki와 Oxigraph
인프로세스 RDFLib는 한 장과 하나의 단위 테스트에는 완벽하지만, 공장은 많은 시스템이 쿼리할 수 있는 영속적인 SPARQL 엔드포인트(endpoint)가 필요하다. 두 개의 오픈소스 스토어가 적합하다.
- Apache Jena Fuseki는 성숙한 선택지다 — TDB 영속 트리플스토어에 의해 뒷받침되고 서비스로 실행되는 SPARQL 1.1 서버다 [8]. 동반 스택에서
semantics프로파일에 해당한다. 동일한 트리플을 적재하고/digitalthread/sparql을 HTTP로 노출하면 된다.
# Illustrative — load the graph into Fuseki (semantics profile)
curl -X POST --data-binary @bioproc.ttl \
-H "Content-Type: text/turtle" \
http://localhost:3030/digitalthread/data
- Oxigraph는 가볍고 임베드 가능한 대안이다 — RocksDB에 의해 뒷받침되는 SPARQL 1.1 쿼리/업데이트 데이터베이스로, JVM 없이 그래프 스토어를 원할 때 이상적이다 [10].
리포지토리는 한 가지 실용적인 주의 사항을 기록한다. 스택은 공식 Apache 이미지 apache/jena-fuseki:5.2.0을 사용하며, examples/platform/compose/compose.yaml에서 태그로 고정되고, 그에 해당하는 매니페스트 다이제스트(manifest digest)가 examples/platform/versions.lock에 함께 기록되어 있다(22장의 공급업체 등록부가 의존하는 패턴이다). Fuseki에 한해서는 그 다이제스트가 VERIFY-BEFORE-USE 자리표시자(placeholder)로 남겨져 있다. 락 파일과 참조 아키텍처 라이선스 표 모두가 언급하듯, 커뮤니티 Fuseki 이미지가 리포지토리를 옮겼으므로, 태그만 신뢰하기보다는 다이제스트로 배포하기 전에 선택한 레지스트리 미러(registry mirror)에 대한 실제 다이제스트를 직접 해석하고 기록해야 한다.
어느 스토어를 선택하든, 더 깊은 보상은 이 3부작 전체를 관통해 온 것이다. 전역적으로 고유한 식별자(globally unique identifier)를 갖춘 공유 어휘 위의 SPARQL 엔드포인트는, 진정으로 FAIR한 — 찾을 수 있고(Findable), 접근 가능하며(Accessible), 상호운용 가능하고(Interoperable), 재사용 가능하며(Reusable), 기계 작동성(machine-actionability)을 명시적 설계 목표로 삼는 — 데이터로 가는 가장 깔끔한 경로다 [11]. 다른 시스템이 맞춤형 통합 없이 쿼리할 수 있는 그래프, 그것이 상호운용성이며, 구체적으로 실현된 것이다.
왜 중요한가
디지털 스레드는 전문 용어 자랑이 아니다. 그것은 현대적 조사의 문자 그대로의 메커니즘이다. 스마트 제조를 위한 디지털 스레드에 관한 체계적 문헌 검토는, 의미론적 링크 — 지식그래프와 온톨로지 — 를 제품 수명주기 전반에 걸친 추적성(traceability)을 제공하는 연결 조직(connective tissue)으로 식별한다 [12]. DP-004(우리가 시드한 OOS 로트)에서 출하 시험이 실패하면, 질문은 즉시 "혈통을 공유하는 다른 것은 무엇인가?"가 된다 — 그리고 캠페인의 모든 배치가 WCB-CHO-001로 추적되기 때문에, 그래프는 "같은 셀뱅크 또는 같은 캡처 스키드(skid)에서 나온 다른 로트는 무엇인가?"를 단 한 번의 순회로 답할 수 있다. 그것이 *영향 분석(impact analysis)*이며, 범위가 한정된 일탈과 맹목적인 캠페인 전체 격리(quarantine) 사이의 차이다.
이는 규제 당국에게도 중요한데, 혈통과 영향 분석이야말로 데이터 무결성(data integrity)과 조사 기대치가 요구하는 바로 그것이기 때문이다. 즉, 임의의 기록에서 그것이 의존하는 모든 기록까지, 시스템을 가로질러, 재현 가능하게 걸어갈 수 있는 능력이다. 지식그래프는 그 걷기를, 스프레드시트를 일주일간 상호 참조하는 일 대신 하나의 쿼리로 만든다.
실제 현장에서는
상용 벤더들은 이것을 "제조 지식그래프", "맥락화된 데이터 패브릭(contextualized data fabric)", 또는 "셀프서비스 데이터 레이어(self-service data layer)" 같은 이름으로, 대개 히스토리안의 자산 모델 위에 계층화하여 판다. 우리가 하나의 파이썬 파일과 하나의 Turtle 어휘로 구축한 것은, 모든 시스템이 말할 수 있는 개방형 표준으로 표현된 동일한 아이디어다 — 그리고 표준 수렴은 실재한다. Allotrope, IOF/BMIC, QUDT는 정확히 한 사이트의 그래프가 다음 사이트에서도 이해 가능하도록 하기 위한 업계 노력이다. 이것은 탁상공론도 아니다. 2026년의 동료 심사(peer-reviewed) 연구는 이질적인 공정 데이터를 통합하고 엔지니어가 매개변수-결과(CPP-to-CQA) 관계를 직접 쿼리할 수 있게 하는 바이오의약품 지식그래프를 구축했다 — 제조 지식그래프 아이디어가 실제로 작동한 것이다 [13].
이것이 바로 NIIMBL의 SABRE 시설 — 2024년 4월에 착공한 NIIMBL/델라웨어 대학교(University of Delaware)의 파일럿 규모 cGMP(current Good Manufacturing Practice, 현행 우수 제조 관리 기준) 공장 — 이 만들어진 세계다. 서로 다른 스키드, 기기, 실험실 시스템의 데이터가 누구든 그것들을 가로질러 추론하기 전에 하나의 배치, 하나의 로트, 하나의 스레드에 묶여야 하는 다중 벤더 라인이다. SABRE는 데이터 표준이 아니라 시설이지만, 공유된 의미 계층(semantic layer)이 더 이상 선택 사항이 아니게 되는 바로 그런 이질적 환경이다.
이제 솔직한 오픈소스 대 상용의 결산이다. 그래프 기술은 오픈소스에서 진정으로 운영 수준이다. RDF, SPARQL, SHACL, Fuseki, Oxigraph는 성숙하고, 표준을 준수하며, 라이선스 함정이 없다(Jena/Fuseki는 Apache-2, RDFLib는 관대한 BSD, Oxigraph는 MIT). 라이선스 비용 없이 진짜 디지털 스레드를 구축할 수 있다. 순수 오픈소스가 건네주지 않는 것은 GxP 래퍼다. 즉, 소스 시스템에서 그래프로의 검증된(validated) 변경 통제 매핑, GAMP-5 하의 트리플스토어에 대한 공급업체 책임, 그리고 적합성 평가(qualification) 하에서 적재 과정이 완전하고 정확하다는 보증이다. 그래프는 또한 파생된(derived) 뷰다 — 그 트리플은 진실의 관계형 기록에서 복사된 것이므로, 적재가 검증되고 변경 통제 하에 다시 실행되지 않는 한, 그래프는 자신이 반영한다고 주장하는 시스템으로부터 소리 없이 표류(drift)할 수 있으며, 이는 데이터 무결성의 정반대다. 이 책의 다른 곳에서와 마찬가지로, 오픈소스는 깔끔하고 검사 가능한 엔진을 건네준다. 그 주위의 검증된 시스템은 여러분이 직접 구축하거나 사야 한다.
핵심 용어
- 온톨로지(Ontology) — 어떤 개체가 존재하는지(Batch, CapturePool)와 그것들이 어떻게 관계 맺는지(
derivedFrom)를 정의하는, 공유되고 기계가 읽을 수 있는 어휘. 서로 다른 시스템이 같은 것을 의미하게 하는 합의. - RDF(트리플)(RDF, triple) — 자원 기술 프레임워크(Resource Description Framework). 모든 사실이 주어-술어-목적어 트리플이며 고정된 스키마가 없어, 관계가 일급 데이터로 저장된다.
- 지식그래프(Knowledge graph) — 배치, 장비, 원료, 레시피, 결과를 하나의 탐색 가능한 전체로 연결하는 RDF 트리플의 그래프.
- 디지털 스레드(Digital thread) — 제품을 그 수명주기 전반에 걸쳐 추적하는 연결된 기록의 끝에서 끝까지 이어진 사슬. 여기서는 의약품에서 셀뱅크까지 거슬러 가는
derivedFrom혈통. - SPARQL 프로퍼티 경로(SPARQL property path) — 관계를 재귀적으로 따라가는 쿼리 연산자(예:
(derivedFrom)+)로, 임의 깊이의 혈통 걷기를 하나의 구문으로 가능하게 한다. - SHACL — 형상 제약 언어(Shapes Constraint Language). 형상 제약에 대해 RDF 그래프를 검증하여, 잘못된 트리플이 그래프에 들어오기 전에 데이터 품질을 강제한다.
- IOF / Allotrope (AFO) / QUDT — 그래프가 정렬되는 개방형 표준들. 각각 제조 온톨로지, 실험실 분석 온톨로지, 단위-양 온톨로지다.
- 추이적 프로퍼티(Transitive property) — A→B와 B→C가 A→C를 함의하는 OWL 관계(
derivedFrom같은)로, 추론기가 전체 사슬을 추론할 수 있게 한다. - 트리플스토어(Triplestore) — SPARQL을 통해 RDF 트리플을 저장하고 쿼리하는 데이터베이스. 여기서는 Apache Jena Fuseki 또는 임베드 가능한 Oxigraph.
다음 이야기
우리는 데이터가 어디서나 같은 것을 의미하게 만들었고, 실제 캠페인 데이터에 대해 한 번의 쿼리로 디지털 스레드를 증명했다. 그러나 그래프는, 그 앞의 맥락화 뷰와 마찬가지로, 숫자가 이미 우리의 오픈 히스토리안에 산다고 가정한다. 대부분의 공장은 수십 년의 공정 데이터를 담은 상용 히스토리안을 가지고 있다 — 그리고 그것이 우리가 다음에 건너갈 경계다. 17장 — 상용 히스토리안으로 다리 놓기: AVEVA/OSIsoft PI에서는 PI Web API로의 양방향 다리(충실한 목업에 대해, 운영 환경의 기본 URL과 자격 증명 교체를 문서화하여)를 구축하여, 오픈 스택과 진실의 검증된 상용 시스템이 마침내 데이터를 교환할 수 있게 한다.