TRENDSHIFT 딥다이브 · 2026.05.27

FinSight-AI 딥다이브
— Spring Boot + pgvector + Redis Lua로 만드는 신뢰 가능한 AI 리서치 에이전트

재무제표·공시·리서치 노트·시장 데이터를 받아 증거에 정박된(evidence-grounded) AI 주식 리서치 리포트를 만드는 오픈소스 에이전트. 핵심은 LLM 호출이 아니라 RAG를 둘러싼 인프라 — 워크플로우 오케스트레이션, 동시성 제어, 캐시 신뢰성, 검색 증거 추적. Java 72.6% · Python 4.6% · ★ 261 · TrendShift 23위 · MIT
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어/시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

무엇을 만드는 레포인가, 한 문장으로
한 문장 정리

"재무제표·공시·리서치 노트·시장 데이터·기업 이벤트를 받아 증거에 정박된 AI 주식 리서치 리포트를 만드는 오픈소스 에이전트 — 본체는 LLM이 아니라 그 LLM을 둘러싼 워크플로우 오케스트레이션·동시성 제어·캐시 신뢰성·검색 증거 추적 인프라다."

대부분의 RAG 데모는 "청크 가져와서 LLM에 던지기"에서 끝난다. FinSight-AI는 그 너머의 "AI 시스템이 실무에서 깨지지 않으려면 무엇을 해야 하는가"를 코드로 보여준다. Spring Boot 3.3 + Java 17이 백엔드 중심이고, Python(FastAPI) AI 서비스는 임베딩·리랭킹·생성을 담당하는 사이드카로 분리된다. AI 응답이 안 와도 결정론적 폴백으로 데모가 계속 돌아가는, 시연에 최적화된 설계가 특징이다.

한 줄로 말하면

"RAG 한 방"이 아니라 장기 실행 워크플로우 + 단일 비행 락 + 데이터 스냅샷 해시 캐시 + pgvector 하이브리드 검색 + RAG 회귀 평가를 한 레포 안에 통합한 백엔드 엔지니어링 쇼케이스. 그래서 풀스택 백엔드 + AI 인프라를 동시에 공부하기 좋은 교재다.

2왜 주목받는가

"AI 데모"가 아니라 "AI 시스템 운영" 문제를 푼다

1. "AI 데모"가 아니라 "AI 시스템 운영" 문제를 푼다

2026년 들어 LLM 활용이 PoC를 넘어 실서비스로 들어가면서, "모델 호출 자체가 아니라 그 주변 인프라가 무너진다"는 문제가 부상했다. 모델 응답 타임아웃, 중복 비용 청구, 캐시 불일치, 인덱싱 부분 실패 — 이런 운영 문제를 체계적으로 보여주는 오픈소스 레포는 의외로 드물다. FinSight-AI는 이 빈틈을 정확히 노린다.

흔한 한계
흔한 RAG 데모의 한계
FinSight-AI의 처방
5가지 운영 인프라로 받친다

2. 경쟁/유사 프로젝트와 비교

포지셔닝

langchain·llamaindex는 라이브러리이고 운영 문제는 사용자에게 맡긴다. dify·langgenius는 워크플로우 UI는 있지만 Spring Boot 같은 백엔드 엔지니어링 문맥은 약하다.

FinSight-AI는 "Java/Spring 백엔드 엔지니어가 AI 시스템을 시연 가능한 형태로 만든다면 어떻게 짤까"에 대한 구체적 답안이다. 별 261개 + TrendShift 23위는 그만큼 "취업/이직 포트폴리오"로서의 호소력이 높다는 시그널. 실제로 docs 폴더에 resume-and-interview.md가 있다.

3기술 스택 전체 지도

백엔드(Java) / AI 사이드카(Python) / 정적 프론트 / 도커 인프라

백엔드 (Java 72.6%)

구성요소버전·역할
Java17 — Spring Boot 3.x 요구사항
Spring Boot3.3 — REST API, 워크플로우 오케스트레이션, 메트릭 엔진
Spring JDBC (JdbcTemplate) + Flyway스키마 마이그레이션 자동화, JSONB·pgvector 컬럼 수동 매핑
Spring Boot Actuator + Micrometer/actuator/health·/actuator/prometheus로 운영 신호 노출
Spring AMQPRabbitMQ 비동기 워크플로우, dead-letter queue 처리
Lettuce / Spring Data RedisRedis Lua 단일 비행 락, AI 리포트 캐시
Maven빌드·테스트, Testcontainers 통합

