본문으로 건너뛰기

스택 띄우기: docker compose up 한 번으로

📍 현재 위치: 1부 2장 — 지난 장에서 설계도를 읽었으니, 이제 노트북 위에서 플랫폼 전체를 부팅합니다. 그래야 이후 모든 장에서 실제로 돌려볼 대상이 생깁니다.

쉽게 말하면

동반 저장소(companion repo)를 조립식 데이터 공장이라고 생각하세요. 데이터베이스, 메시지 브로커(message broker), 대시보드, 바이오리액터 시뮬레이터(bioreactor simulator) — 모든 기계가 저마다 밀봉된 상자(컨테이너, container)에 담겨 옵니다. 한 장의 설명서(compose.yaml)가 어떤 상자를 열지, 서로 어떻게 연결할지, 각각이 제대로 켜졌는지 어떻게 확인할지를 알려줍니다. 명령 하나를 입력하면 공장이 스스로 조립됩니다. 다른 명령 하나를 입력하면 다시 상자 안으로 접혀 들어가며, 여러분의 컴퓨터에 아무 흔적도 남기지 않습니다.

이 장에서 다루는 내용

이 장은 이 책의 실습 전환점입니다. 끝날 무렵이면 여러분은 저장소 하나를 클론(clone)하고, 실제로 작동하는 멀티서비스 바이오프로세스 데이터 플랫폼을 노트북 한 대 위에 띄워 놓게 됩니다. 우리는 다음을 합니다.

  • 핵심 스택 전체를 정의하는 **단 하나의 compose.yaml**을 훑어보고, 각 서비스가 왜 거기 있는지 설명합니다.
  • 고정된 이미지 태그(pinned image tag)가 왜 중요한지 설명합니다 — influxdb:latest→v3 라이선스 함정이 그 교훈담입니다.
  • 이 책이 그대로 출력하는 명령 표면(command surface)인 Makefile을 실행합니다.
  • **첫 데이터 포인트 스모크 테스트(smoke test)**로 스택이 살아 있음을 확인합니다.
  • 이 책 전체가 데이터를 공급받는 원천인 결정론적 CHO 시뮬레이터를 만나봅니다.

아래의 모든 것은 examples/에 실제로 존재하며 직접 실행된 파일에서 나온 것입니다. 지어낸 플래그도, 지어낸 출력도 없습니다.

파일 하나에 핵심 전부

현대적인 컨테이너 플랫폼은 일련의 서비스를 선언적으로(declaratively) 기술할 수 있게 해줍니다 — 각 서비스가 어떤 이미지를 실행하는지, 어떤 포트를 노출하는지, 어떤 볼륨(volume)을 마운트하는지, 정상 상태인지를 어떻게 판단하는지 — 그리고 단일 명령으로 이 모두를 띄웁니다 [1]. 그 기술서는 정식 산출물입니다. Compose 명세(Compose Specification)가 서비스, 네트워크, 볼륨의 스키마를 정의하므로, 같은 YAML이 여러분의 컴퓨터에서도, 동료의 컴퓨터에서도, CI 러너(CI runner)에서도 동일하게 동작합니다 [2].

다음은 examples/platform/compose/compose.yaml에 있는 실제 파일의 윗부분입니다.

# compose.yaml — the base stack for "From Sensor to Submission".
# One file defines every service; Docker Compose PROFILES gate what comes up so a
# reader only pays for the chapter they are on:
# core Ch 1-4, 13-15 (db + broker + dashboards; the CHO simulator is a
# separate Python package run via `make data`)
# capture Ch 5-12 (collectors, edge gateway, ingesters)
# semantics Ch 16 (triplestore)
# trust Ch 20-21 (identity, signing, object store)
# analytics Ch 26 (notebooks, model tracking)
# Bring up just the foundation with: docker compose --profile core up -d
#
# Images are pinned by tag for reproducibility; the matching manifest digests are
# recorded in versions.lock (revisited in the supply-chain chapter, Ch 22).

name: sensor-to-submission

핵심 설계 아이디어는 두꺼운 공유 플랫폼 위의 얇은 장(chapter)들입니다. 이 책의 모든 서비스는 이 파일 하나에 정확히 한 번만 선언되고, Compose 프로파일(profile)(core, capture, semantics, trust, analytics)로 태그가 붙습니다. docker compose --profile core up은 1~4장에 필요한 것만 — 대략 3 GB의 RAM 정도 — 시작하고, 이후 각 부(Part)에서 프로파일을 하나씩 더 켭니다. 스택을 다시 선언하는 일은 결코 없으며, 그저 프로파일을 켜기만 하면 됩니다. 여러분 노트북의 메모리와 CPU 사용량은 지금 실제로 읽고 있는 장에 맞춰 늘어납니다.

core 프로파일은 항상 켜져 있는 기반입니다. examples/platform/compose/compose.yaml에 담긴 한 가지 의도적 선택에 주목하세요.

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"]
<<: *restart
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
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-bioproc} -d ${POSTGRES_DB:-bioproc}"]
interval: 5s
timeout: 5s
retries: 20

별도의 "시계열 데이터베이스(time-series database)" 컨테이너는 없습니다. timescale/timescaledb 이미지는 그 자체로 PostgreSQL [3]이며, TimescaleDB 확장(extension)이 이미 설치되어 있습니다 [4]. 이 단 하나의 결정이 이 책의 나머지 내내 빛을 발합니다. 고속의 센서 이력과 ISA-88/95 배치 모델이 같은 데이터베이스 안에 살기 때문에, 하나의 쿼리로 "용존 산소(dissolved oxygen) 프로브가 14:32에 읽은 값"과 "어떤 배치와 레시피 단계(recipe phase)가 실행 중이었는지"를 시스템 간 데이터 복사 없이 조인(join)할 수 있습니다. 문맥화(contextualization) 장에서 우리는 이 조인에 크게 기댈 것입니다.

그 블록에는 제 몫을 하는 두 가지 세부 사항이 더 있습니다. volumes 줄은 ../db를 PostgreSQL의 첫 부팅 초기화 디렉터리에 마운트하므로, 번호가 매겨진 스키마 파일(00-init.sql부터 60-views.sql까지)이 데이터베이스가 처음 시작될 때 자동으로 실행됩니다 — 스키마가 수동 단계가 아니라 코드인 것입니다. 그리고 healthcheck는 5초마다 pg_isready를 실행하여, 플랫폼이 데이터베이스가 언제 연결을 받아들일 준비가 되었는지를 추측이 아니라 확실히 알 수 있게 합니다. 헬스체크(healthcheck)는 이후 장의 테스트가 실행 전에 깨끗한 의존성을 기다리는 방법입니다.

core의 나머지는 브로커, 대시보드, 그리고 다른 프로파일 아래에 얹혀 가는 트리플스토어/메트릭 한 쌍입니다.

mosquitto:
image: eclipse-mosquitto:2.0.22
profiles: ["core"]
<<: *restart
ports: ["1883:1883"]
volumes:
- ../mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
healthcheck:
test: ["CMD-SHELL", "mosquitto_sub -t '$$SYS/#' -C 1 -W 3 -h localhost || exit 1"]
interval: 10s
timeout: 5s
retries: 10

grafana:
image: grafana/grafana-oss:11.4.0
profiles: ["core"]
<<: *restart
ports: ["3000:3000"]
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- ../dashboards/provisioning:/etc/grafana/provisioning:ro
- grafana:/var/lib/grafana
depends_on:
postgres:
condition: service_healthy

mosquitto는 MQTT 브로커입니다 — 캡처(capture) 장에서 시뮬레이터 텔레메트리(telemetry)가 흘러갈 경량 발행/구독(publish/subscribe) 메시지 버스입니다 [5]. grafana-oss는 히스토리안(historian)을 쿼리하여 이 책이 만드는 배치 오버레이(batch-overlay) 차트와 골든 배치(golden-batch) 차트를 그리는 대시보드 계층입니다 [6]. grafanapostgres에 대해 condition: service_healthydepends_on을 선언한 점에 주목하세요. 데이터베이스가 헬스체크를 통과하기 전까지 Grafana는 그리기를 시작하지 않습니다. 두 서비스 fuseki(지식 그래프 트리플스토어, knowledge-graph triplestore)와 victoriametrics(스택 자체 모니터링)는 semanticsanalytics/ops 프로파일 뒤에 자리하며, 해당 부에 이르기 전까지는 휴면 상태로 있습니다.

Telegraf와 Node-RED — 플러그인 기반 메트릭 에이전트(metrics agent) [7]와 브라우저 기반 로우코드(low-code) 플로우 에디터 [8] — 는 엣지 게이트웨이(edge-gateway) 장에 속하기 때문에 core 프로파일에 없습니다. 이들은 나중에 capture/edge 프로파일과 함께, 정확히 같은 방식으로 켜집니다. 요점은, 지금 여러분이 읽고 있는 이 파일이 이미 그들의 자리까지 담고 있다는 것입니다.

From Sensor to Submission 핵심 스택의 계층형 다이어그램: 단일 compose.yaml이 프로파일로 게이팅된 컨테이너 — PostgreSQL+TimescaleDB, Mosquitto, Grafana — 를 띄우고, Docker 네트워크로 연결하며, CHO 시뮬레이터는 별도의 Python 패키지로서 데이터셋을 안으로 공급하고, make 타깃이 up, seed, data 및 스모크 테스트를 구동한다.

하나의 Compose 파일이 고정되고 헬스체크된 몇 개의 컨테이너로 펼쳐지고, Makefile은 여러분이 입력하는 유일한 명령 표면이며, 프로파일이 공장의 어느 만큼에 전원을 넣을지 결정합니다. Original diagram by the authors, created with AI assistance.

왜 고정된 태그가 중요한가 (latest 함정)

모든 image: 줄을 다시 보세요. timescale/timescaledb:2.17.2-pg17, eclipse-mosquitto:2.0.22, grafana/grafana-oss:11.4.0. 그 어느 것도 :latest라고 적혀 있지 않습니다. 이것은 까다로움이 아닙니다. 재현 가능한 공장과 시한폭탄 사이의 차이입니다.

컨테이너는 OCI 이미지로 배포됩니다 — 콘텐츠 주소 지정(content-addressable) 매니페스트와 레이어들로 이루어지며, 불변의 다이제스트(immutable digest)로 식별됩니다 [9]. 2.17.2-pg17 같은 *태그(tag)*는 그런 다이제스트 하나를 가리키는 친근한 라벨이고, latest 같은 태그는 메인테이너(maintainer)가 가장 최근에 푸시한 무엇이든을 가리킵니다. 시맨틱 버저닝(semantic versioning)이 태그에 의미를 부여합니다. MAJOR.MINOR.PATCH이며, MAJOR가 올라가면 호환성을 깨는 변경(breaking change)을 알립니다 [10]. 버전을 고정하면, 업그레이드가 무엇을 깨고 무엇을 깨지 않을지 추론할 수 있습니다.

대표적인 공포담은 InfluxDB입니다. 2024년에 influxdb:latest라고 적었던 독자는 어느 날 아침 InfluxDB 3를 내려받게 되었습니다 — 저장 엔진이 바뀌고 라이선스 태도까지 바뀐 거의 전면 재작성판이, 아무 경고도 없이 조용히, 그 자리에서요. 이 책은 이런 부류의 사고 전체를 비켜갑니다. InfluxDB를 피하고(대신 Apache-2.0 라이선스의 VictoriaMetrics를 제공합니다), 더 중요하게는 모든 것을 고정함으로써 말입니다. compose.yaml은 각 이미지를 사람이 읽을 수 있는 태그로 고정하고, 동반 파일 platform/versions.lock은 각각에 대응하는 불변 매니페스트 다이제스트(<image:tag> sha256:…)를 기록합니다. 공급망(supply-chain) 장(22장)은 이 잠금 파일을 바탕으로 실행 중인 스택, 라이선스 인벤토리, 공급자 등록부를 하나의 고정된 목록에 대조하여 — 이들이 조용히 어긋나지 않도록 합니다.

