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.94.2) (envelope-from ) id 1rpmIb-008YnG-72 for pgsql-hackers@arkaria.postgresql.org; Thu, 28 Mar 2024 09:43:53 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1rpmIa-00AmRT-8A for pgsql-hackers@arkaria.postgresql.org; Thu, 28 Mar 2024 09:43:52 +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.94.2) (envelope-from ) id 1rpmIZ-00AmRL-To for pgsql-hackers@lists.postgresql.org; Thu, 28 Mar 2024 09:43:51 +0000 Received: from mail-ej1-x62f.google.com ([2a00:1450:4864:20::62f]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1rpmIW-006yKI-0U for pgsql-hackers@lists.postgresql.org; Thu, 28 Mar 2024 09:43:51 +0000 Received: by mail-ej1-x62f.google.com with SMTP id a640c23a62f3a-a4a387ff7acso87709666b.2 for ; Thu, 28 Mar 2024 02:43:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1711619027; x=1712223827; darn=lists.postgresql.org; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=kS/RQi6J0A6Rvc8Pad/i0HQ0V+DB7Cke8JQOXQQjBFQ=; b=XJ/OaJ72cK+4A3Y0qYI1l0jwLzrXZx05YeuK9QXqqhzgn0bRMwFABaXVuZmKWjZr+r AdLTHKD3xcqG4X8IErMP1XGu9ZWCwwfbkzxArfVd6h9EhkYWD4YlYdKsfy5Bvtw3xsym KE5QjF4NLpCWAd3U5asjSD8HywsMbDvwPm2o3MfVNB/kmKAm2MXEMbz65uJXBJlGKzMH Zy/GVkGjQ4FRzbrkwT25bEgWtpcUVkVPiaRNuG/wXRs+7xI7TYc9DFdOUYaFj7Sb+Gdg e0+wtspO6F18X509YVXW9IVAp8W3D8fqP6haB5pxHuyPBBWK5q6J19ZKLGazv02TXpIa o91w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711619027; x=1712223827; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=kS/RQi6J0A6Rvc8Pad/i0HQ0V+DB7Cke8JQOXQQjBFQ=; b=ePUMeg4vfPQWCgNrByVlH1cvg8JMRi3d71kc+9sqGM2Q9zbR9ULVtgjVnCZKkcr4nJ aYr69YfIOdk5aCVYg8wj8ffeYvVhGFu9b5em3BeeKVD5EftPZrPkwj90xIdFbfqpTzcR /E0seah8UZMNN+kMW81cQ4K0VxrPA9/vPjcEahbU39qEhWUJOaSZY+uENZjRGeF3kJn5 Yklgf58uA5yIPt7m9w7UCGv5keCCBxTOozZM39Ic9vkpkxO1rw0tun9MRU6a4j21M4qW 3b0a//0HRojUFXdJVwWbyE2yYjHeZYCJhxzfvu0+XM4voMpTeVgpbsCphhGg8H3//dwc sN1g== X-Forwarded-Encrypted: i=1; AJvYcCXobkz44bjxQ+YmZNZiUe2WVo23hpzzEJIYzxO8ahe5I4k2/G60YjFtvhOcfacov3V7RPrZP9Iy4ArHOALUfkZf6AsUOhdm958DMt2hsFfVN+Ui X-Gm-Message-State: AOJu0YwizDBeaIucDQLgPQO9uP9xc6ofljE1x9Tq7bk+Ifj8vZKM8ocl 9Xz2E8hWnghX/beHVrV2QQlc3yk4QatjrQMaysnRnVVK18hQK6hq X-Google-Smtp-Source: AGHT+IFBI4pNy2gIJXoZZaaIOwLJoxVpspZoWUZJMu0PpIgCqFHfxkLbkfPyfwVI8dv4CzS0yLhUUQ== X-Received: by 2002:a17:906:b80c:b0:a4e:237a:82ac with SMTP id dv12-20020a170906b80c00b00a4e237a82acmr583675ejb.32.1711619026797; Thu, 28 Mar 2024 02:43:46 -0700 (PDT) Received: from ip-10-97-1-34.eu-west-3.compute.internal (ec2-15-236-134-5.eu-west-3.compute.amazonaws.com. [15.236.134.5]) by smtp.gmail.com with ESMTPSA id u8-20020a05600c19c800b0041478393b8fsm4807452wmq.42.2024.03.28.02.43.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 Mar 2024 02:43:46 -0700 (PDT) Date: Thu, 28 Mar 2024 09:43:44 +0000 From: Bertrand Drouvot To: Bharath Rupireddy Cc: shveta malik , Amit Kapila , Nathan Bossart , PostgreSQL Hackers Subject: Re: Introduce XID age and inactive timeout based replication slot invalidation Message-ID: References: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, On Wed, Mar 27, 2024 at 09:00:37PM +0530, Bharath Rupireddy wrote: > standby for sync slots. 0002 implementing inactive timeout GUC based > invalidation mechanism. > > Please have a look. Thanks! Regarding 0002: Some testing: T1 === When the slot is invalidated on the primary, then the reason is propagated to the sync slot (if any). That's fine but we are loosing the inactive_since on the standby: Primary: postgres=# select slot_name,inactive_since,conflicting,invalidation_reason from pg_replication_slots where slot_name='lsub29_slot'; slot_name | inactive_since | conflicting | invalidation_reason -------------+-------------------------------+-------------+--------------------- lsub29_slot | 2024-03-28 08:24:51.672528+00 | f | inactive_timeout (1 row) Standby: postgres=# select slot_name,inactive_since,conflicting,invalidation_reason from pg_replication_slots where slot_name='lsub29_slot'; slot_name | inactive_since | conflicting | invalidation_reason -------------+----------------+-------------+--------------------- lsub29_slot | | f | inactive_timeout (1 row) I think in this case it should always reflect the value from the primary (so that one can understand why it is invalidated). T2 === And it is set to a value during promotion: postgres=# select pg_promote(); pg_promote ------------ t (1 row) postgres=# select slot_name,inactive_since,conflicting,invalidation_reason from pg_replication_slots where slot_name='lsub29_slot'; slot_name | inactive_since | conflicting | invalidation_reason -------------+------------------------------+-------------+--------------------- lsub29_slot | 2024-03-28 08:30:11.74505+00 | f | inactive_timeout (1 row) I think when it is invalidated it should always reflect the value from the primary (so that one can understand why it is invalidated). T3 === As far the slot invalidation on the primary: postgres=# SELECT * FROM pg_logical_slot_get_changes('lsub29_slot', NULL, NULL, 'include-xids', '0'); ERROR: cannot acquire invalidated replication slot "lsub29_slot" Can we make the message more consistent with what can be found in CreateDecodingContext() for example? T4 === Also, it looks like querying pg_replication_slots() does not trigger an invalidation: I think it should if the slot is not invalidated yet (and matches the invalidation criteria). Code review: CR1 === + Invalidate replication slots that are inactive for longer than this + amount of time. If this value is specified without units, it is taken s/Invalidate/Invalidates/? Should we mention the relationship with inactive_since? CR2 === + * + * If check_for_invalidation is true, the slot is checked for invalidation + * based on replication_slot_inactive_timeout GUC and an error is raised after making the slot ours. */ void -ReplicationSlotAcquire(const char *name, bool nowait) +ReplicationSlotAcquire(const char *name, bool nowait, + bool check_for_invalidation) s/check_for_invalidation/check_for_timeout_invalidation/? CR3 === + if (slot->inactive_since == 0 || + replication_slot_inactive_timeout == 0) + return false; Better to test replication_slot_inactive_timeout first? (I mean there is no point of testing inactive_since if replication_slot_inactive_timeout == 0) CR4 === + if (slot->inactive_since > 0 && + replication_slot_inactive_timeout > 0) + { Same. So, instead of CR3 === and CR4 ===, I wonder if it wouldn't be better to do something like: if (replication_slot_inactive_timeout == 0) return false; else if (slot->inactive_since > 0) . . . . else return false; That would avoid checking replication_slot_inactive_timeout and inactive_since multiple times. CR5 === + * held to avoid race conditions -- for example the restart_lsn could move + * forward, or the slot could be dropped. Does the restart_lsn example makes sense here? CR6 === +static bool +InvalidateSlotForInactiveTimeout(ReplicationSlot *slot, bool need_locks) +{ InvalidatePossiblyInactiveSlot() maybe? CR7 === + /* Make sure the invalidated state persists across server restart */ + slot->just_dirtied = true; + slot->dirty = true; + SpinLockRelease(&slot->mutex); Maybe we could create a new function say MarkGivenReplicationSlotDirty() with a slot as parameter, that ReplicationSlotMarkDirty could call too? Then maybe we could set slot->data.invalidated = RS_INVAL_INACTIVE_TIMEOUT in InvalidateSlotForInactiveTimeout()? (to avoid multiple SpinLockAcquire/SpinLockRelease). CR8 === + if (persist_state) + { + char path[MAXPGPATH]; + + sprintf(path, "pg_replslot/%s", NameStr(slot->data.name)); + SaveSlotToPath(slot, path, ERROR); + } Maybe we could create a new function say GivenReplicationSlotSave() with a slot as parameter, that ReplicationSlotSave() could call too? CR9 === + if (check_for_invalidation) + { + /* The slot is ours by now */ + Assert(s->active_pid == MyProcPid); + + /* + * Well, the slot is not yet ours really unless we check for the + * invalidation below. + */ + s->active_pid = 0; + if (InvalidateReplicationSlotForInactiveTimeout(s, true, true)) + { + /* + * If the slot has been invalidated, recalculate the resource + * limits. + */ + ReplicationSlotsComputeRequiredXmin(false); + ReplicationSlotsComputeRequiredLSN(); + + /* Might need it for slot clean up on error, so restore it */ + s->active_pid = MyProcPid; + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot acquire invalidated replication slot \"%s\"", + NameStr(MyReplicationSlot->data.name)))); + } + s->active_pid = MyProcPid; Are we not missing some SpinLockAcquire/Release on the slot's mutex here? (the places where we set the active_pid). CR10 === @@ -1628,6 +1674,10 @@ InvalidatePossiblyObsoleteSlot(ReplicationSlotInvalidationCause cause, if (SlotIsLogical(s)) invalidation_cause = cause; break; + case RS_INVAL_INACTIVE_TIMEOUT: + if (InvalidateReplicationSlotForInactiveTimeout(s, false, false)) + invalidation_cause = cause; + break; InvalidatePossiblyObsoleteSlot() is not called with such a reason, better to use an Assert here and in the caller too? CR11 === +++ b/src/test/recovery/t/050_invalidate_slots.pl why not using 019_replslot_limit.pl? Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com