パフォーチューの中心で悲しみを叫ぶ

祭りじゃ!祭りじゃ!!

今回は、今のプロジェクトであったお祭りの話でもしようかな〜と思うわけである。
なんのお祭りかというと、「パフォーマンス問題祭り」なのであった。

よくある話といえばよくある話。単体テスト結合テストあたりまでは問題にならなかった性能が、実際のマスタデータ投入時や、トランザクションデータボリュームになった途端、とってもパフォーマンス的に悲しいものになったプログラムが出てきたわけで・・・
で、いつものように、気が付けばそのお祭り対応のど真ん中にいるハメになり、日々トレースデータと向き合うことになった。
ということで、基本的な話ばかりかと思うが、ちょっとアドオン作成時のパフォーマンス考慮ポイントを備忘として残しておこうかなあ、というのが、今回の企画である。

ざっくりポイント

パフォチューのポイントとしては、大雑把に以下3点となる。

  • 設計の方針
  • SQL関連
  • 内部テーブル関連

この3つさえ押さえれば、大抵のパフォーマンス問題は解決する。(断言していいのかな・・・)
では、順番に行ってみようか!

最初の一歩・・・設計の方針

まずは、基本中の基本。
ご存知のように、SAPのパッケージは、DBサーバ1台に、複数のAPサーバで構成される。
これは、トランザクション量や、ユーザ数が増えたら、安直にAPサーバを増やして、並列処理をできるようにしましょう、という思想にのっとって、システムが組まれてる。

さて、それを前提に考える。

1.必要なデータを、必要なタイミングで都度DBアクセスし、使い終わったらメモリを開放する。
2.必要なデータは、多少冗長でも一括で読み込み、内部テーブルに格納しておいて使いまわす

このどちらのほうが、設計方針として正しいか、というと、当然2番目のほうになる。そうすることで、負荷をAPサーバに寄せ、APサーバのメモリを多少贅沢に使ったとしても、一台しかないDBサーバを、大事に使いましょう!!ということになるわけだ。

こんなの常識だと思っていたのだが、案外これすらできていないプログラムがあったりする。まあ、小規模なWebアプリケーションを作る時は、どっちかっていうと、必要なデータを都度DBアクセスし、使い終わったらFree、というのが作法になっているから、そっち系のプログラムを今まで書いていた人がABAPを覚えました、見たいな感じだと信じたいんだが・・・

SQLの問題 その1 あれ?誰がやるんだっけ??

できるだけ一括でDBからデータを引いてくる、という方針で設計されていても、SQLがクソだと、当然パフォーマンスは悪くなる。

どこのプロジェクトでも、開発標準みたいのが決められていて、パフォーマンス対策として、「Select発行するときには、アスタリスクで全項目を引いてくるのは禁止」だとか、「For All Entriesは禁止」なんていうのが決まっていると思う。しかし、実際のところこんな話よりも、Where句の検索条件の結果、Indexにもあたらず、100万件シーケンスリードなんて、悲惨な話になっていないかどうか、というのを確認するほうが、何百倍も効いてくる。
お祭りの修正ポイントも、大抵の場合、このポイントなのであった。

そんな当たり前の話するなよ、と、皆さんきっと思ってるんだろうな〜と思う。
しかし、ちょっと胸に手を当て、考えてほしい。
データの件数とIndexとを勘案して、最適なSQLになるように考える役割は、あなたのプロジェクトでは誰になっているだろうか?
普通の設計書は、このデータは、どこのテーブルに入っている、としか書いていないし、実際コーディングする人は、いまからSelectをするテーブルの、本番での想定件数を知らない。この状態で、「データの件数とIndexとを勘案して、最適なSQLになるように考える役割」は、誰がやれるんだろうか?

実は、当たり前のことを実行する人が誰なのか、について、決定されていないことのほうが「普通」だったりしないかい?という話である。
ま、言ってしまうと、この手のパフォーマンス問題とは、ほとんどの部分、マネジメントの問題ってわけなのである。

SQLの問題 その2 プライマリーキーとIndexについて

で、次は技術論の話に移る。これもまず、当然の話からしてみる。

Selectを発行するときは、当然プライマリーキーや、Indexに当てると、飛躍的に早くなる。これは、キーやIndexは、DB内部で昇順に並んでいて、樹形図検索により、特定のレコードをすばやく検索できるようになっているからである。

ということは、だ。
いくらプライマリーキーを使ったとしても、二つあるキー項目のうち、後ろのキー項目だけを使ってSelectをかけてしまったら、まったく意味なしなのである。こんなことしたら、樹形図検索できないから、シーケンスリードとまったく変わらない。
「キーでSelectしてるんですけどね〜」
できてね〜よ!!

続いてIndexを張る時の注意。
上述のように、Indexは、DB内部で昇順に並べ、樹形図検索により特定のレコードを検索する仕組みだ。ということは、検索した結果の「特定のレコード」数が、小さくなるような項目に対して、Indexは貼らないと効果がない。
その仕掛けをわかっていないと、「削除フラグ」にIndexが張ってあって愕然とする、というような事件が、ちょいちょい発生するのである。100万件が80万件にしか絞れない項目にIndexなんか張るなよ、という話である。

また、二つ以上のIndexには、一回のSQLではあたらない、というのも、当たり前だけど忘れがち。上記の「削除フラグ」がどうして愕然とするかというと、たとえば、削除フラグのIndexにあたって対象100万件が80万件になったあとは、その他のWhere句がいくら別のIndexにあたるようにコーディングしていても、シーケンスリードになってしまうのである。

SQLの問題 その3 ちょっと毛色が違うJoin爆弾

通常のデータベースを使ったアプリケーション開発では、Join句を使ったテーブル検索は常識である。しかしこの常識、「Join爆弾」として私は結構恐れているコーディングである。

通常、検索の最適化を図るために、日次で統計情報の更新などが行われる。
ところが、このJoin句に対する「最適化」ってやつが、本当によく間違った方向に倒れるのだ。今までレコード数の小さなテーブルから先に読み、レコード数の大きなテーブルを次に読む、という最適化がされていてハッピーだったのに、平気で突然逆になったりするのである。結果、本番を動かしている最中に、ある日突然前触れもなくパフォーマンスが劇的に悪くなり、JOBが想定時間内に終わらず、関係各所に懺悔してまわるという事態が、結構頻繁に起こるのだ。

まあ、本質的には、なんでそんなことが起こるんだ、というほうが問題なのだろうが、使わなければ心配する必要もない。ということで、多少助長でも、Join句は使わず、SQLを分割することをお勧めするのであった。

内部テーブル関連 Readには気をつけろ!

DBからメモリー上に展開したデータ検索で便利なのが、「Read (内部テーブル) with key」構文である。
しかし、この構文、普通に使ってしまうと、全件シーケンスリードになってしまう。100万件の内部テーブルをループしている中で、この構文が入っていて、実質100万x100万ループになっているのを発見したときには、非常に悲しくなるのである。
検索する内部テーブルの想定件数が多い場合には、速やかにSorted Tableにするか、ハッシュテーブルにして、検索速度を上げるのがよろしい感じなのである。

あ、そうそう、Sorted TableにAPPENDして実行時エラー(ショートダンプ)を出すと、かなり恥ずかしいので注意しましょうね。

え?という情報はないね、やっぱり

なんだか、書いてみると当たり前の話ばっかだな〜、という印象である。
しかし、上記のようなことができていないプログラムが多かったので祭りが始まった、という部分もあり、なんというのか、「当たり前」を「当たり前」に実行することの難しさを痛切に感じる、秋の夜長なのであった。。。