VACUUM์ด ์‹คํŒจํ•˜๋ฉด ํ…Œ์ด๋ธ”์„ ์ˆ˜๋™์œผ๋กœ ์ฒญ์†Œํ•ฉ๋‹ˆ๋‹ค.

์ง„๊ณต PostgreSQL์˜ ํ…Œ์ด๋ธ”์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ๋งŒ "์ •๋ฆฌ"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ฌด๋„ ๋ณผ ์ˆ˜ ์—†์–ด - ์ฆ‰, ์ด๋Ÿฌํ•œ ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ธฐ ์ „์— ์‹œ์ž‘๋œ ๋‹จ์ผ ํ™œ์„ฑ ์š”์ฒญ์ด ์—†์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ๋ถˆ์พŒํ•œ ์œ ํ˜•(OLTP ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋Œ€ํ•œ ์žฅ๊ธฐ OLAP ๋กœ๋“œ)์ด ์—ฌ์ „ํžˆ ์กด์žฌํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ์–ด๋–ป๊ฒŒ ํ™œ๋ฐœํžˆ ๋ณ€ํ™”ํ•˜๋Š” ํ…Œ์ด๋ธ”์„ ์ฒญ์†Œํ•˜์‹ญ์‹œ์˜ค ๊ธด ์งˆ๋ฌธ์— ๋‘˜๋Ÿฌ์‹ธ์—ฌ ์žˆ๊ณ  ๊ฐˆํ€ด๋ฅผ ๋ฐŸ์ง€ ์•Š์Šต๋‹ˆ๊นŒ?

VACUUM์ด ์‹คํŒจํ•˜๋ฉด ํ…Œ์ด๋ธ”์„ ์ˆ˜๋™์œผ๋กœ ์ฒญ์†Œํ•ฉ๋‹ˆ๋‹ค.

๊ฐˆํ€ด๋ฅผ ํŽผ์น˜๋‹ค

๋จผ์ €, ์šฐ๋ฆฌ๊ฐ€ ํ•ด๊ฒฐํ•˜๋ ค๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฌด์—‡์ธ์ง€, ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒฐ์ •ํ•ด ๋ด…์‹œ๋‹ค.

๋ณดํ†ต ์ด๋Ÿฐ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๋Š”๋ฐ ๋น„๊ต์  ์ž‘์€ ํ…Œ์ด๋ธ”์—, ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ณณ ๋งŽ์€ ๋ณ€ํ™”. ๋ณดํ†ต ์ด๋ ‡๊ฑฐ๋‚˜ ์•„๋‹ˆ๋ฉด ๋‹ฌ๋ผ์š” ๋ฏธํ„ฐ/์ง‘๊ณ„/๋“ฑ๊ธ‰, UPDATE๊ฐ€ ์ž์ฃผ ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ ๋˜๋Š” ๋ฒ„ํผ ํ ์ง€์†์ ์œผ๋กœ ์ง„ํ–‰๋˜๋Š” ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ๋ก์ด ์ง€์†์ ์œผ๋กœ ์‚ฝ์ž…/์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.

๋“ฑ๊ธ‰์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ต์…˜์„ ์žฌํ˜„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

CREATE TABLE tbl(k text PRIMARY KEY, v integer);
CREATE INDEX ON tbl(v DESC); -- ะฟะพ ัั‚ะพะผัƒ ะธะฝะดะตะบััƒ ะฑัƒะดะตะผ ัั‚ั€ะพะธั‚ัŒ ั€ะตะนั‚ะธะฝะณ

INSERT INTO
  tbl
SELECT
  chr(ascii('a'::text) + i) k
, 0 v
FROM
  generate_series(0, 25) i;

๊ทธ๋ฆฌ๊ณ  ๋™์‹œ์— ๋‹ค๋ฅธ ์—ฐ๊ฒฐ์—์„œ๋Š” ๊ธธ๊ณ  ๊ธด ์š”์ฒญ์ด ์‹œ์ž‘๋˜์–ด ๋ณต์žกํ•œ ํ†ต๊ณ„๋ฅผ ์ˆ˜์ง‘ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ ํ…Œ์ด๋ธ”์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์•„:

SELECT pg_sleep(10000);

