메뉴 여닫기
개인 메뉴 토글
로그인하지 않음
만약 지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.

오라클 LOB 데이터 구조: 두 판 사이의 차이

DB스터디
편집 요약 없음
 
(같은 사용자의 중간 판 9개는 보이지 않습니다)
79번째 줄: 79번째 줄:
# LOB 세그먼트는 내부적으로 CHUNK(기본 8KB, 블록 사이즈 배수) 단위로 분할 저장.  
# LOB 세그먼트는 내부적으로 CHUNK(기본 8KB, 블록 사이즈 배수) 단위로 분할 저장.  
# 큰 데이터를 CHUNK들의 연결 리스트로 관리하기 때문에 부분 읽기/쓰기(random access)가 가능하고, 전체를 메모리에 올리지 않고도 스트리밍 처리가 가능.
# 큰 데이터를 CHUNK들의 연결 리스트로 관리하기 때문에 부분 읽기/쓰기(random access)가 가능하고, 전체를 메모리에 올리지 않고도 스트리밍 처리가 가능.
* CHUNK란?
** LOB 데이터의 최소 I/O 단위
** DB_BLOCK_SIZE의 배수여야 함 (기본값: DB_BLOCK_SIZE, 최대 32KB 권장)
** 하나의 CHUNK는 여러 Oracle Block으로 구성 가능하나, 하나의 익스텐트 내에 존재
==== CHUNK 설계 원칙 ====
# 소량 다건 CLOB (예: 짧은 메모, 코멘트)
#:<source> CHUNK 8192  -- DB_BLOCK_SIZE와 동일하게</source>
# 대용량 CLOB (예: 문서, XML)
#:<source> CHUNK 32768  -- 더 크게 설정하여 I/O 효율 극대화</source>


=== LOB 인덱스 ===
=== LOB 인덱스 ===
LOB 세그먼트의 CHUNK 위치를 추적하기 위해 별도의 LOB 인덱스가 존재합니다(B-tree 구조로 CHUNK 매핑 관리).
LOB 세그먼트의 CHUNK 위치를 추적하기 위해 별도의 LOB 인덱스가 존재합니다(B-tree 구조로 CHUNK 매핑 관리).
* LOB 데이터가 여러 CHUNK에 걸쳐 저장될 때, 이 청크들의 순서와 위치를 관리하는 B-Tree 기반 인덱스.
<source>
LOB Locator (SETID + LENGTH 정보 포함)
    │
    ▼
