← 기술 지식

기술 지식 / 데이터 시스템 / 데이터베이스

데이터 모델과 트랜잭션 기초

업무 사실의 수명과 질의를 데이터 모델로 표현하고 인덱스·트랜잭션·동시성·복구 경계를 하나의 주문 흐름으로 연결합니다.

데이터베이스 · 원리·구현·운영

자료 대조일 · 적용 버전과 실행 여부는 본문의 검증 범위에 명시합니다.

기술 지식 트리

데이터 모델의 출발점

데이터 모델은 값을 저장하는 모양만 정하는 일이 아니라 어떤 사실을 누가 식별하고 언제까지 유효하게 볼지 정하는 일입니다. 주문 시스템을 예로 들면 고객, 주문, 주문 항목, 결제와 재고는 서로 다른 사실이며 변경 주기와 보존 기간도 다릅니다.

먼저 업무 규칙을 문장으로 씁니다. “한 주문은 하나의 고객에 속한다”, “한 주문 항목은 특정 상품의 당시 가격을 보존한다”, “재고가 1 이상일 때만 한 개를 차감한다”처럼 허용 상태를 적습니다. 이 문장이 함수 종속, 키, 외래키, 조건부 갱신과 검증 기준으로 내려갑니다.

후보키는 행 전체를 결정하는 속성 집합 중 더 줄일 수 없는 집합입니다. 기본키를 하나 골랐다고 다른 업무 유일성 규칙이 없어지지는 않습니다. 샘플에 이메일 중복이 없다는 관찰만으로 이메일이 영구적인 키라고 단정하지 않고, 테넌트 범위·기간·재사용 정책을 확인합니다.

현재 정보와 역사적 사실도 분리합니다. 상품의 현재 이름을 조회하는 것과 주문 당시 상품명·가격을 보존하는 것은 다른 요구입니다. 당시 가격을 현재 상품 테이블과 매번 조인해 얻으려 하면 과거의 의미가 바뀌므로 주문 항목에 당시 값을 저장할 수 있습니다.

저장 모델과 질의 방향

관계형 모델은 행과 관계를 키·제약·조인으로 표현합니다. 키-값 모델은 키 하나로 값을 읽거나 바꾸는 경로가 중심이며, 값 내부 검색은 별도 인덱스나 애플리케이션 처리가 필요할 수 있습니다. 문서 모델은 주문과 항목처럼 함께 읽는 중첩 구조를 하나의 문서로 표현하지만, 다른 문서에 복제한 고객 정보의 최신성은 별도 문제가 됩니다.

와이드 컬럼(wide-column) 모델은 보통 파티션 키와 행 안의 정렬 키를 중심으로 많은 값을 묶습니다. 고객 42의 시간순 사건을 (customer_id=42, event_time)으로 저장하면 그 고객의 범위 조회가 자연스럽지만, 모든 고객의 특정 사건 종류를 찾으려면 다른 질의용 배치가 필요할 수 있습니다. 제품마다 컬럼 패밀리·timestamp·갱신의 의미가 다르므로 한 엔진의 API를 전체 모델의 규칙으로 일반화하지 않습니다. 분석용 컬럼 지향 저장은 열 단위 압축·스캔에 초점이 있으며 와이드 컬럼 모델과 같은 분류가 아닙니다.

그래프 데이터 모델은 정점·간선과 그 속성을 저장해 관계를 따라가는 질의를 표현합니다. 사용자→팀→프로젝트처럼 여러 단계를 탐색할 때 편하지만, 고차수 정점에서 무제한 확장하면 결과와 중간 상태가 폭증합니다. 관계 깊이·방문 수·결과 한도와 순환 처리를 질의 계약에 넣어야 합니다.

