MySQL(InnoDB)のUndo Logs, history listとは何ぞや?

先日、MySQL(InnoDB)でトランザクションを張ったまま大量のクエリを発行することによりhistory listが肥大化し障害に繋がるケースがあることを知り、個人的に調査した記録を残そうと思います。
Undo Logとは
まず、history listを理解する前にUndo Logを理解する必要があります。
https://dev.mysql.com/doc/refman/9.0/en/innodb-undo-logs.html
Undo Logは単一の読み取り/書き込みのトランザクションに関するUndo Logレコードの集まりのことを指します。
Undo Logはトランザクションにおける変更のログを情報として保持するものです。ロールバックの用途であるためにRedo用の情報は含んでいません。 consistent readが実行された際はUndo Logレコードからデータが参照されます。
“consistent read”は以前以下の記事にて取り上げてます。
MySQL(InnoDB)のTransaction Isolation Levelsについてまとめる
history listとは
https://dev.mysql.com/doc/refman/9.0/en/innodb-purge-configuration.html
https://dev.mysql.com/doc/refman/9.0/en/glossary.html#glos_history_list
InnoDBはDELETE文が発行されても即時には物理削除をしません。 MultiVersion Concurrency Control(MVCC)の管理上不要になったり、ロールバックするために必要ではなくなった場合に物理削除されるようになっており、これはpurgeと呼ばれています。
MVCCについては以前以下の記事にて取り上げてます。
InnoDBは定期的にpurgeをするようになっており、purge時にはdelete markerが付けられたレコードが物理削除されますが、そのようなレコードを含んだトランザクションのリストが“history list”です。
history listがinnodb_max_purge_lag の値より大きくなった場合、各DMLは遅延します。
history listを確認する
以下のコマンドを実行するとTRANSACTIONSのセクションにてHistory list lengthが確認できます。
mysql> SHOW ENGINE INNODB STATUS;
...
------------
TRANSACTIONS
------------
Trx id counter 1174188
Purge done for trx's n:o < 1174188 undo n:o < 0 state: running but idle
History list length 33
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421192588579624, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421192588578816, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421192588578008, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421192588577200, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421192588576392, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
...
何故長時間のトランザクションがhistory listの肥大化を招くのか
公式ドキュメントに以下の記載がありました。
The History list length is typically a low value, usually less than a few thousand, but a write-heavy workload or long running transactions can cause it to increase, even for transactions that are read only. The reason that a long running transaction can cause the History list length to increase is that under a consistent read transaction isolation level such as REPEATABLE READ, a transaction must return the same result as when the read view for that transaction was created. https://dev.mysql.com/doc/refman/9.0/en/innodb-purge-configuration.html#idm45552486500384
つまり、Transaction Isolation LevelがREPEATABLE READの場合はconsistent readを実現する(常に同じ取得結果を返す)必要があるが、そのためにはhistory listに変更のログを保持し続ける必要がある。 そのため、トランザクションが張り続けられたまま大量のクエリが発行されるとhistory listの肥大化がDBサーバの負荷となり得るようです。
バックエンドでDBアクセスを行うロジックを書く場合、こういった点も考慮が必要という学びでした。