# Очистка и заморозка

Индекс LLMS: [llms.txt](/ru/llms.txt)

---

<span id="internal-ru-chapter-6" class="pdf-anchor"></span><span id="internal-ru-page-126" class="pdf-anchor"></span>

<span id="internal-ru-dummy-48" class="pdf-anchor"></span>

## 6.1. Очистка {#internal-ru-section-6-1}

Самоочистка выполняется быстро, но освобождает только часть места. Она работает в пределах одной табличной страницы и не затрагивает индексы (или наоборот: в пределах одной индексной и не затрагивает таблицу).

Основная, *обычная очистка* [^internal-ru-126-1] выполняется командой `VACUUM` [^internal-ru-126-2]. Она обрабатывает таблицу полностью, вычищая не только ненужные версии строк, но и ссылки на них из всех индексов.

Очистка работает параллельно с другой активностью в системе. Таблица и индексы при этом могут использоваться обычным образом и для чтения, и для изменения (однако одновременное выполнение таких команд, как `CREATE INDEX`, `ALTER TABLE`, и некоторых других будет невозможно). (*с.* [261](/ru/12-relation-level-locks/#internal-ru-page-262))

Чтобы не просматривать лишние страницы, используется карта видимости. Отмеченные в ней страницы пропускаются, поскольку содержат только актуальные версии строк, а вот в остальных могут быть версии, которые можно вычистить. Очистка обновляет карту видимости, если после обработки на странице остаются только версии строк за горизонтом базы данных. (*с.* [32](/ru/01-introduction/#internal-ru-page-33))

Очистка обновляет и карту свободного пространства, отражая появившееся в страницах свободное место.

<span id="internal-ru-page-127" class="pdf-anchor"></span><span id="internal-ru-dummy-49" class="pdf-anchor"></span>

Создадим таблицу и индекс:

```text {label="psql"}
=> CREATE TABLE vac(
  id integer,
  s char(100)
) WITH (autovacuum_enabled = off);
=> CREATE INDEX vac_s ON vac(s);
```

Здесь с помощью параметра хранения *autovacuum\_enabled* отключается автоматическая очистка, чтобы — исключительно для целей эксперимента — точно управлять моментом очистки.

Добавляем строку и выполняем пару обновлений:

```text {label="psql"}
=> INSERT INTO vac(id,s) VALUES (1,'A');
=> UPDATE vac SET s = 'B';
=> UPDATE vac SET s = 'C';
```

Сейчас в таблице три версии строки:

```text {label="psql"}
=> SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax  | hhu | hot | t_ctid
-------+--------+-------+-------+-----+-----+--------
 (0,1) | normal | 845 c | 846 c |     |     | (0,2)
 (0,2) | normal | 846 c | 847   |     |     | (0,3)
 (0,3) | normal | 847   | 0 a   |     |     | (0,3)
(3 rows)
```

На каждую из них ведет ссылка из индекса:

```text {label="psql"}
=> SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,1) | f
          2 | (0,2) | f
          3 | (0,3) | f
(3 rows)
```

После очистки мертвые версии строк пропадают, и остается только одна, актуальная:

<span id="internal-ru-page-128" class="pdf-anchor"></span>

```text {label="psql"}
=> VACUUM vac;
=> SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax | hhu | hot | t_ctid
-------+--------+-------+------+-----+-----+--------
 (0,1) | unused |       |      |     |     |
 (0,2) | unused |       |      |     |     |
 (0,3) | normal | 847 c | 0 a  |     |     | (0,3)
(3 rows)
```

При этом два первых указателя, ссылавшиеся на вычищенные версии, получили статус unused, а не dead, как было бы при самоочистке, поскольку на них больше нет ссылок из индекса:

```text {label="psql"}
=> SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,3) | f
(1 row)
```

Указатели в статусе unused считаются свободными и могут быть задействованы для новых версий строк.

Страница теперь отмечена в карте видимости, куда мы можем заглянуть с помощью расширения `pg_visibility`:

```text {label="psql"}
=> CREATE EXTENSION pg_visibility;
=> SELECT all_visible
FROM pg_visibility_map('vac',0);
 all_visible
-------------
 t
(1 row)
```

<span id="internal-ru-dummy-50" class="pdf-anchor"></span>

Признак того, что все версии строк на странице видны во всех снимках данных, проставляется и в заголовке табличной страницы:

```text {label="psql"}
=> SELECT flags & 4 > 0 AS all_visible
FROM page_header(get_raw_page('vac',0));
 all_visible
-------------
 t
(1 row)
```

<span id="internal-ru-page-129" class="pdf-anchor"></span>

## 6.2. Еще раз о горизонте базы данных {#internal-ru-section-6-2}

Определяя, какие версии строк считать мертвыми, очистка ориентируется на горизонт базы данных. Это настолько важное понятие, что к нему следует вернуться еще раз.

Повторим предыдущий опыт с начала:

```text {label="psql"}
=> TRUNCATE vac;
=> INSERT INTO vac(id,s) VALUES (1,'A');
=> UPDATE vac SET s = 'B';
```

<span id="internal-ru-dummy-51" class="pdf-anchor"></span>

Но перед тем как снова обновить строку, начнем вторую транзакцию, удерживающую горизонт базы данных (подойдет практически любая транзакция, кроме виртуальной с уровнем изоляции Read Committed). Например, пусть транзакция поменяет что-нибудь в *другой* таблице. (*с.* [106](/ru/04-snapshots/#internal-ru-page-107))

```text {label="psql"}
=> BEGIN;
=> INSERT INTO accounts(id, client, amount)
  VALUES (1, 'alice', 1000.00);
```

```text {label="psql"}
=> UPDATE vac SET s = 'C';
```

Сейчас в таблице три версии строки, а в индексе — три ссылки. Что изменится после очистки?

```text {label="psql"}
=> VACUUM vac;
=> SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax  | hhu | hot | t_ctid
-------+--------+-------+-------+-----+-----+--------
 (0,1) | unused |       |       |     |     |
 (0,2) | normal | 851 c | 853 c |     |     | (0,3)
 (0,3) | normal | 853 c | 0 a   |     |     | (0,3)
(3 rows)
=> SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,2) | f
          2 | (0,3) | f
(2 rows)
```

<span id="internal-ru-page-130" class="pdf-anchor"></span>

Если в прошлый раз в таблице осталась только одна версия строки, то теперь их две: очистка решила, что версия (0,2) еще не может быть удалена. Причина, конечно, в горизонте базы данных, который в нашем примере определяется незавершенной транзакцией:

```text {label="psql"}
=> SELECT backend_xmin FROM pg_stat_activity
WHERE pid = pg_backend_pid();
 backend_xmin
--------------
          852
(1 row)
```

Можно попросить очистку рассказать о том, что происходит, указав предложение `VERBOSE`:

```text {label="psql" hl_lines="6 7"}
=> VACUUM VERBOSE vac;
INFO:  vacuuming "internals.public.vac"
INFO:  finished vacuuming "internals.public.vac": index scans: 0
pages: 0 removed, 1 remain, 1 scanned (100.00% of total), 0 eagerly
scanned
tuples: 0 removed, 2 remain, 1 are dead but not yet removable
removable cutoff: 852, which was 2 XIDs old when operation ended
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
visibility map: 0 pages set all-visible, 0 pages set all-frozen (0 were
all-visible)
index scan not needed: 0 pages from table (0.00% of total) had 0 dead
item identifiers removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 14 hits, 0 reads, 0 dirtied
WAL usage: 0 records, 0 full page images, 0 bytes, 0 buffers full
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM
```

Здесь видно, что:

- в таблице не обнаружено версий строк, которые можно было бы удалить (`tuples: 0 removed`);

- найдено две версии, которые удалять нельзя (`2 remain`);

- одна из неудаляемых строк мертвая (`1 are dead but not yet removable`), другая — актуальная;

- текущий горизонт очистки (`removable cutoff`) совпадает с горизонтом открытой транзакции.

<span id="internal-ru-page-131" class="pdf-anchor"></span>

Завершение открытой транзакции приводит к тому, что горизонт базы данных продвигается и позволяет продолжить очистку:

```text {label="psql"}
=> COMMIT;
```

```text {label="psql" hl_lines="6 7 10"}
=> VACUUM VERBOSE vac;
INFO:  vacuuming "internals.public.vac"
INFO:  finished vacuuming "internals.public.vac": index scans: 1
pages: 0 removed, 1 remain, 1 scanned (100.00% of total), 0 eagerly
scanned
tuples: 1 removed, 1 remain, 0 are dead but not yet removable
removable cutoff: 854, which was 0 XIDs old when operation ended
new relfrozenxid: 853, which is 2 XIDs ahead of previous value
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
visibility map: 1 pages set all-visible, 0 pages set all-frozen (0 were
all-visible)
index scan needed: 1 pages from table (100.00% of total) had 1 dead
item identifiers removed
index "vac_s": pages: 2 in total, 0 newly deleted, 0 currently deleted,
0 reusable
avg read rate: 0.000 MB/s, avg write rate: 95.274 MB/s
buffer usage: 27 hits, 0 reads, 1 dirtied
WAL usage: 6 records, 1 full page images, 8981 bytes, 0 buffers full
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM
```

Очистка обнаружила ушедшую за изменившийся горизонт мертвую версию и удалила ее; в странице осталась только последняя, актуальная версия:

```text {label="psql"}
=> SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax | hhu | hot | t_ctid
-------+--------+-------+------+-----+-----+--------
 (0,1) | unused |       |      |     |     |
 (0,2) | unused |       |      |     |     |
 (0,3) | normal | 853 c | 0 a  |     |     | (0,3)
(3 rows)
```

В индексе также только одна запись:

```text {label="psql"}
=> SELECT * FROM index_page('vac_s',1);
 itemoffset | htid  | dead
------------+-------+------
          1 | (0,3) | f
(1 row)
```

<span id="internal-ru-page-132" class="pdf-anchor"></span>

## 6.3. Этапы выполнения очистки {#internal-ru-section-6-3}

<span id="internal-ru-dummy-52" class="pdf-anchor"></span>

Работа очистки выглядит несложной, но это кажущаяся простота. Ведь и таблицу, и индексы надо обрабатывать одновременно, не блокируя при этом работу остальных процессов. Для этого очистка каждой таблицы выполняется в несколько этапов[^internal-ru-132-1].

Вначале таблица сканируется в поисках мертвых версий строк; найденные версии вычищаются сперва из индекса, а затем из таблицы. Этот процесс может повторяться, если мертвых версий оказалось слишком много. В конце работы таблица по возможности усекается.

Дальше мы поговорим об этих этапах подробнее.

<span id="internal-ru-section-star-93" class="pdf-anchor"></span>

### Сканирование таблицы {#internal-ru-subsection-star-94}

<span id="internal-ru-dummy-53" class="pdf-anchor"></span>

Все начинается со *сканирования таблицы* [^internal-ru-132-2] с учетом карты видимости: отмеченные в карте страницы пропускаются, поскольку гарантированно содержат только актуальные версии строк. Идентификаторы (`tid`) тех версий строк из прочитанных страниц, что выходят за горизонт и больше не нужны, записываются в специальное хранилище в памяти. Сразу удалять эти версии строк нельзя, поскольку на них могут ссылаться индексы. (v. 17)

Хранилище представляет собой префиксное дерево[^internal-ru-132-3]. Ключом в нем служат номера страниц, а в узлах хранятся битовые карты версий строк. Структура ограничена в размерах значением параметра *maintenance\_work\_mem*; память под нее выделяется по мере необходимости. (*с.* [605](/ru/27-sp-gist/#internal-ru-page-606)) (*с.* [427](/ru/20-index-scans/#internal-ru-page-428)) (*по умолчанию:* `64MB`)

> [!NOTE]
> По сравнению с обычным массивом, который использовался до PostgreSQL 17, префиксное дерево хранит идентификаторы на порядок компактнее, а его общий размер может превышать 1 Гбайт — максимальный размер фрагмента, выделяемого в памяти за один раз.

<span id="internal-ru-page-133" class="pdf-anchor"></span><span id="internal-ru-section-star-95" class="pdf-anchor"></span>

### Очистка индексов {#internal-ru-subsection-star-96}

Дальше возможно одно из двух: либо сканирование доходит до конца таблицы, либо заканчивается память, выделенная под идентификаторы. В любом из этих случаев начинается *этап очистки индексов*[^internal-ru-133-1]. Чтобы очистить индексные страницы от записей, ведущих на запомненные версии строк, *каждый* из индексов, созданных для таблицы, сканируется *полностью*.

> [!NOTE]
> Индекс позволяет быстро найти версию строки, если известен ключ индексирования, но нет способа быстро найти индексную строку по идентификатору табличной версии. Работа в этом направлении для B-деревьев пока не привела к успеху[^internal-ru-133-2].

<span id="internal-ru-dummy-54" class="pdf-anchor"></span>

Для каждого индекса, размер которого превышает значение параметра *min\_parallel\_index\_scan\_size*, может запускаться отдельный рабочий процесс; в этом случае очистка нескольких индексов будет происходить в параллельном режиме. Желаемое количество процессов очистки можно указать в команде `VACUUM (parallel N)`. В реальности их будет запущено не больше, чем имеется свободных фоновых процессов [^internal-ru-133-3] и подходящих индексов (поскольку один индекс не может обрабатываться несколькими процессами одновременно). (v. 13) (*по умолчанию:* `512kB`)

На этапе очистки индексов выполняется работа по поддержанию карты свободного пространства в актуальном состоянии и вычислению статистики работы очистки. Но этот этап будет пропущен, если строки только добавляются (но не удаляются и не изменяются), поскольку в таком случае в таблице не будет ни одной мертвой версии. Тогда очистка индексов выполнится принудительно один раз в конце (это называется *«уборкой» индексов* [^internal-ru-133-4]).

Итак, после этого этапа в индексах уже нет ссылок на ненужные версии строк, но сами версии еще присутствуют в таблице. Это нормальная ситуация: при индексном доступе на них уже невозможно попасть, а при последовательном сканировании таблицы мертвые версии будут отметены проверкой видимости.

<span id="internal-ru-page-134" class="pdf-anchor"></span><span id="internal-ru-section-star-97" class="pdf-anchor"></span>

### Очистка таблицы {#internal-ru-subsection-star-98}

Затем начинается *этап очистки таблицы*[^internal-ru-134-1]. Таблица снова сканируется: это необходимо, чтобы вычистить из страниц запомненные ранее версии строк и освободить указатели. Теперь, когда ссылок из индексов уже нет, это можно сделать безопасно.

<span id="internal-ru-dummy-55" class="pdf-anchor"></span>

Освобожденное при очистке место записывается в карту свободного пространства, а страницы, на которых остались только актуальные версии, видимые во всех снимках, отмечаются в карте видимости.

Если на этапе сканирования таблица не была прочитана полностью, то хранилище `tid` очищается, и все повторяется с того момента, на котором сканирование таблицы остановилось в прошлый раз.

<span id="internal-ru-section-star-99" class="pdf-anchor"></span>

### Усечение таблицы {#internal-ru-subsection-star-100}

<span id="internal-ru-dummy-56" class="pdf-anchor"></span>

После очистки в табличных страницах появляется свободное место; иногда может посчастливиться освободить страницу целиком. Если в конце файла образовалось некоторое количество пустых страниц, очистка может «откусить» хвостовую часть файла и вернуть место операционной системе. Это происходит на заключительном *этапе усечения таблицы*[^internal-ru-134-2].

Усечение требует кратковременной исключительной блокировки таблицы. Попытки получить блокировку продолжаются не дольше пяти секунд, чтобы не останавливать надолго работу других процессов. (*с.* [261](/ru/12-relation-level-locks/#internal-ru-page-262))

Из-за необходимости блокировать работу с таблицей усечение выполняется, только если пустых страниц достаточно много: как минимум \(\frac{1}{16}\) часть файла или, для больших таблиц, 1000 страниц (эти пороговые значения не настраиваются).

Есть несколько способов полностью отключить усечение, которые могут пригодиться, если блокировка все равно вызывает проблемы:

- вызвать команду `VACUUM` c указанием `truncate off`; (v. 12)

- <span id="internal-ru-page-135" class="pdf-anchor"></span>сбросить параметры хранения *vacuum\_truncate* и *toast.vacuum\_truncate*; (v. 12)

- сбросить конфигурационный параметр *vacuum\_truncate*. (v. 18)

## 6.4. Заморозка {#internal-ru-section-6-4}

<span id="internal-ru-dummy-57" class="pdf-anchor"></span><span id="internal-ru-section-star-101" class="pdf-anchor"></span>

### Переполнение счетчика транзакций {#internal-ru-subsection-star-102}

<span id="internal-ru-dummy-58" class="pdf-anchor"></span>

Под номер транзакции в PostgreSQL выделено 32 бита. Четыре миллиарда выглядит довольно большим числом, но при активной работе оно может быть израсходовано достаточно быстро. Например, при нагрузке 1000 транзакций в секунду (не считая виртуальных) это произойдет всего через полтора месяца непрерывной работы.

После исчерпания разрядности счетчик должен будет обнулиться и пойти по следующему кругу (по-английски это называется словом wraparound). Но ведь считать, что транзакция с меньшим номером началась раньше транзакции с бóльшим номером, можно только в случае постоянно возрастающих номеров. Поэтому понятно, что просто так обнулить счетчик и начать нумерацию заново нельзя.

Почему под номер транзакции не выделено 64 бита — ведь это полностью исключило бы проблему? Дело в том, что в заголовке каждой версии строки хранятся два номера транзакций — `xmin` и `xmax`. Заголовок и так достаточно большой (минимум 24 байта с учетом выравнивания), а увеличение разрядности добавило бы еще 8 байт. (*с.* [75](/ru/03-pages-and-tuples/#internal-ru-page-76))

> [!NOTE]
> 64-битные номера транзакций, добавляющие к обычному номеру 32-битную «эпоху», тоже используются[^internal-ru-135-1], но только для служебных целей и никогда не попадают в страницы данных.

Чтобы корректно обрабатывать переполнение счетчика, надо сравнивать не номера транзакций, а их возраст (выраженный в количестве транзакций, начатых с момента появления данной транзакции). Таким образом, вместо понятий «меньше» и «больше» используются «предшествует» («старше») и «следует за» («младше»).

<span id="internal-ru-page-136" class="pdf-anchor"></span>

В коде это естественным образом реализуется 32-битной арифметикой: надо найти разницу 32-битных номеров транзакций и сравнить полученный результат с нулем[^internal-ru-136-1].

Графически можно представить, что номера транзакций закольцованы. Тогда для любой транзакции половина номеров против часовой стрелки будет старше (в прошлом), а половина по часовой стрелке — младше (в будущем).

![Переполнение счетчика транзакций: T1T1T1T1; T2T2; б; п; ру; од; шу; щ; л; ое; ееT3](/images/internal/ru/p136-01.svg)
{#internal-ru-fig-6-1 num="6.1" caption="Переполнение счетчика транзакций" .book-original-figure width=722 height=248}

Однако в такой закольцованной схеме возникает неприятная ситуация. Старая транзакция (T1) находится для новых транзакций в далеком прошлом. Но через некоторое время для очередной транзакции она окажется в той половине круга, которая относится к будущему. Если бы это было так, то привело бы к катастрофическим последствиям — изменения, сделанные транзакцией T1 давным-давно, оказались бы невидимыми для новых транзакций.

<span id="internal-ru-section-star-103" class="pdf-anchor"></span>

### Заморозка версий и правила видимости {#internal-ru-subsection-star-104}

Чтобы не допустить таких «путешествий во времени», процесс очистки (помимо освобождения места в страницах) выполняет еще одну задачу[^internal-ru-136-2]. Он ищет версии строк, находящиеся за горизонтом базы данных (видимые во всех снимках), и специальным образом их помечает — *замораживает*. (*с.* [274](/ru/13-row-level-locks/#internal-ru-page-275))

Замороженная версия строки видна во всех снимках данных без оглядки на номер транзакции `xmin`, поэтому такой номер может быть безопасно использован заново.

<span id="internal-ru-page-137" class="pdf-anchor"></span>

Можно считать, что в замороженных версиях строк номер `xmin` заменяется на условную «минус бесконечность» (показанную на рисунке в виде снежинки) — признак того, что создавшая версию транзакция завершилась так давно, что конкретный номер уже не важен. Хотя на самом деле `xmin` не меняется, а в качестве признака заморозки выставляются одновременно два бита-подсказки — бит фиксации и бит отмены.

![Заморозка версий и правила видимости: ^^^; T1T1; ^^; T2; ^; T3T3T2; T4T1T4T1T4](/images/internal/ru/p137-02.svg)
{#internal-ru-fig-6-2 num="6.2" caption="Заморозка версий и правила видимости" .book-original-figure width=722 height=244}

> [!NOTE]
> Многие источники (включая документацию) упоминают специальный номер транзакции FrozenTransactionId = 2. Это и есть та самая «минус бесконечность», на которую заменялся `xmin` до версии 9.4. Используемые сейчас биты-подсказки позволяют сохранить в версии строки исходный номер транзакции, что удобно для поддержки и отладки. Однако транзакции с номером 2 еще могут встретиться в страницах данных старых систем, даже обновленных до последних версий.

Номер транзакции `xmax` никак не участвует в заморозке. Он фигурирует только в неактуальных версиях строк, а когда такие версии перестанут быть видимы в снимках (то есть номер уйдет за горизонт базы данных), они будут удалены очисткой.

<span id="internal-ru-dummy-59" class="pdf-anchor"></span>

Вручную заморозка вызывается командой `VACUUM FREEZE`. В нашем примере она замораживает единственную версию строки:

```text {label="psql"}
=> VACUUM FREEZE vac;
=> SELECT * FROM heap_page('vac',0);
 ctid  | state  | xmin  | xmax | hhu | hot | t_ctid
-------+--------+-------+------+-----+-----+--------
 (0,1) | unused |       |      |     |     |
 (0,2) | unused |       |      |     |     |
 (0,3) | normal | 853 f | 0 a  |     |     | (0,3)
(3 rows)
```

<span id="internal-ru-page-138" class="pdf-anchor"></span>

Страница теперь отмечена не только в карте видимости, но и в карте заморозки:

```text {label="psql"}
=> SELECT all_visible, all_frozen
FROM pg_visibility_map('vac',0);
 all_visible | all_frozen
-------------+------------
 t           | t
(1 row)
```

## 6.5. Анализ {#internal-ru-section-6-5}

<span id="internal-ru-dummy-60" class="pdf-anchor"></span>

В дополнение к очистке есть еще одна задача, никак с ней формально не связанная: *анализ* [^internal-ru-138-1], или, иными словами, сбор статистической информации. Бо́льшая часть статистики предназначается для планировщика запросов: количество строк (`pg_class.reltuples`) и страниц (`pg_class.relpages`) в отношениях, распределение данных по столбцам таблиц и другие сведения. Такие значения, как количество страниц, отмеченных в карте видимости (`pg_class.relallvisible`) и в карте заморозки (`pg_class.relallfrozen`), не используются планировщиком, но нужны для управления самим анализом и очисткой. (*с.* [346](/ru/17-statistics/#internal-ru-page-347))

Вручную анализ можно выполнять сам по себе командой `ANALYZE`[^internal-ru-138-2], но можно и совмещать с очисткой: `VACUUM ANALYZE`. Правда, при этом очистка и анализ выполняются последовательно — никакой экономии не происходит.

> [!NOTE]
> Исторически вариант `VACUUM ANALYZE` возник первым, в версии 6.1, а отдельная команда `ANALYZE` появилась только в версии 7.2. В более ранних версиях статистика обновлялась скриптом на TCL.

[^internal-ru-126-1]: [postgrespro.ru/docs/postgresql/18/routine-vacuuming](<https://postgrespro.ru/docs/postgresql/18/routine-vacuuming>).
[^internal-ru-126-2]: [postgrespro.ru/docs/postgresql/18/sql-vacuum](<https://postgrespro.ru/docs/postgresql/18/sql-vacuum>); [backend/commands/vacuum.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/commands/vacuum.c;hb=REL_18_STABLE>).
[^internal-ru-132-1]: [backend/access/heap/vacuumlazy.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/heap/vacuumlazy.c;hb=REL_18_STABLE>), функция heap\_vacuum\_rel.
[^internal-ru-132-2]: [backend/access/heap/vacuumlazy.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/heap/vacuumlazy.c;hb=REL_18_STABLE>), функция lazy\_scan\_heap.
[^internal-ru-132-3]: [backend/access/common/tidstore.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/common/tidstore.c;hb=REL_18_STABLE>).
[^internal-ru-133-1]: [backend/access/heap/vacuumlazy.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/heap/vacuumlazy.c;hb=REL_18_STABLE>), функция lazy\_vacuum\_all\_indexes.
[^internal-ru-133-2]: [commitfest.postgresql.org/21/1802](<https://commitfest.postgresql.org/21/1802>).
[^internal-ru-133-3]: [postgrespro.ru/docs/postgresql/18/bgworker](<https://postgrespro.ru/docs/postgresql/18/bgworker>).
[^internal-ru-133-4]: [backend/access/heap/vacuumlazy.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/heap/vacuumlazy.c;hb=REL_18_STABLE>), функция lazy\_cleanup\_all\_indexes; [backend/access/nbtree/nbtree.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/nbtree/nbtree.c;hb=REL_18_STABLE>), функция btvacuumcleanup.
[^internal-ru-134-1]: [backend/access/heap/vacuumlazy.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/heap/vacuumlazy.c;hb=REL_18_STABLE>), функция lazy\_vacuum\_heap.
[^internal-ru-134-2]: [backend/access/heap/vacuumlazy.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/heap/vacuumlazy.c;hb=REL_18_STABLE>), функция lazy\_truncate\_heap.
[^internal-ru-135-1]: [include/access/transam.h](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/access/transam.h;hb=REL_18_STABLE>), тип FullTransactionId.
[^internal-ru-136-1]: [backend/access/transam/transam.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/access/transam/transam.c;hb=REL_18_STABLE>), функция TransactionIdPrecedes.
[^internal-ru-136-2]: [postgrespro.ru/docs/postgresql/18/routine-vacuuming#VACUUM-FOR-WRAPAROUND](<https://postgrespro.ru/docs/postgresql/18/routine-vacuuming#VACUUM-FOR-WRAPAROUND>).
[^internal-ru-138-1]: [postgrespro.ru/docs/postgresql/18/routine-vacuuming#VACUUM-FOR-STATISTICS](<https://postgrespro.ru/docs/postgresql/18/routine-vacuuming#VACUUM-FOR-STATISTICS>).
[^internal-ru-138-2]: [backend/commands/analyze.c](<https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/commands/analyze.c;hb=REL_18_STABLE>).
