Việc áp dụng và vận hành Enterprise Haskell tại H-E-B
(blog.haskell.org)- Trong quá trình hiện đại hóa chuỗi cung ứng cho hơn 435 cửa hàng và trung tâm logistics, H-E-B đã đưa Haskell vào sử dụng; sau 8 năm, khoảng 1 triệu dòng mã do nhiều nhóm quản lý đang chạy trong các hệ thống production cốt lõi
- Haskell giúp giảm các loại lỗi runtime, hỗ trợ refactor dựa trên GHC và tích hợp với hệ thống legacy, nhưng phải chấp nhận tự bảo trì do thiếu SDK và độ phức tạp của các tính năng kiểu nâng cao
- Thành công phụ thuộc nhiều vào năng lực và văn hóa của nhóm hơn là ngôn ngữ; ở các nhóm không có chuyên gia Haskell, rò rỉ bộ nhớ dạng space leak và việc các lập trình viên chủ chốt rời đi khiến hai dự án không thể bám rễ với Haskell
- GHC runtime chưa từng là nguyên nhân gây sự cố production tại H-E-B; khi hệ sinh thái như HLS, OpenTelemetry và profiling phát triển hơn, Haskell cho production đã trở thành một lựa chọn cạnh tranh
- Nên giới hạn các tính năng nâng cao phía sau những thư viện nhỏ và giao diện rõ ràng, đồng thời duy trì test, observability và chia sẻ tri thức; trong kỷ nguyên coding agent, kiểm tra kiểu của GHC và vòng phản hồi nhanh càng trở nên quan trọng
Bài toán hiện đại hóa mainframe
- Khách hàng của H-E-B, nhà bán lẻ lớn nhất Texas thuộc sở hữu tư nhân, quan tâm việc hàng hóa có đầy trên kệ hơn là các hệ thống nội bộ vận hành thế nào
- Trong nhiều thập kỷ, mainframe COBOL đã chạy ổn định, nhưng trong khoảng 10 năm gần đây đã xuất hiện các tính năng thiên về công nghệ như nhận hàng tại lề đường, giao hàng tận nhà, tồn kho thời gian thực và tìm kiếm trong cửa hàng
- Khi mở rộng vận hành chuỗi cung ứng cho hơn 435 cửa hàng và các trung tâm logistics hỗ trợ chúng, các hệ thống cốt lõi hiện có bắt đầu phải đáp ứng những yêu cầu vượt ra ngoài phạm vi thiết kế ban đầu
- Khó khăn cốt lõi của việc hiện đại hóa mainframe không phải là chuyển mã lên cloud, mà là bảo toàn tri thức đặc thù của tổ chức đã tích lũy trong logic nghiệp vụ legacy suốt nhiều thập kỷ
- Việc tách một monolith xử lý hàng trăm bảng thành các microservice do các nhóm kỹ sư tự chủ sở hữu là công việc khổng lồ về cả kỹ thuật lẫn chính trị, và cũng rất tốn kém
Đưa Haskell vào sử dụng và quy mô vận hành
- Các tư vấn viên đề xuất Haskell làm ngôn ngữ chung cho một dự án mới nhằm thay thế ứng dụng mainframe lớn nhất của công ty
- Công việc khởi đầu như một thử nghiệm này đã phát triển thành nhiều hệ thống production cốt lõi cho chuỗi cung ứng
- Sau 8 năm áp dụng, nhiều nhóm ở các domain khác nhau đang bảo trì khoảng 1 triệu dòng mã Haskell
- Quá trình vận hành production lâu dài không chỉ cho thấy kết quả thực tế mà còn bộc lộ những khó khăn kỹ thuật và tổ chức
Lợi ích đạt được trong kinh doanh
- Điều doanh nghiệp muốn không phải bản thân lazy evaluation, pure function hay immutable data, mà là một hệ thống kiểu COBOL thế hệ tiếp theo có thể vận hành 40 năm với chi phí bảo trì thấp mà không gặp null pointer exception
- Ứng dụng Haskell loại bỏ mặc định nhiều loại lỗi runtime thường thấy ở các chương trình mệnh lệnh và dynamic type
- Bug không biến mất hoàn toàn, nhưng các vấn đề còn lại tập trung vào lỗi logic nghiệp vụ hơn là null pointer exception hay nhầm lẫn kiểu dữ liệu
- Trong quá trình chuyển dịch legacy kéo dài nhiều năm theo cách strangler fig, GHC hỗ trợ refactor an toàn
- Có thể sửa đổi lặp đi lặp lại khi yêu cầu còn đang hình thành, nên quá trình chuyển đổi có thể diễn ra theo hướng agile thay vì waterfall
- Doanh nghiệp có thể đo lường kết quả dần dần thay vì trả trước cho một sản phẩm dễ bị đóng khung sai ngay từ đầu
- Parser Haskell cho phép dùng trực tiếp các định dạng legacy dài hàng nghìn dòng như IBM copybook fixed-width làm hợp đồng tích hợp
- Giao diện hàm ngoại (FFI) mạnh mẽ cũng cho phép tích hợp với các dịch vụ cũ chỉ cung cấp thư viện C
- H-E-B đã viết các thư viện Haskell bọc quanh
simdjsonC++, OracleODPI-Cvà IBM MQ
- H-E-B đã viết các thư viện Haskell bọc quanh
Thiếu SDK và quyền sở hữu hợp đồng
- Khi SDK cho Haskell còn thiếu, bạn phải tự tạo và tiếp tục bảo trì client cùng mã tích hợp cần thiết
- Đổi lại, bạn sở hữu trực tiếp hợp đồng tích hợp, tránh phụ thuộc nhà cung cấp và không phải chờ người quản lý dự án upstream phát hành bản sửa lỗi
- Với các dependency cốt lõi, H-E-B cho rằng sự đánh đổi này hoàn toàn xứng đáng
- Coding agent xử lý khá tốt công việc cơ học như chuyển thư viện Go sang Haskell, giúp thu hẹp chênh lệch tốc độ phát triển giữa các ngôn ngữ
- Tuy nhiên, nếu chỉ dịch đơn thuần mà không có prompt đủ tinh vi thì có thể không tận dụng được hết lợi ích của hệ thống kiểu Haskell
Tích hợp đa ngôn ngữ bằng DSL nhúng
- Haskell có thế mạnh làm ngôn ngữ chủ cho DSL nhúng (eDSL)
- Nhóm trung tâm có thể định nghĩa model domain hoặc logic nghiệp vụ một lần, rồi sinh ra mã typed an toàn cho các ngôn ngữ mà nhóm khác sử dụng
- Các nhóm khác có thể tích hợp với hệ thống đó mà không cần trực tiếp viết Haskell
- Hydra là framework DSL mã nguồn mở phát triển từ công việc xử lý dữ liệu đồ thị tại Uber
- Nó được bootstrap từ Haskell và sinh ra các hiện thực tương đương về ngữ nghĩa cho Java, Python, Scala...
- Bộ test dùng chung bảo đảm tính tương đương giữa các hiện thực theo từng ngôn ngữ
- H-E-B đã tạo prototype một DSL Haskell để các nhóm Haskell, TypeScript và Java cùng chia sẻ các phép tính tài chính nhạy cảm
- Họ cũng xây dựng một DSL schema JSON hai chiều
- Tương tự autodocodec, nhưng dùng tagless final encoding để người dùng có thể mở rộng ngữ nghĩa cốt lõi bằng type class riêng
- Nó chuẩn hóa versioning schema, sinh tài liệu và OpenAPI, cũng như regression test và golden test
- Có thể viết các interpreter tạm thời để chuyển JSON schema thành định nghĩa bảng PostgreSQL, hoặc thay
aesonbằngsimdjsonvà áp dụng thêm kiểm tra
Tuyển dụng và văn hóa kỹ thuật
- Các ngôn ngữ hiện đại cũng cung cấp nhiều tính năng về type safety, purity và concurrency chịu ảnh hưởng từ Haskell, nên khác biệt thực sự nằm ở văn hóa kỹ thuật mà ngôn ngữ khuyến khích
- Haskell thu hút những lập trình viên coi trọng tính đúng đắn và thiết kế trừu tượng
- Việc tuyển cho các vị trí Haskell không khó như ban lãnh đạo dự đoán
- Có những ứng viên nộp đơn vì chính Haskell, dù không đặc biệt quan tâm đến chuỗi cung ứng
Giới hạn của ngân sách độ phức tạp
- Haskell là một ngôn ngữ lâu đời và có nhiều khiếm khuyết, nên việc theo kịp các bản phát hành GHC mới là rất quan trọng
- Nếu trong nhóm không có chuyên gia, chỉ một khiếm khuyết của ngôn ngữ cũng có thể lan ra toàn bộ dự án
- Ban đầu, các tư vấn viên giàu kinh nghiệm đã đặt ra guardrail để ngăn việc lạm dụng language extension và toán tử tự định nghĩa
- Trước khi có các language edition như GHC2021 và GHC2024, mỗi nhóm phải tự quản lý bộ extension mặc định của mình
- Điều này đóng vai trò như ngân sách độ phức tạp, giới hạn phạm vi ngôn ngữ mà người mới phải học trước khi có thể đóng góp
- Bản thân dependency cũng tiêu tốn ngân sách độ phức tạp, nên rất khó áp đặt từ cấp tổ chức
servantbiểu diễn API bằng DSL ở cấp kiểu, và việc mở rộng bằng combinator tự định nghĩa đi kèmtype familycùng các lỗi khó hiểuhaxlmô hình hóa mọi nguồn dữ liệu thành GADT được đánh chỉ mục theo kiểu kết quả để gom nhóm tác vụ đồng thời
- Khi lập trình viên càng thành thạo, họ càng dễ bị cám dỗ đưa vào các pattern như GADT,
type family, generic, tagless final, arrow, free structure, higher-kinded data type- Khi những khái niệm này chồng lên trên một domain nghiệp vụ vốn đã phức tạp, lời hứa về khả năng bảo trì dễ dàng sẽ suy yếu
- Do các tính năng nâng cao tăng dần theo thời gian khi đội ngũ quen hơn với hệ sinh thái, sự gia tăng độ phức tạp có thể chỉ lộ ra muộn
Nguyên tắc vận hành của Haskell nhàm chán
- Ở cấp tổ chức, khuyến khích Haskell nhàm chán thì dễ nhưng cưỡng chế thì khó
- Nếu bao bọc Haskell nâng cao phía sau các giao diện hợp lý, có thể giảm phạm vi tác động và giới hạn ngân sách độ phức tạp vào những thư viện nhỏ do một số ít chuyên gia quản lý
- Độ phức tạp ở cấp ngôn ngữ không chỉ là vấn đề của Haskell, nhưng Haskell có phạm vi công cụ nâng cao đặc biệt rộng
- So với số dòng mã hay lượng token, việc có bao nhiêu mã bị vỡ ở lần nâng cấp GHC tiếp theo phản ánh độ phức tạp của một dự án Haskell tốt hơn
Hai dự án không bám rễ được
- Haskell có thể trao quyền cho nhóm này nhưng lại là rào cản với nhóm khác, và tại H-E-B cũng có hai dự án không thể bám rễ với ngôn ngữ này vì những lý do khác nhau
- Ở trường hợp đầu, các lập trình viên giàu kinh nghiệm nhưng không có kinh nghiệm Haskell đã làm theo template và quy ước của dự án từ nhóm khác, nhưng space leak khiến toàn bộ chương trình dừng lại
- Vì không có lập trình viên Haskell đủ mạnh để dẫn dắt, chỉ riêng vấn đề này đã khiến dự án phải dừng
- Ở trường hợp thứ hai, các kỹ sư ủng hộ Haskell và xây dựng ứng dụng đã được chuyển sang dự án khác do thay đổi ưu tiên
- Đây là lý do không liên quan đến bản thân ngôn ngữ
- Cả hai nhóm đều có phương án quay về công nghệ cũ và không tin chắc Haskell giúp tăng năng suất
- Khi đã có lối thoát như template Spring Boot, động lực để theo đuổi việc chẩn đoán một space leak khó nhằn đến cùng có thể sẽ yếu đi
- Khi LLM có thể dịch cơ học toàn bộ codebase, các lựa chọn viết lại càng trở nên rộng mở hơn
- Tuyến phòng thủ dài hạn không phải là đoạn mã quá thông minh đến mức khó viết lại, mà là một đội ngũ vẫn muốn tiếp tục bảo trì Haskell
Sự cố production và chẩn đoán
- Ngay cả ở những nhóm chấp nhận Haskell, nhiều vấn đề nghiêm trọng vẫn từng xảy ra
- Lỗi double-free đã xuất hiện trong cơ chế reference counting tự định nghĩa
- Ở một dự án, để xử lý một space leak bùng phát, nhóm đã phải tắt garbage collection song song
- Vòng lặp vô hạn là loại bug đặc biệt đáng sợ vì ngay cả việc phát hiện ra nó cũng khó
- GHC runtime chưa bao giờ là nguyên nhân gốc rễ của sự cố production tại H-E-B
- Thông lượng và mức dùng bộ nhớ ở trạng thái bình thường đủ sức cạnh tranh với workload của H-E-B; các ca có vấn đề là ngoại lệ chứ không phải thường lệ
- Các kỹ sư đã lần theo core dump do segmentation fault, metric garbage collection và heap profile để giải quyết vấn đề
- Không nên hy sinh độ phủ test chỉ vì có hệ thống kiểu mạnh
- Vì các loại lỗi đơn giản đã bị loại bỏ ở bước biên dịch, ứng dụng Haskell có thể dùng chaos engineering để bộc lộ trực tiếp hơn các lỗi logic và lỗi tài nguyên thực tế
Công cụ phát triển và hệ sinh thái đã thay đổi
- Các hệ thống đầu tiên khởi đầu với GHC 8.2, và vào thời điểm đó phần lớn API client và SDK cho các nền tảng phổ biến đều phải tự viết
- Haskell Language Server chưa tồn tại, GHCup chỉ mới xuất hiện và việc profiling runtime cũng rất khó khăn
- Các lập trình viên quen với Java IDE cảm thấy môi trường công cụ của Haskell bất tiện, và H-E-B thậm chí từng tài trợ phát triển hỗ trợ Debug Adapter Protocol cho người dùng VS Code
- Hiện nay, Haskell cho development ở cấp production không chỉ khả thi mà đã trở nên cạnh tranh
- Bộ công cụ phát triển gồm có HLS,
ghcid,ghciwatch,static-ls, tricorder và các plugin cho editor- Một số dùng plugin GHC để có quy trình phát triển phong phú xoay quanh GHCi, một số khác dùng VS Code và HLS
- Mục tiêu là để lập trình viên được chọn công cụ họ ưa thích
- Về observability, họ dùng
hs-opentelemetryvà opentelemetry-autoopentelemetry-autolà plugin GHC tự động chèn OTel span vào các hàm Haskell- Nó có thể đem lại môi trường instrumentation tương đương với Java hay Python
Thư viện production và cải tiến runtime
- Các service sử dụng các thư viện như
warp,aeson,attoparsec,stm,conduit,servant, orville-postgresql - Nếu hệ sinh thái thiếu tính năng cần thiết, họ sẽ tự phát triển thư viện
- arbiter là hàng đợi công việc PostgreSQL dựa trên transaction, ra đời từ công việc tại H-E-B và hiện đang chạy production
- GHC đang cải thiện khả năng chẩn đoán runtime, hiệu năng compiler, các tính năng giám sát như eventlog streaming và exception annotation
- Từ khi info table profiling được đưa vào GHC 9.2, mối lo về space leak đã giảm đi đáng kể
Cộng đồng nội bộ và phân tán tri thức
- H-E-B rất coi trọng các thư viện nội bộ và khuyến khích lập trình viên tham gia cả dự án nội bộ lẫn mã nguồn mở
- Trong nhiều năm, họ tổ chức họp mặt Haskell hai tuần một lần để các kỹ sư chia sẻ khám phá và đào tạo lẫn nhau
- Muốn giảm silo trong tổ chức doanh nghiệp thì ý tưởng và pattern phải được lưu chuyển giữa các nhóm
- Khi nhiều kỹ sư hơn có thể đọc và sở hữu codebase, rủi ro bus factor do một người nghỉ việc làm lung lay cả dự án sẽ giảm đi
Tiêu chí cho phép dùng tính năng nâng cao
- Có thể đặt các quy tắc mặc định lỏng, nhưng quyết định cuối cùng về việc dùng ngân sách độ phức tạp vào đâu thuộc về tech lead
- Nếu phải mã hóa thông tin định danh cá nhân (PII) được lưu trữ, thì các bảo đảm ở cấp kiểu biến rò rỉ dữ liệu nhạy cảm thành lỗi biên dịch thay vì sự cố runtime có thể đáng để đầu tư
- Nhưng việc hợp nhất bốn hợp đồng API bằng pattern trees-that-grow có thể không đáng để dùng
type familynhằm che giấu schema bên ngoài - Haskell nhàm chán không cấm lập trình ở cấp kiểu hay các tính năng GHC nâng cao, mà đặt ra các quy ước có nguyên tắc rồi đánh giá chi phí của việc lệch khỏi chúng
- Quy ước chính là “không có tác động bất ngờ từ nơi xa”
- Tránh sinh mã bằng
TemplateHaskellvà logic serialization dựa trênGeneric - Viết trực tiếp phần boilerplate để lập trình viên thấy rõ hành vi
- Tuy vậy, không cấm tuyệt đối deriving thông qua quasiquoter hay
Generically
- Tránh sinh mã bằng
- Khi triển khai state machine ở cấp kiểu cho web form, hoặc dùng GADT trong trường hợp ADT thông thường là đủ, cần có sự hoài nghi lành mạnh và phản biện ở cấp nhóm
- Haskell nâng cao tồn tại trong cả thư viện lẫn ứng dụng production, nhưng phải giới hạn phạm vi của các thử nghiệm
- Chính ở quá trình cân bằng giữa công việc và thú vui kỹ thuật mà hoạt động engineering thực sự diễn ra
Tương lai của Haskell và coding agent
- Trong nhiều năm dùng GHC, nhóm đã phát hiện và báo cáo một số ít bug, nhưng chưa có vấn đề nào nghiêm trọng đến mức làm gián đoạn công việc
- Sự cố production rất hiếm, và nguyên nhân phát sinh từ logic nghiệp vụ nhiều hơn hẳn so với lỗi kiểu hay runtime
- Khi tỷ trọng mã do coding agent viết ra tăng lên, điểm nghẽn đang chuyển từ viết mã sang xác minh
- Hệ thống kiểu của GHC không chỉ giúp con người refactor an toàn mà còn cung cấp vòng phản hồi nhanh để từ chối trước khi chạy những đoạn mã do agent sinh ra bằng ảo giác
- Để mở rộng khả năng của Haskell trong công nghiệp, cần chia sẻ thêm nhiều câu chuyện thành công production thực tế
1 bình luận
Ý kiến trên Lobste.rs
Ngoài Texas thì có lẽ không nhiều người biết, nhưng H-E-B là một biểu tượng của Texas, quy mô và tầm ảnh hưởng lớn đến mức có thể xem như một sở ban ngành khác của chính quyền bang
Nhiều cửa hàng có khu làm tortilla, mà chất lượng lại rất tốt so với sản phẩm sản xuất quy mô lớn. Hơn nữa còn dùng cả Haskell
Việc một công ty như H-E-B chọn Haskell để thay thế COBOL thực sự rất đáng ngạc nhiên
Hai câu nói rằng agent lập trình có thể máy móc chuyển thư viện Go sang Haskell nên chênh lệch tốc độ phát triển đã thu hẹp, rồi ngay sau đó lại thừa nhận rằng nếu không có prompt đủ khôn khéo thì có thể không tận dụng được đầy đủ hệ thống kiểu, cho thấy một vấn đề mâu thuẫn
Ngược lại, việc các lỗi vặt bị loại bỏ ngay từ giai đoạn biên dịch, để khi bơm sự cố hạ tầng bằng chaos engineering thì thay vì nhiễu từ ngôn ngữ kiểu động sẽ lộ ra các lỗi logic và tài nguyên thực sự, nghe có vẻ là lý do thuyết phục nhất để cân nhắc Haskell
Tuần trước tôi đã thử cấu hình bản dựng Haskell cô lập trong Bazel nhưng không tìm được giải pháp nào dùng được ngay
rules_haskellvề cơ bản không cô lập theo mặc định, và cách tiếp cận phổ biến dường như phụ thuộc rất nhiều vàonixpkgs. Có vẻ Haskell phụ thuộc vào linker C++ của hệ thống; tôi muốn biết liệu đó có phải tình trạng thực tế hiện nay hay tôi đã bỏ lỡ lựa chọn thay thế nào không