ES /docs

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#

  1. 2026-04-28T22:55:37Z -- jason.bussas@unispace.com 첫 번째 iSpring 계정 생성 시도 (info 로그)
  2. 2026-04-28T22:55:38Z -- 첫 번째 400 Bad Request 에러 발생
  3. 2026-04-28T22:55:58Z -- mike.lee@unispace.com 첫 번째 iSpring 계정 생성 시도
  4. 2026-04-28T22:56:16Z -- 마지막 에러 발생 (총 16회)
  5. 2026-04-29 -- Error Sweeper 감지 및 RCA 수행

Error Log#

Datadog Logs

text
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#

  1. Entry point -- 프론트엔드에서 POST /api/v1/users/:id/create_ispring_account 호출
app/controllers/api/v1/users_controller.rb:96-100ruby
def create_ispring_account
  @model = repository_instance.create_ispring_account

  show
end
  1. Repository layer -- UserRepository#create_ispring_account이 서비스 호출
app/repositories/user_repository.rb:271-275ruby
def create_ispring_account
  Cupix::IspringService.create_user_account(@model, @current_team)

  @model
end
  1. Service layer -- Cupix::IspringService.create_user_account에서 로컬 DB 중복 검사 후 iSpring API 호출
app/services/cupix/ispring_service.rb:10-11ruby
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 플랫폼에 직접 생성되었거나 이전에 로컬 레코드가 삭제된 계정은 감지하지 못한다.

  1. Operation layer (failure point) -- IspringOperation.create_user에서 RestClient.post 호출 시 400 Bad Request 발생
app/operations/ispring_operation.rb:33-43ruby
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 StandardErrorRestClient::BadRequest (subclass of RestClient::ExceptionWithResponse)를 잡지만, e.response&.body를 로깅하지 않는다. 동일 파일의 update_user_role 메서드(line 70-71)는 RestClient::Exception을 별도로 잡아 응답 본문을 로깅하는데, create_user에는 이 패턴이 누락되어 있다.

app/operations/ispring_operation.rb:70-72 (update_user_role -- 올바른 패턴)ruby
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)
  1. 에러 전파 -- Cupix::Errors::BadGateway → 컨트롤러 → 프론트엔드에 에러 응답. 프론트엔드가 재시도하여 동일 에러 반복.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-api status:error "iSpring account creation failed"
Time: 2026-04-28T21:55:00Z to 2026-04-28T23:00:00Z
text
service:cupixworks-api "ispring"
Time: 2026-04-28T21:55:00Z to 2026-04-28T23:00:00Z (ascending)

에러 로그 (Cupix::IspringService):

json
{
  "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):

json
{
  "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_userrescue 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_bodyuser.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-37rescue StandardErrorrescue 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 호출 실패율 모니터링 추가:
text
service:cupixworks-api status:error "iSpring" | count by @function
  • 동일 사용자 반복 실패 감지:
text
service:cupixworks-api "iSpring account creation failed" | count by @user.email

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (response body 로깅 추가는 1-2줄 변경)