4 điểm bởi GN⁺ 2025-12-09 | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong quá trình di chuyển codebase từ Scala 2.13 sang Scala 3, đã phát sinh suy giảm hiệu năng ngoài dự kiến
  • Ở các bài test ban đầu và môi trường triển khai, mọi chỉ số đều bình thường, nhưng sau vài giờ thì độ trễ (lag) Kafka tăng lên
  • Kết quả kiểm thử tải cho thấy hiện tượng thông lượng giảm mạnh khi xử lý message được phân mảnh chi tiết
  • Thông qua phân tích bằng async-profiler, nguyên nhân được xác định là bug kém hiệu quả trong đánh giá chuỗi của thư viện Quicklens
  • Sau khi cập nhật thư viện, hiệu năng đã được khôi phục, đồng thời nhấn mạnh sự cần thiết phải lưu ý đến khác biệt trong cách thư viện hoạt động giữa các phiên bản Scala

Quá trình di chuyển dịch vụ

  • Dịch vụ hiện có được chuyển từ Scala 2.13 sang Scala 3.7.3
    • Đây là một dịch vụ tập trung vào thu thập dữ liệu, không dùng macro, và hiệu năng là yếu tố quan trọng
    • Sau khi áp dụng thay đổi về dependency, tùy chọn compiler, kiểu và cú pháp, việc biên dịch đã thành công
  • Trong môi trường thử nghiệm và triển khai từng bước, cả log lẫn metric đều bình thường
    • Các chỉ số ở mức hạ tầng, JVM và ứng dụng đều được xác nhận là khỏe mạnh

Suy giảm hiệu năng chưa rõ nguyên nhân

  • Khoảng 5~6 giờ sau khi triển khai, hiện tượng Kafka lag tăng bắt đầu xuất hiện
    • Ngay cả khi không có spike dữ liệu, thông lượng trên mỗi instance vẫn giảm
    • Sau khi rollback, thông lượng phục hồi ngay lập tức, xác nhận rằng thay đổi trong code là nguyên nhân

Phân tích hiệu năng và lần theo nguyên nhân

  • Trong kiểm thử tải, ban đầu không tái hiện được hồi quy hiệu năng
    • Chỉ khi message có payload phân mảnh nhỏ và không đồng nhất thì thông lượng mới giảm mạnh
  • Các thư viện phụ thuộc (serialization, DB SDK, Docker image, thư viện cấu hình, v.v.) được hoàn nguyên và kiểm thử từng cái một nhưng không có thay đổi
  • Kết quả profiling CPU bằng async-profiler cho thấy,
    • Trên Scala 3, mức sử dụng CPU của JIT compiler và giai đoạn decoding tăng vọt
    • Ở phần trên của flamegraph, các lời gọi Quicklens chiếm một nửa tổng thời gian CPU
    • Trên Scala 2.13, cùng lời gọi đó chỉ ở mức khoảng 0.5%

Nguyên nhân gốc rễ của vấn đề

  • Bug kém hiệu quả trong đánh giá chuỗi của thư viện Quicklens xuất hiện trên Scala 3
    • Bản sửa liên quan đã được phản ánh trong GitHub PR #115
    • Sau khi cập nhật thư viện, khác biệt hiệu năng giữa Scala 3 và 2.13 đã được loại bỏ

Bài học và khuyến nghị

  • Sự phụ thuộc của thư viện vào metaprogramming có thể gây ra khác biệt hiệu năng giữa các phiên bản Scala
  • Ngay cả khi migration hoàn tất bình thường, vẫn cần benchmark các hotspot và điểm nghẽn
  • Với các dịch vụ nhạy cảm về hiệu năng, thay vì giả định “nó hoạt động tốt”, việc xác minh bằng đo đạc thực tế là bắt buộc
  • Cần kiểm tra trước để tránh tình huống benchmark chứ không phải code mới là thứ làm lộ ra điểm nghẽn

1 bình luận

 
GN⁺ 2025-12-09
Ý kiến trên Hacker News
  • Tôi không phải fan của Scala, nhưng quá trình phân tích sâu và debug vấn đề rất ấn tượng
    Blog kỹ thuật nên được viết theo kiểu này. AI khó có thể thay thế được mức độ tư duy như vậy
    • Khi làm mới một trong các dịch vụ của chúng tôi, tôi đã migrate từ Scala 2.13 sang Scala 3
      Câu hỏi đầu tiên rất đơn giản — “tại sao lại phải nâng cấp?”
  • Trong Scala 3, từ khóa inline hoạt động như một phần của hệ thống macro
    Khi dùng inline cho tham số, trình biên dịch sẽ được chỉ thị inline biểu thức ngay tại vị trí gọi
    Nhưng nếu phần này lớn, nó tạo gánh nặng lớn cho trình biên dịch JIT
    Trong Scala 2, @inline chỉ là một gợi ý đơn giản, nhưng ở bản 3 thì được áp dụng bắt buộc
    Vì vậy, việc chỉ đơn giản đổi @inline thành inline là một sai lầm lớn
    • Điểm khác biệt này khá giống với những gì từ khóa register trong C/C++ từng trải qua
      Ban đầu nó mang tính bắt buộc, nhưng khi tối ưu hóa phát triển hơn thì chỉ còn là một khuyến nghị và cuối cùng bị bỏ qua
      inline của C++ cũng đã đi theo con đường tương tự
    • Kotlin dùng inline rất tích cực ở gần như mọi nơi
      Mục đích là để loại bỏ overhead của lambda trong các hàm như map
      Hầu như không có vấn đề hiệu năng nào, nhưng khi kết hợp với hệ thống macro của Scala thì có thể tạo ra các biểu thức phức tạp và gây vấn đề
  • Sau khi nâng cấp thư viện, hiệu năng của Scala 3 trở nên gần như tương đương Scala 2.13
    Tôi cũng từng có trải nghiệm tương tự khi nâng cấp Ruby 2→3
    Không chỉ nâng ngôn ngữ, mà cần cập nhật toàn bộ dependency lên mới thì hệ thống mới ổn định
  • Vấn đề của Scala 3 là không ai thực sự muốn nó
    Các vấn đề suy luận kiểu của Scala 2 vẫn chưa được giải quyết, thay vào đó chỉ có ngôn ngữ bị thay đổi
    Thành ra giống như đã làm ra một sản phẩm mà thị trường không cần, trong khi phớt lờ nhu cầu thực tế
    PS: Cần xây dựng một bộ unit test đúng nghĩa cho trình biên dịch
    • Đáng buồn nhưng tôi đồng ý. Scala từng rất hứa hẹn vào khoảng 2010~15
      Nhưng đợt viết lại Scala 3 đã không giải quyết được tốc độ biên dịch và vấn đề tooling, và khiến dự án mất hoàn toàn đà phát triển
      Tôi tự hỏi liệu đến năm 2025 này còn ai bắt đầu dự án mới bằng Scala nữa không
    • Tôi đã nhiều lần cố học Scala nhưng cuối cùng lại quay về Clojure
      Scala giống một ngôn ngữ do giới học thuật tạo ra, và việc nó từng thịnh hành trong công nghiệp mới là điều lạ
    • Nhận định rất trúng trọng tâm
      Giờ mọi công cụ đều phải thích ứng với Scala 3, và ngay cả IntelliJ cũng vẫn chưa hỗ trợ hoàn hảo
      Giá mà Scala 2 được cải tiến dần dần thì tốt hơn, nhưng có vẻ họ chỉ tập trung vào thành công học thuật
      Ví dụ như chuyện tranh cãi quanh early return trong bài viết của tpolecat, trong khi Kotlin hỗ trợ điều đó hoàn toàn bình thường
    • Câu “không có test” là thông tin sai lệch
      Trình biên dịch Scala có hàng nghìn, hàng chục nghìn test, và
      thư mục test chính thức cùng với
      hệ thống community build dùng để kiểm chứng hàng triệu LOC
    • Có vẻ bạn đã không đọc kỹ bài viết. Luận điểm đang bị chệch hoàn toàn
  • Điều thú vị nhất trong bài này là phải có sẵn kiểm thử hiệu năng tự động và phân tích flamegraph như một mặc định
    Điều đó đặc biệt cần thiết với những thay đổi lớn như nâng phiên bản ngôn ngữ
    • Benchmark test khác với test thông thường ở chỗ cần cấu hình rất chi tiết
      Chúng tôi liên tục benchmark một công cụ viết bằng C++, nhưng vì nhiễu môi trường nên rất khó giữ kết quả nhất quán
      Chúng tôi đang cân nhắc cách chạy nhiều lần trên cùng một máy để so sánh
    • Tôi tò mò hiện nay trên JVM người ta dùng những công cụ test hiệu năng nào
  • Vấn đề duy nhất của Scala 3 là ghen tị với Python
    Sai lầm là tạo ra cú pháp thứ hai rồi ép nó như là tương lai
    Điều này cũng làm hệ sinh thái tooling chậm lại
    • Hiện giờ đa số công cụ đã hỗ trợ cú pháp mới
      Cũng có thể tự động chuyển đổi style bằng trình biên dịch hoặc scalafmt
    • Rất đúng chất Scala: cho phép làm cùng một việc theo nhiều cách
      Giờ thì cú pháp ngoặc nhọn và cú pháp thụt lề đều tồn tại, thành ra gấp đôi
    • Có lẽ so với Haskell thì còn hấp dẫn hơn
    • Với tư cách là một cựu fan Scala, tôi đã khá bối rối khi xem ví dụ cú pháp mới
      Cú pháp match quá dài dòng và giống như đang bắt chước Python
  • Trước đây tôi từng làm migrate Scala 2.x, và thứ khó nhất là phải chờ dependency
    Khi đó Scala được chú ý nhờ Spark, nhưng đã bỏ lỡ cơ hội phát triển thành một ngôn ngữ thương mại
    Giờ thì Spark đi với Python, còn vị trí ngôn ngữ hiện đại trên JVM đã thuộc về Kotlin
    Cuối cùng Scala lại có cảm giác trở về thành một ngôn ngữ học thuật
    • Nhưng Scala vẫn còn được dùng rộng rãi trong các doanh nghiệp lớn
      Có thể thấy điều đó qua Scala Adoption Tracker
      Các tính năng mới của Scala 3 vẫn có tiềm năng đổi mới lại hệ sinh thái ngôn ngữ
      Ví dụ: giải thích về Capture Checking
    • Tôi nghi ngờ việc Kotlin thực sự đã thống trị JVM
      Khi Java bổ sung thêm các tính năng hàm, nó đã hấp thụ một phần sức hút của Scala
    • Kotlin gần như không có hiện diện nào ở JVM phía server
      Theo kinh nghiệm của tôi, nhu cầu thị trường cũng rất nhỏ
    • Kotlin thực chất gần như là ngôn ngữ dành riêng cho Android
      Chỉ là vì Google hạn chế hỗ trợ Java nên mới thành ra như vậy
      Trên toàn thị trường JVM, nó chỉ chiếm khoảng 10%
  • Tôi thấy lạ khi họ chuyển sang Scala 3 mà không nâng cấp phiên bản thư viện
    Nếu phải qua audit bảo mật (PCI-DSS v.v.) thì việc duy trì thư viện mới nhất là bắt buộc
    • Câu “giữ bản mới nhất là thói quen tốt” là kiểu diễn đạt chặn đứng thảo luận
      Tôi thậm chí còn có xu hướng giữ dependency ở trạng thái cũ
      Phiên bản mới đem theo bug mới, thay đổi maintainer hoặc cả rủi ro bảo mật
    • Tôi cũng thấy khó hiểu. Trong bài họ nói “đã cập nhật dependency”, nhưng sau đó lại nói “sau khi cập nhật thì hiệu năng trở nên tương đương”
      Có vẻ ban đầu họ chỉ nâng một phần. Chia nhỏ thành các bước nhỏ là cách làm phổ biến, nhưng có vẻ lần này đã kém may mắn
    • Sau khi biết nguyên nhân bug thì thấy bớt ấn tượng hơn
      Vấn đề không nằm ở bản thân Scala 3 mà là ở sự tương tác giữa nhiều yếu tố
    • Với thay đổi lớn như nâng phiên bản ngôn ngữ, tốt nhất là chỉ đổi từng thứ một
    • Trong Maven/Gradle/SBT có thể đặt ràng buộc phiên bản nên vẫn giữ riêng với phiên bản ngôn ngữ
      Tuy vậy, cần cẩn thận vì các thư viện riêng cho Scala thường có phiên bản Scala trong chính version của chúng
  • Báo cáo lỗi ở SoftwareMill và Scala GitHub là những bản sửa nhỏ nhưng chính xác
    Chúng cho thấy rõ tính biểu đạt và độ an toàn kiểu của Scala
    Như bài viết của Li Haoyi, nó cũng đủ hấp dẫn để làm ngôn ngữ thay thế Python
  • Bài học chính là khi nâng cấp major của ngôn ngữ hay framework thì cũng phải cập nhật thư viện đi kèm
    Điều này đặc biệt quan trọng khi có nhiều thư viện lạm dụng các tính năng ma thuật