AI 서비스 (Python 4.6%)

구성요소버전·역할
FastAPI + Uvicorn임베딩(/embed)·리랭킹(/rerank)·생성(/generate-answer)·종목 분석(/analyze-stock) 엔드포인트 제공
Ollama (선택)qwen2.5:7b 모델로 로컬 LLM 호출. 미설치 시 결정론적 룰 기반 폴백
Pydantic요청/응답 스키마 검증

프론트엔드 (정적 대시보드 · JS 10.6% / CSS 9% / HTML 2.2%)

별도 SPA 프레임워크 없음. Spring Boot가 / 루트에서 정적 파일을 서빙하는 단순한 대시보드. Vanilla JS로 REST API를 호출해 워크플로우 상태·메트릭·검색 증거·타임라인·그래프·평가 결과를 표시한다. 의도적으로 "백엔드 중심" 프로젝트라는 정체성을 지키기 위한 선택.

인프라 (Docker Compose · Shell 0.8% / Dockerfile 0.2%)

서비스이미지역할
postgrespgvector/pgvector:pg16JSONB + 전문검색 + pgvector 코사인 인덱스
redisredis:7-alpineLua 단일 비행 락, 리포트 캐시
rabbitmqrabbitmq:3-management비동기 워크플로우 큐 + DLQ
elasticsearchelasticsearch:8.15.3(확장 영역) 추가 전문검색 백엔드
miniominio:latest(확장 영역) S3 호환 객체 스토리지
ai-serviceFastAPI 컨테이너임베딩/리랭킹/생성
backendSpring Boot 컨테이너API + 워크플로우 + 메트릭
설계 포인트
스프링 프로파일 조합이 핵심이다
한 코드베이스에서 "노 도커 로컬 실행"부터 "풀 프로덕션 스택"까지 모두 돌릴 수 있는 비결은 프로파일 분리. postgres,rabbitmq,redis,prod를 모두 켜면 DB·MQ·캐시가 살아 있는 완전체. 아무것도 안 켜면 in-memory 리포지토리 + 로컬 락 + 결정론 폴백으로 동작한다. "프로파일 하나에 인프라 의존성 하나를 매핑한다"는 패턴은 그대로 베껴서 쓸 만한 설계.

4아키텍처 심화 분석

전체 시스템 구조도 + 4가지 핵심 설계 패턴

전체 시스템 구조도

어느 입구로 들어와도 Spring Boot 게이트웨이가 받고, 그 아래에서 워크플로우 오케스트레이터와 하이브리드 검색이 동작한다. AI 호출은 FastAPI 사이드카로 분리되고, 상태·증거·캐시는 PostgreSQL+Redis+RabbitMQ 셋이 나눠 든다.

┌──────────────────────────────────────────────────────────────────────────┐ │ Dashboard (Static / Vanilla JS) │ │ "/" 경로에서 Spring Boot가 직접 서빙 — SPA 없음 │ └────────────────────────────────┬─────────────────────────────────────────┘ │ REST (JSON) ▼ ┌──────────────────────────────────────────────────────────────────────────┐ │ Spring Boot 3.3 (Java 17) │ │ ┌───────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ │ │ │ REST API │ │ WorkflowOrchestrator │ │ MetricEngine │ │ │ │ Controllers │──▶│ (Idempotency+Lease) │──▶│ + RiskRules │ │ │ └───────┬───────┘ └──────────┬───────────┘ └─────────┬───────────┘ │ │ │ │ │ │ │ │ ┌───────▼────────┐ ┌──────▼──────────┐ │ │ │ │ RecoveryShdlr │ │ DocumentChunker │ │ │ │ │ (timeout scan) │ │ + Embedding │ │ │ │ └────────────────┘ └─────────────────┘ │ │ │ │ │ │ ┌──────────────────────┐ ┌──────────────────────────────┐ │ │ └──▶│ HybridRetrievalGate │──▶│ StockAiAnalysisService │ │ │ │ (keyword + vector) │ │ (snapshot hash + cache) │ │ │ └──────────┬───────────┘ └──────────────┬───────────────┘ │ └─────────────────────────┼──────────────────────────────┼──────────────────┘ │ │ ┌───────────▼──────────┐ ┌──────────────▼──────────────┐ │ PostgreSQL 16 │ │ FastAPI AI Service │ │ + pgvector 코사인 │ │ /embed /rerank │ │ + JSONB GIN │ │ /generate-answer │ │ + Flyway 마이그레이션│ │ /analyze-stock │ └───────────┬──────────┘ └──────────┬──────────────────┘ │ │ │ ┌─────────▼──────────┐ │ │ Ollama qwen2.5:7b │ │ │ (선택, 미설치 시 │ │ │ 결정론 폴백) │ │ └────────────────────┘ ┌─────────▼──────┐ ┌──────────────────┐ │ Redis 7 │ │ RabbitMQ 3 │ │ (Lua Lease │ │ (Async Workflow │ │ + ReportCache)│ │ + Dead Letter) │ └────────────────┘ └──────────────────┘

핵심 설계 패턴 1: 5단 상태머신 워크플로우

한 번의 종목 분석은 "데이터 수집 → 지표 계산 → 문서 인덱싱 → 인텔리전스 빌드 → AI 분석"의 5단계로 쪼개진다. 각 단계는 WorkflowTask 한 행에 status + stage + attempts로 영속화되어, 어느 단계에서 멈췄는지·몇 번 재시도했는지가 항상 추적 가능하다.

[*] → CREATED
CREATED → LEASE_ACQUIRED              ← Redis Lua 락 획득 시점
LEASE_ACQUIRED → INGESTING_DATA       ← Stage 1: 데이터 수집
INGESTING_DATA → METRIC_CALCULATING   ← Stage 2: 재무지표 + 리스크 시그널
METRIC_CALCULATING → DOCUMENT_INDEXING  ← Stage 3: 청크 + 임베딩 인덱싱
DOCUMENT_INDEXING → INTELLIGENCE_BUILDING  ← Stage 4: 타임라인 + KG
INTELLIGENCE_BUILDING → AI_ANALYZING  ← Stage 5: LLM 호출 + 리포트
AI_ANALYZING → SUCCEEDED | FAILED
FAILED → RECOVERING → LEASE_ACQUIRED  ← 재시도 회로
FAILED → DEAD_LETTER                  ← 최대 시도 초과
LEASE_ACQUIRED → LEASE_WAIT           ← 다른 워커가 락을 들고 있음
왜 두 축을 분리하나

status와 stage를 따로 두는 이유

status(CREATED/RUNNING/FAILED/SUCCEEDED/DEAD_LETTER)는 "워크플로우 시스템 입장에서의 생사 여부"를 표현한다. stage(INGESTING/METRIC/INDEX/INTEL/AI)는 "리서치 파이프라인 입장에서 어디까지 갔는가"를 표현한다.

두 축을 분리하면 "AI_ANALYZING 단계에서 FAILED인 작업만 골라 재시도"같은 운영 쿼리가 자연스러워진다. 설계 단계에서 "여러 관심사를 한 컬럼에 욱여넣지 않는다"는 원칙의 좋은 예시.

핵심 설계 패턴 2: Redis Lua 단일 비행 락(Single-Flight Lease)

같은 종목(예: 600519=귀주 마오타이)을 2개 인스턴스가 동시에 분석하려고 하면, LLM 호출이 중복되어 비용이 두 배로 새거나 cache stampede가 발생한다. 이를 막기 위해 FinSight는 Redis Lua 스크립트로 원자적 락 + 펜싱 토큰 발급을 한 방에 처리한다.

-- 의사 코드 (실제 구현 참고)
-- KEYS[1]: lease key (예: "lease:stock:600519")
-- ARGV[1]: caller_id, ARGV[2]: TTL_ms
local existing = redis.call("GET", KEYS[1])
if existing then
  return {0, existing}   -- 락 실패: 누가 들고 있는지 반환
end
local token = redis.call("INCR", "fencing:counter")
redis.call("SET", KEYS[1], ARGV[1] .. ":" .. token, "PX", ARGV[2])
return {1, token}        -- 락 성공: 펜싱 토큰 반환
용어 풀이
펜싱 토큰(Fencing Token)이 왜 중요한가
분산 시스템에서 "락을 받았다고 믿었지만 사실은 만료된 락"이라는 함정이 흔하다. 예: 인스턴스 A가 락을 얻고 GC로 30초 멈춤 → 락 TTL 만료 → 인스턴스 B가 락을 얻고 작업 → A가 깨어나서 DB에 쓰기 시도. 펜싱 토큰은 단조 증가 카운터라서, 다운스트림 쓰기에서 "내 토큰이 최신인지" 확인 가능. Martin Kleppmann의 유명한 "How to do distributed locking" 글의 패턴 그대로다.

