Postgresqlの高速IPロケーション

このトピックでは、Webプロジェクトで非常に一般的なタスク、つまりIPアドレスによる場所の特定について説明します。 まず、ユーザーの場所を特定するために、何らかのジオデータベースが必要です。 人気のある2つの無料オプションを次に示します。

IpGeoBaseは非常に優れた無料のベースですが、残念ながらロシアのIPアドレスでのみです。

MaxMind-すべての国のIPアドレスの巨大なデータベース。 データベースの無料のライトバージョンを提供します。 ロシアのIPアドレスのデータベースの精度は、IpGeoBaseほど正確ではありません。 また、データベースを操作するための何らかのAPIを提供するため、非常に迅速に選択を行うことができます。



これらのデータベースをダウンロードして、Postgresqlデータベースのテーブルにアップロードしたとします(誰かが欲しければ、ダウンロード自体は範囲外です-将来、COPYが何で、何が食べられるのか教えてください)。 一般に、この構造のテーブルを取得します。



startip エンディップ location_id
2130706433 2130706433 1


ここに:

startipは、長い形式のIPアドレスのブロックの始まりです。

endip-長い形式のIPアドレスのブロックの終わり

location_id-ロケーション識別子(都市、地域、国など、maxmindは座標も含む)。







ソリューションNo. 1



これに最初に出会ったとき、私はマシンを次のようにすることに決めました。

1.(startip、endip)にインデックスを作成します

2. IPアドレスをBIGINTに変換する単純な関数を作成します。

CREATE OR REPLACE FUNCTION "public"."extract_long_from_ip" (ip text)

RETURNS bigint AS

$body$

SELECT (((elements[1]::bigint * 256) + elements[2]::bigint) * 256 + elements[3]::bigint) * 256 + elements[4]::bigint

FROM (

SELECT string_to_array($1, '.') as elements

) t;

$body$ LANGUAGE 'sql' IMMUTABLE;







3.出来上がり:

SELECT location_id

FROM geo.ip_blocks

WHERE extract_long_from_ip('93.158.134.8') BETWEEN startip AND endip;







そして、それは簡単ですか? -あなたが尋ねます。 しかし、一度だけではありません。 このリクエストは正しく機能しますが、非常に時間がかかります。 問題は、postgresqlが「SOMETHING BETWEEN X AND Y」のようなクエリでインデックスを使用する方法を知らないことです。 場所をすぐに決定する必要がある場合(そしてそのような場合がありました)-テーブル全体のseqスキャンはまったく価値がありません。



そして今何? IPロケーションをアプリに転送しますか? まったく必要ありません-検索を大幅に高速化できます。 これを行うには、素晴らしいモジュールip4rを使用します。



設置



su -c 'yum install postgresql-ip4r'





または

sudo apt-get install postgresql-8.3-ip4r





または単にサイトからダウンロードします。



contribディレクトリでip4r.sqlファイルを探し、必要なデータベースにインストールします。

psql -U user -f "...../contrib/ip4r.sql" database







テーブル構造



ip4rモジュールは、ip4とip4rの2つの新しいタイプを提供します。 最初はIPv4アドレスに対応します。 2番目は、IPv4アドレスの特定の間隔です。 ip4rタイプは、特に興味深いものです。事実、インデックス可能なのです。



テーブルを変更します。

ALTER TABLE geo.ip_blocks ADD COLUMN ip_range ip4r;

UPDATE geo.ip_blocks SET ip_range = ip4r(startip::ip4, endip::ip4);

ALTER TABLE geo.ipblocks DROP COLUMN startip;

ALTER TABLE geo.ipblocks DROP COLUMN endip;







インデックスを作成します。

CREATE INDEX ip_blocks_idx ON geo.ip_blocks USING gist (ip_range);







それだけです



このような単純なリクエストを使用して、何百回も加速します。

SELECT location_id FROM geo.ip_blocks WHERE ip_range >>= '93.158.134.8'::ip4;









UPD:

もちろん、すべてがハードウェアと基本負荷に大きく依存しています。 しかし、例として、次の結果が得られました。

NOTICE: 100 queries without ip4r 00:00:14.988

NOTICE: 100 queries with ip4r 00:00:00.008









UPD2:

コメントは、迅速なIP検索のための別の解決策を提案しました。

Ip4rは使用されず、startipにインデックスが作成され、リクエストで小さなフェイントを使用します。

CREATE INDEX ip_blocks_idx ON geo.ip_blocks USING btree(startip);

SELECT CASE WHEN extract_long_from_ip('93.158.134.8') <= endip

THEN location_id

ELSE NULL END AS location_id

FROM geo.ip_blocks

WHERE startip <= extract_long_from_ip('93.158.134.8')

ORDER BY startip DESC

LIMIT 1;









UPD3:

コメントから-geoipベースを取得できる別の場所。 Maxmind liteはベースとして使用されますが、ひどく手直しされていません+ SQLベースまたはCSVの形式で提供されます。 また、このデータベースを正しく操作する方法についての適切な説明もあります。

http://ipinfodb.com/ip_database.php



All Articles