Re: speedup COPY TO for partitioned table. - Mailing list pgsql-hackers

From Masahiko Sawada
Subject Re: speedup COPY TO for partitioned table.
Date
Msg-id CAD21AoA7EwQYG+fy01ye9WOZsxOAn=PQXsnibqv-j_5bFfN68g@mail.gmail.com
Whole thread Raw
In response to Re: speedup COPY TO for partitioned table.  (jian he <jian.universality@gmail.com>)
Responses Re: speedup COPY TO for partitioned table.
Re: speedup COPY TO for partitioned table.
List pgsql-hackers
Hi,

On Mon, Oct 6, 2025 at 2:49 AM jian he <jian.universality@gmail.com> wrote:
>
> On Fri, Oct 3, 2025 at 8:31 AM torikoshia <torikoshia@oss.nttdata.com> wrote:
> >
> > It’s been about two months since this discussion, and there don’t seem
> > to be any further comments.
> > How about updating the patch accordingly?
> > If there are no new remarks, I’d like to mark the patch as Ready for
> > Committer.
> >
> > >>   +   List       *children = NIL;
> > >>   ...
> > >>   +       {
> > >>   +           children = find_all_inheritors(RelationGetRelid(rel),
> > >>
> > >> Since 'children' is only used inside the else if block, I think we
> > >> don't
> > >> need the separate "List *children = NIL;" declaration.
> > >> Instead, it could just be  "List *children =
> > >> find_all_inheritors(...)".
> > >>
> > > you are right.
> > > ""List *children = find_all_inheritors(...)"."  should be ok.
> >
>
> hi.
>
> please check the attached v15.
>

Thank you for working on this!  I've reviewed the v15 patch, and here
are review comments:

---
+           children = find_all_inheritors(RelationGetRelid(rel),
+                                          AccessShareLock,
+                                          NULL);
+
+           foreach_oid(childreloid, children)
+           {
+               char         relkind = get_rel_relkind(childreloid);

I think it's better to write some comments summarizing what we're
doing in the loop.

---
+                   relation_name = get_rel_name(childreloid);
+                   ereport(ERROR,
+                           errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                           errmsg("cannot copy from foreign table
\"%s\"", relation_name),
+                           errdetail("Partition \"%s\" is a foreign
table in the partitioned table \"%s\"",
+                                     relation_name,
RelationGetRelationName(rel)),
+                           errhint("Try the COPY (SELECT ...) TO variant."));

I think we don't need "the" in the error message.

It's conventional to put all err*() macros in parentheses (i.e.,
"(errcode(), ...)", it's technically omittable though.

---
+               if (RELKIND_HAS_PARTITIONS(relkind))
+                   continue;
+
+               scan_oids = lappend_oid(scan_oids, childreloid);

find_all_inheritors() returns a list of OIDs of child relations. I
think we can delete relations whose kind is RELKIND_HAS_PARTITIONS()
from the list instead of creating a new list scan_oids. Then, we can
set cstate->partition to the list.

---
        tupDesc = RelationGetDescr(cstate->rel);
+       cstate->partitions = list_copy(scan_oids);
    }

Why do we need to copy the list here?

---
With the patch we have:

   /*
    * If COPY TO source table is a partitioned table, then open each
    * partition and process each individual partition.
    */
   if (cstate->rel && cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
   {
       foreach_oid(scan_oid, cstate->partitions)
       {
           Relation        scan_rel;

           /* We already got the needed lock in BeginCopyTo */
           scan_rel = table_open(scan_oid, NoLock);
           CopyRelTo(cstate, scan_rel, cstate->rel, &processed);
           table_close(scan_rel, NoLock);
       }
   }
   else if (cstate->rel)
       CopyRelTo(cstate, cstate->rel, NULL, &processed);
   else
   {
       /* run the plan --- the dest receiver will send tuples */

I think we can refactor the code structure as follow:

if (cstate->rel)
{
    if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    {
        do CopyRelTo() for each OIDs in cstate->partition here.
    }
    else
        CopyRelTo(cstate, cstate->rel, NULL, &processed);
}
else
{
 ...

---
+   if (root_rel != NULL)
+   {
+       rootdesc = RelationGetDescr(root_rel);
+       root_slot = table_slot_create(root_rel, NULL);
+       map = build_attrmap_by_name_if_req(rootdesc, tupdesc, false);
+   }

rootdesc can be declared inside this if statement or we can directly
pass 'RelationGetDescr(root_rel)' to build_attrmap_by_name_if_req().

---
+       /* Deconstruct the tuple ... */
+       if (map != NULL)
+           copyslot = execute_attr_map_slot(map, slot, root_slot);
+       else
+       {
+           slot_getallattrs(slot);
+           copyslot = slot;
+       }

ISTM that the comment "Deconstruct the tuple" needs to move to before
slot_getallattrs(slot).

How about doing "slot = execute_attr_map_slot(map, slot, root_slot);"
instead? (i.e., no need to have 'copyslot')

---
+   if (root_slot != NULL)
+       ExecDropSingleTupleTableSlot(root_slot);
+   table_endscan(scandesc);

We might want to pfree 'map' if we create it.

Regards,

--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com



pgsql-hackers by date:

Previous
From: Jeremy Schneider
Date:
Subject: Re: sync_standbys_defined and pg_stat_replication
Next
From: Jeremy Schneider
Date:
Subject: Re: another autovacuum scheduling thread