Hi,

We have observed that running the same self JOIN query on postgres FDW
setup is returning different results with set enable_nestloop off & on. I
am at today's latest commit:- 928e05ddfd4031c67e101c5e74dbb5c8ec4f9e23

I created a local FDW setup. And ran this experiment on the same. Kindly
refer to the P.S section for details.

|********************************************************************|
*Below is the output difference along with query plan:-*
postgres@71609=#set enable_nestloop=off;
SET
postgres@71609=#select * from pg_tbl_foreign tbl1 join pg_tbl_foreign tbl2
on tbl1.id1 < 5 and now() < '23-Feb-2020'::timestamp;
 id1 | id2 | id1 | id2
-----+-----+-----+-----
   1 |  10 |   1 |  10
   2 |  20 |   1 |  10
   3 |  30 |   1 |  10
   1 |  10 |   2 |  20
   2 |  20 |   2 |  20
   3 |  30 |   2 |  20
   1 |  10 |   3 |  30
   2 |  20 |   3 |  30
   3 |  30 |   3 |  30
(9 rows)

postgres@71609=#explain (analyze, verbose) select * from pg_tbl_foreign
tbl1 join pg_tbl_foreign tbl2 on tbl1.id1 < 5 and now() <
'23-Feb-2020'::timestamp;
                                                         QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=100.00..49310.40 rows=2183680 width=16) (actual
time=0.514..0.515 rows=9 loops=1)
   Output: tbl1.id1, tbl1.id2, tbl2.id1, tbl2.id2
   Relations: (public.pg_tbl_foreign tbl1) INNER JOIN
(public.pg_tbl_foreign tbl2)
   Remote SQL: SELECT r1.id1, r1.id2, r2.id1, r2.id2 FROM (public.pg_tbl r1
INNER JOIN public.pg_tbl r2 ON (((r1.id1 < 5))))
 Planning Time: 0.139 ms
 Execution Time: 0.984 ms
(6 rows)

postgres@71609=#set enable_nestloop=on;
SET
postgres@71609=#select * from pg_tbl_foreign tbl1 join pg_tbl_foreign tbl2
on tbl1.id1 < 5 and now() < '23-Feb-2020'::timestamp;
 id1 | id2 | id1 | id2
-----+-----+-----+-----
(0 rows)

postgres@71609=#explain (analyze, verbose) select * from pg_tbl_foreign
tbl1 join pg_tbl_foreign tbl2 on tbl1.id1 < 5 and now() <
'23-Feb-2020'::timestamp;
                                                      QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------
 Result  (cost=200.00..27644.00 rows=2183680 width=16) (actual
time=0.003..0.004 rows=0 loops=1)
   Output: tbl1.id1, tbl1.id2, tbl2.id1, tbl2.id2
   One-Time Filter: (now() < '2020-02-23 00:00:00'::timestamp without time
zone)
   ->  Nested Loop  (cost=200.00..27644.00 rows=2183680 width=16) (never
executed)
         Output: tbl1.id1, tbl1.id2, tbl2.id1, tbl2.id2
         ->  Foreign Scan on public.pg_tbl_foreign tbl2
 (cost=100.00..186.80 rows=2560 width=8) (never executed)
               Output: tbl2.id1, tbl2.id2
               Remote SQL: SELECT id1, id2 FROM public.pg_tbl
         ->  Materialize  (cost=100.00..163.32 rows=853 width=8) (never
executed)
               Output: tbl1.id1, tbl1.id2
               ->  Foreign Scan on public.pg_tbl_foreign tbl1
 (cost=100.00..159.06 rows=853 width=8) (never executed)
                     Output: tbl1.id1, tbl1.id2
                     Remote SQL: SELECT id1, id2 FROM public.pg_tbl WHERE
((id1 < 5))
 Planning Time: 0.178 ms
 Execution Time: 0.292 ms
(15 rows)

|********************************************************************|

I debugged this issue and was able to find a fix for the same. Kindly
please refer to the attached fix. With the fix I am able to resolve the
issue. But I am not that confident whether this change would affect some
other existing functionally but it has helped me resolve this result
difference in output.

*What is the technical issue?*
The problem here is the use of extract_actual_clauses. Because of which the
plan creation misses adding the second condition of AND i.e "now() <
'23-Feb-2020'::timestamp" in the plan. Because it is not considered a
pseudo constant and extract_actual_clause is passed with false as the
second parameter and it gets skipped from the list. As a result that
condition is never taken into consideration as either one-time filter
(before or after) or part of SQL remote execution

*Why do I think the fix is correct?*
The fix is simple, where we have created a new function similar to
extract_actual_clause which just extracts all the conditions from the list
with no checks and returns the list to the caller. As a result all
conditions would be taken into consideration in the query plan.

*After my fix patch:-*
postgres@78754=#set enable_nestloop=off;
SET
postgres@78754=#select * from pg_tbl_foreign tbl1 join pg_tbl_foreign tbl2
on tbl1.id1 < 5 and now() < '23-Feb-2020'::timestamp;
 id1 | id2 | id1 | id2
-----+-----+-----+-----
(0 rows)
                                                 ^
postgres@78754=#explain (analyze, verbose) select * from pg_tbl_foreign
tbl1 join pg_tbl_foreign tbl2 on tbl1.id1 < 5 and now() <
'23-Feb-2020'::timestamp;
                                                         QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=100.00..49310.40 rows=2183680 width=16) (actual
time=0.652..0.652 rows=0 loops=1)
   Output: tbl1.id1, tbl1.id2, tbl2.id1, tbl2.id2
   Filter: (now() < '2020-02-23 00:00:00'::timestamp without time zone)
   Rows Removed by Filter: 9
   Relations: (public.pg_tbl_foreign tbl1) INNER JOIN
(public.pg_tbl_foreign tbl2)
   Remote SQL: SELECT r1.id1, r1.id2, r2.id1, r2.id2 FROM (public.pg_tbl r1
INNER JOIN public.pg_tbl r2 ON (((r1.id1 < 5))))
 Planning Time: 0.133 ms
 Execution Time: 1.127 ms
(8 rows)

postgres@78754=#set enable_nestloop=on;
SET
postgres@78754=#select * from pg_tbl_foreign tbl1 join pg_tbl_foreign tbl2
on tbl1.id1 < 5 and now() < '23-Feb-2020'::timestamp;
 id1 | id2 | id1 | id2
-----+-----+-----+-----
(0 rows)

postgres@78754=#explain (analyze, verbose) select * from pg_tbl_foreign
tbl1 join pg_tbl_foreign tbl2 on tbl1.id1 < 5 and now() <
'23-Feb-2020'::timestamp;
                                                      QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------
 Result  (cost=200.00..27644.00 rows=2183680 width=16) (actual
time=0.004..0.005 rows=0 loops=1)
   Output: tbl1.id1, tbl1.id2, tbl2.id1, tbl2.id2
   One-Time Filter: (now() < '2020-02-23 00:00:00'::timestamp without time
zone)
   ->  Nested Loop  (cost=200.00..27644.00 rows=2183680 width=16) (never
executed)
         Output: tbl1.id1, tbl1.id2, tbl2.id1, tbl2.id2
         ->  Foreign Scan on public.pg_tbl_foreign tbl2
 (cost=100.00..186.80 rows=2560 width=8) (never executed)
               Output: tbl2.id1, tbl2.id2
               Remote SQL: SELECT id1, id2 FROM public.pg_tbl
         ->  Materialize  (cost=100.00..163.32 rows=853 width=8) (never
executed)
               Output: tbl1.id1, tbl1.id2
               ->  Foreign Scan on public.pg_tbl_foreign tbl1
 (cost=100.00..159.06 rows=853 width=8) (never executed)
                     Output: tbl1.id1, tbl1.id2
                     Remote SQL: SELECT id1, id2 FROM public.pg_tbl WHERE
((id1 < 5))
 Planning Time: 0.134 ms
 Execution Time: 0.347 ms
(15 rows)
|********************************************************************|

Kindly please comment if I am in the correct direction or not?


Regards,
Nishant Sharma.
Developer at EnterpriseDB, Pune, India.



P.S
Steps that I used to create local postgres FDW setup ( followed link -
https://www.postgresql.org/docs/current/postgres-fdw.html
<https://www.postgresql.org/docs/current/postgres-fdw.html):-> )

1) ./configure --prefix=/home/edb/POSTGRES_INSTALL/MASTER
--with-pgport=9996 --with-openssl --with-libxml --with-zlib --with-tcl
--with-perl --with-libxslt --with-ossp-uuid --with-ldap --with-pam
--enable-nls --enable-debug --enable-depend --enable-dtrace --with-selinux
--with-icu --enable-tap-tests --enable-cassert  CFLAGS="-g -O0"

2) make

3) make install

4) cd contrib/postgres_fdw/

5) make install

6) Start the server

7)
[edb@localhost MASTER]$ bin/psql postgres edb;
psql (16devel)
Type "help" for help.

postgres@70613=#create database remote_db;
CREATE DATABASE
postgres@70613=#quit

[edb@localhost MASTER]$ bin/psql remote_db edb;
psql (16devel)
Type "help" for help.

remote_db@70613=#CREATE USER fdw_user;
CREATE ROLE

remote_db@70613=#GRANT ALL ON SCHEMA public TO fdw_user;
GRANT
remote_db@70613=#quit

[edb@localhost MASTER]$ bin/psql remote_db fdw_user;
psql (16devel)
Type "help" for help.

remote_db@70613=#create table pg_tbl(id1 int, id2 int);
CREATE TABLE
remote_db@70613=#insert into pg_tbl values(1, 10);
INSERT 0 1
remote_db@70613=#insert into pg_tbl values(2, 20);
INSERT 0 1
remote_db@70613=#insert into pg_tbl values(3, 30);
INSERT 0 1

8)
New terminal/Tab:-
[edb@localhost MASTER]$ bin/psql postgres edb;
postgres@71609=#create extension postgres_fdw;
CREATE EXTENSION
postgres@71609=#CREATE SERVER localhost_fdw FOREIGN DATA WRAPPER
postgres_fdw  OPTIONS (dbname 'remote_db', host 'localhost', port '9996');
CREATE SERVER
postgres@71609=#CREATE USER MAPPING for edb SERVER localhost_fdw OPTIONS
(user 'fdw_user', password '');
CREATE USER MAPPING
postgres@71609=#GRANT ALL ON FOREIGN SERVER localhost_fdw TO edb;
GRANT
postgres@71609=#CREATE FOREIGN TABLE pg_tbl_foreign(id1 int, id2 int)
SERVER localhost_fdw OPTIONS (schema_name 'public', table_name 'pg_tbl');
CREATE FOREIGN TABLE
postgres@71609=#select * from pg_tbl_foreign;
 id1 | id2
-----+-----
   1 |  10
   2 |  20
   3 |  30
(3 rows)

Attachment: PG_PATCH_set_nestloop_off_issue_fix.patch
Description: Binary data

Reply via email to