[ 
https://issues.apache.org/jira/browse/SOLR-18415?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18112646#comment-18112646
 ] 

Andrzej Bialecki edited comment on SOLR-18415 at 9/8/26 10:24 AM:
------------------------------------------------------------------

After further analysis this looks somewhat better than I thought, especially 
for updates that are less than 8192 byte in size (serialized), because for 
these the array is resized to the actual size of the payload (as accumulated in 
the {{{}FastOutputStream{}}}'s buffer that the {{JavaBinCodec}} uses). In this 
case no extra padding bytes are wasted.

Since the producer side ({{{}MirroringUpdateProcessor / KafkaMirroringSink{}}}) 
doesn't accumulate individual requests in batches and in a typical scenario 
most documents are smaller than 8kB serialized, this optimization will mostly 
hold.

This is not the case when sending larger documents or when sending 
MirroredConfigSetRequest-s that have multiple content streams. Here's the 
breakdown:
{noformat}
actualSize=61           byteArraySize=64        waste=3         (4.7% of 
transmitted bytes)
actualSize=153          byteArraySize=153       waste=0         (0.0% of 
transmitted bytes)
actualSize=604          byteArraySize=604       waste=0         (0.0% of 
transmitted bytes)
actualSize=4104         byteArraySize=4104      waste=0         (0.0% of 
transmitted bytes)
actualSize=6104         byteArraySize=6104      waste=0         (0.0% of 
transmitted bytes)
actualSize=8104         byteArraySize=8104      waste=0         (0.0% of 
transmitted bytes)
actualSize=8304         byteArraySize=16464     waste=8160      (49.6% of 
transmitted bytes)
actualSize=10104        byteArraySize=20064     waste=9960      (49.6% of 
transmitted bytes)
actualSize=15104        byteArraySize=30064     waste=14960     (49.8% of 
transmitted bytes)
actualSize=20105        byteArraySize=40066     waste=19961     (49.8% of 
transmitted bytes)
actualSize=30105        byteArraySize=32768     waste=2663      (8.1% of 
transmitted bytes)
actualSize=50105        byteArraySize=65536     waste=15431     (23.5% of 
transmitted bytes)
actualSize=100105       byteArraySize=131072    waste=30967     (23.6% of 
transmitted bytes)
{noformat}
The {{actualSize}} represents the size of serialized data, {{byteArraySize}} is 
the size of the returned byte array that contains the serialized data. Up until 
the boundary of 8192 bytes the optimization works very well, but above this 
threshold it produces a lot of waste.

I propose to add a conditional in {{ExposedByteArrayOutputStream.byteArray()}} 
to return the raw buffer only if it's at most 10% larger than the size of 
serialized data. This will cover all small-doc cases and avoid an array copy 
for some less egregious cases for larger docs.

Here's the breakdown with the proposed optimization:
{noformat}
actualSize=61  returnedSize=64  (allocatedBufferSize=64)        waste=3 (4.7% 
of transmitted bytes)
actualSize=153  returnedSize=153        (allocatedBufferSize=153)       waste=0 
(0.0% of transmitted bytes)
actualSize=462  returnedSize=462        (allocatedBufferSize=462)       waste=0 
(0.0% of transmitted bytes)
actualSize=1205  returnedSize=1205      (allocatedBufferSize=1205)      waste=0 
(0.0% of transmitted bytes)
actualSize=1544  returnedSize=1544      (allocatedBufferSize=1544)      waste=0 
(0.0% of transmitted bytes)
actualSize=1771  returnedSize=1771      (allocatedBufferSize=1771)      waste=0 
(0.0% of transmitted bytes)
actualSize=2007  returnedSize=2007      (allocatedBufferSize=2007)      waste=0 
(0.0% of transmitted bytes)
actualSize=2264  returnedSize=2264      (allocatedBufferSize=2264)      waste=0 
(0.0% of transmitted bytes)
actualSize=2855 returnedSize=2855       (allocatedBufferSize=2855)      waste=0 
(0.0% of transmitted bytes)
actualSize=3921 returnedSize=3921       (allocatedBufferSize=3921)      waste=0 
(0.0% of transmitted bytes)
actualSize=4865 returnedSize=4865       (allocatedBufferSize=4865)      waste=0 
(0.0% of transmitted bytes)
actualSize=5835 returnedSize=5835       (allocatedBufferSize=5835)      waste=0 
(0.0% of transmitted bytes)
actualSize=6192 returnedSize=6192       (allocatedBufferSize=6192)      waste=0 
(0.0% of transmitted bytes)
actualSize=6346 returnedSize=6346       (allocatedBufferSize=6346)      waste=0 
(0.0% of transmitted bytes)
actualSize=7330 returnedSize=7330       (allocatedBufferSize=7330)      waste=0 
(0.0% of transmitted bytes)
actualSize=7931 returnedSize=7931       (allocatedBufferSize=7931)      waste=0 
(0.0% of transmitted bytes)
actualSize=8296 returnedSize=8296       (allocatedBufferSize=16384)     waste=0 
(0.0% of transmitted bytes)
actualSize=13570        returnedSize=13570      (allocatedBufferSize=26996)     
waste=0 (0.0% of transmitted bytes)
actualSize=17776        returnedSize=17776      (allocatedBufferSize=35408)     
waste=0 (0.0% of transmitted bytes)
actualSize=26031        returnedSize=26031      (allocatedBufferSize=32768)     
waste=0 (0.0% of transmitted bytes)
actualSize=29673        returnedSize=32768      (allocatedBufferSize=32768)     
waste=3095      (9.4% of transmitted bytes)
actualSize=33733        returnedSize=33733      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=35836        returnedSize=35836      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=37350        returnedSize=37350      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=38449        returnedSize=38449      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=46211        returnedSize=46211      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=54614        returnedSize=54614      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=60514        returnedSize=65536      (allocatedBufferSize=65536)     
waste=5022      (7.7% of transmitted bytes)
actualSize=69125        returnedSize=69125      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=74878        returnedSize=74878      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=76876        returnedSize=76876      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=79056        returnedSize=79056      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=80375        returnedSize=80375      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=83160        returnedSize=83160      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=89323        returnedSize=89323      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=92109        returnedSize=92109      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=99649        returnedSize=99649      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
{noformat}


was (Author: ab):
After further analysis this looks somewhat better than I thought, especially 
for updates that are less than 8192 byte in size (serialized), because for 
these the array is resized to the actual size of the payload (as accumulated in 
the {{FastOutputStream}}'s buffer that the {{JavaBinCodec}} uses). In this case 
no extra padding bytes are wasted.

Since the producer side ({{MirroringUpdateProcessor / KafkaMirroringSink}}) 
doesn't accumulate individual requests in batches and in a typical scenario 
most documents are smaller than 8kB serialized, this optimization will mostly 
hold.

This is not the case when sending larger documents or when sending 
MirroredConfigSetRequest-s that have multiple content streams. Here's the 
breakdown:

{noformat}
actualSize=61           byteArraySize=64        waste=3         (4.7% of 
transmitted bytes)
actualSize=153          byteArraySize=153       waste=0         (0.0% of 
transmitted bytes)
actualSize=604          byteArraySize=604       waste=0         (0.0% of 
transmitted bytes)
actualSize=4104         byteArraySize=4104      waste=0         (0.0% of 
transmitted bytes)
actualSize=6104         byteArraySize=6104      waste=0         (0.0% of 
transmitted bytes)
actualSize=8104         byteArraySize=8104      waste=0         (0.0% of 
transmitted bytes)
actualSize=8304         byteArraySize=16464     waste=8160      (49.6% of 
transmitted bytes)
actualSize=10104        byteArraySize=20064     waste=9960      (49.6% of 
transmitted bytes)
actualSize=15104        byteArraySize=30064     waste=14960     (49.8% of 
transmitted bytes)
actualSize=20105        byteArraySize=40066     waste=19961     (49.8% of 
transmitted bytes)
actualSize=30105        byteArraySize=32768     waste=2663      (8.1% of 
transmitted bytes)
actualSize=50105        byteArraySize=65536     waste=15431     (23.5% of 
transmitted bytes)
actualSize=100105       byteArraySize=131072    waste=30967     (23.6% of 
transmitted bytes)
{noformat}

The {{actualSize}} represents the size of serialized data, {{byteArraySize}} is 
the size of the returned byte array that contains the serialized data. Up until 
the boundary of 8192 bytes the optimization works very well, but above this 
threshold it produces a lot of waste.

I propose to add a conditional in {{ExposedByteArrayOutputStream.byteArray()}} 
to return the raw buffer only if it's at most 10% larger than the size of 
serialized data. This will cover all small-doc cases and avoid an array copy 
for some less egregious cases for larger docs.

Here's the breakdown with the proposed optimization:
{noformat}
actualSize=61   returnedSize=64 (allocatedBufferSize=64)        waste=3 (4.7% 
of transmitted bytes)
actualSize=153  returnedSize=153        (allocatedBufferSize=153)       waste=0 
(0.0% of transmitted bytes)
actualSize=462  returnedSize=462        (allocatedBufferSize=462)       waste=0 
(0.0% of transmitted bytes)
actualSize=1205 returnedSize=1205       (allocatedBufferSize=1205)      waste=0 
(0.0% of transmitted bytes)
actualSize=1544 returnedSize=1544       (allocatedBufferSize=1544)      waste=0 
(0.0% of transmitted bytes)
actualSize=1771 returnedSize=1771       (allocatedBufferSize=1771)      waste=0 
(0.0% of transmitted bytes)
actualSize=2007 returnedSize=2007       (allocatedBufferSize=2007)      waste=0 
(0.0% of transmitted bytes)
actualSize=2264 returnedSize=2264       (allocatedBufferSize=2264)      waste=0 
(0.0% of transmitted bytes)
actualSize=2855 returnedSize=2855       (allocatedBufferSize=2855)      waste=0 
(0.0% of transmitted bytes)
actualSize=3921 returnedSize=3921       (allocatedBufferSize=3921)      waste=0 
(0.0% of transmitted bytes)
actualSize=4865 returnedSize=4865       (allocatedBufferSize=4865)      waste=0 
(0.0% of transmitted bytes)
actualSize=5835 returnedSize=5835       (allocatedBufferSize=5835)      waste=0 
(0.0% of transmitted bytes)
actualSize=6192 returnedSize=6192       (allocatedBufferSize=6192)      waste=0 
(0.0% of transmitted bytes)
actualSize=6346 returnedSize=6346       (allocatedBufferSize=6346)      waste=0 
(0.0% of transmitted bytes)
actualSize=7330 returnedSize=7330       (allocatedBufferSize=7330)      waste=0 
(0.0% of transmitted bytes)
actualSize=7931 returnedSize=7931       (allocatedBufferSize=7931)      waste=0 
(0.0% of transmitted bytes)
actualSize=8296 returnedSize=8296       (allocatedBufferSize=16384)     waste=0 
(0.0% of transmitted bytes)
actualSize=13570        returnedSize=13570      (allocatedBufferSize=26996)     
waste=0 (0.0% of transmitted bytes)
actualSize=17776        returnedSize=17776      (allocatedBufferSize=35408)     
waste=0 (0.0% of transmitted bytes)
actualSize=26031        returnedSize=26031      (allocatedBufferSize=32768)     
waste=0 (0.0% of transmitted bytes)
actualSize=29673        returnedSize=32768      (allocatedBufferSize=32768)     
waste=3095      (9.4% of transmitted bytes)
actualSize=33733        returnedSize=33733      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=35836        returnedSize=35836      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=37350        returnedSize=37350      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=38449        returnedSize=38449      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=46211        returnedSize=46211      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=54614        returnedSize=54614      (allocatedBufferSize=65536)     
waste=0 (0.0% of transmitted bytes)
actualSize=60514        returnedSize=65536      (allocatedBufferSize=65536)     
waste=5022      (7.7% of transmitted bytes)
actualSize=69125        returnedSize=69125      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=74878        returnedSize=74878      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=76876        returnedSize=76876      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=79056        returnedSize=79056      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=80375        returnedSize=80375      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=83160        returnedSize=83160      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=89323        returnedSize=89323      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=92109        returnedSize=92109      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
actualSize=99649        returnedSize=99649      (allocatedBufferSize=131072)    
waste=0 (0.0% of transmitted bytes)
{noformat}


> CrossDC Common: ExposedByteArrayOutputStream optimizes wrong aspect
> -------------------------------------------------------------------
>
>                 Key: SOLR-18415
>                 URL: https://issues.apache.org/jira/browse/SOLR-18415
>             Project: Solr
>          Issue Type: Bug
>          Components: module - crossDC
>    Affects Versions: 10.0, 9.10.1
>            Reporter: Andrzej Bialecki
>            Assignee: Andrzej Bialecki
>            Priority: Major
>             Fix For: main(11.0), 10.1
>
>
> {{MirroredSolrRequestSerializer}} uses a subclass of 
> {{ByteArrayOutputStream}} in an attempt to optimize data transfer by avoiding 
> array copy - instead it passes the whole buffer directly as the output bytes 
> from {{{}serialize(){}}}.
> This sounds like a plausible optimization. However, the initial size of the 
> buffer (32 bytes) is doubled on each overflow as the codec writes to the 
> stream, which means that on average the actual serialized data occupies 
> around half of the buffer, the rest are zeroes. Deserialization will handle 
> these trailing zeros just fine, it's the cost of transmitting roughly twice 
> as much data over the wire that is questionable - this buffer is then passed 
> as-is to the output stream and eventually ends up as network traffic.
> This "optimization" avoids one array copy cost (which reduces GC) but 
> generates multiple costs on the way - not only the empty data has to be sent 
> over the network but also it needs to be stored in source Kafka, in 
> MirrorMaker and in target Kafka, to be finally retrieved by the Consumer 
> (which has to allocate a buffer sufficiently large to fit also the zeroes), 
> only to discard the zeroes during deserialization.
> I propose to get rid of this optimization and use the plain 
> {{ByteArrayOutputStream}} instead.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to