Project

CMS 및 내부정산 시스템 데이터베이스 선택

readyoun 2025. 2. 25. 18:03

1. 들어가며

BFIND 프로젝트의 백엔드 개발을 시작하기에 앞서, 데이터베이스 아키텍처 설계 과정과 의사결정 내용을 정리했다. 현재 프론트엔드 일부만 구현된 상태에서, 백엔드 개발을 위한 데이터베이스 설계를 고민해보았다.

 

API 연동 전 클라이언트만 따로 만든 상태


BFIND는 웹툰, 웹소설 콘텐츠를 제공하는 웹 콘텐츠 플랫폼이다. 콘텐츠 관리 시스템(CMS)과 내부 정산 시스템을 기반으로 하며, Java/Kotlin과 Spring을 활용해 구축하기로 했다.

1.1 기술 스택 선정 배경

Java/Kotlin, Spring 선택 이유

  1. 안정성과 신뢰성
    • 대규모 웹툰 플랫폼에서 검증된 기술 스택
    • 결제/정산과 같은 금전적 트랜잭션의 안정적 처리 보장
    • Spring의 선언적 트랜잭션 관리로 데이터 정합성 확보
  2. 확장성과 유연성
    • 트래픽 증가에 따른 수평적 확장 용이
    • MSA 전환 시 Spring Cloud 활용 가능
    • Kotlin의 도입으로 Java 코드의 점진적 현대화 가능

1.2 데이터베이스 주요 요구사항

  1. 데이터 정합성
    • 결제/정산 데이터의 ACID 트랜잭션 보장
    • 사용자 포인트/코인 관리의 안정성
  2. 성능과 확장성
    • 대용량 콘텐츠 메타데이터 처리
    • 실시간 사용자 활동 로그 처리
    • 빠른 검색과 조회 기능
  3. 유연성
    • 다양한 콘텐츠 형식 지원
    • 동적 설정 관리
    • 실시간 데이터 처리

2. 데이터 특성 분석

웹 콘텐츠 플랫폼의 효율적인 데이터베이스 설계를 위해서는 먼저 데이터의 특성을 정확히 파악해야 한다. 여기서는 데이터 유형, 접근 패턴, 그리고 확장성 요구사항을 분석하여 최적의 데이터베이스 아키텍처를 설계하기 위한 기초를 마련한다.

2.1 데이터 유형 분석

웹 콘텐츠 플랫폼의 데이터는 크게 사용자 관련, 콘텐츠 관련, 서비스 운영 데이터로 구분된다. 각 데이터 유형별로 특성과 요구사항이 다르므로, 이에 맞는 적절한 데이터베이스 선택이 필요하다.

    1. 사용자 관련 데이터 ⭐⭐⭐
      : 핵심 비즈니스 로직과 직결된 데이터로 정합성이 최우선
      • 회원 정보: 이메일, 비밀번호, 프로필 등 기본 정보
      • 결제 수단: 신용카드, 계좌 정보
      • 포인트/코인: 잔액, 사용 내역, 만료 정보
      • 구매 이력: 콘텐츠 구매, 환불 기록
    2. 콘텐츠 관련 데이터 ⭐⭐
      : 서비스의 주요 콘텐츠이나 결제만큼 엄격한 정합성 불필요
      • 웹툰/웹소설 메타데이터: 제목, 작가, 장르, 태그
      • 에피소드 정보: 회차, 가격, 공개 상태
      • 커스텀 스토리: 사용자 생성 콘텐츠, 투고 정보
      • 통계 데이터: 조회수, 좋아요, 댓글수
    3. 서비스 운영 데이터
      : 일시적인 데이터 손실이 허용됨 
      • 세션 정보: 로그인 토큰, 접속 상태
      • 활동 로그: 조회 기록, 검색어, 체류 시간
      • 캐시 데이터: 인기 콘텐츠, 추천 목록

2.2 데이터 접근 패턴

데이터베이스 선택에 있어 중요한 고려사항 중 하나는 데이터 접근 패턴이다. 읽기 중심, 쓰기 중심, 트랜잭션 중요 데이터로 구분하여 각각에 적합한 데이터베이스 전략을 수립해야 한다.

  1. 읽기 중심 데이터
    • 콘텐츠 메타정보: 높은 읽기 빈도, 낮은 수정 빈도
    • 사용자 프로필: 중간 읽기 빈도, 낮은 수정 빈도
  2. 쓰기 중심 데이터
    • 사용자 활동 로그: 높은 쓰기 빈도
    • 실시간 통계: 지속적인 업데이트
  3. 트랜잭션 중요 데이터
    • 결제/환불: 강한 일관성 필요
    • 포인트/코인: 동시성 제어 필요

2.3 확장성 요구사항

서비스의 성장에 따른 데이터 증가와 트래픽 증가에 대비하여, 확장성 요구사항을 명확히 정의하고 이를 만족시킬 수 있는 데이터베이스 아키텍처를 설계해야 한다.

실제 배포한 서비스가 아니므로 특정 상황을 가정한 수치임을 참고.

  1. 데이터 볼륨
    • 콘텐츠 증가: 월 1,000편 이상 신규 콘텐츠
    • 사용자 활동: 일 100만건 이상 로그 생성
    • 미디어 저장: 회차당 평균 10MB
  2. 성능 요구사항
    • 페이지 로딩: 1초 이내
    • 검색 응답: 0.5초 이내
    • 결제 처리: 3초 이내
  3. 동시성 처리
    • 피크 시간대 동시 접속: 10만명
    • 실시간 랭킹 업데이트: 5분 주기
    • 결제 처리: 초당 1,000건

3. 데이터베이스 아키텍처 설계

BFIND 프로젝트의 핵심 요구사항:

  • 결제/정산 시스템의 데이터 정합성
  • 대용량 사용자 활동 로그 처리
  • 실시간 인기 콘텐츠 및 개인화 추천
  • 빠른 콘텐츠 제공과 이어보기 기능

이러한 요구사항을 효과적으로 처리하기 위해 각 데이터베이스의 특성을 활용한 아키텍처를 설계했다.

 

[요약]

  1. PostgreSQL
    • 트랜잭션 ACID 보장으로 결제/포인트 시스템의 안정성 확보
    • 복잡한 정산 처리를 위한 고급 SQL 기능 활용
    • 관계형 데이터 모델링으로 데이터 일관성 유지
  2. MongoDB
    • 유연한 스키마로 다양한 로그 데이터 수용
    • 효율적인 시계열 데이터 처리
    • 확장성 있는 통계 데이터 처리
  3. Redis
    • 빠른 응답 시간으로 실시간 사용자 경험 개선
    • 효율적인 랭킹 시스템 구현
    • 캐싱을 통한 시스템 전반의 성능 향상

3.1 PostgreSQL: 핵심 비즈니스 로직 처리

선정 이유: 트랜잭션 보장이 필요한 결제/포인트 시스템과 복잡한 정산 처리에 적합

주요 기능 및 구현 예시

1. 결제/포인트 시스템

트랜잭션을 통한 데이터 무결성 보장 (ACID)

-- 포인트 및 결제 정보 저장 테이블
CREATE TABLE user_points (
    user_id UUID PRIMARY KEY,  -- 사용자 ID (고유)
    point_balance INTEGER NOT NULL DEFAULT 0,  -- 현재 포인트 잔액
    updated_at TIMESTAMP NOT NULL DEFAULT now()  -- 최근 업데이트 시간
);

-- 포인트 사용 트랜잭션 처리 (동시성 문제 방지)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;  -- 가장 강력한 트랜잭션 격리 수준 설정
    -- 포인트 차감, 단 잔액이 충분할 경우에만 실행
    UPDATE user_points 
    SET point_balance = point_balance - 100
    WHERE user_id = 'user123' AND point_balance >= 100;

    -- 포인트 사용 기록 저장
    INSERT INTO point_transactions (user_id, amount, transaction_type, created_at)
    VALUES ('user123', -100, 'purchase', now());
COMMIT;
  • SERIALIZABLE 격리 수준을 사용해 동시성 문제를 방지
  • 포인트를 차감할 때 point_balance >= 100 조건을 체크하여 잔액 부족 방지
  • 트랜잭션이 정상적으로 수행되지 않으면 ROLLBACK 발생

2. 정산 시스템

월별 판매 수익을 창작자(크리에이터)에게 배분하는 로직

-- 수익 분배 계산 (WITH 문을 사용하여 가독성 개선)
WITH monthly_sales AS (
    SELECT 
        content_id, 
        SUM(sales_amount) AS total_sales
    FROM sales_data
    WHERE sales_date BETWEEN '2024-03-01' AND '2024-03-31'
    GROUP BY content_id
)
SELECT 
    ms.content_id,
    rs.creator_id,
    ms.total_sales * rs.creator_share AS creator_revenue  -- 창작자의 수익 분배 계산
FROM monthly_sales ms
JOIN revenue_share_rules rs USING (content_id);

 

  • WITH monthly_sales AS (...) 서브쿼리를 사용해 월별 총 매출을 미리 계산
  • JOIN revenue_share_rules를 통해 각 콘텐츠에 대한 수익 분배 비율 적용
  • total_sales * creator_share로 창작자의 실제 수익을 계산

3.2 MongoDB: 사용자 활동 및 콘텐츠 분석

선정 이유: 다양한 형태의 로그 데이터를 유연하게 저장하고, 실시간 통계 처리에 효과적

주요 기능 및 구현 예시

1. 사용자 활동 분석

// 사용자 활동 로그 저장 예시
{
    "user_id": ObjectId("user123"),
    "content_id": ObjectId("content456"),
    "action": "view",
    "progress": {
        "episode": 5,
        "paragraph": 10,
        "total_time": 180  // 사용자가 시청한 총 시간 (초 단위)
    },
    "timestamp": ISODate("2024-03-20T12:00:00Z")
}

// 사용자의 선호 콘텐츠 분석 쿼리
db.user_activities.aggregate([
    { $match: { action: "view" } },
    { $group: { _id: "$content_id", views: { $sum: 1 } } },
    { $sort: { views: -1 } },
    { $limit: 10 }
]);

 

사용자 활동 로그 저장 

  • 스키마리스 (Schema-less) 구조 덕분에 새로운 필드가 추가될 경우에도 문제 없음
  • progress 필드를 통해 사용자의 진행 상황(에피소드, 단락, 총 시청 시간 등) 저장

 

사용자 선호 콘텐츠 분석 

  • $match: "view" 이벤트만 필터링
  • $group: content_id 기준으로 그룹화하여 조회수 계산
  • $sort: 조회수 기준 내림차순 정렬
  • $limit: 상위 10개 인기 콘텐츠 조회

 

2. 콘텐츠 통계 

// 콘텐츠별 실시간 통계 데이터
{
    "content_id": ObjectId("content456"),
    "stats": {
        "views": {
            "total": 10000,  // 전체 조회수
            "last_24h": 500   // 최근 24시간 동안의 조회수
        },
        "completion_rate": 0.85  // 평균 콘텐츠 완료율 (85%)
    }
}

 

MongoDB를 사용하면 콘텐츠 소비 패턴을 빠르게 분석하고 실시간으로 반영할 수 있음

3.3 Redis: 실시간 데이터 처리

선정 이유: 빠른 응답 시간이 필요한 실시간 기능과 캐싱에 최적화

주요 기능 및 구현 예시

1. 사용자 세션 및 이어보기

# 사용자가 마지막으로 읽은 콘텐츠의 위치 저장
HSET "reading:user123" "content456" "{
    \"episode\": 5,
    \"paragraph\": 10,
    \"last_read\": \"2024-03-20T12:00:00Z\"
}"
  • HSET을 사용하여 사용자의 진행 상태를 Key-Value 형태로 저장
  • 조회 시 HGET을 사용하면 즉시 읽기 가능 (속도 매우 빠름)

 

2. 실시간 랭킹 및 추천

# 특정 콘텐츠의 조회수 증가 (장르별 인기 작품)
ZINCRBY "trending:romance" 1 "content456"

# 가장 인기 있는 콘텐츠 Top 10 조회
ZREVRANGE "trending:romance" 0 9 WITHSCORES

# 개인화 추천 목록을 1시간 동안 캐싱
SETEX "recommendations:user123" 3600 "[...]"
  • ZINCRBY를 사용해 실시간 조회수 증가
  • ZREVRANGE로 가장 인기 있는 콘텐츠를 빠르게 조회
  • SETEX 명령어를 사용해 TTL(Time To Live) 설정 → 1시간 후 자동 삭제
  • 캐시된 데이터를 사용하면 데이터베이스 부하 감소

 

Redis를 활용하면, 실시간 트래픽이 높은 환경에서도 빠른 사용자 경험을 제공할 수 있음

3.4 향후 고려사항

프로젝트 구현 단계에서 다음 사항들을 추가로 검토할 예정이다.

  1. 데이터 정합성 전략
    • PostgreSQL의 트랜잭션 데이터가 MongoDB의 통계에 반영되는 시점
    • Redis 캐시 데이터의 갱신 주기
  2. 성능 최적화 포인트
    • 인덱스 설계
    • 캐시 전략
    • 쿼리 최적화
  3. 모니터링 계획
    • 각 데이터베이스의 성능 지표
    • 병목 구간 식별

4. 결론

설계 및 정리하면서 배운 내용:

  • 데이터베이스 선택 시 비즈니스 요구사항과 데이터 특성을 고려하는 것이 중요하다.
  • 데이터베이스 아키텍처는 데이터 접근 패턴과 확장성 요구사항에 따라 설계되어야 한다.
  • 데이터베이스 선택에 있어 성능, 확장성, 데이터 정합성 등 다양한 요소를 고려해야 한다.

참고자료

추가학습 자료