failed to get element records - error: 504 Gateway Timeout
RCA: failed to get element records - error: 504 Gateway Timeout
Overview#
What Happened#
2026-04-26 04:53 UTC에 cupixworks-api의 Cupix::SiteinsightsService#get_element_records! 메서드에서 downstream Siteinsights API Gateway로의 HTTP GET 요청이 504 Gateway Timeout으로 실패했다. 요청은 facility umphr6에 대해 300개 element record를 5개 category 필터와 함께 조회하는 것이었으며, 약 29초간 대기 후 타임아웃되었다. 단건 발생으로, 시스템 전반적인 장애는 아니었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | RestClient::Exception |
| exception.message | 504 Gateway Timeout |
| top_frame | app/services/cupix/siteinsights_service.rb:118 |
| env | production, us-west-2 |
| error_code | SYS20000 |
| request_duration | 29,098 ms |
| http_status_returned | 502 (to client) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| pclconstruction (team 739) | 1 | 사용자 Maggie Qu(mqu@pcl.com)의 element record 조회 요청 실패, 502 응답 반환 |
Timeline#
- 2026-04-26T04:53:25Z (추정) —
cupix-agentuser-agent로부터GET /api/v1/siteinsights/element_records요청 수신 - 2026-04-26T04:53:54.282Z — downstream Siteinsights API Gateway에서 504 Gateway Timeout 응답,
Cupix::SiteinsightsService에서 에러 로깅 - 2026-04-26T04:53:54.425Z — 요청 로그 기록: 총 29,098ms 소요, HTTP 502로 클라이언트에 응답
- 2026-04-26T04:53:54Z — Error Sweeper에 의해 감지
Error Log#
failed to get element records - error: 504 Gateway Timeout
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-04-26T04:53:54.282Z
- 최근 발생: 2026-04-26T04:53:54.282Z
단건 발생이며 동일 시간대에 다른 504 에러나 SiteinsightsService 에러는 확인되지 않았다. 7일간 동일 에러 재발 없음.
Root Cause Summary#
downstream Siteinsights 마이크로서비스(AWS API Gateway + Lambda)가 cupixworks-api의 element record 조회 요청을 처리하는 데 API Gateway의 timeout 한도(기본 29초)를 초과하여 504 Gateway Timeout을 반환했다. cupixworks-api의 Cupix::SiteinsightsService#get_element_records! 메서드는 RestClient.get으로 해당 API Gateway를 호출하는데, 명시적인 timeout 설정 없이 기본값(60초)을 사용하고 있다. API Gateway 측에서 먼저 29초 timeout이 발동하여 504를 반환했고, RestClient가 이를 예외로 처리하여 SYS20000 에러로 래핑한 후 클라이언트에 502를 전달했다. 요청 파라미터(per_page: 300, 5개 category_ids, level_ids, completed_until)의 조합이 downstream Lambda 함수의 처리 시간을 초과시킨 것으로 판단된다.
Technical Analysis#
Code Path#
1. Entry point — Controller
Api::V1::ElementRecordsController#index에서 요청을 수신하고 SiteinsightsService를 호출한다.
def index
si_query_option = Cupix::QueryOption::Siteinsights.new(get_query_option, params)
element_records = Cupix::SiteinsightsService.get_element_records!(si_query_option,
fields: @fields, current_user: @current_user, current_team: @current_team)
render_json 200, element_records
end
Controller는 Cupix::Errors::System 예외를 redirect_to_502_error로 처리하여 클라이언트에 502를 반환한다.
rescue_from Cupix::Errors::System, with: :redirect_to_502_error
# ...
def redirect_to_502_error(exception)
raise_error(502, exception, code: 'BG10005', type: Cupix::Errors::BadGateway,
reason: 'BadGateway', message: exception.message)
end
2. Service layer — HTTP 요청 및 실패 지점
get_element_records! 메서드에서 query parameter를 구성하고 RestClient.get으로 downstream API Gateway를 호출한다.
def get_element_records!(query_option, fields: '', current_user: nil, current_team: nil)
raise Cupix::Errors::Parameter.new(code: 'ARG10000', reason: 'facility_key is required') if query_option.facility_key.blank?
begin
params = {
facility_key: query_option.facility_key
}
params[:vendor_ids] = query_option.vendor_ids.join(',') if query_option.vendor_ids.present?
params[:category_ids] = query_option.category_ids.join(',') if query_option.category_ids.present?
params[:phase_ids] = query_option.phase_ids.join(',') if query_option.phase_ids.present?
params[:workarea_ids] = query_option.workarea_ids.join(',') if query_option.workarea_ids.present?
params[:level_ids] = query_option.level_ids.join(',') if query_option.level_ids.present?
params[:versioned_at] = query_option.versioned_at.to_i if query_option.versioned_at.present?
params[:per_page] = query_option.per_page if query_option.per_page.present?
params[:page] = query_option.page if query_option.page.present?
params[:completed_until] = query_option.completed_until if query_option.completed_until.present?
params[:fields] = fields if fields.present?
response = RestClient.get("#{Cupix::Siteinsights.service_url}/element_records?#{params.to_param}")
JSON.parse(response.body)['result']
rescue RestClient::Exception => e
Cupix::Logger.error("failed to get element records - error: #{e.message}",
class: self.name, function: __method__, facility_key: query_option.facility_key, params: params)
raise Cupix::Errors::System.new(code: 'SYS20000',
reason: "failed to get element records - error: #{e.message}")
end
end
- Failure point:
siteinsights_service.rb:118—RestClient.get호출에서 downstream API Gateway가 504를 반환하고, line 121에서RestClient::Exception으로 catch됨 RestClient.get에 명시적인 timeout 파라미터가 설정되어 있지 않음 (기본 60초)
3. Downstream service URL 결정
def service_url
case Cupix::Tesla.tenant
when 'cupix'
case Rails.env
when 'production'
case Cupix::Tesla.region_code
# ...
else
'https://6hdi0xzqqk.execute-api.us-west-2.amazonaws.com/api'
end
end
end
end
us-west-2 production 환경에서는 https://6hdi0xzqqk.execute-api.us-west-2.amazonaws.com/api/element_records로 요청이 전송된다. 이는 AWS API Gateway → Lambda 구성이며, API Gateway의 기본 integration timeout은 29초이다.
Log Evidence#
에러 로그 검색에 사용한 Datadog 쿼리:
service:cupixworks-api status:error "failed to get element records"
에러 로그 원문:
{
"message": "failed to get element records - error: 504 Gateway Timeout",
"class": "Cupix::SiteinsightsService",
"function": "get_element_records!",
"facility_key": "umphr6",
"params": "per_page: 300, page: 1, level_ids: \"45724\", category_ids: \"1021209,1021211,1024063,1024064,1024136\", facility_key: \"umphr6\", completed_until: \"2026-04-23T14:56:48.000Z\"",
"host": "ip-10-1-144-228.us-west-2.compute.internal",
"pid": 2614224,
"request_id": "e6e333ee-aa30-4e95-8c72-8e14246a9a20"
}
요청 로그 (info level):
service:cupixworks-api status:info "element_records"
{
"message": "[502] GET /api/v1/siteinsights/element_records (Api::V1::ElementRecordsController#index)",
"status_code": 502,
"duration": 29098.09,
"db_time": 3.55,
"view_time": 0.12,
"error_code": "SYS20000",
"error_class": "Cupix::Errors::System",
"error_reason": "failed to get element records - error: 504 Gateway Timeout",
"remote_ip": "44.228.8.68",
"user_agent": "cupix-agent",
"user_email": "mqu@pcl.com",
"team_name": "pclconstruction"
}
요청 총 소요 시간은 29,098ms로, DB 시간(3.55ms)과 View 시간(0.12ms)을 제외하면 거의 전부가 downstream Siteinsights API Gateway 대기 시간이다. 이는 AWS API Gateway의 기본 integration timeout인 29초와 정확히 일치한다.
7일간 동일 에러 검색 결과 추가 발생 없음:
service:cupixworks-api status:error "Cupix::SiteinsightsService" @_duration:>7d
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | downstream Siteinsights Lambda가 대용량 쿼리(300 per_page, 5 category, 17 fields) 처리 시 API Gateway의 29초 integration timeout 초과 | 요청 duration 29,098ms가 API Gateway 기본 timeout 29초와 정확히 일치. DB time 3.55ms로 cupixworks-api 자체에는 문제 없음. 요청 파라미터에 다수 필터 + 300건 페이징 포함 | 단건 발생으로 항상 발생하는 문제는 아님 — 데이터 크기나 Lambda cold start 등 일시적 요인 가능 | Confirmed |
| H2 | cupixworks-api의 RestClient timeout 설정 문제 | RestClient.get에 명시적 timeout 미설정 (기본 60초) | 504는 downstream에서 반환한 것으로, RestClient timeout(60초)이 아닌 API Gateway timeout(29초)이 먼저 발동. RestClient timeout은 직접적 원인이 아님 | Rejected |
| H3 | Siteinsights API Gateway/Lambda 전체 장애 | 에러 발생 시점에 504 반환 | 동일 시간대 다른 서비스에서 504 미발생, 7일간 재발 없음, 동일 시간대 SiteinsightsService 추가 에러 없음 — 전체 장애 아닌 단건 | Rejected |
| H4 | Lambda cold start로 인한 처리 지연 | Lambda가 호출 빈도가 낮은 시간대(04:53 UTC)에 cold start 발생 가능 | 증거 부족 — downstream Lambda 로그 미확인 (Datadog에 Siteinsights Lambda 로그 없음) | Inconclusive |
Fix Recommendation#
즉시 조치 (Critical)#
이번 에러는 단건 발생이며 시스템 전반에 영향을 주지 않았으므로, 즉각적인 핫픽스는 불필요하다.
단기 개선 (1주 이내)#
-
RestClient timeout 명시 설정 (
app/services/cupix/siteinsights_service.rb:118):RestClient.get호출에open_timeout과read_timeout을 명시적으로 설정하여, API Gateway timeout(29초)보다 약간 긴 값(예: 35초)으로 지정. 이를 통해 timeout 동작을 예측 가능하게 만들고, 기본 60초 대기를 방지. -
에러 로그에 요청 URL 포함: 현재 에러 로그에는
params만 포함되어 있고, 실제 호출된 URL은 로깅되지 않는다. 디버깅 편의를 위해 downstream URL도 함께 로깅하면 좋다.
장기 개선 (재발 방지)#
-
Downstream Siteinsights Lambda 성능 모니터링: Lambda 함수의 execution duration, cold start 빈도, timeout 발생률을 CloudWatch 또는 Datadog에서 모니터링. 특히
per_page: 300+ 다수 필터 조합의 쿼리 성능을 확인. -
대용량 쿼리에 대한 pagination 최적화:
per_page: 300은 상대적으로 큰 페이지 크기이다. downstream Lambda가 이를 처리하기 어려운 경우, 클라이언트 측에서 더 작은 페이지로 분할 요청하거나, downstream에서 streaming/pagination을 최적화하는 방안 검토. -
Retry 로직 추가 검토: 현재
get_element_records!에는 retry 로직이 없다. 504와 같은 일시적 오류에 대해 1회 정도 retry를 추가하면 cold start나 일시적 지연으로 인한 실패를 줄일 수 있다. 단, retry 시 idempotency를 보장해야 한다 (GET 요청이므로 안전).
Monitoring#
추가 권장 모니터링:
service:cupixworks-api status:error "failed to get element records"
- 위 쿼리로 Datadog Monitor를 설정하여, element record 조회 실패가 특정 threshold(예: 5분간 3건 이상) 초과 시 알림
Cupix::SiteinsightsService관련 에러 발생률 추이 대시보드 추가
service:cupixworks-api @http.url:"/api/v1/siteinsights/element_records" @duration:>20000
- 20초 이상 소요되는 element record 요청을 모니터링하여 timeout 위험을 사전에 감지
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial