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.94.2) (envelope-from ) id 1s33z3-008wlR-QE for pgsql-hackers@arkaria.postgresql.org; Sat, 04 May 2024 01:14:37 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1s33yz-00DfhU-8e for pgsql-hackers@arkaria.postgresql.org; Sat, 04 May 2024 01:14:34 +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.94.2) (envelope-from ) id 1s33yy-00DfhM-RS for pgsql-hackers@lists.postgresql.org; Sat, 04 May 2024 01:14:33 +0000 Received: from mail-lf1-x135.google.com ([2a00:1450:4864:20::135]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1s33yx-001QaY-40 for pgsql-hackers@postgresql.org; Sat, 04 May 2024 01:14:32 +0000 Received: by mail-lf1-x135.google.com with SMTP id 2adb3069b0e04-51f17ac14daso278601e87.1 for ; Fri, 03 May 2024 18:14:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1714785269; x=1715390069; darn=postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=SyR0ekS9SRIu9YaG1WQhEHBFMVyeLZXumkTj21b5eT0=; b=ZupYU35NaPKG2c5pZnyinYca71dl91imjKx36rgaZEGHaUsD86q7cYgACeF1dCE6sC G9qTk7N5il3Zhz1+4osPp5LYvftzA9KronzI3HJyz7dtHR52Zz4WiFs9YBsJkFVj8Dxx WjRQNc+5qMqDY1ic0MvhXImTJedlqwNjE/jx2Wb+FAY9dcE12Cd8LjPNWFRMHCs5niR9 km72ii0yKgi5TkpvshssgfWN0g2qY/9VuokvqXiQBO1QOjy4Is7SJD6+Rha9BK/zpXmD bKsMIpOYOHxl25iPtvR7oJBV6fteaiwD8oTEtVABpuS8vQ/qqhKqIO4v3/hsP/wzU2et u9bA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1714785269; x=1715390069; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=SyR0ekS9SRIu9YaG1WQhEHBFMVyeLZXumkTj21b5eT0=; b=uZS8UVbtnxw4U0CVphE0gDzWb59hkyBsd3H7Nqvpt2Dxv6JCF4auKUuD9pHa/kySjz qLRJJbLLKbBUJdO3TdI/jEW1VBoFi8/7Q+anQCKCwIbOeqamo2bhg4Scdagenr8km2Yn bKDvao3BAy3ReDbp7ZwRPnCPv7Ejv2962//B0W8lKag1C88la5ZBsI/N2LVWgPcR6QKl qMygWfw2Hrx7ADH+6vgwQbSnlGqVICZq7likcM98liHwITE7/aeo2U6H8+hBLfRKFx3V yucF2GAte68t9/uHHicv+73YnE1vmHqzZDU/xDF/mrrf4rpHtBAwl7LRb6qoXrKKink5 RyhQ== X-Gm-Message-State: AOJu0Yx6c30c4hhlWGQ2NVhsAkXrfaTWbubXrF6pU932MzQSFWMDMuD8 WfIE2Q/Zto7sGtFkax4xTvKWkNO9wN5gGFPfNJXdzp2muEpRg+VOhYmUolCzp3o8vi5jbfJ4cr3 BpICAx+IgwDA9A3DaiHLZX8+dXNDCWvxi X-Google-Smtp-Source: AGHT+IFcGI2hNtKjXVUH2ZHlTKZL4Sr5eUoyAu6S4Ci742nvDn8yLpH16Jm7e8AUQHPPhcgKGENFQKfoR1lsSdyUM9o= X-Received: by 2002:a05:6512:539:b0:51e:24c7:bcd4 with SMTP id o25-20020a056512053900b0051e24c7bcd4mr2657303lfc.41.1714785268553; Fri, 03 May 2024 18:14:28 -0700 (PDT) MIME-Version: 1.0 References: <18f2c002a24.11bc2ab825151706.3749144144619388582@highgo.ca> <18f306a1ffd.c3e46e455499033.265730539542922404@highgo.ca> <18f350adaa1.ba4bf61f5717077.8028524953791970317@highgo.ca> <18f3fcf4f30.12c03d898462092.1918273472989699761@highgo.ca> In-Reply-To: <18f3fcf4f30.12c03d898462092.1918273472989699761@highgo.ca> From: David Rowley Date: Sat, 4 May 2024 13:14:16 +1200 Message-ID: Subject: Re: Support tid range scan in parallel? To: Cary Huang Cc: Pgsql Hackers Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Sat, 4 May 2024 at 06:55, Cary Huang wrote: > With syncscan enabled, the "table_block_parallelscan_nextpage()" would > return the next block since the end of the first tid rangescan instead of the > correct start block that should be scanned. I see that single tid rangescan > does not have SO_ALLOW_SYNC set, so I figure syncscan should also be > disabled in parallel case. With this change, then it would be okay to call > heap_setscanlimits() in parallel case, so I added this call back to > heap_set_tidrange() in both serial and parallel cases. This now calls heap_setscanlimits() for the parallel version, it's just that table_block_parallelscan_nextpage() does nothing to obey those limits. The only reason the code isn't reading the entire table is due to the optimisation in heap_getnextslot_tidrange() which returns false when the ctid goes out of range. i.e, this code: /* * 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; If I comment out that line, I see all pages are accessed: postgres=# explain (analyze, buffers) select count(*) from a where ctid >= '(0,1)' and ctid < '(11,0)'; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------- Finalize Aggregate (cost=18.80..18.81 rows=1 width=8) (actual time=33.530..36.118 rows=1 loops=1) Buffers: shared read=4425 -> Gather (cost=18.78..18.79 rows=2 width=8) (actual time=33.456..36.102 rows=3 loops=1) Workers Planned: 2 Workers Launched: 2 Buffers: shared read=4425 -> Partial Aggregate (cost=18.78..18.79 rows=1 width=8) (actual time=20.389..20.390 rows=1 loops=3) Buffers: shared read=4425 -> Parallel Tid Range Scan on a (cost=0.01..16.19 rows=1035 width=0) (actual time=9.375..20.349 rows=829 loops=3) TID Cond: ((ctid >= '(0,1)'::tid) AND (ctid < '(11,0)'::tid)) Buffers: shared read=4425 <---- this is all pages in the table instead of 11 pages. With that code still commented out, the non-parallel version still won't read all pages due to the setscanlimits being obeyed. postgres=# set max_parallel_workers_per_gather=0; SET postgres=# explain (analyze, buffers) select count(*) from a where ctid >= '(0,1)' and ctid < '(11,0)'; QUERY PLAN -------------------------------------------------------------------------------------------------------------- Aggregate (cost=45.07..45.08 rows=1 width=8) (actual time=0.302..0.302 rows=1 loops=1) Buffers: shared hit=11 -> Tid Range Scan on a (cost=0.01..38.86 rows=2485 width=0) (actual time=0.019..0.188 rows=2486 loops=1) TID Cond: ((ctid >= '(0,1)'::tid) AND (ctid < '(11,0)'::tid)) Buffers: shared hit=11 If I put that code back in, how many pages are read depends on the number of parallel workers as workers will keep running with higher page numbers and heap_getnextslot_tidrange() will just (inefficiently) filter those out. max_parallel_workers_per_gather=2; -> Parallel Tid Range Scan on a (cost=0.01..16.19 rows=1035 width=0) (actual time=0.191..0.310 rows=829 loops=3) TID Cond: ((ctid >= '(0,1)'::tid) AND (ctid < '(11,0)'::tid)) Buffers: shared read=17 max_parallel_workers_per_gather=3; -> Parallel Tid Range Scan on a (cost=0.01..12.54 rows=802 width=0) (actual time=0.012..0.114 rows=622 loops=4) TID Cond: ((ctid >= '(0,1)'::tid) AND (ctid < '(11,0)'::tid)) Buffers: shared hit=19 max_parallel_workers_per_gather=4; -> Parallel Tid Range Scan on a (cost=0.01..9.72 rows=621 width=0) (actual time=0.014..0.135 rows=497 loops=5) TID Cond: ((ctid >= '(0,1)'::tid) AND (ctid < '(11,0)'::tid)) Buffers: shared hit=21 To fix this you need to make table_block_parallelscan_nextpage obey the limits imposed by heap_setscanlimits(). The equivalent code in the non-parallel version is in heapgettup_advance_block(). /* check if the limit imposed by heap_setscanlimits() is met */ if (scan->rs_numblocks != InvalidBlockNumber) { if (--scan->rs_numblocks == 0) return InvalidBlockNumber; } I've not studied exactly how you'd get the rs_numblock information down to the parallel scan descriptor. But when you figure that out, just remember that you can't do the --scan->rs_numblocks from table_block_parallelscan_nextpage() as that's not parallel safe. You might be able to add an or condition to: "if (nallocated >= pbscan->phs_nblocks)" to make it "if (nallocated >= pbscan->phs_nblocks || nallocated >= pbscan->phs_numblocks)", although the field names don't seem very intuitive there. It would be nicer if the HeapScanDesc field was called rs_blocklimit rather than rs_numblocks. It's not for this patch to go messing with that, however. David