2012-10-24 13 views
7

私は一種の逆範囲検索のようなものである次のクエリを持っている:だけ$のLTEの識別子を使用して実行すると

db.ip_ranges.find({ $and: [{ start_ip_num: { $lte: 1204135028 } }, { end_ip_num: { $gt: 1204135028 } }] }) 

、クエリはすぐに返されます。しかし、同じクエリで$ gtと$ lteの両方を実行すると、非常に遅い(秒単位)。

start_ip_numフィールドとend_ip_numフィールドの両方にインデックスが付けられます。

このクエリを最適化するにはどうすればよいですか?

EDIT

私は、クエリの説明()関数を使用する場合、私は以下のようになります。私は、複合インデックスを追加したら

{ 
    "cursor" : "BtreeCursor start_ip_num_1", 
    "nscanned" : 452336, 
    "nscannedObjects" : 452336, 
    "n" : 1, 
    "millis" : 2218, 
    "nYields" : 0, 
    "nChunkSkips" : 0, 
    "isMultiKey" : false, 
    "indexOnly" : false, 
    "indexBounds" : { 
     "start_ip_num" : [ 
      [ 
       -1.7976931348623157e+308, 
       1204135028 
      ] 
     ] 
    } 
} 

EDIT 2

を、 explain()関数は、以下を返します。

{ 
    "cursor" : "BtreeCursor start_ip_num_1_end_ip_num_1", 
    "nscanned" : 431776, 
    "nscannedObjects" : 1, 
    "n" : 1, 
    "millis" : 3433, 
    "nYields" : 0, 
    "nChunkSkips" : 0, 
    "isMultiKey" : false, 
    "indexOnly" : false, 
    "indexBounds" : { 
     "start_ip_num" : [ 
      [ 
       -1.7976931348623157e+308, 
       1204135028 
      ] 
     ], 
     "end_ip_num" : [ 
      [ 
       1204135028, 
       1.7976931348623157e+308 
      ] 
     ] 
    } 
} 

ただし、perfはまだ不十分です(秒単位)。

+0

'.find({...})。explain()' 'は良い出発点です。 Wes Freemanが尋ねるように、 '' {start_ip_nm:1、end_ip_num:1} ''のインデックスはありますか? – slee

+1

あなたが解決しなければならないことは、 '$と'を使う代わりに単一のクエリセレクタオブジェクトを使うことです。 'db.ip_ranges.find({start_ip_num:{$ lte:1204135028}、end_ip_num:{$ gt:1204135028}})' – JohnnyHK

+0

Bツリーは1つの一致を見つけるために> 400kのエントリをスキャンする必要があります。それが役立つかどうかを確認するためにボックスクエリを試してみてください。私はあなたが1秒未満でそれを得るだろうと確信しています。 –

答えて

3

したがって、ダブルレンジクエリはMongoでは不適切です。 {start_ip_num: 1, end_ip_num: 1}の両方を含む単一のインデックスがあるとします。

これで十分に近づくことができない場合(多くの場合、最初のフィールドから返されたデータが十分にある場合は遅くなりますが、多くのBツリースキャンが必要です)、できるトリックがありますこれは2次元ボックスのクエリーを使って戦う(一度に2つの範囲にしか作用しない)。

基本的には、[start_ip、end_ip]などの配列内の2つのポイントを含むフィールドに2Dジオインデックスを配置し、最小/最大値を十分に高く設定して、デフォルトではわずか-180/180です。

最後に、ボックスの1つの隅にあるminから$ lteまでの範囲と、ボックスの他の隅にあるgtとmaxの値を持つ範囲クエリを使用します。構文については、http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-BoundsQueriesを参照してください。

それは次のようになります。

db.ip_ranges.find({ip_range:{$within:{$box:[[0, 1204135028], [1204135028, max]]}}}); 

最大はあなたが持つことができる最大のIPがあります。

これを見てから間違っているかもしれませんが、コンセプトは健全であり、ダブルレンジクエリは通常の2フィールドのBツリーインデックス。一貫して1秒以下(普通は数百msですが)、通常のインデックスでは数秒ですが、当時は何億ものドキュメントがあったと思いますが、しばらくしてこの覚えているベンチマークを穀物塩の。結果は、データと範囲のサイズによって大きく異なると私は確信しています。

更新:数値が小さくて数字が大きい場合は、bitsの設定を試してみるとよいでしょう。私にとっては、平均的なクエリには影響していないようです。構文についてはhttp://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-CreatingtheIndexを参照してください。