핵심 설계 패턴 3: 데이터 스냅샷 해시 캐시

AI 응답을 캐시하면 빠르고 싸지만, "데이터가 바뀌었는데 옛 결론을 그대로 돌려주는" 사고가 가장 무섭다. FinSight는 캐시 키를 단순 프롬프트 해시가 아니라 세 겹으로 묶는다.

해시의미변경 트리거
contextHash프롬프트 문맥(질문 + 시스템 지시)의 지문질문 텍스트, 모델 버전 변경
dataSnapshotHash시장 데이터·지표·리스크·증거의 통합 해시새 공시, 지표 재계산, 새 증거 청크
reportVersion종목별 단조 증가 버전매번 새 리포트 생성 시 +1
핵심 효과
"옛 결론 반환"을 원천 차단

캐시 조회 시 (symbol, contextHash, dataSnapshotHash)가 모두 일치해야만 적중. 데이터가 바뀌면 dataSnapshotHash가 바뀌어 자동으로 미스가 되고 새 리포트가 생성된다. "캐시 무효화는 어렵다"는 격언에 대한 매우 깔끔한 답안.

핵심 설계 패턴 4: 하이브리드 검색(키워드 + 벡터)

순수 벡터 검색은 "현금흐름 위험" 같은 정확한 용어 매칭에 약하고, 순수 키워드 검색은 "재무 건전성 우려" 같은 의미적 유사 표현을 놓친다. FinSight는 PostgreSQL 하나로 두 채널을 모두 돌리고 결과를 머지한다.

// 의사 코드: HybridRetrievalGateway
List<Chunk> keywordHits = chunkRepo.fulltextSearch(symbol, query);      // GIN + ts_vector
List<Chunk> vectorHits   = chunkRepo.vectorSearch(symbol, embedding);   // pgvector 코사인
List<Chunk> merged = dedupAndRerank(keywordHits, vectorHits);
return merged.stream().map(this::toEvidence).toList();

5디렉토리 구조 해부

Spring Boot 백엔드 + Python 사이드카 + docs/scripts 학습 자료
FinSight-AI/ ├── backend/ ← Spring Boot 메인 모듈 (Java 72.6%) │ ├── pom.xml ← Maven 빌드 정의 │ ├── src/main/java/... │ │ ├── workflow/ ← WorkflowOrchestrator, RecoveryScheduler, │ │ │ LeaseService(Redis Lua), Publisher(MQ/Direct) │ │ ├── metrics/ ← MetricDefinitionCatalog, RiskRules │ │ ├── rag/ ← DocumentChunker, EmbeddingService, │ │ │ HybridRetrievalGateway, EvidenceRetriever │ │ ├── application/ ← CompanyIntelligenceService (타임라인 + 그래프), │ │ │ StockAiAnalysisService, StockAnalysisCache │ │ ├── api/ ← Controllers: /api/workflows, /api/metrics, │ │ │ /api/evaluations, /api/companies, ... │ │ └── infrastructure/ ← InMemory*/Jdbc* 구현체, @Profile 기반 빈 전환 │ └── src/main/resources/ │ ├── application*.yml ← 프로파일별 설정 분리 │ ├── db/migration/ ← Flyway SQL (V1__init.sql 등) │ └── static/ ← Vanilla JS 대시보드 (index.html, app.js, style.css) │ ├── ai-service/ ← FastAPI AI 사이드카 (Python 4.6%) │ ├── app/main.py ← /embed, /rerank, /generate-answer, /analyze-stock │ ├── requirements.txt │ └── Dockerfile │ ├── docs/ ← 설계 문서 모음 (★ 학습 가치 매우 큼) │ ├── architecture.md ← 전체 시스템 노트 │ ├── design-agent-workflow.md ← 워크플로우 상태머신 설계 │ ├── benchmark.md ← 평가 메트릭 + 데모 결과 │ ├── api.md ← REST 엔드포인트 카탈로그 │ ├── resume-and-interview.md ← 면접/이력서 어필 포인트 │ ├── troubleshooting.md │ └── github-profile.md │ ├── scripts/ ← 시연용 셸 스크립트 (Shell 0.8%) │ ├── run-full-stack.sh ← 전체 도커 컴포즈 + 데모 시드 한 방 │ ├── quick-demo.sh ← 짧은 데모 흐름 │ ├── demo-flow.sh / demo-workflow.sh │ └── run-backend-workflow.sh │ ├── docker-compose.yml ← postgres·redis·rabbitmq·es·minio·backend·ai ├── ROADMAP.md ← 향후 추가 예정 벤치마크/기능 ├── README.md / README.zh-CN.md └── LICENSE (MIT)
학습용 산책 순서

