들어가며

의료 정보 시스템을 구축할 때 가장 먼저 직면하는 질문은 “어떤 표준 코드를 사용할 것인가?”입니다.

MediDaily는 사용자의 건강 정보를 체계적으로 관리하고 AI 검색으로 증상에 맞는 의료 칼럼을 추천하는 헬스케어 플랫폼입니다. 수백 만의 사용자가 자신의 증상을 입력하면, 이를 국제 표준으로 정규화되어야 일관된 분석이 가능합니다.

이 글은 MediDaily가 SNOMED-CT를 선택한 이유, 실제 구현 전략, 그리고 실무에서 고려해야 할 사항들을 공유합니다.


1️⃣ 국제 의료보건 코드 체계 비교

주요 의료표준 코드들

헬스케어 시스템에서 사용되는 표준 코드는 크게 진단, 처치, 검사, 약물로 분류됩니다:

코드 체계주관기관용도범위한국 채택
ICD-10WHO진단 및 질병분류약 15,000개✅ 필수
KCD건강보험심사평가원한국 진단/질병분류ICD-10 기반✅ 필수
SNOMED-CTIHTSDO임상 용어 및 개념약 350,000개⚠️ 선택적
LOINCRegenstrief검사/관찰 코드약 100,000개⚠️ 선택적
CPTAMA의료 처치 및 서비스약 70,000개❌ 미국 중심
RxNormNLM약물 정규화약 15,000개⚠️ 선택적

각 코드 체계의 특성

ICD-10 / KCD

  • 용도: 보험청구, 통계, 진단
  • 특징: 계층적 구조 (A00~Z99.89)
  • 장점: 국내 필수, 보험 청구에 필수
  • 단점: 임상 상황의 세밀한 표현 어려움
예) 당뇨병
- ICD-10: E10 (Type 1 diabetes)
- 세분화: E10.1 (with ketoacidosis)

SNOMED-CT

  • 용도: 임상 기록, 의료 AI, 상호운용성
  • 특징: 의미론적 네트워크 기반
  • 장점: 매우 상세한 임상 표현 가능
  • 단점: 코드 개수 많음, 버전 관리 복잡
예) "급성 후두염"
- SNOMED-CT: 50417007 (Acute laryngitis)
  부모: 63854003 (Laryngitis)
  관계: hasComponent → 63934003 (Fever)

LOINC

  • 용도: 검사 결과, 측정값
  • 특징: 성분(Component) + 재산(Property) 기반
  • 장점: 검사/측정값 국제 표준화
  • 단점: 진단이나 증상 표현에는 부적합
예) 혈당 측정
- LOINC: 2345-7 (Glucose [Mass/volume] in Serum or Plasma)

2️⃣ MediDaily가 SNOMED-CT를 선택한 이유

기술적 요구사항 분석

MediDaily의 핵심 기능은:

  1. 사용자 증상 입력 → 자동 정규화
  2. 의료 칼럼 추천 → 증상 기반 검색
  3. AI 벡터 임베딩 → 의미론적 유사성 계산
  4. 데이터 일관성 → 수백만 건의 기록 통합

이런 요구사항에서 SNOMED-CT가 최적인 이유:

1. 의미론적 네트워크 구조

SNOMED-CT는 단순 분류 코드가 아니라, 의료 개념들의 의미론적 관계 그래프입니다.

// SNOMED-CT의 의미론적 구조
증상: "두통" (25064002)
  ├─ 부위: Head structure (69536005)
  ├─ 성질: Throbbing (417447008)
  ├─ 원인: Hypertension (38341003)
  └─ 자식: Tension headache (16739005)
      └─ 자식: Tension headache on waking (161894004)

이를 통해:

  • AI 임베딩: 코드의 의미 관계를 학습하면 더 나은 벡터 표현 생성
  • 증상 검색: “상세한 두통” vs “일반적인 두통” 구분 가능
  • 환자 교육: 계층적 관계로 설명 자동 생성

2. 임상 세밀도 (Clinical Granularity)

사용자가 입력한 “피로감”을 어떻게 다룰까?

ICD-10으로는:

R53 (Malaise and fatigue) - 끝
세부 분류 불가능

SNOMED-CT로는:

