OLAP vs OLTP
OLTP(Online Transaction Processing)는 주문, 결제, 재고 갱신처럼 짧고 빈번한 읽기·쓰기 트랜잭션을 빠르고 안정적으로 처리하는 업무입니다. 반대로 OLAP(Online Analytical Processing)은 대량의 이력 데이터를 여러 기준으로 집계하고 분석하는, 읽기 중심의 업무입니다.
두 업무는 쿼리의 모양이 달라서 데이터베이스의 설계와 저장 방식도 다르게 가져갑니다. OLTP는 행 하나를 다루는 일이 많아 정규화된 스키마와 인덱스, 행 지향 저장이 어울리고, OLAP은 일부 열을 많은 행에 걸쳐 읽는 일이 많아 컬럼 지향 저장이 유리합니다. 이 토픽에서는 두 업무의 차이와 저장 방식의 차이를 시각화로 확인합니다.
핵심 개념
OLTP
짧은 읽기·쓰기 트랜잭션을 많은 사용자가 동시에 실행합니다. 행 단위 접근이 많고 일관성과 동시성이 중요합니다.
핵심 개념
OLAP
대량의 이력 데이터를 집계하고 분석합니다. 복잡한 읽기 쿼리가 중심이고 한 쿼리가 많은 행을 훑습니다.
핵심 개념
데이터 웨어하우스
여러 업무 시스템의 데이터를 모아 분석용으로 저장하는 저장소입니다. OLAP 분석이 주로 여기서 이루어집니다.
핵심 개념
행 지향 저장
한 행의 값을 이어 붙여 저장합니다. 행 하나를 읽고 쓰기에 좋아 OLTP에 어울립니다.
핵심 개념
컬럼 지향 저장
한 열의 값을 모아 저장합니다. 필요한 열만 읽고 같은 열끼리 압축이 잘 되어 OLAP에 유리합니다.
핵심 개념
ETL
업무 시스템의 데이터를 추출(Extract)하고 변환(Transform)해 웨어하우스에 적재(Load)하는 과정입니다. 분석 부하를 업무 DB와 분리합니다.
OLTP와 OLAP의 배경
OLAP이라는 용어는 1993년 E. F. 코드(E. F. Codd) 등이 쓴 백서에서 사용되었습니다. 앞서 1992년에는 빌 인먼(Bill Inmon)이 『Building the Data Warehouse』에서 데이터 웨어하우스를 주제 지향, 통합, 시간 가변, 비휘발성 데이터 모음으로 정리했고, 이후 업무 처리와 분석을 나누는 구조가 널리 쓰였습니다.
컬럼 지향 저장은 분석 부하에 맞춰 발전했습니다. 2005년 스톤브레이커(M. Stonebraker) 등이 발표한 C-Store 논문이 읽기에 최적화한 컬럼 지향 DBMS의 설계를 제시했고(그 이전에도 컬럼 저장을 쓰는 제품이 있었습니다), 이후 분석용 데이터베이스와 저장 형식에 널리 쓰이고 있습니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
OLTP의 특징은?
주문과 결제처럼 짧고 빈번한 트랜잭션을 빠르고 안정적으로 처리하는 데 초점을 둡니다. 정규화된 스키마와 인덱스를 사용합니다.
면접 질문
OLAP의 특징은?
대량의 이력 데이터를 집계하고 분석하는 읽기 중심 쿼리를 다룹니다. 데이터 웨어하우스에서 주로 사용합니다.
면접 질문
컬럼 지향 저장이 OLAP에 유리한 이유는?
분석 쿼리는 일부 열을 많은 행에 걸쳐 읽으므로 필요한 열만 읽어 I/O를 줄일 수 있고, 같은 열의 값끼리 압축 효율도 높일 수 있습니다.
면접 질문
분석 쿼리를 왜 업무 DB에서 따로 떼어 내나요?
오래 걸리는 집계 쿼리가 업무 DB의 자원을 차지해 주문과 결제 응답에 영향을 줄 수 있어서, 분석 전용 저장소로 데이터를 옮겨 실행합니다.
활용 분야
함께 보면 좋은 토픽
참고 자료
SQL — OLTP의 트랜잭션과 OLAP의 집계 쿼리
-- OLTP: 짧은 트랜잭션, 행 하나씩 BEGIN; INSERT INTO sales (id, sold_on, region, product, amount) VALUES (7, '2024-10-04', 'KR', 'A', 120); UPDATE stock SET quantity = quantity - 1 WHERE product = 'A'; COMMIT; SELECT * FROM sales WHERE id = 7; -- 기본 키 인덱스를 써서 행 하나를 찾을 수 있다 -- OLAP: 많은 행을 훑어 집계 SELECT region, date_trunc('month', sold_on) AS month, SUM(amount) AS total FROM sales WHERE sold_on >= '2022-01-01' GROUP BY region, month ORDER BY region, month; -- 지난 몇 해의 지역별 월 매출
위쪽은 OLTP의 전형적인 모습입니다. 주문 한 건을 INSERT하고 재고를 UPDATE한 뒤 바로 커밋하는 짧은 트랜잭션이고, 조회도 기본 키 인덱스를 써서 행 하나를 찾을 수 있습니다. 시각화의 OLTP 탭이 보여 주는 INSERT, UPDATE, SELECT와 같은 흐름이며, 시각화는 표의 행 하나를 예로 강조합니다. date_trunc와 GROUP BY에서 출력 열 별칭을 쓰는 것은 PostgreSQL 기준 문법이고 제품마다 다릅니다. 아래쪽은 OLAP의 전형적인 모습입니다. 오랜 기간의 많은 행을 훑어 지역과 월별로 묶어 합계를 구합니다. 결과는 요약된 소수의 행이지만 읽는 행은 매우 많아 쿼리 하나가 오래 걸릴 수 있어, 업무 처리용 DB와 분리한 데이터 웨어하우스에서 돌리는 경우가 많습니다. 테이블과 열 이름은 예시입니다.
Python — 행 지향과 컬럼 지향이 읽는 양
rows = [(1, '10-01', 'KR', 'A', 100), (2, '10-01', 'US', 'B', 150), (3, '10-02', 'KR', 'A', 200), (4, '10-02', 'JP', 'C', 80), (5, '10-03', 'US', 'B', 300), (6, '10-03', 'KR', 'A', 50)] names = ('id', 'date', 'region', 'product', 'amount') cols = {n: [r[i] for r in rows] for i, n in enumerate(names)} # 컬럼 지향: 열마다 모아 저장 need = ('region', 'amount') # 집계 쿼리가 필요한 열 row_cells = len(rows) * len(names) # 행 지향: 행 전체를 읽음 col_cells = len(rows) * len(need) # 컬럼 지향: 필요한 열만 읽음 print('읽는 셀:', row_cells, col_cells) total = {} for region, amount in zip(cols['region'], cols['amount']): total[region] = total.get(region, 0) + amount print('집계:', total) print('행 하나(id=3) 읽기:', rows[2]) # 행 지향: 한 번에 연속으로 print('컬럼 지향으로 id=3 읽기:', [cols[n][2] for n in names], '열 5곳을 읽음') # 읽는 셀: 30 12 # 집계: {'KR': 350, 'US': 450, 'JP': 80} # 행 하나(id=3) 읽기: (3, '10-02', 'KR', 'A', 200) # 컬럼 지향으로 id=3 읽기: [3, '10-02', 'KR', 'A', 200] 열 5곳을 읽음
같은 6행을 행 지향(rows)과 컬럼 지향(cols)으로 놓고 읽는 양을 비교하는 장난감 모델입니다. 지역별 합계에는 region과 amount 두 열만 필요하므로, 컬럼 지향은 12칸만 읽고 행 지향은 행 전체인 30칸을 읽습니다. 시각화의 행 vs 컬럼 저장 탭과 같은 결과입니다. 집계 결과는 KR 350, US 450, JP 80입니다. 반대로 id가 3인 행 하나를 읽는 경우, 행 지향은 한 곳에서 5개의 값을 한꺼번에 읽지만 컬럼 지향은 5개의 열을 각각 찾아가야 합니다. 실제 컬럼 지향 저장소는 열을 압축하고 블록 단위로 읽으며 훨씬 복잡합니다.
Java — 스트림 집계와 열 단위 배열
record Sale(int id, String date, String region, String product, int amount) {} List<Sale> rows = List.of( new Sale(1, "10-01", "KR", "A", 100), new Sale(2, "10-01", "US", "B", 150), new Sale(3, "10-02", "KR", "A", 200), new Sale(4, "10-02", "JP", "C", 80), new Sale(5, "10-03", "US", "B", 300), new Sale(6, "10-03", "KR", "A", 50)); Map<String, Integer> byRegion = rows.stream() .collect(Collectors.groupingBy(Sale::region, TreeMap::new, Collectors.summingInt(Sale::amount))); System.out.println("집계: " + byRegion); // 집계: {JP=80, KR=350, US=450} int[] amount = rows.stream().mapToInt(Sale::amount).toArray(); // 컬럼 지향: amount 열만 모은 배열 System.out.println("amount 열: " + Arrays.toString(amount) + " 합계 " + Arrays.stream(amount).sum()); // amount 열: [100, 150, 200, 80, 300, 50] 합계 880
자바에서는 레코드 목록(행 지향)에 스트림으로 groupingBy와 summingInt를 적용해 지역별 합계를 구합니다. TreeMap을 써서 키 순서로 JP, KR, US가 출력되고 값은 파이썬 결과와 같습니다. 이 방식은 Sale 객체를 하나씩 따라가며 region과 amount를 읽습니다. 아래쪽처럼 amount만 모은 배열로 합계를 구하면 필요한 열만 읽는 컬럼 지향의 모습을 흉내 낼 수 있고, 합계 880은 세 지역의 합 350 + 450 + 80과 같습니다. 많은 분석 엔진은 열 단위 저장과 압축을 쓰고, 일부는 벡터화된 계산도 함께 씁니다. 이 코드는 개념 설명용입니다.
Q1
OLTP(Online Transaction Processing)의 특징은?
Q2
OLAP(Online Analytical Processing)의 특징은?
Q3
컬럼 지향(Columnar) 저장 방식이 OLAP에 유리한 이유는?