Re: PATCH: logical_work_mem and logical streaming of large in-progress transactions
Peter Eisentraut <peter.eisentraut@2ndquadrant.com>
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Tighten the concurrent abort check during decoding.
- 2ce353fc1902 14.0 landed
-
Improve hash_create()'s API for some added robustness.
- b3817f5f7746 14.0 landed
-
Use HASH_BLOBS for xidhash.
- a1b8aa1e4eec 14.0 landed
-
Fix initialization of RelationSyncEntry for streaming transactions.
- 69bd60672af6 14.0 landed
-
Remove unused function declaration in logicalproto.h.
- ddd5f6d2609b 14.0 landed
-
Add additional tests to test streaming of in-progress transactions.
- 58b5ae9d62bd 14.0 landed
-
Fix inline marking introduced in commit 464824323e.
- ac15b499f7f9 14.0 landed
-
Add support for streaming to built-in logical replication.
- 464824323e57 14.0 landed
-
Fix the SharedFileSetUnregister API.
- 4ab77697f67a 14.0 landed
-
Fix comment in procarray.c
- 77c7267c37f7 14.0 cited
-
Suppress compiler warning in non-cassert builds.
- e942af7b8261 14.0 cited
-
Extend the BufFile interface.
- 808e13b282ef 14.0 landed
-
Mark a few logical decoding related variables with PGDLLIMPORT.
- b48cac3b10a0 14.0 landed
-
Implement streaming mode in ReorderBuffer.
- 7259736a6e5b 14.0 landed
-
Extend the logical decoding output plugin API with stream methods.
- 45fdc9738b36 14.0 landed
-
WAL Log invalidations at command end with wal_level=logical.
- c55040ccd017 14.0 landed
-
Immediately WAL-log subtransaction and top-level XID association.
- 0bead9af484c 14.0 landed
-
Allow logical replication to transfer data in binary format.
- 9de77b545313 14.0 cited
-
Only superuser can set sslcert/sslkey in postgres_fdw user mappings
- cebf9d6e6ee1 13.0 cited
-
Track statistics for spilling of changes from ReorderBuffer.
- 9290ad198b15 13.0 landed
-
Add logical_decoding_work_mem to limit ReorderBuffer memory usage.
- cec2edfa7859 13.0 landed
-
logical decoding: process ASSIGNMENT during snapshot build
- bac2fae05c77 13.0 cited
-
Emit invalidations to standby for transactions without xid.
- c6ff84b06a68 9.6.0 cited
On 12/22/17 23:57, Tomas Vondra wrote: > PART 1: adding logical_work_mem memory limit (0001) > --------------------------------------------------- > > Currently, limiting the amount of memory consumed by logical decoding is > tricky (or you might say impossible) for several reasons: I would like to see some more discussion on this, but I think not a lot of people understand the details, so I'll try to write up an explanation here. This code is also somewhat new to me, so please correct me if there are inaccuracies, while keeping in mind that I'm trying to simplify. The data in the WAL is written as it happens, so the changes belonging to different transactions are all mixed together. One of the jobs of logical decoding is to reassemble the changes belonging to each transaction. The top-level data structure for that is the infamous ReorderBuffer. So as it reads the WAL and sees something about a transaction, it keeps a copy of that change in memory, indexed by transaction ID (ReorderBufferChange). When the transaction commits, the accumulated changes are passed to the output plugin and then freed. If the transaction aborts, then changes are just thrown away. So when logical decoding is active, a copy of the changes for each active transaction is kept in memory (once per walsender). More precisely, the above happens for each subtransaction. When the top-level transaction commits, it finds all its subtransactions in the ReorderBuffer, reassembles everything in the right order, then invokes the output plugin. All this could end up using an unbounded amount of memory, so there is a mechanism to spill changes to disk. The way this currently works is hardcoded, and this patch proposes to change that. Currently, when a transaction or subtransaction has accumulated 4096 changes, it is spilled to disk. When the top-level transaction commits, things are read back from disk to do the final processing mentioned above. This all works mostly fine, but you can construct some more extreme cases where this can blow up. Here is a mundane example. Let's say a change entry takes 100 bytes (it might contain a new row, or an update key and some new column values, for example). If you have 100 concurrent active sessions and no subtransactions, then logical decoding memory is bounded by 4096 * 100 * 100 = 40 MB (per walsender) before things spill to disk. Now let's say you are using a lot of subtransactions, because you are using PL functions, exception handling, triggers, doing batch updates. If you have 200 subtransactions on average per concurrent session, the memory usage bound in that case would be 4096 * 100 * 100 * 200 = 8 GB (per walsender). And so on. If you have more concurrent sessions or larger changes or more subtransactions, you'll use much more than those 8 GB. And if you don't have those 8 GB, then you're stuck at this point. That is the consideration when we record changes, but we also need memory when we do the final processing at commit time. That is slightly less problematic because we only process one top-level transaction at a time, so the formula is only 4096 * avg_size_of_changes * nr_subxacts (without the concurrent sessions factor). So, this patch proposes to improve this as follows: - We compute the actual size of each ReorderBufferChange and keep a running tally for each transaction, instead of just counting the number of changes. - We have a configuration setting that allows us to change the limit instead of the hardcoded 4096. The configuration setting is also in terms of memory, not in number of changes. - The configuration setting is for the total memory usage per decoding session, not per subtransaction. (So we also keep a running tally for the entire ReorderBuffer.) There are two open issues with this patch: One, this mechanism only applies when recording changes. The processing at commit time still uses the previous hardcoded mechanism. The reason for this is, AFAIU, that as things currently work, you have to have all subtransactions in memory to do the final processing. There are some proposals to change this as well, but they are more involved. Arguably, per my explanation above, memory use at commit time is less likely to be a problem. Two, what to do when the memory limit is reached. With the old accounting, this was easy, because we'd decide for each subtransaction independently whether to spill it to disk, when it has reached its 4096 limit. Now, we are looking at a global limit, so we have to find a transaction to spill in some other way. The proposed patch searches through the entire list of transactions to find the largest one. But as the patch says: "XXX With many subtransactions this might be quite slow, because we'll have to walk through all of them. There are some options how we could improve that: (a) maintain some secondary structure with transactions sorted by amount of changes, (b) not looking for the entirely largest transaction, but e.g. for transaction using at least some fraction of the memory limit, and (c) evicting multiple transactions at once, e.g. to free a given portion of the memory limit (e.g. 50%)." (a) would create more overhead for the case where everything fits into memory, so it seems unattractive. Some combination of (b) and (c) seems useful, but we'd have to come up with something concrete. Thoughts? -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services