์ด์ œ ์šฐ๋ฆฌ๋Š” ์นด์šดํ„ฐ ์ค‘ ํ•˜๋‚˜์˜ ๊ฐ’์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์‹คํ—˜์˜ ์ˆœ์ˆ˜์„ฑ์„ ์œ„ํ•ด ์ด๋ ‡๊ฒŒ ํ•ฉ์‹œ๋‹ค. dblink๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ„๋„์˜ ํŠธ๋žœ์žญ์…˜์—์„œ์‹ค์ œ๋กœ๋Š” ์–ด๋–ป๊ฒŒ ๋ ์ง€:

DO $$
DECLARE
  i integer;
  tsb timestamp;
  tse timestamp;
  d double precision;
BEGIN
  PERFORM dblink_connect('dbname=' || current_database() || ' port=' || current_setting('port'));
  FOR i IN 1..10000 LOOP
    tsb = clock_timestamp();
    PERFORM dblink($e$UPDATE tbl SET v = v + 1 WHERE k = 'a';$e$);
    tse = clock_timestamp();
    IF i % 1000 = 0 THEN
      d = (extract('epoch' from tse) - extract('epoch' from tsb)) * 1000;
      RAISE NOTICE 'i = %, exectime = %', lpad(i::text, 5), lpad(d::text, 5);
    END IF;
  END LOOP;
  PERFORM dblink_disconnect();
END;
$$ LANGUAGE plpgsql;

NOTICE:  i =  1000, exectime = 0.524
NOTICE:  i =  2000, exectime = 0.739
NOTICE:  i =  3000, exectime = 1.188
NOTICE:  i =  4000, exectime = 2.508
NOTICE:  i =  5000, exectime = 1.791
NOTICE:  i =  6000, exectime = 2.658
NOTICE:  i =  7000, exectime = 2.318
NOTICE:  i =  8000, exectime = 2.572
NOTICE:  i =  9000, exectime = 2.929
NOTICE:  i = 10000, exectime = 3.808

๋ฌด์Šจ ์ผ์ด์—์š”? ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ์— ๋Œ€ํ•œ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์—…๋ฐ์ดํŠธ์ธ ์ด์œ  ์‹คํ–‰ ์‹œ๊ฐ„์ด 7๋ฐฐ ๋‹จ์ถ•๋จ - 0.524ms์—์„œ 3.808ms๋กœ? ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ์˜ ํ‰๊ฐ€๋Š” ์ ์  ๋” ๋Š๋ฆฌ๊ฒŒ ๊ตฌ์ถ•๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋‘ MVCC์˜ ์ž˜๋ชป์ž…๋‹ˆ๋‹ค.

๋ชจ๋“  ๊ฒƒ์ด ๋‹ค MVCC ๋ฉ”์ปค๋‹ˆ์ฆ˜, ์ด๋กœ ์ธํ•ด ์ฟผ๋ฆฌ๋Š” ํ•ญ๋ชฉ์˜ ๋ชจ๋“  ์ด์ „ ๋ฒ„์ „์„ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค. ์ด์ œ "์ฃฝ์€" ๋ฒ„์ „์—์„œ ํ…Œ์ด๋ธ”์„ ์ •๋ฆฌํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

VACUUM VERBOSE tbl;

INFO:  vacuuming "public.tbl"
INFO:  "tbl": found 0 removable, 10026 nonremovable row versions in 45 out of 45 pages
DETAIL:  10000 dead row versions cannot be removed yet, oldest xmin: 597439602

์•„, ์ฒญ์†Œํ•  ๊ฒŒ ํ•˜๋‚˜๋„ ์—†์–ด! ํ‰ํ–‰ํ•œ ์‹คํ–‰ ์ค‘์ธ ์š”์ฒญ์ด ์šฐ๋ฆฌ๋ฅผ ๋ฐฉํ•ดํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. - ๊ฒฐ๊ตญ ๊ทธ๋Š” ์–ธ์  ๊ฐ€ ์ด๋Ÿฌํ•œ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ(๋งŒ์•ฝ ๊ทธ๋ ‡๋‹ค๋ฉด?) ํ•ด๋‹น ๋ฒ„์ „์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ VACUUM FULL๋„ ์šฐ๋ฆฌ์—๊ฒŒ ๋„์›€์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ” "์ ‘๊ธฐ"

๊ทธ๋Ÿฌ๋‚˜ ์šฐ๋ฆฌ๋Š” ํ•ด๋‹น ์ฟผ๋ฆฌ์— ํ…Œ์ด๋ธ”์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ํ™•์‹คํžˆ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” VACUUM์ด ์ œ๊ณต๋˜๋ฏ€๋กœ ์ ์–ด๋„ "์ˆ˜๋™์œผ๋กœ" ํ…Œ์ด๋ธ”์—์„œ ๋ถˆํ•„์š”ํ•œ ๋ชจ๋“  ๊ฒƒ์„ ์ œ๊ฑฐํ•˜์—ฌ ์‹œ์Šคํ…œ ์„ฑ๋Šฅ์„ ์ ์ ˆํ•œ ํ•œ๋„๊นŒ์ง€ ๋˜๋Œ๋ฆฌ๋ ค๊ณ  ๋…ธ๋ ฅํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ข€ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ๋ฒ„ํผ ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ฆ‰, INSERT/DELETE์˜ ํ๋ฆ„์ด ํฌ๊ณ , ํ…Œ์ด๋ธ”์ด ์™„์ „ํžˆ ๋น„์–ด ์žˆ๋Š” ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋น„์–ด์žˆ์ง€ ์•Š๋‹ค๋ฉด, ์šฐ๋ฆฌ๋Š” ํ˜„์žฌ ๋‚ด์šฉ์„ ์ €์žฅ.

#0: ์ƒํ™ฉ ํ‰๊ฐ€

๊ฐ ์ž‘์—… ํ›„์—๋„ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ ๋ถ„๋ช…ํ•˜์ง€๋งŒ ์ด๋Š” ๊ทธ๋‹ค์ง€ ์˜๋ฏธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์œ ์ง€ ๊ด€๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œ๋Š” ๋ถ„๋ช…ํžˆ ๋Œ€์ƒ ์ฟผ๋ฆฌ์˜ ์ฒ˜๋ฆฌ๋Ÿ‰๋ณด๋‹ค ํด ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ "ํ–‰๋™ํ•  ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค"๋ผ๋Š” ๊ธฐ์ค€์„ ๊ณต์‹ํ™”ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • VACUUM์€ ๊ฝค ์˜ค๋ž˜ ์ „์— ์ถœ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
    ๋ถ€ํ•˜๊ฐ€ ํด ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ๋‘์‹ญ์‹œ์˜ค. 60 ์ดˆ ๋งˆ์ง€๋ง‰ [์ž๋™]VACUUM ์ดํ›„.
  • ๋ฌผ๋ฆฌ์  ํ…Œ์ด๋ธ” ํฌ๊ธฐ๊ฐ€ ๋Œ€์ƒ๋ณด๋‹ค ํฝ๋‹ˆ๋‹ค.
    ์ตœ์†Œ ํฌ๊ธฐ ๋Œ€๋น„ ํŽ˜์ด์ง€ ์ˆ˜(8KB ๋ธ”๋ก)์˜ ๋‘ ๋ฐฐ๋กœ ์ •์˜ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ํž™์— ๋Œ€ํ•ด 1 blk + ๊ฐ ์ธ๋ฑ์Šค์— ๋Œ€ํ•ด 1 blk - ์ž ์žฌ์ ์œผ๋กœ ๋น„์–ด ์žˆ๋Š” ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ. ์ผ์ •๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•ญ์ƒ "์ •์ƒ์ ์œผ๋กœ" ๋ฒ„ํผ์— ๋‚จ์•„ ์žˆ์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•œ๋‹ค๋ฉด ์ด ๊ณต์‹์„ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค.

ํ™•์ธ์š”์ฒญ

SELECT
  relpages
, ((
    SELECT
      count(*)
    FROM
      pg_index
    WHERE
      indrelid = cl.oid
  ) + 1) << 13 size_norm -- ั‚ัƒั‚ ะฟั€ะฐะฒะธะปัŒะฝะตะต ะดะตะปะฐั‚ัŒ * current_setting('block_size')::bigint, ะฝะพ ะบั‚ะพ ะผะตะฝัะตั‚ ั€ะฐะทะผะตั€ ะฑะปะพะบะฐ?..
