# Занятие 03: Архитектура / Очистка

> Исходная страница в нативном Markdown: Архитектура / Очистка

---

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

---

<!-- Generated by scripts/generate_skeleton.py. -->

> [!NOTE]
> Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета.
> **HTML source:** [dev2_03_arch_vacuum.html](https://edu.postgrespro.ru/16/dev2-16/dev2_03_arch_vacuum.html)
> **Archive:** [DEV2-handouts-16.zip](https://edu.postgrespro.ru/16/DEV2-handouts-16.zip)
> **Страниц PDF:** 27

## Page 1: Архитектура / Очистка {#source-page-001}

### Авторские права

© Postgres Professional, 2017–2024 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
### Использование материалов курса

Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
### Обратная связь

Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
### Отказ от ответственности

Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.

## Page 2: Темы {#source-page-002}

- Очистка версий строк и анализ таблиц
- Заморозка версий строк

## Page 3: Очистка и анализ {#source-page-003}

Как устроен процесс очистки
- Анализ таблиц
- Полная очистка

## Page 4: Очистка {#source-page-004}

**Выполняется командой VACUUM**
  - очищает ненужные версии строк в табличных страницах
  - (пропуская страницы, уже отмеченные в карте видимости)
  - очищает индексные записи, ссылающиеся на очищенные версии строк
  - обновляет карты видимости и свободного пространства
- Обычно работает в автоматическом режиме
  - частота обработки зависит от интенсивности изменений в таблице
  - процессы autovacuum launcher и autovacuum worker

### Комментарии преподавателя

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

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

Обычно очистка запускается не вручную, а работает в автоматическом режиме, учитывая фактическое количество изменений в таблице. Чем активнее изменяются, добавляются и удаляются строки, тем чаще автоочистка обрабатывает таблицу и ее индексы.

При включенной автоочистке в системе всегда присутствует процесс autovacuum launcher, который занимается планированием работы процессов autovacuum worker, непосредственно выполняющих очистку. Несколько экземпляров таких рабочих процессов могут действовать параллельно.

Важно, чтобы процесс автоочистки был правильно настроен. Как это делается, рассматривается в курсе DBA2 «Администрирование PostgreSQL. Настройка и мониторинг». Если автоочистка не будет срабатывать вовремя, таблицы будут разрастаться в размерах. Также важно, чтобы в системе с OLTP-нагрузкой не было длинных транзакций, подолгу удерживающих горизонт базы данных и мешающих очистке.

<https://postgrespro.ru/docs/postgresql/16/sql-vacuum>

<https://postgrespro.ru/docs/postgresql/16/routine-vacuuming>

## Page 5: Пример {#source-page-005}

![Source slide page 5: Пример](images/page-005.png){#source-figure-005 caption="Source slide page 5: Пример"}


### Комментарии преподавателя

Рассмотрим пример.

На рисунке приведена ситуация до очистки. В табличной странице три версии одной строки. Две из них неактуальны, не видны ни в одном снимке и могут быть удалены.

В индексе есть три ссылки: на каждую из версий строки.

## Page 6: Фаза очистки индексов {#source-page-006}

![Source slide page 6: Фаза очистки индексов](images/page-006.png){#source-figure-006 caption="Source slide page 6: Фаза очистки индексов"}


### Комментарии преподавателя

Очистка обрабатывает данные постранично. Таблица при этом может использоваться обычным образом и для чтения, и для изменения (однако одновременное выполнение для таблицы таких команд, как CREATE INDEX, ALTER TABLE и др. будет невозможно — подробнее см. тему «Блокировки»).

Сначала таблица сканируется в поисках версий строк, которые можно очистить. Для этого (с помощью карты видимости) читаются только те страницы, в которых есть неактуальные (мертвые) версии строк. Идентификаторы очищаемых строк запоминаются в оперативной памяти.

Затем начинается фаза очистки индексов. Каждый из индексов, созданных на таблице, полностью сканируется в поисках записей, которые ссылаются на очищаемые версии строк (это еще один повод не создавать лишних индексов!). Найденные индексные записи очищаются. Эту фазу могут выполнять несколько фоновых процессов параллельно, по одному процессу на каждый индекс.

После этой фазы в индексе уже нет ссылок на устаревшие версии строк, но сами версии строк еще присутствуют в таблице.

## Page 7: Фаза очистки таблицы {#source-page-007}

![Source slide page 7: Фаза очистки таблицы](images/page-007.png){#source-figure-007 caption="Source slide page 7: Фаза очистки таблицы"}


### Комментарии преподавателя

Затем выполняется фаза очистки таблицы. На этой фазе таблица снова сканируется, и из нее вычищаются версии строк с запомненными ранее идентификаторами. Несмотря на то, что индексы на предыдущей фазе могут очищаться параллельно, при очистке самой таблицы параллелизма нет: она очищается одним (ведущим) процессом.

После этой фазы устаревших версий строк нет ни в индексах, ни в таблице.

Заметим, что фазы очистки индексов и таблицы могут поочередно выполняться несколько раз, если не хватает памяти для того чтобы сразу запомнить все идентификаторы очищаемых строк. Такой ситуации стараются избегать правильной настройкой автоочистки.

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

## Page 8: Очистка {#source-page-008}

```sql
=> CREATE DATABASE arch_vacuum;
```
```sql
CREATE DATABASE
```
```sql
=> \c arch_vacuum
```
```text
You are now connected to database "arch_vacuum" as user "student".
```
Создадим таблицу и запретим для нее автоматическую очистку (чтобы управлять моментом срабатывания).
```sql
=> CREATE TABLE t(
    id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
) WITH (autovacuum_enabled = off);
```
```sql
CREATE TABLE
```
Вставим в таблицу некоторое количество строк и узнаем размер ее основного слоя:
```sql
=> INSERT INTO t
SELECT n
FROM generate_series(1,1000) as s(n);
```
```sql
INSERT 0 1000
```
```sql
=> SELECT pg_size_pretty(pg_relation_size('t', 'main'));
```
```text
 pg_size_pretty
----------------
 40 kB
(1 row)
```
Теперь, чтобы еще раз напомнить про понятие горизонта, откроем в другом сеансе транзакцию с активным снимком данных.
```sql
=> \c arch_vacuum
```
```text
You are now connected to database "arch_vacuum" as user "student".
```
```text
=> BEGIN ISOLATION LEVEL REPEATABLE READ;
```
```text
BEGIN
```
```sql
=> SELECT 1; -- любой запрос строит снимок
```
```text
 ?column?
----------
        1
(1 row)
```
Горизонт базы данных определяется этим снимком:
```sql
=> SELECT backend_xmin
FROM pg_stat_activity
WHERE pid = pg_backend_pid();
```
```text
 backend_xmin
--------------
          818
(1 row)
```
Обновим все строки таблицы.
```sql
=> UPDATE t SET id = id + 1000;
```
```sql
UPDATE 1000
```
Как изменится размер таблицы?
```sql
=> SELECT pg_size_pretty(pg_relation_size('t', 'main'));
```
```text
 pg_size_pretty
----------------
 72 kB
(1 row)
```

## Page 9: Page 9 {#source-page-009}

![Source slide page 9: Page 9](images/page-009.png){#source-figure-009 caption="Source slide page 9: Page 9"}

Размер основного слоя таблицы увеличился примерно вдвое.
Выполним теперь очистку и попросим ее рассказать о том, что происходит:
```sql
=> VACUUM VERBOSE t;
```
```sql
INFO:  vacuuming "arch_vacuum.public.t"
INFO:  finished vacuuming "arch_vacuum.public.t": index scans: 0
pages: 0 removed, 9 remain, 9 scanned (100.00% of total)
tuples: 0 removed, 2000 remain, 1000 are dead but not yet removable
removable cutoff: 818, which was 1 XIDs old when operation ended
new relfrozenxid: 817, which is 1 XIDs ahead of previous value
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
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: 33.967 MB/s
buffer usage: 26 hits, 0 misses, 3 dirtied
WAL usage: 1 records, 0 full page images, 237 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM
```
Обратите внимание:
tuples: 0 removed, 2000 remain — ни одна версия строк не может быть очищена, 1000 are dead but not yet removable — хотя две трети из них — неактуальные, removable cutoff: 818 — текущий горизонт, индекс не очищался.
Завершим параллельную транзакцию...
```text
=> COMMIT;
```
```text
COMMIT
```
...и снова вызовем очистку:
```sql
=> VACUUM VERBOSE t;
```
```sql
INFO:  vacuuming "arch_vacuum.public.t"
INFO:  finished vacuuming "arch_vacuum.public.t": index scans: 1
pages: 0 removed, 9 remain, 9 scanned (100.00% of total)
tuples: 1000 removed, 1000 remain, 0 are dead but not yet removable
removable cutoff: 819, which was 0 XIDs old when operation ended
new relfrozenxid: 818, which is 1 XIDs ahead of previous value
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
index scan needed: 5 pages from table (55.56% of total) had 1000 dead item identifiers
removed
index "t_pkey": pages: 8 in total, 2 newly deleted, 2 currently deleted, 0 reusable
avg read rate: 0.000 MB/s, avg write rate: 13.587 MB/s
buffer usage: 55 hits, 0 misses, 1 dirtied
WAL usage: 29 records, 1 full page images, 16218 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
VACUUM
```
Теперь:
removable cutoff: 819 — горизонт подвинулся, tuples: 1000 removed — очищены все неактуальные версии строк, index scan needed ... 1000 dead item identifiers removed — очищены указатели на них из индекса.
Посмотрим на размер:
```sql
=> SELECT pg_size_pretty(pg_relation_size('t', 'main'));
```
```text
 pg_size_pretty
----------------
 72 kB
(1 row)
```
Он не изменился, поскольку все свободное место находится внутри страниц — оно не возвращается операционной системе, но будет использовано для размещения новых версий строк.

## Page 10: Анализ {#source-page-010}

**Выполняется командой ANALYZE**
  - собирает статистику для планировщика
  - можно вместе с очисткой командой VACUUM ANALYZE
- Обычно работает в автоматическом режиме
  - вместе с автоматической очисткой

### Комментарии преподавателя

Еще одна задача, которую обычно совмещают с очисткой, — анализ, то есть сбор статистической информации для планировщика запросов. Анализируется число строк в таблице, распределение данных по столбцам и т. п. Более подробно сбор статистики рассматривается в курсе QPT «Оптимизация запросов».

Вручную анализ выполняется командой ANALYZE (только анализ) или VACUUM ANALYZE (и очистка, и анализ).

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

<https://postgrespro.ru/docs/postgresql/16/sql-analyze>

## Page 11: Полная очистка {#source-page-011}

**Перестраивает файлы и возвращает место файловой системе**
- Команда VACUUM FULL
  - полностью перестраивает таблицу и все ее индексы
  - монопольно блокирует таблицу и ее индексы на все время работы
- Команда REINDEX
  - полностью перестраивает только индексы
  - монопольно блокирует индекс и запрещает запись в таблицу
  - вариант REINDEX CONCURRENTLY не мешает чтению и записи,
  - но выполняется дольше

### Комментарии преподавателя

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

Если размер файлов превышает некие разумные пределы, можно выполнить команду VACUUM FULL для полной очистки. При этом таблица и все ее индексы перестраиваются полностью с нуля, а данные упаковываются максимально компактно.

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

<https://postgrespro.ru/docs/postgresql/16/sql-vacuum>

Команда REINDEX перестраивает индексы, не трогая при этом таблицу. Фактически, VACUUM FULL использует эту команду для того, чтобы перестроить индексы. Вариант REINDEX CONCURRENTLY работает дольше, но не блокирует индекс и не мешает чтению и обновлению данных.

<https://postgrespro.ru/docs/postgresql/16/sql-reindex>

## Page 12: Пример {#source-page-012}

![Source slide page 12: Пример](images/page-012.png){#source-figure-012 caption="Source slide page 12: Пример"}


### Комментарии преподавателя

На рисунке приведена та же самая ситуация до очистки, которую мы уже рассматривали.

В табличной странице три версии одной строки. Две из них неактуальны, не видны ни в одном снимке и могут быть удалены.

В индексе есть три ссылки на каждую из версий строки.

## Page 13: После полной очистки {#source-page-013}

![Source slide page 13: После полной очистки](images/page-013.png){#source-figure-013 caption="Source slide page 13: После полной очистки"}


### Комментарии преподавателя

После выполнения полной очистки файлы данных индекса и таблицы перестроены заново, нумерация версий строк изменилась. В страницах отсутствуют «дыры», данные упакованы максимально плотно.

## Page 14: Полная очистка {#source-page-014}

Вызываем полную очистку таблицы:
```sql
=> VACUUM FULL VERBOSE t;
```
```sql
INFO:  vacuuming "public.t"
INFO:  "public.t": found 0 removable, 1000 nonremovable row versions in 9 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM
```
Таблица и индекс теперь полностью перестроены. Как изменился размер?
```sql
=> SELECT pg_size_pretty(pg_relation_size('t', 'main'));
```
```text
 pg_size_pretty
----------------
 40 kB
(1 row)
```
Размер вернулся к первоначальному.

## Page 15: Заморозка {#source-page-015}

Проблема переполнения счетчика транзакций
- Заморозка версий строк

## Page 16: Переполнение счетчика {#source-page-016}

![Source slide page 16: Переполнение счетчика](images/page-016.png){#source-figure-016 caption="Source slide page 16: Переполнение счетчика"}

пространство номеров транзакций (32 бита) закольцовано половина номеров — прошлое, половина — будущее

### Комментарии преподавателя

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

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

Почему под номер транзакции не выделено 64 бита — ведь это полностью исключило бы проблему? Дело в том, что (как рассматривалось в теме «Многоверсионность») в заголовке каждой версии строки хранятся два номера транзакций — xmin и xmax. Заголовок и так достаточно большой, а увеличение разрядности привело бы к его увеличению еще на 8 байт.

## Page 17: Переполнение счетчика {#source-page-017}

![Source slide page 17: Переполнение счетчика](images/page-017.png){#source-figure-017 caption="Source slide page 17: Переполнение счетчика"}


### Комментарии преподавателя

Поэтому вместо линейной схемы все номера транзакций закольцованы. Для любой транзакции половина номеров «против часовой стрелки» считается принадлежащей прошлому, а половина «по часовой стрелке» — будущему.

Возрастом транзакции называется число транзакций, прошедших с момента ее появления в системе (независимо от того, переходил ли счетчик через ноль или нет).

## Page 18: Заморозка {#source-page-018}

![Source slide page 18: Заморозка](images/page-018.png){#source-figure-018 caption="Source slide page 18: Заморозка"}

процесс очистки выполняет заморозку старых версий строк после заморозки номер транзакции xmin может быть использован заново

### Комментарии преподавателя

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

При этом проблемы вызывают только номера транзакций в поле xmin, поскольку неактуальные версии с ненулевым xmax достаточно быстро удаляются при очистке (как только перестают быть видимыми в снимках данных).

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

## Page 19: Заморозка {#source-page-019}

![Source slide page 19: Заморозка](images/page-019.png){#source-figure-019 caption="Source slide page 19: Заморозка"}

замороженные версии строк считаются «бесконечно старыми»

### Комментарии преподавателя

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

<https://postgrespro.ru/docs/postgresql/16/routine-vacuuming#VACUUM-FOR> -WRAPAROUND

## Page 20: Пример {#source-page-020}

![Source slide page 20: Пример](images/page-020.png){#source-figure-020 caption="Source slide page 20: Пример"}


### Комментарии преподавателя

На рисунке приведена ситуация до заморозки. В табличной странице после очистки осталась только одна актуальная версия строки.

## Page 21: После заморозки {#source-page-021}

![Source slide page 21: После заморозки](images/page-021.png){#source-figure-021 caption="Source slide page 21: После заморозки"}

замораживается только номер xmin версии строк с xmax являются неактуальными и будут очищены

### Комментарии преподавателя

Для того чтобы пометить номер транзакции xmin как замороженный, выставляется специальная комбинация информационных битов в заголовке версии строки.

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

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

## Page 22: Заморозка вручную {#source-page-022}

VACUUM FREEZE
  - принудительная заморозка версий строк с xmin любого возраста
  - тот же эффект и при VACUUM FULL, CLUSTER
- COPY ... WITH FREEZE
  - принудительная заморозка во время загрузки
  - таблица должна быть создана или опустошена в той же транзакции
  - могут нарушаться правила изоляции транзакции

### Комментарии преподавателя

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

Заморозку можно вызвать вручную командой VACUUM FREEZE — при этом будут заморожены все версии строк, без оглядки на возраст транзакций. При перестройке таблицы командами VACUUM FULL или CLUSTER все строки также замораживаются.

<https://postgrespro.ru/docs/postgresql/16/sql-vacuum>

Данные можно заморозить и при начальной загрузке с помощью команды COPY, указав параметр FREEZE. Для этого таблица должна быть создана (или опустошена командой TRUNCATE) в той же транзакции, что и COPY. Поскольку для замороженных строк действуют отдельные правила видимости, такие строки будут видны в снимках данных других транзакций в нарушение обычных правил изоляции (это касается транзакций с уровнем Repeatable Read или Serializable), хотя обычно это не представляет проблемы.

<https://postgrespro.ru/docs/postgresql/16/sql-copy>

## Page 23: Итоги {#source-page-023}

**Многоверсионность требует очистки старых версий строк**
  - обычная очистка — как правило в автоматическом режиме
  - полная очистка — в экстренных случаях
- Переполнение счетчика транзакций требует заморозки
- Долгие транзакции и одномоментное обновление большого объема данных приводят к проблемам
  - таблицы и индексы увеличиваются в размерах
  - многократно сканируются индексы при очистке

## Page 24: Практика {#source-page-024}

1. В демонстрации было показано, что при обновлении всех строк таблицы одной командой UPDATE объем данных на диске увеличивается вдвое. Проделайте сходный эксперимент, но обновляйте таблицу небольшими пакетами строк, чередуя обновление с запуском очистки. Как сильно увеличилась таблица на этот раз? 2. Сравните время, за которое удаляются все строки таблицы при использовании команд DELETE и TRUNCATE.

### Комментарии преподавателя

1. С размером пакета можно экспериментировать; возьмите, например, 1 % от числа строк в таблице.

Для пакетной обработки нужно решить, как именно выбирать N строк из имеющихся в таблице.

Учтите, что обработка какого-то пакета может (в принципе) завершиться ошибкой. Важно, чтобы это не повлияло на возможность продолжить обработку и довести ее до конца. Например, если выбирать строки по диапазонам значений идентификатора, то как определить, какие пакеты успешно обработаны, а какие — нет?

```text
2. Воспользуйтесь командой psql \timing.
```

## Page 25: 1. Пакетное обновление {#source-page-025}

```sql
=> CREATE DATABASE arch_vacuum;
```
```sql
CREATE DATABASE
```
```sql
=> \c arch_vacuum
```
```text
You are now connected to database "arch_vacuum" as user "student".
```
Создадим таблицу, сходную с той, что использовалась в демонстрации, и наполним ее данными:
```sql
=> CREATE TABLE t(
    id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    n float,
    processed boolean DEFAULT false
);
```
```sql
CREATE TABLE
```
```sql
=> INSERT INTO t(n) SELECT random() FROM generate_series(1,100_000);
```
```sql
INSERT 0 100000
```
Для упрощения задачи мы отказались от столбца с внешним хранением (TOAST), а дополнительный столбец processed пригодится нам для пакетного обновления.
Размер таблицы:
```sql
=> SELECT pg_size_pretty(pg_relation_size('t'));
```
```text
 pg_size_pretty
----------------
 5096 kB
(1 row)
```
Еще раз проверим показанное в демонстрации. Обновляем все строки:
```sql
=> UPDATE t SET n = n + 1;
```
```sql
UPDATE 100000
```
Размер таблицы после обновления:
```sql
=> SELECT pg_size_pretty(pg_relation_size('t'));
```
```text
 pg_size_pretty
----------------
 10192 kB
(1 row)
```
Таблица увеличилась в два раза.
Теперь нам надо очистить таблицу, поэтому заодно засечем время выполнения команды TRUNCATE:
```text
=> \timing on
```
```text
Timing is on.
```
```text
=> TRUNCATE t;
```
```text
TRUNCATE TABLE
Time: 13,354 ms
```
```text
=> \timing off
```
```text
Timing is off.
```
Заново вставляем те же строки:
```sql
=> INSERT INTO t(n) SELECT random() FROM generate_series(1,100_000);
```
```sql
INSERT 0 100000
```
```sql
=> SELECT pg_size_pretty(pg_relation_size('t'));
```

## Page 26: Page 26 {#source-page-026}

```text
 pg_size_pretty
----------------
 5096 kB
(1 row)
```
Для того чтобы контролировать обработанные строки, будем использовать столбец processed. Для удобства создадим функцию, выполняющую обновление пакета строк, причем размер пакета задается параметром:
```sql
=> CREATE FUNCTION do_update(batch_size integer) RETURNS void
LANGUAGE sql VOLATILE
BEGIN ATOMIC
    WITH batch AS (
        -- отбираем необработанные строки для пакета
        -- и блокируем их, пропуская уже заблокированные
        SELECT * FROM t
        WHERE NOT processed
        LIMIT batch_size
        FOR UPDATE SKIP LOCKED
    )
    UPDATE t
    SET n = n + 1,
        processed = true
    WHERE id IN (SELECT id FROM batch);
END;
```
```sql
CREATE FUNCTION
```
Предложение FOR UPDATE SKIP LOCKED будет подробнее рассмотрено в теме «Блокировки». Такой способ даст необходимое количество необработанных строк (из числа имеющихся в таблице), причем выполнение не будет задержано другими обновлениями, которые могут выполняться в это же время. Удобно также, что этот способ не требует вычисления каких-либо диапазонов. Обновление просто надо продолжать до тех пор, пока в таблице не останется необработанных строк.
Поскольку выполнение команды VACUUM не допускается в блоке транзакции, воспользуется средствами bash, чтобы выполнить нужные команды:
```sh
student$ for i in {1..100}
do
  psql -d arch_vacuum -c 'SELECT do_update(1000);'
  psql -d arch_vacuum -c 'VACUUM;'
done >/dev/null
```
Проверим, что все строки обработаны:
```sql
=> SELECT count(*) FROM t WHERE NOT processed;
```
```text
 count
-------
     0
(1 row)
```
Размер таблицы после пакетного обновления:
```sql
=> SELECT pg_size_pretty(pg_relation_size('t'));
```
```text
 pg_size_pretty
----------------
 5216 kB
(1 row)
```
Таблица увеличилась чуть больше, чем на 2%.
### 2. Удаление строк
```text
=> \timing on
```
```text
Timing is on.
```
```sql
=> DELETE FROM t;
```
```sql
DELETE 100000
Time: 112,229 ms
```
```text
=> \timing off
```
```text
Timing is off.
```
Удаление всех строк командой DELETE выполняется значительно дольше, чем TRUNCATE. Кроме того, после DELETE в таблице остаются удаленные версии строк, которые должны быть очищены.

## Page 27: Page 27 {#source-page-027}

По принципу работы команда TRUNCATE напоминает VACUUM FULL: она заменяет файл данных новым (пустым) файлом, и для этого полностью блокирует работу с таблицей для других транзакций.
