Value AQEBkO1zmCNLB+CX3gbl8GpQWC4iK7N1ejfQf2fLN0ufCiUBFjT10qZfMoOM9weF3FtFJ32gKPiSw1f7q1njUzqK7q7uYe
RCA: SQS ReceiptHandle expired after Python process timeout
Overview#
What Happened#
2026-07-03 14:14 KST에 cupixworks-capture-intelligence-agent (us-west-2, production)에서 SQS DeleteMessage 호출이 The receipt handle has expired로 실패했다. Python 요약 분석 프로세스가 SQS visibility timeout(10분)을 초과한 뒤 abort되어, BaseService의 에러 핸들러가 이미 다시 visible 상태로 돌아온 메시지를 삭제하려 하면서 발생한 파생 에러이다. 같은 이벤트가 관련 클러스터 c4609d42-7ab7-4161-a4d9-9186dfcdb926에서도 관측된다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | AWS.SQS InvalidParameterValue (ReceiptHandle) |
| exception.message | The receipt handle has expired. |
| top_frame | packages/base/src/manager/aws-queue.manager.ts:97 (deleteMessage) |
| upstream | CaptureIntelligenceProcessManager process timeout (AbortError / Process timeout) |
| queue | cupix-capture-intelligence-agent-production.fifo (us-west-2) |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| clark-vdc (capture intelligence) | 1 | 한 개 capture의 summary generation이 재시도 루프에 들어감 (SQS 메시지가 삭제되지 못하고 visibility timeout 이후 재수신) |
Timeline#
- 2026-07-03 14:04 KST 전후 — Python
compass-capture_summary프로세스 실행 시작 (관련CaptureIntelligenceProcess | STDERRffmpeg 로그가 14:01~14:07 KST에 관찰됨). - 2026-07-03 14:14:10 KST —
CaptureIntelligenceProcessManager | process timeout, aborting...— 1시간 timeout 도달,AbortController.abort(new Error('Process timeout'))호출. - 2026-07-03 14:14:10 KST —
CaptureIntelligenceProcess | process error: The operation was aborted— child process가AbortError로 종료. - 2026-07-03 14:14:10 KST —
CupixAuth::handleError | Undefined response: ... AbortError— abort가 상위로 전파. - 2026-07-03 14:14:10 KST —
BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"e3e5871b-9d53-404f-b0c7-4b94c885e66e","Attributes":{"ApproximateReceiveCount":"1"}}}— 에러 핸들러가 메시지 삭제를 시도. - 2026-07-03 14:14:10 KST —
AwsQueueManager::deleteMessage | end - ... The receipt handle has expired.— SQS가 receipt handle 만료로 삭제 거부. Datadog 에러 로그 2건 발생.
Error Log#
Value AQEBkO1zmCNLB+CX3gbl8GpQWC4iK7N1ejfQf2fLN0ufCiUBFjT10qZfMoOM9weF3FtFJ32gKPiSw1f7q1njUzqK7q7uYeFSx6dwDyoc/RMO0klPWuumaM4Zo7C01y9ClWy5OFWhoS+fd4/9e/tIWK1njeH9qNc380MzY92SuuO8w88pukXzJcB5spfQJAtyjVGVlpCQ6keepyIUbxjFVqSqBcPYI6fN2Yr8N/dP9/lbGSfZGCL60kTL97nx9Uk4zlizlNfMDGYN2eZ6PBHsJKqQCcC7i+PWl7cZA5RLXl3UBNsh2EJK6eY4ytSZLk3vFK3q for parameter ReceiptHandle is invalid. Reason: The receipt handle has expired.
Impact#
- Service:
cupixworks-capture-intelligence-agent - Team: clark-vdc
- 발생 횟수: 1
- 최초 발생: 2026-07-03 14:14 KST
- 최근 발생: 2026-07-03 14:14 KST
이 에러 자체는 파생 증상이다. 사용자 영향은 두 가지이다. 첫째, Python 프로세스 timeout으로 인해 해당 capture의 summary 생성이 이번 attempt에서 실패했다. 둘째, DeleteMessage가 실패했기 때문에 SQS 메시지가 큐에 남아 visibility timeout(10분) 후에 재수신되어 ApproximateReceiveCount가 증가한다. MaxReceiveCount에 도달할 때까지 반복 재시도가 발생하며, 각 시도마다 최대 1시간의 Python 실행이 소모될 수 있다.
Root Cause Summary#
Python 분석 프로세스의 timeout(1시간)이 SQS FIFO 큐의 visibility timeout(10분)보다 훨씬 길게 설정되어 있다. Python 프로세스가 10분을 초과하면 SQS는 메시지를 다시 visible로 만들고, 원래 consumer가 들고 있던 receipt handle을 무효화한다. 이후 Python이 최종 timeout에 도달해 AbortError로 종료되면 BaseService.handlingMessageErrors가 deleteByMessage를 호출하는데, 이때 receipt handle은 이미 만료되어 있으므로 SQS가 InvalidParameterValue: The receipt handle has expired.를 반환한다. 처리 중에 visibility를 연장하는 heartbeat 로직이 없다는 것이 근본 원인이다.
Technical Analysis#
Code Path#
- Entry point:
packages/base/src/base-service.ts:86(checkingQueue→runByMessages→runByMessage) - Long-running work:
packages/cupix-capture-intelligence-agent/src/manager/capture-intelligence-process.manager.ts:70(runPythonProcess, timeout 1시간) - Failure point:
packages/base/src/manager/aws-queue.manager.ts:97(deleteMessage) → SQS returnsInvalidParameterValue
Python process timeout 상수:
/** Maximum execution time before process is killed (1 hour). */
private readonly TIMEOUT_MS = 60 * 60 * 1000;
SQS visibility timeout 상수:
export const QueueVisibilityTimeout = 600; // seconds
QueueVisibilityTimeout은 changeMessageVisibility에서만 사용된다. 실제 큐 자체의 default visibility는 SQS 큐 속성이며, 관측 로그와 SQS 기본값을 감안할 때 10분 안팎이다. 어떤 경우든 1시간짜리 Python timeout보다 짧다.
Abort 이후 에러 핸들러:
private handlingMessageErrors = async (error: any): Promise<void> => {
const errorAndMessage = {
error: error,
sqsMessage: {}
};
if (this.messageInProcess) {
errorAndMessage.sqsMessage = {
MessageId: this.messageInProcess.MessageId,
Attributes: this.messageInProcess.Attributes
};
const apiErrorObject = this.getApiErrorToDeleteMessage(error);
if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
try {
errorAndMessage.error = apiErrorObject;
await this.deleteByMessage(this.messageInProcess);
if (this._modelInProcess != undefined && this._modelInProcess.id > 0) await this.updateErrorState(this._modelInProcess);
} catch (error) {
logger.error('BaseService::handlingMessageErrors | Errors in error handling', error);
}
}
}
logger.error('BaseService::handlingMessageErrors | Error and message object - %s', JSON.stringify(errorAndMessage));
resetLogMeta();
};
AbortError는 response가 없으므로 getApiErrorToDeleteMessage가 'undefined response'를 반환한다(base-service.ts:250-252). apiErrorObject != undefined가 참이 되어 deleteByMessage가 실행되지만, receipt handle은 이미 만료된 상태이다.
SQS delete 호출:
deleteMessage = (message: AWS.SQS.Message): Promise<void> => new Promise((resolve, reject) => {
if (this.debugMode) return resolve();
const queueUrl = this.queueUrl;
logger.info('AwsQueueManager::deleteMessage | begin - queue url: %s', queueUrl);
// ...
const params: AWS.SQS.DeleteMessageRequest = {
QueueUrl: queueUrl,
ReceiptHandle: messageReceiptHandle
};
this.sqs.deleteMessage(params, (err: AWS.AWSError) => {
if (err) {
logger.error('AwsQueueManager::deleteMessage | end - %s', err.message);
reject(err);
기대 동작: 메시지 처리 중에 주기적으로 changeMessageVisibility를 호출하여 receipt handle 유효성을 유지한다. 실제 동작: 처리 중 heartbeat가 없어 10분 이후 receipt handle이 만료되고, 이후의 deleteMessage는 반드시 실패한다.
Log Evidence#
Datadog 쿼리 (재현용):
service:cupixworks-capture-intelligence-agent "ReceiptHandle"
service:cupixworks-capture-intelligence-agent status:(error OR warn)
핵심 이벤트 시퀀스 (2026-07-03 14:14:10 KST, 같은 초 안에서):
warn CaptureIntelligenceProcessManager | process timeout, aborting...
error CaptureIntelligenceProcess | process error: The operation was aborted
warn The operation was aborted
warn CupixAuth::handleError | Undefined response: {"stack":"AbortError: The operation was aborted ...","message":"The operation was aborted","cause":{"message":"Process timeout","name":"Error"},"code":"ABORT_ERR","name":"AbortError"}
warn BaseService::cleanUpAnythingRelatedModel | end - undefined modelDirPath
error BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"e3e5871b-9d53-404f-b0c7-4b94c885e66e","Attributes":{"ApproximateReceiveCount":"1"}}}
error AwsQueueManager::deleteMessage | end - Value AQEB... for parameter ReceiptHandle is invalid. Reason: The receipt handle has expired.
error Value AQEB... for parameter ReceiptHandle is invalid. Reason: The receipt handle has expired.
주목할 점:
ApproximateReceiveCount = 1이다. 즉 이 메시지가 이번에 처음 수신되었지만, 처리 시간이 visibility timeout을 넘겼기 때문에 이미 큐로 돌아간 상태였다.- 앞선 timeout 이벤트가 다중으로 관찰된다:
14:09:58,14:11:49,14:14:10KST 세 번의CaptureIntelligenceProcessManager | process timeout, aborting.... 즉 이 서비스에서 process timeout은 산발적으로 반복 발생하는 상태이다. - Downstream Python 프로세스 자체는 실행되고 있었다 — 14:04~14:07 KST 구간에
CaptureIntelligenceProcess | STDERRffmpeg 로그가 관측되므로 Python 프로세스는 살아 있었고 실제로 처리 중이었다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Python 프로세스가 SQS visibility timeout보다 오래 걸려 receipt handle이 만료되었다 (heartbeat 미구현) | TIMEOUT_MS = 60*60*1000 (process manager); QueueVisibilityTimeout = 600s; abort 로그가 receipt-expired 에러 직전에 발생; ApproximateReceiveCount=1인데도 handle 만료 |
— | Confirmed |
| H2 | SQS 클라이언트 SDK 자체의 버그 또는 AWS 측 장애 | — | 다른 deleteMessage 호출은 같은 시간대에 정상 동작 (`AwsQueueManager::deleteMessage |
end - message id: 44f35c29-...at 14:19:45 KST); status-board도dep: 스코프 아님 (svc:cupixworks-capture-intelligence-agent::unknown`) |
| H3 | Message body가 malformed이라서 processing이 실패했다 | — | runByMessage는 body parse 성공 후 run()을 실행했고, error는 Python child process abort에서 발생 (Process timeout cause) |
Rejected |
| H4 | Node.js 컨테이너가 재시작되어 in-flight receipt handle이 유실되었다 | — | 프로세스는 abort 후에도 계속 동작 중이었고 이후 정상 메시지들을 처리 (`CaptureIntelligenceService::run | end - captureId: 725919` at 14:19:45 KST) |
Fix Recommendation#
즉시 조치 (Critical)#
- 없음. 이 에러는 파생 증상이며 데이터 유실이나 서비스 다운을 초래하지 않는다. SQS 메시지는 visibility timeout 이후 재수신되며
MaxReceiveCount도달 시 DLQ로 이동한다. 즉시 롤백/핫픽스 사유가 아니다.
단기 개선 (1주 이내)#
- Visibility timeout heartbeat 추가:
packages/base/src/base-service.ts의runByMessage가 실행되는 동안 주기적으로awsQueueManager.changeMessageVisibility(message)를 호출하도록 한다. 인터벌은QueueVisibilityTimeout(600s)의 약 1/2인 5분 간격이 표준적이다. Python process가 abort될 때 heartbeat도 함께 clearInterval 해야 한다. 방향만 제시하며 구현은 별도 PR에서. - Delete error 완화 처리:
packages/base/src/base-service.ts:290handlingMessageErrors안에서deleteMessage가ReceiptHandleIsInvalid/receipt handle has expired를 반환하는 경우를 별도로 처리하여warn레벨로 낮추고, 이미 재수신될 예정이므로 그대로 흘려보낸다. 현재는 두 개의error로그가 매번 남아 error-sweeper 클러스터를 오염시킨다. - Python 프로세스 실제 실행 시간 측정:
runPythonProcess시작/종료에서 duration을 메트릭(예:capture_intelligence.python.duration)으로 emit하여 어느 capture가 얼마나 걸리는지 파악.
장기 개선 (재발 방지)#
- SQS visibility timeout과 process timeout의 관계를 아키텍처 레벨에서 정합화: Python이 최대 1시간까지 걸릴 수 있다면, SQS FIFO 큐의 visibility timeout을 최대 실행 시간(예: 1시간 + 여유 5분)으로 상향하거나, 위의 heartbeat 방식을 표준으로 채택. FIFO 큐이므로 visibility timeout을 늘리는 쪽이 side effect가 적다 (동일
MessageGroupId=capture_id로 dedup 되어 있음). - Long-running SQS worker 패턴 문서화: 다른 agent들(
cupix-tesla-compute-agent등)도 동일한AwsQueueManager를 공유하므로, agents 리포지토리에 "long-running consumer" 가이드를 추가.
Monitoring#
Datadog release dashboard timeseries widget에 넣을 쿼리 (writing-datadog-monitoring-queries skill 규칙 준수 — pipe/stats/count-by 미사용, timeseries widget 호환 형식).
Receipt handle 만료 발생 빈도:
logs("service:cupixworks-capture-intelligence-agent \"receipt handle has expired\"").index("*").rollup("count").by("environment")
Python process timeout 발생 빈도 (선행 지표):
logs("service:cupixworks-capture-intelligence-agent \"process timeout, aborting\"").index("*").rollup("count").by("environment")
전반적 에러 레벨 로그 카운트:
logs("service:cupixworks-capture-intelligence-agent status:error").index("*").rollup("count").by("environment")
알림 임계값 제안:
process timeout, aborting— 1시간에 3건 이상이면 warn 알림. Python 파이프라인의 성능 회귀 지표.receipt handle has expired— 24시간에 5건 이상이면 heartbeat 개선 우선순위 상향.
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
이 에러는 재시도 안전한 파생 증상이며, 데이터 유실 없이 SQS 재수신으로 복구된다. 그러나 반복될 경우 (1) DLQ 유입 (2) 컴퓨트 낭비 (Python 1시간 재실행) (3) error-sweeper 잡음이 누적되므로 단기 개선(Visibility heartbeat)을 진행하는 것이 바람직하다.