, pg_total_relation_size(oid) size
, coalesce(extract('epoch' from (now() - greatest(
    pg_stat_get_last_vacuum_time(oid)
  , pg_stat_get_last_autovacuum_time(oid)
  ))), 1 << 30) vaclag
FROM
  pg_class cl
WHERE
  oid = $1::regclass -- tbl
LIMIT 1;

relpages | size_norm | size    | vaclag
-------------------------------------------
       0 |     24576 | 1105920 | 3392.484835

#1: ์—ฌ์ „ํžˆ ์ง„๊ณต ์ƒํƒœ

๋ณ‘๋ ฌ ์ฟผ๋ฆฌ๊ฐ€ ์šฐ๋ฆฌ๋ฅผ ํฌ๊ฒŒ ๋ฐฉํ•ดํ•˜๋Š”์ง€ ์—ฌ๋ถ€, ์ฆ‰ ์‹œ์ž‘๋œ ์ดํ›„๋กœ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๋ ˆ์ฝ”๋“œ๊ฐ€ "์˜ค๋ž˜๋œ" ๋ ˆ์ฝ”๋“œ์ธ์ง€ ๋ฏธ๋ฆฌ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์šฐ๋ฆฌ๊ฐ€ ์–ด๋–ป๊ฒŒ๋“  ํ…Œ์ด๋ธ”์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค๋ฉด, ์–ด๋–ค ๊ฒฝ์šฐ๋“  ๋จผ์ € ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ์‹คํ–‰์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ง„๊ณต - VACUUM FULL๊ณผ ๋‹ฌ๋ฆฌ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ/์“ฐ๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ณ‘๋ ฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ฐฉํ•ดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋™์‹œ์— ์ œ๊ฑฐํ•˜๋ ค๋Š” ๋Œ€๋ถ€๋ถ„์˜ ํ•ญ๋ชฉ์„ ์ฆ‰์‹œ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ, ์ด ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ํ›„์† ์ฟผ๋ฆฌ๋Š” ์šฐ๋ฆฌ์—๊ฒŒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. "ํ•ซ ์บ์‹œ"๋กœ, ์ด๋Š” ์ง€์† ์‹œ๊ฐ„์„ ์ค„์—ฌ์ค๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋น„์Šค ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ธํ•ด ๋‹ค๋ฅธ ์‚ฌ๋žŒ์„ ์ฐจ๋‹จํ•˜๋Š” ์ด ์‹œ๊ฐ„๋„ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

#2: ์ง‘์— ๋ˆ„๊ตฌ ์žˆ์–ด์š”?

ํ…Œ์ด๋ธ”์— ์•„๋ฌด๊ฒƒ๋„ ์—†๋Š”์ง€ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

TABLE tbl LIMIT 1;

๋‹จ์ผ ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋‚จ์•„ ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ฐ„๋‹จํžˆ ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„์„ ๋งŽ์ด ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž๋ฅด๊ธฐ:

์ด๋Š” ๊ฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ๋ฌด์กฐ๊ฑด์ ์ธ DELETE ๋ช…๋ น๊ณผ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ ํ…Œ์ด๋ธ”์„ ์Šค์บ”ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํ›จ์”ฌ ๋น ๋ฆ…๋‹ˆ๋‹ค. ๋˜ํ•œ ์ฆ‰์‹œ ๋””์Šคํฌ ๊ณต๊ฐ„์„ ํ™•๋ณดํ•˜๋ฏ€๋กœ ๋‚˜์ค‘์— VACUUM ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ” ์‹œํ€€์Šค ์นด์šดํ„ฐ(RESTART IDENTITY)๋ฅผ ์žฌ์„ค์ •ํ•ด์•ผ ํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

#3: ๋ชจ๋‘ - ์ฐจ๋ก€๋Œ€๋กœ ์ง„ํ–‰ํ•˜์„ธ์š”!

