ES /docs

iSpring API returns 400 — missing error details in logs

RCA: iSpring create user failed: 400 Bad Request

Overview#

What Happened#

2026-04-28 22:55~22:56 UTC (약 38초간) cupixworks-api 서비스의 ap-southeast-2 리전에서 iSpring LMS 사용자 생성 API 호출이 16회 연속 400 Bad Request로 실패했다. 영향 받은 사용자는 Unispace 조직의 2명(mike.lee@unispace.com, jason.bussas@unispace.com)이며, 동일 사용자에 대해 약 2초 간격으로 반복 호출되었다.

Quick Facts#

Field Value
exception.class RestClient::BadRequest (caught as StandardError)
exception.message iSpring create user failed: 400 Bad Request
top_frame IspringOperation.create_user
env production, ap-southeast-2
deploy 20260428T0823Z0, 20260428T0241Z0

Affected Teams#

Team / Domain Error Count Impact
Unispace (@unispace.com) 16 iSpring LMS 계정 생성 실패 — 2명의 사용자가 학습 플랫폼에 접근 불가

Timeline#

  1. 2026-04-28T22:55:38Z — 최초 에러 발생. mike.lee@unispace.comjason.bussas@unispace.com에 대한 iSpring 계정 생성 시도 시작
  2. 2026-04-28T22:56:16Z — 마지막 에러 발생. 38초간 총 16회 실패 (성공 0건)
  3. 2026-04-29 — Error Sweeper 감지 및 RCA 분석

Error Log#

Datadog Logs

text
iSpring create user 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 (POST https://api-learn.ispringlearn.com/user)가 요청 본문의 특정 필드 값을 거부하여 400 Bad Request를 반환했다. 가장 유력한 원인은 해당 이메일 주소로 이미 iSpring 측에 사용자가 존재하는 경우이다. 현재 코드의 중복 검사(ARG30003)는 로컬 DB(ispring_users 테이블)만 확인하고 iSpring API 측 상태는 확인하지 않기 때문에, iSpring에 이미 존재하지만 로컬 DB에는 기록이 없는 사용자에 대해 생성 요청이 반복 시도된다. 또한 IspringOperation.create_user의 에러 핸들링이 StandardError로 포괄적으로 catch하면서 iSpring API 응답 본문을 로깅하지 않아, 정확한 거부 사유를 파악할 수 없다.

Technical Analysis#

Code Path#

  • Entry point: Api::V1::UsersController#create_ispring_accountapp/controllers/api/v1/users_controller.rb:96
  • Repository: UserRepository#create_ispring_accountapp/repositories/user_repository.rb:271
  • Service: Cupix::IspringService.create_user_accountapp/services/cupix/ispring_service.rb:3
  • Operation (HTTP call): IspringOperation.create_userapp/operations/ispring_operation.rb:2
  • Failure point: app/operations/ispring_operation.rb:36rescue StandardError => e

1. 로컬 DB 중복 검사 (서비스 레이어)

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', reason: 'User already has iSpring account', message: "User with email '#{user.email}' already has an iSpring account") if existing_user.present?

이 검사는 로컬 ispring_users 테이블에만 의존한다. iSpring API 측에 이미 사용자가 존재하지만 로컬 DB에 레코드가 없는 경우(예: 직접 iSpring 콘솔에서 생성, 이전 생성 시 DB 저장 실패, 다른 환경에서 생성)에는 이 검사를 통과하여 API 호출로 진행된다.

2. iSpring API 호출 및 에러 핸들링

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

핵심 문제: RestClient::BadRequest (HTTP 400)를 StandardError로 포괄 catch하면서 e.response.body를 로깅하지 않는다. iSpring API가 반환하는 상세 에러 메시지(중복 이메일, 유효하지 않은 필드 등)가 모두 유실된다.

3. 같은 파일의 update_user_role 메서드와 비교

app/operations/ispring_operation.rb:70-72ruby
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)

update_user_rolecreate_department 메서드는 RestClient::Exception을 먼저 catch하여 응답 본문(e.response.body)과 요청 본문(request_body)을 모두 로깅한다. create_user만 이 패턴을 따르지 않는 불일치가 존재한다.

4. 요청 본문 구조

app/operations/ispring_operation.rb:9-24ruby
request_body = {
  departmentId: department_id,
  password: temporary_password,
  fields: {
    login: user.email,
    email: user.email,
    first_name: user.firstname,
    last_name: user.lastname
  },
  sendLoginEmail: true
}.merge(
  user.has_admin_permission?(user.team) ? {
    role: 'department_administrator',
    manageableDepartmentIds: [department_id]
  } : {}
)

loginemail 모두 user.email을 사용한다. iSpring API에서 login 또는 email이 이미 등록된 경우 400 Bad Request를 반환할 수 있다.

Log Evidence#

Datadog 검색 쿼리:

text
service:cupixworks-api status:error @environment:production "iSpring create user failed"

에러 로그 패턴 (요청 당 3건의 로그):

