Thread
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Cross-check lists of built-in LWLock tranches.
- 2047ad068139 19 (unreleased) cited
-
Convert strncpy to strlcpy
- 45e7e8ca9e93 18.0 landed
-
Typo and doc fixups for memory context reporting
- f8c115a6cb65 18.0 landed
-
Add missing string terminator
- d2a1ed1727a8 18.0 landed
-
Rename argument in pg_get_process_memory_contexts().
- c57971034e69 18.0 landed
-
Add function to get memory context stats for processes
- 042a66291b04 18.0 landed
-
Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-10-21T18:24:21Z
Hi, PostgreSQL provides following capabilities for reporting memory contexts statistics. 1. pg_get_backend_memory_contexts(); [1] 2. pg_log_backend_memory_contexts(pid); [2] [1] provides a view of memory context statistics for a local backend, while [2] prints the memory context statistics of any backend or auxiliary process to the PostgreSQL logs. Although [1] offers detailed statistics, it is limited to the local backend, restricting its use to PostgreSQL client backends only. On the other hand, [2] provides the statistics for all backends but logs them in a file, which may not be convenient for quick access. I propose enhancing memory context statistics reporting by combining these capabilities and offering a view of memory statistics for all PostgreSQL backends and auxiliary processes. Attached is a patch that implements this functionality. It introduces a SQL function that takes the PID of a backend as an argument, returning a set of records, each containing statistics for a single memory context. The underlying C function sends a signal to the backend and waits for it to publish its memory context statistics before returning them to the user. The publishing backend copies these statistics during the next CHECK_FOR_INTERRUPTS call. This approach facilitates on-demand publication of memory statistics for a specific backend, rather than collecting them at regular intervals. Since past memory context statistics may no longer be relevant, there is little value in retaining historical data. Any collected statistics can be discarded once read by the client backend. A fixed-size shared memory block, currently accommodating 30 records, is used to store the statistics. This number was chosen arbitrarily, as it covers all parent contexts at level 1 (i.e., direct children of the top memory context) based on my tests. Further experiments are needed to determine the optimal number for summarizing memory statistics. Any additional statistics that exceed the shared memory capacity are written to a file per backend in the PG_TEMP_FILES_DIR. The client backend first reads from the shared memory, and if necessary, retrieves the remaining data from the file, combining everything into a unified view. The files are cleaned up automatically if a backend crashes or during server restarts. The statistics are reported in a breadth-first search order of the memory context tree, with parent contexts reported before their children. This provides a cumulative summary before diving into the details of each child context's consumption. The rationale behind the shared memory chunk is to ensure that the majority of contexts which are the direct children of TopMemoryContext, fit into memory This allows a client to request a summary of memory statistics, which can be served from memory without the overhead of file access, unless necessary. A publishing backend signals waiting client backends using a condition variable when it has finished writing its statistics to memory. The client backend checks whether the statistics belong to the requested backend. If not, it continues waiting on the condition variable, timing out after 2 minutes. This timeout is an arbitrary choice, and further work is required to determine a more practical value. All backends use the same memory space to publish their statistics. Before publishing, a backend checks whether the previous statistics have been successfully read by a client using a shared flag, "in_use." This flag is set by the publishing backend and cleared by the client backend once the data is read. If a backend cannot publish due to shared memory being occupied, it exits the interrupt processing code, and the client backend times out with a warning. Please find below an example query to fetch memory contexts from the backend with id '106114'. Second argument -'get_summary' is 'false', indicating a request for statistics of all the contexts. postgres=# select * FROM pg_get_remote_backend_memory_contexts('116292', false) LIMIT 2; -[ RECORD 1 ]-+---------------------- name | TopMemoryContext ident | type | AllocSet path | {0} total_bytes | 97696 total_nblocks | 5 free_bytes | 15376 free_chunks | 11 used_bytes | 82320 pid | 116292 -[ RECORD 2 ]-+---------------------- name | RowDescriptionContext ident | type | AllocSet path | {0,1} total_bytes | 8192 total_nblocks | 1 free_bytes | 6912 free_chunks | 0 used_bytes | 1280 pid | 116292 TODO: 1. Determine the behaviour when the statistics don't fit in one file. *[1] **PostgreSQL: Re: Creating a function for exposing memory usage of backend process <https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.postgresql.org%2Fmessage-id%2F0a768ae1-1703-59c7-86cc-7068ff5e318c%2540oss.nttdata.com&data=05%7C02%7Csyedrahila%40microsoft.com%7C3b35e97c29cf4796042408dcee8a4dbb%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C638647525436604911%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=cbO2DBP6IsgMPTEVFNh%2FKeq4IoK3MZvTpzKkCQzNPMo%3D&reserved=0>* [2] *PostgreSQL: Re: Get memory contexts of an arbitrary backend process <https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.postgresql.org%2Fmessage-id%2Fbea016ad-d1a7-f01d-a7e8-01106a1de77f%2540oss.nttdata.com&data=05%7C02%7Csyedrahila%40microsoft.com%7C3b35e97c29cf4796042408dcee8a4dbb%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C638647525436629740%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=UCwkwg6kikVEf0oHf3%2BlliA%2FTUdMG%2F0cOiMta7fjPPk%3D&reserved=0>* Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Michael Paquier <michael@paquier.xyz> — 2024-10-22T06:48:39Z
On Mon, Oct 21, 2024 at 11:54:21PM +0530, Rahila Syed wrote: > On the other hand, [2] provides the statistics for all backends but logs > them in a file, which may not be convenient for quick access. To be precise, pg_log_backend_memory_contexts() pushes the memory context stats to LOG_SERVER_ONLY or stderr, hence this is appended to the server logs. > A fixed-size shared memory block, currently accommodating 30 records, > is used to store the statistics. This number was chosen arbitrarily, > as it covers all parent contexts at level 1 (i.e., direct children of the > top memory context) > based on my tests. > Further experiments are needed to determine the optimal number > for summarizing memory statistics. + * Statistics are shared via fixed shared memory which + * can hold statistics for 29 contexts. The rest of the [...] + MemoryContextInfo memctx_infos[30]; [...] + memset(&memCtxState->memctx_infos, 0, 30 * sizeof(MemoryContextInfo)); [...] + size = add_size(size, mul_size(30, sizeof(MemoryContextInfo))); [...] + memset(&memCtxState->memctx_infos, 0, 30 * sizeof(MemoryContextInfo)); [...] + memset(&memCtxState->memctx_infos, 0, 30 * sizeof(MemoryContextInfo)); This number is tied to MemoryContextState added by the patch. Sounds like this would be better as a constant properly defined rather than hardcoded in all these places. This would make the upper-bound more easily switchable in the patch. + Datum path[128]; + char type[128]; [...] + char name[1024]; + char ident[1024]; + char type[128]; + Datum path[128]; Again, constants. Why these values? You may want to use more #defines here. > Any additional statistics that exceed the shared memory capacity > are written to a file per backend in the PG_TEMP_FILES_DIR. The client > backend > first reads from the shared memory, and if necessary, retrieves the > remaining data from the file, > combining everything into a unified view. The files are cleaned up > automatically > if a backend crashes or during server restarts. Is the addition of the file to write any remaining stats really that useful? This comes with a heavy cost in the patch with the "in_use" flag, the various tweaks around the LWLock release/acquire protecting the shmem area and the extra cleanup steps required after even a clean restart. That's a lot of facility for this kind of information. Another thing that may be worth considering is to put this information in a DSM per the variable-size nature of the information, perhaps cap it to a max to make the memory footprint cheaper, and avoid all on-disk footprint because we don't need it to begin with as this is information that makes sense only while the server is running. Also, why the single-backend limitation? One could imagine a shared memory area indexed similarly to pgproc entries, that includes auxiliary processes as much as backends, so as it can be possible to get more memory footprints through SQL for more than one single process at one moment in time. If each backend has its own area of shmem to deal with, they could use a shared LWLock on the shmem area with an extra spinlock while the context data is dumped into memory as the copy is short-lived. Each one of them could save the information in a DSM created only when a dump of the shmem is requested for a given PID, for example. -- Michael
-
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2024-10-23T03:58:12Z
On 2024-10-22 03:24, Rahila Syed wrote: > Hi, > > PostgreSQL provides following capabilities for reporting memory > contexts statistics. > 1. pg_get_backend_memory_contexts(); [1] > 2. pg_log_backend_memory_contexts(pid); [2] > > [1] provides a view of memory context statistics for a local backend, > while [2] prints the memory context statistics of any backend or > auxiliary > process to the PostgreSQL logs. Although [1] offers detailed > statistics, > it is limited to the local backend, restricting its use to PostgreSQL > client backends only. > On the other hand, [2] provides the statistics for all backends but > logs them in a file, > which may not be convenient for quick access. > > I propose enhancing memory context statistics reporting by combining > these > capabilities and offering a view of memory statistics for all > PostgreSQL backends > and auxiliary processes. Thanks for working on this! I originally tried to develop something like your proposal in [2], but there were some difficulties and settled down to implement pg_log_backend_memory_contexts(). > Attached is a patch that implements this functionality. It introduces > a SQL function > that takes the PID of a backend as an argument, returning a set of > records, > each containing statistics for a single memory context. The underlying > C function > sends a signal to the backend and waits for it to publish its memory > context statistics > before returning them to the user. The publishing backend copies > these statistics > during the next CHECK_FOR_INTERRUPTS call. I remember waiting for dumping memory contexts stats could cause trouble considering some erroneous cases. For example, just after the target process finished dumping stats, pg_get_remote_backend_memory_contexts() caller is terminated before reading the stats, calling pg_get_remote_backend_memory_contexts() has no response any more: [session1]$ psql (40699)=# $ kill -s SIGSTOP 40699 [session2] psql (40866)=# select * FROM pg_get_remote_backend_memory_contexts('40699', false); -- waiting $ kill -s SIGSTOP 40866 $ kill -s SIGCONT 40699 [session3] psql (47656) $ select pg_terminate_backend(40866); $ kill -s SIGCONT 40866 -- session2 terminated [session3] (47656)=# select * FROM pg_get_remote_backend_memory_contexts('47656', false); -- no response It seems the reason is memCtxState->in_use is now and memCtxState->proc_id is 40699. We can continue to use pg_get_remote_backend_memory_contexts() after specifying 40699, but it'd be hard to understand for users. > This approach facilitates on-demand publication of memory statistics > for a specific backend, rather than collecting them at regular > intervals. > Since past memory context statistics may no longer be relevant, > there is little value in retaining historical data. Any collected > statistics > can be discarded once read by the client backend. > > A fixed-size shared memory block, currently accommodating 30 records, > is used to store the statistics. This number was chosen arbitrarily, > as it covers all parent contexts at level 1 (i.e., direct children of > the top memory context) > based on my tests. > Further experiments are needed to determine the optimal number > for summarizing memory statistics. > > Any additional statistics that exceed the shared memory capacity > are written to a file per backend in the PG_TEMP_FILES_DIR. The client > backend > first reads from the shared memory, and if necessary, retrieves the > remaining data from the file, > combining everything into a unified view. The files are cleaned up > automatically > if a backend crashes or during server restarts. > > The statistics are reported in a breadth-first search order of the > memory context tree, > with parent contexts reported before their children. This provides a > cumulative summary > before diving into the details of each child context's consumption. > > The rationale behind the shared memory chunk is to ensure that the > majority of contexts which are the direct children of > TopMemoryContext, > fit into memory > This allows a client to request a summary of memory statistics, > which can be served from memory without the overhead of file access, > unless necessary. > > A publishing backend signals waiting client backends using a condition > > variable when it has finished writing its statistics to memory. > The client backend checks whether the statistics belong to the > requested backend. > If not, it continues waiting on the condition variable, timing out > after 2 minutes. > This timeout is an arbitrary choice, and further work is required to > determine > a more practical value. > > All backends use the same memory space to publish their statistics. > Before publishing, a backend checks whether the previous statistics > have been > successfully read by a client using a shared flag, "in_use." > This flag is set by the publishing backend and cleared by the client > backend once the data is read. If a backend cannot publish due to > shared > memory being occupied, it exits the interrupt processing code, > and the client backend times out with a warning. > > Please find below an example query to fetch memory contexts from the > backend > with id '106114'. Second argument -'get_summary' is 'false', > indicating a request for statistics of all the contexts. > > postgres=# > select * FROM pg_get_remote_backend_memory_contexts('116292', false) > LIMIT 2; > -[ RECORD 1 ]-+---------------------- > name | TopMemoryContext > ident | > type | AllocSet > path | {0} > total_bytes | 97696 > total_nblocks | 5 > free_bytes | 15376 > free_chunks | 11 > used_bytes | 82320 > pid | 116292 > -[ RECORD 2 ]-+---------------------- > name | RowDescriptionContext > ident | > type | AllocSet > path | {0,1} > total_bytes | 8192 > total_nblocks | 1 > free_bytes | 6912 > free_chunks | 0 > used_bytes | 1280 > pid | 116292 32d3ed8165f821f introduced 1-based path to pg_backend_memory_contexts, but pg_get_remote_backend_memory_contexts() seems to have 0-base path. pg_backend_memory_contexts has "level" column, but pg_get_remote_backend_memory_contexts doesn't. Are there any reasons for these? > TODO: > 1. Determine the behaviour when the statistics don't fit in one file. > > [1] PostgreSQL: Re: Creating a function for exposing memory usage of > backend process [1] > > [2] PostgreSQL: Re: Get memory contexts of an arbitrary backend > process [2] > > Thank you, > Rahila Syed > > > > Links: > ------ > [1] > https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.postgresql.org%2Fmessage-id%2F0a768ae1-1703-59c7-86cc-7068ff5e318c%2540oss.nttdata.com&data=05%7C02%7Csyedrahila%40microsoft.com%7C3b35e97c29cf4796042408dcee8a4dbb%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C638647525436604911%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=cbO2DBP6IsgMPTEVFNh%2FKeq4IoK3MZvTpzKkCQzNPMo%3D&reserved=0 > [2] > https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.postgresql.org%2Fmessage-id%2Fbea016ad-d1a7-f01d-a7e8-01106a1de77f%2540oss.nttdata.com&data=05%7C02%7Csyedrahila%40microsoft.com%7C3b35e97c29cf4796042408dcee8a4dbb%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C638647525436629740%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=UCwkwg6kikVEf0oHf3%2BlliA%2FTUdMG%2F0cOiMta7fjPPk%3D&reserved=0 -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA GROUP CORPORATION to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-10-23T04:50:16Z
Hi Michael, Thank you for the review. On Tue, Oct 22, 2024 at 12:18 PM Michael Paquier <michael@paquier.xyz> wrote: > On Mon, Oct 21, 2024 at 11:54:21PM +0530, Rahila Syed wrote: > > On the other hand, [2] provides the statistics for all backends but logs > > them in a file, which may not be convenient for quick access. > > To be precise, pg_log_backend_memory_contexts() pushes the memory > context stats to LOG_SERVER_ONLY or stderr, hence this is appended to > the server logs. > > > A fixed-size shared memory block, currently accommodating 30 records, > > is used to store the statistics. This number was chosen arbitrarily, > > as it covers all parent contexts at level 1 (i.e., direct children of > the > > top memory context) > > based on my tests. > > Further experiments are needed to determine the optimal number > > for summarizing memory statistics. > > + * Statistics are shared via fixed shared memory which > + * can hold statistics for 29 contexts. The rest of the > [...] > + MemoryContextInfo memctx_infos[30]; > [...] > + memset(&memCtxState->memctx_infos, 0, 30 * > sizeof(MemoryContextInfo)); > [...] > + size = add_size(size, mul_size(30, sizeof(MemoryContextInfo))); > [...] > + memset(&memCtxState->memctx_infos, 0, 30 * sizeof(MemoryContextInfo)); > [...] > + memset(&memCtxState->memctx_infos, 0, 30 * > sizeof(MemoryContextInfo)); > > This number is tied to MemoryContextState added by the patch. Sounds > like this would be better as a constant properly defined rather than > hardcoded in all these places. This would make the upper-bound more > easily switchable in the patch. > > Makes sense. Fixed in the attached patch. > + Datum path[128]; > + char type[128]; > [...] > + char name[1024]; > + char ident[1024]; > + char type[128]; > + Datum path[128]; > > Again, constants. Why these values? You may want to use more > #defines here. > > I added the #defines for these in the attached patch. Size of the path array should match the number of levels in the memory context tree and type is a constant string. For the name and ident, I have used the existing #define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE as the size limit. > > Any additional statistics that exceed the shared memory capacity > > are written to a file per backend in the PG_TEMP_FILES_DIR. The client > > backend > > first reads from the shared memory, and if necessary, retrieves the > > remaining data from the file, > > combining everything into a unified view. The files are cleaned up > > automatically > > if a backend crashes or during server restarts. > > Is the addition of the file to write any remaining stats really that > useful? This comes with a heavy cost in the patch with the "in_use" > flag, the various tweaks around the LWLock release/acquire protecting > the shmem area and the extra cleanup steps required after even a clean > restart. That's a lot of facility for this kind of information. > The rationale behind using the file is to cater to the unbounded number of memory contexts. The "in_use" flag is used to govern the access to shared memory as I am reserving enough memory for only one backend. It ensures that another backend does not overwrite the statistics in the shared memory, before it is read by a client backend. > Another thing that may be worth considering is to put this information > in a DSM per the variable-size nature of the information, perhaps cap > it to a max to make the memory footprint cheaper, and avoid all > on-disk footprint because we don't need it to begin with as this is > information that makes sense only while the server is running. > > Thank you for the suggestion. I will look into using DSMs especially if there is a way to limit the statistics dump, while still providing a user with enough information to debug memory consumption. In this draft, I preferred using a file over DSMs, as a file can provide ample space for dumping a large number of memory context statistics without the risk of DSM creation failure due to insufficient memory. Also, why the single-backend limitation? To reduce the memory footprint, the shared memory is created for only one backend. Each backend has to wait for previous operation to finish before it can write. I think a good use case for this would be a background process periodically running the monitoring function on each of the backends sequentially to fetch the statistics. This way there will be little contention for shared memory. In case a shared memory is not available, a backend immediately returns from the interrupt handler without blocking its normal operations. One could imagine a shared > memory area indexed similarly to pgproc entries, that includes > auxiliary processes as much as backends, so as it can be possible to > get more memory footprints through SQL for more than one single > process at one moment in time. If each backend has its own area of > shmem to deal with, they could use a shared LWLock on the shmem area > with an extra spinlock while the context data is dumped into memory as > the copy is short-lived. Each one of them could save the information > in a DSM created only when a dump of the shmem is requested for a > given PID, for example. > I agree that such an infrastructure would be useful for fetching memory statistics concurrently without significant synchronization overhead. However, a drawback of this approach is reserving shared memory slots up to MAX_BACKENDS without utilizing them when no concurrent monitoring is happening. As you mentioned, creating a DSM on the fly when a dump request is received could help avoid over-allocating shared memory. I will look into this suggestion Thank you for your feedback! Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-10-24T05:59:46Z
Hi Torikoshia, Thank you for reviewing the patch! On Wed, Oct 23, 2024 at 9:28 AM torikoshia <torikoshia@oss.nttdata.com> wrote: > On 2024-10-22 03:24, Rahila Syed wrote: > > Hi, > > > > PostgreSQL provides following capabilities for reporting memory > > contexts statistics. > > 1. pg_get_backend_memory_contexts(); [1] > > 2. pg_log_backend_memory_contexts(pid); [2] > > > > [1] provides a view of memory context statistics for a local backend, > > while [2] prints the memory context statistics of any backend or > > auxiliary > > process to the PostgreSQL logs. Although [1] offers detailed > > statistics, > > it is limited to the local backend, restricting its use to PostgreSQL > > client backends only. > > On the other hand, [2] provides the statistics for all backends but > > logs them in a file, > > which may not be convenient for quick access. > > > > I propose enhancing memory context statistics reporting by combining > > these > > capabilities and offering a view of memory statistics for all > > PostgreSQL backends > > and auxiliary processes. > > Thanks for working on this! > > I originally tried to develop something like your proposal in [2], but > there were some difficulties and settled down to implement > pg_log_backend_memory_contexts(). > > Yes. I am revisiting this problem :) > > Attached is a patch that implements this functionality. It introduces > > a SQL function > > that takes the PID of a backend as an argument, returning a set of > > records, > > each containing statistics for a single memory context. The underlying > > C function > > sends a signal to the backend and waits for it to publish its memory > > context statistics > > before returning them to the user. The publishing backend copies > > these statistics > > during the next CHECK_FOR_INTERRUPTS call. > > I remember waiting for dumping memory contexts stats could cause trouble > considering some erroneous cases. > > For example, just after the target process finished dumping stats, > pg_get_remote_backend_memory_contexts() caller is terminated before > reading the stats, calling pg_get_remote_backend_memory_contexts() has > no response any more: > > [session1]$ psql > (40699)=# > > $ kill -s SIGSTOP 40699 > > [session2] psql > (40866)=# select * FROM > pg_get_remote_backend_memory_contexts('40699', false); -- waiting > > $ kill -s SIGSTOP 40866 > > $ kill -s SIGCONT 40699 > > [session3] psql > (47656) $ select pg_terminate_backend(40866); > > $ kill -s SIGCONT 40866 -- session2 terminated > > [session3] (47656)=# select * FROM > pg_get_remote_backend_memory_contexts('47656', false); -- no response > > It seems the reason is memCtxState->in_use is now and > memCtxState->proc_id is 40699. > We can continue to use pg_get_remote_backend_memory_contexts() after > specifying 40699, but it'd be hard to understand for users. > > Thanks for testing and reporting. While I am not able to reproduce this problem, I think this may be happening because the requesting backend/caller is terminated before it gets a chance to mark memCtxState->in_use as false. In this case memCtxState->in_use should be marked as 'false' possibly during the processing of ProcDiePending in ProcessInterrupts(). > This approach facilitates on-demand publication of memory statistics > > for a specific backend, rather than collecting them at regular > > intervals. > > Since past memory context statistics may no longer be relevant, > > there is little value in retaining historical data. Any collected > > statistics > > can be discarded once read by the client backend. > > > > A fixed-size shared memory block, currently accommodating 30 records, > > is used to store the statistics. This number was chosen arbitrarily, > > as it covers all parent contexts at level 1 (i.e., direct children of > > the top memory context) > > based on my tests. > > Further experiments are needed to determine the optimal number > > for summarizing memory statistics. > > > > Any additional statistics that exceed the shared memory capacity > > are written to a file per backend in the PG_TEMP_FILES_DIR. The client > > backend > > first reads from the shared memory, and if necessary, retrieves the > > remaining data from the file, > > combining everything into a unified view. The files are cleaned up > > automatically > > if a backend crashes or during server restarts. > > > > The statistics are reported in a breadth-first search order of the > > memory context tree, > > with parent contexts reported before their children. This provides a > > cumulative summary > > before diving into the details of each child context's consumption. > > > > The rationale behind the shared memory chunk is to ensure that the > > majority of contexts which are the direct children of > > TopMemoryContext, > > fit into memory > > This allows a client to request a summary of memory statistics, > > which can be served from memory without the overhead of file access, > > unless necessary. > > > > A publishing backend signals waiting client backends using a condition > > > > variable when it has finished writing its statistics to memory. > > The client backend checks whether the statistics belong to the > > requested backend. > > If not, it continues waiting on the condition variable, timing out > > after 2 minutes. > > This timeout is an arbitrary choice, and further work is required to > > determine > > a more practical value. > > > > All backends use the same memory space to publish their statistics. > > Before publishing, a backend checks whether the previous statistics > > have been > > successfully read by a client using a shared flag, "in_use." > > This flag is set by the publishing backend and cleared by the client > > backend once the data is read. If a backend cannot publish due to > > shared > > memory being occupied, it exits the interrupt processing code, > > and the client backend times out with a warning. > > > > Please find below an example query to fetch memory contexts from the > > backend > > with id '106114'. Second argument -'get_summary' is 'false', > > indicating a request for statistics of all the contexts. > > > > postgres=# > > select * FROM pg_get_remote_backend_memory_contexts('116292', false) > > LIMIT 2; > > -[ RECORD 1 ]-+---------------------- > > name | TopMemoryContext > > ident | > > type | AllocSet > > path | {0} > > total_bytes | 97696 > > total_nblocks | 5 > > free_bytes | 15376 > > free_chunks | 11 > > used_bytes | 82320 > > pid | 116292 > > -[ RECORD 2 ]-+---------------------- > > name | RowDescriptionContext > > ident | > > type | AllocSet > > path | {0,1} > > total_bytes | 8192 > > total_nblocks | 1 > > free_bytes | 6912 > > free_chunks | 0 > > used_bytes | 1280 > > pid | 116292 > > 32d3ed8165f821f introduced 1-based path to pg_backend_memory_contexts, > but pg_get_remote_backend_memory_contexts() seems to have 0-base path. > > Right. I will change it to match this commit. > pg_backend_memory_contexts has "level" column, but > pg_get_remote_backend_memory_contexts doesn't. > > Are there any reasons for these? > > No particular reason, I can add this column as well. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2024-10-25T00:21:52Z
On 2024-10-24 14:59, Rahila Syed wrote: > Hi Torikoshia, > > Thank you for reviewing the patch! > > On Wed, Oct 23, 2024 at 9:28 AM torikoshia > <torikoshia@oss.nttdata.com> wrote: > >> On 2024-10-22 03:24, Rahila Syed wrote: >>> Hi, >>> >>> PostgreSQL provides following capabilities for reporting memory >>> contexts statistics. >>> 1. pg_get_backend_memory_contexts(); [1] >>> 2. pg_log_backend_memory_contexts(pid); [2] >>> >>> [1] provides a view of memory context statistics for a local >> backend, >>> while [2] prints the memory context statistics of any backend or >>> auxiliary >>> process to the PostgreSQL logs. Although [1] offers detailed >>> statistics, >>> it is limited to the local backend, restricting its use to >> PostgreSQL >>> client backends only. >>> On the other hand, [2] provides the statistics for all backends >> but >>> logs them in a file, >>> which may not be convenient for quick access. >>> >>> I propose enhancing memory context statistics reporting by >> combining >>> these >>> capabilities and offering a view of memory statistics for all >>> PostgreSQL backends >>> and auxiliary processes. >> >> Thanks for working on this! >> >> I originally tried to develop something like your proposal in [2], >> but >> there were some difficulties and settled down to implement >> pg_log_backend_memory_contexts(). > > Yes. I am revisiting this problem :) > >>> Attached is a patch that implements this functionality. It >> introduces >>> a SQL function >>> that takes the PID of a backend as an argument, returning a set of >>> records, >>> each containing statistics for a single memory context. The >> underlying >>> C function >>> sends a signal to the backend and waits for it to publish its >> memory >>> context statistics >>> before returning them to the user. The publishing backend copies >>> these statistics >>> during the next CHECK_FOR_INTERRUPTS call. >> >> I remember waiting for dumping memory contexts stats could cause >> trouble >> considering some erroneous cases. >> >> For example, just after the target process finished dumping stats, >> pg_get_remote_backend_memory_contexts() caller is terminated before >> reading the stats, calling pg_get_remote_backend_memory_contexts() >> has >> no response any more: >> >> [session1]$ psql >> (40699)=# >> >> $ kill -s SIGSTOP 40699 >> >> [session2] psql >> (40866)=# select * FROM >> pg_get_remote_backend_memory_contexts('40699', false); -- waiting >> >> $ kill -s SIGSTOP 40866 >> >> $ kill -s SIGCONT 40699 >> >> [session3] psql >> (47656) $ select pg_terminate_backend(40866); >> >> $ kill -s SIGCONT 40866 -- session2 terminated >> >> [session3] (47656)=# select * FROM >> pg_get_remote_backend_memory_contexts('47656', false); -- no >> response >> >> It seems the reason is memCtxState->in_use is now and >> memCtxState->proc_id is 40699. >> We can continue to use pg_get_remote_backend_memory_contexts() after >> >> specifying 40699, but it'd be hard to understand for users. > > Thanks for testing and reporting. While I am not able to reproduce > this problem, > I think this may be happening because the requesting backend/caller is > terminated > before it gets a chance to mark memCtxState->in_use as false. Yeah, when I attached a debugger to 47656 when it was waiting on pg_get_remote_backend_memory_contexts('47656', false), memCtxState->in_use was true as you suspected: (lldb) p memCtxState->in_use (bool) $1 = true (lldb) p memCtxState->proc_id (int) $2 = 40699 (lldb) p pid (int) $3 = 47656 > In this case memCtxState->in_use should be marked as > 'false' possibly during the processing of ProcDiePending in > ProcessInterrupts(). > >>> This approach facilitates on-demand publication of memory >> statistics >>> for a specific backend, rather than collecting them at regular >>> intervals. >>> Since past memory context statistics may no longer be relevant, >>> there is little value in retaining historical data. Any collected >>> statistics >>> can be discarded once read by the client backend. >>> >>> A fixed-size shared memory block, currently accommodating 30 >> records, >>> is used to store the statistics. This number was chosen >> arbitrarily, >>> as it covers all parent contexts at level 1 (i.e., direct >> children of >>> the top memory context) >>> based on my tests. >>> Further experiments are needed to determine the optimal number >>> for summarizing memory statistics. >>> >>> Any additional statistics that exceed the shared memory capacity >>> are written to a file per backend in the PG_TEMP_FILES_DIR. The >> client >>> backend >>> first reads from the shared memory, and if necessary, retrieves >> the >>> remaining data from the file, >>> combining everything into a unified view. The files are cleaned up >>> automatically >>> if a backend crashes or during server restarts. >>> >>> The statistics are reported in a breadth-first search order of the >>> memory context tree, >>> with parent contexts reported before their children. This >> provides a >>> cumulative summary >>> before diving into the details of each child context's >> consumption. >>> >>> The rationale behind the shared memory chunk is to ensure that the >>> majority of contexts which are the direct children of >>> TopMemoryContext, >>> fit into memory >>> This allows a client to request a summary of memory statistics, >>> which can be served from memory without the overhead of file >> access, >>> unless necessary. >>> >>> A publishing backend signals waiting client backends using a >> condition >>> >>> variable when it has finished writing its statistics to memory. >>> The client backend checks whether the statistics belong to the >>> requested backend. >>> If not, it continues waiting on the condition variable, timing out >>> after 2 minutes. >>> This timeout is an arbitrary choice, and further work is required >> to >>> determine >>> a more practical value. >>> >>> All backends use the same memory space to publish their >> statistics. >>> Before publishing, a backend checks whether the previous >> statistics >>> have been >>> successfully read by a client using a shared flag, "in_use." >>> This flag is set by the publishing backend and cleared by the >> client >>> backend once the data is read. If a backend cannot publish due to >>> shared >>> memory being occupied, it exits the interrupt processing code, >>> and the client backend times out with a warning. >>> >>> Please find below an example query to fetch memory contexts from >> the >>> backend >>> with id '106114'. Second argument -'get_summary' is 'false', >>> indicating a request for statistics of all the contexts. >>> >>> postgres=# >>> select * FROM pg_get_remote_backend_memory_contexts('116292', >> false) >>> LIMIT 2; >>> -[ RECORD 1 ]-+---------------------- >>> name | TopMemoryContext >>> ident | >>> type | AllocSet >>> path | {0} >>> total_bytes | 97696 >>> total_nblocks | 5 >>> free_bytes | 15376 >>> free_chunks | 11 >>> used_bytes | 82320 >>> pid | 116292 >>> -[ RECORD 2 ]-+---------------------- >>> name | RowDescriptionContext >>> ident | >>> type | AllocSet >>> path | {0,1} >>> total_bytes | 8192 >>> total_nblocks | 1 >>> free_bytes | 6912 >>> free_chunks | 0 >>> used_bytes | 1280 >>> pid | 116292 >> >> 32d3ed8165f821f introduced 1-based path to >> pg_backend_memory_contexts, >> but pg_get_remote_backend_memory_contexts() seems to have 0-base >> path. > > Right. I will change it to match this commit. > >> pg_backend_memory_contexts has "level" column, but >> pg_get_remote_backend_memory_contexts doesn't. >> >> Are there any reasons for these? > > No particular reason, I can add this column as well. > > Thank you, > Rahila Syed -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA GROUP CORPORATION to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Nitin Jadhav <nitinjadhavpostgres@gmail.com> — 2024-10-26T14:03:32Z
Hi Rahila, I’ve spent some time reviewing the patch, and the review is still ongoing. Here are the comments I’ve found so far. 1. The tests are currently missing. Could you please add them? 2. I have some concerns regarding the function name ‘pg_get_remote_backend_memory_contexts’. Specifically, the term ‘remote’ doesn’t seem appropriate to me. The function retrieves data from other processes running on the same machine, which might give the impression that it deals with processes on different machines. This could be misleading or unclear in this context. The argument ‘pid’ already indicates that it can get data from different processes. Additionally, the term ‘backend’ also seems inappropriate since we are obtaining data from processes that are different from backend processes. 3. > + Datum values[10]; > + bool nulls[10]; Please consider #defining the column count, or you could reuse the existing one ‘PG_GET_BACKEND_MEMORY_CONTEXTS_COLS’. 4. > if (context_id <= 28) > if (context_id == 29) > if (context_id < 29) #define these 5. > for (MemoryContext cur_context = cur; cur_context != NULL; cur_context = cur_context->parent) > { > MemoryContextId *cur_entry; > > cur_entry = hash_search(context_id_lookup, &cur_context, HASH_FIND, &found); > > if (!found) > { > elog(LOG, "hash table corrupted, can't construct path value"); > break; > } > path = lcons_int(cur_entry->context_id, path); > } Similar code already exists in PutMemoryContextsStatsTupleStore(). Could you create a separate function to handle this? 6. > /* > * Shared memory is full, release lock and write to file from next > * iteration > */ > context_id++; > if (context_id == 29) > { What if there are exactly 29 entries in the memory context? In that case, creating the file would be unnecessary. Best Regards, Nitin Jadhav Azure Database for PostgreSQL Microsoft On Wed, Oct 23, 2024 at 10:20 AM Rahila Syed <rahilasyed90@gmail.com> wrote: > > Hi Michael, > > Thank you for the review. > > On Tue, Oct 22, 2024 at 12:18 PM Michael Paquier <michael@paquier.xyz> wrote: >> >> On Mon, Oct 21, 2024 at 11:54:21PM +0530, Rahila Syed wrote: >> > On the other hand, [2] provides the statistics for all backends but logs >> > them in a file, which may not be convenient for quick access. >> >> To be precise, pg_log_backend_memory_contexts() pushes the memory >> context stats to LOG_SERVER_ONLY or stderr, hence this is appended to >> the server logs. >> >> > A fixed-size shared memory block, currently accommodating 30 records, >> > is used to store the statistics. This number was chosen arbitrarily, >> > as it covers all parent contexts at level 1 (i.e., direct children of the >> > top memory context) >> > based on my tests. >> > Further experiments are needed to determine the optimal number >> > for summarizing memory statistics. >> >> + * Statistics are shared via fixed shared memory which >> + * can hold statistics for 29 contexts. The rest of the >> [...] >> + MemoryContextInfo memctx_infos[30]; >> [...] >> + memset(&memCtxState->memctx_infos, 0, 30 * sizeof(MemoryContextInfo)); >> [...] >> + size = add_size(size, mul_size(30, sizeof(MemoryContextInfo))); >> [...] >> + memset(&memCtxState->memctx_infos, 0, 30 * sizeof(MemoryContextInfo)); >> [...] >> + memset(&memCtxState->memctx_infos, 0, 30 * sizeof(MemoryContextInfo)); >> >> This number is tied to MemoryContextState added by the patch. Sounds >> like this would be better as a constant properly defined rather than >> hardcoded in all these places. This would make the upper-bound more >> easily switchable in the patch. >> > > Makes sense. Fixed in the attached patch. > >> >> + Datum path[128]; >> + char type[128]; >> [...] >> + char name[1024]; >> + char ident[1024]; >> + char type[128]; >> + Datum path[128]; >> >> Again, constants. Why these values? You may want to use more >> #defines here. >> > I added the #defines for these in the attached patch. > Size of the path array should match the number of levels in the memory > context tree and type is a constant string. > > For the name and ident, I have used the existing #define > MEMORY_CONTEXT_IDENT_DISPLAY_SIZE as the size limit. > >> >> > Any additional statistics that exceed the shared memory capacity >> > are written to a file per backend in the PG_TEMP_FILES_DIR. The client >> > backend >> > first reads from the shared memory, and if necessary, retrieves the >> > remaining data from the file, >> > combining everything into a unified view. The files are cleaned up >> > automatically >> > if a backend crashes or during server restarts. >> >> Is the addition of the file to write any remaining stats really that >> useful? This comes with a heavy cost in the patch with the "in_use" >> flag, the various tweaks around the LWLock release/acquire protecting >> the shmem area and the extra cleanup steps required after even a clean >> restart. That's a lot of facility for this kind of information. > > > The rationale behind using the file is to cater to the unbounded > number of memory contexts. > The "in_use" flag is used to govern the access to shared memory > as I am reserving enough memory for only one backend. > It ensures that another backend does not overwrite the statistics > in the shared memory, before it is read by a client backend. > >> >> Another thing that may be worth considering is to put this information >> in a DSM per the variable-size nature of the information, perhaps cap >> it to a max to make the memory footprint cheaper, and avoid all >> on-disk footprint because we don't need it to begin with as this is >> information that makes sense only while the server is running. >> > Thank you for the suggestion. I will look into using DSMs especially > if there is a way to limit the statistics dump, while still providing a user > with enough information to debug memory consumption. > > In this draft, I preferred using a file over DSMs, as a file can provide > ample space for dumping a large number of memory context statistics > without the risk of DSM creation failure due to insufficient memory. > >> Also, why the single-backend limitation? > > > To reduce the memory footprint, the shared memory is > created for only one backend. > Each backend has to wait for previous operation > to finish before it can write. > > I think a good use case for this would be a background process > periodically running the monitoring function on each of the > backends sequentially to fetch the statistics. > This way there will be little contention for shared memory. > > In case a shared memory is not available, a backend immediately > returns from the interrupt handler without blocking its normal > operations. > >> One could imagine a shared >> memory area indexed similarly to pgproc entries, that includes >> auxiliary processes as much as backends, so as it can be possible to >> get more memory footprints through SQL for more than one single >> process at one moment in time. If each backend has its own area of >> shmem to deal with, they could use a shared LWLock on the shmem area >> with an extra spinlock while the context data is dumped into memory as >> the copy is short-lived. Each one of them could save the information >> in a DSM created only when a dump of the shmem is requested for a >> given PID, for example. > > > I agree that such an infrastructure would be useful for fetching memory > statistics concurrently without significant synchronization overhead. > However, a drawback of this approach is reserving shared > memory slots up to MAX_BACKENDS without utilizing them > when no concurrent monitoring is happening. > As you mentioned, creating a DSM on the fly when a dump > request is received could help avoid over-allocating shared memory. > I will look into this suggestion > > Thank you for your feedback! > > Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2024-10-26T14:14:25Z
On 2024-Oct-21, Rahila Syed wrote: > I propose enhancing memory context statistics reporting by combining > these capabilities and offering a view of memory statistics for all > PostgreSQL backends and auxiliary processes. Sounds good. > A fixed-size shared memory block, currently accommodating 30 records, > is used to store the statistics. Hmm, would it make sene to use dynamic shared memory for this? The publishing backend could dsm_create one DSM chunk of the exact size that it needs, pass the dsm_handle to the consumer, and then have it be destroy once it's been read. That way you don't have to define an arbitrary limit of any size. (Maybe you could keep a limit to how much is published in shared memory and spill the rest to disk, but I think such a limit should be very high[1], so that it's unlikely to take effect in normal cases.) [1] This is very arbitrary of course, but 1 MB gives enough room for some 7000 contexts, which should cover normal cases. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Find a bug in a program, and fix it, and the program will work today. Show the program how to find and fix a bug, and the program will work forever" (Oliver Silfridge)
-
Re: Enhancing Memory Context Statistics Reporting
Andres Freund <andres@anarazel.de> — 2024-10-29T14:51:57Z
Hi, On 2024-10-26 16:14:25 +0200, Alvaro Herrera wrote: > > A fixed-size shared memory block, currently accommodating 30 records, > > is used to store the statistics. > > Hmm, would it make sene to use dynamic shared memory for this? +1 > The publishing backend could dsm_create one DSM chunk of the exact size that > it needs, pass the dsm_handle to the consumer, and then have it be destroy > once it's been read. I'd probably just make it a dshash table or such, keyed by the pid, pointing to a dsa allocation with the stats. > That way you don't have to define an arbitrary limit > of any size. (Maybe you could keep a limit to how much is published in > shared memory and spill the rest to disk, but I think such a limit should be > very high[1], so that it's unlikely to take effect in normal cases.) > > [1] This is very arbitrary of course, but 1 MB gives enough room for > some 7000 contexts, which should cover normal cases. Agreed. I can see a point in a limit for extreme cases, but spilling to disk doesn't seem particularly useful. Greetings, Andres Freund
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-11-13T07:30:52Z
Hi, Thank you for the review. > > Hmm, would it make sene to use dynamic shared memory for this? The > publishing backend could dsm_create one DSM chunk of the exact size that > it needs, pass the dsm_handle to the consumer, and then have it be > destroy once it's been read. That way you don't have to define an > arbitrary limit of any size. (Maybe you could keep a limit to how much > is published in shared memory and spill the rest to disk, but I think > such a limit should be very high[1], so that it's unlikely to take > effect in normal cases.) > [1] This is very arbitrary of course, but 1 MB gives enough room for > some 7000 contexts, which should cover normal cases. > I used one DSA area per process to share statistics. Currently, the size limit for each DSA is 16 MB, which can accommodate approximately 6,700 MemoryContextInfo structs. Any additional statistics will spill over to a file. I opted for DSAs over DSMs to enable memory reuse by freeing segments for subsequent statistics copies of the same backend, without needing to recreate DSMs for each request. The dsa_handle for each process is stored in an array, indexed by the procNumber, within the shared memory. The maximum size of this array is defined as the sum of MaxBackends and the number of auxiliary processes. As requested earlier, I have renamed the function to pg_get_process_memory_contexts(pid, get_summary). Suggestions for a better name are welcome. When the get_summary argument is set to true, the function provides statistics for memory contexts up to level 2—that is, the top memory context and all its children. Please find attached a rebased patch that includes these changes. I will work on adding a test for the function and some code refactoring suggestions. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Michael Paquier <michael@paquier.xyz> — 2024-11-14T04:39:14Z
On Wed, Nov 13, 2024 at 01:00:52PM +0530, Rahila Syed wrote: > I used one DSA area per process to share statistics. Currently, > the size limit for each DSA is 16 MB, which can accommodate > approximately 6,700 MemoryContextInfo structs. Any additional > statistics will spill over to a file. I opted for DSAs over DSMs to > enable memory reuse by freeing segments for subsequent > statistics copies of the same backend, without needing to > recreate DSMs for each request. Already mentioned previously at [1] and echoing with some surrounding arguments, but I'd suggest to keep it simple and just remove entirely the part of the patch where the stats information gets spilled into disk. With more than 6000-ish context information available with a hard limit in place, there should be plenty enough to know what's going on anyway. [1]: https://postgr.es/m/ZxdKx0DywUTAvkEF@paquier.xyz -- Michael
-
Re: Enhancing Memory Context Statistics Reporting
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2024-11-14T11:48:47Z
On 2024-Nov-14, Michael Paquier wrote: > Already mentioned previously at [1] and echoing with some surrounding > arguments, but I'd suggest to keep it simple and just remove entirely > the part of the patch where the stats information gets spilled into > disk. With more than 6000-ish context information available with a > hard limit in place, there should be plenty enough to know what's > going on anyway. Functionally-wise I don't necessarily agree with _removing_ the spill code, considering that production systems with thousands of tables would easily reach that number of contexts (each index gets its own index info context, each regexp gets its own memcxt); and I don't think silently omitting a fraction of people's memory situation (or erroring out if the case is hit) is going to make us any friends. That said, it worries me that we choose a shared memory size so large that it becomes impractical to hit the spill-to-disk code in regression testing. Maybe we can choose a much smaller limit size when USE_ASSERT_CHECKING is enabled, and use a test that hits that number? That way, we know the code is being hit and tested, without imposing a huge memory consumption on test machines. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Tiene valor aquel que admite que es un cobarde" (Fernandel)
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-11-15T13:58:51Z
Hi, On Thu, Nov 14, 2024 at 5:18 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2024-Nov-14, Michael Paquier wrote: > > > Already mentioned previously at [1] and echoing with some surrounding > > arguments, but I'd suggest to keep it simple and just remove entirely > > the part of the patch where the stats information gets spilled into > > disk. With more than 6000-ish context information available with a > > hard limit in place, there should be plenty enough to know what's > > going on anyway. > > Functionally-wise I don't necessarily agree with _removing_ the spill > code, considering that production systems with thousands of tables would > easily reach that number of contexts (each index gets its own index info > context, each regexp gets its own memcxt); and I don't think silently > omitting a fraction of people's memory situation (or erroring out if the > case is hit) is going to make us any friends. > > While I agree that removing the spill-to-file logic will simplify the code, I also understand the rationale for retaining it to ensure completeness. To achieve both completeness and avoid writing to a file, I can consider displaying the numbers for the remaining contexts as a cumulative total at the end of the output. Something like follows: ``` postgres=# select * from pg_get_process_memory_contexts('237244', false); name | ident | type | path | total_bytes | tot al_nblocks | free_bytes | free_chunks | used_bytes | pid ---------------------------------------+------------------------------------------------+----------+--------------+-------------+---- -----------+------------+-------------+------------+-------- TopMemoryContext | | AllocSet | {0} | 97696 | 5 | 14288 | 11 | 83408 | 237244 search_path processing cache | | AllocSet | {0,1} | 8192 | 1 | 5328 | 7 | 2864 | 237244 *Remaining contexts total: 23456 bytes (total_bytes) , 12345(used_bytes), 11,111(free_bytes)* ``` > That said, it worries me that we choose a shared memory size so large > that it becomes impractical to hit the spill-to-disk code in regression > testing. Maybe we can choose a much smaller limit size when > USE_ASSERT_CHECKING is enabled, and use a test that hits that number? > That way, we know the code is being hit and tested, without imposing a > huge memory consumption on test machines. > Makes sense. I will look into writing such a test, if we finalize the approach of spill-to-file. Please find attached a rebased and updated patch with a basic test and some fixes. Kindly let me know your thoughts. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-11-20T09:09:15Z
Hi, To achieve both completeness and avoid writing to a file, I can consider > displaying the numbers for the remaining contexts as a cumulative total > at the end of the output. > > Something like follows: > ``` > postgres=# select * from pg_get_process_memory_contexts('237244', false); > name | ident > | type | path | total_bytes | tot > al_nblocks | free_bytes | free_chunks | used_bytes | pid > > ---------------------------------------+------------------------------------------------+----------+--------------+-------------+---- > -----------+------------+-------------+------------+-------- > TopMemoryContext | > | AllocSet | {0} | 97696 | > 5 | 14288 | 11 | 83408 | 237244 > search_path processing cache | > | AllocSet | {0,1} | 8192 | > 1 | 5328 | 7 | 2864 | 237244 > > *Remaining contexts total: 23456 bytes (total_bytes) , > 12345(used_bytes), 11,111(free_bytes)* > ``` > Please find attached an updated patch with this change. The file previously used to store spilled statistics has been removed. Instead, a cumulative total of the remaining/spilled context statistics is now stored in the DSM segment, which is displayed as follows. postgres=# select * from pg_get_process_memory_contexts('352966', false); * name * | ident | type | path | *total_bytes* | *total_nblocks* | *free_bytes *| *free_chunks *| *used_bytes* | pi d ------------------------------+-------+----------+--------+-------------+---------------+------------+-------------+------------+---- ---- TopMemoryContext | | AllocSet | {0} | 97696 | 5 | 14288 | 11 | 83408 | 352 966 . . . MdSmgr | | AllocSet | {0,18} | 8192 | 1 | 7424 | 0 | 768 | 352 966 * Remaining Totals* | | | | *1756016* | *188 *| *658584 *| * 132* | * 1097432 *| 352 966 (7129 rows) ----- I believe this serves as a good compromise between completeness and avoiding the overhead of file handling. However, I am open to reintroducing file handling if displaying the complete statistics of the remaining contexts prove to be more important. All the known bugs in the patch have been fixed. In summary, one DSA per PostgreSQL process is used to share the statistics of that process. A DSA is created by the first client backend that requests memory context statistics, and it is pinned for all future requests to that process. A handle to this DSA is shared between the client and the publishing process using fixed shared memory. The fixed shared memory consists of an array of size MaxBackends + auxiliary processes, indexed by procno. Each element in this array is less than 100 bytes in size. A PostgreSQL process uses a condition variable to signal a waiting client backend once it has finished publishing the statistics. If, for some reason, the signal is not sent, the waiting client backend will time out. When statistics of a local backend is requested, this function returns the following WARNING and exits, since this can be handled by an existing function which doesn't require a DSA. WARNING: cannot return statistics for local backend HINT: Use pg_get_backend_memory_contexts instead Looking forward to your review. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2024-11-20T14:01:04Z
On Wed, Nov 20, 2024 at 2:39 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > > Hi, > >> To achieve both completeness and avoid writing to a file, I can consider >> displaying the numbers for the remaining contexts as a cumulative total >> at the end of the output. >> >> Something like follows: >> ``` >> postgres=# select * from pg_get_process_memory_contexts('237244', false); >> name | ident | type | path | total_bytes | tot >> al_nblocks | free_bytes | free_chunks | used_bytes | pid >> ---------------------------------------+------------------------------------------------+----------+--------------+-------------+---- >> -----------+------------+-------------+------------+-------- >> TopMemoryContext | | AllocSet | {0} | 97696 | >> 5 | 14288 | 11 | 83408 | 237244 >> search_path processing cache | | AllocSet | {0,1} | 8192 | >> 1 | 5328 | 7 | 2864 | 237244 >> Remaining contexts total: 23456 bytes (total_bytes) , 12345(used_bytes), 11,111(free_bytes) >> >> ``` > > > Please find attached an updated patch with this change. The file previously used to > store spilled statistics has been removed. Instead, a cumulative total of the > remaining/spilled context statistics is now stored in the DSM segment, which is > displayed as follows. > > postgres=# select * from pg_get_process_memory_contexts('352966', false); > name | ident | type | path | total_bytes | total_nblocks | free_bytes | free_chunks | used_bytes | pi > d > ------------------------------+-------+----------+--------+-------------+---------------+------------+-------------+------------+---- > ---- > TopMemoryContext | | AllocSet | {0} | 97696 | 5 | 14288 | 11 | 83408 | 352 > 966 > . > . > . > MdSmgr | | AllocSet | {0,18} | 8192 | 1 | 7424 | 0 | 768 | 352 > 966 > Remaining Totals | | | | 1756016 | 188 | 658584 | 132 | 1097432 | 352 > 966 > (7129 rows) > ----- > > I believe this serves as a good compromise between completeness > and avoiding the overhead of file handling. However, I am open to > reintroducing file handling if displaying the complete statistics of the > remaining contexts prove to be more important. > > All the known bugs in the patch have been fixed. > > In summary, one DSA per PostgreSQL process is used to share > the statistics of that process. A DSA is created by the first client > backend that requests memory context statistics, and it is pinned > for all future requests to that process. > A handle to this DSA is shared between the client and the publishing > process using fixed shared memory. The fixed shared memory consists > of an array of size MaxBackends + auxiliary processes, indexed > by procno. Each element in this array is less than 100 bytes in size. > > A PostgreSQL process uses a condition variable to signal a waiting client > backend once it has finished publishing the statistics. If, for some reason, > the signal is not sent, the waiting client backend will time out. How does the process know that the client backend has finished reading stats and it can be refreshed? What happens, if the next request for memory context stats comes before first requester has consumed the statistics it requested? Does the shared memory get deallocated when the backend which allocated it exits? > > When statistics of a local backend is requested, this function returns the following > WARNING and exits, since this can be handled by an existing function which > doesn't require a DSA. > > WARNING: cannot return statistics for local backend > HINT: Use pg_get_backend_memory_contexts instead How about using pg_get_backend_memory_contexts() for both - local as well as other backend? Let PID argument default to NULL which would indicate local backend, otherwise some other backend? -- Best Wishes, Ashutosh Bapat -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-11-22T13:03:36Z
Hi, How does the process know that the client backend has finished reading > stats and it can be refreshed? What happens, if the next request for > memory context stats comes before first requester has consumed the > statistics it requested? > > A process that's copying its statistics does not need to know that. Whenever it receives a signal to copy statistics, it goes ahead and copies the latest statistics to the DSA after acquiring an exclusive lwlock. A requestor takes a lock before it starts consuming the statistics. If the next request comes while the first requestor is consuming the statistics, the publishing process will wait on lwlock to be released by the consuming process before it can write the statistics. If the next request arrives before the first requester begins consuming the statistics, the publishing process will acquire the lock and overwrite the earlier statistics with the most recent ones. As a result, both the first and second requesters will consume the updated statistics. Does the shared memory get deallocated when the backend which > allocated it exits? > > Memory in the DSA is allocated by a postgres process and deallocated by the client backend for each request. Both the publishing postgres process and the client backend detach from the DSA at the end of each request. However, the DSM segment(s) persist even after all the processes exit and are only destroyed upon a server restart. Each DSA is associated with the procNumber of a postgres process and can be re-used by any future process with the same procNumber. > > > > When statistics of a local backend is requested, this function returns > the following > > WARNING and exits, since this can be handled by an existing function > which > > doesn't require a DSA. > > > > WARNING: cannot return statistics for local backend > > HINT: Use pg_get_backend_memory_contexts instead > > How about using pg_get_backend_memory_contexts() for both - local as > well as other backend? Let PID argument default to NULL which would > indicate local backend, otherwise some other backend? > > I don't see much value in combining the two, specially since with pg_get_process_memory_contexts() we can query both the postgres backend and a background process, the name pg_get_backend_memory_context() would be inaccurate and I am not sure whether a change to rename the existing function would be welcome. Please find an updated patch which fixes an issue seen in CI runs. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2024-11-25T04:54:47Z
On Fri, Nov 22, 2024 at 6:33 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > > Hi, > >> How does the process know that the client backend has finished reading >> stats and it can be refreshed? What happens, if the next request for >> memory context stats comes before first requester has consumed the >> statistics it requested? >> > A process that's copying its statistics does not need to know that. > Whenever it receives a signal to copy statistics, it goes ahead and > copies the latest statistics to the DSA after acquiring an exclusive > lwlock. > > A requestor takes a lock before it starts consuming the statistics. > If the next request comes while the first requestor is consuming the > statistics, the publishing process will wait on lwlock to be released > by the consuming process before it can write the statistics. > If the next request arrives before the first requester begins consuming > the statistics, the publishing process will acquire the lock and overwrite > the earlier statistics with the most recent ones. > As a result, both the first and second requesters will consume the > updated statistics. IIUC, the publisher and the consumer processes, both, use the same LWLock. Publisher acquires an exclusive lock. Does consumer acquire SHARED lock? The publisher process might be in a transaction, processing a query or doing something else. If it has to wait for an LWLock may affect its performance. This will become even more visible if the client backend is trying to diagnose a slow running query. Have we tried to measure how long the publisher might have to wait for an LWLock while the consumer is consuming statistics OR what is the impact of this wait? >> > >> > When statistics of a local backend is requested, this function returns the following >> > WARNING and exits, since this can be handled by an existing function which >> > doesn't require a DSA. >> > >> > WARNING: cannot return statistics for local backend >> > HINT: Use pg_get_backend_memory_contexts instead >> >> How about using pg_get_backend_memory_contexts() for both - local as >> well as other backend? Let PID argument default to NULL which would >> indicate local backend, otherwise some other backend? >> > I don't see much value in combining the two, specially since with > pg_get_process_memory_contexts() we can query both the postgres > backend and a background process, the name pg_get_backend_memory_context() > would be inaccurate and I am not sure whether a change to rename the > existing function would be welcome. Having two separate functions for the same functionality isn't a friendly user interface. Playing a bit with pg_terminate_backend() which is another function dealing with backends to understand a. what does it do to its own backend and b. which processes are considered backends. 1. pg_terminate_backend() allows to terminate the backend from which it is fired. #select pid, application_name, backend_type, pg_terminate_backend(pid) from pg_stat_activity; FATAL: terminating connection due to administrator command server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. The connection to the server was lost. Attempting reset: Succeeded. 2. It considers autovacuum launcher and logical replication launcher as postgres backends but not checkpointer, background writer and walwriter. #select pid, application_name, backend_type, pg_terminate_backend(pid) from pg_stat_activity where pid <> pg_backend_pid(); WARNING: PID 644887 is not a PostgreSQL backend process WARNING: PID 644888 is not a PostgreSQL backend process WARNING: PID 644890 is not a PostgreSQL backend process pid | application_name | backend_type | pg_terminate_backend --------+------------------+------------------------------+---------------------- 645636 | | autovacuum launcher | t 645677 | | logical replication launcher | t 644887 | | checkpointer | f 644888 | | background writer | f 644890 | | walwriter | f (5 rows) In that sense you are correct that pg_get_backend_memory_context() should not provide context information of WAL writer process for example. But pg_get_process_memory_contexts() would be expected to provide its own memory context information instead of redirecting to another function through a WARNING. It could do that redirection itself. That will also prevent the functions' output format going out of sync. -- Best Wishes, Ashutosh Bapat -
Re: Enhancing Memory Context Statistics Reporting
Tomas Vondra <tomas@vondra.me> — 2024-11-27T16:19:56Z
Hi, I took a quick look at the patch today. Overall, I think this would be very useful, I've repeatedly needed to inspect why a backend uses so much memory, and I ended up triggering MemoryContextStats() from gdb. This would be more convenient / safer. So +1 to the patch intent. A couple review comments: 1) I read through the thread, and in general I agree with the reasoning for removing the file part - it seems perfectly fine to just dump as much as we can fit into a buffer, and then summarize the rest. But do we need to invent a "new" limit here? The other places logging memory contexts do something like this: MemoryContextStatsDetail(TopMemoryContext, 100, 100, false); Which means we only print the 100 memory contexts at the top, and that's it. Wouldn't that give us a reasonable memory limit too? 2) I see the function got renamed to pg_get_process_memory_contexts(), bu the comment still says pg_get_remote_backend_memory_contexts(). 3) I don't see any SGML docs for this new function. I was a bit unsure what the "summary" argument is meant to do. The comment does not explain that either. 4) I wonder if the function needs to return PID. I mean, the caller knows which PID it is for, so it seems rather unnecessary. 5) In the "summary" mode, it might be useful to include info about how many child contexts were aggregated. It's useful to know whether there was 1 child or 10000 children. In the regular (non-summary) mode it'd always be "1", probably, but maybe it'd interact with the limit in (1). Not sure. 6) I feel a bit uneasy about the whole locking / communication scheme. In particular, I'm worried about lockups, deadlocks, etc. So I decided to do a trivial stress-test - just run the new function through pgbench with many clients. The memstats.sql script does just this: SELECT * FROM pg_get_process_memory_contexts( (SELECT pid FROM pg_stat_activity WHERE pid != pg_backend_pid() ORDER BY random() LIMIT 1) , false); where the inner query just picks a PID for some other backend, and asks for memory context stats for that. And just run it like this on a scale 1 pgbench database: pgbench -n -f memstats.sql -c 10 test And it gets stuck *immediately*. I've seen it to wait for other client backends and auxiliary processes like autovacuum launcher. This is absolutely idle system, there's no reason why a process would not respond almost immediately. I wonder if e.g. autovacuum launcher may not be handling these requests, or what if client backends can wait in a cycle. IIRC condition variables are not covered by a deadlock detector, so that would be an issue. But maybe I remember wrong? 7) I've also seen this error: pgbench: error: client 6 script 0 aborted in command 0 query 0: \ ERROR: can't attach the same segment more than once I haven't investigated it, but it seems like a problem handling errors, where we fail to detach from a segment after a timeout. I may be wrong, but it might be related to this: > I opted for DSAs over DSMs to enable memory reuse by freeing > segments for subsequent statistics copies of the same backend, > without needing to recreate DSMs for each request. I feel like this might be a premature optimization - I don't have a clear idea how expensive it is to create DSM per request, but my intuition is that it's cheaper than processing the contexts and generating the info. I'd just remove that, unless someone demonstrates it really matters. I don't really worry about how expensive it is to process a request (within reason, of course) - it will happen only very rarely. It's more important to make sure there's no overhead when no one asks the backend for memory context info, and simplicity. Also, how expensive it is to just keep the DSA "just in case"? Imagine someone asks for the memory context info once - isn't it a was to still keep the DSA? I don't recall how much resources could that be. I don't have a clear opinion on that, I'm more asking for opinions. 8) Two minutes seems pretty arbitrary, and also quite high. If a timeout is necessary, I think it should not be hard-coded. regards -- Tomas Vondra -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-11-28T23:23:57Z
Hi Tomas, Thank you for the review. > > > 1) I read through the thread, and in general I agree with the reasoning > for removing the file part - it seems perfectly fine to just dump as > much as we can fit into a buffer, and then summarize the rest. But do we > need to invent a "new" limit here? The other places logging memory > contexts do something like this: > > MemoryContextStatsDetail(TopMemoryContext, 100, 100, false); > > Which means we only print the 100 memory contexts at the top, and that's > it. Wouldn't that give us a reasonable memory limit too? > > I think this prints more than 100 memory contexts, since 100 denotes the max_level and contexts at each level could have upto 100 children. This limit seems much higher than what I am currently storing in DSA which is approx. 7000 contexts. I will verify this again. > 2) I see the function got renamed to pg_get_process_memory_contexts(), > bu the comment still says pg_get_remote_backend_memory_contexts(). > > Fixed > > 3) I don't see any SGML docs for this new function. I was a bit unsure > what the "summary" argument is meant to do. The comment does not explain > that either. > > Added docs. Intention behind adding a summary argument is to report statistics of contexts at level 0 and 1 i.e TopMemoryContext and its immediate children. 4) I wonder if the function needs to return PID. I mean, the caller > knows which PID it is for, so it seems rather unnecessary. > > Perhaps it can be used to ascertain that the information indeed belongs to the requested pid. 5) In the "summary" mode, it might be useful to include info about how > many child contexts were aggregated. It's useful to know whether there > was 1 child or 10000 children. In the regular (non-summary) mode it'd > always be "1", probably, but maybe it'd interact with the limit in (1). > Not sure. > > Sure, I will add this in the next iteration. > > 6) I feel a bit uneasy about the whole locking / communication scheme. > In particular, I'm worried about lockups, deadlocks, etc. So I decided > to do a trivial stress-test - just run the new function through pgbench > with many clients. > > The memstats.sql script does just this: > > SELECT * FROM pg_get_process_memory_contexts( > (SELECT pid FROM pg_stat_activity > WHERE pid != pg_backend_pid() > ORDER BY random() LIMIT 1) > , false); > > where the inner query just picks a PID for some other backend, and asks > for memory context stats for that. > > And just run it like this on a scale 1 pgbench database: > > pgbench -n -f memstats.sql -c 10 test > > And it gets stuck *immediately*. I've seen it to wait for other client > backends and auxiliary processes like autovacuum launcher. > > This is absolutely idle system, there's no reason why a process would > not respond almost immediately. In my reproduction, this issue occurred because the process was terminated while the requesting backend was waiting on the condition variable to be signaled by it. I don’t see any solution other than having the waiting client backend timeout using ConditionVariableTimedSleep. In the patch, since the timeout was set to a high value, pgbench ended up stuck waiting for the timeout to occur. The failure happens less frequently after I added an additional check for the process's existence, but it cannot be entirely avoided. This is because a process can terminate after we check for its existence but before it signals the client. In such cases, the client will not receive any signal. I wonder if e.g. autovacuum launcher may > not be handling these requests, or what if client backends can wait in a > cycle. Did not see a cyclic wait in client backends due to the pgbench stress test. > > 7) I've also seen this error: > > pgbench: error: client 6 script 0 aborted in command 0 query 0: \ > ERROR: can't attach the same segment more than once > I haven't investigated it, but it seems like a problem handling errors, > where we fail to detach from a segment after a timeout. Thanks for the hint, fixed by adding a missing call to dsa_detach after timeout. > > > I opted for DSAs over DSMs to enable memory reuse by freeing > > segments for subsequent statistics copies of the same backend, > > without needing to recreate DSMs for each request. > > I feel like this might be a premature optimization - I don't have a > clear idea how expensive it is to create DSM per request, but my > intuition is that it's cheaper than processing the contexts and > generating the info. > > I'd just remove that, unless someone demonstrates it really matters. I > don't really worry about how expensive it is to process a request > (within reason, of course) - it will happen only very rarely. It's more > important to make sure there's no overhead when no one asks the backend > for memory context info, and simplicity. > > Also, how expensive it is to just keep the DSA "just in case"? Imagine > someone asks for the memory context info once - isn't it a was to still > keep the DSA? I don't recall how much resources could that be. > > I don't have a clear opinion on that, I'm more asking for opinions. Imagining a tool that periodically queries the backends for statistics, it would be beneficial to avoid recreating the DSAs for each call. Currently, DSAs of size 1MB per process (i.e., a maximum of 1MB * (MaxBackends + auxiliary processes)) would be created and pinned for subsequent reporting. This size does not seem excessively high, even for approx 100 backends and auxiliary processes. > 8) Two minutes seems pretty arbitrary, and also quite high. If a timeout > is necessary, I think it should not be hard-coded. > > Not sure which is the ideal value. Changed it to 15 secs and added a #define as of now. Something that gives enough time for the process to respond but does not hold up the client for too long would be ideal. 15 secs seem to be not enough for github CI tests, which fail with timeout error with this setting. PFA an updated patch with the above changes.
-
Re: Enhancing Memory Context Statistics Reporting
Tomas Vondra <tomas@vondra.me> — 2024-11-29T00:21:30Z
On 11/29/24 00:23, Rahila Syed wrote: > Hi Tomas, > > Thank you for the review. > > > > 1) I read through the thread, and in general I agree with the reasoning > for removing the file part - it seems perfectly fine to just dump as > much as we can fit into a buffer, and then summarize the rest. But do we > need to invent a "new" limit here? The other places logging memory > contexts do something like this: > > MemoryContextStatsDetail(TopMemoryContext, 100, 100, false); > > Which means we only print the 100 memory contexts at the top, and that's > it. Wouldn't that give us a reasonable memory limit too? > > I think this prints more than 100 memory contexts, since 100 denotes the > max_level > and contexts at each level could have upto 100 children. This limit > seems much higher than > what I am currently storing in DSA which is approx. 7000 contexts. I > will verify this again. > Yeah, you may be right. I don't remember what exactly that limit does. > > 2) I see the function got renamed to pg_get_process_memory_contexts(), > bu the comment still says pg_get_remote_backend_memory_contexts(). > > Fixed > > > 3) I don't see any SGML docs for this new function. I was a bit unsure > what the "summary" argument is meant to do. The comment does not explain > that either. > > Added docs. > Intention behind adding a summary argument is to report statistics of > contexts at level 0 > and 1 i.e TopMemoryContext and its immediate children. > OK > 4) I wonder if the function needs to return PID. I mean, the caller > knows which PID it is for, so it seems rather unnecessary. > > Perhaps it can be used to ascertain that the information indeed belongs to > the requested pid. > I find that a bit ... suspicious. By this logic we'd include the input parameters in every result, but we don't. So why is this case different? > 5) In the "summary" mode, it might be useful to include info about how > many child contexts were aggregated. It's useful to know whether there > was 1 child or 10000 children. In the regular (non-summary) mode it'd > always be "1", probably, but maybe it'd interact with the limit in (1). > Not sure. > > Sure, I will add this in the next iteration. > OK > > 6) I feel a bit uneasy about the whole locking / communication scheme. > In particular, I'm worried about lockups, deadlocks, etc. So I decided > to do a trivial stress-test - just run the new function through pgbench > with many clients. > > The memstats.sql script does just this: > > SELECT * FROM pg_get_process_memory_contexts( > (SELECT pid FROM pg_stat_activity > WHERE pid != pg_backend_pid() > ORDER BY random() LIMIT 1) > , false); > > where the inner query just picks a PID for some other backend, and asks > for memory context stats for that. > > And just run it like this on a scale 1 pgbench database: > > pgbench -n -f memstats.sql -c 10 test > > And it gets stuck *immediately*. I've seen it to wait for other client > backends and auxiliary processes like autovacuum launcher. > > This is absolutely idle system, there's no reason why a process would > not respond almost immediately. > > > In my reproduction, this issue occurred because the process was terminated > while the requesting backend was waiting on the condition variable to be > signaled by it. I don’t see any solution other than having the waiting > client > backend timeout using ConditionVariableTimedSleep. > > In the patch, since the timeout was set to a high value, pgbench ended > up stuck > waiting for the timeout to occur. The failure happens less frequently > after I added an > additional check for the process's existence, but it cannot be entirely > avoided. This is because a process can terminate after we check for its > existence but > before it signals the client. In such cases, the client will not receive > any signal. > Hmmm, I see. I guess there's no way to know if a process responds to us, but I guess it should be possible to wake up regularly and check if the process still exists? Wouldn't that solve the case you mentioned? > I wonder if e.g. autovacuum launcher may > not be handling these requests, or what if client backends can wait in a > cycle. > > > Did not see a cyclic wait in client backends due to the pgbench stress test. > Not sure, but if I modify the query to only request memory contexts from non-client processes, i.e. SELECT * FROM pg_get_process_memory_contexts( (SELECT pid FROM pg_stat_activity WHERE pid != pg_backend_pid() AND backend_type != 'client backend' ORDER BY random() LIMIT 1) , false); then it gets stuck and reports this: pgbench -n -f select.sql -c 4 -T 10 test pgbench (18devel) WARNING: Wait for 105029 process to publish stats timed out, ... But process 105029 still very much exists, and it's the checkpointer: $ ps ax | grep 105029 105029 ? Ss 0:00 postgres: checkpointer OTOH if I modify the script to only look at client backends, and wait until the processes get "stuck" (i.e. waiting on the condition variable, consuming 0% CPU), I get this: $ pgbench -n -f select.sql -c 4 -T 10 test pgbench (18devel) WARNING: Wait for 107146 process to publish stats timed out, try again WARNING: Wait for 107144 process to publish stats timed out, try again WARNING: Wait for 107147 process to publish stats timed out, try again transaction type: select.sql ... but when it gets 'stuck', most of the processes are still very much running (but waiting for contexts from some other process). In the above example I see this: 107144 ? Ss 0:02 postgres: user test [local] SELECT 107145 ? Ss 0:01 postgres: user test [local] SELECT 107147 ? Ss 0:02 postgres: user test [local] SELECT So yes, 107146 seems to be gone. But why would that block getting info from 107144 and 107147? Maybe that's acceptable, but couldn't this be an issue with short-lived connections, making it hard to implement the kind of automated collection of stats that you envision. If it hits this kind of timeouts often, it'll make it hard to reliably collect info. No? > > > I opted for DSAs over DSMs to enable memory reuse by freeing > > segments for subsequent statistics copies of the same backend, > > without needing to recreate DSMs for each request. > > I feel like this might be a premature optimization - I don't have a > clear idea how expensive it is to create DSM per request, but my > intuition is that it's cheaper than processing the contexts and > generating the info. > > I'd just remove that, unless someone demonstrates it really matters. I > don't really worry about how expensive it is to process a request > (within reason, of course) - it will happen only very rarely. It's more > important to make sure there's no overhead when no one asks the backend > for memory context info, and simplicity. > > Also, how expensive it is to just keep the DSA "just in case"? Imagine > someone asks for the memory context info once - isn't it a was to still > keep the DSA? I don't recall how much resources could that be. > > I don't have a clear opinion on that, I'm more asking for opinions. > > > Imagining a tool that periodically queries the backends for statistics, > it would be beneficial to avoid recreating the DSAs for each call. I think it would be nice if you backed this with some numbers. I mean, how expensive is it to create/destroy the DSA? How does it compare to the other stuff this function needs to do? > Currently, DSAs of size 1MB per process > (i.e., a maximum of 1MB * (MaxBackends + auxiliary processes)) > would be created and pinned for subsequent reporting. This size does > not seem excessively high, even for approx 100 backends and > auxiliary processes. > That seems like a pretty substantial amount of memory reserved for each connection. IMHO the benefits would have to be pretty significant to justify this, especially considering it's kept "forever", even if you run the function only once per day. > > 8) Two minutes seems pretty arbitrary, and also quite high. If a timeout > is necessary, I think it should not be hard-coded. > > Not sure which is the ideal value. Changed it to 15 secs and added a > #define as of now. > Something that gives enough time for the process to respond but > does not hold up the client for too long would be ideal. 15 secs seem to > be not enough for github CI tests, which fail with timeout error with > this setting. > > PFA an updated patch with the above changes. Why not to make this a parameter of the function? With some sensible default, but easy to override. regards -- Tomas Vondra -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-12-03T19:09:02Z
Hi, > > > > 4) I wonder if the function needs to return PID. I mean, the caller > > knows which PID it is for, so it seems rather unnecessary. > > > > Perhaps it can be used to ascertain that the information indeed belongs > to > > the requested pid. > > > > I find that a bit ... suspicious. By this logic we'd include the input > parameters in every result, but we don't. So why is this case different? > > This was added to address a review suggestion. I had left it in case anyone found it useful for verification. Previously, I included a check for scenarios where multiple processes could write to the same shared memory. Now, each process has a separate shared memory space identified by pgprocno, making it highly unlikely for the receiving process to see another process's memory dump. Such a situation could theoretically occur if another process were mapped to the same pgprocno, although I’m not sure how likely that is. That said, I’ve added a check in the receiver to ensure the PID written in the shared memory matches the PID for which the dump is requested. This guarantees that a user will never see the memory dump of another process. Given this, I’m fine with removing the pid column if it helps to make the output more readable. > 5) In the "summary" mode, it might be useful to include info about how > > many child contexts were aggregated. It's useful to know whether > there > > was 1 child or 10000 children. In the regular (non-summary) mode it'd > > always be "1", probably, but maybe it'd interact with the limit in > (1). > > Not sure. > > > > Sure, I will add this in the next iteration. > > > > OK > I have added this information as a column named "num_agg_contexts", which indicates the number of contexts whose statistics have been aggregated/added for a particular output. In summary mode, all the child contexts of a given level-1 context are aggregated, and their statistics are presented as part of the parent context's statistics. In this case, num_agg_contexts provides the count of all child contexts under a given level-1 context. In regular (non-summary) mode, this column shows a value of 1, meaning the statistics correspond to a single context, with all context statistics displayed individually. In this mode an aggregate result is displayed if the number of contexts exceed the DSA size limit. In this case the num_agg_contexts will display the number of the remaining contexts. > > > In the patch, since the timeout was set to a high value, pgbench ended > > up stuck > > waiting for the timeout to occur. The failure happens less frequently > > after I added an > > additional check for the process's existence, but it cannot be entirely > > avoided. This is because a process can terminate after we check for its > > existence but > > before it signals the client. In such cases, the client will not receive > > any signal. > > > > Hmmm, I see. I guess there's no way to know if a process responds to us, > but I guess it should be possible to wake up regularly and check if the > process still exists? Wouldn't that solve the case you mentioned? > > I have fixed it accordingly in the attached patch by waking up after every 5 seconds to check if the process exists and sleeping again if the wake-up condition is not satisfied. The number of such tries is limited to 20. So, the total wait time can be 100 seconds. I will make the re-tries configurable, inline with your suggestion to be able to override the default waiting time. > > I wonder if e.g. autovacuum launcher may > > not be handling these requests, or what if client backends can wait > in a > > cycle. > > > > > > Did not see a cyclic wait in client backends due to the pgbench stress > test. > > > > Not sure, but if I modify the query to only request memory contexts from > non-client processes, i.e. > > SELECT * FROM pg_get_process_memory_contexts( > (SELECT pid FROM pg_stat_activity > WHERE pid != pg_backend_pid() > AND backend_type != 'client backend' > ORDER BY random() LIMIT 1) > , false); > > then it gets stuck and reports this: > > pgbench -n -f select.sql -c 4 -T 10 test > pgbench (18devel) > WARNING: Wait for 105029 process to publish stats timed out, ... > > But process 105029 still very much exists, and it's the checkpointer: > > In the case of checkpointer, I also see some wait time after running the tests that you mentioned, but it eventually completes the request in my runs. > $ ps ax | grep 105029 > 105029 ? Ss 0:00 postgres: checkpointer > > OTOH if I modify the script to only look at client backends, and wait > until the processes get "stuck" (i.e. waiting on the condition variable, > consuming 0% CPU), I get this: > > $ pgbench -n -f select.sql -c 4 -T 10 test > pgbench (18devel) > WARNING: Wait for 107146 process to publish stats timed out, try again > WARNING: Wait for 107144 process to publish stats timed out, try again > WARNING: Wait for 107147 process to publish stats timed out, try again > transaction type: select.sql > ... > > but when it gets 'stuck', most of the processes are still very much > running (but waiting for contexts from some other process). In the above > example I see this: > > 107144 ? Ss 0:02 postgres: user test [local] SELECT > 107145 ? Ss 0:01 postgres: user test [local] SELECT > 107147 ? Ss 0:02 postgres: user test [local] SELECT > > So yes, 107146 seems to be gone. But why would that block getting info > from 107144 and 107147? > > Most likely 107144 and/or 107147 must also be waiting for 107146 which is gone. Something like 107144 -> 107147 -> 107146(dead) or 107144 ->107146(dead) and 107147->107146(dead). Maybe that's acceptable, but couldn't this be an issue with short-lived > connections, making it hard to implement the kind of automated > collection of stats that you envision. If it hits this kind of timeouts > often, it'll make it hard to reliably collect info. No? Yes, if there is a chain of waiting clients due to a process no longer existing, the waiting time to receive information will increase. However, as long as a failed a request caused by a non-existent process is detected promptly, the wait time should remain manageable, allowing other waiting clients to obtain the requested information from the existing processes. In such cases, it might be necessary to experiment with the waiting times at the receiving client. Making the waiting time user-configurable, as you suggested, by passing it as an argument to the function, could help address this scenario. Thanks for highlighting this, I will test this some more. > > > > > I opted for DSAs over DSMs to enable memory reuse by freeing > > > segments for subsequent statistics copies of the same backend, > > > without needing to recreate DSMs for each request. > > > > I feel like this might be a premature optimization - I don't have a > > clear idea how expensive it is to create DSM per request, but my > > intuition is that it's cheaper than processing the contexts and > > generating the info. > > > > I'd just remove that, unless someone demonstrates it really matters. > I > > don't really worry about how expensive it is to process a request > > (within reason, of course) - it will happen only very rarely. It's > more > > important to make sure there's no overhead when no one asks the > backend > > for memory context info, and simplicity. > > > > Also, how expensive it is to just keep the DSA "just in case"? > Imagine > > someone asks for the memory context info once - isn't it a was to > still > > keep the DSA? I don't recall how much resources could that be. > > > > I don't have a clear opinion on that, I'm more asking for opinions. > > > > > > Imagining a tool that periodically queries the backends for statistics, > > it would be beneficial to avoid recreating the DSAs for each call. > > I think it would be nice if you backed this with some numbers. I mean, > how expensive is it to create/destroy the DSA? How does it compare to > the other stuff this function needs to do? > > After instrumenting the code with timestamps, I observed that DSA creation accounts for approximately 17% to 26% of the total execution time of the function pg_get_process_memory_contexts(). > Currently, DSAs of size 1MB per process > > (i.e., a maximum of 1MB * (MaxBackends + auxiliary processes)) > > would be created and pinned for subsequent reporting. This size does > > not seem excessively high, even for approx 100 backends and > > auxiliary processes. > > > > That seems like a pretty substantial amount of memory reserved for each > connection. IMHO the benefits would have to be pretty significant to > justify this, especially considering it's kept "forever", even if you > run the function only once per day. > > I can reduce the initial segment size to DSA_MIN_SEGMENT_SIZE, which is 256KB per process. If needed, this could grow up to 16MB based on the current settings. However, for the scenario you mentioned, it would be ideal to have a mechanism to mark a pinned DSA (using dsa_pin()) for deletion if it is not used/attached within a specified duration. Alternatively, I could avoid using dsa_pin() altogether, allowing the DSA to be automatically destroyed once all processes detach from it, and recreate it for a new request. At the moment, I am unsure which approach is most feasible. Any suggestions would be greatly appreciated. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Tomas Vondra <tomas@vondra.me> — 2024-12-03T21:09:11Z
On 12/3/24 20:09, Rahila Syed wrote: > > Hi, > > > > > > 4) I wonder if the function needs to return PID. I mean, the > caller > > knows which PID it is for, so it seems rather unnecessary. > > > > Perhaps it can be used to ascertain that the information indeed > belongs to > > the requested pid. > > > > I find that a bit ... suspicious. By this logic we'd include the input > parameters in every result, but we don't. So why is this case different? > > > This was added to address a review suggestion. I had left it in case > anyone found it useful > for verification. > Previously, I included a check for scenarios where multiple processes > could write to the same > shared memory. Now, each process has a separate shared memory space > identified by > pgprocno, making it highly unlikely for the receiving process to see > another process's memory > dump. > Such a situation could theoretically occur if another process were > mapped to the same > pgprocno, although I’m not sure how likely that is. That said, I’ve > added a check in the receiver > to ensure the PID written in the shared memory matches the PID for which > the dump is > requested. > This guarantees that a user will never see the memory dump of another > process. > Given this, I’m fine with removing the pid column if it helps to make > the output more readable. > I'd just remove that. I agree it might have been useful with the single chunk of shared memory, but I think with separate chunks it's not very useful. And if we can end up with multiple processed getting the same pgprocno I guess we have way bigger problems, this won't fix that. > > 5) In the "summary" mode, it might be useful to include info > about how > > many child contexts were aggregated. It's useful to know > whether there > > was 1 child or 10000 children. In the regular (non-summary) > mode it'd > > always be "1", probably, but maybe it'd interact with the > limit in (1). > > Not sure. > > > > Sure, I will add this in the next iteration. > > > > OK > > > I have added this information as a column named "num_agg_contexts", > which indicates > the number of contexts whose statistics have been aggregated/added for a > particular output. > > In summary mode, all the child contexts of a given level-1 context are > aggregated, and > their statistics are presented as part of the parent context's > statistics. In this case, > num_agg_contexts provides the count of all child contexts under a given > level-1 context. > > In regular (non-summary) mode, this column shows a value of 1, meaning > the statistics > correspond to a single context, with all context statistics displayed > individually. In this mode > an aggregate result is displayed if the number of contexts exceed the > DSA size limit. In > this case the num_agg_contexts will display the number of the remaining > contexts. > OK > > > > In the patch, since the timeout was set to a high value, pgbench ended > > up stuck > > waiting for the timeout to occur. The failure happens less frequently > > after I added an > > additional check for the process's existence, but it cannot be > entirely > > avoided. This is because a process can terminate after we check > for its > > existence but > > before it signals the client. In such cases, the client will not > receive > > any signal. > > > > Hmmm, I see. I guess there's no way to know if a process responds to us, > but I guess it should be possible to wake up regularly and check if the > process still exists? Wouldn't that solve the case you mentioned? > > I have fixed it accordingly in the attached patch by waking up after > every 5 seconds > to check if the process exists and sleeping again if the wake-up condition > is not satisfied. The number of such tries is limited to 20. So, the > total wait > time can be 100 seconds. I will make the re-tries configurable, inline > with your > suggestion to be able to override the default waiting time. > Makes sense, although 100 seconds seems a bit weird, it seems we usually pick "natural" values like 60s, or multiples of that. But if it's configurable, that's not a huge issue. Could the process wake up earlier than the timeout, say if it gets EINT signal? That'd break the "total timeout is 100 seconds", and it would be better to check that explicitly. Not sure if this can happen, though. One thing I'd maybe consider is starting with a short timeout, and gradually increasing it until e.g. 5 seconds (or maybe just 1 second would be perfectly fine, IMHO). With the current coding it means we either get the response right away, or wait 5+ seconds. That's a big huge jump. If we start e.g. with 10ms, and then gradually multiply it by 1.2, it means we only wait "0-20% extra" on average. But perhaps this is very unlikely and not worth the complexity. > > > I wonder if e.g. autovacuum launcher may > > not be handling these requests, or what if client backends can > wait in a > > cycle. > > > > > > Did not see a cyclic wait in client backends due to the pgbench > stress test. > > > > Not sure, but if I modify the query to only request memory contexts from > non-client processes, i.e. > > SELECT * FROM pg_get_process_memory_contexts( > (SELECT pid FROM pg_stat_activity > WHERE pid != pg_backend_pid() > AND backend_type != 'client backend' > ORDER BY random() LIMIT 1) > , false); > > then it gets stuck and reports this: > > pgbench -n -f select.sql -c 4 -T 10 test > pgbench (18devel) > WARNING: Wait for 105029 process to publish stats timed out, ... > > But process 105029 still very much exists, and it's the checkpointer: > > In the case of checkpointer, I also see some wait time after running the > tests that you mentioned, but it eventually completes the request in my > runs. > OK, but why should it even wait that long? Surely the checkpointer should be able to report memory contexts too? > > $ ps ax | grep 105029 > 105029 ? Ss 0:00 postgres: checkpointer > > OTOH if I modify the script to only look at client backends, and wait > until the processes get "stuck" (i.e. waiting on the condition variable, > consuming 0% CPU), I get this: > > $ pgbench -n -f select.sql -c 4 -T 10 test > pgbench (18devel) > WARNING: Wait for 107146 process to publish stats timed out, try again > WARNING: Wait for 107144 process to publish stats timed out, try again > WARNING: Wait for 107147 process to publish stats timed out, try again > transaction type: select.sql > ... > > but when it gets 'stuck', most of the processes are still very much > running (but waiting for contexts from some other process). In the above > example I see this: > > 107144 ? Ss 0:02 postgres: user test [local] SELECT > 107145 ? Ss 0:01 postgres: user test [local] SELECT > 107147 ? Ss 0:02 postgres: user test [local] SELECT > > So yes, 107146 seems to be gone. But why would that block getting info > from 107144 and 107147? > > Most likely 107144 and/or 107147 must also be waiting for 107146 which is > gone. Something like 107144 -> 107147 -> 107146(dead) or 107144 - >>107146(dead) > and 107147->107146(dead). > I think I forgot to mention only 107145 was waiting for 107146 (dead), and the other processes were waiting for 107145 in some way. But yeah, detecting the dead process would improve this, although it also shows the issues can "spread" easily. OTOH it's unlikely to have multiple pg_get_process_memory_contexts() queries pointing at each other like this - monitoring will just to that from one backend, and that's it. So not a huge issue. > > Maybe that's acceptable, but couldn't this be an issue with short-lived > connections, making it hard to implement the kind of automated > collection of stats that you envision. If it hits this kind of timeouts > often, it'll make it hard to reliably collect info. No? > > > Yes, if there is a chain of waiting clients due to a process no longer > existing, > the waiting time to receive information will increase. However, as long > as a failed > a request caused by a non-existent process is detected promptly, the > wait time should > remain manageable, allowing other waiting clients to obtain the > requested information > from the existing processes. > > In such cases, it might be necessary to experiment with the waiting > times at the receiving > client. Making the waiting time user-configurable, as you suggested, by > passing it as an > argument to the function, could help address this scenario. > Thanks for highlighting this, I will test this some more. > I think we should try very hard to make this work well without the user having to mess with the timeouts. These are exceptional conditions that happen only very rarely, which makes it hard to find good values. > > > > > > I opted for DSAs over DSMs to enable memory reuse by freeing > > > segments for subsequent statistics copies of the same backend, > > > without needing to recreate DSMs for each request. > > > > I feel like this might be a premature optimization - I don't > have a > > clear idea how expensive it is to create DSM per request, but my > > intuition is that it's cheaper than processing the contexts and > > generating the info. > > > > I'd just remove that, unless someone demonstrates it really > matters. I > > don't really worry about how expensive it is to process a request > > (within reason, of course) - it will happen only very rarely. > It's more > > important to make sure there's no overhead when no one asks > the backend > > for memory context info, and simplicity. > > > > Also, how expensive it is to just keep the DSA "just in case"? > Imagine > > someone asks for the memory context info once - isn't it a was > to still > > keep the DSA? I don't recall how much resources could that be. > > > > I don't have a clear opinion on that, I'm more asking for > opinions. > > > > > > Imagining a tool that periodically queries the backends for > statistics, > > it would be beneficial to avoid recreating the DSAs for each call. > > I think it would be nice if you backed this with some numbers. I mean, > how expensive is it to create/destroy the DSA? How does it compare to > the other stuff this function needs to do? > > After instrumenting the code with timestamps, I observed that DSA creation > accounts for approximately 17% to 26% of the total execution time of the > function > pg_get_process_memory_contexts(). > > > Currently, DSAs of size 1MB per process > > (i.e., a maximum of 1MB * (MaxBackends + auxiliary processes)) > > would be created and pinned for subsequent reporting. This size does > > not seem excessively high, even for approx 100 backends and > > auxiliary processes. > > > > That seems like a pretty substantial amount of memory reserved for each > connection. IMHO the benefits would have to be pretty significant to > justify this, especially considering it's kept "forever", even if you > run the function only once per day. > > I can reduce the initial segment size to DSA_MIN_SEGMENT_SIZE, which is > 256KB per process. If needed, this could grow up to 16MB based on the > current settings. > > However, for the scenario you mentioned, it would be ideal to have a > mechanism > to mark a pinned DSA (using dsa_pin()) for deletion if it is not used/ > attached within a > specified duration. Alternatively, I could avoid using dsa_pin() > altogether, allowing the > DSA to be automatically destroyed once all processes detach from it, and > recreate it > for a new request. > > At the moment, I am unsure which approach is most feasible. Any > suggestions would be > greatly appreciated. > I'm entirely unconcerned about the pg_get_process_memory_contexts() performance, within some reasonable limits. It's something executed every now and then - no one is going to complain it takes 10ms extra, measure tps with this function, etc. 17-26% seems surprisingly high, but Even 256kB is too much, IMHO. I'd just get rid of this optimization until someone complains and explains why it's worth it. Yes, let's make it fast, but I don't think we should optimize it at the expense of "regular workload" ... regards -- Tomas Vondra
-
Re: Enhancing Memory Context Statistics Reporting
amit <amitlangote09@gmail.com> — 2024-12-16T04:44:46Z
Hi Rahila, Thanks for working on this. I've wanted something like this a number of times to replace my current method of attaching gdb like everyone else I suppose. I have a question / suggestion about the interface. +Datum +pg_get_process_memory_contexts(PG_FUNCTION_ARGS) +{ + int pid = PG_GETARG_INT32(0); + bool get_summary = PG_GETARG_BOOL(1); IIUC, this always returns all memory contexts starting from TopMemoryContext, summarizing some child contexts if memory doesn't suffice. Would it be helpful to allow users to specify a context other than TopMemoryContext as the root? This could be particularly useful in cases where the information a user is looking for would otherwise be grouped under "Remaining Totals." Alternatively, is there a way to achieve this with the current function, perhaps by specifying a condition in the WHERE clause? -
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2024-12-16T12:51:53Z
Hi, Thanks for updating the patch and here are some comments: 'path' column of pg_get_process_memory_contexts() begins with 0, but that column of pg_backend_memory_contexts view begins with 1: =# select path FROM pg_get_process_memory_contexts('20271', false); path ------- {0} {0,1} {0,2} .. =# select path from pg_backend_memory_contexts; path ------- {1} {1,2} {1,3} ..asdf asdf Would it be better to begin with 1 to make them consistent? pg_log_backend_memory_contexts() does not allow non-superusers to execute by default since it can peek at other session information. pg_get_process_memory_contexts() does not have this restriction, but wouldn't it be necessary? When the target pid is the local backend, the HINT suggests using pg_get_backend_memory_contexts(), but this function is not described in the manual. How about suggesting pg_backend_memory_contexts view instead? =# select pg_get_process_memory_contexts('27041', false); WARNING: cannot return statistics for local backend HINT: Use pg_get_backend_memory_contexts instead There are no explanations about 'num_agg_contexts', but I thought the explanation like below would be useful. > I have added this information as a column named "num_agg_contexts", > which indicates > the number of contexts whose statistics have been aggregated/added for > a particular output. git apply caused some warnings: $ git apply v7-Function-to-report-memory-context-stats-of-any-backe.patch v7-Function-to-report-memory-context-stats-of-any-backe.patch:71: space before tab in indent. Requests to return the memory contexts of the backend with the v7-Function-to-report-memory-context-stats-of-any-backe.patch:72: space before tab in indent. specified process ID. This function can send the request to v7-Function-to-report-memory-context-stats-of-any-backe.patch:ldmv: space before tab in indent. both the backends and auxiliary processes. After receiving the memory v7-Function-to-report-memory-context-stats-of-any-backe.patch:74: space before tab in indent. contexts from the process, it returns the result as one row per v7-Function-to-report-memory-context-stats-of-any-backe.patch:75: space before tab in indent. context. When get_summary is true, memory contexts at level 0 -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA GROUP CORPORATION to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2024-12-24T20:37:46Z
Hi Tomas, > > I'd just remove that. I agree it might have been useful with the single > chunk of shared memory, but I think with separate chunks it's not very > useful. And if we can end up with multiple processed getting the same > pgprocno I guess we have way bigger problems, this won't fix that. > OK, fixed accordingly in the attached patch. > > > > > In the patch, since the timeout was set to a high value, pgbench > ended > > > up stuck > > > waiting for the timeout to occur. The failure happens less > frequently > > > after I added an > > > additional check for the process's existence, but it cannot be > > entirely > > > avoided. This is because a process can terminate after we check > > for its > > > existence but > > > before it signals the client. In such cases, the client will not > > receive > > > any signal. > > > > > > > Hmmm, I see. I guess there's no way to know if a process responds to > us, > > but I guess it should be possible to wake up regularly and check if > the > > process still exists? Wouldn't that solve the case you mentioned? > > > > I have fixed it accordingly in the attached patch by waking up after > > every 5 seconds > > to check if the process exists and sleeping again if the wake-up > condition > > is not satisfied. The number of such tries is limited to 20. So, the > > total wait > > time can be 100 seconds. I will make the re-tries configurable, inline > > with your > > suggestion to be able to override the default waiting time. > > > > Makes sense, although 100 seconds seems a bit weird, it seems we usually > pick "natural" values like 60s, or multiples of that. But if it's > configurable, that's not a huge issue. > > Could the process wake up earlier than the timeout, say if it gets EINT > signal? That'd break the "total timeout is 100 seconds", and it would be > better to check that explicitly. Not sure if this can happen, though. > > Not sure, I will check again. According to the comment on WaitLatch, a process waiting on it should only wake up when timeout happens or SetLatch is called. > One thing I'd maybe consider is starting with a short timeout, and > gradually increasing it until e.g. 5 seconds (or maybe just 1 second > would be perfectly fine, IMHO). With the current coding it means we > either get the response right away, or wait 5+ seconds. That's a big > huge jump. If we start e.g. with 10ms, and then gradually multiply it by > 1.2, it means we only wait "0-20% extra" on average. > > But perhaps this is very unlikely and not worth the complexity. > OK, Currently I have changed it to always wait for signal from backend or timeout before checking the exit condition. This is to ensure that a backend gets a chance to publish the new statistics since I am retaining the old statistics due to reasons explained below. I will experiment with setting a shorter timeout and gradually increasing it. > In the case of checkpointer, I also see some wait time after running the > > tests that you mentioned, but it eventually completes the request in my > > runs. > > > > OK, but why should it even wait that long? Surely the checkpointer > should be able to report memory contexts too? The checkpointer responds to requests promptly when the requests are sequential. However, a timeout may occur if concurrent requests for memory statistics are sent to the checkpointer. In this case, one client sends a GetMemoryContext signal to the checkpointer. The checkpointer sets the PublishMemoryContextPending flag to true in the handler for this signal. This flag remains true until a CHECK_FOR_INTERRUPTS is called, which processes the interrupt and clears the flag. If another process concurrently sends a GetMemoryContext signal to the checkpointer before the CHECK_FOR_INTERRUPTS is called for the previous signal, the PublishMemoryContextPending flag will already be set to true. When the CHECK_FOR_INTERRUPTS is eventually called by the checkpointer, it processes both requests and dumps its memory context statistics. However, only one of the two waiting clients gets to read the statistics. This is because the first client that gains access to the shared statistics reads the data and frees the DSA memory after it is done. As a result, the second client keeps waiting until it times out, since the checkpointer has already processed its request and sent the statistics which the second client never gets to read. I believe that retaining the DSAs with the latest statistics after each request would help resolve the issue of request timeouts in scenarios with concurrent requests. I have included this in the attached patch. > and the other processes were waiting for 107145 in some way. But yeah, > detecting the dead process would improve this, although it also shows > the issues can "spread" easily. > > OTOH it's unlikely to have multiple pg_get_process_memory_contexts() > queries pointing at each other like this - monitoring will just to that > from one backend, and that's it. So not a huge issue. > > Makes sense. > . > > > > In such cases, it might be necessary to experiment with the waiting > > times at the receiving > > client. Making the waiting time user-configurable, as you suggested, by > > passing it as an > > argument to the function, could help address this scenario. > > Thanks for highlighting this, I will test this some more. > > > > I think we should try very hard to make this work well without the user > having to mess with the timeouts. These are exceptional conditions that > happen only very rarely, which makes it hard to find good values. > > OK. > I'm entirely unconcerned about the pg_get_process_memory_contexts() > performance, within some reasonable limits. It's something executed > every now and then - no one is going to complain it takes 10ms extra, > measure tps with this function, etc. > > 17-26% seems surprisingly high, but Even 256kB is too much, IMHO. I'd > just get rid of this optimization until someone complains and explains > why it's worth it. > > Yes, let's make it fast, but I don't think we should optimize it at the > expense of "regular workload" ... > > After debugging the concurrent requests timeout issue, it appears there is yet another argument in favor of avoiding the recreation of DSAs for every request: we get to retain the last reported statistics for a given postgres process, which can help prevent certain requests to fail in case of concurrent requests to the same process. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-01-06T13:16:30Z
Hi Torikoshia, Thank you for the review. > > > =# select path FROM pg_get_process_memory_contexts('20271', false); > path > ------- > {0} > {0,1} > {0,2} > .. > > =# select path from pg_backend_memory_contexts; > path > ------- > {1} > {1,2} > {1,3} > ..asdf asdf > > Would it be better to begin with 1 to make them consistent? > > Makes sense, fixed in the attached patch. pg_log_backend_memory_contexts() does not allow non-superusers to > execute by default since it can peek at other session information. > pg_get_process_memory_contexts() does not have this restriction, but > wouldn't it be necessary? > > Yes. I added the restriction to only allow super users and users with pg_read_all_stats privileges to query the memory context statistics of another process. > When the target pid is the local backend, the HINT suggests using > pg_get_backend_memory_contexts(), but this function is not described in > the manual. > How about suggesting pg_backend_memory_contexts view instead? > > =# select pg_get_process_memory_contexts('27041', false); > WARNING: cannot return statistics for local backend > HINT: Use pg_get_backend_memory_contexts instead > > > There are no explanations about 'num_agg_contexts', but I thought the > explanation like below would be useful. > > Ok. I added an explanation of this column in the documentation. > > I have added this information as a column named "num_agg_contexts", > > which indicates > > the number of contexts whose statistics have been aggregated/added for > > a particular output. > > git apply caused some warnings: > > Thank you for reporting. They should be gone now. PFA the patch with above updates. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Fujii Masao <masao.fujii@oss.nttdata.com> — 2025-01-06T17:04:20Z
On 2025/01/06 22:16, Rahila Syed wrote: > PFA the patch with above updates. Thanks for updating the patch! I like this feature. I tested this feature and encountered two issues: Issue 1: Error with pg_get_process_memory_contexts() When I used pg_get_process_memory_contexts() on the PID of a backend process that had just caused an error but hadn’t rolled back yet, the following error occurred: Session 1 (PID=70011): =# begin; =# select 1/0; ERROR: division by zero Session 2: =# select * from pg_get_process_memory_contexts(70011, false); Session 1 terminated with: ERROR: ResourceOwnerEnlarge called after release started FATAL: terminating connection because protocol synchronization was lost Issue 2: Segmentation Fault When I ran pg_get_process_memory_contexts() every 0.1 seconds using \watch command while running "make -j 4 installcheck-world", I encountered a segmentation fault: LOG: client backend (PID 97975) was terminated by signal 11: Segmentation fault: 11 DETAIL: Failed process was running: select infinite_recurse(); LOG: terminating any other active server processes Regards, -- Fujii Masao Advanced Computing Technology Center Research and Development Headquarters NTT DATA CORPORATION
-
Re: Enhancing Memory Context Statistics Reporting
Tomas Vondra <tomas@vondra.me> — 2025-01-06T21:02:28Z
Hi Rahila, Thanks for the updated and rebased patch. I've tried the pgbench test again, to see if it gets stuck somewhere, and I'm observing this on a new / idle cluster: $ pgbench -n -f test.sql -P 1 test -T 60 pgbench (18devel) progress: 1.0 s, 1647.9 tps, lat 0.604 ms stddev 0.438, 0 failed progress: 2.0 s, 1374.3 tps, lat 0.727 ms stddev 0.386, 0 failed progress: 3.0 s, 1514.4 tps, lat 0.661 ms stddev 0.330, 0 failed progress: 4.0 s, 1563.4 tps, lat 0.639 ms stddev 0.212, 0 failed progress: 5.0 s, 1665.0 tps, lat 0.600 ms stddev 0.177, 0 failed progress: 6.0 s, 1538.0 tps, lat 0.650 ms stddev 0.192, 0 failed progress: 7.0 s, 1491.4 tps, lat 0.670 ms stddev 0.261, 0 failed progress: 8.0 s, 1539.5 tps, lat 0.649 ms stddev 0.443, 0 failed progress: 9.0 s, 1517.0 tps, lat 0.659 ms stddev 0.167, 0 failed progress: 10.0 s, 1594.0 tps, lat 0.627 ms stddev 0.227, 0 failed progress: 11.0 s, 28.0 tps, lat 0.705 ms stddev 0.277, 0 failed progress: 12.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed progress: 13.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed progress: 14.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed progress: 15.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed progress: 16.0 s, 1480.6 tps, lat 4.043 ms stddev 130.113, 0 failed progress: 17.0 s, 1524.9 tps, lat 0.655 ms stddev 0.286, 0 failed progress: 18.0 s, 1246.0 tps, lat 0.802 ms stddev 0.330, 0 failed progress: 19.0 s, 1383.1 tps, lat 0.722 ms stddev 0.934, 0 failed progress: 20.0 s, 1432.7 tps, lat 0.698 ms stddev 0.199, 0 failed ... There's always a period of 10-15 seconds when everything seems to be working fine, and then a couple seconds when it gets stuck, with the usual LOG: Wait for 69454 process to publish stats timed out, trying again The PIDs I've seen were for checkpointer, autovacuum launcher, ... all of that are processes that should be handling the signal, so how come it gets stuck every now and then? The system is entirely idle, there's no contention for the shmem stuff, etc. Could it be forgetting about the signal in some cases, or something like that? The test.sql is super simple: SELECT * FROM pg_get_process_memory_contexts( (SELECT pid FROM pg_stat_activity WHERE pid != pg_backend_pid() ORDER BY random() LIMIT 1) , false); Aside from this, I went through the patch to do a regular review, so here's the main comments in somewhat random order: 1) The SGML docs talk about "contexts at level" but I don't think that's defined/explained anywhere, there are different ways to assign levels in a tree-like structure, so it's unclear if levels are assigned from the top or bottom. 2) volatile sig_atomic_t PublishMemoryContextPending = false; I'd move this right after LogMemoryContextPending (to match the other places that add new stuff). 3) typedef enum PrintDetails I suppose this should have some comments, explaining what the typedef is for. Also, "details" sounds pretty generic, perhaps "destination" or maybe "target" would be better? 4) The memcpy here seems unnecessary - the string is going to be static in the binary, no need to copy it. In which case the whole switch is going to be the same as in PutMemoryContextsStatsTupleStore, so maybe move that into a separate function? + switch (context->type) + { + case T_AllocSetContext: + type = "AllocSet"; + strncpy(memctx_info[curr_id].type, type, strlen(type)); + break; + case T_GenerationContext: + type = "Generation"; + strncpy(memctx_info[curr_id].type, type, strlen(type)); + break; + case T_SlabContext: + type = "Slab"; + strncpy(memctx_info[curr_id].type, type, strlen(type)); + break; + case T_BumpContext: + type = "Bump"; + strncpy(memctx_info[curr_id].type, type, strlen(type)); + break; + default: + type = "???"; + strncpy(memctx_info[curr_id].type, type, strlen(type)); + break; + } 5) The comment about hash table in ProcessGetMemoryContextInterrupt seems pretty far from hash_create(), so maybe move it. 6) ProcessGetMemoryContextInterrupt seems pretty long / complex, with multiple nested loops, it'd be good to split it into smaller parts that are easier to understand. 7) I'm not sure if/why we need to move MemoryContextId to memutils.h. 8) The new stuff in memutils.h is added to the wrong place, into a section labeled "Memory-context-type-specific functions" (which it certainly is not) 9) autovacuum.c adds the ProcessGetMemoryContextInterrupt() call after ProcessCatchupInterrupt() - that's not wrong, but I'd move it right after ProcessLogMemoryContextInterrupt(), just like everywhere else. 10) The pg_get_process_memory_contexts comment says: Signal a backend or an auxiliary process to send its ... But this is not just about the signal, it also waits for the results and produces the result set. 11) pg_get_process_memory_contexts - Wouldn't it be better to move the InitMaterializedSRF() call until after the privilege check, etc.? 12) The pg_get_process_memory_contexts comment should explain why it's superuser-only function. Presumably it has similar DoS risks as the other functions, because if not why would we have the restriction? 13) I reworded and expanded the pg_get_process_memory_contexts comment a bit, and re-wrapped it too. But I think it also needs to explain how it communicates with the other process (sending signal, sending data through a DSA, ...). And also how the timeouts work. 14) I'm a bit confused about the DSA allocations (but I also haven't worked with DSA very much, so maybe it's fine). Presumably the 16MB is upper limit, we won't use that all the time. We allocate 1MB, but allow it to grow up to 16MB, correct? 16MB seems like a lot, certainly enough for this purpose - if it's not, I don't think we can come up with a better limit. 15) In any case, I don't think the 16 should be hardcoded as a magic constant in multiple places. That's bound to be error-prone. 16) I've reformatted / reindented / wrapped the code in various places, to make it easier to read and more consistent with the nearby code. I also added a bunch of comments explaining what the block of code is meant to do (I mean, what it aims to do). 16) A comment in pg_get_process_memory_contexts says: Pin the mapping so that it doesn't throw a warning That doesn't seem very useful. It's not clear what kind of warning this hides, but more importantly - we're not doing stuff to hide some sort of warning, we do it to prevent what the warning is about. 17) pg_get_process_memory_contexts has a bunch of error cases, where we need to detach the DSA and return NULL. Would be better to do a label with a goto, I think. 18) I think pg_get_process_memory_contexts will have issues if this happens in the first loop: if ((memCtxState[procNumber].proc_id == pid) && DsaPointerIsValid(memCtxState[procNumber].memstats_dsa_pointer)) break; Because then we end up with memctx_info pointing to garbage after the loop. I don't know how hard is to hit this, I guess it can happen in many processes calling pg_get_process_memory_contexts? 19) Minor comment and formatting of MemCtxShmemSize / MemCtxShmemInit. 20) MemoryContextInfo etc. need to be added to typedefs.list, so that pgindent can do the right thing. 21) I think ProcessGetMemoryContextInterrupt has a bug because it uses get_summary before reading it from the shmem. Attached are two patches - 0001 is the original patch, 0002 has most of my review comments (mentioned above), and a couple additional changes to comments/formatting, etc. Those are suggestions rather than issues. regards -- Tomas Vondra -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-01-08T12:03:01Z
Hi Fujii-san, Thank you for testing the feature. > Issue 1: Error with pg_get_process_memory_contexts() > When I used pg_get_process_memory_contexts() on the PID of a backend > process > that had just caused an error but hadn’t rolled back yet, > the following error occurred: > > Session 1 (PID=70011): > =# begin; > =# select 1/0; > ERROR: division by zero > > Session 2: > =# select * from pg_get_process_memory_contexts(70011, false); > > Session 1 terminated with: > ERROR: ResourceOwnerEnlarge called after release started > FATAL: terminating connection because protocol synchronization was lost > > In this scenario, a DSM segment descriptor is created and associated with the CurrentResourceOwner, which is set to the aborting transaction's resource owner. This occurs when ProcessGetMemoryContextInterrupts is called by the backend while a transaction is still open and about to be rolled back. I believe this issue needs to be addressed in the DSA and DSM code by adding a check to ensure that the CurrentResourceOwner is not about to be released before creating a DSM under the CurrentResourceOwner. The attached fix resolves this issue. However, for a more comprehensive solution, I believe the same change should be extended to other parts of the DSA and DSM code where CurrentResourceOwner is referenced. Issue 2: Segmentation Fault > When I ran pg_get_process_memory_contexts() every 0.1 seconds using > \watch command while running "make -j 4 installcheck-world", > I encountered a segmentation fault: > > LOG: client backend (PID 97975) was terminated by signal 11: > Segmentation fault: 11 > DETAIL: Failed process was running: select infinite_recurse(); > LOG: terminating any other active server processes > > I have not been able to reproduce this issue. Could you please clarify which process you ran pg_get_process_memory_context() on, with the interval of 0.1? Was it a backend process created by make installcheck-world, or some other process? Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Fujii Masao <masao.fujii@oss.nttdata.com> — 2025-01-08T15:45:48Z
On 2025/01/08 21:03, Rahila Syed wrote: > I have not been able to reproduce this issue. Could you please clarify which process you ran > |pg_get_process_memory_context()| on, with the interval of 0.1? I used the following query for testing: =# SELECT count(*) FROM pg_stat_activity, pg_get_process_memory_contexts(pid, false) WHERE pid <> pg_backend_pid(); =# \watch 0.1 > Was it a backend process > created by |make installcheck-world|, or some other process? Yes, the target backends were from make installcheck-world. No other workloads were running. Regards, -- Fujii Masao Advanced Computing Technology Center Research and Development Headquarters NTT DATA CORPORATION
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-01-13T02:36:10Z
Hi Tomas, Thank you for the review. On Tue, Jan 7, 2025 at 2:32 AM Tomas Vondra <tomas@vondra.me> wrote: > Hi Rahila, > > Thanks for the updated and rebased patch. I've tried the pgbench test > again, to see if it gets stuck somewhere, and I'm observing this on a > new / idle cluster: > > $ pgbench -n -f test.sql -P 1 test -T 60 > pgbench (18devel) > progress: 1.0 s, 1647.9 tps, lat 0.604 ms stddev 0.438, 0 failed > progress: 2.0 s, 1374.3 tps, lat 0.727 ms stddev 0.386, 0 failed > progress: 3.0 s, 1514.4 tps, lat 0.661 ms stddev 0.330, 0 failed > progress: 4.0 s, 1563.4 tps, lat 0.639 ms stddev 0.212, 0 failed > progress: 5.0 s, 1665.0 tps, lat 0.600 ms stddev 0.177, 0 failed > progress: 6.0 s, 1538.0 tps, lat 0.650 ms stddev 0.192, 0 failed > progress: 7.0 s, 1491.4 tps, lat 0.670 ms stddev 0.261, 0 failed > progress: 8.0 s, 1539.5 tps, lat 0.649 ms stddev 0.443, 0 failed > progress: 9.0 s, 1517.0 tps, lat 0.659 ms stddev 0.167, 0 failed > progress: 10.0 s, 1594.0 tps, lat 0.627 ms stddev 0.227, 0 failed > progress: 11.0 s, 28.0 tps, lat 0.705 ms stddev 0.277, 0 failed > progress: 12.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 13.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 14.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 15.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 16.0 s, 1480.6 tps, lat 4.043 ms stddev 130.113, 0 failed > progress: 17.0 s, 1524.9 tps, lat 0.655 ms stddev 0.286, 0 failed > progress: 18.0 s, 1246.0 tps, lat 0.802 ms stddev 0.330, 0 failed > progress: 19.0 s, 1383.1 tps, lat 0.722 ms stddev 0.934, 0 failed > progress: 20.0 s, 1432.7 tps, lat 0.698 ms stddev 0.199, 0 failed > ... > > There's always a period of 10-15 seconds when everything seems to be > working fine, and then a couple seconds when it gets stuck, with the usual > > LOG: Wait for 69454 process to publish stats timed out, trying again > > The PIDs I've seen were for checkpointer, autovacuum launcher, ... all > of that are processes that should be handling the signal, so how come it > gets stuck every now and then? The system is entirely idle, there's no > contention for the shmem stuff, etc. Could it be forgetting about the > signal in some cases, or something like that? > > I am not sure as of now, I will debug further. Meanwhile, I have addressed the review comments. Please find the details and an updated patch below. > > 1) The SGML docs talk about "contexts at level" but I don't think that's > defined/explained anywhere, there are different ways to assign levels in > a tree-like structure, so it's unclear if levels are assigned from the > top or bottom. > Fixed. > > 2) volatile sig_atomic_t PublishMemoryContextPending = false; > I'd move this right after LogMemoryContextPending (to match the other > places that add new stuff). > Done. > > 3) typedef enum PrintDetails > > I suppose this should have some comments, explaining what the typedef is > for. Also, "details" sounds pretty generic, perhaps "destination" or > maybe "target" would be better? > > I added the comments above the typedef and changed the name to PrintDestination. > 4) The memcpy here seems unnecessary - the string is going to be static > in the binary, no need to copy it. In which case the whole switch is > going to be the same as in PutMemoryContextsStatsTupleStore, so maybe > move that into a separate function? > > + switch (context->type) > + { > + case T_AllocSetContext: > + type = "AllocSet"; > + strncpy(memctx_info[curr_id].type, type, strlen(type)); > + break; > + case T_GenerationContext: > + type = "Generation"; > + strncpy(memctx_info[curr_id].type, type, strlen(type)); > + break; > + case T_SlabContext: > + type = "Slab"; > + strncpy(memctx_info[curr_id].type, type, strlen(type)); > + break; > + case T_BumpContext: > + type = "Bump"; > + strncpy(memctx_info[curr_id].type, type, strlen(type)); > + break; > + default: > + type = "???"; > + strncpy(memctx_info[curr_id].type, type, strlen(type)); > + break; > + } > Got rid of the copy and moved the switch to a separate function. > > 5) The comment about hash table in ProcessGetMemoryContextInterrupt > seems pretty far from hash_create(), so maybe move it. > > Was fixed in your suggestions patch. 6) ProcessGetMemoryContextInterrupt seems pretty long / complex, with > multiple nested loops, it'd be good to split it into smaller parts that > are easier to understand. > > Done the refactoring to move certain parts into separate functions. > 7) I'm not sure if/why we need to move MemoryContextId to memutils.h. > This is because I am referencing it from both mcxt.c and mcxtfuns.c. I can consider moving some of the code out of mcxt.c and consolidating everything related to this patch in mcxtfuncs.c if mcxt.c is intended to contain only the core memory context logic. > 8) The new stuff in memutils.h is added to the wrong place, into a > section labeled "Memory-context-type-specific functions" (which it > certainly is not) > > Fixed. > 9) autovacuum.c adds the ProcessGetMemoryContextInterrupt() call after > ProcessCatchupInterrupt() - that's not wrong, but I'd move it right > after ProcessLogMemoryContextInterrupt(), just like everywhere else. > > Fixed too. > 10) The pg_get_process_memory_contexts comment says: > > Signal a backend or an auxiliary process to send its ... > > But this is not just about the signal, it also waits for the results and > produces the result set. Makes sense, edited accordingly. > > 11) pg_get_process_memory_contexts - Wouldn't it be better to move the > InitMaterializedSRF() call until after the privilege check, etc.? > > I have moved it after the super user check but kept it before some other checks that lead to WARNING, after looking at how other functions have done it. > 12) The pg_get_process_memory_contexts comment should explain why it's > superuser-only function. Presumably it has similar DoS risks as the > other functions, because if not why would we have the restriction? > > Edited accordingly. > 13) I reworded and expanded the pg_get_process_memory_contexts comment a > bit, and re-wrapped it too. But I think it also needs to explain how it > communicates with the other process (sending signal, sending data > through a DSA, ...). And also how the timeouts work. > > Thank you for improving the comments. Added remaining changes as requested. > 14) I'm a bit confused about the DSA allocations (but I also haven't > worked with DSA very much, so maybe it's fine). Presumably the 16MB is > upper limit, we won't use that all the time. We allocate 1MB, but allow > it to grow up to 16MB, correct? Yes. 16MB seems like a lot, certainly enough > for this purpose - if it's not, I don't think we can come up with a > better limit. > > I can try reducing it to 8MB, although it's expected to be only allocated when needed. > 15) In any case, I don't think the 16 should be hardcoded as a magic > constant in multiple places. That's bound to be error-prone. > > Done. > 16) I've reformatted / reindented / wrapped the code in various places, > to make it easier to read and more consistent with the nearby code. I > also added a bunch of comments explaining what the block of code is > meant to do (I mean, what it aims to do). > > Thank you > 16) A comment in pg_get_process_memory_contexts says: > > Pin the mapping so that it doesn't throw a warning > > That doesn't seem very useful. It's not clear what kind of warning this > hides, but more importantly - we're not doing stuff to hide some sort of > warning, we do it to prevent what the warning is about. > > Makes sense, fixed. > 17) pg_get_process_memory_contexts has a bunch of error cases, where we > need to detach the DSA and return NULL. Would be better to do a label > with a goto, I think. > Done. > 18) I think pg_get_process_memory_contexts will have issues if this > happens in the first loop: > > if ((memCtxState[procNumber].proc_id == pid) && > DsaPointerIsValid(memCtxState[procNumber].memstats_dsa_pointer)) > break; > > Because then we end up with memctx_info pointing to garbage after the > loop. I don't know how hard is to hit this, I guess it can happen in > many processes calling pg_get_process_memory_contexts? > I think this is not possible since if the breaking condition is met, it means memstats_dsa_pointer is valid and memctx_info which resides at mestats_dsa_pointer will contain valid data. Am I missing something? Regarding the proc_id == pid check, I have added a comment in the code as requested. > 19) Minor comment and formatting of MemCtxShmemSize / MemCtxShmemInit. > > Ok. 20) MemoryContextInfo etc. need to be added to typedefs.list, so that > pgindent can do the right thing. > > Done. 21) I think ProcessGetMemoryContextInterrupt has a bug because it uses > get_summary before reading it from the shmem. > Fixed. It was not showing up in tests as the result of the bug was some extra memory allocation in dsa and some extra computation to populate all the paths in hash table inspite of get_summary being true. > > Attached are two patches - 0001 is the original patch, 0002 has most of > my review comments (mentioned above), and a couple additional changes to > comments/formatting, etc. Those are suggestions rather than issues. > > Thank you, applied the 0002 patch and made the changes mentioned in XXX. Answering some of your questions in the 0002 patch below: Q. * XXX Also, what if we fill exactly this number of contexts? Won't we * lose the last entry because it will be overwitten by the summary? A. We are filling 0 to max_stats - 2 slots by memory context in the loop foreach_ptr(MemoryContextData, cur, contexts) in ProcessGetMemoryContextInterrupt. max_stats - 1 is reserved for the summary statistics. Q. /* XXX I don't understand why we need to check get_summary here? */ A. get_summary check is there to ensure that the context_id is inserted in the hash_table if get_summary is true. If get_summary is true, the loop will break after the first iteration and the entire main list of contexts won't be traversed and hence context_ids won't be inserted. Hence it is handled separately inside a check for get_summary. Q. /* XXX What if the memstats_dsa_pointer is not valid? Is it even possible? * If it is, we have garbage in memctx_info. Maybe it should be an Assert()? */ A . Agreed. Changed it to an assert. Q. /* * XXX isn't 2 x 1kB for every context a bit too much? Maybe better to * make it variable-length? */ A. I don't know how to do this for a variable in shared memory, won't that mean allocating from the heap and thus the pointer would become invalid in another process? Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-01-21T11:27:14Z
Hi Tomas, I've tried the pgbench test > again, to see if it gets stuck somewhere, and I'm observing this on a > new / idle cluster: > > $ pgbench -n -f test.sql -P 1 test -T 60 > pgbench (18devel) > progress: 1.0 s, 1647.9 tps, lat 0.604 ms stddev 0.438, 0 failed > progress: 2.0 s, 1374.3 tps, lat 0.727 ms stddev 0.386, 0 failed > progress: 3.0 s, 1514.4 tps, lat 0.661 ms stddev 0.330, 0 failed > progress: 4.0 s, 1563.4 tps, lat 0.639 ms stddev 0.212, 0 failed > progress: 5.0 s, 1665.0 tps, lat 0.600 ms stddev 0.177, 0 failed > progress: 6.0 s, 1538.0 tps, lat 0.650 ms stddev 0.192, 0 failed > progress: 7.0 s, 1491.4 tps, lat 0.670 ms stddev 0.261, 0 failed > progress: 8.0 s, 1539.5 tps, lat 0.649 ms stddev 0.443, 0 failed > progress: 9.0 s, 1517.0 tps, lat 0.659 ms stddev 0.167, 0 failed > progress: 10.0 s, 1594.0 tps, lat 0.627 ms stddev 0.227, 0 failed > progress: 11.0 s, 28.0 tps, lat 0.705 ms stddev 0.277, 0 failed > progress: 12.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 13.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 14.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 15.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 16.0 s, 1480.6 tps, lat 4.043 ms stddev 130.113, 0 failed > progress: 17.0 s, 1524.9 tps, lat 0.655 ms stddev 0.286, 0 failed > progress: 18.0 s, 1246.0 tps, lat 0.802 ms stddev 0.330, 0 failed > progress: 19.0 s, 1383.1 tps, lat 0.722 ms stddev 0.934, 0 failed > progress: 20.0 s, 1432.7 tps, lat 0.698 ms stddev 0.199, 0 failed > ... > > There's always a period of 10-15 seconds when everything seems to be > working fine, and then a couple seconds when it gets stuck, with the usual > > LOG: Wait for 69454 process to publish stats timed out, trying again > > The PIDs I've seen were for checkpointer, autovacuum launcher, ... all > of that are processes that should be handling the signal, so how come it > gets stuck every now and then? The system is entirely idle, there's no > contention for the shmem stuff, etc. Could it be forgetting about the > signal in some cases, or something like that? > > Yes, This occurs when, due to concurrent signals received by a backend, both signals are processed together, and stats are published only once. Once the stats are read by the first client that gains access, they are erased, causing the second client to wait until timeout. If we make clients wait for the latest stats, timeouts may occur during concurrent operations. To avoid such timeouts, we can retain the previously published memory statistics for every backend and avoid waiting for the latest statistics when the previous statistics are newer than STALE_STATS_LIMIT. This limit can be determined based on the server load and how fast the memory statistics requests are being handled by the server. For example, on a server running make -j 4 installcheck-world while concurrently probing client backends for memory statistics using pgbench, accepting statistics that were approximately 1 second old helped eliminate timeouts. Conversely, on an idle system, waiting for new statistics when the previous ones were older than 0.1 seconds was sufficient to avoid any timeouts caused by concurrent requests. PFA an updated and rebased patch that includes the capability to associate timestamps with statistics. Additionally, I have made some minor fixes and improved the indentation. Currently, I have set STALE_STATS_LIMIT to 0.5 seconds in code. which means do not do not wait for newer statistics if previous statistics were published within the last 5 seconds of current request. Inshort, there are following options to design the wait for statistics depending on whether we expect concurrent requests to a backend for memory statistics to be common. 1. Always get the latest statistics and timeout if not able to. This works fine for sequential probing which is going to be the most common use case. This can lead to a backend timeouts upto MAX_TRIES * MEMSTATS_WAIT_TIMEOUT. 2. Determine the appropriate STALE_STATS_LIMIT and not wait for the latest stats if previous statistics are within that limit . This will help avoid the timeouts in case of the concurrent requests. 3. Do what v10 patch on this thread does - Wait for the latest statistics for up to MEMSTATS_WAIT_TIMEOUT; otherwise, display the previous statistics, regardless of when they were published. Since timeouts are likely to occur only during concurrent requests, the displayed statistics are unlikely to be very outdated. However, in this scenario, we observe the behavior you mentioned, i.e., concurrent backends can get stuck for the duration of MEMSTATS_WAIT_TIMEOUT (currently 5 seconds as per the current settings). I am inclined toward the third approach, as concurrent requests are not expected to be a common use case for this feature. Moreover, with the second approach, determining an appropriate value for STALE_STATS_LIMIT is challenging, as it depends on the server's load. Kindly let me know your preference. I have attached a patch which implements the 2nd approach for testing, the 3rd approach being implemented in the v10 patch. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Fujii Masao <masao.fujii@oss.nttdata.com> — 2025-01-21T16:31:53Z
On 2025/01/21 20:27, Rahila Syed wrote: > Hi Tomas, > > I've tried the pgbench test > again, to see if it gets stuck somewhere, and I'm observing this on a > new / idle cluster: > > $ pgbench -n -f test.sql -P 1 test -T 60 > pgbench (18devel) > progress: 1.0 s, 1647.9 tps, lat 0.604 ms stddev 0.438, 0 failed > progress: 2.0 s, 1374.3 tps, lat 0.727 ms stddev 0.386, 0 failed > progress: 3.0 s, 1514.4 tps, lat 0.661 ms stddev 0.330, 0 failed > progress: 4.0 s, 1563.4 tps, lat 0.639 ms stddev 0.212, 0 failed > progress: 5.0 s, 1665.0 tps, lat 0.600 ms stddev 0.177, 0 failed > progress: 6.0 s, 1538.0 tps, lat 0.650 ms stddev 0.192, 0 failed > progress: 7.0 s, 1491.4 tps, lat 0.670 ms stddev 0.261, 0 failed > progress: 8.0 s, 1539.5 tps, lat 0.649 ms stddev 0.443, 0 failed > progress: 9.0 s, 1517.0 tps, lat 0.659 ms stddev 0.167, 0 failed > progress: 10.0 s, 1594.0 tps, lat 0.627 ms stddev 0.227, 0 failed > progress: 11.0 s, 28.0 tps, lat 0.705 ms stddev 0.277, 0 failed > progress: 12.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 13.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 14.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 15.0 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed > progress: 16.0 s, 1480.6 tps, lat 4.043 ms stddev 130.113, 0 failed > progress: 17.0 s, 1524.9 tps, lat 0.655 ms stddev 0.286, 0 failed > progress: 18.0 s, 1246.0 tps, lat 0.802 ms stddev 0.330, 0 failed > progress: 19.0 s, 1383.1 tps, lat 0.722 ms stddev 0.934, 0 failed > progress: 20.0 s, 1432.7 tps, lat 0.698 ms stddev 0.199, 0 failed > ... > > There's always a period of 10-15 seconds when everything seems to be > working fine, and then a couple seconds when it gets stuck, with the usual > > LOG: Wait for 69454 process to publish stats timed out, trying again > > The PIDs I've seen were for checkpointer, autovacuum launcher, ... all > of that are processes that should be handling the signal, so how come it > gets stuck every now and then? The system is entirely idle, there's no > contention for the shmem stuff, etc. Could it be forgetting about the > signal in some cases, or something like that? > > Yes, This occurs when, due to concurrent signals received by a backend, > both signals are processed together, and stats are published only once. > Once the stats are read by the first client that gains access, they are erased, > causing the second client to wait until timeout. > > If we make clients wait for the latest stats, timeouts may occur during concurrent > operations. To avoid such timeouts, we can retain the previously published memory > statistics for every backend and avoid waiting for the latest statistics when the > previous statistics are newer than STALE_STATS_LIMIT. This limit can be determined > based on the server load and how fast the memory statistics requests are being > handled by the server. > > For example, on a server running make -j 4 installcheck-world while concurrently > probing client backends for memory statistics using pgbench, accepting statistics > that were approximately 1 second old helped eliminate timeouts. Conversely, on an > idle system, waiting for new statistics when the previous ones were older than 0.1 > seconds was sufficient to avoid any timeouts caused by concurrent requests. > > PFA an updated and rebased patch that includes the capability to associate > timestamps with statistics. Additionally, I have made some minor fixes and improved > the indentation. > > Currently, I have set STALE_STATS_LIMIT to 0.5 seconds in code. which means do not > do not wait for newer statistics if previous statistics were published within the last > 5 seconds of current request. > > Inshort, there are following options to design the wait for statistics depending on whether > we expect concurrent requests to a backend for memory statistics to be common. > > 1. Always get the latest statistics and timeout if not able to. > > This works fine for sequential probing which is going to be the most common use case. > This can lead to a backend timeouts upto MAX_TRIES * MEMSTATS_WAIT_TIMEOUT. > > 2. Determine the appropriate STALE_STATS_LIMIT and not wait for the latest stats if > previous statistics are within that limit . > This will help avoid the timeouts in case of the concurrent requests. > > 3. Do what v10 patch on this thread does - > > Wait for the latest statistics for up to MEMSTATS_WAIT_TIMEOUT; > otherwise, display the previous statistics, regardless of when they were published. > > Since timeouts are likely to occur only during concurrent requests, the displayed > statistics are unlikely to be very outdated. > However, in this scenario, we observe the behavior you mentioned, i.e., concurrent > backends can get stuck for the duration of MEMSTATS_WAIT_TIMEOUT > (currently 5 seconds as per the current settings). > > I am inclined toward the third approach, as concurrent requests are not expected > to be a common use case for this feature. Moreover, with the second approach, > determining an appropriate value for STALE_STATS_LIMIT is challenging, as it > depends on the server's load. Just idea; as an another option, how about blocking new requests to the target process (e.g., causing them to fail with an error or returning NULL with a warning) if a previous request is still pending? Users can simply retry the request if it fails. IMO failing quickly seems preferable to getting stuck for a while in cases with concurrent requests. Regards, -- Fujii Masao Advanced Computing Technology Center Research and Development Headquarters NTT DATA CORPORATION
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-01-24T13:47:35Z
Hi, > > Just idea; as an another option, how about blocking new requests to > the target process (e.g., causing them to fail with an error or > returning NULL with a warning) if a previous request is still pending? > Users can simply retry the request if it fails. IMO failing quickly > seems preferable to getting stuck for a while in cases with concurrent > requests. > > Thank you for the suggestion. I agree that it is better to fail early and avoid waiting for a timeout in such cases. I will add a "pending request" tracker for this in shared memory. This approach will help prevent sending a concurrent request if a request for the same backend is still being processed. IMO, one downside of throwing an error in such cases is that the users might wonder if they need to take a corrective action, even though the issue is actually going to solve itself and they just need to retry. Therefore, issuing a warning or displaying previously updated statistics might be a better alternative to throwing an error. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Tomas Vondra <tomas@vondra.me> — 2025-01-24T22:20:50Z
On 1/24/25 14:47, Rahila Syed wrote: > > Hi, > > > Just idea; as an another option, how about blocking new requests to > the target process (e.g., causing them to fail with an error or > returning NULL with a warning) if a previous request is still pending? > Users can simply retry the request if it fails. IMO failing quickly > seems preferable to getting stuck for a while in cases with concurrent > requests. > > Thank you for the suggestion. I agree that it is better to fail > early and avoid waiting for a timeout in such cases. I will add a > "pending request" tracker for this in shared memory. This approach > will help prevent sending a concurrent request if a request for the > same backend is still being processed. > AFAIK these failures should be extremely rare - we're only talking about that because the workload I used for testing is highly concurrent, i.e. it requests memory context info extremely often. I doubt anyone sane is going to do that in practice ... > IMO, one downside of throwing an error in such cases is that the > users might wonder if they need to take a corrective action, even > though the issue is actually going to solve itself and they just > need to retry. Therefore, issuing a warning or displaying previously > updated statistics might be a better alternative to throwing an > error. > Wouldn't this be mostly mitigated by adding proper detail/hint to the error message? Sure, the user can always ignore that (especially when calling this from a script), but well ... we can only do so much. All this makes me think about how we shared pgstat data before the shmem approach was introduced in PG15. Until then the process signaled pgstat collector, and the collector wrote the statistics into a file, with a timestamp. And the process used the timestamp to decide if it's fresh enough ... Wouldn't the same approach work here? I imagined it would work something like this: requesting backend: ------------------- * set request_ts to current timestamp * signal the target process, to generate memory context info * wait until the DSA gets filled with stats_ts > request_ts * return the data, don't erase anything target backend -------------- * clear the signal * generate the statistics * set stats_ts to current timestamp * wait all the backends waiting for the stats (through CV) I see v11 does almost this, except that it accepts somewhat stale data. But why would that be necessary? I don't think it's needed, and I don't think we should accept data from before the process sends the signal. regards -- Tomas Vondra
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-01-29T12:45:38Z
Hi, On Sat, Jan 25, 2025 at 3:50 AM Tomas Vondra <tomas@vondra.me> wrote: > > > On 1/24/25 14:47, Rahila Syed wrote: > > > > Hi, > > > > > > Just idea; as an another option, how about blocking new requests to > > the target process (e.g., causing them to fail with an error or > > returning NULL with a warning) if a previous request is still > pending? > > Users can simply retry the request if it fails. IMO failing quickly > > seems preferable to getting stuck for a while in cases with > concurrent > > requests. > > > > Thank you for the suggestion. I agree that it is better to fail > > early and avoid waiting for a timeout in such cases. I will add a > > "pending request" tracker for this in shared memory. This approach > > will help prevent sending a concurrent request if a request for the > > same backend is still being processed. > > > > AFAIK these failures should be extremely rare - we're only talking about > that because the workload I used for testing is highly concurrent, i.e. > it requests memory context info extremely often. I doubt anyone sane is > going to do that in practice ... Yes, that makes sense. > > > IMO, one downside of throwing an error in such cases is that the > > users might wonder if they need to take a corrective action, even > > though the issue is actually going to solve itself and they just > > need to retry. Therefore, issuing a warning or displaying previously > > updated statistics might be a better alternative to throwing an > > error. > > > > Wouldn't this be mostly mitigated by adding proper detail/hint to the > error message? Sure, the user can always ignore that (especially when > calling this from a script), but well ... we can only do so much. > OK. All this makes me think about how we shared pgstat data before the shmem > approach was introduced in PG15. Until then the process signaled pgstat > collector, and the collector wrote the statistics into a file, with a > timestamp. And the process used the timestamp to decide if it's fresh > enough ... Wouldn't the same approach work here? > > I imagined it would work something like this: > > requesting backend: > ------------------- > * set request_ts to current timestamp > * signal the target process, to generate memory context info > * wait until the DSA gets filled with stats_ts > request_ts > * return the data, don't erase anything > > target backend > -------------- > * clear the signal > * generate the statistics > * set stats_ts to current timestamp > * wait all the backends waiting for the stats (through CV) > > I see v11 does almost this, except that it accepts somewhat stale data. > That's correct. > But why would that be necessary? I don't think it's needed, and I don't > think we should accept data from before the process sends the signal. > > This is done in an attempt to avoid concurrent requests from timing out. In such cases, data in response to another request is likely to already be in the dynamic shared memory. Hence instead of waiting for the latest data and risking a timeout, the approach displays available statistics that are newer than a defined threshold. Additionally, since we can't distinguish between sequential and concurrent requests, we accept somewhat stale data for all requests. I realize this approach has some issues, mainly regarding how to determine an appropriate threshold value or a limit for old data. Therefore, I agree that it makes sense to display the data that is published after the request is made. If such data can't be published due to concurrent requests or other delays, the function should detect this and return as soon as possible. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-02-03T12:47:14Z
Hi, > > > > Just idea; as an another option, how about blocking new requests to > > the target process (e.g., causing them to fail with an error or > > returning NULL with a warning) if a previous request is still > pending? > > Users can simply retry the request if it fails. IMO failing quickly > > seems preferable to getting stuck for a while in cases with > concurrent > > requests. > > > > Thank you for the suggestion. I agree that it is better to fail > > early and avoid waiting for a timeout in such cases. I will add a > > "pending request" tracker for this in shared memory. This approach > > will help prevent sending a concurrent request if a request for the > > same backend is still being processed. > > Please find attached a patch that adds a request_pending field in shared memory. This allows us to detect concurrent requests early and return a WARNING message immediately, avoiding unnecessary waiting and potential timeouts. This is added in v12-0002* patch. I imagined it would work something like this: > > requesting backend: > ------------------- > * set request_ts to current timestamp > * signal the target process, to generate memory context info > * wait until the DSA gets filled with stats_ts > request_ts > * return the data, don't erase anything > > target backend > -------------- > * clear the signal > * generate the statistics > * set stats_ts to current timestamp > * wait all the backends waiting for the stats (through CV) > The attached v12-0002* patch implements this. We determine the latest statistics based on the stats timestamp, if it is greater than the timestamp when the request was sent, the statistics are considered up to date and are returned immediately. Otherwise, the client waits for the latest statistics to be published until the timeout is reached. With the latest changes, I don't see a dip in tps even when concurrent requests are run in pgbench script. pgbench -n -f monitoring.sql -P 1 postgres -T 60 pgbench (18devel) progress: 1.0 s, 816.9 tps, lat 1.218 ms stddev 0.317, 0 failed progress: 2.0 s, 821.9 tps, lat 1.216 ms stddev 0.177, 0 failed progress: 3.0 s, 817.1 tps, lat 1.224 ms stddev 0.209, 0 failed progress: 4.0 s, 791.0 tps, lat 1.262 ms stddev 0.292, 0 failed progress: 5.0 s, 780.8 tps, lat 1.280 ms stddev 0.326, 0 failed progress: 6.0 s, 675.2 tps, lat 1.482 ms stddev 0.503, 0 failed progress: 7.0 s, 674.0 tps, lat 1.482 ms stddev 0.387, 0 failed progress: 8.0 s, 821.0 tps, lat 1.217 ms stddev 0.272, 0 failed progress: 9.0 s, 903.0 tps, lat 1.108 ms stddev 0.196, 0 failed progress: 10.0 s, 886.9 tps, lat 1.128 ms stddev 0.160, 0 failed progress: 11.0 s, 887.1 tps, lat 1.126 ms stddev 0.243, 0 failed progress: 12.0 s, 871.0 tps, lat 1.147 ms stddev 0.227, 0 failed progress: 13.0 s, 735.0 tps, lat 1.361 ms stddev 0.329, 0 failed progress: 14.0 s, 655.9 tps, lat 1.522 ms stddev 0.331, 0 failed progress: 15.0 s, 674.0 tps, lat 1.484 ms stddev 0.254, 0 failed progress: 16.0 s, 659.0 tps, lat 1.517 ms stddev 0.289, 0 failed progress: 17.0 s, 641.0 tps, lat 1.558 ms stddev 0.281, 0 failed progress: 18.0 s, 707.8 tps, lat 1.412 ms stddev 0.324, 0 failed progress: 19.0 s, 746.3 tps, lat 1.341 ms stddev 0.219, 0 failed progress: 20.0 s, 659.9 tps, lat 1.513 ms stddev 0.372, 0 failed progress: 21.0 s, 651.8 tps, lat 1.533 ms stddev 0.372, 0 failed WARNING: cannot process the request at the moment HINT: Another request is pending, try again progress: 22.0 s, 635.2 tps, lat 1.574 ms stddev 0.519, 0 failed WARNING: cannot process the request at the moment HINT: Another request is pending, try again progress: 23.0 s, 730.0 tps, lat 1.369 ms stddev 0.408, 0 failed WARNING: cannot process the request at the moment HINT: Another request is pending, try again WARNING: cannot process the request at the moment HINT: Another request is pending, try again where monitoring.sql is as follows: SELECT * FROM pg_get_process_memory_contexts( (SELECT pid FROM pg_stat_activity WHERE pid != pg_backend_pid() ORDER BY random() LIMIT 1) , false); I have split the patch into 2 patches with v12-0001* consisting of fixes needed to allow using the MemoryContextStatsInternals for this feature and v12-0002* containing all the remaining changes for the feature. A few outstanding issues are as follows: 1. Currently one DSA is created per backend when the first request for statistics is made and remains for the lifetime of the server. I think I should add logic to periodically destroy DSAs, when memory context statistics are not being *actively* queried from the backend, as determined by the statistics timestamp. 2. The two issues reported by Fujii-san here: [1]. i. I have proposed a fix for the first issue here [2]. ii. I am able to reproduce the second issue. This happens when we try to query statistics of a backend running infinite_recurse.sql. While I am working on finding a root-cause, I think it happens due to some memory being overwritten due to to stack-depth violation, as the issue is not seen when I reduce the max_stack_depth to 100kb. [1]. https://www.postgresql.org/message-id/a1a7e2b7-8f33-4313-baff-42e92ec14fd3%40oss.nttdata.com [2]. https://www.postgresql.org/message-id/CAH2L28shr0j3JE5V3CXDFmDH-agTSnh2V8pR23X0UhRMbDQD9Q%40mail.gmail.com -
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2025-02-10T12:02:56Z
On 2025-02-03 21:47, Rahila Syed wrote: > Hi, > >>> >>> Just idea; as an another option, how about blocking new >> requests to >>> the target process (e.g., causing them to fail with an error >> or >>> returning NULL with a warning) if a previous request is still >> pending? >>> Users can simply retry the request if it fails. IMO failing >> quickly >>> seems preferable to getting stuck for a while in cases with >> concurrent >>> requests. >>> >>> Thank you for the suggestion. I agree that it is better to fail >>> early and avoid waiting for a timeout in such cases. I will add a >>> "pending request" tracker for this in shared memory. This approach >> >>> will help prevent sending a concurrent request if a request for >> the >>> same backend is still being processed. >>> > > Please find attached a patch that adds a request_pending field in > shared memory. This allows us to detect concurrent requests early > and return a WARNING message immediately, avoiding unnecessary > waiting and potential timeouts. This is added in v12-0002* patch. Thanks for updating the patch! The below comments would be a bit too detailed at this stage, but I’d like to share the points I noticed. > 76 + arguments: PID and a boolean, get_summary. The function > can send Since get_summary is a parameter, should we enclose it in <parameter> tags, like <parameter>get_summary</parameter>? > 387 + * The shared memory buffer has a limited size - it the process > has too many > 388 + * memory contexts, Should 'it' be 'if'? > 320 * By default, only superusers are allowed to signal to return the > memory > 321 * contexts because allowing any users to issue this request at an > unbounded > 322 * rate would cause lots of requests to be sent and which can lead > to denial of > 323 * service. Additional roles can be permitted with GRANT. This comment seems to contradict the following code: > 360 * Only superusers or users with pg_read_all_stats privileges > can view the > 361 * memory context statistics of another process > 362 */ > 363 if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS)) > 364 ereport(ERROR, > 365 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), > 366 errmsg("memory context statistics privilege > error"))); > 485 + if (memCtxState[procNumber].memstats_dsa_handle == > DSA_HANDLE_INVALID) > 486 + { > 487 + > 488 + LWLockRelease(&memCtxState[procNumber].lw_lock); > 505 + else > 506 + { > 507 + LWLockRelease(&memCtxState[procNumber].lw_lock); The LWLockRelease() function appears in both the if and else branches. Can we move it outside the conditional block to avoid duplication? > 486 + { > 487 + > 488 + LWLockRelease(&memCtxState[procNumber].lw_lock); The blank line at 487 seems unnecessary. Should we remove it? > 534 { > 535 ereport(LOG, > 536 (errmsg("Wait for %d process to publish stats > timed out, trying again", > 537 pid))); > 538 if (num_retries > MAX_RETRIES) > 539 goto end; > 540 num_retries = num_retries + 1; > 541 } If the target process remains unresponsive, the logs will repeatedly show: LOG: Wait for xxxx process to publish stats timed out, trying again LOG: Wait for xxxx process to publish stats timed out, trying again ... LOG: Wait for xxxx process to publish stats timed out, trying again However, the final log message is misleading because it does not actually try again. Should we adjust the last log message to reflect the correct behavior? > 541 } > 542 > 543 } The blank line at 542 seems unnecessary. Should we remove it? > 874 + context_id_lookup = > hash_create("pg_get_remote_backend_memory_contexts", Should 'pg_get_remote_backend_memory_contexts' be renamed to 'pg_get_process_memory_contexts' now? > 899 + * Allocate memory in this process's dsa for storing statistics > of the the 'the the' is a duplicate. -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA GROUP CORPORATION to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-02-18T13:05:38Z
Hi, > > Thanks for updating the patch! > > The below comments would be a bit too detailed at this stage, but I’d > like to share the points I noticed. > > Thanks for sharing the detailed comments. I have incorporated some of them into the new version of the patch. I will include the rest when I refine and comment the code further. Meanwhile, I have fixed the following outstanding issues: 1. Currently one DSA is created per backend when the first request for > statistics is made and remains for the lifetime of the server. > I think I should add logic to periodically destroy DSAs, when memory > context statistics are not being *actively* queried from the backend, > as determined by the statistics timestamp. > After an offline discussion with Andres and Tomas, I have fixed this to use only one DSA for all the publishing backends/processes. Each backend allocates smaller chunks of memory within the DSA while publishing statistics. These chunks are tracked independently by each backend, ensuring that two publishing backends/processes do not block each other despite using the same DSA. This approach eliminates the overhead of creating multiple DSAs, one for each backend. I am not destroying the DSA area because it stores the previously published statistics for each process. This allows the system to display older statistics when the latest data cannot be retrieved within a reasonable time. Only the most recently updated statistics are kept, while all earlier ones are freed using dsa_free by each backend when they are no longer needed. . > 2. The two issues reported by Fujii-san here: [1]. > i. I have proposed a fix for the first issue here [2]. > ii. I am able to reproduce the second issue. This happens when we try > to query statistics of a backend running infinite_recurse.sql. While I am > working on finding a root-cause, I think it happens due to some memory > being overwritten due to to stack-depth violation, as the issue is not > seen > when I reduce the max_stack_depth to 100kb. > } > } > The second issue is also resolved by using smaller allocations within a DSA. Previously, it occurred because a few statically allocated strings were placed within a single large chunk of DSA allocation. I have changed this to use dynamically allocated chunks with dsa_allocate0 within the same DSA. Please find attached updated and rebased patches. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-02-20T13:26:49Z
Hi, Please find attached the updated patches after some cleanup and test fixes. Thank you, Rahila Syed On Tue, Feb 18, 2025 at 6:35 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > Hi, > >> >> Thanks for updating the patch! >> >> The below comments would be a bit too detailed at this stage, but I’d >> like to share the points I noticed. >> >> Thanks for sharing the detailed comments. I have incorporated some of them > into the new version of the patch. I will include the rest when I refine > and > comment the code further. > > Meanwhile, I have fixed the following outstanding issues: > > 1. Currently one DSA is created per backend when the first request for >> statistics is made and remains for the lifetime of the server. >> I think I should add logic to periodically destroy DSAs, when memory >> context statistics are not being *actively* queried from the backend, >> as determined by the statistics timestamp. >> > > After an offline discussion with Andres and Tomas, I have fixed this to > use > only one DSA for all the publishing backends/processes. Each backend > allocates smaller chunks of memory within the DSA while publishing > statistics. > These chunks are tracked independently by each backend, ensuring that two > publishing backends/processes do not block each other despite using the > same > DSA. This approach eliminates the overhead of creating multiple DSAs, > one for each backend. > > I am not destroying the DSA area because it stores the previously > published > statistics for each process. This allows the system to display older > statistics > when the latest data cannot be retrieved within a reasonable time. > Only the most recently updated statistics are kept, while all earlier ones > are freed using dsa_free by each backend when they are no longer needed. > . > >> 2. The two issues reported by Fujii-san here: [1]. >> i. I have proposed a fix for the first issue here [2]. >> ii. I am able to reproduce the second issue. This happens when we try >> to query statistics of a backend running infinite_recurse.sql. While I am >> working on finding a root-cause, I think it happens due to some memory >> being overwritten due to to stack-depth violation, as the issue is not >> seen >> when I reduce the max_stack_depth to 100kb. >> } >> } >> > > The second issue is also resolved by using smaller allocations within a > DSA. > Previously, it occurred because a few statically allocated strings were > placed > within a single large chunk of DSA allocation. I have changed this to use > dynamically allocated chunks with dsa_allocate0 within the same DSA. > > Please find attached updated and rebased patches. > > Thank you, > Rahila Syed >
-
Re: Enhancing Memory Context Statistics Reporting
Tomas Vondra <tomas@vondra.me> — 2025-02-21T15:01:00Z
On 2/20/25 14:26, Rahila Syed wrote: > Hi, > > Please find attached the updated patches after some cleanup and test > fixes. > > Thank you, > Rahila Syed > > On Tue, Feb 18, 2025 at 6:35 PM Rahila Syed <rahilasyed90@gmail.com > <mailto:rahilasyed90@gmail.com>> wrote: > > Hi, > > > Thanks for updating the patch! > > The below comments would be a bit too detailed at this stage, > but I’d > like to share the points I noticed. > > Thanks for sharing the detailed comments. I have incorporated some > of them > into the new version of the patch. I will include the rest when I > refine and > comment the code further. > > Meanwhile, I have fixed the following outstanding issues: > > 1. Currently one DSA is created per backend when the first > request for > statistics is made and remains for the lifetime of the server. > I think I should add logic to periodically destroy DSAs, when memory > context statistics are not being *actively* queried from the > backend, > as determined by the statistics timestamp. > > > After an offline discussion with Andres and Tomas, I have fixed this > to use > only one DSA for all the publishing backends/processes. Each backend > allocates smaller chunks of memory within the DSA while publishing > statistics. > These chunks are tracked independently by each backend, ensuring > that two > publishing backends/processes do not block each other despite using > the same > DSA. This approach eliminates the overhead of creating multiple DSAs, > one for each backend. > > I am not destroying the DSA area because it stores the previously > published > statistics for each process. This allows the system to display older > statistics > when the latest data cannot be retrieved within a reasonable time. > Only the most recently updated statistics are kept, while all > earlier ones > are freed using dsa_free by each backend when they are no longer needed. > . I think something is not quite right, because if I try running a simple pgbench script that does pg_get_process_memory_contexts() on PIDs of random postgres process (just like in the past), I immediately get this: pgbench: error: client 28 script 0 aborted in command 0 query 0: ERROR: can't attach the same segment more than once pgbench: error: client 10 script 0 aborted in command 0 query 0: ERROR: can't attach the same segment more than once pgbench: error: client 5 script 0 aborted in command 0 query 0: ERROR: can't attach the same segment more than once pgbench: error: client 8 script 0 aborted in command 0 query 0: ERROR: can't attach the same segment more than once ... Perhaps the backends need to synchronize creation of the DSA? > > 2. The two issues reported by Fujii-san here: [1]. > i. I have proposed a fix for the first issue here [2]. > ii. I am able to reproduce the second issue. This happens when > we try > to query statistics of a backend running infinite_recurse.sql. > While I am > working on finding a root-cause, I think it happens due to some > memory > being overwritten due to to stack-depth violation, as the issue > is not seen > when I reduce the max_stack_depth to 100kb. > } > } > > > The second issue is also resolved by using smaller allocations > within a DSA. > Previously, it occurred because a few statically allocated strings > were placed > within a single large chunk of DSA allocation. I have changed this > to use > dynamically allocated chunks with dsa_allocate0 within the same DSA. > Sounds good. Do you have any measurements how much this reduced the size of the entries written to the DSA? How many entries will fit into 1MB of shared memory? regards -- Tomas Vondra
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-02-24T12:46:45Z
> I think something is not quite right, because if I try running a simple > pgbench script that does pg_get_process_memory_contexts() on PIDs of > random postgres process (just like in the past), I immediately get this: > > Thank you for testing. This issue occurs when a process that previously attached to a DSA area for publishing its own context statistics tries to attach to it again while querying statistics from another backend. Previously, I was not detaching at the end of publishing the statistics. I have now changed it to detach from the area after the statistics are published. The fix is included in the updated patch. > Perhaps the backends need to synchronize creation of the DSA? > This has been implemented in the patch. > Sounds good. Do you have any measurements how much this reduced the size > of the entries written to the DSA? How many entries will fit into 1MB of > shared memory? The size of the entries has approximately halved after dynamically allocating the strings and a datum array. Also, previously, I was allocating the entire memory for all contexts in one large chunk from DSA. I have now separated them into smaller allocations per context. The integer counters are still allocated at once for all contexts, but the size of an allocated chunk will not exceed approximately 128 bytes * total_num_of_contexts. Average total number of contexts is in the hundreds. PFA the updated and rebased patches. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-02-28T15:42:37Z
> On 24 Feb 2025, at 13:46, Rahila Syed <rahilasyed90@gmail.com> wrote: > PFA the updated and rebased patches. Thanks for the rebase, a few mostly superficial comments from a first read-through. I'll do some more testing and playing around with it for functional comments. + ... + child contexts' statistics, with num_agg_contexts indicating the number + of these aggregated child contexts. The documentation refers to attributes in the return row but the format of that row isn't displayed which makes following along hard. I think we should include a table or a programlisting showing the return data before this paragraph. +const char * +AssignContextType(NodeTag type) This function doesn't actually assign anything so the name is a bit misleading, it would be better with ContextTypeToString or something similar. + * By default, only superusers or users with PG_READ_ALL_STATS are allowed to This sentence is really long and should probably be broken up. + * The shared memory buffer has a limited size - it the process has too many s/it/if/ + * If the publishing backend does not respond before the condition variable + * times out, which is set to MEMSTATS_WAIT_TIMEOUT, retry for max_tries + * number of times, which is defined by user, before giving up and + * returning previously published statistics, if any. This comment should mention what happens if the process gives up and there is no previously published stats. + int i; ... + for (i = 0; i < memCtxState[procNumber].total_stats; i++) This can be rewritten as "for (int i = 0; .." since we allow C99. + * process running and consuming lots of memory, that it might end on its + * own first and its memory contexts are not logged is not a problem. This comment is copy/pasted from pg_log_backend_memory_contexts and while it mostly still apply it should at least be rewritten to not refer to logging as this function doesn't do that. + ereport(WARNING, + (errmsg("PID %d is not a PostgreSQL server process", No need to add the extra parenthesis around errmsg anymore, so I think new code should omit those. + errhint("Use pg_backend_memory_contexts view instead"))); Super nitpick, but errhints should be complete sentences ending with a period. + * statitics have previously been published by the backend. In which case, s/statitics/statistics/ + * statitics have previously been published by the backend. In which case, + * check if statistics are not older than curr_timestamp, if they are wait I think the sentence around the time check is needlessly confusing, could it be rewritten into something like: "A valid DSA pointer isn't proof that statistics are available, it can be valid due to previously published stats. Check if the stats are updated by comparing the timestamp, if the stats are newer than our previously recorded timestamp from before sending the procsignal they must by definition be updated." + /* Assert for dsa_handle to be valid */ Was this intended to be turned into an Assert call? Else it seems better to remove. + if (print_location != PRINT_STATS_NONE) This warrants a comment stating why it makes sense. + * Do not print the statistics if print_to_stderr is PRINT_STATS_NONE, s/print_to_stderr/print_location/. Also, do we really need print_to_stderr in this function at all? It seems a bit awkward to combine a boolean and a paramter for a tri-state value when the parameter holds the tri_state already. For readability I think just checking print_location will be better since the value will be clear, where print_to_stderr=false is less clear in a tri-state scenario. + * its ancestors to a list, inorder to compute a path. s/inorder/in order/ + elog(LOG, "hash table corrupted, can't construct path value"); + break; This will return either a NIL list or a partial path, but PublishMemoryContext doesn't really take into consideration that it might be so. Is this really benign to the point that we can blindly go on? Also, elog(LOG..) is mostly for tracing or debugging as elog() isn't intended for user facing errors. +static void +compute_num_of_contexts(List *contexts, HTAB *context_id_lookup, + int *stats_count, bool get_summary) This function does a lot than compute the number of contexts so the name seems a bit misleading. Perhaps a rename to compute_contexts() or something similar? + memctx_info[curr_id].name = dsa_allocate0(area, + strlen(clipped_ident) + 1); These calls can use idlen instead of more strlen() calls no? While there is no performance benefit, it would increase readability IMHO if the code first calculates a value, and then use it. + strncpy(name, + clipped_ident, strlen(clipped_ident)); Since clipped_ident has been nul terminated earlier there is no need to use strncpy, we can instead use strlcpy and take the target buffer size into consideration rather than the input string length. PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */ + PROCSIG_GET_MEMORY_CONTEXT, /* ask backend to log the memory contexts */ This comment should be different from the LOG_MEMORY_xx one. +#define MEM_CONTEXT_SHMEM_STATS_SIZE 30 +#define MAX_TYPE_STRING_LENGTH 64 These are unused, from an earlier version of the patch perhaps? + * Singe DSA area is created and used by all the processes, s/Singe/Since/ +typedef struct MemoryContextBackendState This is only used in mcxtfuncs.c and can be moved there rather than being exported in the header. +} MemoryContextId; This lacks an entry in the typedefs.list file. -- Daniel Gustafsson -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-03-04T07:00:02Z
Hi Daniel, Thanks for the rebase, a few mostly superficial comments from a first > read-through. > Thank you for your comments. > The documentation refers to attributes in the return row but the format of > that > row isn't displayed which makes following along hard. I think we should > include a table or a programlisting showing the return data before this > paragraph. > > I included the sql function call and its output in programlisting format after the function description. Since the description was part of a table, I added this additional information at the end of that table. > +const char * > +AssignContextType(NodeTag type) > This function doesn't actually assign anything so the name is a bit > misleading, > it would be better with ContextTypeToString or something similar. > > Done. > > + * By default, only superusers or users with PG_READ_ALL_STATS are > allowed to > This sentence is really long and should probably be broken up. > > Fixed. > > + * The shared memory buffer has a limited size - it the process has too > many > s/it/if/ > > Fixed. > + * If the publishing backend does not respond before the condition > variable > + * times out, which is set to MEMSTATS_WAIT_TIMEOUT, retry for max_tries > + * number of times, which is defined by user, before giving up and > + * returning previously published statistics, if any. > This comment should mention what happens if the process gives up and there > is > no previously published stats. > > Done. > > + int i; > ... > + for (i = 0; i < memCtxState[procNumber].total_stats; i++) > This can be rewritten as "for (int i = 0; .." since we allow C99. > > Done. > > + * process running and consuming lots of memory, that it might end on > its > + * own first and its memory contexts are not logged is not a problem. > This comment is copy/pasted from pg_log_backend_memory_contexts and while > it > mostly still apply it should at least be rewritten to not refer to logging > as > this function doesn't do that. > > Fixed. > > + ereport(WARNING, > + (errmsg("PID %d is not a PostgreSQL server process", > No need to add the extra parenthesis around errmsg anymore, so I think new > code > should omit those. > > Done. > > + errhint("Use pg_backend_memory_contexts view instead"))); > Super nitpick, but errhints should be complete sentences ending with a > period. > > Done. > > + * statitics have previously been published by the backend. In which > case, > s/statitics/statistics/ > > Fixed. > > + * statitics have previously been published by the backend. In which > case, > + * check if statistics are not older than curr_timestamp, if they are > wait > I think the sentence around the time check is needlessly confusing, could > it be > rewritten into something like: > "A valid DSA pointer isn't proof that statistics are available, it can > be > valid due to previously published stats. Check if the stats are > updated by > comparing the timestamp, if the stats are newer than our previously > recorded timestamp from before sending the procsignal they must by > definition be updated." > > Replaced accordingly. > > + /* Assert for dsa_handle to be valid */ > Was this intended to be turned into an Assert call? Else it seems better > to remove. > Added an assert and removed the comment. > + if (print_location != PRINT_STATS_NONE) > This warrants a comment stating why it makes sense. > > + * Do not print the statistics if print_to_stderr is PRINT_STATS_NONE, > s/print_to_stderr/print_location/. Also, do we really need > print_to_stderr in > this function at all? It seems a bit awkward to combine a boolean and a > paramter for a tri-state value when the parameter holds the tri_state > already. > For readability I think just checking print_location will be better since > the > value will be clear, where print_to_stderr=false is less clear in a > tri-state > scenario. > > I removed the boolean print_to_stderr, the checks are now using the tri-state enum-print_location. > + * its ancestors to a list, inorder to compute a path. > s/inorder/in order/ > > Fixed. > > + elog(LOG, "hash table corrupted, can't construct path value"); > + break; > This will return either a NIL list or a partial path, but > PublishMemoryContext > doesn't really take into consideration that it might be so. Is this really > benign to the point that we can blindly go on? Also, elog(LOG..) is > mostly for > tracing or debugging as elog() isn't intended for user facing errors. > > I agree that this should be addressed. I added a check for path value before storing it in shared memory. If the path is NIL, the path pointer in DSA will point to InvalidDsaPointer. When a client encounters an InvalidDsaPointer it will print NULL in the path column. Partial path scenario is unlikely IMO, and I am not sure if it warrants additional checks. > +static void > +compute_num_of_contexts(List *contexts, HTAB *context_id_lookup, > + int *stats_count, bool get_summary) > This function does a lot than compute the number of contexts so the name > seems > a bit misleading. Perhaps a rename to compute_contexts() or something > similar? > > Renamed to compute_contexts_count_and_ids. > > + memctx_info[curr_id].name = dsa_allocate0(area, > + strlen(clipped_ident) + 1); > These calls can use idlen instead of more strlen() calls no? While there > is no > performance benefit, it would increase readability IMHO if the code first > calculates a value, and then use it. > > Done. > > + strncpy(name, > + clipped_ident, strlen(clipped_ident)); > Since clipped_ident has been nul terminated earlier there is no need to use > strncpy, we can instead use strlcpy and take the target buffer size into > consideration rather than the input string length. > > Replaced with the strlcpy calls. > > PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts > */ > + PROCSIG_GET_MEMORY_CONTEXT, /* ask backend to log the memory contexts > */ > This comment should be different from the LOG_MEMORY_xx one. > > Fixed. +#define MEM_CONTEXT_SHMEM_STATS_SIZE 30 > +#define MAX_TYPE_STRING_LENGTH 64 > These are unused, from an earlier version of the patch perhaps? > > Removed + * Singe DSA area is created and used by all the processes, > s/Singe/Since/ > Fixed. +typedef struct MemoryContextBackendState > This is only used in mcxtfuncs.c and can be moved there rather than being > exported in the header. > This is being used in mcxt.c too in the form of the variable memCtxState. > +} MemoryContextId; > This lacks an entry in the typedefs.list file. > > Added. Please find attached the updated patches with the above fixes. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-03-13T13:56:51Z
Hi, Please find attached updated and rebased patches. It has the following changes 1. To prevent memory leaks, ensure that the latest statistics published by a process are freed before it exits. This can be achieved by calling dsa_free in the before_shmem_exit callback. 2. Add a level column to maintain consistency with the output of pg_backend_memory_contexts. Thank you, Rahila Syed On Tue, Mar 4, 2025 at 12:30 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > Hi Daniel, > > Thanks for the rebase, a few mostly superficial comments from a first >> read-through. >> > Thank you for your comments. > > >> The documentation refers to attributes in the return row but the format >> of that >> row isn't displayed which makes following along hard. I think we should >> include a table or a programlisting showing the return data before this >> paragraph. >> >> I included the sql function call and its output in programlisting format > after the > function description. > Since the description was part of a table, I added this additional > information at the > end of that table. > > >> +const char * >> +AssignContextType(NodeTag type) >> This function doesn't actually assign anything so the name is a bit >> misleading, >> it would be better with ContextTypeToString or something similar. >> >> Done. > > >> >> + * By default, only superusers or users with PG_READ_ALL_STATS are >> allowed to >> This sentence is really long and should probably be broken up. >> >> Fixed. > >> >> + * The shared memory buffer has a limited size - it the process has too >> many >> s/it/if/ >> >> Fixed. > > >> + * If the publishing backend does not respond before the condition >> variable >> + * times out, which is set to MEMSTATS_WAIT_TIMEOUT, retry for max_tries >> + * number of times, which is defined by user, before giving up and >> + * returning previously published statistics, if any. >> This comment should mention what happens if the process gives up and >> there is >> no previously published stats. >> >> Done. > > >> >> + int i; >> ... >> + for (i = 0; i < memCtxState[procNumber].total_stats; i++) >> This can be rewritten as "for (int i = 0; .." since we allow C99. >> >> Done. > > >> >> + * process running and consuming lots of memory, that it might end on >> its >> + * own first and its memory contexts are not logged is not a problem. >> This comment is copy/pasted from pg_log_backend_memory_contexts and while >> it >> mostly still apply it should at least be rewritten to not refer to >> logging as >> this function doesn't do that. >> >> Fixed. > > >> >> + ereport(WARNING, >> + (errmsg("PID %d is not a PostgreSQL server process", >> No need to add the extra parenthesis around errmsg anymore, so I think >> new code >> should omit those. >> >> Done. > > >> >> + errhint("Use pg_backend_memory_contexts view instead"))); >> Super nitpick, but errhints should be complete sentences ending with a >> period. >> >> Done. > > >> >> + * statitics have previously been published by the backend. In which >> case, >> s/statitics/statistics/ >> >> Fixed. > > >> >> + * statitics have previously been published by the backend. In which >> case, >> + * check if statistics are not older than curr_timestamp, if they are >> wait >> I think the sentence around the time check is needlessly confusing, could >> it be >> rewritten into something like: >> "A valid DSA pointer isn't proof that statistics are available, it >> can be >> valid due to previously published stats. Check if the stats are >> updated by >> comparing the timestamp, if the stats are newer than our previously >> recorded timestamp from before sending the procsignal they must by >> definition be updated." >> >> Replaced accordingly. > > >> >> + /* Assert for dsa_handle to be valid */ >> Was this intended to be turned into an Assert call? Else it seems better >> to remove. >> > > Added an assert and removed the comment. > > >> + if (print_location != PRINT_STATS_NONE) >> This warrants a comment stating why it makes sense. >> > >> + * Do not print the statistics if print_to_stderr is PRINT_STATS_NONE, >> s/print_to_stderr/print_location/. Also, do we really need >> print_to_stderr in >> this function at all? It seems a bit awkward to combine a boolean and a >> paramter for a tri-state value when the parameter holds the tri_state >> already. >> For readability I think just checking print_location will be better since >> the >> value will be clear, where print_to_stderr=false is less clear in a >> tri-state >> scenario. >> >> I removed the boolean print_to_stderr, the checks are now using > the tri-state enum-print_location. > > >> + * its ancestors to a list, inorder to compute a path. >> s/inorder/in order/ >> >> Fixed. > > >> >> + elog(LOG, "hash table corrupted, can't construct path value"); >> + break; >> This will return either a NIL list or a partial path, but >> PublishMemoryContext >> doesn't really take into consideration that it might be so. Is this >> really >> benign to the point that we can blindly go on? Also, elog(LOG..) is >> mostly for >> tracing or debugging as elog() isn't intended for user facing errors. >> >> I agree that this should be addressed. I added a check for path value > before > storing it in shared memory. If the path is NIL, the path pointer in DSA > will point > to InvalidDsaPointer. > When a client encounters an InvalidDsaPointer it will print NULL in the > path column. > Partial path scenario is unlikely IMO, and I am not sure if it warrants > additional > checks. > > >> +static void >> +compute_num_of_contexts(List *contexts, HTAB *context_id_lookup, >> + int *stats_count, bool get_summary) >> This function does a lot than compute the number of contexts so the name >> seems >> a bit misleading. Perhaps a rename to compute_contexts() or something >> similar? >> >> Renamed to compute_contexts_count_and_ids. > > >> >> + memctx_info[curr_id].name = dsa_allocate0(area, >> + strlen(clipped_ident) + 1); >> These calls can use idlen instead of more strlen() calls no? While there >> is no >> performance benefit, it would increase readability IMHO if the code first >> calculates a value, and then use it. >> >> Done. > > >> >> + strncpy(name, >> + clipped_ident, strlen(clipped_ident)); >> Since clipped_ident has been nul terminated earlier there is no need to >> use >> strncpy, we can instead use strlcpy and take the target buffer size into >> consideration rather than the input string length. >> >> Replaced with the strlcpy calls. > > >> >> PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts >> */ >> + PROCSIG_GET_MEMORY_CONTEXT, /* ask backend to log the memory contexts >> */ >> This comment should be different from the LOG_MEMORY_xx one. >> >> Fixed. > > +#define MEM_CONTEXT_SHMEM_STATS_SIZE 30 >> +#define MAX_TYPE_STRING_LENGTH 64 >> These are unused, from an earlier version of the patch perhaps? >> >> Removed > > + * Singe DSA area is created and used by all the processes, >> s/Singe/Since/ >> > > Fixed. > > +typedef struct MemoryContextBackendState >> This is only used in mcxtfuncs.c and can be moved there rather than being >> exported in the header. >> > > This is being used in mcxt.c too in the form of the variable memCtxState. > > >> > > +} MemoryContextId; >> This lacks an entry in the typedefs.list file. >> >> Added. > > Please find attached the updated patches with the above fixes. > > Thank you, > Rahila Syed > -
Re: Enhancing Memory Context Statistics Reporting
Alexander Korotkov <aekorotkov@gmail.com> — 2025-03-15T08:40:39Z
Hi, Rahila! On Thu, Mar 13, 2025 at 3:57 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > > Please find attached updated and rebased patches. It has the following changes > > 1. To prevent memory leaks, ensure that the latest statistics published by a process > are freed before it exits. This can be achieved by calling dsa_free in the > before_shmem_exit callback. > 2. Add a level column to maintain consistency with the output of > pg_backend_memory_contexts. Thank you for your work on this subject. v17-0001-Preparatory-changes-for-reporting-memory-context-sta.patch It looks like we're increasing *num_contexts twice per child memory context. First, it gets increased with a recursive MemoryContextStatsInternal() call, then by adding an ichild. I might be wrong, but I think these calculations at least deserve more comments. v17-0002-Function-to-report-memory-context-statistics.patch + if (procNumber == MyProcNumber) + { + ereport(WARNING, + errmsg("cannot return statistics for local backend"), + errhint("Use pg_backend_memory_contexts view instead.")); + PG_RETURN_NULL(); + } Is it worth it to keep this restriction? Can we fetch info about local memory context for the sake of generality? I know there have been discussions in the thread before, but the mechanism of publishing memory context stats via DSA looks quite complicated. Also, the user probably intends to inspect memory contexts when there is not a huge amount of free memory. So, failure is probable on DSA allocation. Could we do simpler? For instance, allocate some amount of static shared memory and use it as a message queue between processes. As a heavy load is not supposed to be here, I think one queue would be enough. ------ Regards, Alexander Korotkov Supabase -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-03-17T07:52:41Z
Hi Alexander, Thank you for the review. > It looks like we're increasing *num_contexts twice per child memory > context. First, it gets increased with a recursive > MemoryContextStatsInternal() call, then by adding an ichild. I might > be wrong, but I think these calculations at least deserve more > comments. > I believe that's not the case. The recursive calls only work for children encountered up to max_level and less than max_children per context. The rest of the children are handled using MemoryContextTraverseNext, without recursive calls. Thus, num_contexts is incremented for those children separately from the recursive call counter. I will add more comments around this. > v17-0002-Function-to-report-memory-context-statistics.patch > > + if (procNumber == MyProcNumber) > + { > + ereport(WARNING, > + errmsg("cannot return statistics for local backend"), > + errhint("Use pg_backend_memory_contexts view instead.")); > + PG_RETURN_NULL(); > + } > > Is it worth it to keep this restriction? Can we fetch info about > local memory context for the sake of generality? > > I think that could be done, but using pg_backend_memory_context would be more efficient in this case. > I know there have been discussions in the thread before, but the > mechanism of publishing memory context stats via DSA looks quite > complicated. Also, the user probably intends to inspect memory > contexts when there is not a huge amount of free memory. So, failure > is probable on DSA allocation. Could we do simpler? For instance, > allocate some amount of static shared memory and use it as a message > queue between processes. As a heavy load is not supposed to be here, > I think one queue would be enough. > > There could be other uses for such a function, such as a monitoring dashboard that periodically queries all running backends for memory statistics. If we use a single queue shared between all the backends, they will need to wait for the queue to become available before sharing their statistics, leading to processing delays at the publishing backend. Even with separate queues for each backend or without expecting concurrent use, publishing statistics could be delayed if a message queue is full. This is because a backend needs to wait for a client process to consume existing messages or statistics before publishing more. If a client process exits without consuming messages, the publishing backend will experience timeouts when trying to publish stats. This will impact backend performance as statistics are published during CHECK_FOR_INTERRUPTS. In the current implementation, the backend publishes all the statistics in one go without waiting for clients to read any statistics. In addition, allocating complete message queues in static shared memory can be expensive, especially since these static structures need to be created even if memory context statistics are never queried. On the contrary, a dsa is created for the feature whenever statistics are first queried. We are not preallocating shared memory for this feature, except for small structures to store the dsa_handle and dsa_pointers for each backend. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2025-03-17T08:36:50Z
On Mon, Mar 17, 2025 at 1:23 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > >> >> v17-0002-Function-to-report-memory-context-statistics.patch >> >> + if (procNumber == MyProcNumber) >> + { >> + ereport(WARNING, >> + errmsg("cannot return statistics for local backend"), >> + errhint("Use pg_backend_memory_contexts view instead.")); >> + PG_RETURN_NULL(); >> + } >> >> Is it worth it to keep this restriction? Can we fetch info about >> local memory context for the sake of generality? >> > > I think that could be done, but using pg_backend_memory_context would > be more efficient in this case. > I have raised a similar concern before. Having two separate functions one for local backend and other for remote is going to be confusing. We should have one function doing both and renamed appropriately. -- Best Wishes, Ashutosh Bapat -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-03-20T07:39:45Z
Hi, >> > >> + if (procNumber == MyProcNumber) > >> + { > >> + ereport(WARNING, > >> + errmsg("cannot return statistics for local backend"), > >> + errhint("Use pg_backend_memory_contexts view > instead.")); > >> + PG_RETURN_NULL(); > >> + } > >> > >> Is it worth it to keep this restriction? Can we fetch info about > >> local memory context for the sake of generality? > >> > > > > I think that could be done, but using pg_backend_memory_context would > > be more efficient in this case. > > > > I have raised a similar concern before. Having two separate functions > one for local backend and other for remote is going to be confusing. > We should have one function doing both and renamed appropriately. > > This is a separate concern from what has been raised by Alexander. He has suggested removing the restriction and fetching local backend statistics also with the proposed function. I've removed this restriction in the latest version of the patch. Now, the proposed function can be used to fetch local backend statistics too. Regarding your suggestion on merging these functions, although they both report memory context statistics, they differ in how they fetch these statistics—locally versus from dynamic shared memory. Additionally, the function signatures are different: the proposed function takes three arguments (pid, get_summary, and num_tries), whereas pg_get_backend_memory_contexts does not take any arguments. Therefore, I believe these functions can remain separate as long as we document their usages correctly. Please find attached rebased and updated patches. I have added some more comments and fixed an issue caused due to registering before_shmem_exit callback from interrupt processing routine. To address this issue, I am registering this callback in the InitProcess() function. This happened because interrupt processing could be triggered from a PG_ENSURE_ERROR_CLEANUP block. This block operates under the assumption that the before_shmem_exit callback registered at the beginning of the block, will be the last one in the registered callback list at the end of the block, which would not be the case if I register before_shmem_exit callback in the interrupt handling routine. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-03-25T14:14:08Z
> On 20 Mar 2025, at 08:39, Rahila Syed <rahilasyed90@gmail.com> wrote: Thanks for the new version, I believe this will be a welcome tool in the debugging toolbox. I took a cleanup pass over the docs with among others the below changes: * You had broken the text in paragraphs, but without <para/> tags they are rendered as a single blob of text so added that. * Removed the "(PID)" explanation as process id is used elsewhere on the same page already without explanation. * Added <productname/> markup on PostgreSQL * Added <literal/> markup on paramater values * Switched the example query output to use \x * Added a note on when pg_backend_memory_contexts is a better choice The paragraphs need some re-indenting to avoid too long lines, but I opted out of doing so here to make reviewing the changes easier. A few comments on the code (all comments are performed in 0003 attached here which also has smaller cleanups wrt indentation, code style etc): +#include <math.h> I don't think we need this, maybe it was from an earlier version of the patch? +MEM_CTX_PUBLISH "Waiting for backend to publish memory information." I wonder if this should really be "process" and not backend? + default: + context_type = "???"; + break; In ContextTypeToString() I'm having second thoughts about this, there shouldn't be any legitimate use-case of passing a nodetag this function which would fail MemoryContextIsValid(). I wonder if we aren't helping callers more by erroring out rather than silently returning an unknown? I haven't changed this but maybe we should to set the API right from the start? + * if the process has more memory contexts than that can fit in the allocated s/than that can/than what can/? + errmsg("memory context statistics privilege error")); Similar checks elsewhere in the tree mostly use "permission denied to .." so I think we should adopt that here as well. + LWLockAcquire(&memCtxState[procNumber].lw_lock, LW_EXCLUSIVE); + msecs = + TimestampDifferenceMilliseconds(curr_timestamp, + memCtxState[procNumber].stats_timestamp); Since we only want to consider the stats if they are from the current process, we can delay checking the time difference until after we've checked the pid and thus reduce the amount of time we hold the lock in the error case. + /* + * Recheck the state of the backend before sleeping on the condition + * variable + */ + proc = BackendPidGetProc(pid); Here we are really rechecking that the process is still alive, but I wonder if we should take the opportunity to ensure that the type is what we expect it to be? If the pid has moved from being a backend to an aux proc or vice versa we really don't want to go on. + ereport(WARNING, + errmsg("PID %d is not a PostgreSQL server process", + pid)); I wonder if we should differentiate between the warnings? When we hit this in the loop the errmsg is describing a slightly different case. I did leave it for now, but it's food for thought if we should perhaps reword this one. + ereport(LOG, + errmsg("Wait for %d process to publish stats timed out, trying again", + pid)); This should probably by DEBUG1, in a congested cluster it might cause a fair bit of logging which isn't really helping the user. Also, nitpick, errmsg starts with a lowercase letter. +static Size +MemCtxShmemSize(void) We don't really need this function anymore and keeping it separate we risk it going out of sync with the matching calcuation in MemCtxBackendShmemInit, so I think we should condense into one. else { + Assert(print_location == PRINT_STATS_NONE); Rather than an if-then-else and an assert we can use a switch statement without a default, this way we'll automatically get a warning if a value is missed. + ereport(LOG, + errmsg("hash table corrupted, can't construct path value")); I know you switched from elog(LOG.. to ereport(LOG.. but I still think a LOG entry stating corruption isn't helpful, it's not actionable for the user. Given that it's a case that shouldn't happen I wonder if we should downgrade it to an Assert(false) and potentially a DEBUG1? -- Daniel Gustafsson -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-03-26T10:34:17Z
Hi Daniel, Thank you for your review. I have incorporated all your changes in v20 patches and ensured that the review comments corresponding to 0001 patch are included in that patch and not in 0002. > > +MEM_CTX_PUBLISH "Waiting for backend to publish memory information." > I wonder if this should really be "process" and not backend? > > Fixed. > > + default: > + context_type = "???"; > + break; > In ContextTypeToString() I'm having second thoughts about this, there > shouldn't > be any legitimate use-case of passing a nodetag this function which would > fail > MemoryContextIsValid(). I wonder if we aren't helping callers more by > erroring > out rather than silently returning an unknown? I haven't changed this but > maybe we should to set the API right from the start? > I cannot think of any legitimate scenario where the context type would be unknown. However, if we were to throw an error, it would prevent us from reporting any memory usage information when the context type is unidentified. Perhaps, it would be more informative and less restrictive to label it as "Unrecognized" or "Unknown." I wonder if this was the reasoning behind doing it when it was added with the pg_backend_memory_contexts() function. > > + /* > + * Recheck the state of the backend before sleeping on the > condition > + * variable > + */ > + proc = BackendPidGetProc(pid); > Here we are really rechecking that the process is still alive, but I > wonder if > we should take the opportunity to ensure that the type is what we expect > it to > be? If the pid has moved from being a backend to an aux proc or vice > versa we > really don't want to go on. > > The reasoning makes sense to me. For periodic monitoring of all processes, any PID that reincarnates into a different type could be queried in subsequent function calls. Regarding targeted monitoring of a specific process, such a reincarnated process would exhibit a completely different memory consumption, likely not aligning with the user's original intent behind requesting the statistics. > > + ereport(WARNING, > + errmsg("PID %d is not a PostgreSQL server process", > + pid)); > I wonder if we should differentiate between the warnings? When we hit > this in > the loop the errmsg is describing a slightly different case. I did leave > it > for now, but it's food for thought if we should perhaps reword this one. > > Changed it to "PID %d is no longer the same PostgreSQL server process". > > + ereport(LOG, > + errmsg("hash table corrupted, can't construct path > value")); > I know you switched from elog(LOG.. to ereport(LOG.. but I still think a > LOG > entry stating corruption isn't helpful, it's not actionable for the user. > Given that it's a case that shouldn't happen I wonder if we should > downgrade it > to an Assert(false) and potentially a DEBUG1? > > How about changing it to ERROR, in accordance with current occurrences of the same message? I did it in the attached version, however I am open to changing it to an Assert(false) and DEBUG1. Apart from the above, I made the following improvements. 1. Eliminated the unnecessary creation of an extra memory context before calling hash_create. The hash_create function already generates a memory context containing the hash table, enabling easy memory deallocation by simply deleting the context via hash_destroy. Therefore, the patch relies on hash_destroy for memory management instead of manual freeing. 2. Optimized memory usage by storing the path as an array of integers rather than as an array of Datums. This approach conserves DSA memory allocated for storing this information. 3. Miscellaneous comment cleanups and introduced macros to simplify calculations. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-02T21:44:25Z
> On 26 Mar 2025, at 11:34, Rahila Syed <rahilasyed90@gmail.com> wrote: > + ereport(LOG, > + errmsg("hash table corrupted, can't construct path value")); > I know you switched from elog(LOG.. to ereport(LOG.. but I still think a LOG > entry stating corruption isn't helpful, it's not actionable for the user. > Given that it's a case that shouldn't happen I wonder if we should downgrade it > to an Assert(false) and potentially a DEBUG1? > > How about changing it to ERROR, in accordance with current occurrences of the > same message? I did it in the attached version, however I am open to changing > it to an Assert(false) and DEBUG1. In the attached I moved it to an elog() as it's an internal error, and spending translation effort on it seems fruitless. > 1. Eliminated the unnecessary creation of an extra memory context before calling hash_create. > The hash_create function already generates a memory context containing the hash table, > enabling easy memory deallocation by simply deleting the context via hash_destroy. > Therefore, the patch relies on hash_destroy for memory management instead of manual freeing. Nice > 2. Optimized memory usage by storing the path as an array of integers rather than as an array of > Datums. > This approach conserves DSA memory allocated for storing this information. Ah yes, much better. The attached v21 has a few improvements: * The function documentation didn't specify the return type, only the fact that it's setof record. I've added all output columns. * Some general cleaups of the docs with better markup, improved xref linking and various rewording. * Comment cleanups and language alignment * Added a missing_ok parameter to ContextTypeToString(). While all callers are fine with unknown context types, if we introduce an API for this it seems prudent to not place that burden on callers but to take it on in the function. * Renamed get_summary to just summary, and num_of_tries to retries which feels more in line with the naming convention in other functions * Deferred calling InitMaterializedSRF() until after the PID has been checked for validity. * Pulled back the timeout to 500msec from 1 second. In running congested pgbench simulations I saw better performance and improved results in getting stats. * Replaced strncpy with strlcpy and consistently used idlen to keep all length calculations equal. * Fixed misspelled param name in pg_proc.dat * Pulled back maximum memory usage from 8Mb to 1Mb. 8Mb for the duration of a process (once allocated) is a lot for a niche feature and I while I'm still not sure 1Mb is the right value I think from experimentation that it's closer. I think this version is close to a committable state, will spend a little more time testing, polishing and rewriting the commit message. I will also play around with placement within the memory context code files to keep it from making backpatch issues. -- Daniel Gustafsson -
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-05T19:29:21Z
> On 2 Apr 2025, at 23:44, Daniel Gustafsson <daniel@yesql.se> wrote: > I think this version is close to a committable state, will spend a little more > time testing, polishing and rewriting the commit message. I will also play > around with placement within the memory context code files to keep it from > making backpatch issues. After a bit more polish I landed with the attached, which I most likely will go ahead with after another round in CI. -- Daniel Gustafsson
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-04-07T06:51:43Z
Hi Daniel, > > After a bit more polish I landed with the attached, which I most likely > will go > ahead with after another round in CI. > Thank you for refining the code. The changes look good to me. Regression tests ran smoothly in parallel with the memory monitoring function, pgbench results with the following custom script also shows good performance. ``` SELECT * FROM pg_get_process_memory_contexts( (SELECT pid FROM pg_stat_activity ORDER BY random() LIMIT 1) , false, 5); ``` Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-07T13:41:37Z
Following up on some off-list comments, attached is a v26 with a few small last changes: * Improved documentation (docs and comments) * Fixed up Shmem sizing and init * Delayed registering to the shmem cleanup to get it earlier in cleanup * Renamed a few datastructures to improve readability * Various bits of polish I think this function can be a valuable debugging aid going forward. -- Daniel Gustafsson
-
Re: Enhancing Memory Context Statistics Reporting
Andres Freund <andres@anarazel.de> — 2025-04-07T15:43:51Z
Hi, On 2025-04-07 15:41:37 +0200, Daniel Gustafsson wrote: > I think this function can be a valuable debugging aid going forward. What I am most excited about for this is to be able to measure server-wide and fleet-wide memory usage over time. Today I have actually very little idea about what memory is being used for across all connections, not to speak of a larger number of servers. > diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c > index 4f6795f7265..d3b4df27935 100644 > --- a/src/backend/postmaster/auxprocess.c > +++ b/src/backend/postmaster/auxprocess.c > @@ -84,6 +84,13 @@ AuxiliaryProcessMainCommon(void) > /* register a before-shutdown callback for LWLock cleanup */ > before_shmem_exit(ShutdownAuxiliaryProcess, 0); > > + /* > + * The before shmem exit callback frees the DSA memory occupied by the > + * latest memory context statistics that could be published by this aux > + * proc if requested. > + */ > + before_shmem_exit(AtProcExit_memstats_dsa_free, 0); > + > SetProcessingMode(NormalProcessing); > } How about putting it into BaseInit()? Or maybe just register it when its first used? > diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c > index fda91ffd1ce..d3cb3f1891c 100644 > --- a/src/backend/postmaster/checkpointer.c > +++ b/src/backend/postmaster/checkpointer.c > @@ -663,6 +663,10 @@ ProcessCheckpointerInterrupts(void) > /* Perform logging of memory contexts of this process */ > if (LogMemoryContextPending) > ProcessLogMemoryContextInterrupt(); > + > + /* Publish memory contexts of this process */ > + if (PublishMemoryContextPending) > + ProcessGetMemoryContextInterrupt(); > } > > /* Not this patch's responsibility, but we really desperately need to unify our interrupt handling. Manually keeping a ~dozen of functions similar, but not exactly the same, is an insane approach. > --- a/src/backend/utils/activity/wait_event_names.txt > +++ b/src/backend/utils/activity/wait_event_names.txt > @@ -161,6 +161,7 @@ WAL_RECEIVER_EXIT "Waiting for the WAL receiver to exit." > WAL_RECEIVER_WAIT_START "Waiting for startup process to send initial data for streaming replication." > WAL_SUMMARY_READY "Waiting for a new WAL summary to be generated." > XACT_GROUP_UPDATE "Waiting for the group leader to update transaction status at transaction end." > +MEM_CTX_PUBLISH "Waiting for a process to publish memory information." The memory context stuff abbreviates as cxt not ctx. There's a few more cases of that in the patch. > +const char * > +ContextTypeToString(NodeTag type) > +{ > + const char *context_type; > + > + switch (type) > + { > + case T_AllocSetContext: > + context_type = "AllocSet"; > + break; > + case T_GenerationContext: > + context_type = "Generation"; > + break; > + case T_SlabContext: > + context_type = "Slab"; > + break; > + case T_BumpContext: > + context_type = "Bump"; > + break; > + default: > + context_type = "???"; > + break; > + } > + return (context_type); Why these parens? > + * If the publishing backend does not respond before the condition variable > + * times out, which is set to MEMSTATS_WAIT_TIMEOUT, retry given that there is > + * time left within the timeout specified by the user, before giving up and > + * returning previously published statistics, if any. If no previous statistics > + * exist, return NULL. Why do we need to repeatedly wake up rather than just sleeping with the "remaining" amount of time based on the time the function was called and the time that has passed since? > + /* > + * A valid DSA pointer isn't proof that statistics are available, it can > + * be valid due to previously published stats. Somehow "valid DSA pointer" is a bit too much about the precise mechanics and not enough about what's actually happening. I'd rather say something like "Even if the proc has published statistics, they may not be due to the current request, but previously published stats." > + if (ConditionVariableTimedSleep(&memCtxState[procNumber].memctx_cv, > + MEMSTATS_WAIT_TIMEOUT, > + WAIT_EVENT_MEM_CTX_PUBLISH)) > + { > + timer += MEMSTATS_WAIT_TIMEOUT; > + > + /* > + * Wait for the timeout as defined by the user. If no updated > + * statistics are available within the allowed time then display > + * previously published statistics if there are any. If no > + * previous statistics are available then return NULL. The timer > + * is defined in milliseconds since thats what the condition > + * variable sleep uses. > + */ > + if ((timer * 1000) >= timeout) > + { I'd suggest just comparing how much time has elapsed since the timestamp you've requested earlier. > + LWLockAcquire(&memCtxState[procNumber].lw_lock, LW_EXCLUSIVE); > + /* Displaying previously published statistics if available */ > + if (DsaPointerIsValid(memCtxState[procNumber].memstats_dsa_pointer)) > + break; > + else > + { > + LWLockRelease(&memCtxState[procNumber].lw_lock); > + PG_RETURN_NULL(); > + } > + } > + } > + } > +/* > + * Initialize shared memory for displaying memory context statistics > + */ > +void > +MemoryContextReportingShmemInit(void) > +{ > + bool found; > + > + memCtxArea = (MemoryContextState *) > + ShmemInitStruct("MemoryContextState", sizeof(MemoryContextState), &found); > + > + if (!IsUnderPostmaster) > + { > + Assert(!found); I don't really understand why this uses IsUnderPostmaster? Seems like this should just use found like most (or all) the other *ShmemInit() functions do? > + LWLockInitialize(&memCtxArea->lw_lock, LWLockNewTrancheId()); I think for builtin code we just hardcode the tranches in BuiltinTrancheIds. > + memCtxState = (MemoryContextBackendState *) > + ShmemInitStruct("MemoryContextBackendState", > + ((MaxBackends + NUM_AUXILIARY_PROCS) * sizeof(MemoryContextBackendState)), > + &found); FWIW, I think it'd be mildly better if these two ShmemInitStruct()'s were combined. > static void > MemoryContextStatsInternal(MemoryContext context, int level, > int max_level, int max_children, > MemoryContextCounters *totals, > - bool print_to_stderr) > + PrintDestination print_location, int *num_contexts) > { > MemoryContext child; > int ichild; > @@ -884,10 +923,39 @@ MemoryContextStatsInternal(MemoryContext context, int level, > Assert(MemoryContextIsValid(context)); > > /* Examine the context itself */ > - context->methods->stats(context, > - MemoryContextStatsPrint, > - &level, > - totals, print_to_stderr); > + switch (print_location) > + { > + case PRINT_STATS_TO_STDERR: > + context->methods->stats(context, > + MemoryContextStatsPrint, > + &level, > + totals, true); > + break; > + > + case PRINT_STATS_TO_LOGS: > + context->methods->stats(context, > + MemoryContextStatsPrint, > + &level, > + totals, false); > + break; > + > + case PRINT_STATS_NONE: > + > + /* > + * Do not print the statistics if print_location is > + * PRINT_STATS_NONE, only compute totals. This is used in > + * reporting of memory context statistics via a sql function. Last > + * parameter is not relevant. > + */ > + context->methods->stats(context, > + NULL, > + NULL, > + totals, false); > + break; > + } > + > + /* Increment the context count for each of the recursive call */ > + *num_contexts = *num_contexts + 1; It feels a bit silly to duplicate the call to context->methods->stats three times. We've changed these parameters a bunch in the past, having more callers to fix makes that more work. Can't the switch just set up the args that are then passed to one call to context->methods->stats? > + > + /* Compute the number of stats that can fit in the defined limit */ > + max_stats = (MAX_SEGMENTS_PER_BACKEND * DSA_DEFAULT_INIT_SEGMENT_SIZE) > + / (MAX_MEMORY_CONTEXT_STATS_SIZE); MAX_SEGMENTS_PER_BACKEND sounds way too generic to me for something defined in memutils.h. I don't really understand why DSA_DEFAULT_INIT_SEGMENT_SIZE is something that makes sense to use here? The header says: > +/* Maximum size (in Mb) of DSA area per process */ > +#define MAX_SEGMENTS_PER_BACKEND 1 But the name doesn't at all indicate it's in megabytes. Nor does the way it's used clearly indicate that. That seems to be completely incidental based on the current default value DSA_DEFAULT_INIT_SEGMENT_SIZE. > + /* > + * Hold the process lock to protect writes to process specific memory. Two > + * processes publishing statistics do not block each other. > + */ s/specific/process specific/ > + LWLockAcquire(&memCtxState[idx].lw_lock, LW_EXCLUSIVE); > + memCtxState[idx].proc_id = MyProcPid; > + > + if (DsaPointerIsValid(memCtxState[idx].memstats_dsa_pointer)) > + { > + /* > + * Free any previous allocations, free the name, ident and path > + * pointers before freeing the pointer that contains them. > + */ > + free_memorycontextstate_dsa(area, memCtxState[idx].total_stats, > + memCtxState[idx].memstats_dsa_pointer); > + > + dsa_free(area, memCtxState[idx].memstats_dsa_pointer); > + memCtxState[idx].memstats_dsa_pointer = InvalidDsaPointer; Both callers to free_memorycontextstate_dsa() do these lines immediately after calling free_memorycontextstate_dsa(), why not do that inside? > + for (MemoryContext c = TopMemoryContext->firstchild; c != NULL; > + c = c->nextchild) > + { > + MemoryContextCounters grand_totals; > + int num_contexts = 0; > + int level = 0; > + > + path = NIL; > + memset(&grand_totals, 0, sizeof(grand_totals)); > + > + MemoryContextStatsInternal(c, level, 100, 100, &grand_totals, > + PRINT_STATS_NONE, &num_contexts); > + > + path = compute_context_path(c, context_id_lookup); > + > + PublishMemoryContext(meminfo, ctx_id, c, path, > + grand_totals, num_contexts, area, 100); > + ctx_id = ctx_id + 1; > + } > + memCtxState[idx].total_stats = ctx_id; > + /* Notify waiting backends and return */ > + hash_destroy(context_id_lookup); > + dsa_detach(area); > + signal_memorycontext_reporting(); > + } > + > + foreach_ptr(MemoryContextData, cur, contexts) > + { > + List *path = NIL; > + > + /* > + * Figure out the transient context_id of this context and each of its > + * ancestors, to compute a path for this context. > + */ > + path = compute_context_path(cur, context_id_lookup); > + > + /* Account for saving one statistics slot for cumulative reporting */ > + if (context_id < (max_stats - 1) || stats_count <= max_stats) > + { > + /* Examine the context stats */ > + memset(&stat, 0, sizeof(stat)); > + (*cur->methods->stats) (cur, NULL, NULL, &stat, true); Hm. So here we call the callback ourselves, even though we extended MemoryContextStatsInternal() to satisfy the summary output. I guess it's tolerable, but it's not great. > + /* Copy statistics to DSA memory */ > + PublishMemoryContext(meminfo, context_id, cur, path, stat, 1, area, 100); > + } > + else > + { > + /* Examine the context stats */ > + memset(&stat, 0, sizeof(stat)); > + (*cur->methods->stats) (cur, NULL, NULL, &stat, true); But do we really do it twice in a row? The lines are exactly the same, so it seems that should just be done before the if? > + > + /* Notify waiting backends and return */ > + hash_destroy(context_id_lookup); > + dsa_detach(area); > + signal_memorycontext_reporting(); > +} > + > +/* > + * Signal all the waiting client backends after copying all the statistics. > + */ > +static void > +signal_memorycontext_reporting(void) > +{ > + memCtxState[MyProcNumber].stats_timestamp = GetCurrentTimestamp(); > + LWLockRelease(&memCtxState[MyProcNumber].lw_lock); > + ConditionVariableBroadcast(&memCtxState[MyProcNumber].memctx_cv); > +} IMO somewhat confusing to release the lock in a function named signal_memorycontext_reporting(). Why do we do that after hash_destroy()/dsa_detach()? > +static void > +compute_contexts_count_and_ids(List *contexts, HTAB *context_id_lookup, > + int *stats_count, bool summary) > +{ > + foreach_ptr(MemoryContextData, cur, contexts) > + { > + MemoryContextId *entry; > + bool found; > + > + entry = (MemoryContextId *) hash_search(context_id_lookup, &cur, > + HASH_ENTER, &found); > + Assert(!found); > + > + /* context id starts with 1 */ > + entry->context_id = ++(*stats_count); Given that we don't actually do anything here relating to starting with 1, I find that comment confusing. > +static void > +PublishMemoryContext(MemoryContextStatsEntry *memctx_info, int curr_id, > + MemoryContext context, List *path, > + MemoryContextCounters stat, int num_contexts, > + dsa_area *area, int max_levels) > +{ > + const char *ident = context->ident; > + const char *name = context->name; > + int *path_list; > + > + /* > + * To be consistent with logging output, we label dynahash contexts with > + * just the hash table name as with MemoryContextStatsPrint(). > + */ > + if (context->ident && strncmp(context->name, "dynahash", 8) == 0) > + { > + name = context->ident; > + ident = NULL; > + } > + > + if (name != NULL) > + { > + int namelen = strlen(name); > + char *nameptr; > + > + if (strlen(name) >= MEMORY_CONTEXT_IDENT_SHMEM_SIZE) > + namelen = pg_mbcliplen(name, namelen, > + MEMORY_CONTEXT_IDENT_SHMEM_SIZE - 1); > + > + memctx_info[curr_id].name = dsa_allocate0(area, namelen + 1); Given the number of references to memctx_info[curr_id] I'd put it in a local variable. Why is this a dsa_allocate0 given that we're immediately overwriting it? > + nameptr = (char *) dsa_get_address(area, memctx_info[curr_id].name); > + strlcpy(nameptr, name, namelen + 1); > + } > + else > + memctx_info[curr_id].name = InvalidDsaPointer; > + > + /* Trim and copy the identifier if it is not set to NULL */ > + if (ident != NULL) > + { > + int idlen = strlen(context->ident); > + char *identptr; > + > + /* > + * Some identifiers such as SQL query string can be very long, > + * truncate oversize identifiers. > + */ > + if (idlen >= MEMORY_CONTEXT_IDENT_SHMEM_SIZE) > + idlen = pg_mbcliplen(ident, idlen, > + MEMORY_CONTEXT_IDENT_SHMEM_SIZE - 1); > + > + memctx_info[curr_id].ident = dsa_allocate0(area, idlen + 1); > + identptr = (char *) dsa_get_address(area, memctx_info[curr_id].ident); > + strlcpy(identptr, ident, idlen + 1); Hm. First I thought we'd leak memory if this second (and subsequent) dsa_allocate failed. Then I thought we'd be ok, because the memory would be memory because it'd be reachable from memCtxState[idx].memstats_dsa_pointer. But I think it wouldn't *quite* work, because memCtxState[idx].total_stats is only set *after* we would have failed. > + /* Allocate DSA memory for storing path information */ > + if (path == NIL) > + memctx_info[curr_id].path = InvalidDsaPointer; > + else > + { > + int levels = Min(list_length(path), max_levels); > + > + memctx_info[curr_id].path_length = levels; > + memctx_info[curr_id].path = dsa_allocate0(area, levels * sizeof(int)); > + memctx_info[curr_id].levels = list_length(path); > + path_list = (int *) dsa_get_address(area, memctx_info[curr_id].path); > + > + foreach_int(i, path) > + { > + path_list[foreach_current_index(i)] = i; > + if (--levels == 0) > + break; > + } > + } > + memctx_info[curr_id].type = ContextTypeToString(context->type); I don't think this works across platforms. On windows / EXEC_BACKEND builds the location of string constants can differ across backends. And: Why do we need the string here? You can just call ContextTypeToString when reading? > +/* > + * Free the memory context statistics stored by this process > + * in DSA area. > + */ > +void > +AtProcExit_memstats_dsa_free(int code, Datum arg) > +{ FWIW, to me the fact that it does a dsa_free() is an implementation detail. It's also not the only thing this does. And, I don't think AtProcExit* really is accurate, given that it runs *before* shmem is cleaned up? I wonder if the best approach here wouldn't be to forgo the use of a before_shmem_exit() callback, but instead use on_dsm_detach(). That would require we'd not constantly detach from the dsm segment, but I don't understand why we do that in the first place? > + int idx = MyProcNumber; > + dsm_segment *dsm_seg = NULL; > + dsa_area *area = NULL; > + > + if (memCtxArea->memstats_dsa_handle == DSA_HANDLE_INVALID) > + return; > + > + dsm_seg = dsm_find_mapping(memCtxArea->memstats_dsa_handle); > + > + LWLockAcquire(&memCtxState[idx].lw_lock, LW_EXCLUSIVE); > + > + if (!DsaPointerIsValid(memCtxState[idx].memstats_dsa_pointer)) > + { > + LWLockRelease(&memCtxState[idx].lw_lock); > + return; > + } > + > + /* If the dsm mapping could not be found, attach to the area */ > + if (dsm_seg != NULL) > + return; I don't understand what we do here with the dsm? Why do we not need cleanup if we are already attached to the dsm segment? > +/* > + * Static shared memory state representing the DSA area created for memory > + * context statistics reporting. A single DSA area is created and used by all > + * the processes, each having its specific DSA allocations for sharing memory > + * statistics, tracked by per backend static shared memory state. > + */ > +typedef struct MemoryContextState > +{ > + dsa_handle memstats_dsa_handle; > + LWLock lw_lock; > +} MemoryContextState; IMO that's too generic a name for something in a header. > +/* > + * Used for storage of transient identifiers for pg_get_backend_memory_contexts > + */ > +typedef struct MemoryContextId > +{ > + MemoryContext context; > + int context_id; > +} MemoryContextId; This too. Particularly because MemoryContextData->ident exist but is something different. > +DO $$ > +DECLARE > + launcher_pid int; > + r RECORD; > +BEGIN > + SELECT pid from pg_stat_activity where backend_type='autovacuum launcher' > + INTO launcher_pid; > + > + select type, name, ident > + from pg_get_process_memory_contexts(launcher_pid, false, 20) > + where path = '{1}' into r; > + RAISE NOTICE '%', r; > + select type, name, ident > + from pg_get_process_memory_contexts(pg_backend_pid(), false, 20) > + where path = '{1}' into r; > + RAISE NOTICE '%', r; > +END $$; I'd also test an aux process. I think the AV launcher isn't one, because it actually does "table" access of shared relations. Greetings, Andres Freund -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-04-07T16:27:57Z
Hi, Please see some responses below. On Mon, Apr 7, 2025 at 9:13 PM Andres Freund <andres@anarazel.de> wrote: > Hi, > > On 2025-04-07 15:41:37 +0200, Daniel Gustafsson wrote: > > I think this function can be a valuable debugging aid going forward. > > What I am most excited about for this is to be able to measure server-wide > and > fleet-wide memory usage over time. Today I have actually very little idea > about what memory is being used for across all connections, not to speak > of a > larger number of servers. > > > > diff --git a/src/backend/postmaster/auxprocess.c > b/src/backend/postmaster/auxprocess.c > > index 4f6795f7265..d3b4df27935 100644 > > --- a/src/backend/postmaster/auxprocess.c > > +++ b/src/backend/postmaster/auxprocess.c > > @@ -84,6 +84,13 @@ AuxiliaryProcessMainCommon(void) > > /* register a before-shutdown callback for LWLock cleanup */ > > before_shmem_exit(ShutdownAuxiliaryProcess, 0); > > > > + /* > > + * The before shmem exit callback frees the DSA memory occupied by > the > > + * latest memory context statistics that could be published by > this aux > > + * proc if requested. > > + */ > > + before_shmem_exit(AtProcExit_memstats_dsa_free, 0); > > + > > SetProcessingMode(NormalProcessing); > > } > > How about putting it into BaseInit()? Or maybe just register it when its > first used? > > Problem with registering it when dsa is first used is that dsa is used in an interrupt handler. The handler could be called from the PG_ENSURE_ERROR_CLEANUP block. This block operates under the assumption that the before_shmem_exit callback registered at the beginning, will be the last one in the registered callback list at the end of the block. However, this won't be the case if a callback is registered from an interrupt handler called in the PG_ENSURE_ERROR_CLEANUP block. > I don't really understand why DSA_DEFAULT_INIT_SEGMENT_SIZE is something that makes sense to use here? > > To determine the memory limit per backend in multiples of DSA_DEFAULT_INIT_SEGMENT_SIZE. Currently it is set to 1 * DSA_DEFAULT_INIT_SEGMENT_SIZE. Since a call to dsa_create would create a DSA segment of this size, I thought it makes sense to define a limit related to the segment size. > > +/* > > + /* If the dsm mapping could not be found, attach to the area */ > > + if (dsm_seg != NULL) > > + return; > > I don't understand what we do here with the dsm? Why do we not need > cleanup > if we are already attached to the dsm segment? > I am not expecting to hit this case, since we are always detaching from the dsa. This could be an assert but since it is a cleanup code, I thought returning would be a harmless step. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Andres Freund <andres@anarazel.de> — 2025-04-07T16:38:28Z
Hi, On 2025-04-07 21:57:57 +0530, Rahila Syed wrote: > > > diff --git a/src/backend/postmaster/auxprocess.c > > b/src/backend/postmaster/auxprocess.c > > > index 4f6795f7265..d3b4df27935 100644 > > > --- a/src/backend/postmaster/auxprocess.c > > > +++ b/src/backend/postmaster/auxprocess.c > > > @@ -84,6 +84,13 @@ AuxiliaryProcessMainCommon(void) > > > /* register a before-shutdown callback for LWLock cleanup */ > > > before_shmem_exit(ShutdownAuxiliaryProcess, 0); > > > > > > + /* > > > + * The before shmem exit callback frees the DSA memory occupied by > > the > > > + * latest memory context statistics that could be published by > > this aux > > > + * proc if requested. > > > + */ > > > + before_shmem_exit(AtProcExit_memstats_dsa_free, 0); > > > + > > > SetProcessingMode(NormalProcessing); > > > } > > > > How about putting it into BaseInit()? Or maybe just register it when its > > first used? > > > > > Problem with registering it when dsa is first used is that dsa is used in an > interrupt handler. The handler could be called from the > PG_ENSURE_ERROR_CLEANUP block. This block operates under the assumption that > the before_shmem_exit callback registered at the beginning, will be the last > one in the registered callback list at the end of the block. However, this > won't be the case if a callback is registered from an interrupt handler > called in the PG_ENSURE_ERROR_CLEANUP block. Ugh, I really dislike PG_ENSURE_ERROR_CLEANUP(). That's not an argument against moving it to BaseInit() though, as that's called before procsignal is even initialized and before signals are unmasked. > I don't really understand why DSA_DEFAULT_INIT_SEGMENT_SIZE is > > something that makes sense to use here? > > > > > To determine the memory limit per backend in multiples of > DSA_DEFAULT_INIT_SEGMENT_SIZE. > Currently it is set to 1 * DSA_DEFAULT_INIT_SEGMENT_SIZE. > Since a call to dsa_create would create a DSA segment of this size, I > thought it makes sense > to define a limit related to the segment size. I strongly disagree. The limit should be in an understandable unit, not on another subystems's defaults that might change at some point. > > + /* If the dsm mapping could not be found, attach to the area */ > > > + if (dsm_seg != NULL) > > > + return; > > > > I don't understand what we do here with the dsm? Why do we not need > > cleanup > > if we are already attached to the dsm segment? > > > > I am not expecting to hit this case, since we are always detaching from the > dsa. Pretty sure it's reachable, consider a failure of dsa_allocate(). That'll throw an error, while attached to the segment. > This could be an assert but since it is a cleanup code, I thought returning > would be a harmless step. The problem is that the code seems wrong - if we are already attached we'll leak the memory! As I also mentioned, I don't understand why we're constantly attaching/detaching from the dsa/dsm either. It just seems to make things more complicated an dmore expensive. Greetings, Andres Freund
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-04-07T18:30:54Z
> > > > That's not an argument against moving it to BaseInit() though, as that's > called before procsignal is even initialized and before signals are > unmasked. > Yes, OK. > I don't really understand why DSA_DEFAULT_INIT_SEGMENT_SIZE is > > > > something that makes sense to use here? > > > > > > > > To determine the memory limit per backend in multiples of > > DSA_DEFAULT_INIT_SEGMENT_SIZE. > > Currently it is set to 1 * DSA_DEFAULT_INIT_SEGMENT_SIZE. > > Since a call to dsa_create would create a DSA segment of this size, I > > thought it makes sense > > to define a limit related to the segment size. > > I strongly disagree. The limit should be in an understandable unit, not on > another subystems's defaults that might change at some point. > OK, makes sense. > > > > > + /* If the dsm mapping could not be found, attach to the area */ > > > > + if (dsm_seg != NULL) > > > > + return; > > > > > > I don't understand what we do here with the dsm? Why do we not need > > > cleanup > > > if we are already attached to the dsm segment? > > > > > > > I am not expecting to hit this case, since we are always detaching from > the > > dsa. > > Pretty sure it's reachable, consider a failure of dsa_allocate(). That'll > throw an error, while attached to the segment. > > You are right, I did not think of this scenario. > > > This could be an assert but since it is a cleanup code, I thought > returning > > would be a harmless step. > > The problem is that the code seems wrong - if we are already attached we'll > leak the memory! > > I understand your concern. One issue I recall is that we do not have a dsa_find_mapping function similar to dsm_find_mapping(). If I understand correctly, the only way to access an already attached DSA is to ensure we store the DSA area mapping in a global variable. I'm considering using a global variable and accessing it from the cleanup function in case it is already mapped. Does that sound fine? > As I also mentioned, I don't understand why we're constantly > attaching/detaching from the dsa/dsm either. It just seems to make things > more > complicated an dmore expensive. > OK, I see that this could be expensive if a process is periodically being queried for statistics. However, in scenarios where a process is queried only once for memory, statistics, keeping the area mapped would consume memory resources, correct? Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-07T23:17:17Z
> On 7 Apr 2025, at 17:43, Andres Freund <andres@anarazel.de> wrote: > > Hi, > > On 2025-04-07 15:41:37 +0200, Daniel Gustafsson wrote: >> I think this function can be a valuable debugging aid going forward. > > What I am most excited about for this is to be able to measure server-wide and > fleet-wide memory usage over time. Today I have actually very little idea > about what memory is being used for across all connections, not to speak of a > larger number of servers. Thanks for looking, Rahila and I took a collective stab at the review comments. >> + before_shmem_exit(AtProcExit_memstats_dsa_free, 0); >> + >> SetProcessingMode(NormalProcessing); >> } > > How about putting it into BaseInit()? Or maybe just register it when its > first used? Moved to BaseInit(). >> +MEM_CTX_PUBLISH "Waiting for a process to publish memory information." > > The memory context stuff abbreviates as cxt not ctx. There's a few more cases > of that in the patch. I never get that right. Fixed. >> + return (context_type); > > Why these parens? Must be a leftover from something, fixed. Sorry about that. >> + * If the publishing backend does not respond before the condition variable >> + * times out, which is set to MEMSTATS_WAIT_TIMEOUT, retry given that there is >> + * time left within the timeout specified by the user, before giving up and >> + * returning previously published statistics, if any. If no previous statistics >> + * exist, return NULL. > > Why do we need to repeatedly wake up rather than just sleeping with the > "remaining" amount of time based on the time the function was called and the > time that has passed since? Fair point, the current coding was a conversion from the previous retry-based approach but your suggestion is clearly correct. There is still potential for refactoring but at this point I don't want to change too much all at once. >> + * A valid DSA pointer isn't proof that statistics are available, it can >> + * be valid due to previously published stats. > > Somehow "valid DSA pointer" is a bit too much about the precise mechanics and > not enough about what's actually happening. I'd rather say something like > > "Even if the proc has published statistics, they may not be due to the current > request, but previously published stats." Agreed, thats better. Changed. >> + if (!IsUnderPostmaster) >> + { >> + Assert(!found); > > I don't really understand why this uses IsUnderPostmaster? Seems like this > should just use found like most (or all) the other *ShmemInit() functions do? Agreed, Fixed. >> + LWLockInitialize(&memCtxArea->lw_lock, LWLockNewTrancheId()); > > I think for builtin code we just hardcode the tranches in BuiltinTrancheIds. Fixed. > It feels a bit silly to duplicate the call to context->methods->stats three > times. We've changed these parameters a bunch in the past, having more callers > to fix makes that more work. Can't the switch just set up the args that are > then passed to one call to context->methods->stats? I don't disagree, but I prefer to do that as a separate refactoring to not change too many things all at once. >> + >> + /* Compute the number of stats that can fit in the defined limit */ >> + max_stats = (MAX_SEGMENTS_PER_BACKEND * DSA_DEFAULT_INIT_SEGMENT_SIZE) >> + / (MAX_MEMORY_CONTEXT_STATS_SIZE); > > MAX_SEGMENTS_PER_BACKEND sounds way too generic to me for something defined in > memutils.h. I don't really understand why DSA_DEFAULT_INIT_SEGMENT_SIZE is > something that makes sense to use here? Renamed, and dependency on DSA_DEFAULT_INIT_SEGMENT_SIZE removed. >> + /* >> + * Hold the process lock to protect writes to process specific memory. Two >> + * processes publishing statistics do not block each other. >> + */ > > s/specific/process specific/ That's what it says though.. isn't it? I might be missing something obvious. >> + dsa_free(area, memCtxState[idx].memstats_dsa_pointer); >> + memCtxState[idx].memstats_dsa_pointer = InvalidDsaPointer; > > Both callers to free_memorycontextstate_dsa() do these lines immediately after > calling free_memorycontextstate_dsa(), why not do that inside? Fixed. >> + /* Copy statistics to DSA memory */ >> + PublishMemoryContext(meminfo, context_id, cur, path, stat, 1, area, 100); >> + } >> + else >> + { >> + /* Examine the context stats */ >> + memset(&stat, 0, sizeof(stat)); >> + (*cur->methods->stats) (cur, NULL, NULL, &stat, true); > > But do we really do it twice in a row? The lines are exactly the same, so it > seems that should just be done before the if? Fixed. >> +signal_memorycontext_reporting(void) > > IMO somewhat confusing to release the lock in a function named > signal_memorycontext_reporting(). Why do we do that after > hash_destroy()/dsa_detach()? The function has been renamed for clarity. >> + /* context id starts with 1 */ >> + entry->context_id = ++(*stats_count); > > Given that we don't actually do anything here relating to starting with 1, I > find that comment confusing. Reworded, not sure if it's much better tbh. >> + memctx_info[curr_id].name = dsa_allocate0(area, namelen + 1); > > Given the number of references to memctx_info[curr_id] I'd put it in a local variable. I might be partial, but I sort of prefer this way since it makes the underlying data structure clear to the reader. > Why is this a dsa_allocate0 given that we're immediately overwriting it? It doesn't need to be zeroed as it's immediately overwritten. Fixed. >> + memctx_info[curr_id].ident = dsa_allocate0(area, idlen + 1); >> + identptr = (char *) dsa_get_address(area, memctx_info[curr_id].ident); >> + strlcpy(identptr, ident, idlen + 1); > > Hm. First I thought we'd leak memory if this second (and subsequent) > dsa_allocate failed. Then I thought we'd be ok, because the memory would be > memory because it'd be reachable from memCtxState[idx].memstats_dsa_pointer. > > But I think it wouldn't *quite* work, because memCtxState[idx].total_stats is > only set *after* we would have failed. Keeping a running total in .total_stats should make the leak window smaller. >> + memctx_info[curr_id].type = ContextTypeToString(context->type); > > I don't think this works across platforms. On windows / EXEC_BACKEND builds > the location of string constants can differ across backends. And: Why do we > need the string here? You can just call ContextTypeToString when reading? Correct, we can just store the type and call ContextTypeToString when generating the tuple. Fixed. >> +/* >> + * Free the memory context statistics stored by this process >> + * in DSA area. >> + */ >> +void >> +AtProcExit_memstats_dsa_free(int code, Datum arg) >> +{ > > FWIW, to me the fact that it does a dsa_free() is an implementation > detail. It's also not the only thing this does. Renamed. > And, I don't think AtProcExit* really is accurate, given that it runs *before* > shmem is cleaned up? > > I wonder if the best approach here wouldn't be to forgo the use of a > before_shmem_exit() callback, but instead use on_dsm_detach(). That would > require we'd not constantly detach from the dsm segment, but I don't > understand why we do that in the first place? The attach/detach has been removed. >> + /* If the dsm mapping could not be found, attach to the area */ >> + if (dsm_seg != NULL) >> + return; > > I don't understand what we do here with the dsm? Why do we not need cleanup > if we are already attached to the dsm segment? Fixed. >> +} MemoryContextState; > > IMO that's too generic a name for something in a header. > >> +} MemoryContextId; > > This too. Particularly because MemoryContextData->ident exist but is > something different. Renamed both to use MemoryContextReporting* namespace, which leaves MemoryContextReportingBackendState at an unwieldly long name. I'm running out of ideas on how to improve and it does make purpose quite explicit at least. >> + from pg_get_process_memory_contexts(launcher_pid, false, 20) >> + where path = '{1}' into r; >> + RAISE NOTICE '%', r; >> + select type, name, ident >> + from pg_get_process_memory_contexts(pg_backend_pid(), false, 20) >> + where path = '{1}' into r; >> + RAISE NOTICE '%', r; >> +END $$; > > I'd also test an aux process. I think the AV launcher isn't one, because it > actually does "table" access of shared relations. Fixed, switched from the AV launcher. -- Daniel Gustafsson -
Re: Enhancing Memory Context Statistics Reporting
Andres Freund <andres@anarazel.de> — 2025-04-08T00:03:36Z
Hi, On 2025-04-08 01:17:17 +0200, Daniel Gustafsson wrote: > > On 7 Apr 2025, at 17:43, Andres Freund <andres@anarazel.de> wrote: > > >> + /* > >> + * Hold the process lock to protect writes to process specific memory. Two > >> + * processes publishing statistics do not block each other. > >> + */ > > > > s/specific/process specific/ > > That's what it says though.. isn't it? I might be missing something obvious. Understandable confusion, not sure what my brain was doing anymore either... > >> +} MemoryContextState; > > > > IMO that's too generic a name for something in a header. > > > >> +} MemoryContextId; > > > > This too. Particularly because MemoryContextData->ident exist but is > > something different. > > Renamed both to use MemoryContextReporting* namespace, which leaves > MemoryContextReportingBackendState at an unwieldly long name. I'm running out > of ideas on how to improve and it does make purpose quite explicit at least. How about MemoryContextReportingBackendState -> MemoryStatsBackendState MemoryContextReportingId -> MemoryStatsContextId MemoryContextReportingSharedState -> MemoryStatsCtl MemoryContextReportingStatsEntry -> MemoryStatsEntry > >> + /* context id starts with 1 */ > >> + entry->context_id = ++(*stats_count); > > > > Given that we don't actually do anything here relating to starting with 1, I > > find that comment confusing. > > Reworded, not sure if it's much better tbh. I'd probably just remove the comment. > > Hm. First I thought we'd leak memory if this second (and subsequent) > > dsa_allocate failed. Then I thought we'd be ok, because the memory would be > > memory because it'd be reachable from memCtxState[idx].memstats_dsa_pointer. > > > > But I think it wouldn't *quite* work, because memCtxState[idx].total_stats is > > only set *after* we would have failed. > > Keeping a running total in .total_stats should make the leak window smaller. Why not just initialize .total_stats *before* calling any fallible code? Afaict it's zero-allocated, so the free function should have no problem dealing with the entries that haven't yet been populated/ Greetings, Andres Freund
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-04-08T05:40:34Z
Hi Daniel, Andres, > > > >> +} MemoryContextState; > > > > > > IMO that's too generic a name for something in a header. > > > > > >> +} MemoryContextId; > > > > > > This too. Particularly because MemoryContextData->ident exist but is > > > something different. > > > > Renamed both to use MemoryContextReporting* namespace, which leaves > > MemoryContextReportingBackendState at an unwieldly long name. I'm > running out > > of ideas on how to improve and it does make purpose quite explicit at > least. > > How about > > MemoryContextReportingBackendState -> MemoryStatsBackendState > MemoryContextReportingId -> MemoryStatsContextId > MemoryContextReportingSharedState -> MemoryStatsCtl > MemoryContextReportingStatsEntry -> MemoryStatsEntry > > > Fixed accordingly. > > >> + /* context id starts with 1 */ > > >> + entry->context_id = ++(*stats_count); > > > > > > Given that we don't actually do anything here relating to starting > with 1, I > > > find that comment confusing. > > > > Reworded, not sure if it's much better tbh. > > I'd probably just remove the comment. > > Reworded to mention that we pre-increment stats_count to make sure id starts with 1. > > > > Hm. First I thought we'd leak memory if this second (and subsequent) > > > dsa_allocate failed. Then I thought we'd be ok, because the memory > would be > > > memory because it'd be reachable from > memCtxState[idx].memstats_dsa_pointer. > > > > > > But I think it wouldn't *quite* work, because > memCtxState[idx].total_stats is > > > only set *after* we would have failed. > > > > Keeping a running total in .total_stats should make the leak window > smaller. > > Why not just initialize .total_stats *before* calling any fallible code? > Afaict it's zero-allocated, so the free function should have no problem > dealing with the entries that haven't yet been populated/ > > Fixed accordingly. PFA a v28 which passes all local and github CI tests. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-08T08:03:09Z
> On 8 Apr 2025, at 07:40, Rahila Syed <rahilasyed90@gmail.com> wrote: > >> > Renamed both to use MemoryContextReporting* namespace, which leaves >> > MemoryContextReportingBackendState at an unwieldly long name. I'm running out >> > of ideas on how to improve and it does make purpose quite explicit at least. >> >> How about >> >> MemoryContextReportingBackendState -> MemoryStatsBackendState >> MemoryContextReportingId -> MemoryStatsContextId >> MemoryContextReportingSharedState -> MemoryStatsCtl >> MemoryContextReportingStatsEntry -> MemoryStatsEntry > > Fixed accordingly. That's much better, thanks. There was a bug in the shmem init function which caused it to fail on Windows, the attached fixes that. -- Daniel Gustafsson 
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-08T09:46:59Z
> On 8 Apr 2025, at 10:03, Daniel Gustafsson <daniel@yesql.se> wrote: > There was a bug in the shmem init function which caused it to fail on Windows, > the attached fixes that. With this building green in CI over several re-builds, and another pass over the docs and code with pgindent etc done, I pushed this earlier today. A few BF animals have built green so far but I will continue to monitor it. -- Daniel Gustafsson
-
Re: Enhancing Memory Context Statistics Reporting
Fujii Masao <masao.fujii@oss.nttdata.com> — 2025-04-08T16:41:49Z
On 2025/04/08 18:46, Daniel Gustafsson wrote: >> On 8 Apr 2025, at 10:03, Daniel Gustafsson <daniel@yesql.se> wrote: > >> There was a bug in the shmem init function which caused it to fail on Windows, >> the attached fixes that. > > With this building green in CI over several re-builds, and another pass over > the docs and code with pgindent etc done, I pushed this earlier today. A few > BF animals have built green so far but I will continue to monitor it. Thanks for committing this feature! I noticed that the third argument of pg_get_process_memory_contexts() is named "retries" in pg_proc.dat, while the documentation refers to it as "timeout". Since "retries" is misleading, how about renaming it to "timeout" in pg_proc.dat? Patch attached. Also, as I mentioned earlier, I encountered an issue when calling pg_get_process_memory_contexts() on the PID of a backend that had just encountered an error but hadn't finished rolling back. It led to the following situation: Session 1 (PID=70011): =# begin; =# select 1/0; ERROR: division by zero Session 2: =# select * from pg_get_process_memory_contexts(70011, false, 10); Session 1 terminated with: ERROR: ResourceOwnerEnlarge called after release started FATAL: terminating connection because protocol synchronization was lost Shouldn't this be addressed? Regards, -- Fujii Masao Advanced Computing Technology Center Research and Development Headquarters NTT DATA CORPORATION -
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-08T16:44:41Z
> On 8 Apr 2025, at 18:41, Fujii Masao <masao.fujii@oss.nttdata.com> wrote: > On 2025/04/08 18:46, Daniel Gustafsson wrote: >>> On 8 Apr 2025, at 10:03, Daniel Gustafsson <daniel@yesql.se> wrote: >>> There was a bug in the shmem init function which caused it to fail on Windows, >>> the attached fixes that. >> With this building green in CI over several re-builds, and another pass over >> the docs and code with pgindent etc done, I pushed this earlier today. A few >> BF animals have built green so far but I will continue to monitor it. > > Thanks for committing this feature! > > I noticed that the third argument of pg_get_process_memory_contexts() is named > "retries" in pg_proc.dat, while the documentation refers to it as "timeout". > Since "retries" is misleading, how about renaming it to "timeout" in pg_proc.dat? > Patch attached. Ugh, that's my bad. It was changed from using retries to a timeout and I missed that. > Also, as I mentioned earlier, I encountered an issue when calling > pg_get_process_memory_contexts() on the PID of a backend that had just > encountered an error but hadn't finished rolling back. It led to > the following situation: > > Session 1 (PID=70011): > =# begin; > =# select 1/0; > ERROR: division by zero > > Session 2: > =# select * from pg_get_process_memory_contexts(70011, false, 10); > > Session 1 terminated with: > ERROR: ResourceOwnerEnlarge called after release started > FATAL: terminating connection because protocol synchronization was lost > > Shouldn't this be addressed? Sorry, this must've been missed in this fairly lon thread, will have a look at it tonight. -- Daniel Gustafsson
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-08T21:27:41Z
> On 8 Apr 2025, at 18:41, Fujii Masao <masao.fujii@oss.nttdata.com> wrote: > I noticed that the third argument of pg_get_process_memory_contexts() is named > "retries" in pg_proc.dat, while the documentation refers to it as "timeout". I've committed this patch as it was obviously correct, thanks! > Also, as I mentioned earlier, I encountered an issue when calling > pg_get_process_memory_contexts() on the PID of a backend that had just > encountered an error but hadn't finished rolling back. It led to > the following situation: I reconfirmed that the bugfix that Rahila shared in [0] fixes this issue (and will fix others like it, as it's not related to this patch in particular but is a bug in DSM attaching). My plan is to take that for a more thorough review and test tomorrow and see how far it can be safely backpatched. Thanks for bringing this up, sorry about it getting a bit lost among all the emails. -- Daniel Gustafsson [0] CAH2L28shr0j3JE5V3CXDFmDH-agTSnh2V8pR23X0UhRMbDQD9Q@mail.gmail.com
-
Re: Enhancing Memory Context Statistics Reporting
Fujii Masao <masao.fujii@oss.nttdata.com> — 2025-04-08T23:28:09Z
On 2025/04/09 6:27, Daniel Gustafsson wrote: >> On 8 Apr 2025, at 18:41, Fujii Masao <masao.fujii@oss.nttdata.com> wrote: > >> I noticed that the third argument of pg_get_process_memory_contexts() is named >> "retries" in pg_proc.dat, while the documentation refers to it as "timeout". > > I've committed this patch as it was obviously correct, thanks! Thanks a lot! Since pg_proc.dat was modified, do we need to bump the catalog version? >> Also, as I mentioned earlier, I encountered an issue when calling >> pg_get_process_memory_contexts() on the PID of a backend that had just >> encountered an error but hadn't finished rolling back. It led to >> the following situation: > > I reconfirmed that the bugfix that Rahila shared in [0] fixes this issue (and > will fix others like it, as it's not related to this patch in particular but is > a bug in DSM attaching). My plan is to take that for a more thorough review > and test tomorrow and see how far it can be safely backpatched. Thanks for > bringing this up, sorry about it getting a bit lost among all the emails. Appreciate your work on this! Regards, -- Fujii Masao Advanced Computing Technology Center Research and Development Headquarters NTT DATA CORPORATION
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-04-29T13:13:07Z
Hi, Please find attached a patch with some comments and documentation changes. Additionaly, added a missing '\0' termination to "Remaining Totals" string. I think this became necessary after we replaced dsa_allocate0() with dsa_allocate() is the latest version. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Peter Eisentraut <peter@eisentraut.org> — 2025-04-30T10:14:26Z
On 29.04.25 15:13, Rahila Syed wrote: > Please find attached a patch with some comments and documentation changes. > Additionaly, added a missing '\0' termination to "Remaining Totals" string. > I think this became necessary after we replaced dsa_allocate0() > with dsa_allocate() is the latest version. > strncpy(nameptr, "Remaining Totals", namelen); > + nameptr[namelen] = '\0'; Looks like a case for strlcpy()?
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-04-30T10:43:24Z
> On 30 Apr 2025, at 12:14, Peter Eisentraut <peter@eisentraut.org> wrote: > > On 29.04.25 15:13, Rahila Syed wrote: >> Please find attached a patch with some comments and documentation changes. >> Additionaly, added a missing '\0' termination to "Remaining Totals" string. >> I think this became necessary after we replaced dsa_allocate0() >> with dsa_allocate() is the latest version. > > > strncpy(nameptr, "Remaining Totals", namelen); > > + nameptr[namelen] = '\0'; > > Looks like a case for strlcpy()? True. I did go ahead with the strncpy and nul terminator assignment, mostly out of muscle memory, but I agree that this would be a good place for a strlcpy() instead. -- Daniel Gustafsson
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-07-11T15:31:12Z
Hi, Please find attached the latest memory context statistics monitoring patch. It has been redesigned to address several issues highlighted in the thread [1] and [2]. Here are some key highlights of the new design: - All DSA processing has been moved out of the CFI handler function. Now, all the dynamic shared memory needed to store the statistics is created and deleted in the client function. This change addresses concerns that DSA APIs are too high level to be safely called from interrupt handlers. There was also a concern that DSA API calls might not provide re-entrancy, which could cause issues if CFI is invoked from a DSA function in the future. - The static shared memory array has been replaced with a DSHASH table which now holds metadata such as pointers to actual statistics for each process. - dsm_registry.c APIs are used for creating and attaching to DSA and DSHASH table, which helps prevent code duplication. -To address the memory leak concern, we create an exclusive memory context under the NULL context, which does not fall under the TopMemoryContext tree, to handle all the memory allocations in ProcessGetMemoryContextInterrupt. This ensures the memory context created by the function does not affect its outcome. The memory context is reset at the end of the function, which helps prevent any memory leaks. - Changes made to the mcxt.c file have been relocated to mcxtfuncs.c, which now contains all the existing memory statistics-related functions along with the code for the proposed function. The overall flow of a request is as follows: 1. A client backend running the pg_get_process_memory_contexts function creates a DSA and allocates memory to store statistics, tracked by DSA pointer. This pointer is stored in a DSHASH entry for each client querying the statistics of any process. The client shares its DSHASH table key with the server process using a static shared array of keys indexed by the server's procNumber. It notifies the server process to publish statistics by using SendProcSignal. 2. When a PostgreSQL server process handles the request for memory statistics, the CFI function accesses the client hash key stored in its procNumber slot of the shared keys array. The server process then retrieves the DSHASH entry to obtain the DSA pointer allocated by the client, for storing the statistics. After storing the statistics, it notifies the client through its condition variable. 3. Although the DSA is created just once, the memory inside the DSA is allocated and released by the client process as soon as it finishes reading the statistics. If it fails to do so, it is deleted by the before_shmem_exit callback when the client exits. The client's entry in DSHASH table is also deleted when the client exits. 4. The DSA and DSHASH table are not created until pg_get_process_memory_context function is called. Once created, any client backend querying statistics and any PostgreSQL process publishing statistics will attach to the same area and table. Please let me know your thoughts. Thank you, Rahila Syed [1]. PostgreSQL: Re: pgsql: Add function to get memory context stats for processes <https://www.postgresql.org/message-id/CA%2BTgmoaey-kOP1k5FaUnQFd1fR0majVebWcL8ogfLbG_nt-Ytg%40mail.gmail.com> [2]. PostgreSQL: Re: Prevent an error on attaching/creating a DSM/DSA from an interrupt handler. <https://www.postgresql.org/message-id/flat/8B873D49-E0E5-4F9F-B8D6-CA4836B825CD%40yesql.se#7026d2fe4ab0de6dd5decd32eb9c585a> On Wed, Apr 30, 2025 at 4:13 PM Daniel Gustafsson <daniel@yesql.se> wrote: > > On 30 Apr 2025, at 12:14, Peter Eisentraut <peter@eisentraut.org> wrote: > > > > On 29.04.25 15:13, Rahila Syed wrote: > >> Please find attached a patch with some comments and documentation > changes. > >> Additionaly, added a missing '\0' termination to "Remaining Totals" > string. > >> I think this became necessary after we replaced dsa_allocate0() > >> with dsa_allocate() is the latest version. > > > > > strncpy(nameptr, "Remaining Totals", namelen); > > > + nameptr[namelen] = '\0'; > > > > Looks like a case for strlcpy()? > > True. I did go ahead with the strncpy and nul terminator assignment, > mostly > out of muscle memory, but I agree that this would be a good place for a > strlcpy() instead. > > -- > Daniel Gustafsson > >
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-07-29T13:40:26Z
Hi, Please find attached an updated patch. It contains the following changes. 1. It needed a rebase as highlighted by cfbot <https://cfbot.cputube.org/patch_5938.log>. The method for adding an LWLock was updated in commit-2047ad068139f0b8c6da73d0b845ca9ba30fb33d, so the patch has been adjusted to reflect this change. 2. Updated some comments to align with the latest patch design. 3. Eliminated an unnecessary assertion Thank you, Rahila Syed On Fri, Jul 11, 2025 at 9:01 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > Hi, > > Please find attached the latest memory context statistics monitoring > patch. > It has been redesigned to address several issues highlighted in the thread > [1] > and [2]. > > Here are some key highlights of the new design: > > - All DSA processing has been moved out of the CFI handler function. Now, > all the dynamic shared memory > needed to store the statistics is created and deleted in the client > function. This change addresses concerns > that DSA APIs are too high level to be safely called from interrupt > handlers. There was also a concern that > DSA API calls might not provide re-entrancy, which could cause issues if > CFI is invoked from a DSA function > in the future. > > - The static shared memory array has been replaced with a DSHASH table > which now holds metadata such as > pointers to actual statistics for each process. > > - dsm_registry.c APIs are used for creating and attaching to DSA and > DSHASH table, which helps prevent code > duplication. > > -To address the memory leak concern, we create an exclusive memory context > under the NULL context, which > does not fall under the TopMemoryContext tree, to handle all the memory > allocations in ProcessGetMemoryContextInterrupt. > This ensures the memory context created by the function does not affect > its outcome. > The memory context is reset at the end of the function, which helps > prevent any memory leaks. > > - Changes made to the mcxt.c file have been relocated to mcxtfuncs.c, > which now contains all the existing > memory statistics-related functions along with the code for the proposed > function. > > The overall flow of a request is as follows: > > 1. A client backend running the pg_get_process_memory_contexts function > creates a DSA and allocates memory > to store statistics, tracked by DSA pointer. This pointer is stored in a > DSHASH entry for each client querying the > statistics of any process. > The client shares its DSHASH table key with the server process using a > static shared array of keys indexed > by the server's procNumber. It notifies the server process to publish > statistics by using SendProcSignal. > > 2. When a PostgreSQL server process handles the request for memory > statistics, the CFI function accesses the > client hash key stored in its procNumber slot of the shared keys array. > The server process then retrieves the > DSHASH entry to obtain the DSA pointer allocated by the client, for > storing the statistics. > After storing the statistics, it notifies the client through its > condition variable. > > 3. Although the DSA is created just once, the memory inside the DSA is > allocated and released by the client > process as soon as it finishes reading the statistics. > If it fails to do so, it is deleted by the before_shmem_exit callback when > the client exits. The client's entry in DSHASH > table is also deleted when the client exits. > > 4. The DSA and DSHASH table are not created > until pg_get_process_memory_context function is called. > Once created, any client backend querying statistics and any PostgreSQL > process publishing statistics will > attach to the same area and table. > > Please let me know your thoughts. > > Thank you, > Rahila Syed > > [1]. PostgreSQL: Re: pgsql: Add function to get memory context stats for > processes > <https://www.postgresql.org/message-id/CA%2BTgmoaey-kOP1k5FaUnQFd1fR0majVebWcL8ogfLbG_nt-Ytg%40mail.gmail.com> > [2]. PostgreSQL: Re: Prevent an error on attaching/creating a DSM/DSA > from an interrupt handler. > <https://www.postgresql.org/message-id/flat/8B873D49-E0E5-4F9F-B8D6-CA4836B825CD%40yesql.se#7026d2fe4ab0de6dd5decd32eb9c585a> > > On Wed, Apr 30, 2025 at 4:13 PM Daniel Gustafsson <daniel@yesql.se> wrote: > >> > On 30 Apr 2025, at 12:14, Peter Eisentraut <peter@eisentraut.org> >> wrote: >> > >> > On 29.04.25 15:13, Rahila Syed wrote: >> >> Please find attached a patch with some comments and documentation >> changes. >> >> Additionaly, added a missing '\0' termination to "Remaining Totals" >> string. >> >> I think this became necessary after we replaced dsa_allocate0() >> >> with dsa_allocate() is the latest version. >> > >> > > strncpy(nameptr, "Remaining Totals", namelen); >> > > + nameptr[namelen] = '\0'; >> > >> > Looks like a case for strlcpy()? >> >> True. I did go ahead with the strncpy and nul terminator assignment, >> mostly >> out of muscle memory, but I agree that this would be a good place for a >> strlcpy() instead. >> >> -- >> Daniel Gustafsson >> >>
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-08-08T09:26:52Z
Hi, CFbot indicated that the patch requires a rebase, so I've attached an updated version. The documentation for this feature is now included in the new func-admin.sgml file, due to recent changes in the documentation of sql functions. The following are results from a performance test: pgbench is initialized as follows : pgbench -i -s 100 postgres Test1 - pgbench -c 16 -j 16 postgres -T 100 TPS: 745.02 (average of 3 runs) Test2- pgbench -c 16 -j 16 postgres -T 100 while memory usage of any postgres process is monitored concurrently every 0.1 seconds, using the following method: SELECT * FROM pg_get_process_memory_contexts( (SELECT pid FROM pg_stat_activity ORDER BY random() LIMIT 1) , false, 5); TPS: 750.66 (average of 3 runs) I have not observed any performance decline resulting from the concurrent execution of the memory monitoring function. Thank you, Rahila Syed On Tue, Jul 29, 2025 at 7:10 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > Hi, > > Please find attached an updated patch. It contains the following changes. > > 1. It needed a rebase as highlighted by cfbot > <https://cfbot.cputube.org/patch_5938.log>. The method for adding an > LWLock was updated in commit-2047ad068139f0b8c6da73d0b845ca9ba30fb33d, so > the patch has been adjusted to reflect this change. > 2. Updated some comments to align with the latest patch design. > 3. Eliminated an unnecessary assertion > > Thank you, > Rahila Syed > > On Fri, Jul 11, 2025 at 9:01 PM Rahila Syed <rahilasyed90@gmail.com> > wrote: > >> Hi, >> >> Please find attached the latest memory context statistics monitoring >> patch. >> It has been redesigned to address several issues highlighted in the >> thread [1] >> and [2]. >> >> Here are some key highlights of the new design: >> >> - All DSA processing has been moved out of the CFI handler function. Now, >> all the dynamic shared memory >> needed to store the statistics is created and deleted in the client >> function. This change addresses concerns >> that DSA APIs are too high level to be safely called from interrupt >> handlers. There was also a concern that >> DSA API calls might not provide re-entrancy, which could cause issues if >> CFI is invoked from a DSA function >> in the future. >> >> - The static shared memory array has been replaced with a DSHASH table >> which now holds metadata such as >> pointers to actual statistics for each process. >> >> - dsm_registry.c APIs are used for creating and attaching to DSA and >> DSHASH table, which helps prevent code >> duplication. >> >> -To address the memory leak concern, we create an exclusive memory >> context under the NULL context, which >> does not fall under the TopMemoryContext tree, to handle all the memory >> allocations in ProcessGetMemoryContextInterrupt. >> This ensures the memory context created by the function does not affect >> its outcome. >> The memory context is reset at the end of the function, which helps >> prevent any memory leaks. >> >> - Changes made to the mcxt.c file have been relocated to mcxtfuncs.c, >> which now contains all the existing >> memory statistics-related functions along with the code for the proposed >> function. >> >> The overall flow of a request is as follows: >> >> 1. A client backend running the pg_get_process_memory_contexts function >> creates a DSA and allocates memory >> to store statistics, tracked by DSA pointer. This pointer is stored in a >> DSHASH entry for each client querying the >> statistics of any process. >> The client shares its DSHASH table key with the server process using a >> static shared array of keys indexed >> by the server's procNumber. It notifies the server process to publish >> statistics by using SendProcSignal. >> >> 2. When a PostgreSQL server process handles the request for memory >> statistics, the CFI function accesses the >> client hash key stored in its procNumber slot of the shared keys array. >> The server process then retrieves the >> DSHASH entry to obtain the DSA pointer allocated by the client, for >> storing the statistics. >> After storing the statistics, it notifies the client through its >> condition variable. >> >> 3. Although the DSA is created just once, the memory inside the DSA is >> allocated and released by the client >> process as soon as it finishes reading the statistics. >> If it fails to do so, it is deleted by the before_shmem_exit callback >> when the client exits. The client's entry in DSHASH >> table is also deleted when the client exits. >> >> 4. The DSA and DSHASH table are not created >> until pg_get_process_memory_context function is called. >> Once created, any client backend querying statistics and any PostgreSQL >> process publishing statistics will >> attach to the same area and table. >> >> Please let me know your thoughts. >> >> Thank you, >> Rahila Syed >> >> [1]. PostgreSQL: Re: pgsql: Add function to get memory context stats for >> processes >> <https://www.postgresql.org/message-id/CA%2BTgmoaey-kOP1k5FaUnQFd1fR0majVebWcL8ogfLbG_nt-Ytg%40mail.gmail.com> >> [2]. PostgreSQL: Re: Prevent an error on attaching/creating a DSM/DSA >> from an interrupt handler. >> <https://www.postgresql.org/message-id/flat/8B873D49-E0E5-4F9F-B8D6-CA4836B825CD%40yesql.se#7026d2fe4ab0de6dd5decd32eb9c585a> >> >> On Wed, Apr 30, 2025 at 4:13 PM Daniel Gustafsson <daniel@yesql.se> >> wrote: >> >>> > On 30 Apr 2025, at 12:14, Peter Eisentraut <peter@eisentraut.org> >>> wrote: >>> > >>> > On 29.04.25 15:13, Rahila Syed wrote: >>> >> Please find attached a patch with some comments and documentation >>> changes. >>> >> Additionaly, added a missing '\0' termination to "Remaining Totals" >>> string. >>> >> I think this became necessary after we replaced dsa_allocate0() >>> >> with dsa_allocate() is the latest version. >>> > >>> > > strncpy(nameptr, "Remaining Totals", namelen); >>> > > + nameptr[namelen] = '\0'; >>> > >>> > Looks like a case for strlcpy()? >>> >>> True. I did go ahead with the strncpy and nul terminator assignment, >>> mostly >>> out of muscle memory, but I agree that this would be a good place for a >>> strlcpy() instead. >>> >>> -- >>> Daniel Gustafsson >>> >>> -
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2025-08-12T00:34:00Z
On 2025-08-08 18:26, Rahila Syed wrote: Hi, thanks for working on this again. > Hi, > > CFbot indicated that the patch requires a rebase, so I've attached an > updated version. Here are some comments and questions for v32 patch: > --- a/doc/src/sgml/func/func-admin.sgml > +++ b/doc/src/sgml/func/func-admin.sgml > @@ -251,6 +251,137 @@ > <literal>false</literal> is returned. > </para></entry> > </row> > + > + <row> > + <entry role="func_table_entry"><para role="func_signature"> > + <indexterm> > + <primary>pg_get_process_memory_contexts</primary> This function is added at the end of Table "9.96. Server Signaling Functions", but since pg_get_process_memory_contexts outputs essentially the same information as pg_log_backend_memory_contexts, it might be better to place them next to each other in the table. > + <parameter>stats_timestamp</parameter> <type>timestamptz</type> ) > +typedef struct MemoryStatsDSHashEntry > +{ > + char key[64]; > + ConditionVariable memcxt_cv; > + int proc_id; > + int total_stats; > + bool summary; > + dsa_pointer memstats_dsa_pointer; > + TimestampTz stats_timestamp; > +} MemoryStatsDSHashEntry; stats_timestamp appears only in the two places below in the patch, but it does not seem to be actually output. Is this column unnecessary? =# select * from pg_get_process_memory_contexts(pg_backend_pid(), true, 10); -[ RECORD 1 ]----+----------------------------- name | TopMemoryContext ident | [NULL] type | AllocSet path | {1} level | 1 total_bytes | 222400 total_nblocks | 8 free_bytes | 4776 free_chunks | 8 used_bytes | 217624 num_agg_contexts | 1 Specifying 0 for timeout causes a crash: =# select * from pg_get_process_memory_contexts(74526, true, 0); (0 rows) =# select 1; WARNING: terminating connection because of crash of another server process DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory. HINT: In a moment you should be able to reconnect to the database and repeat your command. server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. Should 0 be handled safely and treated as “no timeout”, or rejected as an error? Similarly, specifying a negative value for timeout still works: =# select * from pg_get_process_memory_contexts(30590, true, -10); It might be better to reject negative values similar to pg_terminate_backend(). > context_id_lookup = > hash_create("pg_get_remote_backend_memory_contexts", > + /* Retreive the client key fo publishing statistics */ fo -> for? > + * If the publishing backend does not respond before the condition > variable > + * times out, which is set to MEMSTATS_WAIT_TIMEOUT, retry given that > there is > + * time left within the timeout specified by the user, before giving > up and > + * returning previously published statistics, if any. If no previous > statistics > + * exist, return NULL. > + */ > +#define MEMSTATS_WAIT_TIMEOUT 100 MEMSTATS_WAIT_TIMEOUT is defined, but it doesn’t seem to be used. -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA Japan Corporation to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-08-13T22:35:59Z
Hi Torikoshia, Thank you for reviewing the patch. > > This function is added at the end of Table "9.96. Server Signaling > Functions", but since pg_get_process_memory_contexts outputs essentially > the same information as pg_log_backend_memory_contexts, it might be > better to place them next to each other in the table. > > The idea was to place the new addition at the end of the table instead of in the middle. I’m fine with putting them together, though. I’ll do that in the next version unless there’s a reason not to. > > > + <parameter>stats_timestamp</parameter> <type>timestamptz</type> ) > > > +typedef struct MemoryStatsDSHashEntry > > +{ > > + char key[64]; > > + ConditionVariable memcxt_cv; > > + int proc_id; > > + int total_stats; > > + bool summary; > > + dsa_pointer memstats_dsa_pointer; > > + TimestampTz stats_timestamp; > > +} MemoryStatsDSHashEntry; > > stats_timestamp appears only in the two places below in the patch, but > it does not seem to be actually output. > Is this column unnecessary? > > Thank you for pointing this out. This is removed in the attached patch, as it was a remnant from the previous design. As old statistics are discarded in the new design, a timestamp field is not needed anymore. > > Specifying 0 for timeout causes a crash: > Should 0 be handled safely and treated as “no timeout”, or rejected as > an error? > Good catch. The crash has been resolved in the attached patch. It was caused by a missing ConditionVariableCancelSleep() call when exiting without statistics due to a timeout value of 0. A 0 timeout means that statistics should only be retrieved if they are immediately available, without waiting. We could exit with a warning/error saying "too low timeout", but I think it's worthwhile to try fetching the statistics if possible. Similarly, specifying a negative value for timeout still works: > > =# select * from pg_get_process_memory_contexts(30590, true, -10); > > It might be better to reject negative values similar to > pg_terminate_backend(). > > > Fixed as suggested by you in the attached patch. Currently, negative values are interpreted as an indefinite wait for statistics. This could cause the client to hang if the server process exits without providing statistics. To avoid this, it would be better to exit after displaying a warning when the user specifies negative timeouts. > > > + /* Retreive the client key fo publishing statistics */ > > fo -> for? > Fixed. > + */ > > +#define MEMSTATS_WAIT_TIMEOUT 100 > > MEMSTATS_WAIT_TIMEOUT is defined, but it doesn’t seem to be used. > > This is removed now as it was a leftover from the previous design. The attached patch also fixes an assertion failure I observed when a client times out before the last requested process can publish its statistics. A client frees the memory reserved for storing the statistics when it exits the function after timeout. Since a server process was notified, it might attempt to read the same client entry and access the dsa memory reserved for statistics resulting in the assertion failure. I resolved this by including a check for this scenario and then exiting the handler function accordingly. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2025-08-18T13:32:09Z
On 2025-08-14 07:35, Rahila Syed wrote: > Hi Torikoshia, > > Thank you for reviewing the patch. > >> This function is added at the end of Table "9.96. Server Signaling >> Functions", but since pg_get_process_memory_contexts outputs >> essentially >> the same information as pg_log_backend_memory_contexts, it might be >> better to place them next to each other in the table. > > The idea was to place the new addition at the end of the table instead > of in the middle. > I’m fine with putting them together, though. I’ll do that in the > next version unless there’s a > reason not to. > >>> + <parameter>stats_timestamp</parameter> >> <type>timestamptz</type> ) >> >>> +typedef struct MemoryStatsDSHashEntry >>> +{ >>> + char key[64]; >>> + ConditionVariable memcxt_cv; >>> + int proc_id; >>> + int total_stats; >>> + bool summary; >>> + dsa_pointer memstats_dsa_pointer; >>> + TimestampTz stats_timestamp; >>> +} MemoryStatsDSHashEntry; >> >> stats_timestamp appears only in the two places below in the patch, >> but >> it does not seem to be actually output. >> Is this column unnecessary? > > Thank you for pointing this out. This is removed in the attached > patch, as it was a > remnant from the previous design. As old statistics are discarded in > the new design, > a timestamp field is not needed anymore. > >> Specifying 0 for timeout causes a crash: >> Should 0 be handled safely and treated as “no timeout”, or >> rejected as >> an error? > > Good catch. > The crash has been resolved in the attached patch. It was caused by a > missing > ConditionVariableCancelSleep() call when exiting without statistics > due to a timeout value of 0. > A 0 timeout means that statistics should only be retrieved if they are > immediately available, > without waiting. We could exit with a warning/error saying "too low > timeout", but I think it's worthwhile > to try fetching the statistics if possible. > >> Similarly, specifying a negative value for timeout still works: >> >> =# select * from pg_get_process_memory_contexts(30590, true, >> -10); >> >> It might be better to reject negative values similar to >> pg_terminate_backend(). > > Fixed as suggested by you in the attached patch. > Currently, negative values are interpreted as an indefinite wait for > statistics. > This could cause the client to hang if the server process exits > without providing statistics. > To avoid this, it would be better to exit after displaying a warning > when the user specifies > negative timeouts. > >>> + /* Retreive the client key fo publishing statistics */ >> >> fo -> for? > > Fixed. > >>> + */ >>> +#define MEMSTATS_WAIT_TIMEOUT 100 >> >> MEMSTATS_WAIT_TIMEOUT is defined, but it doesn’t seem to be used. > > This is removed now as it was a leftover from the previous design. > > The attached patch also fixes an assertion failure I observed when a > client times out > before the last requested process can publish its statistics. A client > frees the memory > reserved for storing the statistics when it exits the function after > timeout. Since a > server process was notified, it might attempt to read the same client > entry and access the dsa > memory reserved for statistics resulting in the assertion > failure. I resolved this by including a check for this scenario and > then exiting the handler > function accordingly. Thanks for updating the patch! However, when I ran pg_get_process_memory_contexts() with summary = true, it took a while and returned nothing: =# select pg_get_process_memory_contexts(pg_backend_pid(), true, 1) from pg_stat_activity ; pg_get_process_memory_contexts -------------------------------- (0 rows) Time: 6026.291 ms (00:06.026) Since v32 patch quickly returned the memory contexts as expected with the same parameter specified, there seems to be some degradation. Could you check it? -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA Japan Corporation to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-08-19T21:42:52Z
Hi, =# select pg_get_process_memory_contexts(pg_backend_pid(), true, 1) > from pg_stat_activity ; > > pg_get_process_memory_contexts > -------------------------------- > (0 rows) > > Time: 6026.291 ms (00:06.026) > > Since v32 patch quickly returned the memory contexts as expected with > the same parameter specified, there seems to be some degradation. Could > you check it? > Thank you for reporting this failure. This issue was a regression caused by the absence of a ConditionVariableSignal() call in the summary = true code path, which happened due to recent code refactoring. PFA the fix. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2025-10-07T00:51:15Z
On 2025-08-20 06:42, Rahila Syed wrote: > PFA the fix. Thanks for updating the patch! Specifying a very small timeout value (such as 0 or 0.0001) and repeatedly executing the function seems to cause unexpected behavior. In some cases, it even leads to a crash. For example: (session1)=# select pg_backend_pid(); pg_backend_pid ---------------- 50917 (session2)=# select pg_get_process_memory_contexts(50917, true, 0.0001); pg_get_process_memory_contexts -------------------------------- (0 rows) (session2)=# \watch 0.01 pg_get_process_memory_contexts -------------------------------- (,,???,,0,0,0,0,0,0,0) ... (21 rows) (session2)=# \watch 0.01 pg_get_process_memory_contexts -------------------------------- (0 rows) ... server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. The connection to the server was lost. Attempting reset: Failed. This issue occurs on my M1 Mac, but I couldn’t reproduce it on Ubuntu, so it might be environment-dependent. Looking at the logs, Assert() is failing: 2025-10-07 08:48:26.766 JST [local] psql [23626] WARNING: 01000: server process 23646 is processing previous request 2025-10-07 08:48:26.766 JST [local] psql [23626] LOCATION: pg_get_process_memory_contexts, mcxtfuncs.c:476 TRAP: failed Assert("victim->magic == FREE_PAGE_SPAN_LEADER_MAGIC"), File: "freepage.c", Line: 1379, PID: 23626 0 postgres 0x000000010357fdf4 ExceptionalCondition + 216 1 postgres 0x00000001035cbe18 FreePageManagerGetInternal + 684 2 postgres 0x00000001035cbb18 FreePageManagerGet + 40 3 postgres 0x00000001035c84cc dsa_allocate_extended + 788 4 postgres 0x0000000103453af0 pg_get_process_memory_contexts + 992 5 postgres 0x0000000103007e94 ExecMakeFunctionResultSet + 616 6 postgres 0x00000001030506b8 ExecProjectSRF + 304 7 postgres 0x0000000103050434 ExecProjectSet + 268 8 postgres 0x0000000103003270 ExecProcNodeFirst + 92 9 postgres 0x0000000102ffa398 ExecProcNode + 60 10 postgres 0x0000000102ff5050 ExecutePlan + 244 11 postgres 0x0000000102ff4ee0 standard_ExecutorRun + 456 12 postgres 0x0000000102ff4d08 ExecutorRun + 84 13 postgres 0x0000000103341c84 PortalRunSelect + 296 14 postgres 0x0000000103341694 PortalRun + 656 15 postgres 0x000000010333c4bc exec_simple_query + 1388 16 postgres 0x000000010333b5d0 PostgresMain + 3252 17 postgres 0x0000000103332750 BackendInitialize + 0 18 postgres 0x0000000103209e48 postmaster_child_launch + 456 19 postgres 0x00000001032118c8 BackendStartup + 304 20 postgres 0x000000010320f72c ServerLoop + 372 21 postgres 0x000000010320e1e4 PostmasterMain + 6448 22 postgres 0x0000000103094b0c main + 924 23 dyld 0x0000000199dc2b98 start + 6076 Could you please check if you can reproduce this crash on your environment? And a few minor comments on the patch itself: > + <parameter>stats_timestamp</parameter> > <type>timestamptz</type> ) As discussed earlier, I believe we decided to remove stats_timestamp, but it seems it’s still mentioned here. > + * Update timestamp and signal all the waiting client backends after > copying > + * all the statistics. > + */ > +static void > +end_memorycontext_reporting(MemoryStatsDSHashEntry *entry, > MemoryContext oldcontext, HTAB *context_id_lookup) Should “Update timestamp” in this comment also be removed for consistency? The column order differs slightly from pg_backend_memory_contexts. If there’s no strong reason for the difference, perhaps aligning the order might improve consistency: =# select * from pg_get_process_memory_contexts(pg_backend_pid(), true, 1) ; name | TopMemoryContext ident | [NULL] type | AllocSet path | {1} level | 1 total_bytes | 222400 =# select * from pg_backend_memory_contexts; name | TopMemoryContext ident | [NULL] type | AllocSet level | 1 path | {1} total_bytes | 99232 ... Regards, -- Atsushi Torikoshi Seconded from NTT DATA Japan Corporation to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-10-09T08:43:25Z
Hi Torikoshia, Thank you for testing and reviewing the patch. > > This issue occurs on my M1 Mac, but I couldn’t reproduce it on Ubuntu, > so it might be environment-dependent. > Looking at the logs, Assert() is failing: > > 2025-10-07 08:48:26.766 JST [local] psql [23626] WARNING: 01000: > server process 23646 is processing previous request > 2025-10-07 08:48:26.766 JST [local] psql [23626] LOCATION: > pg_get_process_memory_contexts, mcxtfuncs.c:476 > TRAP: failed Assert("victim->magic == FREE_PAGE_SPAN_LEADER_MAGIC"), > File: "freepage.c", Line: 1379, PID: 23626 > 0 postgres 0x000000010357fdf4 > ExceptionalCondition + 216 > 1 postgres 0x00000001035cbe18 > FreePageManagerGetInternal + 684 > 2 postgres 0x00000001035cbb18 > FreePageManagerGet + 40 > 3 postgres 0x00000001035c84cc > dsa_allocate_extended + 788 > 4 postgres 0x0000000103453af0 > pg_get_process_memory_contexts + 992 > 5 postgres 0x0000000103007e94 > ExecMakeFunctionResultSet + 616 > 6 postgres 0x00000001030506b8 > ExecProjectSRF + 304 > 7 postgres 0x0000000103050434 > ExecProjectSet + 268 > 8 postgres 0x0000000103003270 > ExecProcNodeFirst + 92 > 9 postgres 0x0000000102ffa398 > ExecProcNode + 60 > 10 postgres 0x0000000102ff5050 ExecutePlan > + 244 > 11 postgres 0x0000000102ff4ee0 > standard_ExecutorRun + 456 > 12 postgres 0x0000000102ff4d08 ExecutorRun > + 84 > 13 postgres 0x0000000103341c84 > PortalRunSelect + 296 > 14 postgres 0x0000000103341694 PortalRun + > 656 > 15 postgres 0x000000010333c4bc > exec_simple_query + 1388 > 16 postgres 0x000000010333b5d0 > PostgresMain + 3252 > 17 postgres 0x0000000103332750 > BackendInitialize + 0 > 18 postgres 0x0000000103209e48 > postmaster_child_launch + 456 > 19 postgres 0x00000001032118c8 > BackendStartup + 304 > 20 postgres 0x000000010320f72c ServerLoop > + 372 > 21 postgres 0x000000010320e1e4 > PostmasterMain + 6448 > 22 postgres 0x0000000103094b0c main + 924 > 23 dyld 0x0000000199dc2b98 start + > 6076 > > > Could you please check if you can reproduce this crash on your > environment? > > I haven't been able to reproduce this issue on Ubuntu. A colleague also tested it on their Mac and didn't encounter the problem. I do have a fix in this area that I believe should address an edge case where data might be written to freed DSA memory. Kindly test using the v35 patch and let me know if you still see the issue. > > As discussed earlier, I believe we decided to remove stats_timestamp, > but it seems it’s still mentioned here. > > > Fixed. > > + * Update timestamp and signal all the waiting client backends after > > copying > > + * all the statistics. > > + */ > > +static void > > +end_memorycontext_reporting(MemoryStatsDSHashEntry *entry, > > MemoryContext oldcontext, HTAB *context_id_lookup) > > Should “Update timestamp” in this comment also be removed for > consistency? > > Fixed. > > The column order differs slightly from pg_backend_memory_contexts. > If there’s no strong reason for the difference, perhaps aligning the > order might improve consistency: > > Makes sense. I will fix this in the next iteration of the patch. I am also attaching a test which implements crash testing using injection points. I plan to improve the tests further to increase the test coverage of the feature. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2025-10-14T09:00:45Z
On 2025-10-09 17:43, Rahila Syed wrote: > Hi Torikoshia, > > Thank you for testing and reviewing the patch. > >> This issue occurs on my M1 Mac, but I couldn’t reproduce it on >> Ubuntu, >> so it might be environment-dependent. > >> Looking at the logs, Assert() is failing: >> >> 2025-10-07 08:48:26.766 JST [local] psql [23626] WARNING: 01000: >> >> server process 23646 is processing previous request >> 2025-10-07 08:48:26.766 JST [local] psql [23626] LOCATION: >> pg_get_process_memory_contexts, mcxtfuncs.c:476 >> TRAP: failed Assert("victim->magic == >> FREE_PAGE_SPAN_LEADER_MAGIC"), >> File: "freepage.c", Line: 1379, PID: 23626 >> 0 postgres 0x000000010357fdf4 >> ExceptionalCondition + 216 >> 1 postgres 0x00000001035cbe18 >> FreePageManagerGetInternal + 684 >> 2 postgres 0x00000001035cbb18 >> FreePageManagerGet + 40 >> 3 postgres 0x00000001035c84cc >> dsa_allocate_extended + 788 >> 4 postgres 0x0000000103453af0 >> pg_get_process_memory_contexts + 992 >> 5 postgres 0x0000000103007e94 >> ExecMakeFunctionResultSet + 616 >> 6 postgres 0x00000001030506b8 >> ExecProjectSRF + 304 >> 7 postgres 0x0000000103050434 >> ExecProjectSet + 268 >> 8 postgres 0x0000000103003270 >> ExecProcNodeFirst + 92 >> 9 postgres 0x0000000102ffa398 >> ExecProcNode + 60 >> 10 postgres 0x0000000102ff5050 >> ExecutePlan >> + 244 >> 11 postgres 0x0000000102ff4ee0 >> standard_ExecutorRun + 456 >> 12 postgres 0x0000000102ff4d08 >> ExecutorRun >> + 84 >> 13 postgres 0x0000000103341c84 >> PortalRunSelect + 296 >> 14 postgres 0x0000000103341694 >> PortalRun + >> 656 >> 15 postgres 0x000000010333c4bc >> exec_simple_query + 1388 >> 16 postgres 0x000000010333b5d0 >> PostgresMain + 3252 >> 17 postgres 0x0000000103332750 >> BackendInitialize + 0 >> 18 postgres 0x0000000103209e48 >> postmaster_child_launch + 456 >> 19 postgres 0x00000001032118c8 >> BackendStartup + 304 >> 20 postgres 0x000000010320f72c >> ServerLoop >> + 372 >> 21 postgres 0x000000010320e1e4 >> PostmasterMain + 6448 >> 22 postgres 0x0000000103094b0c main + >> 924 >> 23 dyld 0x0000000199dc2b98 start >> + >> 6076 >> >> Could you please check if you can reproduce this crash on your >> environment? > > I haven't been able to reproduce this issue on Ubuntu. A colleague > also tested it on their Mac > and didn't encounter the problem. I do have a fix in this area that I > believe should address an edge > case where data might be written to freed DSA memory. > > Kindly test using the v35 patch and let me know if you still see the > issue. Thanks for the update. v35 works fine on my environment. I ran the same test and haven’t encountered the crash anymore. The addition of the following code appears to have resolved the issue: +memstats_dsa_cleanup(char *key) +{ + MemoryStatsDSHashEntry *entry; + + entry = dshash_find(MemoryStatsDsHash, key, true); Since you seem to make a next version patch, I understand v35 is an interim patch, so this isn’t a major concern, but I encountered trailing whitespace warnings when applying the patches: $ git apply 0001-v35-0001-Add-pg_get_process_memory_context-function.patch 0001-v35-0001-Add-pg_get_process_memory_context-function.patch:705: trailing whitespace. 0001-v35-0001-Add-pg_get_process_memory_context-function.patch:1066: trailing whitespace. -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA Japan Corporation to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-10-28T05:36:06Z
Hi, PFA an updated v39 patch which is ready for review in the upcoming commitfest. > v35 works fine on my environment. > I ran the same test and haven’t encountered the crash anymore. > Thank you for testing and confirming the fix. > The addition of the following code appears to have resolved the issue: > > +memstats_dsa_cleanup(char *key) > +{ > + MemoryStatsDSHashEntry *entry; > + > + entry = dshash_find(MemoryStatsDsHash, key, true); > Yes, without this code, the dsa memory was being freed in the timeout path without acquiring a lock. Since you seem to make a next version patch, I understand v35 is an > interim patch, > so this isn’t a major concern, but I encountered trailing whitespace > warnings when applying the patches. > $ git apply > 0001-v35-0001-Add-pg_get_process_memory_context-function.patch > 0001-v35-0001-Add-pg_get_process_memory_context-function.patch:705: > trailing whitespace. > 0001-v35-0001-Add-pg_get_process_memory_context-function.patch:1066: > trailing whitespace. > > Thanks, should be fixed now. The updated patch contains the following changes. These changes are addressing some review comments discussed off list and a couple of bugs found while doing injection points tests. 1. All the changes made to MemoryContextStatsInternal and MemoryContextStatsDetail are removed. Instead of modifying these functions, I have written a separate function MemoryContextStatsCounter that takes care of counting statistics. This approach ensures that the existing functions remain unchanged. 2. Changes to ensure that the wait loop does not exceed the prescribed wait time. Additional exit condition has been added to the infinite loop that waits for request completion. This allows the pg_get_memoy_context_statistics function to return if the elapsed time goes beyond a set limit i.e the following timeout. 3. The user facing timeout is removed as that would complicate the user interface. CFIs are called frequently and the requests are likely to be addressed promptly. A predefined macro MEMORY_CONTEXT_STATS_TIMEOUT 5 (secs) is used for timeout instead. This would also remove the possibility of a user setting very low timeouts, which could cause requests to be incomplete and result in NULL outputs. 4. Miscellaneous cleanups to improve comments and remove left over comments from older versions. Also, removed an unnecessary argument from the PublishMemoryContext function. 5. Addressed Torikoshias suggestion to change the order of columns to match pg_backend_memory_contexts. 6. Attached is a test module that tests error handling by introducing errors using injection points. I have resolved a few bugs, so the memory monitoring function now runs correctly after the previous request ended with an error. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-11-07T22:55:42Z
Hi, I have attached a version 40 patch that has been rebased onto the latest master branch, as CFbot indicated a rebase was needed. The test module patch is unchanged. Thank you, Rahila Syed On Tue, Oct 28, 2025 at 11:06 AM Rahila Syed <rahilasyed90@gmail.com> wrote: > Hi, > > PFA an updated v39 patch which is ready for review in the upcoming > commitfest. > > >> v35 works fine on my environment. >> I ran the same test and haven’t encountered the crash anymore. >> > > Thank you for testing and confirming the fix. > > >> The addition of the following code appears to have resolved the issue: >> >> +memstats_dsa_cleanup(char *key) >> +{ >> + MemoryStatsDSHashEntry *entry; >> + >> + entry = dshash_find(MemoryStatsDsHash, key, true); >> > > Yes, without this code, the dsa memory was being freed in the timeout path > without acquiring a lock. > > Since you seem to make a next version patch, I understand v35 is an >> interim patch, >> so this isn’t a major concern, but I encountered trailing whitespace >> warnings when applying the patches. >> > $ git apply >> 0001-v35-0001-Add-pg_get_process_memory_context-function.patch >> 0001-v35-0001-Add-pg_get_process_memory_context-function.patch:705: >> trailing whitespace. >> 0001-v35-0001-Add-pg_get_process_memory_context-function.patch:1066: >> trailing whitespace. >> >> > Thanks, should be fixed now. > > The updated patch contains the following changes. These changes are > addressing some review comments > discussed off list and a couple of bugs found while doing injection points > tests. > > 1. > All the changes made to MemoryContextStatsInternal and > MemoryContextStatsDetail are removed. > Instead of modifying these functions, I have written a separate function > MemoryContextStatsCounter > that takes care of counting statistics. This approach ensures that the > existing functions remain unchanged. > > 2. Changes to ensure that the wait loop does not exceed the prescribed > wait time. > Additional exit condition has been added to the infinite loop that waits > for request completion. > This allows the pg_get_memoy_context_statistics function to return if the > elapsed time goes beyond > a set limit i.e the following timeout. > > 3. The user facing timeout is removed as that would complicate the user > interface. CFIs > are called frequently and the requests are likely to be addressed promptly. > A predefined macro MEMORY_CONTEXT_STATS_TIMEOUT 5 (secs) is used for > timeout > instead. This would also remove the possibility of a user setting very > low timeouts, which > could cause requests to be incomplete and result in NULL outputs. > > 4. Miscellaneous cleanups to improve comments and remove left over > comments from older > versions. Also, removed an unnecessary argument from the > PublishMemoryContext function. > > 5. Addressed Torikoshias suggestion to change the order of columns to match > pg_backend_memory_contexts. > > 6. Attached is a test module that tests error handling by introducing > errors using > injection points. I have resolved a few bugs, so the memory monitoring > function > now runs correctly after the previous request ended with an error. > > Thank you, > Rahila Syed > -
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-11-20T09:56:43Z
> On 7 Nov 2025, at 23:55, Rahila Syed <rahilasyed90@gmail.com> wrote: > I have attached a version 40 patch that has been rebased onto the > latest master branch, as CFbot indicated a rebase was needed. Thanks for the rebase, below are a few mostly superficial comments on the patch: +#include "access/twophase.h" +#include "catalog/pg_authid_d.h" ... +#include "utils/acl.h" Are these actually required to be included? - MemoryContextId *entry; + MemoryStatsContextId *entry; Why is this needed? MemoryStatsContextId is identical to MemoryContextId and is too only used in mcxtfuncs.c so there is no need to expose it in memutils.h. Can't you just use MemoryContextId everywhere or am I missing something? +#define CLIENT_KEY_SIZE 64 + +static LWLock *client_keys_lock = NULL; +static int *client_keys = NULL; +static dshash_table *MemoryStatsDsHash = NULL; +static dsa_area *MemoryStatsDsaArea = NULL; These new additions have in some cases too generic names (client_keys etc) and they all lack comments explaining why they're needed. Maybe a leading block comment explaining they are used for process memory context reporting, and then inline comments on each with their use? +#define CLIENT_KEY_SIZE 64 ... + char key[CLIENT_KEY_SIZE]; ... + snprintf(key, sizeof(key), "%d", MyProcNumber); Given that MyProcNumber is an index into the proc array, it seems excessive to use 64 bytes to store it, can't we get away with a small stack allocation? + * Retreive the client key for publishing statistics and reset it to -1, s/Retreive/Retrieve/ + ProcNumber procNumber = INVALID_PROC_NUMBER; This variable is never accessed before getting re-assigned, so this assignment in the variable definition can be removed per project style. + InitMaterializedSRF(fcinfo, 0); Can this initialization be postponed till when we know the ResultSetInfo is needed? While a micro optimization, it seems we can avoid that overhead in case the query errors out? + if (MemoryStatsDsHash == NULL) + MemoryStatsDsHash = GetNamedDSHash("memory_context_statistics_dshash", &memctx_dsh_params, &found); Nitpick, but there are a few oversize lines, like this one, which need to be wrapped to match project style. + /* + * XXX. If the process exits without cleaning up its slot, i.e in case of + * an abrupt crash the client_keys slot won't be reset thus resulting in + * false negative and WARNING would be thrown in case another process with + * same slot index is queried for statistics. + */ + if (client_keys[procNumber] == -1) + client_keys[procNumber] = MyProcNumber; + else + { + LWLockRelease(client_keys_lock); + ereport(WARNING, + errmsg("server process %d is processing previous request", pid)); + PG_RETURN_NULL(); + } AFAICT this mean that a failure to clean up (through a crash for example) can block a future backend from reporting which isn't entirely ideal. Is there anything we can do to mitigate this? + bool summary = false; In ProcessGetMemoryContextInterrupt(), can't we just read entry->summary rather than define a local variable and assign it? We already read lots of other fields from entry directly so it seems more readable to be consistent. + /* + * Add the count of children contexts which are traversed + */ + *num_contexts = *num_contexts + ichild; Isn't this really the number of children + the parent context? ichild starts at one to (AIUI) include the parent context. Also, MemoryContextStatsCounter should also make sure to set num_contexts to zero before adding to it. +#define MAX_MEMORY_CONTEXT_STATS_SIZE (sizeof(MemoryStatsEntry)) +#define MAX_MEMORY_CONTEXT_STATS_NUM MEMORY_CONTEXT_REPORT_MAX_PER_BACKEND / MAX_MEMORY_CONTEXT_STATS_SIZE I don't think MAX_MEMORY_CONTEXT_STATS_SIZE adds any value as it's only used once, on the line directly after its definition. We can just use the expansion of ((sizeof(MemoryStatsEntry)) when defining MAX_MEMORY_CONTEXT_STATS_NUM. > The test module patch is unchanged. Please include all commits in the series even if they aren't updated since the CFBot cannot pick them up otherwise. -- Daniel Gustafsson -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-11-25T07:20:44Z
Hi Daniel, Thank you for your comments. Please find attached v41 with all the comments addressed. > > +#include "access/twophase.h" > +#include "catalog/pg_authid_d.h" > ... > +#include "utils/acl.h" > Are these actually required to be included? > > Removed these. > - MemoryContextId *entry; > + MemoryStatsContextId *entry; > Why is this needed? MemoryStatsContextId is identical to MemoryContextId > and > is too only used in mcxtfuncs.c so there is no need to expose it in > memutils.h. > Can't you just use MemoryContextId everywhere or am I missing something? > > MemoryContextId has been renamed to MemoryStatsContextId for better code readability. I removed the leftover MemoryContextId definition. Also, I moved it out of memutils.h. Did the same with some other structures and definitions which were only used in mcxtfuncs.c > +#define CLIENT_KEY_SIZE 64 > + > +static LWLock *client_keys_lock = NULL; > +static int *client_keys = NULL; > +static dshash_table *MemoryStatsDsHash = NULL; > +static dsa_area *MemoryStatsDsaArea = NULL; > These new additions have in some cases too generic names (client_keys etc) > and > they all lack comments explaining why they're needed. Maybe a leading > block > comment explaining they are used for process memory context reporting, and > then > inline comments on each with their use? > > Added comments. > +#define CLIENT_KEY_SIZE 64 > ... > + char key[CLIENT_KEY_SIZE]; > ... > + snprintf(key, sizeof(key), "%d", MyProcNumber); > Given that MyProcNumber is an index into the proc array, it seems > excessive to > use 64 bytes to store it, can't we get away with a small stack allocation? > I agree. Defined it as 32 bytes as MyProcNumber is of size uint32. Kindly let me know if you think it can be reduced further. + * Retreive the client key for publishing statistics and reset it to -1, > s/Retreive/Retrieve/ > Fixed. > + ProcNumber procNumber = INVALID_PROC_NUMBER; > This variable is never accessed before getting re-assigned, so this > assignment > in the variable definition can be removed per project style. > > > Fixed too. > + InitMaterializedSRF(fcinfo, 0); > Can this initialization be postponed till when we know the ResultSetInfo is > needed? While a micro optimization, it seems we can avoid that overhead in > case the query errors out? > > Good point. Added this just before the result set is getting populated. > + if (MemoryStatsDsHash == NULL) > + MemoryStatsDsHash = > GetNamedDSHash("memory_context_statistics_dshash", &memctx_dsh_params, > &found); > Nitpick, but there are a few oversize lines, like this one, which need to > be > wrapped to match project style. > > I have edited this accordingly. > + /* > + * XXX. If the process exits without cleaning up its slot, i.e in case > of > + * an abrupt crash the client_keys slot won't be reset thus resulting > in > + * false negative and WARNING would be thrown in case another process > with > + * same slot index is queried for statistics. > + */ > + if (client_keys[procNumber] == -1) > + client_keys[procNumber] = MyProcNumber; > + else > + { > + LWLockRelease(client_keys_lock); > + ereport(WARNING, > + errmsg("server process %d is processing previous request", > pid)); > + PG_RETURN_NULL(); > + } > AFAICT this mean that a failure to clean up (through a crash for example) > can > block a future backend from reporting which isn't entirely ideal. Is there > anything we can do to mitigate this? > > Yes, we can reset it when the client times out, as long as we verify that the value corresponds to our ProcNumber and not another client's request. Fixed accordingly. > > + bool summary = false; > In ProcessGetMemoryContextInterrupt(), can't we just read entry->summary > rather > than define a local variable and assign it? We already read lots of other > fields from entry directly so it seems more readable to be consistent. > > Fixed. > > + /* > + * Add the count of children contexts which are traversed > + */ > + *num_contexts = *num_contexts + ichild; > Isn't this really the number of children + the parent context? ichild > starts > at one to (AIUI) include the parent context. Also, > MemoryContextStatsCounter > should also make sure to set num_contexts to zero before adding to it. > > Yes. Adjusted the comment to match this and set num_contexts to zero. > > +#define MAX_MEMORY_CONTEXT_STATS_SIZE (sizeof(MemoryStatsEntry)) > +#define MAX_MEMORY_CONTEXT_STATS_NUM > MEMORY_CONTEXT_REPORT_MAX_PER_BACKEND / MAX_MEMORY_CONTEXT_STATS_SIZE > I don't think MAX_MEMORY_CONTEXT_STATS_SIZE adds any value as it's only > used > once, on the line directly after its definition. We can just use the > expansion > of ((sizeof(MemoryStatsEntry)) when defining MAX_MEMORY_CONTEXT_STATS_NUM. > > Fixed. I've attached the test patch as is, I will clean it up and do further improvements to it. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-11-28T09:22:14Z
Hi, I'm attaching the updated patches, which primarily include cleanup and have been rebased following the CFbot report. Thank you, Rahila Syed On Tue, Nov 25, 2025 at 12:50 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > Hi Daniel, > > Thank you for your comments. Please find attached v41 with all the > comments addressed. > > >> >> +#include "access/twophase.h" >> +#include "catalog/pg_authid_d.h" >> ... >> +#include "utils/acl.h" >> Are these actually required to be included? >> >> > Removed these. > > >> - MemoryContextId *entry; >> + MemoryStatsContextId *entry; >> Why is this needed? MemoryStatsContextId is identical to MemoryContextId >> and >> is too only used in mcxtfuncs.c so there is no need to expose it in >> memutils.h. >> Can't you just use MemoryContextId everywhere or am I missing something? >> >> > MemoryContextId has been renamed to MemoryStatsContextId for better > code readability. I removed the leftover MemoryContextId definition. > Also, I moved it out of memutils.h. Did the same with some other structures > and definitions which were only used in mcxtfuncs.c > > >> +#define CLIENT_KEY_SIZE 64 >> + >> +static LWLock *client_keys_lock = NULL; >> +static int *client_keys = NULL; >> +static dshash_table *MemoryStatsDsHash = NULL; >> +static dsa_area *MemoryStatsDsaArea = NULL; >> These new additions have in some cases too generic names (client_keys >> etc) and >> they all lack comments explaining why they're needed. Maybe a leading >> block >> comment explaining they are used for process memory context reporting, >> and then >> inline comments on each with their use? >> >> > Added comments. > > >> +#define CLIENT_KEY_SIZE 64 >> ... >> + char key[CLIENT_KEY_SIZE]; >> ... >> + snprintf(key, sizeof(key), "%d", MyProcNumber); >> Given that MyProcNumber is an index into the proc array, it seems >> excessive to >> use 64 bytes to store it, can't we get away with a small stack allocation? >> > > I agree. Defined it as 32 bytes as MyProcNumber is of size uint32. Kindly > let me know if you think it can be reduced further. > > > + * Retreive the client key for publishing statistics and reset it to >> -1, >> s/Retreive/Retrieve/ >> > > Fixed. > > >> + ProcNumber procNumber = INVALID_PROC_NUMBER; >> This variable is never accessed before getting re-assigned, so this >> assignment >> in the variable definition can be removed per project style. >> >> >> > Fixed too. > > >> + InitMaterializedSRF(fcinfo, 0); >> Can this initialization be postponed till when we know the ResultSetInfo >> is >> needed? While a micro optimization, it seems we can avoid that overhead >> in >> case the query errors out? >> >> > Good point. Added this just before the result set is getting populated. > > >> + if (MemoryStatsDsHash == NULL) >> + MemoryStatsDsHash = >> GetNamedDSHash("memory_context_statistics_dshash", &memctx_dsh_params, >> &found); >> Nitpick, but there are a few oversize lines, like this one, which need to >> be >> wrapped to match project style. >> >> > I have edited this accordingly. > > >> + /* >> + * XXX. If the process exits without cleaning up its slot, i.e in >> case of >> + * an abrupt crash the client_keys slot won't be reset thus resulting >> in >> + * false negative and WARNING would be thrown in case another process >> with >> + * same slot index is queried for statistics. >> + */ >> + if (client_keys[procNumber] == -1) >> + client_keys[procNumber] = MyProcNumber; >> + else >> + { >> + LWLockRelease(client_keys_lock); >> + ereport(WARNING, >> + errmsg("server process %d is processing previous >> request", pid)); >> + PG_RETURN_NULL(); >> + } >> AFAICT this mean that a failure to clean up (through a crash for example) >> can >> block a future backend from reporting which isn't entirely ideal. Is >> there >> anything we can do to mitigate this? >> >> > Yes, we can reset it when the client times out, as long as we verify that > the value corresponds > to our ProcNumber and not another client's request. Fixed accordingly. > > >> >> + bool summary = false; >> In ProcessGetMemoryContextInterrupt(), can't we just read entry->summary >> rather >> than define a local variable and assign it? We already read lots of other >> fields from entry directly so it seems more readable to be consistent. >> >> > Fixed. > > >> >> + /* >> + * Add the count of children contexts which are traversed >> + */ >> + *num_contexts = *num_contexts + ichild; >> Isn't this really the number of children + the parent context? ichild >> starts >> at one to (AIUI) include the parent context. Also, >> MemoryContextStatsCounter >> should also make sure to set num_contexts to zero before adding to it. >> >> > Yes. Adjusted the comment to match this and set num_contexts to zero. > > >> >> +#define MAX_MEMORY_CONTEXT_STATS_SIZE (sizeof(MemoryStatsEntry)) >> +#define MAX_MEMORY_CONTEXT_STATS_NUM >> MEMORY_CONTEXT_REPORT_MAX_PER_BACKEND / MAX_MEMORY_CONTEXT_STATS_SIZE >> I don't think MAX_MEMORY_CONTEXT_STATS_SIZE adds any value as it's only >> used >> once, on the line directly after its definition. We can just use the >> expansion >> of ((sizeof(MemoryStatsEntry)) when defining MAX_MEMORY_CONTEXT_STATS_NUM. >> >> > Fixed. > > I've attached the test patch as is, I will clean it up and do further > improvements to it. > > Thank you, > Rahila Syed > -
Re: Enhancing Memory Context Statistics Reporting
torikoshia <torikoshia@oss.nttdata.com> — 2025-12-08T01:26:33Z
On 2025-11-28 18:22, Rahila Syed wrote: Hi, > I'm attaching the updated patches, which primarily include cleanup and > have been rebased > following the CFbot report. Thanks for updating the patch! I observed an assertion failure when forcing a timeout as follows: ``` $ psql (pid:38587)=# $ kill -s SIGSTOP 38587 $ psql (pid:38618) =# select * from pg_get_process_memory_contexts(38587, false); name | ident | type | level | path | total_bytes | total_nblocks | free_bytes | free_chunks | used_bytes | num_agg_contexts --------+--------+--------+--------+--------+-------------+---------------+------------+-------------+------------+------------------ [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] (1 row) Time: 5013.515 ms (00:05.014) $ kill -s SIGCONT 38587 $ tail postgresql.log TRAP: failed Assert("client_keys[MyProcNumber] != -1"), File: "mcxtfuncs.c", Line: 881, PID: 38587 0 postgres 0x0000000104943400 ExceptionalCondition + 216 1 postgres 0x000000010480f738 ProcessGetMemoryContextInterrupt + 140 2 postgres 0x00000001046f2710 ProcessInterrupts + 3008 3 postgres 0x00000001046f1a78 ProcessClientReadInterrupt + 80 4 postgres 0x0000000104433994 secure_read + 404 5 postgres 0x00000001044411dc pq_recvbuf + 260 6 postgres 0x0000000104441088 pq_getbyte + 96 7 postgres 0x00000001046fa0fc SocketBackend + 44 8 postgres 0x00000001046f6d3c ReadCommand + 44 9 postgres 0x00000001046f6284 PostgresMain + 2900 10 postgres 0x00000001046ed558 BackendInitialize + 0 11 postgres 0x00000001045c0a48 postmaster_child_launch + 456 12 postgres 0x00000001045c8520 BackendStartup + 304 13 postgres 0x00000001045c636c ServerLoop + 372 14 postgres 0x00000001045c4e24 PostmasterMain + 6448 15 postgres 0x0000000104445b4c main + 924 16 dyld 0x0000000188662b98 start + 6076 2025-12-08 07:35:32.608 JST [38540] LOG: 00000: client backend (PID 38587) was terminated by signal 6: Abort trap: 6 2025-12-08 07:35:32.608 JST [38540] LOCATION: LogChildExit, postmaster.c:2872 ``` Below are comments regarding the v42-0001 patch: > In order to not block on busy processes, we have hardcoded > the number of seconds during which to retry before timing out. > In the case where no statistics are published within the set > timeout, NULL is returned. It might be good to also document in func-admin.sgml that the function times out after 5 seconds when the target backend does not respond, and that in such a case NULLs are returned. + * If DSA exists, created by another process requesting statistics, attach + * to it. We expect the client process to create required DSA and Dshash + * table. + */ + if (MemoryStatsDsaArea == NULL) + MemoryStatsDsaArea = GetNamedDSA("memory_context_statistics_dsa", + &found); + + if (MemoryStatsDsHash == NULL) + MemoryStatsDsHash = GetNamedDSHash("memory_context_statistics_dshash", + &memctx_dsh_params, &found); From the comment, it sounded to me as if the client executing pg_get_process_memory_contexts() might not create the DSA in some cases. Is it correct to assume that such a situation can happen? In [1], as a response to concerns about using DSA inside a CFI handler, you wrote that “all the dynamic shared memory needed to store the statistics is created and deleted in the client function”. So I understood that it would never create the DSA inside the CFI handler. If that understanding is correct, perhaps the comment should be reworded to make that clear. + context_id_lookup = hash_create("pg_get_remote_backend_memory_contexts", This appears to use the old function name. Should this be updated to "pg_get_process_memory_contexts" instead? [1] https://www.postgresql.org/message-id/CAH2L28sc-rEhyntPLoaC2XUa0ZjS5ka6KzEbuSVxQBBnUYu1KQ%40mail.gmail.com -- Regards, -- Atsushi Torikoshi Seconded from NTT DATA Japan Corporation to SRA OSS K.K. -
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2025-12-08T09:43:18Z
> On 28 Nov 2025, at 10:22, Rahila Syed <rahilasyed90@gmail.com> wrote: > > Hi, > > I'm attaching the updated patches, which primarily include cleanup and have been rebased > following the CFbot report. Thanks for the patch, below are a few comments and suggestions. As I was reviewing I tweaked the below and have attached the comments as changes in 0003. == in func-admin.sgml + <function>pg_get_process_memory_contexts</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>summary</parameter> <type>boolean</type> ) We recently simplified the UI by removing the timeout, and the more I think about it the more I am convinced that there is more simplification to be had. The most likely usage pattern, IMO, will be to get all the contexts and not the summary, so we can make the summary parameter DEFAULT to false. This allows most uses to just pass the pid, without complicating the code at all. == in mcxtfuncs.c +/* Size of dshash key */ +#define CLIENT_KEY_SIZE 32 That's still pretty generous isn't it? We are printing a uint32 into it so the highest number it can reach is ~4 billion (which while the upper limit, is quite theoretic in this case). 10 + 1 bytes should suffice to store right? - * MemoryContextId + * MemoryStatsContextId Sorry, but I still don't agree with this rename and I think we should skip it, if only to avoid changes to existing parts of the code. + * Entry has been deleted due to client process exit. Make sure that the + * client always deletes the entry after taking required lock or this + * function may end up writing to unallocated memory. Can you explain this a bit further, I'm not sure I get it. The code goes on to release a lock immediately and then destroys the hash. Who is responsible for destroying the entry? == In system-views.sql +REVOKE EXECUTE ON FUNCTION + pg_get_process_memory_contexts(integer, boolean) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION + pg_get_process_memory_contexts(integer, boolean) TO pg_read_all_stats; This is not a view, and the functions aren't used to drive a view, so these should not be defined here. The above mentioned change to add DEFAULT handling to the summary parameter fixes this in the attached. == In ProcessGetMemoryContextInterrupt() I'm not a fan of having to exit's from the function doing duplicative cleanups, in the attached I've wrapped them in a conditional to just have one exit path. What do you think about that? == In PublishMemoryContext() + end_memorycontext_reporting(entry, oldcontext, context_id_lookup); Looking at this more I don't really like that resetting the memory context is done via a separate function, when that function must be called from the exact right place to ensure CurrentMemoryContext is what it thinks it is. It's all a bit too magic. Since this is only called in 3 places I would prefer to inline the code in PublishMemoryContext(). == In PublishMemoryContext() + const char *ident = context->ident; + const char *name = context->name; ident and name are defined as const, but they are later assigned to after the initial assignment. I think we need to unconstify these. == In PublishMemoryContext() + if (strlen(name) >= MEMORY_CONTEXT_NAME_SHMEM_SIZE) We already have namelen which is set to exactly strlen(name), so let's reuse that for readability. == In PublishMemoryContext() + /* Allocate DSA memory for storing path information */ This comment is no longer accurate is it? The DSA has already been allocated at this point. == In memutils.h +#include "utils/dsa.h" This is not needed. I also did some smaller comment rewording and reflowing, some smaller cleanups and a fresh pgindent/pgperltidy run. The attached 0003 contains the above. -- Daniel Gustafsson
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-12-12T09:46:46Z
Hi, Sorry for the late response. Thank you for your reviewing and testing the patch. On Mon, Dec 8, 2025 at 6:56 AM torikoshia <torikoshia@oss.nttdata.com> wrote: > On 2025-11-28 18:22, Rahila Syed wrote: > > Hi, > > > I'm attaching the updated patches, which primarily include cleanup and > > have been rebased > > following the CFbot report. > > Thanks for updating the patch! > > I observed an assertion failure when forcing a timeout as follows: > > Good catch. This assertion is no longer valid because of recent updates that reset the client_keys slot for a request when the client exits with a timeout. To address this, I’ve replaced the assertion with a check for -1 and now return from the function in that case. It might be good to also document in func-admin.sgml that the function > times out after 5 seconds when the target backend does not respond, and > that in such a case NULLs are returned. > > Added this. From the comment, it sounded to me as if the client executing > pg_get_process_memory_contexts() might not create the DSA in some cases. > Is it correct to assume that such a situation can happen? > In [1], as a response to concerns about using DSA inside a CFI handler, > you wrote that “all the dynamic shared memory needed to store the > statistics is created and deleted in the client function”. > So I understood that it would never create the DSA inside the CFI > handler. > If that understanding is correct, perhaps the comment should be reworded > to make that clear. > > Yes, your understanding is correct. I reworded the comment accordingly. > + context_id_lookup = > hash_create("pg_get_remote_backend_memory_contexts", > > This appears to use the old function name. Should this be updated to > "pg_get_process_memory_contexts" instead? > > Modified this. I will post the updated patch in response to Daniel's message that follows your email. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-12-12T11:04:37Z
Hi Daniel, > > Thanks for the patch, below are a few comments and suggestions. As I was > reviewing I tweaked the below and have attached the comments as changes in > 0003. > Thank you for the improvements. All your changes look good to me. I have incorporated those in the v44 patch. > + * Entry has been deleted due to client process exit. Make sure that > the > + * client always deletes the entry after taking required lock or this > + * function may end up writing to unallocated memory. > Can you explain this a bit further, I'm not sure I get it. The code goes > on to > release a lock immediately and then destroys the hash. Who is responsible > for > destroying the entry? > This just points to the general requirements of taking a lock before writing to a shared variable. This serves as a warning to other processes not to delete the entry without taking a lock, since we are about to write to the entry. > == In ProcessGetMemoryContextInterrupt() > I'm not a fan of having to exit's from the function doing duplicative > cleanups, > in the attached I've wrapped them in a conditional to just have one exit > path. > What do you think about that? > I agree with your approach. It certainly makes the code more concise and easier to read. > == In PublishMemoryContext() > + /* Allocate DSA memory for storing path information */ > This comment is no longer accurate is it? The DSA has already been > allocated > at this point. > > Yes, it is not valid anymore. Fixed accordingly. Apart from this, I cleaned up the test module by removing unnecessary sql functions, added some more injection points based tests and a few minor tweaks. Please find attached updated and rebased patches. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-12-18T19:54:42Z
Hi, PFA the updated and rebased patches. To summarize the work done in this thread, here are some key concerns discussed in threads [1] and [2] and steps taken to address those: *DSA APIs and CFI Handler Safety*: DSA APIs, being high-level, are unsafe to call from the CFI handler, which can be invoked from low-level code. This concern was particularly raised for APIs like `dsa_allocate()` and `dsa_create()`. To resolve this, these APIs have been moved out of the CFI handler function. Now, the dynamic shared memory needed to store the statistics is allocated and deleted in the client function. The only operation performed in the CFI handler is `dsm_attach()`, which attaches to DSA for copying statistics. Since dsm_attach() only maps the existing DSM into the current process address space and does not create a new DSM, I don't see any specific reason why it would be unsafe to call it from the CFI handler. *Memory Leak in TopMemoryContext*: A memory leak was reported in the TopMemoryContext and there were concerns that memory allocated while executing the memory statistics reporting function could impact its output. To address this, we create an exclusive memory context under the NULL context to handle all memory allocations in `ProcessGetMemoryContextInterrupt`. This context does not fall under the TopMemoryContext tree, ensuring that allocations do not affect the function's outcome. The memory context is reset at the end of the function, preventing leaks. *Error Reported in Thread [2]*: This issue has been fixed by switching to a NULL resource owner before attaching to DSM in the CFI handler. *Other Improvements*: 1. Simplified the user interface by removing the `timeout` argument and using a constant value instead. 2. Provided a default for the `get_summary` argument so users do not need to pass a value if they choose not to. 3. The dsm_registry APIs are used to create and attach to DSA and DSHASH tables, which helps avoid code duplication. 4. Replaced the static shared memory array with a DSHASH table, which holds metadata such as pointers to memory containing statistics for each process. 5. The updates previously made to mcxt.c have been moved to mcxtfuncs.c, which now includes all the existing memory statistics functions as well as the code for the new proposed function 6. One function that relies on an unexported API is added in mcxt.c Thank you, Rahila Syed [1]. PostgreSQL: Re: pgsql: Add function to get memory context stats for processes <https://www.postgresql.org/message-id/CA%2BTgmoaey-kOP1k5FaUnQFd1fR0majVebWcL8ogfLbG_nt-Ytg%40mail.gmail.com> [2]. PostgreSQL: Re: Prevent an error on attaching/creating a DSM/DSA from an interrupt handler. <https://www.postgresql.org/message-id/flat/8B873D49-E0E5-4F9F-B8D6-CA4836B825CD%40yesql.se#7026d2fe4ab0de6dd5decd32eb9c585a>
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2025-12-29T08:56:40Z
Hi, I've included some additional description inline and attached rebased patches after CFbot reported a conflict. > *DSA APIs and CFI Handler Safety*: DSA APIs, being high-level, are unsafe > to call from the CFI handler, > which can be invoked from low-level code. This concern was particularly > raised for APIs like `dsa_allocate()` > and `dsa_create()`. > To resolve this, these APIs have been moved out of the CFI handler > function. Now, the dynamic shared memory > needed to store the statistics is allocated and deleted in the client > function. The only operation performed in the CFI > handler is `dsm_attach()`, which attaches to DSA for copying statistics. > Since dsm_attach() only maps the existing > DSM into the current process address space and does not create a new DSM, > I don't see any specific reason why > it would be unsafe to call it from the CFI handler. > > Following are the details about the use of DSM in the patch: - DSA Creation: A Dynamic Shared Area (DSA) is used to store memory context statistics. - Client Process: When fetching memory context statistics, the client allocates a 1 MB chunk in the DSA, reads the statistics from the memory chunk, copies it into a tuple store, and then frees the chunk. - Storage: Pointers to these chunks are stored in a DSHASH table indexed by the client’s proc number. Each entry in the DSHASH table also stores additional metadata related to the client’s request. - Attachment: Backends only attach to the DSM segments for the DSA and DSHASH table when necessary i.e when a process queries memory context statistics or is queried by another backend. Once attached, they remain so until the session ends, at which point they remove their DSHASH entry if any and detach from DSA and DSHASH segments. - Lifecycle: The DSA and DSHASH structures are created upon the first SQL function invocation and destroyed on server restart. > *Error Reported in Thread [2]*: This issue has been fixed by switching to > a NULL resource owner before attaching > to DSM in the CFI handler. > > This error mentioned in thread [2] is triggered during CFI() call from secure_read() when a backend is waiting for commands and it has an open transaction which is going to abort Below are some details about this fix. It is safe to temporarily set the resource owner to NULL before attaching to the DSA and DSHASH, since these segments are intended to be attached for the full session and are detached only when the session ends. We also restore the original resource owner immediately after the attach completes. Other possible fixes include: 1.Adjusting resource‑owner behavior Either allow resource‑owner enlargement during release, or delay marking it as releasing until the abort actually begins. 2. Updating DSM registry APIs (e.g., GetNamedDSA) Detect when the current resource owner is in a releasing state and temporarily set CurrentResourceOwner to NULL before calling dsa_attach. 3. Handling it in the DSA layer This was discussed in thread [2], but concerns were raised that DSA should not compensate for incorrect caller state; the caller must ensure the resource owner is valid. Kindly let me know your views. Thank you, Rahila Syed [2]. PostgreSQL: Re: Prevent an error on attaching/creating a DSM/DSA from an interrupt handler <https://www.postgresql.org/message-id/flat/8B873D49-E0E5-4F9F-B8D6-CA4836B825CD%40yesql.se#7026d2fe4ab0de6dd5decd32eb9c585a>
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-01-09T11:22:25Z
Hi, >> *Error Reported in Thread [2]*: This issue has been fixed by switching >> to a NULL resource owner before attaching >> to DSM in the CFI handler. >> >> > This error mentioned in thread [2] is triggered during CFI() call from > secure_read() when a > backend is waiting for commands and it has an open transaction which is > going to abort > > Below are some details about this fix. > > It is safe to temporarily set the resource owner to NULL before attaching > to the DSA > and DSHASH, since these segments are intended to be attached for the full > session > and are detached only when the session ends. > We also restore the original resource owner immediately after the attach > completes. > > After further discussion and reviewing Robert's email[1] on this topic, a safer solution is to avoid running ProcessGetMemoryContextInterrupt during an aborted transaction. This should help prevent additional errors when the transaction is already in error handling state. Also, reporting memory context statistics from an aborting transaction won't be very useful as some of that memory usage won't be valid after abort completes. Attached is the updated patch that addresses this. > Other possible fixes include: > 1.Adjusting resource‑owner behavior > Either allow resource‑owner enlargement during release, or delay marking > it as releasing until > the abort actually begins. > Sorry, this point is invalid as resource-owner is already being marked as releasing from AbortTransaction. Thank you, Rahila Syed [1]. https://www.postgresql.org/message-id/CA%2BTgmoapJ6erjT21uPO12wTtoOmj6w-dp6T3qySN%2BNSc1cdEKw%40mail.gmail.com >
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2026-01-12T13:31:38Z
> On 9 Jan 2026, at 12:22, Rahila Syed <rahilasyed90@gmail.com> wrote: > After further discussion and reviewing Robert's email[1] on this topic, a safer solution > is to avoid running ProcessGetMemoryContextInterrupt during an aborted transaction. > This should help prevent additional errors when the transaction is already in error handling > state. Also, reporting memory context statistics from an aborting transaction won't > be very useful as some of that memory usage won't be valid after abort completes. +1, I think was the right call to make. > Attached is the updated patch that addresses this. A few small comments on v47: +static const char *ContextTypeToString(NodeTag type); I think context_type_to_string() would be a better name on this internal function to model it closer to the existing int_list_to_array(). Personally I would also place it before its first use to avoid the prototype, but that's personal preference. +static void +memstats_dsa_cleanup(char *key) This function warrants a documentation comment describing when it should be used safely. + memstats_dsa_cleanup(key); + memstats_client_key_reset(procNumber); + ConditionVariableCancelSleep(); + PG_RETURN_NULL(); I think we should notify the user in these two timeout cases, why not adding an ereport(NOTICE, "request for memory context statistics timed out")); or something with a better wording than that. + Size sz = 0; + Size TotalProcs = 0; + + TotalProcs = add_size(TotalProcs, NUM_AUXILIARY_PROCS); + TotalProcs = add_size(TotalProcs, MaxBackends); + sz = add_size(sz, mul_size(TotalProcs, sizeof(int))); + + return sz As we discussed off-list, the call to add_size() call can be omitted as it won't affect the calculation. +# Copyright (c) 2025, PostgreSQL Global Development Group Here, and possibly elsewhere, it should say 2026 instead I think. -- Daniel Gustafsson
-
Re: Enhancing Memory Context Statistics Reporting
Robert Haas <robertmhaas@gmail.com> — 2026-01-13T19:31:00Z
On Fri, Jan 9, 2026 at 6:22 AM Rahila Syed <rahilasyed90@gmail.com> wrote: > After further discussion and reviewing Robert's email[1] on this topic, a safer solution > is to avoid running ProcessGetMemoryContextInterrupt during an aborted transaction. > This should help prevent additional errors when the transaction is already in error handling > state. Also, reporting memory context statistics from an aborting transaction won't > be very useful as some of that memory usage won't be valid after abort completes. > Attached is the updated patch that addresses this. I think the question here is whether it's safe to interrupt the server at an arbitrary CFI to run ProcessGetMemoryContextInterrupt. The good news is that we now know that that function won't be executed (or at least not in a substantive way) when we're an aborted transaction. But we have no guarantee that we're in a transaction at all, which I think is concerning, because it seems hard to write code that behaves properly both inside of a transaction and outside of a transaction. Some things I notice reading through: + LWLockAcquire(client_keys_lock, LW_SHARED); LWLocks normally use NamesLikeThis not names_like_this and are generally created by just listing them in lwlocklist.h. Also, it's not really safe to acquire an LWLock if there's no transaction active. If we error afterward, what will release the LWLock? + else + { + clientProcNumber = client_keys[MyProcNumber]; + client_keys[MyProcNumber] = -1; + LWLockRelease(client_keys_lock); + } The "else" is not really necessary here because the "if" portion ends with "return". + memstats_ctx = AllocSetContextCreate((MemoryContext) NULL, + "publish_memory_context_statistics", + ALLOCSET_SMALL_SIZES); The comments do a good job justifying this, but as far as I know it would be the only instance of this pattern in the entire source tree. Are we really sure we want to deviate from the idea of having the memory context tree be a tree? And is it really so bad if the memory used to report memory contexts is included in the output? + MemoryStatsDsaArea = GetNamedDSA("memory_context_statistics_dsa", + &found); ... + MemoryStatsDsHash = GetNamedDSHash("memory_context_statistics_dshash", + &memctx_dsh_params, &found); ... + entry = dshash_find_or_insert(MemoryStatsDsHash, key, &found); Like LWLockAcquire, all of this code supposes that there's a transaction available to manage resource acquisition and release and to clean up after errors. I doubt that any of this is safe without a transaction. I think my view overall here is: bailing out when the current transaction is aborted is a good start in terms of making this safe, but I don't think it's enough. If we get here when the backend is idle, for example, which seems like it would be possible, then we'll have no active transaction, and then I think that a lot of what's being called here is not going to necessarily work as intended. There are other places in the source code that do various push-ups to get around this problem -- e.g. look at RemoveTempRelationsCallback, which knows that it will be called from a transaction but not whether that transaction will be aborted. I don't think that's the approach you want here: restarting an aborted transaction in this context wouldn't be smart. But spinning one up if there isn't one might be reasonable. See ProcessCatchupInterrupt() for an example of existing code that deals with this problem -- again that's probably not quite the same situation, but it gives you an idea what other people have done before. One other point to note here is that if, by some chance, a failure occurs as a result of receiving a memory-context interrupt, it's going to fail the currently-running transaction. Unlike the problem discussed in the previous paragraph, that's not a system-integrity issue: from the point of view of PostgreSQL's transaction system, having process #1 cause process #2's transaction to fail is 100% OK and nothing will break. From the user's point of view, however, this will be very painful: you just wanted to inspect the state of a running transaction, very possibly a long-running one that had done a lot of work, and you accidentally killed it. So I think it's going to be important that such cases are extremely rare in practice. If they happen because of extremely weird scenarios like somebody manually removing a shared memory segment while it's still in use by PostgreSQL, or the system running out of memory at exactly the wrong moment, I think that's probably fine. If they happen because of something like a low-probability race condition, that's going to be unacceptable to users. Nobody is going to be happy about using the feature if it can cause the transactions that they're querying to randomly die, even if the chances are low. I'm not saying the patch actually has this problem, just that it's something to think very carefully about. -- Robert Haas EDB: http://www.enterprisedb.com -
Re: Enhancing Memory Context Statistics Reporting
Andres Freund <andres@anarazel.de> — 2026-01-13T20:26:02Z
Hi, On 2026-01-13 14:31:00 -0500, Robert Haas wrote: > Also, it's not really safe to acquire an LWLock if there's no > transaction active. If we error afterward, what will release the > LWLock? All the error handling paths (hopefully) have an LWLockReleaseAll()... Which is pretty crucial given that we do stuff outside of transactions in other places. That doesn't mean the other concerns about resource management are unfounded, however. Greetings, Andres Freund
-
Re: Enhancing Memory Context Statistics Reporting
Robert Haas <robertmhaas@gmail.com> — 2026-01-13T21:11:24Z
On Tue, Jan 13, 2026 at 3:26 PM Andres Freund <andres@anarazel.de> wrote: > All the error handling paths (hopefully) have an LWLockReleaseAll()... Which > is pretty crucial given that we do stuff outside of transactions in other > places. > > That doesn't mean the other concerns about resource management are unfounded, > however. Yeah, I actually wasn't completely sure about that particular comment. I think what will happen if we ERROR outside of a transaction is that it will become FATAL and kill the backend, but I'm not 100% positive about that. -- Robert Haas EDB: http://www.enterprisedb.com
-
Re: Enhancing Memory Context Statistics Reporting
Andres Freund <andres@anarazel.de> — 2026-01-13T21:47:41Z
Hi, On 2026-01-13 16:11:24 -0500, Robert Haas wrote: > On Tue, Jan 13, 2026 at 3:26 PM Andres Freund <andres@anarazel.de> wrote: > > All the error handling paths (hopefully) have an LWLockReleaseAll()... Which > > is pretty crucial given that we do stuff outside of transactions in other > > places. > > > > That doesn't mean the other concerns about resource management are unfounded, > > however. > > Yeah, I actually wasn't completely sure about that particular comment. > I think what will happen if we ERROR outside of a transaction is that > it will become FATAL and kill the backend, but I'm not 100% positive > about that. I'm pretty sure that doesn't generally happen. There's promotion to FATAL if the top-level sigsetjmp() hasn't yet run (c.f. the check for PG_exception_stack in errstart()), but once it has been reached, it stays configured. Greetings, Andres Freund
-
Re: Enhancing Memory Context Statistics Reporting
Robert Haas <robertmhaas@gmail.com> — 2026-01-14T13:26:24Z
On Tue, Jan 13, 2026 at 4:47 PM Andres Freund <andres@anarazel.de> wrote: > I'm pretty sure that doesn't generally happen. There's promotion to FATAL if > the top-level sigsetjmp() hasn't yet run (c.f. the check for > PG_exception_stack in errstart()), but once it has been reached, it stays > configured. All right, then I guess I don't fully understand how the error-outside-of-a-transaction case is handled. But I still think that code like this needs to run in a transaction to avoid unexpected and undesirable results. Do you see it differently? -- Robert Haas EDB: http://www.enterprisedb.com
-
Re: Enhancing Memory Context Statistics Reporting
Andres Freund <andres@anarazel.de> — 2026-01-14T13:32:36Z
Hi, On 2026-01-14 08:26:24 -0500, Robert Haas wrote: > On Tue, Jan 13, 2026 at 4:47 PM Andres Freund <andres@anarazel.de> wrote: > > I'm pretty sure that doesn't generally happen. There's promotion to FATAL if > > the top-level sigsetjmp() hasn't yet run (c.f. the check for > > PG_exception_stack in errstart()), but once it has been reached, it stays > > configured. > > All right, then I guess I don't fully understand how the > error-outside-of-a-transaction case is handled. For those we just rely on the error cleanup inside the top-level sigsetmp() blocks... Which of course is a pretty random subset of cleanups that also differs between process types, which is quite ... not great. > But I still think that > code like this needs to run in a transaction to avoid unexpected and > undesirable results. Do you see it differently? I'm waffling on it a bit, tbh. I think doing it for something like backends it could make sense, but e.g. for something like checkpointer, that normally never runs a transaction, it seems like a bad idea. Mainly because processes that don't run transactions won't have an AbortCurrentTransaction() or such in their top-level sigsetjmp() and thus would never abort the transaction that we started, if there were an error while doing the reporting. Greetings, Andres Freund
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-01-19T17:31:17Z
Hi, Thank you for your feedback. Please find a few responses inline. I am working on incorporating all the feedback in the patch and will share it in a follow-up email. > Some things I notice reading through: > > + LWLockAcquire(client_keys_lock, LW_SHARED); > > LWLocks normally use NamesLikeThis not names_like_this and are > generally created by just listing them in lwlocklist.h. > > I will fix it, accordingly. > + else > + { > + clientProcNumber = client_keys[MyProcNumber]; > + client_keys[MyProcNumber] = -1; > + LWLockRelease(client_keys_lock); > + } > > The "else" is not really necessary here because the "if" portion ends > with "return". > Fixed this in the attached patch. > > + memstats_ctx = AllocSetContextCreate((MemoryContext) NULL, > + > "publish_memory_context_statistics", > + > ALLOCSET_SMALL_SIZES); > > The comments do a good job justifying this, but as far as I know it > would be the only instance of this pattern in the entire source tree. > Are we really sure we want to deviate from the idea of having the > memory context tree be a tree? And is it really so bad if the memory > used to report memory contexts is included in the output? > > This was implemented in response to a review suggestion [1]. If required, it can be updated to create one under CurrentMemoryContext. + MemoryStatsDsaArea = > GetNamedDSA("memory_context_statistics_dsa", > + > &found); > ... > + MemoryStatsDsHash = > GetNamedDSHash("memory_context_statistics_dshash", > + > &memctx_dsh_params, &found); > ... > + entry = dshash_find_or_insert(MemoryStatsDsHash, key, &found); > > Like LWLockAcquire, all of this code supposes that there's a > transaction available to manage resource acquisition and release and > to clean up after errors. I doubt that any of this is safe without a > transaction. > Starting a transaction from CFI works for a client backend but not for auxiliary processes. When I try to execute StartTransaction() from a checkpointer process, the following assertion fails, Assert(MyProc->vxid.procNumber == vxid.procNumber); This is because MyProc->vxid.procNumber is not set for auxiliary processes. Please find attached a rebased patch, that fixes a build error reported on the commit-fest app. [1]. PostgreSQL: Re: Prevent an error on attaching/creating a DSM/DSA from an interrupt handler. <https://www.postgresql.org/message-id/594293.1747708165%40sss.pgh.pa.us> Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Robert Haas <robertmhaas@gmail.com> — 2026-01-19T18:02:27Z
On Mon, Jan 19, 2026 at 12:31 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > Starting a transaction from CFI works for a client backend but > not for auxiliary processes. When I try to execute StartTransaction() > from a checkpointer process, the following assertion fails, > Assert(MyProc->vxid.procNumber == vxid.procNumber); > This is because MyProc->vxid.procNumber is not set for auxiliary > processes. Unfortunately, somebody is going to need to think through - and perhaps test - what happens in each individual type of background process -- not just auxiliary processes but also background workers, including but not limited to parallel workers. Some of them have error recovery logic that is similar to transaction cleanup but only covers a subset of things, in which case the question will be whether that logic handles all the kinds of resources that this code might acquire. Some of them may just straight up kill the process if an error occurs, which is fine for this patch as long as it only happens in extreme situations (e.g. OOM). In other words, we don't necessarily need a transaction specifically, but we need whatever form of error recovery is in use in any given process to be appropriate to the code that this patch proposes to execute. For a regular backend, that's a transaction. Also worth noting: I don't think that all backend types actually use CHECK_FOR_INTERRUPTS(). For those that don't, other updates may be needed to make this feature work. -- Robert Haas EDB: http://www.enterprisedb.com
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-02-02T12:28:21Z
Hi Robert, Daniel Please find attached the updated patches which incorporate your feedback. Following are responses to Robert's comments: Unfortunately, somebody is going to need to think through - and > perhaps test - what happens in each individual type of background > process -- not just auxiliary processes but also background workers, > including but not limited to parallel workers. Some of them have error > recovery logic that is similar to transaction cleanup but only covers > a subset of things, in which case the question will be whether that > logic handles all the kinds of resources that this code might acquire. > Some of them may just straight up kill the process if an error occurs, > which is fine for this patch as long as it only happens in extreme > situations (e.g. OOM). > I tested the resource cleanup in case of error, with respect to each of the processes types i.e Auxiliary process/background workers/client backends and below are my findings. The only resources acquired within the ProcessGetMemoryContextInterrupt function are of the kind dsm_resowner_desc through GetNamedDSA and GetNamedDsHash. However, these functions make the resource owner forget these resources when they call dsm_pin_mapping. This is done to prevent them from getting detached at the time of error handling or transaction abort/commit. Because when a dsm resource is released, the associated dsm segment is detached. Since we want the DSM segments to remain mapped until a process exits, they are not added to resource owners. This is done intentionally to prevent repeatedly attaching or detaching the DSM segments, if the same process is queried for statistics multiple times. As no resources are tracked during the execution ProcessGetMemoryContextInterrupt irrespective of its execution within or outside a transaction, the error handling in both the cases does not process resource cleanup unless the process exits. During proc_exit, the cleanup/detach runs via dsm_backend_shutdown(). > + LWLockAcquire(client_keys_lock, LW_SHARED); > > LWLocks normally use NamesLikeThis not names_like_this and are > generally created by just listing them in lwlocklist.h. > Fixed. Also, it's not really safe to acquire an LWLock if there's no > transaction active. If we error afterward, what will release the > LWLock? I verified that when an error occurs outside of a transaction in various types of processes, LWLockReleaseAll() is called during error handling. 1. For auxiliary processes - called within sigsetjmp() from main functions. 2. For client backend - called during process exit as error is converted to "FATAL: terminating connection because protocol synchronization was lost" 3. For a background worker like parallel worker - called during process exit, as the worker is killed on encountering an error. + else > + { > + clientProcNumber = client_keys[MyProcNumber]; > + client_keys[MyProcNumber] = -1; > + LWLockRelease(client_keys_lock); > + } > > The "else" is not really necessary here because the "if" portion ends > with "return Fixed accordingly. > One other point to note here is that if, by some chance, a failure occurs as a result of receiving a memory-context interrupt, it's going to fail the currently-running transaction In the updated version, we make sure that no errors are being thrown by ProcessMemoryContextInterrupt and other functions added by this patch. I have also added comments above the GetNamedDSA and GetNamedDsHash functions to highlight the importance of minimizing errors in these functions, as they are called from CFI. This way we try to prevent any non-critical errors from being thrown. Following are responses to Daniel's comments: > > Attached is the updated patch that addresses this. > > A few small comments on v47: > > +static const char *ContextTypeToString(NodeTag type); > I think context_type_to_string() would be a better name on this internal > function to model it closer to the existing int_list_to_array(). > Personally I > would also place it before its first use to avoid the prototype, but that's > personal preference. > > Changed accordingly, also moved compute_context_path() so it appears before its use to avoid the prototype. > > +static void > +memstats_dsa_cleanup(char *key) > This function warrants a documentation comment describing when it should be > used safely. > Added a comment. > > > + memstats_dsa_cleanup(key); > + memstats_client_key_reset(procNumber); > + ConditionVariableCancelSleep(); > + PG_RETURN_NULL(); > I think we should notify the user in these two timeout cases, why not > adding an > ereport(NOTICE, "request for memory context statistics timed out")); or > something with a better wording than that. > > Added this. > + Size sz = 0; > + Size TotalProcs = 0; > + > + TotalProcs = add_size(TotalProcs, NUM_AUXILIARY_PROCS); > + TotalProcs = add_size(TotalProcs, MaxBackends); > + sz = add_size(sz, mul_size(TotalProcs, sizeof(int))); > + > + return sz > As we discussed off-list, the call to add_size() call can be omitted as it > won't affect the calculation. > Fixed accordingly. > > > +# Copyright (c) 2025, PostgreSQL Global Development Group > Here, and possibly elsewhere, it should say 2026 instead I think. > > > Fixed. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-02-10T00:03:37Z
Hi, Please find attached rebased and updated patches that include a few fixes suggested by Daniel. Fixes include 1. Changed LW_SHARED to LW_EXCLUSIVE when resetting client_keys. 2. Replaced calls to MemoryContextReset with MemoryContextDelete since a new memory context is created each time. 3. Removed leftover code from earlier versions in ipci.c 4. Fix the TAP test to compare strings correctly. 5. Added more comments in the TAP test. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Chao Li <li.evan.chao@gmail.com> — 2026-02-10T04:50:53Z
> On Feb 10, 2026, at 08:03, Rahila Syed <rahilasyed90@gmail.com> wrote: > > Hi, > > Please find attached rebased and updated patches that include a few fixes > suggested by Daniel. > > Fixes include > 1. Changed LW_SHARED to LW_EXCLUSIVE when resetting client_keys. > 2. Replaced calls to MemoryContextReset with MemoryContextDelete since a > new memory context is created each time. > 3. Removed leftover code from earlier versions in ipci.c > 4. Fix the TAP test to compare strings correctly. > 5. Added more comments in the TAP test. > > Thank you, > Rahila Syed > <v50-0001-Add-function-to-report-memory-context-statistics.patch><v50-0002-Test-module-to-test-memory-context-reporting-wit.patch> Hi Rahila, Thanks for the patch. I applied v50 locally and played with it a little bit then I reviewed v50. Basically I think it’s convenient to query a server process’ memory usage via a SQL statement. Here comes my review comments. 1 - 0001- mcxt.c ``` +/* + * MemoryContextStatsCounter + * + * Accumulate statistics counts into *totals. totals should not be NULL. + * This involves a non-recursive tree traversal. + */ +void +MemoryContextStatsCounter(MemoryContext context, MemoryContextCounters *totals, + int *num_contexts) +{ + int ichild = 1; + + *num_contexts = 0; + context->methods->stats(context, NULL, NULL, totals, false); ``` As the header comment says that “totals should not be NULL”, maybe add Assert(total!=NULL) to ensure that. 2 - 0001- mcxt.c ``` + *num_contexts = 0; + context->methods->stats(context, NULL, NULL, totals, false); + + for (MemoryContext curr = context->firstchild; + curr != NULL; + curr = MemoryContextTraverseNext(curr, context)) + { + curr->methods->stats(curr, NULL, NULL, totals, false); + ichild++; + } + + /* + * Add the count of all the children contexts which are traversed + * including the parent. + */ + *num_contexts = *num_contexts + ichild; +} ``` *num_contexts is only initialized to 0, then *num_contexts = *num_contexts + ichild. Looks like the the initialization is unnecessary, and the assignment can just be *num_contexts = ichild. 3 - 0001 - mcxtfuncs.c ``` +typedef struct MemoryStatsEntry +{ + char name[MEMORY_CONTEXT_NAME_SHMEM_SIZE]; + char ident[MEMORY_CONTEXT_IDENT_SHMEM_SIZE]; + int path[100]; ``` You already defined a constant MAX_PATH_DISPLAY_LENGTH below, would it make sense to pull up the macro definition and use it for “path” definition? 4 - 0001 - mctxfuncs.c ``` +/* + * Per backend dynamic shared hash entry for memory context statistics + * reporting. + */ +typedef struct MemoryStatsDSHashEntry +{ + char key[64]; + ConditionVariable memcxt_cv; + bool stats_written; + int target_server_id; + int total_stats; + bool summary; + dsa_pointer memstats_dsa_pointer; +} MemoryStatsDSHashEntry; + +static const dshash_parameters memctx_dsh_params = { + offsetof(MemoryStatsDSHashEntry, memcxt_cv), + sizeof(MemoryStatsDSHashEntry), + dshash_strcmp, + dshash_strhash, + dshash_strcpy +}; ``` I wonder why we cannot just use the integer ProcNumber as the hash key? I think dshash fully supports fixed-size binary keys. We can use dshash_memcmp and dshash_memhash for compare and hash functions. 5 - 0001 - mctxfuncs.c ``` +static List * +compute_context_path(MemoryContext c, HTAB *context_id_lookup) +{ + bool found; + List *path = NIL; + MemoryContext cur_context; + + for (cur_context = c; cur_context != NULL; cur_context = cur_context->parent) + { + MemoryContextId *cur_entry; + + cur_entry = hash_search(context_id_lookup, &cur_context, HASH_FIND, &found); + + if (!found) + { + elog(NOTICE, "hash table corrupted, can't construct path value"); + return NIL; + } + path = lcons_int(cur_entry->context_id, path); + } + return path; +} ``` As in the hash entry, path is a 100-elements array, would it make sense to also limit the path list to not append more than 100 items in this function? 6 - 0001 - mctxfuncs.c ``` + entry->stats_written = true; + dshash_release_lock(MemoryStatsDsHash, entry); + hash_destroy(context_id_lookup); + + MemoryContextSwitchTo(oldcontext); + MemoryContextDelete(memstats_ctx); + /* Notify waiting client backend and return */ + ConditionVariableSignal(&entry->memcxt_cv); +} ``` This looks like a race condition. ConditionVariableSignal(&entry->memcxt_cv); is called after the lock is released. So, there is a chance that a process has been terminated, and its before_shmem_exit callback acquired the entry lock and deleted the entry from the hash. So, I think we should send the signal before releasing the lock. 7 - 0001 - mctxfuncs.c ``` + /* + * Wait for MEMORY_STATS_MAX_TIMEOUT. If no statistics are available + * within the allowed time then return NULL. The timer is defined in + * milliseconds since that's what the condition variable sleep uses. + */ + if (ConditionVariableTimedSleep(&entry->memcxt_cv, + (MEMORY_STATS_MAX_TIMEOUT * 1000), + WAIT_EVENT_MEM_CXT_PUBLISH)) ``` For the wait loop, if the condition wakes up for some other reason, it will loop back and wait for the other 5 seconds, then total wait period will exceed 5 seconds, maybe 9 seconds. This is not a big deal, but the doc explicitly says “If the process does not respond with memory contexts statistics in 5 seconds”, so that behavior might be inconsistent with the doc. I think we can calculate remaining time and pass remaining milliseconds into ConditionVariableTimedSleep. 8 - 0001 - mctxfuncs.c ``` + /* + * Wait for MEMORY_STATS_MAX_TIMEOUT. If no statistics are available + * within the allowed time then return NULL. The timer is defined in + * milliseconds since that's what the condition variable sleep uses. + */ + if (ConditionVariableTimedSleep(&entry->memcxt_cv, + (MEMORY_STATS_MAX_TIMEOUT * 1000), + WAIT_EVENT_MEM_CXT_PUBLISH)) + { + /* Timeout has expired, return NULL */ + memstats_dsa_cleanup(key); + memstats_client_key_reset(procNumber); + ConditionVariableCancelSleep(); + ereport(NOTICE, + errmsg("request for memory context statistics for PID %d timed out", + pid)); + PG_RETURN_NULL(); + } + entry = dshash_find_or_insert(MemoryStatsDsHash, key, &found); + Assert(found); ``` After ConditionVariableTimedSleep, rather than Assert(found), I think we should check if (!found). Because it waits for 5 seconds without holding the lock, the process may terminate and delete the entry during the period. 9 - 0001 - proc.c ``` diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 8560a903bc8..f68583aa820 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -51,6 +51,7 @@ #include "storage/procsignal.h" #include "storage/spin.h" #include "storage/standby.h" +#include "utils/memutils.h" #include "utils/timeout.h" #include "utils/timestamp.h" ``` This file is sololy added an include without any other change, that seems unneeded. I tried to remove this include, and the build still passed. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/ -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-02-15T12:40:06Z
Hi Chao, Thank you for testing and reviewing. Please find attached the updated patches that incorporate your review comments. > > As the header comment says that “totals should not be NULL”, maybe add > Assert(total!=NULL) to ensure that. > > Added this. > + /* > + * Add the count of all the children contexts which are traversed > + * including the parent. > + */ > + *num_contexts = *num_contexts + ichild; > +} > ``` > > *num_contexts is only initialized to 0, then *num_contexts = *num_contexts > + ichild. Looks like the the initialization is unnecessary, and the > assignment can just be *num_contexts = ichild. > Fixed accordingly. > > + int path[100]; > ``` > > You already defined a constant MAX_PATH_DISPLAY_LENGTH below, would it > make sense to pull up the macro definition and use it for “path” definition? > > Makes sense, done in the attached patch. > + > +static const dshash_parameters memctx_dsh_params = { > + offsetof(MemoryStatsDSHashEntry, memcxt_cv), > + sizeof(MemoryStatsDSHashEntry), > + dshash_strcmp, > + dshash_strhash, > + dshash_strcpy > +}; > ``` > > I wonder why we cannot just use the integer ProcNumber as the hash key? I > think dshash fully supports fixed-size binary keys. We can use > dshash_memcmp and dshash_memhash for compare and hash functions. > I have incorporated your suggestion in the updated patch—using integer keys will be more efficient for memory usage and will help avoid conversions between data types. Thank you for the suggestion. +static List * > +compute_context_path(MemoryContext c, HTAB *context_id_lookup) > +{ > As in the hash entry, path is a 100-elements array, would it make sense to > also limit the path list to not append more than 100 items in this function? > We need to calculate the full path list to report the number of levels in the tree. We could choose to return the number of levels separately from the path list and limit the path list to no more than 100 levels, but we would still have to iterate through all the levels. Returning a single variable that captures both levels and path keeps the function signature simple. Kindly let me know your view. > + dshash_release_lock(MemoryStatsDsHash, entry); > + hash_destroy(context_id_lookup); > + > + MemoryContextSwitchTo(oldcontext); > + MemoryContextDelete(memstats_ctx); > + /* Notify waiting client backend and return */ > + ConditionVariableSignal(&entry->memcxt_cv); > +} > ``` > > This looks like a race condition. > ConditionVariableSignal(&entry->memcxt_cv); is called after the lock is > released. So, there is a chance that a process has been terminated, and its > before_shmem_exit callback acquired the entry lock and deleted the entry > from the hash. So, I think we should send the signal before releasing the > lock. > > Good catch. Fixed accordingly. > > + */ > + if (ConditionVariableTimedSleep(&entry->memcxt_cv, > + > (MEMORY_STATS_MAX_TIMEOUT * 1000), > + > WAIT_EVENT_MEM_CXT_PUBLISH)) > ``` > > For the wait loop, if the condition wakes up for some other reason, it > will loop back and wait for the other 5 seconds, then total wait period > will exceed 5 seconds, maybe 9 seconds. This is not a big deal, but the doc > explicitly says “If the process does not respond with memory contexts > statistics in 5 seconds”, so that behavior might be inconsistent with the > doc. > > I think we can calculate remaining time and pass remaining milliseconds > into ConditionVariableTimedSleep. > > The following check in the beginning of the wait loop is intended to avoid this. /* Return if we have already exceeded the timeout */ if (elapsed_time >= MEMORY_STATS_MAX_TIMEOUT * 1000) { memstats_dsa_cleanup(client_procno); memstats_client_key_reset(server_procno); ConditionVariableCancelSleep(); ereport(NOTICE, errmsg("request for memory context statistics for PID %d timed out", pid)); PG_RETURN_NULL(); } It may still exceed by an amount less than 5 seconds, but it will always be less than 2 * MEMORY_STATS_MAX_TIMEOUT, because we track the elapsed time and exit after the condition above is satisfied. Maybe we should edit the docs to reflect this. After ConditionVariableTimedSleep, rather than Assert(found), I think we > should check if (!found). Because it waits for 5 seconds without holding > the lock, the process may terminate and delete the entry during the period. > > The client backend deletes the entry only upon exiting; no other process can remove the entry. Therefore, it is not possible for the client backend to execute this Assert after the entry has been deleted. > > This file is sololy added an include without any other change, that seems > unneeded. I tried to remove this include, and the build still passed. > > Fixed. Thank you for noticing this. It looks like a remnant from earlier versions. Thank you, Rahila Syed -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-02-20T23:13:17Z
Hi, Please find attached updated and rebased patches which incorporate code fixes suggested off-list by Daniel These changes include a fix to free dsa memory in one of the exit paths from the client-side function along with several documentation and comment improvements. > I think we can calculate remaining time and pass remaining milliseconds > into ConditionVariableTimedSleep. > > This comment by Chao Li was also addressed as part of Daniel's review fixes. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2026-02-23T09:58:39Z
> On 21 Feb 2026, at 00:13, Rahila Syed <rahilasyed90@gmail.com> wrote: > Please find attached updated and rebased patches which incorporate > code fixes suggested off-list by Daniel > > These changes include a fix to free dsa memory in one of the > exit paths from the client-side function along with several documentation > and comment improvements. Thanks for the new version, I think this is looking pretty good now. The attached .diff.txt (renamed to keep the CFBot from grabbing it) has mostly whitespace fixes, but also a few small things which are discussed below: + <para> + After receiving memory context statistics from the target process, it + returns the results as one row per context. If all the contexts don't Re-reading the docs I think the para's are in the wrong order, as the sentence above makes little sense to be after the output has already been discussed. I think this needs to go earlier, see the attached .diff.txt for what I am proposing. + /* + * The client process should have created the required DSA and DSHash + * table. Here we just attach to those. + */ + if (MemoryStatsDsaArea == NULL) + MemoryStatsDsaArea = GetNamedDSA("memory_context_statistics_dsa", + &found); + + if (MemoryStatsDsHash == NULL) + MemoryStatsDsHash = GetNamedDSHash("memory_context_statistics_dshash", + &memctx_dsh_params, &found); I think this needs an expanded comment discussing why there is no need the check the DSA and DShash after the GetNamed_ functions, since they can be NULL going in to this. The attached .diff.txt has a proposal along with an Assertion to keep future changes of the API from risking subtly breaking this assumption. +REGRESS = test_memcontext_reporting Since the test module doesn't contain any pg_regress style .sql/.out tests so this line in the Makefile cause a test failure when running with Autoconf. +$node->append_conf( + 'postgresql.conf', + qq[ +max_connections = 100 +log_statement = none +restart_after_crash = false +]); Why do we need to set max_connections for this test? +#Server should have thrown error +$node->psql( + 'postgres', + qq(select pg_get_process_memory_contexts($pid, true);), + stderr => \$psql_err); This test doesn't validate that the server actually errored does it? (There is no proposed fix in the attached.) -- Daniel Gustafsson -
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-02-24T11:57:17Z
Hi Daniel, Thank you for the review. All the changes suggested in the v52comments.diff are incorporated in the attached patches. +#Server should have thrown error > +$node->psql( > + 'postgres', > + qq(select pg_get_process_memory_contexts($pid, true);), > + stderr => \$psql_err); > > This test doesn't validate that the server actually errored does it? > (There is > no proposed fix in the attached.) > > This has been fixed by adding a check for the error returned by the above command. While at it, I also added another crash test to the file, This is similar to the existing test for a client backend crash, but in this scenario, it crashes the server process instead. Thank you, Rahila Syed
-
Re: Enhancing Memory Context Statistics Reporting
Rahila Syed <rahilasyed90@gmail.com> — 2026-03-05T22:31:09Z
Hi All, The email below provides a summary of the changes implemented since the previous update on December 18th. 1. I tested and verified that when ProcessGetMemoryContextInterrupt() returns with error from processes that don't start a transaction, it doesn't affect system integrity. In auxiliary processes like ioworker, archiver, and wal_receiver, the ERROR causes the process to exit, which ensures proper resource and lock release. In other cases, cleanup and release are managed by error recovery logic, allowing processes to continue running smoothly. 2. I verified that the only resources acquired in ProcessGetMemoryContextInterrupt are DSMs, which remain attached until the process exits. During proc_exit, the release/detach of DSM runs via dsm_backend_shutdown(). 3.The error reporting level in the interrupt handler has been adjusted from ERROR to NOTICE to prevent unnecessary errors from being raised in interrupted transactions due to minor issues. 4. Aborted transaction check has been added to allow returning without processing the interrupt if it is called from an aborted transaction. 5. DSHash key type has been changed from string to integer, now using procNumber directly. This eliminates string formatting overhead. 6. Timeout messaging has been changed from silently returning empty row to issuing a NOTICE and returning an empty row. 7. Documentation has been to GetNamedDSA() and GetNamedDSHash(), warning that errors thrown from these functions can propagate to any transaction calling the CFI function 8. Subtracted elapsed time ((MEMORY_STATS_MAX_TIMEOUT * 1000) - elapsed_time) from timeout passed to ConditionVariableTimedSleep so the total wait doesn't exceed the intended timeout in case of spurious wakeups. 9. Fixed a race condition bug by ensuring ConditionVariableSignal is called before dshash_release_lock Additionally, I included couple of assertions, renamed variables and functions to enhance clarity and consistency with the existing style, and made improvements to the documentation and tests. Please find attached updated and rebased patches. Thank you, Rahila Syed On Tue, Feb 24, 2026 at 5:27 PM Rahila Syed <rahilasyed90@gmail.com> wrote: > Hi Daniel, > > Thank you for the review. All the changes suggested in the v52comments.diff > are incorporated in the attached patches. > > +#Server should have thrown error >> +$node->psql( >> + 'postgres', >> + qq(select pg_get_process_memory_contexts($pid, true);), >> + stderr => \$psql_err); >> >> This test doesn't validate that the server actually errored does it? >> (There is >> no proposed fix in the attached.) >> >> > This has been fixed by adding a check for the error returned by the above > command. > While at it, I also added another crash test to the file, This is similar > to the existing > test for a client backend crash, but in this scenario, it crashes the > server process > instead. > > Thank you, > Rahila Syed >
-
Re: Enhancing Memory Context Statistics Reporting
Daniel Gustafsson <daniel@yesql.se> — 2026-03-12T13:44:28Z
> On 5 Mar 2026, at 23:31, Rahila Syed <rahilasyed90@gmail.com> wrote: > The email below provides a summary of the changes implemented since the previous > update on December 18th. Thanks for the summary! > Please find attached updated and rebased patches. The rebase needed another rebase which I've attached as v55. I've also fixed up some issues with the meson buildfiles for the tests as well polished a bit here and there. I've also squashed 0001 and 0002 as well as reworded the commit message. I will continue to test this patch with different process types etc, but unless I find something which has escaped detection thus far I intend to go ahead with this patch before the freeze. -- Daniel Gustafsson