{"id":394104,"date":"2024-06-29T11:24:30","date_gmt":"2024-06-29T11:24:30","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=394104"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=394104","title":{"rendered":"<span>Locks in PostgreSQL: 4. Locks in memory<\/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-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">To remind you, we&#8217;ve already talked about <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/500714\/\">relation-level locks<\/a>, <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/503008\/\">row-level locks<\/a>, <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/504498\/\">locks on other objects<\/a> (including predicate locks) and interrelationships of different types of locks.<\/p>\n<p>  The following discussion of <strong>locks in RAM<\/strong> finishes this series of articles. We will consider spinlocks, lightweight locks and buffer pins, as well as events monitoring tools and sampling.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/od\/rh\/bp\/odrhbpges4k1z9d-n_lagra9fgc.png\" data-src=\"https:\/\/habrastorage.org\/webt\/od\/rh\/bp\/odrhbpges4k1z9d-n_lagra9fgc.png\"\/><br \/>  <a name=\"habracut\"><\/a>  <\/p>\n<h1>Spinlocks<\/h1>\n<p>  Unlike normal, \u00abheavy-weight\u00bb, locks, to protect structures in the shared memory, more lightweight and less expensive (in overhead costs) locks are used.<\/p>\n<p>  The simplest of them are <em>spinlocks<\/em>. They are meant to be acquired for very short time intervals (a few processor instructions), and they protect separate memory areas from simultaneous changes.<\/p>\n<p>  Spinlocks are implemented based on atomic processor instructions, such as compare-and-swap. They support the only, exclusive, mode. If a lock is acquired, a waiting process performs busy waiting\u00a0\u2014 the command is repeated (\u00abspins\u00bb in a loop, hence the name) until it is a success. This makes sense since spinlocks are used in the cases where the probability of a conflict is estimated as very low.<\/p>\n<p>  Spinlocks do not enable detection of deadlocks (PostgreSQL developers take care of this) and provide no monitoring tools. Essentially, the only thing we can do with spinlocks is to be aware of their existence.<\/p>\n<h1>Lightweight locks<\/h1>\n<p>  So-called <em>lightweight locks<\/em> (lwlocks) come next.<\/p>\n<p>  They get acquired for a short time that is needed to work with the data structure (such as a hash table or a list of pointers). As a rule, a lightweight lock is held briefly, but sometimes lightweight locks protect input\/output operations, so in general, the time might also be considerable.<\/p>\n<p>  Two modes are supported: exclusive (for data modifications) and shared (only for reading). There is actually no wait queue: if a few processes wait for release of a lock, one of them will get the access in a more or less random fashion. In high-concurrency and large-load systems, this can be troublesome (for example, see this <a href=\"https:\/\/postgrespro.com\/list\/thread-id\/2400193\">discussion<\/a>).<\/p>\n<p>  There are no techniques to check for deadlocks, so this is left to the responsibility of developers of the core. However, lightweight locks have monitoring tools, so, unlike spinlocks, we can \u00absee\u00bb them (I will show a bit later how to do this).<\/p>\n<h1>Buffer pin<\/h1>\n<p>  Yet another type of locks, which we already touched upon in the article on the <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/491730\/\">buffer cache<\/a>, is a <em>buffer pin<\/em>.<\/p>\n<p>  Different operations, including data modifications, can be performed with a pinned buffer, but under the condition that the changes are not visible to other processes due to multiversion concurrency control. That\u00a0is, we can, for instance, add a new row to the page, but cannot replace a page in the buffer with another one.<\/p>\n<p>  If a buffer pin hinders a process, as a rule, the latter just skips this buffer and chooses a different one. But in some cases, where exactly this buffer is needed, the process queues and \u00abfalls asleep\u00bb; the system will wake it up when the buffer is unpinned.<\/p>\n<p>  Monitoring can access waits related to buffer pins.<\/p>\n<h1>Example: buffer cache<\/h1>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/qj\/wl\/xm\/qjwlxmfhdwzljcpcfvjzfo3wvrs.png\" data-src=\"https:\/\/habrastorage.org\/webt\/qj\/wl\/xm\/qjwlxmfhdwzljcpcfvjzfo3wvrs.png\"\/><\/p>\n<p>  Now, in order to get some (although incomplete!) insight into how and where locks are used, let&#8217;s consider the buffer cache as an example.<\/p>\n<p>  To access a hash table that contains links to buffers, a process must acquire a lightweight <em>buffer mapping lock<\/em> in a shared mode, and if the table needs to be updated, in an exclusive mode. To reduce the granularity, this lock is structured as a <em>tranche<\/em> that consists of 128 separate locks, each protecting its own part of the hash table.<\/p>\n<p>  The process gets access to the buffer header using a spinlock. Certain operations (such as a counter increment) can also be performed without explicit locking, by means of atomic processor instructions.<\/p>\n<p>  In order to read the contents of a buffer, a <em>buffer content lock<\/em> is needed. It is usually acquired only for the time needed to read pointers to tuples, and after that, a buffer pin provides sufficient protection. To change the contents of a buffer, this lock must be acquired in an exclusive mode.<\/p>\n<p>  When a buffer is read from disk (or written to disk), an <em>IO in progress<\/em> lock is also acquired, which indicates to other processes that the page is being read (or written)\u00a0\u2014 they can queue up if they need to do something with this page.<\/p>\n<p>  Pointers to free buffers and to the next victim are protected by one <em>buffer strategy lock<\/em> spinlock.<\/p>\n<h1>Example: WAL buffers<\/h1>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/6m\/33\/fc\/6m33fcd_oem_zwhg50wzw17zdt8.png\" data-src=\"https:\/\/habrastorage.org\/webt\/6m\/33\/fc\/6m33fcd_oem_zwhg50wzw17zdt8.png\"\/><br \/>  WAL buffers provide another example.<\/p>\n<p>  The WAL cache also uses a hash table that contains mapping of pages to buffers. Unlike for the buffer cache, this hash table is protected by the only lightweight <code>WALBufMappingLock<\/code> lock since the size of the WAL cache is smaller (usually 1\/32 of the buffer cache) and access to the buffers is more regular.<\/p>\n<p>  Writes of pages to disk are protected by the <code>WALWriteLock<\/code> lock, so that only one process can perform this operation at a time.<\/p>\n<p>  To create a WAL record, the process must first allocate space in a WAL page. To do this, it acquires an <em>insert position lock<\/em> spinlock. When the space is allocated, the process copies the contents of its record to the space allocated. Copying can be performed by several processes simultaneously, therefore, the record is protected by a tranche of 8 lightweight <em>wal insert lock<\/em> locks (the process must acquire <em>any<\/em> of them).<\/p>\n<p>  The figure does not show all WAL-related locks, but this and previous examples must give an idea of how locks in RAM are used.<\/p>\n<h1>Wait events monitoring<\/h1>\n<p>  Starting with PostgreSQL\u00a09.6, the <code>pg_stat_activity<\/code> view has built-in events monitoring tools. When a process (system or backend) cannot do its job and waits for something, we can see this wait in the view: the <code>wait_event_type<\/code> column shows the wait type and the <code>wait_event<\/code> column shows the name of a specific wait.<\/p>\n<p>  Note that the view shows only waits that are properly handled in the source code. If the view does not show a wait, in general, this does not mean with 100 percent probability that the process really waits for nothing.<\/p>\n<p>  Unfortunately, the only available information on waits is the <em>current<\/em> information. No accumulated statistics are maintained. The only way to get the picture of waits in time is <em>sampling<\/em> the state of the view at a certain interval. No built-in tools are provided to this end, but we can use extensions, such as <a href=\"https:\/\/github.com\/postgrespro\/pg_wait_sampling\">pg_wait_sampling<\/a>.<\/p>\n<p>  We need to take into account the probabilistic nature of sampling. To get a more or less credible picture, the number of measurements must be pretty large. Low-frequency sampling may fail to provide a credible picture, while use of higher frequencies will increase overhead costs. For the same reason, sampling is useless to analyze short-lived sessions.<\/p>\n<p>  All the waits can be divided into several types.<\/p>\n<p>  Waits for the locks discussed make up a large category:<\/p>\n<ul>\n<li>Waits for locks on objects (the value of <code>Lock<\/code> in the <code>wait_event_type<\/code> column).<\/li>\n<li>Waits for lightweight locks (<code>LWLock<\/code>).<\/li>\n<li>Waits for a buffer pin (<code>BufferPin<\/code>).<\/li>\n<\/ul>\n<p>  But processes can also await other events:<\/p>\n<ul>\n<li>Waits for input\/output (<code>IO<\/code>) occur when a process needs to read or write data.<\/li>\n<li>A process can wait for the data needed from a client (<code>Client<\/code>) or another process (<code>IPC<\/code>).<\/li>\n<li>Extensions can register their specific waits (<code>Extension<\/code>).<\/li>\n<\/ul>\n<p>  Sometimes situations arise when a process just doesn&#8217;t do any productive work. This category includes:<\/p>\n<ul>\n<li>Waits of a background process in its main loop (<code>Activity<\/code>).<\/li>\n<li>Waits for a timer (<code>Timeout<\/code>).<\/li>\n<\/ul>\n<p>  Usually, waits like these are treated as \u00abnormal\u00bb and do not indicate any problems.<\/p>\n<p>  The wait type is followed by the name of a specific wait. For the complete table, see the <a href=\"https:\/\/postgrespro.com\/docs\/postgresql\/11\/monitoring-stats#WAIT-EVENT-TABLE\">documentation<\/a>.<\/p>\n<p>  If the name of a wait is not defined, the process is not in the waiting state. We need to treat this point in time as <em>unaccounted for<\/em> since we are actually unaware of what exactly is happening at that moment.<\/p>\n<p>  However, let&#8217;s watch this for ourselves.<\/p>\n<pre><code class=\"pgsql\">=> SELECT pid, backend_type, wait_event_type, wait_event FROM pg_stat_activity; <\/code><\/pre>\n<pre><code class=\"plaintext\">  pid  |         backend_type         | wait_event_type |     wait_event       -------+------------------------------+-----------------+---------------------  28739 | logical replication launcher | Activity        | LogicalLauncherMain  28736 | autovacuum launcher          | Activity        | AutoVacuumMain  28963 | client backend               |                 |   28734 | background writer            | Activity        | BgWriterMain  28733 | checkpointer                 | Activity        | CheckpointerMain  28735 | walwriter                    | Activity        | WalWriterMain (6 rows) <\/code><\/pre>\n<p>  It&#8217;s clear that all background backend processes are idle. Empty values of <code>wait_event_type<\/code> and <code>wait_event<\/code> tell us that the process is waiting for nothing; in our example, the backend process is busy executing the query.<\/p>\n<h2>Sampling<\/h2>\n<p>  To get a more or less complete picture of waits by means of sampling, we will use the <a href=\"https:\/\/github.com\/postgrespro\/pg_wait_sampling\">pg_wait_sampling<\/a> extension. We need to build it from source codes, but I will omit this part. Then we add the library name to the <em>shared_preload_libraries<\/em> parameter and restart the server.<\/p>\n<pre><code class=\"pgsql\">=> ALTER SYSTEM SET shared_preload_libraries = 'pg_wait_sampling'; <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">student$ sudo pg_ctlcluster 11 main restart <\/code><\/pre>\n<p>  Now we install the extension in the database.<\/p>\n<pre><code class=\"pgsql\">=> CREATE EXTENSION pg_wait_sampling; <\/code><\/pre>\n<p>  The extension allows us to look through the history of waits, which is stored in a circular buffer. But what&#8217;s mostly interesting to us is the waits profile, that\u00a0is, the statistics accumulated since the server start.<\/p>\n<p>  This is roughly what we will see a few seconds later:<\/p>\n<pre><code class=\"pgsql\">=> SELECT * FROM pg_wait_sampling_profile; <\/code><\/pre>\n<pre><code class=\"plaintext\">  pid  | event_type |        event        | queryid | count  -------+------------+---------------------+---------+-------  29074 | Activity   | LogicalLauncherMain |       0 |   220  29070 | Activity   | WalWriterMain       |       0 |   220  29071 | Activity   | AutoVacuumMain      |       0 |   219  29069 | Activity   | BgWriterMain        |       0 |   220  29111 | Client     | ClientRead          |       0 |     3  29068 | Activity   | CheckpointerMain    |       0 |   220 (6 rows) <\/code><\/pre>\n<p>  Because nothing happened since the server start, most waits refer to the types <code>Activity<\/code> (backend processes wait until there is some work for them) and <code>Client<\/code> (<code>psql<\/code> waits until a user sends a request).<\/p>\n<p>  With the default settings (of the <em>pg_wait_sampling.profile_period<\/em> parameter), the sampling period equals 10 milliseconds, which means that values are saved 100 times a second. Therefore, to evaluate the duration of waits in seconds, we need to divide the value of <code>count<\/code> by 100.<\/p>\n<p>  To figure out which process the waits pertain to, let&#8217;s add the <code>pg_stat_activity<\/code> view to the query:<\/p>\n<pre><code class=\"pgsql\">=> SELECT p.pid, a.backend_type, a.application_name AS app,           p.event_type, p.event, p.count FROM pg_wait_sampling_profile p   LEFT JOIN pg_stat_activity a ON p.pid = a.pid ORDER BY p.pid, p.count DESC; <\/code><\/pre>\n<pre><code class=\"plaintext\">  pid  |         backend_type         | app  | event_type |        event         | count  -------+------------------------------+------+------------+----------------------+-------  29068 | checkpointer                 |      | Activity   | CheckpointerMain     |   222  29069 | background writer            |      | Activity   | BgWriterMain         |   222  29070 | walwriter                    |      | Activity   | WalWriterMain        |   222  29071 | autovacuum launcher          |      | Activity   | AutoVacuumMain       |   221  29074 | logical replication launcher |      | Activity   | LogicalLauncherMain  |   222  29111 | client backend               | psql | Client     | ClientRead           |     4  29111 | client backend               | psql | IPC        | MessageQueueInternal |     1 (7 rows) <\/code><\/pre>\n<p>  Let&#8217;s produce some workload using <code>pgbench<\/code> and see how the picture changes.<\/p>\n<pre><code class=\"plaintext\">student$ pgbench -i test <\/code><\/pre>\n<p>  We reset the accumulated profile to zero and run the test for 30 seconds in a separate process.<\/p>\n<pre><code class=\"pgsql\">=> SELECT pg_wait_sampling_reset_profile(); <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">student$ pgbench -T 30 test <\/code><\/pre>\n<p>  We need to execute the query while the <code>pgbench<\/code> process is not finished yet:<\/p>\n<pre><code class=\"pgsql\">=> SELECT p.pid, a.backend_type, a.application_name AS app,           p.event_type, p.event, p.count FROM pg_wait_sampling_profile p   LEFT JOIN pg_stat_activity a ON p.pid = a.pid WHERE a.application_name = 'pgbench' ORDER BY p.pid, p.count DESC; <\/code><\/pre>\n<pre><code class=\"plaintext\">  pid  |  backend_type  |   app   | event_type |   event    | count  -------+----------------+---------+------------+------------+-------  29148 | client backend | pgbench | IO         | WALWrite   |     8  29148 | client backend | pgbench | Client     | ClientRead |     1 (2 rows) <\/code><\/pre>\n<p>  The waits of the <code>pgbench<\/code> process will certainly differ slightly depending on a particular system. In our situation, a wait for WAL writing (<code>IO<\/code>\/<code>WALWrite<\/code>) is highly likely to be presented, however, most of the time the process was doing something presumably productive rather than being idle.<\/p>\n<h2>Lightweight locks<\/h2>\n<p>  We always need to keep in mind that if a wait is missing when sampling, this does not mean that there was really no wait. If the wait was shorter than the sampling period (a hundredth of a second in our example), it could just fail to get into the sample.<\/p>\n<p>  That&#8217;s why lightweight locks did not occur in the profile, but they will if the data is collected for a long time. To be able to see them for sure, we can intentionally slow down the file system, for example, by using the <a href=\"https:\/\/github.com\/nirs\/slowfs\">slowfs<\/a> project, built on top of the <a href=\"https:\/\/github.com\/libfuse\/libfuse\">FUSE<\/a> file system.<\/p>\n<p>  This is what we can see on the same test if any input\/output operation takes 1\/10\u00a0of\u00a0a\u00a0second.<\/p>\n<pre><code class=\"pgsql\">=> SELECT pg_wait_sampling_reset_profile(); <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">student$ pgbench -T 30 test <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">=> SELECT p.pid, a.backend_type, a.application_name AS app,           p.event_type, p.event, p.count FROM pg_wait_sampling_profile p   LEFT JOIN pg_stat_activity a ON p.pid = a.pid WHERE a.application_name = 'pgbench' ORDER BY p.pid, p.count DESC; <\/code><\/pre>\n<pre><code class=\"plaintext\">  pid  |  backend_type  |   app   | event_type |     event      | count  -------+----------------+---------+------------+----------------+-------  29240 | client backend | pgbench | IO         | WALWrite       |  1445  29240 | client backend | pgbench | LWLock     | WALWriteLock   |   803  29240 | client backend | pgbench | IO         | DataFileExtend |    20 (3 rows) <\/code><\/pre>\n<p>  Now the major wait of the <code>pgbench<\/code> process relates to input\/output, more exactly, to WAL writes, which synchronously occur for every commit. Because (as shown in one of the above examples) a WAL write is protected by a lightweight <code>WALWriteLock<\/code> lock, this lock is also present in the profile\u00a0\u2014 and it&#8217;s just what we wanted to look at.<\/p>\n<h2>Buffer pin<\/h2>\n<p>  To see a buffer pin, let&#8217;s make use of the fact that open cursors hold the pin to faster read the next row.<\/p>\n<p>  Let&#8217;s start a transaction, open a cursor and select one row.<\/p>\n<pre><code class=\"pgsql\">=> BEGIN; => DECLARE c CURSOR FOR SELECT * FROM pgbench_history; => FETCH c; <\/code><\/pre>\n<pre><code class=\"plaintext\"> tid | bid |  aid  | delta |           mtime            | filler  -----+-----+-------+-------+----------------------------+--------    9 |   1 | 35092 |   477 | 2019-09-04 16:16:18.596564 |  (1 row) <\/code><\/pre>\n<p>  Let&#8217;s check that the buffer is pinned (<code>pinning_backends<\/code>):<\/p>\n<pre><code class=\"pgsql\">=> SELECT * FROM pg_buffercache WHERE relfilenode = pg_relation_filenode('pgbench_history') AND relforknumber = 0 \\gx <\/code><\/pre>\n<pre><code class=\"plaintext\">-[ RECORD 1 ]----+------ bufferid         | 190 relfilenode      | 47050 reltablespace    | 1663 reldatabase      | 16386 relforknumber    | 0 relblocknumber   | 0 isdirty          | t usagecount       | 1 pinning_backends | 1     &lt;-- buffer is pinned 1 time <\/code><\/pre>\n<p>  Now let&#8217;s <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/484106\/\">vacuum<\/a> the table:<\/p>\n<pre><code class=\"pgsql\">|  => SELECT pg_backend_pid(); <\/code><\/pre>\n<pre><code class=\"plaintext\">|   pg_backend_pid  |  ---------------- |            29367 |  (1 row) <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">|  => VACUUM VERBOSE pgbench_history; <\/code><\/pre>\n<pre><code class=\"plaintext\">|  INFO:  vacuuming \"public.pgbench_history\" |  INFO:  \"pgbench_history\": found 0 removable, 0 nonremovable row versions in 1 out of 1 pages |  DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 732651 |  There were 0 unused item pointers. <\/code><\/pre>\n<pre><code class=\"plaintext\">|  Skipped 1 page due to buffer pins, 0 frozen pages. <\/code><\/pre>\n<pre><code class=\"plaintext\">|  0 pages are entirely empty. |  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. |  VACUUM <\/code><\/pre>\n<p>  As we can see, the page was skipped (<code>Skipped 1 page due to buffer pins<\/code>). Indeed, VACUUM cannot process it because physical deletion of tuples from a page in a pinned buffer is forbidden. But vacuuming will not wait either, and the page will be processed next time.<\/p>\n<p>  And now let&#8217;s perform <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/487590\/\">vacuuming with freezing<\/a>:<\/p>\n<pre><code class=\"pgsql\">|  => VACUUM FREEZE VERBOSE pgbench_history; <\/code><\/pre>\n<p>  If freezing is explicitly requested, none of the pages tracked in the all-frozen bit can be skipped; otherwise, it is impossible to reduce the maximum age of non-frozen transactions in <code>pg_class.relfrozenxid<\/code>. So, vacuuming hangs until the cursor is closed.<\/p>\n<pre><code class=\"pgsql\">=> SELECT age(relfrozenxid) FROM pg_class WHERE oid = 'pgbench_history'::regclass; <\/code><\/pre>\n<pre><code class=\"plaintext\"> age  -----   27 (1 row) <\/code><\/pre>\n<pre><code class=\"pgsql\">=> COMMIT; -- cursor closes automatically <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">|  INFO:  aggressively vacuuming \"public.pgbench_history\" |  INFO:  \"pgbench_history\": found 0 removable, 26 nonremovable row versions in 1 out of 1 pages |  DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 732651 |  There were 0 unused item pointers. <\/code><\/pre>\n<pre><code class=\"plaintext\">|  Skipped 0 pages due to buffer pins, 0 frozen pages. <\/code><\/pre>\n<pre><code class=\"plaintext\">|  0 pages are entirely empty. |  CPU: user: 0.00 s, system: 0.00 s, elapsed: 3.01 s. |  VACUUM <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">=> SELECT age(relfrozenxid) FROM pg_class WHERE oid = 'pgbench_history'::regclass; <\/code><\/pre>\n<pre><code class=\"plaintext\"> age  -----    0 (1 row) <\/code><\/pre>\n<p>  And let&#8217;s look into the waits profile of the second <code>psql<\/code> session, where the VACUUM commands were executed:<\/p>\n<pre><code class=\"pgsql\">=> SELECT p.pid, a.backend_type, a.application_name AS app,           p.event_type, p.event, p.count FROM pg_wait_sampling_profile p   LEFT JOIN pg_stat_activity a ON p.pid = a.pid WHERE p.pid = 29367 ORDER BY p.pid, p.count DESC; <\/code><\/pre>\n<pre><code class=\"plaintext\">  pid  |  backend_type  | app  | event_type |   event    | count  -------+----------------+------+------------+------------+-------  29367 | client backend | psql | BufferPin  | BufferPin  |   294  29367 | client backend | psql | Client     | ClientRead |    10 (2 rows) <\/code><\/pre>\n<p>  The <code>BufferPin<\/code> wait tells us that VACUUM was waiting for the buffer to get free.<\/p>\n<p>  And this is where we will consider discussing locks as finished. Thank you all for attentive reading and comments!<\/p><\/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\/507036\/\"> https:\/\/habr.com\/ru\/articles\/507036\/<\/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-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">To remind you, we&#8217;ve already talked about <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/500714\/\">relation-level locks<\/a>, <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/503008\/\">row-level locks<\/a>, <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/504498\/\">locks on other objects<\/a> (including predicate locks) and interrelationships of different types of locks.<\/p>\n<p>  The following discussion of <strong>locks in RAM<\/strong> finishes this series of articles. We will consider spinlocks, lightweight locks and buffer pins, as well as events monitoring tools and sampling.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/od\/rh\/bp\/odrhbpges4k1z9d-n_lagra9fgc.png\" data-src=\"https:\/\/habrastorage.org\/webt\/od\/rh\/bp\/odrhbpges4k1z9d-n_lagra9fgc.png\"\/>  <\/p>\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-394104","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/394104","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=394104"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/394104\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=394104"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=394104"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=394104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}