처음 클론한 사람을 위한 추천 동선

  1. docs/architecture.md → 전체 그림 5분 컷
  2. docs/design-agent-workflow.md → 상태머신과 락 설계 이유
  3. backend/workflow/ → 실제 코드와 위 설계 매핑
  4. backend/retrieval/HybridRetrievalGateway → 키워드+벡터 머지 로직
  5. backend/stock/StockAiAnalysisService → 캐시 키 + 폴백 분기
  6. db/migration/V*.sql → JSONB·pgvector 스키마 실제 정의

6학습 포인트 (기술별)

이 레포에서 분야별로 가져갈 8가지 기술
SPRING BOOT

프로파일 기반 인프라 토글

하나의 코드베이스를 "노 도커 in-memory"부터 "풀 프로덕션"까지 돌릴 수 있는 비결. 각 인프라를 별도 프로파일에 매핑한 다음, @Profile("redis")로 빈을 갈아끼우는 패턴을 익혀라.

실습: 자신의 사이드 프로젝트에 local·postgres·redis 프로파일을 도입하고 빈 두 벌을 준비해 보기.

POSTGRESQL · PGVECTOR

JSONB + 전문검색 + 벡터를 한 DB에서

Elasticsearch + Pinecone + RDBMS를 세 벌 운영하는 대신, PostgreSQL 16 + pgvector 확장만으로 하이브리드 검색을 구현하는 법. 인프라 단순화의 모범 사례.

실습: CREATE EXTENSION vector; 후 384차원 컬럼에 코사인 인덱스 만들고 <=> 연산자로 top-k 검색해 보기.

REDIS · DISTRIBUTED LOCK

Lua 스크립트로 만드는 원자적 락

SETNX + EXPIRE 두 명령은 원자성이 깨진다는 함정. Lua 스크립트 한 번 호출 = 한 트랜잭션이라는 사실, 그리고 펜싱 토큰의 역할을 코드로 체득.

실습: Lettuce eval()로 락 획득 Lua를 짜고, 두 스레드가 경쟁하는 JUnit 테스트로 1명만 이기는지 검증.

RABBITMQ · DLQ

비동기 워크플로우와 데드레터 큐

Spring AMQP의 x-dead-letter-exchange·x-dead-letter-routing-key 설정으로 실패 메시지를 격리하고, 회수 스케줄러로 재처리하는 패턴.

실습: 일부러 실패하는 컨슈머를 만들어 메시지가 DLQ로 흘러가는 것을 RabbitMQ 관리 UI(:15672)에서 확인.

RAG · EVALUATION

RAG 회귀 테스트 만들기

"체감상 좋아진 것 같다"를 벗어나 RAG 품질을 숫자로 추적하는 법: hit rate, evidence coverage, hallucination risk, citation presence를 측정해 회귀 테스트로 굳히는 흐름.

실습: 자신의 RAG 프로젝트에 10개 정답 케이스를 만들고, CI에서 점수가 떨어지면 빌드를 깨도록 임계값 설정.

FASTAPI · PYTHON

Spring과 FastAPI 사이드카 분리

"AI 부분은 Python이 편하고, 트랜잭션·MQ·캐시는 Spring이 편하다"는 현실적 절충. REST 경계로 두 언어를 분리하고, 사이드카가 죽어도 폴백으로 데모를 살리는 패턴.

실습: 자신의 Spring 앱에서 FastAPI 엔드포인트를 WebClient로 호출하고, 타임아웃·서킷브레이커를 Resilience4j로 추가.

FLYWAY · MIGRATION

스키마를 git에 두는 습관

V1__init.sqlV2__add_chunks.sql 같은 버전 마이그레이션을 운영. "DB 콘솔에서 ALTER TABLE 친다"는 안티패턴을 버리는 출발점.

실습: Flyway로 컬럼 하나 추가 + 백필 SQL 한 쌍을 짜고, 롤백 시나리오까지 검토.

ACTUATOR · PROMETHEUS

운영 가시성 기본기

/actuator/health로 도커 컴포즈 healthcheck를 묶고, /actuator/prometheus로 Grafana 대시보드 연결. AI 호출 비용·실패율을 카운터로 노출하는 출발점.

실습: 자신의 앱에 finsight.ai.calls.total·finsight.cache.hit.ratio 같은 메트릭을 추가하고 Prometheus에서 PromQL 한 줄 작성.

7하드웨어/시스템 요구사항

노 도커 시연부터 풀 스택 + 로컬 LLM까지

최소 구동(시연용 — 노 도커 모드)

항목요구사항
OSmacOS / Linux / WSL2
JavaJDK 17 (OpenJDK 권장)
Maven3.9+ (또는 포함된 mvnw)
메모리2GB 이상 (in-memory 리포지토리 + 결정론 폴백 모드)
디스크~500MB

풀 스택(도커 컴포즈)

항목요구사항
Docker20.10+ / Docker Compose v2
메모리8GB 이상 권장 (Elasticsearch 512MB heap, JVM 백엔드, AI 서비스 등)
디스크~3GB (postgres+es+minio 볼륨 포함)
포트8080(backend), 8001(ai-service), 5432, 6379, 5672, 15672, 9200, 9000, 9001

로컬 LLM 활성화(선택)

항목요구사항
Ollama설치 + ollama serve 가동
모델ollama pull qwen2.5:7b (~4.7GB)
메모리qwen2.5:7b는 8GB+ 권장, GPU 있으면 빠름
네트워크FastAPI 사이드카 → Ollama: host.docker.internal:11434
설계 의도
왜 Ollama 미설치 시에도 데모가 돌아가나

FastAPI 사이드카의 /analyze-stock은 Ollama 호출에 실패하면 결정론적 룰 기반 응답을 돌려준다(aiGenerated=false 플래그 포함). 덕분에 발표·이력서 시연에서 "왜 인터넷이 끊겼지" 같은 사고가 안 난다. AI 시스템 발표 시 가장 자주 깨지는 부분을 미리 막아둔 영리한 설계.

8직접 해볼 수 있는 실습 과제

EASY 2개 · MID 2개 · HARD 2개 — 6단계 코스
EASY · 30분

실습 1: 노 도커 모드로 데모 한 바퀴

cd backend && mvn spring-boot:runhttp://localhost:8080 접속.

대시보드에서 워크플로우 한 건을 직접 트리거하고, GET /api/workflows/summary 응답을 그대로 캡처해보자. in-memory 모드라도 상태 머신 흐름은 동일하다.

EASY · 1시간

실습 2: docker-compose 전체 스택 띄우기

./scripts/run-full-stack.sh 한 방으로 7개 컨테이너가 뜬다. docker compose ps로 healthcheck 상태 확인.

