← Quay lại danh sách

Tưởng cần Circuit Breaker. Hoá ra không.

Mình tưởng mình đang xây một hệ thống resilient. Thực ra mình đang overengineering một vấn đề không tồn tại.

Đừng xây cầu 8 làn cho 2 cái xe đạp

Có một nghịch lý kỳ lạ trong ngành phần mềm: càng học nhiều về system design, càng biết nhiều pattern, bạn càng dễ overengineering.

Mình từng nghĩ mình đang thiết kế “hệ thống vững chắc”. Thực tế, mình chỉ đang thêm complexity vào chỗ không cần thiết. Đây là câu chuyện mình học được bài học đó theo cách đau nhất.

Mình được giao thiết kế một CDC (Change Data Capture) pipeline để đồng bộ dữ liệu từ database chính sang search engine. Khá bình thường. Nhưng trong đầu mình, nó không phải “chỉ là một pipeline.” Nó là cơ hội để xây thứ gì đó “production-grade.”

Nên mình bắt đầu nghĩ:

  • Nếu service phía sau chết thì sao?
  • Nếu lỗi lan dây chuyền thì sao?
  • Nếu hệ thống không ổn định khi tải cao thì sao?

Và câu trả lời hiển nhiên hiện ra: “Mình cần Circuit Breaker.”

Cái bẫy của engineer

Nghe thì rất đúng. Đúng sách vở luôn:

  1. Ngăn lỗi lan truyền.
  2. Fail nhanh.
  3. Bảo vệ tài nguyên hệ thống.

Mình dành nguyên một tuần xây một Circuit Breaker sạch sẽ, có cấu trúc rõ ràng cho pipeline. Mình tự hào lắm. Mình nghĩ: “Đây mới là engineering thật sự.”

Thực tế phũ phàng

Khi mình trình bày thiết kế cho sếp, anh ấy nhìn một lúc rồi hỏi một câu đơn giản nhưng sắc lẹm:

“Cái này để làm gì?”

Mình tự tin giải thích: “Nếu service phía sau lỗi, mình mở circuit ngay lập tức. Như vậy tránh blocking, giảm áp lực, ngăn lỗi lan truyền.”

Anh ấy dừng lại rồi nói:

“Bỏ đi. Data có bao nhiêu đâu. Lỗi thì retry. Em đang xây cầu 8 làn cho 2 cái xe đạp.”

Ban đầu mình không đồng ý

Mình nghĩ anh ấy đang đơn giản hoá quá mức. Nhưng sau khi lùi lại một bước và phân tích hệ thống kỹ hơn, mình nhận ra điều quan trọng: Mình không sai vì dùng Circuit Breaker. Mình sai vì dùng nó sai ngữ cảnh.

Đây không phải chuyện CDC — mà là áp dụng sai pattern

Circuit Breaker được thiết kế cho một loại vấn đề rất cụ thể: Hệ thống đồng bộ với latency ảnh hưởng trực tiếp đến user.

Ví dụ:

  • API call giữa các service.
  • Request cần response ngay lập tức.
  • Tình huống mà chờ quá lâu sẽ làm giảm trải nghiệm user (UX).

Trong những trường hợp đó, Fail Fast là chiến lược đúng. Nhưng CDC không phải loại hệ thống đó. Nó là bất đồng bộ, event-driven, và eventually consistent. Quan trọng nhất: Không có user nào đang chờ response.

Sự lệch pha cốt lõi

Bằng việc thêm Circuit Breaker, mình đang áp tư duy đồng bộ vào hệ thống bất đồng bộ. CDC không cần “fail nhanh”. Nó cần fail an toàn.

Một CDC pipeline thiết kế tốt đã có sẵn cơ chế chịu lỗi, không cái nào cần Circuit Breaker:

  1. Queue chính là buffer: Nếu downstream chết, message chỉ nằm chờ. Không mất dữ liệu.
  2. Retry là mặc định: Thay vì cắt đứt luồng xử lý, dùng exponential backoff.
  3. Dead Letter Queue (DLQ): Nếu retry liên tục thất bại, chuyển message sang DLQ để xử lý sau.
  4. Idempotency: Vì retry là không tránh khỏi, xử lý phải đảm bảo an toàn khi lặp lại.

YAGNI — You Ain’t Gonna Need It

YAGNI thường bị hiểu nhầm là lười biếng. Không phải. Nó là kỷ luật.

Nhìn lại, vấn đề không phải kỹ thuật mà là tâm lý. Mình không thêm Circuit Breaker vì hệ thống cần nó. Mình thêm vì một câu “nếu như” giả định.

YAGNI nghĩa là:

  • Giải quyết vấn đề tồn tại hôm nay, không phải vấn đề có thể tồn tại ngày mai.
  • Giữ hệ thống đơn giản để dễ phát triển. Complexity khiến thay đổi khó hơn, không phải dễ hơn.
  • Ưu tiên tốc độ, không phải sự hoàn hảo. Đặc biệt trong môi trường thay đổi nhanh.

Kết

Overengineering không làm hệ thống an toàn hơn. Nó chỉ làm bạn cảm thấy an toàn hơn.

Kỹ năng thực sự của senior engineer không phải biết khi nào dùng pattern — mà là biết khi nào nên bỏ nó đi.