# Занятие 09: Журналирование / Журнал предзаписи

> Исходная страница в нативном Markdown: Журналирование / Журнал предзаписи

---

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

---

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

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

## Page 1: Журналирование / Журнал предзаписи {#source-page-001}

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

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

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

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

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

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

- Журнал предзаписи (WAL)
- Логическое и физическое устройство журнала
- Процесс упреждающей записи и восстановление

## Page 3: Журнал предзаписи {#source-page-003}

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

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

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

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

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

<https://postgrespro.ru/docs/postgresql/16/wal-intro>

## Page 4: Что защищено журналом {#source-page-004}

**Изменение любых страниц в буферном кеше**
  - в том числе страницы таблиц и индексов
  - кроме нежурналируемых и временных таблиц
- Фиксация и отмена транзакций — буферы CLOG
- Файловые операции
  - создание и удаление файлов
  - добавление и удаление страниц файлов
  - создание и удаление каталогов

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

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

В частности, в журнал записываются следующие действия:

● изменение страниц в буферном кеше (как правило, это страницы таблиц и индексов) — так как измененная страница попадает на диск не сразу;

● фиксация и отмена транзакций — точно так же, изменение статуса происходит в буфере CLOG и попадает на диск не сразу;

● файловые операции (создание и удаление файлов и каталогов, например, создание файлов при создании таблицы) — так как эти операции должны происходить синхронно с изменением данных.

При этом в журнал не записываются:

● операции с нежурналируемыми таблицами — их название говорит само за себя;

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

## Page 5: Логическое устройство {#source-page-005}

![Source slide page 5: Логическое устройство](images/page-005.png){#source-figure-005 caption="Source slide page 5: Логическое устройство"}

последовательность записей «номер» записи — 64-битный LSN (log sequence number) специальный тип pg\_lsn

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

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

● номер транзакции, к которой относится запись;

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

● контрольная сумма (CRC).

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

<https://postgrespro.ru/docs/postgresql/16/custom-rmgr>

Для того чтобы сослаться на определенную запись, используется тип данных pg\_lsn (LSN = log sequence number) — 64-битное число, представляющее собой байтовое смещение до записи относительно начала журнала.

Начало каждой записи выравнивается по границе машинного слова, это одна из причин двоичной несовместимости WAL на разных платформах.

<https://postgrespro.ru/docs/postgresql/16/datatype-pg-lsn>

## Page 6: Логическое устройство журнала {#source-page-006}

Список менеджеров ресурсов покажет утилита pg\_waldump:
```sh
student$ /usr/lib/postgresql/16/bin/pg_waldump -r list
```
```text
XLOG
Transaction
Storage
CLOG
Database
Tablespace
MultiXact
RelMap
Standby
Heap2
Heap
Btree
Hash
Gin
Gist
Sequence
SPGist
BRIN
CommitTs
ReplicationOrigin
Generic
LogicalMessage
```
LSN выводится как два 32-битных числа в шестнадцатеричной системе через косую черту.
Текущая позиция в журнале:
```sql
=> SELECT pg_current_wal_insert_lsn();
```
```text
 pg_current_wal_insert_lsn
---------------------------
 0/1BB5840
(1 row)
```
При помощи утилиты pg\_waldump и появившегося в 15-й версии PostgreSQL расширения pg\_walinspect мы можем исследовать содержимое журнала предзаписи.
Создадим базу данных и установим расширение (чтобы воспользоваться расширением, роль должна быть включена в pg\_read\_server\_files или быть суперпользовательской):
```sql
=> CREATE DATABASE wal_log;
```
```sql
CREATE DATABASE
```
```sql
=> \c wal_log
```
```text
You are now connected to database "wal_log" as user "student".
```
```sql
=> CREATE EXTENSION pg_walinspect;
```
```sql
CREATE EXTENSION
```
Нам понадобится небольшая таблица:
```sql
=> CREATE TABLE t(note text) WITH (autovacuum_enabled = off);
```
```sql
CREATE TABLE
```
Получим текущую позицию в журнале, после чего вставим в таблицу строку:
```sql
=> SELECT pg_current_wal_insert_lsn();
```
```text
 pg_current_wal_insert_lsn
---------------------------
 0/1FEE9A0
(1 row)
```
```sql
=> INSERT INTO t VALUES ('FOO');
```
```sql
INSERT 0 1
```
Теперь позиция журнала такая:

## Page 7: Page 7 {#source-page-007}

```sql
=> SELECT pg_current_wal_insert_lsn();
```
```text
 pg_current_wal_insert_lsn
---------------------------
 0/1FEEA08
(1 row)
```
Посмотрим, какие записи появились в журнале:
```sql
=> SELECT resource_manager, record_length, xid, start_lsn, prev_lsn, record_type, description
FROM pg_get_wal_records_info('0/1FEE9A0','0/1FEEA08')
WHERE record_type in('INSERT+INIT','COMMIT')
ORDER BY start_lsn \gx
```
```sql
-[ RECORD 1 ]----+------------------------------
resource_manager | Heap
record_length    | 59
xid              | 746
start_lsn        | 0/1FEE9A0
prev_lsn         | 0/1FEE960
record_type      | INSERT+INIT
description      | off: 1, flags: 0x00
-[ RECORD 2 ]----+------------------------------
resource_manager | Transaction
record_length    | 34
xid              | 746
start_lsn        | 0/1FEE9E0
prev_lsn         | 0/1FEE9A0
record_type      | COMMIT
description      | 2026-06-15 12:54:28.750726+03
```
Операция INSERT+INIT инициализирует страницу и добавляет в нее версию строки, операция COMMIT фиксирует транзакцию.
Для записи INSERT+INIT в журнале сохраняется блок данных, содержащий значения полей вставляемой версии строки. Расширение позволяет увидеть эти данные (в столбце block\_data можно различить коды символов добавленной строки):
```sql
=> SELECT * FROM pg_get_wal_block_info('0/1FEE9A0','0/1FEE9E0') \gx
```
```sql
-[ RECORD 1 ]-----+-----------------------
start_lsn         | 0/1FEE9A0
end_lsn           | 0/1FEE9E0
prev_lsn          | 0/1FEE960
block_id          | 0
reltablespace     | 1663
reldatabase       | 16390
relfilenode       | 16398
relforknumber     | 0
relblocknumber    | 0
xid               | 746
resource_manager  | Heap
record_type       | INSERT+INIT
record_length     | 59
main_data_length  | 3
block_data_length | 10
block_fpi_length  | 0
block_fpi_info    |
description       | off: 1, flags: 0x00
block_data        | \x01000208180009464f4f
block_fpi_data    |
```

## Page 8: Физическое устройство {#source-page-008}

![Source slide page 8: Физическое устройство](images/page-008.png){#source-figure-008 caption="Source slide page 8: Физическое устройство"}

В памяти
  - На диске
```text
$PGDATA/pg_wal/
  000000010000000100000001
  000000010000000100000002
  000000010000000100000003
  000000010000000100000004
  000000010000000100000005
  000000010000000100000006
```

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

На диске журнал хранится в виде файлов (сегментов) в каталоге PGDATA/pg\_wal. Каждый файл по умолчанию занимает 16 Мбайт. Размер сегмента может быть задан при инициализации кластера.

Журнальные записи попадают в текущий файл; когда он заполняется — начинает использоваться следующий.

В оперативной памяти для журнала выделены специальные буферы. Размер кеша задается параметром wal\_buffers (значение по умолчанию подразумевает автоматическую настройку: выделяется 1/32 часть буферного кеша, но не меньше чем 64 Кбайт и не больше чем размер одного сегмента WAL).

Журнальный кеш устроен наподобие буферного кеша, но работает преимущественно в режиме кольцевого буфера: записи добавляются в «голову» буфера, а записываются на диск с «хвоста».

## Page 9: Физическое устройство журнала {#source-page-009}

Размер кеша журнала в общей памяти:
```text
=> SHOW wal_buffers;
```
```text
 wal_buffers
-------------
 4MB
(1 row)
```
Все журнальные файлы (сегменты) находятся в каталоге /var/lib/postgresql/16/main/pg\_wal/, их также показывает специальная функция:
```sql
=> SELECT * FROM pg_ls_waldir() LIMIT 10;
```
```text
           name           |   size   |      modification
--------------------------+----------+------------------------
 000000010000000000000001 | 16777216 | 2026-06-15 12:54:29+03
(1 row)
```
Имена файлов составлены из трех чисел. Первое — номер линии времени (используется при восстановлении из архива), а два следующих — старшие разряды LSN.
Размер файлов можно задать при инициализации кластера, по умолчанию — 16 Мбайт.
Запись INSERT+INIT находится в этом файле:
```sql
=> SELECT pg_walfile_name('0/1FEEA08');
```
```text
     pg_walfile_name
--------------------------
 000000010000000000000001
(1 row)
```
Текущий сегмент:
```sql
=> SELECT pg_walfile_name(pg_current_wal_insert_lsn());
```
```text
     pg_walfile_name
--------------------------
 000000010000000000000001
(1 row)
```

## Page 10: Упреждающая запись {#source-page-010}

![Source slide page 10: Упреждающая запись](images/page-010.png){#source-figure-010 caption="Source slide page 10: Упреждающая запись"}

  - • •

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

Проиллюстрируем сказанное выше про упреждающую запись. На слайде показаны три важные области общей памяти экземпляра:

● буферный кеш (размером shared\_buffers),

● только что рассмотренный журнальный кеш WAL (размером wal\_buffers),

● кеш состояния транзакций, называемый также CLOG (размером 128 страниц).

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

Позицию для записи можно узнать с помощью функции pg\_current\_wal\_insert\_lsn().

## Page 11: Упреждающая запись {#source-page-011}

![Source slide page 11: Упреждающая запись](images/page-011.png){#source-figure-011 caption="Source slide page 11: Упреждающая запись"}

  - • •

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

Допустим, далее происходит фиксация транзакции. Для этого формируется еще одна журнальная запись, меняется бит состояния на странице CLOG и ссылка на эту запись проставляется в поле LSN измененной страницы.

При вставке указатель pg\_current\_wal\_insert\_lsn сдвигается вперед.

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

## Page 12: Упреждающая запись {#source-page-012}

![Source slide page 12: Упреждающая запись](images/page-012.png){#source-figure-012 caption="Source slide page 12: Упреждающая запись"}

  - • •

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

Далее в какой-то момент (в какой именно — будет рассмотрено в теме «Настройка журнала») журнальные записи, которые еще не попали на диск, должны на него попасть.

Функция pg\_current\_wal\_flush\_lsn() показывает последнюю запись, уже дошедшую до диска.

## Page 13: Упреждающая запись {#source-page-013}

![Source slide page 13: Упреждающая запись](images/page-013.png){#source-figure-013 caption="Source slide page 13: Упреждающая запись"}

  - • •

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

Только после того, как на диск попали журнальные записи, могут быть сброшены и сами измененные страницы. Порядок контролируется с учетом LSN последнего изменения страницы и текущего значения pg\_current\_wal\_flush\_lsn. При этом работа продолжается, в журнал будут попадать новые и новые записи. Для сброса страницы главное, чтобы запись с LSN последнего ее изменения уже была на диске.

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

## Page 14: Упреждающая запись {#source-page-014}

Мы будем заглядывать в заголовок табличной страницы. Для этого понадобится расширение:
```sql
=> CREATE EXTENSION pageinspect;
```
```sql
CREATE EXTENSION
```
Начнем транзакцию.
```text
=> BEGIN;
```
```text
BEGIN
```
Текущая позиция и текущий сегмент журнала:
```sql
=> SELECT pg_current_wal_insert_lsn(), pg_walfile_name(pg_current_wal_insert_lsn());
```
```text
 pg_current_wal_insert_lsn |     pg_walfile_name
---------------------------+--------------------------
 0/20678B8                 | 000000010000000000000002
(1 row)
```
Изменим строку в таблице:
```sql
=> UPDATE t SET note = 'BAR';
```
```sql
UPDATE 1
```
Позиция в журнале изменилась:
```sql
=> SELECT pg_current_wal_insert_lsn();
```
```text
 pg_current_wal_insert_lsn
---------------------------
 0/2067900
(1 row)
```
Этот же номер LSN (или меньший, если в журнал попали дополнительные записи) мы найдем и в заголовке измененной страницы:
```sql
=> SELECT lsn FROM page_header(get_raw_page('t',0));
```
```text
    lsn
-----------
 0/2067900
(1 row)
```
Завершим транзакцию.
```text
=> COMMIT;
```
```text
COMMIT
```
Позиция в журнале снова изменилась:
```sql
=> SELECT pg_current_wal_insert_lsn();
```
```text
 pg_current_wal_insert_lsn
---------------------------
 0/2067928
(1 row)
```
Размер фрагмента журнала (в байтах), соответствующий нашей транзакции:
```sql
=> SELECT pg_wal_lsn_diff('0/2067928', '0/20678B8');
```
```text
 pg_wal_lsn_diff
-----------------
             112
(1 row)
```
Безусловно, в журнал попадает информация обо всех действиях во всем кластере, но в данном случае мы рассчитываем на то, что в системе ничего не происходит.
Вот сами записи:

## Page 15: Page 15 {#source-page-015}

```sql
=> SELECT xid, record_type, record_length, end_lsn-start_lsn occupied
FROM pg_get_wal_records_info('0/20678B8','0/2067928')
ORDER BY start_lsn;
```
```text
 xid | record_type | record_length | occupied
-----+-------------+---------------+----------
 788 | HOT_UPDATE  |            70 |       72
 788 | COMMIT      |            34 |       40
(2 rows)
```
Заметим, что из-за выравнивания длина записи обычно меньше, чем занимаемое ей место (столбец occupied).
Увидеть содержимое журнала можно и с помощью утилиты pg\_waldump. Она может работать с диапазоном LSN (как в этом примере), выбрать записи для определенного отношения и отдельного слоя, а также для отдельной страницы или указанной транзакции. В отличие от расширения pg\_walinspect, утилитой можно пользоваться и на остановленном сервере, при этом ей нужен доступ на чтение журнальных файлов, поэтому мы выполним команду от имени суперпользователя:
```sh
student$ sudo /usr/lib/postgresql/16/bin/pg_waldump \
-p /var/lib/postgresql/16/main/pg_wal \
-s 0/20678B8 -e 0/2067928 \
000000010000000000000002 000000010000000000000002 \
| fold -sw 100
```
```text
rmgr: Heap        len (rec/tot):     70/    70, tx:        788, lsn: 0/020678B8, prev
0/02067878,
desc: HOT_UPDATE old_xmax: 788, old_off: 1, old_infobits: [], flags: 0x01, new_xmax: 0,
new_off: 2,
blkref #0: rel 1663/16390/16398 blk 0
rmgr: Transaction len (rec/tot):     34/    34, tx:        788, lsn: 0/02067900, prev
0/020678B8,
desc: COMMIT 2026-06-15 12:54:32.418140 MSK
```
Мы видим заголовки журнальных записей:
операция HOT\_UPDATE, относящаяся к измененной странице (rel+blk), операция COMMIT с указанием времени.

## Page 16: Восстановление {#source-page-016}

**Алгоритм (упрощенный)**
  - при старте сервера после сбоя
  - (состояние кластера в pg\_control отличается от «shut down»):
  - 1. для каждой журнальной записи:
  - 1.1. определить страницу, к которой относится эта запись
  - 1.2. применить запись, если ее LSN больше, чем LSN страницы
  - 2. перезаписать нежурналируемые таблицы init-файлами

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

Если в работе сервера произошел сбой, то при последующем запуске процесс startup (запускаемый postmaster-ом в самом начале работы) обнаружит это, посмотрев в файл pg\_control и увидев статус, отличный от «shut down». Тогда автоматически будет выполнено восстановление.

Процесс startup будет последовательно читать журнал и применять записи к страницам, если в этом есть необходимость (что можно проверить, сравнив LSN страницы на диске с LSN журнальной записи). Изменение страниц происходит в буферном кеше, как при обычной работе — для этого postmaster запускает необходимые фоновые процессы.

Аналогично записи применяются и к файлам: например, если запись говорит о том, что файл должен существовать, а его нет — файл создается.

В конце процесса все нежурналируемые таблицы перезаписываются с помощью образов в init-файлах.

В PostgreSQL 15 реализована возможность предвыборки данных журнала при восстановлении — за это отвечает параметр recovery\_prefetch. По умолчанию его значение — try (предвыборка используется, если ее поддерживает ОС). Если предвыборка включена, то значение параметра wal\_decode\_buffer\_size определяет, как далеко сервер будет заглядывать в журнал, чтобы найти там номера нужных страниц.

Приведенный алгоритм является упрощенным. В частности, мы ничего не сказали о том, с какого места надо начинать чтение журнальных записей (это будет рассмотрено в теме «Контрольная точка»).

## Page 17: Итоги {#source-page-017}

**Использование буферов в оперативной памяти приводит к необходимости журналирования**
- Журнал содержит информацию, позволяющую повторно выполнить операции после сбоя и восстановить согласованность
- Журнал всегда записывается на диск до того, как записываются измененные страницы данных

## Page 18: Практика {#source-page-018}

1. Создайте таблицу с первичным ключом и добавьте в нее несколько строк. Сколько байт занимают сгенерированные журнальные записи?
- 2. Чем можно объяснить довольно большое их число? Просмотрите заголовки этих журнальных записей и проверьте свои предположения.
- 3. Измените добавленные в таблицу строки. Снова измените строки, но не фиксируйте транзакцию. Сымитируйте сбой, прервав процесс postmaster.
  - Запустите сервер и убедитесь, что зафиксированные
  - изменения не пропали, а незафиксированная транзакция
  - оборвана. Найдите информацию о восстановлении после
  - сбоя в журнале сообщений сервера.

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

2. Можно воспользоваться утилитой pg\_waldump или расширением pg\_walinspect.

3. Воспользуйтесь командой $ sudo kill -QUIT номер-процесса

Номер процесса находится в первой строке файла postmaster.pid в каталоге PGDATA сервера.

## Page 19: 1. Размер журнальных записей {#source-page-019}

```sql
=> CREATE DATABASE wal_log;
```
```sql
CREATE DATABASE
```
```sql
=> \c wal_log
```
```text
You are now connected to database "wal_log" as user "student".
```
Запомним начальную позицию в журнале:
```sql
=> SELECT pg_current_wal_insert_lsn();
```
```text
 pg_current_wal_insert_lsn
---------------------------
 0/1FE6468
(1 row)
```
Создадим таблицу и добавим строки:
```sql
=> CREATE TABLE t(
  id integer PRIMARY KEY,
  s text
);
```
```sql
CREATE TABLE
```
```sql
=> INSERT INTO t VALUES (1, 'A'), (2, 'B'), (3, 'C');
```
```sql
INSERT 0 3
```
Запомним конечную позицию:
```sql
=> SELECT pg_current_wal_insert_lsn();
```
```text
 pg_current_wal_insert_lsn
---------------------------
 0/1FE9D08
(1 row)
```
Размер журнальных записей:
```sql
=> SELECT '0/1FE9D08'::pg_lsn - '0/1FE6468'::pg_lsn;
```
```text
 ?column?
----------
    14496
(1 row)
```
### 2. Состав журнальных записей
Журнальный файл:
```sql
=> SELECT pg_walfile_name('0/1FE6468');
```
```text
     pg_walfile_name
--------------------------
 000000010000000000000001
(1 row)
```
Понадобится расширение pg\_walinspect:
```sql
=> CREATE EXTENSION pg_walinspect;
```
```sql
CREATE EXTENSION
```
Смотрим записи:
```sql
=> SELECT start_lsn, xid, resource_manager, record_type,
regexp_match(block_ref, 'rel (\d+\/\d+/\d+) fork (\w+) blk (\w+)') AS bref
FROM pg_get_wal_records_info('0/1FE6468', '0/1FE9D08');
```
```sql
 start_lsn | xid | resource_manager | record_type  |           bref
-----------+-----+------------------+--------------+---------------------------
 0/1FE6468 |   0 | Storage          | CREATE       |
```

## Page 20: Page 20 {#source-page-020}

```sql
 0/1FE6498 | 744 | Heap             | INSERT       | {1663/16390/1247,main,14}
 0/1FE6570 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2703,main,2}
 0/1FE65B0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2704,main,2}
 0/1FE65F0 | 744 | Heap2            | MULTI_INSERT | {1663/16390/2608,main,2}
 0/1FE6648 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,4}
 0/1FE6690 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,7}
 0/1FE66D8 | 744 | Heap             | INSERT       | {1663/16390/1247,main,14}
 0/1FE67B0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2703,main,2}
 0/1FE67F0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2704,main,4}
 0/1FE6830 | 744 | Heap2            | MULTI_INSERT | {1663/16390/2608,main,3}
 0/1FE6888 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,4}
 0/1FE68D0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,5}
 0/1FE6918 | 744 | Heap             | INSERT       | {1663/16390/1259,main,0}
 0/1FE69E8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2662,main,2}
 0/1FE6A28 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2663,main,2}
 0/1FE6A68 | 744 | Btree            | INSERT_LEAF  | {1663/16390/3455,main,1}
 0/1FE6AA8 | 744 | Heap2            | MULTI_INSERT | {1663/16390/1249,main,17}
 0/1FE6BD8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE6C18 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE6C58 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE6C98 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE6CD8 | 744 | Heap2            | MULTI_INSERT | {1663/16390/1249,main,17}
 0/1FE7000 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7048 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7088 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE70D0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7110 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7158 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7198 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE71E0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7220 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7268 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE72A8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE72F0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7330 | 744 | Heap             | INSERT       | {1664/0/1214,main,0}
 0/1FE7380 | 744 | Btree            | INSERT_LEAF  | {1664/0/1232,main,1}
 0/1FE73C8 | 744 | Btree            | INSERT_LEAF  | {1664/0/1233,main,1}
 0/1FE7408 | 744 | Heap2            | MULTI_INSERT | {1663/16390/2608,main,3}
 0/1FE7460 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,8}
 0/1FE74A8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,7}
 0/1FE74F0 | 744 | Standby          | LOCK         |
 0/1FE7520 | 744 | Storage          | CREATE       |
 0/1FE7550 | 744 | Heap             | INSERT       | {1663/16390/1259,main,0}
 0/1FE7620 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2662,main,2}
 0/1FE7660 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2663,main,2}
 0/1FE76B0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/3455,main,1}
 0/1FE76F0 | 744 | Heap2            | MULTI_INSERT | {1663/16390/1249,main,17}
 0/1FE78A0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE78E8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7928 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7970 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE79B0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE79F8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7A38 | 744 | Heap2            | MULTI_INSERT | {1663/16390/1249,main,17}
 0/1FE7CE0 | 744 | Heap2            | MULTI_INSERT | {1663/16390/1249,main,56}
 0/1FE7D90 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7DD8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7E18 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7E60 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7EA0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7EE8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7F28 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7F70 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE7FB0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE7FF8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE8050 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE8098 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE80D8 | 744 | Storage          | CREATE       |
 0/1FE8108 | 744 | Standby          | LOCK         |
 0/1FE8138 | 744 | Heap             | INSERT       | {1663/16390/1259,main,0}
 0/1FE8208 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2662,main,2}
 0/1FE8248 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2663,main,2}
 0/1FE82A0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/3455,main,1}
 0/1FE82E0 | 744 | Heap2            | MULTI_INSERT | {1663/16390/1249,main,56}
 0/1FE8410 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE8458 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE8498 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE84E0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE8520 | 744 | Heap             | INSERT       | {1663/16390/2610,main,0}
 0/1FE85F0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2678,main,1}
```

## Page 21: Page 21 {#source-page-021}

```sql
 0/1FE8630 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2679,main,1}
 0/1FE8670 | 744 | Heap2            | MULTI_INSERT | {1663/16390/2608,main,3}
 0/1FE86F0 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,8}
 0/1FE8738 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,7}
 0/1FE8780 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,8}
 0/1FE87C8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,7}
 0/1FE8810 | 744 | XLOG             | FPI          | {1663/16390/16395,main,0}
 0/1FE88A0 | 744 | Heap             | INPLACE      | {1663/16390/1259,main,0}
 0/1FE8960 | 744 | Heap             | INPLACE      | {1663/16390/1259,main,0}
 0/1FE8A20 | 744 | Heap             | HOT_UPDATE   | {1663/16390/1259,main,0}
 0/1FE8A70 | 744 | Heap2            | MULTI_INSERT | {1663/16390/2608,main,3}
 0/1FE8AC8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,8}
 0/1FE8B10 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,7}
 0/1FE8B58 | 744 | Storage          | CREATE       |
 0/1FE8B88 | 744 | Standby          | LOCK         |
 0/1FE8BB8 | 744 | Heap             | INSERT       | {1663/16390/1259,main,0}
 0/1FE8C88 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2662,main,2}
 0/1FE8CC8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2663,main,2}
 0/1FE8D10 | 744 | Btree            | INSERT_LEAF  | {1663/16390/3455,main,1}
 0/1FE8D50 | 744 | Heap2            | MULTI_INSERT | {1663/16390/1249,main,56}
 0/1FE8E00 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2658,main,15}
 0/1FE8E40 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2659,main,10}
 0/1FE8E80 | 744 | Heap             | INSERT       | {1663/16390/2610,main,0}
 0/1FE8F48 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2678,main,1}
 0/1FE8F88 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2679,main,1}
 0/1FE8FC8 | 744 | Heap             | INSERT       | {1663/16390/2606,main,2}
 0/1FE9088 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2579,main,1}
 0/1FE90C8 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2664,main,1}
 0/1FE9110 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2665,main,1}
 0/1FE9158 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2666,main,1}
 0/1FE9198 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2667,main,1}
 0/1FE91D8 | 744 | Heap2            | MULTI_INSERT | {1663/16390/2608,main,3}
 0/1FE9230 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,5}
 0/1FE9278 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,7}
 0/1FE92C0 | 744 | Heap2            | MULTI_INSERT | {1663/16390/2608,main,3}
 0/1FE9318 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2673,main,8}
 0/1FE9360 | 744 | Btree            | INSERT_LEAF  | {1663/16390/2674,main,7}
 0/1FE93A8 | 744 | XLOG             | FPI          | {1663/16390/16396,main,0}
 0/1FE9438 | 744 | Heap             | INPLACE      | {1663/16390/1259,main,0}
 0/1FE94F8 | 744 | Heap             | INPLACE      | {1663/16390/1259,main,0}
 0/1FE95B8 | 744 | Transaction      | COMMIT       |
 0/1FE9B00 | 745 | Heap             | INSERT+INIT  | {1663/16390/16391,main,0}
 0/1FE9B40 | 745 | Btree            | NEWROOT      | {1663/16390/16396,main,1}
 0/1FE9BA0 | 745 | Btree            | INSERT_LEAF  | {1663/16390/16396,main,1}
 0/1FE9BE0 | 745 | Heap             | INSERT       | {1663/16390/16391,main,0}
 0/1FE9C20 | 745 | Btree            | INSERT_LEAF  | {1663/16390/16396,main,1}
 0/1FE9C60 | 745 | Heap             | INSERT       | {1663/16390/16391,main,0}
 0/1FE9CA0 | 745 | Btree            | INSERT_LEAF  | {1663/16390/16396,main,1}
 0/1FE9CE0 | 745 | Transaction      | COMMIT       |
(130 rows)
```
Вначале (до первой операции COMMIT) происходит активная работа с таблицами и индексами системного каталога. За счет этого размер записей и получился существенно больше, чем в демонстрации.
### 3. Восстановление после сбоя
Обновляем строки:
```sql
=> UPDATE t SET s = 'FOO';
```
```sql
UPDATE 3
```
```text
=> BEGIN;
```
```text
BEGIN
```
```sql
=> UPDATE t SET s = 'BAR'; -- не фиксируем транзакцию
```
```sql
UPDATE 3
```
Прерываем основной серверный процесс.
```sh
student$ sudo kill -QUIT $(sudo head -n 1 /var/lib/postgresql/16/main/postmaster.pid)
```
```sh
student$ sudo pg_ctlcluster 16 main status
```
```text
pg_ctl: no server running
```
Запускаем сервер.
```sh
student$ sudo pg_ctlcluster 16 main start
```

## Page 22: Page 22 {#source-page-022}

Проверяем изменения:
```sh
student$ psql wal_log
```
```sql
=> SELECT * FROM t;
```
```text
 id |  s
----+-----
  1 | FOO
  2 | FOO
  3 | FOO
(3 rows)
```
Журнал сообщений:
```sh
student$ tail -n 6 /var/log/postgresql/postgresql-16-main.log
```
```text
2026-06-15 13:23:35.971 MSK [56841] LOG:  redo starts at 0/1BB5790
2026-06-15 13:23:36.047 MSK [56841] LOG:  invalid record length at 0/1FEF8A0: expected at
least 24, got 0
2026-06-15 13:23:36.047 MSK [56841] LOG:  redo done at 0/1FEF878 system usage: CPU: user:
0.00 s, system: 0.01 s, elapsed: 0.07 s
2026-06-15 13:23:36.209 MSK [56839] LOG:  checkpoint starting: end-of-recovery immediate
wait
2026-06-15 13:23:39.554 MSK [56839] LOG:  checkpoint complete: wrote 943 buffers (5.8%);
0 WAL file(s) added, 0 removed, 0 recycled; write=0.053 s, sync=3.233 s, total=3.359 s;
sync files=308, longest=0.031 s, average=0.011 s; distance=4328 kB, estimate=4328 kB;
lsn=0/1FEF8A0, redo lsn=0/1FEF8A0
2026-06-15 13:23:39.578 MSK [56838] LOG:  database system is ready to accept connections
```
