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 1wh2Le-0000iO-0z for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 09:44:14 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wh2Lc-0001HM-19 for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 09:44:13 +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 1wh2Lc-0001HE-00 for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 09:44:12 +0000 Received: from mail-ed1-x531.google.com ([2a00:1450:4864:20::531]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wh2LY-000000000rp-440s for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 09:44:12 +0000 Received: by mail-ed1-x531.google.com with SMTP id 4fb4d7f45d1cf-698562f10e7so5263968a12.0 for ; Tue, 07 Jul 2026 02:44:09 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783417327; cv=none; d=google.com; s=arc-20260327; b=iwUCJ6psH0wpGbcBdM4n0/ypn3ierY4nkRPkOcytr0YvTUVNU1eOsi5bwdYCkHeXwd /AwxfCaq+GGH0O9HN7icwbZ9KoVWWGPFUiWmAoRvYLygRRjeDsI4RmLrG5FR9wsXl2Gy eShhf2I9e1cefpqiYU2M70lGHUkY/vxOI/BbRlBrZJ5zf9tGMe+NE6hjpQ9JM82rAhQv 5KyIV+6JXVGSAUDWn6iP3cUMpg6b+2AfItI2RDMdcGPKnkDx8lNCjm1xAAQy3OvVND92 4FxeN90nfwm9d8TguUs4iHgmZ+NKhq6pn9HMHlUOwlD+HemAdMISQs081kv1Wg5iPfpV roPg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=to:subject:message-id:date:from:mime-version:dkim-signature; bh=K0PHnp8cHoovCfwB3Gwehbs5Ki2TtW3xYpLmnrjvcO4=; fh=nwNxTtLLPTU0ewfLM7SSbrjMajMl+wwnFkCY/fi90vE=; b=HyZ4JK1Dw2kIHZdPG/L5pGORYOLUJvGX8bfXMiQ73NIdx1L+vplWZwK7nXgaX45hIl DELXCeVW3PuBLHkskS59bFTUcS8ASaUgxX6007rSDek/UgOBJxQj3BGJVdb7SUcoybIU RQzXB1R5q3alAllZFtge4ObuguvOLRMLox70XXOoWQK9h68lvrFdFy1mW11HbHlr3iN7 A/UtYi5meFyv7jbHc5gpbb4dAZg8ZUVn3fr/SaD3yEgS4ICh4mlmSaEYvTv/ZQYAxn+g yX56zYOhmh6M/ab9Dk5xcWQUfDapxcZ/sgeGErWoVS/MffY7XAmyp73BV+DfFZiQm8HE YmjA==; 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=adyen.com; s=google; t=1783417327; x=1784022127; darn=lists.postgresql.org; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=K0PHnp8cHoovCfwB3Gwehbs5Ki2TtW3xYpLmnrjvcO4=; b=GSd1V1lYBQgcSAq0yPl4Re/bBjPGQdtOT2KWGcN15ygisbKOlwTM+s+h0q3tuTQuQE cJVjgr/91ILVLdpcd9b1e89C4LeAzV3cuLtI9+LqY1+GAbLvQJKi/ND/Sq0Q8jUjCExI iCL26CXSc9NcT4yiMCDKMw23XVcv2u9vAUp68= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783417327; x=1784022127; h=to:subject:message-id:date:from:mime-version:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=K0PHnp8cHoovCfwB3Gwehbs5Ki2TtW3xYpLmnrjvcO4=; b=okbA7igvg8cwtwl8s0y3dCJZ6FSmWHZgt8DGxKLO+44iI2FH0A7+wLJ7hIRruYx+DP v1rv9lQKyPG3pwlFMpQaZO3+ERlRrM8LrPXYUoLBa60RA0kAsguws4QaYcwOZScP1SVj lA0oDw3VqR9WoYSUkqwvupLeAppoDAJVNhuaebfajNwqeW0TZxBsQLJ22ca4clOK5BzK 1ODOmtuRvInF0HgJjzDn80l/ufVj8o7cqnFx4aWqqKd4PhVo01Esr+a5rZg/kDe/Rqv/ tOly/fYTrwd6YByzvpZplRTbJidnNjt9N7ipGgNrdzQWXxqHq4GspJ2JLiGtuItHTJoz dAJQ== X-Gm-Message-State: AOJu0Yzn9gt3xfeZLComKmgKf0P9g5OXM3NyZei3DoJMuti9ukMN8rF6 jvV69BjoK5l17efRKP9uV7nMxoLP5AvgIqWCp4eJwmM5oXcUW8o4RqjozZ7kmqwAvn1PuPLAst0 wIqWz+thR5f1+b1prX/NGud4U5QyqT+gWpR/h1qvM1ZRMxdv09a4c4Ltzkw== X-Gm-Gg: AfdE7ckTuVmNSBUGKOtmCD9TK0VSvdrl0Zf+7ljcyYEhqZd4saXqNxDOkfA3zdyXMA0 taKYCdW1hAp8mLcStc463FtFcg3ozXyoPHD+DFs4nVGOofQWUePpE9/dl0KDhZLDr6UvmgNm4Is vIVaP+PP7nRunDhV78QHKwMEBiWgWHbj76pBqeHHfTOzLk5eHtU9PaBepu2PMFbJHp+WJ1kKvH4 U/oQvy4ELdkJM9vuf0sWz/nz4zhHIqttOztZgKXtS1xczjFa7WbuzwPQfpiAYN7nrIhxg== X-Received: by 2002:a05:6402:430e:b0:697:f61b:24ca with SMTP id 4fb4d7f45d1cf-69a85c24056mr2116800a12.27.1783417326926; Tue, 07 Jul 2026 02:42:06 -0700 (PDT) MIME-Version: 1.0 From: Cagri Biroglu Date: Tue, 7 Jul 2026 11:41:55 +0200 X-Gm-Features: AVVi8Cd35hvXQ3JaqDuYXqhVVztIQglibUoZK-zMJvrevXlt6C74FJW9EscoPJ8 Message-ID: Subject: Per-table resync for logical replication subscriptions To: pgsql-hackers@lists.postgresql.org Content-Type: multipart/alternative; boundary="00000000000042158b065602343e" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --00000000000042158b065602343e Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi hackers, I would like to propose a subscriber-side way to re-copy a single table that is already part of a logical replication subscription. At the moment, I do not think PostgreSQL has a supported way to do this for one table on one subscriber. ALTER SUBSCRIPTION ... REFRESH PUBLICATION only handles tables that were added to the subscribed publications since the subscription was created, or since the last refresh. It does not re-copy tables that are already known to the subscription. That is fine for the normal =E2=80=9Cnew table was added to the publication= =E2=80=9D case, but it leaves a gap when only one subscriber needs one table to be re-seeded. Some examples are: - the subscriber table became inconsistent; - a row filter changed and the subscriber needs a fresh copy; - a forwarder/subscriber needs to repair one table without disturbing other subscribers. The usual workaround is to temporarily change publication membership, refresh the subscription, truncate the local table, add the table back, and refresh again. That works in simple topologies, but it is not really local to the subscriber. In a fan-out topology it can affect sibling subscribers, and in a forwarding topology the truncate can be replicated further downstream if truncates are published. There is also the unsupported option of updating pg_subscription_rel by hand to put the table back into the initial sync state. That is not safe either, because it races with the apply worker and does not handle the surrounding cleanup and invalidation work. There is already a similar mechanism for sequences. ALTER SUBSCRIPTION ... REFRESH SEQUENCES can re-synchronize sequence data for sequences already known to the subscription, while REFRESH PUBLICATION only discovers newly added ones. This proposal is essentially the table-side equivalent of that idea. What I am proposing is something like: ALTER SUBSCRIPTION name REFRESH TABLE table_name [ WITH (copy_data =3D true, truncate =3D true) ]; or, if people prefer the wording: ALTER SUBSCRIPTION name RESYNC TABLE table_name [ WITH (copy_data =3D true, truncate =3D true) ]; The command would be subscriber-local and would not change publication membership. Internally, it would reuse the existing table synchronization machinery by resetting the selected relation back to the initial sync state, so that a tablesync worker copies it again and moves it through the normal i -> d -> s -> r states. I tested the current behavior on current devel with a small three-node setup: - upstream publisher U; - forwarding subscriber/publisher F; - downstream subscriber D; - sibling subscriber S. The test script keeps a writer running and prints per-table checksums at each step. The important observations were: 1. ALTER SUBSCRIPTION ... REFRESH PUBLICATION on F is a no-op for an already subscribed table. The table remained in ready state, the tuple metadata did not change, no WAL was generated, and no sync worker was started. 2. The publication-membership workaround is not local. In the forwarding case, truncating the table on F also wiped the table on D. In the fan-out case, the sibling subscriber S was left behind because the publication changes were global but the repair was only intended for F. 3. A controlled subscriber-local reset of only the selected table on F produced matching checksums again without touching the publication or S. Doing the same thing as a bare catalog update while the subscription remained enabled was not reliable; the apply worker could continue with cached state and the table diverged. So I do not think the hard part is inventing a new synchronization mechanism. The existing tablesync path already does most of the work. The missing piece is a supported command that performs the reset safely: stopping or coordinating with workers, cleaning up per-table sync state and slots/origins where needed, optionally truncating the target table, invalidating caches, taking the right locks, and validating that the requested table is actually part of the subscription. For a first version, I think it would be reasonable to require the subscription to be disabled. That avoids the apply-worker race and keeps the patch smaller. A later version could support the enabled case, probably by making the running worker notice and re-read the changed relation state. I have a WIP patch for the disabled-subscription, executor changes, and a TAP test. Before polishing it further, I would like to get feedback on whether this direction makes sense ? Regards, Cagri Biroglu --00000000000042158b065602343e Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable

Hi hackers,

I would like to propose a subscriber-= side way to re-copy a single table that is already part of a logical replic= ation subscription.

At the moment, I do not think PostgreSQL has a su= pported way to do this for one table on one subscriber. ALTER SUBSCRI= PTION ... REFRESH PUBLICATION only handles tables that were added to= the subscribed publications since the subscription was created, or since t= he last refresh. It does not re-copy tables that are already known to the s= ubscription.

That is fine for the normal =E2=80=9Cnew table was added= to the publication=E2=80=9D case, but it leaves a gap when only one subscr= iber needs one table to be re-seeded. Some examples are:

  • the = subscriber table became inconsistent;

  • a row filter changed a= nd the subscriber needs a fresh copy;

  • a forwarder/subscriber= needs to repair one table without disturbing other subscribers.

  • The usual workaround is to temporarily change publication membership,= refresh the subscription, truncate the local table, add the table back, an= d refresh again. That works in simple topologies, but it is not really loca= l to the subscriber. In a fan-out topology it can affect sibling subscriber= s, and in a forwarding topology the truncate can be replicated further down= stream if truncates are published.

    There is also the unsupported opti= on of updating pg_subscription_rel by hand to put the table ba= ck into the initial sync state. That is not safe either, because it races w= ith the apply worker and does not handle the surrounding cleanup and invali= dation work.

    There is already a similar mechanism for sequences. ALTER SUBSCRIPTION ... REFRESH SEQUENCES can re-synchronize sequen= ce data for sequences already known to the subscription, while REFRES= H PUBLICATION only discovers newly added ones. This proposal is esse= ntially the table-side equivalent of that idea.

    What I am proposing i= s something like:

    ALTER SUBSCRIP=
    TION name REFRESH TABLE table_name
        [ WITH (copy_data =3D true, truncate =3D true) ];
    

    or, if people prefer the wording:

    ALTER SUBSCRIPTION name RESYNC TABLE table_name
        [ WITH (copy_data =3D true, truncate =3D true) ];
    

    The command would be subscriber-local and would not change = publication membership. Internally, it would reuse the existing table synch= ronization machinery by resetting the selected relation back to the initial= sync state, so that a tablesync worker copies it again and moves it throug= h the normal i -> d -> s -> r states.

    I tested = the current behavior on current devel with a small three-node setup:

  • upstream publisher U;

  • forwarding subscr= iber/publisher F;

  • downstream subscriber D= ;

  • sibling subscriber S.

= The test script keeps a writer running and prints per-table checksums at ea= ch step.

The important observations were:

  1. ALTER S= UBSCRIPTION ... REFRESH PUBLICATION on F is a no-op for= an already subscribed table. The table remained in ready state, the tuple = metadata did not change, no WAL was generated, and no sync worker was start= ed.

  2. The publication-membership workaround is not local. In t= he forwarding case, truncating the table on F also wiped the t= able on D. In the fan-out case, the sibling subscriber S= was left behind because the publication changes were global but the= repair was only intended for F.

  3. A controlled s= ubscriber-local reset of only the selected table on F produced= matching checksums again without touching the publication or S. Doing the same thing as a bare catalog update while the subscription rem= ained enabled was not reliable; the apply worker could continue with cached= state and the table diverged.

So I do not think the hard p= art is inventing a new synchronization mechanism. The existing tablesync pa= th already does most of the work. The missing piece is a supported command = that performs the reset safely: stopping or coordinating with workers, clea= ning up per-table sync state and slots/origins where needed, optionally tru= ncating the target table, invalidating caches, taking the right locks, and = validating that the requested table is actually part of the subscription.

For a first version, I think it would be reasonable to require the sub= scription to be disabled. That avoids the apply-worker race and keeps the p= atch smaller. A later version could support the enabled case, probably by m= aking the running worker notice and re-read the changed relation state.

=

I have a WIP patch for the disabled-subscription, executor changes, and = a TAP test. Before polishing it further, I would like to get feedback on wh= ether this direction makes sense ?

Regards,

Cagri Biroglu

--00000000000042158b065602343e--