์šฐ๋ฆฌ๋Š” ๊ฒฝ์Ÿ์ด ์น˜์—ดํ•œ ํ™˜๊ฒฝ์—์„œ ์ผํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๊ธฐ์—์„œ ํ…Œ์ด๋ธ”์— ํ•ญ๋ชฉ์ด ์—†๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋™์•ˆ ๋ˆ„๊ตฐ๊ฐ€ ์ด๋ฏธ ๊ฑฐ๊ธฐ์— ๋ฌด์–ธ๊ฐ€๋ฅผ ์ผ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด ์ •๋ณด๋ฅผ ์žƒ์–ด๋ฒ„๋ ค์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ๊ทธ๋ ‡์ฃ , ๋ˆ„๊ตฌ๋„ ํ™•์‹คํ•˜๊ฒŒ ๊ธฐ๋กํ•  ์ˆ˜ ์—†๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ง๋ ฌํ™” ๊ฐ€๋Šฅ-ํŠธ๋žœ์žญ์…˜์„ ๊ฒฉ๋ฆฌํ•˜๊ณ (์˜ˆ, ์—ฌ๊ธฐ์„œ๋Š” ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค) ํ…Œ์ด๋ธ”์„ "๋‹จ๋‹จํžˆ" ์ž ๊ธ‰๋‹ˆ๋‹ค.

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
LOCK TABLE tbl IN ACCESS EXCLUSIVE MODE;

์ด ์ฐจ๋‹จ ์ˆ˜์ค€์€ ์ˆ˜ํ–‰ํ•˜๋ ค๋Š” ์ž‘์—…์— ๋”ฐ๋ผ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.

#4: ์ดํ•ด ์ƒ์ถฉ

์šฐ๋ฆฌ๋Š” ์—ฌ๊ธฐ์— ์™€์„œ ํ‘œ์ง€ํŒ์„ "์ž ๊ทธ๊ณ " ์‹ถ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ทธ ์ˆœ๊ฐ„ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ํ‘œ์ง€ํŒ์„ ์ฝ๊ณ  ์žˆ์—ˆ๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š”? ์šฐ๋ฆฌ๋Š” ์ด ๋ธ”๋ก์ด ํ•ด์ œ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ โ€œ์ค‘์ง€โ€ํ•  ๊ฒƒ์ด๋ฉฐ, ์ฝ๊ณ  ์‹ถ์–ดํ•˜๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์šฐ๋ฆฌ๋ฅผ ๋งŒ๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค...

์ด๋Ÿฐ ์ผ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์šฐ๋ฆฌ๋Š” "์šฐ๋ฆฌ ์ž์‹ ์„ ํฌ์ƒ"ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠน์ •(ํ—ˆ์šฉ ๊ฐ€๋Šฅํ•œ ์งง์€) ์‹œ๊ฐ„ ๋‚ด์— ์ž ๊ธˆ์„ ์–ป์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ๋ฒ ์ด์Šค์—์„œ ์˜ˆ์™ธ๋ฅผ ๋ฐ›๊ฒŒ ๋˜์ง€๋งŒ ์ตœ์†Œํ•œ ๋„ˆ๋ฌด ๋งŽ์ด ๊ฐ„์„ญํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์‚ฌ๋žŒ.

์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด ์„ธ์…˜ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์‹ญ์‹œ์˜ค. lock_timeout (๋ฒ„์ „ 9.3+์˜ ๊ฒฝ์šฐ) ๋˜๋Š”/๋ฐ ๋ฌธ_์‹œ๊ฐ„ ์ดˆ๊ณผ. ๊ธฐ์–ตํ•ด์•ผ ํ•  ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ ์€statement_timeout ๊ฐ’์€ ๋‹ค์Œ ๋ช…๋ น๋ฌธ์—์„œ๋งŒ ์ ์šฉ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์ ‘์ฐฉ์—์„œ ์ด๋ ‡๊ฒŒ- ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค:

SET statement_timeout = ...;LOCK TABLE ...;

๋‚˜์ค‘์— ๋ณ€์ˆ˜์˜ "์ด์ „" ๊ฐ’์„ ๋ณต์›ํ•˜๋Š” ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋„๋ก ๋‹ค์Œ ํ˜•์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ๋กœ ์„ค์ •, ์ด๋Š” ์„ค์ • ๋ฒ”์œ„๋ฅผ ํ˜„์žฌ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ด๋ธ”์— ๋งŽ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํŠธ๋žœ์žญ์…˜์ด ํ—ˆ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฐ’์œผ๋กœ ํ™•์žฅ๋˜์ง€ ์•Š๋„๋ก ๋ชจ๋“  ํ›„์† ์š”์ฒญ์— statement_timeout์ด ์ ์šฉ๋œ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•ฉ๋‹ˆ๋‹ค.

