show size of DSAs and dshash tables in pg_dsm_registry_allocations

Started by Nathan Bossartabout 2 months ago9 messages
#1Nathan Bossart
nathandbossart@gmail.com
1 attachment(s)

On Tue, Nov 25, 2025 at 07:13:03PM -0500, Robert Haas wrote:

In my ideal world, it would probably show partially-initialized entires
in some distinguishable way, like with a null size.

I reevaluated this view, and I think we can do what you suggest here.
Right now, we show NULL for DSAs and dshash tables because 1) we might not
be attached to them and 2) we don't have a pointer to the dsa_area * or
dshash_table * in local memory. If we introduce a function that looks up
the size of a DSA given its handle (transiently attaching to the control
segment if needed), we can show the size for all successfully-initialized
entries. Then, we can make NULL mean that the entry was
partially-initialized.

Patch attached.

--
nathan

Attachments:

v1-0001-rework-DSM-registry-view.patchtext/plain; charset=us-asciiDownload
From fc5f0d4eea19df142032c8da4110b1e8aade59f0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 26 Nov 2025 16:48:13 -0600
Subject: [PATCH v1 1/1] rework DSM registry view

---
 doc/src/sgml/system-views.sgml                |  4 +--
 src/backend/storage/ipc/dsm_registry.c        | 17 ++++-----
 src/backend/utils/mmgr/dsa.c                  | 35 +++++++++++++++++++
 src/include/utils/dsa.h                       |  1 +
 .../expected/test_dsm_registry.out            |  4 +--
 .../sql/test_dsm_registry.sql                 |  4 +--
 6 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0e623e7fb86..3d083128a4b 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -1150,8 +1150,8 @@ AND c1.path[c2.level] = c2.path[c2.level];
        <structfield>size</structfield> <type>int8</type>
       </para>
       <para>
-       Size of the allocation in bytes.  NULL for entries of type
-       <literal>area</literal> and <literal>hash</literal>.
+       Size of the allocation in bytes.  NULL for entries that failed
+       initialization.
       </para></entry>
      </row>
     </tbody>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index ef6533b1100..4e327f69789 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -463,17 +463,6 @@ pg_get_dsm_registry_allocations(PG_FUNCTION_ARGS)
 		Datum		vals[3];
 		bool		nulls[3] = {0};
 
-		/* Do not show partially-initialized entries. */
-		if (entry->type == DSMR_ENTRY_TYPE_DSM &&
-			entry->dsm.handle == DSM_HANDLE_INVALID)
-			continue;
-		if (entry->type == DSMR_ENTRY_TYPE_DSA &&
-			entry->dsa.handle == DSA_HANDLE_INVALID)
-			continue;
-		if (entry->type == DSMR_ENTRY_TYPE_DSH &&
-			entry->dsh.dsa_handle == DSA_HANDLE_INVALID)
-			continue;
-
 		vals[0] = CStringGetTextDatum(entry->name);
 		vals[1] = CStringGetTextDatum(DSMREntryTypeNames[entry->type]);
 
@@ -483,6 +472,12 @@ pg_get_dsm_registry_allocations(PG_FUNCTION_ARGS)
 		 */
 		if (entry->type == DSMR_ENTRY_TYPE_DSM)
 			vals[2] = Int64GetDatum(entry->dsm.size);
+		else if (entry->type == DSMR_ENTRY_TYPE_DSA &&
+				 entry->dsa.handle != DSA_HANDLE_INVALID)
+			vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsa.handle));
+		else if (entry->type == DSMR_ENTRY_TYPE_DSH &&
+				 entry->dsh.dsa_handle !=DSA_HANDLE_INVALID)
+			vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsh.dsa_handle));
 		else
 			nulls[2] = true;
 
diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index be43e9351c3..030f9463712 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -1050,6 +1050,41 @@ dsa_get_total_size(dsa_area *area)
 	return size;
 }
 
