Under postgres 8.1, the "<<=" comparison yields very slow queries with large tables. I can rewrite the query without the "<<=" operator by generating all 33 possible netmasks (0 through 32) for a given IP. This ugly rewrite runs about 12 times faster (6 seconds versus
0.5 seconds). Be aware that EXPLAIN ANALYZE seems to be run a different query plan since the first query runs even slower with EXPLAIN ANALYZE. Setting enable_seqscan did not improve the query speed.
-Edwin
Here is the original query:
------------------------------------------------------------------------
SELECT ip_address, ip_block
FROM ip_inventory
JOIN route ON ip_address <<= ip_block
WHERE ip_address < '
1.5.0.0'
ORDER BY ip_address;
------------------------------------------------------------------------
Here is the rewritten query:
------------------------------------------------------------------------
SELECT ip_address, ip_block
from ip_inventory
JOIN route
ON ip_block IN (
network(ip_address),
network((host(ip_address) || '/31')::inet),
network((host(ip_address) || '/30')::inet),
network((host(ip_address) || '/29')::inet),
network((host(ip_address) || '/28')::inet),
network((host(ip_address) || '/27')::inet),
network((host(ip_address) || '/26')::inet),
network((host(ip_address) || '/25')::inet),
network((host(ip_address) || '/24')::inet),
network((host(ip_address) || '/23')::inet),
network((host(ip_address) || '/22')::inet),
network((host(ip_address) || '/21')::inet),
network((host(ip_address) || '/20')::inet),
network((host(ip_address) || '/19')::inet),
network((host(ip_address) || '/18')::inet),
network((host(ip_address) || '/17')::inet),
network((host(ip_address) || '/16')::inet),
network((host(ip_address) || '/15')::inet),
network((host(ip_address) || '/14')::inet),
network((host(ip_address) || '/13')::inet),
network((host(ip_address) || '/12')::inet),
network((host(ip_address) || '/11')::inet),
network((host(ip_address) || '/10')::inet),
network((host(ip_address) || '/9')::inet),
network((host(ip_address) || '/8')::inet),
network((host(ip_address) || '/7')::inet),
network((host(ip_address) || '/6')::inet),
network((host(ip_address) || '/5')::inet),
network((host(ip_address) || '/4')::inet),
network((host(ip_address) || '/3')::inet),
network((host(ip_address) || '/2')::inet),
network((host(ip_address) || '/1')::inet),
'0.0.0.0'::cidr
)
WHERE ip_address < '1.5.0.0'
ORDER BY ip_address;
------------------------------------------------------------------------
Here is the SQL for creating and populating the test tables:
------------------------------------------------------------------------
BEGIN;
CREATE TABLE range (value integer);
COPY range FROM STDIN;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
\.
CREATE TABLE ip_inventory (ip_address inet unique);
-- add 279841 ips
INSERT INTO ip_inventory
SELECT (a || '.' || b || '.' || c || '.' || d)::inet
FROM range AS w(a)
JOIN range AS x(b) ON TRUE
JOIN range AS y(c) ON TRUE
JOIN range AS z(d) ON TRUE;
CREATE TABLE route (ip_block cidr unique);
-- add 12167 routes
INSERT INTO route
SELECT (a || '.' || b || '.' || c || '.0/24')::cidr
FROM range AS w(a)
JOIN range AS x(b) ON TRUE
JOIN range AS y(c) ON TRUE;
COMMIT;
------------------------------------------------------------------------