怖い話 その1 終わらないバッチ処理
インテグレータ勤務 Aさん

 お正月気分も抜けてきた冬のある日,普段と同じように仕事をしていると,突然部長から呼び出された。あるプロジェクトでシステムの稼働開始を1カ月後に控えているのだが,開発が遅れているので支援してほしいと言うことだった。

 そのシステムは製造業のある中堅企業向けで,全国の売り上げを集計して販売分析を行うという普通によくあるものであった。3週間前にプログラミング作業に着手し,予定ではすでにシステム・テストに入っているはずなのに,まだ修正を続けているらしい。

「やはりオープン系RDBは使えない?」

 聞いてみると,夜間の6時間で終わらなければならないバッチ処理が,12時間たっても終わらないのだと言う。ちなみに,そのバッチ処理は,Oracleのストアドプロシジャを使って記述している。売り上げの元データの件数は100万レコード以上。データ量が多いので集計処理に時間がかかっているということだった。しかし,サーバーは十分なスペックのものを用意しているはずである。元々の見込みでは2,3時間で終わるはずの処理が12時間たっても終わらないというのはどうもおかしい。

 プロジェクトのメンバーは,汎用機(メインフレーム)アプリケーションの開発の経験はあるが,オープン系のRDBを使った開発は今回が初めてだという。当初から,メンバーの中ではオープン系のRDBは不安定で遅いという印象があったらしい。このような事態に直面するに至って「やはりオープン系のRDBは使えない」という雰囲気がプロジェクトに蔓延していた。

 バッチ処理の流れを聞いてみると,年月日・店舗・商品ごとの売り上げデータを集計して,(1)商品ごとの月別売り上げ,(2)地域別に見た商品ごとの月別売り上げ,(3)商品分類ごとの月別売り上げ,(4)地域別に見た商品分類ごとの月別売り上げを出力するというものであった(図1)。システムの仕様として,特に難しいところはない。

図1●売り上げ集計処理の流れ
図1●売り上げ集計処理の流れ

 テーブル設計を見せてもらうと図2のようであった(詳細は省略してある)。こちらも別に難しいところはないが,ちょっと不安になった。テーブル間の関連,つまり外部キーなどが一切設定されていないのである。図2のようなテーブルであれば普通,店舗テーブル,商品テーブルと売り上げテーブルが「1対多」の関係にあるのは明らかである。しかし,これらのテーブルには何の関連も設定されていない。

図2●売り上げ集計に利用するテーブルの設計
図2●売り上げ集計に利用するテーブルの設計

 実際のバッチ処理のプログラムを見せてもらって,がく然としてしまった。集計処理のプログラムで,ORDER BY句を付けてSELECTすることによってソートした結果セット(カーソル)を作成した後,カーソルの各行に対してループをまわして加算することで合計を求めていたのである(図3)。

図3●売り上げ集計処理のロジック
図3●売り上げ集計処理のロジック
[画像のクリックで拡大表示]

 しかし,そのような集計処理を行わなくても,集合関数(SUM)とGROUP BY句を使えば,グループごとの集計を一つのSQL文で計算させられるのである。そのことを担当者に告げると「えっ,SQLってそんなこともできるんですか!」と驚く始末。

 ストアドプロシジャでカーソルを使うような“凝った”プログラムを書いておきながら,SQLの基本的な関数を知らなかったのである。集合関数を使って処理を書き直すと,数十行あったプログラムが数行で済んでしまった。

 しかし,プログラムを実行してみると,今度はまったく応答が返ってこなくなってしまった。実は,GROUP BY句を付けた処理は,データベース管理システム(DBMS)内部でソートを行っている。今回対象にしたテーブルは100万レコードもあるため,全件を対象としたSQL文を実行すると,膨大な計算量になってしまう。加えて,図2のテーブル設計では,検索対象のデータ項目にインデックスが付けられていない。

 そこで,商品コードにインデックスを作成し,商品コードごとにデータを区切って集計するSQL文に書き替えた。その結果,処理時間は大幅に短縮され,半日たっても終わらなかった処理が1時間半で終了するようになった。

 この件は,バッチ処理のSQL文の書き方やインデックスが作成されていないのが直接の原因であった。だが,もっと根本的な原因は,RDBの特性を知らずに設計してしまったことにある。全体として,汎用機アプリケーションでファイルを順次処理するようなイメージで設計してしまったためであろう。不必要な中間テーブルが多数あり,無駄なバッチ処理が数多くあった。