34. 고정 길이 레코드 vs 가변 길이 레코드 (Fixed vs Variable Length)
핵심 인사이트 (3줄 요약)
- 본질: 데이터베이스가 논리적 튜플(행)을 디스크의 물리적 레코드로 저장할 때, 그 크기를 고정할 것인지(Fixed) 데이터 크기에 맞춰 변동시킬 것인지(Variable)를 결정하는 포맷 아키텍처다.
- 가치: 고정 길이는 주소 계산이 단순하여 고속 탐색이 가능하지만 내부 단편화가 발생하며, 가변 길이는 공간 효율성이 높지만 슬롯(Slot) 구조와 압축(Compaction) 오버헤드가 발생한다.
- 융합: 운영체제의 메모리 페이징 기법과 파일 시스템 단편화 방지 기술이 결합된 개념이며, 최근 NoSQL의 JSON/BSON 도큐먼트 저장 구조의 근본적인 이해를 돕는다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
관계형 데이터베이스(RDBMS)에서 사용자가 테이블을 생성하고 데이터를 INSERT 하면, DBMS는 이 논리적 데이터를 디스크 블록(Block)에 물리적 레코드의 연속된 바이트 스트림으로 변환하여 저장한다. 이때 설계자는 각 필드(Column)의 데이터 타입(CHAR vs VARCHAR 등)을 지정하게 되며, 이 선택은 레코드의 길이가 항상 일정한 고정 길이 레코드(Fixed Length Record) 포맷을 취할 것인가, 아니면 데이터의 실질적 내용량에 따라 늘어나는 가변 길이 레코드(Variable Length Record) 포맷을 취할 것인가를 결정짓는다.
초기 데이터베이스 시스템은 계산의 단순성을 위해 모든 레코드를 동일한 길이로 고정했다. 하지만 데이터의 특성(예: '성별'은 1바이트 고정이지만 '주소'나 '자기소개'는 수 바이트에서 수천 바이트까지 가변적임)을 무시한 고정 길이 할당은 엄청난 스토리지 낭비(내부 단편화)를 초래했다. 반면, 길이를 가변적으로 허용하면 공간은 아낄 수 있지만 특정 레코드의 시작 주소를 단숨에 연산하기 어려워지는 치명적인 부작용이 발생한다.
이 도식은 데이터의 실제 크기가 불규칙할 때 고정 길이 할당이 초래하는 치명적 낭비를 시각적으로 보여준다.
[고정 길이 레코드의 메모리 낭비 (내부 단편화) 메커니즘]
할당 기준: 레코드당 100 Byte 고정
레코드 1 (실제 20B): [■■■■■ ] (80 Byte 낭비 공간)
레코드 2 (실제 90B): [■■■■■■■■■■■■■■■■■■■■■■ ] (10 Byte 낭비 공간)
레코드 3 (실제 10B): [■■ ] (90 Byte 낭비 공간)
▲
병목/낭비 지점: 사용하지 않는 패딩(Padding) 공간으로 인한 디스크 I/O 손실
이 구조의 핵심 문제는, 빈 공간(Padding)조차도 무의미하게 디스크 공간을 차지하며, 데이터베이스가 블록을 메모리로 퍼올릴 때 쓸데없는 공백까지 함께 읽어 들여 막대한 I/O 대역폭을 낭비한다는 점이다. 따라서 실무에서는 데이터의 밀도를 높이고 디스크 스캔 비용을 줄이기 위해 가변 길이 레코드 구조의 도입이 필수불가결해졌다.
📢 섹션 요약 비유: 아파트 주차장에 무조건 대형 트럭 규격의 고정된 주차선만 그어두면(고정 길이), 경차나 오토바이가 주차했을 때 남는 공간이 너무 많아 주차장이 텅 비어 보이는데도 더 이상 차를 댈 수 없는 비효율이 발생하는 것과 같습니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
레코드의 길이를 가변적으로 처리하기 위해서는 구조적인 메타데이터 관리 기법이 도입되어야 한다. 이를 위해 DBMS는 슬롯 페이지(Slotted Page) 구조를 핵심 아키텍처로 사용한다.
| 레코드 구조 | 메모리 레이아웃 배치 방식 | 주소 접근 연산 (Addressing) | 주요 데이터 타입 |
|---|---|---|---|
| 고정 길이 | 데이터 필드를 정해진 바이트 크기만큼 순차 나열 | 시작 주소 + (레코드 번호 × 고정 크기) | CHAR, INT, DATE |
| 가변 길이 | 헤더(길이 정보) + 구분자(Delimiter) + 실제 데이터 | 슬롯 배열의 포인터를 읽어 오프셋으로 이동 | VARCHAR, BLOB, TEXT |
고정 길이 레코드는 배열(Array)처럼 인덱스 연산(i * size) 한 번으로 원하는 레코드 위치에 O(1) 속도로 정확히 랜딩할 수 있다. 반면 가변 길이 레코드는 길이가 들쭉날쭉하므로, 해당 블록의 헤더 쪽에 '슬롯(Slot) 배열'이라는 목차를 별도로 두어 각 레코드의 시작 포인터를 추적한다.
이 구조도는 가변 길이 레코드가 블록 내에서 어떻게 배치되고 추적되는지 슬롯 페이지 구조를 보여준다.
[가변 길이 레코드의 슬롯 페이지(Slotted Page) 레이아웃]
┌────────────────── Block (Page) ──────────────────────────┐
│ [Block Header] │
│ [Slot 1] ─(포인터)─┐ │
│ [Slot 2] ─(포인터)─┼────────┐ │
│ [Slot N] ─(포인터)─┼────────┼────────┐ │
│ [Free Space (여유 공간)] <───┼────────┼───────────────┤ │
│ │ │ │ │
│ <──┘ │ │ │
│ [Record N (가변크기)] │ │ │
│ [Record 2 (가변크기)] <─────┘ │ │
│ [Record 1 (가변크기)] <──────────────┘ │
└────────────────────────────────────────────────────────┘
이 도식의 배치가 가진 가장 큰 특징은 레코드 데이터가 블록의 맨 뒤에서부터 거꾸로 자라 올라오고, 포인터(슬롯)는 앞에서부터 자라 내려간다는 점이다. 이런 배치는 레코드의 크기가 삽입 시점에 정확히 정해지지 않더라도, 중간의 잉여 공간(Free Space)을 공유하며 양쪽에서 유연하게 메모리를 활용할 수 있도록 하기 때문이며, 파편화를 방지하는 매우 지능적인 디자인이다. 따라서 특정 레코드 Record 2를 읽으려면 반드시 앞단의 Slot 2를 먼저 읽어 오프셋(Offset) 포인터를 획득해야 하는 한 번의 추가 참조(Indirection) 오버헤드가 발생한다.
📢 섹션 요약 비유: 가변 길이 구조는 호텔의 주차장 관리인(슬롯 포인터)이 차의 크기에 맞춰 유연하게 주차 공간의 시작과 끝을 메모해 두고, 차주가 오면 장부를 보고 차가 있는 정확한 위치를 알려주는 맞춤형 발렛 파킹 시스템과 같습니다.
Ⅲ. 융합 비교 및 다각도 분석 (Comparison & Synergy)
고정 길이와 가변 길이의 선택은 성능(Performance)과 저장 공간(Storage) 간의 전형적인 트레이드오프(Trade-off)를 보여준다.
| 비교 항목 | 고정 길이 레코드 (Fixed Length) | 가변 길이 레코드 (Variable Length) | 의사결정 판단 포인트 |
|---|---|---|---|
| 저장 효율성 | 🔴 낮음 (내부 단편화 발생) | 🟢 높음 (실제 데이터만큼만 차지) | 스토리지 볼륨 단가 및 데이터 특성 |
| 검색 속도 | 🟢 최상 (O(1) 산술 연산 점프) | 🟡 보통 (포인터 인디렉션 참조) | 극단적인 단건 탐색 속도 요구 여부 |
| 수정(Update) 오버헤드 | 🟢 없음 (제자리 덮어쓰기 가능) | 🔴 높음 (크기 증가 시 블록 외 오버플로우) | 데이터의 빈번한 크기 변경(Update) 유무 |
| DBMS 데이터 타입 | CHAR(n), 고정 소수점 | VARCHAR(n), TEXT, JSON | 스키마 설계 시 필드 속성 결정 |
이 비교 매트릭스를 해석할 때 가장 주의해야 할 부분은 수정(Update) 시의 부작용이다. 고정 길이 레코드는 데이터를 업데이트할 때 크기가 변하지 않으므로 제자리에서 그대로 덮어쓰면 끝난다(In-place Update). 하지만 가변 길이 레코드에서는 "안녕하세요"를 "안녕하세요. 반갑습니다"로 길게 수정할 경우, 기존 할당된 공간에 들어가지 못해 레코드를 뜯어내어 다른 블록으로 이주시켜야 하는 포워딩(Forwarding) 현상 또는 **외부 단편화(External Fragmentation)**가 발생한다. 이는 시스템 성능을 갉아먹는 치명적 요인이 된다.
📢 섹션 요약 비유: 고정 길이는 옷가게에서 프리사이즈(Free Size) 단일 규격의 옷만 파는 것이고(관리 편함, 핏 안 맞음), 가변 길이는 맞춤 정장을 제작하는 것(원단 절약, 제작 시 치수 변경 오버헤드 발생)에 비유할 수 있습니다.
Ⅳ. 실무 적용 및 기술사적 판단 (Strategy & Decision)
실무에서 데이터베이스 테이블을 모델링할 때, 개발자의 습관적인 데이터 타입 선택(모든 필드를 무분별하게 VARCHAR로 선언하는 등)은 시스템 전체 성능에 치명적 결함을 낳는다.
- 무분별한 VARCHAR 사용의 안티패턴: 성별('M', 'F'), 우편번호(5자리), Y/N 플래그처럼 길이가 사실상 고정된 필드조차 VARCHAR(가변 길이)로 설계하는 경우가 많다. 이럴 경우 DBMS는 각 레코드마다 길이 정보를 저장하는 1~2 바이트의 길이 오버헤드(Length 헤더)를 불필요하게 낭비하게 되며, 검색 시 포인터 계산이 추가되어 CPU 연산량을 가중시킨다. 길이가 확실히 고정된 데이터는 반드시
CHAR타입(고정 길이)을 명시해야 한다. - 단편화 압축(Compaction) 오버헤드: 가변 길이 레코드들이 빈번하게 삭제(Delete)되고 삽입(Insert)되면 블록 내부에 마치 이빨이 빠진 듯한 구멍(Fragmentation)들이 무수히 생긴다.
[가변 길이 레코드의 단편화와 압축(Compaction) 과정]
삭제 발생 시 (구멍 뚫림):
[Slot] [여유공간] [Rec 3] [빈 구멍(삭제됨)] [Rec 1]
▲ 파편화(Fragmentation) 발생
Compaction (조각 모음) 후:
[Slot] [ 넓어진 연속 여유공간 ] [Rec 3] [Rec 1]
▲ 데이터 재배치(Shift) 수행
이 상태도의 핵심은 구멍 난 공간을 합쳐서 새로운 연속된 공간을 만들기 위해 DBMS 내부에서 조각 모음(Compaction)이 일어난다는 점이다. 이는 레코드를 물리적으로 다시 이동(Shift)시켜야 하는 막대한 CPU/메모리 비용을 수반한다. 따라서 실무에서는 데이터 크기가 극심하게 변동하는 테이블의 경우, 여유 공간(Fill Factor / PCTFREE) 설정을 넉넉하게 주어 블록 내에 미리 숨쉴 공간을 확보해 두는 튜닝 전략이 매우 중요하다.
📢 섹션 요약 비유: 가변적인 짐들이 자주 빠지고 들어오는 창고에서는 중간중간 생긴 빈 공간을 모으기 위해 직원들이 짐을 끝으로 다시 밀어붙이는 대청소(Compaction)를 수시로 해야 하며, 이 시간에는 정상적인 입출고 작업이 중단되는 딜레마에 빠지는 것입니다.
Ⅴ. 기대효과 및 결론 (Future & Standard)
데이터의 성격에 맞게 고정 길이와 가변 길이 포맷을 전략적으로 혼용하여 스키마를 설계하면, 스토리지 비용의 혁신적 절감과 디스크 I/O 대역폭의 한계 돌파를 동시에 이룰 수 있다.
| 설계 최적화 전 | 설계 최적화 후 | 정량적 비즈니스 기대효과 |
|---|---|---|
| 상태 플래그를 모두 VARCHAR 선언 | 길이 고정 필드는 CHAR로 통일 | 길이 헤더 오버헤드 제거, 수만 건 쿼리 시 CPU 낭비율 최소 15% 감소 |
| 블록 내 빈 공간(PCTFREE) 0% 설정 | 가변 테이블에 20% 여유공간 설정 | Update 오버플로우 방지, 체이닝 I/O 디스크 지연 90% 차단 |
| 수백 바이트 공백이 포함된 레코드 | 가변 길이로 디스크 밀집도 향상 | 테이블 스페이스 스토리지 낭비 극소화 및 버퍼 캐시 적중률(Hit Ratio) 향상 |
빅데이터 및 클라우드 데이터 웨어하우스(DW) 시대에는 한발 더 나아가, 데이터 압축률을 극한으로 끌어올리기 위해 레코드(행) 단위가 아닌 컬럼(열) 단위로 가변 길이 데이터를 묶어서 압축하는 컬럼 스토어(Columnar Store) 아키텍처가 글로벌 표준으로 자리 잡았다. 또한 NoSQL 진영에서는 스키마가 없는 가변 구조의 끝판왕인 JSON/BSON 형태의 완전 가변형 도큐먼트 모델을 채택하여 유연성을 극대화하는 방향으로 미래가 진화하고 있다.
📢 섹션 요약 비유: 각 물건의 크기를 무시하고 똑같은 규격 상자에 담던 과거 배송 시스템이 발전하여, 이제는 물건을 스캔해 빈틈없이 딱 맞는 가변형 골판지 박스를 즉석에서 접어 포장함으로써 물류 트럭에 3배나 더 많은 짐을 싣는 혁신을 이뤄낸 것과 같습니다.
📌 관련 개념 맵 (Knowledge Graph)
- 블로킹 팩터 (Blocking Factor) | 고정/가변 길이에 따라 하나의 디스크 블록에 담을 수 있는 레코드 밀집도 수치
- 단편화 (Fragmentation) | 메모리나 블록 내에 할당과 해제가 반복되며 버려지는 자투리 공간 (내부/외부 단편화)
- 페이지/블록 튜닝 (PCTFREE, INITRANS) | 가변 길이 레코드의 수정 오버헤드를 막기 위해 블록 생성 시 미리 남겨두는 여유 공간 제어 옵션
- 마이그레이션 포인터 (Row Migration) | 수정된 가변 길이 레코드가 커져서 다른 블록으로 쫓겨날 때 원래 자리에 남기는 이주 주소
- 컬럼형 스토리지 (Columnar Store) | 행 기반 가변/고정 제약을 넘어 열 단위로 데이터를 묶고 압축하여 분석 I/O 성능을 극대화한 구조
👶 어린이를 위한 3줄 비유 설명
- 햄버거 세트를 담을 때 똑같은 크기의 큰 상자(고정 길이)만 쓰면, 감자튀김 하나만 시킨 손님 상자에는 빈 공기가 너무 많아 쓰레기가 낭비돼요.
- 반면에 음식 크기에 딱 맞는 종이봉투(가변 길이)를 쓰면 종이는 아낄 수 있지만, 알바생이 포장할 때마다 크기를 재야 해서 포장 시간이 조금 더 걸린답니다.
- 데이터베이스도 이처럼 '빈 공간을 버릴 것인가' 아니면 '계산하는 시간을 더 쓸 것인가'를 데이터 종류에 맞춰 똑똑하게 선택하는 거예요!