> On Apr 8, 2026, at 19:43, shveta malik <[email protected]> wrote:
> 
> On Wed, Feb 4, 2026 at 12:51 PM Chao Li <[email protected]> wrote:
>> 
>> 
>> 
>>> On Dec 30, 2025, at 16:07, Chao Li <[email protected]> wrote:
>>> 
>>> 
>>> 
>>>> On Dec 22, 2025, at 19:48, Amit Kapila <[email protected]> wrote:
>>>> 
>>>> On Fri, Dec 19, 2025 at 1:39 PM Chao Li <[email protected]> wrote:
>>>>> 
>>>>>> On Dec 18, 2025, at 22:49, Euler Taveira <[email protected]> wrote:
>>>>>> 
>>>>>> On Wed, Dec 17, 2025, at 6:43 PM, GRANT ZHOU wrote:
>>>>>>> On Wed, Dec 17, 2025 at 12:50 PM Euler Taveira <[email protected]> 
>>>>>>> wrote:
>>>>>>>> Each table needs to say what's its row identifier. The user created a 
>>>>>>>> table
>>>>>>>> without primary key. Well, create a primary key. There are dozens of 
>>>>>>>> thousands
>>>>>>>> of objects. Use a script.
>>>>>>> However, I’d like to share a user perspective regarding the "use a
>>>>>>> script" approach. The main value of `FOR TABLES IN SCHEMA` is
>>>>>>> *in-database automation*. If users still need to maintain external
>>>>>>> scripts to monitor and `ALTER` new tables to prevent replication
>>>>>>> errors, it significantly diminishes the value of that automation.
>>>>>>> 
>>>>>> 
>>>>>> As I tried to explain in the previous email, the problem with FOR ALL 
>>>>>> TABLES
>>>>>> and FOR TABLES IN SCHEMA syntax is that the is no catalog information 
>>>>>> about the
>>>>>> relations; the list of relations is collected at runtime.
>>>>>> 
>>>>>> When I suggested "use a script" I was referring to fix the logical 
>>>>>> replication
>>>>>> setup regarding the lack of primary key. There is no need to have an 
>>>>>> automation
>>>>>> outside the database, use an event trigger. If your lazy user doesn't 
>>>>>> create
>>>>>> the primary key, assign REPLICA IDENTITY FULL. Something like
>>>>>> 
>>>>>> -- This example is far from being a complete solution for fixing the 
>>>>>> lack of
>>>>>> -- primary key in a logical replication scenario.
>>>>>> -- ALTER TABLE should be supported too
>>>>>> CREATE OR REPLACE FUNCTION event_trigger_for_replica_identity()
>>>>>>     RETURNS event_trigger LANGUAGE plpgsql AS $$
>>>>>> DECLARE
>>>>>> obj record;
>>>>>> rec record;
>>>>>> ricnt integer := 0;
>>>>>> BEGIN
>>>>>> FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
>>>>>> LOOP
>>>>>>     IF obj.command_tag = 'CREATE TABLE' THEN
>>>>>>         SELECT COUNT(*) INTO ricnt FROM pg_index WHERE indrelid = 
>>>>>> obj.objid AND indisprimary;
>>>>>>         RAISE NOTICE 'ricnt: %', ricnt;
>>>>>>         IF ricnt = 0 THEN
>>>>>>             EXECUTE 'ALTER TABLE ' || obj.object_identity || ' REPLICA 
>>>>>> IDENTITY FULL';
>>>>>>         END IF;
>>>>>>     END IF;
>>>>>> END LOOP;
>>>>>> END;
>>>>>> $$;
>>>>>> 
>>>>>> CREATE EVENT TRIGGER event_trigger_for_replica_identity
>>>>>> ON ddl_command_end
>>>>>> EXECUTE FUNCTION event_trigger_for_replica_identity();
>>>>>> 
>>>>>> CREATE TABLE event_trigger_test_1 (a int);
>>>>>> \d+ event_trigger_test_1
>>>>>> CREATE TABLE event_trigger_test_2 (a int primary key);
>>>>>> \d+ event_trigger_test_2
>>>>>> CREATE TABLE event_trigger_test_3 (a int, b text not null, primary 
>>>>>> key(b));
>>>>>> \d+ event_trigger_test_3
>>>>>> --ALTER TABLE event_trigger_test_3 DROP CONSTRAINT 
>>>>>> event_trigger_test_3_pkey;
>>>>>> --\d+ event_trigger_test_3
>>>>>> 
>>>>>> DROP EVENT TRIGGER event_trigger_for_replica_identity;
>>>>>> DROP FUNCTION event_trigger_for_replica_identity;
>>>>>> DROP TABLE event_trigger_test_1, event_trigger_test_2, 
>>>>>> event_trigger_test_3;
>>>>>> 
>>>>>> 8<----------------------------------------------------------------------------8<
>>>>>> 
>>>>>>> Additionally, tables without Primary Keys are valid SQL and extremely
>>>>>>> common in enterprise environments (e.g., audit logs, data warehousing).
>>>>>>> In large-scale deployments, enforcing PKs on every single table isn't
>>>>>>> always practical.
>>>>>>> 
>>>>>> 
>>>>>> I'm not saying users shouldn't create tables without a primary key. I'm 
>>>>>> arguing
>>>>>> that this decision should take into account what adjustments need to be 
>>>>>> made to
>>>>>> use these tables in logical replication.
>>>>>> 
>>>>>>> 
>>>>>>> I think the goal of this proposal is not to change the underlying table
>>>>>>> property design, but rather to seek a mechanism (like a Publication
>>>>>>> option) to ensure this automation functions safely without external
>>>>>>> intervention. It is simply about allowing the database to handle these
>>>>>>> valid, common scenarios gracefully when automation is enabled.
>>>>>>> 
>>>>>> 
>>>>>> You didn't get it. You already have one property to handle it and you are
>>>>>> proposing to add a second property to handle it.
>>>>>> 
>>>>>> I think you are pursuing the wrong solution. IMO we need a solution to 
>>>>>> enforce
>>>>>> that the logical replication contract is valid. If you create or modify 
>>>>>> a table
>>>>>> that is part of a publication, there is no validation that that table 
>>>>>> complies
>>>>>> with the publication properties (update and delete properties should 
>>>>>> require an
>>>>>> appropriate replica identity). We should close the gaps in both 
>>>>>> publication and
>>>>>> table.
>>>>>> 
>>>> 
>>>> If we want, we can ensure that any table added to that specific
>>>> publication (that has an option replica_identy='full') would
>>>> automatically override the default to FULL, if PK is not available.
>>>> This information can be cached to avoid overhead.
>>>> 
>>>>> 
>>>>> If I summarize Euler’s position in short words: discipline over 
>>>>> convenience. I actually strongly agree with that. In PG we generally 
>>>>> prefer explicit over implicit behavior, and predictability over magic.
>>>>> 
>>>> 
>>>> You haven't told why we can't consider a custom event trigger as
>>>> suggested by Euler for customers who are not willing to change the RI
>>>> default explicitly for each table. I think it is worth considering
>>>> providing a custom solution outside core-postgres for your customers
>>>> for this specific case.
>>> 
>>> Thanks for raising this. Let me clarify why we don’t consider a custom 
>>> event trigger a satisfactory solution in practice, even though it is 
>>> technically possible.
>>> 
>>> I discussed this with our field teams, and some customers have indeed 
>>> experimented with event-trigger-based solutions before. However, they 
>>> generally don’t prefer them for this use case.
>>> 
>>> First, the required logic is non-trivial and fragile. The trigger would 
>>> need to track table creation, primary key creation and removal, and 
>>> distinguish between cases where REPLICA IDENTITY FULL was set implicitly 
>>> versus explicitly by the user. Handling all these cases correctly makes the 
>>> solution feel like a workaround rather than a robust enforcement mechanism.
>>> 
>>> Second, event triggers introduce operational risk. They need to be 
>>> installed, monitored, and maintained separately from the core system. If a 
>>> trigger is accidentally dropped, disabled, or modified, the behavior 
>>> silently changes, which is particularly risky for replication semantics.
>>> 
>>> Third, customers place much higher trust in core PostgreSQL behavior than 
>>> in custom scripts layered on top. Issues caused by core behavior are seen 
>>> as something that can be understood, worked around, or fixed by upgrading, 
>>> whereas failures caused by custom triggers are harder to diagnose and are 
>>> often attributed to the overall solution quality.
>>> 
>>> For these reasons, while event triggers can work as a stopgap, our 
>>> customers strongly prefer a solution where the replication contract is 
>>> enforced by core PostgreSQL rather than external mechanisms.
>>> 
>>>> 
>>>>> Based on the discussion so far, I think we share the following design 
>>>>> goals:
>>>>> 
>>>>> 1) Keep replica identity as a table property.
>>>>> 2) Avoid silent runtime failures when FOR TABLES IN SCHEMA pulls in 
>>>>> tables without primary keys.
>>>>> 3) Avoid global or implicit behavior changes.
>>>>> 4) Preserve explicit opt-in for higher WAL cost.
>>>>> 5) Keep the logical replication contract explicit and enforceable.
>>>>> 
>>>>> I’ve been thinking about whether adding a new replica identity could meet 
>>>>> these goals.
>>>>> 
>>>>> Today we have four replica identities: DEFAULT (PK, fallback to NONE), 
>>>>> INDEX, FULL, and NONE.
>>>>> 
>>>>> What if we introduce a new replica identity, tentatively called “FORCE”: 
>>>>> PK with fallback to FULL. (Let’s keep our focus on the design, not argue 
>>>>> the name for now.)
>>>>> 
>>>>> With this approach:
>>>>> 
>>>>> 1) Replica identity remains a table property.
>>>>> 2) Publication membership is still evaluated at runtime, so FOR TABLES IN 
>>>>> SCHEMA is not special-cased.
>>>>> 3) No new GUCs are required.
>>>>> 4) The user must explicitly opt in by setting the replica identity. Once 
>>>>> FORCE is chosen, adding or dropping a primary key later does not silently 
>>>>> break UPDATE/DELETE replication.
>>>>> 
>>>>> 5) The logical replication contract remains explicit; the table declares 
>>>>> that it is safe for UPDATE/DELETE replication even without a PK, at the 
>>>>> cost of higher WAL volume.
>>>>> 
>>>>> This feels like a small, explicit extension of the existing RI semantics. 
>>>>> Notably, REPLICA IDENTITY DEFAULT already has conditional behavior (PK 
>>>>> fallback
>>>>> to NONE), so conditional RI behavior is not new, this would just make a 
>>>>> different fallback explicit and user-chosen.
>>>>> 
>>>>> After that, we could consider a database-level default_replica_identity 
>>>>> setting, applied at table creation time, for environments that want this 
>>>>> behavior consistently. But that would only make sense if we first agree 
>>>>> on the table-level mechanism.
>>>>> 
>>>> 
>>>> I don't much like the database-level option as it expects a new
>>>> default to be introduced. I think the internal working will almost be
>>>> same as the option at publication-level.
>>> 
>>> That’s fair. I agree that a database-level option wouldn’t be fundamentally 
>>> different from a publication-level solution and would likely share most of 
>>> the same internal mechanics.
>>> 
>>> At this point nothing is decided yet; we’re still exploring different 
>>> approaches and trying to understand the trade-offs.
>>> 
>>> I have a question to better understand how a publication-level approach 
>>> would behave in edge cases.
>>> 
>>> Since replica identity is defined on tables and a table can belong to 
>>> multiple publications, how should UPDATE/DELETE be handled if the same 
>>> table is added to two publications with different expectations?
>>> 
>>> For example, suppose a table without a PK is added to:
>>> - pub_a, which does not require FULL (or effectively falls back to NONE)
>>> - pub_b, which requires FULL for UPDATE/DELETE
>>> 
>>> In this case, should UPDATE/DELETE on the table be allowed at all, and if 
>>> so, based on which publication’s semantics? What do you think?
>>> 
>>> Best regards,
>>> --
>>> Chao Li (Evan)
>>> HighGo Software Co., Ltd.
>>> https://www.highgo.com/
>> 
>> 
>> Hi Amit,
>> 
>> Following your suggestion, I implemented a PoC that adds a new publication 
>> parameter (tentatively named fallbackfull) to make the DEFAULT → FULL 
>> fallback behavior per-publication. I’m not attached to the parameter name — 
>> if we decide to go with the publication approach, I’m happy to adjust naming 
>> based on feedback.
>> 
>> After playing with this implementation for a couple of days, I ran into a 
>> few concerns:
>> 
>> 1. Protocol extension required
>> 
>> If the DEFAULT → FULL fallback is triggered, the subscriber needs to know 
>> whether the corresponding publication has fallbackfull enabled in order to 
>> decide how to apply UPDATE/DELETE. That means we’d need to extend the 
>> logical replication protocol, e.g., by adding a new field to the RELATION 
>> message to carry the fallbackfull flag.
> 
> If the DEFAULT → FULL fallback is triggered, can we send 'full'  to
> the subscriber? I think subscriber need not to know if FULl was set by
> user or is FORCED and the protocol need not be extended.
> 
>> 
>> 2. Impact on decoding plugins
>> 
>> Decoding plugins would need to understand this new flag. In my PoC, I 
>> updated pgoutput, but there may be third-party plugins that would also need 
>> changes. That feels like a compatibility risk.
> 
> If we make the change described in pt1, I think the protocol and
> subscriber sides will not need to change, but pgoutput would still
> need to be updated when sending the RELATION metadata. Specifically,
> instead of using the table’s configured replica identity directly, it
> should use the effective replica identity.
> 
> pq_sendbyte(out, rel->rd_rel->relreplident -->effect _RI);
> 
> So IIUC, third-party plugins may also need such change.
> 
>> 
>> 3. Potential data-integrity issues
>> 
>> This is the most concerning part to me.
>> 
>> Consider a table t1 with REPLICA IDENTITY DEFAULT and no primary key, 
>> included in publication p1. By design, UPDATE/DELETE on t1 are not allowed.
>> 
>> However, a user could work around this by creating a dummy publication, 
>> adding t1 to it, and setting fallbackfull = true on that publication. This 
>> would effectively enable UPDATE/DELETE on t1.
>> 
>> Later, if the owner of p1 decides to enable fallbackfull on p1 to replicate 
>> t1, the subscriber of p1 may already be out of sync due to the earlier 
>> updates/deletes performed via the dummy publication. At that point, 
>> subsequent UPDATE/DELETE replication may fail or behave incorrectly.
>> 
>> From this perspective, allowing fallbackfull at the publication level seems 
>> to open the door to cross-publication interference and data divergence.
>> 
>> Given these concerns, I’m leaning toward keeping fallbackfull as a per-table 
>> option rather than a per-publication one. Curious to hear your thoughts.
>> 
> 
> I see your point. One possible approach could be that when any
> publication is altered to enable the fallback option, the effective
> replica identity for the affected table is promoted to FULL across all
> publications.
> 
> i.e. for t1:
> If RI_DEFAULT + no PK + pub->fallbackfull=true;
> effective_replica_identity for t1 = FULL (across all pubs)
> 
> But then this brought us to the point that if we are deciding
> effective_replica_identity for a table irrespective of publication,
> then why not to make it table property?
> 

This is true, but going back to the motivation for this feature, users do not 
want just another table-level property, because they can already do ALTER TABLE 
t REPLICA IDENTITY FULL today. A table-level property would therefore not help 
much. What users want from this feature is a mechanism that allows new tables 
to fall back from DEFAULT to FULL automatically.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






Reply via email to