Во-первых, как прокомментировал @ xtreme-biker, производительность сильно зависит от вашего оборудования. В частности, мой первый совет будет проверять, работаете ли вы на виртуальной машине или на собственном хосте. В моем случае с виртуальной машиной CentOS на i7 с SDD-диском я могу читать 123 000 документов в секунду, но точно такой же код, работающий на хосте Windows на том же диске, читает до 387 000 документов в секунду.
Далее, давайте предположим, что вам действительно нужно прочитать полную коллекцию. Это означает, что вы должны выполнить полное сканирование. И давайте предположим, что вы не можете изменить конфигурацию сервера MongoDB, а только оптимизировать свой код.
Тогда все сводится к тому, что
collection.find().forEach((Block<Document>) document -> count.increment());
на самом деле.
Быстрое развертывание MongoCollection.find () показывает, что он действительно делает это:
ReadPreference readPref = ReadPreference.primary();
ReadConcern concern = ReadConcern.DEFAULT;
MongoNamespace ns = new MongoNamespace(databaseName,collectionName);
Decoder<Document> codec = new DocumentCodec();
FindOperation<Document> fop = new FindOperation<Document>(ns,codec);
ReadWriteBinding readBinding = new ClusterBinding(getCluster(), readPref, concern);
QueryBatchCursor<Document> cursor = (QueryBatchCursor<Document>) fop.execute(readBinding);
AtomicInteger count = new AtomicInteger(0);
try (MongoBatchCursorAdapter<Document> cursorAdapter = new MongoBatchCursorAdapter<Document>(cursor)) {
while (cursorAdapter.hasNext()) {
Document doc = cursorAdapter.next();
count.incrementAndGet();
}
}
Здесь FindOperation.execute()
довольно быстрый (до 10 мс), и большую часть времени проводит внутри цикла while и, в частности, внутри частного метода QueryBatchCursor.getMore()
getMore()
вызывает DefaultServerConnection.command()
и его время расходуется в основном на две операции: 1) выборка строковых данных с сервера и 2) преобразование строковых данных в BsonDocument .
Оказывается, что Mongo довольно умен в отношении того, сколько сетевых обходов он сделает, чтобы получить большой набор результатов. Сначала он извлекает 100 результатов с помощью команды firstBatch, а затем выбирает большие партии, а nextBatch - это размер пакета, в зависимости от размера коллекции, до предела.
Итак, под лесом что-то вроде этого произойдет, чтобы получить первую партию.
ReadPreference readPref = ReadPreference.primary();
ReadConcern concern = ReadConcern.DEFAULT;
MongoNamespace ns = new MongoNamespace(databaseName,collectionName);
FieldNameValidator noOpValidator = new NoOpFieldNameValidator();
DocumentCodec payloadDecoder = new DocumentCodec();
Constructor<CodecProvider> providerConstructor = (Constructor<CodecProvider>) Class.forName("com.mongodb.operation.CommandResultCodecProvider").getDeclaredConstructor(Decoder.class, List.class);
providerConstructor.setAccessible(true);
CodecProvider firstBatchProvider = providerConstructor.newInstance(payloadDecoder, Collections.singletonList("firstBatch"));
CodecProvider nextBatchProvider = providerConstructor.newInstance(payloadDecoder, Collections.singletonList("nextBatch"));
Codec<BsonDocument> firstBatchCodec = fromProviders(Collections.singletonList(firstBatchProvider)).get(BsonDocument.class);
Codec<BsonDocument> nextBatchCodec = fromProviders(Collections.singletonList(nextBatchProvider)).get(BsonDocument.class);
ReadWriteBinding readBinding = new ClusterBinding(getCluster(), readPref, concern);
BsonDocument find = new BsonDocument("find", new BsonString(collectionName));
Connection conn = readBinding.getReadConnectionSource().getConnection();
BsonDocument results = conn.command(databaseName,find,noOpValidator,readPref,firstBatchCodec,readBinding.getReadConnectionSource().getSessionContext(), true, null, null);
BsonDocument cursor = results.getDocument("cursor");
long cursorId = cursor.getInt64("id").longValue();
BsonArray firstBatch = cursor.getArray("firstBatch");
Затем cursorId
используется для извлечения каждой следующей партии.
По моему мнению, «проблема» с реализацией драйвера заключается в том, что декодер String to JSON внедряется, а JsonReader, на который опирается метод decode (), - нет. Так происходит даже до com.mongodb.internal.connection.InternalStreamConnection
, где вы уже находитесь рядом с сокетом.
Поэтому я думаю, что вряд ли можно что-то сделать, чтобы улучшить MongoCollection.find()
, если вы не зайдете так глубоко, как InternalStreamConnection.sendAndReceiveAsync()
Вы не можете уменьшить количество циклов и не можете изменить способ преобразования ответа в BsonDocument. Не без обхода драйвера и написания своего собственного клиента, что, я сомневаюсь, является хорошей идеей.
PD Если вы хотите попробовать часть приведенного выше кода, вам потребуется метод getCluster (), который требует грязного взлома mongo-java-driver .
private Cluster getCluster() {
Field cluster, delegate;
Cluster mongoCluster = null;
try {
delegate = mongoClient.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
Object clientDelegate = delegate.get(mongoClient);
cluster = clientDelegate.getClass().getDeclaredField("cluster");
cluster.setAccessible(true);
mongoCluster = (Cluster) cluster.get(clientDelegate);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
System.err.println(e.getClass().getName()+" "+e.getMessage());
}
return mongoCluster;
}