222175002 (Fatigue)
├─ 어느 부분의 피로: Generalized fatigue (1250004) vs Local fatigue (15046003)
├─ 지속 시간: Acute (418694008) vs Chronic (441471007)
├─ 원인 추론: Anemia? Infection? Depression?
└─ AI 추천: "피로감 관련 칼럼 12개" + 신뢰도 점수

3. 국제 상호운용성

사용자 데이터: SNOMED-CT (262008)
     ↓
국내 통계: SNOMED-CT → KCD 변환 (E10)
     ↓
해외 협력: SNOMED-CT → ICD-10 국제 코드
     ↓
AI 모델 학습: SNOMED-CT 의미 그래프 활용

4. 검색 및 AI의 우수성

// 일반 코드: "혈당 관련 칼럼" 검색 실패 가능
userInput: "혈당 높음"
code: "E11" (Type 2 diabetes) ← ICD만으로는 "높은 수치" 표현 못함
 
// SNOMED-CT: 의미 기반 검색 가능
userInput: "혈당 높음"
snomed: "250425002" (Finding of glucose level)
  ├─ 상위 개념: "Blood glucose finding"
  ├─ 부모: "Finding of body substance"
  ├─ 관련 칼럼: 자동 추천 (의미 유사성 기반)

3️⃣ MediDaily의 SNOMED-CT 구현

데이터베이스 설계

헬스케어 서비스의 데이터베이스는 SNOMED-CT를 중심으로 설계됩니다:

핵심 테이블 구조:

  1. 증상 코드 테이블 (SymptomCode)

    • PK: symptom_code (SNOMED-CT 코드, 6-18자리)
    • 영문명 (SNOMED 공식 용어)
    • 한글명 (서비스용 번역)
    • 생성일시, 수정일시
  2. 사용자 건강정보 (UserHealth)

    • 사용자 ID
    • 증상 코드 (FK → SymptomCode)
    • 기록 날짜 (YYYYMMDD 형식)
    • 증상값 (“높음”, “낮음”, “120” 등)
    • 중복 방지: (user_id, symptom_code, 날짜) 조합 유니크
  3. 자가진단 (SelfDiagnosis)

    • 진단 ID
    • 증상 코드 (FK → SymptomCode)
    • 질문 텍스트
  4. 콘텐츠-증상 매핑 (ColumnSymptomMapping)

    • 의료 칼럼 ID
    • 증상 코드 (FK → SymptomCode)
    • 관련성 점수 (0.0~1.0: 칼럼과 증상의 연관도)

설계 전략:

  • 다중 스키마 분리 (사용자/칼럼/진단 스키마)
  • SNOMED-CT는 읽기 전용 참고표로 관리
  • 모든 사용자 데이터는 외래키로 SNOMED-CT 코드 참조

코드 검증 전략

SNOMED-CT 코드는 6자리 이상 18자리 이하의 숫자로 정의되어 있습니다.

  • 예) 25064002 (두통), 222175002 (피로감)

검증 단계:

  1. 형식 검증: 정규식으로 길이와 숫자 여부 확인
  2. 존재성 검증: DB에 등록된 코드인지 확인
  3. 에러 처리: 유효하지 않으면 명확한 에러 메시지 반환

예시 검증 로직 (개념):

입력: "25064002"

Step 1: 형식 검증
  └─ /^\d{6,18}$/ 패턴 확인 ✓

Step 2: 존재성 검증
  └─ DB에서 symptom_code = "25064002" 조회
     ├─ 존재함 → 다음 단계
     └─ 없음 → 에러 반환: "등록되지 않은 증상 코드"

Step 3: 비즈니스 로직
  └─ 건강정보 기록 또는 자가진단 진행

API 핸들러는 다음과 같은 검증 순서를 따릅니다:

  • 클라이언트 입력값의 형식 먼저 확인
  • 우리 DB에 존재하는 코드만 처리
  • 예상치 못한 입력에 명확한 에러 메시지 제공

배치 생성 최적화 (N+1 쿼리 제거)

사용자가 100개의 증상 코드를 한번에 등록할 때의 최적화 전략:

❌ 나쁜 방법 (N+1 문제):

for each code in 100 codes:
    - 기존 코드 확인 (1 query)
    - 없으면 생성 (1 query)
→ 총 200 queries

✅ 좋은 방법 (배치 처리):

Step 1: 기존 코드 일괄 확인 (1 query)
  └─ WHERE code IN (code1, code2, ... code100)
     결과: [code1, code5, code67] 이미 존재

Step 2: 새 코드만 필터링 (메모리 작업)
  └─ 100개 - 3개 = 97개의 신규 코드

Step 3: 신규 코드 일괄 생성 (1 query)
  └─ INSERT INTO ... ON CONFLICT IGNORE
     결과: 97개 생성

Step 4: 결과 맵핑 (메모리 작업)
  └─ 각 입력에 대해 성공/실패 여부 응답

→ 총 2 queries

성능 개선:

  • 쿼리 수: 200 → 2 (100배 개선)
  • 응답 시간: 초 단위 → 밀리초 단위
  • DB 커넥션 점유: 200 × Δt → 2 × Δt

4️⃣ SNOMED-CT 사용 시 고려사항

1. 코드 존재성 검증

모든 사용자 입력은 우리 DB에 등록된 코드만 허용해야 합니다.

❌ 문제 있는 접근:

클라이언트 입력: symptomCode = "25064002"
→ 형식만 확인 (/^\d{6,18}$/)
→ 바로 자가진단 진행

왜 위험한가?

  • 유효한 SNOMED-CT 형식이지만 우리가 지원하지 않는 코드일 수 있음
  • 잘못된 코드로 인한 의료 데이터 오염
  • 추후 분석 및 추천에 오류 발생

✅ 올바른 접근:

클라이언트 입력: symptomCode = "25064002"
  ↓
Step 1: 형식 검증 (/^\d{6,18}$/)
  ├─ 유효한 형식? → 계속
  └─ 무효한 형식? → 에러 반환

  ↓
Step 2: 존재성 검증 (DB 조회)
  ├─ 우리 DB에 존재? → 다음 단계
  └─ 없는 코드? → "등록되지 않은 증상 코드" 에러

  ↓
Step 3: 비즈니스 로직 진행
  └─ 안전하게 기록 및 분석

데이터 무결성의 핵심: 자체 승인된 증상 코드 목록만 사용하여 데이터 품질 보장

2. 데이터 중복 방지

문제 시나리오: 사용자가 같은 날짜에 같은 증상을 여러 번 등록하는 경우

2025-01-15 "두통(25064002)" → 1차 기록
2025-01-15 "두통(25064002)" → 2차 기록 (중복!)

해결책: 유니크 제약 설정

사용자 ID + 증상 코드 + 날짜 조합에 유니크 제약을 걸면:

  • 같은 사용자
  • 같은 증상
  • 같은 날짜

이 3가지 모두 일치하면 INSERT 실패

처리 방식:

  • Option 1: 2번째 등록을 거부 (“이미 기록된 증상입니다”)
  • Option 2: 2번째 기록으로 UPDATE (“마지막 기록을 유지”)

3. 칼럼-증상 매핑의 정확성

데이터 구조: 각 의료 칼럼(콘텐츠)은 여러 증상 코드와 매핑되며, 각 매핑마다 **관련성 점수(0.0~1.0)**를 갖습니다.

예시: 칼럼: “당뇨병 관리법”

혈당 높음 (250425002)    → 0.95 (매우 관련)
피로감 (222175002)       → 0.7  (관련)
두통 (25064002)          → 0.4  (약간 관련)
발열 (386661006)         → 0.1  (거의 무관)

중요한 점: 이 매핑은 의료 전문가의 감수 필수입니다.

  • 자동 알고리즘으로만 하면 안 됨
  • 의료적 정확성이 보장되어야 함
  • 사용자의 건강 결정에 영향을 미치기 때문

우리의 추천 시스템: 사용자가 “두통”을 입력 → 관련성 점수 기반으로 칼럼 정렬 → 높은 점수부터 추천

4. 한글화 전략

SNOMED-CT 코드는 국제 표준이므로 영문이 공식 용어입니다. 사용자 경험을 위해 한글화가 필요합니다.

데이터 구조:

SNOMED-CT Code: 271649006

DB 저장:
  - 영문 공식명: "Hypertension" (SNOMED 변경 불가)
  - 한글 번역명: "고혈압" (우리가 유지보수)

API 응답 (사용자):
  - 표시: "고혈압"
  - 코드: "271649006"

API 응답 (의료 전문가):
  - 표시: "고혈압 / Hypertension"
  - 코드: "271649006"

번역 관리의 중요성:

  • SNOMED-CT 버전 업데이트 시 영문 공식명 변경 가능
  • 우리의 한글 번역도 정기적으로 검토 필요
  • 의료 전문가 검수 필수 (오역은 의료 오류로 이어짐)

5️⃣ 일반 코드 vs 헬스케어 코드의 차이점

특성 비교

특성일반 코드헬스케어 코드 (SNOMED-CT)
변경 빈도비교적 드물다자주 변경/확장됨
버전 관리단순 (v1.0, v2.0)복잡 (매월 릴리스)
역호환성코드 폐지 드물다코드 재분류/폐기 발생
번역고정정기적 업데이트
데이터 무결성비용이 낮다매우 중요 (의료 결정에 영향)
감사/추적선택사항필수 (법적 요구)

실제 예제: 코드 변경의 영향

시나리오: SNOMED-CT 2025-03-01 릴리스
- "당뇨병 전기" (Prediabetes): 44054006
  → 재분류됨 (더 정밀한 7개 개념으로 분해)

우리 DB:
- 기존 사용자: 44054006으로 1000건 기록
- 신규 사용자: 어떤 코드로 입력할까? 🤔

해결책: 매핑 테이블
old_code → new_code
44054006 → 726633007, 726637003, ... (선택)

헬스케어 데이터의 특수성

일반 데이터 vs 헬스케어 데이터:

항목일반 데이터헬스케어 데이터
업데이트email = “new@example.com⚠️ 수정 시 원본 기록 보존 필수
추적선택사항법적 필수 (감사 추적)
삭제그냥 DELETE❌ 완전 삭제 금지 (법적 증거)
신뢰도신경 쓰지 않음데이터의 신뢰도 기록 필수

헬스케어 데이터가 기록해야 할 정보:

기본 정보:
  - user_id: 환자 ID
  - symptom_code: 증상 코드
  - symptom_dt: 기록 날짜
  - symptom_value: "높음", "낮음", 수치 등

감사 추적:
  - recorded_at: 언제 기록했는가?
  - recorded_by: 누가 기록했는가? (환자/의료진)
  - source: 어디서 입력했는가? (앱/웹/병원 시스템)

데이터 품질:
  - confidence: 얼마나 확신하는가? (0.0~1.0)
  - change_reason: "정정", "재측정" 등 변경 사유

변경 히스토리:
  - version: 몇 번 수정됐는가?
  - changed_by: 누가 수정했는가?
  - previous_value: 이전 값은?

왜 이렇게 복잡한가? 의료 기록은 법적 문서이며, 환자의 건강 결정과 진료 결정에 영향을 미치기 때문입니다.


6️⃣ 버전 업에 따른 대응 전략

SNOMED-CT 버전 관리 정책

SNOMED-CT는 매달 국제 에디션과 국가별 확장판을 릴리스합니다:

SNOMED CT 2025-03-01 Release
├─ International Edition (기본)
└─ Korean Extension (한국 추가 코드)

주요 변경:
- New concepts (신규 코드 추가): ~2,000개
- Changed descriptions (용어 변경): ~1,500개
- Inactivated concepts (폐기 코드): ~100개

우리의 대응 전략

Phase 1: 모니터링

자동 모니터링 시스템:

매월 1일(SNOMED-CT 공식 릴리스일)을 기준으로 자동 확인:

Scheduled Task (매월 1일):
  1. IHTSDO 공식 API 조회
     └─ 최신 릴리스 버전 확인

  2. 우리 DB와 비교
     └─ SELECT version FROM metadata WHERE key="snomed_version"
        현재 버전: 2025-01-01
        최신 버전: 2025-03-01
        → 차이 감지!

  3. 팀 알림 발송
     └─ Slack: "SNOMED-CT 2025-03-01 릴리스됨"
     └─ 변경사항 요약: 신규 코드 2,150개, 폐기 45개

자동화 도구:

  • 스케줄링: Cron job 또는 클라우드 타이머
  • 알림: Slack, Email, 대시보드
  • 로깅: 버전 체크 히스토리

Phase 2: 영향도 분석

새 버전 릴리스 시 영향도를 파악합니다:

1단계: 폐기된 코드의 영향도

새 SNOMED-CT 버전:
  - Inactivated Concepts (폐기 코드): [45개]

우리 DB 조회:
  SELECT COUNT(DISTINCT user_id)
  FROM user_health
  WHERE symptom_code IN (폐기된 45개)

결과 시나리오:
  - 1,000명 이상 → 심각함! 마이그레이션 계획 수립
  - 100~1000명 → 대응 필요 (선택적 마이그레이션)
  - 10명 이하 → 경미함, 개별 통보 검토
  - 0명 → 영향 없음, 진행

2단계: 신규 코드의 필요성

새 릴리스 신규 코드: [2,150개]

우리 서비스와 관련 있는가?
  - "두통의 새로운 세분 코드" → ✅ 추가해야 함
  - "드문 유전병의 새 코드" → ❌ 지금은 불필요
  - "의료기기 코드" → ❌ 우리는 증상만 다룸

추가할 코드: ~150개 (필요한 것만)

3단계: 영향도 보고서 작성

- 폐기 코드 영향 사용자: 50명
- 신규 추가 코드: 150개
- 예상 마이그레이션 비용: 저
- AI 모델 재학습 필요: 예
- 예상 업데이트 일정: 4월 1주

Phase 3: 마이그레이션

폐기된 코드의 대응책을 미리 준비합니다:

시나리오 1: 1:1 매핑 (자동 마이그레이션)

구 코드: 44054006 (Prediabetes - old)
신 코드: 726633007 (Prediabetes - new)

처리:
  1. 기존 데이터: WHERE symptom_code = "44054006"
  2. UPDATE: SET symptom_code = "726633007"
  3. 감사 로그: old→new 기록
  4. 결과: 150개 레코드 자동 변환

시나리오 2: 1:N 매핑 (의료진 결정)

구 코드: 64572001 (Condition - general)
신 코드: [64572002, 64572003, 64572004] (더 구체적인 3가지)

처리:
  1. 자동 불가능 (선택이 필요)
  2. 의료팀 검토: 기존 데이터를 어느 신 코드로?
  3. 선택 후 UPDATE
  4. 감사 로그: 누가 어떤 선택을 했는지 기록

시나리오 3: 대체 코드 없음 (수동 검토)

구 코드: 99999999 (매우 드문 코드)
신 코드: 없음

처리:
  1. 자동화 불가능
  2. 팀에 플래그: "150개 레코드가 대체 코드 없음"
  3. 옵션 검토:
     - 가장 유사한 신 코드로 대체?
     - 사용자에게 재입력 요청?
     - 레거시 코드로 남겨두기?
  4. 의료 전문가 의견 필수

Phase 4: 테스트 및 배포

스테이징 환경에서 먼저 테스트:

Test 1: 코드 유효성

스테이징 DB에 신버전 로드
  ↓
기존 데이터의 모든 코드 검증:
  - 마이그레이션된 코드가 신버전에 존재하는가?
  - 고아 코드(orphaned code)가 있는가?

결과:
  - 모든 코드 유효 → 다음 단계
  - 유효하지 않은 코드 발견 → 마이그레이션 재검토

Test 2: 통합 테스트

자동 실행 테스트:
  - 자가진단 플로우: 코드 조회 → 질문 실행 → 결과 반환
  - 칼럼 검색: "증상" 입력 → 관련 칼럼 추천
  - 사용자 건강정보: 기록 → 조회 → 분석

목표: 기존 기능이 깨지지 않았는가?

Test 3: AI 모델 영향도

의미론적 변화 확인:
  - 코드의 부모-자식 관계가 변했는가?
  - 새로운 용어가 추가되었는가?
  - 벡터 임베딩이 의료적으로 타당한가?

결과:
  - 의미 변화 큼 → AI 모델 재학습 필수
  - 변화 작음 → 재학습 선택사항

