From 9aae0d2c0417642bf24f66e8fa8ba3497de036ed Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 13 Feb 2026 17:42:53 -0800 Subject: [PATCH 1/2] Fix RawBsonDocument encoding performance regression Add instanceof check in BsonDocumentCodec and BsonArrayCodec to route RawBsonDocument values to RawBsonDocumentCodec, restoring efficient byte-copy encoding. Previous BsonType-based lookup led to sub-optimal performance as it could not distinguish RawBsonDocument from BsonDocument. JAVA-6101 --- .../main/org/bson/codecs/BsonArrayCodec.java | 6 +- .../org/bson/codecs/BsonDocumentCodec.java | 6 +- .../AbstractBsonDocumentBenchmark.java | 2 +- .../benchmark/benchmarks/BenchmarkSuite.java | 3 + .../RawBsonArrayEncodingBenchmark.java | 55 +++++++++++++++++++ .../RawBsonNestedEncodingBenchmark.java | 46 ++++++++++++++++ 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java create mode 100644 driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonNestedEncodingBenchmark.java diff --git a/bson/src/main/org/bson/codecs/BsonArrayCodec.java b/bson/src/main/org/bson/codecs/BsonArrayCodec.java index 6d16bb7d1b0..8e3ac4458a2 100644 --- a/bson/src/main/org/bson/codecs/BsonArrayCodec.java +++ b/bson/src/main/org/bson/codecs/BsonArrayCodec.java @@ -21,6 +21,7 @@ import org.bson.BsonType; import org.bson.BsonValue; import org.bson.BsonWriter; +import org.bson.RawBsonDocument; import org.bson.codecs.configuration.CodecRegistry; import static org.bson.assertions.Assertions.notNull; @@ -36,6 +37,7 @@ public class BsonArrayCodec implements Codec { private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(new BsonValueCodecProvider()); private static final BsonTypeCodecMap DEFAULT_BSON_TYPE_CODEC_MAP = new BsonTypeCodecMap(getBsonTypeClassMap(), DEFAULT_REGISTRY); + private static final RawBsonDocumentCodec RAW_BSON_DOCUMENT_CODEC = new RawBsonDocumentCodec(); private final BsonTypeCodecMap bsonTypeCodecMap; /** @@ -77,7 +79,9 @@ public void encode(final BsonWriter writer, final BsonArray array, final Encoder writer.writeStartArray(); for (BsonValue value : array) { - Codec codec = bsonTypeCodecMap.get(value.getBsonType()); + Codec codec = value instanceof RawBsonDocument + ? RAW_BSON_DOCUMENT_CODEC + : bsonTypeCodecMap.get(value.getBsonType()); encoderContext.encodeWithChildContext(codec, writer, value); } diff --git a/bson/src/main/org/bson/codecs/BsonDocumentCodec.java b/bson/src/main/org/bson/codecs/BsonDocumentCodec.java index 75bd3b7a2b0..8227b81f1b5 100644 --- a/bson/src/main/org/bson/codecs/BsonDocumentCodec.java +++ b/bson/src/main/org/bson/codecs/BsonDocumentCodec.java @@ -22,6 +22,7 @@ import org.bson.BsonType; import org.bson.BsonValue; import org.bson.BsonWriter; +import org.bson.RawBsonDocument; import org.bson.codecs.configuration.CodecRegistry; import org.bson.types.ObjectId; @@ -40,6 +41,7 @@ public class BsonDocumentCodec implements CollectibleCodec { private static final String ID_FIELD_NAME = "_id"; private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(new BsonValueCodecProvider()); private static final BsonTypeCodecMap DEFAULT_BSON_TYPE_CODEC_MAP = new BsonTypeCodecMap(getBsonTypeClassMap(), DEFAULT_REGISTRY); + private static final RawBsonDocumentCodec RAW_BSON_DOCUMENT_CODEC = new RawBsonDocumentCodec(); private final CodecRegistry codecRegistry; private final BsonTypeCodecMap bsonTypeCodecMap; @@ -130,7 +132,9 @@ private boolean skipField(final EncoderContext encoderContext, final String key) @SuppressWarnings({"unchecked", "rawtypes"}) private void writeValue(final BsonWriter writer, final EncoderContext encoderContext, final BsonValue value) { - Codec codec = bsonTypeCodecMap.get(value.getBsonType()); + Codec codec = value instanceof RawBsonDocument + ? RAW_BSON_DOCUMENT_CODEC + : bsonTypeCodecMap.get(value.getBsonType()); encoderContext.encodeWithChildContext(codec, writer, value); } diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/AbstractBsonDocumentBenchmark.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/AbstractBsonDocumentBenchmark.java index 89f932f03cd..78e6e37f7f9 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/AbstractBsonDocumentBenchmark.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/AbstractBsonDocumentBenchmark.java @@ -61,7 +61,7 @@ public int getBytesPerRun() { return fileLength * NUM_INTERNAL_ITERATIONS; } - private byte[] getDocumentAsBuffer(final T document) throws IOException { + protected byte[] getDocumentAsBuffer(final T document) throws IOException { BasicOutputBuffer buffer = new BasicOutputBuffer(); codec.encode(new BsonBinaryWriter(buffer), document, EncoderContext.builder().build()); diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java index 2595568f148..c2a8ed9bafe 100644 --- a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java @@ -71,6 +71,9 @@ private static void runBenchmarks() runBenchmark(new BsonDecodingBenchmark<>("Deep", "extended_bson/deep_bson.json", DOCUMENT_CODEC)); runBenchmark(new BsonDecodingBenchmark<>("Full", "extended_bson/full_bson.json", DOCUMENT_CODEC)); + runBenchmark(new RawBsonNestedEncodingBenchmark("Full RawBsonDocument in BsonDocument BSON Encoding", "extended_bson/full_bson.json")); + runBenchmark(new RawBsonArrayEncodingBenchmark("Full RawBsonDocument Array in BsonDocument BSON Encoding", "extended_bson/full_bson.json", 10)); + runBenchmark(new RunCommandBenchmark<>(DOCUMENT_CODEC)); runBenchmark(new FindOneBenchmark("single_and_multi_document/tweet.json", BenchmarkSuite.DOCUMENT_CLASS)); diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java new file mode 100644 index 00000000000..0768f4f63c6 --- /dev/null +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonArrayEncodingBenchmark.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.benchmark.benchmarks; + +import org.bson.BsonArray;import org.bson.BsonDocument; +import org.bson.RawBsonDocument; +import org.bson.codecs.BsonDocumentCodec; + +import java.io.IOException; + +public class RawBsonArrayEncodingBenchmark extends BsonEncodingBenchmark { + + private final int arraySize; + + public RawBsonArrayEncodingBenchmark(final String name, final String resourcePath, final int arraySize) { + super(name, resourcePath, new BsonDocumentCodec()); + this.arraySize = arraySize; + } + + @Override + public void setUp() throws IOException { + super.setUp(); + RawBsonDocument rawDoc = new RawBsonDocument(document, codec); + + BsonArray array = new BsonArray(); + for (int i = 0; i < arraySize; i++) { + array.add(rawDoc); + } + document = new BsonDocument("results", array); + + // Recalculate documentBytes for accurate throughput reporting + documentBytes = getDocumentAsBuffer(document); + + } + + @Override + public int getBytesPerRun() { + return documentBytes.length * NUM_INTERNAL_ITERATIONS; + } +} \ No newline at end of file diff --git a/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonNestedEncodingBenchmark.java b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonNestedEncodingBenchmark.java new file mode 100644 index 00000000000..3872c5888d9 --- /dev/null +++ b/driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/RawBsonNestedEncodingBenchmark.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.mongodb.benchmark.benchmarks; + +import org.bson.BsonDocument; +import org.bson.RawBsonDocument; +import org.bson.codecs.BsonDocumentCodec; + +import java.io.IOException; + +public class RawBsonNestedEncodingBenchmark extends BsonEncodingBenchmark { + + public RawBsonNestedEncodingBenchmark(final String name, final String resourcePath) { + super(name, resourcePath, new BsonDocumentCodec()); + } + + @Override + public void setUp() throws IOException { + super.setUp(); + + RawBsonDocument rawDoc = new RawBsonDocument(document, codec); + document = new BsonDocument("nested", rawDoc); + + documentBytes = getDocumentAsBuffer(document); + } + + @Override + public int getBytesPerRun() { + return documentBytes.length * NUM_INTERNAL_ITERATIONS; + } +} \ No newline at end of file From 8eccce44f8eaeff67041b792f9cf1d58fccbc5b0 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Wed, 18 Feb 2026 22:46:38 -0800 Subject: [PATCH 2/2] Move RawBsonDocument to encode. --- bson/src/main/org/bson/codecs/BsonArrayCodec.java | 6 +----- bson/src/main/org/bson/codecs/BsonDocumentCodec.java | 8 +++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/bson/src/main/org/bson/codecs/BsonArrayCodec.java b/bson/src/main/org/bson/codecs/BsonArrayCodec.java index 8e3ac4458a2..6d16bb7d1b0 100644 --- a/bson/src/main/org/bson/codecs/BsonArrayCodec.java +++ b/bson/src/main/org/bson/codecs/BsonArrayCodec.java @@ -21,7 +21,6 @@ import org.bson.BsonType; import org.bson.BsonValue; import org.bson.BsonWriter; -import org.bson.RawBsonDocument; import org.bson.codecs.configuration.CodecRegistry; import static org.bson.assertions.Assertions.notNull; @@ -37,7 +36,6 @@ public class BsonArrayCodec implements Codec { private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(new BsonValueCodecProvider()); private static final BsonTypeCodecMap DEFAULT_BSON_TYPE_CODEC_MAP = new BsonTypeCodecMap(getBsonTypeClassMap(), DEFAULT_REGISTRY); - private static final RawBsonDocumentCodec RAW_BSON_DOCUMENT_CODEC = new RawBsonDocumentCodec(); private final BsonTypeCodecMap bsonTypeCodecMap; /** @@ -79,9 +77,7 @@ public void encode(final BsonWriter writer, final BsonArray array, final Encoder writer.writeStartArray(); for (BsonValue value : array) { - Codec codec = value instanceof RawBsonDocument - ? RAW_BSON_DOCUMENT_CODEC - : bsonTypeCodecMap.get(value.getBsonType()); + Codec codec = bsonTypeCodecMap.get(value.getBsonType()); encoderContext.encodeWithChildContext(codec, writer, value); } diff --git a/bson/src/main/org/bson/codecs/BsonDocumentCodec.java b/bson/src/main/org/bson/codecs/BsonDocumentCodec.java index 8227b81f1b5..172b0c94338 100644 --- a/bson/src/main/org/bson/codecs/BsonDocumentCodec.java +++ b/bson/src/main/org/bson/codecs/BsonDocumentCodec.java @@ -103,6 +103,10 @@ protected BsonValue readValue(final BsonReader reader, final DecoderContext deco @Override public void encode(final BsonWriter writer, final BsonDocument value, final EncoderContext encoderContext) { + if (value instanceof RawBsonDocument) { + RAW_BSON_DOCUMENT_CODEC.encode(writer, (RawBsonDocument) value, encoderContext); + return; + } writer.writeStartDocument(); beforeFields(writer, encoderContext, value); @@ -132,9 +136,7 @@ private boolean skipField(final EncoderContext encoderContext, final String key) @SuppressWarnings({"unchecked", "rawtypes"}) private void writeValue(final BsonWriter writer, final EncoderContext encoderContext, final BsonValue value) { - Codec codec = value instanceof RawBsonDocument - ? RAW_BSON_DOCUMENT_CODEC - : bsonTypeCodecMap.get(value.getBsonType()); + Codec codec = bsonTypeCodecMap.get(value.getBsonType()); encoderContext.encodeWithChildContext(codec, writer, value); }