[Aurora PostgreSQL] Toast 테이블과 VACUUM

2025. 10. 8. 18:46·공부/DATABASE

AUTO VACUUM을 사이즈가 큰 테이블의 경우 임계치를 조정하여 한번에 많이돌지 않도록 관리하고 있었는데

지연 쿼리가 다수 발생하는 상황이 확인되었고 내용을 확인하니

생성한 테이블의 경우 사이즈가 적었고 해당 테이블과 연결된 Toast 테이블은 사이즈가 큰 상태였습니다.

그 상황에서 Toast 테이블에 AUTO VACUUM이 동작하여 대역폭 사용으로 인한 지연이 발생하였습니다.

이에 Toast 테이블과 VACUUM을 함께 확인하였습니다.

 

 

PostgreSQL의 TOAST(TOasted Attribute Storage)

  • PostgreSQL은 기본적으로 한 튜플의 사이즈가 8KB 즉 페이지를 넘으면 안 됩니다.
  • text, bytea, 큰 jsonb, 긴 varchar, xml 등 가변 길이 컬럼이 너무 크면, 본문을 압축/분할해서 TOAST 테이블에 저장하고, 본 테이블에는 짧은 포인터만 둡니다.
  • 덕분에 한 컬럼 값이 최대 1GB까지도 저장 가능.

Toast 테이블 동작 방식 

  1. 압축 → 외부 저장 → 분할(chunking) 순으로 용량을 줄이거나 본문을 떼어냅니다.
    • 기본 압축: pglz
    • PG 14+ 에서는 컬럼/테이블 단위로 LZ4를 사용할 수 있음 (toast_compression = 'lz4').
  2. 본문을 떼어낼 때는 수 KB 단위 청크(약 2KB 전후)로 쪼개 TOAST 테이블에 저장, 본 테이블에는 TOAST 포인터만 남음.
  3. 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
'공부/DATABASE' 카테고리의 다른 글
  • [Aurora PostgreSQL] 입력 지연 장애 복기
  • [Aurora, Elasticache] 대역폭 관련
  • [ElastiCache] 실시간 알람을 위한 PUB/SUB
  • [Aurora PostgreSQL] 파티션 테이블 전환
무는빼주세요
무는빼주세요
내 머리를 믿지 말자
  • 무는빼주세요
    공부, 기록
    무는빼주세요
  • 전체
    오늘
    어제
  • 링크

    • 링크드인 (LinkedIn)
    • 분류 전체보기 (259) N
      • 일상 (0)
      • 코딩 (77)
      • 공부 (181) N
        • DATABASE (129) N
        • 도커,쿠버네티스 (1)
        • 소소한 개발 (38)
        • 클라우드 영역 (1)
        • CS 영역 (11)
      • 포럼 (0)
  • 최근 글

  • 인기 글

  • hELLO· Designed By정상우.v4.10.5
무는빼주세요
[Aurora PostgreSQL] Toast 테이블과 VACUUM
상단으로

티스토리툴바