기술

RAG 응답이 37초 걸렸는데, 느린 이유가 의도분석이었습니다.

2026.03.298분 읽기

채팅 응답이 37초 걸렸다

지식그래프 기반 RAG 채팅을 만들고 나서, 처음에는 "답변이 나온다"는 것 자체가 만족스러웠습니다. 질문을 넣으면 그래프에서 관련 노드를 찾고, 리랭크하고, LLM이 답변을 생성하는 파이프라인이 돌아가거든요.

그런데 실제로 써보니까 응답까지 37초가 걸리더라고요. 질문을 던지고 37초를 기다리는 건, 사용자 입장에서는 "이거 멈춘 건가?" 싶은 수준이에요. 데모를 보여줄 때도 어색한 침묵이 계속되고요.

"일단 동작하니까 나중에 최적화하자"고 넘겼는데, 미루다가 이제는 더이상 이걸 해결하지 않으면 실사용이 불가능하다는 걸 깨달았습니다.


어디가 느린 건지부터 찍어봤다

최적화의 첫 단계는 "체감"이 아니라 "측정"이었습니다. 파이프라인 각 단계에 타이밍 로그를 넣어서 구간별 시간을 찍어봤거든요.

사용자 질문
  → 1. 의도분석 (LLM)
  → 2. 임베딩 (Gemini)
  → 3. 검색 (Neo4j + Qdrant, 병렬)
  → 4. 리랭크 (BGE 로컬)
  → 5. 답변생성 (LLM, 스트리밍)
  → 응답

로그를 찍고 보니까 병목이 두 군데 보였습니다.

의도분석이 6초. 사용자의 질문이 "지식 검색"인지 "잡담"인지 "후속 질문"인지를 분류하는 단계인데, 6초나 걸리고 있었어요. 분류라는 게 패턴 매칭에 가까운 간단한 작업인데, 왜 6초나 걸리는 거지?

답변생성이 20초. 전체 37초 중 절반 이상이 답변생성이었습니다. 이건 LLM이 thinking(내부 추론)을 하면서 시간을 쓰는 구간이에요.

나머지는 임베딩 0.6초, 검색 0.6초, 리랭크 1.7초(웜 상태)로 상대적으로 가벼웠거든요.

1.jpeg


의도분석에 좋은 모델을 쓸 필요가 없었다

의도분석이 6초 걸리는 원인은 단순했습니다. 답변생성에 쓰는 것과 같은 모델(gemini-2.5-flash)을 의도분석에도 쓰고 있었거든요.

처음에는 "분류도 정확해야 하니까 좋은 모델을 써야 한다"고 생각했습니다. 의도를 잘못 분류하면 검색 자체가 엉뚱한 방향으로 가니까, 여기서 정확도를 아끼면 안 된다는 판단이었어요.

그런데 실제로 의도분석이 하는 일을 뜯어보면, "이 질문이 지식 검색인지, 잡담인지, 후속 질문인지"를 구분하는 거예요. 복잡한 추론이 필요한 게 아니라 패턴 매칭에 가까운 작업이거든요. 이런 작업에 thinking 기능이 있는 고성능 모델을 쓰면, 모델이 불필요하게 "생각"을 하느라 시간을 잡아먹습니다.

gemini-2.0-flash로 바꿨더니 6초 → 1.3초로 떨어졌어요. 정확도는 체감 차이가 없었고요.

이게 이 최적화에서 가장 큰 깨달음이었습니다. 모든 LLM 호출이 같은 모델을 쓸 이유가 없다는 거예요. 용도별로 모델을 분리하면 비용도 줄고 속도도 빨라집니다.

정리하면 이렇거든요.

용도

모델

이유

의도분석

gemini-2.0-flash

패턴 매칭 수준, thinking 불필요

리랭크

gemini-2.0-flash

관련도 점수 매기기, thinking 불필요

답변생성

gemini-2.5-flash

복잡한 종합/추론 필요, thinking 유용

간단한 작업에 가벼운 모델, 복잡한 작업에 좋은 모델. 당연한 것 같지만, 처음에 파이프라인을 만들 때는 "일단 하나의 모델로 전부 돌리자"가 훨씬 자연스럽거든요. 그리고 그 자연스러운 선택이 병목의 원인이었습니다.


답변생성 20초는 thinking budget으로 줄였다

답변생성이 20초 걸리는 건 모델이 느린 게 아니라, 모델이 너무 열심히 생각하고 있어서였습니다.

Gemini 2.5 Flash에는 thinking이라는 기능이 있어요. 답변을 생성하기 전에 내부적으로 추론 과정을 거치는 건데, 이게 기본값으로는 상한 없이 돌아가거든요. 간단한 질문에도 모델이 풀코스로 생각을 하니까 시간이 길어지는 거예요.

여기에 thinking budget이라는 설정이 있습니다. "생각하는 시간의 상한선"을 정해주는 거예요. 모델이 이 범위 안에서 알아서 조절합니다.

핵심은 모든 질문에 같은 budget을 주면 안 된다는 점이었어요. 잡담에는 thinking이 필요 없고, 검색 결과 5건짜리 간단한 질문에는 짧은 thinking이면 충분하고, 16건 이상의 복잡한 질문에만 긴 thinking이 필요하거든요.

질문 복잡도에 따라 동적으로 조절하는 로직을 넣었습니다.

조건

thinking budget

예상 시간

잡담/후속 질문

0

3~4초

검색 결과 5건 이하

512

8~12초

검색 결과 6~15건

1024

12~18초

검색 결과 16건 이상

2048

18~25초

복잡도 판단 기준을 "검색 결과 건수"로 잡은 건, 이게 가장 단순하면서도 실용적이었기 때문이에요. 검색 결과가 많다는 건 LLM이 종합해야 할 정보가 많다는 뜻이니까, 생각 시간도 그에 비례해서 주는 거죠.

이걸 적용하고 나서 답변생성이 20초 → 10~15초로 줄었습니다.

2.jpeg


검색 결과도 줄였다

세 번째 최적화는 검색 파라미터 조정이었습니다. 처음에는 "많이 검색할수록 좋겠지"라는 생각으로 TOP_K를 100으로 잡아뒀거든요.

그런데 실제로는 검색 결과 100건 중 리랭크 후 쓰이는 건 10~15건이었습니다. 나머지 85건은 검색하고, 리랭크하고, 버려지는 거예요. 이 과정에서 리랭크 시간이 늘어나고, LLM에 전달하는 프롬프트 토큰도 불필요하게 커지고 있었습니다.

TOP_K를 100 → 20으로, RERANK_TOP_K를 15 → 10으로, 이메일 본문 최대 길이를 2000자 → 1500자로 줄였습니다.

결과적으로 검색 결과가 300건(소스 3개 × 100건) → 60건(소스 3개 × 20건)으로 줄었고, LLM에 전달하는 프롬프트 토큰도 크게 감소했어요. 답변 품질은 체감 차이가 없었고요. 리랭크가 잘 동작하고 있으면, 상위 20건 안에 관련 문서가 거의 다 들어오거든요.


API 마이그레이션도 같이 했다

이번 최적화를 하면서 Google의 Gemini API도 같이 마이그레이션했습니다. google.generativeai 패키지가 deprecated 되고 google.genai로 바뀌었거든요.

API가 바뀌면서 thinking budget 설정(ThinkingConfig)이 가능해진 게 이번 최적화의 핵심이었어요. 구 API에서는 thinking 자체를 제어할 수 없었으니까, 모델이 무한정 생각하는 걸 막을 방법이 없었습니다.

마이그레이션 자체는 클라이언트 초기화 방식, 생성/스트리밍/임베딩 호출 방식이 바뀌어서 관련 파일 5개를 수정해야 했는데, 패턴이 일관적이라 한번 파악하면 기계적으로 바꿀 수 있었어요.


37초 → 16초, 핵심은 세 가지였다

전체 결과를 정리하면 이렇습니다.

단계

Before

After

개선 방법

의도분석

6초

1.3초

모델 분리 (gemini-2.0-flash)

임베딩

0.6초

0.6초

-

검색

0.6초

0.4초

TOP_K 100→20

리랭크

1.7초

1.7초

-

답변생성

20초

10~15초

thinking budget 동적 제한

전체

37초

16~21초

43~57% 단축

돌이켜보면 세 가지 최적화 모두 "과하게 쓰고 있던 걸 적정 수준으로 줄인 것"이었어요. 의도분석에 과한 모델, 답변생성에 과한 thinking, 검색에 과한 TOP_K. 리소스를 더 투입한 게 아니라 불필요한 부분을 줄인 거예요.

RAG 파이프라인이 느리다면, 모델을 바꾸거나 인프라를 키우기 전에 구간별 시간부터 찍어보는 걸 추천합니다. 병목은 생각과 다른 곳에 있을 수 있거든요.

#RAG#성능최적화#Gemini#ThinkingBudget#LLM#AI전환기