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万個を超える場合に発生する問題について書きます!
読んでくれてありがとう!間違ったところがあったらメッセージを残して勉強を続けます.