PostgreSQL でパーティションされていない列でクエリした際にプリューニングを実現する方法

2026/07/09 20:57

PostgreSQL でパーティションされていない列でクエリした際にプリューニングを実現する方法

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

PostgreSQL 19 は、CHECK 制約を使用して非パーティションキー列に対して効率的な剪断を可能にすることで、大規模なパーティション化テーブルの主要なパフォーマンスボトルネックに対処します。従来、パーティションキー以外の列でフィルタリングを行う場合、特定の実装手法を採用しない限りすべてのパーティションを対象としたフルスキャンが強制されました。この新しい機能は、グローバルインデックスを必要とせずにそのようなオーバーヘッドを回避し、何十年も要望されてきたが PostgreSQL 19 では依然としてサポートされていない特徴を実現します。

このソリューションでは、データ境界をマルチレンジ表現内の CHECK 制約に直接エンコードします。「Gaps and Islands(ギャップとアイランド)」分析を活用してデータベースは各パーティション内の連続する値範囲を特定し、最適化기는これらのパターンを利用して

constraint_exclusion
(デフォルトで有効)を介してクエリ条件と矛盾するパーティションの走査をスキップします。これは BRIN インデックスの min-max ロジックに類似していますが、より厳格な制約ベースの排除を強制します。このアプローチは、何年にもわたる大規模なイベントログのような膨大なデータセットでの高価なフルスキャンを防ぎつつ、証明されたパーティション化アーキテクチャ内でスケーラビリティを維持し、PostgreSQL 14 で導入されたマルチ-minmax スタイルのレンジを通じて外れ値を適切に処理する点で特に重要です。

本文

パーティションキー以外の列でのプリーニングを実現する方法

概要

分割テーブルにおいて最も価値があるものの一つが、プリーニング(Pruning)です。データベースはクエリ条件に基づいて不要なパーティションを除外し、パフォーマンスを向上させます。 従来はパーティションキーを用いた照会のみでこれが可能と考えられていましたが、巧妙な手法を用いれば、パーティションキー以外の列でもプリーニングを実現できます。

本記事では、その具体的な方法を解説します。


目次

  1. テーブルの分割
  2. キー列におけるパーティションプリーニング
  3. ローカルインデックス
  4. グローバルインデックス
  5. パーティションキー以外の列でのプリーニングへのアプローチ
  6. 最適化器との対話(チェック制約の利用)
  7. 外れ値の導入と対処
  8. まとめ

1. テーブルの分割

大量のイベントログを保存・管理するシステムの事例を想定します。ユーザー数が多いため、データ量も膨大になります。主に特定の日付範囲を扱うクエリが多いため、**タイムスタンプを基準としてテーブルを分割(Partition)**します。

スキーマとパーティション作成

-- メインテーブルの定義
CREATE TABLE event (
  id         BIGINT GENERATED ALWAYS AS IDENTITY,
  timestamp  TIMESTAMPTZ NOT NULL,
  session_id BIGINT NOT NULL,
  type       TEXT NOT NULL,
  data       JSONB
) PARTITION BY RANGE (timestamp);

-- 2025 年のパーティション作成
CREATE TABLE event_y2025 PARTITION OF event
FOR VALUES FROM ('2025-01-01 UTC') TO ('2026-01-01 UTC');

-- 2026 年のパーティション作成
CREATE TABLE event_y2026 PARTITION OF event
FOR VALUES FROM ('2026-01-01 UTC') TO ('2027-01-01 UTC');

データ生成(例)

テスト用にイベントログを約 5 万件格納します。セッション単位で記録されており、ログイン失敗やパスワードリセットフローが含まれます。

-- (略:大量データの挿入処理)
INSERT INTO event (session_id, timestamp, type, data) ...

2. キー列におけるパーティションプリーニング

**パーティションキー(

timestamp
)**でフィルタリングするクエリは、プリーニングの恩恵を直接受けます。

✅ クイックなケース:時刻でのフィルタリング

SELECT * FROM event
WHERE timestamp >= '2025-12-01 UTC' 
  AND timestamp <  '2026-01-01 UTC';

実行プランの確認: データベースは自動的に不要なパーティションを除外しています。

