ES /docs

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에 cupixworks-api 서비스의 Api::V1::CompassSearchController#search 엔드포인트에서 평균 22.9초, 최대 27.1초의 응답 지연이 발생했다. Compass DB Search 파이프라인의 Gemini API 호출(text-to-SQL 변환) 단계에서 1620초 이상 소요되어 전체 요청 시간이 비정상적으로 길어졌다.

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#

  1. 2026-05-28T02:22:47Z — 첫 번째 slow trace 감지 (trace_id: 539312712627189295)
  2. 2026-05-28T02:23:33Z — 두 번째 slow trace 감지 (trace_id: 3292199252165094696)
  3. 2026-05-28T02:28:53Z — 동일 시간대 Pipeline 완료 로그 (t1_text_to_sql_ms: 16682)
  4. 2026-05-28T02:31:07Z — 또 다른 Pipeline 완료 로그 (t1_text_to_sql_ms: 20269)

Error Log#

Datadog Logs

json
{
  "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_sqlCupix::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
app/services/cupix/compass/db_search/pipeline.rb:14-21ruby
# 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
app/services/cupix/compass/db_search/gemini_operation.rb:34-58ruby
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
lib/cupix/http_client.rb:34-46ruby
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 쿼리:

text
service:cupixworks-api "Compass DB Search completed"
Time range: 2026-05-28T02:20:00Z to 2026-05-28T02:50:00Z

핵심 로그 (02:28:53 UTC — 인시던트 시간대 내 가장 가까운 완료 로그):

json
{
  "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 로그 — 또 다른 요청:

json
{
  "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,700ms
  • t1_text_to_sql_ms 중앙값: ~13,000ms
  • t3_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:41Cupix::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_ms P95/P99 메트릭 추적을 위한 커스텀 메트릭 전송
  • Datadog APM alert:
text
avg(last_5m):avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::compasssearchcontroller#search} > 15000000000
  • Gemini API 호출 실패/타임아웃 모니터:
text
service:cupixworks-api "Gemini API" (status:error OR status:warn)

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard — HTTP timeout 추가는 단순하나, 비동기 전환은 아키텍처 변경 필요