大量のDML作業のためのパフォーマンス改善方法
目次[非表示]
- 1.概要
- 2.大量のDelete
- 3.大量のINSERT
- 3.1.インデックスの数によるDirect Path Insertのパフォーマンスの違い
- 3.2.Array Processing
- 3.3.別の難関
- 3.4.110│2013 技術 White Paper
- 3.4.1.結論
概要
大量のデータを変更しなければならない作業は、それ自体だけでも大きな負担です。
しかし、変更作業自体に限定されず、変更前のデータと変更後のデータをそれぞれ保存管理しなければならないメカニズムであれば、性能を改善しなければならないという立場ではさらに大きな負担となります。
文字通り大量のデータを変更しなければならない作業の特性上、SQLチューニングだけでパフォーマンスを改善する余地は多くないだけでなく、改善をしても劇的な効果を期待することは難しいのです。
さらに、必然的に発生するredoとundoデータによる追加の負荷は、パフォーマンスを改善する必要があるという立場では、Oracleの合理的な同時性保証メカニズムがむしろ恥ずかしく感じられるかもしれません。
したがって、今回のトピックは、大量のDMLを処理する際に、パーティションテーブルの構成・変更やDirect Path Insertを誘導する方法などを通じてDMLワーカー自体の性能向上はもちろん、システムのDown Time最小化にも寄与できる方法 についてのお話します。
大量のDelete
月単位または特定の期間単位のデータを定期的に削除する作業は、どのシステムでも簡単であるとみなせる作業の1つです。 おなじみの作業ですが、その作業のパフォーマンスを向上させることは容易ではありません。
大量の Delete 操作は Table Full Scan や Index Scan などの該当テーブルを Access する方法による非効率というよりは必然的に発生するしかない redo、undo データ生成とともに、そのテーブルにインデックスが存在する場合、インデックスの数に応じて追加の Overhead が発生するのが性能低下の主な理由になるからです。
インデックスで発生する追加のオーバーヘッドとは、削除対象となるデータをインデックスからも削除しなければならないと同時に、この過程も redo と undo データが生成されることを意味します。
上記のような負荷を抑えながら大量のDelete操作の実行速度を改善するにはどのような方法があるでしょうか?
答えはPartition Table構成にあります。
定期的に削除する必要がある期間が決まっていれば、その列を Key とする Range Partition を構成し、削除対象となる期間に対応する Partition Table を Alter Table [Partition_Name] Drop Partition [Partition_Name] コマンドを通じて DML ではない DDL で作業を置き換えることができます。
DDL への Delete 操作の置き換えは、 redo データが生成されず単に該当 Partition を Drop する作業だけ行うため、速度の差は比較できないほど大きく、大量の redo/undo データが発生しないので Down Time 最小化にも 大きな貢献ができます。
以下のテストでその違いについて詳しく調べてみましょう。
テーブル生成DDL(Partition_test)
テーブルの作成 DDL (Partition_test2)
データ入力(2つのテーブル同じ)
一般テーブル(Partition_test2)のデータ削除
パーティションテーブル(Partition_test)のデータ削除
大量のINSERT
大量のデータをInsertする作業は、上で紹介したDelete作業に比べて性能改善の余地が多くあります。 これは、データ入力対象テーブルの属性変更(Nologging)とヒント(/*+ append */)だけでDirect Path Insertを誘導してREDOの発生を抑制する方法があります。
しかし、ここには別の障害があります。それはインデックスの存在です。
これは、大量のインサート操作時にデータがテーブルに入力されるタスクよりも、インデックスの数やインデックスの性格によって性能が左右されるとしても無理もないことです。
Direct Path Insert が可能な環境で、2 つのケースに対する大量の Insert タスクのパフォーマンスの違いと、これを改善する方法について学びます。
インデックスの数によるDirect Path Insertのパフォーマンスの違い
Insert 対象となるテーブルにインデックスが多数生成されている場合、それだけの redo 発生量が付随します。なので自然にInsert作業がうと思います。
以下のテストで、インデックスの数に基づいてDirect Path Insert操作の速度にどの程度影響するかを確認します。
テーブル生成 DDL 省略(上記大量の Delete のPartition_test テーブル DDL 参照)
3つのインデックス存在する場合の大量の Insert SQL> alter table partition_testnologging;
既存のインデックスを削除し、Insert 以降のインデックスを作成する
上記のテスト結果のように大量のデータが入力されると、そのテーブルのインデックスがどれだけ存在するかによってパフォーマンスに大きな影響を与えることがわかります。
これは単に大量のデータ入力時にインデックスの存在によるパフォーマンスの違いを示すテストなので、大量のデータが入力されるすべてのタスクにインデックスをドロップしたりUnusable状態に変更して作業するという意味ではないです。
ただし、オンラインユーザーがいない時間帯の配置作業であれば十分考慮できる方法だと考えます。
実行速度はもちろんのことと、redo発生の最小化にも大きな貢献ができるメリットがあるからです。
頻繁なIndex SplitによるDirect Path Insertのパフォーマンスの違い
常に増加する値を挿入する必要がある性格の列が存在する可能性があります。
代表的な例は、SYSDATEが入力される状況です。
この列にIndexが生成されていると仮定し、大量のInsert操作が行われると、9:1 Index Splitが頻繁に発生する現象が自然に現れるはずです。
前述のように、Index Splitは決して軽いものではありません。 なぜ軽くないのか、その過程を見てみましょう。
まず、9:1 SplitとはMax値が入力される状況で右下のIndex Leaf Blockにもはや保存するスペースがなくなった場合、Free ListのFree Blockを取得し、既存の最下位Index Leaf Blockの最小限のデータを新しいFree Blockに 取り込み、新しく保存されるデータを該当ブロックに入力する過程をいいいます。
これはデータが増加する過程で必然的にIndex Sizeが増加し、Index Splitはこの過程で発生する自然な現象です。
しかし、Index Splitプロセスは無料ではないのです。
Index Splitの全プロセスは、redoが発生し、Splitが開始して完了するまでLockingを通じてこの作業を保護します。 この Locking は、Enq : TX – Index Contention という Wait Event で Split が完了する時点まで Split プロセスを保護することになります。
したがって、常に増加する性格のカラムにインデックスが生成されており、当該テーブルに大量のデータが入力される場合、Index SplitによるWait Time増加とともに、redo発生などでInsert速度 の低下は必然的に発生します。
では、これを改善できる方案があるのでしょうか?
結論からお話しますと、問題となるIndexに他のカラムを追加して再生成する方法が存在します。
この方法の目的は、大量のデータが入力されると、既存の右下のIndex Leaf Blockに競合が集中していたことが結合インデックスによってその競合がある程度分散されるためです。
新たに結合するIndex構成対象カラムのNDV(Number of Distinct Value)値が大きいほど、Index Split減少の効果はさらに大きくなります。
ただし、この部分で注意することは、Index Clustering Factor が大幅に減少する可能性があるため、NDV 値の大きい列を優先順位で構成する必要はないことです.
Array Processing
大量のDML操作は、データをLoadするかUpdate(Delete)するときに、一般的にLOOP文を使用して、ガンガンが処理することが多いです。
この方法は、Loopごとに1回ずつDML操作が行われ、その分だけDBMS CALLが発生するため、性能上不利なのです。
しかし、Array Processingを利用したBulk SQLを使用すれば、Loopなしでたった1回のSQLを実行するだけで処理が可能です。 つまり、大量のDMLを1回(Limit予約語を使用して一度に処理できる件数を制限しない場合)に処理できるため、DBMS CALLを減らしてLoopで処理された方法に比べて大きなパフォーマンス改善効果を見ることができます。
以下のテストでその性能差を比較してみましょう。
Array Processing を活用して Insert する Procedure Source – P_BULK_INSERT_TEST1
ガンガン Loop を通じて Insert する Procedure Source – P_BULK_INSERT_TEST2
別の難関
先に紹介した方法の適用が可能であれば、大量のDML作業に対する性能改善 Process を行うことができます。
しかし、もう一つの克服すべき難関がまだ待っています。
これは、Partition Tableへの変更が必要な部分に相当するもので、膨大な量のデータを格納している既存テーブルをPartition Tableに変更する作業が不十分であるということです。
一定時間オフラインが可能だとすれば変更作業を試してみることもできるのですが、常にサービスを続けなければならない環境でPartition Tableに変更しなければならないと勧告すれば、原論的には正しいといえるのですが、実務を担当しているDBAには大きな当惑感を抱かせる結果を生み出すことになります。
しかし、この難しく見える問題も克服できる方法があります。 これはPartition Exchange(以下、PE)機能を利用するものです。 PE機能は、文字通り通常のテーブルをPartition Tableに変更できることを意味します。 さらに、追加のスペースも必要なく、かなり効率的な方法でPartition Tableへの移行を達成することができます。
以下のテストは、PE機能を活用して一般テーブルをPartition Tableに簡単に切り替えることができることを証明しています。
従来使用していた一般表 : Partition_test2
: Delete テストのテーブル生成スクリプトとデータ入力スクリプトのリファレンスPartition Tableに切り替えるPartition Tableを作成する
110│2013 技術 White Paper
上記のようにすると、既存のテーブルは単にPartition Tableに変更できることがわかります。 ただし、注意すべき点は、既存の一般テーブルで持っていた制約条件(Primary Key)やIndex構成はすべて同一に生成することで正常なPEの実行が可能となることです。
結論
先に紹介した大量のDML作業の性能を改善する方法は、テーブルの構造自体を変更や、発生可能なredoの量を最大限減少させる方法、そしてIndexの構造変更やDropなどでまとめることができます。 しかし、このような方法は、ややもすると実務を担当するDBAにとってはただの雲をつかむだけの話に過ぎないかも知れません。
頭では十分理解して共感できる良い方法を提示したとしても、現実に適用できないのであれば、その空虚さはさらに大きな重さになることでしょう。
しかし、上で紹介した内容は実質的に適用でき、また適用した事例があります。 もちろん、業務の性格によっては、数学の公式のように常に同じ方法もより適用できるわけではないですが、その環境に合ったソリューションは常に存在すると信じています。
非現実的だと思ってしまう瞬間、性能改善の余地は遠のいていきます。
上記の内容の核心をきちんと整理していれば、該当する状況について様々な性能改善アイデアを提示でき、その中で最も簡単に適用できる方法を協議を通じて探すことができることでしょう。
大量のDML作業性能改善とシステムのDownTime最小化という二兎を得ることは難しくないのです。