# Khi kiến trúc sư của khách nói thiết kế sẽ sập: biến cuộc tranh luận thành một bài load test chung

> Không ai thắng được bằng lời một cuộc tranh cãi về chuyện chưa xảy ra, nhưng hai bên có thể chốt nó bằng một ngưỡng đạt/trượt cùng ký trước khi bấm chạy.

Bản gốc: https://fdetimes.net/vi/bach-khoa/bat-dong-thiet-ke-voi-kien-truc-su-khach-bang-load-test-chung/

Cuộc họp review kiến trúc đã kéo dài 40 phút. Bạn vừa trình bày thiết kế dịch vụ tìm kiếm tài liệu sẽ deploy ở phía khách thì kiến trúc sư trưởng của họ khoanh tay: “Mọi request đều đi đồng bộ qua một database. Giờ cao điểm là sập.”

Bạn tin thiết kế của mình ổn, còn anh ấy tin nó sẽ sập. Hai người đang cãi về một chuyện chưa xảy ra nên không ai thắng được bằng lời, và cãi thêm phút nào thì khách bớt tin bạn phút đó.

Cãi hay hơn không giải quyết được gì. Thay vào đó, bạn mời anh ấy cùng thiết kế chính bài kiểm tra có thể chứng minh anh ấy đúng. Đây là kỹ năng phân biệt một FDE giỏi với người chỉ biết viết code: biến bất đồng thành một thí nghiệm mà hai bên đã thống nhất cách chấm điểm từ trước.

## Vì sao một bài test lại hạ nhiệt được cuộc cãi vã?

Vì nó đổi câu hỏi. “Có sập không” là chuyện niềm tin. “Sập ở mức tải nào, thành phần nào gãy trước” là câu hỏi kỹ thuật, và kỹ sư nào cũng muốn biết đáp án.

Với Google, phải biết giới hạn của hệ thống thì mới vận hành nó an toàn được. Sách SRE của họ viết thẳng rằng kỹ sư dùng stress test để tìm giới hạn của một web service, và load test chỉ ra điểm gãy, một kiến thức nền tảng cho capacity planning.

Như vậy, lời cảnh báo của kiến trúc sư không phải là thứ gây phiền. Đó là một yêu cầu kiểm thử chưa ai viết ra.

Khi bạn nói “anh nói đúng, mình chưa biết điểm gãy ở đâu, mình cùng tìm nhé”, anh ấy không còn là người phản biện mà thành đồng tác giả. Kết quả thế nào thì anh ấy cũng đã đồng ý với cách đo từ đầu.

**Điểm mấu chốt:** Đừng cố thắng cuộc tranh luận; hãy cùng khách viết ra điều kiện để một bên có thể thua.

## Ngưỡng phải được ký trước khi bấm chạy

Sai lầm tai hại nhất là chạy test trước rồi mới bàn xem kết quả nghĩa là gì. Lúc đó bên nào cũng đọc biểu đồ theo hướng có lợi cho mình. AWS Well-Architected xếp đúng chuyện này vào anti-pattern: không định nghĩa yêu cầu hiệu năng rõ ràng, đo được, như response time, throughput và mục tiêu khả năng mở rộng.

Grafana k6 có sẵn cơ chế cho việc này là threshold, tức tiêu chí đạt/trượt bạn khai báo cho từng metric. Ngưỡng nằm ngay trong file test, nên điều hai bên đã đồng ý được lưu trong version control chứ không chỉ là một dòng trong biên bản họp.

Việc thứ hai cần chốt là chạy ở đâu. AWS cảnh báo môi trường test thu nhỏ có thể dự đoán sai hành vi trên production. Nếu bạn chạy trên một máy staging bé xíu và hệ thống trụ được, kiến trúc sư hoàn toàn có lý khi bác kết quả, nên hãy để chính anh ấy chọn môi trường.

## Một ví dụ làm từ đầu đến cuối

Hãy hình dung tình huống trên với vài con số giả định. Sau 20 phút làm việc, hai bên thống nhất tải bình thường là 50 người dùng đồng thời, còn giờ cao điểm mà kiến trúc sư lo là gấp bốn lần. Ngưỡng đạt ở tải bình thường là p95 dưới 800 ms và tỉ lệ lỗi dưới 1%.