버전을 고를 때 짚어둘 만한 2026년의 라이선스 함정 몇 가지가 있습니다. 조용히 물기 때문입니다. TimescaleDB의 컬럼스토어/압축 및 HA 기능은 TSL 라이선스 아래에 있으므로, 우리는 Apache-2.0 OSS 빌드를 실행하고 OSS에 안전한 파티셔닝과 보존(retention)만 사용합니다. Grafana는 AGPL-3.0입니다(로컬에서 실행하는 것은 전혀 문제없지만, 재배포하거나 타인을 위한 서비스로 호스팅하면 의무가 발생합니다). InfluxDB v3, EMQX의 BSL, Redpanda의 RCL은 이 스택이 의도적으로 피해 가는 다른 지뢰들입니다.

Makefile이 명령 표면이다

이 책에서 여러분은 날것의 docker compose 주문을 결코 입력하지 않을 것입니다. 모든 동작은 make를 거치며, 책은 여러분이 입력하는 그대로를 출력합니다. 다음은 실제 examples/Makefile입니다.

COMPOSE := docker compose -f platform/compose/compose.yaml
PY := sim/.venv/bin/python
export DATABASE_URL ?= postgresql://bioproc:bioproc@localhost:5432/bioproc

.DEFAULT_GOAL := help
.PHONY: help venv up down seed data load contextualize alcoa soft-sensor test clean

help: ## list targets
@grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN{FS=":.*?## "}{printf " %-14s %s\n", $$1, $$2}'

venv: ## create the Python env and install the simulator (uv)
cd sim && uv venv --python 3.12 .venv && uv pip install --python .venv -e . "psycopg[binary]" scikit-learn

up: ## bring up the core stack (postgres+timescale, mosquitto, grafana)
$(COMPOSE) --profile core up -d
@echo "waiting for postgres..." && sleep 3
@until docker exec sensor-to-submission-postgres-1 pg_isready -U bioproc >/dev/null 2>&1; do sleep 2; done
@echo "core stack up."

make help## 주석으로부터 스스로 문서화하므로, 메뉴와 코드가 결코 어긋나지 않습니다. make upcore 프로파일을 띄운 다음 pg_isready가 성공할 때까지 블록(block)되므로, 데이터베이스가 실제로 연결을 받을 수 있게 되기 전까지는 "완료"를 반환하지 않습니다. 그 폴링 루프(polling loop)는 "컨테이너가 시작되었다"와 "서비스가 준비되었다" 사이의 작지만 정직한 차이입니다.

기반을 위한 전체 빌드 순서는 짧습니다.

make venv # Python env + the simulator (uv)
make data # generate every dataset deterministically + MANIFEST.sha256
make up # bring up the core stack (postgres+timescale, mosquitto, grafana)
make seed # load the ISA-88/95 reference CHO line
make load # load the datasets into the historian + lab tables

make down은 스택을 멈추되 데이터를 이름 붙은 볼륨(named volume)에 보존합니다. 진짜 깨끗한 새 출발을 원할 때는 make cleandocker compose down -v를 실행하여 볼륨을 삭제합니다. 전체 환경이 선언적 파일 하나에 명령 하나이므로, 이를 허물어도 여러분의 컴퓨터 곳곳에 흩어진 흔적이 남지 않습니다.

첫 데이터 포인트: 스모크 테스트

서비스를 띄우는 것과, 플랫폼이 처음부터 끝까지 작동함을 증명하는 것은 같지 않습니다. 이 스택의 스모크 테스트는 가능한 한 가장 단순한 질문입니다. 숫자 하나가 히스토리안에 안착하고, 배치에 조인되어 되돌아올 수 있는가?

make up && make seed && make load 이후, 시뮬레이터의 데이터셋이 PostgreSQL+TimescaleDB 안에 들어옵니다. 첫 번째 온전성 검사(sanity check)는 히스토리안 하이퍼테이블(hypertable)에 직접 들어온 것을 세어보는 것입니다.

docker exec -e PGPASSWORD=bioproc sensor-to-submission-postgres-1 \
psql -U bioproc -d bioproc \
-c "select tag, count(*), round(min(value)::numeric,2) lo, round(max(value)::numeric,2) hi \
from ts.sensor_reading where batch_id='BATCH-2026-001' group by tag order by tag limit 4;"
tag | count | lo | hi
---------------+-------+-------+-------
BR101.DO.PV | 20160 | 30.04 | 43.77
BR101.Temp.PV | 20160 | 36.36 | 37.12
BR101.Titer.PV| 20160 | -0.11 | 5.82
BR101.pH.PV | 20160 | 6.91 | 7.08

이 범위들은 유가식(fed-batch) 공정이 스스로에 대해 진실을 말하는 것입니다. 온도는 37 °C 근처를 유지했고, pH는 대략 6.97.1을 오갔으며, 용존 산소는 약 3044 %sat 사이를 탔고, 역가(titer)는 사실상 0(접종 시점의 약간 음수인 측정값으로, 소프트 센서(soft-sensor) 노이즈에서 비롯됩니다)에서 시작해 한 회분(run) 동안 약 6 g/L까지 올라갔습니다. 각 태그는 20,160개의 행을 가집니다 — 14일 배치 전체에 걸쳐 1분에 하나씩입니다(make load가 적재하는 풀 해상도 fedbatch_timeseries.parquet이며, datasets/에는 파일 재생(file-replay) 장을 위한 10분 간격 CSV 다운샘플도 함께 들어 있습니다).

그러나 진짜 스모크 테스트는 조인입니다. make contextualize(문맥화 장에서 제대로 만듭니다)는 같은 스택에 대해 정확히 이 쿼리를 실행합니다.

select phase_name, count(*) n, round(avg(value)::numeric,1) avg_DO
from s88.v_batch_sensor where batch_id='BATCH-2026-001' and tag='BR101.DO.PV'
group by phase_name order by min(ts);

이것이 레시피 *단계(phase)*별로 나뉜 용존 산소 평균을 반환한다면, 플랫폼은 중요한 의미에서 살아 있는 것입니다. 히스토리안에 캡처된 날것의 센서 값이, 하나의 쿼리 안에서 자신의 ISA-88 공정 문맥과 다시 결합된 것입니다. 그것이 곧 축소판 플랫폼 전체이며, 이 책의 나머지가 그 위에 세워지는 증거입니다.

이 책 전체가 데이터를 공급받는 CHO 시뮬레이터

이 책에는 진짜 바이오리액터가 없으므로, 결정론적인 것을 하나 제공합니다. Python 패키지 bioproc_sim(make venv로 설치되고 make data로 구동됨)은 이 책의 모든 데이터셋을 하나의 고정 마스터 시드(master seed) SIM_SEED=2026으로부터 생성하므로, 14일 유가식 트레이스(trace)는 모든 컴퓨터에서 바이트 단위로 동일합니다. 그 결정론(determinism)은 눈속임이 아닙니다 — 그것이 바로 CI가 MANIFEST.sha256을 단언(assert)하고 데이터의 조용한 변동(drift)을 잡아낼 수 있게 하는 것입니다.

이 유가식 회분은 로지스틱 생존 세포(viable-cell) 성장, 모노드(Monod) 글루코스/글루타민 동역학(lactate는 성장 중 부산물로 생성되고 후기에 소비되며, 제한 기질이 아닙니다), 생존 세포의 적분과 함께 누적되는 항체 역가(성장 연관 생산 항), 그리고 경계가 있는 노이즈를 가진 PID 제어 DO 및 pH로 CHO 배양을 모델링합니다. 심지어 7일 차에 의도적인 0.5 °C 일탈(excursion)을 심고, 3, 5, 7, 9, 11, 13일 차에 예정된 볼러스 피드(bolus feed)를 넣어 — 이후 장에서 감지하고, 경보를 울리고, 검토할 실제 이벤트가 있도록 합니다. 골든 트레이스의 한 행은 다음과 같습니다.

