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 team báo có issue bự.

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 con worker.

Vào chi tiết

image

Flow xử lý:

  1. Worker kéo messages từ message queue về (ở đây là SQS).
  2. Trong message đính kèm user_id, next_id, worker dựa vào next_id để truy vấn database records tiếp theo ra.
  3. Xử lý từng message một, xử lý xong mới xóa message đấy đi.

Question: Điều gì xảy ra nếu database records quá lớn?
-> Trường hợp của mình rơi vào khoảng 80-100MB một list, con worker có 1GB RAM tất nhiên chạy ổn. Rồi ngày này cũng đến, có list 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 list 200MB vào memory sẽ scale lên 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 chưa được xử lý xong nên vẫn còn trong queue. Và bạn cũng biết rồi đó, worker nào sống ăn cái message này xong thì tạch, còn worker nào recover lại thì cũng nuốt tiếp message đấy về xử lý và tạch tiếp =)))

Làm sao để fix?

Short term

Đậ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!
  1. Nếu vẫn chưa tối ưu được thì giảm số lượng records một page xuống.

Chúc các bạn không mắc lại sai lầm của mình 😆