Api::V1::CompassSearchController#search (avg 22993ms, max 27086ms)
RCA: CompassSearchController#search Latency (avg 22993ms, max 27086ms)
Overview#
What Happened#
2026-05-28 02:2202:23 UTC에 20초 이상 소요되어 전체 요청 시간이 비정상적으로 길어졌다.cupixworks-api 서비스의 Api::V1::CompassSearchController#search 엔드포인트에서 평균 22.9초, 최대 27.1초의 응답 지연이 발생했다. Compass DB Search 파이프라인의 Gemini API 호출(text-to-SQL 변환) 단계에서 16
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::CompassSearchController#search |
| top_frame | app/services/cupix/compass/db_search/pipeline.rb:10 |
| env | dev, us-west-2 |
| deploy | dev-us-west-2-20260527T1610Z0-0c4df3bc-cupixworks |
Timeline#
- 2026-05-28T02:22:47Z — 첫 번째 slow trace 감지 (trace_id: 539312712627189295)
- 2026-05-28T02:23:33Z — 두 번째 slow trace 감지 (trace_id: 3292199252165094696)
- 2026-05-28T02:28:53Z — 동일 시간대 Pipeline 완료 로그 (t1_text_to_sql_ms: 16682)
- 2026-05-28T02:31:07Z — 또 다른 Pipeline 완료 로그 (t1_text_to_sql_ms: 20269)
Error Log#
{
"resource_name": "Api::V1::CompassSearchController#search",
"service": "cupixworks-api",
"occurrences": 2,
"avg_ms": 22993,
"max_ms": 27086,
"sample_trace_id": "539312712627189295"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 2
- 최초 발생: 2026-05-28T02:22:47.513Z
- 최근 발생: 2026-05-28T02:23:33.530Z
사용자가 Compass 자연어 검색을 요청할 때 20초 이상 응답을 기다려야 하며, 프론트엔드 타임아웃으로 이어질 수 있다. 현재 발생 빈도는 낮으나 (2건), 해당 시간대 전체 Compass 요청에서 Gemini 응답 지연이 구조적으로 발생하고 있다.
Root Cause Summary#
Compass DB Search 파이프라인에서 Gemini API를 호출하여 자연어를 SQL로 변환하는 단계(t1_text_to_sql)가 16~20초 이상 소요되어 전체 요청 시간이 20초를 초과한다. GeminiOperation.generate_sql은 Cupix::HttpClient.post를 사용하여 Gemini API를 동기 호출하며, HTTP 클라이언트에 read timeout이 설정되어 있지 않아 Gemini API의 응답 지연이 그대로 사용자 요청에 전파된다. 추가로 generate_with_correction 루프에서 validation 실패 시 최대 2회 재시도가 가능하여, worst case에서는 Gemini 호출이 3회까지 누적될 수 있다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/compass_search_controller.rb:10 - Pipeline 실행:
app/services/cupix/compass/db_search/pipeline.rb:10 - Gemini 호출 (text-to-SQL):
app/services/cupix/compass/db_search/gemini_operation.rb:34 - HTTP 호출:
lib/cupix/http_client.rb:34(RestClient.post, no timeout) - SQL validation:
app/services/cupix/compass/db_search/query_validator.rb:19 - Failure point: Gemini API 응답 지연 —
gemini_operation.rb:41에서RestClient.post가 16-20초 동안 blocking
# T1: Text-to-SQL via Gemini (with auto-correction loop)
t1_start = clock_ms
schema_text = Cupix::Compass::DbSearch::SchemaContext.build
gemini_result = generate_with_correction(question: question, schema_text: schema_text, user_id: user_id)
generated_sql = gemini_result[:sql]
filtered_sql = gemini_result[:filtered_sql]
token_usage = gemini_result[:token_usage]
t1_ms = clock_ms - t1_start
def call_gemini_api(payload)
model = ENV.fetch('GEMINI_MODEL_ID', 'gemini-3-flash-preview')
api_key = ENV.fetch('GEMINI_API_KEY') { raise 'GEMINI_API_KEY environment variable is required' }
url = "https://generativelanguage.googleapis.com/v1beta/models/#{model}:generateContent?key=#{api_key}"
retries = 0
begin
response = Cupix::HttpClient.post(url, payload.to_json, { content_type: :json, accept: :json })
JSON.parse(response.body)
rescue RestClient::Exception => e
if RETRYABLE_STATUS_CODES.include?(e.http_code) && retries < MAX_RETRIES
retries += 1
sleep((2**(retries - 1)) + rand(0.0..0.5))
retry
end
# ...
end
end
def self.post(url, payload, headers = {}, retries: MAX_RETRIES)
attempt = 0
begin
RestClient.post(url, payload, headers) # timeout 미설정 — RestClient 기본값 60초
rescue RestClient::Exception => e
if RETRIABLE_STATUS_CODES.include?(e.http_code) && attempt < retries
attempt += 1
sleep((2**(attempt - 1)) + rand(0.0..0.5))
retry
end
raise
end
end
기대 동작: Gemini API가 2-5초 이내에 응답하여 전체 요청이 5초 미만으로 완료. 실제 동작: Gemini API 응답이 16-20초 소요, 전체 요청이 17-27초로 비정상적으로 느림.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-api "Compass DB Search completed"
Time range: 2026-05-28T02:20:00Z to 2026-05-28T02:50:00Z
핵심 로그 (02:28:53 UTC — 인시던트 시간대 내 가장 가까운 완료 로그):
{
"message": "Compass DB Search completed",
"class": "Cupix::Compass::DbSearch::Pipeline",
"function": "run",
"question": "가장 최근 캡처가 언제이고 그에 따르면 1층의 작업 현황은?",
"benchmark": {
"t1_text_to_sql_ms": 16682,
"t3_query_execution_ms": 791,
"t_total_ms": 17474
},
"token_usage": {
"input_tokens": 5346,
"output_tokens": 162,
"total_tokens": 8892
},
"@timestamp": "2026-05-28T02:28:53.914Z"
}
02:31:07 UTC 로그 — 또 다른 요청:
{
"message": "Compass DB Search completed",
"benchmark": {
"t1_text_to_sql_ms": 20269,
"t3_query_execution_ms": 47,
"t_total_ms": 20317
},
"token_usage": {
"input_tokens": 5349,
"output_tokens": 27,
"total_tokens": 9308
},
"@timestamp": "2026-05-28T02:31:07.923Z"
}
동일 시간대 전체 벤치마크 분포 (02:09~02:31 UTC, 19개 샘플):
t1_text_to_sql_ms범위: 2,555ms ~ 20,700mst1_text_to_sql_ms중앙값: ~13,000mst3_query_execution_ms범위: 6ms ~ 791ms (SQL 실행 자체는 빠름)- 20초 초과 요청: 7건/19건 (37%)
SQL 실행 단계(t3)는 최대 791ms로 정상이며, 지연의 97% 이상이 Gemini API 호출 단계(t1)에서 발생.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Gemini API 응답 지연 (모델 추론 시간) | 벤치마크 로그에서 t1_text_to_sql_ms가 16-20초. input_tokens 5,346개의 큰 prompt (DB 스키마 포함). 429/503 에러 로그 없음 → 재시도가 아닌 단일 호출 자체가 느림 |
— | Confirmed |
| H2 | Gemini API 재시도(429/503)로 인한 시간 누적 | GeminiOperation에 retry 로직 존재 |
"Gemini API call failed" 에러 로그 0건. SQL correction attempt 로그 0건. 429/503 에러 증거 없음 |
Rejected |
| H3 | SQL 쿼리 실행 자체가 느림 | — | t3_query_execution_ms 최대 791ms. MAX_EXECUTION_TIME = 10000 설정. 타임아웃 에러 없음 |
Rejected |
| H4 | 네트워크 지연 (us-west-2 → Gemini API) | 가능성은 있으나 16-20초는 네트워크 지연으로 설명 불가 | input_tokens 5,346 + 스키마 크기 고려 시 모델 추론 시간이 주 원인 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/services/cupix/compass/db_search/gemini_operation.rb:41—Cupix::HttpClient.post호출 시 timeout 파라미터 추가 (예: 15초). RestClient는timeout:옵션을 지원하지만 현재Cupix::HttpClient에 전달 경로가 없으므로,GeminiOperation에서 직접RestClient::Request.execute를 사용하거나,HttpClient.post에 timeout 옵션을 추가해야 한다.app/controllers/api/v1/compass_search_controller.rb— 컨트롤러 레벨에서Timeout.timeout(20)같은 safety net 추가를 고려.
단기 개선 (1주 이내)#
- Gemini 모델 변경: 현재
gemini-3-flash-preview를 사용 중이며 프롬프트에 5,300+ 토큰 입력. 스키마를 축약하거나, 더 빠른 모델(GA 버전)로 교체하여 추론 시간을 줄인다. - 스키마 캐싱 최적화:
SchemaContext.build는 이미 메모이제이션되어 있으나, 프롬프트 자체의 토큰 수를 줄이기 위해 스키마 표현을 더 간결하게 변경. Pipeline레벨에서 전체 요청 timeout을 설정하고, timeout 시 사용자에게 적절한 에러 메시지 반환.
장기 개선 (재발 방지)#
- Gemini API 호출을 비동기(Sidekiq worker)로 전환하고, 프론트엔드에서 polling/WebSocket으로 결과를 받는 구조로 변경.
- APM에서
t1_text_to_sql_ms> 10초를 별도 alert으로 설정하여 모델 성능 저하를 조기 감지. - 프롬프트 최적화: context caching (Gemini API가 지원하는 경우) 활용으로 스키마 부분의 반복 처리 제거.
Monitoring#
t1_text_to_sql_msP95/P99 메트릭 추적을 위한 커스텀 메트릭 전송- Datadog APM alert:
avg(last_5m):avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::compasssearchcontroller#search} > 15000000000
- Gemini API 호출 실패/타임아웃 모니터:
service:cupixworks-api "Gemini API" (status:error OR status:warn)
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard — HTTP timeout 추가는 단순하나, 비동기 전환은 아키텍처 변경 필요