Xử lý theo lô (batch processing)

Xử lý theo lô (batch processing)

Những mẩu tin ngắn hàng ngày dành cho bạn
  • Quá ghê ghớm, Codeium - vốn được biết đến như một đối thủ của Github Copilot, khi nó cho người dùng dùng miễn phí không giới hạn. Mới đây họ giới thiệu thêm Windsurf Editor - không chỉ còn là VSCode Extentions nữa mà là một trình Editor luôn - cạnh tranh trực tiếp với Cursor. Và điểm chính là nó... hoàn toàn miễn phí 🫣.

    » Xem thêm
  • Tin vui đầu này, Github Copilot đã chính thức có bản Free cho tất cả mọi người.

    Github Copilot là một trợ lý AI code cùng chúng ta, nó có thể tự động hoàn thành mã, trò chuyện hoặc sửa lỗi. Hiện đang hỗ trợ nhiều trình soạn thảo, IDE phổ biến như VSCode, JetBrains, XCode...

    Phiên bản miễn phí đang bị giới hạn 2000 Suggestions, và khoảng 50 requests đến tính năng Chat hàng tháng. Sau đó bạn có thể nâng cấp lên các phiên bản cao cấp hơn với giá từ 10$.

    Theo đánh giá của mình thì Copilot rất tốt và xứng đáng trong tầm giá, đang dùng hàng ngày 😄

    » Xem thêm
  • Có một nghiên cứu khá thú vị mà mình rút ra được đó là: "Người dùng chẳng bao giờ chịu đọc những thứ mà họ không muốn". (Khẳng định chắc nịch thế chứ, đa số thì đúng hơn 😅).

    Không tin á, mình dám cá chắc là bạn đã không ít lần gặp trường hợp nhấn vào một nút gì đó liên tục mà nó không phản hồi, nhưng thật ra là nó có báo một dòng lỗi ở đâu đó. Hoặc nhập xong xuôi các thứ rồi đến lúc nhấn nút gửi thì không gửi được. Bực mình mới kéo lên trên hoặc xuống dưới đọc thì à... hoá ra là phải làm thêm bước này bước kia đúng không?

    Cũng chẳng đâu xa ngay như blog của mình đây. Nghĩ là mọi người ai mà quan tâm đến blog sẽ nhấn vào nút "Cho phép bật thông báo" ở ngay phía dưới bài viết. Nhưng sự thật là chẳng ai thèm bấm cả. Có phải là họ không muốn nhận không? Chắc là không! Mình nghĩ do họ không đọc đến dòng đó thôi.

    Bằng chứng là chỉ khi cho hiện một thông báo chiếm một nửa cái màn hình, hoặc bất ngờ hiện ra để gây sự chú ý thì họ mới thật sự đọc và dĩ nhiên thu hút thêm vài người đăng ký - điều mà trước đó chẳng bao giờ đạt được.

    » Xem thêm

Vấn đề

Có những chủ đề thành thật mà nói là rất khó để viết. Khó không phải vì nó khó (😀???), mà là do không biết trình bày làm sao cho nó thành một bài viết có hệ thống mà ai cũng dễ đọc, dễ hiểu. Từ khi biết đến câu nói "Bạn chỉ thực sự hiểu vấn đề nếu giải thích được cho người khác hiểu" của một vị tiền bối, tôi như bị ám ảnh và tuân theo như một mệnh lệnh trong sứ mệnh truyền đạt nội dung qua con chữ. Trong danh sách những chủ đề nháp, tôi đã bỏ lỡ biết bao nhiêu chỉ vì không đạt được mục đích đó. Nhưng không sao, có lẽ dần dần mình sẽ "thực sự hiểu vấn đề" ra thôi.

Xử lý theo lô (batch processing) cũng vậy, một vấn đề hơi rộng và có phần trừu tượng. Nhưng tôi tin chắc rằng một khi hình dung ra nó là gì thì quá trình xử lý thông tin sẽ diễn ra nhanh và mạnh mẽ hơn trong bất kỳ hệ thống nào. Do đó hy vọng rằng qua bài viết này sẽ giúp bạn đọc nhận ra những lợi ích đó!

Bây giờ, hãy bắt đầu với một câu chuyện đơn giản.

