MySQL lock wait timeout — database contention
RCA: BaseService::handlingMessageErrors | 504 Gateway Timeout on capture update
Overview#
What Happened#
2026-04-23 08:39:59 UTC에 cupixworks-any-voxel-agent 서비스에서 capture 685954의 voxel_state를 "error"로 업데이트하는 PUT 요청이 HTTP 504 Gateway Timeout으로 실패했다. 동일 시간대에 cupixworks-api에서 대규모 MySQL lock wait timeout이 발생하고 있었으며, 이로 인해 내부 API 요청이 게이트웨이 타임아웃으로 실패한 것이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error (axios) |
| exception.message | Request failed with status code 504 |
| top_frame | /tmp/agent/dist/app.js:65374 (createError in bundled axios) |
| env | production, us-west-2 |
Timeline#
- 08:38:02 UTC — Voxel agent가 capture 685954 처리 시작 (setCaptureVoxelsParams)
- 08:38:56 UTC — Voxel 계산 완료 (768 voxels), S3 raw 업로드 시작
- 08:38:59 UTC —
updateVoxelState또는 upload credentials 요청 시 400 에러 발생 (State not changed) - 08:39:56 UTC — Upload credentials 요청에 504 에러 발생
- 08:39:59 UTC — Error handler에서
voxel_state: "error"PUT 요청도 504로 실패 →BaseService::handlingMessageErrors에러 로그 기록
Error Log#
BaseService::handlingMessageErrors | Error and message object - {"error":{"message":"Request failed with status code 504","name":"Error","stack":"Error: Request failed with status code 504
at createError (/tmp/agent/dist/app.js:65374:16)
at settle (/tmp/agent/dist/app.js:65401:13)
at IncomingMessage.handleStreamEnd (/tmp/agent/dist/app.js:69373:12)
...","config":{"timeout":0,"method":"put","data":"{\"voxel_state\":\"error\"}","url":"http://api-tesla.cupix.internal/api/v1/captures/685954?fields=..."},"status":504},"sqsMessage":{"MessageId":"dec12b10-e68c-4f0c-9a4d-b106a3411dfa","Attributes":{"ApproximateReceiveCount":"1"}}}
Impact#
- Service:
cupixworks-any-voxel-agent - 발생 횟수: 1 (이 fingerprint). 동일 시간대 동일 서비스에서 504 에러 다수 발생 (최소 10건 이상)
- 최초 발생: 2026-04-23T08:39:59.632Z
- 최근 발생: 2026-04-23T08:39:59.632Z
Root Cause Summary#
cupixworks-api (Rails) 서비스의 MySQL 데이터베이스에서 Lock wait timeout exceeded 에러가 대규모로 발생하면서, API 요청 처리가 지연/실패했다. 이로 인해 내부 로드밸런서/게이트웨이가 504 Gateway Timeout을 반환했다. Voxel agent는 voxel 계산 완료 후 결과를 업로드하는 과정에서 이 504를 받았고, error handler 내에서 voxel_state: "error" 상태 업데이트 시도도 동일한 504로 실패했다. Agent 코드의 API 호출에는 retry 로직이 적용되지 않으며, axios timeout도 0 (무제한)으로 설정되어 있어 게이트웨이 타임아웃에 취약하다.
Technical Analysis#
Code Path#
1. SQS 메시지 수신 및 처리 시작
Entry point: base-service.ts:107-111 — checkingQueue에서 메시지를 받아 runByMessages를 호출하고, 에러 시 handlingMessageErrors로 전달한다.
} else {
this._countWaitedToStopTask = 0;
try {
await this.runByMessages();
} catch (error) {
await this.handlingMessageErrors(error);
}
this.resetMessages();
await CPUtils.sleep(500);
await this.checkingQueue();
}
2. Voxel 처리 실행
VoxelService.run에서 capture를 로드하고, voxel을 계산/업로드한다. Catch 블록에서 updateVoxelState(VoxelState.Error)를 호출하는데, 이 호출 자체도 API 요청이므로 504에 취약하다.
run = async (targetId: number, msgObject?: any): Promise<void> => {
const targetType = msgObject.type ?? 'capture';
try {
const serverRealityCapture = await this.realityCaptureManager.loadRealityCapture(targetId, targetType);
// ... voxel 계산, 업로드 ...
if (!DEBUG_MODE) await this.voxelManager.uploadRawVoxels(cpRealityCapture);
if (!DEBUG_MODE) await this.voxelManager.uploadXYPlaneVoxels(cpRealityCapture);
} catch (error: any) {
logger.error('VoxelService::run | error', error);
if (!DEBUG_MODE) await this.realityCaptureManager.updateVoxelState(TESLA.VoxelState.Error);
}
};
3. Upload credentials 요청 실패 (504)
VoxelManager.uploadXYPlaneVoxels → createUploadCredentials → cupixApi.capture.createVoxelsUploadCredentials가 504로 실패. 에러 메시지: "failed to create upload credentials - \"Request failed with status code 504\"".
private createUploadCredentials = async (cpRealityCapture: CPRealityCapture): Promise<TESLA.UploadCredentials> => {
let s3Credentials: TESLA.UploadCredentials = {};
try {
if (cpRealityCapture.isCapture) {
s3Credentials = await this.cupixApi.capture.createVoxelsUploadCredentials(cpRealityCapture.id);
}
} catch (error: any) {
throw new Error('failed to create upload credentials - ' + JSON.stringify(error.message));
}
return s3Credentials;
};
4. Error state 업데이트도 504로 실패
VoxelService.run의 catch에서 updateVoxelState(Error) 호출 → cupixApi.capture.update(685954, {voxel_state: "error"}) → 504. 이 에러가 BaseService.runByMessage까지 전파되어 handlingMessageErrors에서 최종 로깅된다.
updateVoxelState = async (state: TESLA.VoxelState): Promise<void> => {
if (!this.realityCaptureId || !this.realityCaptureType) {
logger.error('RealityCaptureManager::updateVoxelState | end - undefined reality capture id or type');
throw new Error('Invalid realityCaptureId or realityCaptureType');
}
if (this.realityCaptureType === 'capture') {
this._srvRealityCapture = await this.cupixApi.capture.update(this.realityCaptureId, { voxel_state: state });
}
};
5. Error handler에서의 메시지 삭제 판단
Failure point: base-service.ts:290-313 — handlingMessageErrors에서 getApiErrorToDeleteMessage를 호출하는데, 504 에러는 axios 에러이므로 error.response가 undefined가 아닌 상태. 그러나 statusCode는 response.statusCode가 아니라 response.status로 설정되고, 504는 400-500 범위 밖(>500)이므로 getApiErrorToDeleteMessage는 undefined를 반환한다. 따라서 checkReceiveCountToDeleteMessage로 fallback하고, ApproximateReceiveCount: 1 < MaxReceiveCount이므로 메시지를 삭제하지 않는다.
if (statusCode != undefined && statusCode >= 400 && statusCode <= 500) {
if (statusCode === 401) return;
return errorMsg;
}
return; // 504는 여기서 undefined 반환 → 메시지 삭제 안 함
6. Retry 로직 미적용
CupixAuth.retryable (cupix-auth.ts:59-77)는 statusCode > 500일 때 retry하는 로직이 있으나, CaptureApiModule.update와 CaptureApiModule.createVoxelsUploadCredentials는 retryable을 사용하지 않고 직접 API를 호출한다. 또한 axios 설정에 timeout: 0 (무제한)이 설정되어 있다.
Log Evidence#
Datadog 쿼리 1: Voxel agent 에러 로그
service:cupixworks-any-voxel-agent status:error
Time: 2026-04-23T07:30:00Z to 2026-04-23T09:30:00Z
08:09~08:39 시간대에 504 에러가 반복적으로 발생. 다수의 capture ID (685930, 685941, 685954, 685176)에서 동일한 패턴 확인:
{"timestamp": "2026-04-23 17:39:59 KST", "status": "error", "message": "BaseService::handlingMessageErrors | Error and message object - {...\"status\":504}"}
{"timestamp": "2026-04-23 17:39:56 KST", "status": "error", "message": "VoxelService::run | end - error: \"failed to create upload credentials - \\\"Request failed with status code 504\\\"\""}
{"timestamp": "2026-04-23 17:38:59 KST", "status": "error", "message": "VoxelService::run | end - error: \"Request failed with status code 400\""}
Datadog 쿼리 2: Capture 685954 관련 전체 로그
service:cupixworks-any-voxel-agent 685954
Time: 2026-04-23T07:30:00Z to 2026-04-23T09:30:00Z
처리 타임라인:
{"timestamp": "2026-04-23 17:38:02 KST", "message": "CPRealityCapture::setCaptureVoxelsParams | capture ID: 685954 - voxelSize: 1"}
{"timestamp": "2026-04-23 17:38:56 KST", "message": "VoxelManager::calculateVoxels | finished - capture ID: 685954, voxelCount: 768"}
{"timestamp": "2026-04-23 17:38:56 KST", "message": "AwsS3Manager::uploadFileStreamToS3 | begin - filePath: .../capture_685954_voxels.csv"}
{"timestamp": "2026-04-23 17:38:59 KST", "status": "warn", "message": "CupixAuth::handleError | Response statusCode: 400, ...body.result: {\"code\":\"STAT40000\",\"reason\":\"State not changed\"}"}
{"timestamp": "2026-04-23 17:39:56 KST", "status": "warn", "message": "CupixAuth::handleError | Response statusCode: 504, ...voxels_upload_credentials..."}
{"timestamp": "2026-04-23 17:39:59 KST", "status": "warn", "message": "CupixAuth::handleError | Response statusCode: 504, ...captures/685954..."}
{"timestamp": "2026-04-23 17:39:59 KST", "status": "error", "message": "BaseService::handlingMessageErrors | ...status:504"}
Datadog 쿼리 3: API-side MySQL lock timeout 에러 (동일 시간대)
service:cupixworks-api (504 OR "Gateway Timeout" OR "timeout")
Time: 2026-04-23T08:35:00Z to 2026-04-23T08:45:00Z
API 서비스에서 대규모 MySQL lock wait timeout이 발생 중:
{"timestamp": "2026-04-23 17:44:29 KST", "message": "[502] PUT /api/v1/captures/684805/meta/prop", "error": {"message": "Mysql2::Error::TimeoutError: Lock wait timeout exceeded; try restarting transaction", "class": "ActiveRecord::LockWaitTimeout"}}
{"timestamp": "2026-04-23 17:44:16 KST", "message": "[500] POST /api/v1/pointclouds", "error": {"message": "Mysql2::Error::TimeoutError: Lock wait timeout exceeded; try restarting transaction", "class": "Cupix::Errors::System"}}
{"timestamp": "2026-04-23 17:43:29 KST", "message": "[500] POST /api/v1/clusters", "error": {"message": "Mysql2::Error::TimeoutError: Lock wait timeout exceeded; try restarting transaction"}}
10분 간 captures, pointclouds, clusters 등 다수 엔드포인트에서 lock wait timeout이 발생. 이는 DB lock contention이 광범위했음을 의미하며, agent의 504는 이 DB 문제로 인해 API 응답이 게이트웨이 타임아웃을 초과한 결과이다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | API-side MySQL lock wait timeout으로 인한 504 Gateway Timeout | 동일 시간대 cupixworks-api에서 Mysql2::Error::TimeoutError: Lock wait timeout exceeded 다수 발생 (captures, pointclouds, clusters 엔드포인트). 여러 capture ID에서 동시에 504 발생. |
— | Confirmed |
| H2 | Voxel agent의 네트워크 문제 (DNS, 연결 실패 등) | — | 에러가 status: 504로 HTTP 응답을 정상 수신했음. 시스템 에러(errno/code/syscall)가 아님. 다른 API 호출(loadRealityCapture, calculateVoxels)은 정상 완료. |
Rejected |
| H3 | API 서버 과부하 또는 배포 중 일시적 장애 | 504가 여러 agent에서 동시에 발생 | 로그에서 배포 관련 징후 없음. 에러 패턴이 DB lock wait와 정확히 일치. | Rejected |
| H4 | Agent의 retry 미적용으로 일시적 504 복구 실패 | CaptureApiModule.update에 retry 로직 없음. CupixAuth.retryable이 존재하지만 API 모듈에서 사용하지 않음. |
DB lock contention이 지속적이었으므로 retry해도 실패했을 가능성 높음. 그러나 retry로 일부 요청은 성공 가능. | Inconclusive |
Fix Recommendation#
즉시 조치 (Critical)#
이 에러의 근본 원인은 cupixworks-api(tesla) 쪽 MySQL lock contention이다. Agent 측에서는 별도의 코드 수정이 필요하지 않으며, DB lock contention의 원인을 조사해야 한다.
- Tesla 레포: 동일 시간대에 captures, pointclouds, clusters 테이블에서 lock contention이 발생한 원인을 조사. 대량 bulk 작업 또는 장기 트랜잭션이 있었는지 확인 필요.
단기 개선 (1주 이내)#
-
Agent API 호출에 retry 적용:
CaptureApiModule.update,createVoxelsUploadCredentials등 중요 API 호출에CupixAuth.retryable래퍼를 적용하거나, axios interceptor 레벨에서 5xx 에러에 대한 exponential backoff retry를 추가한다.- 파일:
packages/api/src/api/capture.api.ts:56-60 - 파일:
packages/api/src/api/base.api.ts— BaseApiModule에 공통 retry 래퍼 추가 가능
- 파일:
-
VoxelService error handler 보강:
voxel-service.ts:63-66의 catch 블록에서updateVoxelState(Error)호출이 실패하더라도 에러가 상위로 전파되지 않도록 try-catch로 감싸야 한다. 현재는 error handler 내 API 호출 실패가BaseService.handlingMessageErrors까지 전파된다.- 파일:
packages/cupix-tesla-voxel-agent/src/voxel-service.ts:65
- 파일:
-
Axios timeout 설정: 현재
timeout: 0(무제한)이므로, 적절한 timeout (예: 30초)을 설정하여 게이트웨이 타임아웃 전에 클라이언트에서 타임아웃을 감지하도록 한다.
장기 개선 (재발 방지)#
- DB lock contention 모니터링:
cupixworks-api에서ActiveRecord::LockWaitTimeout발생 빈도를 모니터링하고, 특정 임계치 초과 시 알림을 설정한다. - Agent resilience pattern: Circuit breaker 패턴을 도입하여 API가 지속적으로 5xx를 반환할 때 불필요한 요청을 줄이고, backoff 후 재시도하는 구조를 고려한다.
Monitoring#
- DB lock wait timeout 알림:
service:cupixworks-api "Lock wait timeout exceeded"
- Voxel agent 504 빈도 모니터링:
service:cupixworks-any-voxel-agent status:error "504"
- API 5xx 비율 메트릭:
sum:rails.request.errors{service:cupixworks-api,status_code:5xx}.as_rate()
Risk Assessment#
- Risk level: medium — DB lock contention이 해소되면 agent 에러도 자연 소멸하지만, 재발 가능성 있음
- 예상 복잡도: standard — agent 측 retry 추가는 표준적 변경이며, 근본 원인인 DB lock contention 해결은 별도 조사 필요