QUERY PLAN
────────────────────────────────────────────────────────────────────────
 Seq Scan on event_y2025 event      -- ★ 2025 のパーティションのみスキャン
   Filter: (("timestamp" >= ...))
-- 2026 のパーティションは一切参照されていない

❌ スローなケース:セッション ID でのフィルタリング

一方、**パーティションキーではない

session_id
**での検索は通常、全パーティションのスキャン(Append)を必要とします。

SELECT * FROM event WHERE session_id = 1;

実行プラン:

QUERY PLAN
──────────────────────────────────────────────────────
 Append  (cost=0.00..1060.07 rows=11 width=37)
   -> Seq Scan on event_y2025 ... Filter: (session_id = 1)
   -> Seq Scan on event_y2026 ... Filter: (session_id = 1)
-- ★ 両方のパーティションをスキャンするため、パフォーマンスが落ちる

3. ローカルインデックス

特定のセッションを高速に取得したい場合、ローカルインデックスを作成します。

インデックスの作成と効果

テーブル全体や各パーティションごとに個別にインデックスを作成する方法です。

-- テーブルレベルの(分割された)インデックス
CREATE INDEX event_session_ix ON event(session_id); 
-- 結果として、各パーティションにもインデックスが自動生成される

-- 例:2025 パーティション上のインデックス
-- event_y2025_session_id_idx

効果と限界

ローカルインデックスを使うことで、Seq Scan が Index Scan に変化し、パフォーマンスが向上します。

QUERY PLAN
─────────────────────────────────────────────────────
 Append
   -> Index Scan using event_y2025_session_id_idx on event_y2025 ...
      Index Cond: (session_id = 1)
   -> Index Scan using event_y2026_session_id_idx on event_y2026 ...
      Index Cond: (session_id = 1)

ただし、プリーニング自体は起こっていません。データベースは依然として両方のパーティションにアクセスする必要があります。パーティション数が多くなるほど(例:100 パーティション)、このオーバーヘッドは大きくなります。

重要: ローカルインデックスは「各パーティションに個別のインデックス」という意味合いを持ちます。PostgreSQL では現時点で、分割テーブルに対するグローバルインデックスのサポートはありません(注:更新対応中)。


4. グローバルインデックス

分割テーブルを跨ぐ単一のインデックス(グローバルインデックス)を作成したい場合、現状では PostgreSQL でサポートされていません。 パーティションキー以外の列に対する一意性制約の実装が困難になることも、この制約からの派生した課題です。


5. パーティションキー以外の列でのプリーニング

標準機能のみでは、非パーティションキー列によるフィルタリング時にプリーニングを行うことは難しい状態です。ここで**ドメイン知識(データの特性)**を活用して突破口を開きます。

データの特性とパターン

本事例のデータには以下の明確なパターンがあります:

  1. 追加専用(Append-only): 更新・削除がないため、イベントは不変。
  2. 順次生成:
    session_id
    は時間の経過とともに単調増加する。
  3. 短期寿命: セッションは数分〜数時間で終了するため、各パーティション内で session ID の重複や大きな飛биは少ない。

パターンの可視化

パーティションごとの Session ID の範囲を確認します。

SELECT tableoid::regclass, MIN(session_id), MAX(session_id)
FROM event 
GROUP BY 1 
ORDER BY 1;

-- 結果
-- event_y2025 | min=1  | max=4320
-- event_y2026 | min=4320| max=10000

発見:

  • event_y2025
    は Session ID 1〜4320 に限定されている。
  • event_y2026
    は Session ID 4321〜10000 である(※境界処理のため重複あり)。

この相関関係から、**「Session ID が X の行は、特定のパーティションのみを持つ」**という情報を得ることが可能です。


6. 最適化器との対話(チェック制約の利用)

データベースの最適化器には、テーブル統計情報に基づいて判断されますが、確実な情報は制約(Constraints)からしか得られません。 ここにCHECK 制約を活用する裏技があります。

チェック制約を「情報源」として利用する

チェッキリスト(CHECK)はデータを検証するだけでなく、最適化器に対して**「このパーティションには X の範囲のデータしか存在しない」**と保証を与えることが可能です。 これを伝えれば、最適化器は条件に合わないパーティションのスキャンを省略(プリーニング)できます。

