ಕನ್ಕರೆಂಟ್ ಸ್ಟ್ರೀಮ್ ಪ್ರೊಸೆಸಿಂಗ್ಗಾಗಿ ಸುಧಾರಿತ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ತಂತ್ರಗಳನ್ನು ಅನ್ವೇಷಿಸಿ. ಅಧಿಕ-ಥ್ರೋಪುಟ್ API ಕರೆಗಳು, ಫೈಲ್ ಪ್ರೊಸೆಸಿಂಗ್, ಮತ್ತು ಡೇಟಾ ಪೈಪ್ಲೈನ್ಗಳಿಗಾಗಿ ಪ್ಯಾರಲಲ್ ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಗಳನ್ನು ನಿರ್ಮಿಸಲು ಕಲಿಯಿರಿ.
ಉನ್ನತ-ಕಾರ್ಯಕ್ಷಮತೆಯ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಅನ್ನು ಅನ್ಲಾಕ್ ಮಾಡುವುದು: ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ ಪ್ಯಾರಲಲ್ ಪ್ರೊಸೆಸಿಂಗ್ ಮತ್ತು ಕನ್ಕರೆಂಟ್ ಸ್ಟ್ರೀಮ್ಗಳ ಆಳವಾದ ನೋಟ
ಆಧುನಿಕ ಸಾಫ್ಟ್ವೇರ್ ಅಭಿವೃದ್ಧಿಯ ಜಗತ್ತಿನಲ್ಲಿ, ಡೇಟಾವು ರಾಜನಾಗಿದೆ. ನಾವು APIಗಳು, ಡೇಟಾಬೇಸ್ಗಳು, ಅಥವಾ ಫೈಲ್ ಸಿಸ್ಟಮ್ಗಳಿಂದ ಬರುವ ಅಗಾಧ ಪ್ರಮಾಣದ ಡೇಟಾ ಸ್ಟ್ರೀಮ್ಗಳನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡುವ ಸವಾಲನ್ನು ನಿರಂತರವಾಗಿ ಎದುರಿಸುತ್ತೇವೆ. ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಡೆವಲಪರ್ಗಳಿಗೆ, ಭಾಷೆಯ ಸಿಂಗಲ್-ಥ್ರೆಡೆಡ್ ಸ್ವಭಾವವು ಒಂದು ಪ್ರಮುಖ ಅಡಚಣೆಯಾಗಬಹುದು. ದೊಡ್ಡ ಡೇಟಾಸೆಟ್ ಅನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡುವ ದೀರ್ಘಕಾಲದ, ಸಿಂಕ್ರೊನಸ್ ಲೂಪ್ ಬ್ರೌಸರ್ನಲ್ಲಿ ಬಳಕೆದಾರ ಇಂಟರ್ಫೇಸ್ ಅನ್ನು ಫ್ರೀಜ್ ಮಾಡಬಹುದು ಅಥವಾ Node.js ನಲ್ಲಿ ಸರ್ವರ್ ಅನ್ನು ಸ್ಥಗಿತಗೊಳಿಸಬಹುದು. ಈ ತೀವ್ರವಾದ ಕೆಲಸದ ಹೊರೆಗಳನ್ನು ಸಮರ್ಥವಾಗಿ ನಿಭಾಯಿಸಬಲ್ಲ ಸ್ಪಂದನಾಶೀಲ, ಉನ್ನತ-ಕಾರ್ಯಕ್ಷಮತೆಯ ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ನಾವು ಹೇಗೆ ನಿರ್ಮಿಸುವುದು?
ಇದಕ್ಕೆ ಉತ್ತರವು ಅಸಿಂಕ್ರೊನಸ್ ಪ್ಯಾಟರ್ನ್ಗಳನ್ನು ಕರಗತ ಮಾಡಿಕೊಳ್ಳುವುದರಲ್ಲಿ ಮತ್ತು ಕನ್ಕರೆನ್ಸಿಯನ್ನು ಅಳವಡಿಸಿಕೊಳ್ಳುವುದರಲ್ಲಿದೆ. ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ಗಾಗಿ ಬರಲಿರುವ ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಸ್ ಪ್ರಸ್ತಾವನೆಯು ನಾವು ಸಿಂಕ್ರೊನಸ್ ಕಲೆಕ್ಷನ್ಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವ ವಿಧಾನವನ್ನು ಕ್ರಾಂತಿಗೊಳಿಸುವ ಭರವಸೆ ನೀಡುತ್ತದೆ, ಆದರೆ ಅದರ ತತ್ವಗಳನ್ನು ಅಸಿಂಕ್ರೊನಸ್ ಜಗತ್ತಿಗೆ ವಿಸ್ತರಿಸಿದಾಗ ಅದರ ನಿಜವಾದ ಶಕ್ತಿಯನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಬಹುದು. ಈ ಲೇಖನವು ಇಟರೇಟರ್-ರೀತಿಯ ಸ್ಟ್ರೀಮ್ಗಳಿಗೆ ಪ್ಯಾರಲಲ್ ಪ್ರೊಸೆಸಿಂಗ್ ಪರಿಕಲ್ಪನೆಯ ಆಳವಾದ ನೋಟವಾಗಿದೆ. ಅಧಿಕ-ಥ್ರೋಪುಟ್ API ಕರೆಗಳು ಮತ್ತು ಪ್ಯಾರಲಲ್ ಡೇಟಾ ರೂಪಾಂತರಗಳಂತಹ ಕಾರ್ಯಗಳನ್ನು ನಿರ್ವಹಿಸಲು ನಮ್ಮದೇ ಆದ ಕನ್ಕರೆಂಟ್ ಸ್ಟ್ರೀಮ್ ಆಪರೇಟರ್ಗಳನ್ನು ಹೇಗೆ ನಿರ್ಮಿಸುವುದು ಎಂಬುದನ್ನು ನಾವು ಅನ್ವೇಷಿಸುತ್ತೇವೆ, ಕಾರ್ಯಕ್ಷಮತೆಯ ಅಡಚಣೆಗಳನ್ನು ಸಮರ್ಥ, ನಾನ್-ಬ್ಲಾಕಿಂಗ್ ಪೈಪ್ಲೈನ್ಗಳಾಗಿ ಪರಿವರ್ತಿಸುತ್ತೇವೆ.
ಅಡಿಪಾಯ: ಇಟರೇಟರ್ಗಳು ಮತ್ತು ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಗಳನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು
ನಾವು ಓಡುವ ಮೊದಲು, ನಡೆಯಲು ಕಲಿಯಬೇಕು. ನಮ್ಮ ಸುಧಾರಿತ ಪ್ಯಾಟರ್ನ್ಗಳಿಗೆ ಬುನಾದಿಯಾಗಿರುವ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ನಲ್ಲಿನ ಪುನರಾವರ್ತನೆಯ (iteration) ಪ್ರಮುಖ ಪರಿಕಲ್ಪನೆಗಳನ್ನು ಸಂಕ್ಷಿಪ್ತವಾಗಿ ಪುನಃ ನೋಡೋಣ.
ಇಟರೇಟರ್ ಪ್ರೋಟೋಕಾಲ್ ಎಂದರೇನು?
ಇಟರೇಟರ್ ಪ್ರೋಟೋಕಾಲ್ ಎಂಬುದು ಮೌಲ್ಯಗಳ ಅನುಕ್ರಮವನ್ನು ಉತ್ಪಾದಿಸುವ ಒಂದು ಪ್ರಮಾಣಿತ ವಿಧಾನವಾಗಿದೆ. ಒಂದು ಆಬ್ಜೆಕ್ಟ್ ಇಟರೇಟರ್ ಆಗಿರುತ್ತದೆ, ಅದು next() ಮೆಥಡ್ ಅನ್ನು ಹೊಂದಿದ್ದರೆ, ಮತ್ತು ಆ ಮೆಥಡ್ ಎರಡು ಪ್ರಾಪರ್ಟಿಗಳನ್ನು ಹೊಂದಿರುವ ಆಬ್ಜೆಕ್ಟ್ ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ:
value: ಅನುಕ್ರಮದಲ್ಲಿನ ಮುಂದಿನ ಮೌಲ್ಯ.done: ಇಟರೇಟರ್ ಖಾಲಿಯಾಗಿದ್ದರೆtrueಮತ್ತು ಇಲ್ಲದಿದ್ದರೆfalseಆಗಿರುವ ಬೂಲಿಯನ್.
ಇಲ್ಲಿ ಒಂದು ನಿರ್ದಿಷ್ಟ ಸಂಖ್ಯೆಯವರೆಗೆ ಎಣಿಸುವ ಕಸ್ಟಮ್ ಇಟರೇಟರ್ನ ಸರಳ ಉದಾಹರಣೆಯಿದೆ:
function createCounter(limit) {
let count = 0;
return {
next: function() {
if (count < limit) {
return { value: count++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
const counter = createCounter(3);
console.log(counter.next()); // { value: 0, done: false }
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: undefined, done: true }
ಅರೇಗಳು (Arrays), ಮ್ಯಾಪ್ಗಳು (Maps), ಮತ್ತು ಸ್ಟ್ರಿಂಗ್ಗಳು (Strings) "ಇಟರೇಬಲ್" ಆಗಿರುತ್ತವೆ ಏಕೆಂದರೆ ಅವುಗಳು [Symbol.iterator] ಮೆಥಡ್ ಅನ್ನು ಹೊಂದಿರುತ್ತವೆ, ಅದು ಇಟರೇಟರ್ ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ. ಇದೇ ಕಾರಣದಿಂದ ನಾವು ಅವುಗಳನ್ನು for...of ಲೂಪ್ಗಳಲ್ಲಿ ಬಳಸಬಹುದು.
ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಗಳ ಭರವಸೆ
TC39 ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಸ್ ಪ್ರಸ್ತಾವನೆಯು Iterator.prototype ಮೇಲೆ ನೇರವಾಗಿ ಯುಟಿಲಿಟಿ ಮೆಥಡ್ಗಳ ಒಂದು ಗುಂಪನ್ನು ಸೇರಿಸುವ ಗುರಿಯನ್ನು ಹೊಂದಿದೆ. ಇದು ನಾವು ಈಗಾಗಲೇ Array.prototype ನಲ್ಲಿ ಹೊಂದಿರುವ map, filter, ಮತ್ತು reduce ನಂತಹ ಶಕ್ತಿಶಾಲಿ ಮೆಥಡ್ಗಳಿಗೆ ಸಮಾನವಾಗಿದೆ, ಆದರೆ ಯಾವುದೇ ಇಟರೇಬಲ್ ಆಬ್ಜೆಕ್ಟ್ಗೆ ಅನ್ವಯಿಸುತ್ತದೆ. ಇದು ಅನುಕ್ರಮಗಳನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡಲು ಹೆಚ್ಚು ಡಿಕ್ಲರೇಟಿವ್ ಮತ್ತು ಮೆಮೊರಿ-ದಕ್ಷ ವಿಧಾನವನ್ನು ಅನುಮತಿಸುತ್ತದೆ.
ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಗಳ ಮೊದಲು (ಹಳೆಯ ವಿಧಾನ):
const numbers = [1, 2, 3, 4, 5, 6];
// To get the sum of squares of even numbers, we create intermediate arrays.
const evenNumbers = numbers.filter(n => n % 2 === 0);
const squares = evenNumbers.map(n => n * n);
const sum = squares.reduce((acc, n) => acc + n, 0);
console.log(sum); // 56 (2*2 + 4*4 + 6*6)
ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಗಳೊಂದಿಗೆ (ಪ್ರಸ್ತಾವಿತ ಭವಿಷ್ಯ):
const numbersIterator = [1, 2, 3, 4, 5, 6].values();
// No intermediate arrays are created. Operations are lazy and pulled one by one.
const sum = numbersIterator
.filter(n => n % 2 === 0) // returns a new iterator
.map(n => n * n) // returns another new iterator
.reduce((acc, n) => acc + n, 0); // consumes the final iterator
console.log(sum); // 56
ಇಲ್ಲಿನ ಪ್ರಮುಖ ಅಂಶವೆಂದರೆ, ಈ ಪ್ರಸ್ತಾವಿತ ಹೆಲ್ಪರ್ಗಳು ಅನುಕ್ರಮವಾಗಿ ಮತ್ತು ಸಿಂಕ್ರೊನಸ್ ಆಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ. ಅವು ಒಂದು ಐಟಂ ಅನ್ನು ಎಳೆಯುತ್ತವೆ, ಅದನ್ನು ಚೈನ್ ಮೂಲಕ ಪ್ರೊಸೆಸ್ ಮಾಡುತ್ತವೆ, ನಂತರ ಮುಂದಿನದನ್ನು ಎಳೆಯುತ್ತವೆ. ಇದು ಮೆಮೊರಿ ದಕ್ಷತೆಗೆ ಉತ್ತಮವಾಗಿದೆ ಆದರೆ ಸಮಯ ತೆಗೆದುಕೊಳ್ಳುವ, I/O-ಬೌಂಡ್ ಕಾರ್ಯಾಚರಣೆಗಳೊಂದಿಗೆ ನಮ್ಮ ಕಾರ್ಯಕ್ಷಮತೆಯ ಸಮಸ್ಯೆಯನ್ನು ಪರಿಹರಿಸುವುದಿಲ್ಲ.
ಸಿಂಗಲ್-ಥ್ರೆಡೆಡ್ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ನಲ್ಲಿನ ಕನ್ಕರೆನ್ಸಿ ಸವಾಲು
ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ನ ಎಕ್ಸಿಕ್ಯೂಶನ್ ಮಾದರಿಯು ಪ್ರಸಿದ್ಧವಾಗಿ ಸಿಂಗಲ್-ಥ್ರೆಡೆಡ್ ಆಗಿದೆ, ಅದು ಈವೆಂಟ್ ಲೂಪ್ ಸುತ್ತ ಸುತ್ತುತ್ತದೆ. ಇದರರ್ಥ ಅದು ತನ್ನ ಮುಖ್ಯ ಕಾಲ್ ಸ್ಟ್ಯಾಕ್ನಲ್ಲಿ ಒಂದೇ ಬಾರಿಗೆ ಕೇವಲ ಒಂದು ಕೋಡ್ ತುಣುಕನ್ನು ಮಾತ್ರ ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದು. ಸಿಂಕ್ರೊನಸ್, CPU-ತೀವ್ರವಾದ ಕಾರ್ಯವು ಚಾಲನೆಯಲ್ಲಿರುವಾಗ (ಬೃಹತ್ ಲೂಪ್ನಂತೆ), ಅದು ಕಾಲ್ ಸ್ಟ್ಯಾಕ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸುತ್ತದೆ. ಬ್ರೌಸರ್ನಲ್ಲಿ, ಇದು ಫ್ರೀಜ್ ಆದ UI ಗೆ ಕಾರಣವಾಗುತ್ತದೆ. ಸರ್ವರ್ನಲ್ಲಿ, ಇದರರ್ಥ ಸರ್ವರ್ ಬೇರೆ ಯಾವುದೇ ಒಳಬರುವ ವಿನಂತಿಗಳಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ.
ಇಲ್ಲಿ ನಾವು ಕನ್ಕರೆನ್ಸಿ (concurrency) ಮತ್ತು ಪ್ಯಾರಲಲಿಸಂ (parallelism) ನಡುವೆ ವ್ಯತ್ಯಾಸವನ್ನು ಗುರುತಿಸಬೇಕು:
- ಕನ್ಕರೆನ್ಸಿ ಎಂದರೆ ಒಂದು ಅವಧಿಯಲ್ಲಿ ಅನೇಕ ಕಾರ್ಯಗಳನ್ನು ನಿರ್ವಹಿಸುವುದು. ಈವೆಂಟ್ ಲೂಪ್ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ಗೆ ಹೆಚ್ಚು ಕನ್ಕರೆಂಟ್ ಆಗಲು ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ನೆಟ್ವರ್ಕ್ ವಿನಂತಿಯನ್ನು (I/O ಕಾರ್ಯಾಚರಣೆ) ಪ್ರಾರಂಭಿಸಬಹುದು, ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆಗಾಗಿ ಕಾಯುತ್ತಿರುವಾಗ, ಅದು ಬಳಕೆದಾರರ ಕ್ಲಿಕ್ಗಳು ಅಥವಾ ಇತರ ಈವೆಂಟ್ಗಳನ್ನು ನಿಭಾಯಿಸಬಹುದು. ಕಾರ್ಯಗಳು ಒಂದರ ನಂತರ ಒಂದರಂತೆ ನಡೆಯುತ್ತವೆ, ಒಂದೇ ಸಮಯದಲ್ಲಿ ಅಲ್ಲ.
- ಪ್ಯಾರಲಲಿಸಂ ಎಂದರೆ ಒಂದೇ ಸಮಯದಲ್ಲಿ ಅನೇಕ ಕಾರ್ಯಗಳನ್ನು ನಡೆಸುವುದು. ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ನಲ್ಲಿ ನಿಜವಾದ ಪ್ಯಾರಲಲಿಸಂ ಅನ್ನು ಸಾಮಾನ್ಯವಾಗಿ ಬ್ರೌಸರ್ನಲ್ಲಿ ವೆಬ್ ವರ್ಕರ್ಸ್ (Web Workers) ಅಥವಾ Node.js ನಲ್ಲಿ ವರ್ಕರ್ ಥ್ರೆಡ್ಸ್/ಚೈಲ್ಡ್ ಪ್ರೊಸೆಸಸ್ (Worker Threads/Child Processes) ನಂತಹ ತಂತ್ರಜ್ಞಾನಗಳನ್ನು ಬಳಸಿ ಸಾಧಿಸಲಾಗುತ್ತದೆ, ಇದು ತಮ್ಮದೇ ಆದ ಈವೆಂಟ್ ಲೂಪ್ಗಳೊಂದಿಗೆ ಪ್ರತ್ಯೇಕ ಥ್ರೆಡ್ಗಳನ್ನು ಒದಗಿಸುತ್ತದೆ.
ನಮ್ಮ ಉದ್ದೇಶಗಳಿಗಾಗಿ, ನಾವು I/O-ಬೌಂಡ್ ಕಾರ್ಯಾಚರಣೆಗಳಿಗೆ (API ಕರೆಗಳಂತಹ) ಹೆಚ್ಚಿನ ಕನ್ಕರೆನ್ಸಿ ಸಾಧಿಸುವುದರ ಮೇಲೆ ಗಮನಹರಿಸುತ್ತೇವೆ, ಇಲ್ಲಿಯೇ ಅತ್ಯಂತ ಮಹತ್ವದ ನೈಜ-ಪ್ರಪಂಚದ ಕಾರ್ಯಕ್ಷಮತೆಯ ಲಾಭಗಳು ಹೆಚ್ಚಾಗಿ ಕಂಡುಬರುತ್ತವೆ.
ಪ್ಯಾರಡೈಮ್ ಶಿಫ್ಟ್: ಅಸಿಂಕ್ರೊನಸ್ ಇಟರೇಟರ್ಗಳು
ಕಾಲಾನಂತರದಲ್ಲಿ ಬರುವ ಡೇಟಾ ಸ್ಟ್ರೀಮ್ಗಳನ್ನು (ನೆಟ್ವರ್ಕ್ ವಿನಂತಿ ಅಥವಾ ದೊಡ್ಡ ಫೈಲ್ನಿಂದ ಬರುವಂತಹ) ನಿಭಾಯಿಸಲು, ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಅಸಿಂಕ್ ಇಟರೇಟರ್ ಪ್ರೋಟೋಕಾಲ್ ಅನ್ನು ಪರಿಚಯಿಸಿತು. ಇದು ಅದರ ಸಿಂಕ್ರೊನಸ್ ಸಹೋದರನಂತೆಯೇ ಇದೆ, ಆದರೆ ಒಂದು ಪ್ರಮುಖ ವ್ಯತ್ಯಾಸದೊಂದಿಗೆ: next() ಮೆಥಡ್ ಒಂದು Promise ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ, ಅದು { value, done } ಆಬ್ಜೆಕ್ಟ್ಗೆ ರಿಸಾಲ್ವ್ ಆಗುತ್ತದೆ.
ಇದು ಎಲ್ಲಾ ಡೇಟಾವನ್ನು ಒಂದೇ ಬಾರಿಗೆ ಲಭ್ಯವಿಲ್ಲದ ಡೇಟಾ ಮೂಲಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡಲು ನಮಗೆ ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಈ ಅಸಿಂಕ್ ಸ್ಟ್ರೀಮ್ಗಳನ್ನು ಸುಲಭವಾಗಿ ಬಳಸಲು, ನಾವು for await...of ಲೂಪ್ ಅನ್ನು ಬಳಸುತ್ತೇವೆ.
API ನಿಂದ ಡೇಟಾದ ಪುಟಗಳನ್ನು ತರುವುದನ್ನು ಅನುಕರಿಸುವ ಅಸಿಂಕ್ ಇಟರೇಟರ್ ಅನ್ನು ರಚಿಸೋಣ:
async function* fetchPaginatedData(url) {
let nextPageUrl = url;
while (nextPageUrl) {
console.log(`Fetching from ${nextPageUrl}...`);
const response = await fetch(nextPageUrl);
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
const data = await response.json();
// Yield each item from the current page's results
for (const item of data.results) {
yield item;
}
// Move to the next page, or stop if there isn't one
nextPageUrl = data.nextPage;
}
}
// Usage:
async function processUsers() {
const userStream = fetchPaginatedData('https://api.example.com/users');
for await (const user of userStream) {
console.log(`Processing user: ${user.name}`);
// This is still sequential processing. We wait for one user to be logged
// before the next one is even requested from the stream.
}
}
ಇದು ಒಂದು ಶಕ್ತಿಯುತ ಪ್ಯಾಟರ್ನ್, ಆದರೆ ಲೂಪ್ನಲ್ಲಿನ ಕಾಮೆಂಟ್ ಅನ್ನು ಗಮನಿಸಿ. ಪ್ರೊಸೆಸಿಂಗ್ ಅನುಕ್ರಮವಾಗಿದೆ. `process user` ಮತ್ತೊಂದು ನಿಧಾನಗತಿಯ, ಅಸಿಂಕ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ಒಳಗೊಂಡಿದ್ದರೆ (ಡೇಟಾಬೇಸ್ಗೆ ಉಳಿಸುವ ಹಾಗೆ), ನಾವು ಪ್ರತಿಯೊಂದನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು ಕಾಯಬೇಕಾಗುತ್ತದೆ, ಮುಂದಿನದನ್ನು ಪ್ರಾರಂಭಿಸುವ ಮೊದಲು. ಇದೇ ನಾವು ನಿವಾರಿಸಲು ಬಯಸುವ ಅಡಚಣೆ.
ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಗಳೊಂದಿಗೆ ಕನ್ಕರೆಂಟ್ ಸ್ಟ್ರೀಮ್ ಕಾರ್ಯಾಚರಣೆಗಳನ್ನು ರೂಪಿಸುವುದು
ಈಗ ನಾವು ನಮ್ಮ ಚರ್ಚೆಯ ತಿರುಳಿಗೆ ಬಂದಿದ್ದೇವೆ. ಹಿಂದಿನ ಐಟಂ ಮುಗಿಯುವವರೆಗೆ ಕಾಯದೆ, ಅಸಿಂಕ್ರೊನಸ್ ಸ್ಟ್ರೀಮ್ನಿಂದ ಐಟಂಗಳನ್ನು ಕನ್ಕರೆಂಟ್ ಆಗಿ ಹೇಗೆ ಪ್ರೊಸೆಸ್ ಮಾಡುವುದು? ನಾವು ಕಸ್ಟಮ್ ಅಸಿಂಕ್ ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ ಅನ್ನು ನಿರ್ಮಿಸುತ್ತೇವೆ, ಅದನ್ನು asyncMapConcurrent ಎಂದು ಕರೆಯೋಣ.
ಈ ಫಂಕ್ಷನ್ ಮೂರು ಆರ್ಗ್ಯುಮೆಂಟ್ಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳುತ್ತದೆ:
sourceIterator: ನಾವು ಐಟಂಗಳನ್ನು ಎಳೆಯಲು ಬಯಸುವ ಅಸಿಂಕ್ ಇಟರೇಟರ್.mapperFn: ಪ್ರತಿಯೊಂದು ಐಟಂಗೆ ಅನ್ವಯಿಸಲಾಗುವ ಅಸಿಂಕ್ ಫಂಕ್ಷನ್.concurrency: ಒಂದೇ ಸಮಯದಲ್ಲಿ ಎಷ್ಟು `mapperFn` ಕಾರ್ಯಾಚರಣೆಗಳು ನಡೆಯಬಹುದು ಎಂಬುದನ್ನು ವ್ಯಾಖ್ಯಾನಿಸುವ ಸಂಖ್ಯೆ.
ಮೂಲ ಪರಿಕಲ್ಪನೆ: ಪ್ರಾಮಿಸ್ಗಳ ವರ್ಕರ್ ಪೂಲ್
ತಂತ್ರವೆಂದರೆ ಸಕ್ರಿಯ ಪ್ರಾಮಿಸ್ಗಳ "ಪೂಲ್" ಅಥವಾ ಒಂದು ಸೆಟ್ ಅನ್ನು ನಿರ್ವಹಿಸುವುದು. ಈ ಪೂಲ್ನ ಗಾತ್ರವು ನಮ್ಮ concurrency ಪ್ಯಾರಾಮೀಟರ್ನಿಂದ ಸೀಮಿತವಾಗಿರುತ್ತದೆ.
- ನಾವು ಮೂಲ ಇಟರೇಟರ್ನಿಂದ ಐಟಂಗಳನ್ನು ಎಳೆಯುವ ಮೂಲಕ ಮತ್ತು ಅವುಗಳಿಗಾಗಿ ಅಸಿಂಕ್ `mapperFn` ಅನ್ನು ಪ್ರಾರಂಭಿಸುವ ಮೂಲಕ ಆರಂಭಿಸುತ್ತೇವೆ.
- ನಾವು `mapperFn` ನಿಂದ ಹಿಂತಿರುಗಿದ ಪ್ರಾಮಿಸ್ ಅನ್ನು ನಮ್ಮ ಸಕ್ರಿಯ ಪೂಲ್ಗೆ ಸೇರಿಸುತ್ತೇವೆ.
- ಪೂಲ್ ತುಂಬುವವರೆಗೆ (ಅದರ ಗಾತ್ರ ನಮ್ಮ `concurrency` ಮಟ್ಟಕ್ಕೆ ಸಮಾನವಾಗುವವರೆಗೆ) ನಾವು ಇದನ್ನು ಮುಂದುವರಿಸುತ್ತೇವೆ.
- ಪೂಲ್ ತುಂಬಿದ ನಂತರ, *ಎಲ್ಲಾ* ಪ್ರಾಮಿಸ್ಗಳಿಗೆ ಕಾಯುವ ಬದಲು, ನಾವು
Promise.race()ಅನ್ನು ಬಳಸಿ ಕೇವಲ *ಒಂದು* ಪೂರ್ಣಗೊಳ್ಳಲು ಕಾಯುತ್ತೇವೆ. - ಒಂದು ಪ್ರಾಮಿಸ್ ಪೂರ್ಣಗೊಂಡಾಗ, ನಾವು ಅದರ ಫಲಿತಾಂಶವನ್ನು ಯೀಲ್ಡ್ (yield) ಮಾಡುತ್ತೇವೆ, ಅದನ್ನು ಪೂಲ್ನಿಂದ ತೆಗೆದುಹಾಕುತ್ತೇವೆ, ಮತ್ತು ಈಗ ಹೊಸದನ್ನು ಸೇರಿಸಲು ಸ್ಥಳಾವಕಾಶವಿದೆ.
- ನಾವು ಮೂಲದಿಂದ ಮುಂದಿನ ಐಟಂ ಅನ್ನು ಎಳೆಯುತ್ತೇವೆ, ಅದರ ಪ್ರೊಸೆಸಿಂಗ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸುತ್ತೇವೆ, ಹೊಸ ಪ್ರಾಮಿಸ್ ಅನ್ನು ಪೂಲ್ಗೆ ಸೇರಿಸುತ್ತೇವೆ, ಮತ್ತು ಚಕ್ರವನ್ನು ಪುನರಾವರ್ತಿಸುತ್ತೇವೆ.
ಇದು ನಿರಂತರ ಹರಿವನ್ನು ಸೃಷ್ಟಿಸುತ್ತದೆ, ಅಲ್ಲಿ ಪ್ರೊಸೆಸ್ ಮಾಡಲು ಡೇಟಾ ಇರುವವರೆಗೂ ಕೆಲಸವು ವ್ಯಾಖ್ಯಾನಿತ ಕನ್ಕರೆನ್ಸಿ ಮಿತಿಯವರೆಗೆ ಯಾವಾಗಲೂ ನಡೆಯುತ್ತಿರುತ್ತದೆ, ನಮ್ಮ ಪ್ರೊಸೆಸಿಂಗ್ ಪೈಪ್ಲೈನ್ ಎಂದಿಗೂ ನಿಷ್ಕ್ರಿಯವಾಗದಂತೆ ಖಚಿತಪಡಿಸುತ್ತದೆ.
`asyncMapConcurrent` ನ ಹಂತ-ಹಂತದ ಅನುಷ್ಠಾನ
ಈ ಯುಟಿಲಿಟಿಯನ್ನು ನಿರ್ಮಿಸೋಣ. ಇದು ಅಸಿಂಕ್ ಜನರೇಟರ್ ಫಂಕ್ಷನ್ ಆಗಿರುತ್ತದೆ, ಇದು ಅಸಿಂಕ್ ಇಟರೇಟರ್ ಪ್ರೋಟೋಕಾಲ್ ಅನ್ನು ಸುಲಭವಾಗಿ ಕಾರ್ಯಗತಗೊಳಿಸಲು ಸಹಾಯ ಮಾಡುತ್ತದೆ.
async function* asyncMapConcurrent(sourceIterator, mapperFn, concurrency = 5) {
const activePromises = new Set();
const source = sourceIterator[Symbol.asyncIterator]();
while (true) {
// 1. Fill the pool up to the concurrency limit
while (activePromises.size < concurrency) {
const { value, done } = await source.next();
if (done) {
// Source iterator is exhausted, break the inner loop
break;
}
const promise = (async () => {
try {
return { result: await mapperFn(value), error: null };
} catch (e) {
return { result: null, error: e };
}
})();
activePromises.add(promise);
// Also, attach a cleanup function to the promise to remove it from the set upon completion.
promise.finally(() => activePromises.delete(promise));
}
// 2. Check if we are done
if (activePromises.size === 0) {
// The source is exhausted and all active promises have finished.
return; // End the generator
}
// 3. Wait for any promise in the pool to finish
const completed = await Promise.race(activePromises);
// 4. Handle the result
if (completed.error) {
// We can decide on an error handling strategy. Here, we re-throw.
throw completed.error;
}
// 5. Yield the successful result
yield completed.result;
}
}
ಅನುಷ್ಠಾನವನ್ನು ವಿಭಜಿಸೋಣ:
- ನಾವು
activePromisesಗಾಗಿSetಅನ್ನು ಬಳಸುತ್ತೇವೆ. ಸೆಟ್ಗಳು ಅನನ್ಯ ಆಬ್ಜೆಕ್ಟ್ಗಳನ್ನು (ಪ್ರಾಮಿಸ್ಗಳಂತಹ) ಸಂಗ್ರಹಿಸಲು ಅನುಕೂಲಕರವಾಗಿವೆ ಮತ್ತು ವೇಗದ ಸೇರ್ಪಡೆ ಮತ್ತು ಅಳಿಸುವಿಕೆಯನ್ನು ಒದಗಿಸುತ್ತವೆ. - ಹೊರಗಿನ
while (true)ಲೂಪ್ ನಾವು ಸ್ಪಷ್ಟವಾಗಿ ನಿರ್ಗಮಿಸುವವರೆಗೂ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಮುಂದುವರಿಸುತ್ತದೆ. - ಒಳಗಿನ
while (activePromises.size < concurrency)ಲೂಪ್ ನಮ್ಮ ವರ್ಕರ್ ಪೂಲ್ ಅನ್ನು ತುಂಬುವ ಜವಾಬ್ದಾರಿಯನ್ನು ಹೊಂದಿದೆ. ಇದು ನಿರಂತರವಾಗಿsourceಇಟರೇಟರ್ನಿಂದ ಎಳೆಯುತ್ತದೆ. - ಮೂಲ ಇಟರೇಟರ್
doneಆದಾಗ, ನಾವು ಹೊಸ ಪ್ರಾಮಿಸ್ಗಳನ್ನು ಸೇರಿಸುವುದನ್ನು ನಿಲ್ಲಿಸುತ್ತೇವೆ. - ಪ್ರತಿ ಹೊಸ ಐಟಂಗೆ, ನಾವು ತಕ್ಷಣವೇ ಅಸಿಂಕ್ IIFE (ತಕ್ಷಣವೇ ಆಹ್ವಾನಿತ ಫಂಕ್ಷನ್ ಎಕ್ಸ್ಪ್ರೆಶನ್) ಅನ್ನು ಕರೆಯುತ್ತೇವೆ. ಇದು
mapperFnಕಾರ್ಯಗತಗೊಳಿಸುವಿಕೆಯನ್ನು ತಕ್ಷಣವೇ ಪ್ರಾರಂಭಿಸುತ್ತದೆ. ಮ್ಯಾಪರ್ನಿಂದ ಸಂಭವನೀಯ ದೋಷಗಳನ್ನು ಸರಾಗವಾಗಿ ನಿಭಾಯಿಸಲು ಮತ್ತು ಸ್ಥಿರವಾದ ಆಬ್ಜೆಕ್ಟ್ ಆಕಾರ{ result, error }ಅನ್ನು ಹಿಂತಿರುಗಿಸಲು ನಾವು ಅದನ್ನು `try...catch` ಬ್ಲಾಕ್ನಲ್ಲಿ ಸುತ್ತುತ್ತೇವೆ. - ನಿರ್ಣಾಯಕವಾಗಿ, ನಾವು
promise.finally(() => activePromises.delete(promise))ಅನ್ನು ಬಳಸುತ್ತೇವೆ. ಪ್ರಾಮಿಸ್ ರಿಸಾಲ್ವ್ ಆಗಲಿ ಅಥವಾ ರಿಜೆಕ್ಟ್ ಆಗಲಿ, ಅದು ನಮ್ಮ ಸಕ್ರಿಯ ಸೆಟ್ನಿಂದ ತೆಗೆದುಹಾಕಲ್ಪಡುತ್ತದೆ, ಹೊಸ ಕೆಲಸಕ್ಕೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. `Promise.race` ನಂತರ ಪ್ರಾಮಿಸ್ ಅನ್ನು ಕೈಯಾರೆ ಹುಡುಕಿ ತೆಗೆದುಹಾಕಲು ಪ್ರಯತ್ನಿಸುವುದಕ್ಕಿಂತ ಇದು ಸ್ವಚ್ಛವಾದ ವಿಧಾನವಾಗಿದೆ. Promise.race(activePromises)ಕನ್ಕರೆನ್ಸಿಯ ಹೃದಯಭಾಗವಾಗಿದೆ. ಇದು ಸೆಟ್ನಲ್ಲಿರುವ *ಮೊದಲ* ಪ್ರಾಮಿಸ್ ರಿಸಾಲ್ವ್ ಅಥವಾ ರಿಜೆಕ್ಟ್ ಆದ ತಕ್ಷಣ ರಿಸಾಲ್ವ್ ಅಥವಾ ರಿಜೆಕ್ಟ್ ಆಗುವ ಹೊಸ ಪ್ರಾಮಿಸ್ ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ.- ಒಂದು ಪ್ರಾಮಿಸ್ ಪೂರ್ಣಗೊಂಡ ನಂತರ, ನಾವು ನಮ್ಮ ಸುತ್ತಿದ ಫಲಿತಾಂಶವನ್ನು ಪರಿಶೀಲಿಸುತ್ತೇವೆ. ದೋಷವಿದ್ದರೆ, ನಾವು ಅದನ್ನು ಥ್ರೋ ಮಾಡುತ್ತೇವೆ, ಜನರೇಟರ್ ಅನ್ನು ಕೊನೆಗೊಳಿಸುತ್ತೇವೆ (ಫೇಲ್-ಫಾಸ್ಟ್ ತಂತ್ರ). ಅದು ಯಶಸ್ವಿಯಾದರೆ, ನಾವು ನಮ್ಮ
asyncMapConcurrentಜನರೇಟರ್ನ ಗ್ರಾಹಕರಿಗೆ ಫಲಿತಾಂಶವನ್ನುyieldಮಾಡುತ್ತೇವೆ. - ಅಂತಿಮ ನಿರ್ಗಮನ ಸ್ಥಿತಿಯೆಂದರೆ, ಮೂಲವು ಖಾಲಿಯಾದಾಗ ಮತ್ತು
activePromisesಸೆಟ್ ಖಾಲಿಯಾದಾಗ. ಈ ಹಂತದಲ್ಲಿ, ಹೊರಗಿನ ಲೂಪ್ ಸ್ಥಿತಿactivePromises.size === 0ಪೂರೈಸಲ್ಪಡುತ್ತದೆ, ಮತ್ತು ನಾವುreturnಮಾಡುತ್ತೇವೆ, ಇದು ನಮ್ಮ ಅಸಿಂಕ್ ಜನರೇಟರ್ನ ಅಂತ್ಯವನ್ನು ಸೂಚಿಸುತ್ತದೆ.
ಪ್ರಾಯೋಗಿಕ ಬಳಕೆಯ ಪ್ರಕರಣಗಳು ಮತ್ತು ಜಾಗತಿಕ ಉದಾಹರಣೆಗಳು
ಈ ಪ್ಯಾಟರ್ನ್ ಕೇವಲ ಒಂದು ಶೈಕ್ಷಣಿಕ ವ್ಯಾಯಾಮವಲ್ಲ. ಇದು ನೈಜ-ಪ್ರಪಂಚದ ಅಪ್ಲಿಕೇಶನ್ಗಳ ಮೇಲೆ ಗಂಭೀರ ಪರಿಣಾಮಗಳನ್ನು ಬೀರುತ್ತದೆ. ಕೆಲವು ಸನ್ನಿವೇಶಗಳನ್ನು ಅನ್ವೇಷಿಸೋಣ.
ಬಳಕೆಯ ಪ್ರಕರಣ 1: ಅಧಿಕ-ಥ್ರೋಪುಟ್ API ಸಂವಹನಗಳು
ಸನ್ನಿವೇಶ: ನೀವು ಜಾಗತಿಕ ಇ-ಕಾಮರ್ಸ್ ಪ್ಲಾಟ್ಫಾರ್ಮ್ಗಾಗಿ ಒಂದು ಸೇವೆಯನ್ನು ನಿರ್ಮಿಸುತ್ತಿದ್ದೀರಿ ಎಂದು ಕಲ್ಪಿಸಿಕೊಳ್ಳಿ. ನಿಮ್ಮ ಬಳಿ 50,000 ಉತ್ಪನ್ನ ID ಗಳ ಪಟ್ಟಿ ಇದೆ, ಮತ್ತು ಪ್ರತಿಯೊಂದಕ್ಕೂ, ನಿರ್ದಿಷ್ಟ ಪ್ರದೇಶದ ಇತ್ತೀಚಿನ ಬೆಲೆಯನ್ನು ಪಡೆಯಲು ನೀವು ಪ್ರೈಸಿಂಗ್ API ಅನ್ನು ಕರೆಯಬೇಕಾಗುತ್ತದೆ.
ಅನುಕ್ರಮದ ಅಡಚಣೆ:
async function updateAllPrices(productIds) {
const startTime = Date.now();
for (const id of productIds) {
await fetchPrice(id); // Assume this takes ~200ms
}
console.log(`Total time: ${(Date.now() - startTime) / 1000}s`);
}
// Estimated time for 50,000 products: 50,000 * 0.2s = 10,000 seconds (~2.7 hours!)
ಕನ್ಕರೆಂಟ್ ಪರಿಹಾರ:
// Helper function to simulate a network request
function fetchPrice(productId) {
return new Promise(resolve => {
setTimeout(() => {
const price = (Math.random() * 100).toFixed(2);
console.log(`Fetched price for ${productId}: $${price}`);
resolve({ productId, price });
}, 200 + Math.random() * 100); // Simulate variable network latency
});
}
async function updateAllPricesConcurrently() {
const productIds = Array.from({ length: 50 }, (_, i) => `product-${i + 1}`);
const idIterator = productIds.values(); // Create a simple iterator
// Use our concurrent mapper with a concurrency of 10
const priceStream = asyncMapConcurrent(idIterator, fetchPrice, 10);
const startTime = Date.now();
for await (const priceData of priceStream) {
// Here you would save the priceData to your database
// console.log(`Processed: ${priceData.productId}`);
}
console.log(`Concurrent total time: ${(Date.now() - startTime) / 1000}s`);
}
updateAllPricesConcurrently();
// Expected output: A flurry of "Fetched price..." logs, and a total time
// that is roughly (Total Items / Concurrency) * Avg Time per Item.
// For 50 items at 200ms with concurrency 10: (50/10) * 0.2s = ~1 second (plus latency variance)
// For 50,000 items: (50000/10) * 0.2s = 1000 seconds (~16.7 minutes). A huge improvement!
ಜಾಗತಿಕ ಪರಿಗಣನೆ: API ದರ ಮಿತಿಗಳ ಬಗ್ಗೆ ಜಾಗರೂಕರಾಗಿರಿ. ಕನ್ಕರೆನ್ಸಿ ಮಟ್ಟವನ್ನು ತುಂಬಾ ಹೆಚ್ಚಾಗಿ ಹೊಂದಿಸುವುದರಿಂದ ನಿಮ್ಮ IP ವಿಳಾಸವನ್ನು ನಿರ್ಬಂಧಿಸಬಹುದು. ಅನೇಕ ಸಾರ್ವಜನಿಕ API ಗಳಿಗಾಗಿ 5-10 ರ ಕನ್ಕರೆನ್ಸಿ ಮಟ್ಟವು ಸಾಮಾನ್ಯವಾಗಿ ಸುರಕ್ಷಿತ ಆರಂಭಿಕ ಹಂತವಾಗಿದೆ.
ಬಳಕೆಯ ಪ್ರಕರಣ 2: Node.js ನಲ್ಲಿ ಪ್ಯಾರಲಲ್ ಫೈಲ್ ಪ್ರೊಸೆಸಿಂಗ್
ಸನ್ನಿವೇಶ: ನೀವು ಬೃಹತ್ ಚಿತ್ರ ಅಪ್ಲೋಡ್ಗಳನ್ನು ಸ್ವೀಕರಿಸುವ ಕಂಟೆಂಟ್ ಮ್ಯಾನೇಜ್ಮೆಂಟ್ ಸಿಸ್ಟಮ್ (CMS) ಅನ್ನು ನಿರ್ಮಿಸುತ್ತಿದ್ದೀರಿ. ಪ್ರತಿ ಅಪ್ಲೋಡ್ ಮಾಡಿದ ಚಿತ್ರಕ್ಕಾಗಿ, ನೀವು ಮೂರು ವಿಭಿನ್ನ ಥಂಬ್ನೇಲ್ ಗಾತ್ರಗಳನ್ನು ರಚಿಸಬೇಕು ಮತ್ತು ಅವುಗಳನ್ನು AWS S3 ಅಥವಾ Google Cloud Storage ನಂತಹ ಕ್ಲೌಡ್ ಸ್ಟೋರೇಜ್ ಪ್ರೊವೈಡರ್ಗೆ ಅಪ್ಲೋಡ್ ಮಾಡಬೇಕು.
ಅನುಕ್ರಮದ ಅಡಚಣೆ: ಒಂದು ಚಿತ್ರವನ್ನು ಸಂಪೂರ್ಣವಾಗಿ ಪ್ರೊಸೆಸ್ ಮಾಡಿ (ಓದಿ, ಮೂರು ಬಾರಿ ಮರುಗಾತ್ರಗೊಳಿಸಿ, ಮೂರು ಬಾರಿ ಅಪ್ಲೋಡ್ ಮಾಡಿ) ಮುಂದಿನದನ್ನು ಪ್ರಾರಂಭಿಸುವ ಮೊದಲು, ಇದು ಅತ್ಯಂತ ಅಸಮರ್ಥವಾಗಿದೆ. ಇದು CPU (ಅಪ್ಲೋಡ್ಗಳಿಗಾಗಿ I/O ಕಾಯುವಿಕೆಯ ಸಮಯದಲ್ಲಿ) ಮತ್ತು ನೆಟ್ವರ್ಕ್ (CPU-ಬೌಂಡ್ ಮರುಗಾತ್ರಗೊಳಿಸುವಿಕೆಯ ಸಮಯದಲ್ಲಿ) ಎರಡನ್ನೂ ಕಡಿಮೆ ಬಳಸಿಕೊಳ್ಳುತ್ತದೆ.
ಕನ್ಕರೆಂಟ್ ಪರಿಹಾರ:
const fs = require('fs/promises');
const path = require('path');
// Assume 'sharp' for resizing and 'aws-sdk' for uploading are available
async function processImage(filePath) {
console.log(`Processing ${path.basename(filePath)}...`);
const imageBuffer = await fs.readFile(filePath);
const sizes = [{w: 100, h: 100}, {w: 300, h: 300}, {w: 600, h: 600}];
const uploadTasks = sizes.map(async (size) => {
const thumbnailBuffer = await sharp(imageBuffer).resize(size.w, size.h).toBuffer();
return uploadToCloud(thumbnailBuffer, `thumb_${size.w}_${path.basename(filePath)}`);
});
await Promise.all(uploadTasks);
console.log(`Finished ${path.basename(filePath)}`);
return { source: filePath, status: 'processed' };
}
async function run() {
const imageDir = './uploads';
const files = await fs.readdir(imageDir);
const filePaths = files.map(f => path.join(imageDir, f));
// Get the number of CPU cores to set a sensible concurrency level
const concurrency = require('os').cpus().length;
const processingStream = asyncMapConcurrent(filePaths.values(), processImage, concurrency);
for await (const result of processingStream) {
console.log(result);
}
}
ಈ ಉದಾಹರಣೆಯಲ್ಲಿ, ನಾವು ಲಭ್ಯವಿರುವ CPU ಕೋರ್ಗಳ ಸಂಖ್ಯೆಗೆ ಕನ್ಕರೆನ್ಸಿ ಮಟ್ಟವನ್ನು ಹೊಂದಿಸಿದ್ದೇವೆ. ಇದು CPU-ಬೌಂಡ್ ಕಾರ್ಯಗಳಿಗೆ ಸಾಮಾನ್ಯವಾದ ಹ್ಯೂರಿಸ್ಟಿಕ್ ಆಗಿದೆ, ಸಿಸ್ಟಮ್ ಸಮಾನಾಂತರವಾಗಿ ನಿಭಾಯಿಸುವುದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಕೆಲಸದಿಂದ ತುಂಬಿಹೋಗದಂತೆ ಖಚಿತಪಡಿಸುತ್ತದೆ.
ಕಾರ್ಯಕ್ಷಮತೆಯ ಪರಿಗಣನೆಗಳು ಮತ್ತು ಉತ್ತಮ ಅಭ್ಯಾಸಗಳು
ಕನ್ಕರೆನ್ಸಿಯನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸುವುದು ಶಕ್ತಿಯುತವಾಗಿದೆ, ಆದರೆ ಇದು ಬೆಳ್ಳಿಯ ಗುಂಡಲ್ಲ. ಇದು ಸಂಕೀರ್ಣತೆಯನ್ನು ಪರಿಚಯಿಸುತ್ತದೆ ಮತ್ತು ಎಚ್ಚರಿಕೆಯ ಪರಿಗಣನೆಯ ಅಗತ್ಯವಿರುತ್ತದೆ.
ಸರಿಯಾದ ಕನ್ಕರೆನ್ಸಿ ಮಟ್ಟವನ್ನು ಆರಿಸುವುದು
ಸೂಕ್ತವಾದ ಕನ್ಕರೆನ್ಸಿ ಮಟ್ಟವು ಯಾವಾಗಲೂ "ಸಾಧ್ಯವಾದಷ್ಟು ಹೆಚ್ಚು" ಅಲ್ಲ. ಇದು ಕಾರ್ಯದ ಸ್ವರೂಪವನ್ನು ಅವಲಂಬಿಸಿರುತ್ತದೆ:
- I/O-ಬೌಂಡ್ ಕಾರ್ಯಗಳು (ಉದಾ., API ಕರೆಗಳು, ಡೇಟಾಬೇಸ್ ಪ್ರಶ್ನೆಗಳು): ನಿಮ್ಮ ಕೋಡ್ ತನ್ನ ಹೆಚ್ಚಿನ ಸಮಯವನ್ನು ಬಾಹ್ಯ ಸಂಪನ್ಮೂಲಗಳಿಗಾಗಿ ಕಾಯುವುದರಲ್ಲಿ ಕಳೆಯುತ್ತದೆ. ನೀವು ಸಾಮಾನ್ಯವಾಗಿ ಹೆಚ್ಚಿನ ಕನ್ಕರೆನ್ಸಿ ಮಟ್ಟವನ್ನು ಬಳಸಬಹುದು (ಉದಾ., 10, 50, ಅಥವಾ 100), ಮುಖ್ಯವಾಗಿ ಬಾಹ್ಯ ಸೇವೆಯ ದರ ಮಿತಿಗಳು ಮತ್ತು ನಿಮ್ಮ ಸ್ವಂತ ನೆಟ್ವರ್ಕ್ ಬ್ಯಾಂಡ್ವಿಡ್ತ್ನಿಂದ ಸೀಮಿತವಾಗಿರುತ್ತದೆ.
- CPU-ಬೌಂಡ್ ಕಾರ್ಯಗಳು (ಉದಾ., ಚಿತ್ರ ಸಂಸ್ಕರಣೆ, ಸಂಕೀರ್ಣ ಗಣನೆಗಳು, ಎನ್ಕ್ರಿಪ್ಶನ್): ನಿಮ್ಮ ಕೋಡ್ ನಿಮ್ಮ ಯಂತ್ರದ ಪ್ರೊಸೆಸಿಂಗ್ ಶಕ್ತಿಯಿಂದ ಸೀಮಿತವಾಗಿದೆ. ಲಭ್ಯವಿರುವ CPU ಕೋರ್ಗಳ ಸಂಖ್ಯೆಗೆ (ಬ್ರೌಸರ್ಗಳಲ್ಲಿ
navigator.hardwareConcurrency, Node.js ನಲ್ಲಿos.cpus().length) ಕನ್ಕರೆನ್ಸಿ ಮಟ್ಟವನ್ನು ಹೊಂದಿಸುವುದು ಉತ್ತಮ ಆರಂಭಿಕ ಹಂತವಾಗಿದೆ. ಅದನ್ನು ಹೆಚ್ಚು ಹೆಚ್ಚಾಗಿ ಹೊಂದಿಸುವುದರಿಂದ ಅತಿಯಾದ ಸಂದರ್ಭ ಬದಲಾವಣೆಗೆ ಕಾರಣವಾಗಬಹುದು, ಇದು ವಾಸ್ತವವಾಗಿ ಕಾರ್ಯಕ್ಷಮತೆಯನ್ನು ನಿಧಾನಗೊಳಿಸಬಹುದು.
ಕನ್ಕರೆಂಟ್ ಸ್ಟ್ರೀಮ್ಗಳಲ್ಲಿ ದೋಷ ನಿರ್ವಹಣೆ
ನಮ್ಮ ಪ್ರಸ್ತುತ ಅನುಷ್ಠಾನವು "ಫೇಲ್-ಫಾಸ್ಟ್" ತಂತ್ರವನ್ನು ಹೊಂದಿದೆ. ಯಾವುದೇ mapperFn ದೋಷವನ್ನು ಎಸೆದರೆ, ಸಂಪೂರ್ಣ ಸ್ಟ್ರೀಮ್ ಕೊನೆಗೊಳ್ಳುತ್ತದೆ. ಇದು ಅಪೇಕ್ಷಣೀಯವಾಗಿರಬಹುದು, ಆದರೆ ಆಗಾಗ್ಗೆ ನೀವು ಇತರ ಐಟಂಗಳನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡುವುದನ್ನು ಮುಂದುವರಿಸಲು ಬಯಸುತ್ತೀರಿ. ನೀವು ವೈಫಲ್ಯಗಳನ್ನು ಸಂಗ್ರಹಿಸಲು ಮತ್ತು ಅವುಗಳನ್ನು ಪ್ರತ್ಯೇಕವಾಗಿ ಯೀಲ್ಡ್ ಮಾಡಲು ಹೆಲ್ಪರ್ ಅನ್ನು ಮಾರ್ಪಡಿಸಬಹುದು, ಅಥವಾ ಅವುಗಳನ್ನು ಲಾಗ್ ಮಾಡಿ ಮುಂದುವರಿಯಬಹುದು.
ಹೆಚ್ಚು ದೃಢವಾದ ಆವೃತ್ತಿಯು ಹೀಗಿರಬಹುದು:
// Modified part of the generator
const completed = await Promise.race(activePromises);
if (completed.error) {
console.error("An error occurred in a concurrent task:", completed.error);
// We don't throw, we just continue the loop to wait for the next promise.
// We could also yield the error for the consumer to handle.
// yield { error: completed.error };
} else {
yield completed.result;
}
ಬ್ಯಾಕ್ಪ್ರೆಶರ್ ನಿರ್ವಹಣೆ
ಬ್ಯಾಕ್ಪ್ರೆಶರ್ ಎಂಬುದು ಸ್ಟ್ರೀಮ್ ಪ್ರೊಸೆಸಿಂಗ್ನಲ್ಲಿ ಒಂದು ನಿರ್ಣಾಯಕ ಪರಿಕಲ್ಪನೆಯಾಗಿದೆ. ವೇಗವಾಗಿ ಉತ್ಪಾದಿಸುವ ಡೇಟಾ ಮೂಲವು ನಿಧಾನಗತಿಯ ಗ್ರಾಹಕವನ್ನು ಮುಳುಗಿಸಿದಾಗ ಇದು ಸಂಭವಿಸುತ್ತದೆ. ನಮ್ಮ ಪುಲ್-ಆಧಾರಿತ ಇಟರೇಟರ್ ವಿಧಾನದ ಸೌಂದರ್ಯವೆಂದರೆ ಅದು ಬ್ಯಾಕ್ಪ್ರೆಶರ್ ಅನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ನಿಭಾಯಿಸುತ್ತದೆ. ನಮ್ಮ asyncMapConcurrent ಫಂಕ್ಷನ್ activePromises ಪೂಲ್ನಲ್ಲಿ ಖಾಲಿ ಸ್ಲಾಟ್ ಇದ್ದಾಗ ಮಾತ್ರ sourceIterator ನಿಂದ ಹೊಸ ಐಟಂ ಅನ್ನು ಎಳೆಯುತ್ತದೆ. ನಮ್ಮ ಸ್ಟ್ರೀಮ್ನ ಗ್ರಾಹಕರು ಯೀಲ್ಡ್ ಮಾಡಿದ ಫಲಿತಾಂಶಗಳನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡಲು ನಿಧಾನವಾಗಿದ್ದರೆ, ನಮ್ಮ ಜನರೇಟರ್ ವಿರಾಮಗೊಳಿಸುತ್ತದೆ, ಮತ್ತು ಪ್ರತಿಯಾಗಿ, ಮೂಲದಿಂದ ಎಳೆಯುವುದನ್ನು ನಿಲ್ಲಿಸುತ್ತದೆ. ಇದು ಅಪಾರ ಸಂಖ್ಯೆಯ ಪ್ರೊಸೆಸ್ ಮಾಡದ ಐಟಂಗಳನ್ನು ಬಫರ್ ಮಾಡುವುದರಿಂದ ಮೆಮೊರಿ ಖಾಲಿಯಾಗುವುದನ್ನು ತಡೆಯುತ್ತದೆ.
ಫಲಿತಾಂಶಗಳ ಕ್ರಮ
ಕನ್ಕರೆಂಟ್ ಪ್ರೊಸೆಸಿಂಗ್ನ ಒಂದು ಪ್ರಮುಖ ಪರಿಣಾಮವೆಂದರೆ, ಫಲಿತಾಂಶಗಳು ಪೂರ್ಣಗೊಂಡ ಕ್ರಮದಲ್ಲಿ ಯೀಲ್ಡ್ ಆಗುತ್ತವೆ, ಮೂಲ ಡೇಟಾದ ಮೂಲ ಕ್ರಮದಲ್ಲಿ ಅಲ್ಲ. ನಿಮ್ಮ ಮೂಲ ಪಟ್ಟಿಯಲ್ಲಿ ಮೂರನೇ ಐಟಂ ಪ್ರೊಸೆಸ್ ಮಾಡಲು ತುಂಬಾ ವೇಗವಾಗಿದ್ದರೆ ಮತ್ತು ಮೊದಲನೆಯದು ತುಂಬಾ ನಿಧಾನವಾಗಿದ್ದರೆ, ನೀವು ಮೂರನೇ ಐಟಂನ ಫಲಿತಾಂಶವನ್ನು ಮೊದಲು ಸ್ವೀಕರಿಸುತ್ತೀರಿ. ಮೂಲ ಕ್ರಮವನ್ನು ನಿರ್ವಹಿಸುವುದು ಒಂದು ಅವಶ್ಯಕತೆಯಾಗಿದ್ದರೆ, ನೀವು ಬಫರಿಂಗ್ ಮತ್ತು ಫಲಿತಾಂಶಗಳನ್ನು ಮರು-ವಿಂಗಡಿಸುವುದನ್ನು ಒಳಗೊಂಡಿರುವ ಹೆಚ್ಚು ಸಂಕೀರ್ಣವಾದ ಪರಿಹಾರವನ್ನು ನಿರ್ಮಿಸಬೇಕಾಗುತ್ತದೆ, ಇದು ಗಮನಾರ್ಹ ಮೆಮೊರಿ ಓವರ್ಹೆಡ್ ಅನ್ನು ಸೇರಿಸುತ್ತದೆ.
ಭವಿಷ್ಯ: ನೇಟಿವ್ ಅನುಷ್ಠಾನಗಳು ಮತ್ತು ಪರಿಸರ ವ್ಯವಸ್ಥೆ
ನಮ್ಮದೇ ಆದ ಕನ್ಕರೆಂಟ್ ಹೆಲ್ಪರ್ ಅನ್ನು ನಿರ್ಮಿಸುವುದು ಒಂದು ಅದ್ಭುತ ಕಲಿಕೆಯ ಅನುಭವವಾಗಿದ್ದರೂ, ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಪರಿಸರ ವ್ಯವಸ್ಥೆಯು ಈ ಕಾರ್ಯಗಳಿಗಾಗಿ ದೃಢವಾದ, ಪರೀಕ್ಷಿತ ಲೈಬ್ರರಿಗಳನ್ನು ಒದಗಿಸುತ್ತದೆ.
- p-map: ನಮ್ಮ
asyncMapConcurrentಮಾಡುವಂತೆಯೇ ಮಾಡುವ ಜನಪ್ರಿಯ ಮತ್ತು ಹಗುರವಾದ ಲೈಬ್ರರಿ, ಆದರೆ ಹೆಚ್ಚಿನ ವೈಶಿಷ್ಟ್ಯಗಳು ಮತ್ತು ಆಪ್ಟಿಮೈಸೇಶನ್ಗಳೊಂದಿಗೆ. - RxJS: ಆಬ್ಸರ್ವೇಬಲ್ಗಳೊಂದಿಗೆ ರಿಯಾಕ್ಟಿವ್ ಪ್ರೋಗ್ರಾಮಿಂಗ್ಗಾಗಿ ಒಂದು ಶಕ್ತಿಯುತ ಲೈಬ್ರರಿ, ಇದು ಸೂಪರ್-ಪವರ್ಡ್ ಸ್ಟ್ರೀಮ್ಗಳಂತಿವೆ. ಇದು
mergeMapನಂತಹ ಆಪರೇಟರ್ಗಳನ್ನು ಹೊಂದಿದೆ, ಅದನ್ನು ಕನ್ಕರೆಂಟ್ ಎಕ್ಸಿಕ್ಯೂಶನ್ಗಾಗಿ ಕಾನ್ಫಿಗರ್ ಮಾಡಬಹುದು. - Node.js ಸ್ಟ್ರೀಮ್ಸ್ API: ಸರ್ವರ್-ಸೈಡ್ ಅಪ್ಲಿಕೇಶನ್ಗಳಿಗಾಗಿ, Node.js ಸ್ಟ್ರೀಮ್ಗಳು ಶಕ್ತಿಯುತ, ಬ್ಯಾಕ್ಪ್ರೆಶರ್-ಅರಿವಿನ ಪೈಪ್ಲೈನ್ಗಳನ್ನು ನೀಡುತ್ತವೆ, ಆದರೂ ಅವುಗಳ API ಮಾಸ್ಟರಿಂಗ್ ಮಾಡಲು ಹೆಚ್ಚು ಸಂಕೀರ್ಣವಾಗಿರಬಹುದು.
ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಭಾಷೆಯು ವಿಕಸನಗೊಂಡಂತೆ, ನಾವು ಒಂದು ದಿನ ನೇಟಿವ್ Iterator.prototype.mapConcurrent ಅಥವಾ ಅಂತಹುದೇ ಯುಟಿಲಿಟಿಯನ್ನು ನೋಡಬಹುದು. TC39 ಸಮಿತಿಯಲ್ಲಿನ ಚರ್ಚೆಗಳು ಡೆವಲಪರ್ಗಳಿಗೆ ಡೇಟಾ ಸ್ಟ್ರೀಮ್ಗಳನ್ನು ನಿಭಾಯಿಸಲು ಹೆಚ್ಚು ಶಕ್ತಿಯುತ ಮತ್ತು ದಕ್ಷತಾಶಾಸ್ತ್ರದ ಸಾಧನಗಳನ್ನು ಒದಗಿಸುವ ಸ್ಪಷ್ಟ ಪ್ರವೃತ್ತಿಯನ್ನು ತೋರಿಸುತ್ತವೆ. ಈ ಲೇಖನದಲ್ಲಿ ನಾವು ಮಾಡಿದಂತೆ, ಆಧಾರವಾಗಿರುವ ತತ್ವಗಳನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು, ಈ ಸಾಧನಗಳು ಬಂದಾಗ ಅವುಗಳನ್ನು ಪರಿಣಾಮಕಾರಿಯಾಗಿ ಬಳಸಲು ನೀವು ಸಿದ್ಧರಾಗಿರುವುದನ್ನು ಖಚಿತಪಡಿಸುತ್ತದೆ.
ತೀರ್ಮಾನ
ನಾವು ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಇಟರೇಟರ್ಗಳ ಮೂಲಭೂತ ಅಂಶಗಳಿಂದ ಕನ್ಕರೆಂಟ್ ಸ್ಟ್ರೀಮ್ ಪ್ರೊಸೆಸಿಂಗ್ ಯುಟಿಲಿಟಿಯ ಸಂಕೀರ್ಣ ವಾಸ್ತುಶಿಲ್ಪದವರೆಗೆ ಪ್ರಯಾಣಿಸಿದ್ದೇವೆ. ಈ ಪ್ರಯಾಣವು ಆಧುನಿಕ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಅಭಿವೃದ್ಧಿಯ ಬಗ್ಗೆ ಒಂದು ಶಕ್ತಿಯುತ ಸತ್ಯವನ್ನು ಬಹಿರಂಗಪಡಿಸುತ್ತದೆ: ಕಾರ್ಯಕ್ಷಮತೆಯು ಕೇವಲ ಒಂದು ಫಂಕ್ಷನ್ ಅನ್ನು ಆಪ್ಟಿಮೈಜ್ ಮಾಡುವುದರ ಬಗ್ಗೆ ಅಲ್ಲ, ಆದರೆ ಸಮರ್ಥ ಡೇಟಾ ಹರಿವುಗಳನ್ನು ರೂಪಿಸುವುದರ ಬಗ್ಗೆ.
ಪ್ರಮುಖ ಅಂಶಗಳು:
- ಪ್ರಮಾಣಿತ ಇಟರೇಟರ್ ಹೆಲ್ಪರ್ಗಳು ಸಿಂಕ್ರೊನಸ್ ಮತ್ತು ಅನುಕ್ರಮವಾಗಿವೆ.
- ಅಸಿಂಕ್ರೊನಸ್ ಇಟರೇಟರ್ಗಳು ಮತ್ತು
for await...ofಡೇಟಾ ಸ್ಟ್ರೀಮ್ಗಳನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡಲು ಸ್ವಚ್ಛವಾದ ಸಿಂಟ್ಯಾಕ್ಸ್ ಅನ್ನು ಒದಗಿಸುತ್ತವೆ ಆದರೆ ಡಿಫಾಲ್ಟ್ ಆಗಿ ಅನುಕ್ರಮವಾಗಿರುತ್ತವೆ. - I/O-ಬೌಂಡ್ ಕಾರ್ಯಗಳಿಗಾಗಿ ನಿಜವಾದ ಕಾರ್ಯಕ್ಷಮತೆಯ ಲಾಭಗಳು ಕನ್ಕರೆನ್ಸಿಯಿಂದ ಬರುತ್ತವೆ - ಒಂದೇ ಸಮಯದಲ್ಲಿ ಅನೇಕ ಐಟಂಗಳನ್ನು ಪ್ರೊಸೆಸ್ ಮಾಡುವುದು.
Promise.raceನೊಂದಿಗೆ ನಿರ್ವಹಿಸಲಾದ ಪ್ರಾಮಿಸ್ಗಳ "ವರ್ಕರ್ ಪೂಲ್" ಕನ್ಕರೆಂಟ್ ಮ್ಯಾಪರ್ಗಳನ್ನು ನಿರ್ಮಿಸಲು ಪರಿಣಾಮಕಾರಿ ಮಾದರಿಯಾಗಿದೆ.- ಈ ಪ್ಯಾಟರ್ನ್ ಅಂತರ್ಗತ ಬ್ಯಾಕ್ಪ್ರೆಶರ್ ನಿರ್ವಹಣೆಯನ್ನು ಒದಗಿಸುತ್ತದೆ, ಮೆಮೊರಿ ಓವರ್ಲೋಡ್ ಅನ್ನು ತಡೆಯುತ್ತದೆ.
- ಪ್ಯಾರಲಲ್ ಪ್ರೊಸೆಸಿಂಗ್ ಅನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸುವಾಗ ಯಾವಾಗಲೂ ಕನ್ಕರೆನ್ಸಿ ಮಿತಿಗಳು, ದೋಷ ನಿರ್ವಹಣೆ, ಮತ್ತು ಫಲಿತಾಂಶದ ಕ್ರಮದ ಬಗ್ಗೆ ಜಾಗರೂಕರಾಗಿರಿ.
ಸರಳ ಲೂಪ್ಗಳನ್ನು ಮೀರಿ ಈ ಸುಧಾರಿತ, ಕನ್ಕರೆಂಟ್ ಸ್ಟ್ರೀಮಿಂಗ್ ಪ್ಯಾಟರ್ನ್ಗಳನ್ನು ಅಳವಡಿಸಿಕೊಳ್ಳುವ ಮೂಲಕ, ನೀವು ಹೆಚ್ಚು ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ಸ್ಕೇಲೆಬಲ್ ಮಾತ್ರವಲ್ಲದೆ, ಭಾರಿ ಡೇಟಾ ಪ್ರೊಸೆಸಿಂಗ್ ಸವಾಲುಗಳ ಮುಖಾಂತರ ಹೆಚ್ಚು ಸ್ಥಿತಿಸ್ಥಾಪಕವಾಗಿರುವ ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್ ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ನಿರ್ಮಿಸಬಹುದು. ಡೇಟಾ ಅಡಚಣೆಗಳನ್ನು ಅಧಿಕ-ವೇಗದ ಪೈಪ್ಲೈನ್ಗಳಾಗಿ ಪರಿವರ್ತಿಸುವ ಜ್ಞಾನವನ್ನು ನೀವು ಈಗ ಹೊಂದಿದ್ದೀರಿ, ಇದು ಇಂದಿನ ಡೇಟಾ-ಚಾಲಿತ ಜಗತ್ತಿನಲ್ಲಿ ಯಾವುದೇ ಡೆವಲಪರ್ಗೆ ನಿರ್ಣಾಯಕ ಕೌಶಲ್ಯವಾಗಿದೆ.