TT Lab
はじめる
学ぶ 学習パス コース

EAI 中間層をつくる

ファイル連携の事故は境界で起きる

TT Labで続きを見る

一言でいうと

ファイル連携は古い方式ではなく、大量処理・精算・突合の標準的な方式です。安全なファイル受信の核心は3つあります。書き終えたファイルだけを取得する(完了フラグ)。取得したものが送ったものと同じか確認する(バイト数・チェックサム・トレーラー)。2回実行しても結果が同じになる(リモート側のマーク・ファイル単位の取り込み記録)。そして最後に、自社の元帳と突き合わせます(突合)。

なぜ必要なのか

保険請求の数万件、カード売上伝票、給与振込明細、日次締めの精算。こうしたものは、1件ごとにAPIを呼び出しません。1日分を1つのファイルにまとめて決まった時刻に渡し、受け取る側は一括で取り込みます。相手の機関がAPIを公開してくれないことも多く、何より、ファイルはその日の境界を明確にします。「9月23日の請求分」が1つのファイルなので、件数と合計を突き合わせて、違っていればファイルごと取り直せばよいのです。

ところがファイル連携の事故は、ほとんどすべて境界で起きます。相手がまだ書いているファイルを取得して、半分だけ取り込んでしまう。転送中に壊れたファイルに気づかず入れてしまう。バッチを再実行したら昨日のファイルをまた入れてしまい、請求が二重に計上される。取り込みはできたのに、自社の受付元帳と3件違っていることを、1か月後の決算で初めて知る。このモジュールのステップは、この4つの事故と1対1で対応しています。

どう動くのか

SFTP。SFTPは、SSH接続の上で動くファイル転送プロトコルです。本番ではsftp user@hostで相手のサーバーにつなぎ、このとき、SSHが相手サーバーのホスト鍵を検証します(初めて見る鍵を確認せずに受け入れる設定は、中間者攻撃を許します)。自動化バッチは対話的に質問と回答ができないので、-b <배치파일>(プレースホルダーはバッチファイルです)でコマンド一覧を渡し、バッチモードでは、コマンドが1つ失敗すると、そこで止まります(sftp(1))。

このラボはPod 1つの中で動きます。パートナーのSFTPサーバーを、SSHサーバー(sshd)ごと別に立てる代わりに、sftpの-Dオプションを使います。このラボで学ぶのはSSHサーバーの運用ではなく、ファイルをやり取りするルールだからです。マニュアルは-Dを「sshを介さずに、ローカルのsftpサーバーに直接接続する」と説明しています。SFTPプロトコル(一覧・取得・名前の変更)は本番と同じで、省いたのはSSHの接続・認証・ホスト鍵の検証です。本番のバッチで使うときは、この3つが必ず加わることを忘れないでください。

完了フラグ。ファイルをすべてアップロードしたあとで置く、小さな目印ファイルです。受け取る側は、フラグのあるファイルだけを取得します。フラグにバイト数とsha256を書いておけば、転送中の破損まで検出できます。SHA-256は、入力が1ビット違うだけでまったく別の値が出るハッシュなので、サイズは同じで内容が壊れているケースを見分けられます。

原子的な名前の変更。受け取るファイルを最終的な名前でいきなり書くと、受信中に別のバッチ(取り込み)がそのファイルを拾ってしまうことがあります。一時的な名前(.part)で受け取りきって検証してから、最終的な名前に変更します。POSIXのrename()は、同じファイルシステム内で対象の名前を原子的に変更します。ほかのプロセスは、古い状態か新しい状態を見るだけで、途中の状態は見ません(rename)。別のファイルシステムへ移すmvは、コピーして削除なので、この性質がありません。そのため、一時ファイルは最終的なディレクトリと同じ場所に置きます。

再実行しても同じになる。バッチは必ず再実行されます(失敗のあと、障害復旧のあと、誰かの誤操作で)。取得したファイルは、リモート側で名前の末尾に.fetchedを付けてマークし、取り込みはファイル名単位で記録(load_log)して、同じファイルを2回入れません。取り込みはファイル1つを1トランザクションにし、すべて入るか、何も入らないかにします。途中で請求番号が重複する行が出たら、前の行まで巻き戻します。半分だけ入ったファイルは、再実行でも直せません。

ヘッダー・トレーラー。ファイルの中にも境界があります。H(ヘッダー)はどんなファイルか(日付・送受信機関)を、T(トレーラー)は何件で合計がいくらかを書きます。受け取る側は、D行を自分で数えて足し、トレーラーと突き合わせます。トレーラーがなければ、ファイルが最後まで届いていません。行の長さはバイトで測ります。ハングルの名前が入った行がUTF-8で保存し直されると、文字は同じに見えても、行が長くなります(モジュール1と同じ落とし穴です)。

突合。取り込みが終わりではありません。パートナーのファイルと自社の受付元帳を請求番号で合わせ、両方にあって金額が同じもの(一致)、パートナーにだけあるもの、自社にだけあるもの、金額が違うものに分けます。不一致は、たいてい業務上の出来事です。こちらが受付を漏らした、あるいはパートナーがキャンセル分を除かなかった、などです。突合の結果は人が見るレポートなので、請求番号順に並べ、金額は両方を見せます。

現場での姿

最もよくある事故は、「20時前にアップロードする」という約束だけを信じて、20時に取得しに行くバッチです。ある日パートナーのサーバーが遅く、20時01分に書き込みが終わり、こちらは半分のファイルを取り込んでしまいます。完了フラグなしに時刻で同期すると、必ず一度はこうなります。2つ目は再実行の事故です。障害復旧の担当者が「念のため」バッチをもう1回実行し、取り込み記録がなかったため、同じファイルが2回入りました。3つ目は、突合をしないことです。連携は「エラーなく終わった」で成功を判断しますが、業務は「数字が合っている」で判断します。最後に、本番のSFTPバッチで、ホスト鍵の検証をオフにする設定(StrictHostKeyChecking=no)を、手軽さのために入れてしまうことがありますが、それは相手のサーバーを確認せずに請求データをやり取りするという意味です。

次のラボですること

パートナーフィクスチャでリモートディレクトリを作り、SFTPバッチで一覧を見ます。そのあと受信スクリプトfetch.shを育てます。フラグのあるファイルだけ、チェックサムの照合と隔離、一時的な名前とリモート側のマークです。続いてヘッダー・トレーラーの検証器check.py、ファイル単位の取り込み器load.py、元帳突合recon.pyを作り、当日のファイルで突合レポートを出します。