Vòng lặp (loop) là một trong những khái niệm cơ bản trong các ngôn ngữ lập trình. Phàm những gì là một danh sách đều có thể lặp qua để "duyệt" từng phần tử ở bên nó. Đôi khi cũng chẳng cần phải lặp qua một danh sách mà chỉ cần lặp qua một số n lần để làm một công việc gì đó cần n lần. Như bài toán Tính tổng dưới đây chẳng hạn.

let sum = 0;
for (i = 1; i <= 99; i++) {
    sum = sum + i;
}

Một bài toán kinh điển đúng không, tôi tin rằng ai mới tiếp xúc với lập trình đều phải viết được. Có lẽ thế mà xử lý lần lượt đã trở nên quen thuộc và đôi khi còn là một thói quen giải quyết bài toán lần lượt.

Lặp cho đến khi i nhỏ hơn hoặc bằng 99 có nghĩa là chúng ta đang thực hiện công việc 99 lần. Số đó thì nhằm nhò gì so với sức mạnh của một con chip thời nay: hàng tỉ tỉ phép tính trong 1 giây cơ mà. Đúng thế! Nhưng 99 chỉ là con số mà tôi ví dụ và sum cũng chỉ đơn giản là một phép cộng. Hãy nghĩ lại trong thực tế, logic trong vòng lặp của bạn phức tạp đến thế nào?

Dưới đây là một đoạn mã để tính tổng mà không cần lặp.

let n = 99;
let sum = (n * (n + 1)) / 2;

Tất cả đã được giải quyết trong một lệnh duy nhất. Một lô 99 phần tử gói gọn vào trong một phép tính. Và rõ ràng là nó hiệu quả hơn so với việc lặp đi lặp lại 99 lần. Vì thế, xử lý dữ liệu theo lô tức là giảm tần suất lặp xuống hoặc tăng lượng xử lý đồng thời lên để tăng hiệu năng cho ứng dụng.

Quay trở lại với một bài toán "thực tế" hơn một chút. Nơi bạn nhận được id của người dùng. Nhiệm vụ là lấy ra danh sách bài viết, bình luận, đồng thời đếm tổng số lượt thích của người này. Thiên thời địa lợi khi dữ liệu cần lấy đã được viết sẵn trong các hàm, dở một cái là nó là 3 hàm truy vấn trong 3 bảng khác nhau.

async function getArticles(userId) {
    ...
}

async function getComments(userId) {
    ...
}

async function countComments(userId) {
    ...
}

Lẽ thường tình, sẽ viết:

const userId = body.userId;
const articles = await getArticles(userId);
const comments = await getComments(userId),
const numComments = await countComments(userId);

Như vậy thì hơi tốn kém, vì numComments cần chờ comments, comments thì lại chờ articles, mà rõ ràng 3 hàm không phụ thuộc vào nhau. Chắc bạn sẽ nhận ra Promise có một hàm all để thực hiện nhiều Promise cùng thời điểm:

const userData = await Promise.all([
    getArticles(userId),
    getComments(userId),
    countComments(userId),
]);

Khi đó userData[0] chứa bài viết, userData[1] chứa bình luận và userData[2] chứa tổng lượt thích.

Các con số 0, 1, 2... khá là ẩn và không đầy đủ thông tin, thay vào đó tôi thích sử dụng Bluebird để làm sáng tỏ hơn một chút.

const userData = await Bluebird.Promise.props({
    articles: getArticles(userId),
    comments: getComments(userId),
    numComments: countComments(userId),
});

Khi đó userData.articles chứa bài viết, userData.comments chứa bình luận và userData.numComments chứa tổng lượt thích.

Thế là giải quyết xong một lô Promise!

Background job là cụm từ để chỉ những công việc cần xử lý dưới nền. Một phần nhằm tăng hiệu suất cho luồng chính, phần vì không phải công việc nào cũng cần xử lý ngay lập tức mà cần phụ thuộc vào thời gian. Lấy ví dụ như các công việc tổng hợp dữ liệu vào cuối mỗi ngày thì thường được background job xử lý.

