Promise Pool - Giảm "down" hiệu quả cho server
May 26, 2021

#### Kiến thức nên có trước khi đọc bài viết - Đọc bài "API Request, to và dài quá cũng khổ!" trước nhé. - Khái niệm Object Pool Design Pattern
## ❖ Mở đầu Để tiếp tục chương trình, sau khi thông qua 2 giải pháp ở bài viết trước, các bạn thấy đó, kênh đào Suez vẫn còn đang bị stress với tình hình 1 con tàu lớn chia thành hàng trăm con tàu nhỏ và gửi đi cùng 1 lúc, gây tắc nghẽn giao thông đường thuỷ, dẫn tới việc các quan chức nhà nước họ phải căng thẳng tột độ để đáp ứng nhu cầu của người sử dụng. Đúng là tránh vỏ dưa gặp vỏ dừa, vừa mới tưởng là giải quyết được rồi ai ngờ lại sinh ra vấn đề khác. Vậy thì mình làm sao để cho giảm stress được cho họ đây? #### ▷ Bài toán Mình xin mượn lại tấm hình mình chụp của bài cũ để nói lên bài toán của bài này nhé:

Vâng, chia request parameters ra thành nhiều request nhỏ hơn, mỗi request đi chứa 500 movieIds. Sẽ thuận lợi và êm đẹp biết bao nếu server chúng ta khá mạnh và có khả năng auto-scale resource để xử lý một lúc nhiều request. Nhưng, đời không đẹp như ta tưởng, 1 ngày đẹp trời như bao ngày khác, vẫn phương án đó, nhưng array của movieIds lại tăng lên một xí, thế là chạm ngưỡng chịu đựng của con server thương mến của chúng ta, và việc gì đến cũng phải đến, nó đã về với tổ tiên sau khi hiện lên màn hình HTTP 502 Bad Gateway. "Nó đi thật rồi ông giáo ạ!". Vậy bây giờ chúng ta giải quyết thế nào? ## ❖ Cách giảm đau ### ▷ Cách giải quyết giảm đau nhất thời Theo như suy nghĩ thông thường, khi các anh chị em ta giải quyết bằng phương pháp thuần tuý ở bài trước sẽ chợt giật mình nhận ra với 1 câu hỏi tu từ là: ”Ủa? Mình request đi 1 lượt vậy, với 10000 Movies chia làm 20 request thì tạm ổn, nhưng nhiều hơn có khi nào mình đang tự DDoS mình không ta?”. Vâng, chính xác là nó đó mọi người ơi, vì là ví dụ với 10000 movies thôi thì cắt 500 ids ra thành 20 requests không thành vấn đề, nhưng nếu trong trường hợp của các anh chị em mình làm Micro-services, có khi rơi vào tình thế phải lấy cả triệu dòng records thì sao? Với những server không có cơ chế tự động scale resource thì rõ ràng là Server sẽ trả 502 Bad Gateway ngay. Vậy để tránh trường hợp mình chia tàu lớn ra nhiều tàu nhỏ và gửi đi cùng 1 lúc gây tắc nghẽn giao thông đường thuỷ nước bạn, thì mình chia từng đợt cho bớt kẹt tàu nước người ta chứ nhỉ? Mình sẽ thêm 1 điều kiện để phân khúc lượt request đi như đoạn code sau:
const movieIds = [
1,
2,
3,
....
10000,
]
const requestAll = [];
while (movieIds.lenght > 0) {
const chunkMovieIds = movieIds.splice(0, 500);
requestAll.push(fetch('https://movie.api-example.com/movies?moviesId=' + chunkMovieIds.join(',')));
// Giả định API trả về JSON như sau
// {results: [{moiveId: 1, movieName: 'phim 1'}, {moiveId: 2, movieName: 'phim 2'}, {moiveId: 3, movieName: 'phim 3'}, ...]}
if(requestAll.lenght >= 3){
const tmpResults = await Promise.all(requestAll);
// map kết quả cho từng đợt request ở đây
requestAll = [];
}
}
Giải thích cho đoạn code trên thì mình inspect thử network xem nó gửi thế nào nhé

function chunkBlock(data, numberPerBlock){
const blocks = [];
while (data.length > 0) {
const chunked = data.splice(0, numberPerBlock);
blocks.push(chunked);
}
return blocks;
}
async function PromisePool(handler, data, concurency){
const iterator = data.entries();
const workers = new Array(concurency)
.fill(iterator)
.map(async (iterator) => {
for(const [index, item] of iterator){
await handler(item, index);
}
});
await Promise.all(workers);
}
(async () => {
const movieIds = [
1,
2,
3,
// ...
10000,
]
const movieBlockIds = chunkBlock(movieIds, 500);
const resultsAll = [];
await PromisePool( async (moveIds, index) => {
const res = await fetch('https://movie.api-example.com/movies?moviesId=' + moveIds.join(','))
resultsAll[index] = (await res.json()).results;
}, movieBlockIds, 3);
// map kết quả cho từng đợt request ở đây
})()
Giải thích cho đoạn code trên thì mình inspect thử network xem nó gửi thế nào nhé

## ❖ Tổng kết Thông qua 2 giải pháp mình phát triển lên từ những ngày đầu còn ngô nghê thì mình xin tổng kết lại 2 phương án trên nhé | **Solutions** | **Advantage** | **Disadvantage** | **Evaluation** | | ------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | Cắt parameter nhưng chia đợt request | Giảm tải cho server tốt | Tốc độ của response bị chậm | Giải quyết được bài toán, nhưng thời gian response của request bị chậm, vì phải chờ kết quả của từng đợt request, và trong mỗi đợt request thì kết quả quyết định bằng response chậm nhất.
→ Cách này khả thi cho những nghiệp vụ chạy Batch nền hoặc những nghiệp vụ không đòi hỏi thời gian response nhanh. | | Cắt parameter và cho request tiếp sức bằng Promise Pool | Tốc độ của response: Trung bình
Khả năng chịu tải của server: Trung bình | Buộc phải tính toán kỹ lưỡng để có tỉ lệ thích hợp giữa số lượt cho từng đợt request và số lượng parameter request | Giải quyết được bài toán ổn định về 2 tiêu chí, nhưng buộc developer phải tính toán và đo ra tỉ lệ hợp lý.
→ Cách này khả thi cho những nghiệp vụ không đòi hỏi cao quá về thời gian response và những server bị hạn chế resource |
## ❖ Lời kết Đến đây mình xin kết thúc 2 bài trong phần series vừa qua. Cho đến thời điểm này tuy sử dụng Promise Pool để giải quyết bài toán khi gửi request lớn đến server có tính ổn định nhất định, nhưng mình và các anh chị em đồng nghiệp vẫn còn nghiên cứu thêm để tối ưu hoá hơn. Các bạn đọc có ideas nào hay hơn và tốt hơn thì cũng đừng ngần ngại chia sẻ với bọn mình nhé. Mình xin gửi lời cám ơn đến anh Kiệt và anh Trung (chiêm) vì đã chấp cánh và hỗ trợ cho câu chuyện của mình. Bài viết cũng hơi bị dài rồi, nên mình xin chào tạm biệt các bạn và hẹn các bạn ở một bài viết khác, nếu may mắn hơn biết đâu chúng ta có thể gặp nhau tại Techbase Vietnam để cùng chia sẻ và nâng cao kiến thức tay nghề nhé. ## ❖ Reference Về những ideas và kiến thức mình xin viết nguồn tham khảo lại đây cho mọi người nhé: - [Object pool pattern](https://en.wikipedia.org/wiki/Object_pool_pattern) - [What is the best way to limit concurrency when using ES6's Promise.all()?](https://stackoverflow.com/a/51020535) - [@supercharge/promise-pool](https://www.npmjs.com/package/@supercharge/promise-pool) - https://medium.com/pipedrive-engineering/javascript-weighted-promises-pool-8153c9688f35 - [431 Request Header Fields Too Large - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431) - [502 Bad Gateway - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) - [Promise - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) - [async function - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)