들어가며: 소셜 기능과 프라이버시, 그 미묘한 균형

에어비앤비가 점점 더 '소셜 플랫폼'으로 진화하고 있습니다. 체험(Experience)에 참여하는 사람들이 누군지 미리 보고, 같은 체험에 참여한 게스트끼리 메시지를 주고받으며, 프로필에서 '함께 만난 사람' 섹션을 제공하는 식이죠.

하지만 이런 기능은 본질적으로 개인정보 노출이라는 리스크를 동반합니다. '누가 이 체험에 참여하는지'를 보여주는 것은 사용자 경험을 높이지만, 동시에 '내가 어디에 있는지', '누구와 함께하는지'가 타인에게 공개된다는 뜻이기도 하니까요.

에어비앤비 엔지니어링 팀이 이 문제를 어떻게 해결했는지, 그 아키텍처적 접근이 국내 서비스 기획자나 개발자에게도 큰 인사이트를 줄 거라고 생각해 이 글을 준비했습니다.

참고: 이 글은 에어비앤비 엔지니어링 블로그의 Privacy-first connections: Empowering social experiences at Airbnb를 기반으로 분석한 내용입니다. 원문에는 없는 국내 적용 맥락과 실무 팁을 추가했어요.

Privacy first architecture diagram showing User and Profile separation for social features IT Technology Image

핵심 아이디어: User와 Profile을 완전히 분리하라

에어비앤비는 '사용자(User)'와 '프로필(Profile)'을 명확히 구분했습니다.

  • User: 이름, 이메일, 전화번호 등 내부적으로 관리하는 실제 사용자 정보
  • Profile: 특정 컨텍스트(예: 특정 체험)에서 외부에 공개되는 정보의 부분 집합

예를 들어, 마리라는 사용자가 '할머니와 파스타 만들기' 체험에는 프로필 A로, '염소 요가' 체험에는 프로필 B로 참여할 수 있습니다. 프로필 A는 이름만 공개하고, 프로필 B는 사진과 자기소개까지 공개하는 식으로 체험별로 다른 공개 범위를 설정할 수 있습니다.

이를 가능하게 한 기술적 결정은 User ID와 Profile ID의 분리입니다.

# 개념적으로 이렇게 분리됩니다
user = {
    "user_id": "u_12345",
    "name": "Marie Kim",
    "email": "marie@example.com",
    "phone": "+82-10-1234-5678"
}

profile_for_experience_a = {
    "profile_id": "p_exp_a_001",
    "user_id": "u_12345",
    "display_name": "Marie",
    "photo_url": None,  # 비공개
    "about_me": ""
}

profile_for_experience_b = {
    "profile_id": "p_exp_b_002",
    "user_id": "u_12345",
    "display_name": "Marie Kim",
    "photo_url": "https://...",  # 공개
    "about_me": "파스타와 염소를 사랑하는 사람"
}

이렇게 분리하면, 체험 A와 체험 B를 같은 사람이 참여하더라도 두 프로필이 서로 연결되지 않습니다. 같은 사용자임을 알 수 있는 건 오직 시스템 내부뿐이죠.

Developer reviewing code with AI powered refactoring tools for privacy compliance Software Concept Art

권한 시스템: Himeji와 최소 권한 원칙

프로필이 분리되었다고 끝이 아닙니다. 누가 어떤 프로필을 볼 수 있는지를 제어하는 권한 시스템이 필요합니다.

에어비앤비는 자체 개발한 권한 시스템 Himeji를 사용합니다. Himeji의 핵심 특징은 데이터 레이어에서 직접 접근 제어를 수행한다는 점입니다. 프론트엔드에서 UI만 가리는 수준이 아니라, 데이터베이스 쿼리 단계에서부터 권한이 없는 데이터는 아예 조회되지 않도록 설계되었습니다.

# Himeji의 동작 방식을 추상화한 예시
class ProfileAccessController:
    def __init__(self):
        # 쓰기 시점에 권한 관계를 비정규화하여 저장
        self.permission_cache = {}
    
    def can_view_profile(self, viewer_id: str, target_profile_id: str) -> bool:
        """
        조회 시점에는 캐시된 권한만 빠르게 확인
        """
        # viewer_id가 target_profile_id를 볼 수 있는 권한이 있는지 확인
        return self.permission_cache.get(f"{viewer_id}:{target_profile_id}", False)
    
    def grant_permission(self, viewer_id: str, target_profile_id: str):
        """
        권한 부여 시점에 관계를 비정규화하여 저장
        """
        self.permission_cache[f"{viewer_id}:{target_profile_id}"] = True

이 방식의 장점은 읽기 성능이 매우 빠르고 확장성이 좋다는 점입니다. 실제 서비스에서는 수백만 건의 권한 관계를 실시간으로 확인해야 하므로, 쓰기 시점에 미리 계산해두는 전략이 효과적입니다.

