Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.80) (envelope-from ) id 1a9MHS-0003PM-NO for pgsql-hackers@arkaria.postgresql.org; Thu, 17 Dec 2015 00:18:50 +0000 Received: from localhost ([127.0.0.1] helo=postgresql.org) by malur.postgresql.org with smtp (Exim 4.84) (envelope-from ) id 1a9MHS-0000pY-5f for pgsql-hackers@arkaria.postgresql.org; Thu, 17 Dec 2015 00:18:50 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84) (envelope-from ) id 1a9MG7-0007NS-F2 for pgsql-hackers@postgresql.org; Thu, 17 Dec 2015 00:17:27 +0000 Received: from mail-wm0-x233.google.com ([2a00:1450:400c:c09::233]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.84) (envelope-from ) id 1a9MG3-0003VF-3A for pgsql-hackers@postgresql.org; Thu, 17 Dec 2015 00:17:26 +0000 Received: by mail-wm0-x233.google.com with SMTP id p187so23868293wmp.0 for ; Wed, 16 Dec 2015 16:17:21 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=subject:to:references:cc:from:message-id:date:user-agent :mime-version:in-reply-to:content-type:content-transfer-encoding; bh=GPJZZLtTFcxJqk2/ITtNtuccmGMKdDplD6IEnSQCptg=; b=BcirHc5WUvI06i649b/Gq67h/6ppOLDNTEaLUBEdBi/nj27F4Johzdb2n35Ag8v97K bffFmEn/ShF6DFwVCh1rPdz3nGiEr0LP1CpFnL40E/HX+E8thUGUn3peOxaGWVK9UKUQ MmnltDX2xMwzghxan6yqbT6rvAn6dRosQTiTh9MQWigeWdiXeq2V8YJYDyWK/d/TiaQ/ t5xtRjTLSq41cJZnhcMnxt+smzkqaNbjUG/7tZ9GQCcjxHW4hcQ5HIZMp+YyLCdX+HuM exIUHjSlhzwTsJCOvphh/uH+mgtS/FCAD0ThONw4K+6iryfcv5SbdBrffdEgnO7gmj3a 5QDQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:subject:to:references:cc:from:message-id:date :user-agent:mime-version:in-reply-to:content-type :content-transfer-encoding; bh=GPJZZLtTFcxJqk2/ITtNtuccmGMKdDplD6IEnSQCptg=; b=OlB8y3xvJr4eGLZo+TL+0nK3YrnLCW4fl+xW6knUdY2NR+76dn7gBC237S631HbSef UJ/T5lxAfpeMX8WOZSAOBWhEVIeac2c01h4mgdykeAy3KWcVNtBUPTnQQ7Gp0D2jUrpR wZ8p1CXQzkBe5BEFeQksCwLFyzs+gMP4fCDdgwLyu0GVZpLCxjyt/R+n/tdSwImCa8kR wwGH6AmEuukBGGDIHHCxB117g2Yt8d418Z7JwTXYXVj/cKLJvmQEx/JWPnc0qV92t1lB JDK4XiKRl0ydD8TmIjunOxVAaEeUAcD8USwJX8miDGbNY/Kuzq2GiQ/AnmnVW49q05N4 t48g== X-Gm-Message-State: ALoCoQnakf/cYJHMoxf74AyV+ZoNVmbLP4qj1Guqi/L1Nh0TX9n2DkGMx2zuyP8ZBynkS8Duv9dnD0z7IxARokHjwztz723G56OWyr9P2o/LazDOx5M/gH2UHEO0yaUngjc2Qys6G5qjK7z3X33+V6M2moWRuSJTEnKc36tRTEIyIV6N4rF8HxQ5f6CJPk20/3fFvWHHkbsrwTYMAuvIx6p2q1hCVJzdTQ== X-Received: by 10.194.110.162 with SMTP id ib2mr11195046wjb.179.1450311441103; Wed, 16 Dec 2015 16:17:21 -0800 (PST) Received: from [10.137.1.13] (ip-78-45-216-26.net.upcbroadband.cz. [78.45.216.26]) by smtp.gmail.com with ESMTPSA id l20sm27625800wmd.20.2015.12.16.16.17.19 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 16 Dec 2015 16:17:20 -0800 (PST) Subject: Re: Performance improvement for joins where outer side is unique To: David Rowley References: <20150320.121140.148610378.horiguchi.kyotaro@lab.ntt.co.jp> <27152324.eFKJb4Khb5@dinodell> <8608754a32d1325c2f83ba8b1df71585@xs4all.nl> <55DA1F9F.5040204@2ndquadrant.com> <5085.1440375543@sss.pgh.pa.us> <8907.1440383377@sss.pgh.pa.us> <56718B15.6060507@2ndquadrant.com> Cc: Tom Lane , PostgreSQL-development From: Tomas Vondra Message-ID: <5671FF0D.2010403@2ndquadrant.com> Date: Thu, 17 Dec 2015 01:17:17 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.1.0 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit X-Pg-Spam-Score: -2.6 (--) List-Archive: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Mailing-List: pgsql-hackers Precedence: bulk Sender: pgsql-hackers-owner@postgresql.org Hi, On 12/16/2015 11:40 PM, David Rowley wrote: > On 17 December 2015 at 05:02, Tomas Vondra > wrote: > > 0) I know the patch does not tweak costing - any plans in this > > direction? Would it be possible to simply use the costing used by > semijoin? > > > Many thanks for looking at this. > > The patch does tweak the costings so that unique joins are costed in the > same way as semi joins. It's a very small change, so easy to miss. Thanks. I missed that bit somehow. > > For example, see the change in initial_cost_nestloop() > > - if (jointype == JOIN_SEMI || jointype == JOIN_ANTI) > + if (jointype == JOIN_SEMI || > + jointype == JOIN_ANTI || > + unique_inner) > { > > Also see the changes in final_cost_nestloop() and final_cost_hashjoin() > > > 1) nodeHashjoin.c (and other join nodes) > > I've noticed we have this in the ExecHashJoin() method: > > /* > * When the inner side is unique or we're performing a > * semijoin, we'll consider returning the first match, but > * after that we're done with this outer tuple. > */ > if (node->js.unique_inner) > node->hj_JoinState = HJ_NEED_NEW_OUTER; > > That seems a bit awkward because the comment speaks about unique > joins *OR* semijoins, but the check only references unique_inner. > That of course works because we do this in ExecInitHashJoin(): > > switch (node->join.jointype) > { > case JOIN_SEMI: > hjstate->js.unique_inner = true; > /* fall through */ > > Either some semijoins may not be unique - in that case the naming is > misleading at best, and we either need to find a better name or > simply check two conditions like this: > > if (node->js.unique_inner || node->join.type == JOIN_SEMI) > node->hj_JoinState = HJ_NEED_NEW_OUTER; > > Or all semijoins are unique joins, and in that case the comment may > need rephrasing. But more importantly, it begs the question why > we're detecting this in the executor and not in the planner? Because > if we detect it in executor, we can't use this bit in costing, for > example. > > > The reason not to detect that in the planner is simply that unique_join > is meant to mean that the relation on the inner side of the join will at > most contain a single Tuple which matches each outer relation tuple, > based on the join condition. I've added no detection for this in semi > joins, and I'd rather not go blindly setting the flag to true in the > planner as it simply may not be true for the semi join. At the moment > that might not matter as we're only using the unique_join flag as an > optimization in the join nodes, but I'd prefer not to do this as its > likely we'll want to do more with this flag later, and I'd rather we > keep the meaning well defined. You might argue that this is also true > for semi joins, but if down the road somewhere we want to perform some > checks on the inner relation before the join takes place, and in that > case the Tuples of the relation might not have the same properties we > claim they do. > > But you're right that reusing the flag in the join nodes is not ideal, > and the comment is not that great either. I'd really rather go down the > path of either renaming the variable, or explaining this better in the > comment. It seems unnecessary to check both for each tuple being joined. > I'd imagine that might add a little overhead to joins which are not unique. I'd be very surprised it that had any measurable impact. > How about changing the comment to: > > /* > * In the event that the inner side has been detected to be > * unique, as an optimization we can skip searching for any > * subsequent matching inner tuples, as none will exist. > * For semijoins unique_inner will always be true, although > * in this case we don't match another inner tuple as this > * is the required semi join behavior. > */ > > Alternatively or additionally we can rename the variable in the executor > state, although I've not thought of a name yet that I don't find overly > verbose: unique_inner_or_semi_join, match_first_tuple_only. I'd go with match_first_tuple_only. > > > 2) analyzejoins.c > > I see that this code in join_is_removable() > > innerrel = find_base_rel(root, innerrelid); > > if (innerrel->reloptkind != RELOPT_BASEREL) > return false; > > was rewritten like this: > > innerrel = find_base_rel(root, innerrelid); > > Assert(innerrel->reloptkind == RELOPT_BASEREL); > > That suggests that previously the function expected cases where > reloptkind may not be RELOPT_BASEREL, but now it'll error out int > such cases. I haven't noticed any changes in the surrounding code > that'd guarantee this won't happen, but I also haven't been able to > come up with an example triggering the assert (haven't been trying > too hard). How do we know the assert() makes sense? > > > I'd have changed this as this should be covered by the if > (!sjinfo->is_unique_join || a few lines up. mark_unique_joins() only > sets is_unique_join to true if specialjoin_is_unique_join() returns true > and that function tests reloptkind to ensure its set to RELOPT_BASEREL > and return false if the relation is not. Perhaps what is missing is a > comment to explain the function should only be used on RELOPT_BASEREL > type relations. Yeah, I think it'd be good to document this contract somewhere. Either in the comment before the function, or perhaps right above the Assert(). regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers