Re: Report bytes and transactions actually sent downtream
Re: Report bytes and transactions actually sent downtream
От:
Chao Li <li.evan.chao@gmail.com>
Дата:
Hi, Ashutosh, I just quickly went through the patch. Obviously I need more time to fully understand the patch, I will do a deep review today. In the meantime, I just caught a nit issue. > On Dec 11, 2025, at 12:59, Ashutosh Bapat wrote: > > > Please review. > > [1] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com > [2] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal > [3] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com > > -- > Best Wishes, > Ashutosh Bapat > 1 ``` + linkend="logicaldecoding-output-plugin-callbacks"/>. A descripancy in those ``` Typo: descripancy => discrepancy Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
Re: Report bytes and transactions actually sent downtream
От:
Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Дата:
Let me go back to the original motivation. As I understand it, the problem was that, among several subscribers connected through different slots, one slot was lagging behind. The total_txns and total_bytes values for that slot appeared smaller, and you wanted to know where the bottleneck was. I wonder if part of the confusion comes from the fact that it is not yet clear what conclusions a user is expected to draw from these new values. What I am still not quite sure about is how the proposed sent_bytes value would be used to make that distinction. It sounds like the idea is not necessarily to look at its rate over time, but perhaps to compare total_bytes with the proposed sent_bytes. However, as has been discussed, these two values seem to measure different things, so I am not sure that such a comparison would be straightforward. I think it would help move the discussion forward if you could explain more concretely how these new values would be used to identify the bottleneck in the case you described. Personally, in such a situation, I would first add some temporary instrumentation at a few likely points and print the values with elog() or similar, just to see which value actually distinguishes the bottleneck. Once we know that, it should be easier to decide whether that measurement is generally useful enough to expose in a statistics view, and where it should be measured. Regards, -- Kyotaro Horiguchi NTT Open Source Software Center
Re: Report bytes and transactions actually sent downtream
От:
Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Дата:
At Mon, 15 Jun 2026 09:48:51 +0530, Ashutosh Bapat wrote in > As I have explained in [1], total_bytes indicates the amount of data > added to the reorder buffer. It does not indicate the amount of data > in logical form sent downstream. The system which triggered this issue > used Debezium as the downstream. The customer wanted to configure > Debezium so that it can consume the logical changes in real time. But > they had no clue about the amount of logical changes they received > from upstream. total_bytes does not help since it's the amount of WAL > added to reorder buffer; not the amount of logical changes sent > downstream. Hence proposal to add new column sent_bytes. > > I hope this helps. > > [1] https://www.postgresql.org/message-id/flat/CAExHW5s6KntzUyUoMbKR5dgwRmd= > V2Ay_2%2BAnTgYGAzo%3DQv61wA%40mail.gmail.com Thanks, that clarifies the use case. If the goal is to estimate the volume of data that downstream consumers such as Debezium need to process, I'm still not sure why this necessarily needs to be the amount of logical change data rather than the amount of data actually sent over the replication connection. For monitoring or capacity-planning purposes, wire bytes seem like they would provide a very similar signal. The protocol overhead is relatively small, especially when the traffic volume is high, and the definition is somewhat more straightforward since it corresponds directly to the amount of data transmitted downstream. Also, I'm not sure that the logical-change size is necessarily a more accurate representation of the amount of change being processed. Since the amount of logical change data is itself influenced by how the output plugin represents changes, it still seems somewhat dependent on representation, just in a different way. Could you explain a bit more about why the logical-change size is the important metric here, rather than the number of bytes actually sent? Regards, -- Kyotaro Horiguchi NTT Open Source Software Center
Re: Report bytes and transactions actually sent downtream
От:
Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Дата:
Hello. At Mon, 15 Jun 2026 13:52:36 +0530, Ashutosh Sharma wrote in > Sorry for chiming in - I may well be misunderstanding this, but here's > how I'm currently thinking about it: > > Total transaction bytes refers to the size of decoded transactional > data accumulated in the reorder buffer for a given transaction. > > Sent bytes (as I understand from the patch) refers to the size of the > downstream output that the output plugin produces from that decoded > data, after any filtering and format conversion. > > To illustrate: if a transaction's decoded changes occupy 550 bytes in > the reorder buffer, but the output plugin filters some out and emits > only 300 bytes downstream, then total transaction bytes = 550 and sent > bytes = 300. Conversely, if all 550 bytes are converted into a more > verbose format and emitted as 700 bytes, total transaction bytes > remains 550 while sent bytes becomes 700. > > If I'm reading this right, since total bytes for a transaction is the > baseline from which transaction-derived downstream output is produced, > I wonder whether sent bytes should include only that > transaction-derived downstream output, or also downstream protocol > traffic such as keepalive messages, which are sent downstream but are > not derived from transaction bytes in the reorder buffer. > > My instinct is that if sent bytes are meant to measure > transaction-output throughput, keepalive messages probably shouldn't > be included, since they have no basis in transaction data and might > distort any comparison with total bytes. But I could be wrong - happy > to be corrected! Thank you for the explanation. I think I understand the distinction you are making. However, my question is one step earlier than the keepalive-message question. I am wondering whether the new metric needs to be defined in terms of logical-change output in the first place. If I understand the use case correctly, I think the discussion here is primarily about relatively high-volume logical replication workloads. My point is that, in that situation, I would expect the amount of logical-change output and the amount of data actually sent over the replication connection to show broadly similar trends. The latter seems easier to interpret, while still providing a useful signal for monitoring and capacity-planning purposes. It also seems more intuitive, since it corresponds directly to the amount of data sent over the replication connection. Regards, -- Kyotaro Horiguchi NTT Open Source Software Center
Re: Report bytes and transactions actually sent downtream
От:
Chao Li <li.evan.chao@gmail.com>
Дата:
> On Dec 17, 2025, at 13:55, Ashutosh Bapat wrote: > > Thanks for pointing this out. I have fixed it my code. However, at > this point I am looking for a design review, especially to verify that > the new implementation addresses Andres's concern raised in [1] while > not introducing any design issues raised earlier e.g. those raised in > threads [2], [3] and [4] > > [1] https://www.postgresql.org/message-id/zzidfgaowvlv4opptrcdlw57vmulnh7gnes4aerl6u35mirelm@tj2vzseptkjk >>> [2] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com >>> [3] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal >>> [4] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com > > -- > Best Wishes, > Ashutosh Bapat Hi Ashutosh, Yeah, I owe you a review. I committed to review this patch but I forgot, sorry about that. From design perspective, I agree increasing counters should belong to the core, plugin should return properly values following the contract. And I got some more comments: 1. I just feel a bool return value might not be clear enough. For example: ``` - ctx->callbacks.change_cb(ctx, txn, relation, change); + if (!ctx->callbacks.change_cb(ctx, txn, relation, change)) + cache->filteredBytes += ReorderBufferChangeSize(change); ``` You increase filteredBytes when change_cb returns false. But if we look at pgoutput_change(), there are many reasons to return false. Counting all the cases to filteredBytes seems wrong. 2. ``` - ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change); + if (!ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change)) + cache->filteredBytes += ReorderBufferChangeSize(change); ``` Row filter doesn’t impact TRUNCATE, why increase filteredBytes after truncate_cb()? 3. ``` - ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn); + if (ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn)) + cache->sentTxns++; ``` For 2-phase commit, it increase sentTxns after prepare_cb, and ``` + if (ctx->callbacks.stream_abort_cb(ctx, txn, abort_lsn)) + cache->sentTxns++; ``` If the transaction is aborted, sentTxns is increased again, which is confusing. Though for aborting there is some data (a notification) is streamed, but I don’t think that should be counted as a transaction. After commit, sentTxns is also increased, so that, a 2-phase commit is counted as two transactions, which feels also confusing. IMO, a 2-phase commit should still be counted as one transaction. 4. You add sentBytes and filteredBytes. I am thinking if it makes sense to also add sentRows and filteredRows. Because tables could be big or small, bytes + rows could show a more clear picture to users. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
Re: Report bytes and transactions actually sent downtream
От:
Chao Li <li.evan.chao@gmail.com>
Дата:
> On Dec 18, 2025, at 20:52, Ashutosh Bapat wrote: > > On Thu, Dec 18, 2025 at 7:56 AM Chao Li wrote: >> >> >> >>> On Dec 17, 2025, at 13:55, Ashutosh Bapat wrote: >>> >>> Thanks for pointing this out. I have fixed it my code. However, at >>> this point I am looking for a design review, especially to verify that >>> the new implementation addresses Andres's concern raised in [1] while >>> not introducing any design issues raised earlier e.g. those raised in >>> threads [2], [3] and [4] >>> >>> [1] https://www.postgresql.org/message-id/zzidfgaowvlv4opptrcdlw57vmulnh7gnes4aerl6u35mirelm@tj2vzseptkjk >>>>> [2] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com >>>>> [3] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal >>>>> [4] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com >>> >>> -- >>> Best Wishes, >>> Ashutosh Bapat >> >> >> Hi Ashutosh, >> >> Yeah, I owe you a review. I committed to review this patch but I forgot, sorry about that. >> >> From design perspective, I agree increasing counters should belong to the core, plugin should return properly values following the contract. And I got some more comments: >> >> 1. I just feel a bool return value might not be clear enough. For example: >> >> ``` >> - ctx->callbacks.change_cb(ctx, txn, relation, change); >> + if (!ctx->callbacks.change_cb(ctx, txn, relation, change)) >> + cache->filteredBytes += ReorderBufferChangeSize(change); >> ``` >> >> You increase filteredBytes when change_cb returns false. But if we look at pgoutput_change(), there are many reasons to return false. Counting all the cases to filteredBytes seems wrong. > > I am not able to understand this. Every "return false" from > pgoutput_change() indicates that the change was filtered out and hence > the size of corresponding change is being added to filteredBytes by > the caller. Which "return false" does not indicate a filtered out > change? I think the confusion comes from the counter name “filteredBytes”, what does “filtered” mean? There are 3 types of data not steaming out: a. WAL data of tables that doesn’t belong to the publication b. table belong to the publication, but action doesn’t. For example, FOR ALL TABLES (INSERT), then update/delete will not be streamed out c. Filtered by row filter (WHERE) I thought only c should be counted to filteredBytes; thinking over again, maybe b should also be counted. But I still don’t think a should be counted. IMO, sentBytes + filteredBytes == supposedToSendBytes. If a table doesn’t belong to a publication, then it should not be counted into supposedToSendBytes, so it should not be counted into filteredBytes. The other point is that, if we count a into filteredBytes, then ends up totalBytes == sendBytes + filteredBytes, if that’s true, why don’t compute such a number by (totalBytes-sendBytes) in client side? If we insist to count a, then maybe we need to consider a better counter name. >> >> 2. >> ``` >> - ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change); >> + if (!ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change)) >> + cache->filteredBytes += ReorderBufferChangeSize(change); >> ``` >> >> Row filter doesn’t impact TRUNCATE, why increase filteredBytes after truncate_cb()? > > A TRUNCATE of a relation which is not part of the publication will be > filtered out. Same as 1. > >> >> 3. >> ``` >> - ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn); >> + if (ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn)) >> + cache->sentTxns++; >> ``` >> >> For 2-phase commit, it increase sentTxns after prepare_cb, and >> ``` >> + if (ctx->callbacks.stream_abort_cb(ctx, txn, abort_lsn)) >> + cache->sentTxns++; >> ``` >> >> If the transaction is aborted, sentTxns is increased again, which is confusing. Though for aborting there is some data (a notification) is streamed, but I don’t think that should be counted as a transaction. >> >> After commit, sentTxns is also increased, so that, a 2-phase commit is counted as two transactions, which feels also confusing. IMO, a 2-phase commit should still be counted as one transaction. > > stream_commit/abort_cb is called after stream_prepare_cb not after prepare_cb. That’s my typo, but the problem is still there. Should we count a 2-phase-commit as 2 transactions? > >> >> 4. You add sentBytes and filteredBytes. I am thinking if it makes sense to also add sentRows and filteredRows. Because tables could be big or small, bytes + rows could show a more clear picture to users. > > We don't have corresponding total_rows and streamed_rows counts. I > think that's because we haven't come across a use case for them. Do > you have a use case in mind? > That’s still related to 1. totalBytes includes tables don’t belong to the publication, thus totalRows doesn’t make much sense. But sendRows will only include those rows belonging to the publication. For filterRows, if we exclude a, then I believe filterRows also makes sense. If you argue that “rows” request should be treated in a separate thread, I’ll be okay with that. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
Re: Report bytes and transactions actually sent downtream
От:
Michael Paquier <michael@paquier.xyz>
Дата:
On Fri, Mar 13, 2026 at 10:39:40AM +0530, Ashutosh Sharma wrote: > Thanks for sharing your thoughts, Michael, I had a similar idea in > mind. I'll go ahead and implement these changes and come up with the > new patches. Now since this is purely additive (new columns only), I'm > thinking a single patch might suffice, Or maybe, we could split it > into two: one that corrects the existing documentation and another > that introduces the new fields for displaying downstream bytes sent > and some other information related to this. Thanks. We'd better move forward quickly with this one based on the current calendar, still it would be good to hear from Ashutosh Bapat and double-check with him. -- Michael
Re: Report bytes and transactions actually sent downtream
От:
Michael Paquier <michael@paquier.xyz>
Дата:
On Mon, Mar 16, 2026 at 08:55:17AM +0530, Ashutosh Bapat wrote: > I would post two patches > 1. To introduce the new column sent_bytes + clarify total_bytes in the > context of sent_bytes. We should consider this patch for committing. Sounds to me like two patches: one for the new column, a second for the docs of the existing columns. Could somebody send these patches? I would be OK to look at them once posted, driving things for this CF. > 2. Addition of filtered_bytes + sent_txns and also the clarification > of total_bytes and total_txns in the context of the new columns. We > will decide whether to commit the patch after 0001 is committed. Not sure about this one, neither do I know if I would be able to look at any proposal for this release cycle on this part. Getting 0001 out of its box seems like a goal good enough for me, TBH. -- Michael
Re: Report bytes and transactions actually sent downtream
От:
Michael Paquier <michael@paquier.xyz>
Дата:
On Mon, Mar 16, 2026 at 01:20:57PM +0530, Ashutosh Sharma wrote: > 1) v20260316-0001-Report-downstream-sent-bytes-in-pg_stat_replication_.patch: > > This patch introduces sent_bytes to report the amount of data sent > downstream. It documents sent_bytes in a way that it clarifies how > sent_bytes differs from total_bytes, without modifying the existing > documentation for total_bytes or total_txns. The patch is purely > additive and does not alter any existing documentation. + elog(DEBUG2, "UpdateDecodingStats: updating stats %p %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64, This was already unreadable before the patch, and it makes the situation worse. Could it be possible to append the names of each field here? + qq(Check that reset timestamp is later after the second reset of stats for slot '$stats_test_slot1' and confirm total_bytes and sent_bytes were set to 0.) Not your fault here as well, but I would replace that by a comment at the top of the test. Still your patch makes things harder to read. + Amount of transaction changes sent downstream for this slot by the + output plugin after applying output plugin filters, if any, and + converting it into the output plugin format. Do we need to be that fancy here? The concept relates to the number of bytes sent downstream. Here is a suggestion: "Amount of bytes decoded and sent downstream by the output plugin. This accounts for the output plugin filters, if any, and for the conversion into the output plugin format." Perhaps we had better document at the top of LogicalOutputWrite() that "sentBytes" ought to be updated for all the fields we are sending part of the tuplestore? Why are you not counting the "now" timestamp in WalSndWriteData()? This is appended to the stream of data with a separate pq_send*() call. Perhaps the reason why this is not appended is worth a comment? The trick with this patch is to make sure that all the relevant places where data is sent downstream are correctly incremented. As far as I can see, things seem to be covered, but I cannot help but wonder if we are missing one or more places. @Amit, do you feel a hole somewhere? -- Michael
Re: Report bytes and transactions actually sent downtream
От:
Michael Paquier <michael@paquier.xyz>
Дата:
On Thu, Mar 12, 2026 at 06:13:33PM +0530, Ashutosh Sharma wrote: > Could you please share your thoughts on this? Your inputs would help > us determine the next steps - whether we should proceed with renaming, > and if so, what names you would suggest, or whether we should leave > things as they are. FWIW, I still find the use of _wal_ in these fields rather confusing, and they add more inconsistencies with the internal structures of reorderbuffer.c. The goal is to add a field to track the number of bytes sent downstream. Hence, I would suggest to give up on the rename, add the new field, perhaps consider improving the docs for the existing fields to tell to which context these numbers refer to, then call it a day. -- Michael
Re: Report bytes and transactions actually sent downtream
От:
Michael Paquier <michael@paquier.xyz>
Дата:
On Tue, Mar 17, 2026 at 10:55:51AM +0530, Ashutosh Bapat wrote: > On Tue, Mar 17, 2026 at 10:32 AM Amit Kapila wrote: >> Yesterday, I raised a point related to this which is whether we want >> to count messages like keep_alive, copy_done, or >> PqReplMsg_PrimaryStatusUpdate? These don't contain the user decoded >> data from WAL but some additional information between >> publisher-subscriber required to ensure data is being applied. > > I think sent_bytes should count the logically decoded data - the data > that the output plugin produces in logical form. This is the amount of > data that the downstream has to process for data replication. So, the > protocol related data shouldn't be counted here, IMO. Compare this > with the total_bytes which does not account for the memory used by the > reorder buffer metadata itself - it just accounts for the size of the > changes. If we describe the sent_bytes as below, we are explicitly > mentioning what sent_bytes is. So anything which is not data in the > output format is excluded including the protocol data. If you think we > need to clarify that we are excluding protocol data, we can add a > line. But that may confuse readers who are unaware of the protocol > data. As we aim to count how many bytes are sent over the wire, including the amount of bytes related to the messages sent as part of the protocol exchanged, sounds like an important property that we should have in the implementation because these bytes are required for the exchanges to work. If an output plugin decodes few changes and has a lot of messages (unlikely in most cases, still imagine it's the case), sent_bytes could be misleading if we only include the transactional changes, couldn't it? -- Michael
Re: Report bytes and transactions actually sent downtream
От:
Michael Paquier <michael@paquier.xyz>
Дата:
On Wed, Mar 18, 2026 at 09:58:58AM +0530, Ashutosh Sharma wrote: > So the right boundary for sent_bytes is according to me would be: > > a) include logical decoding payload sent downstream (or via SQL > decoding path), whether it came from transactional changes or > non-transactional logical messages > b) exclude auxiliary replication protocol traffic such as keepalives > > If this makes sense then the revised documentation shared by Ashutosh > Bapat in his last email looks good to me, which is: > > Amount of transaction changes and non-transactional messages sent > downstream in the output plugin > format for this slot. The output plugin may filter the changes it > receives. Hence the amount of data that it converts to the output > plugin format is less than the total_bytes. > But the format of data before and after the conversion is different. > Hence the value of sent_bytes is not > directly related to the value of > total_bytes. > > If you all agree with this, I will first prepare a patch to fix the > missing part in #2 which is about reporting stats for > non-transactional messages that goes via logical decoding process and > then on top of that share the ongoing 0001 patch with above doc > change. Amit Kapila and I have both mentioned that the definition you are putting behind the sent_bytes field, as in "not accounting for the protocol messages required in the publisher-subscriber exchange" mentioned in b), feels strange, because these messages are sent over the wire and part of the exchanges, like the decoded data. This could be relevant if the exchanges consist of short decoded data and mostly of protocol data. Of course you are free to send an updated patch based on the definition you see fit. Whether one agrees or not with the definition implemented in the patch is a different matter. Anybody is also free to disagree or overwrite our opinions on the matter, of course, that's how consensus is driven. -- Michael
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Thu, Jul 24, 2025 at 12:24:26PM +0530, Ashutosh Bapat wrote: > Here's the next patch which considers all the discussion so far. It > adds four fields to pg_stat_replication_slots. > - plugin - name of the output plugin Is this one needed? (we could get it with a join on pg_replication_slots) > - plugin_filtered_bytes - reports the amount of changes filtered > out by the output plugin > - plugin_sent_txns - the amount of transactions sent downstream by > the output plugin > - plugin_sent_bytes - the amount of data sent downstream by the > outputplugin. > > There are some points up for a discussion: > 1. pg_stat_reset_replication_slot() zeroes out the statistics entry by > calling pgstat_reset() or pgstat_reset_of_kind() which don't know > about the contents of the entry. So > PgStat_StatReplSlotEntry::plugin_has_stats is set to false and plugin > stats are reported as NULL, instead of zero, immediately after reset. > This is the same case when the stats is queried immediately after the > statistics is initialized and before any stats are reported. We could > instead make it report > zero, if we save the plugin_has_stats and restore it after reset. But > doing that in pgstat_reset_of_kind() seems like an extra overhead + we > will need to write a function to find all replication slot entries. Could we store plugin_has_stats in ReplicationSlotPersistentData instead? That way it would not be reset. We would need to access ReplicationSlotPersistentData in pg_stat_get_replication_slot though. Also would that make sense to expose plugin_has_stats in pg_replication_slots? > 2. There's also a bit of asymmetry in the way sent_bytes is handled. > The code which actually sends the logical changes to the downstream is > part of the core code > but the format of the change and hence the number of bytes sent is > decided by the plugin. It's a stat related to plugin but maintained by > the core code. The patch implements it as a plugin stat (so the > corresponding column has "plugin" prefix The way it is done makes sense to me. > 3. The names of new columns have the prefix "plugin_" but the internal > variables tracking those don't for the sake of brevity. If you prefer > to have the same prefix for the internal variables, I can change that. Just my taste: I do prefer when they match. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Fri, Oct 03, 2025 at 12:22:05PM +0530, Ashutosh Bapat wrote: > Here's patchset addressing two issues: Thanks for the patch update! > I > have added a note in documentation for plugin authors, warning them > that this isn't expected. What note are you referring to? (I'm failing to see it). > I don't think it's worth adding complexity > in code to support such a case unless we see a practical need for the > same. Sounds good. > Issue 2: Once X + 2 is loaded, further statistics are accumulated on > the top of statistics accumulated by version X. Attached patch fixes > issue 2 by zero'ing out the stats when the plugin does not report the > statistics. +#define REPLSLOT_SET_TO_ZERO(fld) statent->fld = 0 It looks like that the associated "undef" is missing. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Fri, Sep 26, 2025 at 06:14:28PM +0530, Ashutosh Bapat wrote: > On Fri, Sep 26, 2025 at 4:43 PM Bertrand Drouvot > wrote: > > > > > > === 2 > > > > Issue 1 is that before any decoding happens, pg_stat_replication_slots is still > > showing stale plugin statistics from a plugin that may no longer support stats. > > > > I'm not sure how we could easily fix this issue, as we don't know the plugin's > > stats capability until we actually use it. > > > > I don't think this is an issue. There is no way for the core to tell > whether the plugin will provide stats or not, unless it sets that > ctx->stats which happens in the startup callback. Till then it is > rightly providing the values accumulated so far. Once the decoding > starts, we know that the plugin is not providing any stats and we > don't display anything. Yeah, I got the technical reasons, but I think there's a valid user experience concern here: seeing statistics for a plugin that doesn't actually support statistics is misleading. What we need is a call to pgstat_report_replslot() to display stats that reflect the current plugin behavior. We can't just call pgstat_report_replslot() in say RestoreSlotFromDisk() because we really need the decoding to start. So one idea could be to set a flag (per slot) when pgstat_report_replslot() has been called (for good reasons) and check for this flag in pg_stat_get_replication_slot(). If the flag is not set, then set the plugin fields to NULL. If the flag is set, then display their values (like now). And we should document that the plugin stats are not available (i.e are NULL) until the decoding has valid stats to report after startup. What do you think? > > > -- Issue 2: > > > > Now it reports 10, that's the 9 before we changed the plugin to not have stats > > enabled plus this new one. > > > > Issue 2: when switching from a non-stats plugin back to a stats-capable plugin, it > > shows accumulated values from before the non-stats switch. > > This too seems to be a non-issue to me. The stats in the view get > reset only when a user resets them. So we shouldn't wipe out the > already accumulated values just because the plugin stopped providing > it. If the plugin keeps flip-flopping and only partial statistics > provided by the plugin will be accumulated. That's the plugin's > responsibility. Okay but then I think that the plugin is missing some flexibility. For example, how could the plugin set ctx->stats->sentTxns to zero if it decides not to enable stats (while it was previously enable)? Indeed, not enabling stats, means not doing "ctx->stats = palloc0(sizeof(OutputPluginStats))" which means not having control over the stats anymore. So, with the current design, it has not other choice but having its previous stats not reset to zero. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Fri, Sep 19, 2025 at 08:11:23PM +0530, Ashutosh Bapat wrote:
> On Fri, Sep 19, 2025 at 11:48 AM shveta malik wrote:
> >
> 0001 is the previous patch
> 0002 changes addressing your and Bertrand's comments.
Thanks for the new patch version!
I did not look closely to the code yet but did some testing and I've one remark
regarding plugin_filtered_bytes: It looks ok when a publication is doing rows
filtering but when I:
- create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '0')
then I see plugin_sent_bytes increasing (which makes sense).
- create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '1')
then I don't see plugin_sent_bytes increasing (which makes sense) but I also don't
see plugin_filtered_bytes increasing. I think that would make sense to also increase
plugin_filtered_bytes in this case (and for the other options that would skip
sending data). Thoughts?
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Wed, Sep 24, 2025 at 12:51:29PM +0530, Ashutosh Bapat wrote:
> On Wed, Sep 24, 2025 at 12:32 PM Bertrand Drouvot
> wrote:
> > > > - create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '1')
> > > > then I don't see plugin_sent_bytes increasing (which makes sense) but I also don't
> > > > see plugin_filtered_bytes increasing. I think that would make sense to also increase
> > > > plugin_filtered_bytes in this case (and for the other options that would skip
> > > > sending data). Thoughts?
> > >
> > > Thanks for bringing this up. I don't think we discussed this
> > > explicitly in the thread. The changes which are filtered out by the
> > > core itself e.g. changes to the catalogs or changes to other databases
> > > or changes from undesired origins are not added to the reorder buffer.
> > > They are not counted in total_bytes. The transactions containing only
> > > such changes are not added to reorder buffer, so even total_txns does
> > > not count such empty transactions. If we count these changes and
> > > transactions in plugin_filtered_bytes, and plugin_filtered_txns, that
> > > would create an anomaly - filtered counts being higher than total
> > > counts. Further since core does not add these changes and transactions
> > > to the reorder buffer, there is no way for a plugin to know about
> > > their existence and hence count them. Does that make sense?
> >
> > Yes. Do you think that the doc in the patch is clear enough regarding this point?
> > I mean the doc looks correct (mentioning the output plugin) but would that make
> > sense to insist that core filtering is not taken into account?
>
> Do you mean, should we mention in the docs that core filtering is not
> taken into account?
> I would question whether that's called filtering
> at all, in the context of logical decoding. The view should be read in
> the context of logical decoding. For example, we aren't mentioning
> that total_bytes does not include changes from other database.
Right. But, in the example above, do you consider "skip-empty-xacts" as "core"
or "plugin" filtering?
It's an option part of the "test_decoding" plugin, so it's the plugin choice to
not display empty xacts (should the option be set accordingly). Then should it
be reported in plugin_filtered_bytes? (one could write a plugin, decide to
skip/filter empty xacts or whatever in the plugin callbacks: should that be
reported as plugin_filtered_bytes?)
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Wed, Sep 24, 2025 at 11:38:30AM +0530, shveta malik wrote: > On Wed, Sep 24, 2025 at 11:08 AM Ashutosh Bapat > wrote: > > > > In WalSndWriteData() we can't rely on what happens in a low level API > > like socket_putmessage(). And we are counting the number of bytes in > > the logically decoded message. So, I actually wonder whether we should > > count 1 byte of 'd' in sentBytes. Shveta, Bertand, what do you think? > > > > If we are not counting all such metadata bytes ((or can't reliably do > so), then IMO, we shall skip counting msgtype as well. Agree. Maybe mention in the doc that metadata (including msgtype) bytes are not taken into account? Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Tue, Sep 23, 2025 at 04:15:14PM +0530, Ashutosh Bapat wrote:
> On Tue, Sep 23, 2025 at 12:14 PM Bertrand Drouvot
> wrote:
> >
> > - create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '1')
> > then I don't see plugin_sent_bytes increasing (which makes sense) but I also don't
> > see plugin_filtered_bytes increasing. I think that would make sense to also increase
> > plugin_filtered_bytes in this case (and for the other options that would skip
> > sending data). Thoughts?
>
> Thanks for bringing this up. I don't think we discussed this
> explicitly in the thread. The changes which are filtered out by the
> core itself e.g. changes to the catalogs or changes to other databases
> or changes from undesired origins are not added to the reorder buffer.
> They are not counted in total_bytes. The transactions containing only
> such changes are not added to reorder buffer, so even total_txns does
> not count such empty transactions. If we count these changes and
> transactions in plugin_filtered_bytes, and plugin_filtered_txns, that
> would create an anomaly - filtered counts being higher than total
> counts. Further since core does not add these changes and transactions
> to the reorder buffer, there is no way for a plugin to know about
> their existence and hence count them. Does that make sense?
Yes. Do you think that the doc in the patch is clear enough regarding this point?
I mean the doc looks correct (mentioning the output plugin) but would that make
sense to insist that core filtering is not taken into account?
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Wed, Sep 24, 2025 at 03:37:07PM +0530, Ashutosh Bapat wrote: > On Wed, Sep 24, 2025 at 1:55 PM Bertrand Drouvot > wrote: > > Right. But, in the example above, do you consider "skip-empty-xacts" as "core" > > or "plugin" filtering? > > > > It's an option part of the "test_decoding" plugin, so it's the plugin choice to > > not display empty xacts (should the option be set accordingly). Then should it > > be reported in plugin_filtered_bytes? (one could write a plugin, decide to > > skip/filter empty xacts or whatever in the plugin callbacks: should that be > > reported as plugin_filtered_bytes?) > > If a transaction becomes empty because the plugin filtered all the > changes then plugin_filtered_bytes will be incremented by the amount > of filtered changes. If the transaction was empty because core didn't > send any of the changes to the output plugin, there was nothing > filtered by the output plugin so plugin_filtered_bytes will not be > affected. > > skip_empty_xacts controls whether BEGIN and COMMIT are sent for an > empty transaction or not. It does not filter "changes". It affects > "sent_bytes". skip_empty_xacts was just an example. I mean a plugin could decide to filter all the inserts for example (not saying it makes sense). But I think we'are saying the same: say a plugin wants to filter the inserts then it's its responsability to increment ctx->stats->filteredBytes in its "change_cb" callback for the REORDER_BUFFER_CHANGE_INSERT action, right? If so, I wonder if it would make sense to provide an example in the test_decoding plugin (I can see it's done for pgoutput but that might sound more natural to look in contrib if one is searching for an example). Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Wed, Sep 24, 2025 at 05:28:44PM +0530, Ashutosh Bapat wrote:
> On Wed, Sep 24, 2025 at 2:38 PM shveta malik wrote:
> >
> > On Wed, Sep 24, 2025 at 12:47 PM Ashutosh Bapat
> > wrote:
> > >
> > > On Wed, Sep 24, 2025 at 10:12 AM shveta malik wrote:
> > > >
> > > > I tested the flows with
> > > > a) logical replication slot and get-changes.
> > > > b) filtered data flows: pub-sub creation with row_filters, 'publish'
> > > > options. I tried to verify plugin fields as compared to total_wal*
> > > > fields.
> > > > c) reset flow.
> > > >
> > > > While tests for a and c are present already. I don't see tests for b
> > > > anywhere when it comes to stats. Do you think we shall add a test for
> > > > filtered data using row-filter somewhere?
> > >
> > > Added a test in 028_row_filter. Please find it in the attached
> > > patchset.
> >
> > Test looks good.
>
> Thanks. Added to three more files. I think we have covered all the
> cases where filtering can occur.
>
> PFA patches.
Thanks for the new version!
A few random comments:
=== 1
+
+
+ plugin_filtered_bytes bigint
+
+
+ Amount of changes, from total_wal_bytes, filtered
+ out by the output plugin and not sent downstream. Please note that it
+ does not include the changes filtered before a change is sent to
+ the output plugin, e.g. the changes filtered by origin. The count is
+ maintained by the output plugin mentioned in
+ plugin.
I found "The count" somehow ambiguous. What about "This statistic" instead?
=== 2
+ subtransactions. These transactions are subset of transctions sent to
s/transctions/transactions
=== 3
+ the decoding plugin. Hence this count is expected to be lesser than or
s/be lesser/be less/? (not 100% sure)
=== 4
+extern Size ReorderBufferChangeSize(ReorderBufferChange *change);
Another approach could be to pass the change's size as an argument to the
callbacks? That would avoid to expose ReorderBufferChangeSize publicly.
=== 5
ctx->output_plugin_private = data;
+ ctx->stats = palloc0(sizeof(OutputPluginStats));
I was wondering if we need to free this in pg_decode_shutdown, but it looks
like it's done through FreeDecodingContext() anyway.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Mon, Sep 29, 2025 at 12:54:24PM +0530, Ashutosh Bapat wrote: > On Fri, Sep 26, 2025 at 10:28 PM Bertrand Drouvot > wrote: > > > > > > > > > > > > I don't think this is an issue. There is no way for the core to tell > > > whether the plugin will provide stats or not, unless it sets that > > > ctx->stats which happens in the startup callback. Till then it is > > > rightly providing the values accumulated so far. Once the decoding > > > starts, we know that the plugin is not providing any stats and we > > > don't display anything. > > > > Yeah, I got the technical reasons, but I think there's a valid user experience > > concern here: seeing statistics for a plugin that doesn't actually support > > statistics is misleading. > > > > 3. If the plugin starts supporting statistics and midway discontinues > its support, it already has a problem with backward compatibility. > > Practically it would 1 or 2, which are working fine. > > I don't think we will encounter case 3 practically. Do you have a > practical use case where a plugin would discontinue supporting stats? Not that I can think of currently. That looks unlikely but wanted to raise the point though. Maybe others see a use case and/or have a different point of view. > > What we need is a call to pgstat_report_replslot() to display stats that reflect > > the current plugin behavior. We can't just call pgstat_report_replslot() > > in say RestoreSlotFromDisk() because we really need the decoding to start. > > > > So one idea could be to set a flag (per slot) when pgstat_report_replslot() > > has been called (for good reasons) and check for this flag in > > pg_stat_get_replication_slot(). > > > > If the flag is not set, then set the plugin fields to NULL. > > If the flag is set, then display their values (like now). > > This approach will have the same problem. Till > pgstat_report_replslot() is called, the old statistics will continue > to be shown. I don't think so because the flag would not be set. > > And we should document that the plugin stats are not available (i.e are NULL) > > until the decoding has valid stats to report after startup. > > The current documentation is " It is NULL when statistics is not > initialized or immediately after a reset or when not maintained by the > output plugin.". I think that covers all the cases. Do you think the doc covers the case we discussed above? i.e when a plugin discontinue supporting stats, it would display stats until the decoding actually starts. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Thu, Sep 25, 2025 at 10:16:35AM +0530, Ashutosh Bapat wrote: > On Wed, Sep 24, 2025 at 8:11 PM Bertrand Drouvot > wrote: > > > > === 4 > > > > +extern Size ReorderBufferChangeSize(ReorderBufferChange *change); > > > > Another approach could be to pass the change's size as an argument to the > > callbacks? That would avoid to expose ReorderBufferChangeSize publicly. > > Do you see any problem in exposing ReorderBufferChangeSize(). It's a > pretty small function and may be quite handy to output plugins > otherwise as well. And we expose many ReorderBuffer related functions; > so this isn't the first. Right. I don't see a problem per say, just thinking that the less we expose publicly to be used by extensions/plugins, the better. > If we were to do as you say, it will change other external facing APIs > like change_cb(). Output plugins will need to change their code > accordingly even when they don't want to support plugin statistics. Correct. > Given that we have made maintaining plugin statistics optional, > forcing API change does not make sense. For example, test_decoding > which does not filter anything would unnecessarily have to change its > code. That's right. > I considered adding a field size to ReorderBufferChange itself. But > that means we increase the amount of memory used in the reorder > buffer, which seems to have become prime estate these days. So > rejected that idea as well. > > Advantage of this change is that the minimal cost of calculating the > size and maintaining the code change is incurred only when filtering > happens, by the plugins which want to filter and maintain statistics. Yes, anyway as it's unlikely that we have to fix a bug in a minor release that would need a signature change to ReorderBufferChangeSize(), I think that's fine as proposed. > > > > === 5 > > > > ctx->output_plugin_private = data; > > + ctx->stats = palloc0(sizeof(OutputPluginStats)); > > > > I was wondering if we need to free this in pg_decode_shutdown, but it looks > > like it's done through FreeDecodingContext() anyway. > > That's correct. Even output_plugin_private is freed when the decoding > memory context is freed. > > Thanks for the review comments. I have addressed the comments in my > repository and the changes will be included in the next set of > patches. Thanks! > Do you have any further review comments? Not right now. I'll give it another look by early next week the latest. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Thu, Sep 25, 2025 at 01:01:55PM +0000, Bertrand Drouvot wrote:
> Hi,
>
> On Thu, Sep 25, 2025 at 10:16:35AM +0530, Ashutosh Bapat wrote:
> > Do you have any further review comments?
>
> Not right now. I'll give it another look by early next week the latest.
>
=== 1
@@ -173,6 +173,7 @@ pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
data->only_local = false;
ctx->output_plugin_private = data;
+ ctx->stats = palloc0(sizeof(OutputPluginStats));
I was not sure where it's allocated, but looking at:
Breakpoint 1, pg_decode_startup (ctx=0x1ba853a0, opt=0x1ba85478, is_init=false) at test_decoding.c:164
164 bool enable_streaming = false;
(gdb) n
166 data = palloc0(sizeof(TestDecodingData));
(gdb)
167 data->context = AllocSetContextCreate(ctx->context,
(gdb)
170 data->include_xids = true;
(gdb)
171 data->include_timestamp = false;
(gdb)
172 data->skip_empty_xacts = false;
(gdb)
173 data->only_local = false;
(gdb)
175 ctx->output_plugin_private = data;
(gdb)
176 ctx->stats = palloc0(sizeof(OutputPluginStats));
(gdb)
178 opt->output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT;
(gdb) p CurrentMemoryContext
$7 = (MemoryContext) 0x1ba852a0
(gdb) p (*CurrentMemoryContext).name
$8 = 0xe4057d "Logical decoding context"
(gdb) p ctx->context
$9 = (MemoryContext) 0x1ba852a0
I can see that CurrentMemoryContext is "ctx->context" so the palloc0 done here
are done in the right context.
=== 2
Playing with "has stats" a bit.
-- Issue 1:
Say, plugin has stats enabled and I get:
postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
plugin | plugin_sent_txns
----------------+------------------
pg_commit_info | 9
(1 row)
If the engine is shutdown and the plugin is now replaced by a version that
does not provide stats, then, right after startup, I still get:
postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
plugin | plugin_sent_txns
----------------+------------------
pg_commit_info | 9
(1 row)
And that will be the case until the plugin decodes something (so that
statent->plugin_has_stats gets replaced in pgstat_report_replslot()).
That's because plugin_has_stats is stored in PgStat_StatReplSlotEntry
and so it's restored from the stat file when the engine starts.
Now, let's do some inserts and decode:
postgres=# insert into t1 values ('a');
INSERT 0 1
postgres=# insert into t1 values ('a');
INSERT 0 1
postgres=# select * from pg_logical_slot_get_changes('logical_slot',NULL,NULL);
lsn | xid | data
------------+-----+-----------------------------------------------------------------------------------------
0/407121C0 | 766 | xid 766: lsn:0/40712190 inserts:1 deletes:0 updates:0 truncates:0 relations truncated:0
0/40712268 | 767 | xid 767: lsn:0/40712238 inserts:1 deletes:0 updates:0 truncates:0 relations truncated:0
(2 rows)
postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
plugin | plugin_sent_txns
----------------+------------------
pg_commit_info |
(1 row)
All good.
Issue 1 is that before any decoding happens, pg_stat_replication_slots is still
showing stale plugin statistics from a plugin that may no longer support stats.
I'm not sure how we could easily fix this issue, as we don't know the plugin's
stats capability until we actually use it.
-- Issue 2:
Let's shutdown, replace the plugin with a version that has stats enabled and
restart.
Same behavior as before:
postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
plugin | plugin_sent_txns
----------------+------------------
pg_commit_info |
(1 row)
Until pgstat_report_replslot() is not called, the statent->plugin_has_stats is
not updated. So it displays the stats as they were before the shutdown. But that's
not an issue in this case (when switching from non stats to stats).
Now, let's do some inserts and decode:
postgres=# insert into t1 values ('a');
INSERT 0 1
postgres=# select * from pg_logical_slot_get_changes('logical_slot',NULL,NULL);
lsn | xid | data
------------+-----+-----------------------------------------------------------------------------------------
0/407125B0 | 768 | xid 768: lsn:0/40712580 inserts:1 deletes:0 updates:0 truncates:0 relations truncated:0
(1 row)
and check the stats:
postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
plugin | plugin_sent_txns
----------------+------------------
pg_commit_info | 10
(1 row)
Now it reports 10, that's the 9 before we changed the plugin to not have stats
enabled plus this new one.
Issue 2: when switching from a non-stats plugin back to a stats-capable plugin, it
shows accumulated values from before the non-stats switch.
PFA attached a proposal to fix Issue 2.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Fri, Oct 24, 2025 at 03:23:44PM +0530, Ashutosh Bapat wrote: > On Mon, Oct 6, 2025 at 10:32 AM Ashutosh Bapat > wrote: > > > > On Fri, Oct 3, 2025 at 7:17 PM Bertrand Drouvot > > wrote: > > > > > > > Issue 2: Once X + 2 is loaded, further statistics are accumulated on > > > > the top of statistics accumulated by version X. Attached patch fixes > > > > issue 2 by zero'ing out the stats when the plugin does not report the > > > > statistics. > > > > > > +#define REPLSLOT_SET_TO_ZERO(fld) statent->fld = 0 > > > > > > It looks like that the associated "undef" is missing. > > > > Good catch. Fixed. > > > > Squashed patches into one and rebased. Thanks for the new version! LGTM except the plugin flip-flop behaviour that we discussed up-thread. That said I don't think it hurts that much and maybe that's just me and others don't have a concern with it (in that case that's fine by me). Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Thu, Dec 11, 2025 at 10:29:42AM +0530, Ashutosh Bapat wrote:
> Sorry for the delayed response. PFA the patch implementing the idea
> discussed above. It relies on the output plugin callback to return
> correct boolean but maintains the statistics in the core itself.
Thanks for the new patch version!
What worries me is all those API changes:
-typedef void (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx,
+typedef bool (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx,
Those changes will break existing third party logical decoding plugin, even ones
that don't want the new statistics features.
What about not changing those and just add a single new optional callback, say?
typedef void (*LogicalDecodeReportStatsCB)(
LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
bool *transaction_sent,
size_t *bytes_filtered
);
This way:
- Existing plugins can still work without modification
- New or existing plugins can choose to provide statistics
Thoughts?
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Thu, Dec 18, 2025 at 06:22:40PM +0530, Ashutosh Bapat wrote:
> Hi Bertrand,
>
> On Wed, Dec 17, 2025 at 2:12 PM Bertrand Drouvot
> wrote:
> >
> > What worries me is all those API changes:
> >
> > -typedef void (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx,
> > +typedef bool (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx,
> >
> > Those changes will break existing third party logical decoding plugin, even ones
> > that don't want the new statistics features.
> >
> > What about not changing those and just add a single new optional callback, say?
> >
> > typedef void (*LogicalDecodeReportStatsCB)(
> > LogicalDecodingContext *ctx,
> > ReorderBufferTXN *txn,
> > bool *transaction_sent,
> > size_t *bytes_filtered
> > );
> >
> > This way:
> >
> > - Existing plugins can still work without modification
> > - New or existing plugins can choose to provide statistics
> >
>
> I think that it will bring back the same problems that the previous
> design had or am I missing something?
I think that my example was confusing due to "size_t *bytes_filtered". I think
that what we could do is something like:
"
typedef void (*LogicalDecodeReportStatsCB)(
LogicalDecodingContext *ctx,
LogicalDecodeEventType event_type,
bool *filtered,
bool *txn_sent);
"
Note that there is no more size_t.
Then for, for example in change_cb_wrapper(), we could do:
"
ctx->callbacks.change_cb(ctx, txn, relation, change);
if (ctx->callbacks.report_stats_cb)
{
bool filtered = false;
ctx->callbacks.report_stats_cb(ctx, LOGICALDECODE_CHANGE,
&filtered, NULL);
if (filtered)
cache->filteredBytes += ReorderBufferChangeSize(change);
}
"
The plugin would need to "remember" that it filtered (so that it can
reply to the callback). It could do that by adding say "last_event_filtered" to
it's output_plugin_private structure.
That's more work on the plugin side and we would probably need to provide some
examples from our side.
I think the pros are that:
- plugins that don't want to report stats would have nothing to do (no breaking
changes)
- the core does the computation
Thoughts?
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi,
On Fri, Dec 19, 2025 at 12:32:49PM +0530, Ashutosh Bapat wrote:
> On Thu, Dec 18, 2025 at 11:52 PM Bertrand Drouvot
> wrote:
> >
> > I think that my example was confusing due to "size_t *bytes_filtered". I think
> > that what we could do is something like:
> >
> > "
> > typedef void (*LogicalDecodeReportStatsCB)(
> > LogicalDecodingContext *ctx,
> > LogicalDecodeEventType event_type,
> > bool *filtered,
> > bool *txn_sent);
> > "
> >
> > Note that there is no more size_t.
> >
>
> Thanks for the clarification. It fixes the problem of filteredBytes
> divergence. Since the core is calling stats callback, the problem of
> plugin not calling the function at appropriate places is also not
> there.
Yeah.
> IIUC, it still has some problems from the previous solution and
> some new problems as explained below.
>
> > Then for, for example in change_cb_wrapper(), we could do:
> >
> > "
> > ctx->callbacks.change_cb(ctx, txn, relation, change);
> >
> > if (ctx->callbacks.report_stats_cb)
> > {
> > bool filtered = false;
> >
> > ctx->callbacks.report_stats_cb(ctx, LOGICALDECODE_CHANGE,
> > &filtered, NULL);
> >
> > if (filtered)
> > cache->filteredBytes += ReorderBufferChangeSize(change);
> > }
> > "
> >
> > The plugin would need to "remember" that it filtered (so that it can
> > reply to the callback). It could do that by adding say "last_event_filtered" to
> > it's output_plugin_private structure.
>
>
> Why does the core send NULL for the second parameter? Does the output
> plugin have to take care of NULL references too?
It was just a quick example. I was more focused on demonstrating the concept than
the exact API details.
>
> I think the core will end up calling this or similar stanza at every
> callback since it won't know when the output plugin will have
> statistics to report.
Yes.
> That's more complexity and wasted CPU cycles in core.
I think that should be negligible as compared to what the logical decoding is
already doing at those places.
> > That's more work on the plugin side and we would probably need to provide some
> > examples from our side.
>
> Andres is objecting to this exact thing. IIUC, the code changes there
> were far simpler than this proposal. Am I missing something?
You are right. My main motivation with this idea was to avoid the APIs break.
But maybe that's not worth it.
> I don't think there will be an output plugin which wouldn't want to
> take advantage of the statistics. The easier it is for them to adopt
> the statistics, as is with my proposal, the better. With this proposal
> output plugins have to do more work if they want to support
> statistics. That itself will create a barrier for them to adopt the
> statistics. We want the output plugins to support statistics so that
> users can benefit. Let's make it easier for the output plugins to
> implement them.
That was the main point. With your proposal, the APIs break will occur (and so
the plugin will need some changes) even if they don't want the stats. But, if
we are confident that most (all?) would want to use it, then I agree that your
proposal is better and that's fine by me to move forward with yours.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Michael Paquier <michael@paquier.xyz>
Дата:
On Tue, Feb 10, 2026 at 09:10:45PM +0530, Ashutosh Bapat wrote: > It might be better to just use the phrase "This number ..." since the > earlier sentence mentions it as "number". I have made that change in > my repository. Will send it with the next version of patchset. I have studied this thread, and can get behind the idea of adding the number of bytes sent to the output plugin at this level. However, I think that patch 0001 should be split into two parts, because it is doing two separate things, not one: - One patch for the rename of the existing fields total_txns and total_bytes to respectively total_wal_txns and total_wal_bytes, so as it becomes possible to make the distinction between the stats data coming from WAL. (The suggested rename was a bit confusing for me first, FWIW, when I read total_wal_bytes, my mind understood that as a total number of WAL generated, even if I get that that it comes from an amount of WAL data processed when decoding transactions. Not an objection, just a feeling). - A second patch to introduce the new field for the number of bytes sent to the output plugin. I am also questioning why you did not make the choice of renaming totalBytes and totalTxns in ReorderBuffer. On consistency ground, at least, that seems relevant to me as these two numbers are the source data fed to pgstats. -- Michael
Re: Report bytes and transactions actually sent downtream
От:
Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Дата:
Hi, On Mon, Feb 02, 2026 at 02:06:07PM +0530, Ashutosh Bapat wrote: > On Tue, Jan 27, 2026 at 11:08 PM Andres Freund wrote: > > > > Hi, > > > > On 2025-12-11 10:29:42 +0530, Ashutosh Bapat wrote: > > > Please review. > > > > I'd simplify the patch to, initially, to just track the sent bytes. For one, > > that's by *far* the most useful statistic. But I also have some concerns > > about the other stats: > > > > Ok. I have split the patch into two Thanks! > 0001 to track sent_bytes > 0002 to track filtered_bytes and sent_txns > > I think we all agree that sent_bytes is useful, so I suggest that we > review and commit 0001. I had a look at 0001 and it looks ok to me. That's an extract of the previous patches that we already iterated over up-thread. I just have a few more comments: 1/ commit message s/This statistics/This statistic/ 2/ LogicalOutputWrite + /* Update the amount of data sent downstream. */ I think we can remove the period to be consistent with most of the one line comments around. 3/ WalSndWriteData Same as 2/., remove the period for the same comment. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Tue, Jul 1, 2025 at 7:35 PM Ashutosh Bapat wrote: > > On Tue, Jul 1, 2025 at 4:23 PM Amit Kapila wrote: > > > > On Mon, Jun 30, 2025 at 3:24 PM Ashutosh Bapat > > wrote: > > > > > > Hi All, > > > In a recent logical replication issue, there were multiple replication > > > slots involved, each using a different publication. Thus the amount of > > > data that was replicated through each slot was expected to be > > > different. However, total_bytes and total_txns were reported the same > > > for all the replication slots as expected. One of the slots started > > > lagging and we were trying to figure out whether its the WAL sender > > > slowing down or the consumer (in this case Debezium). The lagging > > > slot then showed total_txns and total_bytes lesser than other slots > > > giving an impression that the WAL sender is processing the data > > > slowly. Had pg_stat_replication_slot reported the amount of data > > > actually sent downstream, it would have been easier to compare it with > > > the amount of data received by the consumer and thus pinpoint the > > > bottleneck. > > > > > > Here's a patch to do the same. It adds two columns > > > - sent_txns: The total number of transactions sent downstream. > > > - sent_bytes: The total number of bytes sent downstream in data messages > > > to pg_stat_replication_slots. sent_bytes includes only the bytes sent > > > as part of 'd' messages and does not include keep alive messages or > > > CopyDone messages for example. But those are very few and can be > > > ignored. If others feel that those are important to be included, we > > > can make that change. > > > > > > Plugins may choose not to send an empty transaction downstream. It's > > > better to increment sent_txns counter in the plugin code when it > > > actually sends a BEGIN message, for example in pgoutput_send_begin() > > > and pg_output_begin(). This means that every plugin will need to be > > > modified to increment the counter for it to reported correctly. > > > > > > > What if some plugin didn't implemented it or does it incorrectly? > > Users will then complain that PG view is showing incorrect value. > > That is right. > > To fix the problem of plugins not implementing the counter increment > logic we could use logic similar to how we track whether > OutputPluginPrepareWrite() has been called or not. In > ReorderBufferTxn, we add a new member sent_status which would be an > enum with 3 values UNKNOWN, SENT, NOT_SENT. Initially the sent_status > = UNKNOWN. We provide a function called > plugin_sent_txn(ReorderBufferTxn txn, sent bool) which will set > sent_status = SENT when sent = true and sent_status = NOT_SENT when > sent = false. In all the end transaction callback wrappers like > commit_cb_wrapper(), prepare_cb_wrapper(), stream_abort_cb_wrapper(), > stream_commit_cb_wrapper() and stream_prepare_cb_wrapper(), if > tsent_status = UNKNOWN, we throw an error. > I think we don't want to make it mandatory for plugins to implement these stats, so instead of throwing ERROR, the view should show that the plugin doesn't provide stats. How about having OutputPluginStats similar to OutputPluginCallbacks and OutputPluginOptions members in LogicalDecodingContext? It will have members like stats_available, txns_sent or txns_skipped, txns_filtered, etc. I am thinking it will be better to provide this information in a separate view like pg_stat_plugin_stats or something like that, here we can report slot_name, plugin_name, then the other stats we want to implement part of OutputPluginStats. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Tue, Nov 4, 2025 at 4:29 PM Ashutosh Bapat wrote: > > On Mon, Nov 3, 2025 at 8:50 PM Andres Freund wrote: > > > > Hi, > > > > On 2025-11-03 19:53:30 +0530, Ashutosh Bapat wrote: > > > This commit adds following fields to pg_stat_replication_slots > > > - plugin_filtered_bytes is the amount of changes filtered out by the > > > output plugin > > > - plugin_sent_txns is the amount of transactions sent downstream by the > > > output plugin > > > - plugin_sent_bytes is the amount of data sent downstream by the output > > > plugin. > > > > > > The prefix "plugin_" indicates that these counters are related to and > > > maintained by the output plugin. An output plugin may choose not to > > > initialize LogicalDecodingContext::stats, which holds these counters, in > > > which case the above columns will be reported as NULL. > > > > I continue to be uncomfortable with doing all this tracking explicitly in > > output plugins. This still seems like something core infrastructure should > > take care of, instead of re-implementing it in different output plugins, with > > the inevitable behaviour differences that will entail. > > I understand your concern, and while I agree that it's ideal to keep > as much of the stats bookkeeping in core there are some nuances here > which makes it hard as explained below. > > My first patch [1] had the stats placed in ReorderBuffer directly. It > was evident from the patch that the sentTxns needs to be set somewhere > in the output plugin code since the output plugin may decide to filter > out or send transaction when processing a change in that transaction > (not necessarily when in begin_cb). Filtered bytes is also something > that is in plugin's control and needs to be updated in the output > plugin code. Few emails, starting from [2], discussed possible > approaches to maintain those in the core vs maintain those in the > output plugin. We decided to let output plugin maintain it for > following reasons > > a. sentTxns and filteredBytes need to be modified in the output plugin > code. The behaviour there is inherently output plugin specific, and > requires output plugin specific implementation. > Is it possible that we allow change callback (LogicalDecodeChangeCB) to return a boolean such that if the change is decoded and sent, it returns true, otherwise, false? If so, the caller could deduce from it the filtered bytes, and if none of the change calls returns true, this means the entire transaction is not sent. I think this should address Andres's concern of explicitly tracking these stats in plugins, what do you think? -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Mon, Jul 14, 2025 at 10:55 AM Ashutosh Bapat wrote: > > On Sun, Jul 13, 2025 at 4:34 PM Amit Kapila wrote: > > > > > > I think we don't want to make it mandatory for plugins to implement > > these stats, so instead of throwing ERROR, the view should show that > > the plugin doesn't provide stats. How about having OutputPluginStats > > similar to OutputPluginCallbacks and OutputPluginOptions members in > > LogicalDecodingContext? It will have members like stats_available, > > txns_sent or txns_skipped, txns_filtered, etc. > > Not making mandatory looks useful. I can try your suggestion. Rather > than having stats_available as a member of OutputPluginStats, it's > better to have a NULL value for the corresponding member in > LogicalDecodingContext. We don't want an output plugin to reset > stats_available once set. Will that work? > We can try that. > > I am thinking it will > > be better to provide this information in a separate view like > > pg_stat_plugin_stats or something like that, here we can report > > slot_name, plugin_name, then the other stats we want to implement part > > of OutputPluginStats. > > As you have previously pointed out, the view should make it explicit > that the new stats are maintained by the plugin and not core. I agree > with that intention. However, already have three views > pg_replication_slots (which has slot name and plugin name), then > pg_replication_stats which is about stats maintained by a WAL sender > or running replication and then pg_stat_replication_slots, which is > about accumulated statistics for a replication through a given > replication slot. It's already a bit hard to keep track of who's who > when debugging an issue. Adding one more view will add to confusion. > > Instead of adding a new view how about > a. name the columns as plugin_sent_txns, plugin_sent_bytes, > plugin_filtered_change_bytes to make it clear that these columns are > maintained by plugin > b. report these NULL if stats_available = false OR OutputPluginStats > is not set in LogicalDecodingContext > c. Document that NULL value for these columns indicates that the > plugin is not maintaining/reporting these stats > d. adding plugin name to pg_stat_replication_slots, that will make it > easy for users to know which plugin they should look at in case of > dubious or unavailable stats > Sounds reasonable. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Tue, Nov 18, 2025 at 4:05 PM Ashutosh Bapat wrote: > > On Tue, Nov 18, 2025 at 3:24 PM Amit Kapila wrote: > > > > On Tue, Nov 4, 2025 at 4:29 PM Ashutosh Bapat > > wrote: > > > > > > a. sentTxns and filteredBytes need to be modified in the output plugin > > > code. The behaviour there is inherently output plugin specific, and > > > requires output plugin specific implementation. > > > > > > > Is it possible that we allow change callback (LogicalDecodeChangeCB) > > to return a boolean such that if the change is decoded and sent, it > > returns true, otherwise, false? If so, the caller could deduce from it > > the filtered bytes, and if none of the change calls returns true, this > > means the entire transaction is not sent. > > > > I think this should address Andres's concern of explicitly tracking > > these stats in plugins, what do you think? > > > > I was thinking about a similar thing. But I am skeptical since the > calling logic is not straight forward - there's an indirection in > between. Second, it means that all the plugins have to adapt to the > new callback definition. It is optional in my current approach. Since > both of us have thought of this approach, I think it's worth a try. > > "if none of the change calls returns true, this means the entire > transaction is not sent" isn't true. A plugin may still send an empty > transaction. I was thinking of making commit/abort/prepare callbacks > to return true/false to indicate whether a transaction was sent or not > and increment the counter accordingly. The plugin has to take care of > not returning true for both prepare and commit or prepare and abort. > So may be just commit and abort should be made to return true or > false. What do you think? > Sounds reasonable to me. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Tue, Mar 17, 2026 at 11:32 AM Ashutosh Bapat wrote: > > On Tue, Mar 17, 2026 at 10:15 AM Amit Kapila wrote: > > > > > > Observation: I do not see UpdateDecodingStats() being triggered for > > > this non-transactional message. > > > > > > > I think that is probably a bug. I see that ReorderBufferQueueMessage() > > queues messages for transactional messages and which would then > > probably be sent later along with commit, so its data will be counted > > by UpdateDecodingStats. But the question is shouldn't we consider the > > data for non-transactional messages as well? > > > > I am also replying to your earlier comment > > > BTW, this also contains changes from pgoutput_message() which could be > > non-transactional. So, saying transaction changes may not be > > appropriate. > > > > We should consider the non-transactional messages as well in > sent_bytes, since that amount of data is sent. Whether we can use the > term "transaction changes" in the description of sent_bytes if we > include non-transactional messages is questionable. I used > "transactional changes" in the description of "sent_bytes" to be > consistent with the description of total_bytes. Looks like you are > suggesting that not accounting for non-transaction messages in > total_bytes is a bug, if we fix that, are we going to fix the > description of "total_bytes"? > Yes, we should do that. > If yes, it makes sense to mention > non-transactional messages separately in sent_bytes description. We > can modify my previous suggestion as > > Amount of transaction changes and non-transactional messages sent > downstream in the output plugin > Isn't it better to use something on the lines of what Michael is proposing in his last email [1]? [1] - https://www.postgresql.org/message-id/abiQe4fxR2fp317F%40paquier.xyz -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Thu, Jun 25, 2026 at 4:37 AM Masahiko Sawada wrote: > > On Tue, Jun 16, 2026 at 2:06 AM Ashutosh Bapat > wrote: > > > > > > Those are logical message types which are part of the logical change > > data - without those messages it's not possible to process the logical > > change data. So they are included. But the keepalive messages, for > > example, aren't part of the logical change data. > > I think logical replication messages like STREAM START/STREAM STOP and > BEGIN/END, versus messages like keepalive and standby/primary status > updates, operate at different layers. The former are the contents of > the logical output and seem to belong naturally to the replication > slot's statistics. I'm not sure the latter should be included as well > -- I'm concerned that counting those bytes would become noise when > analyzing the statistics over time, since they have no relation to the > volume of logical changes. > Both you and Ashutosh seem to make the similar points and sound reasonable, so let's do it that way. > If we do want a statistic showing the literal total bytes sent > downstream including protocol messages, ISTM that should be available > for both logical and physical replication: physical replication also > uses keepalive messages and adds a header to each message. In other > words, that kind of "bytes on the wire" metric isn't really specific > to a logical replication slot, so the slot's statistics don't seem > like the right place for it. > > The proposed column name 'sent_bytes' is also confusing to me, because > I don't think we can call it "total bytes actually sent" in the > logical decoding SQL API case. A name like 'plugin_total_bytes' seems > more straightforward and conveys the intent that protocol messages are > not included. > The SQL API point is genuine and if we display sent_bytes via SQL API then pg_logical_slot_get_changes() will show nonzero sent_bytes even though nothing was ever sent anywhere. OTOH, adding plugin_* prefix also starts to make it sound like stats are plugin specific, how about calling it as 'output_bytes'? It pairs cleanly with the existing column: total_bytes = decoded into the reorder buffer, output_bytes = decoded and handed to the consumer. If we use output_bytes, then we can describe the new stats on the lines of following text in the docs: Amount of decoded data produced for this slot's consumer by the output plugin, after applying any output plugin filters and converting the changes into the output plugin's format. This counts the transaction changes together with the messages that delimit them (such as the begin and commit messages), but not connection-management messages such as keepalives, which are generated by the server rather than the output plugin and are therefore not included. This value can differ from total_bytes: it may be smaller because filtered changes are not output, or larger because the output plugin's format can be more verbose than the decoded changes. For these reasons output_bytes is not directly comparable to total_bytes. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Mon, Jun 30, 2025 at 3:24 PM Ashutosh Bapat wrote: > > Hi All, > In a recent logical replication issue, there were multiple replication > slots involved, each using a different publication. Thus the amount of > data that was replicated through each slot was expected to be > different. However, total_bytes and total_txns were reported the same > for all the replication slots as expected. One of the slots started > lagging and we were trying to figure out whether its the WAL sender > slowing down or the consumer (in this case Debezium). The lagging > slot then showed total_txns and total_bytes lesser than other slots > giving an impression that the WAL sender is processing the data > slowly. Had pg_stat_replication_slot reported the amount of data > actually sent downstream, it would have been easier to compare it with > the amount of data received by the consumer and thus pinpoint the > bottleneck. > > Here's a patch to do the same. It adds two columns > - sent_txns: The total number of transactions sent downstream. > - sent_bytes: The total number of bytes sent downstream in data messages > to pg_stat_replication_slots. sent_bytes includes only the bytes sent > as part of 'd' messages and does not include keep alive messages or > CopyDone messages for example. But those are very few and can be > ignored. If others feel that those are important to be included, we > can make that change. > > Plugins may choose not to send an empty transaction downstream. It's > better to increment sent_txns counter in the plugin code when it > actually sends a BEGIN message, for example in pgoutput_send_begin() > and pg_output_begin(). This means that every plugin will need to be > modified to increment the counter for it to reported correctly. > What if some plugin didn't implemented it or does it incorrectly? Users will then complain that PG view is showing incorrect value. Shouldn't the plugin specific stats be shown differently, for example, one may be interested in how much plugin has filtered the data because it was not published or because something like row_filter caused it skip sending such data? -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Mon, Jun 15, 2026 at 2:34 PM Kyotaro Horiguchi wrote: > > At Mon, 15 Jun 2026 13:52:36 +0530, Ashutosh Sharma wrote in > > Sorry for chiming in - I may well be misunderstanding this, but here's > > how I'm currently thinking about it: > > > > Total transaction bytes refers to the size of decoded transactional > > data accumulated in the reorder buffer for a given transaction. > > > > Sent bytes (as I understand from the patch) refers to the size of the > > downstream output that the output plugin produces from that decoded > > data, after any filtering and format conversion. > > > > To illustrate: if a transaction's decoded changes occupy 550 bytes in > > the reorder buffer, but the output plugin filters some out and emits > > only 300 bytes downstream, then total transaction bytes = 550 and sent > > bytes = 300. Conversely, if all 550 bytes are converted into a more > > verbose format and emitted as 700 bytes, total transaction bytes > > remains 550 while sent bytes becomes 700. > > > > If I'm reading this right, since total bytes for a transaction is the > > baseline from which transaction-derived downstream output is produced, > > I wonder whether sent bytes should include only that > > transaction-derived downstream output, or also downstream protocol > > traffic such as keepalive messages, which are sent downstream but are > > not derived from transaction bytes in the reorder buffer. > > > > My instinct is that if sent bytes are meant to measure > > transaction-output throughput, keepalive messages probably shouldn't > > be included, since they have no basis in transaction data and might > > distort any comparison with total bytes. But I could be wrong - happy > > to be corrected! > > Thank you for the explanation. > > I think I understand the distinction you are making. However, my > question is one step earlier than the keepalive-message question. I am > wondering whether the new metric needs to be defined in terms of > logical-change output in the first place. > > If I understand the use case correctly, I think the discussion here is > primarily about relatively high-volume logical replication > workloads. My point is that, in that situation, I would expect the > amount of logical-change output and the amount of data actually sent > over the replication connection to show broadly similar trends. > > The latter seems easier to interpret, while still providing a useful > signal for monitoring and capacity-planning purposes. It also seems > more intuitive, since it corresponds directly to the amount of data > sent over the replication connection. > BTW, the patch internally counts other protocol messages like START STREAM/STOP STREAM, BEGIN/END, and quite a few others that help apply workers to understand transaction boundaries and messages. So, I feel in that sense we are already counting protocol bytes as part of patch, so why leave the additional messages that are being discussed here. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Fri, Mar 13, 2026 at 7:39 AM Michael Paquier wrote: > > On Thu, Mar 12, 2026 at 06:13:33PM +0530, Ashutosh Sharma wrote: > > Could you please share your thoughts on this? Your inputs would help > > us determine the next steps - whether we should proceed with renaming, > > and if so, what names you would suggest, or whether we should leave > > things as they are. > > FWIW, I still find the use of _wal_ in these fields rather confusing, > and they add more inconsistencies with the internal structures of > reorderbuffer.c. The goal is to add a field to track the number of > bytes sent downstream. Hence, I would suggest to give up on the > rename, add the new field, perhaps consider improving the docs for the > existing fields to tell to which context these numbers refer to, then > call it a day. > +1. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Mon, Mar 16, 2026 at 1:21 PM Ashutosh Sharma wrote: > > 1) v20260316-0001-Report-downstream-sent-bytes-in-pg_stat_replication_.patch: > > This patch introduces sent_bytes to report the amount of data sent > downstream. It documents sent_bytes in a way that it clarifies how > sent_bytes differs from total_bytes, without modifying the existing > documentation for total_bytes or total_txns. The patch is purely > additive and does not alter any existing documentation. > * + sent_bytesbigint + + + Amount of transaction changes sent downstream for this slot by the + output plugin after applying output plugin filters, if any, and + converting it into the output plugin format. BTW, this also contains changes from pgoutput_message() which could be non-transactional. So, saying transaction changes may not be appropriate. * We also send keep_alive kind of messages via wal_sender, those are not counted in sent_bytes. If we don't want to include those then does that need a mention? -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Mon, Mar 16, 2026 at 7:18 PM Ashutosh Sharma wrote:
>
> On Mon, Mar 16, 2026 at 4:40 PM Amit Kapila wrote:
> >
> > On Mon, Mar 16, 2026 at 1:21 PM Ashutosh Sharma wrote:
> > >
> > > 1) v20260316-0001-Report-downstream-sent-bytes-in-pg_stat_replication_.patch:
> > >
> > > This patch introduces sent_bytes to report the amount of data sent
> > > downstream. It documents sent_bytes in a way that it clarifies how
> > > sent_bytes differs from total_bytes, without modifying the existing
> > > documentation for total_bytes or total_txns. The patch is purely
> > > additive and does not alter any existing documentation.
> > >
> >
> > *
> > + sent_bytesbigint
> > +
> > +
> > + Amount of transaction changes sent downstream for this slot by the
> > + output plugin after applying output plugin filters, if any, and
> > + converting it into the output plugin format.
> >
> > BTW, this also contains changes from pgoutput_message() which could be
> > non-transactional. So, saying transaction changes may not be
> > appropriate.
> >
>
> I tried this based on my current understanding on this and observed
> the follwoing:
>
> \c postgres
>
> CREATE PUBLICATION dbg_pub FOR ALL TABLES;
>
> SELECT * FROM pg_create_logical_replication_slot('dbg_pgout', 'pgoutput');
>
> pg_recvlogical -d postgres \
> --slot=dbg_pgout \
> --start \
> -f /tmp/dbg_pgout.bin \
> -o proto_version='1' \
> -o publication_names='dbg_pub' \
> -o messages='true'
>
> SELECT pg_logical_emit_message(false, 'dbg', 'hello-non-tx');
>
> SELECT slot_name, sent_txns, sent_bytes
> FROM pg_stat_replication_slots
> WHERE slot_name = 'dbg_pgout';
>
> Observation: I do not see UpdateDecodingStats() being triggered for
> this non-transactional message.
>
I think that is probably a bug. I see that ReorderBufferQueueMessage()
queues messages for transactional messages and which would then
probably be sent later along with commit, so its data will be counted
by UpdateDecodingStats. But the question is shouldn't we consider the
data for non-transactional messages as well?
>
> Please correct me in case my understanding was wrong and I should have
> checked for something else to verify this.
>
It doesn't seem to be related to this patch, so we can fix that
separately if we agree that it is a bug.
--
With Regards,
Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Tue, Mar 17, 2026 at 4:51 AM Michael Paquier wrote: > > The trick with this patch is to make sure that all the relevant places > where data is sent downstream are correctly incremented. > Right. > As far as I > can see, things seem to be covered, but I cannot help but wonder if we > are missing one or more places. @Amit, do you feel a hole somewhere? > Yesterday, I raised a point related to this which is whether we want to count messages like keep_alive, copy_done, or PqReplMsg_PrimaryStatusUpdate? These don't contain the user decoded data from WAL but some additional information between publisher-subscriber required to ensure data is being applied. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Amit Kapila <amit.kapila16@gmail.com>
Дата:
On Mon, Mar 16, 2026 at 4:40 PM Amit Kapila wrote: > > On Mon, Mar 16, 2026 at 1:21 PM Ashutosh Sharma wrote: > > > > 1) v20260316-0001-Report-downstream-sent-bytes-in-pg_stat_replication_.patch: > > > > This patch introduces sent_bytes to report the amount of data sent > > downstream. It documents sent_bytes in a way that it clarifies how > > sent_bytes differs from total_bytes, without modifying the existing > > documentation for total_bytes or total_txns. The patch is purely > > additive and does not alter any existing documentation. > > > > * > + sent_bytesbigint > + > + > + Amount of transaction changes sent downstream for this slot by the > + output plugin after applying output plugin filters, if any, and > + converting it into the output plugin format. > > BTW, this also contains changes from pgoutput_message() which could be > non-transactional. So, saying transaction changes may not be > appropriate. > > * We also send keep_alive kind of messages via wal_sender, those are > not counted in sent_bytes. > I think the other one to consider in the same category is the PqMsg_CopyDone message. -- With Regards, Amit Kapila.
Re: Report bytes and transactions actually sent downtream
От:
Masahiko Sawada <sawada.mshk@gmail.com>
Дата:
On Tue, Jun 16, 2026 at 2:06 AM Ashutosh Bapat wrote: > > On Mon, Jun 15, 2026 at 7:23 PM Amit Kapila wrote: > > > > On Mon, Jun 15, 2026 at 2:34 PM Kyotaro Horiguchi > > wrote: > > > > > > At Mon, 15 Jun 2026 13:52:36 +0530, Ashutosh Sharma wrote in > > > > Sorry for chiming in - I may well be misunderstanding this, but here's > > > > how I'm currently thinking about it: > > > > > > > > Total transaction bytes refers to the size of decoded transactional > > > > data accumulated in the reorder buffer for a given transaction. > > > > > > > > Sent bytes (as I understand from the patch) refers to the size of the > > > > downstream output that the output plugin produces from that decoded > > > > data, after any filtering and format conversion. > > > > > > > > To illustrate: if a transaction's decoded changes occupy 550 bytes in > > > > the reorder buffer, but the output plugin filters some out and emits > > > > only 300 bytes downstream, then total transaction bytes = 550 and sent > > > > bytes = 300. Conversely, if all 550 bytes are converted into a more > > > > verbose format and emitted as 700 bytes, total transaction bytes > > > > remains 550 while sent bytes becomes 700. > > > > > > > > If I'm reading this right, since total bytes for a transaction is the > > > > baseline from which transaction-derived downstream output is produced, > > > > I wonder whether sent bytes should include only that > > > > transaction-derived downstream output, or also downstream protocol > > > > traffic such as keepalive messages, which are sent downstream but are > > > > not derived from transaction bytes in the reorder buffer. > > > > > > > > My instinct is that if sent bytes are meant to measure > > > > transaction-output throughput, keepalive messages probably shouldn't > > > > be included, since they have no basis in transaction data and might > > > > distort any comparison with total bytes. But I could be wrong - happy > > > > to be corrected! > > > > > > Thank you for the explanation. > > > > > > I think I understand the distinction you are making. However, my > > > question is one step earlier than the keepalive-message question. I am > > > wondering whether the new metric needs to be defined in terms of > > > logical-change output in the first place. > > > > > > If I understand the use case correctly, I think the discussion here is > > > primarily about relatively high-volume logical replication > > > workloads. My point is that, in that situation, I would expect the > > > amount of logical-change output and the amount of data actually sent > > > over the replication connection to show broadly similar trends. > > > > > > The latter seems easier to interpret, while still providing a useful > > > signal for monitoring and capacity-planning purposes. It also seems > > > more intuitive, since it corresponds directly to the amount of data > > > sent over the replication connection. > > > > > > > BTW, the patch internally counts other protocol messages like START > > STREAM/STOP STREAM, BEGIN/END, and quite a few others that help apply > > workers to understand transaction boundaries and messages. So, I feel > > in that sense we are already counting protocol bytes as part of patch, > > so why leave the additional messages that are being discussed here. > > Those are logical message types which are part of the logical change > data - without those messages it's not possible to process the logical > change data. So they are included. But the keepalive messages, for > example, aren't part of the logical change data. I think logical replication messages like STREAM START/STREAM STOP and BEGIN/END, versus messages like keepalive and standby/primary status updates, operate at different layers. The former are the contents of the logical output and seem to belong naturally to the replication slot's statistics. I'm not sure the latter should be included as well -- I'm concerned that counting those bytes would become noise when analyzing the statistics over time, since they have no relation to the volume of logical changes. If we do want a statistic showing the literal total bytes sent downstream including protocol messages, ISTM that should be available for both logical and physical replication: physical replication also uses keepalive messages and adds a header to each message. In other words, that kind of "bytes on the wire" metric isn't really specific to a logical replication slot, so the slot's statistics don't seem like the right place for it. The proposed column name 'sent_bytes' is also confusing to me, because I don't think we can call it "total bytes actually sent" in the logical decoding SQL API case. A name like 'plugin_total_bytes' seems more straightforward and conveys the intent that protocol messages are not included. Regards, -- Masahiko Sawada Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Masahiko Sawada <sawada.mshk@gmail.com>
Дата:
On Tue, Jun 30, 2026 at 5:39 AM Ashutosh Sharma wrote: > > Hi, > > On Tue, Jun 30, 2026 at 12:31 PM Ashutosh Sharma wrote: > > > > Thank you, Masahiko-san, for your review comments. Please find my > > responses inline below: > > > > On Tue, Jun 30, 2026 at 4:32 AM Masahiko Sawada wrote: > > > > > > --- > > > @@ -1979,6 +1980,7 @@ UpdateDecodingStats(LogicalDecodingContext *ctx) > > > repSlotStat.mem_exceeded_count = rb->memExceededCount; > > > repSlotStat.total_txns = rb->totalTxns; > > > repSlotStat.total_bytes = rb->totalBytes; > > > + repSlotStat.output_bytes = rb->outputBytes; > > > > > > Since output_bytes is now the tenth field we accumulate for > > > replication slot statistics, it seems like a good opportunity to > > > improve its maintainability. Instead of adding fields individually, we > > > could define a common structure for these shared metrics so that both > > > PgStat_StatReplSlotEntry and ReorderBuffer can embed it. This way, > > > UpdateDecodingStats() could simply assign the collected struct and > > > clear it using MemSet(). > > > > > > > I agree that this would improve maintainability. > > PgStat_StatReplSlotEntry now has 13 fields, and 10 of them are the > > decoding counters that are also tracked in ReorderBuffer, so a common > > counter-only structure sounds like the right direction. > > > > That said, this is not specific to the output_bytes field being added > > here. It is more of a refactoring of the boundary between logical > > decoding and pgstat statistics. To keep this patch focused, I think it > > would be better to handle that as a separate follow-up patch, possibly > > with a new thread once this work is done. Please let me know your > > thoughts on this? > > > > In case you'd prefer to have the refactoring done as part of this > effort, I'm attaching a patch that performs the refactoring described > above, with the patch for reporting output_bytes built on top of it. > Please have a look and let me know your feedback. Thank you for updating the patch. I agree that the refactoring part should be done in a separate patch. Could you swap these patches' order? I think that these patches are independent of each other and while we have an agreement on the new statistics, we still need discussion for the refactoring part (particularly I guess it's better to avoid including pgstatt.h in reorderbuffer.h) Regards, -- Masahiko Sawada Amazon Web Services: https://aws.amazon.com
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
On Fri, Mar 13, 2026 at 7:39 AM Michael Paquier wrote: > > On Thu, Mar 12, 2026 at 06:13:33PM +0530, Ashutosh Sharma wrote: > > Could you please share your thoughts on this? Your inputs would help > > us determine the next steps - whether we should proceed with renaming, > > and if so, what names you would suggest, or whether we should leave > > things as they are. > > FWIW, I still find the use of _wal_ in these fields rather confusing, > and they add more inconsistencies with the internal structures of > reorderbuffer.c. The goal is to add a field to track the number of > bytes sent downstream. Hence, I would suggest to give up on the > rename, add the new field, perhaps consider improving the docs for the > existing fields to tell to which context these numbers refer to, then > call it a day. Thanks for sharing your thoughts, Michael, I had a similar idea in mind. I'll go ahead and implement these changes and come up with the new patches. Now since this is purely additive (new columns only), I'm thinking a single patch might suffice, Or maybe, we could split it into two: one that corrects the existing documentation and another that introduces the new fields for displaying downstream bytes sent and some other information related to this. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi, On Tue, Jun 30, 2026 at 12:31 PM Ashutosh Sharma wrote: > > Thank you, Masahiko-san, for your review comments. Please find my > responses inline below: > > On Tue, Jun 30, 2026 at 4:32 AM Masahiko Sawada wrote: > > > > --- > > @@ -1979,6 +1980,7 @@ UpdateDecodingStats(LogicalDecodingContext *ctx) > > repSlotStat.mem_exceeded_count = rb->memExceededCount; > > repSlotStat.total_txns = rb->totalTxns; > > repSlotStat.total_bytes = rb->totalBytes; > > + repSlotStat.output_bytes = rb->outputBytes; > > > > Since output_bytes is now the tenth field we accumulate for > > replication slot statistics, it seems like a good opportunity to > > improve its maintainability. Instead of adding fields individually, we > > could define a common structure for these shared metrics so that both > > PgStat_StatReplSlotEntry and ReorderBuffer can embed it. This way, > > UpdateDecodingStats() could simply assign the collected struct and > > clear it using MemSet(). > > > > I agree that this would improve maintainability. > PgStat_StatReplSlotEntry now has 13 fields, and 10 of them are the > decoding counters that are also tracked in ReorderBuffer, so a common > counter-only structure sounds like the right direction. > > That said, this is not specific to the output_bytes field being added > here. It is more of a refactoring of the boundary between logical > decoding and pgstat statistics. To keep this patch focused, I think it > would be better to handle that as a separate follow-up patch, possibly > with a new thread once this work is done. Please let me know your > thoughts on this? > In case you'd prefer to have the refactoring done as part of this effort, I'm attaching a patch that performs the refactoring described above, with the patch for reporting output_bytes built on top of it. Please have a look and let me know your feedback. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi, On Tue, Jun 16, 2026 at 4:55 PM Ashutosh Sharma wrote: > > On Tue, Jun 16, 2026 at 2:36 PM Ashutosh Bapat > wrote: > > > > On Mon, Jun 15, 2026 at 7:23 PM Amit Kapila wrote: > > > > > > On Mon, Jun 15, 2026 at 2:34 PM Kyotaro Horiguchi > > > wrote: > > > > > > > > At Mon, 15 Jun 2026 13:52:36 +0530, Ashutosh Sharma wrote in > > > > > Sorry for chiming in - I may well be misunderstanding this, but here's > > > > > how I'm currently thinking about it: > > > > > > > > > > Total transaction bytes refers to the size of decoded transactional > > > > > data accumulated in the reorder buffer for a given transaction. > > > > > > > > > > Sent bytes (as I understand from the patch) refers to the size of the > > > > > downstream output that the output plugin produces from that decoded > > > > > data, after any filtering and format conversion. > > > > > > > > > > To illustrate: if a transaction's decoded changes occupy 550 bytes in > > > > > the reorder buffer, but the output plugin filters some out and emits > > > > > only 300 bytes downstream, then total transaction bytes = 550 and sent > > > > > bytes = 300. Conversely, if all 550 bytes are converted into a more > > > > > verbose format and emitted as 700 bytes, total transaction bytes > > > > > remains 550 while sent bytes becomes 700. > > > > > > > > > > If I'm reading this right, since total bytes for a transaction is the > > > > > baseline from which transaction-derived downstream output is produced, > > > > > I wonder whether sent bytes should include only that > > > > > transaction-derived downstream output, or also downstream protocol > > > > > traffic such as keepalive messages, which are sent downstream but are > > > > > not derived from transaction bytes in the reorder buffer. > > > > > > > > > > My instinct is that if sent bytes are meant to measure > > > > > transaction-output throughput, keepalive messages probably shouldn't > > > > > be included, since they have no basis in transaction data and might > > > > > distort any comparison with total bytes. But I could be wrong - happy > > > > > to be corrected! > > > > > > > > Thank you for the explanation. > > > > > > > > I think I understand the distinction you are making. However, my > > > > question is one step earlier than the keepalive-message question. I am > > > > wondering whether the new metric needs to be defined in terms of > > > > logical-change output in the first place. > > > > > > > > If I understand the use case correctly, I think the discussion here is > > > > primarily about relatively high-volume logical replication > > > > workloads. My point is that, in that situation, I would expect the > > > > amount of logical-change output and the amount of data actually sent > > > > over the replication connection to show broadly similar trends. > > > > > > > > The latter seems easier to interpret, while still providing a useful > > > > signal for monitoring and capacity-planning purposes. It also seems > > > > more intuitive, since it corresponds directly to the amount of data > > > > sent over the replication connection. > > > > > > > > > > BTW, the patch internally counts other protocol messages like START > > > STREAM/STOP STREAM, BEGIN/END, and quite a few others that help apply > > > workers to understand transaction boundaries and messages. So, I feel > > > in that sense we are already counting protocol bytes as part of patch, > > > so why leave the additional messages that are being discussed here. > > > > Those are logical message types which are part of the logical change > > data - without those messages it's not possible to process the logical > > change data. So they are included. But the keepalive messages, for > > example, aren't part of the logical change data. > > > > I think it would be worth capturing this distinction clearly in the > user documentation. Thanks Amit for raising this point. > PFA a patch rebased on HEAD. It also includes a small documentation correction based on our recent discussion, clarifying that total_bytes is not necessarily greater than sent_bytes. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi Michael, On Fri, Feb 13, 2026 at 6:28 PM Ashutosh Bapat wrote: > > On Fri, Feb 13, 2026 at 10:47 AM Michael Paquier wrote: > > > > On Tue, Feb 10, 2026 at 09:10:45PM +0530, Ashutosh Bapat wrote: > > > It might be better to just use the phrase "This number ..." since the > > > earlier sentence mentions it as "number". I have made that change in > > > my repository. Will send it with the next version of patchset. > > > > I have studied this thread, and can get behind the idea of adding the > > number of bytes sent to the output plugin at this level. > > > > However, I think that patch 0001 should be split into two parts, > > because it is doing two separate things, not one: > > - One patch for the rename of the existing fields total_txns and > > total_bytes to respectively total_wal_txns and total_wal_bytes, so as > > it becomes possible to make the distinction between the stats data > > coming from WAL. (The suggested rename was a bit confusing for me > > first, FWIW, when I read total_wal_bytes, my mind understood that as a > > total number of WAL generated, even if I get that that it comes from > > an amount of WAL data processed when decoding transactions. Not an > > objection, just a feeling). > > - A second patch to introduce the new field for the number of bytes > > sent to the output plugin. > > > > I am open to other suggestions. Since the bytes in total_bytes and > sent_bytes refer to different set of bytes, it was thought to be > better to rename total_bytes and hence total_txns. But then > stream_bytes, spill_bytes too come from WAL and they are not renamed > right now. So there's already some awkwardness. I also see that wal in > the name can be confusing. I thought of using "reordered" instead of > "wal" but reorder buffer is an internal concept. If we change the > reorder buffer mechanism tomorrow, we will need to change the name of > the field. So we discarded that. I am wondering whether we should just > leave the name as is and rely on documentation to clarify the > difference. What do you think? > Could you please share your thoughts on this? Your inputs would help us determine the next steps - whether we should proceed with renaming, and if so, what names you would suggest, or whether we should leave things as they are. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
On Tue, Jun 16, 2026 at 2:36 PM Ashutosh Bapat wrote: > > On Mon, Jun 15, 2026 at 7:23 PM Amit Kapila wrote: > > > > On Mon, Jun 15, 2026 at 2:34 PM Kyotaro Horiguchi > > wrote: > > > > > > At Mon, 15 Jun 2026 13:52:36 +0530, Ashutosh Sharma wrote in > > > > Sorry for chiming in - I may well be misunderstanding this, but here's > > > > how I'm currently thinking about it: > > > > > > > > Total transaction bytes refers to the size of decoded transactional > > > > data accumulated in the reorder buffer for a given transaction. > > > > > > > > Sent bytes (as I understand from the patch) refers to the size of the > > > > downstream output that the output plugin produces from that decoded > > > > data, after any filtering and format conversion. > > > > > > > > To illustrate: if a transaction's decoded changes occupy 550 bytes in > > > > the reorder buffer, but the output plugin filters some out and emits > > > > only 300 bytes downstream, then total transaction bytes = 550 and sent > > > > bytes = 300. Conversely, if all 550 bytes are converted into a more > > > > verbose format and emitted as 700 bytes, total transaction bytes > > > > remains 550 while sent bytes becomes 700. > > > > > > > > If I'm reading this right, since total bytes for a transaction is the > > > > baseline from which transaction-derived downstream output is produced, > > > > I wonder whether sent bytes should include only that > > > > transaction-derived downstream output, or also downstream protocol > > > > traffic such as keepalive messages, which are sent downstream but are > > > > not derived from transaction bytes in the reorder buffer. > > > > > > > > My instinct is that if sent bytes are meant to measure > > > > transaction-output throughput, keepalive messages probably shouldn't > > > > be included, since they have no basis in transaction data and might > > > > distort any comparison with total bytes. But I could be wrong - happy > > > > to be corrected! > > > > > > Thank you for the explanation. > > > > > > I think I understand the distinction you are making. However, my > > > question is one step earlier than the keepalive-message question. I am > > > wondering whether the new metric needs to be defined in terms of > > > logical-change output in the first place. > > > > > > If I understand the use case correctly, I think the discussion here is > > > primarily about relatively high-volume logical replication > > > workloads. My point is that, in that situation, I would expect the > > > amount of logical-change output and the amount of data actually sent > > > over the replication connection to show broadly similar trends. > > > > > > The latter seems easier to interpret, while still providing a useful > > > signal for monitoring and capacity-planning purposes. It also seems > > > more intuitive, since it corresponds directly to the amount of data > > > sent over the replication connection. > > > > > > > BTW, the patch internally counts other protocol messages like START > > STREAM/STOP STREAM, BEGIN/END, and quite a few others that help apply > > workers to understand transaction boundaries and messages. So, I feel > > in that sense we are already counting protocol bytes as part of patch, > > so why leave the additional messages that are being discussed here. > > Those are logical message types which are part of the logical change > data - without those messages it's not possible to process the logical > change data. So they are included. But the keepalive messages, for > example, aren't part of the logical change data. > I think it would be worth capturing this distinction clearly in the user documentation. Thanks Amit for raising this point. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi, On Mon, Mar 16, 2026 at 12:20 PM Michael Paquier wrote: > > On Mon, Mar 16, 2026 at 08:55:17AM +0530, Ashutosh Bapat wrote: > > I would post two patches > > 1. To introduce the new column sent_bytes + clarify total_bytes in the > > context of sent_bytes. We should consider this patch for committing. > > Sounds to me like two patches: one for the new column, a second for > the docs of the existing columns. Could somebody send these patches? > I would be OK to look at them once posted, driving things for this CF. > PFA patches: 1) v20260316-0001-Report-downstream-sent-bytes-in-pg_stat_replication_.patch: This patch introduces sent_bytes to report the amount of data sent downstream. It documents sent_bytes in a way that it clarifies how sent_bytes differs from total_bytes, without modifying the existing documentation for total_bytes or total_txns. The patch is purely additive and does not alter any existing documentation. 2) v20260316-0002-Report-output-plugin-statistics-in-pg_stat_replicati.patch: This patch adds a couple of more metrics to report the amount of changes filtered out by the output plugin, as well as the total number of transactions sent downstream. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
> 0001 is the previous patch > 0002 changes addressing your and Bertrand's comments. > @@ -1573,6 +1573,13 @@ WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, /* output previously gathered data in a CopyData packet */ pq_putmessage_noblock(PqMsg_CopyData, ctx->out->data, ctx->out->len); + /* + * If output plugin maintains statistics, update the amount of data sent + * downstream. + */ + if (ctx->stats) + ctx->stats->sentBytes += ctx->out->len + 1; /* +1 for the 'd' */ + Just a small observation: I think it’s actually pq_flush_if_writable() that writes the buffered data to the socket, not pq_putmessage_noblock (which is actually gathering data in the buffer and not sending). So it might make more sense to increment the sent pointer after the call to pq_flush_if_writable(). Should we also consider - pg_hton32((uint32) (len + 4)); -- the additional 4 bytes of data added to the send buffer. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi, On Fri, Jun 12, 2026 at 12:32 PM Kyotaro Horiguchi wrote: > > Let me go back to the original motivation. > > As I understand it, the problem was that, among several subscribers > connected through different slots, one slot was lagging behind. The > total_txns and total_bytes values for that slot appeared smaller, and > you wanted to know where the bottleneck was. > > I wonder if part of the confusion comes from the fact that it is not > yet clear what conclusions a user is expected to draw from these new > values. > > What I am still not quite sure about is how the proposed sent_bytes > value would be used to make that distinction. It sounds like the idea > is not necessarily to look at its rate over time, but perhaps to > compare total_bytes with the proposed sent_bytes. However, as has > been discussed, these two values seem to measure different things, so > I am not sure that such a comparison would be straightforward. > > I think it would help move the discussion forward if you could explain > more concretely how these new values would be used to identify the > bottleneck in the case you described. > Regarding the use case of this new metric, this is my understanding, although Ashutosh Bapat would be in a better position to comment on its intended value and use cases. I'll wait for his perspective as well. My understanding is as follows: During logical decoding, ReorderBufferProcessTXN() iterates through the decoded changes belonging to a top-level transaction and its subtransactions in LSN order, invoking the output plugin callbacks for each change. For large transactions, it may be useful to expose progress while this processing is still in progress. For example, the system could track logical changes as they are processed and sent downstream, periodically publishing progress to shared memory so that users can understand how far the walsender has progressed through a large transaction. By contrast, total_bytes is essentially an end-of-transaction metric. It becomes available only after processing has completed and therefore does not provide visibility into the walsender's progress while a large transaction is actively being decoded and streamed. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi,
On Mon, Mar 16, 2026 at 4:40 PM Amit Kapila wrote:
>
> On Mon, Mar 16, 2026 at 1:21 PM Ashutosh Sharma wrote:
> >
> > 1) v20260316-0001-Report-downstream-sent-bytes-in-pg_stat_replication_.patch:
> >
> > This patch introduces sent_bytes to report the amount of data sent
> > downstream. It documents sent_bytes in a way that it clarifies how
> > sent_bytes differs from total_bytes, without modifying the existing
> > documentation for total_bytes or total_txns. The patch is purely
> > additive and does not alter any existing documentation.
> >
>
> *
> + sent_bytesbigint
> +
> +
> + Amount of transaction changes sent downstream for this slot by the
> + output plugin after applying output plugin filters, if any, and
> + converting it into the output plugin format.
>
> BTW, this also contains changes from pgoutput_message() which could be
> non-transactional. So, saying transaction changes may not be
> appropriate.
>
I tried this based on my current understanding on this and observed
the follwoing:
\c postgres
CREATE PUBLICATION dbg_pub FOR ALL TABLES;
SELECT * FROM pg_create_logical_replication_slot('dbg_pgout', 'pgoutput');
pg_recvlogical -d postgres \
--slot=dbg_pgout \
--start \
-f /tmp/dbg_pgout.bin \
-o proto_version='1' \
-o publication_names='dbg_pub' \
-o messages='true'
SELECT pg_logical_emit_message(false, 'dbg', 'hello-non-tx');
SELECT slot_name, sent_txns, sent_bytes
FROM pg_stat_replication_slots
WHERE slot_name = 'dbg_pgout';
Observation: I do not see UpdateDecodingStats() being triggered for
this non-transactional message. However, if I pass the transactional
flag as true in pg_logical_emit_message(), UpdateDecodingStats() does
get called, which eventually updates the sent bytes. As a result,
querying pg_stat_replication_slots shows a non-zero value for
sent_bytes. This was verified by attaching the wal sender process for
this slot to the gdb.
Please correct me in case my understanding was wrong and I should have
checked for something else to verify this.
--
With Regards,
Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi, On Thu, Aug 20, 2026 at 8:32 AM Aditya Gollamudi wrote: > > > Thanks for the updated patches. I reviewed the latest two as > part of the July patch review workshop: > > First, I wasn't able to cleanly apply the refactoring patch against the > current tree. It could be an issue on my end, but it may need to be > checked again. > You were right, it needed a rebase. Please find the rebased refactoring patch (0002*.patch) attached. > Secondly, this was mentioned earlier in the thread and handled for > the SQL API path but I believe an excess 25 bytes is still being > counted by output_bytes in the WALsender path because of an > unaccounted for header. > The patch is intended to track the total number of output bytes produced by the logical decoding output path. This includes the payload generated by the output plugin and any message header added by the output writer. In the walsender path, the output writer always prepends a WALData message header to each payload generated by the output plugin. Therefore, the header is included because it forms part of the complete output message passed to libpq for delivery to the downstream consumer. I have updated the documentation to clarify this point. Please let me know if it remains unclear. > Finally, I wonder if the test coverage is sufficient. We only > assert that output_bytes is > 0 and that it is equal to 0 after a reset > happens. Should we also check that it is equal to the expected > number of bytes for the output_plugin? > Yes, it makes sense to add a test case verifying that the number of bytes produced by the logical decoding output path matches the output_bytes value recorded in pg_stat_replication_slots. I have added one of such test-case in the attached 0001 patch. Please have a look into the attached patches with above changes and let me know for any other comments/suggestions. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi, On Thu, Jun 25, 2026 at 4:37 AM Masahiko Sawada wrote: > > The proposed column name 'sent_bytes' is also confusing to me, because > I don't think we can call it "total bytes actually sent" in the > logical decoding SQL API case. A name like 'plugin_total_bytes' seems > more straightforward and conveys the intent that protocol messages are > not included. > Thank you for sharing your perspective. I agree that sent bytes doesn't entirely reflect the bytes transmitted over the wire, rather, it represents the total bytes emitted during the output plugin write cycle. In the walsender path, currently sent bytes = ctx->out->len + 1, which includes: - plugin output - 25-byte WALData header added by OutputPluginPrepareWrite() - 1-byte CopyData message type In the SQL path, sent bytes = ctx->out->len + 12, which includes: - plugin output - XLogRecPtr - XID tuple column payloads With this in mind, it may be worth considering a rename for sent bytes to plugin_total_bytes, or maybe plugin_output_bytes, or simply output_bytes. That said, it would be great to hear what others think before making a decision. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Thank you, Masahiko-san, for your review comments. Please find my responses inline below: On Tue, Jun 30, 2026 at 4:32 AM Masahiko Sawada wrote: > > On Mon, Jun 29, 2026 at 2:08 AM Ashutosh Sharma wrote: > > > Thank you for updating the patch! The new column name output_bytes > looks good to me. > > Here are some comments: > > memset(nulls, 0, sizeof(nulls)); > values[0] = LSNGetDatum(lsn); > + outputBytes += sizeof(XLogRecPtr); > values[1] = TransactionIdGetDatum(xid); > + outputBytes += sizeof(TransactionId); > : > : > /* ick, but cstring_to_text_with_len works for bytea perfectly > fine */ values[2] = > PointerGetDatum(cstring_to_text_with_len(ctx->out->data, > ctx->out->len)); > + outputBytes += ctx->out->len; > > I'm not sure that we should include these values to the new statistics > since they are not the data produced by output plugins. I think it's > better to collect the amount of data output plugins produced and get > it in OutputPluginWrite(). What do you think? > Agreed. The values added by the SQL interface, such as the LSN and XID columns, are not produced by the output plugin, so they should not be counted as output bytes. I also agree that output_bytes should be incremented in OutputPluginWrite(), since that is the common path for both the SQL and walsender consumers and ctx->out contains the data produced by the output plugin there. I have implemented these changes in the attached patch, please have a look and let me know your thoughts. > --- > - elog(DEBUG2, "UpdateDecodingStats: updating stats %p %" PRId64 " > %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 > " %" PRId64 " %" PRId64, > + elog(DEBUG2, "UpdateDecodingStats: updating stats %p %" PRId64 " > %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 > " %" PRId64 " %" PRId64 " %" PRId64, > > This log message is already quite hard to read, and I'm concerned that > simply dumping ten unlabeled numbers is no longer helpful for > debugging. If all of these metrics are indeed still useful, I would > propose adding explicit labels for each one. Otherwise, we might as > well remove this elog() entirely. > I have added labels for each field emitted by this debug message, as I am not quite sure that removing it completely would be a good idea. > --- > @@ -1979,6 +1980,7 @@ UpdateDecodingStats(LogicalDecodingContext *ctx) > repSlotStat.mem_exceeded_count = rb->memExceededCount; > repSlotStat.total_txns = rb->totalTxns; > repSlotStat.total_bytes = rb->totalBytes; > + repSlotStat.output_bytes = rb->outputBytes; > > Since output_bytes is now the tenth field we accumulate for > replication slot statistics, it seems like a good opportunity to > improve its maintainability. Instead of adding fields individually, we > could define a common structure for these shared metrics so that both > PgStat_StatReplSlotEntry and ReorderBuffer can embed it. This way, > UpdateDecodingStats() could simply assign the collected struct and > clear it using MemSet(). > I agree that this would improve maintainability. PgStat_StatReplSlotEntry now has 13 fields, and 10 of them are the decoding counters that are also tracked in ReorderBuffer, so a common counter-only structure sounds like the right direction. That said, this is not specific to the output_bytes field being added here. It is more of a refactoring of the boundary between logical decoding and pgstat statistics. To keep this patch focused, I think it would be better to handle that as a separate follow-up patch, possibly with a new thread once this work is done. Please let me know your thoughts on this? -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi Michael, On Wed, Mar 25, 2026 at 12:25 PM Michael Paquier wrote: > > On Wed, Mar 18, 2026 at 09:58:58AM +0530, Ashutosh Sharma wrote: > > So the right boundary for sent_bytes is according to me would be: > > > > a) include logical decoding payload sent downstream (or via SQL > > decoding path), whether it came from transactional changes or > > non-transactional logical messages > > b) exclude auxiliary replication protocol traffic such as keepalives > > > > If this makes sense then the revised documentation shared by Ashutosh > > Bapat in his last email looks good to me, which is: > > > > Amount of transaction changes and non-transactional messages sent > > downstream in the output plugin > > format for this slot. The output plugin may filter the changes it > > receives. Hence the amount of data that it converts to the output > > plugin format is less than the total_bytes. > > But the format of data before and after the conversion is different. > > Hence the value of sent_bytes is not > > directly related to the value of > > total_bytes. > > > > If you all agree with this, I will first prepare a patch to fix the > > missing part in #2 which is about reporting stats for > > non-transactional messages that goes via logical decoding process and > > then on top of that share the ongoing 0001 patch with above doc > > change. > > Amit Kapila and I have both mentioned that the definition you are > putting behind the sent_bytes field, as in "not accounting for the > protocol messages required in the publisher-subscriber exchange" > mentioned in b), feels strange, because these messages are sent over > the wire and part of the exchanges, like the decoded data. If we start including protocol messages such as keepalives in sent_bytes, we may end up in situations where sent_bytes exceeds total_bytes. All byte-related columns in the pg_stat_replication_slots catalog currently refer specifically to decoded transactional data - none of them account for non-transactional data or data that didn't go through logical decoding. Including keepalive data in sent_bytes could therefore cause it to exceed total_bytes, which would likely add confusion and make the data harder for users to interpret. -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi, On Wed, Mar 18, 2026 at 8:08 AM Michael Paquier wrote: > > On Tue, Mar 17, 2026 at 10:55:51AM +0530, Ashutosh Bapat wrote: > > On Tue, Mar 17, 2026 at 10:32 AM Amit Kapila wrote: > >> Yesterday, I raised a point related to this which is whether we want > >> to count messages like keep_alive, copy_done, or > >> PqReplMsg_PrimaryStatusUpdate? These don't contain the user decoded > >> data from WAL but some additional information between > >> publisher-subscriber required to ensure data is being applied. > > > > I think sent_bytes should count the logically decoded data - the data > > that the output plugin produces in logical form. This is the amount of > > data that the downstream has to process for data replication. So, the > > protocol related data shouldn't be counted here, IMO. Compare this > > with the total_bytes which does not account for the memory used by the > > reorder buffer metadata itself - it just accounts for the size of the > > changes. If we describe the sent_bytes as below, we are explicitly > > mentioning what sent_bytes is. So anything which is not data in the > > output format is excluded including the protocol data. If you think we > > need to clarify that we are excluding protocol data, we can add a > > line. But that may confuse readers who are unaware of the protocol > > data. > > As we aim to count how many bytes are sent over the wire, including > the amount of bytes related to the messages sent as part of the > protocol exchanged, sounds like an important property that we should > have in the implementation because these bytes are required for the > exchanges to work. If an output plugin decodes few changes and has a > lot of messages (unlikely in most cases, still imagine it's the case), > sent_bytes could be misleading if we only include the transactional > changes, couldn't it? Here is my understanding and perspective: 1) For transactional changes, logical decoding accumulates the changes and then sends them downstream either at commit time or while streaming an in-progress transaction. The sent bytes are accounted for when the formatted output is actually written by the decoding output write path, and the slot stats are then reported through the normal decoding stats update path. 2) For non-transactional logical messages, the behavior is different in that they are not queued as reorder buffer changes. They are processed immediately when read from WAL. However, they still go through logical decoding and can still produce output that is written downstream, so their sent bytes should be accounted for as well. For non-transactional messages, we are currently (with the patch) updating the sent bytes but not reporting it. 3) Messages such as keepalives are sent on the replication connection, but they are not logical decoding output. Since no logical decoding payload is being produced there, they should not be included in sent_bytes. So the right boundary for sent_bytes is according to me would be: a) include logical decoding payload sent downstream (or via SQL decoding path), whether it came from transactional changes or non-transactional logical messages b) exclude auxiliary replication protocol traffic such as keepalives If this makes sense then the revised documentation shared by Ashutosh Bapat in his last email looks good to me, which is: Amount of transaction changes and non-transactional messages sent downstream in the output plugin format for this slot. The output plugin may filter the changes it receives. Hence the amount of data that it converts to the output plugin format is less than the total_bytes. But the format of data before and after the conversion is different. Hence the value of sent_bytes is not directly related to the value of total_bytes. If you all agree with this, I will first prepare a patch to fix the missing part in #2 which is about reporting stats for non-transactional messages that goes via logical decoding process and then on top of that share the ongoing 0001 patch with above doc change. Let me know your thoughts. thanks -- With Regards, Ashutosh Sharma.
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Sharma <ashu.coek88@gmail.com>
Дата:
Hi Masahiko-san, On Fri, Jul 3, 2026 at 3:20 AM Masahiko Sawada wrote: > > Could you swap these patches' order? I think that these patches are > independent of each other and while we have an agreement on the new > statistics, we still need discussion for the refactoring part > (particularly I guess it's better to avoid including pgstatt.h in > reorderbuffer.h) > Thank you for your review comments. I have swapped the patch order - please see attached. In the refactoring patch, I also ensured that reorderbuffer.h does not depend on pgstat.h. To make that possible, I introduced a shared header, src/include/replication/replslot_stats.h, where PgStat_ReplSlotStats is defined. Both pgstat.h and reorderbuffer.h include that header. Hope that helps! -- With Regards, Ashutosh Sharma.
Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
Hi All,
In a recent logical replication issue, there were multiple replication
slots involved, each using a different publication. Thus the amount of
data that was replicated through each slot was expected to be
different. However, total_bytes and total_txns were reported the same
for all the replication slots as expected. One of the slots started
lagging and we were trying to figure out whether its the WAL sender
slowing down or the consumer (in this case Debezium). The lagging
slot then showed total_txns and total_bytes lesser than other slots
giving an impression that the WAL sender is processing the data
slowly. Had pg_stat_replication_slot reported the amount of data
actually sent downstream, it would have been easier to compare it with
the amount of data received by the consumer and thus pinpoint the
bottleneck.
Here's a patch to do the same. It adds two columns
- sent_txns: The total number of transactions sent downstream.
- sent_bytes: The total number of bytes sent downstream in data messages
to pg_stat_replication_slots. sent_bytes includes only the bytes sent
as part of 'd' messages and does not include keep alive messages or
CopyDone messages for example. But those are very few and can be
ignored. If others feel that those are important to be included, we
can make that change.
Plugins may choose not to send an empty transaction downstream. It's
better to increment sent_txns counter in the plugin code when it
actually sends a BEGIN message, for example in pgoutput_send_begin()
and pg_output_begin(). This means that every plugin will need to be
modified to increment the counter for it to reported correctly.
I first thought of incrementing sent_bytes in OutputPluginWrite()
which is a central function for all logical replication message
writes. But that calls LogicalDecodingContext::write() which may
further add bytes to the message e.g. WalSndWriteData() and
LogicalOutputWrite(). So it's better to increment the counter in
implementations of LogicalDecodingContext::write(), so that we count
the exact number of bytes. These implementations are within core code
so they won't miss updating sent_bytes.
I think we should rename total_txns and total_bytes to reordered_txns
and reordered_bytes respectively, and also update the documentation
accordingly to make better sense of those numbers. But these patches
do not contain that change. If others feel the same way, I will
provide a patch with that change.
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Thu, Dec 18, 2025 at 7:56 AM Chao Li wrote: > > > > > On Dec 17, 2025, at 13:55, Ashutosh Bapat wrote: > > > > Thanks for pointing this out. I have fixed it my code. However, at > > this point I am looking for a design review, especially to verify that > > the new implementation addresses Andres's concern raised in [1] while > > not introducing any design issues raised earlier e.g. those raised in > > threads [2], [3] and [4] > > > > [1] https://www.postgresql.org/message-id/zzidfgaowvlv4opptrcdlw57vmulnh7gnes4aerl6u35mirelm@tj2vzseptkjk > >>> [2] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com > >>> [3] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal > >>> [4] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com > > > > -- > > Best Wishes, > > Ashutosh Bapat > > > Hi Ashutosh, > > Yeah, I owe you a review. I committed to review this patch but I forgot, sorry about that. > > From design perspective, I agree increasing counters should belong to the core, plugin should return properly values following the contract. And I got some more comments: > > 1. I just feel a bool return value might not be clear enough. For example: > > ``` > - ctx->callbacks.change_cb(ctx, txn, relation, change); > + if (!ctx->callbacks.change_cb(ctx, txn, relation, change)) > + cache->filteredBytes += ReorderBufferChangeSize(change); > ``` > > You increase filteredBytes when change_cb returns false. But if we look at pgoutput_change(), there are many reasons to return false. Counting all the cases to filteredBytes seems wrong. I am not able to understand this. Every "return false" from pgoutput_change() indicates that the change was filtered out and hence the size of corresponding change is being added to filteredBytes by the caller. Which "return false" does not indicate a filtered out change? > > 2. > ``` > - ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change); > + if (!ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change)) > + cache->filteredBytes += ReorderBufferChangeSize(change); > ``` > > Row filter doesn’t impact TRUNCATE, why increase filteredBytes after truncate_cb()? A TRUNCATE of a relation which is not part of the publication will be filtered out. > > 3. > ``` > - ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn); > + if (ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn)) > + cache->sentTxns++; > ``` > > For 2-phase commit, it increase sentTxns after prepare_cb, and > ``` > + if (ctx->callbacks.stream_abort_cb(ctx, txn, abort_lsn)) > + cache->sentTxns++; > ``` > > If the transaction is aborted, sentTxns is increased again, which is confusing. Though for aborting there is some data (a notification) is streamed, but I don’t think that should be counted as a transaction. > > After commit, sentTxns is also increased, so that, a 2-phase commit is counted as two transactions, which feels also confusing. IMO, a 2-phase commit should still be counted as one transaction. stream_commit/abort_cb is called after stream_prepare_cb not after prepare_cb. > > 4. You add sentBytes and filteredBytes. I am thinking if it makes sense to also add sentRows and filteredRows. Because tables could be big or small, bytes + rows could show a more clear picture to users. We don't have corresponding total_rows and streamed_rows counts. I think that's because we haven't come across a use case for them. Do you have a use case in mind? -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Feb 10, 2026 at 7:08 PM Bertrand Drouvot wrote: > > Hi, > > On Mon, Feb 02, 2026 at 02:06:07PM +0530, Ashutosh Bapat wrote: > > On Tue, Jan 27, 2026 at 11:08 PM Andres Freund wrote: > > > > > > Hi, > > > > > > On 2025-12-11 10:29:42 +0530, Ashutosh Bapat wrote: > > > > Please review. > > > > > > I'd simplify the patch to, initially, to just track the sent bytes. For one, > > > that's by *far* the most useful statistic. But I also have some concerns > > > about the other stats: > > > > > > > Ok. I have split the patch into two > > Thanks! > > > 0001 to track sent_bytes > > 0002 to track filtered_bytes and sent_txns > > > > I think we all agree that sent_bytes is useful, so I suggest that we > > review and commit 0001. > > I had a look at 0001 and it looks ok to me. That's an extract of the previous > patches that we already iterated over up-thread. Thanks. > > I just have a few more comments: > > 1/ commit message > > s/This statistics/This statistic/ > It might be better to just use the phrase "This number ..." since the earlier sentence mentions it as "number". I have made that change in my repository. Will send it with the next version of patchset. > 2/ LogicalOutputWrite > > + /* Update the amount of data sent downstream. */ > > I think we can remove the period to be consistent with most of the one line > comments around. I see a few variations, starting with lower case, without a period at the end etc. Don't see a standard per say. Let's defer for the committer to decide. > > 3/ WalSndWriteData > > Same as 2/., remove the period for the same comment. > Same here. I see the following comment which has a period at the end. /* Try taking fast path unless we get too close to walsender timeout. */ -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Sep 23, 2025 at 6:28 PM Ashutosh Sharma wrote: > > > 0001 is the previous patch > > 0002 changes addressing your and Bertrand's comments. > > > > @@ -1573,6 +1573,13 @@ WalSndWriteData(LogicalDecodingContext *ctx, > XLogRecPtr lsn, TransactionId xid, > /* output previously gathered data in a CopyData packet */ > pq_putmessage_noblock(PqMsg_CopyData, ctx->out->data, ctx->out->len); > > + /* > + * If output plugin maintains statistics, update the amount of data sent > + * downstream. > + */ > + if (ctx->stats) > + ctx->stats->sentBytes += ctx->out->len + 1; /* +1 for the 'd' */ > + > > Just a small observation: I think it’s actually pq_flush_if_writable() > that writes the buffered data to the socket, not pq_putmessage_noblock > (which is actually gathering data in the buffer and not sending). So > it might make more sense to increment the sent pointer after the call > to pq_flush_if_writable(). That's a good point. I placed it after pq_putmessage_noblock() so that it's easy to link the increment to sentBytes and the actual bytes being sent. You are right that the bytes won't be sent unless pq_flush_if_writable() is called but it will be called for sure before the next UpdateDecodingStats(). So the reported bytes are never wrong. I would prefer readability over seeming accuracy. > > Should we also consider - pg_hton32((uint32) (len + 4)); -- the > additional 4 bytes of data added to the send buffer. > In WalSndWriteData() we can't rely on what happens in a low level API like socket_putmessage(). And we are counting the number of bytes in the logically decoded message. So, I actually wonder whether we should count 1 byte of 'd' in sentBytes. Shveta, Bertand, what do you think? -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Oct 6, 2025 at 10:32 AM Ashutosh Bapat wrote: > > On Fri, Oct 3, 2025 at 7:17 PM Bertrand Drouvot > wrote: > > > > Hi, > > > > On Fri, Oct 03, 2025 at 12:22:05PM +0530, Ashutosh Bapat wrote: > > > Here's patchset addressing two issues: > > > > Thanks for the patch update! > > > > > I > > > have added a note in documentation for plugin authors, warning them > > > that this isn't expected. > > > > What note are you referring to? (I'm failing to see it). > > Patch 0002, changes in logicaldecoding.sgml. I am a bit hesitant to > add more details as to what "misleading" means since mentioning so > might be seen as a documented behaviour and thus plugin authors > relying on it. > > > > > > I don't think it's worth adding complexity > > > in code to support such a case unless we see a practical need for the > > > same. > > > > Sounds good. > > > > > > > Issue 2: Once X + 2 is loaded, further statistics are accumulated on > > > the top of statistics accumulated by version X. Attached patch fixes > > > issue 2 by zero'ing out the stats when the plugin does not report the > > > statistics. > > > > +#define REPLSLOT_SET_TO_ZERO(fld) statent->fld = 0 > > > > It looks like that the associated "undef" is missing. > > Good catch. Fixed. > Squashed patches into one and rebased. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 12:32 PM Bertrand Drouvot
wrote:
>
> Hi,
>
> On Tue, Sep 23, 2025 at 04:15:14PM +0530, Ashutosh Bapat wrote:
> > On Tue, Sep 23, 2025 at 12:14 PM Bertrand Drouvot
> > wrote:
> > >
> > > - create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '1')
> > > then I don't see plugin_sent_bytes increasing (which makes sense) but I also don't
> > > see plugin_filtered_bytes increasing. I think that would make sense to also increase
> > > plugin_filtered_bytes in this case (and for the other options that would skip
> > > sending data). Thoughts?
> >
> > Thanks for bringing this up. I don't think we discussed this
> > explicitly in the thread. The changes which are filtered out by the
> > core itself e.g. changes to the catalogs or changes to other databases
> > or changes from undesired origins are not added to the reorder buffer.
> > They are not counted in total_bytes. The transactions containing only
> > such changes are not added to reorder buffer, so even total_txns does
> > not count such empty transactions. If we count these changes and
> > transactions in plugin_filtered_bytes, and plugin_filtered_txns, that
> > would create an anomaly - filtered counts being higher than total
> > counts. Further since core does not add these changes and transactions
> > to the reorder buffer, there is no way for a plugin to know about
> > their existence and hence count them. Does that make sense?
>
> Yes. Do you think that the doc in the patch is clear enough regarding this point?
> I mean the doc looks correct (mentioning the output plugin) but would that make
> sense to insist that core filtering is not taken into account?
Do you mean, should we mention in the docs that core filtering is not
taken into account? I would question whether that's called filtering
at all, in the context of logical decoding. The view should be read in
the context of logical decoding. For example, we aren't mentioning
that total_bytes does not include changes from other database.
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Fri, Sep 26, 2025 at 4:43 PM Bertrand Drouvot
wrote:
>
>
> === 2
>
> Playing with "has stats" a bit.
>
> -- Issue 1:
>
Thanks for experiments! Thanks for bringing it up.
> Say, plugin has stats enabled and I get:
>
> postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
> plugin | plugin_sent_txns
> ----------------+------------------
> pg_commit_info | 9
> (1 row)
>
> If the engine is shutdown and the plugin is now replaced by a version that
> does not provide stats, then, right after startup, I still get:
>
> postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
> plugin | plugin_sent_txns
> ----------------+------------------
> pg_commit_info | 9
> (1 row)
>
> And that will be the case until the plugin decodes something (so that
> statent->plugin_has_stats gets replaced in pgstat_report_replslot()).
>
> That's because plugin_has_stats is stored in PgStat_StatReplSlotEntry
> and so it's restored from the stat file when the engine starts.
>
> Now, let's do some inserts and decode:
>
> postgres=# insert into t1 values ('a');
> INSERT 0 1
> postgres=# insert into t1 values ('a');
> INSERT 0 1
> postgres=# select * from pg_logical_slot_get_changes('logical_slot',NULL,NULL);
> lsn | xid | data
> ------------+-----+-----------------------------------------------------------------------------------------
> 0/407121C0 | 766 | xid 766: lsn:0/40712190 inserts:1 deletes:0 updates:0 truncates:0 relations truncated:0
> 0/40712268 | 767 | xid 767: lsn:0/40712238 inserts:1 deletes:0 updates:0 truncates:0 relations truncated:0
> (2 rows)
>
> postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
> plugin | plugin_sent_txns
> ----------------+------------------
> pg_commit_info |
> (1 row)
>
> All good.
>
> Issue 1 is that before any decoding happens, pg_stat_replication_slots is still
> showing stale plugin statistics from a plugin that may no longer support stats.
>
> I'm not sure how we could easily fix this issue, as we don't know the plugin's
> stats capability until we actually use it.
>
I don't think this is an issue. There is no way for the core to tell
whether the plugin will provide stats or not, unless it sets that
ctx->stats which happens in the startup callback. Till then it is
rightly providing the values accumulated so far. Once the decoding
starts, we know that the plugin is not providing any stats and we
don't display anything.
> -- Issue 2:
>
> Let's shutdown, replace the plugin with a version that has stats enabled and
> restart.
>
> Same behavior as before:
>
> postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
> plugin | plugin_sent_txns
> ----------------+------------------
> pg_commit_info |
> (1 row)
>
> Until pgstat_report_replslot() is not called, the statent->plugin_has_stats is
> not updated. So it displays the stats as they were before the shutdown. But that's
> not an issue in this case (when switching from non stats to stats).
>
> Now, let's do some inserts and decode:
>
> postgres=# insert into t1 values ('a');
> INSERT 0 1
> postgres=# select * from pg_logical_slot_get_changes('logical_slot',NULL,NULL);
> lsn | xid | data
> ------------+-----+-----------------------------------------------------------------------------------------
> 0/407125B0 | 768 | xid 768: lsn:0/40712580 inserts:1 deletes:0 updates:0 truncates:0 relations truncated:0
> (1 row)
>
> and check the stats:
>
> postgres=# select plugin,plugin_sent_txns from pg_stat_replication_slots ;
> plugin | plugin_sent_txns
> ----------------+------------------
> pg_commit_info | 10
> (1 row)
>
> Now it reports 10, that's the 9 before we changed the plugin to not have stats
> enabled plus this new one.
>
> Issue 2: when switching from a non-stats plugin back to a stats-capable plugin, it
> shows accumulated values from before the non-stats switch.
This too seems to be a non-issue to me. The stats in the view get
reset only when a user resets them. So we shouldn't wipe out the
already accumulated values just because the plugin stopped providing
it. If the plugin keeps flip-flopping and only partial statistics
provided by the plugin will be accumulated. That's the plugin's
responsibility. Realistically a plugin will either decide to provide
statistics in some version and then continue forever OR it will decide
against it. Flip-flopping won't happen in practice.
If at all we decide to reset the stats when the plugin does not
provide them, I think a better fix is to set them to 0 in
pgstat_report_replslot() independent of previous state of has_stats.
It will be more or less same CPU instructions. like below
if (repSlotStat->plugin_has_stats)
{
REPLSLOT_ACC(plugin_sent_txns);
REPLSLOT_ACC(plugin_sent_bytes);
REPLSLOT_ACC(plugin_filtered_bytes);
}
else
{
statent->plugin_sent_txns = 0;
statent->plugin_sent_bytes = 0;
statent->plugin_filtered_bytes = 0
}
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Nov 18, 2025 at 3:24 PM Amit Kapila wrote: > > On Tue, Nov 4, 2025 at 4:29 PM Ashutosh Bapat > wrote: > > > > On Mon, Nov 3, 2025 at 8:50 PM Andres Freund wrote: > > > > > > Hi, > > > > > > On 2025-11-03 19:53:30 +0530, Ashutosh Bapat wrote: > > > > This commit adds following fields to pg_stat_replication_slots > > > > - plugin_filtered_bytes is the amount of changes filtered out by the > > > > output plugin > > > > - plugin_sent_txns is the amount of transactions sent downstream by the > > > > output plugin > > > > - plugin_sent_bytes is the amount of data sent downstream by the output > > > > plugin. > > > > > > > > The prefix "plugin_" indicates that these counters are related to and > > > > maintained by the output plugin. An output plugin may choose not to > > > > initialize LogicalDecodingContext::stats, which holds these counters, in > > > > which case the above columns will be reported as NULL. > > > > > > I continue to be uncomfortable with doing all this tracking explicitly in > > > output plugins. This still seems like something core infrastructure should > > > take care of, instead of re-implementing it in different output plugins, with > > > the inevitable behaviour differences that will entail. > > > > I understand your concern, and while I agree that it's ideal to keep > > as much of the stats bookkeeping in core there are some nuances here > > which makes it hard as explained below. > > > > My first patch [1] had the stats placed in ReorderBuffer directly. It > > was evident from the patch that the sentTxns needs to be set somewhere > > in the output plugin code since the output plugin may decide to filter > > out or send transaction when processing a change in that transaction > > (not necessarily when in begin_cb). Filtered bytes is also something > > that is in plugin's control and needs to be updated in the output > > plugin code. Few emails, starting from [2], discussed possible > > approaches to maintain those in the core vs maintain those in the > > output plugin. We decided to let output plugin maintain it for > > following reasons > > > > a. sentTxns and filteredBytes need to be modified in the output plugin > > code. The behaviour there is inherently output plugin specific, and > > requires output plugin specific implementation. > > > > Is it possible that we allow change callback (LogicalDecodeChangeCB) > to return a boolean such that if the change is decoded and sent, it > returns true, otherwise, false? If so, the caller could deduce from it > the filtered bytes, and if none of the change calls returns true, this > means the entire transaction is not sent. > > I think this should address Andres's concern of explicitly tracking > these stats in plugins, what do you think? > I was thinking about a similar thing. But I am skeptical since the calling logic is not straight forward - there's an indirection in between. Second, it means that all the plugins have to adapt to the new callback definition. It is optional in my current approach. Since both of us have thought of this approach, I think it's worth a try. "if none of the change calls returns true, this means the entire transaction is not sent" isn't true. A plugin may still send an empty transaction. I was thinking of making commit/abort/prepare callbacks to return true/false to indicate whether a transaction was sent or not and increment the counter accordingly. The plugin has to take care of not returning true for both prepare and commit or prepare and abort. So may be just commit and abort should be made to return true or false. What do you think? -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Thu, Oct 30, 2025 at 9:08 AM shveta malik wrote: > > > > > + * not filtered out, we don't count modify filteredBytes. The filtered > > Something is wrong in this sentence. :), here's better one /* * Even if we filtered out some relations, we still send a TRUNCATE * message for the remaining relations. Since the change, as a whole, is * not filtered out we don't increment filteredBytes. The filtered * out relations will be reflected as a smaller sentBytes count. */ > > Also, regarding "The filtered out relations will be reflected as a > smaller sentBytes count." > Can you please point me to the code where it happens? From what I have > understood, pgoutput_truncate() completely skips the relations which > do not support publishing truncate. Then it sends 'BEGIN', then > schema info of non-filtered relations and then TRUNCATE for > non-filtered relations (based on nrelids). Let's take an example. Assume the TRUNCATE WAL record had relids X, Y, Z and W. Out of those X and Y were filtered out. Then the message sent to the downstream will have only Z, W, let's say "TRUNCATE Z W" - 12 bytes (hypothetically). So sentBytes will be incremented by 12. However, if no relation was filtered, the message would be "TRUNCATE X Y Z W" ~ 16 bytes and thus sentBytes will be incremented by 16 bytes. Thus when the relations are filtered from the truncate message, sentBytes is incremented by a smaller value than those when no relations are filtered. So, even if filteredBytes is same in both cases (filtered some relations vs no relation was filtered), sentBytes indicates the difference. Similarly for column level filtering. However, reading this again, it seems adding more confusion than reducing it. So I propose to just add comment in pgoutput_truncate() /* * Even if we filtered out some relations, we still send a TRUNCATE * message for the remaining relations. Since the change, as a whole, is * not filtered out we don't increment filteredBytes. */ and in pgoutput_change /* * Send the data. Even if we end up filtering some columns while sending the * message, we won't consider the change, as a whole, to be filtered out. Hence * won't increment the filteredBytes. */ Does that look good? -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
Hi Bertrand, On Wed, Dec 17, 2025 at 2:12 PM Bertrand Drouvot wrote: > > What worries me is all those API changes: > > -typedef void (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx, > +typedef bool (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx, > > Those changes will break existing third party logical decoding plugin, even ones > that don't want the new statistics features. > > What about not changing those and just add a single new optional callback, say? > > typedef void (*LogicalDecodeReportStatsCB)( > LogicalDecodingContext *ctx, > ReorderBufferTXN *txn, > bool *transaction_sent, > size_t *bytes_filtered > ); > > This way: > > - Existing plugins can still work without modification > - New or existing plugins can choose to provide statistics > I think that it will bring back the same problems that the previous design had or am I missing something? Let me elaborate: 1. If every plugin implements the calculation of filtered_bytes differently, the same set of WAL passed through different output plugins would report different filtered bytes, even if they filtered the same changes. I think Andres wants minimal changes in the output plugins to avoid these divergences. 2. This also has the problem that you had raised. What if an output plugin had calls to this callback in one version but removed them in the next. 3. An output plugin may simply not realise that it can use this function to maintain statistics. Or The plugin may not call the function in all the places that it needs to. Or It may not realise it needs to call this function in a new callback added in the new PostgreSQL version. There are many ways an output plugin may get it wrong. I think this is also the reason Andres wants minimal changes output plugin to maintaining statistics. 4. filteredBytes and sentTxns are not updated at the same place, so the plugins have to send one of those values as 0 always when calling the function. We need two functions one for each sentTxns and filteredBytes. That means more chances of error and divergence. The new implementation does not have these problems 1. As the API is changed in the new implementation, every output plugin is forced to change their implementation. Amit and I discussed this aspect starting [1]. The plugins will detect the change when compiling their code against PG 19, so they won't miss it. The change expected from every plugin is minimal and well documented. They have to simply return true or false and rest will be taken care of by the core. So there is less chance of error or divergence. 2. The plugin can not go back and forth on maintaining the statistics - an issue you raised. The API will force it to always return the required status. 3. I think getting the correct statistics is more important than making it optional, especially when the changes expected from the plugin are simple. Thinking more about it, users wouldn't want to change their output plugin just because other output plugin supports statistics. Ideally, it would have been better if this was raised when Myself and Amit discussed this proposal [1], a month ago; before I spent time and effort implementing the design. But better now than before a commit. [1] https://www.postgresql.org/message-id/CAA4eK1K4Pq=acoXx3dEF7us_NFrDVU+M7f_j7KXm+Q2ywY+LSQ@mail.gmail.com -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
Hi Amit,
On Mon, Jul 14, 2025 at 3:31 PM Amit Kapila wrote:
>
> On Mon, Jul 14, 2025 at 10:55 AM Ashutosh Bapat
> wrote:
> >
> > On Sun, Jul 13, 2025 at 4:34 PM Amit Kapila wrote:
> > >
> > >
> > > I think we don't want to make it mandatory for plugins to implement
> > > these stats, so instead of throwing ERROR, the view should show that
> > > the plugin doesn't provide stats. How about having OutputPluginStats
> > > similar to OutputPluginCallbacks and OutputPluginOptions members in
> > > LogicalDecodingContext? It will have members like stats_available,
> > > txns_sent or txns_skipped, txns_filtered, etc.
> >
> > Not making mandatory looks useful. I can try your suggestion. Rather
> > than having stats_available as a member of OutputPluginStats, it's
> > better to have a NULL value for the corresponding member in
> > LogicalDecodingContext. We don't want an output plugin to reset
> > stats_available once set. Will that work?
> >
>
> We can try that.
>
> > > I am thinking it will
> > > be better to provide this information in a separate view like
> > > pg_stat_plugin_stats or something like that, here we can report
> > > slot_name, plugin_name, then the other stats we want to implement part
> > > of OutputPluginStats.
> >
> > As you have previously pointed out, the view should make it explicit
> > that the new stats are maintained by the plugin and not core. I agree
> > with that intention. However, already have three views
> > pg_replication_slots (which has slot name and plugin name), then
> > pg_replication_stats which is about stats maintained by a WAL sender
> > or running replication and then pg_stat_replication_slots, which is
> > about accumulated statistics for a replication through a given
> > replication slot. It's already a bit hard to keep track of who's who
> > when debugging an issue. Adding one more view will add to confusion.
> >
> > Instead of adding a new view how about
> > a. name the columns as plugin_sent_txns, plugin_sent_bytes,
> > plugin_filtered_change_bytes to make it clear that these columns are
> > maintained by plugin
> > b. report these NULL if stats_available = false OR OutputPluginStats
> > is not set in LogicalDecodingContext
> > c. Document that NULL value for these columns indicates that the
> > plugin is not maintaining/reporting these stats
> > d. adding plugin name to pg_stat_replication_slots, that will make it
> > easy for users to know which plugin they should look at in case of
> > dubious or unavailable stats
> >
>
> Sounds reasonable.
Here's the next patch which considers all the discussion so far. It
adds four fields to pg_stat_replication_slots.
- plugin - name of the output plugin
- plugin_filtered_bytes - reports the amount of changes filtered
out by the output plugin
- plugin_sent_txns - the amount of transactions sent downstream by
the output plugin
- plugin_sent_bytes - the amount of data sent downstream by the
outputplugin.
There are some points up for a discussion:
1. pg_stat_reset_replication_slot() zeroes out the statistics entry by
calling pgstat_reset() or pgstat_reset_of_kind() which don't know
about the contents of the entry. So
PgStat_StatReplSlotEntry::plugin_has_stats is set to false and plugin
stats are reported as NULL, instead of zero, immediately after reset.
This is the same case when the stats is queried immediately after the
statistics is initialized and before any stats are reported. We could
instead make it report
zero, if we save the plugin_has_stats and restore it after reset. But
doing that in pgstat_reset_of_kind() seems like an extra overhead + we
will need to write a function to find all replication slot entries.
Resetting the stats would be a rare event in practice. Trying to
report 0 instead of NULL in that rare case doesn't seem to be worth
the efforts and code. Given that the core code doesn't know whether a
given plugin reports stats or not, I think this behaviour is
appropriate as long as we document it. Please let me know if the
documentation in the patch is clear enough.
2. There's also a bit of asymmetry in the way sent_bytes is handled.
The code which actually sends the logical changes to the downstream is
part of the core code
but the format of the change and hence the number of bytes sent is
decided by the plugin. It's a stat related to plugin but maintained by
the core code. The patch implements it as a plugin stat (so the
corresponding column has "plugin" prefix and is also reported as NULL
upon reset etc.), but we may want to reconsider how to report and
maintain it.
3. The names of new columns have the prefix "plugin_" but the internal
variables tracking those don't for the sake of brevity. If you prefer
to have the same prefix for the internal variables, I can change that.
I think I have covered all the cases where filteredbytes should be
incremented, but please let me know if I have missed any.
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Mar 16, 2026 at 12:20 PM Michael Paquier wrote: > > On Mon, Mar 16, 2026 at 08:55:17AM +0530, Ashutosh Bapat wrote: > > I would post two patches > > 1. To introduce the new column sent_bytes + clarify total_bytes in the > > context of sent_bytes. We should consider this patch for committing. > > Sounds to me like two patches: one for the new column, a second for > the docs of the existing columns. Could somebody send these patches? > I would be OK to look at them once posted, driving things for this CF. > The way I look at it, we don't need to modify documentation of existing columns. The documentation of the new column should describe it in the context of the existing columns. Let's wait for Ashutosh Sharma to share the patches. If we think we need to modify documentation of the existing columns, we will split it into two as you suggest. > > 2. Addition of filtered_bytes + sent_txns and also the clarification > > of total_bytes and total_txns in the context of the new columns. We > > will decide whether to commit the patch after 0001 is committed. > > Not sure about this one, neither do I know if I would be able to look > at any proposal for this release cycle on this part. Getting 0001 out > of its box seems like a goal good enough for me, TBH. +1. I am fine if we ignore the other patch for now, but let it be there on this thread for completeness/future consideration/reference. I am completely fine if we ignore it forever :). -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Nov 3, 2025 at 8:50 PM Andres Freund wrote: > > Hi, > > On 2025-11-03 19:53:30 +0530, Ashutosh Bapat wrote: > > This commit adds following fields to pg_stat_replication_slots > > - plugin_filtered_bytes is the amount of changes filtered out by the > > output plugin > > - plugin_sent_txns is the amount of transactions sent downstream by the > > output plugin > > - plugin_sent_bytes is the amount of data sent downstream by the output > > plugin. > > > > The prefix "plugin_" indicates that these counters are related to and > > maintained by the output plugin. An output plugin may choose not to > > initialize LogicalDecodingContext::stats, which holds these counters, in > > which case the above columns will be reported as NULL. > > I continue to be uncomfortable with doing all this tracking explicitly in > output plugins. This still seems like something core infrastructure should > take care of, instead of re-implementing it in different output plugins, with > the inevitable behaviour differences that will entail. I understand your concern, and while I agree that it's ideal to keep as much of the stats bookkeeping in core there are some nuances here which makes it hard as explained below. My first patch [1] had the stats placed in ReorderBuffer directly. It was evident from the patch that the sentTxns needs to be set somewhere in the output plugin code since the output plugin may decide to filter out or send transaction when processing a change in that transaction (not necessarily when in begin_cb). Filtered bytes is also something that is in plugin's control and needs to be updated in the output plugin code. Few emails, starting from [2], discussed possible approaches to maintain those in the core vs maintain those in the output plugin. We decided to let output plugin maintain it for following reasons a. sentTxns and filteredBytes need to be modified in the output plugin code. The behaviour there is inherently output plugin specific, and requires output plugin specific implementation. b. an output plugin may or may not want to update their code to track the statistics for various logistic and technical reasons. We need to be flexible about that if possible. The current approach requires only the output plugin specific changes to be made to the output plugin code and also makes it optional for them to do those changes. The only changes in output plugin code are for a. indicating whether it updates the stats and b. updating filteredBytes and sentBytes at appropriate places. I don't see a way to avoid that. Rest of the logic is actually in the core. Unless there's anything we've overlooked in the thread the current approach seems to balance the constraints quite well. Do you have an alternative design in mind? This has been a long thread with many patch versions, and the commit message might need some rewording to describe the proposed functionality better. I hope the above explanation is clearer, and if so I can reword the commit message to include more of it. sentBytes is a slightly different story. The core code updates it. But it's a stat about output plugin's behaviour. Hence it's still exposed as a plugin stats and maintained in LogicalDecodingContext::stats. It can be maintained in ReorderBuffer directly and can be projected as a core stats, renaming it as just "sent_bytes". Please let me know if you would like it that way. [1] https://www.postgresql.org/message-id/CAExHW5s6KntzUyUoMbKR5dgwRmdV2Ay_2%2BAnTgYGAzo%3DQv61wA%40mail.gmail.com [2] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Mar 16, 2026 at 3:47 AM Michael Paquier wrote: > > On Fri, Mar 13, 2026 at 10:39:40AM +0530, Ashutosh Sharma wrote: > > Thanks for sharing your thoughts, Michael, I had a similar idea in > > mind. I'll go ahead and implement these changes and come up with the > > new patches. Now since this is purely additive (new columns only), I'm > > thinking a single patch might suffice, Or maybe, we could split it > > into two: one that corrects the existing documentation and another > > that introduces the new fields for displaying downstream bytes sent > > and some other information related to this. > > Thanks. We'd better move forward quickly with this one based on the > current calendar, still it would be good to hear from Ashutosh Bapat > and double-check with him. I got busy with SQL/PGQ [1], resizable shared structures [2] and pgconf.india last week. Thanks Ashutosh Sharma for picking this up. Addition of _wal_ was suggested by Andres offlist [3]. That was long back and didn't have other opinions to validate the renaming. I see that there are more opinions against renaming now. There is also merit in not renaming it on the grounds that the users are used to the old names and will find new names confusing. Whether we do it now or later, it's going to break backward compatibility. Not renaming now has the advantage that we can avoid it all together if users do not find the names confusing in the context of the new column. Let's leave the renaming aside for now. I would post two patches 1. To introduce the new column sent_bytes + clarify total_bytes in the context of sent_bytes. We should consider this patch for committing. 2. Addition of filtered_bytes + sent_txns and also the clarification of total_bytes and total_txns in the context of the new columns. We will decide whether to commit the patch after 0001 is committed. [1] https://www.postgresql.org/message-id/CAExHW5ufB8y1oguSea_9WPFHFDOOTsxZ3Na_OAUBF-H%2BB7AfYw%40mail.gmail.com [2] https://www.postgresql.org/message-id/flat/CAExHW5vM1bneLYfg0wGeAa%3D52UiJ3z4vKd3AJ72X8Fw6k3KKrg%40mail.gmail.com [3] https://www.postgresql.org/message-id/CAExHW5vnRPp5dof35DEGdc8W2tS16E5CbmGey28Y=RJ9QrpfAQ@mail.gmail.com -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Fri, Oct 3, 2025 at 7:17 PM Bertrand Drouvot wrote: > > Hi, > > On Fri, Oct 03, 2025 at 12:22:05PM +0530, Ashutosh Bapat wrote: > > Here's patchset addressing two issues: > > Thanks for the patch update! > > > I > > have added a note in documentation for plugin authors, warning them > > that this isn't expected. > > What note are you referring to? (I'm failing to see it). Patch 0002, changes in logicaldecoding.sgml. I am a bit hesitant to add more details as to what "misleading" means since mentioning so might be seen as a documented behaviour and thus plugin authors relying on it. > > > I don't think it's worth adding complexity > > in code to support such a case unless we see a practical need for the > > same. > > Sounds good. > > > > Issue 2: Once X + 2 is loaded, further statistics are accumulated on > > the top of statistics accumulated by version X. Attached patch fixes > > issue 2 by zero'ing out the stats when the plugin does not report the > > statistics. > > +#define REPLSLOT_SET_TO_ZERO(fld) statent->fld = 0 > > It looks like that the associated "undef" is missing. Good catch. Fixed. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 10:12 AM shveta malik wrote: > > I tested the flows with > a) logical replication slot and get-changes. > b) filtered data flows: pub-sub creation with row_filters, 'publish' > options. I tried to verify plugin fields as compared to total_wal* > fields. > c) reset flow. > > While tests for a and c are present already. I don't see tests for b > anywhere when it comes to stats. Do you think we shall add a test for > filtered data using row-filter somewhere? Added a test in 028_row_filter. Please find it in the attached patchset. I didn't find tests which test table level filtering or operation level filtering. Can you please point me to such tests. I will add similar test to other places. Once you review the test in 028_row_filter, I will replicate it to other places you point out. On Wed, Sep 24, 2025 at 12:12 PM Bertrand Drouvot wrote: > > Hi, > > On Wed, Sep 24, 2025 at 11:38:30AM +0530, shveta malik wrote: > > On Wed, Sep 24, 2025 at 11:08 AM Ashutosh Bapat > > wrote: > > > > > > In WalSndWriteData() we can't rely on what happens in a low level API > > > like socket_putmessage(). And we are counting the number of bytes in > > > the logically decoded message. So, I actually wonder whether we should > > > count 1 byte of 'd' in sentBytes. Shveta, Bertand, what do you think? > > > > > > > If we are not counting all such metadata bytes ((or can't reliably do > > so), then IMO, we shall skip counting msgtype as well. > > Agree. Maybe mention in the doc that metadata (including msgtype) bytes are not > taken into account? We are counting the sentBytes in central places through which all the logically decoded messages flow. So we are not missing on any metadata bytes. Given that these bytes are part of the logically decoded message itself, I think we should count them in the sentBytes. Now the question remains is whether to count 4 bytes for length in the message itself? The logical decoding code can not control that and thus should not account for it. So I am leaving bytes counted for pg_hton32((uint32) (len + 4)) out of sentBytes calculation. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Mar 17, 2026 at 12:12 PM Amit Kapila wrote: > > > If yes, it makes sense to mention > > non-transactional messages separately in sent_bytes description. We > > can modify my previous suggestion as > > > > Amount of transaction changes and non-transactional messages sent > > downstream in the output plugin > > > > Isn't it better to use something on the lines of what Michael is > proposing in his last email [1]? I missed his version in criss-cross emails. Copied here for completeness: "Amount of bytes decoded and sent downstream by the output plugin. This accounts for the output plugin filters, if any, and for the conversion into the output plugin format." Everywhere else, total_bytes, spill_bytes, we use the term "data" instead of "bytes". The "decoded" in the description of those two fields has a different meaning than what "decoded" means here. It's going to be confusing if we use the same term. I think we need to be consistent with other descriptions. But I agree with Michael's intent to make it less fancy. Following version covers it all Amount of transaction changes and non-transactional messages sent downstream in the output plugin format for this slot. The output plugin may filter the changes it receives. Hence the amount of data that it converts to the output plugin format is less than the total_bytes. But the format of data before and after the conversion is different. Hence the value of sent_bytes is not directly related to the value of total_bytes. It tells what exactly is counted. It's less fancy (though a bit verbose). Once we add non-transactional messages part to total_bytes, both the descriptions will be consistent. It also explains why sent_bytes and total_bytes can not be compared. Since streamed_bytes and spilled_bytes never contain non-transactional messages, we don't need to modify those descriptions. What do you think? -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Sep 23, 2025 at 12:14 PM Bertrand Drouvot
wrote:
>
> Hi,
>
> On Fri, Sep 19, 2025 at 08:11:23PM +0530, Ashutosh Bapat wrote:
> > On Fri, Sep 19, 2025 at 11:48 AM shveta malik wrote:
> > >
> > 0001 is the previous patch
> > 0002 changes addressing your and Bertrand's comments.
>
> Thanks for the new patch version!
>
> I did not look closely to the code yet but did some testing and I've one remark
> regarding plugin_filtered_bytes: It looks ok when a publication is doing rows
> filtering but when I:
>
> - create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '0')
> then I see plugin_sent_bytes increasing (which makes sense).
>
> - create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '1')
> then I don't see plugin_sent_bytes increasing (which makes sense) but I also don't
> see plugin_filtered_bytes increasing. I think that would make sense to also increase
> plugin_filtered_bytes in this case (and for the other options that would skip
> sending data). Thoughts?
Thanks for bringing this up. I don't think we discussed this
explicitly in the thread. The changes which are filtered out by the
core itself e.g. changes to the catalogs or changes to other databases
or changes from undesired origins are not added to the reorder buffer.
They are not counted in total_bytes. The transactions containing only
such changes are not added to reorder buffer, so even total_txns does
not count such empty transactions. If we count these changes and
transactions in plugin_filtered_bytes, and plugin_filtered_txns, that
would create an anomaly - filtered counts being higher than total
counts. Further since core does not add these changes and transactions
to the reorder buffer, there is no way for a plugin to know about
their existence and hence count them. Does that make sense?
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Nov 3, 2025 at 3:25 PM shveta malik wrote: > > > So I propose to just add comment > > > > in pgoutput_truncate() > > /* > > * Even if we filtered out some relations, we still send a TRUNCATE > > * message for the remaining relations. Since the change, as a whole, is > > * not filtered out we don't increment filteredBytes. > > */ > > > > and in pgoutput_change > > /* > > * Send the data. Even if we end up filtering some columns while sending the > > * message, we won't consider the change, as a whole, to be filtered out. Hence > > * won't increment the filteredBytes. > > */ > > > > Does that look good? > > Yes. Works for me. Here's a patch with all comments addressed. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Mar 17, 2026 at 10:15 AM Amit Kapila wrote: > > > > Observation: I do not see UpdateDecodingStats() being triggered for > > this non-transactional message. > > > > I think that is probably a bug. I see that ReorderBufferQueueMessage() > queues messages for transactional messages and which would then > probably be sent later along with commit, so its data will be counted > by UpdateDecodingStats. But the question is shouldn't we consider the > data for non-transactional messages as well? > I am also replying to your earlier comment > BTW, this also contains changes from pgoutput_message() which could be > non-transactional. So, saying transaction changes may not be > appropriate. > We should consider the non-transactional messages as well in sent_bytes, since that amount of data is sent. Whether we can use the term "transaction changes" in the description of sent_bytes if we include non-transactional messages is questionable. I used "transactional changes" in the description of "sent_bytes" to be consistent with the description of total_bytes. Looks like you are suggesting that not accounting for non-transaction messages in total_bytes is a bug, if we fix that, are we going to fix the description of "total_bytes"? If yes, it makes sense to mention non-transactional messages separately in sent_bytes description. We can modify my previous suggestion as Amount of transaction changes and non-transactional messages sent downstream in the output plugin format for this slot. The output plugin may filter the changes it receives. Hence the amount of data that it converts to the output plugin format is less than the total_bytes. But the format of data before and after the conversion is different. Hence the value of sent_bytes is not directly related to the value of total_bytes. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Fri, Dec 19, 2025 at 7:24 AM Chao Li wrote:
>
>
>
> > On Dec 18, 2025, at 20:52, Ashutosh Bapat wrote:
> >
> > On Thu, Dec 18, 2025 at 7:56 AM Chao Li wrote:
> >>
> >>
> >>
> >>> On Dec 17, 2025, at 13:55, Ashutosh Bapat wrote:
> >>>
> >>> Thanks for pointing this out. I have fixed it my code. However, at
> >>> this point I am looking for a design review, especially to verify that
> >>> the new implementation addresses Andres's concern raised in [1] while
> >>> not introducing any design issues raised earlier e.g. those raised in
> >>> threads [2], [3] and [4]
> >>>
> >>> [1] https://www.postgresql.org/message-id/zzidfgaowvlv4opptrcdlw57vmulnh7gnes4aerl6u35mirelm@tj2vzseptkjk
> >>>>> [2] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com
> >>>>> [3] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal
> >>>>> [4] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com
> >>>
> >>> --
> >>> Best Wishes,
> >>> Ashutosh Bapat
> >>
> >>
> >> Hi Ashutosh,
> >>
> >> Yeah, I owe you a review. I committed to review this patch but I forgot, sorry about that.
> >>
> >> From design perspective, I agree increasing counters should belong to the core, plugin should return properly values following the contract. And I got some more comments:
> >>
> >> 1. I just feel a bool return value might not be clear enough. For example:
> >>
> >> ```
> >> - ctx->callbacks.change_cb(ctx, txn, relation, change);
> >> + if (!ctx->callbacks.change_cb(ctx, txn, relation, change))
> >> + cache->filteredBytes += ReorderBufferChangeSize(change);
> >> ```
> >>
> >> You increase filteredBytes when change_cb returns false. But if we look at pgoutput_change(), there are many reasons to return false. Counting all the cases to filteredBytes seems wrong.
> >
> > I am not able to understand this. Every "return false" from
> > pgoutput_change() indicates that the change was filtered out and hence
> > the size of corresponding change is being added to filteredBytes by
> > the caller. Which "return false" does not indicate a filtered out
> > change?
>
> I think the confusion comes from the counter name “filteredBytes”, what does “filtered” mean? There are 3 types of data not steaming out:
>
> a. WAL data of tables that doesn’t belong to the publication
> b. table belong to the publication, but action doesn’t. For example, FOR ALL TABLES (INSERT), then update/delete will not be streamed out
> c. Filtered by row filter (WHERE)
>
> I thought only c should be counted to filteredBytes; thinking over again, maybe b should also be counted. But I still don’t think a should be counted.
>
> IMO, sentBytes + filteredBytes == supposedToSendBytes. If a table doesn’t belong to a publication, then it should not be counted into supposedToSendBytes, so it should not be counted into filteredBytes.
>
> The other point is that, if we count a into filteredBytes, then ends up totalBytes == sendBytes + filteredBytes, if that’s true, why don’t compute such a number by (totalBytes-sendBytes) in client side?
>
> If we insist to count a, then maybe we need to consider a better counter name.
>
> >>
> >> 2.
> >> ```
> >> - ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change);
> >> + if (!ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change))
> >> + cache->filteredBytes += ReorderBufferChangeSize(change);
> >> ```
> >>
> >> Row filter doesn’t impact TRUNCATE, why increase filteredBytes after truncate_cb()?
> >
> > A TRUNCATE of a relation which is not part of the publication will be
> > filtered out.
>
> Same as 1.
>
filtered_bytes is the amount of data filtered out of total_bytes.
Since total_bytes accounts for the changes from tables which are not
included in the publications, filtered_bytes should include them since
they are "filtered" from total_bytes. Hence include a in
filtered_bytes. Quoting from the document
--
Amount of changes, from
total_wal_bytes, filtered
out by the output plugin and not sent downstream. Please note that it
does not include the changes filtered before a change is sent to
the output plugin, e.g. the changes filtered by origin.
--
sent_bytes is related but different metric. From the documentation
--
Amount of transaction changes, in the output format, sent downstream for
this slot by the output plugin.
--
Assumption sentBytes + filteredBytes == supposedToSendBytes. is wrong.
Since filtered_bytes were never converted into the output format we
don't know how many bytes would have been sent downstream, had those
bytes not been filtered. We will never know how much supposedToSend
bytes would be. ALso note that sent_bytes + filtered_bytes is not the
same as total_wal_bytes.
> >
> >>
> >> 3.
> >> ```
> >> - ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn);
> >> + if (ctx->callbacks.prepare_cb(ctx, txn, prepare_lsn))
> >> + cache->sentTxns++;
> >> ```
> >>
> >> For 2-phase commit, it increase sentTxns after prepare_cb, and
> >> ```
> >> + if (ctx->callbacks.stream_abort_cb(ctx, txn, abort_lsn))
> >> + cache->sentTxns++;
> >> ```
> >>
> >> If the transaction is aborted, sentTxns is increased again, which is confusing. Though for aborting there is some data (a notification) is streamed, but I don’t think that should be counted as a transaction.
> >>
> >> After commit, sentTxns is also increased, so that, a 2-phase commit is counted as two transactions, which feels also confusing. IMO, a 2-phase commit should still be counted as one transaction.
> >
> > stream_commit/abort_cb is called after stream_prepare_cb not after prepare_cb.
>
> That’s my typo, but the problem is still there. Should we count a 2-phase-commit as 2 transactions?
>
Can you please provide me a repro where a prepared transaction gets
counted twice as sent_txns?
> >
> >>
> >> 4. You add sentBytes and filteredBytes. I am thinking if it makes sense to also add sentRows and filteredRows. Because tables could be big or small, bytes + rows could show a more clear picture to users.
> >
> > We don't have corresponding total_rows and streamed_rows counts. I
> > think that's because we haven't come across a use case for them. Do
> > you have a use case in mind?
> >
>
> That’s still related to 1. totalBytes includes tables don’t belong to the publication, thus totalRows doesn’t make much sense. But sendRows will only include those rows belonging to the publication. For filterRows, if we exclude a, then I believe filterRows also makes sense.
>
> If you argue that “rows” request should be treated in a separate thread, I’ll be okay with that.
I think so. It will be good to provide examples of how this statistics
will be used. A separate thread will be better.
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Sep 30, 2025 at 12:22 PM Bertrand Drouvot wrote: > > Hi, > > On Mon, Sep 29, 2025 at 12:54:24PM +0530, Ashutosh Bapat wrote: > > On Fri, Sep 26, 2025 at 10:28 PM Bertrand Drouvot > > wrote: > > > > > > > > > > > > > > > > I don't think this is an issue. There is no way for the core to tell > > > > whether the plugin will provide stats or not, unless it sets that > > > > ctx->stats which happens in the startup callback. Till then it is > > > > rightly providing the values accumulated so far. Once the decoding > > > > starts, we know that the plugin is not providing any stats and we > > > > don't display anything. > > > > > > Yeah, I got the technical reasons, but I think there's a valid user experience > > > concern here: seeing statistics for a plugin that doesn't actually support > > > statistics is misleading. > > > > > > > 3. If the plugin starts supporting statistics and midway discontinues > > its support, it already has a problem with backward compatibility. > > > > Practically it would 1 or 2, which are working fine. > > > > I don't think we will encounter case 3 practically. Do you have a > > practical use case where a plugin would discontinue supporting stats? > > Not that I can think of currently. That looks unlikely but wanted to raise > the point though. Maybe others see a use case and/or have a different point > of view. > > > > What we need is a call to pgstat_report_replslot() to display stats that reflect > > > the current plugin behavior. We can't just call pgstat_report_replslot() > > > in say RestoreSlotFromDisk() because we really need the decoding to start. > > > > > > So one idea could be to set a flag (per slot) when pgstat_report_replslot() > > > has been called (for good reasons) and check for this flag in > > > pg_stat_get_replication_slot(). > > > > > > If the flag is not set, then set the plugin fields to NULL. > > > If the flag is set, then display their values (like now). > > > > This approach will have the same problem. Till > > pgstat_report_replslot() is called, the old statistics will continue > > to be shown. > > I don't think so because the flag would not be set. > > > > And we should document that the plugin stats are not available (i.e are NULL) > > > until the decoding has valid stats to report after startup. > > > > The current documentation is " It is NULL when statistics is not > > initialized or immediately after a reset or when not maintained by the > > output plugin.". I think that covers all the cases. > > Do you think the doc covers the case we discussed above? i.e when a plugin > discontinue supporting stats, it would display stats until the decoding actually > starts. Here's patchset addressing two issues: Issue 1: A plugin supports stats in version X. It stopped supporting the stats in version X + 1. It again started supporting stats in version X + 2. Plugin stats will be accumulated when it was at version X. When X + 1 is loaded, the stats will continue to report the stats accumulated (by version X) till the first startup_call for that replication slot happens. If the user knows (from documentation say) that X + 1 does not support stats, seeing statistics will mislead them. We don't know whether there's a practical need to do so. A plugin which flip-flops on stats is breaking backward compatibility. I have added a note in documentation for plugin authors, warning them that this isn't expected. I don't think it's worth adding complexity in code to support such a case unless we see a practical need for the same. Issue 2: Once X + 2 is loaded, further statistics are accumulated on the top of statistics accumulated by version X. Attached patch fixes issue 2 by zero'ing out the stats when the plugin does not report the statistics. The patchset also addresses your earlier review comments. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Fri, Jun 12, 2026 at 12:32 PM Kyotaro Horiguchi wrote: > > Let me go back to the original motivation. > > As I understand it, the problem was that, among several subscribers > connected through different slots, one slot was lagging behind. The > total_txns and total_bytes values for that slot appeared smaller, and > you wanted to know where the bottleneck was. > > I wonder if part of the confusion comes from the fact that it is not > yet clear what conclusions a user is expected to draw from these new > values. > > What I am still not quite sure about is how the proposed sent_bytes > value would be used to make that distinction. It sounds like the idea > is not necessarily to look at its rate over time, but perhaps to > compare total_bytes with the proposed sent_bytes. However, as has > been discussed, these two values seem to measure different things, so > I am not sure that such a comparison would be straightforward. > > I think it would help move the discussion forward if you could explain > more concretely how these new values would be used to identify the > bottleneck in the case you described. As I have explained in [1], total_bytes indicates the amount of data added to the reorder buffer. It does not indicate the amount of data in logical form sent downstream. The system which triggered this issue used Debezium as the downstream. The customer wanted to configure Debezium so that it can consume the logical changes in real time. But they had no clue about the amount of logical changes they received from upstream. total_bytes does not help since it's the amount of WAL added to reorder buffer; not the amount of logical changes sent downstream. Hence proposal to add new column sent_bytes. I hope this helps. [1] https://www.postgresql.org/message-id/flat/CAExHW5s6KntzUyUoMbKR5dgwRmdV2Ay_2%2BAnTgYGAzo%3DQv61wA%40mail.gmail.com -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Fri, Feb 13, 2026 at 10:47 AM Michael Paquier wrote: > > On Tue, Feb 10, 2026 at 09:10:45PM +0530, Ashutosh Bapat wrote: > > It might be better to just use the phrase "This number ..." since the > > earlier sentence mentions it as "number". I have made that change in > > my repository. Will send it with the next version of patchset. > > I have studied this thread, and can get behind the idea of adding the > number of bytes sent to the output plugin at this level. > > However, I think that patch 0001 should be split into two parts, > because it is doing two separate things, not one: > - One patch for the rename of the existing fields total_txns and > total_bytes to respectively total_wal_txns and total_wal_bytes, so as > it becomes possible to make the distinction between the stats data > coming from WAL. (The suggested rename was a bit confusing for me > first, FWIW, when I read total_wal_bytes, my mind understood that as a > total number of WAL generated, even if I get that that it comes from > an amount of WAL data processed when decoding transactions. Not an > objection, just a feeling). > - A second patch to introduce the new field for the number of bytes > sent to the output plugin. > I am open to other suggestions. Since the bytes in total_bytes and sent_bytes refer to different set of bytes, it was thought to be better to rename total_bytes and hence total_txns. But then stream_bytes, spill_bytes too come from WAL and they are not renamed right now. So there's already some awkwardness. I also see that wal in the name can be confusing. I thought of using "reordered" instead of "wal" but reorder buffer is an internal concept. If we change the reorder buffer mechanism tomorrow, we will need to change the name of the field. So we discarded that. I am wondering whether we should just leave the name as is and rely on documentation to clarify the difference. What do you think? Although renaming is a separate change, it's closely related with the new field. Committing that separately won't be any value addition. As is, the fields in the view are quite clear from their names. So it's not clear why we need separate patch for it. If you insist, I will create a separate patch. > I am also questioning why you did not make the choice of renaming > totalBytes and totalTxns in ReorderBuffer. On consistency ground, at > least, that seems relevant to me as these two numbers are the source > data fed to pgstats. No particular reason. In the code it's quite clear what totalBytes means, so I didn't feel like renaming. But I think it's better to be consistent and rename the internal variables as well, if we decide to rename those fields. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
Hi Shveta, Bertrand, Replying to both of your review comments together. On Thu, Sep 18, 2025 at 10:52 AM shveta malik wrote: > > On Wed, Aug 27, 2025 at 7:14 PM Bertrand Drouvot > wrote: > > > > Hi, > > > > On Thu, Jul 24, 2025 at 12:24:26PM +0530, Ashutosh Bapat wrote: > > > Here's the next patch which considers all the discussion so far. It > > > adds four fields to pg_stat_replication_slots. > > > - plugin - name of the output plugin > > > > Is this one needed? (we could get it with a join on pg_replication_slots) > > > > In my opinion, when there are other plugin_* fields present, including > the plugin name directly here seems like a better approach. So, +1 for > the plugin field. Yeah. I think so too. > > > > - plugin_filtered_bytes - reports the amount of changes filtered > > > out by the output plugin > > > - plugin_sent_txns - the amount of transactions sent downstream by > > > the output plugin > > > - plugin_sent_bytes - the amount of data sent downstream by the > > > outputplugin. > > > > > > There are some points up for a discussion: > > > 1. pg_stat_reset_replication_slot() zeroes out the statistics entry by > > > calling pgstat_reset() or pgstat_reset_of_kind() which don't know > > > about the contents of the entry. So > > > PgStat_StatReplSlotEntry::plugin_has_stats is set to false and plugin > > > stats are reported as NULL, instead of zero, immediately after reset. > > > This is the same case when the stats is queried immediately after the > > > statistics is initialized and before any stats are reported. We could > > > instead make it report > > > zero, if we save the plugin_has_stats and restore it after reset. But > > > doing that in pgstat_reset_of_kind() seems like an extra overhead + we > > > will need to write a function to find all replication slot entries. > > I tried to think of an approach where we can differentiate between the > cases 'not initialized' and 'reset' ones with the values. Say instead > of plugin_has_stats, if we have plugin_stats_status, then we can > maintain status like -1(not initialized), 0(reset). But this too will > complicate the code further. Personally, I’m okay with NULL values > appearing even after a reset, especially since the documentation > explains this clearly. Ok. Great. > > > Could we store plugin_has_stats in ReplicationSlotPersistentData instead? That > > way it would not be reset. We would need to access ReplicationSlotPersistentData > > in pg_stat_get_replication_slot though. > > > Also would that make sense to expose plugin_has_stats in pg_replication_slots? > A plugin may change its decision to support the stats across versions, we won't be able to tell when it changes that decision and thus reflect it accurately in ReplicationSlotPersistentData. Doing it in startup gives the opportunity to the plugin to change it as often as it wants OR even based on some plugin specific configurations. Further ReplicationSlotPersistentData is maintained by the core. It will not be a good place to store something plugin specific. > > > > > 2. There's also a bit of asymmetry in the way sent_bytes is handled. > > > The code which actually sends the logical changes to the downstream is > > > part of the core code > > > but the format of the change and hence the number of bytes sent is > > > decided by the plugin. It's a stat related to plugin but maintained by > > > the core code. The patch implements it as a plugin stat (so the > > > corresponding column has "plugin" prefix > > > > The way it is done makes sense to me. Great. > > > > > 3. The names of new columns have the prefix "plugin_" but the internal > > > variables tracking those don't for the sake of brevity. If you prefer > > > to have the same prefix for the internal variables, I can change that. > > > > I am okay either way. > > > Just my taste: I do prefer when they match. I don't see a strong preference to change what's there in the patch. Let's wait for more reviews. > > Few comments: > > 1) > postgres=# select slot_name, > total_bytes,plugin_filtered_bytes,plugin_sent_bytes from > pg_stat_replication_slots order by slot_name; > slot_name | total_bytes | plugin_filtered_bytes | plugin_sent_bytes > -----------+-------------+-----------------------+------------------- > slot1 | 800636 | 793188 | 211 > sub1 | 401496 | 132712 | 84041 > sub2 | 401496 | 396184 | 674 > sub3 | 401496 | 145912 | 79959 > (4 rows) > > Currently it looks quite confusing. 'total_bytes' gives a sense that > it has to be a sum of filtered and sent. But they are no way like > that. In the thread earlier there was a proposal to change the name to > reordered_txns, reordered_bytes. That looks better to me. It will give > clarity without even someone digging into docs. I also agree with that. But that will break backward compatibility. Do you think other columns like spill_* and stream_* should also be renamed with the prefix "reordered"? > > 2) > Tried to verify all filtered data tests, seems to work well. Also I > tried tracking the usage of OutputPluginWrite() to see if there is any > other place where data needs to be considered as filtered-data. > Encountered this: > > send_relation_and_attrs has: > if (!logicalrep_should_publish_column(att, columns, > > include_gencols_type)) > continue; > if (att->atttypid < FirstGenbkiObjectId) > continue; > > But I don't think it needs to be considered as filtered data. This is > mostly schema related info. But I wanted to confirm once. Thoughts? Yeah. It's part of metadata which in turn is sent only when needed. It's not part of, say, transaction changes. So it can't be considered as filtering. > > 3) > +-- total_txns may vary based on the background activity but sent_txns should > +-- always be 1 since the background transactions are always skipped. Filtered > +-- bytes would be set only when there's a change that was passed to the plugin > +-- but was filtered out. Depending upon the background transactions, filtered > +-- bytes may or may not be zero. > +SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS > spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS > total_bytes, plugin_sent_txns, plugin_sent_bytes > 0 AS sent_bytes, > plugin_filtered_bytes >= 0 AS filtered_bytes FROM > pg_stat_replication_slots ORDER BY slot_name; > > In comment either we can say plugin_sent_txns instead of sent_txns or > in the query we can fetch plugin_sent_txns AS sent_txns, so that we > can relate comment and query. > Used plugin_sent_txns in the comment as well as query. > > 4) > + sentTxns is the number of transactions sent downstream > + by the output plugin. sentBytes is the amount of data > + sent downstream by the output plugin. > + OutputPluginWrite is expected to update this counter > + if ctx->stats is initialized by the output plugin. > + filteredBytes is the size of changes in bytes that are > + filtered out by the output plugin. Function > + ReorderBufferChangeSize may be used to find > the size of > + filtered ReorderBufferChange. > + > > Either we can mention units as 'bytes' for both filteredBytes and > sentBytes or for none. Currently filteredBytes says 'in bytes' while > sentBytes does not. Used 'in bytes' in both the places. Thanks for your review. I will include these changes in the next set of patches. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Mar 17, 2026 at 10:32 AM Amit Kapila wrote: > > On Tue, Mar 17, 2026 at 4:51 AM Michael Paquier wrote: > > > > The trick with this patch is to make sure that all the relevant places > > where data is sent downstream are correctly incremented. > > > > Right. > > > As far as I > > can see, things seem to be covered, but I cannot help but wonder if we > > are missing one or more places. @Amit, do you feel a hole somewhere? > > > > Yesterday, I raised a point related to this which is whether we want > to count messages like keep_alive, copy_done, or > PqReplMsg_PrimaryStatusUpdate? These don't contain the user decoded > data from WAL but some additional information between > publisher-subscriber required to ensure data is being applied. I think sent_bytes should count the logically decoded data - the data that the output plugin produces in logical form. This is the amount of data that the downstream has to process for data replication. So, the protocol related data shouldn't be counted here, IMO. Compare this with the total_bytes which does not account for the memory used by the reorder buffer metadata itself - it just accounts for the size of the changes. If we describe the sent_bytes as below, we are explicitly mentioning what sent_bytes is. So anything which is not data in the output format is excluded including the protocol data. If you think we need to clarify that we are excluding protocol data, we can add a line. But that may confuse readers who are unaware of the protocol data. Amount of transaction changes sent downstream in the output plugin format for this slot. The output plugin may filter the changes it receives. Hence the amount of data that it converts to the output plugin format is less than the total_bytes. But the format of data before and after the conversion is different. Hence the value of sent_bytes is not directly related to the value of total_bytes. The phrase "transaction changes" is being debated in a separate sub-thread, I will reply there. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 2:38 PM shveta malik wrote: > > On Wed, Sep 24, 2025 at 12:47 PM Ashutosh Bapat > wrote: > > > > On Wed, Sep 24, 2025 at 10:12 AM shveta malik wrote: > > > > > > I tested the flows with > > > a) logical replication slot and get-changes. > > > b) filtered data flows: pub-sub creation with row_filters, 'publish' > > > options. I tried to verify plugin fields as compared to total_wal* > > > fields. > > > c) reset flow. > > > > > > While tests for a and c are present already. I don't see tests for b > > > anywhere when it comes to stats. Do you think we shall add a test for > > > filtered data using row-filter somewhere? > > > > Added a test in 028_row_filter. Please find it in the attached > > patchset. > > Test looks good. Thanks. Added to three more files. I think we have covered all the cases where filtering can occur. PFA patches. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Jul 1, 2025 at 4:23 PM Amit Kapila wrote: > > On Mon, Jun 30, 2025 at 3:24 PM Ashutosh Bapat > wrote: > > > > Hi All, > > In a recent logical replication issue, there were multiple replication > > slots involved, each using a different publication. Thus the amount of > > data that was replicated through each slot was expected to be > > different. However, total_bytes and total_txns were reported the same > > for all the replication slots as expected. One of the slots started > > lagging and we were trying to figure out whether its the WAL sender > > slowing down or the consumer (in this case Debezium). The lagging > > slot then showed total_txns and total_bytes lesser than other slots > > giving an impression that the WAL sender is processing the data > > slowly. Had pg_stat_replication_slot reported the amount of data > > actually sent downstream, it would have been easier to compare it with > > the amount of data received by the consumer and thus pinpoint the > > bottleneck. > > > > Here's a patch to do the same. It adds two columns > > - sent_txns: The total number of transactions sent downstream. > > - sent_bytes: The total number of bytes sent downstream in data messages > > to pg_stat_replication_slots. sent_bytes includes only the bytes sent > > as part of 'd' messages and does not include keep alive messages or > > CopyDone messages for example. But those are very few and can be > > ignored. If others feel that those are important to be included, we > > can make that change. > > > > Plugins may choose not to send an empty transaction downstream. It's > > better to increment sent_txns counter in the plugin code when it > > actually sends a BEGIN message, for example in pgoutput_send_begin() > > and pg_output_begin(). This means that every plugin will need to be > > modified to increment the counter for it to reported correctly. > > > > What if some plugin didn't implemented it or does it incorrectly? > Users will then complain that PG view is showing incorrect value. That is right. To fix the problem of plugins not implementing the counter increment logic we could use logic similar to how we track whether OutputPluginPrepareWrite() has been called or not. In ReorderBufferTxn, we add a new member sent_status which would be an enum with 3 values UNKNOWN, SENT, NOT_SENT. Initially the sent_status = UNKNOWN. We provide a function called plugin_sent_txn(ReorderBufferTxn txn, sent bool) which will set sent_status = SENT when sent = true and sent_status = NOT_SENT when sent = false. In all the end transaction callback wrappers like commit_cb_wrapper(), prepare_cb_wrapper(), stream_abort_cb_wrapper(), stream_commit_cb_wrapper() and stream_prepare_cb_wrapper(), if tsent_status = UNKNOWN, we throw an error. If sent_status = SENT, we increment sent_txns. That will catch any plugin which does not call plugin_set_txn(). The plugin may still call plugin_sent_txn() with sent = true when it should have called it with sent = false or vice versa, but that's hard to monitor and control. Additionally, we should highlight in the document that sent_txns is as per report from the output plugin so that users know where to look for in case they see a wrong/dubious value. I see this similar to what we do with pg_stat_replication::reply_time which may be wrong if a non-PG standby reports the wrong value. Documentation says "Send time of last reply message received from standby server", so the users know where to look for incase they spot the error. Does that look good? I am open to other suggestions. > Shouldn't the plugin specific stats be shown differently, for example, > one may be interested in how much plugin has filtered the data because > it was not published or because something like row_filter caused it > skip sending such data? > That looks useful, we could track the ReorderBufferChange's that were not sent downstream and add their sizes to another counter ReorderBuffer::filtered_bytes and report it in pg_stat_replication_slots. I think we will need to devise a mechanism similar to above by which the plugin tells core whether a change has been filtered or not. However, that will not be a replacement for sent_bytes, since filtered_bytes or total_bytes - filtered_bytes won't tell us how much data was sent downstream, which is crucial to the purpose stated in my earlier email. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 1:55 PM Bertrand Drouvot
wrote:
>
> Hi,
>
> On Wed, Sep 24, 2025 at 12:51:29PM +0530, Ashutosh Bapat wrote:
> > On Wed, Sep 24, 2025 at 12:32 PM Bertrand Drouvot
> > wrote:
> > > > > - create a table and use pg_logical_slot_get_changes with ('skip-empty-xacts', '1')
> > > > > then I don't see plugin_sent_bytes increasing (which makes sense) but I also don't
> > > > > see plugin_filtered_bytes increasing. I think that would make sense to also increase
> > > > > plugin_filtered_bytes in this case (and for the other options that would skip
> > > > > sending data). Thoughts?
> > > >
> > > > Thanks for bringing this up. I don't think we discussed this
> > > > explicitly in the thread. The changes which are filtered out by the
> > > > core itself e.g. changes to the catalogs or changes to other databases
> > > > or changes from undesired origins are not added to the reorder buffer.
> > > > They are not counted in total_bytes. The transactions containing only
> > > > such changes are not added to reorder buffer, so even total_txns does
> > > > not count such empty transactions. If we count these changes and
> > > > transactions in plugin_filtered_bytes, and plugin_filtered_txns, that
> > > > would create an anomaly - filtered counts being higher than total
> > > > counts. Further since core does not add these changes and transactions
> > > > to the reorder buffer, there is no way for a plugin to know about
> > > > their existence and hence count them. Does that make sense?
> > >
> > > Yes. Do you think that the doc in the patch is clear enough regarding this point?
> > > I mean the doc looks correct (mentioning the output plugin) but would that make
> > > sense to insist that core filtering is not taken into account?
> >
> > Do you mean, should we mention in the docs that core filtering is not
> > taken into account?
> > I would question whether that's called filtering
> > at all, in the context of logical decoding. The view should be read in
> > the context of logical decoding. For example, we aren't mentioning
> > that total_bytes does not include changes from other database.
>
> Right. But, in the example above, do you consider "skip-empty-xacts" as "core"
> or "plugin" filtering?
>
> It's an option part of the "test_decoding" plugin, so it's the plugin choice to
> not display empty xacts (should the option be set accordingly). Then should it
> be reported in plugin_filtered_bytes? (one could write a plugin, decide to
> skip/filter empty xacts or whatever in the plugin callbacks: should that be
> reported as plugin_filtered_bytes?)
If a transaction becomes empty because the plugin filtered all the
changes then plugin_filtered_bytes will be incremented by the amount
of filtered changes. If the transaction was empty because core didn't
send any of the changes to the output plugin, there was nothing
filtered by the output plugin so plugin_filtered_bytes will not be
affected.
skip_empty_xacts controls whether BEGIN and COMMIT are sent for an
empty transaction or not. It does not filter "changes". It affects
"sent_bytes".
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Tue, Jan 27, 2026 at 11:08 PM Andres Freund wrote: > > Hi, > > On 2025-12-11 10:29:42 +0530, Ashutosh Bapat wrote: > > Please review. > > I'd simplify the patch to, initially, to just track the sent bytes. For one, > that's by *far* the most useful statistic. But I also have some concerns > about the other stats: > Ok. I have split the patch into two 0001 to track sent_bytes 0002 to track filtered_bytes and sent_txns > > - To me filteredBytes is a pretty bogus number - the size that the output > plugin would have sent and ReorderBufferChangeSize() are only kinda > related. It'll be a hard number to interpret, I think. > > It also seems to not account for filtering that happens based on the origin > id. > total_bytes doesn't count the amount of changes that were never added to the reorder buffer including those filtered based on the origin. filtered_bytes is supposed to report the amount of changes out of total_bytes that were filtered by the output plugin. It acts as an indicator of filtering efficiency of a publication or whatever filtering mechanism is employed by the output plugin. > > - I don't have fundamental opposition to tracking the number of sent > transactions, but I think the implementation is at the wrong place. > > I think we ought to add explicit support for output plugins to filter > transactions. Calling output plugins once for each change in a large > transaction, which we already decided to not send out, makes no sense. It's > far from free to do all the setup to decode a tuple, if the transaction is > filtered, we shouldn't do that. > > It's also far far from free to restore changes from disk if we are going to > throw away the whole transaction. All the transactions that decided not to be sent out e.g. the transactions committed before a confirmed_flush_lsn or the transactions from filtered origins never reach the reorder buffer. They don't have the problems you mention above. These transactions are not subject of sent_txns at all. > > The current way requires each output plugin to maintain its own tracking > about whether it decided to not output the transaction, which doesn't seem > right to me. The transactions which are sent downstream after applying transaction level filtering by the output plugin are counted by sent_txns. Right now these are the transactions whose all changes have been filtered out by the output plugin. The output plugin can realize only at the time of decoding the end of the transaction that it has not sent any changes for that transaction and filter it out. It can not report earlier than that. So I don't see any other way to implement the counter. If it would have been the case that the output plugin could know that a transaction is going to get filtered completely when decoding one of the changes, what you are suggesting would be helpful. But that's not the case today. > > I'm also just not sure how useful it is, because most of the time we're > going to filter on a per-change basis (e.g. only rels in a publication). But > that's only sometimes going to affect the numbers of sent transactions. > > > I'm not convinced as-is it's worth the breakage of all output plugins. Hmm. I do see a usecase for both filtered_bytes and sent_txns as indicators of filtering efficiency of output plugin. But not everybody will see it worth breaking all output plugins. I think we all agree that sent_bytes is useful, so I suggest that we review and commit 0001. We can debate about 0002 after that. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
Hi All, On Tue, Nov 18, 2025 at 4:14 PM Amit Kapila wrote: > > On Tue, Nov 18, 2025 at 4:05 PM Ashutosh Bapat > wrote: > > > > On Tue, Nov 18, 2025 at 3:24 PM Amit Kapila wrote: > > > > > > On Tue, Nov 4, 2025 at 4:29 PM Ashutosh Bapat > > > wrote: > > > > > > > > a. sentTxns and filteredBytes need to be modified in the output plugin > > > > code. The behaviour there is inherently output plugin specific, and > > > > requires output plugin specific implementation. > > > > > > > > > > Is it possible that we allow change callback (LogicalDecodeChangeCB) > > > to return a boolean such that if the change is decoded and sent, it > > > returns true, otherwise, false? If so, the caller could deduce from it > > > the filtered bytes, and if none of the change calls returns true, this > > > means the entire transaction is not sent. > > > > > > I think this should address Andres's concern of explicitly tracking > > > these stats in plugins, what do you think? > > > > > > > I was thinking about a similar thing. But I am skeptical since the > > calling logic is not straight forward - there's an indirection in > > between. Second, it means that all the plugins have to adapt to the > > new callback definition. It is optional in my current approach. Since > > both of us have thought of this approach, I think it's worth a try. > > > > "if none of the change calls returns true, this means the entire > > transaction is not sent" isn't true. A plugin may still send an empty > > transaction. I was thinking of making commit/abort/prepare callbacks > > to return true/false to indicate whether a transaction was sent or not > > and increment the counter accordingly. The plugin has to take care of > > not returning true for both prepare and commit or prepare and abort. > > So may be just commit and abort should be made to return true or > > false. What do you think? > > > > Sounds reasonable to me. Sorry for the delayed response. PFA the patch implementing the idea discussed above. It relies on the output plugin callback to return correct boolean but maintains the statistics in the core itself. I have reviewed all the previous comments and applied the ones which are relevant to the new approach again. Following two are worth noting here. In order to address Amit's concern [1] that an inaccuracy in these counts because of a bug in output plugin code may be blamed on the core, I have added a note in the documentation of view pg_stat_replication_slot in order to avoid such a blame and also directing users to plugin they should investigate. With the statistics being maintained by the core, Bertrand's concern about stale statistics [2] are also addressed. Also it does not have the asymmetry mentioned in point 2 in [3]. Please review. [1] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com [2] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal [3] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
Hi Amit and Andres, On Thu, Dec 11, 2025 at 10:29 AM Ashutosh Bapat wrote: > > Sorry for the delayed response. PFA the patch implementing the idea > discussed above. It relies on the output plugin callback to return > correct boolean but maintains the statistics in the core itself. > > I have reviewed all the previous comments and applied the ones which > are relevant to the new approach again. Following two are worth noting > here. > > In order to address Amit's concern [1] that an inaccuracy in these > counts because of a bug in output plugin code may be blamed on the > core, I have added a note in the documentation of view > pg_stat_replication_slot in order to avoid such a blame and also > directing users to plugin they should investigate. > > With the statistics being maintained by the core, Bertrand's concern > about stale statistics [2] are also addressed. Also it does not have > the asymmetry mentioned in point 2 in [3]. > > Please review. > > [1] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com > [2] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal > [3] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com Andres, Can you please review the new implementation and let me know whether it addresses the concern you raised in [4] Amit, does it address your concerns in [1] (see above references) reasonably? [4] https://www.postgresql.org/message-id/zzidfgaowvlv4opptrcdlw57vmulnh7gnes4aerl6u35mirelm@tj2vzseptkjk -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Sun, Jul 13, 2025 at 4:34 PM Amit Kapila wrote: > > On Tue, Jul 1, 2025 at 7:35 PM Ashutosh Bapat > wrote: > > > > On Tue, Jul 1, 2025 at 4:23 PM Amit Kapila wrote: > > > > > > On Mon, Jun 30, 2025 at 3:24 PM Ashutosh Bapat > > > wrote: > > > > > > > > Hi All, > > > > In a recent logical replication issue, there were multiple replication > > > > slots involved, each using a different publication. Thus the amount of > > > > data that was replicated through each slot was expected to be > > > > different. However, total_bytes and total_txns were reported the same > > > > for all the replication slots as expected. One of the slots started > > > > lagging and we were trying to figure out whether its the WAL sender > > > > slowing down or the consumer (in this case Debezium). The lagging > > > > slot then showed total_txns and total_bytes lesser than other slots > > > > giving an impression that the WAL sender is processing the data > > > > slowly. Had pg_stat_replication_slot reported the amount of data > > > > actually sent downstream, it would have been easier to compare it with > > > > the amount of data received by the consumer and thus pinpoint the > > > > bottleneck. > > > > > > > > Here's a patch to do the same. It adds two columns > > > > - sent_txns: The total number of transactions sent downstream. > > > > - sent_bytes: The total number of bytes sent downstream in data messages > > > > to pg_stat_replication_slots. sent_bytes includes only the bytes sent > > > > as part of 'd' messages and does not include keep alive messages or > > > > CopyDone messages for example. But those are very few and can be > > > > ignored. If others feel that those are important to be included, we > > > > can make that change. > > > > > > > > Plugins may choose not to send an empty transaction downstream. It's > > > > better to increment sent_txns counter in the plugin code when it > > > > actually sends a BEGIN message, for example in pgoutput_send_begin() > > > > and pg_output_begin(). This means that every plugin will need to be > > > > modified to increment the counter for it to reported correctly. > > > > > > > > > > What if some plugin didn't implemented it or does it incorrectly? > > > Users will then complain that PG view is showing incorrect value. > > > > That is right. > > > > To fix the problem of plugins not implementing the counter increment > > logic we could use logic similar to how we track whether > > OutputPluginPrepareWrite() has been called or not. In > > ReorderBufferTxn, we add a new member sent_status which would be an > > enum with 3 values UNKNOWN, SENT, NOT_SENT. Initially the sent_status > > = UNKNOWN. We provide a function called > > plugin_sent_txn(ReorderBufferTxn txn, sent bool) which will set > > sent_status = SENT when sent = true and sent_status = NOT_SENT when > > sent = false. In all the end transaction callback wrappers like > > commit_cb_wrapper(), prepare_cb_wrapper(), stream_abort_cb_wrapper(), > > stream_commit_cb_wrapper() and stream_prepare_cb_wrapper(), if > > tsent_status = UNKNOWN, we throw an error. > > > > I think we don't want to make it mandatory for plugins to implement > these stats, so instead of throwing ERROR, the view should show that > the plugin doesn't provide stats. How about having OutputPluginStats > similar to OutputPluginCallbacks and OutputPluginOptions members in > LogicalDecodingContext? It will have members like stats_available, > txns_sent or txns_skipped, txns_filtered, etc. Not making mandatory looks useful. I can try your suggestion. Rather than having stats_available as a member of OutputPluginStats, it's better to have a NULL value for the corresponding member in LogicalDecodingContext. We don't want an output plugin to reset stats_available once set. Will that work? > I am thinking it will > be better to provide this information in a separate view like > pg_stat_plugin_stats or something like that, here we can report > slot_name, plugin_name, then the other stats we want to implement part > of OutputPluginStats. As you have previously pointed out, the view should make it explicit that the new stats are maintained by the plugin and not core. I agree with that intention. However, already have three views pg_replication_slots (which has slot name and plugin name), then pg_replication_stats which is about stats maintained by a WAL sender or running replication and then pg_stat_replication_slots, which is about accumulated statistics for a replication through a given replication slot. It's already a bit hard to keep track of who's who when debugging an issue. Adding one more view will add to confusion. Instead of adding a new view how about a. name the columns as plugin_sent_txns, plugin_sent_bytes, plugin_filtered_change_bytes to make it clear that these columns are maintained by plugin b. report these NULL if stats_available = false OR OutputPluginStats is not set in LogicalDecodingContext c. Document that NULL value for these columns indicates that the plugin is not maintaining/reporting these stats d. adding plugin name to pg_stat_replication_slots, that will make it easy for users to know which plugin they should look at in case of dubious or unavailable stats -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Thu, Dec 18, 2025 at 11:52 PM Bertrand Drouvot
wrote:
>
> Hi,
>
> On Thu, Dec 18, 2025 at 06:22:40PM +0530, Ashutosh Bapat wrote:
> > Hi Bertrand,
> >
> > On Wed, Dec 17, 2025 at 2:12 PM Bertrand Drouvot
> > wrote:
> > >
> > > What worries me is all those API changes:
> > >
> > > -typedef void (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx,
> > > +typedef bool (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx,
> > >
> > > Those changes will break existing third party logical decoding plugin, even ones
> > > that don't want the new statistics features.
> > >
> > > What about not changing those and just add a single new optional callback, say?
> > >
> > > typedef void (*LogicalDecodeReportStatsCB)(
> > > LogicalDecodingContext *ctx,
> > > ReorderBufferTXN *txn,
> > > bool *transaction_sent,
> > > size_t *bytes_filtered
> > > );
> > >
> > > This way:
> > >
> > > - Existing plugins can still work without modification
> > > - New or existing plugins can choose to provide statistics
> > >
> >
> > I think that it will bring back the same problems that the previous
> > design had or am I missing something?
>
> I think that my example was confusing due to "size_t *bytes_filtered". I think
> that what we could do is something like:
>
> "
> typedef void (*LogicalDecodeReportStatsCB)(
> LogicalDecodingContext *ctx,
> LogicalDecodeEventType event_type,
> bool *filtered,
> bool *txn_sent);
> "
>
> Note that there is no more size_t.
>
Thanks for the clarification. It fixes the problem of filteredBytes
divergence. Since the core is calling stats callback, the problem of
plugin not calling the function at appropriate places is also not
there. IIUC, it still has some problems from the previous solution and
some new problems as explained below.
> Then for, for example in change_cb_wrapper(), we could do:
>
> "
> ctx->callbacks.change_cb(ctx, txn, relation, change);
>
> if (ctx->callbacks.report_stats_cb)
> {
> bool filtered = false;
>
> ctx->callbacks.report_stats_cb(ctx, LOGICALDECODE_CHANGE,
> &filtered, NULL);
>
> if (filtered)
> cache->filteredBytes += ReorderBufferChangeSize(change);
> }
> "
>
> The plugin would need to "remember" that it filtered (so that it can
> reply to the callback). It could do that by adding say "last_event_filtered" to
> it's output_plugin_private structure.
Why does the core send NULL for the second parameter? Does the output
plugin have to take care of NULL references too?
I think the core will end up calling this or similar stanza at every
callback since it won't know when the output plugin will have
statistics to report. That's more complexity and wasted CPU cycles in
core.
>
> That's more work on the plugin side and we would probably need to provide some
> examples from our side.
Andres is objecting to this exact thing. IIUC, the code changes there
were far simpler than this proposal. Am I missing something?
My feeling is that the core will end up
>
> I think the pros are that:
>
> - plugins that don't want to report stats would have nothing to do (no breaking
> changes)
I don't think there will be an output plugin which wouldn't want to
take advantage of the statistics. The easier it is for them to adopt
the statistics, as is with my proposal, the better. With this proposal
output plugins have to do more work if they want to support
statistics. That itself will create a barrier for them to adopt the
statistics. We want the output plugins to support statistics so that
users can benefit. Let's make it easier for the output plugins to
implement them.
I feel this proposal makes both sides, the core and the output plugin
complex in pursuit of a goal which is not worth it.
This stil has the problem that you had raised. What if an output
plugin stops supporting statistics across versions?
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 8:11 PM Bertrand Drouvot wrote: > > Hi, > > On Wed, Sep 24, 2025 at 05:28:44PM +0530, Ashutosh Bapat wrote: > > On Wed, Sep 24, 2025 at 2:38 PM shveta malik wrote: > > > > > > On Wed, Sep 24, 2025 at 12:47 PM Ashutosh Bapat > > > wrote: > > > > > > > > On Wed, Sep 24, 2025 at 10:12 AM shveta malik wrote: > > > > > > > > > > I tested the flows with > > > > > a) logical replication slot and get-changes. > > > > > b) filtered data flows: pub-sub creation with row_filters, 'publish' > > > > > options. I tried to verify plugin fields as compared to total_wal* > > > > > fields. > > > > > c) reset flow. > > > > > > > > > > While tests for a and c are present already. I don't see tests for b > > > > > anywhere when it comes to stats. Do you think we shall add a test for > > > > > filtered data using row-filter somewhere? > > > > > > > > Added a test in 028_row_filter. Please find it in the attached > > > > patchset. > > > > > > Test looks good. > > > > Thanks. Added to three more files. I think we have covered all the > > cases where filtering can occur. > > > > PFA patches. > > Thanks for the new version! > > A few random comments: > > === 1 > > + > + > + plugin_filtered_bytes bigint > + > + > + Amount of changes, from total_wal_bytes, filtered > + out by the output plugin and not sent downstream. Please note that it > + does not include the changes filtered before a change is sent to > + the output plugin, e.g. the changes filtered by origin. The count is > + maintained by the output plugin mentioned in > + plugin. > > I found "The count" somehow ambiguous. What about "This statistic" instead? Existing fields use term "The counter". Changed "The count" to "The counter". > > === 2 > > + subtransactions. These transactions are subset of transctions sent to > > s/transctions/transactions Done. > > === 3 > > + the decoding plugin. Hence this count is expected to be lesser than or > > s/be lesser/be less/? (not 100% sure) Less than is correct. Fixed. > > === 4 > > +extern Size ReorderBufferChangeSize(ReorderBufferChange *change); > > Another approach could be to pass the change's size as an argument to the > callbacks? That would avoid to expose ReorderBufferChangeSize publicly. Do you see any problem in exposing ReorderBufferChangeSize(). It's a pretty small function and may be quite handy to output plugins otherwise as well. And we expose many ReorderBuffer related functions; so this isn't the first. If we were to do as you say, it will change other external facing APIs like change_cb(). Output plugins will need to change their code accordingly even when they don't want to support plugin statistics. Given that we have made maintaining plugin statistics optional, forcing API change does not make sense. For example, test_decoding which does not filter anything would unnecessarily have to change its code. I considered adding a field size to ReorderBufferChange itself. But that means we increase the amount of memory used in the reorder buffer, which seems to have become prime estate these days. So rejected that idea as well. Advantage of this change is that the minimal cost of calculating the size and maintaining the code change is incurred only when filtering happens, by the plugins which want to filter and maintain statistics. > > === 5 > > ctx->output_plugin_private = data; > + ctx->stats = palloc0(sizeof(OutputPluginStats)); > > I was wondering if we need to free this in pg_decode_shutdown, but it looks > like it's done through FreeDecodingContext() anyway. That's correct. Even output_plugin_private is freed when the decoding memory context is freed. Thanks for the review comments. I have addressed the comments in my repository and the changes will be included in the next set of patches. Do you have any further review comments? -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Fri, Sep 19, 2025 at 11:48 AM shveta malik wrote: > > On Thu, Sep 18, 2025 at 3:54 PM Ashutosh Bapat > wrote: > > > > > > > > > > Few comments: > > > > > > 1) > > > postgres=# select slot_name, > > > total_bytes,plugin_filtered_bytes,plugin_sent_bytes from > > > pg_stat_replication_slots order by slot_name; > > > slot_name | total_bytes | plugin_filtered_bytes | plugin_sent_bytes > > > -----------+-------------+-----------------------+------------------- > > > slot1 | 800636 | 793188 | 211 > > > sub1 | 401496 | 132712 | 84041 > > > sub2 | 401496 | 396184 | 674 > > > sub3 | 401496 | 145912 | 79959 > > > (4 rows) > > > > > > Currently it looks quite confusing. 'total_bytes' gives a sense that > > > it has to be a sum of filtered and sent. But they are no way like > > > that. In the thread earlier there was a proposal to change the name to > > > reordered_txns, reordered_bytes. That looks better to me. It will give > > > clarity without even someone digging into docs. > > > > I also agree with that. But that will break backward compatibility. > > Yes, that it will do. > > > Do > > you think other columns like spill_* and stream_* should also be > > renamed with the prefix "reordered"? > > > > Okay, I see that all fields in pg_stat_replication_slots are related > to the ReorderBuffer. On reconsideration, I’m unsure whether it's > appropriate to prefix all of them with reorderd_. For example, > renaming spill_bytes and stream_bytes to reordered_spill_bytes and > reordered_stream_bytes. These names start to feel overly long, and I > also noticed that ReorderBuffer isn’t clearly defined anywhere in the > documentation (or at least I couldn’t find it), even though the term > 'reorder buffer' does appear in a few places. > > As an example, see ReorderBufferRead, ReorderBufferWrite wait-types > at [1]. Also in plugin-doc [2], we use 'ReorderBufferTXN'. And now, we > are adding: ReorderBufferChangeSize, ReorderBufferChange > > This gives me a feeling, will it be better to let > pg_stat_replication_slots as is and add a brief ReorderBuffer section > under Logical Decoding concepts [3] just before Output Plugins. And > then, pg_stat_replication_slots can refer to that section, clarifying > that the bytes, counts, and txn fields pertain to ReorderBuffer > (without changing any of the fields). > > And then to define plugin related data, we can have a new view, say > pg_stat_plugin_stats (as Amit suggested earlier) or > pg_stat_replication_plugins. I understand that adding a new view might > not be desirable, but it provides better clarity without requiring > changes to the existing fields in pg_stat_replication_slots. I also > strongly feel that to properly tie all this information together, a > brief definition of the ReorderBuffer is needed. Other pages that > reference this term can then point to that section. Thoughts? Even if we keep two views, when they are joined, users will still get confused by total_* names. So it's not solving the underlying problem. Andres had raised the point about renaming total_* fields with me off-list earlier. He suggested names total_wal_bytes, and total_wal_txns in an off list discussion today. I think those convey the true meaning - that these are txns and bytes that come from WAL. Used those in the attached patches. Prefix reordered would give away lower level details, so I didn't use it. I agree that it would be good to mention ReorderBuffer in the logical decoding concepts section since it mentions structures ReorderBuffer*. But that would be a separate patch since we aren't using "reordered" in the names of the fields. 0001 is the previous patch 0002 changes addressing your and Bertrand's comments. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
Hi Chao, On Thu, Dec 11, 2025 at 3:09 PM Chao Li wrote: > > Hi, Ashutosh, > > I just quickly went through the patch. Obviously I need more time to fully understand the patch, I will do a deep review today. In the meantime, I just caught a nit issue. > Thanks for your review. > > On Dec 11, 2025, at 12:59, Ashutosh Bapat wrote: > > > > > > Please review. > > > > [1] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com > > [2] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal > > [3] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com > > > > -- > > Best Wishes, > > Ashutosh Bapat > > > > 1 > ``` > + linkend="logicaldecoding-output-plugin-callbacks"/>. A descripancy in those > ``` > > Typo: descripancy => discrepancy > Thanks for pointing this out. I have fixed it my code. However, at this point I am looking for a design review, especially to verify that the new implementation addresses Andres's concern raised in [1] while not introducing any design issues raised earlier e.g. those raised in threads [2], [3] and [4] [1] https://www.postgresql.org/message-id/zzidfgaowvlv4opptrcdlw57vmulnh7gnes4aerl6u35mirelm@tj2vzseptkjk > > [2] https://www.postgresql.org/message-id/CAA4eK1KzYaq9dcaa20Pv44ewomUPj_PbbeLfEnvzuXYMZtNw0A%40mail.gmail.com > > [3] https://www.postgresql.org/message-id/aNZ1T5vYC1BtKs4M@ip-10-97-1-34.eu-west-3.compute.internal > > [4] https://www.postgresql.org/message-id/CAExHW5tfVHABuv1moL_shp7oPrWmg8ha7T8CqwZxiMrKror7iw%40mail.gmail.com -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Fri, Sep 26, 2025 at 10:28 PM Bertrand Drouvot wrote: > > > > > > > > I don't think this is an issue. There is no way for the core to tell > > whether the plugin will provide stats or not, unless it sets that > > ctx->stats which happens in the startup callback. Till then it is > > rightly providing the values accumulated so far. Once the decoding > > starts, we know that the plugin is not providing any stats and we > > don't display anything. > > Yeah, I got the technical reasons, but I think there's a valid user experience > concern here: seeing statistics for a plugin that doesn't actually support > statistics is misleading. > 1. If the plugin never supported statistics, we will never report stats. So nothing misleading there. 2. If the plugin starts supporting statistics and continues to do so, we will report the stats since the time they are made available and continue to do so. Nothing misleading there. 3. If the plugin starts supporting statistics and midway discontinues its support, it already has a problem with backward compatibility. Practically it would 1 or 2, which are working fine. I don't think we will encounter case 3 practically. Do you have a practical use case where a plugin would discontinue supporting stats? Even in case 3, I think we need to consider the fact that these stats are "cumulative". So if a plugin discontinues reporting stats, they should go NULL only when the next accumulation action happens, not before that. > What we need is a call to pgstat_report_replslot() to display stats that reflect > the current plugin behavior. We can't just call pgstat_report_replslot() > in say RestoreSlotFromDisk() because we really need the decoding to start. > > So one idea could be to set a flag (per slot) when pgstat_report_replslot() > has been called (for good reasons) and check for this flag in > pg_stat_get_replication_slot(). > > If the flag is not set, then set the plugin fields to NULL. > If the flag is set, then display their values (like now). This approach will have the same problem. Till pgstat_report_replslot() is called, the old statistics will continue to be shown. > > And we should document that the plugin stats are not available (i.e are NULL) > until the decoding has valid stats to report after startup. After "startup" would mislead users since then they will think that the statistics will be NULL just before the decoding (re)starts. The current documentation is " It is NULL when statistics is not initialized or immediately after a reset or when not maintained by the output plugin.". I think that covers all the cases. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Oct 27, 2025 at 4:47 PM shveta malik wrote:
>
> Few comments:
>
> 1)
> pgoutput_truncate:
>
> if (nrelids > 0)
> {
> OutputPluginPrepareWrite(ctx, true);
> logicalrep_write_truncate(ctx->out,
> xid,
> nrelids,
> relids,
> change->data.truncate.cascade,
> change->data.truncate.restart_seqs);
> OutputPluginWrite(ctx, true);
> }
> + else
> + ctx->stats->filteredBytes += ReorderBufferChangeSize(change);
> +
>
> It seems that filteredBytes are only counted for TRUNCATE when nrelids
> is 0. Can nrelids only be 0 or same as nrelations?
>
> The below code makes me think that nrelids can be any number between 0
> and nrelations, depending on which relations are publishable and which
> supports publishing TRUNCATE. If that’s true, shouldn’t we count
> filteredBytes in each such skipped case?
IIIUC, you are suggesting that we should add
ReorderBufferChangeSize(change) for every relation which is not part
of the publication or whose truncate is not published. I think that
won't be correct since it can lead to a situation where filtered bytes
> total bytes which should never happen. Even if there is a single
publishable relation whose truncate is published, the change should
not be considered as filtered since something would be output
downstream. Otherwise filtered bytes as well as sent bytes both will
be incremented causing an inconsistency (which would be hard to notice
since total bytes - filtered bytes has something to do with the sent
bytes but the exact correlation is hard to grasp in a formula).
We may increment filteredBytes by sizeof(OID) for every relation we
skip here OR by ReoderBufferChangeSize(change) if all the relations
are filtered, but that's too much dependent on how the WAL record is
encoded; and adding that dependency in an output plugin code seems
hard to manage.
If you are suggesting something else, maybe sharing actual code
changes would help.
>
>
> 2)
> + int64 filteredBytes; /* amount of data from reoder buffer that was
>
> reoder --> reorder
Done.
>
> 3)
> One small nitpick:
>
> + /*
> + * If output plugin has chosen to maintain its stats, update the amount of
> + * data sent downstream.
> + */
> + if (ctx->stats)
> + ctx->stats->sentBytes += ctx->out->len + sizeof(XLogRecPtr) +
> sizeof(TransactionId);
>
> The way sentBytes is updated here feels a bit unnatural; we’re adding
> the lengths for values[2], then [0], and then [1]. Would it be cleaner
> to introduce a len[3] array similar to the existing values[3] and
> nulls[3] arrays? We could initialize len[i] alongside values[i], and
> later just sum up all three elements when updating
> ctx->stats->sentBytes. It would be easier to understand as well.
Instead of an array of length 3, we could keep a counter sentBytes to
accumulate all lengths. It will be assigned to ctx->stats->sentBytes
at the end if ctx->stats != NULL. But that might appear as if we are
performing additions even if it won't be used ultimately. That's not
true, since this plugin will always maintain stats. Changed that way.
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Jun 15, 2026 at 2:34 PM Kyotaro Horiguchi wrote: > > Hello. > > At Mon, 15 Jun 2026 13:52:36 +0530, Ashutosh Sharma wrote in > > Sorry for chiming in - I may well be misunderstanding this, but here's > > how I'm currently thinking about it: > > > > Total transaction bytes refers to the size of decoded transactional > > data accumulated in the reorder buffer for a given transaction. > > > > Sent bytes (as I understand from the patch) refers to the size of the > > downstream output that the output plugin produces from that decoded > > data, after any filtering and format conversion. > > > > To illustrate: if a transaction's decoded changes occupy 550 bytes in > > the reorder buffer, but the output plugin filters some out and emits > > only 300 bytes downstream, then total transaction bytes = 550 and sent > > bytes = 300. Conversely, if all 550 bytes are converted into a more > > verbose format and emitted as 700 bytes, total transaction bytes > > remains 550 while sent bytes becomes 700. > > > > If I'm reading this right, since total bytes for a transaction is the > > baseline from which transaction-derived downstream output is produced, > > I wonder whether sent bytes should include only that > > transaction-derived downstream output, or also downstream protocol > > traffic such as keepalive messages, which are sent downstream but are > > not derived from transaction bytes in the reorder buffer. > > > > My instinct is that if sent bytes are meant to measure > > transaction-output throughput, keepalive messages probably shouldn't > > be included, since they have no basis in transaction data and might > > distort any comparison with total bytes. But I could be wrong - happy > > to be corrected! > > Thank you for the explanation. > > I think I understand the distinction you are making. However, my > question is one step earlier than the keepalive-message question. I am > wondering whether the new metric needs to be defined in terms of > logical-change output in the first place. > > If I understand the use case correctly, I think the discussion here is > primarily about relatively high-volume logical replication > workloads. My point is that, in that situation, I would expect the > amount of logical-change output and the amount of data actually sent > over the replication connection to show broadly similar trends. > > The latter seems easier to interpret, while still providing a useful > signal for monitoring and capacity-planning purposes. It also seems > more intuitive, since it corresponds directly to the amount of data > sent over the replication connection. The total data (bytes) sent over the network are available from network monitoring systems. That includes both protocol messages and logical change data. Out of these two logical change data is processed through a pipeline which actually loads the downstream. The protocol messages are usually processed and responded to quickly. They are not passed further down the pipeline that consumes the logical changes. Hence amount of logical change data is important; not so much the amount of protocol messages. Protocol messages form a small amount usually when there is high volume of data processed, but that need not be true generally. total_bytes is no indicator of high or low volume. The system may be generating huge amounts of WAL but publication may be filtering most of it and sending only a small amount downstream. Amount of only logical changes is accurate measure of load that is being created on the downstream pipeline. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Wed, Oct 29, 2025 at 9:14 AM shveta malik wrote:
>
> On Tue, Oct 28, 2025 at 12:46 PM Ashutosh Bapat
> wrote:
> >
> > On Mon, Oct 27, 2025 at 4:47 PM shveta malik wrote:
> > >
> > > Few comments:
> > >
> > > 1)
> > > pgoutput_truncate:
> > >
> > > if (nrelids > 0)
> > > {
> > > OutputPluginPrepareWrite(ctx, true);
> > > logicalrep_write_truncate(ctx->out,
> > > xid,
> > > nrelids,
> > > relids,
> > > change->data.truncate.cascade,
> > > change->data.truncate.restart_seqs);
> > > OutputPluginWrite(ctx, true);
> > > }
> > > + else
> > > + ctx->stats->filteredBytes += ReorderBufferChangeSize(change);
> > > +
> > >
> > > It seems that filteredBytes are only counted for TRUNCATE when nrelids
> > > is 0. Can nrelids only be 0 or same as nrelations?
> > >
> > > The below code makes me think that nrelids can be any number between 0
> > > and nrelations, depending on which relations are publishable and which
> > > supports publishing TRUNCATE. If that’s true, shouldn’t we count
> > > filteredBytes in each such skipped case?
> >
> > IIIUC, you are suggesting that we should add
> > ReorderBufferChangeSize(change) for every relation which is not part
> > of the publication or whose truncate is not published.
>
> No, that will be wrong.
>
> > I think that
> > won't be correct since it can lead to a situation where filtered bytes
> > > total bytes which should never happen. Even if there is a single
> > publishable relation whose truncate is published, the change should
> > not be considered as filtered since something would be output
> > downstream.
>
> Yes, the entire change should not be treated as filtered. The idea is
> that, for example, if there are 20 relations belonging to different
> publications and only one of them supports publishing TRUNCATE, then
> when a TRUNCATE is triggered on all, the data for that one relation
> should be counted as sent (which is currently happening based on
> nrelids), while the data for the remaining 19 should be considered
> filtered — which is not happening right now.
>
> > Otherwise filtered bytes as well as sent bytes both will
> > be incremented causing an inconsistency (which would be hard to notice
> > since total bytes - filtered bytes has something to do with the sent
> > bytes but the exact correlation is hard to grasp in a formula).
> >
> > We may increment filteredBytes by sizeof(OID) for every relation we
> > skip here OR by ReoderBufferChangeSize(change) if all the relations
> > are filtered, but that's too much dependent on how the WAL record is
> > encoded; and adding that dependency in an output plugin code seems
> > hard to manage.
> >
>
> Yes, that was the idea, to increment filteredBytes in this way. But I
> see your point. I can’t think of a better solution at the moment. If
> you also don’t have any better ideas, then at least adding a comment
> in this function would be helpful. Right now, it looks like we
> overlooked the fact that some relationships should contribute to
> filteredBytes while others should go to sentBytes.
I noticed that we do something similar while filtering columns. I
think we need to add a comment in that code as well. How about
something like below?
diff --git a/src/backend/replication/pgoutput/pgoutput.c
b/src/backend/replication/pgoutput/pgoutput.c
index 4b35f2de6aa..f2d6e20a702 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1621,7 +1621,12 @@ pgoutput_change(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
OutputPluginPrepareWrite(ctx, true);
- /* Send the data */
+ /*
+ * Send the data. Even if we end up filtering some columns
while sending the
+ * message, we won't consider the change, as a whole, to be
filtered out.
+ * Instead the filtered columns will be reflected as a smaller sentBytes
+ * count.
+ */
switch (action)
{
case REORDER_BUFFER_CHANGE_INSERT:
@@ -1728,6 +1733,13 @@ pgoutput_truncate(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
change->data.truncate.cascade,
change->data.truncate.restart_seqs);
OutputPluginWrite(ctx, true);
+
+ /*
+ * Even if we filtered out some relations, we still
send a TRUNCATE
+ * message for the remaining relations. Since the
change, as a whole, is
+ * not filtered out, we don't count modify
filteredBytes. The filtered
+ * out relations will be reflected as a smaller sentBytes count.
+ */
}
else
ctx->stats->filteredBytes += ReorderBufferChangeSize(change);
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Mon, Sep 22, 2025 at 10:44 AM shveta malik wrote:
>
> Few trivial comments:
>
> 1)
> Currently the doc says:
>
> sentTxns is the number of transactions sent downstream by the output
> plugin. sentBytes is the amount of data, in bytes, sent downstream by
> the output plugin. OutputPluginWrite will update this counter if
> ctx->stats is initialized by the output plugin. filteredBytes is the
> size of changes, in bytes, that are filtered out by the output plugin.
> Function ReorderBufferChangeSize may be used to find the size of
> filtered ReorderBufferChange.
>
> Shall we rearrange it to:
>
> sentTxns is the number of transactions sent downstream by the output
> plugin. sentBytes is the amount of data, in bytes, sent downstream by
> the output plugin. filteredBytes is the size of changes, in bytes,
> that are filtered out by the output plugin. OutputPluginWrite will
> update these counters if ctx->stats is initialized by the output
> plugin.
> The function ReorderBufferChangeSize can be used to compute the size
> of a filtered ReorderBufferChange, i.e., the filteredBytes.
>
Only sentBytes is incremented by OutputPluginWrite(), so saying that
it will update counters is not correct. But I think you intend to keep
description of all the fields together followed by any additional
information. How about the following
sentTxns is the number of transactions sent downstream
by the output plugin. sentBytes is the amount of data,
in bytes, sent downstream by the output plugin.
filteredBytes is the size of changes, in bytes, that
are filtered out by the output plugin.
OutputPluginWrite will update
sentBytes if ctx->stats is
initialized by the output plugin. Function
ReorderBufferChangeSize may be used to find the size of
filtered ReorderBufferChange.
> 2)
> My preference will be to rename the fields 'total_txns' and
> 'total_bytes' in PgStat_StatReplSlotEntry to 'total_wal_txns' and
> 'total_wal_bytes' for better clarity. Additionally, upon rethinking,
> it seems better to me that plugin-related fields are also named as
> plugin_* to clearly indicate their association. OTOH, in
> OutputPluginStats, the field names are fine as is, since the structure
> name itself clearly indicates these are plugin-related fields.
> PgStat_StatReplSlotEntry lacks such context and thus using full
> descriptive names there would improve clarity.
Ok. Done.
>
> 3)
> LogicalOutputWrite:
> + if (ctx->stats)
> + ctx->stats->sentBytes += ctx->out->len + sizeof(XLogRecPtr) +
> sizeof(TransactionId);
> p->returned_rows++;
>
> A blank line after the new change will increase readability.
>
Ok.
> ~~
>
> In my testing, the patch works as expected. Thanks!
Thanks for testing. Can we include any of your tests in the patch? Are
the tests in patch enough?
Applied those suggestions in my repository. Do you have any further
review comments?
--
Best Wishes,
Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 6:43 PM Bertrand Drouvot wrote: > > Hi, > > On Wed, Sep 24, 2025 at 03:37:07PM +0530, Ashutosh Bapat wrote: > > On Wed, Sep 24, 2025 at 1:55 PM Bertrand Drouvot > > wrote: > > > Right. But, in the example above, do you consider "skip-empty-xacts" as "core" > > > or "plugin" filtering? > > > > > > It's an option part of the "test_decoding" plugin, so it's the plugin choice to > > > not display empty xacts (should the option be set accordingly). Then should it > > > be reported in plugin_filtered_bytes? (one could write a plugin, decide to > > > skip/filter empty xacts or whatever in the plugin callbacks: should that be > > > reported as plugin_filtered_bytes?) > > > > If a transaction becomes empty because the plugin filtered all the > > changes then plugin_filtered_bytes will be incremented by the amount > > of filtered changes. If the transaction was empty because core didn't > > send any of the changes to the output plugin, there was nothing > > filtered by the output plugin so plugin_filtered_bytes will not be > > affected. > > > > skip_empty_xacts controls whether BEGIN and COMMIT are sent for an > > empty transaction or not. It does not filter "changes". It affects > > "sent_bytes". > > skip_empty_xacts was just an example. I mean a plugin could decide to filter all > the inserts for example (not saying it makes sense). But I think we'are saying the > same: say a plugin wants to filter the inserts then it's its responsability to > increment ctx->stats->filteredBytes in its "change_cb" callback for the > REORDER_BUFFER_CHANGE_INSERT action, right? Right. > If so, I wonder if it would make > sense to provide an example in the test_decoding plugin (I can see it's done > for pgoutput but that might sound more natural to look in contrib if one is > searching for an example). test_decoding does not make use of publication at all. Publication controls filtering and so test_decoding does not have any examples of filtering code. Doesn't make sense to add code to manipulate filteredBytes there. -- Best Wishes, Ashutosh Bapat
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Tue, Sep 23, 2025 at 4:06 PM Ashutosh Bapat wrote: > > On Mon, Sep 22, 2025 at 10:44 AM shveta malik wrote: > > > > Few trivial comments: > > > > 1) > > Currently the doc says: > > > > sentTxns is the number of transactions sent downstream by the output > > plugin. sentBytes is the amount of data, in bytes, sent downstream by > > the output plugin. OutputPluginWrite will update this counter if > > ctx->stats is initialized by the output plugin. filteredBytes is the > > size of changes, in bytes, that are filtered out by the output plugin. > > Function ReorderBufferChangeSize may be used to find the size of > > filtered ReorderBufferChange. > > > > Shall we rearrange it to: > > > > sentTxns is the number of transactions sent downstream by the output > > plugin. sentBytes is the amount of data, in bytes, sent downstream by > > the output plugin. filteredBytes is the size of changes, in bytes, > > that are filtered out by the output plugin. OutputPluginWrite will > > update these counters if ctx->stats is initialized by the output > > plugin. > > The function ReorderBufferChangeSize can be used to compute the size > > of a filtered ReorderBufferChange, i.e., the filteredBytes. > > > > Only sentBytes is incremented by OutputPluginWrite(), so saying that > it will update counters is not correct. But I think you intend to keep > description of all the fields together followed by any additional > information. How about the following > sentTxns is the number of transactions sent downstream > by the output plugin. sentBytes is the amount of data, > in bytes, sent downstream by the output plugin. > filteredBytes is the size of changes, in bytes, that > are filtered out by the output plugin. > OutputPluginWrite will update > sentBytes if ctx->stats is > initialized by the output plugin. Function > ReorderBufferChangeSize may be used to find the size of > filtered ReorderBufferChange. Yes, this looks good. > > > 2) > > My preference will be to rename the fields 'total_txns' and > > 'total_bytes' in PgStat_StatReplSlotEntry to 'total_wal_txns' and > > 'total_wal_bytes' for better clarity. Additionally, upon rethinking, > > it seems better to me that plugin-related fields are also named as > > plugin_* to clearly indicate their association. OTOH, in > > OutputPluginStats, the field names are fine as is, since the structure > > name itself clearly indicates these are plugin-related fields. > > PgStat_StatReplSlotEntry lacks such context and thus using full > > descriptive names there would improve clarity. > > Ok. Done. > > > > > 3) > > LogicalOutputWrite: > > + if (ctx->stats) > > + ctx->stats->sentBytes += ctx->out->len + sizeof(XLogRecPtr) + > > sizeof(TransactionId); > > p->returned_rows++; > > > > A blank line after the new change will increase readability. > > > > Ok. > > > ~~ > > > > In my testing, the patch works as expected. Thanks! > > Thanks for testing. Can we include any of your tests in the patch? Are > the tests in patch enough? I tested the flows with a) logical replication slot and get-changes. b) filtered data flows: pub-sub creation with row_filters, 'publish' options. I tried to verify plugin fields as compared to total_wal* fields. c) reset flow. While tests for a and c are present already. I don't see tests for b anywhere when it comes to stats. Do you think we shall add a test for filtered data using row-filter somewhere? > > Applied those suggestions in my repository. Do you have any further > review comments? > No, I think that is all. thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Thu, Sep 18, 2025 at 3:54 PM Ashutosh Bapat wrote: > > > > > > Few comments: > > > > 1) > > postgres=# select slot_name, > > total_bytes,plugin_filtered_bytes,plugin_sent_bytes from > > pg_stat_replication_slots order by slot_name; > > slot_name | total_bytes | plugin_filtered_bytes | plugin_sent_bytes > > -----------+-------------+-----------------------+------------------- > > slot1 | 800636 | 793188 | 211 > > sub1 | 401496 | 132712 | 84041 > > sub2 | 401496 | 396184 | 674 > > sub3 | 401496 | 145912 | 79959 > > (4 rows) > > > > Currently it looks quite confusing. 'total_bytes' gives a sense that > > it has to be a sum of filtered and sent. But they are no way like > > that. In the thread earlier there was a proposal to change the name to > > reordered_txns, reordered_bytes. That looks better to me. It will give > > clarity without even someone digging into docs. > > I also agree with that. But that will break backward compatibility. Yes, that it will do. > Do > you think other columns like spill_* and stream_* should also be > renamed with the prefix "reordered"? > Okay, I see that all fields in pg_stat_replication_slots are related to the ReorderBuffer. On reconsideration, I’m unsure whether it's appropriate to prefix all of them with reorderd_. For example, renaming spill_bytes and stream_bytes to reordered_spill_bytes and reordered_stream_bytes. These names start to feel overly long, and I also noticed that ReorderBuffer isn’t clearly defined anywhere in the documentation (or at least I couldn’t find it), even though the term 'reorder buffer' does appear in a few places. As an example, see ReorderBufferRead, ReorderBufferWrite wait-types at [1]. Also in plugin-doc [2], we use 'ReorderBufferTXN'. And now, we are adding: ReorderBufferChangeSize, ReorderBufferChange This gives me a feeling, will it be better to let pg_stat_replication_slots as is and add a brief ReorderBuffer section under Logical Decoding concepts [3] just before Output Plugins. And then, pg_stat_replication_slots can refer to that section, clarifying that the bytes, counts, and txn fields pertain to ReorderBuffer (without changing any of the fields). And then to define plugin related data, we can have a new view, say pg_stat_plugin_stats (as Amit suggested earlier) or pg_stat_replication_plugins. I understand that adding a new view might not be desirable, but it provides better clarity without requiring changes to the existing fields in pg_stat_replication_slots. I also strongly feel that to properly tie all this information together, a brief definition of the ReorderBuffer is needed. Other pages that reference this term can then point to that section. Thoughts? [1]: https://www.postgresql.org/docs/17/monitoring-stats.html#WAIT-EVENT-IO-TABLE [2]: https://www.postgresql.org/docs/17/logicaldecoding-output-plugin.html [3]: https://www.postgresql.org/docs/17/logicaldecoding-explanation.html#LOGICALDECODING-EXPLANATION thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Tue, Oct 28, 2025 at 12:46 PM Ashutosh Bapat
wrote:
>
> On Mon, Oct 27, 2025 at 4:47 PM shveta malik wrote:
> >
> > Few comments:
> >
> > 1)
> > pgoutput_truncate:
> >
> > if (nrelids > 0)
> > {
> > OutputPluginPrepareWrite(ctx, true);
> > logicalrep_write_truncate(ctx->out,
> > xid,
> > nrelids,
> > relids,
> > change->data.truncate.cascade,
> > change->data.truncate.restart_seqs);
> > OutputPluginWrite(ctx, true);
> > }
> > + else
> > + ctx->stats->filteredBytes += ReorderBufferChangeSize(change);
> > +
> >
> > It seems that filteredBytes are only counted for TRUNCATE when nrelids
> > is 0. Can nrelids only be 0 or same as nrelations?
> >
> > The below code makes me think that nrelids can be any number between 0
> > and nrelations, depending on which relations are publishable and which
> > supports publishing TRUNCATE. If that’s true, shouldn’t we count
> > filteredBytes in each such skipped case?
>
> IIIUC, you are suggesting that we should add
> ReorderBufferChangeSize(change) for every relation which is not part
> of the publication or whose truncate is not published.
No, that will be wrong.
> I think that
> won't be correct since it can lead to a situation where filtered bytes
> > total bytes which should never happen. Even if there is a single
> publishable relation whose truncate is published, the change should
> not be considered as filtered since something would be output
> downstream.
Yes, the entire change should not be treated as filtered. The idea is
that, for example, if there are 20 relations belonging to different
publications and only one of them supports publishing TRUNCATE, then
when a TRUNCATE is triggered on all, the data for that one relation
should be counted as sent (which is currently happening based on
nrelids), while the data for the remaining 19 should be considered
filtered — which is not happening right now.
> Otherwise filtered bytes as well as sent bytes both will
> be incremented causing an inconsistency (which would be hard to notice
> since total bytes - filtered bytes has something to do with the sent
> bytes but the exact correlation is hard to grasp in a formula).
>
> We may increment filteredBytes by sizeof(OID) for every relation we
> skip here OR by ReoderBufferChangeSize(change) if all the relations
> are filtered, but that's too much dependent on how the WAL record is
> encoded; and adding that dependency in an output plugin code seems
> hard to manage.
>
Yes, that was the idea, to increment filteredBytes in this way. But I
see your point. I can’t think of a better solution at the moment. If
you also don’t have any better ideas, then at least adding a comment
in this function would be helpful. Right now, it looks like we
overlooked the fact that some relationships should contribute to
filteredBytes while others should go to sentBytes.
> If you are suggesting something else, maybe sharing actual code
> changes would help.
>
> >
> >
> > 2)
> > + int64 filteredBytes; /* amount of data from reoder buffer that was
> >
> > reoder --> reorder
>
> Done.
>
> >
> > 3)
> > One small nitpick:
> >
> > + /*
> > + * If output plugin has chosen to maintain its stats, update the amount of
> > + * data sent downstream.
> > + */
> > + if (ctx->stats)
> > + ctx->stats->sentBytes += ctx->out->len + sizeof(XLogRecPtr) +
> > sizeof(TransactionId);
> >
> > The way sentBytes is updated here feels a bit unnatural; we’re adding
> > the lengths for values[2], then [0], and then [1]. Would it be cleaner
> > to introduce a len[3] array similar to the existing values[3] and
> > nulls[3] arrays? We could initialize len[i] alongside values[i], and
> > later just sum up all three elements when updating
> > ctx->stats->sentBytes. It would be easier to understand as well.
>
> Instead of an array of length 3, we could keep a counter sentBytes to
> accumulate all lengths. It will be assigned to ctx->stats->sentBytes
> at the end if ctx->stats != NULL. But that might appear as if we are
> performing additions even if it won't be used ultimately. That's not
> true, since this plugin will always maintain stats. Changed that way.
>
Looks good.
Apart from the above discussion, I have no more comments on this patch.
thanks
Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Wed, Aug 27, 2025 at 7:14 PM Bertrand Drouvot
wrote:
>
> Hi,
>
> On Thu, Jul 24, 2025 at 12:24:26PM +0530, Ashutosh Bapat wrote:
> > Here's the next patch which considers all the discussion so far. It
> > adds four fields to pg_stat_replication_slots.
> > - plugin - name of the output plugin
>
> Is this one needed? (we could get it with a join on pg_replication_slots)
>
In my opinion, when there are other plugin_* fields present, including
the plugin name directly here seems like a better approach. So, +1 for
the plugin field.
> > - plugin_filtered_bytes - reports the amount of changes filtered
> > out by the output plugin
> > - plugin_sent_txns - the amount of transactions sent downstream by
> > the output plugin
> > - plugin_sent_bytes - the amount of data sent downstream by the
> > outputplugin.
> >
> > There are some points up for a discussion:
> > 1. pg_stat_reset_replication_slot() zeroes out the statistics entry by
> > calling pgstat_reset() or pgstat_reset_of_kind() which don't know
> > about the contents of the entry. So
> > PgStat_StatReplSlotEntry::plugin_has_stats is set to false and plugin
> > stats are reported as NULL, instead of zero, immediately after reset.
> > This is the same case when the stats is queried immediately after the
> > statistics is initialized and before any stats are reported. We could
> > instead make it report
> > zero, if we save the plugin_has_stats and restore it after reset. But
> > doing that in pgstat_reset_of_kind() seems like an extra overhead + we
> > will need to write a function to find all replication slot entries.
I tried to think of an approach where we can differentiate between the
cases 'not initialized' and 'reset' ones with the values. Say instead
of plugin_has_stats, if we have plugin_stats_status, then we can
maintain status like -1(not initialized), 0(reset). But this too will
complicate the code further. Personally, I’m okay with NULL values
appearing even after a reset, especially since the documentation
explains this clearly.
>
> > 2. There's also a bit of asymmetry in the way sent_bytes is handled.
> > The code which actually sends the logical changes to the downstream is
> > part of the core code
> > but the format of the change and hence the number of bytes sent is
> > decided by the plugin. It's a stat related to plugin but maintained by
> > the core code. The patch implements it as a plugin stat (so the
> > corresponding column has "plugin" prefix
>
> The way it is done makes sense to me.
>
> > 3. The names of new columns have the prefix "plugin_" but the internal
> > variables tracking those don't for the sake of brevity. If you prefer
> > to have the same prefix for the internal variables, I can change that.
>
I am okay either way.
Few comments:
1)
postgres=# select slot_name,
total_bytes,plugin_filtered_bytes,plugin_sent_bytes from
pg_stat_replication_slots order by slot_name;
slot_name | total_bytes | plugin_filtered_bytes | plugin_sent_bytes
-----------+-------------+-----------------------+-------------------
slot1 | 800636 | 793188 | 211
sub1 | 401496 | 132712 | 84041
sub2 | 401496 | 396184 | 674
sub3 | 401496 | 145912 | 79959
(4 rows)
Currently it looks quite confusing. 'total_bytes' gives a sense that
it has to be a sum of filtered and sent. But they are no way like
that. In the thread earlier there was a proposal to change the name to
reordered_txns, reordered_bytes. That looks better to me. It will give
clarity without even someone digging into docs.
2)
Tried to verify all filtered data tests, seems to work well. Also I
tried tracking the usage of OutputPluginWrite() to see if there is any
other place where data needs to be considered as filtered-data.
Encountered this:
send_relation_and_attrs has:
if (!logicalrep_should_publish_column(att, columns,
include_gencols_type))
continue;
if (att->atttypid < FirstGenbkiObjectId)
continue;
But I don't think it needs to be considered as filtered data. This is
mostly schema related info. But I wanted to confirm once. Thoughts?
3)
+-- total_txns may vary based on the background activity but sent_txns should
+-- always be 1 since the background transactions are always skipped. Filtered
+-- bytes would be set only when there's a change that was passed to the plugin
+-- but was filtered out. Depending upon the background transactions, filtered
+-- bytes may or may not be zero.
+SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS
spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS
total_bytes, plugin_sent_txns, plugin_sent_bytes > 0 AS sent_bytes,
plugin_filtered_bytes >= 0 AS filtered_bytes FROM
pg_stat_replication_slots ORDER BY slot_name;
In comment either we can say plugin_sent_txns instead of sent_txns or
in the query we can fetch plugin_sent_txns AS sent_txns, so that we
can relate comment and query.
4)
+ sentTxns is the number of transactions sent downstream
+ by the output plugin. sentBytes is the amount of data
+ sent downstream by the output plugin.
+ OutputPluginWrite is expected to update this counter
+ if ctx->stats is initialized by the output plugin.
+ filteredBytes is the size of changes in bytes that are
+ filtered out by the output plugin. Function
+ ReorderBufferChangeSize may be used to find
the size of
+ filtered ReorderBufferChange.
+
Either we can mention units as 'bytes' for both filteredBytes and
sentBytes or for none. Currently filteredBytes says 'in bytes' while
sentBytes does not.
thanks
Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Mon, Nov 3, 2025 at 12:23 PM Ashutosh Bapat wrote: > > On Thu, Oct 30, 2025 at 9:08 AM shveta malik wrote: > > > > > > > > > + * not filtered out, we don't count modify filteredBytes. The filtered > > > > Something is wrong in this sentence. > > :), here's better one > > /* > * Even if we filtered out some relations, we still send a TRUNCATE > * message for the remaining relations. Since the change, as a whole, is > * not filtered out we don't increment filteredBytes. The filtered > * out relations will be reflected as a smaller sentBytes count. > */ > > > > > Also, regarding "The filtered out relations will be reflected as a > > smaller sentBytes count." > > Can you please point me to the code where it happens? From what I have > > understood, pgoutput_truncate() completely skips the relations which > > do not support publishing truncate. Then it sends 'BEGIN', then > > schema info of non-filtered relations and then TRUNCATE for > > non-filtered relations (based on nrelids). > > Let's take an example. Assume the TRUNCATE WAL record had relids X, Y, > Z and W. Out of those X and Y were filtered out. Then the message sent > to the downstream will have only Z, W, let's say "TRUNCATE Z W" - 12 > bytes (hypothetically). So sentBytes will be incremented by 12. > However, if no relation was filtered, the message would be "TRUNCATE X > Y Z W" ~ 16 bytes and thus sentBytes will be incremented by 16 bytes. > Thus when the relations are filtered from the truncate message, > sentBytes is incremented by a smaller value than those when no > relations are filtered. So, even if filteredBytes is same in both > cases (filtered some relations vs no relation was filtered), sentBytes > indicates the difference. I understand the point, but I didn’t find the message clearly reflecting it. > Similarly for column level filtering. > However, reading this again, it seems adding more confusion than > reducing it. Right. > So I propose to just add comment > > in pgoutput_truncate() > /* > * Even if we filtered out some relations, we still send a TRUNCATE > * message for the remaining relations. Since the change, as a whole, is > * not filtered out we don't increment filteredBytes. > */ > > and in pgoutput_change > /* > * Send the data. Even if we end up filtering some columns while sending the > * message, we won't consider the change, as a whole, to be filtered out. Hence > * won't increment the filteredBytes. > */ > > Does that look good? Yes. Works for me. thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 5:28 PM Ashutosh Bapat wrote: > > On Wed, Sep 24, 2025 at 2:38 PM shveta malik wrote: > > > > On Wed, Sep 24, 2025 at 12:47 PM Ashutosh Bapat > > wrote: > > > > > > On Wed, Sep 24, 2025 at 10:12 AM shveta malik wrote: > > > > > > > > I tested the flows with > > > > a) logical replication slot and get-changes. > > > > b) filtered data flows: pub-sub creation with row_filters, 'publish' > > > > options. I tried to verify plugin fields as compared to total_wal* > > > > fields. > > > > c) reset flow. > > > > > > > > While tests for a and c are present already. I don't see tests for b > > > > anywhere when it comes to stats. Do you think we shall add a test for > > > > filtered data using row-filter somewhere? > > > > > > Added a test in 028_row_filter. Please find it in the attached > > > patchset. > > > > Test looks good. > > Thanks. Added to three more files. I think we have covered all the > cases where filtering can occur. > Yes. The test looks good now. thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Fri, Sep 19, 2025 at 8:11 PM Ashutosh Bapat wrote: > > On Fri, Sep 19, 2025 at 11:48 AM shveta malik wrote: > > > > On Thu, Sep 18, 2025 at 3:54 PM Ashutosh Bapat > > wrote: > > > > > > > > > > > > > > Few comments: > > > > > > > > 1) > > > > postgres=# select slot_name, > > > > total_bytes,plugin_filtered_bytes,plugin_sent_bytes from > > > > pg_stat_replication_slots order by slot_name; > > > > slot_name | total_bytes | plugin_filtered_bytes | plugin_sent_bytes > > > > -----------+-------------+-----------------------+------------------- > > > > slot1 | 800636 | 793188 | 211 > > > > sub1 | 401496 | 132712 | 84041 > > > > sub2 | 401496 | 396184 | 674 > > > > sub3 | 401496 | 145912 | 79959 > > > > (4 rows) > > > > > > > > Currently it looks quite confusing. 'total_bytes' gives a sense that > > > > it has to be a sum of filtered and sent. But they are no way like > > > > that. In the thread earlier there was a proposal to change the name to > > > > reordered_txns, reordered_bytes. That looks better to me. It will give > > > > clarity without even someone digging into docs. > > > > > > I also agree with that. But that will break backward compatibility. > > > > Yes, that it will do. > > > > > Do > > > you think other columns like spill_* and stream_* should also be > > > renamed with the prefix "reordered"? > > > > > > > Okay, I see that all fields in pg_stat_replication_slots are related > > to the ReorderBuffer. On reconsideration, I’m unsure whether it's > > appropriate to prefix all of them with reorderd_. For example, > > renaming spill_bytes and stream_bytes to reordered_spill_bytes and > > reordered_stream_bytes. These names start to feel overly long, and I > > also noticed that ReorderBuffer isn’t clearly defined anywhere in the > > documentation (or at least I couldn’t find it), even though the term > > 'reorder buffer' does appear in a few places. > > > > As an example, see ReorderBufferRead, ReorderBufferWrite wait-types > > at [1]. Also in plugin-doc [2], we use 'ReorderBufferTXN'. And now, we > > are adding: ReorderBufferChangeSize, ReorderBufferChange > > > > This gives me a feeling, will it be better to let > > pg_stat_replication_slots as is and add a brief ReorderBuffer section > > under Logical Decoding concepts [3] just before Output Plugins. And > > then, pg_stat_replication_slots can refer to that section, clarifying > > that the bytes, counts, and txn fields pertain to ReorderBuffer > > (without changing any of the fields). > > > > And then to define plugin related data, we can have a new view, say > > pg_stat_plugin_stats (as Amit suggested earlier) or > > pg_stat_replication_plugins. I understand that adding a new view might > > not be desirable, but it provides better clarity without requiring > > changes to the existing fields in pg_stat_replication_slots. I also > > strongly feel that to properly tie all this information together, a > > brief definition of the ReorderBuffer is needed. Other pages that > > reference this term can then point to that section. Thoughts? > > Even if we keep two views, when they are joined, users will still get > confused by total_* names. So it's not solving the underlying problem. Okay, I see your point. > Andres had raised the point about renaming total_* fields with me > off-list earlier. He suggested names total_wal_bytes, and > total_wal_txns in an off list discussion today. I think those convey > the true meaning - that these are txns and bytes that come from WAL. I agree. > Used those in the attached patches. Prefix reordered would give away > lower level details, so I didn't use it. > > I agree that it would be good to mention ReorderBuffer in the logical > decoding concepts section since it mentions structures ReorderBuffer*. > But that would be a separate patch since we aren't using "reordered" > in the names of the fields. Okay. > 0001 is the previous patch > 0002 changes addressing your and Bertrand's comments. > Few trivial comments: 1) Currently the doc says: sentTxns is the number of transactions sent downstream by the output plugin. sentBytes is the amount of data, in bytes, sent downstream by the output plugin. OutputPluginWrite will update this counter if ctx->stats is initialized by the output plugin. filteredBytes is the size of changes, in bytes, that are filtered out by the output plugin. Function ReorderBufferChangeSize may be used to find the size of filtered ReorderBufferChange. Shall we rearrange it to: sentTxns is the number of transactions sent downstream by the output plugin. sentBytes is the amount of data, in bytes, sent downstream by the output plugin. filteredBytes is the size of changes, in bytes, that are filtered out by the output plugin. OutputPluginWrite will update these counters if ctx->stats is initialized by the output plugin. The function ReorderBufferChangeSize can be used to compute the size of a filtered ReorderBufferChange, i.e., the filteredBytes. 2) My preference will be to rename the fields 'total_txns' and 'total_bytes' in PgStat_StatReplSlotEntry to 'total_wal_txns' and 'total_wal_bytes' for better clarity. Additionally, upon rethinking, it seems better to me that plugin-related fields are also named as plugin_* to clearly indicate their association. OTOH, in OutputPluginStats, the field names are fine as is, since the structure name itself clearly indicates these are plugin-related fields. PgStat_StatReplSlotEntry lacks such context and thus using full descriptive names there would improve clarity. 3) LogicalOutputWrite: + if (ctx->stats) + ctx->stats->sentBytes += ctx->out->len + sizeof(XLogRecPtr) + sizeof(TransactionId); p->returned_rows++; A blank line after the new change will increase readability. ~~ In my testing, the patch works as expected. Thanks! thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Fri, Oct 3, 2025 at 12:22 PM Ashutosh Bapat wrote: > > > Here's patchset addressing two issues: > > Issue 1: A plugin supports stats in version X. It stopped supporting > the stats in version X + 1. It again started supporting stats in > version X + 2. Plugin stats will be accumulated when it was at version > X. When X + 1 is loaded, the stats will continue to report the stats > accumulated (by version X) till the first startup_call for that > replication slot happens. If the user knows (from documentation say) > that X + 1 does not support stats, seeing statistics will mislead > them. We don't know whether there's a practical need to do so. A > plugin which flip-flops on stats is breaking backward compatibility. I > have added a note in documentation for plugin authors, warning them > that this isn't expected. I don't think it's worth adding complexity > in code to support such a case unless we see a practical need for the > same. I agree. The current Note saying 'result may be misleading' looks good to me. thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 12:47 PM Ashutosh Bapat wrote: > > On Wed, Sep 24, 2025 at 10:12 AM shveta malik wrote: > > > > I tested the flows with > > a) logical replication slot and get-changes. > > b) filtered data flows: pub-sub creation with row_filters, 'publish' > > options. I tried to verify plugin fields as compared to total_wal* > > fields. > > c) reset flow. > > > > While tests for a and c are present already. I don't see tests for b > > anywhere when it comes to stats. Do you think we shall add a test for > > filtered data using row-filter somewhere? > > Added a test in 028_row_filter. Please find it in the attached > patchset. Test looks good. > I didn't find tests which test table level filtering or > operation level filtering. Can you please point me to such tests. I > will add similar test to other places. Once you review the test in > 028_row_filter, I will replicate it to other places you point out. > I can see a few tests of operation level filtering present in 'subscription/t/001_rep_changes.pl' and 'subscription/t/010_truncate.pl' > On Wed, Sep 24, 2025 at 12:12 PM Bertrand Drouvot > wrote: > > > > Hi, > > > > On Wed, Sep 24, 2025 at 11:38:30AM +0530, shveta malik wrote: > > > On Wed, Sep 24, 2025 at 11:08 AM Ashutosh Bapat > > > wrote: > > > > > > > > In WalSndWriteData() we can't rely on what happens in a low level API > > > > like socket_putmessage(). And we are counting the number of bytes in > > > > the logically decoded message. So, I actually wonder whether we should > > > > count 1 byte of 'd' in sentBytes. Shveta, Bertand, what do you think? > > > > > > > > > > If we are not counting all such metadata bytes ((or can't reliably do > > > so), then IMO, we shall skip counting msgtype as well. > > > > Agree. Maybe mention in the doc that metadata (including msgtype) bytes are not > > taken into account? > > We are counting the sentBytes in central places through which all the > logically decoded messages flow. So we are not missing on any metadata > bytes. Given that these bytes are part of the logically decoded > message itself, I think we should count them in the sentBytes. Now the > question remains is whether to count 4 bytes for length in the message > itself? The logical decoding code can not control that and thus should > not account for it. So I am leaving bytes counted for > pg_hton32((uint32) (len + 4)) out of sentBytes calculation. > -- Okay. thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Wed, Sep 24, 2025 at 11:08 AM Ashutosh Bapat wrote: > > On Tue, Sep 23, 2025 at 6:28 PM Ashutosh Sharma wrote: > > > > > 0001 is the previous patch > > > 0002 changes addressing your and Bertrand's comments. > > > > > > > @@ -1573,6 +1573,13 @@ WalSndWriteData(LogicalDecodingContext *ctx, > > XLogRecPtr lsn, TransactionId xid, > > /* output previously gathered data in a CopyData packet */ > > pq_putmessage_noblock(PqMsg_CopyData, ctx->out->data, ctx->out->len); > > > > + /* > > + * If output plugin maintains statistics, update the amount of data sent > > + * downstream. > > + */ > > + if (ctx->stats) > > + ctx->stats->sentBytes += ctx->out->len + 1; /* +1 for the 'd' */ > > + > > > > Just a small observation: I think it’s actually pq_flush_if_writable() > > that writes the buffered data to the socket, not pq_putmessage_noblock > > (which is actually gathering data in the buffer and not sending). So > > it might make more sense to increment the sent pointer after the call > > to pq_flush_if_writable(). > > That's a good point. I placed it after pq_putmessage_noblock() so that > it's easy to link the increment to sentBytes and the actual bytes > being sent. You are right that the bytes won't be sent unless > pq_flush_if_writable() is called but it will be called for sure before > the next UpdateDecodingStats(). So the reported bytes are never wrong. > I would prefer readability over seeming accuracy. > > > > > Should we also consider - pg_hton32((uint32) (len + 4)); -- the > > additional 4 bytes of data added to the send buffer. > > > > In WalSndWriteData() we can't rely on what happens in a low level API > like socket_putmessage(). And we are counting the number of bytes in > the logically decoded message. So, I actually wonder whether we should > count 1 byte of 'd' in sentBytes. Shveta, Bertand, what do you think? > If we are not counting all such metadata bytes ((or can't reliably do so), then IMO, we shall skip counting msgtype as well. thanks Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
Few comments:
1)
pgoutput_truncate:
if (nrelids > 0)
{
OutputPluginPrepareWrite(ctx, true);
logicalrep_write_truncate(ctx->out,
xid,
nrelids,
relids,
change->data.truncate.cascade,
change->data.truncate.restart_seqs);
OutputPluginWrite(ctx, true);
}
+ else
+ ctx->stats->filteredBytes += ReorderBufferChangeSize(change);
+
It seems that filteredBytes are only counted for TRUNCATE when nrelids
is 0. Can nrelids only be 0 or same as nrelations?
The below code makes me think that nrelids can be any number between 0
and nrelations, depending on which relations are publishable and which
supports publishing TRUNCATE. If that’s true, shouldn’t we count
filteredBytes in each such skipped case?
if (!is_publishable_relation(relation))
continue;
relentry = get_rel_sync_entry(data, relation);
if (!relentry->pubactions.pubtruncate)
continue;
2)
+ int64 filteredBytes; /* amount of data from reoder buffer that was
reoder --> reorder
3)
One small nitpick:
+ /*
+ * If output plugin has chosen to maintain its stats, update the amount of
+ * data sent downstream.
+ */
+ if (ctx->stats)
+ ctx->stats->sentBytes += ctx->out->len + sizeof(XLogRecPtr) +
sizeof(TransactionId);
The way sentBytes is updated here feels a bit unnatural; we’re adding
the lengths for values[2], then [0], and then [1]. Would it be cleaner
to introduce a len[3] array similar to the existing values[3] and
nulls[3] arrays? We could initialize len[i] alongside values[i], and
later just sum up all three elements when updating
ctx->stats->sentBytes. It would be easier to understand as well.
thanks
Shveta
Re: Report bytes and transactions actually sent downtream
От:
shveta malik <shveta.malik@gmail.com>
Дата:
On Wed, Oct 29, 2025 at 8:25 PM Ashutosh Bapat
wrote:
>
> On Wed, Oct 29, 2025 at 9:14 AM shveta malik wrote:
> >
> > On Tue, Oct 28, 2025 at 12:46 PM Ashutosh Bapat
> > wrote:
> > >
> > > On Mon, Oct 27, 2025 at 4:47 PM shveta malik wrote:
> > > >
> > > > Few comments:
> > > >
> > > > 1)
> > > > pgoutput_truncate:
> > > >
> > > > if (nrelids > 0)
> > > > {
> > > > OutputPluginPrepareWrite(ctx, true);
> > > > logicalrep_write_truncate(ctx->out,
> > > > xid,
> > > > nrelids,
> > > > relids,
> > > > change->data.truncate.cascade,
> > > > change->data.truncate.restart_seqs);
> > > > OutputPluginWrite(ctx, true);
> > > > }
> > > > + else
> > > > + ctx->stats->filteredBytes += ReorderBufferChangeSize(change);
> > > > +
> > > >
> > > > It seems that filteredBytes are only counted for TRUNCATE when nrelids
> > > > is 0. Can nrelids only be 0 or same as nrelations?
> > > >
> > > > The below code makes me think that nrelids can be any number between 0
> > > > and nrelations, depending on which relations are publishable and which
> > > > supports publishing TRUNCATE. If that’s true, shouldn’t we count
> > > > filteredBytes in each such skipped case?
> > >
> > > IIIUC, you are suggesting that we should add
> > > ReorderBufferChangeSize(change) for every relation which is not part
> > > of the publication or whose truncate is not published.
> >
> > No, that will be wrong.
> >
> > > I think that
> > > won't be correct since it can lead to a situation where filtered bytes
> > > > total bytes which should never happen. Even if there is a single
> > > publishable relation whose truncate is published, the change should
> > > not be considered as filtered since something would be output
> > > downstream.
> >
> > Yes, the entire change should not be treated as filtered. The idea is
> > that, for example, if there are 20 relations belonging to different
> > publications and only one of them supports publishing TRUNCATE, then
> > when a TRUNCATE is triggered on all, the data for that one relation
> > should be counted as sent (which is currently happening based on
> > nrelids), while the data for the remaining 19 should be considered
> > filtered — which is not happening right now.
> >
> > > Otherwise filtered bytes as well as sent bytes both will
> > > be incremented causing an inconsistency (which would be hard to notice
> > > since total bytes - filtered bytes has something to do with the sent
> > > bytes but the exact correlation is hard to grasp in a formula).
> > >
> > > We may increment filteredBytes by sizeof(OID) for every relation we
> > > skip here OR by ReoderBufferChangeSize(change) if all the relations
> > > are filtered, but that's too much dependent on how the WAL record is
> > > encoded; and adding that dependency in an output plugin code seems
> > > hard to manage.
> > >
> >
> > Yes, that was the idea, to increment filteredBytes in this way. But I
> > see your point. I can’t think of a better solution at the moment. If
> > you also don’t have any better ideas, then at least adding a comment
> > in this function would be helpful. Right now, it looks like we
> > overlooked the fact that some relationships should contribute to
> > filteredBytes while others should go to sentBytes.
>
> I noticed that we do something similar while filtering columns. I
> think we need to add a comment in that code as well. How about
> something like below?
>
> diff --git a/src/backend/replication/pgoutput/pgoutput.c
> b/src/backend/replication/pgoutput/pgoutput.c
> index 4b35f2de6aa..f2d6e20a702 100644
> --- a/src/backend/replication/pgoutput/pgoutput.c
> +++ b/src/backend/replication/pgoutput/pgoutput.c
> @@ -1621,7 +1621,12 @@ pgoutput_change(LogicalDecodingContext *ctx,
> ReorderBufferTXN *txn,
>
> OutputPluginPrepareWrite(ctx, true);
>
> - /* Send the data */
> + /*
> + * Send the data. Even if we end up filtering some columns
> while sending the
> + * message, we won't consider the change, as a whole, to be
> filtered out.
> + * Instead the filtered columns will be reflected as a smaller sentBytes
> + * count.
> + */
> switch (action)
> {
> case REORDER_BUFFER_CHANGE_INSERT:
> @@ -1728,6 +1733,13 @@ pgoutput_truncate(LogicalDecodingContext *ctx,
> ReorderBufferTXN *txn,
>
> change->data.truncate.cascade,
>
> change->data.truncate.restart_seqs);
> OutputPluginWrite(ctx, true);
> +
> + /*
> + * Even if we filtered out some relations, we still
> send a TRUNCATE
> + * message for the remaining relations. Since the
> change, as a whole, is
> + * not filtered out, we don't count modify
> filteredBytes. The filtered
> + * out relations will be reflected as a smaller sentBytes count.
> + */
> }
> else
> ctx->stats->filteredBytes += ReorderBufferChangeSize(change);
>
> + * not filtered out, we don't count modify filteredBytes. The filtered
Something is wrong in this sentence.
Also, regarding "The filtered out relations will be reflected as a
smaller sentBytes count."
Can you please point me to the code where it happens? From what I have
understood, pgoutput_truncate() completely skips the relations which
do not support publishing truncate. Then it sends 'BEGIN', then
schema info of non-filtered relations and then TRUNCATE for
non-filtered relations (based on nrelids).
thanks
Shveta
Re: Report bytes and transactions actually sent downtream
От:
Andres Freund <andres@anarazel.de>
Дата:
Hi, On 2025-12-11 10:29:42 +0530, Ashutosh Bapat wrote: > Please review. I'd simplify the patch to, initially, to just track the sent bytes. For one, that's by *far* the most useful statistic. But I also have some concerns about the other stats: - To me filteredBytes is a pretty bogus number - the size that the output plugin would have sent and ReorderBufferChangeSize() are only kinda related. It'll be a hard number to interpret, I think. It also seems to not account for filtering that happens based on the origin id. - I don't have fundamental opposition to tracking the number of sent transactions, but I think the implementation is at the wrong place. I think we ought to add explicit support for output plugins to filter transactions. Calling output plugins once for each change in a large transaction, which we already decided to not send out, makes no sense. It's far from free to do all the setup to decode a tuple, if the transaction is filtered, we shouldn't do that. It's also far far from free to restore changes from disk if we are going to throw away the whole transaction. The current way requires each output plugin to maintain its own tracking about whether it decided to not output the transaction, which doesn't seem right to me. I'm also just not sure how useful it is, because most of the time we're going to filter on a per-change basis (e.g. only rels in a publication). But that's only sometimes going to affect the numbers of sent transactions. I'm not convinced as-is it's worth the breakage of all output plugins. Greetings, Andres
Re: Report bytes and transactions actually sent downtream
От:
Andres Freund <andres@anarazel.de>
Дата:
Hi, On 2025-11-03 19:53:30 +0530, Ashutosh Bapat wrote: > This commit adds following fields to pg_stat_replication_slots > - plugin_filtered_bytes is the amount of changes filtered out by the > output plugin > - plugin_sent_txns is the amount of transactions sent downstream by the > output plugin > - plugin_sent_bytes is the amount of data sent downstream by the output > plugin. > > The prefix "plugin_" indicates that these counters are related to and > maintained by the output plugin. An output plugin may choose not to > initialize LogicalDecodingContext::stats, which holds these counters, in > which case the above columns will be reported as NULL. I continue to be uncomfortable with doing all this tracking explicitly in output plugins. This still seems like something core infrastructure should take care of, instead of re-implementing it in different output plugins, with the inevitable behaviour differences that will entail. Greetings, Andres Freund