15:00カテゴリ:技術ハートビーツ最年長エンジニアの滝澤です。以前、弊社CTOにシニアおっさんエンジニアから若手エンジニアに向けて何か書いてくれと言われた気がしたので、アトミック(atomic)なファイル操作について3編に分けて紹介します。この内容は弊社の社内勉強会で話した内容をまとめ直したものです。そのファイル、安全に更新できていますか?(アトミックなファイル操作:前編)←今回そのファイル、安全に作成できていますか?(アトミックなファイル操作:中編)そのファイル、安全にロックできていますか?(アトミックなファイル操作:後編)今回は「みなさん、安全にファイルの更新ができていますか?」ということについて、考えてみましょう。そのファイル、安全に更新できていますか?あなたはあるサーバ上のファイルの更新を依頼され、もらったファイルをサーバ上でコピーして上書きしました。して、データに異常が発生したので調べて欲しいと言われました。何が起きたのでしょうか。調べてみると、サーバ上にあるファイルは正常でしたが、アプリケーションに読み込まれたデータが不完全でした。アプリケーションがデータファイルを読み込んでいる途中でファイルを上書きコピーをしてしまったようです。何かいけなかったのでしょうか。このケースではアプリケーションを停止できないとします。ファイルを上書きコピーしたときのファイルの状態を追ってみましょう。完全(新しいデータ)コピー中ではデータが不完全な状態にありました。ファイルを上書きコピーしたのがいけなかったようです。利用中のファイルを更新するときにはアトミック(atomic)にファイルを更新すべきでした。それでは、アトミックにファイルを更新するとはどういうことかを考えてみましょう。訳されており、トランザクション処理のACID特性の'A'として知られています。英和・和英辞書の英辞朗では「トランザクションが完全に成功するか、またはエラーが起きたら全部取り消し、中途半端に終わらない性質」「一連の処理が不可分の一体として成功・失敗のいずれかになり、かつ処理途中の状態にほかからアクセスできないこと」という説明があります。ファイル操作においてアトミックであるということは、ファイルの更新途中の中途半端な状態ではなく、更新前の状態か更新が完了した後の状態でのみアクセス可能であることを意味します。新規にファイルを作成するときには、ファイルの作成途中の中途半端な状態ではなく、ファイルが存在しないか、あるいは作成済みの完全なファイルが存在するかのどちらかになります。アトミックにファイルを更新するにはアトミックにファイルを更新するには、rename()というファイル名を変更するシステムコールを使います。mvコマンドはこのrename()を使ってファイル名の変更を行っています。rename"を実行するとrename()のマニュアルが表示されますので一度読んでみてください。*new);変更後のファイル名のファイルnewがすでに存在するときには、ファイルoldがファイルnewを「置き換え」ます。変更前に存在していたファイルnewを「書き換え」ているのではないのです。内部的に行われているのはファイルnewが存在しているディレクトリエントリの書き換えです。ディレクトリエントリの書き換えはアトミックに行われます。そのため、rename()実行前にファイルnewを開いていれば、rename()実行後も更新前のファイルを閉じるまでは操作し続けることができます。rename()実行後にファイルnewを開けば、更新後のファイルを開くことになります。rename()の利用例ここでrename()を利用している例として/etc/passwdの更新について紹介しましょう。して、あなたは/etc/passwdファイルをエディタで直接開いて更新していませんか。複数の人が同時にファイルを編集してしまったら、ファイルが壊れる恐れがあります。編集途中で中途半端な状態の内容でファイルを保存してしまったら、UNIXアカウント情報が正しくない状態が発生する恐れもあります。編集していたら、なんて恐ろしいことをしていたんだと反省してください。/etc/passwdの編集を安全に行うためのコマンドはたいていのシステムで用意されているのでそれを使ってください。Linuxディストリビューションや*BSDの場合だとvipwコマンドが用意されていると思います。このvipwコマンドは次のことを行います。(Linuxディストリビューションでよく使われているshadowパッケージのvipwコマンドの場合)ロックをかける。/etc/passwdファイルのコピー/etc/passwd.editを作成する。コピーされたファイル/etc/passwd.editを指定してエディタを起動する。(作業者がエディタで編集を行い保存し、エディタを終了させる。)古いバックアップファイル/etc/passwd_を削除する。/etc/passwdファイルのバックアップファイル/etc/passwd_をハードリンクで作成する。rename()により、保存したファイル/etc/passwd.editで/etc/passwdを置き換える。以上のような処理により、複数の人が同時に編集するのを防ぎつつ、安全に/etc/passwdファイルを更新することができます。下から3つの処理のソースコードは次の内容です。{rename()の制限rename()を使うことができるのは、変更前のファイルのファイル名と変更後のファイルのファイル名が同じファイルシステム上にある場合のみです。異なるファイルシステム間ではrename()を使うことができません。mvコマンドの制限mvコマンドはrename()のコマンド実装です。rename()は同じファイルシステム上にある場合のみ利用できるので、mvコマンドは元々は同じファイルシステム上にある場合だけしか使うことができませんでした。ちなみに、皆さんが普段使っている機能拡張されたmvコマンドではファイルシステムをまたがって使うことができます。ファイルシステムをまたがっているときには、rename()を使うのではなく、ファイルのコピーと削除を行っているのです。次のコマンドを実行するとしましょう。/oldpathと/newpathは異なるファイルシステムとします。/newpath/to/filemvコマンドは、ファイル/newpath/to/fileが存在しているときには、ファイル/newpath/to/fileを削除します。次に、ファイル/oldpath/to/fileを/newpath/to/fileにコピーします。ファイル/oldpath/to/fileを削除します。このときのmvコマンドは実質的に次のコマンドと等価と考えてよいでしょう。/oldpath/to/fileもちろん、この操作はアトミックではありません。mvコマンドによるアトミックなファイルの更新例mvコマンドでファイルを更新する例で確認してみましょう。2つのファイルnew.datとold.datを用意します。old.datlsコマンドでiノードを確認します。old.datmvコマンドを使ってold.datの内容でnew.datを更新します。new.dat再びiノードを確認します。new.datnew.datのiノードが書き換わっているのがわかるでしょう。new.datの内容を確認します。oldnew.datの内容はold.datのものになっていることがわかります。このように、ディレクトリエントリの書き換えだけが行われているため、アトミックにファイルの更新ができ、ファイルnew.datは常に完全な状態で存在します。ちなみに、cpコマンドを使って上書きコピーをした場合についても確認してみます。先ほどと同じようにnew.datとold.datを作成し、iノードを確認します。old.datcpコマンドを使ってold.datの内容でnew.datを更新し、用済みのold.datを削除します。old.dat再びiノードを確認します。new.datnew.datのiノードが変わっていないことがわかるでしょう。new.datの内容を確認します。oldnew.datの内容はold.datのものになっていますが、上書きされているため、アトミックではありません。このnew.datに対してリダイレクトにより更新してみましょう。newこのときもiノードが変わらないことが確認できるでしょう。同じファイルを書き換えているため、アトミックではありません。スクリプト言語の場合スクリプト言語を使う場合でも、rename()を内部的に使っている関数やメソッドがあるのでそれを使えばアトミックにファイルを更新できます。rename()という名前になっています。Pythonの例を次に示します。osモジュールのrename()関数を使います。main()シェルスクリプトによるファイルの更新例シェルスクリプトでアトミックにファイルを更新する例を紹介します。ここで紹介するスクリプトは次の内容になります。アップロードしたファイルで指定のファイルを更新します。このときアップロード場所のファイルシステムは指定のファイルのファイルシステムと異なります。0主要な部分について説明します。更新前のファイルと更新対象のファイルのファイル名を次のように変数に定義します。一時的に利用するユニークなファイル名を生成します。ユニーク性が保てないと、同じ処理を行う他のプロセスがあったときにはデータが破壊される恐れがあります。ファイル名にプロセスIDやUNIX時間などを付ければそこそこユニーク性を保てるでしょう。+%s)このとき次のようなファイル名が生成できます。/path/to/data/file.dat.4726.1380907208ファイル名のユニーク性を向上させるために、疑似乱数生成器/dev/urandomを使うと次のようになります。40)このとき次のようなファイル名が生成できます。更新元ファイルを更新先ファイルと同じファイルシステム上にコピーします。パーミッションを更新先ファイル名に合わせます。mvコマンドによりファイルを更新します。${TARGET_FILE}以上の処理によりアトミックにファイルの更新を行うことができました。最後に今回紹介した内容は基本的な内容ですが、若手エンジニアには意外と知られていない気がします。知らなかった人はアトミックなファイル操作について考えてみてください。「rename()の利用例」を追加。dstをハードリンクした上でsrcをremoveするという実装になっていた。