49.9. Передача больших транзакций для логического декодирования

Базовые обработчики модуля вывода (например, begin_cb, change_cb, commit_cb и message_cb) вызываются, только когда транзакция на самом деле фиксируется. Изменения в любом случае декодируются из журнала транзакций, но передаются в модуль вывода только при фиксации (и отбрасываются, если транзакция прерывается).

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

Чтобы уменьшить задержку, связанную с большими транзакциями, модуль вывода может предоставлять дополнительный обработчик для поддержки инкрементальной передачи выполняющихся транзакций. Определены несколько обязательных обработчиков передачи (stream_start_cb, stream_stop_cb, stream_abort_cb, stream_commit_cb и stream_change_cb) и два необязательных обработчика (stream_message_cb) и (stream_truncate_cb). Кроме того, если планируется поддерживать потоковую передачу двухфазных команд, нужны дополнительные обработчики. (За подробностями обратитесь к Разделу 49.10).

Когда выполняющаяся транзакция передаётся в потоке, её изменения (и сообщения) передаются блоками, границы которых обозначают вызовы stream_start_cb и stream_stop_cb. После того, как все декодированные изменения переданы, транзакция может быть зафиксирована обработчиком stream_commit_cb (или, возможно, прервана обработчиком stream_abort_cb). Если поддерживаются двухфазные фиксации, транзакция может быть подготовлена обработчиком stream_prepare_cb, зафиксирована (COMMIT PREPARED) обработчиком commit_prepared_cb или прервана обработчиком rollback_prepared_cb.

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

stream_start_cb(...);   <-- начало первого блока изменений
  stream_change_cb(...);
  stream_change_cb(...);
  stream_message_cb(...);
  stream_change_cb(...);
  ...
  stream_change_cb(...);
stream_stop_cb(...);    <-- конец первого блока изменений

stream_start_cb(...);   <-- начало второго блока изменений
  stream_change_cb(...);
  stream_change_cb(...);
  stream_change_cb(...);
  ...
  stream_message_cb(...);
  stream_change_cb(...);
stream_stop_cb(...);    <-- конец второго блока изменений


[a. при использовании нормальной фиксации]
stream_commit_cb(...);  <-- фиксация переданной транзакции 

[b. при использовании двухфазной фиксации]
stream_prepare_cb(...);   <-- подготовка переданной транзакции
commit_prepared_cb(...);  <-- фиксация подготовленной транзакции

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

Подобно поведению вытеснения на диск, передача в потоке запускается, когда общее количество изменений, декодированных из WAL (для всех текущих транзакций), превышает предел, заданный параметром logical_decoding_work_mem. В этот момент выбирается самая большая транзакция (это определяется по объёму памяти, который уже занимают декодированные изменения) верхнего уровня, и она передаётся в потоке. Тем не менее, в некоторых случаях всё равно приходится переносить данные на диск, даже когда включена передача в потоке, поскольку порог памяти уже превышен, но полный кортеж ещё не декодирован, например декодировано добавление только в таблицу TOAST, но не в основную таблицу.

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