ts,tag,value,unit,quality,batch_id
2026-01-05 00:00:00+00:00,BR101.DO.PV,40.8224,%sat,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.Temp.PV,37.0145,degC,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.Titer.PV,-0.0045,g/L,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.pH.PV,7.0511,pH,192,BATCH-2026-001

quality 열은 OPC UA StatusCode 심각도(192 = Good, 64 = Uncertain, 0 = Bad)를 담고, unit 열은 공학 단위(engineering unit)를 숫자에 붙여 두며, batch_id는 모든 판독값을 ISA-88/95 모델로 다시 묶는 실입니다. 같은 엔진은 캡처 장을 위해 OPC UA 서버와 Mosquitto로 실시간 스트리밍할 수도 있고, 프로듀서(producer)를 부팅하지 않고 파일을 재생하며 따라가고 싶은 장을 위해 평평한 골든 파일을 datasets/에 덤프할 수도 있습니다. 하나의 시드, 하나의 단일 진실 원천(source of truth), 책 속 모든 숫자.

왜 중요한가

다운스트림의 모든 것 — 히스토리안, 배치 모델, 문맥화, 감사 체인(audit chain), 소프트 센서 — 은 작동하는, 재현 가능한 기반을 전제합니다. 이를 잘못 잡으면 이후 모든 장이 그 불안정함을 물려받습니다. 제대로 잡으면, 이 책은 읽는 것이 아니라 실행하는 무언가가 됩니다.

규제 측면의 배당도 있습니다. 버전이 고정되고, 선언적이며, 자동화된 환경은 정확히 적격성 평가(qualification) 작업이 원하는 산출물입니다. GAMP 5(2판)는 GxP 컴퓨터화 시스템을 위한 위험 기반 라이프사이클을 제시하며, 인프라 적격성 평가와 오픈소스 소프트웨어에 명시적인 주의를 기울입니다 [11]. 인프라가 코드일 때 — Compose 파일 하나, 다이제스트 잠금 파일 하나(versions.lock), Makefile 하나 — 여러분의 설치(installation) 증거는 누군가의 터미널 스크린샷이 아니라 재현 가능하고 검토 가능한 것이 됩니다. FDA의 컴퓨터 소프트웨어 보증(Computer Software Assurance) 최종 가이던스도 같은 방향을 가리킵니다. 보증은 위험 기반이고 최소 부담이어야 하며, 의례적 문서화보다 로그, 자동화, 공급자 증거에 기대야 한다는 것입니다 [12]. 고정된 스택을 부팅하고 헬스체크를 통과하는 깔끔한 make up은 바로 그러한 프레임워크가 보상하는, 객관적이고 반복 가능한 증거입니다.

실제 현장에서는

물론 실제 공장은 노트북 한 대의 Compose 파일로 돌아가지 않습니다. 프로덕션 히스토리안은 전용의 고가용성(highly-available) 하드웨어 위에서 돌아가는 AVEVA PI일 수 있고, DCS는 Emerson DeltaV나 Siemens일 수 있으며, LIMS는 상용이고 검증(validated)되어 있습니다. 이런 시스템들은 노트북에서 돌아갈 수 없고 오픈소스도 아닙니다 — 그래서 이 책은 자신이 *하이브리드(hybrid)*임을 정직하게 밝힙니다. 여기 있는 오픈소스 핵심은 여러분을 어쩌면 80% 지점까지 데려다주고, GxP의 마지막 1마일(Part 11 전자 서명, 벤더 책임성, 검증된 HA)은 상용 시스템과 정식 검증이 넘겨받는 지점입니다. 이 스택의 어떤 도구도 즉시 Part 11을 준수하지 않으며, 그렇지 않다고 말하는 것은 엔지니어링이 아니라 마케팅일 것입니다.

