「このフォルダにファイルが入ったときにアレしたい」的な機能。
結構要求されがちだが、システムとしては個人的に「絶対に避けるべき」レベルの機能なのでその理由をメモしておく。
記憶装置は入力装置ではない。
ファイルシステムはデータの永続化を目的としたものであり、イベント駆動型の処理には向いていない。
そのため、「フォルダにファイルが入ったら何かする」という設計は、信頼性・パフォーマンス・スケーラビリティの面で問題を引き起こしやすい。
Q. でもそういう機能を搭載した製品あるじゃん。
A. あれらも厳密にファイルの変更をリアルタイムに監視できているわけではないのです...(Google DriveやOneDriveのようなサービスはファイルシステムのイベントを監視しつつ、変更をすぐにアップロードするのではなく、一定の間隔でハッシュ比較などを行いながら同期する方式になっている。
つまり、内部的にはバッチ処理とイベント駆動を組み合わせた設計になっており、常に即座に処理されるわけではない。)
想定している機能
「特定のディレクトリに画像ファイルが入ったら処理したい」
や
「特定のディレクトリ配下のテキストファイルをベクトルDBと同期したい」
(主にファイル更新のイベントに関する話で、簡易で厳密でないバッチ処理はアリかも...?)
理由まとめ
信頼性の無さ
「ファイルの書き込みがいつ終わったか」が書き込み側の都合である。
処理側のプロセスが完了を知る手段が存在ない。
どのタイミングで処理を発火させたら良いかわからず、非効率な処理になる。
書き込み方が千差万別
ファイルの書き込みは1度で終わらないことも多い。
例えば、以下のPowerShellでディレクトリを監視した場合、メモ帳で保存すると1回発火したが、VSCodeだと2回発火した。
書き込みサイズによると思うが、複数回flushすることは珍しくない。
$watchTarget = "./watch_target"
$filter = "*.*"
$includeSubdirectories = $true
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $watchTarget
$watcher.Filter = $filter
$watcher.IncludeSubdirectories = $includeSubdirectories
$watcher.EnableRaisingEvents = $true
$onChanged = Register-ObjectEvent $watcher "Changed" -Action {
Write-Host "File changed: $($eventArgs.FullPath)"
}
$onDeleted = Register-ObjectEvent $watcher "Deleted" -Action {
Write-Host "File deleted: $($eventArgs.FullPath)"
}
while ($true) {
Start-Sleep -Seconds 1
}
速度
そもそもストレージの読み書き速度はRAMに比べてとんでもなく遅い。
誇張抜きに1000倍ほど違う。
ハードウェアの負荷
SSD、HDDは書き込み回数に限度がある。
監視プログラムが常駐していない時を考慮するのが大変
OSのファイルシステムなので、監視していないタイミングでユーザーに変更される可能性がある。
その場合
- とりあえず全部読む
- 処理時に記録しておいたハッシュと突き合わせて変更があったか調べる
の2ステップが必要になる。
全部読むというのか...?大きくなりうるディレクトリを...?
スケーラビリティ
分散ストレージではないので、ファイルが大量になるほど処理が遅くなる。
数GBでもレスポンスが悪くなり、実用的ではなくなると思われる。
解決策
APIがあればそれを使う
書き込み側のシステムにAPIがあればそれを使う。
道具は正しく使おう!
オブジェクトストレージを使う
S3, Cloud Storageなど、スケールできるストレージを使用する。
入口を制限する
書き込み -> 処理
ではなく
処理 -> 書き込み
にすれば幾らかマシなので、専用のUI、APIを用意する。