1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
|
<!-- doc/src/sgml/bloom.sgml -->
<sect1 id="bloom" xreflabel="bloom">
<title>bloom — bloom filter index access method</title>
<indexterm zone="bloom">
<primary>bloom</primary>
</indexterm>
<para>
<literal>bloom</literal> provides an index access method based on
<ulink url="https://en.wikipedia.org/wiki/Bloom_filter">Bloom filters</ulink>.
</para>
<para>
A Bloom filter is a space-efficient data structure that is used to test
whether an element is a member of a set. In the case of an index access
method, it allows fast exclusion of non-matching tuples via signatures
whose size is determined at index creation.
</para>
<para>
A signature is a lossy representation of the indexed attribute(s), and as
such is prone to reporting false positives; that is, it may be reported
that an element is in the set, when it is not. So index search results
must always be rechecked using the actual attribute values from the heap
entry. Larger signatures reduce the odds of a false positive and thus
reduce the number of useless heap visits, but of course also make the index
larger and hence slower to scan.
</para>
<para>
This type of index is most useful when a table has many attributes and
queries test arbitrary combinations of them. A traditional btree index is
faster than a bloom index, but it can require many btree indexes to support
all possible queries where one needs only a single bloom index. Note
however that bloom indexes only support equality queries, whereas btree
indexes can also perform inequality and range searches.
</para>
<sect2 id="bloom-parameters">
<title>Parameters</title>
<para>
A <literal>bloom</literal> index accepts the following parameters in its
<literal>WITH</literal> clause:
</para>
<variablelist>
<varlistentry>
<term><literal>length</literal></term>
<listitem>
<para>
Length of each signature (index entry) in bits. It is rounded up to the
nearest multiple of <literal>16</literal>. The default is
<literal>80</literal> bits and the maximum is <literal>4096</literal>.
</para>
</listitem>
</varlistentry>
</variablelist>
<variablelist>
<varlistentry>
<term><literal>col1 — col32</literal></term>
<listitem>
<para>
Number of bits generated for each index column. Each parameter's name
refers to the number of the index column that it controls. The default
is <literal>2</literal> bits and the maximum is <literal>4095</literal>.
Parameters for index columns not actually used are ignored.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2 id="bloom-examples">
<title>Examples</title>
<para>
This is an example of creating a bloom index:
</para>
<programlisting>
CREATE INDEX bloomidx ON tbloom USING bloom (i1,i2,i3)
WITH (length=80, col1=2, col2=2, col3=4);
</programlisting>
<para>
The index is created with a signature length of 80 bits, with attributes
i1 and i2 mapped to 2 bits, and attribute i3 mapped to 4 bits. We could
have omitted the <literal>length</literal>, <literal>col1</literal>,
and <literal>col2</literal> specifications since those have the default values.
</para>
<para>
Here is a more complete example of bloom index definition and usage, as
well as a comparison with equivalent btree indexes. The bloom index is
considerably smaller than the btree index, and can perform better.
</para>
<programlisting>
=# CREATE TABLE tbloom AS
SELECT
(random() * 1000000)::int as i1,
(random() * 1000000)::int as i2,
(random() * 1000000)::int as i3,
(random() * 1000000)::int as i4,
(random() * 1000000)::int as i5,
(random() * 1000000)::int as i6
FROM
generate_series(1,10000000);
SELECT 10000000
</programlisting>
<para>
A sequential scan over this large table takes a long time:
<programlisting>
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
QUERY PLAN
-------------------------------------------------------------------&zwsp;-----------------------------------
Seq Scan on tbloom (cost=0.00..213744.00 rows=250 width=24) (actual time=357.059..357.059 rows=0.00 loops=1)
Filter: ((i2 = 898732) AND (i5 = 123451))
Rows Removed by Filter: 10000000
Buffers: shared hit=63744
Planning Time: 0.346 ms
Execution Time: 357.076 ms
(6 rows)
</programlisting>
</para>
<para>
Even with the btree index defined the result will still be a
sequential scan:
<programlisting>
=# CREATE INDEX btreeidx ON tbloom (i1, i2, i3, i4, i5, i6);
CREATE INDEX
=# SELECT pg_size_pretty(pg_relation_size('btreeidx'));
pg_size_pretty
----------------
386 MB
(1 row)
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
QUERY PLAN
-------------------------------------------------------------------&zwsp;-----------------------------------
Seq Scan on tbloom (cost=0.00..213744.00 rows=2 width=24) (actual time=351.016..351.017 rows=0.00 loops=1)
Filter: ((i2 = 898732) AND (i5 = 123451))
Rows Removed by Filter: 10000000
Buffers: shared hit=63744
Planning Time: 0.138 ms
Execution Time: 351.035 ms
(6 rows)
</programlisting>
</para>
<para>
Having the bloom index defined on the table is better than btree in
handling this type of search:
<programlisting>
=# CREATE INDEX bloomidx ON tbloom USING bloom (i1, i2, i3, i4, i5, i6);
CREATE INDEX
=# SELECT pg_size_pretty(pg_relation_size('bloomidx'));
pg_size_pretty
----------------
153 MB
(1 row)
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
QUERY PLAN
-------------------------------------------------------------------&zwsp;--------------------------------------------------
Bitmap Heap Scan on tbloom (cost=1792.00..1799.69 rows=2 width=24) (actual time=22.605..22.606 rows=0.00 loops=1)
Recheck Cond: ((i2 = 898732) AND (i5 = 123451))
Rows Removed by Index Recheck: 2300
Heap Blocks: exact=2256
Buffers: shared hit=21864
-> Bitmap Index Scan on bloomidx (cost=0.00..178436.00 rows=1 width=0) (actual time=20.005..20.005 rows=2300.00 loops=1)
Index Cond: ((i2 = 898732) AND (i5 = 123451))
Index Searches: 1
Buffers: shared hit=19608
Planning Time: 0.099 ms
Execution Time: 22.632 ms
(11 rows)
</programlisting>
</para>
<para>
Now, the main problem with the btree search is that btree is inefficient
when the search conditions do not constrain the leading index column(s).
A better strategy for btree is to create a separate index on each column.
Then the planner will choose something like this:
<programlisting>
=# CREATE INDEX btreeidx1 ON tbloom (i1);
CREATE INDEX
=# CREATE INDEX btreeidx2 ON tbloom (i2);
CREATE INDEX
=# CREATE INDEX btreeidx3 ON tbloom (i3);
CREATE INDEX
=# CREATE INDEX btreeidx4 ON tbloom (i4);
CREATE INDEX
=# CREATE INDEX btreeidx5 ON tbloom (i5);
CREATE INDEX
=# CREATE INDEX btreeidx6 ON tbloom (i6);
CREATE INDEX
=# EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
QUERY PLAN
-------------------------------------------------------------------&zwsp;--------------------------------------------------------
Bitmap Heap Scan on tbloom (cost=9.29..13.30 rows=1 width=24) (actual time=0.032..0.033 rows=0.00 loops=1)
Recheck Cond: ((i5 = 123451) AND (i2 = 898732))
Buffers: shared read=6
-> BitmapAnd (cost=9.29..9.29 rows=1 width=0) (actual time=0.047..0.047 rows=0.00 loops=1)
Buffers: shared hit=6
-> Bitmap Index Scan on btreeidx5 (cost=0.00..4.52 rows=11 width=0) (actual time=0.026..0.026 rows=7.00 loops=1)
Index Cond: (i5 = 123451)
Index Searches: 1
Buffers: shared hit=3
-> Bitmap Index Scan on btreeidx2 (cost=0.00..4.52 rows=11 width=0) (actual time=0.007..0.007 rows=8.00 loops=1)
Index Cond: (i2 = 898732)
Index Searches: 1
Buffers: shared hit=3
Planning Time: 0.264 ms
Execution Time: 0.047 ms
(15 rows)
</programlisting>
Although this query runs much faster than with either of the single
indexes, we pay a penalty in index size. Each of the single-column
btree indexes occupies 88.5 MB, so the total space needed is 531 MB,
over three times the space used by the bloom index.
</para>
</sect2>
<sect2 id="bloom-operator-class-interface">
<title>Operator Class Interface</title>
<para>
An operator class for bloom indexes requires only a hash function for the
indexed data type and an equality operator for searching. This example
shows the operator class definition for the <type>text</type> data type:
</para>
<programlisting>
CREATE OPERATOR CLASS text_ops
DEFAULT FOR TYPE text USING bloom AS
OPERATOR 1 =(text, text),
FUNCTION 1 hashtext(text);
</programlisting>
</sect2>
<sect2 id="bloom-limitations">
<title>Limitations</title>
<para>
<itemizedlist>
<listitem>
<para>
Only operator classes for <type>int4</type> and <type>text</type> are
included with the module.
</para>
</listitem>
<listitem>
<para>
Only the <literal>=</literal> operator is supported for search. But
it is possible to add support for arrays with union and intersection
operations in the future.
</para>
</listitem>
<listitem>
<para>
<literal>bloom</literal> access method doesn't support
<literal>UNIQUE</literal> indexes.
</para>
</listitem>
<listitem>
<para>
<literal>bloom</literal> access method doesn't support searching for
<literal>NULL</literal> values.
</para>
</listitem>
</itemizedlist>
</para>
</sect2>
<sect2 id="bloom-authors">
<title>Authors</title>
<para>
Teodor Sigaev <email>teodor@postgrespro.ru</email>,
Postgres Professional, Moscow, Russia
</para>
<para>
Alexander Korotkov <email>a.korotkov@postgrespro.ru</email>,
Postgres Professional, Moscow, Russia
</para>
<para>
Oleg Bartunov <email>obartunov@postgrespro.ru</email>,
Postgres Professional, Moscow, Russia
</para>
</sect2>
</sect1>
|