ES /docs

notification-service returns 403 — downstream availability issue

RCA: Recipe creation failed

Error Log#

Datadog Logs

text
Recipe creation failed

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 1
  • 최초 발생: 2026-04-09T01:45:36.495Z
  • 최근 발생: 2026-04-09T01:45:36.495Z

Root Cause Summary#

Cupix::NotificationService#create_user_recipe 메서드가 notification-service의 /api/recipes/v2 엔드포인트에 record_preview_ready recipe 생성을 요청했으나, notification-service가 {"message":"Forbidden"} (HTTP 403)을 반환했습니다. 같은 요청(request_id: e82c384f) 내에서 동일 사용자(user_id: 14807)의 다른 두 recipe(record_processing_completed, facility_new_project)는 ARG12001 (이미 존재)로 정상 처리되었습니다. 이는 동일 api_token으로 인증했음에도 record_preview_ready 요청만 403을 받은 것으로, notification-service 측의 일시적 인증/권한 검증 실패로 판단됩니다. notification-service 로그에서는 같은 시간대에 해당 recipe 생성을 정상 처리한 기록이 있어, race condition 또는 일시적 서비스 오류가 원인으로 보입니다.

Technical Analysis#

Code Path#

  • Entry point: lib/cupix/pub_sub/subscribers/user_recipe_generator.rb:5-6FacilityPermission 이벤트 수신 시 full_permission_enabled 호출
ruby
# lib/cupix/pub_sub/subscribers/user_recipe_generator.rb:5-6
def full_permission_enabled(event)
  send("_create_recipes_for_#{self.namespace}", event.payload[:model])
end
  • _create_recipes_for_facility_permission 에서 사용자와 facility 정보를 추출하여 _create_recipes 호출:
ruby
# lib/cupix/pub_sub/subscribers/user_recipe_generator.rb:24-31
def _create_recipes_for_facility_permission(model)
  return false unless model.accessor_type == ::User.name

  user = model.accessor
  team_id = model.facility.team_id
  facility_key = model.facility.key

  _create_recipes(user: user, team_id: team_id, facility_key: facility_key)
end
  • 3개의 recipe를 순차적으로 생성 시도:
ruby
# lib/cupix/pub_sub/subscribers/user_recipe_generator.rb:34-42
def _create_recipes(opts = {})
  %w[
    record_preview_ready
    record_processing_completed
    facility_new_project
  ].each do |recipe_name|
    Cupix::NotificationService.new(user: opts[:user]).create_user_recipe(recipe_name, team_id: opts[:team_id], facility_key: opts[:facility_key])
  end
end
  • Failure point: lib/cupix/notification_service.rb:15-21RestClient.post가 notification-service에 HTTP POST를 보내고, 403 응답을 수신:
ruby
# lib/cupix/notification_service.rb:15-21
response = RestClient.post(
  "#{@service_url}/api/recipes/v2",
  params.to_json,
  {
    content_type: :json,
    'x-cupix-auth': @user.api_token
  }
)
  • Error handling: RestClient::ExceptionWithResponse 를 catch하여 response body에 ARG12001이 포함되면 info로 처리, 아니면 error로 로깅:
ruby
# lib/cupix/notification_service.rb:25-43
rescue RestClient::ExceptionWithResponse => e
  response_body = e.response.body

  # ARG12001: recipe already exists - 정상 동작 (idempotent)
  if response_body.include?('ARG12001')
    Cupix::Logger.info('Recipe already exists, skipping creation',
                       class: self.class.name,
                       function: __method__,
                       recipe_name: recipe_name,
                       facility_key: facility_key,
                       response_code: 'ARG12001')
  else
    Cupix::Logger.error('Recipe creation failed',
                        class: self.class.name,
                        function: __method__,
                        recipe_name: recipe_name,
                        facility_key: facility_key,
                        error_response: response_body)
  end
  • 기대 동작: notification-service가 recipe가 이미 존재하면 ARG12001 코드를 반환하고, tesla 측에서 info로 처리
  • 실제 동작: notification-service가 {"message":"Forbidden"} (403)을 반환하여 error로 로깅됨

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-api status:error @class:"Cupix::NotificationService" @function:create_user_recipe

에러 로그 (raw JSON):

json
{
  "timestamp": "2026-04-09T01:45:36.495Z",
  "status": "error",
  "message": "Recipe creation failed",
  "class": "Cupix::NotificationService",
  "function": "create_user_recipe",
  "error_response": "{\"message\":\"Forbidden\"}",
  "recipe_name": "record_preview_ready",
  "facility_key": "pwwaqh",
  "request_id": "e82c384f-a891-4d31-b6bf-3532039f5da8"
}

