SnowflakeのマイクロパーティションをOracleとの比較で理解する
はじめに
マイクロパーティションという概念は、Snowflake固有の概念です。(おそらく。他のDBでも存在するのであればご指摘くださいませ。)したがってとっつきにくいかもしれませんが、Snowflake創業者3人のうちの2人は元々Oracleデータベースの技術者なので、Oracleの動作からアイデアを発展させたようなところがあるのではないかと私は想像しています。私自身Oracleデータベースを触っていた時代が長いので、そことの比較で理解しました。そこで、Oracleとの比較においてSnowflakeのマイクロパーティション(MP)の説明を試みたいと思います。なお、私のOracleの知識はせいぜい12cで止まっている状態なので、最新情報を反映していないことはご容赦ください。
データブロックとの比較
Oracleブロックとマイクロパーティションはどちらも実データの入れ物
以下のような違いはあれど、OracleブロックもSnowflakeマイクロパーティションもテーブルの実データの入れ物です。
Oracleブロック | Snowflakeマイクロパーティション |
---|---|
大きさ固定(CREATE DATABASE時にDB_BLOCK_SIZEをユーザが指定) | 50MB-500MBのデータを圧縮し約16MB(ユーザはサイズを意識せず) |
列指向 | 行指向 |
非圧縮 | 圧縮 |
同じデータブロックが何度も更新される | 一度作られたマイクロパーティションは不変(immutable)で、そのマイクロパーティション上の値が更新されるときは新しいマイクロパーティションが作られる |
Time Travelはマイクロパーティションがimmutableであることを利用
過去の時点のデータを検索するTime Travelはマイクロパーティションがimmutableであることを利用します。更新時に新しいマイクロパーティションを作るということは過去のデータが残っているということに他なりません。過去の時点のデータを検索したければ、その時点でのマイクロパーティションを走査すれば良いわけです。つまりSnowflakeにおける更新トランザクションは、新しいマイクロパーティションの作成とマイクロパーティションに対するポインタの更新を行うということです。
おまけ:Zero Copy Clone
この仕組みがZero Copy Cloneも可能にしています。実データをコピーせずマイクロパーティションに対するポインタだけをコピーして新しいテーブルと見なせば、テーブルのコピーは完了です。コピー直後の状態ではマイクロパーティションは全てコピー元と共有されています。コピーしたテーブルに更新をかけたときは、新しいマイクロパーティションを作成し、コピーしたテーブルのマイクロパーティションに対するポインタを更新すれば良いということになります。
Oracleでも過去時点のデータを検索するFlashback Queryという機能があります。OracleではDBブロックは常に最新であるため、これをロールバックしながら(= undoレコードを適用しながら)目的の時点まで戻すという作業をメモリ上で行なっています。block buffer上にカレントブロックを読み出し、undoを適用していくわけですが、このように作成されたメモリ上の過去の時点のOracleブロックのことをCRブロック(Consistent Read Block)といいます。Flashbak Queryでなくとも、あるトランザクションが書き換えたブロックをそれより前のSCNで検索しているSELECT文が走査するとき、CRブロックがブロックバッファ上に作成されます。undoレコードはロールバックセグメントに記録されますが、ロールバックセグメントは一定の大きさで使い回しが行われているため、無限に残っている訳ではありません。したがって目的の時点まで戻すことができないことがあります。この時に発生するエラーがORA-1555です。
パーティションテーブルのテーブル・パーティションとの比較
テーブル・パーティションと似てるところ・違うところ
マイクロパーティションというくらいなので、データ・ブロックではなくテーブル・パーティションとの類似性があるのではないかと予想する方もいらっしゃるでしょう。そういった面もあります。
そもそもOracleのパーティションテーブルの利点とはおよそ以下のように整理できると思います。
- パフォーマンス
- パーティション・プルーニング
- I/Oの分散とパラレルクエリの有効活用
- 管理容易性
- 可用性
これらのうちSnowflakeのマイクロパーティションで意識すべきなのはパーティション・プルーニングのみです。
Oracleデータベースで大きなテーブルに対するクエリのパフォーマンス改善を目指す際に、ディスク配置を意識しI/Oを分散させ、サーバプロセスをできるだけ有効活用できるようにといったことを意識します。例えば全件検索する対象がハッシュパーティションでほぼ均等な大きさのテーブル・パーティションとして異なるディスクに配置されていれば、I/Oが分散できることが期待できますし(もっともこの用途ではASMなどでディスクストライピングし、ひとつのデータファイルを複数の物理ディスクに分けて存在させる方が主流かもしれません)、パラレルサーバのプロセスをパーティション毎に割り当てるといったことも検討できます(こちらの解説を参照)。なお、パラレルサーバの数(parallel degree)はSnowflakeでは仮想ウェアハウスの大きさに相当します。
Snowflakeのマイクロパーティションはクラウドプロバイダのオブジェクトストレージに配置されているため、ディスク配置を意識する必要がありません。細かい制御ができないという言い方もありうるかもしれませんが、オブジェクトストレージはクラウドサービスベンダーが提供する最も基本的な機能なので、各ベンダーはそのパフォーマンスと可用性に力を入れています。
管理容易性という観点でのメリットとして、各パーティションを別の表領域に配置しておくことで個別にバックアップ/リカバリができるといった例が挙げられますが、Snowflakeには表領域という概念はなく、Time Travel/Failsafeのマイクロパーティションがそのままバックアップとしても機能しているため、Oracleのような考慮は不要でしょう。(あえて言うならクラウドプロバイダのリージョン単位での障害があった場合は考慮する必要があり、そのためにSnowflakeは別リージョンへのレプリケーションの機能を提供していますが、少なくともマイクロパーティション単位で考慮すべき事項はありません。)
可用性という観点でいえることは、特定のパーティションのみ破損していた場合も、正常なパーティションのみで完結する作業は継続可能ということです。しかし、あまりアクセスしないパーティションで障害が発生しやすいという道理もないので、現実的には全体の可用性を上げるにはどうしたら良いかと考えるべきでしょう。Snowflakeは前述のようにクラウドプロバイダの可用性向上の成果を享受しています。
全体に、クラウドプロバイダが提供してくれている高性能なオブジェクトストレージを目一杯利用してやろうというのがSnowflakeの戦略だという捉え方もできるかもしれません。
パーティショニングとクラスタリング
パーティション・プルーニングとは不要なパーティションを読まずにI/Oを減らすことです。例えば日付項目を使って年月でパーティションを作成しておけば、2022-10-22の値を検索するとき2022年9月のパーティションを読み込む必要はありません。
Snowflakeにおいてはマイクロパーティションに含まれる値を明示的に指定する方法はありません。その代わりに存在するのがクラスタリングです。Snowflakeではクラスタリングはバックグラウンドで自動で行われ、同じ値をできるだけ同じマイクロパーティションに含めるようにまとめようとします。「できるだけまとめようとするけれどひとつのマイクロパーティションにひとつの値だけが入っていることを保証する訳ではない」というのがパーティショニングではなくクラスタリングといっている謂です。クラスタリングはバックグラウンドでコンピューティング・リソースを使いますし、クラスタリングでマイクロパーティションを再編する際も元のマイクロパーティションはimmutableなので新しいマイクロパーティションが作成されストレージ領域を使います。パーティション・プルーニングにより得られるメリット以上に頑張って厳密なパーティショニングをする意味はありません。また、ユーザが個々のマイクロパーティションを指定して操作することはないので、2022-10-22のレコードが存在するマイクロパーティションに2022-09-30のレコードが存在しても、管理上問題になることもありません。
どの程度クラスタリングが行われているかは、SYSTEM$CLUSTERING_INFORMATION()というシステム関数でテーブル毎に確認することができます。
まとめ
Oracleの各種概念に親しんでいる方は以下のようにイメージするとわかりやすいのではないかと思います。
CRブロック ≒ 最新ではないマイクロパーティション(Time Travel・Failsafe用)
テーブル・パーティション ≒ クラスタリングされたマイクロパーティション
Discussion