Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1glzxc-0005tB-Bq for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Jan 2019 17:35:40 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1glzxZ-0006U3-KA for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Jan 2019 17:35:37 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1glzxZ-0006SC-4X for pgsql-hackers@lists.postgresql.org; Tue, 22 Jan 2019 17:35:37 +0000 Received: from mail-wm1-x341.google.com ([2a00:1450:4864:20::341]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1glzxO-0003In-AX for pgsql-hackers@lists.postgresql.org; Tue, 22 Jan 2019 17:35:32 +0000 Received: by mail-wm1-x341.google.com with SMTP id d15so15022860wmb.3 for ; Tue, 22 Jan 2019 09:35:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=subject:from:to:cc:references:message-id:date:user-agent :mime-version:in-reply-to:content-language; bh=PLSW0MX51TsPpz6Oyxd/udzkKx2VS6IpnkhQiGASIAk=; b=WtQfj32ssWSjmF8cLdLy0k22L/4czyh5r9gapZwkxxks5HkKcLhiDLgFOrQFU4bXPR zspY/OXXOHpjM+/ZtlpWdEe0jnz/hTjHcssK/BM2KRUJpiYyXMlVsjGr+3o7i8IRPblM daTbcyqBZIHdqHSiBhxm8MG3ojuIjeSFevW9y8hEDJ9jY3VSH97q/5M3k+ngCjMQygDo L9eFUKXSHw8tkgRaQSY6MPoj7zBr86HjClzPgjsAFXJD9WmmB4/lBufr2vDTp94ACOk6 yYuiCIR5sKnAWPzJXGXcSvuZ6RAqQnwCHSMqbLtPGXloTV6zRjTme+6kaukUSwto8SBL QT2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:from:to:cc:references:message-id:date :user-agent:mime-version:in-reply-to:content-language; bh=PLSW0MX51TsPpz6Oyxd/udzkKx2VS6IpnkhQiGASIAk=; b=F/7ee7PdoOjdPF4frevFOuaoQuBlty4x4FyY/gZ9yOcvoxRIi/Omj34imcNwCeZbzl oqY4lL3GTG/mbncKGVfzaDH2u+sgymVcZvnz1IYmEr/HZSTlksdDdlMoDfmen6Y2Sgup EzLsyq0ThQiHSY3Nv7geNYWGXm861x5nIBL2/zsC9C1iwkvEOHMGKAzPYResC4hx5MYw 0aJ3oYez7Z5l4CAOvI5vI+ngXibnrRphbS3kbV9mY7Vo59nfTryW6xcrgmqn9JKVpvaB CWq4Kj0ThVYJ/y9fqmlIXVWdxfZEyiJjU/L0f7itBwe+N2svWnzLR64OiuL8Y7D+RQY9 oXuQ== X-Gm-Message-State: AJcUukfoi3cTP5i0akYGyfHwSl8CFubRv+G+7jFFlgVBl3MBC6b0TYH4 Z8RupZDO4D8q+41dcQht6k3VgyVRjSQY0B4HHZinYRhXUi1/OQ0Ih41wPTXn7yCCSHqk8NHv6Rw fxv4xpeLi6NgtCv5MtiOB40FPACYrBtnkF4KCK5L9ytthgREchNrkGgv6MSl0CHJVQFZBT2QUoC 2bHshga1GW8zM+MrTITmk= X-Google-Smtp-Source: ALg8bN6lHOYDu/qFJXoWg2MJINspRL2lncjeAOFQWbBox36qodRfu47iMKKrys9p4XBvHvWiaSihLg== X-Received: by 2002:a7b:c04e:: with SMTP id u14mr4270762wmc.133.1548178524257; Tue, 22 Jan 2019 09:35:24 -0800 (PST) Received: from [10.137.2.21] (static-84-42-175-93.net.upcbroadband.cz. [84.42.175.93]) by smtp.gmail.com with ESMTPSA id c202sm56517338wmd.40.2019.01.22.09.35.23 (version=TLS1_3 cipher=AEAD-AES128-GCM-SHA256 bits=128/128); Tue, 22 Jan 2019 09:35:23 -0800 (PST) Subject: Re: COPY FROM WHEN condition From: Tomas Vondra To: Andres Freund Cc: Surafel Temesgen , alvherre@2ndquadrant.com, Adam Berlin , pgsql-hackers@lists.postgresql.org References: <3b2b1aac-5861-acd8-fda3-054c4e4da888@2ndquadrant.com> <20190121012405.633row4iw7nfxz7h@alap3.anarazel.de> <6e80b7c1-58cd-4212-41d3-7c519aed0500@2ndquadrant.com> <20190121020805.3kdscvwzwvijlees@alap3.anarazel.de> <20190121021220.7tbu3oizvydov5by@alap3.anarazel.de> <52b10375-aca3-ea9b-10a1-2e4e9a011665@2ndquadrant.com> <20190121185102.m7keci23l7q6byou@alap3.anarazel.de> <0a3d8cff-0b9d-ea2e-634b-5dcb8d0d5766@2ndquadrant.com> Message-ID: Date: Tue, 22 Jan 2019 18:35:21 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.4.0 MIME-Version: 1.0 In-Reply-To: <0a3d8cff-0b9d-ea2e-634b-5dcb8d0d5766@2ndquadrant.com> Content-Type: multipart/mixed; boundary="------------BECE4CF6A9BCE3D17139F757" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------BECE4CF6A9BCE3D17139F757 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit On 1/21/19 11:15 PM, Tomas Vondra wrote: > > > On 1/21/19 7:51 PM, Andres Freund wrote: >> Hi, >> >> On 2019-01-21 16:22:11 +0100, Tomas Vondra wrote: >>> >>> >>> On 1/21/19 4:33 AM, Tomas Vondra wrote: >>>> >>>> >>>> On 1/21/19 3:12 AM, Andres Freund wrote: >>>>> On 2019-01-20 18:08:05 -0800, Andres Freund wrote: >>>>>> On 2019-01-20 21:00:21 -0500, Tomas Vondra wrote: >>>>>>> >>>>>>> >>>>>>> On 1/20/19 8:24 PM, Andres Freund wrote: >>>>>>>> Hi, >>>>>>>> >>>>>>>> On 2019-01-20 00:24:05 +0100, Tomas Vondra wrote: >>>>>>>>> On 1/14/19 10:25 PM, Tomas Vondra wrote: >>>>>>>>>> On 12/13/18 8:09 AM, Surafel Temesgen wrote: >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> On Wed, Dec 12, 2018 at 9:28 PM Tomas Vondra >>>>>>>>>>> > wrote: >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> Can you also update the docs to mention that the functions called from >>>>>>>>>>> the WHERE clause does not see effects of the COPY itself? >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> /Of course, i  also add same comment to insertion method selection >>>>>>>>>>> / >>>>>>>>>> >>>>>>>>>> FWIW I've marked this as RFC and plan to get it committed this week. >>>>>>>>>> >>>>>>>>> >>>>>>>>> Pushed, thanks for the patch. >>>>>>>> >>>>>>>> While rebasing the pluggable storage patch ontop of this I noticed that >>>>>>>> the qual appears to be evaluated in query context. Isn't that a bad >>>>>>>> idea? ISMT it should have been evaluated a few lines above, before the: >>>>>>>> >>>>>>>> /* Triggers and stuff need to be invoked in query context. */ >>>>>>>> MemoryContextSwitchTo(oldcontext); >>>>>>>> >>>>>>>> Yes, that'd require moving the ExecStoreHeapTuple(), but that seems ok? >>>>>>>> >>>>>>> >>>>>>> Yes, I agree. It's a bit too late for me to hack and push stuff, but I'll >>>>>>> fix that tomorrow. >>>>>> >>>>>> NP. On second thought, the problem is probably smaller than I thought at >>>>>> first, because ExecQual() switches to the econtext's per-tuple memory >>>>>> context. But it's only reset once for each batch, so there's some >>>>>> wastage. At least worth a comment. >>>>> >>>>> I'm tired, but perhaps its actually worse - what's being reset currently >>>>> is the ESTate's per-tuple context: >>>>> >>>>> if (nBufferedTuples == 0) >>>>> { >>>>> /* >>>>> * Reset the per-tuple exprcontext. We can only do this if the >>>>> * tuple buffer is empty. (Calling the context the per-tuple >>>>> * memory context is a bit of a misnomer now.) >>>>> */ >>>>> ResetPerTupleExprContext(estate); >>>>> } >>>>> >>>>> but the quals are evaluated in the ExprContext's: >>>>> >>>>> ExecQual(ExprState *state, ExprContext *econtext) >>>>> ... >>>>> ret = ExecEvalExprSwitchContext(state, econtext, &isnull); >>>>> >>>>> >>>>> which is created with: >>>>> >>>>> /* Get an EState's per-output-tuple exprcontext, making it if first use */ >>>>> #define GetPerTupleExprContext(estate) \ >>>>> ((estate)->es_per_tuple_exprcontext ? \ >>>>> (estate)->es_per_tuple_exprcontext : \ >>>>> MakePerTupleExprContext(estate)) >>>>> >>>>> and creates its own context: >>>>> /* >>>>> * Create working memory for expression evaluation in this context. >>>>> */ >>>>> econtext->ecxt_per_tuple_memory = >>>>> AllocSetContextCreate(estate->es_query_cxt, >>>>> "ExprContext", >>>>> ALLOCSET_DEFAULT_SIZES); >>>>> >>>>> so this is currently just never reset. >>>> >>>> Actually, no. The ResetPerTupleExprContext boils down to >>>> >>>> MemoryContextReset((econtext)->ecxt_per_tuple_memory) >>>> >>>> and ExecEvalExprSwitchContext does this >>>> >>>> MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); >>>> >>>> So it's resetting the right context, although only on batch boundary. >> >>>>> Seems just using ExecQualAndReset() ought to be sufficient? >>>>> >>>> >>>> That may still be the right thing to do. >>>> >>> >>> Actually, no, because that would reset the context far too early (and >>> it's easy to trigger segfaults). So the reset would have to happen after >>> processing the row, not this early. >> >> Yea, sorry, I was too tired yesterday evening. I'd spent 10h splitting >> up the pluggable storage patch into individual pieces... >> >> >>> But I think the current behavior is actually OK, as it matches what we >>> do for defexprs. And the comment before ResetPerTupleExprContext says this: >>> >>> /* >>> * Reset the per-tuple exprcontext. We can only do this if the >>> * tuple buffer is empty. (Calling the context the per-tuple >>> * memory context is a bit of a misnomer now.) >>> */ >>> >>> So the per-tuple context is not quite per-tuple anyway. Sure, we might >>> rework that but I don't think that's an issue in this patch. >> >> I'm *not* convinced by this. I think it's bad enough that we do this for >> normal COPY, but for WHEN, we could end up *never* resetting before the >> end. Consider a case where a single tuple is inserted, and then *all* >> rows are filtered. I think this needs a separate econtext that's reset >> every round. Or alternatively you could fix the code not to rely on >> per-tuple not being reset when tuples are buffered - that actually ought >> to be fairly simple. >> > > I think separating the per-tuple and per-batch contexts is the right > thing to do, here. It seems the batching was added somewhat later and > using the per-tuple context is rather confusing. > OK, here is a WIP patch doing that. It creates a new "batch" context, and allocates tuples in it (instead of the per-tuple context). The per-tuple context is now reset always, irrespectedly of nBufferedTuples. And the batch context is reset every time the batch is emptied. It turned out to be a tad more complex due to partitioning, because when we find the partitions do not match, the tuple is already allocated in the "current" context (be it per-tuple or batch). So we can't just free the whole context at that point. The old code worked around this by alternating two contexts, but that seems a bit too cumbersome to me, so the patch simply copies the tuple to the new context. That allows us to reset the batch context always, right after emptying the buffer. I need to do some benchmarking to see if the extra copy causes any regression. Overall, separating the contexts makes it quite a bit clearer. I'm not entirely happy about the per-tuple context being "implicit" (hidden in executor context) while the batch context being explicitly created, but there's not much I can do about that. The patch also includes the fix correcting the volatility check on WHERE clause, although that shall be committed separately. regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --------------BECE4CF6A9BCE3D17139F757 Content-Type: text/x-patch; name="copy-when-fixes.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="copy-when-fixes.patch" diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index c410e0a0dd..68d9409aef 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2323,9 +2323,9 @@ CopyFrom(CopyState cstate) ExprContext *econtext; TupleTableSlot *myslot; MemoryContext oldcontext = CurrentMemoryContext; + MemoryContext batchcxt; PartitionTupleRouting *proute = NULL; - ExprContext *secondaryExprContext = NULL; ErrorContextCallback errcallback; CommandId mycid = GetCurrentCommandId(true); int hi_options = 0; /* start with default heap_insert options */ @@ -2612,8 +2612,7 @@ CopyFrom(CopyState cstate) */ insertMethod = CIM_SINGLE; } - else if (cstate->whereClause != NULL || - contain_volatile_functions(cstate->whereClause)) + else if (contain_volatile_functions(cstate->whereClause)) { /* * Can't support multi-inserts if there are any volatile funcation @@ -2640,20 +2639,10 @@ CopyFrom(CopyState cstate) * Normally, when performing bulk inserts we just flush the insert * buffer whenever it becomes full, but for the partitioned table * case, we flush it whenever the current tuple does not belong to the - * same partition as the previous tuple, and since we flush the - * previous partition's buffer once the new tuple has already been - * built, we're unable to reset the estate since we'd free the memory - * in which the new tuple is stored. To work around this we maintain - * a secondary expression context and alternate between these when the - * partition changes. This does mean we do store the first new tuple - * in a different context than subsequent tuples, but that does not - * matter, providing we don't free anything while it's still needed. + * same partition as the previous tuple. */ if (proute) - { insertMethod = CIM_MULTI_CONDITIONAL; - secondaryExprContext = CreateExprContext(estate); - } else insertMethod = CIM_MULTI; @@ -2686,6 +2675,14 @@ CopyFrom(CopyState cstate) errcallback.previous = error_context_stack; error_context_stack = &errcallback; + /* + * Set up memory context for batches (in CIM_SINGLE mode this is equal + * to per-tuple context, effectively). + */ + batchcxt = AllocSetContextCreate(CurrentMemoryContext, + "copy batch context", + ALLOCSET_DEFAULT_SIZES); + for (;;) { TupleTableSlot *slot; @@ -2693,18 +2690,11 @@ CopyFrom(CopyState cstate) CHECK_FOR_INTERRUPTS(); - if (nBufferedTuples == 0) - { - /* - * Reset the per-tuple exprcontext. We can only do this if the - * tuple buffer is empty. (Calling the context the per-tuple - * memory context is a bit of a misnomer now.) - */ - ResetPerTupleExprContext(estate); - } + /* Reset the per-tuple exprcontext. */ + ResetPerTupleExprContext(estate); /* Switch into its memory context */ - MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + MemoryContextSwitchTo(batchcxt); if (!NextCopyFrom(cstate, econtext, values, nulls)) break; @@ -2757,7 +2747,7 @@ CopyFrom(CopyState cstate) */ if (nBufferedTuples > 0) { - ExprContext *swapcontext; + MemoryContext oldcontext; CopyFromInsertBatch(cstate, estate, mycid, hi_options, prevResultRelInfo, myslot, bistate, @@ -2766,29 +2756,26 @@ CopyFrom(CopyState cstate) nBufferedTuples = 0; bufferedTuplesSize = 0; - Assert(secondaryExprContext); - /* - * Normally we reset the per-tuple context whenever - * the bufferedTuples array is empty at the beginning - * of the loop, however, it is possible since we flush - * the buffer here that the buffer is never empty at - * the start of the loop. To prevent the per-tuple - * context from never being reset we maintain a second - * context and alternate between them when the - * partition changes. We can now reset - * secondaryExprContext as this is no longer needed, - * since we just flushed any tuples stored in it. We - * also now switch over to the other context. This - * does mean that the first tuple in the buffer won't - * be in the same context as the others, but that does - * not matter since we only reset it after the flush. + * The tuple is allocated in the batch context, but we + * want to reset that (and keep the tuple). So we copy + * the tuple into the per-tuple context, do the reset + * and then copy the tuple back. */ - ReScanExprContext(secondaryExprContext); + oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + tuple = heap_copytuple(tuple); + MemoryContextSwitchTo(oldcontext); + + /* free tuples from the batch we just processed */ + MemoryContextReset(batchcxt); - swapcontext = secondaryExprContext; - secondaryExprContext = estate->es_per_tuple_exprcontext; - estate->es_per_tuple_exprcontext = swapcontext; + /* copy the tuple back to the per-tuple context */ + oldcontext = MemoryContextSwitchTo(batchcxt); + tuple = heap_copytuple(tuple); + MemoryContextSwitchTo(oldcontext); + + /* and also store the copied tuple into the slot */ + ExecStoreHeapTuple(tuple, slot, false); } nPartitionChanges++; @@ -2894,10 +2881,10 @@ CopyFrom(CopyState cstate) slot = execute_attr_map_slot(map->attrMap, slot, new_slot); /* - * Get the tuple in the per-tuple context, so that it will be + * Get the tuple in the per-batch context, so that it will be * freed after each batch insert. */ - oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + oldcontext = MemoryContextSwitchTo(batchcxt); tuple = ExecCopySlotHeapTuple(slot); MemoryContextSwitchTo(oldcontext); } @@ -2973,6 +2960,9 @@ CopyFrom(CopyState cstate) firstBufferedLineNo); nBufferedTuples = 0; bufferedTuplesSize = 0; + + /* free memory occupied by tuples from the batch */ + MemoryContextReset(batchcxt); } } else @@ -3054,6 +3044,8 @@ CopyFrom(CopyState cstate) MemoryContextSwitchTo(oldcontext); + MemoryContextDelete(batchcxt); + /* * In the old protocol, tell pqcomm that we can process normal protocol * messages again. --------------BECE4CF6A9BCE3D17139F757--