같은 request_id 내 다른 recipe 결과 (쿼리: service:cupixworks-api @request_id:e82c384f-a891-4d31-b6bf-3532039f5da8 @function:create_user_recipe):

시간 (UTC) recipe_name 결과
01:45:36.495Z record_preview_ready error{"message":"Forbidden"}
01:45:36.496Z record_processing_completed info — ARG12001 (already exists)
01:45:36.497Z facility_new_project info — ARG12001 (already exists)

이전 요청(같은 사용자, 같은 facility)에서 3개 recipe 모두 성공적으로 생성됨 (쿼리: service:cupixworks-api "pwwaqh", 10:45:32-33 KST):

json
{
  "timestamp": "2026-04-09 10:45:33 KST",
  "status": "info",
  "message": "recipe created: {\"result\":{\"data\":{\"id\":\"23a99a98-3b14-4297-9807-a0f4492bbd0d\",\"kind\":\"user_recipe\",\"name\":\"record_preview_ready\",\"user_id\":\"14807\",\"team_id\":\"780\",\"facility_key\":\"pwwaqh\"}}}",
  "class": "Cupix::NotificationService",
  "function": "create_user_recipe"
}

notification-service 측에서도 같은 시간대에 해당 recipe 생성을 처리한 기록 존재 (쿼리: service:notification-service "record_preview_ready" "pwwaqh"):

text
10:45:32 - RecipeRepository::create | creating recipe: id=23a99a98 (첫 번째 — 성공)
10:45:35 - RecipeRepository::create | creating recipe: id=7fab34f2 (두 번째 — notification-service에서는 생성 로그 있음)

notification-service에서 두 번째 record_preview_ready 생성 시도가 처리되었으나, tesla 측에서는 Forbidden을 수신. 이는 notification-service가 recipe를 생성하기 전 또는 생성 중 인증 검증 단계에서 403을 반환한 것으로 보임 — uncertain, notification-service의 인증 로직 내부를 직접 확인하지 못했음.

record_processing_completedfacility_new_project는 동일 api_token으로 요청했는데 ARG12001을 받았으므로, 인증 자체는 유효했음. record_preview_ready만 403을 받은 것은 요청 순서(첫 번째로 전송)와 관련된 일시적 이슈일 가능성이 높음.

지난 7일간 검색 결과 이 에러는 이 1건만 발생 (쿼리: service:cupixworks-api status:error "Recipe creation failed", 2026-04-02 ~ 2026-04-09).

Fix Recommendation#

즉시 조치 (Critical)#

  • 즉시 조치 필요 없음. 단발성(1회) 이벤트이며, 해당 recipe는 이전 요청에서 이미 성공적으로 생성되었으므로 사용자 영향 없음.

단기 개선 (1주 이내)#

  • lib/cupix/notification_service.rb:37-42: 에러 로깅 시 HTTP status code도 함께 기록하도록 개선. 현재는 error_response (body)만 로깅하고 있어 403인지 500인지 구분이 어려움. e.response.code를 추가로 로깅하면 원인 분석이 용이해짐.
  • lib/cupix/notification_service.rb:25-43: 403 응답에 대해 별도 처리 추가 검토. Forbidden은 인증/권한 문제이므로 retry가 무의미하지만, 일시적 오류일 수 있으므로 warn 레벨로 분리하고 recipe가 이미 존재하는지 확인하는 fallback 로직 추가를 고려.

장기 개선 (재발 방지)#

  • UserRecipeGeneratorFacilityPermission 이벤트마다 동일 recipe 3개를 무조건 생성 시도하는 구조. full_permission_enabled 이벤트가 짧은 시간 내 여러 번 발생하면 불필요한 중복 요청이 발생. notification-service 호출 전에 tesla 측에서 캐시 또는 조건 검사를 통해 이미 생성된 recipe에 대한 중복 요청을 방지하는 것을 권장.
  • notification-service의 /api/recipes/v2 엔드포인트가 일시적으로 403을 반환하는 원인 조사 필요 — uncertain, notification-service 코드 직접 확인 필요.

Monitoring#

  • notification-service의 recipe creation 403 응답 모니터링:
text
service:cupixworks-api status:error @class:"Cupix::NotificationService" @function:create_user_recipe
  • 일시적 인증 실패 패턴 감지를 위해 notification-service 측 401/403 응답률 모니터링:
text
service:notification-service status:error (403 OR "Forbidden")

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial