2025/05/21

レプリケーション構成でない2つのMySQLに確実にデータを書き込みたいメモ

  • それぞれのMySQLはSemisyncで正しくフェイルオーバーしたりGroup Replicationだったりで書き込みは信用できることにする
  • サロゲートキー(だけ)でなくて比較可能なPRIMARY KEYが存在することにする
  • 取り敢えず簡単なINSERTで考えるけどUPDATEもありそう

という前提で考え事。
FEDERATEDストレージエンジンはそもそもトランザクションをサポートしないので論外とする。

App側の2層コミットっぽいもの

2 Phase Commitはそもそもコーディネーターの障害に対する耐性がない…。

eval
{
  $dbh1->begin;
  $dbh1->do("INSERT INTO ..");
  $dbh2->begin;
  $dbh2->do("INSERT INTO ..");
};

if ($@)
{
  $dbh1->rollback;
  $dbh2->rollback;
}

$dbh1->commit;

### この瞬間にAppのプロセスが落ちるとダメ
$dbh2->commit;

この場合「必ずdbh2の方が足りない状態になる」のは確かかもしれない。

補償トランザクション的な何か

さっきののcommitの部分にさらに一捻り…したところで

eval
{
  $dbh1->commit;
};
$dbh2->rollback if $@;

eval
{
  $dbh2->commit;
};

if ($@)
{
  $dbh1->begin;
  ### ここらへんで落ちるとやっぱりダメ
  $dbh1->do("DELETE FROM ..");
  $dbh1->commit;
}

補償トランザクション形式だとUPDATEの巻き戻しがなかなか大変そうだし、結局落ちるとダメにはなる。この場合もdbh2が足りない状態になるのは当たりだろうか。
ちなみに MySQLのXAステートメント を使ったところでxidをどこかに記録しておかないと処理が引き継げないので結局何も記録せずに単一プロセスでやろうというのは無理。

さっきのと合わせて、dbh1の側の created_at みたいなのにインデックスを張っておいて定期的にスキャンして突合するような感じになるはず。DELETEまで考えるとdbh2側をスキャンしてdbh1側をNested Loopスキャンもしないといけなくなる(何がなくなったのかは片方だけ見てもわからないから)

非同期ダブルライト型

少なくとも片方のMySQLにはある程度確実に書ける(書けなければちゃんとエラーになる(実際semisyncではこれは保証できないんだけどいったん置いておく)、書いた以上はMySQL側がクラッシュしてフェイルオーバーしても残る)という前提に基づくと、

$dbh1->begin;
eval
{
  $dbh1->do("INSERT INTO metadata ..");
  $dbh1->do("INSERT INTO real_table ..");
  $dbh1->commit;
};

$dbh1->rollback if $@;

メタデータ(例えばdbh2に発行するべきINSERT文そのものでもいい)のテーブルへの記録と、dbh1側の書き込みたいテーブルを1つのトランザクションにまとめると、この2つのテーブルに関してはトランザクションで保護される。
$dbh1->commit までにAppがクラッシュした場合はAppとしてエラーになってメタデータもテーブルも残っていない状態になるし、 $dbh1->commit まで到達した場合は両方とも残っている。
で、別途ワーカーが必要。

$dbh1->begin;
my $sth= $dbh1->prepare("SELECT * FROM metadata ORDER BY id ASC LIMIT 1 FOR UPDATE SKIP LOCKED");
my $row= $sth->selectrow_hashref;
eval
{
  $dbh2->begin;
  $dbh2->do("INSERT INTO .."); 
  $dbh2->commit;
}
$dbh2->rollback if $@;
### ここらへんで落ちるとmetadataだけおかしくなる
$dbh1->do("DELETE FROM metadata ..");
$dbh1->commit;

この場合、

  • ワーカーが動くまではdbh1とdbh2で不整合が起こり続ける(dbh2が足りない状態になる)こと
  • ワーカーがクラッシュするとmetadataだけ消えずに余ること
    なのでmetadataと実際のテーブルの更新情報を突合するためのキーは必ず必要になって(dbh1に記録する時にApp側でUUID生成して、実テーブルの方に last_updated_by = ? みたいに持つとか)ワーカーが同じ更新を二重実行しても安全になるようにしてやらないといけない。
    けれどスキャンは必要なさそうだしワーカーの起動が制御しやすそうだなと思ったのでした。

まるでmysql.slave_relay_log_infoとSQLスレッドだな、とは思った。
ただの考え事でした。


【2025/05/21 21:22】

「非同期ダブルライト型」と呼んでいたやつは「Transactional Outboxパターン」という名前がついていると教えてもらいました! ありがとうございますm(_ _)m



トランザクションアウトボックスパターン - AWS 規範ガイダンス

0 件のコメント :

コメントを投稿