Góc lan man

Đó là một ngày mưa bão, mình cùng cả đội ở lại qua đêm trên công ty, chuẩn bị kĩ càng nhất có thể cho một buổi demo quan trọng. Bọn mình mua đủ thứ, thậm chí còn thắp hương các kiểu con đà điểu, cầu cho buổi demo diễn ra tuyệt vời. Rồi nào là nạp tiền mua 4G chuẩn bị cho trường hợp không may hỏng hóc về đường điện hay mạng mẽo bên mình, rồi nào là kéo tải hệ thống lên và sẵn sàng lôi credit card ra xào vài đường cúng cho nhà cung cấp dịch vụ. Bên phía đối tác còn đích thân lội gió vượt mưa đến văn phòng để phối hợp cùng bọn mình. Rốt cuộc, buổi demo thất bại thảm hại. Một cái deadlock to chình ình trong hệ thống kéo cho database đổ vỡ rồi đến hệ thống treo lủng lẳng như chiếc lá phong cuối cùng của Rô-si. Mọi nỗ lực mà bọn mình chiến đấu từ trước đến giờ cuốn theo những giọt mưa tuôn xối xả.

Trước đó chúng mình đã có thời gian chạy thử với một lượng chừng 1/5 so với buổi demo hôm đó, và chúng mình tự tin đến ngạo mạn rằng cứ nâng mức chịu tổn thất lên là sẽ thành công. Ngạo mạn. Rồi nhận thất bại ê chề. Lúc ấy tinh thần mọi người xuống lắm, song như những bet thủ kì cựu, còn thở là còn gỡ, bọn mình quyết định theo lao đến cùng, tìm cách tối ưu hệ thống cũng như đưa ra giải pháp phân luồng người dùng để giảm tải cho hệ thống. Còn với mình lúc đó, mình nhận ra mình chưa đủ thận trọng cho vụ đặt cược lần này, mình nghĩ tới chuyện phải kiểm tra sức chịu tải của hệ thống theo cách mà có thể đong đếm được.

Đặt vấn đề

Lan man thế đủ rồi. Giờ phân tích vấn đề nào.

Trước hết, mình nhận ra mỗi lần người dùng vào sử dụng hệ thống cần gọi ít nhất 5 API. Việc tối ưu API hay làm gì tạm gác qua một bên, mình sẽ chỉ tập trung khai thác dữ liệu từ API để kiểm thử sức chịu đựng của máy chủ. Đống API này bao gồm:

  1. Đăng nhập
  2. Vào bài
  3. Bắt đầu làm
  4. Lấy đề về làm
  5. Nộp bài

Giữa 4 và 5 có thể có thêm bước lưu tạm bài làm của người dùng. Nhưng không phải những phần chính mà mình muốn thử. Các phần gây ra deadlock chủ yếu rơi vào bước 1, 4, và 5. Vì vậy mình cần khai thác bọn này để giả lập lại lượng người dùng cùng truy cập tại cùng một thời điểm (concurrent user).

Giải pháp

Khi nhắc tới kiểm thử trên đầu API, thứ đầu tiên loé lên trong đầu mình là Postman. Phần mềm này có đủ thứ, từ tạo lập môi trường tới viết test case để kiểm tra dữ liệu trả về. Ngoài ra giao diện tương đối dễ sử dụng cùng hệ thống tài liệu hướng dẫn sử dụng khá là tường minh, thành ra mình chọn luôn thằng này cho nó tiện, dẫu sao mình cũng từng dùng nó để kiểm thử đống API mình viết ra rồi.

Chưa hết, nó còn tích hợp cả Runner, hiểu nôm na là một con hàng chạy tự động theo dataset mà mình truyền vào. Tiện lợi, bổ rẻ, và hợp với người lười như mình (viết bằng axios hay các thứ setup tương đối lâu nên chọn béng một con hàng có sẵn cho nó nhanh).

Thực hiện

Đầu tiên cần tạo ra một Collection, Collection này sẽ chứa toàn bộ các đầu API mà mình muốn kiểm thử.

Collection

Tiếp đến, mình tạo thêm một Environment dành riêng cho Collection này. (Sau đợt đó mình mới biết là có thể dùng luôn Variables trong Collection mà không cần động tới Environment).

Environment

Sau đó, dựng các api để nó nối với các biến mà mình đã khai báo trong Environment như thế này.

Variable

Cũng như sử dụng luôn bộ test của Postman để viết test case để hứng dữ liệu trả về và gán chúng lại cho Environment.

Api Test Case

Rồi tạo ra Dataset dưới dạng JSON với thông tin người dùng thử. Việc này tương đối đơn giản do bọn mình có sẵn một DB riêng để test trên môi trường triển khai (Production). Chẳng hạn như là:

// file_path: dataset.json
[
  {
    "email": "[email protected]",
    "password": "verysecretpassword"
  },
  {
    "email": "[email protected]",
    "password": "ultrasecretpassword"
  }
]
1
2
3
4
5
6
7
8
9
10
11

Sau khi hoàn thiện các bước, chạy thử một lượt và kết quả khá là ổn. Mình tạo tiếp Runner Tab và kéo cái Collection đã được cấu hình hoàn chỉnh vào. Đặt các điều kiện như hình dưới. Bạn có thể tuỳ chỉnh lại số lần lặp tuỳ theo nhu cầu.

Runner Config

Và khi chạy thử thì mình có kiểm tra phần mềm giám sát Database và nó chẳng hề hấn gì. CPU/RAM đều ở ngưỡng bình thường, các câu truy vấn vẫn trả về dữ liệu với khoảng delay hợp lí.

