the way to parallel processing( nodejs )

the way to parallel processing

callback hell을 회피하기위해 Promise, async await 를 사용하여 어느정도 callback hell에대한 이슈들을 커버한다.
특히 async await은 동기코드로 작성할수있는 효과와 무엇보다 가독성이 좋아서 매우 만족스럽다.
하지만 ‘async await이 무조건 좋다’ 보다는 상황에 맞게 써야한다는것을 새삼깨달았다.
특히 병렬처리( 다중 비동기처리 ) 를 수행함에있어서 async await는 사용하지말아야할 것임은 분명하다.
코드를통해 어떻게 동작하는지, 그리고 어떤상황에 효율적으로 써야하는지 정리하려한다.

Promise 그리고 bluebird

병렬처리라고 하면 Promise.all 또는 bluebird.map을 활용하여 처리할수있다.
다음과같이 기본적인 병렬처리로직을 구성할수있다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const bluebird = require('bluebird');
function task({ num }) {
return new Promise((resolve, reject) => {
const second = 1000;
const tenSeconds = 10 * second;
const timeoutSeconds = tenSeconds - (num * second); // num 이 1이면 timeout = 9초, num이 2이면 timeout = 8초
setTimeout(() => {
console.log(num + ' 실행');
resolve(num);
}, timeoutSeconds);
})
};
const list = [
{ num: 0 },
{ num: 1 },
{ num: 2 },
{ num: 3 },
{ num: 4 },
{ num: 5 },
{ num: 6 },
{ num: 7 },
{ num: 8 },
{ num: 9 },
];
( async () => {
const result1 = await bluebird.map(list, item => task(item)) // bluebird style
const result2 = await Promise.all(list.map(item => task(item))) // promise style
// 9 실행
// 8 실행
// 7 실행
// 6 실행
// 5 실행
// 4 실행
// 3 실행
// 2 실행
// 1 실행
// 0 실행
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
})();

결과는 동일하며 num 이 0이 최대 10초가걸리므로 10개의 프로세스가 10초내에 모두 완료되어 병렬처리가된것을 확인할수있다.

어느상황에 적합한가 ?

server에서 scheduler를 통해 mobile에 알림을 push 한다던지, 특정 조건에 만족하는 사용자에게 mail을 send 한다던지 같은 비지니스로직내에서 수신주체가 다를경우 유용하게 사용할수있다.
예를들어 10000명에게 특정 mail을 send한다고 가정하면 async await을 사용시 동기로 동작하기에 1번 사용자 발송 완료되면 2번 그리고 3번 … 10000 번 사용자까지 반복할것이다.
이렇게되면 10000번 사용자는 메일발송 로직이 수행되고나서 한참뒤에 메일을 받게되어 서비스상 문제가될수있다.
그래서 다음과 같이 정의할수있다.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const bluebird = require('bluebird');
const nodemailer = require('nodemailer');
const config = reqlib('/config');
const { service, user, passwd, from } = config.setting.sender.mail;
const sender = nodemailer.createTransport({
service: service,
auth: {
user: user,
pass: passwd
}
});
function send({ to, subject, text, html }){
return new Promise(( resolve, reject ) => {
const options = { from: from, to: to, subject: subject };
if (text) options.text = text;
else if (html) options.html = html;
else if (attachments) options.attachments = attachments;
sender.sendMail(options, ( error, response ) => {
let sended = true;
if (error) sended = false;
resolve({
sended: sended,
mail: to
})
sender.close();
});
})
}
const resources = [
{
subject: 'title',
to: 'setyourmindpark@gmail.com',
text: 'hello world',
},
// { }..
]
( async () => {
const resultArr = await bluebird.map(resources, item => send(item));
// [ { sended: true, mail: 'setyourmindpark@gmail.com' }, ... ]
// 추가로직 수행
})();

메일발송 자체는 callback style로 구성하고 전체적인 동작 자체는 Promise 사용하여 발송여부와 함꼐 메일정보를 리턴한다.
그리고 마지막으로 async await으로 전체 수행결과를 기다린다. ( 메일발송 결과기반 추가로직수행위해 )
이렇게되면 10000명의 사용자에게 메일발송 동작은 병렬로 처리되며 발송 성공/실패 여부와 상관없이 전체 target 사용자에게 메일발송 로직은 처리할수있다.
이렇게 발송결과 데이터를 기반으로 발송실패 사용자를 대상으로 추가적인 비지니스로직을 수행하면될것이다. ( 다른방법으로 정보를알리든지, log를 저장하여 분석한다든지 )