대규모 임상 데이터 처리와 레거시 연동을 위한 MSA 백엔드 최적화 경험
대규모 임상 데이터 처리와 레거시 연동을 위한 MSA 백엔드 최적화 경험 (AMC PROMs 구축기)
1. 도입 및 기술적 도전 과제
현대 의료의 핵심 패러다임인 '환자 중심 의료(Patient-Centered Care)'를 뒷받침하는 핵심 데이터가 바로 PROMs(환자자기평가결과)입니다. 저는 서울아산병원(AMC) PROMs 플랫폼의 백엔드 개발을 담당하며, 방대한 임상 데이터를 안정적으로 수집·분석하고 거대한 병원 코어 시스템(AMIS)과 연동하는 미션을 수행했습니다. 이 과정에서 마주한 인증 한계, 실시간 데이터 시각화 병목, 대용량 소급 계산 OOM, 장애 전파 위험이라는 네 가지 기술적 난제와 이를 아키텍처 관점에서 해결한 과정을 공유하고자 합니다.
2. HTTP Header 제약을 극복한 비표준 Payload 기반 인증 어댑터
[문제 상황: 레거시 환경의 HTTP Header 조작 한계] 최신 MSA 환경과 Spring Security는 HTTP Authorization Header를 통해 JWT를 주고받는 표준 규약을 따릅니다. 하지만 병원 코어 시스템(AMIS)은 구조적 제약으로 인해 표준 HTTP 헤더에 토큰을 실어 보낼 수 없었고, 대신 Request Body(AmcData) 내부의 특정 필드(encToken)에 암호화된 인증 정보를 담아 전송해야 했습니다.
[해결 방안: ContentCachingRequestWrapper 및 커스텀 필터 구현] 비표준 통신을 수용하면서도 내부 보안 표준을 유지하기 위해 앞단에 커스텀 Security Filter를 구현했습니다. 서블릿 특성상 InputStream은 한 번 읽으면 소실되므로, ContentCachingRequestWrapper를 적용해 Request Body를 캐싱했습니다. 필터에서 캐싱된 Body의 encToken을 추출해 Keycloak 서버로 검증한 뒤, 유효한 정보를 ThreadLocal 기반 ContextHolder에 주입했습니다. 이를 통해 외부의 비표준 연동을 수용하면서도 서버 내부는 완벽히 표준 권한 제어 흐름을 따르는 인증 어댑터를 완성했습니다.
3. 실시간 임상 데이터 시각화를 위한 Kafka & CQRS 패턴 도입
[문제 상황: 복잡한 의학 수식 계산에 따른 실시간 DB 병목] 환자가 문진을 제출하고 진료실에 들어오기 전, 의사의 모니터에는 이미 통증 지수나 삶의 질(QoL) 변화 추이가 시각화되어야 합니다. 의료진이 대시보드를 열 때마다 과거의 방대한 설문 응답 테이블을 Join하고 의학적 규칙을 계산한다면 심각한 조회 지연(Latency)이 발생해 사용자에게 불편함을 제공합니다..
[해결 방안: Event-Driven 및 분리된 Query Model(QM) 구축]
실시간성 확보를 위해 CQRS(Command Query Responsibility Segregation) 아키텍처와 Kafka 이벤트 스트리밍을 결합했습니다.
명령(Command)의 경량화: 환자가 설문을 제출하면 원천 데이터만 빠르게 Insert 후 응답을 반환하며, 즉시 Kafka Topic으로 이벤트를 비동기 발행합니다.
비동기 사전 집계(Pre-calculation): Consumer가 이벤트를 구독하여 다이내믹 규칙 엔진(SpEL) 기반으로 무거운 연산(조건부 문항 처리, 점수 합산)을 백그라운드에서 수행합니다.
조회(Query) 모델 최적화: 계산된 결과는 조회를 위해 극단적으로 평탄화된 '통계 테이블에 적재됩니다. 결과적으로 의료진은 무거운 연산 없이 요약 데이터만 즉시 읽어오므로(O(1) 성능), 데이터가 누적되더라도 쾌적한 조회가 가능해졌습니다.
4. 대용량 소급 계산 처리 시 JPA OOM 방지 최적화
[문제 상황: 수십만 건 조회 시 영속성 컨텍스트(L1 Cache) 포화] 통계 기준이 변경되어 수년 치의 과거 문진 데이터를 한 번에 재계산(소급 적용)할 때, 10만 건 이상의 엔티티가 영속성 컨텍스트(1차 캐시)에 적재되며 가비지 컬렉터(GC)가 작동하지 못해 OOM(Out of Memory)이 발생하는 치명적 문제가 확인되었습니다.
[해결 방안: Chunk Processing 및 명시적 메모리 반환]시스템 가용성을 해치지 않기 위해 '청크 기반 메모리 제어' 기법을 적용했습니다. 전체 데이터를 Offset과 Limit을 통해 1,000건 단위로 분할(Slice) 조회하고, 계산 후 Bulk Insert를 적용했습니다. 가장 핵심적인 튜닝은 1,000건 작업 종료 시마다 명시적으로 entityManager.clear()를 호출하여 영속성 컨텍스트를 비워준 것입니다. 이를 통해 대상이 수백만 건으로 늘어나도 서버의 Heap 메모리 사용량은 1,000건 수준의 Flat한 상태를 유지하게 되었습니다.
5. 코어 시스템 연동 시 장애 전파(Cascading Failure) 차단 설계
[문제 상황: 외부 API 지연으로 인한 내부 Thread Pool 고갈] AMIS 시스템에 일시적인 부하나 지연이 발생하면, 그 응답을 기다리는 PROMs 서버의 Tomcat Thread들이 무한정 Block 상태에 빠져 전체 플랫폼이 마비되는 장애 전파(Cascading Failure) 위험이 존재했습니다.
[해결 방안: 지능형 RestClient와 결함 감내(Fault Tolerance) 로직]
위험 원천 차단을 위해 Spring 6의 RestClient를 도입하여 통신 클라이언트를 방어적으로 재설계했습니다.
엄격한 Timeout 격리: Connect Timeout 5초, Read Timeout 30초를 강제하여, 상대 서버 장애 시 30초 내에 연결을 끊고 자원을 회수해 시스템 독립성을 확보했습니다.
Error Bypass 기법: 연동 실패 응답 시 무조건 Exception을 발생시키지 않고, 무시 가능한 특정 에러 코드(ignorableMessageIds)를 가변 인자로 받아 우회(Bypass)시키는 로직을 구현했습니다. 이를 통해 코어 시스템의 불안정성이 환자의 문진 경험 훼손으로 이어지는 것을 방어했습니다.
6. 결론
이번 프로젝트는 트래픽이 급증하고 데이터가 무한히 누적되는 엔터프라이즈 환경에서 백엔드 시스템이 어떻게 버텨내야 하는지 치열하게 고민하고 대처하며, 끝내 성공적으로 완수해 낸 값진 경험이었습니다.비표준 인증 방식의 유연한 수용, CQRS를 통한 데이터 실시간성 확보, 1차 캐시 제어를 통한 대용량 메모리 최적화, 그리고 장애 전파를 막는 방어적 프로그래밍 기법들은 단순히 기능 구현을 넘어 시스템에 '확장성과 견고함(Robustness)'을 불어넣는 든든한 기술적 자산이 되었습니다.지난 6개월이라는 시간 동안 프로젝트를 완수하기 위해 팀원 전체가 밤낮없이 헌신했고, 수많은 난관을 극복해 냈습니다. 그 치열했던 과정의 끝에서 고객의 진심 어린 미소와 감사가 담긴 편지를 건네받았을 때의 감동은 잊을 수 없습니다. 고객의 마음속에 '넥스트리(NEXTREE)'라는 이름을 믿고 맡길 수 있는 최고의 기술 파트너로 멋지게 각인시킬 수 있어 엔지니어로서 더없이 기쁘고 보람찹니다. 앞으로도 이 경험을 자양분 삼아, 고객의 비즈니스 가치를 극대화하는 견고한 아키텍처를 설계해 나가겠습니다.