Ngăn xuất báo cáo bị timeout: job bất đồng bộ, tiến độ và streaming
Ngăn xuất báo cáo bị timeout bằng job xuất bất đồng bộ, chỉ báo tiến độ, phân trang và tải xuống theo luồng cho báo cáo CSV và PDF lớn.

Tại sao xuất báo cáo bị timeout — cách giải thích đơn giản
Một xuất báo cáo bị timeout khi server không hoàn thành công việc trước một mốc thời gian giới hạn. Mốc đó có thể do trình duyệt, reverse proxy, app server hoặc kết nối cơ sở dữ liệu đặt ra. Với người dùng, hiện tượng thường cảm thấy ngẫu nhiên vì lúc thì thành công, lúc thì thất bại.
Trên màn hình, thường thấy một trong các trường hợp sau:
- Một spinner quay mãi không dừng
- Tải về bắt đầu rồi dừng với "network error"
- Trang lỗi sau khoảng chờ dài
- File tải về nhưng trống hoặc bị hỏng
Các xuất lớn gây áp lực lên nhiều phần của hệ thống cùng lúc. Cơ sở dữ liệu phải tìm và tập hợp nhiều dòng. App server phải định dạng thành CSV hoặc render thành PDF. Rồi trình duyệt phải nhận một phản hồi lớn mà không bị rớt kết nối.
Dữ liệu khổng lồ là yếu tố dễ thấy, nhưng các xuất "nhỏ" cũng có thể nặng. Join tốn kém, nhiều trường tính toán, lookup theo từng hàng, và filter thiếu chỉ mục có thể biến một báo cáo bình thường thành một tác vụ timeout. PDF rủi ro hơn do liên quan đến layout, font, ảnh, ngắt trang và thường có các truy vấn bổ sung để lấy dữ liệu liên quan.
Thử lại nhiều lần thường làm tình hình tệ hơn. Khi người dùng refresh hoặc nhấn Export lần nữa, hệ thống có thể bắt đầu cùng công việc hai lần. Lúc đó cơ sở dữ liệu chạy truy vấn trùng lặp, app server tạo file trùng lặp, và bạn có một đợt tăng tải ngay lúc hệ thống đang chật vật.
Để ngăn timeout, hãy đối xử với xuất báo cáo như một tác vụ nền, không phải một lần tải trang thông thường. Ngay cả trong công cụ no-code như AppMaster, mẫu thiết kế quan trọng hơn công cụ: công việc dài cần một luồng khác với "nhấn nút, chờ phản hồi."
Chọn mẫu xuất phù hợp cho ứng dụng
Hầu hết các lỗi xuất xảy ra vì app dùng một mẫu cho mọi tình huống, dù kích thước dữ liệu và thời gian xử lý có thể khác nhau nhiều.
Một xuất đồng bộ đơn giản (người dùng nhấn, server tạo file, bắt đầu tải) ổn khi xuất nhỏ và dự đoán được: vài trăm dòng, cột cơ bản, không định dạng nặng, và không có nhiều người dùng làm đồng thời. Nếu luôn kết thúc trong vài giây, giữ nguyên là tốt.
Với bất cứ thứ gì lâu hoặc khó đoán, dùng job xuất bất đồng bộ. Cách này phù hợp cho dữ liệu lớn, tính toán phức tạp, render PDF, và server chia sẻ nơi một xuất chậm có thể chặn các yêu cầu khác.
Job bất đồng bộ phù hợp khi:
- Xuất thường xuyên mất hơn 10–15 giây
- Người dùng chọn phạm vi ngày rộng hoặc "all time"
- Bạn tạo PDF có biểu đồ, ảnh hoặc nhiều trang
- Nhiều nhóm xuất trong giờ cao điểm
- Cần retry an toàn khi có lỗi
Streaming download cũng hữu ích khi xuất lớn nhưng có thể sản xuất theo thứ tự. Server bắt đầu gửi byte ngay, khiến cảm nhận nhanh hơn và tránh phải dựng toàn bộ file trong bộ nhớ trước. Tốt cho CSV dài, nhưng ít giá trị nếu phải tính toán toàn bộ trước khi ghi dòng đầu tiên.
Bạn có thể kết hợp các cách: chạy job bất đồng bộ để tạo file (hoặc chuẩn bị snapshot), rồi stream khi file sẵn sàng. Trong AppMaster, một cách thực tế là tạo bản ghi "Export Requested", sinh file trong business process backend, và cho người dùng tải kết quả khi xong mà không cần giữ yêu cầu trình duyệt mở.
Từng bước: xây job xuất bất đồng bộ
Thay đổi lớn nhất đơn giản: thôi không tạo file trong cùng một request mà người dùng nhấn.
Job xuất bất đồng bộ chia công việc thành hai phần: một request nhanh tạo job, và công việc nền xây file trong khi app vẫn phản hồi.
Luồng 5 bước thực tế
- Ghi nhận yêu cầu xuất (ai yêu cầu, filter, cột chọn, định dạng đầu ra).
- Tạo bản ghi job với trạng thái (queued, running, done, failed), timestamp và trường lỗi.
- Chạy công việc nặng ở background bằng queue, worker theo lịch, hoặc process worker riêng.
- Ghi kết quả vào storage (object storage hoặc file store), rồi lưu tham chiếu download trên record job.
- Thông báo cho người dùng khi xong bằng thông báo trong app, email, hoặc kênh nhắn mà nhóm đã dùng.
Giữ bản ghi job làm nguồn sự thật. Nếu người dùng refresh, chuyển thiết bị hoặc đóng tab, bạn vẫn có thể hiển thị cùng trạng thái job và cùng nút tải xuống.
Ví dụ: một quản lý hỗ trợ xuất tất cả ticket của quý trước. Thay vì chờ trên tab quay, họ thấy một entry job chuyển từ queued sang done, rồi nút tải xuất hiện. Trong AppMaster, bạn có thể mô hình bảng job trong Data Designer, xây logic nền trong Business Process Editor và dùng trường status để điều khiển trạng thái UI.
Chỉ báo tiến độ mà người dùng thực sự tin
Một chỉ báo tiến độ tốt giảm lo lắng và ngăn người dùng nhấn Export nhiều lần. Nó cũng gián tiếp giúp tránh timeout, vì người dùng sẵn sàng chờ hơn khi app cho thấy tiến triển thực.
Hiển thị tiến độ theo cách người dùng hiểu. Phần trăm một mình thường gây hiểu nhầm, nên ghép với thông tin cụ thể:
- Bước hiện tại (Preparing data, Fetching rows, Building file, Uploading, Ready)
- Số hàng đã xử lý trên tổng (hoặc số trang đã xử lý)
- Thời gian bắt đầu và lần cập nhật gần nhất
- Thời gian ước tính còn lại (chỉ khi tương đối ổn định)
Tránh độ chính xác giả. Nếu bạn chưa biết tổng công việc, đừng hiển thị 73%. Dùng các mốc trước, rồi chuyển sang phần trăm khi biết mẫu số. Một mẫu đơn giản là 0%–10% cho thiết lập, 10%–90% dựa trên hàng đã xử lý, và 90%–100% cho bước hoàn thiện file. Với PDF có kích thước trang biến thiên, theo dõi những thứ nhỏ hơn như "records rendered" hoặc "sections completed."
Cập nhật đủ thường để cảm thấy sống động, nhưng không quá thường làm quá tải database hoặc queue. Cách phổ biến là ghi tiến độ mỗi 1–3 giây, hoặc mỗi N bản ghi (ví dụ mỗi 500 hoặc 1.000 dòng), chọn mức ít rườm rà hơn. Ngoài ra lưu một timestamp heartbeat nhẹ để UI có thể nói "Vẫn đang chạy" ngay cả khi phần trăm không thay đổi.
Cho người dùng quyền khi việc kéo dài: cho họ hủy export đang chạy, bắt đầu export mới mà không mất export cũ, và xem lịch sử xuất với trạng thái (Queued, Running, Failed, Ready) cùng thông báo lỗi ngắn.
Trong AppMaster, một bản ghi điển hình là ExportJob (status, processed_count, total_count, step, updated_at). UI poll bản ghi đó và hiển thị tiến độ trung thực trong khi job nền tạo file.
Phân trang và lọc để giữ công việc có giới hạn
Most timeout xảy ra vì export cố làm mọi thứ cùng lúc: quá nhiều hàng, quá nhiều cột, quá nhiều join. Sửa nhanh nhất là giữ công việc có giới hạn để người dùng xuất một lát cắt dữ liệu nhỏ hơn và rõ ràng hơn.
Bắt đầu từ mục tiêu người dùng. Nếu ai đó cần "hóa đơn tháng trước thất bại", đừng mặc định là "tất cả hóa đơn từ trước tới giờ." Làm cho filter trở nên bình thường, không thành việc rườm rà. Một phạm vi ngày đơn giản cộng một filter trạng thái thường cắt dataset tới 90%.
Form yêu cầu xuất tốt thường bao gồm phạm vi ngày (mặc định hợp lý như 7 hoặc 30 ngày trước), một hai trạng thái chính, tìm kiếm tùy chọn hoặc chọn khách hàng/nhóm, và xem trước số lượng khi có thể (kể cả ước lượng).
Về phía server, đọc dữ liệu theo chunk dùng phân trang. Điều này giữ bộ nhớ ổn định và cho bạn checkpoint tự nhiên để cập nhật tiến độ. Luôn dùng thứ tự ổn định khi paging (ví dụ order by created_at rồi id). Nếu không, hàng mới có thể chui vào các trang trước và bạn sẽ bỏ sót hoặc nhân đôi bản ghi.
Dữ liệu thay đổi trong khi xuất kéo dài, nên quyết định "nhất quán" nghĩa là gì. Cách đơn giản là ghi lại thời điểm snapshot khi job bắt đầu, rồi chỉ xuất các hàng tới thời điểm đó. Nếu cần nhất quán nghiêm ngặt, dùng consistent read hoặc transaction nếu DB hỗ trợ.
Trong công cụ no-code như AppMaster, điều này khớp tự nhiên vào business process: validate filter, set snapshot time, rồi lặp qua các trang cho đến khi không còn dữ liệu.
Streaming download mà không làm sập server
Streaming có nghĩa bạn bắt đầu gửi file cho người dùng trong khi vẫn đang tạo nó. Server không phải dựng cả CSV hay PDF trong bộ nhớ trước. Đây là một trong những cách đáng tin cậy nhất để ngăn timeout khi file lớn.
Streaming không làm truy vấn chậm biến nhanh hơn. Nếu công việc database mất năm phút trước khi byte đầu tiên sẵn sàng, request vẫn có thể timeout. Sửa thường là kết hợp streaming với paging: fetch một chunk, viết nó, và tiếp tục.
Để giữ bộ nhớ thấp, ghi ngay khi có. Sinh một chunk (ví dụ 1.000 dòng CSV hoặc một trang PDF), ghi vào response, rồi flush để client tiếp tục nhận dữ liệu. Tránh gom hàng vào một array lớn "rồi sort sau." Nếu cần thứ tự ổn định, sort ở database.
Header, tên file và content type
Dùng header rõ ràng để trình duyệt và app di động xử lý file đúng. Đặt content type thích hợp (như text/csv hoặc application/pdf) và tên file an toàn. Tên file tránh ký tự đặc biệt, ngắn gọn, và có timestamp nếu người dùng xuất cùng báo cáo nhiều lần.
Resume và tải một phần
Quyết định sớm liệu bạn hỗ trợ resume. Streaming cơ bản thường không hỗ trợ resume theo range byte, đặc biệt với PDF sinh động. Nếu bạn hỗ trợ, phải xử lý Range requests và sinh đầu ra nhất quán cho cùng một job.
Trước khi ra mắt, đảm bảo bạn:
- Gửi header trước khi ghi body, rồi ghi theo chunk và flush
- Kích cỡ chunk ổn định để bộ nhớ không tăng khi tải cao
- Dùng thứ tự xác định để người dùng tin tưởng đầu ra
- Ghi rõ resume có được hỗ trợ hay không và điều gì xảy ra khi rớt kết nối
- Thêm giới hạn phía server (max rows, max time) và trả lỗi thân thiện khi vượt
Nếu xây exports trong AppMaster, giữ logic tạo file trong backend flow và stream từ server side, không từ trình duyệt.
Các chiến thuật thực tế cho CSV lớn
Với CSV lớn, đừng coi file là một blob duy nhất. Xây nó như một vòng lặp: đọc một lát dữ liệu, ghi các dòng, lặp lại. Điều đó giữ bộ nhớ phẳng và làm cho việc retry an toàn hơn.
Ghi CSV theo từng dòng. Ngay cả khi sinh trong job nền, tránh "gom tất cả dòng rồi stringify." Mở một writer và append mỗi dòng ngay khi sẵn sàng. Nếu stack hỗ trợ, dùng cursor DB hoặc phân trang kết quả để không load hàng triệu bản ghi cùng lúc.
Độ đúng CSV quan trọng ngang với tốc độ. File có thể trông ổn cho tới khi ai đó mở trong Excel và một số cột bị lệch.
Quy tắc CSV để tránh file hỏng
- Luôn escape dấu phẩy, dấu nháy và xuống dòng (bọc cả trường trong dấu nháy và nhân đôi dấu nháy bên trong)
- Xuất UTF-8 và kiểm thử tên không phải tiếng Anh end-to-end
- Dùng dòng header ổn định và giữ thứ tự cột cố định giữa các lần xuất
- Chuẩn hóa ngày và số thập phân (chọn một định dạng và dùng nhất quán)
- Tránh công thức nếu dữ liệu có thể bắt đầu bằng =, +, -, hoặc @
Hiệu năng thường chết ở khâu truy cập dữ liệu, không phải việc ghi. Để ý N+1 lookups (ví dụ load mỗi khách hàng trong vòng lặp). Lấy dữ liệu liên quan trong một truy vấn, hoặc preload trước, rồi ghi dòng.
Khi xuất thật sự khổng lồ, chia file có chủ đích. Cách thực tế là một file theo tháng, theo khách hàng, hoặc theo loại thực thể. Một export "5 năm đơn hàng" có thể thành 60 file theo tháng, mỗi file sinh độc lập, nên một tháng chậm không chặn phần còn lại.
Nếu dùng AppMaster, mô hình dataset trong Data Designer và chạy export như business process nền, ghi dòng khi phân trang qua bản ghi.
Xuất PDF lớn: giữ cho nó dự đoán được
Tạo PDF thường chậm hơn CSV vì tốn CPU. Bạn không chỉ di chuyển dữ liệu, mà còn layout trang, đặt font, vẽ bảng và thường resize ảnh. Xử lý PDF như tác vụ nền với giới hạn rõ ràng, không phải phản hồi nhanh.
Lựa chọn template quyết định liệu một export 2 phút thành 20 phút. Layout đơn giản thắng: ít cột, ít bảng lồng, và ngắt trang dự đoán được. Ảnh là một trong những thứ làm chậm nhanh nhất, đặc biệt nếu ảnh lớn, DPI cao hoặc lấy từ nguồn từ xa khi render.
Quyết định template thường cải thiện tốc độ và độ tin cậy:
- Dùng một hai font và tránh chuỗi fallback nặng
- Giữ header và footer đơn giản (tránh chart động trên mỗi trang)
- Ưu tiên icon vector hơn ảnh raster lớn
- Hạn chế layout "auto fit" đo nhiều lần
- Tránh transparency và shadow phức tạp
Với xuất lớn, render theo lô. Sinh một phần hoặc một dải trang nhỏ mỗi lần, ghi vào file tạm, rồi ghép file cuối cùng. Điều này giữ bộ nhớ ổn định và làm cho retry an toàn nếu worker chết giữa chừng. Cách này cũng kết hợp tốt với job bất đồng bộ và tiến độ theo bước ý nghĩa (ví dụ: "Preparing data", "Rendering pages 1-50", "Finalizing file").
Cũng tự hỏi PDF có thực sự là thứ người dùng cần. Nếu họ chủ yếu cần hàng và cột để phân tích, cung cấp CSV bên cạnh "Export PDF." Bạn vẫn có thể sinh một PDF tóm tắt nhỏ hơn cho báo cáo trong khi giữ full dataset ở CSV.
Trong AppMaster, điều này khớp tự nhiên: chạy render PDF như job nền, báo tiến độ và cung cấp file khi job hoàn thành.
Sai lầm phổ biến gây timeout
Thất bại xuất thường không bí ẩn. Một vài lựa chọn chạy tốt với 200 hàng rồi vỡ lúc 200.000.
Những lỗi phổ biến nhất:
- Chạy toàn bộ export trong một web request. Trình duyệt chờ, worker server bận, và truy vấn chậm hoặc file lớn đẩy bạn qua giới hạn thời gian.
- Hiển thị tiến độ dựa trên thời gian thay vì công việc. Đồng hồ chạy tới 90% rồi dừng khiến người dùng refresh hoặc bắt đầu export mới.
- Đọc mọi hàng vào bộ nhớ trước khi viết file. Dễ làm và nhanh chóng chạm giới hạn bộ nhớ.
- Giữ transaction DB dài hoặc bỏ qua lock. Truy vấn export có thể chặn ghi hoặc bị chặn bởi ghi, và độ trễ lan ra toàn app.
- Cho phép xuất không giới hạn và không cleanup. Nhấp nhiều lần tạo đống job, đầy storage và để lại file cũ mãi không xóa.
Ví dụ cụ thể: một trưởng phòng support xuất tất cả ticket 2 năm và bấm hai lần vì tưởng không có gì. Giờ hai export giống nhau cạnh tranh DB, cả hai dựng file lớn trong bộ nhớ, và cả hai timeout.
Nếu xây trong công cụ no-code như AppMaster, nguyên tắc giống nhau: tránh xuất trong request, theo dõi tiến độ theo hàng đã xử lý, ghi đầu ra khi có, và đặt giới hạn đơn giản cho số export người dùng có thể chạy cùng lúc.
Kiểm tra nhanh trước khi ra mắt
Trước khi phát hành tính năng export, chạy nhanh với tư duy "timer": công việc dài ở ngoài request, người dùng thấy tiến trình trung thực, và server không cố làm mọi thứ cùng lúc.
Checklist tiền đường bay:
- Xuất lớn chạy như background job (xuất nhỏ có thể đồng bộ nếu đáng tin là nhanh)
- Người dùng thấy trạng thái rõ như queued, running, done, failed, kèm timestamp
- Dữ liệu đọc theo chunk với thứ tự ổn định (ví dụ created time + id để phá vỡ hòa)
- File hoàn thành có thể tải lại sau mà không cần chạy lại export, ngay cả khi người dùng đóng tab
- Có giới hạn và kế hoạch cleanup file cũ và lịch sử job (xóa theo tuổi, max job mỗi user, hạn mức storage)
Kiểm tra hợp lý là thử worst case: xuất phạm vi lớn nhất bạn cho phép trong khi người khác vẫn thêm bản ghi. Nếu thấy duplicate, missing rows, hoặc tiến độ đứng, tức là ordering hoặc chunking chưa ổn.
Nếu xây trên AppMaster, những kiểm tra này tương ứng với: background process trong Business Process Editor, bản ghi Export Job trong DB, và trường status mà UI đọc và refresh.
Khi thất bại, hãy làm cho nó an toàn. Job thất bại nên giữ message lỗi, cho phép retry, và tránh tạo file partial trông như đã hoàn thành nhưng không đủ dữ liệu.
Ví dụ: xuất nhiều năm dữ liệu mà không làm đóng băng app
Một ops manager cần hai xuất mỗi tháng: một CSV với 2 năm đơn hàng để phân tích, và một bộ PDF hóa đơn hàng tháng cho kế toán. Nếu app cố dựng cả hai trong request web bình thường, bạn sẽ chạm giới hạn thời gian sớm muộn.
Bắt đầu bằng cách giới hạn công việc. Màn xuất yêu cầu phạm vi ngày (mặc định: 30 ngày trước), filter tùy chọn (status, region, sales rep), và lựa chọn cột rõ ràng. Thay đổi này thường biến vấn đề 2 năm, 2 triệu hàng thành thứ có thể quản lý.
Khi người dùng nhấn Export, app tạo một record Export Job (type, filters, requested_by, status, progress, error_text) và đẩy vào queue. Trong AppMaster, đây là model Data Designer cộng Business Process chạy nền.
Trong khi job chạy, UI cho trạng thái đáng tin: queued, processing (ví dụ 3 trên 20 chunk), generating file, ready (nút download), hoặc failed (lỗi rõ ràng và retry).
Chunking là chi tiết then chốt. Job CSV đọc orders theo trang (ví dụ 50.000 một lần), ghi mỗi trang vào output và cập nhật tiến độ sau mỗi chunk. Job PDF làm tương tự theo batch hóa đơn (ví dụ từng tháng), nên một tháng chậm không chặn toàn bộ.
Nếu có lỗi (filter sai, quyền thiếu, storage error), job được đánh dấu Failed với thông báo ngắn người dùng có thể hành động: "Không thể sinh hóa đơn Tháng 3. Vui lòng thử lại, hoặc liên hệ support với Job ID 8F21." Retry dùng lại cùng filter để người dùng không phải bắt đầu lại.
Bước tiếp theo: biến xuất thành tính năng tiêu chuẩn, không phải dập lửa
Cách nhanh nhất để ngăn timeout lâu dài là ngừng xem xuất như nút bấm một lần và biến nó thành tính năng chuẩn theo mẫu lặp lại.
Chọn một cách mặc định và dùng khắp nơi: job bất đồng bộ sinh file nền, rồi người dùng tải khi sẵn sàng. Quyết định đó loại bỏ hầu hết "thử nghiệm thành công" bất ngờ, vì yêu cầu người dùng không phải chờ file hoàn tất.
Giúp người dùng dễ tìm file họ đã tạo. Một trang lịch sử xuất (theo user, workspace hoặc account) giảm xuất lặp, giúp đội support trả lời "file tôi đâu?", và cho chỗ hiển thị trạng thái, lỗi và thời hạn.
Nếu bạn xây pattern này trong AppMaster, nền tảng sinh mã nguồn thực sự và hỗ trợ logic backend, mô hình DB, và UI web/mobile trong một nơi. Đội muốn triển khai job xuất bất đồng bộ nhanh thường dùng appmaster.io để tạo bảng job, process nền và UI tiến độ mà không phải nối tay từ đầu.
Rồi đo đạc chỗ thực sự gây đau. Theo dõi truy vấn DB chậm, thời gian sinh CSV, và thời gian render PDF. Bạn không cần observability hoàn hảo để bắt đầu: log thời lượng và số hàng mỗi export sẽ nhanh chóng cho thấy report hoặc bộ lọc nào là vấn đề.
Đối xử với xuất như tính năng sản phẩm: nhất quán, đo lường được và dễ hỗ trợ.
Câu hỏi thường gặp
Một xuất báo cáo bị timeout khi công việc không hoàn thành trước mốc thời gian giới hạn nằm ở đâu đó trên đường xử lý yêu cầu. Giới hạn đó có thể đến từ trình duyệt, reverse proxy, app server hoặc kết nối cơ sở dữ liệu, nên hiện tượng có vẻ ngẫu nhiên mặc dù nguyên nhân gốc rễ thường là tải cao hoặc các truy vấn chậm.
Chỉ dùng xuất đồng bộ (nhấp rồi tải ngay) khi nó chắc chắn hoàn thành trong vài giây với kích thước dữ liệu dự đoán được. Nếu xuất thường mất hơn 10–15 giây, bao gồm phạm vi ngày lớn, tính toán nặng, hoặc PDF, hãy chuyển sang job bất đồng bộ để yêu cầu trình duyệt không phải giữ kết nối mở lâu.
Tạo trước một bản ghi job, rồi thực hiện công việc nặng ở background, và cuối cùng cho người dùng tải file đã hoàn thành. Trong AppMaster, cấu hình phổ biến là model ExportJob trong Data Designer và một Business Process backend cập nhật status, các trường tiến độ, và tham chiếu file được lưu trong khi chạy.
Theo dõi công việc thực tế, không phải thời gian trôi. Một cách thực dụng là lưu các trường như step, processed_count, total_count (khi biết được), và updated_at, rồi UI poll các trường đó để hiển thị trạng thái rõ ràng, tránh khiến người dùng lo lắng và bấm export liên tục.
Làm cho yêu cầu xuất idempotent và giữ bản ghi job làm nguồn dữ liệu chính. Nếu người dùng nhấn lại, hiển thị job đang chạy hiện có (hoặc chặn trùng lặp cùng bộ lọc) thay vì bắt đầu cùng công việc tốn kém lần nữa.
Đọc và ghi theo chunk để bộ nhớ ổn định và có các mốc kiểm tra tự nhiên. Dùng phân trang ổn định với sắp xếp xác định (ví dụ theo created_at rồi id) để không bỏ sót hoặc nhân đôi bản ghi khi dữ liệu thay đổi trong quá trình xuất.
Ghi lại thời điểm snapshot khi job bắt đầu và chỉ xuất các hàng đến thời điểm đó để đầu ra không thay đổi trong khi chạy. Nếu cần nhất quán chặt chẽ hơn, dùng consistent read hoặc chiến lược giao dịch mà cơ sở dữ liệu hỗ trợ, nhưng bắt đầu bằng quy tắc snapshot đơn giản dễ giải thích cho người dùng.
Streaming giúp khi bạn có thể sản xuất đầu ra theo thứ tự và bắt đầu gửi byte sớm, đặc biệt với CSV lớn. Nó không khắc phục được truy vấn chậm mất nhiều phút mới có byte đầu tiên, và vẫn có thể timeout nếu lâu không ghi gì, nên streaming hiệu quả nhất khi kết hợp với phân trang ghi liên tục.
Ghi dòng ra ngay khi có và tuân theo quy tắc escaping CSV để file không bị hỏng khi mở bằng Excel. Đảm bảo mã hoá nhất quán (thường là UTF-8), tiêu đề và thứ tự cột cố định, và tránh lookup theo từng hàng khiến một export biến thành hàng ngàn truy vấn phụ.
Tạo PDF nặng hơn CSV vì phải layout, font, ảnh và phân trang, nên xử lý nó như job nền có giới hạn rõ ràng. Giữ template đơn giản, tránh ảnh lớn hoặc lấy ảnh từ nguồn từ xa trong lúc render, và hiển thị tiến độ theo bước có ý nghĩa để người dùng biết công việc đang tiến triển.


