Memory leak in pg_logical_slot_{get,peek}_changes
Hi,
I'm starting a new thread for one of the issues reported by Sawada-san at [1]/messages/by-id/CAD21AoDkAhQVSukOfH3_reuF-j4EU0-HxMqU3dU+bSTxsqT14Q@mail.gmail.com.
This is a memory leak on CacheMemoryContext when using pgoutput via SQL APIs:
/* Map must live as long as the session does. */
oldctx = MemoryContextSwitchTo(CacheMemoryContext);
entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
MemoryContextSwitchTo(oldctx);
RelationClose(ancestor);
entry->attrmap is pfree'd only when validating the RelationSyncEntry
so remains even after logical decoding API calls.
This issue can be reproduced with the following test:
-- Create table
create table t1( c1 int, c2 int, c3 int) PARTITION BY LIST (c1);
create table t1_1( c2 int, c1 int, c3 int);
ALTER TABLE t1 ATTACH PARTITION t1_1 FOR VALUES IN (0, 1, 2, 3);
-- Create publication
create publication pub1 FOR TABLE t1, t1_1 WITH
(publish_via_partition_root = true);
-- Create replication slot
SELECT 'init' FROM pg_create_logical_replication_slot('test', 'pgoutput');
-- Run the below steps between Test start and Test end many times to
see the memory usage getting increased
-- Test start
insert into t1 values( 1);
SELECT count(*) FROM pg_logical_slot_get_binary_changes('test', NULL,
NULL, 'proto_version', '4', 'publication_names', 'pub1');
-- Memory increases after each invalidation and insert
SELECT * FROM pg_backend_memory_contexts where name = 'CacheMemoryContext';
-- Test end
The attached patch resolves a memory leak by ensuring that the
attribute map is properly freed during plugin shutdown. This process
is triggered by the SQL API when the decoding context is being
released. Additionally, I am conducting a review to identify and
address any similar memory leaks that may exist elsewhere in the code.
[1]: /messages/by-id/CAD21AoDkAhQVSukOfH3_reuF-j4EU0-HxMqU3dU+bSTxsqT14Q@mail.gmail.com
Regards,
Vignesh
Attachments:
v1-0001-Fix-memory-leak-in-pgoutput-relation-attribute-ma.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Fix-memory-leak-in-pgoutput-relation-attribute-ma.patchDownload
From 36e5c10105d934da0d51474b0ad7c5bd9087e2aa Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Wed, 11 Dec 2024 08:57:06 +0530
Subject: [PATCH v1] Fix memory leak in pgoutput relation attribute map
The pgoutput module caches relation attribute map and frees it upon
invalidation. However, this was not getting freed incase of SQL functions like
pg_logical_slot_{get,peek}_changes() which would bloat its cache memory usage.
Every pg_logical_slot_{get,peek}_changes() call for changes on partition table
creates more bloat. To address this, this relation attribute map is freed while
the output plugin is shutdown.
---
src/backend/replication/pgoutput/pgoutput.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index b50b3d62e3..f1ef13d313 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1747,6 +1747,16 @@ pgoutput_shutdown(LogicalDecodingContext *ctx)
{
if (RelationSyncCache)
{
+ RelationSyncEntry *entry;
+ HASH_SEQ_STATUS status;
+
+ hash_seq_init(&status, RelationSyncCache);
+ while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
+ {
+ if (entry->attrmap)
+ free_attrmap(entry->attrmap);
+ }
+
hash_destroy(RelationSyncCache);
RelationSyncCache = NULL;
}
--
2.43.0
On Wednesday, December 11, 2024 12:28 PM vignesh C <vignesh21@gmail.com> wrote:
Hi,
I'm starting a new thread for one of the issues reported by Sawada-san at [1].
This is a memory leak on CacheMemoryContext when using pgoutput via SQL
APIs:
/* Map must live as long as the session does. */
oldctx = MemoryContextSwitchTo(CacheMemoryContext);entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
MemoryContextSwitchTo(oldctx);
RelationClose(ancestor);entry->attrmap is pfree'd only when validating the RelationSyncEntry
so remains even after logical decoding API calls.This issue can be reproduced with the following test:
-- Create table
create table t1( c1 int, c2 int, c3 int) PARTITION BY LIST (c1);
create table t1_1( c2 int, c1 int, c3 int);
ALTER TABLE t1 ATTACH PARTITION t1_1 FOR VALUES IN (0, 1, 2, 3);-- Create publication
create publication pub1 FOR TABLE t1, t1_1 WITH
(publish_via_partition_root = true);-- Create replication slot
SELECT 'init' FROM pg_create_logical_replication_slot('test', 'pgoutput');-- Run the below steps between Test start and Test end many times to
see the memory usage getting increased
-- Test start
insert into t1 values( 1);
SELECT count(*) FROM pg_logical_slot_get_binary_changes('test', NULL,
NULL, 'proto_version', '4', 'publication_names', 'pub1');-- Memory increases after each invalidation and insert
SELECT * FROM pg_backend_memory_contexts where name =
'CacheMemoryContext';
-- Test endThe attached patch resolves a memory leak by ensuring that the
attribute map is properly freed during plugin shutdown. This process
is triggered by the SQL API when the decoding context is being
released. Additionally, I am conducting a review to identify and
address any similar memory leaks that may exist elsewhere in the code.
Thanks for reporting the issue and share the fix.
I am not sure if freeing them in shutdown callback is safe, because shutdown
callback Is not invoked in case of ERRORs. I think we'd better allocate them
under cachectx in the beginning to avoid freeing them explicitly.
The attachment is a POC that I internally experimented before. It replaces not
only the attrmap's context, but also others which were allocated under
CacheMemoryContext, to be consistent. Note that I have not tested it deeply.
Feel free to use if it works for you.
But the patch can only be used since PG15 where cachectx is introduced. In
older braches, we may need to think some other ways.
Best Regards,
Hou zj
Attachments:
0002-Fix-memory-leak-under-CacheMemoryContext-in-pgoutput.patchapplication/octet-stream; name=0002-Fix-memory-leak-under-CacheMemoryContext-in-pgoutput.patchDownload
From d7bde54c7a08407513755bed59148e4c03f0df40 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Wed, 4 Dec 2024 15:14:26 +0800
Subject: [PATCH 2/2] Fix memory leak under CacheMemoryContext in pgoutput
---
src/backend/replication/pgoutput/pgoutput.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 7a27ee38a0..2d81cd423e 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -222,7 +222,8 @@ static void send_relation_and_attrs(Relation relation, TransactionId xid,
static void rel_sync_cache_relation_cb(Datum arg, Oid relid);
static void rel_sync_cache_publication_cb(Datum arg, int cacheid,
uint32 hashvalue);
-static void set_schema_sent_in_streamed_txn(RelationSyncEntry *entry,
+static void set_schema_sent_in_streamed_txn(PGOutputData *data,
+ RelationSyncEntry *entry,
TransactionId xid);
static bool get_schema_sent_in_streamed_txn(RelationSyncEntry *entry,
TransactionId xid);
@@ -748,7 +749,7 @@ maybe_send_schema(LogicalDecodingContext *ctx,
send_relation_and_attrs(relation, xid, ctx, relentry);
if (data->in_streaming)
- set_schema_sent_in_streamed_txn(relentry, topxid);
+ set_schema_sent_in_streamed_txn(data, relentry, topxid);
else
relentry->schema_sent = true;
}
@@ -1197,8 +1198,8 @@ init_tuple_slot(PGOutputData *data, Relation relation,
TupleDesc indesc = RelationGetDescr(relation);
TupleDesc outdesc = RelationGetDescr(ancestor);
- /* Map must live as long as the session does. */
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ /* Map must live as long as the logical decoding context. */
+ oldctx = MemoryContextSwitchTo(data->cachectx);
entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
@@ -1991,11 +1992,12 @@ get_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid)
* of the relation.
*/
static void
-set_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid)
+set_schema_sent_in_streamed_txn(PGOutputData *data, RelationSyncEntry *entry,
+ TransactionId xid)
{
MemoryContext oldctx;
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ oldctx = MemoryContextSwitchTo(data->cachectx);
entry->streamed_txns = lappend_xid(entry->streamed_txns, xid);
--
2.30.0.windows.2
On Wed, 11 Dec 2024 at 11:39, Zhijie Hou (Fujitsu)
<houzj.fnst@fujitsu.com> wrote:
On Wednesday, December 11, 2024 12:28 PM vignesh C <vignesh21@gmail.com> wrote:
Hi,
I'm starting a new thread for one of the issues reported by Sawada-san at [1].
This is a memory leak on CacheMemoryContext when using pgoutput via SQL
APIs:
/* Map must live as long as the session does. */
oldctx = MemoryContextSwitchTo(CacheMemoryContext);entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
MemoryContextSwitchTo(oldctx);
RelationClose(ancestor);entry->attrmap is pfree'd only when validating the RelationSyncEntry
so remains even after logical decoding API calls.This issue can be reproduced with the following test:
-- Create table
create table t1( c1 int, c2 int, c3 int) PARTITION BY LIST (c1);
create table t1_1( c2 int, c1 int, c3 int);
ALTER TABLE t1 ATTACH PARTITION t1_1 FOR VALUES IN (0, 1, 2, 3);-- Create publication
create publication pub1 FOR TABLE t1, t1_1 WITH
(publish_via_partition_root = true);-- Create replication slot
SELECT 'init' FROM pg_create_logical_replication_slot('test', 'pgoutput');-- Run the below steps between Test start and Test end many times to
see the memory usage getting increased
-- Test start
insert into t1 values( 1);
SELECT count(*) FROM pg_logical_slot_get_binary_changes('test', NULL,
NULL, 'proto_version', '4', 'publication_names', 'pub1');-- Memory increases after each invalidation and insert
SELECT * FROM pg_backend_memory_contexts where name =
'CacheMemoryContext';
-- Test endThe attached patch resolves a memory leak by ensuring that the
attribute map is properly freed during plugin shutdown. This process
is triggered by the SQL API when the decoding context is being
released. Additionally, I am conducting a review to identify and
address any similar memory leaks that may exist elsewhere in the code.Thanks for reporting the issue and share the fix.
I am not sure if freeing them in shutdown callback is safe, because shutdown
callback Is not invoked in case of ERRORs. I think we'd better allocate them
under cachectx in the beginning to avoid freeing them explicitly.
I initially considered addressing this issue in a way similar to your
suggestion while fixing it, but later decided to make the change in
pgoutput_shutdown, following the approach used for RelationSyncCache.
This was because RelationSyncCache relies on CacheMemoryContext, and
attrmap is a member of RelationSyncCache entry. Now that we're
considering attrmap in the context of cachectx, do you think we should
apply cachectx to RelationSyncCache as well to solve the similar issue
that can occur with RelationSyncCache.
Regards,
Vignesh
On Wednesday, December 11, 2024 5:11 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, 11 Dec 2024 at 11:39, Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com>
wrote:On Wednesday, December 11, 2024 12:28 PM vignesh C
<vignesh21@gmail.com> wrote:
The attached patch resolves a memory leak by ensuring that the
attribute map is properly freed during plugin shutdown. This process
is triggered by the SQL API when the decoding context is being
released. Additionally, I am conducting a review to identify and
address any similar memory leaks that may exist elsewhere in the code.Thanks for reporting the issue and share the fix.
I am not sure if freeing them in shutdown callback is safe, because
shutdown callback Is not invoked in case of ERRORs. I think we'd
better allocate them under cachectx in the beginning to avoid freeing themexplicitly.
I initially considered addressing this issue in a way similar to your suggestion
while fixing it, but later decided to make the change in pgoutput_shutdown,
following the approach used for RelationSyncCache.
This was because RelationSyncCache relies on CacheMemoryContext, and
attrmap is a member of RelationSyncCache entry.
I think we have tended to allocate the member of RelationSyncEntry under
logical decoding context since 52e4f0c. I think that makes more sense because these
members ideally should live as long as the decoding context. In addition, it was
suggested[1]/messages/by-id/20220129003110.6ndrrpanem5sb4ee@alap3.anarazel.de that allocating all the thing under CacheMemoryContext is hard to
debug. And people in another thread[2]/messages/by-id/Z1d-uR20pt6wtQIS@paquier.xyz also seems agree to remove the dependency
of CacheMemoryContext in the long term.
Now that we're considering
attrmap in the context of cachectx, do you think we should apply cachectx to
RelationSyncCache as well to solve the similar issue that can occur with
RelationSyncCache.
I personally think it could be considered as a separate project because
RelationSyncCache is accessed even after shutting down the output plugin due to
the registered cache invalidation callbacks. We probably need
MemoryContextRegisterResetCallback() to reset the static pointer
(RelationSyncCache).
[1]: /messages/by-id/20220129003110.6ndrrpanem5sb4ee@alap3.anarazel.de
[2]: /messages/by-id/Z1d-uR20pt6wtQIS@paquier.xyz
Best Regards,
Hou zj
On Wed, 11 Dec 2024 at 15:15, Zhijie Hou (Fujitsu)
<houzj.fnst@fujitsu.com> wrote:
On Wednesday, December 11, 2024 5:11 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, 11 Dec 2024 at 11:39, Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com>
wrote:On Wednesday, December 11, 2024 12:28 PM vignesh C
<vignesh21@gmail.com> wrote:
The attached patch resolves a memory leak by ensuring that the
attribute map is properly freed during plugin shutdown. This process
is triggered by the SQL API when the decoding context is being
released. Additionally, I am conducting a review to identify and
address any similar memory leaks that may exist elsewhere in the code.Thanks for reporting the issue and share the fix.
I am not sure if freeing them in shutdown callback is safe, because
shutdown callback Is not invoked in case of ERRORs. I think we'd
better allocate them under cachectx in the beginning to avoid freeing themexplicitly.
I initially considered addressing this issue in a way similar to your suggestion
while fixing it, but later decided to make the change in pgoutput_shutdown,
following the approach used for RelationSyncCache.
This was because RelationSyncCache relies on CacheMemoryContext, and
attrmap is a member of RelationSyncCache entry.I think we have tended to allocate the member of RelationSyncEntry under
logical decoding context since 52e4f0c. I think that makes more sense because these
members ideally should live as long as the decoding context. In addition, it was
suggested[1] that allocating all the thing under CacheMemoryContext is hard to
debug. And people in another thread[2] also seems agree to remove the dependency
of CacheMemoryContext in the long term.Now that we're considering
attrmap in the context of cachectx, do you think we should apply cachectx to
RelationSyncCache as well to solve the similar issue that can occur with
RelationSyncCache.I personally think it could be considered as a separate project because
RelationSyncCache is accessed even after shutting down the output plugin due to
the registered cache invalidation callbacks. We probably need
MemoryContextRegisterResetCallback() to reset the static pointer
(RelationSyncCache).
Yes that makes sense, let's handle this separately.
Regards,
Vignesh
On Wed, 11 Dec 2024 at 11:39, Zhijie Hou (Fujitsu)
<houzj.fnst@fujitsu.com> wrote:
On Wednesday, December 11, 2024 12:28 PM vignesh C <vignesh21@gmail.com> wrote:
Hi,
I'm starting a new thread for one of the issues reported by Sawada-san at [1].
This is a memory leak on CacheMemoryContext when using pgoutput via SQL
APIs:
/* Map must live as long as the session does. */
oldctx = MemoryContextSwitchTo(CacheMemoryContext);entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
MemoryContextSwitchTo(oldctx);
RelationClose(ancestor);entry->attrmap is pfree'd only when validating the RelationSyncEntry
so remains even after logical decoding API calls.This issue can be reproduced with the following test:
-- Create table
create table t1( c1 int, c2 int, c3 int) PARTITION BY LIST (c1);
create table t1_1( c2 int, c1 int, c3 int);
ALTER TABLE t1 ATTACH PARTITION t1_1 FOR VALUES IN (0, 1, 2, 3);-- Create publication
create publication pub1 FOR TABLE t1, t1_1 WITH
(publish_via_partition_root = true);-- Create replication slot
SELECT 'init' FROM pg_create_logical_replication_slot('test', 'pgoutput');-- Run the below steps between Test start and Test end many times to
see the memory usage getting increased
-- Test start
insert into t1 values( 1);
SELECT count(*) FROM pg_logical_slot_get_binary_changes('test', NULL,
NULL, 'proto_version', '4', 'publication_names', 'pub1');-- Memory increases after each invalidation and insert
SELECT * FROM pg_backend_memory_contexts where name =
'CacheMemoryContext';
-- Test endThe attached patch resolves a memory leak by ensuring that the
attribute map is properly freed during plugin shutdown. This process
is triggered by the SQL API when the decoding context is being
released. Additionally, I am conducting a review to identify and
address any similar memory leaks that may exist elsewhere in the code.Thanks for reporting the issue and share the fix.
I am not sure if freeing them in shutdown callback is safe, because shutdown
callback Is not invoked in case of ERRORs. I think we'd better allocate them
under cachectx in the beginning to avoid freeing them explicitly.The attachment is a POC that I internally experimented before. It replaces not
only the attrmap's context, but also others which were allocated under
CacheMemoryContext, to be consistent. Note that I have not tested it deeply.
Feel free to use if it works for you.But the patch can only be used since PG15 where cachectx is introduced. In
older braches, we may need to think some other ways.
Since we cannot add a new member to PGOutputData, how about creating a
new global static memory context specifically for storing the relation
entry attribute map? This memory context could be allocated and reset
during pgoutput_startup. This way, if a SQL call is made after an
error, the context will be reset, preventing memory bloat as addressed
in the attached patch. Thoughts?
Regards,
Vignesh
Attachments:
PG14_-0001-Fix-memory-leak-in-pgoutput-relation-attribut.patchtext/x-patch; charset=US-ASCII; name=PG14_-0001-Fix-memory-leak-in-pgoutput-relation-attribut.patchDownload
From d9c7159903bc9266a0846269c971631f36bb8748 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Thu, 12 Dec 2024 19:01:32 +0530
Subject: [PATCH vPG14_] Fix memory leak in pgoutput relation attribute map
The pgoutput module caches relation attribute map and frees it upon
invalidation. However, this was not getting freed incase of SQL functions like
pg_logical_slot_{get,peek}_changes() which would bloat its cache memory usage.
Every pg_logical_slot_{get,peek}_changes() call for changes on partition table
creates more bloat. To address this issue, a new global static memory context
has been introduced, which is reset during pgoutput_startup to avoid
bloat after each pg_logical_slot_{get,peek}_changes() call.
---
src/backend/replication/pgoutput/pgoutput.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 78c81888c4..ef2e6ab723 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -64,6 +64,7 @@ static void pgoutput_stream_commit(struct LogicalDecodingContext *ctx,
static bool publications_valid;
static bool in_streaming;
+static MemoryContext relentrycachectx = NULL;
static List *LoadPublications(List *pubnames);
static void publication_invalidation_cb(Datum arg, int cacheid,
@@ -345,6 +346,13 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
/* Disable the streaming during the slot initialization mode. */
ctx->streaming = false;
}
+
+ if (relentrycachectx == NULL)
+ relentrycachectx = AllocSetContextCreate(CacheMemoryContext,
+ "logical replication relation entry cache context",
+ ALLOCSET_SMALL_SIZES);
+ else
+ MemoryContextReset(relentrycachectx);
}
/*
@@ -464,7 +472,7 @@ maybe_send_schema(LogicalDecodingContext *ctx,
MemoryContext oldctx;
/* Map must live as long as the session does. */
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ oldctx = MemoryContextSwitchTo(relentrycachectx);
/*
* Make copies of the TupleDescs that will live as long as the map
--
2.43.0
On Thu, 12 Dec 2024 at 20:32, vignesh C <vignesh21@gmail.com> wrote:
On Wed, 11 Dec 2024 at 11:39, Zhijie Hou (Fujitsu)
<houzj.fnst@fujitsu.com> wrote:On Wednesday, December 11, 2024 12:28 PM vignesh C <vignesh21@gmail.com> wrote:
Hi,
I'm starting a new thread for one of the issues reported by Sawada-san at [1].
This is a memory leak on CacheMemoryContext when using pgoutput via SQL
APIs:
/* Map must live as long as the session does. */
oldctx = MemoryContextSwitchTo(CacheMemoryContext);entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
MemoryContextSwitchTo(oldctx);
RelationClose(ancestor);entry->attrmap is pfree'd only when validating the RelationSyncEntry
so remains even after logical decoding API calls.This issue can be reproduced with the following test:
-- Create table
create table t1( c1 int, c2 int, c3 int) PARTITION BY LIST (c1);
create table t1_1( c2 int, c1 int, c3 int);
ALTER TABLE t1 ATTACH PARTITION t1_1 FOR VALUES IN (0, 1, 2, 3);-- Create publication
create publication pub1 FOR TABLE t1, t1_1 WITH
(publish_via_partition_root = true);-- Create replication slot
SELECT 'init' FROM pg_create_logical_replication_slot('test', 'pgoutput');-- Run the below steps between Test start and Test end many times to
see the memory usage getting increased
-- Test start
insert into t1 values( 1);
SELECT count(*) FROM pg_logical_slot_get_binary_changes('test', NULL,
NULL, 'proto_version', '4', 'publication_names', 'pub1');-- Memory increases after each invalidation and insert
SELECT * FROM pg_backend_memory_contexts where name =
'CacheMemoryContext';
-- Test endThe attached patch resolves a memory leak by ensuring that the
attribute map is properly freed during plugin shutdown. This process
is triggered by the SQL API when the decoding context is being
released. Additionally, I am conducting a review to identify and
address any similar memory leaks that may exist elsewhere in the code.Thanks for reporting the issue and share the fix.
I am not sure if freeing them in shutdown callback is safe, because shutdown
callback Is not invoked in case of ERRORs. I think we'd better allocate them
under cachectx in the beginning to avoid freeing them explicitly.The attachment is a POC that I internally experimented before. It replaces not
only the attrmap's context, but also others which were allocated under
CacheMemoryContext, to be consistent. Note that I have not tested it deeply.
Feel free to use if it works for you.But the patch can only be used since PG15 where cachectx is introduced. In
older braches, we may need to think some other ways.Since we cannot add a new member to PGOutputData, how about creating a
new global static memory context specifically for storing the relation
entry attribute map? This memory context could be allocated and reset
during pgoutput_startup. This way, if a SQL call is made after an
error, the context will be reset, preventing memory bloat as addressed
in the attached patch. Thoughts?
Here is an updated version which includes registers to reset the
memory context that is in-line with a recently committed patch at [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=cfd6cbcf9be078fbdf9587014231a5772039b386.
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=cfd6cbcf9be078fbdf9587014231a5772039b386
Regards,
Vignesh
Attachments:
v2-0001-Fix-memory-leak-in-pgoutput-relation-attribute-ma.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Fix-memory-leak-in-pgoutput-relation-attribute-ma.patchDownload
From abf964263e5de78d44f6235f90789cfe5143b534 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Thu, 26 Dec 2024 11:37:43 +0530
Subject: [PATCH v2] Fix memory leak in pgoutput relation attribute map
The pgoutput module caches relation attribute map only when validating the
RelationSyncEntry. However, this was not getting freed incase of SQL functions
like pg_logical_slot_{get,peek}_changes() which would bloat its cache memory usage.
Every pg_logical_slot_{get,peek}_changes() call for changes on partition table
creates more bloat. To address this, this relation attribute map is allocated in
the plugin's cachectx which will be freed while the context is freed at
the end of pg_logical_slot_{get,peek}_changes() execution.
---
src/backend/replication/pgoutput/pgoutput.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 6db780d733..6c19cc1bb2 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1197,8 +1197,8 @@ init_tuple_slot(PGOutputData *data, Relation relation,
TupleDesc indesc = RelationGetDescr(relation);
TupleDesc outdesc = RelationGetDescr(ancestor);
- /* Map must live as long as the session does. */
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ /* Map must live as long as the logical decoding context. */
+ oldctx = MemoryContextSwitchTo(data->cachectx);
entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
--
2.43.0
v2_PG15-0001-Fix-memory-leak-in-pgoutput-relation-attribu.patchtext/x-patch; charset=US-ASCII; name=v2_PG15-0001-Fix-memory-leak-in-pgoutput-relation-attribu.patchDownload
From 8ebf0403ea4f972b8cc3e58fc153e38f0e1abc38 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Thu, 26 Dec 2024 12:05:33 +0530
Subject: [PATCH v2_PG15] Fix memory leak in pgoutput relation attribute map
The pgoutput module caches relation attribute map only when validating the
RelationSyncEntry. However, this was not getting freed incase of SQL functions
like pg_logical_slot_{get,peek}_changes() which would bloat its cache memory usage.
Every pg_logical_slot_{get,peek}_changes() call for changes on partition table
creates more bloat. To address this, this relation attribute map is allocated in
the plugin's cachectx which will be freed while the context is freed at
the end of pg_logical_slot_{get,peek}_changes() execution.
---
src/backend/replication/pgoutput/pgoutput.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index e178bd77ab..64f62de635 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1139,8 +1139,8 @@ init_tuple_slot(PGOutputData *data, Relation relation,
TupleDesc indesc = RelationGetDescr(relation);
TupleDesc outdesc = RelationGetDescr(ancestor);
- /* Map must live as long as the session does. */
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ /* Map must live as long as the logical decoding context. */
+ oldctx = MemoryContextSwitchTo(data->cachectx);
entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc);
--
2.43.0
v2_PG14-0001-Fix-memory-leak-in-pgoutput-relation-attribu.patchtext/x-patch; charset=US-ASCII; name=v2_PG14-0001-Fix-memory-leak-in-pgoutput-relation-attribu.patchDownload
From f1e2c053c50248595dc6539a606665336449e0e0 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Thu, 26 Dec 2024 15:11:09 +0530
Subject: [PATCH v2_PG14-] Fix memory leak in pgoutput relation attribute map
The pgoutput module caches relation attribute map and frees it upon
invalidation. However, this was not getting freed incase of SQL functions like
pg_logical_slot_{get,peek}_changes() which would bloat its cache memory usage.
Every pg_logical_slot_{get,peek}_changes() call for changes on partition table
creates more bloat. To address this, this commit adds a new memory context
within the logical decoding context. This ensures that the lifespan of the
relation entry attribute map aligns with that of the logical decoding context.
The context is tracked with a static variable whose state is reset with a
MemoryContext reset callback attached to PGOutputData->context, so as ABI
compatibility is preserved in stable branches.
---
src/backend/replication/pgoutput/pgoutput.c | 33 +++++++++++++++++++--
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 9fd879ee0c..577db5909d 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -72,6 +72,13 @@ static bool in_streaming;
*/
static MemoryContext pubctx = NULL;
+/*
+ * Private memory context for relation attribute map, created in
+ * PGOutputData->context when starting pgoutput, and set to NULL when its
+ * parent context is reset via a dedicated MemoryContextCallback.
+ */
+static MemoryContext cachectx = NULL;
+
static List *LoadPublications(List *pubnames);
static void publication_invalidation_cb(Datum arg, int cacheid,
uint32 hashvalue);
@@ -268,6 +275,15 @@ pgoutput_pubctx_reset_callback(void *arg)
pubctx = NULL;
}
+/*
+ * Callback of PGOutputData->context in charge of cleaning cachectx.
+ */
+static void
+pgoutput_cachectx_reset_callback(void *arg)
+{
+ cachectx = NULL;
+}
+
/*
* Initialize this plugin
*/
@@ -278,6 +294,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
PGOutputData *data = palloc0(sizeof(PGOutputData));
static bool publication_callback_registered = false;
MemoryContextCallback *mcallback;
+ MemoryContextCallback *cachectx_mcallback;
/* Create our memory context for private allocations. */
data->context = AllocSetContextCreate(ctx->context,
@@ -293,6 +310,15 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
mcallback->func = pgoutput_pubctx_reset_callback;
MemoryContextRegisterResetCallback(ctx->context, mcallback);
+ Assert(cachectx == NULL);
+ cachectx = AllocSetContextCreate(ctx->context,
+ "logical replication cache context",
+ ALLOCSET_SMALL_SIZES);
+
+ cachectx_mcallback = palloc0(sizeof(MemoryContextCallback));
+ cachectx_mcallback->func = pgoutput_cachectx_reset_callback;
+ MemoryContextRegisterResetCallback(ctx->context, cachectx_mcallback);
+
ctx->output_plugin_private = data;
/* This plugin uses binary protocol. */
@@ -490,7 +516,7 @@ maybe_send_schema(LogicalDecodingContext *ctx,
MemoryContext oldctx;
/* Map must live as long as the session does. */
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ oldctx = MemoryContextSwitchTo(cachectx);
/*
* Make copies of the TupleDescs that will live as long as the map
@@ -812,8 +838,8 @@ pgoutput_origin_filter(LogicalDecodingContext *ctx,
/*
* Shutdown the output plugin.
*
- * Note, we don't need to clean the data->context and pubctx as they are
- * child contexts of the ctx->context so they will be cleaned up by logical
+ * Note, we don't need to clean the data->context, pubctx, and cachectx as they
+ * are child contexts of the ctx->context so they will be cleaned up by logical
* decoding machinery.
*/
static void
@@ -827,6 +853,7 @@ pgoutput_shutdown(LogicalDecodingContext *ctx)
/* Better safe than sorry */
pubctx = NULL;
+ cachectx = NULL;
}
/*
--
2.43.0
On Thursday, December 26, 2024 8:01 PM vignesh C <vignesh21@gmail.com> wrote:
Here is an updated version which includes registers to reset the memory
context that is in-line with a recently committed patch at [1].
Thanks for updating patches ! They look good to me.
Just to confirm, would the other stuff (streamed_txns) that allocated under
CacheMemoryContext also leaks memory ? I think it's OK to change them
separately if it does but just would like to confirm if there is a risk.
[1] -
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=cfd6cbc
f9be078fbdf9587014231a5772039b386
Best Regards,
Hou zj
On Fri, Dec 27, 2024 at 08:13:53AM +0000, Zhijie Hou (Fujitsu) wrote:
Thanks for updating patches ! They look good to me.
Fine by me as well. I had a bit of time today, so I've taken care of
all this one down to 15 for now after checking each branch.
+ cachectx_mcallback = palloc0(sizeof(MemoryContextCallback));
+ cachectx_mcallback->func = pgoutput_cachectx_reset_callback;
+ MemoryContextRegisterResetCallback(ctx->context, cachectx_mcallback);
In the version of the patch for 14 and 13, why don't you just use a
single reset callback to handle both of cachectx and pubctx at the
same time? That would be simpler.
+/*
+ * Private memory context for relation attribute map, created in
+ * PGOutputData->context when starting pgoutput, and set to NULL when its
+ * parent context is reset via a dedicated MemoryContextCallback.
+ */
+static MemoryContext cachectx = NULL;
This comment block is a copy-paste of the previous one, let's just
stick both declarations together.
Just to confirm, would the other stuff (streamed_txns) that allocated under
CacheMemoryContext also leaks memory ? I think it's OK to change them
separately if it does but just would like to confirm if there is a risk.
Yeah, let's tackle this stuff separately and remove more the
dependency to CacheMemoryContext.
--
Michael
On Mon, 30 Dec 2024 at 10:12, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Dec 27, 2024 at 08:13:53AM +0000, Zhijie Hou (Fujitsu) wrote:
Thanks for updating patches ! They look good to me.
Fine by me as well. I had a bit of time today, so I've taken care of
all this one down to 15 for now after checking each branch.
Thanks for pushing this.
+ cachectx_mcallback = palloc0(sizeof(MemoryContextCallback)); + cachectx_mcallback->func = pgoutput_cachectx_reset_callback; + MemoryContextRegisterResetCallback(ctx->context, cachectx_mcallback);In the version of the patch for 14 and 13, why don't you just use a
single reset callback to handle both of cachectx and pubctx at the
same time? That would be simpler.
Modified
+/* + * Private memory context for relation attribute map, created in + * PGOutputData->context when starting pgoutput, and set to NULL when its + * parent context is reset via a dedicated MemoryContextCallback. + */ +static MemoryContext cachectx = NULL;This comment block is a copy-paste of the previous one, let's just
stick both declarations together.
Modified
Just to confirm, would the other stuff (streamed_txns) that allocated under
CacheMemoryContext also leaks memory ? I think it's OK to change them
separately if it does but just would like to confirm if there is a risk.Yeah, let's tackle this stuff separately and remove more the
dependency to CacheMemoryContext.
+1
The attached v3 version has the changes for the same. I have verified
the patch in PG14 and PG13 by attaching to the debugger and calling
"call MemoryContextStats(CacheMemoryContext)" to see there are no
leaks.
Regards,
Vignesh
Attachments:
v3_PG14-0001-Fix-memory-leak-in-pgoutput-relation-attribu.patchtext/x-patch; charset=US-ASCII; name=v3_PG14-0001-Fix-memory-leak-in-pgoutput-relation-attribu.patchDownload
From f41601e0df8e05e3e7d92aa7708f6e0f8ecfc034 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Thu, 26 Dec 2024 15:11:09 +0530
Subject: [PATCH v3_PG14] Fix memory leak in pgoutput relation attribute map
The pgoutput module caches relation attribute map and frees it upon
invalidation. However, this was not getting freed incase of SQL functions like
pg_logical_slot_{get,peek}_changes() which would bloat its cache memory usage.
Every pg_logical_slot_{get,peek}_changes() call for changes on partition table
creates more bloat. To address this, this commit adds a new memory context
within the logical decoding context. This ensures that the lifespan of the
relation entry attribute map aligns with that of the logical decoding context.
The context is tracked with a static variable whose state is reset with a
MemoryContext reset callback attached to PGOutputData->context, so as ABI
compatibility is preserved in stable branches.
---
src/backend/replication/pgoutput/pgoutput.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 9fd879ee0c..32263daa66 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -71,6 +71,7 @@ static bool in_streaming;
* parent context is reset via a dedicated MemoryContextCallback.
*/
static MemoryContext pubctx = NULL;
+static MemoryContext cachectx = NULL;
static List *LoadPublications(List *pubnames);
static void publication_invalidation_cb(Datum arg, int cacheid,
@@ -260,12 +261,13 @@ parse_output_parameters(List *options, PGOutputData *data)
}
/*
- * Callback of PGOutputData->context in charge of cleaning pubctx.
+ * Callback of PGOutputData->context in charge of cleaning pubctx and cachectx.
*/
static void
-pgoutput_pubctx_reset_callback(void *arg)
+pgoutput_ctx_reset_callback(void *arg)
{
pubctx = NULL;
+ cachectx = NULL;
}
/*
@@ -289,8 +291,13 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
"logical replication publication list context",
ALLOCSET_SMALL_SIZES);
+ Assert(cachectx == NULL);
+ cachectx = AllocSetContextCreate(ctx->context,
+ "logical replication cache context",
+ ALLOCSET_SMALL_SIZES);
+
mcallback = palloc0(sizeof(MemoryContextCallback));
- mcallback->func = pgoutput_pubctx_reset_callback;
+ mcallback->func = pgoutput_ctx_reset_callback;
MemoryContextRegisterResetCallback(ctx->context, mcallback);
ctx->output_plugin_private = data;
@@ -490,7 +497,7 @@ maybe_send_schema(LogicalDecodingContext *ctx,
MemoryContext oldctx;
/* Map must live as long as the session does. */
- oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+ oldctx = MemoryContextSwitchTo(cachectx);
/*
* Make copies of the TupleDescs that will live as long as the map
@@ -812,8 +819,8 @@ pgoutput_origin_filter(LogicalDecodingContext *ctx,
/*
* Shutdown the output plugin.
*
- * Note, we don't need to clean the data->context and pubctx as they are
- * child contexts of the ctx->context so they will be cleaned up by logical
+ * Note, we don't need to clean the data->context, pubctx, and cachectx as they
+ * are child contexts of the ctx->context so they will be cleaned up by logical
* decoding machinery.
*/
static void
@@ -827,6 +834,7 @@ pgoutput_shutdown(LogicalDecodingContext *ctx)
/* Better safe than sorry */
pubctx = NULL;
+ cachectx = NULL;
}
/*
--
2.43.0
On Mon, Dec 30, 2024 at 10:31:20PM +0530, vignesh C wrote:
The attached v3 version has the changes for the same. I have verified
the patch in PG14 and PG13 by attaching to the debugger and calling
"call MemoryContextStats(CacheMemoryContext)" to see there are no
leaks.
Sorry for the delay here, I have come back into the business.
I have tuned a bit the comment block on top of the declaration of the
two contexts, fixed a comment in maybe_send_schema() that was now
inexact, and applied that down to v13 and v14.
--
Michael