Re: [(known) BUG] DELETE/UPDATE more than one row in partitioned foreign table

From: Manu <manuelreyesbravo(at)gmail(dot)com>
To: Ajit Awekar <ajitpostgres(at)gmail(dot)com>
Cc: Jakub Wartak <jakub(dot)wartak(at)enterprisedb(dot)com>, Etsuro Fujita <etsuro(dot)fujita(at)gmail(dot)com>, Michael Paquier <michael(at)paquier(dot)xyz>, shihao zhong <zhong950419(at)gmail(dot)com>, Nikita Malakhov <hukutoc(at)gmail(dot)com>, Jehan-Guillaume de Rorthais <jgdr(at)dalibo(dot)com>, Robert Haas <rhaas(at)postgresql(dot)org>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: [(known) BUG] DELETE/UPDATE more than one row in partitioned foreign table
Date: 2026-09-22 14:45:48
Message-ID: 179008834802.2666470.8606898268993687598@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Ajit,

Ajit Awekar <ajitpostgres(at)gmail(dot)com> wrote:
> @Manu: went with your option (1) rather than (2) for case_f
>
> Please find attached V3. Request a review.

Thanks, the new paragraph describes case_f as I saw it, including that
the remote tableoid is the middle foreign table's own OID.

I ran the same eight cases on V3, applied on master e8a3ee5b197, against
that master, both with --enable-cassert. V3 builds without warnings,
and make check (239) and postgres_fdw's check pass.

- Jakub's zero-column case with a BEFORE DELETE trigger no longer fails
the assertion; the output is the same as master's.
- Etsuro's DELETE ... USING case, the dropped and renamed columns with
triggers, the mixed local and foreign partitions and the self-join
give the right rows, as with v2.
- The zero-column RETURNING and the cross-partition cases are the same
as master.
- The chained case still updates the wrong rows, as documented.

Since the commit message mentions the extra column and WHERE clause for
every non-direct UPDATE/DELETE, I measured it, on a loopback server
with 100000 rows, UPDATE ... WHERE id <= N AND random() <= 1, median of 5
runs:

master V3
plain table, 1000 rows 26 ms 29 ms
plain table, 10000 rows 234 ms 224 ms
100 partitions, 1000 rows 42 ms 309 ms
100 partitions, 10000 rows 102 ms 3606 ms

On a plain table I can't see a difference. With partitions V3 looks
much slower, but it is not what it adds. With log_min_duration_statement
= 0, for 1000 rows master sends 100 per-row UPDATEs and V3 sends 1000,
at the same cost each (0.20 ms in one run, 0.25 ms in another, the same
for both). Master sends fewer because each
"WHERE ctid = $1" hits that ctid in every partition, so the rows already
changed are not fetched again. Master is faster only because it is
doing the wrong thing.

What remains is the cost your paragraph describes: each per-row UPDATE
goes through the root and scans every partition, so with 100 partitions
a row costs about ten times what it costs on a plain table (309 ms vs
29 ms for 1000 rows). Prepared on the remote side the way postgres_fdw
does it, "WHERE ctid = $1 AND tableoid = $2" costs about 10% more than
"WHERE ctid = $1" (190 vs 172 ms for 1000 executions), so the tableoid
condition itself is cheap.

The attached fdw_update_cost.sh does both measurements: the medians, and
the statements the remote side receives with their count and time. It
takes an install prefix and a port, like run_cases.sh. The numbers move
a bit between runs, but the counts don't.

I have nothing else to raise on V3.

Regards,
Manu

Attachment Content-Type Size
fdw_update_cost.sh.txt text/plain 2.8 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Greg Burd 2026-09-22 14:54:33 Double content-lock acquisition silently leaks a lock
Previous Message Dmitry Dolgov 2026-09-22 14:28:14 Re: System views for versions reporting