EKS上のDebeziumでAuroraのフェイルオーバー時に自動復旧を実現する方法
Mirai Translate Tech Blog は、EKS で稼働する Debezium が Aurora のフェイルオーバー時にリーダーインスタンスへ接続し続ける問題に対し、DNS キャッシュの調整や livenessProbe を活用した自動復旧策を詳述している。
キーポイント
Aurora フェイルオーバー時の Debezium 接続不整合問題
フェイルオーバー後、DNS キャッシュの影響で Debezium がライターではなくリーダーインスタンスへ接続し続け、'ALTER PUBLICATION in a read-only transaction' エラーが発生して CDC が停止する現象を指摘。
Java アプリケーション固有の DNS キャッシュ対策
通常の Java アプリでは DNS TTL の調整とコネクションプールのテストクエリにより、接続先がリーダーかライターかを判別しフェイルオーバーに対応する手法を補足。
EKS 環境における自動復旧の実装アプローチ
Debezium プロセス自体は生きているため Pod の再起動が必要となる問題を解決するため、livenessProbe でタスク状態(FAILED)を監視し、自動的に新しい Pod を起動させる設定を提案。
Outbox パターンと Debezium の役割
Aurora から Kafka へのデータ転送に Outbox パターンを採用し、トランザクションの一貫性を保ちつつ CDC を行う仕組みの概要と、Debezium v1.9 以降のリトライ機能改善について言及。
Java DNSキャッシュとコネクションプールの限界
SpringBootではDNS TTL設定やHikariCPのテストクエリでAurora FailOverへの対応が可能だが、Debezium(Kafka Connect)ではこれらの設定が機能せず自動復旧しなかった。
Connectorの状態監視によるPod再起動
エラーログ監視ではなく、Kafka ConnectのAPIで取得可能なTask状態(FAILED)をトリガーとして利用し、KubernetesのProbe仕様を活用して自動復旧させる方針へ転換した。
FailOver時の状態遷移と検知
Aurora FailOver発生時はJDBC接続リトライ中でRUNNINGとなるが、リードレプリカへの更新失敗時にTask状態がFAILEDに遷移するため、これを異常検知のトリガーとして活用できる。
影響分析・編集コメントを表示
影響分析
この記事は、大規模なデータベース基盤を運用するエンジニアにとって極めて実用的な知見を提供しています。Aurora の高可用性機能と Debezium の挙動の間に存在する微妙な競合状態(DNS キャッシュによる接続先誤認識)を特定し、具体的な解決策を示すことで、システム全体の信頼性を高める上で重要な役割を果たします。特に EKS 環境での運用経験者が直面しやすい課題に対する即戦力となる対策です。
編集コメント
Aurora のフェイルオーバー時に発生する微妙なタイミングの問題を、DNS キャッシュの観点から掘り下げた非常に質の高い技術記事です。運用担当者が陥りやすい「プロセスは生きているが機能していない」状態への対処法として参考になります。