+/*
+ * Same as dsa_get_total_size(), but accepts a DSA handle.  The area must have
+ * been created with dsa_create (not dsa_create_in_place).
+ */
+size_t
+dsa_get_total_size_from_handle(dsa_handle handle)
+{
+	size_t		size;
+	bool		already_attached;
+	dsm_segment *segment;
+	dsa_area_control *control;
+
+	already_attached = dsa_is_attached(handle);
+	if (already_attached)
+		segment = dsm_find_mapping(handle);
+	else
+		segment = dsm_attach(handle);
+
+	if (segment == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("could not attach to dynamic shared area")));
+
+	control = (dsa_area_control *) dsm_segment_address(segment);
+
+	LWLockAcquire(&control->lock, LW_SHARED);
+	size = control->total_segment_size;
+	LWLockRelease(&control->lock);
+
+	if (!already_attached)
+		dsm_detach(segment);
+
+	return size;
+}
+
 /*
  * Aggressively free all spare memory in the hope of returning DSM segments to
  * the operating system.
diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h
index f2104dacbfc..42449ff22de 100644
--- a/src/include/utils/dsa.h
+++ b/src/include/utils/dsa.h
@@ -161,6 +161,7 @@ extern dsa_pointer dsa_allocate_extended(dsa_area *area, size_t size, int flags)
 extern void dsa_free(dsa_area *area, dsa_pointer dp);
 extern void *dsa_get_address(dsa_area *area, dsa_pointer dp);
 extern size_t dsa_get_total_size(dsa_area *area);
+extern size_t dsa_get_total_size_from_handle(dsa_handle handle);
 extern void dsa_trim(dsa_area *area);
 extern void dsa_dump(dsa_area *area);
 
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
index ca8abbb377e..75d9eda0756 100644
--- a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -1,4 +1,4 @@
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
  name | type | size 
@@ -32,7 +32,7 @@ SELECT get_val_in_hash('test');
 (1 row)
 
 \c
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
           name          |  type   | size 
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
index 965a3f1ebb6..1b9ee3ebf18 100644
--- a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -1,4 +1,4 @@
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
 CREATE EXTENSION test_dsm_registry;
@@ -8,6 +8,6 @@ SELECT set_val_in_hash('test', '1414');
 SELECT get_val_in_shmem();
 SELECT get_val_in_hash('test');
 \c
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
-- 
2.39.5 (Apple Git-154)

#2Rahila Syed
rahilasyed90@gmail.com
In reply to: Nathan Bossart (#1)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

Hi,

Thank you for this improvement. I think it will be very helpful to have the
ability to
report dsa and dshash memory sizes.

Please find below a few comments.

1. As a result of this change, the following comment
in pg_get_dsm_registry_allocations
is incorrect.
/*
* Since we can't know the size of DSA/dshash entries
without first
* attaching to them, return NULL for those.
*/

2. +
+ LWLockAcquire(&control->lock, LW_SHARED);

The dsa_get_total_size function takes LW_EXCLUSIVE lock for the same
purpose.
I wonder why that is the case and whether both should be consistent.

3. +/*
+ * Same as dsa_get_total_size(), but accepts a DSA handle.  The area must
have
+ * been created with dsa_create (not dsa_create_in_place).
+ */
+size_t
+dsa_get_total_size_from_handle(dsa_handle handle)

I believe this function will report the size as long as the dsa control
structure is
created within a dsm segment, since all dsm segments are tracked by the
global list
- dsm_segment_list, regardless of whether the dsa is created with
dsa_create or
dsa_create_in_place. In that case, perhaps we should update the comment
above to reflect this.

4. Since, with this change, the size column will show memory allocation
regardless
of whether it is currently mapped in the local process, I think it would be
helpful to add a
boolean column to display the mapped status as a future enhancement.

Thank you,
Rahila Syed

