CYBERSOFT
Đăng nhập

Thực chiến: kiểm thử đặt vé/giữ chỗ du lịch & chống double-booking khi đồng thời cao

Thực chiến doanh nghiệpTMĐTAPIk6/PerfPlaywrightThực tế
🗓 1 tháng trước22 phút đọc·👁 1,392 lượt xem👤 292 người đọc

Bài sâu: bối cảnh đặt vé du lịch, kiến trúc, bất biến chống double-booking, test plan, ma trận ca, code đồng thời, đối soát tồn chỗ, CI/k6, AI, phỏng vấn.

1. Bối cảnh doanh nghiệp & phạm vi

Một nền tảng du lịch trực tuyến (OTA) bán vé máy bay nội địa, phòng khách sạn và tour ghép, xử lý trung bình 40.000 giao dịch đặt chỗ mỗi ngày, riêng đợt khuyến mãi Tết hoặc hè có thể vọt lên 300.000 giao dịch trong một ngày duy nhất. Mỗi giao dịch trải qua hai bước bắt buộc: giữ chỗ tạm thời (hold) trong một khoảng thời gian sống nhất định (thường 10–15 phút) để khách hoàn tất thanh toán, và xác nhận cuối cùng (confirm) khi thanh toán thành công. Nếu hệ thống giữ chỗ sai — cho phép hai khách hàng cùng giữ một ghế máy bay hạng thương gia cuối cùng hoặc cùng một phòng suite duy nhất còn trống — hậu quả không chỉ là hoàn tiền và bồi thường mà còn là mất uy tín với đối tác hãng bay/khách sạn, những đơn vị đã tin tưởng giao tồn kho thực cho nền tảng quản lý. Một sàn OTA khu vực từng phải bồi thường hàng loạt và tạm ngừng bán vé của một hãng bay đối tác trong 2 tuần sau sự cố double-booking hàng loạt vào đợt cao điểm lễ.

Phạm vi bài viết bao trùm toàn bộ vòng đời một booking: tìm kiếm & khoá giá tạm thời, giữ chỗ (hold) với TTL rõ ràng, thanh toán, xác nhận cuối cùng, huỷ/hoàn tiền theo chính sách, và hậu kiểm đối soát tồn kho định kỳ. Ràng buộc nghiệp vụ quan trọng: mỗi ghế máy bay hoặc phòng khách sạn là một đơn vị tồn kho không thể chia nhỏ (indivisible unit) — không có khái niệm 'bán thừa 0.5 ghế'; và mọi thao tác ghi vào tồn kho phải đi qua một lớp khoá tập trung (Inventory Lock Service) để tránh race condition khi nhiều luồng đọc-sửa-ghi song song. Ràng buộc tuân thủ gồm: log giao dịch giữ chỗ/huỷ phải lưu tối thiểu 1 năm để phục vụ tranh chấp khiếu nại, và số tiền hoàn trả phải khớp chính xác với số tiền đã thu (không làm tròn sai lệch).

Kiến trúc đặt vé/giữ chỗ · Booking & hold architecture Booking Client Booking APIGateway InventoryLock Service Hold ExpiryScheduler PaymentService Seat/RoomInventory DB Hold Ledger(TTL/expiry) BookingState Store Ledger(payment log) Bất biến: 1 ghế/phòng chỉ thuộc về đúng 1 booking hiệu lực tại một thời điểm
Kiến trúc đặt vé/giữ chỗ với Inventory Lock Service và Hold Expiry Scheduler

Phạm vi tự động hoá

  • Kiểm thử API cho luồng hold/pay/confirm/cancel và service khoá tồn kho
  • Kiểm thử đồng thời (concurrency) mô phỏng nhiều khách cùng giữ 1 ghế/phòng
  • Kiểm thử k6 cho tải cao giờ cao điểm săn vé khuyến mãi
Bài này giả định hệ thống có test-only endpoint để tạo dữ liệu tồn kho (chuyến bay/phòng) cố định và reset trạng thái hold/booking giữa các lần chạy.

2. Kiến trúc hệ thống & luồng nghiệp vụ

Kiến trúc gồm 5 thành phần chính: Booking API Gateway (điều phối request từ client, sinh idempotency key cho mỗi thao tác ghi), Inventory Lock Service (giữ khoá phân tán theo seatId/roomId, đảm bảo chỉ một luồng được sửa trạng thái ghế tại một thời điểm), Hold Expiry Scheduler (quét định kỳ các hold quá TTL để tự động trả ghế về tồn kho), Payment Service (xử lý thanh toán, gắn liền idempotency key với giao dịch), và Booking State Store (lưu trạng thái booking theo máy trạng thái hữu hạn HOLD → PAID → CONFIRMED, hoặc HOLD → EXPIRED, hoặc CONFIRMED → CANCELLED). Luồng nghiệp vụ chuẩn: khách chọn ghế, Booking API gọi Inventory Lock Service để khoá ghế và tạo bản ghi Hold với expiresAt = now + TTL; nếu khoá thành công, khách được chuyển sang bước thanh toán; khi Payment Service xác nhận thu tiền thành công trước thời điểm expiresAt, booking chuyển sang CONFIRMED và ghế được trừ vĩnh viễn khỏi tồn kho khả dụng.

