ES /docs

Api::V1::IssuesController#index (avg 1615ms, max 2280ms)

RCA: Api::V1::IssuesController#index Latency (avg 1615ms, max 2280ms)

Overview#

What Happened#

2026-05-26 03:28~12:38 UTC 사이에 cupixworks-api 서비스의 Api::V1::IssuesController#index 엔드포인트에서 평균 1615ms, 최대 2280ms의 응답 지연이 5개 리전(us-west-2, ap-southeast-2, eu-central-1, ap-southeast-1, ap-northeast-1)에서 43회 발생했다. 모든 요청은 HTTP 200으로 성공했으나 SLA 기준(500ms)을 초과했다.

Quick Facts#

Field Value
resource_name Api::V1::IssuesController#index
top_frame app/controllers/api/v1/issues_controller.rb:6
runtime Ruby (Rails)
env production (us-west-2, ap-southeast-2, eu-central-1, ap-southeast-1, ap-northeast-1)

Affected Teams#

Team / Domain Avg Latency Impact
crcc-sama 1093ms (max 2280ms) 이슈 목록 로딩 지연으로 UX 저하
shinryo 1899ms 응답 시간 SLA 초과
fgip-pkg1 1394ms 이슈 목록 로딩 지연
endeavourgroup 2217ms 최대 latency 수준

Timeline#

  1. 2026-05-26T03:28:12Z — 최초 감지 (eu-central-1)
  2. 2026-05-26T05:48:57Z — 최대 latency 2280ms 기록 (eu-central-1, crcc-sama)
  3. 2026-05-26T12:38:05Z — 마지막 발생
  4. 2026-05-27 — RCA 분석 완료

Error Log#

Datadog Logs

json
{
  "resource_name": "Api::V1::IssuesController#index",
  "service": "cupixworks-api",
  "occurrences": 20,
  "avg_ms": 1615,
  "max_ms": 2280,
  "sample_trace_id": "2259982292470400049"
}

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 43
  • 최초 발생: 2026-05-26T03:28:12.778Z
  • 최근 발생: 2026-05-26T12:38:05.439Z

Root Cause Summary#

IssuesController#index는 로컬 DB를 조회하지 않고 외부 issue microservice(apig.{host}/v1/issues)로 HTTP GET 요청을 프록시한다. 이 외부 서비스 호출에 timeout 설정이 없으며, 응답 시간이 tenant별 이슈 데이터 볼륨에 비례하여 증가한다. DB 시간은 평균 5ms(전체의 0.3%)에 불과하고, 렌더링은 0.3ms 미만이므로, 전체 latency의 99.7%는 외부 issue service의 응답 대기에서 발생한다. 이슈가 많은 tenant(crcc-sama, shinryo 등)에서 latency가 크게 증가하는 패턴은 issue service가 pagination 없이 대량 데이터를 반환하거나, 서버 측 처리 시간이 데이터 볼륨에 비례함을 시사한다.

Technical Analysis#

Code Path#

  • Entry point: app/controllers/api/v1/issues_controller.rb:4
  • External call: app/services/cupix/issue_service.rb:89
  • HTTP client: lib/cupix/http_client.rb:15
  • Failure point: HTTP 응답 대기 시간 (no timeout configured)
app/controllers/api/v1/issues_controller.rb:4-11ruby
def index
  url = "#{$CUPIX_ISSUE_SERVICE_URL}/issues"
  resp = Cupix::IssueService.list(url, params)

  raise Cupix::Errors::System.new(code: 'SYS20000', reason: 'Temporarly failed to fetch issues') if resp.nil?

  render_json resp.code, JSON.parse(resp.body)['result']
end

컨트롤러는 params 전체를 그대로 외부 서비스에 전달한다. 로컬 DB 조회 없이 완전한 프록시 패턴.

app/services/cupix/issue_service.rb:82-107ruby
def list(url, params)
  unless url.start_with?('http')
    Cupix::Logger.info('Bypass posting issue because ISSUE_SERVICE_URL is not set', class: self.name, function: __method__)
    return
  end

  begin
    resp = Cupix::HttpClient.get(
      url,
      {
        params: params.as_json,
        content_type: :json,
        accept: :json,
        authorization: "Bearer #{_access_token}"
      }
    )
  rescue RestClient::Exception => e
    Cupix::Logger.error("fail to fetch list from issue service. reason: 'RestClient error' message: #{e.response}", class: self.name, function: __method__)
  rescue StandardError => e
    Cupix::Logger.error("fail to fetch list from issue service. reason: 'Standard error' message: #{e.message}", class: self.name, function: __method__)
  else
    Cupix::Logger.info('fetching list from issue service successful.', class: self.name, function: __method__)
  end

  resp
end

params.as_json은 Rails params 전체(controller, action, format 키 포함)를 전달한다. 외부 서비스에 불필요한 파라미터가 전송됨.

lib/cupix/http_client.rb:12-24ruby
def self.get(url, headers = {}, retries: MAX_RETRIES)
  attempt = 0
  begin
    RestClient.get(url, headers)
  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

