이설아seola12e
전체 프로젝트
PROJECT 01MSA · 모의투자팀 프로젝트

MONI

실시간 시세 기반 모의투자와 AI 기업 이슈 분석, 투자 성향 기반 포트폴리오 진단을 하나의 흐름으로 연결한 초보 투자자용 모의투자 플랫폼

PERIOD
2026.06.10 — 2026.07.13 (약 5주)
TEAM
6인
ROLE
Portfolio 서비스 · 포트폴리오 AI 분석 · 자산 조회
LINKS
GitHub
MONI 서비스 화면 — 종목 상세, AI 기업 분석, 홈, 포트폴리오

01프로젝트 개요

모니는 투자 경험이 부족한 초보 투자자가 복잡한 금융 지표와 기업 정보를 쉽게 이해하고, 실제 자산 없이 안전하게 투자를 경험할 수 있도록 기획한 서비스입니다. 실시간 시세와 연동된 모의 매수·매도, AI가 요약한 기업 이슈와 뉴스, 투자 성향과 보유 종목을 비교하는 포트폴리오 AI 분석을 제공합니다. 사용자·거래·시세·포트폴리오·결제·알림·AI·어드민 8개 도메인 서비스를 Spring Cloud 기반 MSA로 분리했고, 저는 포트폴리오 서비스를 맡아 AI 분석 요청 흐름과 자산 조회를 구현했습니다.

GOAL 1서비스 간 결합도를 최소화해 언제든 스케일 아웃 가능한 MSA 구조
GOAL 2외부 API 장애가 전체로 번지지 않는 Fallback · 타임아웃 설계
GOAL 3모의투자 · 결제 로직의 정합성 보장과 상황에 맞는 동시성 제어

02담당 역할

포트폴리오 AI 분석분석 요청 API를 202 Accepted + Async Worker 구조로 설계하고, 분석 상태(PENDING · SUCCESS · FAILED)와 투자 성향 대비 포트폴리오 위험도 · 적합도 점수 계산을 구현
결제 정책 연동payment-service 구독 이벤트를 Kafka로 수신해 로컬 구독 상태에 반영하고, 무료 플랜 누적 5회 · 전체 사용자 하루 1회 분석 제한 정책을 구현
자산 · 보유 종목 조회매수·매도 이력 기반 예수금 · 평가손익 · 수익률 계산과 AI 분석용 자산 스냅샷 API 구현(trade-service로 이관). 보유 종목 시세를 종목 수만큼 단건 호출하던 방식을 다중 조회 1회로 변경
부하 테스트 · QAk6 부하 테스트 시나리오에서 분석 요청 접수 성공과 이용 한도 도달에 따른 차단을 구분하고, 요청 접수와 결과 조회의 응답 시간을 각각 관찰하도록 개선. 서비스별 Feign 타임아웃 설정, Swagger API 명세 작성, 1~3차 QA 참여

03기술 스택

Kafka분석 요청마다 payment-service에 구독 상태를 동기 조회하던 구조를 구독 이벤트 수신 방식으로 바꿔, 결제 서비스 장애가 AI 분석 요청으로 전파되지 않도록 함
@Async분석 전용 스레드 풀(core 4 · max 16 · queue 100)을 분리해, 트랜잭션 커밋 이후의 외부 서비스 호출과 LLM 분석을 사용자 요청 흐름에서 떼어냄
OpenFeigntrade · user · ai-service 호출에 서비스별 connect/read 타임아웃(trade 3초 · user 2초 · ai 30초)을 명시해 하위 서비스 지연이 무한 대기로 이어지지 않도록 함
PostgreSQL서비스별 독립 DB. 분석 요청 시 포트폴리오 행을 비관적 락(PESSIMISTIC_WRITE)으로 잠가 같은 사용자의 중복 요청을 직렬화
Java 21Spring Boot 3.5Spring Data JPASpring Cloud GatewayEurekaRedisDocker ComposeAWS EC2GitHub ActionsSwaggerk6

04시스템 아키텍처

포트폴리오 AI 분석 흐름. 요청 트랜잭션에는 검증과 PENDING 저장만 남기고 202로 응답하며, 외부 서비스 호출과 LLM 분석은 커밋 이후 Async Worker가 처리합니다. 구독 상태는 결제 서비스를 직접 호출하지 않고 Kafka 이벤트로 받아 둔 로컬 상태를 조회합니다.
MONI 전체 인프라 아키텍처 — AWS VPC 내 서비스 · 인프라 · 모니터링 EC2, ALB, Route53, CloudFront, GitHub Actions CI/CD
전체 인프라 아키텍처 (팀 공동). AWS Private Subnet에 서비스 · 인프라 · 모니터링 EC2를 분리하고 ALB를 통해서만 외부에 노출합니다.

05주요 구현 기능

5.1

202 Accepted + Async Worker 기반 AI 분석

분석 요청 트랜잭션에서는 포트폴리오 행 잠금, 일일 제한 · 결제 정책 검증, PENDING 저장만 수행하고 analysisId를 202로 즉시 반환합니다. 실제 분석은 트랜잭션 커밋 이후 전용 스레드 풀에서 trade-service 자산 스냅샷 → user-service 투자 성향 → ai-service LLM 분석 순으로 진행하고, 사용자는 polling으로 상태를 확인합니다.

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
    @Override
    public void afterCommit() {
        portfolioAnalysisAsyncExecutor.requestAiAnalysis(analysisId, userId);
    }
});
5.2

투자 성향 적합도 점수

보유 종목 집중도(60%) · 주식 비중(25%) · 보유 종목 수(15%)로 포트폴리오 위험도를 0~100점으로 계산하고, 사용자의 투자 성향 점수와의 차이로 적합도(100 − |성향 − 위험도|)를 산출해 AI 분석 요청에 함께 전달합니다. 투자 성향 조회에 실패하면 성향 분석만 생략하고 기본 분석은 계속 진행합니다.

5.3

구독 이벤트 기반 분석 이용 정책

payment.subscription.succeeded · cancelled · suspended 이벤트를 수신해 portfolio-service의 구독 상태 테이블에 반영하고, 분석 요청 시에는 이 로컬 상태로 유료 여부를 판단합니다(상태가 없으면 무료 플랜). 무료 플랜은 누적 5회, 모든 사용자는 하루 1회로 제한하되 FAILED 분석은 제한에 포함하지 않아 재요청할 수 있습니다.

06트러블슈팅

ISSUE 01

AI 분석 요청 지연과 외부 서비스 장애 전파

문제
분석 요청 하나에 payment · trade · user · ai-service 호출이 섞여 있어, PENDING 응답을 받기까지 942ms가 걸렸고 외부 서비스 장애가 분석 요청 자체를 실패시킬 수 있었음
원인
여러 서비스 데이터를 모아 LLM 결과를 만드는 작업을 요청 트랜잭션 안에서 동기로 처리했고, 유료 여부 하나를 알기 위해 매 요청마다 payment-service를 Feign으로 조회하고 있었음
해결
요청은 202로 즉시 수락하고 외부 호출은 커밋 이후 Async Worker로 분리. trade · ai-service 실패는 FAILED 처리, user-service 실패는 fallback으로 분리하고, payment-service 동기 호출은 Kafka 구독 이벤트 기반 로컬 상태 조회로 대체
결과
분석 요청 응답 942ms → 420ms (단건 측정) · 결제 서비스가 중단되어도 AI 분석 요청은 정상 수락
ISSUE 02

비동기 처리 중 PENDING 상태로 남는 분석

문제
Async Worker가 중단되거나 응답을 받지 못하면 분석이 PENDING으로 남아, 하루 1회 제한에 걸려 사용자가 다시 요청할 수 없는 상황이 생길 수 있었음
원인
오래된 PENDING을 스케줄러로 정리하는 방법을 검토했지만, 사용자당 하루 1회인 정책에 비해 전체 DB를 주기적으로 스캔하는 비용이 과하다고 판단
해결
분석 요청(POST)과 단건 조회(GET) 시점에만 10분이 지난 PENDING을 FAILED로 바꾸는 lazy timeout 적용. 결과 저장 직전에 PENDING 여부를 다시 확인하고, FAILED는 일일 제한에서 제외
결과
스케줄러 없이 필요한 시점에만 정리 · 실패 원인이 FAILED로 명확히 남고 실패한 분석은 재요청 가능

07회고

KEEP · 잘한 점

단순히 기능을 구현하는 데 그치지 않고, 어떤 기술이 서비스에 적합한지 고민하고 성능과 장애 상황까지 고려하며 개발했습니다.

PROBLEM · 아쉬운 점

MSA로 서비스를 나눴지만 서비스 간 연결이 여전히 강했습니다. 포트폴리오 AI 분석 하나에도 trade · user · ai · payment 서비스가 모두 얽혀 있어, 한 서비스의 지연이나 변경이 다른 서비스로 이어지는 구조를 완전히 끊어내지 못했습니다.

TRY · 다음에는

설계 단계부터 서비스 경계와 데이터 소유권을 분명히 나누고, 즉시 응답이 꼭 필요한 호출이 아니라면 이벤트로 연결해 결합도를 낮춰 보고 싶습니다. 구독 상태 연동에서 Kafka로 장애 전파를 끊어 본 경험을 바탕으로, 다음에는 서비스 간 연동 전반에 Kafka를 더 폭넓게 적용해 보고 싶습니다.