#3Nathan Bossart
nathandbossart@gmail.com
In reply to: Rahila Syed (#2)
1 attachment(s)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

On Thu, Nov 27, 2025 at 01:51:39PM +0530, Rahila Syed wrote:

Thank you for this improvement. I think it will be very helpful to have
the ability to report dsa and dshash memory sizes.

Thanks for reviewing!

1. As a result of this change, the following comment in
pg_get_dsm_registry_allocations is incorrect.

Fixed.

+ LWLockAcquire(&control->lock, LW_SHARED);

The dsa_get_total_size function takes LW_EXCLUSIVE lock for the same
purpose. I wonder why that is the case and whether both should be
consistent.

That function was added by commit ee1b30f, which AFAICT used an exclusive
lock just to stay consistent with the rest of dsa.c [0]/messages/by-id/CAD21AoDoWrbNf+K2Fwg2n=CZDHigjkndwqy_86BGgXBp9Kbq4Q@mail.gmail.com. I don't see any
discussion about this in the original DSA thread [1]/messages/by-id/CAEepm=1z5WLuNoJ80PaCvz6EtG9dN0j-KuHcHtU6QEfcPP5-qA@mail.gmail.com. Perhaps we could go
through dsa.c and switch to LW_SHARED where appropriate, although I doubt
it makes much difference.

+size_t
+dsa_get_total_size_from_handle(dsa_handle handle)

I believe this function will report the size as long as the dsa control
structure is created within a dsm segment, since all dsm segments are
tracked by the global list - dsm_segment_list, regardless of whether the
dsa is created with dsa_create or dsa_create_in_place. In that case,
perhaps we should update the comment above to reflect this.

Sorry, I'm not following what you think we should update the comment to
say.

4. Since, with this change, the size column will show memory allocation
regardless of whether it is currently mapped in the local process, I
think it would be helpful to add a boolean column to display the mapped
status as a future enhancement.

Maybe, although I'm struggling to think of a scenario where that
information would be useful.

[0]: /messages/by-id/CAD21AoDoWrbNf+K2Fwg2n=CZDHigjkndwqy_86BGgXBp9Kbq4Q@mail.gmail.com
[1]: /messages/by-id/CAEepm=1z5WLuNoJ80PaCvz6EtG9dN0j-KuHcHtU6QEfcPP5-qA@mail.gmail.com

--
nathan

Attachments:

v2-0001-rework-DSM-registry-view.patchtext/plain; charset=us-asciiDownload
From df18d34c52f0f744995c4f01b3b05205ed01b535 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 26 Nov 2025 16:48:13 -0600
Subject: [PATCH v2 1/1] rework DSM registry view

---
 doc/src/sgml/system-views.sgml                |  4 +--
 src/backend/storage/ipc/dsm_registry.c        | 22 ++++--------
 src/backend/utils/mmgr/dsa.c                  | 35 +++++++++++++++++++
 src/include/utils/dsa.h                       |  1 +
 .../expected/test_dsm_registry.out            |  4 +--
 .../sql/test_dsm_registry.sql                 |  4 +--
 6 files changed, 49 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 7db8f73eba2..162c76b729a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -1150,8 +1150,8 @@ AND c1.path[c2.level] = c2.path[c2.level];
        <structfield>size</structfield> <type>int8</type>
       </para>
       <para>
-       Size of the allocation in bytes.  NULL for entries of type
-       <literal>area</literal> and <literal>hash</literal>.
+       Size of the allocation in bytes.  NULL for entries that failed
+       initialization.
       </para></entry>
      </row>
     </tbody>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index ef6533b1100..0f3e08c44d8 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -463,26 +463,18 @@ pg_get_dsm_registry_allocations(PG_FUNCTION_ARGS)
 		Datum		vals[3];
 		bool		nulls[3] = {0};
 
-		/* Do not show partially-initialized entries. */
-		if (entry->type == DSMR_ENTRY_TYPE_DSM &&
-			entry->dsm.handle == DSM_HANDLE_INVALID)
-			continue;
-		if (entry->type == DSMR_ENTRY_TYPE_DSA &&
-			entry->dsa.handle == DSA_HANDLE_INVALID)
-			continue;
-		if (entry->type == DSMR_ENTRY_TYPE_DSH &&
-			entry->dsh.dsa_handle == DSA_HANDLE_INVALID)
-			continue;
-
 		vals[0] = CStringGetTextDatum(entry->name);
 		vals[1] = CStringGetTextDatum(DSMREntryTypeNames[entry->type]);
 
-		/*
-		 * Since we can't know the size of DSA/dshash entries without first
-		 * attaching to them, return NULL for those.
-		 */
+		/* Be careful to only return the sizes of initialized entries. */
 		if (entry->type == DSMR_ENTRY_TYPE_DSM)
 			vals[2] = Int64GetDatum(entry->dsm.size);
+		else if (entry->type == DSMR_ENTRY_TYPE_DSA &&
+				 entry->dsa.handle != DSA_HANDLE_INVALID)
+			vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsa.handle));
+		else if (entry->type == DSMR_ENTRY_TYPE_DSH &&
+				 entry->dsh.dsa_handle !=DSA_HANDLE_INVALID)
+			vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsh.dsa_handle));
 		else
 			nulls[2] = true;
 
diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index be43e9351c3..030f9463712 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -1050,6 +1050,41 @@ dsa_get_total_size(dsa_area *area)
 	return size;
 }
 
+/*
+ * Same as dsa_get_total_size(), but accepts a DSA handle.  The area must have
+ * been created with dsa_create (not dsa_create_in_place).
+ */
+size_t
+dsa_get_total_size_from_handle(dsa_handle handle)
+{
+	size_t		size;
+	bool		already_attached;
+	dsm_segment *segment;
+	dsa_area_control *control;
+
+	already_attached = dsa_is_attached(handle);
+	if (already_attached)
+		segment = dsm_find_mapping(handle);
+	else
+		segment = dsm_attach(handle);
+
+	if (segment == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("could not attach to dynamic shared area")));
+
+	control = (dsa_area_control *) dsm_segment_address(segment);
+
+	LWLockAcquire(&control->lock, LW_SHARED);
+	size = control->total_segment_size;
+	LWLockRelease(&control->lock);
+
+	if (!already_attached)
+		dsm_detach(segment);
+
+	return size;
+}
+
 /*
  * Aggressively free all spare memory in the hope of returning DSM segments to
  * the operating system.
diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h
index f2104dacbfc..42449ff22de 100644
--- a/src/include/utils/dsa.h
+++ b/src/include/utils/dsa.h
@@ -161,6 +161,7 @@ extern dsa_pointer dsa_allocate_extended(dsa_area *area, size_t size, int flags)
 extern void dsa_free(dsa_area *area, dsa_pointer dp);
 extern void *dsa_get_address(dsa_area *area, dsa_pointer dp);
 extern size_t dsa_get_total_size(dsa_area *area);
+extern size_t dsa_get_total_size_from_handle(dsa_handle handle);
 extern void dsa_trim(dsa_area *area);
 extern void dsa_dump(dsa_area *area);
 
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
index ca8abbb377e..75d9eda0756 100644
--- a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -1,4 +1,4 @@
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
  name | type | size 
@@ -32,7 +32,7 @@ SELECT get_val_in_hash('test');
 (1 row)
 
 \c
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
           name          |  type   | size 
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
index 965a3f1ebb6..1b9ee3ebf18 100644
--- a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -1,4 +1,4 @@
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
 CREATE EXTENSION test_dsm_registry;
@@ -8,6 +8,6 @@ SELECT set_val_in_hash('test', '1414');
 SELECT get_val_in_shmem();
 SELECT get_val_in_hash('test');
 \c
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
-- 
2.39.5 (Apple Git-154)

#4Robert Haas
robertmhaas@gmail.com
In reply to: Nathan Bossart (#3)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

On Mon, Dec 1, 2025 at 10:29 AM Nathan Bossart <nathandbossart@gmail.com> wrote:

4. Since, with this change, the size column will show memory allocation
regardless of whether it is currently mapped in the local process, I
think it would be helpful to add a boolean column to display the mapped
status as a future enhancement.

Maybe, although I'm struggling to think of a scenario where that
information would be useful.

I'd be -1 on that idea. I think it's good to keep this as a view of
the global system state, rather than a view of the state of one
particular process.

The patch itself LGTM. I did a casual review only and did not attempt
to verify that it works properly, but I like both the idea and the
execution.

--
Robert Haas
EDB: http://www.enterprisedb.com

#5Nathan Bossart
nathandbossart@gmail.com
In reply to: Robert Haas (#4)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

On Mon, Dec 01, 2025 at 10:58:55AM -0500, Robert Haas wrote:

The patch itself LGTM. I did a casual review only and did not attempt
to verify that it works properly, but I like both the idea and the
execution.

Thanks for reviewing. Once Rahila is happy with the patch, I will commit
it.

--
nathan

#6Chao Li
li.evan.chao@gmail.com
In reply to: Nathan Bossart (#3)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

Hi Nathan,

V2 overall looks good to me. I just got a question and a few nit comments.

On Dec 1, 2025, at 23:29, Nathan Bossart <nathandbossart@gmail.com> wrote:

--
nathan
<v2-0001-rework-DSM-registry-view.patch>

1 - dsa.c
```
+size_t
+dsa_get_total_size_from_handle(dsa_handle handle)
+{
+	size_t		size;
+	bool		already_attached;
+	dsm_segment *segment;
+	dsa_area_control *control;
+
+	already_attached = dsa_is_attached(handle);
+	if (already_attached)
+		segment = dsm_find_mapping(handle);
+	else
+		segment = dsm_attach(handle);
+
+	if (segment == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("could not attach to dynamic shared area")));
```

Why do you error out instead of reporting 0/NULL usage here? When users call this function as shown in the test script:
```
CREATE VIEW pg_dsm_registry_allocations AS
SELECT * FROM pg_get_dsm_registry_allocations();
```

User doesn’t pass in a handle, if the DSA has been released, it should treat a missing mapping as “no size available” and maybe return NULL.

2
```
+		else if (entry->type == DSMR_ENTRY_TYPE_DSH &&
+				 entry->dsh.dsa_handle !=DSA_HANDLE_INVALID)
```

Missing a white space after !=.

3
```
-       Size of the allocation in bytes.  NULL for entries of type
-       <literal>area</literal> and <literal>hash</literal>.
+       Size of the allocation in bytes.  NULL for entries that failed
+       initialization.
```

“Failed initialization”, I guess “to” is needed after “failed”.

4
```
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
```

As you changed the third column from “size” to “size > 0”, now it’s a bool column, so maybe rename the column alias from “size” to something indicating a bool type, such as “size_ok”, “has_size”, etc.

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

#7Nathan Bossart
nathandbossart@gmail.com
In reply to: Chao Li (#6)
1 attachment(s)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

On Tue, Dec 02, 2025 at 05:35:34AM +0800, Chao Li wrote:

V2 overall looks good to me. I just got a question and a few nit comments.

Thanks for reviewing.

Why do you error out instead of reporting 0/NULL usage here?

If attaching to the control segment fails, something has gone horribly
wrong. I don't think returning 0 for the size is appropriate in that case.
Also note that other functions in this file ERROR when attaching fails
(e.g., dsa_attach()).

```
+		else if (entry->type == DSMR_ENTRY_TYPE_DSH &&
+				 entry->dsh.dsa_handle !=DSA_HANDLE_INVALID)
```

Missing a white space after !=.

I agree, but for some reason, pgindent insists on removing that space. I'm
leaving that for another thread.

```
-       Size of the allocation in bytes.  NULL for entries of type
-       <literal>area</literal> and <literal>hash</literal>.
+       Size of the allocation in bytes.  NULL for entries that failed
+       initialization.
```

“Failed initialization”, I guess “to” is needed after “failed”.

IMHO it's fine as-is. We could say "failed to initialize", but I'm not
sure that's materially better at conveying the information.

```
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size
```

As you changed the third column from “size” to “size > 0”, now it’s a
bool column, so maybe rename the column alias from “size” to something
indicating a bool type, such as “size_ok”, “has_size”, etc.

It was already a bool column, but your point still stands. I changed it to
"size_ok".

--
nathan

Attachments:

v3-0001-rework-DSM-registry-view.patchtext/plain; charset=us-asciiDownload
From f28e45a16271265a7d79b4e00594b5a515909df0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 26 Nov 2025 16:48:13 -0600
Subject: [PATCH v3 1/1] rework DSM registry view

---
 doc/src/sgml/system-views.sgml                |  4 +--
 src/backend/storage/ipc/dsm_registry.c        | 22 ++++--------
 src/backend/utils/mmgr/dsa.c                  | 35 +++++++++++++++++++
 src/include/utils/dsa.h                       |  1 +
 .../expected/test_dsm_registry.out            | 12 +++----
 .../sql/test_dsm_registry.sql                 |  4 +--
 6 files changed, 53 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 7db8f73eba2..162c76b729a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -1150,8 +1150,8 @@ AND c1.path[c2.level] = c2.path[c2.level];
        <structfield>size</structfield> <type>int8</type>
       </para>
       <para>
-       Size of the allocation in bytes.  NULL for entries of type
-       <literal>area</literal> and <literal>hash</literal>.
+       Size of the allocation in bytes.  NULL for entries that failed
+       initialization.
       </para></entry>
      </row>
     </tbody>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index ef6533b1100..0f3e08c44d8 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -463,26 +463,18 @@ pg_get_dsm_registry_allocations(PG_FUNCTION_ARGS)
 		Datum		vals[3];
 		bool		nulls[3] = {0};
 
-		/* Do not show partially-initialized entries. */
-		if (entry->type == DSMR_ENTRY_TYPE_DSM &&
-			entry->dsm.handle == DSM_HANDLE_INVALID)
-			continue;
-		if (entry->type == DSMR_ENTRY_TYPE_DSA &&
-			entry->dsa.handle == DSA_HANDLE_INVALID)
-			continue;
-		if (entry->type == DSMR_ENTRY_TYPE_DSH &&
-			entry->dsh.dsa_handle == DSA_HANDLE_INVALID)
-			continue;
-
 		vals[0] = CStringGetTextDatum(entry->name);
 		vals[1] = CStringGetTextDatum(DSMREntryTypeNames[entry->type]);
 
