📌 Description
로컬 Ollama(GPU 1개)는 동시 생성 요청을 병렬이 아닌 순차 처리한다 — 실측 결과 단독 요청 11.4초
대비, 동시 요청 3개가 11.2초 / 22.5초 / 33.6초로 계단식 증가한다(Ollama 자체 로그로 검증,
decode 속도는 18토큰/초로 3건 모두 동일 — 처리 자체가 느려진 게 아니라 순서 대기 때문임을 확인).
지금은 혼잡 감지 기준이 read-timeout(27초)뿐이라, 밀린 요청은 27초를 다 기다려야 실패로
인식되고 fallback으로 전환된다. 그런데 프론트는 29초에 먼저 포기하기 때문에, 완성된 fallback을
사용자가 아예 못 받는 경우가 생긴다. 그 시간 동안 Tomcat 스레드도 계속 점유된다.
대안 검토 — 세마포어(JVM Semaphore) 게이트 기각: 짧게(3초) 대기 후 실패시켜 검색 1등 문서
원문 발췌로 fallback하는 방식을 검토·프로토타입까지 했으나 기각했다. 이 fallback은 LLM을
아예 거치지 않아서, 혼잡 시 사용자가 "AI가 답한 것"이 아니라 "그냥 검색 결과 원문"만 받게 되어
이 프로젝트의 핵심 가치(권한 필터 적용된 벡터 검색 + RAG)가 조용히 검색 기능으로 격하된다.
"빠르게 실패시키는 것"보다 "실패 자체를 없애는 것"이 이 프로젝트 정체성에 더 맞는다고 판단해
비동기 처리로 방향을 전환한다.
🎯 설계 방식 — 비동기 Job 큐 (검색·백엔드·프론트 전체)
흐름
POST /search → 벡터 검색(빠름, 1~2초)만 동기 수행 → 검색 결과 + queryId +
status:PROCESSING을 즉시 반환 (AI 답변은 아직 없음)
RagResponse를 PROCESSING 상태로 즉시 저장, Job 큐에 등록
- 신규 Worker(경량 폴링, 단일 스레드)가
PROCESSING 건을 순서대로 꺼내 OllamaClient.generate()
호출 — Worker가 1개라 자연히 직렬화되므로 별도 세마포어 불필요
- 완료되면
RagResponse를 SUCCESS/FAILED로 갱신 + 해당 유저 전용 WebSocket 채널로 push
- 프론트: 검색 결과(문서 목록)는 1단계 응답으로 즉시 렌더링, AI 답변 카드는 로딩
스켈레톤으로 표시 → WebSocket 알림 수신 시 텍스트로 전환(2단계 UI)
백엔드 변경
RagResponse.answerText NOT NULL 제약 완화 (PROCESSING 시점엔 아직 답이 없음) — 신규 Flyway 마이그레이션
RagResponseCommandService.createPending() 추가
SearchController 응답 계약 변경 — 검색 결과와 AI 답변을 분리해서 반환
- 신규 RAG Worker —
embedding_jobs용 Worker(heartbeat/lease 등 26개 파일 규모)는 과함,
단일 인스턴스·단일 GPU 기준이라 @Scheduled 폴링 + 순차 처리 정도의 경량 버전으로 축소
WebSocketConfig에 유저별 개인 채널(/user/queue/...) 추가 — 지금 있는 /topic/dashboard는
전체 브로드캐스트(관리자용)라 이 목적에 안 맞음. 구독 인가 로직 신규(자기 queryId인지 검증)
ollama.generate-deadline(25초→예: 60초), ollama.server.read-timeout(27초→그보다 크게)
재튜닝 — 지금은 "프론트 29초 제한 전에 끝내야 함"이 기준이었는데, 비동기 전환 후엔 그 압박이
없어짐. 대신 "Worker가 멈춘 요청 하나 때문에 큐 전체가 막히지 않도록" 하는 안전장치로 역할이
바뀌므로, 무한정 늘리지 않고 적정선으로 재설정
프론트 변경
SearchPage.tsx: searching(검색 단계) / answering(AI답변 단계) 상태 분리
- 검색 결과(
source-list)는 1차 응답 즉시 렌더링
answer-card 자리에 로딩 스켈레톤 → WebSocket 수신 시 텍스트로 자연스럽게 전환(fade-in)
- WebSocket 구독/구독해제 로직 추가 (페이지 이탈 시 정리 포함)
✅ To-do
✅ 완료 기준
📒 기타
- 배경 실측 데이터:
docs/design/kangcheolung-#210-ollama-rag-timeout-fix.md
- 상세 설계 문서는 구현 완료 후 별도 작성 예정
📌 Description
로컬 Ollama(GPU 1개)는 동시 생성 요청을 병렬이 아닌 순차 처리한다 — 실측 결과 단독 요청 11.4초
대비, 동시 요청 3개가 11.2초 / 22.5초 / 33.6초로 계단식 증가한다(Ollama 자체 로그로 검증,
decode 속도는 18토큰/초로 3건 모두 동일 — 처리 자체가 느려진 게 아니라 순서 대기 때문임을 확인).
지금은 혼잡 감지 기준이
read-timeout(27초)뿐이라, 밀린 요청은 27초를 다 기다려야 실패로인식되고 fallback으로 전환된다. 그런데 프론트는 29초에 먼저 포기하기 때문에, 완성된 fallback을
사용자가 아예 못 받는 경우가 생긴다. 그 시간 동안 Tomcat 스레드도 계속 점유된다.
대안 검토 — 세마포어(JVM Semaphore) 게이트 기각: 짧게(3초) 대기 후 실패시켜 검색 1등 문서
원문 발췌로 fallback하는 방식을 검토·프로토타입까지 했으나 기각했다. 이 fallback은 LLM을
아예 거치지 않아서, 혼잡 시 사용자가 "AI가 답한 것"이 아니라 "그냥 검색 결과 원문"만 받게 되어
이 프로젝트의 핵심 가치(권한 필터 적용된 벡터 검색 + RAG)가 조용히 검색 기능으로 격하된다.
"빠르게 실패시키는 것"보다 "실패 자체를 없애는 것"이 이 프로젝트 정체성에 더 맞는다고 판단해
비동기 처리로 방향을 전환한다.
🎯 설계 방식 — 비동기 Job 큐 (검색·백엔드·프론트 전체)
흐름
POST /search→ 벡터 검색(빠름, 1~2초)만 동기 수행 → 검색 결과 +queryId+status:PROCESSING을 즉시 반환 (AI 답변은 아직 없음)RagResponse를PROCESSING상태로 즉시 저장, Job 큐에 등록PROCESSING건을 순서대로 꺼내OllamaClient.generate()호출 — Worker가 1개라 자연히 직렬화되므로 별도 세마포어 불필요
RagResponse를SUCCESS/FAILED로 갱신 + 해당 유저 전용 WebSocket 채널로 push스켈레톤으로 표시 → WebSocket 알림 수신 시 텍스트로 전환(2단계 UI)
백엔드 변경
RagResponse.answerTextNOT NULL 제약 완화 (PROCESSING 시점엔 아직 답이 없음) — 신규 Flyway 마이그레이션RagResponseCommandService.createPending()추가SearchController응답 계약 변경 — 검색 결과와 AI 답변을 분리해서 반환embedding_jobs용 Worker(heartbeat/lease 등 26개 파일 규모)는 과함,단일 인스턴스·단일 GPU 기준이라
@Scheduled폴링 + 순차 처리 정도의 경량 버전으로 축소WebSocketConfig에 유저별 개인 채널(/user/queue/...) 추가 — 지금 있는/topic/dashboard는전체 브로드캐스트(관리자용)라 이 목적에 안 맞음. 구독 인가 로직 신규(자기
queryId인지 검증)ollama.generate-deadline(25초→예: 60초),ollama.server.read-timeout(27초→그보다 크게)재튜닝 — 지금은 "프론트 29초 제한 전에 끝내야 함"이 기준이었는데, 비동기 전환 후엔 그 압박이
없어짐. 대신 "Worker가 멈춘 요청 하나 때문에 큐 전체가 막히지 않도록" 하는 안전장치로 역할이
바뀌므로, 무한정 늘리지 않고 적정선으로 재설정
프론트 변경
SearchPage.tsx:searching(검색 단계) /answering(AI답변 단계) 상태 분리source-list)는 1차 응답 즉시 렌더링answer-card자리에 로딩 스켈레톤 → WebSocket 수신 시 텍스트로 자연스럽게 전환(fade-in)✅ To-do
RagResponse.answerTextnullable 마이그레이션RagResponseCommandService.createPending()추가SearchController응답 계약 변경(검색 결과 + queryId + status 즉시 반환)generate-deadline/read-timeout재튜닝WebSocketConfig: user-destination-prefix 추가, per-query 구독 인가 인터셉터 신규searching/answering상태 분리, 답변 스켈레톤 UI✅ 완료 기준
📒 기타
docs/design/kangcheolung-#210-ollama-rag-timeout-fix.md