Postgresデータベースのスケーリングは、アプリケーションを成長させるための通過儀礼です。テーブルが数百万行、さらには数十億行にまで拡大すると、かつては快調だったクエリが遅れ始め、インフラストラクチャ コストの増加が収益に長い影を落とし始めます。あなたは難問に陥っています。愛用している PostgreSQL を手放したくないのですが、増大するデータセットを処理するより効果的な方法が必要なようです。
この記事では、 PostgreSQL のスケーラビリティを向上させるために、柔軟で高性能な列圧縮メカニズムをどのように構築したかについて説明します。列型ストレージと特殊な圧縮アルゴリズムを組み合わせることで、他のリレーショナル データベースでは比類のない驚異的な圧縮率 (+95 %) を達成できます。
データセットを圧縮すると、PostgreSQL データベースをさらに拡張できます。この記事全体で見ていきますが、この非常に効果的な圧縮設計により、大規模な PostgreSQL テーブルのサイズを最大 10 ~ 20 分の 1 に削減できます。クエリのパフォーマンスを向上させながら、より多くのデータをより小さなディスクに保存できます (コストの節約とも言えます)。タイムスケール圧縮も完全に変更可能であるため、データベースの管理と操作が簡単になります。圧縮されたテーブルの列を追加、変更、削除したり、データを直接 INSERT、UPDATE、DELETE したりできます。
よりスケーラブルな PostgreSQL へようこそ!
segmentby
列ごとにグループ化するsegmentby
列の定義orderby
による高度な微調整
しかし、どのように圧縮を構築したかの詳細に入る前に、次の質問に数分かけて答えてみましょう。なぜ PostgreSQL に新しいデータベース圧縮メカニズムを追加する必要があるのでしょうか?
まず、最新のアプリケーションのニーズとソフトウェアの歴史について理解しましょう。
私たちは Postgres が大好きです。その信頼性、柔軟性、豊富なエコシステムの組み合わせは他のデータベースで匹敵するのが非常に難しいため、Postgres がアプリケーションを構築するための最良の基盤であると信じています。しかし、Postgres が誕生したのは数十年前です。この堅牢性には欠点も伴います。
現在、開発者は PostgreSQL を、よく知られている従来の OLTP (オンライン トランザクション処理) のユースケースをはるかに超えて使用しています。 24 時間年中無休で実行され、増え続けるデータを処理する、データ集約型で要求の厳しいアプリケーションの多くは、PostgreSQL を利用しています。
PostgreSQL データベースは、交通管理システム、ユーティリティ ネットワーク、公共安全モニターからストリーミングされる膨大な量のセンサー データを取り込むために使用されています。
エネルギー会社は PostgreSQL を使用して、スマート グリッドや再生可能エネルギー源からのメトリクスを保存および分析しています。
金融分野では、PostgreSQL は市場のティック データをリアルタイムで追跡するシステムの中核となっています。
電子商取引プラットフォームは、PostgreSQL を使用して、ユーザー インタラクションによって生成されたイベントを追跡および分析しています。
Postgres は、AI アプリケーションの新しい波を推進するベクトル データベースとしても使用されています。
その結果、Postgres テーブルは非常に急速に成長しており、本番環境ではテーブルが数十億行に達することが新たな標準となっています。
残念ながら、PostgreSQL にはこの量のデータを処理する能力が元々備わっていません。クエリのパフォーマンスが低下し始め、データベース管理が困難になります。これらの制限に対処するために、私たちは
PostgreSQL 用の高性能圧縮メカニズムの構築も同様に重要なロック解除でした。これらの増え続けるデータセットは、優れたパフォーマンスを得るのが困難であるだけでなく、データの蓄積によりディスクがますます大きくなり、ストレージ料金が高額になります。 PostgreSQL にはソリューションが必要でした。
では、PostgreSQL の既存の TOAST メソッドはどうなるのでしょうか?すごい名前🍞😋にもかかわらず、
TOAST は、PostgreSQL が個々のデータベース ページに収まらない大きな値を保存および管理するために使用する自動メカニズムです。 TOAST にはこれを実現する技術の 1 つとして圧縮が組み込まれていますが、TOAST の主な役割はストレージ領域を全体的に最適化することではありません。
たとえば、小さなタプルで構成された 1 TB のデータベースがある場合、TOAST は、どれだけ微調整を試みても、体系的にその 1 TB を 80 GB に変換することはできません。 TOAST は、2 KB のしきい値を超えると、連続して大きな属性を自動的に圧縮しますが、TOAST は小さな値 (タプル) には役に立ちません。また、1 か月より古いすべてのデータを圧縮するなど、より高度なユーザー構成可能な構成を適用することもできません。特定のテーブルで。 TOAST の圧縮は、より広範なテーブルやデータセットの特性ではなく、個々の列値のサイズに厳密に基づいています。
TOAST は、特に頻繁にアクセスされるサイズの大きい列を含む大きなテーブルの場合、重大な I/O オーバーヘッドを引き起こす可能性があります。このような場合、PostgreSQL は TOAST テーブルからアウトオブライン データを取得する必要があります。これは、メイン テーブルへのアクセスとは別の I/O 操作です。PostgreSQL は、メイン テーブルから TOAST テーブルへのポインタをたどって、完全なデータ。これは通常、パフォーマンスの低下につながります。
最後に、TOAST の圧縮は、すべてのデータ型に対して 1 つの標準アルゴリズムを使用するため、特に高い圧縮率を提供するように設計されていません。
TOAST について簡単に説明することは、データを効果的に圧縮する際の PostgreSQL の制限を理解するのにも役立ちます。先ほど見たように、TOAST の圧縮はデータを行ごとに処理しますが、この行指向のアーキテクチャは、圧縮アルゴリズムが成功する均一性を分散させ、圧縮の運用可能性に関する根本的な上限につながります。これが、ストレージの最適化に関してリレーショナル データベース (ネイティブ Postgres など) が不十分なことが多い根本的な理由です。
これを詳しく見てみましょう。従来、データベースは次の 2 つのカテゴリのいずれかに分類されます。
行指向データベースはデータを行ごとに編成し、各行には特定のレコードのすべてのデータが含まれます。これらは、レコードの挿入、更新、削除が頻繁に行われるトランザクション処理向けに最適化されており、操作に個々のレコードやデータの小さなサブセットが含まれる (特定の顧客に関するすべての情報の取得など) OLTP システムにとって効率的です。
一方、列指向 (別名「列型」) データベースは、データを列ごとに編成します。各列には、複数のレコードにわたる特定の属性のすべてのデータが格納されます。これらは通常、クエリに多くのレコードにわたる集計や操作が含まれる OLAP システム (オンライン分析処理) 用に最適化されています。
これを例で説明してみましょう。 user_id
、 name
、 logins
、およびlast_login
の 4 つの列を持つusers
テーブルがあるとします。このテーブルに 100 万人のユーザーのデータが格納されている場合、実質的に 100 万行と 4 列があり、各ユーザーのデータ (つまり各行) がディスク上に物理的に連続して格納されます。
この行指向の設定では、user_id = 500,000 の行全体が連続して保存されるため、検索が高速になります。その結果、浅くて広いクエリは行ストアで高速になります (例: 「ユーザー X のすべてのデータをフェッチする」)。
-- Create table CREATE TABLE users ( user_id SERIAL PRIMARY KEY, name VARCHAR(100), logins INT DEFAULT 0, last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Assume we have inserted 1M user records into the 'users' table -- Shallow-and-wide query example (faster in row store) SELECT * FROM users WHERE user_id = 500000;
対照的に、列指向ストアでは、すべてのuser_id
、すべての名前、すべてのログイン値などがまとめて格納されるため、各列のデータはディスク上に連続して格納されます。このデータベース アーキテクチャは、「すべてのユーザーの平均ログイン数を計算する」など、深くて狭いクエリに適しています。
-- Deep-and-narrow query example (faster in column store) SELECT AVG(logins) FROM users;
列指向ストアは、広いデータに対する狭いクエリで特にうまく機能します。列指向データベースでは、平均を計算するためにlogins
列データのみを読み取る必要があり、これはディスクから各ユーザーのデータセット全体をロードすることなく実行できます。
もうお気づきかと思いますが、データを行と列に格納することは、データをどの程度圧縮できるかにも影響します。列指向データベースでは、データの個々の列は通常同じタイプであり、多くの場合、より限定されたドメインまたは範囲から抽出されます。
結果として、列指向のストアは通常、行指向のデータベースよりも圧縮率が高くなります。たとえば、以前のlogins
列はすべて整数型であり、おそらく狭い範囲の数値のみで構成されていました (したがって、エントロピーが低く、適切に圧縮されます)。これを行指向形式と比較してください。この形式では、幅広いデータ行全体がさまざまなデータ型と範囲で構成されます。
ただし、OLAP スタイルのクエリと圧縮性に利点があるとしても、列指向ストアにはトレードオフがないわけではありません。
個々の行を取得するクエリはパフォーマンスが大幅に低下します (実行できない場合もあります)。
これらのアーキテクチャは、従来の ACID トランザクションにはあまり適していません。
多くの場合、列型ストアでは更新を実行できません。
行ベースのストアでは、
行ストアを使用すると、データセットの正規化が容易になり、関連するデータセットを他のテーブルにより効率的に格納できるようになります。
それでは、行指向と列指向のどちらが優れているのでしょうか?
従来は、ワークロードに応じて両方のトレードオフを評価していました。典型的な OLTP の使用例を実行している場合は、おそらく PostgreSQL のような行指向のリレーショナル データベースを選択するでしょう。ユースケースが明らかに OLAP である場合は、 ClickHouse のような柱型ストアに傾くかもしれません。
しかし、実際にワークロードに両方が混在している場合はどうなるでしょうか?
アプリケーションのクエリは通常、浅くて広範囲で、個々のクエリがデータの多くの列や、さまざまなデバイス/サーバー/アイテムにわたるデータにアクセスする可能性があります。たとえば、特定の製造工場内のすべてのセンサーについて最後に記録された温度と湿度を表示する必要がある、ユーザー向けの視覚化を強化しているとします。このようなクエリでは、構築基準に一致するすべての行にわたる複数の列にアクセスする必要があり、その範囲は数千または数百万のレコードに及ぶ可能性があります。
ただし、一部のクエリは深くて狭い場合もあり、個々のクエリでは、長期間にわたって特定のセンサーに対して少数の列が選択されます。たとえば、異常を検査するために、過去 1 か月間にわたる特定のデバイスの温度傾向を分析する必要がある場合もあります。このタイプのクエリは単一の列 (温度) に焦点を当てますが、対象期間の各時間間隔に対応する多数の行からこの情報を取得する必要があります。
アプリケーションはデータ集約型で、挿入 (追加) が多い場合もあります。前に説明したように、1 秒あたり数十万件の書き込みを処理するのが新しい標準です。データセットもおそらく非常に細分化されており、たとえばデータを毎秒収集している可能性があります。前の例を続けると、ユーザー向けの視覚化をリアルタイムで強化するために、データベースは継続的な読み取りとともにこれらの大量の書き込みを処理する必要があります。
データは主に追加ですが、必ずしも追加のみである必要はありません。古いレコードを時々更新したり、遅れて到着したデータや順序が狂ったデータを記録する必要がある場合があります。
このワークロードは、従来の意味での OLTP や OLAP ではありません。代わりに、両方の要素が含まれています。じゃあ何をすればいいの?
ハイブリッドに移行しましょう!
前の例のようなワークロードに対応するには、単一のデータベースに次のものを含める必要があります。
1 秒あたり数十万回の書き込みでも容易に高い挿入レートを維持できる機能
遅れたデータまたは順序が狂ったデータの挿入、および既存のデータの変更のサポート
大規模なデータセットにわたる浅くて広いクエリと深くて狭いクエリの両方を効率的に処理するための十分な柔軟性
データベースのサイズを大幅に削減してストレージ効率を向上できる圧縮メカニズム
これは、TimescaleDB (したがって PostgreSQL) に列指向圧縮を追加するときに達成することを目的としていました。
前のセクションで述べたように、PostgreSQL を拡張してパフォーマンスとスケーラビリティを向上させ、次のような要求の厳しいワークロードに適した TimescaleDB を構築しました。
理論的には、これは、TimescaleDB も、適度な圧縮率を持つ PostgreSQL の行指向ストレージ形式にロックされていることを意味します。実際には、ちょっとしたエンジニアリングで解決できないことは何もありません。
二つの観察。初め、
実際、この行から列への変換をデータベース全体に適用する必要はありません。 Timescale ユーザーは、PostgreSQL テーブルを行と列のハイブリッド ストアに変換し、列形式で圧縮するデータを正確に選択できます。
これが実際にどのように機能するかを例を使って説明しましょう。複数のデバイスから毎秒測定値を収集し、タイムスタンプ、デバイス ID、ステータス コード、温度などのデータを保存する温度監視システムを想像してください。
最新の温度データに効率的にアクセスするには、特にさまざまなデバイスからの最新の測定値を分析する必要がある操作クエリの場合、最新のデータ (例: 先週) を従来の非圧縮の行指向の PostgreSQL 構造に保持できます。 。これは高い取り込み速度をサポートし、最近のデータに関するポイント クエリにも最適です。
-- Find the most recent data from a specific device SELECT * FROM temperature_data WHERE device_id = 'A' ORDER BY timestamp DESC LIMIT 1; -- Find all devices in the past hour that are above a temperature threshold SELECT DISTINCT device_id, MAX(temperature) FROM temperature WHERE timestamp > NOW() - INTERVAL '1 hour' AND temperature > 40.0;
ただし、このデータが数日経過すると、以前のような浅くて広範囲のクエリは頻繁に実行されなくなり、代わりに深くて狭い分析クエリがより一般的になります。したがって、このタイプのクエリのストレージ効率とクエリ パフォーマンスを向上させるために、1 週間より古いすべてのデータを高度に圧縮された列形式に変換することを自動的に選択できます。 Timescale でこれを行うには、次のような圧縮ポリシーを定義します。
-- Add a compression policy to compress temperature data older than 1 week SELECT add_compression_policy('temperature_data', INTERVAL '7 days');
データが圧縮されたら、温度データに対して詳細かつ限定的な分析クエリを実行すると (特定のデバイス上であれ、多数のデバイス間であれ)、最適なクエリ パフォーマンスが表示されます。
-- Find daily max temperature for a specific device across past year SELECT time_bucket('1 day', timestamp) AS day, MAX(temperature) FROM temperature_data WHERE timestamp > NOW() - INTERVAL '1 year' AND device_id = 'A' ORDER BY day; -- Find monthly average temperatures across all devices SELECT device_id, time_bucket('1 month', timestamp) AS month, AVG(temperature) FROM temperature_data WHERE timestamp < NOW() - INTERVAL '2 weeks' GROUP BY device_id, month ORDER BY month;
行形式から列形式への「移行」をどのように表現すればよいでしょうか? Timescale のハイパーテーブルは、タイムスタンプやその他のシリアル ID 列などの分割キーに基づいて、データを「チャンク」に分割します。次に、各チャンクには、そのパーティショニング キーの特定の範囲のタイムスタンプまたはその他の値に対応するレコードが格納されます。上記の例では、最新のチャンクが行形式のままとなり、古い週はすべて列形式に変換されるように、気温データが週ごとにパーティション化されます。
このハイブリッド行列ストレージ エンジンは、ストレージの設置面積を大幅に削減しながら、大規模な PostgreSQL データベースでのクエリ パフォーマンスを最適化する非常に強力なツールです。この記事の後半で説明するように、データを列指向形式に変換し、特殊な圧縮メカニズムを適用することにより、分析クエリを高速化できるだけでなく、最大 98% の圧縮率も達成します。これがストレージの請求にどのような影響を与えるか想像してみてください。
クエリのパフォーマンスとストレージの節約について詳しく説明する前に、まずこのメカニズムが内部でどのように機能するか、つまり行から列への変換が実際にどのように実行されるか、および圧縮が列データにどのように適用されるかについて説明します。
圧縮ポリシーが開始されると、基本的に、元の PostgreSQL ハイパーテーブル内の従来の多数の個別レコード (1,000 行が密集していると想像してください) が、単一のよりコンパクトな行構造に変換されます。この圧縮された形式では、各属性または列に各行の単一のエントリが格納されなくなります。代わりに、これら 1,000 行の対応するすべての値の連続した順序付けられたシーケンスがカプセル化されます。これらの 1,000 行をバッチと呼びます。
これを説明するために、次のようなテーブルを想像してみましょう。
| Timestamp | Device ID | Status Code | Temperature | |-----------|-----------|-------------|-------------| | 12:00:01 | A | 0 | 70.11 | | 12:00:01 | B | 0 | 69.70 | | 12:00:02 | A | 0 | 70.12 | | 12:00:02 | B | 0 | 69.69 | | 12:00:03 | A | 0 | 70.14 | | 12:00:03 | B | 4 | 69.70 |
このデータを圧縮用に準備するために、Timescale はまずこの表形式のデータを列形式のストアに変換します。データのバッチ (約 1,000 行) が与えられると、各列のデータが配列に集約され、各配列要素は元の行の 1 つの値に対応します。このプロセスの結果は 1 行になり、各列にはそのバッチからの値の配列が格納されます。
| Timestamp | Device ID | Status Code | Temperature | |------------------------------|--------------------|--------------------|-------------------------------| | [12:00:01, 12:00:01, 12...] | [A, B, A, B, A, B] | [0, 0, 0, 0, 0, 4] | [70.11, 69.70, 70.12, 69....] |
この形式は、圧縮アルゴリズムを適用する前であっても、Timescale の行ごとの内部オーバーヘッドを大幅に削減することにより、ストレージを即座に節約します。 PostgreSQL は通常、行ごとに最大 27 バイトのオーバーヘッドを追加します (たとえば、マルチバージョン同時実行制御または MVCC の場合)。したがって、圧縮を行わなくても、上記のスキーマがたとえば 32 バイトの場合、以前は[1,000 * (32 + 27)] ~= 59 キロバイトかかっていたバッチからの 1,000 行のデータは、[1,000 * 32 + 27]かかります。 ] ~= この形式では 32 キロバイト。
[余談: 大きなテーブルを小さなバッチに「グループ化」し、(テーブル全体の列ではなく) 各バッチの列を連続して格納するというこの概念は、実際には、Apache Parquet ファイル形式の「行グループ」と同様のアプローチです。私たちはその類似性に後になって初めて気づきましたが!]
しかし、この変換の大きな利点は、同様のデータ (タイムスタンプ、デバイス ID、温度測定値など) が連続して保存される形式を考慮して、型固有の圧縮アルゴリズムを適用して、各配列を個別に圧縮できることです。 。これが、Timescale が優れた圧縮率を実現する方法です。
Timescale は、次の圧縮アルゴリズムを自動的に採用します。これらのアルゴリズムはすべて「
デルタオブデルタ +
少数の繰り返し値を含む列の行全体の辞書圧縮 (+ 上部の LZ 圧縮)
他のすべてのタイプの LZ ベースの配列圧縮
Gorilla と Simple-8b を拡張して、データの解凍を逆順で処理できるようにし、逆方向スキャンを使用するクエリを高速化できるようにしました。
このタイプ固有の圧縮は非常に強力であることがわかりました。高い圧縮率に加えて、Gorilla やデルタオブデルタなどの一部の技術は、デコード中に LZ ベースの圧縮よりも最大 40 倍高速になり、クエリのパフォーマンスが大幅に向上します。 。
データを解凍するとき、Timescale はこれらの個別の圧縮バッチを操作し、バッチごとに要求された列のみを解凍します。したがって、元々 100 万行のデータが含まれていたテーブル チャンクから 20 バッチ (元のデータ 20,000 行に相当) だけを処理する必要があるとクエリ エンジンが判断できた場合、クエリは読み取りと解凍が行われるため、より高速に実行できます。データがかなり少なくなります。それがどのように行われるかを見てみましょう。
以前の配列ベースの形式には課題があります。つまり、クエリを解決するためにデータベースがどの行をフェッチして解凍する必要があるかということです。
もう一度温度データの例を見てみましょう。時間範囲によるデータの選択と順序付け、デバイス ID に基づくデータの選択 (WHERE 句または GROUP BY による) など、いくつかの自然なタイプのクエリが何度も出現します。このようなクエリを効率的にサポートするにはどうすればよいでしょうか?
ここで、前日のデータが必要な場合、クエリは圧縮配列の一部となっているタイムスタンプ データをナビゲートする必要があります。それでは、データベースは最近のデータを見つけるためにチャンク全体 (またはハイパーテーブル全体) を解凍する必要があるのでしょうか?
あるいは、圧縮配列 (前述) にグループ化された個々の「バッチ」を識別できたとしても、さまざまなデバイスからのデータが散在しているため、配列全体を解凍して、特定のデバイスに関するデータが含まれているかどうかを確認する必要があるのでしょうか?この単純なアプローチでも良好な圧縮率が得られますが、クエリのパフォーマンスの観点からはそれほど効率的ではありません。
特定のクエリのデータを列形式で効率的に検索して解凍するという課題を解決するには、
segmentby
列ごとにグループ化するTimescale のデータは、最初にチャンクごとに圧縮された列形式に変換されることを思い出してください。特定の列に基づいてフィルタリングするクエリ (例: device_id
による頻繁なクエリ) の効率を高めるために、この特定の列を「」として定義するオプションがあります。
これらのsegmentby
列は、各圧縮チャンク内のデータを論理的に分割するために使用されます。圧縮エンジンは、上に示したように任意の値の圧縮配列を構築するのではなく、最初に同じsegmentby
キーを持つすべての値をグループ化します。
したがって、device_id A に関する 1,000 行のデータは単一の圧縮行に格納される前に高密度にバックアップされ、device_id B に関する 1,000 行というように続きます。したがって、 device_id
segmentby
バイ列として選択された場合、圧縮された各行には、特定のデバイス ID に関するデータの圧縮された列状バッチが含まれ、その行には圧縮されずに保存されます。 Timescale はさらに、圧縮されたチャンク内のこれらのセグメントバイ値にインデックスを構築します。
| Device ID | Timestamp | Status Code | Temperature | |-----------|--------------------------------|-------------|-----------------------| | A | [12:00:01, 12:00:02, 12:00:03] | [0, 0, 0] | [70.11, 70.12, 70.14] | | B | [12:00:01, 12:00:02, 12:00:03] | [0, 0, 4] | [69.70, 69.69, 69.70] |
このデータの連続ストレージによりsegmentby
列でフィルタリングされたクエリの効率が大幅に向上します。 device_id
でフィルタリングされたクエリ ( device_id
がsegmentby
列) を実行すると、Timescale は指定されたデバイス ID を持つチャンク内のすべての圧縮行を (インデックスを介して) 素早く選択し、データを素早くスキップします (そして解凍を回避します)。 ) 要求されたデバイスに関係のないデータ。
たとえば、次のクエリでは、Timescale は device_id A のデータを含む圧縮行のみを効率的に見つけて処理します。
SELECT AVG(temperature) FROM sensor_data WHERE device_id = 'A' AND time >= '2023-01-01' AND time < '2023-02-01';
さらに、タイムスケール ハイパーテーブルには、チャンクがカバーする値の範囲を指定する各チャンクに関連付けられたメタデータが格納されます。したがって、ハイパーテーブルが週ごとにタイムスタンプ パーティション化されている場合、クエリ プランナーが上記のクエリを実行すると、1 月をカバーする 4 ~ 5 個のチャンクのみを処理することが認識され、クエリのパフォーマンスがさらに向上します。
segmentby
列の定義最初にハイパーテーブルの圧縮を有効にするときに、segmentby に使用する列を指定できます。使用する列の選択は、クエリでよく使用される列に基づいて行う必要があります。実際、複数の列を使用してセグメント化できます。たとえば、device_id でバッチをグループ化するのではなく、同じ tenant_id と device_id の両方を持つバッチをグループ化できます。
ただし、選択性を高めすぎないように注意してください。segmentby 列を定義しすぎると、segmentby 列が追加されるたびにデータがより小さなバッチに効果的に分割されるため、圧縮の効率が低下します。
データの 1,000 レコード バッチを作成できなくなり、特定のチャンク内に指定されたセグメントバイ キーを持つレコードが 5 つしかない場合、圧縮はまったくうまくいきません。
ただし、セグメント化する列を特定したら、ハイパーテーブルで圧縮を有効にするときにそれらの列を簡単に構成できます。
ALTER TABLE temperature_data SET ( timescaledb.compress, timescaledb.compress_segmentby = 'device_id' );
orderby
による高度な微調整TimescaleDB は、 compress_orderby
パラメーターで指定された各チャンク内の戦略的なデータ順序付けを通じて、圧縮データに対するクエリのパフォーマンスを強化します。タイムスタンプ (時系列データの一般的なパーティション キー) による順序付けのデフォルト設定はほとんどのシナリオに適していますが、この最適化を理解することは有益です。さらに詳しい技術的な観点については、以下をお読みください。
週単位のチャンクと、1 日に関するデータのみを要求するクエリの例をもう一度考えてみましょう。タイムスタンプ インデックスを持つ通常のテーブルでは、クエリはこのインデックスを効率的にたどってその日のデータを見つけることができます。
ただし、圧縮データの場合は状況が異なります。タイムスタンプは圧縮されているため、バッチ全体を解凍しないとアクセスできません。個々のタイムスタンプにインデックスを作成すると、サイズが大きくなりすぎて圧縮の利点が無効になる可能性があるため、逆効果になります。
Timescale は、基本的にタイムスタンプに従ってバッチ化されるデータを「並べ替え」ることによってこの問題に対処します。次に、各バッチの最小および最大タイムスタンプに関するメタデータを記録します。クエリが実行されると、このメタデータにより、クエリ エンジンはどの圧縮行 (バッチ) がクエリの時間範囲に関連するかを迅速に識別できるため、完全な解凍の必要性が減ります。
この方法論は、segmentby 列の使用とうまく連携します。圧縮プロセス中、データは最初にsegmentby列によってグループ化され、次にorderbyパラメータに基づいて順序付けされ、最後にタイムスタンプ順に並べられた、それぞれ最大1,000行を含む小さな「ミニバッチ」に分割されます。
TimescaleDB のセグメント化と順序付けを組み合わせると、一般的な時系列クエリと分析クエリのパフォーマンスが大幅に向上します。時間 ( orderby
経由) と空間 ( segmentby
経由) の両方にわたるこの最適化により、TimescaleDB は大規模な時系列データを効果的に管理およびクエリできるようになり、圧縮とアクセシビリティの間で最適なバランスが提供されます。
圧縮設計の最初のバージョンは、2019 年にTimescaleDB 1.5でリリースされました。多くのリリースを経て、タイムスケール圧縮は大きな進歩を遂げました。
初期リリースの主な制限の 1 つは、データが圧縮されると、そのデータが存在するハイパーテーブル チャンク全体を手動で解凍しない限り、データのそれ以上の変更 (INSERT、UPDATE、DELETE など) が許可されないことでした。
主に挿入が多く、更新が多くない、分析データと時系列データに基づいてデータ集約型のユース ケースを最適化していたことを考えると、これは従来の OLTP ユース ケースよりもはるかに制限が少ないものでした。データが頻繁に更新される場合 (顧客情報テーブルなど)。しかし、
最初の圧縮リリースのもう 1 つの制限は、圧縮データを含むテーブル内のスキーマの変更が許可されていないことでした。これは、開発者がテーブル全体を解凍しない限りデータ構造を進化させることができないことを意味しました。
現在、これらの制限はすべて取り除かれています。 Timescale では、圧縮データに対して完全なデータ操作言語 (DML) およびデータ定義言語 (DDL) 操作を実行できるようになりました。
UPDATE、UPSERT、および DELETE を実行できます。
デフォルト値を含む列を追加できます。
列の名前を変更したり、列を削除したりできます。
圧縮データに対するデータ変更を自動化する (ユーザーにとってシームレスになるようにする) ために、「ステージング エリア」を導入することで圧縮アプローチを変更しました。これは本質的に、圧縮されずに残り、「非圧縮データに対して」操作を実行する重複するチャンクです。フード。
ユーザーは手動で何もする必要はありません。私たちのエンジンがすべてを自動的に処理しながら、データを直接変更できます。圧縮データに変更を加える機能により、Timescale の行と列のハイブリッド ストレージ エンジンはより柔軟になります。
ステージング領域を介したこの設計により、これが実際に起こっていることなので、INSERT が非圧縮チャンクに挿入するのと同じくらい速くなります (圧縮チャンクに挿入すると、ステージング領域に書き込むことになります)。また、UPDATE、UPSERT、DELETE を直接サポートできるようになりました。値を変更する必要がある場合、エンジンは圧縮データの関連部分をステージング領域に移動し、解凍して変更を行い、(非同期で) 再度移動します。圧縮された形式でメインテーブルにコピーされます。
(このデータ領域は通常、変更をサポートするために圧縮解除する必要があるデータの量を最小限に抑えるために、基盤となる PostgreSQL ストレージ内の「行」を構成する最大 1,000 個の値の圧縮された「ミニバッチ」の規模で動作します。)
この「ステージング領域」には依然として通常のトランザクション セマンティクスがあり、クエリではこれらの値が挿入されるとすぐにこれらの値を参照します。言い換えれば、クエリ プランナーは、これらの行ベースの「ステージング」チャンクと通常の列指向ストレージにわたって適切にクエリを実行する方法を理解するのに十分な賢さを備えています。
この時点で、次に問うべき論理的な質問は、「最終結果はどうなるのか?」ということです。圧縮はクエリのパフォーマンスにどのような影響を与えますか?また、圧縮を使用するとどれくらいのディスク サイズを節約できますか?
この記事で説明してきたように、列指向ストアは一般に、個々の行を取得するクエリではあまりうまく機能しませんが、集計値を調べる分析クエリでははるかにうまく機能する傾向があります。これはまさに Timescale で見られることです。圧縮を使用すると、平均を含む深くて狭いクエリで大幅なパフォーマンスの向上が見られます。
に対していくつかのクエリを実行して、これを説明してみましょう。
特定の期間内のタクシー データセットのサブセットから最高運賃額を求める次のクエリを考えてみましょう。
SELECT max(fare_amount) FROM demo.yellow_compressed_ht WHERE tpep_pickup_datetime >= '2019-09-01' AND tpep_pickup_datetime <= '2019-12-01';
非圧縮データセットに対して実行すると、クエリの実行時間は 4.7 秒になります。私たちは小規模で最適化されていないテスト サービスを使用し、何百万行ものクエリを実行しているため、このパフォーマンスは最高ではありません。ただし、データを圧縮すると、応答時間は 77.074 ミリ秒まで低下します。
別の例を共有しましょう。このクエリは、指定された期間内の特定の料金コードによる旅行の数をカウントします。
SELECT COUNT(*) FROM demo.yellow_compressed_ht WHERE tpep_pickup_datetime >= '2019-09-01' AND tpep_pickup_datetime <= '2019-12-01' AND "RatecodeID" = 99;
非圧縮データに対してこのクエリを実行すると、完了までに 1.6 秒かかります。圧縮データに対して実行される同じクエリは、わずか 18.953 ミリ秒で終了します。繰り返しになりますが、すぐに改善が見られます。これらは単なる例ですが、圧縮がクエリを高速化するためにいかに強力であるかを示しています。
そもそも私たちがここにたどり着いた理由を忘れないでください。PostgreSQL をさらに拡張できるように、大規模な PostgreSQL データベースのサイズを削減できる戦略が必要でした。このタスクに対して Timescale 圧縮がどれほど効果的であるかを示すために、以下の表には、 Timescale の顧客の間で見られる圧縮率の実際の例がいくつか含まれています。
これらのストレージの節約は、コストの節約に直接つながります。
最終的に達成できる圧縮率は、データ タイプやアクセス パターンなどのいくつかの要因によって異なります。ただし、ご覧のとおり、タイムスケール圧縮は非常に効率的です。
私たちのチームは、圧縮を微調整してできる限りコストを節約できるようお手伝いいたします。
「圧縮により、[ディスク サイズが] 平均 97% 削減されました。」
(マイケル・ガリアルド、Ndustrial)
「Timescale の圧縮率はまったく驚異的であることがわかりました。現在、圧縮率は 26 を超えており、すべてのデータを保存するために必要なディスク容量が大幅に削減されています。」
(ニコラス・クインティン、オクターブ)
「Timescale の圧縮は宣伝どおりに優れており、基礎となるハイパーテーブルで +90% (ディスク) スペースを節約できました。」
(パオロ・ベルガンティーノ、METERグループ)
最後に、Timescale の階層型ストレージについて触れずにこの記事を終えることはできません。
圧縮に加えて、Timescale プラットフォームで PostgreSQL データベースをさらに拡張するのに役立つ別のツールが追加されました。アクセス頻度の低い古いデータを、標準経由でアクセスしながら、低コストのオブジェクト ストレージ層に階層化できます。 SQL。
この低コストのストレージ層のデータ料金は定額で 1 GB/月あたり 0.021 ドルで、Amazon S3 よりも安く、数分の 1 のコストで PostgreSQL データベースに多くの TB を保持できます。
これは、階層化ストレージ バックエンドが Timescale プラットフォーム上でどのように動作するか、また低ストレージ層が圧縮とともにどのように動作するかを示しています。
最新のデータは、高速クエリと大量の取り込み向けに最適化された高性能ストレージ層に書き込まれます。この層では、この記事で説明したように、タイムスケール列圧縮を有効にしてデータベース サイズを縮小し、分析クエリを高速化できます。たとえば、1 週間後にデータを圧縮する圧縮ポリシーを定義できます。
アプリケーションがそのデータに頻繁にアクセスしなくなると、階層化ポリシーを設定することで、アプリケーションをより低コストのオブジェクト ストレージ層に自動的に階層化できます。低コストのストレージ層のデータはデータベース内で完全にクエリ可能であり、保存できるデータ量に制限はありません (最大数百 TB 以上)。たとえば、6 か月より古いすべてのデータを低コストのストレージ層に移動する階層化ポリシーを定義できます。
このデータをデータベースに保存する必要がなくなったら、保持ポリシーを通じて削除できます。たとえば、5 年後にすべてのデータを削除できます。
列圧縮機能を追加することで、Postgres に効果的なデータベース圧縮メカニズムを提供しました。これは、今日のデータ集約型の世界において PostgreSQL データベースを拡張するために不可欠な機能です。圧縮により、ディスク使用量の大幅な節約 (安価でより多くのデータを保存) とパフォーマンスの向上 (ミリ秒単位で大量の分析クエリを実行) が可能になります。
Timescale の圧縮設計は、クラス最高の圧縮アルゴリズムと、PostgreSQL 内でハイブリッド行/列ストレージを作成する新しい方法を組み合わせることにより、驚異的な圧縮率を実現します。この機能により、Timescale (したがって PostgreSQL) のストレージ フットプリントは、より制限されたカスタム構築の列型データベースと同等になります。
ただし、多くの列型エンジンとは異なり、Timescale は ACID トランザクション セマンティクスと、圧縮された列型データの変更 (INSERT、UPDATE、UPSERT、DELETE) の直接サポートをサポートしています。 「トランザクション ワークロード用に 1 つのデータベース、分析用にもう 1 つのデータベース」という古いモデルは時代遅れになったため、多くの最新のアプリケーションは両方のパターンに適合するワークロードを実行します。すべてを PostgreSQL で実行できるのに、なぜ 2 つの別々のデータベースを維持する必要があるのでしょうか?
Timescale を使用すると、PostgreSQL で開始し、PostgreSQL に合わせて拡張し、PostgreSQL を使い続けることができます。
-カルロタ・ソトとマイク・フリードマン著。