JSON만으로는 부족합니다: 분석을 위한 FHIR 평탄화의 엔지니어링 골칫거리

발행: (2026년 1월 3일 오전 01:00 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

위의 소스 링크 아래에 번역하고자 하는 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다.

문제: 테이블이 아니라 그래프입니다

문제는 FHIR가 JSON이라는 것이 아니라, FHIR 리소스가 깊게 중첩되고 재귀적이며 다형적이라는 점입니다.

간단한 Patient 리소스를 예로 들어 보겠습니다. SQL 테이블에서는 Name에 대한 컬럼이 있을 것으로 기대합니다. FHIR에서는 Name이 객체들의 배열입니다.

  • 법적 이름이 있습니다.
  • 결혼 전 이름이 있습니다.
  • 별명이 있습니다.

각 객체는 given 배열(이름, 중간 이름)과 family 문자열을 포함합니다.

{
  "resourceType": "Patient",
  "id": "example",
  "name": [
    {
      "use": "official",
      "family": "Chalmers",
      "given": ["Peter", "James"]
    },
    {
      "use": "nickname",
      "given": ["Jim"]
    }
  ],
  "identifier": [
    {
      "system": "http://hospital.org/mrns",
      "value": "12345"
    }
  ]
}

이 데이터를 그대로 BigQuery에 넣고 name.family을 조회하려 하면, 배열 안에 있기 때문에 null이 반환되거나 오류가 발생합니다.

”폭발” 문제

SQL에서 이를 분석하려면 보통 이러한 배열을 UNNEST하거나 LATERAL VIEW EXPLODE해야 합니다.

환자에게 이름이 2개, 식별자가 3개, 확장이 5개 있다면, 이를 조심 없이 단일 넓은 행으로 평탄화하려고 하면 카테시안 곱이 발생합니다.

2 * 3 * 5 = 30 rows for a single patient

이제 수백 개의 라인 아이템과 진단 코드를 가진 ExplanationOfBenefit 리소스에 대해 동일한 작업을 수행한다고 상상해 보세요. 100만 건의 데이터셋이 순식간에 1억 행의 중복된 쓰레기로 변합니다.

전략 1: “충분히 좋은” 추출

분석가가 Official 이름과 MRN만 신경쓴다면, 평탄화 과정을 일반화하려 하지 마세요. 경로를 하드코딩하세요.

Python/Pandas에서는 보통 특정 람다 함수를 작성하는 형태가 됩니다. 이는脆弱하지만 MVP에서는 동작합니다.

import pandas as pd

# Assume 'data' is your list of dicts
def get_official_family_name(patient_row):
    names = patient_row.get('name', [])
    for n in names:
        if n.get('use') == 'official':
            return n.get('family')
    return None

# Apply it
df['official_last_name'] = df.apply(get_official_family_name, axis=1)

작은 스크립트에는 괜찮지만, 테라바이트 규모의 데이터를 처리하는 ETL 파이프라인에서는 확장성이 좋지 않습니다.

Strategy 2: Array‑aware Columnar Formats (The Real Fix)

현대적인 접근 방식—그리고 일반적으로 권장되는 방법—은 FHIR를 CSV‑스타일의 평면 테이블에 바로 강제로 넣으려 하지 않는 것입니다. 대신, Parquet과 같이 중첩 구조를 지원하는 컬럼형 포맷으로 옮깁니다.

Apache Spark나 Databricks 같은 도구는 스키마를 보존합니다. 결국 Tableau/PowerBI 사용자들을 위해 데이터를 평탄화해야 할 때가 오지만, 핵심은 Satellite Tables를 만드는 것입니다.

하나의 거대한 Patient 테이블 대신에 다음과 같이 만듭니다:

  • Patient_Core (id, birthdate, gender)
  • Patient_Names (id, use, family, given)
  • Patient_Identifiers (id, system, value)

이렇게 하면 데이터를 정규화하여 JSON을 관계형 3차 정규형으로 역설계한 효과를 얻습니다.

# Pseudo-code for a Spark transformation
from pyspark.sql.functions import col, explode

# Read raw FHIR JSON
df = spark.read.json("s3://bucket/fhir/Patient/")

# Create the Names table
df_names = df.select(
    col("id").alias("patient_id"),
    explode(col("name")).alias("name_struct")
).select(
    "patient_id",
    "name_struct.use",
    "name_struct.family",
    "name_struct.given"  # Note: given is still an array here!
)

# Write out as Parquet
df_names.write.parquet("s3://bucket/analytics/patient_names/")

Context is King

가장 큰 골칫거리는 구조 자체가 아니라 컨텍스트 손실입니다.

Observation.component를 평탄화하면 값 80120을 얻을 수 있습니다. 어느 것이 수축기 혈압이고 어느 것이 이완기 혈압일까요? JSON에서는 이들이 형제 객체로 존재합니다. 이를 무작정 두 개의 행으로 평탄화한다면, 해당 value 필드와 함께 code 필드를 반드시 함께 옮겨야 합니다.

항상 값 그 레이블을 포함하는 객체를 함께 평탄화하세요. 독립적으로 평탄화하면 숫자와 의미 사이의 연관성을 잃게 됩니다.

Conclusion

FHIR는 앞으로도 계속 사용할 것이며, 스키마는 임상 정확성을 위해 뛰어납니다. 데이터 엔지니어링에서는 사고방식의 전환만 필요합니다. “One Big Table” 사고방식에서 벗어나 파이프라인에서 배열과 구조체를 일급 객체로 다루세요.

복잡한 데이터 아키텍처에 어려움을 겪고 있거나 다른 사람들이 이러한 ETL 과제를 어떻게 해결하는지 보고 싶다면, 다른 기사에서 더 많은 기술 가이드를 확인하세요.

코딩을 즐기시고, 여러분의 JSON이 언제나 유효하기를 바랍니다!

Back to Blog

관련 글

더 보기 »

AWS 커뮤니티 데이 에콰도르 2025

행사 요약: 2025년 10월에 키토에서 AWS Community Day Ecuador가 개최되었습니다. 에너지와 스티커가 가득한 하루였으며, “serp...” 버전도 있었습니다.

소개 :)

About Me 안녕하세요, 제 첫 게시물과 소개에 오신 것을 환영합니다. 제 이름은 M4iR0N이며, 저는 Cyber Security와 Privacy Advocate라고 생각합니다. 집에서는 저는 …

안녕 여러분, 풀스택 Next.js, PostgreSQL, Redis와 백엔드 Django, FastAPI로 부트캠프 채팅을 만들고 싶은데, 관심 있고 진지한 사람 있나요? 함께 동기 부여하고 더 빠르게 배우고 싶어요. 혼자 공부하는 것보다 좋겠어요. 저는 이미 이 스택을 2년간 공부했습니다.

markdown !Forem 로고https://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2...