読者です 読者をやめる 読者になる 読者になる

覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。 twitter: @yyoshikaw

Javaで仮想ファイルシステムを作る時に考慮すべき点

以前のエントリで仮想ファイルシステムを構築するためのfuseと、fuseからJavaにブリッジするためのfuse-jnaについて紹介しました。

実際にfuseとJavaを使って仮想ファイルシステムを構築する際に気にすべきポイントや懸念点があります。
それについてメモしておきます

(※以下内容は、仮想ファイルシステムに求められる性能要件や実行環境によっては全く気にする必要のないポイントもあります)


大量の呼び出し要求が発生する

呼び出し元がファイルのちょっとした操作をしている場合でも、仮想ファイルシステム側には相当な数の呼び出しが発生します。
一つ一つの呼び出しをどれだけ高速にこなすかが一つのポイントとなります。


気にすべき時間のオーダー

基本的にファイルシステムというからには、求められるファイル操作が高速に動作する必要があると考えられます。
秒やミリ秒のオーダーでは考えずに、各種内部処理が、基本的にマイクロ秒単位以下(できればナノ秒単位)で動作するように検討すべきです。


ライブラリの選定

fuseとJavaを組み合わせる場合、fuseとJavaをブリッジするためのライブラリが必要になります。
以前のエントリではfuse-jnaを紹介しました。
基本的にJNAを利用する関係上、必ずしも高速に動作しません。呼び出し元がファイル操作を行ってから、仮想ファイルシステムの実装側に処理が到達するまでのレイテンシが大きくなります。
といっても数十マイクロ秒とかそういったレベルのオーダーです。が、大量な呼び出しの一回一回にこういう遅延が発生することで全体として結構な遅さとなってしまう場合があります。
できればJNRでのブリッジを検討したほうが良いです。


GC

Javaの肝でもあるGC(ガーベジ・コレクション)。
これが、ファイル操作中に発生すると呼び出し元には謎の遅延にしか見えません。
基本的にマイクロ秒単位で処理を応答していても、ミリ秒単位でGCが発生すると相当遅れた状態になってしまいます。
可能な限りGCが発生しないように考慮が必要です。
となると、できるだけヒープを使わないほうがいいということにも繋がってきます(何のためにJavaでやってるんだろう・・・と思いたくもなったりしますが)


JITコンパイル

Javaは実行中にJITコンパイラによりJITコンパイルされて最適化されることによって高速に動作するようになります。
JITコンパイルされるために、対象の処理が一定回数呼び出される必要あります。
仮想ファイルシステム起動直後は遅いが、特定のファイル操作を一定回数やっていると速くなる・・・というのではまずい場合もあると思います。
呼び出し元のファイル操作がなくても、何らかのウォームアップ処理を裏で実行してJITコンパイルが実行されるようにし、最適化がされるようにする必要があります。


永続化領域へのアクセス

ファイルの実体(データ)はもちろん永続化しますが、ファイルの属性情報も何らかの形で永続化領域に管理しなければならないケースも多々出てきます。
たとえばファイルの属性情報をRDBMSに管理するということも考えられます。

が、ファイルの属性情報の取得(getattr)という操作は仮想ファイルシステムの中でも最も頻繁に呼び出され、
属性情報の取得の度に直接RDBMSにアクセスするのはかなり低速になる原因になります。
(相当なIO待ちが発生する可能性があります)

そのため、以下を検討する必要があります

  • 永続化領域を何にするか。仮にプロセス間通信が発生する場合レイテンシが問題にならないか?
  • 属性情報の取得は基本的に永続化領域から直接ではなく、何らかのキャッシュ領域から取得できないか?


メモリの消費

ファイルの属性情報をキャッシュに持ったほうがいいのではないか、と先ほど書きましたが、
たとえば、メモリをキャッシュとして利用する場合、キャッシュする量が増えれば増えるほど当然のようにメモリを消費することになります。
そもそもJavaアプリケーションの場合、基本的にメモリをどうしても大きく消費してまいますのでキャッシュの件と合わせてメモリのリソースが問題ないのかは検討が必要です。
また、キャッシュをヒープ内に持つとGCの件も絡んでくるので問題が大きくなりやすいです。基本的にヒープ外のメモリでキャッシュは持ったほうが良いです。


Java標準のコレクションを使うべきか?

Javaでアプリケーションを組むにあたって、Javaのコレクションを利用するのは当然のことです。
が、Java標準のコレクションは、メモリを結構消費したり速度的な面でも問題があるなどのことがよく言われています。
仮想ファイルシステムを組むに当たっては、もっと性能の良いサードパーティ製のコレクションライブラリを利用した方が良いかもしれません。


非同期にできる部分は非同期に

大量の呼び出しがあること、できるだけ高速に呼び出し元に返す必要があること等を考慮すると、全ての処理を同期型でやっていたのではパフォーマンス的に問題になることが多々あります。
要件によりますが、非同期にできる部分は可能な限り非同期にすべきです。
処理をキューイングするなどの処置は各所で検討すべきです。
ただし、そのキューは揮発性でよいのか永続化しないといけないのか、キューが増えた際にメモリやディスクのリソースを圧迫しないか等の検討も必要かもしれません。


ロギング

アプリケーションを構築をする上で運用時の調査などのためにロギングは必須です。
これは、仮想ファイルシステムでも同様だと思います。
ただし、すでに何度も述べたようにファイルシステムへの呼び出しは大量に行われます。
そのため、単純に各処理でロギングを行うと、とんでもない量のログになってしまいます。
たとえば、ログをファイルに書き出しているとすると、ログ自体がディスクのリソースを占有するような可能性もでてきます。
また、同期型のロギング処理を行っていると、ロギング自体がファイル操作の呼び出しの速度を劣化させてしまうことにもつながります。

そのため、基本的には非同期型でのロギングで、可能な限り的を絞ったログ出力を行う というのが最低限考慮すべきポイントです。