그러나 이 장이 세우는 아키텍처는, 단지 노트북 규모일 뿐, 큰 회사들이 쓰는 것과 똑같습니다. 관계형 기록 시스템 옆의 시계열 히스토리안, 현장(floor)과 IT 사이의 메시지 버스, 그 위의 대시보드. NIIMBL — 미국의 민관 합동 바이오의약품 제조 혁신 연구소(Institute for Innovation in Biopharmaceutical Manufacturing) — 와 그 SABRE 시설(델라웨어 대학교에서 건설 중인 파일럿 규모 현행 우수 제조 관리 기준, 즉 cGMP 시설로, 2024년 4월 착공)은 바로 이런 현대적이고 데이터 풍부한 제조가 상용 라인에 도달하기 전에 위험을 줄이기 위해 존재합니다. 재현 가능하고 프로파일로 게이팅된 개발 스택은 클린룸 없이도 그 아키텍처를 실험하는 방법이며 — 같은 Compose-and-Make 규율은 실제 시설이 요구할 IQ/OQ 증거로 곧장 확장됩니다. 정직한 간극은 데이터 공장의 형태가 아니라 운영적 특성 — 가동 시간 보장, 인증된 지원, 정식 검증 패키지 — 에 있습니다.

핵심 용어

  • 컨테이너 / OCI 이미지(Container / OCI image) — 애플리케이션과 그 의존성을 밀봉해 담은 이식 가능한 묶음으로, 불변의 콘텐츠 다이제스트로 식별되며, 각 서비스가 배포되는 단위.
  • Docker Compose / compose.yaml — 멀티서비스 애플리케이션을 정의하고 명령 하나로 모두 띄우는 선언적 파일(이자 도구).
  • 프로파일(Profile) — 어떤 서비스가 시작될지를 게이팅하는 Compose 라벨로, 독자가 현재 장에 필요한 계층(core, capture, semantics, trust, analytics)만 켤 수 있게 함.
  • 태그 고정(Tag pinning) — 이미지를 :latest가 아니라 특정 MAJOR.MINOR.PATCH 버전(및 다이제스트)으로 고정하여, 환경이 재현 가능하고 업그레이드가 의도적이게 만드는 것.
  • 헬스체크(Healthcheck) — 서비스가 실제로 준비되었는지 판단하기 위해 플랫폼이 실행하는 명령으로, 의존하는 서비스(및 테스트)가 올바르게 기다릴 수 있게 함.
  • 히스토리안(Historian) — 고속 공정 데이터를 위한 시계열 저장소. 여기서는 배치 모델과 같은 PostgreSQL 데이터베이스 안의 TimescaleDB 하이퍼테이블.
  • 하이퍼테이블(Hypertable) — 빠른 시계열 쓰기와 쿼리를 위해 시간 기준으로 자동으로 청크(chunk)로 파티셔닝되는 TimescaleDB의 PostgreSQL 테이블.
  • SIM_SEED=2026 — CHO 시뮬레이터의 출력을 어디서나 바이트 단위로 동일하게 만드는 고정 마스터 시드로, 데이터셋이 재현 가능하고 CI가 이를 검증할 수 있게 함.

다음 이야기

스택이 떠 있고 시뮬레이터의 숫자들이 PostgreSQL에 들어와 있습니다 — 하지만 지금 BR101.DO.PV = 48.6 같은 행은 라벨이 붙은 부동소수점(float)일 뿐입니다. 그것이 무언가를 의미하게 하려면, 그 판독값이 어떤 장비, 어떤 레시피, 어떤 단계, 어떤 배치에 속하는지를 말해주는 골격이 필요합니다. 다음 장 배치 및 장비 데이터 모델: PostgreSQL 속의 ISA-88/95는 바로 그 관계형 등뼈를 세웁니다 — 이후의 모든 숫자를 한 배치에 관한 사실로 바꾸는 모델입니다.