LLM 리포트 생성 파이프라인의 완료 이벤트가 조용히 유실되는 버그를 추적하다가, 7,600줄짜리 오케스트레이터의 복잡함 대부분이 Lambda 15분 타임아웃을 우회하기 위한 코드였음을 알게 됐습니다. 버그를 고치는 대신 실행 기반을 Temporal로 옮기고, 91곳의 정규식 사후 정제를 Spring AI structured output 계약으로 대체한 과정입니다.
2025년 5월, 뭘 만들지도 정하지 못한 채 시작한 프로젝트가 14개월 뒤 결제와 교사 매칭, AI 리포트 생성이 돌아가는 유료 프로덕트가 됐습니다. 그 사이에 아키텍처를 두 번 접었고, 상품 하나를 만들었다 제거했고, 장애를 겪었습니다. 만든 것보다 접은 것 중심으로 정리한 회고입니다.
Native Query를 Kotlin JDSL로 마이그레이션하면서 확장 함수 패턴을 도입했습니다. 일반 join을 fetchJoin으로 변경하여 N+1 쿼리 문제를 해결하고, 데이터베이스 레벨에서 직접 집계 연산을 수행하여 쿼리 성능을 개선했습니다. Kotlin JDSL의 타입 안전성과 확장 함수 패턴을 활용하여 유지보수성을 크게 향상시켰습니다.
HikariCP 대시보드에서 Connection Timeout이 27회 발생하는 문제를 발견했습니다. 원인은 N+1 쿼리 문제와 작은 Connection Pool 크기였습니다. 배치 조회로 N+1 쿼리를 해결하고 Connection Pool 크기를 5에서 20으로 증가시켜 Timeout을 0으로 만들었습니다. 쿼리 수는 161개에서 3개로 98% 감소했습니다.
Multi-Repo 구조의 마이크로서비스에서 코드 중복 문제를 해결하기 위해 Common Package를 도입한 경험을 공유합니다. Mono-Repo vs Multi-Repo 고민, Common Package 설계 원칙, GitHub Packages 배포, 그리고 실제 적용 과정에서 얻은 교훈을 담았습니다.
여러 알림 채널(이메일, 카카오톡, Discord)을 지원하는 Notification Service에서 팩토리 패턴을 적용한 실전 사례입니다. 채널별로 다른 구현을 가진 Provider들을 Factory를 통해 동적으로 선택하여 확장하기 쉽고 유지보수하기 좋은 구조를 만든 과정을 공유합니다.
마이크로서비스 아키텍처에서 동기식 HTTP 호출의 한계를 해결하기 위해 GCP Pub/Sub을 도입하여 비동기 알림 시스템을 구축했습니다. lms-service와 payment-service에서 notification-service로의 알림 전송을 Pub/Sub을 통해 처리하여 서비스 간 결합도를 낮추고 안정성을 높였습니다.
멀티테넌시 LMS 서비스를 구축하면서 Organization Subdomain 기반 라우팅과 도메인 설계를 통해 각 조직의 데이터를 안전하게 격리하는 아키텍처를 구현했습니다. OrganizationIdArgumentResolver를 통한 자동 OrganizationId 추출과 Shared Database 전략을 중심으로 설명합니다.
Spring Boot와 Hibernate를 사용하여 PostgreSQL의 jsonb 타입을 다룰 때 발생하는 타입 불일치 문제를 해결한 경험을 공유합니다. hypersistence-utils 라이브러리를 활용해 간단하고 안전하게 jsonb 컬럼을 Map 타입으로 매핑하는 방법을 설명합니다.