こんにちは。プラットフォーム開発部でリードエンジニアをしている chance です。
今回は EKS で実行している Debezium で、Aurora の FailOver 時に自動復旧させる設定についてお話しします。また補足として、SpringBoot などの Java アプリケーションでの FailOver 対策についても触れています。
Debezium の FailOver 対策としては、EKS の livenessProbe(死活判定プローブ)で tasks の state が FAILED になっていないかどうかを監視する。
通常の Java アプリケーションでは DNS キャッシュ TTL を調整の上、コネクションプールのテストクエリで read-only(読み取り専用)を見分ける。
弊社ではイベント駆動のメッセージングのブローカーとして Kafka(AWS MSK) を利用しています。また、Kafka への書き込みとしては Outbox パターンを採用しており、Aurora から Kafka への Producer として Debezium を利用しています。
Outbox パターン
Outbox パターンによってアプリケーションからの業務データとメッセージングのトランザクションの一貫性が保証されます。
この記事をお読みいただく方に Debezium 自体のご説明は不要かもしれませんので、サラッと書くと、Debezium は CDC(Change Data Capture:変更データキャプチャ) のための OSS です。具体的には、データベースのトランザクションログ(たとえば PostgreSQL の WAL ログ)を読んで更新差分を検知し、Kafka にメッセージを投げ込んでくれます。CDC 自体はデータベースのレプリケーションなどに用いられるものですが、Outbox パターンの実装方法としても最適です。
Debezium は常にデータベースに接続しており FailOver 発生時は自動で接続復旧を試みます。しかし、無事に接続が復旧できた後にエラーになることがあります。今回はこの対処についてお話ししていきます。
Debezium は EKS 上の Pod で動かしており、データベースには Aurora PostgreSQL を利用しています。
CDC(Change Data Capture:変更データキャプチャ) 概要図
以下のバージョンを利用しています。
(話に関係ないであろうもののバージョンは省略)
Aurora は FailOver 時にも素早く復旧してくれます。とはいえ、クライアントからすると一度接続が切れることには変わりありません。Debezium には接続が切れた際にリトライする機構が備わっており、再接続できた場合は処理を再開します。(v1.9 あたりでリトライ周りの実装をいろいろと改善して頂いたみたいです。)
Debezium 1.9 のリリースノート
しかし、ここで問題が発生しました。接続が復旧して Debezium が処理を再開する際、前回までの読み込み位置を確認して Replication Slot を更新しようとするのですが、これに失敗して処理を停止してしまいます。必ず発生するわけではなく、開発環境で試していると何回かに 1 回は発生しました。
エラーが発生する際のログの抜粋は以下です。
2024-01-26 07:12:59,697 ERROR WorkerSourceTask{id=<タスク ID>} Task threw an uncaught and unrecoverable exception. Task is being killed and will not recover until manually restarted [org.apache.kafka.connect.runtime.WorkerTask] org.apache.kafka.connect.errors.ConnectException: Unable to update filtered publication <パブリケーション名> for <スキーマ名>.<テーブル名> at ...(省略) Caused by: org.postgresql.util.PSQLException: ERROR: cannot execute ALTER PUBLICATION in a read-only transaction at ...(省略) 2024-01-26 07:12:59,699 INFO || Stopping down connector [io.debezium.connector.common.BaseSourceTask]
PSQLException: ERROR: cannot execute ALTER PUBLICATION in a read-only transaction
さらに困ったことに、Debezium 自体(Java プロセス)が停止してくれれば新しい Pod が立ち上がってくれるのでまだよいのですが、Java プロセスは生きたままなので CDC のみが停止した状態になってしまい、リカバリ作業が必要になってしまいます。
Aurora は接続用のエンドポイントとしてライター用とリーダー用を用意してくれます。ライター用エンドポイントはライターのインスタンスに、リーダー用エンドポイントはリードレプリカのインスタンスに繋がります。
{クラスタ名}.cluster-xxxxx.{region}.rds.amazonaws.com
{クラスタ名}.cluster-ro-xxxxx.{region}.rds.amazonaws.com
当然、Aurora の挙動としては FailOver 後にエンドポイントの繋ぎ替えまでしてくれます。では、Debezium はなぜ FailOver 後にリーダーインスタンスに接続してしまうのでしょうか。
ひとつには、Java の DNS キャッシュの影響が考えられます。Aurora のエンドポイントのドメインの名前解決をすると最終的にはインスタンスの IP アドレスが返ります。このため、Java は実際にはインスタンスと TCP 接続を結んでいることになります。
Debezium から Aurora への接続
Java は DNS の結果を(デフォルトでは)無制限にキャッシュするため、接続のたびに名前解決をすることなく FailOver 後も FailOver 前と同じインスタンスに繋ぎにいったと推測されます。
Debezium から Aurora への接続(FailOver 後)
Java の DNS キャッシュ対策として、TTL(Time To Live:生存時間)の値を設定して定期的に DNS 問い合わせをやり直すというプラクティスがあります。
// SpringBoot アプリケーションで、プロパティで指定する場合 package xxx.yyy.zzz; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.security.Security; @SpringBootApplication public class DemoApplication { // Java は DNS キャッシュをデフォルトで永続保持してしまい Aurora の failover などに対応できないため、ttl を指定する。 static { Security.setProperty("networkaddress.cache.ttl", "10"); Security.setProperty("networkaddress.cache.negative.ttl", "10"); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
また、コネクションプール(接続プーリング)のテストクエリを工夫する方法も有効なようです。こちらのブログを参考にさせていただきました。
tech.asoview.co.jp
HikariCP の場合 spring: datasource: hikari: # transaction_read_only=off なら成功、それ以外は失敗するクエリにしておく。1/0 とすると SQL 自体が成立しないので random() を利用している。 connection-test-query: select case when current_setting('transaction_read_only') = 'off' then 1 else random()/0 end
Consumer や WebAPI は SpringBoot で開発していたため、これらの設定をすることで FailOver 後も接続を失うことなく動作しました。
しかし、今回の Debezium の場合、TTL のオプションを指定しても事象解決しませんでした。また、コネクションプールに関する設定項目は見当たりませんでした。
JDBC 接続の初期化時に実行するクエリの設定は存在しますが、ここに上記のテストクエリを設定しても接続を再試行してくれず、うまく復旧できませんでした。
さらにソースコードを追いかけていたところ、こればかりに時間をかけすぎられないと判断し、一旦手を止めました。
視点を変え、異常状態を検知して Pod を再起動できないかという検討に切り替えます。
最初に思いついたのは、エラーログ監視をトリガーとして再起動する方法です。エラー内容は既明なのでこれを監視しておき、Pod の再起動を呼び出せないかと考えました。しかし、ログフィルターから Pod 再起動コマンドを発行するまでにかなり遠回りとなるため、構築・保守の負担が増大することと、EKS 外部からの操作のための権限設定も必要になりそうであることから、もう少しシンプルに解決したいところです。
ところで、Debezium は実体としては Kafka Connect の Connector として動作しています。Connector にヘルスチェック機能はないのだろうかということで確認してみると、
docs.confluent.io
GET /connectors/(string:name)/tasks/(int:taskid)/status
Connector と Task がそれぞれ state を持っているようです。
Debezium(Kafka Conncect)のstatus(平常時)
実際に FailOver を実行して状態遷移を見てみると、FailOver 直後に JDBC 接続を失い接続リトライ中は state は RUNNING です。
Debezium(Kafka Conncect)のstatus(FailOver時)
FailOver 完了後に例のごとくリードレプリカに更新を試みて例外が発生したところで、state は FAILED です。
Debezium(Kafka Conncect)のstatus(エラー時)
これがトリガーとして使えそうです。Kubernetes には Pod の状態を識別するための Probe という仕様があります。
起動したことを識別するための条件
再起動が必要であるかを識別するための条件
(APIなどの場合に)serviceから接続できる状態かを識別するための条件
今回の事象に対処する前は、startup も liveness も 8083 ポートの /
変更前 livenessProbe: httpGet: path: / port: 8083 scheme: HTTP timeoutSeconds: 2 successThreshold: 1 failureThreshold: 1 periodSeconds: 60
今回の調査により、FailOver が起因となって Tasks の状態が FAILED となったことが判明しました。
変更後の livenessProbe は以下の通りです:exec: command: - "sh" - "-c" - "curl -s http://localhost:8083/connectors/<コネクタ名>/tasks/0/status | grep -v '"state":"FAILED"'" timeoutSeconds: 2 successThreshold: 1 failureThreshold: 1 periodSeconds: 60
これにより、状態が FAILED となったケースでも対応可能となりました。
EKS で Debezium を利用している環境において、Aurora の FailOver 時に自動復旧する事例を紹介しました。ステータスの見分け方についてはさらに精査が必要かもしれませんが、稼働状況を監視しつつ対応を進めていく方針です。
今回の件がどなたかのお役に立てれば幸いです。
みらい翻訳では、エンジニアを募集しています。
ご興味のある方は、ぜひ下記リンクよりご応募・お問い合わせをお待ちしております。
miraitranslate.com
原文を表示