#5: ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ

ํ…Œ์ด๋ธ”์ด ์™„์ „ํžˆ ๋น„์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด ๋ณด์กฐ ์ž„์‹œ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

CREATE TEMPORARY TABLE _tmp_swap ON COMMIT DROP AS TABLE tbl;

์„œ๋ช… ์ปค๋ฐ‹ ์‚ญ์ œ ์‹œ ์ด๋Š” ํŠธ๋žœ์žญ์…˜์ด ์ข…๋ฃŒ๋˜๋Š” ์ˆœ๊ฐ„ ์ž„์‹œ ํ…Œ์ด๋ธ”์ด ๋” ์ด์ƒ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉฐ ์—ฐ๊ฒฐ ์ปจํ…์ŠคํŠธ์—์„œ ์ˆ˜๋™์œผ๋กœ ์‚ญ์ œํ•  ํ•„์š”๊ฐ€ ์—†์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

"๋ผ์ด๋ธŒ" ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์ง€ ์•Š๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฏ€๋กœ ์ด ์ž‘์—…์€ ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ธ€์Ž„, ๊ทธ๊ฒŒ ๋‹ค์•ผ! ๊ฑฐ๋ž˜ ์™„๋ฃŒ ํ›„ ์žŠ์ง€ ๋งˆ์„ธ์š” ๋ถ„์„ ์‹คํ–‰ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํ…Œ์ด๋ธ” ํ†ต๊ณ„๋ฅผ ์ •๊ทœํ™”ํ•ฉ๋‹ˆ๋‹ค.

์ตœ์ข… ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

์šฐ๋ฆฌ๋Š” ์ด "์˜์‚ฌ ํŒŒ์ด์ฌ"์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

# ัะพะฑะธั€ะฐะตะผ ัั‚ะฐั‚ะธัั‚ะธะบัƒ ั ั‚ะฐะฑะปะธั†ั‹
stat <-
  SELECT
    relpages
  , ((
      SELECT
        count(*)
      FROM
        pg_index
      WHERE
        indrelid = cl.oid
    ) + 1) << 13 size_norm
  , pg_total_relation_size(oid) size
  , coalesce(extract('epoch' from (now() - greatest(
      pg_stat_get_last_vacuum_time(oid)
    , pg_stat_get_last_autovacuum_time(oid)
    ))), 1 << 30) vaclag
  FROM
    pg_class cl
  WHERE
    oid = $1::regclass -- table_name
  LIMIT 1;

