2008/04/05

Webアプリのバッチ処理

こんばんは。なにかとスタンドアローンのじょにです(←ちょっとヤケ)。

普段私はバッチ処理系なものを作ることが多いので、大量データを一気に加工するシーンはよくあります(でもMax60万件くらい。まーそんなもんです。Accessなので)。バッチ処理といえども、流して帰る…、という悠長なことはなかなかやってはいられませんので、なんであれ、待ち時間(処理時間)はなるべく短くすることは常に気にかけています。その際、まず私が考えることは、アプリからDBへのアクセス回数を減らこと。

DB設計も絡んでくる話だとは思いますが、手っ取り早い手段としては、

  • コード等が入っているマスターテーブルをハッシュに入れて、いちいちエラーチェックやら情報付加の為にマスターテーブルにアクセスしにいかない。
  • ぶんぶんLoopさせているところは、SQL一本で一挙にカタをつけられないか考える(Loopの回数を減らす)。

あたりでしょうか。

ADO.NET であれば、テーブルをDataSet(メモリ常駐型のデータ表現)へ展開させるので、自然とそれほどDBへはアクセスしなくてすむようになりましたが(もっともメモリに展開してからの書き方は色々で、書き方次第かなり速度に差は出ます。DataViewに突っ込んでRowFilterを使うよりハッシュを使った方が早いとか、リレーションシップを生成した方が早いとか)、Access単体の時はADO/DAO接続なので、インデックスの張り方、Dictionary関数とSQLの使い方に比重がかかってきます。その為には、メモリでもワークテーブルでも使えるものはなんでも使おうとします。

ところがWebのお作法は少し違うようですね。

近頃近所でアワアワしている人が、テストサーバーでWebアプリからよくCSVファイルをテーブルへ登録する実験を行なっています。登録したい件数が多すぎて(といっても4万件くらい?)、1回でアップロードできないので、その原因を探っているとのこと。テスト環境のColdFusionからかえってくるエラーメッセージはメモリーオーバーです(なんでメモリーオーバーのエラー表示???)。そんなこんなで、現時点で本番環境で速やかにできることといえば、CSVファイルを分割して、ちまちまアップロードすることくらいなんですね。あまりにそれでは手間ヒマがかかりすぎるということで、うまいことソースに修正を加えたいらしいのですが…。

実験してみてわかったことは、一見胡散臭そうにみえたCSVファイルの読み込み、INSERT文自体は4万件であろうとも、実はほんの数秒の処理であること。負荷の原因は、どうやらCSVファイルに対するエラーチェックだということです。で、そのエラーチェックのソースがどうなっているかといえば、1件1件エラーチェックする為に、次々、コード表を持っているテーブルを見に行っては値のチェックを行なっている。え。


4万件×何回テーブルを見に行っているの?


担当者は「Oracleだから沢山アクセスがあってもダイジョーブ」とか云っていますが、それってホントのことですか?この辺のことは簡易DBのAccessユーザーの私には正直わかりませんが、仮に1行につき10回DBを見に行ったとすれば、あっという間に40万回の連続ワザ。ホントにそれでヘーキなのでしょうか。なんであれ、ラウンドトリップ回数なんてなるべく少なくするべきものなんじゃないの???

Webの世界だと、色んな人が色々なテーブルを参照したり更新していたりいるし、アプリケーションサーバーでパワーの要ることをやったり(ハッシュとかも私のようにばんばん使っていると、一人分のメモリ消費×同時アクセスしている人数なので、あっという間にサーバーのメモリを消費してしまうわけですね)、色んなレコードに対して一挙にどうこうすると、Webを使っていた他の人が困っちゃう…、という発想があるから、ロックをかけるレコードはなるべく少なく、ちまちま1件ずつレコードを見に行くのがまずは基本のようですが。

CSVファイルの登録なんてバッチ処理なんだし、ある程度のコード表ならいちいちDBへアクセスしにいかずにメモリで持っていた方が断然早いし、既存データとの重複チェックを行ないたいなら、ワークテーブルに一端入れて、SQLで一発チェックした方が手っ取り早い気がするんですけど(或いはテーブルに入れてステータス管理とかでもまぁいいけど、インデックスとか張っちゃったりしていると問題ありそうなので、ワークテーブル)。ワークテーブルにもセッションみたいなものを入れられるフィールドを1列作っておけば、ワークテーブルに入ったデータのうち、どのセッションで利用したデータかの識別はできるわけだし──とか考えちゃうわけですが、なんらかの「失敗」がおきて残っちゃったワークテーブルのデーターをいつ削除するんだ、とか色々あって、そういう考え方自体危険なのかなぁ…、と無知なだけにモヤモヤ。

普段、スタンドアローン寄りのアプリばかり作っているので、この種の「バッチ処理」の実装部分で私はそこまでメモリの消費を気にしたり、同時アクセスしているメンバーのことをあまり考えていません。複数人で使うLAN内で使ってもらうようなアプリに「バッチ処理」を載せるときには、DBへアクセスしている人が自分以外にいないかをチェックする機能をつけて、「バッチ処理を行なう人は現時点で一人」という環境を作ってから、バッチ処理を実行させたり。「運用」とセットで考えると、単体のプログラムの外側で簡単に解決できちゃうことも多いですから、その辺で無理のないような回避方法を考えるとか(←1本のソフト単体でなんでもかんでもどうにかしようとはあまり思っていない。人の動きを簡便にすることは念頭にありますが)。

私の使っているDBはAccessですし、同時アクセスの人数はもともと少なく、それが通ってしまう素朴な世界に住んでいるので、考え方も「素直」なんですよねー。Web界隈に生息するイシカワさんと話していても、この辺からしていつも距離を感じます。想定条件のあまりの違いに、私ってスタンドアローンなんだなー、Winアプリなんだなー、としみじみ思うわけですね。

まーなにはともあれ、とりあえず、1回のアップロードで全部登録することを最優先に考えるなら、処理時間は無視して、5,000件くらいごとに、スリープさせるロジックをループの最中にいれちゃえば、殆ど今のソースを変えずに、全部登録できるんじゃないかなーというのが私の見込みです。どこでメモリーオーバーをおこしているにせよ、ファイルを小さくすれば通るロジックであることは確かなので、ときどきスリープして、ある程度のかたまり毎に処理を完結させてしまえば、どうにかなるのではないかなーという仮説。根本的な解決にはなりませんが、うまくいっているロジックを大改造するよりは安全・お手軽だし、ファイルをちまちま分割してからアップロードする現状よりは、黙って待つ方がラクチンだと思うし。

なんだか誰も信用してくれずに実験すらしてくれないんですけど(カウントして余りがゼロになったときにスリープするだけなのに!!)、実験してもらえないほど的外れな仮説なのでしょうか?なにかとマイノリティの自覚はあるけどさ…(悲)。

0 件のコメント: