불가능한 Normalization: 왜 당신의 Database는 생물학을 싫어할까 🧬
Source: Dev.to
번역을 진행하려면 번역이 필요한 전체 텍스트를 제공해 주시겠어요?
코드 블록, URL 및 마크다운 형식은 그대로 유지하면서 본문만 한국어로 번역해 드리겠습니다.
User Objects
우리는 User Objects에 대해 이야기해야 합니다.
웹 개발을 5분 이상 해본 사람이라면, 아마도 전자상거래 백엔드나 할 일 관리 앱을 만든 적이 있을 겁니다. 데이터 모델링은 보통 다음과 같습니다:
User가 존재한다.Product가 존재한다 (SKU, 가격, 설명을 가지고 있다).- 사용자가 그 제품을 구매한다.
이것은 깔끔하고 결정론적입니다. 내가 *레드 티‑셔츠 (사이즈 L)*를 구매하면, 그 객체는 변하지 않습니다. 창고에 보관되고, 배송되어, 레드 티‑셔츠 그대로 도착합니다.
이제, 레드 티‑셔츠가 날씨에 따라 스스로 사이즈를 바꾸거나 “약간 붉은‑핑크색인데 만지면 아프다”라고 스스로 설명할 수 있다고 상상해 보세요.
HealthTech에 오신 것을 환영합니다.
“간단한” 증상 함정
환자들이 증상을 추적할 수 있는 앱을 만든다고 가정해 봅시다. 우리 안의 주니어 개발자는 즉시 생각합니다: “쉽다. SQL 테이블.”
CREATE TABLE symptoms (
id SERIAL PRIMARY KEY,
user_id INT,
name VARCHAR(255), -- "Headache"
severity INT, -- 1 to 10
created_at TIMESTAMP
);
그대로 배포하면 되겠죠?
Wrong.
이틀 후, 사용자가 이렇게 씁니다: “제 두통이 10점 만점에 7점인데, 구체적으로 왼쪽 눈 뒤에 있고 일어설 때 맥박이 뛰어요.”
name 컬럼이 이제 깨졌습니다. 위치(왼쪽 눈), 특성(맥박), 악화 요인(일어설 때)이 필요합니다.
그래서 이렇게 생각합니다: “좋아, NoSQL 문서 저장소로 바꾸자. JSON 블롭을 그냥 덤프하면 되겠어!”
{
"symptom": "Headache",
"meta": {
"location": "Left retro-orbital",
"quality": "Pulsating",
"trigger": "Orthostatic"
}
}
좋습니다. 이제 10,000명의 사용자 중에서 “머리 문제”가 있는 모든 사람을 찾기 위해 쿼리해 보세요. 할 수 없습니다. 왜냐하면 사용자 A는 “Headache,” 사용자 B는 “Migraine,” 그리고 사용자 C는 “My head hurts.”라고 적었기 때문입니다.
온톨로지를 소개합니다: SNOMED‑CT와 LOINC
이를 해결하기 위해 업계에서는 표준 용어 체계를 만들었습니다. 여기서 가장 큰 역할을 하는 것이 SNOMED‑CT이며, 이는 존재하는 거의 모든 의료 개념에 고유 코드를 할당합니다. 두통은 “Headache”가 아니라 SCTID: 25064002입니다.
이는 개발자에게는 꿈같은 (정규화!) 상황처럼 보이지만, 실제 구현에서는 악몽이 됩니다. 생물학은 리스트가 아니라 그래프이기 때문입니다. 특정 유형의 두통은 “Pain”(통증)의 자식이며, “Pain”은 “Clinical Finding”(임상 소견)의 자식입니다.
이를 적절히 저장하기 위해 보통 FHIR(Fast Healthcare Interoperability Resources)를 사용합니다. 아래는 체온 관찰을 위한 간소화된 JSON 예시입니다:
{
"resourceType": "Observation",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8310-5",
"display": "Body temperature"
}
]
},
"valueQuantity": {
"value": 39.1,
"unit": "degrees C",
"system": "http://unitsofmeasure.org",
"code": "Cel"
}
// ... timestamps, performers, device used ...
}
우리는 temperature: 39.1이라는 단순 표현에서 30줄에 달하는 JSON 객체로 옮겨갔습니다. 생물학에서는 맥락이 모든 것이기 때문입니다. 구강으로 측정한 39.1 °C와 겨드랑이(axillary)에서 측정한 39.1 °C는 다릅니다. 메타데이터를 저장하지 않으면 데이터는 의료적으로 위험해집니다.
지도는 영토가 아니다
엔지니어가 직면하는 가장 큰 철학적 장벽은 인간의 건강은 연속적이지만 데이터베이스는 이산적이다는 점입니다.
사용자가 드롭다운에서 “중간 통증”을 선택하면, 복잡하고 유동적인 생물학적 감각을 명확한 열거형(enum)으로 축소하게 되며, 그 과정에서 해상도가 손실됩니다.
이 데이터 해상도 손실에 대해 더 자세히 읽고 싶다면 제 개인 블로그에서 다룬 글을 확인해 보세요—건축 이론에 관심이 있다면, 다른 기사도 확인해 보세요.
도전 과제는 “어디가 아프세요?”와 같이 인간 친화적인 UI를 만들면서, 사용자가 해당 표준을 알 필요 없이도 FHIR와 SNOMED 같은 복잡하고 엄격한 온톨로지에 매핑하는 것입니다.
그렇다면 해결책은?
- 원시 의도 저장: 사용자가 실제로 입력하거나 클릭한 내용을 보관합니다. 진실의 원천을 절대 버리지 마세요.
- 나중에 매핑: “My head hurts”(머리가 아파요)를
SCTID: 25064002로 변환하는 인제스트 파이프라인을 사용합니다. 사용자가 SNOMED을 사용하도록 강요하지 마세요. - 하이브리드 스키마: 고수준 데이터(환자 ID, 날짜)에는 관계형 컬럼을, 임상 페이로드(FHIR 리소스)에는
JSONB컬럼을 사용합니다.
이것은 엉망이고 답답하며 티셔츠를 파는 것보다 확실히 어렵습니다. 하지만 적어도 지루하지는 않죠.
지금 HL7/FHIR 통합으로 고생하고 있는 사람 있나요? 댓글에서 위로합시다. 😭