notification-service returns 403 — downstream availability issue
RCA: Recipe creation failed
Error Log#
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-6—FacilityPermission이벤트 수신 시full_permission_enabled호출
# 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호출:
# 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를 순차적으로 생성 시도:
# 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-21—RestClient.post가 notification-service에 HTTP POST를 보내고, 403 응답을 수신:
# 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로 로깅:
# 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 쿼리:
service:cupixworks-api status:error @class:"Cupix::NotificationService" @function:create_user_recipe
에러 로그 (raw 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):
{
"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"):
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_completed와 facility_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 로직 추가를 고려.
장기 개선 (재발 방지)#
UserRecipeGenerator가FacilityPermission이벤트마다 동일 recipe 3개를 무조건 생성 시도하는 구조.full_permission_enabled이벤트가 짧은 시간 내 여러 번 발생하면 불필요한 중복 요청이 발생. notification-service 호출 전에 tesla 측에서 캐시 또는 조건 검사를 통해 이미 생성된 recipe에 대한 중복 요청을 방지하는 것을 권장.- notification-service의
/api/recipes/v2엔드포인트가 일시적으로 403을 반환하는 원인 조사 필요 — uncertain, notification-service 코드 직접 확인 필요.
Monitoring#
- notification-service의 recipe creation 403 응답 모니터링:
service:cupixworks-api status:error @class:"Cupix::NotificationService" @function:create_user_recipe
- 일시적 인증 실패 패턴 감지를 위해 notification-service 측 401/403 응답률 모니터링:
service:notification-service status:error (403 OR "Forbidden")
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial