Màn hình báo timeout, nhưng bài đã nằm trong WordPress. Worker không nhận được phản hồi nên gửi lại cùng yêu cầu. Vài phút sau, biên tập viên thấy hai bản nháp với đường dẫn gần giống nhau. Lỗi này không bắt đầu từ chất lượng bài viết; nó bắt đầu từ cách hệ thống diễn giải sự im lặng của mạng.
Muốn chống tạo bài trùng WordPress khi dùng REST API, cần tách hai câu hỏi: yêu cầu đã được server thực hiện chưa, và client đã nhận được xác nhận chưa. Đây là hai sự kiện khác nhau. Hướng dẫn dưới đây phân tích một thiết kế cho đội tích hợp CMS, dùng ví dụ giả lập, không phải báo cáo sự cố hay kết quả triển khai cho một khách hàng của Onter.
Timeout không phải thông báo rằng WordPress đã hủy thao tác
Theo tài liệu WordPress, tạo bài sử dụng POST tới endpoint posts. Trong một yêu cầu thành công, server trả về đối tượng bài viết có ID. Tuy nhiên, client có thể mất kết nối sau khi WordPress đã lưu bài nhưng trước khi phản hồi đến nơi. Reverse proxy cũng có thể ngừng chờ trong khi tiến trình phía sau vẫn tiếp tục.
Vì vậy, một bộ đếm retry chỉ nhìn HTTP status chưa đủ. Nếu không nhận được status, trạng thái đúng là chưa biết kết quả. Gọi trường hợp ấy là thất bại rồi tạo lại bài sẽ biến một sự cố truyền tin thành lỗi dữ liệu. Người vận hành cần một cách hỏi lại server về chính công việc trước đó.
Nguyên tắc idempotency trong HTTP liên quan đến tác động dự kiến khi lặp lại yêu cầu. RFC9110 không coi POST nói chung là phương thức idempotent. Việc đặt tên endpoint bằng PUT trong một integration riêng cũng không tự tạo ra tính chất này. Phần lưu dữ liệu phải thực sự nhận diện và hợp nhất các lần gọi cùng một thao tác.
Nếu API đã trả mã 429, bài toán lại là tôn trọng giới hạn tốc độ và hạn xử lý. Có thể đọc thêm cách đặt ngân sách retry cho API429; đó là phạm vi khác với việc chưa biết yêu cầu tạo bài đã được lưu hay chưa.
Định danh công việc trước khi có post ID
Post ID chỉ xuất hiện sau khi WordPress tạo bản ghi. Dây chuyền cần article ID từ lúc lập brief, chẳng hạn blog-wordpress-001. ID này ổn định qua quá trình viết, sửa metadata và thay ảnh. Một intent owner riêng mô tả vấn đề mà bài chịu trách nhiệm giải quyết, giúp tránh tình trạng hai brief khác tên nhưng cùng mục đích tìm kiếm.
Idempotency key nhận diện thao tác ghi. Có thể kết hợp namespace của publisher, article ID và loại thao tác, ví dụ tạo draft. Không nên sinh UUID mới cho mỗi lần retry, vì server sẽ hiểu mỗi lần gọi là một việc mới. Cũng không nên dùng mỗi tiêu đề: tiêu đề có thể được biên tập lại trước khi xuất bản.
Đi cùng key là hash payload. Nếu client gửi cùng key nhưng nội dung khác, server phải trả conflict để con người hoặc worker điều phối xử lý. Tự ghi đè bài cũ sẽ che mất một lần sửa; tự tạo bài mới lại phá vỡ cam kết chống trùng. Hai trường hợp cần được phân biệt rõ trong journal.
Chống tạo bài trùng WordPress cần hơn việc kiểm tra slug
Giả sử worker A và B cùng kiểm tra một slug chưa tồn tại. Cả hai đều nhận kết quả trống trước khi bất kỳ worker nào tạo bài. Sau đó mỗi worker gửi một yêu cầu tạo. WordPress có thể điều chỉnh slug để giữ đường dẫn riêng, nhưng điều đó không đồng nghĩa hệ thống chỉ còn một bài cho intent ban đầu.
Đây là khoảng trống giữa kiểm tra và ghi. Một truy vấn tìm slug hữu ích để phát hiện va chạm có sẵn; nó không thay thế một cơ chế đặt chỗ nguyên tử. Với integration nhỏ, có thể sử dụng registry dựa trên khóa duy nhất. Với nhiều worker, database của queue cần UNIQUE cho article ID, intent owner và idempotency key.
WordPress có hàm add_option để thêm option khi chưa tồn tại. Khi dùng nó cho một registry có namespace riêng, cần đọc hành vi và schema phiên bản đang chạy, không suy diễn rằng mọi cache đều là khóa phân tán an toàn. Phương án này còn cần thiết kế khôi phục cho tình huống process dừng giữa việc đặt chỗ và việc lưu post ID.

Khi chưa rõ kết quả, đừng vội tạo lại bài
Trước khi gọi CMS, worker ghi operation ở trạng thái IN_FLIGHT cùng key, article ID và hash đầu vào. Nếu server trả đúng post ID, worker đọc lại bài và đối chiếu nội dung, slug, author cùng metadata trước khi ghi CONFIRMED. Phản hồi HTTP200 nhưng thiếu ID hoặc sai hash vẫn chưa đủ làm bằng chứng.
Khi gặp timeout, giữ nguyên IN_FLIGHT và chuyển sang reconciliation. Worker hỏi integration bằng article ID hoặc key đã đặt chỗ. Nếu registry đã có post ID, nó đọc lại bản ghi ấy. Nếu registry còn tồn tại nhưng chưa có ID, hệ thống dừng riêng thao tác đó để điều tra; không xóa khóa chỉ vì đã quá vài giây.
Độ trễ không tự chứng minh rằng thao tác cũ đã chết. Một tiến trình đang chậm vẫn có thể hoàn tất sau khi khóa bị xóa. Cách khôi phục phải xét journal, trạng thái server và ranh giới transaction. Chỉ khi có bằng chứng chưa tạo bài mới cho phép điều phối một lần thực hiện mới.
Lease của worker và khóa idempotency giải quyết hai vấn đề khác nhau
Lease giúp tránh hai worker xử lý cùng một job trong một khoảng thời gian. Nó thường có hạn sử dụng để hệ thống tiếp tục khi worker mất kết nối. Idempotency registry bảo vệ tác động lên CMS qua các lần gọi mạng. Chỉ có lease không đủ chống trùng nếu request cũ vẫn chạy sau khi lease hết hạn.
Mỗi lần claim nên có fencing token. Khi worker ghi tiến độ, nó phải chứng minh vẫn giữ token hiện hành. Worker cũ quay lại sau thời gian treo không được xác nhận kết quả thay cho worker mới. Tương tự, một callback đến muộn không được đẩy job đã HOLD sang PUBLISHED.
Đối với pilot, concurrency một worker thường dễ quan sát hơn. Điều đó không loại bỏ nhu cầu idempotency: một process đơn vẫn có thể chết sau khi gửi request. Hãy hoàn thiện đường khôi phục trước khi tăng số worker để tránh nhân rộng lỗi chưa hiểu rõ.
Tạo draft và publish cần hai biên an toàn riêng
Tạo được draft chỉ xác nhận nội dung đã vào CMS. Xuất bản là một thao tác khác, cần key riêng và một fingerprint hiện tại. Fingerprint nên bao gồm body, title, slug, author, featured image và những trường SEO quyết định đầu ra công khai. Đổi metadata sau phân tích phải làm receipt cũ mất hiệu lực.
Worker không được coi focus keyword đã lưu là Rank Math đã chạy. Khi analyzer có kết quả, cần lưu qua cơ chế chính thức rồi đọc score native. Receipt phải trỏ đúng post ID và đúng fingerprint. Nếu biên tập viên sửa nội dung trong lúc worker phân tích, giữ SCORE_PENDING cho bài đó thay vì lấy điểm cũ để release.
Ngay trước đổi trạng thái, integration cần kiểm tra lại nội dung trong vùng khóa thích hợp. Sau publish, đọc URL công khai để kiểm tra HTTP, canonical, schema, ảnh và author. Public QA thất bại là một tình huống có journal và rollback có phạm vi; không phải lý do phục hồi cả VPS.
Media cũng có thể bị nhân bản sau timeout
Cùng một ảnh được upload hai lần có thể nhận hai attachment ID khác nhau. Filename giống nhau không bảo đảm WordPress tái sử dụng ảnh cũ. Asset registry cần SHA256 của file và ID tài sản ổn định, kèm giấy phép, creator, nguồn và các bài đang dùng ảnh.
Hash file gốc và hash bản tối ưu phục vụ mục đích khác nhau. Chuyển PNG sang WebP làm byte thay đổi dù hình nhìn tương tự. Lưu quan hệ giữa hai hash sẽ giúp hệ thống tái sử dụng đúng bản xuất, đồng thời giữ provenance. Kiểm tra ảnh gần giống hỗ trợ review, không nên tự kết luận hai ảnh khác hash là hai hình có giá trị riêng.
Khi upload chưa rõ kết quả, tra attachment qua registry thay vì gửi lại file. Trước gán featured image, đọc lại dimensions, ALT và URL. Caption phải mô tả đúng bối cảnh: ảnh stock là minh họa, screenshot trong phòng thử nghiệm không phải ảnh dự án khách hàng.
Kiểm thử lỗi trước khi tin vào đường chạy thành công
Một bài chạy trọn vẹn chưa chứng minh thiết kế chống trùng hoạt động. Cần thử mất phản hồi sau khi server đã tạo draft; hai worker claim đồng thời; cùng key với payload khác; worker cũ gửi callback; và editor sửa bài trước publish. Mỗi thử nghiệm phải có kết quả mong đợi cụ thể.
Ví dụ giả lập: server đã trả post ID501 vào registry nhưng client mất phản hồi. Khi chạy reconciliation, worker phải tìm lại501, xác nhận hash và dùng tiếp bản đó. Nếu lần xử lý thứ hai tạo502, test phải thất bại dù cả hai URL đều trả200. Mục tiêu là bảo toàn một công việc, không phải tối đa số response thành công.
Theo dõi riêng số retry đọc, số mutation còn chưa rõ kết quả và số collision. Một tỷ lệ publish cao có thể che việc hệ thống bỏ qua lỗi đầu vào. Ngược lại, HOLD đúng một job không nên làm mọi bài độc lập phải dừng, trừ khi lý do là sức khỏe chung của WordPress hoặc máy chủ.

Chọn mức tích hợp theo rủi ro thực tế
Đội chỉ đăng thỉnh thoảng có thể bắt đầu với journal nhỏ, thao tác draft và reconciliation có người kiểm tra. Dây chuyền nhiều bài cần registry nguyên tử, giới hạn request và bằng chứng release theo fingerprint. Không nhất thiết xây lại WordPress; native posts, media và revisions vẫn là các thành phần nên tái sử dụng.
Nếu website dùng renderer hoặc trường SEO tùy biến, hãy kiểm tra đầu ra thật trước khi mở rộng worker. Cơ chế tạo bài không biết cách template biến metadata thành HTML. Nếu cần đánh giá một tích hợp CMS hoặc tự động hóa có người duyệt, có thể trao đổi phạm vi với dịch vụ giải pháp AI của Onter. Phạm vi kỹ thuật và điều kiện vận hành cần được xác minh riêng, không suy ra từ một ví dụ kiến trúc.
Nguồn đối chiếu: WordPress REST API Posts; WordPress REST API Media; WordPress add_option; RFC9110 về idempotent methods. Các nguồn mô tả nền tảng; quy tắc registry và state machine trong bài là đề xuất thiết kế, cần kiểm thử theo hạ tầng cụ thể.
Ảnh máy chủ minh họa: Kevin Ache / Unsplash.

