
2026/06/16 2:29
TimescaleDB が時系列データを圧縮する方法
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
TimescaleDB の hypercore エンジンは、行ベースおよび列ベースのストレージ形式を賢く組み合わせることで、時系列データについて最大 98% の圧縮率を実現します。新しいデータは標準的な行形式で入力され、古いチャンクが時間経過に伴い自動的に高圧縮な列ベース形式に変換されます。Gorilla XOR、delta-of-delta、ラン長符号化などの特化的アルゴリズムが横方向のパターンを効率的に捉え、ストレージフットプリントおよび I/O 要求を大幅に低減します。PostgreSQL の TOAST が個別の大規模値を扱うのに対し、hypercore は分析クエリ(範囲スキャンや集約など)においてより少ないバイト数を読み取り、高速に実行できるように大規模データセットを最適化します(実世界のテストでは 28〜42 倍の改善が確認されています)。これらの利点を最大化するには、
segmentby および orderby を使用してセグメントを構成し、各チャンクに十分な行数を含めるようにしてください(理想的には各チャンクあたり 100 以上のあるセグメント値を持つことを推奨します)。このハイブリッドシステムは長期的な保持プロジェクトにおけるストレージコストを大幅に低下させますが、更新時に実行される必要な復号化サイクルにより、ポイントルックアップや直接の行修訂が若干遅延する可能性がある点にご注意ください。本文
TimescaleDB の圧縮:仕組み、設定、パフォーマンス向上ガイド
TimescaleDB は時系列データを最大 98% 圧縮する独自の技術を持っています。本稿では、その仕組み、設定方法、そして PostgreSQL 標準機能との違いを整理します。
1. TimescaleDB の圧縮 vs. PostgreSQL TOAST
PostgreSQL に内蔵されている
TOAST(超大属性保存技術)と TimescaleDB の圧縮は、本質的に異なる問題を解決するための補完的な機構です。
基本の違い
| 特徴 | TOAST (Vanilla PostgreSQL) | TimescaleDB Hypercore |
|---|---|---|
| 設計目標 | 個別の大きな値(2 KB を超える文字列など)を処理する | 時系列データの行間パターンに最適化された圧縮を実現する |
| トリガー | 行数が (~2 KB) を超える場合 | チャンクごとのポリシー(例:データ作成後 7 日)で自動発動 |
| サポートデータ型 | 可変長のみ (, , , ) | すべてのデータ型に対応 |
| アルゴリズム | pglz(デフォルト)、lz4(オプション) | ハイブリッド: デルタ符号化、ランレングス (RLE)、XOR ベースなど |
| 圧縮粒度 | 値単位 (1 値 = 1 バイトストリーム) | バッチ単位 (~1000 行をまとめる) |
| データ構造活用 | なし(不透明なバイト列扱い) | はい(数値構造、単調性、反復性を活用) |
圧縮比率の比較(推定)
| データタイプ | TOAST 標準 (PG14 以前/未設定) | TimescaleDB Hypercore |
|---|---|---|
| センサー浮動小数点 | ~1.0× (圧縮なし) | 10-20× |
| タイムスタンプ | ~1.0× (圧縮なし) | 50-100× |
| テキスト | 2-3× | 5-10× |
注意: TOAST は浮動小数点や固定長整数には効きません。IoT ワークロードなどで TOAST が使えない場合、TimescaleDB は 10-100 倍の圧縮を実現します。
2. Hypercore エンジンとカラム指向ストレージ
TimescaleDB は新しいデータが高速な「行指向」形式で蓄積される一方、古いデータは自動的にカラム指向に変換されます。これを
hypercore というエンジンと呼びます。
ハイブリッド動作のメリット
- 高速な INSERT/UPDATE: 新規データは行指向で保存され、OLTP に最適化されています。
- 高速な分析クエリ: 古いデータ(チャンク)はカラム指向圧縮形式に変換され、読み取り I/O を最小化します。
行からの変換プロセス
圧縮処理では、行を最大 1000 行ずつのバッチにグループ化し、各バッチを圧縮された単一のエントリとして保存します。
【カラム指向バッチの特徴】
- 配列化: カラムごとに最大 1000 個の値を持つ配列として格納されます(例:
)。temperature[1000] - カラムマジョリティ: 同一列の値を連続して配置するため、特定の列のみを参照する際のスキャンが高速化します。
- 高度なアルゴリズム適用: デルタ符号化やランレングス符号化 (RLE) を列レベルで適用し、I/O コストを削減します。
3. 主要な圧縮アルゴリズムの仕組み
TimescaleDB は各データ型の特性に応じて最適なアルゴリズムを選択します。代表的な手法は以下の通りです。
① デルタ符号化 (Delta Encoding)
値の変化分(Delta)のみを保存し、絶対値を削減します。
- 原理: 前の値からの差分 (
,+0.2
) を保存する。-0.1 - 適用対象: 浮動小数点データ(温度、圧力など)、数値が変動する場合。
② デルタ・オブ・デルタ符号化 (Delta-of-Delta Encoding)
時間間隔が規則的な場合、変化分の差分をさらに圧縮します。
- 原理: 「+5 秒」の変化が続く場合は
と表せるため、データ量が急減します。+0 秒 - 適用対象: タイムスタンプ、定期的なサンプリングデータ。
③ ランレングス符号化 (Run-Length Encoding, RLE)
重複する値を「値」×「回数」として保存します。
- 原理:
が 5 回続く場合、文字列を 5 回書く代わりにMACHINE_001
と保存します。MACHINE_001 × 5 - 適用対象: デバイス ID (
)、センサー種別 (machine_id
) などの重複が多いカラム。sensor_type
圧縮技術サマリー
| カラム例 | 推奨手法 | 表現例 |
|---|---|---|
| time | デルタ・オブ・デルタ | , , , ... |
| machine_id | ランレングス符号化 (RLE) | |
| sensor_type | ランレングス符号化 (RLE) | |
| value | デルタ符号化 | , , ... |
重要: アルゴリズムは万能ではありません。高エントロピー(一意な値が多い)なカラム(例:UUID)では辞書圧縮が効果的ですが、逆に単純に不変なデータであっても適切な手法が選ばないと圧縮効率は下がります。TimescaleDB は自動的に最適な組み合わせを選定します。
4. 重要な設定パラメータ
圧縮機能の有効化には、以下の 2 つのパラメータを適切に設定することが不可欠です。これらは**「チャンクの分割基準」と「ソート順」**を決めるためです。
パラメータ定義
: チャンク全体で値が共有されるカラム(例:segmentby
)。machine_id- 同じ
値を持つ行は物理的に近接して保存されます。segmentby - プランナーはこの情報を使って、対象外のチャンクをスキップします(全走査回避)。
- 同じ
: チャンク内のソート順序(通常はorderby
)。time DESC- データを時間順に並べることで、デルタ符号化や RLE の効用を最大化します。
SQL 設定例
ALTER TABLE iot_sensor_data SET ( timescaledb.compress = true, timescaledb.segmentby = 'machine_id', -- 分割基準カラム timescaledb.orderby = 'time DESC' -- ソート順(過去からのソート推奨) ); -- 7 日以上経過したデータを自動的に圧縮するポリシー適用 SELECT add_columnstore_policy( 'iot_sensor_data', after => INTERVAL '7 days' );
設定時のルール(基数制約)
アルゴリズムが機能するためには、圧縮対象のチャンク内で十分な「類似した値」が必要です。
- 推奨範囲: チャンクごとに
のユニーク値数が 100 - 10,000 程度あること。segmentby - 最低ライン: セグメント(バッチ)あたりの行数が 100 行以上。
- 注意点: センサー数(基数)が高すぎると、各センサーのデータ量が少なくなりすぎるため圧縮効率が落ちます。
5. パフォーマンスへの影響
「圧縮はクエリを遅くするか?」という疑問に対して、答えは状況によって異なります。
✅ 高速化するケース(ワークロードの過半数)
- 集計処理: 時間範囲内の
,SUM
,AVG
など。MAX - 範囲スキャン: 大規模な日付範囲検索。
- フィルタ済みクエリ:
カラム(例:特定の ID)で絞る場合。segmentby
理由: I/O コストが 10-20 倍削減されるため、ディスク読み込みとメモリ使用量が大幅に減少します。
例: 圧縮前の 1 GB データ読み取り vs. 圧縮後の 50 MB 読み取り = 圧倒的に高速。
⚠️ 低速化するケース(時系列では稀)
- 単一行ポインタルックアップ:
のような極めて限定された検索。time = '...' AND id = X - 更新・削除操作 (UPDATE/DELETE): デコンプレッション → 変更 → リコンプレッションという処理コストがかかります。
- 高基数フィルタなし:
カラムを指定しない場合の全テーブルスキャン。segmentby
6. 実際のデータベースへの設定と検証
以下は IoT センサー監視システムへの適用例です。
① 自動圧縮の設定
-- 7 日以上前のデータを自動的にカラムストレージに変換するポリシー SELECT add_columnstore_policy( 'iot_sensor_data', after => INTERVAL '7 days' ); -- チャンクの詳細確認(圧縮状態の確認) SELECT chunk_name, is_compressed, range_start, pg_size_pretty(total_bytes) AS size FROM chunks_detailed_size('iot_sensor_data') WHERE timescaledb_hypertable = true AND is_compressed = true;
② 圧縮による高速化の実績(例)
ある MQTT データセットでの比較結果(Rowstore vs Columnstore):
| メトリック | Rowstore (未圧縮) | Columnstore (圧縮後) | 改善率 |
|---|---|---|---|
| サイズ | 2.3 GB | 7.2 MB | 約 43× の削減 |
| 実行時間 | 10.2 ms | 0.36 ms | 約 28× の高速化 |
- 理由:
に基づくメタデータインデックスにより、対象チャンクのみを読み取ることが可能になりました。segmentby
③ クエリプラン分析 (EXPLAIN ANALYZE
)
EXPLAIN ANALYZE圧縮されたチャンク(Columnstore)では、B-tree インデックスの代わりに、TimescaleDB が自動生成した
(segmentby_col, min_val, max_val) というメタインデックスが使われます。これにより、不要なチャンクをスキップして高速に結果を検出できます。
7. 適用前のチェックリスト
設定を行う前に、現在のデータ分布を確認することをお勧めします。
segmentby
の適性確認方法
segmentby-- チャンク内の各行の行数分布を確認 WITH per_id AS ( SELECT id, count(*) AS n FROM _timescaledb_internal._hyper_X_Y_chunk -- 実際のチャンク名を指定してください GROUP BY id ) SELECT SUM(count) FILTER (WHERE n < 100) AS low_volume_count, SUM(count) FILTER (WHERE n >= 100) AS high_volume_count FROM per_id;
- 結果:
が非常に多い場合(例:ID ごとの行数が 100 未満)、現在のlow_volume_count
設定では圧縮効率が下がる可能性があります。segmentby
まとめ
TimescaleDB の圧縮機能は、単なるサイズ削減だけでなく、時系列データの分析クエリを劇的に高速化させる技術です。
- 仕組み: 古いデータは自動的にカラム指向のハイブリッド形式(
)へ変換され、10-100 倍の圧縮を実現します。hypercore - 設定の鍵:
とtimescaledb.segmentby
を正しく設定し、データ分布がアルゴリズムに適した範囲にあるか確認してください。timescaledb.orderby - 効果: IoT や生産性モニタリングなどで長期間データを保持する際、ストレージコストとクエリ速度の両面で優れています。
具体的な導入設計やアーキテクチャサポートが必要な場合は、お気軽にご相談ください。