Tại Slash, chúng tôi đang xử lý hàng tỷ đô la chi tiêu hàng năm. Với mức tăng trưởng hơn 1000% trong 12 tháng qua, chúng tôi liên tục giải quyết các vấn đề mới với quy mô ngày càng lớn. Khi lượng dữ liệu tăng lên và hệ thống trở nên phức tạp hơn, các giả định về mối quan hệ dữ liệu mà chúng tôi đưa ra thường bị phá vỡ. Chúng tôi gặp phải các vấn đề từ nhiều nguồn: dữ liệu bổ sung không chính xác, lỗi trong mã nguồn sản xuất và những sai sót nhỏ trong quá trình di chuyển dữ liệu. Theo thời gian, việc xác minh tính chính xác của dữ liệu trở nên ngày càng khó khăn, dẫn đến việc chúng tôi phát triển hệ thống mà chúng tôi gọi là Health Checks — một hệ thống được thiết kế để liên tục xác minh tính chính xác của dữ liệu trong môi trường sản xuất.
Kiểm tra sức khỏe là công cụ giúp chúng ta xác minh các bất biến trong mã nguồn của mình. Khi xây dựng hệ thống, chúng ta luôn thiết kế chúng dựa trên một tập hợp các bất biến ngầm định và rõ ràng, tức là các quy tắc/giả định mà chúng ta mong đợi luôn đúng để có thể xây dựng các hệ thống phức tạp hơn trên nền tảng đó.
Một ví dụ đơn giản về một hằng số: trongThẻBảng trong cơ sở dữ liệu của chúng tôi, trong đó mỗi hàng đại diện cho một thẻ tín dụng, có mộttrạng tháiCột trong đó một trong các giá trị có thể là “đóng” và mộtLý do đóng cửaCột có thể chứa giá trị NULL. Một điều kiện không đổi là rằngtrạng tháiCột được coi là "đóng" **nếu và chỉ nếu**Lý do đóng cửaTrường này KHÔNG ĐƯỢC RỖNG.
Ví dụ trên không nhất thiết là điều kiện mà chúng ta sẽ viết một kiểm tra sức khỏe, nhưng là một ví dụ về một bất biến mà chúng ta đảm bảo luôn đúng khi làm việc với phần mã nguồn này.
Thiết kế kiểm tra sức khỏe ban đầu của chúng tôi
Phiên bản đầu tiên của quy trình kiểm tra sức khỏe của chúng tôi trông giống như sau:
Ban đầu, chúng tôi thiết kế các kiểm tra sức khỏe để được định nghĩa bằng các truy vấn SQL. Chúng tôi sẽ chạy một truy vấn SQL trên một bảng để tìm các "lỗi tiềm ẩn". Tuy nhiên, vì việc chạy các truy vấn SQL trên các bảng dữ liệu lớn tốn kém, chúng tôi đã chạy các kiểm tra này trên một bản sao đọc nhất quán theo thời gian (trong trường hợp của chúng tôi, đó là Snowflake). Đối với mỗi kết quả trả về từ truy vấn, chúng tôi sau đó sẽ chạy một kiểm tra trên cơ sở dữ liệu sản xuất chính để đảm bảo đó không phải là kết quả dương tính giả. Thiết kế này có một số vấn đề:
- Chúng tôi phải duy trì hai truy vấn. Truy vấn đầu tiên sẽ được thực thi trên bản sao đọc (read replica) cho toàn bộ bảng. Các nhà phát triển phải chủ động xem xét và xử lý vấn đề phân trang (pagination) cho truy vấn này. Truy vấn thứ hai sẽ được thực thi trên môi trường sản xuất (production) cho từng kết quả riêng lẻ mà truy vấn đầu tiên trả về.
- Các truy vấn đối với toàn bộ bảng sẽ trở nên phức tạp và khó đọc / khó duy trì hơn vì chúng ta sẽ cố gắng nhồi nhét một lượng lớn logic kinh doanh vào một truy vấn SQL duy nhất.
Lần kiểm tra sức khỏe thứ hai
Chúng tôi đã quyết định:
- Các kiểm tra sức khỏe luôn phải được thực hiện trên cơ sở dữ liệu sản xuất chính của chúng ta. Nếu không, chúng ta có thể không phát hiện được tất cả các vấn đề thực sự xảy ra.
- Thực hiện "quét toàn bộ bảng" trong một truy vấn SQL là điều không nên, nhưng việc thực hiện một "quét toàn bộ bảng" có kiểm soát bằng cách lặp qua từng hàng và thực hiện kiểm tra sức khỏe thực tế là chấp nhận được — miễn là tải hệ thống ổn định, có thể dự đoán được và không tăng đột biến, mọi thứ thường sẽ ổn.
- Thực hiện lặp lại theo thời gian trên các bảng trong môi trường sản xuất (và ẩn đi quá trình đó đối với nhà phát triển) giúp đơn giản hóa trải nghiệm phát triển (DX) của chúng ta rất nhiều:
- Chúng ta không còn cần phải duy trì hai truy vấn SQL phức tạp với phân trang. Chúng ta chỉ cần định nghĩa logic ứng dụng để kiểm tra tình trạng hoạt động của một mục duy nhất.
- Mỗi kiểm tra sức khỏe được định nghĩa trên toàn bộ bảng (trong tương lai, chúng ta có thể chọn mở rộng kiểm tra sức khỏe để nó có thể lặp qua định nghĩa chỉ mục cụ thể).
Một bài học quan trọng mà đội ngũ chúng tôi đã rút ra qua thời gian là các hệ thống có thể dự đoán được và duy trì tải đều đặn là lý tưởng nhất. Nguyên nhân chính gây ra các vấn đề trong quá trình sản xuất thường là những thay đổi đột ngột và không thể dự đoán được, chẳng hạn như sự gia tăng đột biến về tải cơ sở dữ liệu (DB) hoặc sự thay đổi đột ngột trong trình lập kế hoạch truy vấn nội bộ của cơ sở dữ liệu.
Mặt trái của việc áp dụng tải liên tục lên hệ thống, đặc biệt là đối với cơ sở dữ liệu và hàng đợi, là nó có thể trông giống như một sự lãng phí. Trước đây, tôi từng cho rằng tốt hơn là không áp dụng tải lên hệ thống theo mặc định và chỉ thực hiện công việc vào những thời điểm cần thiết trong ngày. Tuy nhiên, điều này có thể dẫn đến các tải công việc đột biến, đôi khi làm suy giảm hệ thống một cách bất ngờ. Trên thực tế, chúng tôi đã nhận thấy rằng thường tốt hơn là có một tải trọng nhỏ liên tục chạy trên cơ sở dữ liệu trong một khoảng thời gian dài, ngay cả khi tải trọng liên tục này chỉ đóng góp khoảng 1-5% sử dụng CPU 24/7. Khi tải trọng có thể dự đoán được, việc theo dõi tỷ lệ sử dụng trên toàn hệ thống trở nên dễ dàng. Sự dự đoán này cho phép chúng tôi mở rộng quy mô theo chiều ngang một cách tự tin và lập kế hoạch trước cho các vấn đề hiệu suất tiềm ẩn trong tương lai.
Có một bài viết sâu sắc về chủ đề này từ đội ngũ AWS: Độ tin cậy, làm việc liên tục và một tách cà phê ngon.
Phiên bản thứ hai của các kiểm tra sức khỏe hiện có dạng như sau:
Với thiết kế này, việc thêm các kiểm tra sức khỏe mới vào một thực thể duy nhất trở nên rất dễ dàng, vì mỗi kiểm tra sức khỏe được định nghĩa là một hàm asynchronous đơn lẻ. Chúng ta có thể thực hiện các kiểm tra sức khỏe này lặp lại trên toàn bộ bảng nhiều lần trong ngày mà không cần phải đánh đổi giữa tần suất, vì tải trọng là cố định. Đối với kiểm tra sức khỏe trên, mỗi thực thể thực hiện một truy vấn nhanh đến bảng `LimitRule`. Các kiểm tra này chạy liên tục, tạo ra tải trọng tối thiểu nhưng cố định trên cơ sở dữ liệu tại bất kỳ thời điểm nào.
Cách hệ thống này hoạt động bên trong có thể được đơn giản hóa thành thiết kế sau đây:
- Chúng tôi thực hiện các kiểm tra của mình trên Temporal để đảm bảo việc thực thi cuối cùng. Chúng tôi sử dụng sản phẩm "schedules" của Temporal để đảm bảo rằng cron của chúng tôi được chạy mỗi phút.
- Mỗi phút, chúng tôi kiểm tra tất cả các tác vụ cần được lên lịch ngay lập tức và đưa chúng vào hàng đợi tác vụ. Chúng tôi điều chỉnh hàng đợi tác vụ sao cho không bao giờ có hơn một số lượng RPS nhất định được gửi đến cơ sở dữ liệu của chúng tôi.
- Mỗi hoạt động được thực thi đều thực hiện một loại công việc "không thay đổi". Không có độ phức tạp thời gian O(N) hoặc các độ phức tạp thời gian khác tăng theo tổng số thực thể trong cơ sở dữ liệu. Điều này đảm bảo rằng các hoạt động được thực thi nhanh chóng, dễ dự đoán và sẽ không làm tắc nghẽn hàng đợi tác vụ.
Tiếp theo là gì?
Kiểm tra sức khỏe hiện nay đóng vai trò quan trọng trong quy trình kiểm thử của chúng tôi. Đây là nền tảng cho các bài kiểm thử xác minh sản phẩm liên tục của chúng tôi. Khi công ty phát triển, chúng tôi cần thực hiện ngày càng nhiều bài kiểm thử tổng quát để đảm bảo sản phẩm luôn ổn định và hoạt động tốt. Chúng tôi đã phải thử nghiệm nhiều lần mới đạt được kết quả như mong muốn. Chúng tôi đã muốn triển khai một hình thức kiểm thử sổ cái / xác minh dữ liệu trong sản xuất trong ba năm qua. Tuy nhiên, phải đến một năm rưỡi trước, chúng tôi mới thực sự xây dựng được hệ thống này. Nhìn chung, một số bài học quan trọng mà chúng tôi đã rút ra là:
- Gửi đi bất kỳ thứ gì, và liên tục cải tiến nó theo thời gian. Chúng tôi không thể đạt được phiên bản thứ hai của các kiểm tra sức khỏe mà không có phiên bản đầu tiên đã được triển khai.
- Giữ cho mọi thứ đơn giản — chúng tôi muốn giúp các kỹ sư dễ dàng nhất có thể trong việc xây dựng và duy trì các kiểm tra sức khỏe. Logic kinh doanh phức tạp được nhúng trong nhiều truy vấn SQL không bao giờ là điều thú vị để làm việc với.
- Chạy các kiểm tra của chúng tôi trực tiếp trên môi trường sản xuất — đó là nguồn dữ liệu chính xác của chúng tôi, và nếu chúng tôi chạy các kiểm tra trên một nguồn dữ liệu khác, điều đó sẽ tự nhiên phức tạp hơn và dễ dẫn đến kết quả dương tính/âm tính giả.
Hiện nay, các cuộc kiểm tra sức khỏe là bước đầu tiên chúng tôi thực hiện để kiểm tra và xác minh hệ thống của mình một cách hiệu quả hơn so với các phương pháp truyền thống như kiểm thử và giám sát. Khi tiếp tục phát triển, chúng tôi sẽ liên tục đánh giá và tìm ra các phương pháp mới để duy trì sự ổn định của sản phẩm.