数百KB程度の画像データのキャッシュが、複数の階層に分かれて数百万ファイルあります。

この中で作成日時が1週間以上前のファイルを定期的に削除したいと思っております。
現在、findコマンドを使って削除を試みたところ、iowaitが酷い状態で、毎日実行したいのに、実行完了まで2日かかる状況です。

こういったニーズを「Linux上」で「軽いシステム負荷」で実現するための方法を教えて頂けませんでしょうか?
Linux上で1週間以内に作成されたファイルのみが保持されることが目的さえ満たせれば、何かしらのOSS製品を組み合わせる方法でも構いません。
なお、HDDを定期的に初期化をする方法ですと、キャッシュが再生成されるまでシステムが高負荷となってしまうため現実的ではありません。

【環境】
Linux CentOS 5.5 x86_64
HDD: SATA 2TB RAID-1(BBU付きのサーバ用RAIDカード)
ファイルシステム:ext3
※ Windows・Mac用のソフト紹介はご遠慮お願いします

回答の条件
  • 1人2回まで
  • 13歳以上
  • 登録:2011/01/06 18:57:38
  • 終了:2011/01/13 19:00:03

回答(1件)

id:typista No.1

typista回答回数359ベストアンサー獲得回数72011/01/07 02:42:55

ポイント60pt

発想を逆転して、1週間以内のキャッシュを残すようなロジックではどうでしょうか?

つまり、

①キャッシュディレクトリごと一旦リネーム(例ではcache4delete)

②キャッシュディレクトリ再作成(cache)

③1週間以内のキャッシュのみ②に移動

④①でリネームしたディレクトリ削除

です。

以下がそれっぽいシェルコマンドですが、findでヒットするファイル名が"./"付きのため

"mv: rename . to ../cache/.: Invalid argument"とエラーメッセージが表示されてしまいますので修正が必要そうです。

このあたりの処理はko-takadaさんのほうが詳しいかも知れませんね。

$ mv cache cache4delete

$ mkdir cache

$ cd cache4delete

$ find . -ctime -7 -exec mv '{}' ../cache/ ';'

$ cd ..

$ rm -rf cache4delete

id:ko-takada

ありがとうございます。逆転の発想ですね。

発想としては良いと思うのですが、以下の懸念があります。

・findコマンドを使って移動する間に、ローカルキャッシュのヒットレートが長時間落ちこむ

・cache4deleteを削除するのにとても長い時間が掛かる

物理ファイルだとそもそも厳しそうなので、数百KBのファイルを数百万(トータル数百GB)の、自然と消えるファイルシステムや、OSS製品は無いものでしょうか。

2011/01/07 15:02:34
  • id:ko-takada
    パーティション(HDD)を定期的に初期化する方法で強引に行う場合、曜日毎にキャッシュを書き込むパーティションを分けて
    4日前の曜日のパーティションを毎日で初期化する手法もあります。ただ、これだと目的のファイルがどこにあるか分からないため、現実的ではないです。

    また、MySQLなどのRDBにファイルパスと日時情報を入れ、別途削除スクリプトを用意したとしても、削除自体に時間が掛かり、厳しそうな印象です。
  • id:y-kawaz
    たぶん回答制限が設定されてるせいで解答欄が表示されない・・・。
  • id:bayan
    数百万ですか。結構なボリュームですね。
    Webサイトですかね。
    以下思ったことをつらつらと。

    キャッシュはどのように作成されるのでしょうか?
    保存先の決定を制御することはできますか。

    たとえばキャッシュ作成日(時)をディレクトリパスに
    含めて格納するとか。
    削除する際に全体を走査する必要がないので、
    軽くなるかなと。

    キャッシュの参照はどのように行われるのでしょうか?

    キャッシュが参照された時点で初めて作成日時を調べ、
    1週間以上前のファイルは無いものとして扱う
    といった運用は許されそうですか?
  • id:b-wind
    ファイルのタイムスタンプを調べるための stat(2) のコールが重いというのはよく言われる事なので、
    ファイルシステムをそのまま使うのは得策ではないね。

    キャッシュを追加・参照するプログラムの方はどの程度変更なのかな?
    あと HW 的にメモリの追加が可能かどうか等。

    ファイルの格納先に memcached や Tokyo Tyrant などの KVS を使用するあたりが王道。
    http://www.publickey1.jp/blog/10/nosqltokyo_tyrantnetvibes.html

    元アプリの改変が難しければキャッシュ保持用のディレクトリを tmpfs にしてしまえば
    単純にファイルアクセスが高速化されるのでそれも一案かとは思う。
    http://www.atmarkit.co.jp/flinux/rensai/linuxtips/277usetmpfs.html
  • id:ko-takada
    > y-kawaz さま
    特に制限は加えていないので、もし良ければコメント欄にてお願い出来ますでしょうか。

  • id:ko-takada
    > bayan さま
    リクエストが来たURLを元にデータを作成し、そのURLから求められるハッシュ値を元に作成されたディレクトリに画像が格納される仕様です。
    とても簡単なプログラムなので、作り替えることも視野に入れています。サーバにメモリは16GB入れていますが、結構重たく困っています。
    キャッシュ作成日(時)をディレクトリパスに含める場合、データベースなどに対応表を保管する必要がありますよね。
    また、そのように分けたとしても結局はディレクトリ毎削除に相当な時間が掛かる可能性が高いです。
    パーティションをいくつも作るのも煩雑なので避けられるなら避けたいと思っています。
  • id:ko-takada
    > b-wind さま
    そうなのです。statコールが重たく、これはファイルシステムをext4にしても、xfsも、nilfs2もあまり変わりません。
    ハードウェア的にはメモリを16GB積んでおりますが、キャッシュファイルが数百GBあるため、memcachedやtmpfsを使ったオンメモリな運用は厳しいです。
    KVSであるTokyo Tyrantで検証を行った所、1MB近いファイルの並列連続挿入時のパフォーマンスダウンが露骨に発生しました。

    ふと思ったのですが、例えばMySQLにバイナリ形式でデータを入れて、定期的に作成日のタイムスタンプを見て一括削除するアプローチが良いかもしれません。
    容量が相当増えるのであれば、MySQL5.5を複数台使ったパーティショニング的なアプローチも使えそうです。

    何か良さそうなアイディアがありましたらご提案頂けると幸いです。
  • id:windofjuly
    うぃんど 2011/01/07 20:04:36
    同じく回答できない7月の風ことwindofjulyです
     
    どの道、案だけですからコメントで結構なのですが、
    findが重いのは周知のとおりですし、古いものを削除したいという目的でもありますから、お金をかけない方法として以下2点+α
    【1】削除対象絞込み方法の変更による高速化案
    cronで日に一度(数度でも可)updatedbで日毎(日時毎)のファイルを作成する
    最新のファイルと1週間前に作成されたファイルの差分を求め、削除対象を得る(locate & diff など)
    【2】サーバ設定変更での高速化案
    キャッシュということですから、トラブル時に少しばかりのファイルが消えても問題は無いとして、ext3をライトバックモードにする
    【3】削除スクリプトをこまめに起動して削除作業を分散化
    日毎ではなく日時毎にして差分だけを対象にすれば一回の作業で消す対象が減るため負荷も減る
    デリートを連続して行うのではなく、時間をおいて少しずつ削除するようにスクリプトを組んで負荷の集中を抑える
    キャッシュが利用される時間を極力さける努力も必要
     
    キャッシュシステムそのものの見直しが高速化に一番寄与するはずなんですけど、そうそうは変更もできないでしょうから横に老いてます。もとい、置いてます(笑)
  • id:y-kawaz
    何故か回答できないのでコメントで回答することにします。

    1. findで古いファイルを探そうとすることの最大の問題は質問者さんの言う通り遅いことです。
    2. findを使う目的は古いファイルの一覧を取得するためです。
    3. 削除対象のファイル一覧さえ高速に取得出来れば多階層ディレクトリのキャッシュは十分成り立つと思われます。

    なので上記の2さえ解決出来れば良いと思いました。
    で、多分質問者さんのニーズに合うんじゃないかと思うのはinotifyを使う方法です。
    inotifyはファイルシステムへの様々なシステムコールが行われたことをカーネルから通知してもらうことが出来る仕組みです。
    inotifyそのものはライブラリなのでそれを使うにはプログラムを書く必要がありますが、コマンドラインから簡単に使えるinotify-toolsというパッケージがあり(多分yumやaptでインストール出来ます)
    その中のinotifywaitというコマンドが使えそうです。

    例えば以下のように実行します。
    inotifywait -q --timefmt '%Y-%m-%dT%H:%M:%S' --format '%T %e %w%f' -m -r -e create,moved_to .

    この状態で別のターミナルから以下のようなファイル操作を行ってみます。
    touch file
    mkdir dir
    touch dir/file2
    mkdir dir/dir2
    mkdir dir/dir2/dir3
    touch dir/dir2/dir3/file4
    mv file dir/dir2/

    すると最初のターミナルでは以下のような出力がされます。
    2011-01-07T22:16:29 CREATE ./file
    2011-01-07T22:16:32 CREATE,ISDIR ./dir
    2011-01-07T22:16:47 CREATE ./dir/file2
    2011-01-07T22:16:55 CREATE,ISDIR ./dir/dir2
    2011-01-07T22:16:58 CREATE,ISDIR ./dir/dir2/dir3
    2011-01-07T22:17:12 CREATE ./dir/dir2/dir3/file4
    2011-01-07T22:17:24 MOVED_TO ./dir/dir2/file

    これを常時ログに取っておけば、そのログファイルをgrepするだけで4日以上前に作成されたファイル一覧が作れるので、古いファイルの削除が簡単にできると思います。

    ちなみにinotifywaitの引数の説明をしておくとこんな感じです。
    -q イベント以外の余分な出力を抑制する
    --timefmt '%Y-%m-%dT%H:%M:%S' --format '%T %e %w%f' フォーマット指定なしだと出力に時間が出力されないので後での検索に便利なように各行に時間が付くようにする
    -m 無期限にモニターを続けるようにする(指定しないと最初のイベントを受けた時点で終了してしまう)
    -r 指定ディレクトリを再帰的に監視する
    -e create,moved_to ファイルの作成と移動先イベントのみを監視する、指定しないと不要なログが大量に出る
    . カレントディレクトリを監視する

    ただ、inotifyを活用する際に一つ問題となるのは、inotifyで監視していない間(プロセスの停止中)に作成されたファイルはinotifyだけでは知ることが出来ないことです。
    なのでinotifywaitプロセスが停止するの隙間が発生するようであれば、その掃除の為に findによる削除も10日に一度とかのペースで十分だと思いますが、併用するのが良いでしょう。
  • id:y-kawaz
    ああと、inotifyで監視する対象ディレクトリが多いようだと以下のカーネルの制限に引っかかる可能性があります。
    # cat /proc/sys/fs/inotify/max_user_watches
    8192

    ですがその場合は例えば以下のように適当に大きな値を設定しておけば良いかと思います。
    # echo 819200 > /proc/sys/fs/inotify/max_user_watches
  • id:typista
    お見受けしたところ、確実に私以上の造詣を
    お持ちなので恐縮ですが、発想だけなら、もしやというものを
    追加で思いつきました。(正確には私の回答に頂いたコメントへの対処療法ですが…)
    ローカルキャッシュのヒットレートが、長時間落ち込むことへの
    対処として、リネーム→1週間以内を、コピーではなく、
    キャッシュディレクトリをシンボリックリンクにしておき、
    新規フォルダへの1週間以内コピーが完了した時点でシンボリックを
    張り直すというのはどうでしょう?
    数百万の不要ディレクトリ削除に時間がかかることは
    この際、止むを得ないのではないでしょうか。
  • id:ko-takada
    情報お寄せ頂きありがとうございます。
    statコールを最小限に済ませられるようなアプローチを探しています。
    例えばionotify的の方法も素晴らしいです。これを採用したプロダクト、lsyncdもとても便利ですね。

    > キャッシュシステムそのものの見直しが高速化に一番寄与するはずなんですけど、そうそうは変更もできないでしょうから横に老いてます。もとい、置いてます(笑)
    再構築も、視野に入れています。

    調査を進めると、nginxだと容量が超えた時点で、期限を過ぎたファイルを自動的に削除するという機能があるそうです。
    メタ情報のキャッシュをnginxが持っていることもあり、もしかするとstatコールを行わずに削除するかもしれません。
    構築がすぐに出来たのでプログラムを変更し、早速試験を行っているところです。
    キャッシュがある程度貯まったらstraceコマンドを使ってnginxの「nginx: cache manager process」のPIDを監視し、Linuxのシステムコールを追跡したいと思います。
  • id:ko-takada
    nginxだとイベントループが続き、定期的にunlinkコールが走っているようです。
    このため、SSDと組み合わせればかなり良さそうなキャッシュストレージとして使えそうです。

    次にキャッシュ容量を大きくして負荷追跡を行っていきます。

    皆様、ありがとうございました。
  • id:ko-takada
    straceコマンドを使って追跡した際のメモを記載しておきます。
    unlinkコールだけなので、かなり高速な処理が期待出来ます。

    ■ 容量制限にぶつかっていない時のシステムコール
    # strace -p 4803
    Process 4803 attached - interrupt to quit
    epoll_wait(16, {}, 512, 2167) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0
    epoll_wait(16, {}, 512, 10000) = 0

    ■ 容量制限にぶつかった時のシステムコール
    # strace -p 1330
    Process 1330 attached - interrupt to quit
    epoll_wait(16, {}, 512, 1688) = 0
    unlink("/var/******/cache/3/f8/18/e137044b07e62f6ff15607353c318f83") = 0
    unlink("/var/******/cache/3/f8/18/57d6992c33e068d6f2aebf1a78518f83") = 0
    unlink("/var/******/cache/3/06/d0/288e35398e0dfa8c92cbbc8bc8fd0063") = 0
    unlink("/var/******/cache/3/06/63/058824be5bc3fdbcf3ef8a5638063063") = 0
    unlink("/var/******/cache/3/06/aa/f3deeaa81062d2d6b3644604425aa063") = 0
    unlink("/var/******/cache/3/06/1e/cdf7265015ce346040f6c1655c41e063") = 0
    unlink("/var/******/cache/3/06/0f/4696c8f4c511db0517e2aee85170f063") = 0
    unlink("/var/******/cache/3/06/eb/33fa96c953a0031d816e9a37e32eb063") = 0
    unlink("/var/******/cache/3/06/42/0ff98179bff6a63cae76c4847f142063") = 0
    unlink("/var/******/cache/3/06/3f/721694b5f2951440b2458d01d523f063") = 0
    unlink("/var/******/cache/3/06/48/34a76ca5062a9d73fd86955dd5648063") = 0
    unlink("/var/******/cache/3/06/da/c7727888e82151ba627d5321427da063") = 0
    unlink("/var/******/cache/3/06/b5/c6da883b6bc1bc7e9f24e7148c3b5063") = 0
    unlink("/var/******/cache/3/09/c0/3cd86bea55a4c0dd2aad9008b39c0093") = 0
    unlink("/var/******/cache/3/09/8a/5a8c9e2c8a09fc43318db6f550d8a093") = 0
    unlink("/var/******/cache/3/09/5d/676ab95e0fd2d7d5b4f40d06a0a5d093") = 0
    unlink("/var/******/cache/3/09/a9/2c7dc2982bfb3514a34f3aef0f4a9093") = 0
    unlink("/var/******/cache/3/09/48/9ff4f1dfe62c352fb1fdd73ccde48093") = 0
    unlink("/var/******/cache/3/09/41/6825654fc27ca61cc10f4b2ab2d41093") = 0
    unlink("/var/******/cache/3/e8/17/465c6f8e6ff580f23cf193fbe5a17e83") = 0
    unlink("/var/******/cache/3/e8/96/3cda50778ecf596b2e0ced7338596e83") = 0
    unlink("/var/******/cache/3/e8/b3/472b051a35ca5c8c45744078e78b3e83") = 0
    unlink("/var/******/cache/3/e8/2b/b1beb55c6ebb0d44d0110ef11712be83") = 0
    unlink("/var/******/cache/3/e8/07/f681620d44d17391bf685ea7c3b07e83") = 0
    unlink("/var/******/cache/3/e8/ed/95e7a9a1f8858ceac0272676950ede83") = 0
    unlink("/var/******/cache/3/e8/e8/332f4281bbcec39be00415ac8cbe8e83") = 0
    unlink("/var/******/cache/3/e8/6d/73ca1ae2d9879b8e06a819fbb5f6de83") = 0
    unlink("/var/******/cache/3/e8/91/bf9a773b48ee9c5cb906a88aece91e83") = 0
    unlink("/var/******/cache/3/e8/93/1df749ea5c34cb3be2dbf452f0f93e83") = 0
    unlink("/var/******/cache/3/e8/1d/a95e1bac3b1056dd0a9fd7245131de83") = 0
    unlink("/var/******/cache/3/7b/4e/8efa8432b81019f362720000c1f4e7b3") = 0
    unlink("/var/******/cache/3/7b/4b/9c76c92c9aa175bc9ac584120204b7b3") = 0
    unlink("/var/******/cache/3/7b/32/8b6321adbd05b3f960cc51f9c3c327b3") = 0
    unlink("/var/******/cache/3/7b/54/59b7fdc44c50577fc9323bc6766547b3") = 0
    unlink("/var/******/cache/3/7b/d5/5dffd7c9b69e13cfed2cb8e9ee4d57b3") = 0
    unlink("/var/******/cache/3/7b/4a/8103c79af28ead18e715631a0364a7b3") = 0
    unlink("/var/******/cache/3/7b/72/982a3f8f11b628666293058a2ff727b3") = 0
    epoll_wait(16, {}, 512, 10000) = 0
    unlink("/var/******/cache/3/7b/1b/8f44af3e33ff46a41cf785ffaa71b7b3") = 0
    unlink("/var/******/cache/3/7b/be/42c8342f09cd11636d8a37901e2be7b3") = 0
    unlink("/var/******/cache/3/7b/71/adeba6b7b9c7aaa4b7b0629f4ef717b3") = 0

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません