Skip to content

RP Migration Guide — canonical_sub 도입

이 가이드는 이미 logi 와 연동된 RP 가 기존 sub 외래키를 유지한 채 canonical_sub 기반 user 해석으로 안전하게 전환하는 절차를 다룹니다. logi 가 첫 통합 트리거를 production 에 켜기 전에 RP-side 작업이 완료되어 있어야 합니다.

전체 그림

기존 RP DB 는 다음과 같이 logi user 를 참조하고 있습니다:

users (RP own table)
  logi_sub  string  unique   ← logi 의 user.id

마이그레이션 후:

users (그대로 유지)
  logi_sub  string  unique

logi_identity_links (신규)
  primary_user_id  string  ← canonical_sub
  linked_user_id   string  unique  ← 흡수된 sub
  merged_via       string
  occurred_at      datetime

RP 의 모든 user lookup 은 canonical resolver 를 통과합니다:

ruby
def find_canonical_user(sub)
  canonical = LogiIdentityLink.find_by(linked_user_id: sub)&.primary_user_id || sub
  User.find_by(logi_sub: canonical)
end

5단계 deploy 순서

logi 와 협조하는 joint deploy 입니다. 각 단계는 다음 단계 전에 24h soak 권장.

Phase 1 — 스키마 + 비활성 코드

  • logi_identity_links 테이블 추가 + 인덱스
  • canonical resolver 코드 추가 (사용 안 함, feature flag enforce_canonical_resolution = false)
  • existing user lookup path 는 그대로 logi_sub 직접 사용

Phase 2 — webhook receiver 활성화

  • /webhooks/logi endpoint 활성화 → user.merged 수신 시 logi_identity_links row INSERT
  • HMAC 검증 + event_id dedupe 구현
  • 이 시점에는 logi 가 아직 통합을 발화하지 않으므로 row 가 들어오지 않음 — receiver 의 코드 경로 자체가 dry-run

Phase 3 — polling reconciler 활성화

  • 분 단위 GET /api/v1/events?since=<cursor> polling job
  • cursor 를 자기 DB 에 저장 (transaction 안에서 cursor advance + event apply)

Phase 4 — login-time resolver 활성화

  • BearerAuthentication (또는 동등) concern 이 매 token 검증 시 canonical_sub 를 보고 logi_identity_links 와 비교, 차이가 있으면 즉시 row insert
  • 이는 webhook/polling 누락의 마지막 안전망

Phase 5 — enforce_canonical_resolution = true flip

  • 모든 user lookup 이 canonical resolver 를 통과하도록 변경
  • 기존 User.find_by(logi_sub:) 직접 호출이 코드베이스에 남아 있는지 grep 으로 확인 (bin/rails runner + custom audit 권장)

flip 후에야 logi 가 통합 트리거를 production 에서 켭니다.

Rails 기준 코드 패턴

Concern 예시

ruby
# app/controllers/concerns/logi_canonical_resolution.rb
module LogiCanonicalResolution
  extend ActiveSupport::Concern

  private

  def resolve_logi_user(sub:, canonical_sub:, linked_subs: [])
    # 1) login-time fallback: webhook/polling 누락 시 여기서 보정
    if canonical_sub != sub && !LogiIdentityLink.exists?(linked_user_id: sub)
      LogiIdentityLink.create!(
        primary_user_id: canonical_sub,
        linked_user_id:  sub,
        merged_via:      "login_time_fallback",
        occurred_at:     Time.current
      )
    end

    # 2) 항상 canonical 으로 lookup
    User.find_by(logi_sub: canonical_sub)
  end
end

Pundit policy 주의

Pundit policy 가 record.user_id == current_user.id 같은 직접 비교를 한다면, 통합 후 같은 사람이지만 두 row 가 다른 logi_sub 를 가진 케이스에서 권한이 빠질 수 있습니다. 권장 패턴:

ruby
class ApplicationPolicy
  def same_canonical?(record_user)
    LogiCanonical.same_owner?(current_user, record_user)
  end
end

LogiCanonical.same_owner? 는 두 user 의 logi_sub 를 각각 canonical 으로 resolve 한 뒤 비교. 자세한 patterns 는 Rails 통합 가이드 참고.

Merge Reconciler

webhook 또는 polling 으로 user.merged 가 들어오면:

ruby
class MergeReconciler
  def apply(event)
    survivor = event[:data][:survivor_canonical_sub]
    merged   = event[:data][:merged_sub]

    LogiIdentityLink.create_or_find_by!(linked_user_id: merged) do |link|
      link.primary_user_id = survivor
      link.merged_via      = event[:data][:merged_via]
      link.occurred_at     = Time.parse(event[:data][:triggered_at])
    end

    # 도메인 데이터의 logical merge 는 RP 정책에 따라.
    # 예: merged 의 active session 강제 종료, 두 user 의 주문 history 합치기, etc.
  end
end

create_or_find_by! 가 idempotent 를 보장하므로 같은 webhook 이 두 번 와도 안전.

무엇이 안 바뀌나

  • 기존 logi_sub 외래키는 그대로 유지. row 의 logi_sub 가 마이그레이션으로 바뀌지 않습니다.
  • 새로 가입한 user 의 logi_sub 도 그대로. sub 은 OIDC 상 stable.
  • RP 의 외부 API 응답 — user 식별자를 외부에 노출했다면 그 값도 그대로.

무엇이 바뀌나

  • 통합이 일어난 사용자가 새 토큰을 받을 때 canonical_subsub 과 다름.
  • linked_subs 가 채워질 수 있음.
  • 모든 RP-side user lookup 이 canonical resolver 한 hop 을 거쳐야 함.

검증 체크리스트

flip 전:

  • [ ] LogiIdentityLink 테이블 + UNIQUE(linked_user_id) 인덱스
  • [ ] HMAC + event_id dedupe 구현된 webhook receiver
  • [ ] cursor 기반 polling reconciler (분 단위 권장)
  • [ ] login-time canonical resolver
  • [ ] User.find_by(logi_sub:) 직접 호출 모두 canonical resolver 경유로 교체
  • [ ] Pundit policy / scope 가 canonical 비교 사용
  • [ ] feature flag enforce_canonical_resolution = true 로 flip 준비

logi 운영자에게 flip 완료를 통보한 뒤 logi 가 T1 트리거를 production 에 enable 합니다.

관련 문서

MIT License · Identity가 제품의 신뢰를 만듭니다.