- vừa được xem lúc

Series duthaho đi phỏng vấn: System Design về Soft Delete

0 0 5

Người đăng: duthaho

Theo Viblo Asia

Mọi người nhớ ủng hộ bài viết gốc ở blog https://duthaho.substack.com/p/toi-i-phong-van-system-design-ve của mình nhé

Bắt đầu buổi phỏng vấn

(Anh Minh): Chào duthaho, tôi là Minh, Kỹ sư Phần mềm Cấp cao tại công ty Z. Cảm ơn bạn đã tham gia buổi phỏng vấn hôm nay. Trong khoảng 45 phút tới, chúng ta sẽ cùng nhau thảo luận về một bài toán thiết kế hệ thống. Mục tiêu chính là để tôi hiểu hơn về tư duy giải quyết vấn đề, cách bạn phân tích các ưu nhược điểm (trade-offs) và đưa ra quyết định về mặt kiến trúc.

Bạn cứ thoải mái trình bày suy nghĩ, vẽ sơ đồ nếu cần, và đặt câu hỏi nhé. Đây là một buổi thảo luận mở. Bạn có câu hỏi gì trước khi chúng ta bắt đầu không ạ?

(duthaho): Dạ chào anh Minh, em đã sẵn sàng ạ. Em không có câu hỏi gì thêm.

(Anh Minh): Được rồi, chúng ta bắt đầu nhé.

Trình bày vấn đề

(Anh Minh): Giả sử công ty chúng ta có một hệ thống thương mại điện tử. Thành phần cốt lõi là một bảng database tên là orders, chứa dữ liệu về các đơn hàng. Bảng này hiện tại rất lớn, khoảng 500 triệu bản ghi và tốc độ tăng trưởng rất nhanh, khoảng 1 triệu bản ghi mới mỗi ngày.

Yêu cầu đặt ra là: đội ngũ sản phẩm muốn có chức năng "xóa mềm" (soft delete) cho các đơn hàng.

Bạn hãy thiết kế một giải pháp cho yêu cầu này.

(duthaho): Dạ vâng. Đây là một bài toán rất thú vị. Trước khi đi vào giải pháp, em xin phép được đặt một vài câu hỏi để làm rõ yêu cầu ạ.

(Anh Minh): Rất tốt, mời bạn.

(duthaho):

  1. Về chức năng: Mục đích chính của việc xóa mềm này là gì ạ? Có phải để cho người dùng cuối tự khôi phục, hay chỉ cho admin sử dụng để khôi phục khi có sự cố, hay là để phục vụ mục đích audit trail?

  2. Dữ liệu sau khi xóa mềm có cần được truy vấn thường xuyên không? Và ai là người có quyền truy vấn?

  3. Về phi chức năng: Chính sách lưu trữ (retention policy) cho dữ liệu đã xóa mềm là bao lâu? Ví dụ 30 ngày, 90 ngày, hay vĩnh viễn? Sau thời gian đó có cần xóa vĩnh viễn không?

  4. Và quan trọng nhất, yêu cầu về hiệu năng đối với các truy vấn trên dữ liệu "còn sống" (active data) là như thế nào? Thao tác xóa mềm có được phép làm ảnh hưởng đến hiệu năng của các giao dịch đang diễn ra không ạ?

(Anh Minh): Những câu hỏi rất xác đáng, duthaho. Chúng ta hãy làm việc với giả định sau:

  • Mục đích: Chủ yếu để admin có thể khôi phục dữ liệu trong vòng 30 ngày. Dữ liệu đã xóa không cần truy vấn thường xuyên.

  • Xóa vĩnh viễn: Sau 30 ngày, dữ liệu cần được xóa vĩnh viễn (purge).

  • Hiệu năng: Yêu cầu quan trọng nhất là hiệu năng của các truy vấn trên dữ liệu active (ví dụ: xem danh sách đơn hàng, cập nhật trạng thái đơn hàng) gần như không được bị ảnh hưởng.

Với những thông tin này, bạn sẽ bắt đầu như thế nào?

Thảo luận giải pháp

(duthaho): Dạ, với yêu cầu đó, em thấy có một vài hướng tiếp cận với các trade-off khác nhau.

  • Hướng 1: Dùng cột cờ (Flag-based). Đây là cách đơn giản nhất, ta thêm một cột deleted_at vào bảng orders. Khi xóa thì UPDATE cột này, và mọi câu SELECT sẽ phải thêm điều kiện WHERE deleted_at IS NULL.

  • Hướng 2: Di chuyển sang bảng lưu trữ (Archive Table). Ta tạo một bảng orders_archive có cấu trúc tương tự. Khi xóa, ta INSERT bản ghi vào bảng archive rồi DELETE khỏi bảng chính.

(Anh Minh): OK. Bạn có thể phân tích sâu hơn về ưu và nhược điểm của hai cách này, đặc biệt là với bảng orders 500 triệu bản ghi không?

(duthaho): Dạ được ạ.

  • Với Hướng 1, ưu điểm là đơn giản và dễ khôi phục. Nhưng nhược điểm với bảng lớn là rất nghiêm trọng. Index sẽ bị phình to (bloating) vì phải chứa cả dữ liệu đã xóa, khiến các thao tác đọc và ghi trên bảng chính ngày càng chậm. Việc xóa vĩnh viễn bằng DELETE ... WHERE deleted_at < ... sẽ là một cơn ác mộng, có thể gây lock bảng và ảnh hưởng toàn hệ thống.

  • Với Hướng 2, ưu điểm là nó giải quyết được vấn đề cốt lõi: giữ cho bảng orders chính luôn gọn nhẹ và hiệu năng cao. Tuy nhiên, nhược điểm là thao tác xóa trở nên phức tạp hơn, cần xử lý trong transaction để đảm bảo toàn vẹn.

Vì yêu cầu đặt nặng về hiệu năng cho bảng chính, em sẽ nghiêng về Hướng 2. Tuy nhiên, em muốn đề xuất một kiến trúc cải tiến hơn một chút để nó linh hoạt và đáng tin cậy hơn.

(Anh Minh): Rất hay. Tôi đang chờ nghe đây.

(duthaho): Em đề xuất một kiến trúc hướng sự kiện (Event-Driven).

  1. Khi API nhận yêu cầu xóa, nó sẽ không xóa trực tiếp mà chỉ gửi một message chứa order_id cần xóa vào một Message Queue (ví dụ: RabbitMQ hoặc Kafka). Sau đó trả về 202 Accepted ngay cho người dùng.

  2. Sẽ có một nhóm các service worker riêng biệt lắng nghe từ queue này.

  3. Khi nhận được message, worker sẽ thực hiện công việc di chuyển dữ liệu từ orders sang orders_archive trong một transaction.Kiến trúc này giúp tách rời tác vụ xóa khỏi luồng request chính, đảm bảo API phản hồi nhanh và hệ thống có khả năng mở rộng tốt bằng cách tăng số lượng worker.

(Anh Minh): Một giải pháp rất tốt, nó giải quyết được vấn đề về hiệu năng và khả năng mở rộng. Giờ hãy đi sâu hơn vào một vài tình huống thực tế nhé.

Giả sử có yêu cầu xóa toàn bộ đơn hàng của một khách hàng lớn, có thể lên tới hàng chục nghìn đơn. Việc gửi hàng chục nghìn message vào queue có vẻ không tối ưu. Bạn sẽ xử lý yêu cầu xóa theo lô (batch delete) này như thế nào?

(duthaho): Dạ, đó là một vấn đề rất thực tế. Thay vì gửi N message, em sẽ thiết kế một hệ thống xử lý job bất đồng bộ.

  1. Tạo một bảng mới là batch_jobs để lưu thông tin về các job (loại job, điều kiện, trạng thái, tiến trình).

  2. API sẽ tạo một bản ghi trong bảng này, rồi gửi một message duy nhất chứa job_id vào một queue dành riêng cho các job nặng.

  3. Worker chuyên xử lý batch sẽ nhận job, đọc điều kiện và xử lý xóa theo từng chunk nhỏ (ví dụ 10,000 bản ghi/lần) để tránh các transaction quá lớn gây lock database.

(Anh Minh): Rất chi tiết. Vậy với hệ thống batch job đó, nếu một job lớn đang chạy và có một job nhỏ nhưng khẩn cấp (ví dụ: yêu cầu xóa dữ liệu theo GDPR) cần được thực thi ngay, bạn làm thế nào để ưu tiên job khẩn cấp?

(duthaho): Để xử lý ưu tiên, em sẽ không dùng một queue chung nữa mà chia thành nhiều queue, ví dụ: high_priority_queue và normal_priority_queue. Sẽ có các nhóm worker riêng biệt cho từng queue, đảm bảo các job quan trọng không bao giờ bị chặn bởi các job thông thường.

(Anh Minh): Tuyệt vời. Câu hỏi cuối cùng về mặt kỹ thuật: Job xóa có thể chạy trong hàng giờ. Làm sao để người dùng (admin) có thể hủy (cancel) một job đang chạy nếu họ phát hiện ra sai sót?

(duthaho): Để hủy job, em sẽ dùng một cơ chế "hợp tác".

  1. API hủy sẽ cập nhật trạng thái của job trong bảng batch_jobs thành CANCELING.

  2. Worker ở đầu mỗi vòng lặp xử lý chunk mới sẽ kiểm tra trạng thái của job trong DB.

  3. Nếu thấy trạng thái là CANCELING, nó sẽ dừng lại, cập nhật trạng thái cuối cùng là CANCELED và thoát.Để tránh query DB liên tục, có thể dùng cơ chế Redis Pub/Sub. Worker sẽ SUBSCRIBE vào một kênh của Redis, và API PUBLISH một tín hiệu hủy vào kênh đó, giúp worker nhận được thông báo gần như tức thời.

Tổng kết

(Anh Minh): Cảm ơn duthaho. Phần trình bày của bạn rất ấn tượng. Bạn đã đi từ một yêu cầu khá mơ hồ, chủ động đặt câu hỏi để làm rõ, phân tích các hướng tiếp cận khác nhau, đề xuất một kiến trúc vững chắc và xử lý được các trường hợp phức tạp như batch job, ưu tiên và hủy tác vụ.

Tôi đã có đủ thông tin mình cần rồi. Bây giờ, bạn có câu hỏi nào muốn hỏi tôi về công ty, đội ngũ hay dự án không?

(duthaho): Dạ em cảm ơn anh Minh. Em muốn hỏi thêm về...

Bình luận

Bài viết tương tự

- vừa được xem lúc

Một số lưu ý cải thiện performance khi làm việc với Rails

Khi làm việc với ruby on rails chắc hẳn chúng ta sẽ làm việc với active record rất nhiều. Tuy nhiên có nhiều điều có thể ta vẫn chưa thực sự hiểu, ví dụ như ActiveRecord execute SQL query như thế nào? Và cũng còn khá nhiều lập trình viên khác cũng không để ý tới điều này.

0 0 128

- vừa được xem lúc

3 cách tối ưu hóa hiệu suất Rails views

. Chào các bạn, chào các bạn. Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu một số kỹ thuật tối ưu hóa hiệu suất đơn giản và hiệu quả mà bạn có thể sử dụng ngay từ khi bắt đầu viết code.

0 0 83

- vừa được xem lúc

Kĩ thuật Memoize cải thiện performance

Kĩ thuật Memoize cải thiện performance. Memoize là một kĩ thuật cache lại giá trị trả về của các hàm dựa trên tham số truyền vào nó.

0 0 55

- vừa được xem lúc

MySQL Performance Tuning With Index

Lâu rồi không viết bài trên viblo nên để cái tiêu đề hơi công nghiệp =)) Xin chào các bạn, đợt rồi mình mới được làm vài task cải thiện thiện tốc độ truy vấn mysql nên hôm nay xin phép chia sẻ lại chú

0 0 100

- vừa được xem lúc

Làm thế nào để tối ưu những câu query trong Laravel ?

Đặt vấn đề. Nếu thấy ứng dụng của bạn đang chạy chậm , thì có một số nguyên nhân dẫn đến tình trạng này ví dụ như :.

0 0 2.1k

- vừa được xem lúc

Kiểm tra chịu tải của server với K6.io (Phần 1)

Lời mở đầu. Để có được một sản phẩm phần mềm tốt việc test performance là việc hết sức quan trọng để xác định độ ổn định của hệ thống, Để test được hắn một mình bạn không thể gửi request bằng cơm để t

0 0 219