13.1 파일 구성 (File Organization)
데이터베이스는 결국 디스크 위의 파일들의 집합입니다. 그 안을 어떻게 채울까요?
계층 구조
- Database
- 파일(file)들의 집합.
- File
- 레코드(record)들의 시퀀스.
- Record
- 필드(field)들의 시퀀스.
- Field
- 하나의 속성 값.
파일은 고정 길이 블록(fixed-length block)들로 나뉘고, 블록이 저장 할당과 데이터 전송의 단위입니다.
단순화 가정 (먼저 이것부터)
- 고정 길이 레코드(fixed-length record)만 다룬다.
- 한 파일에는 하나의 릴레이션(relation)만 저장한다.
- 각 레코드는 블록(block)보다 작다.
이 가정을 깨는 경우(가변 길이, 큰 객체 등)는 뒤 섹션에서 차례로 풀어 갑니다.
디스크 I/O는 블록(block) 단위로 일어납니다. 레코드 하나를 읽어도 그 레코드가 든 블록 전체가 읽힙니다. 그래서 "어떤 레코드를 어느 블록에 모아 둘 것인가"가 성능을 좌우합니다.
13.2 고정 길이 레코드 (Fixed-Length Records)
모든 레코드 크기가 같으면 위치 계산이 산수입니다.
예: instructor 레코드 크기가 n바이트라면 record0은 0, record1은 n, record2는 2n … 에 위치합니다. 임의 접근(random access)이 단순 곱셈으로 끝납니다.
삭제(deletion)는 어떻게? — 3가지 옵션
레코드 i를 삭제하면 그 자리에 구멍이 생깁니다. 아래 토글로 instructor 12개 레코드에서 각 방식을 비교해 보세요.
① 시프트(shift)
i+1 … n 레코드를 i … n−1로 한 칸씩 당김. 연속성 유지, 그러나 이동량이 많아 비용이 크다.
② 마지막 레코드로 대체
마지막 레코드 n을 빈 자리 i로 이동. 딱 1개만 이동(저비용)하지만 물리 순서가 깨진다. (예: record3 자리에 record11)
③ free list
삭제된 빈 자리들을 포인터로 연결. 파일 헤더가 첫 빈 레코드를 가리키고 각 빈 레코드가 다음 빈 레코드를 가리킴, 마지막은 null.
예: record 1, 4, 6을 삭제하면 header → 1 → 4 → 6 → null. 새 레코드 삽입 시 header가 가리키는 빈 자리를 재사용하고 header를 그 다음 자리로 갱신합니다. 이동이 전혀 없습니다.
13.3 가변 길이 레코드 (Variable-Length Records)
varchar 하나만 있어도 더 이상 n×i가 통하지 않습니다.
발생 원인
- 한 파일에 여러 레코드 타입이 섞일 때.
- 가변 길이 필드(예:
varchar)가 있을 때. - 반복 필드(repeating field)가 있을 때.
표현 방식
속성을 정의 순서대로 둔다. 가변 길이 속성은 고정 크기 (offset, length) 쌍으로 헤더에 두고, 실제 데이터는 고정 길이 필드 뒤에 모아 둔다.
NULL 값은 별도 null-value bitmap으로 표시한다(해당 비트=1이면 null).
예제: instructor 레코드 한 개
// 헤더(고정 길이 영역): 가변 속성은 (offset, length) byte 0 : (21, 5) → ID = "10101" (offset 21, len 5) byte 4 : (26, 10) → name = "Srinivasan" (offset 26, len 10) byte 8 : (36, 10) → dept_name = "Comp. Sci." (offset 36, len 10) byte 12 : 65000 → salary (고정 길이, 그 자리에 직접) byte 20-21 : 0000 → null bitmap (모두 0 = null 없음) // 이어서 가변 데이터 영역: offset 21 : 10101 | Srinivasan | Comp. Sci.
고정 크기 헤더만 보면 모든 속성의 위치를 알 수 있어, 레코드 길이가 달라도 속성 접근이 O(1)입니다. 가변 데이터가 실제로 얼마나 긴지는 length가 알려 줍니다.
BLOB(binary)/CLOB(character) 같은 큰 객체는 레코드(=페이지보다 작음)에 통째로 담기 어렵습니다. 대안: ① 파일 시스템 파일로 저장, ② DB가 관리하는 파일에 저장, ③ 조각(chunk)으로 분할해 저장. 레코드 안에는 보통 그 위치를 가리키는 포인터만 둡니다.
13.4 슬롯 페이지 (Slotted Page Structure)
가변 길이 레코드를 한 페이지 안에서 자유롭게 옮길 수 있게 하는 표준 구조입니다.
① 레코드 엔트리 개수 ② free space의 끝 위치 ③ 각 레코드의 위치(offset)와 크기(size).
레코드 데이터는 페이지 끝(높은 주소)부터 아래로 채워지고, 헤더(슬롯 디렉터리)는 앞에서부터 자랍니다. 외부 포인터는 레코드를 직접 가리키지 않고 헤더의 슬롯 엔트리를 가리킵니다(간접 참조). 그래서 페이지 안에서 레코드를 옮겨도 헤더만 갱신하면 됩니다 — 외부 포인터는 그대로 유효.
삽입 시뮬레이터 — KEY 100 → 300 → 200
4096바이트 페이지에 세 레코드를 차례로 삽입합니다. 물리 위치는 삽입 순서대로 정해지지만 슬롯은 KEY 논리 순서로 정렬됩니다. "다음"을 눌러 단계별로 확인하세요.
KEY 100
offset 4000에 배치. # records = 1, slot0 → 4000.
KEY 300
offset 3000에 배치. # records = 2, slot = [4000, 3000].
KEY 200
offset 2500에 배치. 슬롯만 재정렬 → slot = [4000, 2500, 3000] (= KEY 100, 200, 300).
최종 물리 배치는 @2500=KEY200, @3000=KEY300, @4000=KEY100 으로 삽입 순서를 따릅니다. 그러나 슬롯 배열은 KEY 오름차순(100→200→300)으로 정렬됩니다. 재정렬되는 것은 슬롯 엔트리뿐, 레코드 데이터는 움직이지 않습니다.
13.5 파일 내 레코드 구성 (Organization of Records in Files)
레코드를 파일 안 어디에, 어떤 순서로 둘 것인가.
| 방식 | 배치 규칙 | 특징 |
|---|---|---|
| Heap | 공간 있는 곳 아무데나 | 삽입 빠름. 할당 후 보통 이동 안 함. 검색은 전체 스캔. |
| Sequential | search key 정렬 순서 | 범위 검색 유리. 삽입 시 overflow block 필요. |
| B+-Tree | 동적으로 정렬 유지 | 삽입/삭제에도 효율적 정렬. 자세히는 Ch14. |
| Hashing | hash 값이 블록 결정 | unordered, 등호 검색 빠름. 범위 검색엔 불리. |
Heap — Free-Space Map (2단계)
Heap에서 "어느 블록에 빈 공간이 있나?"를 매번 스캔하면 느립니다. free-space map은 블록당 1엔트리 배열로 free 비율을 기록합니다(예: 3비트면 값/8 만큼 비어 있음). 큰 파일은 2단계 맵으로 요약합니다.
// 1차 맵 (블록당 1엔트리, 16블록) [ 4 2 1 4 | 7 3 6 5 | 1 2 0 1 | 1 0 5 6 ] // 2차 맵 (4블록씩 묶어 그 구간의 최대값을 기록 → 최대 4엔트리) [ 4 7 2 6 ] // 충분한 free 블록을 찾을 때 2차 맵으로 후보 구간부터 좁힌다.
free-space map은 주기적으로만 디스크에 기록하므로 일부 항목이 최신이 아닐(stale) 수 있습니다. 그래도 "확실히 빈 곳"을 찾는 힌트로는 충분합니다 — 정확한 free 양은 실제 블록을 확인할 때 정정됩니다.
Sequential — 정렬 + Overflow Block
search-key 순으로 정렬하고 포인터 체인으로 논리 순서를 잇습니다. 삭제는 체인을 갱신. 삽입은 위치를 찾아 ① 그 블록에 free 공간이 있으면 제자리, ② 없으면 overflow block에 두고 체인을 갱신합니다. 시간이 지나면 체인이 길어져 주기적 재구성(reorganize)이 필요합니다.
// ... 22222 Einstein Physics 95000 → (overflow) 32222 Verdi Music 48000 // 32343 El Said History 60000 ... 논리 순서상 32222(Verdi)는 22222와 32343 사이지만, 물리적으로는 overflow block에 있고 포인터로 연결된다.
Partitioning (파티셔닝)
큰 릴레이션을 작은 릴레이션들로 분할합니다(예: transaction_2023, transaction_2024). 장점:
- selection 가지치기:
year = 2024조건이면 한 파티션만 본다. - free space 관리 등 운영 비용 감소.
- 파티션별 다른 장치 배치 가능(현재=SSD, 과거=HDD).
13.6 데이터 사전 (Data Dictionary)
데이터에 관한 데이터 — 메타데이터(metadata)를 담는 system catalog.
무엇을 저장하나 (metadata)
- 릴레이션 정보: 이름, 속성의 이름/타입/길이, 뷰 정의, 무결성 제약.
- 사용자/계정 정보: 사용자, 권한, 비밀번호.
- 통계(statistics): 릴레이션의 튜플 수 등.
- 물리적 구성: sequential/hash 등 파일 구성과 저장 위치.
- 인덱스(index) 정보.
메타데이터 릴레이션 5개
- Relation_metadata
- 릴레이션 단위 정보.
- Attribute_metadata
- 속성 정보 (relation_name → FK).
- Index_metadata
- 인덱스 정보 (relation_name → FK).
- View_metadata
- 뷰 정의.
- User_metadata
- 사용자/계정.
데이터 사전 자체도 릴레이션(테이블)들로 저장됩니다 — DB가 자기 자신의 구조를 데이터로 들고 있는 셈입니다. Attribute_metadata·Index_metadata의 relation_name은 Relation_metadata를 가리키는 외래키(FK)입니다.
13.7 버퍼 매니저 (Buffer Manager)
디스크 블록을 메모리로 끌어올려 다루고, 다시 디스크로 내려보내는 살림꾼.
- Block
- 저장 할당과 디스크 I/O의 단위. 목표는 블록 전송 횟수 최소화.
- Buffer
- 디스크 블록의 사본을 두는 메인 메모리의 일부.
- Buffer manager
- 버퍼 공간 할당을 담당. 내부 hash table로 "어느 블록이 어느 프레임에 있는지" 추적. 블록이 버퍼에 있으면 주소를 반환, 없으면 교체(필요 시 write back) 후 읽어 반환.
Case 1 요청 블록이 이미 버퍼에 있음 → 디스크 I/O 없이 직접 반환.
Case 2 버퍼에 없고 free 프레임 있음 → free 프레임에 적재(disk read).
Case 3 버퍼에 없고 free 없음 → unpinned 블록을 교체(victim이 dirty면 먼저 write back) 후 적재.
색상: 점선=free(미할당) · 흰색=clean(미수정) · 파랑 D=dirty(수정됨, 미기록). Read/Write 후 어떤 case가 발생했는지 아래 설명에 표시됩니다.
Write & Commit
- Write: 버퍼 내 블록을 수정 → dirty(D)로 표시. 디스크엔 아직 안 씀.
- Commit: dirty 블록을 모두 flush(디스크 기록) → D 해제.
Pinned block & Pin count
Pinned block = 지금 디스크로 내릴 수 없는 블록. 읽기/쓰기 직전 Pin, 끝나면 Unpin. 버퍼 매니저는 블록마다 pin count를 유지합니다.
pin count == 0일 때만 evict(교체) 가능.
Readers = shared(S), Writers = exclusive(X). X는 한 프로세스만 — S와도 동시 불가. S는 X와 동시에 잡을 수 없지만, S끼리는 여러 프로세스가 동시에 가질 수 있습니다.
버퍼가 꽉 찼을 때 교체할 victim은 반드시 pin count == 0(unpinned)이어야 합니다. 모두 pinned면 교체 불가 → 적재 실패. victim이 dirty면 먼저 write back한 뒤 새 블록을 적재합니다(clean이면 그냥 덮어씀).
13.8 버퍼 교체 정책 (Replacement Strategy)
꽉 찬 버퍼에서 누구를 내보낼 것인가. 정답은 "쿼리가 다음에 뭘 쓸지"에 달려 있습니다.
대부분의 OS는 LRU(Least Recently Used)를 씁니다 — 과거 접근으로 미래를 예측. 하지만 DB 쿼리는 접근 패턴이 미리 정해져 있어, 옵티마이저의 힌트를 섞는 편이 낫습니다. 특히 nested loop join에서 LRU는 최악입니다.
내부(inner) 릴레이션을 외부 페이지마다 처음부터 끝까지 반복 스캔하는데, 버퍼가 내부보다 작으면 한 패스가 끝날 때 다음 패스에서 곧 다시 쓸 첫 페이지가 메모리에 없습니다. LRU는 "가장 오래 안 쓴 것"을 내보내므로, 곧 재사용할 그 페이지를 정확히 직전에 evict합니다 → 모든 접근이 miss가 되는 최악의 경우(disk re-read 폭증).
내부 S = s1..s4를 외부 3페이지에 걸쳐 반복 스캔(접근열 = s1 s2 s3 s4 × 3), buffer=3. 빨강 s1 = 매 패스 첫 페이지(곧 다시 쓸 페이지). 두 정책을 나란히 단계 진행하며 evict와 disk re-read 수를 비교합니다. (최종: LRU=12 vs MRU=6 reads)
Toss-immediate
블록의 마지막 레코드를 처리하자마자 즉시 evict. 다시 안 볼 블록을 빨리 비운다.
MRU (Most Recently Used)
방금 쓴 것을 먼저 교체. 반복 스캔에 유리 — 방금 본 페이지는 한동안 안 쓰이므로 버리고, 곧 다시 쓸 페이지는 그대로 보존된다.
Data dictionary 고정
데이터 사전은 매우 자주 접근되므로 버퍼에 고정(pin)해 두는 게 일반적입니다.
13.9 컬럼 지향 저장 (Column-Oriented Storage) 보충
레코드를 "행(row) 단위"로 줄 세울 것인가, "열(column) 단위"로 줄 세울 것인가 — 분석 워크로드의 판도를 바꾸는 선택.
지금까지 다룬 모든 파일 구성은 한 튜플(tuple)의 모든 속성을 인접하게 저장하는 행 지향(row-oriented, 또는 row-store) 방식이었습니다. 교재는 데이터 분석(OLAP) 워크로드를 위한 대안으로 열 지향(column-oriented, column-store) 저장을 함께 설명합니다.
행 지향 (Row-Oriented Store)
한 레코드(튜플)의 모든 속성을 인접하게 한 블록에 저장합니다. (ID, name, dept, salary)가 통째로 한 줄.
- 한 튜플 전체를 읽는 데 유리 — OLTP(단건 조회/삽입/갱신)에 적합.
- 단건
INSERT는 한 블록에 한 번 쓰면 끝. - 특정 속성 하나만 필요해도 튜플 전체(=불필요한 속성까지)를 블록째 읽어야 함.
열 지향 (Column-Oriented Store)
한 속성(컬럼)의 모든 값을 인접하게 저장합니다. 각 컬럼이 별도의 배열/파일처럼 보관되고, 같은 위치(i번째)의 값들이 한 튜플을 이룹니다.
- 쿼리에 필요한 컬럼만 읽음 → I/O 대폭 감소.
- 같은 도메인 값이 인접 → 높은 압축률.
- 특정 i번째 튜플 전체를 얻으려면 여러 컬럼에서 i번째 값을 다시 모아야(reconstruction) 함.
컬럼 스토어의 장점 4가지
| 장점 | 원리 | 효과 |
|---|---|---|
| ① 필요한 컬럼만 읽음 | 쿼리가 참조하는 속성의 파일만 스캔(column pruning). 사용 안 하는 속성은 디스크에서 건드리지 않음. | 분석 쿼리(예: AVG(salary))에서 I/O가 속성 수 비율만큼 감소. |
| ② 높은 압축률 | 같은 컬럼은 같은 도메인·유사 값이 인접 → RLE(run-length encoding), dictionary encoding, delta/bit-packing이 잘 먹힘. | 저장 공간 감소 + 압축된 채로 디스크에서 읽어 I/O 추가 절감. |
| ③ CPU 캐시 · 벡터화(SIMD) | 한 컬럼이 동일 타입 값의 조밀한 배열 → 순차 접근으로 CPU 캐시 적중률↑, SIMD 벡터 연산으로 한 명령에 여러 값 처리. | 집계·필터 연산의 CPU 처리량 향상. |
| ④ Late materialization | 튜플을 미리 재조립하지 않고, 압축·인코딩된 컬럼 표현 위에서 필터/집계를 먼저 수행한 뒤 꼭 필요한 시점에 늦게 튜플을 복원. | 중간 단계에서 다루는 데이터량을 줄여 연산 비용 절감. |
튜플 재구성(tuple reconstruction) 비용: 여러 컬럼에서 같은 위치 값을 모아 한 튜플로 합쳐야 하므로, 튜플 전체를 자주 꺼내는 워크로드(여러 속성을 다 보는 조회)에는 불리합니다. 또한 단건 삽입/갱신이 모든 컬럼 파일을 각각 건드려야 하므로 OLTP에 부적합합니다. 그래서 컬럼 스토어는 보통 대량 적재(bulk load) 후 읽기 위주로 쓰입니다.
어디에 쓰나 — OLAP / 데이터 웨어하우스
컬럼 저장은 OLAP(Online Analytical Processing)·데이터 웨어하우스에서 표준에 가깝습니다. 대표적인 형식·시스템:
- 파일 포맷: Apache Parquet, ORC — 빅데이터 생태계의 컬럼 저장 포맷.
- 시스템: C-Store / Vertica, MonetDB — 컬럼 스토어 DBMS의 대표.
행과 열의 장점을 절충하는 하이브리드(hybrid row/column) 방식도 있습니다. 대표가 PAX(Partition Attributes Across)로, 한 블록(페이지) 안에서는 컬럼별로 값을 모아 두되(블록 내 캐시·압축 이득) 한 튜플의 모든 속성은 같은 블록에 남겨, 튜플 재구성 시 블록 간 I/O가 늘지 않도록 합니다. Parquet/ORC도 큰 단위(row group/stripe) 안에서 컬럼별로 묶는 점에서 이 사상을 공유합니다.
인터랙티브 — Row vs Column 레이아웃
작은 instructor 4행(ID, name, dept, salary)을 디스크 블록에 행 단위로 vs 열 단위로 배치한 모습입니다. 아래에서 쿼리를 골라 "salary만 조회" 시 어느 배치가 더 적은 블록을 읽는지 하이라이트로 비교하세요.
가정: 한 블록에 값 4개가 들어감(데모용 단순화). 초록 테두리 = 쿼리가 실제로 읽는 블록. 행 배치는 한 컬럼만 필요해도 모든 블록을 읽지만, 열 배치는 해당 컬럼 블록만 읽습니다.
13.10 다중 테이블 클러스터링 (Multitable Clustering File Organization) 보충
"한 파일엔 한 릴레이션만"이라는 단순화 가정을 의도적으로 깨서, 자주 조인되는 두 릴레이션을 한 블록에 섞어 둡니다.
지금까지는 각 파일에 단일 릴레이션만 저장했습니다. 다중 테이블 클러스터링 파일 구성은 서로 다른 릴레이션의 관련 레코드를 같은 블록에 인접 저장하여, 특정 조인(join)의 I/O를 줄이는 기법입니다.
정의와 핵심 아이디어 — join locality
두 릴레이션 department와 instructor를 따로 저장하면, department ⨝ instructor 조인 시 각 학과에 대응하는 instructor 레코드들이 서로 다른 블록에 흩어져 있어 블록을 여러 번 읽게 됩니다. 대신 클러스터 키(cluster key)(여기서는 dept_name)가 같은 레코드들을 같은 블록에 함께(인접) 두면, 한 학과의 department 레코드와 그 학과 instructor들을 한 번의 블록 읽기로 가져올 수 있습니다. 이것이 join locality(조인 지역성)입니다.
예제 다이어그램 — Comp. Sci. 클러스터
아래는 dept_name을 클러스터 키로 한 한 블록의 모습입니다. 학과(department) 레코드 바로 뒤에 그 학과 소속 instructor들을 인접 배치합니다.
// 한 블록 (cluster key = dept_name) Comp. Sci. | Taylor | 100000 ← department 레코드 10101 | Srinivasan | 65000 ← instructor (Comp. Sci.) 45565 | Katz | 75000 ← instructor (Comp. Sci.) 83821 | Brandt | 92000 ← instructor (Comp. Sci.) Physics | Watson | 70000 ← 다음 department 레코드 22222 | Einstein | 95000 ← instructor (Physics) 33456 | Gold | 87000 ← instructor (Physics) // department(Comp.Sci.) + 그 학과 instructor들이 인접 → 조인 시 한 블록만 읽으면 됨
레코드 타입이 섞이므로 각 레코드는 자신이 어떤 릴레이션의 레코드인지 식별할 수 있어야 하고(가변 길이 레코드 표현 활용), 같은 학과 instructor들을 잇는 포인터 체인을 두어 한 학과의 레코드를 빠르게 순회하기도 합니다.
장단점과 사용 시점
✅ 장점
- 특정 조인이 빠름:
department ⨝ instructor처럼 클러스터 키로 묶이는 조인은 읽는 블록 수가 급감. - 한 학과의 department + instructor를 한 번의 I/O로 함께 적재.
⚠️ 단점
- 단일 릴레이션 전체 스캔이 느려짐: 예)
SELECT * FROM instructor는 instructor 레코드 사이사이에 department 레코드가 끼어 있어 더 많은 블록을 읽음. - 레코드 타입이 섞여 가변 구조 관리가 복잡(레코드 종류 식별, 포인터 체인 유지).
두 릴레이션이 거의 항상 함께 조인되고(특정 조인 패턴이 지배적), 각 릴레이션을 단독으로 전체 스캔하는 일은 드물 때 유리합니다. 조인 빈도와 단일-릴레이션 스캔 빈도의 트레이드오프를 보고 결정합니다. 어떤 조인을 빠르게 할지에 따라 클러스터 키를 선택합니다.
13.11 레코드/자료형 저장 표현 심화 보충
파일 구성 방식·가변 길이 레코드에서 미처 다루지 않은 표현 세부를, 중복 없이 보강합니다.
여기서는 정렬키, 엔디안(big/little-endian) 같은 세부는 생략하고, 실무에서 자주 부딪히는 표현 선택만 정확히 정리합니다.
가변 길이의 또 다른 표현 — 끝 구분자 vs 길이 prefix
13.3에서는 헤더에 (offset, length) 쌍을 두는 방식을 다뤘습니다. 가변 길이 값 자체를 표현하는 방법은 크게 두 가지입니다.
| 방식 | 표현 | 장점 | 단점 |
|---|---|---|---|
| 끝 구분자 (terminator) | 값 끝에 특별한 종료 표시(예: C 문자열의 null 바이트 \0)를 붙임. | 길이를 미리 몰라도 됨, 표현이 단순. | 길이를 알려면 끝까지 스캔해야 함. 종료 바이트가 데이터에 못 들어감. |
| 길이 prefix (length prefix) | 값 앞에 길이(바이트 수)를 고정 크기 정수로 먼저 저장. | 길이를 O(1)에 알 수 있어 다음 값으로 건너뛰기 빠름. 어떤 바이트든 데이터로 허용. | 길이 필드만큼의 작은 추가 공간. |
13.3의 (offset, length) 헤더는 길이 prefix를 레코드 헤더로 모아 둔 형태로 볼 수 있습니다 — 모든 가변 속성의 위치/길이를 앞쪽에서 한꺼번에 파악하여 임의 속성 접근을 O(1)로 유지합니다.
압축 (Compression)
같은 값이 반복되거나 도메인이 좁은 컬럼은 압축으로 저장량과 I/O를 줄일 수 있습니다(13.9 컬럼 저장에서 특히 효과적).
- RLE(run-length encoding): 연속 반복 값을
(값, 반복횟수)로 축약. 정렬된/저카디널리티 컬럼에 효과적. - Dictionary encoding: 자주 나오는 값을 작은 코드로 매핑(사전 보관) 후 값 대신 코드 저장. 문자열 컬럼에 특히 유리.
압축은 디스크 I/O를 줄이지만 읽을 때 복원(decompress) CPU 비용이 듭니다. 압축된 표현 위에서 바로 연산(예: dictionary 코드 비교)하면 복원을 늦춰 비용을 줄일 수 있습니다(late materialization과 연결).
Large Object(LOB) 저장과 포인터
13.3의 큰 객체(BLOB/CLOB) 메모를 확장합니다. 레코드(=블록보다 작아야 함)에는 LOB 본문을 직접 담지 않고, 본문은 별도 영역에 두고 레코드에는 포인터(위치 참조)만 둡니다.
- 왜 분리하나: LOB를 레코드에 인라인하면 레코드가 블록을 넘겨 슬롯 페이지 관리가 어려워지고, LOB를 안 보는 쿼리도 큰 데이터를 끌고 다녀야 함.
- 저장 위치 선택지: ① 파일 시스템 파일, ② DB가 관리하는 별도 파일/세그먼트, ③ 조각(chunk) 분할 저장(부분 읽기·갱신 용이).
- 트레이드오프: 포인터 분리는 메인 레코드 스캔을 가볍게 하지만, LOB 접근 시 한 번의 추가 간접 참조(I/O)가 필요.
Denormalization vs Clustering — 같은 목표, 다른 도구
"조인을 줄여 읽기를 빠르게"라는 목표는 두 가지로 접근할 수 있습니다. 13.10 다중 테이블 클러스터링과 비교해 정리합니다.
| 기법 | 방법 | 장점 | 비용 |
|---|---|---|---|
| Denormalization | 정규화로 분리된 속성을 한 릴레이션에 중복 저장(예: instructor에 dept 정보를 복사). | 조인 자체를 제거 → 조회 단순·빠름. | 중복 데이터의 갱신 이상(update anomaly) 위험, 저장 공간 증가, 일관성 유지 부담. |
| Multitable clustering | 정규화는 유지하되 관련 레코드를 물리적으로 인접 저장(13.10). | 중복 없이 조인 I/O 감소. 데이터 모델은 그대로. | 단일-릴레이션 전체 스캔 저하, 가변 구조 관리 복잡. |
denormalization은 논리(스키마) 수준에서 중복을 만들어 조인을 없애고, clustering은 물리(저장) 수준에서 배치를 바꿔 조인을 싸게 만듭니다. 전자는 일관성 비용, 후자는 단일 스캔/관리 비용을 치릅니다. 워크로드(읽기 vs 쓰기 비율, 갱신 빈도)에 맞춰 고릅니다.
정리 핵심 한눈에
레코드 & 페이지
- 고정 길이: 위치 = n × i. 삭제 = 시프트 / 마지막으로 대체 / free list.
- 가변 길이: (offset, length) 쌍 + null bitmap.
- 슬롯 페이지 헤더 3정보: 레코드 수 · free end · 각 레코드 위치/크기.
- 포인터는 레코드가 아니라 슬롯 엔트리를 가리킨다(간접). 물리 위치 ≠ 논리 키 순서.
파일 · 버퍼 · 교체
- 파일 구성: Heap / Sequential / B+Tree / Hashing. Heap=free-space map, Sequential=overflow block+reorg.
- 데이터 사전 = system catalog = 메타데이터 5릴레이션.
- 버퍼 Read 3 case, Write→dirty, Commit→flush. pin count==0이라야 evict.
- LRU는 nested loop join에서 나쁨 → MRU/Toss-immediate가 유리.
레코드를 잘 저장했으니, 이제 빠르게 찾는 차례입니다. Ch14 인덱싱에서 B+-Tree와 해싱으로 검색을 가속합니다.