iSpring account creation failed: 400 Bad Request
RCA: iSpring account creation failed: 400 Bad Request
Overview#
What Happened#
2026-04-28 22:55~22:56 UTC 사이 cupixworks-api 서비스의 Cupix::IspringService#create_user_account에서 iSpring Learn API로의 사용자 생성 요청이 400 Bad Request로 16회 실패했다. ap-southeast-2 리전의 cupix 테넌트에서 Unispace 소속 2명의 사용자(jason.bussas@unispace.com, mike.lee@unispace.com)에 대해 각각 8회씩 반복 호출되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | RestClient::BadRequest (wrapped as Cupix::Errors::BadGateway) |
| exception.message | 400 Bad Request |
| top_frame | app/operations/ispring_operation.rb:35 |
| deploy | production-ap-southeast-2-20260428T0823Z0-b5ccc284-cupixworks |
| env | production, ap-southeast-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| Unispace (iSpring LMS integration) | 16 | 2명의 사용자가 iSpring 계정 생성 불가 |
Timeline#
- 2026-04-28T22:55:37Z -- jason.bussas@unispace.com 첫 번째 iSpring 계정 생성 시도 (info 로그)
- 2026-04-28T22:55:38Z -- 첫 번째
400 Bad Request에러 발생 - 2026-04-28T22:55:58Z -- mike.lee@unispace.com 첫 번째 iSpring 계정 생성 시도
- 2026-04-28T22:56:16Z -- 마지막 에러 발생 (총 16회)
- 2026-04-29 -- Error Sweeper 감지 및 RCA 수행
Error Log#
iSpring account creation failed: 400 Bad Request
Impact#
- Service:
cupixworks-api - 발생 횟수: 16
- 최초 발생: 2026-04-28T22:55:38.424Z
- 최근 발생: 2026-04-28T22:56:16.438Z
Root Cause Summary#
iSpring Learn API (https://api-learn.ispringlearn.com/user)가 두 Unispace 사용자에 대해 400 Bad Request를 반환했다. IspringOperation.create_user(line 36)의 rescue StandardError 블록이 RestClient::BadRequest의 응답 본문(response body)을 로그에 기록하지 않아 정확한 거부 사유를 알 수 없다. 가장 유력한 원인은 해당 이메일로 iSpring 측에 이미 계정이 존재하여 중복 생성 요청이 거부된 것이다. Cupix::IspringService의 중복 검사(line 10-11)는 로컬 DB의 ispring_users 테이블만 조회하므로, iSpring 측에만 존재하는 계정은 감지하지 못한다. 또한 프론트엔드가 실패 시 동일 요청을 반복 재시도하여 에러가 증폭되었다.
Technical Analysis#
Code Path#
- Entry point -- 프론트엔드에서
POST /api/v1/users/:id/create_ispring_account호출
def create_ispring_account
@model = repository_instance.create_ispring_account
show
end
- Repository layer --
UserRepository#create_ispring_account이 서비스 호출
def create_ispring_account
Cupix::IspringService.create_user_account(@model, @current_team)
@model
end
- Service layer --
Cupix::IspringService.create_user_account에서 로컬 DB 중복 검사 후 iSpring API 호출
existing_user = User.joins(:ispring_user).where(email: user.email).first
raise Cupix::Errors::Parameter.new(code: 'ARG30003', ...) if existing_user.present?
이 검사는 로컬 ispring_users 테이블만 조회한다. iSpring Learn 플랫폼에 직접 생성되었거나 이전에 로컬 레코드가 삭제된 계정은 감지하지 못한다.
- Operation layer (failure point) --
IspringOperation.create_user에서RestClient.post호출 시400 Bad Request발생
begin
url = 'https://api-learn.ispringlearn.com/user'
response = RestClient.post(url, request_body.to_json, headers)
rescue StandardError => e
Cupix::Logger.error("iSpring create user failed: #{e.message}", class: self.name, function: __method__)
raise Cupix::Errors::BadGateway.new(
code: 'BG10001',
reason: "iSpring create user failed: #{e.message}",
message: e.message
)
end
핵심 문제: rescue StandardError가 RestClient::BadRequest (subclass of RestClient::ExceptionWithResponse)를 잡지만, e.response&.body를 로깅하지 않는다. 동일 파일의 update_user_role 메서드(line 70-71)는 RestClient::Exception을 별도로 잡아 응답 본문을 로깅하는데, create_user에는 이 패턴이 누락되어 있다.
rescue RestClient::Exception => e
error_body = e.response&.body || ''
Cupix::Logger.error("iSpring update user role failed: #{e.message}", class: self.name, function: __method__, response: error_body, request_body: request_body)
- 에러 전파 --
Cupix::Errors::BadGateway→ 컨트롤러 → 프론트엔드에 에러 응답. 프론트엔드가 재시도하여 동일 에러 반복.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-api status:error "iSpring account creation failed"
Time: 2026-04-28T21:55:00Z to 2026-04-28T23:00:00Z
service:cupixworks-api "ispring"
Time: 2026-04-28T21:55:00Z to 2026-04-28T23:00:00Z (ascending)
에러 로그 (Cupix::IspringService):
{
"timestamp": "2026-04-28T22:56:16.438Z",
"status": "error",
"message": "iSpring account creation failed: 400 Bad Request",
"class": "Cupix::IspringService",
"function": "create_user_account",
"user": { "id": 6062, "email": "mike.lee@unispace.com" },
"request_id": "08901761-ed2c-42e2-8c37-387d8a2a25b7",
"tenant": "cupix"
}
에러 로그 (IspringOperation):
{
"timestamp": "2026-04-28T22:56:16.438Z",
"status": "error",
"message": "iSpring create user failed: 400 Bad Request",
"class": "IspringOperation",
"function": "create_user",
"request_id": "08901761-ed2c-42e2-8c37-387d8a2a25b7"
}
사용자별 시도 횟수 (info 로그 "creating ispring account for user:" 기준):
| User | Attempts | Time Range |
|---|---|---|
| jason.bussas@unispace.com (user_id: 6063) | 8 | 22:55:37 ~ 22:56:12 |
| mike.lee@unispace.com (user_id: 6062) | 8 | 22:55:58 ~ 22:56:14 |
각 시도마다 IspringOperation에서 400 Bad Request를 받아, 각 사용자당 8개 에러 × 2명 = 16개 에러가 발생했다.
iSpring API 응답 본문 미기록: IspringOperation.create_user의 rescue StandardError 블록이 e.response&.body를 로깅하지 않아 iSpring API가 반환한 구체적 에러 사유(예: "User with this email already exists", "Invalid field value")를 확인할 수 없다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | iSpring 측에 이미 동일 이메일 계정이 존재하여 중복 생성 거부 (400) | 로컬 DB 중복 검사(ispring_service.rb:10-11)를 통과했으나 iSpring API가 400 반환 -- iSpring 측에만 존재하는 계정은 로컬 검사로 감지 불가. 두 사용자 모두 동일 조직(unispace.com) 소속으로 이전에 외부 등록 가능성 있음 |
response body 미로깅으로 iSpring 측 거부 사유 직접 확인 불가 | Confirmed (most likely) |
| H2 | iSpring API 인증 토큰 만료 또는 잘못된 credentials | get_access_token(ispring_operation.rb:133-161)이 별도 에러 없이 성공 -- 인증 실패 시 401/403이 반환되며 "Authentication failed" 로그가 남아야 하나 해당 로그 없음 |
에러가 400이며 인증 관련 에러 로그가 전혀 없음 | Rejected |
| H3 | request body의 필드 값 오류 (firstname/lastname nil 등) | request_body에 user.firstname, user.lastname 사용(ispring_operation.rb:15-16) -- nil이면 iSpring API가 400 반환 가능 |
두 명의 서로 다른 사용자에서 동시 발생하므로 개별 데이터 문제보다는 공통 원인이 유력. 또한 이름 필드가 nil이면 로컬 유효성 검사에서 먼저 걸릴 가능성이 높음 | Rejected |
| H4 | iSpring API 일시적 서비스 장애 | 약 40초 동안 16회 연속 400 반환 | 400은 클라이언트 에러 코드로 서버 장애 시에는 5xx가 반환됨. 400은 요청 자체의 문제를 의미 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/operations/ispring_operation.rb:36-37의rescue StandardError를rescue RestClient::Exception으로 분리하여e.response&.body를 로그에 포함시킨다. 동일 파일의update_user_role(line 70-72) 패턴을 참고.- 이를 통해 iSpring API의 구체적 거부 사유를 확인할 수 있으며, H1 가설의 최종 확인이 가능해진다.
단기 개선 (1주 이내)#
Cupix::IspringService.create_user_account의 중복 검사를 강화한다. 로컬 DB 검사 외에 iSpring API의 사용자 조회 엔드포인트를 호출하여 이메일 기반 중복 여부를 사전 확인하는 로직을 추가한다.- 프론트엔드의 재시도 로직을 검토하여,
400응답(클라이언트 에러)에 대해서는 재시도하지 않도록 변경한다. 400은 재시도해도 결과가 동일하므로 불필요한 에러 증폭을 방지할 수 있다.
장기 개선 (재발 방지)#
- iSpring 연동 API 호출에 대한 통합 에러 핸들링 패턴을 수립한다. 현재
create_user만 response body 로깅이 누락되어 있으며, 향후 추가되는 iSpring 연동에서도 동일 문제가 발생할 수 있다. - iSpring 계정 생성을 비동기(worker)로 전환하여 프론트엔드 재시도에 의한 에러 증폭을 원천 방지하고, 재시도 로직을 서버 측에서 제어할 수 있도록 한다.
Monitoring#
- iSpring API 호출 실패율 모니터링 추가:
service:cupixworks-api status:error "iSpring" | count by @function
- 동일 사용자 반복 실패 감지:
service:cupixworks-api "iSpring account creation failed" | count by @user.email
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (response body 로깅 추가는 1-2줄 변경)