Làm dev đau lưng

Funny issue với Event driven

Như thường lệ, mình ngủ như lợn đến tận 8h sáng mới dậy, thong thả chuẩn bị đến 9h rồi chạy một ào đến công ty mất 3 phút (uy tín luôn). Vừa vào chỗ thấy leader nhăn nhó:

  • Có bug rồi em ơi
  • Sặc, chỗ nào thế anh

Rồi xong code của mình từ 6 tháng trước do chưa tối ưu nên gây hẹo cả con worker.

Vào chi tiết nhé

image

Flow xử lý: Worker kéo messages từ message queue về (ở đây là SQS). Xử lý từng message một, khi xử lý xong thì xóa message đấy đi, còn nếu có lỗi thì đẩy message ngược lại vào queue để retry. Trong message sẽ đính kèm id của database record, worker dựa vào id để truy vấn record đấy ra.

Question: Điều gì xảy ra nếu record đó quá lớn?
-> Trường hợp của mình rơi vào khoảng 80-100MB một record, con worker có 1GB RAM tất nhiên chạy ổn. Rồi ngày này cũng đến, có một record gần 200MB bay vào thế là tèo luôn.

Question: Vì sao chỉ 200 MB mà ảnh hưởng được? Worker có đến 1GB RAM mà
-> Worker còn phải phục vụ những tasks khác nữa! Thêm vào đó, khi record 200MB vào memory sẽ scale lên nữa, chứ không dừng ở 200MB, cộng thêm code của mình chưa được tối ưu.

Question: Nhưng chỉ vậy làm sao tạch được? Worker có cả high availability mà? Điều gì xảy ra khi worker tạch?
-> Message được bắn vào queue lại để retry, và bạn cũng biết rồi đó, các workers khác kéo đúng cái message đấy về xử lý và tạch tiếp =))) Báo thủ không chớ.

Làm sao để fix?

Short term

Leader của mình đập thêm RAM vào cho worker xử lý xong message này.

Long term

  1. Nhờ có issue này mới biết con worker chưa bật autoscaling.
  2. Optimize code. App đang chạy Golang và đây là một vài điểm cần lưu ý để tối ưu memory hơn:
  • Hãy cấp phát số lượng items ngay khi có thể
var items []string
// Should be
item := make([]string, 5)
  • Tránh cộng chuỗi bằng operator, hãy dùng strings.Builder
example := "This is a"
example += "string"

// Should be
var builder strings.Builder
builder.WriteString("This is a ")
builder.WriteString("string")
  • Nếu làm trên bytes được thì hãy làm trên bytes, hạn chế cast qua string. Đoạn này mình khá ngớ ngẩn, có mỗi replace mà phải cast qua string
bts := []byte{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100} // Hello World
example := string(bts)
example = strings.ReplaceAll(example, "World", "")

// Should be
bts = bytes.ReplaceAll(bts, []byte("World"), []byte(""))
  • Điều cuối cùng mà ai cũng biết, cố gắng giảm loops, đặc biệt là nested loops!

Một ngày ăn bug mệt mỏi, chúc các bạn không mắc lại sai lầm của mình 😆