こんにちは。プラットフォーム開発部でリードエンジニアをしている chance です。
今回はEKSで実行しているDebeziumで、AuroraのFailOver時に自動復旧させる設定についてお話しします。また補足として、SpringBootなどのJavaアプリケーションでのFailOver対策についても触れています。
Debezium の FailOver 対策としては、EKS の livenessProbe で tasks の state が FAILED になっていないかどうかを監視する。
通常の Java アプリケーションでは DNS キャッシュ TTL を調整の上、コネクションプールのテストクエリで read-only を見分ける。
弊社ではイベント駆動のメッセージングのブローカーとして Kafka(AWS MSK) を利用しています。また、Kafkaへの書き込みとしては Outbox パターンを採用しており、Aurora からKafka への Producer として Debezium を利用しています。
Outboxパターン
Outbox パターンによってアプリケーションからの業務データとメッセージングのトランザクションの一貫性が保証されます。
この記事をお読みいただく方に Debezium 自体のご説明は不要かもしれませんので、サラッと書くと、Debezium は CDC(Change Data Capture) のための OSS です。具体的には、データベースのトランザクションログ(たとえば PostgreSQL の WAL ログ)を読んで更新差分を検知し、Kafka にメッセージを投げ込んでくれます。CDC 自体はデータベースのレプリケーションなどに用いられるものですが、Outbox パターンの実装方法としても最適です。
Debezium は常にデータベースに接続しており FailOver 発生時は自動で接続復旧を試みます。しかし、無事に接続が復旧できた後にエラーになることがあります。今回はこの対処についてお話ししていきます。
Debezium は EKS 上の Pod で動かしており、データベースには Aurora PostgreSQL を利用しています。
CDC(Change Data Capture)概要図
以下のバージョンを利用しています。
(話に関係ないであろうもののバージョンは省略)
Aurora は FailOver 時にも素早く復旧してくれます。とはいえ、クライアントからすると一度接続が切れることには変わりありません。Debezium には接続が切れた際にリトライする機構が備わっており、再接続できた場合は処理を再開します。(v1.9あたりでリトライ周りの実装をいろいろと改善して頂いたみたいです。)
Release Notes for Debezium 1.9
しかし、ここで問題が発生しました。接続が復旧して Debezium が処理を再開する際、前回までの読み込み位置を確認して Replication Slot を更新しようとするのですが、これに失敗して処理を停止してしまいます。必ず発生するわけではなく、開発環境で試していると何回かに1回は発生しました。
エラーが発生する際のログの抜粋は以下です。
2024-01-26 07:12:59,697 ERROR WorkerSourceTask{id=<タスクID>} Task threw an uncaught and unrecoverable exception. Task is being killed and will not recover until manually restarted [org.apache.kafka.connect.runtime.WorkerTask] org.apache.kafka.connect.errors.ConnectException: Unable to update filtered publication <パブリケーション名> for <スキーマ名>.<テーブル名> at ...(省略) Caused by: org.postgresql.util.PSQLException: ERROR: cannot execute ALTER PUBLICATION in a read-only transaction at ...(省略) 2024-01-26 07:12:59,699 INFO || Stopping down connector [io.debezium.connector.common.BaseSourceTask]
PSQLException: ERROR: cannot execute ALTER PUBLICATION in a read-only transaction
さらに困ったことに、Debezium 自体(Javaプロセス)が停止してくれれば新しい Pod が立ち上がってくれるのでまだよいのですが、Java プロセスは生きたままなので CDC のみが停止した状態になってしまい、リカバリ作業が必要になってしまいます。
Aurora は接続用のエンドポイントとしてライター用とリーダー用を用意してくれます。ライター用エンドポイントはライターのインスタンスに、リーダー用エンドポイントはリードレプリカのインスタンスに繋がります。
{クラスタ名}.cluster-xxxxx.{region}.rds.amazonaws.com
{クラスタ名}.cluster-ro-xxxxx.{region}.rds.amazonaws.com
当然、Aurora の挙動としては FailOver 後にエンドポイントの繋ぎ替えまでしてくれます。では、Debezium はなぜ FailOver 後にリーダーインスタンスに接続してしまうのでしょうか。
ひとつには、Java の DNS キャッシュの影響が考えられます。Aurora のエンドポイントのドメインの名前解決をすると最終的にはインスタンスの IP アドレスが返ります。このため、Java は実際にはインスタンスと TCP 接続を結んでいることになります。
DebeziumからAuroraへの接続
Java は DNS の結果を(デフォルトでは)無制限にキャッシュするため、接続のたびに名前解決をすることなく FailOver 後も FailOver 前と同じインスタンスに繋ぎにいったと推測されます。
DebeziumからAuroraへの接続(FailOver後)
Java の DNS キャッシュ対策として、TTL の値を設定して定期的に DNS 問い合わせをやり直すというプラクティスがあります。
// SpringBootアプリケーションで、プロパティで指定する場合 package xxx.yyy.zzz; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.security.Security; @SpringBootApplication public class DemoApplication { // JavaはDNSキャッシュをデフォルトで永続保持してしまいAuroraのfailoverなどに対応できないため、ttlを指定する。 static { Security.setProperty("networkaddress.cache.ttl", "10"); Security.setProperty("networkaddress.cache.negative.ttl", "10"); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
また、コネクションプールのテストクエリを工夫する方法も有効なようです。こちらのブログを参考にさせていただきました。
tech.asoview.co.jp
HikariCPの場合 spring: datasource: hikari: # transaction_read_only=offなら成功、それ以外は失敗するクエリにしておく。1/0とするとSQL自体が成立しないのでrandom()を利用している。 connection-test-query: select case when current_setting('transaction_read_only') = 'off' then 1 else random()/0 end
Consumer や WebAPI は SpringBoot で開発していたため、これらの設定をすることで FailOver 後も接続を失うことなく動作しました。
しかし、今回の Debezium の場合、TTL のオプションを指定しても事象解決しませんでした。また、コネクションプールに関する設定項目は見当たリませんでした。
JDBC 接続の初期化時に実行するクエリの設定はあるのですが、ここに上記のテストクエリを設定しても接続をやり直してくれず、うまく復旧できませんでした。
さらにソースコードを追い始めたところで、こればかりにあまり時間は取っていられないと一旦手を止めました。
視点を変えて異常状態を検知してPodを再起動できないか?という検討に切り替えます。
最初に思いついたのは、エラーログ監視をトリガーに再起動する方法です。エラー内容は判明しているのだからこれを監視しておいて Pod 再起動を呼び出せないかと考えました。しかし、ログフィルターから Pod の再起動コマンドを発行するまでにかなり遠回りすることになるので構築・保守の負担が増えることと、EKS 外部からの操作のための権限設定も必要になりそうなので、もう少しシンプルに解決したいところです。
ところで、Debezium は実体としては Kafka Connect の Connector として動いています。Connector にヘルスチェック機能はないのだろうか、ということで見てみると、
docs.confluent.io
GET /connectors/(string:name)/tasks/(int:taskid)/status
Connector と Task がそれぞれ state を持っているようです。
Debezium(Kafka Conncect)のstatus(平常時)
実際に FailOver をさせて状態遷移を見てみると、FailOver 直後に JDBC 接続を失い接続リトライ中は stateは RUNNING
Debezium(Kafka Conncect)のstatus(FailOver時)
FailOver 完了後に例のごとくリードレプリカに更新にいって例外が起きたところで、stateは FAILED
Debezium(Kafka Conncect)のstatus(エラー時)
これがトリガとして使えそうです。kubernetes には pod の状態を識別するための Probe という仕様があります。
起動したことを識別するための条件
再起動が必要であるかを識別するための条件
(APIなどの場合に)serviceから接続できる状態かを識別するための条件
今回の事象に対処する前は、startup も liveness も 8083 ポートの /
変更前 livenessProbe: httpGet: path: / port: 8083 scheme: HTTP timeoutSeconds: 2 successThreshold: 1 failureThreshold: 1 periodSeconds: 60
今回の調査で、FailOver 起因で Tasks の state が FAILED
変更後 livenessProbe: exec: command: - "sh" - "-c" - "curl -s http://localhost:8083/connectors/<コネクタ名>/tasks/0/status | grep -v '\"state\":\"FAILED\"'" timeoutSeconds: 2 successThreshold: 1 failureThreshold: 1 periodSeconds: 60
これにより、state= FAILED
EKS で Debezium を利用している場合に、Aurora の FailOver 時に自動復旧する事例を紹介しました。ステータスの見分けかたについてはもう少し精査が必要かもしれませんが、稼働状況を監視しつつ対応していこうと考えています。
今回の件がどなたかのお役に立てれば幸いです。
みらい翻訳では、エンジニアを募集しています。
ご興味のある方は、ぜひ下記リンクよりご応募・お問い合わせをお待ちしております。
miraitranslate.com
関連記事
AmazonがAIエージェントでサイバー脅威を予測・対抗する方法
Amazonは競合エージェントアーキテクチャを用いて、機械速度でセキュリティ保護を開発し、通常数週間かかる作業を数時間に短縮する継続的改善サイクルを構築している。
MySQL 9.6が外部キー制約とカスケード処理の方法を変更
MySQLがMySQL 9.6から、外部キー制約の検証とカスケード処理をInnoDBストレージエンジンではなくSQLレイヤーで管理するように変更する。これにより変更追跡、レプリケーション精度、データ一貫性が向上し、CDCパイプラインや混合データベース環境、分析用途での信頼性が高まる。
Grafana、LokiをKafka基盤に再設計しコーディングエージェント向け観測CLIをリリース
グラファナラボスはGrafana 13を発表した。LokiをKafka基盤に再設計し、AI監視機能を搭載する。また開発エージェント向け新CLI「GCX」も提供した。