Kiến trúc sư thêm một câu hỏi bạn chưa nghĩ tới: nếu hệ thống gục lúc cao điểm, tải phải giảm bao nhiêu thì nó mới tự đứng dậy? Sách SRE của Google cũng nêu đúng câu hỏi này: khi vài server sập dưới tải nặng, tải phải hạ đến đâu thì hệ thống mới ổn định lại. Vì thế kịch bản gồm ba pha.

```javascript
import http from 'k6/http';
import { check } from 'k6';
import { SharedArray } from 'k6/data';

// Truy vấn thật (đã ẩn danh) do khách cung cấp, không tự bịa
const queries = new SharedArray('queries', () => JSON.parse(open('./queries.json')));

export const options = {
  scenarios: {
    baseline: { executor: 'constant-vus', vus: 50, duration: '15m' },
    stress: {
      executor: 'ramping-vus', startTime: '15m', startVUs: 50,
      stages: [{ duration: '10m', target: 200 }, { duration: '5m', target: 200 }],
    },
    recovery: { executor: 'constant-vus', startTime: '30m', vus: 25, duration: '10m' },
  },
  thresholds: {
    // Ngưỡng hai bên đã ký: chỉ áp cho tải bình thường và pha hồi phục
    'http_req_duration{scenario:baseline}': ['p(95)<800'],
    'http_req_failed{scenario:baseline}': ['rate<0.01'],
    'http_req_duration{scenario:recovery}': ['p(95)<800'],
  },
};

export default function () {
  const q = queries[Math.floor(Math.random() * queries.length)];
  const res = http.post(`${__ENV.BASE_URL}/search`, JSON.stringify({ q }),
    { headers: { 'Content-Type': 'application/json' } });
  check(res, { 'status 200': (r) => r.status === 200 });
}
```

Để ý rằng pha `stress` không có threshold. Pha này không chạy để đạt mà để tìm điểm gãy. k6 phân loại test theo mục đích, và stress test là loại dùng để xem hệ thống chạy ra sao ở giới hạn, khi tải vượt mức trung bình kỳ vọng.

AWS cũng xếp việc không đẩy tải vượt giới hạn kỳ vọng vào anti-pattern.

Giả sử kết quả như sau: pha baseline đạt, ở pha stress p95 vọt lên khi số người dùng vượt khoảng 140, pha recovery đạt. Vậy là cả hai cùng đúng một phần. Thiết kế chịu được tải hiện tại nhưng có trần, và giờ cả hai cùng nhìn thấy trần đó là bao nhiêu.

Pha recovery ở đây chỉ thử một mức 25 người dùng, nên nó chỉ cho biết hệ thống có hồi phục ở mức đó hay không. Muốn trả lời trọn câu hỏi của kiến trúc sư, hãy thay nó bằng một `ramping-vus` hạ tải theo nhiều bậc, chẳng hạn 150, 100, 50 rồi 25, và ghi lại bậc đầu tiên mà p95 quay về dưới 800 ms.

## Các bước tự làm ở khách hàng

Hãy bắt đầu sớm. Ed Glas, trong một bài trên blog Microsoft, nhắc rằng nhiều vấn đề hiệu năng nằm ở kiến trúc, không phải loại sửa được ngay trước khi lên production. Nếu kiến trúc sư đang lo, hãy test ngay tuần này chứ đừng đợi tuần go-live.

Nạp đủ dữ liệu trước khi đo, vì theo Ed Glas nhiều vấn đề hiệu năng chỉ lộ ra khi khối lượng dữ liệu lớn. Một database 10.000 bản ghi không kiểm chứng được lời cảnh báo về một database sẽ chứa cả chục triệu.

Test từng thành phần trước, toàn hệ thống sau. Google SRE nhắc rằng mỗi thành phần có điểm gãy riêng nên cần load test riêng, còn Ed Glas đề xuất đi từ giao dịch đơn lẻ, rồi trộn thêm giao dịch, rồi mới đến toàn bộ.

Trong ví dụ trên, giữ nguyên kịch bản nhưng trỏ `BASE_URL` vào một endpoint chỉ chạy truy vấn database; nếu nó gãy sớm hơn toàn hệ thống, bạn đã tìm ra thủ phạm.