Nhiệm vụ của bạn là mỗi 0h sáng cần thống kê lại một vài chỉ số của ngày hôm trước. Logic là thêm hoặc cập nhật cơ sở dữ liệu. Nếu theo thông thường, lấy ra danh sách người dùng (users), đếm ra tổng số lượt thích của mỗi người rồi tạo bản ghi thống kê vào cơ sở dữ liệu.

for (const user of users) {
    const numComments = await countComments(userId);
    await insertCountComments();
});

Với mỗi insertCountComments là thêm một bản ghi vào cơ sở dữ liệu. Như vậy nếu có 1 triệu users, bạn đang thực hiện 1 triệu lệnh ghi rời rạc vào cơ sở dữ liệu. Thay vào đó hãy gom các bản ghi lại và thực hiện thêm trong một lần duy nhất, hay còn gọi là "bulk insert" - được chứng minh là hiệu quả hơn so với ghi lần lượt.

const records = [];
for (const user of users) {
    const numComments = await countComments(userId);
    records.push(numComments);
});

await insertCountComments();

Thế là giải quyết xong bài toán thêm bản ghi vào cơ sở dữ liệu!

Khi làm việc với message queue, một Producer liên tục gửi thông điệp đến một hàng đợi, hàng đợi lại đẩy từng mẩu tin đó đến các Consumer đang được kết nối. Một vòng sản xuất - tiêu thụ được tuần hoàn như một dây chuyền sản xuất hiện đại.

Điều gì xảy ra khi các tin nhắn gần như là tương đồng với nhau về mặt nội dung và cả cấu trúc? Điều đó có nghĩa hành vi xử lý cho mỗi tin nhắn là gần như giống nhau. Mỗi một tin nhắn, chúng ta lại phải "chọc" vào cơ sở dữ liệu để lấy ra thông tin cần, trong khi nếu nhóm được các thông điệp lại với nhau sẽ giảm được đáng kể việc truy vấn.

Tuỳ thuộc vào công cụ đang sử dụng mà có hay không hỗ trợ gửi một lô tin nhắn. Ví dụ trong RabbitMQ bạn hoàn toàn có gửi một lô thông điệp trong một lần. Hành vi này theo họ nó là nó có thể tăng thêm vài chục lần thông lượng có thể xử lý so với cách xử lý thông thường là gửi từng gói tin.

Khi làm việc với Cloudflare, đặc biệt là Cloudflare Queues - tương tự như một message queue, còn có thể cấu hình đợi Producer gửi đủ số lượng tin nhắn thì mới đẩy một lô đó về Consumer. Ví dụ như là Producer có thể gửi từng thông điệp cho đến khi đủ 100 tin thì 100 tin đó mới được đẩy 1 lần đến Consumer xử lý.

Sau khi một lô tin nhắn về Consumer, chúng ta hoàn toàn có thể áp dụng tiếp quá trình xử lý dữ liệu theo lô nếu điều kiện cho phép.

Cuối cùng còn một trường hợp nữa mà xử lý theo lô phát huy sức mạnh!

I/O là các hành vi bất đồng bộ mà có nguy cơ xảy ra lỗi bất kỳ lúc nào, bởi các hoạt động I/O khá tốn kém và phụ thuộc nhiều vào khả năng xử lý của các yếu tố ngoài như tốc độ mạng, tốc độ phần cứng... Nếu không cẩn thận, chúng ta có thể gặp nhiều lỗi phát sinh trong quá trình xử lý.

Thay vì gọi hàm thực hiện liên tục các hoạt động I/O thì hãy thử giảm tốc độ xuống để giảm tải. Ví dụ như là hành vi ghi dữ liệu vào tệp (ghi logs chẳng hạn). Để ghi được một dòng logs, phải thực hiện các hành vi bao gồm mở file > ghi > đóng file thì hãy tổng hợp một lô dữ liệu thô và thực hiện việc mở > ghi > đóng trong một lần duy nhất.

Tổng kết

Trên đây là một số tình huống mà việc áp dụng xử lý theo lô mà tôi thường xuyên sử dụng. Ngoài ra, vẫn còn rất nhiều trường hợp áp dụng quá trình xử lý theo lô để tăng hiệu suất trong hệ thống thông tin. Bạn có biết thêm cách nào không? Hãy để lại bình luận phía dưới bài viết nhé!

Cao cấp
Hello

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

Bình luận (0)

Nội dung bình luận...