요구 모델의 자연스러운 접근 추가로 지킬 계약
주문·재고의 공동 변경 관계형 행·제약·트랜잭션 격리 수준·동시 재시도
ID별 설정 조회 키-값 조회 값 크기·만료·조건부 갱신
주문 본문과 항목 조회 문서 단위 조회 문서 경계 밖 중복·갱신
고객별 시간 범위 파티션 키·정렬 키 큰 파티션·핫키·다른 질의
연결 관계의 다단계 탐색 정점·간선 순회 확장 폭·순환·조회 권한

이는 제품 추천 순위가 아닙니다. 같은 주문 예제로 읽을 범위·함께 바꿀 범위·실패 후 복구할 범위를 먼저 정하고, 해당 제품이 실제 제공하는 인덱스·거래·일관성 계약을 대조합니다.

정규화와 관계 표현

학생과 강좌와 수강 신청을 한 행에 모두 복사하면 강좌명 변경 때 여러 행을 고쳐야 합니다. 학생 자체, 학기별 개설, 학생과 개설 강좌 사이의 신청이라는 사실을 각각 나누고 신청 테이블에는 관계의 속성인 신청일과 성적을 둡니다.

주문 관계에서도 (order_id, product_id)가 상세 행을 식별한다고 할 때 상품명은 product_id에, 고객명은 customer_id에 의존합니다. 상세 행에 모두 복사하면 부분 종속과 이행 종속이 생깁니다. 1NF·2NF·3NF는 표를 무조건 많이 만드는 규칙이 아니라 갱신·삽입·삭제 이상을 줄이고 제약을 명확히 하는 도구입니다.

3NF를 확인할 때는 선택한 기본키만 보지 않습니다. 모든 후보키와 prime attribute, 즉 어떤 후보키에라도 포함되는 속성을 기준으로 비자명 함수 종속을 검토합니다. surrogate ID를 하나 추가해도 원래 업무 속성 사이의 종속이 사라지는 것은 아닙니다.

외래키는 자식이 참조하는 부모가 존재하는지를 지키지만, 삭제 시점과 보존 기간을 자동으로 결정하지는 않습니다. CASCADE, RESTRICT, SET NULL은 각각 다른 데이터 수명과 위험을 갖습니다. 주문·결제 이력을 계정 삭제와 함께 지울지 익명화할지는 업무 정책으로 명시합니다.

업무 사실에서 테이블과 제약으로 내려갑니다업무 규칙: 식별·수명·허용 상태. 독립 사실과 관계: 고객·주문·상품·재고. 키·외래키·제약. 트랜잭션 갱신사실 분리관계 표현변경 경계업무 규칙식별·수명·허용 상태독립 사실과 관계고객·주문·상품·재고키·외래키·제약트랜잭션 갱신
업무 사실에서 테이블과 제약으로 내려갑니다 화살표는 모델링 순서입니다. 사실의 수명과 식별 범위를 먼저 정해야 정규화와 외래키가 같은 의미를 갖습니다.
그림을 글로 읽기
  • 업무 규칙: 식별·수명·허용 상태
  • 독립 사실과 관계: 고객·주문·상품·재고
  • 키·외래키·제약
  • 트랜잭션 갱신
  1. 업무 규칙 → 독립 사실과 관계: 사실 분리
  2. 독립 사실과 관계 → 키·외래키·제약: 관계 표현
  3. 키·외래키·제약 → 트랜잭션 갱신: 변경 경계

인덱스와 조회 경로

인덱스는 본문과 별도의 탐색 구조입니다. 특정 고객의 주문을 찾을 때 모든 행을 읽는 대신 관련 키 구간으로 들어갈 수 있지만, 인덱스 자체의 저장 공간과 삽입·삭제·갱신 비용이 생깁니다. 인덱스가 많다고 모든 질의가 빨라지지는 않습니다.

