CupixAuth#getSession missing retry on 504 — no retryable wrapper
RCA: PotreeService::handlingMessageErrors — 502 Bad Gateway on GET /api/v1/sessions
Overview#
What Happened#
2026-07-07 03:15 KST 시각에 cupixworks-any-potree-agent (ap-southeast-2)가 SQS 메시지 처리 시작 시 세션을 확인하기 위해 내부 API api-tesla.cupix.internal로 GET /api/v1/sessions?fields[0]=id 요청을 보냈다가 AWS ELB로부터 502 Bad Gateway 응답을 받고 실패했다. 에러는 최상위 runByMessages 루프에서 catch되어 handlingMessageErrors가 로그로 남겼고, 메시지 1건이 처리되지 못했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError |
| exception.message | 502 Bad Gateway (awselb/2.0) |
| top_frame | packages/cupix-tesla-potree-agent/src/potree-service.ts:502 (handlingMessageErrors) |
| upstream call | GET http://api-tesla.cupix.internal/api/v1/sessions?fields[0]=id |
| env | production, ap-southeast-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-any-potree-agent (ap-southeast-2) | 1 | Pointcloud 변환 메시지 1건 처리 지연 (SQS 재시도 대상) |
Timeline#
- 2026-07-07 03:15:23 KST — Agent가 SQS 메시지
a9706694-9915-4b29-8db0-210bf0c7e27e수신 (ApproximateReceiveCount=1). - 2026-07-07 03:15:23 KST —
authenticateByMessage→startSession→getSession()이GET /api/v1/sessions호출. - 2026-07-07 03:15:23 KST —
api-tesla.cupix.internal앞의 ALB (server: awselb/2.0)가502 Bad Gateway응답 (본문 122 bytes, nginx-style HTML). - 2026-07-07 03:15:23 KST —
runByMessage에서 throw된 에러가runByMessages루프의 catch로 전달되어handlingMessageErrors에서 error 로그로 기록.
Error Log#
PotreeService::handlingMessageErrors | Error and message object - {"error":{"response":{"statusCode":502,"body":"<html>\r
<head><title>502 Bad Gateway</title></head>\r
<body>\r
<center><h1>502 Bad Gateway</h1></center>\r
</body>\r
</html>\r
","headers":{"server":"awselb/2.0","date":"Mon, 06 Jul 2026 18:15:23 GMT","content-type":"text/html","content-length":"122","connection":"keep-alive","set-cookie":["AWSALB=15AgdQgxzHH/KOOGOOFZ/yb0nJTHoXL/nvHWx+uR6+Z+GUUKhWRNGWenVqZjeyFAdUfdyTZ1VBJ2+bv9gEn49zSFW/LjcZ2Z2KIilzBuFOK0Pd8iz4XJtGYCvnu6; Expires=Mon, 13 Jul 2026 18:15:23 GMT; Path=/","AWSALBCORS=15AgdQgxzHH/KOOGOOFZ/yb0nJTHoXL/nvHWx+uR6+Z+GUUKhWRNGWenVqZjeyFAdUfdyTZ1VBJ2+bv9gEn49zSFW/LjcZ2Z2KIilzBuFOK0Pd8iz4XJtGYCvnu6; Expires=Mon, 13 Jul 2026 18:15:23 GMT; Path=/; SameSite=None"]},"request":{"uri":{"protocol":"http:","slashes":true,"auth":null,"host":"api-tesla.cupix.internal","port":null,"hostname":"api-tesla.cupix.internal","hash":null,"search":"?fields%5B0%5D=id","query":"fields%5B0%5D=id","pathname":"/api/v1/sessions","path":"/api/v1/sessions?fields%5B0%5D=id","href":"http://api-tesla.cupix.internal/api/v1/sessions?fields%5B0%5D=id"},"method":"GET","headers":{"User-Agent":"cupix-agent","Accept":"application/json","X-CUPIX-AUTH":"session_token:1y44kh0tviwy,session_id:406190"}}},"body":{},"statusCode":502,"name":"HttpError"},"sqsMessage":{"MessageId":"a9706694-9915-4b29-8db0-210bf0c7e27e","Attributes":{"ApproximateReceiveCount":"1"}}}
Impact#
- Service:
cupixworks-any-potree-agent - 발생 횟수: 1
- 최초 발생: 2026-07-07 03:15:23 KST
- 최근 발생: 2026-07-07 03:15:23 KST
- 영향: Pointcloud SQS 메시지 1건이 처리되지 못하고 큐로 되돌아가 재시도됨.
ApproximateReceiveCount=1이므로 첫 번째 시도에서 실패한 상태이며, SQS visibility timeout 이후 재수신되어 재처리될 것으로 예상됨.
Root Cause Summary#
Agent가 SQS 메시지 처리 초입에서 내부 Rails API의 세션 확인 엔드포인트(GET /api/v1/sessions)를 호출했으나, 응답이 AWS ELB의 502 Bad Gateway HTML이었다. server: awselb/2.0 헤더와 짧은 정적 HTML 본문(122 bytes)은 이 응답이 Rails 애플리케이션이 아닌 ALB 자체에서 반환됐음을 의미한다. 즉, 요청 시점에 ALB 뒤의 cupixworks-api 타깃 인스턴스가 unhealthy 상태이거나 응답을 반환하지 못했다. CupixAuth.getSession은 retryable 헬퍼로 감싸져 있지 않고 (retryable은 statusCode > 500에만 재시도하도록 정의되어 있음에도 이 호출 경로에서 사용되지 않음), 실패가 곧바로 runByMessage까지 전파되어 handlingMessageErrors로 기록되었다. 근본 원인은 upstream(ALB↔cupixworks-api)의 순간적 장애이고, 애플리케이션 측 취약점은 세션 확인 호출에 재시도가 없다는 점이다.
Technical Analysis#
Code Path#
- Entry point:
packages/cupix-tesla-potree-agent/src/potree-service.ts:54(checkingQueue) - Message loop:
packages/cupix-tesla-potree-agent/src/potree-service.ts:130(runByMessages) - Failure origin:
packages/api/src/authentication/cupix-auth.ts:321(sessionApi().getSession(['id'])) - Error handler:
packages/cupix-tesla-potree-agent/src/potree-service.ts:502(handlingMessageErrors)
메시지 루프는 각 메시지 처리 실패를 최상위에서 잡아 로깅한다:
} else {
this._countWaitedToStopTask = 0;
await this.agentScaleOut();
try {
await this.runByMessages();
} catch (error) {
await this.handlingMessageErrors(error);
}
this.resetMessages();
await CPUtils.sleep(500);
await this.checkingQueue();
}
runByMessage는 실제 처리 첫 단계에서 세션 인증을 호출한다:
private runByMessage = async (message: AWS.SQS.Message) => {
this._messageInProcess = message;
const messageBody = message.Body;
if (messageBody && CPUtils.isJsonString(messageBody)) {
const msgObject = JSON.parse(messageBody);
const targetId = Environment.DEBUG_MODE && Environment.CPX_MODEL_ID ? Environment.CPX_MODEL_ID : msgObject.id;
setLogMeta({ pointcloud: { id: targetId } });
// ...
await this.authenticateByMessage(msgObject);
authenticateByMessage는 startSession을 호출하고, production 모드(session 파라미터가 세팅됨)에서는 getSession이 실행된다:
startSession = async (): Promise<void> => {
if (this.authParams != undefined) {
if (DEBUG_MODE) {
await this.authenticate(this.authParams);
} else if (this.authParams.session) {
await this.getSession();
} else {
throw new Error('Wrong Auth Params');
}
} else {
throw new Error('No Auth Params');
}
};
getSession은 실패 시 handleError 후 그대로 throw하며, 재시도 로직이 없다:
private getSession = async (): Promise<TESLA.Session | undefined> => {
try {
await this.checkToken();
const res = await this.sessionApi().getSession(['id']);
const attributes = res?.body?.result?.data?.attributes;
this.setSession(attributes);
return attributes;
} catch (ec) {
throw this.handleError(ec);
}
};
참고로 같은 파일에는 5xx에 대한 재시도 헬퍼 retryable이 이미 존재하지만, getSession 경로에서는 사용되지 않는다:
retryable = <T>(f: () => Promise<T>, retries?: number): Promise<T> => new Promise((resolve, reject) => {
const _retries = retries != undefined ? retries : 0;
f()
.then(response => resolve(response))
.catch(e => {
if (e && e.statusCode > 500 && _retries < Constants.MaxRetries) {
// ...
setTimeout(() => {
this.retryable(f, _retries + 1).then(resolve).catch(reject);
}, delay);
} else {
reject(e);
}
});
});
기대 동작 vs 실제 동작: upstream ALB의 일시적 502는 재시도로 흡수되어야 하지만, getSession 경로는 재시도 없이 즉시 실패했고, 메시지 처리가 아예 시작되지 못한 채 상위 catch로 전파되었다.
Log Evidence#
Datadog 쿼리 (재현용):
service:cupixworks-any-potree-agent status:error "handlingMessageErrors"
지난 7일간 cupixworks-any-potree-agent의 error 로그 확인 결과, 이번 502는 단발성 이벤트이며 나머지는 무관한 403 ENT4000 Pointcloud not found 케이스들이었다:
2026-07-07 03:15:23 — statusCode 502, path=/api/v1/sessions (본 인시던트)
2026-07-06 10:12:38 — statusCode 403, ENT4000 Pointcloud not found (id=57672, 무관)
2026-07-06 10:12:36 — statusCode 403, ENT4000 Pointcloud not found (id=57670, 무관)
2026-07-03 17:16:27 — statusCode 403, ENT4000 Pointcloud not found (id=57650, 무관)
2026-07-03 15:28:00 — statusCode 403, ENT4000 Pointcloud not found (id=57638, 무관)
502 응답의 특징적 지표:
{
"statusCode": 502,
"headers": {
"server": "awselb/2.0",
"content-length": "122"
},
"body_snippet": "<html><head><title>502 Bad Gateway</title></head>..."
}
server: awselb/2.0→ 응답이 AWS Application Load Balancer 자체에서 반환됨 (Rails/nginx가 아님).content-length: 122→ ALB의 기본 오류 페이지 크기와 일치.- 짧은 정적 HTML → 애플리케이션 코드가 실행되지 않았음.
같은 시각(±5분) cupixworks-api의 sessions 관련 error 로그 검색은 결과가 없었다 — Rails까지 요청이 도달하지 않고 ALB에서 끊긴 것으로 보이는 것과 일관됨.
SQS 메시지 상태:
MessageId: a9706694-9915-4b29-8db0-210bf0c7e27e
ApproximateReceiveCount: 1
첫 번째 수신에서 실패했으므로 SQS visibility timeout 이후 재시도 대상.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Upstream ALB / cupixworks-api 타깃의 일시적 5xx (transient upstream failure) |
응답 헤더 server: awselb/2.0, 122바이트 정적 502 HTML, 발생 1회, 최근 7일간 재발 없음 |
— | Confirmed |
| H2 | 세션 토큰(session_token/session_id) 만료 또는 무효화 | 요청 헤더에 X-CUPIX-AUTH: session_token:...,session_id:406190 포함 |
만료라면 401/403이 리턴되어야 하며, 응답이 ALB의 502임 (Rails에 도달하지 않음). bodyResult 없음 |
Rejected |
| H3 | 코드 버그 (요청 URL/파라미터 오류) | — | 동일 서비스에서 다른 시점에는 정상 동작 중이며, 요청 URI /api/v1/sessions?fields[0]=id는 getSession(['id']) 스펙과 정확히 일치 (cupix-auth.ts:321) |
Rejected |
| H4 | 서비스 전반의 광범위한 outage (dep-level 인시던트) | — | status-board 조회 결과 active: null, recent: []. 동일 시각에 유사 클러스터 없음 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 즉각 조치 필요 없음. 단발 upstream 5xx이며, SQS 메시지는 visibility timeout 후 재수신되어 재처리된다.
- SQS DLQ 정책과
MaxReceiveCount가 설정되어 있다면 재시도 후에도 실패 시 자동으로 DLQ로 이동하여 데이터 손실은 없다 (potree-service.ts:492-500checkReceiveCountToDeleteMessage참조 —Constants.MaxReceiveCount확인 권장).
단기 개선 (1주 이내)#
packages/api/src/authentication/cupix-auth.ts:318getSession(및 필요 시checkToken경로 전체)을 기존retryable헬퍼로 감싸는 방향을 검토. upstream ALB의 502/503/504 같은 transient 실패는 exponential backoff로 재시도하는 것이 안전하다.handlingMessageErrors의 로그 레벨 재검토: 재시도로 자체 회복 가능한 5xx upstream 오류는error가 아닌warn으로 로깅하여 재발 시 알림 소음을 줄이는 방향을 고려 (참고: 이 코드베이스에서 유사한 log-level downgrade 사례가 있었음).
장기 개선 (재발 방지)#
- Agent 계열 서비스가 공통으로 사용하는 내부 API 호출부에 대해 표준 재시도/circuit breaker 정책 정립.
retryable은 이미 파일 내에 존재하나 일부 경로에서만 사용되고 있다. - ALB 타깃 그룹 헬스체크 및 upstream 인스턴스 스케일링 정책을 재검토하여 502 발생 빈도 자체를 낮춘다.
Monitoring#
- 재발 여부 감지 쿼리 (release dashboard timeseries widget 용):
service:cupixworks-any-potree-agent status:error "statusCode\":502" "sessions"
- Agent가 다른 엔드포인트에서 upstream 5xx을 얼마나 자주 받는지 추이:
service:cupixworks-any-potree-agent status:error "awselb/2.0"
- Agents 전 서비스 관점에서의 5xx upstream 실패 (fingerprint 시각화):
status:error "awselb/2.0" "handlingMessageErrors"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (재시도 감싸기만으로 완화 가능하며, SQS 재시도로 사실상 자체 회복됨)