{"id":467572,"date":"2025-07-18T22:50:20","date_gmt":"2025-07-18T22:50:20","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=467572"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=467572","title":{"rendered":"<span>Redundant statistics slow down your Postgres? Try sampling in pg_stat_statements<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>pg_stat_statements\u00a0is the standard PostgreSQL extension used to track query statistics: number of executions, total and average execution time, number of returned rows, and other metrics. This information allows to analyze query behavior over time, identify problem areas, and make informed optimization decisions. However, in systems with high contention,\u00a0pg_stat_statements\u00a0itself can become a bottleneck and cause performance drops. In this article, we will analyze in which scenarios the extension becomes a source of problems, how sampling is structured, and in which cases its application can reduce overhead.<\/p>\n<h2>Issue<\/h2>\n<p>Let&#8217;s briefly recall how\u00a0<code>pg_stat_statements<\/code>\u00a0is structured in order to understand when and why the extension may slow down performance. This will help understand which mechanisms within the extension can become a bottleneck under high load.<br \/>The key data structure in\u00a0<code>pg_stat_statements<\/code>\u00a0is a hash table. Each bucket in it contains execution metrics for a specific query. The key for this table is formed based on four parameters:<\/p>\n<ul>\n<li>\n<p><code>queryid<\/code>\u00a0\u2014 unique identifier of the normalized query;<\/p>\n<\/li>\n<li>\n<p>User\u00a0<code>OID<\/code>;<\/p>\n<\/li>\n<li>\n<p>Database\u00a0<code>OID<\/code>;<\/p>\n<\/li>\n<li>\n<p>a\u00a0<code>toplevel<\/code>\u00a0flag indicating whether the query is top-level, meaning it&#8217;s not nested within any internal function or subquery.<\/p>\n<\/li>\n<\/ul>\n<p>The hash table finds or creates the corresponding entry using this key and updates the collected query metrics.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a14\/96a\/b76\/a1496ab76536f9c17d352d20bda99731.png\" width=\"621\" height=\"552\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/a14\/96a\/b76\/a1496ab76536f9c17d352d20bda99731.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a14\/96a\/b76\/a1496ab76536f9c17d352d20bda99731.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>Upon receiving a request,\u00a0<code>pg_stat_statements<\/code>\u00a0executes the following sequence of operations:<\/p>\n<ol>\n<li>\n<p>Search for the bucket: a shared LWLock is acquired on \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043f\u0440\u0435\u0434\u043b\u043e\u0433the hash table and it is searched through by key;<\/p>\n<\/li>\n<li>\n<p>Normalization (optional): if there is no suitable bucket, the query is pre-normalized: literals are replaced with placeholders like $1, $2, etc.;<\/p>\n<\/li>\n<li>\n<p>Creating a new bucket: the lock level of LWLock is raised to\u00a0<code>exclusive<\/code>\u00a0and a new bucket is created;<\/p>\n<\/li>\n<li>\n<p>Recording query information in the bucket: to update the query metrics in the bucket, the SpinLock of that bucket is acquired. Then the locks on SpinLock and LWLock are released.\u00a0<\/p>\n<\/li>\n<\/ol>\n<p>These locking operations with a large number of unique queries or high contention in\u00a0<code>pg_stat_statements<\/code>\u00a0become a bottleneck. Let&#8217;s review this using a scenario where all SQL queries are unique from the perspective of\u00a0<code>pg_stat_statements<\/code>. A machine with 48 CPUs can reproduce such a load. To ensure that the queries are unique, we will create 1000 similar tables with different names:<\/p>\n<blockquote>\n<p><strong>init_script.sql<\/strong><\/p>\n<\/blockquote>\n<pre><code class=\"sql\">DO $$  DECLARE  \u00a0\u00a0\u00a0\u00a0i INT;  BEGIN  \u00a0\u00a0\u00a0\u00a0FOR i IN 1..1000 LOOP  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0EXECUTE format('CREATE TABLE table_%s (id INT PRIMARY KEY, value TEXT);', i);  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0EXECUTE format('INSERT INTO table_%s (id, value) VALUES (1, ''test'');', i);  \u00a0\u00a0\u00a0\u00a0END LOOP;  END;  $$;<\/code><\/pre>\n<p>\u00a0Then, using the built-in random number generator in pgbench (<strong>pgbench_script.sq<\/strong>l), we will create queries on these tables so that each of them is different and falls into a new bucket of the\u00a0<code>pg_stat_statements<\/code>\u00a0hash table.<\/p>\n<blockquote>\n<p><strong>pgbench_script.sql<\/strong><\/p>\n<\/blockquote>\n<pre><code class=\"sql\">\\set\u00a0table1_id random(1, 1000)  \\set\u00a0table2_id random(1, 1000)  \\set\u00a0table3_id random(1, 1000)  \u00a0  SELECT\u00a0t1.value\u00a0AS\u00a0value1, t2.value\u00a0AS\u00a0value2, t3.value\u00a0AS\u00a0value3  FROM\u00a0table_:table1_id t1  JOIN\u00a0table_:table2_id t2\u00a0ON\u00a0t1.id = t2.id  JOIN\u00a0table_:table3_id t3\u00a0ON\u00a0t2.id = t3.id  WHERE\u00a0t1.id = 1\u00a0AND\u00a0t2.id = 1\u00a0AND\u00a0t3.id = 1;<\/code><\/pre>\n<p>To visually see what causes the performance drop, let&#8217;s call pg_stat_activity once a second while the benchmark is running. The results of each query will be written in \/tmp\/waits file:<\/p>\n<blockquote>\n<p><strong>waits.sql<\/strong><\/p>\n<\/blockquote>\n<pre><code class=\"sql\">\\o \/tmp\/waits  select\u00a0'OUT',\u00a0COALESCE(wait_event,\u00a0'None') wait_event,\u00a0COALESCE(wait_event_type,\u00a0'No wait') wait_event_type\u00a0from\u00a0\u00a0pg_stat_activity\u00a0where\u00a0state =\u00a0'active';  \\watch 1<\/code><\/pre>\n<p>After the benchmark is completed, we group all types of delays and count how many times each of them occurred:<\/p>\n<pre><code class=\"sql\">cat \/tmp\/waits | grep\u00a0OUT\u00a0| awk\u00a0'{print $2 \"|\" $3}'\u00a0FS=\"|\"\u00a0| sort | uniq -c | sort -n -r -k1<\/code><\/pre>\n<p>Then we run the benchmark, compare the system performance with the pg_stat_statements extension enabled and disabled, and display the reasons for performance drop. To do this, we will use standard pgbench utility:<\/p>\n<ul>\n<li>\n<p>Number of clients (-c): 48 &#8212; according to the number of CPUs;<\/p>\n<\/li>\n<li>\n<p>Threads (-j): 20 &#8212; limits contention at the OS level to avoid overloading the CPU and context switches;<\/p>\n<\/li>\n<li>\n<p>Duration (-T): 120 seconds;<\/p>\n<\/li>\n<li>\n<p>Script: run pgbench_script.sql (-f pgbench_script.sql);<\/p>\n<\/li>\n<li>\n<p>Metrics: total number of delays and final TPS.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"pgsql\">pgbench -c48 -j20 -T120 -f pgbench_script.sql --progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;<\/code><\/pre>\n<p>Now lets combine all these actions in one script:\u00a0<\/p>\n<pre><code class=\"sql\">RESULTS=\"\/tmp\/results\"  \u00a0  rm -rfv $RESULTS  nohup pgbench -c48 -j20 -T120 -f pgbench_script.sql --progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;  timeout 125 psql -f waits.sql  echo \" count | wait_event | wait_event_type\" &gt;&gt;$RESULTS  echo \"--------------------------------------\" &gt;&gt;$RESULTS  cat \/tmp\/waits | grep OUT | awk '{print $2 \"|\" $3}' FS=\"|\" | sort | uniq -c | sort -n -r -k1 &gt;&gt;$RESULTS  cat $RESULTS  rm -rfv \/tmp\/waits<\/code><\/pre>\n<p>We will get the following results:<\/p>\n<pre><code class=\"sql\"># With pg_stat_statements off  tps = 237 437.104223 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a02922\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a0\u00a0918\u00a0 ClientRead\u00a0\u00a0\u00a0 Client  \u00a0  # With pg_stat_statements on  tps =\u00a0 32 112.129029 (without initial connection time)  \u00a0count |\u00a0\u00a0\u00a0\u00a0 wait_event\u00a0\u00a0\u00a0\u00a0 | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a04703\u00a0 pg_stat_statements\u00a0\u00a0\u00a0\u00a0\u00a0 LWLock  \u00a0\u00a0\u00a0\u00a0884\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a0\u00a0143\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client<\/code><\/pre>\n<p>As we see here, with a large number of unique queries, enabled\u00a0<code>pg_stat_statements\u00a0<\/code>can significantly reduce performance \u2014 even leading to fold drop in TPS. And this all is caused by\u00a0<strong>frequent &#8216;exclusive&#8217; LWLock <\/strong><a href=\"http:\/\/acquisition.Now\" rel=\"noopener noreferrer nofollow\"><strong>acquisition.<\/strong>Now<\/a> let&#8217;s review another scenario \u2014 with a large number of similar queries. Here we will need a more powerful machine with 192 CPUs. For the test, we will again use a script that periodically checks pg_stat_activity, but this time we will create the load using the same query executed through pgbench -M prepared -S with 192 clients:<\/p>\n<pre><code class=\"sql\">pgbench -c192 -j20 -T120 -M prepared -S --progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;<\/code><\/pre>\n<p>When we run this benchmark&#8230;<\/p>\n<pre><code class=\"sql\">RESULTS=\"\/tmp\/results\"  \u00a0  rm\u00a0-rfv $RESULTS  pgbench -i -s500  nohup\u00a0pgbench -c192 -j20 -T120 -M prepared -S --progress 10 |\u00a0grep\u00a0\"tps = \"\u00a02&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;  timeout 125 psql -f waits.sql  echo\u00a0\" count | wait_event | wait_event_type\"\u00a0&gt;&gt;$RESULTS  echo\u00a0\"--------------------------------------\"\u00a0&gt;&gt;$RESULTS  cat\u00a0\/tmp\/waits\u00a0|\u00a0grep\u00a0OUT |\u00a0awk\u00a0'{print $2}'\u00a0FS=\"|\"\u00a0|\u00a0sort\u00a0|\u00a0uniq\u00a0-c |\u00a0sort\u00a0-n -r -k1 &gt;&gt;$RESULTS  cat\u00a0$RESULTS  rm\u00a0-rfv\u00a0\/tmp\/waits<\/code><\/pre>\n<p>..we get the following results:<\/p>\n<pre><code class=\"sql\"># Results with pg_stat_statements off  tps = 1 015 425.438193 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a013201\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a03482\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client  \u00a0  # Results with pg_stat_statements on  tps =\u00a0\u00a0 484 338.163894 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a011 214\u00a0 SpinDelay\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Timeout  \u00a0\u00a0\u00a09481\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a0\u00a0930\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client<\/code><\/pre>\n<p>\u00a0If we try to reproduce this on a machine with 48 CPUs (i.e., with 48 users)&#8230;<\/p>\n<pre><code class=\"sql\">pgbench -c48 -j20 -T120 -M prepared -S\u00a0--progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;<\/code><\/pre>\n<p>&#8230;the performance issues will stay well within the bounds of statistical error.<\/p>\n<pre><code class=\"sql\"># Results with pg_stat_statements off  tps = 625 335.965464 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a0\u00a0979\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client  \u00a0\u00a0\u00a0\u00a0927\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0  # Results with pg_stat_statements on  tps = 611 708.477697 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a01000\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client  \u00a0\u00a0\u00a0\u00a0978\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait<\/code><\/pre>\n<p>This indicates that the impact of\u00a0<code>pg_stat_statements<\/code>\u00a0when working with duplicate queries becomes noticeable only at very high levels of parallelism. The main reason is the contention for the same entry in the hash table, which is accompanied by\u00a0<strong>frequent SpinLock acquisitions when updating query metrics in the hash table bucket<\/strong>. When many threads simultaneously execute the same query, they try to update the same structure \u2014 increment call counters, execution time, and other metrics. This leads to severe contention for SpinLock, which under high load causes delays and reduces TPS.<\/p>\n<h3>What is sampling?<\/h3>\n<p>Query sampling is a method of uniform filtering, where only part of all queries is included in the sample. In the context of\u00a0<code>pg_stat_statements<\/code>, this means that metric information is recorded not for every executed query, but only for some of them, with equal probability. A similar approach is used in PostgreSQL in other places: log_transaction_sample_rate and log_statement_sample_rate are used to reduce log volume, as well as in\u00a0<code>auto_explain.sample_rate<\/code> and <code>pg_store_plans.sample_rate<\/code>. In Tantor Postgres 17.5, a corresponding setting was added to pg_stat_statements \u2014 the GUC parameter\u00a0<code><strong>pg_stat_statements.sample_rate<\/strong><\/code>, which allows you to set fraction of queries that is tracked in the extension&#8217;s statistics. The value of the parameter (from 0 to 1) determines what fraction of queries will be tracked in pg_stat_statements. A query will be sampled if the following condition is met:<\/p>\n<blockquote>\n<p>Sampling<\/p>\n<\/blockquote>\n<pre><code class=\"sql\">is_query_sampled = pgss_sample_rate != 0.0 &amp;&amp;  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(pgss_sample_rate == 1.0 ||  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0pg_prng_double(&amp;pg_global_prng_state) &lt;= pgss_sample_rate);<\/code><\/pre>\n<p>Since the number of queries will be very large, using this inequation allows filtering only the specified fraction of the queries. The sampling method has one significant drawback: not all queries are tracked in\u00a0<code>pg_stat_statements<\/code>. This impacts the completeness of the collected information, especially during debugging or analyzing rare but problematic queries. On the other hand, since only a part of the queries is tracked, the load on locks is reduced, and consequently, the overall system performance is improved.<\/p>\n<h3>Limitations of the sampling<\/h3>\n<p>If queries are sampled at the stage of adding a new bucket to the hash table, thereby unloading the LWLock, there is a risk of losing a valuable query hat would offer crucial information if tracked in\u00a0<code>pg_stat_statements<\/code>. Moreover, even if sampling did take care of the performance problem, there is still the issue of security and correctness of query storage. The fact is that adding a bucket to the hash table can occur both before and after the query execution. But the structure required for normalizing the query is relayed only\u00a0<strong>before the query execution<\/strong>, namely at the parsing stage, when we need to create a structure to sore literals for normalization.\u00a0If at this stage\u00a0<code>pg_stat_statements<\/code>\u00a0decides not to save the query due to sampling, but then (after execution) still tries to add it to the hash table, the query will be recorded in its original (non-normalized) form. This may lead to the leakage of sensitive information in pg_stat_statements (for example, passwords or personal data in literals of a SQL query). Therefore, sampling during query parsing is unacceptable: it can violate security requirements.<br \/>Nevertheless, both the PostgreSQL community and the developers at Tantor Labs are trying to solve the problem of a large number of unique queries in a different way \u2014 by merging similar queries under one queryid. This reduces the number of unique entries in the hash table and, accordingly, decreases the frequency of its locking. The community has already merged the following queries into one QueryId:<\/p>\n<ul>\n<li>\n<p>In\u00a0<a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/dc68515968e80d75f8106d0df05da346be537628\" rel=\"noopener noreferrer nofollow\">SET commands<\/a>;<\/p>\n<\/li>\n<li>\n<p>Identical\u00a0<a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/9fbd53dea5d513a78ca04834101ca1aa73b63e59\" rel=\"noopener noreferrer nofollow\">queries with different lengths of IN<\/a>(&#8230;);<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/docs.tantorlabs.ru\/tdb\/ru\/17_5\/se\/differences.html#:~:text=%D0%98%D0%BD%D1%82%D0%B5%D0%BB%D0%BB%D0%B5%D0%BA%D1%82%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D0%B0%D0%B3%D1%80%D0%B5%D0%B3%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%3A%20%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0%20%D1%82%D0%B5%D0%BF%D0%B5%D1%80%D1%8C%20%D0%BC%D0%B0%D1%81%D0%BA%D0%B8%D1%80%D1%83%D0%B5%D1%82%20%D0%B8%D0%BC%D0%B5%D0%BD%D0%B0%20%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D1%85%20%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%20%D0%B8%20%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%BE%D0%B2%2C%20%D1%87%D1%82%D0%BE%20%D0%BF%D0%BE%D0%B7%D0%B2%D0%BE%D0%BB%D1%8F%D0%B5%D1%82%20%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5%20%D1%82%D0%BE%D1%87%D0%BD%D0%BE%20%D0%B0%D0%B3%D1%80%D0%B5%D0%B3%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%81%D1%82%D0%B8%D0%BA%D1%83%20%D0%BF%D0%BE%20%D0%BE%D0%B4%D0%BD%D0%BE%D1%82%D0%B8%D0%BF%D0%BD%D1%8B%D0%BC%20%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0%D0%BC%20%D1%81%20%D1%80%D0%B0%D0%B7%D0%BD%D1%8B%D0%BC%D0%B8%20%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D0%B0%D0%BC%D0%B8\" rel=\"noopener noreferrer nofollow\">Temporary tables<\/a>.<\/p>\n<\/li>\n<\/ul>\n<p>In scenarios where the load on SpinLock becomes bottleneck \u2014 for example, when the same entry in a hash table is updated frequently \u2014 sampling can be quite effective. Since SpinLock protects only a single bucket, fewer calls to it (by skipping some queries) reduces contention between threads and thus improves overall performance.<\/p>\n<h3>Sampling results<\/h3>\n<p>Let&#8217;s review the above scenario with a powerful machine of 192 CPUs. The same <code>waits.sql<\/code> scripts and enabled <code>pg_stat_statements<\/code> are used for the test. Now let&#8217;s run a benchmark to assess the impact of the\u00a0<code>pg_stat_statements.sample_rate<\/code>\u00a0on performance and the nature of waits. We run a loop over five\u00a0<code>sample_rate<\/code>\u00a0values:\u00a0<code>1, 0.75, 0.5, 0.25<\/code>, and\u00a0<code>0<\/code>. For each value, we run a load testing using\u00a0<code>pgbench<\/code>:<\/p>\n<blockquote>\n<p><a href=\"http:\/\/benchmark.sh\" rel=\"noopener noreferrer nofollow\"><strong>benchmark.sh<\/strong><\/a><\/p>\n<\/blockquote>\n<pre><code class=\"sql\">CONNECTIONS=192  RESULTS=\"\/tmp\/results\"  pgbench -i -s500  \u00a0  rm\u00a0-rfv $RESULTS  for\u00a0i\u00a0in\u00a01 .75 .5 .25 0  do  \u00a0\u00a0psql -c\u00a0\"alter system set pg_stat_statements.sample_rate = ${i};\"\u00a02&gt;\/dev\/null\u00a0&gt;\/dev\/null  \u00a0\u00a0psql -c\u00a0\"select pg_reload_conf();\"\u00a02&gt;\/dev\/null\u00a0&gt;\/dev\/null  \u00a0\u00a0psql -c\u00a0\"show pg_stat_statements.sample_rate;\"\u00a02&gt;\/dev\/null\u00a0&gt;\/dev\/null  \u00a0\u00a0echo\u00a0-e\u00a0\"\\nsample_rate = $i\"\u00a0&gt;&gt;$RESULTS  \u00a0\u00a0nohup\u00a0pgbench -c $CONNECTIONS -j20 -T120 -S -Mprepared --progress 10 |\u00a0grep\u00a0\"tps = \"\u00a02&gt;&gt;\/tmp\/results\u00a0&gt;&gt;$RESULTS &amp;  \u00a0\u00a0timeout 125 psql -f\u00a0\/tmp\/waits.sql  \u00a0\u00a0echo\u00a0\" count | wait_event | wait_event_type\"\u00a0&gt;&gt;$RESULTS  \u00a0\u00a0echo\u00a0\"--------------------------------------\"\u00a0&gt;&gt;$RESULTS  \u00a0\u00a0cat\u00a0\/tmp\/waits\u00a0|\u00a0grep\u00a0OUT |\u00a0awk\u00a0'{print $2 \"|\" $3}'\u00a0FS=\"|\"\u00a0|\u00a0sort\u00a0|\u00a0uniq\u00a0-c |\u00a0sort\u00a0-n -r -k1 &gt;&gt;$RESULTS  \u00a0\u00a0rm\u00a0-rfv\u00a0\/tmp\/waits  done  cat\u00a0$RESULTS<\/code><\/pre>\n<p>After running the benchmark with various\u00a0<code>pg_stat_statements.sample_rate<\/code>\u00a0values, we got the results in the table below. It shows how performance and the nature of waits change when we change the fraction of queries hitting the hash table:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<td>\n<p align=\"left\"><strong>sample_rate<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>tps<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>SpinDelay<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>NoWait<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>ClientRead<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">1.00<\/p>\n<\/td>\n<td>\n<p align=\"left\">484 338<\/p>\n<\/td>\n<td>\n<p align=\"left\">11 107<\/p>\n<\/td>\n<td>\n<p align=\"left\">9 568<\/p>\n<\/td>\n<td>\n<p align=\"left\">929<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">0.75<\/p>\n<\/td>\n<td>\n<p align=\"left\">909 547<\/p>\n<\/td>\n<td>\n<p align=\"left\">4 781<\/p>\n<\/td>\n<td>\n<p align=\"left\">12 079<\/p>\n<\/td>\n<td>\n<p align=\"left\">2100<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">0.50<\/p>\n<\/td>\n<td>\n<p align=\"left\">1 019 507<\/p>\n<\/td>\n<td>\n<p align=\"left\">174<\/p>\n<\/td>\n<td>\n<p align=\"left\">13 253<\/p>\n<\/td>\n<td>\n<p align=\"left\">3378<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">0.25<\/p>\n<\/td>\n<td>\n<p align=\"left\">1 019 507<\/p>\n<\/td>\n<td>\n<p align=\"left\">&#8212;<\/p>\n<\/td>\n<td>\n<p align=\"left\">13 397<\/p>\n<\/td>\n<td>\n<p align=\"left\">3423<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">0.00<\/p>\n<\/td>\n<td>\n<p align=\"left\">1 015 425<\/p>\n<\/td>\n<td>\n<p align=\"left\">&#8212;<\/p>\n<\/td>\n<td>\n<p align=\"left\">13 106<\/p>\n<\/td>\n<td>\n<p align=\"left\">3502<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>At sample_rate = 1.0, when metrics are collected for all queries, TPS is the lowest and there is a huge number of waits on SpinLock. As the sample_rate decreases to 0.75 and below, TPS spikes, and SpinDelay decreases by 2.3 times. At sample_rate = 0.25 and below, SpinDelay disappears.<br \/>Thus, sampling effectively reduces the overhead of\u00a0<code>pg_stat_statements<\/code>\u00a0and significantly improves performance in scenarios with high contention for SpinLock.<\/p>\n<h3>Conclusion<\/h3>\n<p>In\u00a0<code>pg_stat_statements<\/code>,\u00a0<code>LWLock<\/code>\u00a0is used when adding a new entry to the hash table, and with a large number of unique queries, it can become a bottleneck. The PostgreSQL community is trying to solve this problem by reducing the number of new entries (namely by merging similar queries under one\u00a0<code>queryid<\/code>), and in Tantor Postgres 17.5, corresponding configuration parameters were added to mask arrays and temporary tables:\u00a0<code>pg_stat_statements.mask_const_arrays\u00a0<\/code>and\u00a0<code>pg_stat_statements.mask_temp_tables<\/code>. This helps to cluster similar queries more precisely.<\/p>\n<p>In turn, SpinLock is used to protect individual buckets and becomes a source of contention when counters for identical queries are frequently updated, especially on machines with a large number of CPUs. To solve this problem,\u00a0<code>pg_stat_statements.sample_rate<\/code>\u00a0parameter was added in Tantor Postgres SE 17.5: it allows reducing the load by sampling queries, thereby eliminating the problem described in the article.<\/p>\n<p>The developers of Tantor Labs proposed to merge the query sampling mechanism in\u00a0<code>pg_stat_statements<\/code>\u00a0in the main branch of PostgreSQL. There&#8217;s already an\u00a0<a href=\"https:\/\/www.postgresql.org\/message-id\/flat\/fe99e0ca-e564-480e-b865-5f0cee30bc60%40tantorlabs.com\" rel=\"noopener noreferrer nofollow\">active discussion<\/a>\u00a0happening over at pgsql-hackers \u2014 if you&#8217;ve got any thoughts or feedback, feel free to jump in!<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/928588\/\"> https:\/\/habr.com\/ru\/articles\/928588\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>pg_stat_statements\u00a0is the standard PostgreSQL extension used to track query statistics: number of executions, total and average execution time, number of returned rows, and other metrics. This information allows to analyze query behavior over time, identify problem areas, and make informed optimization decisions. However, in systems with high contention,\u00a0pg_stat_statements\u00a0itself can become a bottleneck and cause performance drops. In this article, we will analyze in which scenarios the extension becomes a source of problems, how sampling is structured, and in which cases its application can reduce overhead.<\/p>\n<h2>Issue<\/h2>\n<p>Let&#8217;s briefly recall how\u00a0<code>pg_stat_statements<\/code>\u00a0is structured in order to understand when and why the extension may slow down performance. This will help understand which mechanisms within the extension can become a bottleneck under high load.<br \/>The key data structure in\u00a0<code>pg_stat_statements<\/code>\u00a0is a hash table. Each bucket in it contains execution metrics for a specific query. The key for this table is formed based on four parameters:<\/p>\n<ul>\n<li>\n<p><code>queryid<\/code>\u00a0\u2014 unique identifier of the normalized query;<\/p>\n<\/li>\n<li>\n<p>User\u00a0<code>OID<\/code>;<\/p>\n<\/li>\n<li>\n<p>Database\u00a0<code>OID<\/code>;<\/p>\n<\/li>\n<li>\n<p>a\u00a0<code>toplevel<\/code>\u00a0flag indicating whether the query is top-level, meaning it&#8217;s not nested within any internal function or subquery.<\/p>\n<\/li>\n<\/ul>\n<p>The hash table finds or creates the corresponding entry using this key and updates the collected query metrics.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>Upon receiving a request,\u00a0<code>pg_stat_statements<\/code>\u00a0executes the following sequence of operations:<\/p>\n<ol>\n<li>\n<p>Search for the bucket: a shared LWLock is acquired on \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043f\u0440\u0435\u0434\u043b\u043e\u0433the hash table and it is searched through by key;<\/p>\n<\/li>\n<li>\n<p>Normalization (optional): if there is no suitable bucket, the query is pre-normalized: literals are replaced with placeholders like $1, $2, etc.;<\/p>\n<\/li>\n<li>\n<p>Creating a new bucket: the lock level of LWLock is raised to\u00a0<code>exclusive<\/code>\u00a0and a new bucket is created;<\/p>\n<\/li>\n<li>\n<p>Recording query information in the bucket: to update the query metrics in the bucket, the SpinLock of that bucket is acquired. Then the locks on SpinLock and LWLock are released.\u00a0<\/p>\n<\/li>\n<\/ol>\n<p>These locking operations with a large number of unique queries or high contention in\u00a0<code>pg_stat_statements<\/code>\u00a0become a bottleneck. Let&#8217;s review this using a scenario where all SQL queries are unique from the perspective of\u00a0<code>pg_stat_statements<\/code>. A machine with 48 CPUs can reproduce such a load. To ensure that the queries are unique, we will create 1000 similar tables with different names:<\/p>\n<blockquote>\n<p><strong>init_script.sql<\/strong><\/p>\n<\/blockquote>\n<pre><code class=\"sql\">DO $$  DECLARE  \u00a0\u00a0\u00a0\u00a0i INT;  BEGIN  \u00a0\u00a0\u00a0\u00a0FOR i IN 1..1000 LOOP  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0EXECUTE format('CREATE TABLE table_%s (id INT PRIMARY KEY, value TEXT);', i);  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0EXECUTE format('INSERT INTO table_%s (id, value) VALUES (1, ''test'');', i);  \u00a0\u00a0\u00a0\u00a0END LOOP;  END;  $$;<\/code><\/pre>\n<p>\u00a0Then, using the built-in random number generator in pgbench (<strong>pgbench_script.sq<\/strong>l), we will create queries on these tables so that each of them is different and falls into a new bucket of the\u00a0<code>pg_stat_statements<\/code>\u00a0hash table.<\/p>\n<blockquote>\n<p><strong>pgbench_script.sql<\/strong><\/p>\n<\/blockquote>\n<pre><code class=\"sql\">\\set\u00a0table1_id random(1, 1000)  \\set\u00a0table2_id random(1, 1000)  \\set\u00a0table3_id random(1, 1000)  \u00a0  SELECT\u00a0t1.value\u00a0AS\u00a0value1, t2.value\u00a0AS\u00a0value2, t3.value\u00a0AS\u00a0value3  FROM\u00a0table_:table1_id t1  JOIN\u00a0table_:table2_id t2\u00a0ON\u00a0t1.id = t2.id  JOIN\u00a0table_:table3_id t3\u00a0ON\u00a0t2.id = t3.id  WHERE\u00a0t1.id = 1\u00a0AND\u00a0t2.id = 1\u00a0AND\u00a0t3.id = 1;<\/code><\/pre>\n<p>To visually see what causes the performance drop, let&#8217;s call pg_stat_activity once a second while the benchmark is running. The results of each query will be written in \/tmp\/waits file:<\/p>\n<blockquote>\n<p><strong>waits.sql<\/strong><\/p>\n<\/blockquote>\n<pre><code class=\"sql\">\\o \/tmp\/waits  select\u00a0'OUT',\u00a0COALESCE(wait_event,\u00a0'None') wait_event,\u00a0COALESCE(wait_event_type,\u00a0'No wait') wait_event_type\u00a0from\u00a0\u00a0pg_stat_activity\u00a0where\u00a0state =\u00a0'active';  \\watch 1<\/code><\/pre>\n<p>After the benchmark is completed, we group all types of delays and count how many times each of them occurred:<\/p>\n<pre><code class=\"sql\">cat \/tmp\/waits | grep\u00a0OUT\u00a0| awk\u00a0'{print $2 \"|\" $3}'\u00a0FS=\"|\"\u00a0| sort | uniq -c | sort -n -r -k1<\/code><\/pre>\n<p>Then we run the benchmark, compare the system performance with the pg_stat_statements extension enabled and disabled, and display the reasons for performance drop. To do this, we will use standard pgbench utility:<\/p>\n<ul>\n<li>\n<p>Number of clients (-c): 48 &#8212; according to the number of CPUs;<\/p>\n<\/li>\n<li>\n<p>Threads (-j): 20 &#8212; limits contention at the OS level to avoid overloading the CPU and context switches;<\/p>\n<\/li>\n<li>\n<p>Duration (-T): 120 seconds;<\/p>\n<\/li>\n<li>\n<p>Script: run pgbench_script.sql (-f pgbench_script.sql);<\/p>\n<\/li>\n<li>\n<p>Metrics: total number of delays and final TPS.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"pgsql\">pgbench -c48 -j20 -T120 -f pgbench_script.sql --progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;<\/code><\/pre>\n<p>Now lets combine all these actions in one script:\u00a0<\/p>\n<pre><code class=\"sql\">RESULTS=\"\/tmp\/results\"  \u00a0  rm -rfv $RESULTS  nohup pgbench -c48 -j20 -T120 -f pgbench_script.sql --progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;  timeout 125 psql -f waits.sql  echo \" count | wait_event | wait_event_type\" &gt;&gt;$RESULTS  echo \"--------------------------------------\" &gt;&gt;$RESULTS  cat \/tmp\/waits | grep OUT | awk '{print $2 \"|\" $3}' FS=\"|\" | sort | uniq -c | sort -n -r -k1 &gt;&gt;$RESULTS  cat $RESULTS  rm -rfv \/tmp\/waits<\/code><\/pre>\n<p>We will get the following results:<\/p>\n<pre><code class=\"sql\"># With pg_stat_statements off  tps = 237 437.104223 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a02922\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a0\u00a0918\u00a0 ClientRead\u00a0\u00a0\u00a0 Client  \u00a0  # With pg_stat_statements on  tps =\u00a0 32 112.129029 (without initial connection time)  \u00a0count |\u00a0\u00a0\u00a0\u00a0 wait_event\u00a0\u00a0\u00a0\u00a0 | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a04703\u00a0 pg_stat_statements\u00a0\u00a0\u00a0\u00a0\u00a0 LWLock  \u00a0\u00a0\u00a0\u00a0884\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a0\u00a0143\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client<\/code><\/pre>\n<p>As we see here, with a large number of unique queries, enabled\u00a0<code>pg_stat_statements\u00a0<\/code>can significantly reduce performance \u2014 even leading to fold drop in TPS. And this all is caused by\u00a0<strong>frequent &#8216;exclusive&#8217; LWLock <\/strong><a href=\"http:\/\/acquisition.Now\" rel=\"noopener noreferrer nofollow\"><strong>acquisition.<\/strong>Now<\/a> let&#8217;s review another scenario \u2014 with a large number of similar queries. Here we will need a more powerful machine with 192 CPUs. For the test, we will again use a script that periodically checks pg_stat_activity, but this time we will create the load using the same query executed through pgbench -M prepared -S with 192 clients:<\/p>\n<pre><code class=\"sql\">pgbench -c192 -j20 -T120 -M prepared -S --progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;<\/code><\/pre>\n<p>When we run this benchmark&#8230;<\/p>\n<pre><code class=\"sql\">RESULTS=\"\/tmp\/results\"  \u00a0  rm\u00a0-rfv $RESULTS  pgbench -i -s500  nohup\u00a0pgbench -c192 -j20 -T120 -M prepared -S --progress 10 |\u00a0grep\u00a0\"tps = \"\u00a02&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;  timeout 125 psql -f waits.sql  echo\u00a0\" count | wait_event | wait_event_type\"\u00a0&gt;&gt;$RESULTS  echo\u00a0\"--------------------------------------\"\u00a0&gt;&gt;$RESULTS  cat\u00a0\/tmp\/waits\u00a0|\u00a0grep\u00a0OUT |\u00a0awk\u00a0'{print $2}'\u00a0FS=\"|\"\u00a0|\u00a0sort\u00a0|\u00a0uniq\u00a0-c |\u00a0sort\u00a0-n -r -k1 &gt;&gt;$RESULTS  cat\u00a0$RESULTS  rm\u00a0-rfv\u00a0\/tmp\/waits<\/code><\/pre>\n<p>..we get the following results:<\/p>\n<pre><code class=\"sql\"># Results with pg_stat_statements off  tps = 1 015 425.438193 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a013201\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a03482\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client  \u00a0  # Results with pg_stat_statements on  tps =\u00a0\u00a0 484 338.163894 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a011 214\u00a0 SpinDelay\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Timeout  \u00a0\u00a0\u00a09481\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0\u00a0\u00a0\u00a0930\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client<\/code><\/pre>\n<p>\u00a0If we try to reproduce this on a machine with 48 CPUs (i.e., with 48 users)&#8230;<\/p>\n<pre><code class=\"sql\">pgbench -c48 -j20 -T120 -M prepared -S\u00a0--progress 10 | grep \"tps = \" 2&gt;&gt;$RESULTS &gt;&gt;$RESULTS &amp;<\/code><\/pre>\n<p>&#8230;the performance issues will stay well within the bounds of statistical error.<\/p>\n<pre><code class=\"sql\"># Results with pg_stat_statements off  tps = 625 335.965464 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a0\u00a0979\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client  \u00a0\u00a0\u00a0\u00a0927\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait  \u00a0  # Results with pg_stat_statements on  tps = 611 708.477697 (without initial connection time)  \u00a0count | wait_event | wait_event_type  --------------------------------------  \u00a0\u00a0\u00a01000\u00a0 ClientRead\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Client  \u00a0\u00a0\u00a0\u00a0978\u00a0 None\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 No wait<\/code><\/pre>\n<p>This indicates that the impact of\u00a0<code>pg_stat_statements<\/code>\u00a0when working with duplicate queries becomes noticeable only at very high levels of parallelism. The main reason is the contention for the same entry in the hash table, which is accompanied by\u00a0<strong>frequent SpinLock acquisitions when updating query metrics in the hash table bucket<\/strong>. When many threads simultaneously execute the same query, they try to update the same structure \u2014 increment call counters, execution time, and other metrics. This leads to severe contention for SpinLock, which under high load causes delays and reduces TPS.<\/p>\n<h3>What is sampling?<\/h3>\n<p>Query sampling is a method of uniform filtering, where only part of all queries is included in the sample. In the context of\u00a0<code>pg_stat_statements<\/code>, this means that metric information is recorded not for every executed query, but only for some of them, with equal probability. A similar approach is used in PostgreSQL in other places: log_transaction_sample_rate and log_statement_sample_rate are used to reduce log volume, as well as in\u00a0<code>auto_explain.sample_rate<\/code> and <code>pg_store_plans.sample_rate<\/code>. In Tantor Postgres 17.5, a corresponding setting was added to pg_stat_statements \u2014 the GUC parameter\u00a0<code><strong>pg_stat_statements.sample_rate<\/strong><\/code>, which allows you to set fraction of queries that is tracked in the extension&#8217;s statistics. The value of the parameter (from 0 to 1) determines what fraction of queries will be tracked in pg_stat_statements. A query will be sampled if the following condition is met:<\/p>\n<blockquote>\n<p>Sampling<\/p>\n<\/blockquote>\n<pre><code class=\"sql\">is_query_sampled = pgss_sample_rate != 0.0 &amp;&amp;  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(pgss_sample_rate == 1.0 ||  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0pg_prng_double(&amp;pg_global_prng_state) &lt;= pgss_sample_rate);<\/code><\/pre>\n<p>Since the number of queries will be very large, using this inequation allows filtering only the specified fraction of the queries. The sampling method has one significant drawback: not all queries are tracked in\u00a0<code>pg_stat_statements<\/code>. This impacts the completeness of the collected information, especially during debugging or analyzing rare but problematic queries. On the other hand, since only a part of the queries is tracked, the load on locks is<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-467572","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/467572","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=467572"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/467572\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=467572"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=467572"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=467572"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}