Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lC8Td-0001od-43 for pgsql-hackers@arkaria.postgresql.org; Tue, 16 Feb 2021 22:05:49 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1lC8Ta-0007aW-8e for pgsql-hackers@arkaria.postgresql.org; Tue, 16 Feb 2021 22:05:46 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lC8TY-0007aP-Sq for pgsql-hackers@lists.postgresql.org; Tue, 16 Feb 2021 22:05:46 +0000 Received: from [66.111.4.28] (helo=out4-smtp.messagingengine.com) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lC8TQ-0005M4-RN for pgsql-hackers@lists.postgresql.org; Tue, 16 Feb 2021 22:05:44 +0000 Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailout.nyi.internal (Postfix) with ESMTP id ECE2E5C00F7; Tue, 16 Feb 2021 17:05:24 -0500 (EST) Received: from mailfrontend2 ([10.202.2.163]) by compute4.internal (MEProxy); Tue, 16 Feb 2021 17:05:24 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=anarazel.de; h= date:from:to:cc:subject:message-id:references:mime-version :content-type:in-reply-to; s=fm3; bh=ie4a9xPX+fhNxRPPVbQxAa+ELUC PokBxk8M4TWaa9nQ=; b=gYLV35kExE6WJ0taCUSnaolLPuq2ux0TqKOFn25Jdiu +u4SDM3dhTC3FQkR3HZqqUcT2d7dkscHAbVbeHvh8xFdTc5jVHecm6dX5qHlzSzE lZlQLZG3DoCEkqYnY0lSjw4qbfQvNJqNy0BPfiJxwngKZVJ5NZP/DN+l31XyHN18 kwEEmLvE9NjUCY35b/DCKT7mNX8SvwHXc26bea0wm3KiWg0Cb18SpN7RnZjWQKwW hVSnlgL2HsuxqDyKRTjEUV1752xhutb0GTU3QI1y/5WawQqPeQom4eby3Ebrdg1m IK7F3+/gxvp+CQ1JqdXm49kdAxYznyOzTlDsUc3S5Ag== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; bh=ie4a9x PX+fhNxRPPVbQxAa+ELUCPokBxk8M4TWaa9nQ=; b=kwSh/ra9fn45O05OepHLcG yNJYnAAm3xn7XS/QidG14QZU31RbH3jrqrd91pDhrFMHBcMumjpXyPttCM70l166 pXVBJBcxB0MRKfhqhl10xoWuCH6PXYXoAkCGkjAJFdYnMzIcNqRZau6xX1wuigFH aDzCxqymPc8aLEaizm9rnvFDTloIXL1dDlZLXKX2e0JhYz5OWBt9TJZsuo8kCwdD GR7BU3xi7wbGvYOOa8utufWzwZv8Ik7BhS7VSFMsLateWY8QoBMCd1P0JR6u774r CU6XYBq/NCEdlIdTJxAEO2Q5S7hKZXjC+5TsJB4rLv0uX2V/7OITsntgYmRZT6vg == X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeduledrjedtgdduheeiucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhepfffhvffukfhfgggtuggjsehttdertddttddvnecuhfhrohhmpeetnhgurhgv shcuhfhrvghunhguuceorghnughrvghssegrnhgrrhgriigvlhdruggvqeenucggtffrrg htthgvrhhnpedukefhkeelueegveetheelffffjeegleeuudelfeefuedtleffueejfffh ueffudenucfkphepieejrdduiedtrddvudejrddvhedtnecuvehluhhsthgvrhfuihiivg eptdenucfrrghrrghmpehmrghilhhfrhhomheprghnughrvghssegrnhgrrhgriigvlhdr uggv X-ME-Proxy: Received: from intern.anarazel.de (c-67-160-217-250.hsd1.ca.comcast.net [67.160.217.250]) by mail.messagingengine.com (Postfix) with ESMTPA id 70CAD108005C; Tue, 16 Feb 2021 17:05:23 -0500 (EST) Date: Tue, 16 Feb 2021 14:05:22 -0800 From: Andres Freund To: David Rowley Cc: Edmund Horner , David Fetter , Michael Paquier , Alvaro Herrera , Thomas Munro , David Steele , Tom Lane , Tomas Vondra , PostgreSQL Developers Subject: Re: Tid scan improvements Message-ID: <20210216220522.3sn64ui3prwm43zh@alap3.anarazel.de> References: <20191201023416.GV2355@paquier.xyz> <20210101013019.GS13234@fetter.org> <20210203211919.u7q3vq24g4pd63gv@alap3.anarazel.de> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: X-Host-Lookup-Failed: Reverse DNS lookup failed for 66.111.4.28 (deferred) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk Hi, On 2021-02-04 23:51:39 +1300, David Rowley wrote: > I ended up adding just two new API functions to table AM. > > void (*scan_set_tid_range) (TableScanDesc sscan, > ItemPointer mintid, > ItemPointer maxtid); > > and > bool (*scan_tid_range_nextslot) (TableScanDesc sscan, > ScanDirection direction, > TupleTableSlot *slot); > > I added an additional function in tableam.h that does not have a > corresponding API function: > > static inline TableScanDesc > table_tid_range_start(Relation rel, Snapshot snapshot, > ItemPointer mintid, > ItemPointer maxtid) > > This just calls the standard scan_begin then calls scan_set_tid_range > setting the specified mintid and maxtid. Hm. But that means we can't rescan? > I also added 2 new fields to TableScanDesc: > > ItemPointerData rs_mintid; > ItemPointerData rs_maxtid; > > I didn't quite see a need to have a new start and end scan API function. Yea. I guess they're not that large. Avoiding that was one of the two reasons to have a separate scan state somewhere. The other that it seemed like it'd possibly a bit cleaner API wise to deal with rescan. > +bool > +heap_getnextslot_tidrange(TableScanDesc sscan, ScanDirection direction, > + TupleTableSlot *slot) > +{ > + HeapScanDesc scan = (HeapScanDesc) sscan; > + ItemPointer mintid = &sscan->rs_mintid; > + ItemPointer maxtid = &sscan->rs_maxtid; > + > + /* Note: no locking manipulations needed */ > + for (;;) > + { > + if (sscan->rs_flags & SO_ALLOW_PAGEMODE) > + heapgettup_pagemode(scan, direction, sscan->rs_nkeys, sscan->rs_key); > + else > + heapgettup(scan, direction, sscan->rs_nkeys, sscan->rs_key); > + > + if (scan->rs_ctup.t_data == NULL) > + { > + ExecClearTuple(slot); > + return false; > + } > + > + /* > + * heap_set_tidrange will have used heap_setscanlimits to limit the > + * range of pages we scan to only ones that can contain the TID range > + * we're scanning for. Here we must filter out any tuples from these > + * pages that are outwith that range. > + */ > + if (ItemPointerCompare(&scan->rs_ctup.t_self, mintid) < 0) > + { > + ExecClearTuple(slot); > + > + /* > + * When scanning backwards, the TIDs will be in descending order. > + * Future tuples in this direction will be lower still, so we can > + * just return false to indicate there will be no more tuples. > + */ > + if (ScanDirectionIsBackward(direction)) > + return false; > + > + continue; > + } > + > + /* > + * Likewise for the final page, we must filter out TIDs greater than > + * maxtid. > + */ > + if (ItemPointerCompare(&scan->rs_ctup.t_self, maxtid) > 0) > + { > + ExecClearTuple(slot); > + > + /* > + * When scanning forward, the TIDs will be in ascending order. > + * Future tuples in this direction will be higher still, so we can > + * just return false to indicate there will be no more tuples. > + */ > + if (ScanDirectionIsForward(direction)) > + return false; > + continue; > + } > + > + break; > + } > + > + /* > + * if we get here it means we have a new current scan tuple, so point to > + * the proper return buffer and return the tuple. > + */ > + pgstat_count_heap_getnext(scan->rs_base.rs_rd); I wonder if there's an argument for counting the misses above via pgstat_count_heap_fetch()? Probably not, right? > +#define IsCTIDVar(node) \ > + ((node) != NULL && \ > + IsA((node), Var) && \ > + ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \ > + ((Var *) (node))->varlevelsup == 0) > + > +typedef enum > +{ > + TIDEXPR_UPPER_BOUND, > + TIDEXPR_LOWER_BOUND > +} TidExprType; > + > +/* Upper or lower range bound for scan */ > +typedef struct TidOpExpr > +{ > + TidExprType exprtype; /* type of op; lower or upper */ > + ExprState *exprstate; /* ExprState for a TID-yielding subexpr */ > + bool inclusive; /* whether op is inclusive */ > +} TidOpExpr; > + > +/* > + * For the given 'expr', build and return an appropriate TidOpExpr taking into > + * account the expr's operator and operand order. > + */ > +static TidOpExpr * > +MakeTidOpExpr(OpExpr *expr, TidRangeScanState *tidstate) > +{ > + Node *arg1 = get_leftop((Expr *) expr); > + Node *arg2 = get_rightop((Expr *) expr); > + ExprState *exprstate = NULL; > + bool invert = false; > + TidOpExpr *tidopexpr; > + > + if (IsCTIDVar(arg1)) > + exprstate = ExecInitExpr((Expr *) arg2, &tidstate->ss.ps); > + else if (IsCTIDVar(arg2)) > + { > + exprstate = ExecInitExpr((Expr *) arg1, &tidstate->ss.ps); > + invert = true; > + } > + else > + elog(ERROR, "could not identify CTID variable"); > + > + tidopexpr = (TidOpExpr *) palloc(sizeof(TidOpExpr)); > + tidopexpr->inclusive = false; /* for now */ > + > + switch (expr->opno) > + { > + case TIDLessEqOperator: > + tidopexpr->inclusive = true; > + /* fall through */ > + case TIDLessOperator: > + tidopexpr->exprtype = invert ? TIDEXPR_LOWER_BOUND : TIDEXPR_UPPER_BOUND; > + break; > + case TIDGreaterEqOperator: > + tidopexpr->inclusive = true; > + /* fall through */ > + case TIDGreaterOperator: > + tidopexpr->exprtype = invert ? TIDEXPR_UPPER_BOUND : TIDEXPR_LOWER_BOUND; > + break; > + default: > + elog(ERROR, "could not identify CTID operator"); > + } > + > + tidopexpr->exprstate = exprstate; > + > + return tidopexpr; > +} > + > +/* > + * Extract the qual subexpressions that yield TIDs to search for, > + * and compile them into ExprStates if they're ordinary expressions. > + */ > +static void > +TidExprListCreate(TidRangeScanState *tidrangestate) > +{ > + TidRangeScan *node = (TidRangeScan *) tidrangestate->ss.ps.plan; > + List *tidexprs = NIL; > + ListCell *l; > + > + foreach(l, node->tidrangequals) > + { > + OpExpr *opexpr = lfirst(l); > + TidOpExpr *tidopexpr; > + > + if (!IsA(opexpr, OpExpr)) > + elog(ERROR, "could not identify CTID expression"); > + > + tidopexpr = MakeTidOpExpr(opexpr, tidrangestate); > + tidexprs = lappend(tidexprs, tidopexpr); > + } > + > + tidrangestate->trss_tidexprs = tidexprs; > +} Architecturally it feels like this is something that really belongs more into plan time? > +/* > + * table_set_tidrange resets the minimum and maximum TID range to scan for a > + * TableScanDesc created by table_beginscan_tidrange. > + */ > +static inline void > +table_set_tidrange(TableScanDesc sscan, ItemPointer mintid, > + ItemPointer maxtid) > +{ > + /* Ensure table_beginscan_tidrange() was used. */ > + Assert((sscan->rs_flags & SO_TYPE_TIDRANGESCAN) != 0); > + > + sscan->rs_rd->rd_tableam->scan_set_tidrange(sscan, mintid, maxtid); > +} How does this interact with rescans? Greetings, Andres Freund