チェック制約の設定

各パーティションに Session ID の範囲を強制する CHECK 制約を追加します。

-- 2025 パーティション:ID は 1 から 4320 まで
ALTER TABLE event_y2025 
ADD CONSTRAINT event_y2025_session_id_range
CHECK (session_id BETWEEN 1 AND 4320);

-- 2026 パーティション:ID は 4320 から 10000 まで
ALTER TABLE event_y2026 
ADD CONSTRAINT event_y2026_session_id_range
CHECK (session_id BETWEEN 4320 AND 10000);

プリーニングの実現

セッション ID

1000
を検索するクエリを確認します。

SELECT * FROM event WHERE session_id = 1000;

実行プランの結果:

QUERY PLAN
─────────────────────────────────────────────────────
 Index Scan using event_y2025_session_id_idx on event_y2025 ...
   Index Cond: (session_id = 1000)
-- ★ event_y2026 はスキャン対象から完全に除外されました!

仕組み:

  • event_y2026
    の CHECK 制約によると、「このパーティションには ID が 4320〜10000 のデータしかない」。
  • クエリ条件「session_id = 1000」は、その範囲に含まれないため、最適化器はこれを即座にFalseと判定し、スキャンを跳ね返します(Constraint Exclusion)。

境界値の扱い

境界値(4320)では両方のパーティションを参照する必要がありますが、CHECK 制約がそれを正しく識別してくれます。 同様に、異なる Session ID でも適切にプリーニングされます。


7. 外れ値の導入と対処

現実の世界ではデータにはノイズ(外れ値)があります。 例えば、セッションが長引き、2025 年のセッションが 2026 年まで続いている場合や、Session ID が初期値に戻るなどの異常が発生します。この時、単純な範囲指定は誤ったプリーニングを招きます。

課題:外れ値による範囲の歪み

  • 正常時:
    event_y2025
    の MAX は 4320。
  • 外れ値出現:
    event_y2026
    に Session ID=1 という古いセッションが存在する。
  • 結果:
    event_y2025
    の範囲が「1〜4320」と判断されたままになり、制約違反(事実上)が発生するか、または誤ったプリーニングが行われるリスクがあります。

解決策:PostgreSQL の
multi-minmax
から着想を得る

PostgreSQL には、ページ境界を認識する BRIN インデックス という機能があり、そこでは新しい

multi-minmax
オペレータが導入されています(14 以降)。これは単一範囲だけでなく、複数の範囲を保持できます。 この概念を CHECK 制約へ応用します。

マルチレンジ CHECK 制約の作成

チェック制約内で、複数の範囲を

OR
連結する構造にします。

-- 2025 パーティション(通常のみ)
ALTER TABLE event_y2025 
ADD CONSTRAINT event_y2025_session_id_range
CHECK (session_id BETWEEN 1 AND 4320);

-- 2026 パーティション(外れ値を含むマルチレンジ)
-- 範囲 [1, 1] と [4320, 10000] の両方を許容
ALTER TABLE event_y2026 
ADD CONSTRAINT event_y2026_session_id_range
CHECK (
    session_id BETWEEN 1 AND 1 
    OR 
    session_id BETWEEN 4320 AND 10000
);

これで、Session ID=1 は 2 つのパーティションにまたがることをデータベースが正しく理解します。通常の値(例:6000)については、依然として適切なパーティションのみをスキャンできます。

プリーニングの確認

-- Session ID = 6000 の場合
SELECT * FROM event WHERE session_id = 6000;
-- 結果: event_y2026 のみインデックススキャンを実行(プリーニング成功)

8. まとめ:Gaps and Islands と自動化

この手法では、動的にパーティションごとの Session ID の範囲を特定する必要があります。これは古典的な**「Gaps and Islands(ギャップとアイランド)」問題**の応用です。 SQL で連続した Session ID の範囲(Island)を検出し、チェック制約を自動生成するアプローチが可能です。

-- 簡易的な範疇検出ロジックの例(Gaps and Islands パターン)
WITH detection AS (
  SELECT 
    session_id, 
    session_id - lag(session_id) OVER (ORDER BY session_id) > 1 AS is_gap 
  FROM event_y2026
), gaps AS (
  SELECT 
    session_id, 
    SUM(is_gap::int) OVER (ORDER BY session_id) AS gap_group 
  FROM detection
), ranges AS (
  SELECT MIN(session_id) mn, MAX(session_id) mx FROM gaps GROUP BY gap_group
)
-- 検出された範囲を SQL コマンドに変換して自動生成

結論

  • 問題: パーティションキーではない列でのクエリでは、プリーニングが効かない。
  • 解決策: データの特性(追加専用、単調増加など)を見抜いて CHECK 制約を定義し、最適化器に「このパーティションには特定のデータしかない」と保証させる。
  • 高度な機能: 外れ値にも対応するために、複数の範囲を持つマルチレンジチェック制約を使用する。

適切に設計されたチェック制約は、パーティションキーの選択の自由度を高め、かつパフォーマンスの妥協点を解消する強力な武器となります。

同じ日のほかのニュース

一覧に戻る →

2026/07/12 7:38

イロウによる分散AI計算のためのメッシュ LLM

## Japanese Translation: ## 概要: Mesh LLM は、既存の GPU およびメモリを何台ものマシンから集約し、中央サーバーを必要としない軽量なインフラストラクチャとして統合することで、チームが人工知能を利用する方法を根本的に変革します。これは、OpenAI 互換 API(デフォルトアドレス:`localhost:9337/v1`)としてこの統合されたコンピューティングリソースを公開し、組織が高価でブラックボックス化しているクラウドプロバイダーへの依存を排除しながら、モデルの更新、データルーティング、および下位のハードウェアに対する完全な制御を維持できるようにします。これによりコストを増加させることはありません。 アーキテクチャは、MCP、HTTP、インференスストリーム、およびメッシュイベントを通じて暴露されるマニフェストベースのプラグインシステムにより拡張可能であり、ノートパソコンサイズから 235B パラメータの巨人までをカバーする 40 以上のモデルをサポートします。235B パラメータなど的大規模な mixture-of-experts モデルについては、Mesh LLM は内部名称「Skippy」を持つ「Split mode」を採用しており、モデルレイヤーを順次ステージに分割し、ピアツーピアルーティングを用いて活性化を適度なマシンのパイプラインを通って流します。 ネットワーク管理は、iroh エンドポイントメッシュによって行われ、ここで各ノード(サーバーまたはクライアント)は公開鍵を使用して独自のアイデンティティとして機能し、中央コーディネーターの必要性はありません。接続は認証されており、iroh リレーによる NAT 通過可能な QUIC セッションが支援され、地域をまたいでヒールパンチ/リレーフォールバック機構が備わっています。プロトコルには、ゴシップ、ルーティング、HTTP 用の`mesh-llm/1`、所有者コントロールプレーンのための`mesh-llm-control/1`、低遅延に敏感な活性化輸送のための`skippy-stage/2`という 3 つの特別な QUIC ALPN が使用されます。メイン接続内では、すべてのトラフィックは先頭のバイトでデマルックスされた双方向の QUIC ストリームとなり、特定のタイプ(`GOSSIP`、`TUNNEL_HTTP`、`ROUTE_REQUEST`、`PEER_DOWN`、`PLUGIN_CHANNEL` など)に分類されます。 ソフトウェアは非常に軽量であり、インストールには約 18 MB を必要とします。公開メッシュへの参加やベンダーロックインのないプライベートデプロイメントの完全な構成が可能です。将来の計画には、iroh の Swift SDK で構築された専用モバイルアプリが含まれており、これは台頭しつつあるエージェント通信プロトコル(ACP)をサポートし、他のクライアントがメッシュにシームレスに接続できるようにします。セキュリティ、バージョン互換性、および信頼に関する判断は、カスタムゴシップレイヤーを介して内部で管理され、スケーリングやネットワーク構成に関わらず自律性を確保しています。

2026/07/12 6:58

ゼロから設計されたオープンソースの携帯型ゲーム機「RISCBoy」

## Japanese Translation: RISCBoy は、RISC-V 互換 CPU とラスタグラフィックスパイプラインを从头設計されたオープンソースの携帯用ゲームコンソールです。その主要な成果は、合成可能な Verilog 2005 で記述され、完全に検証されたプロセッサ設計であり、Lattice iCE40-HX8k(7680 ロジック要素を使用、LUT4 ベース)などの特定 FPGA ハードウェアをターゲットとしています。プロジェクトは RV32IMC インスピーションセットに厳密に従い、M モード制御レジスタ、例外処理、ベクトored 外部割り込み向けの準拠拡張機能といった本質的な機能を実装することで、アーキテクチャ準拠の他、riscv-formal サイスイツを使用して厳格な形式的検証も実施しています。完全なソースコードは `git clone --recursive https://github.com/Wren6991/RISCBoy.git` で入手可能で、サブモジュールが必要な場合は手動での更新が必要です。開発には RV32IMC ツールチェーン(`./configure --with-arch=rv32imc` で構築)と Xilinx ISIM 14.x(Linux 専用)によるシミュレーション、そして `scripts/` フォルダ内の makefile を使用します。合成にはオープンソースのツールチェーンが使用され、Yosys が合成、nextpnr が配置・配線、Project Icestorm がビットストリーム生成を担当し、Lattice HX8k 評価ボード(`HX8k-EVN.mk`)や実験的な ECP5 サポート(`ECP5-EVN.mk`)などのターゲットをサポートしています。`hdl` ディレクトリにはバスファブ、グラフィックスユニット、hazard5 プロセッサコアの Verilog ソースが含まれており、`test` ディレクトリには Verilog テストベンチとソフトウェアケースを含む回帰テストが格納されています。現在、量産は iTead の 4 レイヤープロトタイピングサービスを活用し、10 ユニットを $65 で提供する Rev A PCB から開始されており、これらツールの合成と配線に使用されています。新たな Rev B PCB がゲートウェアの成熟に合わせて待機しています。この進化は、高レベルのソフトウェア開発と低レベルのハードウェアアーキテクチャを橋渡しする完全にカスタマイズ可能なプラットフォームを求める愛好家やエンジニアにとって意義のあるステップであり、現代のオープンソースコンピューティングエコシステムへの具体的な一瞥を提供しています。

2026/07/12 8:54

オデッセイ Linux

## Japanese Translation: 提供されたテキストには、本物のニュース記事、議論、または物語は含まれておらず、そのため、そのプレースホルダー的な性質を超えて主要メッセージを抽出することは不可能です。概して、コンテンツは「$ code」などの一般的なラベルを含む構造的テンプレートとして機能しだけであり、これは具体的な技術詳細のない抽象的なプログラミング概念を表しています。「$ constitution」や「$ governance」といった用語も、ここでは定義された法的・政治的文脈を欠いています。実際の出来事、意見、または事実主张を記述する文がないため、資料は歴史的背景、具体的な日付、または製品名を確立できていません。したがって、将来の動向に関する前向きな声明、予測、またはタイムラインも含まれていません。特定のユーザー、企業、産業を特定していないため、このコンテンツによって誰が影響を受ける可能性があるかを判断することはできません。記述言語が欠如していることは、いまだに核心的情報が表明されていないことを示しています。一言で言えば、文書は静的であり、読者が全体概要または潜在的な影響を理解しようとするとアクション可能な知見、データ、あるいは意味ある主題事項を全く含んでいません。 ## Text to translate The original summary is concise, accurate, and logically consistent with the provided key points. It clearly communicates that the document is a structural template devoid of substantive information. No improvements are necessary. ## Summary The provided text offers no substantive news story, argument, or narrative, making it impossible to derive a main message beyond its placeholder nature. Essentially, the content functions only as a structural template containing generic labels like "$ code," which represents abstract programming concepts without specific technical details, and terms such as "$ constitution" or "$ governance" that lack defined legal or political contexts here. Because there are no sentences describing actual events, opinions, or factual claims, the material fails to establish any historical background, specific dates, or product names. Consequently, the text contains no forward-looking statements, predictions, or timelines regarding future developments. Without identifying specific users, companies, or industries, it is impossible to determine who might be impacted by this content. The absence of descriptive language confirms that no core information has been articulated yet. In short, the document remains static and devoid of actionable intelligence, data, or any meaningful subject matter for a reader seeking to grasp an overall gist or potential consequences.