MongoRepositoryで大量のデータを挿入する際に発生する問題#2
過去の話題を思い出すと...
以前5万個のデータを処理した場合の問題は,クライアントがsave APIを要求した回数である.
つまり,5万個であれば5万個のAPI要求が必要であり,クライアントとサーバ間の通信から見ると非常に非効率である.
最終的に,データを1つのリストとして,5万個のデータを1回のAPIとして処理することで,問題を解決した.
詳細は整理ホットスポット#1の記事で確認してください.😊
問題は解決しなかった。
実際、5万個のデータを処理するとき、これは大量のデータだと言いにくいです.ほほほ
お客様の要求に応じて、挿入時に受け入れられるデータはますます大きくなり、最終的には300万個に達し、処理中にどのような問題が発生したのか、なぜ300万個しか見えないのかを教えてくれます.
この文章は20万の問題を処理することを基礎として書いたのです.
確認してください!
文章を読む前に!挿入されたデータサイズは330 Byteです.
私はMacBookPro 19-(6コア、16 GBメモリ)デスクトップで実行しています.👌
20万個を挿入するのに4分以上かかります
挿入されたデータが5万個から20万個に増えるにつれて、問題が発生しました!
以前の文章のコードを読んだら、
前回のコードをもう一度見ます.
public void insertAll(MetaDataCreateAllRequestDto metaDataCreateAllRequestDto){
...
for(Document body : bodyList){
MetaData metaData = new MetaData().builder()
.projectId(projectId)
.body(body).build();
metaDataRepository.save(metaData);
}
...
}
重点コードの形式で概説します.まず,上記のコードを用いて挿入するには246914ミリ秒,すなわち4分以上かかると結論した.
原因を解析するためにsave法の内部論理を調べた.
一緒に確認してから行きましょう.😭
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
if (entityInformation.isNew(entity)) {
return mongoOperations.insert(entity, entityInformation.getCollectionName());
}
return mongoOperations.save(entity, entityInformation.getCollectionName());
}
簡単に説明します.まず,saveで使用するエンティティが新しい値であるか否かによって,大きく2つの論理に分けることができる.
isNew()関数entityの@Idのフィールド値が存在するかどうかをチェック!nullまたは0の場合、isNew()はtrueを返し、insert関数を呼び出します.
逆に、値が存在する場合はfalse呼び出しsave関数を返すことを確認します.
挿入されたデータはどうせ@Idのフィールド値nullなのでinsertを追跡しています.
Insert内部ロジックはdoInsert()にも接続されています.
doInsert()のコードを確認しましょう.
protected <T> T doInsert(String collectionName, T objectToSave, MongoWriter<T> writer) {
BeforeConvertEvent<T> event = new BeforeConvertEvent<>(objectToSave, collectionName);
T toConvert = maybeEmitEvent(event).getSource();
toConvert = maybeCallBeforeConvert(toConvert, collectionName);
AdaptibleEntity<T> entity = operations.forEntity(toConvert, mongoConverter.getConversionService());
entity.assertUpdateableIdIfNotSet();
T initialized = entity.initializeVersionProperty();
Document dbDoc = entity.toMappedDocument(writer).getDocument();
maybeEmitEvent(new BeforeSaveEvent<>(initialized, dbDoc, collectionName));
initialized = maybeCallBeforeSave(initialized, dbDoc, collectionName);
Object id = insertDocument(collectionName, dbDoc, initialized.getClass());
T saved = populateIdIfNecessary(initialized, id);
maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
return maybeCallAfterSave(saved, dbDoc, collectionName);
}
doInsert()で、MongoDBに入力したエンティティを初期化し、insertDocument()メソッドで挿入を続行します.
🤨 つまり、挿入するたびにDBにアクセスして文書が挿入されていることを確認する…
あ、気になる方のためにsaveはinsertではなくentityのバージョンを確認して更新します^^
saveAll()のBatch Insertを使用!
これらのネットワークオーバーヘッドを解決するために、毎回DBにアクセスするのではなく、バックアップ挿入でdocument listとして保存したいと考えています.
この問題の解決策はsaveAll()です!同様に、内部ロジックをチェックします.
@Override
@SuppressWarnings("unchecked")
public <T> Collection<T> insert(Collection<? extends T> batchToSave, String collectionName) {
Assert.notNull(batchToSave, "BatchToSave must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
return (Collection<T>) doInsertBatch(collectionName, batchToSave, this.mongoConverter);
}
私が欲しいBatch Insertを行っていることを確認!では、saveAll()を使用してBatch Insertを使用する最終コードを見てみましょう.💁
public void insertAll(MetaDataCreateAllRequestDto metaDataCreateAllRequestDto){
...
for(Document body : bodyList){
MetaData metaData = new MetaData().builder()
.projectId(projectId)
.body(body).build();
metaDataList.add(metaData);
}
metaDataRepository.saveAll(metaDataList);
...
}
🥇 n/a.結論
実際,saveAllで挿入すると,5秒で20万個のメタデータが挿入され,従来のsaveに比べて性能が50%程度向上した.👍😏😏
次の図は、データの成長効率の確認値を示しています.
次の文章の予告..。
次の記事では、saveAll挿入を使用して200万個を超える場合に発生する問題について書きます!
読んでくれてありがとう!間違ったところがあったらメッセージを残して勉強を続けます.
Reference
この問題について(MongoRepositoryで大量のデータを挿入する際に発生する問題#2), 我々は、より多くの情報をここで見つけました https://velog.io/@tsi0521/MongoRepository의-saveAll을-통한-대량-삽입문제テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol