Net::OpenTimeout: Net::OpenTimeout
RCA: Net::OpenTimeout (outbound AWS connect timeout)
Overview#
What Happened#
cupixworks-api (tesla)의 Record#flush_geo_coordinate 가 geo-coordinate JSON 을 S3 에 업로드할 때, AWS SDK 내부의 net/http 커넥트 단계에서 대상 엔드포인트(s3.me-south-1.amazonaws.com:443) 로의 TCP 연결이 timeout 되어 Net::OpenTimeout 이 발생했다. Datadog Error Tracking 은 최하위 프레임이 net/http 이므로 이를 service: net/http, Net::OpenTimeout 이슈로 묶는다. 이 예외는 애플리케이션 코드에서 이미 rescue StandardError 로 처리되어 lock 해제 후 error 로그를 남기고 정상적으로 종료된다. 15.5개월간 누적 60건으로 발생 빈도가 매우 낮은 간헐적 외부 연결 timeout 이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Net::OpenTimeout |
| exception.message | Failed to open TCP connection to s3.me-south-1.amazonaws.com:443 (execution expired) |
| top_frame | app/models/concerns/record_geo_coordinate.rb:45 (geo_coordinate_s3_object.upload_stream) |
| runtime | Ruby / Rails (cupixworks-api, tesla repo) |
| env | production (multi-region: me-south-1, me-central-1, us-west-2, eu-central-1) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
cupixworks-api (Record#flush_geo_coordinate) |
최근 버스트 다수 (s3.me-south-1) |
geo-coordinate S3 업로드 실패 → 다음 cron/재시도에서 재수행되어 최종적으로는 flush 됨 |
cupixworks-api (Cupix::Aws::Kinesis#put_records!) |
소수 (kinesis.us-west-2, kinesis.eu-central-1) |
이벤트 스트림 전송 실패 (동일 예외 클래스로 그룹화됨) |
Timeline#
- 2025-04-17 06:45 KST — 이슈 최초 발생 (first_seen). Error Tracking Representative Error 는 이 시점 샘플로 message 가 비어 있어 STALE.
- 2026-07-22 04:22 KST / 2026-07-25 02:40 KST —
Cupix::Aws::Kinesis#put_records!에서kinesis.eu-central-1/kinesis.us-west-2커넥트 timeout (동일 이슈로 그룹화). - 2026-08-04 14:55 KST — 이슈 last_seen.
- 2026-08-04 15:17~16:36 KST —
Record#flush_geo_coordinate에서s3.me-south-1.amazonaws.com:443커넥트 timeout 이 분당 1~2건 간헐 반복. 사이사이flush_geo_coordinate - finished - duration: 1 seconds성공 로그가 다수 관측됨 → 특정 리전 엔드포인트로의 간헐적 연결 실패.
Error Log#
Net::OpenTimeout
실제 최근 발생(Representative Error 는 message 가 비어 STALE 이며, last_seen 근처 로그가 보여주는 실제 메시지):
flush_geo_coordinate - error - message: Failed to open TCP connection to s3.me-south-1.amazonaws.com:443 (execution expired)
Impact#
- Service:
net/http(실제 애플리케이션은cupixworks-api/ tesla) - 발생 횟수: 60 (15.5개월 누적, 매우 낮은 빈도)
- 최초 발생: 2025-04-17 06:45 KST
- 최근 발생: 2026-08-04 14:55 KST
Root Cause Summary#
Net::OpenTimeout 은 코드 결함이 아니라 간헐적 외부 의존성(AWS S3 / Kinesis) TCP 연결 timeout 이다. Record#flush_geo_coordinate 는 AWS SDK 의 upload_stream 을 통해 S3 에 업로드하는데, SDK 는 내부적으로 Ruby net/http 로 커넥션을 연다. 대상 리전 엔드포인트(주로 s3.me-south-1.amazonaws.com:443)로의 커넥트가 SDK 의 connect timeout 안에 완료되지 못하면 Net::OpenTimeout 이 발생한다. 이 예외는 record_geo_coordinate.rb:90 의 rescue StandardError => e 로 이미 포착되어 lock 을 해제하고 error 로그를 남긴 뒤 false 를 반환하므로 요청 흐름으로 전파되지 않는다. Datadog Error Tracking 은 APM Ruby tracer 가 기록한 span error 를 최하위 프레임(net/http) 기준으로 묶기 때문에 service: net/http, Net::OpenTimeout 라는 인프라 어댑터 이름으로 나타난다. Representative Error 는 first_seen(2025-04) 샘플이라 message 가 비어 STALE 하며, last_seen 근처 실제 메시지는 위 S3 커넥트 timeout 이다.
Technical Analysis#
Code Path#
- Entry point:
app/models/concerns/record_geo_coordinate.rb:37—flush_geo_coordinate - S3 업로드(외부 호출):
app/models/concerns/record_geo_coordinate.rb:45—geo_coordinate_s3_object.upload_stream(...) - Failure point: AWS SDK 내부
net/httpconnect →Net::OpenTimeout - 예외 처리:
app/models/concerns/record_geo_coordinate.rb:90-94—rescue StandardError에서 이미 포착
geo_coordinate_s3_object.upload_stream(
content_type: 'application/json',
cache_control: "max-age=#{1.year.to_i}",
acl: 'bucket-owner-full-control'
) do |write_stream|
write_stream << '{ "panos": ['
rescue StandardError => e
unlock_flushing_geo_coordinate
Cupix::Logger.error("flush_geo_coordinate - error - message: #{e.message}", class: self.class.name, function: __method__, module: 'RecordGeoCoordinate', record: { id: self.id })
false
else
unlock_flushing_geo_coordinate
end
기대 동작: S3 커넥트 성공 → JSON 스트림 업로드 → geo_coordinate_url_updated_at 갱신 → finished 로그.
실제 동작: 대상 리전 엔드포인트 커넥트가 간헐적으로 timeout → Net::OpenTimeout → 이미 존재하는 rescue 에서 포착 → lock 해제 + error 로그 + false 반환. 예외는 상위로 전파되지 않으며, stale_over_hour / delayed flush cron(lib/cupix/cron/record.rb)이 다음 주기에 재수행하므로 최종적으로 flush 된다.
동일 예외 클래스가 다른 외부 호출(Cupix::Aws::Kinesis#put_records! → kinesis.*.amazonaws.com:443)에서도 발생하여 하나의 ET 이슈로 그룹화된다.
Log Evidence#
Datadog 쿼리:
"execution expired"
service:cupixworks-api ("Net::OpenTimeout" OR "execution expired")
Net::OpenTimeout 문자열 자체로는 log index 에서 0건(예외가 ET/APM span 으로만 기록되고 애플리케이션 로그에는 Ruby 렌더링 메시지로 남음). last_seen 근처 실제 발생 로그:
{
"timestamp": "2026-08-04 16:36:27",
"status": "error",
"message": "flush_geo_coordinate - error - message: Failed to open TCP connection to s3.me-south-1.amazonaws.com:443 (execution expired)",
"class": "Record",
"function": "flush_geo_coordinate"
}
동일 이슈로 묶인 다른 엔드포인트(Kinesis):
{
"timestamp": "2026-07-24 17:40:33",
"status": "error",
"message": "Failed to put records: Failed to open TCP connection to kinesis.us-west-2.amazonaws.com:443 (execution expired)",
"class": "Cupix::Aws::Kinesis",
"function": "put_records!"
}
간헐성 근거 — 실패 사이사이 성공 로그가 다수:
flush_geo_coordinate - finished - duration: 1 seconds
flush_geo_coordinate - begin
flush_geo_coordinate - finished - duration: 1 seconds
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 간헐적 외부 의존성(AWS S3/Kinesis) TCP connect timeout | last_seen 근처 로그가 Failed to open TCP connection to s3.me-south-1.amazonaws.com:443 (execution expired); 실패 사이 성공(finished - duration: 1 seconds) 다수; 15.5개월 60건의 낮은 빈도; 여러 리전 엔드포인트(me-south-1/me-central-1/us-west-2/eu-central-1)에서 발생 |
— | Confirmed |
| H2 | 애플리케이션 코드 결함(nil/logic)으로 예외가 전파되어 요청 실패 | — | record_geo_coordinate.rb:90 에서 이미 rescue StandardError 로 포착, lock 해제 후 false 반환하여 전파되지 않음 |
Rejected |
| H3 | Representative Error 의 빈 message 가 실제 현재 증상 | ET 이슈에 그렇게 표시됨 | first_seen(2025-04) 샘플로 STALE. last_seen 근처 실제 메시지는 S3 connect timeout | Rejected |
| H4 | 특정 코드 배포/리전 설정 변경으로 인한 회귀 | — | 15.5개월에 걸쳐 산발 발생, 특정 배포와 상관관계 없음; 성공 케이스가 다수로 정상 동작 중 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 코드 변경 불필요. 예외는 이미
record_geo_coordinate.rb:90및Cupix::Aws::Kinesis#put_records!에서 처리되고, cron 재수행으로 최종 flush 가 보장된다. 이는 외부 연결의 간헐적 timeout 으로 코드 버그가 아니다.
단기 개선 (1주 이내)#
- Error Tracking 노이즈 감소: 이 이슈는 이미 rescue 되는 transient timeout 이므로 ET 에서 IGNORE 처리하거나, 반복 발생 시 임계치 기반 알림으로만 전환하는 것을 권장.
s3.me-south-1등 특정 리전 엔드포인트에서 timeout 비율이 지속적으로 높다면 AWS SDK 의http_open_timeout/ retry 설정 점검. (설정값 확인 필요 — uncertain, needs verification.)
장기 개선 (재발 방지)#
- Datadog Error Tracking 에서 최하위 프레임(
net/http) 기준 그룹화로 인해 서로 다른 외부 호출(S3 upload, Kinesis put_records)이 하나의 이슈로 묶이는 문제 → 애플리케이션 레벨에서Net::OpenTimeout을 도메인별 예외로 래핑하거나 span tag(peer.hostname,component)를 활용해 그룹 세분화 검토.
Monitoring#
특정 리전 S3 connect timeout 발생 추이:
service:cupixworks-api "execution expired" "s3"
flush_geo_coordinate 에러 발생 건수:
service:cupixworks-api @function:flush_geo_coordinate status:error
Kinesis 전송 실패:
service:cupixworks-api "Failed to put records" "execution expired"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (코드 수정 없음, ET IGNORE / 모니터링 조정 수준)
Noise Verdict#
noise — 이미 코드에서 rescue 되고 cron 재수행으로 최종 flush 되는 간헐적 AWS S3/Kinesis TCP connect timeout 이므로 코드 수정이 필요 없는 일시적 외부 의존성 오류다.