Test 4: 수용 테스트

의료 전문가 검증:
  - 자가진단 정확도: 95% 이상 유지?
  - 추천 칼럼이 사용자 증상과 매치되는가?
  - 새로운 코드로 인한 부작용 없는가?

결과:
  - 정확도 95% 이상 → 배포 승인
  - 정확도 저하 → 근본 원인 분석

Phase 5: 문서화 및 공개

모든 이해관계자(사용자, 의료 전문가, 파트너)에게 명확하게 공지합니다:

사용자용 공지문:

제목: SNOMED-CT 증상 코드 업데이트 안내

내용:
- 업데이트 날짜: 2025-04-01
- 신규 추가 증상: "상세한 두통", "알레르기성 기침" 등 12개
- 기존 기록: 모두 안전하게 유지됨
- 서비스 장애 시간: 없음 (배경에서 처리)

사용자가 해야 할 일: 없음 (자동 처리)

의료 전문가용 공지:

제목: SNOMED-CT 2025-03-01 릴리스 대응

내용:
- 폐기 코드: 45개 (대체 코드 제시)
- 신규 코드: 150개 추가됨 (더 정밀한 진단 가능)
- 용어 개선: 1,200개 (의료적 정확성 향상)

의료진이 해야 할 일:
  1. 신규 증상 코드 검토
  2. 칼럼-증상 매핑 재검토
  3. 피드백 제공 (2025-04-15까지)

상세 변경사항: [SNOMED International Release Notes]

파트너사(API 연동)용 공지:

제목: SNOMED-CT 코드 업데이트 - API 호환성 정보

내용:
- API 버전: 변경 없음 (호환성 유지)
- 응답 형식: 동일 (새 코드만 추가)
- 폐기 코드: 2025-06-01부터 지원 중단

파트너가 해야 할 일:
  1. 2025-04-01: 스테이징에서 테스트
  2. 2025-05-01: 프로덕션 적용
  3. 폐기 코드 사용 중단 (deadline: 2025-05-31)

7️⃣ 실제 구현 체크리스트

MediDaily처럼 SNOMED-CT를 도입할 때 확인사항:

  • 기본 구조

    • SNOMED 코드를 PK로 설정 (VarChar(18))
    • 영문명(SNOMED 공식) + 한글명(우리 번역) 구분
    • 다중 스키마 설계 (칼럼, 진단, 사용자 스키마 분리)
  • 검증

    • 형식 검증: /^\d{6,18}$/ 정규식
    • 존재성 검증: DB에 등록된 코드만 허용
    • 배치 작업: N+1 쿼리 제거
  • 데이터 무결성

    • 중복 방지: 유니크 제약 설정
    • 참조 무결성: FK 제약 설정
    • 감사 추적: changed_by, change_reason 기록
  • 버전 관리

    • SNOMED 버전 메타데이터 저장
    • 월간 업데이트 모니터링
    • 폐기 코드 마이그레이션 자동화
    • AI 모델 영향도 분석
  • 운영

    • 칼럼-증상 매핑: 의료 전문가 감수
    • 한글 번역: 정기적 검토
    • 사용자 공지: 업데이트 안내

결론

헬스케어 API를 구축할 때 “어떤 코드를 쓸 것인가”는 단순한 기술 선택이 아니라 아키텍처 결정입니다.

MediDaily가 SNOMED-CT를 선택한 이유:

  1. 의미론적 깊이: AI 임베딩과 검색에 최적
  2. 확장성: 350,000개 개념으로 거의 모든 증상 표현 가능
  3. 상호운용성: 국제 협력과 데이터 교환 용이
  4. 임상 세밀도: 사용자 입력의 다양한 뉘앙스 포착

하지만 SNOMED-CT는 책임감 있게 다뤄야 합니다:

  • 매월 업데이트 모니터링
  • 코드 폐기에 대한 대응 계획
  • 의료 데이터의 감시감독 표준 준수
  • 사용자/의료 전문가 간 명확한 소통

이 글이 여러분의 헬스케어 시스템 설계에 도움이 되길 바랍니다.


🔗 관련 개념

다음 단계:

관련 포스트:


참고 자료


작성: 2026-03-26 최종 수정: 2026-03-26