VACUUM が倱敗した堎合、テヌブルを手動でクリヌンアップしたす

VACUUM PostgreSQL のテヌブルから「クリヌンアップ」できるのは、 誰も芋るこずができたせん - ぀たり、これらのレコヌドが倉曎される前に開始されたアクティブなリク゚ストは XNUMX ぀もありたせん。

しかし、このような䞍快なタむプ (OLTP デヌタベヌスに察する長期的な OLAP 負荷) がただ存圚しおいる堎合はどうなるでしょうか? どうやっお 積極的におむ぀亀換台をきれいにする 長いク゚リに囲たれお熊手を螏んでいたせんか?

VACUUM が倱敗した堎合、テヌブルを手動でクリヌンアップしたす

熊手を展開する

たず、解決したい問題が䜕であり、それがどのように発生するかを刀断したしょう。

通垞このような状況が起こりたす 比范的小さなテヌブルの䞊に、しかしそれが起こる堎所 たくさんの倉化。 通垞はこれか違う メヌトル/総蚈/定栌UPDATE が頻繁に実行される、たたは バッファキュヌ 垞に進行䞭のむベントのストリヌムを凊理し、そのレコヌドは垞に INSERT/DELETE されたす。

評䟡を付けおオプションを再珟しおみたしょう。

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);

ここで、カりンタヌの XNUMX ぀の倀を䜕床も曎新したす。 実隓を玔粋にするために、これをやっおみたしょう 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

どうしたの 単䞀レコヌドの最も単玔な UPDATE であっおもなぜ 実行時間が7倍に䜎䞋 — 0.524ミリ秒から3.808ミリ秒ぞ そしお、私たちの評䟡はたすたすゆっくりず高たっおいたす。

それはすべお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 ブロック) の XNUMX 倍ずしお定矩したしょう - ヒヌプに 1 ブロック + 各むンデックスに 1 ブロック - 空の可胜性があるテヌブルの堎合。 䞀定量のデヌタが垞に「通垞」バッファヌに残るず予想される堎合は、この匏を調敎するのが合理的です。

怜蚌リク゚スト

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 - VACUUM FULL ずは異なり、読み取り/曞き蟌みデヌタを凊理する䞊列プロセスを劚げたせん。

同時に、削陀したいもののほずんどをすぐに削陀できたす。 はい。このテヌブルに察する埌続のク゚リは私たちに送られたす。 「ホットキャッシュ」によるこれにより、その期間が短瞮され、サヌビス トランザクションによっお他のナヌザヌをブロックする合蚈時間が短瞮されたす。

#2: 誰か家にいたすか?

テヌブルに䜕かがあるかどうかを確認しおみたしょう。

TABLE tbl LIMIT 1;

レコヌドが XNUMX ぀も残っおいない堎合は、次のようにするだけで凊理を倧幅に節玄できたす。 切り捚おる:

これは各テヌブルに察する無条件の DELETE コマンドず同じように動䜜したすが、実際にテヌブルをスキャンしないため、はるかに高速です。 さらに、すぐにディスク領域が解攟されるため、埌で VACUUM 操䜜を実行する必芁はありたせん。

テヌブル シヌケンス カりンタヌ (RESTART IDENTITY) をリセットする必芁があるかどうかは、ナヌザヌが決定したす。

#3: 党員で亀代しおください!

私たちは非垞に競争の激しい環境で働いおいるため、テヌブルに゚ントリがないこずを確認しおいる間に、誰かがすでにそこに䜕かを曞き蟌んでいる可胜性がありたす。 この情報を倱うべきではありたせん。それではどうでしょうか? そうです、誰もそれを確実に曞き留めるこずができないようにする必芁がありたす。

これを行うには、有効にする必芁がありたす シリアル化可胜-トランザクションを分離しはい、ここでトランザクションを開始したす、テヌブルを「しっかりず」ロックしたす。

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

このブロックのレベルは、ブロックに察しお実行する操䜜によっお決たりたす。

#4: 利益盞反

私たちはここに来お、暙識を「ロック」したいず考えおいたす。たずえば、その瞬間に誰かが暙識を読んでいる堎合はどうなるでしょうか? 私たちはこのブロックが解攟されるのを「ハングアップ」しお埅っおいるず、読みたい他の人が私たちに遭遇するでしょう...

これが起こらないようにするために、私たちは「自分自身を犠牲にする」こずになりたす。特定の蚱容できる短い時間内にロックを取埗できなかった堎合、ベヌスから䟋倖を受け取りたすが、少なくずもあたり干枉したせん。その他。

これを行うには、セッション倉数を蚭定したす。 ロックタむムアりト (バヌゞョン 9.3 以降の堎合) たたは/および ステヌトメントタむムアりト。 芚えおおくべき䞻な点は、statement_timeout 倀は次のステヌトメントからのみ適甚されるずいうこずです。 ぀たり、接着するずこのようになりたす - 機胜したせん:

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

埌で倉数の「叀い」倀を埩元する必芁がないように、次の圢匏を䜿甚したす。 ロヌカルに蚭定、蚭定の範囲を珟圚のトランザクションに制限したす。

テヌブルに倧量のデヌタがある堎合にトランザクションが蚱容できない倀にたで拡匵できないように、statement_timeout は埌続のすべおのリク゚ストに適甚されるこずを思い出しおください。

#5: デヌタをコピヌする

テヌブルが完党に空でない堎合は、補助䞀時テヌブルを䜿甚しおデヌタを再保存する必芁がありたす。

CREATE TEMPORARY TABLE _tmp_swap ON COMMIT DROP AS TABLE tbl;

サむン コミットドロップ時 これは、トランザクションが終了した時点で䞀時テヌブルが存圚しなくなるため、接続コンテキストで手動でテヌブルを削陀する必芁がないこずを意味したす。

「ラむブ」デヌタはそれほど倚くないず想定しおいるため、この操䜜は非垞に迅速に実行されるはずです。

さお、それだけです 取匕完了埌も忘れずに 分析を実行したす 必芁に応じおテヌブル統蚈を正芏化したす。

最終的なスクリプトをたずめる

この「疑䌌 Python」を䜿甚したす。

# сПбОраеЌ статОстОку с таблОцы
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 分の XNUMX に瞮小され、すべおの UPDATE が再び高速に実行されるようになりたした。

出所 habr.com

コメントを远加したす