+0

はい、私は既にstart_ip_numとend_ip_numフィールドに2つのインデックスを持っています...あなたのソリューションに試してみましょう...ありがとう! – Bryan

+0

両方のフィールドで複合インデックスを最初に試してください。 mongoはクエリごとに1つのインデックスしか使用できないことに注意してください。あなたのexplain()結果を質問に投稿してください。 –

+0

$ boxクエリを試しました。それは動作し、約1秒後に戻ります。これはとても奇妙です。私は約1ミルの文書しか持っていない。このようなレンジ操作はかなりストレートだと思われるが、モンゴはそれをまったく処理していない。 – Bryan

0

実験と研究のトン後、私はこの出くわした:私は周りにダウンし、クエリを取得することができるよ

https://groups.google.com/forum/?fromgroups=#!topic/mongodb-user/IUwOzWsc0Sg

は、このクエリで200-300ms、AND は、すべてのインデックスを削除するあなたはこれが動作するために、すべてのインデックスを削除する必要が!!!):

db.ip_ranges.find({start_ip_num:{$のLTE:1204135028}、end_ip_num:{$ GT:1204135028}})の限界。 (1)

なぜ私に質問しないでください。私はそれを説明することはできません。興味があれば、MongoDBでMaxMindからGeoIPデータベースを構築していました。

+0

Mongoはダブルレンジクエリをうまく実行しません。あなたのインデックスがない結果は、大規模では速くはありませんが、これが単なるGeoIPリストであれば、あまり成長しません。また、あなたは1つの結果しか必要としていないと言いました。 .limit(1)の代わりにfindOneを実行するだけで済みます。私はあなたがなぜ単一の範囲を見つけるためにそのような大きな範囲を検索する必要があるのだろうか不思議です。あなたのIPから(ネットワークや何かの最下位/最上位に到達するために)いくらか妥当な量を引くならば、範囲クエリの最小値/最大値を指定するほうがはるかに速い結果になるでしょう。 –

0

トリックは、$ lteとソートを使用することです。私は数ミリ秒までクエリを取得しました。

私はまったく同じ問題を抱えていました。特定のIPアドレスに一致するCIDRブロックを見つけることができました。私も$ gteと$ lteを使ってみましたが、10秒の応答時間がありました。

私は別の方法で問題を解決しました。 MaxMindデータベースのCIDRブロック(IPアドレス範囲)は重複しないことに注意してください。各IPアドレスは最大で1つの結果と一致します。だから、あなたがする必要があるのは、特定のIPアドレスよりも小さいstart_ip_numが最大のCIDRブロックを見つけることだけです。次に、end_ip_numが特定のIPアドレスよりも大きいことをアプリケーションコードで確認します。ここで

は(ノードのMongoDBクライアントを使用して)コードです:

// Convert IP address to base 10. 
var ipToDecimal = function (ipAddress) { 
    var split = ipAddress.split('.'); 
    return (split[0] * 16777216) + (split[1] * 65536) + (split[2] * 256) + (+split[3]); 
}; 

var ipAddress = '1.2.3.4'; 
var ipDecimal = ipToDecimal(ipAddress); 

db.ip_addresses.find({start_ip_num: {$lte: ipDecimal}}, {_id: 0, country_code: 1, end_ip_num: 1}, {limit: 1, sort: [['start_ip_num', -1]]}).toArray(function (error, ipAddresses) { 
    if (ipAddresses[0] && ipAddresses[0]['end_ip_num'] >= ipDecimal) { 
    console.log('IP address found: ', ipAddresses[0]['country_code']); 
    } else { 
    console.log('IP address not found.'); 
    } 
}); 

はstart_ip_numに索引を作成してください。

5

Ip2location websiteによれば、範囲問合せなしでmongodbを使用してIPアドレスの高速照会を実現できます。 MongoDBの{ ip_to: 1 }に一つだけのインデックスを作成し、でIPを照会:この設定では

db.collection_name.find({ ip_to: { $gte : ip_integer } }).sort({ ip_end: 1 }).limit(1) 

私は600万文書コレクションで1msのクエリ時間を得ました。

+0

素晴らしいこれは私のために働いた。クエリーには約330万のドキュメントが平均で約50ミリ秒かかります。 btw、私のコードでは、返されたドキュメントのstart_ip_numが照会されたIPアドレス以下であることを確認するためのチェックを追加しました。これは、同等の範囲の照会相手を満たすip範囲を持つ文書が実際に存在することを保証するためです – Tanvir

関連する問題