특히 RabbitMQ 관리 UI(http://localhost:15672, finsight/finsight)에서 큐가 어떻게 만들어졌는지 살펴보자.

MID · 2시간

실습 3: 데이터 스냅샷 해시 직접 깨보기

./scripts/quick-demo.sh로 종목 600519 리포트 생성 → 같은 요청 한 번 더 → 캐시 히트 확인.

이어서 POST /api/metrics/recalculate/600519로 지표 재계산 → 같은 분석 요청 → 이번엔 캐시 미스가 나고 새 reportVersion이 부여되는지 검증.

MID · 3시간

실습 4: pgvector 쿼리 손으로 짜보기

psql로 접속해 document_chunks 테이블의 임베딩 컬럼을 확인.

임의의 384차원 벡터를 만들어 SELECT * FROM document_chunks ORDER BY embedding <=> '[...]'::vector LIMIT 5;로 직접 top-k 검색. 그리고 EXPLAIN ANALYZE로 IVFFlat 인덱스가 실제로 사용되는지 확인.

HARD · 반나절

실습 5: RAG 평가 케이스 추가 + CI 회귀 게이트

EvaluationCaseCatalog에 자신의 평가 케이스 3개를 추가하고 POST /api/evaluations/rag/run으로 점수를 뽑는다.

GitHub Actions에서 mvn test가 평가 점수 임계값 80점 이하면 빌드를 깨도록 게이트를 만든다. LLM 평가 회귀 게이트라는 모던한 CI 패턴을 직접 체득.

HARD · 하루

실습 6: 단일 비행 락 동시성 테스트

JUnit에서 100개 스레드가 동시에 같은 종목 분석을 요청하도록 코드를 짜고, 실제로 LLM 호출이 1번만 일어나는지(나머지는 락 미획득 분기로 빠지는지) 검증.

이어서 Redis를 일부러 끄고, 로컬 폴백 락으로도 같은 결과가 나오는지 비교. "분산 락 코드는 단일 노드에서도 안전해야 한다"는 원칙을 손에 익힌다.

9관련 기술 심화 학습 로드맵

8주 코스로 Spring + RAG + 운영을 체득
WEEK 1 — SPRING BOOT 본체 다지기

Spring Boot 3 · 프로파일 · Actuator

FinSight 백엔드를 노 도커 모드로 띄우고 코드 산책. application.yml·application-postgres.yml·application-rabbitmq.yml이 어떻게 합성되는지, @Profile 빈이 어떻게 갈아끼워지는지 추적. Actuator 엔드포인트를 모두 호출해본다.

WEEK 2 — POSTGRESQL 깊게 파기

JSONB · 전문검색 · pgvector

Flyway 마이그레이션 파일을 따라 읽으며 document_chunks·workflow_tasks·stock_analysis_reports 스키마 분석. GIN 인덱스 / IVFFlat 인덱스 / B-tree 차이를 EXPLAIN으로 확인. pg_stat_statements로 자주 도는 쿼리 추출.

WEEK 3 — REDIS · 분산 락

Lua 스크립트 · 펜싱 토큰 · 폴백 락

Martin Kleppmann의 "How to do distributed locking" 글을 읽고, Redlock 비판 논쟁을 정리. FinSight의 LeaseService 코드와 비교. Redis 7의 FUNCTION 명령(영구 Lua 등록) 도입까지 실험.

WEEK 4 — 메시지 큐 운영

RabbitMQ · DLQ · 타임아웃 회수

Spring AMQP의 RabbitListener·RetryTemplate·DLQ 라우팅 패턴을 따라 짜보고, FinSight의 RabbitWorkflowListener와 비교. Kafka로 같은 시나리오를 구현해보면 두 시스템의 의미적 차이가 드러난다.

WEEK 5 — RAG 시스템 엔지니어링

청킹 · 하이브리드 검색 · 증거 추적

Anthropic의 "Contextual Retrieval" 블로그, Pinecone 가이드, LangChain의 RAG 패턴을 정리. FinSight의 DocumentChunker(섹션 메타 + 오버랩)와 HybridRetrievalGateway를 따라 짠 다음, 자신의 코퍼스에 적용.

WEEK 6 — LLM 평가 회귀 게이트

RAGAS · TruLens · 커스텀 메트릭

RAGAS·TruLens 같은 표준 라이브러리를 익히고, FinSight의 RagEvaluationService 메트릭 8종(hit rate, evidence/answer coverage, hallucination, consistency, calibration, latency, citation)을 비교. 자신의 시스템에 맞는 평가셋 + 회귀 임계값을 CI에 박는다.

WEEK 7 — 관측성 + 비용 관리

Prometheus · Grafana · OpenTelemetry

Micrometer로 LLM 호출 카운터·지연시간 히스토그램·캐시 hit ratio를 노출하고 Grafana 대시보드 구성. OpenTelemetry trace로 "한 종목 분석이 5단계 워크플로우를 어떻게 통과했는지" 시각화. AI 비용 알람도 추가.

WEEK 8 — 포트폴리오 한 줄 정리

이력서 · 면접 · 데모 영상

FinSight의 docs/resume-and-interview.md 패턴을 그대로 흉내내, 자신만의 AI 인프라 프로젝트를 한 줄 요약 + 시연 GIF + 핵심 패턴 3개로 정리. "AI 모델 호출"이 아니라 "AI 시스템 운영"이라는 단어로 본인 정체성을 새롭게 포지셔닝.

10핵심 키워드 사전

자주 등장하는 용어 15개 풀이
RAG
RAG (Retrieval-Augmented Generation)
검색으로 가져온 외부 증거를 프롬프트에 동봉해 LLM에 답하게 하는 패턴. FinSight의 모든 분석은 RAG 기반이며, 답변에 항상 증거 청크가 인용된다.
DB · 확장
pgvector
PostgreSQL에서 벡터 임베딩을 저장·검색할 수 있게 해주는 확장. vector(384) 같은 컬럼 타입과 <=>(코사인 거리) 같은 연산자를 제공한다. 별도 벡터 DB 없이 RDBMS 하나로 RAG를 굴릴 수 있다.
분산 시스템
Idempotency Key
"같은 키로 두 번 요청해도 한 번만 실행"을 보장하는 식별자. FinSight는 종목+요청 종류로 만든 키를 createIfAbsent로 DB에 박아 중복 워크플로우 생성을 막는다.
동시성 제어
Single-Flight Lease (단일 비행 락)
"같은 작업이 여러 인스턴스에서 동시에 일어나면, 한 명만 이기고 나머지는 결과를 기다린다"는 패턴. Redis Lua로 원자적으로 구현. cache stampede·중복 LLM 비용을 방지한다.
분산 락
Fencing Token
락에 부여되는 단조 증가 정수. 락이 만료된 줄 모르고 작업을 시도하는 stale owner를 다운스트림에서 거부할 수 있게 해준다. Martin Kleppmann이 분산 락 비판에서 제안.
메시지 큐
Dead Letter Queue (DLQ)
최대 시도 횟수를 넘긴 실패 메시지를 격리하는 별도 큐. 정상 큐를 막지 않으면서 운영자가 사후 분석할 수 있도록 보존한다. RabbitMQ에서는 x-dead-letter-exchange로 설정.
캐시 신뢰성
Data Snapshot Hash
시장 데이터·지표·리스크·증거의 통합 지문. AI 리포트 캐시 키에 포함되어, 데이터가 바뀌면 캐시가 자동으로 무효화된다. 옛 결론을 돌려주는 사고 방지의 핵심.
검색
Hybrid Retrieval
키워드 검색(BM25/전문검색)과 벡터 검색(임베딩 유사도)을 함께 돌려 결과를 머지하는 패턴. 정확 매칭과 의미 매칭의 약점을 서로 보완한다.
RAG
Evidence Chunk
RAG에서 답변의 근거가 되는 문서 조각. FinSight는 DocumentChunker로 섹션 메타 + 오버랩으로 분할해 저장하고, 답변마다 어느 청크가 인용됐는지를 trace에 기록한다.
LLM 품질
Hallucination Risk
LLM이 증거에 없는 내용을 마치 사실처럼 만들어내는 위험. FinSight의 RAG 평가는 인용된 증거와 답변 사이 일관성을 점수화해 회귀 추적한다.
Spring 메커니즘
Spring Profile
환경별 빈 설정을 묶는 Spring의 메커니즘. postgres·rabbitmq·redis·prod를 조합해 한 코드베이스가 다양한 실행 모드를 가질 수 있다.
DB 도구
Flyway
SQL 마이그레이션을 V1__init.sql·V2__add_index.sql 같은 버전 파일로 관리하는 도구. 앱 시작 시 자동으로 적용. "DB 콘솔에서 손으로 ALTER" 안티패턴 방지.
테스트
Testcontainers
JUnit 테스트에서 도커 컨테이너(PostgreSQL+pgvector, RabbitMQ 등)를 띄워 실제 인프라로 통합 테스트를 돌리는 라이브러리. FinSight는 smoke test에 활용.
로컬 LLM
Ollama
로컬에서 LLM을 돌릴 수 있게 해주는 CLI/서버. ollama pull qwen2.5:7b로 모델 다운로드 후 REST API(:11434)로 호출. FinSight는 선택적 백엔드로 통합.
운영 가시성
Actuator
Spring Boot가 제공하는 운영 엔드포인트 모음. /actuator/health·/actuator/metrics·/actuator/prometheus로 Liveness·메트릭·Prometheus scrape 출력을 한 줄 설정으로 노출.

11참고 링크

FinSight 본체 · 핵심 기술 공식 문서 · 심화 글

FinSight-AI 본체

핵심 기술 공식 문서

분산 락 · RAG 평가 등 심화 글