jongkwan.dev
프로젝트 목록으로

MeetX(가제) 전화번호 기반 소셜 매칭

전화번호 상호 등록 매칭 + 토큰 경제 기반 소셜 매칭 서비스

2025.12 - 2026.02·
Python
FastAPI
SQLAlchemy
Alembic
PostgreSQL
React
Vite
Tailwind CSS
Docker
GitHub Actions

배경

'잃어버린 인연을 다시 찾아주는' 소셜 매칭 플랫폼이다. 사용자가 그리운 사람의 전화번호를 익명으로 등록하면, 상대방도 같은 번호 쌍을 등록했을 때만 매칭이 성사된다. 연락처를 평문으로 다룰 수 없고, 상호 등록 여부는 상수 시간에 판정되어야 하며, 결제·구독·소셜 로그인은 여러 플랫폼을 수용해야 했다. 초기에는 Django 프로토타입으로 시작했지만, 비동기 API와 테스트 가능한 레이어 경계가 필요해 FastAPI + async SQLAlchemy로 재구성했다.

시스템 구조

  • 도메인 경계: Router(HTTP) → Service → Repository → UnitOfWork → DB. 도메인 엔티티 10개는 dataclass로 선언해 ORM에 종속되지 않는다.
  • 전화번호 이중 보호: 저장용은 AES-256-GCM으로 가역 암호화, 검색용은 SHA-256 + Pepper로 단방향 해시. 해시 컬럼에 인덱스를 걸어 상호 등록 판정을 O(1)로 처리.
  • 토큰 경제: TokenLedger가 모든 증감을 append-only로 기록. 매칭 즉시 공개와 흔적 공개는 각각 다른 슬롯에서 토큰을 차감한다.
  • 멱등성 결제: Toss는 구현 완료, Apple IAP·Google Play는 구조 정의 완료. webhook_events·outbox_events·payment_ledger·entitlement_audit_logs 4개 테이블로 멱등성 키 + TX ID + 금액 3중 검증을 남긴다.

해결 과정

Django → FastAPI 재구성. 동기 ORM과 라우터에 비즈니스 로직이 혼재된 구조에서, 비동기 처리와 테스트 경계가 필요해 FastAPI + async SQLAlchemy 2.0으로 옮겼다. Router는 HTTP 진입만 담당하고, Service에 도메인 로직을, Repository에 쿼리를 두어 계층별로 단위 테스트가 가능하게 만들었다.

전화번호 이중 보호. 전화번호는 평문으로 DB에 두지 않는다. 저장용 컬럼은 AES-256-GCM으로 암호화해 복호화가 필요한 로직에서만 꺼내 쓰고, 검색·매칭용 컬럼은 SHA-256에 Pepper를 섞은 단방향 해시를 별도 인덱스로 보관한다. 매칭 판정은 해시 컬럼만 조회하므로 평문에 닿지 않는다.

O(1) 상호 등록 감지. 사용자 A가 번호 X를 등록하면, X의 해시를 x_registry.target_phone_hash 인덱스에서 찾아 B가 먼저 등록했는지 확인한다. 있으면 Match(PENDING, unlock_time = now + 10h) 생성, 없으면 match_traces로 흔적만 남긴다. 해시 인덱스 덕분에 등록 규모와 무관하게 상수 시간에 끝난다.

PENDING → REVEALED 전이. 매칭 상태는 두 경로로 공개된다. (1) 배치 잡이 unlock_time이 지난 매칭들을 일괄 REVEALED로 전환 — 시간이 흐르기만 하면 자연스럽게 공개된다. (2) 사용자가 즉시 공개 토큰을 차감하면 match_repository가 단건을 즉시 REVEALED로 올린다. 두 경로 모두 같은 상태 컬럼을 갱신하므로 클라이언트는 상태 한 번만 보면 공개 여부를 안다.

UnitOfWork로 트랜잭션 무결성. 회원가입(소셜 3사 연동 포함), 결제 승인, 계정 병합처럼 여러 리포지토리를 건드리는 연산은 UnitOfWork가 SQLAlchemy 세션을 컨텍스트 매니저로 감싸 하나의 트랜잭션으로 커밋/롤백한다.

3사 결제 멱등성. Toss·Apple IAP·Google Play를 webhook_events로 수신하고, outbox_events로 재시도 큐를, payment_ledger로 최종 상태를, entitlement_audit_logs로 권한 지급 감사 로그를 남긴다. 멱등성 키·거래 ID·금액을 삼중으로 대조해 중복 지급·금액 위조를 막는다.

3사 소셜 로그인과 계정 병합. Kakao·Naver·Google을 oauth_account 추상화로 통합한다. 동일 이메일이면 자동 연동, 같은 전화번호로 SMS 인증하면 수동 연동을 제공한다.

성과

  • Backend 49 엔드포인트, 18 테이블, 서비스·리포지토리 각 13개로 구성된 DDD-lite 레이어드 아키텍처.
  • 전화번호 이중 보호(AES-256-GCM + SHA-256 해시) + O(1) 상호 등록 판정 + 10시간 대기 매칭 로직.
  • TokenLedger 기반 토큰 경제(즉시 공개 / 흔적 공개 슬롯 분리).
  • 결제 4 테이블(webhook_events / outbox_events / payment_ledger / entitlement_audit_logs)로 3사 결제 멱등성·감사 기반 구성.
  • OAuth 3사 통합 + 동일 이메일 자동 병합 + SMS 인증 수동 병합.
  • GitHub Actions 3개 파이프라인(CI / staging / production), EC2 스테이징 서버 운영.