Quần Cam

UDP trong Elixir và thủ thuật cache UDP header

Vài tháng trước tui có giúp phát triển chức năng hỗ trợ Unix domain socket cho Fluxter, cho phép Fluxter gửi metrics qua UDP đến Telegraf dùng Unix domain socket.

Cách thông thường để gửi một UDP packet trong Erlang/Elixir là dùng gen_udp. Ở ví dụ bên dưới ta mở một UDP socket ở passive mode và dùng hàm :gen_udp.send/4 để gửi packet đến một UDP socket ở địa chỉ 127.0.0.1 và port 27027.

{:ok, sock} = :gen_udp.open(0, [active: false])
{ok, addr} = :inet.getaddr('127.0.0.1', :inet)
packet = [my_data]
:gen_udp.send(sock, addr, 27027, packet)

Tuy nhiên lúc đọc code của Fluxter, tui phát hiện ra cách gửi UDP packet khá lạ.

{:ok, sock} = :gen_udp.open(0, [active: false])

{:ok, {n1, n2, n3, n4}} = :inet.getaddr('127.0.0.1', :inet)
port = 27027
header =
  [
    1, # inet address family for Erlang >= 19,
    band(bsr(port, 8), 0xFF),
    band(port, 0xFF),
    band(n1, 0xFF),
    band(n2, 0xFF),
    band(n3, 0xFF),
    band(n4, 0xFF)
  ]
packet = [header, my_data]

send(sock, {self(), {:command, packet}})

Với đoạn code trên, ta tính trước header chứa thông tin đích đến của packet. Rồi mỗi lần cần gửi data, ta chèn header đã tính vào đầu packet rồi gửi bằng Erlang port_command.

Lợi ích khi làm như vậy là giúp tăng tốc. Theo benchmark của tác giả thì tốc độ tăng gần 20% sau khi cache header.

Fluxter là một metrics writer với packet được gửi với số lượng lớn. :gen_udp.send/4 luôn build lại header mỗi khi được gọi trong khi đích đến của ta là không đổi. Đó là một cost không cần thiết và bằng cách cache lại header và dùng port_command để gửi packet, ta giảm được cost này.

Bài viết này sẽ giúp tui tăng lương như thế nào?

Bài viết này được viết sau khi tác giả đã được tăng lương mua quần mới. Còn bạn được tăng lương không tui đâu quan tâm. #ahihi

Cơ mà sau khi nói xong lợi phải nói đến răng, lộn hại.

Tất nhiên việc tăng tốc không free, nó là một tradeoff (tạm dịch: sự đánh đổi). Cost chỉ chuyển hoá từ chỗ này sang chỗ khác, từ dạng này sang dạng khác.

Cost ở đây là việc phải maintain code mà đáng lẽ bạn đã không cần phải làm vậy nếu sử dụng abstraction. Với :gen_udp.send/4, bạn có thể yên tâm upgrade OTP version mà không phải lo gì cả (đã có người viết abstraction lo cho bạn). Còn khi tự build header, bạn sẽ phải tự kiểm tra, cập nhật CHANGELOG để tương thích app qua mỗi version OTP. Ví dụ OTP 19 và OTP 18 có cách build header khác nhau ở address family.

Tuy nhiên với cost là việc maintain thêm chừng 10 dòng code rất hiếm khi có breaking change để đổi lấy 20% giá trị performance, thương vụ này tui sẽ đầu tư. Còn shark Phú, không biết ông ấy nghĩ sao ta?


NGUY HIỂM! KHU VỰC NHIỀU GIÓ!
Khuyến cáo giữ chặt bàn phím và lướt thật nhanh khi đi qua khu vực này.
Chức năng này hỗ trợ markdown và các thứ liên quan.

Bài viết cùng chủ đề

Nhật ký hốt sh*t—Chuyện về cái service A

Bài viết mà thằng chả chém gió về cách chả monitoring và debug một sự cố gặp phải khi vận hành hệ thống Elixir

Vài ghi chép về Elixir Compiler (phần 1)

Bài viết mà thằng chả chém gió về Elixir compiler.

Cache stampede—Hiện tượng chất đống cache

Caching là một kĩ thuật tăng tốc mà hầu như mọi kĩ sư phần mềm đều cần biết. Tuy vậy, đôi khi caching vẫn có thể mang lại cho bạn những vấn đề phiền toái khác như cache stampede.