【テクニカル・上級編】 ネットワークアドレス型 – PostgreSQL

なぜ、IPアドレスを「ただの文字列」として扱うのか?

PostgreSQLを長年触っていると、たまに「IPアドレスをTEXT型で保存している」という設計に出くわすことがあります。検索要件が完全一致だけならまだしも、サブネット範囲の判定やCIDR計算が絡んでくると、アプリケーション側で複雑なパース処理を実装することになり、DBのポテンシャルを殺してしまう。実にもったいない。

PostgreSQLには、`inet`、`cidr`、そして`macaddr`という、ネットワークエンジニアが泣いて喜ぶ「特化型データ型」が標準で備わっています。これらは単なる格納庫ではなく、最適化されたバイナリ表現と、強力な演算子を内包した「第一級市民」です。今日は、この辺りの設計とパフォーマンスの裏側について、少し掘り下げてみましょう。

—

inet と cidr:その微妙な(しかし決定的な)違い

まず、`inet`と`cidr`の違いを整理しましょう。一見似ていますが、その設計思想は少し違います。

  • `inet`: IPアドレスと、それに続くネットマスク(またはホストビット)を保持します。`192.168.1.5/24` といった入力を受け付けます。
  • `cidr`: ネットワークそのものを表現するための型です。ホストビットが設定されていると、PostgreSQL側でエラーを吐くか、あるいは警告と共に正規化してくれます。

内部構造の話

`inet`型は内部的に `struct` で管理されており、アドレスファミリー(IPv4/IPv6)の識別子、ネットマスクの長さ、そして実際のIPアドレスがバイト列として格納されています。

ここがポイントですが、`inet`型は、TEXT型に比べてストレージ効率が圧倒的に高いだけでなく、比較演算子(`<`, `>`, `=`)がバイト単位で比較されるため、B-Treeインデックスと非常に相性が良いのです。

—

インデックス設計:GiSTか、B-Treeか

ネットワークアドレスをインデックス化する場合、迷うのが「B-Treeにするか、GiSTにするか」という点です。

1. B-Tree(標準的アプローチ)

完全一致や、範囲検索(`WHERE ip >= ‘…’ AND ip <= '...'`)を行うなら、迷わずB-Treeを選んでください。PostgreSQLのB-Treeは、`inet`型の比較演算子をサポートしているため、ソートも検索も驚くほど高速です。

2. GiST(包含関係の検索)

もし、「このIPアドレスが、どのサブネット(CIDR)に含まれるか?」という包含検索を頻繁に行うなら、GiSTインデックス一択です。

— 包含演算子 << を使った検索 SELECT FROM networks WHERE network << '192.168.1.0/24'; これにGiSTインデックスを貼ることで、複雑なオーバーラップ判定を空間インデックス的に解決できます。R-Tree的なアプローチでネットワークツリーを探索するため、テーブルが数百万件規模になっても、検索速度の劣化を最小限に抑えられます。 ---

現場で役立つ「計算」の勘所

組み込み関数を使いこなすと、アプリケーション層での面倒なIP計算はDB側で完結します。

  • サブネットの抽出: `inet_merge()` や `inet_same_host()` などは有名ですが、意外と使われないのが `hostmask()` や `netmask()` です。
  • IPアドレスの計算: `set_masklen()` を使うと、サブネットの境界を動的に変更できます。
  • 実用例: 例えば、不正アクセス対策で「特定のサブネットからのリクエストをブロックする」というロジックを組む際、アプリケーションでビット演算をする必要はありません。

— IPがサブネットに含まれるかを確認する演算子
WHERE ip_column <<= '10.0.0.0/8' この演算子一つで、インデックスを有効活用しながら、非常にエレガントかつ高速なフィルタリングが可能です。 ---

パフォーマンストラブルシューティング:罠に落ちないために

最後に、少しだけ現実的な話を。

`inet`型を使っているのに「なぜか検索が遅い」というケースの多くは、「暗黙の型変換」が原因です。例えば、インデックスが貼られた`inet`カラムに対して、文字列リテラルを直接投げている場合、クエリプランナが適切にインデックスを使えないことがあります。

必ず `CAST` を明示するか、適切な型のパラメータとして渡す癖をつけましょう。

また、`macaddr`型についても一言。`macaddr`は6バイトの固定長データ型です。これもTEXTで持つと17バイト以上消費しますが、専用型なら内部的に8バイト(正確にはアライメント込みで)で収まります。大量のMACアドレスをログとして蓄積する場合、この差はメモリ(バッファキャッシュ)のヒット率に直結します。

—

結論:DBエンジニアの美学

結局のところ、データ型を適切に選ぶということは、「そのデータが持つ意味論を、DBに理解させる」という作業に他なりません。

IPアドレスを単なる文字の羅列として扱うのではなく、ネットワークという「数学的な構造を持つ実体」としてDBに渡してあげる。そうすれば、PostgreSQLは期待以上のパフォーマンスで応えてくれます。

コードを書くとき、ふと手を止めて考えてみてください。「これはただの文字列か? それとも、計算されるべきネットワークアドレスか?」と。その問いこそが、枯れた技術をモダンに使いこなす、エンジニアのセンスなのだと思います。

コメント

タイトルとURLをコピーしました