AUTO VACUUM을 사이즈가 큰 테이블의 경우 임계치를 조정하여 한번에 많이돌지 않도록 관리하고 있었는데
지연 쿼리가 다수 발생하는 상황이 확인되었고 내용을 확인하니
생성한 테이블의 경우 사이즈가 적었고 해당 테이블과 연결된 Toast 테이블은 사이즈가 큰 상태였습니다.
그 상황에서 Toast 테이블에 AUTO VACUUM이 동작하여 대역폭 사용으로 인한 지연이 발생하였습니다.
이에 Toast 테이블과 VACUUM을 함께 확인하였습니다.
PostgreSQL의 TOAST(TOasted Attribute Storage)
- PostgreSQL은 기본적으로 한 튜플의 사이즈가 8KB 즉 페이지를 넘으면 안 됩니다.
- text, bytea, 큰 jsonb, 긴 varchar, xml 등 가변 길이 컬럼이 너무 크면, 본문을 압축/분할해서 TOAST 테이블에 저장하고, 본 테이블에는 짧은 포인터만 둡니다.
- 덕분에 한 컬럼 값이 최대 1GB까지도 저장 가능.
Toast 테이블 동작 방식
- 압축 → 외부 저장 → 분할(chunking) 순으로 용량을 줄이거나 본문을 떼어냅니다.
- 기본 압축: pglz
- PG 14+ 에서는 컬럼/테이블 단위로 LZ4를 사용할 수 있음 (toast_compression = 'lz4').
- 본문을 떼어낼 때는 수 KB 단위 청크(약 2KB 전후)로 쪼개 TOAST 테이블에 저장, 본 테이블에는 TOAST 포인터만 남음.
- UPDATE 시 큰 값이 변경되지 않았다면 가능하면 기존 TOAST 값을 재사용해서 불필요한 재쓰기와 팽창을 줄이려고 합니다.
AUTO VACUUM 로그
2025-08-29 15:01:38 UTC::@:[18454]:LOG: automatic aggressive vacuum of table "pg_toast.pg_toast_24780": index scans: 0
pages: 0 removed, 52755374 remain, 0 skipped due to pins, 0 skipped frozen
tuples: 0 removed, 215153729 remain, 0 are dead but not yet removable, oldest xmin: 440832643
index scan bypassed: 40 pages from table (0.00% of total) have 58 dead item identifiers
I/O timings: read: 948187.810 ms, write: 0.000 ms
avg read rate: 0.078 MB/s, avg write rate: 0.000 MB/s
buffer usage: 105719498 hits, 17833 misses, 0 dirtied
WAL usage: 0 records, 0 full page images, 0 bytes
system usage: CPU: user: 430.80 s, system: 8.04 s, elapsed: 1791.08 s
SELECT
n.nspname AS schema_name,
c.relname AS table_name,
reltoastrelid::regclass as toast_table,
c.relpages as table_pages,
pg_stat_get_live_tuples(c.oid) + pg_stat_get_dead_tuples(c.oid) as total_tuple,
pg_stat_get_live_tuples(c.oid) AS live_tuple,
pg_stat_get_dead_tuples(c.oid) AS dead_tupple,
pg_size_pretty(pg_total_relation_size(c.oid)) as total_relation_size,
pg_size_pretty(pg_relation_size(c.oid)) as relation_size,
pg_relation_size(c.oid) as relation_size
FROM pg_class AS c
JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
order by 10 desc
limit 20;
schema_name | table_name | toast_table | table_pages | total_tuple | live_tuple | dead_tupple | total_relation_size | relation_size
-------------+---------------------------------------+-------------------------+-------------+-------------+------------+-------------+---------------------+---------------
public | AA | pg_toast.pg_toast_24796 | 402718399 | 5055798150 | 5055798150 | 0 | 3804 GB | 3077 GB
public | BB | pg_toast.pg_toast_24810 | 120277834 | 4915276411 | 4915276411 | 0 | 1321 GB | 919 GB
public | CC | pg_toast.pg_toast_24750 | 59274807 | 4757300005 | 4757300005 | 0 | 889 GB | 453 GB
pg_toast | pg_toast_24780 (이슈 Toast테이블) | - | 52755374 | 220469814 | 220469814 | 0 | 417 GB | 413 GB
public | 테이블A (연관 테이블 ) | pg_toast.pg_toast_24780 | 4102635 | 37393615 | 34399695 | 2993920 | 461 GB | 32 GB
msu_flowengine=> \d+ 테이블A
Table "public.테이블A"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
----------------------------+-----------------------------+-----------+----------+--------------------------------------------------------------------------------------+----------+-------------+--------------+-------------
pk | bigint | | not null | nextval(''::regclass) | plain | | |
aa | character varying(255) | | not null | | extended | | |
bb | character varying(255) | | not null | | extended | | |
cc | jsonb | | not null | | extended | | |
new_payload | jsonb | | | | extended | | |
error | text | | | | extended | | |
finished_at | timestamp without time zone | | | | plain | | |
created_at | timestamp without time zone | | | CURRENT_TIMESTAMP | plain | | |
Indexes:
Access method: heap
--age
avg | min | max
----------------------
12966 | 368 | 304690
어디에 저장되나?
- 각 본 테이블마다 필요해지면 자동으로 전용 TOAST 테이블이 생성됩니다. 스키마는 pg_toast.
- 이름 예: pg_toast.pg_toast_<relid>
- 인덱스: pg_toast_<relid>_index (청크 식별용)
어떤 컬럼이 토스트되나?
- 컬럼별 storage 전략에 따라 다름:
- PLAIN : 압축/외부저장 안 함 (사실상 TOAST 대상 아님)
- MAIN : 가능하면 본 테이블에 유지, 필요시 일부 외부
- EXTERNAL: 압축 없이 외부 저장
- EXTENDED: 기본값, 압축 후 외부 저장까지 허용(가장 공간 절약)
- 설정 바꾸기:
-- 컬럼을 무조건 본문 유지(토스트 억제)
ALTER TABLE t ALTER COLUMN bigcol SET STORAGE PLAIN;
-- 다시 기본(압축/외부 허용)
ALTER TABLE t ALTER COLUMN bigcol SET STORAGE EXTENDED;
-- (PG14+) 테이블/컬럼 압축 알고리즘 지정
ALTER TABLE t SET (toast_compression = 'lz4'); -- 테이블 전체
ALTER TABLE t ALTER COLUMN bigcol SET (compression = lz4); -- 컬럼별
성능/운영 관점 팁
- 읽기 성능: 본문이 TOAST에 있으면 추가 I/O가 필요(청크 읽기+재조합).
- 쓰기/갱신: 큰 값 업데이트는 다시 압축/분할이 일어날 수 있어 비용 큼.
- 큰 본문을 자주 바꿔야 한다면 스키마 분리 및 관리하는 설계를 고려.
- VACUUM/Autovacuum:
- TOAST 테이블에도 별도 Autovacuum이 동작합니다(파라미터 접두사 toast.autovacuum_*).
- 큰 값 UPDATE/DELETE가 많으면 TOAST 테이블 팽창이 생길 수 있으니 Autovacuum 임계치/스케줄을 조정하거나 필요시 VACUUM (FULL)/CLUSTER/재작성 고려.
- 원본 테이블을 수동 VACUUM 실행하면 맵핑된 TOAST 테이블도 함께 VACUUM이 동작합니다.
위 내용을 바탕으로 해당 테이블은 배치를 통하여 수동 VACUUM 을 처리해주는 방향으로 개선하였습니다.
'공부 > DATABASE' 카테고리의 다른 글
| [Aurora PostgreSQL] 입력 지연 장애 복기 (0) | 2025.10.08 |
|---|---|
| [Aurora, Elasticache] 대역폭 관련 (0) | 2025.10.08 |
| [ElastiCache] 실시간 알람을 위한 PUB/SUB (0) | 2025.10.08 |
| [Aurora PostgreSQL] 파티션 테이블 전환 (0) | 2025.10.08 |
| [AWS] DocumentDB Garbage Collection (0) | 2025.07.05 |