# ั‚ะฐะฑะปะธั†ะฐ ะฑะพะปัŒัˆะต ั†ะตะปะตะฒะพะณะพ ั€ะฐะทะผะตั€ะฐ ะธ VACUUM ะฑั‹ะป ะดะฐะฒะฝะพ
if stat.size > 2 * stat.size_norm and stat.vaclag is None or stat.vaclag > 60:
  -> VACUUM %table;
  try:
    -> BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    # ะฟั‹ั‚ะฐะตะผัั ะทะฐั…ะฒะฐั‚ะธั‚ัŒ ะผะพะฝะพะฟะพะปัŒะฝัƒัŽ ะฑะปะพะบะธั€ะพะฒะบัƒ ั ะฟั€ะตะดะตะปัŒะฝั‹ะผ ะฒั€ะตะผะตะฝะตะผ ะพะถะธะดะฐะฝะธั 1s
    -> SET LOCAL statement_timeout = '1s'; SET LOCAL lock_timeout = '1s';
    -> LOCK TABLE %table IN ACCESS EXCLUSIVE MODE;
    # ะฝะฐะดะพ ัƒะฑะตะดะธั‚ัŒัั ะฒ ะฟัƒัั‚ะพั‚ะต ั‚ะฐะฑะปะธั†ั‹ ะฒะฝัƒั‚ั€ะธ ั‚ั€ะฐะฝะทะฐะบั†ะธะธ ั ะฑะปะพะบะธั€ะพะฒะบะพะน
    row <- TABLE %table LIMIT 1;
    # ะตัะปะธ ะฒ ั‚ะฐะฑะปะธั†ะต ะฝะตั‚ ะฝะธ ะพะดะฝะพะน "ะถะธะฒะพะน" ะทะฐะฟะธัะธ - ะพั‡ะธั‰ะฐะตะผ ะตะต ะฟะพะปะฝะพัั‚ัŒัŽ, ะฒ ะฟั€ะพั‚ะธะฒะฝะพะผ ัะปัƒั‡ะฐะต - "ะฟะตั€ะตะฒัั‚ะฐะฒะปัะตะผ" ะฒัะต ะทะฐะฟะธัะธ ั‡ะตั€ะตะท ะฒั€ะตะผะตะฝะฝัƒัŽ ั‚ะฐะฑะปะธั†ัƒ
    if row is None:
      -> TRUNCATE TABLE %table RESTART IDENTITY;
    else:
      # ัะพะทะดะฐะตะผ ะฒั€ะตะผะตะฝะฝัƒัŽ ั‚ะฐะฑะปะธั†ัƒ ั ะดะฐะฝะฝั‹ะผะธ ั‚ะฐะฑะปะธั†ั‹-ะพั€ะธะณะธะฝะฐะปะฐ
      -> CREATE TEMPORARY TABLE _tmp_swap ON COMMIT DROP AS TABLE %table;
      # ะพั‡ะธั‰ะฐะตะผ ะพั€ะธะณะธะฝะฐะป ะฑะตะท ัะฑั€ะพัะฐ ะฟะพัะปะตะดะพะฒะฐั‚ะตะปัŒะฝะพัั‚ะธ
      -> TRUNCATE TABLE %table;
      # ะฒัั‚ะฐะฒะปัะตะผ ะฒัะต ัะพั…ั€ะฐะฝะตะฝะฝั‹ะต ะฒะพ ะฒั€ะตะผะตะฝะฝะพะน ั‚ะฐะฑะปะธั†ะต ะดะฐะฝะฝั‹ะต ะพะฑั€ะฐั‚ะฝะพ
      -> INSERT INTO %table TABLE _tmp_swap;
    -> COMMIT;
  except Exception as e:
    # ะตัะปะธ ะผั‹ ะฟะพะปัƒั‡ะธะปะธ ะพัˆะธะฑะบัƒ, ะฝะพ ัะพะตะดะธะฝะตะฝะธะต ะฒัะต ะตั‰ะต "ะถะธะฒะพ" - ัะปะพะฒะธะปะธ ั‚ะฐะนะผะฐัƒั‚
    if not isinstance(e, InterfaceError):
      -> ROLLBACK;

๋ฐ์ดํ„ฐ๋ฅผ ๋‘ ๋ฒˆ ๋ณต์‚ฌํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?์›์น™์ ์œผ๋กœ ํ…Œ์ด๋ธ” ์ž์ฒด์˜ oid๊ฐ€ BL ์ธก์˜ ๋‹ค๋ฅธ ํ™œ๋™์ด๋‚˜ DB ์ธก์˜ FK์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

CREATE TABLE _swap_%table(LIKE %table INCLUDING ALL);
INSERT INTO _swap_%table TABLE %table;
DROP TABLE %table;
ALTER TABLE _swap_%table RENAME TO %table;

์†Œ์Šค ํ…Œ์ด๋ธ”์—์„œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์ธก์ •ํ•ญ๋ชฉ์„ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

VACUUM tbl;
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  SET LOCAL statement_timeout = '1s'; SET LOCAL lock_timeout = '1s';
  LOCK TABLE tbl IN ACCESS EXCLUSIVE MODE;
  CREATE TEMPORARY TABLE _tmp_swap ON COMMIT DROP AS TABLE tbl;
  TRUNCATE TABLE tbl;
  INSERT INTO tbl TABLE _tmp_swap;
COMMIT;

relpages | size_norm | size   | vaclag
-------------------------------------------
       0 |     24576 |  49152 | 32.705771

๋ชจ๋“  ๊ฒƒ์ด ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ํ…Œ์ด๋ธ”์ด 50๋ฐฐ๋กœ ์ค„์–ด๋“ค์—ˆ๊ณ  ๋ชจ๋“  ์—…๋ฐ์ดํŠธ๊ฐ€ ๋‹ค์‹œ ๋น ๋ฅด๊ฒŒ ์‹คํ–‰๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€