テーブル継承と宣言的パーティショニングでスケーリングする(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社エンジニアの「?」を「!」に。原著者の許諾を得て翻訳・公開いたします。オープンソースの大規模時系列データベース「timescaledb」を開発する企業です。本記事執筆後間もなく10.1がリリースされました)。10のリリースは、論理レプリケーション、JSONやJSONBでの全文検索サポート、クロスカラム統計、並列クエリのサポート強化といった根本的な新機能を含んでおり、いろいろな意味でエキサイティングです。10は賞賛に値するオープンソースデータベースとして着実な進歩を重ねており、その大きなマイルストーンと言えます。partitioning)があり、これによってPostgreSQLをビッグデータ級のボリュームにスケールさせることが本質的に簡単になります。PostgreSQLをスケーラブルにすることは、将来の成功のために重要です。PostgreSQLでのスケーリングの難しさに、多くの開発者たちが長年不満の声を上げていました。その結果、スケーラビリティを約束する「NoSQL」ソリューション人気を博してそちらに流れる傾向が目立ち、みな熱心にNoSQLソリューションを採用したのです。したNoSQLのオプションは多くの場合、信頼性、パフォーマンス、使いやすさを犠牲にしていました(近年SQLに回帰する開発者が増えた理由のひとつがこれです)。10の基礎部分はこうした他の技術と渡り合えるようになりました。宣言的パーティショニングは、PostgreSQLが提供すべき堅牢さ、信頼性、強力な複合クエリ、おびただしいツールやコネクタのエコシステムを、さらに巨大なビッグデータアプリで最終的に利用できるようにするための第一歩であると考えられます。私はPostgreSQL上で動作する新しい時系列データベースの開発者として、PostgreSQLが追い求める方向性に深い感動を覚えます。10が最初ではありません。多くのユーザーはテーブル継承を長年PostgreSQLテーブルのスケーリングに用いています。宣言的パーティショニングとは、そこに追加された興味深いオプションにすぎません。本記事では、PostgreSQLのパーティショニングで用いられるこれら2つのオプションを吟味することにします。本記事では以下に重点を置きます。私たちは、さまざまな分野の専門知識を兼ね備えた開発者がPostgreSQLに集結するという経験を得られたので、本記事では基本的な事項についていくつか解説したうえで、そうした詳細部分についても追うことにします。最初のセクションから先の部分については皆さまの専門性のレベルに応じて適宜読み飛ばしていただいて結構です。メモ宣言的パーティショニングは、PostgreSQLパーティショニングの新しい進路の第一歩に過ぎません。これを基礎として、この先さらに改良が重ねられる予定です。10の現状についてのみ考察いたします。PostgreSQLだけでは自然なスケールがうまくいかない理由これは、RAMとディスクの間のパフォーマンスの違いに由来します。KB)、システムはそれらの上にデータをインデックス化するためのデータ構造(ヒープなど)を構築します。銀行の口座番号)を持つ行をクエリで素早く検索できます。これにより、そのためにテーブル全体を何らかのソート順に基いて片っ端からスキャンする必要がなくなります。インデックスに使うすべての(ほぼ固定サイズの)ページがメモリ上に収まりきれなくなるほどの大規模データになると、ツリーのさまざまな部分をランダムに更新するときに(ページをディスクからメモリに読み込み、メモリ上で変更し、他のページの場所を空けるためにディスクに書き戻す)ディスクI/Oが著しく増加します。PostgreSQLのようなリレーショナルデータベースは、インデックスの値を効率よく検索するために、各デーブルで用いるインデックス(やB-treeなど)を保持し続けます。インデックスを付けるカラム数が増えるに連れて、問題が複合的に発生します。PostgreSQLにおけるメモリのスワップイン/スワップアウトのコストについては、以下のパフォーマンスグラフでご覧いただけます。テーブルサイズの増加に伴ってINSERTのスループットが分散しつつ急落しています。これは、リクエストがメモリでヒットするか、ディスクからのフェッチ(複数の可能性あり)を要するかによって変わります。v2(8コア)マシン+DDSベースのプレミアムLRSストレージ環境)におけるテーブルサイズの関数としてのINSERTのスループットです。計12個のカラムを含む)。PostgreSQLの測定結果は当初15000件(INSERT/秒)ですが、5千万行を超えると著しく低下し、分散の度合いも極めて大きくなります(中にはINSERT/秒が100件台のものまであります)。(あるベンチマークでは素のPostgreSQLでのINSERTのパフォーマンスは10億行にも達します。1万行のバッチINSERTの結果についてはこちらでご覧いただけます)パーティショニングというソリューションテーブルのパーティショニングは、ディスクへのスワップの高コスト問題を回避するうえで有用です。巨大なテーブルをパーティションに分割することで得られるサブテーブルのインデックスはINSERT中もメモリに保持されます(正しくインデックス化されていればの話です)。これにより、以下を含む多くのメリットを得られます。TABLEだけで行え、高コストのVACUUM操作を回避できるため)巨大なテーブルの代わりにパーティショニングを検討すべき正確なポイントは、負荷やマシンリソースによって異なります。上述のグラフでは、表記の負荷およびマシンスペックにおけるPostgreSQLのパフォーマンスが5千万行を超えたところで低下しています。メモリを追加すれば、この問題の発生ポイントを押し上げられます。一般に、PostgreSQLテーブルに巨大なデータを書き込むのであれば、いずれどこかでパーティショニングは必要になります。10登場前のテーブルパーティショニングは、テーブル継承という形でのみ実行可能でした。10以後も、宣言的パーティショニングでは力不足な状況において有用なパーティショニングオプションのひとつです。テーブル継承を用いてパーティショニングするには、まず親テーブルを1つ作成してから子テーブルをパーティションごとに作成します。TABLEステートメントでINHERITS句を用いて行います(継承リンクが作成されます)。親テーブルにクエリをかけると、親テーブルと子テーブル群の両方からデータが返されます。具体的には、テーブル継承を実装するために以下の操作が必要です。親テーブルを作成する(すべてのパーティションはこれを継承する)。子テーブルを複数作成する(各子テーブルはデータのパーティション1つを表し、親テーブルを継承する)。パーティションテーブルに制約を追加することで、パーティションごとの行の値を定義する。親テーブルや子テーブルのインデックスを個別に作成する(インデックスは親テーブルから子テーブルに展開されない)。マスターテーブルに最適なトリガ関数を1つ作成して、親テーブルへのINSERTを適切なパーティションテーブルに振り分ける。このトリガ関数を呼び出すトリガを1つ作成する。子テーブルのセットが変更される場合は、必ずトリガ関数を再定義すること。テーブル継承は長年の間、開発者がPostgreSQLの扱いにくいテーブルをパーティショニングするための有用な手段でありつづけました。テーブル継承が提供する柔軟性も便利です。親テーブルに現在ないカラムを子テーブルが余分に持つことを許可して、ユーザーの指定に沿った形でデータを分割できます。上述の手順からわかるように、テーブル継承では手動の操作の手間がかなり増えてしまいます。一部の手順についてはスクリプトで自動化できますが、それでもエンジニアがつきっきりで作業しなければならず、オーバーヘッドが増大します。テーブル継承にはいくつかの制限事項もありますデータの一貫性は、子テーブルごとに指定するCHECK制約に依存します。INSERTコマンドやCOPYコマンドを行っても、データは継承階層内の他の小テーブルに自動では反映されませんが、かといってトリガに頼ると今度はINSERTが遅くなります。子テーブルの作成と維持には、本質的に手動の作業が欠かせません。3に関連しますが、パーティショニングの再編成作業はかなりの手間です(ディスクの追加やスキーマ変更など)。パーティション化されたテーブル全体に渡る一意性の確保はサポートされていません。子テーブルには、インデックスや制約といったさまざまなメンテナンス用コマンドを明示的に適用する必要があり、管理タスクが著しく複雑になります。10に導入されました。10の宣言的パーティショニングは、上述の問題1と2を解決します。3の問題についても単純化されますが、それでも結構な量の手動操作や制限事項がつきまといます。10の宣言的パーティショニング宣言的なテーブルパーティショニングは、PostgreSQLのデータをパーティショニングときに必要な作業量を削減します。これは、パーティション化テーブルや子テーブルパーティションの作成構文を改良することで達成されました。現時点の宣言的パーティショニングではRANGEパーティションとLISTパーティションがサポートされています。テーブルを、キーカラムやカラムセットで定義される「範囲」にパーティショニングします。異なるパーティションに振り分けられた値(device_idなど)は重複しません。テーブルを、各パーティションにキーのどの値があるかというリストを明示することでパーティショニングします。適用対象は単一のカラム(device_typeなど)のみとなります。宣言的パーティショニングのしくみ最初にパーティションテーブルを1つ作成します。BY句を用いてパーティション化テーブルを1つ作成します。10ドキュメントをそのまま引用しました)。(logdate);パーティション化テーブルを作成すると、次のようにパーティションを手動で作成できます。('2007--12--01')続いてインデックスを作成できます。(logdate);パーティション化テーブルの動作は、テーブル継承の親(マスター)テーブルと似ていますが、大きく改良されていて、背後にある子テーブルのパーティションに対してはるかに強力なクエリインターフェイスを提供します。TRUNCATEやCOPYといったコマンドをパーティション化テーブル経由で子テーブルに対して実行できます。パーティション化テーブルを経由してデータを背後の子テーブルにINSERTできます。これが可能なのはINSERT時にタプル(行など)が正しいパーティションにルーティングされるためで、テーブル継承のときのようにトリガに頼る必要がありません。以上のように、テーブル継承の8つの手順は宣言的パーティショニングで劇的にシンプルになります。3つの手順で手動操作が削減され、ユーザーにとってわかりやすいものになりました。INSERTの自動化や、パーティション化テーブル経由でのテーブルコマンドの追加反映も大きな成果です。宣言的パーティショニングは、PostgreSQLの新しいパーティショニングの第一歩に過ぎません。私たちが理解した限りにおいてですが、テーブル継承と対照的に、パーティショニングへの追加制約(親テーブル自身はデータを保持してはならないなど)が宣言的パーティショニングに導入されたことで、さらに強力なクエリ最適化などの機能を今後PostgreSQLで構築する準備が整いました。10の宣言的パーティショニングのほとんどの改良点はパーティション定義のインターフェイスや構文に集中していますが、今後はこの構造を用いてさらに高度な機能や最適化を実装できるようになるはずです。宣言的パーティショニングは、PostgreSQLのパーティショニングの自動化を進めるうえで重要な一歩を踏み出していることは間違いありません。制限事項がいくつもあります。手動によるパーティション管理パーティション管理作業は宣言的パーティショニングによって楽になりましたが、それでも必要な手動操作がかなり残っています。データの子テーブルは、データがINSERTされる前に存在している必要があります。データが成長・進化していると、これが管理上の困難になるかもしれません。時期を基準にパーティショニングする場合、新しいデータがやってきたときにシステムが自動で新しいパーティションを新規作成してくれると便利です。現時点ではデータベース管理者が手動で操作しなければなりません。使いもしないパーティションを事前に多数作成するとパフォーマンスに悪影響が生じる可能性もあります。訳文はリンク先から引用し、原文に合わせて調整しました)。すべてのパーティションに適合するインデックスを自動的に作成する機能はありません。インデックスは個々のパーティションについて別々のコマンドで追加しなければなりません。行をあるパーティションから別のパーティションに移動させることになるUPDATEは失敗します。行トリガーが必要であれば、個々のパーティションに定義されなければなりません。した制限事項から、テーブル作成時には現在もテーブル空間(tablespace)の割り当てやインデックス・制約の作成は適宜手動で行う必要があることが見て取れます。同様に、親テーブルを変更する場合は、個別のパーティションにも手動でインデックスや制約の変更をかける必要があります。CLUSTERやREINDEXといった多くのコマンドを個別の子テーブルに適用する必要もあります。10における個別のパーティション管理は、パーティションテーブルのインターフェイス経由ですべて賄えるわけではありません。追加操作が必要とされているため、特にパーティションを後から繰り返し追加したときやデータモデルに変更が生じたときに、データ管理上のボトルネックが形成されてしまう可能性があります。多次元パーティショニングはつらいよスケーラビリティやクエリのさらなる改良のために重要となる「多次元パーティショニング」では、既存パーティション上に最初のパーティションが作成された後に手動でサブパーティションを作成しなければなりません。例として、LIST(デバイスなど)というディメンジョンがひとつ、RANGE(期間など)というディメンジョンがひとつあるとすると、これらをパーティショニングするには、最初にすべてのLISTパーティションを作成してから、それぞれの子テーブルでそれに対応するRANGEサブパーティションを作成する必要があります(逆も同様)。これによってテーブルのツリー階層が形成され、端点(leaf)のサブパーティションが効率よくタプルを保持します。'sensor3');複数に渡る手順による処理と、追加のディメンジョンやパーティションのサブレベルがあることでさらに複雑化が進むため、使いもしない巨大な継承ツリーを作ると、INSERTやクエリの両方で処理する必要のあるテーブル数が増加し、パフォーマンスやスケーラビリティに悪影響が生じる可能性があります。10でサポートされていないものがあります。パーティション上に主キーを作成できません。パーティション化テーブルを参照する外部キーも、パーティション化テーブルから別のテーブルを参照する外部キーも、サポート対象外です。1つのパーティション化テーブル全体に渡る一意制約(排他制約)はサポートされません。一意制約や排他制約は、個別のパーティション上にのみ作成できます。11の機能候補にはあるようです)。update+insert)を利用しているのを目にしますので、そうした分野のアプリではかなりハードルが高くなるかもしれません。主キーが使えない点も、主キーの存在を前提とするORMユーザーにとって問題になるかもしれません。最後に宣言的パーティショニングは、PostgreSQLの巨大データセットでのスケーリングを推進するうえで重要な一歩です。10では、よりシンプルなパーティション定義構文が導入され、タプルもルーティングできるようになりました。どちらもパーティショニング設定を簡単にしてくれるものであり、さまざまな場面で恩恵を得られます。11以降の機能候補に挙がっています)。管理方法や使い勝手の改善の余地も、まだまだたっぷり残されています。PostgreSQLのコア開発とは別立てで、(PostgreSQLの拡張性の高さのおかげで)TimescaleDBなどの拡張を用いてパーティションの管理や使い勝手を大きく改良することもできます。PostgreSQLはこれらの拡張によって、指数関数的に増加する現実世界の時系列データのような、従来のOLTP(オンライントランザクション処理)の範疇を超える多様な負荷を支えることができます。data』をご覧ください。本記事を気に入っていただけましたら、ぜひ元記事へコメントお願いします。TimescaleDBについて詳しくお知りになりたい方は、GitHubのtimescale/timescaledbをご覧いただき(★追加はいつでも大歓迎です?)、メールにてお問い合わせください。コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。enno.jpを運営。実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。Storageを使ってはいけない(翻訳)#Tags#会社紹介#自社サービス#勉強会#Rails#PHP#デザイン#実績#週刊RailsウオッチCONTACTお気軽にご相談ください。(翻訳)CONTACTTechRachoでは、パートナーシップをご検討いただける方からのご連絡をお待ちしております。お気軽にご意見・ご相談ください。