## Datadog
RCA: Floorplan check_tile_uploading 500 — Elasticsearch timeout during tile state transition
Error Log#
{
"name": "HttpError",
"statusCode": 500,
"response": {
"body": { "error": "Internal Server Error", "status": 500 },
"request": {
"method": "PUT",
"uri": { "pathname": "/api/v1/floorplans/626/check_tile_uploading" }
},
"headers": { "x-runtime": "10.470838" }
}
}
Impact#
- Service:
cupixworks-any-floorplan-agent - 발생 횟수: 1
- 최초 발생: 2026-04-16T06:50:34.627Z
- 최근 발생: 2026-04-16T06:50:34.627Z
Root Cause Summary#
Floorplan agent가 floorplan 626의 타일 업로드 완료 후 Tesla API의 PUT /api/v1/floorplans/626/check_tile_uploading 엔드포인트를 호출했으나, 해당 API에서 500 에러(10.47초 소요)가 반환되었습니다. Tesla API 로그에서 Faraday::TimeoutError: Operation timed out after 10002 milliseconds with 0 bytes received 에러가 확인되었으며, 이는 floorplan의 tile_state → :uploaded 상태 전환 시 발생하는 after_commit 콜백에서 Elasticsearch 문서 업데이트(_update_document)가 타임아웃된 것이 원인입니다. 동일 시간대에 Elasticsearch 클러스터에서 circuit_breaking_exception(heap 7.8gb > limit 7.5gb)이 대량 발생하여 모든 ES write 요청이 거부/지연되고 있었습니다.
Technical Analysis#
Code Path#
Agent 측 (cupixworks-any-floorplan-agent):
FloorplanService::run이 SQS 메시지로 floorplan 626 처리 시작 (state: resource_uploaded)FloorplanService::tileFloorplan에서 타일 생성 (15:49:55 ~ 15:50:03)- S3 타일 업로드 후
checkTileUploadingAPI 호출:
// applications/agents/packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:357
await this.cupixApi.floorplan.checkTileUploading(cpFloorplan.id);
- Tesla API가 500 반환 → agent가
HttpError로 로깅
Tesla API 측 (cupixworks-api):
TilableController#check_tile_uploading→TilableRepository#check_tile_uploading호출:
# app/repositories/concerns/tilable_repository.rb:4-9
def check_tile_uploading(params)
@model.check_tile_uploading!
@model.done_state if @model.respond_to?(:state_cloning?) && @model.state_cloning?
@model
end
Tile::S3#check_tile_uploading!에서 S3 타일 확인 → 상태 전환 트리거:
# app/models/concerns/tile/s3.rb:9-14
def check_tile_uploading!
self.tile_size = tile_uploading_objects.size if self.has_attribute?(:tile_size)
raise Cupix::Errors::InvalidState.new(code: 'STAT10000', reason: 'No tile objects found in S3') if tile_size.nil? || tile_size.zero?
self.uploaded_tile_state!
end
tile_state→:uploaded상태 전환의after_transition콜백:
# app/models/concerns/statable/floorplan.rb:167-174
after_transition any => [:uploaded] do |floorplan, transition|
floorplan.increase_tile_revision
if floorplan.state_creating?
floorplan.save! # → after_commit → _update_document → ES update
else
floorplan.fire_events(:done_state) # → state → :done → save! → ES update
end
end
- Failure point:
save!호출 시after_commit콜백에서 Elasticsearch 문서 업데이트 시도:
# app/models/concerns/searchable.rb:16-17, 55, 94
after_commit on: [:update] do
_update_document
end
# line 94 (inside _update_document)
results = __elasticsearch__.client.update(request.merge({ index: __elasticsearch__.index_name }))
이 ES client.update 호출이 Faraday의 10초 타임아웃에 걸려 Faraday::TimeoutError가 발생했고, _update_document의 rescue StandardError 블록에서 에러가 re-raise되어 API가 500을 반환했습니다.
Log Evidence#
Agent 로그 타임라인 (service:cupixworks-any-floorplan-agent):
# 사용 쿼리: service:cupixworks-any-floorplan-agent -f "2026-04-16T05:50:00Z" -t "2026-04-16T07:50:00Z"
15:49:54 KST [info] BaseService::runByMessage | id: 626
15:49:54 KST [info] FloorplanService::run | floorplan state: resource_uploaded, resource_state: uploaded
15:49:55 KST [info] FloorplanService::tileFloorplan | begin
15:50:03 KST [info] FloorplanService::tileFloorplan | tiling done and now save to `/tmp/workspace/626/tile`
15:50:03 KST [info] AwsS3Manager::setCredentials | begin / end
15:50:34 KST [warn] CupixAuth::handleError | Response statusCode: 500, requestUriHref: http://api-tesla.cupix.internal/api/v1/floorplans/626/check_tile_uploading?fields[...]
15:50:34 KST [error] HttpError (statusCode: 500, body: {"error":"Internal Server Error","status":500})
15:50:34 KST [info] BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/626
Tesla API 로그 (service:cupixworks-api):
# 사용 쿼리: service:cupixworks-api "floorplans/626" -f "2026-04-16T06:49:00Z" -t "2026-04-16T06:52:00Z"
15:49:55 KST [info] [200] GET /api/v1/floorplans/626 (FloorplansController#show)
15:49:56 KST [info] [200] PUT /api/v1/floorplans/626 (FloorplansController#update)
15:50:03 KST [info] [200] POST /api/v1/floorplans/626/tile_upload_credentials (FloorplansController#create_tile_upload_credentials)
15:50:36 KST [info] [500] PUT /api/v1/floorplans/626/check_tile_uploading (FloorplansController#check_tile_uploading)
error: ["Faraday::TimeoutError", "Operation timed out after 10002 milliseconds with 0 bytes received"]
duration: 10463.21ms, db: 19.22ms
request_id: 1807baae-a7da-424a-aef3-1dd8f3c2012d
DB 시간은 19ms에 불과하지만 전체 duration은 10463ms — 나머지 ~10444ms가 Elasticsearch 호출에서 소요되었습니다.
Elasticsearch circuit_breaking_exception (동시간대):
# 사용 쿼리: service:cupixworks-api "circuit_breaking_exception" -f "2026-04-16T06:49:00Z" -t "2026-04-16T06:52:00Z"
15:51:50~57 KST [info] 다수의 502 에러 발생:
- PUT /api/v1/panos/430342/check_tile_uploading → circuit_breaking_exception
- PUT /api/v1/panos/430343/check_tile_uploading → circuit_breaking_exception
- PUT /api/v1/panos/430344/check_tile_uploading → circuit_breaking_exception
... (최소 10건 이상)
{
"type": "circuit_breaking_exception",
"reason": "[parent] Data too large, data for [indices:data/write/bulk[s]] would be [8373437306/7.7gb], which is larger than the limit of [8160437862/7.5gb], real usage: [8373433368/7.7gb]",
"bytes_wanted": 8373437306,
"bytes_limit": 8160437862,
"durability": "PERMANENT"
}
Floorplan 626의 에러(15:50:34)가 Pano들의 circuit_breaking_exception(15:51:50~)보다 약 1분 먼저 발생했지만, ES heap이 이미 한계에 근접한 상태에서 write 요청이 극도로 느려지며 timeout이 발생한 것으로 판단됩니다. 1분 후에는 heap이 limit을 초과하여 요청이 즉시 거부(429)되기 시작했습니다.
Fix Recommendation#
즉시 조치 (Critical)#
Tesla API 측에서 _update_document(searchable.rb:112-115)의 rescue StandardError에서 ES 에러를 re-raise하지 않고, 비동기 재시도 큐로 위임하는 방식으로 변경이 필요합니다. 현재 ES 인덱싱 실패가 API 응답 자체를 500으로 만들어, 타일 상태 전환(DB에는 이미 커밋됨)이 성공했음에도 agent가 실패로 인식합니다.
- 파일:
app/models/concerns/searchable.rb:112-115 - ES 인덱싱 에러를
raise대신 로깅 + 비동기 재인덱싱 큐로 처리하는 방향
단기 개선 (1주 이내)#
- Elasticsearch heap 모니터링 강화: 현재 7.5gb limit에 근접(7.7~7.8gb)하고 있으므로, ES 클러스터의 JVM heap 크기를 늘리거나 노드를 추가해야 합니다.
- Floorplan agent 재시도 로직:
checkTileUploading호출 실패 시 재시도 로직 추가. 현재 agent는 API 500 응답에 대해 별도 재시도 없이 실패로 처리합니다.
장기 개선 (재발 방지)#
- ES 인덱싱을 API 요청 경로에서 분리:
after_commit콜백에서 동기적으로 ES를 업데이트하는 대신, Sidekiq worker를 통해 비동기로 인덱싱하여 ES 장애가 API 응답에 영향을 주지 않도록 합니다. - ES 클러스터 autoscaling: heap 사용량 기반의 자동 스케일링 정책을 추가하여 트래픽 급증 시에도 circuit breaker가 동작하지 않도록 합니다.
Monitoring#
- ES heap 사용량 메트릭 알림 추가 (80% 이상 시 warning, 90% 이상 시 critical)
check_tile_uploading엔드포인트의 5xx 비율 모니터링
# Datadog 쿼리 예시
service:cupixworks-api "check_tile_uploading" status:error
service:cupixworks-api "circuit_breaking_exception"
service:cupixworks-api "Faraday::TimeoutError"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard — ES 클러스터 heap 확장은 인프라 변경이 필요하며,
searchable.rb의 에러 핸들링 변경은 다른 모델에도 영향을 줄 수 있어 신중한 테스트가 필요합니다.