ブログ
Boring Engineering #2:Linuxのunlink / renameのアトミック性、その美しさ
状況
アルドネットでは、既存の組込みアプリケーションをクラウドシステムに接続するという開発案件がありました。
その組込みアプリケーションは、複数のセンサをローカルで管理するもので、センサの設定・管理から、データの可視化・管理までを行う充実したUIを備えていました。
この製品は、すでに長年にわたって現場で安定して使われてきたものです。
お客様からのご要望は、センサを一元的かつ遠隔から管理できるようクラウド接続機能を追加すること。ただし、ハードウェアは追加せず、既存アプリケーションへの変更も最小限に抑えることが条件でした。
分析
既存アプリケーションはC++で開発されており、QtベースのUIによるユーザー操作と、センサとのリアルタイム通信の両方を処理するイベント駆動型のアーキテクチャでした。
既存コードやアプリケーションの実行時動作への影響をできるだけ小さくするため、クラウド接続機能は別プロセスとして実装することにしました。
つまり、長年安定して動いてきたものをできるだけ乱すことなく、新しいクラウド機能を追加したかったのです。
OSのプロセス分離を利用することで、通信遅延やデータ転送の集中など、クラウドやネットワークに起因する実行時の問題が、十分に最適化され実績のある既存アーキテクチャに影響しないようにする、という考え方です。

選択
マルチプロセス構成を採用したことで、次に必要となったのがIPC(Inter-Process Communication:プロセス間通信)方式の選択です。つまり、既存アプリケーションと新しいクラウド接続プロセスがデータをやり取りするための、シンプルな方法が必要でした。
大きな制約の一つは、既存システムに新しいサードパーティ製パッケージを一切追加しないことでした。およそ15年前の環境にすでにあるものだけで実現する必要がありました。
さらに、既存システムと同じプログラミング言語、つまりC/C++を使うことも求められていました。
新しく追加するコードをできるだけ保守しやすくするため、メッセージキューや共有メモリなどのIPC方式ではなく、ファイルベースのIPCを選択しました。
ファイルベースIPCには、いくつものメリットがあります。
第一に、ファイルの読み書きは非常に自然で、実装も簡単です。 通信用のリソースを開いたまま維持する必要もありません。開く、書く、閉じる。それだけです。
第二に、ファイルの読み書きはC/C++における最も基本的な処理の一つであり、C/C++プログラマが最初に学ぶことの一つです。 誰もが身につけている基本知識として扱うことができます。
第三に、ファイルベースIPCでは、データが永続ストレージへ安全に書き込まれていれば、プロセスの再起動やシステムの再起動をまたいで自然にデータを保持できます。
そして、ここにシンプルさの魔法がある
ある意味、ファイルベースIPCはデータ共有の一種です。そして、複数のプロセス間でデータを共有するときに問題となるのが、読み書きの競合です。
読み手には、書き込み途中の不完全なデータではなく、常に正しいデータを読んでもらう必要があります。
そこで力を発揮するのがLinuxのファイル操作、特に rename() のアトミック性です。
データを生成する側(以下、Producer)は、必要なだけ時間をかけて一時ファイルに新しいデータを書き込むことができます。
ファイルが完成したら、単純な rename() によって、データを読み取る側(以下、Consumer)から見える状態にします。
data.tmp に書き込む
data.ready にリネームする
Consumerは、ready状態のファイルが存在するかを定期的に確認します。
Consumerからファイルが見えるのは、rename() が完了してからです。その後、ファイルを読み込んで処理し、処理が完了したら unlink()、つまりファイルを削除します。
より一般的には、Producerはタイムスタンプなどを含む名前で一時データファイルを書き込み、準備が完了した時点でrenameします。
Consumerはそれらを検出し、読み込み、処理し、最後に削除します。このようにすると、ファイルを共有するフォルダそのものがシンプルなキューとして機能します。Producerが新しいデータをキューへ投入し、Consumerがファイルをキューから取り出して処理するイメージです。
この構成では、Producerだけがデータを書き込み、Consumerだけがデータを読み込み、削除します。

rename() や unlink() といったLinuxの基本的なファイル操作が持つアトミックな動作と、ごく単純なファイルの読み書きを組み合わせることで、既存アプリケーションとクラウド接続プロセスの間に、堅牢なプロセス間通信の仕組みを構築できます。
既存アプリケーションへの変更は、データをオペレータUIだけでなくクラウドとの間でもやり取りできるようにするため、いくつかの重要な箇所に最小限加えただけです。
既存アプリケーションにファイルの読み書きを追加すること自体は非常に単純で、既存設計にも影響を与えませんでした。
ネットワークに関する複雑な処理は、すべて新しいプロセス側に分離しました。
データベースは不要。新しいIPCフレームワークも不要。追加のサードパーティ製パッケージも不要。
必要なのは、ファイルとLinuxの基本的な操作だけです。
まとめ
今回のプロジェクトでは、ファイルの読み書きと、アトミックな rename()、そしてシンプルな unlink() を組み合わせることで、既存のC/C++アプリケーションへの変更を最小限に抑えながら、クラウド機能を追加することができました。
この方式がうまく適合したのは、このシステムでは5〜10秒程度の遅延が許容できたからです。
一方、数ミリ秒以内にクラウドへ通知しなければならないようなハードリアルタイムシステムであれば、メッセージキューなどのイベント駆動型IPCの方が適しています。
重要なのは、ファイルベースIPCが常に最善の方法だということではありません。もちろん、そうではありません。
ただ、ときにはOSが昔から備えているシンプルな仕組みが、私たちの必要としていることをそのまま実現してくれることがあります。
それこそが、Boring Engineeringです。