LOB Index (CHUNK 순서 매핑)
    │
    ├── CHUNK 1 (위치: block#100)
    ├── CHUNK 2 (위치: block#105)
    └── CHUNK 3 (위치: block#250)
</source>
* 왜 중요 한가?
# LOB의 부분 읽기/쓰기(Partial Read/Write) 시 이 인덱스를 통해 필요한 청크만 접근
# DBMS_LOB.READ(lob, amount, offset) 호출 시 인덱스를 스캔하여 offset에 해당하는 청크로 직행


=== PCTVERSION과 RETENTION (읽기 일관성) ===
==== 일반 테이블 vs LOB의 읽기 일관성 차이 ====
{| class="wikitable"
|+ 캡션 텍스트
|-
! 구분 !! 일반 테이블 !! LOB
|-
| 메커니즘 || UNDO Segment || LOB 자체 버전 관리
|-
| 위치 || 별도 UNDO 테이블스페이스 || LOB Segment 내부
|}
* 핵심: LOB은 UNDO를 사용하지 않고, 자체적으로 이전 버전 데이터를 LOB 세그먼트 내에 보관합니다.
==== PCTVERSION vs RETENTION ====
<source>
-- 방식 1: 공간 비율 기반 (전통적 방식)
PCTVERSION 10  -- 전체 LOB 세그먼트의 10%를 old version 유지에 사용
                -- 초과 시 오래된 버전부터 재사용(overwrite)
-- 방식 2: 시간 기반 (Oracle 10g 이후 권장)
RETENTION      -- UNDO_RETENTION 파라미터 값을 따름
                -- 자동 스페이스 관리(ASSM) tablespace 필요
</source>
* 실무 팁: 대용량 LOB이 빈번히 업데이트되는 시스템에서 PCTVERSION이 너무 낮으면 ORA-22924 에러(snapshot too old 유사 현상) 발생 가능.


=== LOB 세그먼트 정보 확인 ===
=== LOB 세그먼트 정보 확인 ===
97번째 줄: 151번째 줄:


즉, "행 안에 다 넣지 않고 별도 세그먼트에 청크 단위로 분산 저장 + 로케이터로 참조"하는 것이 대용량 저장의 핵심 메커니즘입니다.
즉, "행 안에 다 넣지 않고 별도 세그먼트에 청크 단위로 분산 저장 + 로케이터로 참조"하는 것이 대용량 저장의 핵심 메커니즘입니다.
{| class="wikitable"
|+ 아키텍처 차이
|-
! 구분 !! BasicFile (Legacy) !! SecureFile (Modern)
|-
| 저장 구조 || 고정 청크 기반|| 가변 블록 + B-Tree 유사 구조
|-
| 압축 || 불가 || COMPRESS 옵션 지원
|-
| 중복제거 || 불가 || DEDUPLICATE 지원
|-
| 암호화 || 불가 || ENCRYPT 지원
|-
| 성능 || 불가  || 대폭 개선 (특히 순차 접근)
|}


----
----
112번째 줄: 182번째 줄:
   CACHE | NOCACHE | CACHE READS
   CACHE | NOCACHE | CACHE READS
   LOGGING | NOLOGGING
   LOGGING | NOLOGGING
   COMPRESS [LOW|MEDIUM|HIGH]
   COMPRESS [LOW|MEDIUM|HIGH]   -- SecureFile 전용 기능
   DEDUPLICATE | KEEP_DUPLICATES
   DEDUPLICATE | KEEP_DUPLICATES -- 동일 LOB 데이터 중복 제거 선택
   ENCRYPT ... | DECRYPT
   ENCRYPT ... | DECRYPT
)
)
125번째 줄: 195번째 줄:
! 속성 !! 설명 !! 비고  
! 속성 !! 설명 !! 비고  
|-
|-
| **SECUREFILE / BASICFILE** || LOB 저장 방식 선택  
| **SECUREFILE / BASICFILE** || LOB 저장 방식 선택 || SecureFile 권장(기본값은 DB_SECUREFILE 파라미터에 따름)  
|| SecureFile 권장(기본값은 DB_SECUREFILE 파라미터에 따름)  
|-
|-
| **ENABLE/DISABLE STORAGE IN ROW** || 로우 내 인라인 저장 여부 || 임계값 이하 데이터는 IN ROW 저장  
| **ENABLE/DISABLE STORAGE IN ROW** || 로우 내 인라인 저장 여부 || 임계값 이하 데이터는 IN ROW 저장  
166번째 줄: 235번째 줄:


특정 옵션(예: COMPRESS, DEDUPLICATE의 라이선스 조건이나 RAC FREEPOOLS 튜닝)에 대해 더 깊게 필요하시면 말씀해주세요.
특정 옵션(예: COMPRESS, DEDUPLICATE의 라이선스 조건이나 RAC FREEPOOLS 튜닝)에 대해 더 깊게 필요하시면 말씀해주세요.
==== CACHE / NOCACHE / CACHE READS ====
:* 소량/빈번 접근 LOB: CACHE (예: 사용자 프로필 이미지 썸네일)
:* 대량/1회성 접근 LOB: NOCACHE (예: 로그 파일, 대용량 첨부파일)
::<source>
-- 옵션 1: CACHE
-- LOB 데이터를 Buffer Cache에 정상적으로 캐싱 (일반 테이블처럼)
CACHE
-- 옵션 2: NOCACHE (기본값)
-- Direct Path I/O 사용, Buffer Cache 우회
-- 대용량 LOB에 적합 (캐시 오염 방지)
NOCACHE
-- 옵션 3: CACHE READS
-- 쓰기는 NOCACHE처럼, 읽기는 CACHE처럼 동작
CACHE READS
</source>
=== LOB과 트랜잭션 - Locator의 실체 ===
* LOB Locator 내부 구조 (개념적)
<source>
LOB Locator = {
    LOB Type (CLOB/BLOB/BFILE)
    LOB Length
    Chunk Size
    Position (현재 커서 위치 - 세션 내에서만 유효)
    ... (내부 메타데이터)
}
</source>
* 핵심 함정: LOB Locator의 생명주기
** 잘못된 사용 예 - LOB Locator를 커밋 후 재사용
**:<source lang=sql>
DECLARE
    l_clob CLOB;
BEGIN
    SELECT doc INTO l_clob FROM lob_test WHERE id=1;
    COMMIT;  -- ← 이 시점 이후 l_clob 사용 시 위험할 수 있음
    DBMS_LOB.READ(l_clob, ...);  -- ORA-22990 가능성
END;
</source>
* 원리: LOB Locator는 트랜잭션과 밀접하게 연관되어 있으며, 특정 상황(특히 원격 LOB이나 트랜잭션 경계를 넘는 경우)에서 무효화될 수 있습니다.
=== LOB 실전 튜닝 포인트 ===
1. Fragmentation 확인
<source lang=sql>
SELECT segment_name, bytes/1024/1024 MB, blocks
FROM dba_segments
WHERE segment_name IN (SELECT segment_name FROM dba_lobs WHERE table_name='X');
</source>
2. In-row vs Out-row 비율 확인 (실제 데이터 분포)
<source lang=sql>
SELECT
    CASE WHEN LENGTHB(doc) <= 4000 THEN 'IN_ROW_CANDIDATE'
        ELSE 'OUT_ROW' END,
    COUNT(*)
FROM lob_test
GROUP BY CASE WHEN LENGTHB(doc) <= 4000 THEN 'IN_ROW_CANDIDATE'
              ELSE 'OUT_ROW' END;
</source>
3. Chunk 사용 효율성 (파편화 여부)
<source lang=sql>
SELECT table_name, column_name, chunk
FROM user_lobs;
</source>

2026년 9월 7일 (월) 14:53 기준 최신판

LOB(BLOB/CLOB)대용량 데이터

개요

LOB(BLOB/CLOB)이 대용량 데이터를 저장하는 핵심 원리는 "포인터(로케이터) + 별도 세그먼트 분리 저장" 구조

LOB
├── Internal LOB (DB 내부 저장)
│   ├── CLOB (Character LOB) - 문자 데이터
│   ├── NCLOB (National CLOB) - 다국어 문자셋
│   └── BLOB (Binary LOB) - 바이너리 데이터
└── External LOB
    └── BFILE - OS 파일시스템 참조 (읽기 전용)

CLOB vs VARCHAR2 근본적 차이

  1. VARCHAR2: 최대 32767 bytes (PL/SQL , 19c 이상부터 SQL도 가능), 4000 bytes (SQL) - inline storage 강제
  2. CLOB: 최대 4GB * DB_BLOCK_SIZE (이론상 128TB) - out-of-line storage 가능


LOB 저장 구조 (핵심)

Table Row
    │
    ▼
┌─────────────┐
│ LOB LOCATOR │ ← 테이블 로우에 실제 저장되는 것 (포인터/핸들)
│ (약 4KB↓)   │
└─────────────┘
    │
    ▼
┌─────────────┐
│ LOB INDEX   │ ← LOB 청크들을 관리하는 B-Tree 인덱스
└─────────────┘
    │
    ▼
┌─────────────┐
│ LOB SEGMENT │ ← 실제 데이터가 저장되는 세그먼트 (CHUNK 단위)
└─────────────┘

LOB 로케이터(LOCATOR)

  1. 테이블 로우에는 실제 데이터가 아니라 LOB 데이터를 가리키는 40바이트 내외의 로케이터(포인터)만 저장됨
  2. 실제 데이터는 별도의 LOB 세그먼트에 존재하고, 애플리케이션은 이 로케이터를 통해 스트리밍 방식(READ/WRITE by chunk)으로 데이터에 접근함

In-row vs Out-of-row

- 데이터 크기가 작으면(기본 임계값 관련, ENABLE STORAGE IN ROW) 로우 내부에 직접 저장
- 임계값을 넘으면 별도의 LOB 세그먼트(out-of-row)에 체인 형태로 저장되고 로우에는 포인터만 남음
- 이 덕분에 4000바이트 제한을 가진 VARCHAR2/RAW와 달리 최대 4GB(BasicFile) ~ (LOB) 단위까지 저장 가능
CLOB 데이터 크기 확인
    │
    ├── 4000 bytes 이하 (기본값)
    │   └── ENABLE STORAGE IN ROW
    │       → 로우 내부에 직접 저장 (Inline)
    │       → LOB Locator + 실제 데이터가 로우에 존재
    │
    └── 4000 bytes 초과 (또는 DISABLE STORAGE IN ROW)
        └── OUT OF ROW 강제 이동
            → 로우에는 LOB Locator만 존재
            → 실제 데이터는 별도 LOB Segment에 저장
  • 저장 방식 확인 테스트
CREATE TABLE lob_test (
    id NUMBER,
    doc CLOB
) LOB (doc) STORE AS (
    ENABLE STORAGE IN ROW    -- 4000바이트 이하는 인라인
    CHUNK 8192                -- 청크 크기
    PCTVERSION 10              -- 버전 유지 비율
    NOCACHE                    -- 버퍼캐시 사용 안함
    LOGGING
);
  • 성능 함정: 4000바이트 근처의 데이터가 빈번하게 업데이트되면 in-row ↔ out-of-row 전환이 발생하여 Row Migration과 유사한 오버헤드 발생.

CHUNK 단위 저장

  1. LOB 세그먼트는 내부적으로 CHUNK(기본 8KB, 블록 사이즈 배수) 단위로 분할 저장.
  2. 큰 데이터를 CHUNK들의 연결 리스트로 관리하기 때문에 부분 읽기/쓰기(random access)가 가능하고, 전체를 메모리에 올리지 않고도 스트리밍 처리가 가능.
  • CHUNK란?
    • LOB 데이터의 최소 I/O 단위
    • DB_BLOCK_SIZE의 배수여야 함 (기본값: DB_BLOCK_SIZE, 최대 32KB 권장)
    • 하나의 CHUNK는 여러 Oracle Block으로 구성 가능하나, 하나의 익스텐트 내에 존재

CHUNK 설계 원칙

  1. 소량 다건 CLOB (예: 짧은 메모, 코멘트)
     CHUNK 8192   -- DB_BLOCK_SIZE와 동일하게
  2. 대용량 CLOB (예: 문서, XML)
     CHUNK 32768  -- 더 크게 설정하여 I/O 효율 극대화

LOB 인덱스

LOB 세그먼트의 CHUNK 위치를 추적하기 위해 별도의 LOB 인덱스가 존재합니다(B-tree 구조로 CHUNK 매핑 관리).

  • LOB 데이터가 여러 CHUNK에 걸쳐 저장될 때, 이 청크들의 순서와 위치를 관리하는 B-Tree 기반 인덱스.
 LOB Locator (SETID + LENGTH 정보 포함)
    │
    ▼
LOB Index (CHUNK 순서 매핑)
    │
    ├── CHUNK 1 (위치: block#100)
    ├── CHUNK 2 (위치: block#105)
    └── CHUNK 3 (위치: block#250)
  • 왜 중요 한가?
  1. LOB의 부분 읽기/쓰기(Partial Read/Write) 시 이 인덱스를 통해 필요한 청크만 접근
  2. DBMS_LOB.READ(lob, amount, offset) 호출 시 인덱스를 스캔하여 offset에 해당하는 청크로 직행


PCTVERSION과 RETENTION (읽기 일관성)

일반 테이블 vs LOB의 읽기 일관성 차이

캡션 텍스트
구분 일반 테이블 LOB
메커니즘 UNDO Segment LOB 자체 버전 관리
위치 별도 UNDO 테이블스페이스 LOB Segment 내부
  • 핵심: LOB은 UNDO를 사용하지 않고, 자체적으로 이전 버전 데이터를 LOB 세그먼트 내에 보관합니다.

PCTVERSION vs RETENTION

-- 방식 1: 공간 비율 기반 (전통적 방식)
PCTVERSION 10   -- 전체 LOB 세그먼트의 10%를 old version 유지에 사용
                -- 초과 시 오래된 버전부터 재사용(overwrite)

-- 방식 2: 시간 기반 (Oracle 10g 이후 권장)
RETENTION       -- UNDO_RETENTION 파라미터 값을 따름
                -- 자동 스페이스 관리(ASSM) tablespace 필요
  • 실무 팁: 대용량 LOB이 빈번히 업데이트되는 시스템에서 PCTVERSION이 너무 낮으면 ORA-22924 에러(snapshot too old 유사 현상) 발생 가능.

LOB 세그먼트 정보 확인

SELECT table_name, column_name, segment_name, index_name,
       chunk, pctversion, cache, logging, in_row
  FROM user_lobs -- dba_lobs
 WHERE table_name = 'YOUR_TABLE';

SecureFile vs BasicFile

- BasicFile: 전통적 방식, LOB 세그먼트에 체인 저장
- SecureFile(11g+): ASSM 기반, 압축/중복제거/암호화 지원, 성능 개선된 구조로 대부분 권장

즉, "행 안에 다 넣지 않고 별도 세그먼트에 청크 단위로 분산 저장 + 로케이터로 참조"하는 것이 대용량 저장의 핵심 메커니즘입니다.

아키텍처 차이
구분 BasicFile (Legacy) SecureFile (Modern)
저장 구조 고정 청크 기반 가변 블록 + B-Tree 유사 구조
압축 불가 COMPRESS 옵션 지원
중복제거 불가 DEDUPLICATE 지원
암호화 불가 ENCRYPT 지원
성능 불가 대폭 개선 (특히 순차 접근)



  1. Oracle 19c LOB 저장 속성 정리
    1. LOB STORAGE 절 기본 구조
LOB (column_name) STORE AS [SECUREFILE|BASICFILE] [lob_segment_name]
(
  TABLESPACE ...
  ENABLE|DISABLE STORAGE IN ROW
  CHUNK n
  PCTVERSION n | RETENTION [AUTO|MAX n|MIN n]
  FREEPOOLS n
  CACHE | NOCACHE | CACHE READS
  LOGGING | NOLOGGING
  COMPRESS [LOW|MEDIUM|HIGH]   -- SecureFile 전용 기능
  DEDUPLICATE | KEEP_DUPLICATES -- 동일 LOB 데이터 중복 제거 선택
  ENCRYPT ... | DECRYPT
)


    1. 속성별 정리
캡션 텍스트
속성 설명 비고
**SECUREFILE / BASICFILE** LOB 저장 방식 선택 SecureFile 권장(기본값은 DB_SECUREFILE 파라미터에 따름)
**ENABLE/DISABLE STORAGE IN ROW** 로우 내 인라인 저장 여부 임계값 이하 데이터는 IN ROW 저장
**CHUNK** LOB 저장 최소 단위(바이트) 블록사이즈 배수, 기본 8K
**PCTVERSION** (BasicFile 전용) 이전 버전 유지 공간 % Undo 방식과 다른 read consistency
**RETENTION [AUTO/MAX/MIN]** (SecureFile 전용) 버전 보존 정책 AUTO가 일반적
**CACHE / NOCACHE / CACHE READS** 버퍼캐시 사용 여부 대용량은 보통 NOCACHE
**LOGGING / NOLOGGING** Redo 생성 여부 대량 초기 적재시 NOLOGGING 고려
**COMPRESS [LOW/MEDIUM/HIGH]** (SecureFile 전용) 압축 레벨 ACO(Advanced Compression) 라이선스 필요
**DEDUPLICATE / KEEP_DUPLICATES** (SecureFile 전용) 중복 제거 ACO 라이선스 필요
**ENCRYPT / DECRYPT** TDE 필요
**FREEPOOLS** (SecureFile 전용) 동시성 향상용 free space pool 분리 RAC 환경 동시 write 성능
    1. 🆕 19c 관련 New Feature / 변경사항
    • 1. `DB_SECUREFILE` 파라미터 기본값 변경 없음, 단 SecureFile이 사실상 표준화**

- 12c부터 BasicFile은 "desupported"(deprecated) 상태 유지, 19c에서도 동일하게 BasicFile은 신규 개발에 비권장

    • 2. LOB 관련 초기화 파라미터 정리 지속**

- `DB_SECUREFILE` 파라미터 값: `PERMITTED`(기본), `ALWAYS`, `FORCE`, `NEVER`, `IGNORE` - 19c에서도 동일 옵션 유지, ALWAYS/FORCE 사용시 BasicFile 문법도 자동으로 SecureFile로 생성

    • 3. JSON 관련 LOB 활용 강화 (19c 특징)**

- 19c의 확장된 JSON 지원(다중 JSON 컬럼, 부분 업데이트 등)이 내부적으로 SecureFile LOB 기반으로 동작 — LOB 자체의 새 절이라기보다는 LOB을 활용하는 상위 기능 확장

    • 4. In-Memory 관련**

- 19c에서 `INMEMORY` 절은 LOB 자체에는 직접 적용 불가(LOB은 IM 컬럼 스토어 미지원) — 이 제약은 계속 유지됨(신규 아님, 혼동 주의사항으로 정리)

    • 참고**: 19c 자체에서 LOB STORE AS 문법 자체에 **완전히 새로운 키워드가 추가된 것은 없고**, 12c~18c에서 도입된 SecureFile 관련 옵션들이 19c에서도 그대로 유지·강화되는 흐름입니다. 12c 이후 큰 변화는 없었고, 오히려 "BasicFile 지양, SecureFile 표준화"라는 정책적 방향이 19c에서 더 굳어진 것으로 보시면 됩니다.

특정 옵션(예: COMPRESS, DEDUPLICATE의 라이선스 조건이나 RAC FREEPOOLS 튜닝)에 대해 더 깊게 필요하시면 말씀해주세요.

CACHE / NOCACHE / CACHE READS

  • 소량/빈번 접근 LOB: CACHE (예: 사용자 프로필 이미지 썸네일)
  • 대량/1회성 접근 LOB: NOCACHE (예: 로그 파일, 대용량 첨부파일)
-- 옵션 1: CACHE
-- LOB 데이터를 Buffer Cache에 정상적으로 캐싱 (일반 테이블처럼)
CACHE

-- 옵션 2: NOCACHE (기본값)
-- Direct Path I/O 사용, Buffer Cache 우회
-- 대용량 LOB에 적합 (캐시 오염 방지)
NOCACHE

-- 옵션 3: CACHE READS
-- 쓰기는 NOCACHE처럼, 읽기는 CACHE처럼 동작
CACHE READS

LOB과 트랜잭션 - Locator의 실체

  • LOB Locator 내부 구조 (개념적)
LOB Locator = {
    LOB Type (CLOB/BLOB/BFILE)
    LOB Length
    Chunk Size
    Position (현재 커서 위치 - 세션 내에서만 유효)
    ... (내부 메타데이터)
}
  • 핵심 함정: LOB Locator의 생명주기
    • 잘못된 사용 예 - LOB Locator를 커밋 후 재사용
      DECLARE
          l_clob CLOB;
      BEGIN
          SELECT doc INTO l_clob FROM lob_test WHERE id=1;
          COMMIT;  -- ← 이 시점 이후 l_clob 사용 시 위험할 수 있음
          DBMS_LOB.READ(l_clob, ...);  -- ORA-22990 가능성
      END;
  • 원리: LOB Locator는 트랜잭션과 밀접하게 연관되어 있으며, 특정 상황(특히 원격 LOB이나 트랜잭션 경계를 넘는 경우)에서 무효화될 수 있습니다.

LOB 실전 튜닝 포인트

1. Fragmentation 확인

SELECT segment_name, bytes/1024/1024 MB, blocks
FROM dba_segments 
WHERE segment_name IN (SELECT segment_name FROM dba_lobs WHERE table_name='X');

2. In-row vs Out-row 비율 확인 (실제 데이터 분포)

SELECT 
    CASE WHEN LENGTHB(doc) <= 4000 THEN 'IN_ROW_CANDIDATE' 
         ELSE 'OUT_ROW' END,
    COUNT(*)
FROM lob_test
GROUP BY CASE WHEN LENGTHB(doc) <= 4000 THEN 'IN_ROW_CANDIDATE' 
              ELSE 'OUT_ROW' END;

3. Chunk 사용 효율성 (파편화 여부)

SELECT table_name, column_name, chunk 
FROM user_lobs;