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 1gOUzd-000656-Be for pgsql-hackers@arkaria.postgresql.org; Sun, 18 Nov 2018 21:52:37 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1gOUzb-0008QG-Rg for pgsql-hackers@arkaria.postgresql.org; Sun, 18 Nov 2018 21:52:35 +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 1gOUzb-0008Q1-HA for pgsql-hackers@lists.postgresql.org; Sun, 18 Nov 2018 21:52:35 +0000 Received: from mail-wr1-x442.google.com ([2a00:1450:4864:20::442]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gOUzY-000165-BY for pgsql-hackers@postgresql.org; Sun, 18 Nov 2018 21:52:34 +0000 Received: by mail-wr1-x442.google.com with SMTP id u9-v6so29974731wrr.0 for ; Sun, 18 Nov 2018 13:52:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=to:from:subject:message-id:date:user-agent:mime-version :content-language; bh=3VKO9WhZeRw9yFfQ0PwtvygpRMTx5eGUFoAKBKVKa+Y=; b=DcjU/6Zn1NHcxMIuXkQDRY8MFsb/XCEYiLTMok9WKYn3RtsCp6YBS4hJcJ4YYLp7L4 p2DbQub+pD7NO6EmYxdYizRs+vwWG392H2RlXK6cJQ+VxkNuWjSBW0vAD2aef6mqDdq1 V7cyoXuN3fpUp1Ndq/yKxyUFo5zh6sd38P65o5Tnj/cgUW93zHDmjvqJmSRZ3XNqReG9 1cEw/Ay88+XDUPELk/DN6BKSbL3/mqrIdIIDyn8bRYU+PWao2/Di8dR8TrKiGrP9rbXL /xnRtARO8a5cRqIk7XmuFPIBscBPGotP4vSD8BW7Q0tAnUWGWEwgINSdda7EdyNuVSnz EHgg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:to:from:subject:message-id:date:user-agent :mime-version:content-language; bh=3VKO9WhZeRw9yFfQ0PwtvygpRMTx5eGUFoAKBKVKa+Y=; b=RbDPfcrTUTO0DEVVK0yZQ7MCZ2eJ58lw6bqXryBEsMwzBiCgR1a+urzGCDm9TitTyg 1t3J+S3NgAa8Mdk5+GKY1N0NLZMW4wJzOX/QfoGeBekGGKdN6Xqm5TIWwIon5Gq/BHGl q+3KE3MMLqcd8TToW+Mu6J+ncGDwhOa3pj/NDNXk1q64kbTos15KU6v3s3Lvzz6uM59I 2CoIAZ1gP8Akb4woEpM8gfUE3uu0ONMPnO+Q8RvEj8WS8uPgJxyYohZNXpiKpxHVc97a ubxKfTntr0WueA0hxCMhcrwavkRQ25mP5RfnBAfagpxE1V7usSsi7fwTEkKZ3JBKQXIy Medg== X-Gm-Message-State: AA+aEWbjdgHin9L8tcqjSX1ALUjM7PcJhhzvGjel9Q1HJagCyK2dXOD1 qxEildYUsrc/tm2jmPWQRpx2PIPd02TVQ+vk+IOHXbKt7AB3I9Vfz7Kl2X87OUIFxUjx0vthhTP pqO86V4ya0BIruiPcDPqi3LeTvzsHLMxwI5L/e+I3Qgr3rtNI37w3jb04ffziu1G8j6KJdJweuZ nIAVa7Qh+jgFiJSA== X-Google-Smtp-Source: AFSGD/VIm5Lcft7ZHn2cfB6T+CKAwQ4+Rw8ylGvYfg372hhKgDdO7KBsQc3BlBWqyGOkgaHzRqp2yw== X-Received: by 2002:adf:c547:: with SMTP id s7-v6mr84537wrf.264.1542577950115; Sun, 18 Nov 2018 13:52:30 -0800 (PST) Received: from [10.137.2.19] (ip-86-49-251-50.net.upcbroadband.cz. [86.49.251.50]) by smtp.gmail.com with ESMTPSA id x10sm7324906wrn.29.2018.11.18.13.52.28 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 18 Nov 2018 13:52:29 -0800 (PST) To: PostgreSQL Development From: Tomas Vondra Subject: logical decoding vs. VACUUM FULL / CLUSTER on table with TOAST-ed data Message-ID: <1a17c643-e9af-3dba-486b-fbe31bc1823a@2ndquadrant.com> Date: Sun, 18 Nov 2018 22:52:27 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.3.0 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="------------72572F3D2BF506D973DC84E8" 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. --------------72572F3D2BF506D973DC84E8 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit Hi, It seems we have pretty annoying problem with logical decoding when performing VACUUM FULL / CLUSTER on a table with toast-ed data. The trouble is that the rewritten heap is WAL-logged using XLOG/FPI records, the TOAST data is logged as regular INSERT records. XLOG/FPI is ignored in logical decoding, and so reorderbuffer never gets those records. But we do decode the TOAST data, and reorderbuffer stashes them in toast_hash hash table. Which gets reset only when handling a row from the main heap, and that never arrives. So we end up stashing all the TOAST data in memory :-( So do VACUUM FULL (or CLUSTER) on a sufficiently large table, and you're likely to break any logical replication connection. And it does not matter if you replicate this particular table. Luckily enough, this can leverage some of the pieces introduced by e9edc1ba which was meant to deal with rewrites of system tables, and in raw_heap_insert it added this: /* * The new relfilenode's relcache entrye doesn't have the necessary * information to determine whether a relation should emit data for * logical decoding. Force it to off if necessary. */ if (!RelationIsLogicallyLogged(state->rs_old_rel)) options |= HEAP_INSERT_NO_LOGICAL; As raw_heap_insert is used only for heap rewrites, we can simply remove the if condition and use the HEAP_INSERT_NO_LOGICAL flag for all TOAST data logged from here. This does fix the issue, because we still decode the TOAST changes but there are no data and so if (change->data.tp.newtuple != NULL) { dlist_delete(&change->node); ReorderBufferToastAppendChunk(rb, txn, relation, change); } ends up not stashing the change in the hash table. It's imperfect, because we still decode the changes (and stash them to disk), but ISTM that can be fixed by tweaking DecodeInsert a bit to just ignore those changes entirely. Attached is a PoC patch with these two fixes. regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --------------72572F3D2BF506D973DC84E8 Content-Type: text/x-patch; name="rewrite-toast-fix.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="rewrite-toast-fix.patch" diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index c5db75afa1..ce6f9ed117 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -658,13 +658,8 @@ raw_heap_insert(RewriteState state, HeapTuple tup) if (!state->rs_use_wal) options |= HEAP_INSERT_SKIP_WAL; - /* - * The new relfilenode's relcache entrye doesn't have the necessary - * information to determine whether a relation should emit data for - * logical decoding. Force it to off if necessary. - */ - if (!RelationIsLogicallyLogged(state->rs_old_rel)) - options |= HEAP_INSERT_NO_LOGICAL; + /* do not decode TOAST data for heap rewrites */ + options |= HEAP_INSERT_NO_LOGICAL; heaptup = toast_insert_or_update(state->rs_new_rel, tup, NULL, options); diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afb497227e..f23cb120e8 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -665,6 +665,9 @@ DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf, static void DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { + Size datalen; + char *tupledata; + Size tuplelen; XLogReaderState *r = buf->record; xl_heap_insert *xlrec; ReorderBufferChange *change; @@ -672,6 +675,10 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) xlrec = (xl_heap_insert *) XLogRecGetData(r); + /* ignore insert records without new tuples */ + if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)) + return; + /* only interested in our database */ XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL); if (target_node.dbNode != ctx->slot->data.database) @@ -690,17 +697,13 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode)); - if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE) - { - Size datalen; - char *tupledata = XLogRecGetBlockData(r, 0, &datalen); - Size tuplelen = datalen - SizeOfHeapHeader; + tupledata = XLogRecGetBlockData(r, 0, &datalen); + tuplelen = datalen - SizeOfHeapHeader; - change->data.tp.newtuple = - ReorderBufferGetTupleBuf(ctx->reorder, tuplelen); + change->data.tp.newtuple = + ReorderBufferGetTupleBuf(ctx->reorder, tuplelen); - DecodeXLogTuple(tupledata, datalen, change->data.tp.newtuple); - } + DecodeXLogTuple(tupledata, datalen, change->data.tp.newtuple); change->data.tp.clear_toast_afterwards = true; --------------72572F3D2BF506D973DC84E8--