-		/*
-		 * Since we can't know the size of DSA/dshash entries without first
-		 * attaching to them, return NULL for those.
-		 */
+		/* Be careful to only return the sizes of initialized entries. */
 		if (entry->type == DSMR_ENTRY_TYPE_DSM)
 			vals[2] = Int64GetDatum(entry->dsm.size);
+		else if (entry->type == DSMR_ENTRY_TYPE_DSA &&
+				 entry->dsa.handle != DSA_HANDLE_INVALID)
+			vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsa.handle));
+		else if (entry->type == DSMR_ENTRY_TYPE_DSH &&
+				 entry->dsh.dsa_handle !=DSA_HANDLE_INVALID)
+			vals[2] = Int64GetDatum(dsa_get_total_size_from_handle(entry->dsh.dsa_handle));
 		else
 			nulls[2] = true;
 
diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index be43e9351c3..030f9463712 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -1050,6 +1050,41 @@ dsa_get_total_size(dsa_area *area)
 	return size;
 }
 
+/*
+ * Same as dsa_get_total_size(), but accepts a DSA handle.  The area must have
+ * been created with dsa_create (not dsa_create_in_place).
+ */
+size_t
+dsa_get_total_size_from_handle(dsa_handle handle)
+{
+	size_t		size;
+	bool		already_attached;
+	dsm_segment *segment;
+	dsa_area_control *control;
+
+	already_attached = dsa_is_attached(handle);
+	if (already_attached)
+		segment = dsm_find_mapping(handle);
+	else
+		segment = dsm_attach(handle);
+
+	if (segment == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("could not attach to dynamic shared area")));
+
+	control = (dsa_area_control *) dsm_segment_address(segment);
+
+	LWLockAcquire(&control->lock, LW_SHARED);
+	size = control->total_segment_size;
+	LWLockRelease(&control->lock);
+
+	if (!already_attached)
+		dsm_detach(segment);
+
+	return size;
+}
+
 /*
  * Aggressively free all spare memory in the hope of returning DSM segments to
  * the operating system.
diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h
index f2104dacbfc..42449ff22de 100644
--- a/src/include/utils/dsa.h
+++ b/src/include/utils/dsa.h
@@ -161,6 +161,7 @@ extern dsa_pointer dsa_allocate_extended(dsa_area *area, size_t size, int flags)
 extern void dsa_free(dsa_area *area, dsa_pointer dp);
 extern void *dsa_get_address(dsa_area *area, dsa_pointer dp);
 extern size_t dsa_get_total_size(dsa_area *area);
+extern size_t dsa_get_total_size_from_handle(dsa_handle handle);
 extern void dsa_trim(dsa_area *area);
 extern void dsa_dump(dsa_area *area);
 
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
index ca8abbb377e..9128e171b1b 100644
--- a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -1,8 +1,8 @@
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size_ok
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
- name | type | size 
-------+------+------
+ name | type | size_ok 
+------+------+---------
 (0 rows)
 
 CREATE EXTENSION test_dsm_registry;
@@ -32,11 +32,11 @@ SELECT get_val_in_hash('test');
 (1 row)
 
 \c
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size_ok
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
-          name          |  type   | size 
-------------------------+---------+------
+          name          |  type   | size_ok 
+------------------------+---------+---------
  test_dsm_registry_dsa  | area    | t
  test_dsm_registry_dsm  | segment | t
  test_dsm_registry_hash | hash    | t
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
index 965a3f1ebb6..a606e8872a1 100644
--- a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -1,4 +1,4 @@
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size_ok
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
 CREATE EXTENSION test_dsm_registry;
@@ -8,6 +8,6 @@ SELECT set_val_in_hash('test', '1414');
 SELECT get_val_in_shmem();
 SELECT get_val_in_hash('test');
 \c
-SELECT name, type, size IS DISTINCT FROM 0 AS size
+SELECT name, type, size > 0 AS size_ok
 FROM pg_dsm_registry_allocations
 WHERE name like 'test_dsm_registry%' ORDER BY name;
-- 
2.39.5 (Apple Git-154)

#8Rahila Syed
rahilasyed90@gmail.com
In reply to: Nathan Bossart (#3)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

Hi,

The patch LGTM overall. I had tested the v1 and it worked fine.

That function was added by commit ee1b30f, which AFAICT used an exclusive
lock just to stay consistent with the rest of dsa.c [0]. I don't see any
discussion about this in the original DSA thread [1]. Perhaps we could go
through dsa.c and switch to LW_SHARED where appropriate, although I doubt
it makes much difference.

Thank you for highlighting the discussions. I'm unsure about the best
approach here, but I think it would be safe to stay consistent with the
rest of the code in dsa.c, especially since it's unclear that the use of
LW_EXCLUSIVE for reading values in dsa is a mistake.

+size_t
+dsa_get_total_size_from_handle(dsa_handle handle)

I believe this function will report the size as long as the dsa control
structure is created within a dsm segment, since all dsm segments are
tracked by the global list - dsm_segment_list, regardless of whether the
dsa is created with dsa_create or dsa_create_in_place. In that case,
perhaps we should update the comment above to reflect this.

Sorry, I'm not following what you think we should update the comment to
say.

Sorry for the confusion, I am trying to say that we can change the
following comment

+ *The area must have
+ * been created with dsa_create (not dsa_create_in_place).

to say this:

"The area must have been created using dsm_segments"

Since, this function can report the size of an area created with
dsa_create_in_place
too, as long as the area is created using dsm_segments.

4. Since, with this change, the size column will show memory allocation
regardless of whether it is currently mapped in the local process, I
think it would be helpful to add a boolean column to display the mapped
status as a future enhancement.

Maybe, although I'm struggling to think of a scenario where that
information would be useful.

Fair enough. I was thinking of a scenario where a user might want
to see how much dsa memory is allocated in the client backend process.
However, I understand now that this view is designed for the entire cluster,
and adding a column which is process-specific could lead to confusion.

Thank you,
Rahila Syed

#9Nathan Bossart
nathandbossart@gmail.com
In reply to: Rahila Syed (#8)
Re: show size of DSAs and dshash tables in pg_dsm_registry_allocations

On Tue, Dec 02, 2025 at 02:28:29PM +0530, Rahila Syed wrote:

Thank you for highlighting the discussions. I'm unsure about the best
approach here, but I think it would be safe to stay consistent with the
rest of the code in dsa.c, especially since it's unclear that the use of
LW_EXCLUSIVE for reading values in dsa is a mistake.

Okay. I switched to LW_EXCLUSIVE and will consider starting a new thread
to use LW_SHARED when possible in dsa.c.

Sorry for the confusion, I am trying to say that we can change the
following comment

+ *The area must have
+ * been created with dsa_create (not dsa_create_in_place).

to say this:

"The area must have been created using dsm_segments"

Since, this function can report the size of an area created with
dsa_create_in_place too, as long as the area is created using
dsm_segments.

It cannot report the size of in-place areas, at least not without some
changes, because (AFAICT) there's no way to get a dsa_handle for an
in-place segment.

I've now committed the patch. Thanks everyone for reviewing!

--
nathan