今年の初めに、当社では最大規模のプロジェクトに着手しました。
当然のことながら、TimescaleDB を中核とする成熟したクラウド プラットフォームである Timescale を選択しました。私たちはPostgreSQLの操作に慣れており、PostgreSQL をより高速かつスケーラブルにするために TimescaleDB を構築しました。独自の例に従うより良いことは何でしょうか?
このドッグフーディング実験を説明する最も簡単な方法は、その規模を定量化するのに役立つ数字を使うことです。 Insights を構築するには、継続的に実行されている運用データベース全体にわたってクエリ情報を収集する必要がありました。私たちはプラットフォーム上の個々の (サニタイズされた) クエリに関する1 兆を超えるレコードをすぐに収集しました。
現在、Insights が運用環境で稼働しているため、 1 日に100 億件を超える新しいレコードが取り込まれています。単一の Timescale サービスによって提供されるデータセットは毎日約 3 TBずつ増加し、現在合計350 TBを超えています。また、同じデータベース サービスがすべての顧客のリアルタイム ダッシュボードを強化しています。
このブログ投稿では、インサイト構築プロセスの舞台裏を覗いてみましょう。この規模で運用するということは、単一の Timescale サービスの限界を押し広げ、PostgreSQL だけでなく開発者の共感も拡張することを意味しました。 Timescale は課題を十分に満たしていることがわかりましたが、改善したい領域もあります。
Insights を実現するには、データベース管理者の帽子をかぶって、PostgreSQL を数テラバイトのデータに拡張するためのいくつかの技術的な課題に取り組む必要がありました。私たちは、「特別な」インフラストラクチャを使用せずにプラットフォーム上でホストされるタイムスケール サービスを中央データベースとして使用したいと考えていました。これは次のことを意味します。
私たちは、1 日に数十億のレコードを単一の Timescale サービスに取り込むことができるパイプラインを構築する必要がありました。 Timescale は高い取り込みレートを処理でき、顧客のために定期的に実行しますが、実稼働クエリの負荷下でのこのレベルのスケールは常に眉をひそめます。
当社の顧客は、Insights が提供するすべての分析を強化する柔軟性を備えてこのデータベースにクエリを実行できる必要がありましたが、応答まで何分も待たせることは望ましくありませんでした。
毎日数TBを追加するため、単一のTimescaleサービスに数百TBを保存する必要がありました。古いデータ (つまり、数週間より古いデータ) にはアクセスできる必要がありますが、クエリが必ずしも高速である必要はありません。
データ収集側では、Timescale プラットフォームのアーキテクチャを活用しました。 Timescale は Kubernetes (k8s) 上で実行されており、さまざまな地理的リージョンでいくつかのk8sクラスターが実行されています。これらのクラスターには、1 つ以上の顧客データベース サービスを保持するノードがあります。これらすべてのデータベースのクエリ実行を収集するには、そのデータベースから地域レベルまでバブルアップし、Insights を強化する Timescale データベース サービスにレコードのバッチを保存する地域ライターを用意します。
いくつかの低レベルの悲惨な詳細を避けるために手の波動を許してください。しかし、大まかに言えば、これが物事がどのように機能するかです。フリート全体で実行されている各データベースは、クエリを含むすべてのクエリの後に(プライバシーとセキュリティのためにサニタイズされた)レコードを作成するように装備されています。クエリ自体と重要な統計。
これらのレコードはノード レベルで収集され、ラベルでタグ付けされて、レコードの取得元のデータベース サービスと関連付けられ、バッチ化されて地域ライターに送信されます。リージョン ライター サービスは、各リージョンの負荷を処理するために必要に応じて複製されます。各ライターは各クラスター内のノードからバッチを収集し、さらに大きなバッチを作成します。
これらの大きなバッチは、最初に「COPY」を使用して一時テーブルに書き込まれます (先行書き込みログなし = 高速)。次に、その一時テーブルのエントリは、必要なテーブルを更新するために使用されます (以下を参照)。一時テーブルを使用すると、重複を気にせずに「COPY」を使用できます。重複は、一時テーブルからレコードをマンジする後続の操作によって処理されます。
要約すると:
Insights を強化しているデータベースを詳しく見てみましょう。私たちは、「既製の」タイムスケール サービスで Insights を実行しています。
Insights を支えるデータベースにはかなりの数の部分がありますが、最も重要な部分を取り上げます。
まず、「参照テーブル」として機能する通常の PostgreSQL テーブルが 2 つあります。これらのテーブルには、情報データベースのメタデータとクエリ文字列のメタデータが含まれています。これらの (疑似) スキーマは次のとおりです。
データベースのメタデータ
Table "insights.cloud_db" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- id | bigint | | not null | nextval('cloud_db_id_seq'::regclass) service_id | text | | not null | project_id | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "cloud_db_pkey" PRIMARY KEY, btree (id) "cloud_db_project_id_service_id_key" UNIQUE CONSTRAINT, btree (project_id, service_id)
メタデータのクエリ
Table "insights.queries" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- hash | text | | not null | normalized_query | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "queries_pkey" PRIMARY KEY, btree (hash)
新しいデータベースに対してクエリの実行が開始されると、そのデータベースは `insights.cloud_db` に追加されます。新しい正規化されたクエリが実行されるたびに、そのクエリは「insights.queries」に追加されます。
(正規化されたクエリとは何ですか? これは、すべての定数がプレースホルダーに置き換えられたクエリです。最初の定数は $1、2 番目の定数は $2 というように、値ではなくクエリの「形状」のみが表示されます。 。)
ここまでは、Timescale の秘密のソースを使用せずに、通常の Postgres を使用しているだけです。ただし、データベース内の他の重要なオブジェクトは TimescaleDB に固有のものであり、PostgreSQL を別のレベルに拡張するのに役立ちます。ここで魔法が起こります。ハイパーテーブルと連続集計です。
ハイパーテーブルは、 Timescale の自動的にパーティション化されたテーブルです。データの取り込み中にデータがディメンションごとに自動的に分割されるため、PostgreSQL テーブルをより簡単に大規模に拡張できるようになります。ハイパーテーブルは、Timescale の構成要素です。後で説明するように、クエリ統計メトリクスを巨大なハイパーテーブルに保存しています。
連続集計は、Timescale の PostgreSQL マテリアライズド ビューの改良版であり、増分的および自動マテリアライゼーションを可能にし、Insights を構築する際に非常に役立つことが判明しました。
これらの機能を使用して、ユーザー側で高速な分析クエリを可能にする方法について説明します。
すでに述べたように、すべてのクエリ実行に関する情報を保存するために大規模なハイパーテーブルを使用します。このハイパーテーブルはメインテーブルであり、サニタイズされた生のメトリクスが存在します。これは次のようなもので、タイムスタンプ列 ( created
) を使用して、取り込まれたデータを自動的に分割するように構成されています。
Table "insights.records" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time | bigint | | | rows | bigint | | | ...
この例では多くの統計を省略しましたが、理解していただけたでしょうか。
ここで、ユーザー側からの高速クエリを許可する必要がありますが、このテーブルは巨大です。処理を高速化するために、私たちは連続集計に大きく依存しました (
継続的な集計は、Insights のようなリアルタイムのユーザー向け分析を提供する製品において非常に意味があります。ユーザーに実用的な情報を提供するには、メトリクスを集約する必要があります。ユーザーが実行したすべてのクエリのログを、その横に統計情報とともに表示しているわけではありません。データベースによっては、1 秒あたり数千のクエリを実行しているため、それを見つけるのは悪夢のようなことです。役に立つものなら何でも。代わりに、ユーザーの集合体を提供しています。
したがって、生の個々のレコードをユーザーに表示しないという事実を利用して、結果を保持することもできます。
PostgreSQL マテリアライズド ビューを使用することもできましたが、Timescale の連続集計には、特に役立ついくつかの利点があります。ビューは頻繁に更新されますが、継続的な集計には自動更新用のポリシーが組み込まれており、段階的に更新されます。
ビューは 5 分ごとに更新されるため、実体化された情報全体を 5 分ごとに再生成するのではなく、連続集計は元のテーブルの変更を追跡することによってビューを増分更新します。私たちが運用している規模では、メインのハイパーテーブルを 5 分ごとに上から下までスキャンする余裕はありません。そのため、この連続集計の機能は私たちにとって根本的な「ロック解除」でした。
Insights を舞台裏で強化するこれらの継続的な集計では、興味深い統計のほとんどを
それでも、ある時点から、データベースはこれらすべての生のレコードを挿入し、提供するために実体化するために多くの作業を実行し始めました。私たちは摂取して維持できる量にいくつかの制限に直面していました。
取り込み速度を必要なレベルまでさらに高めるために、UDDSketch 生成をデータベースからリージョン ライターにオフロードしました。ここで、まだある程度のレコードを「生」レコードとして保存しますが、残りも事前に生成されたスケッチにプッシュしてデータベースに保存します。
Table "insights.sketches" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time_dist | uddsketch | | | rows_dist | uddsketch | | | ...
UDDSketch の最も優れた点は、より大きな時間範囲をサポートするためにスケッチを継続的に「ロールアップ」することが非常に簡単であることです。このようなロールアップを使用すると、階層的連続集計の構築時とクエリ時の両方で、狭い時間範囲をカバーするスケッチを広い時間範囲をカバーするスケッチに集約できます。
高速取り込みとクエリの両方がリードレプリカであることを保証するために活用したもう 1 つのツール。 Insights が Timescale プラットフォームの主要な顧客向け機能を強化していることを考慮すると、この場合、レプリケーションの使用は高可用性とパフォーマンスの両方にとって最も重要です。
メインのデータベース インスタンスは、大量の作業、データの書き込み、連続集計の具体化、圧縮の実行などで非常に忙しいです。 (圧縮については後ほど詳しく説明します。) 負荷をいくらか軽減するために、レプリカ サービスの顧客が Insights コンソールからリクエストを読み取れるようにしました。
最後に、数百 TB を単一の Timescale サービスに無理なく適合させる必要がありました。 Insights データベースは急速に拡張しています。私たちが開始したときは 100 TB 程度でしたが、現在は 350 TB を超えています (さらに増え続けています)。
大量のデータを効率的に保存するために、
メインのハイパーテーブルでは20 倍以上の圧縮率が確認されています。
非常に大規模なハイパーテーブルを管理する場合のもう 1 つの大きな利点は、圧縮データのスキーマ変更可能性でした。前のセクションでおおよそのスキーマについて説明しましたが、ご想像のとおり、統計情報などを追加するために頻繁に変更しています。これを圧縮ハイパーテーブルで直接実行できるのは非常に便利です。
私たちは Timescale のデータ階層化のヘビーユーザーでもあります。この機能は今年初めに早期アクセスが開始され (すぐに GA ニュースが届くまでお待ちください 🔥)、Timescale データベースを介して数百 TB にアクセスし続けることができます。データ階層化も非常に効率的であることが証明されています。ここでも驚くべき圧縮率が見られ、130 TB がリソース効率の高い 5 TB に縮小されています。
Insights を構築するプロセスは、当社の製品が実際にどこまで実現できるかを示しましたが、最も良かったのは、顧客の立場になって数マイル歩いたことです。私たちは、Timescale を使用して PostgreSQL をスケーリングするユーザー エクスペリエンスについて多くのことを学び、製品の背後にあるエンジニアとして、やるべきことリストにいくつかのことを追加しました。
良い点もまあまあの点もすべて見てみましょう。
不謹慎ではありますが、私たちは自分たちの製品をとても誇りに思うことがありました。すでに数百 TB ある単一の PostgreSQL データベースに毎日数百億のレコードを取り込むことは、くしゃみするようなことではありません。データベースが稼働し始めたとき、私たちはデータベースのチューニングに数週間を費やしましたが、現在はベビーシッターや継続的な監視なしで正常に動作しています。 (これは監視されていないこととは異なり、確実に監視されていることに注意してください。)
私たちの
圧縮は私たちにとって非常にうまくいきました。前のセクションで共有したように、単純な 1 つの `segmentby` オプションを使用することで、驚異的な圧縮率 (20 倍!) が得られました。私たちにとって、ポリシーの設定と調整の経験は難しくありませんでした。もちろん、この機能を構築したのは私たちでした。私たちはわずかに優位に立っていると言えるでしょう。 🙂 さらに、圧縮データに新しい列をシームレスに追加できる機能により、データベースの柔軟性と適応性がさらに強化されました。私たちはこの機能を複雑にすることなく使用しました。
連続集計により、さまざまな期間を構築する際のロジックが簡素化され、データの分析と処理が合理化されました。大量の階層的連続集計を使用しました。
Timecale のハイパーファンクションに含まれる近似アルゴリズムにより、実装が簡素化され、分析が大幅に拡張されました。スケッチを簡単にロールアップできる機能も、顧客向けの Insights ダッシュボードでさまざまな時間範囲やタイム バケットの粒度を効率的にサポートするための鍵でした。
Timescale データベースがデータ階層化を介して自由に使用できる「無限の」ウォーム ストレージは、拡張する余地が十分にあり、数百 TB まで拡張するために重要でした。現在の __ データ階層化ポリシー __ では、ホット ストレージに 3 週間のレコードが保持されます。
最後に、カスタム ジョブを作成する機能を使用して、可観測性 (ジョブ履歴の監視など) を強化し、実験的な更新戦略を実装しました。
素晴らしいことをすべて話した後は、それほど素晴らしいことではないことを認める時が来ました。タイムスケールも含めて、完璧なものはありません。パイプラインの実装中にいくつかの課題に直面しましたが、これらは不満という意味ではありません。
Timescale プラットフォームでは、特にジョブや連続集計の実体化のパフォーマンスに関して、データベースの可観測性が向上する可能性があります。
TimescaleDB は主にスナップショット ベースのビューを提供するため、時間の経過に伴うパフォーマンスと傾向を理解することが困難になります。たとえば、すぐに使用できる「ジョブ履歴」テーブルはありません。私たちは初期の段階で、連続集計の増分具体化に時間がかかっているように見えることに気づき、最終的にはバグの発見につながりましたが、範囲を確認したり定量化する方法がありませんでした。
前に述べたように、カスタム ジョブを定義して Timescale のジョブ フレームワーク内で実行できる機能により、これの「十分な」バージョンを作成することができました。時間の経過とともに監視したいビューに継続的にクエリを実行し、変更があればハイパーテーブルに挿入します。これは今のところ Insights では機能しますが、すべてが常に高速であるという点を超えてタイムスケールを拡張すると、これらの機能が非常に重要になると考えているため、これらの機能の一部を組み込み機能に変えることにも取り組んでいます。 。
基礎となるデータが大きい場合、連続集計を正しく行うのは難しい場合があります。
連続集計を作成するときに __ WITH NO DATA オプションを使用すると、__ 命の恩人になります。段階的に更新するデータの量が誤って大きくなりすぎないように、更新ポリシーのオフセットを慎重に扱うことも重要です。
このアドバイスに従ったとしても、実体化しようとしているデータの量よりも更新に時間がかかる連続集計が結果として生じる可能性があります (たとえば、15 分のデータを実体化するのに 30 分かかるなど)。これは、基礎となる連続集計タスクが大きすぎてメモリに収まらず、ディスクに溢れてしまう場合があるために発生します。
私たちはこの問題に遭遇しました。この問題は、最終的に実体化にデータを提供しない場合でもクエリ プランに追加のチャンクが含まれる原因となった、off-by-one のバグ (現在は修正されています) が見つかったため悪化しました。このバグの発見は、実際には「dogfoodception」のケースでした。このパフォーマンスの問題は、Insights を構築中に使用中に発見しました 🤯。 Insights で確認したタイミング情報は、ここで何かが間違っていることを示唆していたので、EXPLAIN を使用して計画を確認することで問題を発見しました。したがって、それが機能することがわかります。
マテリアライゼーションを高速化するために、更新する増分サイズを制限するカスタム増分更新ポリシーを作成することになりました。私たちは、これを TimescaleDB に適切に一般化できるかどうかを確認することに取り組んでいます。
大規模な変化は困難です。
データが特定のサイズに達すると、TimescaleDB での一部の DDL (スキーマ変更) 操作に理想以上に時間がかかることがあります。私たちはすでにいくつかの方法でこれを経験しています。
たとえば、大規模なハイパーテーブルに新しいインデックスを追加するには、タイミングが重要になります。 TimescaleDB は現在、「CONCURRENTLY」と「CREATE INDEX」の使用をサポートしていないため、次善のオプションは、組み込みメソッドを使用して一度に 1 チャンクずつインデックスを作成することです。私たちの場合、新しいチャンクが作成された直後にそれを開始する必要があるため、「アクティブな」チャンクのロックは最小限で済みます。つまり、チャンクが新しいときにインデックスを作成するということは、チャンクが (ほぼ) 空であることを意味するため、迅速に完了し、新しい挿入をブロックすることができません。
変更が難しいもう 1 つの理由は、連続集計を更新して新しいメトリクス (列) を追加する場合です。連続集計は現在「ALTER」をサポートしていません。したがって、新しいメトリクスをユーザーに公開したい場合は、連続集計のまったく新しい「バージョン」を作成します。つまり、連続集計「foo」の場合、「foo_v2」、「foo_v3」などになります。これは次のようになります。理想的とは言えませんが、現在は機能しています。
最後に、圧縮設定を変更するのは大規模化すると非常に困難です。実際、現時点では事実上不可能です。すべての圧縮チャンクを解凍し、設定を変更してから再圧縮する必要があり、現在の規模では実現不可能です。
私たちは、これらすべてに対する実行可能な解決策を得るために同僚とブレインストーミングを続けています。私たちだけでなく、すべての Timescale ユーザーも同様です。
1 つの投稿にすべてを載せるにはかなりの量の情報でした。しかし、必要な場合は、
インサイトの構築は、私たちのチームにとって深い経験でした。私たちは、Timescale をどこまで進めて、目覚ましい規模の数値に到達できるかを直接見てきました。その過程で私たちが遭遇した問題点は、私たちに多くの顧客の共感を与えてくれました。それがドッグフーディングの素晴らしさです。
来年は、さらに桁違いに多くのデータベースをどのように監視しているか、また Timescale を大規模に操作するエクスペリエンスをどのように改善し続けているかについて、別のブログ記事を書きたいと思っています。
それではまた! 👋