Hi,

On 2019-04-02 04:23:20 +1300, David Rowley wrote:
> On Mon, 1 Apr 2019 at 08:05, Andres Freund <and...@anarazel.de> wrote:
> > I'll work on pushing all the other pending tableam patches today -
> > leaving COPY the last non pluggable part. You'd written in a private
> > email that you might try to work on this on Monday, so I think I'll give
> > this a shot on Tuesday if you've not gotten around till then? I'd like
> > to push this sooner than the exact end of the freeze...
> 
> I worked on this, but I've not got the patch finished yet.

Thanks! I'm not quite clear whether you planning to continue working on
this, or whether this is a handoff? Either is fine with me, just trying
to avoid unnecessary work / delay.


> Of course, it is possible that some of the bugs account for some of
> the improved time...  but the rows did seem to be in the table
> afterwards.

The speedup likely seems to be from having much larger batches, right?
Unless we insert into enough partitions that batching doesn't ever
encounter two tuples, we'll now efficiently use multi_insert even if
there's no consecutive tuples.


>       if (cstate->rel)
>       {
> -             Datum      *values;
> -             bool       *nulls;
> +             TupleTableSlot *slot;
>               TableScanDesc scandesc;
> -             HeapTuple       tuple;
> -
> -             values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
> -             nulls = (bool *) palloc(num_phys_attrs * sizeof(bool));
>  
>               scandesc = table_beginscan(cstate->rel, GetActiveSnapshot(), 0, 
> NULL);
> +             slot = table_slot_create(cstate->rel, NULL);
>  
>               processed = 0;
> -             while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) 
> != NULL)
> +             while (table_scan_getnextslot(scandesc, ForwardScanDirection, 
> slot))
>               {
>                       CHECK_FOR_INTERRUPTS();
>  
> -                     /* Deconstruct the tuple ... faster than repeated 
> heap_getattr */
> -                     heap_deform_tuple(tuple, tupDesc, values, nulls);
> +                     /* Deconstruct the tuple ... */
> +                     slot_getallattrs(slot);
>  
>                       /* Format and send the data */
> -                     CopyOneRowTo(cstate, values, nulls);
> +                     CopyOneRowTo(cstate, slot);
>                       processed++;
>               }
>  
> +             ExecDropSingleTupleTableSlot(slot);
>               table_endscan(scandesc);
> -
> -             pfree(values);
> -             pfree(nulls);
>       }

Hm, I should just have committed this part separately. Not sure why I
didn't...


> +#define MAX_BUFFERED_TUPLES          1000
> +#define MAX_BUFFERED_BYTES           65535
> +#define MAX_PARTITION_BUFFERS        16
> +
> +typedef struct {
> +     Oid             relid;
> +     TupleTableSlot **slots;
> +     ResultRelInfo *resultRelInfo;
> +     int     nused;
> +} CopyMultiInsertBuffer;
> +
> +typedef struct {
> +     HTAB *multiInsertBufferTab;
> +     CopyMultiInsertBuffer *buffer;
> +     int bufferedTuples;
> +     int bufferedBytes;
> +     int nbuffers;
> +} CopyMultiInsertInfo;

To me it seems a bit better to have this as generic facility - we really
should use multi_insert in more places (like inserting the pg_attribute
rows). But admittedly that can just be done later.


> +static inline void
> +CopyMultiInsertBuffer_RemoveBuffer(CopyMultiInsertInfo *miinfo, 
> CopyMultiInsertBuffer *buffer)
> +{
> +     Oid             relid = buffer->relid;
> +     int             i = 0;
> +
> +     while (buffer->slots[i] != NULL)
> +             ExecClearTuple(buffer->slots[i++]);
> +     pfree(buffer->slots);
> +
> +     hash_search(miinfo->multiInsertBufferTab, (void *) &relid, HASH_REMOVE,
> +                             NULL);
> +     miinfo->nbuffers--;
> +}

Hm, this leaves dangling slots, right?



> +static inline void
> +CopyMultiInsertInfo_SetCurrentBuffer(CopyMultiInsertInfo *miinfo,
> +                                                                      
> ResultRelInfo *rri)
> +{
> +     CopyMultiInsertBuffer *buffer;
> +     Oid             relid = RelationGetRelid(rri->ri_RelationDesc);
> +     bool    found;
> +
> +     Assert(miinfo->multiInsertBufferTab != NULL);
> +
> +     buffer = hash_search(miinfo->multiInsertBufferTab, &relid, HASH_ENTER, 
> &found);
> +
> +     if (!found)
> +     {
> +             buffer->relid = relid;
> +             buffer->slots = palloc0(MAX_BUFFERED_TUPLES * 
> sizeof(TupleTableSlot *));
> +             buffer->resultRelInfo = rri;
> +             buffer->nused = 0;
> +             miinfo->nbuffers++;
> +     }
> +
> +     miinfo->buffer = buffer;
> +}

It still seems wrong to me to just perform a second hashtable search
here, givent that we've already done the partition dispatch.



>               if (proute)
>                       insertMethod = CIM_MULTI_CONDITIONAL;
>               else
>                       insertMethod = CIM_MULTI;

Hm, why do we still have CIM_MULTI and CIM_MULTI_CONDITIONAL?


>                               /*
> -                              * Tests have shown that using multi-inserts 
> when the
> -                              * partition changes on every tuple slightly 
> decreases the
> -                              * performance, however, there are benefits 
> even when only
> -                              * some batches have just 2 tuples, so let's 
> enable
> -                              * multi-inserts even when the average is quite 
> low.
> +                              * Enable multi-inserts when the partition has 
> BEFORE/INSTEAD
> +                              * OF triggers, or if the partition is a 
> foreign partition.
>                                */
>                               leafpart_use_multi_insert = insertMethod == 
> CIM_MULTI_CONDITIONAL &&
> -                                     avgTuplesPerPartChange >= 1.3 &&
> -                                     !has_before_insert_row_trig &&
> -                                     !has_instead_insert_row_trig &&
> -                                     resultRelInfo->ri_FdwRoutine == NULL;
> +                                                                             
>         !has_before_insert_row_trig &&
> +                                                                             
>         !has_instead_insert_row_trig &&
> +                                                                             
>         resultRelInfo->ri_FdwRoutine == NULL;

Not for now / this commit, but I kinda feel like we should determine
whether it's safe to multi-insert at a more centralized location.

Greetings,

Andres Freund


Reply via email to