Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wExpl-004cUi-30 for pgsql-hackers@arkaria.postgresql.org; Mon, 20 Apr 2026 23:15:18 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wExpk-004pF6-09 for pgsql-hackers@arkaria.postgresql.org; Mon, 20 Apr 2026 23:15:16 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wExpj-004pEx-2J for pgsql-hackers@lists.postgresql.org; Mon, 20 Apr 2026 23:15:15 +0000 Received: from mail-pj1-x102e.google.com ([2607:f8b0:4864:20::102e]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wExph-00000002Cko-1MNh for pgsql-hackers@lists.postgresql.org; Mon, 20 Apr 2026 23:15:15 +0000 Received: by mail-pj1-x102e.google.com with SMTP id 98e67ed59e1d1-35fc0d7c310so2502130a91.1 for ; Mon, 20 Apr 2026 16:15:12 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1776726910; cv=none; d=google.com; s=arc-20240605; b=GV8XdWvOVFdR3eY0UzRBysoQE4fkGg2tcJ9eadmOQASDjVWJ4tClDK1O+rSISRXSqt 8Nf+AUQMKlHH6Alspp709Pu9frSd9wq4YTzjRn743Yp2lMotv0umOuNfi//RylYXr4wL 1OMv3olM5RJLQP+scalx0bQbM70Q45r1xD5gcfTvd/6eYsurIdJ+RPImG0NFVWkz8x/B LjeUO/TEJk5v1c90m+EItJAUyV5YEON6S7vaGgus8zUBKhkWru3Og8ew+TdR8jELhtHJ GXe9i7kSgE3IpTNCtcNhWZgEl0/mqvlKKL+62yWLD+ae5QCVqphvI9rA7BJx+tFz26cY cjgg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=v/sTZwCxsRKD3EsXjvdkrK2oUKtNkTbppOERRP350Sw=; fh=c8LQxg3iDtB/0Wdb7wyuGG9iiXm6Os1gDc3lUoindQM=; b=W44+BorMpzVz7WSVB3jDGw7Gzq+S125mpm5BkN3MxSIyELlVNX19XjaPhhkJmtBDkn l7BXCCzgF9F4+IcCvDT8auQx5SgJCeaqlq3YOUVOT0pxkczX2QEdiyYOIcKLBhMFE4N6 UC0pvLkwF9guWYODAllcgehwOmuRjcP88OBtaE95kzj85Rf6JJ9ScWz0Y7HM2/BXRUB7 BTxONO7w7MM5sNTLHapykwHvjqlTD98JqIJ3ulVk9MU8ueCrZDgGZoZTR+BZ8iJ2aoCw ZgN7Qdb65eUyoXHQ3nGZN9TExmZC+EOS4/wS5YsK4YViLZMTEi3ZsoJyXek04QUQLpi7 mU9g==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776726910; x=1777331710; darn=lists.postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=v/sTZwCxsRKD3EsXjvdkrK2oUKtNkTbppOERRP350Sw=; b=b3X/uEfx8eqxMY6kir2m+X0xfbdZVu0PafoYocUrnYMt+B/BbZXBwT3hsMB3RIx+ac YZ+Lkq4JptxXlym26yvY9uyOfwbqt6cCyMj+6Txd1yrPf9K6mvZB8AaRjhuAMDGGUwKi IYaprJuxvXIUH01QHztsbNEtL0qNeyG/Ycr3BtrzpL5VzzF6YUsiJ3LSHl4+NP/elMsZ I49LgO/NnPZs4X2330BJ8fxPfeVB4EdYKcrCJmBbyRvSm8dNjcQZFfb6+QZm1xfwLXLE P++DBqjCIj9fwnUNq9Ytqvj8B/dc03Xq4KdCO6bpmh+n8l3aUhT4/hRD7BNPvEuXEzeh xITg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776726910; x=1777331710; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=v/sTZwCxsRKD3EsXjvdkrK2oUKtNkTbppOERRP350Sw=; b=D9slFJ8Erp/sW2L6xznF7y4BLmfdypeUniEFXMZTUh8G0DPAJ/k9Mtaz9tii6itsRR frofPUnkwTIXNx6t+iOM/5qrrlZ5Lpy9hBdPFznSVHqnGiq6ApI789nZBo8J93UiTABv PgmS7kUV6X1tph2O//IiHOVgz7ksQzbvImv+8HnqeePNTCxYLuwinB2YvTA0cURZWlJb FFxpthxoPtHskj35YsWU3e/5+YCQitzXS09jyuF614Y+ZDe0erBbWq1s3Zv4EqF6uZS9 Q/nC/ujjlTj0tezS7LeJAdyLdPxuKgJdcgd41PJiqjq75AtbRZD4w306CikTFLRDNVMD IM4w== X-Forwarded-Encrypted: i=1; AFNElJ+v33q0Ybi8hcm0P5zuryO2emb7NtVztbefUN+jksPz1bdWV2GEKwoUMWbYhwQJ/A9Dzph6sAjIZYz+WtKi@lists.postgresql.org X-Gm-Message-State: AOJu0Yy+qNFVaOOOa8eXdjBjoSxX9tsst0f8JyUyApnozLzRhlXTWpsr VPXd9Ku23MQhOSzo1ASvq9pEnrW6BoLGe/NuMubpVST6FUKWzibjZeqBG9z+7KSfMN8CtuClLXv 62L4cEL4n1uLEI09jLxVRYyA5ZPjqBf0= X-Gm-Gg: AeBDievat/Xnfu7jKvsxART51r9/DTrGcwQA4498dQ1paxFKkrCjI1l+nCb3XlSF8WP nAxmbB/ZzsxtYqrrUx3wbFm1LuHaSG6YhsKIDm82dOHJoyLMuU4fLXgFCZ2yjLl/gziuiPG/FPO hn4yo5qVBfTbOh2dec5vVmQR88skkKLj6FeEA0GXkFWjkj3IoY+9ckLT9m9vZNXyuD60YWMmPHz FPDGBKSgBeijZHLXBZEHshqcOjUFUuR13GCWtFr4/CZ97Gs030KPkGCFxVUrQs5lLwKMxN+ycqH sxOmkjt8JKy1Q5qyeWxqaOec7p+W5Mtt71ne2amW8pF9KINTNes= X-Received: by 2002:a17:90b:5107:b0:35e:3aec:718b with SMTP id 98e67ed59e1d1-3614046e12amr14262611a91.15.1776726909916; Mon, 20 Apr 2026 16:15:09 -0700 (PDT) MIME-Version: 1.0 References: <080f9394-c127-4cef-865e-10f2f997125c@postgrespro.ru> <38690b0e-f91b-46fa-b72a-57775612e463@postgrespro.ru> In-Reply-To: From: Masahiko Sawada Date: Mon, 20 Apr 2026 16:14:32 -0700 X-Gm-Features: AQROBzBtzP9VH0bN18cC6dFMcg8V14PrOu2IGiA3MrVsVWAXg61rxQsIPsZiXqk Message-ID: Subject: Support logical replication of DDLs, take2 To: Vitaly Davydov Cc: Ashutosh Bapat , PostgreSQL Hackers Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, On Mon, Feb 23, 2026 at 5:21=E2=80=AFPM Masahiko Sawada wrote: > > > One idea I'm experimenting with is that we define an abstract data > type that can represent a DDL (like CollectedCommand) and write it to > a new WAL record so that logical decoding processes it. For CREATE > DDLs, we can use pg_get_xxx_def() function while using a historical > snapshot to get the DDLs. We would need to implement the codes to > generate DROP and ALTER DDLs from the data. I believe DROP DDLs would > not be hard. For ALTER DDLs, we would incur the initial implementation > costs, but we would not change these codes often. > DDL support for logical replication is one of the biggest missing pieces in logical replication. I'd like to resume this work for PG20. We made a lot of effort on this feature through 2022 and 2023, but the development is currently inactive. The last patch was submitted on Jul 18, 2023. I've reviewed the previous patches and discussions, and I would like to summarize how DDL replication was implemented, the main reasons it stalled, and propose an alternative design to address those problems. The overall idea of the previous patch set was to implement DDL deparsing and utilize it for DDL replication. It converted a parse tree into a JSON string. For instance, if a user executes "DROP TABLE t1", the deparser generates from its parse tree: {DROPSTMT :objects (("t1")) :removeType 41 :behavior 0 :missing_ok false :concurrent false} to: {"fmt": "DROP TABLE %{objidentity}s", "objidentity": "public.t1"} This JSON string is self-documenting, meaning someone who gets it can easily reconstruct the original DDL with schema-qualified object names. In a dedicated event trigger for logical replication, we deparsed the parse tree of a DDL, wrote it into a WAL record, and then the logical decoding processed it similarly to DML changes. While there are several benefits to the JSON data approach mentioned in the wiki [1] -- most notably the flexibility to easily remap schemas (e.g., mapping "schema A" on the publisher to "schema B" on the subscriber) -- there was a major concern: the huge maintenance burden. We would need to maintain the JSON serialization code whenever creating or modifying parse nodes, regardless of whether the changes were related to DDL replication. IIUC, this was the primary reason the feature didn't cross the finish line. Additionally, I think there is another design issue: it is not output-plugin agnostic. Since the deparsed DDL was written by a logical-replication-specific event trigger, third-party logical decoding plugins cannot easily detect DDL events. Ideally, we should write DDL information into a WAL record natively when wal_level=3D'logical' (or additionally when a GUC enables DDL events WAL-logging) so that all decoding plugins can detect them. This also allows us to test DDL logical decoding with test_decoding without setting up a full logical replication subscription. To address these two points, I'd like to propose an alternative approach: we introduce a new data type, say DDLCommand, that is self-contained to represent a DDL (like CollectedCommand), and don't rely on event triggers. It would have the command type (and subtype if required), the OIDs of the target object and its namespace, and the OID of the user who executed the DDL. We write it to a new WAL record at appropriate places during DDL execution, and the logical decoding layer passes the data to output plugins. That way, any logical decoding plugin can detect DDL changes, and it's up to the plugins how to decode the DDL information. In pgoutput, for CREATE DDLs, we can use the pg_get_xxx_ddl() functions while using a historical snapshot to get the DDLs, saving maintenance costs. We would still need to implement the code to generate DROP and ALTER DDLs from the data. I believe DROP DDLs would not be hard. For ALTER DDLs, we would incur an initial implementation cost, but we would not need to change this code often. We can implement the DDL generation code in a way that improves ddlutils.c. Also, because DDLCommand is separated from parse nodes, we only need to change the DDL deparse/replication code when it is actually needed. Additionally, this approach would eliminate the code around the two-step process (using DCT_TableDropStart and DCT_TableDropEnd) for DROP TABLE. While it would miss the flexibility benefits that the JSON deparsing approach has, I guess it would not be very hard to implement the mapping in the deparse layer even without the JSON data. Regarding the publication syntax, previous patches proposed: CREATE PUBLICATION pub FOR ALL TABLES WITH (ddl =3D 'table'); While simple, it doesn't support critical enterprise use cases (e.g., DWH environments) where users want to replicate CREATE and ALTER, but explicitly filter out DROP TABLE to prevent accidental data loss. We should consider introducing publish_ddl options to filter operations: CREATE PUBLICATION pub FOR ALL TABLES WITH (publish_ddl =3D 'create, alter'= ); I have implemented the basic idea with the above changes and it seems to work well, though the patch is not yet ready to share. I'd like to resume the discussion to move this project forward. My initial MVP goal is to support CREATE/ALTER/DROP TABLE, which covers the vast majority of use cases, and incrementally extend support for other object types later. FYI I've experimented with auto-generation approaches too. For instance, gen_node_support.pl generates C code that converts parse nodes to the corresponding text representations. Or gen_node_support.pl generates C code that makes all objects in the given SQL query text fully-schema qualified. While these ideas are promising they didn't help reduce the maintenance burden much as the parse node definitions are already complex and vary on nodes much. Thank you for taking the time to read through this long email. Regards, [1] https://wiki.postgresql.org/wiki/Logical_replication_of_DDLs#JSONB_Bene= fits --=20 Masahiko Sawada Amazon Web Services: https://aws.amazon.com