Skip to main content
Summary

운영 데이터와 분석 데이터는 목적이 다르다

서비스 데이터베이스는 보통 현재 상태를 정확하게 처리하기 위해 설계된다. 사용자가 주문을 만들고, 결제가 승인되고, 재고가 줄어드는 순간에 데이터가 틀리면 안 된다. 이런 시스템을 OLTP, 즉 온라인 트랜잭션 처리 시스템이라고 부른다. OLTP의 핵심은 짧은 트랜잭션, 빠른 단건 조회, 정합성 있는 변경이다.

분석용 데이터는 질문에 답하기 위해 설계된다. 지난달 신규 사용자의 7일 재방문율은 얼마인가. 쿠폰을 받은 사용자의 결제 전환율은 높아졌는가. 특정 카테고리의 매출이 감소한 이유는 무엇인가. 이런 질문은 한두 행을 빠르게 찾는 것보다 많은 데이터를 스캔하고 집계하는 능력이 중요하다. 이 영역을 OLAP, 즉 온라인 분석 처리라고 부른다.

두 시스템은 같은 데이터를 다루지만 쿼리 패턴이 다르다.

  • OLTP는 주문 id = 123처럼 특정 행을 빠르게 찾고 변경한다.

  • OLAP는 월별, 카테고리별, 유입 채널별 매출 합계처럼 많은 행을 읽고 묶는다.

  • OLTP는 중복을 줄이고 변경 비용을 낮추는 정규화된 구조가 유리하다.

  • OLAP는 조인을 줄이고 집계를 쉽게 하기 위해 의도적인 중복과 넓은 테이블을 사용하기도 한다.

운영 DB의 스키마가 분석에 항상 좋은 것은 아니다. 예를 들어 주문 상세 화면에는 주문, 주문 상품, 결제, 배송, 쿠폰 테이블을 조인해도 괜찮다. 단일 주문만 조회하기 때문이다. 하지만 1년치 주문을 기준으로 카테고리별 매출과 쿠폰 사용 효과를 계산한다면 같은 조인이 수천만 행에 반복된다. 운영 쿼리에는 문제가 없던 구조가 분석 쿼리에서는 비싼 구조가 된다.

분석 설계의 출발점은 데이터를 많이 저장하는 것이 아니다. 먼저 어떤 질문에 답할지 정해야 한다. 제품 개선에 필요한 질문이 없으면 이벤트와 테이블은 금방 의미 없는 로그 더미가 된다.

분석을 위한 데이터 설계AI 생성 이미지

이벤트 로그는 행동을 기록한다

운영 데이터는 주로 최종 상태를 저장한다. 사용자의 현재 등급, 주문의 현재 상태, 상품의 현재 가격처럼 지금 필요한 값이 중심이다. 반면 제품 분석에서는 과정이 중요하다. 사용자가 어떤 화면을 보고, 어떤 버튼을 눌렀으며, 어디에서 이탈했는지를 알아야 한다.

이때 사용하는 대표적인 데이터가 이벤트 로그다. 이벤트 로그는 사용자의 행동이나 시스템에서 발생한 사실을 시간순으로 기록한 데이터다. 좋은 이벤트 로그에는 최소한 다음 요소가 일관되게 들어가야 한다.

  • 사용자 식별자: 누가 행동했는지 나타낸다.

  • 이벤트명: 어떤 행동이 발생했는지 나타낸다.

  • 속성: 행동의 맥락을 설명한다.

  • 발생 시각: 언제 발생했는지 나타낸다.

예를 들어 커머스 서비스라면 다음과 같은 이벤트를 기록할 수 있다.

  • product_viewed: 사용자가 상품 상세를 봄

  • add_to_cart: 장바구니에 상품을 담음

  • checkout_started: 결제 과정을 시작함

  • payment_completed: 결제가 완료됨

이벤트명은 단순해야 하지만 의미가 모호하면 안 된다. click 같은 이름만으로는 분석하기 어렵다. 어떤 화면의 어떤 의도를 가진 클릭인지 알 수 없기 때문이다. 반대로 이벤트명을 너무 세분화해 main_banner_blue_button_clicked_v2처럼 만들면 서비스 변경 때마다 분석 기준이 흔들린다. 보통은 행동을 이벤트명으로 두고, 화면 이름, 버튼 위치, 실험 그룹, 상품 id 같은 정보는 속성으로 넣는 방식이 관리하기 쉽다.

