Vercel Cron Jobs trong môi trường production: an toàn và chống trùng lặp với Next.js
Cron job chạy lúc 02:00 UTC mỗi ngày có thể không bao giờ tới ứng dụng, hoặc tới hai lần vào hôm sau và cả hai lần đều cố tạo cùng một báo cáo. Một route xử lý "hôm nay" theo mỗi request sẽ bỏ qua ngày bị lỡ và có thể lặp lại việc của hôm qua. Cách khắc phục là ghi lại ngày nào đến hạn và ngày nào đã hoàn thành trong PostgreSQL.
Bài viết xây dựng một endpoint cron trong Next.js: mỗi ngày báo cáo đã đóng được đưa vào hàng đợi đúng một lần, tự bắt kịp sau các lần gọi bị lỡ và có thể tiếp tục sau khi tiến trình gặp sự cố. Ví dụ ghi số lượng sự kiện theo ngày vào PostgreSQL; phần sau giải thích điều gì thay đổi khi job gửi email hoặc gọi API bên ngoài.
Cấu hình lịch và bảo vệ route
Vercel Cron Jobs gọi một route production bằng HTTP GET theo lịch UTC. Cấu hình đặt ở thư mục gốc của dự án Vercel; trong monorepo đó là app root đã cấu hình, có thể khác gốc repository. Ví dụ đặt lịch 02:00 UTC hằng ngày, nhưng gói Hobby có thể chạy trong phạm vi giờ đã định nên không nên phụ thuộc vào phút chính xác.
Đặt một CRON_SECRET ngẫu nhiên ở môi trường Production; Vercel gửi nó dưới dạng Bearer token. Route phải từ chối khi thiếu biến này và không bao giờ ghi log header. Một request tới route chỉ là tín hiệu đánh thức: nó có thể đến trễ, bị lặp hoặc không đến, và Vercel không thử lại lần gọi cron thất bại. Vì vậy hãy giữ lịch như cơ chế kích hoạt và lưu trạng thái báo cáo thực tế trong cơ sở dữ liệu.
Mô hình hóa theo ngày xử lý
Khóa nghiệp vụ là ngày báo cáo theo UTC. Request ID chỉ dùng để nhận diện lần gọi và phục vụ log, còn hai request cho cùng một ngày phải chia sẻ cùng khóa nghiệp vụ. Cần lưu con trỏ cho ngày kế tiếp cần đưa vào hàng đợi, một job duy nhất cho mỗi ngày và bản thân báo cáo. Trạng thái đã xếp hàng và đã hoàn thành phải tách biệt: xếp hàng thành công không có nghĩa báo cáo đã tồn tại.
Schema gồm bảng sự kiện nguồn, bảng lịch với cột next_day, bảng job theo ngày với queued_at và completed_at, cùng bảng báo cáo. Nếu cần báo cáo theo từng workspace thì dùng khóa job ghép và đưa workspace vào cả truy vấn dữ liệu lẫn khóa báo cáo.
Backfill ngày bị lỡ mà không tranh chấp
Ngày đến hạn được tính bằng đồng hồ cơ sở dữ liệu theo UTC. Lúc 01:59 UTC ngày 11/11, ngày 10/11 vẫn nằm trong khoảng ân hạn hai giờ; đến 02:00 thì thành đến hạn. Quy tắc này xử lý được cả lần gọi trễ lẫn gọi thủ công mà không phải đoán khoảng thời gian từ lúc request tới.
Một câu lệnh duy nhất khóa dòng lịch, đưa tối đa bảy ngày còn thiếu vào hàng đợi và tiến con trỏ. PostgreSQL thực hiện các thay đổi này một cách nguyên tử: nếu thất bại thì cả con trỏ lẫn hàng đợi giữ nguyên; nếu thành công thì job đã bền vững trước khi route xử lý. Lần gọi thứ hai có thể chờ giao dịch ngắn đó kết thúc, thấy con trỏ mới và xếp lô kế tiếp nếu còn. Khóa chính của job bảo vệ chống trùng sau khi sửa tay hoặc chạy lại. Giới hạn bảy ngày giúp mỗi lần gọi có biên; sau sự cố dài cần nhiều lượt quét, nên cảnh báo theo khoảng cách giữa ngày đến hạn mới nhất và con trỏ.
Nhận việc và ghi kết quả trong cùng giao dịch
Với báo cáo chỉ dùng cơ sở dữ liệu, worker sạch nhất là một giao dịch. Chọn một dòng đang chờ bằng FOR UPDATE SKIP LOCKED để các worker cạnh tranh xử lý các ngày khác nhau trong khi dòng được chọn vẫn bị khóa. Câu lệnh ghi số liệu tổng hợp và đánh dấu job hoàn thành cùng lúc; sự cố trước khi commit sẽ nhả khóa và để dòng ở trạng thái chờ.
Khoảng UTC nửa mở gồm nửa đêm đầu ngày và loại nửa đêm ngày kế. Dùng COUNT(e.id) để ngày trống vẫn bằng 0. SKIP LOCKED phù hợp để nhận việc, không phù hợp cho báo cáo cần nhìn toàn bộ dòng đang chờ. Ví dụ cho phép các ngày độc lập hoàn thành không theo thứ tự; nếu thứ Ba phụ thuộc thứ Hai thì phải thể hiện phụ thuộc trong quy tắc nhận việc hoặc tuần tự hóa theo luồng job.
Giữ handler Next.js trong giới hạn
Cài pg và kiểu TypeScript, xuất hai câu SQL trên dưới dạng chuỗi tĩnh. Handler dùng một kết nối PostgreSQL mỗi lần gọi và chạy mỗi câu lệnh trong giao dịch riêng; URL cơ sở dữ liệu và cron secret chỉ tồn tại phía server. Đặt runtime là nodejs, maxDuration 30 giây, giới hạn connection timeout và statement timeout.
Handler so sánh secret bằng timingSafeEqual trên digest SHA-256, trả 503 nếu thiếu cấu hình và 401 nếu sai. Sau khi xếp lịch, vòng lặp xử lý tối đa bảy job trong khoảng 20 giây, ghi log kết quả kèm runId và trả về enqueued, completedDays, nextDay, elapsedMs với Cache-Control no-store. Vì hai câu lệnh commit riêng, phần xếp hàng sống sót qua sự cố trước khi xử lý, và báo cáo đã xong sống sót khi mất phản hồi. Với tải nặng hơn, nên chuyển worker sang queue hoặc workflow bền vững và để route cron chỉ lo đối soát.
Xử lý hiệu ứng bên ngoài riêng
Khóa SQL không thể hoàn tác việc gửi email hay gọi HTTP. Không giữ giao dịch mở trong khi gọi nhà cung cấp chậm; hãy ghi một dòng outbox trong cùng giao dịch với trạng thái nghiệp vụ, rồi để worker khác nhận và gửi. Dùng idempotency key ổn định như tên báo cáo cộng ngày và ID người nhận, ghi lại kết quả nhà cung cấp. Nếu nhà cung cấp đã nhận request nhưng phản hồi bị mất và không thể chống trùng, phải đối soát trước khi thử lại; chỉ riêng timeout không cho biết hiệu ứng đã xảy ra hay chưa.
Kiểm thử trùng lặp, ngày bị lỡ và gián đoạn
Áp schema lên cơ sở dữ liệu PostgreSQL dùng một lần. Chạy truy vấn xếp hàng tại 2026-11-11T01:59:00Z rồi tại 02:00:00Z: với con trỏ ví dụ, lần đầu đưa hai ngày vào hàng đợi, lần sau thêm một ngày. Gọi lại với cùng mốc thời gian và kỳ vọng không có dòng mới. Một mốc tương lai xa hơn con trỏ chỉ nên xếp tối đa bảy ngày.
Nguồn: Planet PostgreSQL
Trích nguồn từ: Planet PostgreSQL
Bài viết liên quan
Cloudflare chọn QuePaxa: thuật toán đồng thuận không cần leader cho mạng toàn cầu
Cloudflare giới thiệu Meerkat, dịch vụ đồng thuận phân tán thử nghiệm dùng thuật toán QuePaxa thay vì Raft hay Paxos, nhằm duy trì ổn định trên mạng diện rộng có độ trễ biến động.
Đọc thêm
Claude Fable 5.2 vượt GPT-6 Cyber trong bài kiểm tra tấn công mạng đa giai đoạn
Anthropic ra mắt Claude Fable 5.2 và Claude Mythos 5.2 với cải thiện về lập trình, suy luận và thị giác; Fable 5.2 đạt 76,3% trên CyScenarioBench, cao hơn mức 61% của GPT-6 Cyber.
Đọc thêm
Microsoft chính thức ra mắt .NET 11 với C# 15, Runtime Async và công cụ AI
Microsoft công bố .NET 11, bản phát hành STS hỗ trợ 2 năm, mang đến C# 15 với union types, Runtime Async dạng preview, cải thiện hiệu năng và bộ công cụ AI gồm MCP, MEAI và MAF.
Đọc thêmCodeRabbit tăng tốc phát triển phần mềm bằng AI, giao hàng nhiều gấp 20 lần
CodeRabbit sử dụng các mô hình OpenAI để kiểm soát và tin cậy mã do AI tạo ra, đồng thời dùng Codex để giao phần mềm nhiều gấp 20 lần với cùng đội ngũ kỹ sư.
Đọc thêmLLM đang tác động thế nào đến PostgreSQL và các DBA
Hubert 'depesz' Lubaczewski chia sẻ quan sát về làn sóng báo lỗi gia tăng trên PostgreSQL, nguyên nhân một số tính năng bị gỡ khỏi PostgreSQL 19 và những lo ngại về tác động của LLM tới cộng đồng và nghề DBA.
Đọc thêm
Agentic engineering: hướng dẫn cho developer vượt ra ngoài việc sinh mã
GitHub giới thiệu quy trình agentic engineering gồm bốn giai đoạn Plan, Build, Verify, Observe, giúp developer dùng Copilot hiệu quả hơn thay vì chỉ sinh mã.
Đọc thêm
OpenAI ra mắt GPT-6 Cyber sau khi AI giúp phát hiện 81 lỗ hổng Firefox và 948 lỗ hổng Linux
OpenAI công bố GPT-6 Cyber với khả năng tìm và vá lỗ hổng bảo mật vượt trội, đồng thời tiết lộ ba sự cố lệch chuẩn liên quan đến GPT-6 Astra và một mô hình nghiên cứu nội bộ.
Đọc thêm
Hiện đại hóa ứng dụng .NET cho Windows Arm64 với GitHub Copilot
GitHub Copilot upgrade agent giúp đánh giá, lập kế hoạch và thực hiện việc chuyển ứng dụng .NET từ Windows x64/x86 sang Windows Arm64, xác định các rào cản và cập nhật mã nguồn, phụ thuộc, cấu hình.
Đọc thêm
GitHub ra mắt kiến trúc lưu trữ Git mới cho kỷ nguyên tác nhân AI
GitHub công bố kiến trúc lưu trữ Git mới có khả năng xử lý hàng nghìn lượt ghi mỗi giây, đáp ứng nhu cầu phát triển phần mềm bằng AI agent với quy mô hàng trăm triệu lập trình viên.
Đọc thêmGPT-6 Cyber: Bước tiến mới cho các tác nhân an ninh mạng được căn chỉnh
OpenAI giới thiệu GPT-6 Cyber, mô hình an ninh mạng chuyên tìm lỗ hổng có thể khai thác và đề xuất bản vá, được huấn luyện để chỉ hoạt động trong phạm vi được cấp phép.
Đọc thêm