Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wc9j4-0038D0-0o for pgsql-hackers@arkaria.postgresql.org; Tue, 23 Jun 2026 22:36:14 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wc9j2-00E1p0-2y for pgsql-hackers@arkaria.postgresql.org; Tue, 23 Jun 2026 22:36:12 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wc9j2-00E1os-1m for pgsql-hackers@lists.postgresql.org; Tue, 23 Jun 2026 22:36:12 +0000 Received: from mail-dl1-x1232.google.com ([2607:f8b0:4864:20::1232]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wc9j0-00000001zyJ-27KM for pgsql-hackers@lists.postgresql.org; Tue, 23 Jun 2026 22:36:12 +0000 Received: by mail-dl1-x1232.google.com with SMTP id a92af1059eb24-139986373b8so552400c88.0 for ; Tue, 23 Jun 2026 15:36:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782254168; x=1782858968; darn=lists.postgresql.org; h=content-transfer-encoding:in-reply-to:from:content-language :references:to:subject:user-agent:mime-version:date:message-id:from :to:cc:subject:date:message-id:reply-to; bh=Jgg6O2JAPYjtGja2pDrtg2vKHFcbECRrVLjEE6Bva6k=; b=f/Co63QcBQnschIXR8T80enqdvjA8JPCgpedbRTDaKP78JZKkFgLgrbvO2DlHSnYSX qQ5eT0tpLXVMj9lcW/r1M7ld1gWH4FniR5pq6aRypYF5VqJq3ypc9Z6GA5/Za04B5Bz0 zAcf7P4Z75RIZBbkunSYuL1HU0+KZxhdkZZfP9zQn62DHXw/UeZjMpDnauZ3Nt+AcIwW YJXbhjvieqGvYEp/2RIiQE/CT4qFvGUSWqiQFJvMUR4/aYoqIm+SgwIUxaCSi9l3Ua/6 VD5B0vjFklcyl3KyPav33pykAcUqhPBKjuPvwgA8sCd3hoqf46XEivM1N1OeX98g2LC8 gZkA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782254168; x=1782858968; h=content-transfer-encoding:in-reply-to:from:content-language :references:to:subject:user-agent:mime-version:date:message-id :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=Jgg6O2JAPYjtGja2pDrtg2vKHFcbECRrVLjEE6Bva6k=; b=d87IAD6RKtTW3+8TCExBgpTFG2nTEThU4kaO+yxggaEd+A4oay0CUbSsqZZhOvpJQa ZqCAjaWcLjbkPWd+uqXU6gGlLU4B0QNht+0Ag8x0yFjqS7szXkz9SEfwzm/JBAWO+n9i /tMpxmCTpRn8KRCAcxeAouA1Be9v7ht8MvjmPEZ0uHwv5xFSjuNpHZ/WW+odN70TdqXn M6xeJpMOgr5JWPPbNNqKC+wLlvQpKbcsYmPabIBXR+LmtZSvBjL7ywxRVuO3iE20W+Jo 2keRZIXk3cxpqW/C59UwOMNXHP7LD79rHlofJZxkFKzfkuHefJggnDCTEBkgT69qZg5r SDvg== X-Forwarded-Encrypted: i=1; AFNElJ85Oeel7WTbbIiI6YYfGLzjdNKBtkzK3RJgEZuil0RKnXqiHgagIqoJGLlk2zVvM5Yf1CGGlNvbCERb82lg@lists.postgresql.org X-Gm-Message-State: AOJu0YyBl8K0NuyWmzf7gfm5As3pJrW4J3vi2l+yvtL4EJUQvpvws3b5 xwB8+Ky6g83QUt9z2x2uNVAsLqsEn3moKn2QkGsVdwkF2NTbnk+ajemu X-Gm-Gg: AfdE7ckYAgCOsR9HbuuhO1rQ1nr7fvVfYowou8d/54PNiQezKXG2QujB548BsAs5KBv TAZ+ONuLd0gcB9lFgFYbwWgIliixcjwNL8TOGXAjtYVssXqWk8yba2sAr+pgkk1knl5LltFY7ZD Ty2huCXestLwrnhAfZDxDoPMSzBVcPshfd5TVAgh3DchurpzUMOeXkz7SC2bEFeWrme/lyTCzj6 1trhalYpeJqIESMOdeJsrjpcfx/2egE8de1wkLBAOz1Pgz13yfOBBdQVBjXVyhuC4t5yTG0URcM Y1eMPwiy836bHid+9JjTzUq4OBIxRIKpZ4EKcHpvR17VG+q51ngLbPky0fQAA0xMOr61H5vxJxT rS1DPsgqfd6/gQgvSPXjUbpJCdyYRGlGJl7DiCAX8n7Qn8siNLl8bHbo3XgwRpW9DYLU2zEkFVO 50eYOBM38fMsUXj/G+7x6N/5esjhhvh7H0aLfmDaXQbdvy X-Received: by 2002:a05:7022:79d:b0:139:819b:ec4f with SMTP id a92af1059eb24-139d0c04bddmr697778c88.14.1782254167755; Tue, 23 Jun 2026 15:36:07 -0700 (PDT) Received: from ?IPV6:2620:149:13d0::4a5? ([2620:149:13d0::4a5]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-139adcc5f14sm16792034c88.6.2026.06.23.15.36.07 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 23 Jun 2026 15:36:07 -0700 (PDT) Message-ID: <2277c338-87ee-424c-a03c-4b6f589ccf26@gmail.com> Date: Tue, 23 Jun 2026 15:36:06 -0700 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: hashjoins vs. Bloom filters (yet again) To: Tomas Vondra , PostgreSQL Hackers References: <5cd8c20c-14b5-4b0d-bedc-69bf714e87eb@vondra.me> Content-Language: en-US From: Ben Mejia In-Reply-To: <5cd8c20c-14b5-4b0d-bedc-69bf714e87eb@vondra.me> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On 5/29/26 5:55 PM, Tomas Vondra wrote: > old patches > ----------- > > Those old patches tried to do a fairly small thing during a hash join, > and that's building a Bloom filter on the inner relation (the one that > gets hashed), and then use that filter before probing the hash table. > > The benefits come from Bloom filters being (fairly) cheap, and a > negative answer (hash is not in the filter) may allows us to skip a much > more expensive operation. > > The old threads patches focused especially at two hash join cases: > > (a) A very selective join, i.e. a significant fraction of outer tuples > does not have a match in the hash table. > > (b) A selective hash join forced to do batching because the hash table > is too large, and thus forced to spill outer tuples to temporary files. > > For (a), the benefit comes from Bloom filters being much cheaper to > probe than a hash table. The exact cost depends on the implementation, > sizes, etc. We're in the ballpark of 50 vs. 500 cycles, maybe. But if > the filter discards 90% of tuples, it can be a big win. > > For (b), the filter (for all the batches at once) allows us to discard > some of the outer tuples without writing them to temporary files. Which > is way more expensive than probing a hash table. As it happens, I've been exploring the use of a bitmap filter for the same two cases you mention. This has some relevance to the issues you mention in your post about sizing, false-positive rate, etc. Instead of a Bloom filter, I chose to use a bitmap filter, with one bit per bucket on the build side. As the inner table is built, I set a bit in the bitmap filter for every occupied bucket. If a bucket is empty, there are no matching hashes and those hash values can be skipped where appropriate. The advantages of this bitmap over a Bloom filter are: - sizing is pre-determined by nbuckets - small bitmaps (4k for 32k buckets) - cheaper - nominal cost to set/check bits A well-chosen Bloom filter will be more discriminating, but the bitmap has the same no-false-negatives guarantee and costs much less space and time to build. I implemented both of your cases: Drop-before-spill: (Case b) Build per-batch bitmaps during inner partition pass and drop tuples that don't have a bit set. Saves I/O on tuples that will never match. This only works for inner and semi joins. Single-Batch probe: (Case a) Only pays off in high-miss-rate joins and a bucket array larger than L2/L3 cache. This case has a higher penalty for hash table lookup than the in-cache bitmap check. This case works in multi-batch, but the I/O cost dominates and there is no gain. I put runtime guards on both of these; I sampled the drop rate over a window and disable the filter for the rest of the pass if the rate falls below a threshold. (~5% for case b; ~25% for case a) The benchmarks are encouraging: For case a, I was able to see a best-case improvement of ~15% for carefully chosen data (dependent on L2/L3 cache size). For case b, I tested 3 cases with sparse, average and dense probe hits: sparse probe (~95% miss): +18% to +36% avg probe (~37% miss): +9% to +13% dense probe (FK-like, ~0% miss): flat, within noise (This was on a 8-core x86-64, L1 32KB/core, L2 4MB/core, L3 32MB, 31 GB RAM. PostgreSQL 19devel, serial hash join, max_parallel_workers_per_gather = 0, across work_mem = 1-8MB) Happy to share the patch and full benchmark data if useful. -Ben Mejia