※ PostgreSQL: TOAST.
안녕하세요. 듀스트림입니다.
오늘의 주제는 PostgreSQL의 스토리지 분리 메커니즘인 토스트에 대한 내용입니다.
1. TOAST
- PostgreSQL에서 TOAST (The Oversized-Attribute Storage Technique)는 테이블 컬럼에 너무 큰 데이터가 들어올 경우 이를 별도의 저장소로 분리해 저장하는 메커니즘입니다.
- TOAST는 PostgreSQL의 저장 구조와 퍼포먼스 최적화에 매우 중요한 역할을 합니다.
2. TOAST의 필요성
- PostgreSQL의 데이터 페이지는 기본값으로 설치할 경우 8KB입니다.
- 테이블의 한 row 전체는 하나의 페이지를 초과할 수 없습니다.
- text, bytea, json, xml, jsonb, tsvector, tsquery 등의 데이터 타입은 수십 MB 이상 커질 수 있습니다.
이런 oversized attribute를 저장하기 위해 TOAST가 도입되었습니다.
3. TOAST의 동작 방식
TOAST는 다음과 같은 순서로 동작합니다.
- 압축 (Compression)
- TOAST 가능한 타입(storage EXTENDED)은 우선 zlib을 사용한 pglz 압축 시도.
- 압축된 결과가 원본보다 작을 경우에만 압축본 사용.
- 이 압축은 TOAST 테이블로 넘어가기 전 첫 번째 절차입니다.
- 외부 저장 (Out-of-line Storage)
- 압축 후에도 row 전체 크기가 2KB 이상이면, 해당 attribute만 TOAST table이라는 별도 테이블에 저장됩니다.
- 원 테이블에는 TOAST Pointer만 남게 됩니다.
+ 원 테이블에 저장된 TOAST 포인터 크기
- PostgreSQL의 TOAST 포인터는 varattrib_4b_external 구조체로 정의되어 있으며, 다음과 같은 필드를 가집니다.
typedef struct varatt_external {
int32 va_rawsize; // 원본 크기
int32 va_extsize; // 압축 후 크기
Oid va_valueid; // TOAST chunk_id
Oid va_toastrelid; // TOAST 테이블 OID
} varatt_external;
// 총 4 + 4 + 4 + 4 = 16바이트
// + varlena 헤더까지 포함되면 보통 20~24바이트 정도의 크기로 포인터가 본문에 저장됩니다.
postgres/src/include/access/tupmacs.h at master · postgres/postgres
Mirror of the official PostgreSQL GIT repository. Note that this is just a *mirror* - we don't work with pull requests on github. To contribute, please see https://wiki.postgresql.org/wiki/Subm...
github.com
postgres/src/include/postgres.h at master · postgres/postgres
Mirror of the official PostgreSQL GIT repository. Note that this is just a *mirror* - we don't work with pull requests on github. To contribute, please see https://wiki.postgresql.org/wiki/Subm...
github.com
4. TOAST 테이블 구조
TOAST 테이블은 자동 생성되며, pg_toast.pg_toast_<oid> 형태입니다.
| 필드 | 설명 |
| chunk_id | 원본 row를 식별할 수 있는 id |
| chunk_seq | 조각 순서 |
| chunk_data | 실제 데이터의 일부 (최대 2KB) |
TOAST 테이블은 기본적으로 B-Tree 인덱스를 갖습니다 (pg_toast.pg_toast_<oid>_index).
5. Storage 유형과 TOAST의 관계
PostgreSQL은 컬럼별로 TOAST storage 전략을 설정할 수 있습니다.
| Storage 옵션 | 설명 |
| PLAIN | 압축/TOAST 없음 (기본적으로 작은 데이터에만 사용) |
| EXTENDED | 압축 + TOAST 허용 (기본) |
| EXTERNAL | 압축 없이 TOAST만 |
| MAIN | TOAST는 최소화하고 가능한 한 본문에 저장 |
구문 예시
ALTER TABLE <table_name> ALTER COLUMN <col_name> SET STORAGE EXTERNAL;6. TOAST 관련 파라미터
| 파라미터 | 설명 | 기본값 | 비고 |
| toast_tuple_target | TOAST 처리를 고려하는 row 크기 임계값 (바이트 단위) | 2048 bytes | 내부 하드코딩 상수이며, 사용자 설정 불가 |
| TOAST_MAX_CHUNK_SIZE | TOAST 테이블에 저장되는 각 청크의 최대 크기 | 약 1996 bytes | 하드코딩 상수이며, 사용자 설정 불가 |
| bytea_output | bytea 데이터의 출력 형식 설정 (hex 또는 escape) | hex | 사용자 설정 가능 (SET bytea_output TO 'escape'; 등) |
7. TOAST 튜닝 시 주의사항
- Autovacuum이 TOAST 테이블에도 필요합니다.
- TOAST는 비순차적 접근이 많고, 디스크 I/O 병목이 발생할 수 있습니다.
- TOAST 테이블에 HOT update는 불가능합니다.
- jsonb, text, xml 등의 칼럼에 큰 데이터가 자주 갱신되는 경우 TOAST 오버헤드가 큽니다.
- 이 경우 PLAIN 스토리지 설정이나 LOB 전략으로 구조 조정이 필요할 수 있습니다.
8. TOAST 테이블 조회 방법
-- 특정 테이블의 TOAST 테이블 확인
SELECT reltoastrelid::regclass FROM pg_class WHERE relname = 'table_name';
-- TOAST된 칼럼 확인
SELECT attname, attstorage FROM pg_attribute WHERE attrelid = 'table_name'::regclass;
-- TOAST 테이블 row 수 확인
SELECT count(*) FROM pg_toast.pg_toast_<oid>;9. 예제
테이블 생성
CREATE TABLE toast_test (id serial PRIMARY KEY, big_text text);Small Data 삽입 (TOAST 발생 X)
데이터 삽입
INSERT INTO toast_test (big_text) SELECT repeat('a', 1000); -- 약 1KB
원 테이블 컬럼 사이즈, 길이 조회
-- TOAST 조건보다 작기 때문에 TOAST 발생 X
-- pg_column_size() 단위는 byte
-- length()는 실제 데이터 길이
SELECT id, pg_column_size(big_text), length(big_text) FROM toast_test;
-- pg_column_size()는 "현재 세션에서 메모리에 로드된 값" 기준으로 동작하기 때문에,
-- TOAST된 컬럼을 접근하면 PostgreSQL이 자동으로 디토스트(de-toast)하여 본문으로 불러오고,
-- 그 결과의 전체 크기를 반환하게 됩니다.
Big Data 삽입 (TOAST 발생)
데이터 삽입
INSERT INTO toast_test (big_text) SELECT repeat('x', 10000000);
원 테이블 컬럼 사이즈, 길이 조회
SELECT id, pg_column_size(big_text), length(big_text) FROM toast_test;
TOAST 테이블명 조회
-- 원 테이블의 TOAST 테이블 이름 확인
SELECT reltoastrelid::regclass AS toast_table FROM pg_class WHERE relname = 'toast_test';
TOAST 테이블 조회
SELECT * FROM pg_toast.pg_toast_16803;
TOAST 테이블의 청크_id와 청크 수 조회
-- chunk_id 별 몇 개의 chunk로 나뉘었는지 조회
SELECT chunk_id, count(*) AS chunks FROM pg_toast.pg_toast_16803 GROUP BY chunk_id ORDER BY chunks DESC;
오늘은 여기까지~
'PostgreSQL' 카테고리의 다른 글
| PostgreSQL: WAL (0) | 2025.05.23 |
|---|---|
| PostgreSQL: reload (0) | 2025.05.22 |
| PostgreSQL: VACUUM (0) | 2025.05.17 |
| PostgreSQL: MVCC (0) | 2025.05.16 |
| PostgreSQL: SLRU(Simple Least Recently Used) 버퍼 캐시 (0) | 2025.05.15 |