AI 리팩토링과 전사적 협업

이런 아키텍처 변경을 실제 코드베이스에 적용하는 것은 단순하지 않습니다. 에어비앤비는 다음과 같은 프로세스를 거쳤습니다.

  1. 자동 감사(Audit): Python 스크립트로 코드베이스 전체를 스캔하여 User ID를 직접 사용하는 모든 위치를 찾아냄
  2. 팀 소유권 매핑: 각 코드 조각을 담당 팀에 자동 할당
  3. 수동 검토: 각 팀이 해당 코드가 내부용인지 외부 노출용인지 직접 판단
  4. AI 리팩토링: AI 도구가 코드 변경을 제안하면, 엔지니어가 검토 및 수정
  5. 전사 정렬: 엔지니어링, 제품, 법무, 프라이버시 팀이 모두 함께 움직임

이 과정에서 **타입 안전성(Type Safety)**이 중요한 역할을 했습니다. Profile ID와 User ID가 실수로 섞이지 않도록 타입 시스템으로 강제했습니다.

// TypeScript로 표현한 타입 안전성
// User ID와 Profile ID를 별도 타입으로 선언
type UserId = string & { __brand: 'UserId' };
type ProfileId = string & { __brand: 'ProfileId' };

function getProfileByUserId(userId: UserId): Profile | null {
    // User ID로 프로필을 찾는 함수
}

function getProfileByProfileId(profileId: ProfileId): Profile | null {
    // Profile ID로 프로필을 찾는 함수
}

// 컴파일 타임에 잘못된 사용을 방지
const uid: UserId = "u_12345" as UserId;
const pid: ProfileId = "p_exp_001" as ProfileId;

// getProfileByUserId(pid); // ❌ 타입 에러! ProfileId를 UserId 자리에 쓸 수 없음
getProfileByUserId(uid);    // ✅ 정상

Network of connected profiles with context aware permissions and access control Coding Session Visual

국내 서비스에 적용할 수 있는 인사이트

1. 'User-Profile 분리'는 생각보다 많은 곳에 필요합니다

국내 커뮤니티 서비스(예: 당근마켓, 오늘의집, 솜씨당)에서도 동일한 고민이 필요합니다.

  • 당근마켓: 동네 이웃과 거래할 때는 프로필이 보여야 하지만, 특정 물품에 '관심 있어요'를 누른 사람들의 프로필은 제한적으로 보여줘야 함
  • 오늘의집: '이 집에 살고 있어요' 인증을 한 사용자와 일반 방문자의 프로필 공개 범위가 달라야 함

'사용자'라는 하나의 개념으로 모든 것을 처리하려다 보면, 나중에 프라이버시 기능을 추가할 때 대규모 리팩토링이 필요해집니다. 처음부터 '컨텍스트별 프로필' 개념을 도입하는 것이 좋습니다.

2. 권한 시스템은 데이터 레이어에서 처리하라

프론트엔드에서만 권한을 체크하면, API를 직접 호출하는 경우 우회가 가능합니다. Himeji처럼 데이터베이스나 ORM 레이어에서 권한을 강제하는 것이 보안의 기본입니다.

3. AI 리팩토링 도구는 '제안'으로 쓰고 '검토'는 사람이 하라

에어비앤비 사례에서 인상 깊었던 점은 AI가 제안한 코드 변경을 엔지니어가 반드시 검토했다는 것입니다. AI가 비즈니스 로직의 미묘한 차이를 완벽히 이해하지 못하기 때문입니다. 국내에서도 GitHub Copilot이나 Cursor 같은 도구를 사용할 때, '일단 AI가 작성했으니 믿자'는 태도는 위험합니다.

4. 주의사항: 이 아키텍처의 비용

User-Profile 분리는 분명 강력하지만, 복잡성과 운영 비용이 증가합니다.

  • 데이터베이스 쿼리가 더 복잡해짐 (User → Profile → Permission 관계)
  • 프로필 생성/삭제/변경 시 동기화 이슈 발생 가능
  • 기존 User ID에 의존하던 레거시 코드를 모두 마이그레이션해야 함

에어비앤비는 대규모 엔지니어링 조직이 있었기에 가능했지만, 스타트업 초기에는 필요한 최소한의 프라이버시 기능만 구현하고, 서비스가 성장함에 따라 점진적으로 도입하는 전략이 더 현실적일 수 있습니다.

5. 다음 단계 학습 방향

  • Zero Trust Architecture: 모든 요청을 신뢰하지 않고 매번 인증/인가하는 접근법
  • GDPR/개인정보보호법 준수: 서비스 글로벌 진출 시 필수
  • ABAC (Attribute-Based Access Control): Himeji와 같은 고급 권한 시스템의 이론적 배경

함께 보면 좋은 글

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.