데이터베이스 구축 3강. 물리 설계와 인덱스·트랜잭션
은행 계좌 이체 도중에 정전이 나면 돈은 어디에 있을까요. 이 질문에 답하기 위해 트랜잭션이라는 개념이 만들어졌습니다.
풀고 시작
물리 설계는 저장 장치와의 협상입니다
논리 설계가 끝나면 실제 저장 구조를 정합니다. 어떤 자료형을 쓸지, 어떤 컬럼에 인덱스를 걸지, 파일을 어떻게 배치할지, 용량은 얼마나 잡을지를 결정하는 단계죠. 논리 설계가 "무엇을 저장할까"라면 물리 설계는 "어떻게 빨리 꺼낼까"입니다.
물리 설계에서 가장 중요한 결정이 인덱스입니다. 책 뒤의 찾아보기와 같습니다. 인덱스가 없으면 원하는 값을 찾기 위해 표를 처음부터 끝까지 훑어야 합니다(전체 테이블 스캔). 인덱스가 있으면 정렬된 색인을 타고 바로 갈 수 있죠.
구조로는 B트리 인덱스가 표준입니다. 균형 잡힌 트리라 어느 값을 찾아도 비슷한 깊이만 내려가면 됩니다. 비트맵 인덱스는 값의 종류가 적은 컬럼, 예컨대 성별이나 상태 코드에 유리합니다. 해시 인덱스는 정확히 일치하는 값을 찾을 때는 빠르지만 범위 조회에는 쓸 수 없습니다.
인덱스에는 대가가 있습니다. 조회는 빨라지지만 삽입·수정·삭제는 느려집니다. 데이터가 바뀔 때마다 인덱스도 함께 갱신해야 하니까요. 저장 공간도 추가로 듭니다. 그래서 "일단 다 걸어 두자"는 접근은 틀렸습니다. 조건절과 조인, 정렬에 자주 쓰이는 컬럼에 선별적으로 겁니다.
트랜잭션의 네 글자, ACID
계좌 A에서 B로 10만 원을 옮긴다고 합시다. A에서 빼고 B에 더하는 두 연산 사이에 정전이 나면 돈이 증발합니다. 이 사고를 막기 위해 트랜잭션은 여러 연산을 하나의 논리적 작업 단위로 묶습니다.
| 특성 | 뜻 |
|---|---|
| 원자성 (Atomicity) | 전부 반영되거나 전혀 반영되지 않는다 |
| 일관성 (Consistency) | 실행 전후로 데이터베이스가 모순 없는 상태를 유지한다 |
| 격리성 (Isolation) | 수행 중인 트랜잭션에 다른 트랜잭션이 끼어들 수 없다 |
| 지속성 (Durability) | 성공적으로 완료된 결과는 영구히 보존된다 |
트랜잭션의 상태도 자주 나옵니다. 실행을 시작한 활동, 마지막 연산까지 끝낸 부분 완료, 결과를 확정한 완료, 오류로 중단된 실패, 그리고 시작 이전으로 되돌린 철회입니다. 결과를 확정하는 명령이 COMMIT, 되돌리는 명령이 ROLLBACK입니다.
동시성 제어는 순서를 지켜 주는 일입니다
여러 트랜잭션이 동시에 같은 데이터를 건드리면 문제가 생깁니다. 갱신 손실은 두 트랜잭션이 같은 값을 고쳐 한쪽 결과가 사라지는 현상, 오손 읽기(dirty read)는 아직 확정되지 않은 값을 다른 트랜잭션이 읽는 현상, 모순성은 한 트랜잭션이 같은 값을 두 번 읽었는데 결과가 다른 현상입니다.
해결책의 기본은 로킹(locking)입니다. 데이터를 쓰기 전에 잠그고, 끝나면 풉니다. 잠그는 단위를 로킹 단위라고 하는데, 단위가 크면 관리가 쉬운 대신 동시성이 떨어지고, 단위가 작으면 동시성이 올라가는 대신 관리 부담이 커집니다. 이 맞교환이 그대로 문제로 나옵니다.
2단계 로킹 규약은 잠금을 얻기만 하는 확장 단계와 풀기만 하는 축소 단계로 나눕니다. 이렇게 하면 직렬 가능성이 보장되지만, 서로가 서로의 잠금을 기다리는 교착 상태(deadlock)가 생길 수 있습니다. 교착 상태를 다루는 방법은 예방, 회피, 탐지와 회복으로 나뉩니다.
타임스탬프 순서 규약은 다른 접근입니다. 트랜잭션마다 도착 시각을 붙여 두고 그 순서대로 처리하게 강제하므로 교착 상태가 아예 생기지 않습니다. 대신 순서를 어긴 트랜잭션은 철회되고 다시 시작해야 합니다.
회복은 로그가 있어야 가능합니다
장애가 나면 데이터베이스를 정상 상태로 되돌려야 합니다. 이때 쓰는 것이 로그입니다. 무엇을 어떻게 바꿨는지 기록해 두었다가, 완료된 트랜잭션은 다시 반영하고(REDO), 완료되지 못한 트랜잭션은 되돌립니다(UNDO).
즉시 갱신 기법은 변경을 곧바로 데이터베이스에 반영하므로 회복 시 REDO와 UNDO가 모두 필요합니다. 지연 갱신 기법은 커밋 전까지 실제 반영을 미루므로 UNDO가 필요 없습니다. 그 외에 특정 시점의 상태를 통째로 저장해 두는 검사점 기법과 그림자 페이징 기법이 있습니다.
다음 강에서는 이 데이터베이스를 실제로 다루는 언어, SQL로 들어갑니다.
인출 문제
생각해볼 질문 (정답 없음)
동시성을 높이려면 잠금을 잘게 걸어야 하고, 안전을 높이려면 크게 걸어야 합니다. 어떤 서비스에서 어느 쪽으로 기울이시겠습니까. 그 판단은 무엇을 측정해야 정당해질까요.
이전: 2강 정규화와 이상 현상 · 다음: 4강 SQL DDL과 DML