요청 ID 08901761-ed2c-42e2-8c37-387d8a2a25b7 기준:

text
[2026-04-28T22:56:14.437Z] [INFO] Cupix::IspringService#create_user_account
  "creating ispring account for user: mike.lee@unispace.com" (user_id=6062)

[2026-04-28T22:56:16.438Z] [ERROR] IspringOperation#create_user
  "iSpring create user failed: 400 Bad Request"

[2026-04-28T22:56:16.438Z] [ERROR] Cupix::IspringService#create_user_account
  "iSpring account creation failed: 400 Bad Request" (error.msg="400 Bad Request", user_id=6062)

영향 받은 사용자:

User ID Email 시도 횟수
6062 mike.lee@unispace.com ~10
6063 jason.bussas@unispace.com ~6

주요 관찰:

  • 100% 실패율: 해당 시간대에 iSpring 사용자 생성 성공 건수가 0건
  • 반복 재시도: 동일 사용자에 대해 약 2초 간격으로 반복 호출 — 클라이언트 측 자동 재시도 또는 사용자의 반복 클릭 추정
  • 2개 호스트에서 동시 발생: ip-10-1-83-50ip-10-1-16-74 모두에서 에러 발생
  • 응답 본문 미기록: iSpring API의 상세 거부 사유를 확인할 수 없음

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 iSpring 측에 이미 해당 이메일로 사용자가 존재하여 중복 생성 거부 로컬 DB 중복 검사(ARG30003)가 iSpring API 상태를 확인하지 않음 (ispring_service.rb:10-11). 두 사용자 모두 Unispace 조직으로 다른 경로로 이미 등록되었을 가능성. 100% 실패율이 일관됨 응답 본문이 로깅되지 않아 iSpring 측 에러 메시지 직접 확인 불가 Confirmed (most likely)
H2 iSpring API 인증 토큰 만료 또는 권한 부족 인증 실패 시 401 Unauthorized 또는 403 Forbidden이 반환되어야 하지만, 실제로는 400 Bad Request가 반환됨. get_access_token에서 에러가 발생했다면 별도의 에러 메시지가 기록되었을 것 Rejected
H3 요청 본문의 필수 필드 누락 또는 잘못된 형식 (예: firstname/lastname가 nil) user.firstname 또는 user.lastname가 nil인 경우 iSpring API가 400을 반환할 수 있음 Unispace 조직의 사용자가 이름/성이 없을 가능성은 낮음. 또한 두 사용자 모두 동일하게 실패하는 것은 필드 누락보다 구조적 원인을 시사 Inconclusive — 응답 본문 로깅 부재로 확인 불가
H4 iSpring API 일시적 장애 또는 rate limit 38초간 16회 호출은 rate limit 트리거 가능 Rate limit 초과 시 보통 429 Too Many Requests가 반환됨. 또한 첫 번째 요청부터 실패하므로 rate limit이 근본 원인은 아님 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • app/operations/ispring_operation.rb:36-42create_user 메서드의 에러 핸들링을 update_user_role (line 70-72) 및 create_department (line 110-112)와 동일한 패턴으로 수정: RestClient::Exception을 먼저 catch하여 e.response.body를 로깅에 포함
  • 이를 통해 iSpring API가 반환하는 정확한 거부 사유(중복 이메일, 유효하지 않은 필드 등)를 파악 가능

단기 개선 (1주 이내)#

  • app/services/cupix/ispring_service.rb:10-11ARG30003 중복 검사를 강화하여, 로컬 DB뿐 아니라 iSpring API의 사용자 조회 엔드포인트를 호출하여 이미 등록된 사용자인지 사전 확인하는 로직 추가 검토
  • 클라이언트(프론트엔드) 측에서 생성 실패 시 반복 재시도하는 로직이 있는지 확인하여, 불필요한 반복 호출 방지

장기 개선 (재발 방지)#

  • iSpring 연동 로직 전반에 걸쳐 에러 핸들링 일관성 확보: 모든 external API 호출에서 RestClient::Exception을 별도로 catch하고 응답 본문 + 요청 본문을 로깅하는 패턴 통일
  • iSpring 사용자 동기화 메커니즘 도입 검토: 로컬 DB와 iSpring 측 상태 불일치를 주기적으로 감지하고 복구하는 배치 작업

Monitoring#

  • iSpring API 호출 실패율 모니터링 추가:
text
service:cupixworks-api "iSpring create user failed" status:error
  • 동일 사용자에 대한 반복 실패 알림 (5분 내 동일 user_id로 3회 이상 실패 시):
text
service:cupixworks-api "iSpring account creation failed" @usr.id:* status:error

Risk Assessment#

  • Risk level: medium — 사용자 onboarding이 차단되지만, 핵심 서비스(캡처, 3D 처리)에는 영향 없음
  • 예상 복잡도: trivial — create_user 메서드의 rescue 절에 RestClient::Exception catch 추가는 기존 코드 패턴(update_user_role)을 따르면 됨