JavaScript & TypeScript

[NestJS] iterable 객체 병렬처리 하기(feat.Bluebird JS)

Chunghyun Lee 2022. 10. 9. 22:47

이번 포스팅에서는 iterable 객체 병렬처리에 대해 알아보겠습니다.

 

iterable 객체체인 updateDtoList 배열을 돌면서,

db에서 updateDto에 담겨있는 도메인 idx로 find하고

해당 엔티티를 업데이트 하는 작업을 하고 있었는데요.

 

아래 예시 코드처럼 처리하게 되면, updateDtoList 배열을 순차적으로 순회하면서 실행되기 때문에 속도적인 측면에서 좋지 않다고 생각했습니다.

 

const updateEntities = await updateDtoList.map(async (updateDto) => {
	const updateEntity = await this.entityRepository.findOne(updateDto.idx);
     updateEntity.startDate = updateDto.startDate;
     updateEntity.endDate = updateDto.endDate;

     return updateStudentClassState;
});

await this.entityRepository.save(updateEntities);

 

그래서 이를 해결하기 위해 iterable객체를 병렬로 처리하기 위해 고민한것은 다음과 같이 크게 두가지가 있는데요.(각각에 대한 설명은 스킵하겠습니다.)

1. Promise.all

2. Bluebird.map

 

아래 예시 코드는 Promise.all을 사용한 코드입니다.

동작 순서를 확인하기 위해 로그도 함께 작성해보았습니다.

 

const debugArray: number[] = [];

const updateStudentClassStates = await Promise.all(
	updateStudentClassDtoList.map(async (updateStudentClassDto) => {
		console.debug(`${updateStudentClassDto.idx}번 start`);
		const updateStudentClassState = await this.studentArtClassStateRepository.findOne();
        
		updateStudentClassState.startDate = updateStudentClassDto.startDate;
		updateStudentClassState.endDate = updateStudentClassDto.endDate;

		console.debug(`${updateStudentClassDto.idx}번 end`);
		debugArray.push(updateStudentClassDto.idx);
		return updateStudentClassState;
	})
);

console.debug('debugArray', debugArray);

 

아래 이미지는 위의 코드를 실행했을 때, 동작되는 순서를 보여주는데요.

보시는 것과 같이 배열이 순차실행 되는것이 아닌 각각 실행되고 전부 완료되었을 때 새로운 배열을 반환해주는 것을 알 수 있습니다.

 

 

다음은 Bluebird.map을 사용한 예시입니다.

아래의 이미지를 보면 다음의 코드 또한 Promise.all과 동일한 결과를 보여주는것을 알 수 있습니다.

 

const debugArray: number[] = [];

const updateStudentClassStates = await Bluebird.map(updateStudentClassDtoList, async (updateStudentClassDto) => {
		console.debug(`${updateStudentClassDto.idx}번 start`);
		const updateStudentClassState = await this.studentArtClassStateRepository.findOne();
        
		updateStudentClassState.startDate = updateStudentClassDto.startDate;
		updateStudentClassState.endDate = updateStudentClassDto.endDate;

		console.debug(`${updateStudentClassDto.idx}번 end`);
		debugArray.push(updateStudentClassDto.idx);
		return updateStudentClassState;
	});

console.debug('debugArray', debugArray);

 

 

그렇다면 둘 중 어떤 것을 선택했을 까요?

저는 둘의 동작은 동일하지만 concurrency 옵션을 편하게 사용가능한 Bluebird.map을 최종적으로 선택했습니다.

물론 현재 작업하고 있는 부분에서 기획적으로 100개 이상씩 병렬 처리를 한다거나 할 일은 없습니다만,

추후 필요시 사용가능 하다는 점과 N개 이상의 병렬처리는 Bluebird.map, 단순한 병렬처리는 Promise.all 이렇게 이분화 하여 사용하는 것보다 통일하는 것이 좋다고 판단했습니다.

 

마지막으로 concurrency 옵션을 적용한 코드와 동작 이미지 첨부하면서 글을 마치도록 하겠습니다.

 

const debugArray: number[] = [];

const updateStudentClassStates = await Bluebird.map(updateStudentClassDtoList, async (updateStudentClassDto) => {
		console.debug(`${updateStudentClassDto.idx}번 start`);
		const updateStudentClassState = await this.studentArtClassStateRepository.findOne();
        
		updateStudentClassState.startDate = updateStudentClassDto.startDate;
		updateStudentClassState.endDate = updateStudentClassDto.endDate;

		console.debug(`${updateStudentClassDto.idx}번 end`);
		debugArray.push(updateStudentClassDto.idx);
		return updateStudentClassState;
	},
    {concurrency: 2}
);

console.debug('debugArray', debugArray);