복합 B-tree 인덱스 (customer_id, created_at, id)는 고객별로 묶은 뒤 시각과 ID 순으로 정렬합니다. 고객을 고정하고 최신 20건을 가져오는 질의에는 후보가 자연스럽지만, 전체 고객의 최신 주문을 같은 인덱스의 마지막에서 찾을 수 있다는 뜻은 아닙니다. 앞 컬럼이 전체 범위를 가르면 뒤 컬럼의 전역 정렬과 별도 정렬 필요성이 달라집니다.

키셋 페이지는 마지막 (created_at,id)보다 작은 다음 구간을 조건으로 삼아 큰 OFFSET의 앞부분을 반복해서 건너뛰는 비용을 줄입니다. 같은 시각을 구분하는 보조 키가 없으면 누락과 중복 경계를 정할 수 없습니다. 키셋은 페이지 간 고정 snapshot을 자동으로 제공하지 않으므로 변경·삭제와 replica 지연을 별도로 다룹니다.

covering 인덱스는 질의에 필요한 값을 인덱스에서 모두 얻어 원본 행 접근을 줄이는 형태입니다. 그래도 엔진에 따라 가시성 확인을 위한 원본 접근이 남을 수 있습니다. seek, scan이라는 이름만 보고 결론 내리지 말고 후보 행, 실제 행 접근, 정렬, spill, 최종 반환량을 실행 계획과 함께 봅니다.

인덱스를 추가한 뒤 읽기 시간이 줄어도 쓰기 지연과 저장 공간이 늘 수 있습니다. 고객별 최신 주문, 전체 최신 주문, 작은 고객과 큰 고객, cold cache와 warm cache를 같은 데이터와 바인드로 비교하고, 인덱스 생성·삭제의 운영 잠금과 디스크 여유도 포함합니다. 클러스터링·행 locator의 의미는 엔진별로 달라 일반 규칙으로 바꾸지 않습니다.

트랜잭션과 원자성

트랜잭션은 여러 DB 작업을 하나의 확정 단위로 묶습니다. 재고 차감과 판매 기록을 각각 성공시키면 하나만 남을 수 있으므로, 같은 거래 안에서 모두 커밋되거나 모두 롤백되어야 합니다. 하지만 트랜잭션을 사용했다는 사실만으로 다른 거래가 오래된 값을 덮어쓰지 않는 것은 아닙니다.

업무 단계는 보통 실행 중, 실패, 커밋 요청 이후, 커밋 완료, 중단으로 나뉘어 생각할 수 있습니다. 커밋 명령을 보냈다는 상태와 실제로 커밋이 완료되어 복구 가능한 상태를 같은 것으로 취급하지 않습니다. 정확한 상태 전이·내구 지점은 DB 제품과 설정의 계약으로 확인해야 합니다.

재고가 10인 상품을 A와 B가 동시에 읽고 각각 9를 계산해 저장하면 최종 재고 9에 판매 두 건이 남을 수 있습니다. 이것이 갱신 유실입니다. 현재 값에서 한 개를 차감하는 문제라면 애플리케이션이 과거 값을 저장하지 않고 조건부 UPDATE를 사용합니다.

UPDATE inventory
SET remaining = remaining - 1
WHERE id = :id
  AND remaining >= 1;

영향받은 행이 1이면 차감이 반영된 것이고 0이면 대상 부재나 재고 조건 실패입니다. 판매 기록도 같은 거래에 포함해야 합니다. 다만 응답 유실로 같은 논리 요청을 다시 보내는 중복 실행은 별도 문제이므로, 요청 ID와 dedupe 기록이 필요합니다.

ACID와 거래 상태

ACID는 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 내구성(Durability)을 구분하는 용어입니다. 원자성은 관련 DB 변경의 전부 또는 전무, 일관성은 정의된 제약을 만족하는 상태 전이, 격리성은 동시 거래의 관찰·충돌 규칙, 내구성은 성공한 변경의 약속된 장애 후 보존을 뜻합니다. DB가 업무 규칙을 저절로 알아내는 것은 아니며, ACID를 쓴다는 말만으로 외부 결제까지 한 거래에 들어가지 않습니다.

교과서의 Active → Partially Committed → Committed는 실행·마지막 명령 종료·최종 확정을 나누는 개념 모형입니다. 오류가 나면 Failed → Aborted로 되돌리는 경로를 생각할 수 있습니다. 제품 API의 실제 상태 이름은 다를 수 있고, 클라이언트의 응답 timeout은 이 중 하나를 확정해 주지 않습니다. 서버는 이미 커밋했는데 클라이언트만 결과를 모르는 경우를 위해 논리 요청 ID와 결과 조회를 둡니다.

격리와 동시성

격리는 동시에 실행되는 거래가 서로의 중간 상태와 충돌을 어떻게 보는지에 대한 계약입니다. MVCC는 다중 버전 동시성 제어로, 이전 버전을 잠시 보존하고 각 읽기가 스냅샷 규칙에 맞는 버전을 선택하게 합니다. “잠금이 없다”는 뜻이 아니라 읽기와 쓰기의 버전 경계를 다르게 관리한다는 뜻입니다.

문장별 스냅샷과 거래 전체 스냅샷은 같은 이름의 격리 설명만으로 결과를 결정할 수 없습니다. A가 100을 읽은 뒤 B가 120으로 커밋하고 A가 다시 읽을 때, 문장별 정책은 120을, 같은 스냅샷 정책은 100을 볼 수 있습니다. 실제 엔진·버전·격리 수준·autocommit을 고정해야 합니다.

두 거래가 각각 다른 행을 갱신해도 전체 규칙이 깨질 수 있습니다. 당직자 A와 B가 서로 상대방이 근무 중임을 보고 동시에 퇴근하면 각 행은 정상이어도 “최소 한 명” 조건은 위반됩니다. 이를 쓰기 편향이라고 하며, 자기 행 version 검사나 자기 행 잠금만으로 충분하지 않습니다.

해결 방법은 공통 count 행의 조건부 감소, 관련 집합의 잠금, 직렬화 격리와 거래 전체 재시도입니다. 직렬화 실패 뒤에는 같은 UPDATE만 재전송하지 않고 새 거래에서 읽기와 판단을 다시 수행해야 합니다. 외부 결제·메일은 DB 롤백으로 취소되지 않을 수 있으므로 거래 재시도 함수 안에서 직접 중복 실행하지 않습니다.

재고 갱신의 판단과 확정 경계요청 A·B: 같은 상품·수량 1. 조건부 차감: remaining >= 1. 판매 기록. 조건 실패·충돌. 같은 거래의 확정동시 시도영향 행 1영향 행 0원자 커밋요청 A·B같은 상품·수량 1조건부 차감remaining >= 1판매 기록조건 실패·충돌같은 거래의 확정
재고 갱신의 판단과 확정 경계 화살표는 두 요청이 같은 재고를 처리하는 흐름입니다. 조건부 갱신은 읽은 숫자를 저장하는 대신 DB 시점의 조건을 함께 평가합니다.
그림을 글로 읽기
  • 요청 A·B: 같은 상품·수량 1
  • 조건부 차감: remaining >= 1
  • 판매 기록
  • 조건 실패·충돌
  • 같은 거래의 확정
  1. 요청 A·B → 조건부 차감: 동시 시도
  2. 조건부 차감 → 판매 기록: 영향 행 1
  3. 조건부 차감 → 조건 실패·충돌: 영향 행 0
  4. 판매 기록 → 같은 거래의 확정: 원자 커밋

복구와 Replica 읽기

WAL은 변경을 복구할 로그를 데이터 페이지보다 먼저 내구화할 수 있게 하는 write-ahead 순서입니다. 로그 레코드 생성, 약속한 flush, commit 응답, 데이터 페이지 기록, replica 적용은 서로 다른 사건입니다. 메모리나 OS cache에만 기록된 성공을 장치 내구성과 같은 뜻으로 보지 않습니다.

group commit은 여러 거래의 로그를 한 번의 flush에 포함해 왕복 비용을 나눌 수 있지만, 각 거래가 약속한 내구 지점까지 포함된 뒤 응답해야 합니다. checkpoint는 복구 시작 범위와 더티 페이지 처리를 조정하는 지점이지 백업 자체가 아닙니다. 시점 복구에는 기준 백업과 목표 지점까지의 연속 로그가 필요합니다.

원본에서 version 8을 커밋하고 성공을 반환해도 replica가 version 7을 읽을 수 있습니다. 본인 쓰기 관찰이 필요하면 커밋을 포함하는 최소 로그 위치 또는 검증된 토큰을 전달하고 replica의 실제 replay 위치가 그 지점에 도달했는지 확인합니다. 단순한 시간 지연이나 클라이언트 시각은 강한 보장이 아닙니다.

비동기 복제에서 원본이 성공 응답한 로그가 승격된 새 leader에 없으면 그 쓰기가 사라질 수 있습니다. dedupe 기록도 함께 사라질 수 있으므로 외부 결제 원장과 논리 요청 ID로 대사합니다. 오래된 token을 새 timeline의 숫자와 바로 비교하지 않고, 무한 대기 대신 명시적 불확정 결과와 복구 경로를 둡니다.

주문 흐름의 설계

주문 생성 흐름을 다음처럼 구성할 수 있습니다. 요청 ID를 먼저 검증하고, 재고와 주문·판매 기록을 같은 거래에서 조건부로 변경하며, outbox에 후속 발행 사실을 남깁니다. 거래가 커밋된 뒤 별도 worker가 outbox를 읽어 메시지를 발행하고, 외부 시스템은 논리 ID로 중복을 제거합니다.

createOrder(requestId, itemId, quantity):
    begin transaction
    existing = find_result_by_request_id(requestId)
    if existing exists: commit; return existing
    changed = conditional_decrement(itemId, quantity)
    if changed != 1: rollback; return sold_out
    order = insert_order(requestId, itemId, quantity)
    insert_outbox(requestId, order.id)
    commit
    return order

위 의사코드는 설명용이며 실제 DB 드라이버의 연결·예외·rollback·고유 제약 오류 처리가 필요합니다. 같은 requestId의 동시 요청은 조회 후 삽입 사이에 경합하므로 요청 ID UNIQUE와 충돌 재조회가 함께 필요합니다. 외부 결제는 거래 안에서 호출하지 않고, 결제 결과를 별도 상태 전이와 대사로 관리합니다.

검증과 운영

검증에서는 두 연결을 첫 읽기 직후 멈추고 갱신 순서를 고정합니다. 갱신 유실, 조건부 재고 차감, version 충돌, 쓰기 편향, replica stale read를 각각 독립 시험합니다. 실행 결과에는 DB 제품·정확한 버전·격리 수준·autocommit·SQL·오류를 남기며, 이 문서의 숫자 trace는 논리 예이지 이 문서의 작성 과정에서 특정 DB를 실행한 결과가 아닙니다.

운영 중에는 인덱스 plan regression, lock wait·deadlock, 충돌률·재시도 횟수, transaction age·version 공간, WAL flush 지연, replica receive/replay 차이, outbox 적체와 대사 실패를 분리해서 봅니다. 하나의 “DB가 느리다” 지표로 모델·조회·동시성·복구 문제를 섞지 않습니다.

참고 자료와 검증 범위

이 장은 특정 DB 제품에서 트랜잭션·복제·전원 장애·시점 복구를 실행한 결과가 아닙니다. 적용 가능한 엔진 버전과 격리 설정을 고정한 뒤 두 연결 실험, 실행 계획, 실제 restore와 대사를 수행해야 합니다.

후속·관련 개념

함께 읽을 문서

분야별 학습 경로

전체 기술 지식