Điểm khó khi kiểm thử

Điểm khó nhất là kiểm chứng tính đúng đắn của khoá phân tán dưới tải đồng thời cao: hai request hold cùng một seatId gửi tới trong khoảng vài mili-giây phải có đúng một request thắng, request còn lại phải nhận lỗi rõ ràng (409 Conflict) thay vì cả hai đều 'thành công' rồi ghi đè lẫn nhau ở tầng dữ liệu — đây là lỗi race condition kinh điển mà kiểm thử tuần tự thông thường không bao giờ phát hiện được. Điểm khó thứ hai là xử lý biên thời gian: khi request thanh toán đến rất sát thời điểm hold hết hạn, hệ thống cần một quy tắc rõ ràng (ví dụ: chỉ chấp nhận nếu Payment Service ghi nhận thành công TRƯỚC khi Hold Expiry Scheduler xử lý bản ghi đó) để tránh tình trạng vừa hết hạn vừa được xác nhận thanh toán — hai trạng thái mâu thuẫn tồn tại song song. Cuối cùng, việc trả ghế về tồn kho khi hold hết hạn phải là thao tác nguyên tử (atomic) và idempotent, vì scheduler có thể quét trùng cùng một bản ghi ở hai lần chạy gần nhau do lỗi mạng hoặc restart.

💡 Luôn kiểm thử khoá phân tán bằng cách gửi N request đồng thời thật sự (Promise.all hoặc công cụ tải) chứ không phải N request tuần tự — race condition chỉ lộ diện khi có tranh chấp thời gian thực.

3. Mô hình dữ liệu & bất biến nghiệp vụ (oracle)

Mô hình dữ liệu cốt lõi gồm 5 thực thể: Inventory (seatId/roomId, tripId, status AVAILABLE/HELD/SOLD), Hold (id, seatId, bookingId, createdAt, expiresAt, version), Booking (id, customerId, items, status HOLD/PAID/CONFIRMED/EXPIRED/CANCELLED, totalAmount), Payment (bookingId, amount, idempotencyKey, status), và AuditLog (mọi thay đổi trạng thái, ghi kèm actor và lý do). Bất biến quan trọng nhất — cũng là oracle chính của toàn bộ bài toán — là: tại mọi thời điểm, mỗi Inventory item chỉ được liên kết với đúng một Hold hoặc Booking đang hiệu lực (không EXPIRED, không CANCELLED); nói cách khác, không bao giờ tồn tại hai booking CONFIRMED cùng trỏ tới một seatId — đây chính là bất biến chống double-booking.

  • Bất biến 1: Không double-booking — 1 seatId chỉ có tối đa 1 Hold/Booking hiệu lực tại một thời điểm
  • Bất biến 2: Hold hết hạn (expiresAt < now, chưa PAID) phải được trả về tồn kho AVAILABLE trong thời gian ngắn (SLA quét)
  • Bất biến 3: Booking.totalAmount = Σ(giá từng item + phụ phí), khớp chính xác với Payment.amount đã thu
  • Bất biến 4: Mọi thao tác pay/confirm/cancel đều idempotent theo idempotencyKey — gửi lại không tạo bản ghi trùng
  • Bất biến 5: Trạng thái Booking chỉ đi theo đúng máy trạng thái hữu hạn, không có bước nhảy trái phép (ví dụ EXPIRED không thể chuyển thẳng sang CONFIRMED)
🔒

Nội dung bị khoá

Bạn đang xem bản xem trước. Đăng nhập để mở khoá toàn bộ bài viết và tất cả tài liệu Tester/QA.

Đã xem 24% nội dung
  • Đọc trọn vẹn mọi bài viết
  • Lưu bài & ghi chú cá nhân
  • Theo dõi tiến độ đã đọc
  • Luyện phỏng vấn, ISTQB, Mock
Đăng nhập để đọc tiếp

Chưa có mã? Lấy mã qua Fanpage / Zalo CyberSoft.

0

💬 Bình luận (0)

Bạn có thể đọc mọi bình luận. Đăng nhập để bình luận

Chưa có bình luận nào. Hãy là người đầu tiên!