사용자 식별자도 신중해야 한다. 로그인 사용자는 user_id로 추적할 수 있지만, 로그인 전 행동은 익명 식별자인 anonymous_id나 기기 식별자를 사용해야 할 수 있다. 이후 로그인 시 두 식별자를 연결하는 규칙이 필요하다. 그렇지 않으면 광고를 보고 방문한 익명 사용자가 가입 후 결제했는지 연결하지 못한다.

발생 시각에는 적어도 두 가지가 있다. 하나는 클라이언트나 서버에서 실제 이벤트가 발생한 시각이고, 다른 하나는 분석 시스템에 수집된 시각이다. 모바일 환경에서는 네트워크가 끊겼다가 나중에 로그가 전송될 수 있다. 이때 수집 시각만 보면 사용자의 실제 행동 순서가 왜곡된다. 분석 목적에 따라 occurred_at과 ingested_at을 분리해 저장하는 편이 안전하다.

속성은 유연해야 하지만 무질서하면 안 된다. JSON 형태로 아무 값이나 넣을 수 있게 만들면 초기에 편하다. 그러나 시간이 지나면 같은 의미가 product_id, itemId, goods_no처럼 여러 이름으로 쌓인다. 분석가는 매번 값을 맞춰야 하고, 대시보드는 서로 다른 숫자를 보여준다. 이벤트 정의서, 허용된 속성 이름, 타입, 필수 여부를 관리해야 하는 이유다.

중복 전송을 처리할 기준도 필요하다. 모바일 앱이 재시도하면서 같은 이벤트를 두 번 보낼 수 있고, 서버 로그 수집기가 장애 후 재처리하면서 이미 적재된 이벤트를 다시 보낼 수도 있다. 중요한 이벤트라면 event_id처럼 고유하게 식별할 수 있는 값을 두고, 같은 이벤트가 다시 들어왔을 때 제거할 수 있어야 한다. 특히 결제, 가입, 쿠폰 사용처럼 지표에 직접 영향을 주는 이벤트는 중복 하나가 숫자를 크게 흔들 수 있다.

스타 스키마로 분석하기 쉽게 만든다

분석 데이터 모델에서 자주 쓰는 구조가 스타 스키마다. 중심에는 팩트 테이블이 있고, 주변에는 디멘션 테이블이 있다. 그림으로 그리면 별 모양처럼 보여서 이런 이름이 붙었다.

팩트 테이블은 분석할 사건이나 측정값을 담는다. 주문, 결제, 상품 조회, 배송 완료처럼 수치화하거나 집계할 수 있는 일이 여기에 해당한다. 디멘션 테이블은 그 사건을 설명하는 기준 정보를 담는다. 사용자, 상품, 날짜, 카테고리, 지역, 유입 채널이 대표적이다.

주문·결제 분석을 예로 들어 보자.

fact_order_payment 팩트 테이블에는 다음과 같은 값이 들어갈 수 있다.

  • 주문 id

  • 결제 id

  • 사용자 id

  • 상품 id

  • 주문 일자 키

  • 결제 수단 키

  • 판매 수량

  • 상품 금액

  • 할인 금액

  • 결제 금액

주변 디멘션 테이블은 다음처럼 나눌 수 있다.

  • dim_user: 가입일, 성별, 연령대, 회원 등급

  • dim_product: 상품명, 브랜드, 카테고리, 판매 상태

  • dim_date: 날짜, 주차, 월, 분기, 공휴일 여부

  • dim_payment_method: 카드, 계좌이체, 간편결제 등

  • dim_channel: 광고, 검색, 추천, 직접 방문 등

이 구조의 장점은 질문을 만들기 쉽다는 데 있다. 월별 매출을 보려면 팩트의 결제 금액을 dim_date의 월 기준으로 합산한다. 카테고리별 쿠폰 효과를 보려면 팩트의 할인 금액과 결제 금액을 dim_product의 카테고리 기준으로 묶는다. 신규 사용자 매출을 보려면 dim_user의 가입일과 팩트의 결제일을 비교한다.

팩트 테이블을 설계할 때는 한 행이 무엇을 의미하는지 먼저 정해야 한다. 이를 보통 팩트의 단위, 또는 그레인이라고 부른다. 한 행이 주문 한 건인지, 결제 한 건인지, 주문 상품 한 줄인지에 따라 합산 결과가 달라진다. 예를 들어 주문 단위 팩트에 상품 id를 여러 개 억지로 넣으면 카테고리별 매출을 계산하기 어렵다. 반대로 주문 상품 단위 팩트에서 주문 수를 단순히 세면 한 주문에 상품이 세 개 들어 있을 때 주문이 세 건으로 계산될 수 있다. 분석 테이블은 컬럼 이름만큼이나 "한 행의 의미"가 중요하다.

운영 DB에서는 결제 상태가 계속 바뀔 수 있다. 결제 완료 후 부분 취소가 발생하고, 배송이 완료된 뒤 반품될 수도 있다. 분석에서는 어떤 시점의 사실을 기준으로 볼지 정해야 한다. 매출을 결제 완료 시점 기준으로 볼지, 구매 확정 시점 기준으로 볼지, 환불을 음수 매출로 반영할지에 따라 숫자가 달라진다. 그래서 팩트 테이블을 만들 때는 단순히 운영 테이블을 복사하지 말고, 측정 기준을 명확히 해야 한다.

디멘션도 현재 값만 저장하면 분석이 왜곡될 수 있다. 상품이 3월에는 A 카테고리였고 5월에는 B 카테고리로 변경되었다고 하자. 과거 매출을 현재 카테고리 기준으로 다시 계산할 것인지, 당시 카테고리 기준으로 유지할 것인지 결정해야 한다. 이처럼 시간에 따라 변하는 디멘션을 다룰 때는 이력 관리가 필요하다. 모든 이력을 복잡하게 관리할 필요는 없지만, 매출·정산·성과 평가에 영향을 주는 기준값은 과거 의미가 보존되어야 한다.

ETL과 ELT는 변환 시점이 다르다

운영 데이터와 이벤트 로그는 그대로 분석에 쓰기 어렵다. 형식이 다르고, 누락이 있고, 중복이 있으며, 서비스 내부 코드값이 그대로 들어 있기 때문이다. 분석 가능한 데이터로 만들려면 수집, 정제, 변환 과정이 필요하다.

ETL은 Extract, Transform, Load의 약자다. 데이터를 원천 시스템에서 추출한 뒤, 중간 단계에서 정제하고 변환한 다음, 분석 저장소에 적재한다. 데이터 웨어하우스에 들어가기 전에 품질을 통제하기 좋다. 반면 변환 로직이 앞단에 고정되므로 나중에 다른 방식으로 분석하고 싶을 때 원본 정보가 부족할 수 있다.

ELT는 Extract, Load, Transform의 약자다. 먼저 원천 데이터를 분석 저장소에 적재하고, 그 안에서 필요한 형태로 변환한다. 클라우드 데이터 웨어하우스처럼 저장과 연산을 크게 확장할 수 있는 환경에서 자주 쓰인다. 원본에 가까운 데이터를 보존하므로 재처리와 새로운 분석에 유리하다. 대신 저장소 안에 정제되지 않은 데이터가 함께 쌓이므로 권한, 비용, 품질 관리가 중요하다.

선택 기준은 단순하다.

  • 원천 데이터 품질을 강하게 통제해야 하고 저장소 비용이 크다면 ETL이 맞을 수 있다.

  • 다양한 분석 요구가 자주 생기고 원본 재처리가 중요하다면 ELT가 유리하다.

  • 개인정보나 민감 정보는 어느 방식을 쓰든 적재 전후의 마스킹, 암호화, 접근 제어가 필요하다.

처리 방식도 나눠야 한다. 배치 처리는 일정 주기로 데이터를 모아 처리한다. 매일 새벽 전날 매출을 집계하거나, 한 시간마다 이벤트를 적재하는 방식이다. 구현과 운영이 비교적 단순하고 실패 시 재실행하기 쉽다. 대부분의 리포트, 정산 보조 데이터, 장기 추세 분석은 배치로 충분하다.

스트리밍 처리는 이벤트가 발생하는 즉시 또는 짧은 지연으로 데이터를 처리한다. 실시간 이상 거래 탐지, 실시간 재고 알림, 라이브 대시보드처럼 빠른 반응이 필요한 경우에 적합하다. 하지만 중복 이벤트, 순서 뒤바뀜, 늦게 도착한 데이터, 재처리 문제를 직접 다뤄야 한다. 실시간이라는 말은 매력적이지만 운영 비용이 높다. 의사결정이 하루 단위로 이루어진다면 굳이 스트리밍을 선택할 필요가 없다.

운영 DB에서 직접 분석하면 위험하다

작은 서비스에서는 운영 DB에 직접 접속해 통계 쿼리를 실행하기 쉽다. 처음에는 문제가 없어 보인다. 데이터가 적고 사용자가 많지 않기 때문이다. 하지만 서비스가 커지면 무거운 분석 쿼리는 운영 장애의 원인이 된다.

분석 쿼리는 많은 행을 읽고, 정렬하고, 그룹화한다. 이 과정에서 CPU, 디스크 I/O, 메모리, 임시 파일, 버퍼 캐시를 많이 사용한다. 운영 트랜잭션이 써야 할 자원을 분석 쿼리가 차지하면 사용자 요청이 느려진다. 인덱스를 타지 않는 통계 쿼리가 수천만 행을 스캔하면 주문 생성, 로그인, 결제 확인 같은 핵심 기능까지 영향을 받을 수 있다.

락과 복제 지연도 문제다. 일부 DBMS나 쿼리에서는 긴 조회가 정리 작업이나 스키마 변경을 방해할 수 있다. 리더가 아닌 읽기 복제본에서 분석 쿼리를 실행하더라도 무조건 안전하지는 않다. 복제본이 과부하되면 복제 지연이 커지고, 애플리케이션이 그 복제본을 읽기 트래픽에도 사용한다면 사용자는 오래된 데이터를 보게 된다.

분리 전략은 단계적으로 적용할 수 있다.

첫째, 운영 DB에는 제한을 둔다. 분석 계정은 읽기 권한만 주고, 접근 가능한 테이블과 실행 시간을 제한한다. 업무 시간 중 대량 쿼리를 금지하고, 필요한 인덱스와 실행 계획을 확인한다.

둘째, 읽기 복제본을 활용한다. 쓰기를 담당하는 운영 DB와 분석 조회를 분리하면 즉각적인 위험을 줄일 수 있다. 다만 복제 지연과 복제본 부하를 모니터링해야 한다. 읽기 복제본은 분석 전용과 서비스 읽기용을 분리하는 것이 더 안전하다.

셋째, 별도의 분석 저장소를 둔다. 운영 DB의 변경 데이터를 주기적으로 데이터 웨어하우스나 데이터 마트로 옮긴다. PostgreSQL이나 MySQL은 운영 트랜잭션에 집중하고, BigQuery, Snowflake, Redshift, ClickHouse 같은 분석 저장소는 대량 스캔과 집계를 담당하게 할 수 있다. 특정 제품이 정답은 아니며, 데이터 크기, 비용, 팀의 운영 역량, 필요한 지연 시간을 기준으로 선택해야 한다.

넷째, 대시보드용 집계 테이블을 만든다. 매번 원천 데이터를 스캔하지 않고 일별 매출, 시간대별 가입자 수, 캠페인별 전환율처럼 자주 보는 지표를 미리 계산해 둔다. 단, 집계 테이블은 원본과 불일치할 수 있으므로 생성 기준, 갱신 주기, 재계산 방법을 문서화해야 한다.

좋은 분석 데이터는 질문과 함께 설계된다

분석 데이터 설계의 실패는 보통 두 방향에서 발생한다. 하나는 아무것도 남기지 않아 나중에 질문에 답할 수 없는 경우다. 다른 하나는 모든 것을 남겼지만 의미가 정리되지 않아 누구도 믿지 못하는 경우다.

개발자는 이벤트를 추가할 때 다음 질문을 확인해야 한다.

  • 이 데이터로 어떤 의사결정을 할 것인가.

  • 식별자는 로그인 전후, 기기 변경, 계정 통합 상황에서도 연결 가능한가.

  • 이벤트명과 속성 이름은 팀 안에서 같은 의미로 쓰이는가.

  • 발생 시각, 수집 시각, 처리 시각 중 어떤 시간을 기준으로 분석할 것인가.

  • 개인정보나 민감 정보가 불필요하게 포함되지는 않는가.

  • 같은 이벤트가 중복 전송되었을 때 제거할 기준이 있는가.

  • 스키마가 바뀌었을 때 과거 데이터와 함께 해석할 수 있는가.

제품을 개선하려면 데이터를 저장하는 것에서 끝나지 않아야 한다. 데이터는 질문에 답할 수 있는 형태로 쌓여야 한다. 운영 DB는 서비스를 안전하게 움직이는 엔진이고, 분석 데이터는 그 서비스가 어디로 가고 있는지 보여주는 계기판이다. 둘을 같은 방식으로 설계하면 어느 쪽도 만족시키기 어렵다. 현재 상태를 지키는 구조와 과거 행동을 이해하는 구조를 분리할 때, 데이터는 비로소 의사결정에 사용할 수 있는 자산이 된다.

책 목록

개발자가 알아야 할 데이터베이스 지식

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기