InvitationOperation synchronous Cognito calls blocking request thread
RCA: Api::V1::TeamsController#invitation Latency
Overview#
What Happened#
2026-05-26 03:26~08:27 UTC 사이에 cupixworks-api의 TeamsController#invitation 엔드포인트에서 평균 5172ms, 최대 9275ms의 비정상적 latency가 5개 리전(ca-central-1, ap-southeast-1, ap-northeast-1, ap-southeast-2, us-west-2)에서 8건 발생했다. 원인은 초대 플로우에서 새 사용자 생성 시 AWS Cognito API 호출, Segment Analytics identify, Kinesis event publish가 HTTP 요청 라이프사이클 내에서 동기적으로 순차 실행되기 때문이다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::TeamsController#invitation |
| top_frame | app/operations/invitation_operation.rb:61-94 |
| runtime | Ruby on Rails (cupixworks-api) |
| env | production (multi-region) |
Timeline#
- 2026-05-26T03:26:44Z — 최초 latency 감지 (ap-southeast-2, 1541ms)
- 2026-05-26T03:30~03:33Z — liam.hong@cupix.com이 5개 리전에 동시 초대 수행 (1282~2740ms)
- 2026-05-26T08:27:57Z — 최대 latency 발생 (us-west-2, 9274ms, 2명 동시 초대)
- 2026-05-27 — RCA 분석 완료
Error Log#
{
"resource_name": "Api::V1::TeamsController#invitation",
"service": "cupixworks-api",
"occurrences": 6,
"avg_ms": 1837,
"max_ms": 2742,
"sample_trace_id": "1536084496537550476"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 8
- 최초 발생: 2026-05-26T03:26:44.534Z
- 최근 발생: 2026-05-26T08:27:57.758Z
- 영향 범위: 모든 프로덕션 리전의 팀 초대 기능. 사용자가 여러 명을 초대할 때 응답 시간이 초대 인원 수에 비례하여 증가 (1명당 ~2-3초).
Root Cause Summary#
InvitationOperation.invite_people가 초대 이메일 목록을 순차 루프로 처리하면서, 각 신규 사용자 생성 시 after_create 콜백에서 AWS Cognito API 호출 (create_cognito_user)과 Segment Analytics identify (segment_user_event), 그리고 Kinesis put_records (EventService.publish_event)가 모두 HTTP 요청 내에서 동기 실행된다. 2명을 초대하면 Cognito 왕복 2회(각 ~2초) + Segment 2회 + Kinesis 2회가 순차로 누적되어 9초 이상 소요된다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/teams_controller.rb:40-44
def invitation
InvitationOperation.invite_people(params, current_user, @current_team)
render_json 200, { user_emails: params[:user_emails] }
end
- 핵심 루프:
app/operations/invitation_operation.rb:61-94
invitees = emails.map do |email|
begin
invitee = User.find_by(team: current_team, email: email)
if invitee.present?
# ... update existing user state ...
invitee.save!
else
user_factory = UserFactory.new(current_user: current_user, current_team: current_team, skip_permission_check: params[:skip_permission_check])
invitee = user_factory.create!({
team: current_team,
email: email
})
end
end
send_invitation_mail({ user_email: email, redirect_url: redirect_url }, current_user, current_team, invitee)
invitee
end
각 이메일에 대해 순차적으로 User.find_by → user_factory.create! → send_invitation_mail 수행. 신규 사용자일 경우 create!에서 User.new + save! → after_create 콜백 체인 발동.
- Cognito 동기 호출:
lib/cupix/aws/cognito/user.rb:10,54-72
after_create :create_cognito_user, unless: :skip_cognito_user_creation
def create_cognito_user
if Cupix::Aws::Cognito.user_exists?(self.email)
cognito_user = Cupix::Aws::Cognito.get_user_by_email(self.email)
self.update!(cognito_user_id: cognito_user.attributes.find { |attr| attr.name == 'sub' }.value)
else
cognito_user_creation_response = Cupix::Aws::Cognito.create_user!(self)
self.update!(cognito_user_id: cognito_user_creation_response.user.attributes.find { |attr| attr.name == 'sub' }.value)
end
end
after_create 콜백으로 등록된 이 메서드는 AWS Cognito User Pool에 HTTP 요청을 보내 사용자를 생성한다. 리전 간 latency에 따라 1회 호출당 1~3초 소요.
- Segment identify 동기 호출:
app/models/concerns/eventable/user.rb:8,12-29
after_create :segment_user_event, if: :send_segment_event?
def segment_user_event
::Analytics.identify(
user_id: crn,
traits: {
email: email,
firstname: firstname,
lastname: lastname,
teamId: team_id,
# ...
}
)
end
- Kinesis event publish 동기 호출:
lib/cupix/event_service.rb:21-54
response = Cupix::Aws::Kinesis.put_records!({ stream_name: stream_name, records: records })
UserFactory.create!에서 track_team_event! 플래그를 설정하여 after_create 시 Kinesis에 이벤트를 동기 publish한다.
- Failure point: 단일 failure point은 아님. 누적 latency 문제로, N명 초대 시
N × (Cognito + Segment + Kinesis + DB)시간이 동기적으로 합산됨.
Log Evidence#
Datadog 검색 쿼리:
service:cupixworks-api TeamsController invitation
Time: 2026-05-26T02:26:00Z to 2026-05-26T09:00:00Z
가장 느린 요청 (9274ms, request_id: 7e09729c-0366-4a9c-857e-9d4a75d04554):
08:28:05.564Z - Cognito.create_cognito_user: "Creating user in Cognito: osaulnier-boileau@stl.laval.qc.ca"
08:28:05.564Z - Cognito.create_cognito_user: "User created in Cognito: osaulnier-boileau@stl.laval.qc.ca"
08:28:05.564Z - User.segment_user_event: "Segment event sent for User 49610"
08:28:05.564Z - Cupix::EventService.publish_event: "Published event - failed_record_count: 0 / 1"
08:28:07.565Z - Cognito.create_cognito_user: "Creating user in Cognito: mdavid@stl.laval.qc.ca"
08:28:07.565Z - Cognito.create_cognito_user: "User created in Cognito: mdavid@stl.laval.qc.ca"
08:28:07.565Z - User.segment_user_event: "Segment event sent for User 49611"
08:28:07.565Z - Cupix::EventService.publish_event: "Published event"
08:28:07.518Z - [200] POST /api/v1/teams/invitation, duration: 9274.56ms, db: 2119.73ms
2명 초대 시 Cognito 생성이 순차로 2회 실행: 첫 번째 사용자 생성 후 ~2초 뒤 두 번째 사용자 생성 시작. DB 시간(2119ms)을 제외한 ~7155ms가 외부 API 호출(Cognito + Segment + Kinesis)에 소비됨.
1명 초대 시 latency 비교 (같은 시간대):
03:32:10.230Z - ap-northeast-1, 1명 초대, duration: 2740ms, db: 37ms
03:33:00.776Z - ca-central-1, 1명 초대, duration: 2010ms, db: 29ms
03:32:45.915Z - ap-southeast-1, 1명 초대, duration: 1752ms, db: 41ms
1명 초대 시에도 ~1700-2740ms 소요 — Cognito API 단일 호출의 cross-region latency가 주요 원인.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 동기적 Cognito API 호출이 주 latency 원인 | 로그에서 create_cognito_user 시작→완료 간 ~2초 확인, 2명 초대 시 순차 실행으로 ~4초 |
— | Confirmed |
| H2 | N+1 DB 쿼리로 인한 latency | 루프 내 find_by + save! per email (N+1 패턴) |
DB 시간은 최대 2119ms이나 전체 9274ms 중 23%에 불과. 1명 초대 시 DB 29-41ms로 미미 | Rejected (부분 기여) |
| H3 | 이메일 발송(SendGrid)이 동기 실행 | — | TeamInvitationMailerWorker.perform_async 사용 확인 (Sidekiq 비동기), Datadog에서 worker 로그 미발견 |
Rejected |
| H4 | Segment/Kinesis 동기 호출 기여 | 로그 타임라인에서 Segment identify + EventService publish가 요청 내 실행 확인 | 개별 호출당 latency는 Cognito 대비 작음 (수백ms 수준) | Confirmed (보조 원인) |
Fix Recommendation#
즉시 조치 (Critical)#
lib/cupix/aws/cognito/user.rb:10—after_create :create_cognito_user콜백을 비동기 Sidekiq worker로 전환. 초대 플로우에서cognito_user_id가 즉시 필요하지 않으므로 (invitation token 기반 인증), background job으로 이동 가능.app/operations/invitation_operation.rb:61-94— 루프 내 순차 처리 대신 bulk 사용자 생성 후 Cognito 호출을 별도 worker에서 일괄 처리하도록 분리.
단기 개선 (1주 이내)#
InvitationOperation.invite_people에서emails.map루프를ActiveRecord::Base.transaction블록 내에서 사용자만 먼저 생성하고, Cognito 등록은 별도CognitoUserCreationWorker.perform_bulk호출로 분리.app/models/concerns/eventable/user.rb:8—segment_user_event을after_commit+ async로 변경하거나, invitation 컨텍스트에서는 skip하도록 조건 추가.
장기 개선 (재발 방지)#
- 초대 API를 비동기 패턴으로 재설계: API는 즉시 202 Accepted 반환 후, 실제 사용자 생성/Cognito 등록/이벤트 발행은 background pipeline에서 처리. 클라이언트에 WebSocket/polling으로 완료 통지.
- Cognito 호출에 circuit breaker 패턴 적용 (latency spike 시 graceful degradation).
Monitoring#
- 추가할 메트릭:
TeamsController#invitationp95/p99 duration 알림 (threshold: 3000ms) - Cognito API 호출 latency 별도 tracking
service:cupixworks-api resource_name:"Api::V1::TeamsController#invitation" @duration:>3000ms env:production
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard — Cognito 콜백을 async worker로 이동하는 것은 기존
skip_cognito_user_creation!메커니즘이 이미 존재하므로 패턴이 확립되어 있음. 다만,cognito_user_id가 후속 로직에서 참조되는 경우 의존성 확인 필요.