RestClient.get:timeout 또는 :open_timeout 옵션이 설정되지 않았다. RestClient의 기본 timeout은 무제한(nil)이므로, 외부 서비스가 느리면 Rails worker가 무기한 블로킹된다.

기대 동작: 외부 issue service가 200300ms 내에 응답하여 전체 요청이 500ms 이내에 완료. 실제 동작: issue service가 tenant별 이슈 볼륨에 따라 5002280ms 소요. timeout이 없어 Rails worker가 응답 대기 중 블로킹.

Log Evidence#

Datadog 쿼리:

text
service:cupixworks-api resource_name:"Api::V1::IssuesController#index" @duration:>500ms env:production

주요 로그 (모두 HTTP 200, error/warn 없음):

text
2026-05-26T05:48:57.891Z | duration=2278.5ms | db=5.31ms | view=0.15ms | region=eu-central-1 | team=crcc-sama
2026-05-26T08:03:52.139Z | duration=2216.8ms | db=3.99ms | view=0.12ms | region=ap-southeast-2 | team=endeavourgroup
2026-05-26T04:38:24.033Z | duration=2201.6ms | db=5.35ms | view=0.09ms | region=eu-central-1 | team=crcc-sama
2026-05-26T09:14:44.369Z | duration=2071.1ms | db=5.61ms | view=0.29ms | region=us-west-2 | team=cupix

DB 시간 분석:

text
avg_db_ms: 5.0ms (max 17.0ms)
avg_total_ms: 693.9ms (for all sampled requests)
DB percentage of total: 0.3% (for requests >1000ms)

리전별 분포:

text
ap-southeast-2: 64 requests, avg 523ms, max 1665ms
eu-central-1:   21 requests, avg 882ms, max 2118ms
us-west-2:      14 requests, avg 1106ms, max 1895ms
ap-northeast-1:  1 request,  1899ms

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 외부 issue service 응답 지연 (데이터 볼륨 비례) DB=5ms, view=0.15ms로 99.7%가 app-level 대기. tenant별 latency 차이 존재 (crcc-sama max 2280ms vs 소규모 tenant 300ms). 로그에 Cupix::IssueService#list 호출만 기록됨. Confirmed
H2 로컬 DB 쿼리 병목 (N+1, 인덱스 누락) DB 시간 평균 5ms, max 17ms. 이 컨트롤러는 로컬 DB를 전혀 조회하지 않음 (코드 확인). Rejected
H3 네트워크 지연 (리전 간 호출) eu-central-1 평균 882ms로 ap-southeast-2(523ms)보다 높음 issue service가 모든 리전에서 동일 endpoint(apig.cupix.works) 사용. ap-southeast-2가 가장 많은 요청(64건)인데 가장 낮은 latency. 리전보다 tenant 데이터 볼륨과의 상관관계가 더 강함. Rejected
H4 RestClient timeout 미설정으로 worker 블로킹 http_client.rb:15에 timeout 파라미터 없음. RestClient 기본값은 nil(무제한). 느린 응답 시 worker가 무기한 점유됨. 현재까지 max 2280ms로 catastrophic하지 않으나, timeout 없으면 향후 worker pool 고갈 위험. Confirmed (contributing factor)

Fix Recommendation#

즉시 조치 (Critical)#

  • lib/cupix/http_client.rb:15RestClient.get 호출에 :timeout(read timeout)과 :open_timeout(connection timeout) 파라미터 추가. 권장값: open_timeout: 5, timeout: 10 (초).
  • 이는 외부 서비스 장애 시 Rails worker pool 고갈을 방지하는 안전장치.

단기 개선 (1주 이내)#

  • 외부 issue service 측 pagination 최적화: issue service가 대량 이슈를 가진 tenant에 대해 적절한 pagination을 제공하는지 확인. paramsper_page/page 파라미터가 전달되는지, issue service가 이를 활용하는지 검증 필요.
  • 불필요한 params 제거: params.as_json이 Rails 내부 키(controller, action, format)까지 전달하므로, params.permit(...)으로 필요한 파라미터만 전달하도록 수정.
  • Response caching: 동일 tenant/params 조합에 대해 짧은 TTL(30~60초)의 캐시 적용 고려.

장기 개선 (재발 방지)#

  • Cupix::HttpClient 모듈에 기본 timeout 정책을 글로벌로 적용 (모든 GET/POST/PUT/DELETE에 일관된 timeout 설정).
  • Issue service 성능 프로파일링: 대량 이슈 tenant에서의 쿼리 최적화, DB 인덱스 점검.
  • Circuit breaker 패턴 도입: 외부 서비스가 일정 시간 이상 응답하지 않을 때 빠르게 실패하여 worker 보호.

Monitoring#

  • APM p95/p99 latency alert for Api::V1::IssuesController#index:
text
avg(last_5m):trace.rack.request.duration{service:cupixworks-api, resource_name:Api::V1::IssuesController#index} > 1000
  • Timeout 발생 빈도 모니터링:
text
service:cupixworks-api "fail to fetch list from issue service" status:error
  • Issue service 자체의 응답 시간 메트릭 추가 (현재 issue service 측 모니터링 여부 확인 필요)

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard (timeout 추가는 trivial, issue service 측 최적화는 별도 조사 필요)