前回はデータの書き込みのパフォーマンス測定を実施しました。今回はデータの読み込みのパフォーマンス測定を行いたいと思います。

Introduction

結果はともかく、測定の課程は散々でした。
InsertOne してもメモリが解放されないのもあれですが。

環境は前回と同じです。

Explanation

Read Small Objects

まず、下記のオブジェクトを1,000,000個書き込みます。
データベースは、SmallObjectData、コレクション名は、SmallObjectData です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public sealed class SmallObjectData
{

public ObjectId Id
{
get;
set;
}

public int No
{
set;
get;
}

public DateTime Established
{
get;
set;
}

}

No プロパティには 0 から 999,999 が入ります。
そのあと、No プロパティを 100000 で剰余した余りが0のものだけを Select します。
下記のようなコードになります。

1
2
var collection = database.GetCollection<SmallObjectData>(CollectionName);
var list = collection.FindSync(data => data.No % 100000 == 0).ToList();

ToList メソッドをコールしているのは、この時点で検索結果を確定して、オブジェクトを取得するためです。
IEnumerable の場合、実際に使用するまでは、評価はされないので。
試行回数は3回で、平均値をとっています。
が、どうも最初だけ遅くて、あとはキャッシュが効いている感じがします。

なので、10回の試行で平均値をとります。
平均は420.8 ms でした。

Read Large Objects

まず、下記のオブジェクトを1,000,000個書き込みます。
データベースは、LargeObjectData、コレクション名は、LargeObjectData です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public sealed class LargeObjectData
{

public ObjectId Id
{
get;
set;
}

public int No
{
set;
get;
}

public byte[] DataBytes
{
set;
get;
}

public DateTime Established
{
get;
set;
}

}

No プロパティには 0 から 999,999 が入ります。
そのあと、No プロパティを 100000 で剰余した余りが0のものだけを Select します。
Select コードは前段と同じです。

が、結論から言うと、計測できませんでした。


剰余する数を増やそうが減らそうがとにかく、mongod.exeOutOfMemoryException でこけます。
Selectする段階で、途中結果を解放していないのかしりませんが、メモリが急激に増えていって、こけます。

例外は、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ハンドルされていない例外: System.FormatException: An error occurred while deserializing the DataBytes property of class MongoDB7.Program+MiddleObjectData: 種類 'System.OutOfMemoryException' の例外がスローされました。 ---> System.OutOfMemoryException: 種類 'System.OutOfMemoryException' の例外がスローされました。
場所 MongoDB.Bson.IO.BsonStreamExtensions.ReadBytes(BsonStream stream, Int32 count)
場所 MongoDB.Bson.IO.BsonBinaryReader.ReadBytes()
場所 MongoDB.Bson.Serialization.Serializers.ByteArraySerializer.DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
場所 MongoDB.Bson.Serialization.Serializers.SealedClassSerializerBase`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
場所 MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
場所 MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize(IBsonSerializer serializer, BsonDeserializationContext context)
場所 MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap)
--- 内部例外スタック トレースの終わり ---
場所 MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap)
場所 MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeClass(BsonDeserializationContext context)
場所 MongoDB.Bson.Serialization.BsonClassMapSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
場所 MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
場所 MongoDB.Driver.Core.Operations.CursorBatchDeserializationHelper.DeserializeBatch[TDocument](RawBsonArray batch, IBsonSerializer`1 documentSerializer, MessageEncoderSettings messageEncoderSettings)
場所 MongoDB.Driver.Core.Operations.AsyncCursor`1.CreateCursorBatch(BsonDocument result)
場所 MongoDB.Driver.Core.Operations.AsyncCursor`1.ExecuteGetMoreCommand(IChannelHandle channel, CancellationToken cancellationToken)
場所 MongoDB.Driver.Core.Operations.AsyncCursor`1.GetNextBatch(CancellationToken cancellationToken)
場所 MongoDB.Driver.Core.Operations.AsyncCursor`1.MoveNext(CancellationToken cancellationToken)
場所 MongoDB.Driver.IAsyncCursorExtensions.ToList[TDocument](IAsyncCursor`1 source, CancellationToken cancellationToken)
場所 MongoDB.Driver.IAsyncCursorSourceExtensions.ToList[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)

とでます。
ReadBytes とあるので、mongod.exe からの結果の受け渡しか何かのためのバッファを作成するために、マネージドにメモリを作った際、LOHがたくさんあって、メモリが確保できずに例外発生、というように見えます。

Read Middle Objects

とにかくオブジェクトサイズを減らしてみます。
GC が苦手とする 85,000 が問題としか思えませんので、オブジェクト全体が 85,000 バイトを超えないようにします。
前段のオブジェクト同じですが、DataBytes の長さを 80,000 にします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public sealed class MiddleObjectData
{

public ObjectId Id
{
get;
set;
}

public int No
{
set;
get;
}

public byte[] DataBytes
{
set;
get;
}

public DateTime Established
{
get;
set;
}

}

これを1,000,000 個追加すると、下記の結果になりました。

1
2
3
4
Data Size 80.1 gb
Storage Size 4.12 gb
Avg Obj Size # 80.1 kb
Objects # 1000000

これで一つのオブジェクトの復元で 85,000 バイトを超えないので、 LOH にならず、GC の影響を避けることができます。
これでこれまでと同じようにselectしてみます。

今度は成功しました。が、べらぼうに遅いです。
でも、オブジェクトサイズによって、何かしら致命的な影響があるように見えます。

Conclusion

3種のオブジェクトを検索した結果は下記になります。

オブジェクトサイズ/オブジェクト数 1,000,000
Small ( 51.0 bytes) 420.8 ms
Middle (80.1 kb) 221577.1 ms
Large (100 kb) 計測不可

.NETのGCの影響をもろに受けているとしか思えない感じです。
次回はインデックスを使って、検索速度の改善を試みたいと思います。

Source Code

テストに使った最終的なコードは下記になります。
https://github.com/takuya-takeuchi/Demo/tree/master/Database/MongoDB/MongoDB7