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 1wUiVt-001OBN-1x for pgsql-hackers@arkaria.postgresql.org; Wed, 03 Jun 2026 10:07:53 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wUiVr-000x9g-1R for pgsql-hackers@arkaria.postgresql.org; Wed, 03 Jun 2026 10:07:51 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wUiVr-000x9Y-08 for pgsql-hackers@lists.postgresql.org; Wed, 03 Jun 2026 10:07:51 +0000 Received: from relay8-d.mail.gandi.net ([217.70.183.201]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wUiVl-00000000tPm-1PUF for pgsql-hackers@postgresql.org; Wed, 03 Jun 2026 10:07:50 +0000 Received: by mail.gandi.net (Postfix) with ESMTPSA id 940403E997; Wed, 3 Jun 2026 10:07:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=vondra.me; s=gm1; t=1780481261; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=GSQh+pGERpzSOdvzLa9j4Lxt8u4GKILn2lrLK98Ii0U=; b=KRTlrqWFazaWJjzZ0aSgi0whHqPKDa1VvNTs7L3exZ54jb6z+fxsrGvj0pZze+pKZBzbF1 Icto1CUux3PZa/ED3mLZ7QZUjMqMMzfo/+WjsXm8Etr/a9AaSwL52uur2/yYl63aPCXZxI dW0BjMuAp6K9yn1Kl5sISl87nbrCTlcMzst+k/b/ySgp02WlSmKCEykpXCXME+vy6Tdf7v BgH1CSdUwthYougJk8tNsbd/KwB77+VfuEdJLJwCNoPlKKcL5sVP2eEIH0uLeaywp0kkS/ xJ9yMS/A5GFYXL/nH1LzYHAoAQU7hYyttUtVenZdsl6tSVD3dZ7SMcpoBv1bTA== Message-ID: Date: Wed, 3 Jun 2026 12:07:40 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: hashjoins vs. Bloom filters (yet again) To: Oleg Bartunov Cc: PostgreSQL Hackers References: <5cd8c20c-14b5-4b0d-bedc-69bf714e87eb@vondra.me> Content-Language: en-US From: Tomas Vondra In-Reply-To: Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-GND-Sasl: tomas@vondra.me X-GND-Cause: dmFkZTElSe3eQlOpl7mMHQQZRnV5DqHA/GXvisrpab3wplsJ6b1E4oR3ZnvSjwBRbpRWJKxeOQqJwaS9ow1K/SU3gzomMDF7JbdpSHUdke96HOx3BJtDLiZd+Bbtn8xYXPAmLvY+gHrx9+kv0z6feGkabXTT7s6Q5tMdciZfOdd1uZcwJD8uzdY5p8MhPiIRDvCfJlIJd0XTd0az+Q1WaswzPRWWndnbWTJm8o7ugiM3ucwM/rT4YraHh4lp+Z21/fZlnu81MLateFywROuPleBMtv0PklHZwhwQeXCfHscUmoQTqvmI+VuvRxrb7fy2s8IKzhKiENrf82Gz/UOTLV5YgnQUBCI16G3tG1FOdo3oKCcpT1kmpjYnGebaHUBJZnD9idR9denFEdAjLewgSR2TOxgIuIeD1iU23AhC68+gOVhRjJpQYGZ1U3bxpUUgQzIQIHkAW7VI5DwjbqtyKdatj8elGH9PBv8qAagQlyheL0BSMHwRspWhgoRCxBdsK/i6Xc2ncIcdlXpQQZ5nn+EhsaHEVA06nHneqSrt0hp1xsFGjtyRy0HUlnzrwPJ9cQJNrbuVhPg9ucghqmkQScfwbZ0RNFoPHn8pVW3KDjADQGC18WIdIKmxujgij3Ui/5pf1i5mRPKtNNdngW9AWbNnbZ72wMkSeo86Gy6f6CE3/qmWyg X-GND-State: clean X-GND-Score: -100 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On 6/3/26 11:20, Oleg Bartunov wrote: > ... > > Bloom filters have two rather different roles here. > > For a local Hash Join optimization, Bloom does not require any > particular physical ordering of the heap. It can be useful simply when > the join is selective enough, or when batching/spilling makes failed > probes expensive: the Bloom filter rejects many outer tuples before a > full hash-table probe or before writing them to temporary batches. > Right. Adding a filter within a hash join is certainly less ambitious, and the possible benefits are smaller. > But once we talk about pushing a runtime filter down to the scan/storage > layer, the physical preconditions become crucial. To get more than a > cheap per-row check, the scan must have something coarse-grained to > skip: partitions, row groups, chunks, block ranges, dictionaries, min/ > max metadata, BRIN-like summaries, etc. Without that, the filter is > still correct, but the benefit is mostly CPU/probe reduction rather than > avoiding data production. > Maybe, but there's also ongoing work on adding batches to the executor, in which case we'd eliminate "row groups" even when using a filter in the scope of a hashjoin operator. Of course, the tuples will flow all the way up to that operator. > So for me the most interesting part of this thread is not Bloom itself, > but the architectural idea: pushing runtime knowledge down to the scan > node, against the normal direction of data flow. The build side of a > join produces compact knowledge about admissible keys, and lower layers > may use it before rows are materialized and sent upward. > > I saw this in my own experiments with zone/chunk-oriented storage for > Postgres: static predicates could prune zones nicely, but joins were the > hard case because the useful filtering knowledge was produced above the > scan. A runtime semi-join filter pushed from the Hash Join build side > into the scan could turn join-derived knowledge into scan-level pruning. > > For example: > >   SELECT sum(e.cost) >   FROM events e >   JOIN accounts a ON e.account_id = a.id >   WHERE a.region = 'NP'; -- Nepal > > The events scan does not know which account_id values are EU accounts. > That knowledge is produced above it, on the build side of the join. A > runtime semi-join filter pushed from the Hash Join build side down into > the events scan could let the scan reject impossible account_id values > before producing tuples. > Yes. This is known as "predicate transfer" in academic papers. > For a plain heap scan this may mostly save hash probes. But with zone/ > chunk-oriented storage, where chunks have dictionaries, min/max > metadata, Bloom summaries, or tenant ranges, the same runtime filter can > skip whole chunks. That is the part I find most interesting: turning > join-derived knowledge into scan-level pruning, against the normal > direction of data flow. > > Bloom is just one carrier for that knowledge. The real feature is a > pluggable runtime-filter mechanism that heap, CustomScan, FDW, columnar/ > table AMs, partitioned storage, or chunk/cold storage can consume at the > level they understand. > > This may be a topic for a separate thread, because it quickly becomes > less about Hash Join Bloom filters and more about runtime knowledge > pushdown into storage. > Right, there's a general concept of a "filter", and Bloom filters are just one example of that. And maybe we could build other types of filters more suitable for the scan. But I think it'll still be tied to a hash join, because what other nodes / joins can build the filter? regards -- Tomas Vondra