Amazon Web Services ブログ
PostgreSQL 11 の新機能を詳しくご紹介
初期のPostgreSQL プロジェクトは 1986 年に大学のプロジェクトとしてスタートしました。1996 年に PostgreSQL プロジェクトはオープンソースコミュニティが引き継ぎ、毎年メジャーバージョンを定期的にリリースしています。ソフトウェアの複雑さを考えると、このような早急なリリーススケジュールには、主要な機能を小さく基本的な要素に分割する必要があります。こうした小規模で基本的な機能を組み合わせることで、最新リリースの PostgreSQL 11 を含む PostgreSQL のすべてのメジャーリリースが行われています。PostgreSQL 11 は、Amazon RDS for PostgreSQL および PostgreSQL と互換性のある Amazon Aurora の両方に対応しています。
この記事では、パーティション分割、並列処理、ジャストインタイム (JIT) コンパイルという、PostgreSQL 11 の 3 つの素晴らしい機能について詳しく説明します。複数の PostgreSQL バージョンにおけるこれらの機能の進化について調べます。また、PostgreSQL 11 が提供する利点についても説明し、これらの機能をアプリケーションに適合させる方法を説明する実用的な例を示します。
パーティション分割
データベースが成長するにつれ、少数のテーブルが通常は成長の主な原因となっています。成長は、すべてのアクティビティの履歴ログを保持するテーブルや、ユーザーテーブルに集中する場合があります。
テーブルのパーティション分割により、データベースの大幅な成長に対処できます。つまり、単一の大きなテーブルをより小さく管理しやすいチャンク (パーティション) に分割することで、大きなテーブルでのクエリを高速化します。クエリの実行中にデータベースがパーティション全体を除外できると、処理するデータが大幅に減るため、パフォーマンスが向上します。テーブルを分割するという概念は PostgreSQL 11 の新機能ではありません。PostgreSQL では、2005 年リリースのバージョン 8.1 において最初にテーブルのパーティション分割という形式を導入しました。
以下の orders
テーブルを例に考えてみましょう。アプリケーションは、販売注文がある度に新しい行をこのテーブルに追加します。注文が多くなるにつれ、このテーブルは日ごとに大きくなります。このように時間の流れと共に成長するテーブルには、時間に基づくパーティション分割が基本的に使われます。
PostgreSQL のバージョン 8.1 から 9.6 では、「テーブル継承」という固有の機能を使用してパーティション分割を設定します。 つまり、テーブルの制約を持つ親から継承した子テーブルを作成し、その子テーブルに含まれるデータ範囲を強制することにより、パーティションを毎年設定します。
その後 PostgreSQL 最適化ツールは、排他制約と呼ばれる機能を使用します。この機能は制約を使用してクエリ時にパーティションを除外し、クエリのパフォーマンスを向上させます。
排他制約は効果的ですが、この方法は子継承モデルにとって欠点があります。具体的に言うと、排他制約により、パーティション分割されたテーブルへのデータの読み込みが複雑になります。そのため、適切な子に行を移動するには、親テーブルでトリガーする必要があります。
PostgreSQL 10 では、宣言的なパーティション分割の構文を提供しています。パーティション分割メソッドとパーティション分割キーを定義します。PostgreSQL 10 は、範囲パーティション分割メソッドおよびリストパーティション分割メソッドをサポートします。
分かりやすいパーティション分割の構文に加えて、PostgreSQL 10 では親テーブルのトリガーをする必要がなくなりました。トリガーをしなければ、コードがなくてもテーブルをパーティション分割でき、パーティション分割の構文によりテーブルのメンテナンスが容易になります。PostgreSQL は、行を正しいパーティションに自動的にルーティングするようになったので、データのロードとメンテナンスの両方が改善されました。
名前から分かるように、範囲パーティション分割が扱うのは値の範囲です。たとえば時系列データでは、日付範囲を使用して、毎日、毎月、または毎年のパーティションを作成できます。これで orders
テーブルに完璧に合うようになりました。
固定値の数が限られている場合は、リストパーティション分割を使用します。パーティション作成中に、各パーティションの値のリストを定義します。ここでは、customer
テーブルにリストパーティション分割を使用し、各リージョンに属する国のリストを使ってリージョンごとにパーティション分割します。
パーティション分割メソッドとパーティション分割キーのうちどちらを選択するのが正しいかは、アプリケーションによって異なります。データの変更に応じて、選択を変更することもできます。範囲パーティション分割とリストパーティション分割が、同じスキーマで必要になる場合があります。
PostgreSQL 11 ではこの構文をさらに拡張し、デフォルトのパーティションを定義できるようにしています。デフォルトのパーティションは、確立されたパーティションの範囲外の値を持つデータを取得する場合に役立ちます。たとえば、ここでは orders
テーブルに 2015 年以降の毎年のパーティションがあります。ただし、注文日が空欄になっている行 (たとえば 1970 年 1 月 1 日) を追加できます。エラーは発生しませんが、確立されたパーティションの範囲外の値を持つ行がこの行にデフォルト設定されます。
PostgreSQL 11 ではパーティションテーブルでのインデックス作成も簡単です。PostgreSQL 10 では、すべてのパーティションでローカルインデックスを作成するには、各パーティションで個別に作成するしかありません。メインの親テーブルにインデックスを作成しようとすると、エラーが返されます。
たとえば、以下のステートメントでは PostgreSQL 10 の orders
テーブルにインデックスを作成しようとしています。この試行には予想どおりエラーが返されます。そこで、パーティションにインデックスを作成してみます。
対照的に、PostgreSQL 11 の orders
テーブルにインデックスを作成しようとすると、ステートメントはすべてのパーティションにローカルインデックスを構築することができました。
PostgreSQL 11 では、PostgreSQL 10 で導入された範囲メソッドおよびリストメソッドに追加するハッシュパーティション分割メソッドも導入しています。ハッシュパーティション分割は、パーティションをする必要のある論理値または自然値の範囲を含まない大きなテーブルの場合に便利です。
このセールスデータベースでは、part
テーブルにはハッシュパーティション分割が完全に適しています。テーブルは時間の経過と共に成長する設計になっていますが、時間の値がテーブルの行を決定することはありません。新しいデータと同じ頻度で古いデータを参照できます。このテーブルは、テーブルのプライマリキーをハッシュパーティション分割するのに最適なユースケースを表しています。
ここでは作成するパーティション数を決定するだけで済みます。最適なパーティション数はアプリケーションによって異なりますが、たとえば 10 個のパーティションが必要な場合は 10 個のモジュールでパーティションを定義します。
PostgreSQL 11 ではパーティションの刈り込みがさらに強化されています。つまり、クエリのためにパーティション全体をスキャンしなくてもよいのです。PostgreSQL 10 以前のバージョンでは、クエリ実行計画中にパーティションが除外されます。値の範囲を除外するのに十分な情報が提供された場合、PostgreSQL はスキャンから対応するパーティションを取り除きます。必要な情報は通常、定数を含む WHERE
条件です。このメソッドは簡単なクエリに効果的です。
もちろん、別の小さなテーブルに結合することで大きなパーティションテーブルにアクセスするのが一般的です。しかし、PostgreSQL 11 では実行時にパーティションを除外します。join
の結果、不要なパーティションのスキャンが削除されます。以下のクエリは、パーティションの刈り込みの両タイプの例です。
クエリは、両方ともハッシュパーティション分割された 2 つのテーブルを結合します。クエリの WHERE
条件 "WHERE lineitem.l_orderkey = 215528935"
を使用すると、PostgreSQL はクエリ計画中に単一のパーティション以外のすべてを除外できます。
これは以下の PostgreSQL のクエリ計画内にある「Index Scan using lineitem_p78_pkey on public.lineitem_p78
」で始まる行で確認できます。 クエリ計画では、行の項目のうち、他の 99 個のパーティションは考慮されません。
「part」テーブルのスキャンは、これとは異なる動作をします。複数の行が「Index Scan using part_pN_pkey
」というテキストで始まります。 クエリ計画中は、partkey
テーブルの値は不明です。なので PostgreSQL は「part」テーブルのパーティションをすべてスキャンするか検討する必要があります。PostgreSQL が lineitem
テーブルのスキャンを終えると、PostgreSQL では partkey
の値が分かります。PostgreSQL は次に、実行時に不要なパーティションを除外できます。
クエリ計画は、「(never executed)」というテキストを含む「Index Scan」の行にこれを反映します。 PostgreSQL は、lineitem
から返された行に基づき、10 個のパーティションのうちの 5 個を除外できます。
並列処理
PostgreSQL の旧バージョンでは、各クエリの実行全体にわたって単一の CPU コアを使用しています。PostgreSQL では、並列処理が初めて導入されたバージョン 9.6 以降から、クエリごとに 1 つの CPU に依存していた部分を変更しました。
並列処理により、PostgreSQL は複数の CPU を使用してクエリを実行することができます。これを実現するために、バックグラウンドワーカーはクエリ実行の負荷を共有して、中間結果をリーダープロセスに返します。リーダープロセスはすべてを結合してクライアントに送信します。結果的に PostgreSQL はクエリを迅速に実行できるようになりました。
たとえば、多くのビジネスで標準的に用いられている簡単なクエリである、月別の平均売上高について見てみましょう。以下のクエリでは、前に定義した orders
テーブルに対して、2018 年第 1 四半期の月別の平均販売価格を計算しています。
PostgreSQL は、orders
テーブルの 2018 年度のパーティション全体を順次スキャンしてから、数百万の値の平均値を計算します。そのため、結果が返されるまでに 13 秒以上かかります。
PostgreSQL 9.6 には並列シーケンシャルスキャンと並列集計が追加され、複数の CPU を使用して月別の平均売上高のクエリを実行できます。並列処理レベルを 2 に上げると、結果を 2 倍以上速く得ることができます。
PostgreSQL 10 では、追加のクエリ実行操作を並行して実行できます。並列インデックススキャンが最も一般的ですが、並列マージ結合やストアドファンクションの並列実行といった操作もあります。テーブルが大きく成長するにつれ、そのインデックスも大きくなります。大きなインデックスに対してクエリを実行する場合、並列スキャンによって結果をさらに迅速に得ることができます。
以下のクエリでは、先ほど作成したインデックスを使用して、平均販売価格のクエリを営業週にまで絞り込みます。
インデックスを使用すると、クエリは 1.5 秒で結果を獲得します。並列処理レベルを 2 に上げて、結果を 2 倍以上速く得ることができます。
これらの確立された機能を改善して、PostgreSQL 11 ではメンテナンス操作を並行して実行する機能も導入しています。たとえば、PostgreSQL 11 ではインデックスを並行して作成できます。メンテナンス操作の迅速な実行は、エンドユーザーのパフォーマンスにとっては欠かせないことです。したがって、メンテナンス操作は通常、メンテナンス時間中に実行されます。
以下のステートメントでは、customer
テーブルのすべてのパーティション用のインデックスを作成しています。
操作は 20 秒強で完了します。このセッションではメンテナンスコマンドの並列処理レベルを 4 に上げており、このインデックスはほぼ 3 倍の速度で作成されます。
JIT コンパイル
PostgreSQL 11 で提供されている新機能の 1 つに、ジャストインタイム (JIT) コンパイルがあります。JIT では、SQL 式の一部を CPU が迅速に実行できるネイティブプログラムにコンパイルします。このプログラムを生成するとコストが高くなります。JIT プログラムの生成とクエリの最適化に 1 秒以上かかるクエリもあります。クエリが最適化されると、解釈済みの SQL 式よりも迅速に実行できます。
一般的に、多くの行で計算を実行するクエリではクエリ時間が短縮されます。以下のクエリでは、3 年間の販売状況の合計売上、平均販売価格、最低売上、最高売上を計算します。
このクエリは、約 51 秒で 3 つの集計行から結果を返します。JIT をオンにすると、クエリは同じ 3 行の結果を約 35秒で返します。
JIT を有効にすると、このクエリのパフォーマンスが 30% 向上します。実行に数十秒以上かかるクエリには、JIT を使用するとパフォーマンスを大幅に向上させることができます。ただし、ほとんどの一般的なクエリでは、JIT を有効にするとパフォーマンスが遅くなり、デフォルトで機能を無効にするのと同程度になります。この機能には将来性があるため、インフラストラクチャを整備して PostgreSQL の今後リリースされるバージョンで JIT の機能を改善できるようにしています。
まとめ
この記事では PostgreSQL 11 の 3 つの主な改善点、パーティション分割、並列処理、JIT コンパイルを検討しました。PostgreSQL 11 によって、ストアドプロシージャのトランザクション制御や、インデックスのカバーなどのパフォーマンスも向上します。PostgreSQL 11 の機能の完全なリストについては、PostgreSQL Version 11.1 on Amazon RDS のドキュメントをご覧ください。
PostgreSQL 11 と PostgreSQL と互換性のある Amazon Aurora を組み合わせることで、PostgreSQL コミュニティにある多くの素晴らしい機能にアクセスできるだけでなく、Aurora の持つすべてのメリットを得ることができます。Aurora では、自動フェイルオーバー、バックアップとリカバリ、自動パッチ適用、プッシュボタンのスケーリングなど、マネージド型データベース機能を提供しています。Aurora のストレージではパフォーマンスと耐久性が向上しており、このサービスには高速データベースクローニング、クエリ計画管理、クラスターキャッシュ管理といった、より高度な機能も含まれています。
PostgreSQL 12 が早くも楽しみです。パーティション分割、SQL パスや JSON パスの実装開始など、使いやすさがさらに向上することが期待できます。PostgreSQL データベースは 30 年以上のものですが、プロジェクトはさらに急速に革新を続けています。
著者について
Jim は AWS の主席データベースエンジニアです。AWS 入社前は OpenSCG 社の CTO であり、Postgres-XC のスケーラブルなパブリッククラウドソリューションである StormDB 社の CEO でもありました。Jim は EnterpriseDB 社のチーフアーキテクトであり、創設チームの一員でした。PostgreSQL コミュニティでも非常にアクティブに活動している Jim は、ニューヨークとフィラデルフィア両都市の PostgreSQL Meetup グループを共同で立ち上げ、最大規模の PostgreSQL カンファレンスを共同開催しています。また、PostgreSQL Foundation の理事も務めています。Jim は PostgreSQL および他のオープンソースカンファレンスでも定期的に講演しています。