Api::V1::AssetsController#update (avg 14630ms, max 14630ms)
RCA: Api::V1::AssetsController#update (avg 14630ms, max 14630ms)
Overview#
What Happened#
2026-07-01 11:05 KST 경 cupixworks-api의 Api::V1::AssetsController#update 트레이스 1건이 14.6초간 지속된 뒤 HTTP 500으로 종료됐다. 원인은 cupix-tesla-static S3 버킷(리전: us-west-2)에 대한 PUT 요청이 잘못된 리전(us-east-1)으로 서명되어 AuthorizationHeaderMalformed로 거절됐고, Excon/Fog가 여러 차례 재시도하면서 요청 전체가 14초대까지 늘어난 것이다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::AssetsController#update |
| upstream error | Excon::Error::BadRequest (HTTP 400 from S3) |
| S3 error code | AuthorizationHeaderMalformed |
| S3 message | The authorization header is malformed; the region 'us-east-1' is wrong; expecting 'us-west-2' |
| bucket / host | cupix-tesla-static / cupix-tesla-static.s3.amazonaws.com |
| trace_id | 1807238531335885042 |
| avg / max duration | 14630ms / 14630ms |
| env | production, us-west-2, tenant cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api / Assets | 1 | 단일 자산(kucudjj1zg8k)의 PUT 요청이 14.6초 뒤 500으로 실패. 동일 창의 다른 Asset#update 수천 건은 200 응답. |
Timeline#
- 2026-07-01 11:02:16 KST — 직전
svc:cupixworks-api::unknownservice-degraded 인시던트(2026-07-01-svc-cupixworks-api--unknown-1, cluster 5건) 해소. - 2026-07-01 11:05:19 KST — 문제의
Api::V1::AssetsController#update트레이스 시작 (트레이스 ID1807238531335885042). - 2026-07-01 11:05:33 KST — S3가
AuthorizationHeaderMalformed로 응답 (Date: Wed, 01 Jul 2026 02:05:33 GMT). Excon/Fog 재시도 후 최종 실패. - 2026-07-01 11:05:35 KST — Rails가 500 응답. Datadog 로그에
[500] PUT /api/v1/assets/kucudjj1zg8k (Api::V1::AssetsController#update)기록. - 2026-07-01 11:05:37 ~ 11:05:41 KST — 클라이언트가
cover_upload_url→check_cover_uploading으로 폴백. 이후 동일 asset 대한GET /api/v1/assets/kucudjj1zg8k는 정상.
Error Log#
[500] PUT /api/v1/assets/kucudjj1zg8k (Api::V1::AssetsController#update)
Excon::Error::BadRequest
Expected(200) <=> Actual(400 Bad Request)
excon.error.response
:body => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<Error>
<Code>AuthorizationHeaderMalformed</Code>
<Message>The authorization header is malformed;
the region 'us-east-1' is wrong;
expecting 'us-west-2'</Message>
<Region>us-west-2</Region>
<RequestId>ANJADEW33FQVVSKD</RequestId>
</Error>"
:host => "cupix-tesla-static.s3.amazonaws.com"
:method => "PUT"
:path => "/cupix-tesla-static/cupix-tesla-static/uploads/tmp/1782871521-183424823984822-1247-0930/kucudjj1zg8k_thumbnail.jpg"
:port => 443
:status => 400
:status_line => "HTTP/1.1 400 Bad Request\r\n"
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-07-01 11:05:19 KST
- 최근 발생: 2026-07-01 11:05:19 KST
- 사용자 영향: 단일 자산의 썸네일 업로드 실패로 UI에서 저장 실패로 관측. 15초 지연 후 500. 같은 창의 다른 Asset#update 요청은 정상이었으므로 대규모 영향은 아님.
Root Cause Summary#
CarrierWave 글로벌 초기화가 cupix-tesla-static(리전 us-west-2) 버킷을 fog_directory로 사용하면서도 fog_credentials에 region 키를 지정하지 않았다. 결과적으로 Fog가 기본값(us-east-1)으로 SigV4 서명을 만들고 글로벌 엔드포인트 cupix-tesla-static.s3.amazonaws.com으로 PUT을 시도했다. 실제 버킷이 us-west-2에 존재하므로 S3가 AuthorizationHeaderMalformed(HTTP 400)로 거절했고, Excon/Fog의 자동 재시도가 반복되며 요청 전체가 14.6초 지연 후 500으로 종료됐다. CoverUploader, LogoUploader, AvatarUploader 등 개별 uploader는 initialize에서 명시적으로 region을 설정해 이 문제를 피하지만, 이번 요청은 이 보정이 없는 CarrierWave global 경로(캐시 저장, uploads/tmp/...)를 통해 S3로 나갔기 때문에 그대로 리전 불일치가 노출됐다. 요청 경로에 버킷명이 두 번(/cupix-tesla-static/cupix-tesla-static/uploads/tmp/...) 들어간 것도 path_style: true가 글로벌 호스트와 결합했을 때의 Fog 특성이며, 리전 문제와 함께 이 경로 구성이 잘못됐음을 시사한다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/assets_controller.rb:33-37—AssetsController#update→repository_instance.update(params) - Persistence:
app/repositories/asset_repository.rb:14-26—AssetRepository#update는super후set_parameters→@model.save! - Uploader mount:
app/models/concerns/thumbnailable.rb:12—mount_uploader :thumbnail, ThumbnailUploader - CarrierWave global config (문제 지점):
config/initializers/carrierwave.rb:2-10—fog_credentials에region없음 - Uploader init:
app/uploaders/thumbnail_uploader.rb:13-23—self.fog_credentials.merge!(model.fog_credentials)로 model 값과 병합 - Failure point:
PUT https://cupix-tesla-static.s3.amazonaws.com/...→ S3가AuthorizationHeaderMalformed로 400 반환 (Fog 재시도 후 500)
Rails.application.config.to_prepare do
CarrierWave.configure do |config|
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'] || Rails.application.credentials[Rails.env.to_sym][:aws][:access_key_id],
aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] || Rails.application.credentials[Rails.env.to_sym][:aws][:secret_access_key],
path_style: true
# ← region 지정이 없음. Fog는 us-east-1로 폴백함.
}
config.fog_directory = $AWS[:s3][:static_bucket_name] # 'cupix-tesla-static' — us-west-2 소재
end
end
static_bucket_name: 'cupix-tesla-static'
static_bucket_region: 'us-west-2'
def initialize(*)
super
self.fog_credentials['region'] = $AWS[:s3][:default_bucket_region]
self.asset_host = "https://#{$AWS[:cloudfront][:default_hostname]}"
self.fog_attributes = {
cache_control: 'max-age=315576000'
}
end
def initialize(*)
super
self.asset_host = model.asset_host
self.fog_credentials.merge!(model.fog_credentials)
self.fog_directory = model.fog_directory
self.fog_attributes = {
cache_control: 'max-age=315576000'
}
end
기대 동작: ThumbnailUploader#initialize가 model.fog_credentials(= thumbnail_settings[:fog_credentials])로 base config를 병합하므로 리전이 static_bucket_region (=us-west-2)으로 덮여야 한다.
실제 동작: 문제의 요청 경로는 모델에 mount된 uploader의 store가 아니라 CarrierWave의 캐시(cache) 단계 (uploads/tmp/<cache_id>/<filename> 패턴)에서 S3로 PUT을 시도했다. 캐시 저장은 initializer의 global fog_credentials만 사용하며, region merge를 못 받으므로 us-east-1 서명이 그대로 나갔다. 도메인이 가상 호스팅 스타일(cupix-tesla-static.s3.amazonaws.com)로 잡히면서 path_style: true가 함께 적용돼 경로에 버킷명이 중복(/cupix-tesla-static/cupix-tesla-static/...) 출력된 것도 이 캐시 코드경로가 개별 uploader의 endpoint/asset_host 세팅을 우회했음을 뒷받침한다.
Uncertainty — 정확히 어느 시점(직접 파일 업로드? move_to_store 전 캐시?)에서 캐시 PUT이 발생했는지는 트레이스에 span 상세가 남지 않아 최종 확정하지 못했다 (@dd.trace_id:1807238531335885042 검색 시 로그 0건). 다만 실패 URL의 uploads/tmp/<CarrierWave CACHE_ID> 패턴은 CarrierWave Storage::Fog#cache!의 기본 경로와 일치한다.
Log Evidence#
Datadog 쿼리 (실제로 사용한 쿼리):
service:cupixworks-api "AssetsController" "update"
시간창: 2026-07-01T01:50:00Z ~ 2026-07-01T02:15:00Z
service:cupixworks-api "kucudjj1zg8k"
시간창: 2026-07-01T01:50:00Z ~ 2026-07-01T02:15:00Z
핵심 로그 (해당 asset의 시간 순서):
2026-07-01 11:05:17 KST [200] GET /api/v1/assets/kucudjj1zg8k (show)
2026-07-01 11:05:17 KST [302] GET /api/v1/assets/kucudjj1zg8k/download
2026-07-01 11:05:35 KST [500] PUT /api/v1/assets/kucudjj1zg8k ← 문제 발생 (14.6s 트레이스)
2026-07-01 11:05:37 KST [200] POST /api/v1/assets/kucudjj1zg8k/cover_upload_url
2026-07-01 11:05:41 KST [200] PUT /api/v1/assets/kucudjj1zg8k/check_cover_uploading
500 응답의 error 페이로드 (원문 발췌):
{
"class": "Excon::Error::BadRequest",
"message": "Expected(200) <=> Actual(400 Bad Request)",
"body": "<Error><Code>AuthorizationHeaderMalformed</Code><Message>The authorization header is malformed; the region 'us-east-1' is wrong; expecting 'us-west-2'</Message><Region>us-west-2</Region><RequestId>ANJADEW33FQVVSKD</RequestId></Error>",
"host": "cupix-tesla-static.s3.amazonaws.com",
"method": "PUT",
"path": "/cupix-tesla-static/cupix-tesla-static/uploads/tmp/1782871521-183424823984822-1247-0930/kucudjj1zg8k_thumbnail.jpg",
"status": 400
}
동일 시간창의 대조 로그 — 다른 cupix-tesla-static 호출은 올바른 리전 엔드포인트(s3.us-west-2.amazonaws.com) 사용:
{
"timestamp": "2026-07-01 11:31:17 KST",
"message": "[400] PUT /api/v1/captures/724249 (Api::V1::CapturesController#update)",
"host": "s3.us-west-2.amazonaws.com",
"path": "/cupix-tesla-static/8ebe4fde3b207eaa/thumbnail/captures/724249/weoneil.724249_1418035_pointcloud_thumbnail_1.jpg",
"status": 503,
"s3_code": "SlowDown"
}
같은 버킷에 대한 정상 요청이 s3.us-west-2.amazonaws.com (path-style, region 명시)로 나가는데 비해, 실패한 요청은 cupix-tesla-static.s3.amazonaws.com (virtual-hosted, region 미명시)로 나갔다. 두 경로의 Fog 설정이 다르다는 것을 실측 로그로 확인.
Status board 조회 결과:
{
"scope": "svc:cupixworks-api::unknown",
"active": null,
"recent_incident_id": "2026-07-01-svc-cupixworks-api--unknown-1",
"resolved_at": "2026-07-01T02:02:16.858Z"
}
문제 트레이스는 직전 서비스 저하 인시던트 해소(11:02:16 KST) 이후 약 3분 뒤에 발생. 별개 이슈로 판단.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | CarrierWave global fog_credentials에 region 미지정 → Fog가 us-east-1로 서명 → us-west-2 버킷이 AuthorizationHeaderMalformed로 거절, Excon/Fog 재시도로 14.6s 소요 후 500 |
S3 응답 원문 region 'us-east-1' is wrong; expecting 'us-west-2' (500 로그). config/initializers/carrierwave.rb:3-8에 region 없음. aws.yaml:396-397 static_bucket_region: us-west-2. 실패 URL host cupix-tesla-static.s3.amazonaws.com (region 미명시 글로벌 엔드포인트). 다른 정상 호출은 s3.us-west-2.amazonaws.com 사용. |
— | Confirmed |
| H2 | 최근 배포에서 리전 설정을 잘못 변경 | 배포 SHA 미확인. git log로 확인한 카리어웨이브 initializer 히스토리 미조사. 단, 실패는 1건이고 동일 시간창에서 다른 Asset#update 수천 건이 성공 → 광범위 배포 회귀는 아님. |
동일 창의 성공 트래픽 대량 존재. 이전 14일 내 AuthorizationHeaderMalformed 로그 검색 시 결과 0건이지만, 이는 로그 인덱싱 시간차일 가능성도 있음. |
Rejected (증거 부족, 광범위 회귀 아님) |
| H3 | S3 us-west-2 리전 자체의 일시적 장애 | svc:cupixworks-api::unknown 최근 인시던트가 11:02:16 KST에 해소된 직후 발생 |
S3는 명확한 400을 즉시 응답 (2xx도 있어야 할 시점의 5xx가 아님). 리전 코드 문제 메시지는 클라이언트 서명 오류이지 서버측 장애가 아님. 동시각 다른 us-west-2 요청은 성공. | Rejected |
| H4 | 애플리케이션 재시도(Sidekiq re-enqueue 등)가 14s를 만든 원인 | 트레이스 시작 02:05:19 → S3 응답 헤더 Date 02:05:33 → 500 로그 02:05:35 (16s span) | 이는 Excon/Fog retry_limit(기본 4)와 retry_interval의 지수 백오프에 부합. 애플리케이션 레벨 재시도 로그 없음. |
Rejected (Fog 내부 재시도로 충분히 설명됨) |
| H5 | 요청 경로에 버킷명이 중복(/cupix-tesla-static/cupix-tesla-static/uploads/tmp/...) — 별도의 경로 조합 버그 |
실패 URL path에 실제로 중복 확인. | 중복 경로 자체는 서명 오류(us-east-1 vs us-west-2)의 원인은 아니며, path_style: true + virtual-hosted host 결합의 부산물일 수 있음. Region이 올바르면 S3는 405/404를 낼 가능성. 개별 문제이나 이번 500의 root cause는 아님. |
Inconclusive (동반 이슈로 추적 필요) |
Fix Recommendation#
즉시 조치 (Critical)#
config/initializers/carrierwave.rb:3-8—fog_credentials에region: $AWS[:s3][:static_bucket_region]을 추가.fog_directory가static_bucket_name이므로 대응하는static_bucket_region을 기본으로 두는 것이 자연스럽다. 이렇게 하면 개별 uploader가 region을 덮어씌우지 않는 코드 경로(캐시, CarrierWave 내부 store 이동 등)에서도 올바른 SigV4 서명을 사용한다.- 이미 개별 uploader들이
initialize에서 region을 덮고 있어 회귀 위험은 낮다. 그러나 최소한 QA 환경에서 배포 전 검증 필요.
단기 개선 (1주 이내)#
- 실패 URL의
/cupix-tesla-static/cupix-tesla-static/uploads/tmp/...이중 버킷명 경로가 진짜로 나가는지 로그 재현 (Fogpath_style+ endpoint 조합 검증). 필요시 CarrierWave initializer에서endpoint를 명시적으로https://s3.<region>.amazonaws.com으로 지정해 virtual-hosted/global 혼용을 제거. - Fog/Excon 재시도로 인해 클라이언트 관점에서 14초 지연이 발생.
connection_options: { retry_limit: 1 }정도로 낮춰 리전/권한 오류처럼 재시도 무의미한 4xx를 빠르게 실패시키고 사용자 대기시간을 줄이는 것을 검토. AuthorizationHeaderMalformed발생 시 즉시 알림이 갈 수 있도록 로그에 명시적 태그(s3_error_code:AuthorizationHeaderMalformed)로 추출해 Datadog monitor에 태우기.
장기 개선 (재발 방지)#
- CarrierWave/Fog 설정을 한 곳(예:
Cupix::StorageService)에서 중앙 관리하고 uploader마다region을 개별 세팅하는 관행을 제거. 리전이 항상 base config에서 결정되도록 하면 이번 같은 "누락된 세팅" 회귀가 재발하지 않는다. - 배포 시 S3 접근을 대상으로 하는 헬스체크(작은 오브젝트 PUT/DELETE)를 추가해 리전 오설정을 즉시 감지.
Monitoring#
Datadog timeseries widget에 넣을 쿼리 (writing-datadog-monitoring-queries 규칙 준수 — pipe/stats 등 monitor-only 문법 금지):
sum:trace.rack.request.errors{service:cupixworks-api,resource_name:api::v1::assetscontroller#update}.as_count()
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::assetscontroller#update}
로그 기반 카운트 (facet 기반, 이미 표시되는 로그가 있음):
service:cupixworks-api "AuthorizationHeaderMalformed"
service:cupixworks-api "cupix-tesla-static" status:error
권장 알림:
Api::V1::AssetsController#update의 p95 duration이 5s를 넘거나 5xx율이 1%를 초과하면 알림.- 로그에
AuthorizationHeaderMalformed가 5분에 1건 이상이면 알림 (실질적으로 0건이 정상).
Risk Assessment#
- Risk level: low (수정 범위는 initializer 1개 파일, 개별 uploader의 명시적 region 세팅을 그대로 상속받는 형태라 회귀 위험 최소)
- 예상 복잡도: trivial (한 줄 추가 + QA 배포 검증)