Cuối cùng, ghi lại. ADR (Architectural Decision Record) lưu một quyết định kiến trúc cùng lý do, đánh đổi và hệ quả. Một ADR kiểu “giữ thiết kế đồng bộ, trần đo được khoảng 140 người dùng, thêm hàng đợi khi tải thực tế chạm 100” biến kết quả test thành một cam kết có điều kiện, để ba tháng sau không ai phải cãi lại từ đầu.

## Những lỗi khiến bài test chung phản tác dụng

Lỗi hay gặp nhất là bạn tự viết kịch bản rồi mời khách đến xem kết quả. Khách không được tham gia thiết kế thì cũng sẽ không tin kết quả. Hãy để kiến trúc sư chọn mức tải cao điểm và môi trường, kể cả khi bạn thấy con số của anh ấy hơi bi quan.

Lỗi thứ hai là dùng truy vấn tự bịa. Một danh sách năm câu lặp đi lặp lại sẽ được cache hết, và p95 sẽ đẹp đến mức đáng ngờ. Hãy xin khách một mẫu truy vấn thật đã ẩn danh.

Lỗi thứ ba là coi pha stress như bài thi bắt buộc phải đạt. Nếu bạn né việc đẩy hệ thống tới điểm gãy, kiến trúc sư sẽ nghĩ bạn sợ câu trả lời. Tìm ra điểm gãy là thắng lợi chung của hai bên chứ không phải thất bại của bạn.

Lỗi cuối cùng là quên pha hồi phục. Một hệ thống chậm đi lúc cao điểm rồi tự lành rất khác một hệ thống gục rồi nằm luôn. Hãy hỏi thẳng kiến trúc sư trường hợp nào anh ấy lo hơn, rồi để câu trả lời đó quyết định pha hồi phục cần đo kỹ đến đâu.

## Biến nó thành một dòng trong CV

Trong CV, đừng chỉ viết “thực hiện load test”. Hãy viết kiểu: thiết kế load test chung với đội kiến trúc của khách, chốt ngưỡng p95 trước khi chạy, xác định điểm gãy của database và ghi quyết định vào ADR.

Khi đi phỏng vấn vị trí FDE, hãy chuẩn bị kể lại một lần như thế, vì nó cho thấy bạn giải quyết được bất đồng kỹ thuật với khách bằng con số chứ không phải bằng việc ai nói to hơn.

Bài tập tuần này: nhớ lại lần gần nhất bạn bất đồng với ai đó về hiệu năng. Viết ra ba con số lẽ ra hai bên phải chốt trước, cùng một kịch bản ba pha để kiểm chứng. Lần tới có người khoanh tay nói “sẽ sập”, bạn đã có sẵn câu trả lời: “Được, mình cùng tìm xem nó sập ở đâu.”

**Thử ngay tuần này:**

- Chọn một endpoint trong dự án hiện tại và viết ra ba con số: p95 chấp nhận được, tỉ lệ lỗi tối đa, tải giờ cao điểm. Hỏi một người phía khách xem họ có đồng ý không.
- Chạy kịch bản k6 ba pha trong bài trên môi trường staging, và kiểm tra xem dữ liệu trên staging có lớn bằng production không.
- Viết một ADR một trang cho quyết định kiến trúc gần nhất mà team từng cãi nhau, kèm con số đã dùng để chốt.

## Nguồn

- [Testing for Reliability (Google SRE book)](https://sre.google/sre-book/testing-reliability/)

- [Addressing Cascading Failures (Google SRE book)](https://sre.google/sre-book/addressing-cascading-failures/)

- [A pattern for successful load testing: Component Load Testing | Microsoft Learn](https://learn.microsoft.com/en-us/archive/blogs/edglas/a-pattern-for-successful-load-testing-component-load-testing)

- [REL12-BP03 Test scalability and performance requirements (AWS Well-Architected)](https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_testing_resiliency_test_non_functional.html)

- [Thresholds in k6: Pass/Fail Criteria for Tests](https://grafana.com/docs/k6/latest/using-k6/thresholds/)

- [Load test types (Grafana k6 docs)](https://grafana.com/docs/k6/latest/testing-guides/test-types/)

- [Architectural Decision Records (ADRs)](https://adr.github.io/)