Có điều gì đấy sai sai. Mình kiểm tra tiếp đến số lượng kết nối và nhận ra là con Runner đang chạy tuần tự, dù mình nhân Runner lên để chạy thì nó cũng vẫn là tuần tự. Chẳng hạn mình có 4 con runner chạy cùng lúc, mỗi con đều có dataset là 3000 người dùng, nhưng thực tế lại là 4 người dùng vào cùng một thời điểm và chạy tuần tự từng API từ 1 đến 5 rồi chuyển sang người dùng tiếp theo trong danh sách.

Lúc này anh Google lại là điểm sáng, mình tìm xem có cách nào chạy được cùng lúc (Parallel) hay không và kết quả không được như mong muốn lắm. Chính Postman nhận là mình không hỗ trợ việc đó, nhưng lại có một thư viện NodeJS tên là newmanopen in new window làm được việc này. newman được thiết kế để nhận Collection, Environment và Dataset mình vừa tạo trên Postman để chạy. Thế là mình lại lao đầu vào tạo project NodeJS để chạy được đồng thời.

Mình chỉ cần vào Postman, chọn export Collection, Environment của mình ra, rồi đưa toàn bộ những gì cần thiết vào cùng folder để chạy code. Rồi implement đoạn code sau vào là có thể chạy được trên nodejs rồi.

const path = require('path')
const async = require('async')
const newman = require('newman')
const fs = require('fs')

const dataset = JSON.parse(fs.readFileSync(path.join(__dirname, './dataset.json')))

const PARALLEL_RUN_COUNT = 3000

const parametersForTestRun = {
  collection: path.join(__dirname, './collection.json'), // đường dẫn đến Collection
  environment: path.join(__dirname, './environment.json'), // đường dẫn đến Environment
  reporters: ['cli']
}

let commands = []
for (let index = 0; index < PARALLEL_RUN_COUNT; index++) {
  const parallelCollectionRun = function (done) {
    newman.run({
      ...parametersForTestRun,
      iterationData: [dataset[index]],
    }, done)
  }
  commands.push(parallelCollectionRun)
}

// Dùng thư viện async để chạy các lệnh postman cùng một thời điểm.
async.parallel(
  commands,
  (err, results) => {
    err && console.error(err)
  })











 
 















 





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

Mọi thứ lúc này về cơ bản đã xong, và khi chạy thì CPU và RAM của DB lập tức nhảy vọt liên tục. Thực sự đã có hơn 3000 người dùng cùng đăng nhập và cùng làm bài một lúc, cũng như cùng nộp bài cùng một thời điểm.

Đánh giá

Với cách stress test này, chúng mình có được công cụ đánh giá tính hiệu quả của mức chịu tải. Một phần biết được với lượng người dùng nào thì cần đẩy mức chịu thiệt hại chi phí lên mức nào. Từ công cụ nho nhỏ này, chúng mình cũng có luôn đồ hàng để kiểm thử cho các phân đoạn cần kiểm tra sức chịu đựng trong tương lai.

À tí quên, trong lúc thực hiện giải pháp này, mình không mấy chú tâm vào tính toàn vẹn của dữ liệu mà đăm đăm kiểm tra xem DB có chịu nổi tải hay không. Để kiểm tra sự toàn vẹn của dữ liệu, chẳng hạn với trường hợp của mình là mỗi đề có câu hỏi bị trộn ngẫu nhiên, mình cần kiểm tra xem nó có thực sự ngẫu nhiên hay không thì chỉ có cách nhòm vào một số đoạn logs bất kì trong số các test case được chạy.Để thực hiện việc này, bạn có thể code thêm vào trong test case của Postman và re-run lại cả quá trình, hoặc đọc thẳng file logs mà đống newman trả cho bạn.

Tuy nhiên, file logs thường không dễ đọc, nhất là khi chúng được in trên màn hình Terminal. Và vi diệu làm sao, newman có một rổ thư viện dành cho việc báo cáo, mình chọn dùng HTML Reporteropen in new window để xem báo cáo dưới dạng dễ đọc nhất. Giờ ta sẽ tuỳ chỉnh lại đoạn code phía trên để có thể xuất được báo cáo dạng này nhé!

const parametersForTestRun = {
  collection: path.join(__dirname, './collection.json'), // đường dẫn đến Collection
  environment: path.join(__dirname, './environment.json'), // đường dẫn đến Environment
  reporters: ['cli', 'htmlextra']
}

let commands = []
for (let index = 0; index < PARALLEL_RUN_COUNT; index++) {
  const parallelCollectionRun = function (done) {
    newman.run({
      ...parametersForTestRun,
      iterationData: [dataset[index]],
      reporter: {htmlextra: {export: `./report/report_${index}.html`}}
    }, done)
  }
  commands.push(parallelCollectionRun)
}



 








 




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Báo cáo sau khi được tạo ra có thể mở thẳng trên bất cứ trình duyệt nào, và có thể xem được chi tiết mọi thứ. Chẳng hạn:

HTML report

Các cách cải thiện dễ thấy nhất là nên nạp dữ liệu mà người dùng muốn truy xuất lên một Cache Server như Redis để giảm tải cho Server chính. Nhưng đó là chuyện khi có tay.

Tài liệu tham khảo

  • Postman Test case
  • Postman Runner
  • Newman

Lưu ý

  1. Bạn chỉ nên thực hiện Stress Test khi có được sự đồng thuận của cả đội hoặc người phụ trách cấp cao. Hiện mình đang làm việc trong startup nên có khi chỉ là vỗ vai người trông server là xong.
  2. Các ảnh chụp được lấy từ một project đã chạy thực tế nên cần phải che đi các thông tin nhạy cảm nhằm bảo mật dữ liệu.
Last Updated: