1. 개요
헬스케어 플랫폼의 핵심 가치는 사용자가 자신이 섭취한 음식을 정교하고 신속하게 찾아내어 영양성분(탄수화물, 단백질, 지방, 칼로리 등)을 일지에 정밀하게 남기는 반응성에 있습니다. DoEatFit (v2.0) 역시 식약처에서 제공하는 공공데이터 포털의 **약 27만 건에 달하는 대용량 식품 영양 정보 DB**를 이식해 사용자에게 최고의 검색 경험을 제공하도록 기획되었습니다.
그러나 초기 단계에서 공공 Open API를 실시간으로 원격 호출하거나 단순 데이터베이스 RDBMS LIKE 검색을 시도했던 구조는 심각한 속도 저하와 데이터 오염 리스크를 낳았습니다. 이를 극복하기 위해 빠르고 가벼운 모던 오픈소스 검색 엔진 Meilisearch를 핵심 캐시 레이어로 전진 배치하고 MySQL을 스냅샷 저장소로 두는 하이브리드 아키텍처를 수립했습니다. 27만 건의 벌크 데이터 적재 파이프라인을 견고하게 가공하고, 초성 매칭 및 오타 교정을 15ms 성능으로 안착시킨 치열한 인프라 고도화 회고를 정리해 드립니다.
2. 핵심 내용
2-1. 🚨 대용량 데이터 조회 한계와 하이브리드 아키텍처 기획
- 기존 한계점:
- 원격 API 레이턴시 및 차단 리스크: 식약처 Open API 공용 서버의 응답 지연이 심해 검색 속도가 수 초 이상 널뛰기를 했습니다. 트래픽 유입 시 호출 한도 제한에 걸려 API Key가 불시에 잠기는 안정성 결함도 안고 있었습니다.
- 경직된 검색 경험: 공용 데이터 특성상 오타나 띄어쓰기가 뒤섞여 있는데, 일반적인 데이터베이스 쿼리(
LIKE %keyword%) 방식은 인덱스를 타지 못해 검색 응답이 지연되고, 초성 부분 매칭이나 한글 오타 교정(Typo-tolerance) 같은 미려한 인터렉션을 구현하기 불가능했습니다.
- MySQL & Meilisearch 하이브리드 아키텍처:
- 속도가 눈부시게 빠르고 메모리 오버헤드가 적은 Meilisearch를 프론트 뷰단 검색 엔드포인트에 실시간 캐시 용도로 앞세우고, 전통 RDBMS MySQL을 최종 확정 데이터 보관 영수증 저장소로 분할 구축했습니다.
- 특히 **과거 식단 데이터의 불변 무결성(Data Immutability)**을 확보하기 위해 스냅샷 전략을 주입했습니다. 식단 일지 저장 시 식품의 유동적인 고유 식별자 키만 단순 참조 형태로 기록하면, 추후 외부 공공데이터 정정 시 과거 기록 수치마저 변동되는 보안 사고가 터집니다.
- 이를 막기 위해 사용자가 식단을 최종 확정하는 시점에는 Meilisearch에서 스캔한 당대의 칼로리 및 3대 영양소 값 자체를 MySQL 일지 스냅샷 테이블에 통째로 영속화(Snapshotting) 하여 외부 환경 변동에 철벽 차단되도록 설계했습니다.
[식약처 공공 OpenAPI]
↓ (정기 벌크 인덱싱 파이프라인)
[Meilisearch (초고속 캐싱 검색 레이어)] ↔ [Spring Boot API Gateway (Proxy)]
↕
[MySQL (영수증 Snapshot DB)] ↖ (식단 일지 확정 시점에 원본 영양 수치 통째 박제)
2-2. 💡 데이터 적재 장애 극복과 벌크 파이프라인 구현
- 배치 파이프라인 1차 실패와 원인:
- 초기 적재 파이프라인은 공용 XML 포맷을 수신해
jackson-dataformat-xml라이브러리로 마셜링 후 벌크 주입을 행했습니다. - 그러나 XML 규격의 무거운 페이로드 파싱 탓에 JVM GC 오버헤드가 발생해 서버 힙 덤프가 터졌고, 스로틀링 없는 과도한 동시 트래픽으로 공공기관 측 방화벽에 DDOS 봇 공격으로 판단되어 차단당하는 참극을 맞이했습니다.
- 초기 적재 파이프라인은 공용 XML 포맷을 수신해
- 벌크 파이프라인 원천 개선 명세:
- 지연 스로틀링(Rate Limiting) 장착: 배치 통신 간 500건 단위 페이징 호출 시점에 의도적으로
Thread.sleep(200)의 200ms 속도 조율 간격을 주입하여 대상 API 서버에 주는 물리적 충격을 완벽하게 중화했습니다. - 이어하기(Resume) 엔진 수립: 네트워크 순간 탈락 시 처음부터 다시 27만 건을 로드하는 낭비를 종식하기 위해 MySQL 내부에 동기화 성공 이력을 저장하는
FoodSyncHistory스키마를 신설하고 마지막 성공 페이지를 타이트하게 마킹해 프로세스 다운 시에도 즉각 복원 및 이어하기를 구축했습니다. - XML ➡ JSON 페이로드 경량화: 네트워크 전송 지연이 높은 XML 포맷을 탈피하고 경량의 JSON 규격(
type=json)으로 전면 교체하여 파싱 비용을 경감하고 불필요한 마셜링 라이브러리를 가차 없이 덜어냈습니다.
- 지연 스로틀링(Rate Limiting) 장착: 배치 통신 간 500건 단위 페이징 호출 시점에 의도적으로
sequenceDiagram participant Admin as CMS 어드민 CMS participant Server as Spring Boot 서버 (Batch) participant OpenAPI as 식약처 공공 OpenAPI participant DB as MySQL (SyncHistory) participant Meili as Meilisearch 캐시 엔진 Admin->>Server: 식품 대용량 초기화 인덱싱 트리거 Server->>DB: 마지막 동기화 기록 (lastPageNo) 스캔 Loop 500건 단위 Paging Bulk Fetch Server->>OpenAPI: JSON 포맷 호출 (200ms Rate Limit 스로틀 작동) OpenAPI-->>Server: 경량 JSON 페이로드 리턴 Server->>Server: 필수값 데이터 정제 및 Null 대체 필터링 Server->>Meili: Meilisearch 벌크 인덱스 적재 요청 Server->>DB: 현재 페이지 적재 성공 기록 (lastProcessedPage) End Server-->>Admin: 27만 건 무결 데이터 마이그레이션 완료 통보
2-3. 🏛️ API 보안 프록시 고찰 및 모던 오픈소스 라이브러리 검토
- 보안을 위한 API Proxy 방식 사수:
- “프론트엔드 브라우저에서 직접 Meilisearch에 검색 API를 날리면 네트워크 통신을 한 단계 덜 거치므로 물리적으로 더 빠른 것 아닌가요?”라는 아키텍처적 도전을 마주했습니다.
- 하지만 보안성과 추상화를 완벽히 지켜내기 위해 백엔드를 우회하는 Spring Boot Backend Proxy 통신 방식을 타협 없이 고수했습니다.
- 보안 및 크롤링 원천 봉쇄: 웹 브라우저 단에 Meilisearch Search Key를 노출하는 순간 아무리 읽기 권한이라 해도 API 식별 키가 털릴 수밖에 없고, 이는 식품 DB 대량 크롤링 스크래핑 루트를 해커에게 고스란히 상납하는 자살골이 됩니다. 백엔드 프록시를 관통하면 Spring Security 방화벽 안에서 철저히 키를 숨길 수 있습니다.
- 느슨한 결합(Loose Coupling) 이점: 차후 서비스 볼륨 확대에 대응해 검색 엔진을 Elasticsearch나 하이브리드 벡터 검색기로 전면 교체하거나 정교한 커스텀 랭킹 가중치 알고리즘을 프론트 수정 없이 오직 백엔드 비즈니스 레이어에서만 조용히 개편해 갈 수 있는 결합도 분리 가치를 달성했습니다.
- 추천 오픈소스 라이브러리 및 도구 검토:
- meilisearch-java SDK
- 평가: 공식 자바 SDK로써 인덱스 튜닝 속성(Typo Tolerance, Stop Words 등)을 자바 설정 빈 코드로 타이트하게 관리할 수 있어 Spring Boot 통합에 최적입니다.
- Resilience4j
- 평가: 대용량 배치 적재 시 외부 연동망의 Flaky Connection을 견디도록
Retry(재시도) 및Circuit Breaker(서킷 브레이커) 아키텍처를 안전하게 결합하는 데 강력한 복원력을 선사해 줍니다.
- 평가: 대용량 배치 적재 시 외부 연동망의 Flaky Connection을 견디도록
- Vaul (by Radix UI)
- 평가: 모바일 뷰포트에서 세련되고 감각적으로 미끄러져 올라오는 바텀 시트 인터페이스 드로어를 구현해 줍니다. 타이핑 입력창 Debounce 로직과 Vaul 바텀 시트를 매칭하면 사용자에게 매끄럽고 명품 앱 같은 식단 검색 경험을 보장합니다.
- meilisearch-java SDK
- 최종 검증:
- 기존 쿼리 시 풀 테이블 스캔으로 평균 1.5초 이상 무겁게 겉돌던 영양성분 조회가, Meilisearch Proxy 검색 도입 후 평균 15ms 미만의 실시간 응답속도로 비약적으로 100배 단축되었습니다. 벌크 적재 실패 테스트에서도 전원을 강제로 내렸다 다시 올려 보았는데 이중 기록 없이 실패 구간부터 안전하게 재적재 완료됨을 성공적으로 검증했습니다.