yigarashiのブログ

学んだことや考えていることを書きます

embulk-input-mysqlでCommunications link failureになる時の原因と解決方法

最近Embulkを使ってMySQLのデータをBigQueryに転送する仕事をよくやっています。データに基づく意思決定を促進するための大事な仕事です。これをやっている時にたまに以下のようなエラーに出会います。

org.embulk.exec.PartialExecutionException: java.lang.RuntimeException: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
 
 The last packet successfully received from the server was 5,024 milliseconds ago.  The last packet sent successfully to the server was 5,020 milliseconds ago.

この記事ではこのエラーの原因と解決方法を紹介します。

原因

結論から言うとMySQL Connector/JのsocketTimeoutオプション1によるタイムアウトが原因です。socketTimeoutは、このオプションで指定された秒数を超えてサーバーからのデータ送信が確認されない場合にタイムアウトにするものです。以下のようなEmbulkの設定で簡単に再現することができます。この例ではsocketTimeoutを5秒に指定してMySQL側で10秒sleepしています。

in:
  type: mysql
  host: ...
  user: ...
  database: ...
  table: ...
  select: sleep(10)
  socket_timeout: 5

out:
  type: stdout

この場合はMySQLからのレスポンスが遅いためにタイムアウトが発生しています。Embulkで実際にデータを転送する際には、クライアント(つまりEmbulk)側の処理が遅いために次のfetchを実行できず、結果としてデータ送信が発生せずにタイムアウトする場合もあります。

とはいえ、デフォルトのタイムアウトは30分とかなり長めに設定されているので、このエラーが発生するのは極めてスペックの低い環境で実行している場合に限られそうです。また、今回紹介した原因とは別にCommunications link failureが起こるケースも様々あるようなのですが、今回はそちらについては触れません。

解決方法

原因が分かってしまえば解決するのは簡単です。socketTimeoutよりも短い間隔でMySQLからデータが送信されるようにしてやれば良いのです。以下のように様々な方法が考えられます。

(1) socketTimeoutを伸ばす

embulk-input-mysqlのデフォルトのsocketTimeoutは1800秒です2。当然これを伸ばせばタイムアウトしづらくなります。さらに0を指定するとそもそもタイムアウトしなくなります。とはいえ、TCPコネクションが確立されたあとにサーバーがダウンしたケースもsocketTimeoutでカバーされるという意見もある3ので、バッチ処理を適切に失敗させるために適度なタイムアウトを設定するのが無難かと思います。Fargateで実行して気づかずに永遠に実行していたといった事故が起こりかねないと予想されます(他にも失敗させる機構はあるかもしれませんが)。

(2) fetch_rowsを小さくする

MySQL Connector/Jでは一回の通信で何件のレコードをfetchするかを指定することができます。embulk-input-mysqlにもfetch_rowsというオプションがあり指定することができます(デフォルトは10,000) 。これを小さくすると、一回のfetchにかかる時間や、embulk側でfetchしたレコードを処理して次のfetchを行うまでの時間が短くなるので、タイムアウトしづらくなります。当然サーバークライアント間の往復数は増えるのでオーバーヘッドは増加します。

(3) Embulk実行環境のスペックを上げる

Emulk側で処理して次のfetchを行うまでに時間がかかっている場合に有効です。ECSで実行しているならContainer Insightsを見てリソースが100%に当たっていないかを確認すると良いでしょう。何件か実行した感覚としては、Embulkを実行するときはCPUリソースが足りなくなりがちです。

(4) Embulkの並列数を下げる

Embulkを実行する際は、Digdagのようなワークフローエンジンと組み合わせて並列に実行することも多いように思います。このとき、並列数が多すぎるとCPUを食い潰してタイムアウトが発生することがあるようです。ワークフローエンジンの設定で並列数を調整してみましょう。

(5) データベースの当たり前のポイントを確認する

インデックスが効かない異常なクエリを投げたりロックが取られていたり、DB側のリソースが圧迫されてデータを返せなかったりすると当然タイムアウトする可能性があります。データベースからデータを読み出す際の一般的な注意点は踏襲しましょう。

最後に

デフォルトのタイムアウトが30分とかなり長めなので、調べながらホントに起こってたのか?という気持ちにはなったのですが、ひとまず手元で実験した結果や一般的に言えそうなことをまとめてみました。基本的にはEmbulkというよりJDBCMySQLの知識といった様子でしたが、大規模データを転送する場面では特に起こりやすいエラーではあるでしょう。ちなみに、一番オススメの解決策は(3)で、クラウドで実行しているならインフラ費用で殴って解決しましょう。実行時間も短くなってお得です。